diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpMessageDispatcherServiceIntegrationTest.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpMessageDispatcherServiceIntegrationTest.java index 8e2c2b080..43753f557 100644 --- a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpMessageDispatcherServiceIntegrationTest.java +++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpMessageDispatcherServiceIntegrationTest.java @@ -12,7 +12,9 @@ import java.util.Optional; import java.util.UUID; import java.util.concurrent.Callable; +import org.eclipse.hawkbit.dmf.json.model.DmfActionStatus; 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; @@ -30,6 +32,7 @@ import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; import org.eclipse.hawkbit.repository.test.matcher.Expect; import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents; import org.junit.Test; +import org.springframework.amqp.core.Message; import ru.yandex.qatools.allure.annotations.Description; import ru.yandex.qatools.allure.annotations.Features; @@ -53,7 +56,7 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AmqpServiceInte final String controllerId = TARGET_PREFIX + "sendDownloadAndInstallStatus"; registerTargetAndAssignDistributionSet(controllerId); - waitUntilTargetStatusIsPending(controllerId); + waitUntilTargetHasStatus(controllerId, TargetUpdateStatus.PENDING); assertDownloadAndInstallMessage(getDistributionSet().getModules(), controllerId); } @@ -75,7 +78,7 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AmqpServiceInte assignDistributionSetWithMaintenanceWindow(distributionSet.getId(), controllerId, getTestSchedule(2), getTestDuration(10), getTestTimeZone()); - waitUntilTargetStatusIsPending(controllerId); + waitUntilTargetHasStatus(controllerId, TargetUpdateStatus.PENDING); assertDownloadMessage(distributionSet.getModules(), controllerId); } @@ -97,7 +100,7 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AmqpServiceInte assignDistributionSetWithMaintenanceWindow(distributionSet.getId(), controllerId, getTestSchedule(-5), getTestDuration(10), getTestTimeZone()); - waitUntilTargetStatusIsPending(controllerId); + waitUntilTargetHasStatus(controllerId, TargetUpdateStatus.PENDING); assertDownloadAndInstallMessage(distributionSet.getModules(), controllerId); } @@ -122,7 +125,7 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AmqpServiceInte assertCancelActionMessage(assignmentResult.getActions().get(0), controllerId); createAndSendTarget(controllerId, TENANT_EXIST); - waitUntilTargetStatusIsPending(controllerId); + waitUntilTargetHasStatus(controllerId, TargetUpdateStatus.PENDING); assertCancelActionMessage(assignmentResult.getActions().get(0), controllerId); } @@ -143,7 +146,7 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AmqpServiceInte final Long actionId = registerTargetAndCancelActionId(controllerId); createAndSendTarget(controllerId, TENANT_EXIST); - waitUntilTargetStatusIsPending(controllerId); + waitUntilTargetHasStatus(controllerId, TargetUpdateStatus.PENDING); assertCancelActionMessage(actionId, controllerId); } @@ -159,11 +162,47 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AmqpServiceInte assertDeleteMessage(controllerId); } - private void waitUntilTargetStatusIsPending(final String controllerId) { + + @Test + @Description("Verify that attribute update is requested after device successfully closed software update.") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1), + @Expect(type = TargetAssignDistributionSetEvent.class, count = 2), + @Expect(type = ActionUpdatedEvent.class, count = 2), @Expect(type = ActionCreatedEvent.class, count = 2), + @Expect(type = SoftwareModuleCreatedEvent.class, count = 3), + @Expect(type = DistributionSetCreatedEvent.class, count = 1), + @Expect(type = TargetUpdatedEvent.class, count = 4), + @Expect(type = TargetAttributesRequestedEvent.class, count = 1), + @Expect(type = TargetPollEvent.class, count = 1) }) + public void attributeRequestAfterSuccessfulUpdate() { + final String controllerId = TARGET_PREFIX + "attributeUpdateRequest"; + registerAndAssertTargetWithExistingTenant(controllerId); + final Target target = controllerManagement.getByControllerId(controllerId).get(); + final DistributionSet distributionSet = testdataFactory.createDistributionSet(UUID.randomUUID().toString()); + + final long actionId1 = assignDistributionSet(distributionSet, target).getActions().get(0); + waitUntilTargetHasStatus(controllerId, TargetUpdateStatus.PENDING); + final Message messageError = createActionStatusUpdateMessage(controllerId, TENANT_EXIST, actionId1, + DmfActionStatus.ERROR); + getDmfClient().send(messageError); + waitUntilTargetHasStatus(controllerId, TargetUpdateStatus.ERROR); + + assertRequestAttributesUpdateMessageAbsent(); + + final long actionId2 = assignDistributionSet(distributionSet, target).getActions().get(0); + waitUntilTargetHasStatus(controllerId, TargetUpdateStatus.PENDING); + final Message messageFin = createActionStatusUpdateMessage(controllerId, TENANT_EXIST, actionId2, + DmfActionStatus.FINISHED); + getDmfClient().send(messageFin); + waitUntilTargetHasStatus(controllerId, TargetUpdateStatus.IN_SYNC); + + assertRequestAttributesUpdateMessage(controllerId); + } + + private void waitUntilTargetHasStatus(final String controllerId, final TargetUpdateStatus status) { waitUntil(() -> { final Optional findTargetByControllerID = targetManagement.getByControllerID(controllerId); return findTargetByControllerID.isPresent() - && TargetUpdateStatus.PENDING.equals(findTargetByControllerID.get().getUpdateStatus()); + && status.equals(findTargetByControllerID.get().getUpdateStatus()); }); } diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpMessageHandlerServiceIntegrationTest.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpMessageHandlerServiceIntegrationTest.java index 68bf97577..fb9175887 100644 --- a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpMessageHandlerServiceIntegrationTest.java +++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpMessageHandlerServiceIntegrationTest.java @@ -27,6 +27,7 @@ import org.eclipse.hawkbit.dmf.json.model.DmfAttributeUpdate; import org.eclipse.hawkbit.dmf.json.model.DmfUpdateMode; import org.eclipse.hawkbit.repository.RepositoryConstants; import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent; +import org.eclipse.hawkbit.repository.event.remote.TargetAttributesRequestedEvent; 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; @@ -363,13 +364,14 @@ public class AmqpMessageHandlerServiceIntegrationTest extends AmqpServiceIntegra } @Test - @Description("Tests register target and cancel a assignment") + @Description("Tests register target and send finished message") @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1), @Expect(type = TargetAssignDistributionSetEvent.class, count = 1), @Expect(type = ActionUpdatedEvent.class, count = 1), @Expect(type = ActionCreatedEvent.class, count = 1), @Expect(type = SoftwareModuleCreatedEvent.class, count = 3), @Expect(type = SoftwareModuleUpdatedEvent.class, count = 6), @Expect(type = DistributionSetCreatedEvent.class, count = 1), + @Expect(type = TargetAttributesRequestedEvent.class, count = 1), @Expect(type = TargetUpdatedEvent.class, count = 2), @Expect(type = TargetPollEvent.class, count = 1) }) public void finishActionStatus() { final String controllerId = TARGET_PREFIX + "finishActionStatus"; @@ -805,7 +807,7 @@ public class AmqpMessageHandlerServiceIntegrationTest extends AmqpServiceIntegra verifyNumberOfDeadLetterMessages(3); } - private void sendUpdateAttributesMessageWithGivenAttributes(String target, String key, String value) { + private void sendUpdateAttributesMessageWithGivenAttributes(final String target, final String key, final String value) { final DmfAttributeUpdate controllerAttribute = new DmfAttributeUpdate(); controllerAttribute.getAttributes().put(key, value); final Message message = createUpdateAttributesMessage(target, TENANT_EXIST, controllerAttribute); @@ -889,7 +891,7 @@ public class AmqpMessageHandlerServiceIntegrationTest extends AmqpServiceIntegra verifyNumberOfDeadLetterMessages(1); } - private void verifyNumberOfDeadLetterMessages(int numberOfInvocations) { + private void verifyNumberOfDeadLetterMessages(final int numberOfInvocations) { assertEmptyReceiverQueueCount(); createConditionFactory() .until(() -> Mockito.verify(getDeadletterListener(), Mockito.times(numberOfInvocations)).handleMessage(Mockito.any())); diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpServiceIntegrationTest.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpServiceIntegrationTest.java index 58eb8fbe5..819f179c3 100644 --- a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpServiceIntegrationTest.java +++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpServiceIntegrationTest.java @@ -23,6 +23,8 @@ import org.eclipse.hawkbit.dmf.amqp.api.EventTopic; import org.eclipse.hawkbit.dmf.amqp.api.MessageHeaderKey; import org.eclipse.hawkbit.dmf.amqp.api.MessageType; 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.DmfDownloadAndUpdateRequest; import org.eclipse.hawkbit.dmf.json.model.DmfMetadata; @@ -147,6 +149,14 @@ public abstract class AmqpServiceIntegrationTest extends AbstractAmqpIntegration assertThat(headers.get(MessageHeaderKey.TYPE)).isEqualTo(MessageType.THING_DELETED.toString()); } + protected void assertRequestAttributesUpdateMessage(final String target) { + assertReplyMessageHeader(EventTopic.REQUEST_ATTRIBUTES_UPDATE, target); + } + + protected void assertRequestAttributesUpdateMessageAbsent() { + assertThat(replyToListener.getEventTopicMessages()).doesNotContainKey(EventTopic.REQUEST_ATTRIBUTES_UPDATE); + } + protected void assertPingReplyMessage(final String correlationId) { verifyReplyToListener(); @@ -302,6 +312,17 @@ public abstract class AmqpServiceIntegrationTest extends AbstractAmqpIntegration return createMessage(null, messageProperties); } + protected Message createActionStatusUpdateMessage(final String target, final String tenant, final long actionId, + final DmfActionStatus status) { + final MessageProperties messageProperties = createMessagePropertiesWithTenant(tenant); + messageProperties.getHeaders().put(MessageHeaderKey.THING_ID, target); + messageProperties.getHeaders().put(MessageHeaderKey.TYPE, MessageType.EVENT.toString()); + messageProperties.getHeaders().put(MessageHeaderKey.TOPIC, EventTopic.UPDATE_ACTION_STATUS.toString()); + + final DmfActionUpdateStatus dmfActionUpdateStatus = new DmfActionUpdateStatus(actionId, status); + return createMessage(dmfActionUpdateStatus, messageProperties); + } + protected MessageProperties createMessagePropertiesWithTenant(final String tenant) { final MessageProperties messageProperties = new MessageProperties(); messageProperties.getHeaders().put(MessageHeaderKey.TENANT, tenant); 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 f5433b2af..693f68a77 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 @@ -8,6 +8,9 @@ */ package org.eclipse.hawkbit.repository.jpa; +import static org.eclipse.hawkbit.repository.model.Target.CONTROLLER_ATTRIBUTE_KEY_SIZE; +import static org.eclipse.hawkbit.repository.model.Target.CONTROLLER_ATTRIBUTE_VALUE_SIZE; + import java.net.URI; import java.time.Duration; import java.time.ZonedDateTime; @@ -37,6 +40,7 @@ import org.eclipse.hawkbit.repository.MaintenanceScheduleHelper; import org.eclipse.hawkbit.repository.QuotaManagement; import org.eclipse.hawkbit.repository.RepositoryConstants; import org.eclipse.hawkbit.repository.RepositoryProperties; +import org.eclipse.hawkbit.repository.TargetManagement; import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.repository.UpdateMode; import org.eclipse.hawkbit.repository.builder.ActionStatusCreate; @@ -97,9 +101,6 @@ import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; -import static org.eclipse.hawkbit.repository.model.Target.CONTROLLER_ATTRIBUTE_KEY_SIZE; -import static org.eclipse.hawkbit.repository.model.Target.CONTROLLER_ATTRIBUTE_VALUE_SIZE; - /** * JPA based {@link ControllerManagement} implementation. * @@ -120,6 +121,9 @@ public class JpaControllerManagement implements ControllerManagement { @Autowired private TargetRepository targetRepository; + @Autowired + private TargetManagement targetManagement; + @Autowired private SoftwareModuleRepository softwareModuleRepository; @@ -595,7 +599,9 @@ public class JpaControllerManagement implements ControllerManagement { * Sets {@link TargetUpdateStatus} based on given {@link ActionStatus}. */ private Action handleAddUpdateActionStatus(final JpaActionStatus actionStatus, final JpaAction action) { - LOG.debug("addUpdateActionStatus for action {}", action.getId()); + + String controllerId = null; + LOG.debug("handleAddUpdateActionStatus for action {}", action.getId()); switch (actionStatus.getStatus()) { case ERROR: @@ -604,7 +610,7 @@ public class JpaControllerManagement implements ControllerManagement { handleErrorOnAction(action, target); break; case FINISHED: - handleFinishedAndStoreInTargetStatus(action); + controllerId = handleFinishedAndStoreInTargetStatus(action); break; default: // information status entry - check for a potential DOS attack @@ -615,10 +621,13 @@ public class JpaControllerManagement implements ControllerManagement { actionStatus.setAction(action); actionStatusRepository.save(actionStatus); + final Action savedAction = actionRepository.save(action); - LOG.debug("addUpdateActionStatus for action {} isfinished.", action.getId()); - - return actionRepository.save(action); + if (controllerId != null) { + targetManagement.requestControllerAttributes(controllerId); + } + + return savedAction; } private void handleErrorOnAction(final JpaAction mergedAction, final JpaTarget mergedTarget) { @@ -634,7 +643,7 @@ public class JpaControllerManagement implements ControllerManagement { ActionStatus.class, Action.class, actionStatusRepository::countByActionId); } - private void handleFinishedAndStoreInTargetStatus(final JpaAction action) { + private String handleFinishedAndStoreInTargetStatus(final JpaAction action) { final JpaTarget target = (JpaTarget) action.getTarget(); action.setActive(false); action.setStatus(Status.FINISHED); @@ -651,8 +660,9 @@ public class JpaControllerManagement implements ControllerManagement { } targetRepository.save(target); - entityManager.detach(ds); + + return target.getControllerId(); } @Override 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 e42e0afbc..e6ac27da5 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 @@ -29,6 +29,7 @@ import org.apache.commons.lang3.RandomUtils; import org.eclipse.hawkbit.repository.RepositoryProperties; import org.eclipse.hawkbit.repository.UpdateMode; import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent; +import org.eclipse.hawkbit.repository.event.remote.TargetAttributesRequestedEvent; 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; @@ -137,6 +138,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest { @Expect(type = ActionCreatedEvent.class, count = 1), @Expect(type = ActionUpdatedEvent.class, count = 1), @Expect(type = TargetUpdatedEvent.class, count = 2), @Expect(type = TargetAssignDistributionSetEvent.class, count = 1), + @Expect(type = TargetAttributesRequestedEvent.class, count = 1), @Expect(type = SoftwareModuleCreatedEvent.class, count = 3) }) public void controllerConfirmsUpdateWithFinished() { final Long actionId = createTargetAndAssignDs(); @@ -188,6 +190,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest { @Expect(type = CancelTargetAssignmentEvent.class, count = 1), @Expect(type = TargetUpdatedEvent.class, count = 2), @Expect(type = TargetAssignDistributionSetEvent.class, count = 1), + @Expect(type = TargetAttributesRequestedEvent.class, count = 1), @Expect(type = SoftwareModuleCreatedEvent.class, count = 3) }) public void controllerConfirmsUpdateWithFinishedAndIgnorsCancellationWithThat() { final Long actionId = createTargetAndAssignDs(); @@ -557,6 +560,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest { @Expect(type = ActionCreatedEvent.class, count = 1), @Expect(type = ActionUpdatedEvent.class, count = 1), @Expect(type = TargetUpdatedEvent.class, count = 2), @Expect(type = TargetAssignDistributionSetEvent.class, count = 1), + @Expect(type = TargetAttributesRequestedEvent.class, count = 1), @Expect(type = SoftwareModuleCreatedEvent.class, count = 3) }) public void tryToFinishUpdateProcessMoreThanOnce() { final Long actionId = prepareFinishedUpdate().getId(); @@ -593,6 +597,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest { @Expect(type = ActionCreatedEvent.class, count = 1), @Expect(type = ActionUpdatedEvent.class, count = 1), @Expect(type = TargetUpdatedEvent.class, count = 2), @Expect(type = TargetAssignDistributionSetEvent.class, count = 1), + @Expect(type = TargetAttributesRequestedEvent.class, count = 1), @Expect(type = SoftwareModuleCreatedEvent.class, count = 3) }) public void sendUpdatesForFinishUpdateProcessDropedIfDisabled() { repositoryProperties.setRejectActionStatusForClosedAction(true); @@ -619,6 +624,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest { @Expect(type = ActionCreatedEvent.class, count = 1), @Expect(type = ActionUpdatedEvent.class, count = 1), @Expect(type = TargetUpdatedEvent.class, count = 2), @Expect(type = TargetAssignDistributionSetEvent.class, count = 1), + @Expect(type = TargetAttributesRequestedEvent.class, count = 1), @Expect(type = SoftwareModuleCreatedEvent.class, count = 3) }) public void sendUpdatesForFinishUpdateProcessAcceptedIfEnabled() { repositoryProperties.setRejectActionStatusForClosedAction(false); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementTest.java index fa7dcdd49..85f7015e1 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementTest.java @@ -29,6 +29,7 @@ import org.apache.commons.lang3.RandomStringUtils; import org.eclipse.hawkbit.im.authentication.SpPermission; import org.eclipse.hawkbit.repository.FilterParams; 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; @@ -427,6 +428,7 @@ public class TargetManagementTest extends AbstractJpaIntegrationTest { @Expect(type = ActionCreatedEvent.class, count = 2), @Expect(type = ActionUpdatedEvent.class, count = 1), @Expect(type = TargetAssignDistributionSetEvent.class, count = 2), @Expect(type = SoftwareModuleCreatedEvent.class, count = 6), + @Expect(type = TargetAttributesRequestedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 1) }) public void findTargetByControllerIDWithDetails() { final DistributionSet set = testdataFactory.createDistributionSet("test"); diff --git a/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiRootControllerTest.java b/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiRootControllerTest.java index c0619e8e1..c6154b8af 100644 --- a/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiRootControllerTest.java +++ b/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiRootControllerTest.java @@ -27,8 +27,12 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import java.util.Collections; +import java.util.Map; + import org.eclipse.hawkbit.im.authentication.SpPermission; import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent; +import org.eclipse.hawkbit.repository.event.remote.TargetAttributesRequestedEvent; 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; @@ -54,9 +58,11 @@ import org.eclipse.hawkbit.util.IpUtil; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.ResultActions; import ru.yandex.qatools.allure.annotations.Description; import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Step; import ru.yandex.qatools.allure.annotations.Stories; /** @@ -192,6 +198,7 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest { @Expect(type = TargetUpdatedEvent.class, count = 3), @Expect(type = ActionUpdatedEvent.class, count = 1), @Expect(type = DistributionSetCreatedEvent.class, count = 2), @Expect(type = ActionCreatedEvent.class, count = 2), + @Expect(type = TargetAttributesRequestedEvent.class, count = 1), @Expect(type = SoftwareModuleCreatedEvent.class, count = 6) }) public void rootRsNotModified() throws Exception { final String etag = mvc.perform(get("/{tenant}/controller/v1/4711", tenantAware.getCurrentTenant())) @@ -227,11 +234,9 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest { etagWithFirstUpdate)).andDo(MockMvcResultPrinter.print()).andExpect(status().isNotModified()); // now lets finish the update - mvc.perform(post("/{tenant}/controller/v1/4711/deploymentBase/" + updateAction.getId() + "/feedback", - tenantAware.getCurrentTenant()) - .content(JsonBuilder.deploymentActionFeedback(updateAction.getId().toString(), "closed")) - .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) - .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); + sendDeploymentActionFeedback(target, updateAction, + JsonBuilder.deploymentActionFeedback(updateAction.getId().toString(), "closed")) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); // we are again at the original state mvc.perform(get("/{tenant}/controller/v1/4711", tenantAware.getCurrentTenant()).header("If-None-Match", etag)) @@ -262,7 +267,7 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest { @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1), @Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 1) }) public void rootRsPrecommissioned() throws Exception { - final Target target = testdataFactory.createTarget("4711"); + testdataFactory.createTarget("4711"); assertThat(targetManagement.getByControllerID("4711").get().getUpdateStatus()) .isEqualTo(TargetUpdateStatus.UNKNOWN); @@ -335,6 +340,7 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest { @Expect(type = TargetAssignDistributionSetEvent.class, count = 1), @Expect(type = ActionCreatedEvent.class, count = 1), @Expect(type = ActionUpdatedEvent.class, count = 1), @Expect(type = TargetUpdatedEvent.class, count = 2), + @Expect(type = TargetAttributesRequestedEvent.class, count = 1), @Expect(type = SoftwareModuleCreatedEvent.class, count = 3) }) public void tryToFinishAnUpdateProcessAfterItHasBeenFinished() throws Exception { final DistributionSet ds = testdataFactory.createDistributionSet(""); @@ -343,23 +349,89 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest { .next(); final Action savedAction = deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId()) .getContent().get(0); - mvc.perform(post("/{tenant}/controller/v1/911/deploymentBase/" + savedAction.getId() + "/feedback", - tenantAware.getCurrentTenant()) - .content(JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "proceeding")) - .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) - .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); + sendDeploymentActionFeedback(savedTarget, savedAction, + JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "proceeding")) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); - mvc.perform(post("/{tenant}/controller/v1/911/deploymentBase/" + savedAction.getId() + "/feedback", - tenantAware.getCurrentTenant()).content( - JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "closed", "failure")) - .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) - .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); + sendDeploymentActionFeedback(savedTarget, savedAction, + JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "closed", "failure")) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); - mvc.perform(post("/{tenant}/controller/v1/911/deploymentBase/" + savedAction.getId() + "/feedback", - tenantAware.getCurrentTenant()).content( - JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "closed", "success")) - .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) - .andDo(MockMvcResultPrinter.print()).andExpect(status().isGone()); + sendDeploymentActionFeedback(savedTarget, savedAction, + JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "closed", "success")) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isGone()); + } + + @Test + @Description("Controller sends attribute update request after device successfully closed software update.") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1), + @Expect(type = DistributionSetCreatedEvent.class, count = 1), + @Expect(type = TargetAssignDistributionSetEvent.class, count = 2), + @Expect(type = ActionCreatedEvent.class, count = 2), @Expect(type = ActionUpdatedEvent.class, count = 2), + @Expect(type = TargetUpdatedEvent.class, count = 6), @Expect(type = TargetPollEvent.class, count = 4), + @Expect(type = SoftwareModuleCreatedEvent.class, count = 3), + @Expect(type = TargetAttributesRequestedEvent.class, count = 1) }) + public void testAttributeUpdateRequestSendingAfterSuccessfulDeployment() throws Exception { + final DistributionSet ds = testdataFactory.createDistributionSet("1"); + final Target savedTarget = testdataFactory.createTarget("922"); + final Map attributes = Collections.singletonMap("AttributeKey", "AttributeValue"); + assertThatAttributesUpdateIsRequested(savedTarget.getControllerId()); + + mvc.perform(put("/{tenant}/controller/v1/{controllerId}/configData", tenantAware.getCurrentTenant(), + savedTarget.getControllerId()) + .content(JsonBuilder.configData(savedTarget.getControllerId(), attributes, "closed")) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + assertThatAttributesUpdateIsNotRequested(savedTarget.getControllerId()); + + assertAttributesUpdateNotRequestedAfterFailedDeployment(savedTarget, ds); + + assertAttributesUpdateRequestedAfterSuccessfulDeployment(savedTarget, ds); + } + + @Step + private void assertAttributesUpdateNotRequestedAfterFailedDeployment(Target target, final DistributionSet ds) + throws Exception { + target = assignDistributionSet(ds.getId(), target.getControllerId()).getAssignedEntity().iterator().next(); + assignDistributionSet(ds.getId(), target.getControllerId()); + final Action action = deploymentManagement.findActiveActionsByTarget(PAGE, target.getControllerId()) + .getContent().get(0); + sendDeploymentActionFeedback(target, action, + JsonBuilder.deploymentActionFeedback(action.getId().toString(), "closed", "failure", "")) + .andExpect(status().isOk()); + assertThatAttributesUpdateIsNotRequested(target.getControllerId()); + } + + @Step + private void assertAttributesUpdateRequestedAfterSuccessfulDeployment(Target target, final DistributionSet ds) + throws Exception { + target = assignDistributionSet(ds.getId(), target.getControllerId()).getAssignedEntity().iterator().next(); + final Action action = deploymentManagement.findActiveActionsByTarget(PAGE, target.getControllerId()) + .getContent().get(0); + sendDeploymentActionFeedback(target, action, + JsonBuilder.deploymentActionFeedback(action.getId().toString(), "closed")).andExpect(status().isOk()); + assertThatAttributesUpdateIsRequested(target.getControllerId()); + } + + private void assertThatAttributesUpdateIsRequested(final String targetControllerId) + throws Exception { + mvc.perform( + get("/{tenant}/controller/v1/{controllerId}", tenantAware.getCurrentTenant(), targetControllerId) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()).andExpect(jsonPath("$._links.configData.href").isNotEmpty()); + } + + private void assertThatAttributesUpdateIsNotRequested(final String targetControllerId) throws Exception { + mvc.perform(get("/{tenant}/controller/v1/{controllerId}", tenantAware.getCurrentTenant(), targetControllerId) + .accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk()) + .andExpect(jsonPath("$._links.configData").doesNotExist()); + } + + private ResultActions sendDeploymentActionFeedback(final Target target, final Action action, + final String feedback) throws Exception { + return mvc.perform(post("/{tenant}/controller/v1/{controllerId}/deploymentBase/{actionId}/feedback", + tenantAware.getCurrentTenant(), target.getControllerId(), action.getId()).content(feedback) + .contentType(MediaType.APPLICATION_JSON)); } @Test @@ -369,6 +441,7 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest { @Expect(type = TargetAssignDistributionSetEvent.class, count = 1), @Expect(type = ActionCreatedEvent.class, count = 1), @Expect(type = ActionUpdatedEvent.class, count = 2), @Expect(type = TargetUpdatedEvent.class, count = 2), + @Expect(type = TargetAttributesRequestedEvent.class, count = 1), @Expect(type = SoftwareModuleCreatedEvent.class, count = 3) }) public void testActionHistoryCount() throws Exception { final DistributionSet ds = testdataFactory.createDistributionSet(""); @@ -378,26 +451,20 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest { final Action savedAction = deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId()) .getContent().get(0); - mvc.perform(post("/{tenant}/controller/v1/911/deploymentBase/" + savedAction.getId() + "/feedback", - tenantAware.getCurrentTenant()) - .content(JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "scheduled", - TARGET_SCHEDULED_INSTALLATION_MSG)) - .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) - .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); + sendDeploymentActionFeedback(savedTarget, savedAction, + JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "scheduled", + TARGET_SCHEDULED_INSTALLATION_MSG)).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isOk()); - mvc.perform(post("/{tenant}/controller/v1/911/deploymentBase/" + savedAction.getId() + "/feedback", - tenantAware.getCurrentTenant()) - .content(JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "proceeding", - TARGET_PROCEEDING_INSTALLATION_MSG)) - .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) - .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); + sendDeploymentActionFeedback(savedTarget, savedAction, + JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "proceeding", + TARGET_PROCEEDING_INSTALLATION_MSG)).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isOk()); - mvc.perform(post("/{tenant}/controller/v1/911/deploymentBase/" + savedAction.getId() + "/feedback", - tenantAware.getCurrentTenant()) - .content(JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "closed", - "success", TARGET_COMPLETED_INSTALLATION_MSG)) - .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) - .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); + sendDeploymentActionFeedback(savedTarget, savedAction, + JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "closed", "success", + TARGET_COMPLETED_INSTALLATION_MSG)).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isOk()); mvc.perform(get("/{tenant}/controller/v1/911/deploymentBase/" + savedAction.getId() + "?actionHistory=3", tenantAware.getCurrentTenant()).contentType(MediaType.APPLICATION_JSON) @@ -416,6 +483,7 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest { @Expect(type = TargetAssignDistributionSetEvent.class, count = 1), @Expect(type = ActionCreatedEvent.class, count = 1), @Expect(type = ActionUpdatedEvent.class, count = 2), @Expect(type = TargetUpdatedEvent.class, count = 2), + @Expect(type = TargetAttributesRequestedEvent.class, count = 1), @Expect(type = SoftwareModuleCreatedEvent.class, count = 3) }) public void testActionHistoryZeroInput() throws Exception { final DistributionSet ds = testdataFactory.createDistributionSet(""); @@ -425,26 +493,20 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest { final Action savedAction = deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId()) .getContent().get(0); - mvc.perform(post("/{tenant}/controller/v1/911/deploymentBase/" + savedAction.getId() + "/feedback", - tenantAware.getCurrentTenant()) - .content(JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "scheduled", - TARGET_SCHEDULED_INSTALLATION_MSG)) - .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) - .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); + sendDeploymentActionFeedback(savedTarget, savedAction, + JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "scheduled", + TARGET_SCHEDULED_INSTALLATION_MSG)).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isOk()); - mvc.perform(post("/{tenant}/controller/v1/911/deploymentBase/" + savedAction.getId() + "/feedback", - tenantAware.getCurrentTenant()) - .content(JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "proceeding", - TARGET_PROCEEDING_INSTALLATION_MSG)) - .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) - .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); + sendDeploymentActionFeedback(savedTarget, savedAction, + JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "proceeding", + TARGET_PROCEEDING_INSTALLATION_MSG)).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isOk()); - mvc.perform(post("/{tenant}/controller/v1/911/deploymentBase/" + savedAction.getId() + "/feedback", - tenantAware.getCurrentTenant()) - .content(JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "closed", - "success", TARGET_COMPLETED_INSTALLATION_MSG)) - .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) - .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); + sendDeploymentActionFeedback(savedTarget, savedAction, + JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "closed", "success", + TARGET_COMPLETED_INSTALLATION_MSG)).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isOk()); mvc.perform(get("/{tenant}/controller/v1/911/deploymentBase/" + savedAction.getId() + "?actionHistory=-2", tenantAware.getCurrentTenant()).contentType(MediaType.APPLICATION_JSON) @@ -459,6 +521,7 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest { @Expect(type = TargetAssignDistributionSetEvent.class, count = 1), @Expect(type = ActionCreatedEvent.class, count = 1), @Expect(type = ActionUpdatedEvent.class, count = 2), @Expect(type = TargetUpdatedEvent.class, count = 2), + @Expect(type = TargetAttributesRequestedEvent.class, count = 1), @Expect(type = SoftwareModuleCreatedEvent.class, count = 3) }) public void testActionHistoryNegativeInput() throws Exception { final DistributionSet ds = testdataFactory.createDistributionSet(""); @@ -468,26 +531,20 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest { final Action savedAction = deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId()) .getContent().get(0); - mvc.perform(post("/{tenant}/controller/v1/911/deploymentBase/" + savedAction.getId() + "/feedback", - tenantAware.getCurrentTenant()) - .content(JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "scheduled", - TARGET_SCHEDULED_INSTALLATION_MSG)) - .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) - .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); + sendDeploymentActionFeedback(savedTarget, savedAction, + JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "scheduled", + TARGET_SCHEDULED_INSTALLATION_MSG)).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isOk()); - mvc.perform(post("/{tenant}/controller/v1/911/deploymentBase/" + savedAction.getId() + "/feedback", - tenantAware.getCurrentTenant()) - .content(JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "proceeding", - TARGET_PROCEEDING_INSTALLATION_MSG)) - .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) - .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); + sendDeploymentActionFeedback(savedTarget, savedAction, + JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "proceeding", + TARGET_PROCEEDING_INSTALLATION_MSG)).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isOk()); - mvc.perform(post("/{tenant}/controller/v1/911/deploymentBase/" + savedAction.getId() + "/feedback", - tenantAware.getCurrentTenant()) - .content(JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "closed", - "success", TARGET_COMPLETED_INSTALLATION_MSG)) - .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) - .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); + sendDeploymentActionFeedback(savedTarget, savedAction, + JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "closed", "success", + TARGET_COMPLETED_INSTALLATION_MSG)).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isOk()); mvc.perform(get("/{tenant}/controller/v1/911/deploymentBase/" + savedAction.getId() + "?actionHistory=-1", tenantAware.getCurrentTenant()).contentType(MediaType.APPLICATION_JSON)