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
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user