Assign multiple distribution sets to a target via mgmt api (#886)

* Add multiassignment to mgmt api target endpoint
* Remove single assignment ds to targets offline
* Fix tests
* Add quota for maxResultingActionsPerManualAssignment
* Fix assignment with same target or distribution set multiple times in one request
* Log UI error
* Add tests
* Enable single assignment requests with multiple DSs and types
* Remove redundant target to DS assignment methods
* Add tests, fix assignment
* Fix possible nullpointer during target assignment request
* Update api docu
* Clean up deployment management code
* Enforce MaxActions quota for offline assignment
* Fix review findings
* Rename property, add migration into
* Add builder for DeploymentRequest
* Change offline assignment method to accept an assignment list, like online assignment
* Fix PR findings

Signed-off-by: Stefan Klotz <stefan.klotz@bosch-si.com>
This commit is contained in:
Stefan Klotz
2019-09-17 14:20:26 +02:00
committed by Stefan Behl
parent dba972423b
commit 8687510131
50 changed files with 1466 additions and 559 deletions

View File

@@ -21,7 +21,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.io.ByteArrayInputStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@@ -73,7 +72,7 @@ public class RootControllerDocumentationTest extends AbstractApiRestDocumentatio
final DistributionSet set = testdataFactory.createDistributionSet("one");
final Target target = targetManagement.create(entityFactory.target().create().controllerId(CONTROLLER_ID));
deploymentManagement.assignDistributionSet(set.getId(), Arrays.asList(target.getTargetWithActionType()));
assignDistributionSet(set.getId(), target.getControllerId());
mockMvc.perform(get(DdiRestConstants.BASE_V1_REQUEST_MAPPING + "/{controllerId}",
tenantAware.getCurrentTenant(), target.getControllerId()).accept(MediaTypes.HAL_JSON_VALUE))
@@ -101,8 +100,8 @@ public class RootControllerDocumentationTest extends AbstractApiRestDocumentatio
final DistributionSet setTwo = testdataFactory.createDistributionSet("two");
final Target target = targetManagement.create(entityFactory.target().create().controllerId(CONTROLLER_ID));
deploymentManagement.assignDistributionSet(set.getId(), Arrays.asList(target.getTargetWithActionType()));
deploymentManagement.assignDistributionSet(setTwo.getId(), Arrays.asList(target.getTargetWithActionType()));
assignDistributionSet(set.getId(), target.getControllerId());
assignDistributionSet(setTwo.getId(), target.getControllerId());
mockMvc.perform(get(DdiRestConstants.BASE_V1_REQUEST_MAPPING + "/{controllerId}",
tenantAware.getCurrentTenant(), target.getControllerId()).accept(MediaTypes.HAL_JSON_VALUE))
@@ -137,8 +136,7 @@ public class RootControllerDocumentationTest extends AbstractApiRestDocumentatio
});
final Target target = targetManagement.create(entityFactory.target().create().controllerId(CONTROLLER_ID));
final Long actionId = getFirstAssignedActionId(deploymentManagement.assignDistributionSet(set.getId(),
Arrays.asList(target.getTargetWithActionType())));
final Long actionId = getFirstAssignedActionId(assignDistributionSet(set.getId(), target.getControllerId()));
final Action cancelAction = deploymentManagement.cancelAction(actionId);
mockMvc.perform(
@@ -169,8 +167,7 @@ public class RootControllerDocumentationTest extends AbstractApiRestDocumentatio
final DistributionSet set = testdataFactory.createDistributionSet("one");
final Target target = targetManagement.create(entityFactory.target().create().controllerId(CONTROLLER_ID));
final Long actionId = getFirstAssignedActionId(deploymentManagement.assignDistributionSet(set.getId(),
Arrays.asList(target.getTargetWithActionType())));
final Long actionId = getFirstAssignedActionId(assignDistributionSet(set.getId(), target.getControllerId()));
final Action cancelAction = deploymentManagement.cancelAction(actionId);
mockMvc.perform(post(
@@ -387,8 +384,7 @@ public class RootControllerDocumentationTest extends AbstractApiRestDocumentatio
final DistributionSet set = testdataFactory.createDistributionSet("one");
final Target target = targetManagement.create(entityFactory.target().create().controllerId(CONTROLLER_ID));
final Long actionId = getFirstAssignedActionId(deploymentManagement.assignDistributionSet(set.getId(),
Arrays.asList(target.getTargetWithActionType())));
final Long actionId = getFirstAssignedActionId(assignDistributionSet(set.getId(), target.getControllerId()));
mockMvc.perform(post(DdiRestConstants.BASE_V1_REQUEST_MAPPING + "/{controllerId}/"
+ DdiRestConstants.DEPLOYMENT_BASE_ACTION + "/{actionId}/feedback", tenantAware.getCurrentTenant(),
@@ -434,7 +430,7 @@ public class RootControllerDocumentationTest extends AbstractApiRestDocumentatio
.create(new ArtifactUpload(new ByteArrayInputStream(random), module.getId(), "binaryFile", false, 0));
final Target target = targetManagement.create(entityFactory.target().create().controllerId(CONTROLLER_ID));
deploymentManagement.assignDistributionSet(set.getId(), Arrays.asList(target.getTargetWithActionType()));
assignDistributionSet(set.getId(), target.getControllerId());
mockMvc.perform(
get(DdiRestConstants.BASE_V1_REQUEST_MAPPING + "/{controllerId}/softwaremodules/{moduleId}/artifacts",

View File

@@ -27,6 +27,7 @@ import org.eclipse.hawkbit.ddi.rest.resource.DdiApiConfiguration;
import org.eclipse.hawkbit.mgmt.rest.resource.MgmtApiConfiguration;
import org.eclipse.hawkbit.repository.jpa.RepositoryApplicationConfiguration;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.ArtifactUpload;
import org.eclipse.hawkbit.repository.model.DistributionSet;
@@ -169,7 +170,8 @@ public abstract class AbstractApiRestDocumentation extends AbstractRestIntegrati
private List<Target> assignWithoutMaintenanceWindow(final DistributionSet distributionSet, final Target savedTarget,
final boolean timeforced) {
final List<Action> actions = timeforced
? assignDistributionSetTimeForced(distributionSet, savedTarget).getAssignedEntity()
? assignDistributionSet(distributionSet.getId(), savedTarget.getControllerId(), ActionType.TIMEFORCED)
.getAssignedEntity()
: assignDistributionSet(distributionSet, savedTarget).getAssignedEntity();
return actions.stream().map(Action::getTarget).collect(Collectors.toList());
}
@@ -178,8 +180,8 @@ public abstract class AbstractApiRestDocumentation extends AbstractRestIntegrati
final boolean timeforced, final String maintenanceWindowSchedule, final String maintenanceWindowDuration,
final String maintenanceWindowTimeZone) {
final List<Action> actions = timeforced
? assignDistributionSetWithMaintenanceWindowTimeForced(distributionSet.getId(),
savedTarget.getControllerId(), maintenanceWindowSchedule, maintenanceWindowDuration,
? assignDistributionSetWithMaintenanceWindow(distributionSet.getId(), savedTarget.getControllerId(),
ActionType.TIMEFORCED, maintenanceWindowSchedule, maintenanceWindowDuration,
maintenanceWindowTimeZone).getAssignedEntity()
: assignDistributionSetWithMaintenanceWindow(distributionSet.getId(), savedTarget.getControllerId(),
maintenanceWindowSchedule, maintenanceWindowDuration, maintenanceWindowTimeZone)

View File

@@ -188,7 +188,7 @@ public final class MgmtApiModelProperties {
// request parameter
public static final String FORCETIME = "Forcetime in milliseconds.";
public static final String FORCE = "Force as boolean.";
public static final String FORCETIME_TYPE = "The type of the forcetime.";
public static final String ASSIGNMENT_TYPE = "The type of the assignment.";
public static final String TARGET_ASSIGNED = "The number of targets that have been assigned as part of this operation.";
public static final String TARGET_ASSIGNED_ALREADY = "The number of targets which already had been the assignment.";
public static final String TARGET_ASSIGNED_TOTAL = "The total number of targets that are part of this operation.";

View File

@@ -383,17 +383,20 @@ public class DistributionSetsDocumentationTest extends AbstractApiRestDocumentat
parameterWithName("distributionSetId").description(ApiModelPropertiesGeneric.ITEM_ID)),
requestParameters(parameterWithName("offline")
.description(MgmtApiModelProperties.OFFLINE_UPDATE).optional()),
requestFields(requestFieldWithPath("[]forcetime").description(MgmtApiModelProperties.FORCETIME),
requestFieldWithPath("[]id").description(ApiModelPropertiesGeneric.ITEM_ID),
requestFieldWithPath("[]maintenanceWindow")
.description(MgmtApiModelProperties.MAINTENANCE_WINDOW).optional(),
requestFieldWithPath("[]maintenanceWindow.schedule")
.description(MgmtApiModelProperties.MAINTENANCE_WINDOW_SCHEDULE).optional(),
requestFieldWithPath("[]maintenanceWindow.duration")
.description(MgmtApiModelProperties.MAINTENANCE_WINDOW_DURATION).optional(),
requestFieldWithPath("[]maintenanceWindow.timezone")
.description(MgmtApiModelProperties.MAINTENANCE_WINDOW_TIMEZONE).optional(),
requestFieldWithPath("[]type").description(MgmtApiModelProperties.FORCETIME_TYPE)
requestFields(
requestFieldWithPath("[].id").description(ApiModelPropertiesGeneric.ITEM_ID),
optionalRequestFieldWithPath("[].forcetime")
.description(MgmtApiModelProperties.FORCETIME),
optionalRequestFieldWithPath("[].maintenanceWindow")
.description(MgmtApiModelProperties.MAINTENANCE_WINDOW),
optionalRequestFieldWithPath("[].maintenanceWindow.schedule")
.description(MgmtApiModelProperties.MAINTENANCE_WINDOW_SCHEDULE),
optionalRequestFieldWithPath("[].maintenanceWindow.duration")
.description(MgmtApiModelProperties.MAINTENANCE_WINDOW_DURATION),
optionalRequestFieldWithPath("[].maintenanceWindow.timezone")
.description(MgmtApiModelProperties.MAINTENANCE_WINDOW_TIMEZONE),
optionalRequestFieldWithPath("[].type")
.description(MgmtApiModelProperties.ASSIGNMENT_TYPE)
.attributes(key("value").value("['soft', 'forced','timeforced', 'downloadonly']"))),
responseFields(
fieldWithPath("assigned").description(MgmtApiModelProperties.DS_NEW_ASSIGNED_TARGETS),

View File

@@ -48,6 +48,7 @@ import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.hateoas.MediaTypes;
import org.springframework.http.MediaType;
import org.springframework.restdocs.payload.FieldDescriptor;
import org.springframework.restdocs.payload.JsonFieldType;
import com.fasterxml.jackson.core.JsonProcessingException;
@@ -395,8 +396,8 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
public void switchActionToForced() throws Exception {
final Target target = testdataFactory.createTarget(targetId);
final DistributionSet set = testdataFactory.createDistributionSet();
final Long actionId = getFirstAssignedActionId(deploymentManagement.assignDistributionSet(set.getId(),
ActionType.SOFT, 0, Collections.singletonList(target.getControllerId())));
final Long actionId = getFirstAssignedActionId(
assignDistributionSet(set.getId(), target.getControllerId(), ActionType.SOFT));
assertThat(deploymentManagement.findAction(actionId).get().getActionType()).isEqualTo(ActionType.SOFT);
final Map<String, Object> body = new HashMap<>();
@@ -485,7 +486,7 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
pathParameters(parameterWithName("targetId").description(ApiModelPropertiesGeneric.ITEM_ID)),
getResponseFieldsDistributionSet(false)));
}
@Test
@Description("Handles the POST request for assigning a distribution set to a specific target. Required Permission: READ_REPOSITORY and UPDATE_TARGET.")
public void postAssignDistributionSetToTarget() throws Exception {
@@ -511,30 +512,80 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
pathParameters(parameterWithName("targetId").description(ApiModelPropertiesGeneric.ITEM_ID)),
requestParameters(parameterWithName("offline")
.description(MgmtApiModelProperties.OFFLINE_UPDATE).optional()),
requestFields(requestFieldWithPath("forcetime").description(MgmtApiModelProperties.FORCETIME),
requestFields(
requestFieldWithPath("id").description(ApiModelPropertiesGeneric.ITEM_ID),
requestFieldWithPath("maintenanceWindow")
.description(MgmtApiModelProperties.MAINTENANCE_WINDOW).optional(),
requestFieldWithPath("maintenanceWindow.schedule")
.description(MgmtApiModelProperties.MAINTENANCE_WINDOW_SCHEDULE).optional(),
requestFieldWithPath("maintenanceWindow.duration")
.description(MgmtApiModelProperties.MAINTENANCE_WINDOW_DURATION).optional(),
requestFieldWithPath("maintenanceWindow.timezone")
.description(MgmtApiModelProperties.MAINTENANCE_WINDOW_TIMEZONE).optional(),
requestFieldWithPath("type").description(MgmtApiModelProperties.FORCETIME_TYPE)
optionalRequestFieldWithPath("forcetime").description(MgmtApiModelProperties.FORCETIME),
optionalRequestFieldWithPath("maintenanceWindow")
.description(MgmtApiModelProperties.MAINTENANCE_WINDOW),
optionalRequestFieldWithPath("maintenanceWindow.schedule")
.description(MgmtApiModelProperties.MAINTENANCE_WINDOW_SCHEDULE),
optionalRequestFieldWithPath("maintenanceWindow.duration")
.description(MgmtApiModelProperties.MAINTENANCE_WINDOW_DURATION),
optionalRequestFieldWithPath("maintenanceWindow.timezone")
.description(MgmtApiModelProperties.MAINTENANCE_WINDOW_TIMEZONE),
optionalRequestFieldWithPath("type").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)
.description(MgmtApiModelProperties.DS_ALREADY_ASSIGNED_TARGETS),
fieldWithPath("assignedActions").type(JsonFieldType.ARRAY)
.description(MgmtApiModelProperties.DS_NEW_ASSIGNED_ACTIONS),
fieldWithPath("assignedActions.[].id").type(JsonFieldType.NUMBER)
.description(MgmtApiModelProperties.ACTION_ID),
fieldWithPath("assignedActions.[]._links.self").type(JsonFieldType.OBJECT)
.description(MgmtApiModelProperties.LINK_TO_ACTION),
fieldWithPath("total").type(JsonFieldType.NUMBER)
.description(MgmtApiModelProperties.DS_TOTAL_ASSIGNED_TARGETS))));
responseFields(getDsAssignmentResponseFieldDescriptors())));
}
@Test
@Description("Handles the POST request for assigning distribution sets to a specific target. Required Permission: READ_REPOSITORY and UPDATE_TARGET.")
public void postAssignDistributionSetsToTarget() throws Exception {
// create target and ds, and assign ds
final List<DistributionSet> sets = testdataFactory.createDistributionSets(2);
testdataFactory.createTarget(targetId);
final long forceTime = System.currentTimeMillis();
final JSONArray body = new JSONArray();
body.put(new JSONObject().put("id", sets.get(1).getId()).put("type", "timeforced")
.put("forcetime", forceTime)
.put("maintenanceWindow", new JSONObject().put("schedule", getTestSchedule(100))
.put("duration", getTestDuration(10)).put("timezone", getTestTimeZone())))
.toString();
body.put(new JSONObject().put("id", sets.get(0).getId()).put("type", "forced"));
enableMultiAssignments();
mockMvc.perform(post(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/{targetId}/"
+ MgmtRestConstants.TARGET_V1_ASSIGNED_DISTRIBUTION_SET, targetId).content(body.toString())
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andDo(this.document.document(
pathParameters(parameterWithName("targetId").description(ApiModelPropertiesGeneric.ITEM_ID)),
requestParameters(parameterWithName("offline")
.description(MgmtApiModelProperties.OFFLINE_UPDATE).optional()),
requestFields(
requestFieldWithPath("[].id").description(ApiModelPropertiesGeneric.ITEM_ID),
optionalRequestFieldWithPath("[].forcetime")
.description(MgmtApiModelProperties.FORCETIME),
optionalRequestFieldWithPath("[].maintenanceWindow")
.description(MgmtApiModelProperties.MAINTENANCE_WINDOW),
optionalRequestFieldWithPath("[].maintenanceWindow.schedule")
.description(MgmtApiModelProperties.MAINTENANCE_WINDOW_SCHEDULE),
optionalRequestFieldWithPath("[].maintenanceWindow.duration")
.description(MgmtApiModelProperties.MAINTENANCE_WINDOW_DURATION),
optionalRequestFieldWithPath("[].maintenanceWindow.timezone")
.description(MgmtApiModelProperties.MAINTENANCE_WINDOW_TIMEZONE),
optionalRequestFieldWithPath("[].type")
.description(MgmtApiModelProperties.ASSIGNMENT_TYPE)
.attributes(key("[].value")
.value("['soft', 'forced','timeforced', 'downloadonly']"))),
responseFields(getDsAssignmentResponseFieldDescriptors())));
}
private static FieldDescriptor[] getDsAssignmentResponseFieldDescriptors() {
final FieldDescriptor[] descriptors = {
fieldWithPath("assigned").description(MgmtApiModelProperties.DS_NEW_ASSIGNED_TARGETS),
fieldWithPath("alreadyAssigned").type(JsonFieldType.NUMBER)
.description(MgmtApiModelProperties.DS_ALREADY_ASSIGNED_TARGETS),
fieldWithPath("assignedActions").type(JsonFieldType.ARRAY)
.description(MgmtApiModelProperties.DS_NEW_ASSIGNED_ACTIONS),
fieldWithPath("assignedActions.[].id").type(JsonFieldType.NUMBER)
.description(MgmtApiModelProperties.ACTION_ID),
fieldWithPath("assignedActions.[]._links.self").type(JsonFieldType.OBJECT)
.description(MgmtApiModelProperties.LINK_TO_ACTION),
fieldWithPath("total").type(JsonFieldType.NUMBER)
.description(MgmtApiModelProperties.DS_TOTAL_ASSIGNED_TARGETS) };
return descriptors;
}
@Test