Support user consent flow (#1293)
* Introduce user consent flow * Add permissions to confirmation management * rename from consent to confirmation * Reformat code. Remove unused imports. Change and add permission checks when configuring auto-confirmation. * Do not include null values for DDI confirmation base endpoint * fix confirmation required checkbox id * Remove unused import. Fix consume/produce type of new API's. * Change term processing to proceeding when activating user consent flow * Align formatting and extend integration test cases for DMF and DDI. * Extend DMF test cases to consider auto-confirmation * Refactor action management to fix problem of handling action status updates on closed actions. * remove unsupported validation * use new confirmation api for DMF. Extend test cases., * Remove unnecessary fields. * Extend API documentation for DDI and MGMT API. * adapt ddi api docs adoc file * Fixed the duplicate migration version for db files * fix method to support confirmation * Fixed PR comments * Addressed PR comments * Fixed after merge compilation issue * Fixed after merge compilation issue * Fix failing tests in MgmtRolloutResourceTest * Fixed the permissions issue reflected by integration tests * Added back the missing line of code lost during merge * Fix the failing test on Jenkins Signed-off-by: Stanislav Trailov <stanislav.trailov@bosch.io> Signed-off-by: Dimitar Shterev <dimitar.shterev@bosch.io> Signed-off-by: Michael Herdt <Michael.Herdt@bosch.io> Signed-off-by: Shruthi Manavalli Ramanna <shruthimanavalli.ramanna@bosch-si.com> Co-authored-by: Shruthi Manavalli Ramanna <shruthimanavalli.ramanna@bosch-si.com>
This commit is contained in:
@@ -57,11 +57,13 @@ public abstract class AbstractDsAssignmentStrategy {
|
||||
private final ActionStatusRepository actionStatusRepository;
|
||||
private final QuotaManagement quotaManagement;
|
||||
private final BooleanSupplier multiAssignmentsConfig;
|
||||
private final BooleanSupplier confirmationFlowConfig;
|
||||
|
||||
AbstractDsAssignmentStrategy(final TargetRepository targetRepository,
|
||||
final AfterTransactionCommitExecutor afterCommit, final EventPublisherHolder eventPublisherHolder,
|
||||
final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository,
|
||||
final QuotaManagement quotaManagement, final BooleanSupplier multiAssignmentsConfig) {
|
||||
final QuotaManagement quotaManagement, final BooleanSupplier multiAssignmentsConfig,
|
||||
final BooleanSupplier confirmationFlowConfig) {
|
||||
this.targetRepository = targetRepository;
|
||||
this.afterCommit = afterCommit;
|
||||
this.eventPublisherHolder = eventPublisherHolder;
|
||||
@@ -69,6 +71,7 @@ public abstract class AbstractDsAssignmentStrategy {
|
||||
this.actionStatusRepository = actionStatusRepository;
|
||||
this.quotaManagement = quotaManagement;
|
||||
this.multiAssignmentsConfig = multiAssignmentsConfig;
|
||||
this.confirmationFlowConfig = confirmationFlowConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -276,4 +279,8 @@ public abstract class AbstractDsAssignmentStrategy {
|
||||
protected boolean isMultiAssignmentsEnabled() {
|
||||
return multiAssignmentsConfig.getAsBoolean();
|
||||
}
|
||||
|
||||
protected boolean isConfirmationFlowEnabled() {
|
||||
return confirmationFlowConfig.getAsBoolean();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,8 +233,8 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
|
||||
|
||||
/**
|
||||
* Retrieves all {@link Action}s of a specific target and given active flag
|
||||
* ordered by action ID. Loads also the lazy
|
||||
* {@link Action#getDistributionSet()} field.
|
||||
* ordered by action ID. Loads also the lazy {@link Action#getDistributionSet()}
|
||||
* field.
|
||||
*
|
||||
* @param pageable
|
||||
* page parameters
|
||||
@@ -251,9 +251,8 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
|
||||
@Param("active") boolean active);
|
||||
|
||||
/**
|
||||
* Switches the status of actions from one specific status into another,
|
||||
* only if the actions are in a specific status. This should be a atomar
|
||||
* operation.
|
||||
* Switches the status of actions from one specific status into another, only if
|
||||
* the actions are in a specific status. This should be a atomar operation.
|
||||
*
|
||||
* @param statusToSet
|
||||
* the new status the actions should get
|
||||
@@ -270,6 +269,21 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
|
||||
void switchStatus(@Param("statusToSet") Action.Status statusToSet, @Param("targetsIds") List<Long> targetIds,
|
||||
@Param("active") boolean active, @Param("currentStatus") Action.Status currentStatus);
|
||||
|
||||
/**
|
||||
*
|
||||
* Retrieves all active {@link Action}s by given controllerId filtered by a
|
||||
* status
|
||||
*
|
||||
* @param controllerId
|
||||
* the IDs of targets for the actions
|
||||
* @param status
|
||||
* the current status of the actions
|
||||
* @return the found list of {@link Action}
|
||||
*/
|
||||
@Query("SELECT a FROM JpaAction a WHERE a.target.controllerId = :controllerId AND a.active = true AND a.status = :status")
|
||||
List<JpaAction> findByTargetIdAndIsActiveAndActionStatus(@Param("controllerId") String controllerId,
|
||||
@Param("status") Action.Status status);
|
||||
|
||||
/**
|
||||
*
|
||||
* Retrieves all IDs for {@link Action}s referring to the given target IDs,
|
||||
@@ -490,8 +504,8 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
|
||||
boolean existsByRolloutIdAndStatusNotIn(@Param("rolloutId") Long rolloutId, @Param("status") Status status);
|
||||
|
||||
/**
|
||||
* Retrieving all actions referring to a given rollout with a specific
|
||||
* action as parent reference and a specific status.
|
||||
* Retrieving all actions referring to a given rollout with a specific action as
|
||||
* parent reference and a specific status.
|
||||
*
|
||||
* Finding all actions of a specific rolloutgroup parent relation.
|
||||
*
|
||||
@@ -506,7 +520,7 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
|
||||
* @return the actions referring a specific rollout and a specific parent
|
||||
* rolloutgroup in a specific status
|
||||
*/
|
||||
@EntityGraph(attributePaths = { "target" }, type = EntityGraphType.LOAD)
|
||||
@EntityGraph(attributePaths = { "target", "target.autoConfirmationStatus", "rolloutGroup" }, type = EntityGraphType.LOAD)
|
||||
Page<Action> findByRolloutIdAndRolloutGroupParentIdAndStatus(Pageable pageable, Long rollout,
|
||||
Long rolloutGroupParent, Status actionStatus);
|
||||
|
||||
@@ -522,7 +536,8 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
|
||||
* @return the actions referring a specific rollout and a specific parent
|
||||
* rolloutgroup in a specific status
|
||||
*/
|
||||
@EntityGraph(attributePaths = { "target" }, type = EntityGraphType.LOAD)
|
||||
@EntityGraph(attributePaths = { "target", "target.autoConfirmationStatus",
|
||||
"rolloutGroup" }, type = EntityGraphType.LOAD)
|
||||
Page<Action> findByRolloutIdAndRolloutGroupParentIsNullAndStatus(Pageable pageable, Long rollout,
|
||||
Status actionStatus);
|
||||
|
||||
|
||||
@@ -14,24 +14,44 @@ import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.hawkbit.repository.QuotaManagement;
|
||||
import org.eclipse.hawkbit.repository.RepositoryProperties;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||
import org.eclipse.hawkbit.repository.jpa.builder.JpaActionStatusCreate;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus;
|
||||
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
|
||||
import org.eclipse.hawkbit.repository.model.Action;
|
||||
import org.eclipse.hawkbit.repository.model.ActionStatus;
|
||||
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
|
||||
import static org.eclipse.hawkbit.repository.model.Action.ActionType.DOWNLOAD_ONLY;
|
||||
import static org.eclipse.hawkbit.repository.model.Action.Status.FINISHED;
|
||||
|
||||
/**
|
||||
* Implements utility methods for managing {@link Action}s
|
||||
*/
|
||||
public class JpaActionManagement {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JpaActionManagement.class);
|
||||
|
||||
protected final ActionRepository actionRepository;
|
||||
protected final ActionStatusRepository actionStatusRepository;
|
||||
protected final QuotaManagement quotaManagement;
|
||||
protected final RepositoryProperties repositoryProperties;
|
||||
|
||||
protected JpaActionManagement(final ActionRepository actionRepository,
|
||||
final ActionStatusRepository actionStatusRepository, final QuotaManagement quotaManagement,
|
||||
final RepositoryProperties repositoryProperties) {
|
||||
this.actionRepository = actionRepository;
|
||||
this.actionStatusRepository = actionStatusRepository;
|
||||
this.quotaManagement = quotaManagement;
|
||||
this.repositoryProperties = repositoryProperties;
|
||||
}
|
||||
|
||||
|
||||
protected List<Action> findActiveActionsWithHighestWeightConsideringDefault(final String controllerId,
|
||||
final int maxActionCount) {
|
||||
if (!actionRepository.activeActionExistsForControllerId(controllerId)) {
|
||||
@@ -50,8 +70,90 @@ public class JpaActionManagement {
|
||||
return actions.stream().sorted(actionImportance).limit(maxActionCount).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
protected List<JpaAction> findActiveActionsHavingStatus(final String controllerId, final Action.Status status) {
|
||||
if (!actionRepository.activeActionExistsForControllerId(controllerId)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return Collections
|
||||
.unmodifiableList(actionRepository.findByTargetIdAndIsActiveAndActionStatus(controllerId, status));
|
||||
}
|
||||
|
||||
protected Action addActionStatus(final JpaActionStatusCreate statusCreate) {
|
||||
final Long actionId = statusCreate.getActionId();
|
||||
final JpaActionStatus actionStatus = statusCreate.build();
|
||||
final JpaAction action = getActionAndThrowExceptionIfNotFound(actionId);
|
||||
|
||||
if (isUpdatingActionStatusAllowed(action, actionStatus)) {
|
||||
return handleAddUpdateActionStatus(actionStatus, action);
|
||||
}
|
||||
|
||||
LOG.debug("Update of actionStatus {} for action {} not possible since action not active anymore.",
|
||||
actionStatus.getStatus(), action.getId());
|
||||
return action;
|
||||
}
|
||||
|
||||
/**
|
||||
* ActionStatus updates are allowed mainly if the action is active. If the
|
||||
* action is not active we accept further status updates if permitted so by
|
||||
* repository configuration. In this case, only the values: Status.ERROR and
|
||||
* Status.FINISHED are allowed. In the case of a DOWNLOAD_ONLY action, we accept
|
||||
* status updates only once.
|
||||
*/
|
||||
protected boolean isUpdatingActionStatusAllowed(final JpaAction action, final JpaActionStatus actionStatus) {
|
||||
|
||||
final boolean isIntermediateFeedback = (FINISHED != actionStatus.getStatus())
|
||||
&& (Action.Status.ERROR != actionStatus.getStatus());
|
||||
|
||||
final boolean isAllowedByRepositoryConfiguration = !repositoryProperties.isRejectActionStatusForClosedAction()
|
||||
&& isIntermediateFeedback;
|
||||
|
||||
final boolean isAllowedForDownloadOnlyActions = isDownloadOnly(action) && !isIntermediateFeedback;
|
||||
|
||||
return action.isActive() || isAllowedByRepositoryConfiguration || isAllowedForDownloadOnlyActions;
|
||||
}
|
||||
|
||||
protected int getWeightConsideringDefault(final Action action) {
|
||||
return action.getWeight().orElse(repositoryProperties.getActionWeightIfAbsent());
|
||||
}
|
||||
|
||||
protected JpaAction getActionAndThrowExceptionIfNotFound(final Long actionId) {
|
||||
return actionRepository.findById(actionId)
|
||||
.orElseThrow(() -> new EntityNotFoundException(Action.class, actionId));
|
||||
}
|
||||
|
||||
protected static boolean isDownloadOnly(final JpaAction action) {
|
||||
return DOWNLOAD_ONLY == action.getActionType();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets {@link TargetUpdateStatus} based on given {@link ActionStatus}.
|
||||
*/
|
||||
protected Action handleAddUpdateActionStatus(final JpaActionStatus actionStatus, final JpaAction action) {
|
||||
// information status entry - check for a potential DOS attack
|
||||
assertActionStatusQuota(action);
|
||||
assertActionStatusMessageQuota(actionStatus);
|
||||
actionStatus.setAction(action);
|
||||
|
||||
onActionStatusUpdate(actionStatus.getStatus(), action);
|
||||
|
||||
actionStatusRepository.save(actionStatus);
|
||||
|
||||
action.setLastActionStatusCode(actionStatus.getCode().orElse(null));
|
||||
return actionRepository.save(action);
|
||||
}
|
||||
|
||||
protected void onActionStatusUpdate(final Action.Status updatedActionStatus, final JpaAction action){
|
||||
// can be overwritten to intercept the persistence of the action status
|
||||
}
|
||||
|
||||
protected void assertActionStatusQuota(final JpaAction action) {
|
||||
QuotaHelper.assertAssignmentQuota(action.getId(), 1, quotaManagement.getMaxStatusEntriesPerAction(),
|
||||
ActionStatus.class, Action.class, actionStatusRepository::countByActionId);
|
||||
}
|
||||
|
||||
protected void assertActionStatusMessageQuota(final JpaActionStatus actionStatus) {
|
||||
QuotaHelper.assertAssignmentQuota(actionStatus.getId(), actionStatus.getMessages().size(),
|
||||
quotaManagement.getMaxMessagesPerActionStatus(), "Message", ActionStatus.class.getSimpleName(), null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
/**
|
||||
* Copyright (c) 2022 Bosch.IO 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.jpa;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.hawkbit.repository.ConfirmationManagement;
|
||||
import org.eclipse.hawkbit.repository.EntityFactory;
|
||||
import org.eclipse.hawkbit.repository.QuotaManagement;
|
||||
import org.eclipse.hawkbit.repository.RepositoryConstants;
|
||||
import org.eclipse.hawkbit.repository.RepositoryProperties;
|
||||
import org.eclipse.hawkbit.repository.builder.ActionStatusCreate;
|
||||
import org.eclipse.hawkbit.repository.exception.AutoConfirmationAlreadyActiveException;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||
import org.eclipse.hawkbit.repository.exception.InvalidConfirmationFeedbackException;
|
||||
import org.eclipse.hawkbit.repository.jpa.builder.JpaActionStatusCreate;
|
||||
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaAutoConfirmationStatus;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
|
||||
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.AutoConfirmationStatus;
|
||||
import org.eclipse.hawkbit.repository.model.Target;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.dao.ConcurrencyFailureException;
|
||||
import org.springframework.retry.annotation.Backoff;
|
||||
import org.springframework.retry.annotation.Retryable;
|
||||
import org.springframework.transaction.annotation.Isolation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
/**
|
||||
* JPA implementation for {@link ConfirmationManagement}.
|
||||
*
|
||||
*/
|
||||
@Transactional(readOnly = true)
|
||||
@Validated
|
||||
public class JpaConfirmationManagement extends JpaActionManagement implements ConfirmationManagement {
|
||||
|
||||
public static final String CONFIRMATION_CODE_MSG_PREFIX = "Confirmation status code: %d";
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JpaConfirmationManagement.class);
|
||||
|
||||
private final EntityFactory entityFactory;
|
||||
private final TargetRepository targetRepository;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
protected JpaConfirmationManagement(final TargetRepository targetRepository,
|
||||
final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository,
|
||||
final RepositoryProperties repositoryProperties, final QuotaManagement quotaManagement,
|
||||
final EntityFactory entityFactory) {
|
||||
super(actionRepository, actionStatusRepository, quotaManagement, repositoryProperties);
|
||||
this.targetRepository = targetRepository;
|
||||
this.entityFactory = entityFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Action> findActiveActionsWaitingConfirmation(final String controllerId) {
|
||||
return Collections.unmodifiableList(findActiveActionsHavingStatus(controllerId, Status.WAIT_FOR_CONFIRMATION));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public AutoConfirmationStatus activateAutoConfirmation(final String controllerId, final String initiator,
|
||||
final String remark) {
|
||||
LOG.trace("'activateAutoConfirmation' was called with values: controllerId={}; initiator={}; remark={}",
|
||||
controllerId, initiator, remark);
|
||||
final JpaTarget target = getTargetByControllerIdAndThrowIfNotFound(controllerId);
|
||||
if (target.getAutoConfirmationStatus() != null) {
|
||||
LOG.debug("'activateAutoConfirmation' was called for an controller id {} with active auto confirmation.",
|
||||
controllerId);
|
||||
throw new AutoConfirmationAlreadyActiveException(controllerId);
|
||||
}
|
||||
final JpaAutoConfirmationStatus confirmationStatus = new JpaAutoConfirmationStatus(initiator, remark, target);
|
||||
target.setAutoConfirmationStatus(confirmationStatus);
|
||||
final JpaTarget updatedTarget = targetRepository.save(target);
|
||||
final AutoConfirmationStatus autoConfStatus = updatedTarget.getAutoConfirmationStatus();
|
||||
if (autoConfStatus == null) {
|
||||
final String message = String.format("Persisted auto confirmation status is null. "
|
||||
+ "Cannot proceed with giving confirmations for active actions for device %s with initiator %s.",
|
||||
controllerId, initiator);
|
||||
LOG.error("message");
|
||||
throw new IllegalStateException(message);
|
||||
}
|
||||
giveConfirmationForActiveActions(autoConfStatus);
|
||||
return autoConfStatus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<AutoConfirmationStatus> getStatus(final String controllerId) {
|
||||
return Optional.of(getTargetByControllerIdAndThrowIfNotFound(controllerId)).map(JpaTarget::getAutoConfirmationStatus);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public List<Action> autoConfirmActiveActions(final String controllerId) {
|
||||
final JpaTarget target = getTargetByControllerIdAndThrowIfNotFound(controllerId);
|
||||
if (target.getAutoConfirmationStatus() == null) {
|
||||
// auto-confirmation is not active
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return giveConfirmationForActiveActions(target.getAutoConfirmationStatus());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(isolation = Isolation.READ_COMMITTED)
|
||||
@Retryable(include = {
|
||||
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
|
||||
public Action confirmAction(final long actionId, final Integer code, final Collection<String> deviceMessages) {
|
||||
LOG.trace("Action with id {} confirm request is triggered.", actionId);
|
||||
final Action action = getActionAndThrowExceptionIfNotFound(actionId);
|
||||
assertActionCanAcceptFeedback(action);
|
||||
final List<String> messages = new ArrayList<>();
|
||||
if (deviceMessages != null) {
|
||||
messages.addAll(deviceMessages);
|
||||
}
|
||||
messages.add(RepositoryConstants.SERVER_MESSAGE_PREFIX + "Target confirmed action."
|
||||
+ " Therefore, it will be set to the running state to proceed with the deployment.");
|
||||
final ActionStatusCreate statusCreate = createConfirmationActionStatus(action.getId(), code, messages)
|
||||
.status(Status.RUNNING);
|
||||
return addActionStatus((JpaActionStatusCreate) statusCreate);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(isolation = Isolation.READ_COMMITTED)
|
||||
@Retryable(include = {
|
||||
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
|
||||
public Action denyAction(final long actionId, final Integer code, final Collection<String> deviceMessages) {
|
||||
LOG.trace("Action with id {} deny request is triggered.", actionId);
|
||||
final Action action = getActionAndThrowExceptionIfNotFound(actionId);
|
||||
assertActionCanAcceptFeedback(action);
|
||||
final List<String> messages = new ArrayList<>();
|
||||
if (deviceMessages != null) {
|
||||
messages.addAll(deviceMessages);
|
||||
}
|
||||
messages.add(RepositoryConstants.SERVER_MESSAGE_PREFIX + "Target rejected action."
|
||||
+ " Action will stay in confirmation pending state.");
|
||||
final ActionStatusCreate statusCreate = createConfirmationActionStatus(action.getId(), code, messages)
|
||||
.status(Status.WAIT_FOR_CONFIRMATION);
|
||||
return addActionStatus((JpaActionStatusCreate) statusCreate);
|
||||
}
|
||||
|
||||
private ActionStatusCreate createConfirmationActionStatus(final long actionId, final Integer code,
|
||||
final Collection<String> messages) {
|
||||
final ActionStatusCreate statusCreate = entityFactory.actionStatus().create(actionId);
|
||||
if (!CollectionUtils.isEmpty(messages)) {
|
||||
statusCreate.messages(messages);
|
||||
}
|
||||
if (code != null) {
|
||||
statusCreate.code(code);
|
||||
statusCreate.message(String.format(CONFIRMATION_CODE_MSG_PREFIX, code));
|
||||
}
|
||||
return statusCreate;
|
||||
}
|
||||
|
||||
private static void assertActionCanAcceptFeedback(final Action action) {
|
||||
if (!action.isActive()) {
|
||||
final String msg = String.format(
|
||||
"Confirming action %s is not possible since the action is not active anymore.", action.getId());
|
||||
LOG.warn(msg);
|
||||
throw new InvalidConfirmationFeedbackException(InvalidConfirmationFeedbackException.Reason.ACTION_CLOSED,
|
||||
msg);
|
||||
} else if (!action.isWaitingConfirmation()) {
|
||||
LOG.debug("Action is not waiting for confirmation, deny request.");
|
||||
final String msg = String.format("Action %s is not waiting for confirmation.", action.getId());
|
||||
LOG.warn(msg);
|
||||
throw new InvalidConfirmationFeedbackException(
|
||||
InvalidConfirmationFeedbackException.Reason.NOT_AWAITING_CONFIRMATION, msg);
|
||||
}
|
||||
}
|
||||
|
||||
private List<Action> giveConfirmationForActiveActions(final AutoConfirmationStatus autoConfirmationStatus) {
|
||||
final Target target = autoConfirmationStatus.getTarget();
|
||||
return findActiveActionsHavingStatus(target.getControllerId(), Status.WAIT_FOR_CONFIRMATION).stream()
|
||||
.map(action -> autoConfirmAction(action, autoConfirmationStatus)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private Action autoConfirmAction(final JpaAction action, final AutoConfirmationStatus autoConfirmationStatus) {
|
||||
if (!action.isWaitingConfirmation()) {
|
||||
LOG.debug("Auto-confirming action is not necessary, since action {} is in RUNNING state already.",
|
||||
action.getId());
|
||||
return action;
|
||||
}
|
||||
final JpaActionStatus actionStatus = (JpaActionStatus) entityFactory.actionStatus().create(action.getId())
|
||||
.status(Status.RUNNING)
|
||||
.messages(Collections.singletonList(autoConfirmationStatus.constructActionMessage())).build();
|
||||
LOG.debug(
|
||||
"Automatically confirm actionId '{}' due to active auto-confirmation initiated by '{}' and rollouts system user '{}'",
|
||||
action.getId(), autoConfirmationStatus.getInitiator(), autoConfirmationStatus.getCreatedBy());
|
||||
|
||||
// do not make use of
|
||||
// org.eclipse.hawkbit.repository.jpa.JpaActionManagement.handleAddUpdateActionStatus
|
||||
// to bypass the quota check. Otherwise the action will not be confirmed in case
|
||||
// of exceeded action status quota.
|
||||
action.setStatus(Status.RUNNING);
|
||||
actionStatus.setAction(action);
|
||||
|
||||
actionStatusRepository.save(actionStatus);
|
||||
return actionRepository.save(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void deactivateAutoConfirmation(String controllerId) {
|
||||
LOG.debug("Deactivate auto confirmation for controllerId '{}'", controllerId);
|
||||
final JpaTarget target = getTargetByControllerIdAndThrowIfNotFound(controllerId);
|
||||
target.setAutoConfirmationStatus(null);
|
||||
targetRepository.save(target);
|
||||
}
|
||||
|
||||
private JpaTarget getTargetByControllerIdAndThrowIfNotFound(final String controllerId) {
|
||||
return targetRepository.findOne(TargetSpecifications.hasControllerId(controllerId))
|
||||
.orElseThrow(() -> new EntityNotFoundException(Target.class, controllerId));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActionStatusUpdate(final Status updatedActionStatus, final JpaAction action) {
|
||||
if (updatedActionStatus == Status.RUNNING && action.isActive()) {
|
||||
action.setStatus(Status.RUNNING);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.jpa;
|
||||
|
||||
import static org.eclipse.hawkbit.repository.model.Action.ActionType.DOWNLOAD_ONLY;
|
||||
import static org.eclipse.hawkbit.repository.model.Action.Status.DOWNLOADED;
|
||||
import static org.eclipse.hawkbit.repository.model.Action.Status.FINISHED;
|
||||
import static org.eclipse.hawkbit.repository.model.Target.CONTROLLER_ATTRIBUTE_KEY_SIZE;
|
||||
@@ -39,6 +38,7 @@ import javax.persistence.criteria.Root;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import org.eclipse.hawkbit.repository.ConfirmationManagement;
|
||||
import org.eclipse.hawkbit.repository.ControllerManagement;
|
||||
import org.eclipse.hawkbit.repository.EntityFactory;
|
||||
import org.eclipse.hawkbit.repository.MaintenanceScheduleHelper;
|
||||
@@ -72,6 +72,7 @@ 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.ActionStatus;
|
||||
import org.eclipse.hawkbit.repository.model.AutoConfirmationStatus;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModule;
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata;
|
||||
@@ -125,12 +126,6 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
|
||||
@Autowired
|
||||
private SoftwareModuleRepository softwareModuleRepository;
|
||||
|
||||
@Autowired
|
||||
private ActionStatusRepository actionStatusRepository;
|
||||
|
||||
@Autowired
|
||||
private QuotaManagement quotaManagement;
|
||||
|
||||
@Autowired
|
||||
private TenantConfigurationManagement tenantConfigurationManagement;
|
||||
|
||||
@@ -155,9 +150,13 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
|
||||
@Autowired
|
||||
private TenantAware tenantAware;
|
||||
|
||||
@Autowired
|
||||
private ConfirmationManagement confirmationManagement;
|
||||
|
||||
public JpaControllerManagement(final ScheduledExecutorService executorService,
|
||||
final RepositoryProperties repositoryProperties, final ActionRepository actionRepository) {
|
||||
super(actionRepository, repositoryProperties);
|
||||
final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository,
|
||||
final QuotaManagement quotaManagement, final RepositoryProperties repositoryProperties) {
|
||||
super(actionRepository, actionStatusRepository, quotaManagement, repositoryProperties);
|
||||
|
||||
if (!repositoryProperties.isEagerPollPersistence()) {
|
||||
executorService.scheduleWithFixedDelay(this::flushUpdateQueue,
|
||||
@@ -565,11 +564,6 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
|
||||
return action;
|
||||
}
|
||||
|
||||
private void assertActionStatusMessageQuota(final JpaActionStatus actionStatus) {
|
||||
QuotaHelper.assertAssignmentQuota(actionStatus.getId(), actionStatus.getMessages().size(),
|
||||
quotaManagement.getMaxMessagesPerActionStatus(), "Message", ActionStatus.class.getSimpleName(), null);
|
||||
}
|
||||
|
||||
private void handleFinishedCancelation(final JpaActionStatus actionStatus, final JpaAction action) {
|
||||
// in case of successful cancellation we also report the success at
|
||||
// the canceled action itself.
|
||||
@@ -582,88 +576,41 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
|
||||
@Transactional(isolation = Isolation.READ_COMMITTED)
|
||||
@Retryable(include = {
|
||||
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
|
||||
public Action addUpdateActionStatus(final ActionStatusCreate c) {
|
||||
final JpaActionStatusCreate create = (JpaActionStatusCreate) c;
|
||||
final JpaAction action = getActionAndThrowExceptionIfNotFound(create.getActionId());
|
||||
final JpaActionStatus actionStatus = create.build();
|
||||
|
||||
if (isUpdatingActionStatusAllowed(action, actionStatus)) {
|
||||
return handleAddUpdateActionStatus(actionStatus, action);
|
||||
}
|
||||
|
||||
LOG.debug("Update of actionStatus {} for action {} not possible since action not active anymore.",
|
||||
actionStatus.getStatus(), action.getId());
|
||||
return action;
|
||||
public Action addUpdateActionStatus(final ActionStatusCreate statusCreate) {
|
||||
return addActionStatus((JpaActionStatusCreate) statusCreate);
|
||||
}
|
||||
|
||||
/**
|
||||
* ActionStatus updates are allowed mainly if the action is active. If the
|
||||
* action is not active we accept further status updates if permitted so by
|
||||
* repository configuration. In this case, only the values: Status.ERROR and
|
||||
* Status.FINISHED are allowed. In the case of a DOWNLOAD_ONLY action, we accept
|
||||
* status updates only once.
|
||||
*/
|
||||
private boolean isUpdatingActionStatusAllowed(final JpaAction action, final JpaActionStatus actionStatus) {
|
||||
|
||||
final boolean isIntermediateFeedback = (FINISHED != actionStatus.getStatus())
|
||||
&& (Status.ERROR != actionStatus.getStatus());
|
||||
|
||||
final boolean isAllowedByRepositoryConfiguration = !repositoryProperties.isRejectActionStatusForClosedAction()
|
||||
&& isIntermediateFeedback;
|
||||
|
||||
final boolean isAllowedForDownloadOnlyActions = isDownloadOnly(action) && !isIntermediateFeedback;
|
||||
|
||||
return action.isActive() || isAllowedByRepositoryConfiguration || isAllowedForDownloadOnlyActions;
|
||||
}
|
||||
|
||||
private static boolean isDownloadOnly(final JpaAction action) {
|
||||
return DOWNLOAD_ONLY == action.getActionType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets {@link TargetUpdateStatus} based on given {@link ActionStatus}.
|
||||
*/
|
||||
private Action handleAddUpdateActionStatus(final JpaActionStatus actionStatus, final JpaAction action) {
|
||||
|
||||
String controllerId = null;
|
||||
LOG.debug("handleAddUpdateActionStatus for action {}", action.getId());
|
||||
|
||||
// information status entry - check for a potential DOS attack
|
||||
assertActionStatusQuota(action);
|
||||
assertActionStatusMessageQuota(actionStatus);
|
||||
|
||||
switch (actionStatus.getStatus()) {
|
||||
@Override
|
||||
protected void onActionStatusUpdate(final Action.Status updatedActionStatus, final JpaAction action) {
|
||||
switch (updatedActionStatus) {
|
||||
case ERROR:
|
||||
final JpaTarget target = (JpaTarget) action.getTarget();
|
||||
target.setUpdateStatus(TargetUpdateStatus.ERROR);
|
||||
handleErrorOnAction(action, target);
|
||||
break;
|
||||
case FINISHED:
|
||||
controllerId = handleFinishedAndStoreInTargetStatus(action);
|
||||
handleFinishedAndStoreInTargetStatus(action).ifPresent(this::requestControllerAttributes);
|
||||
break;
|
||||
case DOWNLOADED:
|
||||
controllerId = handleDownloadedActionStatus(action);
|
||||
handleDownloadedActionStatus(action).ifPresent(this::requestControllerAttributes);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
actionStatus.setAction(action);
|
||||
actionStatusRepository.save(actionStatus);
|
||||
|
||||
action.setLastActionStatusCode(actionStatus.getCode().orElse(null));
|
||||
final Action savedAction = actionRepository.save(action);
|
||||
|
||||
if (controllerId != null) {
|
||||
requestControllerAttributes(controllerId);
|
||||
}
|
||||
|
||||
return savedAction;
|
||||
}
|
||||
|
||||
private String handleDownloadedActionStatus(final JpaAction action) {
|
||||
/**
|
||||
* Handles the case where the {@link Action.Status#DOWNLOADED} status is
|
||||
* reported by the device. In case the update is finished, a controllerId will
|
||||
* be returned to trigger a request for attributes.
|
||||
*
|
||||
* @param action
|
||||
* updated action
|
||||
* @return a present controllerId in case the attributes needs to be requested.
|
||||
*/
|
||||
private Optional<String> handleDownloadedActionStatus(final JpaAction action) {
|
||||
if (!isDownloadOnly(action)) {
|
||||
return null;
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
final JpaTarget target = (JpaTarget) action.getTarget();
|
||||
@@ -672,7 +619,7 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
|
||||
target.setUpdateStatus(TargetUpdateStatus.IN_SYNC);
|
||||
targetRepository.save(target);
|
||||
|
||||
return target.getControllerId();
|
||||
return Optional.of(target.getControllerId());
|
||||
}
|
||||
|
||||
private void requestControllerAttributes(final String controllerId) {
|
||||
@@ -695,12 +642,16 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
|
||||
targetRepository.save(mergedTarget);
|
||||
}
|
||||
|
||||
private void assertActionStatusQuota(final JpaAction action) {
|
||||
QuotaHelper.assertAssignmentQuota(action.getId(), 1, quotaManagement.getMaxStatusEntriesPerAction(),
|
||||
ActionStatus.class, Action.class, actionStatusRepository::countByActionId);
|
||||
}
|
||||
|
||||
private String handleFinishedAndStoreInTargetStatus(final JpaAction action) {
|
||||
/**
|
||||
* Handles the case where the {@link Action.Status#FINISHED} status is
|
||||
* reported by the device. In case the update is finished, a controllerId will
|
||||
* be returned to trigger a request for attributes.
|
||||
*
|
||||
* @param action
|
||||
* updated action
|
||||
* @return a present controllerId in case the attributes needs to be requested.
|
||||
*/
|
||||
private Optional<String> handleFinishedAndStoreInTargetStatus(final JpaAction action) {
|
||||
final JpaTarget target = (JpaTarget) action.getTarget();
|
||||
action.setActive(false);
|
||||
action.setStatus(Status.FINISHED);
|
||||
@@ -727,7 +678,7 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
|
||||
targetRepository.save(target);
|
||||
entityManager.detach(ds);
|
||||
|
||||
return target.getControllerId();
|
||||
return Optional.of(target.getControllerId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -884,11 +835,6 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
|
||||
return actionStatusRepository.save(statusMessage);
|
||||
}
|
||||
|
||||
private JpaAction getActionAndThrowExceptionIfNotFound(final Long actionId) {
|
||||
return actionRepository.findById(actionId)
|
||||
.orElseThrow(() -> new EntityNotFoundException(Action.class, actionId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Target> getByControllerId(final String controllerId) {
|
||||
return targetRepository.findOne(TargetSpecifications.hasControllerId(controllerId)).map(Target.class::cast);
|
||||
@@ -1076,6 +1022,17 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AutoConfirmationStatus activateAutoConfirmation(final String controllerId, final String initiator,
|
||||
final String remark) {
|
||||
return confirmationManagement.activateAutoConfirmation(controllerId, initiator, remark);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deactivateAutoConfirmation(final String controllerId) {
|
||||
confirmationManagement.deactivateAutoConfirmation(controllerId);
|
||||
}
|
||||
|
||||
private void cancelAssignDistributionSetEvent(final Action action) {
|
||||
afterCommit.afterCommit(() -> eventPublisherHolder.getEventPublisher().publishEvent(
|
||||
new CancelTargetAssignmentEvent(action, eventPublisherHolder.getApplicationId())));
|
||||
|
||||
@@ -16,6 +16,7 @@ import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
@@ -61,7 +62,6 @@ import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility;
|
||||
import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications;
|
||||
import org.eclipse.hawkbit.repository.jpa.utils.DeploymentHelper;
|
||||
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
|
||||
import org.eclipse.hawkbit.repository.jpa.utils.TenantConfigHelper;
|
||||
import org.eclipse.hawkbit.repository.jpa.utils.WeightValidationHelper;
|
||||
import org.eclipse.hawkbit.repository.model.Action;
|
||||
import org.eclipse.hawkbit.repository.model.Action.ActionType;
|
||||
@@ -81,6 +81,7 @@ import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder;
|
||||
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
|
||||
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
||||
import org.eclipse.hawkbit.tenancy.TenantAware;
|
||||
import org.eclipse.hawkbit.utils.TenantConfigHelper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.dao.ConcurrencyFailureException;
|
||||
@@ -138,14 +139,12 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
|
||||
private final DistributionSetManagement distributionSetManagement;
|
||||
private final DistributionSetRepository distributionSetRepository;
|
||||
private final TargetRepository targetRepository;
|
||||
private final ActionStatusRepository actionStatusRepository;
|
||||
private final AuditorAware<String> auditorProvider;
|
||||
private final VirtualPropertyReplacer virtualPropertyReplacer;
|
||||
private final PlatformTransactionManager txManager;
|
||||
private final OnlineDsAssignmentStrategy onlineDsAssignmentStrategy;
|
||||
private final OfflineDsAssignmentStrategy offlineDsAssignmentStrategy;
|
||||
private final TenantConfigurationManagement tenantConfigurationManagement;
|
||||
private final QuotaManagement quotaManagement;
|
||||
private final SystemSecurityContext systemSecurityContext;
|
||||
private final TenantAware tenantAware;
|
||||
private final Database database;
|
||||
@@ -160,26 +159,25 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
|
||||
final TenantConfigurationManagement tenantConfigurationManagement, final QuotaManagement quotaManagement,
|
||||
final SystemSecurityContext systemSecurityContext, final TenantAware tenantAware, final Database database,
|
||||
final RepositoryProperties repositoryProperties) {
|
||||
super(actionRepository, repositoryProperties);
|
||||
super(actionRepository, actionStatusRepository, quotaManagement, repositoryProperties);
|
||||
this.entityManager = entityManager;
|
||||
this.distributionSetRepository = distributionSetRepository;
|
||||
this.distributionSetManagement = distributionSetManagement;
|
||||
this.targetRepository = targetRepository;
|
||||
this.actionStatusRepository = actionStatusRepository;
|
||||
this.auditorProvider = auditorProvider;
|
||||
this.virtualPropertyReplacer = virtualPropertyReplacer;
|
||||
this.txManager = txManager;
|
||||
onlineDsAssignmentStrategy = new OnlineDsAssignmentStrategy(targetRepository, afterCommit, eventPublisherHolder,
|
||||
actionRepository, actionStatusRepository, quotaManagement, this::isMultiAssignmentsEnabled);
|
||||
actionRepository, actionStatusRepository, quotaManagement, this::isMultiAssignmentsEnabled,
|
||||
this::isConfirmationFlowEnabled);
|
||||
offlineDsAssignmentStrategy = new OfflineDsAssignmentStrategy(targetRepository, afterCommit,
|
||||
eventPublisherHolder, actionRepository, actionStatusRepository, quotaManagement,
|
||||
this::isMultiAssignmentsEnabled);
|
||||
this::isMultiAssignmentsEnabled, this::isConfirmationFlowEnabled);
|
||||
this.tenantConfigurationManagement = tenantConfigurationManagement;
|
||||
this.quotaManagement = quotaManagement;
|
||||
this.systemSecurityContext = systemSecurityContext;
|
||||
this.tenantAware = tenantAware;
|
||||
this.database = database;
|
||||
retryTemplate = createRetryTemplate();
|
||||
this.retryTemplate = createRetryTemplate();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -393,7 +391,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
|
||||
targetEntitiesIdsChunks.forEach(this::cancelInactiveScheduledActionsForTargets);
|
||||
setAssignedDistributionSetAndTargetUpdateStatus(assignmentStrategy, distributionSetEntity,
|
||||
targetEntitiesIdsChunks);
|
||||
final List<JpaAction> assignedActions = createActions(initiatedBy, targetsWithActionType, targetEntities,
|
||||
final Map<TargetWithActionType, JpaAction> assignedActions = createActions(initiatedBy, targetsWithActionType, targetEntities,
|
||||
assignmentStrategy, distributionSetEntity);
|
||||
// create initial action status when action is created so we remember
|
||||
// the initial running status because we will change the status
|
||||
@@ -402,7 +400,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
|
||||
createActionsStatus(assignedActions, assignmentStrategy, actionMessage);
|
||||
|
||||
detachEntitiesAndSendTargetUpdatedEvents(distributionSetEntity, targetEntities, assignmentStrategy);
|
||||
return assignedActions;
|
||||
return new ArrayList<>(assignedActions.values());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -473,20 +471,62 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
|
||||
assignmentStrategy.setAssignedDistributionSetAndTargetStatus(set, targetIdsChunks, currentUser);
|
||||
}
|
||||
|
||||
private List<JpaAction> createActions(final String initiatedBy,
|
||||
private Map<TargetWithActionType, JpaAction> createActions(final String initiatedBy,
|
||||
final Collection<TargetWithActionType> targetsWithActionType, final List<JpaTarget> targets,
|
||||
final AbstractDsAssignmentStrategy assignmentStrategy, final JpaDistributionSet set) {
|
||||
|
||||
return targetsWithActionType.stream()
|
||||
.map(twt -> assignmentStrategy.createTargetAction(initiatedBy, twt, targets, set))
|
||||
.filter(Objects::nonNull).map(actionRepository::save).collect(Collectors.toList());
|
||||
final Map<TargetWithActionType, JpaAction> persistedActions = new LinkedHashMap<>();
|
||||
|
||||
for (final TargetWithActionType twt : targetsWithActionType) {
|
||||
final JpaAction targetAction = assignmentStrategy.createTargetAction(initiatedBy, twt, targets, set);
|
||||
if (targetAction != null) {
|
||||
persistedActions.put(twt, actionRepository.save(targetAction));
|
||||
}
|
||||
}
|
||||
return persistedActions;
|
||||
}
|
||||
|
||||
private void createActionsStatus(final Collection<JpaAction> actions,
|
||||
private void createActionsStatus(final Map<TargetWithActionType, JpaAction> actions,
|
||||
final AbstractDsAssignmentStrategy assignmentStrategy, final String actionMessage) {
|
||||
actionStatusRepository
|
||||
.saveAll(actions.stream().map(action -> assignmentStrategy.createActionStatus(action, actionMessage))
|
||||
.collect(Collectors.toList()));
|
||||
actionStatusRepository.saveAll(actions.entrySet().stream().map(entry -> {
|
||||
final JpaAction action = entry.getValue();
|
||||
final JpaActionStatus actionStatus = assignmentStrategy.createActionStatus(action, actionMessage);
|
||||
verifyAndAddConfirmationStatus(action, actionStatus, entry.getKey().isConfirmationRequired());
|
||||
return actionStatus;
|
||||
}).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
private void setInitialActionStatusOfRolloutGroup(final List<JpaAction> actions) {
|
||||
final List<JpaActionStatus> statusList = new ArrayList<>();
|
||||
for (final JpaAction action : actions) {
|
||||
final JpaActionStatus actionStatus = onlineDsAssignmentStrategy.createActionStatus(action, null);
|
||||
verifyAndAddConfirmationStatus(action, actionStatus, action.getRolloutGroup().isConfirmationRequired());
|
||||
statusList.add(actionStatus);
|
||||
}
|
||||
actionStatusRepository.saveAll(statusList);
|
||||
}
|
||||
|
||||
private void verifyAndAddConfirmationStatus(final JpaAction action, final JpaActionStatus actionStatus,
|
||||
final boolean isConfirmationRequired) {
|
||||
if (actionStatus.getStatus() == Status.WAIT_FOR_CONFIRMATION) {
|
||||
if (action.getStatus().equals(Status.RUNNING)) {
|
||||
// action is in RUNNING state only if it's confirmed during assignment already
|
||||
if (!isConfirmationRequired) {
|
||||
// confirmation given on assignment dialog
|
||||
actionStatus.addMessage(
|
||||
String.format("Assignment confirmed by initiator [%s].", action.getInitiatedBy()));
|
||||
} else if (action.getTarget().getAutoConfirmationStatus() != null) {
|
||||
// auto-confirmation is configured
|
||||
actionStatus.addMessage(action.getTarget().getAutoConfirmationStatus().constructActionMessage());
|
||||
} else {
|
||||
throw new IllegalStateException("Action in RUNNING state without given confirmation.");
|
||||
}
|
||||
|
||||
} else {
|
||||
actionStatus
|
||||
.addMessage("Waiting for the confirmation by the device before processing with the deployment");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void detachEntitiesAndSendTargetUpdatedEvents(final JpaDistributionSet set, final List<JpaTarget> targets,
|
||||
@@ -643,15 +683,15 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
|
||||
if (!isMultiAssignmentsEnabled()) {
|
||||
closeOrCancelOpenDeviceActions(actions);
|
||||
}
|
||||
final List<JpaAction> savedActions = activateActions(actions);
|
||||
setInitialActionStatus(savedActions);
|
||||
final List<JpaAction> savedActions = activateActionsOfRolloutGroup(actions);
|
||||
setInitialActionStatusOfRolloutGroup(savedActions);
|
||||
setAssignmentOnTargets(savedActions);
|
||||
return Collections.unmodifiableList(savedActions);
|
||||
}
|
||||
|
||||
private void closeOrCancelOpenDeviceActions(final List<JpaAction> actions) {
|
||||
private void closeOrCancelOpenDeviceActions(final List<JpaAction> actions){
|
||||
final List<Long> targetIds = actions.stream().map(JpaAction::getTarget).map(Target::getId)
|
||||
.collect(Collectors.toList());
|
||||
.collect(Collectors.toList());
|
||||
if (isActionsAutocloseEnabled()) {
|
||||
onlineDsAssignmentStrategy.closeObsoleteUpdateActions(targetIds);
|
||||
} else {
|
||||
@@ -659,9 +699,15 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
|
||||
}
|
||||
}
|
||||
|
||||
private List<JpaAction> activateActions(final List<JpaAction> actions) {
|
||||
private List<JpaAction> activateActionsOfRolloutGroup(final List<JpaAction> actions) {
|
||||
actions.forEach(action -> {
|
||||
action.setActive(true);
|
||||
final boolean confirmationRequired = action.getRolloutGroup().isConfirmationRequired()
|
||||
&& action.getTarget().getAutoConfirmationStatus() == null;
|
||||
if (isConfirmationFlowEnabled() && confirmationRequired) {
|
||||
action.setStatus(Status.WAIT_FOR_CONFIRMATION);
|
||||
return;
|
||||
}
|
||||
action.setStatus(Status.RUNNING);
|
||||
});
|
||||
return actionRepository.saveAll(actions);
|
||||
@@ -678,14 +724,6 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
|
||||
targetRepository.saveAll(assignedDsTargets);
|
||||
}
|
||||
|
||||
private void setInitialActionStatus(final List<JpaAction> actions) {
|
||||
final List<JpaActionStatus> statusList = new ArrayList<>();
|
||||
for (final JpaAction action : actions) {
|
||||
statusList.add(onlineDsAssignmentStrategy.createActionStatus(action, null));
|
||||
}
|
||||
actionStatusRepository.saveAll(statusList);
|
||||
}
|
||||
|
||||
private void setSkipActionStatus(final JpaAction action) {
|
||||
final JpaActionStatus actionStatus = new JpaActionStatus();
|
||||
actionStatus.setAction(action);
|
||||
@@ -745,7 +783,6 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
|
||||
public List<Action> findActiveActionsWithHighestWeight(final String controllerId, final int maxActionCount) {
|
||||
return findActiveActionsWithHighestWeightConsideringDefault(controllerId, maxActionCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWeightConsideringDefault(final Action action) {
|
||||
return super.getWeightConsideringDefault(action);
|
||||
@@ -967,6 +1004,11 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
|
||||
.isMultiAssignmentsEnabled();
|
||||
}
|
||||
|
||||
private boolean isConfirmationFlowEnabled() {
|
||||
return TenantConfigHelper.usingContext(systemSecurityContext, tenantConfigurationManagement)
|
||||
.isConfirmationFlowEnabled();
|
||||
}
|
||||
|
||||
private <T extends Serializable> T getConfigValue(final String key, final Class<T> valueType) {
|
||||
return systemSecurityContext
|
||||
.runAsSystem(() -> tenantConfigurationManagement.getConfigurationValue(key, valueType).getValue());
|
||||
@@ -1002,5 +1044,4 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -71,6 +71,7 @@ import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder;
|
||||
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
|
||||
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
||||
import org.eclipse.hawkbit.tenancy.TenantAware;
|
||||
import org.eclipse.hawkbit.utils.TenantConfigHelper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -193,10 +194,11 @@ public class JpaRolloutManagement implements RolloutManagement {
|
||||
@Transactional
|
||||
@Retryable(include = {
|
||||
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
|
||||
public Rollout create(final RolloutCreate rollout, final int amountGroup, final RolloutGroupConditions conditions) {
|
||||
public Rollout create(final RolloutCreate rollout, final int amountGroup, final boolean confirmationRequired,
|
||||
final RolloutGroupConditions conditions) {
|
||||
RolloutHelper.verifyRolloutGroupParameter(amountGroup, quotaManagement);
|
||||
final JpaRollout savedRollout = createRollout((JpaRollout) rollout.build());
|
||||
return createRolloutGroups(amountGroup, conditions, savedRollout);
|
||||
return createRolloutGroups(amountGroup, conditions, savedRollout, confirmationRequired);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -222,7 +224,7 @@ public class JpaRolloutManagement implements RolloutManagement {
|
||||
}
|
||||
|
||||
private Rollout createRolloutGroups(final int amountOfGroups, final RolloutGroupConditions conditions,
|
||||
final JpaRollout rollout) {
|
||||
final JpaRollout rollout, final boolean isConfirmationRequired) {
|
||||
RolloutHelper.verifyRolloutInStatus(rollout, RolloutStatus.CREATING);
|
||||
RolloutHelper.verifyRolloutGroupConditions(conditions);
|
||||
|
||||
@@ -241,6 +243,7 @@ public class JpaRolloutManagement implements RolloutManagement {
|
||||
group.setRollout(savedRollout);
|
||||
group.setParent(lastSavedGroup);
|
||||
group.setStatus(RolloutGroupStatus.CREATING);
|
||||
group.setConfirmationRequired(isConfirmationRequired);
|
||||
|
||||
addSuccessAndErrorConditionsAndActions(group, conditions);
|
||||
|
||||
@@ -283,6 +286,7 @@ public class JpaRolloutManagement implements RolloutManagement {
|
||||
group.setRollout(savedRollout);
|
||||
group.setParent(lastSavedGroup);
|
||||
group.setStatus(RolloutGroupStatus.CREATING);
|
||||
group.setConfirmationRequired(srcGroup.isConfirmationRequired());
|
||||
|
||||
group.setTargetPercentage(srcGroup.getTargetPercentage());
|
||||
if (srcGroup.getTargetFilterQuery() != null) {
|
||||
|
||||
@@ -43,6 +43,7 @@ import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
|
||||
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
|
||||
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
||||
import org.eclipse.hawkbit.tenancy.TenantAware;
|
||||
import org.eclipse.hawkbit.utils.TenantConfigHelper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.dao.ConcurrencyFailureException;
|
||||
@@ -249,6 +250,7 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
|
||||
// set the new query
|
||||
targetFilterQuery.setQuery(query);
|
||||
});
|
||||
update.getConfirmationRequired().ifPresent(targetFilterQuery::setConfirmationRequired);
|
||||
|
||||
return targetFilterQueryRepository.save(targetFilterQuery);
|
||||
}
|
||||
@@ -263,6 +265,7 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
|
||||
targetFilterQuery.setAutoAssignActionType(null);
|
||||
targetFilterQuery.setAutoAssignWeight(null);
|
||||
targetFilterQuery.setAutoAssignInitiatedBy(null);
|
||||
targetFilterQuery.setConfirmationRequired(false);
|
||||
} else {
|
||||
WeightValidationHelper.usingContext(systemSecurityContext, tenantConfigurationManagement).validate(update);
|
||||
// we cannot be sure that the quota was enforced at creation time
|
||||
@@ -277,10 +280,18 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
|
||||
targetFilterQuery.setAutoAssignInitiatedBy(tenantAware.getCurrentUsername());
|
||||
targetFilterQuery.setAutoAssignActionType(sanitizeAutoAssignActionType(update.getActionType()));
|
||||
targetFilterQuery.setAutoAssignWeight(update.getWeight());
|
||||
final boolean confirmationRequired = update.isConfirmationRequired() == null ? isConfirmationFlowEnabled()
|
||||
: update.isConfirmationRequired();
|
||||
targetFilterQuery.setConfirmationRequired(confirmationRequired);
|
||||
}
|
||||
return targetFilterQueryRepository.save(targetFilterQuery);
|
||||
}
|
||||
|
||||
private boolean isConfirmationFlowEnabled() {
|
||||
return TenantConfigHelper.usingContext(systemSecurityContext, tenantConfigurationManagement)
|
||||
.isConfirmationFlowEnabled();
|
||||
}
|
||||
|
||||
private static void verifyDistributionSetAndThrowExceptionIfDeleted(final DistributionSet distributionSet) {
|
||||
if (distributionSet.isDeleted()) {
|
||||
throw new EntityNotFoundException(DistributionSet.class, distributionSet.getId());
|
||||
|
||||
@@ -40,12 +40,14 @@ import org.eclipse.hawkbit.repository.builder.TargetUpdate;
|
||||
import org.eclipse.hawkbit.repository.event.remote.TargetAttributesRequestedEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.TargetDeletedEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent;
|
||||
import org.eclipse.hawkbit.repository.exception.AutoConfirmationAlreadyActiveException;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||
import org.eclipse.hawkbit.repository.jpa.builder.JpaTargetCreate;
|
||||
import org.eclipse.hawkbit.repository.jpa.builder.JpaTargetUpdate;
|
||||
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
|
||||
import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaAutoConfirmationStatus;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetMetadata;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetMetadata_;
|
||||
@@ -57,6 +59,7 @@ import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility;
|
||||
import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder;
|
||||
import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications;
|
||||
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
|
||||
import org.eclipse.hawkbit.repository.model.AutoConfirmationStatus;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSetType;
|
||||
import org.eclipse.hawkbit.repository.model.MetaData;
|
||||
|
||||
@@ -45,9 +45,10 @@ public class OfflineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
|
||||
OfflineDsAssignmentStrategy(final TargetRepository targetRepository,
|
||||
final AfterTransactionCommitExecutor afterCommit, final EventPublisherHolder eventPublisherHolder,
|
||||
final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository,
|
||||
final QuotaManagement quotaManagement, final BooleanSupplier multiAssignmentsConfig) {
|
||||
final QuotaManagement quotaManagement, final BooleanSupplier multiAssignmentsConfig,
|
||||
final BooleanSupplier confirmationFlowConfig) {
|
||||
super(targetRepository, afterCommit, eventPublisherHolder, actionRepository, actionStatusRepository,
|
||||
quotaManagement, multiAssignmentsConfig);
|
||||
quotaManagement, multiAssignmentsConfig, confirmationFlowConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -48,9 +48,10 @@ public class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
|
||||
OnlineDsAssignmentStrategy(final TargetRepository targetRepository,
|
||||
final AfterTransactionCommitExecutor afterCommit, final EventPublisherHolder eventPublisherHolder,
|
||||
final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository,
|
||||
final QuotaManagement quotaManagement, final BooleanSupplier multiAssignmentsConfig) {
|
||||
final QuotaManagement quotaManagement, final BooleanSupplier multiAssignmentsConfig,
|
||||
final BooleanSupplier confirmationFlowConfig) {
|
||||
super(targetRepository, afterCommit, eventPublisherHolder, actionRepository, actionStatusRepository,
|
||||
quotaManagement, multiAssignmentsConfig);
|
||||
quotaManagement, multiAssignmentsConfig, confirmationFlowConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -130,15 +131,28 @@ public class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
|
||||
final List<JpaTarget> targets, final JpaDistributionSet set) {
|
||||
final JpaAction result = super.createTargetAction(initiatedBy, targetWithActionType, targets, set);
|
||||
if (result != null) {
|
||||
result.setStatus(Status.RUNNING);
|
||||
final boolean confirmationRequired = targetWithActionType.isConfirmationRequired()
|
||||
&& result.getTarget().getAutoConfirmationStatus() == null;
|
||||
if (isConfirmationFlowEnabled() && confirmationRequired) {
|
||||
result.setStatus(Status.WAIT_FOR_CONFIRMATION);
|
||||
} else {
|
||||
result.setStatus(Status.RUNNING);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will be called to create the initial action status for an action
|
||||
*/
|
||||
@Override
|
||||
public JpaActionStatus createActionStatus(final JpaAction action, final String actionMessage) {
|
||||
final JpaActionStatus result = super.createActionStatus(action, actionMessage);
|
||||
result.setStatus(Status.RUNNING);
|
||||
if (isConfirmationFlowEnabled()) {
|
||||
result.setStatus(Status.WAIT_FOR_CONFIRMATION);
|
||||
} else {
|
||||
result.setStatus(Status.RUNNING);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.eclipse.hawkbit.repository.ArtifactEncryptionSecretsStore;
|
||||
import org.eclipse.hawkbit.repository.ArtifactEncryptionService;
|
||||
import org.eclipse.hawkbit.repository.ArtifactManagement;
|
||||
import org.eclipse.hawkbit.repository.BaseRepositoryTypeProvider;
|
||||
import org.eclipse.hawkbit.repository.ConfirmationManagement;
|
||||
import org.eclipse.hawkbit.repository.ControllerManagement;
|
||||
import org.eclipse.hawkbit.repository.DeploymentManagement;
|
||||
import org.eclipse.hawkbit.repository.DistributionSetManagement;
|
||||
@@ -742,6 +743,16 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
|
||||
quotaManagement, systemSecurityContext, tenantAware, properties.getDatabase(), repositoryProperties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
ConfirmationManagement confirmationManagement(final TargetRepository targetRepository,
|
||||
final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository,
|
||||
final RepositoryProperties repositoryProperties, final QuotaManagement quotaManagement,
|
||||
final EntityFactory entityFactory) {
|
||||
return new JpaConfirmationManagement(targetRepository, actionRepository, actionStatusRepository,
|
||||
repositoryProperties, quotaManagement, entityFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link JpaControllerManagement} bean.
|
||||
*
|
||||
@@ -750,8 +761,10 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
ControllerManagement controllerManagement(final ScheduledExecutorService executorService,
|
||||
final RepositoryProperties repositoryProperties, final ActionRepository actionRepository) {
|
||||
return new JpaControllerManagement(executorService, repositoryProperties, actionRepository);
|
||||
final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository,
|
||||
final QuotaManagement quotaManagement, final RepositoryProperties repositoryProperties) {
|
||||
return new JpaControllerManagement(executorService, actionRepository, actionStatusRepository, quotaManagement,
|
||||
repositoryProperties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -167,7 +167,7 @@ public abstract class AbstractAutoAssignExecutor implements AutoAssignExecutor {
|
||||
.map(controllerId -> DeploymentManagement
|
||||
.deploymentRequest(controllerId, filterQuery.getAutoAssignDistributionSet().getId())
|
||||
.setActionType(autoAssignActionType).setWeight(filterQuery.getAutoAssignWeight().orElse(null))
|
||||
.build())
|
||||
.setConfirmationRequired(filterQuery.isConfirmationRequired()).build())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ public class JpaRolloutGroupCreate extends AbstractRolloutGroupCreate<RolloutGro
|
||||
if (conditions != null) {
|
||||
addSuccessAndErrorConditionsAndActions(group, conditions);
|
||||
}
|
||||
group.setConfirmationRequired(confirmationRequired);
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
@@ -31,11 +31,10 @@ public class JpaTargetFilterQueryCreate extends AbstractTargetFilterQueryUpdateC
|
||||
|
||||
@Override
|
||||
public JpaTargetFilterQuery build() {
|
||||
|
||||
return new JpaTargetFilterQuery(name, query,
|
||||
getAutoAssignDistributionSetId().map(distributionSetManagement::getValidAndComplete).orElse(null),
|
||||
getAutoAssignActionType().filter(JpaTargetFilterQueryCreate::isAutoAssignActionTypeValid).orElse(null),
|
||||
weight);
|
||||
weight, getConfirmationRequired().orElse(false));
|
||||
}
|
||||
|
||||
private static boolean isAutoAssignActionTypeValid(final ActionType actionType) {
|
||||
|
||||
@@ -108,7 +108,8 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
|
||||
@ConversionValue(objectValue = "DOWNLOAD", dataValue = "7"),
|
||||
@ConversionValue(objectValue = "SCHEDULED", dataValue = "8"),
|
||||
@ConversionValue(objectValue = "CANCEL_REJECTED", dataValue = "9"),
|
||||
@ConversionValue(objectValue = "DOWNLOADED", dataValue = "10") })
|
||||
@ConversionValue(objectValue = "DOWNLOADED", dataValue = "10"),
|
||||
@ConversionValue(objectValue = "WAIT_FOR_CONFIRMATION", dataValue = "11")})
|
||||
@Convert("status")
|
||||
@NotNull
|
||||
private Status status;
|
||||
@@ -236,7 +237,8 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
|
||||
public String toString() {
|
||||
return "JpaAction [distributionSet=" + distributionSet.getId() + ", version=" + getOptLockRevision() + ", id="
|
||||
+ getId() + ", actionType=" + getActionType() + ", weight=" + getWeight() + ", isActive=" + isActive()
|
||||
+ ", createdAt=" + getCreatedAt() + ", lastModifiedAt=" + getLastModifiedAt() + "]";
|
||||
+ ", createdAt=" + getCreatedAt() + ", lastModifiedAt=" + getLastModifiedAt() + ", status="
|
||||
+ getStatus().name() + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -387,4 +389,8 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
|
||||
public void setLastActionStatusCode(final Integer lastActionStatusCode) {
|
||||
this.lastActionStatusCode = lastActionStatusCode;
|
||||
}
|
||||
|
||||
public boolean isWaitingConfirmation() {
|
||||
return status == Status.WAIT_FOR_CONFIRMATION;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,8 @@ public class JpaActionStatus extends AbstractJpaTenantAwareBaseEntity implements
|
||||
@ConversionValue(objectValue = "DOWNLOAD", dataValue = "7"),
|
||||
@ConversionValue(objectValue = "SCHEDULED", dataValue = "8"),
|
||||
@ConversionValue(objectValue = "CANCEL_REJECTED", dataValue = "9"),
|
||||
@ConversionValue(objectValue = "DOWNLOADED", dataValue = "10")})
|
||||
@ConversionValue(objectValue = "DOWNLOADED", dataValue = "10"),
|
||||
@ConversionValue(objectValue = "WAIT_FOR_CONFIRMATION", dataValue = "11")})
|
||||
@Convert("status")
|
||||
@NotNull
|
||||
private Status status;
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* Copyright (c) 2022 Bosch.IO 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.jpa.model;
|
||||
|
||||
import org.eclipse.hawkbit.repository.model.AutoConfirmationStatus;
|
||||
import org.eclipse.hawkbit.repository.model.NamedEntity;
|
||||
import org.eclipse.hawkbit.repository.model.Target;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.ConstraintMode;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.ForeignKey;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.OneToOne;
|
||||
import javax.persistence.Table;
|
||||
import javax.validation.constraints.Size;
|
||||
|
||||
@Entity
|
||||
@Table(name = "sp_target_conf_status")
|
||||
public class JpaAutoConfirmationStatus extends AbstractJpaTenantAwareBaseEntity implements AutoConfirmationStatus {
|
||||
|
||||
@OneToOne(optional = false, fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "target_id", nullable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_target_auto_conf"))
|
||||
private JpaTarget target;
|
||||
|
||||
@Column(name = "initiator", length = USERNAME_FIELD_LENGTH)
|
||||
@Size(max = USERNAME_FIELD_LENGTH)
|
||||
private String initiator;
|
||||
|
||||
@Column(name = "remark", length = NamedEntity.DESCRIPTION_MAX_SIZE)
|
||||
@Size(max = NamedEntity.DESCRIPTION_MAX_SIZE)
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* Default constructor needed for JPA entities.
|
||||
*/
|
||||
public JpaAutoConfirmationStatus() {
|
||||
// Default constructor needed for JPA entities.
|
||||
}
|
||||
|
||||
public JpaAutoConfirmationStatus(final String initiator, final String remark, final Target target) {
|
||||
this.target = (JpaTarget) target;
|
||||
this.initiator = StringUtils.isEmpty(initiator) ? null : initiator;
|
||||
this.remark = StringUtils.isEmpty(remark) ? null : remark;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Target getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getInitiator() {
|
||||
return initiator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getActivatedAt() {
|
||||
return getCreatedAt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRemark() {
|
||||
return remark;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String constructActionMessage() {
|
||||
final String remarkMessage = StringUtils.hasText(remark) ? remark : "n/a";
|
||||
final String formattedInitiator = StringUtils.hasText(initiator) ? initiator : "n/a";
|
||||
final String createdByRolloutsUser = StringUtils.hasText(getCreatedBy()) ? getCreatedBy() : "n/a";
|
||||
return String.format("Assignment automatically confirmed by initiator '%s'. %n%n" //
|
||||
+ "Auto confirmation activated by system user: '%s' %n%n" //
|
||||
+ "Remark: %s", formattedInitiator, createdByRolloutsUser, remarkMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AutoConfirmationStatus [id=" + getId() + ", target=" + target.getControllerId() + ", initiator="
|
||||
+ initiator + ", bosch_user=" + getCreatedBy() + ", activatedAt=" + getCreatedAt() + ", remark="
|
||||
+ remark + "]";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -117,6 +117,9 @@ public class JpaRolloutGroup extends AbstractJpaNamedEntity implements RolloutGr
|
||||
|
||||
@Column(name = "target_percentage")
|
||||
private float targetPercentage = 100;
|
||||
|
||||
@Column(name = "confirmation_required")
|
||||
private boolean confirmationRequired;
|
||||
|
||||
@Transient
|
||||
private transient TotalTargetCountStatus totalTargetCountStatus;
|
||||
@@ -251,6 +254,15 @@ public class JpaRolloutGroup extends AbstractJpaNamedEntity implements RolloutGr
|
||||
return targetPercentage;
|
||||
}
|
||||
|
||||
public void setConfirmationRequired(final boolean confirmationRequired) {
|
||||
this.confirmationRequired = confirmationRequired;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConfirmationRequired() {
|
||||
return confirmationRequired;
|
||||
}
|
||||
|
||||
public void setTargetPercentage(final float targetPercentage) {
|
||||
this.targetPercentage = targetPercentage;
|
||||
}
|
||||
@@ -279,7 +291,8 @@ public class JpaRolloutGroup extends AbstractJpaNamedEntity implements RolloutGr
|
||||
return "RolloutGroup [rollout=" + (rollout != null ? rollout.getId() : "") + ", status=" + status
|
||||
+ ", rolloutTargetGroup=" + rolloutTargetGroup + ", parent=" + parent + ", finishCondition="
|
||||
+ successCondition + ", finishExp=" + successConditionExp + ", errorCondition=" + errorCondition
|
||||
+ ", errorExp=" + errorConditionExp + ", getName()=" + getName() + ", getId()=" + getId() + "]";
|
||||
+ ", errorExp=" + errorConditionExp + ", getName()=" + getName() + ", getId()=" + getId()
|
||||
+ ", isConfirmationRequired()=" + isConfirmationRequired() + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -36,6 +36,8 @@ import javax.persistence.ManyToMany;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.MapKeyColumn;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.OneToOne;
|
||||
import javax.persistence.PrimaryKeyJoinColumn;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.UniqueConstraint;
|
||||
import javax.validation.constraints.NotNull;
|
||||
@@ -49,6 +51,7 @@ import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.helper.SecurityChecker;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.helper.SecurityTokenGeneratorHolder;
|
||||
import org.eclipse.hawkbit.repository.model.Action;
|
||||
import org.eclipse.hawkbit.repository.model.AutoConfirmationStatus;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||
import org.eclipse.hawkbit.repository.model.PollStatus;
|
||||
import org.eclipse.hawkbit.repository.model.Target;
|
||||
@@ -177,6 +180,10 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
|
||||
@OneToMany(mappedBy = "target", fetch = FetchType.LAZY, targetEntity = JpaTargetMetadata.class)
|
||||
private List<TargetMetadata> metadata;
|
||||
|
||||
@OneToOne(fetch = FetchType.LAZY, mappedBy = "target", orphanRemoval = true)
|
||||
@PrimaryKeyJoinColumn
|
||||
private JpaAutoConfirmationStatus autoConfirmationStatus;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
@@ -372,6 +379,15 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public AutoConfirmationStatus getAutoConfirmationStatus() {
|
||||
return autoConfirmationStatus;
|
||||
}
|
||||
|
||||
public void setAutoConfirmationStatus(final JpaAutoConfirmationStatus autoConfirmationStatus) {
|
||||
this.autoConfirmationStatus = autoConfirmationStatus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getLastTargetQuery() {
|
||||
return lastTargetQuery;
|
||||
|
||||
@@ -80,6 +80,9 @@ public class JpaTargetFilterQuery extends AbstractJpaTenantAwareBaseEntity
|
||||
@Column(name = "auto_assign_initiated_by", nullable = true, length = USERNAME_FIELD_LENGTH)
|
||||
private String autoAssignInitiatedBy;
|
||||
|
||||
@Column(name = "confirmation_required")
|
||||
private boolean confirmationRequired;
|
||||
|
||||
public JpaTargetFilterQuery() {
|
||||
// Default constructor for JPA.
|
||||
}
|
||||
@@ -97,14 +100,17 @@ public class JpaTargetFilterQuery extends AbstractJpaTenantAwareBaseEntity
|
||||
* of the {@link TargetFilterQuery}.
|
||||
* @param autoAssignWeight
|
||||
* of the {@link TargetFilterQuery}.
|
||||
* @param confirmationRequired
|
||||
* of the {@link TargetFilterQuery}.
|
||||
*/
|
||||
public JpaTargetFilterQuery(final String name, final String query, final DistributionSet autoAssignDistributionSet,
|
||||
final ActionType autoAssignActionType, final Integer autoAssignWeight) {
|
||||
final ActionType autoAssignActionType, final Integer autoAssignWeight, final boolean confirmationRequired) {
|
||||
this.name = name;
|
||||
this.query = query;
|
||||
this.autoAssignDistributionSet = (JpaDistributionSet) autoAssignDistributionSet;
|
||||
this.autoAssignActionType = autoAssignActionType;
|
||||
this.autoAssignWeight = autoAssignWeight;
|
||||
this.confirmationRequired = confirmationRequired;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -160,6 +166,15 @@ public class JpaTargetFilterQuery extends AbstractJpaTenantAwareBaseEntity
|
||||
this.autoAssignInitiatedBy = autoAssignInitiatedBy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConfirmationRequired() {
|
||||
return confirmationRequired;
|
||||
}
|
||||
|
||||
public void setConfirmationRequired(final boolean confirmationRequired) {
|
||||
this.confirmationRequired = confirmationRequired;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fireCreateEvent(final DescriptorEvent descriptorEvent) {
|
||||
EventPublisherHolder.getInstance().getEventPublisher().publishEvent(
|
||||
@@ -177,4 +192,5 @@ public class JpaTargetFilterQuery extends AbstractJpaTenantAwareBaseEntity
|
||||
EventPublisherHolder.getInstance().getEventPublisher().publishEvent(new TargetFilterQueryDeletedEvent(
|
||||
getTenant(), getId(), getClass(), EventPublisherHolder.getInstance().getApplicationId()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
/**
|
||||
* 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.jpa.utils;
|
||||
|
||||
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.MULTI_ASSIGNMENTS_ENABLED;
|
||||
|
||||
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
||||
|
||||
/**
|
||||
* A collection of static helper methods for the tenant configuration
|
||||
*/
|
||||
public final class TenantConfigHelper {
|
||||
|
||||
private final TenantConfigurationManagement tenantConfigurationManagement;
|
||||
private final SystemSecurityContext systemSecurityContext;
|
||||
|
||||
private TenantConfigHelper(final SystemSecurityContext systemSecurityContext,
|
||||
final TenantConfigurationManagement tenantConfigurationManagement) {
|
||||
this.systemSecurityContext = systemSecurityContext;
|
||||
this.tenantConfigurationManagement = tenantConfigurationManagement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setting the context of the tenant.
|
||||
*
|
||||
* @param systemSecurityContext
|
||||
* Security context used to get the tenant and for execution
|
||||
* @param tenantConfigurationManagement
|
||||
* to get the value from
|
||||
* @return is active
|
||||
*/
|
||||
public static TenantConfigHelper usingContext(final SystemSecurityContext systemSecurityContext,
|
||||
final TenantConfigurationManagement tenantConfigurationManagement) {
|
||||
return new TenantConfigHelper(systemSecurityContext, tenantConfigurationManagement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is multi-assignments enabled for the current tenant
|
||||
*
|
||||
* @return is active
|
||||
*/
|
||||
public boolean isMultiAssignmentsEnabled() {
|
||||
return systemSecurityContext.runAsSystem(() -> tenantConfigurationManagement
|
||||
.getConfigurationValue(MULTI_ASSIGNMENTS_ENABLED, Boolean.class).getValue());
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import org.eclipse.hawkbit.repository.jpa.builder.JpaTargetFilterQueryCreate;
|
||||
import org.eclipse.hawkbit.repository.model.DeploymentRequest;
|
||||
import org.eclipse.hawkbit.repository.model.Rollout;
|
||||
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
||||
import org.eclipse.hawkbit.utils.TenantConfigHelper;
|
||||
|
||||
/**
|
||||
* Utility class to handle weight validation in Rollout, Auto Assignments, and
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
ALTER TABLE sp_rolloutgroup ADD COLUMN confirmation_required BOOLEAN;
|
||||
UPDATE sp_rolloutgroup SET confirmation_required = 0;
|
||||
|
||||
ALTER TABLE sp_target_filter_query ADD COLUMN confirmation_required BOOLEAN;
|
||||
UPDATE sp_target_filter_query SET confirmation_required = 0;
|
||||
|
||||
CREATE TABLE sp_target_conf_status
|
||||
(
|
||||
id BIGINT GENERATED always AS IDENTITY NOT NULL,
|
||||
target_id bigint not null,
|
||||
initiator VARCHAR(64),
|
||||
remark VARCHAR(512),
|
||||
created_at BIGINT,
|
||||
created_by VARCHAR(64),
|
||||
last_modified_at BIGINT,
|
||||
last_modified_by VARCHAR(64),
|
||||
optlock_revision BIGINT,
|
||||
tenant VARCHAR(40) not null,
|
||||
primary key (id)
|
||||
);
|
||||
ALTER TABLE sp_target_conf_status
|
||||
ADD CONSTRAINT fk_target_auto_conf FOREIGN KEY (target_id) REFERENCES sp_target (id) ON DELETE CASCADE;
|
||||
@@ -0,0 +1,22 @@
|
||||
ALTER TABLE sp_rolloutgroup ADD column confirmation_required BOOLEAN;
|
||||
UPDATE sp_rolloutgroup SET confirmation_required = 0;
|
||||
|
||||
ALTER TABLE sp_target_filter_query ADD column confirmation_required BOOLEAN;
|
||||
UPDATE sp_target_filter_query SET confirmation_required = 0;
|
||||
|
||||
create table sp_target_conf_status
|
||||
(
|
||||
id bigint not null auto_increment,
|
||||
target_id bigint not null,
|
||||
initiator varchar(64),
|
||||
remark VARCHAR(512),
|
||||
created_at bigint,
|
||||
created_by varchar(64),
|
||||
last_modified_at bigint,
|
||||
last_modified_by varchar(64),
|
||||
optlock_revision bigint,
|
||||
tenant varchar(40) not null,
|
||||
primary key (id)
|
||||
);
|
||||
ALTER TABLE sp_target_conf_status
|
||||
ADD CONSTRAINT fk_target_auto_conf FOREIGN KEY (target_id) REFERENCES sp_target (id) ON DELETE CASCADE;
|
||||
@@ -0,0 +1,22 @@
|
||||
ALTER TABLE sp_rolloutgroup ADD column confirmation_required BOOLEAN;
|
||||
UPDATE sp_rolloutgroup SET confirmation_required = 0;
|
||||
|
||||
ALTER TABLE sp_target_filter_query ADD column confirmation_required BOOLEAN;
|
||||
UPDATE sp_target_filter_query SET confirmation_required = 0;
|
||||
|
||||
create table sp_target_conf_status
|
||||
(
|
||||
id bigint not null auto_increment,
|
||||
target_id bigint not null,
|
||||
initiator varchar(64),
|
||||
remark varchar(512),
|
||||
created_at bigint,
|
||||
created_by varchar(64),
|
||||
last_modified_at bigint,
|
||||
last_modified_by varchar(64),
|
||||
optlock_revision bigint,
|
||||
tenant varchar(40) not null,
|
||||
primary key (id)
|
||||
);
|
||||
ALTER TABLE sp_target_conf_status
|
||||
ADD CONSTRAINT fk_target_auto_conf FOREIGN KEY (target_id) REFERENCES sp_target (id) ON DELETE CASCADE;
|
||||
@@ -1,3 +1,3 @@
|
||||
ALTER TABLE sp_distribution_set ADD COLUMN valid BOOLEAN;
|
||||
|
||||
UPDATE sp_distribution_set SET valid = 1;
|
||||
UPDATE sp_distribution_set SET valid = TRUE;
|
||||
@@ -1,3 +1,3 @@
|
||||
ALTER TABLE sp_base_software_module ADD COLUMN encrypted BOOLEAN;
|
||||
|
||||
UPDATE sp_base_software_module SET encrypted = 0;
|
||||
UPDATE sp_base_software_module SET encrypted = FALSE;
|
||||
@@ -1 +1 @@
|
||||
ALTER TABLE sp_target_type ALTER COLUMN name VARCHAR (128);
|
||||
ALTER TABLE sp_target_type ALTER COLUMN name TYPE VARCHAR (128);
|
||||
@@ -1,4 +1,4 @@
|
||||
ALTER TABLE sp_action_status ADD code INTEGER;
|
||||
ALTER TABLE sp_action_status ADD COLUMN code INTEGER;
|
||||
CREATE INDEX sp_idx_action_status_03
|
||||
ON sp_action_status
|
||||
USING BTREE (tenant, code);
|
||||
@@ -0,0 +1,26 @@
|
||||
ALTER TABLE sp_rolloutgroup
|
||||
ADD COLUMN confirmation_required BOOLEAN;
|
||||
ALTER TABLE sp_target_filter_query
|
||||
ADD COLUMN confirmation_required BOOLEAN;
|
||||
|
||||
UPDATE sp_rolloutgroup SET confirmation_required = FALSE;
|
||||
UPDATE sp_target_filter_query SET confirmation_required = FALSE;
|
||||
|
||||
CREATE TABLE sp_target_conf_status
|
||||
(
|
||||
id BIGSERIAL,
|
||||
target_id BIGINT NOT NULL,
|
||||
initiator VARCHAR(64),
|
||||
remark VARCHAR(512),
|
||||
tenant VARCHAR(40) NOT NULL,
|
||||
created_at BIGINT NOT NULL,
|
||||
created_by VARCHAR(64) NOT NULL,
|
||||
last_modified_at BIGINT NOT NULL,
|
||||
last_modified_by VARCHAR(64) NOT NULL,
|
||||
optlock_revision BIGINT NULL
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE sp_target_conf_status
|
||||
ADD CONSTRAINT pk_sp_target_conf_status PRIMARY KEY (id);
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
ALTER TABLE sp_rolloutgroup ADD confirmation_required BIT DEFAULT 0;
|
||||
ALTER TABLE sp_target_filter_query ADD confirmation_required BIT DEFAULT 0;
|
||||
|
||||
CREATE TABLE sp_target_conf_status
|
||||
(
|
||||
id NUMERIC(19) IDENTITY NOT NULL,
|
||||
target_id NUMERIC(19) NOT NULL,
|
||||
initiator VARCHAR(64),
|
||||
remark VARCHAR(512),
|
||||
tenant VARCHAR(40) NOT NULL,
|
||||
created_at NUMERIC(19) NOT NULL,
|
||||
created_by VARCHAR(64) NOT NULL,
|
||||
last_modified_at NUMERIC(19) NOT NULL,
|
||||
last_modified_by VARCHAR(64) NOT NULL,
|
||||
optlock_revision INTEGER NULL,
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
ALTER TABLE sp_target_conf_status
|
||||
ADD CONSTRAINT fk_target_auto_conf FOREIGN KEY (target_id) REFERENCES sp_target (id) ON DELETE CASCADE;
|
||||
Reference in New Issue
Block a user