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:
committed by
Dominic Schabel
parent
f16f986eed
commit
1ac40a7a95
@@ -191,6 +191,7 @@ public enum SpServerError {
|
||||
*/
|
||||
SP_CONFIGURATION_VALUE_INVALID("hawkbit.server.error.configValueInvalid",
|
||||
"The given configuration value is invalid."),
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@@ -232,7 +233,14 @@ public enum SpServerError {
|
||||
* invalid.
|
||||
*/
|
||||
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 message;
|
||||
|
||||
@@ -17,6 +17,7 @@ import org.eclipse.hawkbit.cache.DownloadIdCache;
|
||||
import org.eclipse.hawkbit.dmf.amqp.api.AmqpSettings;
|
||||
import org.eclipse.hawkbit.repository.ArtifactManagement;
|
||||
import org.eclipse.hawkbit.repository.ControllerManagement;
|
||||
import org.eclipse.hawkbit.repository.DeploymentManagement;
|
||||
import org.eclipse.hawkbit.repository.DistributionSetManagement;
|
||||
import org.eclipse.hawkbit.repository.EntityFactory;
|
||||
import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
|
||||
@@ -240,9 +241,11 @@ public class AmqpConfiguration {
|
||||
@Bean
|
||||
public AmqpMessageHandlerService amqpMessageHandlerService(final RabbitTemplate rabbitTemplate,
|
||||
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,
|
||||
entityFactory);
|
||||
entityFactory, systemSecurityContext, tenantConfigurationManagement);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -319,10 +322,10 @@ public class AmqpConfiguration {
|
||||
final AmqpMessageSenderService amqpSenderService, final ArtifactUrlHandler artifactUrlHandler,
|
||||
final SystemSecurityContext systemSecurityContext, final SystemManagement systemManagement,
|
||||
final TargetManagement targetManagement, final DistributionSetManagement distributionSetManagement,
|
||||
final SoftwareModuleManagement softwareModuleManagement) {
|
||||
final SoftwareModuleManagement softwareModuleManagement, final DeploymentManagement deploymentManagement) {
|
||||
return new AmqpMessageDispatcherService(rabbitTemplate, amqpSenderService, artifactUrlHandler,
|
||||
systemSecurityContext, systemManagement, targetManagement, serviceMatcher, distributionSetManagement,
|
||||
softwareModuleManagement);
|
||||
softwareModuleManagement, deploymentManagement);
|
||||
}
|
||||
|
||||
private static Map<String, Object> getTTLMaxArgsAuthenticationQueue() {
|
||||
|
||||
@@ -8,8 +8,11 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.amqp;
|
||||
|
||||
import static org.eclipse.hawkbit.repository.RepositoryConstants.MAX_ACTION_COUNT;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
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.DmfDownloadAndUpdateRequest;
|
||||
import org.eclipse.hawkbit.dmf.json.model.DmfMetadata;
|
||||
import org.eclipse.hawkbit.dmf.json.model.DmfMultiActionRequest;
|
||||
import org.eclipse.hawkbit.dmf.json.model.DmfSoftwareModule;
|
||||
import org.eclipse.hawkbit.repository.DeploymentManagement;
|
||||
import org.eclipse.hawkbit.repository.DistributionSetManagement;
|
||||
import org.eclipse.hawkbit.repository.RepositoryConstants;
|
||||
import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
|
||||
import org.eclipse.hawkbit.repository.SystemManagement;
|
||||
import org.eclipse.hawkbit.repository.TargetManagement;
|
||||
import org.eclipse.hawkbit.repository.event.remote.MultiActionEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.TargetAttributesRequestedEvent;
|
||||
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.ActionProperties;
|
||||
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.SoftwareModuleMetadata;
|
||||
import org.eclipse.hawkbit.repository.model.Target;
|
||||
@@ -79,6 +86,7 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
|
||||
private final TargetManagement targetManagement;
|
||||
private final ServiceMatcher serviceMatcher;
|
||||
private final DistributionSetManagement distributionSetManagement;
|
||||
private final DeploymentManagement deploymentManagement;
|
||||
private final SoftwareModuleManagement softwareModuleManagement;
|
||||
|
||||
/**
|
||||
@@ -107,7 +115,7 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
|
||||
final SystemSecurityContext systemSecurityContext, final SystemManagement systemManagement,
|
||||
final TargetManagement targetManagement, final ServiceMatcher serviceMatcher,
|
||||
final DistributionSetManagement distributionSetManagement,
|
||||
final SoftwareModuleManagement softwareModuleManagement) {
|
||||
final SoftwareModuleManagement softwareModuleManagement, final DeploymentManagement deploymentManagement) {
|
||||
super(rabbitTemplate);
|
||||
this.artifactUrlHandler = artifactUrlHandler;
|
||||
this.amqpSenderService = amqpSenderService;
|
||||
@@ -117,6 +125,7 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
|
||||
this.serviceMatcher = serviceMatcher;
|
||||
this.distributionSetManagement = distributionSetManagement;
|
||||
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.");
|
||||
|
||||
distributionSetManagement.get(assignedEvent.getDistributionSetId()).ifPresent(set -> {
|
||||
distributionSetManagement.get(assignedEvent.getDistributionSetId()).ifPresent(ds -> {
|
||||
|
||||
final Map<SoftwareModule, List<SoftwareModuleMetadata>> modules = Maps
|
||||
.newHashMapWithExpectedSize(set.getModules().size());
|
||||
set.getModules()
|
||||
.forEach(
|
||||
module -> modules.put(module,
|
||||
softwareModuleManagement.findMetaDataBySoftwareModuleIdAndTargetVisible(
|
||||
PageRequest.of(0, RepositoryConstants.MAX_META_DATA_COUNT), module.getId())
|
||||
.getContent()));
|
||||
final Map<SoftwareModule, List<SoftwareModuleMetadata>> softwareModules = getSoftwareModulesWithMetadata(
|
||||
ds);
|
||||
|
||||
targetManagement.getByControllerID(assignedEvent.getActions().keySet()).forEach(
|
||||
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
|
||||
* DOWNLOAD_ONLY action or if it has a valid maintenance window available
|
||||
* or not based on defined maintenance schedule. In case of no maintenance
|
||||
* schedule or if there is a valid window available, the topic {@link EventTopic#DOWNLOAD_AND_INSTALL} is
|
||||
* returned else {@link EventTopic#DOWNLOAD} is returned.
|
||||
* DOWNLOAD_ONLY action or if it has a valid maintenance window available or
|
||||
* not based on defined maintenance schedule. In case of no maintenance
|
||||
* schedule or if there is a valid window available, the topic
|
||||
* {@link EventTopic#DOWNLOAD_AND_INSTALL} is returned else
|
||||
* {@link EventTopic#DOWNLOAD} is returned.
|
||||
*
|
||||
* @param action
|
||||
* current action properties.
|
||||
@@ -165,8 +256,24 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
|
||||
* @return {@link EventTopic} to use for message.
|
||||
*/
|
||||
private static EventTopic getEventTypeForTarget(final ActionProperties action) {
|
||||
return (Action.ActionType.DOWNLOAD_ONLY.equals(action.getActionType()) ||
|
||||
!action.isMaintenanceWindowAvailable()) ? EventTopic.DOWNLOAD : EventTopic.DOWNLOAD_AND_INSTALL;
|
||||
return (Action.ActionType.DOWNLOAD_ONLY.equals(action.getActionType())
|
||||
|| !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.
|
||||
*
|
||||
* @param cancelEvent
|
||||
* the object to be send.
|
||||
* that is to be converted to a DMF message
|
||||
*/
|
||||
@EventListener(classes = CancelTargetAssignmentEvent.class)
|
||||
protected void targetCancelAssignmentToDistributionSet(final CancelTargetAssignmentEvent cancelEvent) {
|
||||
@@ -211,7 +318,7 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
|
||||
protected void sendUpdateMessageToTarget(final ActionProperties action, final Target target,
|
||||
final Map<SoftwareModule, List<SoftwareModuleMetadata>> modules) {
|
||||
|
||||
String tenant = action.getTenant();
|
||||
final String tenant = action.getTenant();
|
||||
|
||||
final URI targetAdress = target.getAddress();
|
||||
if (!IpUtil.isAmqpUri(targetAdress)) {
|
||||
@@ -361,4 +468,17 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,11 +8,16 @@
|
||||
*/
|
||||
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.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -27,14 +32,17 @@ import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails;
|
||||
import org.eclipse.hawkbit.repository.ControllerManagement;
|
||||
import org.eclipse.hawkbit.repository.EntityFactory;
|
||||
import org.eclipse.hawkbit.repository.RepositoryConstants;
|
||||
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||
import org.eclipse.hawkbit.repository.UpdateMode;
|
||||
import org.eclipse.hawkbit.repository.builder.ActionStatusCreate;
|
||||
import org.eclipse.hawkbit.repository.model.Action;
|
||||
import org.eclipse.hawkbit.repository.model.Action.Status;
|
||||
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.SoftwareModuleMetadata;
|
||||
import org.eclipse.hawkbit.repository.model.Target;
|
||||
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
||||
import org.eclipse.hawkbit.util.IpUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -42,6 +50,7 @@ import org.springframework.amqp.AmqpRejectAndDontRequeueException;
|
||||
import org.springframework.amqp.core.Message;
|
||||
import org.springframework.amqp.rabbit.annotation.RabbitListener;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.messaging.handler.annotation.Header;
|
||||
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||||
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.util.StringUtils;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
/**
|
||||
*
|
||||
* {@link AmqpMessageHandlerService} handles all incoming target interaction
|
||||
@@ -70,6 +77,10 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
|
||||
|
||||
private final EntityFactory entityFactory;
|
||||
|
||||
private final TenantConfigurationManagement tenantConfigurationManagement;
|
||||
|
||||
private final SystemSecurityContext systemSecurityContext;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
@@ -84,11 +95,15 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
|
||||
*/
|
||||
public AmqpMessageHandlerService(final RabbitTemplate rabbitTemplate,
|
||||
final AmqpMessageDispatcherService amqpMessageDispatcherService,
|
||||
final ControllerManagement controllerManagement, final EntityFactory entityFactory) {
|
||||
final ControllerManagement controllerManagement, final EntityFactory entityFactory,
|
||||
final SystemSecurityContext systemSecurityContext,
|
||||
final TenantConfigurationManagement tenantConfigurationManagement) {
|
||||
super(rabbitTemplate);
|
||||
this.amqpMessageDispatcherService = amqpMessageDispatcherService;
|
||||
this.controllerManagement = controllerManagement;
|
||||
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);
|
||||
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
|
||||
.findOldestActiveActionByTarget(target.getControllerId());
|
||||
|
||||
@@ -207,20 +242,23 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
|
||||
if (action.isCancelingOrCanceled()) {
|
||||
amqpMessageDispatcherService.sendCancelMessageToTarget(target.getTenant(), target.getControllerId(),
|
||||
action.getId(), target.getAddress());
|
||||
return;
|
||||
} else {
|
||||
amqpMessageDispatcherService.sendUpdateMessageToTarget(new ActionProperties(action), action.getTarget(),
|
||||
getSoftwareModulesWithMetadata(action.getDistributionSet()));
|
||||
}
|
||||
}
|
||||
|
||||
final Map<SoftwareModule, List<SoftwareModuleMetadata>> modules = Maps
|
||||
.newHashMapWithExpectedSize(action.getDistributionSet().getModules().size());
|
||||
private Map<SoftwareModule, List<SoftwareModuleMetadata>> getSoftwareModulesWithMetadata(
|
||||
final DistributionSet distributionSet) {
|
||||
final List<Long> smIds = distributionSet.getModules().stream().map(SoftwareModule::getId)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
final Map<Long, List<SoftwareModuleMetadata>> metadata = controllerManagement
|
||||
.findTargetVisibleMetaDataBySoftwareModuleId(action.getDistributionSet().getModules().stream()
|
||||
.map(SoftwareModule::getId).collect(Collectors.toList()));
|
||||
.findTargetVisibleMetaDataBySoftwareModuleId(smIds);
|
||||
|
||||
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)
|
||||
.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()
|
||||
&& addUpdateActionStatus.isMaintenanceWindowAvailable())) {
|
||||
lookIfUpdateAvailable(action.getTarget());
|
||||
if (shouldTargetProceed(updatedAction)) {
|
||||
sendUpdateCommandToTarget(action.getTarget());
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean shouldTargetProceed(final Action action) {
|
||||
return !action.isActive() || (action.hasMaintenanceSchedule() && action.isMaintenanceWindowAvailable());
|
||||
}
|
||||
|
||||
private static boolean isCorrelationIdNotEmpty(final Message message) {
|
||||
return StringUtils.hasLength(message.getMessageProperties().getCorrelationId());
|
||||
}
|
||||
@@ -337,13 +380,6 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
|
||||
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.
|
||||
// get will not be called
|
||||
@SuppressWarnings("squid:S3655")
|
||||
@@ -373,4 +409,13 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,14 +16,10 @@ import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.util.Optional;
|
||||
|
||||
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.dmf.amqp.api.MessageHeaderKey;
|
||||
import org.eclipse.hawkbit.dmf.amqp.api.MessageType;
|
||||
@@ -109,7 +105,7 @@ public class AmqpControllerAuthenticationTest {
|
||||
private ControllerManagement controllerManagementMock;
|
||||
|
||||
@Mock
|
||||
private Target targteMock;
|
||||
private Target targetMock;
|
||||
|
||||
private static final TenantConfigurationValue<Boolean> CONFIG_VALUE_FALSE = TenantConfigurationValue
|
||||
.<Boolean> builder().value(Boolean.FALSE).build();
|
||||
@@ -137,11 +133,11 @@ public class AmqpControllerAuthenticationTest {
|
||||
.thenReturn(CONFIG_VALUE_FALSE);
|
||||
|
||||
final ControllerManagement controllerManagement = mock(ControllerManagement.class);
|
||||
when(controllerManagement.getByControllerId(anyString())).thenReturn(Optional.of(targteMock));
|
||||
when(controllerManagement.get(any(Long.class))).thenReturn(Optional.of(targteMock));
|
||||
when(controllerManagement.getByControllerId(anyString())).thenReturn(Optional.of(targetMock));
|
||||
when(controllerManagement.get(any(Long.class))).thenReturn(Optional.of(targetMock));
|
||||
|
||||
when(targteMock.getSecurityToken()).thenReturn(CONTROLLER_ID);
|
||||
when(targteMock.getControllerId()).thenReturn(CONTROLLER_ID);
|
||||
when(targetMock.getSecurityToken()).thenReturn(CONTROLLER_ID);
|
||||
when(targetMock.getControllerId()).thenReturn(CONTROLLER_ID);
|
||||
|
||||
final SecurityContextTenantAware tenantAware = new SecurityContextTenantAware();
|
||||
final SystemSecurityContext systemSecurityContext = new SystemSecurityContext(tenantAware);
|
||||
@@ -162,11 +158,9 @@ public class AmqpControllerAuthenticationTest {
|
||||
when(artifactManagementMock.get(ARTIFACT_ID)).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,
|
||||
mock(AmqpMessageDispatcherService.class), controllerManagementMock, new JpaEntityFactory());
|
||||
mock(AmqpMessageDispatcherService.class), controllerManagementMock, new JpaEntityFactory(),
|
||||
systemSecurityContext, tenantConfigurationManagementMock);
|
||||
|
||||
amqpAuthenticationMessageHandlerService = new AmqpAuthenticationMessageHandler(rabbitTemplate,
|
||||
authenticationManager, artifactManagementMock, cacheMock, hostnameResolverMock,
|
||||
|
||||
@@ -13,7 +13,6 @@ import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyObject;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@@ -101,7 +100,7 @@ public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTest {
|
||||
senderService = Mockito.mock(DefaultAmqpMessageSenderService.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")));
|
||||
|
||||
systemManagement = Mockito.mock(SystemManagement.class);
|
||||
@@ -113,7 +112,7 @@ public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTest {
|
||||
|
||||
amqpMessageDispatcherService = new AmqpMessageDispatcherService(rabbitTemplate, senderService,
|
||||
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) {
|
||||
return deploymentManagement.findAction(assignDistributionSet(testDs, testTarget).getActions().get(0)).get();
|
||||
return deploymentManagement.findAction(assignDistributionSet(testDs, testTarget).getActionIds().get(0)).get();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
package org.eclipse.hawkbit.amqp;
|
||||
|
||||
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.mockito.ArgumentMatchers.any;
|
||||
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.ControllerManagement;
|
||||
import org.eclipse.hawkbit.repository.EntityFactory;
|
||||
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||
import org.eclipse.hawkbit.repository.UpdateMode;
|
||||
import org.eclipse.hawkbit.repository.builder.ActionStatusBuilder;
|
||||
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.DistributionSet;
|
||||
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.FileResource;
|
||||
import org.eclipse.hawkbit.security.SecurityContextTenantAware;
|
||||
import org.eclipse.hawkbit.security.SecurityTokenGenerator;
|
||||
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
||||
import org.eclipse.hawkbit.tenancy.TenantAware;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -100,6 +105,9 @@ public class AmqpMessageHandlerServiceTest {
|
||||
@Mock
|
||||
private ArtifactManagement artifactManagementMock;
|
||||
|
||||
@Mock
|
||||
private TenantConfigurationManagement tenantConfigurationManagement;
|
||||
|
||||
@Mock
|
||||
private AmqpControllerAuthentication authenticationManagerMock;
|
||||
|
||||
@@ -128,16 +136,21 @@ public class AmqpMessageHandlerServiceTest {
|
||||
private ArgumentCaptor<UpdateMode> modeCaptor;
|
||||
|
||||
@Before
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
public void before() throws Exception {
|
||||
messageConverter = new Jackson2JsonMessageConverter();
|
||||
when(rabbitTemplate.getMessageConverter()).thenReturn(messageConverter);
|
||||
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,
|
||||
controllerManagementMock, entityFactoryMock);
|
||||
|
||||
amqpMessageHandlerService = new AmqpMessageHandlerService(rabbitTemplate, amqpMessageDispatcherServiceMock,
|
||||
controllerManagementMock, entityFactoryMock);
|
||||
controllerManagementMock, entityFactoryMock, systemSecurityContext, tenantConfigurationManagement);
|
||||
amqpAuthenticationMessageHandlerService = new AmqpAuthenticationMessageHandler(rabbitTemplate,
|
||||
authenticationManagerMock, artifactManagementMock, downloadIdCache, hostnameResolverMock,
|
||||
controllerManagementMock, tenantAwareMock);
|
||||
@@ -465,8 +478,8 @@ public class AmqpMessageHandlerServiceTest {
|
||||
final ArgumentCaptor<ActionProperties> actionPropertiesCaptor = ArgumentCaptor.forClass(ActionProperties.class);
|
||||
final ArgumentCaptor<Target> targetCaptor = ArgumentCaptor.forClass(Target.class);
|
||||
|
||||
verify(amqpMessageDispatcherServiceMock, times(1))
|
||||
.sendUpdateMessageToTarget(actionPropertiesCaptor.capture(), targetCaptor.capture(), any(Map.class));
|
||||
verify(amqpMessageDispatcherServiceMock, times(1)).sendUpdateMessageToTarget(actionPropertiesCaptor.capture(),
|
||||
targetCaptor.capture(), any(Map.class));
|
||||
final ActionProperties actionProperties = actionPropertiesCaptor.getValue();
|
||||
assertThat(actionProperties).isNotNull();
|
||||
assertThat(actionProperties.getTenant()).as("event has tenant").isEqualTo("DEFAULT");
|
||||
|
||||
@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.integration;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
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 CREATED_BY = "CONTROLLER_PLUG_AND_PLAY";
|
||||
|
||||
protected ReplyToListener replyToListener;
|
||||
private DeadletterListener deadletterListener;
|
||||
private ReplyToListener replyToListener;
|
||||
private DistributionSet distributionSet;
|
||||
|
||||
@Autowired
|
||||
@@ -86,11 +87,12 @@ public abstract class AbstractAmqpServiceIntegrationTest extends AbstractAmqpInt
|
||||
Mockito.reset(deadletterListener);
|
||||
replyToListener = harness.getSpy(ReplyToListener.LISTENER_ID);
|
||||
assertThat(replyToListener).isNotNull();
|
||||
replyToListener.purge();
|
||||
Mockito.reset(replyToListener);
|
||||
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(() -> {
|
||||
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(() -> {
|
||||
Mockito.verify(getDeadletterListener(), Mockito.times(expectedMessages)).handleMessage(Mockito.any());
|
||||
assertThat(replyToListener.getLatestEventMessageTopics())
|
||||
.containsExactlyInAnyOrderElementsOf(Arrays.asList(eventTopics));
|
||||
});
|
||||
replyToListener.resetLatestEventMessageTopics();
|
||||
}
|
||||
|
||||
protected DeadletterListener getDeadletterListener() {
|
||||
@@ -158,7 +162,7 @@ public abstract class AbstractAmqpServiceIntegrationTest extends AbstractAmqpInt
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -196,15 +200,17 @@ public abstract class AbstractAmqpServiceIntegrationTest extends AbstractAmqpInt
|
||||
assertThat(updatedTarget.getSecurityToken()).isEqualTo(downloadAndUpdateRequest.getTargetSecurityToken());
|
||||
}
|
||||
|
||||
protected void assertDownloadAndInstallMessage(final Set<SoftwareModule> dsModules, final String controllerId) {
|
||||
assertAssignmentMessage(dsModules, controllerId, EventTopic.DOWNLOAD_AND_INSTALL);
|
||||
protected void assertDownloadAndInstallMessage(final Set<SoftwareModule> softwareModules,
|
||||
final String controllerId) {
|
||||
assertAssignmentMessage(softwareModules, controllerId, EventTopic.DOWNLOAD_AND_INSTALL);
|
||||
|
||||
}
|
||||
|
||||
protected void assertDownloadMessage(final Set<SoftwareModule> dsModules, final String controllerId) {
|
||||
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);
|
||||
getDmfClient().send(message);
|
||||
}
|
||||
@@ -229,7 +235,7 @@ public abstract class AbstractAmqpServiceIntegrationTest extends AbstractAmqpInt
|
||||
|
||||
protected Long registerTargetAndCancelActionId(final String 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) {
|
||||
@@ -238,7 +244,8 @@ public abstract class AbstractAmqpServiceIntegrationTest extends AbstractAmqpInt
|
||||
|
||||
protected Message assertReplyMessageHeader(final EventTopic eventTopic, final String controllerId) {
|
||||
verifyReplyToListener();
|
||||
final Message replyMessage = replyToListener.getEventTopicMessages().get(eventTopic);
|
||||
|
||||
final Message replyMessage = replyToListener.getLatestEventMessage(eventTopic);
|
||||
assertAllTargetsCount(1);
|
||||
final Map<String, Object> headers = replyMessage.getMessageProperties().getHeaders();
|
||||
assertThat(headers.get(MessageHeaderKey.TOPIC)).isEqualTo(eventTopic.toString());
|
||||
@@ -264,7 +271,7 @@ public abstract class AbstractAmqpServiceIntegrationTest extends AbstractAmqpInt
|
||||
protected void registerAndAssertTargetWithExistingTenant(final String target,
|
||||
final int existingTargetsAfterCreation, final TargetUpdateStatus expectedTargetStatus,
|
||||
final String createdBy) {
|
||||
createAndSendTarget(target, TENANT_EXIST);
|
||||
createAndSendThingCreated(target, TENANT_EXIST);
|
||||
final Target registerdTarget = waitUntilIsPresent(() -> targetManagement.getByControllerID(target));
|
||||
assertAllTargetsCount(existingTargetsAfterCreation);
|
||||
assertTarget(registerdTarget, expectedTargetStatus, createdBy);
|
||||
@@ -273,7 +280,7 @@ public abstract class AbstractAmqpServiceIntegrationTest extends AbstractAmqpInt
|
||||
protected void registerSameTargetAndAssertBasedOnVersion(final String controllerId,
|
||||
final int existingTargetsAfterCreation, final TargetUpdateStatus expectedTargetStatus) {
|
||||
final int version = controllerManagement.getByControllerId(controllerId).get().getOptLockRevision();
|
||||
createAndSendTarget(controllerId, TENANT_EXIST);
|
||||
createAndSendThingCreated(controllerId, TENANT_EXIST);
|
||||
final Target registeredTarget = waitUntilIsPresent(() -> findTargetBasedOnNewVersion(controllerId, version));
|
||||
assertAllTargetsCount(existingTargetsAfterCreation);
|
||||
assertThat(registeredTarget.getUpdateStatus()).isEqualTo(expectedTargetStatus);
|
||||
@@ -315,7 +322,7 @@ public abstract class AbstractAmqpServiceIntegrationTest extends AbstractAmqpInt
|
||||
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 MessageProperties messageProperties = createMessagePropertiesWithTenant(tenant);
|
||||
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());
|
||||
|
||||
final DmfActionUpdateStatus dmfActionUpdateStatus = new DmfActionUpdateStatus(actionId, status);
|
||||
return createMessage(dmfActionUpdateStatus, messageProperties);
|
||||
getDmfClient().send(createMessage(dmfActionUpdateStatus, messageProperties));
|
||||
}
|
||||
|
||||
protected MessageProperties createMessagePropertiesWithTenant(final String tenant) {
|
||||
|
||||
@@ -8,13 +8,30 @@
|
||||
*/
|
||||
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.Entry;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.hawkbit.dmf.amqp.api.EventTopic;
|
||||
import org.eclipse.hawkbit.dmf.amqp.api.MessageHeaderKey;
|
||||
import org.eclipse.hawkbit.dmf.json.model.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.TargetAttributesRequestedEvent;
|
||||
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.CancelTargetAssignmentEvent;
|
||||
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.SoftwareModuleUpdatedEvent;
|
||||
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.model.DistributionSet;
|
||||
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.TargetUpdateStatus;
|
||||
import org.eclipse.hawkbit.repository.test.matcher.Expect;
|
||||
import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents;
|
||||
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.amqp.core.Message;
|
||||
@@ -42,11 +66,6 @@ import io.qameta.allure.Description;
|
||||
import io.qameta.allure.Feature;
|
||||
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")
|
||||
@Story("Amqp Message Dispatcher Service")
|
||||
public class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpServiceIntegrationTest {
|
||||
@@ -131,14 +150,222 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpSer
|
||||
final DistributionSet distributionSet2 = testdataFactory.createDistributionSet(UUID.randomUUID().toString());
|
||||
registerTargetAndAssignDistributionSet(distributionSet2.getId(), TargetUpdateStatus.PENDING,
|
||||
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);
|
||||
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
|
||||
@Description("Verify that a cancel assignment send a cancel message.")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
|
||||
@@ -154,7 +381,7 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpSer
|
||||
|
||||
final Long actionId = registerTargetAndCancelActionId(controllerId);
|
||||
|
||||
createAndSendTarget(controllerId, TENANT_EXIST);
|
||||
createAndSendThingCreated(controllerId, TENANT_EXIST);
|
||||
waitUntilTargetHasStatus(controllerId, TargetUpdateStatus.PENDING);
|
||||
assertCancelActionMessage(actionId, controllerId);
|
||||
}
|
||||
@@ -171,39 +398,28 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpSer
|
||||
assertDeleteMessage(controllerId);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
@Description("Verify that attribute update is requested after device successfully closed software update.")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
|
||||
@Expect(type = TargetAssignDistributionSetEvent.class, count = 2),
|
||||
@Expect(type = ActionUpdatedEvent.class, count = 2), @Expect(type = ActionCreatedEvent.class, count = 2),
|
||||
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3),
|
||||
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
|
||||
@Expect(type = SoftwareModuleCreatedEvent.class, count = 6),
|
||||
@Expect(type = DistributionSetCreatedEvent.class, count = 2),
|
||||
@Expect(type = TargetUpdatedEvent.class, count = 4),
|
||||
@Expect(type = TargetAttributesRequestedEvent.class, count = 1),
|
||||
@Expect(type = TargetPollEvent.class, count = 1) })
|
||||
public void attributeRequestAfterSuccessfulUpdate() {
|
||||
final String controllerId = TARGET_PREFIX + "attributeUpdateRequest";
|
||||
registerAndAssertTargetWithExistingTenant(controllerId);
|
||||
final Target target = controllerManagement.getByControllerId(controllerId).get();
|
||||
final DistributionSet distributionSet = testdataFactory.createDistributionSet(UUID.randomUUID().toString());
|
||||
|
||||
final long actionId1 = assignDistributionSet(distributionSet, target).getActions().get(0);
|
||||
waitUntilTargetHasStatus(controllerId, TargetUpdateStatus.PENDING);
|
||||
final Message messageError = createActionStatusUpdateMessage(controllerId, TENANT_EXIST, actionId1,
|
||||
DmfActionStatus.ERROR);
|
||||
getDmfClient().send(messageError);
|
||||
final long actionId1 = assignNewDsToTarget(controllerId);
|
||||
updateActionViaDmfClient(controllerId, actionId1, DmfActionStatus.ERROR);
|
||||
waitUntilTargetHasStatus(controllerId, TargetUpdateStatus.ERROR);
|
||||
|
||||
assertRequestAttributesUpdateMessageAbsent();
|
||||
|
||||
final long actionId2 = assignDistributionSet(distributionSet, target).getActions().get(0);
|
||||
waitUntilTargetHasStatus(controllerId, TargetUpdateStatus.PENDING);
|
||||
final Message messageFin = createActionStatusUpdateMessage(controllerId, TENANT_EXIST, actionId2,
|
||||
DmfActionStatus.FINISHED);
|
||||
getDmfClient().send(messageFin);
|
||||
final long actionId2 = assignNewDsToTarget(controllerId);
|
||||
updateActionViaDmfClient(controllerId, actionId2, DmfActionStatus.FINISHED);
|
||||
waitUntilTargetHasStatus(controllerId, TargetUpdateStatus.IN_SYNC);
|
||||
|
||||
assertRequestAttributesUpdateMessage(controllerId);
|
||||
}
|
||||
|
||||
@@ -220,7 +436,6 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpSer
|
||||
final String controllerId = TARGET_PREFIX + "registerTargets_1";
|
||||
final DistributionSet distributionSet = createTargetAndDistributionSetAndAssign(controllerId, DOWNLOAD_ONLY);
|
||||
|
||||
// verify
|
||||
final Message message = assertReplyMessageHeader(EventTopic.DOWNLOAD, controllerId);
|
||||
Mockito.verifyZeroInteractions(getDeadletterListener());
|
||||
|
||||
@@ -249,4 +464,54 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpSer
|
||||
private void waitUntil(final Callable<Boolean> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) })
|
||||
public void registerEmptyTarget() {
|
||||
createAndSendTarget("", TENANT_EXIST);
|
||||
createAndSendThingCreated("", TENANT_EXIST);
|
||||
assertAllTargetsCount(0);
|
||||
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")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) })
|
||||
public void registerWhitespaceTarget() {
|
||||
createAndSendTarget("Invalid Invalid", TENANT_EXIST);
|
||||
createAndSendThingCreated("Invalid Invalid", TENANT_EXIST);
|
||||
assertAllTargetsCount(0);
|
||||
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")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) })
|
||||
public void registerInvalidNullTargets() {
|
||||
createAndSendTarget(null, TENANT_EXIST);
|
||||
createAndSendThingCreated(null, TENANT_EXIST);
|
||||
assertAllTargetsCount(0);
|
||||
verifyOneDeadLetterMessage();
|
||||
|
||||
@@ -571,13 +571,13 @@ public class AmqpMessageHandlerServiceIntegrationTest extends AbstractAmqpServic
|
||||
final DistributionSet distributionSet = testdataFactory.createDistributionSet(UUID.randomUUID().toString());
|
||||
final DistributionSetAssignmentResult distributionSetAssignmentResult = assignDistributionSet(
|
||||
distributionSet.getId(), controllerId);
|
||||
deploymentManagement.cancelAction(distributionSetAssignmentResult.getActions().get(0));
|
||||
deploymentManagement.cancelAction(distributionSetAssignmentResult.getActionIds().get(0));
|
||||
|
||||
// test
|
||||
registerSameTargetAndAssertBasedOnVersion(controllerId, 1, TargetUpdateStatus.PENDING);
|
||||
|
||||
// verify
|
||||
assertCancelActionMessage(distributionSetAssignmentResult.getActions().get(0), controllerId);
|
||||
assertCancelActionMessage(distributionSetAssignmentResult.getActionIds().get(0), controllerId);
|
||||
Mockito.verifyZeroInteractions(getDeadletterListener());
|
||||
}
|
||||
|
||||
@@ -908,7 +908,7 @@ public class AmqpMessageHandlerServiceIntegrationTest extends AbstractAmqpServic
|
||||
|
||||
private Long registerTargetAndSendActionStatus(final DmfActionStatus sendActionStatus, final String 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));
|
||||
return actionId;
|
||||
}
|
||||
|
||||
@@ -10,8 +10,11 @@ package org.eclipse.hawkbit.integration.listener;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
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 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> pingResponseMessages = new HashMap<>();
|
||||
|
||||
@Override
|
||||
@RabbitListener(id = LISTENER_ID, queues = REPLY_TO_QUEUE)
|
||||
public void handleMessage(final Message message) {
|
||||
|
||||
final MessageType messageType = MessageType
|
||||
.valueOf(message.getMessageProperties().getHeaders().get(MessageHeaderKey.TYPE).toString());
|
||||
|
||||
if (messageType == MessageType.EVENT) {
|
||||
final EventTopic eventTopic = EventTopic
|
||||
.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;
|
||||
}
|
||||
|
||||
@@ -62,8 +70,28 @@ public class ReplyToListener implements TestRabbitListener {
|
||||
|
||||
}
|
||||
|
||||
public Map<EventTopic, Message> getEventTopicMessages() {
|
||||
return eventTopicMessages;
|
||||
public void purge() {
|
||||
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() {
|
||||
|
||||
@@ -17,25 +17,35 @@ public enum EventTopic {
|
||||
* Topic when sending and receiving a update status.
|
||||
*/
|
||||
UPDATE_ACTION_STATUS,
|
||||
|
||||
/**
|
||||
* Topic when sending and receiving a download and install task.
|
||||
*/
|
||||
DOWNLOAD_AND_INSTALL,
|
||||
|
||||
/**
|
||||
* Topic when sending and receiving a cancel download task.
|
||||
*/
|
||||
CANCEL_DOWNLOAD,
|
||||
|
||||
/**
|
||||
* Topic when updating device attributes.
|
||||
*/
|
||||
UPDATE_ATTRIBUTES,
|
||||
|
||||
/**
|
||||
* Topic when sending a download only task, skipping the install.
|
||||
*/
|
||||
DOWNLOAD,
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -151,6 +151,19 @@ public interface ControllerManagement {
|
||||
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
|
||||
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
|
||||
* attributes.
|
||||
|
||||
@@ -104,6 +104,33 @@ public interface DeploymentManagement {
|
||||
DistributionSetAssignmentResult assignDistributionSet(long dsID,
|
||||
@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
|
||||
* their IDs with a specific {@link ActionType} and an action message.
|
||||
|
||||
@@ -32,6 +32,11 @@ public final class RepositoryConstants {
|
||||
*/
|
||||
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
|
||||
* {@link Action}.
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -26,12 +26,16 @@ public class DistributionSetAssignmentResult extends AssignmentResult<Target> {
|
||||
private final List<String> assignedTargets;
|
||||
private final List<Action> actions;
|
||||
|
||||
private final DistributionSet distributionSet;
|
||||
|
||||
private final TargetManagement targetManagement;
|
||||
|
||||
/**
|
||||
*
|
||||
* Constructor.
|
||||
*
|
||||
* @param distributionSet
|
||||
* that has been assigned
|
||||
* @param assignedTargets
|
||||
* the target objects which have been assigned to the
|
||||
* distribution set
|
||||
@@ -39,37 +43,53 @@ public class DistributionSetAssignmentResult extends AssignmentResult<Target> {
|
||||
* count of the assigned targets
|
||||
* @param alreadyAssigned
|
||||
* the count of the already assigned targets
|
||||
* @param targetManagement
|
||||
* to retrieve the assigned targets
|
||||
* @param actions
|
||||
* of the assignment
|
||||
*
|
||||
* @param targetManagement
|
||||
* to retrieve the assigned targets
|
||||
*/
|
||||
public DistributionSetAssignmentResult(final List<String> assignedTargets, final int assigned,
|
||||
final int alreadyAssigned, final List<Action> actions, final TargetManagement targetManagement) {
|
||||
public DistributionSetAssignmentResult(final DistributionSet distributionSet, final List<String> assignedTargets,
|
||||
final int assigned, final int alreadyAssigned, final List<Action> actions,
|
||||
final TargetManagement targetManagement) {
|
||||
super(assigned, alreadyAssigned, 0, Collections.emptyList(), Collections.emptyList());
|
||||
this.distributionSet = distributionSet;
|
||||
this.assignedTargets = assignedTargets;
|
||||
this.actions = actions;
|
||||
this.targetManagement = targetManagement;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The distribution set that has been assigned
|
||||
*/
|
||||
public DistributionSet getDistributionSet() {
|
||||
return distributionSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the actionIds
|
||||
*/
|
||||
public List<Long> getActions() {
|
||||
public List<Long> getActionIds() {
|
||||
if (actions == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
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
|
||||
public List<Target> getAssignedEntity() {
|
||||
if (CollectionUtils.isEmpty(assignedTargets)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return targetManagement.getByControllerID(assignedTargets);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ package org.eclipse.hawkbit.repository.model;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* represents a tenant configuration value including some meta data
|
||||
* Represents a tenant configuration value including some meta data
|
||||
*
|
||||
* @param <T>
|
||||
* type of the configuration value
|
||||
|
||||
@@ -142,6 +142,11 @@ public class TenantConfigurationProperties {
|
||||
*/
|
||||
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 defaultValue = "";
|
||||
private Class<?> dataType = String.class;
|
||||
|
||||
@@ -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.DistributionSetTypeDeletedEvent;
|
||||
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.RolloutGroupDeletedEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.SoftwareModuleDeletedEvent;
|
||||
@@ -132,6 +133,9 @@ public class EventType {
|
||||
|
||||
// target attributes requested flag
|
||||
TYPES.put(37, TargetAttributesRequestedEvent.class);
|
||||
|
||||
// deployment event for assignments and /or cancellations
|
||||
TYPES.put(38, MultiActionEvent.class);
|
||||
}
|
||||
|
||||
private int value;
|
||||
|
||||
@@ -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.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
|
||||
|
||||
@@ -17,7 +17,6 @@ import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.hawkbit.repository.QuotaManagement;
|
||||
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.TargetUpdatedEvent;
|
||||
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.Status;
|
||||
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.TargetWithActionType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.cloud.bus.BusProperties;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* {@link DistributionSet} to {@link Target} assignment strategy as utility for
|
||||
@@ -51,7 +50,7 @@ public abstract class AbstractDsAssignmentStrategy {
|
||||
protected final AfterTransactionCommitExecutor afterCommit;
|
||||
protected final ApplicationEventPublisher eventPublisher;
|
||||
protected final BusProperties bus;
|
||||
private final ActionRepository actionRepository;
|
||||
protected final ActionRepository actionRepository;
|
||||
private final ActionStatusRepository actionStatusRepository;
|
||||
private final QuotaManagement quotaManagement;
|
||||
|
||||
@@ -79,6 +78,13 @@ public abstract class AbstractDsAssignmentStrategy {
|
||||
*/
|
||||
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.
|
||||
*
|
||||
@@ -89,8 +95,8 @@ public abstract class AbstractDsAssignmentStrategy {
|
||||
* @param currentUser
|
||||
* for auditing
|
||||
*/
|
||||
abstract void updateTargetStatus(final JpaDistributionSet distributionSet, final List<List<Long>> targetIds,
|
||||
final String currentUser);
|
||||
abstract void setAssignedDistributionSetAndTargetStatus(final JpaDistributionSet distributionSet,
|
||||
final List<List<Long>> targetIds, final String currentUser);
|
||||
|
||||
/**
|
||||
* Cancels actions that can be canceled (i.e.
|
||||
@@ -112,35 +118,12 @@ public abstract class AbstractDsAssignmentStrategy {
|
||||
*
|
||||
* @param targetIds
|
||||
* to cancel actions for
|
||||
* @return {@link Set} of {@link Target#getId()}s
|
||||
*/
|
||||
abstract void closeActiveActions(List<List<Long>> targetIds);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
abstract void sendDeploymentEvents(final DistributionSetAssignmentResult assignmentResult);
|
||||
|
||||
protected 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())));
|
||||
}
|
||||
abstract void sendDeploymentEvents(final List<DistributionSetAssignmentResult> assignmentResults);
|
||||
|
||||
protected void sendTargetUpdatedEvent(final JpaTarget target) {
|
||||
afterCommit.afterCommit(() -> eventPublisher.publishEvent(new TargetUpdatedEvent(target, bus.getId())));
|
||||
|
||||
@@ -359,6 +359,14 @@ public class JpaControllerManagement implements ControllerManagement {
|
||||
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
|
||||
public Optional<Action> findActionWithDetails(final long actionId) {
|
||||
return actionRepository.getById(actionId);
|
||||
@@ -647,7 +655,7 @@ public class JpaControllerManagement implements ControllerManagement {
|
||||
return null;
|
||||
}
|
||||
|
||||
JpaTarget target = (JpaTarget) action.getTarget();
|
||||
final JpaTarget target = (JpaTarget) action.getTarget();
|
||||
action.setActive(false);
|
||||
action.setStatus(DOWNLOADED);
|
||||
target.setUpdateStatus(TargetUpdateStatus.IN_SYNC);
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
*/
|
||||
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.Collection;
|
||||
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.security.SystemSecurityContext;
|
||||
import org.eclipse.hawkbit.tenancy.TenantAware;
|
||||
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
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.annotation.Isolation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
@@ -123,9 +125,6 @@ public class JpaDeploymentManagement implements DeploymentManagement {
|
||||
private final ActionStatusRepository actionStatusRepository;
|
||||
private final TargetManagement targetManagement;
|
||||
private final AuditorAware<String> auditorProvider;
|
||||
private final ApplicationEventPublisher eventPublisher;
|
||||
private final BusProperties bus;
|
||||
private final AfterTransactionCommitExecutor afterCommit;
|
||||
private final VirtualPropertyReplacer virtualPropertyReplacer;
|
||||
private final PlatformTransactionManager txManager;
|
||||
private final OnlineDsAssignmentStrategy onlineDsAssignmentStrategy;
|
||||
@@ -151,13 +150,10 @@ public class JpaDeploymentManagement implements DeploymentManagement {
|
||||
this.actionStatusRepository = actionStatusRepository;
|
||||
this.targetManagement = targetManagement;
|
||||
this.auditorProvider = auditorProvider;
|
||||
this.eventPublisher = eventPublisher;
|
||||
this.bus = bus;
|
||||
this.afterCommit = afterCommit;
|
||||
this.virtualPropertyReplacer = virtualPropertyReplacer;
|
||||
this.txManager = txManager;
|
||||
onlineDsAssignmentStrategy = new OnlineDsAssignmentStrategy(targetRepository, afterCommit, eventPublisher, bus,
|
||||
actionRepository, actionStatusRepository, quotaManagement);
|
||||
actionRepository, actionStatusRepository, quotaManagement, this::isMultiAssignmentsEnabled);
|
||||
offlineDsAssignmentStrategy = new OfflineDsAssignmentStrategy(targetRepository, afterCommit, eventPublisher,
|
||||
bus, actionRepository, actionStatusRepository, quotaManagement);
|
||||
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))
|
||||
public DistributionSetAssignmentResult offlineAssignedDistributionSet(final Long dsID,
|
||||
final Collection<String> controllerIDs) {
|
||||
return assignDistributionSetToTargets(dsID,
|
||||
final DistributionSetAssignmentResult result = assignDistributionSetToTargets(dsID,
|
||||
controllerIDs.stream()
|
||||
.map(controllerId -> new TargetWithActionType(controllerId, ActionType.FORCED, -1))
|
||||
.collect(Collectors.toList()),
|
||||
null, offlineDsAssignmentStrategy);
|
||||
offlineDsAssignmentStrategy.sendDeploymentEvents(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -187,12 +185,13 @@ public class JpaDeploymentManagement implements DeploymentManagement {
|
||||
public DistributionSetAssignmentResult assignDistributionSet(final long dsID, final ActionType actionType,
|
||||
final long forcedTimestamp, final Collection<String> controllerIDs) {
|
||||
|
||||
return assignDistributionSetToTargets(dsID,
|
||||
final DistributionSetAssignmentResult result = assignDistributionSetToTargets(dsID,
|
||||
controllerIDs.stream()
|
||||
.map(controllerId -> new TargetWithActionType(controllerId, actionType, forcedTimestamp))
|
||||
.collect(Collectors.toList()),
|
||||
null, onlineDsAssignmentStrategy);
|
||||
|
||||
onlineDsAssignmentStrategy.sendDeploymentEvents(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -202,7 +201,21 @@ public class JpaDeploymentManagement implements DeploymentManagement {
|
||||
public DistributionSetAssignmentResult assignDistributionSet(final long dsID,
|
||||
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
|
||||
@@ -212,7 +225,10 @@ public class JpaDeploymentManagement implements DeploymentManagement {
|
||||
public DistributionSetAssignmentResult assignDistributionSet(final long dsID,
|
||||
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
|
||||
entityManager.detach(distributionSetEntity);
|
||||
// return with nothing as all targets had the DS already assigned
|
||||
return new DistributionSetAssignmentResult(Collections.emptyList(), 0, targetsWithActionType.size(),
|
||||
Collections.emptyList(), targetManagement);
|
||||
return new DistributionSetAssignmentResult(distributionSetEntity, Collections.emptyList(), 0,
|
||||
targetsWithActionType.size(), Collections.emptyList(), targetManagement);
|
||||
}
|
||||
|
||||
// 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()),
|
||||
Constants.MAX_ENTRIES_IN_STATEMENT);
|
||||
|
||||
// override all active actions and set them into canceling state, we
|
||||
// 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);
|
||||
closeOrCancelActiveActions(assignmentStrategy, targetEntitiesIdsChunks);
|
||||
// cancel all scheduled actions which are in-active, these actions were
|
||||
// not active before and the manual assignment which has been done
|
||||
// cancels them
|
||||
@@ -291,10 +302,9 @@ public class JpaDeploymentManagement implements DeploymentManagement {
|
||||
// action history.
|
||||
createActionsStatus(controllerIdsToActions.values(), assignmentStrategy, actionMessage);
|
||||
|
||||
detachEntitiesAndSendAssignmentEvents(distributionSetEntity, targetEntities, assignmentStrategy,
|
||||
cancelingTargetEntitiesIds, controllerIdsToActions);
|
||||
detachEntitiesAndSendTargetUpdatedEvents(distributionSetEntity, targetEntities, assignmentStrategy);
|
||||
|
||||
return new DistributionSetAssignmentResult(
|
||||
return new DistributionSetAssignmentResult(distributionSetEntity,
|
||||
targetEntities.stream().map(Target::getControllerId).collect(Collectors.toList()),
|
||||
targetEntities.size(), controllerIDs.size() - targetEntities.size(),
|
||||
Lists.newArrayList(controllerIdsToActions.values()), targetManagement);
|
||||
@@ -340,34 +350,37 @@ public class JpaDeploymentManagement implements DeploymentManagement {
|
||||
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) {
|
||||
|
||||
if (isMultiAssignmentsEnabled()) {
|
||||
LOG.debug("Multi Assignments feature is enabled: No need to close /cancel active actions.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (isActionsAutocloseEnabled()) {
|
||||
assignmentStrategy.closeActiveActions(targetIdsChunks);
|
||||
return Collections.emptySet();
|
||||
} 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
|
||||
@Transactional(isolation = Isolation.READ_COMMITTED)
|
||||
@Retryable(include = {
|
||||
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
|
||||
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,
|
||||
final JpaDistributionSet set, final List<List<Long>> targetIdsChunks) {
|
||||
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,
|
||||
@@ -388,15 +401,13 @@ public class JpaDeploymentManagement implements DeploymentManagement {
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
private void detachEntitiesAndSendAssignmentEvents(final JpaDistributionSet set, final List<JpaTarget> targets,
|
||||
final AbstractDsAssignmentStrategy assignmentStrategy, final Set<Long> targetIdsCancellList,
|
||||
final Map<String, JpaAction> controllerIdsToActions) {
|
||||
private void detachEntitiesAndSendTargetUpdatedEvents(final JpaDistributionSet set, final List<JpaTarget> targets,
|
||||
final AbstractDsAssignmentStrategy assignmentStrategy) {
|
||||
// detaching as it is not necessary to persist the set itself
|
||||
entityManager.detach(set);
|
||||
// detaching as the entity has been updated by the JPQL query above
|
||||
targets.forEach(entityManager::detach);
|
||||
|
||||
assignmentStrategy.sendAssignmentEvents(set, targets, targetIdsCancellList, controllerIdsToActions);
|
||||
assignmentStrategy.sendTargetUpdatedEvents(set, targets);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -421,7 +432,8 @@ public class JpaDeploymentManagement implements DeploymentManagement {
|
||||
actionStatusRepository.save(new JpaActionStatus(action, Status.CANCELING, System.currentTimeMillis(),
|
||||
RepositoryConstants.SERVER_MESSAGE_PREFIX + "manual cancelation requested"));
|
||||
final Action saveAction = actionRepository.save(action);
|
||||
onlineDsAssignmentStrategy.cancelAssignDistributionSetEvent(action.getTarget(), action.getId());
|
||||
|
||||
onlineDsAssignmentStrategy.cancelAssignment(action);
|
||||
|
||||
return saveAction;
|
||||
} else {
|
||||
@@ -481,18 +493,13 @@ public class JpaDeploymentManagement implements DeploymentManagement {
|
||||
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()
|
||||
.map(action -> (JpaAction) action).map(this::closeActionIfSetWasAlreadyAssigned)
|
||||
.filter(Objects::nonNull).map(this::startScheduledActionIfNoCancelationHasToBeHandledFirst)
|
||||
.filter(Objects::nonNull).collect(Collectors.toList());
|
||||
|
||||
if (!CollectionUtils.isEmpty(targetAssignments)) {
|
||||
afterCommit.afterCommit(() -> eventPublisher.publishEvent(new TargetAssignDistributionSetEvent(tenant,
|
||||
distributionSetId, targetAssignments, bus.getId(), maintenanceWindowAvailable)));
|
||||
if (!targetAssignments.isEmpty()) {
|
||||
onlineDsAssignmentStrategy.sendDeploymentEvents(distributionSetId, targetAssignments);
|
||||
}
|
||||
|
||||
return rolloutGroupActions.getTotalElements();
|
||||
@@ -513,11 +520,18 @@ public class JpaDeploymentManagement implements DeploymentManagement {
|
||||
}
|
||||
|
||||
private JpaAction closeActionIfSetWasAlreadyAssigned(final JpaAction action) {
|
||||
|
||||
if (isMultiAssignmentsEnabled()) {
|
||||
return action;
|
||||
}
|
||||
|
||||
final JpaTarget target = (JpaTarget) action.getTarget();
|
||||
if (target.getAssignedDistributionSet() != null
|
||||
&& action.getDistributionSet().getId().equals(target.getAssignedDistributionSet().getId())) {
|
||||
// the target has already the distribution set assigned, we don't
|
||||
// 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.setActive(false);
|
||||
setSkipActionStatus(action);
|
||||
@@ -532,15 +546,16 @@ public class JpaDeploymentManagement implements DeploymentManagement {
|
||||
// check if we need to override running update actions
|
||||
final List<Long> overrideObsoleteUpdateActions;
|
||||
|
||||
if (systemSecurityContext.runAsSystem(() -> tenantConfigurationManagement
|
||||
.getConfigurationValue(TenantConfigurationKey.REPOSITORY_ACTIONS_AUTOCLOSE_ENABLED, Boolean.class)
|
||||
.getValue())) {
|
||||
if (isMultiAssignmentsEnabled()) {
|
||||
overrideObsoleteUpdateActions = Collections.emptyList();
|
||||
onlineDsAssignmentStrategy
|
||||
.closeObsoleteUpdateActions(Collections.singletonList(action.getTarget().getId()));
|
||||
} else {
|
||||
overrideObsoleteUpdateActions = onlineDsAssignmentStrategy
|
||||
.overrideObsoleteUpdateActions(Collections.singletonList(action.getTarget().getId()));
|
||||
final List<Long> targetId = Collections.singletonList(action.getTarget().getId());
|
||||
if (isActionsAutocloseEnabled()) {
|
||||
overrideObsoleteUpdateActions = Collections.emptyList();
|
||||
onlineDsAssignmentStrategy.closeObsoleteUpdateActions(targetId);
|
||||
} else {
|
||||
overrideObsoleteUpdateActions = onlineDsAssignmentStrategy.overrideObsoleteUpdateActions(targetId);
|
||||
}
|
||||
}
|
||||
|
||||
action.setActive(true);
|
||||
@@ -782,4 +797,18 @@ public class JpaDeploymentManagement implements DeploymentManagement {
|
||||
protected ActionRepository getActionRepository() {
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,9 +8,13 @@
|
||||
*/
|
||||
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 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.model.JpaTenantConfiguration;
|
||||
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.TenantConfigurationKey;
|
||||
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.cache.annotation.CacheEvict;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
@@ -37,6 +43,8 @@ import org.springframework.validation.annotation.Validated;
|
||||
@Validated
|
||||
public class JpaTenantConfigurationManagement implements TenantConfigurationManagement {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JpaTenantConfigurationManagement.class);
|
||||
|
||||
@Autowired
|
||||
private TenantConfigurationRepository tenantConfigurationRepository;
|
||||
|
||||
@@ -150,6 +158,8 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
|
||||
tenantConfiguration.setValue(value.toString());
|
||||
}
|
||||
|
||||
assertValueChangeIsAllowed(configurationKeyName, tenantConfiguration);
|
||||
|
||||
final JpaTenantConfiguration updatedTenantConfiguration = tenantConfigurationRepository
|
||||
.save(tenantConfiguration);
|
||||
|
||||
@@ -163,6 +173,41 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
|
||||
.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
|
||||
@CacheEvict(value = "tenantConfiguration", key = "#configurationKeyName")
|
||||
@Transactional
|
||||
|
||||
@@ -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.model.Action.Status;
|
||||
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.TargetWithActionType;
|
||||
import org.springframework.cloud.bus.BusProperties;
|
||||
@@ -50,9 +51,7 @@ public class OfflineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
|
||||
}
|
||||
|
||||
@Override
|
||||
void sendAssignmentEvents(final DistributionSet set, final List<JpaTarget> targets,
|
||||
final Set<Long> targetIdsCancellList, final Map<String, JpaAction> targetIdsToActions) {
|
||||
|
||||
void sendTargetUpdatedEvents(final DistributionSet set, final List<JpaTarget> targets) {
|
||||
targets.forEach(target -> {
|
||||
target.setUpdateStatus(TargetUpdateStatus.IN_SYNC);
|
||||
sendTargetUpdatedEvent(target);
|
||||
@@ -79,7 +78,8 @@ public class OfflineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
|
||||
}
|
||||
|
||||
@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(
|
||||
TargetUpdateStatus.IN_SYNC, set, System.currentTimeMillis(), currentUser, tIds));
|
||||
}
|
||||
@@ -103,4 +103,14 @@ public class OfflineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,13 +8,21 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.jpa;
|
||||
|
||||
import static org.eclipse.hawkbit.repository.RepositoryConstants.MAX_ACTION_COUNT;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
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.executor.AfterTransactionCommitExecutor;
|
||||
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.Status;
|
||||
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.TargetUpdateStatus;
|
||||
import org.eclipse.hawkbit.repository.model.TargetWithActionType;
|
||||
import org.springframework.cloud.bus.BusProperties;
|
||||
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;
|
||||
|
||||
@@ -39,35 +51,70 @@ import com.google.common.collect.Lists;
|
||||
*/
|
||||
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,
|
||||
final AfterTransactionCommitExecutor afterCommit, final ApplicationEventPublisher eventPublisher,
|
||||
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,
|
||||
quotaManagement);
|
||||
this.multiAssignmentsConfig = multiAssignmentsConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
void sendAssignmentEvents(final DistributionSet set, final List<JpaTarget> targets,
|
||||
final Set<Long> targetIdsCancellList, final Map<String, JpaAction> targetIdsToActions) {
|
||||
|
||||
final List<Action> actions = targets.stream().map(target -> {
|
||||
void sendTargetUpdatedEvents(final DistributionSet set, final List<JpaTarget> targets) {
|
||||
targets.stream().forEach(target -> {
|
||||
target.setUpdateStatus(TargetUpdateStatus.PENDING);
|
||||
sendTargetUpdatedEvent(target);
|
||||
});
|
||||
}
|
||||
|
||||
return target;
|
||||
}).filter(target -> !targetIdsCancellList.contains(target.getId())).map(Target::getControllerId)
|
||||
.map(targetIdsToActions::get).collect(Collectors.toList());
|
||||
@Override
|
||||
void sendDeploymentEvents(final DistributionSetAssignmentResult assignmentResult) {
|
||||
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
|
||||
List<JpaTarget> findTargetsForAssignment(final List<String> controllerIDs, final long setId) {
|
||||
return Lists.partition(controllerIDs, Constants.MAX_ENTRIES_IN_STATEMENT).stream()
|
||||
.map(ids -> targetRepository
|
||||
.findAll(TargetSpecifications.hasControllerIdAndAssignedDistributionSetIdNot(ids, setId)))
|
||||
final Function<List<String>, List<JpaTarget>> mapper;
|
||||
if (isMultiAssignmentsEnabled()) {
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -83,7 +130,8 @@ public class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
|
||||
}
|
||||
|
||||
@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,
|
||||
set, System.currentTimeMillis(), currentUser, tIds));
|
||||
|
||||
@@ -106,4 +154,82 @@ public class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -223,6 +223,17 @@ public interface TargetRepository extends BaseEntityRepository<JpaTarget, Long>,
|
||||
@Query("SELECT t FROM JpaTarget t WHERE t.id IN ?1")
|
||||
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.
|
||||
|
||||
@@ -1039,7 +1039,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
|
||||
final DistributionSet testDs = testdataFactory.createDistributionSet("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)
|
||||
.status(Action.Status.RUNNING).messages(Lists.newArrayList("proceeding message 1")));
|
||||
@@ -1068,7 +1068,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
|
||||
|
||||
// test for informational status
|
||||
final Long actionId1 = assignDistributionSet(testdataFactory.createDistributionSet("ds1"),
|
||||
testdataFactory.createTargets(1, "t1")).getActions().get(0);
|
||||
testdataFactory.createTargets(1, "t1")).getActionIds().get(0);
|
||||
assertThat(actionId1).isNotNull();
|
||||
for (int i = 0; i < maxStatusEntries; ++i) {
|
||||
controllerManagement.addInformationalActionStatus(entityFactory.actionStatus().create(actionId1)
|
||||
@@ -1079,7 +1079,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
|
||||
|
||||
// test for update status (and mixed case)
|
||||
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);
|
||||
for (int i = 0; i < maxStatusEntries; ++i) {
|
||||
controllerManagement.addUpdateActionStatus(entityFactory.actionStatus().create(actionId2)
|
||||
@@ -1102,7 +1102,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
|
||||
final int maxMessages = quotaManagement.getMaxMessagesPerActionStatus();
|
||||
|
||||
final Long actionId = assignDistributionSet(testdataFactory.createDistributionSet("ds1"),
|
||||
testdataFactory.createTargets(1)).getActions().get(0);
|
||||
testdataFactory.createTargets(1)).getActionIds().get(0);
|
||||
assertThat(actionId).isNotNull();
|
||||
|
||||
final List<String> messages = Lists.newArrayList();
|
||||
@@ -1258,19 +1258,22 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
|
||||
IntStream.range(0, maxMessages).forEach(i -> controllerManagement
|
||||
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Status.DOWNLOADED)));
|
||||
fail("No QuotaExceededException thrown for too many DOWNLOADED updateActionStatus updates");
|
||||
} catch (QuotaExceededException e) { }
|
||||
} catch (final QuotaExceededException e) {
|
||||
}
|
||||
|
||||
try {
|
||||
IntStream.range(0, maxMessages).forEach(i -> controllerManagement
|
||||
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Status.ERROR)));
|
||||
fail("No QuotaExceededException thrown for too many ERROR updateActionStatus updates");
|
||||
} catch (QuotaExceededException e) { }
|
||||
} catch (final QuotaExceededException e) {
|
||||
}
|
||||
|
||||
try {
|
||||
IntStream.range(0, maxMessages).forEach(i -> controllerManagement
|
||||
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Status.FINISHED)));
|
||||
fail("No QuotaExceededException thrown for too many FINISHED updateActionStatus updates");
|
||||
} catch (QuotaExceededException e) { }
|
||||
} catch (final QuotaExceededException e) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1290,19 +1293,22 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
|
||||
IntStream.range(0, maxMessages).forEach(i -> controllerManagement
|
||||
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Status.DOWNLOADED)));
|
||||
fail("No QuotaExceededException thrown for too many DOWNLOADED updateActionStatus updates");
|
||||
} catch (QuotaExceededException e) { }
|
||||
} catch (final QuotaExceededException e) {
|
||||
}
|
||||
|
||||
try {
|
||||
IntStream.range(0, maxMessages).forEach(i -> controllerManagement
|
||||
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Status.ERROR)));
|
||||
fail("No QuotaExceededException thrown for too many ERROR updateActionStatus updates");
|
||||
} catch (QuotaExceededException e) { }
|
||||
} catch (final QuotaExceededException e) {
|
||||
}
|
||||
|
||||
try {
|
||||
IntStream.range(0, maxMessages).forEach(i -> controllerManagement
|
||||
.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Status.FINISHED)));
|
||||
fail("No QuotaExceededException thrown for too many FINISHED updateActionStatus updates");
|
||||
} catch (QuotaExceededException e) { }
|
||||
} catch (final QuotaExceededException e) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1386,7 +1392,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
|
||||
final Long forcedDistributionSetId = testdataFactory.createDistributionSet("forcedDs1").getId();
|
||||
final DistributionSetAssignmentResult assignmentResult =
|
||||
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);
|
||||
assertInstalledDistributionSetId(DEFAULT_CONTROLLER_ID, forcedDistributionSetId);
|
||||
assertNoActiveActionsExistsForControllerId(DEFAULT_CONTROLLER_ID);
|
||||
@@ -1426,4 +1432,4 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
|
||||
assertThat(actionRepository.activeActionExistsForControllerId(controllerId)).isEqualTo(false);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
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.entity.ActionCreatedEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.entity.ActionUpdatedEvent;
|
||||
@@ -85,6 +86,10 @@ import io.qameta.allure.Story;
|
||||
@Feature("Component Tests - Repository")
|
||||
@Story("Deployment Management")
|
||||
public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
|
||||
|
||||
private static final boolean STATE_ACTIVE = true;
|
||||
private static final boolean STATE_INACTIVE = false;
|
||||
|
||||
private EventHandlerStub eventHandlerStub;
|
||||
|
||||
private CancelEventHandlerStub cancelEventHandlerStub;
|
||||
@@ -146,7 +151,7 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
|
||||
new ArrayList<DistributionSetTag>());
|
||||
final List<Target> testTarget = testdataFactory.createTargets(1);
|
||||
// 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();
|
||||
|
||||
assertThat(action.getDistributionSet()).as("DistributionSet in action").isNotNull();
|
||||
@@ -163,7 +168,7 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
|
||||
new ArrayList<DistributionSetTag>());
|
||||
final List<Target> testTarget = testdataFactory.createTargets(1);
|
||||
// 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
|
||||
final Slice<Action> actions = deploymentManagement.findActionsByTarget(testTarget.get(0).getControllerId(),
|
||||
@@ -212,7 +217,7 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
|
||||
new ArrayList<DistributionSetTag>());
|
||||
final List<Target> testTarget = testdataFactory.createTargets(1);
|
||||
// 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(),
|
||||
PAGE);
|
||||
final ActionStatus expectedActionStatus = ((JpaAction) actions.getContent().get(0)).getActionStatus().get(0);
|
||||
@@ -231,7 +236,7 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
|
||||
new ArrayList<DistributionSetTag>());
|
||||
final List<Target> testTarget = testdataFactory.createTargets(1);
|
||||
// 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
|
||||
controllerManagement.addUpdateActionStatus(entityFactory.actionStatus().create(actionId)
|
||||
.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)
|
||||
.getAssignedEntity();
|
||||
assertThat(actionRepository.count()).isEqualTo(20);
|
||||
assertThat(actionRepository.findByDistributionSetId(PAGE, ds.getId()))
|
||||
.as("Offline actions are not active").allMatch(action -> !action.isActive());
|
||||
assertThat(actionRepository.findByDistributionSetId(PAGE, ds.getId())).as("Offline actions are not active")
|
||||
.allMatch(action -> !action.isActive());
|
||||
assertThat(targetManagement.findByInstalledDistributionSet(PAGE, ds.getId()).getContent()).containsAll(targets)
|
||||
.hasSize(10).containsAll(targetManagement.findByAssignedDistributionSet(PAGE, ds.getId()))
|
||||
.as("InstallationDate set").allMatch(target -> target.getInstallationDate() >= current)
|
||||
@@ -515,26 +520,14 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
|
||||
final DistributionSet ds1 = testdataFactory.createDistributionSet("1");
|
||||
assignDistributionSet(ds1, targets);
|
||||
|
||||
List<Action> assignmentOne = actionRepository.findByDistributionSetId(PAGE, ds1.getId()).getContent();
|
||||
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);
|
||||
assertDsExclusivelyAssignedToTargets(targets, ds1.getId(), STATE_ACTIVE, Status.RUNNING);
|
||||
|
||||
// Second assignment
|
||||
final DistributionSet ds2 = testdataFactory.createDistributionSet("2");
|
||||
assignDistributionSet(ds2, targets);
|
||||
|
||||
final List<Action> assignmentTwo = actionRepository.findByDistributionSetId(PAGE, ds2.getId()).getContent();
|
||||
assignmentOne = actionRepository.findByDistributionSetId(PAGE, ds1.getId()).getContent();
|
||||
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);
|
||||
assertDsExclusivelyAssignedToTargets(targets, ds2.getId(), STATE_ACTIVE, Status.RUNNING);
|
||||
assertDsExclusivelyAssignedToTargets(targets, ds1.getId(), STATE_INACTIVE, Status.CANCELED);
|
||||
|
||||
assertThat(targetManagement.findByAssignedDistributionSet(PAGE, ds2.getId()).getContent()).hasSize(10)
|
||||
.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
|
||||
* {@link TargetRepository#assignDistributionSet(DistributionSet, Iterable)}
|
||||
@@ -992,7 +1023,7 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
|
||||
ds.getId(), ActionType.SOFT,
|
||||
org.eclipse.hawkbit.repository.model.RepositoryModelConstants.NO_FORCE_TIME,
|
||||
Arrays.asList(target.getControllerId()));
|
||||
final Long actionId = assignDistributionSet.getActions().get(0);
|
||||
final Long actionId = assignDistributionSet.getActionIds().get(0);
|
||||
// verify preparation
|
||||
Action findAction = deploymentManagement.findAction(actionId).get();
|
||||
assertThat(findAction.getActionType()).as("action type is wrong").isEqualTo(ActionType.SOFT);
|
||||
@@ -1016,7 +1047,7 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
|
||||
ds.getId(), ActionType.FORCED,
|
||||
org.eclipse.hawkbit.repository.model.RepositoryModelConstants.NO_FORCE_TIME,
|
||||
Arrays.asList(target.getControllerId()));
|
||||
final Long actionId = assignDistributionSet.getActions().get(0);
|
||||
final Long actionId = assignDistributionSet.getActionIds().get(0);
|
||||
// verify perparation
|
||||
Action findAction = deploymentManagement.findAction(actionId).get();
|
||||
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.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());
|
||||
|
||||
List<Long> targetActiveActionIds = targets.stream()
|
||||
final List<Long> targetActiveActionIds = targets.stream()
|
||||
.map(t -> deploymentManagement.findActiveActionsByTarget(PAGE, t.getControllerId()).getContent())
|
||||
.flatMap(List::stream)
|
||||
.map(Action::getId)
|
||||
.collect(Collectors.toList());
|
||||
.flatMap(List::stream).map(Action::getId).collect(Collectors.toList());
|
||||
assertThat(eventActionIds).containsOnlyElementsOf(targetActiveActionIds);
|
||||
}
|
||||
|
||||
@@ -1216,4 +1245,8 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
|
||||
}
|
||||
}
|
||||
|
||||
private void enableMultiAssignments() {
|
||||
tenantConfigurationManagement.addOrUpdateConfiguration(TenantConfigurationKey.MULTI_ASSIGNMENTS_ENABLED, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
|
||||
testdataFactory.createTarget(knownControllerId);
|
||||
final DistributionSetAssignmentResult assignmentResult = deploymentManagement.assignDistributionSet(
|
||||
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
|
||||
// start rollout
|
||||
@@ -144,7 +144,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
|
||||
testdataFactory.createTarget(knownControllerId);
|
||||
final DistributionSetAssignmentResult assignmentResult = deploymentManagement.assignDistributionSet(
|
||||
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
|
||||
// start rollout
|
||||
|
||||
@@ -96,7 +96,7 @@ public class TargetManagementSearchTest extends AbstractJpaIntegrationTest {
|
||||
final String assignedB = targBs.iterator().next().getControllerId();
|
||||
assignDistributionSet(setA.getId(), assignedB);
|
||||
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
|
||||
// value and name
|
||||
@@ -701,7 +701,7 @@ public class TargetManagementSearchTest extends AbstractJpaIntegrationTest {
|
||||
List<Target> installedtargets = testdataFactory.createTargets(10, "assigned", "assigned");
|
||||
|
||||
// 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)));
|
||||
assignDistributionSet(assignedSet, installedtargets);
|
||||
|
||||
|
||||
@@ -477,7 +477,7 @@ public class TargetManagementTest extends AbstractJpaIntegrationTest {
|
||||
final DistributionSetAssignmentResult result = assignDistributionSet(set.getId(), "4711");
|
||||
|
||||
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");
|
||||
|
||||
target = targetManagement.getByControllerID("4711").get();
|
||||
|
||||
@@ -59,7 +59,7 @@ public class AutoAssignCheckerTest extends AbstractJpaIntegrationTest {
|
||||
testdataFactory.createTarget(knownControllerId);
|
||||
final DistributionSetAssignmentResult assignmentResult = deploymentManagement.assignDistributionSet(
|
||||
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
|
||||
final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement
|
||||
|
||||
@@ -81,7 +81,7 @@ public class AutoActionCleanupTest extends AbstractJpaIntegrationTest {
|
||||
final DistributionSet ds2 = testdataFactory.createDistributionSet("ds2");
|
||||
|
||||
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,
|
||||
Collections.singletonList(trg2.getControllerId()));
|
||||
|
||||
@@ -110,11 +110,11 @@ public class AutoActionCleanupTest extends AbstractJpaIntegrationTest {
|
||||
final DistributionSet ds2 = testdataFactory.createDistributionSet("ds2");
|
||||
|
||||
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,
|
||||
Collections.singletonList(trg2.getControllerId())).getActions().get(0);
|
||||
Collections.singletonList(trg2.getControllerId())).getActionIds().get(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);
|
||||
|
||||
@@ -145,11 +145,11 @@ public class AutoActionCleanupTest extends AbstractJpaIntegrationTest {
|
||||
final DistributionSet ds2 = testdataFactory.createDistributionSet("ds2");
|
||||
|
||||
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,
|
||||
Collections.singletonList(trg2.getControllerId())).getActions().get(0);
|
||||
Collections.singletonList(trg2.getControllerId())).getActionIds().get(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);
|
||||
|
||||
@@ -182,11 +182,11 @@ public class AutoActionCleanupTest extends AbstractJpaIntegrationTest {
|
||||
final DistributionSet ds2 = testdataFactory.createDistributionSet("ds2");
|
||||
|
||||
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,
|
||||
Collections.singletonList(trg2.getControllerId())).getActions().get(0);
|
||||
Collections.singletonList(trg2.getControllerId())).getActionIds().get(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);
|
||||
|
||||
|
||||
@@ -35,7 +35,6 @@ import org.junit.Test;
|
||||
import org.springframework.hateoas.MediaTypes;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.integration.json.JsonPathUtils;
|
||||
import org.springframework.test.util.JsonPathExpectationsHelper;
|
||||
|
||||
import io.qameta.allure.Description;
|
||||
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.")
|
||||
public void cancelActionCbor() throws Exception {
|
||||
final DistributionSet ds = testdataFactory.createDistributionSet("");
|
||||
final Target savedTarget = testdataFactory.createTarget();
|
||||
final Long actionId = assignDistributionSet(ds.getId(), TestdataFactory.DEFAULT_CONTROLLER_ID).getActions()
|
||||
testdataFactory.createTarget();
|
||||
final Long actionId = assignDistributionSet(ds.getId(), TestdataFactory.DEFAULT_CONTROLLER_ID).getActionIds()
|
||||
.get(0);
|
||||
final Action cancelAction = deploymentManagement.cancelAction(actionId);
|
||||
|
||||
// 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))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
|
||||
.andExpect(content().contentType(DdiRestConstants.MEDIA_TYPE_CBOR_UTF8))
|
||||
@@ -81,7 +80,7 @@ public class DdiCancelActionTest extends AbstractDDiApiIntegrationTest {
|
||||
final DistributionSet ds = testdataFactory.createDistributionSet("");
|
||||
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);
|
||||
|
||||
@@ -134,7 +133,7 @@ public class DdiCancelActionTest extends AbstractDDiApiIntegrationTest {
|
||||
final DistributionSet ds = testdataFactory.createDistributionSet("");
|
||||
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();
|
||||
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 List<Target> toAssign = new ArrayList<>();
|
||||
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);
|
||||
}
|
||||
@@ -263,7 +262,7 @@ public class DdiCancelActionTest extends AbstractDDiApiIntegrationTest {
|
||||
|
||||
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);
|
||||
|
||||
// cancel action manually
|
||||
@@ -338,11 +337,11 @@ public class DdiCancelActionTest extends AbstractDDiApiIntegrationTest {
|
||||
|
||||
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);
|
||||
final Long actionId2 = assignDistributionSet(ds2.getId(), TestdataFactory.DEFAULT_CONTROLLER_ID).getActions()
|
||||
final Long actionId2 = assignDistributionSet(ds2.getId(), TestdataFactory.DEFAULT_CONTROLLER_ID).getActionIds()
|
||||
.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);
|
||||
|
||||
assertThat(deploymentManagement.countActionStatusAll()).isEqualTo(3);
|
||||
@@ -452,7 +451,7 @@ public class DdiCancelActionTest extends AbstractDDiApiIntegrationTest {
|
||||
testdataFactory.createTarget();
|
||||
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);
|
||||
|
||||
final Action cancelAction = deploymentManagement.cancelAction(actionId);
|
||||
|
||||
@@ -267,7 +267,7 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest {
|
||||
final DistributionSet ds = testdataFactory.createDistributionSet("", true);
|
||||
|
||||
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()))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
|
||||
@@ -566,7 +566,7 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest {
|
||||
final List<Target> toAssign = Arrays.asList(target);
|
||||
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()))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
|
||||
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<>();
|
||||
toAssign.add(savedTarget1);
|
||||
|
||||
final Long actionId1 = assignDistributionSet(ds1.getId(), "4712").getActions().get(0);
|
||||
final Long actionId2 = assignDistributionSet(ds2.getId(), "4712").getActions().get(0);
|
||||
final Long actionId3 = assignDistributionSet(ds3.getId(), "4712").getActions().get(0);
|
||||
final Long actionId1 = assignDistributionSet(ds1.getId(), "4712").getActionIds().get(0);
|
||||
final Long actionId2 = assignDistributionSet(ds2.getId(), "4712").getActionIds().get(0);
|
||||
final Long actionId3 = assignDistributionSet(ds3.getId(), "4712").getActionIds().get(0);
|
||||
|
||||
Target myT = targetManagement.getByControllerID("4712").get();
|
||||
assertThat(myT.getUpdateStatus()).isEqualTo(TargetUpdateStatus.PENDING);
|
||||
|
||||
@@ -29,7 +29,9 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
||||
|
||||
import java.util.Collections;
|
||||
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.im.authentication.SpPermission;
|
||||
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
|
||||
@@ -258,9 +260,8 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
||||
etagWithFirstUpdate)).andDo(MockMvcResultPrinter.print()).andExpect(status().isNotModified());
|
||||
|
||||
// now lets finish the update
|
||||
sendDeploymentActionFeedback(target, updateAction,
|
||||
JsonBuilder.deploymentActionFeedback(updateAction.getId().toString(), "closed"))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
|
||||
sendDeploymentActionFeedback(target, updateAction, "closed", null).andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk());
|
||||
|
||||
// we are again at the original state
|
||||
mvc.perform(get("/{tenant}/controller/v1/4711", tenantAware.getCurrentTenant()).header("If-None-Match", etag))
|
||||
@@ -364,7 +365,6 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
||||
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
|
||||
@Expect(type = ActionCreatedEvent.class, count = 1), @Expect(type = ActionUpdatedEvent.class, count = 1),
|
||||
@Expect(type = TargetUpdatedEvent.class, count = 2),
|
||||
@Expect(type = TargetAttributesRequestedEvent.class, count = 1),
|
||||
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3) })
|
||||
public void tryToFinishAnUpdateProcessAfterItHasBeenFinished() throws Exception {
|
||||
final DistributionSet ds = testdataFactory.createDistributionSet("");
|
||||
@@ -373,17 +373,14 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
||||
.next();
|
||||
final Action savedAction = deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId())
|
||||
.getContent().get(0);
|
||||
sendDeploymentActionFeedback(savedTarget, savedAction,
|
||||
JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "proceeding"))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
|
||||
sendDeploymentActionFeedback(savedTarget, savedAction, "proceeding", null).andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk());
|
||||
|
||||
sendDeploymentActionFeedback(savedTarget, savedAction,
|
||||
JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "closed", "failure"))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
|
||||
sendDeploymentActionFeedback(savedTarget, savedAction, "closed", "failure").andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk());
|
||||
|
||||
sendDeploymentActionFeedback(savedTarget, savedAction,
|
||||
JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "closed", "success"))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isGone());
|
||||
sendDeploymentActionFeedback(savedTarget, savedAction, "closed", "success").andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isGone());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -419,9 +416,7 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
||||
target = assignDistributionSet(ds.getId(), target.getControllerId()).getAssignedEntity().iterator().next();
|
||||
final Action action = deploymentManagement.findActiveActionsByTarget(PAGE, target.getControllerId())
|
||||
.getContent().get(0);
|
||||
sendDeploymentActionFeedback(target, action,
|
||||
JsonBuilder.deploymentActionFeedback(action.getId().toString(), "closed", "failure", ""))
|
||||
.andExpect(status().isOk());
|
||||
sendDeploymentActionFeedback(target, action, "closed", "failure").andExpect(status().isOk());
|
||||
assertThatAttributesUpdateIsNotRequested(target.getControllerId());
|
||||
}
|
||||
|
||||
@@ -431,8 +426,7 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
||||
target = assignDistributionSet(ds.getId(), target.getControllerId()).getAssignedEntity().iterator().next();
|
||||
final Action action = deploymentManagement.findActiveActionsByTarget(PAGE, target.getControllerId())
|
||||
.getContent().get(0);
|
||||
sendDeploymentActionFeedback(target, action,
|
||||
JsonBuilder.deploymentActionFeedback(action.getId().toString(), "closed")).andExpect(status().isOk());
|
||||
sendDeploymentActionFeedback(target, action, "closed", null).andExpect(status().isOk());
|
||||
assertThatAttributesUpdateIsRequested(target.getControllerId());
|
||||
}
|
||||
|
||||
@@ -448,13 +442,26 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
||||
.andExpect(jsonPath("$._links.configData").doesNotExist());
|
||||
}
|
||||
|
||||
private ResultActions sendDeploymentActionFeedback(final Target target, final Action action, final String feedback)
|
||||
throws Exception {
|
||||
private ResultActions sendDeploymentActionFeedback(final Target target, final Action action, final String execution,
|
||||
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",
|
||||
tenantAware.getCurrentTenant(), target.getControllerId(), action.getId()).content(feedback)
|
||||
.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
|
||||
@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),
|
||||
@@ -472,20 +479,14 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
||||
final Action savedAction = deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId())
|
||||
.getContent().get(0);
|
||||
|
||||
sendDeploymentActionFeedback(savedTarget, savedAction,
|
||||
JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "scheduled",
|
||||
TARGET_SCHEDULED_INSTALLATION_MSG)).andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk());
|
||||
sendDeploymentActionFeedback(savedTarget, savedAction, "scheduled", null, TARGET_SCHEDULED_INSTALLATION_MSG)
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
|
||||
|
||||
sendDeploymentActionFeedback(savedTarget, savedAction,
|
||||
JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "proceeding",
|
||||
TARGET_PROCEEDING_INSTALLATION_MSG)).andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk());
|
||||
sendDeploymentActionFeedback(savedTarget, savedAction, "proceeding", null, TARGET_PROCEEDING_INSTALLATION_MSG)
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
|
||||
|
||||
sendDeploymentActionFeedback(savedTarget, savedAction,
|
||||
JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "closed", "success",
|
||||
TARGET_COMPLETED_INSTALLATION_MSG)).andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk());
|
||||
sendDeploymentActionFeedback(savedTarget, savedAction, "closed", "success", TARGET_COMPLETED_INSTALLATION_MSG)
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
|
||||
|
||||
mvc.perform(get("/{tenant}/controller/v1/911/deploymentBase/" + savedAction.getId() + "?actionHistory=3",
|
||||
tenantAware.getCurrentTenant()).contentType(MediaType.APPLICATION_JSON)
|
||||
@@ -514,20 +515,14 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
||||
final Action savedAction = deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId())
|
||||
.getContent().get(0);
|
||||
|
||||
sendDeploymentActionFeedback(savedTarget, savedAction,
|
||||
JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "scheduled",
|
||||
TARGET_SCHEDULED_INSTALLATION_MSG)).andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk());
|
||||
sendDeploymentActionFeedback(savedTarget, savedAction, "scheduled", null, TARGET_SCHEDULED_INSTALLATION_MSG)
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
|
||||
|
||||
sendDeploymentActionFeedback(savedTarget, savedAction,
|
||||
JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "proceeding",
|
||||
TARGET_PROCEEDING_INSTALLATION_MSG)).andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk());
|
||||
sendDeploymentActionFeedback(savedTarget, savedAction, "proceeding", null, TARGET_PROCEEDING_INSTALLATION_MSG)
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
|
||||
|
||||
sendDeploymentActionFeedback(savedTarget, savedAction,
|
||||
JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "closed", "success",
|
||||
TARGET_COMPLETED_INSTALLATION_MSG)).andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk());
|
||||
sendDeploymentActionFeedback(savedTarget, savedAction, "closed", "success", TARGET_COMPLETED_INSTALLATION_MSG)
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
|
||||
|
||||
mvc.perform(get("/{tenant}/controller/v1/911/deploymentBase/" + savedAction.getId() + "?actionHistory=-2",
|
||||
tenantAware.getCurrentTenant()).contentType(MediaType.APPLICATION_JSON)
|
||||
@@ -552,20 +547,14 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
||||
final Action savedAction = deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId())
|
||||
.getContent().get(0);
|
||||
|
||||
sendDeploymentActionFeedback(savedTarget, savedAction,
|
||||
JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "scheduled",
|
||||
TARGET_SCHEDULED_INSTALLATION_MSG)).andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk());
|
||||
sendDeploymentActionFeedback(savedTarget, savedAction, "scheduled", null, TARGET_SCHEDULED_INSTALLATION_MSG)
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
|
||||
|
||||
sendDeploymentActionFeedback(savedTarget, savedAction,
|
||||
JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "proceeding",
|
||||
TARGET_PROCEEDING_INSTALLATION_MSG)).andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk());
|
||||
sendDeploymentActionFeedback(savedTarget, savedAction, "proceeding", null, TARGET_PROCEEDING_INSTALLATION_MSG)
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
|
||||
|
||||
sendDeploymentActionFeedback(savedTarget, savedAction,
|
||||
JsonBuilder.deploymentActionFeedback(savedAction.getId().toString(), "closed", "success",
|
||||
TARGET_COMPLETED_INSTALLATION_MSG)).andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk());
|
||||
sendDeploymentActionFeedback(savedTarget, savedAction, "closed", "success", TARGET_COMPLETED_INSTALLATION_MSG)
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk());
|
||||
|
||||
mvc.perform(get("/{tenant}/controller/v1/911/deploymentBase/" + savedAction.getId() + "?actionHistory=-1",
|
||||
tenantAware.getCurrentTenant()).contentType(MediaType.APPLICATION_JSON)
|
||||
@@ -665,4 +654,34 @@ public class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
||||
.andExpect(jsonPath("$.deployment.update", equalTo("forced")))
|
||||
.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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1239,7 +1239,7 @@ public class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest
|
||||
final DistributionSet set = testdataFactory.createDistributionSet();
|
||||
final Long actionId = deploymentManagement
|
||||
.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);
|
||||
|
||||
final String body = new JSONObject().put("forceType", "forced").toString();
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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_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_CONFIGURATION_VALUE_CHANGE_NOT_ALLOWED, HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
private static HttpStatus getStatusOrDefault(final SpServerError error) {
|
||||
|
||||
@@ -138,7 +138,7 @@ public class RootControllerDocumentationTest extends AbstractApiRestDocumentatio
|
||||
|
||||
final Target target = targetManagement.create(entityFactory.target().create().controllerId(CONTROLLER_ID));
|
||||
final Long actionId = deploymentManagement
|
||||
.assignDistributionSet(set.getId(), Arrays.asList(target.getTargetWithActionType())).getActions()
|
||||
.assignDistributionSet(set.getId(), Arrays.asList(target.getTargetWithActionType())).getActionIds()
|
||||
.get(0);
|
||||
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 Long actionId = deploymentManagement
|
||||
.assignDistributionSet(set.getId(), Arrays.asList(target.getTargetWithActionType())).getActions()
|
||||
.assignDistributionSet(set.getId(), Arrays.asList(target.getTargetWithActionType())).getActionIds()
|
||||
.get(0);
|
||||
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 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(
|
||||
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 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(
|
||||
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 Long actionId = deploymentManagement
|
||||
.assignDistributionSet(set.getId(), Arrays.asList(target.getTargetWithActionType())).getActions()
|
||||
.assignDistributionSet(set.getId(), Arrays.asList(target.getTargetWithActionType())).getActionIds()
|
||||
.get(0);
|
||||
|
||||
mockMvc.perform(post(DdiRestConstants.BASE_V1_REQUEST_MAPPING + "/{controllerId}/"
|
||||
|
||||
@@ -397,7 +397,7 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
|
||||
final DistributionSet set = testdataFactory.createDistributionSet();
|
||||
final Long actionId = deploymentManagement
|
||||
.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);
|
||||
|
||||
final Map<String, Object> body = new HashMap<>();
|
||||
|
||||
@@ -88,6 +88,8 @@ public class TenantResourceDocumentationTest extends AbstractApiRestDocumentatio
|
||||
"the list of action status that should be taken into account for the cleanup.");
|
||||
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.");
|
||||
CONFIG_ITEM_DESCRIPTIONS.put(TenantConfigurationKey.MULTI_ASSIGNMENTS_ENABLED,
|
||||
"if multiple distribution sets can be assigned to the same targets.");
|
||||
}
|
||||
|
||||
@Autowired
|
||||
|
||||
@@ -209,6 +209,8 @@ public class UiProperties implements Serializable {
|
||||
*/
|
||||
private String rolloutView = "";
|
||||
|
||||
private String provisioningStateMachine = "";
|
||||
|
||||
public String getDeploymentView() {
|
||||
return deploymentView;
|
||||
}
|
||||
@@ -249,6 +251,10 @@ public class UiProperties implements Serializable {
|
||||
return maintenanceWindowView;
|
||||
}
|
||||
|
||||
public String getProvisioningStateMachine() {
|
||||
return provisioningStateMachine;
|
||||
}
|
||||
|
||||
public void setDeploymentView(final String deploymentView) {
|
||||
this.deploymentView = deploymentView;
|
||||
}
|
||||
@@ -289,6 +295,10 @@ public class UiProperties implements Serializable {
|
||||
this.maintenanceWindowView = maintenanceWindowView;
|
||||
}
|
||||
|
||||
public void setProvisioningStateMachine(final String provisioningStateMachine) {
|
||||
this.provisioningStateMachine = provisioningStateMachine;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final Documentation documentation = new Documentation();
|
||||
|
||||
@@ -12,7 +12,9 @@ import java.util.Optional;
|
||||
|
||||
import org.eclipse.hawkbit.repository.DistributionSetManagement;
|
||||
import org.eclipse.hawkbit.repository.DistributionSetTagManagement;
|
||||
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||
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.common.tagdetails.DistributionTagToken;
|
||||
import org.eclipse.hawkbit.ui.components.SPUIComponentProvider;
|
||||
@@ -56,6 +58,8 @@ public abstract class AbstractDistributionSetDetails
|
||||
|
||||
private final SoftwareModuleDetailsTable softwareModuleDetailsTable;
|
||||
|
||||
private final transient TenantConfigurationManagement tenantConfigurationManagement;
|
||||
|
||||
private VerticalLayout softwareModuleTab;
|
||||
|
||||
protected AbstractDistributionSetDetails(final VaadinMessageSource i18n, final UIEventBus eventBus,
|
||||
@@ -64,7 +68,8 @@ public abstract class AbstractDistributionSetDetails
|
||||
final DistributionSetManagement distributionSetManagement,
|
||||
final DsMetadataPopupLayout dsMetadataPopupLayout, final UINotification uiNotification,
|
||||
final DistributionSetTagManagement distributionSetTagManagement,
|
||||
final SoftwareModuleDetailsTable softwareModuleDetailsTable) {
|
||||
final SoftwareModuleDetailsTable softwareModuleDetailsTable,
|
||||
final TenantConfigurationManagement tenantConfigurationManagement) {
|
||||
super(i18n, eventBus, permissionChecker, managementUIState);
|
||||
this.distributionAddUpdateWindowLayout = distributionAddUpdateWindowLayout;
|
||||
this.uiNotification = uiNotification;
|
||||
@@ -73,6 +78,7 @@ public abstract class AbstractDistributionSetDetails
|
||||
this.distributionTagToken = new DistributionTagToken(permissionChecker, i18n, uiNotification, eventBus,
|
||||
managementUIState, distributionSetTagManagement, distributionSetManagement);
|
||||
this.softwareModuleDetailsTable = softwareModuleDetailsTable;
|
||||
this.tenantConfigurationManagement = tenantConfigurationManagement;
|
||||
|
||||
dsMetadataTable = new DistributionSetMetadataDetailsLayout(i18n, distributionSetManagement,
|
||||
dsMetadataPopupLayout);
|
||||
@@ -175,9 +181,19 @@ public abstract class AbstractDistributionSetDetails
|
||||
typeLabel.setId(UIComponentIdProvider.DETAILS_TYPE_LABEL_ID);
|
||||
detailsTabLayout.addComponent(typeLabel);
|
||||
|
||||
detailsTabLayout.addComponent(
|
||||
SPUIComponentProvider.createNameValueLabel(getI18n().getMessage("checkbox.dist.migration.required"),
|
||||
getMigrationRequiredValue(isMigrationRequired)));
|
||||
final Label requiredMigrationStepLabel = SPUIComponentProvider.createNameValueLabel(
|
||||
getI18n().getMessage("checkbox.dist.migration.required"),
|
||||
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) {
|
||||
|
||||
@@ -21,6 +21,7 @@ import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
|
||||
import org.eclipse.hawkbit.repository.SoftwareModuleTypeManagement;
|
||||
import org.eclipse.hawkbit.repository.SystemManagement;
|
||||
import org.eclipse.hawkbit.repository.TargetManagement;
|
||||
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||
import org.eclipse.hawkbit.ui.AbstractHawkbitUI;
|
||||
import org.eclipse.hawkbit.ui.SpPermissionChecker;
|
||||
import org.eclipse.hawkbit.ui.artifacts.event.SoftwareModuleEvent;
|
||||
@@ -102,7 +103,8 @@ public class DistributionsView extends AbstractNotificationView implements Brows
|
||||
final DistributionsViewClientCriterion distributionsViewClientCriterion,
|
||||
final ArtifactUploadState artifactUploadState, final SystemManagement systemManagement,
|
||||
final ArtifactManagement artifactManagement, final NotificationUnreadButton notificationUnreadButton,
|
||||
final DistributionsViewMenuItem distributionsViewMenuItem) {
|
||||
final DistributionsViewMenuItem distributionsViewMenuItem,
|
||||
final TenantConfigurationManagement configManagement) {
|
||||
super(eventBus, notificationUnreadButton);
|
||||
this.permChecker = permChecker;
|
||||
this.i18n = i18n;
|
||||
@@ -117,7 +119,7 @@ public class DistributionsView extends AbstractNotificationView implements Brows
|
||||
this.distributionTableLayout = new DistributionSetTableLayout(i18n, eventBus, permChecker, manageDistUIState,
|
||||
softwareModuleManagement, distributionSetManagement, distributionSetTypeManagement, targetManagement,
|
||||
entityFactory, uiNotification, distributionSetTagManagement, distributionsViewClientCriterion,
|
||||
systemManagement);
|
||||
systemManagement, configManagement);
|
||||
this.softwareModuleTableLayout = new SwModuleTableLayout(i18n, uiNotification, eventBus,
|
||||
softwareModuleManagement, softwareModuleTypeManagement, entityFactory, manageDistUIState, permChecker,
|
||||
distributionsViewClientCriterion, artifactUploadState, artifactManagement);
|
||||
|
||||
@@ -13,6 +13,7 @@ import java.util.Map;
|
||||
|
||||
import org.eclipse.hawkbit.repository.DistributionSetManagement;
|
||||
import org.eclipse.hawkbit.repository.DistributionSetTagManagement;
|
||||
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModule;
|
||||
import org.eclipse.hawkbit.ui.SpPermissionChecker;
|
||||
import org.eclipse.hawkbit.ui.artifacts.event.SoftwareModuleEvent;
|
||||
@@ -58,11 +59,12 @@ public class DistributionSetDetails extends AbstractDistributionSetDetails {
|
||||
final DistributionAddUpdateWindowLayout distributionAddUpdateWindowLayout,
|
||||
final DistributionSetManagement distributionSetManagement, final UINotification uiNotification,
|
||||
final DistributionSetTagManagement distributionSetTagManagement,
|
||||
final DsMetadataPopupLayout dsMetadataPopupLayout) {
|
||||
final DsMetadataPopupLayout dsMetadataPopupLayout, final TenantConfigurationManagement configManagement) {
|
||||
super(i18n, eventBus, permissionChecker, managementUIState, distributionAddUpdateWindowLayout,
|
||||
distributionSetManagement, dsMetadataPopupLayout, uiNotification, distributionSetTagManagement,
|
||||
createSoftwareModuleDetailsTable(i18n, permissionChecker, distributionSetManagement, eventBus,
|
||||
manageDistUIState, uiNotification));
|
||||
manageDistUIState, uiNotification),
|
||||
configManagement);
|
||||
this.manageDistUIState = manageDistUIState;
|
||||
|
||||
tfqDetailsTable = new TargetFilterQueryDetailsTable(i18n);
|
||||
|
||||
@@ -15,6 +15,7 @@ import org.eclipse.hawkbit.repository.EntityFactory;
|
||||
import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
|
||||
import org.eclipse.hawkbit.repository.SystemManagement;
|
||||
import org.eclipse.hawkbit.repository.TargetManagement;
|
||||
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||
import org.eclipse.hawkbit.ui.SpPermissionChecker;
|
||||
import org.eclipse.hawkbit.ui.common.table.AbstractTableLayout;
|
||||
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 DistributionSetTagManagement distributionSetTagManagement,
|
||||
final DistributionsViewClientCriterion distributionsViewClientCriterion,
|
||||
final SystemManagement systemManagement) {
|
||||
final SystemManagement systemManagement, final TenantConfigurationManagement configManagement) {
|
||||
|
||||
this.distributionSetTable = new DistributionSetTable(eventBus, i18n, uiNotification, permissionChecker,
|
||||
manageDistUIState, distributionSetManagement, softwareManagement, distributionsViewClientCriterion,
|
||||
@@ -49,7 +50,7 @@ public class DistributionSetTableLayout extends AbstractTableLayout<Distribution
|
||||
|
||||
final DistributionAddUpdateWindowLayout distributionAddUpdateWindowLayout = new DistributionAddUpdateWindowLayout(
|
||||
i18n, uiNotification, eventBus, distributionSetManagement, distributionSetTypeManagement,
|
||||
systemManagement, entityFactory, distributionSetTable);
|
||||
systemManagement, entityFactory, distributionSetTable, configManagement);
|
||||
|
||||
final DsMetadataPopupLayout popupLayout = new DsMetadataPopupLayout(i18n, uiNotification, eventBus,
|
||||
distributionSetManagement, entityFactory, permissionChecker);
|
||||
@@ -60,7 +61,7 @@ public class DistributionSetTableLayout extends AbstractTableLayout<Distribution
|
||||
distributionSetTable,
|
||||
new DistributionSetDetails(i18n, eventBus, permissionChecker, manageDistUIState, null,
|
||||
distributionAddUpdateWindowLayout, distributionSetManagement, uiNotification,
|
||||
distributionSetTagManagement, popupLayout));
|
||||
distributionSetTagManagement, popupLayout, configManagement));
|
||||
}
|
||||
|
||||
public DistributionSetTable getDistributionSetTable() {
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.eclipse.hawkbit.repository.SystemManagement;
|
||||
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
|
||||
import org.eclipse.hawkbit.repository.TargetManagement;
|
||||
import org.eclipse.hawkbit.repository.TargetTagManagement;
|
||||
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||
import org.eclipse.hawkbit.ui.AbstractHawkbitUI;
|
||||
import org.eclipse.hawkbit.ui.SpPermissionChecker;
|
||||
import org.eclipse.hawkbit.ui.UiProperties;
|
||||
@@ -127,6 +128,7 @@ public class DeploymentView extends AbstractNotificationView implements BrowserW
|
||||
final TargetTagManagement targetTagManagement,
|
||||
final DistributionSetTagManagement distributionSetTagManagement,
|
||||
final TargetFilterQueryManagement targetFilterQueryManagement, final SystemManagement systemManagement,
|
||||
final TenantConfigurationManagement configManagement,
|
||||
final NotificationUnreadButton notificationUnreadButton,
|
||||
final DeploymentViewMenuItem deploymentViewMenuItem, @Qualifier("uiExecutor") final Executor uiExecutor) {
|
||||
super(eventBus, notificationUnreadButton);
|
||||
@@ -147,7 +149,7 @@ public class DeploymentView extends AbstractNotificationView implements BrowserW
|
||||
targetFilterQueryManagement, targetTagManagement);
|
||||
final TargetTable targetTable = new TargetTable(eventBus, i18n, uiNotification, targetManagement,
|
||||
managementUIState, permChecker, managementViewClientCriterion, distributionSetManagement,
|
||||
targetTagManagement, deploymentManagement, uiProperties);
|
||||
targetTagManagement, deploymentManagement, configManagement, uiProperties);
|
||||
this.countMessageLabel = new CountMessageLabel(eventBus, targetManagement, i18n, managementUIState,
|
||||
targetTable);
|
||||
|
||||
@@ -175,7 +177,7 @@ public class DeploymentView extends AbstractNotificationView implements BrowserW
|
||||
this.distributionTableLayout = new DistributionTableLayout(i18n, eventBus, permChecker, managementUIState,
|
||||
distributionSetManagement, distributionSetTypeManagement, managementViewClientCriterion,
|
||||
entityFactory, uiNotification, distributionSetTagManagement, targetTagManagement, systemManagement,
|
||||
targetManagement, deploymentManagement, uiProperties);
|
||||
targetManagement, deploymentManagement, configManagement, uiProperties);
|
||||
} else {
|
||||
this.distributionTagLayout = null;
|
||||
this.distributionTableLayout = null;
|
||||
|
||||
@@ -8,9 +8,7 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.ui.management;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
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.exception.InvalidMaintenanceScheduleException;
|
||||
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.RepositoryModelConstants;
|
||||
import org.eclipse.hawkbit.repository.model.Target;
|
||||
import org.eclipse.hawkbit.repository.model.TargetWithActionType;
|
||||
import org.eclipse.hawkbit.ui.UiProperties;
|
||||
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.components.SPUIComponentProvider;
|
||||
import org.eclipse.hawkbit.ui.management.event.PinUnpinEvent;
|
||||
import org.eclipse.hawkbit.ui.management.event.SaveActionWindowEvent;
|
||||
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.MaintenanceWindowLayout;
|
||||
import org.eclipse.hawkbit.ui.management.state.ManagementUIState;
|
||||
@@ -41,7 +41,6 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.vaadin.spring.events.EventBus.UIEventBus;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.vaadin.data.Property;
|
||||
import com.vaadin.ui.CheckBox;
|
||||
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
|
||||
* the management UI state
|
||||
* @param actionTypeOptionGroupLayout
|
||||
@@ -78,79 +81,64 @@ public final class TargetAssignmentOperations {
|
||||
* @param eventSource
|
||||
* 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 MaintenanceWindowLayout maintenanceWindowLayout, final DeploymentManagement deploymentManagement,
|
||||
final UINotification notification, final UIEventBus eventBus, final VaadinMessageSource i18n,
|
||||
final Object eventSource) {
|
||||
final Set<TargetIdName> itemIds = managementUIState.getAssignedList().keySet();
|
||||
Long distId;
|
||||
List<TargetIdName> targetIdSetList;
|
||||
List<TargetIdName> tempIdList;
|
||||
final ActionType actionType = ((AbstractActionTypeOptionGroupLayout.ActionTypeOption) actionTypeOptionGroupLayout
|
||||
.getActionTypeOptionGroup().getValue()).getActionType();
|
||||
final long forcedTimeStamp = actionTypeOptionGroupLayout.getActionTypeOptionGroup()
|
||||
.getValue() == AbstractActionTypeOptionGroupLayout.ActionTypeOption.AUTO_FORCED
|
||||
|
||||
final ActionType actionType = ((ActionTypeOption) actionTypeOptionGroupLayout.getActionTypeOptionGroup()
|
||||
.getValue()).getActionType();
|
||||
|
||||
final long forcedTimeStamp = (((ActionTypeOption) actionTypeOptionGroupLayout.getActionTypeOptionGroup()
|
||||
.getValue()) == ActionTypeOption.AUTO_FORCED)
|
||||
? actionTypeOptionGroupLayout.getForcedTimeDateField().getValue().getTime()
|
||||
: 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 maintenanceDuration = maintenanceWindowLayout.getMaintenanceDuration();
|
||||
final String maintenanceTimeZone = maintenanceWindowLayout.getMaintenanceTimeZone();
|
||||
|
||||
for (final Map.Entry<Long, List<TargetIdName>> mapEntry : saveAssignedList.entrySet()) {
|
||||
tempIdList = saveAssignedList.get(mapEntry.getKey());
|
||||
final DistributionSetAssignmentResult distributionSetAssignmentResult = deploymentManagement
|
||||
.assignDistributionSet(mapEntry.getKey(),
|
||||
tempIdList.stream().map(t -> maintenanceWindowLayout.isEnabled()
|
||||
? new TargetWithActionType(t.getControllerId(), actionType, forcedTimeStamp,
|
||||
maintenanceSchedule, maintenanceDuration, maintenanceTimeZone)
|
||||
: new TargetWithActionType(t.getControllerId(), actionType, forcedTimeStamp))
|
||||
.collect(Collectors.toList()));
|
||||
if (distributionSetAssignmentResult.getAssigned() > 0) {
|
||||
notification.displaySuccess(
|
||||
i18n.getMessage("message.target.assignment", distributionSetAssignmentResult.getAssigned()));
|
||||
}
|
||||
if (distributionSetAssignmentResult.getAlreadyAssigned() > 0) {
|
||||
notification.displaySuccess(i18n.getMessage("message.target.alreadyAssigned",
|
||||
distributionSetAssignmentResult.getAlreadyAssigned()));
|
||||
}
|
||||
final Set<Long> dsIds = distributionSets.stream().map(DistributionSet::getId).collect(Collectors.toSet());
|
||||
final List<TargetWithActionType> trgActionType = targets.stream()
|
||||
.map(t -> maintenanceWindowLayout.isEnabled()
|
||||
? new TargetWithActionType(t.getControllerId(), actionType, forcedTimeStamp,
|
||||
maintenanceSchedule, maintenanceDuration, maintenanceTimeZone)
|
||||
: new TargetWithActionType(t.getControllerId(), actionType, forcedTimeStamp))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
final List<DistributionSetAssignmentResult> results = deploymentManagement.assignDistributionSets(dsIds,
|
||||
trgActionType);
|
||||
|
||||
// use the last one for the notification box
|
||||
final DistributionSetAssignmentResult assignmentResult = results.get(results.size() - 1);
|
||||
if (assignmentResult.getAssigned() > 0) {
|
||||
notification.displaySuccess(i18n.getMessage("message.target.assignment", assignmentResult.getAssigned()));
|
||||
}
|
||||
refreshPinnedDetails(saveAssignedList, managementUIState, eventBus, eventSource);
|
||||
managementUIState.getAssignedList().clear();
|
||||
if (assignmentResult.getAlreadyAssigned() > 0) {
|
||||
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"));
|
||||
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 Optional<Long> pinnedDist = managementUIState.getTargetTableFilters().getPinnedDistId();
|
||||
final Optional<TargetIdName> pinnedTarget = managementUIState.getDistributionTableFilters().getPinnedTarget();
|
||||
|
||||
if (pinnedDist.isPresent()) {
|
||||
if (saveAssignedList.keySet().contains(pinnedDist.get())) {
|
||||
if (dsIds.contains(pinnedDist.get())) {
|
||||
eventBus.publish(eventSource, PinUnpinEvent.PIN_DISTRIBUTION);
|
||||
}
|
||||
} else if (pinnedTarget.isPresent()) {
|
||||
final Set<TargetIdName> assignedTargetIds = managementUIState.getAssignedList().keySet();
|
||||
if (assignedTargetIds.contains(pinnedTarget.get())) {
|
||||
eventBus.publish(eventSource, PinUnpinEvent.PIN_TARGET);
|
||||
}
|
||||
} else if (pinnedTarget.isPresent() && targetIds.contains(pinnedTarget.get().getTargetId())) {
|
||||
eventBus.publish(eventSource, PinUnpinEvent.PIN_TARGET);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,7 +175,8 @@ public final class TargetAssignmentOperations {
|
||||
* @param maintenanceWindowLayout
|
||||
* the Maintenance Window Layout
|
||||
* @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
|
||||
* the Vaadin Message Source for multi language
|
||||
* @param uiProperties
|
||||
@@ -283,4 +272,4 @@ public final class TargetAssignmentOperations {
|
||||
return SPUIComponentProvider.getHelpLink(i18n, maintenanceWindowHelpUrl);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -15,10 +15,12 @@ import org.eclipse.hawkbit.repository.DistributionSetManagement;
|
||||
import org.eclipse.hawkbit.repository.DistributionSetTypeManagement;
|
||||
import org.eclipse.hawkbit.repository.EntityFactory;
|
||||
import org.eclipse.hawkbit.repository.SystemManagement;
|
||||
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSetType;
|
||||
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.SaveDialogCloseListener;
|
||||
import org.eclipse.hawkbit.ui.common.DistributionSetTypeBeanQuery;
|
||||
@@ -64,6 +66,7 @@ public class DistributionAddUpdateWindowLayout extends CustomComponent {
|
||||
private final transient DistributionSetTypeManagement distributionSetTypeManagement;
|
||||
private final transient SystemManagement systemManagement;
|
||||
private final transient EntityFactory entityFactory;
|
||||
private final transient TenantConfigurationManagement tenantConfigurationManagement;
|
||||
|
||||
private final DistributionSetTable distributionSetTable;
|
||||
|
||||
@@ -94,11 +97,14 @@ public class DistributionAddUpdateWindowLayout extends CustomComponent {
|
||||
* EntityFactory
|
||||
* @param distributionSetTable
|
||||
* DistributionSetTable
|
||||
* @param tenantConfigurationManagement
|
||||
* TenantConfigurationManagement
|
||||
*/
|
||||
public DistributionAddUpdateWindowLayout(final VaadinMessageSource i18n, final UINotification notificationMessage,
|
||||
final UIEventBus eventBus, final DistributionSetManagement distributionSetManagement,
|
||||
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.notificationMessage = notificationMessage;
|
||||
this.eventBus = eventBus;
|
||||
@@ -107,6 +113,7 @@ public class DistributionAddUpdateWindowLayout extends CustomComponent {
|
||||
this.systemManagement = systemManagement;
|
||||
this.entityFactory = entityFactory;
|
||||
this.distributionSetTable = distributionSetTable;
|
||||
this.tenantConfigurationManagement = tenantConfigurationManagement;
|
||||
createRequiredComponents();
|
||||
buildLayout();
|
||||
}
|
||||
@@ -134,8 +141,8 @@ public class DistributionAddUpdateWindowLayout extends CustomComponent {
|
||||
final DistributionSet currentDS = distributionSetManagement.update(entityFactory.distributionSet()
|
||||
.update(editDistId).name(distNameTextField.getValue()).description(descTextArea.getValue())
|
||||
.version(distVersionTextField.getValue()).requiredMigrationStep(isMigStepReq));
|
||||
notificationMessage.displaySuccess(i18n.getMessage("message.new.dist.save.success",
|
||||
new Object[] { currentDS.getName(), currentDS.getVersion() }));
|
||||
notificationMessage.displaySuccess(
|
||||
i18n.getMessage("message.new.dist.save.success", currentDS.getName(), currentDS.getVersion()));
|
||||
// update table row+details layout
|
||||
eventBus.publish(this, new DistributionTableEvent(BaseEntityEventType.UPDATED_ENTITY, currentDS));
|
||||
});
|
||||
@@ -283,6 +290,12 @@ public class DistributionAddUpdateWindowLayout extends CustomComponent {
|
||||
distsetTypeNameComboBox.setEnabled(true);
|
||||
descTextArea.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) {
|
||||
@@ -351,7 +364,8 @@ public class DistributionAddUpdateWindowLayout extends CustomComponent {
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ package org.eclipse.hawkbit.ui.management.dstable;
|
||||
|
||||
import org.eclipse.hawkbit.repository.DistributionSetManagement;
|
||||
import org.eclipse.hawkbit.repository.DistributionSetTagManagement;
|
||||
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||
import org.eclipse.hawkbit.ui.SpPermissionChecker;
|
||||
import org.eclipse.hawkbit.ui.common.detailslayout.AbstractDistributionSetDetails;
|
||||
import org.eclipse.hawkbit.ui.common.detailslayout.SoftwareModuleDetailsTable;
|
||||
@@ -31,10 +32,12 @@ public class DistributionDetails extends AbstractDistributionSetDetails {
|
||||
final DistributionSetManagement distributionSetManagement,
|
||||
final DsMetadataPopupLayout dsMetadataPopupLayout, final UINotification uiNotification,
|
||||
final DistributionSetTagManagement distributionSetTagManagement,
|
||||
final DistributionAddUpdateWindowLayout distributionAddUpdateWindowLayout) {
|
||||
final DistributionAddUpdateWindowLayout distributionAddUpdateWindowLayout,
|
||||
final TenantConfigurationManagement tenantConfigurationManagement) {
|
||||
super(i18n, eventBus, permissionChecker, managementUIState, distributionAddUpdateWindowLayout,
|
||||
distributionSetManagement, dsMetadataPopupLayout, uiNotification, distributionSetTagManagement,
|
||||
createSoftwareModuleDetailsTable(i18n, permissionChecker, uiNotification));
|
||||
createSoftwareModuleDetailsTable(i18n, permissionChecker, uiNotification),
|
||||
tenantConfigurationManagement);
|
||||
restoreState();
|
||||
}
|
||||
|
||||
|
||||
@@ -404,84 +404,61 @@ public class DistributionTable extends AbstractNamedVersionTable<DistributionSet
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (targetDetailsList.isEmpty()) {
|
||||
if (targets.isEmpty()) {
|
||||
getNotification().displayWarning(getI18n().getMessage(TARGETS_NOT_EXISTS));
|
||||
return;
|
||||
}
|
||||
|
||||
final Long distId = (Long) item.getItemProperty("id").getValue();
|
||||
selectDroppedEntities(distId);
|
||||
final Optional<DistributionSet> findDistributionSetById = distributionSetManagement.get(distId);
|
||||
if (!findDistributionSetById.isPresent()) {
|
||||
final Optional<DistributionSet> distributionSet = distributionSetManagement.get(distId);
|
||||
if (!distributionSet.isPresent()) {
|
||||
getNotification().displayWarning(getI18n().getMessage(DISTRIBUTIONSET_NOT_EXISTS));
|
||||
return;
|
||||
}
|
||||
|
||||
addNewDistributionToAssignmentList(targetDetailsList, findDistributionSetById.get());
|
||||
openConfirmationWindowForAssignment(findDistributionSetById.get().getName(), targetDetailsList);
|
||||
openConfirmationWindowForAssignment(distributionSet.get(), targets);
|
||||
}
|
||||
|
||||
private void addNewDistributionToAssignmentList(final List<Target> targetDetailsList,
|
||||
final DistributionSet distributionSet) {
|
||||
String pendingActionMessage = null;
|
||||
final DistributionSetIdName distributionSetIdName = new DistributionSetIdName(distributionSet);
|
||||
private void openConfirmationWindowForAssignment(final DistributionSet distributionSet,
|
||||
final List<Target> targets) {
|
||||
|
||||
for (final Target target : targetDetailsList) {
|
||||
final TargetIdName key = new TargetIdName(target);
|
||||
if (managementUIState.getAssignedList().keySet().contains(key)
|
||||
&& managementUIState.getAssignedList().get(key).equals(distributionSetIdName)) {
|
||||
pendingActionMessage = getPendingActionMessage(pendingActionMessage, target.getControllerId(),
|
||||
HawkbitCommonUtil.getDistributionNameAndVersion(distributionSetIdName.getName(),
|
||||
distributionSetIdName.getVersion()));
|
||||
getNotification().displayValidationError(pendingActionMessage);
|
||||
} else {
|
||||
managementUIState.getAssignedList().put(key, distributionSetIdName);
|
||||
final String question = getAssignmentConfirmationMessage(distributionSet, targets);
|
||||
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, question, okLabel, cancelLabel, ok -> {
|
||||
if (ok && isMaintenanceWindowValid(maintenanceWindowLayout, getNotification())) {
|
||||
saveAllAssignments(targets, Collections.singletonList(distributionSet), managementUIState,
|
||||
actionTypeOptionGroupLayout, maintenanceWindowLayout, deploymentManagement, getNotification(),
|
||||
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());
|
||||
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() {
|
||||
return isEnabled -> confirmDialog.getOkButton().setEnabled(isEnabled);
|
||||
}
|
||||
|
||||
private String createConfirmationQuestionForAssignment(final String distributionNameToAssign,
|
||||
final List<Target> targetDetailsList) {
|
||||
if (targetDetailsList.size() == 1) {
|
||||
return getI18n().getMessage(MESSAGE_CONFIRM_ASSIGN_ENTITY, distributionNameToAssign, "target",
|
||||
targetDetailsList.get(0).getName());
|
||||
} else {
|
||||
return getI18n().getMessage(MESSAGE_CONFIRM_ASSIGN_MULTIPLE_ENTITIES, targetDetailsList.size(), "targets",
|
||||
distributionNameToAssign);
|
||||
private String getAssignmentConfirmationMessage(final DistributionSet distributionSet, final List<Target> targets) {
|
||||
final String distributionName = distributionSet.getName();
|
||||
final int targetCount = targets.size();
|
||||
if (targetCount > 1) {
|
||||
return getI18n().getMessage(MESSAGE_CONFIRM_ASSIGN_MULTIPLE_ENTITIES, targetCount, "targets",
|
||||
distributionName);
|
||||
}
|
||||
return getI18n().getMessage(MESSAGE_CONFIRM_ASSIGN_ENTITY, distributionName, "target",
|
||||
targets.get(0).getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -516,15 +493,6 @@ public class DistributionTable extends AbstractNamedVersionTable<DistributionSet
|
||||
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) {
|
||||
final Item item = getContainerDataSource().getItem(editedDs.getId());
|
||||
if (item == null) {
|
||||
@@ -768,4 +736,4 @@ public class DistributionTable extends AbstractNamedVersionTable<DistributionSet
|
||||
return "";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import org.eclipse.hawkbit.repository.EntityFactory;
|
||||
import org.eclipse.hawkbit.repository.SystemManagement;
|
||||
import org.eclipse.hawkbit.repository.TargetManagement;
|
||||
import org.eclipse.hawkbit.repository.TargetTagManagement;
|
||||
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||
import org.eclipse.hawkbit.ui.SpPermissionChecker;
|
||||
import org.eclipse.hawkbit.ui.UiProperties;
|
||||
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 TargetTagManagement targetTagManagement, final SystemManagement systemManagement,
|
||||
final TargetManagement targetManagement, final DeploymentManagement deploymentManagement,
|
||||
final UiProperties uiProperties) {
|
||||
final TenantConfigurationManagement configManagement, final UiProperties uiProperties) {
|
||||
|
||||
final DistributionAddUpdateWindowLayout distributionAddUpdateWindowLayout = new DistributionAddUpdateWindowLayout(
|
||||
i18n, notification, eventBus, distributionSetManagement, distributionSetTypeManagement,
|
||||
systemManagement, entityFactory, null);
|
||||
systemManagement, entityFactory, null, configManagement);
|
||||
|
||||
final DsMetadataPopupLayout dsMetadataPopupLayout = new DsMetadataPopupLayout(i18n, notification, eventBus,
|
||||
distributionSetManagement, entityFactory, permissionChecker);
|
||||
@@ -60,7 +61,7 @@ public class DistributionTableLayout extends AbstractTableLayout<DistributionTab
|
||||
distributionTable,
|
||||
new DistributionDetails(i18n, eventBus, permissionChecker, managementUIState, distributionSetManagement,
|
||||
dsMetadataPopupLayout, notification, distributionSetTagManagement,
|
||||
distributionAddUpdateWindowLayout));
|
||||
distributionAddUpdateWindowLayout, configManagement));
|
||||
}
|
||||
|
||||
public DistributionTable getDistributionTable() {
|
||||
|
||||
@@ -10,9 +10,7 @@ package org.eclipse.hawkbit.ui.management.state;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
@@ -40,8 +38,6 @@ public class ManagementUIState implements ManagementEntityState, Serializable {
|
||||
|
||||
private final TargetTableFilters targetTableFilters;
|
||||
|
||||
private final Map<TargetIdName, DistributionSetIdName> assignedList = new HashMap<>();
|
||||
|
||||
private final Set<DistributionSetIdName> deletedDistributionList = new HashSet<>();
|
||||
|
||||
private final Set<TargetIdName> deletedTargetList = new HashSet<>();
|
||||
@@ -137,10 +133,6 @@ public class ManagementUIState implements ManagementEntityState, Serializable {
|
||||
return distributionTableFilters;
|
||||
}
|
||||
|
||||
public Map<TargetIdName, DistributionSetIdName> getAssignedList() {
|
||||
return assignedList;
|
||||
}
|
||||
|
||||
public Set<DistributionSetIdName> getDeletedDistributionList() {
|
||||
return deletedDistributionList;
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ import org.eclipse.hawkbit.repository.DistributionSetManagement;
|
||||
import org.eclipse.hawkbit.repository.FilterParams;
|
||||
import org.eclipse.hawkbit.repository.TargetManagement;
|
||||
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.model.DistributionSet;
|
||||
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.TargetTagAssignmentResult;
|
||||
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.UiProperties;
|
||||
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 String MESSAGE_ASSIGN_TARGET_TO_MULTIPLE_DISTRIBUTIONS = "message.confirm.assign.multiple.entities.multiple.distributions";
|
||||
|
||||
private final transient TargetManagement targetManagement;
|
||||
|
||||
private final transient DistributionSetManagement distributionSetManagement;
|
||||
@@ -123,6 +127,8 @@ public class TargetTable extends AbstractTable<Target> {
|
||||
|
||||
private final transient DeploymentManagement deploymentManagement;
|
||||
|
||||
private final transient TenantConfigurationManagement configManagement;
|
||||
|
||||
private final ManagementViewClientCriterion managementViewClientCriterion;
|
||||
|
||||
private final ManagementUIState managementUIState;
|
||||
@@ -143,7 +149,8 @@ public class TargetTable extends AbstractTable<Target> {
|
||||
final TargetManagement targetManagement, final ManagementUIState managementUIState,
|
||||
final SpPermissionChecker permChecker, final ManagementViewClientCriterion managementViewClientCriterion,
|
||||
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);
|
||||
this.targetManagement = targetManagement;
|
||||
this.managementViewClientCriterion = managementViewClientCriterion;
|
||||
@@ -151,6 +158,7 @@ public class TargetTable extends AbstractTable<Target> {
|
||||
this.distributionSetManagement = distributionSetManagement;
|
||||
this.tagManagement = tagManagement;
|
||||
this.deploymentManagement = deploymentManagement;
|
||||
this.configManagement = configManagement;
|
||||
this.uiProperties = uiProperties;
|
||||
this.actionTypeOptionGroupLayout = new ActionTypeOptionGroupAssignmentLayout(i18n);
|
||||
this.maintenanceWindowLayout = new MaintenanceWindowLayout(i18n);
|
||||
@@ -355,23 +363,18 @@ public class TargetTable extends AbstractTable<Target> {
|
||||
|
||||
private Map<String, Object> prepareQueryConfigFilters() {
|
||||
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());
|
||||
|
||||
if (
|
||||
|
||||
isFilteredByTags()) {
|
||||
if (isFilteredByTags()) {
|
||||
final List<String> list = new ArrayList<>(managementUIState.getTargetTableFilters().getClickedTargetTags());
|
||||
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) {
|
||||
final TableTransferable transferable = (TableTransferable) event.getTransferable();
|
||||
final AbstractTable<?> source = (AbstractTable<?>) transferable.getSourceComponent();
|
||||
final Set<Long> ids = source.getSelectedEntitiesByTransferable(transferable);
|
||||
// only one distribution can be assigned to a target
|
||||
final Long idToSelect = ids.iterator().next();
|
||||
selectDraggedEntities(source, new HashSet<>(Arrays.asList(idToSelect)));
|
||||
|
||||
final Set<Long> dsIds = filterDistributionSetsToAssign(source.getSelectedEntitiesByTransferable(transferable));
|
||||
if (dsIds.isEmpty()) {
|
||||
getNotification().displayWarning(getI18n().getMessage(DISTRIBUTIONSET_NOT_EXISTS));
|
||||
return;
|
||||
}
|
||||
|
||||
selectDraggedEntities(source, dsIds);
|
||||
final AbstractSelectTargetDetails dropData = (AbstractSelectTargetDetails) event.getTargetDetails();
|
||||
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) {
|
||||
getNotification().displayWarning(getI18n().getMessage(TARGETS_NOT_EXISTS, ""));
|
||||
return;
|
||||
@@ -873,45 +880,57 @@ public class TargetTable extends AbstractTable<Target> {
|
||||
return;
|
||||
}
|
||||
|
||||
final TargetIdName createTargetIdName = new TargetIdName(target.get());
|
||||
final List<DistributionSet> findDistributionSetById = distributionSetManagement
|
||||
.get(new HashSet<>(Arrays.asList(idToSelect)));
|
||||
|
||||
if (findDistributionSetById.isEmpty()) {
|
||||
final List<DistributionSet> distributionSets = distributionSetManagement.get(dsIds);
|
||||
if (distributionSets.isEmpty()) {
|
||||
getNotification().displayWarning(getI18n().getMessage(DISTRIBUTIONSET_NOT_EXISTS));
|
||||
return;
|
||||
}
|
||||
final DistributionSet distributionSetToAssign = findDistributionSetById.get(0);
|
||||
addNewTargetToAssignmentList(createTargetIdName, distributionSetToAssign);
|
||||
openConfirmationWindowForAssignment(distributionSetToAssign.getName(), createTargetIdName.getTargetName());
|
||||
openConfirmationWindowForAssignments(target.get(), distributionSets);
|
||||
}
|
||||
|
||||
private void openConfirmationWindowForAssignment(final String distributionNameToAssign, final String targetName) {
|
||||
confirmDialog = new ConfirmationDialog(getI18n().getMessage(CAPTION_ENTITY_ASSIGN_ACTION_CONFIRMBOX),
|
||||
getI18n().getMessage(MESSAGE_CONFIRM_ASSIGN_ENTITY, distributionNameToAssign, "target", targetName),
|
||||
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 Set<Long> filterDistributionSetsToAssign(final Set<Long> ids) {
|
||||
if (ids.isEmpty()) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
if (isMultiAssignmentsEnabled()) {
|
||||
return new HashSet<>(ids);
|
||||
}
|
||||
return Collections.singleton(ids.iterator().next());
|
||||
}
|
||||
|
||||
private void openConfirmationWindowForAssignments(final Target target,
|
||||
final List<DistributionSet> distributionSets) {
|
||||
|
||||
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());
|
||||
confirmDialog.getWindow().bringToFront();
|
||||
}
|
||||
|
||||
private Consumer<Boolean> saveButtonToggle() {
|
||||
return isEnabled -> confirmDialog.getOkButton().setEnabled(isEnabled);
|
||||
private String getConfirmationMessage(final Target target, final List<DistributionSet> distributionSets) {
|
||||
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,
|
||||
final DistributionSet findDistributionSetAllById) {
|
||||
final DistributionSetIdName distributionNameId = new DistributionSetIdName(findDistributionSetAllById);
|
||||
managementUIState.getAssignedList().put(createTargetIdName, distributionNameId);
|
||||
private Consumer<Boolean> saveButtonToggle() {
|
||||
return isEnabled -> confirmDialog.getOkButton().setEnabled(isEnabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -963,4 +982,11 @@ public class TargetTable extends AbstractTable<Target> {
|
||||
return "";
|
||||
}
|
||||
|
||||
}
|
||||
private boolean isMultiAssignmentsEnabled() {
|
||||
return configManagement
|
||||
.getConfigurationValue(TenantConfigurationProperties.TenantConfigurationKey.MULTI_ASSIGNMENTS_ENABLED,
|
||||
Boolean.class)
|
||||
.getValue();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,18 +9,22 @@
|
||||
package org.eclipse.hawkbit.ui.tenantconfiguration;
|
||||
|
||||
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||
import org.eclipse.hawkbit.ui.UiProperties;
|
||||
import org.eclipse.hawkbit.ui.components.SPUIComponentProvider;
|
||||
import org.eclipse.hawkbit.ui.tenantconfiguration.generic.BooleanConfigurationItem;
|
||||
import org.eclipse.hawkbit.ui.tenantconfiguration.repository.ActionAutocleanupConfigurationItem;
|
||||
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.VaadinMessageSource;
|
||||
|
||||
import com.vaadin.data.Property.ValueChangeEvent;
|
||||
import com.vaadin.data.Property.ValueChangeListener;
|
||||
import com.vaadin.ui.Alignment;
|
||||
import com.vaadin.ui.CheckBox;
|
||||
import com.vaadin.ui.GridLayout;
|
||||
import com.vaadin.ui.Label;
|
||||
import com.vaadin.ui.Link;
|
||||
import com.vaadin.ui.Panel;
|
||||
import com.vaadin.ui.VerticalLayout;
|
||||
|
||||
@@ -36,21 +40,30 @@ public class RepositoryConfigurationView extends BaseConfigurationView
|
||||
|
||||
private final VaadinMessageSource i18n;
|
||||
|
||||
private final UiProperties uiProperties;
|
||||
|
||||
private final ActionAutocloseConfigurationItem actionAutocloseConfigurationItem;
|
||||
|
||||
private final ActionAutocleanupConfigurationItem actionAutocleanupConfigurationItem;
|
||||
|
||||
private final MultiAssignmentsConfigurationItem multiAssignmentsConfigurationItem;
|
||||
|
||||
private CheckBox actionAutocloseCheckBox;
|
||||
|
||||
private CheckBox actionAutocleanupCheckBox;
|
||||
|
||||
private CheckBox multiAssignmentsCheckBox;
|
||||
|
||||
RepositoryConfigurationView(final VaadinMessageSource i18n,
|
||||
final TenantConfigurationManagement tenantConfigurationManagement) {
|
||||
final TenantConfigurationManagement tenantConfigurationManagement, final UiProperties uiProperties) {
|
||||
this.i18n = i18n;
|
||||
this.uiProperties = uiProperties;
|
||||
this.actionAutocloseConfigurationItem = new ActionAutocloseConfigurationItem(tenantConfigurationManagement,
|
||||
i18n);
|
||||
this.actionAutocleanupConfigurationItem = new ActionAutocleanupConfigurationItem(tenantConfigurationManagement,
|
||||
i18n);
|
||||
this.multiAssignmentsConfigurationItem = new MultiAssignmentsConfigurationItem(tenantConfigurationManagement,
|
||||
i18n);
|
||||
|
||||
init();
|
||||
}
|
||||
@@ -70,27 +83,46 @@ public class RepositoryConfigurationView extends BaseConfigurationView
|
||||
header.addStyleName("config-panel-header");
|
||||
vLayout.addComponent(header);
|
||||
|
||||
final GridLayout gridLayout = new GridLayout(2, 2);
|
||||
final GridLayout gridLayout = new GridLayout(3, 3);
|
||||
gridLayout.setSpacing(true);
|
||||
gridLayout.setImmediate(true);
|
||||
gridLayout.setColumnExpandRatio(1, 1.0F);
|
||||
gridLayout.setSizeFull();
|
||||
|
||||
final boolean isMultiAssignmentsEnabled = multiAssignmentsConfigurationItem.isConfigEnabled();
|
||||
|
||||
actionAutocloseCheckBox = SPUIComponentProvider.getCheckBox("", DIST_CHECKBOX_STYLE, null, false, "");
|
||||
actionAutocloseCheckBox.setId(UIComponentIdProvider.REPOSITORY_ACTIONS_AUTOCLOSE_CHECKBOX);
|
||||
actionAutocloseCheckBox.setEnabled(!isMultiAssignmentsEnabled);
|
||||
actionAutocloseConfigurationItem.setEnabled(!isMultiAssignmentsEnabled);
|
||||
actionAutocloseCheckBox.setValue(actionAutocloseConfigurationItem.isConfigEnabled());
|
||||
actionAutocloseCheckBox.addValueChangeListener(this);
|
||||
actionAutocloseConfigurationItem.addChangeListener(this);
|
||||
gridLayout.addComponent(actionAutocloseCheckBox, 0, 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.setId(UIComponentIdProvider.REPOSITORY_ACTIONS_AUTOCLEANUP_CHECKBOX);
|
||||
actionAutocleanupCheckBox.setValue(actionAutocleanupConfigurationItem.isConfigEnabled());
|
||||
actionAutocleanupCheckBox.addValueChangeListener(this);
|
||||
actionAutocleanupConfigurationItem.addChangeListener(this);
|
||||
gridLayout.addComponent(actionAutocleanupCheckBox, 0, 1);
|
||||
gridLayout.addComponent(actionAutocleanupConfigurationItem, 1, 1);
|
||||
gridLayout.addComponent(actionAutocleanupCheckBox, 0, 2);
|
||||
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);
|
||||
rootPanel.setContent(vLayout);
|
||||
@@ -101,18 +133,29 @@ public class RepositoryConfigurationView extends BaseConfigurationView
|
||||
public void save() {
|
||||
actionAutocloseConfigurationItem.save();
|
||||
actionAutocleanupConfigurationItem.save();
|
||||
multiAssignmentsConfigurationItem.save();
|
||||
|
||||
final boolean isMultiAssignmentsEnabled = multiAssignmentsConfigurationItem.isConfigEnabled();
|
||||
multiAssignmentsCheckBox.setEnabled(!isMultiAssignmentsEnabled);
|
||||
multiAssignmentsConfigurationItem.setEnabled(!isMultiAssignmentsEnabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUserInputValid() {
|
||||
return actionAutocloseConfigurationItem.isUserInputValid()
|
||||
&& actionAutocleanupConfigurationItem.isUserInputValid();
|
||||
&& actionAutocleanupConfigurationItem.isUserInputValid()
|
||||
&& multiAssignmentsConfigurationItem.isUserInputValid();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undo() {
|
||||
multiAssignmentsConfigurationItem.undo();
|
||||
final boolean isMultiAssignmentsEnabled = multiAssignmentsConfigurationItem.isConfigEnabled();
|
||||
multiAssignmentsCheckBox.setValue(isMultiAssignmentsEnabled);
|
||||
actionAutocloseConfigurationItem.undo();
|
||||
actionAutocloseCheckBox.setValue(actionAutocloseConfigurationItem.isConfigEnabled());
|
||||
actionAutocloseCheckBox.setEnabled(!isMultiAssignmentsEnabled);
|
||||
actionAutocloseConfigurationItem.setEnabled(!isMultiAssignmentsEnabled);
|
||||
actionAutocleanupConfigurationItem.undo();
|
||||
actionAutocleanupCheckBox.setValue(actionAutocleanupConfigurationItem.isConfigEnabled());
|
||||
}
|
||||
@@ -138,6 +181,10 @@ public class RepositoryConfigurationView extends BaseConfigurationView
|
||||
configurationItem = actionAutocloseConfigurationItem;
|
||||
} else if (actionAutocleanupCheckBox.equals(checkBox)) {
|
||||
configurationItem = actionAutocleanupConfigurationItem;
|
||||
} else if (multiAssignmentsCheckBox.equals(checkBox)) {
|
||||
configurationItem = multiAssignmentsConfigurationItem;
|
||||
actionAutocloseCheckBox.setEnabled(!checkBox.getValue());
|
||||
actionAutocloseConfigurationItem.setEnabled(!checkBox.getValue());
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import org.eclipse.hawkbit.ui.utils.VaadinMessageSource;
|
||||
import com.vaadin.data.Property;
|
||||
import com.vaadin.ui.Alignment;
|
||||
import com.vaadin.ui.CheckBox;
|
||||
import com.vaadin.ui.HorizontalLayout;
|
||||
import com.vaadin.ui.GridLayout;
|
||||
import com.vaadin.ui.Label;
|
||||
import com.vaadin.ui.Link;
|
||||
import com.vaadin.ui.Panel;
|
||||
@@ -61,24 +61,26 @@ public class RolloutConfigurationView extends BaseConfigurationView
|
||||
header.addStyleName("config-panel-header");
|
||||
vLayout.addComponent(header);
|
||||
|
||||
final HorizontalLayout hLayout = new HorizontalLayout();
|
||||
hLayout.setSpacing(true);
|
||||
hLayout.setImmediate(true);
|
||||
final GridLayout gridLayout = new GridLayout(3, 1);
|
||||
gridLayout.setSpacing(true);
|
||||
gridLayout.setImmediate(true);
|
||||
gridLayout.setColumnExpandRatio(1, 1.0F);
|
||||
gridLayout.setSizeFull();
|
||||
|
||||
approvalCheckbox = SPUIComponentProvider.getCheckBox("", "", null, false, "");
|
||||
approvalCheckbox.setId(UIComponentIdProvider.ROLLOUT_APPROVAL_ENABLED_CHECKBOX);
|
||||
approvalCheckbox.setValue(approvalConfigurationItem.isConfigEnabled());
|
||||
approvalCheckbox.addValueChangeListener(this);
|
||||
approvalConfigurationItem.addChangeListener(this);
|
||||
hLayout.addComponent(approvalCheckbox);
|
||||
hLayout.addComponent(approvalConfigurationItem);
|
||||
gridLayout.addComponent(approvalCheckbox, 0, 0);
|
||||
gridLayout.addComponent(approvalConfigurationItem, 1, 0);
|
||||
|
||||
final Link linkToApprovalHelp = SPUIComponentProvider.getHelpLink(i18n,
|
||||
uiProperties.getLinks().getDocumentation().getRollout());
|
||||
hLayout.addComponent(linkToApprovalHelp);
|
||||
hLayout.setComponentAlignment(linkToApprovalHelp, Alignment.BOTTOM_RIGHT);
|
||||
gridLayout.addComponent(linkToApprovalHelp, 2, 0);
|
||||
gridLayout.setComponentAlignment(linkToApprovalHelp, Alignment.BOTTOM_RIGHT);
|
||||
|
||||
vLayout.addComponent(hLayout);
|
||||
vLayout.addComponent(gridLayout);
|
||||
rootPanel.setContent(vLayout);
|
||||
setCompositionRoot(rootPanel);
|
||||
}
|
||||
|
||||
@@ -91,7 +91,8 @@ public class TenantConfigurationDashboardView extends CustomComponent implements
|
||||
securityTokenGenerator, uiProperties);
|
||||
this.pollingConfigurationView = new PollingConfigurationView(i18n, controllerPollProperties,
|
||||
tenantConfigurationManagement);
|
||||
this.repositoryConfigurationView = new RepositoryConfigurationView(i18n, tenantConfigurationManagement);
|
||||
this.repositoryConfigurationView = new RepositoryConfigurationView(i18n, tenantConfigurationManagement,
|
||||
uiProperties);
|
||||
this.rolloutConfigurationView = new RolloutConfigurationView(i18n, tenantConfigurationManagement, uiProperties);
|
||||
|
||||
this.i18n = i18n;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -608,6 +608,11 @@ public final class UIComponentIdProvider {
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
@@ -1154,6 +1159,11 @@ public final class UIComponentIdProvider {
|
||||
*/
|
||||
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 .
|
||||
*/
|
||||
@@ -1240,6 +1250,12 @@ public final class UIComponentIdProvider {
|
||||
*/
|
||||
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
|
||||
* {@link TenantConfigurationKey#ROLLOUT_APPROVAL_ENABLED}
|
||||
@@ -1279,4 +1295,4 @@ public final class UIComponentIdProvider {
|
||||
private UIComponentIdProvider() {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.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.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.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
|
||||
@@ -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
|
||||
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.multiple.distributions = Are you sure you want to assign {0} distributions to {1} {2}?
|
||||
|
||||
# character descriptions
|
||||
character.whitespace = whitespace
|
||||
|
||||
Reference in New Issue
Block a user