Semi automatic Rollouts with fine groups definition (#337)

* Rollout Mgmt API accepts now extended Group definition. Filling Reollout Groups with Targets is now a scheduled task.

Signed-off-by: Dominik Herbst <dominik.herbst@bosch-si.com>

* Fire RolloutGroupCreated event and fix db migration.

Signed-off-by: Dominik Herbst <dominik.herbst@bosch-si.com>

* Fill groups now excludes targets in own group

Signed-off-by: Dominik Herbst <dominik.herbst@bosch-si.com>

* Starting of Rollouts as scheduled task

Signed-off-by: Dominik Herbst <dominik.herbst@bosch-si.com>

* Finished implementation of new Rollout starting proccess

Signed-off-by: Dominik Herbst <dominik.herbst@bosch-si.com>

* Reset last check on status change and fixed unused imports

Signed-off-by: Dominik Herbst <dominik.herbst@bosch-si.com>

* Code quality improvements

Signed-off-by: Dominik Herbst <dominik.herbst@bosch-si.com>

* Reworked start of scheduled Actions. Improved code quality.

Signed-off-by: Dominik Herbst <dominik.herbst@bosch-si.com>
This commit is contained in:
Dominik Herbst
2016-11-16 09:26:50 +01:00
committed by Michael Hirsch
parent 66b6983406
commit b6834e9ee2
37 changed files with 2009 additions and 457 deletions

View File

@@ -276,25 +276,6 @@ public interface DeploymentManagement {
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
List<Action> findActionsByRolloutAndStatus(@NotNull Rollout rollout, @NotNull Action.Status actionStatus);
/**
* 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 rollout group the actions should reference
* @param actionStatus
* the status the actions have
* @return the actions referring a specific rollout and a specific parent
* rollout group in a specific status
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
List<Long> findActionsByRolloutGroupParentAndStatus(@NotNull Rollout rollout,
@NotNull RolloutGroup rolloutGroupParent, @NotNull Action.Status actionStatus);
/**
* Retrieves all {@link Action}s of a specific target.
*
@@ -513,6 +494,19 @@ public interface DeploymentManagement {
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
Action startScheduledAction(@NotNull Long actionId);
/**
* Starts all scheduled actions of an RolloutGroup parent.
*
* @param rollout
* the rollout the actions belong to
* @param rolloutGroupParent
* the parent rollout group the actions should reference. null
* references the first group
* @return the amount of started actions
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
long startScheduledActionsByRolloutGroupParent(@NotNull Rollout rollout, RolloutGroup rolloutGroupParent);
/**
* All {@link ActionStatus} entries in the repository.
*

View File

@@ -11,7 +11,6 @@ package org.eclipse.hawkbit.repository;
import javax.validation.constraints.NotNull;
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
import org.eclipse.hawkbit.repository.event.remote.entity.RolloutGroupCreatedEvent;
import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException;
@@ -26,6 +25,8 @@ import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.security.access.prepost.PreAuthorize;
import java.util.List;
/**
* RolloutManagement to control rollouts e.g. like creating, starting, resuming
* and pausing rollouts. This service secures all the functionality based on the
@@ -64,6 +65,30 @@ public interface RolloutManagement {
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE)
void checkRunningRollouts(long delayBetweenChecks);
/**
* Checking Rollouts that are currently being created with asynchronous
* assignment of targets to the Rollout Groups.
*
* @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).
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE)
void checkCreatingRollouts(long delayBetweenChecks);
/**
* Checking Rollouts that are currently being started with asynchronous
* creation of actions to the targets of a group.
*
* @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).
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE)
void checkStartingRollouts(long delayBetweenChecks);
/**
* Counts all {@link Rollout}s in the repository.
*
@@ -85,14 +110,17 @@ public interface RolloutManagement {
/**
* 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.
* which are effected by this rollout to create. The amount of groups will
* be defined as equally sized.
*
* 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
* creating and persisting all the necessary groups. The Rollout and the
* groups are persisted in {@link RolloutStatus#CREATING} and
* {@link RolloutGroupStatus#CREATING}.
*
* The RolloutScheduler will start to assign targets to the groups. Once all
* targets have been assigned to the groups, the rollout status is changed
* to {@link RolloutStatus#READY} so it can be started with
* {@link #startRollout(Rollout)}.
*
* @param rollout
@@ -112,40 +140,52 @@ public interface RolloutManagement {
/**
* 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}.
* {@link Rollout#getTargetFilterQuery()} is used to filter the targets
* which are affected by this rollout. The given groups will be used to
* create the groups.
*
* 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
* creating and persisting all the necessary groups. The Rollout and the
* groups are persisted in {@link RolloutStatus#CREATING} and
* {@link RolloutGroupStatus#CREATING}.
*
* The RolloutScheduler will start to assign targets to the groups. Once all
* targets have been assigned to the groups, the rollout status is changed
* to {@link RolloutStatus#READY} so it can be started with
* {@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
* the rollout entity to create
* @param groups
* optional definition of groups
* @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}
* the rollout group conditions and actions which should be
* applied for each {@link RolloutGroup} if not defined by the
* RolloutGroup itself
* @return the persisted rollout.
*
* @throws IllegalArgumentException
* in case the given groupSize is zero or lower.
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE)
Rollout createRolloutAsync(@NotNull Rollout rollout, int amountGroup, @NotNull RolloutGroupConditions conditions);
Rollout createRollout(@NotNull Rollout rollout, @NotNull List<RolloutGroup> groups, RolloutGroupConditions conditions);
/**
* Can be called on a Rollout in {@link RolloutStatus#CREATING} to
* automatically fill it with targets.
*
* Works through all Rollout groups in {@link RolloutGroupStatus#CREATING}
* and fills them with remaining targets until the supposed amount of
* targets for the group is reached. Targets are added to a group when they
* match the overall {@link Rollout#getTargetFilterQuery()} and the
* {@link RolloutGroup#getTargetFilterQuery()} and not more than
* {@link RolloutGroup#getTargetPercentage()} are assigned to the group.
*
* @param rollout
* the rollout
*/
void fillRolloutGroupsWithTargets(final Rollout rollout);
/**
* Retrieves all rollouts.
@@ -284,12 +324,10 @@ public interface RolloutManagement {
/**
* 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}.
* {@link RolloutStatus#READY} state. The Rollout will be set into the
* {@link RolloutStatus#STARTING} state. The RolloutScheduler will ensure
* all actions are created and the first group is started. The rollout
* itself will be then also in {@link RolloutStatus#RUNNING}.
*
* @param rollout
* the rollout to be started
@@ -303,28 +341,6 @@ public interface RolloutManagement {
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE)
Rollout startRollout(@NotNull Rollout rollout);
/**
* 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
*
* @return the started rollout
*
* @throws RolloutIllegalStateException
* if given rollout is not in {@link RolloutStatus#READY}. Only
* ready rollouts can be started.
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE)
Rollout startRolloutAsync(@NotNull Rollout rollout);
/**
* Update rollout details.
*

View File

@@ -19,6 +19,7 @@ import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
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.TargetFilterQuery;
@@ -268,7 +269,7 @@ public interface TargetManagement {
* id of the {@link DistributionSet}
* @param targetFilterQuery
* {@link TargetFilterQuery}
* @return the found {@link TargetIdName}s
* @return a page of the found {@link Target}s
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
Page<Target> findAllTargetsByTargetFilterQueryAndNonDS(@NotNull Pageable pageRequest, Long distributionSetId,
@@ -283,11 +284,54 @@ public interface TargetManagement {
* id of the {@link DistributionSet}
* @param targetFilterQuery
* {@link TargetFilterQuery}
* @return the found {@link TargetIdName}s
* @return the count of found {@link Target}s
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
Long countTargetsByTargetFilterQueryAndNonDS(Long distributionSetId, @NotNull TargetFilterQuery targetFilterQuery);
/**
* Finds all targets for all the given parameter {@link TargetFilterQuery}
* and that are not assigned to one of the {@link RolloutGroup}s
*
* @param pageRequest
* the pageRequest to enhance the query for paging and sorting
* @param groups
* the list of {@link RolloutGroup}s
* @param targetFilterQuery
* RSQL filter
* @return a page of the found {@link Target}s
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
Page<Target> findAllTargetsByTargetFilterQueryAndNotInRolloutGroups(@NotNull Pageable pageRequest,
List<RolloutGroup> groups, @NotNull String targetFilterQuery);
/**
* Counts all targets for all the given parameter {@link TargetFilterQuery}
* and that are not assigned to one of the {@link RolloutGroup}s
*
* @param groups
* the list of {@link RolloutGroup}s
* @param targetFilterQuery
* RSQL filter
* @return count of the found {@link Target}s
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
Long countAllTargetsByTargetFilterQueryAndNotInRolloutGroups(List<RolloutGroup> groups,
@NotNull String targetFilterQuery);
/**
* Finds all targets of the provided {@link RolloutGroup} that have no
* Action for the RolloutGroup.
*
* @param pageRequest
* the pageRequest to enhance the query for paging and sorting
* @param group
* the {@link RolloutGroup}
* @return the found {@link Target}s
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
Page<Target> findAllTargetsInRolloutGroupWithoutAction(@NotNull Pageable pageRequest, @NotNull RolloutGroup group);
/**
* retrieves {@link Target}s by the assigned {@link DistributionSet} without
* details, i.e. NO {@link Target#getTags()} and {@link Target#getActions()}

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.AbstractServerRtException;
import org.eclipse.hawkbit.exception.SpServerError;
/**
* the {@link RolloutVerificationException} is thrown when a rollout or
* its groups get created or modified with a configuration that is
* not valid or can't be verified
*
*/
public class RolloutVerificationException extends AbstractServerRtException {
private static final long serialVersionUID = 1L;
private static final SpServerError THIS_ERROR = SpServerError.SP_ROLLOUT_VERIFICATION_FAILED;
/**
* Default constructor.
*/
public RolloutVerificationException() {
super(THIS_ERROR);
}
/**
* Parameterized constructor.
*
* @param cause
* of the exception
*/
public RolloutVerificationException(final Throwable cause) {
super(THIS_ERROR, cause);
}
/**
* Parameterized constructor.
*
* @param message
* of the exception
* @param cause
* of the exception
*/
public RolloutVerificationException(final String message, final Throwable cause) {
super(message, THIS_ERROR, cause);
}
/**
* Parameterized constructor.
*
* @param message
* of the exception
*/
public RolloutVerificationException(final String message) {
super(message, THIS_ERROR);
}
}

View File

@@ -146,13 +146,17 @@ public interface Rollout extends NamedEntity {
/**
* Rollout could not be created due to errors, might be a database
* problem during asynchronous creating.
* @deprecated legacy status is not used anymore
*/
@Deprecated
ERROR_CREATING,
/**
* Rollout could not be started due to errors, might be database problem
* during asynchronous starting.
* @deprecated legacy status is not used anymore
*/
@Deprecated
ERROR_STARTING;
}

View File

@@ -152,6 +152,23 @@ public interface RolloutGroup extends NamedEntity {
*/
String getSuccessActionExp();
/**
* @param successAction
* which is executed if the {@link RolloutGroupSuccessCondition}
* is met
*/
void setSuccessAction(RolloutGroupSuccessAction successAction);
/**
*
* @param successActionExp
* a String representation of the expression to be evaluated by
* the {@link RolloutGroupSuccessAction} might be {@code null} if
* no expression must be set for the
* {@link RolloutGroupSuccessAction}
*/
void setSuccessActionExp(String successActionExp);
/**
* @return the total amount of targets containing in this group
*/
@@ -169,10 +186,41 @@ public interface RolloutGroup extends NamedEntity {
void setTotalTargetCountStatus(TotalTargetCountStatus totalTargetCountStatus);
/**
* Rollout goup state machine.
* @return the target filter query, that is used to assign Targets to this
* Group
*/
String getTargetFilterQuery();
/**
* @param targetFilterQuery
* the target filter query, that is used to assign Targets to
* this Group. Can be null, if no restrictions should apply.
*/
void setTargetFilterQuery(String targetFilterQuery);
/**
* @return the percentage of matching Targets that should be assigned to
* this Group
*/
float getTargetPercentage();
/**
* @param targetPercentage
* the percentage of matching Targets that should be assigned to
* this Group
*/
void setTargetPercentage(float targetPercentage);
/**
* Rollout group state machine.
*
*/
public enum RolloutGroupStatus {
enum RolloutGroupStatus {
/**
* Group has been defined, but not all targets have been assigned yet.
*/
CREATING,
/**
* Ready to start the group.
@@ -198,18 +246,18 @@ public interface RolloutGroup extends NamedEntity {
/**
* Group is running.
*/
RUNNING;
RUNNING
}
/**
* The condition to evaluate if an group is success state.
*/
public enum RolloutGroupSuccessCondition {
enum RolloutGroupSuccessCondition {
THRESHOLD("thresholdRolloutGroupSuccessCondition");
private final String beanName;
private RolloutGroupSuccessCondition(final String beanName) {
RolloutGroupSuccessCondition(final String beanName) {
this.beanName = beanName;
}
@@ -224,12 +272,12 @@ public interface RolloutGroup extends NamedEntity {
/**
* The condition to evaluate if an group is in error state.
*/
public enum RolloutGroupErrorCondition {
enum RolloutGroupErrorCondition {
THRESHOLD("thresholdRolloutGroupErrorCondition");
private final String beanName;
private RolloutGroupErrorCondition(final String beanName) {
RolloutGroupErrorCondition(final String beanName) {
this.beanName = beanName;
}
@@ -242,14 +290,14 @@ public interface RolloutGroup extends NamedEntity {
}
/**
* The actions executed when the {@link RolloutGroup#errorCondition} is hit.
* The actions executed when the {@link RolloutGroup#getErrorCondition()} is hit.
*/
public enum RolloutGroupErrorAction {
enum RolloutGroupErrorAction {
PAUSE("pauseRolloutGroupAction");
private final String beanName;
private RolloutGroupErrorAction(final String beanName) {
RolloutGroupErrorAction(final String beanName) {
this.beanName = beanName;
}
@@ -262,15 +310,15 @@ public interface RolloutGroup extends NamedEntity {
}
/**
* The actions executed when the {@link RolloutGroup#successCondition} is
* The actions executed when the {@link RolloutGroup#getSuccessCondition()} is
* hit.
*/
public enum RolloutGroupSuccessAction {
enum RolloutGroupSuccessAction {
NEXTGROUP("startNextRolloutGroupAction");
private final String beanName;
private RolloutGroupSuccessAction(final String beanName) {
RolloutGroupSuccessAction(final String beanName) {
this.beanName = beanName;
}

View File

@@ -18,14 +18,14 @@ import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCond
* easily built.
*/
public class RolloutGroupConditions {
private RolloutGroupSuccessCondition successCondition;
private String successConditionExp;
private RolloutGroupSuccessAction successAction;
private String successActionExp;
private RolloutGroupErrorCondition errorCondition;
private String errorConditionExp;
private RolloutGroupErrorAction errorAction;
private String errorActionExp;
private RolloutGroupSuccessCondition successCondition = RolloutGroupSuccessCondition.THRESHOLD;
private String successConditionExp = "50";
private RolloutGroupSuccessAction successAction = RolloutGroupSuccessAction.NEXTGROUP;
private String successActionExp = "";
private RolloutGroupErrorCondition errorCondition = RolloutGroupErrorCondition.THRESHOLD;
private String errorConditionExp = "50";
private RolloutGroupErrorAction errorAction = RolloutGroupErrorAction.PAUSE;
private String errorActionExp = "";
public RolloutGroupSuccessCondition getSuccessCondition() {
return successCondition;

View File

@@ -37,7 +37,6 @@ 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.hateoas.Identifiable;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
@@ -339,6 +338,8 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
*
* Finding all actions of a specific rolloutgroup parent relation.
*
* @param pageable
* page parameters
* @param rollout
* the rollout the actions belong to
* @param rolloutGroupParent
@@ -348,7 +349,38 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
* @return the actions referring a specific rollout and a specific parent
* rolloutgroup in a specific status
*/
List<Identifiable<Long>> findByRolloutAndRolloutGroupParentAndStatus(JpaRollout rollout,
Page<Action> findByRolloutAndRolloutGroupParentAndStatus(Pageable pageable, JpaRollout rollout,
JpaRolloutGroup rolloutGroupParent, Status actionStatus);
/**
* Retrieving all actions referring to the first group of a rollout.
*
* @param pageable
* page parameters
* @param rollout
* the rollout the actions belong to
* @param actionStatus
* the status the actions have
* @return the actions referring a specific rollout and a specific parent
* rolloutgroup in a specific status
*/
Page<Action> findByRolloutAndRolloutGroupParentIsNullAndStatus(Pageable pageable, JpaRollout rollout,
Status actionStatus);
/**
* Retrieving all actions referring to a given rollout group with a specific
* a specific status.
*
* @param rollout
* the rollout the actions belong to
* @param rolloutGroupParent
* the parent rollout group the actions should reference
* @param actionStatus
* the status the actions have
* @return the actions ids referring a specific rollout group and a specific
* status
*/
List<Long> findByRolloutAndRolloutGroupAndStatus(JpaRollout rollout,
JpaRolloutGroup rolloutGroupParent, Status actionStatus);
/**

View File

@@ -78,13 +78,18 @@ import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.domain.AuditorAware;
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.domain.Slice;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.validation.annotation.Validated;
import com.google.common.collect.Lists;
@@ -98,6 +103,11 @@ import com.google.common.collect.Lists;
public class JpaDeploymentManagement implements DeploymentManagement {
private static final Logger LOG = LoggerFactory.getLogger(JpaDeploymentManagement.class);
/**
* Maximum amount of Actions that are started at once.
*/
private static final int ACTION_PAGE_LIMIT = 1000;
@Autowired
private EntityManager entityManager;
@@ -134,6 +144,9 @@ public class JpaDeploymentManagement implements DeploymentManagement {
@Autowired
private VirtualPropertyReplacer virtualPropertyReplacer;
@Autowired
private PlatformTransactionManager txManager;
@Override
@Transactional(isolation = Isolation.READ_COMMITTED)
@Modifying
@@ -482,13 +495,60 @@ public class JpaDeploymentManagement implements DeploymentManagement {
});
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_COMMITTED)
public long startScheduledActionsByRolloutGroupParent(@NotNull final Rollout rollout,
final RolloutGroup rolloutGroupParent) {
long totalActionsCount = 0L;
long lastStartedActionsCount;
do {
lastStartedActionsCount = startScheduledActionsByRolloutGroupParentInNewTransaction(rollout,
rolloutGroupParent, ACTION_PAGE_LIMIT);
totalActionsCount += lastStartedActionsCount;
} while (lastStartedActionsCount > 0);
return totalActionsCount;
}
private long startScheduledActionsByRolloutGroupParentInNewTransaction(final Rollout rollout,
final RolloutGroup rolloutGroupParent, final int limit) {
final DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setName("startScheduledActions");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
return new TransactionTemplate(txManager, def).execute(status -> {
final Page<Action> rolloutGroupActions = findActionsByRolloutAndRolloutGroupParent(rollout,
rolloutGroupParent, limit);
rolloutGroupActions.map(action -> (JpaAction) action).forEach(this::startScheduledAction);
return rolloutGroupActions.getTotalElements();
});
}
private Page<Action> findActionsByRolloutAndRolloutGroupParent(final Rollout rollout,
final RolloutGroup rolloutGroupParent, final int limit) {
JpaRollout jpaRollout = (JpaRollout) rollout;
JpaRolloutGroup jpaRolloutGroup = (JpaRolloutGroup) rolloutGroupParent;
PageRequest pageRequest = new PageRequest(0, limit);
if (rolloutGroupParent == null) {
return actionRepository.findByRolloutAndRolloutGroupParentIsNullAndStatus(pageRequest, jpaRollout,
Action.Status.SCHEDULED);
} else {
return actionRepository.findByRolloutAndRolloutGroupParentAndStatus(pageRequest, jpaRollout,
jpaRolloutGroup, Action.Status.SCHEDULED);
}
}
@Override
@Modifying
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED)
public Action startScheduledAction(final Long actionId) {
final JpaAction action = actionRepository.findById(actionId);
return startScheduledAction(action);
}
private Action startScheduledAction(final JpaAction action) {
// check if we need to override running update actions
final Set<Long> overrideObsoleteUpdateActions = overrideObsoleteUpdateActions(
Collections.singletonList(action.getTarget().getId()));
@@ -665,14 +725,6 @@ public class JpaDeploymentManagement implements DeploymentManagement {
return actionStatusRepository.getByAction(pageReq, (JpaAction) action);
}
@Override
public List<Long> findActionsByRolloutGroupParentAndStatus(final Rollout rollout,
final RolloutGroup rolloutGroupParent, final Action.Status actionStatus) {
return actionRepository.findByRolloutAndRolloutGroupParentAndStatus((JpaRollout) rollout,
(JpaRolloutGroup) rolloutGroupParent, actionStatus).stream().map(ident -> ident.getId())
.collect(Collectors.toList());
}
@Override
public List<Action> findActionsByRolloutAndStatus(final Rollout rollout, final Action.Status actionStatus) {
return actionRepository.findByRolloutAndStatus((JpaRollout) rollout, actionStatus);

View File

@@ -11,21 +11,21 @@ package org.eclipse.hawkbit.repository.jpa;
import java.util.Collections;
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 org.apache.commons.lang3.StringUtils;
import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.OffsetBasedPageRequest;
import org.eclipse.hawkbit.repository.RolloutFields;
import org.eclipse.hawkbit.repository.RolloutGroupManagement;
import org.eclipse.hawkbit.repository.RolloutManagement;
import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.event.remote.entity.RolloutGroupCreatedEvent;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException;
import org.eclipse.hawkbit.repository.exception.RolloutVerificationException;
import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout;
import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout_;
@@ -44,7 +44,6 @@ import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition;
import org.eclipse.hawkbit.repository.model.RolloutGroupConditions;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetWithActionType;
import org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus;
import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus;
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
@@ -52,23 +51,23 @@ 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.context.ApplicationEventPublisher;
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.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.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.Assert;
import org.springframework.validation.annotation.Validated;
@@ -81,6 +80,11 @@ import org.springframework.validation.annotation.Validated;
public class JpaRolloutManagement implements RolloutManagement {
private static final Logger LOGGER = LoggerFactory.getLogger(RolloutManagement.class);
/**
* Maximum amount of targets that are assigned to a Rollout Group in one transaction.
*/
private static final long TRANSACTION_TARGETS = 1000;
@Autowired
private EntityManager entityManager;
@@ -90,9 +94,6 @@ public class JpaRolloutManagement implements RolloutManagement {
@Autowired
private TargetManagement targetManagement;
@Autowired
private TargetRepository targetRepository;
@Autowired
private RolloutGroupRepository rolloutGroupRepository;
@@ -124,24 +125,7 @@ public class JpaRolloutManagement implements RolloutManagement {
private VirtualPropertyReplacer virtualPropertyReplacer;
@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();
private AfterTransactionCommitExecutor afterCommit;
@Override
public Page<Rollout> findAll(final Pageable pageable) {
@@ -177,176 +161,406 @@ public class JpaRolloutManagement implements RolloutManagement {
@Modifying
public Rollout createRollout(final Rollout rollout, final int amountGroup,
final RolloutGroupConditions conditions) {
final JpaRollout savedRollout = createRollout((JpaRollout) rollout, amountGroup);
RolloutHelper.verifyRolloutGroupParameter(amountGroup);
final JpaRollout savedRollout = createRollout((JpaRollout) rollout);
return createRolloutGroups(amountGroup, conditions, savedRollout);
}
@Override
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
public Rollout createRolloutAsync(final Rollout rollout, final int amountGroup,
final RolloutGroupConditions conditions) {
final JpaRollout savedRollout = createRollout((JpaRollout) rollout, amountGroup);
creatingRollouts.add(savedRollout.getName());
// need to flush the entity manager here to get the ID of the rollout,
// because entity manager is set to FlushMode#Auto, entitymanager will
// flush the Target entity, due the indirect relationship to the Rollout
// entity without set an ID JPA is throwing a Invalid
// 'org.springframework.dao.InvalidDataAccessApiUsageException: During
// synchronization aect was found through a relationship that was not
// marked cascade PERSIST'
entityManager.flush();
executor.execute(() -> {
try {
createRolloutGroupsInNewTransaction(amountGroup, conditions, savedRollout);
} finally {
creatingRollouts.remove(savedRollout.getName());
}
});
return savedRollout;
public Rollout createRollout(final Rollout rollout,
final List<RolloutGroup> groups,
final RolloutGroupConditions conditions) {
RolloutHelper.verifyRolloutGroupParameter(groups.size());
final JpaRollout savedRollout = createRollout((JpaRollout) rollout);
return createRolloutGroups(groups, conditions, savedRollout);
}
private JpaRollout createRollout(final JpaRollout rollout, final int amountGroup) {
verifyRolloutGroupParameter(amountGroup);
private JpaRollout createRollout(final JpaRollout rollout) {
JpaRollout existingRollout = rolloutRepository.findByName(rollout.getName());
if(existingRollout != null) {
throw new EntityAlreadyExistsException(existingRollout.getName());
}
final Long totalTargets = targetManagement.countTargetByTargetFilterQuery(rollout.getTargetFilterQuery());
rollout.setTotalTargets(totalTargets.longValue());
if(totalTargets == 0) {
throw new RolloutVerificationException("Rollout does not match any existing targets");
}
rollout.setTotalTargets(totalTargets);
return rolloutRepository.save(rollout);
}
private static 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");
}
}
private Rollout createRolloutGroupsInNewTransaction(final int amountOfGroups,
final RolloutGroupConditions conditions, final JpaRollout savedRollout) {
final DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setName("creatingRollout");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
return new TransactionTemplate(txManager, def)
.execute(status -> createRolloutGroups(amountOfGroups, conditions, savedRollout));
}
/**
* 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 amountOfGroups
* 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 amountOfGroups, final RolloutGroupConditions conditions,
final JpaRollout savedRollout) {
int pageIndex = 0;
int groupIndex = 0;
final Long totalCount = savedRollout.getTotalTargets();
final int groupSize = (int) Math.ceil((double) totalCount / (double) amountOfGroups);
final Rollout rollout) {
RolloutHelper.verifyRolloutInStatus(rollout, RolloutStatus.CREATING);
RolloutHelper.verifyRolloutGroupConditions(conditions);
final JpaRollout savedRollout = (JpaRollout) rollout;
RolloutGroup lastSavedGroup = null;
while (pageIndex < totalCount) {
groupIndex++;
final String nameAndDesc = "group-" + groupIndex;
for (int i = 0; i < amountOfGroups; i++) {
final String nameAndDesc = "group-" + (i + 1);
final JpaRolloutGroup group = new JpaRolloutGroup();
group.setName(nameAndDesc);
group.setDescription(nameAndDesc);
group.setRollout(savedRollout);
group.setParent(lastSavedGroup);
group.setStatus(RolloutGroupStatus.CREATING);
group.setSuccessCondition(conditions.getSuccessCondition());
group.setSuccessConditionExp(conditions.getSuccessConditionExp());
group.setSuccessAction(conditions.getSuccessAction());
group.setSuccessActionExp(conditions.getSuccessActionExp());
group.setErrorCondition(conditions.getErrorCondition());
group.setErrorConditionExp(conditions.getErrorConditionExp());
group.setErrorAction(conditions.getErrorAction());
group.setErrorActionExp(conditions.getErrorActionExp());
final JpaRolloutGroup savedGroup = rolloutGroupRepository.save(group);
group.setTargetPercentage(1.0F / (amountOfGroups - i) * 100);
final Slice<Target> targetGroup = targetManagement.findTargetsAll(savedRollout.getTargetFilterQuery(),
new OffsetBasedPageRequest(pageIndex, groupSize, new Sort(Direction.ASC, "id")));
savedGroup.setTotalTargets(targetGroup.getContent().size());
lastSavedGroup = rolloutGroupRepository.save(group);
publishRolloutGroupCreatedEventAfterCommit(lastSavedGroup);
lastSavedGroup = savedGroup;
targetGroup
.forEach(target -> rolloutTargetGroupRepository.save(new RolloutTargetGroup(savedGroup, target)));
eventPublisher.publishEvent(new RolloutGroupCreatedEvent(group, context.getId()));
pageIndex += groupSize;
}
savedRollout.setRolloutGroupsCreated(groupIndex);
savedRollout.setStatus(RolloutStatus.READY);
savedRollout.setRolloutGroupsCreated(amountOfGroups);
return rolloutRepository.save(savedRollout);
}
private Rollout createRolloutGroups(final List<RolloutGroup> groupList, final RolloutGroupConditions conditions,
final Rollout rollout) {
RolloutHelper.verifyRolloutInStatus(rollout, RolloutStatus.CREATING);
final JpaRollout savedRollout = (JpaRollout) rollout;
// Preparing the groups
final List<RolloutGroup> groups = groupList.stream().map(
group -> RolloutHelper.prepareRolloutGroupWithDefaultConditions(group, conditions))
.collect(Collectors.toList());
groups.forEach(RolloutHelper::verifyRolloutGroupHasConditions);
verifyRolloutGroupTargetCounts(groups, savedRollout);
// Persisting the groups
RolloutGroup lastSavedGroup = null;
for (final RolloutGroup srcGroup : groups) {
final JpaRolloutGroup group = new JpaRolloutGroup();
group.setName(srcGroup.getName());
group.setDescription(srcGroup.getDescription());
group.setRollout(savedRollout);
group.setParent(lastSavedGroup);
group.setStatus(RolloutGroupStatus.CREATING);
group.setTargetPercentage(srcGroup.getTargetPercentage());
if (srcGroup.getTargetFilterQuery() != null) {
group.setTargetFilterQuery(srcGroup.getTargetFilterQuery());
} else {
group.setTargetFilterQuery("");
}
group.setSuccessCondition(srcGroup.getSuccessCondition());
group.setSuccessConditionExp(srcGroup.getSuccessConditionExp());
group.setSuccessAction(srcGroup.getSuccessAction());
group.setSuccessActionExp(srcGroup.getSuccessActionExp());
group.setErrorCondition(srcGroup.getErrorCondition());
group.setErrorConditionExp(srcGroup.getErrorConditionExp());
group.setErrorAction(srcGroup.getErrorAction());
group.setErrorActionExp(srcGroup.getErrorActionExp());
lastSavedGroup = rolloutGroupRepository.save(group);
publishRolloutGroupCreatedEventAfterCommit(lastSavedGroup);
}
savedRollout.setRolloutGroupsCreated(groups.size());
return rolloutRepository.save(savedRollout);
}
private void publishRolloutGroupCreatedEventAfterCommit(final RolloutGroup group) {
afterCommit
.afterCommit(() -> eventPublisher.publishEvent(new RolloutGroupCreatedEvent(group, context.getId())));
}
@Override
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
public void fillRolloutGroupsWithTargets(final Rollout rollout) {
RolloutHelper.verifyRolloutInStatus(rollout, RolloutStatus.CREATING);
final JpaRollout jpaRollout = (JpaRollout) rollout;
List<RolloutGroup> rolloutGroups = RolloutHelper.getOrderedGroups(rollout);
int readyGroups = 0;
int totalTargets = 0;
for (RolloutGroup group : rolloutGroups) {
if (group.getStatus() != RolloutGroupStatus.CREATING) {
readyGroups++;
totalTargets += group.getTotalTargets();
continue;
}
final RolloutGroup filledGroup = fillRolloutGroupWithTargets(rollout, group);
if(filledGroup.getStatus() == RolloutGroupStatus.READY) {
readyGroups++;
totalTargets += filledGroup.getTotalTargets();
}
}
// When all groups are ready the rollout status can be changed to be ready, too.
if(readyGroups == rolloutGroups.size()) {
jpaRollout.setStatus(RolloutStatus.READY);
jpaRollout.setLastCheck(0);
jpaRollout.setTotalTargets(totalTargets);
rolloutRepository.save(jpaRollout);
}
}
private RolloutGroup fillRolloutGroupWithTargets(final Rollout rollout, final RolloutGroup group1) {
RolloutHelper.verifyRolloutInStatus(rollout, RolloutStatus.CREATING);
JpaRolloutGroup group = (JpaRolloutGroup) group1;
final String baseFilter = RolloutHelper.getTargetFilterQuery(rollout);
final String groupTargetFilter;
if (StringUtils.isEmpty(group.getTargetFilterQuery())) {
groupTargetFilter = baseFilter;
} else {
groupTargetFilter = baseFilter + ";" + group.getTargetFilterQuery();
}
final List<RolloutGroup> readyGroups = RolloutHelper.getGroupsByStatusIncludingGroup(rollout,
RolloutGroupStatus.READY, group);
final long targetsInGroupFilter = targetManagement
.countAllTargetsByTargetFilterQueryAndNotInRolloutGroups(readyGroups, groupTargetFilter);
final long expectedInGroup = Math.round(group.getTargetPercentage() / 100 * (double) targetsInGroupFilter);
final long currentlyInGroup = rolloutTargetGroupRepository.countByRolloutGroup(group);
// Switch the Group status to READY, when there are enough Targets in
// the Group
if (currentlyInGroup >= expectedInGroup) {
group.setStatus(RolloutGroupStatus.READY);
return rolloutGroupRepository.save(group);
}
long targetsLeftToAdd = expectedInGroup - currentlyInGroup;
try {
do {
// Add up to TRANSACTION_TARGETS of the left targets
// In case a TransactionException is thrown this loop aborts
targetsLeftToAdd -= assignTargetsToGroupInNewTransaction(rollout, group, groupTargetFilter,
Math.min(TRANSACTION_TARGETS, targetsLeftToAdd));
} while (targetsLeftToAdd > 0);
group.setStatus(RolloutGroupStatus.READY);
group.setTotalTargets(rolloutTargetGroupRepository.countByRolloutGroup(group).intValue());
return rolloutGroupRepository.save(group);
} catch (TransactionException e) {
LOGGER.warn("Transaction assigning Targets to RolloutGroup failed", e);
return group;
}
}
private Long runInNewCountingTransaction(final String transactionName, TransactionCallback<Long> action) {
final DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setName(transactionName);
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
return new TransactionTemplate(txManager, def).execute(action);
}
private long assignTargetsToGroupInNewTransaction(final Rollout rollout, final RolloutGroup group,
final String targetFilter, final long limit) {
return runInNewCountingTransaction("assignTargetsToRolloutGroup", status -> {
final PageRequest pageRequest = new PageRequest(0, Math.toIntExact(limit));
final List<RolloutGroup> readyGroups = RolloutHelper.getGroupsByStatusIncludingGroup(rollout,
RolloutGroupStatus.READY, group);
Page<Target> targets = targetManagement.findAllTargetsByTargetFilterQueryAndNotInRolloutGroups(pageRequest,
readyGroups, targetFilter);
createAssignmentOfTargetsToGroup(targets, group);
return targets.getTotalElements();
});
}
private void createAssignmentOfTargetsToGroup(final Page<Target> targets, final RolloutGroup group) {
targets.forEach(target -> rolloutTargetGroupRepository.save(new RolloutTargetGroup(group, target)));
}
private void verifyRolloutGroupTargetCounts(final List<RolloutGroup> groups, final JpaRollout rollout) {
final String baseFilter = RolloutHelper.getTargetFilterQuery(rollout);
final long totalTargets = targetManagement.countTargetByTargetFilterQuery(baseFilter);
if (totalTargets == 0) {
throw new RolloutVerificationException("Rollout target filter does not match any targets");
}
long targetCount = totalTargets;
long unusedTargetsCount = 0;
for (int i = 0; i < groups.size(); i++) {
final RolloutGroup group = groups.get(i);
RolloutHelper.verifyRolloutGroupTargetPercentage(group.getTargetPercentage());
final long targetsInGroupFilter = countTargetsOfGroup(baseFilter, totalTargets, group);
final long overlappingTargets = countOverlappingTargetsWithPreviousGroups(baseFilter, groups, group, i);
final long realTargetsInGroup;
// Assume that targets which were not used in the previous groups are used in this group
if (overlappingTargets > 0 && unusedTargetsCount > 0) {
realTargetsInGroup = targetsInGroupFilter - overlappingTargets + unusedTargetsCount;
unusedTargetsCount = 0;
} else {
realTargetsInGroup = targetsInGroupFilter - overlappingTargets;
}
final long reducedTargetsInGroup = Math
.round(group.getTargetPercentage() / 100 * (double) realTargetsInGroup);
targetCount -= reducedTargetsInGroup;
unusedTargetsCount += realTargetsInGroup - reducedTargetsInGroup;
}
RolloutHelper.verifyRemainingTargets(targetCount);
}
private long countTargetsOfGroup(final String baseFilter, final long baseFilterCount, final RolloutGroup group) {
if (StringUtils.isEmpty(group.getTargetFilterQuery())) {
return baseFilterCount;
} else {
return targetManagement
.countTargetByTargetFilterQuery(baseFilter + ";" + group.getTargetFilterQuery());
}
}
private long countOverlappingTargetsWithPreviousGroups(final String baseFilter, final List<RolloutGroup> groups,
final RolloutGroup group, final int groupIndex) {
// there can't be overlapping targets in the first group
if(groupIndex == 0) {
return 0;
}
final List<RolloutGroup> previousGroups = groups.subList(0, groupIndex);
String overlappingTargetsFilter = RolloutHelper.getOverlappingWithGroupsTargetFilter(previousGroups, group);
if (StringUtils.isEmpty(overlappingTargetsFilter)) {
overlappingTargetsFilter = baseFilter;
} else {
overlappingTargetsFilter = baseFilter + ";" + overlappingTargetsFilter;
}
return targetManagement.countTargetByTargetFilterQuery(overlappingTargetsFilter);
}
@Override
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
public Rollout startRollout(final Rollout rollout) {
final JpaRollout mergedRollout = entityManager.merge((JpaRollout) rollout);
checkIfRolloutCanStarted(rollout, mergedRollout);
return doStartRollout(mergedRollout);
}
@Override
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
public Rollout startRolloutAsync(final Rollout rollout) {
final JpaRollout mergedRollout = entityManager.merge((JpaRollout) rollout);
checkIfRolloutCanStarted(rollout, mergedRollout);
mergedRollout.setStatus(RolloutStatus.STARTING);
final JpaRollout 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;
return rolloutRepository.save(mergedRollout);
}
private void startFirstRolloutGroup(final Rollout rollout) {
RolloutHelper.verifyRolloutInStatus(rollout, RolloutStatus.STARTING);
final JpaRollout jpaRollout = (JpaRollout) rollout;
final List<JpaRolloutGroup> rolloutGroups = rolloutGroupRepository.findByRolloutOrderByIdAsc(jpaRollout);
final JpaRolloutGroup rolloutGroup = rolloutGroups.get(0);
if (rolloutGroup.getParent() != null) {
throw new RolloutIllegalStateException("First Group is not the first group.");
}
deploymentManagement.startScheduledActionsByRolloutGroupParent(rollout, null);
rolloutGroup.setStatus(RolloutGroupStatus.RUNNING);
rolloutGroupRepository.save(rolloutGroup);
jpaRollout.setStatus(RolloutStatus.RUNNING);
jpaRollout.setLastCheck(0);
rolloutRepository.save(jpaRollout);
}
private Rollout doStartRollout(final JpaRollout rollout) {
final DistributionSet distributionSet = rollout.getDistributionSet();
final ActionType actionType = rollout.getActionType();
final long forceTime = rollout.getForcedTime();
final List<JpaRolloutGroup> rolloutGroups = rolloutGroupRepository.findByRolloutOrderByIdAsc(rollout);
for (int iGroup = 0; iGroup < rolloutGroups.size(); iGroup++) {
final JpaRolloutGroup 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);
private boolean ensureAllGroupsAreScheduled(final Rollout rollout) {
RolloutHelper.verifyRolloutInStatus(rollout, RolloutStatus.STARTING);
final JpaRollout jpaRollout = (JpaRollout) rollout;
final List<JpaRolloutGroup> groupsToBeScheduled = rolloutGroupRepository.findByRolloutAndStatus(rollout,
RolloutGroupStatus.READY);
final long scheduledGroups = groupsToBeScheduled.stream()
.filter(group -> scheduleRolloutGroup(jpaRollout, group)).count();
return scheduledGroups == groupsToBeScheduled.size();
}
/**
* Schedules a group of the rollout. Scheduled Actions are created to
* achieve this. The creation of those Actions is allowed to fail.
*
* @param rollout
* the Rollout
* @param group
* the RolloutGroup
* @return whether the complete group was scheduled
*/
private boolean scheduleRolloutGroup(final JpaRollout rollout, final JpaRolloutGroup group) {
final long targetsInGroup = rolloutTargetGroupRepository.countByRolloutGroup(group);
final long countOfActions = actionRepository.countByRolloutAndRolloutGroup(rollout, group);
long actionsLeft = targetsInGroup - countOfActions;
if (actionsLeft > 0) {
actionsLeft -= createActionsForRolloutGroup(rollout, group);
}
rollout.setStatus(RolloutStatus.RUNNING);
return rolloutRepository.save(rollout);
if (actionsLeft <= 0) {
group.setStatus(RolloutGroupStatus.SCHEDULED);
rolloutGroupRepository.save(group);
return true;
}
return false;
}
private long createActionsForRolloutGroup(final Rollout rollout, final RolloutGroup group) {
long totalActionsCreated = 0;
try {
long actionsCreated;
do {
actionsCreated = createActionsForTargetsInNewTransaction(rollout.getId(), group.getId(),
TRANSACTION_TARGETS);
totalActionsCreated += actionsCreated;
} while (actionsCreated > 0);
} catch (TransactionException e) {
LOGGER.warn("Transaction assigning Targets to RolloutGroup failed", e);
return 0;
}
return totalActionsCreated;
}
private long createActionsForTargetsInNewTransaction(final long rolloutId, final long groupId, final long limit) {
return runInNewCountingTransaction("createActionsForTargets", status -> {
final PageRequest pageRequest = new PageRequest(0, Math.toIntExact(limit));
final Rollout rollout = rolloutRepository.findOne(rolloutId);
final RolloutGroup group = rolloutGroupRepository.findOne(groupId);
final DistributionSet distributionSet = rollout.getDistributionSet();
final ActionType actionType = rollout.getActionType();
final long forceTime = rollout.getForcedTime();
Page<Target> targets = targetManagement.findAllTargetsInRolloutGroupWithoutAction(pageRequest, group);
if (targets.getTotalElements() > 0) {
deploymentManagement.createScheduledAction(targets.getContent(), distributionSet, actionType, forceTime,
rollout, group);
}
return targets.getTotalElements();
});
}
@Override
@@ -384,7 +598,6 @@ public class JpaRolloutManagement implements RolloutManagement {
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_UNCOMMITTED)
@Modifying
public void checkRunningRollouts(final long delayBetweenChecks) {
verifyStuckedRollouts();
final long lastCheck = System.currentTimeMillis();
final int updated = rolloutRepository.updateLastCheck(lastCheck, delayBetweenChecks, RolloutStatus.RUNNING);
@@ -425,36 +638,6 @@ public class JpaRolloutManagement implements RolloutManagement {
}
}
/**
* 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<JpaRollout> 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<JpaRollout> 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 JpaRollout rollout, final List<JpaRolloutGroup> rolloutGroups) {
for (final JpaRolloutGroup rolloutGroup : rolloutGroups) {
@@ -573,6 +756,54 @@ public class JpaRolloutManagement implements RolloutManagement {
rolloutGroup, rolloutGroup.getSuccessActionExp());
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_UNCOMMITTED)
@Modifying
public void checkCreatingRollouts(long delayBetweenChecks) {
final long lastCheck = System.currentTimeMillis();
final int updated = rolloutRepository.updateLastCheck(lastCheck, delayBetweenChecks, RolloutStatus.CREATING);
if (updated == 0) {
// nothing to check, maybe another instance already checked in
// between
LOGGER.debug("No rollouts creating check necessary for current scheduled check {}, next check at {}", lastCheck,
lastCheck + delayBetweenChecks);
return;
}
final List<JpaRollout> rolloutsToCheck = rolloutRepository.findByLastCheckAndStatus(lastCheck,
RolloutStatus.CREATING);
LOGGER.info("Found {} creating rollouts to check", rolloutsToCheck.size());
rolloutsToCheck.forEach(this::fillRolloutGroupsWithTargets);
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_UNCOMMITTED)
@Modifying
public void checkStartingRollouts(long delayBetweenChecks) {
final long lastCheck = System.currentTimeMillis();
final int updated = rolloutRepository.updateLastCheck(lastCheck, delayBetweenChecks, RolloutStatus.STARTING);
if (updated == 0) {
// nothing to check, maybe another instance already checked in
// between
LOGGER.debug("No rollouts starting check necessary for current scheduled check {}, next check at {}", lastCheck,
lastCheck + delayBetweenChecks);
return;
}
final List<JpaRollout> rolloutsToCheck = rolloutRepository.findByLastCheckAndStatus(lastCheck,
RolloutStatus.STARTING);
LOGGER.info("Found {} starting rollouts to check", rolloutsToCheck.size());
rolloutsToCheck.forEach(rollout -> {
if(ensureAllGroupsAreScheduled(rollout)) {
startFirstRolloutGroup(rollout);
}
});
}
@Override
public Long countRolloutsAll() {
return rolloutRepository.count();

View File

@@ -43,6 +43,7 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaTargetInfo_;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_;
import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder;
import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications;
@@ -585,6 +586,38 @@ public class JpaTargetManagement implements TargetManagement {
}
@Override
public Page<Target> findAllTargetsByTargetFilterQueryAndNotInRolloutGroups(@NotNull final Pageable pageRequest,
final List<RolloutGroup> groups, @NotNull final String targetFilterQuery) {
final Specification<JpaTarget> spec = RSQLUtility.parse(targetFilterQuery, TargetFields.class,
virtualPropertyReplacer);
return findTargetsBySpec((root, cq, cb) -> cb.and(spec.toPredicate(root, cq, cb),
TargetSpecifications.isNotInRolloutGroups(groups).toPredicate(root, cq, cb)), pageRequest);
}
@Override
public Page<Target> findAllTargetsInRolloutGroupWithoutAction(@NotNull final Pageable pageRequest,
@NotNull final RolloutGroup group) {
return findTargetsBySpec(
(root, cq, cb) -> TargetSpecifications.hasNoActionInRolloutGroup(group).toPredicate(root, cq, cb),
pageRequest);
}
@Override
public Long countAllTargetsByTargetFilterQueryAndNotInRolloutGroups(final List<RolloutGroup> groups,
@NotNull final String targetFilterQuery) {
final Specification<JpaTarget> spec = RSQLUtility.parse(targetFilterQuery, TargetFields.class,
virtualPropertyReplacer);
final List<Specification<JpaTarget>> specList = new ArrayList<>(2);
specList.add(spec);
specList.add(TargetSpecifications.isNotInRolloutGroups(groups));
return countByCriteriaAPI(specList);
}
@Override
public Long countTargetsByTargetFilterQueryAndNonDS(final Long distributionSetId,
@NotNull final TargetFilterQuery targetFilterQuery) {

View File

@@ -0,0 +1,246 @@
/**
* 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.jpa;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException;
import org.eclipse.hawkbit.repository.exception.RolloutVerificationException;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.RolloutGroupConditions;
import java.util.List;
import java.util.stream.Collectors;
/**
* A collection of static helper methods for the {@link JpaRolloutManagement}
*/
final class RolloutHelper {
private RolloutHelper() {
}
/**
* Verifies that the required success condition and action are actually set.
*
* @param conditions
* input conditions and actions
*/
static void verifyRolloutGroupConditions(final RolloutGroupConditions conditions) {
if (conditions.getSuccessCondition() == null) {
throw new RolloutVerificationException("Rollout group is missing success condition");
}
if (conditions.getSuccessAction() == null) {
throw new RolloutVerificationException("Rollout group is missing success action");
}
}
/**
* Verifies that the group has the required success condition and action.
*
* @param group
* the input group
* @return the verified group
*/
static RolloutGroup verifyRolloutGroupHasConditions(final RolloutGroup group) {
if (group.getSuccessCondition() == null) {
throw new RolloutVerificationException("Rollout group is missing success condition");
}
if (group.getSuccessAction() == null) {
throw new RolloutVerificationException("Rollout group is missing success action");
}
return group;
}
/**
* In case the given group is missing conditions or actions, they will be
* set from the supplied default conditions.
*
* @param group
* group to check
* @param conditions
* default conditions and actions
* @return group with all conditions and actions
*/
static RolloutGroup prepareRolloutGroupWithDefaultConditions(final RolloutGroup group,
final RolloutGroupConditions conditions) {
if (group.getSuccessCondition() == null) {
group.setSuccessCondition(conditions.getSuccessCondition());
}
if (group.getSuccessConditionExp() == null) {
group.setSuccessConditionExp(conditions.getSuccessConditionExp());
}
if (group.getSuccessAction() == null) {
group.setSuccessAction(conditions.getSuccessAction());
}
if (group.getSuccessActionExp() == null) {
group.setSuccessActionExp(conditions.getSuccessActionExp());
}
if (group.getErrorCondition() == null) {
group.setErrorCondition(conditions.getErrorCondition());
}
if (group.getErrorConditionExp() == null) {
group.setErrorConditionExp(conditions.getErrorConditionExp());
}
if (group.getErrorAction() == null) {
group.setErrorAction(conditions.getErrorAction());
}
if (group.getErrorActionExp() == null) {
group.setErrorActionExp(conditions.getErrorActionExp());
}
return group;
}
/**
* Verify if the supplied amount of groups is in range
*
* @param amountGroup
* amount of groups
*/
static void verifyRolloutGroupParameter(final int amountGroup) {
if (amountGroup <= 0) {
throw new RolloutVerificationException("the amountGroup must be greater than zero");
} else if (amountGroup > 500) {
throw new RolloutVerificationException("the amountGroup must not be greater than 500");
}
}
/**
* Verify that the supplied percentage is in range
*
* @param percentage
* the percentage
*/
static void verifyRolloutGroupTargetPercentage(final float percentage) {
if (percentage <= 0) {
throw new RolloutVerificationException("the percentage must be greater than zero");
} else if (percentage > 100) {
throw new RolloutVerificationException("the percentage must not be greater than 100");
}
}
/**
* Modifies the target filter query to only match targets that were created
* after the Rollout.
*
* @param rollout
* Rollout to derive the filter from
* @return resulting target filter query
*/
static String getTargetFilterQuery(final Rollout rollout) {
if (rollout.getCreatedAt() != null) {
return rollout.getTargetFilterQuery() + ";createdat=le=" + rollout.getCreatedAt().toString();
}
return rollout.getTargetFilterQuery();
}
/**
* Verifies that the Rollout is in the required status.
*
* @param rollout
* the Rollout
* @param status
* the Status
*/
static void verifyRolloutInStatus(final Rollout rollout, final Rollout.RolloutStatus status) {
if (!rollout.getStatus().equals(status)) {
throw new RolloutIllegalStateException("Rollout is not in status " + status.toString());
}
}
/**
* Filters the groups of a Rollout to match a specific status and adds a
* group to the result.
*
* @param rollout
* the rollout
* @param status
* the required status for the groups
* @param group
* the group to add
* @return list of groups
*/
static List<RolloutGroup> getGroupsByStatusIncludingGroup(final Rollout rollout,
final RolloutGroup.RolloutGroupStatus status, final RolloutGroup group) {
return rollout.getRolloutGroups().stream()
.filter(innerGroup -> innerGroup.getStatus().equals(status) || innerGroup.equals(group))
.collect(Collectors.toList());
}
/**
* Returns the groups of a rollout by their Ids order
*
* @param rollout
* the rollout
* @return ordered list of groups
*/
static List<RolloutGroup> getOrderedGroups(final Rollout rollout) {
return rollout.getRolloutGroups().stream().sorted((group1, group2) -> {
if (group1.getId() < group2.getId()) {
return -1;
}
if (group1.getId() > group2.getId()) {
return 1;
}
return 0;
}).collect(Collectors.toList());
}
/**
* Creates an RSQL expression that matches all targets in the provided groups.
* Links all target filter queries with OR.
*
* @param groups the rollout groups
* @return RSQL string without base filter of the Rollout. Can be an empty string.
*/
static String getAllGroupsTargetFilter(final List<RolloutGroup> groups) {
if (groups.stream().anyMatch(group -> StringUtils.isEmpty(group.getTargetFilterQuery()))) {
return "";
}
return groups.stream().map(RolloutGroup::getTargetFilterQuery).collect(Collectors.joining(","));
}
/**
* Creates an RSQL Filter that matches all targets that are in the provided group and
* in the provided groups.
*
* @param groups the rollout groups
* @param group the group
* @return RSQL string without base filter of the Rollout. Can be an empty string.
*/
static String getOverlappingWithGroupsTargetFilter(final List<RolloutGroup> groups, final RolloutGroup group) {
final String previousGroupFilters = getAllGroupsTargetFilter(groups);
if (StringUtils.isNotEmpty(previousGroupFilters) && StringUtils.isNotEmpty(group.getTargetFilterQuery())) {
return group.getTargetFilterQuery() + ";(" + previousGroupFilters + ")";
} else if (StringUtils.isNotEmpty(previousGroupFilters)) {
return "(" + previousGroupFilters + ")";
} else if (StringUtils.isNotEmpty(group.getTargetFilterQuery())) {
return group.getTargetFilterQuery();
} else {
return "";
}
}
/**
* Verifies that no targets are left
*
* @param targetCount
* the count of left targets
*/
static void verifyRemainingTargets(final long targetCount) {
if (targetCount > 0) {
throw new RolloutVerificationException(
"Rollout groups don't match all targets that are targeted by the rollout");
}
if (targetCount != 0) {
throw new RolloutVerificationException("Rollout groups target count verification failed");
}
}
}

View File

@@ -8,6 +8,7 @@
*/
package org.eclipse.hawkbit.repository.jpa;
import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup;
import org.eclipse.hawkbit.repository.jpa.model.RolloutTargetGroup;
import org.eclipse.hawkbit.repository.jpa.model.RolloutTargetGroupId;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
@@ -22,4 +23,13 @@ import org.springframework.transaction.annotation.Transactional;
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public interface RolloutTargetGroupRepository
extends CrudRepository<RolloutTargetGroup, RolloutTargetGroupId>, JpaSpecificationExecutor<RolloutTargetGroup> {
/**
* Counts all entries that have the specified rolloutGroup
*
* @param rolloutGroup
* the group to filter for
* @return count of targets in the group
*/
Long countByRolloutGroup(final JpaRolloutGroup rolloutGroup);
}

View File

@@ -53,7 +53,7 @@ public class JpaRolloutGroup extends AbstractJpaNamedEntity implements RolloutGr
private JpaRollout rollout;
@Column(name = "status")
private RolloutGroupStatus status = RolloutGroupStatus.READY;
private RolloutGroupStatus status = RolloutGroupStatus.CREATING;
@OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST }, targetEntity = RolloutTargetGroup.class)
@JoinColumn(name = "rolloutGroup_Id", insertable = false, updatable = false)
@@ -93,6 +93,13 @@ public class JpaRolloutGroup extends AbstractJpaNamedEntity implements RolloutGr
@Column(name = "total_targets")
private int totalTargets;
@Column(name = "target_filter", length = 1024)
@Size(max = 1024)
private String targetFilterQuery = "";
@Column(name = "target_percentage")
private float targetPercentage = 100;
@Transient
private transient TotalTargetCountStatus totalTargetCountStatus;
@@ -212,14 +219,36 @@ public class JpaRolloutGroup extends AbstractJpaNamedEntity implements RolloutGr
this.totalTargets = totalTargets;
}
@Override
public void setSuccessAction(final RolloutGroupSuccessAction successAction) {
this.successAction = successAction;
}
@Override
public void setSuccessActionExp(final String successActionExp) {
this.successActionExp = successActionExp;
}
@Override
public String getTargetFilterQuery() {
return targetFilterQuery;
}
@Override
public void setTargetFilterQuery(String targetFilterQuery) {
this.targetFilterQuery = targetFilterQuery;
}
@Override
public float getTargetPercentage() {
return targetPercentage;
}
@Override
public void setTargetPercentage(float targetPercentage) {
this.targetPercentage = targetPercentage;
}
/**
* @return the totalTargetCountStatus
*/
@@ -242,10 +271,10 @@ public class JpaRolloutGroup extends AbstractJpaNamedEntity implements RolloutGr
@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() + "]";
return "RolloutGroup [rollout=" + (rollout != null ? rollout.getId() : "") + ", status=" + status
+ ", rolloutTargetGroup=" + rolloutTargetGroup + ", parent=" + parent + ", finishCondition="
+ successCondition + ", finishExp=" + successConditionExp + ", errorCondition=" + errorCondition
+ ", errorExp=" + errorConditionExp + ", getName()=" + getName() + ", getId()=" + getId() + "]";
}
@Override

View File

@@ -73,7 +73,10 @@ public class RolloutScheduler {
LOGGER.info("Checking rollouts for {} tenants", tenants.size());
for (final String tenant : tenants) {
tenantAware.runAsTenant(tenant, () -> {
rolloutManagement.checkRunningRollouts(rolloutProperties.getScheduler().getFixedDelay());
final long fixedDelay = rolloutProperties.getScheduler().getFixedDelay();
rolloutManagement.checkCreatingRollouts(fixedDelay);
rolloutManagement.checkStartingRollouts(fixedDelay);
rolloutManagement.checkRunningRollouts(fixedDelay);
return null;
});
}

View File

@@ -13,7 +13,6 @@ import java.util.List;
import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.jpa.RolloutGroupRepository;
import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup;
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;
@@ -57,14 +56,11 @@ public class StartNextGroupRolloutGroupSuccessAction implements RolloutGroupActi
// retrieve all actions according to the parent group of the finished
// rolloutGroup, so retrieve all child-group actions which need to be
// started.
final List<Long> rolloutGroupActions = deploymentManagement.findActionsByRolloutGroupParentAndStatus(rollout,
rolloutGroup, Action.Status.SCHEDULED);
logger.debug("{} Next actions to start for rollout {} and parent group {}", rolloutGroupActions.size(), rollout,
final long countOfStartedActions = deploymentManagement.startScheduledActionsByRolloutGroupParent(rollout,
rolloutGroup);
rolloutGroupActions.forEach(deploymentManagement::startScheduledAction);
logger.debug("{} actions started for rollout {} and parent group {}", rolloutGroupActions.size(), rollout,
logger.debug("{} Next actions started for rollout {} and parent group {}", countOfStartedActions, rollout,
rolloutGroup);
if (!rolloutGroupActions.isEmpty()) {
if (countOfStartedActions > 0) {
// get all next scheduled groups and set them in state running
rolloutGroupRepository.setStatusForCildren(RolloutGroupStatus.RUNNING, rolloutGroup);
} else {

View File

@@ -31,7 +31,10 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaTargetInfo_;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag_;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_;
import org.eclipse.hawkbit.repository.jpa.model.RolloutTargetGroup;
import org.eclipse.hawkbit.repository.jpa.model.RolloutTargetGroup_;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetInfo;
import org.eclipse.hawkbit.repository.model.TargetTag;
@@ -255,6 +258,45 @@ public final class TargetSpecifications {
};
}
/**
* {@link Specification} for retrieving {@link Target}s that are not in the
* given {@link RolloutGroup}s
*
* @param groups
* the {@link RolloutGroup}s
* @return the {@link Target} {@link Specification}
*/
public static Specification<JpaTarget> isNotInRolloutGroups(final List<RolloutGroup> groups) {
return (targetRoot, query, cb) -> {
ListJoin<JpaTarget, RolloutTargetGroup> rolloutTargetJoin = targetRoot.join(JpaTarget_.rolloutTargetGroup,
JoinType.LEFT);
Predicate inRolloutGroups = rolloutTargetJoin.get(RolloutTargetGroup_.rolloutGroup).in(groups);
rolloutTargetJoin.on(inRolloutGroups);
return cb.isNull(rolloutTargetJoin.get(RolloutTargetGroup_.target));
};
}
/**
* {@link Specification} for retrieving {@link Target}s that have no Action
* of the {@link RolloutGroup}.
*
* @param group
* the {@link RolloutGroup}
* @return the {@link Target} {@link Specification}
*/
public static Specification<JpaTarget> hasNoActionInRolloutGroup(final RolloutGroup group) {
return (targetRoot, query, cb) -> {
ListJoin<JpaTarget, RolloutTargetGroup> rolloutTargetJoin = targetRoot.join(JpaTarget_.rolloutTargetGroup,
JoinType.INNER);
rolloutTargetJoin.on(cb.equal(rolloutTargetJoin.get(RolloutTargetGroup_.rolloutGroup), group));
final ListJoin<JpaTarget, JpaAction> actionsJoin = targetRoot.join(JpaTarget_.actions, JoinType.LEFT);
actionsJoin.on(cb.equal(actionsJoin.get(JpaAction_.rolloutGroup), group));
return cb.isNull(actionsJoin.get(JpaAction_.id));
};
}
/**
* {@link Specification} for retrieving {@link Target}s by assigned
* distribution set.

View File

@@ -0,0 +1,4 @@
ALTER TABLE sp_rolloutgroup
ADD COLUMN target_percentage FLOAT;
ALTER TABLE sp_rolloutgroup
ADD COLUMN target_filter VARCHAR (1024);

View File

@@ -0,0 +1,4 @@
ALTER TABLE sp_rolloutgroup
ADD COLUMN target_percentage FLOAT;
ALTER TABLE sp_rolloutgroup
ADD COLUMN target_filter VARCHAR (1024);

View File

@@ -9,16 +9,21 @@
package org.eclipse.hawkbit.repository.jpa;
import static org.fest.assertions.api.Assertions.assertThat;
import static org.fest.assertions.api.Assertions.fail;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import org.eclipse.hawkbit.repository.OffsetBasedPageRequest;
import org.eclipse.hawkbit.repository.RolloutGroupManagement;
import org.eclipse.hawkbit.repository.RolloutManagement;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException;
import org.eclipse.hawkbit.repository.exception.RolloutVerificationException;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout;
@@ -190,7 +195,11 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
final Rollout createdRollout = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout,
amountOtherTargets, amountGroups, successCondition, errorCondition);
rolloutManagement.startRollout(createdRollout);
return createdRollout;
// Run here, because scheduler is disabled during tests
rolloutManagement.checkStartingRollouts(0);
return rolloutManagement.findRolloutById(createdRollout.getId());
}
@Step("Finish three actions of the rollout group and delete two targets")
@@ -407,6 +416,10 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
validateRolloutActionStatus(createdRollout.getId(), validationMap);
rolloutManagement.startRollout(createdRollout);
// Run here, because scheduler is disabled during tests
rolloutManagement.checkStartingRollouts(0);
// 6 targets are ready and 2 are running
validationMap = createInitStatusMap();
validationMap.put(TotalTargetCountStatus.Status.SCHEDULED, 6L);
@@ -466,6 +479,10 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
validateRolloutActionStatus(createdRollout.getId(), validationMap);
rolloutManagement.startRollout(createdRollout);
// Run here, because scheduler is disabled during tests
rolloutManagement.checkStartingRollouts(0);
// 6 targets are ready and 2 are running
validationMap = createInitStatusMap();
validationMap.put(TotalTargetCountStatus.Status.SCHEDULED, 6L);
@@ -496,19 +513,28 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
changeStatusForAllRunningActions(createdRollout, Status.FINISHED);
rolloutManagement.checkRunningRollouts(0);
// 3 targets finished (Group 1), 3 targets running (Group 3) and 3
// targets SCHEDULED (Group 3)
// round(9/4)=2 targets finished (Group 1)
// round(7/3)=2 targets running (Group 3)
// round(5/2)=3 targets SCHEDULED (Group 3)
// round(2/1)=2 targets SCHEDULED (Group 4)
createdRollout = rolloutManagement.findRolloutById(createdRollout.getId());
final List<RolloutGroup> rolloutGruops = createdRollout.getRolloutGroups();
final List<RolloutGroup> rolloutGroups = createdRollout.getRolloutGroups();
Map<TotalTargetCountStatus.Status, Long> expectedTargetCountStatus = createInitStatusMap();
expectedTargetCountStatus.put(TotalTargetCountStatus.Status.FINISHED, 3L);
validateRolloutGroupActionStatus(rolloutGruops.get(0), expectedTargetCountStatus);
expectedTargetCountStatus.put(TotalTargetCountStatus.Status.FINISHED, 2L);
validateRolloutGroupActionStatus(rolloutGroups.get(0), expectedTargetCountStatus);
expectedTargetCountStatus = createInitStatusMap();
expectedTargetCountStatus.put(TotalTargetCountStatus.Status.RUNNING, 3L);
validateRolloutGroupActionStatus(rolloutGruops.get(1), expectedTargetCountStatus);
expectedTargetCountStatus.put(TotalTargetCountStatus.Status.RUNNING, 2L);
validateRolloutGroupActionStatus(rolloutGroups.get(1), expectedTargetCountStatus);
expectedTargetCountStatus = createInitStatusMap();
expectedTargetCountStatus.put(TotalTargetCountStatus.Status.SCHEDULED, 3L);
validateRolloutGroupActionStatus(rolloutGruops.get(2), expectedTargetCountStatus);
validateRolloutGroupActionStatus(rolloutGroups.get(2), expectedTargetCountStatus);
expectedTargetCountStatus = createInitStatusMap();
expectedTargetCountStatus.put(TotalTargetCountStatus.Status.SCHEDULED, 2L);
validateRolloutGroupActionStatus(rolloutGroups.get(3), expectedTargetCountStatus);
}
@@ -584,6 +610,10 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
validateRolloutActionStatus(rolloutOne.getId(), expectedTargetCountStatus);
rolloutManagement.startRollout(rolloutTwo);
// Run here, because scheduler is disabled during tests
rolloutManagement.checkStartingRollouts(0);
// Verify that 5 targets are finished, 5 are still running and 5 are
// cancelled.
expectedTargetCountStatus = createInitStatusMap();
@@ -631,11 +661,15 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
amountGroupsForRolloutTwo, "controllerId==rollout-*", distributionSet, "50", "80");
rolloutManagement.startRollout(rolloutTwo);
// Run here, because scheduler is disabled during tests
rolloutManagement.checkStartingRollouts(0);
rolloutTwo = rolloutManagement.findRolloutById(rolloutTwo.getId());
// 6 error targets are know running
expectedTargetCountStatus = createInitStatusMap();
expectedTargetCountStatus.put(TotalTargetCountStatus.Status.RUNNING, 6L);
expectedTargetCountStatus.put(TotalTargetCountStatus.Status.NOTSTARTED, 9L);
expectedTargetCountStatus.put(TotalTargetCountStatus.Status.FINISHED, 9L);
validateRolloutActionStatus(rolloutTwo.getId(), expectedTargetCountStatus);
changeStatusForAllRunningActions(rolloutTwo, Status.FINISHED);
final Page<Target> targetPage = targetManagement.findTargetByUpdateStatus(pageReq, TargetUpdateStatus.IN_SYNC);
@@ -729,12 +763,15 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
final Rollout rolloutA = createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, amountGroups,
successCondition, errorCondition, "RolloutA", "RolloutA");
rolloutManagement.startRollout(rolloutA);
rolloutManagement.checkStartingRollouts(0);
final int amountTargetsForRollout2 = 10;
final int amountGroups2 = 2;
final Rollout rolloutB = createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout2, amountGroups2,
successCondition, errorCondition, "RolloutB", "RolloutB");
rolloutManagement.startRollout(rolloutB);
rolloutManagement.checkStartingRollouts(0);
changeStatusForAllRunningActions(rolloutB, Status.FINISHED);
rolloutManagement.checkRunningRollouts(0);
@@ -743,6 +780,8 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
final Rollout rolloutC = createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout3, amountGroups3,
successCondition, errorCondition, "RolloutC", "RolloutC");
rolloutManagement.startRollout(rolloutC);
rolloutManagement.checkStartingRollouts(0);
changeStatusForAllRunningActions(rolloutC, Status.ERROR);
rolloutManagement.checkRunningRollouts(0);
@@ -751,6 +790,8 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
final Rollout rolloutD = createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout4, amountGroups4,
successCondition, errorCondition, "RolloutD", "RolloutD");
rolloutManagement.startRollout(rolloutD);
rolloutManagement.checkStartingRollouts(0);
changeStatusForRunningActions(rolloutD, Status.ERROR, 1);
rolloutManagement.checkRunningRollouts(0);
changeStatusForAllRunningActions(rolloutD, Status.FINISHED);
@@ -882,6 +923,10 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
Rollout myRollout = createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, amountGroups,
successCondition, errorCondition, rolloutName, rolloutName);
rolloutManagement.startRollout(myRollout);
// Run here, because scheduler is disabled during tests
rolloutManagement.checkStartingRollouts(0);
changeStatusForRunningActions(myRollout, Status.FINISHED, 2);
rolloutManagement.checkRunningRollouts(0);
myRollout = rolloutManagement.findRolloutById(myRollout.getId());
@@ -924,6 +969,10 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
final String rsqlParam = "controllerId==*MyRoll*";
rolloutManagement.startRollout(myRollout);
// Run here, because scheduler is disabled during tests
rolloutManagement.checkStartingRollouts(0);
myRollout = rolloutManagement.findRolloutById(myRollout.getId());
final List<RolloutGroup> rolloutGroups = myRollout.getRolloutGroups();
@@ -948,8 +997,101 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
}
@Test
@Description("Verify the creation and the start of a rollout in asynchronous mode.")
public void createAndStartRolloutInAsync() throws Exception {
@Description("Verify the creation of a Rollout without targets throws an Exception.")
public void createRolloutNotMatchingTargets() {
final int amountGroups = 5;
final String successCondition = "50";
final String errorCondition = "80";
final String rolloutName = "rolloutTest3";
final DistributionSet distributionSet = testdataFactory.createDistributionSet("dsFor" + rolloutName);
try {
createRolloutByVariables(rolloutName, "desc", amountGroups, "id==notExisting", distributionSet,
successCondition, errorCondition);
fail("Was able to create a Rollout without targets.");
} catch(RolloutVerificationException e) {
// OK
}
}
@Test
@Description("Verify the creation of a Rollout with the same name throws an Exception.")
public void createDuplicateRollout() {
final int amountGroups = 5;
final int amountTargetsForRollout = 10;
final String successCondition = "50";
final String errorCondition = "80";
final String rolloutName = "rolloutTest4";
targetManagement.createTargets(testdataFactory.generateTargets(amountTargetsForRollout, "dup-ro-", "rollout"));
final DistributionSet distributionSet = testdataFactory.createDistributionSet("dsFor" + rolloutName);
createRolloutByVariables(rolloutName, "desc", amountGroups, "id==dup-ro-*", distributionSet,
successCondition, errorCondition);
try {
createRolloutByVariables(rolloutName, "desc", amountGroups, "id==dup-ro-*", distributionSet,
successCondition, errorCondition);
fail("Was able to create a duplicate Rollout.");
} catch(EntityAlreadyExistsException e) {
// OK
}
}
@Test
@Description("Verify the creation and the start of a Rollout with more groups than targets.")
public void createAndStartRolloutWithEmptyGroups() throws Exception {
final int amountTargetsForRollout = 3;
final int amountGroups = 5;
final String successCondition = "50";
final String errorCondition = "80";
final String rolloutName = "rolloutTestG";
final String targetPrefixName = rolloutName;
final DistributionSet distributionSet = testdataFactory.createDistributionSet("dsFor" + rolloutName);
targetManagement.createTargets(
testdataFactory.generateTargets(amountTargetsForRollout, targetPrefixName + "-", targetPrefixName));
Rollout myRollout = createRolloutByVariables(rolloutName, "desc", amountGroups,
"controllerId==" + targetPrefixName + "-*", distributionSet, successCondition, errorCondition);
assertThat(myRollout.getStatus()).isEqualTo(RolloutStatus.READY);
List<RolloutGroup> groups = myRollout.getRolloutGroups();
assertThat(groups.get(0).getStatus()).isEqualTo(RolloutGroupStatus.READY);
assertThat(groups.get(0).getTotalTargets()).isEqualTo(1);
assertThat(groups.get(1).getStatus()).isEqualTo(RolloutGroupStatus.READY);
assertThat(groups.get(1).getTotalTargets()).isEqualTo(1);
assertThat(groups.get(2).getStatus()).isEqualTo(RolloutGroupStatus.READY);
assertThat(groups.get(2).getTotalTargets()).isEqualTo(0);
assertThat(groups.get(3).getStatus()).isEqualTo(RolloutGroupStatus.READY);
assertThat(groups.get(3).getTotalTargets()).isEqualTo(1);
assertThat(groups.get(4).getStatus()).isEqualTo(RolloutGroupStatus.READY);
assertThat(groups.get(4).getTotalTargets()).isEqualTo(0);
rolloutManagement.startRollout(myRollout);
// Run here, because scheduler is disabled during tests
rolloutManagement.checkStartingRollouts(0);
SuccessConditionRolloutStatus conditionRolloutStatus = new SuccessConditionRolloutStatus(RolloutStatus.RUNNING);
assertThat(MultipleInvokeHelper.doWithTimeout(new RolloutStatusCallable(myRollout.getId()),
conditionRolloutStatus, 15000, 500)).as("Rollout status").isNotNull();
myRollout = rolloutManagement.findRolloutById(myRollout.getId());
assertThat(myRollout.getStatus()).isEqualTo(RolloutStatus.RUNNING);
final Map<TotalTargetCountStatus.Status, Long> expectedTargetCountStatus = createInitStatusMap();
expectedTargetCountStatus.put(TotalTargetCountStatus.Status.RUNNING, 1L);
expectedTargetCountStatus.put(TotalTargetCountStatus.Status.SCHEDULED, 2L);
validateRolloutActionStatus(myRollout.getId(), expectedTargetCountStatus);
}
@Test
@Description("Verify the creation and the start of a rollout.")
public void createAndStartRollout() throws Exception {
final int amountTargetsForRollout = 500;
final int amountGroups = 5;
@@ -960,28 +1102,18 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
final DistributionSet distributionSet = testdataFactory.createDistributionSet("dsFor" + rolloutName);
targetManagement.createTargets(
testdataFactory.generateTargets(amountTargetsForRollout, targetPrefixName + "-", targetPrefixName));
final RolloutGroupConditions conditions = new RolloutGroupConditionBuilder()
.successCondition(RolloutGroupSuccessCondition.THRESHOLD, successCondition)
.errorCondition(RolloutGroupErrorCondition.THRESHOLD, errorCondition)
.errorAction(RolloutGroupErrorAction.PAUSE, null).build();
Rollout myRollout = new JpaRollout();
myRollout.setName(rolloutName);
myRollout.setDescription("This is a test description for the rollout");
myRollout.setTargetFilterQuery("controllerId==" + targetPrefixName + "-*");
myRollout.setDistributionSet(distributionSet);
myRollout = rolloutManagement.createRolloutAsync(myRollout, amountGroups, conditions);
Rollout myRollout = createRolloutByVariables(rolloutName, "desc", amountGroups,
"controllerId==" + targetPrefixName + "-*", distributionSet, successCondition, errorCondition);
assertThat(myRollout.getStatus()).isEqualTo(RolloutStatus.READY);
rolloutManagement.startRollout(myRollout);
// Run here, because scheduler is disabled during tests
rolloutManagement.checkStartingRollouts(0);
SuccessConditionRolloutStatus conditionRolloutTargetCount = new SuccessConditionRolloutStatus(
RolloutStatus.READY);
assertThat(MultipleInvokeHelper.doWithTimeout(new RolloutStatusCallable(myRollout.getId()),
conditionRolloutTargetCount, 15000, 500)).as("Rollout status").isNotNull();
myRollout = rolloutManagement.findRolloutById(myRollout.getId());
assertThat(myRollout.getStatus()).isEqualTo(RolloutStatus.READY);
rolloutManagement.startRolloutAsync(myRollout);
conditionRolloutTargetCount = new SuccessConditionRolloutStatus(RolloutStatus.RUNNING);
RolloutStatus.RUNNING);
assertThat(MultipleInvokeHelper.doWithTimeout(new RolloutStatusCallable(myRollout.getId()),
conditionRolloutTargetCount, 15000, 500)).as("Rollout status").isNotNull();
@@ -993,6 +1125,198 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
validateRolloutActionStatus(myRollout.getId(), expectedTargetCountStatus);
}
@Test
@Description("Verify the creation of a Rollout with a groups definition.")
public void createRolloutWithGroupDefinition() throws Exception {
final String rolloutName = "rolloutTest3";
final int amountTargetsInGroup1 = 100;
final int percentTargetsInGroup1 = 100;
final int amountTargetsInGroup1and2 = 500;
final int percentTargetsInGroup2 = 20;
final int percentTargetsInGroup3 = 100;
final int countTargetsInGroup2 = (int) Math
.ceil((double) percentTargetsInGroup2 / 100 * (double) amountTargetsInGroup1and2);
final int countTargetsInGroup3 = amountTargetsInGroup1and2 - countTargetsInGroup2;
final RolloutGroupConditions conditions = new RolloutGroupConditionBuilder().build();
// Generate Targets for group 2 and 3 and generate the Rollout
Rollout myRollout = generateTargetsAndRollout(rolloutName, amountTargetsInGroup1and2);
// Generate Targets for group 1
targetManagement.createTargets(
testdataFactory.generateTargets(amountTargetsInGroup1, rolloutName + "-gr1-", rolloutName));
List<RolloutGroup> rolloutGroups = new ArrayList<>(3);
rolloutGroups.add(generateRolloutGroup(0, percentTargetsInGroup1, "id==" + rolloutName + "-gr1-*"));
rolloutGroups.add(generateRolloutGroup(1, percentTargetsInGroup2, null));
rolloutGroups.add(generateRolloutGroup(2, percentTargetsInGroup3, null));
myRollout = rolloutManagement.createRollout(myRollout, rolloutGroups, conditions);
myRollout = rolloutManagement.findRolloutById(myRollout.getId());
assertThat(myRollout.getStatus()).isEqualTo(RolloutStatus.CREATING);
for (RolloutGroup group : myRollout.getRolloutGroups()) {
assertThat(group.getStatus()).isEqualTo(RolloutGroupStatus.CREATING);
}
// Generate Targets that must not be addressed by the rollout, because
// they were added after the rollout was created
TimeUnit.SECONDS.sleep(1);
targetManagement.createTargets(testdataFactory.generateTargets(10, rolloutName + "-notIn-", rolloutName));
rolloutManagement.fillRolloutGroupsWithTargets(myRollout);
myRollout = rolloutManagement.findRolloutById(myRollout.getId());
assertThat(myRollout.getStatus()).isEqualTo(RolloutStatus.READY);
assertThat(myRollout.getTotalTargets()).isEqualTo(amountTargetsInGroup1and2 + amountTargetsInGroup1);
List<RolloutGroup> groups = myRollout.getRolloutGroups();
assertThat(groups.get(0).getStatus()).isEqualTo(RolloutGroupStatus.READY);
assertThat(groups.get(0).getTotalTargets()).isEqualTo(amountTargetsInGroup1);
assertThat(groups.get(1).getStatus()).isEqualTo(RolloutGroupStatus.READY);
assertThat(groups.get(1).getTotalTargets()).isEqualTo(countTargetsInGroup2);
assertThat(groups.get(2).getStatus()).isEqualTo(RolloutGroupStatus.READY);
assertThat(groups.get(2).getTotalTargets()).isEqualTo(countTargetsInGroup3);
}
@Test
@Description("Verify Exception when a Rollout with Group definition is created that does not address all targets")
public void createRolloutWithGroupsNotMatchingTargets() throws Exception {
final String rolloutName = "rolloutTest4";
final int amountTargetsForRollout = 500;
final int percentTargetsInGroup1 = 20;
final int percentTargetsInGroup2 = 50;
final RolloutGroupConditions conditions = new RolloutGroupConditionBuilder().build();
Rollout myRollout = generateTargetsAndRollout(rolloutName, amountTargetsForRollout);
List<RolloutGroup> rolloutGroups = new ArrayList<>(2);
rolloutGroups.add(generateRolloutGroup(0, percentTargetsInGroup1, null));
rolloutGroups.add(generateRolloutGroup(1, percentTargetsInGroup2, null));
try {
rolloutManagement.createRollout(myRollout, rolloutGroups, conditions);
fail("Was able to create a Rollout with groups that are not addressing all targets");
} catch(RolloutVerificationException e) {
// OK
}
}
@Test
@Description("Verify Exception when a Rollout with Group definition is created that contains an illegal percentage")
public void createRolloutWithIllegalPercentage() throws Exception {
final String rolloutName = "rolloutTest6";
final int amountTargetsForRollout = 10;
final int percentTargetsInGroup1 = 101;
final int percentTargetsInGroup2 = 50;
final RolloutGroupConditions conditions = new RolloutGroupConditionBuilder().build();
Rollout myRollout = generateTargetsAndRollout(rolloutName, amountTargetsForRollout);
List<RolloutGroup> rolloutGroups = new ArrayList<>(2);
rolloutGroups.add(generateRolloutGroup(0, percentTargetsInGroup1, null));
rolloutGroups.add(generateRolloutGroup(1, percentTargetsInGroup2, null));
try {
rolloutManagement.createRollout(myRollout, rolloutGroups, conditions);
fail("Was able to create a Rollout with groups that have illegal percentages");
} catch(RolloutVerificationException e) {
// OK
}
}
@Test
@Description("Verify Exception when a Rollout is created with too much groups")
public void createRolloutWithIllegalAmountOfGroups() throws Exception {
final String rolloutName = "rolloutTest5";
final int amountTargetsForRollout = 10;
final int illegalGroupAmount = 501;
final RolloutGroupConditions conditions = new RolloutGroupConditionBuilder().build();
Rollout myRollout = generateTargetsAndRollout(rolloutName, amountTargetsForRollout);
try {
rolloutManagement.createRollout(myRollout, illegalGroupAmount, conditions);
fail("Was able to create a Rollout with too many groups");
} catch(RolloutVerificationException e) {
// OK
}
}
@Test
@Description("Verify the start of a Rollout does not work during creation phase.")
public void createAndStartRolloutDuringCreationFails() throws Exception {
final int amountTargetsForRollout = 3;
final int amountGroups = 5;
final String successCondition = "50";
final String errorCondition = "80";
final String rolloutName = "rolloutTestGC";
final String targetPrefixName = rolloutName;
final DistributionSet distributionSet = testdataFactory.createDistributionSet("dsFor" + rolloutName);
targetManagement.createTargets(
testdataFactory.generateTargets(amountTargetsForRollout, targetPrefixName + "-", targetPrefixName));
final RolloutGroupConditions conditions = new RolloutGroupConditionBuilder()
.successCondition(RolloutGroupSuccessCondition.THRESHOLD, successCondition)
.errorCondition(RolloutGroupErrorCondition.THRESHOLD, errorCondition)
.errorAction(RolloutGroupErrorAction.PAUSE, null).build();
final Rollout rolloutToCreate = new JpaRollout();
rolloutToCreate.setName(rolloutName);
rolloutToCreate.setDescription("some description");
rolloutToCreate.setTargetFilterQuery("id==" + targetPrefixName + "-*");
rolloutToCreate.setDistributionSet(distributionSet);
Rollout myRollout = rolloutManagement.createRollout(rolloutToCreate, amountGroups, conditions);
myRollout = rolloutManagement.findRolloutById(myRollout.getId());
assertThat(myRollout.getStatus()).isEqualTo(RolloutStatus.CREATING);
try {
rolloutManagement.startRollout(myRollout);
fail("Was able to start a Rollout in CREATING status");
} catch(RolloutIllegalStateException e) {
// OK
}
}
private RolloutGroup generateRolloutGroup(final int index, Integer percentage, String targetFilter) {
RolloutGroup group = entityFactory.generateRolloutGroup();
group.setName("Group" + index);
group.setDescription("Group" + index + "desc");
if (percentage != null) {
group.setTargetPercentage(percentage);
}
if (targetFilter != null) {
group.setTargetFilterQuery(targetFilter);
}
return group;
}
private Rollout generateTargetsAndRollout(final String rolloutName, final int amountTargetsForRollout) {
final DistributionSet distributionSet = testdataFactory.createDistributionSet("dsFor" + rolloutName);
targetManagement.createTargets(
testdataFactory.generateTargets(amountTargetsForRollout, rolloutName + "-", rolloutName));
Rollout myRollout = entityFactory.generateRollout();
myRollout.setName(rolloutName);
myRollout.setDescription("This is a test description for the rollout");
myRollout.setTargetFilterQuery("controllerId==" + rolloutName + "-*");
myRollout.setDistributionSet(distributionSet);
return myRollout;
}
private void validateRolloutGroupActionStatus(final RolloutGroup rolloutGroup,
final Map<TotalTargetCountStatus.Status, Long> expectedTargetCountStatus) {
final RolloutGroup rolloutGroupWithDetail = rolloutGroupManagement
@@ -1052,7 +1376,12 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
rolloutToCreate.setDescription(rolloutDescription);
rolloutToCreate.setTargetFilterQuery(filterQuery);
rolloutToCreate.setDistributionSet(distributionSet);
return rolloutManagement.createRollout(rolloutToCreate, groupSize, conditions);
final Rollout rollout = rolloutManagement.createRollout(rolloutToCreate, groupSize, conditions);
// Run here, because Scheduler is disabled during tests
rolloutManagement.fillRolloutGroupsWithTargets(rolloutManagement.findRolloutById(rollout.getId()));
return rolloutManagement.findRolloutById(rollout.getId());
}
private int changeStatusForAllRunningActions(final Rollout rollout, final Status status) {

View File

@@ -18,6 +18,7 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.junit.Before;
import org.junit.Test;
import org.springframework.data.domain.PageRequest;
@@ -36,17 +37,20 @@ public class RSQLActionFieldsTest extends AbstractJpaIntegrationTest {
@Before
public void setupBeforeTest() {
final DistributionSet dsA = testdataFactory.createDistributionSet("daA");
target = new JpaTarget("targetId123");
target.setDescription("targetId123");
targetManagement.createTarget(target);
action = new JpaAction();
action.setActionType(ActionType.SOFT);
action.setDistributionSet(dsA);
target.addAction(action);
action.setTarget(target);
actionRepository.save(action);
for (int i = 0; i < 10; i++) {
final JpaAction newAction = new JpaAction();
newAction.setActionType(ActionType.SOFT);
newAction.setDistributionSet(dsA);
newAction.setActive(i % 2 == 0);
newAction.setTarget(target);
actionRepository.save(newAction);