Support for Multi-Assignments (#833)

* First draft of new assignment logic

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

* Enhancements of System Configuration view

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

* Drag&drop enhancements for multiple distribution set assignments

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

* Misc fixes

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

* test that previous assignments are not canceled

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

* add description and expected events to test

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

* extend TenantConfigurationManagement by NullPointerException proof
getConfigurationValue method

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

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

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

* Make fields transient

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

* Add IDs for Required Migration Step elements

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

* Save work in progress

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

* Added new DMF message DmfMultiActionRequest

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

* DMF enhancements to send out MultiActionRequest messages

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

* Minor changes

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

* Multi Assignment support for cancellations

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

* fix permission problems and immutable lists

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

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

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

* Implement Multi-Assignment support for rollout groups

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

* Minor changes

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

* Refactoring

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

* Register new deployment event with protobuff framework

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

* Allow same DS to be assigned multiple times

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

* Fix assignment with pending cancellations

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

* Reduce repository /DB calls

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

* Revert latest perf fix (causing a regression)

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

* test if a rollout sends multiaction messages in multiassignment mode

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

* Minor changes

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

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

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

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

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

* add javadoc to test method

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

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

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

* Add link to Provisioning State Machine

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

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

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

* refactor AmqpMessageHandlerService code

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

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

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

* add license header, remove unused instance variables

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

* fix tenantConfigurationManagement mock in test

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

* return empty list instead of null

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

* add ddi test for multiassignment, fix old test

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

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

# WARNING: head commit changed in the meantime

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

* Add test for autoClose /multiAssign

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

* Javadoc improvements

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

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

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

* clean up code

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

* Fix UI-related PR review findings

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

* Fix PR review findings

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

* Fix PR review findings

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

* Fix PR review findings

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

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

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

* Fix PR review findings

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

* Fix PR review findings

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

* Fix PR review findings

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

* Fix Sonar findings

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

* Fix PR review findings

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

* Fix PR review findings

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

* Fix PR review findings

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

View File

@@ -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())));

View File

@@ -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);

View File

@@ -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());
}
}

View File

@@ -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

View File

@@ -27,6 +27,7 @@ import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder;
import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications;
import org.eclipse.hawkbit.repository.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
}
}

View File

@@ -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());
}
}

View File

@@ -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.