Multi-Asssignments feature removal (#2893)

* Multi-Asssignments feature removal

Signed-off-by: strailov <Stanislav.Trailov@bosch.io>

* fix some sonar findings

Signed-off-by: strailov <Stanislav.Trailov@bosch.io>

* fixes after review

Signed-off-by: strailov <Stanislav.Trailov@bosch.io>

---------

Signed-off-by: strailov <Stanislav.Trailov@bosch.io>
This commit is contained in:
Stanislav Trailov
2026-02-04 16:59:09 +02:00
committed by GitHub
parent 2bf443661d
commit c33156b134
43 changed files with 123 additions and 1610 deletions

View File

@@ -10,7 +10,6 @@
package org.eclipse.hawkbit.amqp;
import static org.eclipse.hawkbit.context.AccessContext.asSystem;
import static org.eclipse.hawkbit.repository.RepositoryConstants.MAX_ACTION_COUNT;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.BATCH_ASSIGNMENTS_ENABLED;
import java.net.URI;
@@ -43,24 +42,18 @@ import org.eclipse.hawkbit.dmf.json.model.DmfBatchDownloadAndUpdateRequest;
import org.eclipse.hawkbit.dmf.json.model.DmfConfirmRequest;
import org.eclipse.hawkbit.dmf.json.model.DmfDownloadAndUpdateRequest;
import org.eclipse.hawkbit.dmf.json.model.DmfMetadata;
import org.eclipse.hawkbit.dmf.json.model.DmfMultiActionRequest;
import org.eclipse.hawkbit.dmf.json.model.DmfSoftwareModule;
import org.eclipse.hawkbit.dmf.json.model.DmfTarget;
import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.RepositoryProperties;
import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.event.remote.CancelTargetAssignmentEvent;
import org.eclipse.hawkbit.repository.event.remote.MultiActionAssignEvent;
import org.eclipse.hawkbit.repository.event.remote.MultiActionCancelEvent;
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.service.CancelTargetAssignmentServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.MultiActionAssignServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.MultiActionCancelServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.TargetAssignDistributionSetServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.TargetAttributesRequestedServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.TargetDeletedServiceEvent;
@@ -99,7 +92,6 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
private final SoftwareModuleManagement<? extends SoftwareModule> softwareModuleManagement;
private final DistributionSetManagement<? extends DistributionSet> distributionSetManagement;
private final DeploymentManagement deploymentManagement;
private final RepositoryProperties repositoryProperties;
@SuppressWarnings("java:S107")
protected AmqpMessageDispatcherService(
@@ -109,8 +101,7 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
final TargetManagement<? extends Target> targetManagement,
final SoftwareModuleManagement<? extends SoftwareModule> softwareModuleManagement,
final DistributionSetManagement<? extends DistributionSet> distributionSetManagement,
final DeploymentManagement deploymentManagement,
final RepositoryProperties repositoryProperties) {
final DeploymentManagement deploymentManagement) {
super(rabbitTemplate);
this.artifactUrlHandler = artifactUrlHandler;
this.amqpSenderService = amqpSenderService;
@@ -119,7 +110,6 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
this.softwareModuleManagement = softwareModuleManagement;
this.distributionSetManagement = distributionSetManagement;
this.deploymentManagement = deploymentManagement;
this.repositoryProperties = repositoryProperties;
}
public boolean isBatchAssignmentsEnabled() {
@@ -142,30 +132,6 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
}
}
/**
* Listener for Multi-Action events.
*
* @param multiActionAssignServiceEvent the Multi-Action event to be processed
*/
@EventListener(classes = MultiActionAssignServiceEvent.class)
protected void onMultiActionAssign(final MultiActionAssignServiceEvent multiActionAssignServiceEvent) {
final MultiActionAssignEvent multiActionAssignEvent = multiActionAssignServiceEvent.getRemoteEvent();
log.debug("MultiActionAssignEvent received for {}", multiActionAssignEvent.getControllerIds());
sendMultiActionRequestMessages(multiActionAssignEvent.getControllerIds());
}
/**
* Listener for Multi-Action events.
*
* @param multiActionCancelServiceEvent the Multi-Action event to be processed
*/
@EventListener(classes = MultiActionCancelServiceEvent.class)
protected void onMultiActionCancel(final MultiActionCancelServiceEvent multiActionCancelServiceEvent) {
final MultiActionCancelEvent multiActionCancelEvent = multiActionCancelServiceEvent.getRemoteEvent();
log.debug("MultiActionCancelEvent received for {}", multiActionCancelEvent.getControllerIds());
sendMultiActionRequestMessages(multiActionCancelEvent.getControllerIds());
}
protected void sendUpdateMessageToTarget(
final ActionProperties actionsProps, final Target target,
final Map<SoftwareModule, Map<String, String>> softwareModules) {
@@ -247,39 +213,6 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
return new DmfConfirmRequest(actionId, asSystem(target::getSecurityToken), convertToAmqpSoftwareModules(target, softwareModules));
}
void sendMultiActionRequestToTarget(
final Target target, final List<Action> actions,
final Function<SoftwareModule, Map<String, String>> getSoftwareModuleMetaData) {
final URI targetAddress = IpUtil.addressToUri(target.getAddress());
if (!IpUtil.isAmqpUri(targetAddress) || CollectionUtils.isEmpty(actions)) {
return;
}
final DmfMultiActionRequest multiActionRequest = new DmfMultiActionRequest(
actions.stream()
.map(action -> {
final DmfActionRequest actionRequest = createDmfActionRequest(
target, action,
action.getDistributionSet().getModules().stream()
.collect(Collectors.toMap(Function.identity(), module -> {
final Map<String, String> softwareModuleMetadata = getSoftwareModuleMetaData.apply(module);
return softwareModuleMetadata == null ? Collections.emptyMap() : softwareModuleMetadata;
})));
final int weight = getWeightConsideringDefault(action);
return new DmfMultiActionRequest.DmfMultiActionElement(getEventTypeForAction(action), actionRequest, weight);
})
.toList());
final Message message = getMessageConverter().toMessage(
multiActionRequest,
createConnectorMessagePropertiesEvent(target.getTenant(), target.getControllerId(), EventTopic.MULTI_ACTION));
amqpSenderService.sendMessage(message, targetAddress);
}
private int getWeightConsideringDefault(final Action action) {
return action.getWeight().orElse(repositoryProperties.getActionWeightIfAbsent());
}
/**
* Method to get the type of event depending on whether the action is a DOWNLOAD_ONLY action or if it has a valid maintenance window
* available or not based on defined maintenance schedule. In case of no maintenance schedule or if there is a valid window available,
@@ -297,19 +230,6 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
: EventTopic.DOWNLOAD_AND_INSTALL;
}
/**
* Determines the {@link EventTopic} for the given {@link Action}, depending on its action type.
*
* @param action to obtain the corresponding {@link EventTopic} for
* @return the {@link EventTopic} for this action
*/
private static EventTopic getEventTypeForAction(final Action action) {
if (action.isCancelingOrCanceled()) {
return EventTopic.CANCEL_DOWNLOAD;
}
return getEventTypeForTarget(new ActionProperties(action));
}
private static <T, R> List<R> partitionedParallelExecution(
final Collection<T> controllerIds, final Function<Collection<T>, List<R>> loadingFunction) {
// Ensure not exceeding the max value of MAX_PROCESSING_SIZE
@@ -412,40 +332,6 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
}
}
private void sendMultiActionRequestMessages(final List<String> controllerIds) {
final Map<String, List<Action>> controllerIdToActions = controllerIds.stream()
.collect(Collectors.toMap(
Function.identity(),
controllerId -> deploymentManagement.findActiveActionsWithHighestWeight(controllerId, MAX_ACTION_COUNT)));
// gets all software modules for all action at once
final Set<Long> allSmIds = controllerIdToActions.values().stream()
.flatMap(actions -> actions.stream()
.map(Action::getDistributionSet)
.flatMap(ds -> ds.getModules().stream())
.map(SoftwareModule::getId))
.collect(Collectors.toSet());
final Map<Long, ? extends Map<String, String>> getSoftwareModuleMetadata =
allSmIds.isEmpty()
? Collections.emptyMap()
: softwareModuleManagement.findMetaDataBySoftwareModuleIdsAndTargetVisible(allSmIds);
targetManagement.findByControllerId(controllerIds).forEach(target ->
sendMultiActionRequestToTarget(
target, controllerIdToActions.get(target.getControllerId()), module -> getSoftwareModuleMetadata.get(module.getId())));
}
private DmfActionRequest createDmfActionRequest(
final Target target, final Action action,
final Map<SoftwareModule, Map<String, String>> softwareModules) {
if (action.isCancelingOrCanceled()) {
return new DmfActionRequest(action.getId());
} else if (action.isWaitingConfirmation()) {
return createConfirmRequest(target, action.getId(), softwareModules);
}
return createDownloadAndUpdateRequest(target, action.getId(), softwareModules);
}
private void sendSingleUpdateMessage(
final ActionProperties action, final Target target, final Map<SoftwareModule, Map<String, String>> modules) {
final String tenant = action.getTenant();

View File

@@ -9,15 +9,12 @@
*/
package org.eclipse.hawkbit.amqp;
import static org.eclipse.hawkbit.repository.RepositoryConstants.MAX_ACTION_COUNT;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -42,7 +39,6 @@ import org.eclipse.hawkbit.repository.RepositoryConstants;
import org.eclipse.hawkbit.repository.UpdateMode;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.helper.TenantConfigHelper;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionStatusCreate;
import org.eclipse.hawkbit.repository.model.Action.ActionStatusCreate.ActionStatusCreateBuilder;
@@ -52,7 +48,6 @@ import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.tenancy.TenantAwareAuthenticationDetails;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties;
import org.eclipse.hawkbit.utils.IpUtil;
import org.springframework.amqp.AmqpRejectAndDontRequeueException;
import org.springframework.amqp.core.Message;
@@ -315,29 +310,7 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
}
private void sendUpdateCommandToTarget(final Target target) {
if (TenantConfigHelper.getAsSystem(TenantConfigurationProperties.TenantConfigurationKey.MULTI_ASSIGNMENTS_ENABLED, Boolean.class)) {
sendCurrentActionsAsMultiActionToTarget(target);
} else {
sendOldestActionToTarget(target);
}
}
private void sendCurrentActionsAsMultiActionToTarget(final Target target) {
final List<Action> actions = controllerManagement.findActiveActionsWithHighestWeight(target.getControllerId(), MAX_ACTION_COUNT);
// gets all software modules for all action at once
final Set<Long> allSmIds = actions.stream()
.map(Action::getDistributionSet)
.flatMap(ds -> ds.getModules().stream())
.map(SoftwareModule::getId)
.collect(Collectors.toSet());
final Map<Long, Map<String, String>> getSoftwareModuleMetadata =
allSmIds.isEmpty() ? Collections.emptyMap() : controllerManagement.findTargetVisibleMetaDataBySoftwareModuleId(allSmIds);
amqpMessageDispatcherService.sendMultiActionRequestToTarget(target, actions, module -> getSoftwareModuleMetadata.get(module.getId()));
}
private void sendOldestActionToTarget(final Target target) {
// send oldest action to Target
final Optional<Action> actionOptional = controllerManagement.findActiveActionWithHighestWeight(target.getControllerId());
if (actionOptional.isEmpty()) {
return;

View File

@@ -265,11 +265,10 @@ public class DmfApiConfiguration {
final SystemManagement systemManagement,
final TargetManagement<? extends Target> targetManagement,
final DistributionSetManagement<? extends DistributionSet> distributionSetManagement,
final SoftwareModuleManagement<? extends SoftwareModule> softwareModuleManagement, final DeploymentManagement deploymentManagement,
final RepositoryProperties repositoryProperties) {
final SoftwareModuleManagement<? extends SoftwareModule> softwareModuleManagement, final DeploymentManagement deploymentManagement) {
return new AmqpMessageDispatcherService(rabbitTemplate, amqpSenderService, artifactUrlHandler,
systemManagement, targetManagement, softwareModuleManagement, distributionSetManagement,
deploymentManagement, repositoryProperties);
deploymentManagement);
}
private static Map<String, Object> getTTLMaxArgsAuthenticationQueue() {

View File

@@ -78,9 +78,6 @@ import org.springframework.test.context.TestPropertySource;
"spring.main.allow-bean-definition-overriding=true" })
class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTest {
@Autowired
protected RepositoryProperties repositoryProperties;
private static final String TENANT = "DEFAULT";
private static final Long TENANT_ID = 4711L;
@@ -117,7 +114,7 @@ class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTest {
amqpMessageDispatcherService = new AmqpMessageDispatcherService(rabbitTemplate, senderService,
artifactUrlHandlerMock, systemManagement, targetManagement,
softwareModuleManagement, distributionSetManagement, deploymentManagement, repositoryProperties);
softwareModuleManagement, distributionSetManagement, deploymentManagement);
}

View File

@@ -11,7 +11,6 @@ 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.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
@@ -112,10 +111,6 @@ class AmqpMessageHandlerServiceTest {
TenantConfigHelper.setTenantConfigurationManagement(tenantConfigurationManagement);
messageConverter = new Jackson2JsonMessageConverter();
lenient().when(rabbitTemplate.getMessageConverter()).thenReturn(messageConverter);
final TenantConfigurationValue multiAssignmentConfig = TenantConfigurationValue.builder().value(Boolean.FALSE)
.global(Boolean.FALSE).build();
lenient().when(tenantConfigurationManagement.getConfigurationValue(MULTI_ASSIGNMENTS_ENABLED, Boolean.class))
.thenReturn(multiAssignmentConfig);
amqpMessageHandlerService = new AmqpMessageHandlerService(
rabbitTemplate, amqpMessageDispatcherServiceMock, controllerManagementMock, confirmationManagementMock);

View File

@@ -10,7 +10,6 @@
package org.eclipse.hawkbit.integration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.eclipse.hawkbit.dmf.amqp.api.EventTopic.BATCH_DOWNLOAD;
import static org.eclipse.hawkbit.dmf.amqp.api.EventTopic.BATCH_DOWNLOAD_AND_INSTALL;
import static org.eclipse.hawkbit.dmf.amqp.api.EventTopic.DOWNLOAD;
@@ -18,31 +17,19 @@ import static org.eclipse.hawkbit.dmf.amqp.api.MessageType.EVENT;
import static org.eclipse.hawkbit.repository.model.Action.ActionType.DOWNLOAD_ONLY;
import static org.eclipse.hawkbit.repository.model.Action.ActionType.FORCED;
import java.util.AbstractMap.SimpleEntry;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.stream.Collectors;
import org.eclipse.hawkbit.dmf.amqp.api.EventTopic;
import org.eclipse.hawkbit.dmf.amqp.api.MessageHeaderKey;
import org.eclipse.hawkbit.dmf.json.model.DmfActionRequest;
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.DmfMultiActionRequest;
import org.eclipse.hawkbit.dmf.json.model.DmfMultiActionRequest.DmfMultiActionElement;
import org.eclipse.hawkbit.dmf.json.model.DmfSoftwareModule;
import org.eclipse.hawkbit.dmf.json.model.DmfTarget;
import org.eclipse.hawkbit.repository.event.remote.CancelTargetAssignmentEvent;
import org.eclipse.hawkbit.repository.event.remote.MultiActionAssignEvent;
import org.eclipse.hawkbit.repository.event.remote.MultiActionCancelEvent;
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
import org.eclipse.hawkbit.repository.event.remote.TargetAttributesRequestedEvent;
import org.eclipse.hawkbit.repository.event.remote.TargetDeletedEvent;
@@ -51,23 +38,16 @@ import org.eclipse.hawkbit.repository.event.remote.entity.ActionCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.ActionUpdatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetUpdatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.RolloutCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.RolloutGroupCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.RolloutGroupUpdatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.RolloutUpdatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleUpdatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.TenantConfigurationCreatedEvent;
import org.eclipse.hawkbit.repository.exception.TenantConfigurationValueChangeNotAllowedException;
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;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
@@ -212,294 +192,6 @@ class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpServiceInt
assertDownloadAndInstallMessage(distributionSet2.getModules(), controllerId);
}
/**
* If multi assignment is enabled multi-action messages are sent.
*/
@Test
@ExpectEvents({
@Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = MultiActionAssignEvent.class, count = 2),
@Expect(type = TargetAssignDistributionSetEvent.class, count = 0),
@Expect(type = CancelTargetAssignmentEvent.class, count = 0),
@Expect(type = ActionCreatedEvent.class, count = 2),
@Expect(type = ActionUpdatedEvent.class, count = 0),
@Expect(type = DistributionSetCreatedEvent.class, count = 2),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 6),
@Expect(type = DistributionSetUpdatedEvent.class, count = 2), // implicit lock
@Expect(type = SoftwareModuleUpdatedEvent.class, count = 6), // implicit lock
@Expect(type = TargetUpdatedEvent.class, count = 2),
@Expect(type = TargetPollEvent.class, count = 1),
@Expect(type = TenantConfigurationCreatedEvent.class, count = 1) })
void assignMultipleDsInMultiAssignMode() {
enableMultiAssignments();
final String controllerId = TARGET_PREFIX + "assignMultipleDsInMultiAssignMode";
registerAndAssertTargetWithExistingTenant(controllerId);
final Long actionId1 = assignNewDsToTarget(controllerId, 450);
final SimpleEntry<Long, EventTopic> action1Install = new SimpleEntry<>(actionId1, EventTopic.DOWNLOAD_AND_INSTALL);
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION);
assertLatestMultiActionMessage(controllerId, Collections.singletonList(action1Install));
final Long actionId2 = assignNewDsToTarget(controllerId, 111);
final SimpleEntry<Long, EventTopic> action2Install = new SimpleEntry<>(actionId2, EventTopic.DOWNLOAD_AND_INSTALL);
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION);
assertLatestMultiActionMessage(controllerId, Arrays.asList(action1Install, action2Install));
}
/**
* Verify payload of multi action messages.
*/
@Test
void assertMultiActionMessagePayloads() {
final int expectedWeightIfNotSet = 1000;
final int weight1 = 600;
final String controllerId = UUID.randomUUID().toString();
registerAndAssertTargetWithExistingTenant(controllerId);
final DistributionSet ds = testdataFactory.createDistributionSet();
testdataFactory.addSoftwareModuleMetadata(ds);
final Long installActionId = makeAssignment(DeploymentRequest.builder(controllerId, ds.getId())
.actionType(ActionType.FORCED).build()).getAssignedEntity().get(0).getId();
enableMultiAssignments();
final Long downloadActionId = makeAssignment(DeploymentRequest.builder(controllerId, ds.getId())
.actionType(ActionType.DOWNLOAD_ONLY).weight(weight1).build()).getAssignedEntity().get(0).getId();
final Long cancelActionId = makeAssignment(
DeploymentRequest.builder(controllerId, ds.getId()).weight(DEFAULT_TEST_WEIGHT).build())
.getAssignedEntity().get(0).getId();
// make sure the latest message in the queue is the one triggered by the
// cancellation
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.DOWNLOAD_AND_INSTALL, EventTopic.MULTI_ACTION,
EventTopic.MULTI_ACTION);
deploymentManagement.cancelAction(cancelActionId);
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION);
final List<DmfMultiActionElement> multiActionMessages = getLatestMultiActionMessages(controllerId);
assertThat(multiActionMessages).hasSize(3);
final DmfMultiActionElement installMessage = multiActionMessages.stream()
.filter(message -> message.getTopic().equals(EventTopic.DOWNLOAD_AND_INSTALL)).findFirst().get();
final DmfMultiActionElement downloadMessage = multiActionMessages.stream()
.filter(message -> message.getTopic().equals(EventTopic.DOWNLOAD)).findFirst().get();
final DmfMultiActionElement cancelMessage = multiActionMessages.stream()
.filter(message -> message.getTopic().equals(EventTopic.CANCEL_DOWNLOAD)).findFirst().get();
assertThat(installMessage.getWeight()).isEqualTo(expectedWeightIfNotSet);
assertThat(downloadMessage.getWeight()).isEqualTo(weight1);
assertThat(cancelMessage.getWeight()).isEqualTo(DEFAULT_TEST_WEIGHT);
assertThat(installMessage.getAction()).isExactlyInstanceOf(DmfDownloadAndUpdateRequest.class)
.hasFieldOrPropertyWithValue("actionId", installActionId);
assertThat(downloadMessage.getAction()).isExactlyInstanceOf(DmfDownloadAndUpdateRequest.class)
.hasFieldOrPropertyWithValue("actionId", downloadActionId);
assertThat(cancelMessage.getAction()).isExactlyInstanceOf(DmfActionRequest.class)
.hasFieldOrPropertyWithValue("actionId", cancelActionId);
assertDmfDownloadAndUpdateRequest((DmfDownloadAndUpdateRequest) installMessage.getAction(), ds.getModules(),
controllerId);
assertDmfDownloadAndUpdateRequest((DmfDownloadAndUpdateRequest) downloadMessage.getAction(), ds.getModules(),
controllerId);
}
/**
* Handle cancelation process of an action in multi assignment mode.
*/
@Test
@ExpectEvents({
@Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = TargetUpdatedEvent.class, count = 2),
@Expect(type = TargetPollEvent.class, count = 1),
@Expect(type = TenantConfigurationCreatedEvent.class, count = 1),
@Expect(type = MultiActionCancelEvent.class, count = 1),
@Expect(type = MultiActionAssignEvent.class, count = 2),
@Expect(type = TargetAssignDistributionSetEvent.class, count = 0),
@Expect(type = CancelTargetAssignmentEvent.class, count = 0),
@Expect(type = ActionCreatedEvent.class, count = 2),
@Expect(type = ActionUpdatedEvent.class, count = 2),
@Expect(type = DistributionSetCreatedEvent.class, count = 2),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 6),
@Expect(type = DistributionSetUpdatedEvent.class, count = 2), // implicit lock
@Expect(type = SoftwareModuleUpdatedEvent.class, count = 6) // implicit lock
})
void cancelActionInMultiAssignMode() {
enableMultiAssignments();
final String controllerId = TARGET_PREFIX + "cancelActionInMultiAssignMode";
registerAndAssertTargetWithExistingTenant(controllerId);
final long actionId1 = assignNewDsToTarget(controllerId, 675);
final long actionId2 = assignNewDsToTarget(controllerId, 343);
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION, EventTopic.MULTI_ACTION);
deploymentManagement.cancelAction(actionId1);
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION);
final SimpleEntry<Long, EventTopic> action1Cancel = new SimpleEntry<>(actionId1, EventTopic.CANCEL_DOWNLOAD);
final SimpleEntry<Long, EventTopic> action2Install = new SimpleEntry<>(actionId2, EventTopic.DOWNLOAD_AND_INSTALL);
assertLatestMultiActionMessage(controllerId, Arrays.asList(action1Cancel, action2Install));
updateActionViaDmfClient(controllerId, actionId1, DmfActionStatus.CANCELED);
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION);
assertLatestMultiActionMessage(controllerId, Collections.singletonList(action2Install));
}
/**
* Handle finishing an action in multi assignment mode.
*/
@Test
@ExpectEvents({
@Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = MultiActionAssignEvent.class, count = 2),
@Expect(type = TargetAttributesRequestedEvent.class, count = 1),
@Expect(type = TargetAssignDistributionSetEvent.class, count = 0),
@Expect(type = CancelTargetAssignmentEvent.class, count = 0),
@Expect(type = ActionCreatedEvent.class, count = 2),
@Expect(type = ActionUpdatedEvent.class, count = 1),
@Expect(type = DistributionSetCreatedEvent.class, count = 2),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 6),
@Expect(type = DistributionSetUpdatedEvent.class, count = 2), // implicit lock
@Expect(type = SoftwareModuleUpdatedEvent.class, count = 6), // implicit lock
@Expect(type = TargetUpdatedEvent.class, count = 3),
@Expect(type = TargetPollEvent.class, count = 1),
@Expect(type = TenantConfigurationCreatedEvent.class, count = 1) })
void finishActionInMultiAssignMode() {
enableMultiAssignments();
final String controllerId = TARGET_PREFIX + "finishActionInMultiAssignMode";
registerAndAssertTargetWithExistingTenant(controllerId);
final long actionId1 = assignNewDsToTarget(controllerId, 66);
final long actionId2 = assignNewDsToTarget(controllerId, 767);
final SimpleEntry<Long, EventTopic> action2Install = new SimpleEntry<>(actionId2, EventTopic.DOWNLOAD_AND_INSTALL);
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION, EventTopic.MULTI_ACTION);
updateActionViaDmfClient(controllerId, actionId1, DmfActionStatus.FINISHED);
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.REQUEST_ATTRIBUTES_UPDATE, EventTopic.MULTI_ACTION);
assertRequestAttributesUpdateMessage(controllerId);
assertLatestMultiActionMessage(controllerId, Collections.singletonList(action2Install));
}
/**
* If multi assignment is enabled assigning a DS multiple times creates a new action every time.
*/
@Test
@ExpectEvents({
@Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = MultiActionAssignEvent.class, count = 2),
@Expect(type = TargetAssignDistributionSetEvent.class, count = 0),
@Expect(type = CancelTargetAssignmentEvent.class, count = 0),
@Expect(type = ActionCreatedEvent.class, count = 2),
@Expect(type = ActionUpdatedEvent.class, count = 0),
@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 = 3), // implicit lock
@Expect(type = TargetUpdatedEvent.class, count = 2),
@Expect(type = TargetPollEvent.class, count = 1),
@Expect(type = TenantConfigurationCreatedEvent.class, count = 1) })
void assignDsMultipleTimesInMultiAssignMode() {
enableMultiAssignments();
final String controllerId = TARGET_PREFIX + "assignDsMultipleTimesInMultiAssignMode";
registerAndAssertTargetWithExistingTenant(controllerId);
final Long dsId = testdataFactory.createDistributionSet().getId();
final Long actionId1 = getFirstAssignedAction(assignDistributionSet(dsId, controllerId, 344)).getId();
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION);
final Long actionId2 = getFirstAssignedAction(assignDistributionSet(dsId, controllerId, 775)).getId();
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION);
final SimpleEntry<Long, EventTopic> action1Install = new SimpleEntry<>(actionId1, EventTopic.DOWNLOAD_AND_INSTALL);
final SimpleEntry<Long, EventTopic> action2Install = new SimpleEntry<>(actionId2, EventTopic.DOWNLOAD_AND_INSTALL);
assertLatestMultiActionMessage(controllerId, Arrays.asList(action1Install, action2Install));
}
/**
* If multi assignment is enabled multiple rollouts with the same DS lead to multiple actions.
*/
@Test
@ExpectEvents({
@Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = MultiActionAssignEvent.class, count = 2),
@Expect(type = TargetAssignDistributionSetEvent.class, count = 0),
@Expect(type = CancelTargetAssignmentEvent.class, count = 0),
@Expect(type = ActionCreatedEvent.class, count = 2),
@Expect(type = ActionUpdatedEvent.class, count = 2),
@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 = 3), // implicit lock
@Expect(type = TargetUpdatedEvent.class, count = 1),
@Expect(type = TargetPollEvent.class, count = 1),
@Expect(type = RolloutCreatedEvent.class, count = 2),
@Expect(type = RolloutUpdatedEvent.class, count = 6),
@Expect(type = RolloutGroupCreatedEvent.class, count = 2),
@Expect(type = RolloutGroupUpdatedEvent.class, count = 6),
@Expect(type = TenantConfigurationCreatedEvent.class, count = 1) })
void startRolloutsWithSameDsInMultiAssignMode() {
enableMultiAssignments();
final String controllerId = TARGET_PREFIX + "startRolloutsWithSameDsInMultiAssignMode";
registerAndAssertTargetWithExistingTenant(controllerId);
final DistributionSet ds = testdataFactory.createDistributionSet();
final Set<Long> smIds = getSoftwareModuleIds(ds);
final String filterQuery = "controllerId==" + controllerId;
createAndStartRollout(ds, filterQuery, 122);
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION);
assertLatestMultiActionMessageContainsInstallMessages(controllerId, Collections.singletonList(smIds));
createAndStartRollout(ds, filterQuery, 43);
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION);
assertLatestMultiActionMessageContainsInstallMessages(controllerId, Arrays.asList(smIds, smIds));
}
/**
* If multi assignment is enabled finishing one rollout does not affect other rollouts of the target.
*/
@Test
@ExpectEvents({
@Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = MultiActionAssignEvent.class, count = 3),
@Expect(type = ActionCreatedEvent.class, count = 3),
@Expect(type = ActionUpdatedEvent.class, count = 5),
@Expect(type = DistributionSetCreatedEvent.class, count = 2),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 6),
@Expect(type = DistributionSetUpdatedEvent.class, count = 2), // implicit lock
@Expect(type = SoftwareModuleUpdatedEvent.class, count = 6), // implicit lock
@Expect(type = TargetUpdatedEvent.class, count = 5),
@Expect(type = TargetPollEvent.class, count = 1),
@Expect(type = TargetAttributesRequestedEvent.class, count = 2),
@Expect(type = RolloutCreatedEvent.class, count = 3),
@Expect(type = RolloutUpdatedEvent.class, count = 9),
@Expect(type = RolloutGroupCreatedEvent.class, count = 3),
@Expect(type = RolloutGroupUpdatedEvent.class, count = 9),
@Expect(type = TenantConfigurationCreatedEvent.class, count = 1) })
void startMultipleRolloutsAndFinishInMultiAssignMode() {
enableMultiAssignments();
final String controllerId = TARGET_PREFIX + "startMultipleRolloutsAndFinishInMultiAssignMode";
registerAndAssertTargetWithExistingTenant(controllerId);
final String filterQuery = "controllerId==" + controllerId;
final DistributionSet ds1 = testdataFactory.createDistributionSet();
final Set<Long> smIds1 = getSoftwareModuleIds(ds1);
final DistributionSet ds2 = testdataFactory.createDistributionSet();
final Set<Long> smIds2 = getSoftwareModuleIds(ds2);
createAndStartRollout(ds1, filterQuery, 12);
createAndStartRollout(ds2, filterQuery, 45);
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION, EventTopic.MULTI_ACTION);
createAndStartRollout(ds1, filterQuery, 65);
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION);
assertLatestMultiActionMessageContainsInstallMessages(controllerId, Arrays.asList(smIds1, smIds2, smIds1));
final List<Long> installActions = getLatestMultiActionMessageActions(controllerId).stream()
.filter(entry -> entry.getValue().equals(EventTopic.DOWNLOAD_AND_INSTALL))
.map(Entry::getKey)
.toList();
updateActionViaDmfClient(controllerId, installActions.get(0), DmfActionStatus.FINISHED);
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.REQUEST_ATTRIBUTES_UPDATE, EventTopic.MULTI_ACTION);
assertLatestMultiActionMessageContainsInstallMessages(controllerId, Arrays.asList(smIds2, smIds1));
updateActionViaDmfClient(controllerId, installActions.get(1), DmfActionStatus.FINISHED);
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.REQUEST_ATTRIBUTES_UPDATE, EventTopic.MULTI_ACTION);
assertLatestMultiActionMessageContainsInstallMessages(controllerId, Collections.singletonList(smIds1));
}
/**
* Verify that a cancel assignment send a cancel message.
*/
@@ -649,21 +341,6 @@ class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpServiceInt
assertThat(replyToListener.getLatestEventMessage(eventTopic)).isNull();
}
/**
* Verify that batch and multi-assignments can't be activated at the same time.
*/
@Test
void assertBatchAndMultiAssignmentsNotCompatible() {
enableBatchAssignments();
assertThatExceptionOfType(TenantConfigurationValueChangeNotAllowedException.class)
.isThrownBy(() -> enableMultiAssignments());
disableBatchAssignments();
enableMultiAssignments();
assertThatExceptionOfType(TenantConfigurationValueChangeNotAllowedException.class)
.isThrownBy(() -> enableBatchAssignments());
}
/**
* Verify payload of batch assignments.
*/
@@ -734,27 +411,6 @@ class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpServiceInt
assertEventMessageNotPresent(EventTopic.DOWNLOAD_AND_INSTALL);
}
private static Set<Long> getSmIds(final DmfDownloadAndUpdateRequest request) {
return request.getSoftwareModules().stream().map(DmfSoftwareModule::getModuleId).collect(Collectors.toSet());
}
private static List<DmfDownloadAndUpdateRequest> getDownloadAndUpdateRequests(final DmfMultiActionRequest request) {
return request.getElements().stream()
.filter(AmqpMessageDispatcherServiceIntegrationTest::isDownloadAndUpdateRequest)
.map(multiAction -> (DmfDownloadAndUpdateRequest) multiAction.getAction()).toList();
}
private static boolean isDownloadAndUpdateRequest(final DmfMultiActionElement multiActionElement) {
return multiActionElement.getTopic().equals(EventTopic.DOWNLOAD)
|| multiActionElement.getTopic().equals(EventTopic.DOWNLOAD_AND_INSTALL);
}
private List<DmfMultiActionElement> getLatestMultiActionMessages(final String expectedControllerId) {
final Message multiactionMessage = replyToListener.getLatestEventMessage(EventTopic.MULTI_ACTION);
assertThat(multiactionMessage.getMessageProperties().getHeaders()).containsEntry(MessageHeaderKey.THING_ID, expectedControllerId);
return ((DmfMultiActionRequest) getDmfClient().getMessageConverter().fromMessage(multiactionMessage)).getElements();
}
private void updateActionViaDmfClient(final String controllerId, final long actionId,
final DmfActionStatus status) {
createAndSendActionStatusUpdateMessage(controllerId, actionId, status);
@@ -773,18 +429,6 @@ class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpServiceInt
return actionId;
}
private Set<Long> getSoftwareModuleIds(final DistributionSet ds) {
return ds.getModules().stream().map(SoftwareModule::getId).collect(Collectors.toSet());
}
private Rollout createAndStartRollout(final DistributionSet ds, final String filterQuery, final Integer weight) {
final Rollout rollout = testdataFactory.createRolloutByVariables(UUID.randomUUID().toString(), "", 1,
filterQuery, ds, "50", "5", ActionType.FORCED, weight, false);
rolloutManagement.start(rollout.getId());
rolloutHandler.handleAll();
return rollout;
}
private void waitUntilTargetHasStatus(final String controllerId, final TargetUpdateStatus status) {
waitUntil(() -> {
final Optional<Target> findTargetByControllerID = targetManagement.findByControllerId(controllerId);
@@ -795,29 +439,4 @@ class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpServiceInt
private void waitUntil(final Callable<Boolean> callable) {
await().until(() -> SecurityContextSwitch.asPrivileged(callable));
}
private void assertLatestMultiActionMessageContainsInstallMessages(final String controllerId,
final List<Set<Long>> smIdsOfActionsExpected) {
final Message multiactionMessage = replyToListener.getLatestEventMessage(EventTopic.MULTI_ACTION);
assertThat(multiactionMessage.getMessageProperties().getHeaders()).containsEntry(MessageHeaderKey.THING_ID, controllerId);
final DmfMultiActionRequest multiActionRequest =
(DmfMultiActionRequest) getDmfClient().getMessageConverter().fromMessage(multiactionMessage);
final List<Set<Long>> smIdsOfActionsFound = getDownloadAndUpdateRequests(multiActionRequest).stream()
.map(AmqpMessageDispatcherServiceIntegrationTest::getSmIds).toList();
assertThat(smIdsOfActionsFound).containsExactlyInAnyOrderElementsOf(smIdsOfActionsExpected);
}
private void assertLatestMultiActionMessage(final String controllerId,
final List<SimpleEntry<Long, EventTopic>> actionsExpected) {
final List<SimpleEntry<Long, EventTopic>> actionsFromMessage = getLatestMultiActionMessageActions(controllerId);
assertThat(actionsFromMessage).containsExactlyInAnyOrderElementsOf(actionsExpected);
}
private List<SimpleEntry<Long, EventTopic>> getLatestMultiActionMessageActions(final String expectedControllerId) {
final List<DmfMultiActionElement> multiActionRequest = getLatestMultiActionMessages(expectedControllerId);
return multiActionRequest.stream()
.map(request -> new SimpleEntry<>(request.getAction().getActionId(), request.getTopic()))
.toList();
}
}