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:
Sebastian Firsching
2021-09-30 15:26:36 +02:00
committed by GitHub
parent b25e118e6c
commit 825cb64448
66 changed files with 2413 additions and 511 deletions

View File

@@ -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]]

View File

@@ -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());

View File

@@ -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.";

View File

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