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:
committed by
Michael Hirsch
parent
66b6983406
commit
b6834e9ee2
@@ -190,7 +190,7 @@ public class ConfigurableScenario {
|
||||
.getBody();
|
||||
|
||||
// start the created Rollout
|
||||
rolloutResource.start(rolloutResponseBody.getRolloutId(), true);
|
||||
rolloutResource.start(rolloutResponseBody.getRolloutId());
|
||||
|
||||
// wait until rollout is complete
|
||||
do {
|
||||
|
||||
@@ -102,7 +102,7 @@ public class CreateStartedRolloutExample {
|
||||
.getBody();
|
||||
|
||||
// start the created Rollout
|
||||
rolloutResource.start(rolloutResponseBody.getRolloutId(), false);
|
||||
rolloutResource.start(rolloutResponseBody.getRolloutId());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -164,7 +164,12 @@ public enum SpServerError {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
SP_ROLLOUT_ILLEGAL_STATE("hawkbit.server.error.rollout.illegalstate", "The rollout is currently in the wrong state for the current operation");
|
||||
SP_ROLLOUT_ILLEGAL_STATE("hawkbit.server.error.rollout.illegalstate", "The rollout is in the wrong state for the requested operation"),
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
SP_ROLLOUT_VERIFICATION_FAILED("hawkbit.server.error.rollout.verificationFailed", "The rollout configuration could not be verified successfully");
|
||||
|
||||
private final String key;
|
||||
private final String message;
|
||||
@@ -172,7 +177,7 @@ public enum SpServerError {
|
||||
/*
|
||||
* Repository side Error codes
|
||||
*/
|
||||
private SpServerError(final String errorKey, final String message) {
|
||||
SpServerError(final String errorKey, final String message) {
|
||||
key = errorKey;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,14 @@ public enum TargetFields implements FieldNameProvider {
|
||||
* The description field.
|
||||
*/
|
||||
DESCRIPTION("description"),
|
||||
/**
|
||||
* The createdAt field.
|
||||
*/
|
||||
CREATEDAT("createdAt"),
|
||||
/**
|
||||
* The createdAt field.
|
||||
*/
|
||||
LASTMODIFIEDAT("lastModifiedAt"),
|
||||
/**
|
||||
* The controllerId field.
|
||||
*/
|
||||
@@ -75,19 +83,19 @@ public enum TargetFields implements FieldNameProvider {
|
||||
private List<String> subEntityAttribues;
|
||||
private boolean mapField;
|
||||
|
||||
private TargetFields(final String fieldName) {
|
||||
TargetFields(final String fieldName) {
|
||||
this(fieldName, false, Collections.emptyList());
|
||||
}
|
||||
|
||||
private TargetFields(final String fieldName, final boolean isMapField) {
|
||||
TargetFields(final String fieldName, final boolean isMapField) {
|
||||
this(fieldName, isMapField, Collections.emptyList());
|
||||
}
|
||||
|
||||
private TargetFields(final String fieldName, final String... subEntityAttribues) {
|
||||
TargetFields(final String fieldName, final String... subEntityAttribues) {
|
||||
this(fieldName, false, Arrays.asList(subEntityAttribues));
|
||||
}
|
||||
|
||||
private TargetFields(final String fieldName, final boolean mapField, final List<String> subEntityAttribues) {
|
||||
TargetFields(final String fieldName, final boolean mapField, final List<String> subEntityAttribues) {
|
||||
this.fieldName = fieldName;
|
||||
this.mapField = mapField;
|
||||
this.subEntityAttribues = subEntityAttribues;
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* 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.mgmt.json.model.rollout;
|
||||
|
||||
import org.eclipse.hawkbit.mgmt.json.model.MgmtNamedEntity;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
|
||||
/**
|
||||
* Model for defining Conditions and Actions
|
||||
*/
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public abstract class AbstractMgmtRolloutConditionsEntity extends MgmtNamedEntity {
|
||||
|
||||
private MgmtRolloutCondition successCondition;
|
||||
private MgmtRolloutSuccessAction successAction;
|
||||
private MgmtRolloutCondition errorCondition;
|
||||
private MgmtRolloutErrorAction errorAction;
|
||||
|
||||
public MgmtRolloutCondition getSuccessCondition() {
|
||||
return successCondition;
|
||||
}
|
||||
|
||||
public void setSuccessCondition(final MgmtRolloutCondition successCondition) {
|
||||
this.successCondition = successCondition;
|
||||
}
|
||||
|
||||
public MgmtRolloutSuccessAction getSuccessAction() {
|
||||
return successAction;
|
||||
}
|
||||
|
||||
public void setSuccessAction(final MgmtRolloutSuccessAction successAction) {
|
||||
this.successAction = successAction;
|
||||
}
|
||||
|
||||
public MgmtRolloutCondition getErrorCondition() {
|
||||
return errorCondition;
|
||||
}
|
||||
|
||||
public void setErrorCondition(final MgmtRolloutCondition errorCondition) {
|
||||
this.errorCondition = errorCondition;
|
||||
}
|
||||
|
||||
public MgmtRolloutErrorAction getErrorAction() {
|
||||
return errorAction;
|
||||
}
|
||||
|
||||
public void setErrorAction(final MgmtRolloutErrorAction errorAction) {
|
||||
this.errorAction = errorAction;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,14 +13,34 @@ import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
|
||||
/**
|
||||
*
|
||||
* An action that runs when the error condition is met
|
||||
*/
|
||||
@JsonInclude(Include.NON_NULL)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class MgmtRolloutErrorAction {
|
||||
|
||||
private ErrorAction action = ErrorAction.PAUSE;
|
||||
private String expression = null;
|
||||
private String expression;
|
||||
|
||||
/**
|
||||
* Creates a rollout error action
|
||||
*
|
||||
* @param action
|
||||
* the action to run when th error condition is met
|
||||
* @param expression
|
||||
* the expression for the action
|
||||
*/
|
||||
public MgmtRolloutErrorAction(ErrorAction action, String expression) {
|
||||
this.action = action;
|
||||
this.expression = expression;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
*/
|
||||
public MgmtRolloutErrorAction() {
|
||||
// Instantiate default error action
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the action
|
||||
@@ -52,6 +72,9 @@ public class MgmtRolloutErrorAction {
|
||||
this.expression = expression;
|
||||
}
|
||||
|
||||
/**
|
||||
* Possible actions
|
||||
*/
|
||||
public enum ErrorAction {
|
||||
PAUSE;
|
||||
}
|
||||
|
||||
@@ -8,12 +8,14 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.mgmt.json.model.rollout;
|
||||
|
||||
import org.eclipse.hawkbit.mgmt.json.model.MgmtNamedEntity;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtActionType;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.rolloutgroup.MgmtRolloutGroup;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Model for request containing a rollout body e.g. in a POST request of
|
||||
@@ -21,66 +23,18 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
*/
|
||||
@JsonInclude(Include.NON_NULL)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class MgmtRolloutRestRequestBody extends MgmtNamedEntity {
|
||||
public class MgmtRolloutRestRequestBody extends AbstractMgmtRolloutConditionsEntity {
|
||||
|
||||
private String targetFilterQuery;
|
||||
private long distributionSetId;
|
||||
|
||||
private int amountGroups = 1;
|
||||
|
||||
private MgmtRolloutCondition successCondition = new MgmtRolloutCondition();
|
||||
private MgmtRolloutSuccessAction successAction = new MgmtRolloutSuccessAction();
|
||||
private MgmtRolloutCondition errorCondition = null;
|
||||
private MgmtRolloutErrorAction errorAction = null;
|
||||
private Integer amountGroups;
|
||||
|
||||
private Long forcetime;
|
||||
|
||||
private MgmtActionType type;
|
||||
|
||||
/**
|
||||
* @return the finishCondition
|
||||
*/
|
||||
public MgmtRolloutCondition getSuccessCondition() {
|
||||
return successCondition;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param successCondition
|
||||
* the finishCondition to set
|
||||
*/
|
||||
public void setSuccessCondition(final MgmtRolloutCondition successCondition) {
|
||||
this.successCondition = successCondition;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the successAction
|
||||
*/
|
||||
public MgmtRolloutSuccessAction getSuccessAction() {
|
||||
return successAction;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param successAction
|
||||
* the successAction to set
|
||||
*/
|
||||
public void setSuccessAction(final MgmtRolloutSuccessAction successAction) {
|
||||
this.successAction = successAction;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the errorCondition
|
||||
*/
|
||||
public MgmtRolloutCondition getErrorCondition() {
|
||||
return errorCondition;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param errorCondition
|
||||
* the errorCondition to set
|
||||
*/
|
||||
public void setErrorCondition(final MgmtRolloutCondition errorCondition) {
|
||||
this.errorCondition = errorCondition;
|
||||
}
|
||||
private List<MgmtRolloutGroup> groups;
|
||||
|
||||
/**
|
||||
* @return the targetFilterQuery
|
||||
@@ -115,7 +69,7 @@ public class MgmtRolloutRestRequestBody extends MgmtNamedEntity {
|
||||
/**
|
||||
* @return the groupSize
|
||||
*/
|
||||
public int getAmountGroups() {
|
||||
public Integer getAmountGroups() {
|
||||
return amountGroups;
|
||||
}
|
||||
|
||||
@@ -123,7 +77,7 @@ public class MgmtRolloutRestRequestBody extends MgmtNamedEntity {
|
||||
* @param groupSize
|
||||
* the groupSize to set
|
||||
*/
|
||||
public void setAmountGroups(final int groupSize) {
|
||||
public void setAmountGroups(final Integer groupSize) {
|
||||
this.amountGroups = groupSize;
|
||||
}
|
||||
|
||||
@@ -158,18 +112,16 @@ public class MgmtRolloutRestRequestBody extends MgmtNamedEntity {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the errorAction
|
||||
* @return the List of defined Groups
|
||||
*/
|
||||
public MgmtRolloutErrorAction getErrorAction() {
|
||||
return errorAction;
|
||||
public List<MgmtRolloutGroup> getGroups() {
|
||||
return groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param errorAction
|
||||
* the errorAction to set
|
||||
* @param groups List of {@link MgmtRolloutGroup}
|
||||
*/
|
||||
public void setErrorAction(final MgmtRolloutErrorAction errorAction) {
|
||||
this.errorAction = errorAction;
|
||||
public void setGroups(List<MgmtRolloutGroup> groups) {
|
||||
this.groups = groups;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.mgmt.json.model.rolloutgroup;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.rollout.AbstractMgmtRolloutConditionsEntity;
|
||||
|
||||
/**
|
||||
* Model for defining the Attributes of a Rollout Group
|
||||
*/
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class MgmtRolloutGroup extends AbstractMgmtRolloutConditionsEntity {
|
||||
|
||||
private String targetFilterQuery;
|
||||
private Float targetPercentage;
|
||||
|
||||
public String getTargetFilterQuery() {
|
||||
return targetFilterQuery;
|
||||
}
|
||||
|
||||
public void setTargetFilterQuery(final String targetFilterQuery) {
|
||||
this.targetFilterQuery = targetFilterQuery;
|
||||
}
|
||||
|
||||
public Float getTargetPercentage() {
|
||||
return targetPercentage;
|
||||
}
|
||||
|
||||
public void setTargetPercentage(Float targetPercentage) {
|
||||
this.targetPercentage = targetPercentage;
|
||||
}
|
||||
}
|
||||
@@ -8,20 +8,21 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.mgmt.json.model.rolloutgroup;
|
||||
|
||||
import org.eclipse.hawkbit.mgmt.json.model.MgmtNamedEntity;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Model for the rollout group annotated with json-annotations for easier
|
||||
* serialization and de-serialization.
|
||||
*/
|
||||
@JsonInclude(Include.NON_NULL)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class MgmtRolloutGroupResponseBody extends MgmtNamedEntity {
|
||||
public class MgmtRolloutGroupResponseBody extends MgmtRolloutGroup {
|
||||
|
||||
@JsonProperty(value = "id", required = true)
|
||||
private Long rolloutGroupId;
|
||||
@@ -29,6 +30,10 @@ public class MgmtRolloutGroupResponseBody extends MgmtNamedEntity {
|
||||
@JsonProperty(required = true)
|
||||
private String status;
|
||||
|
||||
private int totalTargets;
|
||||
|
||||
private final Map<String, Long> totalTargetsPerStatus = new HashMap<>();
|
||||
|
||||
/**
|
||||
* @return the rolloutGroupId
|
||||
*/
|
||||
@@ -58,4 +63,17 @@ public class MgmtRolloutGroupResponseBody extends MgmtNamedEntity {
|
||||
public void setStatus(final String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public int getTotalTargets() {
|
||||
return totalTargets;
|
||||
}
|
||||
|
||||
public void setTotalTargets(int totalTargets) {
|
||||
this.totalTargets = totalTargets;
|
||||
}
|
||||
|
||||
public Map<String, Long> getTotalTargetsPerStatus() {
|
||||
return totalTargetsPerStatus;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -88,8 +88,7 @@ public interface MgmtRolloutRestApi {
|
||||
*/
|
||||
@RequestMapping(method = RequestMethod.POST, value = "/{rolloutId}/start", produces = { "application/hal+json",
|
||||
MediaType.APPLICATION_JSON_VALUE })
|
||||
ResponseEntity<Void> start(@PathVariable("rolloutId") final Long rolloutId,
|
||||
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_ASYNC, defaultValue = "false") final boolean startAsync);
|
||||
ResponseEntity<Void> start(@PathVariable("rolloutId") final Long rolloutId);
|
||||
|
||||
/**
|
||||
* Handles the POST request for pausing a rollout.
|
||||
|
||||
@@ -15,15 +15,20 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutCondition;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutCondition.Condition;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutErrorAction;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutErrorAction.ErrorAction;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutResponseBody;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutRestRequestBody;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutSuccessAction;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutSuccessAction.SuccessAction;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.rolloutgroup.MgmtRolloutGroup;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.rolloutgroup.MgmtRolloutGroupResponseBody;
|
||||
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
|
||||
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRolloutRestApi;
|
||||
import org.eclipse.hawkbit.repository.EntityFactory;
|
||||
import org.eclipse.hawkbit.repository.exception.ConstraintViolationException;
|
||||
import org.eclipse.hawkbit.repository.model.Action.ActionType;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||
import org.eclipse.hawkbit.repository.model.Rollout;
|
||||
@@ -76,8 +81,7 @@ final class MgmtRolloutMapper {
|
||||
}
|
||||
|
||||
body.add(linkTo(methodOn(MgmtRolloutRestApi.class).getRollout(rollout.getId())).withRel("self"));
|
||||
body.add(linkTo(methodOn(MgmtRolloutRestApi.class).start(rollout.getId(), false)).withRel("start"));
|
||||
body.add(linkTo(methodOn(MgmtRolloutRestApi.class).start(rollout.getId(), true)).withRel("startAsync"));
|
||||
body.add(linkTo(methodOn(MgmtRolloutRestApi.class).start(rollout.getId())).withRel("start"));
|
||||
body.add(linkTo(methodOn(MgmtRolloutRestApi.class).pause(rollout.getId())).withRel("pause"));
|
||||
body.add(linkTo(methodOn(MgmtRolloutRestApi.class).resume(rollout.getId())).withRel("resume"));
|
||||
body.add(linkTo(methodOn(MgmtRolloutRestApi.class).getRolloutGroups(rollout.getId(),
|
||||
@@ -88,12 +92,12 @@ final class MgmtRolloutMapper {
|
||||
}
|
||||
|
||||
static Rollout fromRequest(final EntityFactory entityFactory, final MgmtRolloutRestRequestBody restRequest,
|
||||
final DistributionSet distributionSet, final String filterQuery) {
|
||||
final DistributionSet distributionSet) {
|
||||
final Rollout rollout = entityFactory.generateRollout();
|
||||
rollout.setName(restRequest.getName());
|
||||
rollout.setDescription(restRequest.getDescription());
|
||||
rollout.setDistributionSet(distributionSet);
|
||||
rollout.setTargetFilterQuery(filterQuery);
|
||||
rollout.setTargetFilterQuery(restRequest.getTargetFilterQuery());
|
||||
final ActionType convertActionType = MgmtRestModelMapper.convertActionType(restRequest.getType());
|
||||
if (convertActionType != null) {
|
||||
rollout.setActionType(convertActionType);
|
||||
@@ -105,6 +109,44 @@ final class MgmtRolloutMapper {
|
||||
return rollout;
|
||||
}
|
||||
|
||||
static RolloutGroup fromRequest(final EntityFactory entityFactory, final MgmtRolloutGroup restRequest) {
|
||||
final RolloutGroup group = entityFactory.generateRolloutGroup();
|
||||
group.setName(restRequest.getName());
|
||||
group.setDescription(restRequest.getDescription());
|
||||
|
||||
if (restRequest.getTargetFilterQuery() != null) {
|
||||
group.setTargetFilterQuery(restRequest.getTargetFilterQuery());
|
||||
}
|
||||
|
||||
final Float targetPercentage = restRequest.getTargetPercentage();
|
||||
if (targetPercentage == null) {
|
||||
group.setTargetPercentage(100);
|
||||
} else if (targetPercentage <= 0 || targetPercentage > 100) {
|
||||
throw new ConstraintViolationException("Target percentage out of Range >0 - 100.");
|
||||
} else {
|
||||
group.setTargetPercentage(restRequest.getTargetPercentage());
|
||||
}
|
||||
|
||||
if (restRequest.getSuccessCondition() != null) {
|
||||
group.setSuccessCondition(mapFinishCondition(restRequest.getSuccessCondition().getCondition()));
|
||||
group.setSuccessConditionExp(restRequest.getSuccessCondition().getExpression());
|
||||
}
|
||||
if (restRequest.getSuccessAction() != null) {
|
||||
group.setSuccessAction(map(restRequest.getSuccessAction().getAction()));
|
||||
group.setSuccessActionExp(restRequest.getSuccessAction().getExpression());
|
||||
}
|
||||
if (restRequest.getErrorCondition() != null) {
|
||||
group.setErrorCondition(mapErrorCondition(restRequest.getErrorCondition().getCondition()));
|
||||
group.setErrorConditionExp(restRequest.getErrorCondition().getExpression());
|
||||
}
|
||||
if (restRequest.getErrorAction() != null) {
|
||||
group.setErrorAction(map(restRequest.getErrorAction().getAction()));
|
||||
group.setErrorActionExp(restRequest.getErrorAction().getExpression());
|
||||
}
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
static List<MgmtRolloutGroupResponseBody> toResponseRolloutGroup(final List<RolloutGroup> rollouts) {
|
||||
if (rollouts == null) {
|
||||
return Collections.emptyList();
|
||||
@@ -123,6 +165,25 @@ final class MgmtRolloutMapper {
|
||||
body.setName(rolloutGroup.getName());
|
||||
body.setRolloutGroupId(rolloutGroup.getId());
|
||||
body.setStatus(rolloutGroup.getStatus().toString().toLowerCase());
|
||||
body.setTargetPercentage(rolloutGroup.getTargetPercentage());
|
||||
body.setTargetFilterQuery(rolloutGroup.getTargetFilterQuery());
|
||||
body.setTotalTargets(rolloutGroup.getTotalTargets());
|
||||
|
||||
body.setSuccessCondition(new MgmtRolloutCondition(map(rolloutGroup.getSuccessCondition()),
|
||||
rolloutGroup.getSuccessConditionExp()));
|
||||
body.setSuccessAction(
|
||||
new MgmtRolloutSuccessAction(map(rolloutGroup.getSuccessAction()), rolloutGroup.getSuccessActionExp()));
|
||||
|
||||
body.setErrorCondition(
|
||||
new MgmtRolloutCondition(map(rolloutGroup.getErrorCondition()), rolloutGroup.getErrorConditionExp()));
|
||||
body.setErrorAction(
|
||||
new MgmtRolloutErrorAction(map(rolloutGroup.getErrorAction()), rolloutGroup.getErrorActionExp()));
|
||||
|
||||
for (final TotalTargetCountStatus.Status status : TotalTargetCountStatus.Status.values()) {
|
||||
body.getTotalTargetsPerStatus().put(status.name().toLowerCase(),
|
||||
rolloutGroup.getTotalTargetCountStatus().getTotalTargetCountByStatus(status));
|
||||
}
|
||||
|
||||
body.add(linkTo(methodOn(MgmtRolloutRestApi.class).getRolloutGroup(rolloutGroup.getRollout().getId(),
|
||||
rolloutGroup.getId())).withRel("self"));
|
||||
return body;
|
||||
@@ -149,6 +210,13 @@ final class MgmtRolloutMapper {
|
||||
throw new IllegalArgumentException("Rollout group condition " + rolloutCondition + NOT_SUPPORTED);
|
||||
}
|
||||
|
||||
static Condition map(final RolloutGroupErrorCondition rolloutCondition) {
|
||||
if (RolloutGroupErrorCondition.THRESHOLD.equals(rolloutCondition)) {
|
||||
return Condition.THRESHOLD;
|
||||
}
|
||||
throw new IllegalArgumentException("Rollout group condition " + rolloutCondition + NOT_SUPPORTED);
|
||||
}
|
||||
|
||||
static RolloutGroupErrorAction map(final ErrorAction action) {
|
||||
if (ErrorAction.PAUSE.equals(action)) {
|
||||
return RolloutGroupErrorAction.PAUSE;
|
||||
@@ -163,6 +231,20 @@ final class MgmtRolloutMapper {
|
||||
throw new IllegalArgumentException("Success Action " + action + NOT_SUPPORTED);
|
||||
}
|
||||
|
||||
static SuccessAction map(final RolloutGroupSuccessAction successAction) {
|
||||
if (RolloutGroupSuccessAction.NEXTGROUP.equals(successAction)) {
|
||||
return SuccessAction.NEXTGROUP;
|
||||
}
|
||||
throw new IllegalArgumentException("Rollout group success action " + successAction + NOT_SUPPORTED);
|
||||
}
|
||||
|
||||
static ErrorAction map(final RolloutGroupErrorAction errorAction) {
|
||||
if (RolloutGroupErrorAction.PAUSE.equals(errorAction)) {
|
||||
return ErrorAction.PAUSE;
|
||||
}
|
||||
throw new IllegalArgumentException("Rollout group error action " + errorAction + NOT_SUPPORTED);
|
||||
}
|
||||
|
||||
private static String createIllegalArgumentLiteral(final Condition condition) {
|
||||
return "Condition " + condition + NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
package org.eclipse.hawkbit.mgmt.rest.resource;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.hawkbit.mgmt.json.model.PagedList;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutResponseBody;
|
||||
@@ -24,6 +25,7 @@ import org.eclipse.hawkbit.repository.RolloutGroupManagement;
|
||||
import org.eclipse.hawkbit.repository.RolloutManagement;
|
||||
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||
import org.eclipse.hawkbit.repository.exception.RolloutVerificationException;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||
import org.eclipse.hawkbit.repository.model.Rollout;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup;
|
||||
@@ -143,23 +145,30 @@ public class MgmtRolloutResource implements MgmtRolloutRestApi {
|
||||
.successCondition(successCondition, successConditionExpr)
|
||||
.successAction(successAction, successActionExpr).errorCondition(errorCondition, errorConditionExpr)
|
||||
.errorAction(errorAction, errorActionExpr).build();
|
||||
final Rollout rollout = this.rolloutManagement.createRollout(
|
||||
MgmtRolloutMapper.fromRequest(entityFactory, rolloutRequestBody, distributionSet,
|
||||
rolloutRequestBody.getTargetFilterQuery()),
|
||||
rolloutRequestBody.getAmountGroups(), rolloutGroupConditions);
|
||||
|
||||
Rollout rollout = MgmtRolloutMapper.fromRequest(entityFactory, rolloutRequestBody, distributionSet);
|
||||
|
||||
if (rolloutRequestBody.getGroups() != null) {
|
||||
final List<RolloutGroup> rolloutGroups = rolloutRequestBody.getGroups().stream()
|
||||
.map(mgmtRolloutGroup -> MgmtRolloutMapper.fromRequest(entityFactory, mgmtRolloutGroup))
|
||||
.collect(Collectors.toList());
|
||||
rollout = rolloutManagement.createRollout(rollout, rolloutGroups, rolloutGroupConditions);
|
||||
|
||||
} else if (rolloutRequestBody.getAmountGroups() != null) {
|
||||
rollout = rolloutManagement.createRollout(rollout, rolloutRequestBody.getAmountGroups(),
|
||||
rolloutGroupConditions);
|
||||
|
||||
} else {
|
||||
throw new RolloutVerificationException("Either 'amountGroups' or 'groups' must be defined in the request");
|
||||
}
|
||||
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(MgmtRolloutMapper.toResponseRollout(rollout));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseEntity<Void> start(@PathVariable("rolloutId") final Long rolloutId,
|
||||
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_ASYNC, defaultValue = "false") final boolean startAsync) {
|
||||
public ResponseEntity<Void> start(@PathVariable("rolloutId") final Long rolloutId) {
|
||||
final Rollout rollout = findRolloutOrThrowException(rolloutId);
|
||||
if (startAsync) {
|
||||
this.rolloutManagement.startRolloutAsync(rollout);
|
||||
} else {
|
||||
this.rolloutManagement.startRollout(rollout);
|
||||
}
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
@@ -30,6 +31,7 @@ import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroupConditionBuilder;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroupConditions;
|
||||
import org.eclipse.hawkbit.repository.model.Target;
|
||||
import org.eclipse.hawkbit.repository.test.util.WithUser;
|
||||
import org.eclipse.hawkbit.rest.AbstractRestIntegrationTest;
|
||||
@@ -117,8 +119,85 @@ public class MgmtRolloutResourceTest extends AbstractRestIntegrationTest {
|
||||
@Test
|
||||
@Description("Testing that rollout can be created")
|
||||
public void createRollout() throws Exception {
|
||||
targetManagement.createTargets(testdataFactory.generateTargets(20, "target", "rollout"));
|
||||
|
||||
final DistributionSet dsA = testdataFactory.createDistributionSet("");
|
||||
postRollout("rollout1", 10, dsA.getId(), "name==target1");
|
||||
postRollout("rollout1", 10, dsA.getId(), "id==target*");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Testing that rollout can be created with groups")
|
||||
public void createRolloutWithGroupsDefinitions() throws Exception {
|
||||
final DistributionSet dsA = testdataFactory.createDistributionSet("ro");
|
||||
|
||||
final int amountTargets = 10;
|
||||
targetManagement.createTargets(testdataFactory.generateTargets(amountTargets, "ro-target", "rollout"));
|
||||
|
||||
List<RolloutGroup> rolloutGroups = new ArrayList<>(2);
|
||||
final int percentTargetsInGroup1 = 20;
|
||||
final int percentTargetsInGroup2 = 100;
|
||||
|
||||
RolloutGroup group1 = entityFactory.generateRolloutGroup();
|
||||
group1.setName("Group1");
|
||||
group1.setDescription("Group1desc");
|
||||
group1.setTargetPercentage(percentTargetsInGroup1);
|
||||
rolloutGroups.add(group1);
|
||||
|
||||
RolloutGroup group2 = entityFactory.generateRolloutGroup();
|
||||
group2.setName("Group2");
|
||||
group2.setDescription("Group2desc");
|
||||
group2.setTargetPercentage(percentTargetsInGroup2);
|
||||
rolloutGroups.add(group2);
|
||||
|
||||
RolloutGroupConditions rolloutGroupConditions = new RolloutGroupConditionBuilder().build();
|
||||
|
||||
mvc.perform(post("/rest/v1/rollouts")
|
||||
.content(JsonBuilder.rollout("rollout2", "desc", null, dsA.getId(), "id==ro-target*",
|
||||
rolloutGroupConditions, rolloutGroups))
|
||||
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isCreated()).andReturn();
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Testing that no rollout with groups that have illegal percentages can be created")
|
||||
public void createRolloutWithIllegalPercentages() throws Exception {
|
||||
final DistributionSet dsA = testdataFactory.createDistributionSet("ro2");
|
||||
|
||||
final int amountTargets = 10;
|
||||
targetManagement.createTargets(testdataFactory.generateTargets(amountTargets, "ro-target", "rollout"));
|
||||
|
||||
List<RolloutGroup> rolloutGroups = new ArrayList<>(2);
|
||||
|
||||
RolloutGroup group1 = entityFactory.generateRolloutGroup();
|
||||
group1.setName("Group1");
|
||||
group1.setDescription("Group1desc");
|
||||
group1.setTargetPercentage(0);
|
||||
rolloutGroups.add(group1);
|
||||
|
||||
RolloutGroup group2 = entityFactory.generateRolloutGroup();
|
||||
group2.setName("Group2");
|
||||
group2.setDescription("Group2desc");
|
||||
group2.setTargetPercentage(100);
|
||||
rolloutGroups.add(group2);
|
||||
|
||||
RolloutGroupConditions rolloutGroupConditions = new RolloutGroupConditionBuilder().build();
|
||||
|
||||
mvc.perform(post("/rest/v1/rollouts")
|
||||
.content(JsonBuilder.rollout("rollout4", "desc", null, dsA.getId(), "id==ro-target*",
|
||||
rolloutGroupConditions, rolloutGroups))
|
||||
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isInternalServerError())
|
||||
.andExpect(jsonPath("$.errorCode", equalTo("hawkbit.server.error.repo.constraintViolation")));
|
||||
|
||||
group1.setTargetPercentage(101);
|
||||
mvc.perform(post("/rest/v1/rollouts")
|
||||
.content(JsonBuilder.rollout("rollout4", "desc", null, dsA.getId(), "id==ro-target*",
|
||||
rolloutGroupConditions, rolloutGroups))
|
||||
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isInternalServerError())
|
||||
.andExpect(jsonPath("$.errorCode", equalTo("hawkbit.server.error.repo.constraintViolation")));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -133,20 +212,26 @@ public class MgmtRolloutResourceTest extends AbstractRestIntegrationTest {
|
||||
@Description("Testing that rollout paged list contains rollouts")
|
||||
public void rolloutPagedListContainsAllRollouts() throws Exception {
|
||||
final DistributionSet dsA = testdataFactory.createDistributionSet("");
|
||||
|
||||
targetManagement.createTargets(testdataFactory.generateTargets(20, "target", "rollout"));
|
||||
|
||||
// setup - create 2 rollouts
|
||||
postRollout("rollout1", 10, dsA.getId(), "name==target1");
|
||||
postRollout("rollout2", 5, dsA.getId(), "name==target2");
|
||||
postRollout("rollout1", 10, dsA.getId(), "id==target*");
|
||||
postRollout("rollout2", 5, dsA.getId(), "id==target-0001*");
|
||||
|
||||
// Run here, because Scheduler is disabled during tests
|
||||
rolloutManagement.checkCreatingRollouts(0);
|
||||
|
||||
mvc.perform(get("/rest/v1/rollouts")).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
|
||||
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
|
||||
.andExpect(jsonPath("$.content", hasSize(2))).andExpect(jsonPath("$.total", equalTo(2)))
|
||||
.andExpect(jsonPath("content[0].name", equalTo("rollout1")))
|
||||
.andExpect(jsonPath("content[0].status", equalTo("ready")))
|
||||
.andExpect(jsonPath("content[0].targetFilterQuery", equalTo("name==target1")))
|
||||
.andExpect(jsonPath("content[0].targetFilterQuery", equalTo("id==target*")))
|
||||
.andExpect(jsonPath("content[0].distributionSetId", equalTo(dsA.getId().intValue())))
|
||||
.andExpect(jsonPath("content[1].name", equalTo("rollout2")))
|
||||
.andExpect(jsonPath("content[1].status", equalTo("ready")))
|
||||
.andExpect(jsonPath("content[1].targetFilterQuery", equalTo("name==target2")))
|
||||
.andExpect(jsonPath("content[1].targetFilterQuery", equalTo("id==target-0001*")))
|
||||
.andExpect(jsonPath("content[1].distributionSetId", equalTo(dsA.getId().intValue())));
|
||||
}
|
||||
|
||||
@@ -154,9 +239,15 @@ public class MgmtRolloutResourceTest extends AbstractRestIntegrationTest {
|
||||
@Description("Testing that rollout paged list is limited by the query param limit")
|
||||
public void rolloutPagedListIsLimitedToQueryParam() throws Exception {
|
||||
final DistributionSet dsA = testdataFactory.createDistributionSet("");
|
||||
|
||||
targetManagement.createTargets(testdataFactory.generateTargets(20, "target", "rollout"));
|
||||
|
||||
// setup - create 2 rollouts
|
||||
postRollout("rollout1", 10, dsA.getId(), "name==target1");
|
||||
postRollout("rollout2", 5, dsA.getId(), "name==target2");
|
||||
postRollout("rollout1", 10, dsA.getId(), "id==target*");
|
||||
postRollout("rollout2", 5, dsA.getId(), "id==target*");
|
||||
|
||||
// Run here, because Scheduler is disabled during tests
|
||||
rolloutManagement.checkCreatingRollouts(0);
|
||||
|
||||
mvc.perform(get("/rest/v1/rollouts?limit=1")).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
|
||||
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
|
||||
@@ -186,7 +277,7 @@ public class MgmtRolloutResourceTest extends AbstractRestIntegrationTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Testing that starting the rollout switches the state to running")
|
||||
@Description("Testing that starting the rollout switches the state to starting and then to running")
|
||||
public void startingRolloutSwitchesIntoRunningState() throws Exception {
|
||||
// setup
|
||||
final int amountTargets = 20;
|
||||
@@ -200,6 +291,15 @@ public class MgmtRolloutResourceTest extends AbstractRestIntegrationTest {
|
||||
mvc.perform(post("/rest/v1/rollouts/{rolloutId}/start", rollout.getId())).andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk());
|
||||
|
||||
// check rollout is in starting state
|
||||
mvc.perform(get("/rest/v1/rollouts/{rolloutId}", rollout.getId())).andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
|
||||
.andExpect(jsonPath("id", equalTo(rollout.getId().intValue())))
|
||||
.andExpect(jsonPath("status", equalTo("starting")));
|
||||
|
||||
// Run here, because scheduler is disabled during tests
|
||||
rolloutManagement.checkStartingRollouts(0);
|
||||
|
||||
// check rollout is in running state
|
||||
mvc.perform(get("/rest/v1/rollouts/{rolloutId}", rollout.getId())).andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
|
||||
@@ -222,6 +322,9 @@ public class MgmtRolloutResourceTest extends AbstractRestIntegrationTest {
|
||||
mvc.perform(post("/rest/v1/rollouts/{rolloutId}/start", rollout.getId())).andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk());
|
||||
|
||||
// Run here, because scheduler is disabled during tests
|
||||
rolloutManagement.checkStartingRollouts(0);
|
||||
|
||||
// pausing rollout
|
||||
mvc.perform(post("/rest/v1/rollouts/{rolloutId}/pause", rollout.getId())).andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk());
|
||||
@@ -248,6 +351,9 @@ public class MgmtRolloutResourceTest extends AbstractRestIntegrationTest {
|
||||
mvc.perform(post("/rest/v1/rollouts/{rolloutId}/start", rollout.getId())).andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk());
|
||||
|
||||
// Run here, because scheduler is disabled during tests
|
||||
rolloutManagement.checkStartingRollouts(0);
|
||||
|
||||
// pausing rollout
|
||||
mvc.perform(post("/rest/v1/rollouts/{rolloutId}/pause", rollout.getId())).andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk());
|
||||
@@ -278,6 +384,9 @@ public class MgmtRolloutResourceTest extends AbstractRestIntegrationTest {
|
||||
mvc.perform(post("/rest/v1/rollouts/{rolloutId}/start", rollout.getId())).andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk());
|
||||
|
||||
// Run here, because scheduler is disabled during tests
|
||||
rolloutManagement.checkStartingRollouts(0);
|
||||
|
||||
// starting rollout - already started should lead into bad request
|
||||
mvc.perform(post("/rest/v1/rollouts/{rolloutId}/start", rollout.getId())).andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isBadRequest())
|
||||
@@ -316,6 +425,9 @@ public class MgmtRolloutResourceTest extends AbstractRestIntegrationTest {
|
||||
mvc.perform(post("/rest/v1/rollouts/{rolloutId}/start", rollout.getId())).andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk());
|
||||
|
||||
// Run here, because scheduler is disabled during tests
|
||||
rolloutManagement.checkStartingRollouts(0);
|
||||
|
||||
// retrieve rollout groups from created rollout - 2 groups exists
|
||||
// (amountTargets / groupSize = 2)
|
||||
mvc.perform(get("/rest/v1/rollouts/{rolloutId}/deploygroups?sort=ID:ASC", rollout.getId()))
|
||||
@@ -409,6 +521,9 @@ public class MgmtRolloutResourceTest extends AbstractRestIntegrationTest {
|
||||
|
||||
rolloutManagement.startRollout(rollout);
|
||||
|
||||
// Run here, because scheduler is disabled during tests
|
||||
rolloutManagement.checkStartingRollouts(0);
|
||||
|
||||
final RolloutGroup firstGroup = rolloutGroupManagement
|
||||
.findRolloutGroupsByRolloutId(rollout.getId(), new PageRequest(0, 1, Direction.ASC, "id")).getContent()
|
||||
.get(0);
|
||||
@@ -432,10 +547,12 @@ public class MgmtRolloutResourceTest extends AbstractRestIntegrationTest {
|
||||
final Rollout rollout = createRollout("rollout1", 4, dsA.getId(), "controllerId==rollout*");
|
||||
|
||||
// starting rollout
|
||||
mvc.perform(post("/rest/v1/rollouts/{rolloutId}/start", rollout.getId())
|
||||
.param(MgmtRestConstants.REQUEST_PARAMETER_ASYNC, "true")).andDo(MockMvcResultPrinter.print())
|
||||
mvc.perform(post("/rest/v1/rollouts/{rolloutId}/start", rollout.getId())).andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk());
|
||||
|
||||
// Run here, because scheduler is disabled during tests
|
||||
rolloutManagement.checkStartingRollouts(0);
|
||||
|
||||
// check if running
|
||||
assertThat(doWithTimeout(() -> getRollout(rollout.getId()), result -> success(result), 60_000, 100))
|
||||
.isNotNull();
|
||||
@@ -549,19 +666,25 @@ public class MgmtRolloutResourceTest extends AbstractRestIntegrationTest {
|
||||
private void postRollout(final String name, final int groupSize, final long distributionSetId,
|
||||
final String targetFilterQuery) throws Exception {
|
||||
mvc.perform(post("/rest/v1/rollouts")
|
||||
.content(JsonBuilder.rollout(name, "desc", groupSize, distributionSetId, targetFilterQuery, null))
|
||||
.content(JsonBuilder.rollout(name, "desc", groupSize, distributionSetId, targetFilterQuery,
|
||||
new RolloutGroupConditionBuilder().build()))
|
||||
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isCreated()).andReturn();
|
||||
}
|
||||
|
||||
private Rollout createRollout(final String name, final int amountGroups, final long distributionSetId,
|
||||
final String targetFilterQuery) {
|
||||
final Rollout rollout = entityFactory.generateRollout();
|
||||
Rollout rollout = entityFactory.generateRollout();
|
||||
rollout.setDistributionSet(distributionSetManagement.findDistributionSetById(distributionSetId));
|
||||
rollout.setName(name);
|
||||
rollout.setTargetFilterQuery(targetFilterQuery);
|
||||
return rolloutManagement.createRollout(rollout, amountGroups, new RolloutGroupConditionBuilder()
|
||||
rollout = rolloutManagement.createRollout(rollout, amountGroups, new RolloutGroupConditionBuilder()
|
||||
.successCondition(RolloutGroupSuccessCondition.THRESHOLD, "100").build());
|
||||
|
||||
// Run here, because Scheduler is disabled during tests
|
||||
rolloutManagement.fillRolloutGroupsWithTargets(rolloutManagement.findRolloutById(rollout.getId()));
|
||||
|
||||
return rolloutManagement.findRolloutById(rollout.getId());
|
||||
}
|
||||
|
||||
protected boolean success(final Rollout result) {
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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 rollout group conditions and actions which should be
|
||||
* applied for each {@link RolloutGroup}
|
||||
* @return the created rollout entity in state
|
||||
* {@link RolloutStatus#CREATING}
|
||||
* 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.
|
||||
*
|
||||
|
||||
@@ -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()}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
public Rollout createRollout(final Rollout rollout,
|
||||
final List<RolloutGroup> groups,
|
||||
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;
|
||||
RolloutHelper.verifyRolloutGroupParameter(groups.size());
|
||||
final JpaRollout savedRollout = createRollout((JpaRollout) rollout);
|
||||
return createRolloutGroups(groups, conditions, savedRollout);
|
||||
}
|
||||
|
||||
private JpaRollout createRollout(final JpaRollout rollout) {
|
||||
JpaRollout existingRollout = rolloutRepository.findByName(rollout.getName());
|
||||
if(existingRollout != null) {
|
||||
throw new EntityAlreadyExistsException(existingRollout.getName());
|
||||
}
|
||||
|
||||
private JpaRollout createRollout(final JpaRollout rollout, final int amountGroup) {
|
||||
verifyRolloutGroupParameter(amountGroup);
|
||||
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(() -> {
|
||||
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 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);
|
||||
}
|
||||
|
||||
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 {
|
||||
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;
|
||||
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 Rollout doStartRollout(final JpaRollout rollout) {
|
||||
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();
|
||||
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);
|
||||
|
||||
Page<Target> targets = targetManagement.findAllTargetsInRolloutGroupWithoutAction(pageRequest, group);
|
||||
if (targets.getTotalElements() > 0) {
|
||||
deploymentManagement.createScheduledAction(targets.getContent(), distributionSet, actionType, forceTime,
|
||||
rollout, group);
|
||||
}
|
||||
rolloutGroupRepository.save(rolloutGroup);
|
||||
}
|
||||
rollout.setStatus(RolloutStatus.RUNNING);
|
||||
return rolloutRepository.save(rollout);
|
||||
|
||||
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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
ALTER TABLE sp_rolloutgroup
|
||||
ADD COLUMN target_percentage FLOAT;
|
||||
ALTER TABLE sp_rolloutgroup
|
||||
ADD COLUMN target_filter VARCHAR (1024);
|
||||
@@ -0,0 +1,4 @@
|
||||
ALTER TABLE sp_rolloutgroup
|
||||
ADD COLUMN target_percentage FLOAT;
|
||||
ALTER TABLE sp_rolloutgroup
|
||||
ADD COLUMN target_filter VARCHAR (1024);
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -16,6 +16,7 @@ import java.util.stream.Collectors;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSetType;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroupConditions;
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModule;
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
|
||||
@@ -399,6 +400,12 @@ public abstract class JsonBuilder {
|
||||
|
||||
public static String rollout(final String name, final String description, final int groupSize,
|
||||
final long distributionSetId, final String targetFilterQuery, final RolloutGroupConditions conditions) {
|
||||
return rollout(name, description, groupSize, distributionSetId, targetFilterQuery, conditions, null);
|
||||
}
|
||||
|
||||
public static String rollout(final String name, final String description, final Integer groupSize,
|
||||
final long distributionSetId, final String targetFilterQuery, final RolloutGroupConditions conditions,
|
||||
final List<RolloutGroup> groups) {
|
||||
final JSONObject json = new JSONObject();
|
||||
json.put("name", name);
|
||||
json.put("description", description);
|
||||
@@ -410,22 +417,64 @@ public abstract class JsonBuilder {
|
||||
final JSONObject successCondition = new JSONObject();
|
||||
json.put("successCondition", successCondition);
|
||||
successCondition.put("condition", conditions.getSuccessCondition().toString());
|
||||
successCondition.put("expression", conditions.getSuccessConditionExp().toString());
|
||||
successCondition.put("expression", conditions.getSuccessConditionExp());
|
||||
|
||||
final JSONObject successAction = new JSONObject();
|
||||
json.put("successAction", successAction);
|
||||
successAction.put("action", conditions.getSuccessAction().toString());
|
||||
successAction.put("expression", conditions.getSuccessActionExp().toString());
|
||||
successAction.put("expression", conditions.getSuccessActionExp());
|
||||
|
||||
final JSONObject errorCondition = new JSONObject();
|
||||
json.put("errorCondition", errorCondition);
|
||||
errorCondition.put("condition", conditions.getErrorCondition().toString());
|
||||
errorCondition.put("expression", conditions.getErrorConditionExp().toString());
|
||||
errorCondition.put("expression", conditions.getErrorConditionExp());
|
||||
|
||||
final JSONObject errorAction = new JSONObject();
|
||||
json.put("errorAction", errorAction);
|
||||
errorAction.put("action", conditions.getErrorAction().toString());
|
||||
errorAction.put("expression", conditions.getErrorActionExp().toString());
|
||||
errorAction.put("expression", conditions.getErrorActionExp());
|
||||
}
|
||||
|
||||
if(groups != null) {
|
||||
final JSONArray jsonGroups = new JSONArray();
|
||||
|
||||
for (RolloutGroup group : groups) {
|
||||
final JSONObject jsonGroup = new JSONObject();
|
||||
jsonGroup.put("name", group.getName());
|
||||
jsonGroup.put("description", group.getDescription());
|
||||
jsonGroup.put("targetFilterQuery", group.getTargetFilterQuery());
|
||||
jsonGroup.put("targetPercentage", group.getTargetPercentage());
|
||||
|
||||
if(group.getSuccessCondition() != null) {
|
||||
final JSONObject successCondition = new JSONObject();
|
||||
jsonGroup.put("successCondition", successCondition);
|
||||
successCondition.put("condition", group.getSuccessCondition().toString());
|
||||
successCondition.put("expression", group.getSuccessConditionExp());
|
||||
}
|
||||
if(group.getSuccessAction() != null) {
|
||||
final JSONObject successAction = new JSONObject();
|
||||
jsonGroup.put("successAction", successAction);
|
||||
successAction.put("action", group.getSuccessAction().toString());
|
||||
successAction.put("expression", group.getSuccessActionExp());
|
||||
}
|
||||
if(group.getErrorCondition() != null) {
|
||||
final JSONObject errorCondition = new JSONObject();
|
||||
jsonGroup.put("errorCondition", errorCondition);
|
||||
errorCondition.put("condition", group.getErrorCondition().toString());
|
||||
errorCondition.put("expression", group.getErrorConditionExp());
|
||||
}
|
||||
if(group.getErrorAction() != null) {
|
||||
final JSONObject errorAction = new JSONObject();
|
||||
jsonGroup.put("errorAction", errorAction);
|
||||
errorAction.put("action", group.getErrorAction().toString());
|
||||
errorAction.put("expression", group.getErrorActionExp());
|
||||
}
|
||||
|
||||
jsonGroups.put(jsonGroup);
|
||||
}
|
||||
|
||||
json.put("groups", jsonGroups);
|
||||
|
||||
}
|
||||
|
||||
return json.toString();
|
||||
|
||||
@@ -491,7 +491,7 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
|
||||
rolloutToCreate.setActionType(getActionType());
|
||||
rolloutToCreate.setForcedTime(getForcedTimeStamp());
|
||||
|
||||
rolloutToCreate = rolloutManagement.createRolloutAsync(rolloutToCreate, amountGroup, conditions);
|
||||
rolloutToCreate = rolloutManagement.createRollout(rolloutToCreate, amountGroup, conditions);
|
||||
return rolloutToCreate;
|
||||
}
|
||||
|
||||
|
||||
@@ -414,7 +414,7 @@ public class RolloutListGrid extends AbstractGrid {
|
||||
final String rolloutName = (String) row.getItemProperty(SPUILabelDefinitions.VAR_NAME).getValue();
|
||||
|
||||
if (RolloutStatus.READY.equals(rolloutStatus)) {
|
||||
rolloutManagement.startRolloutAsync(rolloutManagement.findRolloutByName(rolloutName));
|
||||
rolloutManagement.startRollout(rolloutManagement.findRolloutByName(rolloutName));
|
||||
uiNotification.displaySuccess(i18n.get("message.rollout.started", rolloutName));
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user