Optional controller attributes for THING_CREATED (#1154)

* added optional attributes payload to THING_CREATED DMF message

Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch.io>

* added optional attributes unit test for THING_CREATED message handler

Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch.io>

* extended Target interface with getControllerAttributes method, extended DMF message handler integration tests

Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch.io>

* updated hawkbit docs

Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch.io>

* fixed review findings

Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch.io>
This commit is contained in:
Bondar Bogdan
2021-08-02 17:45:48 +02:00
committed by GitHub
parent ace2f00742
commit 98535c96b4
7 changed files with 307 additions and 118 deletions

View File

@@ -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<Action> actions = controllerManagement
.findActiveActionsWithHighestWeight(target.getControllerId(), MAX_ACTION_COUNT);
final List<Action> actions = controllerManagement.findActiveActionsWithHighestWeight(target.getControllerId(),
MAX_ACTION_COUNT);
final Set<DistributionSet> distributionSets = actions.stream().map(Action::getDistributionSet)
.collect(Collectors.toSet());

View File

@@ -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<String> targetIdCaptor;
@Captor
private ArgumentCaptor<String> targetNameCaptor;
@Captor
private ArgumentCaptor<URI> 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<String> targetIdCaptor = ArgumentCaptor.forClass(String.class);
final ArgumentCaptor<URI> 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<String> 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<String, String> 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);
}
}

View File

@@ -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> T waitUntilIsPresent(final Callable<Optional<T>> 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<SoftwareModule> 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<String, String> 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<String, String> 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<String, String> attributes,
final Callable<Optional<Target>> 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<String, String> 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<Target> 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<String, String> 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<String, String> 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) {

View File

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

View File

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