Support for Multi-Assignments (#833)

* First draft of new assignment logic

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Enhancements of System Configuration view

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Drag&drop enhancements for multiple distribution set assignments

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Misc fixes

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* test that previous assignments are not canceled

Signed-off-by: Stefan Klotz <stefan.klotz@bosch-si.com>

* add description and expected events to test

Signed-off-by: Stefan Klotz <stefan.klotz@bosch-si.com>

* extend TenantConfigurationManagement by NullPointerException proof
getConfigurationValue method

Signed-off-by: Stefan Klotz <stefan.klotz@bosch-si.com>

* Hide "Required Migration Step" if Multi Assignments is enabled

Signed-off-by: Stefan Klotz <stefan.klotz@bosch-si.com>

* Make fields transient

Signed-off-by: Stefan Klotz <stefan.klotz@bosch-si.com>

* Add IDs for Required Migration Step elements

Signed-off-by: Stefan Klotz <stefan.klotz@bosch-si.com>

* Save work in progress

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Added new DMF message DmfMultiActionRequest

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* DMF enhancements to send out MultiActionRequest messages

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Minor changes

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Multi Assignment support for cancellations

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* fix permission problems and immutable lists

Signed-off-by: Stefan Klotz <stefan.klotz@bosch-si.com>

* add message dispatcher tests for outgoing multiassignment messages, fix
old tests

Signed-off-by: Stefan Klotz <stefan.klotz@bosch-si.com>

* Implement Multi-Assignment support for rollout groups

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Minor changes

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Refactoring

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Register new deployment event with protobuff framework

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Allow same DS to be assigned multiple times

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Fix assignment with pending cancellations

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Reduce repository /DB calls

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Revert latest perf fix (causing a regression)

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* test if a rollout sends multiaction messages in multiassignment mode

Signed-off-by: Stefan Klotz <stefan.klotz@bosch-si.com>

* Minor changes

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Do not close new action if DS is already assigned (if multi-assign on)

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* add test that starts and finishes multiple rollouts in multiassignt mode

Signed-off-by: Stefan Klotz <stefan.klotz@bosch-si.com>

* add javadoc to test method

Signed-off-by: Stefan Klotz <stefan.klotz@bosch-si.com>

* Prevent Multi-Assignments from being disabled via Repo Config UI

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Add link to Provisioning State Machine

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* test that Multiassignment can not be disabled via mgmt-api

Signed-off-by: Stefan Klotz <stefan.klotz@bosch-si.com>

* refactor AmqpMessageHandlerService code

Signed-off-by: Stefan Klotz <stefan.klotz@bosch-si.com>

* Prevent Multi-Assignments feature from being disabled via Mgmt REST API

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* add license header, remove unused instance variables

Signed-off-by: Stefan Klotz <stefan.klotz@bosch-si.com>

* fix tenantConfigurationManagement mock in test

Signed-off-by: Stefan Klotz <stefan.klotz@bosch-si.com>

* return empty list instead of null

Signed-off-by: Stefan Klotz <stefan.klotz@bosch-si.com>

* add ddi test for multiassignment, fix old test

Signed-off-by: Stefan Klotz <stefan.klotz@bosch-si.com>

* Prevent autoclose from being modified if Multi-Assignments is enabled

# WARNING: head commit changed in the meantime

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Add test for autoClose /multiAssign

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Javadoc improvements

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* change test method that waits for dmf messages to be dispatched

Signed-off-by: Stefan Klotz <stefan.klotz@bosch-si.com>

* clean up code

Signed-off-by: Stefan Klotz <stefan.klotz@bosch-si.com>

* Fix UI-related PR review findings

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Fix PR review findings

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Fix PR review findings

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Fix PR review findings

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Fix PR review findings, Sonar issues, and test failures

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Fix PR review findings

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Fix PR review findings

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Fix PR review findings

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Fix Sonar findings

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Fix PR review findings

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Fix PR review findings

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>

* Fix PR review findings

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>
This commit is contained in:
Stefan Behl
2019-05-16 10:30:40 +02:00
committed by Dominic Schabel
parent f16f986eed
commit 1ac40a7a95
65 changed files with 1906 additions and 588 deletions

View File

@@ -191,6 +191,7 @@ public enum SpServerError {
*/ */
SP_CONFIGURATION_VALUE_INVALID("hawkbit.server.error.configValueInvalid", SP_CONFIGURATION_VALUE_INVALID("hawkbit.server.error.configValueInvalid",
"The given configuration value is invalid."), "The given configuration value is invalid."),
/** /**
* *
*/ */
@@ -232,7 +233,14 @@ public enum SpServerError {
* invalid. * invalid.
*/ */
SP_AUTO_ASSIGN_DISTRIBUTION_SET_INVALID("hawkbit.server.error.repo.invalidAutoAssignDistributionSet", SP_AUTO_ASSIGN_DISTRIBUTION_SET_INVALID("hawkbit.server.error.repo.invalidAutoAssignDistributionSet",
"The given distribution set for auto-assignment is invalid: it is either incomplete (i.e. mandatory modules are missing) or soft deleted"); "The given distribution set for auto-assignment is invalid: it is either incomplete (i.e. mandatory modules are missing) or soft deleted"),
/**
* Error message informing the user that the requested tenant configuration
* change is not allowed.
*/
SP_CONFIGURATION_VALUE_CHANGE_NOT_ALLOWED("hawkbit.server.error.repo.tenantConfigurationValueChangeNotAllowed",
"The requested tenant configuration value modification is not allowed.");
private final String key; private final String key;
private final String message; private final String message;

View File

@@ -17,6 +17,7 @@ import org.eclipse.hawkbit.cache.DownloadIdCache;
import org.eclipse.hawkbit.dmf.amqp.api.AmqpSettings; import org.eclipse.hawkbit.dmf.amqp.api.AmqpSettings;
import org.eclipse.hawkbit.repository.ArtifactManagement; import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.ControllerManagement; import org.eclipse.hawkbit.repository.ControllerManagement;
import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.DistributionSetManagement; import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.EntityFactory; import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.SoftwareModuleManagement; import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
@@ -240,9 +241,11 @@ public class AmqpConfiguration {
@Bean @Bean
public AmqpMessageHandlerService amqpMessageHandlerService(final RabbitTemplate rabbitTemplate, public AmqpMessageHandlerService amqpMessageHandlerService(final RabbitTemplate rabbitTemplate,
final AmqpMessageDispatcherService amqpMessageDispatcherService, final AmqpMessageDispatcherService amqpMessageDispatcherService,
final ControllerManagement controllerManagement, final EntityFactory entityFactory) { final ControllerManagement controllerManagement, final EntityFactory entityFactory,
final SystemSecurityContext systemSecurityContext,
final TenantConfigurationManagement tenantConfigurationManagement) {
return new AmqpMessageHandlerService(rabbitTemplate, amqpMessageDispatcherService, controllerManagement, return new AmqpMessageHandlerService(rabbitTemplate, amqpMessageDispatcherService, controllerManagement,
entityFactory); entityFactory, systemSecurityContext, tenantConfigurationManagement);
} }
/** /**
@@ -319,10 +322,10 @@ public class AmqpConfiguration {
final AmqpMessageSenderService amqpSenderService, final ArtifactUrlHandler artifactUrlHandler, final AmqpMessageSenderService amqpSenderService, final ArtifactUrlHandler artifactUrlHandler,
final SystemSecurityContext systemSecurityContext, final SystemManagement systemManagement, final SystemSecurityContext systemSecurityContext, final SystemManagement systemManagement,
final TargetManagement targetManagement, final DistributionSetManagement distributionSetManagement, final TargetManagement targetManagement, final DistributionSetManagement distributionSetManagement,
final SoftwareModuleManagement softwareModuleManagement) { final SoftwareModuleManagement softwareModuleManagement, final DeploymentManagement deploymentManagement) {
return new AmqpMessageDispatcherService(rabbitTemplate, amqpSenderService, artifactUrlHandler, return new AmqpMessageDispatcherService(rabbitTemplate, amqpSenderService, artifactUrlHandler,
systemSecurityContext, systemManagement, targetManagement, serviceMatcher, distributionSetManagement, systemSecurityContext, systemManagement, targetManagement, serviceMatcher, distributionSetManagement,
softwareModuleManagement); softwareModuleManagement, deploymentManagement);
} }
private static Map<String, Object> getTTLMaxArgsAuthenticationQueue() { private static Map<String, Object> getTTLMaxArgsAuthenticationQueue() {

View File

@@ -8,8 +8,11 @@
*/ */
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.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
@@ -28,12 +31,15 @@ import org.eclipse.hawkbit.dmf.json.model.DmfArtifact;
import org.eclipse.hawkbit.dmf.json.model.DmfArtifactHash; import org.eclipse.hawkbit.dmf.json.model.DmfArtifactHash;
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.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.DistributionSetManagement; import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.RepositoryConstants; import org.eclipse.hawkbit.repository.RepositoryConstants;
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.MultiActionEvent;
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;
@@ -41,6 +47,7 @@ import org.eclipse.hawkbit.repository.event.remote.entity.CancelTargetAssignment
import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.ActionProperties; import org.eclipse.hawkbit.repository.model.ActionProperties;
import org.eclipse.hawkbit.repository.model.Artifact; import org.eclipse.hawkbit.repository.model.Artifact;
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.SoftwareModuleMetadata; import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata;
import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.Target;
@@ -79,6 +86,7 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
private final TargetManagement targetManagement; private final TargetManagement targetManagement;
private final ServiceMatcher serviceMatcher; private final ServiceMatcher serviceMatcher;
private final DistributionSetManagement distributionSetManagement; private final DistributionSetManagement distributionSetManagement;
private final DeploymentManagement deploymentManagement;
private final SoftwareModuleManagement softwareModuleManagement; private final SoftwareModuleManagement softwareModuleManagement;
/** /**
@@ -107,7 +115,7 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
final SystemSecurityContext systemSecurityContext, final SystemManagement systemManagement, final SystemSecurityContext systemSecurityContext, final SystemManagement systemManagement,
final TargetManagement targetManagement, final ServiceMatcher serviceMatcher, final TargetManagement targetManagement, final ServiceMatcher serviceMatcher,
final DistributionSetManagement distributionSetManagement, final DistributionSetManagement distributionSetManagement,
final SoftwareModuleManagement softwareModuleManagement) { final SoftwareModuleManagement softwareModuleManagement, final DeploymentManagement deploymentManagement) {
super(rabbitTemplate); super(rabbitTemplate);
this.artifactUrlHandler = artifactUrlHandler; this.artifactUrlHandler = artifactUrlHandler;
this.amqpSenderService = amqpSenderService; this.amqpSenderService = amqpSenderService;
@@ -117,6 +125,7 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
this.serviceMatcher = serviceMatcher; this.serviceMatcher = serviceMatcher;
this.distributionSetManagement = distributionSetManagement; this.distributionSetManagement = distributionSetManagement;
this.softwareModuleManagement = softwareModuleManagement; this.softwareModuleManagement = softwareModuleManagement;
this.deploymentManagement = deploymentManagement;
} }
/** /**
@@ -134,30 +143,112 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
LOG.debug("targetAssignDistributionSet retrieved. I will forward it to DMF broker."); LOG.debug("targetAssignDistributionSet retrieved. I will forward it to DMF broker.");
distributionSetManagement.get(assignedEvent.getDistributionSetId()).ifPresent(set -> { distributionSetManagement.get(assignedEvent.getDistributionSetId()).ifPresent(ds -> {
final Map<SoftwareModule, List<SoftwareModuleMetadata>> modules = Maps final Map<SoftwareModule, List<SoftwareModuleMetadata>> softwareModules = getSoftwareModulesWithMetadata(
.newHashMapWithExpectedSize(set.getModules().size()); ds);
set.getModules()
.forEach(
module -> modules.put(module,
softwareModuleManagement.findMetaDataBySoftwareModuleIdAndTargetVisible(
PageRequest.of(0, RepositoryConstants.MAX_META_DATA_COUNT), module.getId())
.getContent()));
targetManagement.getByControllerID(assignedEvent.getActions().keySet()).forEach( targetManagement.getByControllerID(assignedEvent.getActions().keySet()).forEach(
target -> sendUpdateMessageToTarget(assignedEvent.getActions().get(target.getControllerId()), target -> sendUpdateMessageToTarget(assignedEvent.getActions().get(target.getControllerId()),
target, modules)); target, softwareModules));
}); });
} }
/**
* Listener for Multi-Action events.
*
* @param e
* the Multi-Action event to be processed
*/
@EventListener(classes = MultiActionEvent.class)
protected void onMultiAction(final MultiActionEvent e) {
if (isNotFromSelf(e)) {
return;
}
LOG.debug("MultiActionEvent received for {}", e.getControllerIds());
sendMultiActionRequestMessages(e.getTenant(), e.getControllerIds());
}
protected void sendMultiActionRequestMessages(final String tenant, final List<String> controllerIds) {
final Map<Long, Map<SoftwareModule, List<SoftwareModuleMetadata>>> softwareModuleMetadata = new HashMap<>();
targetManagement.getByControllerID(controllerIds).stream()
.filter(target -> IpUtil.isAmqpUri(target.getAddress())).forEach(target -> {
final List<Action> activeActions = deploymentManagement
.findActiveActionsByTarget(PageRequest.of(0, MAX_ACTION_COUNT), target.getControllerId())
.getContent();
activeActions.forEach(action -> {
final DistributionSet distributionSet = action.getDistributionSet();
softwareModuleMetadata.computeIfAbsent(distributionSet.getId(),
id -> getSoftwareModulesWithMetadata(distributionSet));
});
if (!activeActions.isEmpty()) {
sendMultiActionRequestToTarget(tenant, target, activeActions, softwareModuleMetadata);
}
});
}
protected void sendMultiActionRequestToTarget(final String tenant, final Target target, final List<Action> actions,
final Map<Long, Map<SoftwareModule, List<SoftwareModuleMetadata>>> softwareModulesPerDistributionSet) {
final URI targetAdress = target.getAddress();
if (!IpUtil.isAmqpUri(targetAdress) || CollectionUtils.isEmpty(actions)) {
return;
}
final DmfMultiActionRequest multiActionRequest = new DmfMultiActionRequest();
actions.forEach(action -> {
final DmfActionRequest actionRequest = createDmfActionRequest(target, action,
softwareModulesPerDistributionSet.get(action.getDistributionSet().getId()));
multiActionRequest.addElement(getEventTypeForAction(action), actionRequest);
});
final Message message = getMessageConverter().toMessage(multiActionRequest,
createConnectorMessagePropertiesEvent(tenant, target.getControllerId(), EventTopic.MULTI_ACTION));
amqpSenderService.sendMessage(message, targetAdress);
}
private DmfActionRequest createDmfActionRequest(final Target target, final Action action,
final Map<SoftwareModule, List<SoftwareModuleMetadata>> softwareModules) {
if (action.isCancelingOrCanceled()) {
return createPlainActionRequest(action);
}
return createDownloadAndUpdateRequest(target, action, softwareModules);
}
private static DmfActionRequest createPlainActionRequest(final Action action) {
final DmfActionRequest actionRequest = new DmfActionRequest();
actionRequest.setActionId(action.getId());
return actionRequest;
}
private DmfDownloadAndUpdateRequest createDownloadAndUpdateRequest(final Target target, final Action action,
final Map<SoftwareModule, List<SoftwareModuleMetadata>> softwareModules) {
final DmfDownloadAndUpdateRequest request = new DmfDownloadAndUpdateRequest();
request.setActionId(action.getId());
request.setTargetSecurityToken(systemSecurityContext.runAsSystem(target::getSecurityToken));
if (softwareModules != null) {
softwareModules.entrySet()
.forEach(entry -> request.addSoftwareModule(convertToAmqpSoftwareModule(target, entry)));
}
return request;
}
/** /**
* Method to get the type of event depending on whether the action is a * 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 * DOWNLOAD_ONLY action or if it has a valid maintenance window available or
* or not based on defined maintenance schedule. In case of no maintenance * not based on defined maintenance schedule. In case of no maintenance
* schedule or if there is a valid window available, the topic {@link EventTopic#DOWNLOAD_AND_INSTALL} is * schedule or if there is a valid window available, the topic
* returned else {@link EventTopic#DOWNLOAD} is returned. * {@link EventTopic#DOWNLOAD_AND_INSTALL} is returned else
* {@link EventTopic#DOWNLOAD} is returned.
* *
* @param action * @param action
* current action properties. * current action properties.
@@ -165,8 +256,24 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
* @return {@link EventTopic} to use for message. * @return {@link EventTopic} to use for message.
*/ */
private static EventTopic getEventTypeForTarget(final ActionProperties action) { private static EventTopic getEventTypeForTarget(final ActionProperties action) {
return (Action.ActionType.DOWNLOAD_ONLY.equals(action.getActionType()) || return (Action.ActionType.DOWNLOAD_ONLY.equals(action.getActionType())
!action.isMaintenanceWindowAvailable()) ? EventTopic.DOWNLOAD : EventTopic.DOWNLOAD_AND_INSTALL; || !action.isMaintenanceWindowAvailable()) ? EventTopic.DOWNLOAD : 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));
} }
/** /**
@@ -174,7 +281,7 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
* the Distribution set to a Target has been canceled. * the Distribution set to a Target has been canceled.
* *
* @param cancelEvent * @param cancelEvent
* the object to be send. * that is to be converted to a DMF message
*/ */
@EventListener(classes = CancelTargetAssignmentEvent.class) @EventListener(classes = CancelTargetAssignmentEvent.class)
protected void targetCancelAssignmentToDistributionSet(final CancelTargetAssignmentEvent cancelEvent) { protected void targetCancelAssignmentToDistributionSet(final CancelTargetAssignmentEvent cancelEvent) {
@@ -211,7 +318,7 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
protected void sendUpdateMessageToTarget(final ActionProperties action, final Target target, protected void sendUpdateMessageToTarget(final ActionProperties action, final Target target,
final Map<SoftwareModule, List<SoftwareModuleMetadata>> modules) { final Map<SoftwareModule, List<SoftwareModuleMetadata>> modules) {
String tenant = action.getTenant(); final String tenant = action.getTenant();
final URI targetAdress = target.getAddress(); final URI targetAdress = target.getAddress();
if (!IpUtil.isAmqpUri(targetAdress)) { if (!IpUtil.isAmqpUri(targetAdress)) {
@@ -361,4 +468,17 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
return artifact; return artifact;
} }
private Map<SoftwareModule, List<SoftwareModuleMetadata>> getSoftwareModulesWithMetadata(
final DistributionSet distributionSet) {
final Map<SoftwareModule, List<SoftwareModuleMetadata>> moduleMetadata = Maps
.newHashMapWithExpectedSize(distributionSet.getModules().size());
distributionSet.getModules()
.forEach(
module -> moduleMetadata.put(module,
softwareModuleManagement.findMetaDataBySoftwareModuleIdAndTargetVisible(
PageRequest.of(0, RepositoryConstants.MAX_META_DATA_COUNT), module.getId())
.getContent()));
return moduleMetadata;
}
} }

View File

@@ -8,11 +8,16 @@
*/ */
package org.eclipse.hawkbit.amqp; package org.eclipse.hawkbit.amqp;
import static org.eclipse.hawkbit.repository.RepositoryConstants.MAX_ACTION_COUNT;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.MULTI_ASSIGNMENTS_ENABLED;
import java.io.Serializable;
import java.net.URI; import java.net.URI;
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.stream.Collectors; import java.util.stream.Collectors;
@@ -27,14 +32,17 @@ import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails;
import org.eclipse.hawkbit.repository.ControllerManagement; import org.eclipse.hawkbit.repository.ControllerManagement;
import org.eclipse.hawkbit.repository.EntityFactory; import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.RepositoryConstants; import org.eclipse.hawkbit.repository.RepositoryConstants;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.UpdateMode; import org.eclipse.hawkbit.repository.UpdateMode;
import org.eclipse.hawkbit.repository.builder.ActionStatusCreate; import org.eclipse.hawkbit.repository.builder.ActionStatusCreate;
import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.ActionProperties; import org.eclipse.hawkbit.repository.model.ActionProperties;
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.SoftwareModuleMetadata; import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata;
import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.util.IpUtil; import org.eclipse.hawkbit.util.IpUtil;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -42,6 +50,7 @@ import org.springframework.amqp.AmqpRejectAndDontRequeueException;
import org.springframework.amqp.core.Message; import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.data.domain.PageRequest;
import org.springframework.messaging.handler.annotation.Header; import org.springframework.messaging.handler.annotation.Header;
import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
@@ -51,8 +60,6 @@ import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextImpl; import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import com.google.common.collect.Maps;
/** /**
* *
* {@link AmqpMessageHandlerService} handles all incoming target interaction * {@link AmqpMessageHandlerService} handles all incoming target interaction
@@ -70,6 +77,10 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
private final EntityFactory entityFactory; private final EntityFactory entityFactory;
private final TenantConfigurationManagement tenantConfigurationManagement;
private final SystemSecurityContext systemSecurityContext;
/** /**
* Constructor. * Constructor.
* *
@@ -84,11 +95,15 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
*/ */
public AmqpMessageHandlerService(final RabbitTemplate rabbitTemplate, public AmqpMessageHandlerService(final RabbitTemplate rabbitTemplate,
final AmqpMessageDispatcherService amqpMessageDispatcherService, final AmqpMessageDispatcherService amqpMessageDispatcherService,
final ControllerManagement controllerManagement, final EntityFactory entityFactory) { final ControllerManagement controllerManagement, final EntityFactory entityFactory,
final SystemSecurityContext systemSecurityContext,
final TenantConfigurationManagement tenantConfigurationManagement) {
super(rabbitTemplate); super(rabbitTemplate);
this.amqpMessageDispatcherService = amqpMessageDispatcherService; this.amqpMessageDispatcherService = amqpMessageDispatcherService;
this.controllerManagement = controllerManagement; this.controllerManagement = controllerManagement;
this.entityFactory = entityFactory; this.entityFactory = entityFactory;
this.systemSecurityContext = systemSecurityContext;
this.tenantConfigurationManagement = tenantConfigurationManagement;
} }
/** /**
@@ -191,11 +206,31 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotExist(thingId, amqpUri); final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotExist(thingId, amqpUri);
LOG.debug("Target {} reported online state.", thingId); LOG.debug("Target {} reported online state.", thingId);
lookIfUpdateAvailable(target); sendUpdateCommandToTarget(target);
} }
private void lookIfUpdateAvailable(final Target target) { private void sendUpdateCommandToTarget(final Target target) {
if (isMultiAssignmentsEnabled()) {
sendCurrentActionsAsMultiActionToTarget(target);
} else {
sendOldestActionToTarget(target);
}
}
private void sendCurrentActionsAsMultiActionToTarget(final Target target) {
final List<Action> actions = controllerManagement
.findActiveActionsByTarget(PageRequest.of(0, MAX_ACTION_COUNT), target.getControllerId()).getContent();
final Set<DistributionSet> distributionSets = actions.stream().map(Action::getDistributionSet)
.collect(Collectors.toSet());
final Map<Long, Map<SoftwareModule, List<SoftwareModuleMetadata>>> softwareModulesPerDistributionSet = distributionSets
.stream().collect(Collectors.toMap(DistributionSet::getId, this::getSoftwareModulesWithMetadata));
amqpMessageDispatcherService.sendMultiActionRequestToTarget(target.getTenant(), target, actions,
softwareModulesPerDistributionSet);
}
private void sendOldestActionToTarget(final Target target) {
final Optional<Action> actionOptional = controllerManagement final Optional<Action> actionOptional = controllerManagement
.findOldestActiveActionByTarget(target.getControllerId()); .findOldestActiveActionByTarget(target.getControllerId());
@@ -207,20 +242,23 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
if (action.isCancelingOrCanceled()) { if (action.isCancelingOrCanceled()) {
amqpMessageDispatcherService.sendCancelMessageToTarget(target.getTenant(), target.getControllerId(), amqpMessageDispatcherService.sendCancelMessageToTarget(target.getTenant(), target.getControllerId(),
action.getId(), target.getAddress()); action.getId(), target.getAddress());
return; } else {
amqpMessageDispatcherService.sendUpdateMessageToTarget(new ActionProperties(action), action.getTarget(),
getSoftwareModulesWithMetadata(action.getDistributionSet()));
} }
}
final Map<SoftwareModule, List<SoftwareModuleMetadata>> modules = Maps private Map<SoftwareModule, List<SoftwareModuleMetadata>> getSoftwareModulesWithMetadata(
.newHashMapWithExpectedSize(action.getDistributionSet().getModules().size()); final DistributionSet distributionSet) {
final List<Long> smIds = distributionSet.getModules().stream().map(SoftwareModule::getId)
.collect(Collectors.toList());
final Map<Long, List<SoftwareModuleMetadata>> metadata = controllerManagement final Map<Long, List<SoftwareModuleMetadata>> metadata = controllerManagement
.findTargetVisibleMetaDataBySoftwareModuleId(action.getDistributionSet().getModules().stream() .findTargetVisibleMetaDataBySoftwareModuleId(smIds);
.map(SoftwareModule::getId).collect(Collectors.toList()));
action.getDistributionSet().getModules().forEach(module -> modules.put(module, metadata.get(module.getId()))); return distributionSet.getModules().stream()
.collect(Collectors.toMap(sm -> sm, sm -> metadata.getOrDefault(sm.getId(), Collections.emptyList())));
amqpMessageDispatcherService.sendUpdateMessageToTarget(new ActionProperties(action), action.getTarget(),
modules);
} }
/** /**
@@ -275,14 +313,19 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
final ActionStatusCreate actionStatus = entityFactory.actionStatus().create(action.getId()).status(status) final ActionStatusCreate actionStatus = entityFactory.actionStatus().create(action.getId()).status(status)
.messages(messages); .messages(messages);
final Action addUpdateActionStatus = getUpdateActionStatus(status, actionStatus); final Action updatedAction = Status.CANCELED.equals(status)
? controllerManagement.addCancelActionStatus(actionStatus)
: controllerManagement.addUpdateActionStatus(actionStatus);
if (!addUpdateActionStatus.isActive() || (addUpdateActionStatus.hasMaintenanceSchedule() if (shouldTargetProceed(updatedAction)) {
&& addUpdateActionStatus.isMaintenanceWindowAvailable())) { sendUpdateCommandToTarget(action.getTarget());
lookIfUpdateAvailable(action.getTarget());
} }
} }
private static boolean shouldTargetProceed(final Action action) {
return !action.isActive() || (action.hasMaintenanceSchedule() && action.isMaintenanceWindowAvailable());
}
private static boolean isCorrelationIdNotEmpty(final Message message) { private static boolean isCorrelationIdNotEmpty(final Message message) {
return StringUtils.hasLength(message.getMessageProperties().getCorrelationId()); return StringUtils.hasLength(message.getMessageProperties().getCorrelationId());
} }
@@ -337,13 +380,6 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
return null; return null;
} }
private Action getUpdateActionStatus(final Status status, final ActionStatusCreate actionStatus) {
if (Status.CANCELED.equals(status)) {
return controllerManagement.addCancelActionStatus(actionStatus);
}
return controllerManagement.addUpdateActionStatus(actionStatus);
}
// Exception squid:S3655 - logAndThrowMessageError throws exception, i.e. // Exception squid:S3655 - logAndThrowMessageError throws exception, i.e.
// get will not be called // get will not be called
@SuppressWarnings("squid:S3655") @SuppressWarnings("squid:S3655")
@@ -373,4 +409,13 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
return null; return null;
} }
private boolean isMultiAssignmentsEnabled() {
return getConfigValue(MULTI_ASSIGNMENTS_ENABLED, Boolean.class);
}
private <T extends Serializable> T getConfigValue(final String key, final Class<T> valueType) {
return systemSecurityContext
.runAsSystem(() -> tenantConfigurationManagement.getConfigurationValue(key, valueType).getValue());
}
} }

View File

@@ -16,14 +16,10 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import java.io.File;
import java.net.URL; import java.net.URL;
import java.util.Optional; import java.util.Optional;
import org.eclipse.hawkbit.api.HostnameResolver; import org.eclipse.hawkbit.api.HostnameResolver;
import org.eclipse.hawkbit.artifact.repository.ArtifactFilesystem;
import org.eclipse.hawkbit.artifact.repository.model.AbstractDbArtifact;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash;
import org.eclipse.hawkbit.cache.DownloadIdCache; import org.eclipse.hawkbit.cache.DownloadIdCache;
import org.eclipse.hawkbit.dmf.amqp.api.MessageHeaderKey; import org.eclipse.hawkbit.dmf.amqp.api.MessageHeaderKey;
import org.eclipse.hawkbit.dmf.amqp.api.MessageType; import org.eclipse.hawkbit.dmf.amqp.api.MessageType;
@@ -109,7 +105,7 @@ public class AmqpControllerAuthenticationTest {
private ControllerManagement controllerManagementMock; private ControllerManagement controllerManagementMock;
@Mock @Mock
private Target targteMock; private Target targetMock;
private static final TenantConfigurationValue<Boolean> CONFIG_VALUE_FALSE = TenantConfigurationValue private static final TenantConfigurationValue<Boolean> CONFIG_VALUE_FALSE = TenantConfigurationValue
.<Boolean> builder().value(Boolean.FALSE).build(); .<Boolean> builder().value(Boolean.FALSE).build();
@@ -137,11 +133,11 @@ public class AmqpControllerAuthenticationTest {
.thenReturn(CONFIG_VALUE_FALSE); .thenReturn(CONFIG_VALUE_FALSE);
final ControllerManagement controllerManagement = mock(ControllerManagement.class); final ControllerManagement controllerManagement = mock(ControllerManagement.class);
when(controllerManagement.getByControllerId(anyString())).thenReturn(Optional.of(targteMock)); when(controllerManagement.getByControllerId(anyString())).thenReturn(Optional.of(targetMock));
when(controllerManagement.get(any(Long.class))).thenReturn(Optional.of(targteMock)); when(controllerManagement.get(any(Long.class))).thenReturn(Optional.of(targetMock));
when(targteMock.getSecurityToken()).thenReturn(CONTROLLER_ID); when(targetMock.getSecurityToken()).thenReturn(CONTROLLER_ID);
when(targteMock.getControllerId()).thenReturn(CONTROLLER_ID); when(targetMock.getControllerId()).thenReturn(CONTROLLER_ID);
final SecurityContextTenantAware tenantAware = new SecurityContextTenantAware(); final SecurityContextTenantAware tenantAware = new SecurityContextTenantAware();
final SystemSecurityContext systemSecurityContext = new SystemSecurityContext(tenantAware); final SystemSecurityContext systemSecurityContext = new SystemSecurityContext(tenantAware);
@@ -162,11 +158,9 @@ public class AmqpControllerAuthenticationTest {
when(artifactManagementMock.get(ARTIFACT_ID)).thenReturn(Optional.of(testArtifact)); when(artifactManagementMock.get(ARTIFACT_ID)).thenReturn(Optional.of(testArtifact));
when(artifactManagementMock.findFirstBySHA1(SHA1)).thenReturn(Optional.of(testArtifact)); when(artifactManagementMock.findFirstBySHA1(SHA1)).thenReturn(Optional.of(testArtifact));
final AbstractDbArtifact artifact = new ArtifactFilesystem(new File("does not exist"), SHA1,
new DbArtifactHash(SHA1, "md5 test"), ARTIFACT_SIZE, null);
amqpMessageHandlerService = new AmqpMessageHandlerService(rabbitTemplate, amqpMessageHandlerService = new AmqpMessageHandlerService(rabbitTemplate,
mock(AmqpMessageDispatcherService.class), controllerManagementMock, new JpaEntityFactory()); mock(AmqpMessageDispatcherService.class), controllerManagementMock, new JpaEntityFactory(),
systemSecurityContext, tenantConfigurationManagementMock);
amqpAuthenticationMessageHandlerService = new AmqpAuthenticationMessageHandler(rabbitTemplate, amqpAuthenticationMessageHandlerService = new AmqpAuthenticationMessageHandler(rabbitTemplate,
authenticationManager, artifactManagementMock, cacheMock, hostnameResolverMock, authenticationManager, artifactManagementMock, cacheMock, hostnameResolverMock,

View File

@@ -13,7 +13,6 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyObject;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@@ -101,7 +100,7 @@ public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTest {
senderService = Mockito.mock(DefaultAmqpMessageSenderService.class); senderService = Mockito.mock(DefaultAmqpMessageSenderService.class);
final ArtifactUrlHandler artifactUrlHandlerMock = Mockito.mock(ArtifactUrlHandler.class); final ArtifactUrlHandler artifactUrlHandlerMock = Mockito.mock(ArtifactUrlHandler.class);
when(artifactUrlHandlerMock.getUrls(anyObject(), anyObject())) when(artifactUrlHandlerMock.getUrls(any(), any()))
.thenReturn(Arrays.asList(new ArtifactUrl("http", "download", "http://mockurl"))); .thenReturn(Arrays.asList(new ArtifactUrl("http", "download", "http://mockurl")));
systemManagement = Mockito.mock(SystemManagement.class); systemManagement = Mockito.mock(SystemManagement.class);
@@ -113,7 +112,7 @@ public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTest {
amqpMessageDispatcherService = new AmqpMessageDispatcherService(rabbitTemplate, senderService, amqpMessageDispatcherService = new AmqpMessageDispatcherService(rabbitTemplate, senderService,
artifactUrlHandlerMock, systemSecurityContext, systemManagement, targetManagement, serviceMatcher, artifactUrlHandlerMock, systemSecurityContext, systemManagement, targetManagement, serviceMatcher,
distributionSetManagement, softwareModuleManagement); distributionSetManagement, softwareModuleManagement, deploymentManagement);
} }
@@ -125,7 +124,7 @@ public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTest {
} }
private Action createAction(final DistributionSet testDs) { private Action createAction(final DistributionSet testDs) {
return deploymentManagement.findAction(assignDistributionSet(testDs, testTarget).getActions().get(0)).get(); return deploymentManagement.findAction(assignDistributionSet(testDs, testTarget).getActionIds().get(0)).get();
} }
@Test @Test

View File

@@ -9,6 +9,7 @@
package org.eclipse.hawkbit.amqp; package org.eclipse.hawkbit.amqp;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.MULTI_ASSIGNMENTS_ENABLED;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyLong;
@@ -39,6 +40,7 @@ import org.eclipse.hawkbit.dmf.json.model.DmfUpdateMode;
import org.eclipse.hawkbit.repository.ArtifactManagement; import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.ControllerManagement; import org.eclipse.hawkbit.repository.ControllerManagement;
import org.eclipse.hawkbit.repository.EntityFactory; import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.UpdateMode; import org.eclipse.hawkbit.repository.UpdateMode;
import org.eclipse.hawkbit.repository.builder.ActionStatusBuilder; import org.eclipse.hawkbit.repository.builder.ActionStatusBuilder;
import org.eclipse.hawkbit.repository.builder.ActionStatusCreate; import org.eclipse.hawkbit.repository.builder.ActionStatusCreate;
@@ -49,9 +51,12 @@ import org.eclipse.hawkbit.repository.model.ActionProperties;
import org.eclipse.hawkbit.repository.model.Artifact; import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TenantConfigurationValue;
import org.eclipse.hawkbit.security.DmfTenantSecurityToken; import org.eclipse.hawkbit.security.DmfTenantSecurityToken;
import org.eclipse.hawkbit.security.DmfTenantSecurityToken.FileResource; import org.eclipse.hawkbit.security.DmfTenantSecurityToken.FileResource;
import org.eclipse.hawkbit.security.SecurityContextTenantAware;
import org.eclipse.hawkbit.security.SecurityTokenGenerator; import org.eclipse.hawkbit.security.SecurityTokenGenerator;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAware; import org.eclipse.hawkbit.tenancy.TenantAware;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@@ -100,6 +105,9 @@ public class AmqpMessageHandlerServiceTest {
@Mock @Mock
private ArtifactManagement artifactManagementMock; private ArtifactManagement artifactManagementMock;
@Mock
private TenantConfigurationManagement tenantConfigurationManagement;
@Mock @Mock
private AmqpControllerAuthentication authenticationManagerMock; private AmqpControllerAuthentication authenticationManagerMock;
@@ -128,16 +136,21 @@ public class AmqpMessageHandlerServiceTest {
private ArgumentCaptor<UpdateMode> modeCaptor; private ArgumentCaptor<UpdateMode> modeCaptor;
@Before @Before
@SuppressWarnings({ "rawtypes", "unchecked" })
public void before() throws Exception { public void before() throws Exception {
messageConverter = new Jackson2JsonMessageConverter(); messageConverter = new Jackson2JsonMessageConverter();
when(rabbitTemplate.getMessageConverter()).thenReturn(messageConverter); when(rabbitTemplate.getMessageConverter()).thenReturn(messageConverter);
when(artifactManagementMock.findFirstBySHA1(SHA1)).thenReturn(Optional.empty()); when(artifactManagementMock.findFirstBySHA1(SHA1)).thenReturn(Optional.empty());
final TenantConfigurationValue multiAssignmentConfig = TenantConfigurationValue.builder().value(Boolean.FALSE)
.global(Boolean.FALSE).build();
when(tenantConfigurationManagement.getConfigurationValue(MULTI_ASSIGNMENTS_ENABLED, Boolean.class))
.thenReturn(multiAssignmentConfig);
final SecurityContextTenantAware tenantAware = new SecurityContextTenantAware();
final SystemSecurityContext systemSecurityContext = new SystemSecurityContext(tenantAware);
amqpMessageHandlerService = new AmqpMessageHandlerService(rabbitTemplate, amqpMessageDispatcherServiceMock, amqpMessageHandlerService = new AmqpMessageHandlerService(rabbitTemplate, amqpMessageDispatcherServiceMock,
controllerManagementMock, entityFactoryMock); controllerManagementMock, entityFactoryMock, systemSecurityContext, tenantConfigurationManagement);
amqpMessageHandlerService = new AmqpMessageHandlerService(rabbitTemplate, amqpMessageDispatcherServiceMock,
controllerManagementMock, entityFactoryMock);
amqpAuthenticationMessageHandlerService = new AmqpAuthenticationMessageHandler(rabbitTemplate, amqpAuthenticationMessageHandlerService = new AmqpAuthenticationMessageHandler(rabbitTemplate,
authenticationManagerMock, artifactManagementMock, downloadIdCache, hostnameResolverMock, authenticationManagerMock, artifactManagementMock, downloadIdCache, hostnameResolverMock,
controllerManagementMock, tenantAwareMock); controllerManagementMock, tenantAwareMock);
@@ -465,8 +478,8 @@ public class AmqpMessageHandlerServiceTest {
final ArgumentCaptor<ActionProperties> actionPropertiesCaptor = ArgumentCaptor.forClass(ActionProperties.class); final ArgumentCaptor<ActionProperties> actionPropertiesCaptor = ArgumentCaptor.forClass(ActionProperties.class);
final ArgumentCaptor<Target> targetCaptor = ArgumentCaptor.forClass(Target.class); final ArgumentCaptor<Target> targetCaptor = ArgumentCaptor.forClass(Target.class);
verify(amqpMessageDispatcherServiceMock, times(1)) verify(amqpMessageDispatcherServiceMock, times(1)).sendUpdateMessageToTarget(actionPropertiesCaptor.capture(),
.sendUpdateMessageToTarget(actionPropertiesCaptor.capture(), targetCaptor.capture(), any(Map.class)); targetCaptor.capture(), any(Map.class));
final ActionProperties actionProperties = actionPropertiesCaptor.getValue(); final ActionProperties actionProperties = actionPropertiesCaptor.getValue();
assertThat(actionProperties).isNotNull(); assertThat(actionProperties).isNotNull();
assertThat(actionProperties.getTenant()).as("event has tenant").isEqualTo("DEFAULT"); assertThat(actionProperties.getTenant()).as("event has tenant").isEqualTo("DEFAULT");

View File

@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.integration;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
@@ -69,8 +70,8 @@ public abstract class AbstractAmqpServiceIntegrationTest extends AbstractAmqpInt
protected static final String TENANT_EXIST = "DEFAULT"; protected static final String TENANT_EXIST = "DEFAULT";
protected static final String CREATED_BY = "CONTROLLER_PLUG_AND_PLAY"; protected static final String CREATED_BY = "CONTROLLER_PLUG_AND_PLAY";
protected ReplyToListener replyToListener;
private DeadletterListener deadletterListener; private DeadletterListener deadletterListener;
private ReplyToListener replyToListener;
private DistributionSet distributionSet; private DistributionSet distributionSet;
@Autowired @Autowired
@@ -86,11 +87,12 @@ public abstract class AbstractAmqpServiceIntegrationTest extends AbstractAmqpInt
Mockito.reset(deadletterListener); Mockito.reset(deadletterListener);
replyToListener = harness.getSpy(ReplyToListener.LISTENER_ID); replyToListener = harness.getSpy(ReplyToListener.LISTENER_ID);
assertThat(replyToListener).isNotNull(); assertThat(replyToListener).isNotNull();
replyToListener.purge();
Mockito.reset(replyToListener); Mockito.reset(replyToListener);
getDmfClient().setExchange(AmqpSettings.DMF_EXCHANGE); getDmfClient().setExchange(AmqpSettings.DMF_EXCHANGE);
} }
protected <T> T waitUntilIsPresent(final Callable<Optional<T>> callable) { private <T> T waitUntilIsPresent(final Callable<Optional<T>> callable) {
createConditionFactory().until(() -> { createConditionFactory().until(() -> {
return securityRule.runAsPrivileged(() -> callable.call().isPresent()); return securityRule.runAsPrivileged(() -> callable.call().isPresent());
}); });
@@ -102,10 +104,12 @@ public abstract class AbstractAmqpServiceIntegrationTest extends AbstractAmqpInt
} }
} }
protected void verifyDeadLetterMessages(final int expectedMessages) { protected void waitUntilEventMessagesAreDispatchedToTarget(final EventTopic... eventTopics) {
createConditionFactory().untilAsserted(() -> { createConditionFactory().untilAsserted(() -> {
Mockito.verify(getDeadletterListener(), Mockito.times(expectedMessages)).handleMessage(Mockito.any()); assertThat(replyToListener.getLatestEventMessageTopics())
.containsExactlyInAnyOrderElementsOf(Arrays.asList(eventTopics));
}); });
replyToListener.resetLatestEventMessageTopics();
} }
protected DeadletterListener getDeadletterListener() { protected DeadletterListener getDeadletterListener() {
@@ -158,7 +162,7 @@ public abstract class AbstractAmqpServiceIntegrationTest extends AbstractAmqpInt
} }
protected void assertRequestAttributesUpdateMessageAbsent() { protected void assertRequestAttributesUpdateMessageAbsent() {
assertThat(replyToListener.getEventTopicMessages()).doesNotContainKey(EventTopic.REQUEST_ATTRIBUTES_UPDATE); assertThat(replyToListener.getEventMessages()).doesNotContainKey(EventTopic.REQUEST_ATTRIBUTES_UPDATE);
} }
protected void assertPingReplyMessage(final String correlationId) { protected void assertPingReplyMessage(final String correlationId) {
@@ -196,15 +200,17 @@ public abstract class AbstractAmqpServiceIntegrationTest extends AbstractAmqpInt
assertThat(updatedTarget.getSecurityToken()).isEqualTo(downloadAndUpdateRequest.getTargetSecurityToken()); assertThat(updatedTarget.getSecurityToken()).isEqualTo(downloadAndUpdateRequest.getTargetSecurityToken());
} }
protected void assertDownloadAndInstallMessage(final Set<SoftwareModule> dsModules, final String controllerId) { protected void assertDownloadAndInstallMessage(final Set<SoftwareModule> softwareModules,
assertAssignmentMessage(dsModules, controllerId, EventTopic.DOWNLOAD_AND_INSTALL); final String controllerId) {
assertAssignmentMessage(softwareModules, controllerId, EventTopic.DOWNLOAD_AND_INSTALL);
} }
protected void assertDownloadMessage(final Set<SoftwareModule> dsModules, final String controllerId) { protected void assertDownloadMessage(final Set<SoftwareModule> dsModules, final String controllerId) {
assertAssignmentMessage(dsModules, controllerId, EventTopic.DOWNLOAD); assertAssignmentMessage(dsModules, controllerId, EventTopic.DOWNLOAD);
} }
protected void createAndSendTarget(final String target, final String tenant) { protected void createAndSendThingCreated(final String target, final String tenant) {
final Message message = createTargetMessage(target, tenant); final Message message = createTargetMessage(target, tenant);
getDmfClient().send(message); getDmfClient().send(message);
} }
@@ -229,7 +235,7 @@ public abstract class AbstractAmqpServiceIntegrationTest extends AbstractAmqpInt
protected Long registerTargetAndCancelActionId(final String controllerId) { protected Long registerTargetAndCancelActionId(final String controllerId) {
final DistributionSetAssignmentResult assignmentResult = registerTargetAndAssignDistributionSet(controllerId); final DistributionSetAssignmentResult assignmentResult = registerTargetAndAssignDistributionSet(controllerId);
return cancelAction(assignmentResult.getActions().get(0), controllerId); return cancelAction(assignmentResult.getActionIds().get(0), controllerId);
} }
protected void assertAllTargetsCount(final long expectedTargetsCount) { protected void assertAllTargetsCount(final long expectedTargetsCount) {
@@ -238,7 +244,8 @@ public abstract class AbstractAmqpServiceIntegrationTest extends AbstractAmqpInt
protected Message assertReplyMessageHeader(final EventTopic eventTopic, final String controllerId) { protected Message assertReplyMessageHeader(final EventTopic eventTopic, final String controllerId) {
verifyReplyToListener(); verifyReplyToListener();
final Message replyMessage = replyToListener.getEventTopicMessages().get(eventTopic);
final Message replyMessage = replyToListener.getLatestEventMessage(eventTopic);
assertAllTargetsCount(1); assertAllTargetsCount(1);
final Map<String, Object> headers = replyMessage.getMessageProperties().getHeaders(); final Map<String, Object> headers = replyMessage.getMessageProperties().getHeaders();
assertThat(headers.get(MessageHeaderKey.TOPIC)).isEqualTo(eventTopic.toString()); assertThat(headers.get(MessageHeaderKey.TOPIC)).isEqualTo(eventTopic.toString());
@@ -264,7 +271,7 @@ public abstract class AbstractAmqpServiceIntegrationTest extends AbstractAmqpInt
protected void registerAndAssertTargetWithExistingTenant(final String target, protected void registerAndAssertTargetWithExistingTenant(final String target,
final int existingTargetsAfterCreation, final TargetUpdateStatus expectedTargetStatus, final int existingTargetsAfterCreation, final TargetUpdateStatus expectedTargetStatus,
final String createdBy) { final String createdBy) {
createAndSendTarget(target, TENANT_EXIST); createAndSendThingCreated(target, TENANT_EXIST);
final Target registerdTarget = waitUntilIsPresent(() -> targetManagement.getByControllerID(target)); final Target registerdTarget = waitUntilIsPresent(() -> targetManagement.getByControllerID(target));
assertAllTargetsCount(existingTargetsAfterCreation); assertAllTargetsCount(existingTargetsAfterCreation);
assertTarget(registerdTarget, expectedTargetStatus, createdBy); assertTarget(registerdTarget, expectedTargetStatus, createdBy);
@@ -273,7 +280,7 @@ public abstract class AbstractAmqpServiceIntegrationTest extends AbstractAmqpInt
protected void registerSameTargetAndAssertBasedOnVersion(final String controllerId, protected void registerSameTargetAndAssertBasedOnVersion(final String controllerId,
final int existingTargetsAfterCreation, final TargetUpdateStatus expectedTargetStatus) { final int existingTargetsAfterCreation, final TargetUpdateStatus expectedTargetStatus) {
final int version = controllerManagement.getByControllerId(controllerId).get().getOptLockRevision(); final int version = controllerManagement.getByControllerId(controllerId).get().getOptLockRevision();
createAndSendTarget(controllerId, TENANT_EXIST); createAndSendThingCreated(controllerId, TENANT_EXIST);
final Target registeredTarget = waitUntilIsPresent(() -> findTargetBasedOnNewVersion(controllerId, version)); final Target registeredTarget = waitUntilIsPresent(() -> findTargetBasedOnNewVersion(controllerId, version));
assertAllTargetsCount(existingTargetsAfterCreation); assertAllTargetsCount(existingTargetsAfterCreation);
assertThat(registeredTarget.getUpdateStatus()).isEqualTo(expectedTargetStatus); assertThat(registeredTarget.getUpdateStatus()).isEqualTo(expectedTargetStatus);
@@ -315,7 +322,7 @@ public abstract class AbstractAmqpServiceIntegrationTest extends AbstractAmqpInt
return createMessage(null, messageProperties); return createMessage(null, messageProperties);
} }
protected Message createActionStatusUpdateMessage(final String target, final String tenant, final long actionId, protected void createAndSendActionStatusUpdateMessage(final String target, final String tenant, final long actionId,
final DmfActionStatus status) { final DmfActionStatus status) {
final MessageProperties messageProperties = createMessagePropertiesWithTenant(tenant); final MessageProperties messageProperties = createMessagePropertiesWithTenant(tenant);
messageProperties.getHeaders().put(MessageHeaderKey.THING_ID, target); messageProperties.getHeaders().put(MessageHeaderKey.THING_ID, target);
@@ -323,7 +330,7 @@ public abstract class AbstractAmqpServiceIntegrationTest extends AbstractAmqpInt
messageProperties.getHeaders().put(MessageHeaderKey.TOPIC, EventTopic.UPDATE_ACTION_STATUS.toString()); messageProperties.getHeaders().put(MessageHeaderKey.TOPIC, EventTopic.UPDATE_ACTION_STATUS.toString());
final DmfActionUpdateStatus dmfActionUpdateStatus = new DmfActionUpdateStatus(actionId, status); final DmfActionUpdateStatus dmfActionUpdateStatus = new DmfActionUpdateStatus(actionId, status);
return createMessage(dmfActionUpdateStatus, messageProperties); getDmfClient().send(createMessage(dmfActionUpdateStatus, messageProperties));
} }
protected MessageProperties createMessagePropertiesWithTenant(final String tenant) { protected MessageProperties createMessagePropertiesWithTenant(final String tenant) {

View File

@@ -8,13 +8,30 @@
*/ */
package org.eclipse.hawkbit.integration; package org.eclipse.hawkbit.integration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.eclipse.hawkbit.dmf.amqp.api.EventTopic.DOWNLOAD;
import static org.eclipse.hawkbit.dmf.amqp.api.MessageType.EVENT;
import static org.eclipse.hawkbit.repository.model.Action.ActionType.DOWNLOAD_ONLY;
import java.util.AbstractMap.SimpleEntry;
import java.util.Arrays;
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.UUID; 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.DmfActionStatus; import org.eclipse.hawkbit.dmf.json.model.DmfActionStatus;
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.repository.event.remote.MultiActionEvent;
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;
@@ -23,6 +40,10 @@ 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.CancelTargetAssignmentEvent; import org.eclipse.hawkbit.repository.event.remote.entity.CancelTargetAssignmentEvent;
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.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;
@@ -30,10 +51,13 @@ import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
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.Rollout;
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;
import org.eclipse.hawkbit.repository.test.matcher.Expect; import org.eclipse.hawkbit.repository.test.matcher.Expect;
import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents; import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.junit.Test; import org.junit.Test;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.springframework.amqp.core.Message; import org.springframework.amqp.core.Message;
@@ -42,11 +66,6 @@ import io.qameta.allure.Description;
import io.qameta.allure.Feature; import io.qameta.allure.Feature;
import io.qameta.allure.Story; import io.qameta.allure.Story;
import static org.assertj.core.api.Assertions.assertThat;
import static org.eclipse.hawkbit.dmf.amqp.api.EventTopic.DOWNLOAD;
import static org.eclipse.hawkbit.dmf.amqp.api.MessageType.EVENT;
import static org.eclipse.hawkbit.repository.model.Action.ActionType.DOWNLOAD_ONLY;
@Feature("Component Tests - Device Management Federation API") @Feature("Component Tests - Device Management Federation API")
@Story("Amqp Message Dispatcher Service") @Story("Amqp Message Dispatcher Service")
public class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpServiceIntegrationTest { public class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpServiceIntegrationTest {
@@ -131,14 +150,222 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpSer
final DistributionSet distributionSet2 = testdataFactory.createDistributionSet(UUID.randomUUID().toString()); final DistributionSet distributionSet2 = testdataFactory.createDistributionSet(UUID.randomUUID().toString());
registerTargetAndAssignDistributionSet(distributionSet2.getId(), TargetUpdateStatus.PENDING, registerTargetAndAssignDistributionSet(distributionSet2.getId(), TargetUpdateStatus.PENDING,
getDistributionSet().getModules(), controllerId); getDistributionSet().getModules(), controllerId);
assertCancelActionMessage(assignmentResult.getActions().get(0), controllerId); assertCancelActionMessage(assignmentResult.getActionIds().get(0), controllerId);
createAndSendTarget(controllerId, TENANT_EXIST); createAndSendThingCreated(controllerId, TENANT_EXIST);
waitUntilTargetHasStatus(controllerId, TargetUpdateStatus.PENDING); waitUntilTargetHasStatus(controllerId, TargetUpdateStatus.PENDING);
assertCancelActionMessage(assignmentResult.getActions().get(0), controllerId); assertCancelActionMessage(assignmentResult.getActionIds().get(0), controllerId);
} }
@Test
@Description("If multi assignment is enabled multi-action messages are sent.")
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = MultiActionEvent.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 = SoftwareModuleCreatedEvent.class, count = 6),
@Expect(type = DistributionSetCreatedEvent.class, count = 2),
@Expect(type = TargetUpdatedEvent.class, count = 2), @Expect(type = TargetPollEvent.class, count = 1) })
public void assignMultipleDsInMultiAssignMode() {
enableMultiAssignments();
final String controllerId = TARGET_PREFIX + "assignMultipleDsInMultiAssignMode";
registerAndAssertTargetWithExistingTenant(controllerId);
final Long actionId1 = assignNewDsToTarget(controllerId);
final Entry<Long, EventTopic> action1Install = new SimpleEntry<>(actionId1, EventTopic.DOWNLOAD_AND_INSTALL);
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION);
assertLatestMultiActionMessage(controllerId, Arrays.asList(action1Install));
final Long actionId2 = assignNewDsToTarget(controllerId);
final Entry<Long, EventTopic> action2Install = new SimpleEntry<>(actionId2, EventTopic.DOWNLOAD_AND_INSTALL);
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION);
assertLatestMultiActionMessage(controllerId, Arrays.asList(action1Install, action2Install));
}
@Test
@Description("Handle cancelation process of an action in multi assignment mode.")
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = MultiActionEvent.class, count = 3),
@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 = SoftwareModuleCreatedEvent.class, count = 6),
@Expect(type = DistributionSetCreatedEvent.class, count = 2),
@Expect(type = TargetUpdatedEvent.class, count = 2), @Expect(type = TargetPollEvent.class, count = 1) })
public void cancelActionInMultiAssignMode() {
enableMultiAssignments();
final String controllerId = TARGET_PREFIX + "cancelActionInMultiAssignMode";
registerAndAssertTargetWithExistingTenant(controllerId);
final long actionId1 = assignNewDsToTarget(controllerId);
final long actionId2 = assignNewDsToTarget(controllerId);
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION, EventTopic.MULTI_ACTION);
deploymentManagement.cancelAction(actionId1);
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION);
final Entry<Long, EventTopic> action1Cancel = new SimpleEntry<>(actionId1, EventTopic.CANCEL_DOWNLOAD);
final Entry<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, Arrays.asList(action2Install));
}
@Test
@Description("Handle finishing an action in multi assignment mode.")
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = MultiActionEvent.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 = SoftwareModuleCreatedEvent.class, count = 6),
@Expect(type = DistributionSetCreatedEvent.class, count = 2),
@Expect(type = TargetUpdatedEvent.class, count = 3), @Expect(type = TargetPollEvent.class, count = 1) })
public void finishActionInMultiAssignMode() {
enableMultiAssignments();
final String controllerId = TARGET_PREFIX + "finishActionInMultiAssignMode";
registerAndAssertTargetWithExistingTenant(controllerId);
final long actionId1 = assignNewDsToTarget(controllerId);
final long actionId2 = assignNewDsToTarget(controllerId);
final Entry<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, Arrays.asList(action2Install));
}
@Test
@Description("If multi assignment is enabled assigning a DS multiple times creates a new action every time.")
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = MultiActionEvent.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 = SoftwareModuleCreatedEvent.class, count = 3),
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
@Expect(type = TargetUpdatedEvent.class, count = 2), @Expect(type = TargetPollEvent.class, count = 1) })
public void assignDsMultipleTimesInMultiAssignMode() {
enableMultiAssignments();
final String controllerId = TARGET_PREFIX + "assignDsMultipleTimesInMultiAssignMode";
registerAndAssertTargetWithExistingTenant(controllerId);
final DistributionSet ds = testdataFactory.createDistributionSet(UUID.randomUUID().toString());
final Long actionId1 = assignDistributionSet(ds.getId(), controllerId).getActionIds().get(0);
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION);
final Long actionId2 = assignDistributionSet(ds.getId(), controllerId).getActionIds().get(0);
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION);
final Entry<Long, EventTopic> action1Install = new SimpleEntry<>(actionId1, EventTopic.DOWNLOAD_AND_INSTALL);
final Entry<Long, EventTopic> action2Install = new SimpleEntry<>(actionId2, EventTopic.DOWNLOAD_AND_INSTALL);
assertLatestMultiActionMessage(controllerId, Arrays.asList(action1Install, action2Install));
}
private void updateActionViaDmfClient(final String controllerId, final long actionId,
final DmfActionStatus status) {
createAndSendActionStatusUpdateMessage(controllerId, TENANT_EXIST, actionId, status);
}
private Long assignNewDsToTarget(final String controllerId) {
final DistributionSet ds = testdataFactory.createDistributionSet(UUID.randomUUID().toString());
final Long actionId = assignDistributionSet(ds.getId(), controllerId).getActionIds().get(0);
waitUntilTargetHasStatus(controllerId, TargetUpdateStatus.PENDING);
return actionId;
}
@Test
@Description("If multi assignment is enabled multiple rollouts with the same DS lead to multiple actions.")
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = MultiActionEvent.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 = SoftwareModuleCreatedEvent.class, count = 3),
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
@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 = 4) })
public void startRolloutsWithSameDsInMultiAssignMode() {
enableMultiAssignments();
final String controllerId = TARGET_PREFIX + "startRolloutsWithSameDsInMultiAssignMode";
registerAndAssertTargetWithExistingTenant(controllerId);
final DistributionSet ds = testdataFactory.createDistributionSet(UUID.randomUUID().toString());
final Set<Long> smIds = getSoftwareModuleIds(ds);
final String filterQuery = "controllerId==" + controllerId;
createAndStartRollout(ds, filterQuery);
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION);
assertLatestMultiActionMessageContainsInstallMessages(controllerId, Arrays.asList(smIds));
createAndStartRollout(ds, filterQuery);
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION);
assertLatestMultiActionMessageContainsInstallMessages(controllerId, Arrays.asList(smIds, smIds));
}
@Test
@Description("If multi assignment is enabled finishing one rollout does not affect other rollouts of the target.")
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = MultiActionEvent.class, count = 3), @Expect(type = ActionCreatedEvent.class, count = 3),
@Expect(type = ActionUpdatedEvent.class, count = 5),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 6),
@Expect(type = DistributionSetCreatedEvent.class, count = 2),
@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 = 6) })
public void startMultipleRolloutsAndFinishInMultiAssignMode() {
enableMultiAssignments();
final String controllerId = TARGET_PREFIX + "startMultipleRolloutsAndFinishInMultiAssignMode";
registerAndAssertTargetWithExistingTenant(controllerId);
final String filterQuery = "controllerId==" + controllerId;
final DistributionSet ds1 = testdataFactory.createDistributionSet(UUID.randomUUID().toString());
final Set<Long> smIds1 = getSoftwareModuleIds(ds1);
final DistributionSet ds2 = testdataFactory.createDistributionSet(UUID.randomUUID().toString());
final Set<Long> smIds2 = getSoftwareModuleIds(ds2);
createAndStartRollout(ds1, filterQuery);
createAndStartRollout(ds2, filterQuery);
waitUntilEventMessagesAreDispatchedToTarget(EventTopic.MULTI_ACTION, EventTopic.MULTI_ACTION);
createAndStartRollout(ds1, filterQuery);
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)
.collect(Collectors.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, Arrays.asList(smIds1));
}
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 Rollout rollout = testdataFactory.createRolloutByVariables(UUID.randomUUID().toString(), "", 1,
filterQuery, ds, "50", "5");
rolloutManagement.start(rollout.getId());
rolloutManagement.handleRollouts();
return rollout;
}
@Test @Test
@Description("Verify that a cancel assignment send a cancel message.") @Description("Verify that a cancel assignment send a cancel message.")
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1), @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@@ -154,7 +381,7 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpSer
final Long actionId = registerTargetAndCancelActionId(controllerId); final Long actionId = registerTargetAndCancelActionId(controllerId);
createAndSendTarget(controllerId, TENANT_EXIST); createAndSendThingCreated(controllerId, TENANT_EXIST);
waitUntilTargetHasStatus(controllerId, TargetUpdateStatus.PENDING); waitUntilTargetHasStatus(controllerId, TargetUpdateStatus.PENDING);
assertCancelActionMessage(actionId, controllerId); assertCancelActionMessage(actionId, controllerId);
} }
@@ -171,39 +398,28 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpSer
assertDeleteMessage(controllerId); assertDeleteMessage(controllerId);
} }
@Test @Test
@Description("Verify that attribute update is requested after device successfully closed software update.") @Description("Verify that attribute update is requested after device successfully closed software update.")
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1), @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = TargetAssignDistributionSetEvent.class, count = 2), @Expect(type = TargetAssignDistributionSetEvent.class, count = 2),
@Expect(type = ActionUpdatedEvent.class, count = 2), @Expect(type = ActionCreatedEvent.class, count = 2), @Expect(type = ActionUpdatedEvent.class, count = 2), @Expect(type = ActionCreatedEvent.class, count = 2),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3), @Expect(type = SoftwareModuleCreatedEvent.class, count = 6),
@Expect(type = DistributionSetCreatedEvent.class, count = 1), @Expect(type = DistributionSetCreatedEvent.class, count = 2),
@Expect(type = TargetUpdatedEvent.class, count = 4), @Expect(type = TargetUpdatedEvent.class, count = 4),
@Expect(type = TargetAttributesRequestedEvent.class, count = 1), @Expect(type = TargetAttributesRequestedEvent.class, count = 1),
@Expect(type = TargetPollEvent.class, count = 1) }) @Expect(type = TargetPollEvent.class, count = 1) })
public void attributeRequestAfterSuccessfulUpdate() { public void attributeRequestAfterSuccessfulUpdate() {
final String controllerId = TARGET_PREFIX + "attributeUpdateRequest"; final String controllerId = TARGET_PREFIX + "attributeUpdateRequest";
registerAndAssertTargetWithExistingTenant(controllerId); registerAndAssertTargetWithExistingTenant(controllerId);
final Target target = controllerManagement.getByControllerId(controllerId).get();
final DistributionSet distributionSet = testdataFactory.createDistributionSet(UUID.randomUUID().toString());
final long actionId1 = assignDistributionSet(distributionSet, target).getActions().get(0); final long actionId1 = assignNewDsToTarget(controllerId);
waitUntilTargetHasStatus(controllerId, TargetUpdateStatus.PENDING); updateActionViaDmfClient(controllerId, actionId1, DmfActionStatus.ERROR);
final Message messageError = createActionStatusUpdateMessage(controllerId, TENANT_EXIST, actionId1,
DmfActionStatus.ERROR);
getDmfClient().send(messageError);
waitUntilTargetHasStatus(controllerId, TargetUpdateStatus.ERROR); waitUntilTargetHasStatus(controllerId, TargetUpdateStatus.ERROR);
assertRequestAttributesUpdateMessageAbsent(); assertRequestAttributesUpdateMessageAbsent();
final long actionId2 = assignDistributionSet(distributionSet, target).getActions().get(0); final long actionId2 = assignNewDsToTarget(controllerId);
waitUntilTargetHasStatus(controllerId, TargetUpdateStatus.PENDING); updateActionViaDmfClient(controllerId, actionId2, DmfActionStatus.FINISHED);
final Message messageFin = createActionStatusUpdateMessage(controllerId, TENANT_EXIST, actionId2,
DmfActionStatus.FINISHED);
getDmfClient().send(messageFin);
waitUntilTargetHasStatus(controllerId, TargetUpdateStatus.IN_SYNC); waitUntilTargetHasStatus(controllerId, TargetUpdateStatus.IN_SYNC);
assertRequestAttributesUpdateMessage(controllerId); assertRequestAttributesUpdateMessage(controllerId);
} }
@@ -220,7 +436,6 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpSer
final String controllerId = TARGET_PREFIX + "registerTargets_1"; final String controllerId = TARGET_PREFIX + "registerTargets_1";
final DistributionSet distributionSet = createTargetAndDistributionSetAndAssign(controllerId, DOWNLOAD_ONLY); final DistributionSet distributionSet = createTargetAndDistributionSetAndAssign(controllerId, DOWNLOAD_ONLY);
// verify
final Message message = assertReplyMessageHeader(EventTopic.DOWNLOAD, controllerId); final Message message = assertReplyMessageHeader(EventTopic.DOWNLOAD, controllerId);
Mockito.verifyZeroInteractions(getDeadletterListener()); Mockito.verifyZeroInteractions(getDeadletterListener());
@@ -249,4 +464,54 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpSer
private void waitUntil(final Callable<Boolean> callable) { private void waitUntil(final Callable<Boolean> callable) {
createConditionFactory().until(() -> securityRule.runAsPrivileged(callable)); createConditionFactory().until(() -> securityRule.runAsPrivileged(callable));
} }
private void enableMultiAssignments() {
tenantConfigurationManagement.addOrUpdateConfiguration(TenantConfigurationKey.MULTI_ASSIGNMENTS_ENABLED, true);
}
private void assertLatestMultiActionMessageContainsInstallMessages(final String controllerId,
final List<Set<Long>> smIdsOfActionsExpected) {
final Message multiactionMessage = replyToListener.getLatestEventMessage(EventTopic.MULTI_ACTION);
assertThat(multiactionMessage.getMessageProperties().getHeaders().get(MessageHeaderKey.THING_ID))
.isEqualTo(controllerId);
final DmfMultiActionRequest multiActionRequest = (DmfMultiActionRequest) getDmfClient().getMessageConverter()
.fromMessage(multiactionMessage);
final List<Set<Long>> smIdsOfActionsFound = getDownloadAndUpdateRequests(multiActionRequest).stream()
.map(AmqpMessageDispatcherServiceIntegrationTest::getSmIds).collect(Collectors.toList());
assertThat(smIdsOfActionsFound).containsExactlyInAnyOrderElementsOf(smIdsOfActionsExpected);
}
private void assertLatestMultiActionMessage(final String controllerId,
final List<Entry<Long, EventTopic>> actionsExpected) {
final List<Entry<Long, EventTopic>> actionsFromMessage = getLatestMultiActionMessageActions(controllerId);
assertThat(actionsFromMessage).containsExactlyInAnyOrderElementsOf(actionsExpected);
}
private List<Entry<Long, EventTopic>> getLatestMultiActionMessageActions(final String expectedControllerId) {
final Message multiactionMessage = replyToListener.getLatestEventMessage(EventTopic.MULTI_ACTION);
assertThat(multiactionMessage.getMessageProperties().getHeaders().get(MessageHeaderKey.THING_ID))
.isEqualTo(expectedControllerId);
final List<DmfMultiActionElement> multiActionRequest = ((DmfMultiActionRequest) getDmfClient()
.getMessageConverter().fromMessage(multiactionMessage)).getElements();
return multiActionRequest.stream()
.map(request -> new SimpleEntry<>(request.getAction().getActionId(), request.getTopic()))
.collect(Collectors.toList());
}
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()).collect(Collectors.toList());
}
private static boolean isDownloadAndUpdateRequest(final DmfMultiActionElement multiActionElement) {
return multiActionElement.getTopic().equals(EventTopic.DOWNLOAD)
|| multiActionElement.getTopic().equals(EventTopic.DOWNLOAD_AND_INSTALL);
}
} }

View File

@@ -104,7 +104,7 @@ public class AmqpMessageHandlerServiceIntegrationTest extends AbstractAmqpServic
@Description("Tests register invalid target withy empty controller id. Tests register invalid target with null controller id") @Description("Tests register invalid target withy empty controller id. Tests register invalid target with null controller id")
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) }) @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) })
public void registerEmptyTarget() { public void registerEmptyTarget() {
createAndSendTarget("", TENANT_EXIST); createAndSendThingCreated("", TENANT_EXIST);
assertAllTargetsCount(0); assertAllTargetsCount(0);
verifyOneDeadLetterMessage(); verifyOneDeadLetterMessage();
@@ -114,7 +114,7 @@ public class AmqpMessageHandlerServiceIntegrationTest extends AbstractAmqpServic
@Description("Tests register invalid target with whitspace controller id. Tests register invalid target with null controller id") @Description("Tests register invalid target with whitspace controller id. Tests register invalid target with null controller id")
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) }) @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) })
public void registerWhitespaceTarget() { public void registerWhitespaceTarget() {
createAndSendTarget("Invalid Invalid", TENANT_EXIST); createAndSendThingCreated("Invalid Invalid", TENANT_EXIST);
assertAllTargetsCount(0); assertAllTargetsCount(0);
verifyOneDeadLetterMessage(); verifyOneDeadLetterMessage();
@@ -124,7 +124,7 @@ public class AmqpMessageHandlerServiceIntegrationTest extends AbstractAmqpServic
@Description("Tests register invalid target with null controller id. Tests register invalid target with null controller id") @Description("Tests register invalid target with null controller id. Tests register invalid target with null controller id")
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) }) @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) })
public void registerInvalidNullTargets() { public void registerInvalidNullTargets() {
createAndSendTarget(null, TENANT_EXIST); createAndSendThingCreated(null, TENANT_EXIST);
assertAllTargetsCount(0); assertAllTargetsCount(0);
verifyOneDeadLetterMessage(); verifyOneDeadLetterMessage();
@@ -571,13 +571,13 @@ public class AmqpMessageHandlerServiceIntegrationTest extends AbstractAmqpServic
final DistributionSet distributionSet = testdataFactory.createDistributionSet(UUID.randomUUID().toString()); final DistributionSet distributionSet = testdataFactory.createDistributionSet(UUID.randomUUID().toString());
final DistributionSetAssignmentResult distributionSetAssignmentResult = assignDistributionSet( final DistributionSetAssignmentResult distributionSetAssignmentResult = assignDistributionSet(
distributionSet.getId(), controllerId); distributionSet.getId(), controllerId);
deploymentManagement.cancelAction(distributionSetAssignmentResult.getActions().get(0)); deploymentManagement.cancelAction(distributionSetAssignmentResult.getActionIds().get(0));
// test // test
registerSameTargetAndAssertBasedOnVersion(controllerId, 1, TargetUpdateStatus.PENDING); registerSameTargetAndAssertBasedOnVersion(controllerId, 1, TargetUpdateStatus.PENDING);
// verify // verify
assertCancelActionMessage(distributionSetAssignmentResult.getActions().get(0), controllerId); assertCancelActionMessage(distributionSetAssignmentResult.getActionIds().get(0), controllerId);
Mockito.verifyZeroInteractions(getDeadletterListener()); Mockito.verifyZeroInteractions(getDeadletterListener());
} }
@@ -908,7 +908,7 @@ public class AmqpMessageHandlerServiceIntegrationTest extends AbstractAmqpServic
private Long registerTargetAndSendActionStatus(final DmfActionStatus sendActionStatus, final String controllerId) { private Long registerTargetAndSendActionStatus(final DmfActionStatus sendActionStatus, final String controllerId) {
final DistributionSetAssignmentResult assignmentResult = registerTargetAndAssignDistributionSet(controllerId); final DistributionSetAssignmentResult assignmentResult = registerTargetAndAssignDistributionSet(controllerId);
final Long actionId = assignmentResult.getActions().get(0); final Long actionId = assignmentResult.getActionIds().get(0);
sendActionUpdateStatus(new DmfActionUpdateStatus(actionId, sendActionStatus)); sendActionUpdateStatus(new DmfActionUpdateStatus(actionId, sendActionStatus));
return actionId; return actionId;
} }

View File

@@ -10,8 +10,11 @@ package org.eclipse.hawkbit.integration.listener;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap; import java.util.EnumMap;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import org.eclipse.hawkbit.dmf.amqp.api.EventTopic; import org.eclipse.hawkbit.dmf.amqp.api.EventTopic;
@@ -26,21 +29,26 @@ public class ReplyToListener implements TestRabbitListener {
public static final String LISTENER_ID = "replyto"; public static final String LISTENER_ID = "replyto";
public static final String REPLY_TO_QUEUE = "reply_queue"; public static final String REPLY_TO_QUEUE = "reply_queue";
private final Map<EventTopic, Message> eventTopicMessages = new EnumMap<>(EventTopic.class); private final Map<EventTopic, List<Message>> eventMessages = new EnumMap<>(EventTopic.class);
private final List<EventTopic> eventMessageTopics = new ArrayList<>();
private final Map<String, Message> deleteMessages = new HashMap<>(); private final Map<String, Message> deleteMessages = new HashMap<>();
private final Map<String, Message> pingResponseMessages = new HashMap<>(); private final Map<String, Message> pingResponseMessages = new HashMap<>();
@Override @Override
@RabbitListener(id = LISTENER_ID, queues = REPLY_TO_QUEUE) @RabbitListener(id = LISTENER_ID, queues = REPLY_TO_QUEUE)
public void handleMessage(final Message message) { public void handleMessage(final Message message) {
final MessageType messageType = MessageType final MessageType messageType = MessageType
.valueOf(message.getMessageProperties().getHeaders().get(MessageHeaderKey.TYPE).toString()); .valueOf(message.getMessageProperties().getHeaders().get(MessageHeaderKey.TYPE).toString());
if (messageType == MessageType.EVENT) { if (messageType == MessageType.EVENT) {
final EventTopic eventTopic = EventTopic final EventTopic eventTopic = EventTopic
.valueOf(message.getMessageProperties().getHeaders().get(MessageHeaderKey.TOPIC).toString()); .valueOf(message.getMessageProperties().getHeaders().get(MessageHeaderKey.TOPIC).toString());
eventTopicMessages.put(eventTopic, message); eventMessageTopics.add(eventTopic);
eventMessages.merge(eventTopic, Collections.singletonList(message), (oldList, listToAdd) -> {
final List<Message> newList = new ArrayList<>(oldList);
newList.addAll(listToAdd);
return newList;
});
return; return;
} }
@@ -62,8 +70,28 @@ public class ReplyToListener implements TestRabbitListener {
} }
public Map<EventTopic, Message> getEventTopicMessages() { public void purge() {
return eventTopicMessages; eventMessages.clear();
deleteMessages.clear();
pingResponseMessages.clear();
eventMessageTopics.clear();
}
public List<EventTopic> getLatestEventMessageTopics() {
return eventMessageTopics;
}
public void resetLatestEventMessageTopics() {
eventMessageTopics.clear();
}
public Message getLatestEventMessage(final EventTopic eventTopic) {
final List<Message> messages = getEventMessages().get(eventTopic);
return messages == null ? null : messages.get(messages.size() - 1);
}
public Map<EventTopic, List<Message>> getEventMessages() {
return eventMessages;
} }
public Map<String, Message> getDeleteMessages() { public Map<String, Message> getDeleteMessages() {

View File

@@ -17,25 +17,35 @@ public enum EventTopic {
* Topic when sending and receiving a update status. * Topic when sending and receiving a update status.
*/ */
UPDATE_ACTION_STATUS, UPDATE_ACTION_STATUS,
/** /**
* Topic when sending and receiving a download and install task. * Topic when sending and receiving a download and install task.
*/ */
DOWNLOAD_AND_INSTALL, DOWNLOAD_AND_INSTALL,
/** /**
* Topic when sending and receiving a cancel download task. * Topic when sending and receiving a cancel download task.
*/ */
CANCEL_DOWNLOAD, CANCEL_DOWNLOAD,
/** /**
* Topic when updating device attributes. * Topic when updating device attributes.
*/ */
UPDATE_ATTRIBUTES, UPDATE_ATTRIBUTES,
/** /**
* Topic when sending a download only task, skipping the install. * Topic when sending a download only task, skipping the install.
*/ */
DOWNLOAD, DOWNLOAD,
/** /**
* Topic when an update of device attributes is requested. * Topic when an update of device attributes is requested.
*/ */
REQUEST_ATTRIBUTES_UPDATE; REQUEST_ATTRIBUTES_UPDATE,
/**
* Topic to send multiple actions to the device.
*/
MULTI_ACTION;
} }

View File

@@ -0,0 +1,94 @@
/**
* Copyright (c) 2019 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.dmf.json.model;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.hawkbit.dmf.amqp.api.EventTopic;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonValue;
/**
* JSON representation of a multi-action request.
*/
@JsonInclude(Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class DmfMultiActionRequest {
private List<DmfMultiActionElement> elements;
public DmfMultiActionRequest() {
}
@JsonCreator
public DmfMultiActionRequest(final List<DmfMultiActionElement> elements) {
this.elements = elements;
}
@JsonValue
public List<DmfMultiActionElement> getElements() {
return elements;
}
public void addElement(final DmfMultiActionElement element) {
if (elements == null) {
elements = new ArrayList<>();
}
elements.add(element);
}
public void addElement(final EventTopic topic, final DmfActionRequest action) {
final DmfMultiActionElement element = new DmfMultiActionElement();
element.setTopic(topic);
element.setAction(action);
addElement(element);
}
/**
* Represents an element within a {@link DmfMultiActionRequest}.
*/
public static class DmfMultiActionElement {
@JsonProperty
private EventTopic topic;
@JsonProperty
private DmfActionRequest action;
public DmfActionRequest getAction() {
return action;
}
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "topic", defaultImpl = DmfActionRequest.class)
@JsonSubTypes({ @Type(value = DmfDownloadAndUpdateRequest.class, name = "DOWNLOAD"),
@Type(value = DmfDownloadAndUpdateRequest.class, name = "DOWNLOAD_AND_INSTALL") })
public void setAction(final DmfActionRequest action) {
this.action = action;
}
public EventTopic getTopic() {
return topic;
}
public void setTopic(final EventTopic actionType) {
this.topic = actionType;
}
}
}

View File

@@ -151,6 +151,19 @@ public interface ControllerManagement {
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER) @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
Optional<Action> findOldestActiveActionByTarget(@NotEmpty String controllerId); Optional<Action> findOldestActiveActionByTarget(@NotEmpty String controllerId);
/**
* Retrieves all active actions which are assigned to the target with the
* given controller ID.
*
* @param pageable
* pagination parameter
* @param controllerId
* of the target
* @return the requested {@link Page} with {@link Action}s
*/
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
Page<Action> findActiveActionsByTarget(@NotNull Pageable pageable, @NotEmpty String controllerId);
/** /**
* Get the {@link Action} entity for given actionId with all lazy * Get the {@link Action} entity for given actionId with all lazy
* attributes. * attributes.

View File

@@ -104,6 +104,33 @@ public interface DeploymentManagement {
DistributionSetAssignmentResult assignDistributionSet(long dsID, DistributionSetAssignmentResult assignDistributionSet(long dsID,
@NotEmpty Collection<TargetWithActionType> targets); @NotEmpty Collection<TargetWithActionType> targets);
/**
* Assigns the given set of {@link DistributionSet} entities to the given
* {@link Target}s using the specified {@link ActionType} and
* {@code forcetime}.
*
* @param dsIDs
* the set of IDs of the distribution sets to assign
* @param targets
* a list of all targets and their action type
* @return the list of assignment results
*
* @throws IncompleteDistributionSetException
* if mandatory {@link SoftwareModuleType} are not assigned as
* defined by the {@link DistributionSetType}.
*
* @throws EntityNotFoundException
* if either provided {@link DistributionSet} or {@link Target}s
* do not exist
*
* @throws QuotaExceededException
* if the maximum number of targets the distribution set can be
* assigned to at once is exceeded
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET)
List<DistributionSetAssignmentResult> assignDistributionSets(@NotEmpty Set<Long> dsIDs,
@NotEmpty Collection<TargetWithActionType> targets);
/** /**
* Assigns the addressed {@link DistributionSet} to all {@link Target}s by * Assigns the addressed {@link DistributionSet} to all {@link Target}s by
* their IDs with a specific {@link ActionType} and an action message. * their IDs with a specific {@link ActionType} and an action message.

View File

@@ -32,6 +32,11 @@ public final class RepositoryConstants {
*/ */
public static final int DEFAULT_DS_TYPES_IN_TENANT = 3; public static final int DEFAULT_DS_TYPES_IN_TENANT = 3;
/**
* Maximum number of actions that can be retrieved.
*/
public static final int MAX_ACTION_COUNT = 100;
/** /**
* Maximum number of messages that can be retrieved by a controller for an * Maximum number of messages that can be retrieved by a controller for an
* {@link Action}. * {@link Action}.

View File

@@ -0,0 +1,58 @@
/**
* Copyright (c) 2019 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.event.remote;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* 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).
*/
public class MultiActionEvent extends RemoteTenantAwareEvent implements Iterable<String> {
private static final long serialVersionUID = 1L;
private final List<String> controllerIds = new ArrayList<>();
/**
* Default constructor.
*/
public MultiActionEvent() {
// for serialization libs like jackson
}
/**
* Constructor.
*
* @param tenant
* tenant the event is scoped to
* @param applicationId
* the application id
* @param controllerIds
* the controller IDs of the affected targets
*/
public MultiActionEvent(final String tenant, final String applicationId, final List<String> controllerIds) {
super(applicationId, tenant, applicationId);
this.controllerIds.addAll(controllerIds);
}
public List<String> getControllerIds() {
return controllerIds;
}
@Override
public Iterator<String> iterator() {
return controllerIds.iterator();
}
}

View File

@@ -0,0 +1,43 @@
/**
* Copyright (c) 2019 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.exception;
import org.eclipse.hawkbit.exception.AbstractServerRtException;
import org.eclipse.hawkbit.exception.SpServerError;
/**
* Exception which is supposed to be thrown if a property value is valid but
* cannot be set in the current context.
*/
public class TenantConfigurationValueChangeNotAllowedException extends AbstractServerRtException {
private static final long serialVersionUID = 1L;
/**
* Creates a new exception for the
* {@link SpServerError#SP_CONFIGURATION_VALUE_CHANGE_NOT_ALLOWED} error
* case.
*/
public TenantConfigurationValueChangeNotAllowedException() {
super(SpServerError.SP_CONFIGURATION_VALUE_CHANGE_NOT_ALLOWED);
}
/**
* Creates a new exception for the
* {@link SpServerError#SP_CONFIGURATION_VALUE_CHANGE_NOT_ALLOWED} error
* case.
*
* @param message
* A custom error message.
*/
public TenantConfigurationValueChangeNotAllowedException(final String message) {
super(message, SpServerError.SP_CONFIGURATION_VALUE_CHANGE_NOT_ALLOWED);
}
}

View File

@@ -26,12 +26,16 @@ public class DistributionSetAssignmentResult extends AssignmentResult<Target> {
private final List<String> assignedTargets; private final List<String> assignedTargets;
private final List<Action> actions; private final List<Action> actions;
private final DistributionSet distributionSet;
private final TargetManagement targetManagement; private final TargetManagement targetManagement;
/** /**
* *
* Constructor. * Constructor.
* *
* @param distributionSet
* that has been assigned
* @param assignedTargets * @param assignedTargets
* the target objects which have been assigned to the * the target objects which have been assigned to the
* distribution set * distribution set
@@ -39,37 +43,53 @@ public class DistributionSetAssignmentResult extends AssignmentResult<Target> {
* count of the assigned targets * count of the assigned targets
* @param alreadyAssigned * @param alreadyAssigned
* the count of the already assigned targets * the count of the already assigned targets
* @param targetManagement
* to retrieve the assigned targets
* @param actions * @param actions
* of the assignment * of the assignment
* * @param targetManagement
* to retrieve the assigned targets
*/ */
public DistributionSetAssignmentResult(final List<String> assignedTargets, final int assigned, public DistributionSetAssignmentResult(final DistributionSet distributionSet, final List<String> assignedTargets,
final int alreadyAssigned, final List<Action> actions, final TargetManagement targetManagement) { final int assigned, final int alreadyAssigned, final List<Action> actions,
final TargetManagement targetManagement) {
super(assigned, alreadyAssigned, 0, Collections.emptyList(), Collections.emptyList()); super(assigned, alreadyAssigned, 0, Collections.emptyList(), Collections.emptyList());
this.distributionSet = distributionSet;
this.assignedTargets = assignedTargets; this.assignedTargets = assignedTargets;
this.actions = actions; this.actions = actions;
this.targetManagement = targetManagement; this.targetManagement = targetManagement;
} }
/**
* @return The distribution set that has been assigned
*/
public DistributionSet getDistributionSet() {
return distributionSet;
}
/** /**
* @return the actionIds * @return the actionIds
*/ */
public List<Long> getActions() { public List<Long> getActionIds() {
if (actions == null) { if (actions == null) {
return Collections.emptyList(); return Collections.emptyList();
} }
return actions.stream().map(Action::getId).collect(Collectors.toList()); return actions.stream().map(Action::getId).collect(Collectors.toList());
} }
/**
* @return the actions
*/
public List<Action> getActions() {
if (actions == null) {
return Collections.emptyList();
}
return actions;
}
@Override @Override
public List<Target> getAssignedEntity() { public List<Target> getAssignedEntity() {
if (CollectionUtils.isEmpty(assignedTargets)) { if (CollectionUtils.isEmpty(assignedTargets)) {
return Collections.emptyList(); return Collections.emptyList();
} }
return targetManagement.getByControllerID(assignedTargets); return targetManagement.getByControllerID(assignedTargets);
} }

View File

@@ -11,7 +11,7 @@ package org.eclipse.hawkbit.repository.model;
import java.io.Serializable; import java.io.Serializable;
/** /**
* represents a tenant configuration value including some meta data * Represents a tenant configuration value including some meta data
* *
* @param <T> * @param <T>
* type of the configuration value * type of the configuration value

View File

@@ -142,6 +142,11 @@ public class TenantConfigurationProperties {
*/ */
public static final String ACTION_CLEANUP_ACTION_STATUS = "action.cleanup.actionStatus"; public static final String ACTION_CLEANUP_ACTION_STATUS = "action.cleanup.actionStatus";
/**
* Switch to enable/disable the multi-assignment feature.
*/
public static final String MULTI_ASSIGNMENTS_ENABLED = "multi.assignments.enabled";
private String keyName; private String keyName;
private String defaultValue = ""; private String defaultValue = "";
private Class<?> dataType = String.class; private Class<?> dataType = String.class;

View File

@@ -17,6 +17,7 @@ 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.MultiActionEvent;
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.SoftwareModuleDeletedEvent; import org.eclipse.hawkbit.repository.event.remote.SoftwareModuleDeletedEvent;
@@ -132,6 +133,9 @@ 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, MultiActionEvent.class);
} }
private int value; private int value;

View File

@@ -91,4 +91,10 @@ hawkbit.server.tenant.configuration.action-cleanup-action-expiry.validator=org.e
hawkbit.server.tenant.configuration.action-cleanup-action-status.keyName=action.cleanup.actionStatus hawkbit.server.tenant.configuration.action-cleanup-action-status.keyName=action.cleanup.actionStatus
hawkbit.server.tenant.configuration.action-cleanup-action-status.defaultValue=CANCELED,ERROR hawkbit.server.tenant.configuration.action-cleanup-action-status.defaultValue=CANCELED,ERROR
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.multi-assignments-enabled.validator=org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationBooleanValidator
# Default tenant configuration - END # Default tenant configuration - END

View File

@@ -17,7 +17,6 @@ import java.util.stream.Collectors;
import org.eclipse.hawkbit.repository.QuotaManagement; import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.RepositoryConstants; import org.eclipse.hawkbit.repository.RepositoryConstants;
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.CancelTargetAssignmentEvent; import org.eclipse.hawkbit.repository.event.remote.entity.CancelTargetAssignmentEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants; import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
@@ -30,13 +29,13 @@ import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.Action.Status;
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.Target; import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetWithActionType; import org.eclipse.hawkbit.repository.model.TargetWithActionType;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.cloud.bus.BusProperties; import org.springframework.cloud.bus.BusProperties;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
import org.springframework.util.CollectionUtils;
/** /**
* {@link DistributionSet} to {@link Target} assignment strategy as utility for * {@link DistributionSet} to {@link Target} assignment strategy as utility for
@@ -51,7 +50,7 @@ public abstract class AbstractDsAssignmentStrategy {
protected final AfterTransactionCommitExecutor afterCommit; protected final AfterTransactionCommitExecutor afterCommit;
protected final ApplicationEventPublisher eventPublisher; protected final ApplicationEventPublisher eventPublisher;
protected final BusProperties bus; protected final BusProperties bus;
private final ActionRepository actionRepository; protected final ActionRepository actionRepository;
private final ActionStatusRepository actionStatusRepository; private final ActionStatusRepository actionStatusRepository;
private final QuotaManagement quotaManagement; private final QuotaManagement quotaManagement;
@@ -79,6 +78,13 @@ public abstract class AbstractDsAssignmentStrategy {
*/ */
abstract List<JpaTarget> findTargetsForAssignment(final List<String> controllerIDs, final long distributionSetId); abstract List<JpaTarget> findTargetsForAssignment(final List<String> controllerIDs, final long distributionSetId);
/**
*
* @param set
* @param targets
*/
abstract void sendTargetUpdatedEvents(final DistributionSet set, final List<JpaTarget> targets);
/** /**
* Update status and DS fields of given target. * Update status and DS fields of given target.
* *
@@ -89,8 +95,8 @@ public abstract class AbstractDsAssignmentStrategy {
* @param currentUser * @param currentUser
* for auditing * for auditing
*/ */
abstract void updateTargetStatus(final JpaDistributionSet distributionSet, final List<List<Long>> targetIds, abstract void setAssignedDistributionSetAndTargetStatus(final JpaDistributionSet distributionSet,
final String currentUser); final List<List<Long>> targetIds, final String currentUser);
/** /**
* Cancels actions that can be canceled (i.e. * Cancels actions that can be canceled (i.e.
@@ -112,35 +118,12 @@ public abstract class AbstractDsAssignmentStrategy {
* *
* @param targetIds * @param targetIds
* to cancel actions for * to cancel actions for
* @return {@link Set} of {@link Target#getId()}s
*/ */
abstract void closeActiveActions(List<List<Long>> targetIds); abstract void closeActiveActions(List<List<Long>> targetIds);
/** abstract void sendDeploymentEvents(final DistributionSetAssignmentResult assignmentResult);
* Handles event sending related to the assignment.
*
* @param set
* that has been assigned
* @param targets
* to send events for
* @param targetIdsCancelList
* targets where an action was canceled
* @param controllerIdsToActions
* mapping of {@link Target#getControllerId()} to new
* {@link Action} that was created as part of the assignment.
*/
abstract void sendAssignmentEvents(DistributionSet set, final List<JpaTarget> targets,
final Set<Long> targetIdsCancelList, final Map<String, JpaAction> controllerIdsToActions);
protected void sendTargetAssignDistributionSetEvent(final String tenant, final long distributionSetId, abstract void sendDeploymentEvents(final List<DistributionSetAssignmentResult> assignmentResults);
final List<Action> actions) {
if (CollectionUtils.isEmpty(actions)) {
return;
}
afterCommit.afterCommit(() -> eventPublisher.publishEvent(new TargetAssignDistributionSetEvent(tenant,
distributionSetId, actions, bus.getId(), actions.get(0).isMaintenanceWindowAvailable())));
}
protected void sendTargetUpdatedEvent(final JpaTarget target) { protected void sendTargetUpdatedEvent(final JpaTarget target) {
afterCommit.afterCommit(() -> eventPublisher.publishEvent(new TargetUpdatedEvent(target, bus.getId()))); afterCommit.afterCommit(() -> eventPublisher.publishEvent(new TargetUpdatedEvent(target, bus.getId())));

View File

@@ -359,6 +359,14 @@ public class JpaControllerManagement implements ControllerManagement {
true); true);
} }
@Override
public Page<Action> findActiveActionsByTarget(final Pageable pageable, final String controllerId) {
if (!actionRepository.activeActionExistsForControllerId(controllerId)) {
return Page.empty();
}
return actionRepository.findByActiveAndTarget(pageable, controllerId, true);
}
@Override @Override
public Optional<Action> findActionWithDetails(final long actionId) { public Optional<Action> findActionWithDetails(final long actionId) {
return actionRepository.getById(actionId); return actionRepository.getById(actionId);
@@ -647,7 +655,7 @@ public class JpaControllerManagement implements ControllerManagement {
return null; return null;
} }
JpaTarget target = (JpaTarget) action.getTarget(); final JpaTarget target = (JpaTarget) action.getTarget();
action.setActive(false); action.setActive(false);
action.setStatus(DOWNLOADED); action.setStatus(DOWNLOADED);
target.setUpdateStatus(TargetUpdateStatus.IN_SYNC); target.setUpdateStatus(TargetUpdateStatus.IN_SYNC);

View File

@@ -8,6 +8,10 @@
*/ */
package org.eclipse.hawkbit.repository.jpa; package org.eclipse.hawkbit.repository.jpa;
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 java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
@@ -66,7 +70,6 @@ import org.eclipse.hawkbit.repository.model.TargetWithActionType;
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer; import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
import org.eclipse.hawkbit.security.SystemSecurityContext; import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAware; import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.cloud.bus.BusProperties; import org.springframework.cloud.bus.BusProperties;
@@ -85,7 +88,6 @@ import org.springframework.retry.annotation.Retryable;
import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
@@ -123,9 +125,6 @@ public class JpaDeploymentManagement implements DeploymentManagement {
private final ActionStatusRepository actionStatusRepository; private final ActionStatusRepository actionStatusRepository;
private final TargetManagement targetManagement; private final TargetManagement targetManagement;
private final AuditorAware<String> auditorProvider; private final AuditorAware<String> auditorProvider;
private final ApplicationEventPublisher eventPublisher;
private final BusProperties bus;
private final AfterTransactionCommitExecutor afterCommit;
private final VirtualPropertyReplacer virtualPropertyReplacer; private final VirtualPropertyReplacer virtualPropertyReplacer;
private final PlatformTransactionManager txManager; private final PlatformTransactionManager txManager;
private final OnlineDsAssignmentStrategy onlineDsAssignmentStrategy; private final OnlineDsAssignmentStrategy onlineDsAssignmentStrategy;
@@ -151,13 +150,10 @@ public class JpaDeploymentManagement implements DeploymentManagement {
this.actionStatusRepository = actionStatusRepository; this.actionStatusRepository = actionStatusRepository;
this.targetManagement = targetManagement; this.targetManagement = targetManagement;
this.auditorProvider = auditorProvider; this.auditorProvider = auditorProvider;
this.eventPublisher = eventPublisher;
this.bus = bus;
this.afterCommit = afterCommit;
this.virtualPropertyReplacer = virtualPropertyReplacer; this.virtualPropertyReplacer = virtualPropertyReplacer;
this.txManager = txManager; this.txManager = txManager;
onlineDsAssignmentStrategy = new OnlineDsAssignmentStrategy(targetRepository, afterCommit, eventPublisher, bus, onlineDsAssignmentStrategy = new OnlineDsAssignmentStrategy(targetRepository, afterCommit, eventPublisher, bus,
actionRepository, actionStatusRepository, quotaManagement); actionRepository, actionStatusRepository, quotaManagement, this::isMultiAssignmentsEnabled);
offlineDsAssignmentStrategy = new OfflineDsAssignmentStrategy(targetRepository, afterCommit, eventPublisher, offlineDsAssignmentStrategy = new OfflineDsAssignmentStrategy(targetRepository, afterCommit, eventPublisher,
bus, actionRepository, actionStatusRepository, quotaManagement); bus, actionRepository, actionStatusRepository, quotaManagement);
this.tenantConfigurationManagement = tenantConfigurationManagement; this.tenantConfigurationManagement = tenantConfigurationManagement;
@@ -173,11 +169,13 @@ public class JpaDeploymentManagement implements DeploymentManagement {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public DistributionSetAssignmentResult offlineAssignedDistributionSet(final Long dsID, public DistributionSetAssignmentResult offlineAssignedDistributionSet(final Long dsID,
final Collection<String> controllerIDs) { final Collection<String> controllerIDs) {
return assignDistributionSetToTargets(dsID, final DistributionSetAssignmentResult result = assignDistributionSetToTargets(dsID,
controllerIDs.stream() controllerIDs.stream()
.map(controllerId -> new TargetWithActionType(controllerId, ActionType.FORCED, -1)) .map(controllerId -> new TargetWithActionType(controllerId, ActionType.FORCED, -1))
.collect(Collectors.toList()), .collect(Collectors.toList()),
null, offlineDsAssignmentStrategy); null, offlineDsAssignmentStrategy);
offlineDsAssignmentStrategy.sendDeploymentEvents(result);
return result;
} }
@Override @Override
@@ -187,12 +185,13 @@ public class JpaDeploymentManagement implements DeploymentManagement {
public DistributionSetAssignmentResult assignDistributionSet(final long dsID, final ActionType actionType, public DistributionSetAssignmentResult assignDistributionSet(final long dsID, final ActionType actionType,
final long forcedTimestamp, final Collection<String> controllerIDs) { final long forcedTimestamp, final Collection<String> controllerIDs) {
return assignDistributionSetToTargets(dsID, final DistributionSetAssignmentResult result = assignDistributionSetToTargets(dsID,
controllerIDs.stream() controllerIDs.stream()
.map(controllerId -> new TargetWithActionType(controllerId, actionType, forcedTimestamp)) .map(controllerId -> new TargetWithActionType(controllerId, actionType, forcedTimestamp))
.collect(Collectors.toList()), .collect(Collectors.toList()),
null, onlineDsAssignmentStrategy); null, onlineDsAssignmentStrategy);
onlineDsAssignmentStrategy.sendDeploymentEvents(result);
return result;
} }
@Override @Override
@@ -202,7 +201,21 @@ public class JpaDeploymentManagement implements DeploymentManagement {
public DistributionSetAssignmentResult assignDistributionSet(final long dsID, public DistributionSetAssignmentResult assignDistributionSet(final long dsID,
final Collection<TargetWithActionType> targets) { final Collection<TargetWithActionType> targets) {
return assignDistributionSetToTargets(dsID, targets, null, onlineDsAssignmentStrategy); final DistributionSetAssignmentResult result = assignDistributionSetToTargets(dsID, targets, null,
onlineDsAssignmentStrategy);
onlineDsAssignmentStrategy.sendDeploymentEvents(result);
return result;
}
@Override
public List<DistributionSetAssignmentResult> assignDistributionSets(final Set<Long> dsIDs,
final Collection<TargetWithActionType> targets) {
final List<DistributionSetAssignmentResult> results = dsIDs.stream()
.map(dsID -> assignDistributionSetToTargets(dsID, targets, null, onlineDsAssignmentStrategy))
.collect(Collectors.toList());
onlineDsAssignmentStrategy.sendDeploymentEvents(results);
return results;
} }
@Override @Override
@@ -212,7 +225,10 @@ public class JpaDeploymentManagement implements DeploymentManagement {
public DistributionSetAssignmentResult assignDistributionSet(final long dsID, public DistributionSetAssignmentResult assignDistributionSet(final long dsID,
final Collection<TargetWithActionType> targets, final String actionMessage) { final Collection<TargetWithActionType> targets, final String actionMessage) {
return assignDistributionSetToTargets(dsID, targets, actionMessage, onlineDsAssignmentStrategy); final DistributionSetAssignmentResult result = assignDistributionSetToTargets(dsID, targets, actionMessage,
onlineDsAssignmentStrategy);
onlineDsAssignmentStrategy.sendDeploymentEvents(result);
return result;
} }
/** /**
@@ -257,8 +273,8 @@ public class JpaDeploymentManagement implements DeploymentManagement {
// detaching as it is not necessary to persist the set itself // detaching as it is not necessary to persist the set itself
entityManager.detach(distributionSetEntity); entityManager.detach(distributionSetEntity);
// return with nothing as all targets had the DS already assigned // return with nothing as all targets had the DS already assigned
return new DistributionSetAssignmentResult(Collections.emptyList(), 0, targetsWithActionType.size(), return new DistributionSetAssignmentResult(distributionSetEntity, Collections.emptyList(), 0,
Collections.emptyList(), targetManagement); targetsWithActionType.size(), Collections.emptyList(), targetManagement);
} }
// split tIDs length into max entries in-statement because many database // split tIDs length into max entries in-statement because many database
@@ -269,12 +285,7 @@ public class JpaDeploymentManagement implements DeploymentManagement {
targetEntities.stream().map(Target::getId).collect(Collectors.toList()), targetEntities.stream().map(Target::getId).collect(Collectors.toList()),
Constants.MAX_ENTRIES_IN_STATEMENT); Constants.MAX_ENTRIES_IN_STATEMENT);
// override all active actions and set them into canceling state, we closeOrCancelActiveActions(assignmentStrategy, targetEntitiesIdsChunks);
// need to remember which one we have been switched to canceling state
// because for targets which we have changed to canceling we don't want
// to publish the new action update event.
final Set<Long> cancelingTargetEntitiesIds = 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 // not active before and the manual assignment which has been done
// cancels them // cancels them
@@ -291,10 +302,9 @@ public class JpaDeploymentManagement implements DeploymentManagement {
// action history. // action history.
createActionsStatus(controllerIdsToActions.values(), assignmentStrategy, actionMessage); createActionsStatus(controllerIdsToActions.values(), assignmentStrategy, actionMessage);
detachEntitiesAndSendAssignmentEvents(distributionSetEntity, targetEntities, assignmentStrategy, detachEntitiesAndSendTargetUpdatedEvents(distributionSetEntity, targetEntities, assignmentStrategy);
cancelingTargetEntitiesIds, controllerIdsToActions);
return new DistributionSetAssignmentResult( return new DistributionSetAssignmentResult(distributionSetEntity,
targetEntities.stream().map(Target::getControllerId).collect(Collectors.toList()), targetEntities.stream().map(Target::getControllerId).collect(Collectors.toList()),
targetEntities.size(), controllerIDs.size() - targetEntities.size(), targetEntities.size(), controllerIDs.size() - targetEntities.size(),
Lists.newArrayList(controllerIdsToActions.values()), targetManagement); Lists.newArrayList(controllerIdsToActions.values()), targetManagement);
@@ -340,34 +350,37 @@ public class JpaDeploymentManagement implements DeploymentManagement {
quotaManagement.getMaxTargetsPerManualAssignment(), Target.class, DistributionSet.class, null); quotaManagement.getMaxTargetsPerManualAssignment(), Target.class, DistributionSet.class, null);
} }
private Set<Long> closeOrCancelActiveActions(final AbstractDsAssignmentStrategy assignmentStrategy, private void closeOrCancelActiveActions(final AbstractDsAssignmentStrategy assignmentStrategy,
final List<List<Long>> targetIdsChunks) { final List<List<Long>> targetIdsChunks) {
if (isMultiAssignmentsEnabled()) {
LOG.debug("Multi Assignments feature is enabled: No need to close /cancel active actions.");
return;
}
if (isActionsAutocloseEnabled()) { if (isActionsAutocloseEnabled()) {
assignmentStrategy.closeActiveActions(targetIdsChunks); assignmentStrategy.closeActiveActions(targetIdsChunks);
return Collections.emptySet();
} else { } else {
return assignmentStrategy.cancelActiveActions(targetIdsChunks); assignmentStrategy.cancelActiveActions(targetIdsChunks);
} }
} }
protected boolean isActionsAutocloseEnabled() {
return systemSecurityContext.runAsSystem(() -> tenantConfigurationManagement
.getConfigurationValue(TenantConfigurationKey.REPOSITORY_ACTIONS_AUTOCLOSE_ENABLED, Boolean.class)
.getValue());
}
@Override @Override
@Transactional(isolation = Isolation.READ_COMMITTED) @Transactional(isolation = Isolation.READ_COMMITTED)
@Retryable(include = { @Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void cancelInactiveScheduledActionsForTargets(final List<Long> targetIds) { public void cancelInactiveScheduledActionsForTargets(final List<Long> targetIds) {
actionRepository.switchStatus(Status.CANCELED, targetIds, false, Status.SCHEDULED); if (!isMultiAssignmentsEnabled()) {
actionRepository.switchStatus(Status.CANCELED, targetIds, false, Status.SCHEDULED);
} else {
LOG.debug("The Multi Assignments feature is enabled: No need to cancel inactive scheduled actions.");
}
} }
private void setAssignedDistributionSetAndTargetUpdateStatus(final AbstractDsAssignmentStrategy assignmentStrategy, private void setAssignedDistributionSetAndTargetUpdateStatus(final AbstractDsAssignmentStrategy assignmentStrategy,
final JpaDistributionSet set, final List<List<Long>> targetIdsChunks) { final JpaDistributionSet set, final List<List<Long>> targetIdsChunks) {
final String currentUser = auditorProvider.getCurrentAuditor().orElse(null); final String currentUser = auditorProvider.getCurrentAuditor().orElse(null);
assignmentStrategy.updateTargetStatus(set, targetIdsChunks, currentUser); assignmentStrategy.setAssignedDistributionSetAndTargetStatus(set, targetIdsChunks, currentUser);
} }
private Map<String, JpaAction> createActions(final Collection<TargetWithActionType> targetsWithActionType, private Map<String, JpaAction> createActions(final Collection<TargetWithActionType> targetsWithActionType,
@@ -388,15 +401,13 @@ public class JpaDeploymentManagement implements DeploymentManagement {
.collect(Collectors.toList())); .collect(Collectors.toList()));
} }
private void detachEntitiesAndSendAssignmentEvents(final JpaDistributionSet set, final List<JpaTarget> targets, private void detachEntitiesAndSendTargetUpdatedEvents(final JpaDistributionSet set, final List<JpaTarget> targets,
final AbstractDsAssignmentStrategy assignmentStrategy, final Set<Long> targetIdsCancellList, final AbstractDsAssignmentStrategy assignmentStrategy) {
final Map<String, JpaAction> controllerIdsToActions) {
// detaching as it is not necessary to persist the set itself // detaching as it is not necessary to persist the set itself
entityManager.detach(set); entityManager.detach(set);
// detaching as the entity has been updated by the JPQL query above // detaching as the entity has been updated by the JPQL query above
targets.forEach(entityManager::detach); targets.forEach(entityManager::detach);
assignmentStrategy.sendTargetUpdatedEvents(set, targets);
assignmentStrategy.sendAssignmentEvents(set, targets, targetIdsCancellList, controllerIdsToActions);
} }
@Override @Override
@@ -421,7 +432,8 @@ public class JpaDeploymentManagement implements DeploymentManagement {
actionStatusRepository.save(new JpaActionStatus(action, Status.CANCELING, System.currentTimeMillis(), actionStatusRepository.save(new JpaActionStatus(action, Status.CANCELING, System.currentTimeMillis(),
RepositoryConstants.SERVER_MESSAGE_PREFIX + "manual cancelation requested")); RepositoryConstants.SERVER_MESSAGE_PREFIX + "manual cancelation requested"));
final Action saveAction = actionRepository.save(action); final Action saveAction = actionRepository.save(action);
onlineDsAssignmentStrategy.cancelAssignDistributionSetEvent(action.getTarget(), action.getId());
onlineDsAssignmentStrategy.cancelAssignment(action);
return saveAction; return saveAction;
} else { } else {
@@ -481,18 +493,13 @@ public class JpaDeploymentManagement implements DeploymentManagement {
return 0L; return 0L;
} }
final String tenant = rolloutGroupActions.getContent().get(0).getTenant();
final boolean maintenanceWindowAvailable = rolloutGroupActions.getContent().get(0)
.isMaintenanceWindowAvailable();
final List<Action> targetAssignments = rolloutGroupActions.getContent().stream() final List<Action> targetAssignments = rolloutGroupActions.getContent().stream()
.map(action -> (JpaAction) action).map(this::closeActionIfSetWasAlreadyAssigned) .map(action -> (JpaAction) action).map(this::closeActionIfSetWasAlreadyAssigned)
.filter(Objects::nonNull).map(this::startScheduledActionIfNoCancelationHasToBeHandledFirst) .filter(Objects::nonNull).map(this::startScheduledActionIfNoCancelationHasToBeHandledFirst)
.filter(Objects::nonNull).collect(Collectors.toList()); .filter(Objects::nonNull).collect(Collectors.toList());
if (!CollectionUtils.isEmpty(targetAssignments)) { if (!targetAssignments.isEmpty()) {
afterCommit.afterCommit(() -> eventPublisher.publishEvent(new TargetAssignDistributionSetEvent(tenant, onlineDsAssignmentStrategy.sendDeploymentEvents(distributionSetId, targetAssignments);
distributionSetId, targetAssignments, bus.getId(), maintenanceWindowAvailable)));
} }
return rolloutGroupActions.getTotalElements(); return rolloutGroupActions.getTotalElements();
@@ -513,11 +520,18 @@ public class JpaDeploymentManagement implements DeploymentManagement {
} }
private JpaAction closeActionIfSetWasAlreadyAssigned(final JpaAction action) { private JpaAction closeActionIfSetWasAlreadyAssigned(final JpaAction action) {
if (isMultiAssignmentsEnabled()) {
return action;
}
final JpaTarget target = (JpaTarget) action.getTarget(); final JpaTarget target = (JpaTarget) action.getTarget();
if (target.getAssignedDistributionSet() != null if (target.getAssignedDistributionSet() != null
&& action.getDistributionSet().getId().equals(target.getAssignedDistributionSet().getId())) { && action.getDistributionSet().getId().equals(target.getAssignedDistributionSet().getId())) {
// the target has already the distribution set assigned, we don't // the target has already the distribution set assigned, we don't
// need to start the scheduled action, just finish it. // need to start the scheduled action, just finish it.
LOG.debug("Target {} has distribution set {} assigned. Closing action...", target.getControllerId(),
action.getDistributionSet().getName());
action.setStatus(Status.FINISHED); action.setStatus(Status.FINISHED);
action.setActive(false); action.setActive(false);
setSkipActionStatus(action); setSkipActionStatus(action);
@@ -532,15 +546,16 @@ public class JpaDeploymentManagement implements DeploymentManagement {
// check if we need to override running update actions // check if we need to override running update actions
final List<Long> overrideObsoleteUpdateActions; final List<Long> overrideObsoleteUpdateActions;
if (systemSecurityContext.runAsSystem(() -> tenantConfigurationManagement if (isMultiAssignmentsEnabled()) {
.getConfigurationValue(TenantConfigurationKey.REPOSITORY_ACTIONS_AUTOCLOSE_ENABLED, Boolean.class)
.getValue())) {
overrideObsoleteUpdateActions = Collections.emptyList(); overrideObsoleteUpdateActions = Collections.emptyList();
onlineDsAssignmentStrategy
.closeObsoleteUpdateActions(Collections.singletonList(action.getTarget().getId()));
} else { } else {
overrideObsoleteUpdateActions = onlineDsAssignmentStrategy final List<Long> targetId = Collections.singletonList(action.getTarget().getId());
.overrideObsoleteUpdateActions(Collections.singletonList(action.getTarget().getId())); if (isActionsAutocloseEnabled()) {
overrideObsoleteUpdateActions = Collections.emptyList();
onlineDsAssignmentStrategy.closeObsoleteUpdateActions(targetId);
} else {
overrideObsoleteUpdateActions = onlineDsAssignmentStrategy.overrideObsoleteUpdateActions(targetId);
}
} }
action.setActive(true); action.setActive(true);
@@ -782,4 +797,18 @@ public class JpaDeploymentManagement implements DeploymentManagement {
protected ActionRepository getActionRepository() { protected ActionRepository getActionRepository() {
return actionRepository; return actionRepository;
} }
protected boolean isActionsAutocloseEnabled() {
return getConfigValue(REPOSITORY_ACTIONS_AUTOCLOSE_ENABLED, Boolean.class);
}
private boolean isMultiAssignmentsEnabled() {
return getConfigValue(MULTI_ASSIGNMENTS_ENABLED, Boolean.class);
}
private <T extends Serializable> T getConfigValue(final String key, final Class<T> valueType) {
return systemSecurityContext
.runAsSystem(() -> tenantConfigurationManagement.getConfigurationValue(key, valueType).getValue());
}
} }

View File

@@ -8,9 +8,13 @@
*/ */
package org.eclipse.hawkbit.repository.jpa; package org.eclipse.hawkbit.repository.jpa;
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 java.io.Serializable; import java.io.Serializable;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.exception.TenantConfigurationValueChangeNotAllowedException;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants; import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
import org.eclipse.hawkbit.repository.jpa.model.JpaTenantConfiguration; import org.eclipse.hawkbit.repository.jpa.model.JpaTenantConfiguration;
import org.eclipse.hawkbit.repository.model.TenantConfiguration; import org.eclipse.hawkbit.repository.model.TenantConfiguration;
@@ -18,6 +22,8 @@ import org.eclipse.hawkbit.repository.model.TenantConfigurationValue;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties; import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey; import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationValidatorException; import org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationValidatorException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Cacheable;
@@ -37,6 +43,8 @@ import org.springframework.validation.annotation.Validated;
@Validated @Validated
public class JpaTenantConfigurationManagement implements TenantConfigurationManagement { public class JpaTenantConfigurationManagement implements TenantConfigurationManagement {
private static final Logger LOG = LoggerFactory.getLogger(JpaTenantConfigurationManagement.class);
@Autowired @Autowired
private TenantConfigurationRepository tenantConfigurationRepository; private TenantConfigurationRepository tenantConfigurationRepository;
@@ -150,6 +158,8 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
tenantConfiguration.setValue(value.toString()); tenantConfiguration.setValue(value.toString());
} }
assertValueChangeIsAllowed(configurationKeyName, tenantConfiguration);
final JpaTenantConfiguration updatedTenantConfiguration = tenantConfigurationRepository final JpaTenantConfiguration updatedTenantConfiguration = tenantConfigurationRepository
.save(tenantConfiguration); .save(tenantConfiguration);
@@ -163,6 +173,41 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
.value(conversionService.convert(updatedTenantConfiguration.getValue(), clazzT)).build(); .value(conversionService.convert(updatedTenantConfiguration.getValue(), clazzT)).build();
} }
/**
* Asserts that the requested configuration value change is allowed. Throws
* a {@link TenantConfigurationValueChangeNotAllowedException} otherwise.
*
* @param configurationKeyName
* The configuration key.
* @param tenantConfiguration
* 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, valueChange);
}
@SuppressWarnings("squid:S1172")
private void assertAutoCloseValueChange(final String key, final JpaTenantConfiguration valueChange) {
if (REPOSITORY_ACTIONS_AUTOCLOSE_ENABLED.equals(key)
&& getConfigurationValue(MULTI_ASSIGNMENTS_ENABLED, Boolean.class).getValue()) {
LOG.debug(
"The property '{}' must not be changed because the Multi-Assignments feature is currently enabled.",
key);
throw new TenantConfigurationValueChangeNotAllowedException();
}
}
private static 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();
}
}
@Override @Override
@CacheEvict(value = "tenantConfiguration", key = "#configurationKeyName") @CacheEvict(value = "tenantConfiguration", key = "#configurationKeyName")
@Transactional @Transactional

View File

@@ -27,6 +27,7 @@ import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder;
import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications; import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications;
import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.Action.Status;
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.TargetUpdateStatus; import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.repository.model.TargetWithActionType; import org.eclipse.hawkbit.repository.model.TargetWithActionType;
import org.springframework.cloud.bus.BusProperties; import org.springframework.cloud.bus.BusProperties;
@@ -50,9 +51,7 @@ public class OfflineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
} }
@Override @Override
void sendAssignmentEvents(final DistributionSet set, final List<JpaTarget> targets, void sendTargetUpdatedEvents(final DistributionSet set, final List<JpaTarget> targets) {
final Set<Long> targetIdsCancellList, final Map<String, JpaAction> targetIdsToActions) {
targets.forEach(target -> { targets.forEach(target -> {
target.setUpdateStatus(TargetUpdateStatus.IN_SYNC); target.setUpdateStatus(TargetUpdateStatus.IN_SYNC);
sendTargetUpdatedEvent(target); sendTargetUpdatedEvent(target);
@@ -79,7 +78,8 @@ public class OfflineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
} }
@Override @Override
void updateTargetStatus(final JpaDistributionSet set, final List<List<Long>> targetIds, final String currentUser) { void setAssignedDistributionSetAndTargetStatus(final JpaDistributionSet set, final List<List<Long>> targetIds,
final String currentUser) {
targetIds.forEach(tIds -> targetRepository.setAssignedAndInstalledDistributionSetAndUpdateStatus( targetIds.forEach(tIds -> targetRepository.setAssignedAndInstalledDistributionSetAndUpdateStatus(
TargetUpdateStatus.IN_SYNC, set, System.currentTimeMillis(), currentUser, tIds)); TargetUpdateStatus.IN_SYNC, set, System.currentTimeMillis(), currentUser, tIds));
} }
@@ -103,4 +103,14 @@ public class OfflineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
return result; return result;
} }
@Override
void sendDeploymentEvents(final DistributionSetAssignmentResult assignmentResult) {
// no need to send deployment events in the offline case
}
@Override
void sendDeploymentEvents(final List<DistributionSetAssignmentResult> assignmentResults) {
// no need to send deployment events in the offline case
}
} }

View File

@@ -8,13 +8,21 @@
*/ */
package org.eclipse.hawkbit.repository.jpa; package org.eclipse.hawkbit.repository.jpa;
import static org.eclipse.hawkbit.repository.RepositoryConstants.MAX_ACTION_COUNT;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.hawkbit.repository.QuotaManagement; import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.event.remote.MultiActionEvent;
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants; import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor; import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction; import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
@@ -25,11 +33,15 @@ import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications;
import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.Action.Status;
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.Target; import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.repository.model.TargetWithActionType; import org.eclipse.hawkbit.repository.model.TargetWithActionType;
import org.springframework.cloud.bus.BusProperties; import org.springframework.cloud.bus.BusProperties;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.util.CollectionUtils;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
@@ -39,35 +51,70 @@ import com.google.common.collect.Lists;
*/ */
public class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy { public class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
private static final Pageable ACTION_PAGE_REQUEST = PageRequest.of(0, MAX_ACTION_COUNT);
private final Supplier<Boolean> multiAssignmentsConfig;
OnlineDsAssignmentStrategy(final TargetRepository targetRepository, OnlineDsAssignmentStrategy(final TargetRepository targetRepository,
final AfterTransactionCommitExecutor afterCommit, final ApplicationEventPublisher eventPublisher, final AfterTransactionCommitExecutor afterCommit, final ApplicationEventPublisher eventPublisher,
final BusProperties bus, final ActionRepository actionRepository, final BusProperties bus, final ActionRepository actionRepository,
final ActionStatusRepository actionStatusRepository, final QuotaManagement quotaManagement) { final ActionStatusRepository actionStatusRepository, final QuotaManagement quotaManagement,
final Supplier<Boolean> multiAssignmentsConfig) {
super(targetRepository, afterCommit, eventPublisher, bus, actionRepository, actionStatusRepository, super(targetRepository, afterCommit, eventPublisher, bus, actionRepository, actionStatusRepository,
quotaManagement); quotaManagement);
this.multiAssignmentsConfig = multiAssignmentsConfig;
} }
@Override @Override
void sendAssignmentEvents(final DistributionSet set, final List<JpaTarget> targets, void sendTargetUpdatedEvents(final DistributionSet set, final List<JpaTarget> targets) {
final Set<Long> targetIdsCancellList, final Map<String, JpaAction> targetIdsToActions) { targets.stream().forEach(target -> {
final List<Action> actions = targets.stream().map(target -> {
target.setUpdateStatus(TargetUpdateStatus.PENDING); target.setUpdateStatus(TargetUpdateStatus.PENDING);
sendTargetUpdatedEvent(target); sendTargetUpdatedEvent(target);
});
}
return target; @Override
}).filter(target -> !targetIdsCancellList.contains(target.getId())).map(Target::getControllerId) void sendDeploymentEvents(final DistributionSetAssignmentResult assignmentResult) {
.map(targetIdsToActions::get).collect(Collectors.toList()); if (isMultiAssignmentsEnabled()) {
sendDeploymentEvents(Collections.singletonList(assignmentResult));
} else {
sendDistributionSetAssignedEvent(assignmentResult);
}
}
sendTargetAssignDistributionSetEvent(set.getTenant(), set.getId(), actions); @Override
void sendDeploymentEvents(final List<DistributionSetAssignmentResult> assignmentResults) {
if (isMultiAssignmentsEnabled()) {
sendDeploymentEvent(assignmentResults.stream().flatMap(result -> result.getActions().stream())
.collect(Collectors.toList()));
} else {
assignmentResults.forEach(this::sendDistributionSetAssignedEvent);
}
}
void sendDeploymentEvents(final long distributionSetId, final List<Action> actions) {
if (isMultiAssignmentsEnabled()) {
sendDeploymentEvent(actions);
return;
}
final List<Action> filteredActions = getActionsWithoutCancellations(actions);
if (filteredActions.isEmpty()) {
return;
}
sendTargetAssignDistributionSetEvent(filteredActions.get(0).getTenant(), distributionSetId, filteredActions);
} }
@Override @Override
List<JpaTarget> findTargetsForAssignment(final List<String> controllerIDs, final long setId) { List<JpaTarget> findTargetsForAssignment(final List<String> controllerIDs, final long setId) {
return Lists.partition(controllerIDs, Constants.MAX_ENTRIES_IN_STATEMENT).stream() final Function<List<String>, List<JpaTarget>> mapper;
.map(ids -> targetRepository if (isMultiAssignmentsEnabled()) {
.findAll(TargetSpecifications.hasControllerIdAndAssignedDistributionSetIdNot(ids, setId))) mapper = targetRepository::findAllByControllerId;
} else {
mapper = ids -> targetRepository
.findAll(TargetSpecifications.hasControllerIdAndAssignedDistributionSetIdNot(ids, setId));
}
return Lists.partition(controllerIDs, Constants.MAX_ENTRIES_IN_STATEMENT).stream().map(mapper)
.flatMap(List::stream).collect(Collectors.toList()); .flatMap(List::stream).collect(Collectors.toList());
} }
@@ -83,7 +130,8 @@ public class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
} }
@Override @Override
void updateTargetStatus(final JpaDistributionSet set, final List<List<Long>> targetIds, final String currentUser) { void setAssignedDistributionSetAndTargetStatus(final JpaDistributionSet set, final List<List<Long>> targetIds,
final String currentUser) {
targetIds.forEach(tIds -> targetRepository.setAssignedDistributionSetAndUpdateStatus(TargetUpdateStatus.PENDING, targetIds.forEach(tIds -> targetRepository.setAssignedDistributionSetAndUpdateStatus(TargetUpdateStatus.PENDING,
set, System.currentTimeMillis(), currentUser, tIds)); set, System.currentTimeMillis(), currentUser, tIds));
@@ -106,4 +154,82 @@ public class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
return result; return result;
} }
void cancelAssignment(final JpaAction action) {
if (isMultiAssignmentsEnabled()) {
sendMultiActionEvent(action.getTarget());
} else {
cancelAssignDistributionSetEvent(action.getTarget(), action.getId());
}
}
private void sendMultiActionEvent(final Target target) {
sendMultiActionEvent(target.getTenant(), Collections.singletonList(target.getControllerId()));
}
private void sendDeploymentEvent(final List<Action> actions) {
final List<Action> filteredActions = getActionsWithoutCancellations(actions);
if (filteredActions.isEmpty()) {
return;
}
final String tenant = filteredActions.get(0).getTenant();
sendMultiActionEvent(tenant, filteredActions.stream().map(action -> action.getTarget().getControllerId())
.collect(Collectors.toList()));
}
private DistributionSetAssignmentResult sendDistributionSetAssignedEvent(
final DistributionSetAssignmentResult assignmentResult) {
final List<Action> filteredActions = filterCancellations(assignmentResult.getActions())
.filter(action -> !hasPendingCancellations(action.getTarget())).collect(Collectors.toList());
final DistributionSet set = assignmentResult.getDistributionSet();
sendTargetAssignDistributionSetEvent(set.getTenant(), set.getId(), filteredActions);
return assignmentResult;
}
private void sendTargetAssignDistributionSetEvent(final String tenant, final long distributionSetId,
final List<Action> actions) {
if (CollectionUtils.isEmpty(actions)) {
return;
}
afterCommit.afterCommit(() -> eventPublisher.publishEvent(new TargetAssignDistributionSetEvent(tenant,
distributionSetId, actions, bus.getId(), actions.get(0).isMaintenanceWindowAvailable())));
}
private boolean hasPendingCancellations(final Target target) {
return actionRepository.findByActiveAndTarget(ACTION_PAGE_REQUEST, target.getControllerId(), true).getContent()
.stream().anyMatch(action -> action.getStatus() == Status.CANCELING);
}
/**
* Helper to fire a {@link MultiActionEvent}. This method may only be called
* if the Multi-Assignments feature is enabled.
*
* @param tenant
* the event is scoped to
* @param controllerIds
* of the targets the event refers to
*/
private void sendMultiActionEvent(final String tenant, final List<String> controllerIds) {
afterCommit.afterCommit(
() -> eventPublisher.publishEvent(new MultiActionEvent(tenant, bus.getId(), controllerIds)));
}
private boolean isMultiAssignmentsEnabled() {
return multiAssignmentsConfig.get();
}
private static Stream<Action> filterCancellations(final List<Action> actions) {
return actions.stream().filter(action -> {
final Status actionStatus = action.getStatus();
return Status.CANCELING != actionStatus && Status.CANCELED != actionStatus;
});
}
private static List<Action> getActionsWithoutCancellations(final List<Action> actions) {
if (actions == null || actions.isEmpty()) {
return Collections.emptyList();
}
return filterCancellations(actions).collect(Collectors.toList());
}
} }

View File

@@ -223,6 +223,17 @@ public interface TargetRepository extends BaseEntityRepository<JpaTarget, Long>,
@Query("SELECT t FROM JpaTarget t WHERE t.id IN ?1") @Query("SELECT t FROM JpaTarget t WHERE t.id IN ?1")
List<JpaTarget> findAllById(Iterable<Long> ids); List<JpaTarget> findAllById(Iterable<Long> ids);
/**
* Finds all targets for the given list of controller IDs.
*
* @param controllerIds
* The controller IDs to look for.
*
* @return The list of matching targets.
*/
@Query("SELECT t FROM JpaTarget t WHERE t.controllerId IN ?1")
List<JpaTarget> findAllByControllerId(Iterable<String> controllerIds);
/** /**
* *
* Finds all targets of a rollout group. * Finds all targets of a rollout group.

View File

@@ -1039,7 +1039,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
final DistributionSet testDs = testdataFactory.createDistributionSet("1"); final DistributionSet testDs = testdataFactory.createDistributionSet("1");
final List<Target> testTarget = testdataFactory.createTargets(1); final List<Target> testTarget = testdataFactory.createTargets(1);
final Long actionId = assignDistributionSet(testDs, testTarget).getActions().get(0); final Long actionId = assignDistributionSet(testDs, testTarget).getActionIds().get(0);
controllerManagement.addUpdateActionStatus(entityFactory.actionStatus().create(actionId) controllerManagement.addUpdateActionStatus(entityFactory.actionStatus().create(actionId)
.status(Action.Status.RUNNING).messages(Lists.newArrayList("proceeding message 1"))); .status(Action.Status.RUNNING).messages(Lists.newArrayList("proceeding message 1")));
@@ -1068,7 +1068,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
// test for informational status // test for informational status
final Long actionId1 = assignDistributionSet(testdataFactory.createDistributionSet("ds1"), final Long actionId1 = assignDistributionSet(testdataFactory.createDistributionSet("ds1"),
testdataFactory.createTargets(1, "t1")).getActions().get(0); testdataFactory.createTargets(1, "t1")).getActionIds().get(0);
assertThat(actionId1).isNotNull(); assertThat(actionId1).isNotNull();
for (int i = 0; i < maxStatusEntries; ++i) { for (int i = 0; i < maxStatusEntries; ++i) {
controllerManagement.addInformationalActionStatus(entityFactory.actionStatus().create(actionId1) controllerManagement.addInformationalActionStatus(entityFactory.actionStatus().create(actionId1)
@@ -1079,7 +1079,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
// test for update status (and mixed case) // test for update status (and mixed case)
final Long actionId2 = assignDistributionSet(testdataFactory.createDistributionSet("ds2"), final Long actionId2 = assignDistributionSet(testdataFactory.createDistributionSet("ds2"),
testdataFactory.createTargets(1, "t2")).getActions().get(0); testdataFactory.createTargets(1, "t2")).getActionIds().get(0);
assertThat(actionId2).isNotEqualTo(actionId1); assertThat(actionId2).isNotEqualTo(actionId1);
for (int i = 0; i < maxStatusEntries; ++i) { for (int i = 0; i < maxStatusEntries; ++i) {
controllerManagement.addUpdateActionStatus(entityFactory.actionStatus().create(actionId2) controllerManagement.addUpdateActionStatus(entityFactory.actionStatus().create(actionId2)
@@ -1102,7 +1102,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
final int maxMessages = quotaManagement.getMaxMessagesPerActionStatus(); final int maxMessages = quotaManagement.getMaxMessagesPerActionStatus();
final Long actionId = assignDistributionSet(testdataFactory.createDistributionSet("ds1"), final Long actionId = assignDistributionSet(testdataFactory.createDistributionSet("ds1"),
testdataFactory.createTargets(1)).getActions().get(0); testdataFactory.createTargets(1)).getActionIds().get(0);
assertThat(actionId).isNotNull(); assertThat(actionId).isNotNull();
final List<String> messages = Lists.newArrayList(); final List<String> messages = Lists.newArrayList();
@@ -1258,19 +1258,22 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
IntStream.range(0, maxMessages).forEach(i -> controllerManagement IntStream.range(0, maxMessages).forEach(i -> controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Status.DOWNLOADED))); .addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Status.DOWNLOADED)));
fail("No QuotaExceededException thrown for too many DOWNLOADED updateActionStatus updates"); fail("No QuotaExceededException thrown for too many DOWNLOADED updateActionStatus updates");
} catch (QuotaExceededException e) { } } catch (final QuotaExceededException e) {
}
try { try {
IntStream.range(0, maxMessages).forEach(i -> controllerManagement IntStream.range(0, maxMessages).forEach(i -> controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Status.ERROR))); .addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Status.ERROR)));
fail("No QuotaExceededException thrown for too many ERROR updateActionStatus updates"); fail("No QuotaExceededException thrown for too many ERROR updateActionStatus updates");
} catch (QuotaExceededException e) { } } catch (final QuotaExceededException e) {
}
try { try {
IntStream.range(0, maxMessages).forEach(i -> controllerManagement IntStream.range(0, maxMessages).forEach(i -> controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Status.FINISHED))); .addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Status.FINISHED)));
fail("No QuotaExceededException thrown for too many FINISHED updateActionStatus updates"); fail("No QuotaExceededException thrown for too many FINISHED updateActionStatus updates");
} catch (QuotaExceededException e) { } } catch (final QuotaExceededException e) {
}
} }
@Test @Test
@@ -1290,19 +1293,22 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
IntStream.range(0, maxMessages).forEach(i -> controllerManagement IntStream.range(0, maxMessages).forEach(i -> controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Status.DOWNLOADED))); .addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Status.DOWNLOADED)));
fail("No QuotaExceededException thrown for too many DOWNLOADED updateActionStatus updates"); fail("No QuotaExceededException thrown for too many DOWNLOADED updateActionStatus updates");
} catch (QuotaExceededException e) { } } catch (final QuotaExceededException e) {
}
try { try {
IntStream.range(0, maxMessages).forEach(i -> controllerManagement IntStream.range(0, maxMessages).forEach(i -> controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Status.ERROR))); .addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Status.ERROR)));
fail("No QuotaExceededException thrown for too many ERROR updateActionStatus updates"); fail("No QuotaExceededException thrown for too many ERROR updateActionStatus updates");
} catch (QuotaExceededException e) { } } catch (final QuotaExceededException e) {
}
try { try {
IntStream.range(0, maxMessages).forEach(i -> controllerManagement IntStream.range(0, maxMessages).forEach(i -> controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Status.FINISHED))); .addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Status.FINISHED)));
fail("No QuotaExceededException thrown for too many FINISHED updateActionStatus updates"); fail("No QuotaExceededException thrown for too many FINISHED updateActionStatus updates");
} catch (QuotaExceededException e) { } } catch (final QuotaExceededException e) {
}
} }
@Test @Test
@@ -1386,7 +1392,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
final Long forcedDistributionSetId = testdataFactory.createDistributionSet("forcedDs1").getId(); final Long forcedDistributionSetId = testdataFactory.createDistributionSet("forcedDs1").getId();
final DistributionSetAssignmentResult assignmentResult = final DistributionSetAssignmentResult assignmentResult =
assignDistributionSet(forcedDistributionSetId, DEFAULT_CONTROLLER_ID, Action.ActionType.SOFT); assignDistributionSet(forcedDistributionSetId, DEFAULT_CONTROLLER_ID, Action.ActionType.SOFT);
addUpdateActionStatus(assignmentResult.getActions().get(0), DEFAULT_CONTROLLER_ID, Status.FINISHED); addUpdateActionStatus(assignmentResult.getActions().get(0).getId(), DEFAULT_CONTROLLER_ID, Status.FINISHED);
assertAssignedDistributionSetId(DEFAULT_CONTROLLER_ID, forcedDistributionSetId); assertAssignedDistributionSetId(DEFAULT_CONTROLLER_ID, forcedDistributionSetId);
assertInstalledDistributionSetId(DEFAULT_CONTROLLER_ID, forcedDistributionSetId); assertInstalledDistributionSetId(DEFAULT_CONTROLLER_ID, forcedDistributionSetId);
assertNoActiveActionsExistsForControllerId(DEFAULT_CONTROLLER_ID); assertNoActiveActionsExistsForControllerId(DEFAULT_CONTROLLER_ID);

View File

@@ -25,6 +25,7 @@ import java.util.stream.Collectors;
import java.util.stream.IntStream; import java.util.stream.IntStream;
import org.eclipse.hawkbit.repository.ActionStatusFields; import org.eclipse.hawkbit.repository.ActionStatusFields;
import org.eclipse.hawkbit.repository.event.remote.MultiActionEvent;
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;
@@ -85,6 +86,10 @@ import io.qameta.allure.Story;
@Feature("Component Tests - Repository") @Feature("Component Tests - Repository")
@Story("Deployment Management") @Story("Deployment Management")
public class DeploymentManagementTest extends AbstractJpaIntegrationTest { public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
private static final boolean STATE_ACTIVE = true;
private static final boolean STATE_INACTIVE = false;
private EventHandlerStub eventHandlerStub; private EventHandlerStub eventHandlerStub;
private CancelEventHandlerStub cancelEventHandlerStub; private CancelEventHandlerStub cancelEventHandlerStub;
@@ -146,7 +151,7 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
new ArrayList<DistributionSetTag>()); new ArrayList<DistributionSetTag>());
final List<Target> testTarget = testdataFactory.createTargets(1); final List<Target> testTarget = testdataFactory.createTargets(1);
// one action with one action status is generated // one action with one action status is generated
final Long actionId = assignDistributionSet(testDs, testTarget).getActions().get(0); final Long actionId = assignDistributionSet(testDs, testTarget).getActionIds().get(0);
final Action action = deploymentManagement.findActionWithDetails(actionId).get(); final Action action = deploymentManagement.findActionWithDetails(actionId).get();
assertThat(action.getDistributionSet()).as("DistributionSet in action").isNotNull(); assertThat(action.getDistributionSet()).as("DistributionSet in action").isNotNull();
@@ -163,7 +168,7 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
new ArrayList<DistributionSetTag>()); new ArrayList<DistributionSetTag>());
final List<Target> testTarget = testdataFactory.createTargets(1); final List<Target> testTarget = testdataFactory.createTargets(1);
// one action with one action status is generated // one action with one action status is generated
final Long actionId = assignDistributionSet(testDs, testTarget).getActions().get(0); final Long actionId = assignDistributionSet(testDs, testTarget).getActionIds().get(0);
// act // act
final Slice<Action> actions = deploymentManagement.findActionsByTarget(testTarget.get(0).getControllerId(), final Slice<Action> actions = deploymentManagement.findActionsByTarget(testTarget.get(0).getControllerId(),
@@ -212,7 +217,7 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
new ArrayList<DistributionSetTag>()); new ArrayList<DistributionSetTag>());
final List<Target> testTarget = testdataFactory.createTargets(1); final List<Target> testTarget = testdataFactory.createTargets(1);
// one action with one action status is generated // one action with one action status is generated
final Long actionId = assignDistributionSet(testDs, testTarget).getActions().get(0); final Long actionId = assignDistributionSet(testDs, testTarget).getActionIds().get(0);
final Slice<Action> actions = deploymentManagement.findActionsByTarget(testTarget.get(0).getControllerId(), final Slice<Action> actions = deploymentManagement.findActionsByTarget(testTarget.get(0).getControllerId(),
PAGE); PAGE);
final ActionStatus expectedActionStatus = ((JpaAction) actions.getContent().get(0)).getActionStatus().get(0); final ActionStatus expectedActionStatus = ((JpaAction) actions.getContent().get(0)).getActionStatus().get(0);
@@ -231,7 +236,7 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
new ArrayList<DistributionSetTag>()); new ArrayList<DistributionSetTag>());
final List<Target> testTarget = testdataFactory.createTargets(1); final List<Target> testTarget = testdataFactory.createTargets(1);
// one action with one action status is generated // one action with one action status is generated
final Long actionId = assignDistributionSet(testDs, testTarget).getActions().get(0); final Long actionId = assignDistributionSet(testDs, testTarget).getActionIds().get(0);
// create action-status entry with one message // create action-status entry with one message
controllerManagement.addUpdateActionStatus(entityFactory.actionStatus().create(actionId) controllerManagement.addUpdateActionStatus(entityFactory.actionStatus().create(actionId)
.status(Action.Status.FINISHED).messages(Arrays.asList("finished message"))); .status(Action.Status.FINISHED).messages(Arrays.asList("finished message")));
@@ -485,8 +490,8 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
final List<Target> targets = deploymentManagement.offlineAssignedDistributionSet(ds.getId(), controllerIds) final List<Target> targets = deploymentManagement.offlineAssignedDistributionSet(ds.getId(), controllerIds)
.getAssignedEntity(); .getAssignedEntity();
assertThat(actionRepository.count()).isEqualTo(20); assertThat(actionRepository.count()).isEqualTo(20);
assertThat(actionRepository.findByDistributionSetId(PAGE, ds.getId())) assertThat(actionRepository.findByDistributionSetId(PAGE, ds.getId())).as("Offline actions are not active")
.as("Offline actions are not active").allMatch(action -> !action.isActive()); .allMatch(action -> !action.isActive());
assertThat(targetManagement.findByInstalledDistributionSet(PAGE, ds.getId()).getContent()).containsAll(targets) assertThat(targetManagement.findByInstalledDistributionSet(PAGE, ds.getId()).getContent()).containsAll(targets)
.hasSize(10).containsAll(targetManagement.findByAssignedDistributionSet(PAGE, ds.getId())) .hasSize(10).containsAll(targetManagement.findByAssignedDistributionSet(PAGE, ds.getId()))
.as("InstallationDate set").allMatch(target -> target.getInstallationDate() >= current) .as("InstallationDate set").allMatch(target -> target.getInstallationDate() >= current)
@@ -515,26 +520,14 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
final DistributionSet ds1 = testdataFactory.createDistributionSet("1"); final DistributionSet ds1 = testdataFactory.createDistributionSet("1");
assignDistributionSet(ds1, targets); assignDistributionSet(ds1, targets);
List<Action> assignmentOne = actionRepository.findByDistributionSetId(PAGE, ds1.getId()).getContent(); assertDsExclusivelyAssignedToTargets(targets, ds1.getId(), STATE_ACTIVE, Status.RUNNING);
assertThat(assignmentOne).hasSize(10).as("Is active").allMatch(Action::isActive)
.as("Is assigned to DS " + ds1.getId())
.allMatch(action -> action.getDistributionSet().getId().equals(ds1.getId())).as("Is running")
.allMatch(action -> action.getStatus() == Status.RUNNING);
// Second assignment // Second assignment
final DistributionSet ds2 = testdataFactory.createDistributionSet("2"); final DistributionSet ds2 = testdataFactory.createDistributionSet("2");
assignDistributionSet(ds2, targets); assignDistributionSet(ds2, targets);
final List<Action> assignmentTwo = actionRepository.findByDistributionSetId(PAGE, ds2.getId()).getContent(); assertDsExclusivelyAssignedToTargets(targets, ds2.getId(), STATE_ACTIVE, Status.RUNNING);
assignmentOne = actionRepository.findByDistributionSetId(PAGE, ds1.getId()).getContent(); assertDsExclusivelyAssignedToTargets(targets, ds1.getId(), STATE_INACTIVE, Status.CANCELED);
assertThat(assignmentTwo).hasSize(10).as("Is active").allMatch(Action::isActive)
.as("Is assigned to DS " + ds2.getId())
.allMatch(action -> action.getDistributionSet().getId().equals(ds2.getId())).as("Is running")
.allMatch(action -> action.getStatus() == Status.RUNNING);
assertThat(assignmentOne).hasSize(10).as("Is active").allMatch(action -> !action.isActive())
.as("Is assigned to DS " + ds1.getId())
.allMatch(action -> action.getDistributionSet().getId().equals(ds1.getId())).as("Is cancelled")
.allMatch(action -> action.getStatus() == Status.CANCELED);
assertThat(targetManagement.findByAssignedDistributionSet(PAGE, ds2.getId()).getContent()).hasSize(10) assertThat(targetManagement.findByAssignedDistributionSet(PAGE, ds2.getId()).getContent()).hasSize(10)
.as("InstallationDate not set").allMatch(target -> (target.getInstallationDate() == null)); .as("InstallationDate not set").allMatch(target -> (target.getInstallationDate() == null));
@@ -545,6 +538,44 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
} }
} }
@Test
@Description("If multi-assignment is enabled, verify that the previous Distribution Set assignment is not canceled when a new one is assigned.")
@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 = MultiActionEvent.class, count = 2),
@Expect(type = TargetAssignDistributionSetEvent.class, count = 0) })
public void previousAssignmentsAreNotCanceledInMultiAssignMode() {
enableMultiAssignments();
final List<Target> targets = testdataFactory.createTargets(10);
// First assignment
final DistributionSet ds1 = testdataFactory.createDistributionSet("Multi-assign-1");
assignDistributionSet(ds1, targets);
assertDsExclusivelyAssignedToTargets(targets, ds1.getId(), STATE_ACTIVE, Status.RUNNING);
// Second assignment
final DistributionSet ds2 = testdataFactory.createDistributionSet("Multi-assign-2");
assignDistributionSet(ds2, targets);
assertDsExclusivelyAssignedToTargets(targets, ds2.getId(), STATE_ACTIVE, Status.RUNNING);
assertDsExclusivelyAssignedToTargets(targets, ds1.getId(), STATE_ACTIVE, Status.RUNNING);
}
private void assertDsExclusivelyAssignedToTargets(final List<Target> targets, final long dsId, final boolean active,
final Status status) {
final List<Action> assignment = actionRepository.findByDistributionSetId(PAGE, dsId).getContent();
assertThat(assignment).hasSize(10).allMatch(action -> action.isActive() == active)
.as("Is assigned to DS " + dsId).allMatch(action -> action.getDistributionSet().getId().equals(dsId))
.as("State is " + status).allMatch(action -> action.getStatus() == status);
final long[] targetIds = targets.stream().mapToLong(Target::getId).toArray();
assertThat(targetIds).as("All targets represented in assignment").containsExactlyInAnyOrder(
assignment.stream().mapToLong(action -> action.getTarget().getId()).toArray());
}
/** /**
* test a simple deployment by calling the * test a simple deployment by calling the
* {@link TargetRepository#assignDistributionSet(DistributionSet, Iterable)} * {@link TargetRepository#assignDistributionSet(DistributionSet, Iterable)}
@@ -992,7 +1023,7 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
ds.getId(), ActionType.SOFT, ds.getId(), ActionType.SOFT,
org.eclipse.hawkbit.repository.model.RepositoryModelConstants.NO_FORCE_TIME, org.eclipse.hawkbit.repository.model.RepositoryModelConstants.NO_FORCE_TIME,
Arrays.asList(target.getControllerId())); Arrays.asList(target.getControllerId()));
final Long actionId = assignDistributionSet.getActions().get(0); final Long actionId = assignDistributionSet.getActionIds().get(0);
// verify preparation // verify preparation
Action findAction = deploymentManagement.findAction(actionId).get(); Action findAction = deploymentManagement.findAction(actionId).get();
assertThat(findAction.getActionType()).as("action type is wrong").isEqualTo(ActionType.SOFT); assertThat(findAction.getActionType()).as("action type is wrong").isEqualTo(ActionType.SOFT);
@@ -1016,7 +1047,7 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
ds.getId(), ActionType.FORCED, ds.getId(), ActionType.FORCED,
org.eclipse.hawkbit.repository.model.RepositoryModelConstants.NO_FORCE_TIME, org.eclipse.hawkbit.repository.model.RepositoryModelConstants.NO_FORCE_TIME,
Arrays.asList(target.getControllerId())); Arrays.asList(target.getControllerId()));
final Long actionId = assignDistributionSet.getActions().get(0); final Long actionId = assignDistributionSet.getActionIds().get(0);
// verify perparation // verify perparation
Action findAction = deploymentManagement.findAction(actionId).get(); Action findAction = deploymentManagement.findAction(actionId).get();
assertThat(findAction.getActionType()).as("action type is wrong").isEqualTo(ActionType.FORCED); assertThat(findAction.getActionType()).as("action type is wrong").isEqualTo(ActionType.FORCED);
@@ -1087,14 +1118,12 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
assertThat(event).isNotNull(); assertThat(event).isNotNull();
assertThat(event.getDistributionSetId()).isEqualTo(ds.getId()); assertThat(event.getDistributionSetId()).isEqualTo(ds.getId());
List<Long> eventActionIds = event.getActions().values().stream().map(ActionProperties::getId) final List<Long> eventActionIds = event.getActions().values().stream().map(ActionProperties::getId)
.collect(Collectors.toList()); .collect(Collectors.toList());
List<Long> targetActiveActionIds = targets.stream() final List<Long> targetActiveActionIds = targets.stream()
.map(t -> deploymentManagement.findActiveActionsByTarget(PAGE, t.getControllerId()).getContent()) .map(t -> deploymentManagement.findActiveActionsByTarget(PAGE, t.getControllerId()).getContent())
.flatMap(List::stream) .flatMap(List::stream).map(Action::getId).collect(Collectors.toList());
.map(Action::getId)
.collect(Collectors.toList());
assertThat(eventActionIds).containsOnlyElementsOf(targetActiveActionIds); assertThat(eventActionIds).containsOnlyElementsOf(targetActiveActionIds);
} }
@@ -1216,4 +1245,8 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
} }
} }
private void enableMultiAssignments() {
tenantConfigurationManagement.addOrUpdateConfiguration(TenantConfigurationKey.MULTI_ASSIGNMENTS_ENABLED, true);
}
} }

View File

@@ -104,7 +104,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
testdataFactory.createTarget(knownControllerId); testdataFactory.createTarget(knownControllerId);
final DistributionSetAssignmentResult assignmentResult = deploymentManagement.assignDistributionSet( final DistributionSetAssignmentResult assignmentResult = deploymentManagement.assignDistributionSet(
knownDistributionSet.getId(), ActionType.FORCED, 0, Collections.singleton(knownControllerId)); knownDistributionSet.getId(), ActionType.FORCED, 0, Collections.singleton(knownControllerId));
final Long manuallyAssignedActionId = assignmentResult.getActions().get(0); final Long manuallyAssignedActionId = assignmentResult.getActionIds().get(0);
// create rollout with the same distribution set already assigned // create rollout with the same distribution set already assigned
// start rollout // start rollout
@@ -144,7 +144,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
testdataFactory.createTarget(knownControllerId); testdataFactory.createTarget(knownControllerId);
final DistributionSetAssignmentResult assignmentResult = deploymentManagement.assignDistributionSet( final DistributionSetAssignmentResult assignmentResult = deploymentManagement.assignDistributionSet(
firstDistributionSet.getId(), ActionType.FORCED, 0, Collections.singleton(knownControllerId)); firstDistributionSet.getId(), ActionType.FORCED, 0, Collections.singleton(knownControllerId));
final Long manuallyAssignedActionId = assignmentResult.getActions().get(0); final Long manuallyAssignedActionId = assignmentResult.getActionIds().get(0);
// create rollout with the same distribution set already assigned // create rollout with the same distribution set already assigned
// start rollout // start rollout

View File

@@ -96,7 +96,7 @@ public class TargetManagementSearchTest extends AbstractJpaIntegrationTest {
final String assignedB = targBs.iterator().next().getControllerId(); final String assignedB = targBs.iterator().next().getControllerId();
assignDistributionSet(setA.getId(), assignedB); assignDistributionSet(setA.getId(), assignedB);
final String installedC = targCs.iterator().next().getControllerId(); final String installedC = targCs.iterator().next().getControllerId();
final Long actionId = assignDistributionSet(installedSet.getId(), assignedC).getActions().get(0); final Long actionId = assignDistributionSet(installedSet.getId(), assignedC).getActionIds().get(0);
// add attributes to match against only attribute value or attribute // add attributes to match against only attribute value or attribute
// value and name // value and name
@@ -701,7 +701,7 @@ public class TargetManagementSearchTest extends AbstractJpaIntegrationTest {
List<Target> installedtargets = testdataFactory.createTargets(10, "assigned", "assigned"); List<Target> installedtargets = testdataFactory.createTargets(10, "assigned", "assigned");
// set on installed and assign another one // set on installed and assign another one
assignDistributionSet(installedSet, installedtargets).getActions().forEach(actionId -> controllerManagement assignDistributionSet(installedSet, installedtargets).getActionIds().forEach(actionId -> controllerManagement
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Status.FINISHED))); .addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Status.FINISHED)));
assignDistributionSet(assignedSet, installedtargets); assignDistributionSet(assignedSet, installedtargets);

View File

@@ -477,7 +477,7 @@ public class TargetManagementTest extends AbstractJpaIntegrationTest {
final DistributionSetAssignmentResult result = assignDistributionSet(set.getId(), "4711"); final DistributionSetAssignmentResult result = assignDistributionSet(set.getId(), "4711");
controllerManagement.addUpdateActionStatus( controllerManagement.addUpdateActionStatus(
entityFactory.actionStatus().create(result.getActions().get(0)).status(Status.FINISHED)); entityFactory.actionStatus().create(result.getActionIds().get(0)).status(Status.FINISHED));
assignDistributionSet(set2.getId(), "4711"); assignDistributionSet(set2.getId(), "4711");
target = targetManagement.getByControllerID("4711").get(); target = targetManagement.getByControllerID("4711").get();

View File

@@ -59,7 +59,7 @@ public class AutoAssignCheckerTest extends AbstractJpaIntegrationTest {
testdataFactory.createTarget(knownControllerId); testdataFactory.createTarget(knownControllerId);
final DistributionSetAssignmentResult assignmentResult = deploymentManagement.assignDistributionSet( final DistributionSetAssignmentResult assignmentResult = deploymentManagement.assignDistributionSet(
firstDistributionSet.getId(), ActionType.FORCED, 0, Collections.singleton(knownControllerId)); firstDistributionSet.getId(), ActionType.FORCED, 0, Collections.singleton(knownControllerId));
final Long manuallyAssignedActionId = assignmentResult.getActions().get(0); final Long manuallyAssignedActionId = assignmentResult.getActionIds().get(0);
// target filter query that matches all targets // target filter query that matches all targets
final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement

View File

@@ -81,7 +81,7 @@ public class AutoActionCleanupTest extends AbstractJpaIntegrationTest {
final DistributionSet ds2 = testdataFactory.createDistributionSet("ds2"); final DistributionSet ds2 = testdataFactory.createDistributionSet("ds2");
final Long action1 = deploymentManagement.assignDistributionSet(ds1.getId(), ActionType.FORCED, 0, final Long action1 = deploymentManagement.assignDistributionSet(ds1.getId(), ActionType.FORCED, 0,
Collections.singletonList(trg1.getControllerId())).getActions().get(0); Collections.singletonList(trg1.getControllerId())).getActionIds().get(0);
deploymentManagement.assignDistributionSet(ds2.getId(), ActionType.FORCED, 0, deploymentManagement.assignDistributionSet(ds2.getId(), ActionType.FORCED, 0,
Collections.singletonList(trg2.getControllerId())); Collections.singletonList(trg2.getControllerId()));
@@ -110,11 +110,11 @@ public class AutoActionCleanupTest extends AbstractJpaIntegrationTest {
final DistributionSet ds2 = testdataFactory.createDistributionSet("ds2"); final DistributionSet ds2 = testdataFactory.createDistributionSet("ds2");
final Long action1 = deploymentManagement.assignDistributionSet(ds1.getId(), ActionType.FORCED, 0, final Long action1 = deploymentManagement.assignDistributionSet(ds1.getId(), ActionType.FORCED, 0,
Collections.singletonList(trg1.getControllerId())).getActions().get(0); Collections.singletonList(trg1.getControllerId())).getActionIds().get(0);
final Long action2 = deploymentManagement.assignDistributionSet(ds2.getId(), ActionType.FORCED, 0, final Long action2 = deploymentManagement.assignDistributionSet(ds2.getId(), ActionType.FORCED, 0,
Collections.singletonList(trg2.getControllerId())).getActions().get(0); Collections.singletonList(trg2.getControllerId())).getActionIds().get(0);
final Long action3 = deploymentManagement.assignDistributionSet(ds2.getId(), ActionType.FORCED, 0, final Long action3 = deploymentManagement.assignDistributionSet(ds2.getId(), ActionType.FORCED, 0,
Collections.singletonList(trg3.getControllerId())).getActions().get(0); Collections.singletonList(trg3.getControllerId())).getActionIds().get(0);
assertThat(actionRepository.count()).isEqualTo(3); assertThat(actionRepository.count()).isEqualTo(3);
@@ -145,11 +145,11 @@ public class AutoActionCleanupTest extends AbstractJpaIntegrationTest {
final DistributionSet ds2 = testdataFactory.createDistributionSet("ds2"); final DistributionSet ds2 = testdataFactory.createDistributionSet("ds2");
final Long action1 = deploymentManagement.assignDistributionSet(ds1.getId(), ActionType.FORCED, 0, final Long action1 = deploymentManagement.assignDistributionSet(ds1.getId(), ActionType.FORCED, 0,
Collections.singletonList(trg1.getControllerId())).getActions().get(0); Collections.singletonList(trg1.getControllerId())).getActionIds().get(0);
final Long action2 = deploymentManagement.assignDistributionSet(ds2.getId(), ActionType.FORCED, 0, final Long action2 = deploymentManagement.assignDistributionSet(ds2.getId(), ActionType.FORCED, 0,
Collections.singletonList(trg2.getControllerId())).getActions().get(0); Collections.singletonList(trg2.getControllerId())).getActionIds().get(0);
final Long action3 = deploymentManagement.assignDistributionSet(ds2.getId(), ActionType.FORCED, 0, final Long action3 = deploymentManagement.assignDistributionSet(ds2.getId(), ActionType.FORCED, 0,
Collections.singletonList(trg3.getControllerId())).getActions().get(0); Collections.singletonList(trg3.getControllerId())).getActionIds().get(0);
assertThat(actionRepository.count()).isEqualTo(3); assertThat(actionRepository.count()).isEqualTo(3);
@@ -182,11 +182,11 @@ public class AutoActionCleanupTest extends AbstractJpaIntegrationTest {
final DistributionSet ds2 = testdataFactory.createDistributionSet("ds2"); final DistributionSet ds2 = testdataFactory.createDistributionSet("ds2");
final Long action1 = deploymentManagement.assignDistributionSet(ds1.getId(), ActionType.FORCED, 0, final Long action1 = deploymentManagement.assignDistributionSet(ds1.getId(), ActionType.FORCED, 0,
Collections.singletonList(trg1.getControllerId())).getActions().get(0); Collections.singletonList(trg1.getControllerId())).getActionIds().get(0);
final Long action2 = deploymentManagement.assignDistributionSet(ds2.getId(), ActionType.FORCED, 0, final Long action2 = deploymentManagement.assignDistributionSet(ds2.getId(), ActionType.FORCED, 0,
Collections.singletonList(trg2.getControllerId())).getActions().get(0); Collections.singletonList(trg2.getControllerId())).getActionIds().get(0);
final Long action3 = deploymentManagement.assignDistributionSet(ds2.getId(), ActionType.FORCED, 0, final Long action3 = deploymentManagement.assignDistributionSet(ds2.getId(), ActionType.FORCED, 0,
Collections.singletonList(trg3.getControllerId())).getActions().get(0); Collections.singletonList(trg3.getControllerId())).getActionIds().get(0);
assertThat(actionRepository.count()).isEqualTo(3); assertThat(actionRepository.count()).isEqualTo(3);

View File

@@ -35,7 +35,6 @@ import org.junit.Test;
import org.springframework.hateoas.MediaTypes; import org.springframework.hateoas.MediaTypes;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.integration.json.JsonPathUtils; import org.springframework.integration.json.JsonPathUtils;
import org.springframework.test.util.JsonPathExpectationsHelper;
import io.qameta.allure.Description; import io.qameta.allure.Description;
import io.qameta.allure.Feature; import io.qameta.allure.Feature;
@@ -52,13 +51,13 @@ public class DdiCancelActionTest extends AbstractDDiApiIntegrationTest {
@Description("Tests that the cancel action resource can be used with CBOR.") @Description("Tests that the cancel action resource can be used with CBOR.")
public void cancelActionCbor() throws Exception { public void cancelActionCbor() throws Exception {
final DistributionSet ds = testdataFactory.createDistributionSet(""); final DistributionSet ds = testdataFactory.createDistributionSet("");
final Target savedTarget = testdataFactory.createTarget(); testdataFactory.createTarget();
final Long actionId = assignDistributionSet(ds.getId(), TestdataFactory.DEFAULT_CONTROLLER_ID).getActions() final Long actionId = assignDistributionSet(ds.getId(), TestdataFactory.DEFAULT_CONTROLLER_ID).getActionIds()
.get(0); .get(0);
final Action cancelAction = deploymentManagement.cancelAction(actionId); final Action cancelAction = deploymentManagement.cancelAction(actionId);
// check that we can get the cancel action as CBOR // check that we can get the cancel action as CBOR
byte[] result = mvc.perform(get("/{tenant}/controller/v1/" + TestdataFactory.DEFAULT_CONTROLLER_ID + "/cancelAction/" final byte[] result = mvc.perform(get("/{tenant}/controller/v1/" + TestdataFactory.DEFAULT_CONTROLLER_ID + "/cancelAction/"
+ cancelAction.getId(), tenantAware.getCurrentTenant()).accept(DdiRestConstants.MEDIA_TYPE_CBOR)) + cancelAction.getId(), tenantAware.getCurrentTenant()).accept(DdiRestConstants.MEDIA_TYPE_CBOR))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(DdiRestConstants.MEDIA_TYPE_CBOR_UTF8)) .andExpect(content().contentType(DdiRestConstants.MEDIA_TYPE_CBOR_UTF8))
@@ -81,7 +80,7 @@ public class DdiCancelActionTest extends AbstractDDiApiIntegrationTest {
final DistributionSet ds = testdataFactory.createDistributionSet(""); final DistributionSet ds = testdataFactory.createDistributionSet("");
final Target savedTarget = testdataFactory.createTarget(); final Target savedTarget = testdataFactory.createTarget();
final Long actionId = assignDistributionSet(ds.getId(), savedTarget.getControllerId()).getActions().get(0); final Long actionId = assignDistributionSet(ds.getId(), savedTarget.getControllerId()).getActionIds().get(0);
final Action cancelAction = deploymentManagement.cancelAction(actionId); final Action cancelAction = deploymentManagement.cancelAction(actionId);
@@ -134,7 +133,7 @@ public class DdiCancelActionTest extends AbstractDDiApiIntegrationTest {
final DistributionSet ds = testdataFactory.createDistributionSet(""); final DistributionSet ds = testdataFactory.createDistributionSet("");
final Target savedTarget = testdataFactory.createTarget(); final Target savedTarget = testdataFactory.createTarget();
final Long actionId = assignDistributionSet(ds.getId(), savedTarget.getControllerId()).getActions().get(0); final Long actionId = assignDistributionSet(ds.getId(), savedTarget.getControllerId()).getActionIds().get(0);
long current = System.currentTimeMillis(); long current = System.currentTimeMillis();
mvc.perform(get("/{tenant}/controller/v1/{controller}", tenantAware.getCurrentTenant(), mvc.perform(get("/{tenant}/controller/v1/{controller}", tenantAware.getCurrentTenant(),
@@ -250,7 +249,7 @@ public class DdiCancelActionTest extends AbstractDDiApiIntegrationTest {
final Target savedTarget = testdataFactory.createTarget(targetid); final Target savedTarget = testdataFactory.createTarget(targetid);
final List<Target> toAssign = new ArrayList<>(); final List<Target> toAssign = new ArrayList<>();
toAssign.add(savedTarget); toAssign.add(savedTarget);
final Long actionId = assignDistributionSet(ds, toAssign).getActions().get(0); final Long actionId = assignDistributionSet(ds, toAssign).getActionIds().get(0);
return deploymentManagement.cancelAction(actionId); return deploymentManagement.cancelAction(actionId);
} }
@@ -263,7 +262,7 @@ public class DdiCancelActionTest extends AbstractDDiApiIntegrationTest {
final Target savedTarget = testdataFactory.createTarget(); final Target savedTarget = testdataFactory.createTarget();
final Long actionId = assignDistributionSet(ds.getId(), TestdataFactory.DEFAULT_CONTROLLER_ID).getActions() final Long actionId = assignDistributionSet(ds.getId(), TestdataFactory.DEFAULT_CONTROLLER_ID).getActionIds()
.get(0); .get(0);
// cancel action manually // cancel action manually
@@ -338,11 +337,11 @@ public class DdiCancelActionTest extends AbstractDDiApiIntegrationTest {
final Target savedTarget = testdataFactory.createTarget(); final Target savedTarget = testdataFactory.createTarget();
final Long actionId = assignDistributionSet(ds.getId(), TestdataFactory.DEFAULT_CONTROLLER_ID).getActions() final Long actionId = assignDistributionSet(ds.getId(), TestdataFactory.DEFAULT_CONTROLLER_ID).getActionIds()
.get(0); .get(0);
final Long actionId2 = assignDistributionSet(ds2.getId(), TestdataFactory.DEFAULT_CONTROLLER_ID).getActions() final Long actionId2 = assignDistributionSet(ds2.getId(), TestdataFactory.DEFAULT_CONTROLLER_ID).getActionIds()
.get(0); .get(0);
final Long actionId3 = assignDistributionSet(ds3.getId(), TestdataFactory.DEFAULT_CONTROLLER_ID).getActions() final Long actionId3 = assignDistributionSet(ds3.getId(), TestdataFactory.DEFAULT_CONTROLLER_ID).getActionIds()
.get(0); .get(0);
assertThat(deploymentManagement.countActionStatusAll()).isEqualTo(3); assertThat(deploymentManagement.countActionStatusAll()).isEqualTo(3);
@@ -452,7 +451,7 @@ public class DdiCancelActionTest extends AbstractDDiApiIntegrationTest {
testdataFactory.createTarget(); testdataFactory.createTarget();
final DistributionSet ds = testdataFactory.createDistributionSet(""); final DistributionSet ds = testdataFactory.createDistributionSet("");
final Long actionId = assignDistributionSet(ds.getId(), TestdataFactory.DEFAULT_CONTROLLER_ID).getActions() final Long actionId = assignDistributionSet(ds.getId(), TestdataFactory.DEFAULT_CONTROLLER_ID).getActionIds()
.get(0); .get(0);
final Action cancelAction = deploymentManagement.cancelAction(actionId); final Action cancelAction = deploymentManagement.cancelAction(actionId);

View File

@@ -267,7 +267,7 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest {
final DistributionSet ds = testdataFactory.createDistributionSet("", true); final DistributionSet ds = testdataFactory.createDistributionSet("", true);
final Long actionId = deploymentManagement.assignDistributionSet(ds.getId(), ActionType.TIMEFORCED, final Long actionId = deploymentManagement.assignDistributionSet(ds.getId(), ActionType.TIMEFORCED,
System.currentTimeMillis() + 2_000, Arrays.asList(target.getControllerId())).getActions().get(0); System.currentTimeMillis() + 2_000, Arrays.asList(target.getControllerId())).getActionIds().get(0);
MvcResult mvcResult = mvc.perform(get("/{tenant}/controller/v1/4712", tenantAware.getCurrentTenant())) MvcResult mvcResult = mvc.perform(get("/{tenant}/controller/v1/4712", tenantAware.getCurrentTenant()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
@@ -566,7 +566,7 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest {
final List<Target> toAssign = Arrays.asList(target); final List<Target> toAssign = Arrays.asList(target);
final DistributionSet savedSet = testdataFactory.createDistributionSet(""); final DistributionSet savedSet = testdataFactory.createDistributionSet("");
final Long actionId = assignDistributionSet(savedSet, toAssign).getActions().get(0); final Long actionId = assignDistributionSet(savedSet, toAssign).getActionIds().get(0);
mvc.perform(get("/{tenant}/controller/v1/4712/deploymentBase/" + actionId, tenantAware.getCurrentTenant())) mvc.perform(get("/{tenant}/controller/v1/4712/deploymentBase/" + actionId, tenantAware.getCurrentTenant()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
mvc.perform(get("/{tenant}/controller/v1/4712/deploymentBase/" + actionId, tenantAware.getCurrentTenant()) mvc.perform(get("/{tenant}/controller/v1/4712/deploymentBase/" + actionId, tenantAware.getCurrentTenant())
@@ -639,9 +639,9 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest {
final List<Target> toAssign = new ArrayList<>(); final List<Target> toAssign = new ArrayList<>();
toAssign.add(savedTarget1); toAssign.add(savedTarget1);
final Long actionId1 = assignDistributionSet(ds1.getId(), "4712").getActions().get(0); final Long actionId1 = assignDistributionSet(ds1.getId(), "4712").getActionIds().get(0);
final Long actionId2 = assignDistributionSet(ds2.getId(), "4712").getActions().get(0); final Long actionId2 = assignDistributionSet(ds2.getId(), "4712").getActionIds().get(0);
final Long actionId3 = assignDistributionSet(ds3.getId(), "4712").getActions().get(0); final Long actionId3 = assignDistributionSet(ds3.getId(), "4712").getActionIds().get(0);
Target myT = targetManagement.getByControllerID("4712").get(); Target myT = targetManagement.getByControllerID("4712").get();
assertThat(myT.getUpdateStatus()).isEqualTo(TargetUpdateStatus.PENDING); assertThat(myT.getUpdateStatus()).isEqualTo(TargetUpdateStatus.PENDING);

View File

@@ -29,7 +29,9 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import java.util.Collections; import java.util.Collections;
import java.util.Map; import java.util.Map;
import java.util.UUID;
import org.apache.commons.lang3.RandomStringUtils;
import org.eclipse.hawkbit.ddi.rest.api.DdiRestConstants; import org.eclipse.hawkbit.ddi.rest.api.DdiRestConstants;
import org.eclipse.hawkbit.im.authentication.SpPermission; import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent; import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
@@ -258,9 +260,8 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
etagWithFirstUpdate)).andDo(MockMvcResultPrinter.print()).andExpect(status().isNotModified()); etagWithFirstUpdate)).andDo(MockMvcResultPrinter.print()).andExpect(status().isNotModified());
// now lets finish the update // now lets finish the update
sendDeploymentActionFeedback(target, updateAction, sendDeploymentActionFeedback(target, updateAction, "closed", null).andDo(MockMvcResultPrinter.print())
JsonBuilder.deploymentActionFeedback(updateAction.getId().toString(), "closed")) .andExpect(status().isOk());
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
// we are again at the original state // we are again at the original state
mvc.perform(get("/{tenant}/controller/v1/4711", tenantAware.getCurrentTenant()).header("If-None-Match", etag)) mvc.perform(get("/{tenant}/controller/v1/4711", tenantAware.getCurrentTenant()).header("If-None-Match", etag))
@@ -364,7 +365,6 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1), @Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
@Expect(type = ActionCreatedEvent.class, count = 1), @Expect(type = ActionUpdatedEvent.class, count = 1), @Expect(type = ActionCreatedEvent.class, count = 1), @Expect(type = ActionUpdatedEvent.class, count = 1),
@Expect(type = TargetUpdatedEvent.class, count = 2), @Expect(type = TargetUpdatedEvent.class, count = 2),
@Expect(type = TargetAttributesRequestedEvent.class, count = 1),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3) }) @Expect(type = SoftwareModuleCreatedEvent.class, count = 3) })
public void tryToFinishAnUpdateProcessAfterItHasBeenFinished() throws Exception { public void tryToFinishAnUpdateProcessAfterItHasBeenFinished() throws Exception {
final DistributionSet ds = testdataFactory.createDistributionSet(""); final DistributionSet ds = testdataFactory.createDistributionSet("");
@@ -373,17 +373,14 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
.next(); .next();
final Action savedAction = deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId()) final Action savedAction = deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId())
.getContent().get(0); .getContent().get(0);
sendDeploymentActionFeedback(savedTarget, savedAction, sendDeploymentActionFeedback(savedTarget, savedAction, "proceeding", null).andDo(MockMvcResultPrinter.print())
JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "proceeding")) .andExpect(status().isOk());
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
sendDeploymentActionFeedback(savedTarget, savedAction, sendDeploymentActionFeedback(savedTarget, savedAction, "closed", "failure").andDo(MockMvcResultPrinter.print())
JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "closed", "failure")) .andExpect(status().isOk());
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
sendDeploymentActionFeedback(savedTarget, savedAction, sendDeploymentActionFeedback(savedTarget, savedAction, "closed", "success").andDo(MockMvcResultPrinter.print())
JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "closed", "success")) .andExpect(status().isGone());
.andDo(MockMvcResultPrinter.print()).andExpect(status().isGone());
} }
@Test @Test
@@ -419,9 +416,7 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
target = assignDistributionSet(ds.getId(), target.getControllerId()).getAssignedEntity().iterator().next(); target = assignDistributionSet(ds.getId(), target.getControllerId()).getAssignedEntity().iterator().next();
final Action action = deploymentManagement.findActiveActionsByTarget(PAGE, target.getControllerId()) final Action action = deploymentManagement.findActiveActionsByTarget(PAGE, target.getControllerId())
.getContent().get(0); .getContent().get(0);
sendDeploymentActionFeedback(target, action, sendDeploymentActionFeedback(target, action, "closed", "failure").andExpect(status().isOk());
JsonBuilder.deploymentActionFeedback(action.getId().toString(), "closed", "failure", ""))
.andExpect(status().isOk());
assertThatAttributesUpdateIsNotRequested(target.getControllerId()); assertThatAttributesUpdateIsNotRequested(target.getControllerId());
} }
@@ -431,8 +426,7 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
target = assignDistributionSet(ds.getId(), target.getControllerId()).getAssignedEntity().iterator().next(); target = assignDistributionSet(ds.getId(), target.getControllerId()).getAssignedEntity().iterator().next();
final Action action = deploymentManagement.findActiveActionsByTarget(PAGE, target.getControllerId()) final Action action = deploymentManagement.findActiveActionsByTarget(PAGE, target.getControllerId())
.getContent().get(0); .getContent().get(0);
sendDeploymentActionFeedback(target, action, sendDeploymentActionFeedback(target, action, "closed", null).andExpect(status().isOk());
JsonBuilder.deploymentActionFeedback(action.getId().toString(), "closed")).andExpect(status().isOk());
assertThatAttributesUpdateIsRequested(target.getControllerId()); assertThatAttributesUpdateIsRequested(target.getControllerId());
} }
@@ -448,13 +442,26 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
.andExpect(jsonPath("$._links.configData").doesNotExist()); .andExpect(jsonPath("$._links.configData").doesNotExist());
} }
private ResultActions sendDeploymentActionFeedback(final Target target, final Action action, final String feedback) private ResultActions sendDeploymentActionFeedback(final Target target, final Action action, final String execution,
throws Exception { String finished, String message) throws Exception {
if (finished == null) {
finished = "none";
}
if (message == null) {
message = RandomStringUtils.randomAlphanumeric(1000);
}
final String feedback = JsonBuilder.deploymentActionFeedback(action.getId().toString(), execution, finished,
message);
return mvc.perform(post("/{tenant}/controller/v1/{controllerId}/deploymentBase/{actionId}/feedback", return mvc.perform(post("/{tenant}/controller/v1/{controllerId}/deploymentBase/{actionId}/feedback",
tenantAware.getCurrentTenant(), target.getControllerId(), action.getId()).content(feedback) tenantAware.getCurrentTenant(), target.getControllerId(), action.getId()).content(feedback)
.contentType(MediaType.APPLICATION_JSON)); .contentType(MediaType.APPLICATION_JSON));
} }
private ResultActions sendDeploymentActionFeedback(final Target target, final Action action, final String execution,
final String finished) throws Exception {
return sendDeploymentActionFeedback(target, action, execution, finished, null);
}
@Test @Test
@Description("Test to verify that only a specific count of messages are returned based on the input actionHistory for getControllerDeploymentActionFeedback endpoint.") @Description("Test to verify that only a specific count of messages are returned based on the input actionHistory for getControllerDeploymentActionFeedback endpoint.")
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1), @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@@ -472,20 +479,14 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
final Action savedAction = deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId()) final Action savedAction = deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId())
.getContent().get(0); .getContent().get(0);
sendDeploymentActionFeedback(savedTarget, savedAction, sendDeploymentActionFeedback(savedTarget, savedAction, "scheduled", null, TARGET_SCHEDULED_INSTALLATION_MSG)
JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "scheduled", .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
TARGET_SCHEDULED_INSTALLATION_MSG)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
sendDeploymentActionFeedback(savedTarget, savedAction, sendDeploymentActionFeedback(savedTarget, savedAction, "proceeding", null, TARGET_PROCEEDING_INSTALLATION_MSG)
JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "proceeding", .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
TARGET_PROCEEDING_INSTALLATION_MSG)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
sendDeploymentActionFeedback(savedTarget, savedAction, sendDeploymentActionFeedback(savedTarget, savedAction, "closed", "success", TARGET_COMPLETED_INSTALLATION_MSG)
JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "closed", "success", .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
TARGET_COMPLETED_INSTALLATION_MSG)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
mvc.perform(get("/{tenant}/controller/v1/911/deploymentBase/" + savedAction.getId() + "?actionHistory=3", mvc.perform(get("/{tenant}/controller/v1/911/deploymentBase/" + savedAction.getId() + "?actionHistory=3",
tenantAware.getCurrentTenant()).contentType(MediaType.APPLICATION_JSON) tenantAware.getCurrentTenant()).contentType(MediaType.APPLICATION_JSON)
@@ -514,20 +515,14 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
final Action savedAction = deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId()) final Action savedAction = deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId())
.getContent().get(0); .getContent().get(0);
sendDeploymentActionFeedback(savedTarget, savedAction, sendDeploymentActionFeedback(savedTarget, savedAction, "scheduled", null, TARGET_SCHEDULED_INSTALLATION_MSG)
JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "scheduled", .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
TARGET_SCHEDULED_INSTALLATION_MSG)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
sendDeploymentActionFeedback(savedTarget, savedAction, sendDeploymentActionFeedback(savedTarget, savedAction, "proceeding", null, TARGET_PROCEEDING_INSTALLATION_MSG)
JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "proceeding", .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
TARGET_PROCEEDING_INSTALLATION_MSG)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
sendDeploymentActionFeedback(savedTarget, savedAction, sendDeploymentActionFeedback(savedTarget, savedAction, "closed", "success", TARGET_COMPLETED_INSTALLATION_MSG)
JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "closed", "success", .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
TARGET_COMPLETED_INSTALLATION_MSG)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
mvc.perform(get("/{tenant}/controller/v1/911/deploymentBase/" + savedAction.getId() + "?actionHistory=-2", mvc.perform(get("/{tenant}/controller/v1/911/deploymentBase/" + savedAction.getId() + "?actionHistory=-2",
tenantAware.getCurrentTenant()).contentType(MediaType.APPLICATION_JSON) tenantAware.getCurrentTenant()).contentType(MediaType.APPLICATION_JSON)
@@ -552,20 +547,14 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
final Action savedAction = deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId()) final Action savedAction = deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId())
.getContent().get(0); .getContent().get(0);
sendDeploymentActionFeedback(savedTarget, savedAction, sendDeploymentActionFeedback(savedTarget, savedAction, "scheduled", null, TARGET_SCHEDULED_INSTALLATION_MSG)
JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "scheduled", .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
TARGET_SCHEDULED_INSTALLATION_MSG)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
sendDeploymentActionFeedback(savedTarget, savedAction, sendDeploymentActionFeedback(savedTarget, savedAction, "proceeding", null, TARGET_PROCEEDING_INSTALLATION_MSG)
JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "proceeding", .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
TARGET_PROCEEDING_INSTALLATION_MSG)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
sendDeploymentActionFeedback(savedTarget, savedAction, sendDeploymentActionFeedback(savedTarget, savedAction, "closed", "success", TARGET_COMPLETED_INSTALLATION_MSG)
JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "closed", "success", .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
TARGET_COMPLETED_INSTALLATION_MSG)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
mvc.perform(get("/{tenant}/controller/v1/911/deploymentBase/" + savedAction.getId() + "?actionHistory=-1", mvc.perform(get("/{tenant}/controller/v1/911/deploymentBase/" + savedAction.getId() + "?actionHistory=-1",
tenantAware.getCurrentTenant()).contentType(MediaType.APPLICATION_JSON) tenantAware.getCurrentTenant()).contentType(MediaType.APPLICATION_JSON)
@@ -665,4 +654,34 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
.andExpect(jsonPath("$.deployment.update", equalTo("forced"))) .andExpect(jsonPath("$.deployment.update", equalTo("forced")))
.andExpect(jsonPath("$.deployment.maintenanceWindow", equalTo("available"))); .andExpect(jsonPath("$.deployment.maintenanceWindow", equalTo("available")));
} }
@Test
@Description("Assign multiple DS in multi-assignment mode. The earliest active Action is exposed to the controller.")
public void earliestActionIsExposedToControllerInMultiAssignMode() throws Exception {
setMultiAssignmentsEnabled();
final Target target = testdataFactory.createTarget();
final DistributionSet ds1 = testdataFactory.createDistributionSet(UUID.randomUUID().toString());
final DistributionSet ds2 = testdataFactory.createDistributionSet(UUID.randomUUID().toString());
final Action action1 = assignDistributionSet(ds1, target).getActions().get(0);
final Action action2 = assignDistributionSet(ds2, target).getActions().get(0);
assertDeploymentActionIsExposedToTarget(target.getControllerId(), action1.getId());
sendDeploymentActionFeedback(target, action1, "closed", "success");
assertDeploymentActionIsExposedToTarget(target.getControllerId(), action2.getId());
}
public void assertDeploymentActionIsExposedToTarget(final String controllerId, final long expectedActionId)
throws Exception {
final String expectedDeploymentBaseLink = String.format("/%s/controller/v1/%s/deploymentBase/%d",
tenantAware.getCurrentTenant(), controllerId, expectedActionId);
mvc.perform(get("/{tenant}/controller/v1/{controllerId}", tenantAware.getCurrentTenant(), controllerId)
.accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(jsonPath("$._links.deploymentBase.href", containsString(expectedDeploymentBaseLink)));
}
private void setMultiAssignmentsEnabled() {
tenantConfigurationManagement.addOrUpdateConfiguration(TenantConfigurationKey.MULTI_ASSIGNMENTS_ENABLED, true);
}
} }

View File

@@ -1239,7 +1239,7 @@ public class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest
final DistributionSet set = testdataFactory.createDistributionSet(); final DistributionSet set = testdataFactory.createDistributionSet();
final Long actionId = deploymentManagement final Long actionId = deploymentManagement
.assignDistributionSet(set.getId(), ActionType.SOFT, 0, Arrays.asList(target.getControllerId())) .assignDistributionSet(set.getId(), ActionType.SOFT, 0, Arrays.asList(target.getControllerId()))
.getActions().get(0); .getActionIds().get(0);
assertThat(deploymentManagement.findAction(actionId).get().getActionType()).isEqualTo(ActionType.SOFT); assertThat(deploymentManagement.findAction(actionId).get().getActionType()).isEqualTo(ActionType.SOFT);
final String body = new JSONObject().put("forceType", "forced").toString(); final String body = new JSONObject().put("forceType", "forced").toString();

View File

@@ -0,0 +1,73 @@
/**
* Copyright (c) 2019 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.mgmt.rest.resource;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.rest.util.MockMvcResultPrinter;
import org.json.JSONObject;
import org.junit.Test;
import org.springframework.http.MediaType;
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.qameta.allure.Story;
/**
* Spring MVC Tests against the MgmtTenantManagementResource.
*
*/
@Feature("Component Tests - Management API")
@Story("Tenant Management Resource")
public class MgmtTenantManagementResourceTest extends AbstractManagementApiIntegrationTest {
private static final String KEY_MULTI_ASSIGNMENTS = "multi.assignments.enabled";
private static final String KEY_AUTO_CLOSE = "repository.actions.autoclose.enabled";
@Test
@Description("The 'multi.assignments.enabled' property must not be changed to false.")
public 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(MgmtRestConstants.SYSTEM_V1_REQUEST_MAPPING + "/configs/{keyName}", KEY_MULTI_ASSIGNMENTS)
.content(bodyActivate).contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
mvc.perform(put(MgmtRestConstants.SYSTEM_V1_REQUEST_MAPPING + "/configs/{keyName}", KEY_MULTI_ASSIGNMENTS)
.content(bodyDeactivate).contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isForbidden());
}
@Test
@Description("The 'repository.actions.autoclose.enabled' property must not be modified if Multi-Assignments is enabled.")
public 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(MgmtRestConstants.SYSTEM_V1_REQUEST_MAPPING + "/configs/{keyName}", KEY_MULTI_ASSIGNMENTS)
.content(bodyActivate).contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
// try to enable Auto-Close
mvc.perform(put(MgmtRestConstants.SYSTEM_V1_REQUEST_MAPPING + "/configs/{keyName}", KEY_AUTO_CLOSE)
.content(bodyActivate).contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isForbidden());
// try to disable Auto-Close
mvc.perform(put(MgmtRestConstants.SYSTEM_V1_REQUEST_MAPPING + "/configs/{keyName}", KEY_AUTO_CLOSE)
.content(bodyDeactivate).contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isForbidden());
}
}

View File

@@ -78,6 +78,7 @@ public class ResponseExceptionHandler {
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_TARGET_ATTRIBUTES_INVALID, HttpStatus.BAD_REQUEST); ERROR_TO_HTTP_STATUS.put(SpServerError.SP_TARGET_ATTRIBUTES_INVALID, HttpStatus.BAD_REQUEST);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_AUTO_ASSIGN_ACTION_TYPE_INVALID, HttpStatus.BAD_REQUEST); ERROR_TO_HTTP_STATUS.put(SpServerError.SP_AUTO_ASSIGN_ACTION_TYPE_INVALID, HttpStatus.BAD_REQUEST);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_AUTO_ASSIGN_DISTRIBUTION_SET_INVALID, HttpStatus.BAD_REQUEST); ERROR_TO_HTTP_STATUS.put(SpServerError.SP_AUTO_ASSIGN_DISTRIBUTION_SET_INVALID, HttpStatus.BAD_REQUEST);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_CONFIGURATION_VALUE_CHANGE_NOT_ALLOWED, HttpStatus.FORBIDDEN);
} }
private static HttpStatus getStatusOrDefault(final SpServerError error) { private static HttpStatus getStatusOrDefault(final SpServerError error) {

View File

@@ -138,7 +138,7 @@ public class RootControllerDocumentationTest extends AbstractApiRestDocumentatio
final Target target = targetManagement.create(entityFactory.target().create().controllerId(CONTROLLER_ID)); final Target target = targetManagement.create(entityFactory.target().create().controllerId(CONTROLLER_ID));
final Long actionId = deploymentManagement final Long actionId = deploymentManagement
.assignDistributionSet(set.getId(), Arrays.asList(target.getTargetWithActionType())).getActions() .assignDistributionSet(set.getId(), Arrays.asList(target.getTargetWithActionType())).getActionIds()
.get(0); .get(0);
final Action cancelAction = deploymentManagement.cancelAction(actionId); final Action cancelAction = deploymentManagement.cancelAction(actionId);
@@ -171,7 +171,7 @@ public class RootControllerDocumentationTest extends AbstractApiRestDocumentatio
final Target target = targetManagement.create(entityFactory.target().create().controllerId(CONTROLLER_ID)); final Target target = targetManagement.create(entityFactory.target().create().controllerId(CONTROLLER_ID));
final Long actionId = deploymentManagement final Long actionId = deploymentManagement
.assignDistributionSet(set.getId(), Arrays.asList(target.getTargetWithActionType())).getActions() .assignDistributionSet(set.getId(), Arrays.asList(target.getTargetWithActionType())).getActionIds()
.get(0); .get(0);
final Action cancelAction = deploymentManagement.cancelAction(actionId); final Action cancelAction = deploymentManagement.cancelAction(actionId);
@@ -265,7 +265,7 @@ public class RootControllerDocumentationTest extends AbstractApiRestDocumentatio
final Target target = targetManagement.create(entityFactory.target().create().controllerId(CONTROLLER_ID)); final Target target = targetManagement.create(entityFactory.target().create().controllerId(CONTROLLER_ID));
final Long actionId = assignDistributionSetWithMaintenanceWindow(set.getId(), target.getControllerId(), final Long actionId = assignDistributionSetWithMaintenanceWindow(set.getId(), target.getControllerId(),
getTestSchedule(-5), getTestDuration(10), getTestTimeZone()).getActions().get(0); getTestSchedule(-5), getTestDuration(10), getTestTimeZone()).getActionIds().get(0);
controllerManagement.addInformationalActionStatus( controllerManagement.addInformationalActionStatus(
entityFactory.actionStatus().create(actionId).message("Started download").status(Status.DOWNLOAD)); entityFactory.actionStatus().create(actionId).message("Started download").status(Status.DOWNLOAD));
@@ -347,7 +347,7 @@ public class RootControllerDocumentationTest extends AbstractApiRestDocumentatio
final Target target = targetManagement.create(entityFactory.target().create().controllerId(CONTROLLER_ID)); final Target target = targetManagement.create(entityFactory.target().create().controllerId(CONTROLLER_ID));
final Long actionId = assignDistributionSetWithMaintenanceWindow(set.getId(), target.getControllerId(), final Long actionId = assignDistributionSetWithMaintenanceWindow(set.getId(), target.getControllerId(),
getTestSchedule(2), getTestDuration(1), getTestTimeZone()).getActions().get(0); getTestSchedule(2), getTestDuration(1), getTestTimeZone()).getActionIds().get(0);
mockMvc.perform(get( mockMvc.perform(get(
DdiRestConstants.BASE_V1_REQUEST_MAPPING + "/{controllerId}/" + DdiRestConstants.DEPLOYMENT_BASE_ACTION DdiRestConstants.BASE_V1_REQUEST_MAPPING + "/{controllerId}/" + DdiRestConstants.DEPLOYMENT_BASE_ACTION
@@ -388,7 +388,7 @@ public class RootControllerDocumentationTest extends AbstractApiRestDocumentatio
final Target target = targetManagement.create(entityFactory.target().create().controllerId(CONTROLLER_ID)); final Target target = targetManagement.create(entityFactory.target().create().controllerId(CONTROLLER_ID));
final Long actionId = deploymentManagement final Long actionId = deploymentManagement
.assignDistributionSet(set.getId(), Arrays.asList(target.getTargetWithActionType())).getActions() .assignDistributionSet(set.getId(), Arrays.asList(target.getTargetWithActionType())).getActionIds()
.get(0); .get(0);
mockMvc.perform(post(DdiRestConstants.BASE_V1_REQUEST_MAPPING + "/{controllerId}/" mockMvc.perform(post(DdiRestConstants.BASE_V1_REQUEST_MAPPING + "/{controllerId}/"

View File

@@ -397,7 +397,7 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
final DistributionSet set = testdataFactory.createDistributionSet(); final DistributionSet set = testdataFactory.createDistributionSet();
final Long actionId = deploymentManagement final Long actionId = deploymentManagement
.assignDistributionSet(set.getId(), ActionType.SOFT, 0, Arrays.asList(target.getControllerId())) .assignDistributionSet(set.getId(), ActionType.SOFT, 0, Arrays.asList(target.getControllerId()))
.getActions().get(0); .getActionIds().get(0);
assertThat(deploymentManagement.findAction(actionId).get().getActionType()).isEqualTo(ActionType.SOFT); assertThat(deploymentManagement.findAction(actionId).get().getActionType()).isEqualTo(ActionType.SOFT);
final Map<String, Object> body = new HashMap<>(); final Map<String, Object> body = new HashMap<>();

View File

@@ -88,6 +88,8 @@ public class TenantResourceDocumentationTest extends AbstractApiRestDocumentatio
"the list of action status that should be taken into account for the cleanup."); "the list of action status that should be taken into account for the cleanup.");
CONFIG_ITEM_DESCRIPTIONS.put(TenantConfigurationKey.ACTION_CLEANUP_ACTION_EXPIRY, CONFIG_ITEM_DESCRIPTIONS.put(TenantConfigurationKey.ACTION_CLEANUP_ACTION_EXPIRY,
"the expiry time in milliseconds that needs to elapse before an action may be cleaned up."); "the expiry time in milliseconds that needs to elapse before an action may be cleaned up.");
CONFIG_ITEM_DESCRIPTIONS.put(TenantConfigurationKey.MULTI_ASSIGNMENTS_ENABLED,
"if multiple distribution sets can be assigned to the same targets.");
} }
@Autowired @Autowired

View File

@@ -209,6 +209,8 @@ public class UiProperties implements Serializable {
*/ */
private String rolloutView = ""; private String rolloutView = "";
private String provisioningStateMachine = "";
public String getDeploymentView() { public String getDeploymentView() {
return deploymentView; return deploymentView;
} }
@@ -249,6 +251,10 @@ public class UiProperties implements Serializable {
return maintenanceWindowView; return maintenanceWindowView;
} }
public String getProvisioningStateMachine() {
return provisioningStateMachine;
}
public void setDeploymentView(final String deploymentView) { public void setDeploymentView(final String deploymentView) {
this.deploymentView = deploymentView; this.deploymentView = deploymentView;
} }
@@ -289,6 +295,10 @@ public class UiProperties implements Serializable {
this.maintenanceWindowView = maintenanceWindowView; this.maintenanceWindowView = maintenanceWindowView;
} }
public void setProvisioningStateMachine(final String provisioningStateMachine) {
this.provisioningStateMachine = provisioningStateMachine;
}
} }
private final Documentation documentation = new Documentation(); private final Documentation documentation = new Documentation();

View File

@@ -12,7 +12,9 @@ import java.util.Optional;
import org.eclipse.hawkbit.repository.DistributionSetManagement; import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.DistributionSetTagManagement; import org.eclipse.hawkbit.repository.DistributionSetTagManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.eclipse.hawkbit.ui.SpPermissionChecker; import org.eclipse.hawkbit.ui.SpPermissionChecker;
import org.eclipse.hawkbit.ui.common.tagdetails.DistributionTagToken; import org.eclipse.hawkbit.ui.common.tagdetails.DistributionTagToken;
import org.eclipse.hawkbit.ui.components.SPUIComponentProvider; import org.eclipse.hawkbit.ui.components.SPUIComponentProvider;
@@ -56,6 +58,8 @@ public abstract class AbstractDistributionSetDetails
private final SoftwareModuleDetailsTable softwareModuleDetailsTable; private final SoftwareModuleDetailsTable softwareModuleDetailsTable;
private final transient TenantConfigurationManagement tenantConfigurationManagement;
private VerticalLayout softwareModuleTab; private VerticalLayout softwareModuleTab;
protected AbstractDistributionSetDetails(final VaadinMessageSource i18n, final UIEventBus eventBus, protected AbstractDistributionSetDetails(final VaadinMessageSource i18n, final UIEventBus eventBus,
@@ -64,7 +68,8 @@ public abstract class AbstractDistributionSetDetails
final DistributionSetManagement distributionSetManagement, final DistributionSetManagement distributionSetManagement,
final DsMetadataPopupLayout dsMetadataPopupLayout, final UINotification uiNotification, final DsMetadataPopupLayout dsMetadataPopupLayout, final UINotification uiNotification,
final DistributionSetTagManagement distributionSetTagManagement, final DistributionSetTagManagement distributionSetTagManagement,
final SoftwareModuleDetailsTable softwareModuleDetailsTable) { final SoftwareModuleDetailsTable softwareModuleDetailsTable,
final TenantConfigurationManagement tenantConfigurationManagement) {
super(i18n, eventBus, permissionChecker, managementUIState); super(i18n, eventBus, permissionChecker, managementUIState);
this.distributionAddUpdateWindowLayout = distributionAddUpdateWindowLayout; this.distributionAddUpdateWindowLayout = distributionAddUpdateWindowLayout;
this.uiNotification = uiNotification; this.uiNotification = uiNotification;
@@ -73,6 +78,7 @@ public abstract class AbstractDistributionSetDetails
this.distributionTagToken = new DistributionTagToken(permissionChecker, i18n, uiNotification, eventBus, this.distributionTagToken = new DistributionTagToken(permissionChecker, i18n, uiNotification, eventBus,
managementUIState, distributionSetTagManagement, distributionSetManagement); managementUIState, distributionSetTagManagement, distributionSetManagement);
this.softwareModuleDetailsTable = softwareModuleDetailsTable; this.softwareModuleDetailsTable = softwareModuleDetailsTable;
this.tenantConfigurationManagement = tenantConfigurationManagement;
dsMetadataTable = new DistributionSetMetadataDetailsLayout(i18n, distributionSetManagement, dsMetadataTable = new DistributionSetMetadataDetailsLayout(i18n, distributionSetManagement,
dsMetadataPopupLayout); dsMetadataPopupLayout);
@@ -175,9 +181,19 @@ public abstract class AbstractDistributionSetDetails
typeLabel.setId(UIComponentIdProvider.DETAILS_TYPE_LABEL_ID); typeLabel.setId(UIComponentIdProvider.DETAILS_TYPE_LABEL_ID);
detailsTabLayout.addComponent(typeLabel); detailsTabLayout.addComponent(typeLabel);
detailsTabLayout.addComponent( final Label requiredMigrationStepLabel = SPUIComponentProvider.createNameValueLabel(
SPUIComponentProvider.createNameValueLabel(getI18n().getMessage("checkbox.dist.migration.required"), getI18n().getMessage("checkbox.dist.migration.required"),
getMigrationRequiredValue(isMigrationRequired))); getMigrationRequiredValue(isMigrationRequired));
requiredMigrationStepLabel.setId(UIComponentIdProvider.DETAILS_REQUIRED_MIGRATION_STEP_LABEL_ID);
if (!isMultiAssignmentEnabled()) {
detailsTabLayout.addComponent(requiredMigrationStepLabel);
}
}
private boolean isMultiAssignmentEnabled() {
return tenantConfigurationManagement
.getConfigurationValue(TenantConfigurationKey.MULTI_ASSIGNMENTS_ENABLED, Boolean.class).getValue();
} }
private String getMigrationRequiredValue(final Boolean isMigrationRequired) { private String getMigrationRequiredValue(final Boolean isMigrationRequired) {

View File

@@ -21,6 +21,7 @@ import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
import org.eclipse.hawkbit.repository.SoftwareModuleTypeManagement; import org.eclipse.hawkbit.repository.SoftwareModuleTypeManagement;
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.TenantConfigurationManagement;
import org.eclipse.hawkbit.ui.AbstractHawkbitUI; import org.eclipse.hawkbit.ui.AbstractHawkbitUI;
import org.eclipse.hawkbit.ui.SpPermissionChecker; import org.eclipse.hawkbit.ui.SpPermissionChecker;
import org.eclipse.hawkbit.ui.artifacts.event.SoftwareModuleEvent; import org.eclipse.hawkbit.ui.artifacts.event.SoftwareModuleEvent;
@@ -102,7 +103,8 @@ public class DistributionsView extends AbstractNotificationView implements Brows
final DistributionsViewClientCriterion distributionsViewClientCriterion, final DistributionsViewClientCriterion distributionsViewClientCriterion,
final ArtifactUploadState artifactUploadState, final SystemManagement systemManagement, final ArtifactUploadState artifactUploadState, final SystemManagement systemManagement,
final ArtifactManagement artifactManagement, final NotificationUnreadButton notificationUnreadButton, final ArtifactManagement artifactManagement, final NotificationUnreadButton notificationUnreadButton,
final DistributionsViewMenuItem distributionsViewMenuItem) { final DistributionsViewMenuItem distributionsViewMenuItem,
final TenantConfigurationManagement configManagement) {
super(eventBus, notificationUnreadButton); super(eventBus, notificationUnreadButton);
this.permChecker = permChecker; this.permChecker = permChecker;
this.i18n = i18n; this.i18n = i18n;
@@ -117,7 +119,7 @@ public class DistributionsView extends AbstractNotificationView implements Brows
this.distributionTableLayout = new DistributionSetTableLayout(i18n, eventBus, permChecker, manageDistUIState, this.distributionTableLayout = new DistributionSetTableLayout(i18n, eventBus, permChecker, manageDistUIState,
softwareModuleManagement, distributionSetManagement, distributionSetTypeManagement, targetManagement, softwareModuleManagement, distributionSetManagement, distributionSetTypeManagement, targetManagement,
entityFactory, uiNotification, distributionSetTagManagement, distributionsViewClientCriterion, entityFactory, uiNotification, distributionSetTagManagement, distributionsViewClientCriterion,
systemManagement); systemManagement, configManagement);
this.softwareModuleTableLayout = new SwModuleTableLayout(i18n, uiNotification, eventBus, this.softwareModuleTableLayout = new SwModuleTableLayout(i18n, uiNotification, eventBus,
softwareModuleManagement, softwareModuleTypeManagement, entityFactory, manageDistUIState, permChecker, softwareModuleManagement, softwareModuleTypeManagement, entityFactory, manageDistUIState, permChecker,
distributionsViewClientCriterion, artifactUploadState, artifactManagement); distributionsViewClientCriterion, artifactUploadState, artifactManagement);

View File

@@ -13,6 +13,7 @@ import java.util.Map;
import org.eclipse.hawkbit.repository.DistributionSetManagement; import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.DistributionSetTagManagement; import org.eclipse.hawkbit.repository.DistributionSetTagManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.ui.SpPermissionChecker; import org.eclipse.hawkbit.ui.SpPermissionChecker;
import org.eclipse.hawkbit.ui.artifacts.event.SoftwareModuleEvent; import org.eclipse.hawkbit.ui.artifacts.event.SoftwareModuleEvent;
@@ -58,11 +59,12 @@ public class DistributionSetDetails extends AbstractDistributionSetDetails {
final DistributionAddUpdateWindowLayout distributionAddUpdateWindowLayout, final DistributionAddUpdateWindowLayout distributionAddUpdateWindowLayout,
final DistributionSetManagement distributionSetManagement, final UINotification uiNotification, final DistributionSetManagement distributionSetManagement, final UINotification uiNotification,
final DistributionSetTagManagement distributionSetTagManagement, final DistributionSetTagManagement distributionSetTagManagement,
final DsMetadataPopupLayout dsMetadataPopupLayout) { final DsMetadataPopupLayout dsMetadataPopupLayout, final TenantConfigurationManagement configManagement) {
super(i18n, eventBus, permissionChecker, managementUIState, distributionAddUpdateWindowLayout, super(i18n, eventBus, permissionChecker, managementUIState, distributionAddUpdateWindowLayout,
distributionSetManagement, dsMetadataPopupLayout, uiNotification, distributionSetTagManagement, distributionSetManagement, dsMetadataPopupLayout, uiNotification, distributionSetTagManagement,
createSoftwareModuleDetailsTable(i18n, permissionChecker, distributionSetManagement, eventBus, createSoftwareModuleDetailsTable(i18n, permissionChecker, distributionSetManagement, eventBus,
manageDistUIState, uiNotification)); manageDistUIState, uiNotification),
configManagement);
this.manageDistUIState = manageDistUIState; this.manageDistUIState = manageDistUIState;
tfqDetailsTable = new TargetFilterQueryDetailsTable(i18n); tfqDetailsTable = new TargetFilterQueryDetailsTable(i18n);

View File

@@ -15,6 +15,7 @@ import org.eclipse.hawkbit.repository.EntityFactory;
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.TenantConfigurationManagement;
import org.eclipse.hawkbit.ui.SpPermissionChecker; import org.eclipse.hawkbit.ui.SpPermissionChecker;
import org.eclipse.hawkbit.ui.common.table.AbstractTableLayout; import org.eclipse.hawkbit.ui.common.table.AbstractTableLayout;
import org.eclipse.hawkbit.ui.dd.criteria.DistributionsViewClientCriterion; import org.eclipse.hawkbit.ui.dd.criteria.DistributionsViewClientCriterion;
@@ -41,7 +42,7 @@ public class DistributionSetTableLayout extends AbstractTableLayout<Distribution
final EntityFactory entityFactory, final UINotification uiNotification, final EntityFactory entityFactory, final UINotification uiNotification,
final DistributionSetTagManagement distributionSetTagManagement, final DistributionSetTagManagement distributionSetTagManagement,
final DistributionsViewClientCriterion distributionsViewClientCriterion, final DistributionsViewClientCriterion distributionsViewClientCriterion,
final SystemManagement systemManagement) { final SystemManagement systemManagement, final TenantConfigurationManagement configManagement) {
this.distributionSetTable = new DistributionSetTable(eventBus, i18n, uiNotification, permissionChecker, this.distributionSetTable = new DistributionSetTable(eventBus, i18n, uiNotification, permissionChecker,
manageDistUIState, distributionSetManagement, softwareManagement, distributionsViewClientCriterion, manageDistUIState, distributionSetManagement, softwareManagement, distributionsViewClientCriterion,
@@ -49,7 +50,7 @@ public class DistributionSetTableLayout extends AbstractTableLayout<Distribution
final DistributionAddUpdateWindowLayout distributionAddUpdateWindowLayout = new DistributionAddUpdateWindowLayout( final DistributionAddUpdateWindowLayout distributionAddUpdateWindowLayout = new DistributionAddUpdateWindowLayout(
i18n, uiNotification, eventBus, distributionSetManagement, distributionSetTypeManagement, i18n, uiNotification, eventBus, distributionSetManagement, distributionSetTypeManagement,
systemManagement, entityFactory, distributionSetTable); systemManagement, entityFactory, distributionSetTable, configManagement);
final DsMetadataPopupLayout popupLayout = new DsMetadataPopupLayout(i18n, uiNotification, eventBus, final DsMetadataPopupLayout popupLayout = new DsMetadataPopupLayout(i18n, uiNotification, eventBus,
distributionSetManagement, entityFactory, permissionChecker); distributionSetManagement, entityFactory, permissionChecker);
@@ -60,7 +61,7 @@ public class DistributionSetTableLayout extends AbstractTableLayout<Distribution
distributionSetTable, distributionSetTable,
new DistributionSetDetails(i18n, eventBus, permissionChecker, manageDistUIState, null, new DistributionSetDetails(i18n, eventBus, permissionChecker, manageDistUIState, null,
distributionAddUpdateWindowLayout, distributionSetManagement, uiNotification, distributionAddUpdateWindowLayout, distributionSetManagement, uiNotification,
distributionSetTagManagement, popupLayout)); distributionSetTagManagement, popupLayout, configManagement));
} }
public DistributionSetTable getDistributionSetTable() { public DistributionSetTable getDistributionSetTable() {

View File

@@ -22,6 +22,7 @@ import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement; import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
import org.eclipse.hawkbit.repository.TargetManagement; import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.TargetTagManagement; import org.eclipse.hawkbit.repository.TargetTagManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.ui.AbstractHawkbitUI; import org.eclipse.hawkbit.ui.AbstractHawkbitUI;
import org.eclipse.hawkbit.ui.SpPermissionChecker; import org.eclipse.hawkbit.ui.SpPermissionChecker;
import org.eclipse.hawkbit.ui.UiProperties; import org.eclipse.hawkbit.ui.UiProperties;
@@ -127,6 +128,7 @@ public class DeploymentView extends AbstractNotificationView implements BrowserW
final TargetTagManagement targetTagManagement, final TargetTagManagement targetTagManagement,
final DistributionSetTagManagement distributionSetTagManagement, final DistributionSetTagManagement distributionSetTagManagement,
final TargetFilterQueryManagement targetFilterQueryManagement, final SystemManagement systemManagement, final TargetFilterQueryManagement targetFilterQueryManagement, final SystemManagement systemManagement,
final TenantConfigurationManagement configManagement,
final NotificationUnreadButton notificationUnreadButton, final NotificationUnreadButton notificationUnreadButton,
final DeploymentViewMenuItem deploymentViewMenuItem, @Qualifier("uiExecutor") final Executor uiExecutor) { final DeploymentViewMenuItem deploymentViewMenuItem, @Qualifier("uiExecutor") final Executor uiExecutor) {
super(eventBus, notificationUnreadButton); super(eventBus, notificationUnreadButton);
@@ -147,7 +149,7 @@ public class DeploymentView extends AbstractNotificationView implements BrowserW
targetFilterQueryManagement, targetTagManagement); targetFilterQueryManagement, targetTagManagement);
final TargetTable targetTable = new TargetTable(eventBus, i18n, uiNotification, targetManagement, final TargetTable targetTable = new TargetTable(eventBus, i18n, uiNotification, targetManagement,
managementUIState, permChecker, managementViewClientCriterion, distributionSetManagement, managementUIState, permChecker, managementViewClientCriterion, distributionSetManagement,
targetTagManagement, deploymentManagement, uiProperties); targetTagManagement, deploymentManagement, configManagement, uiProperties);
this.countMessageLabel = new CountMessageLabel(eventBus, targetManagement, i18n, managementUIState, this.countMessageLabel = new CountMessageLabel(eventBus, targetManagement, i18n, managementUIState,
targetTable); targetTable);
@@ -175,7 +177,7 @@ public class DeploymentView extends AbstractNotificationView implements BrowserW
this.distributionTableLayout = new DistributionTableLayout(i18n, eventBus, permChecker, managementUIState, this.distributionTableLayout = new DistributionTableLayout(i18n, eventBus, permChecker, managementUIState,
distributionSetManagement, distributionSetTypeManagement, managementViewClientCriterion, distributionSetManagement, distributionSetTypeManagement, managementViewClientCriterion,
entityFactory, uiNotification, distributionSetTagManagement, targetTagManagement, systemManagement, entityFactory, uiNotification, distributionSetTagManagement, targetTagManagement, systemManagement,
targetManagement, deploymentManagement, uiProperties); targetManagement, deploymentManagement, configManagement, uiProperties);
} else { } else {
this.distributionTagLayout = null; this.distributionTagLayout = null;
this.distributionTableLayout = null; this.distributionTableLayout = null;

View File

@@ -8,9 +8,7 @@
*/ */
package org.eclipse.hawkbit.ui.management; package org.eclipse.hawkbit.ui.management;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.function.Consumer; import java.util.function.Consumer;
@@ -20,17 +18,19 @@ import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.MaintenanceScheduleHelper; import org.eclipse.hawkbit.repository.MaintenanceScheduleHelper;
import org.eclipse.hawkbit.repository.exception.InvalidMaintenanceScheduleException; import org.eclipse.hawkbit.repository.exception.InvalidMaintenanceScheduleException;
import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.Action.ActionType;
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.Target;
import org.eclipse.hawkbit.repository.model.TargetWithActionType; import org.eclipse.hawkbit.repository.model.TargetWithActionType;
import org.eclipse.hawkbit.ui.UiProperties; import org.eclipse.hawkbit.ui.UiProperties;
import org.eclipse.hawkbit.ui.common.confirmwindow.layout.ConfirmationTab; import org.eclipse.hawkbit.ui.common.confirmwindow.layout.ConfirmationTab;
import org.eclipse.hawkbit.ui.common.entity.DistributionSetIdName;
import org.eclipse.hawkbit.ui.common.entity.TargetIdName; import org.eclipse.hawkbit.ui.common.entity.TargetIdName;
import org.eclipse.hawkbit.ui.components.SPUIComponentProvider; import org.eclipse.hawkbit.ui.components.SPUIComponentProvider;
import org.eclipse.hawkbit.ui.management.event.PinUnpinEvent; import org.eclipse.hawkbit.ui.management.event.PinUnpinEvent;
import org.eclipse.hawkbit.ui.management.event.SaveActionWindowEvent; import org.eclipse.hawkbit.ui.management.event.SaveActionWindowEvent;
import org.eclipse.hawkbit.ui.management.miscs.AbstractActionTypeOptionGroupLayout; import org.eclipse.hawkbit.ui.management.miscs.AbstractActionTypeOptionGroupLayout;
import org.eclipse.hawkbit.ui.management.miscs.AbstractActionTypeOptionGroupLayout.ActionTypeOption;
import org.eclipse.hawkbit.ui.management.miscs.ActionTypeOptionGroupAssignmentLayout; import org.eclipse.hawkbit.ui.management.miscs.ActionTypeOptionGroupAssignmentLayout;
import org.eclipse.hawkbit.ui.management.miscs.MaintenanceWindowLayout; import org.eclipse.hawkbit.ui.management.miscs.MaintenanceWindowLayout;
import org.eclipse.hawkbit.ui.management.state.ManagementUIState; import org.eclipse.hawkbit.ui.management.state.ManagementUIState;
@@ -41,7 +41,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.vaadin.spring.events.EventBus.UIEventBus; import org.vaadin.spring.events.EventBus.UIEventBus;
import com.google.common.collect.Maps;
import com.vaadin.data.Property; import com.vaadin.data.Property;
import com.vaadin.ui.CheckBox; import com.vaadin.ui.CheckBox;
import com.vaadin.ui.HorizontalLayout; import com.vaadin.ui.HorizontalLayout;
@@ -59,8 +58,12 @@ public final class TargetAssignmentOperations {
} }
/** /**
* Save all target(s)-distributionSet assignments * Save the given distribution set assignments
* *
* @param targets
* to assign the given distribution sets to
* @param distributionSets
* to assign to the given targets
* @param managementUIState * @param managementUIState
* the management UI state * the management UI state
* @param actionTypeOptionGroupLayout * @param actionTypeOptionGroupLayout
@@ -78,79 +81,64 @@ public final class TargetAssignmentOperations {
* @param eventSource * @param eventSource
* the source object for sending potential events * the source object for sending potential events
*/ */
public static void saveAllAssignments(final ManagementUIState managementUIState, public static void saveAllAssignments(final List<Target> targets, final List<DistributionSet> distributionSets,
final ManagementUIState managementUIState,
final ActionTypeOptionGroupAssignmentLayout actionTypeOptionGroupLayout, final ActionTypeOptionGroupAssignmentLayout actionTypeOptionGroupLayout,
final MaintenanceWindowLayout maintenanceWindowLayout, final DeploymentManagement deploymentManagement, final MaintenanceWindowLayout maintenanceWindowLayout, final DeploymentManagement deploymentManagement,
final UINotification notification, final UIEventBus eventBus, final VaadinMessageSource i18n, final UINotification notification, final UIEventBus eventBus, final VaadinMessageSource i18n,
final Object eventSource) { final Object eventSource) {
final Set<TargetIdName> itemIds = managementUIState.getAssignedList().keySet();
Long distId; final ActionType actionType = ((ActionTypeOption) actionTypeOptionGroupLayout.getActionTypeOptionGroup()
List<TargetIdName> targetIdSetList; .getValue()).getActionType();
List<TargetIdName> tempIdList;
final ActionType actionType = ((AbstractActionTypeOptionGroupLayout.ActionTypeOption) actionTypeOptionGroupLayout final long forcedTimeStamp = (((ActionTypeOption) actionTypeOptionGroupLayout.getActionTypeOptionGroup()
.getActionTypeOptionGroup().getValue()).getActionType(); .getValue()) == ActionTypeOption.AUTO_FORCED)
final long forcedTimeStamp = actionTypeOptionGroupLayout.getActionTypeOptionGroup()
.getValue() == AbstractActionTypeOptionGroupLayout.ActionTypeOption.AUTO_FORCED
? actionTypeOptionGroupLayout.getForcedTimeDateField().getValue().getTime() ? actionTypeOptionGroupLayout.getForcedTimeDateField().getValue().getTime()
: RepositoryModelConstants.NO_FORCE_TIME; : RepositoryModelConstants.NO_FORCE_TIME;
final Map<Long, List<TargetIdName>> saveAssignedList = Maps.newHashMapWithExpectedSize(itemIds.size());
for (final TargetIdName itemId : itemIds) {
final DistributionSetIdName distitem = managementUIState.getAssignedList().get(itemId);
distId = distitem.getId();
if (saveAssignedList.containsKey(distId)) {
targetIdSetList = saveAssignedList.get(distId);
} else {
targetIdSetList = new ArrayList<>();
}
targetIdSetList.add(itemId);
saveAssignedList.put(distId, targetIdSetList);
}
final String maintenanceSchedule = maintenanceWindowLayout.getMaintenanceSchedule(); final String maintenanceSchedule = maintenanceWindowLayout.getMaintenanceSchedule();
final String maintenanceDuration = maintenanceWindowLayout.getMaintenanceDuration(); final String maintenanceDuration = maintenanceWindowLayout.getMaintenanceDuration();
final String maintenanceTimeZone = maintenanceWindowLayout.getMaintenanceTimeZone(); final String maintenanceTimeZone = maintenanceWindowLayout.getMaintenanceTimeZone();
for (final Map.Entry<Long, List<TargetIdName>> mapEntry : saveAssignedList.entrySet()) { final Set<Long> dsIds = distributionSets.stream().map(DistributionSet::getId).collect(Collectors.toSet());
tempIdList = saveAssignedList.get(mapEntry.getKey()); final List<TargetWithActionType> trgActionType = targets.stream()
final DistributionSetAssignmentResult distributionSetAssignmentResult = deploymentManagement .map(t -> maintenanceWindowLayout.isEnabled()
.assignDistributionSet(mapEntry.getKey(), ? new TargetWithActionType(t.getControllerId(), actionType, forcedTimeStamp,
tempIdList.stream().map(t -> maintenanceWindowLayout.isEnabled() maintenanceSchedule, maintenanceDuration, maintenanceTimeZone)
? new TargetWithActionType(t.getControllerId(), actionType, forcedTimeStamp, : new TargetWithActionType(t.getControllerId(), actionType, forcedTimeStamp))
maintenanceSchedule, maintenanceDuration, maintenanceTimeZone) .collect(Collectors.toList());
: new TargetWithActionType(t.getControllerId(), actionType, forcedTimeStamp))
.collect(Collectors.toList())); final List<DistributionSetAssignmentResult> results = deploymentManagement.assignDistributionSets(dsIds,
if (distributionSetAssignmentResult.getAssigned() > 0) { trgActionType);
notification.displaySuccess(
i18n.getMessage("message.target.assignment", distributionSetAssignmentResult.getAssigned())); // use the last one for the notification box
} final DistributionSetAssignmentResult assignmentResult = results.get(results.size() - 1);
if (distributionSetAssignmentResult.getAlreadyAssigned() > 0) { if (assignmentResult.getAssigned() > 0) {
notification.displaySuccess(i18n.getMessage("message.target.alreadyAssigned", notification.displaySuccess(i18n.getMessage("message.target.assignment", assignmentResult.getAssigned()));
distributionSetAssignmentResult.getAlreadyAssigned()));
}
} }
refreshPinnedDetails(saveAssignedList, managementUIState, eventBus, eventSource); if (assignmentResult.getAlreadyAssigned() > 0) {
managementUIState.getAssignedList().clear(); notification.displaySuccess(
i18n.getMessage("message.target.alreadyAssigned", assignmentResult.getAlreadyAssigned()));
}
final Set<Long> targetIds = targets.stream().map(Target::getId).collect(Collectors.toSet());
refreshPinnedDetails(dsIds, targetIds, managementUIState, eventBus, eventSource);
notification.displaySuccess(i18n.getMessage("message.target.ds.assign.success")); notification.displaySuccess(i18n.getMessage("message.target.ds.assign.success"));
eventBus.publish(eventSource, SaveActionWindowEvent.SAVED_ASSIGNMENTS); eventBus.publish(eventSource, SaveActionWindowEvent.SAVED_ASSIGNMENTS);
} }
private static void refreshPinnedDetails(final Map<Long, List<TargetIdName>> saveAssignedList, private static void refreshPinnedDetails(final Set<Long> dsIds, final Set<Long> targetIds,
final ManagementUIState managementUIState, final UIEventBus eventBus, final Object eventSource) { final ManagementUIState managementUIState, final UIEventBus eventBus, final Object eventSource) {
final Optional<Long> pinnedDist = managementUIState.getTargetTableFilters().getPinnedDistId(); final Optional<Long> pinnedDist = managementUIState.getTargetTableFilters().getPinnedDistId();
final Optional<TargetIdName> pinnedTarget = managementUIState.getDistributionTableFilters().getPinnedTarget(); final Optional<TargetIdName> pinnedTarget = managementUIState.getDistributionTableFilters().getPinnedTarget();
if (pinnedDist.isPresent()) { if (pinnedDist.isPresent()) {
if (saveAssignedList.keySet().contains(pinnedDist.get())) { if (dsIds.contains(pinnedDist.get())) {
eventBus.publish(eventSource, PinUnpinEvent.PIN_DISTRIBUTION); eventBus.publish(eventSource, PinUnpinEvent.PIN_DISTRIBUTION);
} }
} else if (pinnedTarget.isPresent()) { } else if (pinnedTarget.isPresent() && targetIds.contains(pinnedTarget.get().getTargetId())) {
final Set<TargetIdName> assignedTargetIds = managementUIState.getAssignedList().keySet(); eventBus.publish(eventSource, PinUnpinEvent.PIN_TARGET);
if (assignedTargetIds.contains(pinnedTarget.get())) {
eventBus.publish(eventSource, PinUnpinEvent.PIN_TARGET);
}
} }
} }
@@ -187,7 +175,8 @@ public final class TargetAssignmentOperations {
* @param maintenanceWindowLayout * @param maintenanceWindowLayout
* the Maintenance Window Layout * the Maintenance Window Layout
* @param saveButtonToggle * @param saveButtonToggle
* The event listener to derimne if save button should be enabled or not * The event listener to derimne if save button should be enabled
* or not
* @param i18n * @param i18n
* the Vaadin Message Source for multi language * the Vaadin Message Source for multi language
* @param uiProperties * @param uiProperties

View File

@@ -15,10 +15,12 @@ import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.DistributionSetTypeManagement; import org.eclipse.hawkbit.repository.DistributionSetTypeManagement;
import org.eclipse.hawkbit.repository.EntityFactory; import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.SystemManagement; import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.TenantMetaData; import org.eclipse.hawkbit.repository.model.TenantMetaData;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.eclipse.hawkbit.ui.common.CommonDialogWindow; import org.eclipse.hawkbit.ui.common.CommonDialogWindow;
import org.eclipse.hawkbit.ui.common.CommonDialogWindow.SaveDialogCloseListener; import org.eclipse.hawkbit.ui.common.CommonDialogWindow.SaveDialogCloseListener;
import org.eclipse.hawkbit.ui.common.DistributionSetTypeBeanQuery; import org.eclipse.hawkbit.ui.common.DistributionSetTypeBeanQuery;
@@ -64,6 +66,7 @@ public class DistributionAddUpdateWindowLayout extends CustomComponent {
private final transient DistributionSetTypeManagement distributionSetTypeManagement; private final transient DistributionSetTypeManagement distributionSetTypeManagement;
private final transient SystemManagement systemManagement; private final transient SystemManagement systemManagement;
private final transient EntityFactory entityFactory; private final transient EntityFactory entityFactory;
private final transient TenantConfigurationManagement tenantConfigurationManagement;
private final DistributionSetTable distributionSetTable; private final DistributionSetTable distributionSetTable;
@@ -94,11 +97,14 @@ public class DistributionAddUpdateWindowLayout extends CustomComponent {
* EntityFactory * EntityFactory
* @param distributionSetTable * @param distributionSetTable
* DistributionSetTable * DistributionSetTable
* @param tenantConfigurationManagement
* TenantConfigurationManagement
*/ */
public DistributionAddUpdateWindowLayout(final VaadinMessageSource i18n, final UINotification notificationMessage, public DistributionAddUpdateWindowLayout(final VaadinMessageSource i18n, final UINotification notificationMessage,
final UIEventBus eventBus, final DistributionSetManagement distributionSetManagement, final UIEventBus eventBus, final DistributionSetManagement distributionSetManagement,
final DistributionSetTypeManagement distributionSetTypeManagement, final SystemManagement systemManagement, final DistributionSetTypeManagement distributionSetTypeManagement, final SystemManagement systemManagement,
final EntityFactory entityFactory, final DistributionSetTable distributionSetTable) { final EntityFactory entityFactory, final DistributionSetTable distributionSetTable,
final TenantConfigurationManagement tenantConfigurationManagement) {
this.i18n = i18n; this.i18n = i18n;
this.notificationMessage = notificationMessage; this.notificationMessage = notificationMessage;
this.eventBus = eventBus; this.eventBus = eventBus;
@@ -107,6 +113,7 @@ public class DistributionAddUpdateWindowLayout extends CustomComponent {
this.systemManagement = systemManagement; this.systemManagement = systemManagement;
this.entityFactory = entityFactory; this.entityFactory = entityFactory;
this.distributionSetTable = distributionSetTable; this.distributionSetTable = distributionSetTable;
this.tenantConfigurationManagement = tenantConfigurationManagement;
createRequiredComponents(); createRequiredComponents();
buildLayout(); buildLayout();
} }
@@ -134,8 +141,8 @@ public class DistributionAddUpdateWindowLayout extends CustomComponent {
final DistributionSet currentDS = distributionSetManagement.update(entityFactory.distributionSet() final DistributionSet currentDS = distributionSetManagement.update(entityFactory.distributionSet()
.update(editDistId).name(distNameTextField.getValue()).description(descTextArea.getValue()) .update(editDistId).name(distNameTextField.getValue()).description(descTextArea.getValue())
.version(distVersionTextField.getValue()).requiredMigrationStep(isMigStepReq)); .version(distVersionTextField.getValue()).requiredMigrationStep(isMigStepReq));
notificationMessage.displaySuccess(i18n.getMessage("message.new.dist.save.success", notificationMessage.displaySuccess(
new Object[] { currentDS.getName(), currentDS.getVersion() })); i18n.getMessage("message.new.dist.save.success", currentDS.getName(), currentDS.getVersion()));
// update table row+details layout // update table row+details layout
eventBus.publish(this, new DistributionTableEvent(BaseEntityEventType.UPDATED_ENTITY, currentDS)); eventBus.publish(this, new DistributionTableEvent(BaseEntityEventType.UPDATED_ENTITY, currentDS));
}); });
@@ -283,6 +290,12 @@ public class DistributionAddUpdateWindowLayout extends CustomComponent {
distsetTypeNameComboBox.setEnabled(true); distsetTypeNameComboBox.setEnabled(true);
descTextArea.clear(); descTextArea.clear();
reqMigStepCheckbox.clear(); reqMigStepCheckbox.clear();
reqMigStepCheckbox.setVisible(!isMultiAssignmentEnabled());
}
private boolean isMultiAssignmentEnabled() {
return tenantConfigurationManagement.getConfigurationValue(TenantConfigurationKey.MULTI_ASSIGNMENTS_ENABLED,
Boolean.class).getValue();
} }
private void populateValuesOfDistribution(final Long editDistId) { private void populateValuesOfDistribution(final Long editDistId) {
@@ -351,7 +364,8 @@ public class DistributionAddUpdateWindowLayout extends CustomComponent {
populateValuesOfDistribution(editDistId); populateValuesOfDistribution(editDistId);
} }
return new WindowBuilder(SPUIDefinitions.CREATE_UPDATE_WINDOW).caption(caption).content(this).layout(formLayout) return new WindowBuilder(SPUIDefinitions.CREATE_UPDATE_WINDOW).caption(caption).content(this)
.id(UIComponentIdProvider.CREATE_POPUP_ID).layout(formLayout)
.i18n(i18n).saveDialogCloseListener(saveDialogCloseListener).buildCommonDialogWindow(); .i18n(i18n).saveDialogCloseListener(saveDialogCloseListener).buildCommonDialogWindow();
} }

View File

@@ -10,6 +10,7 @@ package org.eclipse.hawkbit.ui.management.dstable;
import org.eclipse.hawkbit.repository.DistributionSetManagement; import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.DistributionSetTagManagement; import org.eclipse.hawkbit.repository.DistributionSetTagManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.ui.SpPermissionChecker; import org.eclipse.hawkbit.ui.SpPermissionChecker;
import org.eclipse.hawkbit.ui.common.detailslayout.AbstractDistributionSetDetails; import org.eclipse.hawkbit.ui.common.detailslayout.AbstractDistributionSetDetails;
import org.eclipse.hawkbit.ui.common.detailslayout.SoftwareModuleDetailsTable; import org.eclipse.hawkbit.ui.common.detailslayout.SoftwareModuleDetailsTable;
@@ -31,10 +32,12 @@ public class DistributionDetails extends AbstractDistributionSetDetails {
final DistributionSetManagement distributionSetManagement, final DistributionSetManagement distributionSetManagement,
final DsMetadataPopupLayout dsMetadataPopupLayout, final UINotification uiNotification, final DsMetadataPopupLayout dsMetadataPopupLayout, final UINotification uiNotification,
final DistributionSetTagManagement distributionSetTagManagement, final DistributionSetTagManagement distributionSetTagManagement,
final DistributionAddUpdateWindowLayout distributionAddUpdateWindowLayout) { final DistributionAddUpdateWindowLayout distributionAddUpdateWindowLayout,
final TenantConfigurationManagement tenantConfigurationManagement) {
super(i18n, eventBus, permissionChecker, managementUIState, distributionAddUpdateWindowLayout, super(i18n, eventBus, permissionChecker, managementUIState, distributionAddUpdateWindowLayout,
distributionSetManagement, dsMetadataPopupLayout, uiNotification, distributionSetTagManagement, distributionSetManagement, dsMetadataPopupLayout, uiNotification, distributionSetTagManagement,
createSoftwareModuleDetailsTable(i18n, permissionChecker, uiNotification)); createSoftwareModuleDetailsTable(i18n, permissionChecker, uiNotification),
tenantConfigurationManagement);
restoreState(); restoreState();
} }

View File

@@ -404,84 +404,61 @@ public class DistributionTable extends AbstractNamedVersionTable<DistributionSet
assignTargetToDs(getItem(distItemId), targetManagement.get(targetIdSet)); assignTargetToDs(getItem(distItemId), targetManagement.get(targetIdSet));
} }
private void assignTargetToDs(final Item item, final List<Target> targetDetailsList) { private void assignTargetToDs(final Item item, final List<Target> targets) {
if (item == null || item.getItemProperty("id") == null) { if (item == null || item.getItemProperty("id") == null) {
return; return;
} }
if (targetDetailsList.isEmpty()) { if (targets.isEmpty()) {
getNotification().displayWarning(getI18n().getMessage(TARGETS_NOT_EXISTS)); getNotification().displayWarning(getI18n().getMessage(TARGETS_NOT_EXISTS));
return; return;
} }
final Long distId = (Long) item.getItemProperty("id").getValue(); final Long distId = (Long) item.getItemProperty("id").getValue();
selectDroppedEntities(distId); selectDroppedEntities(distId);
final Optional<DistributionSet> findDistributionSetById = distributionSetManagement.get(distId); final Optional<DistributionSet> distributionSet = distributionSetManagement.get(distId);
if (!findDistributionSetById.isPresent()) { if (!distributionSet.isPresent()) {
getNotification().displayWarning(getI18n().getMessage(DISTRIBUTIONSET_NOT_EXISTS)); getNotification().displayWarning(getI18n().getMessage(DISTRIBUTIONSET_NOT_EXISTS));
return; return;
} }
addNewDistributionToAssignmentList(targetDetailsList, findDistributionSetById.get()); openConfirmationWindowForAssignment(distributionSet.get(), targets);
openConfirmationWindowForAssignment(findDistributionSetById.get().getName(), targetDetailsList);
} }
private void addNewDistributionToAssignmentList(final List<Target> targetDetailsList, private void openConfirmationWindowForAssignment(final DistributionSet distributionSet,
final DistributionSet distributionSet) { final List<Target> targets) {
String pendingActionMessage = null;
final DistributionSetIdName distributionSetIdName = new DistributionSetIdName(distributionSet);
for (final Target target : targetDetailsList) { final String question = getAssignmentConfirmationMessage(distributionSet, targets);
final TargetIdName key = new TargetIdName(target); final String caption = getI18n().getMessage(CAPTION_ENTITY_ASSIGN_ACTION_CONFIRMBOX);
if (managementUIState.getAssignedList().keySet().contains(key) final String okLabel = getI18n().getMessage(UIMessageIdProvider.BUTTON_OK);
&& managementUIState.getAssignedList().get(key).equals(distributionSetIdName)) { final String cancelLabel = getI18n().getMessage(UIMessageIdProvider.BUTTON_CANCEL);
pendingActionMessage = getPendingActionMessage(pendingActionMessage, target.getControllerId(),
HawkbitCommonUtil.getDistributionNameAndVersion(distributionSetIdName.getName(), confirmDialog = new ConfirmationDialog(caption, question, okLabel, cancelLabel, ok -> {
distributionSetIdName.getVersion())); if (ok && isMaintenanceWindowValid(maintenanceWindowLayout, getNotification())) {
getNotification().displayValidationError(pendingActionMessage); saveAllAssignments(targets, Collections.singletonList(distributionSet), managementUIState,
} else { actionTypeOptionGroupLayout, maintenanceWindowLayout, deploymentManagement, getNotification(),
managementUIState.getAssignedList().put(key, distributionSetIdName); getEventBus(), getI18n(), this);
} }
} }, createAssignmentTab(actionTypeOptionGroupLayout, maintenanceWindowLayout, saveButtonToggle(), getI18n(),
} uiProperties), UIComponentIdProvider.DIST_SET_TO_TARGET_ASSIGNMENT_CONFIRM_ID);
private void openConfirmationWindowForAssignment(final String distributionNameToAssign,
final List<Target> targetDetailsList) {
final String confirmQuestion = createConfirmationQuestionForAssignment(distributionNameToAssign,
targetDetailsList);
createConfirmationWindowForAssignment(confirmQuestion);
UI.getCurrent().addWindow(confirmDialog.getWindow()); UI.getCurrent().addWindow(confirmDialog.getWindow());
confirmDialog.getWindow().bringToFront(); confirmDialog.getWindow().bringToFront();
} }
private void createConfirmationWindowForAssignment(final String confirmQuestion) {
confirmDialog = new ConfirmationDialog(getI18n().getMessage(CAPTION_ENTITY_ASSIGN_ACTION_CONFIRMBOX),
confirmQuestion, getI18n().getMessage(UIMessageIdProvider.BUTTON_OK),
getI18n().getMessage(UIMessageIdProvider.BUTTON_CANCEL), ok -> {
if (ok && isMaintenanceWindowValid(maintenanceWindowLayout, getNotification())) {
saveAllAssignments(managementUIState, actionTypeOptionGroupLayout, maintenanceWindowLayout,
deploymentManagement, getNotification(), getEventBus(), getI18n(), this);
} else {
managementUIState.getAssignedList().clear();
}
}, createAssignmentTab(actionTypeOptionGroupLayout, maintenanceWindowLayout, saveButtonToggle(),
getI18n(), uiProperties),
UIComponentIdProvider.DIST_SET_TO_TARGET_ASSIGNMENT_CONFIRM_ID);
}
private Consumer<Boolean> saveButtonToggle() { private Consumer<Boolean> saveButtonToggle() {
return isEnabled -> confirmDialog.getOkButton().setEnabled(isEnabled); return isEnabled -> confirmDialog.getOkButton().setEnabled(isEnabled);
} }
private String createConfirmationQuestionForAssignment(final String distributionNameToAssign, private String getAssignmentConfirmationMessage(final DistributionSet distributionSet, final List<Target> targets) {
final List<Target> targetDetailsList) { final String distributionName = distributionSet.getName();
if (targetDetailsList.size() == 1) { final int targetCount = targets.size();
return getI18n().getMessage(MESSAGE_CONFIRM_ASSIGN_ENTITY, distributionNameToAssign, "target", if (targetCount > 1) {
targetDetailsList.get(0).getName()); return getI18n().getMessage(MESSAGE_CONFIRM_ASSIGN_MULTIPLE_ENTITIES, targetCount, "targets",
} else { distributionName);
return getI18n().getMessage(MESSAGE_CONFIRM_ASSIGN_MULTIPLE_ENTITIES, targetDetailsList.size(), "targets",
distributionNameToAssign);
} }
return getI18n().getMessage(MESSAGE_CONFIRM_ASSIGN_ENTITY, distributionName, "target",
targets.get(0).getName());
} }
@Override @Override
@@ -516,15 +493,6 @@ public class DistributionTable extends AbstractNamedVersionTable<DistributionSet
return false; return false;
} }
private String getPendingActionMessage(final String message, final String controllerId,
final String distNameVersion) {
String pendActionMsg = getI18n().getMessage("message.target.assigned.pending");
if (null == message) {
pendActionMsg = getI18n().getMessage("message.dist.pending.action", controllerId, distNameVersion);
}
return pendActionMsg;
}
private void updateDistributionInTable(final DistributionSet editedDs) { private void updateDistributionInTable(final DistributionSet editedDs) {
final Item item = getContainerDataSource().getItem(editedDs.getId()); final Item item = getContainerDataSource().getItem(editedDs.getId());
if (item == null) { if (item == null) {

View File

@@ -16,6 +16,7 @@ import org.eclipse.hawkbit.repository.EntityFactory;
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.TargetTagManagement; import org.eclipse.hawkbit.repository.TargetTagManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.ui.SpPermissionChecker; import org.eclipse.hawkbit.ui.SpPermissionChecker;
import org.eclipse.hawkbit.ui.UiProperties; import org.eclipse.hawkbit.ui.UiProperties;
import org.eclipse.hawkbit.ui.common.table.AbstractTableLayout; import org.eclipse.hawkbit.ui.common.table.AbstractTableLayout;
@@ -43,11 +44,11 @@ public class DistributionTableLayout extends AbstractTableLayout<DistributionTab
final UINotification notification, final DistributionSetTagManagement distributionSetTagManagement, final UINotification notification, final DistributionSetTagManagement distributionSetTagManagement,
final TargetTagManagement targetTagManagement, final SystemManagement systemManagement, final TargetTagManagement targetTagManagement, final SystemManagement systemManagement,
final TargetManagement targetManagement, final DeploymentManagement deploymentManagement, final TargetManagement targetManagement, final DeploymentManagement deploymentManagement,
final UiProperties uiProperties) { final TenantConfigurationManagement configManagement, final UiProperties uiProperties) {
final DistributionAddUpdateWindowLayout distributionAddUpdateWindowLayout = new DistributionAddUpdateWindowLayout( final DistributionAddUpdateWindowLayout distributionAddUpdateWindowLayout = new DistributionAddUpdateWindowLayout(
i18n, notification, eventBus, distributionSetManagement, distributionSetTypeManagement, i18n, notification, eventBus, distributionSetManagement, distributionSetTypeManagement,
systemManagement, entityFactory, null); systemManagement, entityFactory, null, configManagement);
final DsMetadataPopupLayout dsMetadataPopupLayout = new DsMetadataPopupLayout(i18n, notification, eventBus, final DsMetadataPopupLayout dsMetadataPopupLayout = new DsMetadataPopupLayout(i18n, notification, eventBus,
distributionSetManagement, entityFactory, permissionChecker); distributionSetManagement, entityFactory, permissionChecker);
@@ -60,7 +61,7 @@ public class DistributionTableLayout extends AbstractTableLayout<DistributionTab
distributionTable, distributionTable,
new DistributionDetails(i18n, eventBus, permissionChecker, managementUIState, distributionSetManagement, new DistributionDetails(i18n, eventBus, permissionChecker, managementUIState, distributionSetManagement,
dsMetadataPopupLayout, notification, distributionSetTagManagement, dsMetadataPopupLayout, notification, distributionSetTagManagement,
distributionAddUpdateWindowLayout)); distributionAddUpdateWindowLayout, configManagement));
} }
public DistributionTable getDistributionTable() { public DistributionTable getDistributionTable() {

View File

@@ -10,9 +10,7 @@ package org.eclipse.hawkbit.ui.management.state;
import java.io.Serializable; import java.io.Serializable;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
@@ -40,8 +38,6 @@ public class ManagementUIState implements ManagementEntityState, Serializable {
private final TargetTableFilters targetTableFilters; private final TargetTableFilters targetTableFilters;
private final Map<TargetIdName, DistributionSetIdName> assignedList = new HashMap<>();
private final Set<DistributionSetIdName> deletedDistributionList = new HashSet<>(); private final Set<DistributionSetIdName> deletedDistributionList = new HashSet<>();
private final Set<TargetIdName> deletedTargetList = new HashSet<>(); private final Set<TargetIdName> deletedTargetList = new HashSet<>();
@@ -137,10 +133,6 @@ public class ManagementUIState implements ManagementEntityState, Serializable {
return distributionTableFilters; return distributionTableFilters;
} }
public Map<TargetIdName, DistributionSetIdName> getAssignedList() {
return assignedList;
}
public Set<DistributionSetIdName> getDeletedDistributionList() { public Set<DistributionSetIdName> getDeletedDistributionList() {
return deletedDistributionList; return deletedDistributionList;
} }

View File

@@ -33,6 +33,7 @@ import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.FilterParams; import org.eclipse.hawkbit.repository.FilterParams;
import org.eclipse.hawkbit.repository.TargetManagement; import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.TargetTagManagement; import org.eclipse.hawkbit.repository.TargetTagManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.event.remote.entity.RemoteEntityEvent; import org.eclipse.hawkbit.repository.event.remote.entity.RemoteEntityEvent;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Tag; import org.eclipse.hawkbit.repository.model.Tag;
@@ -40,6 +41,7 @@ import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetTag; import org.eclipse.hawkbit.repository.model.TargetTag;
import org.eclipse.hawkbit.repository.model.TargetTagAssignmentResult; import org.eclipse.hawkbit.repository.model.TargetTagAssignmentResult;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties;
import org.eclipse.hawkbit.ui.SpPermissionChecker; import org.eclipse.hawkbit.ui.SpPermissionChecker;
import org.eclipse.hawkbit.ui.UiProperties; import org.eclipse.hawkbit.ui.UiProperties;
import org.eclipse.hawkbit.ui.common.ConfirmationDialog; import org.eclipse.hawkbit.ui.common.ConfirmationDialog;
@@ -115,6 +117,8 @@ public class TargetTable extends AbstractTable<Target> {
private static final int PROPERTY_DEPT = 3; private static final int PROPERTY_DEPT = 3;
private static final String MESSAGE_ASSIGN_TARGET_TO_MULTIPLE_DISTRIBUTIONS = "message.confirm.assign.multiple.entities.multiple.distributions";
private final transient TargetManagement targetManagement; private final transient TargetManagement targetManagement;
private final transient DistributionSetManagement distributionSetManagement; private final transient DistributionSetManagement distributionSetManagement;
@@ -123,6 +127,8 @@ public class TargetTable extends AbstractTable<Target> {
private final transient DeploymentManagement deploymentManagement; private final transient DeploymentManagement deploymentManagement;
private final transient TenantConfigurationManagement configManagement;
private final ManagementViewClientCriterion managementViewClientCriterion; private final ManagementViewClientCriterion managementViewClientCriterion;
private final ManagementUIState managementUIState; private final ManagementUIState managementUIState;
@@ -143,7 +149,8 @@ public class TargetTable extends AbstractTable<Target> {
final TargetManagement targetManagement, final ManagementUIState managementUIState, final TargetManagement targetManagement, final ManagementUIState managementUIState,
final SpPermissionChecker permChecker, final ManagementViewClientCriterion managementViewClientCriterion, final SpPermissionChecker permChecker, final ManagementViewClientCriterion managementViewClientCriterion,
final DistributionSetManagement distributionSetManagement, final TargetTagManagement tagManagement, final DistributionSetManagement distributionSetManagement, final TargetTagManagement tagManagement,
final DeploymentManagement deploymentManagement, final UiProperties uiProperties) { final DeploymentManagement deploymentManagement, final TenantConfigurationManagement configManagement,
final UiProperties uiProperties) {
super(eventBus, i18n, notification, permChecker); super(eventBus, i18n, notification, permChecker);
this.targetManagement = targetManagement; this.targetManagement = targetManagement;
this.managementViewClientCriterion = managementViewClientCriterion; this.managementViewClientCriterion = managementViewClientCriterion;
@@ -151,6 +158,7 @@ public class TargetTable extends AbstractTable<Target> {
this.distributionSetManagement = distributionSetManagement; this.distributionSetManagement = distributionSetManagement;
this.tagManagement = tagManagement; this.tagManagement = tagManagement;
this.deploymentManagement = deploymentManagement; this.deploymentManagement = deploymentManagement;
this.configManagement = configManagement;
this.uiProperties = uiProperties; this.uiProperties = uiProperties;
this.actionTypeOptionGroupLayout = new ActionTypeOptionGroupAssignmentLayout(i18n); this.actionTypeOptionGroupLayout = new ActionTypeOptionGroupAssignmentLayout(i18n);
this.maintenanceWindowLayout = new MaintenanceWindowLayout(i18n); this.maintenanceWindowLayout = new MaintenanceWindowLayout(i18n);
@@ -355,23 +363,18 @@ public class TargetTable extends AbstractTable<Target> {
private Map<String, Object> prepareQueryConfigFilters() { private Map<String, Object> prepareQueryConfigFilters() {
final Map<String, Object> queryConfig = Maps.newHashMapWithExpectedSize(7); final Map<String, Object> queryConfig = Maps.newHashMapWithExpectedSize(7);
managementUIState.getTargetTableFilters().getSearchText().ifPresent( managementUIState.getTargetTableFilters().getSearchText()
.ifPresent(value -> queryConfig.put(SPUIDefinitions.FILTER_BY_TEXT, value));
managementUIState.getTargetTableFilters().getDistributionSet()
.ifPresent(value -> queryConfig.put(SPUIDefinitions.FILTER_BY_DISTRIBUTION, value.getId()));
managementUIState.getTargetTableFilters().getPinnedDistId()
.ifPresent(value -> queryConfig.put(SPUIDefinitions.ORDER_BY_DISTRIBUTION, value));
managementUIState.getTargetTableFilters().getTargetFilterQuery()
.ifPresent(value -> queryConfig.put(SPUIDefinitions.FILTER_BY_TARGET_FILTER_QUERY, value));
value -> queryConfig.put(SPUIDefinitions.FILTER_BY_TEXT, value));
managementUIState.getTargetTableFilters().getDistributionSet().ifPresent(
value -> queryConfig.put(SPUIDefinitions.FILTER_BY_DISTRIBUTION, value.getId()));
managementUIState.getTargetTableFilters().getPinnedDistId().ifPresent(
value -> queryConfig.put(SPUIDefinitions.ORDER_BY_DISTRIBUTION, value));
managementUIState.getTargetTableFilters().getTargetFilterQuery().ifPresent(
value -> queryConfig.put(SPUIDefinitions.FILTER_BY_TARGET_FILTER_QUERY, value));
queryConfig.put(SPUIDefinitions.FILTER_BY_NO_TAG, managementUIState.getTargetTableFilters().isNoTagSelected()); queryConfig.put(SPUIDefinitions.FILTER_BY_NO_TAG, managementUIState.getTargetTableFilters().isNoTagSelected());
if ( if (isFilteredByTags()) {
isFilteredByTags()) {
final List<String> list = new ArrayList<>(managementUIState.getTargetTableFilters().getClickedTargetTags()); final List<String> list = new ArrayList<>(managementUIState.getTargetTableFilters().getClickedTargetTags());
queryConfig.put(SPUIDefinitions.FILTER_BY_TAG, list.toArray(new String[list.size()])); queryConfig.put(SPUIDefinitions.FILTER_BY_TAG, list.toArray(new String[list.size()]));
} }
@@ -853,13 +856,17 @@ public class TargetTable extends AbstractTable<Target> {
private void assignDsToTarget(final DragAndDropEvent event) { private void assignDsToTarget(final DragAndDropEvent event) {
final TableTransferable transferable = (TableTransferable) event.getTransferable(); final TableTransferable transferable = (TableTransferable) event.getTransferable();
final AbstractTable<?> source = (AbstractTable<?>) transferable.getSourceComponent(); final AbstractTable<?> source = (AbstractTable<?>) transferable.getSourceComponent();
final Set<Long> ids = source.getSelectedEntitiesByTransferable(transferable);
// only one distribution can be assigned to a target final Set<Long> dsIds = filterDistributionSetsToAssign(source.getSelectedEntitiesByTransferable(transferable));
final Long idToSelect = ids.iterator().next(); if (dsIds.isEmpty()) {
selectDraggedEntities(source, new HashSet<>(Arrays.asList(idToSelect))); getNotification().displayWarning(getI18n().getMessage(DISTRIBUTIONSET_NOT_EXISTS));
return;
}
selectDraggedEntities(source, dsIds);
final AbstractSelectTargetDetails dropData = (AbstractSelectTargetDetails) event.getTargetDetails(); final AbstractSelectTargetDetails dropData = (AbstractSelectTargetDetails) event.getTargetDetails();
final Object targetItemId = dropData.getItemIdOver(); final Object targetItemId = dropData.getItemIdOver();
LOG.debug("Adding a log to check if targetItemId is null : {} ", targetItemId); LOG.debug("Drop target: {} ", targetItemId);
if (targetItemId == null) { if (targetItemId == null) {
getNotification().displayWarning(getI18n().getMessage(TARGETS_NOT_EXISTS, "")); getNotification().displayWarning(getI18n().getMessage(TARGETS_NOT_EXISTS, ""));
return; return;
@@ -873,45 +880,57 @@ public class TargetTable extends AbstractTable<Target> {
return; return;
} }
final TargetIdName createTargetIdName = new TargetIdName(target.get()); final List<DistributionSet> distributionSets = distributionSetManagement.get(dsIds);
final List<DistributionSet> findDistributionSetById = distributionSetManagement if (distributionSets.isEmpty()) {
.get(new HashSet<>(Arrays.asList(idToSelect)));
if (findDistributionSetById.isEmpty()) {
getNotification().displayWarning(getI18n().getMessage(DISTRIBUTIONSET_NOT_EXISTS)); getNotification().displayWarning(getI18n().getMessage(DISTRIBUTIONSET_NOT_EXISTS));
return; return;
} }
final DistributionSet distributionSetToAssign = findDistributionSetById.get(0); openConfirmationWindowForAssignments(target.get(), distributionSets);
addNewTargetToAssignmentList(createTargetIdName, distributionSetToAssign);
openConfirmationWindowForAssignment(distributionSetToAssign.getName(), createTargetIdName.getTargetName());
} }
private void openConfirmationWindowForAssignment(final String distributionNameToAssign, final String targetName) { private Set<Long> filterDistributionSetsToAssign(final Set<Long> ids) {
confirmDialog = new ConfirmationDialog(getI18n().getMessage(CAPTION_ENTITY_ASSIGN_ACTION_CONFIRMBOX), if (ids.isEmpty()) {
getI18n().getMessage(MESSAGE_CONFIRM_ASSIGN_ENTITY, distributionNameToAssign, "target", targetName), return Collections.emptySet();
getI18n().getMessage(UIMessageIdProvider.BUTTON_OK), }
getI18n().getMessage(UIMessageIdProvider.BUTTON_CANCEL), ok -> { if (isMultiAssignmentsEnabled()) {
if (ok && isMaintenanceWindowValid(maintenanceWindowLayout, getNotification())) { return new HashSet<>(ids);
saveAllAssignments(managementUIState, actionTypeOptionGroupLayout, maintenanceWindowLayout, }
deploymentManagement, getNotification(), getEventBus(), getI18n(), this); return Collections.singleton(ids.iterator().next());
} else { }
managementUIState.getAssignedList().clear();
} private void openConfirmationWindowForAssignments(final Target target,
}, createAssignmentTab(actionTypeOptionGroupLayout, maintenanceWindowLayout, saveButtonToggle(), final List<DistributionSet> distributionSets) {
getI18n(), uiProperties),
UIComponentIdProvider.DIST_SET_TO_TARGET_ASSIGNMENT_CONFIRM_ID); final String confirmationMessage = getConfirmationMessage(target, distributionSets);
final String caption = getI18n().getMessage(CAPTION_ENTITY_ASSIGN_ACTION_CONFIRMBOX);
final String okLabel = getI18n().getMessage(UIMessageIdProvider.BUTTON_OK);
final String cancelLabel = getI18n().getMessage(UIMessageIdProvider.BUTTON_CANCEL);
confirmDialog = new ConfirmationDialog(caption, confirmationMessage, okLabel, cancelLabel, ok -> {
if (ok && isMaintenanceWindowValid(maintenanceWindowLayout, getNotification())) {
saveAllAssignments(Collections.singletonList(target), distributionSets, managementUIState,
actionTypeOptionGroupLayout, maintenanceWindowLayout, deploymentManagement, getNotification(),
getEventBus(), getI18n(), this);
}
}, createAssignmentTab(actionTypeOptionGroupLayout, maintenanceWindowLayout, saveButtonToggle(), getI18n(),
uiProperties), UIComponentIdProvider.DIST_SET_TO_TARGET_ASSIGNMENT_CONFIRM_ID);
UI.getCurrent().addWindow(confirmDialog.getWindow()); UI.getCurrent().addWindow(confirmDialog.getWindow());
confirmDialog.getWindow().bringToFront(); confirmDialog.getWindow().bringToFront();
} }
private Consumer<Boolean> saveButtonToggle() { private String getConfirmationMessage(final Target target, final List<DistributionSet> distributionSets) {
return isEnabled -> confirmDialog.getOkButton().setEnabled(isEnabled); final String targetName = target.getName();
final int dsCount = distributionSets.size();
if (dsCount > 1) {
return getI18n().getMessage(MESSAGE_ASSIGN_TARGET_TO_MULTIPLE_DISTRIBUTIONS, dsCount, "target", targetName);
}
return getI18n().getMessage(MESSAGE_CONFIRM_ASSIGN_ENTITY, distributionSets.get(0).getName(), "target",
targetName);
} }
private void addNewTargetToAssignmentList(final TargetIdName createTargetIdName, private Consumer<Boolean> saveButtonToggle() {
final DistributionSet findDistributionSetAllById) { return isEnabled -> confirmDialog.getOkButton().setEnabled(isEnabled);
final DistributionSetIdName distributionNameId = new DistributionSetIdName(findDistributionSetAllById);
managementUIState.getAssignedList().put(createTargetIdName, distributionNameId);
} }
@Override @Override
@@ -963,4 +982,11 @@ public class TargetTable extends AbstractTable<Target> {
return ""; return "";
} }
private boolean isMultiAssignmentsEnabled() {
return configManagement
.getConfigurationValue(TenantConfigurationProperties.TenantConfigurationKey.MULTI_ASSIGNMENTS_ENABLED,
Boolean.class)
.getValue();
}
} }

View File

@@ -9,18 +9,22 @@
package org.eclipse.hawkbit.ui.tenantconfiguration; package org.eclipse.hawkbit.ui.tenantconfiguration;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.ui.UiProperties;
import org.eclipse.hawkbit.ui.components.SPUIComponentProvider; import org.eclipse.hawkbit.ui.components.SPUIComponentProvider;
import org.eclipse.hawkbit.ui.tenantconfiguration.generic.BooleanConfigurationItem; import org.eclipse.hawkbit.ui.tenantconfiguration.generic.BooleanConfigurationItem;
import org.eclipse.hawkbit.ui.tenantconfiguration.repository.ActionAutocleanupConfigurationItem; import org.eclipse.hawkbit.ui.tenantconfiguration.repository.ActionAutocleanupConfigurationItem;
import org.eclipse.hawkbit.ui.tenantconfiguration.repository.ActionAutocloseConfigurationItem; import org.eclipse.hawkbit.ui.tenantconfiguration.repository.ActionAutocloseConfigurationItem;
import org.eclipse.hawkbit.ui.tenantconfiguration.repository.MultiAssignmentsConfigurationItem;
import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider; import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider;
import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; import org.eclipse.hawkbit.ui.utils.VaadinMessageSource;
import com.vaadin.data.Property.ValueChangeEvent; import com.vaadin.data.Property.ValueChangeEvent;
import com.vaadin.data.Property.ValueChangeListener; import com.vaadin.data.Property.ValueChangeListener;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.CheckBox; import com.vaadin.ui.CheckBox;
import com.vaadin.ui.GridLayout; import com.vaadin.ui.GridLayout;
import com.vaadin.ui.Label; import com.vaadin.ui.Label;
import com.vaadin.ui.Link;
import com.vaadin.ui.Panel; import com.vaadin.ui.Panel;
import com.vaadin.ui.VerticalLayout; import com.vaadin.ui.VerticalLayout;
@@ -36,21 +40,30 @@ public class RepositoryConfigurationView extends BaseConfigurationView
private final VaadinMessageSource i18n; private final VaadinMessageSource i18n;
private final UiProperties uiProperties;
private final ActionAutocloseConfigurationItem actionAutocloseConfigurationItem; private final ActionAutocloseConfigurationItem actionAutocloseConfigurationItem;
private final ActionAutocleanupConfigurationItem actionAutocleanupConfigurationItem; private final ActionAutocleanupConfigurationItem actionAutocleanupConfigurationItem;
private final MultiAssignmentsConfigurationItem multiAssignmentsConfigurationItem;
private CheckBox actionAutocloseCheckBox; private CheckBox actionAutocloseCheckBox;
private CheckBox actionAutocleanupCheckBox; private CheckBox actionAutocleanupCheckBox;
private CheckBox multiAssignmentsCheckBox;
RepositoryConfigurationView(final VaadinMessageSource i18n, RepositoryConfigurationView(final VaadinMessageSource i18n,
final TenantConfigurationManagement tenantConfigurationManagement) { final TenantConfigurationManagement tenantConfigurationManagement, final UiProperties uiProperties) {
this.i18n = i18n; this.i18n = i18n;
this.uiProperties = uiProperties;
this.actionAutocloseConfigurationItem = new ActionAutocloseConfigurationItem(tenantConfigurationManagement, this.actionAutocloseConfigurationItem = new ActionAutocloseConfigurationItem(tenantConfigurationManagement,
i18n); i18n);
this.actionAutocleanupConfigurationItem = new ActionAutocleanupConfigurationItem(tenantConfigurationManagement, this.actionAutocleanupConfigurationItem = new ActionAutocleanupConfigurationItem(tenantConfigurationManagement,
i18n); i18n);
this.multiAssignmentsConfigurationItem = new MultiAssignmentsConfigurationItem(tenantConfigurationManagement,
i18n);
init(); init();
} }
@@ -70,27 +83,46 @@ public class RepositoryConfigurationView extends BaseConfigurationView
header.addStyleName("config-panel-header"); header.addStyleName("config-panel-header");
vLayout.addComponent(header); vLayout.addComponent(header);
final GridLayout gridLayout = new GridLayout(2, 2); final GridLayout gridLayout = new GridLayout(3, 3);
gridLayout.setSpacing(true); gridLayout.setSpacing(true);
gridLayout.setImmediate(true); gridLayout.setImmediate(true);
gridLayout.setColumnExpandRatio(1, 1.0F); gridLayout.setColumnExpandRatio(1, 1.0F);
gridLayout.setSizeFull(); gridLayout.setSizeFull();
final boolean isMultiAssignmentsEnabled = multiAssignmentsConfigurationItem.isConfigEnabled();
actionAutocloseCheckBox = SPUIComponentProvider.getCheckBox("", DIST_CHECKBOX_STYLE, null, false, ""); actionAutocloseCheckBox = SPUIComponentProvider.getCheckBox("", DIST_CHECKBOX_STYLE, null, false, "");
actionAutocloseCheckBox.setId(UIComponentIdProvider.REPOSITORY_ACTIONS_AUTOCLOSE_CHECKBOX); actionAutocloseCheckBox.setId(UIComponentIdProvider.REPOSITORY_ACTIONS_AUTOCLOSE_CHECKBOX);
actionAutocloseCheckBox.setEnabled(!isMultiAssignmentsEnabled);
actionAutocloseConfigurationItem.setEnabled(!isMultiAssignmentsEnabled);
actionAutocloseCheckBox.setValue(actionAutocloseConfigurationItem.isConfigEnabled()); actionAutocloseCheckBox.setValue(actionAutocloseConfigurationItem.isConfigEnabled());
actionAutocloseCheckBox.addValueChangeListener(this); actionAutocloseCheckBox.addValueChangeListener(this);
actionAutocloseConfigurationItem.addChangeListener(this); actionAutocloseConfigurationItem.addChangeListener(this);
gridLayout.addComponent(actionAutocloseCheckBox, 0, 0); gridLayout.addComponent(actionAutocloseCheckBox, 0, 0);
gridLayout.addComponent(actionAutocloseConfigurationItem, 1, 0); gridLayout.addComponent(actionAutocloseConfigurationItem, 1, 0);
multiAssignmentsCheckBox = SPUIComponentProvider.getCheckBox("", DIST_CHECKBOX_STYLE, null, false, "");
multiAssignmentsCheckBox.setId(UIComponentIdProvider.REPOSITORY_MULTI_ASSIGNMENTS_CHECKBOX);
multiAssignmentsCheckBox.setValue(multiAssignmentsConfigurationItem.isConfigEnabled());
multiAssignmentsCheckBox.addValueChangeListener(this);
multiAssignmentsCheckBox.setEnabled(!isMultiAssignmentsEnabled);
multiAssignmentsConfigurationItem.setEnabled(!isMultiAssignmentsEnabled);
multiAssignmentsConfigurationItem.addChangeListener(this);
gridLayout.addComponent(multiAssignmentsCheckBox, 0, 1);
gridLayout.addComponent(multiAssignmentsConfigurationItem, 1, 1);
actionAutocleanupCheckBox = SPUIComponentProvider.getCheckBox("", DIST_CHECKBOX_STYLE, null, false, ""); actionAutocleanupCheckBox = SPUIComponentProvider.getCheckBox("", DIST_CHECKBOX_STYLE, null, false, "");
actionAutocleanupCheckBox.setId(UIComponentIdProvider.REPOSITORY_ACTIONS_AUTOCLEANUP_CHECKBOX); actionAutocleanupCheckBox.setId(UIComponentIdProvider.REPOSITORY_ACTIONS_AUTOCLEANUP_CHECKBOX);
actionAutocleanupCheckBox.setValue(actionAutocleanupConfigurationItem.isConfigEnabled()); actionAutocleanupCheckBox.setValue(actionAutocleanupConfigurationItem.isConfigEnabled());
actionAutocleanupCheckBox.addValueChangeListener(this); actionAutocleanupCheckBox.addValueChangeListener(this);
actionAutocleanupConfigurationItem.addChangeListener(this); actionAutocleanupConfigurationItem.addChangeListener(this);
gridLayout.addComponent(actionAutocleanupCheckBox, 0, 1); gridLayout.addComponent(actionAutocleanupCheckBox, 0, 2);
gridLayout.addComponent(actionAutocleanupConfigurationItem, 1, 1); gridLayout.addComponent(actionAutocleanupConfigurationItem, 1, 2);
final Link linkToProvisioningHelp = SPUIComponentProvider.getHelpLink(i18n,
uiProperties.getLinks().getDocumentation().getProvisioningStateMachine());
gridLayout.addComponent(linkToProvisioningHelp, 2, 2);
gridLayout.setComponentAlignment(linkToProvisioningHelp, Alignment.BOTTOM_RIGHT);
vLayout.addComponent(gridLayout); vLayout.addComponent(gridLayout);
rootPanel.setContent(vLayout); rootPanel.setContent(vLayout);
@@ -101,18 +133,29 @@ public class RepositoryConfigurationView extends BaseConfigurationView
public void save() { public void save() {
actionAutocloseConfigurationItem.save(); actionAutocloseConfigurationItem.save();
actionAutocleanupConfigurationItem.save(); actionAutocleanupConfigurationItem.save();
multiAssignmentsConfigurationItem.save();
final boolean isMultiAssignmentsEnabled = multiAssignmentsConfigurationItem.isConfigEnabled();
multiAssignmentsCheckBox.setEnabled(!isMultiAssignmentsEnabled);
multiAssignmentsConfigurationItem.setEnabled(!isMultiAssignmentsEnabled);
} }
@Override @Override
public boolean isUserInputValid() { public boolean isUserInputValid() {
return actionAutocloseConfigurationItem.isUserInputValid() return actionAutocloseConfigurationItem.isUserInputValid()
&& actionAutocleanupConfigurationItem.isUserInputValid(); && actionAutocleanupConfigurationItem.isUserInputValid()
&& multiAssignmentsConfigurationItem.isUserInputValid();
} }
@Override @Override
public void undo() { public void undo() {
multiAssignmentsConfigurationItem.undo();
final boolean isMultiAssignmentsEnabled = multiAssignmentsConfigurationItem.isConfigEnabled();
multiAssignmentsCheckBox.setValue(isMultiAssignmentsEnabled);
actionAutocloseConfigurationItem.undo(); actionAutocloseConfigurationItem.undo();
actionAutocloseCheckBox.setValue(actionAutocloseConfigurationItem.isConfigEnabled()); actionAutocloseCheckBox.setValue(actionAutocloseConfigurationItem.isConfigEnabled());
actionAutocloseCheckBox.setEnabled(!isMultiAssignmentsEnabled);
actionAutocloseConfigurationItem.setEnabled(!isMultiAssignmentsEnabled);
actionAutocleanupConfigurationItem.undo(); actionAutocleanupConfigurationItem.undo();
actionAutocleanupCheckBox.setValue(actionAutocleanupConfigurationItem.isConfigEnabled()); actionAutocleanupCheckBox.setValue(actionAutocleanupConfigurationItem.isConfigEnabled());
} }
@@ -138,6 +181,10 @@ public class RepositoryConfigurationView extends BaseConfigurationView
configurationItem = actionAutocloseConfigurationItem; configurationItem = actionAutocloseConfigurationItem;
} else if (actionAutocleanupCheckBox.equals(checkBox)) { } else if (actionAutocleanupCheckBox.equals(checkBox)) {
configurationItem = actionAutocleanupConfigurationItem; configurationItem = actionAutocleanupConfigurationItem;
} else if (multiAssignmentsCheckBox.equals(checkBox)) {
configurationItem = multiAssignmentsConfigurationItem;
actionAutocloseCheckBox.setEnabled(!checkBox.getValue());
actionAutocloseConfigurationItem.setEnabled(!checkBox.getValue());
} else { } else {
return; return;
} }

View File

@@ -18,7 +18,7 @@ import org.eclipse.hawkbit.ui.utils.VaadinMessageSource;
import com.vaadin.data.Property; import com.vaadin.data.Property;
import com.vaadin.ui.Alignment; import com.vaadin.ui.Alignment;
import com.vaadin.ui.CheckBox; import com.vaadin.ui.CheckBox;
import com.vaadin.ui.HorizontalLayout; import com.vaadin.ui.GridLayout;
import com.vaadin.ui.Label; import com.vaadin.ui.Label;
import com.vaadin.ui.Link; import com.vaadin.ui.Link;
import com.vaadin.ui.Panel; import com.vaadin.ui.Panel;
@@ -61,24 +61,26 @@ public class RolloutConfigurationView extends BaseConfigurationView
header.addStyleName("config-panel-header"); header.addStyleName("config-panel-header");
vLayout.addComponent(header); vLayout.addComponent(header);
final HorizontalLayout hLayout = new HorizontalLayout(); final GridLayout gridLayout = new GridLayout(3, 1);
hLayout.setSpacing(true); gridLayout.setSpacing(true);
hLayout.setImmediate(true); gridLayout.setImmediate(true);
gridLayout.setColumnExpandRatio(1, 1.0F);
gridLayout.setSizeFull();
approvalCheckbox = SPUIComponentProvider.getCheckBox("", "", null, false, ""); approvalCheckbox = SPUIComponentProvider.getCheckBox("", "", null, false, "");
approvalCheckbox.setId(UIComponentIdProvider.ROLLOUT_APPROVAL_ENABLED_CHECKBOX); approvalCheckbox.setId(UIComponentIdProvider.ROLLOUT_APPROVAL_ENABLED_CHECKBOX);
approvalCheckbox.setValue(approvalConfigurationItem.isConfigEnabled()); approvalCheckbox.setValue(approvalConfigurationItem.isConfigEnabled());
approvalCheckbox.addValueChangeListener(this); approvalCheckbox.addValueChangeListener(this);
approvalConfigurationItem.addChangeListener(this); approvalConfigurationItem.addChangeListener(this);
hLayout.addComponent(approvalCheckbox); gridLayout.addComponent(approvalCheckbox, 0, 0);
hLayout.addComponent(approvalConfigurationItem); gridLayout.addComponent(approvalConfigurationItem, 1, 0);
final Link linkToApprovalHelp = SPUIComponentProvider.getHelpLink(i18n, final Link linkToApprovalHelp = SPUIComponentProvider.getHelpLink(i18n,
uiProperties.getLinks().getDocumentation().getRollout()); uiProperties.getLinks().getDocumentation().getRollout());
hLayout.addComponent(linkToApprovalHelp); gridLayout.addComponent(linkToApprovalHelp, 2, 0);
hLayout.setComponentAlignment(linkToApprovalHelp, Alignment.BOTTOM_RIGHT); gridLayout.setComponentAlignment(linkToApprovalHelp, Alignment.BOTTOM_RIGHT);
vLayout.addComponent(hLayout); vLayout.addComponent(gridLayout);
rootPanel.setContent(vLayout); rootPanel.setContent(vLayout);
setCompositionRoot(rootPanel); setCompositionRoot(rootPanel);
} }

View File

@@ -91,7 +91,8 @@ public class TenantConfigurationDashboardView extends CustomComponent implements
securityTokenGenerator, uiProperties); securityTokenGenerator, uiProperties);
this.pollingConfigurationView = new PollingConfigurationView(i18n, controllerPollProperties, this.pollingConfigurationView = new PollingConfigurationView(i18n, controllerPollProperties,
tenantConfigurationManagement); tenantConfigurationManagement);
this.repositoryConfigurationView = new RepositoryConfigurationView(i18n, tenantConfigurationManagement); this.repositoryConfigurationView = new RepositoryConfigurationView(i18n, tenantConfigurationManagement,
uiProperties);
this.rolloutConfigurationView = new RolloutConfigurationView(i18n, tenantConfigurationManagement, uiProperties); this.rolloutConfigurationView = new RolloutConfigurationView(i18n, tenantConfigurationManagement, uiProperties);
this.i18n = i18n; this.i18n = i18n;

View File

@@ -0,0 +1,111 @@
/**
* Copyright (c) 2019 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.ui.tenantconfiguration.repository;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.eclipse.hawkbit.ui.common.builder.LabelBuilder;
import org.eclipse.hawkbit.ui.tenantconfiguration.generic.AbstractBooleanTenantConfigurationItem;
import org.eclipse.hawkbit.ui.utils.VaadinMessageSource;
import com.vaadin.ui.Label;
import com.vaadin.ui.VerticalLayout;
/**
* This class represents the UI item for enabling /disabling the
* Multi-Assignments feature as part of the repository configuration view.
*/
public class MultiAssignmentsConfigurationItem extends AbstractBooleanTenantConfigurationItem {
private static final long serialVersionUID = 1L;
private static final String MSG_KEY_CHECKBOX = "label.configuration.repository.multiassignments";
private static final String MSG_KEY_NOTICE = "label.configuration.repository.multiassignments.notice";
private final VerticalLayout container;
private final VaadinMessageSource i18n;
private boolean isMultiAssignmentsEnabled;
private boolean multiAssignmentsEnabledChanged;
/**
* Constructor.
*
* @param tenantConfigurationManagement
* to read /write tenant-specific configuration properties
* @param i18n
* to obtain localized strings
*/
public MultiAssignmentsConfigurationItem(final TenantConfigurationManagement tenantConfigurationManagement,
final VaadinMessageSource i18n) {
super(TenantConfigurationKey.MULTI_ASSIGNMENTS_ENABLED, tenantConfigurationManagement, i18n);
this.i18n = i18n;
super.init(MSG_KEY_CHECKBOX);
isMultiAssignmentsEnabled = isConfigEnabled();
container = new VerticalLayout();
container.setImmediate(true);
container.addComponent(newLabel(MSG_KEY_NOTICE));
if (isMultiAssignmentsEnabled) {
setSettingsVisible(isMultiAssignmentsEnabled);
}
}
@Override
public void configEnable() {
if (!isMultiAssignmentsEnabled) {
multiAssignmentsEnabledChanged = true;
}
isMultiAssignmentsEnabled = true;
setSettingsVisible(true);
}
@Override
public void configDisable() {
if (isMultiAssignmentsEnabled) {
multiAssignmentsEnabledChanged = true;
}
isMultiAssignmentsEnabled = false;
setSettingsVisible(false);
}
@Override
public void save() {
if (!multiAssignmentsEnabledChanged) {
return;
}
getTenantConfigurationManagement().addOrUpdateConfiguration(getConfigurationKey(), isMultiAssignmentsEnabled);
}
@Override
public void undo() {
multiAssignmentsEnabledChanged = false;
isMultiAssignmentsEnabled = getTenantConfigurationManagement()
.getConfigurationValue(getConfigurationKey(), Boolean.class).getValue();
}
private void setSettingsVisible(final boolean visible) {
if (visible) {
addComponent(container);
} else {
removeComponent(container);
}
}
private Label newLabel(final String msgKey) {
final Label label = new LabelBuilder().name(i18n.getMessage(msgKey)).buildLabel();
label.setWidthUndefined();
return label;
}
}

View File

@@ -608,6 +608,11 @@ public final class UIComponentIdProvider {
*/ */
public static final String DETAILS_TYPE_LABEL_ID = "details.type"; public static final String DETAILS_TYPE_LABEL_ID = "details.type";
/**
* Table details Required Migration Step label id.
*/
public static final String DETAILS_REQUIRED_MIGRATION_STEP_LABEL_ID = "details.required.migration.step";
/** /**
* Id of show filter button in software module table. * Id of show filter button in software module table.
*/ */
@@ -1154,6 +1159,11 @@ public final class UIComponentIdProvider {
*/ */
public static final String ROLLOUT_POPUP_ID = "add.update.rollout.popup"; public static final String ROLLOUT_POPUP_ID = "add.update.rollout.popup";
/**
* Create popup id.
*/
public static final String CREATE_POPUP_ID = "create.popup.id";
/** /**
* DistributionSet table details tab id in Distributions . * DistributionSet table details tab id in Distributions .
*/ */
@@ -1240,6 +1250,12 @@ public final class UIComponentIdProvider {
*/ */
public static final String REPOSITORY_ACTIONS_AUTOCLEANUP_CHECKBOX = "repositoryactionsautocleanupcheckbox"; public static final String REPOSITORY_ACTIONS_AUTOCLEANUP_CHECKBOX = "repositoryactionsautocleanupcheckbox";
/**
* Configuration checkbox for
* {@link TenantConfigurationKey#MULTI_ASSIGNMENTS_ENABLED}.
*/
public static final String REPOSITORY_MULTI_ASSIGNMENTS_CHECKBOX = "repositorymultiassignmentscheckbox";
/** /**
* Configuration checkbox for * Configuration checkbox for
* {@link TenantConfigurationKey#ROLLOUT_APPROVAL_ENABLED} * {@link TenantConfigurationKey#ROLLOUT_APPROVAL_ENABLED}

View File

@@ -245,6 +245,8 @@ label.configuration.repository.autocleanup.action.suffix = day(s)
label.configuration.repository.autocleanup.action.expiry.invalid = The specified number of days is invalid. Please enter a positive integer value between 1 and 1000. label.configuration.repository.autocleanup.action.expiry.invalid = The specified number of days is invalid. Please enter a positive integer value between 1 and 1000.
label.configuration.anonymous.download = Allow targets to download artifacts without security credentials label.configuration.anonymous.download = Allow targets to download artifacts without security credentials
label.configuration.repository.autocleanup.action.notice = Warning: The actions are deleted from the repository and cannot be restored label.configuration.repository.autocleanup.action.notice = Warning: The actions are deleted from the repository and cannot be restored
label.configuration.repository.multiassignments = Allow parallel execution of multiple distribution set assignments and rollouts
label.configuration.repository.multiassignments.notice = Warning: Once this assignment behavior is active, it cannot be deactivated.
label.unsupported.browser.ie = Sorry! Your current browser is not supported. Please use Internet Explorer 11 and above label.unsupported.browser.ie = Sorry! Your current browser is not supported. Please use Internet Explorer 11 and above
label.auto.assign.description = When an auto assign distribution set is selected, it will be automatically assigned to all targets that match the target filter. label.auto.assign.description = When an auto assign distribution set is selected, it will be automatically assigned to all targets that match the target filter.
label.auto.assign.enable = Enable auto assignment label.auto.assign.enable = Enable auto assignment
@@ -711,6 +713,7 @@ message.confirm.delete.entity = Are you sure you want to delete {0} {1}{2}?
caption.entity.assign.action.confirmbox = Confirm Assignment caption.entity.assign.action.confirmbox = Confirm Assignment
message.confirm.assign.entity = Are you sure you want to assign distribution {0} to {1} {2}? message.confirm.assign.entity = Are you sure you want to assign distribution {0} to {1} {2}?
message.confirm.assign.multiple.entities = Are you sure you want to assign {0} {1} to distribution {2}? message.confirm.assign.multiple.entities = Are you sure you want to assign {0} {1} to distribution {2}?
message.confirm.assign.multiple.entities.multiple.distributions = Are you sure you want to assign {0} distributions to {1} {2}?
# character descriptions # character descriptions
character.whitespace = whitespace character.whitespace = whitespace