Semi automatic Rollouts with fine groups definition (#337)

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

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

* Fire RolloutGroupCreated event and fix db migration.

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

* Fill groups now excludes targets in own group

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

* Starting of Rollouts as scheduled task

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

* Finished implementation of new Rollout starting proccess

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

* Reset last check on status change and fixed unused imports

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

* Code quality improvements

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

* Reworked start of scheduled Actions. Improved code quality.

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

View File

@@ -190,7 +190,7 @@ public class ConfigurableScenario {
.getBody(); .getBody();
// start the created Rollout // start the created Rollout
rolloutResource.start(rolloutResponseBody.getRolloutId(), true); rolloutResource.start(rolloutResponseBody.getRolloutId());
// wait until rollout is complete // wait until rollout is complete
do { do {

View File

@@ -102,7 +102,7 @@ public class CreateStartedRolloutExample {
.getBody(); .getBody();
// start the created Rollout // start the created Rollout
rolloutResource.start(rolloutResponseBody.getRolloutId(), false); rolloutResource.start(rolloutResponseBody.getRolloutId());
} }
} }

View File

@@ -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 key;
private final String message; private final String message;
@@ -172,7 +177,7 @@ public enum SpServerError {
/* /*
* Repository side Error codes * Repository side Error codes
*/ */
private SpServerError(final String errorKey, final String message) { SpServerError(final String errorKey, final String message) {
key = errorKey; key = errorKey;
this.message = message; this.message = message;
} }

View File

@@ -32,6 +32,14 @@ public enum TargetFields implements FieldNameProvider {
* The description field. * The description field.
*/ */
DESCRIPTION("description"), DESCRIPTION("description"),
/**
* The createdAt field.
*/
CREATEDAT("createdAt"),
/**
* The createdAt field.
*/
LASTMODIFIEDAT("lastModifiedAt"),
/** /**
* The controllerId field. * The controllerId field.
*/ */
@@ -75,19 +83,19 @@ public enum TargetFields implements FieldNameProvider {
private List<String> subEntityAttribues; private List<String> subEntityAttribues;
private boolean mapField; private boolean mapField;
private TargetFields(final String fieldName) { TargetFields(final String fieldName) {
this(fieldName, false, Collections.emptyList()); 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()); 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)); 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.fieldName = fieldName;
this.mapField = mapField; this.mapField = mapField;
this.subEntityAttribues = subEntityAttribues; this.subEntityAttribues = subEntityAttribues;

View File

@@ -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;
}
}

View File

@@ -13,14 +13,34 @@ import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonInclude.Include;
/** /**
* * An action that runs when the error condition is met
*/ */
@JsonInclude(Include.NON_NULL) @JsonInclude(Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
public class MgmtRolloutErrorAction { public class MgmtRolloutErrorAction {
private ErrorAction action = ErrorAction.PAUSE; 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 * @return the action
@@ -52,6 +72,9 @@ public class MgmtRolloutErrorAction {
this.expression = expression; this.expression = expression;
} }
/**
* Possible actions
*/
public enum ErrorAction { public enum ErrorAction {
PAUSE; PAUSE;
} }

View File

@@ -8,12 +8,14 @@
*/ */
package org.eclipse.hawkbit.mgmt.json.model.rollout; 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 org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtActionType;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include; 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 * 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) @JsonInclude(Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
public class MgmtRolloutRestRequestBody extends MgmtNamedEntity { public class MgmtRolloutRestRequestBody extends AbstractMgmtRolloutConditionsEntity {
private String targetFilterQuery; private String targetFilterQuery;
private long distributionSetId; private long distributionSetId;
private int amountGroups = 1; private Integer amountGroups;
private MgmtRolloutCondition successCondition = new MgmtRolloutCondition();
private MgmtRolloutSuccessAction successAction = new MgmtRolloutSuccessAction();
private MgmtRolloutCondition errorCondition = null;
private MgmtRolloutErrorAction errorAction = null;
private Long forcetime; private Long forcetime;
private MgmtActionType type; private MgmtActionType type;
/** private List<MgmtRolloutGroup> groups;
* @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;
}
/** /**
* @return the targetFilterQuery * @return the targetFilterQuery
@@ -115,7 +69,7 @@ public class MgmtRolloutRestRequestBody extends MgmtNamedEntity {
/** /**
* @return the groupSize * @return the groupSize
*/ */
public int getAmountGroups() { public Integer getAmountGroups() {
return amountGroups; return amountGroups;
} }
@@ -123,7 +77,7 @@ public class MgmtRolloutRestRequestBody extends MgmtNamedEntity {
* @param groupSize * @param groupSize
* the groupSize to set * the groupSize to set
*/ */
public void setAmountGroups(final int groupSize) { public void setAmountGroups(final Integer groupSize) {
this.amountGroups = 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() { public List<MgmtRolloutGroup> getGroups() {
return errorAction; return groups;
} }
/** /**
* @param errorAction * @param groups List of {@link MgmtRolloutGroup}
* the errorAction to set
*/ */
public void setErrorAction(final MgmtRolloutErrorAction errorAction) { public void setGroups(List<MgmtRolloutGroup> groups) {
this.errorAction = errorAction; this.groups = groups;
} }
} }

View File

@@ -0,0 +1,40 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.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;
}
}

View File

@@ -8,20 +8,21 @@
*/ */
package org.eclipse.hawkbit.mgmt.json.model.rolloutgroup; 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.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty; 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 * Model for the rollout group annotated with json-annotations for easier
* serialization and de-serialization. * serialization and de-serialization.
*/ */
@JsonInclude(Include.NON_NULL) @JsonInclude(Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
public class MgmtRolloutGroupResponseBody extends MgmtNamedEntity { public class MgmtRolloutGroupResponseBody extends MgmtRolloutGroup {
@JsonProperty(value = "id", required = true) @JsonProperty(value = "id", required = true)
private Long rolloutGroupId; private Long rolloutGroupId;
@@ -29,6 +30,10 @@ public class MgmtRolloutGroupResponseBody extends MgmtNamedEntity {
@JsonProperty(required = true) @JsonProperty(required = true)
private String status; private String status;
private int totalTargets;
private final Map<String, Long> totalTargetsPerStatus = new HashMap<>();
/** /**
* @return the rolloutGroupId * @return the rolloutGroupId
*/ */
@@ -58,4 +63,17 @@ public class MgmtRolloutGroupResponseBody extends MgmtNamedEntity {
public void setStatus(final String status) { public void setStatus(final String status) {
this.status = status; this.status = status;
} }
public int getTotalTargets() {
return totalTargets;
}
public void setTotalTargets(int totalTargets) {
this.totalTargets = totalTargets;
}
public Map<String, Long> getTotalTargetsPerStatus() {
return totalTargetsPerStatus;
}
} }

View File

@@ -88,8 +88,7 @@ public interface MgmtRolloutRestApi {
*/ */
@RequestMapping(method = RequestMethod.POST, value = "/{rolloutId}/start", produces = { "application/hal+json", @RequestMapping(method = RequestMethod.POST, value = "/{rolloutId}/start", produces = { "application/hal+json",
MediaType.APPLICATION_JSON_VALUE }) MediaType.APPLICATION_JSON_VALUE })
ResponseEntity<Void> start(@PathVariable("rolloutId") final Long rolloutId, ResponseEntity<Void> start(@PathVariable("rolloutId") final Long rolloutId);
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_ASYNC, defaultValue = "false") final boolean startAsync);
/** /**
* Handles the POST request for pausing a rollout. * Handles the POST request for pausing a rollout.

View File

@@ -15,15 +15,20 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; 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.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.MgmtRolloutErrorAction.ErrorAction;
import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutResponseBody; 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.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.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.json.model.rolloutgroup.MgmtRolloutGroupResponseBody;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants; import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRolloutRestApi; import org.eclipse.hawkbit.mgmt.rest.api.MgmtRolloutRestApi;
import org.eclipse.hawkbit.repository.EntityFactory; 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.Action.ActionType;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Rollout; 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).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())).withRel("start"));
body.add(linkTo(methodOn(MgmtRolloutRestApi.class).start(rollout.getId(), true)).withRel("startAsync"));
body.add(linkTo(methodOn(MgmtRolloutRestApi.class).pause(rollout.getId())).withRel("pause")); 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).resume(rollout.getId())).withRel("resume"));
body.add(linkTo(methodOn(MgmtRolloutRestApi.class).getRolloutGroups(rollout.getId(), 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, static Rollout fromRequest(final EntityFactory entityFactory, final MgmtRolloutRestRequestBody restRequest,
final DistributionSet distributionSet, final String filterQuery) { final DistributionSet distributionSet) {
final Rollout rollout = entityFactory.generateRollout(); final Rollout rollout = entityFactory.generateRollout();
rollout.setName(restRequest.getName()); rollout.setName(restRequest.getName());
rollout.setDescription(restRequest.getDescription()); rollout.setDescription(restRequest.getDescription());
rollout.setDistributionSet(distributionSet); rollout.setDistributionSet(distributionSet);
rollout.setTargetFilterQuery(filterQuery); rollout.setTargetFilterQuery(restRequest.getTargetFilterQuery());
final ActionType convertActionType = MgmtRestModelMapper.convertActionType(restRequest.getType()); final ActionType convertActionType = MgmtRestModelMapper.convertActionType(restRequest.getType());
if (convertActionType != null) { if (convertActionType != null) {
rollout.setActionType(convertActionType); rollout.setActionType(convertActionType);
@@ -105,6 +109,44 @@ final class MgmtRolloutMapper {
return rollout; 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) { static List<MgmtRolloutGroupResponseBody> toResponseRolloutGroup(final List<RolloutGroup> rollouts) {
if (rollouts == null) { if (rollouts == null) {
return Collections.emptyList(); return Collections.emptyList();
@@ -123,6 +165,25 @@ final class MgmtRolloutMapper {
body.setName(rolloutGroup.getName()); body.setName(rolloutGroup.getName());
body.setRolloutGroupId(rolloutGroup.getId()); body.setRolloutGroupId(rolloutGroup.getId());
body.setStatus(rolloutGroup.getStatus().toString().toLowerCase()); 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(), body.add(linkTo(methodOn(MgmtRolloutRestApi.class).getRolloutGroup(rolloutGroup.getRollout().getId(),
rolloutGroup.getId())).withRel("self")); rolloutGroup.getId())).withRel("self"));
return body; return body;
@@ -149,6 +210,13 @@ final class MgmtRolloutMapper {
throw new IllegalArgumentException("Rollout group condition " + rolloutCondition + NOT_SUPPORTED); 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) { static RolloutGroupErrorAction map(final ErrorAction action) {
if (ErrorAction.PAUSE.equals(action)) { if (ErrorAction.PAUSE.equals(action)) {
return RolloutGroupErrorAction.PAUSE; return RolloutGroupErrorAction.PAUSE;
@@ -163,6 +231,20 @@ final class MgmtRolloutMapper {
throw new IllegalArgumentException("Success Action " + action + NOT_SUPPORTED); 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) { private static String createIllegalArgumentLiteral(final Condition condition) {
return "Condition " + condition + NOT_SUPPORTED; return "Condition " + condition + NOT_SUPPORTED;
} }

View File

@@ -9,6 +9,7 @@
package org.eclipse.hawkbit.mgmt.rest.resource; package org.eclipse.hawkbit.mgmt.rest.resource;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import org.eclipse.hawkbit.mgmt.json.model.PagedList; import org.eclipse.hawkbit.mgmt.json.model.PagedList;
import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutResponseBody; 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.RolloutManagement;
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement; import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; 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.DistributionSet;
import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup; import org.eclipse.hawkbit.repository.model.RolloutGroup;
@@ -143,23 +145,30 @@ public class MgmtRolloutResource implements MgmtRolloutRestApi {
.successCondition(successCondition, successConditionExpr) .successCondition(successCondition, successConditionExpr)
.successAction(successAction, successActionExpr).errorCondition(errorCondition, errorConditionExpr) .successAction(successAction, successActionExpr).errorCondition(errorCondition, errorConditionExpr)
.errorAction(errorAction, errorActionExpr).build(); .errorAction(errorAction, errorActionExpr).build();
final Rollout rollout = this.rolloutManagement.createRollout(
MgmtRolloutMapper.fromRequest(entityFactory, rolloutRequestBody, distributionSet, Rollout rollout = MgmtRolloutMapper.fromRequest(entityFactory, rolloutRequestBody, distributionSet);
rolloutRequestBody.getTargetFilterQuery()),
rolloutRequestBody.getAmountGroups(), rolloutGroupConditions); 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)); return ResponseEntity.status(HttpStatus.CREATED).body(MgmtRolloutMapper.toResponseRollout(rollout));
} }
@Override @Override
public ResponseEntity<Void> start(@PathVariable("rolloutId") final Long rolloutId, public ResponseEntity<Void> start(@PathVariable("rolloutId") final Long rolloutId) {
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_ASYNC, defaultValue = "false") final boolean startAsync) {
final Rollout rollout = findRolloutOrThrowException(rolloutId); final Rollout rollout = findRolloutOrThrowException(rolloutId);
if (startAsync) { this.rolloutManagement.startRollout(rollout);
this.rolloutManagement.startRolloutAsync(rollout);
} else {
this.rolloutManagement.startRollout(rollout);
}
return ResponseEntity.ok().build(); return ResponseEntity.ok().build();
} }

View File

@@ -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.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.Callable; 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;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition; import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition;
import org.eclipse.hawkbit.repository.model.RolloutGroupConditionBuilder; 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.model.Target;
import org.eclipse.hawkbit.repository.test.util.WithUser; import org.eclipse.hawkbit.repository.test.util.WithUser;
import org.eclipse.hawkbit.rest.AbstractRestIntegrationTest; import org.eclipse.hawkbit.rest.AbstractRestIntegrationTest;
@@ -117,8 +119,85 @@ public class MgmtRolloutResourceTest extends AbstractRestIntegrationTest {
@Test @Test
@Description("Testing that rollout can be created") @Description("Testing that rollout can be created")
public void createRollout() throws Exception { public void createRollout() throws Exception {
targetManagement.createTargets(testdataFactory.generateTargets(20, "target", "rollout"));
final DistributionSet dsA = testdataFactory.createDistributionSet(""); 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 @Test
@@ -133,20 +212,26 @@ public class MgmtRolloutResourceTest extends AbstractRestIntegrationTest {
@Description("Testing that rollout paged list contains rollouts") @Description("Testing that rollout paged list contains rollouts")
public void rolloutPagedListContainsAllRollouts() throws Exception { public void rolloutPagedListContainsAllRollouts() throws Exception {
final DistributionSet dsA = testdataFactory.createDistributionSet(""); final DistributionSet dsA = testdataFactory.createDistributionSet("");
targetManagement.createTargets(testdataFactory.generateTargets(20, "target", "rollout"));
// setup - create 2 rollouts // setup - create 2 rollouts
postRollout("rollout1", 10, dsA.getId(), "name==target1"); postRollout("rollout1", 10, dsA.getId(), "id==target*");
postRollout("rollout2", 5, dsA.getId(), "name==target2"); 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()) mvc.perform(get("/rest/v1/rollouts")).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
.andExpect(jsonPath("$.content", hasSize(2))).andExpect(jsonPath("$.total", equalTo(2))) .andExpect(jsonPath("$.content", hasSize(2))).andExpect(jsonPath("$.total", equalTo(2)))
.andExpect(jsonPath("content[0].name", equalTo("rollout1"))) .andExpect(jsonPath("content[0].name", equalTo("rollout1")))
.andExpect(jsonPath("content[0].status", equalTo("ready"))) .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[0].distributionSetId", equalTo(dsA.getId().intValue())))
.andExpect(jsonPath("content[1].name", equalTo("rollout2"))) .andExpect(jsonPath("content[1].name", equalTo("rollout2")))
.andExpect(jsonPath("content[1].status", equalTo("ready"))) .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()))); .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") @Description("Testing that rollout paged list is limited by the query param limit")
public void rolloutPagedListIsLimitedToQueryParam() throws Exception { public void rolloutPagedListIsLimitedToQueryParam() throws Exception {
final DistributionSet dsA = testdataFactory.createDistributionSet(""); final DistributionSet dsA = testdataFactory.createDistributionSet("");
targetManagement.createTargets(testdataFactory.generateTargets(20, "target", "rollout"));
// setup - create 2 rollouts // setup - create 2 rollouts
postRollout("rollout1", 10, dsA.getId(), "name==target1"); postRollout("rollout1", 10, dsA.getId(), "id==target*");
postRollout("rollout2", 5, dsA.getId(), "name==target2"); 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()) mvc.perform(get("/rest/v1/rollouts?limit=1")).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
@@ -186,7 +277,7 @@ public class MgmtRolloutResourceTest extends AbstractRestIntegrationTest {
} }
@Test @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 { public void startingRolloutSwitchesIntoRunningState() throws Exception {
// setup // setup
final int amountTargets = 20; 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()) mvc.perform(post("/rest/v1/rollouts/{rolloutId}/start", rollout.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()); .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 // check rollout is in running state
mvc.perform(get("/rest/v1/rollouts/{rolloutId}", rollout.getId())).andDo(MockMvcResultPrinter.print()) mvc.perform(get("/rest/v1/rollouts/{rolloutId}", rollout.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) .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()) mvc.perform(post("/rest/v1/rollouts/{rolloutId}/start", rollout.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()); .andExpect(status().isOk());
// Run here, because scheduler is disabled during tests
rolloutManagement.checkStartingRollouts(0);
// pausing rollout // pausing rollout
mvc.perform(post("/rest/v1/rollouts/{rolloutId}/pause", rollout.getId())).andDo(MockMvcResultPrinter.print()) mvc.perform(post("/rest/v1/rollouts/{rolloutId}/pause", rollout.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()); .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()) mvc.perform(post("/rest/v1/rollouts/{rolloutId}/start", rollout.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()); .andExpect(status().isOk());
// Run here, because scheduler is disabled during tests
rolloutManagement.checkStartingRollouts(0);
// pausing rollout // pausing rollout
mvc.perform(post("/rest/v1/rollouts/{rolloutId}/pause", rollout.getId())).andDo(MockMvcResultPrinter.print()) mvc.perform(post("/rest/v1/rollouts/{rolloutId}/pause", rollout.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()); .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()) mvc.perform(post("/rest/v1/rollouts/{rolloutId}/start", rollout.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()); .andExpect(status().isOk());
// Run here, because scheduler is disabled during tests
rolloutManagement.checkStartingRollouts(0);
// starting rollout - already started should lead into bad request // starting rollout - already started should lead into bad request
mvc.perform(post("/rest/v1/rollouts/{rolloutId}/start", rollout.getId())).andDo(MockMvcResultPrinter.print()) mvc.perform(post("/rest/v1/rollouts/{rolloutId}/start", rollout.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isBadRequest()) .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()) mvc.perform(post("/rest/v1/rollouts/{rolloutId}/start", rollout.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()); .andExpect(status().isOk());
// Run here, because scheduler is disabled during tests
rolloutManagement.checkStartingRollouts(0);
// retrieve rollout groups from created rollout - 2 groups exists // retrieve rollout groups from created rollout - 2 groups exists
// (amountTargets / groupSize = 2) // (amountTargets / groupSize = 2)
mvc.perform(get("/rest/v1/rollouts/{rolloutId}/deploygroups?sort=ID:ASC", rollout.getId())) 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); rolloutManagement.startRollout(rollout);
// Run here, because scheduler is disabled during tests
rolloutManagement.checkStartingRollouts(0);
final RolloutGroup firstGroup = rolloutGroupManagement final RolloutGroup firstGroup = rolloutGroupManagement
.findRolloutGroupsByRolloutId(rollout.getId(), new PageRequest(0, 1, Direction.ASC, "id")).getContent() .findRolloutGroupsByRolloutId(rollout.getId(), new PageRequest(0, 1, Direction.ASC, "id")).getContent()
.get(0); .get(0);
@@ -432,10 +547,12 @@ public class MgmtRolloutResourceTest extends AbstractRestIntegrationTest {
final Rollout rollout = createRollout("rollout1", 4, dsA.getId(), "controllerId==rollout*"); final Rollout rollout = createRollout("rollout1", 4, dsA.getId(), "controllerId==rollout*");
// starting rollout // starting rollout
mvc.perform(post("/rest/v1/rollouts/{rolloutId}/start", rollout.getId()) mvc.perform(post("/rest/v1/rollouts/{rolloutId}/start", rollout.getId())).andDo(MockMvcResultPrinter.print())
.param(MgmtRestConstants.REQUEST_PARAMETER_ASYNC, "true")).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()); .andExpect(status().isOk());
// Run here, because scheduler is disabled during tests
rolloutManagement.checkStartingRollouts(0);
// check if running // check if running
assertThat(doWithTimeout(() -> getRollout(rollout.getId()), result -> success(result), 60_000, 100)) assertThat(doWithTimeout(() -> getRollout(rollout.getId()), result -> success(result), 60_000, 100))
.isNotNull(); .isNotNull();
@@ -549,19 +666,25 @@ public class MgmtRolloutResourceTest extends AbstractRestIntegrationTest {
private void postRollout(final String name, final int groupSize, final long distributionSetId, private void postRollout(final String name, final int groupSize, final long distributionSetId,
final String targetFilterQuery) throws Exception { final String targetFilterQuery) throws Exception {
mvc.perform(post("/rest/v1/rollouts") 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)) .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isCreated()).andReturn(); .andDo(MockMvcResultPrinter.print()).andExpect(status().isCreated()).andReturn();
} }
private Rollout createRollout(final String name, final int amountGroups, final long distributionSetId, private Rollout createRollout(final String name, final int amountGroups, final long distributionSetId,
final String targetFilterQuery) { final String targetFilterQuery) {
final Rollout rollout = entityFactory.generateRollout(); Rollout rollout = entityFactory.generateRollout();
rollout.setDistributionSet(distributionSetManagement.findDistributionSetById(distributionSetId)); rollout.setDistributionSet(distributionSetManagement.findDistributionSetById(distributionSetId));
rollout.setName(name); rollout.setName(name);
rollout.setTargetFilterQuery(targetFilterQuery); rollout.setTargetFilterQuery(targetFilterQuery);
return rolloutManagement.createRollout(rollout, amountGroups, new RolloutGroupConditionBuilder() rollout = rolloutManagement.createRollout(rollout, amountGroups, new RolloutGroupConditionBuilder()
.successCondition(RolloutGroupSuccessCondition.THRESHOLD, "100").build()); .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) { protected boolean success(final Rollout result) {

View File

@@ -276,25 +276,6 @@ public interface DeploymentManagement {
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
List<Action> findActionsByRolloutAndStatus(@NotNull Rollout rollout, @NotNull Action.Status actionStatus); 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. * Retrieves all {@link Action}s of a specific target.
* *
@@ -513,6 +494,19 @@ public interface DeploymentManagement {
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
Action startScheduledAction(@NotNull Long actionId); 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. * All {@link ActionStatus} entries in the repository.
* *

View File

@@ -11,7 +11,6 @@ package org.eclipse.hawkbit.repository;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions; 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.RSQLParameterSyntaxException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException; import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException; 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.data.domain.Slice;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import java.util.List;
/** /**
* RolloutManagement to control rollouts e.g. like creating, starting, resuming * RolloutManagement to control rollouts e.g. like creating, starting, resuming
* and pausing rollouts. This service secures all the functionality based on the * 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) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE)
void checkRunningRollouts(long delayBetweenChecks); 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. * Counts all {@link Rollout}s in the repository.
* *
@@ -85,14 +110,17 @@ public interface RolloutManagement {
/** /**
* Persists a new rollout entity. The filter within the * Persists a new rollout entity. The filter within the
* {@link Rollout#getTargetFilterQuery()} is used to retrieve the targets * {@link Rollout#getTargetFilterQuery()} is used to retrieve the targets
* which are effected by this rollout to create. The targets will then be * which are effected by this rollout to create. The amount of groups will
* split up into groups. The size of the groups can be defined in the * be defined as equally sized.
* {@code groupSize} parameter.
* *
* The rollout is not started. Only the preparation of the rollout is done, * The rollout is not started. Only the preparation of the rollout is done,
* persisting and creating all the necessary groups. The Rollout and the * creating and persisting all the necessary groups. The Rollout and the
* groups are persisted in {@link RolloutStatus#READY} and * groups are persisted in {@link RolloutStatus#CREATING} and
* {@link RolloutGroupStatus#READY} so they can be started * {@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)}. * {@link #startRollout(Rollout)}.
* *
* @param rollout * @param rollout
@@ -112,40 +140,52 @@ public interface RolloutManagement {
/** /**
* Persists a new rollout entity. The filter within the * Persists a new rollout entity. The filter within the
* {@link Rollout#getTargetFilterQuery()} is used to retrieve the targets * {@link Rollout#getTargetFilterQuery()} is used to filter the targets
* which are effected by this rollout to create. The creation of the rollout * which are affected by this rollout. The given groups will be used to
* will be done synchronously and will be returned. The targets will then be * create the groups.
* split up into groups. The size of the groups can be defined in the
* {@code groupSize} parameter.
*
* The creation of the rollout groups is executed asynchronously due it
* might take some time to split up the targets into groups. The creation of
* the {@link RolloutGroup} is published as event
* {@link RolloutGroupCreatedEvent}.
*
* The rollout is in status {@link RolloutStatus#CREATING} until all rollout
* groups has been created and the targets are split up, then the rollout
* will change the status to {@link RolloutStatus#READY}.
* *
* The rollout is not started. Only the preparation of the rollout is done, * The rollout is not started. Only the preparation of the rollout is done,
* persisting and creating all the necessary groups. The Rollout and the * creating and persisting all the necessary groups. The Rollout and the
* groups are persisted in {@link RolloutStatus#READY} and * groups are persisted in {@link RolloutStatus#CREATING} and
* {@link RolloutGroupStatus#READY} so they can be started * {@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)}. * {@link #startRollout(Rollout)}.
* *
* @param rollout * @param rollout
* the rollout to be created * the rollout entity to create
* @param amountGroup * @param groups
* the number of groups should be created for the rollout and * optional definition of groups
* split up the targets
* @param conditions * @param conditions
* the rolloutgroup conditions and actions which should be * the rollout group conditions and actions which should be
* applied for each {@link RolloutGroup} * applied for each {@link RolloutGroup} if not defined by the
* @return the created rollout entity in state * RolloutGroup itself
* {@link RolloutStatus#CREATING} * @return the persisted rollout.
*
* @throws IllegalArgumentException
* in case the given groupSize is zero or lower.
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE) @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. * Retrieves all rollouts.
@@ -284,12 +324,10 @@ public interface RolloutManagement {
/** /**
* Starts a rollout which has been created. The rollout must be in * Starts a rollout which has been created. The rollout must be in
* {@link RolloutStatus#READY} state. The according actions will be created * {@link RolloutStatus#READY} state. The Rollout will be set into the
* for each affected target in the rollout. The actions of the first group * {@link RolloutStatus#STARTING} state. The RolloutScheduler will ensure
* will be started immediately {@link RolloutGroupStatus#RUNNING} as the * all actions are created and the first group is started. The rollout
* other groups will be {@link RolloutGroupStatus#SCHEDULED} state. * itself will be then also in {@link RolloutStatus#RUNNING}.
*
* The rollout itself will be then also in {@link RolloutStatus#RUNNING}.
* *
* @param rollout * @param rollout
* the rollout to be started * the rollout to be started
@@ -303,28 +341,6 @@ public interface RolloutManagement {
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE)
Rollout startRollout(@NotNull Rollout rollout); 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. * Update rollout details.
* *

View File

@@ -19,6 +19,7 @@ import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException; import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException; import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
import org.eclipse.hawkbit.repository.model.DistributionSet; 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.Tag;
import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery; import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
@@ -268,7 +269,7 @@ public interface TargetManagement {
* id of the {@link DistributionSet} * id of the {@link DistributionSet}
* @param targetFilterQuery * @param targetFilterQuery
* {@link TargetFilterQuery} * {@link TargetFilterQuery}
* @return the found {@link TargetIdName}s * @return a page of the found {@link Target}s
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
Page<Target> findAllTargetsByTargetFilterQueryAndNonDS(@NotNull Pageable pageRequest, Long distributionSetId, Page<Target> findAllTargetsByTargetFilterQueryAndNonDS(@NotNull Pageable pageRequest, Long distributionSetId,
@@ -283,11 +284,54 @@ public interface TargetManagement {
* id of the {@link DistributionSet} * id of the {@link DistributionSet}
* @param targetFilterQuery * @param targetFilterQuery
* {@link TargetFilterQuery} * {@link TargetFilterQuery}
* @return the found {@link TargetIdName}s * @return the count of found {@link Target}s
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
Long countTargetsByTargetFilterQueryAndNonDS(Long distributionSetId, @NotNull TargetFilterQuery targetFilterQuery); 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 * retrieves {@link Target}s by the assigned {@link DistributionSet} without
* details, i.e. NO {@link Target#getTags()} and {@link Target#getActions()} * details, i.e. NO {@link Target#getTags()} and {@link Target#getActions()}

View File

@@ -0,0 +1,63 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.exception;
import org.eclipse.hawkbit.exception.AbstractServerRtException;
import org.eclipse.hawkbit.exception.SpServerError;
/**
* the {@link RolloutVerificationException} is thrown when a rollout or
* its groups get created or modified with a configuration that is
* not valid or can't be verified
*
*/
public class RolloutVerificationException extends AbstractServerRtException {
private static final long serialVersionUID = 1L;
private static final SpServerError THIS_ERROR = SpServerError.SP_ROLLOUT_VERIFICATION_FAILED;
/**
* Default constructor.
*/
public RolloutVerificationException() {
super(THIS_ERROR);
}
/**
* Parameterized constructor.
*
* @param cause
* of the exception
*/
public RolloutVerificationException(final Throwable cause) {
super(THIS_ERROR, cause);
}
/**
* Parameterized constructor.
*
* @param message
* of the exception
* @param cause
* of the exception
*/
public RolloutVerificationException(final String message, final Throwable cause) {
super(message, THIS_ERROR, cause);
}
/**
* Parameterized constructor.
*
* @param message
* of the exception
*/
public RolloutVerificationException(final String message) {
super(message, THIS_ERROR);
}
}

View File

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

View File

@@ -152,6 +152,23 @@ public interface RolloutGroup extends NamedEntity {
*/ */
String getSuccessActionExp(); 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 * @return the total amount of targets containing in this group
*/ */
@@ -169,10 +186,41 @@ public interface RolloutGroup extends NamedEntity {
void setTotalTargetCountStatus(TotalTargetCountStatus totalTargetCountStatus); 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. * Ready to start the group.
@@ -198,18 +246,18 @@ public interface RolloutGroup extends NamedEntity {
/** /**
* Group is running. * Group is running.
*/ */
RUNNING; RUNNING
} }
/** /**
* The condition to evaluate if an group is success state. * The condition to evaluate if an group is success state.
*/ */
public enum RolloutGroupSuccessCondition { enum RolloutGroupSuccessCondition {
THRESHOLD("thresholdRolloutGroupSuccessCondition"); THRESHOLD("thresholdRolloutGroupSuccessCondition");
private final String beanName; private final String beanName;
private RolloutGroupSuccessCondition(final String beanName) { RolloutGroupSuccessCondition(final String beanName) {
this.beanName = beanName; this.beanName = beanName;
} }
@@ -224,12 +272,12 @@ public interface RolloutGroup extends NamedEntity {
/** /**
* The condition to evaluate if an group is in error state. * The condition to evaluate if an group is in error state.
*/ */
public enum RolloutGroupErrorCondition { enum RolloutGroupErrorCondition {
THRESHOLD("thresholdRolloutGroupErrorCondition"); THRESHOLD("thresholdRolloutGroupErrorCondition");
private final String beanName; private final String beanName;
private RolloutGroupErrorCondition(final String beanName) { RolloutGroupErrorCondition(final String beanName) {
this.beanName = 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"); PAUSE("pauseRolloutGroupAction");
private final String beanName; private final String beanName;
private RolloutGroupErrorAction(final String beanName) { RolloutGroupErrorAction(final String beanName) {
this.beanName = 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. * hit.
*/ */
public enum RolloutGroupSuccessAction { enum RolloutGroupSuccessAction {
NEXTGROUP("startNextRolloutGroupAction"); NEXTGROUP("startNextRolloutGroupAction");
private final String beanName; private final String beanName;
private RolloutGroupSuccessAction(final String beanName) { RolloutGroupSuccessAction(final String beanName) {
this.beanName = beanName; this.beanName = beanName;
} }

View File

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

View File

@@ -37,7 +37,6 @@ import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
import org.springframework.hateoas.Identifiable;
import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional; 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. * Finding all actions of a specific rolloutgroup parent relation.
* *
* @param pageable
* page parameters
* @param rollout * @param rollout
* the rollout the actions belong to * the rollout the actions belong to
* @param rolloutGroupParent * @param rolloutGroupParent
@@ -348,7 +349,38 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
* @return the actions referring a specific rollout and a specific parent * @return the actions referring a specific rollout and a specific parent
* rolloutgroup in a specific status * 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); JpaRolloutGroup rolloutGroupParent, Status actionStatus);
/** /**

View File

@@ -78,13 +78,18 @@ import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.domain.AuditorAware; import org.springframework.data.domain.AuditorAware;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice; import org.springframework.data.domain.Slice;
import org.springframework.data.jpa.domain.Specification; import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.Modifying; 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.Isolation;
import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional; 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 org.springframework.validation.annotation.Validated;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
@@ -98,6 +103,11 @@ import com.google.common.collect.Lists;
public class JpaDeploymentManagement implements DeploymentManagement { public class JpaDeploymentManagement implements DeploymentManagement {
private static final Logger LOG = LoggerFactory.getLogger(JpaDeploymentManagement.class); 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 @Autowired
private EntityManager entityManager; private EntityManager entityManager;
@@ -134,6 +144,9 @@ public class JpaDeploymentManagement implements DeploymentManagement {
@Autowired @Autowired
private VirtualPropertyReplacer virtualPropertyReplacer; private VirtualPropertyReplacer virtualPropertyReplacer;
@Autowired
private PlatformTransactionManager txManager;
@Override @Override
@Transactional(isolation = Isolation.READ_COMMITTED) @Transactional(isolation = Isolation.READ_COMMITTED)
@Modifying @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 @Override
@Modifying @Modifying
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED) @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED)
public Action startScheduledAction(final Long actionId) { public Action startScheduledAction(final Long actionId) {
final JpaAction action = actionRepository.findById(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 // check if we need to override running update actions
final Set<Long> overrideObsoleteUpdateActions = overrideObsoleteUpdateActions( final Set<Long> overrideObsoleteUpdateActions = overrideObsoleteUpdateActions(
Collections.singletonList(action.getTarget().getId())); Collections.singletonList(action.getTarget().getId()));
@@ -665,14 +725,6 @@ public class JpaDeploymentManagement implements DeploymentManagement {
return actionStatusRepository.getByAction(pageReq, (JpaAction) action); 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 @Override
public List<Action> findActionsByRolloutAndStatus(final Rollout rollout, final Action.Status actionStatus) { public List<Action> findActionsByRolloutAndStatus(final Rollout rollout, final Action.Status actionStatus) {
return actionRepository.findByRolloutAndStatus((JpaRollout) rollout, actionStatus); return actionRepository.findByRolloutAndStatus((JpaRollout) rollout, actionStatus);

View File

@@ -11,21 +11,21 @@ package org.eclipse.hawkbit.repository.jpa;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.hawkbit.repository.DeploymentManagement; import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.OffsetBasedPageRequest;
import org.eclipse.hawkbit.repository.RolloutFields; import org.eclipse.hawkbit.repository.RolloutFields;
import org.eclipse.hawkbit.repository.RolloutGroupManagement; import org.eclipse.hawkbit.repository.RolloutGroupManagement;
import org.eclipse.hawkbit.repository.RolloutManagement; import org.eclipse.hawkbit.repository.RolloutManagement;
import org.eclipse.hawkbit.repository.TargetManagement; import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.event.remote.entity.RolloutGroupCreatedEvent; 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.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.JpaRollout;
import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup; import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout_; 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.RolloutGroup.RolloutGroupSuccessCondition;
import org.eclipse.hawkbit.repository.model.RolloutGroupConditions; import org.eclipse.hawkbit.repository.model.RolloutGroupConditions;
import org.eclipse.hawkbit.repository.model.Target; 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.TotalTargetCountActionStatus;
import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus; import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus;
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer; import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
@@ -52,23 +51,23 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice; 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.domain.Specification;
import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Modifying;
import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.DefaultTransactionDefinition; import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate; import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
@@ -81,6 +80,11 @@ import org.springframework.validation.annotation.Validated;
public class JpaRolloutManagement implements RolloutManagement { public class JpaRolloutManagement implements RolloutManagement {
private static final Logger LOGGER = LoggerFactory.getLogger(RolloutManagement.class); 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 @Autowired
private EntityManager entityManager; private EntityManager entityManager;
@@ -90,9 +94,6 @@ public class JpaRolloutManagement implements RolloutManagement {
@Autowired @Autowired
private TargetManagement targetManagement; private TargetManagement targetManagement;
@Autowired
private TargetRepository targetRepository;
@Autowired @Autowired
private RolloutGroupRepository rolloutGroupRepository; private RolloutGroupRepository rolloutGroupRepository;
@@ -124,24 +125,7 @@ public class JpaRolloutManagement implements RolloutManagement {
private VirtualPropertyReplacer virtualPropertyReplacer; private VirtualPropertyReplacer virtualPropertyReplacer;
@Autowired @Autowired
@Qualifier("asyncExecutor") private AfterTransactionCommitExecutor afterCommit;
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();
@Override @Override
public Page<Rollout> findAll(final Pageable pageable) { public Page<Rollout> findAll(final Pageable pageable) {
@@ -177,176 +161,406 @@ public class JpaRolloutManagement implements RolloutManagement {
@Modifying @Modifying
public Rollout createRollout(final Rollout rollout, final int amountGroup, public Rollout createRollout(final Rollout rollout, final int amountGroup,
final RolloutGroupConditions conditions) { final RolloutGroupConditions conditions) {
final JpaRollout savedRollout = createRollout((JpaRollout) rollout, amountGroup); RolloutHelper.verifyRolloutGroupParameter(amountGroup);
final JpaRollout savedRollout = createRollout((JpaRollout) rollout);
return createRolloutGroups(amountGroup, conditions, savedRollout); return createRolloutGroups(amountGroup, conditions, savedRollout);
} }
@Override @Override
@Transactional(isolation = Isolation.READ_UNCOMMITTED) @Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying @Modifying
public Rollout createRolloutAsync(final Rollout rollout, final int amountGroup, public Rollout createRollout(final Rollout rollout,
final RolloutGroupConditions conditions) { final List<RolloutGroup> groups,
final JpaRollout savedRollout = createRollout((JpaRollout) rollout, amountGroup); final RolloutGroupConditions conditions) {
creatingRollouts.add(savedRollout.getName()); RolloutHelper.verifyRolloutGroupParameter(groups.size());
// need to flush the entity manager here to get the ID of the rollout, final JpaRollout savedRollout = createRollout((JpaRollout) rollout);
// because entity manager is set to FlushMode#Auto, entitymanager will return createRolloutGroups(groups, conditions, savedRollout);
// 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;
} }
private JpaRollout createRollout(final JpaRollout rollout, final int amountGroup) { private JpaRollout createRollout(final JpaRollout rollout) {
verifyRolloutGroupParameter(amountGroup); JpaRollout existingRollout = rolloutRepository.findByName(rollout.getName());
if(existingRollout != null) {
throw new EntityAlreadyExistsException(existingRollout.getName());
}
final Long totalTargets = targetManagement.countTargetByTargetFilterQuery(rollout.getTargetFilterQuery()); 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); 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, private Rollout createRolloutGroups(final int amountOfGroups, final RolloutGroupConditions conditions,
final JpaRollout savedRollout) { final Rollout rollout) {
int pageIndex = 0; RolloutHelper.verifyRolloutInStatus(rollout, RolloutStatus.CREATING);
int groupIndex = 0; RolloutHelper.verifyRolloutGroupConditions(conditions);
final Long totalCount = savedRollout.getTotalTargets();
final int groupSize = (int) Math.ceil((double) totalCount / (double) amountOfGroups); final JpaRollout savedRollout = (JpaRollout) rollout;
RolloutGroup lastSavedGroup = null; RolloutGroup lastSavedGroup = null;
while (pageIndex < totalCount) { for (int i = 0; i < amountOfGroups; i++) {
groupIndex++; final String nameAndDesc = "group-" + (i + 1);
final String nameAndDesc = "group-" + groupIndex;
final JpaRolloutGroup group = new JpaRolloutGroup(); final JpaRolloutGroup group = new JpaRolloutGroup();
group.setName(nameAndDesc); group.setName(nameAndDesc);
group.setDescription(nameAndDesc); group.setDescription(nameAndDesc);
group.setRollout(savedRollout); group.setRollout(savedRollout);
group.setParent(lastSavedGroup); group.setParent(lastSavedGroup);
group.setStatus(RolloutGroupStatus.CREATING);
group.setSuccessCondition(conditions.getSuccessCondition()); group.setSuccessCondition(conditions.getSuccessCondition());
group.setSuccessConditionExp(conditions.getSuccessConditionExp()); group.setSuccessConditionExp(conditions.getSuccessConditionExp());
group.setSuccessAction(conditions.getSuccessAction());
group.setSuccessActionExp(conditions.getSuccessActionExp());
group.setErrorCondition(conditions.getErrorCondition()); group.setErrorCondition(conditions.getErrorCondition());
group.setErrorConditionExp(conditions.getErrorConditionExp()); group.setErrorConditionExp(conditions.getErrorConditionExp());
group.setErrorAction(conditions.getErrorAction()); group.setErrorAction(conditions.getErrorAction());
group.setErrorActionExp(conditions.getErrorActionExp()); 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(), lastSavedGroup = rolloutGroupRepository.save(group);
new OffsetBasedPageRequest(pageIndex, groupSize, new Sort(Direction.ASC, "id"))); publishRolloutGroupCreatedEventAfterCommit(lastSavedGroup);
savedGroup.setTotalTargets(targetGroup.getContent().size());
lastSavedGroup = savedGroup;
targetGroup
.forEach(target -> rolloutTargetGroupRepository.save(new RolloutTargetGroup(savedGroup, target)));
eventPublisher.publishEvent(new RolloutGroupCreatedEvent(group, context.getId()));
pageIndex += groupSize;
} }
savedRollout.setRolloutGroupsCreated(groupIndex); savedRollout.setRolloutGroupsCreated(amountOfGroups);
savedRollout.setStatus(RolloutStatus.READY);
return rolloutRepository.save(savedRollout); 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 @Override
@Transactional(isolation = Isolation.READ_UNCOMMITTED) @Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying @Modifying
public Rollout startRollout(final Rollout rollout) { 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); final JpaRollout mergedRollout = entityManager.merge((JpaRollout) rollout);
checkIfRolloutCanStarted(rollout, mergedRollout); checkIfRolloutCanStarted(rollout, mergedRollout);
mergedRollout.setStatus(RolloutStatus.STARTING); mergedRollout.setStatus(RolloutStatus.STARTING);
final JpaRollout updatedRollout = rolloutRepository.save(mergedRollout); return rolloutRepository.save(mergedRollout);
startingRollouts.add(updatedRollout.getName()); }
executor.execute(() -> {
try { private void startFirstRolloutGroup(final Rollout rollout) {
final DefaultTransactionDefinition def = new DefaultTransactionDefinition(); RolloutHelper.verifyRolloutInStatus(rollout, RolloutStatus.STARTING);
def.setName("startingRollout"); final JpaRollout jpaRollout = (JpaRollout) rollout;
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
new TransactionTemplate(txManager, def).execute(status -> { final List<JpaRolloutGroup> rolloutGroups = rolloutGroupRepository.findByRolloutOrderByIdAsc(jpaRollout);
doStartRollout(updatedRollout); final JpaRolloutGroup rolloutGroup = rolloutGroups.get(0);
return null; if (rolloutGroup.getParent() != null) {
}); throw new RolloutIllegalStateException("First Group is not the first group.");
} finally { }
startingRollouts.remove(updatedRollout.getName());
} deploymentManagement.startScheduledActionsByRolloutGroupParent(rollout, null);
});
return updatedRollout; rolloutGroup.setStatus(RolloutGroupStatus.RUNNING);
rolloutGroupRepository.save(rolloutGroup);
jpaRollout.setStatus(RolloutStatus.RUNNING);
jpaRollout.setLastCheck(0);
rolloutRepository.save(jpaRollout);
} }
private Rollout doStartRollout(final JpaRollout rollout) { private boolean ensureAllGroupsAreScheduled(final Rollout rollout) {
final DistributionSet distributionSet = rollout.getDistributionSet(); RolloutHelper.verifyRolloutInStatus(rollout, RolloutStatus.STARTING);
final ActionType actionType = rollout.getActionType(); final JpaRollout jpaRollout = (JpaRollout) rollout;
final long forceTime = rollout.getForcedTime();
final List<JpaRolloutGroup> rolloutGroups = rolloutGroupRepository.findByRolloutOrderByIdAsc(rollout); final List<JpaRolloutGroup> groupsToBeScheduled = rolloutGroupRepository.findByRolloutAndStatus(rollout,
for (int iGroup = 0; iGroup < rolloutGroups.size(); iGroup++) { RolloutGroupStatus.READY);
final JpaRolloutGroup rolloutGroup = rolloutGroups.get(iGroup); final long scheduledGroups = groupsToBeScheduled.stream()
final List<Target> targetGroup = targetRepository.findByRolloutTargetGroupRolloutGroup(rolloutGroup); .filter(group -> scheduleRolloutGroup(jpaRollout, group)).count();
// firstgroup can already be started
if (iGroup == 0) { return scheduledGroups == groupsToBeScheduled.size();
final List<TargetWithActionType> targetsWithActionType = targetGroup.stream() }
.map(t -> new TargetWithActionType(t.getControllerId(), actionType, forceTime))
.collect(Collectors.toList()); /**
deploymentManagement.assignDistributionSet(distributionSet.getId(), targetsWithActionType, rollout, * Schedules a group of the rollout. Scheduled Actions are created to
rolloutGroup); * achieve this. The creation of those Actions is allowed to fail.
rolloutGroup.setStatus(RolloutGroupStatus.RUNNING); *
} else { * @param rollout
// create only not active actions with status scheduled so they * the Rollout
// can be activated later * @param group
deploymentManagement.createScheduledAction(targetGroup, distributionSet, actionType, forceTime, rollout, * the RolloutGroup
rolloutGroup); * @return whether the complete group was scheduled
rolloutGroup.setStatus(RolloutGroupStatus.SCHEDULED); */
} private boolean scheduleRolloutGroup(final JpaRollout rollout, final JpaRolloutGroup group) {
rolloutGroupRepository.save(rolloutGroup); final long targetsInGroup = rolloutTargetGroupRepository.countByRolloutGroup(group);
final long countOfActions = actionRepository.countByRolloutAndRolloutGroup(rollout, group);
long actionsLeft = targetsInGroup - countOfActions;
if (actionsLeft > 0) {
actionsLeft -= createActionsForRolloutGroup(rollout, group);
} }
rollout.setStatus(RolloutStatus.RUNNING);
return rolloutRepository.save(rollout); if (actionsLeft <= 0) {
group.setStatus(RolloutGroupStatus.SCHEDULED);
rolloutGroupRepository.save(group);
return true;
}
return false;
}
private long createActionsForRolloutGroup(final Rollout rollout, final RolloutGroup group) {
long totalActionsCreated = 0;
try {
long actionsCreated;
do {
actionsCreated = createActionsForTargetsInNewTransaction(rollout.getId(), group.getId(),
TRANSACTION_TARGETS);
totalActionsCreated += actionsCreated;
} while (actionsCreated > 0);
} catch (TransactionException e) {
LOGGER.warn("Transaction assigning Targets to RolloutGroup failed", e);
return 0;
}
return totalActionsCreated;
}
private long createActionsForTargetsInNewTransaction(final long rolloutId, final long groupId, final long limit) {
return runInNewCountingTransaction("createActionsForTargets", status -> {
final PageRequest pageRequest = new PageRequest(0, Math.toIntExact(limit));
final Rollout rollout = rolloutRepository.findOne(rolloutId);
final RolloutGroup group = rolloutGroupRepository.findOne(groupId);
final DistributionSet distributionSet = rollout.getDistributionSet();
final ActionType actionType = rollout.getActionType();
final long forceTime = rollout.getForcedTime();
Page<Target> targets = targetManagement.findAllTargetsInRolloutGroupWithoutAction(pageRequest, group);
if (targets.getTotalElements() > 0) {
deploymentManagement.createScheduledAction(targets.getContent(), distributionSet, actionType, forceTime,
rollout, group);
}
return targets.getTotalElements();
});
} }
@Override @Override
@@ -384,7 +598,6 @@ public class JpaRolloutManagement implements RolloutManagement {
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_UNCOMMITTED) @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_UNCOMMITTED)
@Modifying @Modifying
public void checkRunningRollouts(final long delayBetweenChecks) { public void checkRunningRollouts(final long delayBetweenChecks) {
verifyStuckedRollouts();
final long lastCheck = System.currentTimeMillis(); final long lastCheck = System.currentTimeMillis();
final int updated = rolloutRepository.updateLastCheck(lastCheck, delayBetweenChecks, RolloutStatus.RUNNING); 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) { private void executeRolloutGroups(final JpaRollout rollout, final List<JpaRolloutGroup> rolloutGroups) {
for (final JpaRolloutGroup rolloutGroup : rolloutGroups) { for (final JpaRolloutGroup rolloutGroup : rolloutGroups) {
@@ -573,6 +756,54 @@ public class JpaRolloutManagement implements RolloutManagement {
rolloutGroup, rolloutGroup.getSuccessActionExp()); 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 @Override
public Long countRolloutsAll() { public Long countRolloutsAll() {
return rolloutRepository.count(); return rolloutRepository.count();

View File

@@ -43,6 +43,7 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaTargetInfo_;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag; import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_;
import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility; 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.rsql.VirtualPropertyReplacer;
import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder; import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder;
import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications; 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 @Override
public Long countTargetsByTargetFilterQueryAndNonDS(final Long distributionSetId, public Long countTargetsByTargetFilterQueryAndNonDS(final Long distributionSetId,
@NotNull final TargetFilterQuery targetFilterQuery) { @NotNull final TargetFilterQuery targetFilterQuery) {

View File

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

View File

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

View File

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

View File

@@ -73,7 +73,10 @@ public class RolloutScheduler {
LOGGER.info("Checking rollouts for {} tenants", tenants.size()); LOGGER.info("Checking rollouts for {} tenants", tenants.size());
for (final String tenant : tenants) { for (final String tenant : tenants) {
tenantAware.runAsTenant(tenant, () -> { 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; return null;
}); });
} }

View File

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

View File

@@ -31,7 +31,10 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaTargetInfo_;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag; import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag_; import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag_;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_; 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.DistributionSet;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetInfo; import org.eclipse.hawkbit.repository.model.TargetInfo;
import org.eclipse.hawkbit.repository.model.TargetTag; 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 * {@link Specification} for retrieving {@link Target}s by assigned
* distribution set. * distribution set.

View File

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

View File

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

View File

@@ -9,16 +9,21 @@
package org.eclipse.hawkbit.repository.jpa; package org.eclipse.hawkbit.repository.jpa;
import static org.fest.assertions.api.Assertions.assertThat; import static org.fest.assertions.api.Assertions.assertThat;
import static org.fest.assertions.api.Assertions.fail;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import org.eclipse.hawkbit.repository.OffsetBasedPageRequest; import org.eclipse.hawkbit.repository.OffsetBasedPageRequest;
import org.eclipse.hawkbit.repository.RolloutGroupManagement; import org.eclipse.hawkbit.repository.RolloutGroupManagement;
import org.eclipse.hawkbit.repository.RolloutManagement; 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.JpaAction;
import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus; import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout; import org.eclipse.hawkbit.repository.jpa.model.JpaRollout;
@@ -190,7 +195,11 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
final Rollout createdRollout = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, final Rollout createdRollout = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout,
amountOtherTargets, amountGroups, successCondition, errorCondition); amountOtherTargets, amountGroups, successCondition, errorCondition);
rolloutManagement.startRollout(createdRollout); 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") @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); validateRolloutActionStatus(createdRollout.getId(), validationMap);
rolloutManagement.startRollout(createdRollout); rolloutManagement.startRollout(createdRollout);
// Run here, because scheduler is disabled during tests
rolloutManagement.checkStartingRollouts(0);
// 6 targets are ready and 2 are running // 6 targets are ready and 2 are running
validationMap = createInitStatusMap(); validationMap = createInitStatusMap();
validationMap.put(TotalTargetCountStatus.Status.SCHEDULED, 6L); validationMap.put(TotalTargetCountStatus.Status.SCHEDULED, 6L);
@@ -466,6 +479,10 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
validateRolloutActionStatus(createdRollout.getId(), validationMap); validateRolloutActionStatus(createdRollout.getId(), validationMap);
rolloutManagement.startRollout(createdRollout); rolloutManagement.startRollout(createdRollout);
// Run here, because scheduler is disabled during tests
rolloutManagement.checkStartingRollouts(0);
// 6 targets are ready and 2 are running // 6 targets are ready and 2 are running
validationMap = createInitStatusMap(); validationMap = createInitStatusMap();
validationMap.put(TotalTargetCountStatus.Status.SCHEDULED, 6L); validationMap.put(TotalTargetCountStatus.Status.SCHEDULED, 6L);
@@ -496,19 +513,28 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
changeStatusForAllRunningActions(createdRollout, Status.FINISHED); changeStatusForAllRunningActions(createdRollout, Status.FINISHED);
rolloutManagement.checkRunningRollouts(0); rolloutManagement.checkRunningRollouts(0);
// 3 targets finished (Group 1), 3 targets running (Group 3) and 3 // round(9/4)=2 targets finished (Group 1)
// targets SCHEDULED (Group 3) // 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()); createdRollout = rolloutManagement.findRolloutById(createdRollout.getId());
final List<RolloutGroup> rolloutGruops = createdRollout.getRolloutGroups(); final List<RolloutGroup> rolloutGroups = createdRollout.getRolloutGroups();
Map<TotalTargetCountStatus.Status, Long> expectedTargetCountStatus = createInitStatusMap(); Map<TotalTargetCountStatus.Status, Long> expectedTargetCountStatus = createInitStatusMap();
expectedTargetCountStatus.put(TotalTargetCountStatus.Status.FINISHED, 3L); expectedTargetCountStatus.put(TotalTargetCountStatus.Status.FINISHED, 2L);
validateRolloutGroupActionStatus(rolloutGruops.get(0), expectedTargetCountStatus); validateRolloutGroupActionStatus(rolloutGroups.get(0), expectedTargetCountStatus);
expectedTargetCountStatus = createInitStatusMap(); expectedTargetCountStatus = createInitStatusMap();
expectedTargetCountStatus.put(TotalTargetCountStatus.Status.RUNNING, 3L); expectedTargetCountStatus.put(TotalTargetCountStatus.Status.RUNNING, 2L);
validateRolloutGroupActionStatus(rolloutGruops.get(1), expectedTargetCountStatus); validateRolloutGroupActionStatus(rolloutGroups.get(1), expectedTargetCountStatus);
expectedTargetCountStatus = createInitStatusMap(); expectedTargetCountStatus = createInitStatusMap();
expectedTargetCountStatus.put(TotalTargetCountStatus.Status.SCHEDULED, 3L); 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); validateRolloutActionStatus(rolloutOne.getId(), expectedTargetCountStatus);
rolloutManagement.startRollout(rolloutTwo); 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 // Verify that 5 targets are finished, 5 are still running and 5 are
// cancelled. // cancelled.
expectedTargetCountStatus = createInitStatusMap(); expectedTargetCountStatus = createInitStatusMap();
@@ -631,11 +661,15 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
amountGroupsForRolloutTwo, "controllerId==rollout-*", distributionSet, "50", "80"); amountGroupsForRolloutTwo, "controllerId==rollout-*", distributionSet, "50", "80");
rolloutManagement.startRollout(rolloutTwo); rolloutManagement.startRollout(rolloutTwo);
// Run here, because scheduler is disabled during tests
rolloutManagement.checkStartingRollouts(0);
rolloutTwo = rolloutManagement.findRolloutById(rolloutTwo.getId()); rolloutTwo = rolloutManagement.findRolloutById(rolloutTwo.getId());
// 6 error targets are know running // 6 error targets are know running
expectedTargetCountStatus = createInitStatusMap(); expectedTargetCountStatus = createInitStatusMap();
expectedTargetCountStatus.put(TotalTargetCountStatus.Status.RUNNING, 6L); expectedTargetCountStatus.put(TotalTargetCountStatus.Status.RUNNING, 6L);
expectedTargetCountStatus.put(TotalTargetCountStatus.Status.NOTSTARTED, 9L); expectedTargetCountStatus.put(TotalTargetCountStatus.Status.FINISHED, 9L);
validateRolloutActionStatus(rolloutTwo.getId(), expectedTargetCountStatus); validateRolloutActionStatus(rolloutTwo.getId(), expectedTargetCountStatus);
changeStatusForAllRunningActions(rolloutTwo, Status.FINISHED); changeStatusForAllRunningActions(rolloutTwo, Status.FINISHED);
final Page<Target> targetPage = targetManagement.findTargetByUpdateStatus(pageReq, TargetUpdateStatus.IN_SYNC); 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, final Rollout rolloutA = createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, amountGroups,
successCondition, errorCondition, "RolloutA", "RolloutA"); successCondition, errorCondition, "RolloutA", "RolloutA");
rolloutManagement.startRollout(rolloutA); rolloutManagement.startRollout(rolloutA);
rolloutManagement.checkStartingRollouts(0);
final int amountTargetsForRollout2 = 10; final int amountTargetsForRollout2 = 10;
final int amountGroups2 = 2; final int amountGroups2 = 2;
final Rollout rolloutB = createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout2, amountGroups2, final Rollout rolloutB = createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout2, amountGroups2,
successCondition, errorCondition, "RolloutB", "RolloutB"); successCondition, errorCondition, "RolloutB", "RolloutB");
rolloutManagement.startRollout(rolloutB); rolloutManagement.startRollout(rolloutB);
rolloutManagement.checkStartingRollouts(0);
changeStatusForAllRunningActions(rolloutB, Status.FINISHED); changeStatusForAllRunningActions(rolloutB, Status.FINISHED);
rolloutManagement.checkRunningRollouts(0); rolloutManagement.checkRunningRollouts(0);
@@ -743,6 +780,8 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
final Rollout rolloutC = createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout3, amountGroups3, final Rollout rolloutC = createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout3, amountGroups3,
successCondition, errorCondition, "RolloutC", "RolloutC"); successCondition, errorCondition, "RolloutC", "RolloutC");
rolloutManagement.startRollout(rolloutC); rolloutManagement.startRollout(rolloutC);
rolloutManagement.checkStartingRollouts(0);
changeStatusForAllRunningActions(rolloutC, Status.ERROR); changeStatusForAllRunningActions(rolloutC, Status.ERROR);
rolloutManagement.checkRunningRollouts(0); rolloutManagement.checkRunningRollouts(0);
@@ -751,6 +790,8 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
final Rollout rolloutD = createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout4, amountGroups4, final Rollout rolloutD = createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout4, amountGroups4,
successCondition, errorCondition, "RolloutD", "RolloutD"); successCondition, errorCondition, "RolloutD", "RolloutD");
rolloutManagement.startRollout(rolloutD); rolloutManagement.startRollout(rolloutD);
rolloutManagement.checkStartingRollouts(0);
changeStatusForRunningActions(rolloutD, Status.ERROR, 1); changeStatusForRunningActions(rolloutD, Status.ERROR, 1);
rolloutManagement.checkRunningRollouts(0); rolloutManagement.checkRunningRollouts(0);
changeStatusForAllRunningActions(rolloutD, Status.FINISHED); changeStatusForAllRunningActions(rolloutD, Status.FINISHED);
@@ -882,6 +923,10 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
Rollout myRollout = createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, amountGroups, Rollout myRollout = createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, amountGroups,
successCondition, errorCondition, rolloutName, rolloutName); successCondition, errorCondition, rolloutName, rolloutName);
rolloutManagement.startRollout(myRollout); rolloutManagement.startRollout(myRollout);
// Run here, because scheduler is disabled during tests
rolloutManagement.checkStartingRollouts(0);
changeStatusForRunningActions(myRollout, Status.FINISHED, 2); changeStatusForRunningActions(myRollout, Status.FINISHED, 2);
rolloutManagement.checkRunningRollouts(0); rolloutManagement.checkRunningRollouts(0);
myRollout = rolloutManagement.findRolloutById(myRollout.getId()); myRollout = rolloutManagement.findRolloutById(myRollout.getId());
@@ -924,6 +969,10 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
final String rsqlParam = "controllerId==*MyRoll*"; final String rsqlParam = "controllerId==*MyRoll*";
rolloutManagement.startRollout(myRollout); rolloutManagement.startRollout(myRollout);
// Run here, because scheduler is disabled during tests
rolloutManagement.checkStartingRollouts(0);
myRollout = rolloutManagement.findRolloutById(myRollout.getId()); myRollout = rolloutManagement.findRolloutById(myRollout.getId());
final List<RolloutGroup> rolloutGroups = myRollout.getRolloutGroups(); final List<RolloutGroup> rolloutGroups = myRollout.getRolloutGroups();
@@ -948,8 +997,101 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
} }
@Test @Test
@Description("Verify the creation and the start of a rollout in asynchronous mode.") @Description("Verify the creation of a Rollout without targets throws an Exception.")
public void createAndStartRolloutInAsync() throws 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 amountTargetsForRollout = 500;
final int amountGroups = 5; final int amountGroups = 5;
@@ -960,28 +1102,18 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
final DistributionSet distributionSet = testdataFactory.createDistributionSet("dsFor" + rolloutName); final DistributionSet distributionSet = testdataFactory.createDistributionSet("dsFor" + rolloutName);
targetManagement.createTargets( targetManagement.createTargets(
testdataFactory.generateTargets(amountTargetsForRollout, targetPrefixName + "-", targetPrefixName)); 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( SuccessConditionRolloutStatus conditionRolloutTargetCount = new SuccessConditionRolloutStatus(
RolloutStatus.READY); RolloutStatus.RUNNING);
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);
assertThat(MultipleInvokeHelper.doWithTimeout(new RolloutStatusCallable(myRollout.getId()), assertThat(MultipleInvokeHelper.doWithTimeout(new RolloutStatusCallable(myRollout.getId()),
conditionRolloutTargetCount, 15000, 500)).as("Rollout status").isNotNull(); conditionRolloutTargetCount, 15000, 500)).as("Rollout status").isNotNull();
@@ -993,6 +1125,198 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
validateRolloutActionStatus(myRollout.getId(), expectedTargetCountStatus); 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, private void validateRolloutGroupActionStatus(final RolloutGroup rolloutGroup,
final Map<TotalTargetCountStatus.Status, Long> expectedTargetCountStatus) { final Map<TotalTargetCountStatus.Status, Long> expectedTargetCountStatus) {
final RolloutGroup rolloutGroupWithDetail = rolloutGroupManagement final RolloutGroup rolloutGroupWithDetail = rolloutGroupManagement
@@ -1052,7 +1376,12 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
rolloutToCreate.setDescription(rolloutDescription); rolloutToCreate.setDescription(rolloutDescription);
rolloutToCreate.setTargetFilterQuery(filterQuery); rolloutToCreate.setTargetFilterQuery(filterQuery);
rolloutToCreate.setDistributionSet(distributionSet); 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) { private int changeStatusForAllRunningActions(final Rollout rollout, final Status status) {

View File

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

View File

@@ -16,6 +16,7 @@ import java.util.stream.Collectors;
import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomStringUtils;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetType; 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.RolloutGroupConditions;
import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType; import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
@@ -398,7 +399,13 @@ public abstract class JsonBuilder {
} }
public static String rollout(final String name, final String description, final int groupSize, public static String rollout(final String name, final String description, final int groupSize,
final long distributionSetId, final String targetFilterQuery, final RolloutGroupConditions conditions) { 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(); final JSONObject json = new JSONObject();
json.put("name", name); json.put("name", name);
json.put("description", description); json.put("description", description);
@@ -410,22 +417,64 @@ public abstract class JsonBuilder {
final JSONObject successCondition = new JSONObject(); final JSONObject successCondition = new JSONObject();
json.put("successCondition", successCondition); json.put("successCondition", successCondition);
successCondition.put("condition", conditions.getSuccessCondition().toString()); successCondition.put("condition", conditions.getSuccessCondition().toString());
successCondition.put("expression", conditions.getSuccessConditionExp().toString()); successCondition.put("expression", conditions.getSuccessConditionExp());
final JSONObject successAction = new JSONObject(); final JSONObject successAction = new JSONObject();
json.put("successAction", successAction); json.put("successAction", successAction);
successAction.put("action", conditions.getSuccessAction().toString()); successAction.put("action", conditions.getSuccessAction().toString());
successAction.put("expression", conditions.getSuccessActionExp().toString()); successAction.put("expression", conditions.getSuccessActionExp());
final JSONObject errorCondition = new JSONObject(); final JSONObject errorCondition = new JSONObject();
json.put("errorCondition", errorCondition); json.put("errorCondition", errorCondition);
errorCondition.put("condition", conditions.getErrorCondition().toString()); errorCondition.put("condition", conditions.getErrorCondition().toString());
errorCondition.put("expression", conditions.getErrorConditionExp().toString()); errorCondition.put("expression", conditions.getErrorConditionExp());
final JSONObject errorAction = new JSONObject(); final JSONObject errorAction = new JSONObject();
json.put("errorAction", errorAction); json.put("errorAction", errorAction);
errorAction.put("action", conditions.getErrorAction().toString()); 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(); return json.toString();

View File

@@ -491,7 +491,7 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
rolloutToCreate.setActionType(getActionType()); rolloutToCreate.setActionType(getActionType());
rolloutToCreate.setForcedTime(getForcedTimeStamp()); rolloutToCreate.setForcedTime(getForcedTimeStamp());
rolloutToCreate = rolloutManagement.createRolloutAsync(rolloutToCreate, amountGroup, conditions); rolloutToCreate = rolloutManagement.createRollout(rolloutToCreate, amountGroup, conditions);
return rolloutToCreate; return rolloutToCreate;
} }

View File

@@ -414,7 +414,7 @@ public class RolloutListGrid extends AbstractGrid {
final String rolloutName = (String) row.getItemProperty(SPUILabelDefinitions.VAR_NAME).getValue(); final String rolloutName = (String) row.getItemProperty(SPUILabelDefinitions.VAR_NAME).getValue();
if (RolloutStatus.READY.equals(rolloutStatus)) { if (RolloutStatus.READY.equals(rolloutStatus)) {
rolloutManagement.startRolloutAsync(rolloutManagement.findRolloutByName(rolloutName)); rolloutManagement.startRollout(rolloutManagement.findRolloutByName(rolloutName));
uiNotification.displaySuccess(i18n.get("message.rollout.started", rolloutName)); uiNotification.displaySuccess(i18n.get("message.rollout.started", rolloutName));
return; return;
} }