Merge pull request #24 from bsinno/rollout_mgmt

Looks good, merged
This commit is contained in:
Kai Zimmermann
2016-02-04 18:47:35 +01:00
145 changed files with 14001 additions and 353 deletions

View File

@@ -12,6 +12,7 @@ import java.util.concurrent.Executor;
import org.eclipse.hawkbit.eventbus.EventBusSubscriberProcessor;
import org.eclipse.hawkbit.eventbus.EventSubscriber;
import org.eclipse.hawkbit.repository.model.helper.EventBusHolder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -54,4 +55,12 @@ public class EventBusAutoConfiguration {
return new EventBusSubscriberProcessor();
}
/**
* @return the singleton instance of the {@link EventBusHolder}
*/
@Bean
public EventBusHolder eventBusHolder() {
return EventBusHolder.getInstance();
}
}

View File

@@ -45,7 +45,7 @@ spring.mvc.favicon.enabled=false
hawkbit.threadpool.corethreads=5
hawkbit.threadpool.maxthreads=20
hawkbit.threadpool.idletimeout=10000
hawkbit.threadpool.queuesize=250
hawkbit.threadpool.queuesize=20000
# Defines the polling time for the controllers in HH:MM:SS notation
hawkbit.controller.pollingTime=00:05:00

View File

@@ -32,7 +32,6 @@
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>

View File

@@ -0,0 +1,45 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.eventbus.event;
/**
* Abstract event definition class which holds the necessary revsion and tenant
* information which every event needs.
*
* @author Michael Hirsch
* @see AbstractDistributedEvent for events which should be distributed to other
* cluster nodes
*/
public class AbstractEvent implements Event {
private final long revision;
private final String tenant;
/**
* @param revision
* the revision number of the event
* @param tenant
* the tenant of the event
*/
protected AbstractEvent(final long revision, final String tenant) {
this.revision = revision;
this.tenant = tenant;
}
@Override
public long getRevision() {
return revision;
}
@Override
public String getTenant() {
return tenant;
}
}

View File

@@ -17,7 +17,7 @@ import java.net.URI;
*
*
*/
public class CancelTargetAssignmentEvent {
public class CancelTargetAssignmentEvent extends AbstractEvent {
private final String controllerId;
private final Long actionId;
@@ -26,6 +26,10 @@ public class CancelTargetAssignmentEvent {
/**
* Creates a new {@link CancelTargetAssignmentEvent}.
*
* @param revision
* the revision for this event
* @param tenant
* the tenant for this event
* @param controllerId
* the ID of the controller
* @param actionId
@@ -33,7 +37,9 @@ public class CancelTargetAssignmentEvent {
* @param targetAdress
* the targetAdress of the target
*/
public CancelTargetAssignmentEvent(final String controllerId, final Long actionId, final URI targetAdress) {
public CancelTargetAssignmentEvent(final long revision, final String tenant, final String controllerId,
final Long actionId, final URI targetAdress) {
super(revision, tenant);
this.controllerId = controllerId;
this.actionId = actionId;
this.targetAdress = targetAdress;

View File

@@ -174,7 +174,13 @@ public enum SpServerError {
*
*/
SP_REPO_ENTITY_READ_ONLY("hawkbit.server.error.entityreadonly",
"The given entity is read only and the change cannot be completed.");
"The given entity is read only and the change cannot be completed."),
/**
*
*/
SP_ROLLOUT_ILLEGAL_STATE("hawkbit.server.error.rollout.illegalstate",
"The rollout is currently in the wrong state for the current operation");
private final String key;
private final String message;

View File

@@ -0,0 +1,40 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository;
/**
* Describing the fields of the Rollout model which can be used in the REST API
* e.g. for sorting etc.
*
*/
public enum RolloutFields implements FieldNameProvider {
/**
* The name field.
*/
NAME("name"),
/**
* The description field.
*/
DESCRIPTION("description"),
/**
* The id field.
*/
ID("id");
private final String fieldName;
private RolloutFields(final String fieldName) {
this.fieldName = fieldName;
}
@Override
public String getFieldName() {
return fieldName;
}
}

View File

@@ -0,0 +1,40 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository;
/**
* Describing the fields of the RolloutGroup model which can be used in the REST
* API e.g. for sorting etc.
*
*/
public enum RolloutGroupFields implements FieldNameProvider {
/**
* The name field.
*/
NAME("name"),
/**
* The description field.
*/
DESCRIPTION("description"),
/**
* The id field.
*/
ID("id");
private final String fieldName;
private RolloutGroupFields(final String fieldName) {
this.fieldName = fieldName;
}
@Override
public String getFieldName() {
return fieldName;
}
}

View File

@@ -79,8 +79,10 @@ public class AmqpMessageDispatcherService {
downloadAndUpdateRequest.addSoftwareModule(amqpSoftwareModule);
}
final Message message = rabbitTemplate.getMessageConverter().toMessage(downloadAndUpdateRequest,
createConnectorMessageProperties(controllerId, EventTopic.DOWNLOAD_AND_INSTALL));
final Message message = rabbitTemplate.getMessageConverter().toMessage(
downloadAndUpdateRequest,
createConnectorMessageProperties(targetAssignDistributionSetEvent.getTenant(), controllerId,
EventTopic.DOWNLOAD_AND_INSTALL));
sendMessage(targetAdress.getHost(), message);
}
@@ -96,8 +98,10 @@ public class AmqpMessageDispatcherService {
final CancelTargetAssignmentEvent cancelTargetAssignmentDistributionSetEvent) {
final String controllerId = cancelTargetAssignmentDistributionSetEvent.getControllerId();
final Long actionId = cancelTargetAssignmentDistributionSetEvent.getActionId();
final Message message = rabbitTemplate.getMessageConverter().toMessage(actionId,
createConnectorMessageProperties(controllerId, EventTopic.CANCEL_DOWNLOAD));
final Message message = rabbitTemplate.getMessageConverter().toMessage(
actionId,
createConnectorMessageProperties(cancelTargetAssignmentDistributionSetEvent.getTenant(), controllerId,
EventTopic.CANCEL_DOWNLOAD));
sendMessage(cancelTargetAssignmentDistributionSetEvent.getTargetAdress().getHost(), message);
@@ -117,11 +121,12 @@ public class AmqpMessageDispatcherService {
rabbitTemplate.send(message);
}
private MessageProperties createConnectorMessageProperties(final String controllerId, final EventTopic topic) {
private MessageProperties createConnectorMessageProperties(final String tenant, final String controllerId,
final EventTopic topic) {
final MessageProperties messageProperties = createMessageProperties();
messageProperties.setHeader(MessageHeaderKey.TOPIC, topic);
messageProperties.setHeader(MessageHeaderKey.THING_ID, controllerId);
messageProperties.setHeader(MessageHeaderKey.TENANT, tenantAware.getCurrentTenant());
messageProperties.setHeader(MessageHeaderKey.TENANT, tenant);
messageProperties.setHeader(MessageHeaderKey.TYPE, MessageType.EVENT);
return messageProperties;
}

View File

@@ -153,8 +153,8 @@ public class AmqpMessageHandlerService {
final String sha1 = secruityToken.getSha1();
try {
SecurityContextHolder.getContext().setAuthentication(authenticationManager.doAuthenticate(secruityToken));
final LocalArtifact localArtifact = artifactManagement
.findFirstLocalArtifactsBySHA1(secruityToken.getSha1());
final LocalArtifact localArtifact = artifactManagement.findFirstLocalArtifactsBySHA1(secruityToken
.getSha1());
if (localArtifact == null) {
throw new EntityNotFoundException();
}
@@ -177,9 +177,9 @@ public class AmqpMessageHandlerService {
final String downloadId = UUID.randomUUID().toString();
final DownloadArtifactCache downloadCache = new DownloadArtifactCache(DownloadType.BY_SHA1, sha1);
cache.put(downloadId, downloadCache);
authentificationResponse
.setDownloadUrl(UriComponentsBuilder.fromUri(hostnameResolver.resolveHostname().toURI())
.path("/api/v1/downloadserver/downloadId/").path(downloadId).build().toUriString());
authentificationResponse.setDownloadUrl(UriComponentsBuilder
.fromUri(hostnameResolver.resolveHostname().toURI()).path("/api/v1/downloadserver/downloadId/")
.path(downloadId).build().toUriString());
authentificationResponse.setResponseCode(HttpStatus.OK.value());
} catch (final BadCredentialsException | AuthenticationServiceException | CredentialsExpiredException e) {
LOG.error("Login failed", e);
@@ -219,9 +219,9 @@ public class AmqpMessageHandlerService {
}
private static void setTenantSecurityContext(final String tenantId) {
final AnonymousAuthenticationToken authenticationToken = new AnonymousAuthenticationToken(
UUID.randomUUID().toString(), "AMQP-Controller",
Collections.singletonList(new SimpleGrantedAuthority(SpringEvalExpressions.CONTROLLER_ROLE_ANONYMOUS)));
final AnonymousAuthenticationToken authenticationToken = new AnonymousAuthenticationToken(UUID.randomUUID()
.toString(), "AMQP-Controller", Collections.singletonList(new SimpleGrantedAuthority(
SpringEvalExpressions.CONTROLLER_ROLE_ANONYMOUS)));
authenticationToken.setDetails(new TenantAwareAuthenticationDetails(tenantId, true));
setSecurityContext(authenticationToken);
}
@@ -267,8 +267,8 @@ public class AmqpMessageHandlerService {
final DistributionSet distributionSet = action.getDistributionSet();
final List<SoftwareModule> softwareModuleList = controllerManagement
.findSoftwareModulesByDistributionSet(distributionSet);
eventBus.post(new TargetAssignDistributionSetEvent(target.getControllerId(), action.getId(), softwareModuleList,
target.getTargetInfo().getAddress()));
eventBus.post(new TargetAssignDistributionSetEvent(target.getOptLockRevision(), target.getTenant(), target
.getControllerId(), action.getId(), softwareModuleList, target.getTargetInfo().getAddress()));
}
@@ -299,8 +299,8 @@ public class AmqpMessageHandlerService {
private void updateActionStatus(final Message message) {
final ActionUpdateStatus actionUpdateStatus = convertMessage(message, ActionUpdateStatus.class);
final Long actionId = actionUpdateStatus.getActionId();
LOG.debug("Target notifies intermediate about action {} with status {}.", actionId,
actionUpdateStatus.getActionStatus().name());
LOG.debug("Target notifies intermediate about action {} with status {}.", actionId, actionUpdateStatus
.getActionStatus().name());
if (actionId == null) {
logAndThrowMessageError(message, "Invalid message no action id");
@@ -309,8 +309,8 @@ public class AmqpMessageHandlerService {
final Action action = controllerManagement.findActionWithDetails(actionId);
if (action == null) {
logAndThrowMessageError(message,
"Got intermediate notification about action " + actionId + " but action does not exist");
logAndThrowMessageError(message, "Got intermediate notification about action " + actionId
+ " but action does not exist");
}
final ActionStatus actionStatus = new ActionStatus();
@@ -371,8 +371,8 @@ public class AmqpMessageHandlerService {
// back to running action status
} else {
logAndThrowMessageError(message,
"Cancel Recjected message is not allowed, if action is on state: " + action.getStatus());
logAndThrowMessageError(message, "Cancel Recjected message is not allowed, if action is on state: "
+ action.getStatus());
}
}
@@ -387,8 +387,8 @@ public class AmqpMessageHandlerService {
*/
@SuppressWarnings("unchecked")
private <T> T convertMessage(final Message message, final Class<T> clazz) {
message.getMessageProperties().getHeaders().put(AbstractJavaTypeMapper.DEFAULT_CLASSID_FIELD_NAME,
clazz.getTypeName());
message.getMessageProperties().getHeaders()
.put(AbstractJavaTypeMapper.DEFAULT_CLASSID_FIELD_NAME, clazz.getTypeName());
return (T) rabbitTemplate.getMessageConverter().fromMessage(message);
}

View File

@@ -87,7 +87,7 @@ public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTestWit
@Description("Verfies that download and install event with no software modul works")
public void testSendDownloadRequesWithEmptySoftwareModules() {
final TargetAssignDistributionSetEvent targetAssignDistributionSetEvent = new TargetAssignDistributionSetEvent(
CONTROLLER_ID, 1l, new ArrayList<SoftwareModule>(), IpUtil.createAmqpUri("mytest"));
1L, "default", CONTROLLER_ID, 1l, new ArrayList<SoftwareModule>(), IpUtil.createAmqpUri("mytest"));
amqpMessageDispatcherService.targetAssignDistributionSet(targetAssignDistributionSetEvent);
final Message sendMessage = createArgumentCapture(targetAssignDistributionSetEvent.getTargetAdress().getHost());
final DownloadAndUpdateRequest downloadAndUpdateRequest = assertDownloadAndInstallMessage(sendMessage);
@@ -100,7 +100,7 @@ public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTestWit
final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
final TargetAssignDistributionSetEvent targetAssignDistributionSetEvent = new TargetAssignDistributionSetEvent(
CONTROLLER_ID, 1l, dsA.getModules(), IpUtil.createAmqpUri("mytest"));
1L, "default", CONTROLLER_ID, 1l, dsA.getModules(), IpUtil.createAmqpUri("mytest"));
amqpMessageDispatcherService.targetAssignDistributionSet(targetAssignDistributionSetEvent);
final Message sendMessage = createArgumentCapture(targetAssignDistributionSetEvent.getTargetAdress().getHost());
final DownloadAndUpdateRequest downloadAndUpdateRequest = assertDownloadAndInstallMessage(sendMessage);
@@ -134,7 +134,7 @@ public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTestWit
Mockito.when(rabbitTemplate.convertSendAndReceive(any())).thenReturn(receivedList);
final TargetAssignDistributionSetEvent targetAssignDistributionSetEvent = new TargetAssignDistributionSetEvent(
CONTROLLER_ID, 1l, dsA.getModules(), IpUtil.createAmqpUri("mytest"));
1L, "default", CONTROLLER_ID, 1l, dsA.getModules(), IpUtil.createAmqpUri("mytest"));
amqpMessageDispatcherService.targetAssignDistributionSet(targetAssignDistributionSetEvent);
final Message sendMessage = createArgumentCapture(targetAssignDistributionSetEvent.getTargetAdress().getHost());
final DownloadAndUpdateRequest downloadAndUpdateRequest = assertDownloadAndInstallMessage(sendMessage);
@@ -152,7 +152,7 @@ public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTestWit
@Description("Verfies that send cancel event works")
public void testSendCancelRequest() {
final CancelTargetAssignmentEvent cancelTargetAssignmentDistributionSetEvent = new CancelTargetAssignmentEvent(
CONTROLLER_ID, 1l, IpUtil.createAmqpUri("mytest"));
1L, "default", CONTROLLER_ID, 1l, IpUtil.createAmqpUri("mytest"));
amqpMessageDispatcherService
.targetCancelAssignmentToDistributionSet(cancelTargetAssignmentDistributionSetEvent);
final Message sendMessage = createArgumentCapture(

View File

@@ -9,4 +9,39 @@ The repository is in charge for managing the meta data of the update server, e.g
$ mvn clean install
----
Note, in order to build correctly in your IDE, you have to add ./target/generated-sources/apt to your build path.
Note, in order to build correctly in your IDE, you have to add ./target/generated-sources/apt to your build path.
#Concepts
## Rollout
A rollout consists of the distribution set and a target-query-filter which defines the targets to be updated in this rollout. The targets within this filter are split up in configured amount of _Deployment Groups_ at creation time.
When starting a rollout, for all targets within this rollout deployment actions will be created. The deployment actions of the first group will be started immediately all other deployment actions will be scheduled.
> Due rollouts might include a large number of targets and deployment group, creation as well as starting a rollout might take some time and therefore the creation and starting of an rollout is executed asynchronously. The creation and starting progress is reflected by the rollout's status attribute
### Rollout Creation
The targets reflected by the target-query-filter is interpreted at creation time. Only targets which have been created at that time are included in the rollout. Targets which are created after the rollout creation will not be included. At rollout creation time all necessary deployment groups containing the targets will be created. Dynamically adding targets to a created or running rollout is currently not supported.
### Rollout Starting
The rollout is using the same concept based on _deployment acionts_ per target. All necessary _deployment acionts_ will be created at starting point but not all _deployment acionts_ will be set to running. Only the _deployment acionts_ of the first _deployment group_ is set to running, all other actions are created in _scheduled_ state.
If targets having not finished _deployment actions_ at rollout starting time, these action are tried to cancel (state: canceling). Scheduled actions from another rollout are canceled directly on server side because they were never running before and can be safely canceled.
>Multiple rollouts can be running at the same time, but if the rollouts effect the same targets they will interfer the targets _deployment actions_ e.g. canceling/cancel already running/scheduled _deployment actions_.
### Deployment Group Condition/Action
#### Success Condition/Action
A _success condition_ defines when a deployment group is interpreted as success and triggers the _success action_. E.g. a threshold of successfully updated targets within the group.
The _success action_ is executed if the _success_condition_ is fullfilled. Currently only the _success action_ starting the next following deployment group is available.
#### Error Condition/Action
A _error condition_ defines when a deployment group is interpreted as failure and triggers the _error action_. E.g. a threshold of update failures of targets within the group.
The _error action_ is executed if the _error condition_ is fullfilled. Currently only the _error action_ pausing the whole rollout is available, a paused rollout can be resumed by a user.
### Rollout Scheduler
The rollout is managed by a scheduler task which is checking for rollouts in _running_ state. This is done atomic by updating the row in the database, so only the rollouts are checked and processed for the current instance to avoid that multiple instances are processing a rollout.
>Due the scheduler, it might take some time until a rollouts is processed and switching is state or starting the next deployment group.

View File

@@ -11,6 +11,7 @@ package org.eclipse.hawkbit;
import javax.persistence.EntityManager;
import javax.transaction.Transaction;
import org.eclipse.hawkbit.repository.RolloutManagement;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.exception.TenantNotExistException;
import org.eclipse.hawkbit.tenancy.TenantAware;
@@ -46,7 +47,8 @@ public class MultiTenantJpaTransactionManager extends JpaTransactionManager {
if (!definition.getName().startsWith(SystemManagement.class.getCanonicalName() + ".findTenants")
&& !definition.getName().startsWith(SystemManagement.class.getCanonicalName() + ".deleteTenant")
&& !definition.getName()
.startsWith(SystemManagement.class.getCanonicalName() + ".currentTenantKeyGenerator")) {
.startsWith(SystemManagement.class.getCanonicalName() + ".currentTenantKeyGenerator")
&& !definition.getName().startsWith(RolloutManagement.class.getCanonicalName() + ".rolloutScheduler")) {
final String currentTenant = tenantAware.getCurrentTenant();
if (currentTenant == null) {

View File

@@ -13,6 +13,7 @@ import java.util.Map;
import org.eclipse.hawkbit.aspects.ExceptionMappingAspectHandler;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.model.helper.AfterTransactionCommitExecutorHolder;
import org.eclipse.hawkbit.repository.model.helper.CacheManagerHolder;
import org.eclipse.hawkbit.repository.model.helper.PollConfigurationHelper;
import org.eclipse.hawkbit.repository.model.helper.SecurityTokenGeneratorHolder;
@@ -112,6 +113,16 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
return CacheManagerHolder.getInstance();
}
/**
*
* @return the singleton instance of the
* {@link AfterTransactionCommitExecutorHolder}
*/
@Bean
public AfterTransactionCommitExecutorHolder afterTransactionCommitExecutorHolder() {
return AfterTransactionCommitExecutorHolder.getInstance();
}
/**
* Defines the validation processor bean.
*

View File

@@ -28,6 +28,8 @@ public final class CacheKeys {
* event.
*/
public static final String DOWNLOAD_PROGRESS_PERCENT = "download.progress.percent";
public static final String ROLLOUT_GROUP_CREATED = "rollout.group.created";
public static final String ROLLOUT_GROUP_TOTAL = "rollout.group.total";
/**
* utility class only private constructor.

View File

@@ -9,8 +9,10 @@
package org.eclipse.hawkbit.cache;
import org.eclipse.hawkbit.eventbus.event.DownloadProgressEvent;
import org.eclipse.hawkbit.eventbus.event.RolloutGroupCreatedEvent;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.ActionStatus;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
@@ -70,6 +72,43 @@ public class CacheWriteNotify {
eventBus.post(new DownloadProgressEvent(tenantAware.getCurrentTenant(), statusId, progressPercent));
}
/**
* Writes the {@link CacheKeys#ROLLOUT_GROUP_CREATED} and
* {@link CacheKeys#ROLLOUT_GROUP_TOTAL} into the cache and notfies the
* {@link EventBus} with a {@link RolloutGroupCreatedEvent}.
*
* @param revision
* the revision of the event
* @param rolloutId
* the ID of the rollout the group has been created
* @param rolloutGroupId
* the ID of the rollout group which has been created
* @param totalRolloutGroup
* the total number of rollout groups for this rollout
* @param createdRolloutGroup
* the number of already created groups of the rollout
*/
public void rolloutGroupCreated(final long revision, final Long rolloutId, final Long rolloutGroupId,
final int totalRolloutGroup, final int createdRolloutGroup) {
final Cache cache = cacheManager.getCache(Rollout.class.getName());
final String cacheKeyGroupTotal = CacheKeys.entitySpecificCacheKey(String.valueOf(rolloutId),
CacheKeys.ROLLOUT_GROUP_TOTAL);
final String cacheKeyGroupCreated = CacheKeys.entitySpecificCacheKey(String.valueOf(rolloutId),
CacheKeys.ROLLOUT_GROUP_CREATED);
if (createdRolloutGroup < totalRolloutGroup) {
cache.put(cacheKeyGroupTotal, totalRolloutGroup);
cache.put(cacheKeyGroupCreated, createdRolloutGroup);
} else {
// in case we reached progress 100 delete the cache value again
// because otherwise he will keep there forever
cache.evict(cacheKeyGroupTotal);
cache.evict(cacheKeyGroupCreated);
}
eventBus.post(new RolloutGroupCreatedEvent(tenantAware.getCurrentTenant(), revision, rolloutId, rolloutGroupId,
totalRolloutGroup, createdRolloutGroup));
}
/**
* @param cacheManager
* the cacheManager to set

View File

@@ -144,4 +144,5 @@ public class EntityChangeEventListener {
private boolean isTargetInfoNew(final Object targetInfo) {
return ((TargetInfo) targetInfo).isNew();
}
}

View File

@@ -0,0 +1,107 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.eventbus;
import java.util.Map;
import java.util.stream.Collectors;
import org.eclipse.hawkbit.eventbus.event.AbstractPropertyChangeEvent;
import org.eclipse.hawkbit.eventbus.event.ActionCreatedEvent;
import org.eclipse.hawkbit.eventbus.event.ActionPropertyChangeEvent;
import org.eclipse.hawkbit.eventbus.event.RolloutGroupPropertyChangeEvent;
import org.eclipse.hawkbit.eventbus.event.RolloutPropertyChangeEvent;
import org.eclipse.hawkbit.executor.AfterTransactionCommitExecutor;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.BaseEntity;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.helper.AfterTransactionCommitExecutorHolder;
import org.eclipse.hawkbit.repository.model.helper.EventBusHolder;
import org.eclipse.persistence.descriptors.DescriptorEvent;
import org.eclipse.persistence.descriptors.DescriptorEventAdapter;
import org.eclipse.persistence.internal.sessions.ObjectChangeSet;
import org.eclipse.persistence.queries.UpdateObjectQuery;
import org.eclipse.persistence.sessions.changesets.DirectToFieldChangeRecord;
import com.google.common.eventbus.EventBus;
/**
* Listens to change in property values of an entity.
*
*/
public class EntityPropertyChangeListener extends DescriptorEventAdapter {
/*
* (non-Javadoc)
*
* @see
* org.eclipse.persistence.descriptors.DescriptorEventAdapter#postInsert
* (org.eclipse.persistence.descriptors.DescriptorEvent)
*/
@Override
public void postInsert(final DescriptorEvent event) {
if (event.getObject().getClass().equals(Action.class)) {
final Action action = (Action) event.getObject();
if (action.getRollout() != null) {
final EventBus eventBus = getEventBus();
final AfterTransactionCommitExecutor afterCommit = getAfterTransactionCommmitExecutor();
afterCommit.afterCommit(() -> eventBus.post(new ActionCreatedEvent(action)));
}
}
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.persistence.descriptors.DescriptorEventAdapter#postUpdate
* (org.eclipse.persistence.descriptors.DescriptorEvent)
*/
@Override
public void postUpdate(final DescriptorEvent event) {
if (event.getObject().getClass().equals(Action.class)) {
getAfterTransactionCommmitExecutor().afterCommit(
() -> getEventBus()
.post(new ActionPropertyChangeEvent((Action) event.getObject(), getChangeSet(Action.class,
event))));
} else if (event.getObject().getClass().equals(Rollout.class)) {
getAfterTransactionCommmitExecutor().afterCommit(
() -> getEventBus().post(
new RolloutPropertyChangeEvent((Rollout) event.getObject(), getChangeSet(Rollout.class,
event))));
} else if (event.getObject().getClass().equals(RolloutGroup.class)) {
getAfterTransactionCommmitExecutor().afterCommit(
() -> getEventBus().post(
new RolloutGroupPropertyChangeEvent((RolloutGroup) event.getObject(), getChangeSet(
RolloutGroup.class, event))));
}
}
private <T extends BaseEntity> Map<String, AbstractPropertyChangeEvent<T>.Values> getChangeSet(
final Class<T> clazz, final DescriptorEvent event) {
final T rolloutGroup = clazz.cast(event.getObject());
final ObjectChangeSet changeSet = ((UpdateObjectQuery) event.getQuery()).getObjectChangeSet();
return changeSet
.getChanges()
.stream()
.filter(record -> record instanceof DirectToFieldChangeRecord)
.map(record -> (DirectToFieldChangeRecord) record)
.collect(
Collectors.toMap(record -> record.getAttribute(), record -> new AbstractPropertyChangeEvent<T>(
rolloutGroup, null).new Values(record.getOldValue(), record.getNewValue())));
}
private AfterTransactionCommitExecutor getAfterTransactionCommmitExecutor() {
return AfterTransactionCommitExecutorHolder.getInstance().getAfterCommit();
}
private EventBus getEventBus() {
return EventBusHolder.getInstance().getEventBus();
}
}

View File

@@ -0,0 +1,83 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.eventbus.event;
import java.util.Map;
import org.eclipse.hawkbit.repository.model.BaseEntity;
/**
* Property change event.
*
* @param <E>
*/
public class AbstractPropertyChangeEvent<E extends BaseEntity> extends AbstractBaseEntityEvent<E> {
private static final long serialVersionUID = -3671601415138242311L;
private final transient Map<String, Values> changeSet;
/**
* Initialize base entity and property changed with old and new value.
*
* @param baseEntity
* entity changed
* @param changeSetValues
* details of properties changed and old value and new value of
* the changed properties
*/
public AbstractPropertyChangeEvent(final E baseEntity, final Map<String, Values> changeSetValues) {
super(baseEntity);
this.changeSet = changeSetValues;
}
/**
* @return the changeSet
*/
public Map<String, Values> getChangeSet() {
return changeSet;
}
/**
* Carries old value and new value of a property .
*
*/
public class Values {
private final Object oldValue;
private final Object newValue;
/**
* Initialize old value and new changes value of property.
*
* @param oldValue
* old value before change
* @param newValue
* new value after change
*/
public Values(final Object oldValue, final Object newValue) {
super();
this.oldValue = oldValue;
this.newValue = newValue;
}
/**
* @return the oldValue
*/
public Object getOldValue() {
return oldValue;
}
/**
* @return the newValue
*/
public Object getNewValue() {
return newValue;
}
}
}

View File

@@ -0,0 +1,26 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.eventbus.event;
import org.eclipse.hawkbit.repository.model.Action;
/**
* Defines the {@link AbstractBaseEntityEvent} of creating a new {@link Action}.
*/
public class ActionCreatedEvent extends AbstractBaseEntityEvent<Action> {
private static final long serialVersionUID = 181780358321768629L;
/**
* @param action
*/
public ActionCreatedEvent(final Action action) {
super(action);
}
}

View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.eventbus.event;
import java.util.Map;
import org.eclipse.hawkbit.repository.model.Action;
/**
* Defines the {@link AbstractPropertyChangeEvent} of {@link Action}.
*/
public class ActionPropertyChangeEvent extends AbstractPropertyChangeEvent<Action> {
private static final long serialVersionUID = 181780358321768629L;
/**
* @param action
* @param changeSetValues
*/
public ActionPropertyChangeEvent(final Action action,
final Map<String, AbstractPropertyChangeEvent<Action>.Values> changeSetValues) {
super(action, changeSetValues);
}
}

View File

@@ -0,0 +1,185 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.eventbus.event;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.hawkbit.eventbus.EventSubscriber;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import com.google.common.eventbus.AllowConcurrentEvents;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
/**
* Collects and merges fine grained events to more generic events and push them
* in a fixed delay on the events bus. This helps for code which are not
* interested in all fine grained events, e.g. UI code. The UI code is not
* interested in handling the flood of events, so collecting the events and
* merge them to one event together and post them in a fixed interval is easier
* to consume e.g. for push notifcations on UI.
*
* @author Michael Hirsch
*
*/
@EventSubscriber
public class EventMerger {
private static final Set<RolloutEventKey> rolloutEvents = ConcurrentHashMap.newKeySet();
private static final Set<RolloutEventKey> rolloutGroupEvents = ConcurrentHashMap.newKeySet();
@Autowired
private EventBus eventBus;
/**
* Checks if there are events to publish in the fixed interval.
*/
@Scheduled(initialDelay = 10000, fixedDelay = 2000)
public void rolloutEventScheduler() {
final Iterator<RolloutEventKey> rolloutIterator = rolloutEvents.iterator();
while (rolloutIterator.hasNext()) {
final RolloutEventKey eventKey = rolloutIterator.next();
eventBus.post(new RolloutChangeEvent(1, eventKey.tenant, eventKey.rolloutId));
rolloutIterator.remove();
}
final Iterator<RolloutEventKey> rolloutGroupIterator = rolloutGroupEvents.iterator();
while (rolloutGroupIterator.hasNext()) {
final RolloutEventKey eventKey = rolloutGroupIterator.next();
eventBus.post(new RolloutGroupChangeEvent(1, eventKey.tenant, eventKey.rolloutId, eventKey.rolloutGroupId));
rolloutGroupIterator.remove();
}
}
/**
* Called by the event bus to retrieve all necessary events to collect and
* merge.
*
* @param event
* the event on the event bus
*/
@Subscribe
@AllowConcurrentEvents
public void onEvent(final Event event) {
Long rolloutId = null;
Long rolloutGroupId = null;
if (event instanceof ActionCreatedEvent) {
rolloutId = getRolloutId(((ActionCreatedEvent) event).getEntity().getRollout());
rolloutGroupId = getRolloutGroupId(((ActionCreatedEvent) event).getEntity().getRolloutGroup());
} else if (event instanceof ActionPropertyChangeEvent) {
rolloutId = getRolloutId(((ActionPropertyChangeEvent) event).getEntity().getRollout());
rolloutGroupId = getRolloutGroupId(((ActionPropertyChangeEvent) event).getEntity().getRolloutGroup());
} else if (event instanceof RolloutPropertyChangeEvent) {
rolloutId = ((RolloutPropertyChangeEvent) event).getEntity().getId();
} else if (event instanceof RolloutGroupCreatedEvent) {
rolloutId = ((RolloutGroupCreatedEvent) event).getRolloutId();
rolloutGroupId = ((RolloutGroupCreatedEvent) event).getRolloutGroupId();
} else if (event instanceof RolloutGroupPropertyChangeEvent) {
final RolloutGroup rolloutGroup = ((RolloutGroupPropertyChangeEvent) event).getEntity();
rolloutId = rolloutGroup.getRollout().getId();
rolloutGroupId = rolloutGroup.getId();
}
if (rolloutId != null) {
rolloutEvents.add(new RolloutEventKey(rolloutId, event.getTenant()));
if (rolloutGroupId != null) {
rolloutGroupEvents.add(new RolloutEventKey(rolloutId, rolloutGroupId, event.getTenant()));
}
}
}
private Long getRolloutGroupId(final RolloutGroup rolloutGroup) {
if (rolloutGroup != null) {
return rolloutGroup.getId();
}
return null;
}
private Long getRolloutId(final Rollout rollout) {
if (rollout != null) {
return rollout.getId();
}
return null;
}
/**
* The rollout key in the concurrent set to be hold.
*
* @author Michael Hirsch
*
*/
private static final class RolloutEventKey {
private final Long rolloutId;
private final String tenant;
private final Long rolloutGroupId;
private RolloutEventKey(final Long rolloutId, final Long rolloutGroupId, final String tenant) {
this.rolloutGroupId = rolloutGroupId;
this.rolloutId = rolloutId;
this.tenant = tenant;
}
private RolloutEventKey(final Long rolloutId, final String tenant) {
this(rolloutId, null, tenant);
}
@Override
public int hashCode() {// NOSONAR - as this is generated
final int prime = 31;
int result = 1;
result = prime * result + ((rolloutGroupId == null) ? 0 : rolloutGroupId.hashCode());
result = prime * result + ((rolloutId == null) ? 0 : rolloutId.hashCode());
result = prime * result + ((tenant == null) ? 0 : tenant.hashCode());
return result;
}
@Override
public boolean equals(final Object obj) {// NOSONAR - as this is
// generated
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final RolloutEventKey other = (RolloutEventKey) obj;
if (rolloutGroupId == null) {
if (other.rolloutGroupId != null) {
return false;
}
} else if (!rolloutGroupId.equals(other.rolloutGroupId)) {
return false;
}
if (rolloutId == null) {
if (other.rolloutId != null) {
return false;
}
} else if (!rolloutId.equals(other.rolloutId)) {
return false;
}
if (tenant == null) {
if (other.tenant != null) {
return false;
}
} else if (!tenant.equals(other.tenant)) {
return false;
}
return true;
}
}
}

View File

@@ -0,0 +1,40 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.eventbus.event;
import org.eclipse.hawkbit.eventbus.event.AbstractEvent;
/**
* Event declaration for the UI to notify the UI that a rollout has been
* changed.
*
* @author Michael Hirsch
*
*/
public class RolloutChangeEvent extends AbstractEvent {
private final Long rolloutId;
/**
* @param revision
* the revision of the event
* @param tenant
* the tenant of the event
* @param rolloutId
* the ID of the rollout which has been changed
*/
public RolloutChangeEvent(final long revision, final String tenant, final Long rolloutId) {
super(revision, tenant);
this.rolloutId = rolloutId;
}
public Long getRolloutId() {
return rolloutId;
}
}

View File

@@ -0,0 +1,47 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.eventbus.event;
/**
* Event declaration for the UI to notify the UI that a rollout has been
* changed.
*
* @author Michael Hirsch
*
*/
public class RolloutGroupChangeEvent extends AbstractEvent {
private final Long rolloutId;
private final Long rolloutGroupId;
/**
* @param revision
* the revision of the event
* @param tenant
* the tenant of the event
* @param rolloutId
* the ID of the rollout which has been changed
* @param rolloutGroupId
* the ID of the rollout group which has been changed
*/
public RolloutGroupChangeEvent(final long revision, final String tenant, final Long rolloutId,
final Long rolloutGroupId) {
super(revision, tenant);
this.rolloutId = rolloutId;
this.rolloutGroupId = rolloutGroupId;
}
public Long getRolloutId() {
return rolloutId;
}
public Long getRolloutGroupId() {
return rolloutGroupId;
}
}

View File

@@ -0,0 +1,65 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.eventbus.event;
/**
* Event definition which is been published in case a rollout group has been
* created for a specific rollout.
*
* @author Michael Hirsch
*
*/
public class RolloutGroupCreatedEvent extends AbstractDistributedEvent {
private static final long serialVersionUID = 1L;
private final Long rolloutId;
private final Long rolloutGroupId;
private final int totalRolloutGroup;
private final int createdRolloutGroup;
/**
* Creating a new rollout group created event for a specific rollout.
*
* @param tenant
* the tenant of this event
* @param revision
* the revision of the event
* @param rolloutId
* the ID of the rollout the group has been created
* @param totalRolloutGroup
* the total number of rollout groups for this rollout
* @param createdRolloutGroup
* the number of already created groups of the rollout
*/
public RolloutGroupCreatedEvent(final String tenant, final long revision, final Long rolloutId,
final Long rolloutGroupId, final int totalRolloutGroup, final int createdRolloutGroup) {
super(revision, tenant);
this.rolloutId = rolloutId;
this.rolloutGroupId = rolloutGroupId;
this.totalRolloutGroup = totalRolloutGroup;
this.createdRolloutGroup = createdRolloutGroup;
}
public Long getRolloutId() {
return rolloutId;
}
public int getTotalRolloutGroup() {
return totalRolloutGroup;
}
public int getCreatedRolloutGroup() {
return createdRolloutGroup;
}
public Long getRolloutGroupId() {
return rolloutGroupId;
}
}

View File

@@ -0,0 +1,32 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.eventbus.event;
import java.util.Map;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
/**
* Defines the {@link AbstractPropertyChangeEvent} of {@link RolloutGroup}.
*/
public class RolloutGroupPropertyChangeEvent extends AbstractPropertyChangeEvent<RolloutGroup> {
private static final long serialVersionUID = 4026477044419472686L;
/**
*
* @param rolloutGroup
* @param changeSetValues
*/
public RolloutGroupPropertyChangeEvent(final RolloutGroup rolloutGroup,
final Map<String, AbstractPropertyChangeEvent<RolloutGroup>.Values> changeSetValues) {
super(rolloutGroup, changeSetValues);
}
}

View File

@@ -0,0 +1,31 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.eventbus.event;
import java.util.Map;
import org.eclipse.hawkbit.repository.model.Rollout;
/**
* Defines the {@link AbstractPropertyChangeEvent} of {@link Rollout}.
*/
public class RolloutPropertyChangeEvent extends AbstractPropertyChangeEvent<Rollout> {
private static final long serialVersionUID = 1056221355466373514L;
/**
*
* @param rollout
* @param changeSetValues
*/
public RolloutPropertyChangeEvent(final Rollout rollout,
final Map<String, AbstractPropertyChangeEvent<Rollout>.Values> changeSetValues) {
super(rollout, changeSetValues);
}
}

View File

@@ -19,7 +19,7 @@ import org.eclipse.hawkbit.repository.model.SoftwareModule;
*
*
*/
public class TargetAssignDistributionSetEvent {
public class TargetAssignDistributionSetEvent extends AbstractEvent {
private final Collection<SoftwareModule> softwareModules;
private final String controllerId;
@@ -29,6 +29,10 @@ public class TargetAssignDistributionSetEvent {
/**
* Creates a new {@link TargetAssignDistributionSetEvent}.
*
* @param revision
* the revision of the event
* @param tenant
* the tenant of the event
* @param controllerId
* the ID of the controller
* @param actionId
@@ -38,8 +42,9 @@ public class TargetAssignDistributionSetEvent {
* @param targetAdress
* the targetAdress of the target
*/
public TargetAssignDistributionSetEvent(final String controllerId, final Long actionId,
final Collection<SoftwareModule> softwareModules, final URI targetAdress) {
public TargetAssignDistributionSetEvent(final long revision, final String tenant, final String controllerId,
final Long actionId, final Collection<SoftwareModule> softwareModules, final URI targetAdress) {
super(revision, tenant);
this.controllerId = controllerId;
this.actionId = actionId;
this.softwareModules = softwareModules;

View File

@@ -14,8 +14,6 @@ import org.eclipse.hawkbit.repository.model.Target;
* Defines the {@link AbstractBaseEntityEvent} of creating a new {@link Target}.
*
*
*
*
*/
public class TargetCreatedEvent extends AbstractBaseEntityEvent<Target> {

View File

@@ -12,9 +12,13 @@ import java.util.Collection;
import java.util.List;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
@@ -30,10 +34,6 @@ import org.springframework.transaction.annotation.Transactional;
/**
* {@link Action} repository.
*
*
*
*
*
*/
@Transactional(readOnly = true)
public interface ActionRepository extends BaseEntityRepository<Action, Long>, JpaSpecificationExecutor<Action> {
@@ -187,6 +187,47 @@ public interface ActionRepository extends BaseEntityRepository<Action, Long>, Jp
void setToInactive(@Param("keySet") List<Action> keySet, @Param("targetsIds") List<Long> targetsIds);
/**
* Switches the status of actions from one specific status into another,
* only if the actions are in a specific status. This should be a atomar
* operation.
*
* @param statusToSet
* the new status the actions should get
* @param targetIds
* the IDs of the targets of the actions which are affected
* @param active
* the active flag of the actions which should be affected
* @param currentStatus
* the current status of the actions which are affected
*/
@Modifying
@Transactional
@Query("UPDATE Action a SET a.status = :statusToSet WHERE a.target IN :targetsIds AND a.active = :active AND a.status = :currentStatus AND a.distributionSet.requiredMigrationStep = false")
void switchStatus(@Param("statusToSet") Action.Status statusToSet, @Param("targetsIds") List<Long> targetIds,
@Param("active") boolean active, @Param("currentStatus") Action.Status currentStatus);
/**
* Switches the status of actions from one specific status into another,
* only if the actions are in a specific status. This should be a atomar
* operation.
*
* @param statusToSet
* the new status the actions should get
* @param rollout
* the rollout of the actions which are affected
* @param active
* the active flag of the actions which should be affected
* @param currentStatus
* the current status of the actions which are affected
*/
@Modifying
@Transactional
@Query("UPDATE Action a SET a.status = :statusToSet WHERE a.rollout = :rollout AND a.active = :active AND a.status = :currentStatus")
void switchStatus(@Param("statusToSet") Action.Status statusToSet, @Param("rollout") Rollout rollout,
@Param("active") boolean active, @Param("currentStatus") Action.Status currentStatus);
/**
*
* Retrieves all {@link Action}s which are active and referring to the given
* target Ids and distribution set required migration step.
*
@@ -234,7 +275,139 @@ public interface ActionRepository extends BaseEntityRepository<Action, Long>, Jp
*
* @param distributionSet
* DistributionSet to count the {@link Action}s from
* @return the count of actions referring to the given target
* @return the count of actions referring to the given distributionSet
*/
Long countByDistributionSet(DistributionSet distributionSet);
/**
* Counts all {@link Action}s referring to the given rollout.
*
* @param rollout
* the rollout to count the {@link Action}s from
* @return the count of actions referring to the given rollout
*/
Long countByRollout(Rollout rollout);
/**
* Counts all actions referring to a given rollout and rolloutgroup which
* are currently not in the given status. An in-clause statement does not
* work with the spring-data, so this is specific usecase regarding to the
* rollout-management to find out actions which are not in specific states.
*
* @param rollout
* the rollout the actions are belong to
* @param rolloutGroup
* the rolloutgroup the actions are belong to
* @param notStatus1
* the status the action should not have
* @param notStatus2
* the status the action should not have
* @param notStatus3
* the status the action should not have
* @return the count of actions referring the rollout and rolloutgroup and
* are not in given states
*/
Long countByRolloutAndRolloutGroupAndStatusNotAndStatusNotAndStatusNot(Rollout rollout, RolloutGroup rolloutGroup,
Status notStatus1, Status notStatus2, Status notStatus3);
/**
* Counts all actions referring to a given rollout and rolloutgroup.
*
* @param rollout
* the rollout the actions belong to
* @param rolloutGroup
* the rolloutgroup the actions belong to
* @return the count of actions referring to a rollout and rolloutgroup
*/
Long countByRolloutAndRolloutGroup(Rollout rollout, RolloutGroup rolloutGroup);
/**
* Counts all actions referring to a given rollout, rolloutgroup and status.
*
* @param rolloutId
* the ID of rollout the actions belong to
* @param rolloutGroupId
* the ID rolloutgroup the actions belong to
* @param status
* the status the actions should have
* @return the count of actions referring to a rollout, rolloutgroup and are
* in a given status
*/
Long countByRolloutIdAndRolloutGroupIdAndStatus(Long rolloutId, Long rolloutGroupId, Action.Status status);
/**
* Retrieving all actions referring to a given rollout with a specific
* action as parent reference and a specific status.
*
* Finding all actions of a specific rolloutgroup parent relation.
*
* @param rollout
* the rollout the actions belong to
* @param rolloutGroupParent
* the parent rolloutgroup the actions should reference
* @param actionStatus
* the status the actions have
* @return the actions referring a specific rollout and a specific parent
* rolloutgroup in a specific status
*/
List<Action> findByRolloutAndRolloutGroupParentAndStatus(Rollout rollout, RolloutGroup rolloutGroupParent,
Status actionStatus);
/**
* Retrieves all actions for a specific rollout and in a specific status.
*
* @param rollout
* the rollout the actions beglong to
* @param actionStatus
* the status of the actions
* @return the actions referring a specific rollout an in a specific status
*/
List<Action> findByRolloutAndStatus(Rollout rollout, Status actionStatus);
/**
* Get list of objects which has details of status and count of targets in
* each status in specified rollout.
*
* @param rolloutId
* id of {@link Rollout}
* @return list of objects with status and target count
*/
@Query("SELECT NEW org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus( a.rollout.id, a.status , COUNT(a.target)) FROM Action a WHERE a.rollout.id IN ?1 GROUP BY a.rollout.id,a.status")
List<TotalTargetCountActionStatus> getStatusCountByRolloutId(List<Long> rolloutId);
/**
* Get list of objects which has details of status and count of targets in
* each status in specified rollout.
*
* @param rolloutId
* id of {@link Rollout}
* @return list of objects with status and target count
*/
@Query("SELECT NEW org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus( a.rollout.id, a.status , COUNT(a.target)) FROM Action a WHERE a.rollout.id = ?1 GROUP BY a.rollout.id,a.status")
List<TotalTargetCountActionStatus> getStatusCountByRolloutId(Long rolloutId);
/**
* Get list of objects which has details of status and count of targets in
* each status in specified rollout group.
*
* @param rolloutGroupId
* id of {@link RolloutGroup}
* @return list of objects with status and target count
*/
@Query("SELECT NEW org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus(a.rolloutGroup.id, a.status , COUNT(a.target)) FROM Action a WHERE a.rolloutGroup.id = ?1 GROUP BY a.rolloutGroup.id, a.status")
List<TotalTargetCountActionStatus> getStatusCountByRolloutGroupId(Long rolloutGroupId);
/**
* Get list of objects which has details of status and count of targets in
* each status in specified rollout group.
*
* @param rolloutGroupId
* list of id of {@link RolloutGroup}
* @return list of objects with status and target count
*/
@Query("SELECT NEW org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus(a.rolloutGroup.id, a.status , COUNT(a.target)) FROM Action a WHERE a.rolloutGroup.id IN ?1 GROUP BY a.rolloutGroup.id, a.status")
List<TotalTargetCountActionStatus> getStatusCountByRolloutGroupId(List<Long> rolloutGroupId);
// Asha-ends here
}

View File

@@ -23,7 +23,6 @@ import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.ListJoin;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.validation.constraints.NotNull;
@@ -46,6 +45,9 @@ import org.eclipse.hawkbit.repository.model.Action_;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.DistributionSet_;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.Rollout_;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.repository.model.Target;
@@ -228,7 +230,7 @@ public class DeploymentManagement {
String.format("no %s with id %d found", DistributionSet.class.getSimpleName(), dsID));
}
return assignDistributionSetToTargets(set, targets);
return assignDistributionSetToTargets(set, targets, null, null);
}
/**
@@ -237,8 +239,45 @@ public class DeploymentManagement {
*
* @param dsID
* the ID of the distribution set to assign
* @param targetsWithActionType
* @param targets
* a list of all targets and their action type
* @param rollout
* the rollout for this assignment
* @param rolloutgroup
* the rolloutgroup for this assignment
* @return the assignment result
*
* @throw IncompleteDistributionSetException if mandatory
* {@link SoftwareModuleType} are not assigned as define by the
* {@link DistributionSetType}.
*/
@Modifying
@Transactional
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET)
@CacheEvict(value = { "distributionUsageAssigned" }, allEntries = true)
public DistributionSetAssignmentResult assignDistributionSet(@NotNull final Long dsID,
final List<TargetWithActionType> targets, final Rollout rollout, final RolloutGroup rolloutGroup) {
final DistributionSet set = distributoinSetRepository.findOne(dsID);
if (set == null) {
throw new EntityNotFoundException(
String.format("no %s with id %d found", DistributionSet.class.getSimpleName(), dsID));
}
return assignDistributionSetToTargets(set, targets, rollout, rolloutGroup);
}
/**
* method assigns the {@link DistributionSet} to all {@link Target}s by
* their IDs with a specific {@link ActionType} and {@code forcetime}.
*
* @param dsID
* the ID of the distribution set to assign
* @param targets
* a list of all targets and their action type
* @param rollout
* the rollout for this assignment
* @param rolloutgroup
* the rolloutgroup for this assignment
* @return the assignment result
*
* @throw IncompleteDistributionSetException if mandatory
@@ -246,7 +285,8 @@ public class DeploymentManagement {
* {@link DistributionSetType}.
*/
private DistributionSetAssignmentResult assignDistributionSetToTargets(@NotNull final DistributionSet set,
final List<TargetWithActionType> targetsWithActionType) {
final List<TargetWithActionType> targetsWithActionType, final Rollout rollout,
final RolloutGroup rolloutGroup) {
if (!set.isComplete()) {
throw new IncompleteDistributionSetException(
@@ -293,8 +333,10 @@ public class DeploymentManagement {
final Set<Long> targetIdsCancellList = new HashSet<Long>();
targetIds.forEach(ids -> targetIdsCancellList.addAll(overrideObsoleteUpdateActions(ids)));
// check for old assigments that needs cancelation
// final List<Long> canncelledTargetIds =
// cancel all scheduled actions which are in-active, these actions were
// not active before and the manual assignment which has been done
// cancels the
targetIds.forEach(tIds -> actionRepository.switchStatus(Status.CANCELED, tIds, false, Status.SCHEDULED));
// set assigned distribution set and TargetUpdateStatus
final String currentUser;
@@ -317,6 +359,8 @@ public class DeploymentManagement {
tAction.setStatus(Status.RUNNING);
tAction.setTarget(t);
tAction.setDistributionSet(set);
tAction.setRollout(rollout);
tAction.setRolloutGroup(rolloutGroup);
return tAction;
}).collect(Collectors.toList())).stream()
.collect(Collectors.toMap(a -> a.getTarget().getControllerId(), Function.identity()));
@@ -359,7 +403,7 @@ public class DeploymentManagement {
/**
* Sends the {@link TargetAssignDistributionSetEvent} for a specific target
* to the {@link EventBus}.
*
*
* @param target
* the Target which has been assigned to a distribution set
* @param actionId
@@ -372,14 +416,14 @@ public class DeploymentManagement {
target.getTargetInfo().setUpdateStatus(TargetUpdateStatus.PENDING);
afterCommit.afterCommit(() -> {
eventBus.post(new TargetInfoUpdateEvent(target.getTargetInfo()));
eventBus.post(new TargetAssignDistributionSetEvent(target.getControllerId(), actionId, softwareModules,
target.getTargetInfo().getAddress()));
eventBus.post(new TargetAssignDistributionSetEvent(target.getOptLockRevision(), target.getTenant(),
target.getControllerId(), actionId, softwareModules, target.getTargetInfo().getAddress()));
});
}
/**
* Removes {@link UpdateAction}s that are no longer necessary and sends
* cancellations to the controller.
* cancelations to the controller.
*
* @param myTarget
* to override {@link UpdateAction}s
@@ -390,13 +434,14 @@ public class DeploymentManagement {
// Figure out if there are potential target/action combinations that
// need to be considered
// for cancellation
// for cancelation
final List<Action> activeActions = actionRepository
.findByActiveAndTargetIdInAndActionStatusNotEqualToAndDistributionSetRequiredMigrationStep(targetsIds,
Action.Status.CANCELING);
activeActions.forEach(action -> {
action.setStatus(Status.CANCELING);
// document that the status has been retrieved
actionStatusRepository.save(new ActionStatus(action, Status.CANCELING, System.currentTimeMillis(),
"manual cancelation requested"));
@@ -404,15 +449,20 @@ public class DeploymentManagement {
cancelledTargetIds.add(action.getTarget().getId());
});
actionRepository.save(activeActions);
return cancelledTargetIds;
}
private DistributionSetAssignmentResult assignDistributionSetByTargetId(@NotNull final DistributionSet set,
@NotEmpty final List<String> tIDs, final ActionType actionType, final long forcedTime) {
return assignDistributionSetToTargets(set, tIDs.stream()
.map(t -> new TargetWithActionType(t, actionType, forcedTime)).collect(Collectors.toList()));
.map(t -> new TargetWithActionType(t, actionType, forcedTime)).collect(Collectors.toList()), null,
null);
}
/**
@@ -475,7 +525,6 @@ public class DeploymentManagement {
actionStatusRepository.save(new ActionStatus(myAction, Status.CANCELING, System.currentTimeMillis(),
"manual cancelation requested"));
final Action saveAction = actionRepository.save(myAction);
cancelAssignDistributionSetEvent(target, myAction.getId());
return saveAction;
@@ -488,15 +537,15 @@ public class DeploymentManagement {
/**
* Sends the {@link CancelTargetAssignmentEvent} for a specific target to
* the {@link EventBus}.
*
*
* @param target
* the Target which has been assigned to a distribution set
* @param actionId
* the action id of the assignment
*/
private void cancelAssignDistributionSetEvent(final Target target, final Long actionId) {
afterCommit.afterCommit(() -> eventBus.post(new CancelTargetAssignmentEvent(target.getControllerId(), actionId,
target.getTargetInfo().getAddress())));
afterCommit.afterCommit(() -> eventBus.post(new CancelTargetAssignmentEvent(target.getOptLockRevision(),
target.getTenant(), target.getControllerId(), actionId, target.getTargetInfo().getAddress())));
}
/**
@@ -542,6 +591,111 @@ public class DeploymentManagement {
return actionRepository.save(mergedAction);
}
/**
* Creates an action entry into the action repository. In case of existing
* scheduled actions the scheduled actions gets canceled. A scheduled action
* is created in-active.
*
* @param targets
* the targets to create scheduled actions for
* @param distributionSet
* the distribution set for the actions
* @param actionType
* the action type for the action
* @param forcedTime
* the forcedTime of the action
* @param rollout
* the rollout for this action
* @param rolloutGroup
* the rolloutgroup for this action
*/
@Modifying
@Transactional
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET)
public void createScheduledAction(final List<Target> targets, final DistributionSet distributionSet,
final ActionType actionType, final long forcedTime, final Rollout rollout,
final RolloutGroup rolloutGroup) {
// cancel all current scheduled actions for this target. E.g. an action
// is already scheduled and a next action is created then cancel the
// current scheduled action to cancel. E.g. a new scheduled action is
// created.
final List<Long> targetIds = targets.stream().map(t -> t.getId()).collect(Collectors.toList());
actionRepository.switchStatus(Action.Status.CANCELED, targetIds, false, Action.Status.SCHEDULED);
targets.forEach(target -> {
final Action action = new Action();
action.setTarget(target);
action.setActive(false);
action.setDistributionSet(distributionSet);
action.setActionType(actionType);
action.setForcedTime(forcedTime);
action.setStatus(Status.SCHEDULED);
action.setRollout(rollout);
action.setRolloutGroup(rolloutGroup);
actionRepository.save(action);
});
}
/**
* Starting an action which is scheduled, e.g. in case of rollout a
* scheduled action must be started now.
*
* @param action
* the action to start now.
* @return the action which has been started
*/
@Modifying
@Transactional
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.IS_SYSTEM_CODE)
public Action startScheduledAction(@NotNull final Action action) {
final Action mergedAction = entityManager.merge(action);
final Target mergedTarget = entityManager.merge(action.getTarget());
// check if we need to override running update actions
final Set<Long> overrideObsoleteUpdateActions = overrideObsoleteUpdateActions(
Collections.singletonList(action.getTarget().getId()));
final boolean hasDistributionSetAlreadyAssigned = targetRepository
.count(TargetSpecifications.hasControllerIdAndAssignedDistributionSetIdNot(
Collections.singletonList(mergedTarget.getControllerId()),
action.getDistributionSet().getId())) == 0;
if (hasDistributionSetAlreadyAssigned) {
// the target has already the distribution set assigned, we don't
// need to start the scheduled action, just finished it.
mergedAction.setStatus(Status.FINISHED);
mergedAction.setActive(false);
return actionRepository.save(mergedAction);
}
mergedAction.setActive(true);
mergedAction.setStatus(Status.RUNNING);
final Action savedAction = actionRepository.save(mergedAction);
final ActionStatus actionStatus = new ActionStatus();
actionStatus.setAction(action);
actionStatus.setOccurredAt(action.getCreatedAt());
actionStatus.setStatus(Status.RUNNING);
actionStatusRepository.save(actionStatus);
mergedTarget.setAssignedDistributionSet(action.getDistributionSet());
final TargetInfo targetInfo = mergedTarget.getTargetInfo();
targetInfo.setUpdateStatus(TargetUpdateStatus.PENDING);
targetRepository.save(mergedTarget);
targetInfoRepository.save(targetInfo);
// in case we canceled an action before for this target, then don't fire
// assignment event
if (!overrideObsoleteUpdateActions.contains(savedAction.getId())) {
final List<SoftwareModule> softwareModules = softwareModuleRepository
.findByAssignedTo(action.getDistributionSet());
// send distribution set assignment event
assignDistributionSetEvent(mergedAction.getTarget(), mergedAction.getId(), softwareModules);
}
return savedAction;
}
/**
* Get the {@link Action} entity for given actionId.
*
@@ -609,13 +763,14 @@ public class DeploymentManagement {
final Root<Action> actionRoot = query.from(Action.class);
final ListJoin<Action, ActionStatus> actionStatusJoin = actionRoot.join(Action_.actionStatus, JoinType.LEFT);
final Join<Action, DistributionSet> actionDsJoin = actionRoot.join(Action_.distributionSet);
final Join<Action, Rollout> actionRolloutJoin = actionRoot.join(Action_.rollout, JoinType.LEFT);
final CriteriaQuery<ActionWithStatusCount> multiselect = query.distinct(true).multiselect(
actionRoot.get(Action_.id), actionRoot.get(Action_.actionType), actionRoot.get(Action_.active),
actionRoot.get(Action_.forcedTime), actionRoot.get(Action_.status), actionRoot.get(Action_.createdAt),
actionRoot.get(Action_.lastModifiedAt), actionDsJoin.get(DistributionSet_.id),
actionDsJoin.get(DistributionSet_.name), actionDsJoin.get(DistributionSet_.version),
cb.count(actionStatusJoin));
cb.count(actionStatusJoin), actionRolloutJoin.get(Rollout_.name));
multiselect.where(cb.equal(actionRoot.get(Action_.target), target));
multiselect.orderBy(cb.desc(actionRoot.get(Action_.id)));
multiselect.groupBy(actionRoot.get(Action_.id));
@@ -640,25 +795,19 @@ public class DeploymentManagement {
public Slice<Action> findActionsByTarget(final Specification<Action> specifiction, final Target target,
final Pageable pageable) {
return actionRepository.findAll(new Specification<Action>() {
@Override
public Predicate toPredicate(final Root<Action> root, final CriteriaQuery<?> query,
final CriteriaBuilder cb) {
return cb.and(specifiction.toPredicate(root, query, cb), cb.equal(root.get(Action_.target), target));
}
}, pageable);
return actionRepository.findAll((Specification<Action>) (root, query, cb) -> cb
.and(specifiction.toPredicate(root, query, cb), cb.equal(root.get(Action_.target), target)), pageable);
}
/**
* Retrieves all {@link Action}s which are referring the given
* {@link Target}.
*
* @param pageable
* page parameters
*
* @param foundTarget
* the target to find assigned actions
* @return the found {@link Action}s
* the target to find actions for
* @param pageable
* the pageable request to limit, sort the actions
* @return a slice of actions found for a specific target
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
public Slice<Action> findActionsByTarget(final Target foundTarget, final Pageable pageable) {
@@ -744,13 +893,8 @@ public class DeploymentManagement {
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
public Long countActionsByTarget(@NotNull final Specification<Action> spec, @NotNull final Target target) {
return actionRepository.count(new Specification<Action>() {
@Override
public Predicate toPredicate(final Root<Action> root, final CriteriaQuery<?> query,
final CriteriaBuilder cb) {
return cb.and(spec.toPredicate(root, query, cb), cb.equal(root.get(Action_.target), target));
}
});
return actionRepository.count((root, query, cb) -> cb.and(spec.toPredicate(root, query, cb),
cb.equal(root.get(Action_.target), target)));
}
/**
@@ -802,7 +946,7 @@ public class DeploymentManagement {
* This method is called, when cancellation has been successful. It sets the
* action to canceled, resets the meta data of the target and in case there
* is a new action this action is triggered.
*
*
* @param action
* the action which is set to canceled
*/
@@ -824,4 +968,40 @@ public class DeploymentManagement {
}
targetManagement.updateTarget(target);
}
/**
* Retrieving all actions referring to a given rollout with a specific
* action as parent reference and a specific status.
*
* Finding all actions of a specific rolloutgroup parent relation.
*
* @param rollout
* the rollout the actions belong to
* @param rolloutGroupParent
* the parent rolloutgroup the actions should reference
* @param actionStatus
* the status the actions have
* @return the actions referring a specific rollout and a specific parent
* rolloutgroup in a specific status
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.IS_SYSTEM_CODE)
public List<Action> findActionsByRolloutGroupParentAndStatus(final Rollout rollout,
final RolloutGroup rolloutGroupParent, final Action.Status actionStatus) {
return actionRepository.findByRolloutAndRolloutGroupParentAndStatus(rollout, rolloutGroupParent, actionStatus);
}
/**
* Retrieves all actions for a specific rollout and in a specific status.
*
* @param rollout
* the rollout the actions beglong to
* @param actionStatus
* the status of the actions
* @return the actions referring a specific rollout an in a specific status
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
public List<Action> findActionsByRolloutAndStatus(final Rollout rollout, final Action.Status actionStatus) {
return actionRepository.findByRolloutAndStatus(rollout, actionStatus);
}
}

View File

@@ -0,0 +1,69 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
/**
* An implementation of the {@link PageRequest} which is offset based by means
* the offset is given and not the page number as in the original
* {@link PageRequest} implemntation where the offset is generated. Due that the
* REST-API is working with {@code offset} and {@code limit} parameter we need
* an offset based page request for JPA.
*
* @author Michael Hirsch
* @since 0.2.2
*
*/
public final class OffsetBasedPageRequest extends PageRequest {
private static final long serialVersionUID = 1L;
private final int offset;
/**
* Creates a new {@link OffsetBasedPageRequest}. Offsets are zero indexed,
* thus providing 0 for {@code offset} will return the first entry.
*
* @param offset
* zero-based offset index.
* @param limit
* the limit of the page to be returned.
*/
public OffsetBasedPageRequest(final int offset, final int limit) {
this(offset, limit, null);
}
/**
* Creates a new {@link OffsetBasedPageRequest}. Offsets are zero indexed,
* thus providing 0 for {@code offset} will return the first entry.
*
* @param offset
* zero-based offset index.
* @param limit
* the limit of the page to be returned.
* @param sort
* sort can be {@literal null}.
*/
public OffsetBasedPageRequest(final int offset, final int limit, final Sort sort) {
super(0, limit, sort);
this.offset = offset;
}
@Override
public int getOffset() {
return offset;
}
@Override
public String toString() {
return "OffsetBasedPageRequest [offset=" + offset + ", getPageSize()=" + getPageSize() + ", getPageNumber()="
+ getPageNumber() + "]";
}
}

View File

@@ -0,0 +1,262 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.persistence.EntityManager;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.ListJoin;
import javax.persistence.criteria.Root;
import javax.validation.constraints.NotNull;
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action_;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.RolloutGroup_;
import org.eclipse.hawkbit.repository.model.RolloutTargetGroup;
import org.eclipse.hawkbit.repository.model.RolloutTargetGroup_;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetWithActionStatus;
import org.eclipse.hawkbit.repository.model.Target_;
import org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus;
import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
/**
* RolloutGroupManagement to control rollout groups. This service secures all
* the functionality based on the {@link PreAuthorize} annotation on methods.
*/
@Validated
@Service
@Transactional(readOnly = true)
public class RolloutGroupManagement {
@Autowired
private RolloutGroupRepository rolloutGroupRepository;
@Autowired
private ActionRepository actionRepository;
@Autowired
private TargetRepository targetRepository;
@Autowired
private EntityManager entityManager;
/**
* Retrieves a single {@link RolloutGroup} by its ID.
*
* @param rolloutGroupId
* the ID of the rollout group to find
* @return the found {@link RolloutGroup} by its ID or {@code null} if it
* does not exists
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
public RolloutGroup findRolloutGroupById(final Long rolloutGroupId) {
return rolloutGroupRepository.findOne(rolloutGroupId);
}
/**
* Retrieves a page of {@link RolloutGroup}s filtered by a given
* {@link Rollout}.
*
* @param rolloutId
* the ID of the rollout to filter the {@link RolloutGroup}s
* @param page
* the page request to sort and limit the result
* @return a page of found {@link RolloutGroup}s
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
public Page<RolloutGroup> findRolloutGroupsByRolloutId(final Long rolloutId, final Pageable page) {
return rolloutGroupRepository.findByRolloutId(rolloutId, page);
}
/**
* Retrieves a page of {@link RolloutGroup}s filtered by a given
* {@link Rollout} and the given {@link Specification}.
*
* @param rolloutId
* the ID of the rollout to filter the {@link RolloutGroup}s
* @param specification
* the specification to filter the result set based on attributes
* of the {@link RolloutGroup}
* @param page
* the page request to sort and limit the result
* @return a page of found {@link RolloutGroup}s
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
public Page<RolloutGroup> findRolloutGroupsByPredicate(final Rollout rollout,
final Specification<RolloutGroup> specification, final Pageable page) {
return rolloutGroupRepository.findAll((root, query, criteriaBuilder) -> criteriaBuilder.and(
criteriaBuilder.equal(root.get(RolloutGroup_.rollout), rollout),
specification.toPredicate(root, query, criteriaBuilder)), page);
}
/**
* Retrieves a page of {@link RolloutGroup}s filtered by a given
* {@link Rollout} with the detailed status.
*
* @param rolloutId
* the ID of the rollout to filter the {@link RolloutGroup}s
* @param page
* the page request to sort and limit the result
* @return a page of found {@link RolloutGroup}s
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
public Page<RolloutGroup> findAllRolloutGroupsWithDetailedStatus(final Long rolloutId, final Pageable page) {
final Page<RolloutGroup> rolloutGroups = rolloutGroupRepository.findByRolloutId(rolloutId, page);
final List<Long> rolloutGroupIds = rolloutGroups.getContent().stream().map(rollout -> rollout.getId())
.collect(Collectors.toList());
final Map<Long, List<TotalTargetCountActionStatus>> allStatesForRollout = getStatusCountItemForRolloutGroup(
rolloutGroupIds);
for (final RolloutGroup rolloutGroup : rolloutGroups) {
final TotalTargetCountStatus totalTargetCountStatus = new TotalTargetCountStatus(
allStatesForRollout.get(rolloutGroup.getId()), rolloutGroup.getTotalTargets());
rolloutGroup.setTotalTargetCountStatus(totalTargetCountStatus);
}
return rolloutGroups;
}
/**
* Get count of targets in different status in rollout group.
*
* @param rolloutGroupId
* rollout group id
* @return rolloutGroup with details of targets count for different statuses
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
public RolloutGroup findRolloutGroupWithDetailedStatus(final Long rolloutGroupId) {
final RolloutGroup rolloutGroup = findRolloutGroupById(rolloutGroupId);
final List<TotalTargetCountActionStatus> rolloutStatusCountItems = actionRepository
.getStatusCountByRolloutGroupId(rolloutGroupId);
final TotalTargetCountStatus totalTargetCountStatus = new TotalTargetCountStatus(rolloutStatusCountItems,
rolloutGroup.getTotalTargets());
rolloutGroup.setTotalTargetCountStatus(totalTargetCountStatus);
return rolloutGroup;
}
private Map<Long, List<TotalTargetCountActionStatus>> getStatusCountItemForRolloutGroup(
final List<Long> rolloutGroupIds) {
final List<TotalTargetCountActionStatus> resultList = actionRepository
.getStatusCountByRolloutGroupId(rolloutGroupIds);
return resultList.stream().collect(Collectors.groupingBy(TotalTargetCountActionStatus::getId));
}
/**
* Get targets of specified rollout group.
*
* @param rolloutGroup
* rollout group
* @param specification
* the specification for filtering the targets of a rollout group
* @param page
* the page request to sort and limit the result
*
* @return Page<Target> list of targets of a rollout group
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
public Page<Target> findRolloutGroupTargets(final RolloutGroup rolloutGroup,
final Specification<Target> specification, final Pageable page) {
return targetRepository.findAll((root, query, criteriaBuilder) -> {
final ListJoin<Target, RolloutTargetGroup> rolloutTargetJoin = root.join(Target_.rolloutTargetGroup);
return criteriaBuilder.and(specification.toPredicate(root, query, criteriaBuilder),
criteriaBuilder.equal(rolloutTargetJoin.get(RolloutTargetGroup_.rolloutGroup), rolloutGroup));
} , page);
}
/**
* Get targets of specified rollout group.
*
* @param rolloutGroup
* rollout group
* @param page
* the page request to sort and limit the result
*
* @return Page<Target> list of targets of a rollout group
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
public Page<Target> findRolloutGroupTargets(@NotNull final RolloutGroup rolloutGroup, final Pageable page) {
if (isRolloutStatusReady(rolloutGroup)) {
// in case of status ready the action has not been created yet and
// the relation information between target and rollout-group is
// stored in the #TargetRolloutGroup.
return targetRepository.findByRolloutTargetGroupRolloutGroupId(rolloutGroup.getId(), page);
}
return targetRepository.findByActionsRolloutGroup(rolloutGroup, page);
}
private boolean isRolloutStatusReady(final RolloutGroup rolloutGroup) {
return rolloutGroup != null && RolloutStatus.READY.equals(rolloutGroup.getRollout().getStatus());
}
/**
*
* Find all targets with action status by rollout group id. The action
* status might be {@code null} if for the target within the rollout no
* actions as been created, e.g. the target already had assigned the same
* distribution set we do not create an action for it but the target is in
* the result list of the rollout-group.
*
* @param pageRequest
* the page request to sort and limit the result
* @param rolloutGroup
* rollout group
* @return {@link TargetWithActionStatus} target with action status
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
public Page<TargetWithActionStatus> findAllTargetsWithActionStatus(final PageRequest pageRequest,
@NotNull final RolloutGroup rolloutGroup) {
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
final CriteriaQuery<Object[]> query = cb.createQuery(Object[].class);
final CriteriaQuery<Long> countQuery = cb.createQuery(Long.class);
final Root<RolloutTargetGroup> targetRoot = query.distinct(true).from(RolloutTargetGroup.class);
final Join<RolloutTargetGroup, Target> targetJoin = targetRoot.join(RolloutTargetGroup_.target);
final ListJoin<RolloutTargetGroup, Action> actionJoin = targetRoot.join(RolloutTargetGroup_.actions,
JoinType.LEFT);
final Root<RolloutTargetGroup> countQueryFrom = countQuery.distinct(true).from(RolloutTargetGroup.class);
countQuery
.select(cb.count(countQueryFrom.join(RolloutTargetGroup_.target).join(Target_.actions, JoinType.LEFT)))
.where(cb.equal(countQueryFrom.get(RolloutTargetGroup_.rolloutGroup), rolloutGroup));
final Long totalCount = entityManager.createQuery(countQuery).getSingleResult();
final CriteriaQuery<Object[]> multiselect = query.multiselect(targetJoin, actionJoin.get(Action_.status))
.where(cb.equal(targetRoot.get(RolloutTargetGroup_.rolloutGroup), rolloutGroup));
final List<TargetWithActionStatus> targetWithActionStatus = entityManager.createQuery(multiselect)
.setFirstResult(pageRequest.getOffset()).setMaxResults(pageRequest.getPageSize()).getResultList()
.stream().map(o -> new TargetWithActionStatus((Target) o[0], (Action.Status) o[1]))
.collect(Collectors.toList());
return new PageImpl<>(targetWithActionStatus, pageRequest, totalCount);
}
}

View File

@@ -0,0 +1,131 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository;
import java.util.List;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Transactional;
/**
* The repository interface for the {@link RolloutGroup} model.
*/
@Transactional(readOnly = true)
public interface RolloutGroupRepository
extends BaseEntityRepository<RolloutGroup, Long>, JpaSpecificationExecutor<RolloutGroup> {
/**
* Retrieves all {@link RolloutGroup} referring a specific rollout in the
* order of creating them. ID ASC.
*
* @param rollout
* the rollout the rolloutgroups belong to
* @return the rollout groups belonging to a rollout ordered by ID ASC.
*/
List<RolloutGroup> findByRolloutOrderByIdAsc(final Rollout rollout);
/**
* Retrieves all {@link RolloutGroup} referring a specific rollout in a
* specific {@link RolloutGroupStatus}.
*
* @param rollout
* the rollout the rolloutgroup belong to
* @param status
* the status of the rollout groups
* @return the rollout groups belonging to a rollout in a specific status
*/
List<RolloutGroup> findByRolloutAndStatus(final Rollout rollout, final RolloutGroupStatus status);
/**
* Counts all {@link RolloutGroup} referring a specific rollout.
*
* @param rollout
* the rollout the rolloutgroup belong to
* @return the count of the rollout groups for a specific rollout
*/
Long countByRollout(final Rollout rollout);
/**
* Counts all {@link RolloutGroup} referring a specific rollout in a
* specific {@link RolloutGroupStatus}.
*
* @param rollout
* the rollout the rolloutgroup belong to
* @param rolloutGroupStatus
* the status of the rollout groups
* @return the count of rollout groups belonging to a rollout in a specific
* status
*/
Long countByRolloutAndStatus(Rollout rollout, RolloutGroupStatus rolloutGroupStatus);
/**
* Counts all {@link RolloutGroup} referring a specific rollout in specific
* {@link RolloutGroupStatus}s. An in-clause statement does not work with
* the spring-data, so this is specific usecase regarding to the
* rollout-management to find out rolloutgroups which are in specific
* states.
*
* @param rollout
* the rollout the rolloutgroup belong to
* @param rolloutGroupStatus1
* the status of the rollout groups
* @param rolloutGroupStatus2
* the status of the rollout groups
* @return the count of rollout groups belonging to a rollout in specific
* status
*/
@Query("SELECT COUNT(r.id) FROM RolloutGroup r WHERE r.rollout = :rollout and (r.status = :status1 or r.status = :status2)")
Long countByRolloutAndStatusOrStatus(@Param("rollout") Rollout rollout,
@Param("status1") RolloutGroupStatus rolloutGroupStatus1,
@Param("status2") RolloutGroupStatus rolloutGroupStatus2);
/**
* Retrieves all {@link RolloutGroup} for a specific parent in a specific
* status. Retrieves the child rolloutgroup for a specific status.
*
* @param rolloutGroup
* the parent rolloutgroup
* @param status
* the status of the rolloutgroups
* @return The child {@link RolloutGroup}s in a specific status
*/
List<RolloutGroup> findByParentAndStatus(RolloutGroup rolloutGroup, RolloutGroupStatus status);
/**
* Retrieves all {@link RolloutGroup} for a specific rollout and status not
* having ordered by ID DESC, latest top.
*
* @param rollout
* the rollout the rolloutgroup belong to
* @param notStatus
* the status which the rolloutgroup should not have
* @return rolloutgroup referring to a rollout and not having a specific
* status ordered by ID DESC.
*/
List<RolloutGroup> findByRolloutAndStatusNotOrderByIdDesc(Rollout rollout, RolloutGroupStatus notStatus);
/**
* Retrieves all {@link RolloutGroup} for a specific rollout.
*
* @param rolloutId
* the ID of the rollout to find the rollout groups
* @param page
* the page request to sort, limit the result
* @return a page of found {@link RolloutGroup} or {@code empty}.
*/
Page<RolloutGroup> findByRolloutId(final Long rolloutId, Pageable page);
}

View File

@@ -0,0 +1,870 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.stream.Collectors;
import javax.persistence.EntityManager;
import javax.validation.constraints.NotNull;
import org.eclipse.hawkbit.cache.CacheWriteNotify;
import org.eclipse.hawkbit.eventbus.event.RolloutGroupCreatedEvent;
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupConditions;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupErrorCondition;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition;
import org.eclipse.hawkbit.repository.model.RolloutTargetGroup;
import org.eclipse.hawkbit.repository.model.Rollout_;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus;
import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus;
import org.eclipse.hawkbit.rollout.condition.RolloutGroupActionEvaluator;
import org.eclipse.hawkbit.rollout.condition.RolloutGroupConditionEvaluator;
import org.hibernate.validator.constraints.NotEmpty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.Assert;
import org.springframework.validation.annotation.Validated;
/**
* RolloutManagement to control rollouts e.g. like creating, starting, resuming
* and pausing rollouts. This service secures all the functionality based on the
* {@link PreAuthorize} annotation on methods.
*/
@Validated
@Service
@EnableScheduling
@Transactional(readOnly = true)
public class RolloutManagement {
private static final Logger LOGGER = LoggerFactory.getLogger(RolloutManagement.class);
@Autowired
private EntityManager entityManager;
@Autowired
private RolloutRepository rolloutRepository;
@Autowired
private TargetManagement targetManagement;
@Autowired
private TargetRepository targetRepository;
@Autowired
private RolloutGroupRepository rolloutGroupRepository;
@Autowired
private DeploymentManagement deploymentManagement;
@Autowired
private RolloutTargetGroupRepository rolloutTargetGroupRepository;
@Autowired
private ActionRepository actionRepository;
@Autowired
private ApplicationContext context;
@Autowired
private NoCountPagingRepository criteriaNoCountDao;
@Autowired
private PlatformTransactionManager txManager;
@Autowired
private CacheWriteNotify cacheWriteNotify;
@Autowired
@Qualifier("asyncExecutor")
private Executor executor;
/*
* set which stores the rollouts which are asynchronously creating. This is
* necessary to verify rollouts which maybe stuck during creationg e.g.
* because of database interruption, failures or even application crash.
* !This is not cluster aware!
*/
private static final Set<String> creatingRollouts = ConcurrentHashMap.newKeySet();
/*
* set which stores the rollouts which are asynchronously starting. This is
* necessary to verify rollouts which maybe stuck during starting e.g.
* because of database interruption, failures or even application crash.
* !This is not cluster aware!
*/
private static final Set<String> startingRollouts = ConcurrentHashMap.newKeySet();
/**
* Retrieves all rollouts.
*
* @param page
* the page request to sort and limit the result
* @return a page of found rollouts
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
public Page<Rollout> findAll(final Pageable page) {
return rolloutRepository.findAll(page);
}
/**
* Retrieves all rollouts found by the given specification.
*
* @param specification
* the specification to filter rollouts
* @param page
* the page request to sort and limit the result
* @return a page of found rollouts
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
public Page<Rollout> findAllWithDetailedStatusByPredicate(final Specification<Rollout> specification,
final Pageable page) {
final Page<Rollout> findAll = rolloutRepository.findAll(specification, page);
setRolloutStatusDetails(findAll);
return findAll;
}
/**
* Retrieves a specific rollout by its ID.
*
* @param rolloutId
* the ID of the rollout to retrieve
* @return the founded rollout or {@code null} if rollout with given ID does
* not exists
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
public Rollout findRolloutById(final Long rolloutId) {
return rolloutRepository.findOne(rolloutId);
}
/**
* Persists a new rollout entity. The filter within the
* {@link Rollout#getTargetFilterQuery()} is used to retrieve the targets
* which are effected by this rollout to create. The targets will then be
* split up into groups. The size of the groups can be defined in the
* {@code groupSize} parameter.
*
* The rollout is not started. Only the preparation of the rollout is done,
* persisting and creating all the necessary groups. The Rollout and the
* groups are persisted in {@link RolloutStatus#READY} and
* {@link RolloutGroupStatus#READY} so they can be started
* {@link #startRollout(Rollout)}.
*
* @param rollout
* the rollout entity to create
* @param amountGroup
* the amount of groups to split the rollout into
* @param conditions
* the rolloutgroup conditions and actions which should be
* applied for each {@link RolloutGroup}
* @return the persisted rollout.
*
* @throws IllegalArgumentException
* in case the given groupSize is zero or lower.
*/
@Transactional
@Modifying
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE)
public Rollout createRollout(final Rollout rollout, final int amountGroup,
final RolloutGroupConditions conditions) {
final Rollout savedRollout = createRollout(rollout, amountGroup);
return createRolloutGroups(amountGroup, conditions, savedRollout);
}
/**
* Persists a new rollout entity. The filter within the
* {@link Rollout#getTargetFilterQuery()} is used to retrieve the targets
* which are effected by this rollout to create. The creation of the rollout
* will be done synchronously and will be returned. The targets will then be
* split up into groups. The size of the groups can be defined in the
* {@code groupSize} parameter.
*
* The creation of the rollout groups is executed asynchronously due it
* might take some time to split up the targets into groups. The creation of
* the {@link RolloutGroup} is published as event
* {@link RolloutGroupCreatedEvent}.
*
* The rollout is in status {@link RolloutStatus#CREATING} until all rollout
* groups has been created and the targets are split up, then the rollout
* will change the status to {@link RolloutStatus#READY}.
*
* The rollout is not started. Only the preparation of the rollout is done,
* persisting and creating all the necessary groups. The Rollout and the
* groups are persisted in {@link RolloutStatus#READY} and
* {@link RolloutGroupStatus#READY} so they can be started
* {@link #startRollout(Rollout)}.
*
* @param rollout
* the rollout to be created
* @param amountGroup
* the number of groups should be created for the rollout and
* split up the targets
* @param conditions
* the rolloutgroup conditions and actions which should be
* applied for each {@link RolloutGroup}
* @return the created rollout entity in state
* {@link RolloutStatus#CREATING}
*/
@Transactional
@Modifying
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE)
public Rollout createRolloutAsync(final Rollout rollout, final int amountGroup,
final RolloutGroupConditions conditions) {
final Rollout savedRollout = createRollout(rollout, amountGroup);
creatingRollouts.add(savedRollout.getName());
executor.execute(() -> {
try {
final DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setName("creatingRollout");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
new TransactionTemplate(txManager, def).execute(status -> {
createRolloutGroups(amountGroup, conditions, savedRollout);
return null;
});
} finally {
creatingRollouts.remove(savedRollout.getName());
}
});
return savedRollout;
}
private Rollout createRollout(final Rollout rollout, final int amountGroup) {
verifyRolloutGroupParameter(amountGroup);
final Long totalTargets = targetManagement.countTargetByTargetFilterQuery(rollout.getTargetFilterQuery());
rollout.setTotalTargets(totalTargets.longValue());
return rolloutRepository.save(rollout);
}
private void verifyRolloutGroupParameter(final int amountGroup) {
if (amountGroup <= 0) {
throw new IllegalArgumentException("the amountGroup must be greater than zero");
} else if (amountGroup > 500) {
throw new IllegalArgumentException("the amountGroup must not be greater than 500");
}
}
/**
* Method for creating rollout groups and calculating group sizes. Group
* sizes are calculated by dividing the total count of targets through the
* amount of given groups. In same cases this will lead to less rollout
* groups than given by client.
*
* @param amountGroup
* the amount of groups
* @param conditions
* the rollout group conditions
* @param savedRollout
* the rollout
* @return the rollout with created groups
*/
private Rollout createRolloutGroups(final int amountGroup, final RolloutGroupConditions conditions,
final Rollout savedRollout) {
int pageIndex = 0;
int groupIndex = 0;
final Long totalCount = savedRollout.getTotalTargets();
final int groupSize = (int) Math.ceil((double) totalCount / (double) amountGroup);
// validate if the amount of groups that will be created are the amount
// of groups that the client what's to have created.
int amountGroupValidated = amountGroup;
final int amountGroupCreation = (int) (Math.ceil((double) totalCount / (double) groupSize));
if (amountGroupCreation == (amountGroup - 1)) {
amountGroupValidated--;
}
RolloutGroup lastSavedGroup = null;
while (pageIndex < totalCount) {
groupIndex++;
final String nameAndDesc = "group-" + groupIndex;
final RolloutGroup group = new RolloutGroup();
group.setName(nameAndDesc);
group.setDescription(nameAndDesc);
group.setRollout(savedRollout);
group.setParent(lastSavedGroup);
group.setSuccessCondition(conditions.getSuccessCondition());
group.setSuccessConditionExp(conditions.getSuccessConditionExp());
group.setErrorCondition(conditions.getErrorCondition());
group.setErrorConditionExp(conditions.getErrorConditionExp());
group.setErrorAction(conditions.getErrorAction());
group.setErrorActionExp(conditions.getErrorActionExp());
final RolloutGroup savedGroup = rolloutGroupRepository.save(group);
final Slice<Target> targetGroup = targetManagement.findTargetsAll(savedRollout.getTargetFilterQuery(),
new OffsetBasedPageRequest(pageIndex, groupSize, new Sort(Direction.ASC, "id")));
savedGroup.setTotalTargets(targetGroup.getContent().size());
lastSavedGroup = savedGroup;
targetGroup
.forEach(target -> rolloutTargetGroupRepository.save(new RolloutTargetGroup(savedGroup, target)));
cacheWriteNotify.rolloutGroupCreated(groupIndex, savedRollout.getId(), savedGroup.getId(),
amountGroupValidated, groupIndex);
pageIndex += groupSize;
}
savedRollout.setStatus(RolloutStatus.READY);
return rolloutRepository.save(savedRollout);
}
/**
* Starts a rollout which has been created. The rollout must be in
* {@link RolloutStatus#READY} state. The according actions will be created
* for each affected target in the rollout. The actions of the first group
* will be started immediately {@link RolloutGroupStatus#RUNNING} as the
* other groups will be {@link RolloutGroupStatus#SCHEDULED} state.
*
* The rollout itself will be then also in {@link RolloutStatus#RUNNING}.
*
* @param rollout
* the rollout to be started
*
* @throws RolloutIllegalStateException
* if given rollout is not in {@link RolloutStatus#READY}. Only
* ready rollouts can be started.
*/
@Transactional
@Modifying
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.IS_SYSTEM_CODE)
public Rollout startRollout(final Rollout rollout) {
final Rollout mergedRollout = entityManager.merge(rollout);
checkIfRolloutCanStarted(rollout, mergedRollout);
return doStartRollout(mergedRollout);
}
/**
* Starts a rollout asynchronously which has been created. The rollout must
* be in {@link RolloutStatus#READY} state. The according actions will be
* created asynchronously for each affected target in the rollout. The
* actions of the first group will be started immediately
* {@link RolloutGroupStatus#RUNNING} as the other groups will be
* {@link RolloutGroupStatus#SCHEDULED} state.
*
* The rollout itself will be then also in {@link RolloutStatus#RUNNING}.
*
* @param rollout
* the rollout to be started
*
* @throws RolloutIllegalStateException
* if given rollout is not in {@link RolloutStatus#READY}. Only
* ready rollouts can be started.
*/
@Transactional
@Modifying
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.IS_SYSTEM_CODE)
public Rollout startRolloutAsync(final Rollout rollout) {
final Rollout mergedRollout = entityManager.merge(rollout);
checkIfRolloutCanStarted(rollout, mergedRollout);
mergedRollout.setStatus(RolloutStatus.STARTING);
final Rollout updatedRollout = rolloutRepository.save(mergedRollout);
startingRollouts.add(updatedRollout.getName());
executor.execute(() -> {
try {
final DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setName("startingRollout");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
new TransactionTemplate(txManager, def).execute(status -> {
doStartRollout(updatedRollout);
return null;
});
} finally {
startingRollouts.remove(updatedRollout.getName());
}
});
return updatedRollout;
}
private Rollout doStartRollout(final Rollout rollout) {
final DistributionSet distributionSet = rollout.getDistributionSet();
final ActionType actionType = rollout.getActionType();
final long forceTime = rollout.getForcedTime();
final List<RolloutGroup> rolloutGroups = rolloutGroupRepository.findByRolloutOrderByIdAsc(rollout);
for (int iGroup = 0; iGroup < rolloutGroups.size(); iGroup++) {
final RolloutGroup rolloutGroup = rolloutGroups.get(iGroup);
final List<Target> targetGroup = targetRepository.findByRolloutTargetGroupRolloutGroup(rolloutGroup);
// firstgroup can already be started
if (iGroup == 0) {
final List<TargetWithActionType> targetsWithActionType = targetGroup.stream()
.map(t -> new TargetWithActionType(t.getControllerId(), actionType, forceTime))
.collect(Collectors.toList());
deploymentManagement.assignDistributionSet(distributionSet.getId(), targetsWithActionType, rollout,
rolloutGroup);
rolloutGroup.setStatus(RolloutGroupStatus.RUNNING);
} else {
// create only not active actions with status scheduled so they
// can be activated later
deploymentManagement.createScheduledAction(targetGroup, distributionSet, actionType, forceTime, rollout,
rolloutGroup);
rolloutGroup.setStatus(RolloutGroupStatus.SCHEDULED);
}
rolloutGroupRepository.save(rolloutGroup);
}
rollout.setStatus(RolloutStatus.RUNNING);
return rolloutRepository.save(rollout);
}
/**
* Pauses a rollout which is currently running. The Rollout switches
* {@link RolloutStatus#PAUSED}. {@link RolloutGroup}s which are currently
* running will be untouched. {@link RolloutGroup}s which are
* {@link RolloutGroupStatus#SCHEDULED} will not be started and keep in
* {@link RolloutGroupStatus#SCHEDULED} state until the rollout is
* {@link RolloutManagement#resumeRollout(Rollout)}.
*
* Switching the rollout status to {@link RolloutStatus#PAUSED} is
* sufficient due the {@link #checkRunningRollouts(long)} will not check
* this rollout anymore.
*
* @param rollout
* the rollout to be paused.
*
* @throws RolloutIllegalStateException
* if given rollout is not in {@link RolloutStatus#RUNNING}.
* Only running rollouts can be paused.
*/
@Transactional
@Modifying
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.IS_SYSTEM_CODE)
public void pauseRollout(final Rollout rollout) {
final Rollout mergedRollout = entityManager.merge(rollout);
if (mergedRollout.getStatus() != RolloutStatus.RUNNING) {
throw new RolloutIllegalStateException("Rollout can only be paused in state running but current state is "
+ rollout.getStatus().name().toLowerCase());
}
// setting the complete rollout only in paused state. This is sufficient
// due the currently running groups will be completed and new groups are
// not started until rollout goes back to running state again. The
// periodically check for running rollouts will skip rollouts in pause
// state.
mergedRollout.setStatus(RolloutStatus.PAUSED);
rolloutRepository.save(mergedRollout);
}
/**
* Resumes a paused rollout. The rollout switches back to
* {@link RolloutStatus#RUNNING} state which is then picked up again by the
* {@link #checkRunningRollouts(long)}.
*
* @param rollout
* the rollout to be resumed
* @throws RolloutIllegalStateException
* if given rollout is not in {@link RolloutStatus#PAUSED}. Only
* paused rollouts can be resumed.
*/
@Transactional
@Modifying
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.IS_SYSTEM_CODE)
public void resumeRollout(final Rollout rollout) {
final Rollout mergedRollout = entityManager.merge(rollout);
if (!(RolloutStatus.PAUSED.equals(mergedRollout.getStatus()))) {
throw new RolloutIllegalStateException("Rollout can only be resumed in state paused but current state is "
+ rollout.getStatus().name().toLowerCase());
}
mergedRollout.setStatus(RolloutStatus.RUNNING);
rolloutRepository.save(mergedRollout);
}
/**
* Checking running rollouts. Rollouts which are checked updating the
* {@link Rollout#setLastCheck(long)} to indicate that the current instance
* is handling the specific rollout. This code should run as system-code.
*
* <pre>
* {@code
* SystemSecurityContext.runAsSystem(new Callable<Void>() {
* public Void call() throws Exception {
* //run system-code
* }
* });
* }
* </pre>
*
* This method is attend to be called by a scheduler.
* {@link RolloutScheduler}. And must be running in an transaction so it's
* splitted from the scheduler.
*
* Rollouts which are currently running are investigated, by means the
* error- and finish condition of running groups in this rollout are
* evaluated.
*
* @param delayBetweenChecks
* the time in milliseconds of the delay between the further and
* this check. This check is only applied if the last check is
* less than (lastcheck-delay).
*/
@Transactional
@Modifying
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.IS_SYSTEM_CODE)
public void checkRunningRollouts(final long delayBetweenChecks) {
verifyStuckedRollouts();
final long lastCheck = System.currentTimeMillis();
final int updated = rolloutRepository.updateLastCheck(lastCheck, delayBetweenChecks, RolloutStatus.RUNNING);
if (updated == 0) {
// nothing to check, maybe another instance already checked in
// between
LOGGER.info("No rolloutcheck necessary for current scheduled check {}, next check at {}", lastCheck,
lastCheck + delayBetweenChecks);
return;
}
final List<Rollout> rolloutsToCheck = rolloutRepository.findByLastCheckAndStatus(lastCheck,
RolloutStatus.RUNNING);
LOGGER.info("Found {} running rollouts to check", rolloutsToCheck.size());
for (final Rollout rollout : rolloutsToCheck) {
LOGGER.debug("Checking rollout {}", rollout);
final List<RolloutGroup> rolloutGroups = rolloutGroupRepository.findByRolloutAndStatus(rollout,
RolloutGroupStatus.RUNNING);
if (rolloutGroups.isEmpty()) {
// no running rollouts, probably there was an error
// somewhere at the latest group. And the latest group has
// been switched from running into error state. So we need
// to find the latest group which
executeLatestRolloutGroup(rollout);
} else {
LOGGER.debug("Rollout {} has {} running groups", rollout.getId(), rolloutGroups.size());
executeRolloutGroups(rollout, rolloutGroups);
}
if (isRolloutComplete(rollout)) {
LOGGER.info("Rollout {} is finished, setting finished status", rollout);
rollout.setStatus(RolloutStatus.FINISHED);
rolloutRepository.save(rollout);
}
}
}
/**
* Verifies and handles stucked rollouts in asynchronous creation or
* starting state. If rollouts are created or started asynchronously it
* might be that they keep in state {@link RolloutStatus#CREATING} or
* {@link RolloutStatus#STARTING} due database or application interruption.
* In case this happens, set the rollout to error state.
*/
private void verifyStuckedRollouts() {
final List<Rollout> rolloutsInCreatingState = rolloutRepository.findByStatus(RolloutStatus.CREATING);
rolloutsInCreatingState.stream().filter(rollout -> !creatingRollouts.contains(rollout.getName()))
.forEach(rollout -> {
LOGGER.warn(
"Determined error during rollout creation of rollout {}, stucking in creating state, setting to status",
rollout, RolloutStatus.ERROR_CREATING);
rollout.setStatus(RolloutStatus.ERROR_CREATING);
rolloutRepository.save(rollout);
});
final List<Rollout> rolloutsInStartingState = rolloutRepository.findByStatus(RolloutStatus.STARTING);
rolloutsInStartingState.stream().filter(rollout -> !startingRollouts.contains(rollout.getName()))
.forEach(rollout -> {
LOGGER.warn(
"Determined error during rollout starting of rollout {}, stucking in starting state, setting to status",
rollout, RolloutStatus.ERROR_STARTING);
rollout.setStatus(RolloutStatus.ERROR_STARTING);
rolloutRepository.save(rollout);
});
}
private void executeRolloutGroups(final Rollout rollout, final List<RolloutGroup> rolloutGroups) {
for (final RolloutGroup rolloutGroup : rolloutGroups) {
// error state check, do we need to stop the whole
// rollout because of error?
final RolloutGroupErrorCondition errorCondition = rolloutGroup.getErrorCondition();
final boolean isError = checkErrorState(rollout, rolloutGroup, errorCondition);
if (isError) {
LOGGER.info("Rollout {} {} has error, calling error action", rollout.getName(), rollout.getId());
callErrorAction(rollout, rolloutGroup);
} else {
// not in error so check finished state, do we need to
// start the next group?
final RolloutGroupSuccessCondition finishedCondition = rolloutGroup.getSuccessCondition();
checkFinishCondition(rollout, rolloutGroup, finishedCondition);
if (isRolloutGroupComplete(rollout, rolloutGroup)) {
rolloutGroup.setStatus(RolloutGroupStatus.FINISHED);
rolloutGroupRepository.save(rolloutGroup);
}
}
}
}
private void executeLatestRolloutGroup(final Rollout rollout) {
final List<RolloutGroup> latestRolloutGroup = rolloutGroupRepository
.findByRolloutAndStatusNotOrderByIdDesc(rollout, RolloutGroupStatus.SCHEDULED);
if (latestRolloutGroup.isEmpty()) {
return;
}
executeRolloutGroupSuccessAction(rollout, latestRolloutGroup.get(0));
}
private void callErrorAction(final Rollout rollout, final RolloutGroup rolloutGroup) {
try {
context.getBean(rolloutGroup.getErrorAction().getBeanName(), RolloutGroupActionEvaluator.class)
.eval(rollout, rolloutGroup, rolloutGroup.getErrorActionExp());
} catch (final BeansException e) {
LOGGER.error("Something bad happend when accessing the error action bean {}",
rolloutGroup.getErrorAction().getBeanName(), e);
}
}
private boolean isRolloutComplete(final Rollout rollout) {
final Long groupsActiveLeft = rolloutGroupRepository.countByRolloutAndStatusOrStatus(rollout,
RolloutGroupStatus.RUNNING, RolloutGroupStatus.SCHEDULED);
return groupsActiveLeft == 0;
}
private boolean isRolloutGroupComplete(final Rollout rollout, final RolloutGroup rolloutGroup) {
final Long actionsLeftForRollout = actionRepository
.countByRolloutAndRolloutGroupAndStatusNotAndStatusNotAndStatusNot(rollout, rolloutGroup,
Action.Status.ERROR, Action.Status.FINISHED, Action.Status.CANCELED);
return actionsLeftForRollout == 0;
}
private boolean checkErrorState(final Rollout rollout, final RolloutGroup rolloutGroup,
final RolloutGroupErrorCondition errorCondition) {
if (errorCondition == null) {
// there is no error condition, so return false, don't have error.
return false;
}
try {
return context.getBean(errorCondition.getBeanName(), RolloutGroupConditionEvaluator.class).eval(rollout,
rolloutGroup, rolloutGroup.getErrorConditionExp());
} catch (final BeansException e) {
LOGGER.error("Something bad happend when accessing the error condition bean {}",
errorCondition.getBeanName(), e);
return false;
}
}
private boolean checkFinishCondition(final Rollout rollout, final RolloutGroup rolloutGroup,
final RolloutGroupSuccessCondition finishCondition) {
LOGGER.trace("Checking finish condition {} on rolloutgroup {}", finishCondition, rolloutGroup);
try {
final boolean isFinished = context
.getBean(finishCondition.getBeanName(), RolloutGroupConditionEvaluator.class)
.eval(rollout, rolloutGroup, rolloutGroup.getSuccessConditionExp());
if (isFinished) {
LOGGER.info("Rolloutgroup {} is finished, starting next group", rolloutGroup);
executeRolloutGroupSuccessAction(rollout, rolloutGroup);
} else {
LOGGER.debug("Rolloutgroup {} is still running", rolloutGroup);
}
return isFinished;
} catch (final BeansException e) {
LOGGER.error("Something bad happend when accessing the finish condition bean {}",
finishCondition.getBeanName(), e);
return false;
}
}
private void executeRolloutGroupSuccessAction(final Rollout rollout, final RolloutGroup rolloutGroup) {
context.getBean(rolloutGroup.getSuccessAction().getBeanName(), RolloutGroupActionEvaluator.class).eval(rollout,
rolloutGroup, rolloutGroup.getSuccessActionExp());
}
/**
* Counts all {@link Target}s in the repository.
*
* @return number of targets
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
public Long countRolloutsAll() {
return rolloutRepository.count();
}
/**
* Count rollouts by specified filter text.
*
* @param searchText
* name or description
* @return total count rollouts for specified filter text.
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
public Long countRolloutsAllByFilters(final String searchText) {
return rolloutRepository.count(likeNameOrDescription(searchText));
}
private static Specification<Rollout> likeNameOrDescription(final String searchText) {
return (rolloutRoot, query, criteriaBuilder) -> {
final String searchTextToLower = searchText.toLowerCase();
return criteriaBuilder.or(
criteriaBuilder.like(criteriaBuilder.lower(rolloutRoot.get(Rollout_.name)), searchTextToLower),
criteriaBuilder.like(criteriaBuilder.lower(rolloutRoot.get(Rollout_.description)),
searchTextToLower));
};
}
/**
* * Retrieves a specific rollout by its ID.
*
* @param pageable
* the page request to sort and limit the result
* @param searchText
* search text which matches name or description of rollout
* @return the founded rollout or {@code null} if rollout with given ID does
* not exists
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
public Slice<Rollout> findRolloutByFilters(final Pageable pageable, @NotEmpty final String searchText) {
final Specification<Rollout> specs = likeNameOrDescription(searchText);
final Slice<Rollout> findAll = criteriaNoCountDao.findAll(specs, pageable, Rollout.class);
setRolloutStatusDetails(findAll);
return findAll;
}
/**
* Retrieves a specific rollout by its name.
*
* @param rolloutName
* the name of the rollout to retrieve
* @return the founded rollout or {@code null} if rollout with given name
* does not exists
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
public Rollout findRolloutByName(final String rolloutName) {
return rolloutRepository.findByName(rolloutName);
}
/**
* Update rollout details.
*
* @param rollout
* rollout to be updated
*
* @return Rollout updated rollout
*/
@NotNull
@Transactional
@Modifying
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE)
public Rollout updateRollout(@NotNull final Rollout rollout) {
Assert.notNull(rollout.getId());
return rolloutRepository.save(rollout);
}
/**
* Get count of targets in different status in rollout.
*
* @param page
* the page request to sort and limit the result
* @return a list of rollouts with details of targets count for different
* statuses
*
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
public Page<Rollout> findAllRolloutsWithDetailedStatus(final Pageable page) {
final Page<Rollout> rollouts = findAll(page);
setRolloutStatusDetails(rollouts);
return rollouts;
}
/**
* Get count of targets in different status in rollout.
*
* @param rolloutId
* rollout id
* @return rollout details of targets count for different statuses
*
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
public Rollout findRolloutWithDetailedStatus(final Long rolloutId) {
final Rollout rollout = findRolloutById(rolloutId);
final List<TotalTargetCountActionStatus> rolloutStatusCountItems = actionRepository
.getStatusCountByRolloutId(rolloutId);
final TotalTargetCountStatus totalTargetCountStatus = new TotalTargetCountStatus(rolloutStatusCountItems,
rollout.getTotalTargets());
rollout.setTotalTargetCountStatus(totalTargetCountStatus);
return rollout;
}
private Map<Long, List<TotalTargetCountActionStatus>> getStatusCountItemForRollout(final List<Long> rolloutIds) {
final List<TotalTargetCountActionStatus> resultList = actionRepository.getStatusCountByRolloutId(rolloutIds);
return resultList.stream().collect(Collectors.groupingBy(TotalTargetCountActionStatus::getId));
}
private void setRolloutStatusDetails(final Slice<Rollout> rollouts) {
final List<Long> rolloutIds = rollouts.getContent().stream().map(rollout -> rollout.getId())
.collect(Collectors.toList());
final Map<Long, List<TotalTargetCountActionStatus>> allStatesForRollout = getStatusCountItemForRollout(
rolloutIds);
for (final Rollout rollout : rollouts) {
final TotalTargetCountStatus totalTargetCountStatus = new TotalTargetCountStatus(
allStatesForRollout.get(rollout.getId()), rollout.getTotalTargets());
rollout.setTotalTargetCountStatus(totalTargetCountStatus);
}
}
private void checkIfRolloutCanStarted(final Rollout rollout, final Rollout mergedRollout) {
if (!(RolloutStatus.READY.equals(mergedRollout.getStatus()))) {
throw new RolloutIllegalStateException("Rollout can only be started in state ready but current state is "
+ rollout.getStatus().name().toLowerCase());
}
}
/***
* Get finished percentage details for a specified group which is in running
* state.
*
* @param rolloutId
* the ID of the {@link Rollout}
* @param rolloutGroup
* the ID of the {@link RolloutGroup}
* @return percentage finished
*/
public float getFinishedPercentForRunningGroup(final Long rolloutId, final RolloutGroup rolloutGroup) {
final Long totalGroup = rolloutGroup.getTotalTargets();
final Long finished = actionRepository.countByRolloutIdAndRolloutGroupIdAndStatus(rolloutId,
rolloutGroup.getId(), Action.Status.FINISHED);
if (totalGroup == 0) {
// in case e.g. targets has been deleted we don't have any actions
// left for this group, so the group is finished
return 100;
}
// calculate threshold
return ((float) finished / (float) totalGroup) * 100;
}
}

View File

@@ -0,0 +1,87 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository;
import java.util.List;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Transactional;
/**
* The repository interface for the {@link Rollout} model.
*/
@Transactional(readOnly = true)
public interface RolloutRepository extends BaseEntityRepository<Rollout, Long>, JpaSpecificationExecutor<Rollout> {
/**
* Updates the {@code lastCheck} field of the {@link Rollout} for rollouts
* in a specific status and only if the {@code lastCheck} is overdue.
*
* @param lastCheck
* the time in milliseconds to set to the lastCheck column
* @param delay
* the delay between last checks
* @param status
* the status which the rollout should have to update the last
* check field
* @return the count of the updated rows. Zero if no row has been updated
*/
@Modifying
@Transactional
@Query("UPDATE Rollout r SET r.lastCheck = :lastCheck WHERE r.lastCheck < (:lastCheck - :delay) AND r.status=:status")
int updateLastCheck(@Param("lastCheck") final long lastCheck, @Param("delay") final long delay,
@Param("status") final RolloutStatus status);
/**
* Retrieves all {@link Rollout} for a specific {@code lastCheck} time and
* for a specific status.
*
* @param lastCheck
* the lastCheck time to find the specific rollout.
* @param status
* the status of the rollout to find
* @return the list of {@link Rollout} for specific lastCheck time and
* status
*/
List<Rollout> findByLastCheckAndStatus(long lastCheck, RolloutStatus status);
/**
* Retrieves all {@link Rollout} for a specific {@code name}
*
* @param name
* the rollout name
* @return {@link Rollout} for specific name
*/
Page<Rollout> findByName(final Pageable pageable, String name);
/**
* Retrieves all {@link Rollout} for a specific {@code name}
*
* @param name
* the rollout name
* @return {@link Rollout} for specific name
*/
Rollout findByName(String name);
/**
* Retrieves all {@link Rollout} for a specific status.
*
* @param status
* the status of the rollouts to retrieve
* @return a list of {@link Rollout} having the given status
*/
List<Rollout> findByStatus(final RolloutStatus status);
}

View File

@@ -0,0 +1,91 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository;
import java.util.List;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Profile;
import org.springframework.core.env.Environment;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
/**
* Scheduler to schedule the
* {@link RolloutManagement#checkRunningRollouts(long)}. The delay between the
* checks be be configured using the property
* {@link #PROP_SCHEDULER_DELAY_PLACEHOLDER}.
*/
@Component
// don't active the rollout scheduler in test, otherwise it is hard to test
// rolloutmanagement and leads weird side-effects maybe.
@Profile("!test")
public class RolloutScheduler implements EnvironmentAware {
private static final Logger logger = LoggerFactory.getLogger(RolloutScheduler.class);
private static final String PROP_SCHEDULER_DELAY = "hawkbit.rollout.scheduler.fixedDelay";
private static final long DEFAULT_SCHEDULER_DELAY = 30000L;
private static final String PROP_SCHEDULER_DELAY_PLACEHOLDER = "${" + PROP_SCHEDULER_DELAY + ":"
+ DEFAULT_SCHEDULER_DELAY + "}";
@Autowired
private TenantAware tenantAware;
@Autowired
private SystemManagement systemManagement;
@Autowired
private RolloutManagement rolloutManagement;
@Autowired
private SystemSecurityContext systemSecurityContext;
private long fixedDelay = DEFAULT_SCHEDULER_DELAY;
/**
* Scheduler method called by the spring-async mechanism. Retrieves all
* tenants from the {@link SystemManagement#findTenants()} and runs for each
* tenant the {@link RolloutManagement#checkRunningRollouts(long)} in the
* {@link SystemSecurityContext}.
*/
@Scheduled(initialDelayString = PROP_SCHEDULER_DELAY_PLACEHOLDER, fixedDelayString = PROP_SCHEDULER_DELAY_PLACEHOLDER)
public void rolloutScheduler() {
logger.debug("rollout schedule checker has been triggered.");
// run this code in system code privileged to have the necessary
// permission to query and create entities.
systemSecurityContext.runAsSystem(() -> {
// workaround eclipselink that is currently not possible to
// execute a query without multitenancy if MultiTenant
// annotation is used.
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=355458. So
// iterate through all tenants and execute the rollout check for
// each tenant seperately.
final List<String> tenants = systemManagement.findTenants();
logger.info("Checking rollouts for {} tenants", tenants.size());
for (final String tenant : tenants) {
tenantAware.runAsTenant(tenant, () -> {
rolloutManagement.checkRunningRollouts(fixedDelay);
return null;
});
}
return null;
});
}
@Override
public void setEnvironment(final Environment environment) {
fixedDelay = environment.getProperty(PROP_SCHEDULER_DELAY, Long.class, DEFAULT_SCHEDULER_DELAY);
}
}

View File

@@ -0,0 +1,22 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository;
import org.eclipse.hawkbit.repository.model.RolloutTargetGroup;
import org.eclipse.hawkbit.repository.model.RolloutTargetGroupId;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.CrudRepository;
/**
*
*
*/
public interface RolloutTargetGroupRepository
extends CrudRepository<RolloutTargetGroup, RolloutTargetGroupId>, JpaSpecificationExecutor<RolloutTargetGroup> {
}

View File

@@ -96,6 +96,12 @@ public class SystemManagement implements EnvironmentAware {
@Autowired
private TenantConfigurationRepository tenantConfigurationRepository;
@Autowired
private RolloutRepository rolloutRepository;
@Autowired
private RolloutGroupRepository rolloutGroupRepository;
@Autowired
private TenantAware tenantAware;
@@ -209,7 +215,8 @@ public class SystemManagement implements EnvironmentAware {
* @return list of all tenant names in the system.
*/
@NotNull
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_SYSTEM_ADMIN)
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_SYSTEM_ADMIN + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.IS_SYSTEM_CODE)
// tenant independent
public List<String> findTenants() {
return tenantMetaDataRepository.findAll().stream().map(md -> md.getTenant()).collect(Collectors.toList());
@@ -234,11 +241,13 @@ public class SystemManagement implements EnvironmentAware {
tenantMetaDataRepository.deleteByTenantIgnoreCase(tenant);
tenantConfigurationRepository.deleteByTenantIgnoreCase(tenant);
targetRepository.deleteByTenantIgnoreCase(tenant);
actionRepository.deleteByTenantIgnoreCase(tenant);
rolloutGroupRepository.deleteByTenantIgnoreCase(tenant);
rolloutRepository.deleteByTenantIgnoreCase(tenant);
artifactRepository.deleteByTenantIgnoreCase(tenant);
externalArtifactRepository.deleteByTenantIgnoreCase(tenant);
externalArtifactProviderRepository.deleteByTenantIgnoreCase(tenant);
targetTagRepository.deleteByTenantIgnoreCase(tenant);
actionRepository.deleteByTenantIgnoreCase(tenant);
distributionSetTagRepository.deleteByTenantIgnoreCase(tenant);
distributionSetRepository.deleteByTenantIgnoreCase(tenant);
distributionSetTypeRepository.deleteByTenantIgnoreCase(tenant);

View File

@@ -12,10 +12,12 @@ import java.util.Collection;
import java.util.List;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.Tag;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetTag;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.repository.model.TargetWithActionStatus;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
@@ -278,4 +280,43 @@ public interface TargetRepository extends BaseEntityRepository<Target, Long>, Jp
@Query("UPDATE Target t SET t.assignedDistributionSet = :set, t.lastModifiedAt = :lastModifiedAt, t.lastModifiedBy = :lastModifiedBy WHERE t.id IN :targets")
void setAssignedDistributionSet(@Param("set") DistributionSet set, @Param("lastModifiedAt") Long modifiedAt,
@Param("lastModifiedBy") String modifiedBy, @Param("targets") Collection<Long> targets);
List<Target> findByRolloutTargetGroupRolloutGroup(final RolloutGroup rolloutGroup);
/**
*
* Finds all targets of a rollout group.
*
* @param rolloutGroupId
* the ID of the rollout group
* @param page
* the page request parameter
* @return a page of all targets related to a rollout group
*/
Page<Target> findByRolloutTargetGroupRolloutGroupId(final Long rolloutGroupId, Pageable page);
/**
* Finds all targets related to a target rollout group stored for a specific
* rollout.
*
* @param rolloutGroup
* the rollout group the targets should belong to
* @param page
* the page request parameter
* @return a page of all targets related to a rollout group
*/
Page<Target> findByActionsRolloutGroup(RolloutGroup rolloutGroup, Pageable page);
/**
* Find all targets with action status for a specific group.
*
* @param pageable
* the page request parameter
* @param rolloutGroupId
* the ID of the rollout group
* @return targets with action status
*/
@Query("select DISTINCT NEW org.eclipse.hawkbit.repository.model.TargetWithActionStatus(a.target,a.status) from Action a inner join fetch a.target t where a.rolloutGroup.id = :rolloutGroupId")
Page<TargetWithActionStatus> findTargetsWithActionStatusByRolloutGroupId(final Pageable pageable,
@Param("rolloutGroupId") Long rolloutGroupId);
}

View File

@@ -0,0 +1,63 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.exception;
import org.eclipse.hawkbit.exception.SpServerError;
import org.eclipse.hawkbit.exception.SpServerRtException;
/**
* the {@link RolloutIllegalStateException} is thrown when a rollout is changing
* it's state which is not valid. E.g. trying to start a already running
* rollout, or trying to resume a already finished rollout.
*
*/
public class RolloutIllegalStateException extends SpServerRtException {
private static final long serialVersionUID = 1L;
private static final SpServerError THIS_ERROR = SpServerError.SP_ROLLOUT_ILLEGAL_STATE;
/**
* Default constructor.
*/
public RolloutIllegalStateException() {
super(THIS_ERROR);
}
/**
* Parameterized constructor.
*
* @param cause
* of the exception
*/
public RolloutIllegalStateException(final Throwable cause) {
super(THIS_ERROR, cause);
}
/**
* Parameterized constructor.
*
* @param message
* of the exception
* @param cause
* of the exception
*/
public RolloutIllegalStateException(final String message, final Throwable cause) {
super(message, THIS_ERROR, cause);
}
/**
* Parameterized constructor.
*
* @param message
* of the exception
*/
public RolloutIllegalStateException(final String message) {
super(message, THIS_ERROR);
}
}

View File

@@ -94,6 +94,14 @@ public class Action extends BaseEntity implements Comparable<Action> {
CascadeType.REMOVE })
private List<ActionStatus> actionStatus;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "rolloutgroup", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_action_rolloutgroup") )
private RolloutGroup rolloutGroup;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "rollout", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_action_rollout") )
private Rollout rollout;
/**
* Note: filled only in {@link Status#DOWNLOAD}.
*/
@@ -221,6 +229,36 @@ public class Action extends BaseEntity implements Comparable<Action> {
this.forcedTime = forcedTime;
}
/**
* @return the rolloutGroup
*/
public RolloutGroup getRolloutGroup() {
return rolloutGroup;
}
/**
* @param rolloutGroup
* the rolloutGroup to set
*/
public void setRolloutGroup(final RolloutGroup rolloutGroup) {
this.rolloutGroup = rolloutGroup;
}
/**
* @return the rollout
*/
public Rollout getRollout() {
return rollout;
}
/**
* @param rollout
* the rollout to set
*/
public void setRollout(final Rollout rollout) {
this.rollout = rollout;
}
@Override
public int compareTo(final Action o) {
if (super.getId() == null || o == null || o.getId() == null) {
@@ -379,7 +417,13 @@ public class Action extends BaseEntity implements Comparable<Action> {
/**
* Action needs download by this target which has now started.
*/
DOWNLOAD;
DOWNLOAD,
/**
* Action is in waiting state, e.g. the action is scheduled in a rollout
* but not yet activated.
*/
SCHEDULED;
}
/**

View File

@@ -32,6 +32,7 @@ public class ActionWithStatusCount {
private final String dsName;
private final String dsVersion;
private final Action action;
private final String rolloutName;
/**
* JPA constructor, the parameter are the result set columns of the custom
@@ -59,10 +60,14 @@ public class ActionWithStatusCount {
* the version of the distributionset
* @param actionStatusCount
* the count of the action status for this action
* @param rolloutName
* the rollout name
*/
public ActionWithStatusCount(final Long actionId, final ActionType actionType, final boolean active,
final long forcedTime, final Status status, final Long actionCreatedAt, final Long actionLastModifiedAt,
final Long dsId, final String dsName, final String dsVersion, final Long actionStatusCount) {
final Long dsId, final String dsName, final String dsVersion, final Long actionStatusCount,
final String rolloutName) {
this.actionId = actionId;
this.actionType = actionType;
this.actionActive = active;
@@ -74,6 +79,7 @@ public class ActionWithStatusCount {
this.dsName = dsName;
this.dsVersion = dsVersion;
this.actionStatusCount = actionStatusCount;
this.rolloutName = rolloutName;
this.action = new Action();
this.action.setActionType(actionType);
@@ -166,4 +172,12 @@ public class ActionWithStatusCount {
public Long getActionStatusCount() {
return actionStatusCount;
}
/**
* @return the rolloutName
*/
public String getRolloutName() {
return rolloutName;
}
}

View File

@@ -22,6 +22,7 @@ import javax.persistence.PrePersist;
import javax.persistence.Version;
import org.eclipse.hawkbit.eventbus.CacheFieldEntityListener;
import org.eclipse.hawkbit.eventbus.EntityPropertyChangeListener;
import org.eclipse.hawkbit.repository.exception.TenantNotExistException;
import org.eclipse.hawkbit.repository.model.helper.SystemManagementHolder;
import org.eclipse.hawkbit.repository.model.helper.TenantAwareHolder;
@@ -45,7 +46,7 @@ import org.springframework.hateoas.Identifiable;
*/
@MappedSuperclass
@Access(AccessType.FIELD)
@EntityListeners({ AuditingEntityListener.class, CacheFieldEntityListener.class })
@EntityListeners({ AuditingEntityListener.class, CacheFieldEntityListener.class, EntityPropertyChangeListener.class })
@TenantDiscriminatorColumn(name = "tenant", length = 40)
@Multitenant(MultitenantType.SINGLE_TABLE)
public abstract class BaseEntity implements Serializable, Identifiable<Long> {
@@ -98,9 +99,9 @@ public abstract class BaseEntity implements Serializable, Identifiable<Long> {
// service
final String currentTenant = SystemManagementHolder.getInstance().currentTenant();
if (currentTenant == null) {
throw new TenantNotExistException(
"Tenant " + TenantAwareHolder.getInstance().getTenantAware().getCurrentTenant()
+ " does not exists, cannot create entity " + this.getClass() + " with id " + id);
throw new TenantNotExistException("Tenant "
+ TenantAwareHolder.getInstance().getTenantAware().getCurrentTenant()
+ " does not exists, cannot create entity " + this.getClass() + " with id " + id);
}
setTenant(currentTenant.toUpperCase());
}

View File

@@ -0,0 +1,311 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.model;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.ConstraintMode;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.ForeignKey;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.persistence.UniqueConstraint;
import org.eclipse.hawkbit.cache.CacheField;
import org.eclipse.hawkbit.cache.CacheKeys;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
/**
* @author Michael Hirsch
*
*/
@Entity
@Table(name = "sp_rollout", indexes = {
@Index(name = "sp_idx_rollout_01", columnList = "tenant,name") }, uniqueConstraints = @UniqueConstraint(columnNames = {
"name", "tenant" }, name = "uk_rollout") )
public class Rollout extends NamedEntity {
private static final long serialVersionUID = 1L;
@OneToMany(targetEntity = RolloutGroup.class)
@JoinColumn(name = "rollout", insertable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rollout_rolloutgroup") )
private List<RolloutGroup> rolloutGroups;
@Column(name = "target_filter", length = 1024, nullable = false)
private String targetFilterQuery;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "distribution_set", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rolltout_ds") )
private DistributionSet distributionSet;
@Column(name = "status")
private RolloutStatus status = RolloutStatus.CREATING;
@Column(name = "last_check")
private long lastCheck = 0L;
@Column(name = "action_type", nullable = false)
@Enumerated(EnumType.STRING)
private ActionType actionType = ActionType.FORCED;
@Column(name = "forced_time")
private long forcedTime;
@Column(name = "total_targets")
private long totalTargets;
@Transient
@CacheField(key = CacheKeys.ROLLOUT_GROUP_TOTAL)
private int rolloutGroupsTotal = 0;
@Transient
@CacheField(key = CacheKeys.ROLLOUT_GROUP_CREATED)
private int rolloutGroupsCreated = 0;
@Transient
private TotalTargetCountStatus totalTargetCountStatus;
/**
* @return the distributionSet
*/
public DistributionSet getDistributionSet() {
return distributionSet;
}
/**
* @param distributionSet
* the distributionSet to set
*/
public void setDistributionSet(final DistributionSet distributionSet) {
this.distributionSet = distributionSet;
}
/**
* @return the rolloutGroups
*/
public List<RolloutGroup> getRolloutGroups() {
return rolloutGroups;
}
/**
* @param rolloutGroups
* the rolloutGroups to set
*/
public void setRolloutGroups(final List<RolloutGroup> rolloutGroups) {
this.rolloutGroups = rolloutGroups;
}
/**
* @return the targetFilterQuery
*/
public String getTargetFilterQuery() {
return targetFilterQuery;
}
/**
* @param targetFilterQuery
* the targetFilterQuery to set
*/
public void setTargetFilterQuery(final String targetFilterQuery) {
this.targetFilterQuery = targetFilterQuery;
}
/**
* @return the status
*/
public RolloutStatus getStatus() {
return status;
}
/**
* @param status
* the status to set
*/
public void setStatus(final RolloutStatus status) {
this.status = status;
}
/**
* @return the lastCheck
*/
public long getLastCheck() {
return lastCheck;
}
/**
* @param lastCheck
* the lastCheck to set
*/
public void setLastCheck(final long lastCheck) {
this.lastCheck = lastCheck;
}
/**
* @return the actionType
*/
public ActionType getActionType() {
return actionType;
}
/**
* @param actionType
* the actionType to set
*/
public void setActionType(final ActionType actionType) {
this.actionType = actionType;
}
/**
* @return the forcedTime
*/
public long getForcedTime() {
return forcedTime;
}
/**
* @param forcedTime
* the forcedTime to set
*/
public void setForcedTime(final long forcedTime) {
this.forcedTime = forcedTime;
}
/**
* @return the totalTargets
*/
public long getTotalTargets() {
return totalTargets;
}
/**
* @param totalTargets
* the totalTargets to set
*/
public void setTotalTargets(final long totalTargets) {
this.totalTargets = totalTargets;
}
/**
* @return the rolloutGroupsTotal
*/
public int getRolloutGroupsTotal() {
return rolloutGroupsTotal;
}
/**
* @param rolloutGroupsTotal
* the rolloutGroupsTotal to set
*/
public void setRolloutGroupsTotal(final int rolloutGroupsTotal) {
this.rolloutGroupsTotal = rolloutGroupsTotal;
}
/**
* @return the rolloutGroupsCreated
*/
public int getRolloutGroupsCreated() {
return rolloutGroupsCreated;
}
/**
* @param rolloutGroupsCreated
* the rolloutGroupsCreated to set
*/
public void setRolloutGroupsCreated(final int rolloutGroupsCreated) {
this.rolloutGroupsCreated = rolloutGroupsCreated;
}
/**
* @return the totalTargetCountStatus
*/
public TotalTargetCountStatus getTotalTargetCountStatus() {
if (totalTargetCountStatus == null) {
this.totalTargetCountStatus = new TotalTargetCountStatus(totalTargets);
}
return totalTargetCountStatus;
}
/**
* @param totalTargetCountStatus
* the totalTargetCountStatus to set
*/
public void setTotalTargetCountStatus(final TotalTargetCountStatus totalTargetCountStatus) {
this.totalTargetCountStatus = totalTargetCountStatus;
}
@Override
public String toString() {
return "Rollout [rolloutGroups=" + rolloutGroups + ", targetFilterQuery=" + targetFilterQuery
+ ", distributionSet=" + distributionSet + ", status=" + status + ", lastCheck=" + lastCheck
+ ", getName()=" + getName() + ", getId()=" + getId() + "]";
}
/**
*
* @author Michael Hirsch
*
*/
public static enum RolloutStatus {
/**
* Rollouts is beeing created.
*/
CREATING,
/**
* Rollout is ready to start.
*/
READY,
/**
* Rollout is paused.
*/
PAUSED,
/**
* Rollout is starting.
*/
STARTING,
/**
* Rollout is stopped.
*/
STOPPED,
/**
* Rollout is running.
*/
RUNNING,
/**
* Rollout is finished.
*/
FINISHED,
/**
* Rollout could not created due errors, might be database problem due
* asynchronous creating.
*/
ERROR_CREATING,
/**
* Rollout could not started due errors, might be database problem due
* asynchronous starting.
*/
ERROR_STARTING;
}
}

View File

@@ -0,0 +1,481 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.model;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.ConstraintMode;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.ForeignKey;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.persistence.UniqueConstraint;
/**
* JPA entity definition of persisting a group of an rollout.
*
* @author Michael Hirsch
*
*/
@Entity
@Table(name = "sp_rolloutgroup", indexes = {
@Index(name = "sp_idx_rolloutgroup_01", columnList = "tenant,name") }, uniqueConstraints = @UniqueConstraint(columnNames = {
"name", "rollout", "tenant" }, name = "uk_rolloutgroup") )
public class RolloutGroup extends NamedEntity {
private static final long serialVersionUID = 1L;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "rollout", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rolloutgroup_rollout") )
private Rollout rollout;
@Column(name = "status")
private RolloutGroupStatus status = RolloutGroupStatus.READY;
@OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST })
@JoinColumn(name = "rolloutGroup_Id", insertable = false, updatable = false)
private final List<RolloutTargetGroup> rolloutTargetGroup = new ArrayList<>();
@ManyToOne(fetch = FetchType.LAZY)
private RolloutGroup parent;
@Column(name = "success_condition", nullable = false)
private RolloutGroupSuccessCondition successCondition = RolloutGroupSuccessCondition.THRESHOLD;
@Column(name = "success_condition_exp", length = 512, nullable = false)
private String successConditionExp = null;
@Column(name = "success_action", nullable = false)
private RolloutGroupSuccessAction successAction = RolloutGroupSuccessAction.NEXTGROUP;
@Column(name = "success_action_exp", length = 512, nullable = false)
private String successActionExp = null;
@Column(name = "error_condition")
private RolloutGroupErrorCondition errorCondition = null;
@Column(name = "error_condition_exp", length = 512)
private String errorConditionExp = null;
@Column(name = "error_action")
private RolloutGroupErrorAction errorAction = null;
@Column(name = "error_action_exp", length = 512)
private String errorActionExp = null;
@Column(name = "total_targets")
private long totalTargets;
@Transient
private transient TotalTargetCountStatus totalTargetCountStatus;
public Rollout getRollout() {
return rollout;
}
public void setRollout(final Rollout rollout) {
this.rollout = rollout;
}
public RolloutGroupStatus getStatus() {
return status;
}
public void setStatus(final RolloutGroupStatus status) {
this.status = status;
}
public List<RolloutTargetGroup> getRolloutTargetGroup() {
return rolloutTargetGroup;
}
public RolloutGroup getParent() {
return parent;
}
public void setParent(final RolloutGroup parent) {
this.parent = parent;
}
public RolloutGroupSuccessCondition getSuccessCondition() {
return successCondition;
}
public void setSuccessCondition(final RolloutGroupSuccessCondition finishCondition) {
this.successCondition = finishCondition;
}
public String getSuccessConditionExp() {
return successConditionExp;
}
public void setSuccessConditionExp(final String finishExp) {
this.successConditionExp = finishExp;
}
public RolloutGroupErrorCondition getErrorCondition() {
return errorCondition;
}
public void setErrorCondition(final RolloutGroupErrorCondition errorCondition) {
this.errorCondition = errorCondition;
}
public String getErrorConditionExp() {
return errorConditionExp;
}
public void setErrorConditionExp(final String errorExp) {
this.errorConditionExp = errorExp;
}
public RolloutGroupErrorAction getErrorAction() {
return errorAction;
}
public void setErrorAction(final RolloutGroupErrorAction errorAction) {
this.errorAction = errorAction;
}
public String getErrorActionExp() {
return errorActionExp;
}
public void setErrorActionExp(final String errorActionExp) {
this.errorActionExp = errorActionExp;
}
public RolloutGroupSuccessAction getSuccessAction() {
return successAction;
}
public String getSuccessActionExp() {
return successActionExp;
}
public long getTotalTargets() {
return totalTargets;
}
public void setTotalTargets(final long totalTargets) {
this.totalTargets = totalTargets;
}
public void setSuccessAction(final RolloutGroupSuccessAction successAction) {
this.successAction = successAction;
}
public void setSuccessActionExp(final String successActionExp) {
this.successActionExp = successActionExp;
}
/**
* @return the totalTargetCountStatus
*/
public TotalTargetCountStatus getTotalTargetCountStatus() {
if (totalTargetCountStatus == null) {
this.totalTargetCountStatus = new TotalTargetCountStatus(totalTargets);
}
return totalTargetCountStatus;
}
/**
* @param totalTargetCountStatus
* the totalTargetCountStatus to set
*/
public void setTotalTargetCountStatus(final TotalTargetCountStatus totalTargetCountStatus) {
this.totalTargetCountStatus = totalTargetCountStatus;
}
@Override
public String toString() {
return "RolloutGroup [rollout=" + rollout + ", status=" + status + ", rolloutTargetGroup=" + rolloutTargetGroup
+ ", parent=" + parent + ", finishCondition=" + successCondition + ", finishExp=" + successConditionExp
+ ", errorCondition=" + errorCondition + ", errorExp=" + errorConditionExp + ", getName()=" + getName()
+ ", getId()=" + getId() + "]";
}
/**
*
* @author Michael Hirsch
*
*/
public enum RolloutGroupStatus {
/**
* Ready to start the group.
*/
READY,
/**
* Group is scheduled and started sometime, e.g. trigger of group
* before.
*/
SCHEDULED,
/**
* Group is finished.
*/
FINISHED,
/**
* Group is finished and has errors.
*/
ERROR,
/**
* Group is running.
*/
RUNNING;
}
/**
* The condition to evaluate if an group is success state.
*/
public enum RolloutGroupSuccessCondition {
THRESHOLD("thresholdRolloutGroupSuccessCondition");
private final String beanName;
private RolloutGroupSuccessCondition(final String beanName) {
this.beanName = beanName;
}
/**
* @return the beanName
*/
public String getBeanName() {
return beanName;
}
}
/**
* The condition to evaluate if an group is in error state.
*/
public enum RolloutGroupErrorCondition {
THRESHOLD("thresholdRolloutGroupErrorCondition");
private final String beanName;
private RolloutGroupErrorCondition(final String beanName) {
this.beanName = beanName;
}
/**
* @return the beanName
*/
public String getBeanName() {
return beanName;
}
}
/**
* The actions executed when the {@link RolloutGroup#errorCondition} is hit.
*/
public enum RolloutGroupErrorAction {
PAUSE("pauseRolloutGroupAction");
private final String beanName;
private RolloutGroupErrorAction(final String beanName) {
this.beanName = beanName;
}
/**
* @return the beanName
*/
public String getBeanName() {
return beanName;
}
}
/**
* The actions executed when the {@link RolloutGroup#successCondition} is
* hit.
*/
public enum RolloutGroupSuccessAction {
NEXTGROUP("startNextRolloutGroupAction");
private final String beanName;
private RolloutGroupSuccessAction(final String beanName) {
this.beanName = beanName;
}
/**
* @return the beanName
*/
public String getBeanName() {
return beanName;
}
}
/**
* Object which holds all {@link RolloutGroup} conditions together which can
* easily built.
*/
public static class RolloutGroupConditions {
private RolloutGroupSuccessCondition successCondition = null;
private String successConditionExp = null;
private RolloutGroupSuccessAction successAction = null;
private String successActionExp = null;
private RolloutGroupErrorCondition errorCondition = null;
private String errorConditionExp = null;
private RolloutGroupErrorAction errorAction = null;
private String errorActionExp = null;
public RolloutGroupSuccessCondition getSuccessCondition() {
return successCondition;
}
public void setSuccessCondition(final RolloutGroupSuccessCondition finishCondition) {
this.successCondition = finishCondition;
}
public String getSuccessConditionExp() {
return successConditionExp;
}
public void setSuccessConditionExp(final String finishConditionExp) {
this.successConditionExp = finishConditionExp;
}
public RolloutGroupSuccessAction getSuccessAction() {
return successAction;
}
public void setSuccessAction(final RolloutGroupSuccessAction successAction) {
this.successAction = successAction;
}
public String getSuccessActionExp() {
return successActionExp;
}
public void setSuccessActionExp(final String successActionExp) {
this.successActionExp = successActionExp;
}
public RolloutGroupErrorCondition getErrorCondition() {
return errorCondition;
}
public void setErrorCondition(final RolloutGroupErrorCondition errorCondition) {
this.errorCondition = errorCondition;
}
public String getErrorConditionExp() {
return errorConditionExp;
}
public void setErrorConditionExp(final String errorConditionExp) {
this.errorConditionExp = errorConditionExp;
}
public RolloutGroupErrorAction getErrorAction() {
return errorAction;
}
public void setErrorAction(final RolloutGroupErrorAction errorAction) {
this.errorAction = errorAction;
}
public String getErrorActionExp() {
return errorActionExp;
}
public void setErrorActionExp(final String errorActionExp) {
this.errorActionExp = errorActionExp;
}
}
/**
* Builder to build easily the {@link RolloutGroupConditions}.
*
*/
public static class RolloutGroupConditionBuilder {
private final RolloutGroupConditions conditions = new RolloutGroupConditions();
public RolloutGroupConditions build() {
return conditions;
}
/**
* Sets the finish condition and expression on the builder.
*
* @param condition
* the finish condition
* @param expression
* the finish expression
* @return the builder itself
*/
public RolloutGroupConditionBuilder successCondition(final RolloutGroupSuccessCondition condition,
final String expression) {
conditions.setSuccessCondition(condition);
conditions.setSuccessConditionExp(expression);
return this;
}
/**
* Sets the success action and expression on the builder.
*
* @param action
* the success action
* @param expression
* the error expression
* @return the builder itself
*/
public RolloutGroupConditionBuilder successAction(final RolloutGroupSuccessAction action,
final String expression) {
conditions.setSuccessAction(action);
conditions.setSuccessActionExp(expression);
return this;
}
/**
* Sets the error condition and expression on the builder.
*
* @param condition
* the error condition
* @param expression
* the error expression
* @return the builder itself
*/
public RolloutGroupConditionBuilder errorCondition(final RolloutGroupErrorCondition condition,
final String expression) {
conditions.setErrorCondition(condition);
conditions.setErrorConditionExp(expression);
return this;
}
/**
* Sets the error action and expression on the builder.
*
* @param action
* the error action
* @param expression
* the error expression
* @return the builder itself
*/
public RolloutGroupConditionBuilder errorAction(final RolloutGroupErrorAction action, final String expression) {
conditions.setErrorAction(action);
conditions.setErrorActionExp(expression);
return this;
}
}
}

View File

@@ -0,0 +1,65 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.model;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.ConstraintMode;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.ForeignKey;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.JoinColumn;
import javax.persistence.JoinColumns;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
/**
* @author Michael Hirsch
*
*/
@IdClass(RolloutTargetGroupId.class)
@Entity
@Table(name = "sp_rollouttargetgroup")
public class RolloutTargetGroup {
@Id
@ManyToOne(targetEntity = RolloutGroup.class, fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST })
@JoinColumn(name = "rolloutGroup_Id", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rollouttargetgroup_group"))
private RolloutGroup rolloutGroup;
@Id
@ManyToOne(targetEntity = Target.class, fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST })
@JoinColumn(name = "target_id", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rollouttargetgroup_target"))
private Target target;
@OneToMany(targetEntity = Action.class, fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST })
@JoinColumns(value = { @JoinColumn(name = "rolloutgroup", referencedColumnName = "rolloutGroup_Id"),
@JoinColumn(name = "target", referencedColumnName = "target_id") })
private List<Action> actions;
/**
* default constructor for JPA.
*/
public RolloutTargetGroup() {
// JPA constructor
}
public RolloutTargetGroup(final RolloutGroup rolloutGroup, final Target target) {
this.rolloutGroup = rolloutGroup;
this.target = target;
}
public RolloutTargetGroupId getId() {
return new RolloutTargetGroupId(rolloutGroup, target);
}
}

View File

@@ -0,0 +1,55 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.model;
import java.io.Serializable;
/**
* Combined unique key of the table {@link RolloutTargetGroup}.
*
* @author Michael Hirsch
*
*/
public class RolloutTargetGroupId implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private Long rolloutGroup;
private Long target;
/**
* default constructor necessary for JPA.
*/
public RolloutTargetGroupId() {
// default constructor necessary for JPA, empty.
}
/**
* Constructor.
*
* @param rolloutGroup
* the rollout group for this key
* @param target
* the target for this key
*/
public RolloutTargetGroupId(final RolloutGroup rolloutGroup, final Target target) {
this.rolloutGroup = rolloutGroup.getId();
this.target = target.getId();
}
public Long getRolloutGroup() {
return rolloutGroup;
}
public Long getTarget() {
return target;
}
}

View File

@@ -47,7 +47,7 @@ public class SoftwareModuleMetadata implements Serializable {
@Id
@ManyToOne(targetEntity = SoftwareModule.class, fetch = FetchType.LAZY)
@JoinColumn(name = "sw_id", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_metadata_sw") )
@JoinColumn(name = "sw_id", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_metadata_sw"))
private SoftwareModule softwareModule;
public SoftwareModuleMetadata() {

View File

@@ -106,6 +106,11 @@ public class Target extends NamedEntity implements Persistable<Long> {
@Column(name = "sec_token", insertable = true, updatable = true, nullable = false, length = 128)
private String securityToken = null;
@CascadeOnDelete
@OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.REMOVE, CascadeType.PERSIST })
@JoinColumn(name = "target_Id", insertable = false, updatable = false)
private final List<RolloutTargetGroup> rolloutTargetGroup = new ArrayList<>();
/**
* Constructor.
*

View File

@@ -0,0 +1,63 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.model;
import org.eclipse.hawkbit.repository.model.Action.Status;
/**
*
* Rollout - Target with action status.
*
*/
public class TargetWithActionStatus {
private Target target;
private Status status = null;;
public TargetWithActionStatus(final Target target) {
this.target = target;
}
public TargetWithActionStatus(final Target target, final Status status) {
this.status = status;
this.target = target;
}
/**
* @return the target
*/
public Target getTarget() {
return target;
}
/**
* @return the status
*/
public Status getStatus() {
return status;
}
/**
* @param target
* the target to set
*/
public void setTarget(final Target target) {
this.target = target;
}
/**
* @param status
* the status to set
*/
public void setStatus(final Status status) {
this.status = status;
}
}

View File

@@ -0,0 +1,53 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.model;
/**
* Represents rollout or rollout group statuses and count of targets in each
* status.
*
*/
public class TotalTargetCountActionStatus {
private final Action.Status status;
private final Long count;
private Long id;
public TotalTargetCountActionStatus(final Long id, final Action.Status status, final Long count) {
this.status = status;
this.count = count;
this.id = id;
}
public TotalTargetCountActionStatus(final Action.Status status, final Long count) {
this.status = status;
this.count = count;
}
/**
* @return the id
*/
public Long getId() {
return id;
}
/**
* @return the status
*/
public Action.Status getStatus() {
return status;
}
/**
* @return the count
*/
public Long getCount() {
return count;
}
}

View File

@@ -0,0 +1,125 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.model;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
*
* Store all states with the target count of a rollout or rolloutgroup.
*
*/
public class TotalTargetCountStatus {
/**
* Status of the total target counts.
*/
public enum Status {
SCHEDULED, RUNNING, ERROR, FINISHED, CANCELLED, NOTSTARTED
}
private final Map<Status, Long> statusTotalCountMap = new HashMap<>();
private final Long totalTargetCount;
/**
* Create a new states map with the target count for each state.
*
* @param targetCountActionStatus
* the action state map
* @param totalTargets
* the total target count
*/
public TotalTargetCountStatus(final List<TotalTargetCountActionStatus> targetCountActionStatus,
final Long totalTargetCount) {
this.totalTargetCount = totalTargetCount;
mapActionStatusToTotalTargetCountStatus(targetCountActionStatus);
}
/**
* Create a new states map with the target count for each state.
*
* @param totalTargetCount
* the total target count
*/
public TotalTargetCountStatus(final Long totalTargetCount) {
this(Collections.emptyList(), totalTargetCount);
}
/**
* The current state mape which the total target count
*
* @return the statusTotalCountMap the state map
*/
public Map<Status, Long> getStatusTotalCountMap() {
return statusTotalCountMap;
}
/**
* Gets the total target count from a state.
*
* @param status
* the state key
* @return the current target count cannot be <null>
*/
public Long getTotalTargetCountByStatus(final Status status) {
final Long count = statusTotalCountMap.get(status);
return count == null ? 0L : count;
}
/**
* Populate all target status to a the given map
*
* @param statusTotalCountMap
* the map
* @param rolloutStatusCountItems
* all target statut with total count
* @return <true> some state is populated <false> nothing is happend
*/
private final void mapActionStatusToTotalTargetCountStatus(
final List<TotalTargetCountActionStatus> targetCountActionStatus) {
if (targetCountActionStatus == null) {
statusTotalCountMap.put(TotalTargetCountStatus.Status.NOTSTARTED, totalTargetCount);
return;
}
statusTotalCountMap.put(Status.RUNNING, 0L);
Long notStartedTargetCount = totalTargetCount;
for (final TotalTargetCountActionStatus item : targetCountActionStatus) {
switch (item.getStatus()) {
case SCHEDULED:
statusTotalCountMap.put(Status.SCHEDULED, item.getCount());
break;
case ERROR:
statusTotalCountMap.put(Status.ERROR, item.getCount());
break;
case FINISHED:
statusTotalCountMap.put(Status.FINISHED, item.getCount());
break;
case RETRIEVED:
case RUNNING:
case WARNING:
case DOWNLOAD:
case CANCELING:
final Long runningItemsCount = statusTotalCountMap.get(Status.RUNNING) + item.getCount();
statusTotalCountMap.put(Status.RUNNING, runningItemsCount);
break;
case CANCELED:
statusTotalCountMap.put(Status.CANCELLED, item.getCount());
break;
default:
throw new IllegalArgumentException("State " + item.getStatus() + "is not valid");
}
notStartedTargetCount -= item.getCount();
}
statusTotalCountMap.put(TotalTargetCountStatus.Status.NOTSTARTED, notStartedTargetCount);
}
}

View File

@@ -0,0 +1,54 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.model.helper;
import org.eclipse.hawkbit.eventbus.EntityPropertyChangeListener;
import org.eclipse.hawkbit.executor.AfterTransactionCommitExecutor;
import org.springframework.beans.factory.annotation.Autowired;
/**
* A singleton bean which holds the {@link AfterTransactionCommitExecutor} to
* have to the cache manager in beans not instantiated by spring e.g. JPA
* entities or {@link EntityPropertyChangeListener} which cannot be autowired.
*
*/
public final class AfterTransactionCommitExecutorHolder {
private static final AfterTransactionCommitExecutorHolder SINGLETON = new AfterTransactionCommitExecutorHolder();
@Autowired
private AfterTransactionCommitExecutor afterCommit;
private AfterTransactionCommitExecutorHolder() {
}
/**
* @return the cache manager holder singleton instance
*/
public static AfterTransactionCommitExecutorHolder getInstance() {
return SINGLETON;
}
/**
* @return the afterCommit
*/
public AfterTransactionCommitExecutor getAfterCommit() {
return afterCommit;
}
/**
* @param afterCommit
* the afterCommit to set
*/
public void setAfterCommit(final AfterTransactionCommitExecutor afterCommit) {
this.afterCommit = afterCommit;
}
}

View File

@@ -0,0 +1,55 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.model.helper;
import org.eclipse.hawkbit.eventbus.CacheFieldEntityListener;
import org.springframework.beans.factory.annotation.Autowired;
import com.google.common.eventbus.EventBus;
/**
* A singleton bean which holds the {@link EventBus} to have to the cache
* manager in beans not instantiated by spring e.g. JPA entities or
* {@link CacheFieldEntityListener} which cannot be autowired.
*
*/
public final class EventBusHolder {
private static final EventBusHolder SINGLETON = new EventBusHolder();
@Autowired
private EventBus eventBus;
private EventBusHolder() {
}
/**
* @return the cache manager holder singleton instance
*/
public static EventBusHolder getInstance() {
return SINGLETON;
}
/**
* @return the eventBus
*/
public EventBus getEventBus() {
return eventBus;
}
/**
* @param eventBus
* the eventBus to set
*/
public void setEventBus(final EventBus eventBus) {
this.eventBus = eventBus;
}
}

View File

@@ -104,6 +104,30 @@ public final class RSQLUtility {
return new RSQLSpecification<>(rsql, fieldNameProvider);
}
/**
* Validate the given rsql string regarding existence and correct syntax.
*
* @param rsql
* the rsql string to get validated
*
*/
public static void isValid(final String rsql) {
parseRsql(rsql);
}
private static Node parseRsql(final String rsql) {
try {
LOGGER.debug("parsing rsql string {}", rsql);
final Set<ComparisonOperator> operators = RSQLOperators.defaultOperators();
operators.add(new ComparisonOperator("=li=", false));
return new RSQLParser(operators).parse(rsql);
} catch (final IllegalArgumentException e) {
throw new RSQLParameterSyntaxException("rsql filter must not be null", e);
} catch (final RSQLParserException e) {
throw new RSQLParameterSyntaxException(e);
}
}
private static final class RSQLSpecification<A extends Enum<A> & FieldNameProvider, T> implements Specification<T> {
private final String rsql;
@@ -125,15 +149,7 @@ public final class RSQLUtility {
@Override
public Predicate toPredicate(final Root<T> root, final CriteriaQuery<?> query, final CriteriaBuilder cb) {
final Node rootNode;
try {
LOGGER.debug("parsing rsql string {}", rsql);
final Set<ComparisonOperator> operators = RSQLOperators.defaultOperators();
operators.add(new ComparisonOperator("=li=", false));
rootNode = new RSQLParser(operators).parse(rsql);
} catch (final RSQLParserException e) {
throw new RSQLParameterSyntaxException(e);
}
final Node rootNode = parseRsql(rsql);
final JpqQueryRSQLVisitor<A, T> jpqQueryRSQLVisitor = new JpqQueryRSQLVisitor<>(root, cb, enumType);
final List<Predicate> accept = rootNode.<List<Predicate>, String> accept(jpqQueryRSQLVisitor);

View File

@@ -0,0 +1,55 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.rollout.condition;
import java.util.concurrent.Callable;
import org.eclipse.hawkbit.repository.RolloutGroupRepository;
import org.eclipse.hawkbit.repository.RolloutManagement;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* Error action evaluator which pauses the whole {@link Rollout} and sets the
* current {@link RolloutGroup} to error.
*/
@Component("pauseRolloutGroupAction")
public class PauseRolloutGroupAction implements RolloutGroupActionEvaluator {
@Autowired
private RolloutManagement rolloutManagement;
@Autowired
private RolloutGroupRepository rolloutGroupRepository;
@Autowired
private SystemSecurityContext systemSecurityContext;
@Override
public boolean verifyExpression(final String expression) {
return true;
}
@Override
public void eval(final Rollout rollout, final RolloutGroup rolloutGroup, final String expression) {
systemSecurityContext.runAsSystem(new Callable<Void>() {
@Override
public Void call() throws Exception {
rolloutGroup.setStatus(RolloutGroupStatus.ERROR);
rolloutGroupRepository.save(rolloutGroup);
rolloutManagement.pauseRollout(rollout);
return null;
}
});
}
}

View File

@@ -0,0 +1,22 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.rollout.condition;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
/**
*
*/
public interface RolloutGroupActionEvaluator {
boolean verifyExpression(final String expression);
void eval(Rollout rollout, RolloutGroup rolloutGroup, final String expression);
}

View File

@@ -0,0 +1,22 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.rollout.condition;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
/**
*
*/
public interface RolloutGroupConditionEvaluator {
boolean verifyExpression(final String expression);
boolean eval(Rollout rollout, RolloutGroup rolloutGroup, final String expression);
}

View File

@@ -0,0 +1,90 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.rollout.condition;
import java.util.List;
import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.RolloutGroupRepository;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* Success action which starts the next following {@link RolloutGroup}.
*/
@Component("startNextRolloutGroupAction")
public class StartNextGroupRolloutGroupSuccessAction implements RolloutGroupActionEvaluator {
private static final Logger logger = LoggerFactory.getLogger(StartNextGroupRolloutGroupSuccessAction.class);
@Autowired
private RolloutGroupRepository rolloutGroupRepository;
@Autowired
private DeploymentManagement deploymentManagement;
@Autowired
private SystemSecurityContext systemSecurityContext;
@Override
public boolean verifyExpression(final String expression) {
return true;
}
@Override
public void eval(final Rollout rollout, final RolloutGroup rolloutGroup, final String expression) {
systemSecurityContext.runAsSystem(() -> {
startNextGroup(rollout, rolloutGroup);
return null;
});
}
private void startNextGroup(final Rollout rollout, final RolloutGroup rolloutGroup) {
// retrieve all actions accroding to the parent group of the finished
// rolloutGroup, so retrieve all child-group actions which need to be
// started.
final List<Action> rolloutGroupActions = deploymentManagement.findActionsByRolloutGroupParentAndStatus(rollout,
rolloutGroup, Action.Status.SCHEDULED);
logger.debug("{} Next actions to start for rollout {} and parent group {}", rolloutGroupActions.size(), rollout,
rolloutGroup);
rolloutGroupActions.forEach(action -> deploymentManagement.startScheduledAction(action));
if (!rolloutGroupActions.isEmpty()) {
// get all next scheduled groups based on the found actions and set
// them in state running
rolloutGroupActions.forEach(action -> {
final RolloutGroup nextGroup = action.getRolloutGroup();
logger.debug("Rolloutgroup {} is now running", nextGroup);
nextGroup.setStatus(RolloutGroupStatus.RUNNING);
rolloutGroupRepository.save(nextGroup);
});
} else {
logger.info("No actions to start for next rolloutgroup of parent {}", rolloutGroup);
// nothing for next group, just finish the group, this can happen
// e.g. if targets has been deleted after the group has been
// scheduled. If the group is empty now, we just finish the group if
// there are not actions available for this group.
final List<RolloutGroup> findByRolloutGroupParent = rolloutGroupRepository
.findByParentAndStatus(rolloutGroup, RolloutGroupStatus.SCHEDULED);
findByRolloutGroupParent.forEach(nextGroup -> {
logger.debug("Rolloutgroup {} is finished, starting next group", nextGroup);
nextGroup.setStatus(RolloutGroupStatus.FINISHED);
rolloutGroupRepository.save(nextGroup);
// find the next group to set in running state
startNextGroup(rollout, nextGroup);
});
}
}
}

View File

@@ -0,0 +1,68 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.rollout.condition;
import org.eclipse.hawkbit.repository.ActionRepository;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
*
*/
@Component("thresholdRolloutGroupErrorCondition")
public class ThresholdRolloutGroupErrorCondition implements RolloutGroupConditionEvaluator {
private static Logger logger = LoggerFactory.getLogger(ThresholdRolloutGroupErrorCondition.class);
@Autowired
private ActionRepository actionRepository;
@Override
public boolean verifyExpression(final String expression) {
// percentage value between 0 and 100
try {
final Integer value = Integer.valueOf(expression);
if (value >= 0 || value <= 100) {
return true;
}
return true;
} catch (final RuntimeException e) {
}
return false;
}
@Override
public boolean eval(final Rollout rollout, final RolloutGroup rolloutGroup, final String expression) {
final Long totalGroup = actionRepository.countByRolloutAndRolloutGroup(rollout, rolloutGroup);
final Long error = actionRepository.countByRolloutIdAndRolloutGroupIdAndStatus(rollout.getId(),
rolloutGroup.getId(), Action.Status.ERROR);
try {
final Integer threshold = Integer.valueOf(expression);
if (totalGroup == 0) {
// in case e.g. targets has been deleted we don't have any
// actions left for this group, so the group is finished
return false;
}
// calculate threshold
return ((float) error / (float) totalGroup) > ((float) threshold / 100F);
} catch (final NumberFormatException e) {
logger.error("Cannot evaluate condition expression " + expression, e);
return false;
}
}
}

View File

@@ -0,0 +1,58 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.rollout.condition;
import org.eclipse.hawkbit.repository.ActionRepository;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
*
*/
@Component("thresholdRolloutGroupSuccessCondition")
public class ThresholdRolloutGroupSuccessCondition implements RolloutGroupConditionEvaluator {
@Autowired
private ActionRepository actionRepository;
@Override
public boolean verifyExpression(final String expression) {
// percentage value between 0 and 100
try {
final Integer value = Integer.valueOf(expression);
if (value >= 0 || value <= 100) {
return true;
}
return true;
} catch (final RuntimeException e) {
}
return false;
}
@Override
public boolean eval(final Rollout rollout, final RolloutGroup rolloutGroup, final String expression) {
final Long totalGroup = rolloutGroup.getTotalTargets();
final Long finished = actionRepository.countByRolloutIdAndRolloutGroupIdAndStatus(rollout.getId(),
rolloutGroup.getId(), Action.Status.FINISHED);
final Integer threshold = Integer.valueOf(expression);
if (totalGroup == 0) {
// in case e.g. targets has been deleted we don't have any actions
// left for this group, so the group is finished
return true;
}
// calculate threshold
return ((float) finished / (float) totalGroup) >= ((float) threshold / 100F);
}
}

View File

@@ -0,0 +1,103 @@
create table sp_rolloutgroup (
id bigint generated by default as identity,
created_at bigint,
created_by varchar(40),
last_modified_at bigint,
last_modified_by varchar(40),
optlock_revision bigint,
tenant varchar(40) not null,
description varchar(512),
name varchar(64) not null,
error_condition integer,
error_condition_exp varchar(512),
error_action integer,
error_action_exp varchar(512),
success_condition integer not null,
success_condition_exp varchar(512) not null,
success_action integer not null,
success_action_exp varchar(512),
status integer,
parent_id bigint,
rollout bigint,
total_targets bigint,
primary key (id)
);
create table sp_rollout (
id bigint generated by default as identity,
created_at bigint,
created_by varchar(40),
last_modified_at bigint,
last_modified_by varchar(40),
optlock_revision bigint,
tenant varchar(40) not null,
description varchar(512),
name varchar(64) not null,
last_check bigint,
group_theshold float,
status integer,
distribution_set bigint,
target_filter varchar(1024),
action_type varchar(255) not null,
forced_time bigint,
total_targets bigint,
primary key (id)
);
create table sp_rollouttargetgroup (
target_Id bigint not null,
rolloutGroup_Id bigint not null,
primary key (rolloutGroup_Id, target_Id)
);
create index sp_idx_rollout_01 on sp_rollout (tenant, name);
create index sp_idx_rolloutgroup_01 on sp_rolloutgroup (tenant, name);
ALTER TABLE sp_action ADD COLUMN rollout bigint;
ALTER TABLE sp_action ADD COLUMN rolloutgroup bigint;
alter table sp_rollout
add constraint uk_rollout unique (name, tenant);
alter table sp_rolloutgroup
add constraint uk_rolloutgroup unique (name, rollout, tenant);
alter table sp_action
add constraint fk_action_rollout
foreign key (rollout)
references sp_rollout;
alter table sp_action
add constraint fk_action_rolloutgroup
foreign key (rolloutgroup)
references sp_rolloutgroup;
alter table sp_rollout
add constraint fk_rollout_ds
foreign key (distribution_set)
references sp_distribution_set;
alter table sp_rolloutgroup
add constraint fk_rolloutgroup_rollout
foreign key (rollout)
references sp_rollout
on delete cascade;
alter table sp_rolloutgroup
add constraint fk_rolloutgroup_rolloutgroup
foreign key (parent_id)
references sp_rolloutgroup
on delete cascade;
alter table sp_rollouttargetgroup
add constraint fk_rollouttargetgroup_target
foreign key (target_id)
references sp_target
on delete cascade;
alter table sp_rollouttargetgroup
add constraint fk_rollouttargetgroup_rolloutgroup
foreign key (rolloutgroup_id)
references sp_rolloutgroup
on delete cascade;

View File

@@ -0,0 +1,103 @@
create table sp_rolloutgroup (
id bigint not null auto_increment,
created_at bigint,
created_by varchar(40),
last_modified_at bigint,
last_modified_by varchar(40),
optlock_revision bigint,
tenant varchar(40) not null,
description varchar(512),
name varchar(64) not null,
error_condition integer,
error_condition_exp varchar(512),
error_action integer,
error_action_exp varchar(512),
success_condition integer not null,
success_condition_exp varchar(512) not null,
success_action integer not null,
success_action_exp varchar(512),
status integer,
parent_id bigint,
rollout bigint,
total_targets bigint,
primary key (id)
);
create table sp_rollout (
id bigint not null auto_increment,
created_at bigint,
created_by varchar(40),
last_modified_at bigint,
last_modified_by varchar(40),
optlock_revision bigint,
tenant varchar(40) not null,
description varchar(512),
name varchar(64) not null,
last_check bigint,
group_theshold float,
status integer,
distribution_set bigint,
target_filter varchar(1024),
action_type varchar(255) not null,
forced_time bigint,
total_targets bigint,
primary key (id)
);
create table sp_rollouttargetgroup (
target_Id bigint not null,
rolloutGroup_Id bigint not null,
primary key (rolloutGroup_Id, target_Id)
);
create index sp_idx_rollout_01 on sp_rollout (tenant, name);
create index sp_idx_rolloutgroup_01 on sp_rolloutgroup (tenant, name);
ALTER TABLE sp_action ADD COLUMN rollout bigint;
ALTER TABLE sp_action ADD COLUMN rolloutgroup bigint;
alter table sp_rollout
add constraint uk_rollout unique (name, tenant);
alter table sp_rolloutgroup
add constraint uk_rolloutgroup unique (name, rollout, tenant);
alter table sp_action
add constraint fk_action_rollout
foreign key (rollout)
references sp_rollout (id);
alter table sp_action
add constraint fk_action_rolloutgroup
foreign key (rolloutgroup)
references sp_rolloutgroup (id);
alter table sp_rollout
add constraint fk_rollout_ds
foreign key (distribution_set)
references sp_distribution_set (id);
alter table sp_rolloutgroup
add constraint fk_rolloutgroup_rollout
foreign key (rollout)
references sp_rollout (id)
on delete cascade;
alter table sp_rolloutgroup
add constraint fk_rolloutgroup_rolloutgroup
foreign key (parent_id)
references sp_rolloutgroup (id)
on delete cascade;
alter table sp_rollouttargetgroup
add constraint fk_rollouttargetgroup_target
foreign key (target_id)
references sp_target (id)
on delete cascade;
alter table sp_rollouttargetgroup
add constraint fk_rollouttargetgroup_rolloutgroup
foreign key (rolloutgroup_id)
references sp_rolloutgroup (id)
on delete cascade;

View File

@@ -28,6 +28,9 @@ import org.eclipse.hawkbit.repository.DistributionSetTagRepository;
import org.eclipse.hawkbit.repository.DistributionSetTypeRepository;
import org.eclipse.hawkbit.repository.ExternalArtifactRepository;
import org.eclipse.hawkbit.repository.LocalArtifactRepository;
import org.eclipse.hawkbit.repository.RolloutGroupManagement;
import org.eclipse.hawkbit.repository.RolloutGroupRepository;
import org.eclipse.hawkbit.repository.RolloutManagement;
import org.eclipse.hawkbit.repository.SoftwareManagement;
import org.eclipse.hawkbit.repository.SoftwareModuleMetadataRepository;
import org.eclipse.hawkbit.repository.SoftwareModuleRepository;
@@ -178,6 +181,15 @@ public abstract class AbstractIntegrationTest implements EnvironmentAware {
@Autowired
protected TenantAwareCacheManager cacheManager;
@Autowired
protected RolloutManagement rolloutManagement;
@Autowired
protected RolloutGroupManagement rolloutGroupManagement;
@Autowired
protected RolloutGroupRepository rolloutGroupRepository;
protected MockMvc mvc;
@Autowired

View File

@@ -9,10 +9,12 @@
package org.eclipse.hawkbit;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import org.eclipse.hawkbit.cache.CacheConstants;
import org.eclipse.hawkbit.cache.TenancyCacheManager;
import org.eclipse.hawkbit.cache.TenantAwareCacheManager;
import org.eclipse.hawkbit.repository.model.helper.EventBusHolder;
import org.eclipse.hawkbit.repository.utils.RepositoryDataGenerator;
import org.eclipse.hawkbit.repository.utils.RepositoryDataGenerator.DatabaseCleanupUtil;
import org.eclipse.hawkbit.security.SecurityContextTenantAware;
@@ -30,7 +32,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.data.domain.AuditorAware;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.security.concurrent.DelegatingSecurityContextExecutor;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import com.google.common.eventbus.AsyncEventBus;
@@ -91,9 +93,14 @@ public class TestConfiguration implements AsyncConfigurer {
return new AsyncEventBus(asyncExecutor());
}
@Bean
public EventBusHolder eventBusHolder() {
return EventBusHolder.getInstance();
}
@Bean
public Executor asyncExecutor() {
return new ThreadPoolTaskExecutor();
return new DelegatingSecurityContextExecutor(Executors.newSingleThreadExecutor());
}
@Bean

View File

@@ -21,5 +21,4 @@ public class CacheKeysTest {
final String entitySpecificCacheKey = CacheKeys.entitySpecificCacheKey(knownEntityId, knownCacheKey);
assertThat(entitySpecificCacheKey).isEqualTo(knownEntityId + "." + knownCacheKey);
}
}

View File

@@ -0,0 +1,94 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.rsql;
import static org.fest.assertions.api.Assertions.assertThat;
import org.eclipse.hawkbit.AbstractIntegrationTest;
import org.eclipse.hawkbit.TestDataUtil;
import org.eclipse.hawkbit.repository.RolloutGroupFields;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupConditionBuilder;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition;
import org.junit.Before;
import org.junit.Test;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import ru.yandex.qatools.allure.annotations.Description;
import ru.yandex.qatools.allure.annotations.Features;
import ru.yandex.qatools.allure.annotations.Stories;
@Features("Component Tests - RSQL filtering")
@Stories("RSQL filter rollout group")
public class RSQLRolloutGroupFields extends AbstractIntegrationTest {
private Long rolloutGroupId;
private Rollout rollout;
@Before
public void seuptBeforeTest() {
final int amountTargets = 20;
targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargets, "rollout", "rollout"));
final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
rollout = createRollout("rollout1", 4, dsA.getId(), "controllerId==rollout*");
rollout = rolloutManagement.findRolloutById(rollout.getId());
this.rolloutGroupId = rollout.getRolloutGroups().get(0).getId();
}
@Test
@Description("Test filter rollout group by id")
public void testFilterByParameterId() {
assertRSQLQuery(RolloutGroupFields.ID.name() + "==" + rolloutGroupId, 1);
assertRSQLQuery(RolloutGroupFields.ID.name() + "==noExist*", 0);
assertRSQLQuery(RolloutGroupFields.ID.name() + "=in=(" + rolloutGroupId + ")", 1);
assertRSQLQuery(RolloutGroupFields.ID.name() + "=out=(" + rolloutGroupId + ")", 3);
}
@Test
@Description("Test filter rollout group by name")
public void testFilterByParameterName() {
assertRSQLQuery(RolloutGroupFields.NAME.name() + "==group-1", 1);
assertRSQLQuery(RolloutGroupFields.NAME.name() + "==*", 4);
assertRSQLQuery(RolloutGroupFields.NAME.name() + "==noExist*", 0);
assertRSQLQuery(RolloutGroupFields.NAME.name() + "=in=(group-1,group-2)", 2);
assertRSQLQuery(RolloutGroupFields.NAME.name() + "=out=(group-1,group-2)", 2);
}
@Test
@Description("Test filter rollout group by description")
public void testFilterByParameterDescription() {
assertRSQLQuery(RolloutGroupFields.DESCRIPTION.name() + "==group-1", 1);
assertRSQLQuery(RolloutGroupFields.DESCRIPTION.name() + "==group*", 4);
assertRSQLQuery(RolloutGroupFields.DESCRIPTION.name() + "==noExist*", 0);
assertRSQLQuery(RolloutGroupFields.DESCRIPTION.name() + "=in=(group-1,notexist)", 1);
assertRSQLQuery(RolloutGroupFields.DESCRIPTION.name() + "=out=(group-1,notexist)", 3);
}
private void assertRSQLQuery(final String rsqlParam, final long expcetedTargets) {
final Page<RolloutGroup> findTargetPage = rolloutGroupManagement.findRolloutGroupsByPredicate(rollout,
RSQLUtility.parse(rsqlParam, RolloutGroupFields.class), new PageRequest(0, 100));
final long countTargetsAll = findTargetPage.getTotalElements();
assertThat(findTargetPage).isNotNull();
assertThat(countTargetsAll).isEqualTo(expcetedTargets);
}
private Rollout createRollout(final String name, final int amountGroups, final long distributionSetId,
final String targetFilterQuery) {
final Rollout rollout = new Rollout();
rollout.setDistributionSet(distributionSetManagement.findDistributionSetById(distributionSetId));
rollout.setName(name);
rollout.setTargetFilterQuery(targetFilterQuery);
return rolloutManagement.createRollout(rollout, amountGroups, new RolloutGroupConditionBuilder()
.successCondition(RolloutGroupSuccessCondition.THRESHOLD, "100").build());
}
}

View File

@@ -154,6 +154,16 @@ public final class RestConstants {
*/
public static final String DISTRIBUTIONSET_V1_REQUEST_MAPPING = BASE_V1_REQUEST_MAPPING + "/distributionsets";
/**
* The rollout URL mapping rest resource.
*/
public static final String ROLLOUT_V1_REQUEST_MAPPING = BASE_V1_REQUEST_MAPPING + "/rollouts";
/**
* Request parameter for async
*/
public static final String REQUEST_PARAMETER_ASYNC = "async";
/**
* The target URL mapping, href link for artifact download.
*/

View File

@@ -14,7 +14,7 @@ import java.util.List;
import org.eclipse.hawkbit.rest.resource.model.PagedList;
/**
* Paged list rest model for {@link Action} to RESTful API representation.
* Paged list rest model for {@link ErrorAction} to RESTful API representation.
*
*/
public class ActionPagedList extends PagedList<ActionRest> {

View File

@@ -0,0 +1,69 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.rest.resource.model.rollout;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
/**
*
*/
@JsonInclude(Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class RolloutCondition {
private Condition condition = Condition.THRESHOLD;
private String expression = "100";
/**
*
*/
public RolloutCondition() {
}
public RolloutCondition(final Condition condition, final String expression) {
this.condition = condition;
this.expression = expression;
}
/**
* @return the condition
*/
public Condition getCondition() {
return condition;
}
/**
* @param condition
* the condition to set
*/
public void setCondition(final Condition condition) {
this.condition = condition;
}
/**
* @return the expession
*/
public String getExpression() {
return expression;
}
/**
* @param expession
* the expession to set
*/
public void setExpression(final String expession) {
this.expression = expession;
}
public enum Condition {
THRESHOLD;
}
}

View File

@@ -0,0 +1,58 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.rest.resource.model.rollout;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
/**
*
*/
@JsonInclude(Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class RolloutErrorAction {
private ErrorAction action = ErrorAction.PAUSE;
private String expression = null;
/**
* @return the action
*/
public ErrorAction getAction() {
return action;
}
/**
* @param action
* the action to set
*/
public void setAction(final ErrorAction action) {
this.action = action;
}
/**
* @return the expression
*/
public String getExpression() {
return expression;
}
/**
* @param expression
* the expression to set
*/
public void setExpression(final String expression) {
this.expression = expression;
}
public enum ErrorAction {
PAUSE;
}
}

View File

@@ -0,0 +1,36 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.rest.resource.model.rollout;
import java.util.List;
import org.eclipse.hawkbit.rest.resource.model.PagedList;
/**
* Paged list for Rollout.
*
*
*/
public class RolloutPagedList extends PagedList<RolloutResponseBody> {
private final List<RolloutResponseBody> content;
public RolloutPagedList(final List<RolloutResponseBody> content, final long total) {
super(content, total);
this.content = content;
}
/**
* @return the content of the paged list. Never {@code null}.
*/
public List<RolloutResponseBody> getContent() {
return content;
}
}

View File

@@ -0,0 +1,124 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.rest.resource.model.rollout;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.hawkbit.rest.resource.model.NamedEntityRest;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
*
*/
@JsonInclude(Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class RolloutResponseBody extends NamedEntityRest {
private String targetFilterQuery;
private Long distributionSetId;
@JsonProperty(value = "id", required = true)
private Long rolloutId;
@JsonProperty(required = true)
private String status;
@JsonProperty(required = true)
private Long totalTargets;
@JsonProperty(required = true)
private final Map<String, Long> totalTargetsPerStatus = new HashMap<>();
/**
* @return the status
*/
public String getStatus() {
return status;
}
/**
* @param status
* the status to set
*/
public void setStatus(final String status) {
this.status = status;
}
/**
* @return the rolloutId
*/
public Long getRolloutId() {
return rolloutId;
}
/**
* @param rolloutId
* the rolloutId to set
*/
public void setRolloutId(final Long rolloutId) {
this.rolloutId = rolloutId;
}
/**
* @return the targetFilterQuery
*/
public String getTargetFilterQuery() {
return targetFilterQuery;
}
/**
* @param targetFilterQuery
* the targetFilterQuery to set
*/
public void setTargetFilterQuery(final String targetFilterQuery) {
this.targetFilterQuery = targetFilterQuery;
}
/**
* @return the distributionSetId
*/
public Long getDistributionSetId() {
return distributionSetId;
}
/**
* @param distributionSetId
* the distributionSetId to set
*/
public void setDistributionSetId(final Long distributionSetId) {
this.distributionSetId = distributionSetId;
}
/**
* @param totalTargets
* the totalTargets to set
*/
public void setTotalTargets(final Long totalTargets) {
this.totalTargets = totalTargets;
}
/**
* @return the totalTargets
*/
public Long getTotalTargets() {
return totalTargets;
}
/**
* @return the totalTargetsPerStatus
*/
public Map<String, Long> getTotalTargetsPerStatus() {
return totalTargetsPerStatus;
}
}

View File

@@ -0,0 +1,175 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.rest.resource.model.rollout;
import org.eclipse.hawkbit.rest.resource.model.NamedEntityRest;
import org.eclipse.hawkbit.rest.resource.model.distributionset.ActionTypeRest;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
/**
* Model for request containing a rollout body e.g. in a POST request of
* creating a rollout via REST API.
*/
@JsonInclude(Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class RolloutRestRequestBody extends NamedEntityRest {
private String targetFilterQuery;
private long distributionSetId;
private int amountGroups = 1;
private RolloutCondition successCondition = new RolloutCondition();
private RolloutSuccessAction successAction = new RolloutSuccessAction();
private RolloutCondition errorCondition = null;
private RolloutErrorAction errorAction = null;
private Long forcetime;
private ActionTypeRest type;
/**
* @return the finishCondition
*/
public RolloutCondition getSuccessCondition() {
return successCondition;
}
/**
* @param successCondition
* the finishCondition to set
*/
public void setSuccessCondition(final RolloutCondition successCondition) {
this.successCondition = successCondition;
}
/**
* @return the successAction
*/
public RolloutSuccessAction getSuccessAction() {
return successAction;
}
/**
* @param successAction
* the successAction to set
*/
public void setSuccessAction(final RolloutSuccessAction successAction) {
this.successAction = successAction;
}
/**
* @return the errorCondition
*/
public RolloutCondition getErrorCondition() {
return errorCondition;
}
/**
* @param errorCondition
* the errorCondition to set
*/
public void setErrorCondition(final RolloutCondition errorCondition) {
this.errorCondition = errorCondition;
}
/**
* @return the targetFilterQuery
*/
public String getTargetFilterQuery() {
return targetFilterQuery;
}
/**
* @param targetFilterQuery
* the targetFilterQuery to set
*/
public void setTargetFilterQuery(final String targetFilterQuery) {
this.targetFilterQuery = targetFilterQuery;
}
/**
* @return the distributionSetId
*/
public long getDistributionSetId() {
return distributionSetId;
}
/**
* @param distributionSetId
* the distributionSetId to set
*/
public void setDistributionSetId(final long distributionSetId) {
this.distributionSetId = distributionSetId;
}
/**
* @return the groupSize
*/
public int getAmountGroups() {
return amountGroups;
}
/**
* @param groupSize
* the groupSize to set
*/
public void setAmountGroups(final int groupSize) {
this.amountGroups = groupSize;
}
/**
* @return the forcetime
*/
public Long getForcetime() {
return forcetime;
}
/**
* @param forcetime
* the forcetime to set
*/
public void setForcetime(final Long forcetime) {
this.forcetime = forcetime;
}
/**
* @return the type
*/
public ActionTypeRest getType() {
return type;
}
/**
* @param type
* the type to set
*/
public void setType(final ActionTypeRest type) {
this.type = type;
}
/**
* @return the errorAction
*/
public RolloutErrorAction getErrorAction() {
return errorAction;
}
/**
* @param errorAction
* the errorAction to set
*/
public void setErrorAction(final RolloutErrorAction errorAction) {
this.errorAction = errorAction;
}
}

View File

@@ -0,0 +1,69 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.rest.resource.model.rollout;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
/**
*
*/
@JsonInclude(Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class RolloutSuccessAction {
private SuccessAction action = SuccessAction.NEXTGROUP;
private String expression = null;
/**
*
*/
public RolloutSuccessAction() {
}
public RolloutSuccessAction(final SuccessAction action, final String expression) {
this.action = action;
this.expression = expression;
}
/**
* @return the action
*/
public SuccessAction getAction() {
return action;
}
/**
* @param action
* the action to set
*/
public void setAction(final SuccessAction action) {
this.action = action;
}
/**
* @return the expession
*/
public String getExpression() {
return expression;
}
/**
* @param expession
* the expession to set
*/
public void setExpression(final String expession) {
this.expression = expession;
}
public enum SuccessAction {
NEXTGROUP;
}
}

View File

@@ -0,0 +1,35 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.rest.resource.model.rolloutgroup;
import java.util.List;
import org.eclipse.hawkbit.rest.resource.model.PagedList;
/**
* Paged list for Rollout.
*
*/
public class RolloutGroupPagedList extends PagedList<RolloutGroupResponseBody> {
private final List<RolloutGroupResponseBody> content;
public RolloutGroupPagedList(final List<RolloutGroupResponseBody> content, final long total) {
super(content, total);
this.content = content;
}
/**
* @return the content of the paged list. Never {@code null}.
*/
public List<RolloutGroupResponseBody> getContent() {
return content;
}
}

View File

@@ -0,0 +1,61 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.rest.resource.model.rolloutgroup;
import org.eclipse.hawkbit.rest.resource.model.NamedEntityRest;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* Model for the rollout group annotated with json-annotations for easier
* serialization and de-serialization.
*/
@JsonInclude(Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class RolloutGroupResponseBody extends NamedEntityRest {
@JsonProperty(value = "id", required = true)
private Long rolloutGroupId;
@JsonProperty(required = true)
private String status;
/**
* @return the rolloutGroupId
*/
public Long getRolloutGroupId() {
return rolloutGroupId;
}
/**
* @param rolloutGroupId
* the rolloutGroupId to set
*/
public void setRolloutGroupId(final Long rolloutGroupId) {
this.rolloutGroupId = rolloutGroupId;
}
/**
* @return the status
*/
public String getStatus() {
return status;
}
/**
* @param status
* the status to set
*/
public void setStatus(final String status) {
this.status = status;
}
}

View File

@@ -1,97 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.cache;
import org.eclipse.hawkbit.eventbus.event.DownloadProgressEvent;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.ActionStatus;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Service;
import com.google.common.eventbus.EventBus;
/**
* An service which combines the functionality for functional use cases to write
* into the cache an notify the writing to the cache to the {@link EventBus}.
*
*
*
*
*/
@Service
public class CacheWriteNotify {
/**
*
*/
private static final int DOWNLOAD_PROGRESS_MAX = 100;
@Autowired
private CacheManager cacheManager;
@Autowired
private EventBus eventBus;
@Autowired
private TenantAware tenantAware;
/**
* writes the download progress in percentage into the cache
* {@link CacheKeys#DOWNLOAD_PROGRESS_PERCENT} and notifies the
* {@link EventBus} with a {@link DownloadProgressEvent}.
*
* @param statusId
* the ID of the {@link ActionStatus}
* @param progressPercent
* the progress in percentage which must be between 0-100
*/
public void downloadProgressPercent(final long statusId, final int progressPercent) {
final Cache cache = cacheManager.getCache(Action.class.getName());
final String cacheKey = CacheKeys.entitySpecificCacheKey(String.valueOf(statusId),
CacheKeys.DOWNLOAD_PROGRESS_PERCENT);
if (progressPercent < DOWNLOAD_PROGRESS_MAX) {
cache.put(cacheKey, progressPercent);
} else {
// in case we reached progress 100 delete the cache value again
// because otherwise he will
// keep there forever
cache.evict(cacheKey);
}
eventBus.post(new DownloadProgressEvent(tenantAware.getCurrentTenant(), statusId, progressPercent));
}
/**
* @param cacheManager
* the cacheManager to set
*/
void setCacheManager(final CacheManager cacheManager) {
this.cacheManager = cacheManager;
}
/**
* @param eventBus
* the eventBus to set
*/
void setEventBus(final EventBus eventBus) {
this.eventBus = eventBus;
}
/**
* @param tenantAware
* the tenantAware to set
*/
void setTenantAware(final TenantAware tenantAware) {
this.tenantAware = tenantAware;
}
}

View File

@@ -12,6 +12,8 @@ import org.eclipse.hawkbit.repository.ActionFields;
import org.eclipse.hawkbit.repository.ActionStatusFields;
import org.eclipse.hawkbit.repository.DistributionSetFields;
import org.eclipse.hawkbit.repository.DistributionSetMetadataFields;
import org.eclipse.hawkbit.repository.RolloutFields;
import org.eclipse.hawkbit.repository.RolloutGroupFields;
import org.eclipse.hawkbit.repository.SoftwareModuleFields;
import org.eclipse.hawkbit.repository.SoftwareModuleMetadataFields;
import org.eclipse.hawkbit.repository.TargetFields;
@@ -127,4 +129,25 @@ public final class PagingUtility {
return sorting;
}
static Sort sanitizeRolloutSortParam(final String sortParam) {
final Sort sorting;
if (sortParam != null) {
sorting = new Sort(SortUtility.parse(RolloutFields.class, sortParam));
} else {
// default sort
sorting = new Sort(Direction.ASC, RolloutFields.NAME.getFieldName());
}
return sorting;
}
static Sort sanitizeRolloutGroupSortParam(final String sortParam) {
final Sort sorting;
if (sortParam != null) {
sorting = new Sort(SortUtility.parse(RolloutGroupFields.class, sortParam));
} else {
// default sort
sorting = new Sort(Direction.ASC, RolloutGroupFields.NAME.getFieldName());
}
return sorting;
}
}

View File

@@ -64,7 +64,7 @@ public class ResponseExceptionHandler {
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_DS_TYPE_UNDEFINED, HttpStatus.BAD_REQUEST);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REPO_TENANT_NOT_EXISTS, HttpStatus.BAD_REQUEST);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ENTITY_LOCKED, HttpStatus.LOCKED);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ROLLOUT_ILLEGAL_STATE, HttpStatus.BAD_REQUEST);
}
private static HttpStatus getStatusOrDefault(final SpServerError error) {
@@ -84,8 +84,7 @@ public class ResponseExceptionHandler {
* as entity.
*/
@ExceptionHandler(SpServerRtException.class)
public ResponseEntity<ExceptionInfo> handleSpServerRtExceptions(final HttpServletRequest request,
final Exception ex) {
public ResponseEntity<ExceptionInfo> handleSpServerRtExceptions(final HttpServletRequest request, final Exception ex) {
LOG.debug("Handling exception of request {}", request.getRequestURL());
final ExceptionInfo response = new ExceptionInfo();
final HttpStatus responseStatus;

View File

@@ -0,0 +1,162 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.rest.resource;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupErrorAction;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupErrorCondition;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessAction;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition;
import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus;
import org.eclipse.hawkbit.rest.resource.helper.RestResourceConversionHelper;
import org.eclipse.hawkbit.rest.resource.model.rollout.RolloutCondition.Condition;
import org.eclipse.hawkbit.rest.resource.model.rollout.RolloutErrorAction.ErrorAction;
import org.eclipse.hawkbit.rest.resource.model.rollout.RolloutResponseBody;
import org.eclipse.hawkbit.rest.resource.model.rollout.RolloutRestRequestBody;
import org.eclipse.hawkbit.rest.resource.model.rollout.RolloutSuccessAction.SuccessAction;
import org.eclipse.hawkbit.rest.resource.model.rolloutgroup.RolloutGroupResponseBody;
/**
* A mapper which maps repository model to RESTful model representation and
* back.
*
*
*/
final class RolloutMapper {
private static final String NOT_SUPPORTED = " is not supported";
private RolloutMapper() {
// Utility class
}
static List<RolloutResponseBody> toResponseRollout(final List<Rollout> rollouts) {
final List<RolloutResponseBody> result = new ArrayList<>(rollouts.size());
rollouts.forEach(r -> result.add(toResponseRollout(r)));
return result;
}
static RolloutResponseBody toResponseRollout(final Rollout rollout) {
final RolloutResponseBody body = new RolloutResponseBody();
body.setCreatedAt(rollout.getCreatedAt());
body.setCreatedBy(rollout.getCreatedBy());
body.setDescription(rollout.getDescription());
body.setLastModifiedAt(rollout.getLastModifiedAt());
body.setLastModifiedBy(rollout.getLastModifiedBy());
body.setName(rollout.getName());
body.setRolloutId(rollout.getId());
body.setTargetFilterQuery(rollout.getTargetFilterQuery());
body.setDistributionSetId(rollout.getDistributionSet().getId());
body.setStatus(rollout.getStatus().toString().toLowerCase());
body.setTotalTargets(rollout.getTotalTargets());
for (final TotalTargetCountStatus.Status status : TotalTargetCountStatus.Status.values()) {
body.getTotalTargetsPerStatus().put(status.name().toLowerCase(),
rollout.getTotalTargetCountStatus().getTotalTargetCountByStatus(status));
}
body.add(linkTo(methodOn(RolloutResource.class).getRollout(rollout.getId())).withRel("self"));
body.add(linkTo(methodOn(RolloutResource.class).start(rollout.getId(), false)).withRel("start"));
body.add(linkTo(methodOn(RolloutResource.class).start(rollout.getId(), true)).withRel("startAsync"));
body.add(linkTo(methodOn(RolloutResource.class).pause(rollout.getId())).withRel("pause"));
body.add(linkTo(methodOn(RolloutResource.class).resume(rollout.getId())).withRel("resume"));
body.add(linkTo(methodOn(RolloutResource.class).getRolloutGroups(rollout.getId(),
Integer.parseInt(RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET),
Integer.parseInt(RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT), null, null)).withRel("groups"));
return body;
}
static Rollout fromRequest(final RolloutRestRequestBody restRequest, final DistributionSet distributionSet,
final String filterQuery) {
final Rollout rollout = new Rollout();
rollout.setName(restRequest.getName());
rollout.setDescription(restRequest.getDescription());
rollout.setDistributionSet(distributionSet);
rollout.setTargetFilterQuery(filterQuery);
final ActionType convertActionType = RestResourceConversionHelper.convertActionType(restRequest.getType());
if (convertActionType != null) {
rollout.setActionType(convertActionType);
}
if (restRequest.getForcetime() != null) {
rollout.setForcedTime(restRequest.getForcetime());
}
return rollout;
}
static List<RolloutGroupResponseBody> toResponseRolloutGroup(final List<RolloutGroup> rollouts) {
final List<RolloutGroupResponseBody> result = new ArrayList<>(rollouts.size());
rollouts.forEach(r -> result.add(toResponseRolloutGroup(r)));
return result;
}
static RolloutGroupResponseBody toResponseRolloutGroup(final RolloutGroup rolloutGroup) {
final RolloutGroupResponseBody body = new RolloutGroupResponseBody();
body.setCreatedAt(rolloutGroup.getCreatedAt());
body.setCreatedBy(rolloutGroup.getCreatedBy());
body.setDescription(rolloutGroup.getDescription());
body.setLastModifiedAt(rolloutGroup.getLastModifiedAt());
body.setLastModifiedBy(rolloutGroup.getLastModifiedBy());
body.setName(rolloutGroup.getName());
body.setRolloutGroupId(rolloutGroup.getId());
body.setStatus(rolloutGroup.getStatus().toString().toLowerCase());
body.add(linkTo(methodOn(RolloutResource.class).getRolloutGroup(rolloutGroup.getRollout().getId(),
rolloutGroup.getId())).withRel("self"));
return body;
}
static RolloutGroupErrorCondition mapErrorCondition(final Condition condition) {
if (Condition.THRESHOLD.equals(condition)) {
return RolloutGroupErrorCondition.THRESHOLD;
}
throw new IllegalArgumentException(createIllegalArgumentLiteral(condition));
}
static RolloutGroupSuccessCondition mapFinishCondition(final Condition condition) {
if (Condition.THRESHOLD.equals(condition)) {
return RolloutGroupSuccessCondition.THRESHOLD;
}
throw new IllegalArgumentException(createIllegalArgumentLiteral(condition));
}
static Condition map(final RolloutGroupSuccessCondition rolloutCondition) {
if (RolloutGroupSuccessCondition.THRESHOLD.equals(rolloutCondition)) {
return Condition.THRESHOLD;
}
throw new IllegalArgumentException("Rollout group condition " + rolloutCondition + NOT_SUPPORTED);
}
static RolloutGroupErrorAction map(final ErrorAction action) {
if (ErrorAction.PAUSE.equals(action)) {
return RolloutGroupErrorAction.PAUSE;
}
throw new IllegalArgumentException("Error Action " + action + NOT_SUPPORTED);
}
static RolloutGroupSuccessAction map(final SuccessAction action) {
if (SuccessAction.NEXTGROUP.equals(action)) {
return RolloutGroupSuccessAction.NEXTGROUP;
}
throw new IllegalArgumentException("Success Action " + action + NOT_SUPPORTED);
}
private static String createIllegalArgumentLiteral(final Condition condition) {
return "Condition " + condition + NOT_SUPPORTED;
}
}

View File

@@ -0,0 +1,402 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.rest.resource;
import java.util.List;
import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.RolloutFields;
import org.eclipse.hawkbit.repository.RolloutGroupFields;
import org.eclipse.hawkbit.repository.RolloutGroupManagement;
import org.eclipse.hawkbit.repository.RolloutManagement;
import org.eclipse.hawkbit.repository.TargetFields;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupConditions;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupErrorAction;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupErrorCondition;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessAction;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.rsql.RSQLUtility;
import org.eclipse.hawkbit.rest.resource.model.rollout.RolloutPagedList;
import org.eclipse.hawkbit.rest.resource.model.rollout.RolloutResponseBody;
import org.eclipse.hawkbit.rest.resource.model.rollout.RolloutRestRequestBody;
import org.eclipse.hawkbit.rest.resource.model.rolloutgroup.RolloutGroupPagedList;
import org.eclipse.hawkbit.rest.resource.model.rolloutgroup.RolloutGroupResponseBody;
import org.eclipse.hawkbit.rest.resource.model.target.TargetPagedList;
import org.eclipse.hawkbit.rest.resource.model.target.TargetRest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* REST Resource handling rollout CRUD operations.
*
*/
@RestController
@RequestMapping(RestConstants.ROLLOUT_V1_REQUEST_MAPPING)
public class RolloutResource {
@Autowired
private RolloutManagement rolloutManagement;
@Autowired
private RolloutGroupManagement rolloutGroupManagement;
@Autowired
private DistributionSetManagement distributionSetManagement;
/**
* Handles the GET request of retrieving all rollouts.
*
* @param pagingOffsetParam
* the offset of list of rollouts for pagination, might not be
* present in the rest request then default value will be applied
* @param pagingLimitParam
* the limit of the paged request, might not be present in the
* rest request then default value will be applied
* @param sortParam
* the sorting parameter in the request URL, syntax
* {@code field:direction, field:direction}
* @param rsqlParam
* the search parameter in the request URL, syntax
* {@code q=name==abc}
* @return a list of all rollouts for a defined or default page request with
* status OK. The response is always paged. In any failure the
* JsonResponseExceptionHandler is handling the response.
*/
@RequestMapping(method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_VALUE, "application/hal+json" })
public ResponseEntity<RolloutPagedList> getRollouts(
@RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam,
@RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam,
@RequestParam(value = RestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam,
@RequestParam(value = RestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam) {
final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam);
final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam);
final Sort sorting = PagingUtility.sanitizeRolloutSortParam(sortParam);
final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting);
final Page<Rollout> findModulesAll;
if (rsqlParam != null) {
findModulesAll = rolloutManagement
.findAllWithDetailedStatusByPredicate(RSQLUtility.parse(rsqlParam, RolloutFields.class), pageable);
} else {
findModulesAll = rolloutManagement.findAll(pageable);
}
final List<RolloutResponseBody> rest = RolloutMapper.toResponseRollout(findModulesAll.getContent());
return new ResponseEntity<>(new RolloutPagedList(rest, findModulesAll.getTotalElements()), HttpStatus.OK);
}
/**
* Handles the GET request of retrieving a single rollout.
*
* @param rolloutId
* the ID of the rollout to retrieve
* @return a single rollout with status OK.
* @throws EntityNotFoundException
* in case no rollout with the given {@code rolloutId} exists.
*/
@RequestMapping(value = "/{rolloutId}", method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_VALUE,
"application/hal+json" })
public ResponseEntity<RolloutResponseBody> getRollout(@PathVariable("rolloutId") final Long rolloutId) {
final Rollout findRolloutById = findRolloutOrThrowException(rolloutId);
return new ResponseEntity<>(RolloutMapper.toResponseRollout(findRolloutById), HttpStatus.OK);
}
/**
* Handles the POST request for creating rollout.
*
* @param rollout
* the rollout body to be created.
* @return In case rollout could successful created the ResponseEntity with
* status code 201 with the successfully created rollout. In any
* failure the JsonResponseExceptionHandler is handling the
* response.
* @throws EntityNotFoundException
*/
@RequestMapping(method = RequestMethod.POST, consumes = { "application/hal+json",
MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE })
public ResponseEntity<RolloutResponseBody> create(@RequestBody final RolloutRestRequestBody rolloutRequestBody) {
// first check the given RSQL query if it's well formed, otherwise and
// exception is thrown
RSQLUtility.isValid(rolloutRequestBody.getTargetFilterQuery());
final DistributionSet distributionSet = findDistributionSetOrThrowException(rolloutRequestBody);
// success condition
RolloutGroupSuccessCondition successCondition = null;
String successConditionExpr = null;
// success action
RolloutGroupSuccessAction successAction = null;
String successActionExpr = null;
// error condition
RolloutGroupErrorCondition errorCondition = null;
// error action
String errorConditionExpr = null;
RolloutGroupErrorAction errorAction = null;
String errorActionExpr = null;
if (rolloutRequestBody.getSuccessCondition() != null) {
successCondition = RolloutMapper
.mapFinishCondition(rolloutRequestBody.getSuccessCondition().getCondition());
successConditionExpr = rolloutRequestBody.getSuccessCondition().getExpression();
}
if (rolloutRequestBody.getSuccessAction() != null) {
successAction = RolloutMapper.map(rolloutRequestBody.getSuccessAction().getAction());
successActionExpr = rolloutRequestBody.getSuccessAction().getExpression();
}
if (rolloutRequestBody.getErrorCondition() != null) {
errorCondition = RolloutMapper.mapErrorCondition(rolloutRequestBody.getErrorCondition().getCondition());
errorConditionExpr = rolloutRequestBody.getErrorCondition().getExpression();
}
if (rolloutRequestBody.getErrorAction() != null) {
errorAction = RolloutMapper.map(rolloutRequestBody.getErrorAction().getAction());
errorActionExpr = rolloutRequestBody.getErrorAction().getExpression();
}
final RolloutGroupConditions rolloutGroupConditions = new RolloutGroup.RolloutGroupConditionBuilder()
.successCondition(successCondition, successConditionExpr)
.successAction(successAction, successActionExpr).errorCondition(errorCondition, errorConditionExpr)
.errorAction(errorAction, errorActionExpr).build();
final Rollout rollout = rolloutManagement.createRollout(
RolloutMapper.fromRequest(rolloutRequestBody, distributionSet,
rolloutRequestBody.getTargetFilterQuery()),
rolloutRequestBody.getAmountGroups(), rolloutGroupConditions);
return ResponseEntity.status(HttpStatus.CREATED).body(RolloutMapper.toResponseRollout(rollout));
}
/**
* Handles the POST request for starting a rollout.
*
* @param rolloutId
* the ID of the rollout to be started.
* @return OK response (200) if rollout could be started. In case of any
* exception the corresponding errors occur.
* @throws EntityNotFoundException
* @see RolloutManagement#startRollout(Rollout)
* @see ResponseExceptionHandler
*/
@RequestMapping(method = RequestMethod.POST, value = "/{rolloutId}/start", produces = { "application/hal+json",
MediaType.APPLICATION_JSON_VALUE })
public ResponseEntity<Void> start(@PathVariable("rolloutId") final Long rolloutId,
@RequestParam(value = RestConstants.REQUEST_PARAMETER_ASYNC, defaultValue = "false") final boolean startAsync) {
final Rollout rollout = findRolloutOrThrowException(rolloutId);
if (startAsync) {
rolloutManagement.startRolloutAsync(rollout);
} else {
rolloutManagement.startRollout(rollout);
}
return ResponseEntity.ok().build();
}
/**
* Handles the POST request for pausing a rollout.
*
* @param rolloutId
* the ID of the rollout to be paused.
* @return OK response (200) if rollout could be paused. In case of any
* exception the corresponding errors occur.
* @throws EntityNotFoundException
* @see RolloutManagement#pauseRollout(Rollout)
* @see ResponseExceptionHandler
*/
@RequestMapping(method = RequestMethod.POST, value = "/{rolloutId}/pause", produces = { "application/hal+json",
MediaType.APPLICATION_JSON_VALUE })
public ResponseEntity<Void> pause(@PathVariable("rolloutId") final Long rolloutId) {
final Rollout rollout = findRolloutOrThrowException(rolloutId);
rolloutManagement.pauseRollout(rollout);
return ResponseEntity.ok().build();
}
/**
* Handles the POST request for resuming a rollout.
*
* @param rolloutId
* the ID of the rollout to be resumed.
* @return OK response (200) if rollout could be resumed. In case of any
* exception the corresponding errors occur.
* @throws EntityNotFoundException
* @see RolloutManagement#resumeRollout(Rollout)
* @see ResponseExceptionHandler
*/
@RequestMapping(method = RequestMethod.POST, value = "/{rolloutId}/resume", produces = { "application/hal+json",
MediaType.APPLICATION_JSON_VALUE })
public ResponseEntity<Void> resume(@PathVariable("rolloutId") final Long rolloutId) {
final Rollout rollout = findRolloutOrThrowException(rolloutId);
rolloutManagement.resumeRollout(rollout);
return ResponseEntity.ok().build();
}
/**
* Handles the GET request of retrieving all rollout groups referred to a
* rollout.
*
* @param pagingOffsetParam
* the offset of list of rollout groups for pagination, might not
* be present in the rest request then default value will be
* applied
* @param pagingLimitParam
* the limit of the paged request, might not be present in the
* rest request then default value will be applied
* @param sortParam
* the sorting parameter in the request URL, syntax
* {@code field:direction, field:direction}
* @param rsqlParam
* the search parameter in the request URL, syntax
* {@code q=name==abc}
* @return a list of all rollout groups referred to a rollout for a defined
* or default page request with status OK. The response is always
* paged. In any failure the JsonResponseExceptionHandler is
* handling the response.
*/
@RequestMapping(method = RequestMethod.GET, value = "/{rolloutId}/deploygroups", produces = {
MediaType.APPLICATION_JSON_VALUE, "application/hal+json" })
public ResponseEntity<RolloutGroupPagedList> getRolloutGroups(@PathVariable("rolloutId") final Long rolloutId,
@RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam,
@RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam,
@RequestParam(value = RestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam,
@RequestParam(value = RestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam) {
final Rollout rollout = findRolloutOrThrowException(rolloutId);
final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam);
final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam);
final Sort sorting = PagingUtility.sanitizeRolloutGroupSortParam(sortParam);
final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting);
final Page<RolloutGroup> findRolloutGroupsAll;
if (rsqlParam != null) {
findRolloutGroupsAll = rolloutGroupManagement.findRolloutGroupsByPredicate(rollout,
RSQLUtility.parse(rsqlParam, RolloutGroupFields.class), pageable);
} else {
findRolloutGroupsAll = rolloutGroupManagement.findRolloutGroupsByRolloutId(rolloutId, pageable);
}
final List<RolloutGroupResponseBody> rest = RolloutMapper
.toResponseRolloutGroup(findRolloutGroupsAll.getContent());
return new ResponseEntity<>(new RolloutGroupPagedList(rest, findRolloutGroupsAll.getTotalElements()),
HttpStatus.OK);
}
/**
* Handles the GET request for retrieving a single rollout group.
*
* @param rolloutId
* the rolloutId to retrieve the group from
* @param groupId
* the groupId to retrieve the rollout group
* @return the OK response containing the {@link RolloutGroupResponseBody}
* @throws EntityNotFoundException
*/
@RequestMapping(method = RequestMethod.GET, value = "/{rolloutId}/deploygroups/{groupId}", produces = {
MediaType.APPLICATION_JSON_VALUE, "application/hal+json" })
public ResponseEntity<RolloutGroupResponseBody> getRolloutGroup(@PathVariable("rolloutId") final Long rolloutId,
@PathVariable("groupId") final Long groupId) {
findRolloutOrThrowException(rolloutId);
final RolloutGroup rolloutGroup = findRolloutGroupOrThrowException(groupId);
return ResponseEntity.ok(RolloutMapper.toResponseRolloutGroup(rolloutGroup));
}
/**
* Retrieves all targets related to a specific rollout group.
*
* @param rolloutId
* the ID of the rollout
* @param groupId
* the ID of the rollout group
* @param pagingOffsetParam
* the offset of list of rollout groups for pagination, might not
* be present in the rest request then default value will be
* applied
* @param pagingLimitParam
* the limit of the paged request, might not be present in the
* rest request then default value will be applied
* @param sortParam
* the sorting parameter in the request URL, syntax
* {@code field:direction, field:direction}
* @param rsqlParam
* the search parameter in the request URL, syntax
* {@code q=name==abc}
* @return a paged list of targets related to a specific rollout and rollout
* group.
*/
@RequestMapping(method = RequestMethod.GET, value = "/{rolloutId}/deploygroups/{groupId}/targets", produces = {
MediaType.APPLICATION_JSON_VALUE, "application/hal+json" })
public ResponseEntity<TargetPagedList> getRolloutGroupTargets(@PathVariable("rolloutId") final Long rolloutId,
@PathVariable("groupId") final Long groupId,
@RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam,
@RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam,
@RequestParam(value = RestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam,
@RequestParam(value = RestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam) {
findRolloutOrThrowException(rolloutId);
final RolloutGroup rolloutGroup = findRolloutGroupOrThrowException(groupId);
final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam);
final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam);
final Sort sorting = PagingUtility.sanitizeTargetSortParam(sortParam);
final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting);
final Page<Target> rolloutGroupTargets;
if (rsqlParam != null) {
final Specification<Target> rsqlSpecification = RSQLUtility.parse(rsqlParam, TargetFields.class);
rolloutGroupTargets = rolloutGroupManagement.findRolloutGroupTargets(rolloutGroup, rsqlSpecification,
pageable);
} else {
final Page<Target> pageTargets = rolloutGroupManagement.findRolloutGroupTargets(rolloutGroup, pageable);
rolloutGroupTargets = pageTargets;
}
final List<TargetRest> rest = TargetMapper.toResponse(rolloutGroupTargets.getContent());
return new ResponseEntity<>(new TargetPagedList(rest, rolloutGroupTargets.getTotalElements()), HttpStatus.OK);
}
private Rollout findRolloutOrThrowException(final Long rolloutId) {
final Rollout rollout = rolloutManagement.findRolloutWithDetailedStatus(rolloutId);
if (rollout == null) {
throw new EntityNotFoundException("Rollout with Id {" + rolloutId + "} does not exist");
}
return rollout;
}
private RolloutGroup findRolloutGroupOrThrowException(final Long rolloutGroupId) {
final RolloutGroup rolloutGroup = rolloutGroupManagement.findRolloutGroupById(rolloutGroupId);
if (rolloutGroup == null) {
throw new EntityNotFoundException("Group with Id {" + rolloutGroupId + "} does not exist");
}
return rolloutGroup;
}
private DistributionSet findDistributionSetOrThrowException(final RolloutRestRequestBody rolloutRequestBody) {
final DistributionSet ds = distributionSetManagement
.findDistributionSetById(rolloutRequestBody.getDistributionSetId());
if (ds == null) {
throw new EntityNotFoundException(
"DistributionSet with Id {" + rolloutRequestBody.getDistributionSetId() + "} does not exist");
}
return ds;
}
}

View File

@@ -16,6 +16,7 @@ import java.util.stream.Collectors;
import org.apache.commons.lang3.RandomStringUtils;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupConditions;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.repository.model.Target;
@@ -40,9 +41,9 @@ public abstract class JsonBuilder {
try {
builder.append(new JSONObject().put("name", module.getName())
.put("description", module.getDescription()).put("type", module.getType().getKey())
.put("id", Long.MAX_VALUE).put("vendor", module.getVendor()).put("version", module.getVersion())
.put("createdAt", "0").put("updatedAt", "0").put("createdBy", "fghdfkjghdfkjh")
.put("updatedBy", "fghdfkjghdfkjh").toString());
.put("id", Long.MAX_VALUE).put("vendor", module.getVendor())
.put("version", module.getVersion()).put("createdAt", "0").put("updatedAt", "0")
.put("createdBy", "fghdfkjghdfkjh").put("updatedBy", "fghdfkjghdfkjh").toString());
} catch (final Exception e) {
e.printStackTrace();
}
@@ -184,13 +185,15 @@ public abstract class JsonBuilder {
final List<String> messages = new ArrayList<String>();
messages.add(message);
return new JSONObject().put("id", id).put("time", "20140511T121314")
return new JSONObject()
.put("id", id)
.put("time", "20140511T121314")
.put("status",
new JSONObject().put("execution", execution)
new JSONObject()
.put("execution", execution)
.put("result",
new JSONObject().put("finished", finished).put("progress",
new JSONObject().put("cnt", 2).put("of", 5)))
.put("details", messages))
new JSONObject().put("cnt", 2).put("of", 5))).put("details", messages))
.toString();
}
@@ -378,9 +381,9 @@ public abstract class JsonBuilder {
for (final Target target : targets) {
try {
builder.append(new JSONObject().put("controllerId", target.getControllerId())
.put("description", target.getDescription()).put("name", target.getName()).put("createdAt", "0")
.put("updatedAt", "0").put("createdBy", "fghdfkjghdfkjh").put("updatedBy", "fghdfkjghdfkjh")
.toString());
.put("description", target.getDescription()).put("name", target.getName())
.put("createdAt", "0").put("updatedAt", "0").put("createdBy", "fghdfkjghdfkjh")
.put("updatedBy", "fghdfkjghdfkjh").toString());
} catch (final Exception e) {
e.printStackTrace();
}
@@ -395,6 +398,40 @@ public abstract class JsonBuilder {
return builder.toString();
}
public static String rollout(final String name, final String description, final int groupSize,
final long distributionSetId, final String targetFilterQuery, final RolloutGroupConditions conditions) {
final JSONObject json = new JSONObject();
json.put("name", name);
json.put("description", description);
json.put("amountGroups", groupSize);
json.put("distributionSetId", distributionSetId);
json.put("targetFilterQuery", targetFilterQuery);
if (conditions != null) {
final JSONObject successCondition = new JSONObject();
json.put("successCondition", successCondition);
successCondition.put("condition", conditions.getSuccessCondition().toString());
successCondition.put("expression", conditions.getSuccessConditionExp().toString());
final JSONObject successAction = new JSONObject();
json.put("successAction", successAction);
successAction.put("action", conditions.getSuccessAction().toString());
successAction.put("expression", conditions.getSuccessActionExp().toString());
final JSONObject errorCondition = new JSONObject();
json.put("errorCondition", errorCondition);
errorCondition.put("condition", conditions.getErrorCondition().toString());
errorCondition.put("expression", conditions.getErrorConditionExp().toString());
final JSONObject errorAction = new JSONObject();
json.put("errorAction", errorAction);
errorAction.put("action", conditions.getErrorAction().toString());
errorAction.put("expression", conditions.getErrorActionExp().toString());
}
return json.toString();
}
public static String cancelActionFeedback(final String id, final String execution) throws JSONException {
return cancelActionFeedback(id, execution, RandomStringUtils.randomAscii(1000));
@@ -404,7 +441,9 @@ public abstract class JsonBuilder {
throws JSONException {
final List<String> messages = new ArrayList<String>();
messages.add(message);
return new JSONObject().put("id", id).put("time", "20140511T121314")
return new JSONObject()
.put("id", id)
.put("time", "20140511T121314")
.put("status",
new JSONObject().put("execution", execution)
.put("result", new JSONObject().put("finished", "success")).put("details", messages))
@@ -414,12 +453,13 @@ public abstract class JsonBuilder {
public static String configData(final String id, final Map<String, String> attributes, final String execution)
throws JSONException {
return new JSONObject().put("id", id).put("time", "20140511T121314")
return new JSONObject()
.put("id", id)
.put("time", "20140511T121314")
.put("status",
new JSONObject().put("execution", execution)
.put("result", new JSONObject().put("finished", "success"))
.put("details", new ArrayList<String>()))
.put("data", attributes).toString();
.put("details", new ArrayList<String>())).put("data", attributes).toString();
}

View File

@@ -0,0 +1,598 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.rest.resource;
import static org.fest.assertions.api.Assertions.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.notNullValue;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.List;
import java.util.concurrent.Callable;
import org.eclipse.hawkbit.AbstractIntegrationTest;
import org.eclipse.hawkbit.MockMvcResultPrinter;
import org.eclipse.hawkbit.TestDataUtil;
import org.eclipse.hawkbit.WithUser;
import org.eclipse.hawkbit.repository.RolloutGroupManagement;
import org.eclipse.hawkbit.repository.RolloutManagement;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupConditionBuilder;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition;
import org.eclipse.hawkbit.repository.model.Target;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Description;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.http.MediaType;
import ru.yandex.qatools.allure.annotations.Features;
import ru.yandex.qatools.allure.annotations.Stories;
/**
* Tests for covering the {@link RolloutResource}.
*/
@Features("Component Tests - Management RESTful API")
@Stories("Rollout Resource")
public class RolloutResourceTest extends AbstractIntegrationTest {
@Autowired
private RolloutManagement rolloutManagement;
@Autowired
private RolloutGroupManagement rolloutGroupManagement;
@Test
@Description("Testing that creating rollout with wrong body returns bad request")
public void createRolloutWithInvalidBodyReturnsBadRequest() throws Exception {
mvc.perform(post("/rest/v1/rollouts").content("invalid body").contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isBadRequest())
.andExpect(jsonPath("errorCode", equalTo("hawkbit.server.error.rest.body.notReadable")));
}
@Test
@Description("Testing that creating rollout with insufficient permission returns forbidden")
@WithUser(allSpPermissions = true, removeFromAllPermission = "ROLLOUT_MANAGEMENT")
public void createRolloutWithInsufficientPermissionReturnsForbidden() throws Exception {
final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
mvc.perform(post("/rest/v1/rollouts")
.content(JsonBuilder.rollout("name", "desc", 10, dsA.getId(), "name==test", null))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().is(403)).andReturn();
}
@Test
@Description("Testing that creating rollout with not exisiting distribution set returns not found")
public void createRolloutWithNotExistingDistributionSetReturnsNotFound() throws Exception {
mvc.perform(post("/rest/v1/rollouts").content(JsonBuilder.rollout("name", "desc", 10, 1234, "name==test", null))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isNotFound()).andReturn();
}
@Test
@Description("Testing that creating rollout with not valid formed target filter query returns bad request")
public void createRolloutWithNotWellFormedFilterReturnsBadRequest() throws Exception {
final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
mvc.perform(post("/rest/v1/rollouts")
.content(JsonBuilder.rollout("name", "desc", 10, dsA.getId(), "name=test", null))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isBadRequest())
.andExpect(jsonPath("errorCode", equalTo("hawkbit.server.error.rest.param.rsqlParamSyntax")))
.andReturn();
}
@Description("TODO")
public void missingTargetFilterQueryInRollout() throws Exception {
final String targetFilterQuery = null;
final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
mvc.perform(post("/rest/v1/rollouts")
.content(JsonBuilder.rollout("rollout1", "desc", 10, dsA.getId(), targetFilterQuery, null))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isBadRequest())
.andExpect(jsonPath("errorCode", equalTo("hawkbit.server.error.rest.param.rsqlParamSyntax")))
.andReturn();
}
@Test
@Description("Testing that rollout can be created")
public void createRollout() throws Exception {
final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
postRollout("rollout1", 10, dsA.getId(), "name==target1");
}
@Test
@Description("Testing the empty list is returned if no rollout exists")
public void noRolloutReturnsEmptyList() throws Exception {
mvc.perform(get("/rest/v1/rollouts")).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$content", hasSize(0))).andExpect(jsonPath("$total", equalTo(0)));
}
@Test
@Description("Testing that rollout paged list contains rollouts")
public void rolloutPagedListContainsAllRollouts() throws Exception {
final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
// setup - create 2 rollouts
postRollout("rollout1", 10, dsA.getId(), "name==target1");
postRollout("rollout2", 5, dsA.getId(), "name==target2");
mvc.perform(get("/rest/v1/rollouts")).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$content", hasSize(2))).andExpect(jsonPath("$total", equalTo(2)))
.andExpect(jsonPath("content[0].name", equalTo("rollout1")))
.andExpect(jsonPath("content[0].status", equalTo("ready")))
.andExpect(jsonPath("content[0].targetFilterQuery", equalTo("name==target1")))
.andExpect(jsonPath("content[0].distributionSetId", equalTo(dsA.getId().intValue())))
.andExpect(jsonPath("content[1].name", equalTo("rollout2")))
.andExpect(jsonPath("content[1].status", equalTo("ready")))
.andExpect(jsonPath("content[1].targetFilterQuery", equalTo("name==target2")))
.andExpect(jsonPath("content[1].distributionSetId", equalTo(dsA.getId().intValue())));
}
@Test
@Description("Testing that rollout paged list is limited by the query param limit")
public void rolloutPagedListIsLimitedToQueryParam() throws Exception {
final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
// setup - create 2 rollouts
postRollout("rollout1", 10, dsA.getId(), "name==target1");
postRollout("rollout2", 5, dsA.getId(), "name==target2");
mvc.perform(get("/rest/v1/rollouts?limit=1")).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$content", hasSize(1))).andExpect(jsonPath("$total", equalTo(2)));
}
@Test
@Description("Testing that rollout paged list is limited by the query param limit")
public void retrieveRolloutGroupsForSpecificRollout() throws Exception {
// setup
final int amountTargets = 20;
targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargets, "rollout", "rollout"));
final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
// create rollout including the created targets with prefix 'rollout'
final Rollout rollout = createRollout("rollout1", 4, dsA.getId(), "controllerId==rollout*");
// retrieve rollout groups from created rollout
mvc.perform(get("/rest/v1/rollouts/{rolloutId}/deploygroups", rollout.getId()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$content", hasSize(4))).andExpect(jsonPath("$total", equalTo(4)))
.andExpect(jsonPath("$content[0].status", equalTo("ready")))
.andExpect(jsonPath("$content[1].status", equalTo("ready")))
.andExpect(jsonPath("$content[2].status", equalTo("ready")))
.andExpect(jsonPath("$content[3].status", equalTo("ready")));
}
@Test
@Description("Testing that starting the rollout switches the state to running")
public void startingRolloutSwitchesIntoRunningState() throws Exception {
// setup
final int amountTargets = 20;
targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargets, "rollout", "rollout"));
final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
// create rollout including the created targets with prefix 'rollout'
final Rollout rollout = createRollout("rollout1", 4, dsA.getId(), "controllerId==rollout*");
// starting rollout
mvc.perform(post("/rest/v1/rollouts/{rolloutId}/start", rollout.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
// check rollout is in running state
mvc.perform(get("/rest/v1/rollouts/{rolloutId}", rollout.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("id", equalTo(rollout.getId().intValue())))
.andExpect(jsonPath("status", equalTo("running")));
}
@Test
@Description("Testing that pausing the rollout switches the state to paused")
public void pausingRolloutSwitchesIntoPausedState() throws Exception {
// setup
final int amountTargets = 20;
targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargets, "rollout", "rollout"));
final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
// create rollout including the created targets with prefix 'rollout'
final Rollout rollout = createRollout("rollout1", 4, dsA.getId(), "controllerId==rollout*");
// starting rollout
mvc.perform(post("/rest/v1/rollouts/{rolloutId}/start", rollout.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
// pausing rollout
mvc.perform(post("/rest/v1/rollouts/{rolloutId}/pause", rollout.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
// check rollout is in running state
mvc.perform(get("/rest/v1/rollouts/{rolloutId}", rollout.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("id", equalTo(rollout.getId().intValue())))
.andExpect(jsonPath("status", equalTo("paused")));
}
@Test
@Description("Testing that resuming the rollout switches the state to running")
public void resumingRolloutSwitchesIntoRunningState() throws Exception {
// setup
final int amountTargets = 20;
targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargets, "rollout", "rollout"));
final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
// create rollout including the created targets with prefix 'rollout'
final Rollout rollout = createRollout("rollout1", 4, dsA.getId(), "controllerId==rollout*");
// starting rollout
mvc.perform(post("/rest/v1/rollouts/{rolloutId}/start", rollout.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
// pausing rollout
mvc.perform(post("/rest/v1/rollouts/{rolloutId}/pause", rollout.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
// resume rollout
mvc.perform(post("/rest/v1/rollouts/{rolloutId}/resume", rollout.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
// check rollout is in running state
mvc.perform(get("/rest/v1/rollouts/{rolloutId}", rollout.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("id", equalTo(rollout.getId().intValue())))
.andExpect(jsonPath("status", equalTo("running")));
}
@Test
@Description("Testing that an already started rollout cannot be started again and returns bad request")
public void startingAlreadyStartedRolloutReturnsBadRequest() throws Exception {
// setup
final int amountTargets = 20;
targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargets, "rollout", "rollout"));
final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
// create rollout including the created targets with prefix 'rollout'
final Rollout rollout = createRollout("rollout1", 4, dsA.getId(), "controllerId==rollout*");
// starting rollout
mvc.perform(post("/rest/v1/rollouts/{rolloutId}/start", rollout.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
// starting rollout - already started should lead into bad request
mvc.perform(post("/rest/v1/rollouts/{rolloutId}/start", rollout.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isBadRequest())
.andExpect(jsonPath("errorCode", equalTo("hawkbit.server.error.rollout.illegalstate")));
}
@Test
@Description("Testing that resuming a rollout which is not started leads to bad request")
public void resumingNotStartedRolloutReturnsBadRequest() throws Exception {
// setup
final int amountTargets = 20;
targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargets, "rollout", "rollout"));
final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
// create rollout including the created targets with prefix 'rollout'
final Rollout rollout = createRollout("rollout1", 4, dsA.getId(), "controllerId==rollout*");
// resume not yet started rollout
mvc.perform(post("/rest/v1/rollouts/{rolloutId}/resume", rollout.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isBadRequest())
.andExpect(jsonPath("errorCode", equalTo("hawkbit.server.error.rollout.illegalstate")));
}
@Test
@Description("Testing that starting rollout the first rollout group is in running state")
public void startingRolloutFirstRolloutGroupIsInRunningState() throws Exception {
// setup
final int amountTargets = 10;
targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargets, "rollout", "rollout"));
final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
// create rollout including the created targets with prefix 'rollout'
final Rollout rollout = createRollout("rollout1", 2, dsA.getId(), "controllerId==rollout*");
// starting rollout
mvc.perform(post("/rest/v1/rollouts/{rolloutId}/start", rollout.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
// retrieve rollout groups from created rollout - 2 groups exists
// (amountTargets / groupSize = 2)
mvc.perform(get("/rest/v1/rollouts/{rolloutId}/deploygroups?sort=ID:ASC", rollout.getId()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$content", hasSize(2))).andExpect(jsonPath("$total", equalTo(2)))
.andExpect(jsonPath("$content[0].status", equalTo("running")))
.andExpect(jsonPath("$content[1].status", equalTo("scheduled")));
}
@Test
@Description("Testing that a single rollout group can be retrieved")
public void retrieveSingleRolloutGroup() throws Exception {
// setup
final int amountTargets = 10;
targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargets, "rollout", "rollout"));
final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
// create rollout including the created targets with prefix 'rollout'
final Rollout rollout = createRollout("rollout1", 4, dsA.getId(), "controllerId==rollout*");
final RolloutGroup firstGroup = rolloutGroupManagement
.findRolloutGroupsByRolloutId(rollout.getId(), new PageRequest(0, 1, Direction.ASC, "id")).getContent()
.get(0);
// retrieve single rollout group with known ID
mvc.perform(get("/rest/v1/rollouts/{rolloutId}/deploygroups/{groupId}", rollout.getId(), firstGroup.getId()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("id", equalTo(firstGroup.getId().intValue())))
.andExpect(jsonPath("status", equalTo("ready"))).andExpect(jsonPath("name", notNullValue()));
}
@Test
@Description("Testing that the targets of rollout group can be retrieved")
public void retrieveTargetsFromRolloutGroup() throws Exception {
// setup
final int amountTargets = 10;
targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargets, "rollout", "rollout"));
final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
// create rollout including the created targets with prefix 'rollout'
final Rollout rollout = createRollout("rollout1", 2, dsA.getId(), "controllerId==rollout*");
final RolloutGroup firstGroup = rolloutGroupManagement
.findRolloutGroupsByRolloutId(rollout.getId(), new PageRequest(0, 1, Direction.ASC, "id")).getContent()
.get(0);
// retrieve targets from the first rollout group with known ID
mvc.perform(get("/rest/v1/rollouts/{rolloutId}/deploygroups/{groupId}/targets", rollout.getId(),
firstGroup.getId())).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$content", hasSize(5))).andExpect(jsonPath("$total", equalTo(5)));
}
@Test
@Description("Testing that the targets of rollout group can be retrieved with rsql query param")
public void retrieveTargetsFromRolloutGroupWithQuery() throws Exception {
// setup
final int amountTargets = 10;
final List<Target> targets = targetManagement
.createTargets(TestDataUtil.buildTargetFixtures(amountTargets, "rollout", "rollout"));
final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
// create rollout including the created targets with prefix 'rollout'
final Rollout rollout = createRollout("rollout1", 2, dsA.getId(), "controllerId==rollout*");
final RolloutGroup firstGroup = rolloutGroupManagement
.findRolloutGroupsByRolloutId(rollout.getId(), new PageRequest(0, 1, Direction.ASC, "id")).getContent()
.get(0);
// retrieve targets from the first rollout group with known ID
mvc.perform(
get("/rest/v1/rollouts/{rolloutId}/deploygroups/{groupId}/targets", rollout.getId(), firstGroup.getId())
.param("q", "controllerId==" + targets.get(0).getControllerId()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$content", hasSize(1))).andExpect(jsonPath("$total", equalTo(1)));
}
@Test
@Description("Testing that the targets of rollout group can be retrieved after the rollout has been started")
public void retrieveTargetsFromRolloutGroupAfterRolloutIsStarted() throws Exception {
// setup
final int amountTargets = 10;
targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargets, "rollout", "rollout"));
final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
// create rollout including the created targets with prefix 'rollout'
final Rollout rollout = createRollout("rollout1", 2, dsA.getId(), "controllerId==rollout*");
rolloutManagement.startRollout(rollout);
final RolloutGroup firstGroup = rolloutGroupManagement
.findRolloutGroupsByRolloutId(rollout.getId(), new PageRequest(0, 1, Direction.ASC, "id")).getContent()
.get(0);
// retrieve targets from the first rollout group with known ID
mvc.perform(get("/rest/v1/rollouts/{rolloutId}/deploygroups/{groupId}/targets", rollout.getId(),
firstGroup.getId())).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$content", hasSize(5))).andExpect(jsonPath("$total", equalTo(5)));
}
// TODO
@Test
@Description("Start the rollout in async mode")
public void startingRolloutSwitchesIntoRunningStateAsync() throws Exception {
final int amountTargets = 1000;
targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargets, "rollout", "rollout"));
final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
// create rollout including the created targets with prefix 'rollout'
final Rollout rollout = createRollout("rollout1", 4, dsA.getId(), "controllerId==rollout*");
// starting rollout
mvc.perform(post("/rest/v1/rollouts/{rolloutId}/start", rollout.getId())
.param(RestConstants.REQUEST_PARAMETER_ASYNC, "true")).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
// check if running
assertThat(doWithTimeout(() -> getRollout(rollout.getId()), result -> success(result), 5000, 100)).isNotNull();
}
@Test
@Description("Testing that rollout paged list with rsql parameter")
public void getRolloutWithRSQLParam() throws Exception {
final int amountTargetsRollout1 = 25;
final int amountTargetsRollout2 = 25;
final int amountTargetsRollout3 = 25;
final int amountTargetsOther = 25;
targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargetsRollout1, "rollout1", "rollout1"));
targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargetsRollout2, "rollout2", "rollout2"));
targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargetsRollout3, "rollout3", "rollout3"));
targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargetsOther, "other1", "other1"));
final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
createRollout("rollout1", 5, dsA.getId(), "controllerId==rollout1*");
final Rollout rollout2 = createRollout("rollout2", 5, dsA.getId(), "controllerId==rollout2*");
createRollout("rollout3", 5, dsA.getId(), "controllerId==rollout3*");
createRollout("other1", 5, dsA.getId(), "controllerId==other1*");
mvc.perform(get("/rest/v1/rollouts").param(RestConstants.REQUEST_PARAMETER_SEARCH, "name==*2"))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$content", hasSize(1))).andExpect(jsonPath("$total", equalTo(1)))
.andExpect(jsonPath("$content[0].name", equalTo(rollout2.getName())));
mvc.perform(get("/rest/v1/rollouts").param(RestConstants.REQUEST_PARAMETER_SEARCH, "name==rollout*"))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$content", hasSize(3))).andExpect(jsonPath("$total", equalTo(3)));
mvc.perform(get("/rest/v1/rollouts").param(RestConstants.REQUEST_PARAMETER_SEARCH, "name==*1"))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$content", hasSize(2))).andExpect(jsonPath("$total", equalTo(2)));
}
@Test
@Description("Testing that rolloutgroup paged list with rsql parameter")
public void retrieveRolloutGroupsForSpecificRolloutWithRSQLParam() throws Exception {
// setup
final int amountTargets = 20;
targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargets, "rollout", "rollout"));
final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
// create rollout including the created targets with prefix 'rollout'
final Rollout rollout = createRollout("rollout1", 4, dsA.getId(), "controllerId==rollout*");
// retrieve rollout groups from created rollout
mvc.perform(get("/rest/v1/rollouts/{rolloutId}/deploygroups", rollout.getId())
.param(RestConstants.REQUEST_PARAMETER_SEARCH, "name==group-1")).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$content", hasSize(1))).andExpect(jsonPath("$total", equalTo(1)))
.andExpect(jsonPath("$content[0].name", equalTo("group-1")));
mvc.perform(get("/rest/v1/rollouts/{rolloutId}/deploygroups", rollout.getId())
.param(RestConstants.REQUEST_PARAMETER_SEARCH, "name==group*")).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$content", hasSize(4))).andExpect(jsonPath("$total", equalTo(4)));
mvc.perform(get("/rest/v1/rollouts/{rolloutId}/deploygroups", rollout.getId())
.param(RestConstants.REQUEST_PARAMETER_SEARCH, "name==group-1,name==group-2"))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$content", hasSize(2))).andExpect(jsonPath("$total", equalTo(2)));
}
// TODO copied code from sp-bic-test
protected <T> T doWithTimeout(final Callable<T> callable, final SuccessCondition<T> successCondition,
final long timeout, final long pollInterval) throws Exception // NOPMD
{
if (pollInterval < 0) {
throw new IllegalArgumentException("pollInterval must non negative");
}
long duration = 0;
Exception exception = null;
T returnValue = null;
while (untilTimeoutReached(timeout, duration)) {
try {
returnValue = callable.call();
// clear exception
exception = null;
} catch (final Exception ex) {
exception = ex;
}
Thread.sleep(pollInterval);
duration += pollInterval > 0 ? pollInterval : 1;
if (exception == null && successCondition.success(returnValue)) {
return returnValue;
} else {
returnValue = null;
}
}
if (exception != null) {
throw exception;
}
return returnValue;
}
protected boolean untilTimeoutReached(final long timeout, final long duration) {
return duration <= timeout || timeout < 0;
}
private void postRollout(final String name, final int groupSize, final long distributionSetId,
final String targetFilterQuery) throws Exception {
mvc.perform(post("/rest/v1/rollouts")
.content(JsonBuilder.rollout(name, "desc", groupSize, distributionSetId, targetFilterQuery, null))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isCreated()).andReturn();
}
private Rollout createRollout(final String name, final int amountGroups, final long distributionSetId,
final String targetFilterQuery) {
final Rollout rollout = new Rollout();
rollout.setDistributionSet(distributionSetManagement.findDistributionSetById(distributionSetId));
rollout.setName(name);
rollout.setTargetFilterQuery(targetFilterQuery);
return rolloutManagement.createRollout(rollout, amountGroups, new RolloutGroupConditionBuilder()
.successCondition(RolloutGroupSuccessCondition.THRESHOLD, "100").build());
}
protected boolean success(final Rollout result) {
if (null != result && result.getStatus() == RolloutStatus.RUNNING) {
return true;
}
return false;
}
public Rollout getRollout(final Long rolloutId) throws Exception {
return rolloutManagement.findRolloutById(rolloutId);
}
}

View File

@@ -0,0 +1,26 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.rest.resource;
/**
*
* @author Dennis Melzer
*
* @param <T>
*/
public interface SuccessCondition<T> {
/**
*
* @param result
* @return
*/
boolean success(final T result);
}

View File

@@ -23,8 +23,7 @@ import org.springframework.security.core.GrantedAuthority;
* The Permissions cover CRUD for two data areas of SP:<br/>
* <br/>
* XX_Target_CRUD which covers the following entities: {@link Target} entities
* including metadata, {@link TargetTag}s, {@link Action}s,
* {@link TargetRegistrationRule}s<br/>
* including metadata, {@link TargetTag}s, {@link TargetRegistrationRule}s<br/>
* XX_Repository CRUD which covers: {@link DistributionSet}s,
* {@link SoftwareModule}s, DS Tags<br/>
* </p>
@@ -131,6 +130,11 @@ public final class SpPermission {
*/
public static final String TENANT_CONFIGURATION = "TENANT_CONFIGURATION";
/**
* Permission to administrate a rollout management.
*/
public static final String ROLLOUT_MANAGEMENT = "ROLLOUT_MANAGEMENT";
private SpPermission() {
// Constants only
}
@@ -178,6 +182,12 @@ public final class SpPermission {
*/
public static final String CONTROLLER_ROLE_ANONYMOUS = "ROLE_CONTROLLER_ANONYMOUS";
/**
* The role which contains the spring security context in case the
* system is executing code which is necessary to be privileged.
*/
public static final String SYSTEM_ROLE = "ROLE_SYSTEM_CODE";
/**
* The spring security eval expression operator {@code or}.
*/
@@ -265,8 +275,15 @@ public final class SpPermission {
* context contains the anoynmous role or the controller specific role
* {@link SpPermission#CONTROLLER_ROLE}.
*/
public static final String IS_CONTROLLER = "hasAnyRole('" + CONTROLLER_ROLE_ANONYMOUS + "', '" + CONTROLLER_ROLE
+ "')";
public static final String IS_CONTROLLER = "hasAnyRole('" + CONTROLLER_ROLE_ANONYMOUS + "', '"
+ CONTROLLER_ROLE + "')";
/**
* Spring security eval hasAnyRole expression to check if the spring
* context contains system code role
* {@link SpringEvalExpressions#SYSTEM_ROLE}.
*/
public static final String IS_SYSTEM_CODE = HAS_AUTH_PREFIX + SYSTEM_ROLE + HAS_AUTH_SUFFIX;
/**
* Spring security eval hasAuthority expression to check if spring
@@ -276,6 +293,21 @@ public final class SpPermission {
public static final String HAS_AUTH_CREATE_REPOSITORY_AND_CREATE_TARGET = HAS_AUTH_PREFIX + CREATE_REPOSITORY
+ HAS_AUTH_SUFFIX + HAS_AUTH_AND + HAS_AUTH_PREFIX + CREATE_TARGET + HAS_AUTH_SUFFIX;
/**
* Spring security eval hasAuthority expression to check if spring
* context contains {@link SpPermission#ROLLOUT_MANAGEMENT}
*/
public static final String HAS_AUTH_ROLLOUT_MANAGEMENT_READ = HAS_AUTH_PREFIX + ROLLOUT_MANAGEMENT
+ HAS_AUTH_SUFFIX;
/**
* Spring security eval hasAuthority expression to check if spring
* context contains {@link SpPermission#ROLLOUT_MANAGEMENT} and
* {@link SpPermission#UPDATE_TARGET}.
*/
public static final String HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE = HAS_AUTH_PREFIX + ROLLOUT_MANAGEMENT
+ HAS_AUTH_SUFFIX + HAS_AUTH_AND + HAS_AUTH_PREFIX + UPDATE_TARGET + HAS_AUTH_SUFFIX;
private SpringEvalExpressions() {
// utility class
}

View File

@@ -8,6 +8,8 @@
*/
package org.eclipse.hawkbit.security;
import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.springframework.security.core.Authentication;
@@ -27,6 +29,7 @@ import org.springframework.security.core.context.SecurityContextHolder;
public class SecurityContextTenantAware implements TenantAware {
private static final ThreadLocal<String> TENANT_THREAD_LOCAL = new ThreadLocal<>();
private static final ThreadLocal<AtomicInteger> RUN_AS_DEPTH = new ThreadLocal<>();
/*
* (non-Javadoc)
@@ -56,11 +59,21 @@ public class SecurityContextTenantAware implements TenantAware {
*/
@Override
public <T> T runAsTenant(final String tenant, final TenantRunner<T> callable) {
AtomicInteger runAsDepth = RUN_AS_DEPTH.get();
if (runAsDepth == null) {
runAsDepth = new AtomicInteger(1);
RUN_AS_DEPTH.set(runAsDepth);
} else {
runAsDepth.incrementAndGet();
}
TENANT_THREAD_LOCAL.set(tenant);
try {
return callable.run();
} finally {
TENANT_THREAD_LOCAL.remove();
if (runAsDepth.decrementAndGet() <= 0) {
RUN_AS_DEPTH.remove();
TENANT_THREAD_LOCAL.remove();
}
}
}
}

View File

@@ -0,0 +1,112 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.security;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.hawkbit.tenancy.TenantAware.TenantRunner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.stereotype.Service;
import com.google.common.base.Throwables;
/**
* @author Michael Hirsch
*
*/
@Service
public class SystemSecurityContext {
private static final Logger logger = LoggerFactory.getLogger(SystemSecurityContext.class);
@Autowired
private TenantAware tenantAware;
public <T> T runAsSystem(final Callable<T> callable) {
final SecurityContext oldContext = SecurityContextHolder.getContext();
try {
logger.debug("entering system code execution");
return tenantAware.runAsTenant(tenantAware.getCurrentTenant(), new TenantRunner<T>() {
@Override
public T run() {
try {
setSystemContext();
return callable.call();
} catch (final Exception e) {
throw Throwables.propagate(e);
}
}
});
} finally {
SecurityContextHolder.setContext(oldContext);
logger.debug("leaving system code execution");
}
}
private static void setSystemContext() {
final SecurityContextImpl securityContextImpl = new SecurityContextImpl();
securityContextImpl.setAuthentication(new SystemCodeAuthentication());
SecurityContextHolder.setContext(securityContextImpl);
}
public static class SystemCodeAuthentication implements Authentication {
private static final long serialVersionUID = 1L;
private static final List<SimpleGrantedAuthority> AUTHORITIES = Collections
.singletonList(new SimpleGrantedAuthority(SpringEvalExpressions.SYSTEM_ROLE));
@Override
public String getName() {
return null;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return AUTHORITIES;
}
@Override
public Object getCredentials() {
return null;
}
@Override
public Object getDetails() {
return null;
}
@Override
public Object getPrincipal() {
return null;
}
@Override
public boolean isAuthenticated() {
return true;
}
@Override
public void setAuthenticated(final boolean isAuthenticated) throws IllegalArgumentException {
}
}
}

View File

@@ -30,6 +30,7 @@
<!-- We are doing "inplace" gwt compilation but into subdir VAADIN/widgetsets -->
<webappDirectory>${project.build.directory}/classes/VAADIN/widgetsets</webappDirectory>
<hostedWebapp>${project.build.directory}/classes/VAADIN/widgetsets</hostedWebapp>
<warSourceDirectory>src/main/resources</warSourceDirectory>
<noServer>true</noServer>
<!-- Remove draftCompile when project is ready -->
<draftCompile>false</draftCompile>
@@ -212,6 +213,16 @@
<groupId>org.vaadin.addons</groupId>
<artifactId>tokenfield</artifactId>
</dependency>
<!-- To be mentioned in release notes -->
<dependency>
<groupId>org.vaadin.alump.distributionbar</groupId>
<artifactId>dbar-addon</artifactId>
</dependency>
<dependency>
<groupId>org.vaadin.addons</groupId>
<artifactId>contextmenu</artifactId>
</dependency>
<!-- To be mentioned in release notes -->
<!-- Test -->
<dependency>

View File

@@ -139,4 +139,42 @@ public class SpPermissionChecker implements Serializable {
return hasReadDistributionPermission() && permissionService.hasPermission(SpPermission.DELETE_REPOSITORY);
}
/**
* Gets the SP rollout create permission.
*
* @return permission for rollout update
*/
public boolean hasRolloutUpdatePermission() {
return hasUpdateTargetPermission() && hasReadDistributionPermission()
&& permissionService.hasPermission(SpPermission.ROLLOUT_MANAGEMENT);
}
/**
* Gets the SP rollout create permission.
*
* @return permission for rollout create
*/
public boolean hasRolloutCreatePermission() {
return hasUpdateTargetPermission() && hasReadDistributionPermission()
&& permissionService.hasPermission(SpPermission.ROLLOUT_MANAGEMENT);
}
/**
*
* Gets the SP rollout read permission.
*
* @return Gets the SP rollout read permission.
*/
public boolean hasRolloutReadPermission() {
return permissionService.hasPermission(SpPermission.ROLLOUT_MANAGEMENT);
}
/**
* Gets the SP rollout targets read permission.
*
* @return permission to read rollout targets
*/
public boolean hasRolloutTargetsReadPermission() {
return hasTargetReadPermission() && permissionService.hasPermission(SpPermission.ROLLOUT_MANAGEMENT);
}
}

View File

@@ -21,4 +21,10 @@
<inherits name="org.vaadin.tokenfield.TokenfieldWidgetset" />
<inherits name="org.vaadin.alump.distributionbar.gwt.DistributionBarWidgetset" />
<inherits name="org.vaadin.peter.contextmenu.ContextmenuWidgetset" />
</module>

View File

@@ -25,7 +25,7 @@ import com.vaadin.server.Resource;
*
*/
@Component
@Order(400)
@Order(500)
public class UploadArtifactViewMenuItem implements DashboardMenuItem {
private static final long serialVersionUID = 4096851897640769726L;

View File

@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.ui.components;
import java.net.URI;
import java.security.SecureRandom;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetIdName;
@@ -53,6 +54,8 @@ public class ProxyTarget extends Target {
private String modifiedByUser;
private Status status;
/**
* @param controllerId
*/
@@ -297,4 +300,19 @@ public class ProxyTarget extends Target {
public void setPollStatusToolTip(final String pollStatusToolTip) {
this.pollStatusToolTip = pollStatusToolTip;
}
/**
* @return the status
*/
public Status getStatus() {
return status;
}
/**
* @param status
* the status to set
*/
public void setStatus(final Status status) {
this.status = status;
}
}

View File

@@ -26,7 +26,7 @@ import com.vaadin.server.Resource;
*
*/
@Component
@Order(300)
@Order(400)
public class DistributionsViewMenuItem implements DashboardMenuItem {
private static final long serialVersionUID = -4048522766974227222L;

View File

@@ -45,7 +45,10 @@ public enum DocumentationPageLink {
TARGET_SECURITY_TOKEN("security.html", DocumentationUtil.DEVELOPERGUIDE, "concepts"),
// userguide/targetfilter
TARGET_FILTER_VIEW("targetfilter.html", DocumentationUtil.USERGUIDE);
TARGET_FILTER_VIEW("targetfilter.html", DocumentationUtil.USERGUIDE),
// userguide/ROLLOUT
ROLLOUT_VIEW("rollout.html", DocumentationUtil.USERGUIDE);
private static final String ROOT_PATH = "../documentation";

View File

@@ -186,7 +186,7 @@ public class CreateOrUpdateFilterTable extends Table {
columnList.add(new TableColumn(SPUILabelDefinitions.INSTALLED_DISTRIBUTION_NAME_VER,
i18n.get("header.installed.ds"), 0.125F));
columnList.add(new TableColumn(SPUILabelDefinitions.VAR_DESC, i18n.get("header.description"), 0.1F));
columnList.add(new TableColumn(SPUILabelDefinitions.STATUS_ICON, i18n.get("header.target.status"), 0.1F));
columnList.add(new TableColumn(SPUILabelDefinitions.STATUS_ICON, i18n.get("header.status"), 0.1F));
return columnList;
}

View File

@@ -26,7 +26,7 @@ import com.vaadin.server.Resource;
*
*/
@Component
@Order(200)
@Order(300)
public class FilterManagementViewMenuItem implements DashboardMenuItem {
private static final long serialVersionUID = -1272853053031512243L;

View File

@@ -63,20 +63,20 @@ public final class FilterQueryValidation {
setExceptionDetails(new Exception(ex.getCause().getCause()), expectedTokens, result, tokenDesc);
result.setMessage(getCustomMessage(ex.getCause().getMessage(), result.getExpectedTokens()));
result.setIsValidationFailed(Boolean.TRUE);
LOGGER.info("Syntax exception on parsing :", ex);
LOGGER.trace("Syntax exception on parsing :", ex);
} catch (final RSQLParserException ex) {
setExceptionDetails(ex, expectedTokens, result, tokenDesc);
result.setMessage(getCustomMessage(ex.getMessage(), result.getExpectedTokens()));
result.setIsValidationFailed(Boolean.TRUE);
LOGGER.info("Exception on parsing :", ex);
LOGGER.trace("Exception on parsing :", ex);
} catch (final IllegalArgumentException ex) {
result.setMessage(getCustomMessage(ex.getMessage(), null));
result.setIsValidationFailed(Boolean.TRUE);
LOGGER.info("Illegal argument on parsing :", ex);
LOGGER.trace("Illegal argument on parsing :", ex);
} catch (final RSQLParameterUnsupportedFieldException ex) {
result.setMessage(getCustomMessage(ex.getMessage(), null));
result.setIsValidationFailed(Boolean.TRUE);
LOGGER.info("Unsupported field on parsing :", ex);
LOGGER.trace("Unsupported field on parsing :", ex);
}
return result;

Some files were not shown because too many files have changed in this diff Show More