Introduce basic functionality for invalidation of distributionsets (#1179)

* Basic DS invalidation functionality

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Add checks for valid/complete DS

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Stop rollouts + auto assignments when invalidating a DS

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Add methods to count AAs + rollouts for invalidation

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Small refactoring for DS management

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Add invalidation functionality to REST API

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Fix update stopped rollouts status

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Add various tests

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Introduce countActionsForInvalidation

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Fix event tests with incomplete DS

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Add H2 migration script

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Fix action count method

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Fix REST documentation tests

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Change flyway version number

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Add lock for DS invalidation + adapt tests

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Move concurrency test to own class

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Handle possible InterruptedException

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Fix concurrency test

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Use one transaction for all invalidations

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Add invalidate endpoint to REST docu

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Execute invalidation in transaction when actions are cancelled

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Check that distribution set is valid when editing/creating metadata

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Remove all changes in UI

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Add DB migration files for all databases

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Implement review findings

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Move DS invalidation to own class to check permissions for single steps

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Move invalidation count methods to management classes

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>

* Fix failing tests

Signed-off-by: Sebastian Firsching <sebastian.firsching@bosch-si.com>
This commit is contained in:
Sebastian Firsching
2021-09-30 15:26:36 +02:00
committed by GitHub
parent b25e118e6c
commit 825cb64448
66 changed files with 2413 additions and 511 deletions

View File

@@ -32,7 +32,7 @@ public enum SpServerError {
"The given entity cannot be saved due to Constraint Violation"), "The given entity cannot be saved due to Constraint Violation"),
/** /**
* *
*/ */
SP_REPO_INVALID_TARGET_ADDRESS("hawkbit.server.error.repo.invalidTargetAddress", SP_REPO_INVALID_TARGET_ADDRESS("hawkbit.server.error.repo.invalidTargetAddress",
"The target address is not well formed"), "The target address is not well formed"),
@@ -180,6 +180,11 @@ public enum SpServerError {
SP_DS_INCOMPLETE("hawkbit.server.error.distributionset.incomplete", SP_DS_INCOMPLETE("hawkbit.server.error.distributionset.incomplete",
"Distribution set is assigned to a target that is incomplete (i.e. mandatory modules are missing)"), "Distribution set is assigned to a target that is incomplete (i.e. mandatory modules are missing)"),
/**
*
*/
SP_DS_INVALID("hawkbit.server.error.distributionset.invalid", "Invalid distribution set is assigned to a target"),
/** /**
* *
*/ */
@@ -210,18 +215,18 @@ public enum SpServerError {
"The given entity is read only and the change cannot be completed."), "The given entity is read only and the change cannot be completed."),
/** /**
* *
*/ */
SP_CONFIGURATION_VALUE_INVALID("hawkbit.server.error.configValueInvalid", SP_CONFIGURATION_VALUE_INVALID("hawkbit.server.error.configValueInvalid",
"The given configuration value is invalid."), "The given configuration value is invalid."),
/** /**
* *
*/ */
SP_CONFIGURATION_KEY_INVALID("hawkbit.server.error.configKeyInvalid", "The given configuration key is invalid."), SP_CONFIGURATION_KEY_INVALID("hawkbit.server.error.configKeyInvalid", "The given configuration key is invalid."),
/** /**
* *
*/ */
SP_ROLLOUT_ILLEGAL_STATE("hawkbit.server.error.rollout.illegalstate", SP_ROLLOUT_ILLEGAL_STATE("hawkbit.server.error.rollout.illegalstate",
"The rollout is in the wrong state for the requested operation"), "The rollout is in the wrong state for the requested operation"),
@@ -251,13 +256,6 @@ public enum SpServerError {
SP_AUTO_ASSIGN_ACTION_TYPE_INVALID("hawkbit.server.error.repo.invalidAutoAssignActionType", SP_AUTO_ASSIGN_ACTION_TYPE_INVALID("hawkbit.server.error.repo.invalidAutoAssignActionType",
"The given action type for auto-assignment is invalid: allowed values are ['forced', 'soft', 'downloadonly']"), "The given action type for auto-assignment is invalid: allowed values are ['forced', 'soft', 'downloadonly']"),
/**
* Error message informing that the distribution set for auto-assignment is
* invalid.
*/
SP_AUTO_ASSIGN_DISTRIBUTION_SET_INVALID("hawkbit.server.error.repo.invalidAutoAssignDistributionSet",
"The given distribution set for auto-assignment is invalid: it is either incomplete (i.e. mandatory modules are missing) or soft deleted"),
/** /**
* Error message informing the user that the requested tenant configuration * Error message informing the user that the requested tenant configuration
* change is not allowed. * change is not allowed.
@@ -277,7 +275,9 @@ public enum SpServerError {
SP_NO_WEIGHT_PROVIDED_IN_MULTIASSIGNMENT_MODE("hawkbit.server.error.noWeightProvidedInMultiAssignmentMode", SP_NO_WEIGHT_PROVIDED_IN_MULTIASSIGNMENT_MODE("hawkbit.server.error.noWeightProvidedInMultiAssignmentMode",
"The requested operation requires a weight to be specified when multi assignments is enabled."), "The requested operation requires a weight to be specified when multi assignments is enabled."),
SP_TARGET_TYPE_IN_USE("hawkbit.server.error.target.type.used", "Target type is still in use by a target."); SP_TARGET_TYPE_IN_USE("hawkbit.server.error.target.type.used", "Target type is still in use by a target."),
SP_STOP_ROLLOUT_FAILED("hawkbit.server.error.stopRolloutFailed", "Stopping the rollout failed");
private final String key; private final String key;
private final String message; private final String message;
@@ -292,7 +292,7 @@ public enum SpServerError {
/** /**
* Gets the key of the error * Gets the key of the error
* *
* @return the key of the error * @return the key of the error
*/ */
public String getKey() { public String getKey() {
@@ -301,7 +301,7 @@ public enum SpServerError {
/** /**
* Gets the message of the error * Gets the message of the error
* *
* @return message of the error * @return message of the error
*/ */
public String getMessage() { public String getMessage() {

View File

@@ -20,11 +20,11 @@ import javax.validation.constraints.NotNull;
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions; import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent; import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
import org.eclipse.hawkbit.repository.exception.CancelActionNotAllowedException; import org.eclipse.hawkbit.repository.exception.CancelActionNotAllowedException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException; import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException;
import org.eclipse.hawkbit.repository.exception.MultiAssignmentIsNotEnabledException; import org.eclipse.hawkbit.repository.exception.MultiAssignmentIsNotEnabledException;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException; import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException; import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action;
@@ -34,6 +34,7 @@ import org.eclipse.hawkbit.repository.model.DeploymentRequest;
import org.eclipse.hawkbit.repository.model.DeploymentRequestBuilder; import org.eclipse.hawkbit.repository.model.DeploymentRequestBuilder;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult; import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation.CancelationType;
import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType; import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.Target;
@@ -56,7 +57,7 @@ public interface DeploymentManagement {
* *
* @param deploymentRequests * @param deploymentRequests
* information about all target-ds-assignments that shall be made * information about all target-ds-assignments that shall be made
* *
* @return the list of assignment results * @return the list of assignment results
* *
* @throws IncompleteDistributionSetException * @throws IncompleteDistributionSetException
@@ -66,17 +67,18 @@ public interface DeploymentManagement {
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if either provided {@link DistributionSet} or {@link Target}s * if either provided {@link DistributionSet} or {@link Target}s
* do not exist * do not exist
* *
* @throws AssignmentQuotaExceededException * @throws AssignmentQuotaExceededException
* if the maximum number of targets the distribution set can be * if the maximum number of targets the distribution set can be
* assigned to at once is exceeded * assigned to at once is exceeded
* @throws MultiAssignmentIsNotEnabledException * @throws MultiAssignmentIsNotEnabledException
* if the request results in multiple assignments to the same * if the request results in multiple assignments to the same
* target and multiassignment is disabled * target and multiassignment is disabled
* *
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET)
List<DistributionSetAssignmentResult> assignDistributionSets(@Valid @NotEmpty List<DeploymentRequest> deploymentRequests); List<DistributionSetAssignmentResult> assignDistributionSets(
@Valid @NotEmpty List<DeploymentRequest> deploymentRequests);
/** /**
* Assigns {@link DistributionSet}s to {@link Target}s according to the * Assigns {@link DistributionSet}s to {@link Target}s according to the
@@ -88,7 +90,7 @@ public interface DeploymentManagement {
* information about all target-ds-assignments that shall be made * information about all target-ds-assignments that shall be made
* @param actionMessage * @param actionMessage
* an optional message for the action status * an optional message for the action status
* *
* @return the list of assignment results * @return the list of assignment results
* *
* @throws IncompleteDistributionSetException * @throws IncompleteDistributionSetException
@@ -98,23 +100,23 @@ public interface DeploymentManagement {
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if either provided {@link DistributionSet} or {@link Target}s * if either provided {@link DistributionSet} or {@link Target}s
* do not exist * do not exist
* *
* @throws AssignmentQuotaExceededException * @throws AssignmentQuotaExceededException
* if the maximum number of targets the distribution set can be * if the maximum number of targets the distribution set can be
* assigned to at once is exceeded * assigned to at once is exceeded
* @throws MultiAssignmentIsNotEnabledException * @throws MultiAssignmentIsNotEnabledException
* if the request results in multiple assignments to the same * if the request results in multiple assignments to the same
* target and multiassignment is disabled * target and multiassignment is disabled
* *
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET)
List<DistributionSetAssignmentResult> assignDistributionSets(String initiatedBy, List<DistributionSetAssignmentResult> assignDistributionSets(String initiatedBy,
@Valid @NotEmpty List<DeploymentRequest> deploymentRequests, String actionMessage); @Valid @NotEmpty List<DeploymentRequest> deploymentRequests, String actionMessage);
/** /**
* build a {@link DeploymentRequest} for a target distribution set * build a {@link DeploymentRequest} for a target distribution set
* assignment * assignment
* *
* @param controllerId * @param controllerId
* ID of target * ID of target
* @param distributionSetId * @param distributionSetId
@@ -128,9 +130,9 @@ public interface DeploymentManagement {
/** /**
* Registers "offline" assignments. "offline" assignment means adding a * Registers "offline" assignments. "offline" assignment means adding a
* completed action for a {@link DistributionSet} to a {@link Target}. * completed action for a {@link DistributionSet} to a {@link Target}.
* *
* The handling differs to hawkBit-managed updates by means that:<br/> * The handling differs to hawkBit-managed updates by means that:<br/>
* *
* <ol type="A"> * <ol type="A">
* <li>it ignores targets completely that are in * <li>it ignores targets completely that are in
* {@link TargetUpdateStatus#PENDING}.</li> * {@link TargetUpdateStatus#PENDING}.</li>
@@ -139,12 +141,12 @@ public interface DeploymentManagement {
* status to {@link TargetUpdateStatus#IN_SYNC}.</li> * status to {@link TargetUpdateStatus#IN_SYNC}.</li>
* <li>does not send a {@link TargetAssignDistributionSetEvent}.</li> * <li>does not send a {@link TargetAssignDistributionSetEvent}.</li>
* </ol> * </ol>
* *
* @param assignments * @param assignments
* target IDs with the respective distribution set ID which they * target IDs with the respective distribution set ID which they
* are supposed to be assigned to * are supposed to be assigned to
* @return the assignment results * @return the assignment results
* *
* @throws IncompleteDistributionSetException * @throws IncompleteDistributionSetException
* if mandatory {@link SoftwareModuleType} are not assigned as * if mandatory {@link SoftwareModuleType} are not assigned as
* defined by the {@link DistributionSetType}. * defined by the {@link DistributionSetType}.
@@ -152,11 +154,11 @@ public interface DeploymentManagement {
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if either provided {@link DistributionSet} or {@link Target}s * if either provided {@link DistributionSet} or {@link Target}s
* do not exist * do not exist
* *
* @throws AssignmentQuotaExceededException * @throws AssignmentQuotaExceededException
* if the maximum number of targets the distribution set can be * if the maximum number of targets the distribution set can be
* assigned to at once is exceeded * assigned to at once is exceeded
* *
* @throws MultiAssignmentIsNotEnabledException * @throws MultiAssignmentIsNotEnabledException
* if the request results in multiple assignments to the same * if the request results in multiple assignments to the same
* target and multiassignment is disabled * target and multiassignment is disabled
@@ -173,7 +175,7 @@ public interface DeploymentManagement {
* to be canceled * to be canceled
* *
* @return canceled {@link Action} * @return canceled {@link Action}
* *
* @throws CancelActionNotAllowedException * @throws CancelActionNotAllowedException
* in case the given action is not active or is already a cancel * in case the given action is not active or is already a cancel
* action * action
@@ -221,20 +223,43 @@ public interface DeploymentManagement {
* @param controllerId * @param controllerId
* the target associated to the actions to count * the target associated to the actions to count
* @return the count value of found actions associated to the target * @return the count value of found actions associated to the target
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if target with given ID does not exist * if target with given ID does not exist
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
long countActionsByTarget(@NotEmpty String controllerId); long countActionsByTarget(@NotEmpty String controllerId);
/**
* Counts all active {@link Action}s referring to the given DistributionSet.
*
* @param distributionSet
* DistributionSet to count the {@link Action}s from
* @return the count of actions referring to the given distributionSet
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
long countActionsByDistributionSetIdAndActiveIsTrue(Long distributionSet);
/**
* Counts all active {@link Action}s referring to the given DistributionSet
* that are not in a given state.
*
* @param distributionSet
* DistributionSet to count the {@link Action}s from
* @param status
* the state the actions should not have
* @return the count of actions referring to the given distributionSet
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
long countActionsByDistributionSetIdAndActiveIsTrueAndStatusIsNot(Long distributionSet, Status status);
/** /**
* Get the {@link Action} entity for given actionId. * Get the {@link Action} entity for given actionId.
* *
* @param actionId * @param actionId
* to be id of the action * to be id of the action
* @return the corresponding {@link Action} * @return the corresponding {@link Action}
* *
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
Optional<Action> findAction(long actionId); Optional<Action> findAction(long actionId);
@@ -252,7 +277,7 @@ public interface DeploymentManagement {
/** /**
* Retrieves all {@link Action} which assigned to a specific * Retrieves all {@link Action} which assigned to a specific
* {@link DistributionSet}. * {@link DistributionSet}.
* *
* @param pageable * @param pageable
* the page request parameter for paging and sorting the result * the page request parameter for paging and sorting the result
* @param distributionSetId * @param distributionSetId
@@ -260,7 +285,7 @@ public interface DeploymentManagement {
* in the result * in the result
* @return a list of {@link Action} which are assigned to a specific * @return a list of {@link Action} which are assigned to a specific
* {@link DistributionSet} * {@link DistributionSet}
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if distribution set with given ID does not exist * if distribution set with given ID does not exist
*/ */
@@ -279,7 +304,7 @@ public interface DeploymentManagement {
* the page request * the page request
* @return a slice of actions assigned to the specific target and the * @return a slice of actions assigned to the specific target and the
* specification * specification
* *
* @throws RSQLParameterUnsupportedFieldException * @throws RSQLParameterUnsupportedFieldException
* if a field in the RSQL string is used but not provided by the * if a field in the RSQL string is used but not provided by the
* given {@code fieldNameProvider} * given {@code fieldNameProvider}
@@ -299,7 +324,7 @@ public interface DeploymentManagement {
* @param pageable * @param pageable
* the pageable request to limit, sort the actions * the pageable request to limit, sort the actions
* @return a slice of actions found for a specific target * @return a slice of actions found for a specific target
* *
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
Slice<Action> findActionsByTarget(@NotEmpty String controllerId, @NotNull Pageable pageable); Slice<Action> findActionsByTarget(@NotEmpty String controllerId, @NotNull Pageable pageable);
@@ -313,7 +338,7 @@ public interface DeploymentManagement {
* @param actionId * @param actionId
* to be filtered on * to be filtered on
* @return the corresponding {@link Page} of {@link ActionStatus} * @return the corresponding {@link Page} of {@link ActionStatus}
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if action with given ID does not exist * if action with given ID does not exist
*/ */
@@ -346,13 +371,13 @@ public interface DeploymentManagement {
/** /**
* Retrieves all active {@link Action}s of a specific target. * Retrieves all active {@link Action}s of a specific target.
* *
* @param pageable * @param pageable
* the page request parameter for paging and sorting the result * the page request parameter for paging and sorting the result
* @param controllerId * @param controllerId
* the target associated with the actions * the target associated with the actions
* @return a list of actions associated with the given target * @return a list of actions associated with the given target
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if target with given ID does not exist * if target with given ID does not exist
*/ */
@@ -367,7 +392,7 @@ public interface DeploymentManagement {
* @param controllerId * @param controllerId
* the target associated with the actions * the target associated with the actions
* @return a list of actions associated with the given target * @return a list of actions associated with the given target
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if target with given ID does not exist * if target with given ID does not exist
*/ */
@@ -377,13 +402,13 @@ public interface DeploymentManagement {
/** /**
* Retrieves active {@link Action}s with highest weight that are assigned to * Retrieves active {@link Action}s with highest weight that are assigned to
* a {@link Target}. * a {@link Target}.
* *
* @param controllerId * @param controllerId
* identifies the target to retrieve the action from * identifies the target to retrieve the action from
* @param maxActionCount * @param maxActionCount
* max size of returned list * max size of returned list
* @return the action * @return the action
* *
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
List<Action> findActiveActionsWithHighestWeight(@NotEmpty String controllerId, int maxActionCount); List<Action> findActiveActionsWithHighestWeight(@NotEmpty String controllerId, int maxActionCount);
@@ -391,7 +416,7 @@ public interface DeploymentManagement {
/** /**
* Get weight of an Action. Returns the default value if the weight is null * Get weight of an Action. Returns the default value if the weight is null
* according to the properties. * according to the properties.
* *
* @param action * @param action
* to extract the weight from * to extract the weight from
* @return weight of the action * @return weight of the action
@@ -408,10 +433,10 @@ public interface DeploymentManagement {
* to be canceled * to be canceled
* *
* @return quite {@link Action} * @return quite {@link Action}
* *
* @throws CancelActionNotAllowedException * @throws CancelActionNotAllowedException
* in case the given action is not active * in case the given action is not active
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if action with given ID does not exist * if action with given ID does not exist
*/ */
@@ -425,7 +450,7 @@ public interface DeploymentManagement {
* @param actionId * @param actionId
* the ID of the action * the ID of the action
* @return the updated or the found {@link Action} * @return the updated or the found {@link Action}
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if action with given ID does not exist * if action with given ID does not exist
*/ */
@@ -438,7 +463,7 @@ public interface DeploymentManagement {
* *
* @param targetIds * @param targetIds
* ids of the {@link Target}s the actions belong to * ids of the {@link Target}s the actions belong to
* *
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET)
void cancelInactiveScheduledActionsForTargets(List<Long> targetIds); void cancelInactiveScheduledActionsForTargets(List<Long> targetIds);
@@ -468,11 +493,11 @@ public interface DeploymentManagement {
/** /**
* Returns {@link DistributionSet} that is assigned to given {@link Target}. * Returns {@link DistributionSet} that is assigned to given {@link Target}.
* *
* @param controllerId * @param controllerId
* of target * of target
* @return assigned {@link DistributionSet} * @return assigned {@link DistributionSet}
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if target with given ID does not exist * if target with given ID does not exist
*/ */
@@ -481,11 +506,11 @@ public interface DeploymentManagement {
/** /**
* Returns {@link DistributionSet} that is installed on given * Returns {@link DistributionSet} that is installed on given
* {@link Target}. * {@link Target}.
* *
* @param controllerId * @param controllerId
* of target * of target
* @return installed {@link DistributionSet} * @return installed {@link DistributionSet}
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if target with given ID does not exist * if target with given ID does not exist
*/ */
@@ -494,20 +519,20 @@ public interface DeploymentManagement {
/** /**
* Deletes actions which match one of the given action status and which have * Deletes actions which match one of the given action status and which have
* not been modified since the given (absolute) time-stamp. * not been modified since the given (absolute) time-stamp.
* *
* @param status * @param status
* Set of action status. * Set of action status.
* @param lastModified * @param lastModified
* A time-stamp in milliseconds. * A time-stamp in milliseconds.
* *
* @return The number of action entries that were deleted. * @return The number of action entries that were deleted.
*/ */
@PreAuthorize(SpringEvalExpressions.IS_SYSTEM_CODE) @PreAuthorize(SpringEvalExpressions.IS_SYSTEM_CODE)
int deleteActionsByStatusAndLastModifiedBefore(@NotNull Set<Action.Status> status, long lastModified); int deleteActionsByStatusAndLastModifiedBefore(@NotNull Set<Action.Status> status, long lastModified);
/** /**
* Checks if there is an action for the device with the given controller ID that * Checks if there is an action for the device with the given controller ID
* is in the {@link Action.Status#CANCELING} state. * that is in the {@link Action.Status#CANCELING} state.
* *
* @param controllerId * @param controllerId
* of target * of target
@@ -516,4 +541,16 @@ public interface DeploymentManagement {
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
boolean hasPendingCancellations(@NotEmpty String controllerId); boolean hasPendingCancellations(@NotEmpty String controllerId);
/**
* Cancels all actions that refer to a given distribution set. This method
* is called when a distribution set is invalidated.
*
* @param cancelationType
* defines if a force or soft cancel is executed
* @param set
* the distribution set for that the actions should be canceled
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET)
void cancelActionsForDistributionSet(final CancelationType cancelationType, final DistributionSet set);
} }

View File

@@ -0,0 +1,49 @@
/**
* Copyright (c) 2021 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;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidationCount;
/**
* A DistributionSetInvalidationManagement service provides operations to
* invalidate {@link DistributionSet}s.
*
*/
public interface DistributionSetInvalidationManagement {
/**
* Invalidates a given {@link DistributionSet}. The invalidation always
* cancels all auto assignments referring this {@link DistributionSet} and
* can not be undone. Optionally, all rollouts and actions referring this
* {@link DistributionSet} can be canceled.
*
* @param distributionSetInvalidation
* defines the {@link DistributionSet} and options what should be
* canceled
*/
void invalidateDistributionSet(final DistributionSetInvalidation distributionSetInvalidation);
/**
* Counts all entities for a list of {@link DistributionSet}s that will be
* canceled when invalidation is called for those {@link DistributionSet}s.
*
*
* @param distributionSetInvalidation
* defines the {@link DistributionSet} and options what should be
* canceled
* @return The {@link DistributionSetInvalidationCount} object that holds
* information about the count of affected rollouts,
* auto-assignments and actions
*/
DistributionSetInvalidationCount countEntitiesForInvalidation(
final DistributionSetInvalidation distributionSetInvalidation);
}

View File

@@ -18,10 +18,12 @@ import javax.validation.constraints.NotNull;
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions; import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
import org.eclipse.hawkbit.repository.builder.DistributionSetCreate; import org.eclipse.hawkbit.repository.builder.DistributionSetCreate;
import org.eclipse.hawkbit.repository.builder.DistributionSetUpdate; import org.eclipse.hawkbit.repository.builder.DistributionSetUpdate;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException; import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException; import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException;
import org.eclipse.hawkbit.repository.exception.InvalidDistributionSetException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException; import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException; import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
import org.eclipse.hawkbit.repository.exception.UnsupportedSoftwareModuleForThisDistributionSetException; import org.eclipse.hawkbit.repository.exception.UnsupportedSoftwareModuleForThisDistributionSetException;
@@ -53,20 +55,20 @@ public interface DistributionSetManagement
* to assign and update * to assign and update
* @param moduleIds * @param moduleIds
* to get assigned * to get assigned
* *
* @return the updated {@link DistributionSet}. * @return the updated {@link DistributionSet}.
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if (at least one) given module does not exist * if (at least one) given module does not exist
* *
* @throws EntityReadOnlyException * @throws EntityReadOnlyException
* if use tries to change the {@link DistributionSet} s while * if use tries to change the {@link DistributionSet} s while
* the DS is already in use. * the DS is already in use.
* *
* @throws UnsupportedSoftwareModuleForThisDistributionSetException * @throws UnsupportedSoftwareModuleForThisDistributionSetException
* if {@link SoftwareModule#getType()} is not supported by this * if {@link SoftwareModule#getType()} is not supported by this
* {@link DistributionSet#getType()}. * {@link DistributionSet#getType()}.
* *
* @throws AssignmentQuotaExceededException * @throws AssignmentQuotaExceededException
* if the maximum number of {@link SoftwareModule}s is exceeded * if the maximum number of {@link SoftwareModule}s is exceeded
* for the addressed {@link DistributionSet}. * for the addressed {@link DistributionSet}.
@@ -82,9 +84,9 @@ public interface DistributionSetManagement
* to assign for * to assign for
* @param tagId * @param tagId
* to assign * to assign
* *
* @return list of assigned ds * @return list of assigned ds
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if tag with given ID does not exist or (at least one) of the * if tag with given ID does not exist or (at least one) of the
* distribution sets. * distribution sets.
@@ -101,14 +103,14 @@ public interface DistributionSetManagement
* @param metadata * @param metadata
* the meta data entries to create or update * the meta data entries to create or update
* @return the updated or created distribution set meta data entries * @return the updated or created distribution set meta data entries
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if given set does not exist * if given set does not exist
* *
* @throws EntityAlreadyExistsException * @throws EntityAlreadyExistsException
* in case one of the meta data entry already exists for the * in case one of the meta data entry already exists for the
* specific key * specific key
* *
* @throws AssignmentQuotaExceededException * @throws AssignmentQuotaExceededException
* if the maximum number of {@link MetaData} entries is exceeded * if the maximum number of {@link MetaData} entries is exceeded
* for the addressed {@link DistributionSet} * for the addressed {@link DistributionSet}
@@ -123,7 +125,7 @@ public interface DistributionSetManagement
* where meta data has to be deleted * where meta data has to be deleted
* @param key * @param key
* of the meta data element * of the meta data element
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if given set does not exist * if given set does not exist
*/ */
@@ -136,7 +138,7 @@ public interface DistributionSetManagement
* @param actionId * @param actionId
* the action associated with the distribution set * the action associated with the distribution set
* @return the distribution set which is associated with the action * @return the distribution set which is associated with the action
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if action with given ID does not exist * if action with given ID does not exist
*/ */
@@ -152,7 +154,7 @@ public interface DistributionSetManagement
* *
* @param setId * @param setId
* to look for. * to look for.
* *
* @return {@link DistributionSet} * @return {@link DistributionSet}
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
@@ -165,15 +167,69 @@ public interface DistributionSetManagement
* name of {@link DistributionSet}; case insensitive * name of {@link DistributionSet}; case insensitive
* @param version * @param version
* version of {@link DistributionSet} * version of {@link DistributionSet}
* *
* @return the page with the found {@link DistributionSet} * @return the page with the found {@link DistributionSet}
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
Optional<DistributionSet> getByNameAndVersion(@NotEmpty String distributionName, @NotEmpty String version); Optional<DistributionSet> getByNameAndVersion(@NotEmpty String distributionName, @NotEmpty String version);
/**
* Find distribution set by id and throw an exception if it is deleted,
* incomplete or invalidated.
*
* @param id
* id of {@link DistributionSet}
*
* @return the found valid {@link DistributionSet}
*
* @throws EntityNotFoundException
* if distribution set with given ID does not exist
*
* @throws InvalidDistributionSetException
* if distribution set with given ID is invalidated
*
* @throws IncompleteDistributionSetException
* if distribution set with given ID is incomplete
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
DistributionSet getValidAndComplete(long id);
/**
* Find distribution set by id and throw an exception if it is deleted or
* invalidated.
*
* @param id
* id of {@link DistributionSet}
*
* @return the found valid {@link DistributionSet}
*
* @throws EntityNotFoundException
* if distribution set with given ID does not exist
*
* @throws InvalidDistributionSetException
* if distribution set with given ID is invalidated
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
DistributionSet getValid(long id);
/**
* Find distribution set by id and throw an exception if it is (soft)
* deleted.
*
* @param id
* id of {@link DistributionSet}
*
* @return the found valid {@link DistributionSet}
*
* @throws EntityNotFoundException
* if distribution set with given ID does not exist
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
DistributionSet getOrElseThrowException(long id);
/** /**
* Finds all meta data by the given distribution set id. * Finds all meta data by the given distribution set id.
* *
* @param pageable * @param pageable
* the page request to page the result * the page request to page the result
* @param setId * @param setId
@@ -181,7 +237,7 @@ public interface DistributionSetManagement
* *
* @return a paged result of all meta data entries for a given distribution * @return a paged result of all meta data entries for a given distribution
* set id * set id
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if distribution set with given ID does not exist * if distribution set with given ID does not exist
*/ */
@@ -190,7 +246,7 @@ public interface DistributionSetManagement
/** /**
* Finds all meta data by the given distribution set id. * Finds all meta data by the given distribution set id.
* *
* @param pageable * @param pageable
* the page request to page the result * the page request to page the result
* @param setId * @param setId
@@ -200,14 +256,14 @@ public interface DistributionSetManagement
* *
* @return a paged result of all meta data entries for a given distribution * @return a paged result of all meta data entries for a given distribution
* set id * set id
* *
* @throws RSQLParameterUnsupportedFieldException * @throws RSQLParameterUnsupportedFieldException
* if a field in the RSQL string is used but not provided by the * if a field in the RSQL string is used but not provided by the
* given {@code fieldNameProvider} * given {@code fieldNameProvider}
* *
* @throws RSQLParameterSyntaxException * @throws RSQLParameterSyntaxException
* if the RSQL syntax is wrong * if the RSQL syntax is wrong
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* of distribution set with given ID does not exist * of distribution set with given ID does not exist
*/ */
@@ -249,7 +305,7 @@ public interface DistributionSetManagement
* has details of filters to be applied * has details of filters to be applied
* @param assignedOrInstalled * @param assignedOrInstalled
* the id of the Target to be ordered by * the id of the Target to be ordered by
* *
* @return {@link DistributionSet}s * @return {@link DistributionSet}s
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
@@ -277,14 +333,14 @@ public interface DistributionSetManagement
* @param tagId * @param tagId
* of the tag the DS are assigned to * of the tag the DS are assigned to
* @return the page of found {@link DistributionSet} * @return the page of found {@link DistributionSet}
* *
* @throws RSQLParameterUnsupportedFieldException * @throws RSQLParameterUnsupportedFieldException
* if a field in the RSQL string is used but not provided by the * if a field in the RSQL string is used but not provided by the
* given {@code fieldNameProvider} * given {@code fieldNameProvider}
* *
* @throws RSQLParameterSyntaxException * @throws RSQLParameterSyntaxException
* if the RSQL syntax is wrong * if the RSQL syntax is wrong
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* of distribution set tag with given ID does not exist * of distribution set tag with given ID does not exist
*/ */
@@ -301,7 +357,7 @@ public interface DistributionSetManagement
* @param tagId * @param tagId
* of the tag the DS are assigned to * of the tag the DS are assigned to
* @return the page of found {@link DistributionSet} * @return the page of found {@link DistributionSet}
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* of distribution set tag with given ID does not exist * of distribution set tag with given ID does not exist
*/ */
@@ -316,7 +372,7 @@ public interface DistributionSetManagement
* @param key * @param key
* of the meta data element * of the meta data element
* @return the found DistributionSetMetadata * @return the found DistributionSetMetadata
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* is set with given ID does not exist * is set with given ID does not exist
*/ */
@@ -329,7 +385,7 @@ public interface DistributionSetManagement
* *
* @param setId * @param setId
* to check * to check
* *
* @return <code>true</code> if in use * @return <code>true</code> if in use
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
@@ -338,8 +394,8 @@ public interface DistributionSetManagement
/** /**
* Toggles {@link DistributionSetTag} assignment to given * Toggles {@link DistributionSetTag} assignment to given
* {@link DistributionSet}s by means that if some (or all) of the targets in * {@link DistributionSet}s by means that if some (or all) of the targets in
* the list have the {@link Tag} not yet assigned, they will be. Only if all of * the list have the {@link Tag} not yet assigned, they will be. Only if all
* theme have the tag already assigned they will be removed instead. * of theme have the tag already assigned they will be removed instead.
* *
* @param setIds * @param setIds
* to toggle for * to toggle for
@@ -347,7 +403,7 @@ public interface DistributionSetManagement
* to toggle * to toggle
* @return {@link DistributionSetTagAssignmentResult} with all meta data of * @return {@link DistributionSetTagAssignmentResult} with all meta data of
* the assignment outcome. * the assignment outcome.
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if given tag does not exist or (at least one) module * if given tag does not exist or (at least one) module
*/ */
@@ -363,10 +419,10 @@ public interface DistributionSetManagement
* @param moduleId * @param moduleId
* to be unassigned * to be unassigned
* @return the updated {@link DistributionSet}. * @return the updated {@link DistributionSet}.
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if given module or DS does not exist * if given module or DS does not exist
* *
* @throws EntityReadOnlyException * @throws EntityReadOnlyException
* if use tries to change the {@link DistributionSet} s while * if use tries to change the {@link DistributionSet} s while
* the DS is already in use. * the DS is already in use.
@@ -383,7 +439,7 @@ public interface DistributionSetManagement
* @param tagId * @param tagId
* to unassign * to unassign
* @return the unassigned ds or <null> if no ds is unassigned * @return the unassigned ds or <null> if no ds is unassigned
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if set or tag with given ID does not exist * if set or tag with given ID does not exist
*/ */
@@ -398,7 +454,7 @@ public interface DistributionSetManagement
* @param metadata * @param metadata
* meta data entry to be updated * meta data entry to be updated
* @return the updated meta data entry * @return the updated meta data entry
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* in case the meta data entry does not exists and cannot be * in case the meta data entry does not exists and cannot be
* updated * updated
@@ -409,16 +465,25 @@ public interface DistributionSetManagement
/** /**
* Count all {@link DistributionSet}s in the repository that are not marked * Count all {@link DistributionSet}s in the repository that are not marked
* as deleted. * as deleted.
* *
* @param typeId * @param typeId
* to look for * to look for
* *
* @return number of {@link DistributionSet}s * @return number of {@link DistributionSet}s
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if type with given ID does not exist * if type with given ID does not exist
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
long countByTypeId(long typeId); long countByTypeId(long typeId);
/**
* Sets the specified {@link DistributionSet} as invalidated.
*
* @param set
* the ID of the {@link DistributionSet} to be set to invalid
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
void invalidate(DistributionSet set);
} }

View File

@@ -59,6 +59,12 @@ public class RepositoryProperties {
*/ */
private int actionWeightIfAbsent = 1000; private int actionWeightIfAbsent = 1000;
/**
* Defines a timeout for the lock during invalidation of distribution sets
* (in seconds).
*/
private long dsInvalidationLockTimeout = 5;
public boolean isEagerPollPersistence() { public boolean isEagerPollPersistence() {
return eagerPollPersistence; return eagerPollPersistence;
} }
@@ -107,4 +113,12 @@ public class RepositoryProperties {
this.actionWeightIfAbsent = actionWeightIfAbsent; this.actionWeightIfAbsent = actionWeightIfAbsent;
} }
public long getDsInvalidationLockTimeout() {
return dsInvalidationLockTimeout;
}
public void setDsInvalidationLockTimeout(final long dsInvalidationLockTimeout) {
this.dsInvalidationLockTimeout = dsInvalidationLockTimeout;
}
} }

View File

@@ -20,9 +20,9 @@ import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
import org.eclipse.hawkbit.repository.builder.RolloutCreate; import org.eclipse.hawkbit.repository.builder.RolloutCreate;
import org.eclipse.hawkbit.repository.builder.RolloutGroupCreate; import org.eclipse.hawkbit.repository.builder.RolloutGroupCreate;
import org.eclipse.hawkbit.repository.builder.RolloutUpdate; import org.eclipse.hawkbit.repository.builder.RolloutUpdate;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException; import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException; import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException; import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException; import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException;
@@ -49,29 +49,29 @@ public interface RolloutManagement {
/** /**
* Process rollout based on its current {@link Rollout#getStatus()}. * Process rollout based on its current {@link Rollout#getStatus()}.
* *
* For {@link RolloutStatus#CREATING} that means creating the * For {@link RolloutStatus#CREATING} that means creating the
* {@link RolloutGroup}s with {@link Target}s and when finished switch to * {@link RolloutGroup}s with {@link Target}s and when finished switch to
* {@link RolloutStatus#READY}. * {@link RolloutStatus#READY}.
* *
* For {@link RolloutStatus#READY} that means switching to * For {@link RolloutStatus#READY} that means switching to
* {@link RolloutStatus#STARTING} if the {@link Rollout#getStartAt()} is set * {@link RolloutStatus#STARTING} if the {@link Rollout#getStartAt()} is set
* and time of calling this method is beyond this point in time. This auto * and time of calling this method is beyond this point in time. This auto
* start mechanism is optional. Call {@link #start(Long)} otherwise. * start mechanism is optional. Call {@link #start(Long)} otherwise.
* *
* For {@link RolloutStatus#STARTING} that means starting the first * For {@link RolloutStatus#STARTING} that means starting the first
* {@link RolloutGroup}s in line and when finished switch to * {@link RolloutGroup}s in line and when finished switch to
* {@link RolloutStatus#RUNNING}. * {@link RolloutStatus#RUNNING}.
* *
* For {@link RolloutStatus#RUNNING} that means checking to activate further * For {@link RolloutStatus#RUNNING} that means checking to activate further
* groups based on the defined thresholds. Switched to * groups based on the defined thresholds. Switched to
* {@link RolloutStatus#FINISHED} is all groups are finished. * {@link RolloutStatus#FINISHED} is all groups are finished.
* *
* For {@link RolloutStatus#DELETING} that means either soft delete in case * For {@link RolloutStatus#DELETING} that means either soft delete in case
* rollout was already {@link RolloutStatus#RUNNING} which results in status * rollout was already {@link RolloutStatus#RUNNING} which results in status
* change {@link RolloutStatus#DELETED} or hard delete from the persistence * change {@link RolloutStatus#DELETED} or hard delete from the persistence
* otherwise. * otherwise.
* *
*/ */
@PreAuthorize(SpringEvalExpressions.IS_SYSTEM_CODE) @PreAuthorize(SpringEvalExpressions.IS_SYSTEM_CODE)
void handleRollouts(); void handleRollouts();
@@ -95,6 +95,17 @@ public interface RolloutManagement {
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
long countByFilters(@NotEmpty String searchText); long countByFilters(@NotEmpty String searchText);
/**
* Counts all {@link Rollout}s for a specific {@link DistributionSet} that
* are stoppable
*
* @param setId
* the distribution set
* @return the count
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
long countByDistributionSetIdAndRolloutIsStoppable(long setId);
/** /**
* Persists a new rollout entity. The filter within the * Persists a new rollout entity. The filter within the
* {@link Rollout#getTargetFilterQuery()} is used to retrieve the targets * {@link Rollout#getTargetFilterQuery()} is used to retrieve the targets
@@ -217,7 +228,7 @@ public interface RolloutManagement {
/** /**
* Retrieves all rollouts found by the given specification. * Retrieves all rollouts found by the given specification.
* *
* @param pageable * @param pageable
* the page request to sort and limit the result * the page request to sort and limit the result
* @param rsqlParam * @param rsqlParam
@@ -226,7 +237,7 @@ public interface RolloutManagement {
* flag if deleted rollouts should be included * flag if deleted rollouts should be included
* *
* @return a page of found rollouts * @return a page of found rollouts
* *
* @throws RSQLParameterUnsupportedFieldException * @throws RSQLParameterUnsupportedFieldException
* if a field in the RSQL string is used but not provided by the * if a field in the RSQL string is used but not provided by the
* given {@code fieldNameProvider} * given {@code fieldNameProvider}
@@ -282,7 +293,7 @@ public interface RolloutManagement {
* @param deleted * @param deleted
* flag if deleted rollouts should be included * flag if deleted rollouts should be included
* @return rollout details of targets count for different statuses * @return rollout details of targets count for different statuses
* *
* *
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ)
@@ -293,7 +304,7 @@ public interface RolloutManagement {
* *
* @param rolloutId * @param rolloutId
* rollout id * rollout id
* *
* @return <code>true</code> if rollout exists * @return <code>true</code> if rollout exists
*/ */
boolean exists(long rolloutId); boolean exists(long rolloutId);
@@ -318,7 +329,7 @@ public interface RolloutManagement {
* @throws RolloutIllegalStateException * @throws RolloutIllegalStateException
* if given rollout is not in {@link RolloutStatus#RUNNING}. * if given rollout is not in {@link RolloutStatus#RUNNING}.
* Only running rollouts can be paused. * Only running rollouts can be paused.
* *
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_HANDLE) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_HANDLE)
void pauseRollout(long rolloutId); void pauseRollout(long rolloutId);
@@ -330,7 +341,7 @@ public interface RolloutManagement {
* *
* @param rolloutId * @param rolloutId
* the rollout to be resumed * the rollout to be resumed
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if rollout with given ID does not exist * if rollout with given ID does not exist
* @throws RolloutIllegalStateException * @throws RolloutIllegalStateException
@@ -368,14 +379,14 @@ public interface RolloutManagement {
* {@link RolloutStatus#WAITING_FOR_APPROVAL}. If the rollout is approved, * {@link RolloutStatus#WAITING_FOR_APPROVAL}. If the rollout is approved,
* it switches state to {@link RolloutStatus#READY}, otherwise it switches * it switches state to {@link RolloutStatus#READY}, otherwise it switches
* to state {@link RolloutStatus#APPROVAL_DENIED} * to state {@link RolloutStatus#APPROVAL_DENIED}
* *
* @param rolloutId * @param rolloutId
* the rollout to be approved or denied. * the rollout to be approved or denied.
* @param decision * @param decision
* decision whether a rollout is approved or denied. * decision whether a rollout is approved or denied.
* @param remark * @param remark
* user remark on approve / deny decision * user remark on approve / deny decision
* *
* @return approved or denied rollout * @return approved or denied rollout
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
@@ -399,7 +410,7 @@ public interface RolloutManagement {
* the rollout to be started * the rollout to be started
* *
* @return started rollout * @return started rollout
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if rollout with given ID does not exist * if rollout with given ID does not exist
* @throws RolloutIllegalStateException * @throws RolloutIllegalStateException
@@ -416,13 +427,13 @@ public interface RolloutManagement {
* rollout to be updated * rollout to be updated
* *
* @return Rollout updated rollout * @return Rollout updated rollout
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if rollout or DS with given IDs do not exist * if rollout or DS with given IDs do not exist
* @throws EntityReadOnlyException * @throws EntityReadOnlyException
* if rollout is in soft deleted state, i.e. only kept as * if rollout is in soft deleted state, i.e. only kept as
* reference * reference
* *
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_UPDATE) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_UPDATE)
Rollout update(@NotNull @Valid RolloutUpdate update); Rollout update(@NotNull @Valid RolloutUpdate update);
@@ -430,12 +441,24 @@ public interface RolloutManagement {
/** /**
* Deletes a rollout. A rollout might be deleted asynchronously by * Deletes a rollout. A rollout might be deleted asynchronously by
* indicating the rollout by {@link RolloutStatus#DELETING} * indicating the rollout by {@link RolloutStatus#DELETING}
* *
* *
* @param rolloutId * @param rolloutId
* the ID of the rollout to be deleted * the ID of the rollout to be deleted
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_DELETE) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_DELETE)
void delete(long rolloutId); void delete(long rolloutId);
/**
* Cancels all rollouts that refer to the given {@link DistributionSet}.
* This is called when a distribution set is invalidated and the cancel
* rollouts option is activated.
*
* @param set
* the {@link DistributionSet} for that the rollouts should be
* canceled
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_UPDATE)
void cancelRolloutsForDistributionSet(DistributionSet set);
} }

View File

@@ -18,10 +18,11 @@ import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
import org.eclipse.hawkbit.repository.builder.AutoAssignDistributionSetUpdate; import org.eclipse.hawkbit.repository.builder.AutoAssignDistributionSetUpdate;
import org.eclipse.hawkbit.repository.builder.TargetFilterQueryCreate; import org.eclipse.hawkbit.repository.builder.TargetFilterQueryCreate;
import org.eclipse.hawkbit.repository.builder.TargetFilterQueryUpdate; import org.eclipse.hawkbit.repository.builder.TargetFilterQueryUpdate;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignActionTypeException;
import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignDistributionSetException;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException; import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException;
import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignActionTypeException;
import org.eclipse.hawkbit.repository.exception.InvalidDistributionSetException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException; import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException; import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.Action.ActionType;
@@ -42,13 +43,13 @@ public interface TargetFilterQueryManagement {
* *
* @param create * @param create
* to create * to create
* *
* @return the new {@link TargetFilterQuery} * @return the new {@link TargetFilterQuery}
* *
* @throws ConstraintViolationException * @throws ConstraintViolationException
* if fields are not filled as specified. Check * if fields are not filled as specified. Check
* {@link TargetFilterQueryCreate} for field constraints. * {@link TargetFilterQueryCreate} for field constraints.
* *
* @throws AssignmentQuotaExceededException * @throws AssignmentQuotaExceededException
* if the maximum number of targets that is addressed by the * if the maximum number of targets that is addressed by the
* given query is exceeded (auto-assignments only) * given query is exceeded (auto-assignments only)
@@ -61,7 +62,7 @@ public interface TargetFilterQueryManagement {
* *
* @param targetFilterQueryId * @param targetFilterQueryId
* IDs of target filter query to be deleted * IDs of target filter query to be deleted
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if filter with given ID does not exist * if filter with given ID does not exist
*/ */
@@ -70,16 +71,16 @@ public interface TargetFilterQueryManagement {
/** /**
* Verifies the provided filter syntax. * Verifies the provided filter syntax.
* *
* @param query * @param query
* to verify * to verify
* *
* @return <code>true</code> if syntax is valid * @return <code>true</code> if syntax is valid
* *
* @throws RSQLParameterUnsupportedFieldException * @throws RSQLParameterUnsupportedFieldException
* if a field in the RSQL string is used but not provided by the * if a field in the RSQL string is used but not provided by the
* given {@code fieldNameProvider} * given {@code fieldNameProvider}
* *
* @throws RSQLParameterSyntaxException * @throws RSQLParameterSyntaxException
* if the RSQL syntax is wrong * if the RSQL syntax is wrong
*/ */
@@ -99,12 +100,23 @@ public interface TargetFilterQueryManagement {
/** /**
* Counts all {@link TargetFilterQuery}s. * Counts all {@link TargetFilterQuery}s.
* *
* @return the number of all target filter queries * @return the number of all target filter queries
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
long count(); long count();
/**
* Counts all target filters that have a given auto assign distribution set
* assigned.
*
* @param autoAssignDistributionSetId
* the id of the distribution set
* @return the count
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
long countByAutoAssignDistributionSetId(long autoAssignDistributionSetId);
/** /**
* Retrieves all {@link TargetFilterQuery}s which match the given name * Retrieves all {@link TargetFilterQuery}s which match the given name
* filter. * filter.
@@ -154,7 +166,7 @@ public interface TargetFilterQueryManagement {
* @param rsqlParam * @param rsqlParam
* RSQL filter * RSQL filter
* @return the page with the found {@link TargetFilterQuery}s * @return the page with the found {@link TargetFilterQuery}s
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if DS with given ID does not exist * if DS with given ID does not exist
*/ */
@@ -198,17 +210,17 @@ public interface TargetFilterQueryManagement {
* *
* @param update * @param update
* to be updated * to be updated
* *
* @return the updated {@link TargetFilterQuery} * @return the updated {@link TargetFilterQuery}
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if either {@link TargetFilterQuery} and/or autoAssignDs are * if either {@link TargetFilterQuery} and/or autoAssignDs are
* provided but not found * provided but not found
* *
* @throws ConstraintViolationException * @throws ConstraintViolationException
* if fields are not filled as specified. Check * if fields are not filled as specified. Check
* {@link TargetFilterQueryUpdate} for field constraints. * {@link TargetFilterQueryUpdate} for field constraints.
* *
* @throws QuotaExceededException * @throws QuotaExceededException
* if the update contains a new query which addresses too many * if the update contains a new query which addresses too many
* targets (auto-assignments only) * targets (auto-assignments only)
@@ -218,29 +230,43 @@ public interface TargetFilterQueryManagement {
/** /**
* Updates the auto assign settings of an {@link TargetFilterQuery}. * Updates the auto assign settings of an {@link TargetFilterQuery}.
* *
* @param autoAssignDistributionSetUpdate * @param autoAssignDistributionSetUpdate
* the new auto assignment * the new auto assignment
* *
* @return the updated {@link TargetFilterQuery} * @return the updated {@link TargetFilterQuery}
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if either {@link TargetFilterQuery} and/or autoAssignDs are * if either {@link TargetFilterQuery} and/or autoAssignDs are
* provided but not found * provided but not found
* *
* @throws AssignmentQuotaExceededException * @throws AssignmentQuotaExceededException
* if the query that is already associated with this filter * if the query that is already associated with this filter
* query addresses too many targets (auto-assignments only) * query addresses too many targets (auto-assignments only)
* *
* @throws InvalidAutoAssignActionTypeException * @throws InvalidAutoAssignActionTypeException
* if the provided auto-assign {@link ActionType} is not valid * if the provided auto-assign {@link ActionType} is not valid
* (neither FORCED, nor SOFT) * (neither FORCED, nor SOFT)
* *
* @throws InvalidAutoAssignDistributionSetException * @throws IncompleteDistributionSetException
* if the provided auto-assign {@link DistributionSet} is not * if the provided auto-assign {@link DistributionSet} is
* valid (incomplete or soft deleted) * incomplete
*
* @throws InvalidDistributionSetException
* if the provided auto-assign {@link DistributionSet} is
* invalidated
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET)
TargetFilterQuery updateAutoAssignDS( TargetFilterQuery updateAutoAssignDS(
@NotNull @Valid AutoAssignDistributionSetUpdate autoAssignDistributionSetUpdate); @NotNull @Valid AutoAssignDistributionSetUpdate autoAssignDistributionSetUpdate);
/**
* Removes the given {@link DistributionSet} from all auto assignments.
*
* @param setId
* the {@link DistributionSet} to be removed from auto
* assignments.
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET)
void cancelAutoAssignmentForDistributionSet(long setId);
} }

View File

@@ -0,0 +1,69 @@
/**
* Copyright (c) 2021 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.event.remote;
import java.util.Collection;
import org.eclipse.hawkbit.repository.model.DistributionSet;
/**
*
* Event that is published when a rollout is stopped due to invalidation of a
* {@link DistributionSet}.
*/
public class RolloutStoppedEvent extends RemoteTenantAwareEvent {
private static final long serialVersionUID = 1L;
private Collection<Long> rolloutGroupIds;
private long rolloutId;
/**
* Default constructor.
*/
public RolloutStoppedEvent() {
// for serialization libs like jackson
}
/**
* Constructor for json serialization.
*
* @param tenant
* the tenant
* @param entityId
* the entity id
* @param entityClass
* the entity class
* @param applicationId
* the origin application id
*/
public RolloutStoppedEvent(final String tenant, final String applicationId, final long rolloutId,
final Collection<Long> rolloutGroupIds) {
super(rolloutId, tenant, applicationId);
this.rolloutId = rolloutId;
this.rolloutGroupIds = rolloutGroupIds;
}
public Collection<Long> getRolloutGroupIds() {
return rolloutGroupIds;
}
public void setRolloutGroupIds(final Collection<Long> rolloutGroupIds) {
this.rolloutGroupIds = rolloutGroupIds;
}
public long getRolloutId() {
return rolloutId;
}
public void setRolloutId(final long rolloutId) {
this.rolloutId = rolloutId;
}
}

View File

@@ -1,30 +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.exception;
import org.eclipse.hawkbit.exception.AbstractServerRtException;
import org.eclipse.hawkbit.exception.SpServerError;
/**
* Thrown if a distribution set for auto-assignment is incomplete (i.e.
* mandatory modules are missing) or soft deleted.
*/
public class InvalidAutoAssignDistributionSetException extends AbstractServerRtException {
private static final long serialVersionUID = 1L;
private static final SpServerError THIS_ERROR = SpServerError.SP_AUTO_ASSIGN_DISTRIBUTION_SET_INVALID;
/**
* Default constructor.
*/
public InvalidAutoAssignDistributionSetException() {
super(THIS_ERROR);
}
}

View File

@@ -0,0 +1,44 @@
/**
* Copyright (c) 2021 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.exception;
import org.eclipse.hawkbit.exception.AbstractServerRtException;
import org.eclipse.hawkbit.exception.SpServerError;
public class InvalidDistributionSetException extends AbstractServerRtException {
/**
*
*/
private static final long serialVersionUID = 1L;
/**
* Creates a new InvalidDistributionSetException with
* {@link SpServerError#SP_DS_INVALID} error.
*/
public InvalidDistributionSetException() {
super(SpServerError.SP_DS_INVALID);
}
/**
* @param cause
* for the exception
*/
public InvalidDistributionSetException(final Throwable cause) {
super(SpServerError.SP_DS_INVALID, cause);
}
/**
* @param message
* of the error
*/
public InvalidDistributionSetException(final String message) {
super(message, SpServerError.SP_DS_INVALID);
}
}

View File

@@ -0,0 +1,51 @@
/**
* Copyright (c) 2021 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.exception;
import org.eclipse.hawkbit.exception.AbstractServerRtException;
import org.eclipse.hawkbit.exception.SpServerError;
/**
* {@link StopRolloutException} is thrown when an error occurs while stopping
* the rollout (due to an invalidation of distribution set). This could be
* caused by a long ongoing creation of a rollout.
*/
public class StopRolloutException extends AbstractServerRtException {
private static final long serialVersionUID = 1L;
/**
* Creates a new StopRolloutException with
* {@link SpServerError#SP_STOP_ROLLOUT_FAILED} error.
*/
public StopRolloutException() {
super(SpServerError.SP_STOP_ROLLOUT_FAILED);
}
/**
* Creates a new StopRolloutException with
* {@link SpServerError#SP_STOP_ROLLOUT_FAILED} error.
*
* @param cause
* for the exception
*/
public StopRolloutException(final Throwable cause) {
super(SpServerError.SP_STOP_ROLLOUT_FAILED, cause);
}
/**
* Creates a new StopRolloutException with
* {@link SpServerError#SP_STOP_ROLLOUT_FAILED} error.
*
* @param message
* of the error
*/
public StopRolloutException(final String message) {
super(message, SpServerError.SP_STOP_ROLLOUT_FAILED);
}
}

View File

@@ -16,7 +16,7 @@ import java.util.Set;
* A {@link DistributionSet} defines a meta package that combines a set of * A {@link DistributionSet} defines a meta package that combines a set of
* {@link SoftwareModule}s which have to be or are provisioned to a * {@link SoftwareModule}s which have to be or are provisioned to a
* {@link Target}. * {@link Target}.
* *
* <p> * <p>
* A {@link Target} has exactly one target {@link DistributionSet} assigned. * A {@link Target} has exactly one target {@link DistributionSet} assigned.
* </p> * </p>
@@ -71,4 +71,10 @@ public interface DistributionSet extends NamedVersionedEntity {
*/ */
boolean isComplete(); boolean isComplete();
/**
* @return <code>false</code> if this {@link DistributionSet} is
* invalidated.
*/
boolean isValid();
} }

View File

@@ -20,6 +20,7 @@ public final class DistributionSetFilter {
public static class DistributionSetFilterBuilder { public static class DistributionSetFilterBuilder {
private Boolean isDeleted; private Boolean isDeleted;
private Boolean isComplete; private Boolean isComplete;
private Boolean isValid;
private DistributionSetType type; private DistributionSetType type;
private String searchText; private String searchText;
private String filterString; private String filterString;
@@ -57,6 +58,11 @@ public final class DistributionSetFilter {
return this; return this;
} }
public DistributionSetFilterBuilder setIsValid(final Boolean isValid) {
this.isValid = isValid;
return this;
}
public DistributionSetFilterBuilder setSearchText(final String searchText) { public DistributionSetFilterBuilder setSearchText(final String searchText) {
this.searchText = searchText; this.searchText = searchText;
return this; return this;
@@ -86,6 +92,7 @@ public final class DistributionSetFilter {
private final Boolean isDeleted; private final Boolean isDeleted;
private final Boolean isComplete; private final Boolean isComplete;
private final Boolean isValid;
private final DistributionSetType type; private final DistributionSetType type;
private final String searchText; private final String searchText;
private final String filterString; private final String filterString;
@@ -104,6 +111,7 @@ public final class DistributionSetFilter {
public DistributionSetFilter(final DistributionSetFilterBuilder builder) { public DistributionSetFilter(final DistributionSetFilterBuilder builder) {
this.isDeleted = builder.isDeleted; this.isDeleted = builder.isDeleted;
this.isComplete = builder.isComplete; this.isComplete = builder.isComplete;
this.isValid = builder.isValid;
this.type = builder.type; this.type = builder.type;
this.searchText = builder.searchText; this.searchText = builder.searchText;
this.filterString = builder.filterString; this.filterString = builder.filterString;
@@ -129,6 +137,10 @@ public final class DistributionSetFilter {
return isDeleted; return isDeleted;
} }
public Boolean getIsValid() {
return isValid;
}
public String getSearchText() { public String getSearchText() {
return searchText; return searchText;
} }

View File

@@ -0,0 +1,71 @@
/**
* Copyright (c) 2021 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.model;
import java.util.Collection;
/**
* Holds the information about the invalidation of a distribution set
*/
public class DistributionSetInvalidation {
private Collection<Long> distributionSetIds;
private CancelationType cancelationType;
private boolean cancelRollouts;
/**
* Parametric constructor
*
* @param distributionSetIds
* defines which distribution sets should be canceled
* @param cancelationType
* defines if actions should be canceled
* @param cancelRollouts
* defines if rollouts should be canceled
*/
public DistributionSetInvalidation(final Collection<Long> distributionSetIds, final CancelationType cancelationType,
final boolean cancelRollouts) {
this.distributionSetIds = distributionSetIds;
this.cancelationType = cancelationType;
this.cancelRollouts = cancelRollouts;
}
public Collection<Long> getDistributionSetIds() {
return distributionSetIds;
}
public void setDistributionSetIds(final Collection<Long> distributionSetIds) {
this.distributionSetIds = distributionSetIds;
}
public CancelationType getCancelationType() {
return cancelationType;
}
public void setCancelationType(final CancelationType cancelationType) {
this.cancelationType = cancelationType;
}
public boolean isCancelRollouts() {
return cancelRollouts;
}
public void setCancelRollouts(final boolean cancelRollouts) {
this.cancelRollouts = cancelRollouts;
}
/**
* Defines if and how actions should be canceled when invalidating a
* distribution set
*/
public enum CancelationType {
FORCE, SOFT, NONE;
}
}

View File

@@ -0,0 +1,41 @@
/**
* Copyright (c) 2021 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.model;
/**
* object that holds information about the count of affected rollouts,
* auto-assignments and actions, when a list of distribution sets gets
* invalidated
*/
public class DistributionSetInvalidationCount {
private final long rolloutsCount;
private final long autoAssignmentCount;
private final long actionCount;
public DistributionSetInvalidationCount(final long rolloutsCount, final long autoAssignmentCount,
final long actionCount) {
this.rolloutsCount = rolloutsCount;
this.autoAssignmentCount = autoAssignmentCount;
this.actionCount = actionCount;
}
public long getRolloutsCount() {
return rolloutsCount;
}
public long getAutoAssignmentCount() {
return autoAssignmentCount;
}
public long getActionCount() {
return actionCount;
}
}

View File

@@ -17,7 +17,7 @@ import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus.Status;
/** /**
* Software update operations in large scale IoT scenarios with hundred of * Software update operations in large scale IoT scenarios with hundred of
* thousands of devices require special handling. * thousands of devices require special handling.
* *
* That includes secure handling of large volumes of devices at rollout creation * That includes secure handling of large volumes of devices at rollout creation
* time. Monitoring of the rollout progress. Emergency rollout shutdown in case * time. Monitoring of the rollout progress. Emergency rollout shutdown in case
* of problems on to many devices and reporting capabilities for a complete * of problems on to many devices and reporting capabilities for a complete
@@ -149,6 +149,11 @@ public interface Rollout extends NamedEntity {
*/ */
STOPPED, STOPPED,
/**
* Rollout is going to be stopped.
*/
STOPPING,
/** /**
* Rollout is running. * Rollout is running.
*/ */
@@ -174,7 +179,7 @@ public interface Rollout extends NamedEntity {
/** /**
* Rollout could not be created due to errors, might be a database * Rollout could not be created due to errors, might be a database
* problem during asynchronous creating. * problem during asynchronous creating.
* *
* @deprecated legacy status is not used anymore * @deprecated legacy status is not used anymore
*/ */
@Deprecated @Deprecated
@@ -183,7 +188,7 @@ public interface Rollout extends NamedEntity {
/** /**
* Rollout could not be started due to errors, might be database problem * Rollout could not be started due to errors, might be database problem
* during asynchronous starting. * during asynchronous starting.
* *
* @deprecated legacy status is not used anymore * @deprecated legacy status is not used anymore
*/ */
@Deprecated @Deprecated

View File

@@ -21,6 +21,7 @@ import org.eclipse.hawkbit.repository.event.remote.MultiActionAssignEvent;
import org.eclipse.hawkbit.repository.event.remote.MultiActionCancelEvent; import org.eclipse.hawkbit.repository.event.remote.MultiActionCancelEvent;
import org.eclipse.hawkbit.repository.event.remote.RolloutDeletedEvent; import org.eclipse.hawkbit.repository.event.remote.RolloutDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.RolloutGroupDeletedEvent; import org.eclipse.hawkbit.repository.event.remote.RolloutGroupDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.RolloutStoppedEvent;
import org.eclipse.hawkbit.repository.event.remote.SoftwareModuleDeletedEvent; import org.eclipse.hawkbit.repository.event.remote.SoftwareModuleDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.SoftwareModuleTypeDeletedEvent; import org.eclipse.hawkbit.repository.event.remote.SoftwareModuleTypeDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent; import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
@@ -60,7 +61,7 @@ import org.eclipse.hawkbit.repository.event.remote.entity.TenantConfigurationUpd
* The {@link EventType} class declares the event-type and it's corresponding * The {@link EventType} class declares the event-type and it's corresponding
* encoding value in the payload of an remote header. The event-type is encoded * encoding value in the payload of an remote header. The event-type is encoded
* into the payload of the message which is distributed. * into the payload of the message which is distributed.
* *
* To encode and decode the event class type we need some conversation mapping * To encode and decode the event class type we need some conversation mapping
* between the actual class and the corresponding integer value which is the * between the actual class and the corresponding integer value which is the
* encoded value in the byte-payload. * encoded value in the byte-payload.
@@ -146,6 +147,9 @@ public class EventType {
TYPES.put(40, TenantConfigurationCreatedEvent.class); TYPES.put(40, TenantConfigurationCreatedEvent.class);
TYPES.put(41, TenantConfigurationUpdatedEvent.class); TYPES.put(41, TenantConfigurationUpdatedEvent.class);
TYPES.put(42, TenantConfigurationDeletedEvent.class); TYPES.put(42, TenantConfigurationDeletedEvent.class);
// rollout stopped due to invalidated distribution set
TYPES.put(43, RolloutStoppedEvent.class);
} }
private int value; private int value;
@@ -159,7 +163,7 @@ public class EventType {
/** /**
* Constructor. * Constructor.
* *
* @param value * @param value
* the value to initialize * the value to initialize
*/ */
@@ -177,7 +181,7 @@ public class EventType {
/** /**
* Returns a {@link EventType} based on the given class type. * Returns a {@link EventType} based on the given class type.
* *
* @param clazz * @param clazz
* the clazz type to retrieve the corresponding {@link EventType} * the clazz type to retrieve the corresponding {@link EventType}
* . * .

View File

@@ -18,6 +18,7 @@ import org.eclipse.hawkbit.cache.TenancyCacheManager;
import org.eclipse.hawkbit.cache.TenantAwareCacheManager; import org.eclipse.hawkbit.cache.TenantAwareCacheManager;
import org.eclipse.hawkbit.repository.event.remote.RolloutDeletedEvent; import org.eclipse.hawkbit.repository.event.remote.RolloutDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.RolloutGroupDeletedEvent; import org.eclipse.hawkbit.repository.event.remote.RolloutGroupDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.RolloutStoppedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.AbstractActionEvent; import org.eclipse.hawkbit.repository.event.remote.entity.AbstractActionEvent;
import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup; import org.eclipse.hawkbit.repository.model.RolloutGroup;
@@ -67,7 +68,7 @@ public class RolloutStatusCache {
/** /**
* Retrieves cached list of {@link TotalTargetCountActionStatus} of * Retrieves cached list of {@link TotalTargetCountActionStatus} of
* {@link Rollout}s. * {@link Rollout}s.
* *
* @param rollouts * @param rollouts
* rolloutIds to retrieve cache entries for * rolloutIds to retrieve cache entries for
* @return map of cached entries * @return map of cached entries
@@ -81,7 +82,7 @@ public class RolloutStatusCache {
/** /**
* Retrieves cached list of {@link TotalTargetCountActionStatus} of * Retrieves cached list of {@link TotalTargetCountActionStatus} of
* {@link Rollout}s. * {@link Rollout}s.
* *
* @param rolloutId * @param rolloutId
* to retrieve cache entries for * to retrieve cache entries for
* @return map of cached entries * @return map of cached entries
@@ -95,7 +96,7 @@ public class RolloutStatusCache {
/** /**
* Retrieves cached list of {@link TotalTargetCountActionStatus} of * Retrieves cached list of {@link TotalTargetCountActionStatus} of
* {@link RolloutGroup}s. * {@link RolloutGroup}s.
* *
* @param rolloutGroups * @param rolloutGroups
* rolloutGroupsIds to retrieve cache entries for * rolloutGroupsIds to retrieve cache entries for
* @return map of cached entries * @return map of cached entries
@@ -109,7 +110,7 @@ public class RolloutStatusCache {
/** /**
* Retrieves cached list of {@link TotalTargetCountActionStatus} of * Retrieves cached list of {@link TotalTargetCountActionStatus} of
* {@link RolloutGroup}. * {@link RolloutGroup}.
* *
* @param groupId * @param groupId
* to retrieve cache entries for * to retrieve cache entries for
* @return map of cached entries * @return map of cached entries
@@ -123,7 +124,7 @@ public class RolloutStatusCache {
/** /**
* Put map of {@link TotalTargetCountActionStatus} for multiple * Put map of {@link TotalTargetCountActionStatus} for multiple
* {@link Rollout}s into cache. * {@link Rollout}s into cache.
* *
* @param put * @param put
* map of cached entries * map of cached entries
*/ */
@@ -135,12 +136,12 @@ public class RolloutStatusCache {
/** /**
* Put {@link TotalTargetCountActionStatus} for one {@link Rollout}s into * Put {@link TotalTargetCountActionStatus} for one {@link Rollout}s into
* cache. * cache.
* *
* @param rolloutId * @param rolloutId
* the cache entries belong to * the cache entries belong to
* @param status * @param status
* list to cache * list to cache
* *
*/ */
public void putRolloutStatus(final Long rolloutId, final List<TotalTargetCountActionStatus> status) { public void putRolloutStatus(final Long rolloutId, final List<TotalTargetCountActionStatus> status) {
final Cache cache = cacheManager.getCache(CACHE_RO_NAME); final Cache cache = cacheManager.getCache(CACHE_RO_NAME);
@@ -150,7 +151,7 @@ public class RolloutStatusCache {
/** /**
* Put {@link TotalTargetCountActionStatus} for multiple * Put {@link TotalTargetCountActionStatus} for multiple
* {@link RolloutGroup}s into cache. * {@link RolloutGroup}s into cache.
* *
* @param put * @param put
* map of cached entries * map of cached entries
*/ */
@@ -162,7 +163,7 @@ public class RolloutStatusCache {
/** /**
* Put {@link TotalTargetCountActionStatus} for multiple * Put {@link TotalTargetCountActionStatus} for multiple
* {@link RolloutGroup}s into cache. * {@link RolloutGroup}s into cache.
* *
* @param groupId * @param groupId
* the cache entries belong to * the cache entries belong to
* @param status * @param status
@@ -223,6 +224,18 @@ public class RolloutStatusCache {
cache.evict(event.getEntityId()); cache.evict(event.getEntityId());
} }
@EventListener(classes = RolloutStoppedEvent.class)
void invalidateCachedTotalTargetCountOnRolloutStopped(final RolloutStoppedEvent event) {
final Cache cache = tenantAware.runAsTenant(event.getTenant(), () -> cacheManager.getCache(CACHE_RO_NAME));
cache.evict(event.getRolloutId());
for (final long groupId : event.getRolloutGroupIds()) {
final Cache groupCache = tenantAware.runAsTenant(event.getTenant(),
() -> cacheManager.getCache(CACHE_GR_NAME));
groupCache.evict(groupId);
}
}
/** /**
* Evicts all caches for a given tenant. All caches under a certain tenant * Evicts all caches for a given tenant. All caches under a certain tenant
* gets evicted. * gets evicted.

View File

@@ -64,6 +64,28 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
*/ */
Page<Action> findByDistributionSetId(Pageable pageable, Long dsId); Page<Action> findByDistributionSetId(Pageable pageable, Long dsId);
/**
* Retrieves all active {@link Action}s which are referring the given
* {@link DistributionSet}.
*
* @param set
* the {@link DistributionSet} on which will be filtered
* @return the found {@link Action}s
*/
List<Action> findByDistributionSetAndActiveIsTrue(DistributionSet set);
/**
* Retrieves all active {@link Action}s which are referring the given
* {@link DistributionSet} and are not in the given state
*
* @param set
* the {@link DistributionSet} on which will be filtered
* @param status
* the state the actions should not have
* @return the found {@link Action}s
*/
List<Action> findByDistributionSetAndActiveIsTrueAndStatusIsNot(DistributionSet set, Status status);
/** /**
* Retrieves all {@link Action}s which are referring the given * Retrieves all {@link Action}s which are referring the given
* {@link Target}. * {@link Target}.
@@ -103,7 +125,7 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
* Retrieves the active {@link Action}s with the highest weights that refer * Retrieves the active {@link Action}s with the highest weights that refer
* to the given {@link Target}. If {@link Action}s have the same weight they * to the given {@link Target}. If {@link Action}s have the same weight they
* are ordered ascending by ID (oldest ones first). * are ordered ascending by ID (oldest ones first).
* *
* @param pageable * @param pageable
* pageable * pageable
* @param controllerId * @param controllerId
@@ -117,7 +139,7 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
/** /**
* Retrieves the active {@link Action}s with the lowest IDs (the oldest one) * Retrieves the active {@link Action}s with the lowest IDs (the oldest one)
* whose weight is null and that that refers to the given {@link Target}. * whose weight is null and that that refers to the given {@link Target}.
* *
* @param pageable * @param pageable
* pageable * pageable
* @param controllerId * @param controllerId
@@ -131,7 +153,7 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
/** /**
* Checks if an active action exists for given * Checks if an active action exists for given
* {@link Target#getControllerId()}. * {@link Target#getControllerId()}.
* *
* @param controllerId * @param controllerId
* of target to check * of target to check
* @return <code>true</code> if an active action for the target exists. * @return <code>true</code> if an active action for the target exists.
@@ -264,10 +286,9 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
List<Action> findByExternalRefInAndActive(@Param("externalRefs") List<String> externalRefs, List<Action> findByExternalRefInAndActive(@Param("externalRefs") List<String> externalRefs,
@Param("active") boolean active); @Param("active") boolean active);
/** /**
* Retrieves an {@link Action} that matches the queried externalRef. * Retrieves an {@link Action} that matches the queried externalRef.
* *
* @param externalRef * @param externalRef
* of the action. See {@link Action#getExternalRef()} * of the action. See {@link Action#getExternalRef()}
* @return the found {@link Action} * @return the found {@link Action}
@@ -351,6 +372,27 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
*/ */
Long countByDistributionSetId(Long distributionSet); Long countByDistributionSetId(Long distributionSet);
/**
* Counts all active {@link Action}s referring to the given DistributionSet.
*
* @param distributionSet
* DistributionSet to count the {@link Action}s from
* @return the count of actions referring to the given distributionSet
*/
Long countByDistributionSetIdAndActiveIsTrue(Long distributionSet);
/**
* Counts all active {@link Action}s referring to the given DistributionSet
* that are not in a given state.
*
* @param distributionSet
* DistributionSet to count the {@link Action}s from
* @param status
* the state the actions should not have
* @return the count of actions referring to the given distributionSet
*/
Long countByDistributionSetIdAndActiveIsTrueAndStatusIsNot(Long distributionSet, Status status);
/** /**
* Counts all actions referring to a given rollout and rolloutgroup which * Counts all actions referring to a given rollout and rolloutgroup which
* are currently not in the given status. An in-clause statement does not * are currently not in the given status. An in-clause statement does not
@@ -396,7 +438,7 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
/** /**
* Counts all actions referring to a given rollout and status. * Counts all actions referring to a given rollout and status.
* *
* @param rolloutId * @param rolloutId
* the ID of the rollout the actions belong to * the ID of the rollout the actions belong to
* @param status * @param status
@@ -409,7 +451,7 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
/** /**
* Returns {@code true} if actions for the given rollout exists, otherwise * Returns {@code true} if actions for the given rollout exists, otherwise
* {@code false} * {@code false}
* *
* @param rolloutId * @param rolloutId
* the ID of the rollout the actions belong to * the ID of the rollout the actions belong to
* @return {@code true} if actions for the given rollout exists, otherwise * @return {@code true} if actions for the given rollout exists, otherwise
@@ -421,7 +463,7 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
/** /**
* Returns {@code true} if actions for the given rollout exists, otherwise * Returns {@code true} if actions for the given rollout exists, otherwise
* {@code false} * {@code false}
* *
* @param rolloutId * @param rolloutId
* the ID of the rollout the actions belong to * the ID of the rollout the actions belong to
* @param status * @param status
@@ -471,7 +513,7 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
/** /**
* Retrieves all actions for a specific rollout and in a specific status. * Retrieves all actions for a specific rollout and in a specific status.
* *
* @param pageable * @param pageable
* page parameters * page parameters
* @param rolloutId * @param rolloutId
@@ -528,7 +570,7 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
/** /**
* Deletes all actions with the given IDs. * Deletes all actions with the given IDs.
* *
* @param actionIDs * @param actionIDs
* the IDs of the actions to be deleted. * the IDs of the actions to be deleted.
*/ */

View File

@@ -35,6 +35,7 @@ import javax.persistence.criteria.Root;
import org.eclipse.hawkbit.repository.ActionFields; import org.eclipse.hawkbit.repository.ActionFields;
import org.eclipse.hawkbit.repository.DeploymentManagement; import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.QuotaManagement; import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.RepositoryConstants; import org.eclipse.hawkbit.repository.RepositoryConstants;
import org.eclipse.hawkbit.repository.RepositoryProperties; import org.eclipse.hawkbit.repository.RepositoryProperties;
@@ -43,7 +44,6 @@ import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEv
import org.eclipse.hawkbit.repository.exception.CancelActionNotAllowedException; import org.eclipse.hawkbit.repository.exception.CancelActionNotAllowedException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.ForceQuitActionNotAllowedException; import org.eclipse.hawkbit.repository.exception.ForceQuitActionNotAllowedException;
import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException;
import org.eclipse.hawkbit.repository.exception.MultiAssignmentIsNotEnabledException; import org.eclipse.hawkbit.repository.exception.MultiAssignmentIsNotEnabledException;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants; import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor; import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor;
@@ -67,6 +67,7 @@ import org.eclipse.hawkbit.repository.model.ActionStatus;
import org.eclipse.hawkbit.repository.model.DeploymentRequest; import org.eclipse.hawkbit.repository.model.DeploymentRequest;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult; import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation.CancelationType;
import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType; import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.Target;
@@ -130,6 +131,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
} }
private final EntityManager entityManager; private final EntityManager entityManager;
private final DistributionSetManagement distributionSetManagement;
private final DistributionSetRepository distributionSetRepository; private final DistributionSetRepository distributionSetRepository;
private final TargetRepository targetRepository; private final TargetRepository targetRepository;
private final ActionStatusRepository actionStatusRepository; private final ActionStatusRepository actionStatusRepository;
@@ -146,6 +148,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
private final RetryTemplate retryTemplate; private final RetryTemplate retryTemplate;
protected JpaDeploymentManagement(final EntityManager entityManager, final ActionRepository actionRepository, protected JpaDeploymentManagement(final EntityManager entityManager, final ActionRepository actionRepository,
final DistributionSetManagement distributionSetManagement,
final DistributionSetRepository distributionSetRepository, final TargetRepository targetRepository, final DistributionSetRepository distributionSetRepository, final TargetRepository targetRepository,
final ActionStatusRepository actionStatusRepository, final AuditorAware<String> auditorProvider, final ActionStatusRepository actionStatusRepository, final AuditorAware<String> auditorProvider,
final EventPublisherHolder eventPublisherHolder, final AfterTransactionCommitExecutor afterCommit, final EventPublisherHolder eventPublisherHolder, final AfterTransactionCommitExecutor afterCommit,
@@ -156,6 +159,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
super(actionRepository, repositoryProperties); super(actionRepository, repositoryProperties);
this.entityManager = entityManager; this.entityManager = entityManager;
this.distributionSetRepository = distributionSetRepository; this.distributionSetRepository = distributionSetRepository;
this.distributionSetManagement = distributionSetManagement;
this.targetRepository = targetRepository; this.targetRepository = targetRepository;
this.actionStatusRepository = actionStatusRepository; this.actionStatusRepository = actionStatusRepository;
this.auditorProvider = auditorProvider; this.auditorProvider = auditorProvider;
@@ -254,8 +258,8 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
/** /**
* method assigns the {@link DistributionSet} to all {@link Target}s by * method assigns the {@link DistributionSet} to all {@link Target}s by
* their IDs with a specific {@link ActionType} and {@code forcetime}. * their IDs with a specific {@link ActionType} and {@code forcetime}.
* *
* *
* In case the update was executed offline (i.e. not managed by hawkBit) the * In case the update was executed offline (i.e. not managed by hawkBit) the
* handling differs my means that:<br/> * handling differs my means that:<br/>
* A. it ignores targets completely that are in * A. it ignores targets completely that are in
@@ -285,7 +289,8 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
final Collection<TargetWithActionType> targetsWithActionType, final String actionMessage, final Collection<TargetWithActionType> targetsWithActionType, final String actionMessage,
final AbstractDsAssignmentStrategy assignmentStrategy) { final AbstractDsAssignmentStrategy assignmentStrategy) {
final JpaDistributionSet distributionSetEntity = getAndValidateDsById(dsID); final JpaDistributionSet distributionSetEntity = (JpaDistributionSet) distributionSetManagement
.getValidAndComplete(dsID);
final List<String> providedTargetIds = targetsWithActionType.stream().map(TargetWithActionType::getControllerId) final List<String> providedTargetIds = targetsWithActionType.stream().map(TargetWithActionType::getControllerId)
.distinct().collect(Collectors.toList()); .distinct().collect(Collectors.toList());
@@ -363,18 +368,6 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
return new DistributionSetAssignmentResult(distributionSet, alreadyAssignedTargetsCount, assignedActions); return new DistributionSetAssignmentResult(distributionSet, alreadyAssignedTargetsCount, assignedActions);
} }
private JpaDistributionSet getAndValidateDsById(final Long dsID) {
final JpaDistributionSet distributionSet = distributionSetRepository.findById(dsID)
.orElseThrow(() -> new EntityNotFoundException(DistributionSet.class, dsID));
if (!distributionSet.isComplete()) {
throw new IncompleteDistributionSetException("Distribution set of type "
+ distributionSet.getType().getKey() + " is incomplete: " + distributionSet.getId());
}
return distributionSet;
}
private void checkQuotaForAssignment(final Collection<DeploymentRequest> deploymentRequests) { private void checkQuotaForAssignment(final Collection<DeploymentRequest> deploymentRequests) {
if (!deploymentRequests.isEmpty()) { if (!deploymentRequests.isEmpty()) {
enforceMaxAssignmentsPerRequest(deploymentRequests.size()); enforceMaxAssignmentsPerRequest(deploymentRequests.size());
@@ -656,8 +649,8 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
} }
private Specification<JpaAction> createSpecificationFor(final String controllerId, final String rsqlParam) { private Specification<JpaAction> createSpecificationFor(final String controllerId, final String rsqlParam) {
final Specification<JpaAction> spec = RSQLUtility.buildRsqlSpecification(rsqlParam, ActionFields.class, virtualPropertyReplacer, final Specification<JpaAction> spec = RSQLUtility.buildRsqlSpecification(rsqlParam, ActionFields.class,
database); virtualPropertyReplacer, database);
return (root, query, cb) -> cb.and(spec.toPredicate(root, query, cb), return (root, query, cb) -> cb.and(spec.toPredicate(root, query, cb),
cb.equal(root.get(JpaAction_.target).get(JpaTarget_.controllerId), controllerId)); cb.equal(root.get(JpaAction_.target).get(JpaTarget_.controllerId), controllerId));
} }
@@ -779,6 +772,17 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
return actionRepository.count(); return actionRepository.count();
} }
@Override
public long countActionsByDistributionSetIdAndActiveIsTrue(final Long distributionSet) {
return actionRepository.countByDistributionSetIdAndActiveIsTrue(distributionSet);
}
@Override
public long countActionsByDistributionSetIdAndActiveIsTrueAndStatusIsNot(final Long distributionSet,
final Status status) {
return actionRepository.countByDistributionSetIdAndActiveIsTrueAndStatusIsNot(distributionSet, status);
}
@Override @Override
public Slice<Action> findActionsByDistributionSet(final Pageable pageable, final long dsId) { public Slice<Action> findActionsByDistributionSet(final Pageable pageable, final long dsId) {
throwExceptionIfDistributionSetDoesNotExist(dsId); throwExceptionIfDistributionSetDoesNotExist(dsId);
@@ -832,7 +836,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
} }
@Override @Override
public boolean hasPendingCancellations(String controllerId) { public boolean hasPendingCancellations(final String controllerId) {
return actionRepository.existsByTargetControllerIdAndStatusAndActiveIsTrue(controllerId, return actionRepository.existsByTargetControllerIdAndStatusAndActiveIsTrue(controllerId,
Action.Status.CANCELING); Action.Status.CANCELING);
} }
@@ -881,4 +885,21 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
return template; return template;
} }
@Override
@Transactional
public void cancelActionsForDistributionSet(final CancelationType cancelationType, final DistributionSet set) {
actionRepository.findByDistributionSetAndActiveIsTrueAndStatusIsNot(set, Status.CANCELING).forEach(action -> {
final JpaAction jpaAction = (JpaAction) action;
cancelAction(jpaAction.getId());
LOG.debug("Action {} canceled", jpaAction.getId());
});
if (cancelationType == CancelationType.FORCE) {
actionRepository.findByDistributionSetAndActiveIsTrue(set).forEach(action -> {
final JpaAction jpaAction = (JpaAction) action;
forceQuitAction(jpaAction.getId());
LOG.debug("Action {} force canceled", jpaAction.getId());
});
}
}
} }

View File

@@ -0,0 +1,165 @@
/**
* Copyright (c) 2021 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.Collection;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.DistributionSetInvalidationManagement;
import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.RepositoryProperties;
import org.eclipse.hawkbit.repository.RolloutManagement;
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
import org.eclipse.hawkbit.repository.exception.StopRolloutException;
import org.eclipse.hawkbit.repository.jpa.utils.DeploymentHelper;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation.CancelationType;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidationCount;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.integration.support.locks.LockRegistry;
import org.springframework.transaction.PlatformTransactionManager;
/**
* Jpa implementation for {@link DistributionSetInvalidationManagement}
*
*/
public class JpaDistributionSetInvalidationManagement implements DistributionSetInvalidationManagement {
private static final Logger LOG = LoggerFactory.getLogger(JpaDistributionSetInvalidationManagement.class);
private final DistributionSetManagement distributionSetManagement;
private final RolloutManagement rolloutManagement;
private final DeploymentManagement deploymentManagement;
private final TargetFilterQueryManagement targetFilterQueryManagement;
private final PlatformTransactionManager txManager;
private final RepositoryProperties repositoryProperties;
private final TenantAware tenantAware;
private final LockRegistry lockRegistry;
protected JpaDistributionSetInvalidationManagement(final DistributionSetManagement distributionSetManagement,
final RolloutManagement rolloutManagement, final DeploymentManagement deploymentManagement,
final TargetFilterQueryManagement targetFilterQueryManagement, final PlatformTransactionManager txManager,
final RepositoryProperties repositoryProperties, final TenantAware tenantAware,
final LockRegistry lockRegistry) {
this.distributionSetManagement = distributionSetManagement;
this.rolloutManagement = rolloutManagement;
this.deploymentManagement = deploymentManagement;
this.targetFilterQueryManagement = targetFilterQueryManagement;
this.txManager = txManager;
this.repositoryProperties = repositoryProperties;
this.tenantAware = tenantAware;
this.lockRegistry = lockRegistry;
}
@Override
public void invalidateDistributionSet(final DistributionSetInvalidation distributionSetInvalidation) {
LOG.debug("Invalidate distribution sets {}", distributionSetInvalidation.getDistributionSetIds());
final String tenant = tenantAware.getCurrentTenant();
if (shouldRolloutsBeCanceled(distributionSetInvalidation.getCancelationType(),
distributionSetInvalidation.isCancelRollouts())) {
final String handlerId = JpaRolloutManagement.createRolloutLockKey(tenant);
final Lock lock = lockRegistry.obtain(handlerId);
try {
if (!lock.tryLock(repositoryProperties.getDsInvalidationLockTimeout(), TimeUnit.SECONDS)) {
throw new StopRolloutException("Timeout while trying to invalidate distribution sets");
}
try {
invalidateDistributionSetsInTransaction(distributionSetInvalidation, tenant);
} finally {
lock.unlock();
}
} catch (final InterruptedException e) {
LOG.error("InterruptedException while invalidating distribution sets {}!",
distributionSetInvalidation.getDistributionSetIds(), e);
Thread.currentThread().interrupt();
}
} else {
// no lock is needed as no rollout will be stopped
invalidateDistributionSetsInTransaction(distributionSetInvalidation, tenant);
}
}
private void invalidateDistributionSetsInTransaction(final DistributionSetInvalidation distributionSetInvalidation,
final String tenant) {
DeploymentHelper.runInNewTransaction(txManager, tenant + "-invalidateDS", status -> {
distributionSetInvalidation.getDistributionSetIds().forEach(setId -> invalidateDistributionSet(setId,
distributionSetInvalidation.getCancelationType(), distributionSetInvalidation.isCancelRollouts()));
return 0;
});
}
private void invalidateDistributionSet(final long setId, final CancelationType cancelationType,
final boolean cancelRollouts) {
final DistributionSet set = distributionSetManagement.getValidAndComplete(setId);
distributionSetManagement.invalidate(set);
LOG.debug("Distribution set {} set to invalid", setId);
if (shouldRolloutsBeCanceled(cancelationType, cancelRollouts)) {
rolloutManagement.cancelRolloutsForDistributionSet(set);
}
if (cancelationType != CancelationType.NONE) {
deploymentManagement.cancelActionsForDistributionSet(cancelationType, set);
}
targetFilterQueryManagement.cancelAutoAssignmentForDistributionSet(setId);
}
private static boolean shouldRolloutsBeCanceled(final CancelationType cancelationType,
final boolean cancelRollouts) {
return cancelationType != CancelationType.NONE || cancelRollouts;
}
@Override
public DistributionSetInvalidationCount countEntitiesForInvalidation(
final DistributionSetInvalidation distributionSetInvalidation) {
final Collection<Long> setIds = distributionSetInvalidation.getDistributionSetIds();
final long rolloutsCount = shouldRolloutsBeCanceled(distributionSetInvalidation.getCancelationType(),
distributionSetInvalidation.isCancelRollouts()) ? countRolloutsForInvalidation(setIds) : 0;
final long autoAssignmentsCount = countAutoAssignmentsForInvalidation(setIds);
final long actionsCount = countActionsForInvalidation(setIds, distributionSetInvalidation.getCancelationType());
return new DistributionSetInvalidationCount(rolloutsCount, autoAssignmentsCount, actionsCount);
}
private long countRolloutsForInvalidation(final Collection<Long> setIds) {
return setIds.stream().mapToLong(rolloutManagement::countByDistributionSetIdAndRolloutIsStoppable).sum();
}
private long countAutoAssignmentsForInvalidation(final Collection<Long> setIds) {
return setIds.stream().mapToLong(targetFilterQueryManagement::countByAutoAssignDistributionSetId).sum();
}
private long countActionsForInvalidation(final Collection<Long> setIds, final CancelationType cancelationType) {
long affectedActionsByDSInvalidation = 0;
if (cancelationType == CancelationType.FORCE) {
affectedActionsByDSInvalidation = countActionsForForcedInvalidation(setIds);
} else if (cancelationType == CancelationType.SOFT) {
affectedActionsByDSInvalidation = countActionsForSoftInvalidation(setIds);
}
return affectedActionsByDSInvalidation;
}
private long countActionsForForcedInvalidation(final Collection<Long> setIds) {
return setIds.stream().mapToLong(deploymentManagement::countActionsByDistributionSetIdAndActiveIsTrue).sum();
}
private long countActionsForSoftInvalidation(final Collection<Long> setIds) {
return setIds.stream().mapToLong(distributionSet -> deploymentManagement
.countActionsByDistributionSetIdAndActiveIsTrueAndStatusIsNot(distributionSet, Status.CANCELING)).sum();
}
}

View File

@@ -32,6 +32,8 @@ import org.eclipse.hawkbit.repository.event.remote.DistributionSetDeletedEvent;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException; import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException;
import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException;
import org.eclipse.hawkbit.repository.exception.InvalidDistributionSetException;
import org.eclipse.hawkbit.repository.jpa.builder.JpaDistributionSetCreate; import org.eclipse.hawkbit.repository.jpa.builder.JpaDistributionSetCreate;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants; import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor; import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor;
@@ -213,7 +215,7 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
public DistributionSet update(final DistributionSetUpdate u) { public DistributionSet update(final DistributionSetUpdate u) {
final GenericDistributionSetUpdate update = (GenericDistributionSetUpdate) u; final GenericDistributionSetUpdate update = (GenericDistributionSetUpdate) u;
final JpaDistributionSet set = findDistributionSetAndThrowExceptionIfNotFound(update.getId()); final JpaDistributionSet set = (JpaDistributionSet) getValid(update.getId());
update.getName().ifPresent(set::setName); update.getName().ifPresent(set::setName);
update.getDescription().ifPresent(set::setDescription); update.getDescription().ifPresent(set::setDescription);
@@ -228,11 +230,6 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
return distributionSetRepository.save(set); return distributionSetRepository.save(set);
} }
private JpaDistributionSet findDistributionSetAndThrowExceptionIfNotFound(final Long setId) {
return (JpaDistributionSet) get(setId)
.orElseThrow(() -> new EntityNotFoundException(DistributionSet.class, setId));
}
private JpaSoftwareModule findSoftwareModuleAndThrowExceptionIfNotFound(final Long moduleId) { private JpaSoftwareModule findSoftwareModuleAndThrowExceptionIfNotFound(final Long moduleId) {
return softwareModuleRepository.findById(moduleId) return softwareModuleRepository.findById(moduleId)
.orElseThrow(() -> new EntityNotFoundException(SoftwareModule.class, moduleId)); .orElseThrow(() -> new EntityNotFoundException(SoftwareModule.class, moduleId));
@@ -315,7 +312,7 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
assertDistributionSetIsNotAssignedToTargets(setId); assertDistributionSetIsNotAssignedToTargets(setId);
final JpaDistributionSet set = findDistributionSetAndThrowExceptionIfNotFound(setId); final JpaDistributionSet set = (JpaDistributionSet) getValid(setId);
assertSoftwareModuleQuota(setId, modules.size()); assertSoftwareModuleQuota(setId, modules.size());
@@ -329,7 +326,7 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
@Retryable(include = { @Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public DistributionSet unassignSoftwareModule(final long setId, final long moduleId) { public DistributionSet unassignSoftwareModule(final long setId, final long moduleId) {
final JpaDistributionSet set = findDistributionSetAndThrowExceptionIfNotFound(setId); final JpaDistributionSet set = (JpaDistributionSet) getValid(setId);
final JpaSoftwareModule module = findSoftwareModuleAndThrowExceptionIfNotFound(moduleId); final JpaSoftwareModule module = findSoftwareModuleAndThrowExceptionIfNotFound(moduleId);
assertDistributionSetIsNotAssignedToTargets(setId); assertDistributionSetIsNotAssignedToTargets(setId);
@@ -531,7 +528,7 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
* of the DS to touch * of the DS to touch
*/ */
private JpaDistributionSet touch(final Long distId) { private JpaDistributionSet touch(final Long distId) {
return touch(get(distId).orElseThrow(() -> new EntityNotFoundException(DistributionSet.class, distId))); return touch(getValid(distId));
} }
@Override @Override
@@ -595,7 +592,7 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
private static List<Specification<JpaDistributionSet>> buildDistributionSetSpecifications( private static List<Specification<JpaDistributionSet>> buildDistributionSetSpecifications(
final DistributionSetFilter distributionSetFilter) { final DistributionSetFilter distributionSetFilter) {
final List<Specification<JpaDistributionSet>> specList = Lists.newArrayListWithExpectedSize(8); final List<Specification<JpaDistributionSet>> specList = Lists.newArrayListWithExpectedSize(9);
Specification<JpaDistributionSet> spec; Specification<JpaDistributionSet> spec;
@@ -609,6 +606,11 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
specList.add(spec); specList.add(spec);
} }
if (distributionSetFilter.getIsValid() != null) {
spec = DistributionSetSpecification.isValid(distributionSetFilter.getIsValid());
specList.add(spec);
}
if (distributionSetFilter.getType() != null) { if (distributionSetFilter.getType() != null) {
spec = DistributionSetSpecification.byType(distributionSetFilter.getType()); spec = DistributionSetSpecification.byType(distributionSetFilter.getType());
specList.add(spec); specList.add(spec);
@@ -781,8 +783,8 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
public Page<DistributionSet> findByRsqlAndTag(final Pageable pageable, final String rsqlParam, final long tagId) { public Page<DistributionSet> findByRsqlAndTag(final Pageable pageable, final String rsqlParam, final long tagId) {
throwEntityNotFoundExceptionIfDsTagDoesNotExist(tagId); throwEntityNotFoundExceptionIfDsTagDoesNotExist(tagId);
final Specification<JpaDistributionSet> spec = RSQLUtility.buildRsqlSpecification(rsqlParam, DistributionSetFields.class, final Specification<JpaDistributionSet> spec = RSQLUtility.buildRsqlSpecification(rsqlParam,
virtualPropertyReplacer, database); DistributionSetFields.class, virtualPropertyReplacer, database);
return convertDsPage(findByCriteriaAPI(pageable, Arrays.asList(spec, DistributionSetSpecification.hasTag(tagId), return convertDsPage(findByCriteriaAPI(pageable, Arrays.asList(spec, DistributionSetSpecification.hasTag(tagId),
DistributionSetSpecification.isDeleted(false))), pageable); DistributionSetSpecification.isDeleted(false))), pageable);
@@ -797,8 +799,8 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
@Override @Override
public Page<DistributionSet> findByRsql(final Pageable pageable, final String rsqlParam) { public Page<DistributionSet> findByRsql(final Pageable pageable, final String rsqlParam) {
final Specification<JpaDistributionSet> spec = RSQLUtility.buildRsqlSpecification(rsqlParam, DistributionSetFields.class, final Specification<JpaDistributionSet> spec = RSQLUtility.buildRsqlSpecification(rsqlParam,
virtualPropertyReplacer, database); DistributionSetFields.class, virtualPropertyReplacer, database);
return convertDsPage( return convertDsPage(
findByCriteriaAPI(pageable, Arrays.asList(spec, DistributionSetSpecification.isDeleted(false))), findByCriteriaAPI(pageable, Arrays.asList(spec, DistributionSetSpecification.isDeleted(false))),
@@ -815,4 +817,41 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
return distributionSetRepository.existsById(id); return distributionSetRepository.existsById(id);
} }
@Override
public DistributionSet getOrElseThrowException(final long id) {
return get(id).orElseThrow(() -> new EntityNotFoundException(DistributionSet.class, id));
}
@Override
public DistributionSet getValidAndComplete(final long id) {
final DistributionSet distributionSet = getValid(id);
if (!distributionSet.isComplete()) {
throw new IncompleteDistributionSetException("Distribution set of type "
+ distributionSet.getType().getKey() + " is incomplete: " + distributionSet.getId());
}
return distributionSet;
}
@Override
public DistributionSet getValid(final long id) {
final DistributionSet distributionSet = getOrElseThrowException(id);
if (!distributionSet.isValid()) {
throw new InvalidDistributionSetException("Distribution set of type " + distributionSet.getType().getKey()
+ " is invalid: " + distributionSet.getId());
}
return distributionSet;
}
@Override
@Transactional
public void invalidate(final DistributionSet set) {
final JpaDistributionSet jpaSet = (JpaDistributionSet) set;
jpaSet.invalidate();
distributionSetRepository.save(jpaSet);
}
} }

View File

@@ -25,6 +25,7 @@ import org.eclipse.hawkbit.repository.RolloutHelper;
import org.eclipse.hawkbit.repository.RolloutManagement; import org.eclipse.hawkbit.repository.RolloutManagement;
import org.eclipse.hawkbit.repository.TargetManagement; import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.event.remote.RolloutGroupDeletedEvent; import org.eclipse.hawkbit.repository.event.remote.RolloutGroupDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.RolloutStoppedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.RolloutUpdatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.RolloutUpdatedEvent;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException; import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException;
@@ -155,6 +156,9 @@ public class JpaRolloutExecutor implements RolloutExecutor {
case RUNNING: case RUNNING:
handleRunningRollout((JpaRollout) rollout); handleRunningRollout((JpaRollout) rollout);
break; break;
case STOPPING:
handleStopRollout((JpaRollout) rollout);
break;
default: default:
LOGGER.error("Rollout in status {} not supposed to be handled!", rollout.getStatus()); LOGGER.error("Rollout in status {} not supposed to be handled!", rollout.getStatus());
break; break;
@@ -244,6 +248,39 @@ public class JpaRolloutExecutor implements RolloutExecutor {
sendRolloutGroupDeletedEvents(rollout); sendRolloutGroupDeletedEvents(rollout);
} }
private void handleStopRollout(final JpaRollout rollout) {
LOGGER.debug("handleStopRollout called for {}", rollout.getId());
// clean up all scheduled actions
final Slice<JpaAction> scheduledActions = findScheduledActionsByRollout(rollout);
deleteScheduledActions(rollout, scheduledActions);
// avoid another scheduler round and re-check if all scheduled actions
// has been cleaned up. we flush first to ensure that the we include the
// deletion above
entityManager.flush();
final boolean hasScheduledActionsLeft = actionRepository.countByRolloutIdAndStatus(rollout.getId(),
Status.SCHEDULED) > 0;
if (hasScheduledActionsLeft) {
return;
}
rolloutGroupRepository.findByRolloutAndStatusNotIn(rollout,
Arrays.asList(RolloutGroupStatus.FINISHED, RolloutGroupStatus.ERROR)).forEach(rolloutGroup -> {
rolloutGroup.setStatus(RolloutGroupStatus.FINISHED);
rolloutGroupRepository.save(rolloutGroup);
});
rollout.setStatus(RolloutStatus.FINISHED);
rolloutRepository.save(rollout);
final List<Long> groupIds = rollout.getRolloutGroups().stream().map(RolloutGroup::getId)
.collect(Collectors.toList());
afterCommit.afterCommit(() -> eventPublisherHolder.getEventPublisher().publishEvent(new RolloutStoppedEvent(
tenantAware.getCurrentTenant(), eventPublisherHolder.getApplicationId(), rollout.getId(), groupIds)));
}
private void handleReadyRollout(final Rollout rollout) { private void handleReadyRollout(final Rollout rollout) {
if (rollout.getStartAt() != null && rollout.getStartAt() <= System.currentTimeMillis()) { if (rollout.getStartAt() != null && rollout.getStartAt() <= System.currentTimeMillis()) {
LOGGER.debug( LOGGER.debug(

View File

@@ -100,7 +100,12 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
private static final Logger LOGGER = LoggerFactory.getLogger(JpaRolloutManagement.class); private static final Logger LOGGER = LoggerFactory.getLogger(JpaRolloutManagement.class);
private static final List<RolloutStatus> ACTIVE_ROLLOUTS = Arrays.asList(RolloutStatus.CREATING, private static final List<RolloutStatus> ACTIVE_ROLLOUTS = Arrays.asList(RolloutStatus.CREATING,
RolloutStatus.DELETING, RolloutStatus.STARTING, RolloutStatus.READY, RolloutStatus.RUNNING); RolloutStatus.DELETING, RolloutStatus.STARTING, RolloutStatus.READY, RolloutStatus.RUNNING,
RolloutStatus.STOPPING);
private static final List<RolloutStatus> ROLLOUT_STATUS_STOPPABLE = Arrays.asList(RolloutStatus.RUNNING,
RolloutStatus.CREATING, RolloutStatus.PAUSED, RolloutStatus.READY, RolloutStatus.STARTING,
RolloutStatus.WAITING_FOR_APPROVAL, RolloutStatus.APPROVAL_DENIED);
@Autowired @Autowired
private RolloutRepository rolloutRepository; private RolloutRepository rolloutRepository;
@@ -126,8 +131,8 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
private final Database database; private final Database database;
public JpaRolloutManagement(final TargetManagement targetManagement, final DeploymentManagement deploymentManagement, public JpaRolloutManagement(final TargetManagement targetManagement,
final RolloutGroupManagement rolloutGroupManagement, final DeploymentManagement deploymentManagement, final RolloutGroupManagement rolloutGroupManagement,
final DistributionSetManagement distributionSetManagement, final ApplicationContext context, final DistributionSetManagement distributionSetManagement, final ApplicationContext context,
final EventPublisherHolder eventPublisherHolder, final VirtualPropertyReplacer virtualPropertyReplacer, final EventPublisherHolder eventPublisherHolder, final VirtualPropertyReplacer virtualPropertyReplacer,
final PlatformTransactionManager txManager, final TenantAware tenantAware, final LockRegistry lockRegistry, final PlatformTransactionManager txManager, final TenantAware tenantAware, final LockRegistry lockRegistry,
@@ -151,7 +156,8 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
@Override @Override
public Page<Rollout> findByRsql(final Pageable pageable, final String rsqlParam, final boolean deleted) { public Page<Rollout> findByRsql(final Pageable pageable, final String rsqlParam, final boolean deleted) {
final List<Specification<JpaRollout>> specList = Lists.newArrayListWithExpectedSize(2); final List<Specification<JpaRollout>> specList = Lists.newArrayListWithExpectedSize(2);
specList.add(RSQLUtility.buildRsqlSpecification(rsqlParam, RolloutFields.class, virtualPropertyReplacer, database)); specList.add(
RSQLUtility.buildRsqlSpecification(rsqlParam, RolloutFields.class, virtualPropertyReplacer, database));
specList.add(RolloutSpecification.isDeletedWithDistributionSet(deleted)); specList.add(RolloutSpecification.isDeletedWithDistributionSet(deleted));
return JpaRolloutHelper.convertPage(findByCriteriaAPI(pageable, specList), pageable); return JpaRolloutHelper.convertPage(findByCriteriaAPI(pageable, specList), pageable);
@@ -400,7 +406,7 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
final String tenant = tenantAware.getCurrentTenant(); final String tenant = tenantAware.getCurrentTenant();
final String handlerId = tenant + "-rollout"; final String handlerId = createRolloutLockKey(tenant);
final Lock lock = lockRegistry.obtain(handlerId); final Lock lock = lockRegistry.obtain(handlerId);
if (!lock.tryLock()) { if (!lock.tryLock()) {
return; return;
@@ -414,6 +420,10 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
} }
} }
public static String createRolloutLockKey(final String tenant) {
return tenant + "-rollout";
}
private long handleRollout(final long rolloutId) { private long handleRollout(final long rolloutId) {
final JpaRollout rollout = rolloutRepository.findById(rolloutId) final JpaRollout rollout = rolloutRepository.findById(rolloutId)
.orElseThrow(() -> new EntityNotFoundException(Rollout.class, rolloutId)); .orElseThrow(() -> new EntityNotFoundException(Rollout.class, rolloutId));
@@ -451,6 +461,11 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
return rolloutRepository.count(JpaRolloutHelper.likeNameOrDescription(searchText, false)); return rolloutRepository.count(JpaRolloutHelper.likeNameOrDescription(searchText, false));
} }
@Override
public long countByDistributionSetIdAndRolloutIsStoppable(final long setId) {
return rolloutRepository.countByDistributionSetIdAndStatusIn(setId, ROLLOUT_STATUS_STOPPABLE);
}
@Override @Override
public Slice<Rollout> findByFiltersWithDetailedStatus(final Pageable pageable, final String searchText, public Slice<Rollout> findByFiltersWithDetailedStatus(final Pageable pageable, final String searchText,
final boolean deleted) { final boolean deleted) {
@@ -483,8 +498,7 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
// null // null
rollout.setStartAt(update.getStartAt().orElse(null)); rollout.setStartAt(update.getStartAt().orElse(null));
update.getSet().ifPresent(setId -> { update.getSet().ifPresent(setId -> {
final DistributionSet set = distributionSetManagement.get(setId) final DistributionSet set = distributionSetManagement.getValidAndComplete(setId);
.orElseThrow(() -> new EntityNotFoundException(DistributionSet.class, setId));
rollout.setDistributionSet(set); rollout.setDistributionSet(set);
}); });
@@ -596,7 +610,20 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
} }
private void runInUserContext(final BaseEntity rollout, final Runnable handler) { private void runInUserContext(final BaseEntity rollout, final Runnable handler) {
DeploymentHelper.runInNonSystemContext(handler, () -> Objects.requireNonNull(rollout.getCreatedBy()), tenantAware); DeploymentHelper.runInNonSystemContext(handler, () -> Objects.requireNonNull(rollout.getCreatedBy()),
tenantAware);
}
@Override
@Transactional
public void cancelRolloutsForDistributionSet(final DistributionSet set) {
// stop all rollouts for this distribution set
rolloutRepository.findByDistributionSetAndStatusIn(set, ROLLOUT_STATUS_STOPPABLE).forEach(rollout -> {
final JpaRollout jpaRollout = (JpaRollout) rollout;
jpaRollout.setStatus(RolloutStatus.STOPPING);
rolloutRepository.save(jpaRollout);
LOGGER.debug("Rollout {} stopped", jpaRollout.getId());
});
} }
} }

View File

@@ -26,7 +26,6 @@ import org.eclipse.hawkbit.repository.builder.TargetFilterQueryCreate;
import org.eclipse.hawkbit.repository.builder.TargetFilterQueryUpdate; import org.eclipse.hawkbit.repository.builder.TargetFilterQueryUpdate;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignActionTypeException; import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignActionTypeException;
import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignDistributionSetException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException; import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
import org.eclipse.hawkbit.repository.jpa.builder.JpaTargetFilterQueryCreate; import org.eclipse.hawkbit.repository.jpa.builder.JpaTargetFilterQueryCreate;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants; import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
@@ -72,7 +71,7 @@ import cz.jirutka.rsql.parser.RSQLParserException;
public class JpaTargetFilterQueryManagement implements TargetFilterQueryManagement { public class JpaTargetFilterQueryManagement implements TargetFilterQueryManagement {
private static final Logger LOGGER = LoggerFactory.getLogger(JpaTargetFilterQueryManagement.class); private static final Logger LOGGER = LoggerFactory.getLogger(JpaTargetFilterQueryManagement.class);
private final TargetFilterQueryRepository targetFilterQueryRepository; private final TargetFilterQueryRepository targetFilterQueryRepository;
private final TargetManagement targetManagement; private final TargetManagement targetManagement;
@@ -147,6 +146,11 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
return targetFilterQueryRepository.count(); return targetFilterQueryRepository.count();
} }
@Override
public long countByAutoAssignDistributionSetId(final long autoAssignDistributionSetId) {
return targetFilterQueryRepository.countByAutoAssignDistributionSetId(autoAssignDistributionSetId);
}
private static Page<TargetFilterQuery> convertPage(final Page<JpaTargetFilterQuery> findAll, private static Page<TargetFilterQuery> convertPage(final Page<JpaTargetFilterQuery> findAll,
final Pageable pageable) { final Pageable pageable) {
return new PageImpl<>(new ArrayList<>(findAll.getContent()), pageable, findAll.getTotalElements()); return new PageImpl<>(new ArrayList<>(findAll.getContent()), pageable, findAll.getTotalElements());
@@ -165,8 +169,8 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
public Page<TargetFilterQuery> findByRsql(final Pageable pageable, final String rsqlFilter) { public Page<TargetFilterQuery> findByRsql(final Pageable pageable, final String rsqlFilter) {
List<Specification<JpaTargetFilterQuery>> specList = Collections.emptyList(); List<Specification<JpaTargetFilterQuery>> specList = Collections.emptyList();
if (!StringUtils.isEmpty(rsqlFilter)) { if (!StringUtils.isEmpty(rsqlFilter)) {
specList = Collections.singletonList( specList = Collections.singletonList(RSQLUtility.buildRsqlSpecification(rsqlFilter,
RSQLUtility.buildRsqlSpecification(rsqlFilter, TargetFilterQueryFields.class, virtualPropertyReplacer, database)); TargetFilterQueryFields.class, virtualPropertyReplacer, database));
} }
return convertPage(findTargetFilterQueryByCriteriaAPI(pageable, specList), pageable); return convertPage(findTargetFilterQueryByCriteriaAPI(pageable, specList), pageable);
} }
@@ -185,13 +189,13 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
final String rsqlFilter) { final String rsqlFilter) {
final List<Specification<JpaTargetFilterQuery>> specList = Lists.newArrayListWithExpectedSize(2); final List<Specification<JpaTargetFilterQuery>> specList = Lists.newArrayListWithExpectedSize(2);
final DistributionSet distributionSet = findDistributionSetAndThrowExceptionIfNotFound(setId); final DistributionSet distributionSet = distributionSetManagement.getOrElseThrowException(setId);
specList.add(TargetFilterQuerySpecification.byAutoAssignDS(distributionSet)); specList.add(TargetFilterQuerySpecification.byAutoAssignDS(distributionSet));
if (!StringUtils.isEmpty(rsqlFilter)) { if (!StringUtils.isEmpty(rsqlFilter)) {
specList.add( specList.add(RSQLUtility.buildRsqlSpecification(rsqlFilter, TargetFilterQueryFields.class,
RSQLUtility.buildRsqlSpecification(rsqlFilter, TargetFilterQueryFields.class, virtualPropertyReplacer, database)); virtualPropertyReplacer, database));
} }
return convertPage(findTargetFilterQueryByCriteriaAPI(pageable, specList), pageable); return convertPage(findTargetFilterQueryByCriteriaAPI(pageable, specList), pageable);
} }
@@ -264,8 +268,9 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
// specify an // specify an
// auto-assign distribution set when creating a target filter query // auto-assign distribution set when creating a target filter query
assertMaxTargetsQuota(targetFilterQuery.getQuery()); assertMaxTargetsQuota(targetFilterQuery.getQuery());
final JpaDistributionSet ds = findDistributionSetAndThrowExceptionIfNotFound(update.getDsId()); final JpaDistributionSet ds = (JpaDistributionSet) distributionSetManagement
verifyDistributionSetAndThrowExceptionIfNotValid(ds); .getValidAndComplete(update.getDsId());
verifyDistributionSetAndThrowExceptionIfDeleted(ds);
targetFilterQuery.setAutoAssignDistributionSet(ds); targetFilterQuery.setAutoAssignDistributionSet(ds);
targetFilterQuery.setAutoAssignInitiatedBy(tenantAware.getCurrentUsername()); targetFilterQuery.setAutoAssignInitiatedBy(tenantAware.getCurrentUsername());
targetFilterQuery.setAutoAssignActionType(sanitizeAutoAssignActionType(update.getActionType())); targetFilterQuery.setAutoAssignActionType(sanitizeAutoAssignActionType(update.getActionType()));
@@ -274,9 +279,9 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
return targetFilterQueryRepository.save(targetFilterQuery); return targetFilterQueryRepository.save(targetFilterQuery);
} }
private static void verifyDistributionSetAndThrowExceptionIfNotValid(final DistributionSet distributionSet) { private static void verifyDistributionSetAndThrowExceptionIfDeleted(final DistributionSet distributionSet) {
if (!distributionSet.isComplete() || distributionSet.isDeleted()) { if (distributionSet.isDeleted()) {
throw new InvalidAutoAssignDistributionSetException(); throw new EntityNotFoundException(DistributionSet.class, distributionSet.getId());
} }
} }
@@ -292,11 +297,6 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
return actionType; return actionType;
} }
private JpaDistributionSet findDistributionSetAndThrowExceptionIfNotFound(final Long setId) {
return (JpaDistributionSet) distributionSetManagement.get(setId)
.orElseThrow(() -> new EntityNotFoundException(DistributionSet.class, setId));
}
private JpaTargetFilterQuery findTargetFilterQueryOrThrowExceptionIfNotFound(final Long queryId) { private JpaTargetFilterQuery findTargetFilterQueryOrThrowExceptionIfNotFound(final Long queryId) {
return targetFilterQueryRepository.findById(queryId) return targetFilterQueryRepository.findById(queryId)
.orElseThrow(() -> new EntityNotFoundException(TargetFilterQuery.class, queryId)); .orElseThrow(() -> new EntityNotFoundException(TargetFilterQuery.class, queryId));
@@ -317,4 +317,11 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
QuotaHelper.assertAssignmentQuota(targetManagement.countByRsql(query), QuotaHelper.assertAssignmentQuota(targetManagement.countByRsql(query),
quotaManagement.getMaxTargetsPerAutoAssignment(), Target.class, TargetFilterQuery.class); quotaManagement.getMaxTargetsPerAutoAssignment(), Target.class, TargetFilterQuery.class);
} }
@Override
@Transactional
public void cancelAutoAssignmentForDistributionSet(final long setId) {
targetFilterQueryRepository.unsetAutoAssignDistributionSetAndActionType(setId);
LOGGER.debug("Auto assignments for distribution sets {} deactivated", setId);
}
} }

View File

@@ -82,8 +82,8 @@ import org.eclipse.hawkbit.repository.jpa.rollout.condition.PauseRolloutGroupAct
import org.eclipse.hawkbit.repository.jpa.rollout.condition.StartNextGroupRolloutGroupSuccessAction; import org.eclipse.hawkbit.repository.jpa.rollout.condition.StartNextGroupRolloutGroupSuccessAction;
import org.eclipse.hawkbit.repository.jpa.rollout.condition.ThresholdRolloutGroupErrorCondition; import org.eclipse.hawkbit.repository.jpa.rollout.condition.ThresholdRolloutGroupErrorCondition;
import org.eclipse.hawkbit.repository.jpa.rollout.condition.ThresholdRolloutGroupSuccessCondition; import org.eclipse.hawkbit.repository.jpa.rollout.condition.ThresholdRolloutGroupSuccessCondition;
import org.eclipse.hawkbit.repository.jpa.rsql.RsqlParserValidationOracle;
import org.eclipse.hawkbit.repository.jpa.rsql.DefaultRsqlVisitorFactory; import org.eclipse.hawkbit.repository.jpa.rsql.DefaultRsqlVisitorFactory;
import org.eclipse.hawkbit.repository.jpa.rsql.RsqlParserValidationOracle;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.Rollout;
@@ -238,13 +238,14 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
} }
@Bean @Bean
TargetBuilder targetBuilder(final TargetTypeManagement targetTypeManagement){ TargetBuilder targetBuilder(final TargetTypeManagement targetTypeManagement) {
return new JpaTargetBuilder(targetTypeManagement); return new JpaTargetBuilder(targetTypeManagement);
} }
/** /**
* @param dsTypeManagement * @param dsTypeManagement
* for loading {@link TargetType#getCompatibleDistributionSetTypes()} * for loading
* {@link TargetType#getCompatibleDistributionSetTypes()}
* @return TargetTypeBuilder bean * @return TargetTypeBuilder bean
*/ */
@Bean @Bean
@@ -434,7 +435,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
@Override @Override
@Bean @Bean
public PlatformTransactionManager transactionManager( public PlatformTransactionManager transactionManager(
ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) { final ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
return new MultiTenantJpaTransactionManager(); return new MultiTenantJpaTransactionManager();
} }
@@ -485,12 +486,12 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
DistributionSetTypeManagement distributionSetTypeManagement( DistributionSetTypeManagement distributionSetTypeManagement(
final DistributionSetTypeRepository distributionSetTypeRepository, final DistributionSetTypeRepository distributionSetTypeRepository,
final SoftwareModuleTypeRepository softwareModuleTypeRepository, final SoftwareModuleTypeRepository softwareModuleTypeRepository,
final DistributionSetRepository distributionSetRepository, final DistributionSetRepository distributionSetRepository, final TargetTypeRepository targetTypeRepository,
final TargetTypeRepository targetTypeRepository,
final VirtualPropertyReplacer virtualPropertyReplacer, final JpaProperties properties, final VirtualPropertyReplacer virtualPropertyReplacer, final JpaProperties properties,
final QuotaManagement quotaManagement) { final QuotaManagement quotaManagement) {
return new JpaDistributionSetTypeManagement(distributionSetTypeRepository, softwareModuleTypeRepository, return new JpaDistributionSetTypeManagement(distributionSetTypeRepository, softwareModuleTypeRepository,
distributionSetRepository, targetTypeRepository, virtualPropertyReplacer, properties.getDatabase(), quotaManagement); distributionSetRepository, targetTypeRepository, virtualPropertyReplacer, properties.getDatabase(),
quotaManagement);
} }
/** /**
@@ -541,13 +542,15 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
final TargetRepository targetRepository, final TargetMetadataRepository targetMetadataRepository, final TargetRepository targetRepository, final TargetMetadataRepository targetMetadataRepository,
final RolloutGroupRepository rolloutGroupRepository, final RolloutGroupRepository rolloutGroupRepository,
final DistributionSetRepository distributionSetRepository, final DistributionSetRepository distributionSetRepository,
final TargetFilterQueryRepository targetFilterQueryRepository, final TargetTypeRepository targetTypeRepository, final TargetFilterQueryRepository targetFilterQueryRepository,
final TargetTagRepository targetTagRepository, final EventPublisherHolder eventPublisherHolder, final TargetTypeRepository targetTypeRepository, final TargetTagRepository targetTagRepository,
final TenantAware tenantAware, final AfterTransactionCommitExecutor afterCommit, final EventPublisherHolder eventPublisherHolder, final TenantAware tenantAware,
final VirtualPropertyReplacer virtualPropertyReplacer, final JpaProperties properties) { final AfterTransactionCommitExecutor afterCommit, final VirtualPropertyReplacer virtualPropertyReplacer,
return new JpaTargetManagement(entityManager, quotaManagement, targetRepository, targetTypeRepository, targetMetadataRepository, final JpaProperties properties) {
rolloutGroupRepository, distributionSetRepository, targetFilterQueryRepository, targetTagRepository, return new JpaTargetManagement(entityManager, quotaManagement, targetRepository, targetTypeRepository,
eventPublisherHolder, tenantAware, afterCommit, virtualPropertyReplacer, properties.getDatabase()); targetMetadataRepository, rolloutGroupRepository, distributionSetRepository,
targetFilterQueryRepository, targetTagRepository, eventPublisherHolder, tenantAware, afterCommit,
virtualPropertyReplacer, properties.getDatabase());
} }
/** /**
@@ -718,17 +721,17 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
@ConditionalOnMissingBean @ConditionalOnMissingBean
DeploymentManagement deploymentManagement(final EntityManager entityManager, DeploymentManagement deploymentManagement(final EntityManager entityManager,
final ActionRepository actionRepository, final DistributionSetRepository distributionSetRepository, final ActionRepository actionRepository, final DistributionSetRepository distributionSetRepository,
final TargetRepository targetRepository, final ActionStatusRepository actionStatusRepository, final DistributionSetManagement distributionSetManagement, final TargetRepository targetRepository,
final AuditorAware<String> auditorProvider, final EventPublisherHolder eventPublisherHolder, final ActionStatusRepository actionStatusRepository, final AuditorAware<String> auditorProvider,
final AfterTransactionCommitExecutor afterCommit, final VirtualPropertyReplacer virtualPropertyReplacer, final EventPublisherHolder eventPublisherHolder, final AfterTransactionCommitExecutor afterCommit,
final PlatformTransactionManager txManager, final VirtualPropertyReplacer virtualPropertyReplacer, final PlatformTransactionManager txManager,
final TenantConfigurationManagement tenantConfigurationManagement, final QuotaManagement quotaManagement, final TenantConfigurationManagement tenantConfigurationManagement, final QuotaManagement quotaManagement,
final SystemSecurityContext systemSecurityContext, final TenantAware tenantAware, final SystemSecurityContext systemSecurityContext, final TenantAware tenantAware,
final JpaProperties properties, final RepositoryProperties repositoryProperties) { final JpaProperties properties, final RepositoryProperties repositoryProperties) {
return new JpaDeploymentManagement(entityManager, actionRepository, distributionSetRepository, targetRepository, return new JpaDeploymentManagement(entityManager, actionRepository, distributionSetManagement,
actionStatusRepository, auditorProvider, eventPublisherHolder, afterCommit, virtualPropertyReplacer, distributionSetRepository, targetRepository, actionStatusRepository, auditorProvider,
txManager, tenantConfigurationManagement, quotaManagement, systemSecurityContext, tenantAware, eventPublisherHolder, afterCommit, virtualPropertyReplacer, txManager, tenantConfigurationManagement,
properties.getDatabase(), repositoryProperties); quotaManagement, systemSecurityContext, tenantAware, properties.getDatabase(), repositoryProperties);
} }
/** /**
@@ -813,7 +816,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
/** /**
* {@link AutoAssignScheduler} bean. * {@link AutoAssignScheduler} bean.
* *
* Note: does not activate in test profile, otherwise it is hard to test the * Note: does not activate in test profile, otherwise it is hard to test the
* auto assign functionality. * auto assign functionality.
* *
@@ -843,12 +846,12 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
/** /**
* {@link AutoActionCleanup} bean. * {@link AutoActionCleanup} bean.
* *
* @param deploymentManagement * @param deploymentManagement
* Deployment management service * Deployment management service
* @param configManagement * @param configManagement
* Tenant configuration service * Tenant configuration service
* *
* @return a new {@link AutoActionCleanup} bean * @return a new {@link AutoActionCleanup} bean
*/ */
@Bean @Bean
@@ -859,7 +862,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
/** /**
* {@link AutoCleanupScheduler} bean. * {@link AutoCleanupScheduler} bean.
* *
* @param systemManagement * @param systemManagement
* to find all tenants * to find all tenants
* @param systemSecurityContext * @param systemSecurityContext
@@ -868,7 +871,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
* to lock the tenant for auto assignment * to lock the tenant for auto assignment
* @param cleanupTasks * @param cleanupTasks
* a list of cleanup tasks * a list of cleanup tasks
* *
* @return a new {@link AutoCleanupScheduler} bean * @return a new {@link AutoCleanupScheduler} bean
*/ */
@Bean @Bean
@@ -883,10 +886,10 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
/** /**
* {@link RolloutScheduler} bean. * {@link RolloutScheduler} bean.
* *
* Note: does not activate in test profile, otherwise it is hard to test the * Note: does not activate in test profile, otherwise it is hard to test the
* rollout handling functionality. * rollout handling functionality.
* *
* @param systemManagement * @param systemManagement
* to find all tenants * to find all tenants
* @param rolloutManagement * @param rolloutManagement
@@ -906,7 +909,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
/** /**
* Creates the {@link RsqlVisitorFactory} bean. * Creates the {@link RsqlVisitorFactory} bean.
* *
* @return A new {@link RsqlVisitorFactory} bean. * @return A new {@link RsqlVisitorFactory} bean.
*/ */
@Bean @Bean
@@ -917,7 +920,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
/** /**
* Obtains the {@link RsqlVisitorFactoryHolder} bean. * Obtains the {@link RsqlVisitorFactoryHolder} bean.
* *
* @return The {@link RsqlVisitorFactoryHolder} singleton. * @return The {@link RsqlVisitorFactoryHolder} singleton.
*/ */
@Bean @Bean
@@ -925,4 +928,22 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
return RsqlVisitorFactoryHolder.getInstance(); return RsqlVisitorFactoryHolder.getInstance();
} }
/**
* {@link JpaDistributionSetInvalidationManagement} bean.
*
* @return a new {@link JpaDistributionSetInvalidationManagement}
*/
@Bean
@ConditionalOnMissingBean
JpaDistributionSetInvalidationManagement distributionSetInvalidationManagement(
final DistributionSetManagement distributionSetManagement, final RolloutManagement rolloutManagement,
final DeploymentManagement deploymentManagement,
final TargetFilterQueryManagement targetFilterQueryManagement, final PlatformTransactionManager txManager,
final RepositoryProperties repositoryProperties, final TenantAware tenantAware,
final LockRegistry lockRegistry) {
return new JpaDistributionSetInvalidationManagement(distributionSetManagement, rolloutManagement,
deploymentManagement, targetFilterQueryManagement, txManager, repositoryProperties, tenantAware,
lockRegistry);
}
} }

View File

@@ -8,6 +8,7 @@
*/ */
package org.eclipse.hawkbit.repository.jpa; package org.eclipse.hawkbit.repository.jpa;
import java.util.Collection;
import java.util.List; import java.util.List;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout; import org.eclipse.hawkbit.repository.jpa.model.JpaRollout;
@@ -33,7 +34,7 @@ public interface RolloutGroupRepository
/** /**
* Retrieves all {@link RolloutGroup} referring a specific rollout in the * Retrieves all {@link RolloutGroup} referring a specific rollout in the
* order of creating them. ID ASC. * order of creating them. ID ASC.
* *
* @param rollout * @param rollout
* the rollout the rolloutgroups belong to * the rollout the rolloutgroups belong to
* @return the rollout groups belonging to a rollout ordered by ID ASC. * @return the rollout groups belonging to a rollout ordered by ID ASC.
@@ -43,7 +44,7 @@ public interface RolloutGroupRepository
/** /**
* Retrieves all {@link RolloutGroup} referring a specific rollout in a * Retrieves all {@link RolloutGroup} referring a specific rollout in a
* specific {@link RolloutGroupStatus}. * specific {@link RolloutGroupStatus}.
* *
* @param rollout * @param rollout
* the rollout the rolloutgroup belong to * the rollout the rolloutgroup belong to
* @param status * @param status
@@ -58,7 +59,7 @@ public interface RolloutGroupRepository
* the spring-data, so this is specific usecase regarding to the * the spring-data, so this is specific usecase regarding to the
* rollout-management to find out rolloutgroups which are in specific * rollout-management to find out rolloutgroups which are in specific
* states. * states.
* *
* @param rolloutId * @param rolloutId
* the ID of the rollout the rolloutgroup belong to * the ID of the rollout the rolloutgroup belong to
* @param rolloutGroupStatus1 * @param rolloutGroupStatus1
@@ -74,10 +75,10 @@ public interface RolloutGroupRepository
@Param("status2") RolloutGroupStatus rolloutGroupStatus2); @Param("status2") RolloutGroupStatus rolloutGroupStatus2);
/** /**
* *
* Counts all rollout-groups refering to a given {@link Rollout} by its ID * Counts all rollout-groups refering to a given {@link Rollout} by its ID
* and groups which not having the given status. * and groups which not having the given status.
* *
* @param rolloutId * @param rolloutId
* the ID of the rollout refering the groups * the ID of the rollout refering the groups
* @param status1 * @param status1
@@ -97,7 +98,7 @@ public interface RolloutGroupRepository
/** /**
* Retrieves all {@link RolloutGroup} for a specific parent in a specific * Retrieves all {@link RolloutGroup} for a specific parent in a specific
* status. Retrieves the child rolloutgroup for a specific status. * status. Retrieves the child rolloutgroup for a specific status.
* *
* @param rolloutGroupId * @param rolloutGroupId
* the rolloutgroupId to find the parents * the rolloutgroupId to find the parents
* @param status * @param status
@@ -111,7 +112,7 @@ public interface RolloutGroupRepository
/** /**
* Updates all {@link RolloutGroup#getStatus()} of children for given * Updates all {@link RolloutGroup#getStatus()} of children for given
* parent. * parent.
* *
* @param parent * @param parent
* the parent rolloutgroup * the parent rolloutgroup
* @param status * @param status
@@ -125,7 +126,7 @@ public interface RolloutGroupRepository
/** /**
* Retrieves all {@link RolloutGroup} for a specific rollout and status not * Retrieves all {@link RolloutGroup} for a specific rollout and status not
* having ordered by ID DESC, latest top. * having ordered by ID DESC, latest top.
* *
* @param rollout * @param rollout
* the rollout the rolloutgroup belong to * the rollout the rolloutgroup belong to
* @param notStatus * @param notStatus
@@ -135,9 +136,22 @@ public interface RolloutGroupRepository
*/ */
List<JpaRolloutGroup> findByRolloutAndStatusNotOrderByIdDesc(JpaRollout rollout, RolloutGroupStatus notStatus); List<JpaRolloutGroup> findByRolloutAndStatusNotOrderByIdDesc(JpaRollout rollout, RolloutGroupStatus notStatus);
/**
* Retrieves all {@link RolloutGroup}s for a specific rollout and status not
* having.
*
* @param rollout
* the rollout the rolloutgroup belong to
* @param status
* the status which the rolloutgroup should not have
* @return rolloutgroup referring to a rollout and not having a specific
* status.
*/
List<JpaRolloutGroup> findByRolloutAndStatusNotIn(JpaRollout rollout, Collection<RolloutGroupStatus> status);
/** /**
* Retrieves all {@link RolloutGroup} for a specific rollout. * Retrieves all {@link RolloutGroup} for a specific rollout.
* *
* @param rolloutId * @param rolloutId
* the ID of the rollout to find the rollout groups * the ID of the rollout to find the rollout groups
* @param page * @param page
@@ -148,10 +162,10 @@ public interface RolloutGroupRepository
/** /**
* Counts all {@link RolloutGroup} for a specific rollout. * Counts all {@link RolloutGroup} for a specific rollout.
* *
* @param rolloutId * @param rolloutId
* the ID of the rollout to find the rollout groups * the ID of the rollout to find the rollout groups
* *
* @return the amount of found {@link RolloutGroup}s. * @return the amount of found {@link RolloutGroup}s.
*/ */
long countByRolloutId(Long rolloutId); long countByRolloutId(Long rolloutId);

View File

@@ -15,6 +15,7 @@ import java.util.Optional;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout; import org.eclipse.hawkbit.repository.jpa.model.JpaRollout;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus; import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus;
import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity; import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity;
@@ -33,7 +34,7 @@ public interface RolloutRepository
/** /**
* Retrieves all {@link Rollout} for given status. * Retrieves all {@link Rollout} for given status.
* *
* @param status * @param status
* the status of the rollouts to find * the status of the rollouts to find
* @return the list of {@link Rollout} for specific status * @return the list of {@link Rollout} for specific status
@@ -44,7 +45,7 @@ public interface RolloutRepository
/** /**
* Retrieves all {@link Rollout} for a specific {@code name} * Retrieves all {@link Rollout} for a specific {@code name}
* *
* @param name * @param name
* the rollout name * the rollout name
* @return {@link Rollout} for specific name * @return {@link Rollout} for specific name
@@ -64,4 +65,28 @@ public interface RolloutRepository
@Transactional @Transactional
@Query("DELETE FROM JpaRollout t WHERE t.tenant = :tenant") @Query("DELETE FROM JpaRollout t WHERE t.tenant = :tenant")
void deleteByTenant(@Param("tenant") String tenant); void deleteByTenant(@Param("tenant") String tenant);
/**
* Retrieves all {@link Rollout}s for a specific {@link DistributionSet} in
* a given {@link RolloutStatus}.
*
* @param set
* the distribution set
* @param status
* the status of the rollout
* @return {@link Rollout} for specific distribution set
*/
List<Rollout> findByDistributionSetAndStatusIn(DistributionSet set, Collection<RolloutStatus> status);
/**
* Counts all {@link Rollout}s for a specific {@link DistributionSet} in a
* given {@link RolloutStatus}.
*
* @param distributionSetId
* the distribution set
* @param status
* the status of the rollout
* @return the count
*/
long countByDistributionSetIdAndStatusIn(long distributionSetId, Collection<RolloutStatus> status);
} }

View File

@@ -32,7 +32,7 @@ public interface TargetFilterQueryRepository
/** /**
* Find customer target filter by name * Find customer target filter by name
* *
* @param name * @param name
* @return custom target filter * @return custom target filter
*/ */
@@ -56,6 +56,16 @@ public interface TargetFilterQueryRepository
@Query("update JpaTargetFilterQuery d set d.autoAssignDistributionSet = NULL, d.autoAssignActionType = NULL where d.autoAssignDistributionSet in :ids") @Query("update JpaTargetFilterQuery d set d.autoAssignDistributionSet = NULL, d.autoAssignActionType = NULL where d.autoAssignDistributionSet in :ids")
void unsetAutoAssignDistributionSetAndActionType(@Param("ids") Long... dsIds); void unsetAutoAssignDistributionSetAndActionType(@Param("ids") Long... dsIds);
/**
* Counts all target filters that have a given auto assign distribution set
* assigned.
*
* @param autoAssignDistributionSetId
* the id of the distribution set
* @return the count
*/
long countByAutoAssignDistributionSetId(long autoAssignDistributionSetId);
/** /**
* Deletes all {@link TenantAwareBaseEntity} of a given tenant. For safety * Deletes all {@link TenantAwareBaseEntity} of a given tenant. For safety
* reasons (this is a "delete everything" query after all) we add the tenant * reasons (this is a "delete everything" query after all) we add the tenant

View File

@@ -11,9 +11,7 @@ package org.eclipse.hawkbit.repository.jpa.builder;
import org.eclipse.hawkbit.repository.DistributionSetManagement; import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.builder.AbstractRolloutUpdateCreate; import org.eclipse.hawkbit.repository.builder.AbstractRolloutUpdateCreate;
import org.eclipse.hawkbit.repository.builder.RolloutCreate; import org.eclipse.hawkbit.repository.builder.RolloutCreate;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout; import org.eclipse.hawkbit.repository.jpa.model.JpaRollout;
import org.eclipse.hawkbit.repository.model.DistributionSet;
public class JpaRolloutCreate extends AbstractRolloutUpdateCreate<RolloutCreate> implements RolloutCreate { public class JpaRolloutCreate extends AbstractRolloutUpdateCreate<RolloutCreate> implements RolloutCreate {
private final DistributionSetManagement distributionSetManagement; private final DistributionSetManagement distributionSetManagement;
@@ -28,7 +26,7 @@ public class JpaRolloutCreate extends AbstractRolloutUpdateCreate<RolloutCreate>
rollout.setName(name); rollout.setName(name);
rollout.setDescription(description); rollout.setDescription(description);
rollout.setDistributionSet(findDistributionSetAndThrowExceptionIfNotFound(set)); rollout.setDistributionSet(distributionSetManagement.getValidAndComplete(set));
rollout.setTargetFilterQuery(targetFilterQuery); rollout.setTargetFilterQuery(targetFilterQuery);
rollout.setStartAt(startAt); rollout.setStartAt(startAt);
rollout.setWeight(weight); rollout.setWeight(weight);
@@ -43,9 +41,4 @@ public class JpaRolloutCreate extends AbstractRolloutUpdateCreate<RolloutCreate>
return rollout; return rollout;
} }
private DistributionSet findDistributionSetAndThrowExceptionIfNotFound(final Long setId) {
return distributionSetManagement.get(setId)
.orElseThrow(() -> new EntityNotFoundException(DistributionSet.class, setId));
}
} }

View File

@@ -11,11 +11,9 @@ package org.eclipse.hawkbit.repository.jpa.builder;
import org.eclipse.hawkbit.repository.DistributionSetManagement; import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.builder.AbstractTargetFilterQueryUpdateCreate; import org.eclipse.hawkbit.repository.builder.AbstractTargetFilterQueryUpdateCreate;
import org.eclipse.hawkbit.repository.builder.TargetFilterQueryCreate; import org.eclipse.hawkbit.repository.builder.TargetFilterQueryCreate;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignActionTypeException; import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignActionTypeException;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetFilterQuery; import org.eclipse.hawkbit.repository.jpa.model.JpaTargetFilterQuery;
import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery; import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
/** /**
@@ -35,16 +33,11 @@ public class JpaTargetFilterQueryCreate extends AbstractTargetFilterQueryUpdateC
public JpaTargetFilterQuery build() { public JpaTargetFilterQuery build() {
return new JpaTargetFilterQuery(name, query, return new JpaTargetFilterQuery(name, query,
getAutoAssignDistributionSetId().map(this::findDistributionSetAndThrowExceptionIfNotFound).orElse(null), getAutoAssignDistributionSetId().map(distributionSetManagement::getValidAndComplete).orElse(null),
getAutoAssignActionType().filter(JpaTargetFilterQueryCreate::isAutoAssignActionTypeValid).orElse(null), getAutoAssignActionType().filter(JpaTargetFilterQueryCreate::isAutoAssignActionTypeValid).orElse(null),
weight); weight);
} }
private DistributionSet findDistributionSetAndThrowExceptionIfNotFound(final Long setId) {
return distributionSetManagement.get(setId)
.orElseThrow(() -> new EntityNotFoundException(DistributionSet.class, setId));
}
private static boolean isAutoAssignActionTypeValid(final ActionType actionType) { private static boolean isAutoAssignActionTypeValid(final ActionType actionType) {
if (!TargetFilterQuery.ALLOWED_AUTO_ASSIGN_ACTION_TYPES.contains(actionType)) { if (!TargetFilterQuery.ALLOWED_AUTO_ASSIGN_ACTION_TYPES.contains(actionType)) {
throw new InvalidAutoAssignActionTypeException(); throw new InvalidAutoAssignActionTypeException();

View File

@@ -116,6 +116,9 @@ public class JpaDistributionSet extends AbstractJpaNamedVersionedEntity implemen
@Column(name = "complete") @Column(name = "complete")
private boolean complete; private boolean complete;
@Column(name = "valid")
private boolean valid;
/** /**
* Default constructor. * Default constructor.
*/ */
@@ -145,6 +148,7 @@ public class JpaDistributionSet extends AbstractJpaNamedVersionedEntity implemen
super(name, version, description); super(name, version, description);
this.requiredMigrationStep = requiredMigrationStep; this.requiredMigrationStep = requiredMigrationStep;
this.valid = true;
this.type = type; this.type = type;
if (moduleList != null) { if (moduleList != null) {
moduleList.forEach(this::addModule); moduleList.forEach(this::addModule);
@@ -327,6 +331,15 @@ public class JpaDistributionSet extends AbstractJpaNamedVersionedEntity implemen
return complete; return complete;
} }
@Override
public boolean isValid() {
return valid;
}
public void invalidate() {
this.valid = false;
}
@Override @Override
public void fireCreateEvent(final DescriptorEvent descriptorEvent) { public void fireCreateEvent(final DescriptorEvent descriptorEvent) {
publishEventWithEventPublisher( publishEventWithEventPublisher(

View File

@@ -93,7 +93,8 @@ public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, Event
@ConversionValue(objectValue = "DELETING", dataValue = "9"), @ConversionValue(objectValue = "DELETING", dataValue = "9"),
@ConversionValue(objectValue = "DELETED", dataValue = "10"), @ConversionValue(objectValue = "DELETED", dataValue = "10"),
@ConversionValue(objectValue = "WAITING_FOR_APPROVAL", dataValue = "11"), @ConversionValue(objectValue = "WAITING_FOR_APPROVAL", dataValue = "11"),
@ConversionValue(objectValue = "APPROVAL_DENIED", dataValue = "12") }) @ConversionValue(objectValue = "APPROVAL_DENIED", dataValue = "12"),
@ConversionValue(objectValue = "STOPPING", dataValue = "13") })
@Convert("rolloutstatus") @Convert("rolloutstatus")
@NotNull @NotNull
private RolloutStatus status = RolloutStatus.CREATING; private RolloutStatus status = RolloutStatus.CREATING;

View File

@@ -45,7 +45,7 @@ public final class DistributionSetSpecification {
/** /**
* {@link Specification} for retrieving {@link DistributionSet}s by its * {@link Specification} for retrieving {@link DistributionSet}s by its
* DELETED attribute. * DELETED attribute.
* *
* @param isDeleted * @param isDeleted
* TRUE/FALSE are compared to the attribute DELETED. If NULL the * TRUE/FALSE are compared to the attribute DELETED. If NULL the
* attribute is ignored * attribute is ignored
@@ -59,7 +59,7 @@ public final class DistributionSetSpecification {
/** /**
* {@link Specification} for retrieving {@link DistributionSet}s by its * {@link Specification} for retrieving {@link DistributionSet}s by its
* COMPLETED attribute. * COMPLETED attribute.
* *
* @param isCompleted * @param isCompleted
* TRUE/FALSE are compared to the attribute COMPLETED. If NULL * TRUE/FALSE are compared to the attribute COMPLETED. If NULL
* the attribute is ignored * the attribute is ignored
@@ -70,6 +70,20 @@ public final class DistributionSetSpecification {
} }
/**
* {@link Specification} for retrieving {@link DistributionSet}s by its
* VALID attribute.
*
* @param isValid
* TRUE/FALSE are compared to the attribute VALID. If NULL the
* attribute is ignored
* @return the {@link DistributionSet} {@link Specification}
*/
public static Specification<JpaDistributionSet> isValid(final Boolean isValid) {
return (targetRoot, query, cb) -> cb.equal(targetRoot.<Boolean> get(JpaDistributionSet_.valid), isValid);
}
/** /**
* {@link Specification} for retrieving {@link DistributionSet} with given * {@link Specification} for retrieving {@link DistributionSet} with given
* {@link DistributionSet#getId()}. * {@link DistributionSet#getId()}.
@@ -111,7 +125,7 @@ public final class DistributionSetSpecification {
/** /**
* {@link Specification} for retrieving {@link DistributionSet}s by "like * {@link Specification} for retrieving {@link DistributionSet}s by "like
* name or like description or like version". * name or like description or like version".
* *
* @param subString * @param subString
* to be filtered on * to be filtered on
* @return the {@link DistributionSet} {@link Specification} * @return the {@link DistributionSet} {@link Specification}
@@ -126,7 +140,7 @@ public final class DistributionSetSpecification {
/** /**
* {@link Specification} for retrieving {@link DistributionSet}s by "like * {@link Specification} for retrieving {@link DistributionSet}s by "like
* name and like version". * name and like version".
* *
* @param name * @param name
* to be filtered on * to be filtered on
* @param version * @param version
@@ -142,7 +156,7 @@ public final class DistributionSetSpecification {
/** /**
* {@link Specification} for retrieving {@link DistributionSet}s by "has at * {@link Specification} for retrieving {@link DistributionSet}s by "has at
* least one of the given tag names". * least one of the given tag names".
* *
* @param tagNames * @param tagNames
* to be filtered on * to be filtered on
* @param selectDSWithNoTag * @param selectDSWithNoTag
@@ -187,7 +201,7 @@ public final class DistributionSetSpecification {
/** /**
* returns query criteria {@link Specification} comparing case insensitive * returns query criteria {@link Specification} comparing case insensitive
* "NAME == AND VERSION ==". * "NAME == AND VERSION ==".
* *
* @param name * @param name
* to be filtered on * to be filtered on
* @param version * @param version

View File

@@ -0,0 +1,3 @@
ALTER TABLE sp_distribution_set ADD COLUMN valid BOOLEAN;
UPDATE sp_distribution_set SET valid = 1;

View File

@@ -0,0 +1,3 @@
ALTER TABLE sp_distribution_set ADD COLUMN valid BOOLEAN;
UPDATE sp_distribution_set SET valid = 1;

View File

@@ -0,0 +1,3 @@
ALTER TABLE sp_distribution_set ADD COLUMN valid BOOLEAN;
UPDATE sp_distribution_set SET valid = 1;

View File

@@ -0,0 +1,3 @@
ALTER TABLE sp_distribution_set ADD COLUMN valid BOOLEAN;
UPDATE sp_distribution_set SET valid = 1;

View File

@@ -0,0 +1,3 @@
ALTER TABLE sp_distribution_set ADD COLUMN valid BOOLEAN;
UPDATE sp_distribution_set SET valid = 1;

View File

@@ -8,10 +8,13 @@
*/ */
package org.eclipse.hawkbit.repository.event.remote.entity; package org.eclipse.hawkbit.repository.event.remote.entity;
import java.util.Collections;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition; import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition;
import org.eclipse.hawkbit.repository.model.RolloutGroupConditionBuilder; import org.eclipse.hawkbit.repository.model.RolloutGroupConditionBuilder;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import io.qameta.allure.Description; import io.qameta.allure.Description;
@@ -34,8 +37,11 @@ public class RolloutEventTest extends AbstractRemoteEntityEventTest<Rollout> {
@Override @Override
protected Rollout createEntity() { protected Rollout createEntity() {
testdataFactory.createTarget("12345"); testdataFactory.createTarget("12345");
final DistributionSet ds = distributionSetManagement.create(entityFactory.distributionSet() final SoftwareModule module = softwareModuleManagement.create(
.create().name("incomplete").version("2").description("incomplete").type("os")); entityFactory.softwareModule().create().name("swm").version("2").description("desc").type("os"));
final DistributionSet ds = distributionSetManagement
.create(entityFactory.distributionSet().create().name("complete").version("2").description("complete")
.type("os").modules(Collections.singletonList(module.getId())));
return rolloutManagement.create( return rolloutManagement.create(
entityFactory.rollout().create().name("exampleRollout").targetFilterQuery("controllerId==*").set(ds), entityFactory.rollout().create().name("exampleRollout").targetFilterQuery("controllerId==*").set(ds),

View File

@@ -10,6 +10,7 @@ package org.eclipse.hawkbit.repository.event.remote.entity;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import java.util.Collections;
import java.util.UUID; import java.util.UUID;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
@@ -17,6 +18,7 @@ import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup; import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition; import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition;
import org.eclipse.hawkbit.repository.model.RolloutGroupConditionBuilder; import org.eclipse.hawkbit.repository.model.RolloutGroupConditionBuilder;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import io.qameta.allure.Description; import io.qameta.allure.Description;
@@ -75,9 +77,11 @@ public class RolloutGroupEventTest extends AbstractRemoteEntityEventTest<Rollout
@Override @Override
protected RolloutGroup createEntity() { protected RolloutGroup createEntity() {
testdataFactory.createTarget(UUID.randomUUID().toString()); testdataFactory.createTarget(UUID.randomUUID().toString());
final SoftwareModule module = softwareModuleManagement.create(
final DistributionSet ds = distributionSetManagement.create(entityFactory.distributionSet().create() entityFactory.softwareModule().create().name("swm").version("2").description("desc").type("os"));
.name("incomplete").version("2").description("incomplete").type("os")); final DistributionSet ds = distributionSetManagement
.create(entityFactory.distributionSet().create().name("complete").version("2").description("complete")
.type("os").modules(Collections.singletonList(module.getId())));
final Rollout entity = rolloutManagement.create( final Rollout entity = rolloutManagement.create(
entityFactory.rollout().create().name("exampleRollout").targetFilterQuery("controllerId==*").set(ds), entityFactory.rollout().create().name("exampleRollout").targetFilterQuery("controllerId==*").set(ds),

View File

@@ -0,0 +1,79 @@
/**
* Copyright (c) 2021 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 static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import java.util.Collections;
import org.awaitility.Awaitility;
import org.eclipse.hawkbit.repository.exception.StopRolloutException;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation.CancelationType;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupErrorAction;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupErrorCondition;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition;
import org.eclipse.hawkbit.repository.model.RolloutGroupConditionBuilder;
import org.eclipse.hawkbit.repository.model.RolloutGroupConditions;
import org.junit.jupiter.api.Test;
import org.springframework.test.context.TestPropertySource;
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.qameta.allure.Story;
/**
* Test class testing the invalidation of a {@link DistributionSet} while the
* handle rollouts is ongoing.
*
*/
@Feature("Component Tests - Repository")
@Story("Concurrent Distribution Set invalidation")
@TestPropertySource(properties = "hawkbit.server.repository.dsInvalidationLockTimeout=1")
public class ConcurrentDistributionSetInvalidationTest extends AbstractJpaIntegrationTest {
@Test
@Description("Verify that a large rollout causes a timeout when trying to invalidate a distribution set")
public void verifyInvalidateDistributionSetWithLargeRolloutThrowsException() throws Exception {
final DistributionSet distributionSet = testdataFactory.createDistributionSet();
testdataFactory.createTargets(10000, "verifyInvalidateDistributionSetWithLargeRolloutThrowsException");
final RolloutGroupConditions conditions = new RolloutGroupConditionBuilder().withDefaults()
.successCondition(RolloutGroupSuccessCondition.THRESHOLD, "50")
.errorCondition(RolloutGroupErrorCondition.THRESHOLD, "80")
.errorAction(RolloutGroupErrorAction.PAUSE, null).build();
final Rollout rollout = rolloutManagement.create(entityFactory.rollout().create()
.name("verifyInvalidateDistributionSetWithLargeRolloutThrowsException").description("desc")
.targetFilterQuery("name==*").set(distributionSet).actionType(ActionType.FORCED), 20, conditions);
final String tenant = tenantAware.getCurrentTenant();
// run in new Thread so that the invalidation can be executed in
// parallel
final Thread handleRolloutsThread = new Thread(() -> {
tenantAware.runAsTenant(tenant, () -> systemSecurityContext.runAsSystem(() -> {
rolloutManagement.handleRollouts();
return 0;
}));
});
handleRolloutsThread.start();
// wait until at least one RolloutGroup is created, as this means that
// the thread has started and has acquired the lock
Awaitility.await().until(() -> tenantAware.runAsTenant(tenant, () -> systemSecurityContext
.runAsSystem(() -> rolloutGroupManagement.findByRollout(PAGE, rollout.getId()).getSize() > 0)));
assertThatExceptionOfType(StopRolloutException.class)
.as("Invalidation of distributionSet should throw an exception")
.isThrownBy(() -> distributionSetInvalidationManagement.invalidateDistributionSet(
new DistributionSetInvalidation(Collections.singletonList(distributionSet.getId()),
CancelationType.SOFT, true)));
}
}

View File

@@ -43,6 +43,7 @@ import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.ForceQuitActionNotAllowedException; import org.eclipse.hawkbit.repository.exception.ForceQuitActionNotAllowedException;
import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException; import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException;
import org.eclipse.hawkbit.repository.exception.InvalidDistributionSetException;
import org.eclipse.hawkbit.repository.exception.MultiAssignmentIsNotEnabledException; import org.eclipse.hawkbit.repository.exception.MultiAssignmentIsNotEnabledException;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants; import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction; import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
@@ -844,8 +845,8 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
/** /**
* test a simple deployment by calling the * test a simple deployment by calling the
* {@link TargetRepository#assignDistributionSet(DistributionSet, Iterable)} and * {@link TargetRepository#assignDistributionSet(DistributionSet, Iterable)}
* checking the active action and the action history of the targets. * and checking the active action and the action history of the targets.
* *
*/ */
@Test @Test
@@ -1088,9 +1089,10 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
} }
/** /**
* test the deletion of {@link DistributionSet}s including exception in case of * test the deletion of {@link DistributionSet}s including exception in case
* {@link Target}s are assigned by {@link Target#getAssignedDistributionSet()} * of {@link Target}s are assigned by
* or {@link Target#getInstalledDistributionSet()} * {@link Target#getAssignedDistributionSet()} or
* {@link Target#getInstalledDistributionSet()}
*/ */
@Test @Test
@Description("Deletes distribution set. Expected behaviour is that a soft delete is performed " @Description("Deletes distribution set. Expected behaviour is that a soft delete is performed "
@@ -1357,12 +1359,36 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
assertThat(distributionSetRepository.findAll()).hasSize(1); assertThat(distributionSetRepository.findAll()).hasSize(1);
} }
@Test
@Description("Tests that an exception is thrown when a target is assigned to an incomplete distribution set")
public void verifyAssignTargetsToIncompleteDistribution() {
final DistributionSet distributionSet = testdataFactory.createIncompleteDistributionSet();
final Target target = testdataFactory.createTarget();
assertThatExceptionOfType(IncompleteDistributionSetException.class)
.as("Incomplete distributionSet should throw an exception")
.isThrownBy(() -> assignDistributionSet(distributionSet, target));
}
@Test
@Description("Tests that an exception is thrown when a target is assigned to an invalidated distribution set")
public void verifyAssignTargetsToInvalidDistribution() {
final DistributionSet distributionSet = testdataFactory.createAndInvalidateDistributionSet();
final Target target = testdataFactory.createTarget();
assertThatExceptionOfType(InvalidDistributionSetException.class)
.as("Invalid distributionSet should throw an exception")
.isThrownBy(() -> assignDistributionSet(distributionSet, target));
}
/** /**
* Helper methods that creates 2 lists of targets and a list of distribution * Helper methods that creates 2 lists of targets and a list of distribution
* sets. * sets.
* <p> * <p>
* <b>All created distribution sets are assigned to all targets of the target * <b>All created distribution sets are assigned to all targets of the
* list deployedTargets.</b> * target list deployedTargets.</b>
* *
* @param undeployedTargetPrefix * @param undeployedTargetPrefix
* prefix to be used as target controller prefix * prefix to be used as target controller prefix
@@ -1371,7 +1397,8 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
* @param deployedTargetPrefix * @param deployedTargetPrefix
* prefix to be used as target controller prefix * prefix to be used as target controller prefix
* @param noOfDeployedTargets * @param noOfDeployedTargets
* number of targets to which the created distribution sets assigned * number of targets to which the created distribution sets
* assigned
* @param noOfDistributionSets * @param noOfDistributionSets
* number of distribution sets * number of distribution sets
* @param distributionSetPrefix * @param distributionSetPrefix

View File

@@ -0,0 +1,304 @@
/**
* Copyright (c) 2021 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 static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import java.util.Collections;
import java.util.List;
import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException;
import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException;
import org.eclipse.hawkbit.repository.exception.InvalidDistributionSetException;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation.CancelationType;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidationCount;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.repository.test.util.WithUser;
import org.junit.jupiter.api.Test;
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.qameta.allure.Story;
/**
* Test class testing the functionality of invalidating a
* {@link DistributionSet}
*
*/
@Feature("Component Tests - Repository")
@Story("Distribution set invalidation management")
class DistributionSetInvalidationManagementTest extends AbstractJpaIntegrationTest {
@Test
@Description("Verify invalidation of distribution sets that only removes distribution sets from auto assignments")
void verifyInvalidateDistributionSetStopAutoAssignment() {
final InvalidationTestData invalidationTestData = createInvalidationTestData(
"verifyInvalidateDistributionSetStopAutoAssignment");
final DistributionSetInvalidation distributionSetInvalidation = new DistributionSetInvalidation(
Collections.singletonList(invalidationTestData.getDistributionSet().getId()), CancelationType.NONE,
false);
final DistributionSetInvalidationCount distributionSetInvalidationCount = distributionSetInvalidationManagement
.countEntitiesForInvalidation(distributionSetInvalidation);
assertDistributionSetInvalidationCount(distributionSetInvalidationCount, 1, 0, 0);
distributionSetInvalidationManagement.invalidateDistributionSet(distributionSetInvalidation);
rolloutManagement.handleRollouts();
assertThat(targetFilterQueryManagement.get(invalidationTestData.getTargetFilterQuery().getId()).get()
.getAutoAssignDistributionSet()).isNull();
assertThat(rolloutRepository.findById(invalidationTestData.getRollout().getId()).get().getStatus())
.isNotIn(RolloutStatus.STOPPING, RolloutStatus.FINISHED);
for (final Target target : invalidationTestData.getTargets()) {
// if status is pending, the assignment has not been canceled
assertThat(targetRepository.findById(target.getId()).get().getUpdateStatus())
.isEqualTo(TargetUpdateStatus.PENDING);
assertThat(actionRepository.findByTarget(target).size()).isEqualTo(1);
assertThat(actionRepository.findByTarget(target).get(0).getStatus()).isEqualTo(Status.RUNNING);
}
}
@Test
@Description("Verify invalidation of distribution sets that removes distribution sets from auto assignments and stops rollouts")
void verifyInvalidateDistributionSetStopRollouts() {
final InvalidationTestData invalidationTestData = createInvalidationTestData(
"verifyInvalidateDistributionSetStopRollouts");
final DistributionSetInvalidation distributionSetInvalidation = new DistributionSetInvalidation(
Collections.singletonList(invalidationTestData.getDistributionSet().getId()), CancelationType.NONE,
true);
final DistributionSetInvalidationCount distributionSetInvalidationCount = distributionSetInvalidationManagement
.countEntitiesForInvalidation(distributionSetInvalidation);
assertDistributionSetInvalidationCount(distributionSetInvalidationCount, 1, 0, 1);
distributionSetInvalidationManagement.invalidateDistributionSet(distributionSetInvalidation);
rolloutManagement.handleRollouts();
assertThat(targetFilterQueryManagement.get(invalidationTestData.getTargetFilterQuery().getId()).get()
.getAutoAssignDistributionSet()).isNull();
assertThat(rolloutRepository.findById(invalidationTestData.getRollout().getId()).get().getStatus())
.isEqualTo(RolloutStatus.FINISHED);
assertNoScheduledActionsExist(invalidationTestData.getRollout());
assertRolloutGroupsAreFinished(invalidationTestData.getRollout());
for (final Target target : invalidationTestData.getTargets()) {
// if status is pending, the assignment has not been canceled
assertThat(
targetRepository.findById(invalidationTestData.getTargets().get(0).getId()).get().getUpdateStatus())
.isEqualTo(TargetUpdateStatus.PENDING);
assertThat(actionRepository.findByTarget(target).size()).isEqualTo(1);
assertThat(actionRepository.findByTarget(target).get(0).getStatus()).isEqualTo(Status.RUNNING);
}
}
@Test
@Description("Verify invalidation of distribution sets that removes distribution sets from auto assignments, stops rollouts and force cancels assignments")
void verifyInvalidateDistributionSetStopAllAndForceCancel() {
final InvalidationTestData invalidationTestData = createInvalidationTestData(
"verifyInvalidateDistributionSetStopAllAndForceCancel");
final DistributionSetInvalidation distributionSetInvalidation = new DistributionSetInvalidation(
Collections.singletonList(invalidationTestData.getDistributionSet().getId()), CancelationType.FORCE,
true);
final DistributionSetInvalidationCount distributionSetInvalidationCount = distributionSetInvalidationManagement
.countEntitiesForInvalidation(distributionSetInvalidation);
assertDistributionSetInvalidationCount(distributionSetInvalidationCount, 1, 5, 1);
distributionSetInvalidationManagement.invalidateDistributionSet(distributionSetInvalidation);
rolloutManagement.handleRollouts();
assertThat(targetFilterQueryManagement.get(invalidationTestData.getTargetFilterQuery().getId()).get()
.getAutoAssignDistributionSet()).isNull();
assertThat(rolloutRepository.findById(invalidationTestData.getRollout().getId()).get().getStatus())
.isEqualTo(RolloutStatus.FINISHED);
assertNoScheduledActionsExist(invalidationTestData.getRollout());
assertRolloutGroupsAreFinished(invalidationTestData.getRollout());
for (final Target target : invalidationTestData.getTargets()) {
assertThat(targetRepository.findById(target.getId()).get().getUpdateStatus())
.isEqualTo(TargetUpdateStatus.IN_SYNC);
assertThat(actionRepository.findByTarget(target).size()).isEqualTo(1);
assertThat(actionRepository.findByTarget(target).get(0).getStatus()).isEqualTo(Status.CANCELED);
}
}
private void assertNoScheduledActionsExist(final Rollout rollout) {
assertThat(
actionRepository.findByRolloutIdAndStatus(PAGE, rollout.getId(), Status.SCHEDULED).getTotalElements())
.isZero();
}
private void assertRolloutGroupsAreFinished(final Rollout rollout) {
assertThat(rolloutGroupRepository.findByRolloutId(rollout.getId(), PAGE))
.allMatch(rolloutGroup -> rolloutGroup.getStatus().equals(RolloutGroupStatus.FINISHED));
}
@Test
@Description("Verify invalidation of distribution sets that removes distribution sets from auto assignments, stops rollouts and cancels assignments")
void verifyInvalidateDistributionSetStopAll() {
final InvalidationTestData invalidationTestData = createInvalidationTestData(
"verifyInvalidateDistributionSetStopAll");
final DistributionSetInvalidation distributionSetInvalidation = new DistributionSetInvalidation(
Collections.singletonList(invalidationTestData.getDistributionSet().getId()), CancelationType.SOFT,
true);
final DistributionSetInvalidationCount distributionSetInvalidationCount = distributionSetInvalidationManagement
.countEntitiesForInvalidation(distributionSetInvalidation);
assertDistributionSetInvalidationCount(distributionSetInvalidationCount, 1, 5, 1);
distributionSetInvalidationManagement.invalidateDistributionSet(distributionSetInvalidation);
assertThat(targetFilterQueryManagement.get(invalidationTestData.getTargetFilterQuery().getId()).get()
.getAutoAssignDistributionSet()).isNull();
assertThat(rolloutRepository.findById(invalidationTestData.getRollout().getId()).get().getStatus())
.isIn(RolloutStatus.STOPPING, RolloutStatus.FINISHED);
for (final Target target : invalidationTestData.getTargets()) {
assertThat(targetRepository.findById(target.getId()).get().getUpdateStatus())
.isEqualTo(TargetUpdateStatus.PENDING);
assertThat(actionRepository.findByTarget(target).size()).isEqualTo(1);
assertThat(actionRepository.findByTarget(target).get(0).getStatus()).isEqualTo(Status.CANCELING);
}
}
@Test
@Description("Verify that invalidating an incomplete distribution set throws an exception")
void verifyInvalidateIncompleteDistributionSetThrowsException() {
final DistributionSet distributionSet = testdataFactory.createIncompleteDistributionSet();
assertThatExceptionOfType(IncompleteDistributionSetException.class)
.as("Incomplete distributionSet should throw an exception")
.isThrownBy(() -> distributionSetInvalidationManagement.invalidateDistributionSet(
new DistributionSetInvalidation(Collections.singletonList(distributionSet.getId()),
CancelationType.SOFT, true)));
}
@Test
@Description("Verify that invalidating an invalidated distribution set throws an exception")
void verifyInvalidateInvalidatedDistributionSetThrowsException() {
final DistributionSet distributionSet = testdataFactory.createAndInvalidateDistributionSet();
assertThatExceptionOfType(InvalidDistributionSetException.class)
.as("Invalid distributionSet should throw an exception")
.isThrownBy(() -> distributionSetInvalidationManagement.invalidateDistributionSet(
new DistributionSetInvalidation(Collections.singletonList(distributionSet.getId()),
CancelationType.SOFT, true)));
}
@Test
@Description("Verify that a user that has authority READ_REPOSITORY and UPDATE_REPOSITORY is not allowed to invalidate a distribution set")
@WithUser(authorities = { "READ_REPOSITORY", "UPDATE_REPOSITORY" })
void verifyInvalidateWithReadAndUpdateRepoAuthority() {
final InvalidationTestData invalidationTestData = systemSecurityContext
.runAsSystem(() -> createInvalidationTestData("verifyInvalidateWithUpdateRepoAuthority"));
assertThatExceptionOfType(InsufficientPermissionException.class)
.as("Insufficient permission exception expected")
.isThrownBy(() -> distributionSetInvalidationManagement
.invalidateDistributionSet(new DistributionSetInvalidation(
Collections.singletonList(invalidationTestData.getDistributionSet().getId()),
CancelationType.NONE, false)));
}
@Test
@Description("Verify that a user that has authority READ_REPOSITORY, UPDATE_REPOSITORY and UPDATE_TARGET is allowed to invalidate a distribution set only without canceling rollouts")
@WithUser(authorities = { "READ_REPOSITORY", "UPDATE_REPOSITORY", "UPDATE_TARGET" })
void verifyInvalidateWithReadAndUpdateRepoAndUpdateTargetAuthority() {
final InvalidationTestData invalidationTestData = systemSecurityContext.runAsSystem(
() -> createInvalidationTestData("verifyInvalidateWithUpdateRepoAndUpdateTargetAuthority"));
assertThatExceptionOfType(InsufficientPermissionException.class)
.as("Insufficient permission exception expected")
.isThrownBy(() -> distributionSetInvalidationManagement
.invalidateDistributionSet(new DistributionSetInvalidation(
Collections.singletonList(invalidationTestData.getDistributionSet().getId()),
CancelationType.SOFT, true)));
distributionSetInvalidationManagement.invalidateDistributionSet(new DistributionSetInvalidation(
Collections.singletonList(invalidationTestData.getDistributionSet().getId()), CancelationType.NONE,
false));
assertThat(
distributionSetRepository.findById(invalidationTestData.getDistributionSet().getId()).get().isValid())
.isFalse();
}
@Test
@Description("Verify that a user that has authority READ_REPOSITORY, UPDATE_REPOSITORY, UPDATE_ROLLOUT and UPDATE_TARGET is allowed to invalidate a distribution")
@WithUser(authorities = { "READ_REPOSITORY", "UPDATE_REPOSITORY", "UPDATE_TARGET", "UPDATE_ROLLOUT" })
void verifyInvalidateWithReadAndUpdateRepoAndUpdateTargetAndUpdateRolloutAuthority() {
final InvalidationTestData invalidationTestData = systemSecurityContext.runAsSystem(
() -> createInvalidationTestData("verifyInvalidateWithUpdateRepoAndUpdateTargetAuthority"));
distributionSetInvalidationManagement.invalidateDistributionSet(new DistributionSetInvalidation(
Collections.singletonList(invalidationTestData.getDistributionSet().getId()), CancelationType.SOFT,
true));
assertThat(
distributionSetRepository.findById(invalidationTestData.getDistributionSet().getId()).get().isValid())
.isFalse();
}
private InvalidationTestData createInvalidationTestData(final String testName) {
final DistributionSet distributionSet = testdataFactory.createDistributionSet();
final List<Target> targets = testdataFactory.createTargets(5, testName);
assignDistributionSet(distributionSet, targets);
final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement.create(entityFactory.targetFilterQuery()
.create().name(testName).query("name==*").autoAssignDistributionSet(distributionSet));
final Rollout rollout = testdataFactory.createRolloutByVariables(testName, "desc", 2, "name==*",
distributionSet, "50", "80");
return new InvalidationTestData(distributionSet, targets, targetFilterQuery, rollout);
}
private static class InvalidationTestData {
private final DistributionSet distributionSet;
private final List<Target> targets;
private final TargetFilterQuery targetFilterQuery;
private final Rollout rollout;
public InvalidationTestData(final DistributionSet distributionSet, final List<Target> targets,
final TargetFilterQuery targetFilterQuery, final Rollout rollout) {
super();
this.distributionSet = distributionSet;
this.targets = targets;
this.targetFilterQuery = targetFilterQuery;
this.rollout = rollout;
}
public DistributionSet getDistributionSet() {
return distributionSet;
}
public List<Target> getTargets() {
return targets;
}
public TargetFilterQuery getTargetFilterQuery() {
return targetFilterQuery;
}
public Rollout getRollout() {
return rollout;
}
}
private void assertDistributionSetInvalidationCount(
final DistributionSetInvalidationCount distributionSetInvalidationCount,
final long expectedAutoAssignmentCount, final long expectedActionCount, final long expectedRolloutCount) {
assertThat(distributionSetInvalidationCount.getAutoAssignmentCount()).isEqualTo(expectedAutoAssignmentCount);
assertThat(distributionSetInvalidationCount.getActionCount()).isEqualTo(expectedActionCount);
assertThat(distributionSetInvalidationCount.getRolloutsCount()).isEqualTo(expectedRolloutCount);
}
}

View File

@@ -31,16 +31,20 @@ import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetCreated
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetTagCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetTagCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetUpdatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetUpdatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException; import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException; import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException;
import org.eclipse.hawkbit.repository.exception.InvalidDistributionSetException;
import org.eclipse.hawkbit.repository.exception.UnsupportedSoftwareModuleForThisDistributionSetException; import org.eclipse.hawkbit.repository.exception.UnsupportedSoftwareModuleForThisDistributionSetException;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetMetadata; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetMetadata;
import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetFilter.DistributionSetFilterBuilder; import org.eclipse.hawkbit.repository.model.DistributionSetFilter.DistributionSetFilterBuilder;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation.CancelationType;
import org.eclipse.hawkbit.repository.model.DistributionSetMetadata; import org.eclipse.hawkbit.repository.model.DistributionSetMetadata;
import org.eclipse.hawkbit.repository.model.DistributionSetTag; import org.eclipse.hawkbit.repository.model.DistributionSetTag;
import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.DistributionSetType;
@@ -174,6 +178,13 @@ public class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
verifyThrownExceptionBy(() -> distributionSetManagement.updateMetaData(set.getId(), verifyThrownExceptionBy(() -> distributionSetManagement.updateMetaData(set.getId(),
entityFactory.generateDsMetadata(NOT_EXIST_ID, "xxx")), "DistributionSetMetadata"); entityFactory.generateDsMetadata(NOT_EXIST_ID, "xxx")), "DistributionSetMetadata");
verifyThrownExceptionBy(() -> distributionSetManagement.getOrElseThrowException(NOT_EXIST_IDL),
"DistributionSet");
verifyThrownExceptionBy(() -> distributionSetManagement.getValidAndComplete(NOT_EXIST_IDL), "DistributionSet");
verifyThrownExceptionBy(() -> distributionSetManagement.getValid(NOT_EXIST_IDL), "DistributionSet");
} }
@Test @Test
@@ -493,6 +504,16 @@ public class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
assertThat(ds.isRequiredMigrationStep()).isTrue(); assertThat(ds.isRequiredMigrationStep()).isTrue();
} }
@Test
@Description("Verifies that an exception is thrown when trying to update an invalid distribution set")
public void updateInvalidDistributionSet() {
final DistributionSet distributionSet = testdataFactory.createAndInvalidateDistributionSet();
assertThatExceptionOfType(InvalidDistributionSetException.class)
.as("Invalid distributionSet should throw an exception").isThrownBy(() -> distributionSetManagement
.update(entityFactory.distributionSet().update(distributionSet.getId()).name("new_name")));
}
@Test @Test
@Description("Verifies the enforcement of the software module quota per distribution set.") @Description("Verifies the enforcement of the software module quota per distribution set.")
public void assignSoftwareModulesUntilQuotaIsExceeded() { public void assignSoftwareModulesUntilQuotaIsExceeded() {
@@ -533,6 +554,33 @@ public class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
} }
@Test
@Description("Verifies that an exception is thrown when trying to assign software modules to an invalidated distribution set.")
public void verifyAssignSoftwareModulesToInvalidDistributionSet() {
final DistributionSet distributionSet = testdataFactory.createAndInvalidateDistributionSet();
final SoftwareModule softwareModule = testdataFactory.createSoftwareModuleOs();
assertThatExceptionOfType(InvalidDistributionSetException.class)
.as("Invalid distributionSet should throw an exception")
.isThrownBy(() -> distributionSetManagement.assignSoftwareModules(distributionSet.getId(),
Collections.singletonList(softwareModule.getId())));
}
@Test
@Description("Verifies that an exception is thrown when trying to unassign a software module from an invalidated distribution set.")
public void verifyUnassignSoftwareModulesToInvalidDistributionSet() {
final DistributionSet distributionSet = testdataFactory.createDistributionSet();
final SoftwareModule softwareModule = testdataFactory.createSoftwareModuleOs();
distributionSetManagement.assignSoftwareModules(distributionSet.getId(),
Collections.singletonList(softwareModule.getId()));
distributionSetInvalidationManagement.invalidateDistributionSet(new DistributionSetInvalidation(
Collections.singletonList(distributionSet.getId()), CancelationType.NONE, false));
assertThatExceptionOfType(InvalidDistributionSetException.class)
.as("Invalid distributionSet should throw an exception").isThrownBy(() -> distributionSetManagement
.unassignSoftwareModule(distributionSet.getId(), softwareModule.getId()));
}
@Test @Test
@WithUser(allSpPermissions = true) @WithUser(allSpPermissions = true)
@Description("Checks that metadata for a distribution set can be updated.") @Description("Checks that metadata for a distribution set can be updated.")
@@ -937,6 +985,24 @@ public class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
assertThat(distributionSetManagement.findByCompleted(PAGE, true).getTotalElements()).isEqualTo(1); assertThat(distributionSetManagement.findByCompleted(PAGE, true).getTotalElements()).isEqualTo(1);
} }
@Test
@Description("Deletes an invalid distribution set")
public void deleteInvalidDistributionSet() {
final DistributionSet set = testdataFactory.createAndInvalidateDistributionSet();
assertThat(distributionSetRepository.findById(set.getId())).isNotEmpty();
distributionSetManagement.delete(set.getId());
assertThat(distributionSetRepository.findById(set.getId())).isEmpty();
}
@Test
@Description("Deletes an incomplete distribution set")
public void deleteIncompleteDistributionSet() {
final DistributionSet set = testdataFactory.createIncompleteDistributionSet();
assertThat(distributionSetRepository.findById(set.getId())).isNotEmpty();
distributionSetManagement.delete(set.getId());
assertThat(distributionSetRepository.findById(set.getId())).isEmpty();
}
@Test @Test
@Description("Queries and loads the metadata related to a given software module.") @Description("Queries and loads the metadata related to a given software module.")
public void findAllDistributionSetMetadataByDsId() { public void findAllDistributionSetMetadataByDsId() {
@@ -1013,4 +1079,54 @@ public class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
assertThat(collect).containsAll(searchIds); assertThat(collect).containsAll(searchIds);
} }
@Test
@Description("Verify that an exception is thrown when trying to get an invalid distribution set")
public void verifyGetValid() {
final DistributionSet distributionSet = testdataFactory.createAndInvalidateDistributionSet();
assertThatExceptionOfType(InvalidDistributionSetException.class)
.as("Invalid distributionSet should throw an exception")
.isThrownBy(() -> distributionSetManagement.getValid(distributionSet.getId()));
assertThatExceptionOfType(InvalidDistributionSetException.class)
.as("Invalid distributionSet should throw an exception")
.isThrownBy(() -> distributionSetManagement.getValidAndComplete(distributionSet.getId()));
}
@Test
@Description("Verify that an exception is thrown when trying to get an incomplete distribution set")
public void verifyGetValidAndComplete() {
final DistributionSet distributionSet = testdataFactory.createIncompleteDistributionSet();
assertThatExceptionOfType(IncompleteDistributionSetException.class)
.as("Incomplete distributionSet should throw an exception")
.isThrownBy(() -> distributionSetManagement.getValidAndComplete(distributionSet.getId()));
}
@Test
@Description("Verify that an exception is thrown when trying to create or update metadata for an invalid distribution set.")
public void createMetadataForInvalidDistributionSet() {
final String knownKey1 = "myKnownKey1";
final String knownKey2 = "myKnownKey2";
final String knownValue = "myKnownValue";
final String knownUpdateValue = "knownUpdateValue";
final DistributionSet ds = testdataFactory.createDistributionSet();
distributionSetManagement.createMetaData(ds.getId(),
Collections.singletonList(entityFactory.generateDsMetadata(knownKey1, knownValue)));
distributionSetInvalidationManagement.invalidateDistributionSet(
new DistributionSetInvalidation(Arrays.asList(ds.getId()), CancelationType.NONE, false));
// assert that no new metadata can be created
assertThatExceptionOfType(InvalidDistributionSetException.class)
.as("Invalid distributionSet should throw an exception")
.isThrownBy(() -> distributionSetManagement.createMetaData(ds.getId(),
Collections.singletonList(entityFactory.generateDsMetadata(knownKey2, knownValue))));
// assert that an existing metadata can not be updated
assertThatExceptionOfType(InvalidDistributionSetException.class)
.as("Invalid distributionSet should throw an exception").isThrownBy(() -> distributionSetManagement
.updateMetaData(ds.getId(), entityFactory.generateDsMetadata(knownKey1, knownUpdateValue)));
}
} }

View File

@@ -46,6 +46,8 @@ import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException; import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException; import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException;
import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException;
import org.eclipse.hawkbit.repository.exception.InvalidDistributionSetException;
import org.eclipse.hawkbit.repository.exception.MultiAssignmentIsNotEnabledException; import org.eclipse.hawkbit.repository.exception.MultiAssignmentIsNotEnabledException;
import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException; import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction; import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
@@ -72,7 +74,6 @@ import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus;
import org.eclipse.hawkbit.repository.test.matcher.Expect; import org.eclipse.hawkbit.repository.test.matcher.Expect;
import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents; import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey; import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
@@ -243,8 +244,9 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
assertThat(firstGroup.getStatus()).isEqualTo(RolloutGroupStatus.RUNNING); assertThat(firstGroup.getStatus()).isEqualTo(RolloutGroupStatus.RUNNING);
// verify other groups are scheduled // verify other groups are scheduled
final List<RolloutGroup> scheduledGroups = rolloutGroupManagement.findByRollout( final List<RolloutGroup> scheduledGroups = rolloutGroupManagement
new OffsetBasedPageRequest(1, 100, Sort.by(Direction.ASC, "id")), createdRollout.getId()).getContent(); .findByRollout(new OffsetBasedPageRequest(1, 100, Sort.by(Direction.ASC, "id")), createdRollout.getId())
.getContent();
scheduledGroups.forEach(group -> assertThat(group.getStatus()).isEqualTo(RolloutGroupStatus.SCHEDULED) scheduledGroups.forEach(group -> assertThat(group.getStatus()).isEqualTo(RolloutGroupStatus.SCHEDULED)
.as("group which should be in scheduled state is in " + group.getStatus() + " state")); .as("group which should be in scheduled state is in " + group.getStatus() + " state"));
// verify that the first group actions has been started and are in state // verify that the first group actions has been started and are in state
@@ -436,8 +438,9 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
assertThat(errorGroup.get(0).getStatus()).isEqualTo(RolloutGroupStatus.ERROR); assertThat(errorGroup.get(0).getStatus()).isEqualTo(RolloutGroupStatus.ERROR);
// all other groups should still be in scheduled state // all other groups should still be in scheduled state
final List<RolloutGroup> scheduleGroups = rolloutGroupManagement.findByRollout( final List<RolloutGroup> scheduleGroups = rolloutGroupManagement
new OffsetBasedPageRequest(1, 100, Sort.by(Direction.ASC, "id")), createdRollout.getId()).getContent(); .findByRollout(new OffsetBasedPageRequest(1, 100, Sort.by(Direction.ASC, "id")), createdRollout.getId())
.getContent();
scheduleGroups.forEach(group -> assertThat(group.getStatus()).isEqualTo(RolloutGroupStatus.SCHEDULED)); scheduleGroups.forEach(group -> assertThat(group.getStatus()).isEqualTo(RolloutGroupStatus.SCHEDULED));
} }
@@ -470,8 +473,9 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
assertThat(rollout.getStatus()).isEqualTo(RolloutStatus.PAUSED); assertThat(rollout.getStatus()).isEqualTo(RolloutStatus.PAUSED);
// all other groups should still be in scheduled state // all other groups should still be in scheduled state
final List<RolloutGroup> scheduleGroups = rolloutGroupManagement.findByRollout( final List<RolloutGroup> scheduleGroups = rolloutGroupManagement
new OffsetBasedPageRequest(1, 100, Sort.by(Direction.ASC, "id")), createdRollout.getId()).getContent(); .findByRollout(new OffsetBasedPageRequest(1, 100, Sort.by(Direction.ASC, "id")), createdRollout.getId())
.getContent();
scheduleGroups.forEach(group -> assertThat(group.getStatus()).isEqualTo(RolloutGroupStatus.SCHEDULED)); scheduleGroups.forEach(group -> assertThat(group.getStatus()).isEqualTo(RolloutGroupStatus.SCHEDULED));
// resume the rollout again after it gets paused by error action // resume the rollout again after it gets paused by error action
@@ -521,8 +525,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
// verify all groups are in finished state // verify all groups are in finished state
rolloutGroupManagement rolloutGroupManagement
.findByRollout(new OffsetBasedPageRequest(0, 100, Sort.by(Direction.ASC, "id")), .findByRollout(new OffsetBasedPageRequest(0, 100, Sort.by(Direction.ASC, "id")), createdRollout.getId())
createdRollout.getId())
.forEach(group -> assertThat(group.getStatus()).isEqualTo(RolloutGroupStatus.FINISHED)); .forEach(group -> assertThat(group.getStatus()).isEqualTo(RolloutGroupStatus.FINISHED));
// verify that rollout itself is in finished state // verify that rollout itself is in finished state
@@ -1823,6 +1826,56 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
assertThat(actions).allMatch(action -> !action.getWeight().isPresent()); assertThat(actions).allMatch(action -> !action.getWeight().isPresent());
} }
@Test
@Description("Verifies that an exception is thrown when trying to create a rollout with an invalidated distribution set.")
public void createRolloutWithInvalidDistributionSet() {
final DistributionSet distributionSet = testdataFactory.createAndInvalidateDistributionSet();
assertThatExceptionOfType(InvalidDistributionSetException.class)
.as("Invalid distributionSet should throw an exception")
.isThrownBy(() -> testdataFactory.createRolloutByVariables("createRolloutWithInvalidDistributionSet",
"desc", 2, "name==*", distributionSet, "50", "80"));
}
@Test
@Description("Verifies that an exception is thrown when trying to create a rollout with an incomplete distribution set.")
public void createRolloutWithIncompleteDistributionSet() {
final DistributionSet distributionSet = testdataFactory.createIncompleteDistributionSet();
assertThatExceptionOfType(IncompleteDistributionSetException.class)
.as("Incomplete distributionSet should throw an exception")
.isThrownBy(() -> testdataFactory.createRolloutByVariables("createRolloutWithIncompleteDistributionSet",
"desc", 2, "name==*", distributionSet, "50", "80"));
}
@Test
@Description("Verifies that an exception is thrown when trying to update a rollout with an invalidated distribution set.")
public void updateRolloutWithInvalidDistributionSet() {
final DistributionSet distributionSet = testdataFactory.createDistributionSet();
testdataFactory.createTarget();
final Rollout rollout = testdataFactory.createRolloutByVariables("updateRolloutWithInvalidDistributionSet",
"desc", 2, "name==*", distributionSet, "50", "80");
final DistributionSet invalidDistributionSet = testdataFactory.createAndInvalidateDistributionSet();
assertThatExceptionOfType(InvalidDistributionSetException.class)
.as("Invalid distributionSet should throw an exception").isThrownBy(() -> rolloutManagement
.update(entityFactory.rollout().update(rollout.getId()).set(invalidDistributionSet.getId())));
}
@Test
@Description("Verifies that an exception is thrown when trying to update a rollout with an incomplete distribution set.")
public void updateRolloutWithIncompleteDistributionSet() {
final DistributionSet distributionSet = testdataFactory.createDistributionSet();
testdataFactory.createTarget();
final Rollout rollout = testdataFactory.createRolloutByVariables("updateRolloutWithIncompleteDistributionSet",
"desc", 2, "name==*", distributionSet, "50", "80");
final DistributionSet incompleteDistributionSet = testdataFactory.createIncompleteDistributionSet();
assertThatExceptionOfType(IncompleteDistributionSetException.class)
.as("Incomplete distributionSet should throw an exception").isThrownBy(() -> rolloutManagement.update(
entityFactory.rollout().update(rollout.getId()).set(incompleteDistributionSet.getId())));
}
private RolloutGroupCreate generateRolloutGroup(final int index, final Integer percentage, private RolloutGroupCreate generateRolloutGroup(final int index, final Integer percentage,
final String targetFilter) { final String targetFilter) {
return entityFactory.rolloutGroup().create().name("Group" + index).description("Group" + index + "desc") return entityFactory.rolloutGroup().create().name("Group" + index).description("Group" + index + "desc")

View File

@@ -23,14 +23,17 @@ import javax.validation.ConstraintViolationException;
import org.assertj.core.api.Assertions; import org.assertj.core.api.Assertions;
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement; import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
import org.eclipse.hawkbit.repository.builder.AutoAssignDistributionSetUpdate;
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.TargetFilterQueryCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TargetFilterQueryCreatedEvent;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException; import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException;
import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignActionTypeException; import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignActionTypeException;
import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignDistributionSetException; import org.eclipse.hawkbit.repository.exception.InvalidDistributionSetException;
import org.eclipse.hawkbit.repository.exception.MultiAssignmentIsNotEnabledException; import org.eclipse.hawkbit.repository.exception.MultiAssignmentIsNotEnabledException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException; import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action;
@@ -51,7 +54,7 @@ import io.qameta.allure.Story;
/** /**
* Test class for {@link TargetFilterQueryManagement}. * Test class for {@link TargetFilterQueryManagement}.
* *
*/ */
@Feature("Component Tests - Repository") @Feature("Component Tests - Repository")
@Story("Target Filter Query Management") @Story("Target Filter Query Management")
@@ -252,7 +255,7 @@ public class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest
.create(entityFactory.distributionSet().create().name("incomplete").version("1") .create(entityFactory.distributionSet().create().name("incomplete").version("1")
.type(testdataFactory.findOrCreateDefaultTestDsType())); .type(testdataFactory.findOrCreateDefaultTestDsType()));
assertThatExceptionOfType(InvalidAutoAssignDistributionSetException.class) assertThatExceptionOfType(IncompleteDistributionSetException.class)
.isThrownBy(() -> targetFilterQueryManagement.updateAutoAssignDS(entityFactory.targetFilterQuery() .isThrownBy(() -> targetFilterQueryManagement.updateAutoAssignDS(entityFactory.targetFilterQuery()
.updateAutoAssign(targetFilterQuery.getId()).ds(incompleteDistributionSet.getId()))); .updateAutoAssign(targetFilterQuery.getId()).ds(incompleteDistributionSet.getId())));
} }
@@ -263,7 +266,7 @@ public class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest
assignDistributionSet(softDeletedDs, testdataFactory.createTarget("forSoftDeletedDs")); assignDistributionSet(softDeletedDs, testdataFactory.createTarget("forSoftDeletedDs"));
distributionSetManagement.delete(softDeletedDs.getId()); distributionSetManagement.delete(softDeletedDs.getId());
assertThatExceptionOfType(InvalidAutoAssignDistributionSetException.class) assertThatExceptionOfType(EntityNotFoundException.class)
.isThrownBy(() -> targetFilterQueryManagement.updateAutoAssignDS(entityFactory.targetFilterQuery() .isThrownBy(() -> targetFilterQueryManagement.updateAutoAssignDS(entityFactory.targetFilterQuery()
.updateAutoAssign(targetFilterQuery.getId()).ds(softDeletedDs.getId()))); .updateAutoAssign(targetFilterQuery.getId()).ds(softDeletedDs.getId())));
} }
@@ -502,4 +505,60 @@ public class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest
assertThat(targetFilterQueryManagement.get(filterId).get().getAutoAssignWeight().get()) assertThat(targetFilterQueryManagement.get(filterId).get().getAutoAssignWeight().get())
.isEqualTo(Action.WEIGHT_MIN); .isEqualTo(Action.WEIGHT_MIN);
} }
@Test
@Description("Verifies that an exception is thrown when trying to create a target filter with an invalidated distribution set.")
public void createTargetFilterWithInvalidDistributionSet() {
final DistributionSet distributionSet = testdataFactory.createAndInvalidateDistributionSet();
assertThatExceptionOfType(InvalidDistributionSetException.class)
.as("Invalid distributionSet should throw an exception")
.isThrownBy(() -> targetFilterQueryManagement.create(
entityFactory.targetFilterQuery().create().name("createTargetFilterWithInvalidDistributionSet")
.query("name==*").autoAssignDistributionSet(distributionSet)));
}
@Test
@Description("Verifies that an exception is thrown when trying to create a target filter with an incomplete distribution set.")
public void createTargetFilterWithIncompleteDistributionSet() {
final DistributionSet distributionSet = testdataFactory.createIncompleteDistributionSet();
assertThatExceptionOfType(IncompleteDistributionSetException.class)
.as("Incomplete distributionSet should throw an exception")
.isThrownBy(() -> targetFilterQueryManagement.create(entityFactory.targetFilterQuery().create()
.name("createTargetFilterWithIncompleteDistributionSet").query("name==*")
.autoAssignDistributionSet(distributionSet)));
}
@Test
@Description("Verifies that an exception is thrown when trying to update a target filter with an invalidated distribution set.")
public void updateAutoAssignDsWithInvalidDistributionSet() {
final DistributionSet distributionSet = testdataFactory.createDistributionSet();
final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement
.create(entityFactory.targetFilterQuery().create().name("updateAutoAssignDsWithInvalidDistributionSet")
.query("name==*").autoAssignDistributionSet(distributionSet));
final DistributionSet invalidDistributionSet = testdataFactory.createAndInvalidateDistributionSet();
assertThatExceptionOfType(InvalidDistributionSetException.class)
.as("Invalid distributionSet should throw an exception")
.isThrownBy(() -> targetFilterQueryManagement
.updateAutoAssignDS(new AutoAssignDistributionSetUpdate(targetFilterQuery.getId())
.ds(invalidDistributionSet.getId())));
}
@Test
@Description("Verifies that an exception is thrown when trying to update a target filter with an incomplete distribution set.")
public void updateAutoAssignDsWithIncompleteDistributionSet() {
final DistributionSet distributionSet = testdataFactory.createDistributionSet();
final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement.create(
entityFactory.targetFilterQuery().create().name("updateAutoAssignDsWithIncompleteDistributionSet")
.query("name==*").autoAssignDistributionSet(distributionSet));
final DistributionSet incompleteDistributionSet = testdataFactory.createIncompleteDistributionSet();
assertThatExceptionOfType(IncompleteDistributionSetException.class)
.as("Incomplete distributionSet should throw an exception")
.isThrownBy(() -> targetFilterQueryManagement
.updateAutoAssignDS(new AutoAssignDistributionSetUpdate(targetFilterQuery.getId())
.ds(incompleteDistributionSet.getId())));
}
} }

View File

@@ -15,7 +15,7 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignDistributionSetException; import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException;
import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest;
import org.eclipse.hawkbit.repository.jpa.ActionRepository; import org.eclipse.hawkbit.repository.jpa.ActionRepository;
import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action;
@@ -105,11 +105,11 @@ public class AutoAssignCheckerTest extends AbstractJpaIntegrationTest {
final DistributionSet setB = testdataFactory.createDistributionSet("dsB"); final DistributionSet setB = testdataFactory.createDistributionSet("dsB");
// target filter query that matches all targets // target filter query that matches all targets
final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement.updateAutoAssignDS( final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement.updateAutoAssignDS(entityFactory
entityFactory.targetFilterQuery() .targetFilterQuery()
.updateAutoAssign(targetFilterQueryManagement.create( .updateAutoAssign(targetFilterQueryManagement
entityFactory.targetFilterQuery().create().name("filterA").query("name==*")).getId()) .create(entityFactory.targetFilterQuery().create().name("filterA").query("name==*")).getId())
.ds(setA.getId())); .ds(setA.getId()));
final String targetDsAIdPref = "targ"; final String targetDsAIdPref = "targ";
final List<Target> targets = testdataFactory.createTargets(100, targetDsAIdPref, final List<Target> targets = testdataFactory.createTargets(100, targetDsAIdPref,
@@ -159,7 +159,7 @@ public class AutoAssignCheckerTest extends AbstractJpaIntegrationTest {
// target filter query that matches first bunch of targets, that should // target filter query that matches first bunch of targets, that should
// fail // fail
assertThatExceptionOfType(InvalidAutoAssignDistributionSetException.class).isThrownBy(() -> { assertThatExceptionOfType(IncompleteDistributionSetException.class).isThrownBy(() -> {
final Long filterId = targetFilterQueryManagement.create( final Long filterId = targetFilterQueryManagement.create(
entityFactory.targetFilterQuery().create().name("filterA").query("id==" + targetDsFIdPref + "*")) entityFactory.targetFilterQuery().create().name("filterA").query("id==" + targetDsFIdPref + "*"))
.getId(); .getId();
@@ -221,10 +221,10 @@ public class AutoAssignCheckerTest extends AbstractJpaIntegrationTest {
final DistributionSet distributionSet, final List<Target> targets) { final DistributionSet distributionSet, final List<Target> targets) {
final Set<String> targetIds = targets.stream().map(Target::getControllerId).collect(Collectors.toSet()); final Set<String> targetIds = targets.stream().map(Target::getControllerId).collect(Collectors.toSet());
actionRepository.findByDistributionSetId(Pageable.unpaged(), distributionSet.getId()) actionRepository.findByDistributionSetId(Pageable.unpaged(), distributionSet.getId()).stream()
.stream().filter(a -> targetIds.contains(a.getTarget().getControllerId())) .filter(a -> targetIds.contains(a.getTarget().getControllerId()))
.forEach(a -> assertThat(a.getInitiatedBy()).as( .forEach(a -> assertThat(a.getInitiatedBy())
"Action should be initiated by the user who initiated the auto assignment") .as("Action should be initiated by the user who initiated the auto assignment")
.isEqualTo(targetFilterQuery.getAutoAssignInitiatedBy())); .isEqualTo(targetFilterQuery.getAutoAssignInitiatedBy()));
} }

View File

@@ -31,6 +31,7 @@ import org.eclipse.hawkbit.cache.TenantAwareCacheManager;
import org.eclipse.hawkbit.repository.ArtifactManagement; import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.ControllerManagement; import org.eclipse.hawkbit.repository.ControllerManagement;
import org.eclipse.hawkbit.repository.DeploymentManagement; import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.DistributionSetInvalidationManagement;
import org.eclipse.hawkbit.repository.DistributionSetManagement; import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.DistributionSetTagManagement; import org.eclipse.hawkbit.repository.DistributionSetTagManagement;
import org.eclipse.hawkbit.repository.DistributionSetTypeManagement; import org.eclipse.hawkbit.repository.DistributionSetTypeManagement;
@@ -159,6 +160,9 @@ public abstract class AbstractIntegrationTest {
@Autowired @Autowired
protected DeploymentManagement deploymentManagement; protected DeploymentManagement deploymentManagement;
@Autowired
protected DistributionSetInvalidationManagement distributionSetInvalidationManagement;
@Autowired @Autowired
protected ArtifactManagement artifactManagement; protected ArtifactManagement artifactManagement;

View File

@@ -28,6 +28,7 @@ import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.Constants; import org.eclipse.hawkbit.repository.Constants;
import org.eclipse.hawkbit.repository.ControllerManagement; import org.eclipse.hawkbit.repository.ControllerManagement;
import org.eclipse.hawkbit.repository.DeploymentManagement; import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.DistributionSetInvalidationManagement;
import org.eclipse.hawkbit.repository.DistributionSetManagement; import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.DistributionSetTagManagement; import org.eclipse.hawkbit.repository.DistributionSetTagManagement;
import org.eclipse.hawkbit.repository.DistributionSetTypeManagement; import org.eclipse.hawkbit.repository.DistributionSetTypeManagement;
@@ -48,6 +49,8 @@ import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.ArtifactUpload; import org.eclipse.hawkbit.repository.model.ArtifactUpload;
import org.eclipse.hawkbit.repository.model.BaseEntity; import org.eclipse.hawkbit.repository.model.BaseEntity;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation.CancelationType;
import org.eclipse.hawkbit.repository.model.DistributionSetTag; import org.eclipse.hawkbit.repository.model.DistributionSetTag;
import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.NamedEntity; import org.eclipse.hawkbit.repository.model.NamedEntity;
@@ -140,6 +143,9 @@ public class TestdataFactory {
@Autowired @Autowired
private DistributionSetManagement distributionSetManagement; private DistributionSetManagement distributionSetManagement;
@Autowired
private DistributionSetInvalidationManagement distributionSetInvalidationManagement;
@Autowired @Autowired
private DistributionSetTypeManagement distributionSetTypeManagement; private DistributionSetTypeManagement distributionSetTypeManagement;
@@ -172,11 +178,11 @@ public class TestdataFactory {
* {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} * {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT}
* , {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} and * , {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} and
* {@link DistributionSet#isRequiredMigrationStep()} <code>false</code>. * {@link DistributionSet#isRequiredMigrationStep()} <code>false</code>.
* *
* @param prefix * @param prefix
* for {@link SoftwareModule}s and {@link DistributionSet}s name, * for {@link SoftwareModule}s and {@link DistributionSet}s name,
* vendor and description. * vendor and description.
* *
* @return {@link DistributionSet} entity. * @return {@link DistributionSet} entity.
*/ */
public DistributionSet createDistributionSet(final String prefix) { public DistributionSet createDistributionSet(final String prefix) {
@@ -188,7 +194,7 @@ public class TestdataFactory {
* {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} * {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT}
* , {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} and * , {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} and
* {@link DistributionSet#isRequiredMigrationStep()} <code>false</code>. * {@link DistributionSet#isRequiredMigrationStep()} <code>false</code>.
* *
* @return {@link DistributionSet} entity. * @return {@link DistributionSet} entity.
*/ */
public DistributionSet createDistributionSet() { public DistributionSet createDistributionSet() {
@@ -200,10 +206,10 @@ public class TestdataFactory {
* {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} * {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT}
* , {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} and * , {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} and
* {@link DistributionSet#isRequiredMigrationStep()} <code>false</code>. * {@link DistributionSet#isRequiredMigrationStep()} <code>false</code>.
* *
* @param modules * @param modules
* of {@link DistributionSet#getModules()} * of {@link DistributionSet#getModules()}
* *
* @return {@link DistributionSet} entity. * @return {@link DistributionSet} entity.
*/ */
public DistributionSet createDistributionSet(final Collection<SoftwareModule> modules) { public DistributionSet createDistributionSet(final Collection<SoftwareModule> modules) {
@@ -215,13 +221,13 @@ public class TestdataFactory {
* {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} * {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT}
* , {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} and * , {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} and
* {@link DistributionSet#isRequiredMigrationStep()} <code>false</code>. * {@link DistributionSet#isRequiredMigrationStep()} <code>false</code>.
* *
* @param modules * @param modules
* of {@link DistributionSet#getModules()} * of {@link DistributionSet#getModules()}
* @param prefix * @param prefix
* for {@link SoftwareModule}s and {@link DistributionSet}s name, * for {@link SoftwareModule}s and {@link DistributionSet}s name,
* vendor and description. * vendor and description.
* *
* @return {@link DistributionSet} entity. * @return {@link DistributionSet} entity.
*/ */
public DistributionSet createDistributionSet(final Collection<SoftwareModule> modules, final String prefix) { public DistributionSet createDistributionSet(final Collection<SoftwareModule> modules, final String prefix) {
@@ -232,13 +238,13 @@ public class TestdataFactory {
* Creates {@link DistributionSet} in repository including three * Creates {@link DistributionSet} in repository including three
* {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} * {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT}
* , {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION}. * , {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION}.
* *
* @param prefix * @param prefix
* for {@link SoftwareModule}s and {@link DistributionSet}s name, * for {@link SoftwareModule}s and {@link DistributionSet}s name,
* vendor and description. * vendor and description.
* @param isRequiredMigrationStep * @param isRequiredMigrationStep
* for {@link DistributionSet#isRequiredMigrationStep()} * for {@link DistributionSet#isRequiredMigrationStep()}
* *
* @return {@link DistributionSet} entity. * @return {@link DistributionSet} entity.
*/ */
public DistributionSet createDistributionSet(final String prefix, final boolean isRequiredMigrationStep) { public DistributionSet createDistributionSet(final String prefix, final boolean isRequiredMigrationStep) {
@@ -250,13 +256,13 @@ public class TestdataFactory {
* {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} * {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT}
* , {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} and * , {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} and
* {@link DistributionSet#isRequiredMigrationStep()} <code>false</code>. * {@link DistributionSet#isRequiredMigrationStep()} <code>false</code>.
* *
* @param prefix * @param prefix
* for {@link SoftwareModule}s and {@link DistributionSet}s name, * for {@link SoftwareModule}s and {@link DistributionSet}s name,
* vendor and description. * vendor and description.
* @param tags * @param tags
* {@link DistributionSet#getTags()} * {@link DistributionSet#getTags()}
* *
* @return {@link DistributionSet} entity. * @return {@link DistributionSet} entity.
*/ */
public DistributionSet createDistributionSet(final String prefix, final Collection<DistributionSetTag> tags) { public DistributionSet createDistributionSet(final String prefix, final Collection<DistributionSetTag> tags) {
@@ -267,7 +273,7 @@ public class TestdataFactory {
* Creates {@link DistributionSet} in repository including three * Creates {@link DistributionSet} in repository including three
* {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} * {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT}
* , {@link #SM_TYPE_APP}. * , {@link #SM_TYPE_APP}.
* *
* @param prefix * @param prefix
* for {@link SoftwareModule}s and {@link DistributionSet}s name, * for {@link SoftwareModule}s and {@link DistributionSet}s name,
* vendor and description. * vendor and description.
@@ -277,7 +283,7 @@ public class TestdataFactory {
* number. * number.
* @param isRequiredMigrationStep * @param isRequiredMigrationStep
* for {@link DistributionSet#isRequiredMigrationStep()} * for {@link DistributionSet#isRequiredMigrationStep()}
* *
* @return {@link DistributionSet} entity. * @return {@link DistributionSet} entity.
*/ */
public DistributionSet createDistributionSet(final String prefix, final String version, public DistributionSet createDistributionSet(final String prefix, final String version,
@@ -306,12 +312,12 @@ public class TestdataFactory {
/** /**
* Adds {@link SoftwareModuleMetadata} to every module of given * Adds {@link SoftwareModuleMetadata} to every module of given
* {@link DistributionSet}. * {@link DistributionSet}.
* *
* {@link #VISIBLE_SM_MD_VALUE}, {@link #VISIBLE_SM_MD_KEY} with * {@link #VISIBLE_SM_MD_VALUE}, {@link #VISIBLE_SM_MD_KEY} with
* {@link SoftwareModuleMetadata#isTargetVisible()} and * {@link SoftwareModuleMetadata#isTargetVisible()} and
* {@link #INVISIBLE_SM_MD_KEY}, {@link #INVISIBLE_SM_MD_VALUE} without * {@link #INVISIBLE_SM_MD_KEY}, {@link #INVISIBLE_SM_MD_VALUE} without
* {@link SoftwareModuleMetadata#isTargetVisible()} * {@link SoftwareModuleMetadata#isTargetVisible()}
* *
* @param set * @param set
* to add metadata to * to add metadata to
*/ */
@@ -329,7 +335,7 @@ public class TestdataFactory {
/** /**
* Creates {@link DistributionSet} in repository. * Creates {@link DistributionSet} in repository.
* *
* @param prefix * @param prefix
* for {@link SoftwareModule}s and {@link DistributionSet}s name, * for {@link SoftwareModule}s and {@link DistributionSet}s name,
* vendor and description. * vendor and description.
@@ -341,7 +347,7 @@ public class TestdataFactory {
* for {@link DistributionSet#isRequiredMigrationStep()} * for {@link DistributionSet#isRequiredMigrationStep()}
* @param modules * @param modules
* for {@link DistributionSet#getModules()} * for {@link DistributionSet#getModules()}
* *
* @return {@link DistributionSet} entity. * @return {@link DistributionSet} entity.
*/ */
public DistributionSet createDistributionSet(final String prefix, final String version, public DistributionSet createDistributionSet(final String prefix, final String version,
@@ -358,7 +364,7 @@ public class TestdataFactory {
* Creates {@link DistributionSet} in repository including three * Creates {@link DistributionSet} in repository including three
* {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} * {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT}
* , {@link #SM_TYPE_APP}. * , {@link #SM_TYPE_APP}.
* *
* @param prefix * @param prefix
* for {@link SoftwareModule}s and {@link DistributionSet}s name, * for {@link SoftwareModule}s and {@link DistributionSet}s name,
* vendor and description. * vendor and description.
@@ -368,7 +374,7 @@ public class TestdataFactory {
* number.updat * number.updat
* @param tags * @param tags
* {@link DistributionSet#getTags()} * {@link DistributionSet#getTags()}
* *
* @return {@link DistributionSet} entity. * @return {@link DistributionSet} entity.
*/ */
public DistributionSet createDistributionSet(final String prefix, final String version, public DistributionSet createDistributionSet(final String prefix, final String version,
@@ -388,10 +394,10 @@ public class TestdataFactory {
* , {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} followed by an * , {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} followed by an
* iterative number and {@link DistributionSet#isRequiredMigrationStep()} * iterative number and {@link DistributionSet#isRequiredMigrationStep()}
* <code>false</code>. * <code>false</code>.
* *
* @param number * @param number
* of {@link DistributionSet}s to create * of {@link DistributionSet}s to create
* *
* @return {@link List} of {@link DistributionSet} entities * @return {@link List} of {@link DistributionSet} entities
*/ */
public List<DistributionSet> createDistributionSets(final int number) { public List<DistributionSet> createDistributionSets(final int number) {
@@ -402,7 +408,7 @@ public class TestdataFactory {
/** /**
* Create a list of {@link DistributionSet}s without modules, i.e. * Create a list of {@link DistributionSet}s without modules, i.e.
* incomplete. * incomplete.
* *
* @param number * @param number
* of {@link DistributionSet}s to create * of {@link DistributionSet}s to create
* @return {@link List} of {@link DistributionSet} entities * @return {@link List} of {@link DistributionSet} entities
@@ -425,13 +431,13 @@ public class TestdataFactory {
* , {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} followed by an * , {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} followed by an
* iterative number and {@link DistributionSet#isRequiredMigrationStep()} * iterative number and {@link DistributionSet#isRequiredMigrationStep()}
* <code>false</code>. * <code>false</code>.
* *
* @param prefix * @param prefix
* for {@link SoftwareModule}s and {@link DistributionSet}s name, * for {@link SoftwareModule}s and {@link DistributionSet}s name,
* vendor and description. * vendor and description.
* @param number * @param number
* of {@link DistributionSet}s to create * of {@link DistributionSet}s to create
* *
* @return {@link List} of {@link DistributionSet} entities * @return {@link List} of {@link DistributionSet} entities
*/ */
public List<DistributionSet> createDistributionSets(final String prefix, final int number) { public List<DistributionSet> createDistributionSets(final String prefix, final int number) {
@@ -448,12 +454,12 @@ public class TestdataFactory {
* Creates {@link DistributionSet}s in repository with * Creates {@link DistributionSet}s in repository with
* {@link #DEFAULT_DESCRIPTION} and * {@link #DEFAULT_DESCRIPTION} and
* {@link DistributionSet#isRequiredMigrationStep()} <code>false</code>. * {@link DistributionSet#isRequiredMigrationStep()} <code>false</code>.
* *
* @param name * @param name
* {@link DistributionSet#getName()} * {@link DistributionSet#getName()}
* @param version * @param version
* {@link DistributionSet#getVersion()} * {@link DistributionSet#getVersion()}
* *
* @return {@link DistributionSet} entity * @return {@link DistributionSet} entity
*/ */
public DistributionSet createDistributionSetWithNoSoftwareModules(final String name, final String version) { public DistributionSet createDistributionSetWithNoSoftwareModules(final String name, final String version) {
@@ -465,10 +471,10 @@ public class TestdataFactory {
/** /**
* Creates {@link Artifact}s for given {@link SoftwareModule} with a small * Creates {@link Artifact}s for given {@link SoftwareModule} with a small
* text payload. * text payload.
* *
* @param moduleId * @param moduleId
* the {@link Artifact}s belong to. * the {@link Artifact}s belong to.
* *
* @return {@link Artifact} entity. * @return {@link Artifact} entity.
*/ */
public List<Artifact> createArtifacts(final Long moduleId) { public List<Artifact> createArtifacts(final Long moduleId) {
@@ -484,16 +490,16 @@ public class TestdataFactory {
/** /**
* Create an {@link Artifact} for given {@link SoftwareModule} with a small * Create an {@link Artifact} for given {@link SoftwareModule} with a small
* text payload. * text payload.
* *
* @param artifactData * @param artifactData
* the {@link Artifact} Inputstream * the {@link Artifact} Inputstream
* *
* @param moduleId * @param moduleId
* the {@link Artifact} belongs to * the {@link Artifact} belongs to
* *
* @param filename * @param filename
* that was provided during upload. * that was provided during upload.
* *
* @return {@link Artifact} entity. * @return {@link Artifact} entity.
*/ */
public Artifact createArtifact(final String artifactData, final Long moduleId, final String filename) { public Artifact createArtifact(final String artifactData, final Long moduleId, final String filename) {
@@ -530,10 +536,10 @@ public class TestdataFactory {
* Creates {@link SoftwareModule} with {@link #DEFAULT_VENDOR} and * Creates {@link SoftwareModule} with {@link #DEFAULT_VENDOR} and
* {@link #DEFAULT_VERSION} and random generated * {@link #DEFAULT_VERSION} and random generated
* {@link Target#getDescription()} in the repository. * {@link Target#getDescription()} in the repository.
* *
* @param typeKey * @param typeKey
* of the {@link SoftwareModuleType} * of the {@link SoftwareModuleType}
* *
* @return persisted {@link SoftwareModule}. * @return persisted {@link SoftwareModule}.
*/ */
public SoftwareModule createSoftwareModule(final String typeKey) { public SoftwareModule createSoftwareModule(final String typeKey) {
@@ -545,8 +551,8 @@ public class TestdataFactory {
* {@value Constants#SMT_DEFAULT_APP_KEY} with {@link #DEFAULT_VENDOR} and * {@value Constants#SMT_DEFAULT_APP_KEY} with {@link #DEFAULT_VENDOR} and
* {@link #DEFAULT_VERSION} and random generated * {@link #DEFAULT_VERSION} and random generated
* {@link Target#getDescription()} in the repository. * {@link Target#getDescription()} in the repository.
* *
* *
* @return persisted {@link SoftwareModule}. * @return persisted {@link SoftwareModule}.
*/ */
public SoftwareModule createSoftwareModuleApp() { public SoftwareModule createSoftwareModuleApp() {
@@ -558,11 +564,11 @@ public class TestdataFactory {
* {@value Constants#SMT_DEFAULT_APP_KEY} with {@link #DEFAULT_VENDOR} and * {@value Constants#SMT_DEFAULT_APP_KEY} with {@link #DEFAULT_VENDOR} and
* {@link #DEFAULT_VERSION} and random generated * {@link #DEFAULT_VERSION} and random generated
* {@link Target#getDescription()} in the repository. * {@link Target#getDescription()} in the repository.
* *
* @param prefix * @param prefix
* added to name and version * added to name and version
* *
* *
* @return persisted {@link SoftwareModule}. * @return persisted {@link SoftwareModule}.
*/ */
public SoftwareModule createSoftwareModuleApp(final String prefix) { public SoftwareModule createSoftwareModuleApp(final String prefix) {
@@ -574,8 +580,8 @@ public class TestdataFactory {
* {@value Constants#SMT_DEFAULT_OS_KEY} with {@link #DEFAULT_VENDOR} and * {@value Constants#SMT_DEFAULT_OS_KEY} with {@link #DEFAULT_VENDOR} and
* {@link #DEFAULT_VERSION} and random generated * {@link #DEFAULT_VERSION} and random generated
* {@link Target#getDescription()} in the repository. * {@link Target#getDescription()} in the repository.
* *
* *
* @return persisted {@link SoftwareModule}. * @return persisted {@link SoftwareModule}.
*/ */
public SoftwareModule createSoftwareModuleOs() { public SoftwareModule createSoftwareModuleOs() {
@@ -587,11 +593,11 @@ public class TestdataFactory {
* {@value Constants#SMT_DEFAULT_OS_KEY} with {@link #DEFAULT_VENDOR} and * {@value Constants#SMT_DEFAULT_OS_KEY} with {@link #DEFAULT_VENDOR} and
* {@link #DEFAULT_VERSION} and random generated * {@link #DEFAULT_VERSION} and random generated
* {@link Target#getDescription()} in the repository. * {@link Target#getDescription()} in the repository.
* *
* @param prefix * @param prefix
* added to name and version * added to name and version
* *
* *
* @return persisted {@link SoftwareModule}. * @return persisted {@link SoftwareModule}.
*/ */
public SoftwareModule createSoftwareModuleOs(final String prefix) { public SoftwareModule createSoftwareModuleOs(final String prefix) {
@@ -602,12 +608,12 @@ public class TestdataFactory {
* Creates {@link SoftwareModule} with {@link #DEFAULT_VENDOR} and * Creates {@link SoftwareModule} with {@link #DEFAULT_VENDOR} and
* {@link #DEFAULT_VERSION} and random generated * {@link #DEFAULT_VERSION} and random generated
* {@link Target#getDescription()} in the repository. * {@link Target#getDescription()} in the repository.
* *
* @param typeKey * @param typeKey
* of the {@link SoftwareModuleType} * of the {@link SoftwareModuleType}
* @param prefix * @param prefix
* added to name and version * added to name and version
* *
* @return persisted {@link SoftwareModule}. * @return persisted {@link SoftwareModule}.
*/ */
public SoftwareModule createSoftwareModule(final String typeKey, final String prefix) { public SoftwareModule createSoftwareModule(final String typeKey, final String prefix) {
@@ -652,12 +658,12 @@ public class TestdataFactory {
* @param targetName * @param targetName
* name of the target * name of the target
* @param targetTypeId * @param targetTypeId
* target type id * target type id
* @return persisted {@link Target} * @return persisted {@link Target}
*/ */
public Target createTarget(final String controllerId, final String targetName, final Long targetTypeId) { public Target createTarget(final String controllerId, final String targetName, final Long targetTypeId) {
final Target target = targetManagement final Target target = targetManagement.create(
.create(entityFactory.target().create().controllerId(controllerId).name(targetName).targetType(targetTypeId)); entityFactory.target().create().controllerId(controllerId).name(targetName).targetType(targetTypeId));
assertTargetProperlyCreated(target); assertTargetProperlyCreated(target);
return target; return target;
} }
@@ -677,12 +683,12 @@ public class TestdataFactory {
* , {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} followed by an * , {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} followed by an
* iterative number and {@link DistributionSet#isRequiredMigrationStep()} * iterative number and {@link DistributionSet#isRequiredMigrationStep()}
* <code>false</code>. * <code>false</code>.
* *
* In addition it updates the created {@link DistributionSet}s and * In addition it updates the created {@link DistributionSet}s and
* {@link SoftwareModule}s to ensure that * {@link SoftwareModule}s to ensure that
* {@link BaseEntity#getLastModifiedAt()} and * {@link BaseEntity#getLastModifiedAt()} and
* {@link BaseEntity#getLastModifiedBy()} is filled. * {@link BaseEntity#getLastModifiedBy()} is filled.
* *
* @return persisted {@link DistributionSet}. * @return persisted {@link DistributionSet}.
*/ */
public DistributionSet createUpdatedDistributionSet() { public DistributionSet createUpdatedDistributionSet() {
@@ -716,12 +722,12 @@ public class TestdataFactory {
/** /**
* Creates {@link DistributionSetType} in repository. * Creates {@link DistributionSetType} in repository.
* *
* @param dsTypeKey * @param dsTypeKey
* {@link DistributionSetType#getKey()} * {@link DistributionSetType#getKey()}
* @param dsTypeName * @param dsTypeName
* {@link DistributionSetType#getName()} * {@link DistributionSetType#getName()}
* *
* @return persisted {@link DistributionSetType} * @return persisted {@link DistributionSetType}
*/ */
public DistributionSetType findOrCreateDistributionSetType(final String dsTypeKey, final String dsTypeName) { public DistributionSetType findOrCreateDistributionSetType(final String dsTypeKey, final String dsTypeName) {
@@ -733,7 +739,7 @@ public class TestdataFactory {
/** /**
* Finds {@link DistributionSetType} in repository with given * Finds {@link DistributionSetType} in repository with given
* {@link DistributionSetType#getKey()} or creates if it does not exist yet. * {@link DistributionSetType#getKey()} or creates if it does not exist yet.
* *
* @param dsTypeKey * @param dsTypeKey
* {@link DistributionSetType#getKey()} * {@link DistributionSetType#getKey()}
* @param dsTypeName * @param dsTypeName
@@ -742,7 +748,7 @@ public class TestdataFactory {
* {@link DistributionSetType#getMandatoryModuleTypes()} * {@link DistributionSetType#getMandatoryModuleTypes()}
* @param optional * @param optional
* {@link DistributionSetType#getOptionalModuleTypes()} * {@link DistributionSetType#getOptionalModuleTypes()}
* *
* @return persisted {@link DistributionSetType} * @return persisted {@link DistributionSetType}
*/ */
public DistributionSetType findOrCreateDistributionSetType(final String dsTypeKey, final String dsTypeName, public DistributionSetType findOrCreateDistributionSetType(final String dsTypeKey, final String dsTypeName,
@@ -758,10 +764,10 @@ public class TestdataFactory {
* Finds {@link SoftwareModuleType} in repository with given * Finds {@link SoftwareModuleType} in repository with given
* {@link SoftwareModuleType#getKey()} or creates if it does not exist yet * {@link SoftwareModuleType#getKey()} or creates if it does not exist yet
* with {@link SoftwareModuleType#getMaxAssignments()} = 1. * with {@link SoftwareModuleType#getMaxAssignments()} = 1.
* *
* @param key * @param key
* {@link SoftwareModuleType#getKey()} * {@link SoftwareModuleType#getKey()}
* *
* @return persisted {@link SoftwareModuleType} * @return persisted {@link SoftwareModuleType}
*/ */
public SoftwareModuleType findOrCreateSoftwareModuleType(final String key) { public SoftwareModuleType findOrCreateSoftwareModuleType(final String key) {
@@ -771,18 +777,18 @@ public class TestdataFactory {
/** /**
* Finds {@link SoftwareModuleType} in repository with given * Finds {@link SoftwareModuleType} in repository with given
* {@link SoftwareModuleType#getKey()} or creates if it does not exist yet. * {@link SoftwareModuleType#getKey()} or creates if it does not exist yet.
* *
* @param key * @param key
* {@link SoftwareModuleType#getKey()} * {@link SoftwareModuleType#getKey()}
* @param maxAssignments * @param maxAssignments
* {@link SoftwareModuleType#getMaxAssignments()} * {@link SoftwareModuleType#getMaxAssignments()}
* *
* @return persisted {@link SoftwareModuleType} * @return persisted {@link SoftwareModuleType}
*/ */
public SoftwareModuleType findOrCreateSoftwareModuleType(final String key, final int maxAssignments) { public SoftwareModuleType findOrCreateSoftwareModuleType(final String key, final int maxAssignments) {
return softwareModuleTypeManagement.getByKey(key) return softwareModuleTypeManagement.getByKey(key).orElseGet(
.orElseGet(() -> softwareModuleTypeManagement.create(entityFactory.softwareModuleType().create() () -> softwareModuleTypeManagement.create(entityFactory.softwareModuleType().create().key(key).name(key)
.key(key).name(key).description(LOREM.words(10)).colour("#ffffff").maxAssignments(maxAssignments))); .description(LOREM.words(10)).colour("#ffffff").maxAssignments(maxAssignments)));
} }
/** /**
@@ -796,7 +802,7 @@ public class TestdataFactory {
* {@link DistributionSet#getType()} * {@link DistributionSet#getType()}
* @param modules * @param modules
* {@link DistributionSet#getModules()} * {@link DistributionSet#getModules()}
* *
* @return the created {@link DistributionSet} * @return the created {@link DistributionSet}
*/ */
public DistributionSet createDistributionSet(final String name, final String version, public DistributionSet createDistributionSet(final String name, final String version,
@@ -819,7 +825,7 @@ public class TestdataFactory {
* {@link DistributionSet#getModules()} * {@link DistributionSet#getModules()}
* @param requiredMigrationStep * @param requiredMigrationStep
* {@link DistributionSet#isRequiredMigrationStep()} * {@link DistributionSet#isRequiredMigrationStep()}
* *
* @return the created {@link DistributionSet} * @return the created {@link DistributionSet}
*/ */
public DistributionSet generateDistributionSet(final String name, final String version, public DistributionSet generateDistributionSet(final String name, final String version,
@@ -841,7 +847,7 @@ public class TestdataFactory {
* {@link DistributionSet#getType()} * {@link DistributionSet#getType()}
* @param modules * @param modules
* {@link DistributionSet#getModules()} * {@link DistributionSet#getModules()}
* *
* @return the created {@link DistributionSet} * @return the created {@link DistributionSet}
*/ */
public DistributionSet generateDistributionSet(final String name, final String version, public DistributionSet generateDistributionSet(final String name, final String version,
@@ -854,7 +860,7 @@ public class TestdataFactory {
* *
* @param name * @param name
* {@link DistributionSet#getName()} * {@link DistributionSet#getName()}
* *
* @return the generated {@link DistributionSet} * @return the generated {@link DistributionSet}
*/ */
public DistributionSet generateDistributionSet(final String name) { public DistributionSet generateDistributionSet(final String name) {
@@ -866,10 +872,10 @@ public class TestdataFactory {
* Creates {@link Target}s in repository and with * Creates {@link Target}s in repository and with
* {@link #DEFAULT_CONTROLLER_ID} as prefix for * {@link #DEFAULT_CONTROLLER_ID} as prefix for
* {@link Target#getControllerId()}. * {@link Target#getControllerId()}.
* *
* @param number * @param number
* of {@link Target}s to create * of {@link Target}s to create
* *
* @return {@link List} of {@link Target} entities * @return {@link List} of {@link Target} entities
*/ */
public List<Target> createTargets(final int number) { public List<Target> createTargets(final int number) {
@@ -884,10 +890,10 @@ public class TestdataFactory {
/** /**
* Creates {@link Target}s in repository and with given targetIds. * Creates {@link Target}s in repository and with given targetIds.
* *
* @param targetIds * @param targetIds
* specifies the IDs of the targets * specifies the IDs of the targets
* *
* @return {@link List} of {@link Target} entities * @return {@link List} of {@link Target} entities
*/ */
public List<Target> createTargets(final String... targetIds) { public List<Target> createTargets(final String... targetIds) {
@@ -903,7 +909,7 @@ public class TestdataFactory {
/** /**
* Builds {@link Target} objects with given prefix for * Builds {@link Target} objects with given prefix for
* {@link Target#getControllerId()} followed by a number suffix. * {@link Target#getControllerId()} followed by a number suffix.
* *
* @param start * @param start
* value for the controllerId suffix * value for the controllerId suffix
* @param numberOfTargets * @param numberOfTargets
@@ -925,7 +931,7 @@ public class TestdataFactory {
* Builds {@link Target} objects with given prefix for * Builds {@link Target} objects with given prefix for
* {@link Target#getControllerId()} followed by a number suffix starting * {@link Target#getControllerId()} followed by a number suffix starting
* with 0. * with 0.
* *
* @param numberOfTargets * @param numberOfTargets
* of {@link Target}s to generate * of {@link Target}s to generate
* @param controllerIdPrefix * @param controllerIdPrefix
@@ -1015,10 +1021,10 @@ public class TestdataFactory {
/** /**
* Creates {@link DistributionSetTag}s in repository. * Creates {@link DistributionSetTag}s in repository.
* *
* @param number * @param number
* of {@link DistributionSetTag}s * of {@link DistributionSetTag}s
* *
* @return the persisted {@link DistributionSetTag}s * @return the persisted {@link DistributionSetTag}s
*/ */
public List<DistributionSetTag> createDistributionSetTags(final int number) { public List<DistributionSetTag> createDistributionSetTags(final int number) {
@@ -1042,14 +1048,14 @@ public class TestdataFactory {
/** /**
* Append {@link ActionStatus} to all {@link Action}s of given * Append {@link ActionStatus} to all {@link Action}s of given
* {@link Target}s. * {@link Target}s.
* *
* @param targets * @param targets
* to add {@link ActionStatus} * to add {@link ActionStatus}
* @param status * @param status
* to add * to add
* @param message * @param message
* to add * to add
* *
* @return updated {@link Action}. * @return updated {@link Action}.
*/ */
public List<Action> sendUpdateActionStatusToTargets(final Collection<Target> targets, final Status status, public List<Action> sendUpdateActionStatusToTargets(final Collection<Target> targets, final Status status,
@@ -1060,14 +1066,14 @@ public class TestdataFactory {
/** /**
* Append {@link ActionStatus} to all {@link Action}s of given * Append {@link ActionStatus} to all {@link Action}s of given
* {@link Target}s. * {@link Target}s.
* *
* @param targets * @param targets
* to add {@link ActionStatus} * to add {@link ActionStatus}
* @param status * @param status
* to add * to add
* @param msgs * @param msgs
* to add * to add
* *
* @return updated {@link Action}. * @return updated {@link Action}.
*/ */
public List<Action> sendUpdateActionStatusToTargets(final Collection<Target> targets, final Status status, public List<Action> sendUpdateActionStatusToTargets(final Collection<Target> targets, final Status status,
@@ -1085,7 +1091,7 @@ public class TestdataFactory {
/** /**
* Creates rollout based on given parameters. * Creates rollout based on given parameters.
* *
* @param rolloutName * @param rolloutName
* of the {@link Rollout} * of the {@link Rollout}
* @param rolloutDescription * @param rolloutDescription
@@ -1155,7 +1161,7 @@ public class TestdataFactory {
/** /**
* Create {@link Rollout} with a new {@link DistributionSet} and * Create {@link Rollout} with a new {@link DistributionSet} and
* {@link Target}s. * {@link Target}s.
* *
* @param prefix * @param prefix
* for rollouts name, description, * for rollouts name, description,
* {@link Target#getControllerId()} filter * {@link Target#getControllerId()} filter
@@ -1170,7 +1176,7 @@ public class TestdataFactory {
/** /**
* Create the soft deleted {@link Rollout} with a new * Create the soft deleted {@link Rollout} with a new
* {@link DistributionSet} and {@link Target}s. * {@link DistributionSet} and {@link Target}s.
* *
* @param prefix * @param prefix
* for rollouts name, description, * for rollouts name, description,
* {@link Target#getControllerId()} filter * {@link Target#getControllerId()} filter
@@ -1187,8 +1193,8 @@ public class TestdataFactory {
/** /**
* Finds {@link TargetType} in repository with given * Finds {@link TargetType} in repository with given
* {@link TargetType#getName()} or creates if it does not exist yet. * {@link TargetType#getName()} or creates if it does not exist yet. No ds
* No ds types are assigned on creation. * types are assigned on creation.
* *
* @param targetTypeName * @param targetTypeName
* {@link TargetType#getName()} * {@link TargetType#getName()}
@@ -1197,25 +1203,26 @@ public class TestdataFactory {
*/ */
public TargetType findOrCreateTargetType(final String targetTypeName) { public TargetType findOrCreateTargetType(final String targetTypeName) {
return targetTypeManagement.getByName(targetTypeName) return targetTypeManagement.getByName(targetTypeName)
.orElseGet(() -> targetTypeManagement.create(entityFactory.targetType().create() .orElseGet(() -> targetTypeManagement.create(entityFactory.targetType().create().name(targetTypeName)
.name(targetTypeName).description(targetTypeName + " description").colour(DEFAULT_COLOUR))); .description(targetTypeName + " description").colour(DEFAULT_COLOUR)));
} }
/** /**
* Creates {@link TargetType} in repository with given * Creates {@link TargetType} in repository with given
* {@link TargetType#getName()}. Compatible distribution set types are assigned on creation * {@link TargetType#getName()}. Compatible distribution set types are
* assigned on creation
* *
* @param targetTypeName * @param targetTypeName
* {@link TargetType#getName()} * {@link TargetType#getName()}
* *
* @return persisted {@link TargetType} * @return persisted {@link TargetType}
*/ */
public TargetType createTargetType(final String targetTypeName, List<DistributionSetType> compatibleDsTypes) { public TargetType createTargetType(final String targetTypeName, final List<DistributionSetType> compatibleDsTypes) {
return targetTypeManagement.create(entityFactory.targetType().create() return targetTypeManagement.create(entityFactory.targetType().create().name(targetTypeName)
.name(targetTypeName).description(targetTypeName + " description").colour(DEFAULT_COLOUR) .description(targetTypeName + " description").colour(DEFAULT_COLOUR)
.compatible(compatibleDsTypes.stream().map(DistributionSetType::getId).collect(Collectors.toList()))); .compatible(compatibleDsTypes.stream().map(DistributionSetType::getId).collect(Collectors.toList())));
} }
/** /**
* Creates {@link TargetType} in repository with given * Creates {@link TargetType} in repository with given
* {@link TargetType#getName()}. No ds types are assigned on creation. * {@link TargetType#getName()}. No ds types are assigned on creation.
@@ -1225,13 +1232,37 @@ public class TestdataFactory {
* *
* @return persisted {@link TargetType} * @return persisted {@link TargetType}
*/ */
public List<TargetType> createTargetTypes(final String targetTypePrefix, int count) { public List<TargetType> createTargetTypes(final String targetTypePrefix, final int count) {
final List<TargetTypeCreate> result = Lists.newArrayListWithExpectedSize(count); final List<TargetTypeCreate> result = Lists.newArrayListWithExpectedSize(count);
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
result.add(entityFactory.targetType().create().name(targetTypePrefix + i).description(targetTypePrefix + " description") result.add(entityFactory.targetType().create().name(targetTypePrefix + i)
.colour(DEFAULT_COLOUR)); .description(targetTypePrefix + " description").colour(DEFAULT_COLOUR));
} }
return targetTypeManagement.create(result); return targetTypeManagement.create(result);
} }
/**
* Creates a distribution set and directly invalidates it. No actions will
* be canceled and no rollouts will be stopped with this invalidation.
*
* @return created invalidated {@link DistributionSet}
*/
public DistributionSet createAndInvalidateDistributionSet() {
final DistributionSet distributionSet = createDistributionSet();
distributionSetInvalidationManagement.invalidateDistributionSet(
new DistributionSetInvalidation(Arrays.asList(distributionSet.getId()), CancelationType.NONE, false));
return distributionSet;
}
/**
* Creates a distribution set that has no software modules assigned, so it
* is incomplete.
*
* @return created incomplete {@link DistributionSet}
*/
public DistributionSet createIncompleteDistributionSet() {
return distributionSetManagement.create(entityFactory.distributionSet().create()
.name(UUID.randomUUID().toString()).version(DEFAULT_VERSION).description(LOREM.words(10))
.type(findOrCreateDefaultTestDsType()).requiredMigrationStep(false));
}
} }

View File

@@ -0,0 +1,44 @@
/**
* Copyright (c) 2021 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.mgmt.json.model.distributionset;
import com.fasterxml.jackson.annotation.JsonValue;
/**
* Definition of the action cancel type for the distribution set invalidation
* via REST management API.
*
*/
public enum MgmtCancelationType {
/**
* Actions will be soft canceled.
*/
SOFT("soft"),
/**
* Actions will be force quit.
*/
FORCE("force"),
/**
* No actions will be canceled.
*/
NONE("none");
private final String name;
private MgmtCancelationType(final String name) {
this.name = name;
}
@JsonValue
public String getName() {
return name;
}
}

View File

@@ -49,6 +49,17 @@ public class MgmtDistributionSet extends MgmtNamedEntity {
@JsonProperty @JsonProperty
private boolean deleted; private boolean deleted;
@JsonProperty
private boolean valid;
public boolean isValid() {
return valid;
}
public void setValid(final boolean valid) {
this.valid = valid;
}
public boolean isDeleted() { public boolean isDeleted() {
return deleted; return deleted;
} }

View File

@@ -0,0 +1,43 @@
/**
* Copyright (c) 2021 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.mgmt.json.model.distributionset;
import javax.validation.constraints.NotNull;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* A json annotated rest model for invalidate DistributionSet requests.
*
*/
public class MgmtInvalidateDistributionSetRequestBody {
@NotNull
@JsonProperty
private MgmtCancelationType actionCancelationType;
@JsonProperty
private boolean cancelRollouts;
public MgmtCancelationType getActionCancelationType() {
return actionCancelationType;
}
public void setActionCancelationType(final MgmtCancelationType actionCancelationType) {
this.actionCancelationType = actionCancelationType;
}
public boolean isCancelRollouts() {
return cancelRollouts;
}
public void setCancelRollouts(final boolean cancelRollouts) {
this.cancelRollouts = cancelRollouts;
}
}

View File

@@ -10,12 +10,15 @@ package org.eclipse.hawkbit.mgmt.rest.api;
import java.util.List; import java.util.List;
import javax.validation.Valid;
import org.eclipse.hawkbit.mgmt.json.model.MgmtMetadata; import org.eclipse.hawkbit.mgmt.json.model.MgmtMetadata;
import org.eclipse.hawkbit.mgmt.json.model.MgmtMetadataBodyPut; import org.eclipse.hawkbit.mgmt.json.model.MgmtMetadataBodyPut;
import org.eclipse.hawkbit.mgmt.json.model.PagedList; import org.eclipse.hawkbit.mgmt.json.model.PagedList;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSet; import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSet;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSetRequestBodyPost; import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSetRequestBodyPost;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSetRequestBodyPut; import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSetRequestBodyPut;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtInvalidateDistributionSetRequestBody;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtTargetAssignmentRequestBody; import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtTargetAssignmentRequestBody;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtTargetAssignmentResponseBody; import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtTargetAssignmentResponseBody;
import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModule; import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModule;
@@ -378,4 +381,19 @@ public interface MgmtDistributionSetRestApi {
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) int pagingOffsetParam, @RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) int pagingOffsetParam,
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) int pagingLimitParam, @RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) int pagingLimitParam,
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SORTING, required = false) String sortParam); @RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SORTING, required = false) String sortParam);
/**
* Invalidates a distribution set
*
* @param distributionSetId
* the ID of the distribution set to invalidate
* @param invalidateRequestBody
* the definition if rollouts and actions should be canceled
* @return status OK if the invalidation was successful
*/
@PostMapping(value = "/{distributionSetId}/invalidate", consumes = { MediaTypes.HAL_JSON_VALUE,
MediaType.APPLICATION_JSON_VALUE }, produces = { MediaTypes.HAL_JSON_VALUE,
MediaType.APPLICATION_JSON_VALUE })
ResponseEntity<Void> invalidateDistributionSet(@PathVariable("distributionSetId") Long distributionSetId,
@Valid MgmtInvalidateDistributionSetRequestBody invalidateRequestBody);
} }

View File

@@ -110,6 +110,7 @@ public final class MgmtDistributionSetMapper {
response.setComplete(distributionSet.isComplete()); response.setComplete(distributionSet.isComplete());
response.setType(distributionSet.getType().getKey()); response.setType(distributionSet.getType().getKey());
response.setDeleted(distributionSet.isDeleted()); response.setDeleted(distributionSet.isDeleted());
response.setValid(distributionSet.isValid());
distributionSet.getModules() distributionSet.getModules()
.forEach(module -> response.getModules().add(MgmtSoftwareModuleMapper.toResponse(module))); .forEach(module -> response.getModules().add(MgmtSoftwareModuleMapper.toResponse(module)));

View File

@@ -9,17 +9,21 @@
package org.eclipse.hawkbit.mgmt.rest.resource; package org.eclipse.hawkbit.mgmt.rest.resource;
import java.util.AbstractMap.SimpleEntry; import java.util.AbstractMap.SimpleEntry;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.validation.Valid;
import org.eclipse.hawkbit.mgmt.json.model.MgmtMetadata; import org.eclipse.hawkbit.mgmt.json.model.MgmtMetadata;
import org.eclipse.hawkbit.mgmt.json.model.MgmtMetadataBodyPut; import org.eclipse.hawkbit.mgmt.json.model.MgmtMetadataBodyPut;
import org.eclipse.hawkbit.mgmt.json.model.PagedList; import org.eclipse.hawkbit.mgmt.json.model.PagedList;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSet; import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSet;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSetRequestBodyPost; import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSetRequestBodyPost;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSetRequestBodyPut; import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSetRequestBodyPut;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtInvalidateDistributionSetRequestBody;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtTargetAssignmentRequestBody; import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtTargetAssignmentRequestBody;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtTargetAssignmentResponseBody; import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtTargetAssignmentResponseBody;
import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModule; import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModule;
@@ -29,6 +33,7 @@ import org.eclipse.hawkbit.mgmt.json.model.targetfilter.MgmtTargetFilterQuery;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtDistributionSetRestApi; import org.eclipse.hawkbit.mgmt.rest.api.MgmtDistributionSetRestApi;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants; import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.repository.DeploymentManagement; import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.DistributionSetInvalidationManagement;
import org.eclipse.hawkbit.repository.DistributionSetManagement; import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.EntityFactory; import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.OffsetBasedPageRequest; import org.eclipse.hawkbit.repository.OffsetBasedPageRequest;
@@ -40,6 +45,7 @@ import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.model.DeploymentRequest; import org.eclipse.hawkbit.repository.model.DeploymentRequest;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult; import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation;
import org.eclipse.hawkbit.repository.model.DistributionSetMetadata; import org.eclipse.hawkbit.repository.model.DistributionSetMetadata;
import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.Target;
@@ -81,11 +87,14 @@ public class MgmtDistributionSetResource implements MgmtDistributionSetRestApi {
private final SystemSecurityContext systemSecurityContext; private final SystemSecurityContext systemSecurityContext;
private final DistributionSetInvalidationManagement distributionSetInvalidationManagement;
MgmtDistributionSetResource(final SoftwareModuleManagement softwareModuleManagement, MgmtDistributionSetResource(final SoftwareModuleManagement softwareModuleManagement,
final TargetManagement targetManagement, final TargetFilterQueryManagement targetFilterQueryManagement, final TargetManagement targetManagement, final TargetFilterQueryManagement targetFilterQueryManagement,
final DeploymentManagement deployManagament, final SystemManagement systemManagement, final DeploymentManagement deployManagament, final SystemManagement systemManagement,
final EntityFactory entityFactory, final DistributionSetManagement distributionSetManagement, final EntityFactory entityFactory, final DistributionSetManagement distributionSetManagement,
final SystemSecurityContext systemSecurityContext) { final SystemSecurityContext systemSecurityContext,
final DistributionSetInvalidationManagement distributionSetInvalidationManagement) {
this.softwareModuleManagement = softwareModuleManagement; this.softwareModuleManagement = softwareModuleManagement;
this.targetManagement = targetManagement; this.targetManagement = targetManagement;
this.targetFilterQueryManagement = targetFilterQueryManagement; this.targetFilterQueryManagement = targetFilterQueryManagement;
@@ -94,6 +103,7 @@ public class MgmtDistributionSetResource implements MgmtDistributionSetRestApi {
this.entityFactory = entityFactory; this.entityFactory = entityFactory;
this.distributionSetManagement = distributionSetManagement; this.distributionSetManagement = distributionSetManagement;
this.systemSecurityContext = systemSecurityContext; this.systemSecurityContext = systemSecurityContext;
this.distributionSetInvalidationManagement = distributionSetInvalidationManagement;
} }
@Override @Override
@@ -125,7 +135,7 @@ public class MgmtDistributionSetResource implements MgmtDistributionSetRestApi {
@Override @Override
public ResponseEntity<MgmtDistributionSet> getDistributionSet( public ResponseEntity<MgmtDistributionSet> getDistributionSet(
@PathVariable("distributionSetId") final Long distributionSetId) { @PathVariable("distributionSetId") final Long distributionSetId) {
final DistributionSet foundDs = findDistributionSetWithExceptionIfNotFound(distributionSetId); final DistributionSet foundDs = distributionSetManagement.getOrElseThrowException(distributionSetId);
final MgmtDistributionSet response = MgmtDistributionSetMapper.toResponse(foundDs); final MgmtDistributionSet response = MgmtDistributionSetMapper.toResponse(foundDs);
MgmtDistributionSetMapper.addLinks(foundDs, response); MgmtDistributionSetMapper.addLinks(foundDs, response);
@@ -206,7 +216,7 @@ public class MgmtDistributionSetResource implements MgmtDistributionSetRestApi {
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam) { @RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam) {
// check if distribution set exists otherwise throw exception // check if distribution set exists otherwise throw exception
// immediately // immediately
findDistributionSetWithExceptionIfNotFound(distributionSetId); distributionSetManagement.getOrElseThrowException(distributionSetId);
final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam); final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam);
final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam); final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam);
@@ -254,11 +264,10 @@ public class MgmtDistributionSetResource implements MgmtDistributionSetRestApi {
final List<Entry<String, Long>> offlineAssignments = assignments.stream() final List<Entry<String, Long>> offlineAssignments = assignments.stream()
.map(assignment -> new SimpleEntry<String, Long>(assignment.getId(), distributionSetId)) .map(assignment -> new SimpleEntry<String, Long>(assignment.getId(), distributionSetId))
.collect(Collectors.toList()); .collect(Collectors.toList());
return ResponseEntity return ResponseEntity.ok(MgmtDistributionSetMapper
.ok(MgmtDistributionSetMapper .toResponse(deployManagament.offlineAssignedDistributionSets(offlineAssignments)));
.toResponse(deployManagament.offlineAssignedDistributionSets(offlineAssignments)));
} }
final List<DeploymentRequest> deploymentRequests = assignments.stream() final List<DeploymentRequest> deploymentRequests = assignments.stream()
.map(assignment -> MgmtDeploymentRequestMapper.createAssignmentRequest(assignment, distributionSetId)) .map(assignment -> MgmtDeploymentRequestMapper.createAssignmentRequest(assignment, distributionSetId))
.collect(Collectors.toList()); .collect(Collectors.toList());
@@ -374,8 +383,14 @@ public class MgmtDistributionSetResource implements MgmtDistributionSetRestApi {
softwaremodules.getTotalElements())); softwaremodules.getTotalElements()));
} }
private DistributionSet findDistributionSetWithExceptionIfNotFound(final Long distributionSetId) { @Override
return distributionSetManagement.get(distributionSetId) public ResponseEntity<Void> invalidateDistributionSet(
.orElseThrow(() -> new EntityNotFoundException(DistributionSet.class, distributionSetId)); @PathVariable("distributionSetId") final Long distributionSetId,
@Valid @RequestBody final MgmtInvalidateDistributionSetRequestBody invalidateRequestBody) {
distributionSetInvalidationManagement
.invalidateDistributionSet(new DistributionSetInvalidation(Arrays.asList(distributionSetId),
MgmtRestModelMapper.convertCancelationType(invalidateRequestBody.getActionCancelationType()),
invalidateRequestBody.isCancelRollouts()));
return ResponseEntity.ok().build();
} }
} }

View File

@@ -11,7 +11,9 @@ package org.eclipse.hawkbit.mgmt.rest.resource;
import org.eclipse.hawkbit.mgmt.json.model.MgmtBaseEntity; import org.eclipse.hawkbit.mgmt.json.model.MgmtBaseEntity;
import org.eclipse.hawkbit.mgmt.json.model.MgmtNamedEntity; import org.eclipse.hawkbit.mgmt.json.model.MgmtNamedEntity;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtActionType; import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtActionType;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtCancelationType;
import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation.CancelationType;
import org.eclipse.hawkbit.repository.model.NamedEntity; import org.eclipse.hawkbit.repository.model.NamedEntity;
import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity; import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity;
@@ -48,10 +50,10 @@ public final class MgmtRestModelMapper {
/** /**
* Convert the given {@link MgmtActionType} into a corresponding repository * Convert the given {@link MgmtActionType} into a corresponding repository
* {@link ActionType}. * {@link ActionType}.
* *
* @param actionTypeRest * @param actionTypeRest
* the REST representation of the action type * the REST representation of the action type
* *
* @return <null> or the repository action type * @return <null> or the repository action type
*/ */
public static ActionType convertActionType(final MgmtActionType actionTypeRest) { public static ActionType convertActionType(final MgmtActionType actionTypeRest) {
@@ -76,10 +78,10 @@ public final class MgmtRestModelMapper {
/** /**
* Converts the given repository {@link ActionType} into a corresponding * Converts the given repository {@link ActionType} into a corresponding
* {@link MgmtActionType}. * {@link MgmtActionType}.
* *
* @param actionType * @param actionType
* the repository representation of the action type * the repository representation of the action type
* *
* @return <null> or the REST action type * @return <null> or the REST action type
*/ */
public static MgmtActionType convertActionType(final ActionType actionType) { public static MgmtActionType convertActionType(final ActionType actionType) {
@@ -100,4 +102,30 @@ public final class MgmtRestModelMapper {
throw new IllegalStateException("Action Type is not supported"); throw new IllegalStateException("Action Type is not supported");
} }
} }
/**
* Converts the given repository {@link CancelationType} into a
* corresponding {@link MgmtCancelationType}.
*
* @param cancelationType
* the repository representation of the cancellation type
*
* @return <null> or the REST cancellation type
*/
public static CancelationType convertCancelationType(final MgmtCancelationType cancelationType) {
if (cancelationType == null) {
return null;
}
switch (cancelationType) {
case SOFT:
return CancelationType.SOFT;
case FORCE:
return CancelationType.FORCE;
case NONE:
return CancelationType.NONE;
default:
throw new IllegalStateException("Action Cancelation Type is not supported");
}
}
} }

View File

@@ -110,7 +110,8 @@ public class MgmtRolloutResource implements MgmtRolloutRestApi {
// exception is thrown // exception is thrown
targetFilterQueryManagement.verifyTargetFilterQuerySyntax(rolloutRequestBody.getTargetFilterQuery()); targetFilterQueryManagement.verifyTargetFilterQuerySyntax(rolloutRequestBody.getTargetFilterQuery());
final DistributionSet distributionSet = findDistributionSetOrThrowException(rolloutRequestBody); final DistributionSet distributionSet = distributionSetManagement
.getValidAndComplete(rolloutRequestBody.getDistributionSetId());
final RolloutGroupConditions rolloutGroupConditions = MgmtRolloutMapper.fromRequest(rolloutRequestBody, true); final RolloutGroupConditions rolloutGroupConditions = MgmtRolloutMapper.fromRequest(rolloutRequestBody, true);
final RolloutCreate create = MgmtRolloutMapper.fromRequest(entityFactory, rolloutRequestBody, distributionSet); final RolloutCreate create = MgmtRolloutMapper.fromRequest(entityFactory, rolloutRequestBody, distributionSet);
@@ -234,10 +235,4 @@ public class MgmtRolloutResource implements MgmtRolloutRestApi {
final List<MgmtTarget> rest = MgmtTargetMapper.toResponse(rolloutGroupTargets.getContent()); final List<MgmtTarget> rest = MgmtTargetMapper.toResponse(rolloutGroupTargets.getContent());
return ResponseEntity.ok(new PagedList<>(rest, rolloutGroupTargets.getTotalElements())); return ResponseEntity.ok(new PagedList<>(rest, rolloutGroupTargets.getTotalElements()));
} }
private DistributionSet findDistributionSetOrThrowException(final MgmtRolloutRestRequestBody rolloutRequestBody) {
return this.distributionSetManagement.get(rolloutRequestBody.getDistributionSetId()).orElseThrow(
() -> new EntityNotFoundException(DistributionSet.class, rolloutRequestBody.getDistributionSetId()));
}
} }

View File

@@ -40,8 +40,12 @@ import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetMetadata; import org.eclipse.hawkbit.repository.model.DistributionSetMetadata;
import org.eclipse.hawkbit.repository.model.NamedEntity; import org.eclipse.hawkbit.repository.model.NamedEntity;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus;
import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.repository.test.util.TestdataFactory; import org.eclipse.hawkbit.repository.test.util.TestdataFactory;
import org.eclipse.hawkbit.repository.test.util.WithUser; import org.eclipse.hawkbit.repository.test.util.WithUser;
import org.eclipse.hawkbit.rest.util.JsonBuilder; import org.eclipse.hawkbit.rest.util.JsonBuilder;
@@ -1350,4 +1354,37 @@ public class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegr
assertThat(actions).size().isEqualTo(1); assertThat(actions).size().isEqualTo(1);
assertThat(actions.get(0).getWeight()).get().isEqualTo(weight); assertThat(actions.get(0).getWeight()).get().isEqualTo(weight);
} }
@Test
@Description("Verify invalidation of distribution sets that removes distribution sets from auto assignments, stops rollouts and cancels assignments")
public void invalidateDistributionSet() throws Exception {
final DistributionSet distributionSet = testdataFactory.createDistributionSet();
final List<Target> targets = testdataFactory.createTargets(5, "invalidateDistributionSet");
assignDistributionSet(distributionSet, targets);
final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement
.create(entityFactory.targetFilterQuery().create().name("invalidateDistributionSet").query("name==*")
.autoAssignDistributionSet(distributionSet));
final Rollout rollout = testdataFactory.createRolloutByVariables("invalidateDistributionSet", "desc", 2,
"name==*", distributionSet, "50", "80");
final JSONObject jsonObject = new JSONObject();
jsonObject.put("actionCancelationType", "soft");
jsonObject.put("cancelRollouts", true);
mvc.perform(post("/rest/v1/distributionsets/{ds}/invalidate", distributionSet.getId())
.content(jsonObject.toString()).contentType(MediaType.APPLICATION_JSON)).andExpect(status().isOk());
assertThat(targetFilterQueryManagement.get(targetFilterQuery.getId()).get().getAutoAssignDistributionSet())
.isNull();
assertThat(rolloutManagement.get(rollout.getId()).get().getStatus()).isIn(RolloutStatus.STOPPING,
RolloutStatus.FINISHED);
for (final Target target : targets) {
assertThat(targetManagement.get(target.getId()).get().getUpdateStatus())
.isEqualTo(TargetUpdateStatus.PENDING);
assertThat(deploymentManagement.findActionsByTarget(target.getControllerId(), PageRequest.of(0, 100))
.getNumberOfElements()).isEqualTo(1);
assertThat(deploymentManagement.findActionsByTarget(target.getControllerId(), PageRequest.of(0, 100))
.getContent().get(0).getStatus()).isEqualTo(Status.CANCELING);
}
}
} }

View File

@@ -25,9 +25,10 @@ import java.util.List;
import org.eclipse.hawkbit.exception.SpServerError; import org.eclipse.hawkbit.exception.SpServerError;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtActionType; import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtActionType;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants; import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignActionTypeException;
import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignDistributionSetException;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException; import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException;
import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignActionTypeException;
import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
@@ -294,7 +295,7 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte
assertThat(exceptionInfo.getExceptionClass()).isEqualTo(MessageNotReadableException.class.getName()); assertThat(exceptionInfo.getExceptionClass()).isEqualTo(MessageNotReadableException.class.getName());
assertThat(exceptionInfo.getErrorCode()).isEqualTo(SpServerError.SP_REST_BODY_NOT_READABLE.getKey()); assertThat(exceptionInfo.getErrorCode()).isEqualTo(SpServerError.SP_REST_BODY_NOT_READABLE.getKey());
} }
@Test @Test
@Description("Ensures that the creation of a target filter query based on an invalid RSQL query results in a HTTP Bad Request error (400).") @Description("Ensures that the creation of a target filter query based on an invalid RSQL query results in a HTTP Bad Request error (400).")
public void createTargetFilterWithInvalidQuery() throws Exception { public void createTargetFilterWithInvalidQuery() throws Exception {
@@ -324,7 +325,8 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte
post(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + filterQuery.getId() + "/autoAssignDS") post(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + filterQuery.getId() + "/autoAssignDS")
.content("{\"id\":" + set.getId() + "}").contentType(MediaType.APPLICATION_JSON)) .content("{\"id\":" + set.getId() + "}").contentType(MediaType.APPLICATION_JSON))
.andDo(print()).andExpect(status().isForbidden()) .andDo(print()).andExpect(status().isForbidden())
.andExpect(jsonPath(JSON_PATH_EXCEPTION_CLASS, equalTo(AssignmentQuotaExceededException.class.getName()))) .andExpect(
jsonPath(JSON_PATH_EXCEPTION_CLASS, equalTo(AssignmentQuotaExceededException.class.getName())))
.andExpect(jsonPath(JSON_PATH_ERROR_CODE, equalTo(SpServerError.SP_QUOTA_EXCEEDED.getKey()))); .andExpect(jsonPath(JSON_PATH_ERROR_CODE, equalTo(SpServerError.SP_QUOTA_EXCEEDED.getKey())));
} }
@@ -356,7 +358,8 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte
mvc.perform(put(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + filterQuery.getId()) mvc.perform(put(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + filterQuery.getId())
.content("{\"query\":\"controllerId==target*\"}").contentType(MediaType.APPLICATION_JSON)) .content("{\"query\":\"controllerId==target*\"}").contentType(MediaType.APPLICATION_JSON))
.andDo(print()).andExpect(status().isForbidden()) .andDo(print()).andExpect(status().isForbidden())
.andExpect(jsonPath(JSON_PATH_EXCEPTION_CLASS, equalTo(AssignmentQuotaExceededException.class.getName()))) .andExpect(
jsonPath(JSON_PATH_EXCEPTION_CLASS, equalTo(AssignmentQuotaExceededException.class.getName())))
.andExpect(jsonPath(JSON_PATH_ERROR_CODE, equalTo(SpServerError.SP_QUOTA_EXCEEDED.getKey()))); .andExpect(jsonPath(JSON_PATH_ERROR_CODE, equalTo(SpServerError.SP_QUOTA_EXCEEDED.getKey())));
} }
@@ -470,9 +473,8 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte
.content("{\"id\":" + incompleteDistributionSet.getId() + "}").contentType(MediaType.APPLICATION_JSON)) .content("{\"id\":" + incompleteDistributionSet.getId() + "}").contentType(MediaType.APPLICATION_JSON))
.andDo(print()).andExpect(status().isBadRequest()) .andDo(print()).andExpect(status().isBadRequest())
.andExpect(jsonPath(JSON_PATH_EXCEPTION_CLASS, .andExpect(jsonPath(JSON_PATH_EXCEPTION_CLASS,
equalTo(InvalidAutoAssignDistributionSetException.class.getName()))) equalTo(IncompleteDistributionSetException.class.getName())))
.andExpect(jsonPath(JSON_PATH_ERROR_CODE, .andExpect(jsonPath(JSON_PATH_ERROR_CODE, equalTo(SpServerError.SP_DS_INCOMPLETE.getKey())));
equalTo(SpServerError.SP_AUTO_ASSIGN_DISTRIBUTION_SET_INVALID.getKey())));
} }
@Step @Step
@@ -483,11 +485,9 @@ public class MgmtTargetFilterQueryResourceTest extends AbstractManagementApiInte
mvc.perform(post(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + tfq.getId() + "/autoAssignDS") mvc.perform(post(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + tfq.getId() + "/autoAssignDS")
.content("{\"id\":" + softDeletedDs.getId() + "}").contentType(MediaType.APPLICATION_JSON)) .content("{\"id\":" + softDeletedDs.getId() + "}").contentType(MediaType.APPLICATION_JSON))
.andDo(print()).andExpect(status().isBadRequest()) .andDo(print()).andExpect(status().isNotFound())
.andExpect(jsonPath(JSON_PATH_EXCEPTION_CLASS, .andExpect(jsonPath(JSON_PATH_EXCEPTION_CLASS, equalTo(EntityNotFoundException.class.getName())))
equalTo(InvalidAutoAssignDistributionSetException.class.getName()))) .andExpect(jsonPath(JSON_PATH_ERROR_CODE, equalTo(SpServerError.SP_REPO_ENTITY_NOT_EXISTS.getKey())));
.andExpect(jsonPath(JSON_PATH_ERROR_CODE,
equalTo(SpServerError.SP_AUTO_ASSIGN_DISTRIBUTION_SET_INVALID.getKey())));
} }
@Test @Test

View File

@@ -83,11 +83,13 @@ public class ResponseExceptionHandler {
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_MAINTENANCE_SCHEDULE_INVALID, HttpStatus.BAD_REQUEST); ERROR_TO_HTTP_STATUS.put(SpServerError.SP_MAINTENANCE_SCHEDULE_INVALID, HttpStatus.BAD_REQUEST);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_TARGET_ATTRIBUTES_INVALID, HttpStatus.BAD_REQUEST); ERROR_TO_HTTP_STATUS.put(SpServerError.SP_TARGET_ATTRIBUTES_INVALID, HttpStatus.BAD_REQUEST);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_AUTO_ASSIGN_ACTION_TYPE_INVALID, HttpStatus.BAD_REQUEST); ERROR_TO_HTTP_STATUS.put(SpServerError.SP_AUTO_ASSIGN_ACTION_TYPE_INVALID, HttpStatus.BAD_REQUEST);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_AUTO_ASSIGN_DISTRIBUTION_SET_INVALID, HttpStatus.BAD_REQUEST);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_CONFIGURATION_VALUE_CHANGE_NOT_ALLOWED, HttpStatus.FORBIDDEN); ERROR_TO_HTTP_STATUS.put(SpServerError.SP_CONFIGURATION_VALUE_CHANGE_NOT_ALLOWED, HttpStatus.FORBIDDEN);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_MULTIASSIGNMENT_NOT_ENABLED, HttpStatus.BAD_REQUEST); ERROR_TO_HTTP_STATUS.put(SpServerError.SP_MULTIASSIGNMENT_NOT_ENABLED, HttpStatus.BAD_REQUEST);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_NO_WEIGHT_PROVIDED_IN_MULTIASSIGNMENT_MODE, HttpStatus.BAD_REQUEST); ERROR_TO_HTTP_STATUS.put(SpServerError.SP_NO_WEIGHT_PROVIDED_IN_MULTIASSIGNMENT_MODE, HttpStatus.BAD_REQUEST);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_TARGET_TYPE_IN_USE, HttpStatus.CONFLICT); ERROR_TO_HTTP_STATUS.put(SpServerError.SP_TARGET_TYPE_IN_USE, HttpStatus.CONFLICT);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_DS_INVALID, HttpStatus.BAD_REQUEST);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_DS_INCOMPLETE, HttpStatus.BAD_REQUEST);
ERROR_TO_HTTP_STATUS.put(SpServerError.SP_STOP_ROLLOUT_FAILED, HttpStatus.LOCKED);
} }
private static HttpStatus getStatusOrDefault(final SpServerError error) { private static HttpStatus getStatusOrDefault(final SpServerError error) {

View File

@@ -830,6 +830,54 @@ include::../errors/429.adoc[]
|=== |===
== POST /rest/v1/distributionsets/{distributionSetId}/invalidate
=== Implementation Notes
Invalidate a distribution set. Once a distribution set is invalidated, it can not be valid again. An invalidated distribution set cannot be assigned to targets anymore. The distribution set that is going to be invalidated will be removed from all auto assignments. Furthermore, the user can choose to cancel all rollouts and (force) cancel all actions connected to this distribution set. Required permission: UPDATE_REPOSITORY
=== Invalidate a distribution set
==== Curl
include::{snippets}/distributionsets/invalidate/curl-request.adoc[]
==== Request URL
include::{snippets}/distributionsets/invalidate/http-request.adoc[]
==== Request path parameter
include::{snippets}/distributionsets/invalidate/path-parameters.adoc[]
==== Request fields
include::{snippets}/distributionsets/invalidate/request-fields.adoc[]
=== Response (Status 200)
==== Response example
include::{snippets}/distributionsets/invalidate/http-response.adoc[]
=== Error responses
|===
| HTTP Status Code | Reason | Response Model
include::../errors/400.adoc[]
include::../errors/401.adoc[]
include::../errors/403.adoc[]
include::../errors/404.adoc[]
include::../errors/405.adoc[]
include::../errors/406.adoc[]
include::../errors/409.adoc[]
include::../errors/415.adoc[]
include::../errors/429.adoc[]
|===
== Additional content == Additional content
[[error-body]] [[error-body]]

View File

@@ -89,8 +89,9 @@ public abstract class AbstractApiRestDocumentation extends AbstractRestIntegrati
protected String host = "management-api.host"; protected String host = "management-api.host";
/** /**
* The generated REST docs snippets will be outputted to an own resource folder. * The generated REST docs snippets will be outputted to an own resource
* The child class has to specify the name of that output folder where to put its corresponding snippets. * folder. The child class has to specify the name of that output folder
* where to put its corresponding snippets.
* *
* @return the name of the resource folder * @return the name of the resource folder
*/ */
@@ -101,8 +102,8 @@ public abstract class AbstractApiRestDocumentation extends AbstractRestIntegrati
this.document = document(getResourceName() + "/{method-name}", preprocessRequest(prettyPrint()), this.document = document(getResourceName() + "/{method-name}", preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint())); preprocessResponse(prettyPrint()));
this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext) this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
.apply(MockMvcRestDocumentation.documentationConfiguration(restDocContext).uris() .apply(MockMvcRestDocumentation.documentationConfiguration(restDocContext).uris().withScheme("https")
.withScheme("https").withHost(host + ".com").withPort(443)) .withHost(host + ".com").withPort(443))
.alwaysDo(this.document).addFilter(filterHttpResponse).build(); .alwaysDo(this.document).addFilter(filterHttpResponse).build();
arrayPrefix = "[]"; arrayPrefix = "[]";
} }
@@ -163,33 +164,36 @@ public abstract class AbstractApiRestDocumentation extends AbstractRestIntegrati
protected Target createTargetByGivenNameWithAttributes(final String name, final boolean inSync, protected Target createTargetByGivenNameWithAttributes(final String name, final boolean inSync,
final boolean timeforced, final DistributionSet distributionSet) { final boolean timeforced, final DistributionSet distributionSet) {
return createTargetByGivenNameWithAttributes(name, inSync, timeforced, distributionSet, null, null, null, false); return createTargetByGivenNameWithAttributes(name, inSync, timeforced, distributionSet, null, null, null,
false);
} }
protected Target createTargetByGivenNameWithAttributes(final String name, final boolean inSync, protected Target createTargetByGivenNameWithAttributes(final String name, final boolean inSync,
final boolean timeforced, final DistributionSet distributionSet, final boolean createRollout) { final boolean timeforced, final DistributionSet distributionSet, final boolean createRollout) {
return createTargetByGivenNameWithAttributes(name, inSync, timeforced, distributionSet, null, null, null, createRollout); return createTargetByGivenNameWithAttributes(name, inSync, timeforced, distributionSet, null, null, null,
createRollout);
} }
protected Target createTargetByGivenNameWithAttributes(final String name, final boolean inSync, protected Target createTargetByGivenNameWithAttributes(final String name, final boolean inSync,
final boolean timeforced, final DistributionSet distributionSet, final String maintenanceWindowSchedule, final boolean timeforced, final DistributionSet distributionSet, final String maintenanceWindowSchedule,
final String maintenanceWindowDuration, final String maintenanceWindowTimeZone, final boolean createRollout) { final String maintenanceWindowDuration, final String maintenanceWindowTimeZone,
final boolean createRollout) {
final Target savedTarget = targetManagement.create(entityFactory.target().create().controllerId(name) final Target savedTarget = targetManagement.create(entityFactory.target().create().controllerId(name)
.status(TargetUpdateStatus.UNKNOWN).address("http://192.168.0.1").description("My name is " + name) .status(TargetUpdateStatus.UNKNOWN).address("http://192.168.0.1").description("My name is " + name)
.lastTargetQuery(System.currentTimeMillis())); .lastTargetQuery(System.currentTimeMillis()));
final List<Target> updatedTargets; final List<Target> updatedTargets;
if (createRollout) { if (createRollout) {
final Rollout rollout = testdataFactory.createRolloutByVariables("rollout", "rollout desc", 1, final Rollout rollout = testdataFactory.createRolloutByVariables("rollout", "rollout desc", 1,
"name==" + name, distributionSet, "50", "5", timeforced ? ActionType.TIMEFORCED : ActionType.FORCED, "name==" + name, distributionSet, "50", "5", timeforced ? ActionType.TIMEFORCED : ActionType.FORCED,
isMultiAssignmentsEnabled() ? 600 : null); isMultiAssignmentsEnabled() ? 600 : null);
// start the rollout and handle it // start the rollout and handle it
rolloutManagement.start(rollout.getId()); rolloutManagement.start(rollout.getId());
rolloutManagement.handleRollouts(); rolloutManagement.handleRollouts();
updatedTargets = Collections.singletonList(savedTarget); updatedTargets = Collections.singletonList(savedTarget);
} else { } else {
@@ -208,7 +212,7 @@ public abstract class AbstractApiRestDocumentation extends AbstractRestIntegrati
updatedTargets = makeAssignment(deploymentRequestBuilder.build()).getAssignedEntity().stream() updatedTargets = makeAssignment(deploymentRequestBuilder.build()).getAssignedEntity().stream()
.map(Action::getTarget).collect(Collectors.toList()); .map(Action::getTarget).collect(Collectors.toList());
} }
if (inSync) { if (inSync) {
feedbackToByInSync(distributionSet); feedbackToByInSync(distributionSet);
} }
@@ -318,6 +322,7 @@ public abstract class AbstractApiRestDocumentation extends AbstractRestIntegrati
.description(MgmtApiModelProperties.DS_REQUIRED_STEP), .description(MgmtApiModelProperties.DS_REQUIRED_STEP),
fieldWithPath(arrayPrefix + "complete").description(MgmtApiModelProperties.DS_COMPLETE), fieldWithPath(arrayPrefix + "complete").description(MgmtApiModelProperties.DS_COMPLETE),
fieldWithPath(arrayPrefix + "deleted").description(ApiModelPropertiesGeneric.DELETED), fieldWithPath(arrayPrefix + "deleted").description(ApiModelPropertiesGeneric.DELETED),
fieldWithPath(arrayPrefix + "valid").description(MgmtApiModelProperties.DS_VALID),
fieldWithPath(arrayPrefix + "version").description(MgmtApiModelProperties.VERSION), fieldWithPath(arrayPrefix + "version").description(MgmtApiModelProperties.VERSION),
fieldWithPath(arrayPrefix + "_links.self").ignored(), fieldWithPath(arrayPrefix + "modules").ignored()); fieldWithPath(arrayPrefix + "_links.self").ignored(), fieldWithPath(arrayPrefix + "modules").ignored());

View File

@@ -67,6 +67,8 @@ public final class MgmtApiModelProperties {
public static final String DS_ALREADY_ASSIGNED_TARGETS = "Targets that had this distribution set already assigned (in \"offline\" case this includes targets that have arbitrary updates running)"; public static final String DS_ALREADY_ASSIGNED_TARGETS = "Targets that had this distribution set already assigned (in \"offline\" case this includes targets that have arbitrary updates running)";
public static final String DS_TOTAL_ASSIGNED_TARGETS = "Overall assigned as part of this request."; public static final String DS_TOTAL_ASSIGNED_TARGETS = "Overall assigned as part of this request.";
public static final String DS_ID = "Id of the distribution set."; public static final String DS_ID = "Id of the distribution set.";
public static final String DS_INVALIDATION_ACTION_CANCELATION_TYPE = "Type of cancelation for actions referring to the given distribution set.";
public static final String DS_INVALIDATION_CANCEL_ROLLOUTS = "Defines if rollouts referring to this distribution set should be canceled.";
// Target // Target
public static final String INSTALLED_AT = "Installation time of current installed DistributionSet."; public static final String INSTALLED_AT = "Installation time of current installed DistributionSet.";
@@ -184,6 +186,8 @@ public final class MgmtApiModelProperties {
public static final String DS_COMPLETE = "True of the distribution set software module setup is complete as defined by the distribution set type."; public static final String DS_COMPLETE = "True of the distribution set software module setup is complete as defined by the distribution set type.";
public static final String DS_VALID = "True by default and false after the distribution set is invalidated by the user.";
public static final String DS_TYPE_MANDATORY_MODULES = "Mandatory module type IDs."; public static final String DS_TYPE_MANDATORY_MODULES = "Mandatory module type IDs.";
public static final String DS_TYPE_OPTIONAL_MODULES = "Optional module type IDs."; public static final String DS_TYPE_OPTIONAL_MODULES = "Optional module type IDs.";

View File

@@ -379,8 +379,7 @@ public class DistributionSetsDocumentationTest extends AbstractApiRestDocumentat
parameterWithName("distributionSetId").description(ApiModelPropertiesGeneric.ITEM_ID)), parameterWithName("distributionSetId").description(ApiModelPropertiesGeneric.ITEM_ID)),
requestParameters(parameterWithName("offline") requestParameters(parameterWithName("offline")
.description(MgmtApiModelProperties.OFFLINE_UPDATE).optional()), .description(MgmtApiModelProperties.OFFLINE_UPDATE).optional()),
requestFields( requestFields(requestFieldWithPath("[].id").description(ApiModelPropertiesGeneric.ITEM_ID),
requestFieldWithPath("[].id").description(ApiModelPropertiesGeneric.ITEM_ID),
requestFieldWithPathMandatoryInMultiAssignMode("[].weight") requestFieldWithPathMandatoryInMultiAssignMode("[].weight")
.description(MgmtApiModelProperties.ASSIGNMENT_WEIGHT) .description(MgmtApiModelProperties.ASSIGNMENT_WEIGHT)
.type(JsonFieldType.NUMBER).attributes(key("value").value("0 - 1000")), .type(JsonFieldType.NUMBER).attributes(key("value").value("0 - 1000")),
@@ -395,8 +394,8 @@ public class DistributionSetsDocumentationTest extends AbstractApiRestDocumentat
optionalRequestFieldWithPath("[].maintenanceWindow.timezone") optionalRequestFieldWithPath("[].maintenanceWindow.timezone")
.description(MgmtApiModelProperties.MAINTENANCE_WINDOW_TIMEZONE), .description(MgmtApiModelProperties.MAINTENANCE_WINDOW_TIMEZONE),
optionalRequestFieldWithPath("[].type") optionalRequestFieldWithPath("[].type")
.description(MgmtApiModelProperties.ASSIGNMENT_TYPE) .description(MgmtApiModelProperties.ASSIGNMENT_TYPE).attributes(
.attributes(key("value").value("['soft', 'forced','timeforced', 'downloadonly']"))), key("value").value("['soft', 'forced','timeforced', 'downloadonly']"))),
responseFields( responseFields(
fieldWithPath("assigned").description(MgmtApiModelProperties.DS_NEW_ASSIGNED_TARGETS), fieldWithPath("assigned").description(MgmtApiModelProperties.DS_NEW_ASSIGNED_TARGETS),
fieldWithPath("alreadyAssigned").type(JsonFieldType.NUMBER) fieldWithPath("alreadyAssigned").type(JsonFieldType.NUMBER)
@@ -445,8 +444,7 @@ public class DistributionSetsDocumentationTest extends AbstractApiRestDocumentat
mockMvc.perform(delete( mockMvc.perform(delete(
MgmtRestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING MgmtRestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING
+ "/{distributionSetId}/assignedSM/{softwareModuleId}", + "/{distributionSetId}/assignedSM/{softwareModuleId}",
set.getId(), set.findFirstModuleByType(osType).get().getId()) set.getId(), set.findFirstModuleByType(osType).get().getId()).contentType(MediaType.APPLICATION_JSON))
.contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andDo(this.document.document(pathParameters( .andDo(this.document.document(pathParameters(
parameterWithName("distributionSetId").description(ApiModelPropertiesGeneric.ITEM_ID), parameterWithName("distributionSetId").description(ApiModelPropertiesGeneric.ITEM_ID),
@@ -667,4 +665,28 @@ public class DistributionSetsDocumentationTest extends AbstractApiRestDocumentat
optionalRequestFieldWithPath("[]value") optionalRequestFieldWithPath("[]value")
.description(MgmtApiModelProperties.META_DATA_VALUE)))); .description(MgmtApiModelProperties.META_DATA_VALUE))));
} }
@Test
@Description("Invalidates a distribution set. Required Permission: " + SpPermission.UPDATE_REPOSITORY)
public void invalidate() throws Exception {
final DistributionSet testDS = testdataFactory.createDistributionSet();
final JSONObject jsonObject = new JSONObject();
jsonObject.put("actionCancelationType", "soft");
jsonObject.put("cancelRollouts", true);
mockMvc.perform(post(MgmtRestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/{distributionSetId}/invalidate",
testDS.getId()).content(jsonObject.toString()).contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()).andDo(
this.document.document(
pathParameters(parameterWithName("distributionSetId")
.description(ApiModelPropertiesGeneric.ITEM_ID)),
requestFields(
requestFieldWithPath("actionCancelationType")
.description(
MgmtApiModelProperties.DS_INVALIDATION_ACTION_CANCELATION_TYPE)
.attributes(key("value").value("['force','soft','none']")),
optionalRequestFieldWithPath("cancelRollouts")
.description(MgmtApiModelProperties.DS_INVALIDATION_CANCEL_ROLLOUTS))));
}
} }

View File

@@ -27,7 +27,7 @@ import org.springframework.security.core.GrantedAuthority;
* *
* <p> * <p>
* The Permissions cover CRUD for two data areas: * The Permissions cover CRUD for two data areas:
* *
* XX_Target_CRUD which covers the following entities: {@link Target} entities * XX_Target_CRUD which covers the following entities: {@link Target} entities
* including metadata, {@link TargetTag}s, {@link TargetRegistrationRule}s * including metadata, {@link TargetTag}s, {@link TargetRegistrationRule}s
* XX_Repository CRUD which covers: {@link DistributionSet}s, * XX_Repository CRUD which covers: {@link DistributionSet}s,
@@ -157,7 +157,7 @@ public final class SpPermission {
/** /**
* Return all permission. * Return all permission.
* *
* @param exclusionRoles * @param exclusionRoles
* roles which will excluded * roles which will excluded
* @return all permissions * @return all permissions
@@ -184,10 +184,10 @@ public final class SpPermission {
* Contains all the spring security evaluation expressions for the * Contains all the spring security evaluation expressions for the
* {@link PreAuthorize} annotation for method security. * {@link PreAuthorize} annotation for method security.
* </p> * </p>
* *
* <p> * <p>
* Examples: * Examples:
* *
* {@code * {@code
* hasRole([role]) Returns true if the current principal has the specified role. * hasRole([role]) Returns true if the current principal has the specified role.
* hasAnyRole([role1,role2]) Returns true if the current principal has any of the supplied roles (given as a comma-separated list of strings) * hasAnyRole([role1,role2]) Returns true if the current principal has any of the supplied roles (given as a comma-separated list of strings)
@@ -331,6 +331,15 @@ public final class SpPermission {
public static final String HAS_AUTH_UPDATE_REPOSITORY = HAS_AUTH_PREFIX + UPDATE_REPOSITORY + HAS_AUTH_SUFFIX public static final String HAS_AUTH_UPDATE_REPOSITORY = HAS_AUTH_PREFIX + UPDATE_REPOSITORY + HAS_AUTH_SUFFIX
+ HAS_AUTH_OR + IS_SYSTEM_CODE; + HAS_AUTH_OR + IS_SYSTEM_CODE;
/**
* Spring security eval hasAuthority expression to check if spring
* context contains {@link SpPermission#READ_REPOSITORY} and
* {@link SpPermission#UPDATE_REPOSITORY} or {@link #IS_SYSTEM_CODE}.
*/
public static final String HAS_AUTH_READ_REPOSITORY_AND_UPDATE_REPOSITORY = BRACKET_OPEN + HAS_AUTH_PREFIX
+ READ_REPOSITORY + HAS_AUTH_SUFFIX + HAS_AUTH_AND + HAS_AUTH_PREFIX + UPDATE_REPOSITORY
+ HAS_AUTH_SUFFIX + BRACKET_CLOSE + HAS_AUTH_OR + IS_SYSTEM_CODE;
/** /**
* Spring security eval hasAuthority expression to check if spring * Spring security eval hasAuthority expression to check if spring
* context contains {@link SpPermission#READ_REPOSITORY} and * context contains {@link SpPermission#READ_REPOSITORY} and