Initial Contribution of the rollout-management feature

- Repository functionality for rollout, rolloutgroup entities
- Rollout scheduler to watch and handle running rollouts and start next
group of rollout
- Vaadin view to administrate rollouts and reflect the current rollout
status
- REST resources to cover rollout creation, updating, starting, pausing
and resuming

Signed-off-by: Michael Hirsch <michael.hirsch@bosch-si.com>
This commit is contained in:
Michael Hirsch
2016-02-04 15:10:44 +01:00
parent 5331a9c06e
commit f18825ce34
145 changed files with 13995 additions and 353 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,21 @@
/**
* Copyright (c) 2011-2015 Bosch Software Innovations GmbH, Germany. All rights reserved.
*/
package org.eclipse.hawkbit.rest.resource;
/**
*
* @author Dennis Melzer
*
* @param <T>
*/
public interface SuccessCondition<T> {
/**
*
* @param result
* @return
*/
boolean success(final T result);
}