From c8848ad5f0c9043014a9aa68a8a352555dfa0740 Mon Sep 17 00:00:00 2001 From: Michael Herdt Date: Thu, 17 Sep 2026 13:29:39 +0200 Subject: [PATCH] feat(dmf): Add externalRef if present to messages send by DMF Signed-off-by: Michael Herdt --- .../amqp/AmqpMessageDispatcherService.java | 33 +++++---- .../amqp/AmqpMessageHandlerService.java | 7 +- .../AmqpMessageDispatcherServiceTest.java | 3 + .../AbstractAmqpServiceIntegrationTest.java | 45 +++++++++--- ...ssageDispatcherServiceIntegrationTest.java | 72 ++++++++++++++----- .../dmf/json/model/DmfActionRequest.java | 6 +- .../dmf/json/model/DmfConfirmRequest.java | 10 +-- .../model/DmfDownloadAndUpdateRequest.java | 12 ++-- .../hawkbit/dmf/json/model/DmfTarget.java | 5 +- .../repository/model/ActionProperties.java | 5 +- 10 files changed, 141 insertions(+), 57 deletions(-) diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherService.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherService.java index aad5e3cd54..bf41940709 100644 --- a/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherService.java +++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherService.java @@ -141,9 +141,10 @@ protected void sendUpdateMessageToTarget( } protected DmfDownloadAndUpdateRequest createDownloadAndUpdateRequest( - final Target target, final Long actionId, final Map> softwareModules) { + final Target target, final Long actionId, final String externalRef, + final Map> softwareModules) { return new DmfDownloadAndUpdateRequest( - actionId, asSystem(target::getSecurityToken), convertToAmqpSoftwareModules(target, softwareModules)); + actionId, asSystem(target::getSecurityToken), convertToAmqpSoftwareModules(target, softwareModules), externalRef); } /** @@ -158,9 +159,9 @@ protected void targetCancelAssignmentToDistributionSet(final CancelTargetAssignm final List eventTargets = partitionedParallelExecution(cancelEvent.getActions().keySet(), targetManagement::findByControllerId); eventTargets.forEach(target -> cancelEvent.getActionPropertiesForController(target.getControllerId()) - .map(ActionProperties::getId) - .ifPresent(actionId -> sendCancelMessageToTarget - (cancelEvent.getTenant(), target.getControllerId(), actionId, IpUtil.addressToUri(target.getAddress())))); + .ifPresent(action -> sendCancelMessageToTarget( + cancelEvent.getTenant(), target.getControllerId(), action.getId(), action.getExternalRef(), + IpUtil.addressToUri(target.getAddress())))); } /** @@ -196,20 +197,24 @@ protected void sendPingResponseToDmfReceiver(final Message ping, final String te IpUtil.createAmqpUri(virtualHost, ping.getMessageProperties().getReplyTo())); } - protected void sendCancelMessageToTarget(final String tenant, final String controllerId, final Long actionId, final URI address) { + protected void sendCancelMessageToTarget( + final String tenant, final String controllerId, final Long actionId, final String externalRef, final URI address) { if (!IpUtil.isAmqpUri(address)) { return; } final Message message = getMessageConverter().toMessage( - new DmfActionRequest(actionId), createConnectorMessagePropertiesEvent(tenant, controllerId, EventTopic.CANCEL_DOWNLOAD)); + new DmfActionRequest(actionId, externalRef), + createConnectorMessagePropertiesEvent(tenant, controllerId, EventTopic.CANCEL_DOWNLOAD)); amqpSenderService.sendMessage(message, address); } protected DmfConfirmRequest createConfirmRequest( - final Target target, final Long actionId, final Map> softwareModules) { - return new DmfConfirmRequest(actionId, asSystem(target::getSecurityToken), convertToAmqpSoftwareModules(target, softwareModules)); + final Target target, final Long actionId, final String externalRef, + final Map> softwareModules) { + return new DmfConfirmRequest( + actionId, asSystem(target::getSecurityToken), convertToAmqpSoftwareModules(target, softwareModules), externalRef); } /** @@ -344,9 +349,9 @@ private void sendSingleUpdateMessage( if (action.isWaitingConfirmation()) { // For the moment the confirmation request is the same as download and update request. // It can be modified not to expose all the software modules in the future. - request = createConfirmRequest(target, action.getId(), modules); + request = createConfirmRequest(target, action.getId(), action.getExternalRef(), modules); } else { - request = createDownloadAndUpdateRequest(target, action.getId(), modules); + request = createDownloadAndUpdateRequest(target, action.getId(), action.getExternalRef(), modules); } final Message message = getMessageConverter().toMessage( @@ -450,7 +455,11 @@ private void sendBatchUpdateMessage( final List dmfTargets = targets.stream() .filter(target -> IpUtil.isAmqpUri(IpUtil.addressToUri(target.getAddress()))) // as system - the security token is sent to DMF receiver - .map(t -> new DmfTarget(actions.get(t.getControllerId()).getId(), t.getControllerId(), asSystem(t::getSecurityToken))) + .map(t -> { + final ActionProperties action = actions.get(t.getControllerId()); + return new DmfTarget( + action.getId(), t.getControllerId(), asSystem(t::getSecurityToken), action.getExternalRef()); + }) .toList(); // due to the fact that all targets in a batch use the same set of software modules we don't generate target-specific urls 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 3c39541f69..52acc8dc7b 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 @@ -9,6 +9,7 @@ */ package org.eclipse.hawkbit.amqp; +import jakarta.validation.constraints.NotNull; import java.net.URI; import java.util.ArrayList; import java.util.Collections; @@ -18,9 +19,6 @@ import java.util.UUID; import java.util.function.Function; import java.util.stream.Collectors; - -import jakarta.validation.constraints.NotNull; - import lombok.extern.slf4j.Slf4j; import org.eclipse.hawkbit.audit.AuditLog; import org.eclipse.hawkbit.auth.SpRole; @@ -317,7 +315,8 @@ private void sendUpdateCommandToTarget(final Target target) { final Action action = actionOptional.get(); if (action.isCancelingOrCanceled()) { amqpMessageDispatcherService.sendCancelMessageToTarget( - target.getTenant(), target.getControllerId(), action.getId(), IpUtil.addressToUri(target.getAddress())); + target.getTenant(), target.getControllerId(), action.getId(), action.getExternalRef(), + IpUtil.addressToUri(target.getAddress())); } else { amqpMessageDispatcherService.sendUpdateMessageToTarget( new ActionProperties(action), action.getTarget(), getSoftwareModulesWithMetadata(action.getDistributionSet())); diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherServiceTest.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherServiceTest.java index fcf6d64838..b47fcc71e8 100644 --- a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherServiceTest.java +++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherServiceTest.java @@ -82,6 +82,7 @@ class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTest { private static final URI AMQP_URI = IpUtil.createAmqpUri("vHost", "mytest"); private static final String TEST_TOKEN = "testToken"; private static final String CONTROLLER_ID = "1"; + private static final String EXTERNAL_REF = "external-system-reference"; private AmqpMessageDispatcherService amqpMessageDispatcherService; private RabbitTemplate rabbitTemplate; @@ -243,6 +244,7 @@ void testSendCancelRequest() { when(action.getId()).thenReturn(1L); when(action.getTenant()).thenReturn(TENANT); when(action.getTarget()).thenReturn(testTarget); + when(action.getExternalRef()).thenReturn(EXTERNAL_REF); final CancelTargetAssignmentEvent cancelTargetAssignmentDistributionSetEvent = new CancelTargetAssignmentEvent(action); final CancelTargetAssignmentServiceEvent serviceCancelTargetAssignmentDistributionSetEvent = new CancelTargetAssignmentServiceEvent(cancelTargetAssignmentDistributionSetEvent); @@ -322,6 +324,7 @@ private void assertCancelMessage(final Message sendMessage) { assertThat(actionId.getActionId()) .as("Action ID should be 1") .isOne(); + assertThat(actionId.getExternalRef()).isEqualTo(EXTERNAL_REF); assertThat(sendMessage.getMessageProperties().getHeaders()) .as("The topc in the message should be a CANCEL_DOWNLOAD value") .containsEntry(MessageHeaderKey.TOPIC, EventTopic.CANCEL_DOWNLOAD); diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AbstractAmqpServiceIntegrationTest.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AbstractAmqpServiceIntegrationTest.java index 7b9c57445c..6391936f97 100644 --- a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AbstractAmqpServiceIntegrationTest.java +++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AbstractAmqpServiceIntegrationTest.java @@ -11,6 +11,7 @@ import static org.assertj.core.api.Assertions.assertThat; +import com.cronutils.utils.StringUtils; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collections; @@ -20,8 +21,6 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.Callable; - -import com.cronutils.utils.StringUtils; import org.assertj.core.api.HamcrestCondition; import org.eclipse.hawkbit.amqp.DmfApiConfiguration; import org.eclipse.hawkbit.dmf.amqp.api.AmqpSettings; @@ -143,11 +142,16 @@ protected DistributionSetAssignmentResult registerTargetAndAssignDistributionSet } protected void assertCancelActionMessage(final Long actionId, final String controllerId) { + assertCancelActionMessage(actionId, controllerId, null); + } + + protected void assertCancelActionMessage(final Long actionId, final String controllerId, final String externalRef) { final Message replyMessage = assertReplyMessageHeader(EventTopic.CANCEL_DOWNLOAD, controllerId); final DmfActionRequest actionUpdateStatus = (DmfActionRequest) getDmfClient().getMessageConverter() .fromMessage(replyMessage); assertThat(actionUpdateStatus.getActionId()).isEqualTo(actionId); + assertThat(actionUpdateStatus.getExternalRef()).isEqualTo(externalRef); } protected void assertDeleteMessage(final String target) { @@ -186,19 +190,36 @@ protected void assertPingReplyMessage(final String correlationId) { protected void assertDmfDownloadAndUpdateRequest( final DmfDownloadAndUpdateRequest request, final Set softwareModules, final String controllerId) { + assertDmfDownloadAndUpdateRequest(request, softwareModules, controllerId, null); + } + + protected void assertDmfDownloadAndUpdateRequest( + final DmfDownloadAndUpdateRequest request, final Set softwareModules, + final String controllerId, final String externalRef) { assertSoftwareModules(softwareModules, request.getSoftwareModules()); final Target updatedTarget = waitUntilIsPresent(() -> targetManagement.findByControllerId(controllerId)); assertThat(updatedTarget).isNotNull(); assertThat(updatedTarget.getSecurityToken()).isEqualTo(request.getTargetSecurityToken()); + assertThat(request.getExternalRef()).isEqualTo(externalRef); } protected void assertDownloadAndInstallMessage(final Set softwareModules, final String controllerId) { - assertAssignmentMessage(softwareModules, controllerId, EventTopic.DOWNLOAD_AND_INSTALL); + assertDownloadAndInstallMessage(softwareModules, controllerId, null); + } + + protected void assertDownloadAndInstallMessage(final Set softwareModules, + final String controllerId, final String externalRef) { + assertAssignmentMessage(softwareModules, controllerId, EventTopic.DOWNLOAD_AND_INSTALL, externalRef); } protected void assertDownloadMessage(final Set dsModules, final String controllerId) { - assertAssignmentMessage(dsModules, controllerId, EventTopic.DOWNLOAD); + assertDownloadMessage(dsModules, controllerId, null); + } + + protected void assertDownloadMessage(final Set dsModules, + final String controllerId, final String externalRef) { + assertAssignmentMessage(dsModules, controllerId, EventTopic.DOWNLOAD, externalRef); } protected void createAndSendThingCreated(final String controllerId) { @@ -401,6 +422,11 @@ protected void assertSoftwareModules(final Set expectedSoftwareM } protected void assertConfirmMessage(final Set dsModules, final String controllerId) { + assertConfirmMessage(dsModules, controllerId, null); + } + + protected void assertConfirmMessage(final Set dsModules, + final String controllerId, final String externalRef) { final Message replyMessage = assertReplyMessageHeader(EventTopic.CONFIRM, controllerId); assertAllTargetsCount(1); @@ -408,24 +434,27 @@ protected void assertConfirmMessage(final Set dsModules, final S final DmfConfirmRequest confirmRequest = (DmfConfirmRequest) getDmfClient() .getMessageConverter().fromMessage(replyMessage); - assertConfirmRequest(confirmRequest, dsModules, controllerId); + assertConfirmRequest(confirmRequest, dsModules, controllerId, externalRef); } - protected void assertConfirmRequest(final DmfConfirmRequest request, final Set softwareModules, final String controllerId) { + protected void assertConfirmRequest(final DmfConfirmRequest request, final Set softwareModules, + final String controllerId, final String externalRef) { assertSoftwareModules(softwareModules, request.getSoftwareModules()); final Target updatedTarget = waitUntilIsPresent(() -> targetManagement.findByControllerId(controllerId)); assertThat(updatedTarget).isNotNull(); assertThat(updatedTarget.getSecurityToken()).isEqualTo(request.getTargetSecurityToken()); + assertThat(request.getExternalRef()).isEqualTo(externalRef); } - private void assertAssignmentMessage(final Set dsModules, final String controllerId, final EventTopic topic) { + private void assertAssignmentMessage(final Set dsModules, final String controllerId, + final EventTopic topic, final String externalRef) { final Message replyMessage = assertReplyMessageHeader(topic, controllerId); assertAllTargetsCount(1); final DmfDownloadAndUpdateRequest downloadAndUpdateRequest = (DmfDownloadAndUpdateRequest) getDmfClient() .getMessageConverter().fromMessage(replyMessage); - assertDmfDownloadAndUpdateRequest(downloadAndUpdateRequest, dsModules, controllerId); + assertDmfDownloadAndUpdateRequest(downloadAndUpdateRequest, dsModules, controllerId, externalRef); } private void registerAndAssertTargetWithExistingTenant( 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 3a5bb98746..ddff6c4e12 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 @@ -28,6 +28,7 @@ import org.eclipse.hawkbit.dmf.amqp.api.EventTopic; import org.eclipse.hawkbit.dmf.json.model.DmfActionStatus; import org.eclipse.hawkbit.dmf.json.model.DmfBatchDownloadAndUpdateRequest; +import org.eclipse.hawkbit.dmf.json.model.DmfDownloadAndUpdateRequest; import org.eclipse.hawkbit.dmf.json.model.DmfTarget; import org.eclipse.hawkbit.repository.event.remote.CancelTargetAssignmentEvent; import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent; @@ -45,6 +46,7 @@ import org.eclipse.hawkbit.repository.event.remote.entity.TenantConfigurationCreatedEvent; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; import org.eclipse.hawkbit.repository.model.Action.ActionType; +import org.eclipse.hawkbit.repository.model.DeploymentRequest; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult; import org.eclipse.hawkbit.repository.model.RepositoryModelConstants; @@ -84,10 +86,15 @@ class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpServiceInt @Expect(type = TargetPollEvent.class, count = 1) }) void sendDownloadAndInstallStatus() { final String controllerId = TARGET_PREFIX + "sendDownloadAndInstallStatus"; - registerTargetAndAssignDistributionSet(controllerId); + registerAndAssertTargetWithExistingTenant(controllerId); + final DistributionSet distributionSet = testdataFactory.createDistributionSet(); + testdataFactory.addSoftwareModuleMetadata(distributionSet); + final String externalRef = "download-and-install-external-ref"; + final DistributionSetAssignmentResult assignmentResult = assignDistributionSets(List.of( + DeploymentRequest.builder(controllerId, distributionSet.getId()).externalRef(externalRef).build())).get(0); waitUntilTargetHasStatus(controllerId, TargetUpdateStatus.PENDING); - assertDownloadAndInstallMessage(getDistributionSet().getModules(), controllerId); + assertDownloadAndInstallMessage(assignmentResult.getDistributionSet().getModules(), controllerId, externalRef); } /** @@ -122,15 +129,15 @@ void sendDownloadStatusBeforeMaintenanceWindowStartTime() { */ @Test @ExpectEvents({ - @Expect(type = TargetCreatedEvent.class, count = 1), - @Expect(type = TargetAssignDistributionSetEvent.class, count = 1), - @Expect(type = ActionCreatedEvent.class, count = 1), - @Expect(type = DistributionSetCreatedEvent.class, count = 1), - @Expect(type = SoftwareModuleCreatedEvent.class, count = 3), - @Expect(type = DistributionSetUpdatedEvent.class, count = 1), // implicit lock - @Expect(type = SoftwareModuleUpdatedEvent.class, count = 9), // implicit lock - @Expect(type = TargetUpdatedEvent.class, count = 1), - @Expect(type = TargetPollEvent.class, count = 1) }) + @Expect(type = TargetCreatedEvent.class, count = 1), + @Expect(type = TargetAssignDistributionSetEvent.class, count = 1), + @Expect(type = ActionCreatedEvent.class, count = 1), + @Expect(type = DistributionSetCreatedEvent.class, count = 1), + @Expect(type = SoftwareModuleCreatedEvent.class, count = 3), + @Expect(type = DistributionSetUpdatedEvent.class, count = 1), // implicit lock + @Expect(type = SoftwareModuleUpdatedEvent.class, count = 9), // implicit lock + @Expect(type = TargetUpdatedEvent.class, count = 1), + @Expect(type = TargetPollEvent.class, count = 1) }) void sendDownloadAndInstallStatusMessageDuringMaintenanceWindow() { final String controllerId = TARGET_PREFIX + "sendDAndIStatusMessageDuringWindow"; @@ -138,7 +145,7 @@ void sendDownloadAndInstallStatusMessageDuringMaintenanceWindow() { final DistributionSet distributionSet = testdataFactory.createDistributionSet(); testdataFactory.addSoftwareModuleMetadata(distributionSet); assignDistributionSetWithMaintenanceWindow(distributionSet.getId(), controllerId, getTestSchedule(-5), - getTestDuration(10), getTestTimeZone()); + getTestDuration(10), getTestTimeZone()); waitUntilTargetHasStatus(controllerId, TargetUpdateStatus.PENDING); assertDownloadAndInstallMessage(distributionSet.getModules(), controllerId); @@ -210,12 +217,20 @@ void assignDistributionSetMultipleTimes() { @Expect(type = TargetPollEvent.class, count = 2) }) void sendCancelStatus() { final String controllerId = TARGET_PREFIX + "sendCancelStatus"; + final String externalRef = "cancel-external-ref"; - final Long actionId = registerTargetAndCancelActionId(controllerId); + registerAndAssertTargetWithExistingTenant(controllerId); + final DistributionSet distributionSet = testdataFactory.createDistributionSet(); + testdataFactory.addSoftwareModuleMetadata(distributionSet); + final DistributionSetAssignmentResult assignmentResult = assignDistributionSets(List.of( + DeploymentRequest.builder(controllerId, distributionSet.getId()).externalRef(externalRef).build())).get(0); + final Long actionId = getFirstAssignedActionId(assignmentResult); + deploymentManagement.cancelAction(actionId); + assertCancelActionMessage(actionId, controllerId, externalRef); createAndSendThingCreated(controllerId); waitUntilTargetHasStatus(controllerId, TargetUpdateStatus.PENDING); - assertCancelActionMessage(actionId, controllerId); + assertCancelActionMessage(actionId, controllerId, externalRef); } /** @@ -281,7 +296,13 @@ void attributeRequestAfterSuccessfulUpdate() { @Expect(type = TargetPollEvent.class, count = 1) }) void downloadOnlyAssignmentSendsDownloadMessageTopic() { final String controllerId = TARGET_PREFIX + "registerTargets_1"; - final DistributionSet distributionSet = createTargetAndDistributionSetAndAssign(controllerId, DOWNLOAD_ONLY); + registerAndAssertTargetWithExistingTenant(controllerId); + final DistributionSet distributionSet = testdataFactory.createDistributionSet(); + testdataFactory.addSoftwareModuleMetadata(distributionSet); + final String externalRef = "download-only-external-ref"; + assignDistributionSets(List.of( + DeploymentRequest.builder(controllerId, distributionSet.getId()).actionType(DOWNLOAD_ONLY) + .externalRef(externalRef).build())); final Message message = assertReplyMessageHeader(EventTopic.DOWNLOAD, controllerId); Mockito.verifyNoInteractions(getDeadletterListener()); @@ -293,6 +314,10 @@ void downloadOnlyAssignmentSendsDownloadMessageTopic() { .containsEntry("type", EVENT.toString()) .containsEntry("topic", DOWNLOAD.toString()); + final DmfDownloadAndUpdateRequest request = + (DmfDownloadAndUpdateRequest) getDmfClient().getMessageConverter().fromMessage(message); + assertThat(request.getExternalRef()).isEqualTo(externalRef); + final Optional target = controllerManagement.findByControllerId(controllerId); assertThat(target).isPresent(); @@ -334,6 +359,7 @@ protected void assertDmfBatchDownloadAndUpdateRequest(final DmfBatchDownloadAndU requestTargets.forEach(requestTarget -> { assertThat(requestTarget).isNotNull(); assertThat(tokens.contains(requestTarget.getTargetSecurityToken())); + assertThat(requestTarget.getExternalRef()).isEqualTo("batch-external-ref-" + requestTarget.getControllerId()); }); } @@ -369,7 +395,11 @@ void assertBatchAssignmentsMessagePayload(final EventTopic topic) { final DistributionSet ds = testdataFactory.createDistributionSet(); testdataFactory.addSoftwareModuleMetadata(ds); - assignDistributionSet(ds.getId(), targets, topic == BATCH_DOWNLOAD ? DOWNLOAD_ONLY : FORCED); + assignDistributionSets(targets.stream() + .map(controllerId -> DeploymentRequest.builder(controllerId, ds.getId()) + .actionType(topic == BATCH_DOWNLOAD ? DOWNLOAD_ONLY : FORCED) + .externalRef("batch-external-ref-" + controllerId).build()) + .toList()); waitUntilEventMessagesAreDispatchedToTarget(topic); @@ -403,11 +433,17 @@ void assertBatchAssignmentsMessagePayload(final EventTopic topic) { @Expect(type = TenantConfigurationCreatedEvent.class, count = 1) }) void sendConfirmStatus() { final String controllerId = TARGET_PREFIX + "sendConfirmStatus"; + final String externalRef = "confirm-external-ref"; enableConfirmationFlow(); - registerTargetAndAssignDistributionSet(controllerId); + registerAndAssertTargetWithExistingTenant(controllerId); + final DistributionSet distributionSet = testdataFactory.createDistributionSet(); + testdataFactory.addSoftwareModuleMetadata(distributionSet); + assignDistributionSets(List.of( + DeploymentRequest.builder(controllerId, distributionSet.getId()).confirmationRequired(true) + .externalRef(externalRef).build())); waitUntilTargetHasStatus(controllerId, TargetUpdateStatus.PENDING); - assertConfirmMessage(getDistributionSet().getModules(), controllerId); + assertConfirmMessage(distributionSet.getModules(), controllerId, externalRef); assertEventMessageNotPresent(EventTopic.DOWNLOAD_AND_INSTALL); } diff --git a/hawkbit-dmf/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/json/model/DmfActionRequest.java b/hawkbit-dmf/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/json/model/DmfActionRequest.java index 518abb5471..7a8f9f752a 100644 --- a/hawkbit-dmf/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/json/model/DmfActionRequest.java +++ b/hawkbit-dmf/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/json/model/DmfActionRequest.java @@ -25,9 +25,13 @@ public class DmfActionRequest { private final Long actionId; + private final String externalRef; @JsonCreator - public DmfActionRequest(@JsonProperty("actionId") final Long actionId) { + public DmfActionRequest( + @JsonProperty("actionId") final Long actionId, + @JsonProperty("externalRef") final String externalRef) { this.actionId = actionId; + this.externalRef = externalRef; } } \ No newline at end of file diff --git a/hawkbit-dmf/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/json/model/DmfConfirmRequest.java b/hawkbit-dmf/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/json/model/DmfConfirmRequest.java index 5e0785d13d..e3e5518b72 100644 --- a/hawkbit-dmf/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/json/model/DmfConfirmRequest.java +++ b/hawkbit-dmf/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/json/model/DmfConfirmRequest.java @@ -9,14 +9,13 @@ */ package org.eclipse.hawkbit.dmf.json.model; -import java.util.Collections; -import java.util.List; - import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Collections; +import java.util.List; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.ToString; @@ -38,8 +37,9 @@ public class DmfConfirmRequest extends DmfActionRequest { public DmfConfirmRequest( @JsonProperty("actionId") final Long actionId, @JsonProperty("targetSecurityToken") final String targetSecurityToken, - @JsonProperty("softwareModules") final List softwareModules) { - super(actionId); + @JsonProperty("softwareModules") final List softwareModules, + @JsonProperty("externalRef") final String externalRef) { + super(actionId, externalRef); this.targetSecurityToken = targetSecurityToken; this.softwareModules = softwareModules; } diff --git a/hawkbit-dmf/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/json/model/DmfDownloadAndUpdateRequest.java b/hawkbit-dmf/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/json/model/DmfDownloadAndUpdateRequest.java index be08b5ce3a..193b53d3b0 100644 --- a/hawkbit-dmf/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/json/model/DmfDownloadAndUpdateRequest.java +++ b/hawkbit-dmf/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/json/model/DmfDownloadAndUpdateRequest.java @@ -9,14 +9,13 @@ */ package org.eclipse.hawkbit.dmf.json.model; -import java.util.Collections; -import java.util.List; - import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Collections; +import java.util.List; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.ToString; @@ -38,9 +37,10 @@ public class DmfDownloadAndUpdateRequest extends DmfActionRequest { public DmfDownloadAndUpdateRequest( @JsonProperty("actionId") final Long actionId, @JsonProperty("targetSecurityToken") final String targetSecurityToken, - @JsonProperty("softwareModules") final List softwareModules) { - super(actionId); + @JsonProperty("softwareModules") final List softwareModules, + @JsonProperty("externalRef") final String externalRef) { + super(actionId, externalRef); this.targetSecurityToken = targetSecurityToken; this.softwareModules = softwareModules == null ? Collections.emptyList() : Collections.unmodifiableList(softwareModules); } -} \ No newline at end of file +} diff --git a/hawkbit-dmf/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/json/model/DmfTarget.java b/hawkbit-dmf/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/json/model/DmfTarget.java index d97f4b05ad..08b72684c6 100644 --- a/hawkbit-dmf/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/json/model/DmfTarget.java +++ b/hawkbit-dmf/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/json/model/DmfTarget.java @@ -29,14 +29,17 @@ public class DmfTarget { private final String controllerId; @ToString.Exclude private final String targetSecurityToken; + private final String externalRef; @JsonCreator public DmfTarget( @JsonProperty("actionId") final Long actionId, @JsonProperty("controllerId") final String controllerId, - @JsonProperty("targetSecurityToken") final String targetSecurityToken) { + @JsonProperty("targetSecurityToken") final String targetSecurityToken, + @JsonProperty("externalRef") final String externalRef) { this.actionId = actionId; this.controllerId = controllerId; this.targetSecurityToken = targetSecurityToken; + this.externalRef = externalRef; } } \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/ActionProperties.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/ActionProperties.java index 3f3f9df066..7c336950d7 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/ActionProperties.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/ActionProperties.java @@ -9,10 +9,9 @@ */ package org.eclipse.hawkbit.repository.model; +import com.fasterxml.jackson.annotation.JsonIgnore; import java.io.Serial; import java.io.Serializable; - -import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; import lombok.NoArgsConstructor; @@ -32,6 +31,7 @@ public class ActionProperties implements Serializable { private boolean maintenanceWindowAvailable; private Action.Status status; + private String externalRef; /** * Constructor @@ -44,6 +44,7 @@ public ActionProperties(final Action action) { this.tenant = action.getTenant(); this.maintenanceWindowAvailable = action.isMaintenanceWindowAvailable(); this.status = action.getStatus(); + this.externalRef = action.getExternalRef(); } @JsonIgnore