diff --git a/docs/content/apis/dmf_api.md b/docs/content/apis/dmf_api.md
index 29662c6bc..f4bd51142 100644
--- a/docs/content/apis/dmf_api.md
+++ b/docs/content/apis/dmf_api.md
@@ -63,11 +63,19 @@ Payload Template (optional):
```json
{
- "name": "String"
+ "name": "String",
+ "attributeUpdate": {
+ "attributes": {
+ "exampleKey1" : "exampleValue1",
+ "exampleKey2" : "exampleValue2"
+ },
+ "mode": "String"
+ }
}
```
-The "name" property specifies the name of the thing, which by default is the thing ID. This property is optional.
+The "name" property specifies the name of the thing, which by default is the thing ID. This property is optional.
+The "attributeUpdate" property provides the attributes of the thing, for details see UPDATE_ATTRIBUTES message. This property is optional.
### THING_REMOVED
@@ -93,7 +101,7 @@ type=THING\_REMOVED
tenant=default
thingId=abc | content\_type=app
### UPDATE_ATTRIBUTES
-Message to update target attributes. This message can be send in response to a _REQUEST_ATTRIBUTES_UPDATE_ event, sent by hawkBit.
+Message to update target attributes. This message can be send in response to a REQUEST_ATTRIBUTES_UPDATE event, sent by hawkBit.
| Header | Description | Type | Mandatory
|-----------------------------|----------------------------------|-------------------------------------|----------------
diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerService.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerService.java
index 6eca6ca92..fdeb16205 100644
--- a/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerService.java
+++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerService.java
@@ -10,7 +10,6 @@ package org.eclipse.hawkbit.amqp;
import static org.eclipse.hawkbit.repository.RepositoryConstants.MAX_ACTION_COUNT;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.MULTI_ASSIGNMENTS_ENABLED;
-import static org.springframework.util.StringUtils.hasText;
import java.io.Serializable;
import java.net.URI;
@@ -53,7 +52,6 @@ import org.springframework.amqp.AmqpRejectAndDontRequeueException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
-import org.springframework.amqp.support.converter.MessageConversionException;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
@@ -201,12 +199,12 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
}
/**
- * Method to create a new target or to find the target if it already exists and
- * update its poll time, status and optionally its name.
+ * Method to create a new target or to find the target if it already exists
+ * and update its poll time, status and optionally its name and attributes.
*
* @param message
- * the message that contains replyTo property and optionally the name
- * in body
+ * the message that contains replyTo property and optionally the
+ * name and attributes in body
* @param virtualHost
* the virtual host
*/
@@ -225,14 +223,22 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
target = controllerManagement.findOrRegisterTargetIfItDoesNotExist(thingId, amqpUri);
} else {
checkContentTypeJson(message);
+ final DmfCreateThing thingCreateBody = convertMessage(message, DmfCreateThing.class);
+ final DmfAttributeUpdate thingAttributeUpdateBody = thingCreateBody.getAttributeUpdate();
- target = controllerManagement.findOrRegisterTargetIfItDoesNotExist(thingId, amqpUri, convertMessage(message, DmfCreateThing.class).getName());
+ target = controllerManagement.findOrRegisterTargetIfItDoesNotExist(thingId, amqpUri,
+ thingCreateBody.getName());
+ if (thingAttributeUpdateBody != null) {
+ controllerManagement.updateControllerAttributes(thingId, thingAttributeUpdateBody.getAttributes(),
+ getUpdateMode(thingAttributeUpdateBody));
+ }
}
LOG.debug("Target {} reported online state.", thingId);
sendUpdateCommandToTarget(target);
} catch (final EntityAlreadyExistsException e) {
- throw new AmqpRejectAndDontRequeueException("Tried to register previously registered target, message will be ignored!", e);
+ throw new AmqpRejectAndDontRequeueException(
+ "Tried to register previously registered target, message will be ignored!", e);
}
}
@@ -251,8 +257,8 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
}
private void sendCurrentActionsAsMultiActionToTarget(final Target target) {
- final List actions = controllerManagement
- .findActiveActionsWithHighestWeight(target.getControllerId(), MAX_ACTION_COUNT);
+ final List actions = controllerManagement.findActiveActionsWithHighestWeight(target.getControllerId(),
+ MAX_ACTION_COUNT);
final Set distributionSets = actions.stream().map(Action::getDistributionSet)
.collect(Collectors.toSet());
diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java
index b3ec8f82d..0688f685f 100644
--- a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java
+++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java
@@ -14,8 +14,8 @@ import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationPrope
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -79,6 +79,7 @@ import org.springframework.http.HttpStatus;
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
+import io.qameta.allure.Step;
import io.qameta.allure.Story;
@ExtendWith(MockitoExtension.class)
@@ -143,6 +144,9 @@ public class AmqpMessageHandlerServiceTest {
@Captor
private ArgumentCaptor targetIdCaptor;
+ @Captor
+ private ArgumentCaptor targetNameCaptor;
+
@Captor
private ArgumentCaptor uriCaptor;
@@ -185,50 +189,64 @@ public class AmqpMessageHandlerServiceTest {
@Description("Tests the creation of a target/thing by calling the same method that incoming RabbitMQ messages would access.")
public void createThing() {
final String knownThingId = "1";
- final MessageProperties messageProperties = createMessageProperties(MessageType.THING_CREATED);
- messageProperties.setHeader(MessageHeaderKey.THING_ID, "1");
- final Message message = messageConverter.toMessage(new byte[0], messageProperties);
+
+ processThingCreatedMessage(knownThingId, null);
+
+ assertThingIdCapturedField(knownThingId);
+ assertReplyToCapturedField("MyTest");
+ }
+
+ @Step
+ private void processThingCreatedMessage(final String thingId, final DmfCreateThing payload) {
+ final MessageProperties messageProperties = getThingCreatedMessageProperties(thingId);
+ final Message message = createMessage(payload != null ? payload : new byte[0], messageProperties);
final Target targetMock = mock(Target.class);
-
- final ArgumentCaptor targetIdCaptor = ArgumentCaptor.forClass(String.class);
- final ArgumentCaptor uriCaptor = ArgumentCaptor.forClass(URI.class);
- when(controllerManagementMock.findOrRegisterTargetIfItDoesNotExist(targetIdCaptor.capture(),
- uriCaptor.capture())).thenReturn(targetMock);
+ if (payload == null) {
+ when(controllerManagementMock.findOrRegisterTargetIfItDoesNotExist(targetIdCaptor.capture(),
+ uriCaptor.capture())).thenReturn(targetMock);
+ } else {
+ when(controllerManagementMock.findOrRegisterTargetIfItDoesNotExist(targetIdCaptor.capture(),
+ uriCaptor.capture(), targetNameCaptor.capture())).thenReturn(targetMock);
+ if (payload.getAttributeUpdate() != null) {
+ when(controllerManagementMock.updateControllerAttributes(targetIdCaptor.capture(),
+ attributesCaptor.capture(), modeCaptor.capture())).thenReturn(null);
+ }
+ }
when(controllerManagementMock.findActiveActionWithHighestWeight(any())).thenReturn(Optional.empty());
amqpMessageHandlerService.onMessage(message, MessageType.THING_CREATED.name(), TENANT, VIRTUAL_HOST);
+ }
- // verify
- assertThat(targetIdCaptor.getValue()).as("Thing id is wrong").isEqualTo(knownThingId);
- assertThat(uriCaptor.getValue().toString()).as("Uri is not right")
- .isEqualTo("amqp://" + VIRTUAL_HOST + "/MyTest");
+ @Step
+ private void assertThingIdCapturedField(final String thingId) {
+ assertThat(targetIdCaptor.getValue()).as("Thing id is wrong").isEqualTo(thingId);
+ }
+
+ @Step
+ private void assertReplyToCapturedField(final String replyTo) {
+ assertThat(uriCaptor.getValue()).as("Uri is not right").hasToString("amqp://" + VIRTUAL_HOST + "/" + replyTo);
}
@Test
@Description("Tests the creation of a target/thing with specified name by calling the same method that incoming RabbitMQ messages would access.")
public void createThingWithName() {
final String knownThingId = "2";
- final DmfCreateThing targetProperties = new DmfCreateThing();
- targetProperties.setName("NonDefaultTargetName");
+ final String knownThingName = "NonDefaultTargetName";
- final Target targetMock = mock(Target.class);
+ final DmfCreateThing payload = new DmfCreateThing();
+ payload.setName(knownThingName);
- targetIdCaptor = ArgumentCaptor.forClass(String.class);
- uriCaptor = ArgumentCaptor.forClass(URI.class);
- final ArgumentCaptor targetNameCaptor = ArgumentCaptor.forClass(String.class);
+ processThingCreatedMessage(knownThingId, payload);
- when(controllerManagementMock.findOrRegisterTargetIfItDoesNotExist(targetIdCaptor.capture(),
- uriCaptor.capture(), targetNameCaptor.capture())).thenReturn(targetMock);
- when(controllerManagementMock.findActiveActionWithHighestWeight(any())).thenReturn(Optional.empty());
+ assertThingIdCapturedField(knownThingId);
+ assertReplyToCapturedField("MyTest");
+ assertThingNameCapturedField(knownThingName);
+ }
- amqpMessageHandlerService.onMessage(
- createMessage(targetProperties, getThingCreatedMessageProperties(knownThingId)),
- MessageType.THING_CREATED.name(), TENANT, "vHost");
-
- assertThat(targetIdCaptor.getValue()).as("Thing id is wrong").isEqualTo(knownThingId);
- assertThat(uriCaptor.getValue().toString()).as("Uri is not right").isEqualTo("amqp://vHost/MyTest");
- assertThat(targetNameCaptor.getValue()).as("Thing name is not right").isEqualTo(targetProperties.getName());
+ @Step
+ private void assertThingNameCapturedField(final String thingName) {
+ assertThat(targetNameCaptor.getValue()).as("Thing name is wrong").isEqualTo(thingName);
}
@Test
@@ -243,6 +261,58 @@ public class AmqpMessageHandlerServiceTest {
MessageType.THING_CREATED.name(), TENANT, VIRTUAL_HOST));
}
+ @Test
+ @Description("Tests the creation of a target/thing with specified attributes by calling the same method that incoming RabbitMQ messages would access.")
+ public void createThingWithAttributes() {
+ final String knownThingId = "4";
+
+ final DmfAttributeUpdate attributeUpdate = new DmfAttributeUpdate();
+ attributeUpdate.getAttributes().put("testKey1", "testValue1");
+ attributeUpdate.getAttributes().put("testKey2", "testValue2");
+
+ final DmfCreateThing payload = new DmfCreateThing();
+ payload.setAttributeUpdate(attributeUpdate);
+
+ processThingCreatedMessage(knownThingId, payload);
+
+ assertThingIdCapturedField(knownThingId);
+ assertReplyToCapturedField("MyTest");
+ assertThingAttributesCapturedField(attributeUpdate.getAttributes());
+ }
+
+ @Step
+ private void assertThingAttributesCapturedField(final Map attributes) {
+ assertThat(attributesCaptor.getValue()).as("Attributes is not right").isEqualTo(attributes);
+ }
+
+ @Test
+ @Description("Tests the creation of a target/thing with specified name and attributes by calling the same method that incoming RabbitMQ messages would access.")
+ public void createThingWithNameAndAttributes() {
+ final String knownThingId = "5";
+ final String knownThingName = "NonDefaultTargetName";
+
+ final DmfAttributeUpdate attributeUpdate = new DmfAttributeUpdate();
+ attributeUpdate.getAttributes().put("testKey1", "testValue1");
+ attributeUpdate.getAttributes().put("testKey2", "testValue2");
+ attributeUpdate.setMode(DmfUpdateMode.REPLACE);
+
+ final DmfCreateThing payload = new DmfCreateThing();
+ payload.setName(knownThingName);
+ payload.setAttributeUpdate(attributeUpdate);
+
+ processThingCreatedMessage(knownThingId, payload);
+
+ assertThingIdCapturedField(knownThingId);
+ assertReplyToCapturedField("MyTest");
+ assertThingAttributesCapturedField(attributeUpdate.getAttributes());
+ assertThingAttributesModeCapturedField(UpdateMode.REPLACE);
+ }
+
+ @Step
+ private void assertThingAttributesModeCapturedField(final UpdateMode attributesUpdateMode) {
+ assertThat(modeCaptor.getValue()).as("Attributes update mode is not right").isEqualTo(attributesUpdateMode);
+ }
+
@Test
@Description("Tests the target attribute update by calling the same method that incoming RabbitMQ messages would access.")
public void updateAttributes() {
@@ -254,18 +324,15 @@ public class AmqpMessageHandlerServiceTest {
attributeUpdate.getAttributes().put("testKey1", "testValue1");
attributeUpdate.getAttributes().put("testKey2", "testValue2");
- final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(attributeUpdate,
- messageProperties);
+ final Message message = createMessage(attributeUpdate, messageProperties);
when(controllerManagementMock.updateControllerAttributes(targetIdCaptor.capture(), attributesCaptor.capture(),
modeCaptor.capture())).thenReturn(null);
amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, VIRTUAL_HOST);
- // verify
- assertThat(targetIdCaptor.getValue()).as("Thing id is wrong").isEqualTo(knownThingId);
- assertThat(attributesCaptor.getValue()).as("Attributes is not right")
- .isEqualTo(attributeUpdate.getAttributes());
+ assertThingIdCapturedField(knownThingId);
+ assertThingAttributesCapturedField(attributeUpdate.getAttributes());
}
@Test
@@ -283,32 +350,32 @@ public class AmqpMessageHandlerServiceTest {
modeCaptor.capture())).thenReturn(null);
// send a message which does not specify a update mode
- Message message = amqpMessageHandlerService.getMessageConverter().toMessage(attributeUpdate, messageProperties);
+ Message message = createMessage(attributeUpdate, messageProperties);
amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, VIRTUAL_HOST);
// verify that NO fallback is made on the way to the controller
// management layer
- assertThat(modeCaptor.getValue()).isNull();
+ assertThingAttributesModeCapturedField(null);
// send a message which specifies update mode MERGE
attributeUpdate.setMode(DmfUpdateMode.MERGE);
- message = amqpMessageHandlerService.getMessageConverter().toMessage(attributeUpdate, messageProperties);
+ message = createMessage(attributeUpdate, messageProperties);
amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, VIRTUAL_HOST);
// verify that the update mode is converted and forwarded as expected
- assertThat(modeCaptor.getValue()).isEqualTo(UpdateMode.MERGE);
+ assertThingAttributesModeCapturedField(UpdateMode.MERGE);
// send a message which specifies update mode REPLACE
attributeUpdate.setMode(DmfUpdateMode.REPLACE);
- message = amqpMessageHandlerService.getMessageConverter().toMessage(attributeUpdate, messageProperties);
+ message = createMessage(attributeUpdate, messageProperties);
amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, VIRTUAL_HOST);
// verify that the update mode is converted and forwarded as expected
- assertThat(modeCaptor.getValue()).isEqualTo(UpdateMode.REPLACE);
+ assertThingAttributesModeCapturedField(UpdateMode.REPLACE);
// send a message which specifies update mode REMOVE
attributeUpdate.setMode(DmfUpdateMode.REMOVE);
- message = amqpMessageHandlerService.getMessageConverter().toMessage(attributeUpdate, messageProperties);
+ message = createMessage(attributeUpdate, messageProperties);
amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, VIRTUAL_HOST);
// verify that the update mode is converted and forwarded as expected
- assertThat(modeCaptor.getValue()).isEqualTo(UpdateMode.REMOVE);
+ assertThingAttributesModeCapturedField(UpdateMode.REMOVE);
}
@Test
@@ -316,7 +383,7 @@ public class AmqpMessageHandlerServiceTest {
public void createThingWithoutReplyTo() {
final MessageProperties messageProperties = createMessageProperties(MessageType.THING_CREATED, null);
messageProperties.setHeader(MessageHeaderKey.THING_ID, "1");
- final Message message = messageConverter.toMessage("", messageProperties);
+ final Message message = createMessage("", messageProperties);
assertThatExceptionOfType(AmqpRejectAndDontRequeueException.class)
.as(FAIL_MESSAGE_AMQP_REJECT_REASON + "since no replyTo header was set")
@@ -328,7 +395,7 @@ public class AmqpMessageHandlerServiceTest {
@Description("Tests the creation of a target/thing without a thingID by calling the same method that incoming RabbitMQ messages would access.")
public void createThingWithoutID() {
final MessageProperties messageProperties = createMessageProperties(MessageType.THING_CREATED);
- final Message message = messageConverter.toMessage(new byte[0], messageProperties);
+ final Message message = createMessage(new byte[0], messageProperties);
assertThatExceptionOfType(AmqpRejectAndDontRequeueException.class)
.as(FAIL_MESSAGE_AMQP_REJECT_REASON + "since no thingId was set")
@@ -342,7 +409,7 @@ public class AmqpMessageHandlerServiceTest {
final String type = "bumlux";
final MessageProperties messageProperties = createMessageProperties(MessageType.THING_CREATED);
messageProperties.setHeader(MessageHeaderKey.THING_ID, "");
- final Message message = messageConverter.toMessage(new byte[0], messageProperties);
+ final Message message = createMessage(new byte[0], messageProperties);
assertThatExceptionOfType(AmqpRejectAndDontRequeueException.class)
.as(FAIL_MESSAGE_AMQP_REJECT_REASON + "due to unknown message type")
@@ -378,8 +445,7 @@ public class AmqpMessageHandlerServiceTest {
final MessageProperties messageProperties = createMessageProperties(MessageType.EVENT);
messageProperties.setHeader(MessageHeaderKey.TOPIC, EventTopic.UPDATE_ACTION_STATUS.name());
final DmfActionUpdateStatus actionUpdateStatus = new DmfActionUpdateStatus(1L, DmfActionStatus.DOWNLOAD);
- final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(actionUpdateStatus,
- messageProperties);
+ final Message message = createMessage(actionUpdateStatus, messageProperties);
assertThatExceptionOfType(AmqpRejectAndDontRequeueException.class)
.as(FAIL_MESSAGE_AMQP_REJECT_REASON + "since no action id was set")
@@ -395,8 +461,7 @@ public class AmqpMessageHandlerServiceTest {
when(controllerManagementMock.findActionWithDetails(anyLong())).thenReturn(Optional.empty());
final DmfActionUpdateStatus actionUpdateStatus = createActionUpdateStatus(DmfActionStatus.DOWNLOAD);
- final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(actionUpdateStatus,
- messageProperties);
+ final Message message = createMessage(actionUpdateStatus, messageProperties);
assertThatExceptionOfType(AmqpRejectAndDontRequeueException.class)
.as(FAIL_MESSAGE_AMQP_REJECT_REASON + "since no action id was set")
@@ -410,8 +475,7 @@ public class AmqpMessageHandlerServiceTest {
final MessageProperties messageProperties = createMessageProperties(null);
final DmfTenantSecurityToken securityToken = new DmfTenantSecurityToken(TENANT, TENANT_ID, CONTROLLER_ID,
TARGET_ID, FileResource.createFileResourceBySha1("12345"));
- final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken,
- messageProperties);
+ final Message message = createMessage(securityToken, messageProperties);
// test
final Message onMessage = amqpAuthenticationMessageHandlerService.onAuthenticationRequest(message);
@@ -429,8 +493,7 @@ public class AmqpMessageHandlerServiceTest {
final MessageProperties messageProperties = createMessageProperties(null);
final DmfTenantSecurityToken securityToken = new DmfTenantSecurityToken(TENANT, TENANT_ID, CONTROLLER_ID,
TARGET_ID, FileResource.createFileResourceBySha1("12345"));
- final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken,
- messageProperties);
+ final Message message = createMessage(securityToken, messageProperties);
final Artifact localArtifactMock = mock(Artifact.class);
when(artifactManagementMock.findFirstBySHA1(anyString())).thenReturn(Optional.of(localArtifactMock));
@@ -451,8 +514,7 @@ public class AmqpMessageHandlerServiceTest {
final MessageProperties messageProperties = createMessageProperties(null);
final DmfTenantSecurityToken securityToken = new DmfTenantSecurityToken(TENANT, TENANT_ID, CONTROLLER_ID,
TARGET_ID, FileResource.createFileResourceBySha1("12345"));
- final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken,
- messageProperties);
+ final Message message = createMessage(securityToken, messageProperties);
// mock
final Artifact localArtifactMock = mock(Artifact.class);
@@ -494,14 +556,12 @@ public class AmqpMessageHandlerServiceTest {
when(create.status(any())).thenReturn(create);
when(entityFactoryMock.actionStatus()).thenReturn(builder);
// for the test the same action can be used
- when(controllerManagementMock.findActiveActionWithHighestWeight(any()))
- .thenReturn(Optional.of(action));
+ when(controllerManagementMock.findActiveActionWithHighestWeight(any())).thenReturn(Optional.of(action));
final MessageProperties messageProperties = createMessageProperties(MessageType.EVENT);
messageProperties.setHeader(MessageHeaderKey.TOPIC, EventTopic.UPDATE_ACTION_STATUS.name());
final DmfActionUpdateStatus actionUpdateStatus = createActionUpdateStatus(DmfActionStatus.FINISHED, 23L);
- final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(actionUpdateStatus,
- messageProperties);
+ final Message message = createMessage(actionUpdateStatus, messageProperties);
// test
amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, VIRTUAL_HOST);
@@ -579,7 +639,7 @@ public class AmqpMessageHandlerServiceTest {
final String knownThingId = "1";
final MessageProperties messageProperties = createMessageProperties(MessageType.THING_REMOVED);
messageProperties.setHeader(MessageHeaderKey.THING_ID, knownThingId);
- final Message message = messageConverter.toMessage(new byte[0], messageProperties);
+ final Message message = createMessage(new byte[0], messageProperties);
// test
amqpMessageHandlerService.onMessage(message, MessageType.THING_REMOVED.name(), TENANT, VIRTUAL_HOST);
@@ -593,7 +653,7 @@ public class AmqpMessageHandlerServiceTest {
public void deleteThingWithoutThingId() {
// prepare invalid message
final MessageProperties messageProperties = createMessageProperties(MessageType.THING_REMOVED);
- final Message message = messageConverter.toMessage(new byte[0], messageProperties);
+ final Message message = createMessage(new byte[0], messageProperties);
assertThatExceptionOfType(AmqpRejectAndDontRequeueException.class)
.as(FAIL_MESSAGE_AMQP_REJECT_REASON + "since no thingId was set")
@@ -601,13 +661,13 @@ public class AmqpMessageHandlerServiceTest {
VIRTUAL_HOST));
}
- private MessageProperties getThingCreatedMessageProperties(String thingId) {
+ private MessageProperties getThingCreatedMessageProperties(final String thingId) {
final MessageProperties messageProperties = createMessageProperties(MessageType.THING_CREATED);
messageProperties.setHeader(MessageHeaderKey.THING_ID, thingId);
return messageProperties;
}
- private Message createMessage(Object object, MessageProperties messageProperties) {
+ private Message createMessage(final Object object, final MessageProperties messageProperties) {
return messageConverter.toMessage(object, messageProperties);
}
}
diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AbstractAmqpServiceIntegrationTest.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AbstractAmqpServiceIntegrationTest.java
index bb3dc748f..d97a74f47 100644
--- a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AbstractAmqpServiceIntegrationTest.java
+++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AbstractAmqpServiceIntegrationTest.java
@@ -12,6 +12,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
+import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
@@ -28,6 +29,7 @@ import org.eclipse.hawkbit.dmf.json.model.DmfActionRequest;
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.DmfCreateThing;
import org.eclipse.hawkbit.dmf.json.model.DmfDownloadAndUpdateRequest;
import org.eclipse.hawkbit.dmf.json.model.DmfMetadata;
import org.eclipse.hawkbit.integration.listener.DeadletterListener;
@@ -54,6 +56,9 @@ import org.springframework.amqp.rabbit.test.RabbitListenerTestHarness;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.stream.test.binder.TestSupportBinderAutoConfiguration;
import org.springframework.test.context.ContextConfiguration;
+import org.springframework.util.CollectionUtils;
+
+import com.cronutils.utils.StringUtils;
import io.qameta.allure.Step;
@@ -92,7 +97,8 @@ public abstract class AbstractAmqpServiceIntegrationTest extends AbstractAmqpInt
private T waitUntilIsPresent(final Callable> callable) {
- createConditionFactory().until(() -> WithSpringAuthorityRule.runAsPrivileged(() -> callable.call().isPresent()));
+ createConditionFactory()
+ .until(() -> WithSpringAuthorityRule.runAsPrivileged(() -> callable.call().isPresent()));
try {
return WithSpringAuthorityRule.runAsPrivileged(() -> callable.call().get());
@@ -188,7 +194,8 @@ public abstract class AbstractAmqpServiceIntegrationTest extends AbstractAmqpInt
protected void assertDmfDownloadAndUpdateRequest(final DmfDownloadAndUpdateRequest request,
final Set softwareModules, final String controllerId) {
- assertThat(softwareModules).is(new HamcrestCondition<>(SoftwareModuleJsonMatcher.containsExactly(request.getSoftwareModules())));
+ assertThat(softwareModules)
+ .is(new HamcrestCondition<>(SoftwareModuleJsonMatcher.containsExactly(request.getSoftwareModules())));
request.getSoftwareModules().forEach(dmfModule -> assertThat(dmfModule.getMetadata()).containsExactly(
new DmfMetadata(TestdataFactory.VISIBLE_SM_MD_KEY, TestdataFactory.VISIBLE_SM_MD_VALUE)));
final Target updatedTarget = waitUntilIsPresent(() -> targetManagement.getByControllerID(controllerId));
@@ -205,8 +212,13 @@ public abstract class AbstractAmqpServiceIntegrationTest extends AbstractAmqpInt
assertAssignmentMessage(dsModules, controllerId, EventTopic.DOWNLOAD);
}
- protected void createAndSendThingCreated(final String target, final String tenant) {
- final Message message = createTargetMessage(target, tenant);
+ protected void createAndSendThingCreated(final String controllerId, final String tenant) {
+ createAndSendThingCreated(controllerId, null, null, tenant);
+ }
+
+ protected void createAndSendThingCreated(final String controllerId, final String name,
+ final Map attributes, final String tenant) {
+ final Message message = createTargetMessage(controllerId, name, attributes, tenant);
getDmfClient().send(message);
}
@@ -254,32 +266,50 @@ public abstract class AbstractAmqpServiceIntegrationTest extends AbstractAmqpInt
registerAndAssertTargetWithExistingTenant(controllerId, 1);
}
- protected void registerAndAssertTargetWithExistingTenant(final String target,
+ protected void registerAndAssertTargetWithExistingTenant(final String controllerId,
final int existingTargetsAfterCreation) {
-
- registerAndAssertTargetWithExistingTenant(target, existingTargetsAfterCreation, TargetUpdateStatus.REGISTERED,
- CREATED_BY);
-
+ registerAndAssertTargetWithExistingTenant(controllerId, existingTargetsAfterCreation,
+ TargetUpdateStatus.REGISTERED, CREATED_BY);
}
- protected void registerAndAssertTargetWithExistingTenant(final String target,
+ protected void registerAndAssertTargetWithExistingTenant(final String controllerId,
final int existingTargetsAfterCreation, final TargetUpdateStatus expectedTargetStatus,
final String createdBy) {
- createAndSendThingCreated(target, TENANT_EXIST);
- final Target registeredTarget = waitUntilIsPresent(() -> targetManagement.getByControllerID(target));
+ registerAndAssertTargetWithExistingTenant(controllerId, null, existingTargetsAfterCreation,
+ expectedTargetStatus, createdBy, null);
+ }
+
+ protected void registerAndAssertTargetWithExistingTenant(final String controllerId, final String name,
+ final int existingTargetsAfterCreation, final TargetUpdateStatus expectedTargetStatus,
+ final String createdBy, final Map attributes) {
+ registerAndAssertTargetWithExistingTenant(controllerId, name, existingTargetsAfterCreation,
+ expectedTargetStatus, createdBy, attributes, () -> targetManagement.getByControllerID(controllerId));
+ }
+
+ private void registerAndAssertTargetWithExistingTenant(final String controllerId, final String name,
+ final int existingTargetsAfterCreation, final TargetUpdateStatus expectedTargetStatus,
+ final String createdBy, final Map attributes,
+ final Callable> fetchTarget) {
+ createAndSendThingCreated(controllerId, name, attributes, TENANT_EXIST);
+ final Target registeredTarget = waitUntilIsPresent(fetchTarget::call);
assertAllTargetsCount(existingTargetsAfterCreation);
assertThat(registeredTarget).isNotNull();
- assertTarget(registeredTarget, expectedTargetStatus, createdBy);
+ assertTarget(registeredTarget, name != null ? name : controllerId, expectedTargetStatus, createdBy,
+ attributes != null ? attributes : Collections.emptyMap());
}
protected void registerSameTargetAndAssertBasedOnVersion(final String controllerId,
final int existingTargetsAfterCreation, final TargetUpdateStatus expectedTargetStatus) {
+ registerSameTargetAndAssertBasedOnVersion(controllerId, null, existingTargetsAfterCreation,
+ expectedTargetStatus, null);
+ }
+
+ protected void registerSameTargetAndAssertBasedOnVersion(final String controllerId, final String name,
+ final int existingTargetsAfterCreation, final TargetUpdateStatus expectedTargetStatus,
+ final Map attributes) {
final int version = controllerManagement.getByControllerId(controllerId).get().getOptLockRevision();
- createAndSendThingCreated(controllerId, TENANT_EXIST);
- final Target registeredTarget = waitUntilIsPresent(() -> findTargetBasedOnNewVersion(controllerId, version));
- assertAllTargetsCount(existingTargetsAfterCreation);
- assertThat(registeredTarget).isNotNull();
- assertThat(registeredTarget.getUpdateStatus()).isEqualTo(expectedTargetStatus);
+ registerAndAssertTargetWithExistingTenant(controllerId, name, existingTargetsAfterCreation,
+ expectedTargetStatus, CREATED_BY, attributes, () -> findTargetBasedOnNewVersion(controllerId, version));
}
private Optional findTargetBasedOnNewVersion(final String controllerId, final int version) {
@@ -290,23 +320,43 @@ public abstract class AbstractAmqpServiceIntegrationTest extends AbstractAmqpInt
return Optional.empty();
}
- private void assertTarget(final Target target, final TargetUpdateStatus updateStatus, final String createdBy) {
+ private void assertTarget(final Target target, final String name, final TargetUpdateStatus updateStatus,
+ final String createdBy, final Map attributes) {
assertThat(target.getTenant()).isEqualTo(TENANT_EXIST);
+ assertThat(target.getName()).isEqualTo(name);
assertThat(target.getDescription()).contains("Plug and Play");
assertThat(target.getDescription()).contains(target.getControllerId());
assertThat(target.getCreatedBy()).isEqualTo(createdBy);
assertThat(target.getUpdateStatus()).isEqualTo(updateStatus);
- assertThat(target.getAddress()).isEqualTo(
- IpUtil.createAmqpUri(getVirtualHost(), DmfTestConfiguration.REPLY_TO_EXCHANGE));
+ assertThat(target.getAddress())
+ .isEqualTo(IpUtil.createAmqpUri(getVirtualHost(), DmfTestConfiguration.REPLY_TO_EXCHANGE));
+ assertThat(targetManagement.getControllerAttributes(target.getControllerId())).isEqualTo(attributes);
}
- protected Message createTargetMessage(final String target, final String tenant) {
+ protected Message createTargetMessage(final String controllerId, final String tenant) {
+ return createTargetMessage(controllerId, null, null, tenant);
+ }
+
+ protected Message createTargetMessage(final String controllerId, final String name,
+ final Map attributes, final String tenant) {
final MessageProperties messageProperties = createMessagePropertiesWithTenant(tenant);
- messageProperties.getHeaders().put(MessageHeaderKey.THING_ID, target);
+ messageProperties.getHeaders().put(MessageHeaderKey.THING_ID, controllerId);
messageProperties.getHeaders().put(MessageHeaderKey.TYPE, MessageType.THING_CREATED.toString());
messageProperties.setReplyTo(DmfTestConfiguration.REPLY_TO_EXCHANGE);
- return createMessage(null, messageProperties);
+ DmfCreateThing payload = null;
+ if (!StringUtils.isEmpty(name) || !CollectionUtils.isEmpty(attributes)) {
+ payload = new DmfCreateThing();
+ payload.setName(name);
+
+ if (!CollectionUtils.isEmpty(attributes)) {
+ final DmfAttributeUpdate attributeUpdate = new DmfAttributeUpdate();
+ attributeUpdate.getAttributes().putAll(attributes);
+ payload.setAttributeUpdate(attributeUpdate);
+ }
+ }
+
+ return createMessage(payload, messageProperties);
}
protected Message createPingMessage(final String correlationId, final String tenant) {
diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpMessageHandlerServiceIntegrationTest.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpMessageHandlerServiceIntegrationTest.java
index bfdb18f8f..61f97ed8e 100644
--- a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpMessageHandlerServiceIntegrationTest.java
+++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpMessageHandlerServiceIntegrationTest.java
@@ -92,7 +92,7 @@ public class AmqpMessageHandlerServiceIntegrationTest extends AbstractAmqpServic
assertPingReplyMessage(CORRELATION_ID);
- Mockito.verifyZeroInteractions(getDeadletterListener());
+ Mockito.verifyNoInteractions(getDeadletterListener());
}
@Test
@@ -107,7 +107,63 @@ public class AmqpMessageHandlerServiceIntegrationTest extends AbstractAmqpServic
registerAndAssertTargetWithExistingTenant(target2, 2);
registerSameTargetAndAssertBasedOnVersion(target2, 2, TargetUpdateStatus.REGISTERED);
- Mockito.verifyZeroInteractions(getDeadletterListener());
+ Mockito.verifyNoInteractions(getDeadletterListener());
+ }
+
+ @Test
+ @Description("Tests register target with name")
+ @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
+ @Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 2) })
+ public void registerTargetWithName() {
+ final String controllerId = TARGET_PREFIX + "registerTargetWithName";
+ final String name = "NonDefaultTargetName";
+ registerAndAssertTargetWithExistingTenant(controllerId, name, 1, TargetUpdateStatus.REGISTERED, CREATED_BY,
+ null);
+
+ registerSameTargetAndAssertBasedOnVersion(controllerId, name + "_updated", 1, TargetUpdateStatus.REGISTERED,
+ null);
+
+ Mockito.verifyNoInteractions(getDeadletterListener());
+ }
+
+ @Test
+ @Description("Tests register target with attributes")
+ @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
+ @Expect(type = TargetUpdatedEvent.class, count = 2), @Expect(type = TargetPollEvent.class, count = 2) })
+ public void registerTargetWithAttributes() {
+ final String controllerId = TARGET_PREFIX + "registerTargetWithAttributes";
+ final Map attributes = new HashMap<>();
+ attributes.put("testKey1", "testValue1");
+ attributes.put("testKey2", "testValue2");
+
+ registerAndAssertTargetWithExistingTenant(controllerId, null, 1, TargetUpdateStatus.REGISTERED, CREATED_BY,
+ attributes);
+
+ attributes.put("testKey3", "testValue3");
+ registerSameTargetAndAssertBasedOnVersion(controllerId, null, 1, TargetUpdateStatus.REGISTERED, attributes);
+
+ Mockito.verifyNoInteractions(getDeadletterListener());
+ }
+
+ @Test
+ @Description("Tests register target with name and attributes")
+ @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
+ @Expect(type = TargetUpdatedEvent.class, count = 3), @Expect(type = TargetPollEvent.class, count = 2) })
+ public void registerTargetWithNameAndAttributes() {
+ final String controllerId = TARGET_PREFIX + "registerTargetWithAttributes";
+ final String name = "NonDefaultTargetName";
+ final Map attributes = new HashMap<>();
+ attributes.put("testKey1", "testValue1");
+ attributes.put("testKey2", "testValue2");
+
+ registerAndAssertTargetWithExistingTenant(controllerId, name, 1, TargetUpdateStatus.REGISTERED, CREATED_BY,
+ attributes);
+
+ attributes.put("testKey3", "testValue3");
+ registerSameTargetAndAssertBasedOnVersion(controllerId, name + "_updated", 1, TargetUpdateStatus.REGISTERED,
+ attributes);
+
+ Mockito.verifyNoInteractions(getDeadletterListener());
}
@Test
@@ -510,7 +566,7 @@ public class AmqpMessageHandlerServiceIntegrationTest extends AbstractAmqpServic
// verify
assertDownloadAndInstallMessage(distributionSet.getModules(), controllerId);
- Mockito.verifyZeroInteractions(getDeadletterListener());
+ Mockito.verifyNoInteractions(getDeadletterListener());
}
@Test
@@ -537,7 +593,7 @@ public class AmqpMessageHandlerServiceIntegrationTest extends AbstractAmqpServic
// verify
assertDownloadMessage(distributionSet.getModules(), controllerId);
- Mockito.verifyZeroInteractions(getDeadletterListener());
+ Mockito.verifyNoInteractions(getDeadletterListener());
}
@Test
@@ -564,7 +620,7 @@ public class AmqpMessageHandlerServiceIntegrationTest extends AbstractAmqpServic
// verify
assertDownloadAndInstallMessage(distributionSet.getModules(), controllerId);
- Mockito.verifyZeroInteractions(getDeadletterListener());
+ Mockito.verifyNoInteractions(getDeadletterListener());
}
@Test
@@ -592,7 +648,7 @@ public class AmqpMessageHandlerServiceIntegrationTest extends AbstractAmqpServic
// verify
assertCancelActionMessage(getFirstAssignedActionId(distributionSetAssignmentResult), controllerId);
- Mockito.verifyZeroInteractions(getDeadletterListener());
+ Mockito.verifyNoInteractions(getDeadletterListener());
}
@Test
@@ -836,15 +892,15 @@ public class AmqpMessageHandlerServiceIntegrationTest extends AbstractAmqpServic
// verify
final Message message = assertReplyMessageHeader(EventTopic.DOWNLOAD, controllerId);
- Mockito.verifyZeroInteractions(getDeadletterListener());
+ Mockito.verifyNoInteractions(getDeadletterListener());
// get actionId from Message
- Long actionId = Long.parseLong(getJsonFieldFromBody(message.getBody(), "actionId"));
+ final Long actionId = Long.parseLong(getJsonFieldFromBody(message.getBody(), "actionId"));
// Send DOWNLOADED message
createAndSendActionStatusUpdateMessage(controllerId, actionId, DmfActionStatus.DOWNLOADED);
assertAction(actionId, 1, Status.RUNNING, Status.DOWNLOADED);
- Mockito.verifyZeroInteractions(getDeadletterListener());
+ Mockito.verifyNoInteractions(getDeadletterListener());
verifyAssignedDsAndInstalledDs(controllerId, distributionSet.getId(), null);
}
@@ -868,22 +924,22 @@ public class AmqpMessageHandlerServiceIntegrationTest extends AbstractAmqpServic
// verify
final Message message = assertReplyMessageHeader(EventTopic.DOWNLOAD, controllerId);
- Mockito.verifyZeroInteractions(getDeadletterListener());
+ Mockito.verifyNoInteractions(getDeadletterListener());
// get actionId from Message
- Long actionId = Long.parseLong(getJsonFieldFromBody(message.getBody(), "actionId"));
+ final Long actionId = Long.parseLong(getJsonFieldFromBody(message.getBody(), "actionId"));
// Send DOWNLOADED message, should result in the action being closed
createAndSendActionStatusUpdateMessage(controllerId, actionId, DmfActionStatus.DOWNLOADED);
assertAction(actionId, 1, Status.RUNNING, Status.DOWNLOADED);
- Mockito.verifyZeroInteractions(getDeadletterListener());
+ Mockito.verifyNoInteractions(getDeadletterListener());
verifyAssignedDsAndInstalledDs(controllerId, distributionSet.getId(), null);
// Send FINISHED message
createAndSendActionStatusUpdateMessage(controllerId, actionId, DmfActionStatus.FINISHED);
assertAction(actionId, 2, Status.RUNNING, Status.DOWNLOADED, Status.FINISHED);
- Mockito.verifyZeroInteractions(getDeadletterListener());
+ Mockito.verifyNoInteractions(getDeadletterListener());
verifyAssignedDsAndInstalledDs(controllerId, distributionSet.getId(), distributionSet.getId());
}
@@ -899,7 +955,7 @@ public class AmqpMessageHandlerServiceIntegrationTest extends AbstractAmqpServic
final String controllerId = "dummy_target";
try {
- for (Class extends RuntimeException> exceptionClass : exceptionsThatShouldNotBeRequeued) {
+ for (final Class extends RuntimeException> exceptionClass : exceptionsThatShouldNotBeRequeued) {
doThrow(exceptionClass).when(mockedControllerManagement)
.findOrRegisterTargetIfItDoesNotExist(eq(controllerId), any());
@@ -998,7 +1054,7 @@ public class AmqpMessageHandlerServiceIntegrationTest extends AbstractAmqpServic
}
private void assertEmptyReceiverQueueCount() {
- assertThat(getAuthenticationMessageCount()).isEqualTo(0);
+ assertThat(getAuthenticationMessageCount()).isZero();
}
private void verifyOneDeadLetterMessage() {
@@ -1013,7 +1069,7 @@ public class AmqpMessageHandlerServiceIntegrationTest extends AbstractAmqpServic
}
private static String getJsonFieldFromBody(final byte[] body, final String fieldName) throws IOException {
- ObjectMapper objectMapper = new ObjectMapper();
+ final ObjectMapper objectMapper = new ObjectMapper();
final ObjectNode node = objectMapper.readValue(new String(body, Charset.defaultCharset()), ObjectNode.class);
assertThat(node.has(fieldName)).isTrue();
return node.get(fieldName).asText();
diff --git a/hawkbit-dmf/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/json/model/DmfCreateThing.java b/hawkbit-dmf/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/json/model/DmfCreateThing.java
index 4e6d84c5e..b99ca293d 100644
--- a/hawkbit-dmf/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/json/model/DmfCreateThing.java
+++ b/hawkbit-dmf/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/json/model/DmfCreateThing.java
@@ -23,6 +23,9 @@ public class DmfCreateThing {
@JsonProperty
private String name;
+ @JsonProperty
+ private DmfAttributeUpdate attributeUpdate;
+
public String getName() {
return name;
}
@@ -31,4 +34,11 @@ public class DmfCreateThing {
this.name = name;
}
+ public DmfAttributeUpdate getAttributeUpdate() {
+ return attributeUpdate;
+ }
+
+ public void setAttributeUpdate(final DmfAttributeUpdate attributeUpdate) {
+ this.attributeUpdate = attributeUpdate;
+ }
}
diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/Target.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/Target.java
index 8591b2572..952c432e7 100644
--- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/Target.java
+++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/Target.java
@@ -98,5 +98,4 @@ public interface Target extends NamedEntity {
* {@link #getControllerAttributes()}.
*/
boolean isRequestControllerAttributes();
-
}