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:
Stefan Behl
2018-03-26 17:50:20 +02:00
committed by Kai Zimmermann
parent e700acc312
commit 607cf92a9e
19 changed files with 746 additions and 125 deletions

View File

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

View File

@@ -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() + "]";
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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"))

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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