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

@@ -155,7 +155,7 @@ public enum SpServerError {
SP_CONFIGURATION_VALUE_CHANGE_NOT_ALLOWED( SP_CONFIGURATION_VALUE_CHANGE_NOT_ALLOWED(
"hawkbit.server.error.repo.tenantConfigurationValueChangeNotAllowed", "hawkbit.server.error.repo.tenantConfigurationValueChangeNotAllowed",
"The requested tenant configuration value modification is not allowed."), "The requested tenant configuration value modification is not allowed."),
SP_MULTIASSIGNMENT_NOT_ENABLED( SP_MULTIASSIGNMENT(
"hawkbit.server.error.multiAssignmentNotEnabled", "hawkbit.server.error.multiAssignmentNotEnabled",
"The requested operation requires multi assignments to be enabled."), "The requested operation requires multi assignments to be enabled."),
SP_NO_WEIGHT_PROVIDED_IN_MULTIASSIGNMENT_MODE( SP_NO_WEIGHT_PROVIDED_IN_MULTIASSIGNMENT_MODE(

View File

@@ -687,23 +687,6 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
.andExpect(jsonPath("$.deployment.maintenanceWindow", equalTo("available"))); .andExpect(jsonPath("$.deployment.maintenanceWindow", equalTo("available")));
} }
/**
* Assign multiple DS in multi-assignment mode. The earliest active Action is exposed to the controller.
*/
@Test
void earliestActionIsExposedToControllerInMultiAssignMode() throws Exception {
enableMultiAssignments();
final Target target = testdataFactory.createTarget();
final DistributionSet ds1 = testdataFactory.createDistributionSet(UUID.randomUUID().toString());
final DistributionSet ds2 = testdataFactory.createDistributionSet(UUID.randomUUID().toString());
final Action action1 = getFirstAssignedAction(assignDistributionSet(ds1.getId(), target.getControllerId(), 56));
final Long action2Id = getFirstAssignedActionId(assignDistributionSet(ds2.getId(), target.getControllerId(), 34));
assertDeploymentActionIsExposedToTarget(target.getControllerId(), action1.getId());
sendDeploymentActionFeedback(target, action1, "closed", "success");
assertDeploymentActionIsExposedToTarget(target.getControllerId(), action2Id);
}
/** /**
* The system should not create a new target because of a too long controller id. * The system should not create a new target because of a too long controller id.
*/ */
@@ -759,16 +742,6 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
return sendDeploymentActionFeedback(target, action, execution, finished, null); return sendDeploymentActionFeedback(target, action, execution, finished, null);
} }
private void assertDeploymentActionIsExposedToTarget(final String controllerId, final long expectedActionId) throws Exception {
final String expectedDeploymentBaseLink = String.format(
"/%s/controller/v1/%s/deploymentBase/%d",
AccessContext.tenant(), controllerId, expectedActionId);
mvc.perform(get(CONTROLLER_BASE, AccessContext.tenant(), controllerId).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk())
.andExpect(jsonPath("$._links.deploymentBase.href", containsString(expectedDeploymentBaseLink)));
}
private void withPollingTime(final String pollingTime, final Callable<Void> runnable) throws Exception { private void withPollingTime(final String pollingTime, final Callable<Void> runnable) throws Exception {
getAs(withUser("tenantadmin", TENANT_CONFIGURATION), getAs(withUser("tenantadmin", TENANT_CONFIGURATION),
() -> { () -> {

View File

@@ -10,7 +10,6 @@
package org.eclipse.hawkbit.amqp; package org.eclipse.hawkbit.amqp;
import static org.eclipse.hawkbit.context.AccessContext.asSystem; 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 static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.BATCH_ASSIGNMENTS_ENABLED;
import java.net.URI; 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.DmfConfirmRequest;
import org.eclipse.hawkbit.dmf.json.model.DmfDownloadAndUpdateRequest; import org.eclipse.hawkbit.dmf.json.model.DmfDownloadAndUpdateRequest;
import org.eclipse.hawkbit.dmf.json.model.DmfMetadata; 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.DmfSoftwareModule;
import org.eclipse.hawkbit.dmf.json.model.DmfTarget; import org.eclipse.hawkbit.dmf.json.model.DmfTarget;
import org.eclipse.hawkbit.repository.DeploymentManagement; import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.DistributionSetManagement; import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.RepositoryProperties;
import org.eclipse.hawkbit.repository.SoftwareModuleManagement; import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
import org.eclipse.hawkbit.repository.SystemManagement; import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.TargetManagement; import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.event.remote.CancelTargetAssignmentEvent; 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.TargetAssignDistributionSetEvent;
import org.eclipse.hawkbit.repository.event.remote.TargetAttributesRequestedEvent; import org.eclipse.hawkbit.repository.event.remote.TargetAttributesRequestedEvent;
import org.eclipse.hawkbit.repository.event.remote.TargetDeletedEvent; 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.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.TargetAssignDistributionSetServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.TargetAttributesRequestedServiceEvent; import org.eclipse.hawkbit.repository.event.remote.service.TargetAttributesRequestedServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.TargetDeletedServiceEvent; 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 SoftwareModuleManagement<? extends SoftwareModule> softwareModuleManagement;
private final DistributionSetManagement<? extends DistributionSet> distributionSetManagement; private final DistributionSetManagement<? extends DistributionSet> distributionSetManagement;
private final DeploymentManagement deploymentManagement; private final DeploymentManagement deploymentManagement;
private final RepositoryProperties repositoryProperties;
@SuppressWarnings("java:S107") @SuppressWarnings("java:S107")
protected AmqpMessageDispatcherService( protected AmqpMessageDispatcherService(
@@ -109,8 +101,7 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
final TargetManagement<? extends Target> targetManagement, final TargetManagement<? extends Target> targetManagement,
final SoftwareModuleManagement<? extends SoftwareModule> softwareModuleManagement, final SoftwareModuleManagement<? extends SoftwareModule> softwareModuleManagement,
final DistributionSetManagement<? extends DistributionSet> distributionSetManagement, final DistributionSetManagement<? extends DistributionSet> distributionSetManagement,
final DeploymentManagement deploymentManagement, final DeploymentManagement deploymentManagement) {
final RepositoryProperties repositoryProperties) {
super(rabbitTemplate); super(rabbitTemplate);
this.artifactUrlHandler = artifactUrlHandler; this.artifactUrlHandler = artifactUrlHandler;
this.amqpSenderService = amqpSenderService; this.amqpSenderService = amqpSenderService;
@@ -119,7 +110,6 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
this.softwareModuleManagement = softwareModuleManagement; this.softwareModuleManagement = softwareModuleManagement;
this.distributionSetManagement = distributionSetManagement; this.distributionSetManagement = distributionSetManagement;
this.deploymentManagement = deploymentManagement; this.deploymentManagement = deploymentManagement;
this.repositoryProperties = repositoryProperties;
} }
public boolean isBatchAssignmentsEnabled() { 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( protected void sendUpdateMessageToTarget(
final ActionProperties actionsProps, final Target target, final ActionProperties actionsProps, final Target target,
final Map<SoftwareModule, Map<String, String>> softwareModules) { 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)); 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 * 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, * 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; : 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( private static <T, R> List<R> partitionedParallelExecution(
final Collection<T> controllerIds, final Function<Collection<T>, List<R>> loadingFunction) { final Collection<T> controllerIds, final Function<Collection<T>, List<R>> loadingFunction) {
// Ensure not exceeding the max value of MAX_PROCESSING_SIZE // 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( private void sendSingleUpdateMessage(
final ActionProperties action, final Target target, final Map<SoftwareModule, Map<String, String>> modules) { final ActionProperties action, final Target target, final Map<SoftwareModule, Map<String, String>> modules) {
final String tenant = action.getTenant(); final String tenant = action.getTenant();

View File

@@ -9,15 +9,12 @@
*/ */
package org.eclipse.hawkbit.amqp; package org.eclipse.hawkbit.amqp;
import static org.eclipse.hawkbit.repository.RepositoryConstants.MAX_ACTION_COUNT;
import java.net.URI; import java.net.URI;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; 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.UpdateMode;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException; import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; 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;
import org.eclipse.hawkbit.repository.model.Action.ActionStatusCreate; import org.eclipse.hawkbit.repository.model.Action.ActionStatusCreate;
import org.eclipse.hawkbit.repository.model.Action.ActionStatusCreate.ActionStatusCreateBuilder; 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.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.tenancy.TenantAwareAuthenticationDetails; import org.eclipse.hawkbit.tenancy.TenantAwareAuthenticationDetails;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties;
import org.eclipse.hawkbit.utils.IpUtil; import org.eclipse.hawkbit.utils.IpUtil;
import org.springframework.amqp.AmqpRejectAndDontRequeueException; import org.springframework.amqp.AmqpRejectAndDontRequeueException;
import org.springframework.amqp.core.Message; import org.springframework.amqp.core.Message;
@@ -315,29 +310,7 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
} }
private void sendUpdateCommandToTarget(final Target target) { private void sendUpdateCommandToTarget(final Target target) {
if (TenantConfigHelper.getAsSystem(TenantConfigurationProperties.TenantConfigurationKey.MULTI_ASSIGNMENTS_ENABLED, Boolean.class)) { // send oldest action to Target
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) {
final Optional<Action> actionOptional = controllerManagement.findActiveActionWithHighestWeight(target.getControllerId()); final Optional<Action> actionOptional = controllerManagement.findActiveActionWithHighestWeight(target.getControllerId());
if (actionOptional.isEmpty()) { if (actionOptional.isEmpty()) {
return; return;

View File

@@ -265,11 +265,10 @@ public class DmfApiConfiguration {
final SystemManagement systemManagement, final SystemManagement systemManagement,
final TargetManagement<? extends Target> targetManagement, final TargetManagement<? extends Target> targetManagement,
final DistributionSetManagement<? extends DistributionSet> distributionSetManagement, final DistributionSetManagement<? extends DistributionSet> distributionSetManagement,
final SoftwareModuleManagement<? extends SoftwareModule> softwareModuleManagement, final DeploymentManagement deploymentManagement, final SoftwareModuleManagement<? extends SoftwareModule> softwareModuleManagement, final DeploymentManagement deploymentManagement) {
final RepositoryProperties repositoryProperties) {
return new AmqpMessageDispatcherService(rabbitTemplate, amqpSenderService, artifactUrlHandler, return new AmqpMessageDispatcherService(rabbitTemplate, amqpSenderService, artifactUrlHandler,
systemManagement, targetManagement, softwareModuleManagement, distributionSetManagement, systemManagement, targetManagement, softwareModuleManagement, distributionSetManagement,
deploymentManagement, repositoryProperties); deploymentManagement);
} }
private static Map<String, Object> getTTLMaxArgsAuthenticationQueue() { 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" }) "spring.main.allow-bean-definition-overriding=true" })
class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTest { class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTest {
@Autowired
protected RepositoryProperties repositoryProperties;
private static final String TENANT = "DEFAULT"; private static final String TENANT = "DEFAULT";
private static final Long TENANT_ID = 4711L; private static final Long TENANT_ID = 4711L;
@@ -117,7 +114,7 @@ class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTest {
amqpMessageDispatcherService = new AmqpMessageDispatcherService(rabbitTemplate, senderService, amqpMessageDispatcherService = new AmqpMessageDispatcherService(rabbitTemplate, senderService,
artifactUrlHandlerMock, systemManagement, targetManagement, 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.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; 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.any;
import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.anyString;
@@ -112,10 +111,6 @@ class AmqpMessageHandlerServiceTest {
TenantConfigHelper.setTenantConfigurationManagement(tenantConfigurationManagement); TenantConfigHelper.setTenantConfigurationManagement(tenantConfigurationManagement);
messageConverter = new Jackson2JsonMessageConverter(); messageConverter = new Jackson2JsonMessageConverter();
lenient().when(rabbitTemplate.getMessageConverter()).thenReturn(messageConverter); 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( amqpMessageHandlerService = new AmqpMessageHandlerService(
rabbitTemplate, amqpMessageDispatcherServiceMock, controllerManagementMock, confirmationManagementMock); rabbitTemplate, amqpMessageDispatcherServiceMock, controllerManagementMock, confirmationManagementMock);

View File

@@ -10,7 +10,6 @@
package org.eclipse.hawkbit.integration; package org.eclipse.hawkbit.integration;
import static org.assertj.core.api.Assertions.assertThat; 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;
import static org.eclipse.hawkbit.dmf.amqp.api.EventTopic.BATCH_DOWNLOAD_AND_INSTALL; import static org.eclipse.hawkbit.dmf.amqp.api.EventTopic.BATCH_DOWNLOAD_AND_INSTALL;
import static org.eclipse.hawkbit.dmf.amqp.api.EventTopic.DOWNLOAD; 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.DOWNLOAD_ONLY;
import static org.eclipse.hawkbit.repository.model.Action.ActionType.FORCED; import static org.eclipse.hawkbit.repository.model.Action.ActionType.FORCED;
import java.util.AbstractMap.SimpleEntry;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Callable; 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.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.DmfActionStatus;
import org.eclipse.hawkbit.dmf.json.model.DmfBatchDownloadAndUpdateRequest; 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.dmf.json.model.DmfTarget;
import org.eclipse.hawkbit.repository.event.remote.CancelTargetAssignmentEvent; 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.TargetAssignDistributionSetEvent;
import org.eclipse.hawkbit.repository.event.remote.TargetAttributesRequestedEvent; import org.eclipse.hawkbit.repository.event.remote.TargetAttributesRequestedEvent;
import org.eclipse.hawkbit.repository.event.remote.TargetDeletedEvent; 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.ActionUpdatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetCreatedEvent; 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.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.SoftwareModuleCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleUpdatedEvent; 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.TargetCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.TenantConfigurationCreatedEvent; 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.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.model.Action.ActionType; 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.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult; import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult;
import org.eclipse.hawkbit.repository.model.RepositoryModelConstants; 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.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
@@ -212,294 +192,6 @@ class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpServiceInt
assertDownloadAndInstallMessage(distributionSet2.getModules(), controllerId); 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. * Verify that a cancel assignment send a cancel message.
*/ */
@@ -649,21 +341,6 @@ class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpServiceInt
assertThat(replyToListener.getLatestEventMessage(eventTopic)).isNull(); 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. * Verify payload of batch assignments.
*/ */
@@ -734,27 +411,6 @@ class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpServiceInt
assertEventMessageNotPresent(EventTopic.DOWNLOAD_AND_INSTALL); 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, private void updateActionViaDmfClient(final String controllerId, final long actionId,
final DmfActionStatus status) { final DmfActionStatus status) {
createAndSendActionStatusUpdateMessage(controllerId, actionId, status); createAndSendActionStatusUpdateMessage(controllerId, actionId, status);
@@ -773,18 +429,6 @@ class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpServiceInt
return actionId; 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) { private void waitUntilTargetHasStatus(final String controllerId, final TargetUpdateStatus status) {
waitUntil(() -> { waitUntil(() -> {
final Optional<Target> findTargetByControllerID = targetManagement.findByControllerId(controllerId); final Optional<Target> findTargetByControllerID = targetManagement.findByControllerId(controllerId);
@@ -795,29 +439,4 @@ class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpServiceInt
private void waitUntil(final Callable<Boolean> callable) { private void waitUntil(final Callable<Boolean> callable) {
await().until(() -> SecurityContextSwitch.asPrivileged(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();
}
} }

View File

@@ -1484,52 +1484,30 @@ class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegrationTe
.andExpect(jsonPath("total", equalTo(1))); .andExpect(jsonPath("total", equalTo(1)));
} }
/**
* Assigning targets multiple times to a DS in one request works in multi-assignment mode.
*/
@Test
void multiAssignment() throws Exception {
final List<String> targetIds = testdataFactory.createTargets(2).stream().map(Target::getControllerId).toList();
final Long dsId = testdataFactory.createDistributionSet().getId();
final JSONArray body = new JSONArray();
body.put(getAssignmentObject(targetIds.get(0), MgmtActionType.FORCED, 56));
body.put(getAssignmentObject(targetIds.get(0), MgmtActionType.FORCED, 78));
body.put(getAssignmentObject(targetIds.get(1), MgmtActionType.FORCED, 67));
body.put(getAssignmentObject(targetIds.get(1), MgmtActionType.SOFT, 34));
enableMultiAssignments();
mvc.perform(post("/rest/v1/distributionsets/{ds}/assignedTargets", dsId).content(body.toString())
.contentType(APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk())
.andExpect(jsonPath("total", equalTo(body.length())));
}
/** /**
* An assignment request containing a weight is only accepted when weight is valide and multi assignment is on. * An assignment request containing a weight is only accepted when weight is valide and multi assignment is on.
*/ */
@Test @Test
void weightValidation() throws Exception { void weightValidation() throws Exception {
final String targetId = testdataFactory.createTarget().getControllerId(); final String targetId = testdataFactory.createTarget().getControllerId();
final Long dsId = testdataFactory.createDistributionSet().getId(); final Long dsId1 = testdataFactory.createDistributionSet().getId();
final Long dsId2 = testdataFactory.createDistributionSet().getId();
final int weight = 78; final int weight = 78;
final JSONArray bodyValide = new JSONArray().put(getAssignmentObject(targetId, MgmtActionType.FORCED, weight)); final JSONArray bodyValide = new JSONArray().put(getAssignmentObject(targetId, MgmtActionType.FORCED, weight));
final JSONArray bodyInvalide = new JSONArray() final JSONArray bodyInvalide = new JSONArray()
.put(getAssignmentObject(targetId, MgmtActionType.FORCED, Action.WEIGHT_MIN - 1)); .put(getAssignmentObject(targetId, MgmtActionType.FORCED, Action.WEIGHT_MIN - 1));
mvc.perform(post("/rest/v1/distributionsets/{ds}/assignedTargets", dsId).content(bodyValide.toString()) mvc.perform(post("/rest/v1/distributionsets/{ds}/assignedTargets", dsId1).content(bodyValide.toString())
.contentType(APPLICATION_JSON)) .contentType(APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()) .andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()); .andExpect(status().isOk());
enableMultiAssignments(); mvc.perform(post("/rest/v1/distributionsets/{ds}/assignedTargets", dsId2).content(bodyInvalide.toString())
mvc.perform(post("/rest/v1/distributionsets/{ds}/assignedTargets", dsId).content(bodyInvalide.toString())
.contentType(APPLICATION_JSON)) .contentType(APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()) .andDo(MockMvcResultPrinter.print())
.andExpect(status().isBadRequest()) .andExpect(status().isBadRequest())
.andExpect(jsonPath("$.errorCode", equalTo("hawkbit.server.error.repo.constraintViolation"))); .andExpect(jsonPath("$.errorCode", equalTo("hawkbit.server.error.repo.constraintViolation")));
mvc.perform(post("/rest/v1/distributionsets/{ds}/assignedTargets", dsId).content(bodyValide.toString()) mvc.perform(post("/rest/v1/distributionsets/{ds}/assignedTargets", dsId2).content(bodyValide.toString())
.contentType(APPLICATION_JSON)) .contentType(APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()) .andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()); .andExpect(status().isOk());

View File

@@ -105,7 +105,6 @@ class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTest {
*/ */
@Test @Test
void getRollout() throws Exception { void getRollout() throws Exception {
enableMultiAssignments();
approvalStrategy.setApprovalNeeded(true); approvalStrategy.setApprovalNeeded(true);
try { try {
approvalStrategy.setApprovalDecidedBy("exampleUsername"); approvalStrategy.setApprovalDecidedBy("exampleUsername");
@@ -1678,26 +1677,20 @@ class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTest {
null, null); null, null);
final String valideWeightRequest = JsonBuilder.rollout("withWeight", "d", 2, dsId, "id==rollout*", final String valideWeightRequest = JsonBuilder.rollout("withWeight", "d", 2, dsId, "id==rollout*",
new RolloutGroupConditionBuilder().withDefaults().build(), null, null, weight, null, null, null); new RolloutGroupConditionBuilder().withDefaults().build(), null, null, weight, null, null, null);
final String valideWeightRequestMultiAssignment = JsonBuilder.rollout("withWeightMultiAssignment", "d", 2, dsId, "id==rollout*",
new RolloutGroupConditionBuilder().withDefaults().build(), null, null, weight, null, null, null);
mvc.perform(post("/rest/v1/rollouts").content(valideWeightRequest).contentType(MediaType.APPLICATION_JSON) mvc.perform(post("/rest/v1/rollouts").content(valideWeightRequest).contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)) .accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()) .andDo(MockMvcResultPrinter.print())
.andExpect(status().isCreated()); .andExpect(status().isCreated());
enableMultiAssignments();
mvc.perform(post("/rest/v1/rollouts").content(invalideWeightRequest).contentType(MediaType.APPLICATION_JSON) mvc.perform(post("/rest/v1/rollouts").content(invalideWeightRequest).contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)) .accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()) .andDo(MockMvcResultPrinter.print())
.andExpect(status().isBadRequest()) .andExpect(status().isBadRequest())
.andExpect(jsonPath("$.errorCode", equalTo("hawkbit.server.error.repo.constraintViolation"))); .andExpect(jsonPath("$.errorCode", equalTo("hawkbit.server.error.repo.constraintViolation")));
mvc.perform(post("/rest/v1/rollouts").content(valideWeightRequestMultiAssignment).contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print())
.andExpect(status().isCreated());
final List<Rollout> rollouts = rolloutManagement.findAll(false, PAGE).getContent(); final List<Rollout> rollouts = rolloutManagement.findAll(false, PAGE).getContent();
assertThat(rollouts).hasSize(2); assertThat(rollouts).hasSize(1);
assertThat(rollouts.get(0).getWeight()).get().isEqualTo(weight); assertThat(rollouts.get(0).getWeight()).get().isEqualTo(weight);
} }

View File

@@ -560,7 +560,6 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte
*/ */
@Test @Test
void createAutoAssignDS() throws Exception { void createAutoAssignDS() throws Exception {
enableMultiAssignments();
enableConfirmationFlow(); enableConfirmationFlow();
final String filterName = "filter_01"; final String filterName = "filter_01";
@@ -643,7 +642,7 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte
filterId).content(valideWeightRequest).contentType(MediaType.APPLICATION_JSON)) filterId).content(valideWeightRequest).contentType(MediaType.APPLICATION_JSON))
.andDo(print()) .andDo(print())
.andExpect(status().isOk()); .andExpect(status().isOk());
enableMultiAssignments();
mvc.perform(post(MgmtTargetFilterQueryRestApi.TARGETFILTERS_V1 + "/{targetFilterQueryId}/autoAssignDS", mvc.perform(post(MgmtTargetFilterQueryRestApi.TARGETFILTERS_V1 + "/{targetFilterQueryId}/autoAssignDS",
filterId).content(invalideWeightRequest).contentType(MediaType.APPLICATION_JSON)) filterId).content(invalideWeightRequest).contentType(MediaType.APPLICATION_JSON))
.andDo(print()) .andDo(print())

View File

@@ -2213,24 +2213,6 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest {
.andExpect(jsonPath("total", equalTo(1))); .andExpect(jsonPath("total", equalTo(1)));
} }
/**
* Assign multiple DSs to a target in one request with multiassignments enabled.
*/
@Test
void multiAssignment() throws Exception {
final String targetId = testdataFactory.createTarget().getControllerId();
final List<Long> dsIds = testdataFactory.createDistributionSets(2).stream().map(DistributionSet::getId).toList();
final JSONArray body = new JSONArray();
dsIds.forEach(id -> body.put(getAssignmentObject(id, MgmtActionType.FORCED, 76)));
enableMultiAssignments();
mvc.perform(post("/rest/v1/targets/{targetId}/assignedDS", targetId).content(body.toString()).contentType(APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk())
.andExpect(jsonPath("total", equalTo(2)));
}
/** /**
* An assignment request containing a weight is only accepted when weight is valid and multi assignment is on. * An assignment request containing a weight is only accepted when weight is valid and multi assignment is on.
*/ */
@@ -2243,7 +2225,6 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest {
final JSONObject bodyValid = getAssignmentObject(dsId, MgmtActionType.FORCED, weight); final JSONObject bodyValid = getAssignmentObject(dsId, MgmtActionType.FORCED, weight);
final JSONObject bodyInvalid = getAssignmentObject(dsId, MgmtActionType.FORCED, Action.WEIGHT_MIN - 1); final JSONObject bodyInvalid = getAssignmentObject(dsId, MgmtActionType.FORCED, Action.WEIGHT_MIN - 1);
enableMultiAssignments();
mvc.perform(post("/rest/v1/targets/{targetId}/assignedDS", targetId).content(bodyInvalid.toString()).contentType(APPLICATION_JSON)) mvc.perform(post("/rest/v1/targets/{targetId}/assignedDS", targetId).content(bodyInvalid.toString()).contentType(APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()) .andDo(MockMvcResultPrinter.print())
.andExpect(status().isBadRequest()) .andExpect(status().isBadRequest())
@@ -2272,48 +2253,24 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest {
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
/**
* An assignment request containing a valid weight when multi assignment is on.
*/
@Test
void weightWithMultiAssignment() throws Exception {
final String targetId = testdataFactory.createTarget().getControllerId();
final Long dsId = testdataFactory.createDistributionSet().getId();
final int weight = 98;
final JSONObject bodyValid = getAssignmentObject(dsId, MgmtActionType.FORCED, weight);
enableMultiAssignments();
mvc.perform(post("/rest/v1/targets/{targetId}/assignedDS", targetId).content(bodyValid.toString()).contentType(APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
final List<Action> actions = deploymentManagement.findActionsAll(PAGE).get().toList();
assertThat(actions).size().isEqualTo(1);
assertThat(actions.get(0).getWeight()).get().isEqualTo(weight);
}
/** /**
* Get weight of action * Get weight of action
*/ */
@Test @Test
void getActionWeight() throws Exception { void getActionWeight() throws Exception {
final String targetId = testdataFactory.createTarget().getControllerId(); final String targetId = testdataFactory.createTarget().getControllerId();
final Long dsId = testdataFactory.createDistributionSet().getId(); final Long dsId1 = testdataFactory.createDistributionSet().getId();
final int customWeightHigh = 800; final Long dsId2 = testdataFactory.createDistributionSet().getId();
final int customWeightLow = 300; final int customWeight = 800;
assignDistributionSet(dsId, targetId); // default weight 1000 assignDistributionSet(dsId1, targetId); // default weight 1000
enableMultiAssignments(); assignDistributionSet(dsId2, targetId, customWeight);
assignDistributionSet(dsId, targetId, customWeightHigh);
assignDistributionSet(dsId, targetId, customWeightLow);
mvc.perform(get("/rest/v1/targets/{targetId}/actions", targetId) mvc.perform(get("/rest/v1/targets/{targetId}/actions", targetId)
.param(MgmtRestConstants.REQUEST_PARAMETER_SORTING, "WEIGHT:ASC")) .param(MgmtRestConstants.REQUEST_PARAMETER_SORTING, "WEIGHT:ASC"))
.andDo(MockMvcResultPrinter.print()) .andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(jsonPath("content.[0].weight", equalTo(customWeightLow))) .andExpect(jsonPath("content.[0].weight", equalTo(customWeight)))
.andExpect(jsonPath("content.[1].weight", equalTo(customWeightHigh))) .andExpect(jsonPath("content.[1].weight", equalTo(1000)));
.andExpect(jsonPath("content.[2].weight", equalTo(1000)));
} }
/** /**

View File

@@ -15,7 +15,6 @@ import static org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch.get
import static org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch.withUser; import static org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch.withUser;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_ENABLED; import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_ENABLED;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY; import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.MULTI_ASSIGNMENTS_ENABLED;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.REPOSITORY_ACTIONS_AUTOCLOSE_ENABLED; import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.REPOSITORY_ACTIONS_AUTOCLOSE_ENABLED;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.ROLLOUT_APPROVAL_ENABLED; import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.ROLLOUT_APPROVAL_ENABLED;
import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.equalTo;
@@ -141,27 +140,6 @@ public class MgmtTenantManagementResourceTest extends AbstractManagementApiInteg
assertDefaultDsTypeUpdateBadRequestFails(newDefaultDsType, oldDefaultDsType, status().isNotFound()); assertDefaultDsTypeUpdateBadRequestFails(newDefaultDsType, oldDefaultDsType, status().isNotFound());
} }
/**
* The 'multi.assignments.enabled' property must not be changed to false.
*/
@Test
void deactivateMultiAssignment() throws Exception {
final String bodyActivate = new JSONObject().put("value", true).toString();
final String bodyDeactivate = new JSONObject().put("value", false).toString();
mvc.perform(put(SYSTEM_V1 + "/configs/{keyName}", MULTI_ASSIGNMENTS_ENABLED)
.content(bodyActivate)
.contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print())
.andExpect(status().isNoContent());
mvc.perform(put(SYSTEM_V1 + "/configs/{keyName}", MULTI_ASSIGNMENTS_ENABLED)
.content(bodyDeactivate)
.contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print())
.andExpect(status().isForbidden());
}
/** /**
* The Batch configuration should not be applied, because of invalid TenantConfiguration props * The Batch configuration should not be applied, because of invalid TenantConfiguration props
*/ */
@@ -249,36 +227,6 @@ public class MgmtTenantManagementResourceTest extends AbstractManagementApiInteg
"Change BatchConfiguration was successful but TenantConfiguration property was not actually changed."); "Change BatchConfiguration was successful but TenantConfiguration property was not actually changed.");
} }
/**
* The 'repository.actions.autoclose.enabled' property must not be modified if Multi-Assignments is enabled.
*/
@Test
void autoCloseCannotBeModifiedIfMultiAssignmentIsEnabled() throws Exception {
final String bodyActivate = new JSONObject().put("value", true).toString();
final String bodyDeactivate = new JSONObject().put("value", false).toString();
// enable Multi-Assignments
mvc.perform(put(SYSTEM_V1 + "/configs/{keyName}", MULTI_ASSIGNMENTS_ENABLED)
.content(bodyActivate)
.contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print())
.andExpect(status().isNoContent());
// try to enable Auto-Close
mvc.perform(put(SYSTEM_V1 + "/configs/{keyName}", REPOSITORY_ACTIONS_AUTOCLOSE_ENABLED)
.content(bodyActivate)
.contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print())
.andExpect(status().isForbidden());
// try to disable Auto-Close
mvc.perform(put(SYSTEM_V1 + "/configs/{keyName}", REPOSITORY_ACTIONS_AUTOCLOSE_ENABLED)
.content(bodyDeactivate)
.contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print())
.andExpect(status().isForbidden());
}
/** /**
* Handles DELETE request deleting a tenant specific configuration. * Handles DELETE request deleting a tenant specific configuration.
*/ */

View File

@@ -32,7 +32,7 @@ import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException
import org.eclipse.hawkbit.repository.exception.CancelActionNotAllowedException; import org.eclipse.hawkbit.repository.exception.CancelActionNotAllowedException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException; import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException;
import org.eclipse.hawkbit.repository.exception.MultiAssignmentIsNotEnabledException; import org.eclipse.hawkbit.repository.exception.MultiAssignmentException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException; import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException; import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action;
@@ -76,8 +76,8 @@ public interface DeploymentManagement extends PermissionSupport {
* @throws EntityNotFoundException if either provided {@link DistributionSet} or {@link Target}s do not exist * @throws EntityNotFoundException if either provided {@link DistributionSet} or {@link Target}s do not exist
* @throws AssignmentQuotaExceededException if the maximum number of targets the distribution set can be * @throws AssignmentQuotaExceededException if the maximum number of targets the distribution set can be
* assigned to at once is exceeded * assigned to at once is exceeded
* @throws MultiAssignmentIsNotEnabledException if the request results in multiple assignments to the same * @throws MultiAssignmentException if the request results in multiple assignments to the same
* target and multi-assignment is disabled * target
*/ */
@PreAuthorize(HAS_UPDATE_TARGET_AND_READ_DISTRIBUTION_SET) @PreAuthorize(HAS_UPDATE_TARGET_AND_READ_DISTRIBUTION_SET)
List<DistributionSetAssignmentResult> assignDistributionSets( List<DistributionSetAssignmentResult> assignDistributionSets(
@@ -102,8 +102,8 @@ public interface DeploymentManagement extends PermissionSupport {
* defined by the {@link DistributionSetType}. * defined by the {@link DistributionSetType}.
* @throws EntityNotFoundException if either provided {@link DistributionSet} or {@link Target}s do not exist * @throws EntityNotFoundException if either provided {@link DistributionSet} or {@link Target}s do not exist
* @throws AssignmentQuotaExceededException if the maximum number of targets the distribution set can be assigned to at once is exceeded * @throws AssignmentQuotaExceededException if the maximum number of targets the distribution set can be assigned to at once is exceeded
* @throws MultiAssignmentIsNotEnabledException if the request results in multiple assignments to the same * @throws MultiAssignmentException if the request results in multiple assignments to the same
* target and multi-assignment is disabled * target
*/ */
@PreAuthorize(HAS_UPDATE_TARGET_AND_READ_DISTRIBUTION_SET) @PreAuthorize(HAS_UPDATE_TARGET_AND_READ_DISTRIBUTION_SET)
List<DistributionSetAssignmentResult> offlineAssignedDistributionSets(Collection<Entry<String, Long>> assignments); List<DistributionSetAssignmentResult> offlineAssignedDistributionSets(Collection<Entry<String, Long>> assignments);

View File

@@ -1,40 +0,0 @@
/**
* Copyright (c) 2020 Bosch.IO GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.event.remote;
import java.io.Serial;
import java.util.List;
import lombok.NoArgsConstructor;
import org.eclipse.hawkbit.repository.model.Action;
/**
* Generic deployment event for the Multi-Assignments feature. The event extends
* the {@link MultiActionEvent} and holds a list of controller IDs to identify
* the targets which are affected by a deployment action and a list of
* actionIds containing the identifiers of the affected actions
* as payload. This event is only published in case of an assignment.
*/
@NoArgsConstructor
public class MultiActionAssignEvent extends MultiActionEvent {
@Serial
private static final long serialVersionUID = 1L;
/**
* Constructor.
*
* @param tenant tenant the event is scoped to
* @param actions the actions of the deployment action
*/
public MultiActionAssignEvent(String tenant, List<Action> actions) {
super(tenant, actions);
}
}

View File

@@ -1,32 +0,0 @@
/**
* Copyright (c) 2020 Bosch.IO GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.event.remote;
import java.io.Serial;
import java.util.List;
import lombok.NoArgsConstructor;
import org.eclipse.hawkbit.repository.model.Action;
/**
* Generic deployment event for the Multi-Assignments feature. The event extends the {@link MultiActionEvent} and holds a list of controller IDs
* to identify the targets which are affected by a deployment action and a list of actionIds containing the identifiers of the affected actions
* as payload. This event is only published in case of a cancellation.
*/
@NoArgsConstructor // for serialization libs like jackson
public class MultiActionCancelEvent extends MultiActionEvent {
@Serial
private static final long serialVersionUID = 1L;
public MultiActionCancelEvent(String tenant, List<Action> actions) {
super(tenant, actions);
}
}

View File

@@ -1,62 +0,0 @@
/**
* Copyright (c) 2019 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.event.remote;
import java.io.Serial;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import lombok.AccessLevel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.ToString;
import org.eclipse.hawkbit.repository.Identifiable;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Target;
/**
* Generic deployment event for the Multi-Assignments feature. The event payload holds a list of controller IDs identifying the targets which
* are affected by a deployment action (e.g. a software assignment (update) or a cancellation of an update).
*/
@NoArgsConstructor(access = AccessLevel.PROTECTED) // for serialization libs like jackson
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public abstract class MultiActionEvent extends RemoteTenantAwareEvent implements Iterable<String> {
@Serial
private static final long serialVersionUID = 1L;
private final List<String> controllerIds = new ArrayList<>();
private final List<Long> actionIds = new ArrayList<>();
protected MultiActionEvent(final String tenant, final List<Action> actions) {
super(tenant, null);
this.controllerIds.addAll(getControllerIdsFromActions(actions));
this.actionIds.addAll(getIdsFromActions(actions));
}
@Override
@NonNull
public Iterator<String> iterator() {
return controllerIds.iterator();
}
private static List<String> getControllerIdsFromActions(final List<Action> actions) {
return actions.stream().map(Action::getTarget).map(Target::getControllerId).distinct().toList();
}
private static List<Long> getIdsFromActions(final List<Action> actions) {
return actions.stream().map(Identifiable::getId).toList();
}
}

View File

@@ -1,31 +0,0 @@
/**
* Copyright (c) 2025 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.event.remote.service;
import java.io.Serial;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.eclipse.hawkbit.repository.event.remote.MultiActionAssignEvent;
/**
* Service event for {@link MultiActionAssignEvent}. Event that needs single replica processing
*/
public class MultiActionAssignServiceEvent extends AbstractServiceRemoteEvent<MultiActionAssignEvent> {
@Serial
private static final long serialVersionUID = 1L;
@JsonCreator
public MultiActionAssignServiceEvent(@JsonProperty("payload") final MultiActionAssignEvent remoteEvent) {
super(remoteEvent);
}
}

View File

@@ -1,31 +0,0 @@
/**
* Copyright (c) 2025 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.event.remote.service;
import java.io.Serial;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.eclipse.hawkbit.repository.event.remote.MultiActionCancelEvent;
/**
* Service event for {@link MultiActionCancelEvent}. Event that needs single replica processing
*/
public class MultiActionCancelServiceEvent extends AbstractServiceRemoteEvent<MultiActionCancelEvent> {
@Serial
private static final long serialVersionUID = 1L;
@JsonCreator
public MultiActionCancelServiceEvent(@JsonProperty("payload") final MultiActionCancelEvent remoteEvent) {
super(remoteEvent);
}
}

View File

@@ -21,14 +21,14 @@ import org.eclipse.hawkbit.exception.SpServerError;
*/ */
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true) @ToString(callSuper = true)
public class MultiAssignmentIsNotEnabledException extends AbstractServerRtException { public class MultiAssignmentException extends AbstractServerRtException {
@Serial @Serial
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private static final SpServerError THIS_ERROR = SpServerError.SP_MULTIASSIGNMENT_NOT_ENABLED; private static final SpServerError THIS_ERROR = SpServerError.SP_MULTIASSIGNMENT;
public MultiAssignmentIsNotEnabledException() { public MultiAssignmentException() {
super(THIS_ERROR); super(THIS_ERROR);
} }
} }

View File

@@ -10,7 +10,6 @@
package org.eclipse.hawkbit.repository.helper; package org.eclipse.hawkbit.repository.helper;
import static org.eclipse.hawkbit.context.AccessContext.asSystem; import static org.eclipse.hawkbit.context.AccessContext.asSystem;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.MULTI_ASSIGNMENTS_ENABLED;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.USER_CONFIRMATION_FLOW_ENABLED; import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.USER_CONFIRMATION_FLOW_ENABLED;
import java.io.Serializable; import java.io.Serializable;
@@ -45,10 +44,6 @@ public final class TenantConfigHelper {
return asSystem(() -> getTenantConfigurationManagement().getConfigurationValue(key, valueType).getValue()); return asSystem(() -> getTenantConfigurationManagement().getConfigurationValue(key, valueType).getValue());
} }
public static boolean isMultiAssignmentsEnabled() {
return getAsSystem(MULTI_ASSIGNMENTS_ENABLED, Boolean.class);
}
public static boolean isUserConfirmationFlowEnabled() { public static boolean isUserConfirmationFlowEnabled() {
return getAsSystem(USER_CONFIRMATION_FLOW_ENABLED, Boolean.class); return getAsSystem(USER_CONFIRMATION_FLOW_ENABLED, Boolean.class);
} }

View File

@@ -116,10 +116,6 @@ public class TenantConfigurationProperties {
* Configuration value for percentage of oldest actions to be cleaned if @maxActionsPerTarget quota is hit * Configuration value for percentage of oldest actions to be cleaned if @maxActionsPerTarget quota is hit
*/ */
public static final String ACTION_CLEANUP_ON_QUOTA_HIT_PERCENTAGE = "action.cleanup.onQuotaHit.percent"; public static final String ACTION_CLEANUP_ON_QUOTA_HIT_PERCENTAGE = "action.cleanup.onQuotaHit.percent";
/**
* Switch to enable/disable the multi-assignment feature.
*/
public static final String MULTI_ASSIGNMENTS_ENABLED = "multi.assignments.enabled";
/** /**
* Switch to enable/disable the batch-assignment feature. * Switch to enable/disable the batch-assignment feature.
*/ */

View File

@@ -24,8 +24,6 @@ import org.eclipse.hawkbit.repository.event.remote.DistributionSetDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.DistributionSetTagDeletedEvent; import org.eclipse.hawkbit.repository.event.remote.DistributionSetTagDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.DistributionSetTypeDeletedEvent; import org.eclipse.hawkbit.repository.event.remote.DistributionSetTypeDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.DownloadProgressEvent; import org.eclipse.hawkbit.repository.event.remote.DownloadProgressEvent;
import org.eclipse.hawkbit.repository.event.remote.MultiActionAssignEvent;
import org.eclipse.hawkbit.repository.event.remote.MultiActionCancelEvent;
import org.eclipse.hawkbit.repository.event.remote.RolloutDeletedEvent; import org.eclipse.hawkbit.repository.event.remote.RolloutDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.RolloutGroupDeletedEvent; import org.eclipse.hawkbit.repository.event.remote.RolloutGroupDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.RolloutStoppedEvent; import org.eclipse.hawkbit.repository.event.remote.RolloutStoppedEvent;
@@ -68,8 +66,6 @@ import org.eclipse.hawkbit.repository.event.remote.entity.TenantConfigurationUpd
import org.eclipse.hawkbit.repository.event.remote.service.ActionCreatedServiceEvent; import org.eclipse.hawkbit.repository.event.remote.service.ActionCreatedServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.ActionUpdatedServiceEvent; import org.eclipse.hawkbit.repository.event.remote.service.ActionUpdatedServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.CancelTargetAssignmentServiceEvent; 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.TargetAssignDistributionSetServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.TargetAttributesRequestedServiceEvent; import org.eclipse.hawkbit.repository.event.remote.service.TargetAttributesRequestedServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.TargetCreatedServiceEvent; import org.eclipse.hawkbit.repository.event.remote.service.TargetCreatedServiceEvent;
@@ -162,9 +158,6 @@ public class EventType {
// target attributes requested flag // target attributes requested flag
TYPES.put(37, TargetAttributesRequestedEvent.class); TYPES.put(37, TargetAttributesRequestedEvent.class);
// deployment event for assignments and /or cancellations
TYPES.put(38, MultiActionAssignEvent.class);
TYPES.put(39, MultiActionCancelEvent.class);
// tenant configuration // tenant configuration
TYPES.put(40, TenantConfigurationCreatedEvent.class); TYPES.put(40, TenantConfigurationCreatedEvent.class);
@@ -186,8 +179,6 @@ public class EventType {
TYPES.put(1003, TargetAssignDistributionSetServiceEvent.class); TYPES.put(1003, TargetAssignDistributionSetServiceEvent.class);
TYPES.put(1004, TargetAttributesRequestedServiceEvent.class); TYPES.put(1004, TargetAttributesRequestedServiceEvent.class);
TYPES.put(1005, CancelTargetAssignmentServiceEvent.class); TYPES.put(1005, CancelTargetAssignmentServiceEvent.class);
TYPES.put(1006, MultiActionAssignServiceEvent.class);
TYPES.put(1007, MultiActionCancelServiceEvent.class);
TYPES.put(1008, ActionCreatedServiceEvent.class); TYPES.put(1008, ActionCreatedServiceEvent.class);
TYPES.put(1009, ActionUpdatedServiceEvent.class); TYPES.put(1009, ActionUpdatedServiceEvent.class);
} }

View File

@@ -17,8 +17,6 @@ import lombok.NonNull;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.repository.event.remote.AbstractRemoteEvent; import org.eclipse.hawkbit.repository.event.remote.AbstractRemoteEvent;
import org.eclipse.hawkbit.repository.event.remote.CancelTargetAssignmentEvent; 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.TargetAssignDistributionSetEvent;
import org.eclipse.hawkbit.repository.event.remote.TargetAttributesRequestedEvent; import org.eclipse.hawkbit.repository.event.remote.TargetAttributesRequestedEvent;
import org.eclipse.hawkbit.repository.event.remote.TargetDeletedEvent; import org.eclipse.hawkbit.repository.event.remote.TargetDeletedEvent;
@@ -29,8 +27,6 @@ import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent;
import org.eclipse.hawkbit.repository.event.remote.service.ActionCreatedServiceEvent; import org.eclipse.hawkbit.repository.event.remote.service.ActionCreatedServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.ActionUpdatedServiceEvent; import org.eclipse.hawkbit.repository.event.remote.service.ActionUpdatedServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.CancelTargetAssignmentServiceEvent; 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.TargetAssignDistributionSetServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.TargetAttributesRequestedServiceEvent; import org.eclipse.hawkbit.repository.event.remote.service.TargetAttributesRequestedServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.TargetCreatedServiceEvent; import org.eclipse.hawkbit.repository.event.remote.service.TargetCreatedServiceEvent;
@@ -77,8 +73,6 @@ public final class EventPublisherHolder {
TargetAssignDistributionSetEvent.class, TargetAssignDistributionSetEvent.class,
CancelTargetAssignmentEvent.class, CancelTargetAssignmentEvent.class,
TargetAttributesRequestedEvent.class, TargetAttributesRequestedEvent.class,
MultiActionAssignEvent.class,
MultiActionCancelEvent.class,
ActionCreatedEvent.class, ActionCreatedEvent.class,
ActionUpdatedEvent.class ActionUpdatedEvent.class
); );
@@ -183,10 +177,6 @@ public final class EventPublisherHolder {
return new CancelTargetAssignmentServiceEvent(cancelTargetAssignmentEvent); return new CancelTargetAssignmentServiceEvent(cancelTargetAssignmentEvent);
} else if (event instanceof TargetAttributesRequestedEvent targetAttributesRequestedEvent) { } else if (event instanceof TargetAttributesRequestedEvent targetAttributesRequestedEvent) {
return new TargetAttributesRequestedServiceEvent(targetAttributesRequestedEvent); return new TargetAttributesRequestedServiceEvent(targetAttributesRequestedEvent);
} else if (event instanceof MultiActionAssignEvent multiActionAssignEvent) {
return new MultiActionAssignServiceEvent(multiActionAssignEvent);
} else if (event instanceof MultiActionCancelEvent multiActionCancelEvent) {
return new MultiActionCancelServiceEvent(multiActionCancelEvent);
} else if (event instanceof ActionCreatedEvent actionCreatedEvent) { } else if (event instanceof ActionCreatedEvent actionCreatedEvent) {
return new ActionCreatedServiceEvent(actionCreatedEvent); return new ActionCreatedServiceEvent(actionCreatedEvent);
} else if (event instanceof ActionUpdatedEvent actionUpdatedEvent) { } else if (event instanceof ActionUpdatedEvent actionUpdatedEvent) {

View File

@@ -85,10 +85,6 @@ hawkbit.server.tenant.configuration.action-cleanup-on-quota-hit-percent.keyName=
hawkbit.server.tenant.configuration.action-cleanup-on-quota-hit-percent.defaultValue=0 hawkbit.server.tenant.configuration.action-cleanup-on-quota-hit-percent.defaultValue=0
hawkbit.server.tenant.configuration.action-cleanup-on-quota-hit-percent.dataType=java.lang.Integer hawkbit.server.tenant.configuration.action-cleanup-on-quota-hit-percent.dataType=java.lang.Integer
hawkbit.server.tenant.configuration.multi-assignments-enabled.keyName=multi.assignments.enabled
hawkbit.server.tenant.configuration.multi-assignments-enabled.defaultValue=false
hawkbit.server.tenant.configuration.multi-assignments-enabled.dataType=java.lang.Boolean
hawkbit.server.tenant.configuration.batch-assignments-enabled.keyName=batch.assignments.enabled hawkbit.server.tenant.configuration.batch-assignments-enabled.keyName=batch.assignments.enabled
hawkbit.server.tenant.configuration.batch-assignments-enabled.defaultValue=false hawkbit.server.tenant.configuration.batch-assignments-enabled.defaultValue=false
hawkbit.server.tenant.configuration.batch-assignments-enabled.dataType=java.lang.Boolean hawkbit.server.tenant.configuration.batch-assignments-enabled.dataType=java.lang.Boolean

View File

@@ -65,7 +65,6 @@ public abstract class AbstractDsAssignmentStrategy {
private final ActionStatusRepository actionStatusRepository; private final ActionStatusRepository actionStatusRepository;
private final QuotaManagement quotaManagement; private final QuotaManagement quotaManagement;
private final BooleanSupplier multiAssignmentsConfig;
private final BooleanSupplier confirmationFlowConfig; private final BooleanSupplier confirmationFlowConfig;
private final RepositoryProperties repositoryProperties; private final RepositoryProperties repositoryProperties;
private final Consumer<MaxAssignmentsExceededInfo> maxAssignmentExceededHandler; private final Consumer<MaxAssignmentsExceededInfo> maxAssignmentExceededHandler;
@@ -74,14 +73,13 @@ public abstract class AbstractDsAssignmentStrategy {
AbstractDsAssignmentStrategy( AbstractDsAssignmentStrategy(
final TargetRepository targetRepository, final TargetRepository targetRepository,
final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository, final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository,
final QuotaManagement quotaManagement, final BooleanSupplier multiAssignmentsConfig, final QuotaManagement quotaManagement, final BooleanSupplier confirmationFlowConfig,
final BooleanSupplier confirmationFlowConfig, final RepositoryProperties repositoryProperties, final RepositoryProperties repositoryProperties,
final Consumer<MaxAssignmentsExceededInfo> maxAssignmentExceededHandler) { final Consumer<MaxAssignmentsExceededInfo> maxAssignmentExceededHandler) {
this.targetRepository = targetRepository; this.targetRepository = targetRepository;
this.actionRepository = actionRepository; this.actionRepository = actionRepository;
this.actionStatusRepository = actionStatusRepository; this.actionStatusRepository = actionStatusRepository;
this.quotaManagement = quotaManagement; this.quotaManagement = quotaManagement;
this.multiAssignmentsConfig = multiAssignmentsConfig;
this.confirmationFlowConfig = confirmationFlowConfig; this.confirmationFlowConfig = confirmationFlowConfig;
this.repositoryProperties = repositoryProperties; this.repositoryProperties = repositoryProperties;
this.maxAssignmentExceededHandler = maxAssignmentExceededHandler; this.maxAssignmentExceededHandler = maxAssignmentExceededHandler;
@@ -211,10 +209,6 @@ public abstract class AbstractDsAssignmentStrategy {
afterCommit(() -> EventPublisherHolder.getInstance().getEventPublisher().publishEvent(new CancelTargetAssignmentEvent(action))); afterCommit(() -> EventPublisherHolder.getInstance().getEventPublisher().publishEvent(new CancelTargetAssignmentEvent(action)));
} }
protected boolean isMultiAssignmentsEnabled() {
return multiAssignmentsConfig.getAsBoolean();
}
protected boolean isConfirmationFlowEnabled() { protected boolean isConfirmationFlowEnabled() {
return confirmationFlowConfig.getAsBoolean(); return confirmationFlowConfig.getAsBoolean();
} }

View File

@@ -52,7 +52,7 @@ import org.eclipse.hawkbit.repository.exception.ForceQuitActionNotAllowedExcepti
import org.eclipse.hawkbit.repository.exception.IncompatibleTargetTypeException; import org.eclipse.hawkbit.repository.exception.IncompatibleTargetTypeException;
import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException; import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException;
import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException; import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException;
import org.eclipse.hawkbit.repository.exception.MultiAssignmentIsNotEnabledException; import org.eclipse.hawkbit.repository.exception.MultiAssignmentException;
import org.eclipse.hawkbit.repository.helper.TenantConfigHelper; import org.eclipse.hawkbit.repository.helper.TenantConfigHelper;
import org.eclipse.hawkbit.repository.jpa.Jpa; import org.eclipse.hawkbit.repository.jpa.Jpa;
import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper; import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper;
@@ -159,10 +159,10 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
maxAssignmentsExceededInfo.requested, maxAssignmentsExceededInfo.requested,
maxAssignmentsExceededInfo.quotaExceededException); maxAssignmentsExceededInfo.quotaExceededException);
onlineDsAssignmentStrategy = new OnlineDsAssignmentStrategy(targetRepository, actionRepository, actionStatusRepository, onlineDsAssignmentStrategy = new OnlineDsAssignmentStrategy(targetRepository, actionRepository, actionStatusRepository,
quotaManagement, this::isMultiAssignmentsEnabled, this::isConfirmationFlowEnabled, repositoryProperties, quotaManagement, this::isConfirmationFlowEnabled, repositoryProperties,
maxAssignmentsExceededHandler); maxAssignmentsExceededHandler);
offlineDsAssignmentStrategy = new OfflineDsAssignmentStrategy(targetRepository, actionRepository, actionStatusRepository, offlineDsAssignmentStrategy = new OfflineDsAssignmentStrategy(targetRepository, actionRepository, actionStatusRepository,
quotaManagement, this::isMultiAssignmentsEnabled, this::isConfirmationFlowEnabled, repositoryProperties, quotaManagement, this::isConfirmationFlowEnabled, repositoryProperties,
maxAssignmentsExceededHandler); maxAssignmentsExceededHandler);
} }
@@ -423,16 +423,12 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY)) backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void cancelInactiveScheduledActionsForTargets(final List<Long> targetIds) { public void cancelInactiveScheduledActionsForTargets(final List<Long> targetIds) {
if (!isMultiAssignmentsEnabled()) {
targetRepository.getAccessController().ifPresent(v -> { targetRepository.getAccessController().ifPresent(v -> {
if (targetRepository.count(AccessController.Operation.UPDATE, TargetSpecifications.hasIdIn(targetIds)) != targetIds.size()) { if (targetRepository.count(AccessController.Operation.UPDATE, TargetSpecifications.hasIdIn(targetIds)) != targetIds.size()) {
throw new EntityNotFoundException(Target.class, targetIds); throw new EntityNotFoundException(Target.class, targetIds);
} }
}); });
actionRepository.switchStatus(Status.CANCELED, targetIds, false, Status.SCHEDULED); actionRepository.switchStatus(Status.CANCELED, targetIds, false, Status.SCHEDULED);
} else {
log.debug("The Multi Assignments feature is enabled: No need to cancel inactive scheduled actions.");
}
} }
@Override @Override
@@ -668,12 +664,10 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
} }
private void checkForMultiAssignment(final Collection<DeploymentRequest> deploymentRequests) { private void checkForMultiAssignment(final Collection<DeploymentRequest> deploymentRequests) {
if (!isMultiAssignmentsEnabled()) {
final long distinctTargetsInRequest = deploymentRequests.stream() final long distinctTargetsInRequest = deploymentRequests.stream()
.map(request -> request.getTargetWithActionType().getControllerId()).distinct().count(); .map(request -> request.getTargetWithActionType().getControllerId()).distinct().count();
if (distinctTargetsInRequest < deploymentRequests.size()) { if (distinctTargetsInRequest < deploymentRequests.size()) {
throw new MultiAssignmentIsNotEnabledException(); throw new MultiAssignmentException();
}
} }
} }
@@ -819,9 +813,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
final List<JpaTarget> targetEntities) { final List<JpaTarget> targetEntities) {
final List<List<Long>> targetEntitiesIdsChunks = getTargetEntitiesAsChunks(targetEntities); final List<List<Long>> targetEntitiesIdsChunks = getTargetEntitiesAsChunks(targetEntities);
if (!isMultiAssignmentsEnabled()) {
closeOrCancelActiveActions(assignmentStrategy, targetEntitiesIdsChunks); closeOrCancelActiveActions(assignmentStrategy, targetEntitiesIdsChunks);
}
// cancel all scheduled actions which are in-active, these actions were // cancel all scheduled actions which are in-active, these actions were
// not active before and the manual assignment which has been done cancels them // not active before and the manual assignment which has been done cancels them
targetEntitiesIdsChunks.forEach(this::cancelInactiveScheduledActionsForTargets); targetEntitiesIdsChunks.forEach(this::cancelInactiveScheduledActionsForTargets);
@@ -944,10 +936,6 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
} }
private JpaAction closeActionIfSetWasAlreadyAssigned(final JpaAction action) { private JpaAction closeActionIfSetWasAlreadyAssigned(final JpaAction action) {
if (isMultiAssignmentsEnabled()) {
return action;
}
final JpaTarget target = action.getTarget(); final JpaTarget target = action.getTarget();
if (target.getAssignedDistributionSet() != null && action.getDistributionSet().getId() if (target.getAssignedDistributionSet() != null && action.getDistributionSet().getId()
.equals(target.getAssignedDistributionSet().getId())) { .equals(target.getAssignedDistributionSet().getId())) {
@@ -965,9 +953,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
} }
private List<Action> startScheduledActionsAndHandleOpenCancellationFirst(final List<JpaAction> actions) { private List<Action> startScheduledActionsAndHandleOpenCancellationFirst(final List<JpaAction> actions) {
if (!isMultiAssignmentsEnabled()) {
closeOrCancelOpenDeviceActions(actions); closeOrCancelOpenDeviceActions(actions);
}
final List<JpaAction> savedActions = activateActionsOfRolloutGroup(actions); final List<JpaAction> savedActions = activateActionsOfRolloutGroup(actions);
setInitialActionStatusOfRolloutGroup(savedActions); setInitialActionStatusOfRolloutGroup(savedActions);
setAssignmentOnTargets(savedActions); setAssignmentOnTargets(savedActions);
@@ -1017,10 +1003,6 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
actionStatusRepository.save(actionStatus); actionStatusRepository.save(actionStatus);
} }
private boolean isMultiAssignmentsEnabled() {
return TenantConfigHelper.isMultiAssignmentsEnabled();
}
private boolean isConfirmationFlowEnabled() { private boolean isConfirmationFlowEnabled() {
return TenantConfigHelper.isUserConfirmationFlowEnabled(); return TenantConfigHelper.isUserConfirmationFlowEnabled();
} }

View File

@@ -167,7 +167,7 @@ public class JpaRolloutManagement implements RolloutManagement {
this.repositoryProperties = repositoryProperties; this.repositoryProperties = repositoryProperties;
onlineDsAssignmentStrategy = new OnlineDsAssignmentStrategy(targetRepository, actionRepository, actionStatusRepository, onlineDsAssignmentStrategy = new OnlineDsAssignmentStrategy(targetRepository, actionRepository, actionStatusRepository,
quotaManagement, this::isMultiAssignmentsEnabled, this::isConfirmationFlowEnabled, repositoryProperties, null); quotaManagement, this::isConfirmationFlowEnabled, repositoryProperties, null);
} }
@Autowired @Autowired
@@ -554,7 +554,7 @@ public class JpaRolloutManagement implements RolloutManagement {
storeActionsAndStatuses(actions, Action.Status.CANCELING); storeActionsAndStatuses(actions, Action.Status.CANCELING);
// send cancellation messages to event publisher // send cancellation messages to event publisher
onlineDsAssignmentStrategy.sendCancellationMessages(actions, AccessContext.tenant()); onlineDsAssignmentStrategy.sendCancellationMessages(actions);
} }
private void forceQuitActionsOfRollout(final Rollout rollout) { private void forceQuitActionsOfRollout(final Rollout rollout) {
@@ -1033,10 +1033,6 @@ public class JpaRolloutManagement implements RolloutManagement {
return new TargetCount(totalTargets, baseFilter); return new TargetCount(totalTargets, baseFilter);
} }
private boolean isMultiAssignmentsEnabled() {
return TenantConfigHelper.isMultiAssignmentsEnabled();
}
private boolean isConfirmationFlowEnabled() { private boolean isConfirmationFlowEnabled() {
return TenantConfigHelper.isUserConfirmationFlowEnabled(); return TenantConfigHelper.isUserConfirmationFlowEnabled();
} }

View File

@@ -11,8 +11,6 @@ package org.eclipse.hawkbit.repository.jpa.management;
import static org.eclipse.hawkbit.auth.SpPermission.READ_GATEWAY_SECURITY_TOKEN; import static org.eclipse.hawkbit.auth.SpPermission.READ_GATEWAY_SECURITY_TOKEN;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY; import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.BATCH_ASSIGNMENTS_ENABLED;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.MULTI_ASSIGNMENTS_ENABLED;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.POLLING_TIME; import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.POLLING_TIME;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.REPOSITORY_ACTIONS_AUTOCLOSE_ENABLED; import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.REPOSITORY_ACTIONS_AUTOCLOSE_ENABLED;
@@ -207,7 +205,6 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
tenantConfiguration.setValue(valueToString); tenantConfiguration.setValue(valueToString);
} }
assertValueChangeIsAllowed(e.getKey(), tenantConfiguration);
return tenantConfiguration; return tenantConfiguration;
}).toList()); }).toList());
} }
@@ -273,58 +270,6 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
return CONVERSION_SERVICE.convert(key.getDefaultValue(), propertyType); return CONVERSION_SERVICE.convert(key.getDefaultValue(), propertyType);
} }
/**
* Asserts that the requested configuration value change is allowed. Throws a {@link TenantConfigurationValueChangeNotAllowedException}
* otherwise.
*
* @param key The configuration key.
* @param valueChange The configuration to be validated.
* @throws TenantConfigurationValueChangeNotAllowedException if the requested configuration change is not allowed.
*/
private void assertValueChangeIsAllowed(final String key, final JpaTenantConfiguration valueChange) {
assertMultiAssignmentsValueChange(key, valueChange);
assertAutoCloseValueChange(key);
assertBatchAssignmentValueChange(key, valueChange);
}
private void assertMultiAssignmentsValueChange(final String key, final JpaTenantConfiguration valueChange) {
if (MULTI_ASSIGNMENTS_ENABLED.equals(key) && !Boolean.parseBoolean(valueChange.getValue())) {
log.debug("The Multi-Assignments '{}' feature cannot be disabled.", key);
throw new TenantConfigurationValueChangeNotAllowedException();
}
if (MULTI_ASSIGNMENTS_ENABLED.equals(key) && Boolean.parseBoolean(valueChange.getValue())) {
JpaTenantConfiguration batchConfig = tenantConfigurationRepository.findByKey(BATCH_ASSIGNMENTS_ENABLED);
if (batchConfig != null && Boolean.parseBoolean(batchConfig.getValue())) {
log.debug(
"The Multi-Assignments '{}' feature cannot be enabled as it contradicts with the Batch-Assignments feature, which is already enabled .",
key);
throw new TenantConfigurationValueChangeNotAllowedException();
}
}
}
private void assertAutoCloseValueChange(final String key) {
if (REPOSITORY_ACTIONS_AUTOCLOSE_ENABLED.equals(key)
&& Boolean.TRUE.equals(Optional.ofNullable(getConfigurationValue0(MULTI_ASSIGNMENTS_ENABLED, Boolean.class))
.map(TenantConfigurationValue::getValue)
.orElse(null))) {
log.debug("The property '{}' must not be changed because the Multi-Assignments feature is currently enabled.", key);
throw new TenantConfigurationValueChangeNotAllowedException();
}
}
private void assertBatchAssignmentValueChange(final String key, final JpaTenantConfiguration valueChange) {
if (BATCH_ASSIGNMENTS_ENABLED.equals(key) && Boolean.parseBoolean(valueChange.getValue())) {
final JpaTenantConfiguration multiConfig = tenantConfigurationRepository.findByKey(MULTI_ASSIGNMENTS_ENABLED);
if (multiConfig != null && Boolean.parseBoolean(multiConfig.getValue())) {
log.debug(
"The Batch-Assignments '{}' feature cannot be enabled as it contradicts with the Multi-Assignments feature, which is already enabled .",
key);
throw new TenantConfigurationValueChangeNotAllowedException();
}
}
}
private static PollStatus pollStatus(final long lastTargetQuery, final PollingInterval pollingInterval, final Duration pollingOverdueTime) { private static PollStatus pollStatus(final long lastTargetQuery, final PollingInterval pollingInterval, final Duration pollingOverdueTime) {
final LocalDateTime currentDate = LocalDateTime.now(); final LocalDateTime currentDate = LocalDateTime.now();
final LocalDateTime lastPollDate = LocalDateTime.ofInstant(Instant.ofEpochMilli(lastTargetQuery), ZoneId.systemDefault()); final LocalDateTime lastPollDate = LocalDateTime.ofInstant(Instant.ofEpochMilli(lastTargetQuery), ZoneId.systemDefault());

View File

@@ -46,11 +46,11 @@ class OfflineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
OfflineDsAssignmentStrategy( OfflineDsAssignmentStrategy(
final TargetRepository targetRepository, final TargetRepository targetRepository,
final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository, final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository,
final QuotaManagement quotaManagement, final BooleanSupplier multiAssignmentsConfig, final QuotaManagement quotaManagement,
final BooleanSupplier confirmationFlowConfig, final RepositoryProperties repositoryProperties, final BooleanSupplier confirmationFlowConfig, final RepositoryProperties repositoryProperties,
final Consumer<MaxAssignmentsExceededInfo> maxAssignmentExceededHandler) { final Consumer<MaxAssignmentsExceededInfo> maxAssignmentExceededHandler) {
super(targetRepository, actionRepository, actionStatusRepository, super(targetRepository, actionRepository, actionStatusRepository,
quotaManagement, multiAssignmentsConfig, confirmationFlowConfig, repositoryProperties, maxAssignmentExceededHandler); quotaManagement, confirmationFlowConfig, repositoryProperties, maxAssignmentExceededHandler);
} }
@Override @Override
@@ -74,14 +74,11 @@ class OfflineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
@Override @Override
public List<JpaTarget> findTargetsForAssignment(final List<String> controllerIDs, final long setId) { public List<JpaTarget> findTargetsForAssignment(final List<String> controllerIDs, final long setId) {
final Function<List<String>, List<JpaTarget>> mapper; final Function<List<String>, List<JpaTarget>> mapper =
if (isMultiAssignmentsEnabled()) { ids -> targetRepository.findAll(JpaManagementHelper.combineWithAnd(List.of(
mapper = ids -> targetRepository.findAll(TargetSpecifications.hasControllerIdIn(ids));
} else {
mapper = ids -> targetRepository.findAll(JpaManagementHelper.combineWithAnd(List.of(
TargetSpecifications.hasControllerIdAndAssignedDistributionSetIdNot(ids, setId), TargetSpecifications.hasControllerIdAndAssignedDistributionSetIdNot(ids, setId),
TargetSpecifications.notEqualToTargetUpdateStatus(TargetUpdateStatus.PENDING)))); TargetSpecifications.notEqualToTargetUpdateStatus(TargetUpdateStatus.PENDING))));
}
return ListUtils.partition(controllerIDs, Constants.MAX_ENTRIES_IN_STATEMENT).stream().map(mapper).flatMap(List::stream).toList(); return ListUtils.partition(controllerIDs, Constants.MAX_ENTRIES_IN_STATEMENT).stream().map(mapper).flatMap(List::stream).toList();
} }

View File

@@ -23,8 +23,6 @@ import org.eclipse.hawkbit.context.AccessContext;
import org.eclipse.hawkbit.repository.QuotaManagement; import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.RepositoryProperties; import org.eclipse.hawkbit.repository.RepositoryProperties;
import org.eclipse.hawkbit.repository.event.EventPublisherHolder; import org.eclipse.hawkbit.repository.event.EventPublisherHolder;
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.TargetAssignDistributionSetEvent;
import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException; import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException;
import org.eclipse.hawkbit.repository.jpa.acm.AccessController; import org.eclipse.hawkbit.repository.jpa.acm.AccessController;
@@ -54,19 +52,14 @@ class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
OnlineDsAssignmentStrategy( OnlineDsAssignmentStrategy(
final TargetRepository targetRepository, final TargetRepository targetRepository,
final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository, final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository,
final QuotaManagement quotaManagement, final BooleanSupplier multiAssignmentsConfig, final QuotaManagement quotaManagement,
final BooleanSupplier confirmationFlowConfig, final RepositoryProperties repositoryProperties, final BooleanSupplier confirmationFlowConfig, final RepositoryProperties repositoryProperties,
final Consumer<MaxAssignmentsExceededInfo> maxAssignmentExceededHandler) { final Consumer<MaxAssignmentsExceededInfo> maxAssignmentExceededHandler) {
super(targetRepository, actionRepository, actionStatusRepository, super(targetRepository, actionRepository, actionStatusRepository,
quotaManagement, multiAssignmentsConfig, confirmationFlowConfig, repositoryProperties, maxAssignmentExceededHandler); quotaManagement, confirmationFlowConfig, repositoryProperties, maxAssignmentExceededHandler);
} }
public void sendDeploymentEvents(final long distributionSetId, final List<Action> actions) { public void sendDeploymentEvents(final long distributionSetId, final List<Action> actions) {
if (isMultiAssignmentsEnabled()) {
sendDeploymentEvent(actions);
return;
}
final List<Action> filteredActions = getActionsWithoutCancellations(actions); final List<Action> filteredActions = getActionsWithoutCancellations(actions);
if (filteredActions.isEmpty()) { if (filteredActions.isEmpty()) {
return; return;
@@ -106,13 +99,9 @@ class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
@Override @Override
public List<JpaTarget> findTargetsForAssignment(final List<String> controllerIDs, final long setId) { public List<JpaTarget> findTargetsForAssignment(final List<String> controllerIDs, final long setId) {
final Function<List<String>, List<JpaTarget>> mapper; final Function<List<String>, List<JpaTarget>> mapper =
if (isMultiAssignmentsEnabled()) { ids -> targetRepository.findAll(
mapper = ids -> targetRepository.findAll(TargetSpecifications.hasControllerIdIn(ids)); TargetSpecifications.hasControllerIdAndAssignedDistributionSetIdNot(ids, setId));
} else {
mapper = ids -> targetRepository
.findAll(TargetSpecifications.hasControllerIdAndAssignedDistributionSetIdNot(ids, setId));
}
return ListUtils.partition(controllerIDs, Constants.MAX_ENTRIES_IN_STATEMENT).stream().map(mapper) return ListUtils.partition(controllerIDs, Constants.MAX_ENTRIES_IN_STATEMENT).stream().map(mapper)
.flatMap(List::stream).toList(); .flatMap(List::stream).toList();
} }
@@ -163,28 +152,16 @@ class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
@Override @Override
public void sendDeploymentEvents(final List<DistributionSetAssignmentResult> assignmentResults) { public void sendDeploymentEvents(final List<DistributionSetAssignmentResult> assignmentResults) {
if (isMultiAssignmentsEnabled()) {
sendDeploymentEvent(assignmentResults.stream().flatMap(result -> result.getAssignedEntity().stream()).toList());
} else {
assignmentResults.forEach(this::sendDistributionSetAssignedEvent); assignmentResults.forEach(this::sendDistributionSetAssignedEvent);
} }
}
void sendCancellationMessage(final JpaAction action) { void sendCancellationMessage(final JpaAction action) {
if (isMultiAssignmentsEnabled()) {
sendMultiActionCancelEvent(action);
} else {
cancelAssignDistributionSetEvent(action); cancelAssignDistributionSetEvent(action);
} }
}
void sendCancellationMessages(final List<JpaAction> actions, final String tenant) { void sendCancellationMessages(final List<JpaAction> actions) {
if (isMultiAssignmentsEnabled()) {
sendMultiActionCancelEvent(tenant, Collections.unmodifiableList(actions));
} else {
actions.forEach(this::cancelAssignDistributionSetEvent); actions.forEach(this::cancelAssignDistributionSetEvent);
} }
}
private static Stream<Action> filterCancellations(final List<Action> actions) { private static Stream<Action> filterCancellations(final List<Action> actions) {
return actions.stream().filter(action -> { return actions.stream().filter(action -> {
@@ -200,19 +177,6 @@ class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
return filterCancellations(actions).toList(); return filterCancellations(actions).toList();
} }
private void sendMultiActionCancelEvent(final Action action) {
sendMultiActionCancelEvent(action.getTenant(), Collections.singletonList(action));
}
private void sendDeploymentEvent(final List<Action> actions) {
final List<Action> filteredActions = getActionsWithoutCancellations(actions);
if (filteredActions.isEmpty()) {
return;
}
final String tenant = filteredActions.get(0).getTenant();
sendMultiActionAssignEvent(tenant, filteredActions);
}
private void sendDistributionSetAssignedEvent(final DistributionSetAssignmentResult assignmentResult) { private void sendDistributionSetAssignedEvent(final DistributionSetAssignmentResult assignmentResult) {
final List<Action> filteredActions = filterCancellations(assignmentResult.getAssignedEntity()).toList(); final List<Action> filteredActions = filterCancellations(assignmentResult.getAssignedEntity()).toList();
final DistributionSet set = assignmentResult.getDistributionSet(); final DistributionSet set = assignmentResult.getDistributionSet();
@@ -228,25 +192,4 @@ class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
afterCommit(() -> EventPublisherHolder.getInstance().getEventPublisher().publishEvent( afterCommit(() -> EventPublisherHolder.getInstance().getEventPublisher().publishEvent(
new TargetAssignDistributionSetEvent(tenant, distributionSetId, actions, actions.get(0).isMaintenanceWindowAvailable()))); new TargetAssignDistributionSetEvent(tenant, distributionSetId, actions, actions.get(0).isMaintenanceWindowAvailable())));
} }
/**
* Helper to fire a {@link MultiActionCancelEvent}. This method may only be
* called if the Multi-Assignments feature is enabled.
*
* @param tenant the event is scoped to
* @param actions assigned to the targets
*/
private void sendMultiActionCancelEvent(final String tenant, final List<Action> actions) {
afterCommit(() -> EventPublisherHolder.getInstance().getEventPublisher().publishEvent(new MultiActionCancelEvent(tenant, actions)));
}
/**
* Helper to fire a {@link MultiActionAssignEvent}. This method may only be called if the Multi-Assignments feature is enabled.
*
* @param tenant the event is scoped to
* @param actions assigned to the targets
*/
private void sendMultiActionAssignEvent(final String tenant, final List<Action> actions) {
afterCommit(() -> EventPublisherHolder.getInstance().getEventPublisher().publishEvent(new MultiActionAssignEvent(tenant, actions)));
}
} }

View File

@@ -84,8 +84,7 @@ public final class WeightValidationHelper {
public static void validateWeight(final boolean hasWeight, final boolean hasNoWeight) { public static void validateWeight(final boolean hasWeight, final boolean hasNoWeight) {
// remove bypassing the weight enforcement as soon as weight can be set via UI // remove bypassing the weight enforcement as soon as weight can be set via UI
final boolean bypassWeightEnforcement = true; final boolean bypassWeightEnforcement = true;
final boolean multiAssignmentsEnabled = TenantConfigHelper.isMultiAssignmentsEnabled(); if (!bypassWeightEnforcement && hasNoWeight) {
if (!bypassWeightEnforcement && multiAssignmentsEnabled && hasNoWeight) {
throw new NoWeightProvidedInMultiAssignmentModeException(); throw new NoWeightProvidedInMultiAssignmentModeException();
} }
} }

View File

@@ -30,44 +30,6 @@ class RemoteTenantEventTest extends AbstractRemoteEventTest {
private static final String TENANT_DEFAULT = "DEFAULT"; private static final String TENANT_DEFAULT = "DEFAULT";
/**
* Verifies that a testMultiActionAssignEvent can be properly serialized and deserialized
*/
@Test
void testMultiActionAssignEvent() {
final List<String> controllerIds = List.of("id0", "id1", "id2", "id3", "id4loooooooooooooooooooooooooooooooooooonnnnnnnnnnnnnnnnnng");
final List<Action> actions = controllerIds.stream().map(this::createAction).toList();
final MultiActionAssignEvent assignEvent = new MultiActionAssignEvent(TENANT_DEFAULT, actions);
final MultiActionAssignEvent remoteAssignEventProtoStuff = createProtoStuffEvent(assignEvent);
assertThat(assignEvent).isEqualTo(remoteAssignEventProtoStuff);
assertThat(remoteAssignEventProtoStuff.getControllerIds()).containsExactlyElementsOf(controllerIds);
final MultiActionAssignEvent remoteAssignEventJackson = createJacksonEvent(assignEvent);
assertThat(assignEvent).isEqualTo(remoteAssignEventJackson);
assertThat(remoteAssignEventJackson.getControllerIds()).containsExactlyElementsOf(controllerIds);
}
/**
* Verifies that a MultiActionCancelEvent can be properly serialized and deserialized
*/
@Test
void testMultiActionCancelEvent() {
final List<String> controllerIds = List.of("id0", "id1", "id2", "id3", "id4loooooooooooooooooooooooooooooooooooonnnnnnnnnnnnnnnnnng");
final List<Action> actions = controllerIds.stream().map(this::createAction).toList();
final MultiActionCancelEvent cancelEvent = new MultiActionCancelEvent(TENANT_DEFAULT, actions);
final MultiActionCancelEvent remoteCancelEventProtoStuff = createProtoStuffEvent(cancelEvent);
assertThat(cancelEvent).isEqualTo(remoteCancelEventProtoStuff);
assertThat(remoteCancelEventProtoStuff.getControllerIds()).containsExactlyElementsOf(controllerIds);
final MultiActionCancelEvent remoteCancelEventJackson = createJacksonEvent(cancelEvent);
assertThat(cancelEvent).isEqualTo(remoteCancelEventJackson);
assertThat(remoteCancelEventJackson.getControllerIds()).containsExactlyElementsOf(controllerIds);
}
/** /**
* Verifies that a DownloadProgressEvent can be properly serialized and deserialized * Verifies that a DownloadProgressEvent can be properly serialized and deserialized
*/ */
@@ -135,15 +97,6 @@ class RemoteTenantEventTest extends AbstractRemoteEventTest {
assertCancelTargetAssignmentEvent(action, remoteEventJackson); assertCancelTargetAssignmentEvent(action, remoteEventJackson);
} }
private Action createAction(final String controllerId) {
final JpaAction generateAction = new JpaAction();
generateAction.setId(1L);
generateAction.setActionType(ActionType.FORCED);
generateAction.setTarget(testdataFactory.createTarget(controllerId));
generateAction.setStatus(Status.RUNNING);
return generateAction;
}
private void assertTargetAssignDistributionSetEvent(final Action action, final TargetAssignDistributionSetEvent underTest) { private void assertTargetAssignDistributionSetEvent(final Action action, final TargetAssignDistributionSetEvent underTest) {
assertThat(underTest.getActions()).hasSize(1); assertThat(underTest.getActions()).hasSize(1);
final ActionProperties actionProperties = underTest.getActions().get(action.getTarget().getControllerId()); final ActionProperties actionProperties = underTest.getActions().get(action.getTarget().getControllerId());

View File

@@ -16,7 +16,6 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import java.util.List;
import java.util.Set; import java.util.Set;
import org.eclipse.hawkbit.repository.event.EventPublisherHolder; import org.eclipse.hawkbit.repository.event.EventPublisherHolder;
@@ -25,7 +24,6 @@ import org.eclipse.hawkbit.repository.event.remote.entity.ActionUpdatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent; 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.TargetUpdatedEvent;
import org.eclipse.hawkbit.repository.event.remote.service.CancelTargetAssignmentServiceEvent; 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.TargetAssignDistributionSetServiceEvent; 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.TargetAttributesRequestedServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.TargetCreatedServiceEvent; import org.eclipse.hawkbit.repository.event.remote.service.TargetCreatedServiceEvent;
@@ -79,8 +77,6 @@ class ServiceEventsTest {
TargetAssignDistributionSetEvent.class, TargetAssignDistributionSetEvent.class,
CancelTargetAssignmentEvent.class, CancelTargetAssignmentEvent.class,
TargetAttributesRequestedEvent.class, TargetAttributesRequestedEvent.class,
MultiActionAssignEvent.class,
MultiActionCancelEvent.class,
ActionCreatedEvent.class, ActionCreatedEvent.class,
ActionUpdatedEvent.class ActionUpdatedEvent.class
); );
@@ -124,16 +120,6 @@ class ServiceEventsTest {
verify(streamBridge).send(eq("group"), any(TargetAttributesRequestedServiceEvent.class)); verify(streamBridge).send(eq("group"), any(TargetAttributesRequestedServiceEvent.class));
} }
@Test
void testProcessingMultiActionAssignmentEventIsSent() {
MultiActionAssignEvent event = new MultiActionAssignEvent("testtenant", List.of(mockAction()));
publisher.publishEvent(event);
verify(streamBridge).send("fanout", event);
verify(streamBridge).send(eq("group"), any(MultiActionAssignServiceEvent.class));
}
@Test @Test
void testCancelTargetAssignmentEventIsSent() { void testCancelTargetAssignmentEventIsSent() {
CancelTargetAssignmentEvent event = new CancelTargetAssignmentEvent(mockAction()); CancelTargetAssignmentEvent event = new CancelTargetAssignmentEvent(mockAction());

View File

@@ -64,37 +64,6 @@ class ConfirmationManagementTest extends AbstractJpaIntegrationTest {
.allMatch(action -> action.getStatus() == Status.WAIT_FOR_CONFIRMATION); .allMatch(action -> action.getStatus() == Status.WAIT_FOR_CONFIRMATION);
} }
/**
* Verify 'findActiveActionsWaitingConfirmation' method is filtering like expected with multi assignment active
*/
@Test
void retrieveActionsWithConfirmationStateInMultiAssignment() {
enableMultiAssignments();
enableConfirmationFlow();
final String controllerId = testdataFactory.createTarget().getControllerId();
final Long dsId = testdataFactory.createDistributionSet().getId();
final List<Action> actions = assignDistributionSet(dsId, controllerId).getAssignedEntity();
assertThat(actions).hasSize(1);
assertThat(confirmationManagement.findActiveActionsWaitingConfirmation(controllerId)).hasSize(1)
.allMatch(action -> action.getStatus() == Status.WAIT_FOR_CONFIRMATION);
final Long dsId2 = testdataFactory.createDistributionSet().getId();
assignDistributionSet(dsId2, controllerId);
assertThat(confirmationManagement.findActiveActionsWaitingConfirmation(controllerId)).hasSize(2)
.allMatch(action -> action.getStatus() == Status.WAIT_FOR_CONFIRMATION);
confirmationManagement.confirmAction(actions.get(0).getId(), null, null);
assertThat(confirmationManagement.findActiveActionsWaitingConfirmation(controllerId)).hasSize(1)
.allMatch(action -> action.getStatus() == Status.WAIT_FOR_CONFIRMATION
&& Objects.equals(action.getDistributionSet().getId(), dsId2));
}
/** /**
* Verify confirming an action will put it to the running state * Verify confirming an action will put it to the running state
*/ */
@@ -191,34 +160,6 @@ class ConfirmationManagementTest extends AbstractJpaIntegrationTest {
.noneMatch(status -> status.getStatus() == Status.RUNNING); .noneMatch(status -> status.getStatus() == Status.RUNNING);
} }
/**
* Verify multiple actions in WFC state will be transferred in RUNNING state in case auto-confirmation is activated.
*/
@Test
void activateAutoConfirmationInMultiAssignment() {
enableMultiAssignments();
enableConfirmationFlow();
final String controllerId = testdataFactory.createTarget().getControllerId();
final Long dsId = testdataFactory.createDistributionSet().getId();
final Long dsId2 = testdataFactory.createDistributionSet().getId();
final List<Action> actions = assignDistributionSets(
Arrays.asList(toDeploymentRequest(controllerId, dsId), toDeploymentRequest(controllerId, dsId2)))
.stream().flatMap(s -> s.getAssignedEntity().stream()).toList();
assertThat(actions).hasSize(2);
assertThat(confirmationManagement.findActiveActionsWaitingConfirmation(controllerId)).hasSize(2)
.allMatch(action -> action.getStatus() == Status.WAIT_FOR_CONFIRMATION);
confirmationManagement.activateAutoConfirmation(controllerId, null, null);
assertThat(confirmationManagement.findActiveActionsWaitingConfirmation(controllerId)).isEmpty();
assertThat(deploymentManagement.findActionsByTarget(controllerId, PAGE).getContent()).hasSize(2)
.allMatch(action -> action.getStatus() == Status.RUNNING);
}
/** /**
* Verify action in WFC state will be transferred in RUNNING state in case auto-confirmation is activated. * Verify action in WFC state will be transferred in RUNNING state in case auto-confirmation is activated.
*/ */
@@ -324,10 +265,6 @@ class ConfirmationManagementTest extends AbstractJpaIntegrationTest {
Arguments.of(null, null)); Arguments.of(null, null));
} }
private static DeploymentRequest toDeploymentRequest(final String controllerId, final Long distributionSetId) {
return DeploymentRequest.builder(controllerId, distributionSetId).confirmationRequired(true).build();
}
private void verifyAutoConfirmationIsDisabled(final String controllerId) { private void verifyAutoConfirmationIsDisabled(final String controllerId) {
assertThat(targetManagement.getWithAutoConfigurationStatus(controllerId).getAutoConfirmationStatus()).isNull(); assertThat(targetManagement.getWithAutoConfigurationStatus(controllerId).getAutoConfirmationStatus()).isNull();
} }

View File

@@ -1582,41 +1582,6 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest {
assertNoActiveActionsExistsForControllerId(DEFAULT_CONTROLLER_ID); assertNoActiveActionsExistsForControllerId(DEFAULT_CONTROLLER_ID);
} }
/**
* Actions are exposed according to thier weight in multi assignment mode.
*/
@Test
void actionsAreExposedAccordingToTheirWeight() {
final String targetId = testdataFactory.createTarget().getControllerId();
final DistributionSet ds = testdataFactory.createDistributionSet();
final Long actionWeightNull = assignDistributionSet(ds.getId(), targetId).getAssignedEntity().get(0).getId();
enableMultiAssignments();
final Long actionWeight500old = assignDistributionSet(ds.getId(), targetId, 500).getAssignedEntity().get(0)
.getId();
final Long actionWeight500new = assignDistributionSet(ds.getId(), targetId, 500).getAssignedEntity().get(0)
.getId();
final Long actionWeight1000 = assignDistributionSet(ds.getId(), targetId, 1000).getAssignedEntity().get(0)
.getId();
assertThat(controllerManagement.findActiveActionWithHighestWeight(targetId).get().getId())
.isEqualTo(actionWeightNull);
controllerManagement
.addUpdateActionStatus(ActionStatusCreate.builder().actionId(actionWeightNull).status(Status.FINISHED).build());
assertThat(controllerManagement.findActiveActionWithHighestWeight(targetId).get().getId())
.isEqualTo(actionWeight1000);
controllerManagement
.addUpdateActionStatus(ActionStatusCreate.builder().actionId(actionWeight1000).status(Status.FINISHED).build());
assertThat(controllerManagement.findActiveActionWithHighestWeight(targetId).get().getId())
.isEqualTo(actionWeight500old);
controllerManagement
.addUpdateActionStatus(ActionStatusCreate.builder().actionId(actionWeight500old).status(Status.FINISHED).build());
assertThat(controllerManagement.findActiveActionWithHighestWeight(targetId).get().getId())
.isEqualTo(actionWeight500new);
controllerManagement
.addUpdateActionStatus(ActionStatusCreate.builder().actionId(actionWeight500new).status(Status.FINISHED).build());
assertThat(controllerManagement.findActiveActionWithHighestWeight(targetId)).isEmpty();
}
/** /**
* Delete a target on requested target deletion from client side * Delete a target on requested target deletion from client side
*/ */

View File

@@ -37,8 +37,6 @@ import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.DistributionSetTagManagement; import org.eclipse.hawkbit.repository.DistributionSetTagManagement;
import org.eclipse.hawkbit.repository.Identifiable; import org.eclipse.hawkbit.repository.Identifiable;
import org.eclipse.hawkbit.repository.event.remote.CancelTargetAssignmentEvent; 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.TargetAssignDistributionSetEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.ActionCreatedEvent; 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.ActionUpdatedEvent;
@@ -50,13 +48,18 @@ 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.TargetUpdatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.TenantConfigurationCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TenantConfigurationCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.TenantConfigurationUpdatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TenantConfigurationUpdatedEvent;
import org.eclipse.hawkbit.repository.event.remote.service.ActionCreatedServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.ActionUpdatedServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.CancelTargetAssignmentServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.TargetAssignDistributionSetServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.TargetUpdatedServiceEvent;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException; import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.ForceQuitActionNotAllowedException; import org.eclipse.hawkbit.repository.exception.ForceQuitActionNotAllowedException;
import org.eclipse.hawkbit.repository.exception.IncompatibleTargetTypeException; import org.eclipse.hawkbit.repository.exception.IncompatibleTargetTypeException;
import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException; import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException;
import org.eclipse.hawkbit.repository.exception.InvalidDistributionSetException; import org.eclipse.hawkbit.repository.exception.InvalidDistributionSetException;
import org.eclipse.hawkbit.repository.exception.MultiAssignmentIsNotEnabledException; import org.eclipse.hawkbit.repository.exception.MultiAssignmentException;
import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction; import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus; import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus;
@@ -208,19 +211,22 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
*/ */
@Test @Test
void assertMaxActionsPerTargetQuotaIsEnforced() { void assertMaxActionsPerTargetQuotaIsEnforced() {
enableMultiAssignments();
final int maxActions = quotaManagement.getMaxActionsPerTarget(); final int maxActions = quotaManagement.getMaxActionsPerTarget();
final Target testTarget = testdataFactory.createTarget(); final Target testTarget = testdataFactory.createTarget();
final Long ds1Id = testdataFactory.createDistributionSet("ds1").getId(); final List<Long> dsIds = new ArrayList<>();
for (int i = 0; i < maxActions; i++) {
dsIds.add(testdataFactory.createDistributionSet("ds" + i).getId());
}
final String controllerId = testTarget.getControllerId(); final String controllerId = testTarget.getControllerId();
for (int i = 0; i < maxActions; i++) { for (int i = 0; i < maxActions; i++) {
deploymentManagement.offlineAssignedDistributionSets(List.of(new SimpleEntry<>(controllerId, ds1Id))); deploymentManagement.offlineAssignedDistributionSets(List.of(new SimpleEntry<>(controllerId, dsIds.get(i))));
} }
final long dsIdThatShouldNotBeAssigned = testdataFactory.createDistributionSet("shouldNotBeAssigned").getId();
assertThatExceptionOfType(AssignmentQuotaExceededException.class) assertThatExceptionOfType(AssignmentQuotaExceededException.class)
.isThrownBy(() -> assignDistributionSet(ds1Id, controllerId, 77)); .isThrownBy(() -> assignDistributionSet(dsIdThatShouldNotBeAssigned, controllerId, 77));
} }
/** /**
@@ -539,44 +545,6 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
.allMatch(target -> target.getLastModifiedAt() == target.getInstallationDate()); .allMatch(target -> target.getLastModifiedAt() == target.getInstallationDate());
} }
/**
* Offline assign multiple DSs to a single Target in multiassignment mode.
*/
@Test
@ExpectEvents({
@Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = TargetUpdatedEvent.class, count = 4),
@Expect(type = ActionCreatedEvent.class, count = 4),
@Expect(type = DistributionSetCreatedEvent.class, count = 4),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 12),
@Expect(type = DistributionSetUpdatedEvent.class, count = 4), // implicit lock
@Expect(type = SoftwareModuleUpdatedEvent.class, count = 12), // implicit lock
@Expect(type = TenantConfigurationCreatedEvent.class, count = 1) })
void multiOfflineAssignment() {
final List<String> targetIds = testdataFactory.createTargets(1).stream().map(Target::getControllerId).toList();
final List<Long> dsIds = testdataFactory.createDistributionSets(4).stream().map(DistributionSet::getId).toList();
enableMultiAssignments();
final List<Entry<String, Long>> offlineAssignments = new ArrayList<>();
targetIds.forEach(targetId -> dsIds.forEach(dsId -> offlineAssignments.add(new SimpleEntry<>(targetId, dsId))));
final List<DistributionSetAssignmentResult> assignmentResults = deploymentManagement
.offlineAssignedDistributionSets(offlineAssignments);
assertThat(getResultingActionCount(assignmentResults)).isEqualTo(4);
targetIds.forEach(controllerId -> {
final List<Long> assignedDsIds = deploymentManagement.findActionsByTarget(controllerId, PAGE).stream()
.map(a -> {
// don't use peek since it is by documentation mainly for debugging and could be skipped in some cases
assertThat(a.getInitiatedBy())
.as("Actions should be initiated by current user")
.isEqualTo(AccessContext.actor());
return a;
})
.map(action -> action.getDistributionSet().getId()).toList();
assertThat(assignedDsIds).containsExactlyInAnyOrderElementsOf(dsIds);
});
}
/** /**
* Verifies that if an account is set to action autoclose running actions in case of a new assigned set get closed and set to CANCELED. * Verifies that if an account is set to action autoclose running actions in case of a new assigned set get closed and set to CANCELED.
*/ */
@@ -620,118 +588,11 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
} }
} }
/**
* If multi-assignment is enabled, verify that the previous Distribution Set assignment is not canceled when a new one is assigned.
*/
@Test
@ExpectEvents({
@Expect(type = TargetCreatedEvent.class, count = 10),
@Expect(type = TargetUpdatedEvent.class, count = 20),
@Expect(type = ActionCreatedEvent.class, count = 20),
@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 = MultiActionAssignEvent.class, count = 2),
@Expect(type = TenantConfigurationCreatedEvent.class, count = 1) })
void previousAssignmentsAreNotCanceledInMultiAssignMode() {
enableMultiAssignments();
final List<Target> targets = testdataFactory.createTargets(10);
final List<String> targetIds = targets.stream().map(Target::getControllerId).toList();
// First assignment
final DistributionSet ds1 = testdataFactory.createDistributionSet("Multi-assign-1");
assignDistributionSet(ds1.getId(), targetIds, 77);
assertDsExclusivelyAssignedToTargets(targets, ds1.getId(), STATE_ACTIVE, RUNNING);
// Second assignment
final DistributionSet ds2 = testdataFactory.createDistributionSet("Multi-assign-2");
assignDistributionSet(ds2.getId(), targetIds, 45);
assertDsExclusivelyAssignedToTargets(targets, ds2.getId(), STATE_ACTIVE, RUNNING);
assertDsExclusivelyAssignedToTargets(targets, ds1.getId(), STATE_ACTIVE, RUNNING);
}
/**
* Assign multiple DSs to a single Target in one request in multiassignment mode.
*/
@Test
@ExpectEvents({
@Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = TargetUpdatedEvent.class, count = 4),
@Expect(type = ActionCreatedEvent.class, count = 4),
@Expect(type = DistributionSetCreatedEvent.class, count = 4),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 12),
@Expect(type = DistributionSetUpdatedEvent.class, count = 4), // implicit lock
@Expect(type = SoftwareModuleUpdatedEvent.class, count = 12), // implicit lock
@Expect(type = MultiActionAssignEvent.class, count = 1),
@Expect(type = TenantConfigurationCreatedEvent.class, count = 1) })
void multiAssignmentInOneRequest() {
final List<Target> targets = testdataFactory.createTargets(1);
final List<DistributionSet> distributionSets = testdataFactory.createDistributionSets(4);
final List<DeploymentRequest> deploymentRequests = createAssignmentRequests(distributionSets, targets, 34);
enableMultiAssignments();
final List<DistributionSetAssignmentResult> results = deploymentManagement.assignDistributionSets(deploymentRequests, null);
assertThat(getResultingActionCount(results)).isEqualTo(deploymentRequests.size());
final List<Long> dsIds = distributionSets.stream().map(DistributionSet::getId).toList();
targets.forEach(target -> {
final List<Long> assignedDsIds = deploymentManagement.findActionsByTarget(target.getControllerId(), PAGE)
.stream()
.map(a -> {
// don't use peek since it is by documentation mainly for debugging and could be skipped in some cases
assertThat(a.getInitiatedBy()).as("Initiated by current user")
.isEqualTo(AccessContext.actor());
return a;
})
.map(action -> action.getDistributionSet().getId()).toList();
assertThat(assignedDsIds).containsExactlyInAnyOrderElementsOf(dsIds);
});
}
/**
* Assign multiple DSs to single Target in one request in multiAssignment mode and cancel each created action afterwards.
*/
@Test
@ExpectEvents({
@Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = TargetUpdatedEvent.class, count = 4),
@Expect(type = ActionCreatedEvent.class, count = 4),
@Expect(type = DistributionSetCreatedEvent.class, count = 4),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 12),
@Expect(type = DistributionSetUpdatedEvent.class, count = 4), // implicit lock
@Expect(type = SoftwareModuleUpdatedEvent.class, count = 12), // implicit lock
@Expect(type = MultiActionAssignEvent.class, count = 1),
@Expect(type = MultiActionCancelEvent.class, count = 4),
@Expect(type = ActionUpdatedEvent.class, count = 4),
@Expect(type = TenantConfigurationCreatedEvent.class, count = 1) })
void cancelMultiAssignmentActions() {
final List<Target> targets = testdataFactory.createTargets(1);
final List<DistributionSet> distributionSets = testdataFactory.createDistributionSets(4);
final List<DeploymentRequest> deploymentRequests = createAssignmentRequests(distributionSets, targets, 34, false);
enableMultiAssignments();
final List<DistributionSetAssignmentResult> results = deploymentManagement.assignDistributionSets(deploymentRequests, null);
assertThat(getResultingActionCount(results)).isEqualTo(deploymentRequests.size());
final List<Long> dsIds = distributionSets.stream().map(DistributionSet::getId).toList();
targets.forEach(target ->
deploymentManagement.findActionsByTarget(target.getControllerId(), PAGE).forEach(action -> {
assertThat(action.getDistributionSet().getId()).isIn(dsIds);
assertThat(action.getInitiatedBy())
.as("Should be Initiated by current user").isEqualTo(AccessContext.actor());
deploymentManagement.cancelAction(action.getId());
}));
}
/** /**
* A Request resulting in multiple assignments to a single target is only allowed when multiassignment is enabled. * A Request resulting in multiple assignments to a single target is only allowed when multiassignment is enabled.
*/ */
@Test @Test
void multipleAssignmentsToTargetOnlyAllowedInMultiAssignMode() { void multipleAssignmentsToTargetNotAllowed() {
final Target target = testdataFactory.createTarget(); final Target target = testdataFactory.createTarget();
final List<DistributionSet> distributionSets = testdataFactory.createDistributionSets(2); final List<DistributionSet> distributionSets = testdataFactory.createDistributionSets(2);
@@ -742,11 +603,9 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
.builder(target.getControllerId(), distributionSets.get(1).getId()).weight(565).build(); .builder(target.getControllerId(), distributionSets.get(1).getId()).weight(565).build();
final List<DeploymentRequest> deploymentRequests = List.of(targetToDS0, targetToDS1); final List<DeploymentRequest> deploymentRequests = List.of(targetToDS0, targetToDS1);
Assertions.assertThatExceptionOfType(MultiAssignmentIsNotEnabledException.class) Assertions.assertThatExceptionOfType(MultiAssignmentException.class)
.isThrownBy(() -> deploymentManagement.assignDistributionSets(deploymentRequests, null)); .isThrownBy(() -> deploymentManagement.assignDistributionSets(deploymentRequests, null));
enableMultiAssignments();
assertThat(getResultingActionCount(deploymentManagement.assignDistributionSets(List.of(targetToDS0, targetToDS1), null))).isEqualTo(2);
} }
/** /**
@@ -912,77 +771,46 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
})); }));
} }
/**
* Duplicate Assignments are removed from a request when multi-assignment is disabled, otherwise not
*/
@Test
@ExpectEvents({
@Expect(type = TargetCreatedEvent.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 = 3), // implicit lock
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
@Expect(type = ActionCreatedEvent.class, count = 2),
@Expect(type = TargetUpdatedEvent.class, count = 2),
@Expect(type = MultiActionAssignEvent.class, count = 1),
@Expect(type = TenantConfigurationCreatedEvent.class, count = 1) })
void duplicateAssignmentsInRequestAreRemovedIfMultiassignmentEnabled() {
final String targetId = testdataFactory.createTarget().getControllerId();
final Long dsId = testdataFactory.createDistributionSet().getId();
final List<DeploymentRequest> twoEqualAssignments = Collections.nCopies(2, DeploymentRequest.builder(targetId, dsId).build());
assertThat(getResultingActionCount(deploymentManagement.assignDistributionSets(twoEqualAssignments, null))).isEqualTo(1);
enableMultiAssignments();
final List<DeploymentRequest> twoEqualAssignmentsWithWeight = Collections.nCopies(
2, DeploymentRequest.builder(targetId, dsId).weight(555).build());
assertThat(getResultingActionCount(deploymentManagement.assignDistributionSets(twoEqualAssignmentsWithWeight, null))).isEqualTo(1);
}
/** /**
* An assignment request is not accepted if it would lead to a target exceeding the max actions per target quota. * An assignment request is not accepted if it would lead to a target exceeding the max actions per target quota.
*/ */
@Test @Test
@ExpectEvents({ @ExpectEvents({
@Expect(type = TargetCreatedEvent.class, count = 1), @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = TargetUpdatedEvent.class, count = 20),
@Expect(type = TargetUpdatedServiceEvent.class, count = 20),
@Expect(type = TargetAssignDistributionSetEvent.class, count = 20),
@Expect(type = CancelTargetAssignmentEvent.class, count = 19),
@Expect(type = CancelTargetAssignmentServiceEvent.class, count = 19),
@Expect(type = DistributionSetCreatedEvent.class, count = 21), // max actions per target are 20 for test @Expect(type = DistributionSetCreatedEvent.class, count = 21), // max actions per target are 20 for test
@Expect(type = DistributionSetUpdatedEvent.class, count = 20),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3 * 21), @Expect(type = SoftwareModuleCreatedEvent.class, count = 3 * 21),
@Expect(type = TenantConfigurationCreatedEvent.class, count = 1) }) @Expect(type = SoftwareModuleUpdatedEvent.class, count = 3 * 20),
@Expect(type = ActionUpdatedEvent.class, count = 19),
@Expect(type = ActionCreatedEvent.class, count = 20),
@Expect(type = ActionCreatedServiceEvent.class, count = 20),
@Expect(type = TargetAssignDistributionSetEvent.class, count = 20),
})
void maxActionsPerTargetIsCheckedBeforeAssignmentExecution() { void maxActionsPerTargetIsCheckedBeforeAssignmentExecution() {
final int maxActions = quotaManagement.getMaxActionsPerTarget(); final int maxActions = quotaManagement.getMaxActionsPerTarget();
assertThat(maxActions) assertThat(maxActions)
.as("Expect 20 as maxActionPerTarget. If not the case change @Expect counts for " + .as("Expect 20 as maxActionPerTarget. If not the case change @Expect counts for " +
"DistributionSetCreatedEvent and SoftwareModuleCreatedEvent accordingly!") "DistributionSetCreatedEvent and SoftwareModuleCreatedEvent accordingly!")
.isEqualTo(20); .isEqualTo(20);
final int size = maxActions + 1;
final String controllerId = testdataFactory.createTarget().getControllerId(); final String controllerId = testdataFactory.createTarget().getControllerId();
final List<DeploymentRequest> deploymentRequests = new ArrayList<>(); for (int i = 0; i < maxActions; i++) {
for (int i = 0; i < size; i++) {
final Long dsId = testdataFactory.createDistributionSet().getId(); final Long dsId = testdataFactory.createDistributionSet().getId();
deploymentRequests.add(DeploymentRequest.builder(controllerId, dsId).weight(24).build()); deploymentManagement.assignDistributionSets(
List.of(DeploymentRequest.builder(controllerId, dsId).weight(24).build()), null);
} }
enableMultiAssignments(); final Long dsId = testdataFactory.createDistributionSet().getId();
final List<DeploymentRequest> deploymentRequests = List.of(DeploymentRequest.builder(controllerId, dsId).weight(24).build());
Assertions.assertThatExceptionOfType(AssignmentQuotaExceededException.class) Assertions.assertThatExceptionOfType(AssignmentQuotaExceededException.class)
.isThrownBy(() -> deploymentManagement.assignDistributionSets(deploymentRequests, null)); .isThrownBy(() -> deploymentManagement.assignDistributionSets(deploymentRequests, null));
assertThat(actionRepository.countByTargetControllerId(controllerId)).isZero(); assertThat(actionRepository.countByTargetControllerId(controllerId)).isEqualTo(20L);
}
/**
* An assignment request without a weight is ok when multi assignment in enabled.
*/
@Test
void weightNotRequiredInMultiAssignmentMode() {
final String targetId = testdataFactory.createTarget().getControllerId();
final Long dsId = testdataFactory.createDistributionSet().getId();
final DeploymentRequest assignWithoutWeight = DeploymentRequest.builder(targetId, dsId).build();
final DeploymentRequest assignWithWeight = DeploymentRequest.builder(targetId, dsId).weight(567).build();
enableMultiAssignments();
assertThat(deploymentManagement.assignDistributionSets(List.of(assignWithoutWeight, assignWithWeight), null)).isNotNull();
} }
/** /**
@@ -1003,22 +831,28 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
@Test @Test
@ExpectEvents({ @ExpectEvents({
@Expect(type = TargetCreatedEvent.class, count = 1), @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = DistributionSetCreatedEvent.class, count = 1), @Expect(type = DistributionSetCreatedEvent.class, count = 2),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3), @Expect(type = SoftwareModuleCreatedEvent.class, count = 6),
@Expect(type = DistributionSetUpdatedEvent.class, count = 1), // implicit lock @Expect(type = DistributionSetUpdatedEvent.class, count = 2), // implicit lock
@Expect(type = SoftwareModuleUpdatedEvent.class, count = 3), // implicit lock @Expect(type = SoftwareModuleUpdatedEvent.class, count = 6), // implicit lock
@Expect(type = ActionCreatedEvent.class, count = 2), @Expect(type = ActionCreatedEvent.class, count = 2),
@Expect(type = ActionUpdatedEvent.class, count = 1),
@Expect(type = ActionUpdatedServiceEvent.class, count = 1),
@Expect(type = TargetUpdatedEvent.class, count = 2), @Expect(type = TargetUpdatedEvent.class, count = 2),
@Expect(type = MultiActionAssignEvent.class, count = 2), @Expect(type = TargetAssignDistributionSetEvent.class, count = 2),
@Expect(type = TenantConfigurationCreatedEvent.class, count = 1) }) @Expect(type = TargetAssignDistributionSetServiceEvent.class, count = 2),
@Expect(type = CancelTargetAssignmentEvent.class, count = 1),
@Expect(type = CancelTargetAssignmentServiceEvent.class, count = 1)
})
void weightValidatedAndSaved() { void weightValidatedAndSaved() {
final String targetId = testdataFactory.createTarget().getControllerId(); final String targetId = testdataFactory.createTarget().getControllerId();
final Long dsId = testdataFactory.createDistributionSet().getId(); final Long dsId1 = testdataFactory.createDistributionSet().getId();
final DeploymentRequest validRequest1 = DeploymentRequest.builder(targetId, dsId).weight(Action.WEIGHT_MAX).build(); final Long dsId2 = testdataFactory.createDistributionSet().getId();
final DeploymentRequest validRequest2 = DeploymentRequest.builder(targetId, dsId).weight(Action.WEIGHT_MIN).build(); final DeploymentRequest validRequest1 = DeploymentRequest.builder(targetId, dsId1).weight(Action.WEIGHT_MAX).build();
final DeploymentRequest weightTooLow = DeploymentRequest.builder(targetId, dsId).weight(Action.WEIGHT_MIN - 1).build(); final DeploymentRequest validRequest2 = DeploymentRequest.builder(targetId, dsId2).weight(Action.WEIGHT_MIN).build();
final DeploymentRequest weightTooHigh = DeploymentRequest.builder(targetId, dsId).weight(Action.WEIGHT_MAX + 1).build(); final DeploymentRequest weightTooLow = DeploymentRequest.builder(targetId, dsId1).weight(Action.WEIGHT_MIN - 1).build();
enableMultiAssignments(); final DeploymentRequest weightTooHigh = DeploymentRequest.builder(targetId, dsId1).weight(Action.WEIGHT_MAX + 1).build();
final List<DeploymentRequest> deploymentRequestsTooLow = Collections.singletonList(weightTooLow); final List<DeploymentRequest> deploymentRequestsTooLow = Collections.singletonList(weightTooLow);
Assertions.assertThatExceptionOfType(ConstraintViolationException.class) Assertions.assertThatExceptionOfType(ConstraintViolationException.class)
.isThrownBy(() -> deploymentManagement.assignDistributionSets(deploymentRequestsTooLow, null)); .isThrownBy(() -> deploymentManagement.assignDistributionSets(deploymentRequestsTooLow, null));
@@ -1027,9 +861,11 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
() -> deploymentManagement.assignDistributionSets(deploymentRequestsTooHigh, null)); () -> deploymentManagement.assignDistributionSets(deploymentRequestsTooHigh, null));
final Long validActionId1 = getFirstAssignedAction( final Long validActionId1 = getFirstAssignedAction(
deploymentManagement.assignDistributionSets(Collections.singletonList(validRequest1), null).get(0)).getId(); deploymentManagement.assignDistributionSets(Collections.singletonList(validRequest1), null).get(0)).getId();
final Long validActionId2 = getFirstAssignedAction(
deploymentManagement.assignDistributionSets(Collections.singletonList(validRequest2), null).get(0)).getId();
assertThat(actionRepository.findById(validActionId1).get().getWeight()).get().isEqualTo(Action.WEIGHT_MAX); assertThat(actionRepository.findById(validActionId1).get().getWeight()).get().isEqualTo(Action.WEIGHT_MAX);
DistributionSetAssignmentResult assignmentResult = deploymentManagement.assignDistributionSets(Collections.singletonList(validRequest2), null).get(0);
System.out.println(assignmentResult.getAssignedEntity().toString());
final Long validActionId2 = getFirstAssignedAction(assignmentResult).getId();
assertThat(actionRepository.findById(validActionId2).get().getWeight()).get().isEqualTo(Action.WEIGHT_MIN); assertThat(actionRepository.findById(validActionId2).get().getWeight()).get().isEqualTo(Action.WEIGHT_MIN);
} }
@@ -1720,20 +1556,6 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
} }
private List<DeploymentRequest> createAssignmentRequests(
final Collection<DistributionSet> distributionSets, final Collection<Target> targets, final int weight) {
return createAssignmentRequests(distributionSets, targets, weight, false);
}
private List<DeploymentRequest> createAssignmentRequests(final Collection<DistributionSet> distributionSets,
final Collection<Target> targets, final int weight, final boolean confirmationRequired) {
final List<DeploymentRequest> deploymentRequests = new ArrayList<>();
distributionSets.forEach(ds -> targets.forEach(target -> deploymentRequests.add(
DeploymentRequest.builder(target.getControllerId(), ds.getId())
.weight(weight).confirmationRequired(confirmationRequired).build())));
return deploymentRequests;
}
private JpaAction assignSet(final Target target, final DistributionSet ds) { private JpaAction assignSet(final Target target, final DistributionSet ds) {
assignDistributionSet(ds.getId(), target.getControllerId()); assignDistributionSet(ds.getId(), target.getControllerId());
implicitLock(ds); implicitLock(ds);

View File

@@ -25,6 +25,7 @@ import java.util.stream.Stream;
import jakarta.validation.ConstraintViolationException; import jakarta.validation.ConstraintViolationException;
import jakarta.validation.ValidationException; import jakarta.validation.ValidationException;
import lombok.extern.slf4j.Slf4j;
import org.assertj.core.api.Assertions; import org.assertj.core.api.Assertions;
import org.assertj.core.api.Condition; import org.assertj.core.api.Condition;
import org.eclipse.hawkbit.auth.SpPermission; import org.eclipse.hawkbit.auth.SpPermission;
@@ -1994,18 +1995,6 @@ class RolloutManagementTest extends AbstractJpaIntegrationTest {
assertThat(rolloutsOrderedByName).containsSubsequence(List.of(rolloutRunning, rolloutReady)); assertThat(rolloutsOrderedByName).containsSubsequence(List.of(rolloutRunning, rolloutReady));
} }
/**
* Creating a rollout without weight value when multi assignment in enabled.
*/
@Test
void weightNotRequiredInMultiAssignmentMode() {
enableMultiAssignments();
final Rollout rollout = testdataFactory.createSimpleTestRolloutWithTargetsAndDistributionSet(10, 10, 2, "50",
"80",
ActionType.FORCED, null);
assertThat(rollout).isNotNull();
}
/** /**
* Creating a rollout with a weight causes an error when multi assignment in disabled. * Creating a rollout with a weight causes an error when multi assignment in disabled.
*/ */
@@ -2024,7 +2013,6 @@ class RolloutManagementTest extends AbstractJpaIntegrationTest {
void weightValidatedAndSaved() { void weightValidatedAndSaved() {
final String targetPrefix = UUID.randomUUID().toString(); final String targetPrefix = UUID.randomUUID().toString();
testdataFactory.createTargets(4, targetPrefix); testdataFactory.createTargets(4, targetPrefix);
enableMultiAssignments();
final String rolloutName = UUID.randomUUID().toString(); final String rolloutName = UUID.randomUUID().toString();
final String targetPrefixName = UUID.randomUUID().toString(); final String targetPrefixName = UUID.randomUUID().toString();
@@ -2054,7 +2042,6 @@ class RolloutManagementTest extends AbstractJpaIntegrationTest {
void actionsWithWeightAreCreated() { void actionsWithWeightAreCreated() {
final int amountOfTargets = 5; final int amountOfTargets = 5;
final int weight = 99; final int weight = 99;
enableMultiAssignments();
final Long rolloutId = testdataFactory final Long rolloutId = testdataFactory
.createSimpleTestRolloutWithTargetsAndDistributionSet(amountOfTargets, 2, amountOfTargets, .createSimpleTestRolloutWithTargetsAndDistributionSet(amountOfTargets, 2, amountOfTargets,
"80", "50", null, weight).getId(); "80", "50", null, weight).getId();
@@ -2066,24 +2053,6 @@ class RolloutManagementTest extends AbstractJpaIntegrationTest {
.allMatch(action -> action.getWeight().get() == weight); .allMatch(action -> action.getWeight().get() == weight);
} }
/**
* Rollout can be created without weight in single assignment and be started in multi assignment
*/
@Test
void createInSingleStartInMultiassignMode() {
final int amountOfTargets = 5;
final Long rolloutId = testdataFactory.createSimpleTestRolloutWithTargetsAndDistributionSet(amountOfTargets, 2,
amountOfTargets,
"80", "50", null, null).getId();
enableMultiAssignments();
rolloutManagement.start(rolloutId);
rolloutHandler.handleAll();
final List<Action> actions = deploymentManagement.findActionsAll(PAGE).getContent();
// wight replaced with default
assertThat(actions).hasSize(5).allMatch(action -> action.getWeight().isPresent());
}
/** /**
* Verifies that an exception is thrown when trying to create a rollout with an invalidated distribution set. * Verifies that an exception is thrown when trying to create a rollout with an invalidated distribution set.
*/ */

View File

@@ -244,23 +244,6 @@ class TargetFilterQueryManagementTest extends AbstractRepositoryManagementTest<T
verifyFindForAllWithAutoAssignDs(tfq, tfq2); verifyFindForAllWithAutoAssignDs(tfq, tfq2);
} }
/**
* Creating or updating a target filter query with autoassignment and no-value weight when multi assignment in enabled.
*/
@Test
void weightNotRequiredInMultiAssignmentMode() {
enableMultiAssignments();
final DistributionSet ds = testdataFactory.createDistributionSet();
final Long filterId = targetFilterQueryManagement.create(Create.builder().name("a").query("name==*").build()).getId();
assertThat(
targetFilterQueryManagement.create(Create.builder().name("b").query("name==*").autoAssignDistributionSet(ds).build()))
.isNotNull();
assertThat(
targetFilterQueryManagement.updateAutoAssignDS(new AutoAssignDistributionSetUpdate(filterId).ds(ds.getId())))
.isNotNull();
}
/** /**
* Creating or updating a target filter query with autoassignment with a weight causes an error when multi assignment in disabled. * Creating or updating a target filter query with autoassignment with a weight causes an error when multi assignment in disabled.
*/ */
@@ -279,23 +262,8 @@ class TargetFilterQueryManagementTest extends AbstractRepositoryManagementTest<T
.isNotNull(); .isNotNull();
} }
/**
* Auto assignment can be removed from filter when multi assignment in enabled.
*/
@Test
void removeDsFromFilterWhenMultiAssignmentModeNotEnabled() {
enableMultiAssignments();
final DistributionSet ds = testdataFactory.createDistributionSet();
final Long filterId = targetFilterQueryManagement.create(
Create.builder().name("a").query("name==*").autoAssignDistributionSet(ds).autoAssignWeight(23).build()).getId();
assertThat(targetFilterQueryManagement.updateAutoAssignDS(
new AutoAssignDistributionSetUpdate(filterId).ds(null).weight(null)))
.isNotNull();
}
@Test @Test
void weightValidatedAndSaved() { void weightValidatedAndSaved() {
enableMultiAssignments();
final DistributionSet ds = testdataFactory.createDistributionSet(); final DistributionSet ds = testdataFactory.createDistributionSet();
final Create targetFilterQueryCreate = Create.builder().name("a") final Create targetFilterQueryCreate = Create.builder().name("a")

View File

@@ -328,7 +328,6 @@ class AutoAssignHandlerIntTest extends AbstractJpaIntegrationTest {
final int amountOfTargets = 5; final int amountOfTargets = 5;
final DistributionSet ds = testdataFactory.createDistributionSet(); final DistributionSet ds = testdataFactory.createDistributionSet();
final int weight = 32; final int weight = 32;
enableMultiAssignments();
targetFilterQueryManagement.create(Create.builder() targetFilterQueryManagement.create(Create.builder()
.name("a").query("name==*").autoAssignDistributionSet(ds).autoAssignWeight(weight) .name("a").query("name==*").autoAssignDistributionSet(ds).autoAssignWeight(weight)
@@ -342,25 +341,6 @@ class AutoAssignHandlerIntTest extends AbstractJpaIntegrationTest {
.allMatch(action -> action.getWeight().get() == weight); .allMatch(action -> action.getWeight().get() == weight);
} }
/**
* An auto assignment target filter without weight still works after multi assignment is enabled
*/
@Test
void filterWithoutWeightWorksInMultiAssignmentMode() {
final int amountOfTargets = 5;
final DistributionSet ds = testdataFactory.createDistributionSet();
targetFilterQueryManagement.create(Create.builder().name("a").query("name==*").autoAssignDistributionSet(ds).build());
enableMultiAssignments();
testdataFactory.createTargets(amountOfTargets);
autoAssignChecker.handleAll();
final List<Action> actions = deploymentManagement.findActionsAll(PAGE).getContent();
assertThat(actions)
.hasSize(amountOfTargets)
.allMatch(action -> action.getWeight().isPresent());
}
/** /**
* Verifies an auto assignment only creates actions for compatible targets * Verifies an auto assignment only creates actions for compatible targets
*/ */

View File

@@ -34,8 +34,6 @@ import org.awaitility.Awaitility;
import org.awaitility.core.ConditionTimeoutException; import org.awaitility.core.ConditionTimeoutException;
import org.eclipse.hawkbit.repository.event.remote.AbstractRemoteEvent; import org.eclipse.hawkbit.repository.event.remote.AbstractRemoteEvent;
import org.eclipse.hawkbit.repository.event.remote.CancelTargetAssignmentEvent; 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.RemoteIdEvent; import org.eclipse.hawkbit.repository.event.remote.RemoteIdEvent;
import org.eclipse.hawkbit.repository.event.remote.RemoteTenantAwareEvent; import org.eclipse.hawkbit.repository.event.remote.RemoteTenantAwareEvent;
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent; import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
@@ -48,8 +46,6 @@ import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent;
import org.eclipse.hawkbit.repository.event.remote.service.ActionCreatedServiceEvent; import org.eclipse.hawkbit.repository.event.remote.service.ActionCreatedServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.ActionUpdatedServiceEvent; import org.eclipse.hawkbit.repository.event.remote.service.ActionUpdatedServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.CancelTargetAssignmentServiceEvent; 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.TargetAssignDistributionSetServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.TargetAttributesRequestedServiceEvent; import org.eclipse.hawkbit.repository.event.remote.service.TargetAttributesRequestedServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.TargetCreatedServiceEvent; import org.eclipse.hawkbit.repository.event.remote.service.TargetCreatedServiceEvent;
@@ -177,10 +173,6 @@ public class EventVerifier extends AbstractTestExecutionListener {
modifiedEvents.add(new DynamicExpect(TargetDeletedServiceEvent.class, event.count())); modifiedEvents.add(new DynamicExpect(TargetDeletedServiceEvent.class, event.count()));
} else if (type.isAssignableFrom(TargetAssignDistributionSetEvent.class)) { } else if (type.isAssignableFrom(TargetAssignDistributionSetEvent.class)) {
modifiedEvents.add(new DynamicExpect(TargetAssignDistributionSetServiceEvent.class, event.count())); modifiedEvents.add(new DynamicExpect(TargetAssignDistributionSetServiceEvent.class, event.count()));
} else if (type.isAssignableFrom(MultiActionAssignEvent.class)) {
modifiedEvents.add(new DynamicExpect(MultiActionAssignServiceEvent.class, event.count()));
} else if (type.isAssignableFrom(MultiActionCancelEvent.class)) {
modifiedEvents.add(new DynamicExpect(MultiActionCancelServiceEvent.class, event.count()));
} else if (type.isAssignableFrom(TargetAttributesRequestedEvent.class)) { } else if (type.isAssignableFrom(TargetAttributesRequestedEvent.class)) {
modifiedEvents.add(new DynamicExpect(TargetAttributesRequestedServiceEvent.class, event.count())); modifiedEvents.add(new DynamicExpect(TargetAttributesRequestedServiceEvent.class, event.count()));
} else if (type.isAssignableFrom(CancelTargetAssignmentEvent.class)) { } else if (type.isAssignableFrom(CancelTargetAssignmentEvent.class)) {

View File

@@ -409,10 +409,6 @@ public abstract class AbstractIntegrationTest {
return assignDistributionSet(dsId, Collections.singletonList(targetId), weight); return assignDistributionSet(dsId, Collections.singletonList(targetId), weight);
} }
protected void enableMultiAssignments() {
tenantConfigurationManagement().addOrUpdateConfiguration(TenantConfigurationKey.MULTI_ASSIGNMENTS_ENABLED, true);
}
protected void enableConfirmationFlow() { protected void enableConfirmationFlow() {
tenantConfigurationManagement().addOrUpdateConfiguration(TenantConfigurationKey.USER_CONFIRMATION_FLOW_ENABLED, true); tenantConfigurationManagement().addOrUpdateConfiguration(TenantConfigurationKey.USER_CONFIRMATION_FLOW_ENABLED, true);
} }

View File

@@ -147,7 +147,7 @@ public class RestConfiguration {
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REPO_AUTO_CONFIRMATION_ALREADY_ACTIVE, CONFLICT); ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REPO_AUTO_CONFIRMATION_ALREADY_ACTIVE, CONFLICT);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_AUTO_ASSIGN_ACTION_TYPE_INVALID, BAD_REQUEST); ERROR_TO_HTTP_STATUS.put(SpServerError.SP_AUTO_ASSIGN_ACTION_TYPE_INVALID, BAD_REQUEST);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_CONFIGURATION_VALUE_CHANGE_NOT_ALLOWED, FORBIDDEN); ERROR_TO_HTTP_STATUS.put(SpServerError.SP_CONFIGURATION_VALUE_CHANGE_NOT_ALLOWED, FORBIDDEN);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_MULTIASSIGNMENT_NOT_ENABLED, BAD_REQUEST); ERROR_TO_HTTP_STATUS.put(SpServerError.SP_MULTIASSIGNMENT, BAD_REQUEST);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_NO_WEIGHT_PROVIDED_IN_MULTIASSIGNMENT_MODE, BAD_REQUEST); ERROR_TO_HTTP_STATUS.put(SpServerError.SP_NO_WEIGHT_PROVIDED_IN_MULTIASSIGNMENT_MODE, BAD_REQUEST);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_TARGET_TYPE_IN_USE, CONFLICT); ERROR_TO_HTTP_STATUS.put(SpServerError.SP_TARGET_TYPE_IN_USE, CONFLICT);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_TARGET_TYPE_INCOMPATIBLE, BAD_REQUEST); ERROR_TO_HTTP_STATUS.put(SpServerError.SP_TARGET_TYPE_INCOMPATIBLE, BAD_REQUEST);