diff --git a/docs/content/apis/dmf_api.md b/docs/content/apis/dmf_api.md
index 1c024fe3f..48c64f580 100644
--- a/docs/content/apis/dmf_api.md
+++ b/docs/content/apis/dmf_api.md
@@ -58,6 +58,28 @@ Header
----------------------------------------------------------------------------------- | -------------------------------------------------------------------------------
type=THING\_CREATED
tenant=tenant123
thingId=abc
sender=Lwm2m | content\_type=application/json
reply_to (optional) =sp.connector.replyTo
+
+### THING_REMOVED
+
+Message to request the deletion of a provisioning target.
+
+Header | Description | Type | Mandatory
+-------------- | ------------------------------------------------ | ---------------------------- | -------------------------------------------------------------
+type | Type of the message | Fixed string "THING_REMOVED" | true
+thingId | The ID of the registered provisioning target | String | true
+tenant | The tenant this provisioning target belongs to | String | false
+
+Message Properties | Description | Type | Mandatory
+------------------ | ---------------------------------------------------------------------------------------------------- | ------ | -------------------------------------------------------------
+content_type | The content type of the payload | String | true
+
+Example headers
+
+Header | MessageProperties
+----------------------------------------------------------------------------------- | -------------------------------------------------------------------------------
+type=THING\_REMOVED
tenant=tenant123
thingId=abc | content\_type=application/json
+
+
### UPDATE_ATTRIBUTES
Message to update target attributes. This message can be send in response to a _REQUEST_ATTRIBUTES_UPDATE_ event, sent by hawkBit.
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 9c36c5d7c..7bdbd8cdd 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
@@ -82,6 +82,8 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
private final SystemSecurityContext systemSecurityContext;
+ private static final String THING_ID_NULL = "ThingId is null";
+
/**
* Constructor.
*
@@ -156,6 +158,10 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
setTenantSecurityContext(tenant);
registerTarget(message, virtualHost);
break;
+ case THING_REMOVED:
+ setTenantSecurityContext(tenant);
+ deleteTarget(message);
+ break;
case EVENT:
checkContentTypeJson(message);
setTenantSecurityContext(tenant);
@@ -200,7 +206,7 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
* the ip of the target/thing
*/
private void registerTarget(final Message message, final String virtualHost) {
- final String thingId = getStringHeaderKey(message, MessageHeaderKey.THING_ID, "ThingId is null");
+ final String thingId = getStringHeaderKey(message, MessageHeaderKey.THING_ID, THING_ID_NULL);
final String replyTo = message.getMessageProperties().getReplyTo();
if (StringUtils.isEmpty(replyTo)) {
@@ -274,8 +280,6 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
*
* @param message
* the incoming event message.
- * @param topic
- * the topic of the event.
*/
private void handleIncomingEvent(final Message message) {
switch (EventTopic.valueOf(getStringHeaderKey(message, MessageHeaderKey.TOPIC, "EventTopic is null"))) {
@@ -292,9 +296,14 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
}
+ private void deleteTarget(final Message message) {
+ final String thingId = getStringHeaderKey(message, MessageHeaderKey.THING_ID, THING_ID_NULL);
+ controllerManagement.deleteExistingTarget(thingId);
+ }
+
private void updateAttributes(final Message message) {
final DmfAttributeUpdate attributeUpdate = convertMessage(message, DmfAttributeUpdate.class);
- final String thingId = getStringHeaderKey(message, MessageHeaderKey.THING_ID, "ThingId is null");
+ final String thingId = getStringHeaderKey(message, MessageHeaderKey.THING_ID, THING_ID_NULL);
controllerManagement.updateControllerAttributes(thingId, attributeUpdate.getAttributes(),
getUpdateMode(attributeUpdate));
@@ -303,7 +312,7 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
/**
* Method to update the action status of an action through the event.
*
- * @param actionUpdateStatus
+ * @param message
* the object form the ampq message
*/
private void updateActionStatus(final Message message) {
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 8fe0132ce..723e4028c 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
@@ -9,8 +9,8 @@
package org.eclipse.hawkbit.amqp;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.MULTI_ASSIGNMENTS_ENABLED;
-import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
@@ -82,10 +82,14 @@ import io.qameta.allure.Story;
@Story("AmqpMessage Handler Service Test")
public class AmqpMessageHandlerServiceTest {
+ private static final String FAIL_MESSAGE_AMQP_REJECT_REASON = AmqpRejectAndDontRequeueException.class.getSimpleName()
+ + " was expected, ";
+
private static final String SHA1 = "12345";
+ private static final String VIRTUAL_HOST = "vHost";
private static final String TENANT = "DEFAULT";
private static final Long TENANT_ID = 123L;
- private static final String CONTROLLLER_ID = "123";
+ private static final String CONTROLLER_ID = "123";
private static final Long TARGET_ID = 123L;
private AmqpMessageHandlerService amqpMessageHandlerService;
@@ -162,11 +166,10 @@ public class AmqpMessageHandlerServiceTest {
final MessageProperties messageProperties = new MessageProperties();
messageProperties.setContentType("xml");
final Message message = new Message(new byte[0], messageProperties);
- try {
- amqpMessageHandlerService.onMessage(message, MessageType.THING_CREATED.name(), TENANT, "vHost");
- fail("AmqpRejectAndDontRequeueException was excepeted due to worng content type");
- } catch (final AmqpRejectAndDontRequeueException e) {
- }
+ assertThatExceptionOfType(AmqpRejectAndDontRequeueException.class)
+ .as(FAIL_MESSAGE_AMQP_REJECT_REASON + "due to wrong content type")
+ .isThrownBy(() -> amqpMessageHandlerService.onMessage(message, MessageType.THING_CREATED.name(), TENANT,
+ VIRTUAL_HOST));
}
@Test
@@ -185,12 +188,11 @@ public class AmqpMessageHandlerServiceTest {
uriCaptor.capture())).thenReturn(targetMock);
when(controllerManagementMock.findOldestActiveActionByTarget(any())).thenReturn(Optional.empty());
- amqpMessageHandlerService.onMessage(message, MessageType.THING_CREATED.name(), TENANT, "vHost");
+ 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://vHost/MyTest");
-
+ assertThat(uriCaptor.getValue().toString()).as("Uri is not right").isEqualTo("amqp://"+VIRTUAL_HOST+"/MyTest");
}
@Test
@@ -210,13 +212,12 @@ public class AmqpMessageHandlerServiceTest {
when(controllerManagementMock.updateControllerAttributes(targetIdCaptor.capture(), attributesCaptor.capture(),
modeCaptor.capture())).thenReturn(null);
- amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, "vHost");
+ 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());
-
}
@Test
@@ -235,7 +236,7 @@ public class AmqpMessageHandlerServiceTest {
// 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");
+ 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();
@@ -243,40 +244,36 @@ public class AmqpMessageHandlerServiceTest {
// 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");
+ 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);
// 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");
+ 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);
// 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");
+ 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);
-
}
@Test
@Description("Tests the creation of a thing without a 'reply to' header in message.")
- public void createThingWitoutReplyTo() {
+ public void createThingWithoutReplyTo() {
final MessageProperties messageProperties = createMessageProperties(MessageType.THING_CREATED, null);
messageProperties.setHeader(MessageHeaderKey.THING_ID, "1");
final Message message = messageConverter.toMessage("", messageProperties);
- try {
- amqpMessageHandlerService.onMessage(message, MessageType.THING_CREATED.name(), TENANT, "vHost");
- fail("AmqpRejectAndDontRequeueException was excepeted since no replyTo header was set");
- } catch (final AmqpRejectAndDontRequeueException exception) {
- // test ok - exception was excepted
- }
-
+ assertThatExceptionOfType(AmqpRejectAndDontRequeueException.class)
+ .as(FAIL_MESSAGE_AMQP_REJECT_REASON + "since no replyTo header was set")
+ .isThrownBy(() -> amqpMessageHandlerService.onMessage(message, MessageType.THING_CREATED.name(), TENANT,
+ VIRTUAL_HOST));
}
@Test
@@ -284,12 +281,11 @@ public class AmqpMessageHandlerServiceTest {
public void createThingWithoutID() {
final MessageProperties messageProperties = createMessageProperties(MessageType.THING_CREATED);
final Message message = messageConverter.toMessage(new byte[0], messageProperties);
- try {
- amqpMessageHandlerService.onMessage(message, MessageType.THING_CREATED.name(), TENANT, "vHost");
- fail("AmqpRejectAndDontRequeueException was excepeted since no thingID was set");
- } catch (final AmqpRejectAndDontRequeueException exception) {
- // test ok - exception was excepted
- }
+
+ assertThatExceptionOfType(AmqpRejectAndDontRequeueException.class)
+ .as(FAIL_MESSAGE_AMQP_REJECT_REASON + "since no thingId was set")
+ .isThrownBy(() -> amqpMessageHandlerService.onMessage(message, MessageType.THING_CREATED.name(), TENANT,
+ VIRTUAL_HOST));
}
@Test
@@ -300,12 +296,9 @@ public class AmqpMessageHandlerServiceTest {
messageProperties.setHeader(MessageHeaderKey.THING_ID, "");
final Message message = messageConverter.toMessage(new byte[0], messageProperties);
- try {
- amqpMessageHandlerService.onMessage(message, type, TENANT, "vHost");
- fail("AmqpRejectAndDontRequeueException was excepeted due to unknown message type");
- } catch (final AmqpRejectAndDontRequeueException exception) {
- // test ok - exception was excepted
- }
+ assertThatExceptionOfType(AmqpRejectAndDontRequeueException.class)
+ .as(FAIL_MESSAGE_AMQP_REJECT_REASON + "due to unknown message type")
+ .isThrownBy(() -> amqpMessageHandlerService.onMessage(message, type, TENANT, VIRTUAL_HOST));
}
@Test
@@ -313,27 +306,23 @@ public class AmqpMessageHandlerServiceTest {
public void invalidEventTopic() {
final MessageProperties messageProperties = createMessageProperties(MessageType.EVENT);
final Message message = new Message(new byte[0], messageProperties);
- try {
- amqpMessageHandlerService.onMessage(message, "unknownMessageType", TENANT, "vHost");
- fail("AmqpRejectAndDontRequeueException was excepeted due to unknown message type");
- } catch (final AmqpRejectAndDontRequeueException e) {
- }
- try {
- messageProperties.setHeader(MessageHeaderKey.TOPIC, "wrongTopic");
- amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, "vHost");
- fail("AmqpRejectAndDontRequeueException was excepeted due to unknown topic");
- } catch (final AmqpRejectAndDontRequeueException e) {
- }
+ assertThatExceptionOfType(AmqpRejectAndDontRequeueException.class)
+ .as(FAIL_MESSAGE_AMQP_REJECT_REASON + "due to unknown message type")
+ .isThrownBy(() -> amqpMessageHandlerService.onMessage(message, "unknownMessageType", TENANT,
+ VIRTUAL_HOST));
+
+ messageProperties.setHeader(MessageHeaderKey.TOPIC, "wrongTopic");
+ assertThatExceptionOfType(AmqpRejectAndDontRequeueException.class)
+ .as(FAIL_MESSAGE_AMQP_REJECT_REASON + "due to unknown topic")
+ .isThrownBy(() -> amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT,
+ VIRTUAL_HOST));
messageProperties.setHeader(MessageHeaderKey.TOPIC, EventTopic.CANCEL_DOWNLOAD.name());
- try {
- amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, "vHost");
- fail("AmqpRejectAndDontRequeueException was excepeted because there was no event topic");
- } catch (final AmqpRejectAndDontRequeueException exception) {
- // test ok - exception was excepted
- }
-
+ assertThatExceptionOfType(AmqpRejectAndDontRequeueException.class)
+ .as(FAIL_MESSAGE_AMQP_REJECT_REASON + "because there was no event topic")
+ .isThrownBy(() -> amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT,
+ VIRTUAL_HOST));
}
@Test
@@ -346,12 +335,10 @@ public class AmqpMessageHandlerServiceTest {
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(actionUpdateStatus,
messageProperties);
- try {
- amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, "vHost");
- fail("AmqpRejectAndDontRequeueException was excepeted since no action id was set");
- } catch (final AmqpRejectAndDontRequeueException exception) {
- // test ok - exception was excepted
- }
+ assertThatExceptionOfType(AmqpRejectAndDontRequeueException.class)
+ .as(FAIL_MESSAGE_AMQP_REJECT_REASON + "since no action id was set")
+ .isThrownBy(() -> amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT,
+ VIRTUAL_HOST));
}
@Test
@@ -365,20 +352,17 @@ public class AmqpMessageHandlerServiceTest {
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(actionUpdateStatus,
messageProperties);
- try {
- amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, "vHost");
- fail("AmqpRejectAndDontRequeueException was excepeted since no action id was set");
- } catch (final AmqpRejectAndDontRequeueException exception) {
- // test ok - exception was excepted
- }
-
+ assertThatExceptionOfType(AmqpRejectAndDontRequeueException.class)
+ .as(FAIL_MESSAGE_AMQP_REJECT_REASON + "since no action id was set")
+ .isThrownBy(() -> amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT,
+ VIRTUAL_HOST));
}
@Test
@Description("Tests that an download request is denied for an artifact which does not exists")
public void authenticationRequestDeniedForArtifactWhichDoesNotExists() {
final MessageProperties messageProperties = createMessageProperties(null);
- final DmfTenantSecurityToken securityToken = new DmfTenantSecurityToken(TENANT, TENANT_ID, CONTROLLLER_ID,
+ final DmfTenantSecurityToken securityToken = new DmfTenantSecurityToken(TENANT, TENANT_ID, CONTROLLER_ID,
TARGET_ID, FileResource.createFileResourceBySha1("12345"));
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken,
messageProperties);
@@ -397,7 +381,7 @@ public class AmqpMessageHandlerServiceTest {
@Description("Tests that an download request is denied for an artifact which is not assigned to the requested target")
public void authenticationRequestDeniedForArtifactWhichIsNotAssignedToTarget() {
final MessageProperties messageProperties = createMessageProperties(null);
- final DmfTenantSecurityToken securityToken = new DmfTenantSecurityToken(TENANT, TENANT_ID, CONTROLLLER_ID,
+ final DmfTenantSecurityToken securityToken = new DmfTenantSecurityToken(TENANT, TENANT_ID, CONTROLLER_ID,
TARGET_ID, FileResource.createFileResourceBySha1("12345"));
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken,
messageProperties);
@@ -419,7 +403,7 @@ public class AmqpMessageHandlerServiceTest {
@Description("Tests that an download request is allowed for an artifact which exists and assigned to the requested target")
public void authenticationRequestAllowedForArtifactWhichExistsAndAssignedToTarget() throws MalformedURLException {
final MessageProperties messageProperties = createMessageProperties(null);
- final DmfTenantSecurityToken securityToken = new DmfTenantSecurityToken(TENANT, TENANT_ID, CONTROLLLER_ID,
+ final DmfTenantSecurityToken securityToken = new DmfTenantSecurityToken(TENANT, TENANT_ID, CONTROLLER_ID,
TARGET_ID, FileResource.createFileResourceBySha1("12345"));
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken,
messageProperties);
@@ -473,7 +457,7 @@ public class AmqpMessageHandlerServiceTest {
messageProperties);
// test
- amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, "vHost");
+ amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, VIRTUAL_HOST);
final ArgumentCaptor actionPropertiesCaptor = ArgumentCaptor.forClass(ActionProperties.class);
final ArgumentCaptor targetCaptor = ArgumentCaptor.forClass(Target.class);
@@ -485,7 +469,6 @@ public class AmqpMessageHandlerServiceTest {
assertThat(actionProperties.getTenant()).as("event has tenant").isEqualTo("DEFAULT");
assertThat(targetCaptor.getValue().getControllerId()).as("event has wrong controller id").isEqualTo("target1");
assertThat(actionProperties.getId()).as("event has wrong action id").isEqualTo(22L);
-
}
private DmfActionUpdateStatus createActionUpdateStatus(final DmfActionStatus status) {
@@ -515,7 +498,7 @@ public class AmqpMessageHandlerServiceTest {
private Action createActionWithTarget(final Long targetId, final Status status) throws IllegalAccessException {
// is needed for the creation of targets
- initalizeSecurityTokenGenerator();
+ initializeSecurityTokenGenerator();
// Mock
final Action actionMock = mock(Action.class);
@@ -531,7 +514,7 @@ public class AmqpMessageHandlerServiceTest {
return actionMock;
}
- private void initalizeSecurityTokenGenerator() throws IllegalAccessException {
+ private void initializeSecurityTokenGenerator() throws IllegalAccessException {
final SecurityTokenGeneratorHolder instance = SecurityTokenGeneratorHolder.getInstance();
final Field[] fields = instance.getClass().getDeclaredFields();
for (final Field field : fields) {
@@ -541,4 +524,34 @@ public class AmqpMessageHandlerServiceTest {
}
}
}
+
+ @Test
+ @Description("Tests the deletion of a target/thing, requested by the target itself.")
+ public void deleteThing() {
+ // prepare valid message
+ 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);
+
+ // test
+ amqpMessageHandlerService.onMessage(message, MessageType.THING_REMOVED.name(), TENANT, VIRTUAL_HOST);
+
+ //verify
+ verify(controllerManagementMock).deleteExistingTarget(knownThingId);
+ }
+
+ @Test
+ @Description("Tests the deletion of a target/thing with missing thingId")
+ public void deleteThingWithoutThingId() {
+ // prepare invalid message
+ final MessageProperties messageProperties = createMessageProperties(MessageType.THING_REMOVED);
+ final Message message = messageConverter.toMessage(new byte[0], messageProperties);
+
+ assertThatExceptionOfType(AmqpRejectAndDontRequeueException.class)
+ .as(FAIL_MESSAGE_AMQP_REJECT_REASON + "since no thingId was set")
+ .isThrownBy(() -> amqpMessageHandlerService.onMessage(message, MessageType.THING_REMOVED.name(), TENANT,
+ VIRTUAL_HOST));
+ }
+
}
diff --git a/hawkbit-dmf/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/amqp/api/MessageType.java b/hawkbit-dmf/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/amqp/api/MessageType.java
index 64cb28407..dfcd1a362 100644
--- a/hawkbit-dmf/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/amqp/api/MessageType.java
+++ b/hawkbit-dmf/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/amqp/api/MessageType.java
@@ -29,13 +29,18 @@ public enum MessageType {
*/
THING_DELETED,
+ /**
+ * The request to delete a target.
+ */
+ THING_REMOVED,
+
/**
* DMF receiver health check type.
*/
PING,
/**
- * DMF receiver health check reponse type.
+ * DMF receiver health check response type.
*/
PING_RESPONSE
diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ControllerManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ControllerManagement.java
index 712ce8637..3a6d43dc2 100644
--- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ControllerManagement.java
+++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ControllerManagement.java
@@ -48,14 +48,14 @@ public interface ControllerManagement {
/**
* Adds an {@link ActionStatus} for a cancel {@link Action} including
* potential state changes for the target and the {@link Action} itself.
- *
+ *
* @param create
* to be added
* @return the updated {@link Action}
- *
+ *
* @throws EntityAlreadyExistsException
* if a given entity already exists
- *
+ *
* @throws QuotaExceededException
* if more than the allowed number of status entries or messages
* per entry are inserted
@@ -64,14 +64,14 @@ public interface ControllerManagement {
* @throws ConstraintViolationException
* if fields are not filled as specified. Check
* {@link ActionStatusCreate} for field constraints.
- *
+ *
*/
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
Action addCancelActionStatus(@NotNull @Valid ActionStatusCreate create);
/**
* Retrieves assigned {@link SoftwareModule} of a target.
- *
+ *
* @param moduleId
* of the {@link SoftwareModule}
* @return {@link SoftwareModule} identified by ID
@@ -82,7 +82,7 @@ public interface ControllerManagement {
/**
* Retrieves {@link SoftwareModuleMetadata} where
* {@link SoftwareModuleMetadata#isTargetVisible()}.
- *
+ *
* @param moduleId
* of the {@link SoftwareModule}
* @return list of {@link SoftwareModuleMetadata} with maximum size of
@@ -95,12 +95,12 @@ public interface ControllerManagement {
/**
* Simple addition of a new {@link ActionStatus} entry to the {@link Action}
* . No state changes.
- *
+ *
* @param create
* to add to the action
- *
+ *
* @return created {@link ActionStatus} entity
- *
+ *
* @throws QuotaExceededException
* if more than the allowed number of status entries or messages
* per entry are inserted
@@ -138,15 +138,15 @@ public interface ControllerManagement {
/**
* Retrieves oldest {@link Action} that is active and assigned to a
* {@link Target}.
- *
+ *
* For performance reasons this method does not throw
- * {@link EntityNotFoundException} in case target with given controlelrId
+ * {@link EntityNotFoundException} in case target with given controllerId
* does not exist but will return an {@link Optional#empty()} instead.
*
* @param controllerId
* identifies the target to retrieve the actions from
* @return a list of actions assigned to given target which are active
- *
+ *
*/
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
Optional findOldestActiveActionByTarget(@NotEmpty String controllerId);
@@ -154,7 +154,7 @@ public interface ControllerManagement {
/**
* Retrieves all active actions which are assigned to the target with the
* given controller ID.
- *
+ *
* @param pageable
* pagination parameter
* @param controllerId
@@ -184,7 +184,7 @@ public interface ControllerManagement {
* @param actionId
* to be filtered on
* @return the corresponding {@link Page} of {@link ActionStatus}
- *
+ *
* @throws EntityNotFoundException
* if action with given ID does not exist
*/
@@ -217,7 +217,7 @@ public interface ControllerManagement {
* of the the {@link SoftwareModule} that should be assigned to
* the target
* @return last {@link Action} for given combination
- *
+ *
* @throws EntityNotFoundException
* if target with given ID does not exist
*
@@ -244,7 +244,7 @@ public interface ControllerManagement {
/**
* Returns the count to be used for reducing polling interval while calling
- * {@link ControllerManagement#getPollingTimeForAction()}.
+ * {@link ControllerManagement#getPollingTimeForAction(long)}.
*
* @return configured value of
* {@link TenantConfigurationKey#MAINTENANCE_WINDOW_POLL_COUNT}.
@@ -277,7 +277,7 @@ public interface ControllerManagement {
* a target is allowed to download a given artifact because it has currently
* assigned or had ever been assigned to the target and so it's visible to a
* specific target e.g. for downloading.
- *
+ *
* @param controllerId
* the ID of the target to check
* @param sha1Hash
@@ -286,7 +286,7 @@ public interface ControllerManagement {
* @return {@code true} if the given target has currently or had ever a
* relation to the given artifact through the action history,
* otherwise {@code false}
- *
+ *
* @throws EntityNotFoundException
* if target with given ID does not exist
*/
@@ -299,7 +299,7 @@ public interface ControllerManagement {
* a target is allowed to download a given artifact because it has currently
* assigned or had ever been assigned to the target and so it's visible to a
* specific target e.g. for downloading.
- *
+ *
* @param targetId
* the ID of the target to check
* @param sha1Hash
@@ -308,7 +308,7 @@ public interface ControllerManagement {
* @return {@code true} if the given target has currently or had ever a
* relation to the given artifact through the action history,
* otherwise {@code false}
- *
+ *
* @throws EntityNotFoundException
* if target with given ID does not exist
*/
@@ -325,7 +325,7 @@ public interface ControllerManagement {
* for the status
* @return the update action in case the status has been changed to
* {@link Status#RETRIEVED}
- *
+ *
* @throws EntityNotFoundException
* if action with given ID does not exist
*/
@@ -432,7 +432,7 @@ public interface ControllerManagement {
/**
* Updates given {@link Action} with its external id.
- *
+ *
* @param actionId
* to be updated
* @param externalRef
@@ -451,4 +451,13 @@ public interface ControllerManagement {
*/
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
List getActiveActionsByExternalRef(@NotNull List externalRefs);
+
+ /**
+ * Delete a single target.
+ *
+ * @param controllerId
+ * of the target to delete
+ */
+ @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
+ void deleteExistingTarget(@NotEmpty String controllerId);
}
diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaControllerManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaControllerManagement.java
index 83eea384f..1b6fea651 100644
--- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaControllerManagement.java
+++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaControllerManagement.java
@@ -193,7 +193,7 @@ public class JpaControllerManagement implements ControllerManagement {
/**
* Returns the count to be used for reducing polling interval while calling
- * {@link ControllerManagement#getPollingTimeForAction()}.
+ * {@link ControllerManagement#getPollingTimeForAction(long)}.
*
* @return configured value of
* {@link TenantConfigurationKey#MAINTENANCE_WINDOW_POLL_COUNT}.
@@ -243,7 +243,7 @@ public class JpaControllerManagement implements ControllerManagement {
* @param minimumEventInterval
* for loading {@link DistributionSet#getModules()}. This
* puts a lower bound to the timer value
- * @param timerUnit
+ * @param timeUnit
* representing the unit of time to be used for timer.
*/
EventTimer(final String defaultEventInterval, final String minimumEventInterval, final TemporalUnit timeUnit) {
@@ -375,6 +375,13 @@ public class JpaControllerManagement implements ControllerManagement {
return actionRepository.findByExternalRefInAndActive(externalRefs, true);
}
+ @Override
+ public void deleteExistingTarget(@NotEmpty String controllerId) {
+ final Target target = targetRepository.findByControllerId(controllerId)
+ .orElseThrow(() -> new EntityNotFoundException(Target.class, controllerId));
+ targetRepository.deleteById(target.getId());
+ }
+
@Override
@Transactional(isolation = Isolation.READ_COMMITTED)
@Retryable(include = ConcurrencyFailureException.class, exclude = EntityAlreadyExistsException.class, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
@@ -997,8 +1004,8 @@ public class JpaControllerManagement implements ControllerManagement {
* Cancels given {@link Action} for this {@link Target}. The method will
* immediately add a {@link Status#CANCELED} status to the action. However,
* it might be possible that the controller will continue to work on the
- * cancelation. The controller needs to acknowledge or reject the
- * cancelation using {@link DdiRootController#postCancelActionFeedback}.
+ * cancellation. The controller needs to acknowledge or reject the
+ * cancellation using {@link DdiRootController#postCancelActionFeedback}.
*
* @param actionId
* to be canceled
diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/ControllerManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/ControllerManagementTest.java
index 324cafd03..082432e5f 100644
--- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/ControllerManagementTest.java
+++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/ControllerManagementTest.java
@@ -14,7 +14,6 @@ import static org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpre
import static org.eclipse.hawkbit.repository.jpa.configuration.Constants.TX_RT_MAX;
import static org.eclipse.hawkbit.repository.model.Action.ActionType.DOWNLOAD_ONLY;
import static org.eclipse.hawkbit.repository.test.util.TestdataFactory.DEFAULT_CONTROLLER_ID;
-import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -40,6 +39,7 @@ 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.TargetAttributesRequestedEvent;
+import org.eclipse.hawkbit.repository.event.remote.TargetDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.TargetPollEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.ActionCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.ActionUpdatedEvent;
@@ -51,6 +51,7 @@ import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent;
import org.eclipse.hawkbit.repository.exception.CancelActionNotAllowedException;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
+import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.InvalidTargetAttributeException;
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
@@ -89,7 +90,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
private RepositoryProperties repositoryProperties;
@Test
- @Description("Verifies that management get access react as specfied on calls for non existing entities by means "
+ @Description("Verifies that management get access react as specified on calls for non existing entities by means "
+ "of Optional not present.")
@ExpectEvents({@Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 1)})
@@ -110,7 +111,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
}
@Test
- @Description("Verifies that management queries react as specfied on calls for non existing entities "
+ @Description("Verifies that management queries react as specified on calls for non existing entities "
+ " by means of throwing EntityNotFoundException.")
@ExpectEvents({@Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 1)})
@@ -147,7 +148,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
}
@Test
- @Description("Controller confirms successfull update with FINISHED status.")
+ @Description("Controller confirms successful update with FINISHED status.")
@ExpectEvents({@Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
@Expect(type = ActionCreatedEvent.class, count = 1), @Expect(type = ActionUpdatedEvent.class, count = 1),
@@ -170,7 +171,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
}
@Test
- @Description("Controller confirmation failes with invalid messages.")
+ @Description("Controller confirmation fails with invalid messages.")
@ExpectEvents({@Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
@Expect(type = ActionCreatedEvent.class, count = 1), @Expect(type = TargetUpdatedEvent.class, count = 1),
@@ -182,23 +183,23 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
simulateIntermediateStatusOnUpdate(actionId);
assertThatExceptionOfType(ConstraintViolationException.class)
+ .as("set invalid description text should not be created")
.isThrownBy(() -> controllerManagement.addUpdateActionStatus(entityFactory.actionStatus()
- .create(actionId).status(Action.Status.FINISHED).message(INVALID_TEXT_HTML)))
- .as("set invalid description text should not be created");
+ .create(actionId).status(Action.Status.FINISHED).message(INVALID_TEXT_HTML)));
assertThatExceptionOfType(ConstraintViolationException.class)
+ .as("set invalid description text should not be created")
.isThrownBy(() -> controllerManagement.addUpdateActionStatus(
entityFactory.actionStatus().create(actionId).status(Action.Status.FINISHED)
- .messages(Arrays.asList("this is valid.", INVALID_TEXT_HTML))))
- .as("set invalid description text should not be created");
+ .messages(Arrays.asList("this is valid.", INVALID_TEXT_HTML))));
assertThat(actionStatusRepository.count()).isEqualTo(6);
assertThat(controllerManagement.findActionStatusByAction(PAGE, actionId).getNumberOfElements()).isEqualTo(6);
}
@Test
- @Description("Controller confirms successfull update with FINISHED status on a action that is on canceling. "
- + "Reason: The decission to ignore the cancellation is in fact up to the controller.")
+ @Description("Controller confirms successful update with FINISHED status on a action that is on canceling. "
+ + "Reason: The decision to ignore the cancellation is in fact up to the controller.")
@ExpectEvents({@Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
@Expect(type = ActionCreatedEvent.class, count = 1), @Expect(type = ActionUpdatedEvent.class, count = 2),
@@ -207,7 +208,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
@Expect(type = TargetAttributesRequestedEvent.class, count = 1),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3)})
- public void controllerConfirmsUpdateWithFinishedAndIgnorsCancellationWithThat() {
+ public void controllerConfirmsUpdateWithFinishedAndIgnoresCancellationWithThat() {
final Long actionId = createTargetAndAssignDs();
deploymentManagement.cancelAction(actionId);
@@ -221,7 +222,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
}
@Test
- @Description("Update server rejects cancelation feedback if action is not in CANCELING state.")
+ @Description("Update server rejects cancellation feedback if action is not in CANCELING state.")
@ExpectEvents({@Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
@Expect(type = ActionCreatedEvent.class, count = 1), @Expect(type = TargetUpdatedEvent.class, count = 1),
@@ -230,24 +231,20 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
public void cancellationFeedbackRejectedIfActionIsNotInCanceling() {
final Long actionId = createTargetAndAssignDs();
- try {
- controllerManagement.addCancelActionStatus(
- entityFactory.actionStatus().create(actionId).status(Action.Status.FINISHED));
- fail("Expected " + CancelActionNotAllowedException.class.getName());
- } catch (final CancelActionNotAllowedException e) {
- // expected
- }
+ assertThatExceptionOfType(CancelActionNotAllowedException.class)
+ .as("Expected " + CancelActionNotAllowedException.class.getName())
+ .isThrownBy(() -> controllerManagement.addCancelActionStatus(
+ entityFactory.actionStatus().create(actionId).status(Action.Status.FINISHED)));
assertActionStatus(actionId, DEFAULT_CONTROLLER_ID, TargetUpdateStatus.PENDING, Action.Status.RUNNING,
Action.Status.RUNNING, true);
assertThat(actionStatusRepository.count()).isEqualTo(1);
assertThat(controllerManagement.findActionStatusByAction(PAGE, actionId).getNumberOfElements()).isEqualTo(1);
-
}
@Test
- @Description("Controller confirms action cancelation with FINISHED status.")
+ @Description("Controller confirms action cancellation with FINISHED status.")
@ExpectEvents({@Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
@Expect(type = ActionCreatedEvent.class, count = 1), @Expect(type = ActionUpdatedEvent.class, count = 2),
@@ -255,7 +252,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
@Expect(type = CancelTargetAssignmentEvent.class, count = 1),
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3)})
- public void controllerConfirmsActionCancelationWithFinished() {
+ public void controllerConfirmsActionCancellationWithFinished() {
final Long actionId = createTargetAndAssignDs();
deploymentManagement.cancelAction(actionId);
@@ -274,7 +271,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
}
@Test
- @Description("Controller confirms action cancelation with FINISHED status.")
+ @Description("Controller confirms action cancellation with FINISHED status.")
@ExpectEvents({@Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
@Expect(type = ActionCreatedEvent.class, count = 1), @Expect(type = ActionUpdatedEvent.class, count = 2),
@@ -282,7 +279,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
@Expect(type = CancelTargetAssignmentEvent.class, count = 1),
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3)})
- public void controllerConfirmsActionCancelationWithCanceled() {
+ public void controllerConfirmsActionCancellationWithCanceled() {
final Long actionId = createTargetAndAssignDs();
deploymentManagement.cancelAction(actionId);
@@ -301,7 +298,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
}
@Test
- @Description("Controller rejects action cancelation with CANCEL_REJECTED status. Action goes back to RUNNING status as it expects "
+ @Description("Controller rejects action cancellation with CANCEL_REJECTED status. Action goes back to RUNNING status as it expects "
+ "that the controller will continue the original update.")
@ExpectEvents({@Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
@@ -310,7 +307,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
@Expect(type = CancelTargetAssignmentEvent.class, count = 1),
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3)})
- public void controllerRejectsActionCancelationWithReject() {
+ public void controllerRejectsActionCancellationWithReject() {
final Long actionId = createTargetAndAssignDs();
deploymentManagement.cancelAction(actionId);
@@ -329,7 +326,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
}
@Test
- @Description("Controller rejects action cancelation with ERROR status. Action goes back to RUNNING status as it expects "
+ @Description("Controller rejects action cancellation with ERROR status. Action goes back to RUNNING status as it expects "
+ "that the controller will continue the original update.")
@ExpectEvents({@Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
@@ -338,7 +335,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
@Expect(type = CancelTargetAssignmentEvent.class, count = 1),
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3)})
- public void controllerRejectsActionCancelationWithError() {
+ public void controllerRejectsActionCancellationWithError() {
final Long actionId = createTargetAndAssignDs();
deploymentManagement.cancelAction(actionId);
@@ -466,7 +463,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
}
@Test
- @Description("Verifies that assignement verification works based on SHA1 hash. By design it is not important which artifact "
+ @Description("Verifies that assignment verification works based on SHA1 hash. By design it is not important which artifact "
+ "is actually used for the check as long as they have an identical binary, i.e. same SHA1 hash. ")
@ExpectEvents({@Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = DistributionSetCreatedEvent.class, count = 2),
@@ -511,28 +508,28 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
final Target sameTarget = controllerManagement.findOrRegisterTargetIfItDoesNotExist("AA", LOCALHOST);
assertThat(target.getId()).as("Target should be the equals").isEqualTo(sameTarget.getId());
- assertThat(targetRepository.count()).as("Only 1 target should be registred").isEqualTo(1L);
+ assertThat(targetRepository.count()).as("Only 1 target should be registered").isEqualTo(1L);
}
@Test
@Description("Tries to register a target with an invalid controller id")
public void findOrRegisterTargetIfItDoesNotExistThrowsExceptionForInvalidControllerIdParam() {
assertThatExceptionOfType(ConstraintViolationException.class)
- .isThrownBy(() -> controllerManagement.findOrRegisterTargetIfItDoesNotExist(null, LOCALHOST))
- .as("register target with null as controllerId should fail");
+ .as("register target with null as controllerId should fail")
+ .isThrownBy(() -> controllerManagement.findOrRegisterTargetIfItDoesNotExist(null, LOCALHOST));
assertThatExceptionOfType(ConstraintViolationException.class)
- .isThrownBy(() -> controllerManagement.findOrRegisterTargetIfItDoesNotExist("", LOCALHOST))
- .as("register target with empty controllerId should fail");
+ .as("register target with empty controllerId should fail")
+ .isThrownBy(() -> controllerManagement.findOrRegisterTargetIfItDoesNotExist("", LOCALHOST));
assertThatExceptionOfType(ConstraintViolationException.class)
- .isThrownBy(() -> controllerManagement.findOrRegisterTargetIfItDoesNotExist(" ", LOCALHOST))
- .as("register target with empty controllerId should fail");
+ .as("register target with empty controllerId should fail")
+ .isThrownBy(() -> controllerManagement.findOrRegisterTargetIfItDoesNotExist(" ", LOCALHOST));
assertThatExceptionOfType(ConstraintViolationException.class)
+ .as("register target with too long controllerId should fail")
.isThrownBy(() -> controllerManagement.findOrRegisterTargetIfItDoesNotExist(
- RandomStringUtils.randomAlphabetic(Target.CONTROLLER_ID_MAX_SIZE + 1), LOCALHOST))
- .as("register target with too long controllerId should fail");
+ RandomStringUtils.randomAlphabetic(Target.CONTROLLER_ID_MAX_SIZE + 1), LOCALHOST));
}
@Test
@@ -544,11 +541,13 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
((JpaControllerManagement) controllerManagement).setTargetRepository(mockTargetRepository);
try {
- controllerManagement.findOrRegisterTargetIfItDoesNotExist("AA", LOCALHOST);
- fail("Expected an ConcurrencyFailureException to be thrown!");
- } catch (final ConcurrencyFailureException e) {
+ assertThatExceptionOfType(ConcurrencyFailureException.class)
+ .as("Expected an ConcurrencyFailureException to be thrown!")
+ .isThrownBy(() -> controllerManagement.findOrRegisterTargetIfItDoesNotExist("AA", LOCALHOST));
+
verify(mockTargetRepository, times(TX_RT_MAX)).findOne(any());
- } finally {
+ }
+ finally {
// revert
((JpaControllerManagement) controllerManagement).setTargetRepository(targetRepository);
}
@@ -593,12 +592,13 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
when(mockTargetRepository.save(any())).thenThrow(EntityAlreadyExistsException.class);
try {
- controllerManagement.findOrRegisterTargetIfItDoesNotExist("1234", LOCALHOST);
- fail("Expected an EntityAlreadyExistsException to be thrown!");
- } catch (final EntityAlreadyExistsException e) {
+ assertThatExceptionOfType(EntityAlreadyExistsException.class)
+ .as("Expected an EntityAlreadyExistsException to be thrown!")
+ .isThrownBy(() -> controllerManagement.findOrRegisterTargetIfItDoesNotExist("1234", LOCALHOST));
verify(mockTargetRepository, times(1)).findOne(any());
verify(mockTargetRepository, times(1)).save(any());
- } finally {
+ }
+ finally {
// revert
((JpaControllerManagement) controllerManagement).setTargetRepository(targetRepository);
}
@@ -615,11 +615,13 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
when(mockTargetRepository.findOne(any())).thenThrow(RuntimeException.class);
try {
- controllerManagement.findOrRegisterTargetIfItDoesNotExist("aControllerId", LOCALHOST);
- fail("Expected a RuntimeException to be thrown!");
- } catch (final RuntimeException e) {
+ assertThatExceptionOfType(RuntimeException.class)
+ .as("Expected a RuntimeException to be thrown!")
+ .isThrownBy(() -> controllerManagement.findOrRegisterTargetIfItDoesNotExist("aControllerId",
+ LOCALHOST));
verify(mockTargetRepository, times(1)).findOne(any());
- } finally {
+ }
+ finally {
// revert
((JpaControllerManagement) controllerManagement).setTargetRepository(targetRepository);
}
@@ -639,7 +641,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
set.getModules().stream().map(SoftwareModule::getId).collect(Collectors.toList()));
assertThat(result).hasSize(3);
- result.entrySet().forEach(entry -> assertThat(entry.getValue()).hasSize(1));
+ result.forEach((key, value) -> assertThat(value).hasSize(1));
}
@Test
@@ -684,7 +686,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
Action.Status.ERROR, false);
// try with enabled late feedback - should not make a difference as it
- // only allows intermediate feedbacks and not multiple close
+ // only allows intermediate feedback and not multiple close
repositoryProperties.setRejectActionStatusForClosedAction(false);
controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.FINISHED));
@@ -699,7 +701,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
}
@Test
- @Description("Controller trys to finish an update process after it has been finished by an FINISHED action status.")
+ @Description("Controller tries to finish an update process after it has been finished by an FINISHED action status.")
@ExpectEvents({@Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
@Expect(type = ActionCreatedEvent.class, count = 1), @Expect(type = ActionUpdatedEvent.class, count = 1),
@@ -720,7 +722,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
Action.Status.FINISHED, false);
// try with enabled late feedback - should not make a difference as it
- // only allows intermediate feedbacks and not multiple close
+ // only allows intermediate feedback and not multiple close
repositoryProperties.setRejectActionStatusForClosedAction(false);
controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.FINISHED));
@@ -735,7 +737,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
}
@Test
- @Description("Controller trys to send an update feedback after it has been finished which is reject as the repository is "
+ @Description("Controller tries to send an update feedback after it has been finished which is reject as the repository is "
+ "configured to reject that.")
@ExpectEvents({@Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
@@ -744,7 +746,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
@Expect(type = TargetAttributesRequestedEvent.class, count = 1),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3)})
- public void sendUpdatesForFinishUpdateProcessDropedIfDisabled() {
+ public void sendUpdatesForFinishUpdateProcessDroppedIfDisabled() {
repositoryProperties.setRejectActionStatusForClosedAction(true);
final Action action = prepareFinishedUpdate();
@@ -762,7 +764,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
}
@Test
- @Description("Controller trys to send an update feedback after it has been finished which is accepted as the repository is "
+ @Description("Controller tries to send an update feedback after it has been finished which is accepted as the repository is "
+ "configured to accept them.")
@ExpectEvents({@Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
@@ -1001,24 +1003,24 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
testdataFactory.createTarget(controllerId);
assertThatExceptionOfType(InvalidTargetAttributeException.class)
+ .as("Attribute with key too long should not be created")
.isThrownBy(() -> controllerManagement.updateControllerAttributes(controllerId,
- Collections.singletonMap(keyTooLong, valueValid), null))
- .as("Attribute with key too long should not be created");
+ Collections.singletonMap(keyTooLong, valueValid), null));
assertThatExceptionOfType(InvalidTargetAttributeException.class)
+ .as("Attribute with key too long and value too long should not be created")
.isThrownBy(() -> controllerManagement.updateControllerAttributes(controllerId,
- Collections.singletonMap(keyTooLong, valueTooLong), null))
- .as("Attribute with key too long and value too long should not be created");
+ Collections.singletonMap(keyTooLong, valueTooLong), null));
assertThatExceptionOfType(InvalidTargetAttributeException.class)
+ .as("Attribute with value too long should not be created")
.isThrownBy(() -> controllerManagement.updateControllerAttributes(controllerId,
- Collections.singletonMap(keyValid, valueTooLong), null))
- .as("Attribute with value too long should not be created");
+ Collections.singletonMap(keyValid, valueTooLong), null));
assertThatExceptionOfType(InvalidTargetAttributeException.class)
+ .as("Attribute with key NULL should not be created")
.isThrownBy(() -> controllerManagement.updateControllerAttributes(controllerId,
- Collections.singletonMap(keyNull, valueValid), null))
- .as("Attribute with key NULL should not be created");
+ Collections.singletonMap(keyNull, valueValid), null));
}
@Test
@@ -1253,32 +1255,26 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
@Expect(type = ActionUpdatedEvent.class, count = 1),
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3)})
- public void quotaEceededExceptionWhenControllerReportsTooManyUpdateActionStatusMessagesForDownloadOnlyAction() {
+ public void quotaExceededExceptionWhenControllerReportsTooManyUpdateActionStatusMessagesForDownloadOnlyAction() {
final int maxMessages = quotaManagement.getMaxMessagesPerActionStatus();
testdataFactory.createTarget();
final Long actionId = createAndAssignDsAsDownloadOnly("downloadOnlyDs", DEFAULT_CONTROLLER_ID);
assertThat(actionId).isNotNull();
- try {
- IntStream.range(0, maxMessages).forEach(i -> controllerManagement
- .addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Status.DOWNLOADED)));
- fail("No QuotaExceededException thrown for too many DOWNLOADED updateActionStatus updates");
- } catch (final QuotaExceededException e) {
- }
+ assertThatExceptionOfType(QuotaExceededException.class)
+ .as("No QuotaExceededException thrown for too many DOWNLOADED updateActionStatus updates")
+ .isThrownBy(() -> IntStream.range(0, maxMessages).forEach(i -> controllerManagement
+ .addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Status.DOWNLOADED))));
- try {
- IntStream.range(0, maxMessages).forEach(i -> controllerManagement
- .addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Status.ERROR)));
- fail("No QuotaExceededException thrown for too many ERROR updateActionStatus updates");
- } catch (final QuotaExceededException e) {
- }
+ assertThatExceptionOfType(QuotaExceededException.class)
+ .as("No QuotaExceededException thrown for too many ERROR updateActionStatus updates")
+ .isThrownBy(() -> IntStream.range(0, maxMessages).forEach(i -> controllerManagement
+ .addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Status.ERROR))));
- try {
- IntStream.range(0, maxMessages).forEach(i -> controllerManagement
- .addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Status.FINISHED)));
- fail("No QuotaExceededException thrown for too many FINISHED updateActionStatus updates");
- } catch (final QuotaExceededException e) {
- }
+ assertThatExceptionOfType(QuotaExceededException.class)
+ .as("No QuotaExceededException thrown for too many FINISHED updateActionStatus updates")
+ .isThrownBy(() -> IntStream.range(0, maxMessages).forEach(i -> controllerManagement
+ .addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Status.FINISHED))));
}
@Test
@@ -1288,35 +1284,29 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
@Expect(type = ActionCreatedEvent.class, count = 1), @Expect(type = TargetUpdatedEvent.class, count = 1),
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3)})
- public void quotaEceededExceptionWhenControllerReportsTooManyUpdateActionStatusMessagesForForced() {
+ public void quotaExceededExceptionWhenControllerReportsTooManyUpdateActionStatusMessagesForForced() {
final int maxMessages = quotaManagement.getMaxMessagesPerActionStatus();
final Long actionId = createTargetAndAssignDs();
assertThat(actionId).isNotNull();
- try {
- IntStream.range(0, maxMessages).forEach(i -> controllerManagement
- .addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Status.DOWNLOADED)));
- fail("No QuotaExceededException thrown for too many DOWNLOADED updateActionStatus updates");
- } catch (final QuotaExceededException e) {
- }
+ assertThatExceptionOfType(QuotaExceededException.class)
+ .as("No QuotaExceededException thrown for too many DOWNLOADED updateActionStatus updates")
+ .isThrownBy( ()-> IntStream.range(0, maxMessages).forEach(i -> controllerManagement
+ .addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Status.DOWNLOADED))));
- try {
- IntStream.range(0, maxMessages).forEach(i -> controllerManagement
- .addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Status.ERROR)));
- fail("No QuotaExceededException thrown for too many ERROR updateActionStatus updates");
- } catch (final QuotaExceededException e) {
- }
+ assertThatExceptionOfType(QuotaExceededException.class)
+ .as("No QuotaExceededException thrown for too many ERROR updateActionStatus updates")
+ .isThrownBy(()->IntStream.range(0, maxMessages).forEach(i -> controllerManagement
+ .addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Status.ERROR))));
- try {
- IntStream.range(0, maxMessages).forEach(i -> controllerManagement
- .addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Status.FINISHED)));
- fail("No QuotaExceededException thrown for too many FINISHED updateActionStatus updates");
- } catch (final QuotaExceededException e) {
- }
+ assertThatExceptionOfType(QuotaExceededException.class)
+ .as("No QuotaExceededException thrown for too many FINISHED updateActionStatus updates")
+ .isThrownBy(()->IntStream.range(0, maxMessages).forEach(i -> controllerManagement
+ .addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Status.FINISHED))));
}
@Test
- @Description("Verify that the attaching externalRef to an action is propery stored")
+ @Description("Verify that the attaching externalRef to an action is properly stored")
public void updatedExternalRefOnActionIsReallyUpdated() {
final List allExternalRef = new ArrayList<>();
final List allActionId = new ArrayList<>();
@@ -1324,15 +1314,15 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
final DistributionSet knownDistributionSet = testdataFactory.createDistributionSet();
for (int i = 0; i < numberOfActions; i++) {
final String knownControllerId = "controllerId" + i;
- final String knownExternalref = "externalRefId" + i;
+ final String knownExternalRef = "externalRefId" + i;
testdataFactory.createTarget(knownControllerId);
final DistributionSetAssignmentResult assignmentResult = assignDistributionSet(knownDistributionSet.getId(),
knownControllerId);
final Long actionId = getFirstAssignedActionId(assignmentResult);
- controllerManagement.updateActionExternalRef(actionId, knownExternalref);
+ controllerManagement.updateActionExternalRef(actionId, knownExternalRef);
- allExternalRef.add(knownExternalref);
+ allExternalRef.add(knownExternalRef);
allActionId.add(actionId);
}
@@ -1346,11 +1336,9 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
@Test
@Description("Verify that a null externalRef cannot be assigned to an action")
public void externalRefCannotBeNull() {
- try {
- controllerManagement.updateActionExternalRef(1L, null);
- fail("No ConstraintViolationException thrown when a null externalRef was set on an action");
- } catch (final ConstraintViolationException e) {
- }
+ assertThatExceptionOfType(ConstraintViolationException.class)
+ .as("No ConstraintViolationException thrown when a null externalRef was set on an action")
+ .isThrownBy(() -> controllerManagement.updateActionExternalRef(1L, null));
}
@Test
@@ -1470,4 +1458,47 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
assertThat(actionRepository.activeActionExistsForControllerId(controllerId)).isEqualTo(false);
}
+
+ @Test
+ @Description("Delete a target on requested target deletion from client side")
+ @ExpectEvents({@Expect(type = TargetCreatedEvent.class, count = 1),
+ @Expect(type = TargetPollEvent.class, count = 1),
+ @Expect(type = TargetDeletedEvent.class, count = 1)})
+ public void deleteTargetWithValidThingId() {
+ final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotExist("AA", LOCALHOST);
+ assertThat(target).as("target should not be null").isNotNull();
+ assertThat(targetRepository.count()).as("target exists and is ready for deletion").isEqualTo(1L);
+
+ controllerManagement.deleteExistingTarget(target.getControllerId());
+
+ assertThat(targetRepository.count()).as("target should not exist anymore").isEqualTo(0L);
+ }
+
+ @Test
+ @Description("Delete a target with a non existing thingId")
+ @ExpectEvents({@Expect(type = TargetDeletedEvent.class, count = 0)})
+ public void deleteTargetWithInvalidThingId() {
+ assertThatExceptionOfType(EntityNotFoundException.class)
+ .as("No EntityNotFoundException thrown when deleting a non-existing target")
+ .isThrownBy(() -> controllerManagement.deleteExistingTarget("BB"));
+ assertThat(targetRepository.count()).as("target should not exist").isEqualTo(0L);
+ }
+
+ @Test
+ @Description("Delete a target after it has been deleted already")
+ @ExpectEvents({@Expect(type = TargetCreatedEvent.class, count = 1),
+ @Expect(type = TargetPollEvent.class, count = 1),
+ @Expect(type = TargetDeletedEvent.class, count = 1)})
+ public void deleteTargetAfterItWasDeleted() {
+ final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotExist("AA", LOCALHOST);
+ assertThat(target).as("target should not be null").isNotNull();
+ assertThat(targetRepository.count()).as("target exists and is ready for deletion").isEqualTo(1L);
+
+ controllerManagement.deleteExistingTarget(target.getControllerId());
+ assertThat(targetRepository.count()).as("target should not exist anymore").isEqualTo(0L);
+
+ assertThatExceptionOfType(EntityNotFoundException.class)
+ .as("No EntityNotFoundException thrown when deleting a non-existing target")
+ .isThrownBy(() -> controllerManagement.deleteExistingTarget(target.getControllerId()));
+ }
}