Feature Reset Target Attributes (#664)
* Enhance DDI putConfigData REST entry point with update mode Signed-off-by: stefbehl <stefan.behl@bosch-si.com> * ControllerManagement unit tests for new target attribute update modes Signed-off-by: stefbehl <stefan.behl@bosch-si.com> * Added DdiRootControllerRestApi test for new update mode Signed-off-by: stefbehl <stefan.behl@bosch-si.com> * Enhance DMF UPDATE_ATTRIBUTES message with update mode Signed-off-by: stefbehl <stefan.behl@bosch-si.com> * Enhance DMF integration tests Signed-off-by: stefbehl <stefan.behl@bosch-si.com> * Enhance DMF integration tests Signed-off-by: stefbehl <stefan.behl@bosch-si.com> * Fix DMF integration tests * Fix failing tests Signed-off-by: stefbehl <stefan.behl@bosch-si.com> * Fix failing tests Signed-off-by: stefbehl <stefan.behl@bosch-si.com> * Fix Sonar findings Signed-off-by: stefbehl <stefan.behl@bosch-si.com> * Javadoc improvements Signed-off-by: stefbehl <stefan.behl@bosch-si.com> * Fix codacy findings Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com> * Fix PR review findings Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com> * Fix PR review findings Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com> * Fix PR review findings Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com> * Fix PR review findings Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com> * Fix PR review findings Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com> * Fix PR review findings Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com> * Fix PR review findings Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>
This commit is contained in:
committed by
Kai Zimmermann
parent
e700acc312
commit
607cf92a9e
@@ -17,74 +17,96 @@ public enum SpServerError {
|
||||
*
|
||||
*/
|
||||
SP_REPO_GENERIC_ERROR("hawkbit.server.error.repo.genericError", "unknown error occured"),
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
SP_REPO_ENTITY_ALRREADY_EXISTS("hawkbit.server.error.repo.entitiyAlreayExists", "The given entity already exists in database"),
|
||||
SP_REPO_ENTITY_ALRREADY_EXISTS("hawkbit.server.error.repo.entitiyAlreayExists",
|
||||
"The given entity already exists in database"),
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
SP_REPO_CONSTRAINT_VIOLATION("hawkbit.server.error.repo.constraintViolation", "The given entity cannot be saved due to Constraint Violation"),
|
||||
SP_REPO_CONSTRAINT_VIOLATION("hawkbit.server.error.repo.constraintViolation",
|
||||
"The given entity cannot be saved due to Constraint Violation"),
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
SP_REPO_INVALID_TARGET_ADDRESS("hawkbit.server.error.repo.invalidTargetAddress", "The target address is not well formed"),
|
||||
SP_REPO_INVALID_TARGET_ADDRESS("hawkbit.server.error.repo.invalidTargetAddress",
|
||||
"The target address is not well formed"),
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
SP_REPO_ENTITY_NOT_EXISTS("hawkbit.server.error.repo.entitiyNotFound", "The given entity does not exist in the repository"),
|
||||
*
|
||||
*/
|
||||
SP_REPO_ENTITY_NOT_EXISTS("hawkbit.server.error.repo.entitiyNotFound",
|
||||
"The given entity does not exist in the repository"),
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
SP_REPO_CONCURRENT_MODIFICATION("hawkbit.server.error.repo.concurrentModification", "The given entity has been changed by another user/session"),
|
||||
*
|
||||
*/
|
||||
SP_REPO_CONCURRENT_MODIFICATION("hawkbit.server.error.repo.concurrentModification",
|
||||
"The given entity has been changed by another user/session"),
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
SP_REST_SORT_PARAM_SYNTAX("hawkbit.server.error.rest.param.sortParamSyntax", "The given sort paramter is not well formed"),
|
||||
*
|
||||
*/
|
||||
SP_REST_SORT_PARAM_SYNTAX("hawkbit.server.error.rest.param.sortParamSyntax",
|
||||
"The given sort paramter is not well formed"),
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
SP_REST_RSQL_SEARCH_PARAM_SYNTAX("hawkbit.server.error.rest.param.rsqlParamSyntax",
|
||||
"The given search paramter is not well formed"),
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
SP_REST_RSQL_PARAM_INVALID_FIELD("hawkbit.server.error.rest.param.rsqlInvalidField",
|
||||
"The given search parameter field does not exist"),
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
SP_REST_SORT_PARAM_INVALID_FIELD("hawkbit.server.error.rest.param.invalidField",
|
||||
"The given sort parameter field does not exist"),
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
SP_REST_SORT_PARAM_INVALID_DIRECTION("hawkbit.server.error.rest.param.invalidDirection",
|
||||
"The given sort parameter direction does not exist"),
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
SP_REST_BODY_NOT_READABLE("hawkbit.server.error.rest.body.notReadable",
|
||||
"The given request body is not well formed"),
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
SP_ARTIFACT_UPLOAD_FAILED("hawkbit.server.error.artifact.uploadFailed",
|
||||
"Upload of artifact failed with internal server error."),
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
SP_REST_RSQL_SEARCH_PARAM_SYNTAX("hawkbit.server.error.rest.param.rsqlParamSyntax", "The given search paramter is not well formed"),
|
||||
SP_ARTIFACT_UPLOAD_FAILED_MD5_MATCH("hawkbit.server.error.artifact.uploadFailed.checksum.md5.match",
|
||||
"Upload of artifact failed as the provided MD5 checksum did not match with the provided artifact."),
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
SP_REST_RSQL_PARAM_INVALID_FIELD("hawkbit.server.error.rest.param.rsqlInvalidField", "The given search parameter field does not exist"),
|
||||
SP_ARTIFACT_UPLOAD_FAILED_SHA1_MATCH("hawkbit.server.error.artifact.uploadFailed.checksum.sha1.match",
|
||||
"Upload of artifact failed as the provided SHA1 checksum did not match with the provided artifact."),
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
SP_REST_SORT_PARAM_INVALID_FIELD("hawkbit.server.error.rest.param.invalidField", "The given sort parameter field does not exist"),
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
SP_REST_SORT_PARAM_INVALID_DIRECTION("hawkbit.server.error.rest.param.invalidDirection", "The given sort parameter direction does not exist"),
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
SP_REST_BODY_NOT_READABLE("hawkbit.server.error.rest.body.notReadable", "The given request body is not well formed"),
|
||||
/**
|
||||
*
|
||||
*/
|
||||
SP_ARTIFACT_UPLOAD_FAILED("hawkbit.server.error.artifact.uploadFailed", "Upload of artifact failed with internal server error."),
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
SP_ARTIFACT_UPLOAD_FAILED_MD5_MATCH("hawkbit.server.error.artifact.uploadFailed.checksum.md5.match", "Upload of artifact failed as the provided MD5 checksum did not match with the provided artifact."),
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
SP_ARTIFACT_UPLOAD_FAILED_SHA1_MATCH("hawkbit.server.error.artifact.uploadFailed.checksum.sha1.match", "Upload of artifact failed as the provided SHA1 checksum did not match with the provided artifact."),
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
SP_DS_CREATION_FAILED_MISSING_MODULE("hawkbit.server.error.distributionset.creationFailed.missingModule", "Creation if Distribution Set failed as module is missing that is configured as mandatory."),
|
||||
SP_DS_CREATION_FAILED_MISSING_MODULE("hawkbit.server.error.distributionset.creationFailed.missingModule",
|
||||
"Creation if Distribution Set failed as module is missing that is configured as mandatory."),
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -94,63 +116,74 @@ public enum SpServerError {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
SP_ARTIFACT_DELETE_FAILED("hawkbit.server.error.artifact.deleteFailed", "Deletion of artifact failed with internal server error."),
|
||||
/**
|
||||
*
|
||||
*/
|
||||
SP_ARTIFACT_LOAD_FAILED("hawkbit.server.error.artifact.loadFailed", "Load of artifact failed with internal server error."),
|
||||
SP_ARTIFACT_DELETE_FAILED("hawkbit.server.error.artifact.deleteFailed",
|
||||
"Deletion of artifact failed with internal server error."),
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
*
|
||||
*/
|
||||
SP_ARTIFACT_LOAD_FAILED("hawkbit.server.error.artifact.loadFailed",
|
||||
"Load of artifact failed with internal server error."),
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
SP_QUOTA_EXCEEDED("hawkbit.server.error.quota.tooManyEntries", "Too many entries have been inserted."),
|
||||
|
||||
/**
|
||||
* error message, which describes that the action can not be canceled cause
|
||||
* the action is inactive.
|
||||
*/
|
||||
SP_ACTION_NOT_CANCELABLE("hawkbit.server.error.action.notcancelable", "Only active actions which are in status pending are canceable."),
|
||||
SP_ACTION_NOT_CANCELABLE("hawkbit.server.error.action.notcancelable",
|
||||
"Only active actions which are in status pending are canceable."),
|
||||
|
||||
/**
|
||||
* error message, which describes that the action can not be force quit
|
||||
* cause the action is inactive.
|
||||
*/
|
||||
SP_ACTION_NOT_FORCE_QUITABLE("hawkbit.server.error.action.notforcequitable", "Only active actions which are in status pending can be force quit."),
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
SP_DS_INCOMPLETE("hawkbit.server.error.distributionset.incomplete", "Distribution set is assigned to a a target that is incomplete (i.e. mandatory modules are missing)"),
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
SP_DS_TYPE_UNDEFINED("hawkbit.server.error.distributionset.type.undefined", "Distribution set type is not yet defined. Modules cannot be added until definition."),
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
SP_DS_MODULE_UNSUPPORTED("hawkbit.server.error.distributionset.modules.unsupported", "Distribution set type does not contain the given module, i.e. is incompatible."),
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
SP_REPO_TENANT_NOT_EXISTS("hawkbit.server.error.repo.tenantNotExists", "The entity cannot be inserted due the tenant does not exists"),
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
SP_ENTITY_LOCKED("hawkbit.server.error.entitiylocked", "The given entity is locked by the server."),
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
SP_REPO_ENTITY_READ_ONLY("hawkbit.server.error.entityreadonly", "The given entity is read only and the change cannot be completed."),
|
||||
SP_ACTION_NOT_FORCE_QUITABLE("hawkbit.server.error.action.notforcequitable",
|
||||
"Only active actions which are in status pending can be force quit."),
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
SP_CONFIGURATION_VALUE_INVALID("hawkbit.server.error.configValueInvalid", "The given configuration value is invalid."),
|
||||
SP_DS_INCOMPLETE("hawkbit.server.error.distributionset.incomplete",
|
||||
"Distribution set is assigned to a a target that is incomplete (i.e. mandatory modules are missing)"),
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
SP_DS_TYPE_UNDEFINED("hawkbit.server.error.distributionset.type.undefined",
|
||||
"Distribution set type is not yet defined. Modules cannot be added until definition."),
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
SP_DS_MODULE_UNSUPPORTED("hawkbit.server.error.distributionset.modules.unsupported",
|
||||
"Distribution set type does not contain the given module, i.e. is incompatible."),
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
SP_REPO_TENANT_NOT_EXISTS("hawkbit.server.error.repo.tenantNotExists",
|
||||
"The entity cannot be inserted due the tenant does not exists"),
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
SP_ENTITY_LOCKED("hawkbit.server.error.entitiylocked", "The given entity is locked by the server."),
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
SP_REPO_ENTITY_READ_ONLY("hawkbit.server.error.entityreadonly",
|
||||
"The given entity is read only and the change cannot be completed."),
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
SP_CONFIGURATION_VALUE_INVALID("hawkbit.server.error.configValueInvalid",
|
||||
"The given configuration value is invalid."),
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@@ -159,22 +192,26 @@ public enum SpServerError {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
SP_ROLLOUT_ILLEGAL_STATE("hawkbit.server.error.rollout.illegalstate", "The rollout is in the wrong state for the requested operation"),
|
||||
SP_ROLLOUT_ILLEGAL_STATE("hawkbit.server.error.rollout.illegalstate",
|
||||
"The rollout is in the wrong state for the requested operation"),
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
SP_ROLLOUT_VERIFICATION_FAILED("hawkbit.server.error.rollout.verificationFailed", "The rollout configuration could not be verified successfully"),
|
||||
SP_ROLLOUT_VERIFICATION_FAILED("hawkbit.server.error.rollout.verificationFailed",
|
||||
"The rollout configuration could not be verified successfully"),
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
SP_REPO_OPERATION_NOT_SUPPORTED("hawkbit.server.error.operation.notSupported", "Operation or method is (no longer) supported by service."),
|
||||
*
|
||||
*/
|
||||
SP_REPO_OPERATION_NOT_SUPPORTED("hawkbit.server.error.operation.notSupported",
|
||||
"Operation or method is (no longer) supported by service."),
|
||||
|
||||
/**
|
||||
* Error message informing that the maintenance schedule is invalid.
|
||||
*/
|
||||
SP_MAINTENANCE_SCHEDULE_INVALID("hawkbit.server.error.maintenanceScheduleInvalid", "Information for schedule, duration or timezone is missing; or there is no valid maintenance window available in future.");
|
||||
SP_MAINTENANCE_SCHEDULE_INVALID("hawkbit.server.error.maintenanceScheduleInvalid",
|
||||
"Information for schedule, duration or timezone is missing; or there is no valid maintenance window available in future.");
|
||||
|
||||
private final String key;
|
||||
private final String message;
|
||||
|
||||
@@ -23,6 +23,8 @@ public class DdiConfigData extends DdiActionFeedback {
|
||||
@NotEmpty
|
||||
private final Map<String, String> data;
|
||||
|
||||
private final DdiUpdateMode mode;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
@@ -38,18 +40,24 @@ public class DdiConfigData extends DdiActionFeedback {
|
||||
@JsonCreator
|
||||
public DdiConfigData(@JsonProperty(value = "id") final Long id, @JsonProperty(value = "time") final String time,
|
||||
@JsonProperty(value = "status") final DdiStatus status,
|
||||
@JsonProperty(value = "data") final Map<String, String> data) {
|
||||
@JsonProperty(value = "data") final Map<String, String> data,
|
||||
@JsonProperty(value = "mode") final DdiUpdateMode mode) {
|
||||
super(id, time, status);
|
||||
this.data = data;
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
public Map<String, String> getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public DdiUpdateMode getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ConfigData [data=" + data + ", toString()=" + super.toString() + "]";
|
||||
return "ConfigData [data=" + data + ", mode=" + mode + ", toString()=" + super.toString() + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) 2018 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.ddi.json.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
|
||||
/**
|
||||
* Enumerates the supported update modes. Each mode represents an attribute
|
||||
* update strategy.
|
||||
*
|
||||
* @see DdiConfigData
|
||||
*/
|
||||
public enum DdiUpdateMode {
|
||||
|
||||
/**
|
||||
* Merge update strategy
|
||||
*/
|
||||
MERGE("merge"),
|
||||
|
||||
/**
|
||||
* Replacement update strategy
|
||||
*/
|
||||
REPLACE("replace"),
|
||||
|
||||
/**
|
||||
* Removal update strategy
|
||||
*/
|
||||
REMOVE("remove");
|
||||
|
||||
private String name;
|
||||
|
||||
DdiUpdateMode(final String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@JsonValue
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import org.eclipse.hawkbit.ddi.json.model.DdiDeployment.HandlingType;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiDeployment.DdiMaintenanceWindowStatus;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiDeploymentBase;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiResult.FinalResult;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiUpdateMode;
|
||||
import org.eclipse.hawkbit.ddi.rest.api.DdiRestConstants;
|
||||
import org.eclipse.hawkbit.ddi.rest.api.DdiRootControllerRestApi;
|
||||
import org.eclipse.hawkbit.repository.ArtifactManagement;
|
||||
@@ -39,6 +40,7 @@ import org.eclipse.hawkbit.repository.ControllerManagement;
|
||||
import org.eclipse.hawkbit.repository.EntityFactory;
|
||||
import org.eclipse.hawkbit.repository.RepositoryConstants;
|
||||
import org.eclipse.hawkbit.repository.SystemManagement;
|
||||
import org.eclipse.hawkbit.repository.UpdateMode;
|
||||
import org.eclipse.hawkbit.repository.builder.ActionStatusCreate;
|
||||
import org.eclipse.hawkbit.repository.event.remote.DownloadProgressEvent;
|
||||
import org.eclipse.hawkbit.repository.exception.ArtifactBinaryNotFoundException;
|
||||
@@ -440,8 +442,8 @@ public class DdiRootController implements DdiRootControllerRestApi {
|
||||
@Override
|
||||
public ResponseEntity<Void> putConfigData(@Valid @RequestBody final DdiConfigData configData,
|
||||
@PathVariable("tenant") final String tenant, @PathVariable("controllerId") final String controllerId) {
|
||||
controllerManagement.updateControllerAttributes(controllerId, configData.getData());
|
||||
|
||||
controllerManagement.updateControllerAttributes(controllerId, configData.getData(), getUpdateMode(configData));
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
@@ -579,4 +581,16 @@ public class DdiRootController implements DdiRootControllerRestApi {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the update mode from the given update message.
|
||||
*/
|
||||
private static UpdateMode getUpdateMode(final DdiConfigData configData) {
|
||||
final DdiUpdateMode mode = configData.getMode();
|
||||
if (mode != null) {
|
||||
return UpdateMode.valueOf(mode.name());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ import com.google.common.collect.Maps;
|
||||
|
||||
import ru.yandex.qatools.allure.annotations.Description;
|
||||
import ru.yandex.qatools.allure.annotations.Features;
|
||||
import ru.yandex.qatools.allure.annotations.Step;
|
||||
import ru.yandex.qatools.allure.annotations.Stories;
|
||||
|
||||
/**
|
||||
@@ -46,6 +47,7 @@ public class DdiConfigDataTest extends AbstractDDiApiIntegrationTest {
|
||||
@Test
|
||||
@Description("We verify that the config data (i.e. device attributes like serial number, hardware revision etc.) "
|
||||
+ "are requested only once from the device.")
|
||||
@SuppressWarnings("squid:S2925")
|
||||
public void requestConfigDataIfEmpty() throws Exception {
|
||||
final Target savedTarget = testdataFactory.createTarget("4712");
|
||||
|
||||
@@ -69,10 +71,9 @@ public class DdiConfigDataTest extends AbstractDDiApiIntegrationTest {
|
||||
attributes.put("dsafsdf", "sdsds");
|
||||
|
||||
final Target updateControllerAttributes = controllerManagement
|
||||
.updateControllerAttributes(savedTarget.getControllerId(), attributes);
|
||||
.updateControllerAttributes(savedTarget.getControllerId(), attributes, null);
|
||||
// request controller attributes need to be false because we don't want
|
||||
// to request the
|
||||
// controller attributes again
|
||||
// to request the controller attributes again
|
||||
assertThat(updateControllerAttributes.isRequestControllerAttributes()).isFalse();
|
||||
|
||||
mvc.perform(
|
||||
@@ -133,7 +134,7 @@ public class DdiConfigDataTest extends AbstractDDiApiIntegrationTest {
|
||||
@Description("We verify that the config data (i.e. device attributes like serial number, hardware revision etc.) "
|
||||
+ "resource behaves as exptected in cae of invalid request attempts.")
|
||||
public void badConfigData() throws Exception {
|
||||
final Target savedTarget = testdataFactory.createTarget("4712");
|
||||
testdataFactory.createTarget("4712");
|
||||
|
||||
// not allowed methods
|
||||
mvc.perform(post("/{tenant}/controller/v1/4712/configData", tenantAware.getCurrentTenant()))
|
||||
@@ -163,4 +164,143 @@ public class DdiConfigDataTest extends AbstractDDiApiIntegrationTest {
|
||||
.content("{\"id\": \"51659181\"}").contentType(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isBadRequest());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verify that config data (device attributes) can be updated by the controller using different update modes (merge, replace, remove).")
|
||||
public void putConfigDataWithDifferentUpdateModes() throws Exception {
|
||||
|
||||
// create a target
|
||||
final String controllerId = "4717";
|
||||
testdataFactory.createTarget(controllerId);
|
||||
final String configDataPath = "/{tenant}/controller/v1/" + controllerId + "/configData";
|
||||
|
||||
// no update mode
|
||||
putConfigDataWithoutUpdateMode(controllerId, configDataPath);
|
||||
|
||||
// update mode REPLACE
|
||||
putConfigDataWithUpdateModeReplace(controllerId, configDataPath);
|
||||
|
||||
// update mode MERGE
|
||||
putConfigDataWithUpdateModeMerge(controllerId, configDataPath);
|
||||
|
||||
// update mode REMOVE
|
||||
putConfigDataWithUpdateModeRemove(controllerId, configDataPath);
|
||||
|
||||
// invalid update mode
|
||||
putConfigDataWithInvalidUpdateMode(configDataPath);
|
||||
|
||||
}
|
||||
|
||||
@Step
|
||||
private void putConfigDataWithInvalidUpdateMode(final String configDataPath)
|
||||
throws Exception {
|
||||
|
||||
// create some attriutes
|
||||
final Map<String, String> attributes = new HashMap<>();
|
||||
attributes.put("k0", "v0");
|
||||
attributes.put("k1", "v1");
|
||||
|
||||
// use an invalid update mode
|
||||
mvc.perform(put(configDataPath, tenantAware.getCurrentTenant())
|
||||
.content(JsonBuilder.configData("", attributes, "closed", "KJHGKJHGKJHG"))
|
||||
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isBadRequest());
|
||||
}
|
||||
|
||||
@Step
|
||||
private void putConfigDataWithUpdateModeRemove(final String controllerId, final String configDataPath)
|
||||
throws Exception {
|
||||
|
||||
// get the current attributes
|
||||
final int previousSize = targetManagement.getControllerAttributes(controllerId).size();
|
||||
|
||||
// update the attributes using update mode REMOVE
|
||||
final Map<String, String> removeAttributes = new HashMap<>();
|
||||
removeAttributes.put("k1", "foo");
|
||||
removeAttributes.put("k3", "bar");
|
||||
|
||||
mvc.perform(put(configDataPath, tenantAware.getCurrentTenant())
|
||||
.content(JsonBuilder.configData("", removeAttributes, "closed", "remove"))
|
||||
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk());
|
||||
|
||||
// verify attribute removal
|
||||
final Map<String, String> updatedAttributes = targetManagement.getControllerAttributes(controllerId);
|
||||
assertThat(updatedAttributes.size()).isEqualTo(previousSize - 2);
|
||||
assertThat(updatedAttributes).doesNotContainKeys("k1", "k3");
|
||||
|
||||
}
|
||||
|
||||
@Step
|
||||
private void putConfigDataWithUpdateModeMerge(final String controllerId, final String configDataPath)
|
||||
throws Exception {
|
||||
|
||||
// get the current attributes
|
||||
final Map<String, String> attributes = new HashMap<>(targetManagement.getControllerAttributes(controllerId));
|
||||
|
||||
// update the attributes using update mode MERGE
|
||||
final Map<String, String> mergeAttributes = new HashMap<>();
|
||||
mergeAttributes.put("k1", "v1_modified_again");
|
||||
mergeAttributes.put("k4", "v4");
|
||||
mvc.perform(put(configDataPath, tenantAware.getCurrentTenant())
|
||||
.content(JsonBuilder.configData("", mergeAttributes, "closed", "merge"))
|
||||
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk());
|
||||
|
||||
// verify attribute merge
|
||||
final Map<String, String> updatedAttributes = targetManagement.getControllerAttributes(controllerId);
|
||||
assertThat(updatedAttributes.size()).isEqualTo(4);
|
||||
assertThat(updatedAttributes).containsAllEntriesOf(mergeAttributes);
|
||||
assertThat(updatedAttributes.get("k1")).isEqualTo("v1_modified_again");
|
||||
attributes.keySet().forEach(assertThat(updatedAttributes)::containsKey);
|
||||
|
||||
}
|
||||
|
||||
@Step
|
||||
private void putConfigDataWithUpdateModeReplace(final String controllerId, final String configDataPath)
|
||||
throws Exception {
|
||||
|
||||
// get the current attributes
|
||||
final Map<String, String> attributes = new HashMap<>(targetManagement.getControllerAttributes(controllerId));
|
||||
|
||||
// update the attributes using update mode REPLACE
|
||||
final Map<String, String> replacementAttributes = new HashMap<>();
|
||||
replacementAttributes.put("k1", "v1_modified");
|
||||
replacementAttributes.put("k2", "v2");
|
||||
replacementAttributes.put("k3", "v3");
|
||||
mvc.perform(put(configDataPath, tenantAware.getCurrentTenant())
|
||||
.content(JsonBuilder.configData("", replacementAttributes, "closed", "replace"))
|
||||
.contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk());
|
||||
|
||||
// verify attribute replacement
|
||||
final Map<String, String> updatedAttributes = targetManagement.getControllerAttributes(controllerId);
|
||||
assertThat(updatedAttributes.size()).isEqualTo(replacementAttributes.size());
|
||||
assertThat(updatedAttributes).containsAllEntriesOf(replacementAttributes);
|
||||
assertThat(updatedAttributes.get("k1")).isEqualTo("v1_modified");
|
||||
attributes.entrySet().forEach(assertThat(updatedAttributes)::doesNotContain);
|
||||
|
||||
}
|
||||
|
||||
@Step
|
||||
private void putConfigDataWithoutUpdateMode(final String controllerId, final String configDataPath)
|
||||
throws Exception {
|
||||
|
||||
// create some attriutes
|
||||
final Map<String, String> attributes = new HashMap<>();
|
||||
attributes.put("k0", "v0");
|
||||
attributes.put("k1", "v1");
|
||||
|
||||
// set the initial attributes
|
||||
mvc.perform(put(configDataPath, tenantAware.getCurrentTenant())
|
||||
.content(JsonBuilder.configData("", attributes, "closed")).contentType(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
|
||||
|
||||
// verify the initial parameters
|
||||
final Map<String, String> updatedAttributes = targetManagement.getControllerAttributes(controllerId);
|
||||
assertThat(updatedAttributes.size()).isEqualTo(attributes.size());
|
||||
assertThat(updatedAttributes).containsAllEntriesOf(attributes);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,11 +22,13 @@ import org.eclipse.hawkbit.dmf.amqp.api.MessageHeaderKey;
|
||||
import org.eclipse.hawkbit.dmf.amqp.api.MessageType;
|
||||
import org.eclipse.hawkbit.dmf.json.model.DmfActionUpdateStatus;
|
||||
import org.eclipse.hawkbit.dmf.json.model.DmfAttributeUpdate;
|
||||
import org.eclipse.hawkbit.dmf.json.model.DmfUpdateMode;
|
||||
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
|
||||
import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails;
|
||||
import org.eclipse.hawkbit.repository.ControllerManagement;
|
||||
import org.eclipse.hawkbit.repository.EntityFactory;
|
||||
import org.eclipse.hawkbit.repository.RepositoryConstants;
|
||||
import org.eclipse.hawkbit.repository.UpdateMode;
|
||||
import org.eclipse.hawkbit.repository.builder.ActionStatusCreate;
|
||||
import org.eclipse.hawkbit.repository.model.Action;
|
||||
import org.eclipse.hawkbit.repository.model.Action.Status;
|
||||
@@ -248,8 +250,8 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
|
||||
private void updateAttributes(final Message message) {
|
||||
final DmfAttributeUpdate attributeUpdate = convertMessage(message, DmfAttributeUpdate.class);
|
||||
final String thingId = getStringHeaderKey(message, MessageHeaderKey.THING_ID, "ThingId is null");
|
||||
|
||||
controllerManagement.updateControllerAttributes(thingId, attributeUpdate.getAttributes());
|
||||
controllerManagement.updateControllerAttributes(thingId, attributeUpdate.getAttributes(),
|
||||
getUpdateMode(attributeUpdate));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -364,4 +366,16 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
|
||||
|
||||
return findActionWithDetails.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the update mode from the given update message.
|
||||
*/
|
||||
private static UpdateMode getUpdateMode(final DmfAttributeUpdate update) {
|
||||
final DmfUpdateMode mode = update.getMode();
|
||||
if (mode != null) {
|
||||
return UpdateMode.valueOf(mode.name());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -36,9 +36,11 @@ import org.eclipse.hawkbit.dmf.json.model.DmfActionStatus;
|
||||
import org.eclipse.hawkbit.dmf.json.model.DmfActionUpdateStatus;
|
||||
import org.eclipse.hawkbit.dmf.json.model.DmfAttributeUpdate;
|
||||
import org.eclipse.hawkbit.dmf.json.model.DmfDownloadResponse;
|
||||
import org.eclipse.hawkbit.dmf.json.model.DmfUpdateMode;
|
||||
import org.eclipse.hawkbit.repository.ArtifactManagement;
|
||||
import org.eclipse.hawkbit.repository.ControllerManagement;
|
||||
import org.eclipse.hawkbit.repository.EntityFactory;
|
||||
import org.eclipse.hawkbit.repository.UpdateMode;
|
||||
import org.eclipse.hawkbit.repository.builder.ActionStatusBuilder;
|
||||
import org.eclipse.hawkbit.repository.builder.ActionStatusCreate;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||
@@ -123,6 +125,9 @@ public class AmqpMessageHandlerServiceTest {
|
||||
@Captor
|
||||
private ArgumentCaptor<String> targetIdCaptor;
|
||||
|
||||
@Captor
|
||||
private ArgumentCaptor<UpdateMode> modeCaptor;
|
||||
|
||||
@Before
|
||||
public void before() throws Exception {
|
||||
messageConverter = new Jackson2JsonMessageConverter();
|
||||
@@ -181,7 +186,7 @@ public class AmqpMessageHandlerServiceTest {
|
||||
public void updateAttributes() {
|
||||
final String knownThingId = "1";
|
||||
final MessageProperties messageProperties = createMessageProperties(MessageType.EVENT);
|
||||
messageProperties.setHeader(MessageHeaderKey.THING_ID, "1");
|
||||
messageProperties.setHeader(MessageHeaderKey.THING_ID, knownThingId);
|
||||
messageProperties.setHeader(MessageHeaderKey.TOPIC, "UPDATE_ATTRIBUTES");
|
||||
final DmfAttributeUpdate attributeUpdate = new DmfAttributeUpdate();
|
||||
attributeUpdate.getAttributes().put("testKey1", "testValue1");
|
||||
@@ -190,8 +195,8 @@ public class AmqpMessageHandlerServiceTest {
|
||||
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(attributeUpdate,
|
||||
messageProperties);
|
||||
|
||||
when(controllerManagementMock.updateControllerAttributes(targetIdCaptor.capture(), attributesCaptor.capture()))
|
||||
.thenReturn(null);
|
||||
when(controllerManagementMock.updateControllerAttributes(targetIdCaptor.capture(), attributesCaptor.capture(),
|
||||
modeCaptor.capture())).thenReturn(null);
|
||||
|
||||
amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, "vHost");
|
||||
|
||||
@@ -202,6 +207,50 @@ public class AmqpMessageHandlerServiceTest {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verifies that the update mode is retrieved from the UPDATE_ATTRIBUTES message and passed to the controller management.")
|
||||
public void attributeUpdateModes() {
|
||||
final String knownThingId = "1";
|
||||
final MessageProperties messageProperties = createMessageProperties(MessageType.EVENT);
|
||||
messageProperties.setHeader(MessageHeaderKey.THING_ID, knownThingId);
|
||||
messageProperties.setHeader(MessageHeaderKey.TOPIC, "UPDATE_ATTRIBUTES");
|
||||
final DmfAttributeUpdate attributeUpdate = new DmfAttributeUpdate();
|
||||
attributeUpdate.getAttributes().put("testKey1", "testValue1");
|
||||
attributeUpdate.getAttributes().put("testKey2", "testValue2");
|
||||
|
||||
when(controllerManagementMock.updateControllerAttributes(targetIdCaptor.capture(), attributesCaptor.capture(),
|
||||
modeCaptor.capture())).thenReturn(null);
|
||||
|
||||
// send a message which does not specify a update mode
|
||||
Message message = amqpMessageHandlerService.getMessageConverter().toMessage(attributeUpdate, messageProperties);
|
||||
amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, "vHost");
|
||||
// verify that NO fallback is made on the way to the controller
|
||||
// management layer
|
||||
assertThat(modeCaptor.getValue()).isNull();
|
||||
|
||||
// send a message which specifies update mode MERGE
|
||||
attributeUpdate.setMode(DmfUpdateMode.MERGE);
|
||||
message = amqpMessageHandlerService.getMessageConverter().toMessage(attributeUpdate, messageProperties);
|
||||
amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, "vHost");
|
||||
// verify that the update mode is converted and forwarded as expected
|
||||
assertThat(modeCaptor.getValue()).isEqualTo(UpdateMode.MERGE);
|
||||
|
||||
// send a message which specifies update mode REPLACE
|
||||
attributeUpdate.setMode(DmfUpdateMode.REPLACE);
|
||||
message = amqpMessageHandlerService.getMessageConverter().toMessage(attributeUpdate, messageProperties);
|
||||
amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, "vHost");
|
||||
// verify that the update mode is converted and forwarded as expected
|
||||
assertThat(modeCaptor.getValue()).isEqualTo(UpdateMode.REPLACE);
|
||||
|
||||
// send a message which specifies update mode REMOVE
|
||||
attributeUpdate.setMode(DmfUpdateMode.REMOVE);
|
||||
message = amqpMessageHandlerService.getMessageConverter().toMessage(attributeUpdate, messageProperties);
|
||||
amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, "vHost");
|
||||
// verify that the update mode is converted and forwarded as expected
|
||||
assertThat(modeCaptor.getValue()).isEqualTo(UpdateMode.REMOVE);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests the creation of a thing without a 'reply to' header in message.")
|
||||
public void createThingWitoutReplyTo() {
|
||||
|
||||
@@ -10,7 +10,9 @@ package org.eclipse.hawkbit.integration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -22,6 +24,7 @@ import org.eclipse.hawkbit.dmf.amqp.api.MessageType;
|
||||
import org.eclipse.hawkbit.dmf.json.model.DmfActionStatus;
|
||||
import org.eclipse.hawkbit.dmf.json.model.DmfActionUpdateStatus;
|
||||
import org.eclipse.hawkbit.dmf.json.model.DmfAttributeUpdate;
|
||||
import org.eclipse.hawkbit.dmf.json.model.DmfUpdateMode;
|
||||
import org.eclipse.hawkbit.repository.RepositoryConstants;
|
||||
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.TargetPollEvent;
|
||||
@@ -49,6 +52,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import ru.yandex.qatools.allure.annotations.Description;
|
||||
import ru.yandex.qatools.allure.annotations.Features;
|
||||
import ru.yandex.qatools.allure.annotations.Step;
|
||||
import ru.yandex.qatools.allure.annotations.Stories;
|
||||
|
||||
@Features("Component Tests - Device Management Federation API")
|
||||
@@ -634,23 +638,104 @@ public class AmqpMessageHandlerServiceIntegrationTest extends AmqpServiceIntegra
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verify that sending an update controller attribute message to an existing target works.")
|
||||
@Description("Verify that sending an update controller attribute message to an existing target works. Verify that different update modes (merge, replace, remove) can be used.")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
|
||||
@Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 1) })
|
||||
public void updateAttributes() {
|
||||
@Expect(type = TargetUpdatedEvent.class, count = 4), @Expect(type = TargetPollEvent.class, count = 1) })
|
||||
public void updateAttributesWithDifferentUpdateModes() {
|
||||
final String controllerId = TARGET_PREFIX + "updateAttributes";
|
||||
|
||||
// setup
|
||||
registerAndAssertTargetWithExistingTenant(controllerId);
|
||||
final DmfAttributeUpdate controllerAttribute = new DmfAttributeUpdate();
|
||||
controllerAttribute.getAttributes().put("test1", "testA");
|
||||
controllerAttribute.getAttributes().put("test2", "testB");
|
||||
|
||||
// test
|
||||
sendUpdateAttributeMessage(controllerId, TENANT_EXIST, controllerAttribute);
|
||||
// no update mode specified
|
||||
updateAttributesWithoutUpdateMode(controllerId);
|
||||
|
||||
// update mode REPLACE
|
||||
updateAttributesWithUpdateModeReplace(controllerId);
|
||||
|
||||
// update mode REPLACE
|
||||
updateAttributesWithUpdateModeMerge(controllerId);
|
||||
|
||||
// update mode REMOVE
|
||||
updateAttributesWithUpdateModeRemove(controllerId);
|
||||
|
||||
}
|
||||
|
||||
@Step
|
||||
private void updateAttributesWithUpdateModeRemove(final String controllerId) {
|
||||
|
||||
// assemble the expected attributes
|
||||
final Map<String, String> expectedAttributes = targetManagement.getControllerAttributes(controllerId);
|
||||
expectedAttributes.remove("k1");
|
||||
expectedAttributes.remove("k3");
|
||||
|
||||
// send a update message with update mode
|
||||
final Map<String, String> removeAttributes = new HashMap<>();
|
||||
removeAttributes.put("k1", "foo");
|
||||
removeAttributes.put("k3", "bar");
|
||||
final DmfAttributeUpdate remove = new DmfAttributeUpdate();
|
||||
remove.setMode(DmfUpdateMode.REMOVE);
|
||||
remove.getAttributes().putAll(removeAttributes);
|
||||
sendUpdateAttributeMessage(controllerId, TENANT_EXIST, remove);
|
||||
|
||||
// validate
|
||||
assertUpdateAttributes(controllerId, controllerAttribute.getAttributes());
|
||||
assertUpdateAttributes(controllerId, expectedAttributes);
|
||||
}
|
||||
|
||||
@Step
|
||||
private void updateAttributesWithUpdateModeMerge(final String controllerId) {
|
||||
|
||||
// get the current attributes
|
||||
final Map<String, String> attributes = new HashMap<>(targetManagement.getControllerAttributes(controllerId));
|
||||
|
||||
// send a update message with update mode MERGE
|
||||
final Map<String, String> mergeAttributes = new HashMap<>();
|
||||
mergeAttributes.put("k1", "v1_modified_again");
|
||||
mergeAttributes.put("k4", "v4");
|
||||
final DmfAttributeUpdate merge = new DmfAttributeUpdate();
|
||||
merge.setMode(DmfUpdateMode.MERGE);
|
||||
merge.getAttributes().putAll(mergeAttributes);
|
||||
sendUpdateAttributeMessage(controllerId, TENANT_EXIST, merge);
|
||||
|
||||
// validate
|
||||
final Map<String, String> expectedAttributes = new HashMap<>();
|
||||
expectedAttributes.putAll(attributes);
|
||||
expectedAttributes.putAll(mergeAttributes);
|
||||
assertUpdateAttributes(controllerId, expectedAttributes);
|
||||
}
|
||||
|
||||
@Step
|
||||
private void updateAttributesWithUpdateModeReplace(final String controllerId) {
|
||||
|
||||
// send a update message with update mode REPLACE
|
||||
final Map<String, String> replacementAttributes = new HashMap<>();
|
||||
replacementAttributes.put("k1", "v1_modified");
|
||||
replacementAttributes.put("k2", "v2");
|
||||
replacementAttributes.put("k3", "v3");
|
||||
final DmfAttributeUpdate replace = new DmfAttributeUpdate();
|
||||
replace.setMode(DmfUpdateMode.REPLACE);
|
||||
replace.getAttributes().putAll(replacementAttributes);
|
||||
sendUpdateAttributeMessage(controllerId, TENANT_EXIST, replace);
|
||||
|
||||
// validate
|
||||
final Map<String, String> expectedAttributes = replacementAttributes;
|
||||
assertUpdateAttributes(controllerId, expectedAttributes);
|
||||
}
|
||||
|
||||
@Step
|
||||
private void updateAttributesWithoutUpdateMode(final String controllerId) {
|
||||
|
||||
// send a update message which does not specify a update mode
|
||||
final Map<String, String> initialAttributes = new HashMap<>();
|
||||
initialAttributes.put("k0", "v0");
|
||||
initialAttributes.put("k1", "v1");
|
||||
final DmfAttributeUpdate defaultUpdate = new DmfAttributeUpdate();
|
||||
defaultUpdate.getAttributes().putAll(initialAttributes);
|
||||
sendUpdateAttributeMessage(controllerId, TENANT_EXIST, defaultUpdate);
|
||||
|
||||
// validate
|
||||
final Map<String, String> expectedAttributes = initialAttributes;
|
||||
assertUpdateAttributes(controllerId, expectedAttributes);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -49,6 +49,8 @@ import org.springframework.amqp.rabbit.test.RabbitListenerTestHarness;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.SpringApplicationConfiguration;
|
||||
|
||||
import ru.yandex.qatools.allure.annotations.Step;
|
||||
|
||||
/**
|
||||
*
|
||||
* Common class for {@link AmqpMessageHandlerServiceIntegrationTest} and
|
||||
@@ -233,6 +235,7 @@ public abstract class AmqpServiceIntegrationTest extends AbstractAmqpIntegration
|
||||
return replyMessage;
|
||||
}
|
||||
|
||||
@Step
|
||||
protected void registerAndAssertTargetWithExistingTenant(final String controllerId) {
|
||||
registerAndAssertTargetWithExistingTenant(controllerId, 1);
|
||||
}
|
||||
@@ -326,6 +329,7 @@ public abstract class AmqpServiceIntegrationTest extends AbstractAmqpIntegration
|
||||
|
||||
}
|
||||
|
||||
@Step
|
||||
protected void assertUpdateAttributes(final String controllerId, final Map<String, String> attributes) {
|
||||
final Target findByControllerId = waitUntilIsPresent(
|
||||
() -> controllerManagement.getByControllerId(controllerId));
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.dmf.json.model;
|
||||
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -18,16 +17,28 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* Map of {@link Target} attributes.
|
||||
*
|
||||
* JSON representation of the Attribute Update message.
|
||||
*/
|
||||
@JsonInclude(Include.NON_NULL)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class DmfAttributeUpdate {
|
||||
|
||||
@JsonProperty
|
||||
private final Map<String, String> attributes = new HashMap<>();
|
||||
|
||||
@JsonProperty
|
||||
private DmfUpdateMode mode;
|
||||
|
||||
public DmfUpdateMode getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
public void setMode(final DmfUpdateMode mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
public Map<String, String> getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright (c) 2018 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.dmf.json.model;
|
||||
|
||||
/**
|
||||
* Enumerates the supported update modes. Each mode represents an attribute
|
||||
* update strategy.
|
||||
*
|
||||
* @see DmfAttributeUpdate
|
||||
*/
|
||||
public enum DmfUpdateMode {
|
||||
|
||||
/**
|
||||
* Merge update strategy
|
||||
*/
|
||||
MERGE,
|
||||
|
||||
/**
|
||||
* Replacement update strategy
|
||||
*/
|
||||
REPLACE,
|
||||
|
||||
/**
|
||||
* Removal update strategy
|
||||
*/
|
||||
REMOVE;
|
||||
|
||||
}
|
||||
@@ -1383,7 +1383,7 @@ public class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest
|
||||
knownControllerAttrs.put("a", "1");
|
||||
knownControllerAttrs.put("b", "2");
|
||||
testdataFactory.createTarget(knownTargetId);
|
||||
controllerManagement.updateControllerAttributes(knownTargetId, knownControllerAttrs);
|
||||
controllerManagement.updateControllerAttributes(knownTargetId, knownControllerAttrs, null);
|
||||
|
||||
// test query target over rest resource
|
||||
mvc.perform(get(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/" + knownTargetId + "/attributes"))
|
||||
|
||||
@@ -319,12 +319,15 @@ public interface ControllerManagement {
|
||||
Action registerRetrieved(long actionId, String message);
|
||||
|
||||
/**
|
||||
* Updates attributes of the controller.
|
||||
* Updates attributes of the controller according to the given
|
||||
* {@link UpdateMode}.
|
||||
*
|
||||
* @param controllerId
|
||||
* to update
|
||||
* @param attributes
|
||||
* to insert
|
||||
* @param mode
|
||||
* the update mode or <code>null</code>
|
||||
*
|
||||
* @return updated {@link Target}
|
||||
*
|
||||
@@ -334,7 +337,8 @@ public interface ControllerManagement {
|
||||
* if maximum number of attribzes per target is exceeded
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
|
||||
Target updateControllerAttributes(@NotEmpty String controllerId, @NotNull Map<String, String> attributes);
|
||||
Target updateControllerAttributes(@NotEmpty String controllerId, @NotNull Map<String, String> attributes,
|
||||
UpdateMode mode);
|
||||
|
||||
/**
|
||||
* Finds {@link Target} based on given controller ID returns found Target
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) 2018 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Enumerates the supported update modes. Each mode represents an attribute
|
||||
* update strategy.
|
||||
*
|
||||
* @see ControllerManagement
|
||||
*/
|
||||
public enum UpdateMode {
|
||||
|
||||
/**
|
||||
* Merge update strategy
|
||||
*/
|
||||
MERGE,
|
||||
|
||||
/**
|
||||
* Replacement update strategy
|
||||
*/
|
||||
REPLACE,
|
||||
|
||||
/**
|
||||
* Removal update strategy
|
||||
*/
|
||||
REMOVE;
|
||||
|
||||
public static Optional<UpdateMode> valueOfIgnoreCase(final String name) {
|
||||
return Arrays.stream(values()).filter(mode -> mode.name().equalsIgnoreCase(name)).findAny();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -38,6 +38,7 @@ import org.eclipse.hawkbit.repository.QuotaManagement;
|
||||
import org.eclipse.hawkbit.repository.RepositoryConstants;
|
||||
import org.eclipse.hawkbit.repository.RepositoryProperties;
|
||||
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||
import org.eclipse.hawkbit.repository.UpdateMode;
|
||||
import org.eclipse.hawkbit.repository.builder.ActionStatusCreate;
|
||||
import org.eclipse.hawkbit.repository.event.remote.TargetPollEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.entity.CancelTargetAssignmentEvent;
|
||||
@@ -666,18 +667,41 @@ public class JpaControllerManagement implements ControllerManagement {
|
||||
@Transactional
|
||||
@Retryable(include = {
|
||||
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
|
||||
public Target updateControllerAttributes(final String controllerId, final Map<String, String> data) {
|
||||
public Target updateControllerAttributes(final String controllerId, final Map<String, String> data,
|
||||
final UpdateMode mode) {
|
||||
final JpaTarget target = (JpaTarget) targetRepository.findByControllerId(controllerId)
|
||||
.orElseThrow(() -> new EntityNotFoundException(Target.class, controllerId));
|
||||
|
||||
target.getControllerAttributes().putAll(data);
|
||||
// get the modifiable attribute map
|
||||
final Map<String, String> controllerAttributes = target.getControllerAttributes();
|
||||
|
||||
if (target.getControllerAttributes().size() > quotaManagement.getMaxAttributeEntriesPerTarget()) {
|
||||
throw new QuotaExceededException("Controller attribues", target.getControllerAttributes().size(),
|
||||
quotaManagement.getMaxAttributeEntriesPerTarget());
|
||||
final UpdateMode updateMode = mode != null ? mode : UpdateMode.MERGE;
|
||||
switch (updateMode) {
|
||||
case REMOVE:
|
||||
// remove the addressed attributes
|
||||
data.keySet().forEach(controllerAttributes::remove);
|
||||
break;
|
||||
case REPLACE:
|
||||
// clear the attributes before adding the new attributes
|
||||
controllerAttributes.clear();
|
||||
controllerAttributes.putAll(data);
|
||||
target.setRequestControllerAttributes(false);
|
||||
break;
|
||||
case MERGE:
|
||||
// just merge the attributes in
|
||||
controllerAttributes.putAll(data);
|
||||
target.setRequestControllerAttributes(false);
|
||||
break;
|
||||
default:
|
||||
// unknown update mode
|
||||
throw new IllegalStateException("The update mode " + updateMode + " is not supported.");
|
||||
}
|
||||
|
||||
target.setRequestControllerAttributes(false);
|
||||
final int attributeCount = controllerAttributes.size();
|
||||
if (attributeCount > quotaManagement.getMaxAttributeEntriesPerTarget()) {
|
||||
throw new QuotaExceededException("Controller attributes", attributeCount,
|
||||
quotaManagement.getMaxAttributeEntriesPerTarget());
|
||||
}
|
||||
|
||||
return targetRepository.save(target);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import static org.junit.Assert.fail;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -24,6 +25,7 @@ import javax.validation.ConstraintViolationException;
|
||||
|
||||
import org.apache.commons.lang3.RandomUtils;
|
||||
import org.eclipse.hawkbit.repository.RepositoryProperties;
|
||||
import org.eclipse.hawkbit.repository.UpdateMode;
|
||||
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.TargetPollEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.entity.ActionCreatedEvent;
|
||||
@@ -121,8 +123,8 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
|
||||
|
||||
verifyThrownExceptionBy(() -> controllerManagement.registerRetrieved(NOT_EXIST_IDL, "test message"), "Action");
|
||||
|
||||
verifyThrownExceptionBy(() -> controllerManagement.updateControllerAttributes(NOT_EXIST_ID, Maps.newHashMap()),
|
||||
"Target");
|
||||
verifyThrownExceptionBy(
|
||||
() -> controllerManagement.updateControllerAttributes(NOT_EXIST_ID, Maps.newHashMap(), null), "Target");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -658,7 +660,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
|
||||
private void addAttributeAndVerify(final String controllerId) {
|
||||
final Map<String, String> testData = Maps.newHashMapWithExpectedSize(1);
|
||||
testData.put("test1", "testdata1");
|
||||
controllerManagement.updateControllerAttributes(controllerId, testData);
|
||||
controllerManagement.updateControllerAttributes(controllerId, testData, null);
|
||||
|
||||
assertThat(targetManagement.getControllerAttributes(controllerId)).as("Controller Attributes are wrong")
|
||||
.isEqualTo(testData);
|
||||
@@ -668,7 +670,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
|
||||
private void addSecondAttributeAndVerify(final String controllerId) {
|
||||
final Map<String, String> testData = Maps.newHashMapWithExpectedSize(2);
|
||||
testData.put("test2", "testdata20");
|
||||
controllerManagement.updateControllerAttributes(controllerId, testData);
|
||||
controllerManagement.updateControllerAttributes(controllerId, testData, null);
|
||||
|
||||
testData.put("test1", "testdata1");
|
||||
assertThat(targetManagement.getControllerAttributes(controllerId)).as("Controller Attributes are wrong")
|
||||
@@ -680,13 +682,111 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
|
||||
final Map<String, String> testData = Maps.newHashMapWithExpectedSize(2);
|
||||
testData.put("test1", "testdata12");
|
||||
|
||||
controllerManagement.updateControllerAttributes(controllerId, testData);
|
||||
controllerManagement.updateControllerAttributes(controllerId, testData, null);
|
||||
|
||||
testData.put("test2", "testdata20");
|
||||
assertThat(targetManagement.getControllerAttributes(controllerId)).as("Controller Attributes are wrong")
|
||||
.isEqualTo(testData);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Ensures that target attributes can be updated using different update modes.")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
|
||||
@Expect(type = TargetUpdatedEvent.class, count = 4) })
|
||||
public void updateTargetAttributesWithDifferentUpdateModes() {
|
||||
|
||||
final String controllerId = "testCtrl";
|
||||
testdataFactory.createTarget(controllerId);
|
||||
|
||||
// no update mode
|
||||
updateTargetAttributesWithoutUpdateMode(controllerId);
|
||||
|
||||
// update mode REPLACE
|
||||
updateTargetAttributesWithUpdateModeReplace(controllerId);
|
||||
|
||||
// update mode MERGE
|
||||
updateTargetAttributesWithUpdateModeMerge(controllerId);
|
||||
|
||||
// update mode REMOVE
|
||||
updateTargetAttributesWithUpdateModeRemove(controllerId);
|
||||
|
||||
}
|
||||
|
||||
@Step
|
||||
private void updateTargetAttributesWithUpdateModeRemove(final String controllerId) {
|
||||
|
||||
final int previousSize = targetManagement.getControllerAttributes(controllerId).size();
|
||||
|
||||
// update the attributes using update mode REMOVE
|
||||
final Map<String, String> removeAttributes = new HashMap<>();
|
||||
removeAttributes.put("k1", "foo");
|
||||
removeAttributes.put("k3", "bar");
|
||||
controllerManagement.updateControllerAttributes(controllerId, removeAttributes, UpdateMode.REMOVE);
|
||||
|
||||
// verify attribute removal
|
||||
final Map<String, String> updatedAttributes = targetManagement.getControllerAttributes(controllerId);
|
||||
assertThat(updatedAttributes.size()).isEqualTo(previousSize - 2);
|
||||
assertThat(updatedAttributes).doesNotContainKeys("k1", "k3");
|
||||
|
||||
}
|
||||
|
||||
@Step
|
||||
private void updateTargetAttributesWithUpdateModeMerge(final String controllerId) {
|
||||
// get the current attributes
|
||||
final HashMap<String, String> attributes = new HashMap<>(
|
||||
targetManagement.getControllerAttributes(controllerId));
|
||||
|
||||
// update the attributes using update mode MERGE
|
||||
final Map<String, String> mergeAttributes = new HashMap<>();
|
||||
mergeAttributes.put("k1", "v1_modified_again");
|
||||
mergeAttributes.put("k4", "v4");
|
||||
controllerManagement.updateControllerAttributes(controllerId, mergeAttributes, UpdateMode.MERGE);
|
||||
|
||||
// verify attribute merge
|
||||
final Map<String, String> updatedAttributes = targetManagement.getControllerAttributes(controllerId);
|
||||
assertThat(updatedAttributes.size()).isEqualTo(4);
|
||||
assertThat(updatedAttributes).containsAllEntriesOf(mergeAttributes);
|
||||
assertThat(updatedAttributes.get("k1")).isEqualTo("v1_modified_again");
|
||||
attributes.keySet().forEach(assertThat(updatedAttributes)::containsKey);
|
||||
}
|
||||
|
||||
@Step
|
||||
private void updateTargetAttributesWithUpdateModeReplace(final String controllerId) {
|
||||
|
||||
// get the current attributes
|
||||
final HashMap<String, String> attributes = new HashMap<>(
|
||||
targetManagement.getControllerAttributes(controllerId));
|
||||
|
||||
// update the attributes using update mode REPLACE
|
||||
final Map<String, String> replacementAttributes = new HashMap<>();
|
||||
replacementAttributes.put("k1", "v1_modified");
|
||||
replacementAttributes.put("k2", "v2");
|
||||
replacementAttributes.put("k3", "v3");
|
||||
controllerManagement.updateControllerAttributes(controllerId, replacementAttributes, UpdateMode.REPLACE);
|
||||
|
||||
// verify attribute replacement
|
||||
final Map<String, String> updatedAttributes = targetManagement.getControllerAttributes(controllerId);
|
||||
assertThat(updatedAttributes.size()).isEqualTo(replacementAttributes.size());
|
||||
assertThat(updatedAttributes).containsAllEntriesOf(replacementAttributes);
|
||||
assertThat(updatedAttributes.get("k1")).isEqualTo("v1_modified");
|
||||
attributes.entrySet().forEach(assertThat(updatedAttributes)::doesNotContain);
|
||||
}
|
||||
|
||||
@Step
|
||||
private void updateTargetAttributesWithoutUpdateMode(final String controllerId) {
|
||||
|
||||
// set the initial attributes
|
||||
final Map<String, String> attributes = new HashMap<>();
|
||||
attributes.put("k0", "v0");
|
||||
attributes.put("k1", "v1");
|
||||
controllerManagement.updateControllerAttributes(controllerId, attributes, null);
|
||||
|
||||
// verify initial attributes
|
||||
final Map<String, String> updatedAttributes = targetManagement.getControllerAttributes(controllerId);
|
||||
assertThat(updatedAttributes.size()).isEqualTo(attributes.size());
|
||||
assertThat(updatedAttributes).containsAllEntriesOf(attributes);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Ensures that target attribute update fails if quota hits.")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
|
||||
@@ -730,7 +830,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
|
||||
for (int i = 0; i < allowedAttributes; i++) {
|
||||
testData.put(keyPrefix + i, valuePrefix);
|
||||
}
|
||||
controllerManagement.updateControllerAttributes(controllerId, testData);
|
||||
controllerManagement.updateControllerAttributes(controllerId, testData, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -418,7 +418,7 @@ public class TargetManagementTest extends AbstractJpaIntegrationTest {
|
||||
testData.put("test1", "testdata1");
|
||||
|
||||
targetManagement.create(entityFactory.target().create().controllerId(controllerId));
|
||||
final Target target = controllerManagement.updateControllerAttributes(controllerId, testData);
|
||||
final Target target = controllerManagement.updateControllerAttributes(controllerId, testData, null);
|
||||
|
||||
assertThat(targetManagement.getControllerAttributes(controllerId)).as("Controller Attributes are wrong")
|
||||
.isEqualTo(testData);
|
||||
|
||||
@@ -47,14 +47,14 @@ public class RSQLTargetFieldTest extends AbstractJpaIntegrationTest {
|
||||
target = targetManagement.create(entityFactory.target().create().controllerId("targetId123")
|
||||
.name("targetName123").description("targetDesc123"));
|
||||
attributes.put("revision", "1.1");
|
||||
target = controllerManagement.updateControllerAttributes(target.getControllerId(), attributes);
|
||||
target = controllerManagement.updateControllerAttributes(target.getControllerId(), attributes, null);
|
||||
target = controllerManagement.findOrRegisterTargetIfItDoesNotexist(target.getControllerId(), LOCALHOST);
|
||||
|
||||
target2 = targetManagement
|
||||
.create(entityFactory.target().create().controllerId("targetId1234").description("targetId1234"));
|
||||
attributes.put("revision", "1.2");
|
||||
Thread.sleep(1);
|
||||
target2 = controllerManagement.updateControllerAttributes(target2.getControllerId(), attributes);
|
||||
target2 = controllerManagement.updateControllerAttributes(target2.getControllerId(), attributes, null);
|
||||
target2 = controllerManagement.findOrRegisterTargetIfItDoesNotexist(target2.getControllerId(), LOCALHOST);
|
||||
|
||||
final Target target3 = testdataFactory.createTarget("targetId1235");
|
||||
|
||||
@@ -387,7 +387,7 @@ public abstract class JsonBuilder {
|
||||
|
||||
public static String distributionSetUpdateValidFieldsOnly(final DistributionSet set) throws JSONException {
|
||||
|
||||
final List<JSONObject> modules = set.getModules().stream()
|
||||
set.getModules().stream()
|
||||
.map(module -> new JSONObject().put("id", module.getId())).collect(Collectors.toList());
|
||||
|
||||
return new JSONObject().put("name", set.getName()).put("description", set.getDescription())
|
||||
@@ -535,13 +535,21 @@ 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 configData(id, attributes, execution, null);
|
||||
}
|
||||
|
||||
public static String configData(final String id, final Map<String, String> attributes, final String execution,
|
||||
final String mode) throws JSONException {
|
||||
final JSONObject json = 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("data", attributes);
|
||||
if (mode != null) {
|
||||
json.put("mode", mode);
|
||||
}
|
||||
return json.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user