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();
// start the created Rollout
rolloutResource.start(rolloutResponseBody.getRolloutId(), true);
rolloutResource.start(rolloutResponseBody.getRolloutId());
// wait until rollout is complete
do {

View File

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

View File

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

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;
/**
*
* An action that runs when the error condition is met
*/
@JsonInclude(Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class MgmtRolloutErrorAction {
private ErrorAction action = ErrorAction.PAUSE;
private String expression = null;
private String expression;
/**
* Creates a rollout error action
*
* @param action
* the action to run when th error condition is met
* @param expression
* the expression for the action
*/
public MgmtRolloutErrorAction(ErrorAction action, String expression) {
this.action = action;
this.expression = expression;
}
/**
* Default constructor
*/
public MgmtRolloutErrorAction() {
// Instantiate default error action
}
/**
* @return the action
@@ -52,6 +72,9 @@ public class MgmtRolloutErrorAction {
this.expression = expression;
}
/**
* Possible actions
*/
public enum ErrorAction {
PAUSE;
}

View File

@@ -8,12 +8,14 @@
*/
package org.eclipse.hawkbit.mgmt.json.model.rollout;
import org.eclipse.hawkbit.mgmt.json.model.MgmtNamedEntity;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtActionType;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import org.eclipse.hawkbit.mgmt.json.model.rolloutgroup.MgmtRolloutGroup;
import java.util.List;
/**
* Model for request containing a rollout body e.g. in a POST request of
@@ -21,66 +23,18 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include;
*/
@JsonInclude(Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class MgmtRolloutRestRequestBody extends MgmtNamedEntity {
public class MgmtRolloutRestRequestBody extends AbstractMgmtRolloutConditionsEntity {
private String targetFilterQuery;
private long distributionSetId;
private int amountGroups = 1;
private MgmtRolloutCondition successCondition = new MgmtRolloutCondition();
private MgmtRolloutSuccessAction successAction = new MgmtRolloutSuccessAction();
private MgmtRolloutCondition errorCondition = null;
private MgmtRolloutErrorAction errorAction = null;
private Integer amountGroups;
private Long forcetime;
private MgmtActionType type;
/**
* @return the finishCondition
*/
public MgmtRolloutCondition getSuccessCondition() {
return successCondition;
}
/**
* @param successCondition
* the finishCondition to set
*/
public void setSuccessCondition(final MgmtRolloutCondition successCondition) {
this.successCondition = successCondition;
}
/**
* @return the successAction
*/
public MgmtRolloutSuccessAction getSuccessAction() {
return successAction;
}
/**
* @param successAction
* the successAction to set
*/
public void setSuccessAction(final MgmtRolloutSuccessAction successAction) {
this.successAction = successAction;
}
/**
* @return the errorCondition
*/
public MgmtRolloutCondition getErrorCondition() {
return errorCondition;
}
/**
* @param errorCondition
* the errorCondition to set
*/
public void setErrorCondition(final MgmtRolloutCondition errorCondition) {
this.errorCondition = errorCondition;
}
private List<MgmtRolloutGroup> groups;
/**
* @return the targetFilterQuery
@@ -115,7 +69,7 @@ public class MgmtRolloutRestRequestBody extends MgmtNamedEntity {
/**
* @return the groupSize
*/
public int getAmountGroups() {
public Integer getAmountGroups() {
return amountGroups;
}
@@ -123,7 +77,7 @@ public class MgmtRolloutRestRequestBody extends MgmtNamedEntity {
* @param groupSize
* the groupSize to set
*/
public void setAmountGroups(final int groupSize) {
public void setAmountGroups(final Integer groupSize) {
this.amountGroups = groupSize;
}
@@ -158,18 +112,16 @@ public class MgmtRolloutRestRequestBody extends MgmtNamedEntity {
}
/**
* @return the errorAction
* @return the List of defined Groups
*/
public MgmtRolloutErrorAction getErrorAction() {
return errorAction;
public List<MgmtRolloutGroup> getGroups() {
return groups;
}
/**
* @param errorAction
* the errorAction to set
* @param groups List of {@link MgmtRolloutGroup}
*/
public void setErrorAction(final MgmtRolloutErrorAction errorAction) {
this.errorAction = errorAction;
public void setGroups(List<MgmtRolloutGroup> groups) {
this.groups = groups;
}
}

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

View File

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

View File

@@ -15,15 +15,20 @@ import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutCondition;
import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutCondition.Condition;
import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutErrorAction;
import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutErrorAction.ErrorAction;
import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutResponseBody;
import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutRestRequestBody;
import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutSuccessAction;
import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutSuccessAction.SuccessAction;
import org.eclipse.hawkbit.mgmt.json.model.rolloutgroup.MgmtRolloutGroup;
import org.eclipse.hawkbit.mgmt.json.model.rolloutgroup.MgmtRolloutGroupResponseBody;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRolloutRestApi;
import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.exception.ConstraintViolationException;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Rollout;
@@ -76,8 +81,7 @@ final class MgmtRolloutMapper {
}
body.add(linkTo(methodOn(MgmtRolloutRestApi.class).getRollout(rollout.getId())).withRel("self"));
body.add(linkTo(methodOn(MgmtRolloutRestApi.class).start(rollout.getId(), false)).withRel("start"));
body.add(linkTo(methodOn(MgmtRolloutRestApi.class).start(rollout.getId(), true)).withRel("startAsync"));
body.add(linkTo(methodOn(MgmtRolloutRestApi.class).start(rollout.getId())).withRel("start"));
body.add(linkTo(methodOn(MgmtRolloutRestApi.class).pause(rollout.getId())).withRel("pause"));
body.add(linkTo(methodOn(MgmtRolloutRestApi.class).resume(rollout.getId())).withRel("resume"));
body.add(linkTo(methodOn(MgmtRolloutRestApi.class).getRolloutGroups(rollout.getId(),
@@ -88,12 +92,12 @@ final class MgmtRolloutMapper {
}
static Rollout fromRequest(final EntityFactory entityFactory, final MgmtRolloutRestRequestBody restRequest,
final DistributionSet distributionSet, final String filterQuery) {
final DistributionSet distributionSet) {
final Rollout rollout = entityFactory.generateRollout();
rollout.setName(restRequest.getName());
rollout.setDescription(restRequest.getDescription());
rollout.setDistributionSet(distributionSet);
rollout.setTargetFilterQuery(filterQuery);
rollout.setTargetFilterQuery(restRequest.getTargetFilterQuery());
final ActionType convertActionType = MgmtRestModelMapper.convertActionType(restRequest.getType());
if (convertActionType != null) {
rollout.setActionType(convertActionType);
@@ -105,6 +109,44 @@ final class MgmtRolloutMapper {
return rollout;
}
static RolloutGroup fromRequest(final EntityFactory entityFactory, final MgmtRolloutGroup restRequest) {
final RolloutGroup group = entityFactory.generateRolloutGroup();
group.setName(restRequest.getName());
group.setDescription(restRequest.getDescription());
if (restRequest.getTargetFilterQuery() != null) {
group.setTargetFilterQuery(restRequest.getTargetFilterQuery());
}
final Float targetPercentage = restRequest.getTargetPercentage();
if (targetPercentage == null) {
group.setTargetPercentage(100);
} else if (targetPercentage <= 0 || targetPercentage > 100) {
throw new ConstraintViolationException("Target percentage out of Range >0 - 100.");
} else {
group.setTargetPercentage(restRequest.getTargetPercentage());
}
if (restRequest.getSuccessCondition() != null) {
group.setSuccessCondition(mapFinishCondition(restRequest.getSuccessCondition().getCondition()));
group.setSuccessConditionExp(restRequest.getSuccessCondition().getExpression());
}
if (restRequest.getSuccessAction() != null) {
group.setSuccessAction(map(restRequest.getSuccessAction().getAction()));
group.setSuccessActionExp(restRequest.getSuccessAction().getExpression());
}
if (restRequest.getErrorCondition() != null) {
group.setErrorCondition(mapErrorCondition(restRequest.getErrorCondition().getCondition()));
group.setErrorConditionExp(restRequest.getErrorCondition().getExpression());
}
if (restRequest.getErrorAction() != null) {
group.setErrorAction(map(restRequest.getErrorAction().getAction()));
group.setErrorActionExp(restRequest.getErrorAction().getExpression());
}
return group;
}
static List<MgmtRolloutGroupResponseBody> toResponseRolloutGroup(final List<RolloutGroup> rollouts) {
if (rollouts == null) {
return Collections.emptyList();
@@ -123,6 +165,25 @@ final class MgmtRolloutMapper {
body.setName(rolloutGroup.getName());
body.setRolloutGroupId(rolloutGroup.getId());
body.setStatus(rolloutGroup.getStatus().toString().toLowerCase());
body.setTargetPercentage(rolloutGroup.getTargetPercentage());
body.setTargetFilterQuery(rolloutGroup.getTargetFilterQuery());
body.setTotalTargets(rolloutGroup.getTotalTargets());
body.setSuccessCondition(new MgmtRolloutCondition(map(rolloutGroup.getSuccessCondition()),
rolloutGroup.getSuccessConditionExp()));
body.setSuccessAction(
new MgmtRolloutSuccessAction(map(rolloutGroup.getSuccessAction()), rolloutGroup.getSuccessActionExp()));
body.setErrorCondition(
new MgmtRolloutCondition(map(rolloutGroup.getErrorCondition()), rolloutGroup.getErrorConditionExp()));
body.setErrorAction(
new MgmtRolloutErrorAction(map(rolloutGroup.getErrorAction()), rolloutGroup.getErrorActionExp()));
for (final TotalTargetCountStatus.Status status : TotalTargetCountStatus.Status.values()) {
body.getTotalTargetsPerStatus().put(status.name().toLowerCase(),
rolloutGroup.getTotalTargetCountStatus().getTotalTargetCountByStatus(status));
}
body.add(linkTo(methodOn(MgmtRolloutRestApi.class).getRolloutGroup(rolloutGroup.getRollout().getId(),
rolloutGroup.getId())).withRel("self"));
return body;
@@ -149,6 +210,13 @@ final class MgmtRolloutMapper {
throw new IllegalArgumentException("Rollout group condition " + rolloutCondition + NOT_SUPPORTED);
}
static Condition map(final RolloutGroupErrorCondition rolloutCondition) {
if (RolloutGroupErrorCondition.THRESHOLD.equals(rolloutCondition)) {
return Condition.THRESHOLD;
}
throw new IllegalArgumentException("Rollout group condition " + rolloutCondition + NOT_SUPPORTED);
}
static RolloutGroupErrorAction map(final ErrorAction action) {
if (ErrorAction.PAUSE.equals(action)) {
return RolloutGroupErrorAction.PAUSE;
@@ -163,6 +231,20 @@ final class MgmtRolloutMapper {
throw new IllegalArgumentException("Success Action " + action + NOT_SUPPORTED);
}
static SuccessAction map(final RolloutGroupSuccessAction successAction) {
if (RolloutGroupSuccessAction.NEXTGROUP.equals(successAction)) {
return SuccessAction.NEXTGROUP;
}
throw new IllegalArgumentException("Rollout group success action " + successAction + NOT_SUPPORTED);
}
static ErrorAction map(final RolloutGroupErrorAction errorAction) {
if (RolloutGroupErrorAction.PAUSE.equals(errorAction)) {
return ErrorAction.PAUSE;
}
throw new IllegalArgumentException("Rollout group error action " + errorAction + NOT_SUPPORTED);
}
private static String createIllegalArgumentLiteral(final Condition condition) {
return "Condition " + condition + NOT_SUPPORTED;
}

View File

@@ -9,6 +9,7 @@
package org.eclipse.hawkbit.mgmt.rest.resource;
import java.util.List;
import java.util.stream.Collectors;
import org.eclipse.hawkbit.mgmt.json.model.PagedList;
import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutResponseBody;
@@ -24,6 +25,7 @@ import org.eclipse.hawkbit.repository.RolloutGroupManagement;
import org.eclipse.hawkbit.repository.RolloutManagement;
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.RolloutVerificationException;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
@@ -143,23 +145,30 @@ public class MgmtRolloutResource implements MgmtRolloutRestApi {
.successCondition(successCondition, successConditionExpr)
.successAction(successAction, successActionExpr).errorCondition(errorCondition, errorConditionExpr)
.errorAction(errorAction, errorActionExpr).build();
final Rollout rollout = this.rolloutManagement.createRollout(
MgmtRolloutMapper.fromRequest(entityFactory, rolloutRequestBody, distributionSet,
rolloutRequestBody.getTargetFilterQuery()),
rolloutRequestBody.getAmountGroups(), rolloutGroupConditions);
Rollout rollout = MgmtRolloutMapper.fromRequest(entityFactory, rolloutRequestBody, distributionSet);
if (rolloutRequestBody.getGroups() != null) {
final List<RolloutGroup> rolloutGroups = rolloutRequestBody.getGroups().stream()
.map(mgmtRolloutGroup -> MgmtRolloutMapper.fromRequest(entityFactory, mgmtRolloutGroup))
.collect(Collectors.toList());
rollout = rolloutManagement.createRollout(rollout, rolloutGroups, rolloutGroupConditions);
} else if (rolloutRequestBody.getAmountGroups() != null) {
rollout = rolloutManagement.createRollout(rollout, rolloutRequestBody.getAmountGroups(),
rolloutGroupConditions);
} else {
throw new RolloutVerificationException("Either 'amountGroups' or 'groups' must be defined in the request");
}
return ResponseEntity.status(HttpStatus.CREATED).body(MgmtRolloutMapper.toResponseRollout(rollout));
}
@Override
public ResponseEntity<Void> start(@PathVariable("rolloutId") final Long rolloutId,
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_ASYNC, defaultValue = "false") final boolean startAsync) {
public ResponseEntity<Void> start(@PathVariable("rolloutId") final Long rolloutId) {
final Rollout rollout = findRolloutOrThrowException(rolloutId);
if (startAsync) {
this.rolloutManagement.startRolloutAsync(rollout);
} else {
this.rolloutManagement.startRollout(rollout);
}
return ResponseEntity.ok().build();
}

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.status;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
@@ -30,6 +31,7 @@ import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition;
import org.eclipse.hawkbit.repository.model.RolloutGroupConditionBuilder;
import org.eclipse.hawkbit.repository.model.RolloutGroupConditions;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.test.util.WithUser;
import org.eclipse.hawkbit.rest.AbstractRestIntegrationTest;
@@ -117,8 +119,85 @@ public class MgmtRolloutResourceTest extends AbstractRestIntegrationTest {
@Test
@Description("Testing that rollout can be created")
public void createRollout() throws Exception {
targetManagement.createTargets(testdataFactory.generateTargets(20, "target", "rollout"));
final DistributionSet dsA = testdataFactory.createDistributionSet("");
postRollout("rollout1", 10, dsA.getId(), "name==target1");
postRollout("rollout1", 10, dsA.getId(), "id==target*");
}
@Test
@Description("Testing that rollout can be created with groups")
public void createRolloutWithGroupsDefinitions() throws Exception {
final DistributionSet dsA = testdataFactory.createDistributionSet("ro");
final int amountTargets = 10;
targetManagement.createTargets(testdataFactory.generateTargets(amountTargets, "ro-target", "rollout"));
List<RolloutGroup> rolloutGroups = new ArrayList<>(2);
final int percentTargetsInGroup1 = 20;
final int percentTargetsInGroup2 = 100;
RolloutGroup group1 = entityFactory.generateRolloutGroup();
group1.setName("Group1");
group1.setDescription("Group1desc");
group1.setTargetPercentage(percentTargetsInGroup1);
rolloutGroups.add(group1);
RolloutGroup group2 = entityFactory.generateRolloutGroup();
group2.setName("Group2");
group2.setDescription("Group2desc");
group2.setTargetPercentage(percentTargetsInGroup2);
rolloutGroups.add(group2);
RolloutGroupConditions rolloutGroupConditions = new RolloutGroupConditionBuilder().build();
mvc.perform(post("/rest/v1/rollouts")
.content(JsonBuilder.rollout("rollout2", "desc", null, dsA.getId(), "id==ro-target*",
rolloutGroupConditions, rolloutGroups))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isCreated()).andReturn();
}
@Test
@Description("Testing that no rollout with groups that have illegal percentages can be created")
public void createRolloutWithIllegalPercentages() throws Exception {
final DistributionSet dsA = testdataFactory.createDistributionSet("ro2");
final int amountTargets = 10;
targetManagement.createTargets(testdataFactory.generateTargets(amountTargets, "ro-target", "rollout"));
List<RolloutGroup> rolloutGroups = new ArrayList<>(2);
RolloutGroup group1 = entityFactory.generateRolloutGroup();
group1.setName("Group1");
group1.setDescription("Group1desc");
group1.setTargetPercentage(0);
rolloutGroups.add(group1);
RolloutGroup group2 = entityFactory.generateRolloutGroup();
group2.setName("Group2");
group2.setDescription("Group2desc");
group2.setTargetPercentage(100);
rolloutGroups.add(group2);
RolloutGroupConditions rolloutGroupConditions = new RolloutGroupConditionBuilder().build();
mvc.perform(post("/rest/v1/rollouts")
.content(JsonBuilder.rollout("rollout4", "desc", null, dsA.getId(), "id==ro-target*",
rolloutGroupConditions, rolloutGroups))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isInternalServerError())
.andExpect(jsonPath("$.errorCode", equalTo("hawkbit.server.error.repo.constraintViolation")));
group1.setTargetPercentage(101);
mvc.perform(post("/rest/v1/rollouts")
.content(JsonBuilder.rollout("rollout4", "desc", null, dsA.getId(), "id==ro-target*",
rolloutGroupConditions, rolloutGroups))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isInternalServerError())
.andExpect(jsonPath("$.errorCode", equalTo("hawkbit.server.error.repo.constraintViolation")));
}
@Test
@@ -133,20 +212,26 @@ public class MgmtRolloutResourceTest extends AbstractRestIntegrationTest {
@Description("Testing that rollout paged list contains rollouts")
public void rolloutPagedListContainsAllRollouts() throws Exception {
final DistributionSet dsA = testdataFactory.createDistributionSet("");
targetManagement.createTargets(testdataFactory.generateTargets(20, "target", "rollout"));
// setup - create 2 rollouts
postRollout("rollout1", 10, dsA.getId(), "name==target1");
postRollout("rollout2", 5, dsA.getId(), "name==target2");
postRollout("rollout1", 10, dsA.getId(), "id==target*");
postRollout("rollout2", 5, dsA.getId(), "id==target-0001*");
// Run here, because Scheduler is disabled during tests
rolloutManagement.checkCreatingRollouts(0);
mvc.perform(get("/rest/v1/rollouts")).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
.andExpect(jsonPath("$.content", hasSize(2))).andExpect(jsonPath("$.total", equalTo(2)))
.andExpect(jsonPath("content[0].name", equalTo("rollout1")))
.andExpect(jsonPath("content[0].status", equalTo("ready")))
.andExpect(jsonPath("content[0].targetFilterQuery", equalTo("name==target1")))
.andExpect(jsonPath("content[0].targetFilterQuery", equalTo("id==target*")))
.andExpect(jsonPath("content[0].distributionSetId", equalTo(dsA.getId().intValue())))
.andExpect(jsonPath("content[1].name", equalTo("rollout2")))
.andExpect(jsonPath("content[1].status", equalTo("ready")))
.andExpect(jsonPath("content[1].targetFilterQuery", equalTo("name==target2")))
.andExpect(jsonPath("content[1].targetFilterQuery", equalTo("id==target-0001*")))
.andExpect(jsonPath("content[1].distributionSetId", equalTo(dsA.getId().intValue())));
}
@@ -154,9 +239,15 @@ public class MgmtRolloutResourceTest extends AbstractRestIntegrationTest {
@Description("Testing that rollout paged list is limited by the query param limit")
public void rolloutPagedListIsLimitedToQueryParam() throws Exception {
final DistributionSet dsA = testdataFactory.createDistributionSet("");
targetManagement.createTargets(testdataFactory.generateTargets(20, "target", "rollout"));
// setup - create 2 rollouts
postRollout("rollout1", 10, dsA.getId(), "name==target1");
postRollout("rollout2", 5, dsA.getId(), "name==target2");
postRollout("rollout1", 10, dsA.getId(), "id==target*");
postRollout("rollout2", 5, dsA.getId(), "id==target*");
// Run here, because Scheduler is disabled during tests
rolloutManagement.checkCreatingRollouts(0);
mvc.perform(get("/rest/v1/rollouts?limit=1")).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
@@ -186,7 +277,7 @@ public class MgmtRolloutResourceTest extends AbstractRestIntegrationTest {
}
@Test
@Description("Testing that starting the rollout switches the state to running")
@Description("Testing that starting the rollout switches the state to starting and then to running")
public void startingRolloutSwitchesIntoRunningState() throws Exception {
// setup
final int amountTargets = 20;
@@ -200,6 +291,15 @@ public class MgmtRolloutResourceTest extends AbstractRestIntegrationTest {
mvc.perform(post("/rest/v1/rollouts/{rolloutId}/start", rollout.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
// check rollout is in starting state
mvc.perform(get("/rest/v1/rollouts/{rolloutId}", rollout.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
.andExpect(jsonPath("id", equalTo(rollout.getId().intValue())))
.andExpect(jsonPath("status", equalTo("starting")));
// Run here, because scheduler is disabled during tests
rolloutManagement.checkStartingRollouts(0);
// check rollout is in running state
mvc.perform(get("/rest/v1/rollouts/{rolloutId}", rollout.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
@@ -222,6 +322,9 @@ public class MgmtRolloutResourceTest extends AbstractRestIntegrationTest {
mvc.perform(post("/rest/v1/rollouts/{rolloutId}/start", rollout.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
// Run here, because scheduler is disabled during tests
rolloutManagement.checkStartingRollouts(0);
// pausing rollout
mvc.perform(post("/rest/v1/rollouts/{rolloutId}/pause", rollout.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
@@ -248,6 +351,9 @@ public class MgmtRolloutResourceTest extends AbstractRestIntegrationTest {
mvc.perform(post("/rest/v1/rollouts/{rolloutId}/start", rollout.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
// Run here, because scheduler is disabled during tests
rolloutManagement.checkStartingRollouts(0);
// pausing rollout
mvc.perform(post("/rest/v1/rollouts/{rolloutId}/pause", rollout.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
@@ -278,6 +384,9 @@ public class MgmtRolloutResourceTest extends AbstractRestIntegrationTest {
mvc.perform(post("/rest/v1/rollouts/{rolloutId}/start", rollout.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
// Run here, because scheduler is disabled during tests
rolloutManagement.checkStartingRollouts(0);
// starting rollout - already started should lead into bad request
mvc.perform(post("/rest/v1/rollouts/{rolloutId}/start", rollout.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isBadRequest())
@@ -316,6 +425,9 @@ public class MgmtRolloutResourceTest extends AbstractRestIntegrationTest {
mvc.perform(post("/rest/v1/rollouts/{rolloutId}/start", rollout.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
// Run here, because scheduler is disabled during tests
rolloutManagement.checkStartingRollouts(0);
// retrieve rollout groups from created rollout - 2 groups exists
// (amountTargets / groupSize = 2)
mvc.perform(get("/rest/v1/rollouts/{rolloutId}/deploygroups?sort=ID:ASC", rollout.getId()))
@@ -409,6 +521,9 @@ public class MgmtRolloutResourceTest extends AbstractRestIntegrationTest {
rolloutManagement.startRollout(rollout);
// Run here, because scheduler is disabled during tests
rolloutManagement.checkStartingRollouts(0);
final RolloutGroup firstGroup = rolloutGroupManagement
.findRolloutGroupsByRolloutId(rollout.getId(), new PageRequest(0, 1, Direction.ASC, "id")).getContent()
.get(0);
@@ -432,10 +547,12 @@ public class MgmtRolloutResourceTest extends AbstractRestIntegrationTest {
final Rollout rollout = createRollout("rollout1", 4, dsA.getId(), "controllerId==rollout*");
// starting rollout
mvc.perform(post("/rest/v1/rollouts/{rolloutId}/start", rollout.getId())
.param(MgmtRestConstants.REQUEST_PARAMETER_ASYNC, "true")).andDo(MockMvcResultPrinter.print())
mvc.perform(post("/rest/v1/rollouts/{rolloutId}/start", rollout.getId())).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
// Run here, because scheduler is disabled during tests
rolloutManagement.checkStartingRollouts(0);
// check if running
assertThat(doWithTimeout(() -> getRollout(rollout.getId()), result -> success(result), 60_000, 100))
.isNotNull();
@@ -549,19 +666,25 @@ public class MgmtRolloutResourceTest extends AbstractRestIntegrationTest {
private void postRollout(final String name, final int groupSize, final long distributionSetId,
final String targetFilterQuery) throws Exception {
mvc.perform(post("/rest/v1/rollouts")
.content(JsonBuilder.rollout(name, "desc", groupSize, distributionSetId, targetFilterQuery, null))
.content(JsonBuilder.rollout(name, "desc", groupSize, distributionSetId, targetFilterQuery,
new RolloutGroupConditionBuilder().build()))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isCreated()).andReturn();
}
private Rollout createRollout(final String name, final int amountGroups, final long distributionSetId,
final String targetFilterQuery) {
final Rollout rollout = entityFactory.generateRollout();
Rollout rollout = entityFactory.generateRollout();
rollout.setDistributionSet(distributionSetManagement.findDistributionSetById(distributionSetId));
rollout.setName(name);
rollout.setTargetFilterQuery(targetFilterQuery);
return rolloutManagement.createRollout(rollout, amountGroups, new RolloutGroupConditionBuilder()
rollout = rolloutManagement.createRollout(rollout, amountGroups, new RolloutGroupConditionBuilder()
.successCondition(RolloutGroupSuccessCondition.THRESHOLD, "100").build());
// Run here, because Scheduler is disabled during tests
rolloutManagement.fillRolloutGroupsWithTargets(rolloutManagement.findRolloutById(rollout.getId()));
return rolloutManagement.findRolloutById(rollout.getId());
}
protected boolean success(final Rollout result) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -16,6 +16,7 @@ import java.util.stream.Collectors;
import org.apache.commons.lang3.RandomStringUtils;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.RolloutGroupConditions;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
@@ -399,6 +400,12 @@ public abstract class JsonBuilder {
public static String rollout(final String name, final String description, final int groupSize,
final long distributionSetId, final String targetFilterQuery, final RolloutGroupConditions conditions) {
return rollout(name, description, groupSize, distributionSetId, targetFilterQuery, conditions, null);
}
public static String rollout(final String name, final String description, final Integer groupSize,
final long distributionSetId, final String targetFilterQuery, final RolloutGroupConditions conditions,
final List<RolloutGroup> groups) {
final JSONObject json = new JSONObject();
json.put("name", name);
json.put("description", description);
@@ -410,22 +417,64 @@ public abstract class JsonBuilder {
final JSONObject successCondition = new JSONObject();
json.put("successCondition", successCondition);
successCondition.put("condition", conditions.getSuccessCondition().toString());
successCondition.put("expression", conditions.getSuccessConditionExp().toString());
successCondition.put("expression", conditions.getSuccessConditionExp());
final JSONObject successAction = new JSONObject();
json.put("successAction", successAction);
successAction.put("action", conditions.getSuccessAction().toString());
successAction.put("expression", conditions.getSuccessActionExp().toString());
successAction.put("expression", conditions.getSuccessActionExp());
final JSONObject errorCondition = new JSONObject();
json.put("errorCondition", errorCondition);
errorCondition.put("condition", conditions.getErrorCondition().toString());
errorCondition.put("expression", conditions.getErrorConditionExp().toString());
errorCondition.put("expression", conditions.getErrorConditionExp());
final JSONObject errorAction = new JSONObject();
json.put("errorAction", errorAction);
errorAction.put("action", conditions.getErrorAction().toString());
errorAction.put("expression", conditions.getErrorActionExp().toString());
errorAction.put("expression", conditions.getErrorActionExp());
}
if(groups != null) {
final JSONArray jsonGroups = new JSONArray();
for (RolloutGroup group : groups) {
final JSONObject jsonGroup = new JSONObject();
jsonGroup.put("name", group.getName());
jsonGroup.put("description", group.getDescription());
jsonGroup.put("targetFilterQuery", group.getTargetFilterQuery());
jsonGroup.put("targetPercentage", group.getTargetPercentage());
if(group.getSuccessCondition() != null) {
final JSONObject successCondition = new JSONObject();
jsonGroup.put("successCondition", successCondition);
successCondition.put("condition", group.getSuccessCondition().toString());
successCondition.put("expression", group.getSuccessConditionExp());
}
if(group.getSuccessAction() != null) {
final JSONObject successAction = new JSONObject();
jsonGroup.put("successAction", successAction);
successAction.put("action", group.getSuccessAction().toString());
successAction.put("expression", group.getSuccessActionExp());
}
if(group.getErrorCondition() != null) {
final JSONObject errorCondition = new JSONObject();
jsonGroup.put("errorCondition", errorCondition);
errorCondition.put("condition", group.getErrorCondition().toString());
errorCondition.put("expression", group.getErrorConditionExp());
}
if(group.getErrorAction() != null) {
final JSONObject errorAction = new JSONObject();
jsonGroup.put("errorAction", errorAction);
errorAction.put("action", group.getErrorAction().toString());
errorAction.put("expression", group.getErrorActionExp());
}
jsonGroups.put(jsonGroup);
}
json.put("groups", jsonGroups);
}
return json.toString();

View File

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

View File

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