Introduce basic functionality for invalidation of distributionsets (#1179)
* Basic DS invalidation functionality Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com> * Add checks for valid/complete DS Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com> * Stop rollouts + auto assignments when invalidating a DS Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com> * Add methods to count AAs + rollouts for invalidation Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com> * Small refactoring for DS management Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com> * Add invalidation functionality to REST API Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com> * Fix update stopped rollouts status Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com> * Add various tests Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com> * Introduce countActionsForInvalidation Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com> * Fix event tests with incomplete DS Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com> * Add H2 migration script Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com> * Fix action count method Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com> * Fix REST documentation tests Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com> * Change flyway version number Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com> * Add lock for DS invalidation + adapt tests Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com> * Move concurrency test to own class Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com> * Handle possible InterruptedException Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com> * Fix concurrency test Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com> * Use one transaction for all invalidations Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com> * Add invalidate endpoint to REST docu Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com> * Execute invalidation in transaction when actions are cancelled Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com> * Check that distribution set is valid when editing/creating metadata Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com> * Remove all changes in UI Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com> * Add DB migration files for all databases Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com> * Implement review findings Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com> * Move DS invalidation to own class to check permissions for single steps Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com> * Move invalidation count methods to management classes Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com> * Fix failing tests Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>
This commit is contained in:
committed by
GitHub
parent
b25e118e6c
commit
825cb64448
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright (c) 2021 Bosch.IO 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.distributionset;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
|
||||
/**
|
||||
* Definition of the action cancel type for the distribution set invalidation
|
||||
* via REST management API.
|
||||
*
|
||||
*/
|
||||
public enum MgmtCancelationType {
|
||||
/**
|
||||
* Actions will be soft canceled.
|
||||
*/
|
||||
SOFT("soft"),
|
||||
|
||||
/**
|
||||
* Actions will be force quit.
|
||||
*/
|
||||
FORCE("force"),
|
||||
|
||||
/**
|
||||
* No actions will be canceled.
|
||||
*/
|
||||
NONE("none");
|
||||
|
||||
private final String name;
|
||||
|
||||
private MgmtCancelationType(final String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@JsonValue
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -49,6 +49,17 @@ public class MgmtDistributionSet extends MgmtNamedEntity {
|
||||
@JsonProperty
|
||||
private boolean deleted;
|
||||
|
||||
@JsonProperty
|
||||
private boolean valid;
|
||||
|
||||
public boolean isValid() {
|
||||
return valid;
|
||||
}
|
||||
|
||||
public void setValid(final boolean valid) {
|
||||
this.valid = valid;
|
||||
}
|
||||
|
||||
public boolean isDeleted() {
|
||||
return deleted;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright (c) 2021 Bosch.IO 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.distributionset;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* A json annotated rest model for invalidate DistributionSet requests.
|
||||
*
|
||||
*/
|
||||
public class MgmtInvalidateDistributionSetRequestBody {
|
||||
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private MgmtCancelationType actionCancelationType;
|
||||
@JsonProperty
|
||||
private boolean cancelRollouts;
|
||||
|
||||
public MgmtCancelationType getActionCancelationType() {
|
||||
return actionCancelationType;
|
||||
}
|
||||
|
||||
public void setActionCancelationType(final MgmtCancelationType actionCancelationType) {
|
||||
this.actionCancelationType = actionCancelationType;
|
||||
}
|
||||
|
||||
public boolean isCancelRollouts() {
|
||||
return cancelRollouts;
|
||||
}
|
||||
|
||||
public void setCancelRollouts(final boolean cancelRollouts) {
|
||||
this.cancelRollouts = cancelRollouts;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,12 +10,15 @@ package org.eclipse.hawkbit.mgmt.rest.api;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
||||
import org.eclipse.hawkbit.mgmt.json.model.MgmtMetadata;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.MgmtMetadataBodyPut;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.PagedList;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSet;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSetRequestBodyPost;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSetRequestBodyPut;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtInvalidateDistributionSetRequestBody;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtTargetAssignmentRequestBody;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtTargetAssignmentResponseBody;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModule;
|
||||
@@ -378,4 +381,19 @@ public interface MgmtDistributionSetRestApi {
|
||||
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) int pagingOffsetParam,
|
||||
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) int pagingLimitParam,
|
||||
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SORTING, required = false) String sortParam);
|
||||
|
||||
/**
|
||||
* Invalidates a distribution set
|
||||
*
|
||||
* @param distributionSetId
|
||||
* the ID of the distribution set to invalidate
|
||||
* @param invalidateRequestBody
|
||||
* the definition if rollouts and actions should be canceled
|
||||
* @return status OK if the invalidation was successful
|
||||
*/
|
||||
@PostMapping(value = "/{distributionSetId}/invalidate", consumes = { MediaTypes.HAL_JSON_VALUE,
|
||||
MediaType.APPLICATION_JSON_VALUE }, produces = { MediaTypes.HAL_JSON_VALUE,
|
||||
MediaType.APPLICATION_JSON_VALUE })
|
||||
ResponseEntity<Void> invalidateDistributionSet(@PathVariable("distributionSetId") Long distributionSetId,
|
||||
@Valid MgmtInvalidateDistributionSetRequestBody invalidateRequestBody);
|
||||
}
|
||||
|
||||
@@ -110,6 +110,7 @@ public final class MgmtDistributionSetMapper {
|
||||
response.setComplete(distributionSet.isComplete());
|
||||
response.setType(distributionSet.getType().getKey());
|
||||
response.setDeleted(distributionSet.isDeleted());
|
||||
response.setValid(distributionSet.isValid());
|
||||
|
||||
distributionSet.getModules()
|
||||
.forEach(module -> response.getModules().add(MgmtSoftwareModuleMapper.toResponse(module)));
|
||||
|
||||
@@ -9,17 +9,21 @@
|
||||
package org.eclipse.hawkbit.mgmt.rest.resource;
|
||||
|
||||
import java.util.AbstractMap.SimpleEntry;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
||||
import org.eclipse.hawkbit.mgmt.json.model.MgmtMetadata;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.MgmtMetadataBodyPut;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.PagedList;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSet;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSetRequestBodyPost;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSetRequestBodyPut;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtInvalidateDistributionSetRequestBody;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtTargetAssignmentRequestBody;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtTargetAssignmentResponseBody;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModule;
|
||||
@@ -29,6 +33,7 @@ import org.eclipse.hawkbit.mgmt.json.model.targetfilter.MgmtTargetFilterQuery;
|
||||
import org.eclipse.hawkbit.mgmt.rest.api.MgmtDistributionSetRestApi;
|
||||
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
|
||||
import org.eclipse.hawkbit.repository.DeploymentManagement;
|
||||
import org.eclipse.hawkbit.repository.DistributionSetInvalidationManagement;
|
||||
import org.eclipse.hawkbit.repository.DistributionSetManagement;
|
||||
import org.eclipse.hawkbit.repository.EntityFactory;
|
||||
import org.eclipse.hawkbit.repository.OffsetBasedPageRequest;
|
||||
@@ -40,6 +45,7 @@ import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||
import org.eclipse.hawkbit.repository.model.DeploymentRequest;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSetMetadata;
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModule;
|
||||
import org.eclipse.hawkbit.repository.model.Target;
|
||||
@@ -81,11 +87,14 @@ public class MgmtDistributionSetResource implements MgmtDistributionSetRestApi {
|
||||
|
||||
private final SystemSecurityContext systemSecurityContext;
|
||||
|
||||
private final DistributionSetInvalidationManagement distributionSetInvalidationManagement;
|
||||
|
||||
MgmtDistributionSetResource(final SoftwareModuleManagement softwareModuleManagement,
|
||||
final TargetManagement targetManagement, final TargetFilterQueryManagement targetFilterQueryManagement,
|
||||
final DeploymentManagement deployManagament, final SystemManagement systemManagement,
|
||||
final EntityFactory entityFactory, final DistributionSetManagement distributionSetManagement,
|
||||
final SystemSecurityContext systemSecurityContext) {
|
||||
final SystemSecurityContext systemSecurityContext,
|
||||
final DistributionSetInvalidationManagement distributionSetInvalidationManagement) {
|
||||
this.softwareModuleManagement = softwareModuleManagement;
|
||||
this.targetManagement = targetManagement;
|
||||
this.targetFilterQueryManagement = targetFilterQueryManagement;
|
||||
@@ -94,6 +103,7 @@ public class MgmtDistributionSetResource implements MgmtDistributionSetRestApi {
|
||||
this.entityFactory = entityFactory;
|
||||
this.distributionSetManagement = distributionSetManagement;
|
||||
this.systemSecurityContext = systemSecurityContext;
|
||||
this.distributionSetInvalidationManagement = distributionSetInvalidationManagement;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -125,7 +135,7 @@ public class MgmtDistributionSetResource implements MgmtDistributionSetRestApi {
|
||||
@Override
|
||||
public ResponseEntity<MgmtDistributionSet> getDistributionSet(
|
||||
@PathVariable("distributionSetId") final Long distributionSetId) {
|
||||
final DistributionSet foundDs = findDistributionSetWithExceptionIfNotFound(distributionSetId);
|
||||
final DistributionSet foundDs = distributionSetManagement.getOrElseThrowException(distributionSetId);
|
||||
|
||||
final MgmtDistributionSet response = MgmtDistributionSetMapper.toResponse(foundDs);
|
||||
MgmtDistributionSetMapper.addLinks(foundDs, response);
|
||||
@@ -206,7 +216,7 @@ public class MgmtDistributionSetResource implements MgmtDistributionSetRestApi {
|
||||
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam) {
|
||||
// check if distribution set exists otherwise throw exception
|
||||
// immediately
|
||||
findDistributionSetWithExceptionIfNotFound(distributionSetId);
|
||||
distributionSetManagement.getOrElseThrowException(distributionSetId);
|
||||
|
||||
final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam);
|
||||
final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam);
|
||||
@@ -254,11 +264,10 @@ public class MgmtDistributionSetResource implements MgmtDistributionSetRestApi {
|
||||
final List<Entry<String, Long>> offlineAssignments = assignments.stream()
|
||||
.map(assignment -> new SimpleEntry<String, Long>(assignment.getId(), distributionSetId))
|
||||
.collect(Collectors.toList());
|
||||
return ResponseEntity
|
||||
.ok(MgmtDistributionSetMapper
|
||||
.toResponse(deployManagament.offlineAssignedDistributionSets(offlineAssignments)));
|
||||
return ResponseEntity.ok(MgmtDistributionSetMapper
|
||||
.toResponse(deployManagament.offlineAssignedDistributionSets(offlineAssignments)));
|
||||
}
|
||||
|
||||
|
||||
final List<DeploymentRequest> deploymentRequests = assignments.stream()
|
||||
.map(assignment -> MgmtDeploymentRequestMapper.createAssignmentRequest(assignment, distributionSetId))
|
||||
.collect(Collectors.toList());
|
||||
@@ -374,8 +383,14 @@ public class MgmtDistributionSetResource implements MgmtDistributionSetRestApi {
|
||||
softwaremodules.getTotalElements()));
|
||||
}
|
||||
|
||||
private DistributionSet findDistributionSetWithExceptionIfNotFound(final Long distributionSetId) {
|
||||
return distributionSetManagement.get(distributionSetId)
|
||||
.orElseThrow(() -> new EntityNotFoundException(DistributionSet.class, distributionSetId));
|
||||
@Override
|
||||
public ResponseEntity<Void> invalidateDistributionSet(
|
||||
@PathVariable("distributionSetId") final Long distributionSetId,
|
||||
@Valid @RequestBody final MgmtInvalidateDistributionSetRequestBody invalidateRequestBody) {
|
||||
distributionSetInvalidationManagement
|
||||
.invalidateDistributionSet(new DistributionSetInvalidation(Arrays.asList(distributionSetId),
|
||||
MgmtRestModelMapper.convertCancelationType(invalidateRequestBody.getActionCancelationType()),
|
||||
invalidateRequestBody.isCancelRollouts()));
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,9 @@ package org.eclipse.hawkbit.mgmt.rest.resource;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.MgmtBaseEntity;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.MgmtNamedEntity;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtActionType;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtCancelationType;
|
||||
import org.eclipse.hawkbit.repository.model.Action.ActionType;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation.CancelationType;
|
||||
import org.eclipse.hawkbit.repository.model.NamedEntity;
|
||||
import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity;
|
||||
|
||||
@@ -48,10 +50,10 @@ public final class MgmtRestModelMapper {
|
||||
/**
|
||||
* Convert the given {@link MgmtActionType} into a corresponding repository
|
||||
* {@link ActionType}.
|
||||
*
|
||||
*
|
||||
* @param actionTypeRest
|
||||
* the REST representation of the action type
|
||||
*
|
||||
*
|
||||
* @return <null> or the repository action type
|
||||
*/
|
||||
public static ActionType convertActionType(final MgmtActionType actionTypeRest) {
|
||||
@@ -76,10 +78,10 @@ public final class MgmtRestModelMapper {
|
||||
/**
|
||||
* Converts the given repository {@link ActionType} into a corresponding
|
||||
* {@link MgmtActionType}.
|
||||
*
|
||||
*
|
||||
* @param actionType
|
||||
* the repository representation of the action type
|
||||
*
|
||||
*
|
||||
* @return <null> or the REST action type
|
||||
*/
|
||||
public static MgmtActionType convertActionType(final ActionType actionType) {
|
||||
@@ -100,4 +102,30 @@ public final class MgmtRestModelMapper {
|
||||
throw new IllegalStateException("Action Type is not supported");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given repository {@link CancelationType} into a
|
||||
* corresponding {@link MgmtCancelationType}.
|
||||
*
|
||||
* @param cancelationType
|
||||
* the repository representation of the cancellation type
|
||||
*
|
||||
* @return <null> or the REST cancellation type
|
||||
*/
|
||||
public static CancelationType convertCancelationType(final MgmtCancelationType cancelationType) {
|
||||
if (cancelationType == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (cancelationType) {
|
||||
case SOFT:
|
||||
return CancelationType.SOFT;
|
||||
case FORCE:
|
||||
return CancelationType.FORCE;
|
||||
case NONE:
|
||||
return CancelationType.NONE;
|
||||
default:
|
||||
throw new IllegalStateException("Action Cancelation Type is not supported");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,7 +110,8 @@ public class MgmtRolloutResource implements MgmtRolloutRestApi {
|
||||
// exception is thrown
|
||||
targetFilterQueryManagement.verifyTargetFilterQuerySyntax(rolloutRequestBody.getTargetFilterQuery());
|
||||
|
||||
final DistributionSet distributionSet = findDistributionSetOrThrowException(rolloutRequestBody);
|
||||
final DistributionSet distributionSet = distributionSetManagement
|
||||
.getValidAndComplete(rolloutRequestBody.getDistributionSetId());
|
||||
final RolloutGroupConditions rolloutGroupConditions = MgmtRolloutMapper.fromRequest(rolloutRequestBody, true);
|
||||
|
||||
final RolloutCreate create = MgmtRolloutMapper.fromRequest(entityFactory, rolloutRequestBody, distributionSet);
|
||||
@@ -234,10 +235,4 @@ public class MgmtRolloutResource implements MgmtRolloutRestApi {
|
||||
final List<MgmtTarget> rest = MgmtTargetMapper.toResponse(rolloutGroupTargets.getContent());
|
||||
return ResponseEntity.ok(new PagedList<>(rest, rolloutGroupTargets.getTotalElements()));
|
||||
}
|
||||
|
||||
private DistributionSet findDistributionSetOrThrowException(final MgmtRolloutRestRequestBody rolloutRequestBody) {
|
||||
return this.distributionSetManagement.get(rolloutRequestBody.getDistributionSetId()).orElseThrow(
|
||||
() -> new EntityNotFoundException(DistributionSet.class, rolloutRequestBody.getDistributionSetId()));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,8 +40,12 @@ import org.eclipse.hawkbit.repository.model.Action.Status;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSetMetadata;
|
||||
import org.eclipse.hawkbit.repository.model.NamedEntity;
|
||||
import org.eclipse.hawkbit.repository.model.Rollout;
|
||||
import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus;
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModule;
|
||||
import org.eclipse.hawkbit.repository.model.Target;
|
||||
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
|
||||
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
|
||||
import org.eclipse.hawkbit.repository.test.util.TestdataFactory;
|
||||
import org.eclipse.hawkbit.repository.test.util.WithUser;
|
||||
import org.eclipse.hawkbit.rest.util.JsonBuilder;
|
||||
@@ -1350,4 +1354,37 @@ public class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegr
|
||||
assertThat(actions).size().isEqualTo(1);
|
||||
assertThat(actions.get(0).getWeight()).get().isEqualTo(weight);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verify invalidation of distribution sets that removes distribution sets from auto assignments, stops rollouts and cancels assignments")
|
||||
public void invalidateDistributionSet() throws Exception {
|
||||
final DistributionSet distributionSet = testdataFactory.createDistributionSet();
|
||||
final List<Target> targets = testdataFactory.createTargets(5, "invalidateDistributionSet");
|
||||
assignDistributionSet(distributionSet, targets);
|
||||
final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement
|
||||
.create(entityFactory.targetFilterQuery().create().name("invalidateDistributionSet").query("name==*")
|
||||
.autoAssignDistributionSet(distributionSet));
|
||||
final Rollout rollout = testdataFactory.createRolloutByVariables("invalidateDistributionSet", "desc", 2,
|
||||
"name==*", distributionSet, "50", "80");
|
||||
|
||||
final JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("actionCancelationType", "soft");
|
||||
jsonObject.put("cancelRollouts", true);
|
||||
|
||||
mvc.perform(post("/rest/v1/distributionsets/{ds}/invalidate", distributionSet.getId())
|
||||
.content(jsonObject.toString()).contentType(MediaType.APPLICATION_JSON)).andExpect(status().isOk());
|
||||
|
||||
assertThat(targetFilterQueryManagement.get(targetFilterQuery.getId()).get().getAutoAssignDistributionSet())
|
||||
.isNull();
|
||||
assertThat(rolloutManagement.get(rollout.getId()).get().getStatus()).isIn(RolloutStatus.STOPPING,
|
||||
RolloutStatus.FINISHED);
|
||||
for (final Target target : targets) {
|
||||
assertThat(targetManagement.get(target.getId()).get().getUpdateStatus())
|
||||
.isEqualTo(TargetUpdateStatus.PENDING);
|
||||
assertThat(deploymentManagement.findActionsByTarget(target.getControllerId(), PageRequest.of(0, 100))
|
||||
.getNumberOfElements()).isEqualTo(1);
|
||||
assertThat(deploymentManagement.findActionsByTarget(target.getControllerId(), PageRequest.of(0, 100))
|
||||
.getContent().get(0).getStatus()).isEqualTo(Status.CANCELING);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,9 +25,10 @@ import java.util.List;
|
||||
import org.eclipse.hawkbit.exception.SpServerError;
|
||||
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtActionType;
|
||||
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
|
||||
import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignActionTypeException;
|
||||
import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignDistributionSetException;
|
||||
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||
import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException;
|
||||
import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignActionTypeException;
|
||||
import org.eclipse.hawkbit.repository.model.Action;
|
||||
import org.eclipse.hawkbit.repository.model.Action.ActionType;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||
@@ -294,7 +295,7 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte
|
||||
assertThat(exceptionInfo.getExceptionClass()).isEqualTo(MessageNotReadableException.class.getName());
|
||||
assertThat(exceptionInfo.getErrorCode()).isEqualTo(SpServerError.SP_REST_BODY_NOT_READABLE.getKey());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
@Description("Ensures that the creation of a target filter query based on an invalid RSQL query results in a HTTP Bad Request error (400).")
|
||||
public void createTargetFilterWithInvalidQuery() throws Exception {
|
||||
@@ -324,7 +325,8 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte
|
||||
post(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + filterQuery.getId() + "/autoAssignDS")
|
||||
.content("{\"id\":" + set.getId() + "}").contentType(MediaType.APPLICATION_JSON))
|
||||
.andDo(print()).andExpect(status().isForbidden())
|
||||
.andExpect(jsonPath(JSON_PATH_EXCEPTION_CLASS, equalTo(AssignmentQuotaExceededException.class.getName())))
|
||||
.andExpect(
|
||||
jsonPath(JSON_PATH_EXCEPTION_CLASS, equalTo(AssignmentQuotaExceededException.class.getName())))
|
||||
.andExpect(jsonPath(JSON_PATH_ERROR_CODE, equalTo(SpServerError.SP_QUOTA_EXCEEDED.getKey())));
|
||||
}
|
||||
|
||||
@@ -356,7 +358,8 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte
|
||||
mvc.perform(put(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + filterQuery.getId())
|
||||
.content("{\"query\":\"controllerId==target*\"}").contentType(MediaType.APPLICATION_JSON))
|
||||
.andDo(print()).andExpect(status().isForbidden())
|
||||
.andExpect(jsonPath(JSON_PATH_EXCEPTION_CLASS, equalTo(AssignmentQuotaExceededException.class.getName())))
|
||||
.andExpect(
|
||||
jsonPath(JSON_PATH_EXCEPTION_CLASS, equalTo(AssignmentQuotaExceededException.class.getName())))
|
||||
.andExpect(jsonPath(JSON_PATH_ERROR_CODE, equalTo(SpServerError.SP_QUOTA_EXCEEDED.getKey())));
|
||||
|
||||
}
|
||||
@@ -470,9 +473,8 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte
|
||||
.content("{\"id\":" + incompleteDistributionSet.getId() + "}").contentType(MediaType.APPLICATION_JSON))
|
||||
.andDo(print()).andExpect(status().isBadRequest())
|
||||
.andExpect(jsonPath(JSON_PATH_EXCEPTION_CLASS,
|
||||
equalTo(InvalidAutoAssignDistributionSetException.class.getName())))
|
||||
.andExpect(jsonPath(JSON_PATH_ERROR_CODE,
|
||||
equalTo(SpServerError.SP_AUTO_ASSIGN_DISTRIBUTION_SET_INVALID.getKey())));
|
||||
equalTo(IncompleteDistributionSetException.class.getName())))
|
||||
.andExpect(jsonPath(JSON_PATH_ERROR_CODE, equalTo(SpServerError.SP_DS_INCOMPLETE.getKey())));
|
||||
}
|
||||
|
||||
@Step
|
||||
@@ -483,11 +485,9 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte
|
||||
|
||||
mvc.perform(post(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + tfq.getId() + "/autoAssignDS")
|
||||
.content("{\"id\":" + softDeletedDs.getId() + "}").contentType(MediaType.APPLICATION_JSON))
|
||||
.andDo(print()).andExpect(status().isBadRequest())
|
||||
.andExpect(jsonPath(JSON_PATH_EXCEPTION_CLASS,
|
||||
equalTo(InvalidAutoAssignDistributionSetException.class.getName())))
|
||||
.andExpect(jsonPath(JSON_PATH_ERROR_CODE,
|
||||
equalTo(SpServerError.SP_AUTO_ASSIGN_DISTRIBUTION_SET_INVALID.getKey())));
|
||||
.andDo(print()).andExpect(status().isNotFound())
|
||||
.andExpect(jsonPath(JSON_PATH_EXCEPTION_CLASS, equalTo(EntityNotFoundException.class.getName())))
|
||||
.andExpect(jsonPath(JSON_PATH_ERROR_CODE, equalTo(SpServerError.SP_REPO_ENTITY_NOT_EXISTS.getKey())));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -83,11 +83,13 @@ public class ResponseExceptionHandler {
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_MAINTENANCE_SCHEDULE_INVALID, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_TARGET_ATTRIBUTES_INVALID, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_AUTO_ASSIGN_ACTION_TYPE_INVALID, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_AUTO_ASSIGN_DISTRIBUTION_SET_INVALID, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_CONFIGURATION_VALUE_CHANGE_NOT_ALLOWED, HttpStatus.FORBIDDEN);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_MULTIASSIGNMENT_NOT_ENABLED, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_NO_WEIGHT_PROVIDED_IN_MULTIASSIGNMENT_MODE, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_TARGET_TYPE_IN_USE, HttpStatus.CONFLICT);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_DS_INVALID, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_DS_INCOMPLETE, HttpStatus.BAD_REQUEST);
|
||||
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_STOP_ROLLOUT_FAILED, HttpStatus.LOCKED);
|
||||
}
|
||||
|
||||
private static HttpStatus getStatusOrDefault(final SpServerError error) {
|
||||
|
||||
@@ -830,6 +830,54 @@ include::../errors/429.adoc[]
|
||||
|===
|
||||
|
||||
|
||||
== POST /rest/v1/distributionsets/{distributionSetId}/invalidate
|
||||
|
||||
|
||||
=== Implementation Notes
|
||||
|
||||
Invalidate a distribution set. Once a distribution set is invalidated, it can not be valid again. An invalidated distribution set cannot be assigned to targets anymore. The distribution set that is going to be invalidated will be removed from all auto assignments. Furthermore, the user can choose to cancel all rollouts and (force) cancel all actions connected to this distribution set. Required permission: UPDATE_REPOSITORY
|
||||
|
||||
=== Invalidate a distribution set
|
||||
|
||||
==== Curl
|
||||
|
||||
include::{snippets}/distributionsets/invalidate/curl-request.adoc[]
|
||||
|
||||
==== Request URL
|
||||
|
||||
include::{snippets}/distributionsets/invalidate/http-request.adoc[]
|
||||
|
||||
==== Request path parameter
|
||||
|
||||
include::{snippets}/distributionsets/invalidate/path-parameters.adoc[]
|
||||
|
||||
==== Request fields
|
||||
|
||||
include::{snippets}/distributionsets/invalidate/request-fields.adoc[]
|
||||
|
||||
=== Response (Status 200)
|
||||
|
||||
==== Response example
|
||||
|
||||
include::{snippets}/distributionsets/invalidate/http-response.adoc[]
|
||||
|
||||
=== Error responses
|
||||
|
||||
|===
|
||||
| HTTP Status Code | Reason | Response Model
|
||||
|
||||
include::../errors/400.adoc[]
|
||||
include::../errors/401.adoc[]
|
||||
include::../errors/403.adoc[]
|
||||
include::../errors/404.adoc[]
|
||||
include::../errors/405.adoc[]
|
||||
include::../errors/406.adoc[]
|
||||
include::../errors/409.adoc[]
|
||||
include::../errors/415.adoc[]
|
||||
include::../errors/429.adoc[]
|
||||
|===
|
||||
|
||||
|
||||
== Additional content
|
||||
|
||||
[[error-body]]
|
||||
|
||||
@@ -89,8 +89,9 @@ public abstract class AbstractApiRestDocumentation extends AbstractRestIntegrati
|
||||
protected String host = "management-api.host";
|
||||
|
||||
/**
|
||||
* The generated REST docs snippets will be outputted to an own resource folder.
|
||||
* The child class has to specify the name of that output folder where to put its corresponding snippets.
|
||||
* The generated REST docs snippets will be outputted to an own resource
|
||||
* folder. The child class has to specify the name of that output folder
|
||||
* where to put its corresponding snippets.
|
||||
*
|
||||
* @return the name of the resource folder
|
||||
*/
|
||||
@@ -101,8 +102,8 @@ public abstract class AbstractApiRestDocumentation extends AbstractRestIntegrati
|
||||
this.document = document(getResourceName() + "/{method-name}", preprocessRequest(prettyPrint()),
|
||||
preprocessResponse(prettyPrint()));
|
||||
this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
|
||||
.apply(MockMvcRestDocumentation.documentationConfiguration(restDocContext).uris()
|
||||
.withScheme("https").withHost(host + ".com").withPort(443))
|
||||
.apply(MockMvcRestDocumentation.documentationConfiguration(restDocContext).uris().withScheme("https")
|
||||
.withHost(host + ".com").withPort(443))
|
||||
.alwaysDo(this.document).addFilter(filterHttpResponse).build();
|
||||
arrayPrefix = "[]";
|
||||
}
|
||||
@@ -163,33 +164,36 @@ public abstract class AbstractApiRestDocumentation extends AbstractRestIntegrati
|
||||
|
||||
protected Target createTargetByGivenNameWithAttributes(final String name, final boolean inSync,
|
||||
final boolean timeforced, final DistributionSet distributionSet) {
|
||||
return createTargetByGivenNameWithAttributes(name, inSync, timeforced, distributionSet, null, null, null, false);
|
||||
return createTargetByGivenNameWithAttributes(name, inSync, timeforced, distributionSet, null, null, null,
|
||||
false);
|
||||
}
|
||||
|
||||
protected Target createTargetByGivenNameWithAttributes(final String name, final boolean inSync,
|
||||
final boolean timeforced, final DistributionSet distributionSet, final boolean createRollout) {
|
||||
return createTargetByGivenNameWithAttributes(name, inSync, timeforced, distributionSet, null, null, null, createRollout);
|
||||
return createTargetByGivenNameWithAttributes(name, inSync, timeforced, distributionSet, null, null, null,
|
||||
createRollout);
|
||||
}
|
||||
|
||||
protected Target createTargetByGivenNameWithAttributes(final String name, final boolean inSync,
|
||||
final boolean timeforced, final DistributionSet distributionSet, final String maintenanceWindowSchedule,
|
||||
final String maintenanceWindowDuration, final String maintenanceWindowTimeZone, final boolean createRollout) {
|
||||
final String maintenanceWindowDuration, final String maintenanceWindowTimeZone,
|
||||
final boolean createRollout) {
|
||||
|
||||
final Target savedTarget = targetManagement.create(entityFactory.target().create().controllerId(name)
|
||||
.status(TargetUpdateStatus.UNKNOWN).address("http://192.168.0.1").description("My name is " + name)
|
||||
.lastTargetQuery(System.currentTimeMillis()));
|
||||
|
||||
|
||||
final List<Target> updatedTargets;
|
||||
if (createRollout) {
|
||||
|
||||
final Rollout rollout = testdataFactory.createRolloutByVariables("rollout", "rollout desc", 1,
|
||||
"name==" + name, distributionSet, "50", "5", timeforced ? ActionType.TIMEFORCED : ActionType.FORCED,
|
||||
isMultiAssignmentsEnabled() ? 600 : null);
|
||||
|
||||
|
||||
// start the rollout and handle it
|
||||
rolloutManagement.start(rollout.getId());
|
||||
rolloutManagement.handleRollouts();
|
||||
|
||||
|
||||
updatedTargets = Collections.singletonList(savedTarget);
|
||||
|
||||
} else {
|
||||
@@ -208,7 +212,7 @@ public abstract class AbstractApiRestDocumentation extends AbstractRestIntegrati
|
||||
updatedTargets = makeAssignment(deploymentRequestBuilder.build()).getAssignedEntity().stream()
|
||||
.map(Action::getTarget).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
||||
if (inSync) {
|
||||
feedbackToByInSync(distributionSet);
|
||||
}
|
||||
@@ -318,6 +322,7 @@ public abstract class AbstractApiRestDocumentation extends AbstractRestIntegrati
|
||||
.description(MgmtApiModelProperties.DS_REQUIRED_STEP),
|
||||
fieldWithPath(arrayPrefix + "complete").description(MgmtApiModelProperties.DS_COMPLETE),
|
||||
fieldWithPath(arrayPrefix + "deleted").description(ApiModelPropertiesGeneric.DELETED),
|
||||
fieldWithPath(arrayPrefix + "valid").description(MgmtApiModelProperties.DS_VALID),
|
||||
fieldWithPath(arrayPrefix + "version").description(MgmtApiModelProperties.VERSION),
|
||||
fieldWithPath(arrayPrefix + "_links.self").ignored(), fieldWithPath(arrayPrefix + "modules").ignored());
|
||||
|
||||
|
||||
@@ -67,6 +67,8 @@ public final class MgmtApiModelProperties {
|
||||
public static final String DS_ALREADY_ASSIGNED_TARGETS = "Targets that had this distribution set already assigned (in \"offline\" case this includes targets that have arbitrary updates running)";
|
||||
public static final String DS_TOTAL_ASSIGNED_TARGETS = "Overall assigned as part of this request.";
|
||||
public static final String DS_ID = "Id of the distribution set.";
|
||||
public static final String DS_INVALIDATION_ACTION_CANCELATION_TYPE = "Type of cancelation for actions referring to the given distribution set.";
|
||||
public static final String DS_INVALIDATION_CANCEL_ROLLOUTS = "Defines if rollouts referring to this distribution set should be canceled.";
|
||||
|
||||
// Target
|
||||
public static final String INSTALLED_AT = "Installation time of current installed DistributionSet.";
|
||||
@@ -184,6 +186,8 @@ public final class MgmtApiModelProperties {
|
||||
|
||||
public static final String DS_COMPLETE = "True of the distribution set software module setup is complete as defined by the distribution set type.";
|
||||
|
||||
public static final String DS_VALID = "True by default and false after the distribution set is invalidated by the user.";
|
||||
|
||||
public static final String DS_TYPE_MANDATORY_MODULES = "Mandatory module type IDs.";
|
||||
|
||||
public static final String DS_TYPE_OPTIONAL_MODULES = "Optional module type IDs.";
|
||||
|
||||
@@ -379,8 +379,7 @@ public class DistributionSetsDocumentationTest extends AbstractApiRestDocumentat
|
||||
parameterWithName("distributionSetId").description(ApiModelPropertiesGeneric.ITEM_ID)),
|
||||
requestParameters(parameterWithName("offline")
|
||||
.description(MgmtApiModelProperties.OFFLINE_UPDATE).optional()),
|
||||
requestFields(
|
||||
requestFieldWithPath("[].id").description(ApiModelPropertiesGeneric.ITEM_ID),
|
||||
requestFields(requestFieldWithPath("[].id").description(ApiModelPropertiesGeneric.ITEM_ID),
|
||||
requestFieldWithPathMandatoryInMultiAssignMode("[].weight")
|
||||
.description(MgmtApiModelProperties.ASSIGNMENT_WEIGHT)
|
||||
.type(JsonFieldType.NUMBER).attributes(key("value").value("0 - 1000")),
|
||||
@@ -395,8 +394,8 @@ public class DistributionSetsDocumentationTest extends AbstractApiRestDocumentat
|
||||
optionalRequestFieldWithPath("[].maintenanceWindow.timezone")
|
||||
.description(MgmtApiModelProperties.MAINTENANCE_WINDOW_TIMEZONE),
|
||||
optionalRequestFieldWithPath("[].type")
|
||||
.description(MgmtApiModelProperties.ASSIGNMENT_TYPE)
|
||||
.attributes(key("value").value("['soft', 'forced','timeforced', 'downloadonly']"))),
|
||||
.description(MgmtApiModelProperties.ASSIGNMENT_TYPE).attributes(
|
||||
key("value").value("['soft', 'forced','timeforced', 'downloadonly']"))),
|
||||
responseFields(
|
||||
fieldWithPath("assigned").description(MgmtApiModelProperties.DS_NEW_ASSIGNED_TARGETS),
|
||||
fieldWithPath("alreadyAssigned").type(JsonFieldType.NUMBER)
|
||||
@@ -445,8 +444,7 @@ public class DistributionSetsDocumentationTest extends AbstractApiRestDocumentat
|
||||
mockMvc.perform(delete(
|
||||
MgmtRestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING
|
||||
+ "/{distributionSetId}/assignedSM/{softwareModuleId}",
|
||||
set.getId(), set.findFirstModuleByType(osType).get().getId())
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
set.getId(), set.findFirstModuleByType(osType).get().getId()).contentType(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
|
||||
.andDo(this.document.document(pathParameters(
|
||||
parameterWithName("distributionSetId").description(ApiModelPropertiesGeneric.ITEM_ID),
|
||||
@@ -667,4 +665,28 @@ public class DistributionSetsDocumentationTest extends AbstractApiRestDocumentat
|
||||
optionalRequestFieldWithPath("[]value")
|
||||
.description(MgmtApiModelProperties.META_DATA_VALUE))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Invalidates a distribution set. Required Permission: " + SpPermission.UPDATE_REPOSITORY)
|
||||
public void invalidate() throws Exception {
|
||||
final DistributionSet testDS = testdataFactory.createDistributionSet();
|
||||
|
||||
final JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("actionCancelationType", "soft");
|
||||
jsonObject.put("cancelRollouts", true);
|
||||
|
||||
mockMvc.perform(post(MgmtRestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/{distributionSetId}/invalidate",
|
||||
testDS.getId()).content(jsonObject.toString()).contentType(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()).andDo(
|
||||
this.document.document(
|
||||
pathParameters(parameterWithName("distributionSetId")
|
||||
.description(ApiModelPropertiesGeneric.ITEM_ID)),
|
||||
requestFields(
|
||||
requestFieldWithPath("actionCancelationType")
|
||||
.description(
|
||||
MgmtApiModelProperties.DS_INVALIDATION_ACTION_CANCELATION_TYPE)
|
||||
.attributes(key("value").value("['force','soft','none']")),
|
||||
optionalRequestFieldWithPath("cancelRollouts")
|
||||
.description(MgmtApiModelProperties.DS_INVALIDATION_CANCEL_ROLLOUTS))));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user