Protection against misuse with system-wide quota definitions (#670)

* Added quota for meta data per software module

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

* Added unit test for "meta data per module" quota

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

* quota test enhancements

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

* Verify enforcement of meta data quota via REST

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

* Quota for distribution set meta data

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

* Verify enforcement of distribution set meta data quota via REST

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

* software modules per distribution set quota

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

* Integration test enhancements for Modules per DistSet quota

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

* Quota for software module types per distribution set type

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

* Quota for max artifacts per software module

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

* Quotas for ActionStatus per Action and Messages per ActionStatus

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

* Quota attributes per target

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

* Quota targets per rollout group

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

* Quota max targets per rollout group

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

* Quota max targets per rollout group

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

* Quota max targets per rollout group

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

* Quota max targets per rollout group

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

* Quota max targets per group

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

* quota max actions per target

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

* Quota max targets per rollout group

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

* Quota max actions per target

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

* Quota max actions per target

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

* Quota max targets per auto assignment

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

* Quota max targets per manual assignment

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

* Quota max targets per auto assignment

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

* Quota max targets per auto assign

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

* Quota max targets per auto assignment

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

* Quota max actions per target

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

* Quota max targets per manual assignment

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

* Fix issues caused by merge

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

* Fix failing tests

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

* Fix failing tests

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

* Improve JavaDoc

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

* Fix failing tests

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

* Fix Sonar issues

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

* Fix Sonar findings

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

* Quota max artifact size

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

* Optimize quota configuration

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

* Fix test failures

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

* Fix failing tests

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

* Improve test coverage

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

* Quota max rollout groups per rollout

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

* Fix failing tests

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

* Configure Rollout UI enhancements

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

* UI enhancements

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

* Minor changes

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

* Quota max targets per group

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

* Quota max targets per group

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

* fix failing tests

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

* Fix failing tests

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

* Fix Sonar findings

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

* Fix Sonar findings

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

* Fix failing tests

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

* Fix failing tests

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

* Fix Sonar finding

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

* Fix code review findings

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

* Fix review findings

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

* New approach for 'max artifact size' enforcement

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

* Fix failing tests

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

* Fix failing tests

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

* Fix failing tests

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

* Fix Sonar findings

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

* Fix failing tests

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

* Reduce max artifact size for tests

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

* Fix Kai's review findings

Signed-off-by: Stefan Behl <stefan.behl@bosch-si.com>
This commit is contained in:
Stefan Behl
2018-05-02 12:09:29 +02:00
committed by Kai Zimmermann
parent fcc15a0484
commit 6dd98d2134
63 changed files with 2383 additions and 523 deletions

View File

@@ -54,6 +54,8 @@ public interface ArtifactManagement {
* @param overrideExisting
* to <code>true</code> if the artifact binary can be overridden
* if it already exists
* @param filesize
* the size of the file in bytes.
*
* @return uploaded {@link Artifact}
*
@@ -64,7 +66,7 @@ public interface ArtifactManagement {
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_CREATE_REPOSITORY)
Artifact create(@NotNull InputStream inputStream, long moduleId, final String filename,
final boolean overrideExisting);
final boolean overrideExisting, final long filesize);
/**
* Persists artifact binary as provided by given InputStream. assign the
@@ -85,6 +87,9 @@ public interface ArtifactManagement {
* if it already exists
* @param contentType
* the contentType of the file
* @param filesize
* the size of the file in bytes.
*
* @return uploaded {@link Artifact}
*
* @throws EntityNotFoundException
@@ -100,7 +105,7 @@ public interface ArtifactManagement {
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_CREATE_REPOSITORY)
Artifact create(@NotNull InputStream stream, long moduleId, @NotEmpty String filename, String providedMd5Sum,
String providedSha1Sum, boolean overrideExisting, String contentType);
String providedSha1Sum, boolean overrideExisting, String contentType, long filesize);
/**
* Garbage collects artifact binaries if only referenced by given

View File

@@ -19,6 +19,7 @@ import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEv
import org.eclipse.hawkbit.repository.exception.CancelActionNotAllowedException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException;
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
import org.eclipse.hawkbit.repository.model.Action;
@@ -45,7 +46,7 @@ import org.springframework.security.access.prepost.PreAuthorize;
public interface DeploymentManagement {
/**
* method assigns the {@link DistributionSet} to all {@link Target}s by
* Assigns the addressed {@link DistributionSet} to all {@link Target}s by
* their IDs with a specific {@link ActionType} and {@code forcetime}.
*
* @param dsID
@@ -66,13 +67,17 @@ public interface DeploymentManagement {
* @throws EntityNotFoundException
* if either provided {@link DistributionSet} or {@link Target}s
* do not exist
*
* @throws QuotaExceededException
* if the maximum number of targets the distribution set can be
* assigned to at once is exceeded
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET)
DistributionSetAssignmentResult assignDistributionSet(long dsID, @NotNull ActionType actionType,
long forcedTimestamp, @NotEmpty Collection<String> controllerIDs);
/**
* method assigns the {@link DistributionSet} to all {@link Target}s by
* Assigns the addressed {@link DistributionSet} to all {@link Target}s by
* their IDs with a specific {@link ActionType} and {@code forcetime}.
*
* @param dsID
@@ -88,13 +93,17 @@ public interface DeploymentManagement {
* @throws EntityNotFoundException
* if either provided {@link DistributionSet} or {@link Target}s
* do not exist
*
* @throws QuotaExceededException
* if the maximum number of targets the distribution set can be
* assigned to at once is exceeded
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET)
DistributionSetAssignmentResult assignDistributionSet(long dsID,
@NotEmpty Collection<TargetWithActionType> targets);
/**
* method assigns the {@link DistributionSet} to all {@link Target}s by
* Assigns the addressed {@link DistributionSet} to all {@link Target}s by
* their IDs with a specific {@link ActionType} and an action message.
*
* @param dsID
@@ -112,16 +121,20 @@ public interface DeploymentManagement {
* @throws EntityNotFoundException
* if either provided {@link DistributionSet} or {@link Target}s
* do not exist
*
* @throws QuotaExceededException
* if the maximum number of targets the distribution set can be
* assigned to at once is exceeded
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET)
DistributionSetAssignmentResult assignDistributionSet(long dsID, @NotEmpty Collection<TargetWithActionType> targets,
String actionMessage);
/**
* Method registers an "offline" assignment, i.e. adds a completed action
* for the given {@link DistributionSet} to the given {@link Target}s.
* Registers an "offline" assignment, i.e. adds a completed action for the
* given {@link DistributionSet} to the given {@link Target}s.
*
* The handling differs to hawkBit managed updates my means that:<br/>
* The handling differs to hawkBit-managed updates by means that:<br/>
*
* <ol type="A">
* <li>it ignores targets completely that are in
@@ -145,15 +158,18 @@ public interface DeploymentManagement {
* @throws EntityNotFoundException
* if either provided {@link DistributionSet} or {@link Target}s
* do not exist
*
* @throws QuotaExceededException
* if the maximum number of targets the distribution set can be
* assigned to at once is exceeded
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET)
DistributionSetAssignmentResult offlineAssignedDistributionSet(Long dsID, Collection<String> controllerIDs);
/**
* Cancels given {@link Action} for given {@link Target}. The method will
* immediately add a {@link Status#CANCELED} status to the action. However,
* it might be possible that the controller will continue to work on the
* cancellation.
* Cancels the {@link Action} with the given ID. The method will immediately
* add a {@link Status#CANCELED} status to the action. However, it might be
* possible that the controller will continue to work on the cancellation.
*
* @param actionId
* to be canceled
@@ -170,7 +186,7 @@ public interface DeploymentManagement {
Action cancelAction(long actionId);
/**
* counts all actions associated to a specific target.
* Counts all actions associated to a specific target.
*
* @param rsqlParam
* rsql query string
@@ -202,7 +218,7 @@ public interface DeploymentManagement {
long countActionsAll();
/**
* counts all actions associated to a specific target.
* Counts all actions associated to a specific target.
*
* @param controllerId
* the target associated to the actions to count

View File

@@ -21,6 +21,7 @@ import org.eclipse.hawkbit.repository.builder.DistributionSetUpdate;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException;
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
import org.eclipse.hawkbit.repository.exception.UnsupportedSoftwareModuleForThisDistributionSetException;
@@ -52,6 +53,7 @@ public interface DistributionSetManagement
* to assign and update
* @param moduleIds
* to get assigned
*
* @return the updated {@link DistributionSet}.
*
* @throws EntityNotFoundException
@@ -62,8 +64,12 @@ public interface DistributionSetManagement
* the DS is already in use.
*
* @throws UnsupportedSoftwareModuleForThisDistributionSetException
* is {@link SoftwareModule#getType()} is not supported by this
* if {@link SoftwareModule#getType()} is not supported by this
* {@link DistributionSet#getType()}.
*
* @throws QuotaExceededException
* if the maximum number of {@link SoftwareModule}s is exceeded
* for the addressed {@link DistributionSet}.
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
DistributionSet assignSoftwareModules(long setId, @NotEmpty Collection<Long> moduleIds);
@@ -76,6 +82,7 @@ public interface DistributionSetManagement
* to assign for
* @param tagId
* to assign
*
* @return list of assigned ds
*
* @throws EntityNotFoundException
@@ -86,7 +93,7 @@ public interface DistributionSetManagement
List<DistributionSet> assignTag(@NotEmpty Collection<Long> setIds, long tagId);
/**
* creates a list of distribution set meta data entries.
* Creates a list of distribution set meta data entries.
*
* @param setId
* if the {@link DistributionSet} the metadata has to be created
@@ -97,15 +104,20 @@ public interface DistributionSetManagement
*
* @throws EntityNotFoundException
* if given set does not exist
*
* @throws EntityAlreadyExistsException
* in case one of the meta data entry already exists for the
* specific key
*
* @throws QuotaExceededException
* if the maximum number of {@link MetaData} entries is exceeded
* for the addressed {@link DistributionSet}
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
List<DistributionSetMetadata> createMetaData(long setId, @NotEmpty Collection<MetaData> metadata);
/**
* deletes a distribution set meta data entry.
* Deletes a distribution set meta data entry.
*
* @param setId
* where meta data has to be deleted
@@ -119,7 +131,7 @@ public interface DistributionSetManagement
void deleteMetaData(long setId, @NotEmpty String key);
/**
* retrieves the distribution set for a given action.
* Retrieves the distribution set for a given action.
*
* @param actionId
* the action associated with the distribution set
@@ -140,6 +152,7 @@ public interface DistributionSetManagement
*
* @param setId
* to look for.
*
* @return {@link DistributionSet}
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
@@ -152,13 +165,14 @@ public interface DistributionSetManagement
* name of {@link DistributionSet}; case insensitive
* @param version
* version of {@link DistributionSet}
*
* @return the page with the found {@link DistributionSet}
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
Optional<DistributionSet> getByNameAndVersion(@NotEmpty String distributionName, @NotEmpty String version);
/**
* finds all meta data by the given distribution set id.
* Finds all meta data by the given distribution set id.
*
* @param pageable
* the page request to page the result
@@ -175,7 +189,7 @@ public interface DistributionSetManagement
Page<DistributionSetMetadata> findMetaDataByDistributionSetId(@NotNull Pageable pageable, long setId);
/**
* finds all meta data by the given distribution set id.
* Finds all meta data by the given distribution set id.
*
* @param pageable
* the page request to page the result
@@ -190,6 +204,7 @@ public interface DistributionSetManagement
* @throws RSQLParameterUnsupportedFieldException
* if a field in the RSQL string is used but not provided by the
* given {@code fieldNameProvider}
*
* @throws RSQLParameterSyntaxException
* if the RSQL syntax is wrong
*
@@ -201,7 +216,7 @@ public interface DistributionSetManagement
@NotNull String rsqlParam);
/**
* finds all {@link DistributionSet}s.
* Finds all {@link DistributionSet}s.
*
* @param pageable
* the pagination parameter
@@ -210,14 +225,13 @@ public interface DistributionSetManagement
* sets or <code>false</code> for only incomplete ones nor
* <code>null</code> to return both.
*
*
* @return all found {@link DistributionSet}s
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
Page<DistributionSet> findByCompleted(@NotNull Pageable pageable, Boolean complete);
/**
* method retrieves all {@link DistributionSet}s from the repository in the
* Method retrieves all {@link DistributionSet}s from the repository in the
* following order:
* <p>
* 1) {@link DistributionSet}s which have the given {@link Target} as
@@ -235,6 +249,7 @@ public interface DistributionSetManagement
* has details of filters to be applied
* @param assignedOrInstalled
* the id of the Target to be ordered by
*
* @return {@link DistributionSet}s
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
@@ -242,7 +257,7 @@ public interface DistributionSetManagement
@NotNull DistributionSetFilterBuilder distributionSetFilterBuilder, @NotEmpty String assignedOrInstalled);
/**
* retrieves {@link DistributionSet}s by filtering on the given parameters.
* Retrieves {@link DistributionSet}s by filtering on the given parameters.
*
* @param pageable
* page parameter
@@ -255,7 +270,7 @@ public interface DistributionSetManagement
@NotNull DistributionSetFilter distributionSetFilter);
/**
* retrieves {@link DistributionSet}s by filtering on the given parameters.
* Retrieves {@link DistributionSet}s by filtering on the given parameters.
*
* @param pageable
* page parameter
@@ -266,6 +281,7 @@ public interface DistributionSetManagement
* @throws RSQLParameterUnsupportedFieldException
* if a field in the RSQL string is used but not provided by the
* given {@code fieldNameProvider}
*
* @throws RSQLParameterSyntaxException
* if the RSQL syntax is wrong
*
@@ -276,7 +292,7 @@ public interface DistributionSetManagement
Page<DistributionSet> findByTag(@NotNull Pageable pageable, long tagId);
/**
* retrieves {@link DistributionSet}s by filtering on the given parameters.
* Retrieves {@link DistributionSet}s by filtering on the given parameters.
*
* @param pageable
* page parameter
@@ -293,7 +309,7 @@ public interface DistributionSetManagement
Page<DistributionSet> findByRsqlAndTag(@NotNull Pageable pageable, @NotNull String rsqlParam, long tagId);
/**
* finds a single distribution set meta data by its id.
* Finds a single distribution set meta data by its id.
*
* @param setId
* of the {@link DistributionSet}
@@ -375,7 +391,7 @@ public interface DistributionSetManagement
DistributionSet unAssignTag(long setId, long tagId);
/**
* updates a distribution set meta data value if corresponding entry exists.
* Updates a distribution set meta data value if corresponding entry exists.
*
* @param setId
* {@link DistributionSet} of the meta data entry to be updated

View File

@@ -18,6 +18,7 @@ import org.eclipse.hawkbit.repository.builder.DistributionSetTypeCreate;
import org.eclipse.hawkbit.repository.builder.DistributionSetTypeUpdate;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException;
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
@@ -63,6 +64,10 @@ public interface DistributionSetTypeManagement
* @throws EntityReadOnlyException
* if the {@link DistributionSetType} while it is already in use
* by a {@link DistributionSet}
*
* @throws QuotaExceededException
* if the maximum number of {@link SoftwareModuleType}s is
* exceeded for the addressed {@link DistributionSetType}
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
DistributionSetType assignOptionalSoftwareModuleTypes(long dsTypeId,
@@ -84,6 +89,10 @@ public interface DistributionSetTypeManagement
* @throws EntityReadOnlyException
* if the {@link DistributionSetType} while it is already in use
* by a {@link DistributionSet}
*
* @throws QuotaExceededException
* if the maximum number of {@link SoftwareModuleType}s is
* exceeded for the addressed {@link DistributionSetType}
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
DistributionSetType assignMandatorySoftwareModuleTypes(long dsTypeId,

View File

@@ -20,7 +20,7 @@ import org.eclipse.hawkbit.repository.model.RolloutGroup;
public interface QuotaManagement {
/**
* @return Maximum number of {@link ActionStatus} entries that the
* @return maximum number of {@link ActionStatus} entries that the
* controller can report for an {@link Action}.
*/
int getMaxStatusEntriesPerAction();
@@ -43,4 +43,57 @@ public interface QuotaManagement {
*/
int getMaxMessagesPerActionStatus();
/**
* @return maximum number of meta data entries per software module
*/
int getMaxMetaDataEntriesPerSoftwareModule();
/**
* @return maximum number of meta data entries per distribution set
*/
int getMaxMetaDataEntriesPerDistributionSet();
/**
* @return maximum number of software modules per distribution set
*/
int getMaxSoftwareModulesPerDistributionSet();
/**
* @return the maximum number of software module types per distribution set
* type
*/
int getMaxSoftwareModuleTypesPerDistributionSetType();
/**
* @return the maximum number of artifacts per software module
*/
int getMaxArtifactsPerSoftwareModule();
/**
* @return the maximum number of targets per rollout group
*/
int getMaxTargetsPerRolloutGroup();
/**
* @return the maximum number of targets which for a manual distribution set
* assignment
*/
int getMaxTargetsPerManualAssignment();
/**
* @return the maximum number of targets for an automatic distribution set
* assignment
*/
int getMaxTargetsPerAutoAssignment();
/**
* @return the maximum number of actions per target
*/
int getMaxActionsPerTarget();
/**
* @return the maximum size of software artifacts in bytes
*/
long getMaxArtifactSize();
}

View File

@@ -22,6 +22,7 @@ import org.eclipse.hawkbit.repository.builder.RolloutGroupCreate;
import org.eclipse.hawkbit.repository.builder.RolloutUpdate;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException;
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException;
@@ -123,6 +124,9 @@ public interface RolloutManagement {
* if given {@link DistributionSet} does not exist
* @throws ConstraintViolationException
* if rollout or group parameters are invalid.
* @throws QuotaExceededException
* if the maximum number of allowed targets per rollout group is
* exceeded.
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_CREATE)
Rollout create(@NotNull RolloutCreate create, int amountGroup, @NotNull RolloutGroupConditions conditions);
@@ -157,6 +161,9 @@ public interface RolloutManagement {
* if given {@link DistributionSet} does not exist
* @throws ConstraintViolationException
* if rollout or group parameters are invalid
* @throws QuotaExceededException
* if the maximum number of allowed targets per rollout group is
* exceeded.
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_CREATE)
Rollout create(@NotNull @Valid RolloutCreate rollout, @NotNull @Valid List<RolloutGroupCreate> groups,

View File

@@ -23,6 +23,7 @@ import org.eclipse.hawkbit.repository.builder.SoftwareModuleMetadataUpdate;
import org.eclipse.hawkbit.repository.builder.SoftwareModuleUpdate;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
import org.eclipse.hawkbit.repository.model.AssignedSoftwareModule;
@@ -60,37 +61,51 @@ public interface SoftwareModuleManagement
long countByTextAndType(String searchText, Long typeId);
/**
* creates a list of software module meta data entries.
* Creates a list of software module meta data entries.
*
* @param metadata
* the meta data entries to create
*
* @return the updated or created software module meta data entries
*
* @throws EntityAlreadyExistsException
* in case one of the meta data entry already exists for the
* specific key
*
* @throws EntityNotFoundException
* if software module with given ID does not exist
*
* @throws QuotaExceededException
* if the maximum number of {@link SoftwareModuleMetadata}
* entries is exceeded for the addressed {@link SoftwareModule}
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
List<SoftwareModuleMetadata> createMetaData(@NotNull @Valid Collection<SoftwareModuleMetadataCreate> metadata);
/**
* creates or updates a single software module meta data entry.
* Creates or updates a single software module meta data entry.
*
* @param metadata
* the meta data entry to create
*
* @return the updated or created software module meta data entry
*
* @throws EntityAlreadyExistsException
* in case the meta data entry already exists for the specific
* key
*
* @throws EntityNotFoundException
* if software module with given ID does not exist
*
* @throws QuotaExceededException
* if the maximum number of {@link SoftwareModuleMetadata}
* entries is exceeded for the addressed {@link SoftwareModule}
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
SoftwareModuleMetadata createMetaData(@NotNull @Valid SoftwareModuleMetadataCreate metadata);
/**
* deletes a software module meta data entry.
* Deletes a software module meta data entry.
*
* @param moduleId
* where meta data has to be deleted
@@ -104,12 +119,13 @@ public interface SoftwareModuleManagement
void deleteMetaData(long moduleId, @NotEmpty String key);
/**
* returns all modules assigned to given {@link DistributionSet}.
* Returns all modules assigned to given {@link DistributionSet}.
*
* @param pageable
* the page request to page the result set
* @param setId
* to search for
*
* @return all {@link SoftwareModule}s that are assigned to given
* {@link DistributionSet}.
*
@@ -130,6 +146,7 @@ public interface SoftwareModuleManagement
* to be filtered as "like" on {@link SoftwareModule#getName()}
* @param typeId
* to be filtered as "like" on {@link SoftwareModule#getType()}
*
* @return the page of found {@link SoftwareModule}
*
* @throws EntityNotFoundException
@@ -139,7 +156,7 @@ public interface SoftwareModuleManagement
Slice<SoftwareModule> findByTextAndType(@NotNull Pageable pageable, String searchText, Long typeId);
/**
* retrieves {@link SoftwareModule} by their name AND version AND type..
* Retrieves {@link SoftwareModule} by their name AND version AND type..
*
* @param name
* of the {@link SoftwareModule}
@@ -147,6 +164,7 @@ public interface SoftwareModuleManagement
* of the {@link SoftwareModule}
* @param typeId
* of the {@link SoftwareModule}
*
* @return the found {@link SoftwareModule}
*
* @throws EntityNotFoundException
@@ -156,7 +174,7 @@ public interface SoftwareModuleManagement
Optional<SoftwareModule> getByNameAndVersionAndType(@NotEmpty String name, @NotEmpty String version, long typeId);
/**
* finds a single software module meta data by its id.
* Finds a single software module meta data by its id.
*
* @param moduleId
* where meta data has to be found
@@ -171,7 +189,7 @@ public interface SoftwareModuleManagement
Optional<SoftwareModuleMetadata> getMetaDataBySoftwareModuleId(long moduleId, @NotEmpty String key);
/**
* finds all meta data by the given software module id.
* Finds all meta data by the given software module id.
*
* @param pageable
* the page request to page the result
@@ -188,7 +206,7 @@ public interface SoftwareModuleManagement
Page<SoftwareModuleMetadata> findMetaDataBySoftwareModuleId(@NotNull Pageable pageable, long moduleId);
/**
* finds all meta data by the given software module id where
* Finds all meta data by the given software module id where
* {@link SoftwareModuleMetadata#isTargetVisible()}.
*
* @param pageable
@@ -207,7 +225,7 @@ public interface SoftwareModuleManagement
long moduleId);
/**
* finds all meta data by the given software module id.
* Finds all meta data by the given software module id.
*
* @param pageable
* the page request to page the result
@@ -222,8 +240,10 @@ public interface SoftwareModuleManagement
* @throws RSQLParameterUnsupportedFieldException
* if a field in the RSQL string is used but not provided by the
* given {@code fieldNameProvider}
*
* @throws RSQLParameterSyntaxException
* if the RSQL syntax is wrong
*
* @throws EntityNotFoundException
* if software module with given ID does not exist
*/
@@ -249,6 +269,7 @@ public interface SoftwareModuleManagement
* filtered as "like" on {@link SoftwareModule#getName()}
* @param typeId
* filtered as "equal" on {@link SoftwareModule#getType()}
*
* @return the page of found {@link SoftwareModule}
*
* @throws EntityNotFoundException
@@ -259,13 +280,14 @@ public interface SoftwareModuleManagement
@NotNull Pageable pageable, long orderByDistributionId, String searchText, Long typeId);
/**
* retrieves the {@link SoftwareModule}s by their {@link SoftwareModuleType}
* Retrieves the {@link SoftwareModule}s by their {@link SoftwareModuleType}
* .
*
* @param pageable
* page parameters
* @param typeId
* to be filtered on
*
* @return the found {@link SoftwareModule}s
*
* @throws EntityNotFoundException
@@ -275,7 +297,7 @@ public interface SoftwareModuleManagement
Slice<SoftwareModule> findByType(@NotNull Pageable pageable, long typeId);
/**
* updates a distribution set meta data value if corresponding entry exists.
* Updates a distribution set meta data value if corresponding entry exists.
*
* @param update
* the meta data entry to be updated

View File

@@ -18,8 +18,10 @@ import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
import org.eclipse.hawkbit.repository.builder.TargetFilterQueryCreate;
import org.eclipse.hawkbit.repository.builder.TargetFilterQueryUpdate;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
@@ -32,20 +34,26 @@ import org.springframework.security.access.prepost.PreAuthorize;
public interface TargetFilterQueryManagement {
/**
* creating new {@link TargetFilterQuery}.
* Creates a new {@link TargetFilterQuery}.
*
* @param create
* to create
* @return the created {@link TargetFilterQuery}
*
* @return the new {@link TargetFilterQuery}
*
* @throws ConstraintViolationException
* if fields are not filled as specified. Check
* {@link TargetFilterQueryCreate} for field constraints.
*
* @throws QuotaExceededException
* if the maximum number of targets that is addressed by the
* given query is exceeded (auto-assignments only)
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_CREATE_TARGET)
TargetFilterQuery create(@NotNull @Valid TargetFilterQueryCreate create);
/**
* Delete target filter query.
* Deletes the {@link TargetFilterQuery} with the given ID.
*
* @param targetFilterQueryId
* IDs of target filter query to be deleted
@@ -57,7 +65,7 @@ public interface TargetFilterQueryManagement {
void delete(long targetFilterQueryId);
/**
* Verifies provided filter syntax.
* Verifies the provided filter syntax.
*
* @param query
* to verify
@@ -67,6 +75,7 @@ public interface TargetFilterQueryManagement {
* @throws RSQLParameterUnsupportedFieldException
* if a field in the RSQL string is used but not provided by the
* given {@code fieldNameProvider}
*
* @throws RSQLParameterSyntaxException
* if the RSQL syntax is wrong
*/
@@ -75,7 +84,7 @@ public interface TargetFilterQueryManagement {
/**
*
* Retrieves all target filter query{@link TargetFilterQuery}.
* Retrieves all {@link TargetFilterQuery}s.
*
* @param pageable
* pagination parameter
@@ -85,7 +94,7 @@ public interface TargetFilterQueryManagement {
Page<TargetFilterQuery> findAll(@NotNull Pageable pageable);
/**
* Counts all target filter queries
* Counts all {@link TargetFilterQuery}s.
*
* @return the number of all target filter queries
*/
@@ -93,46 +102,46 @@ public interface TargetFilterQueryManagement {
long count();
/**
* Retrieves all target filter query which {@link TargetFilterQuery}.
*
* Retrieves all {@link TargetFilterQuery}s which match the given name
* filter.
*
* @param pageable
* pagination parameter
* @param name
* name filter
* @return the page with the found {@link TargetFilterQuery}
* @return the page with the found {@link TargetFilterQuery}s
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
Page<TargetFilterQuery> findByName(@NotNull Pageable pageable, @NotNull String name);
/**
* Retrieves all target filter query which {@link TargetFilterQuery}.
*
* Retrieves all {@link TargetFilterQuery} which match the given RSQL
* filter.
*
* @param pageable
* pagination parameter
* @param rsqlFilter
* RSQL filter string
* @return the page with the found {@link TargetFilterQuery}
* @return the page with the found {@link TargetFilterQuery}s
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
Page<TargetFilterQuery> findByRsql(@NotNull Pageable pageable, @NotNull String rsqlFilter);
/**
* Retrieves all target filter query which have exactly the provided query.
* Retrieves all {@link TargetFilterQuery}s which match the given query.
*
* @param pageable
* pagination parameter
* @param query
* the query saved in the target filter query
* @return the page with the found {@link TargetFilterQuery}
* @return the page with the found {@link TargetFilterQuery}s
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
Page<TargetFilterQuery> findByQuery(@NotNull Pageable pageable, @NotNull String query);
/**
* Retrieves all target filter query which {@link TargetFilterQuery}.
*
* Retrieves all {@link TargetFilterQuery}s which match the given
* auto-assign distribution set and RSQL filter.
*
* @param pageable
* pagination parameter
@@ -140,28 +149,26 @@ public interface TargetFilterQueryManagement {
* the auto assign distribution set
* @param rsqlParam
* RSQL filter
* @return the page with the found {@link TargetFilterQuery}
* @return the page with the found {@link TargetFilterQuery}s
*
* @throws EntityNotFoundException
* if DS with given ID does not exist
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
Page<TargetFilterQuery> findByAutoAssignDSAndRsql(@NotNull Pageable pageable, long setId,
String rsqlParam);
Page<TargetFilterQuery> findByAutoAssignDSAndRsql(@NotNull Pageable pageable, long setId, String rsqlParam);
/**
* Retrieves all target filter query with auto assign DS which
* {@link TargetFilterQuery}.
* Retrieves all {@link TargetFilterQuery}s with an auto-assign distribution
* set.
*
*
* @return the page with the found {@link TargetFilterQuery}
* @return the page with the found {@link TargetFilterQuery}s
* @param pageable
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
Page<TargetFilterQuery> findWithAutoAssignDS(@NotNull Pageable pageable);
/**
* Find target filter query by id.
* Finds the {@link TargetFilterQuery} by id.
*
* @param targetFilterQueryId
* Target filter query id
@@ -172,7 +179,7 @@ public interface TargetFilterQueryManagement {
Optional<TargetFilterQuery> get(long targetFilterQueryId);
/**
* Find target filter query by name.
* Finds the {@link TargetFilterQuery} that matches the given name.
*
* @param targetFilterQueryName
* Target filter query name
@@ -183,7 +190,7 @@ public interface TargetFilterQueryManagement {
Optional<TargetFilterQuery> getByName(@NotNull String targetFilterQueryName);
/**
* updates the {@link TargetFilterQuery}.
* Updates the {@link TargetFilterQuery}.
*
* @param update
* to be updated
@@ -193,25 +200,36 @@ public interface TargetFilterQueryManagement {
* @throws EntityNotFoundException
* if either {@link TargetFilterQuery} and/or autoAssignDs are
* provided but not found
*
* @throws ConstraintViolationException
* if fields are not filled as specified. Check
* {@link TargetFilterQueryUpdate} for field constraints.
*
* @throws QuotaExceededException
* if the update contains a new query which addresses too many
* targets (auto-assignments only)
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET)
TargetFilterQuery update(@NotNull @Valid TargetFilterQueryUpdate update);
/**
* updates the {@link TargetFilterQuery#getAutoAssignDistributionSet()}.
* Updates the the auto-assign {@link DistributionSet} of the addressed
* {@link TargetFilterQuery}.
*
* @param queryId
* to be updated
* of the target filter query to be updated
* @param dsId
* to be updated or <code>null</code> in order to remove it
*
* @return the updated {@link TargetFilterQuery}
*
* @throws EntityNotFoundException
* if either {@link TargetFilterQuery} and/or autoAssignDs are
* provided but not found
*
* @throws QuotaExceededException
* if the query that is already associated with this filter
* query addresses too many targets (auto-assignments only)
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET)
TargetFilterQuery updateAutoAssignDS(long queryId, Long dsId);

View File

@@ -17,8 +17,11 @@ import org.eclipse.hawkbit.repository.model.BaseEntity;
*
*/
public final class QuotaExceededException extends AbstractServerRtException {
private static final long serialVersionUID = 1L;
private static final String ASSIGNMENT_QUOTA_EXCEEDED_MESSAGE = "Quota exceeded: Cannot assign %s more %s entities to %s '%s'. The maximum is %s.";
/**
* Creates a new QuotaExceededException with
* {@link SpServerError#SP_QUOTA_EXCEEDED} error.
@@ -28,11 +31,26 @@ public final class QuotaExceededException extends AbstractServerRtException {
}
/**
* Creates a new QuotaExceededException with a custom error message.
*
* @param message
* The custom error message.
*/
public QuotaExceededException(final String message) {
super(message, SpServerError.SP_QUOTA_EXCEEDED);
}
/**
* Creates a QuotaExceededException with a custom error message and a root
* cause.
*
* @param message
* The custom error message.
* @param cause
* for the exception
*/
public QuotaExceededException(final Throwable cause) {
super(SpServerError.SP_QUOTA_EXCEEDED, cause);
public QuotaExceededException(final String message, final Throwable cause) {
super(message, SpServerError.SP_QUOTA_EXCEEDED, cause);
}
/**
@@ -57,7 +75,55 @@ public final class QuotaExceededException extends AbstractServerRtException {
* that is defined by the repository
*/
public QuotaExceededException(final String type, final long inserted, final int quota) {
super("Request contains too many entries of {" + type + "}. {" + inserted + "} is bejond the permitted {"
super("Request contains too many entries of {" + type + "}. {" + inserted + "} is beyond the permitted {"
+ quota + "}.", SpServerError.SP_QUOTA_EXCEEDED);
}
/**
* Creates a QuotaExceededException which is to be thrown when an assignment
* quota is exceeded.
*
* @param type
* The type of the entities that shall be assigned to the
* specified parent entity.
* @param parentType
* The type of the parent entity.
* @param parentId
* The ID of the parent entity.
* @param requested
* The number of entities that shall be assigned to the specified
* parent entity.
* @param quota
* The maximum number of entities that can be assigned to the
* parent entity.
*/
public QuotaExceededException(final Class<?> type, final Class<?> parentType, final Long parentId,
final long requested, final long quota) {
this(type.getSimpleName(), parentType.getSimpleName(), parentId, requested, quota);
}
/**
* Creates a QuotaExceededException which is to be thrown when an assignment
* quota is exceeded.
*
* @param type
* The type of the entities that shall be assigned to the
* specified parent entity.
* @param parentType
* The type of the parent entity.
* @param parentId
* The ID of the parent entity.
* @param requested
* The number of entities that shall be assigned to the specified
* parent entity.
* @param quota
* The maximum number of entities that can be assigned to the
* parent entity.
*/
public QuotaExceededException(final String type, final String parentType, final Long parentId, final long requested,
final long quota) {
super(String.format(ASSIGNMENT_QUOTA_EXCEEDED_MESSAGE, requested, type, parentType,
parentId != null ? String.valueOf(parentId) : "<new>", quota), SpServerError.SP_QUOTA_EXCEEDED);
}
}

View File

@@ -21,12 +21,12 @@ public class RolloutGroupsValidation {
/**
* The total amount of targets in a {@link Rollout}
*/
private long totalTargets;
private final long totalTargets;
/**
* A list containing the count of targets for each {@link RolloutGroup}
*/
private List<Long> targetsPerGroup;
private final List<Long> targetsPerGroup;
/**
* Instantiates a new validation result

View File

@@ -28,8 +28,8 @@ public final class DurationHelper {
*
*/
public static final class DurationRangeValidator {
final Duration min;
final Duration max;
private final Duration min;
private final Duration max;
private DurationRangeValidator(final Duration min, final Duration max) {
this.min = min;

View File

@@ -48,4 +48,54 @@ public class PropertiesQuotaManagement implements QuotaManagement {
return securityProperties.getDos().getMaxMessagesPerActionStatus();
}
@Override
public int getMaxMetaDataEntriesPerSoftwareModule() {
return securityProperties.getDos().getMaxMetaDataEntriesPerSoftwareModule();
}
@Override
public int getMaxMetaDataEntriesPerDistributionSet() {
return securityProperties.getDos().getMaxMetaDataEntriesPerDistributionSet();
}
@Override
public int getMaxSoftwareModulesPerDistributionSet() {
return securityProperties.getDos().getMaxSoftwareModulesPerDistributionSet();
}
@Override
public int getMaxSoftwareModuleTypesPerDistributionSetType() {
return securityProperties.getDos().getMaxSoftwareModuleTypesPerDistributionSetType();
}
@Override
public int getMaxArtifactsPerSoftwareModule() {
return securityProperties.getDos().getMaxArtifactsPerSoftwareModule();
}
@Override
public int getMaxTargetsPerRolloutGroup() {
return securityProperties.getDos().getMaxTargetsPerRolloutGroup();
}
@Override
public int getMaxActionsPerTarget() {
return securityProperties.getDos().getMaxActionsPerTarget();
}
@Override
public int getMaxTargetsPerManualAssignment() {
return securityProperties.getDos().getMaxTargetsPerManualAssignment();
}
@Override
public int getMaxTargetsPerAutoAssignment() {
return securityProperties.getDos().getMaxTargetsPerAutoAssignment();
}
@Override
public long getMaxArtifactSize() {
return securityProperties.getDos().getMaxArtifactSize();
}
}

View File

@@ -14,6 +14,7 @@ import java.util.stream.Collectors;
import javax.validation.ValidationException;
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
@@ -75,9 +76,11 @@ public final class RolloutHelper {
*/
public static void verifyRolloutGroupParameter(final int amountGroup, final QuotaManagement quotaManagement) {
if (amountGroup <= 0) {
throw new ValidationException("the amount of groups cannot be lower than zero");
throw new ValidationException("The amount of groups cannot be lower than zero");
} else if (amountGroup > quotaManagement.getMaxRolloutGroupsPerRollout()) {
throw new ValidationException("the amount of groups cannot be greater than 500");
throw new QuotaExceededException(
"The amount of groups cannot be greater than " + quotaManagement.getMaxRolloutGroupsPerRollout());
}
}
@@ -89,9 +92,9 @@ public final class RolloutHelper {
*/
public static void verifyRolloutGroupTargetPercentage(final float percentage) {
if (percentage <= 0) {
throw new ValidationException("the percentage must be greater than zero");
throw new ValidationException("The percentage must be greater than zero");
} else if (percentage > 100) {
throw new ValidationException("the percentage must not be greater than 100");
throw new ValidationException("The percentage must not be greater than 100");
}
}

View File

@@ -14,6 +14,7 @@ import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.RepositoryConstants;
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.CancelTargetAssignmentEvent;
@@ -24,6 +25,7 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.DistributionSet;
@@ -46,17 +48,19 @@ public abstract class AbstractDsAssignmentStrategy {
protected final ApplicationContext applicationContext;
private final ActionRepository actionRepository;
private final ActionStatusRepository actionStatusRepository;
private final QuotaManagement quotaManagement;
AbstractDsAssignmentStrategy(final TargetRepository targetRepository,
final AfterTransactionCommitExecutor afterCommit, final ApplicationEventPublisher eventPublisher,
final ApplicationContext applicationContext, final ActionRepository actionRepository,
final ActionStatusRepository actionStatusRepository) {
final ActionStatusRepository actionStatusRepository, final QuotaManagement quotaManagement) {
this.targetRepository = targetRepository;
this.afterCommit = afterCommit;
this.eventPublisher = eventPublisher;
this.applicationContext = applicationContext;
this.actionRepository = actionRepository;
this.actionStatusRepository = actionStatusRepository;
this.quotaManagement = quotaManagement;
}
/**
@@ -213,6 +217,11 @@ public abstract class AbstractDsAssignmentStrategy {
JpaAction createTargetAction(final Map<String, TargetWithActionType> targetsWithActionMap, final JpaTarget target,
final JpaDistributionSet set) {
// enforce the 'max actions per target' quota
assertActionsPerTargetQuota(target, 1);
// create the action
final JpaAction actionForTarget = new JpaAction();
final TargetWithActionType targetWithActionType = targetsWithActionMap.get(target.getControllerId());
actionForTarget.setActionType(targetWithActionType.getActionType());
@@ -237,4 +246,11 @@ public abstract class AbstractDsAssignmentStrategy {
return actionStatus;
}
private void assertActionsPerTargetQuota(final Target target, final int requested) {
final int quota = quotaManagement.getMaxActionsPerTarget();
QuotaHelper.assertAssignmentQuota(target.getId(), requested, quota, Action.class, Target.class,
actionRepository::countByTargetId);
}
}

View File

@@ -39,6 +39,16 @@ public interface ActionStatusRepository
*/
Long countByAction(JpaAction action);
/**
* Counts {@link ActionStatus} entries of given {@link Action} in
* repository.
*
* @param actionId
* of the action to count status entries for
* @return number of actions in repository
*/
long countByActionId(Long actionId);
/**
* Retrieves all {@link ActionStatus} entries from repository of given
* ActionId.

View File

@@ -13,6 +13,7 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetMetadata;
import org.eclipse.hawkbit.repository.model.DistributionSetMetadata;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Transactional;
/**
@@ -23,4 +24,14 @@ public interface DistributionSetMetadataRepository
extends PagingAndSortingRepository<JpaDistributionSetMetadata, DsMetadataCompositeKey>,
JpaSpecificationExecutor<JpaDistributionSetMetadata> {
/**
* Counts the meta data entries that match the given distribution set ID.
*
* @param id
* of the distribution set.
*
* @return The number of matching meta data entries.
*/
long countByDistributionSetId(@Param("id") Long id);
}

View File

@@ -15,6 +15,7 @@ import javax.persistence.EntityManager;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleType;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
@@ -40,6 +41,7 @@ public interface DistributionSetTypeRepository
* @param isDeleted
* to <code>true</code> if only soft deleted entries of
* <code>false</code> if undeleted ones
*
* @return list of found {@link DistributionSetType}s
*/
Page<JpaDistributionSetType> findByDeleted(Pageable pageable, boolean isDeleted);
@@ -47,7 +49,8 @@ public interface DistributionSetTypeRepository
/**
* @param isDeleted
* to <code>true</code> if only marked as deleted have to be
* count or all undeleted.
* counted or all undeleted.
*
* @return number of {@link DistributionSetType}s in the repository.
*/
long countByDeleted(boolean isDeleted);
@@ -59,6 +62,7 @@ public interface DistributionSetTypeRepository
* @param softwareModuleType
* the software module type to count the distribution set type
* which has this software module type assigned
*
* @return the number of {@link DistributionSetType}s in the repository
* assigned to the given software module type
*/
@@ -78,8 +82,29 @@ public interface DistributionSetTypeRepository
@Query("DELETE FROM JpaDistributionSetType t WHERE t.tenant = :tenant")
void deleteByTenant(@Param("tenant") String tenant);
/**
* Retrieves the {@link DistributionSetType}s for the given IDs. Workaround
* for https://bugs.eclipse.org/bugs/show_bug.cgi?id=349477
*
* @param ids
* of the types to be located
*
* @return a list of distribution set types
*/
@Override
// Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=349477
@Query("SELECT d FROM JpaDistributionSetType d WHERE d.id IN ?1")
List<JpaDistributionSetType> findAll(Iterable<Long> ids);
/**
* Counts the {@link SoftwareModuleType}s which are associated with the
* addressed {@link DistributionSetType}.
*
* @param id
* of the distribution set type
*
* @return the number of associated software module types
*/
@Query("SELECT COUNT (e.smType) FROM DistributionSetTypeElement e WHERE e.dsType.id = :id")
long countSmTypesById(@Param("id") Long id);
}

View File

@@ -17,15 +17,18 @@ import org.eclipse.hawkbit.artifact.repository.HashNotMatchException;
import org.eclipse.hawkbit.artifact.repository.model.AbstractDbArtifact;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash;
import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.exception.ArtifactDeleteFailedException;
import org.eclipse.hawkbit.repository.exception.ArtifactUploadFailedException;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.InvalidMD5HashException;
import org.eclipse.hawkbit.repository.exception.InvalidSHA1HashException;
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
import org.eclipse.hawkbit.repository.jpa.model.JpaArtifact;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule;
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.tenancy.TenantAware;
@@ -49,6 +52,8 @@ public class JpaArtifactManagement implements ArtifactManagement {
private static final Logger LOG = LoggerFactory.getLogger(JpaArtifactManagement.class);
private static final String MAX_ARTIFACT_SIZE_EXCEEDED = "Quota exceeded: The artifact '%s' (%s bytes) which has been uploaded for software module '%s' exceeds the maximum artifact size of %s bytes.";
private final LocalArtifactRepository localArtifactRepository;
private final SoftwareModuleRepository softwareModuleRepository;
@@ -57,12 +62,15 @@ public class JpaArtifactManagement implements ArtifactManagement {
private final TenantAware tenantAware;
private final QuotaManagement quotaManagement;
JpaArtifactManagement(final LocalArtifactRepository localArtifactRepository,
final SoftwareModuleRepository softwareModuleRepository, final ArtifactRepository artifactRepository,
final TenantAware tenantAware) {
final QuotaManagement quotaManagement, final TenantAware tenantAware) {
this.localArtifactRepository = localArtifactRepository;
this.softwareModuleRepository = softwareModuleRepository;
this.artifactRepository = artifactRepository;
this.quotaManagement = quotaManagement;
this.tenantAware = tenantAware;
}
@@ -87,13 +95,16 @@ public class JpaArtifactManagement implements ArtifactManagement {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public Artifact create(final InputStream stream, final long moduleId, final String filename,
final String providedMd5Sum, final String providedSha1Sum, final boolean overrideExisting,
final String contentType) {
final String contentType, final long filesize) {
AbstractDbArtifact result = null;
final SoftwareModule softwareModule = getModuleAndThrowExceptionIfThatFails(moduleId);
final Artifact existing = checkForExistingArtifact(filename, overrideExisting, softwareModule);
assertArtifactQuota(moduleId, 1);
assertMaxArtifactSizeQuota(filename, moduleId, filesize);
try {
result = artifactRepository.store(tenantAware.getCurrentTenant(), stream, filename, contentType,
new DbArtifactHash(providedSha1Sum, providedMd5Sum));
@@ -113,6 +124,23 @@ public class JpaArtifactManagement implements ArtifactManagement {
return storeArtifactMetadata(softwareModule, filename, result, existing);
}
private void assertArtifactQuota(final long id, final int requested) {
QuotaHelper.assertAssignmentQuota(id, requested, quotaManagement.getMaxArtifactsPerSoftwareModule(),
Artifact.class, SoftwareModule.class, localArtifactRepository::countBySoftwareModuleId);
}
private void assertMaxArtifactSizeQuota(final String filename, final long id, final long artifactSize) {
final long maxArtifactSize = quotaManagement.getMaxArtifactSize();
if (maxArtifactSize <= 0) {
return;
}
if (artifactSize > maxArtifactSize) {
final String msg = String.format(MAX_ARTIFACT_SIZE_EXCEEDED, filename, artifactSize, id, maxArtifactSize);
LOG.warn(msg);
throw new QuotaExceededException(msg);
}
}
@Override
@Transactional
@Retryable(include = {
@@ -207,8 +235,8 @@ public class JpaArtifactManagement implements ArtifactManagement {
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public Artifact create(final InputStream inputStream, final long moduleId, final String filename,
final boolean overrideExisting) {
return create(inputStream, moduleId, filename, null, null, overrideExisting, null);
final boolean overrideExisting, final long filesize) {
return create(inputStream, moduleId, filename, null, null, overrideExisting, null, filesize);
}
@Override

View File

@@ -44,7 +44,6 @@ import org.eclipse.hawkbit.repository.event.remote.TargetPollEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.CancelTargetAssignmentEvent;
import org.eclipse.hawkbit.repository.exception.CancelActionNotAllowedException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
import org.eclipse.hawkbit.repository.jpa.builder.JpaActionStatusCreate;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor;
@@ -56,6 +55,7 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_;
import org.eclipse.hawkbit.repository.jpa.specifications.ActionSpecifications;
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.ActionStatus;
@@ -535,8 +535,8 @@ public class JpaControllerManagement implements ControllerManagement {
break;
default:
// information status entry - check for a potential DOS attack
checkForTooManyStatusEntries(action);
checkForTooManyStatusMessages(actionStatus);
assertActionStatusQuota(action);
assertActionStatusMessageQuota(actionStatus);
break;
}
@@ -546,12 +546,9 @@ public class JpaControllerManagement implements ControllerManagement {
return action;
}
private void checkForTooManyStatusMessages(final JpaActionStatus actionStatus) {
if (actionStatus.getMessages().size() > quotaManagement.getMaxMessagesPerActionStatus()) {
throw new QuotaExceededException("ActionStatus messages", actionStatus.getMessages().size(),
quotaManagement.getMaxStatusEntriesPerAction());
}
private void assertActionStatusMessageQuota(final JpaActionStatus actionStatus) {
QuotaHelper.assertAssignmentQuota(actionStatus.getId(), actionStatus.getMessages().size(),
quotaManagement.getMaxMessagesPerActionStatus(), "Message", ActionStatus.class.getSimpleName(), null);
}
private void handleFinishedCancelation(final JpaActionStatus actionStatus, final JpaAction action) {
@@ -607,8 +604,8 @@ public class JpaControllerManagement implements ControllerManagement {
break;
default:
// information status entry - check for a potential DOS attack
checkForTooManyStatusEntries(action);
checkForTooManyStatusMessages(actionStatus);
assertActionStatusQuota(action);
assertActionStatusMessageQuota(actionStatus);
break;
}
@@ -628,16 +625,9 @@ public class JpaControllerManagement implements ControllerManagement {
targetRepository.save(mergedTarget);
}
private void checkForTooManyStatusEntries(final JpaAction action) {
if (quotaManagement.getMaxStatusEntriesPerAction() > 0) {
final Long statusCount = actionStatusRepository.countByAction(action);
if (statusCount >= quotaManagement.getMaxStatusEntriesPerAction()) {
throw new QuotaExceededException(ActionStatus.class, statusCount + 1,
quotaManagement.getMaxStatusEntriesPerAction());
}
}
private void assertActionStatusQuota(final JpaAction action) {
QuotaHelper.assertAssignmentQuota(action.getId(), 1, quotaManagement.getMaxStatusEntriesPerAction(),
ActionStatus.class, Action.class, actionStatusRepository::countByActionId);
}
private void handleFinishedAndStoreInTargetStatus(final JpaAction action) {
@@ -694,16 +684,17 @@ public class JpaControllerManagement implements ControllerManagement {
// unknown update mode
throw new IllegalStateException("The update mode " + updateMode + " is not supported.");
}
final int attributeCount = controllerAttributes.size();
if (attributeCount > quotaManagement.getMaxAttributeEntriesPerTarget()) {
throw new QuotaExceededException("Controller attributes", attributeCount,
quotaManagement.getMaxAttributeEntriesPerTarget());
}
assertTargetAttributesQuota(target);
return targetRepository.save(target);
}
private void assertTargetAttributesQuota(final JpaTarget target) {
final int limit = quotaManagement.getMaxAttributeEntriesPerTarget();
QuotaHelper.assertAssignmentQuota(target.getId(), target.getControllerAttributes().size(), limit, "Attribute",
Target.class.getSimpleName(), null);
}
@Override
@Transactional
@Retryable(include = {
@@ -775,8 +766,8 @@ public class JpaControllerManagement implements ControllerManagement {
final JpaActionStatus statusMessage = create.build();
statusMessage.setAction(action);
checkForTooManyStatusEntries(action);
checkForTooManyStatusMessages(statusMessage);
assertActionStatusQuota(action);
assertActionStatusMessageQuota(statusMessage);
return actionStatusRepository.save(statusMessage);
}

View File

@@ -28,6 +28,7 @@ import javax.persistence.criteria.Root;
import org.eclipse.hawkbit.repository.ActionFields;
import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.RepositoryConstants;
import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
@@ -46,6 +47,7 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_;
import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility;
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.Action.Status;
@@ -115,6 +117,7 @@ public class JpaDeploymentManagement implements DeploymentManagement {
private final OnlineDsAssignmentStrategy onlineDsAssignmentStrategy;
private final OfflineDsAssignmentStrategy offlineDsAssignmentStrategy;
private final TenantConfigurationManagement tenantConfigurationManagement;
private final QuotaManagement quotaManagement;
private final SystemSecurityContext systemSecurityContext;
private final Database database;
@@ -124,7 +127,7 @@ public class JpaDeploymentManagement implements DeploymentManagement {
final AuditorAware<String> auditorProvider, final ApplicationEventPublisher eventPublisher,
final ApplicationContext applicationContext, final AfterTransactionCommitExecutor afterCommit,
final VirtualPropertyReplacer virtualPropertyReplacer, final PlatformTransactionManager txManager,
final TenantConfigurationManagement tenantConfigurationManagement,
final TenantConfigurationManagement tenantConfigurationManagement, final QuotaManagement quotaManagement,
final SystemSecurityContext systemSecurityContext, final Database database) {
this.entityManager = entityManager;
this.actionRepository = actionRepository;
@@ -139,10 +142,11 @@ public class JpaDeploymentManagement implements DeploymentManagement {
this.virtualPropertyReplacer = virtualPropertyReplacer;
this.txManager = txManager;
onlineDsAssignmentStrategy = new OnlineDsAssignmentStrategy(targetRepository, afterCommit, eventPublisher,
applicationContext, actionRepository, actionStatusRepository);
applicationContext, actionRepository, actionStatusRepository, quotaManagement);
offlineDsAssignmentStrategy = new OfflineDsAssignmentStrategy(targetRepository, afterCommit, eventPublisher,
applicationContext, actionRepository, actionStatusRepository);
applicationContext, actionRepository, actionStatusRepository, quotaManagement);
this.tenantConfigurationManagement = tenantConfigurationManagement;
this.quotaManagement = quotaManagement;
this.systemSecurityContext = systemSecurityContext;
this.database = database;
}
@@ -215,8 +219,8 @@ public class JpaDeploymentManagement implements DeploymentManagement {
* a list of all targets and their action type
* @param actionMessage
* an optional message to be written into the action status
* @param offline
* to <code>true</code> in offline case
* @param assignmentStrategy
* the assignment strategy (online /offline)
* @return the assignment result
*
* @throw IncompleteDistributionSetException if mandatory
@@ -240,6 +244,11 @@ public class JpaDeploymentManagement implements DeploymentManagement {
final List<String> controllerIDs = targetsWithActionType.stream().map(TargetWithActionType::getControllerId)
.collect(Collectors.toList());
// enforce the 'max targets per manual assignment' quota
if (!controllerIDs.isEmpty()) {
assertMaxTargetsPerManualAssignmentQuota(set.getId(), controllerIDs.size());
}
LOG.debug("assignDistribution({}) to {} targets", set, controllerIDs.size());
final Map<String, TargetWithActionType> targetsWithActionMap = targetsWithActionType.stream()
@@ -318,6 +327,20 @@ public class JpaDeploymentManagement implements DeploymentManagement {
targetManagement);
}
/**
* Enforces the quota defining the maximum number of {@link Target}s per
* manual {@link DistributionSet} assignment.
*
* @param id
* of the distribution set
* @param requested
* number of targets to check
*/
private void assertMaxTargetsPerManualAssignmentQuota(final Long id, final int requested) {
QuotaHelper.assertAssignmentQuota(id, requested, quotaManagement.getMaxTargetsPerManualAssignment(),
Target.class, DistributionSet.class, null);
}
@Override
@Transactional(isolation = Isolation.READ_COMMITTED)
@Retryable(include = {

View File

@@ -23,6 +23,7 @@ import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.DistributionSetMetadataFields;
import org.eclipse.hawkbit.repository.DistributionSetTagManagement;
import org.eclipse.hawkbit.repository.DistributionSetTypeManagement;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.builder.DistributionSetCreate;
import org.eclipse.hawkbit.repository.builder.DistributionSetUpdate;
@@ -43,6 +44,7 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule;
import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility;
import org.eclipse.hawkbit.repository.jpa.specifications.DistributionSetSpecification;
import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder;
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetFilter;
@@ -91,6 +93,8 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
private final DistributionSetTypeManagement distributionSetTypeManagement;
private final QuotaManagement quotaManagement;
private final DistributionSetMetadataRepository distributionSetMetadataRepository;
private final TargetFilterQueryRepository targetFilterQueryRepository;
@@ -118,7 +122,7 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
JpaDistributionSetManagement(final EntityManager entityManager,
final DistributionSetRepository distributionSetRepository,
final DistributionSetTagManagement distributionSetTagManagement, final SystemManagement systemManagement,
final DistributionSetTypeManagement distributionSetTypeManagement,
final DistributionSetTypeManagement distributionSetTypeManagement, final QuotaManagement quotaManagement,
final DistributionSetMetadataRepository distributionSetMetadataRepository,
final TargetFilterQueryRepository targetFilterQueryRepository, final ActionRepository actionRepository,
final NoCountPagingRepository criteriaNoCountDao, final ApplicationEventPublisher eventPublisher,
@@ -132,6 +136,7 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
this.distributionSetTagManagement = distributionSetTagManagement;
this.systemManagement = systemManagement;
this.distributionSetTypeManagement = distributionSetTypeManagement;
this.quotaManagement = quotaManagement;
this.distributionSetMetadataRepository = distributionSetMetadataRepository;
this.targetFilterQueryRepository = targetFilterQueryRepository;
this.actionRepository = actionRepository;
@@ -317,6 +322,9 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
assertDistributionSetIsNotAssignedToTargets(setId);
final JpaDistributionSet set = findDistributionSetAndThrowExceptionIfNotFound(setId);
assertSoftwareModuleQuota(setId, modules.size());
modules.forEach(set::addModule);
return distributionSetRepository.save(set);
@@ -452,6 +460,8 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
md.forEach(meta -> checkAndThrowAlreadyIfDistributionSetMetadataExists(
new DsMetadataCompositeKey(dsId, meta.getKey())));
assertMetaDataQuota(dsId, md.size());
final JpaDistributionSet set = touch(dsId);
return Collections.unmodifiableList(md.stream()
@@ -460,6 +470,18 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
.collect(Collectors.toList()));
}
private void assertMetaDataQuota(final Long dsId, final int requested) {
QuotaHelper.assertAssignmentQuota(dsId, requested, quotaManagement.getMaxMetaDataEntriesPerDistributionSet(),
DistributionSetMetadata.class, DistributionSet.class,
distributionSetMetadataRepository::countByDistributionSetId);
}
private void assertSoftwareModuleQuota(final Long id, final int requested) {
QuotaHelper.assertAssignmentQuota(id, requested, quotaManagement.getMaxSoftwareModulesPerDistributionSet(),
SoftwareModule.class, DistributionSet.class,
softwareModuleRepository::countByAssignedToId);
}
@Override
@Transactional
@Retryable(include = {
@@ -500,9 +522,8 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
private JpaDistributionSet touch(final DistributionSet ds) {
// merge base distribution set so optLockRevision gets updated and audit
// log written because
// modifying metadata is modifying the base distribution set itself for
// auditing purposes.
// log written because modifying metadata is modifying the base
// distribution set itself for auditing purposes.
final JpaDistributionSet result = entityManager.merge((JpaDistributionSet) ds);
result.setLastModifiedAt(0L);

View File

@@ -17,11 +17,13 @@ import java.util.stream.Collectors;
import org.eclipse.hawkbit.repository.DistributionSetTypeFields;
import org.eclipse.hawkbit.repository.DistributionSetTypeManagement;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.builder.DistributionSetTypeCreate;
import org.eclipse.hawkbit.repository.builder.DistributionSetTypeUpdate;
import org.eclipse.hawkbit.repository.builder.GenericDistributionSetTypeUpdate;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException;
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
import org.eclipse.hawkbit.repository.jpa.builder.JpaDistributionSetTypeCreate;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType;
@@ -29,6 +31,7 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleType;
import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility;
import org.eclipse.hawkbit.repository.jpa.specifications.DistributionSetTypeSpecification;
import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder;
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
@@ -64,17 +67,20 @@ public class JpaDistributionSetTypeManagement implements DistributionSetTypeMana
private final NoCountPagingRepository criteriaNoCountDao;
private final Database database;
private final QuotaManagement quotaManagement;
JpaDistributionSetTypeManagement(final DistributionSetTypeRepository distributionSetTypeRepository,
final SoftwareModuleTypeRepository softwareModuleTypeRepository,
final DistributionSetRepository distributionSetRepository,
final VirtualPropertyReplacer virtualPropertyReplacer, final NoCountPagingRepository criteriaNoCountDao,
final Database database) {
final Database database, final QuotaManagement quotaManagement) {
this.distributionSetTypeRepository = distributionSetTypeRepository;
this.softwareModuleTypeRepository = softwareModuleTypeRepository;
this.distributionSetRepository = distributionSetRepository;
this.virtualPropertyReplacer = virtualPropertyReplacer;
this.criteriaNoCountDao = criteriaNoCountDao;
this.database = database;
this.quotaManagement = quotaManagement;
}
@Override
@@ -116,6 +122,7 @@ public class JpaDistributionSetTypeManagement implements DistributionSetTypeMana
final JpaDistributionSetType type = findDistributionSetTypeAndThrowExceptionIfNotFound(dsTypeId);
checkDistributionSetTypeSoftwareModuleTypesIsAllowedToModify(dsTypeId);
assertSoftwareModuleTypeQuota(dsTypeId, softwareModulesTypeIds.size());
modules.forEach(type::addMandatoryModuleType);
@@ -138,11 +145,31 @@ public class JpaDistributionSetTypeManagement implements DistributionSetTypeMana
final JpaDistributionSetType type = findDistributionSetTypeAndThrowExceptionIfNotFound(dsTypeId);
checkDistributionSetTypeSoftwareModuleTypesIsAllowedToModify(dsTypeId);
assertSoftwareModuleTypeQuota(dsTypeId, softwareModulesTypeIds.size());
modules.forEach(type::addOptionalModuleType);
return distributionSetTypeRepository.save(type);
}
/**
* Enforces the quota specifiying the maximum number of
* {@link SoftwareModuleType}s per {@link DistributionSetType}.
*
* @param id
* of the distribution set type
* @param requested
* number of software module types to check
*
* @throws QuotaExceededException
* if the software module type quota is exceeded
*/
private void assertSoftwareModuleTypeQuota(final long id, final int requested) {
QuotaHelper.assertAssignmentQuota(id, requested,
quotaManagement.getMaxSoftwareModuleTypesPerDistributionSetType(), SoftwareModuleType.class,
DistributionSetType.class, distributionSetTypeRepository::countSmTypesById);
}
@Override
@Transactional
@Retryable(include = {

View File

@@ -52,6 +52,7 @@ import org.eclipse.hawkbit.repository.jpa.rollout.condition.RolloutGroupConditio
import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility;
import org.eclipse.hawkbit.repository.jpa.specifications.RolloutSpecification;
import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder;
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.Action.Status;
@@ -226,11 +227,15 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
}
private Rollout createRolloutGroups(final int amountOfGroups, final RolloutGroupConditions conditions,
final Rollout rollout) {
final JpaRollout rollout) {
RolloutHelper.verifyRolloutInStatus(rollout, RolloutStatus.CREATING);
RolloutHelper.verifyRolloutGroupConditions(conditions);
final JpaRollout savedRollout = (JpaRollout) rollout;
final JpaRollout savedRollout = rollout;
// we can enforce the 'max targets per group' quota right here because
// we want to distribute the targets equally to the different groups
assertTargetsPerRolloutGroupQuota(rollout.getTotalTargets() / amountOfGroups);
RolloutGroup lastSavedGroup = null;
for (int i = 0; i < amountOfGroups; i++) {
@@ -269,7 +274,7 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
RolloutHelper.verifyRolloutInStatus(rollout, RolloutStatus.CREATING);
final JpaRollout savedRollout = (JpaRollout) rollout;
// Preparing the groups
// prepare the groups
final List<RolloutGroup> groups = groupList.stream()
.map(group -> JpaRolloutHelper.prepareRolloutGroupWithDefaultConditions(group, conditions))
.collect(Collectors.toList());
@@ -278,7 +283,13 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
RolloutHelper.verifyRemainingTargets(
calculateRemainingTargets(groups, savedRollout.getTargetFilterQuery(), savedRollout.getCreatedAt()));
// Persisting the groups
// check if we need to enforce the 'max targets per group' quota
if (quotaManagement.getMaxTargetsPerRolloutGroup() > 0) {
validateTargetsInGroups(groups, savedRollout.getTargetFilterQuery(), savedRollout.getCreatedAt())
.getTargetsPerGroup().forEach(this::assertTargetsPerRolloutGroupQuota);
}
// create and persist the groups (w/o filling them with targets)
RolloutGroup lastSavedGroup = null;
for (final RolloutGroup srcGroup : groups) {
final JpaRolloutGroup group = new JpaRolloutGroup();
@@ -346,7 +357,7 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
// When all groups are ready the rollout status can be changed to be
// ready, too.
if (readyGroups == rolloutGroups.size()) {
LOGGER.debug("rollout {} creatin done. Switch to READY.", rollout.getId());
LOGGER.debug("rollout {} creation done. Switch to READY.", rollout.getId());
rollout.setStatus(RolloutStatus.READY);
rollout.setLastCheck(0);
rollout.setTotalTargets(totalTargets);
@@ -383,9 +394,10 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
return rolloutGroupRepository.save(group);
}
long targetsLeftToAdd = expectedInGroup - currentlyInGroup;
try {
long targetsLeftToAdd = expectedInGroup - currentlyInGroup;
do {
// Add up to TRANSACTION_TARGETS of the left targets
// In case a TransactionException is thrown this loop aborts
@@ -558,6 +570,9 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
final List<Long> targetIds = targets.stream().map(Target::getId).collect(Collectors.toList());
actionRepository.switchStatus(Action.Status.CANCELED, targetIds, false, Action.Status.SCHEDULED);
targets.forEach(target -> {
assertActionsPerTargetQuota(target, 1);
final JpaAction action = new JpaAction();
action.setTarget(target);
action.setActive(false);
@@ -1006,6 +1021,11 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
return rollout;
}
@Override
public boolean exists(final long rolloutId) {
return rolloutRepository.exists(rolloutId);
}
private Map<Long, List<TotalTargetCountActionStatus>> getStatusCountItemForRollout(final List<Long> rollouts) {
if (rollouts.isEmpty()) {
return null;
@@ -1044,8 +1064,33 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
}
}
@Override
public boolean exists(final long rolloutId) {
return rolloutRepository.exists(rolloutId);
/**
* Enforces the quota defining the maximum number of {@link Target}s per
* {@link RolloutGroup}.
*
* @param group
* The rollout group
* @param requested
* number of targets to check
*/
private void assertTargetsPerRolloutGroupQuota(final long requested) {
final int quota = quotaManagement.getMaxTargetsPerRolloutGroup();
QuotaHelper.assertAssignmentQuota(requested, quota, Target.class, RolloutGroup.class);
}
/**
* Enforces the quota defining the maximum number of {@link Action}s per
* {@link Target}.
*
* @param target
* The target
* @param requested
* number of actions to check
*/
private void assertActionsPerTargetQuota(final Target target, final int requested) {
final int quota = quotaManagement.getMaxActionsPerTarget();
QuotaHelper.assertAssignmentQuota(target.getId(), requested, quota, Action.class, Target.class,
actionRepository::countByTargetId);
}
}

View File

@@ -14,6 +14,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
@@ -28,6 +29,7 @@ import javax.persistence.criteria.Root;
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.RepositoryConstants;
import org.eclipse.hawkbit.repository.SoftwareModuleFields;
import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
@@ -53,6 +55,7 @@ import org.eclipse.hawkbit.repository.jpa.model.SwMetadataCompositeKey;
import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility;
import org.eclipse.hawkbit.repository.jpa.specifications.SoftwareModuleSpecification;
import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder;
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.AssignedSoftwareModule;
import org.eclipse.hawkbit.repository.model.DistributionSet;
@@ -103,7 +106,10 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
private final ArtifactManagement artifactManagement;
private final QuotaManagement quotaManagement;
private final VirtualPropertyReplacer virtualPropertyReplacer;
private final Database database;
JpaSoftwareModuleManagement(final EntityManager entityManager,
@@ -112,8 +118,8 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
final SoftwareModuleMetadataRepository softwareModuleMetadataRepository,
final SoftwareModuleTypeRepository softwareModuleTypeRepository,
final NoCountPagingRepository criteriaNoCountDao, final AuditorAware<String> auditorProvider,
final ArtifactManagement artifactManagement, final VirtualPropertyReplacer virtualPropertyReplacer,
final Database database) {
final ArtifactManagement artifactManagement, final QuotaManagement quotaManagement,
final VirtualPropertyReplacer virtualPropertyReplacer, final Database database) {
this.entityManager = entityManager;
this.distributionSetRepository = distributionSetRepository;
this.softwareModuleRepository = softwareModuleRepository;
@@ -122,6 +128,7 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
this.criteriaNoCountDao = criteriaNoCountDao;
this.auditorProvider = auditorProvider;
this.artifactManagement = artifactManagement;
this.quotaManagement = quotaManagement;
this.virtualPropertyReplacer = virtualPropertyReplacer;
this.database = database;
}
@@ -461,19 +468,13 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public SoftwareModuleMetadata createMetaData(final SoftwareModuleMetadataCreate c) {
final JpaSoftwareModuleMetadataCreate create = (JpaSoftwareModuleMetadataCreate) c;
final Long moduleId = create.getSoftwareModuleId();
assertSoftwareModuleExists(moduleId);
assertMetaDataQuota(moduleId, 1);
checkAndThrowAlreadyIfSoftwareModuleMetadataExists(create.getSoftwareModuleId(), create);
touch(create.getSoftwareModuleId());
return softwareModuleMetadataRepository.save(create.build());
}
private void checkAndThrowAlreadyIfSoftwareModuleMetadataExists(final Long moduleId,
final JpaSoftwareModuleMetadataCreate md) {
if (softwareModuleMetadataRepository.exists(new SwMetadataCompositeKey(moduleId, md.getKey()))) {
throwMetadataKeyAlreadyExists(md.getKey());
}
return saveMetadata(create);
}
@Override
@@ -482,7 +483,71 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public List<SoftwareModuleMetadata> createMetaData(final Collection<SoftwareModuleMetadataCreate> create) {
return create.stream().map(this::createMetaData).collect(Collectors.toList());
if (!create.isEmpty()) {
// check if all meta data entries refer to the same software module
final Long moduleId = ((JpaSoftwareModuleMetadataCreate) create.iterator().next()).getSoftwareModuleId();
if (createJpaMetadataCreateStream(create).allMatch(c -> moduleId.equals(c.getSoftwareModuleId()))) {
assertSoftwareModuleExists(moduleId);
assertMetaDataQuota(moduleId, create.size());
return createJpaMetadataCreateStream(create).map(this::saveMetadata).collect(Collectors.toList());
} else {
// group by software module id to minimize database access
final Map<Long, List<JpaSoftwareModuleMetadataCreate>> groups = createJpaMetadataCreateStream(create)
.collect(Collectors.groupingBy(JpaSoftwareModuleMetadataCreate::getSoftwareModuleId));
return groups.entrySet().stream().flatMap(e -> {
final Long id = e.getKey();
final List<JpaSoftwareModuleMetadataCreate> group = e.getValue();
assertSoftwareModuleExists(id);
assertMetaDataQuota(id, group.size());
return group.stream().map(this::saveMetadata);
}).collect(Collectors.toList());
}
}
return Collections.emptyList();
}
private static Stream<JpaSoftwareModuleMetadataCreate> createJpaMetadataCreateStream(
final Collection<SoftwareModuleMetadataCreate> create) {
return create.stream().map(c -> (JpaSoftwareModuleMetadataCreate) c);
}
private SoftwareModuleMetadata saveMetadata(final JpaSoftwareModuleMetadataCreate create) {
assertSoftwareModuleMetadataDoesNotExist(create.getSoftwareModuleId(), create);
return softwareModuleMetadataRepository.save(create.build());
}
private void assertSoftwareModuleMetadataDoesNotExist(final Long moduleId,
final JpaSoftwareModuleMetadataCreate md) {
if (softwareModuleMetadataRepository.exists(new SwMetadataCompositeKey(moduleId, md.getKey()))) {
throwMetadataKeyAlreadyExists(md.getKey());
}
}
private void assertSoftwareModuleExists(final Long moduleId) {
touch(moduleId);
}
/**
* Asserts the meta data quota for the software module with the given ID.
*
* @param moduleId
* The software module ID.
* @param requested
* Number of meta data entries to be created.
*/
private void assertMetaDataQuota(final Long moduleId, final int requested) {
final int maxMetaData = quotaManagement.getMaxMetaDataEntriesPerSoftwareModule();
QuotaHelper.assertAssignmentQuota(moduleId, requested, maxMetaData, SoftwareModuleMetadata.class,
SoftwareModule.class, softwareModuleMetadataRepository::countBySoftwareModuleId);
}
@Override
@@ -514,9 +579,8 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
*/
private JpaSoftwareModule touch(final SoftwareModule latestModule) {
// merge base distribution set so optLockRevision gets updated and audit
// log written because
// modifying metadata is modifying the base distribution set itself for
// auditing purposes.
// log written because modifying metadata is modifying the base
// distribution set itself for auditing purposes.
final JpaSoftwareModule result = entityManager.merge((JpaSoftwareModule) latestModule);
result.setLastModifiedAt(0L);

View File

@@ -14,6 +14,7 @@ import java.util.List;
import java.util.Optional;
import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.TargetFields;
import org.eclipse.hawkbit.repository.TargetFilterQueryFields;
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
@@ -28,7 +29,9 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaTargetFilterQuery;
import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility;
import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder;
import org.eclipse.hawkbit.repository.jpa.specifications.TargetFilterQuerySpecification;
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
import org.springframework.dao.ConcurrencyFailureException;
@@ -56,18 +59,24 @@ import com.google.common.collect.Lists;
public class JpaTargetFilterQueryManagement implements TargetFilterQueryManagement {
private final TargetFilterQueryRepository targetFilterQueryRepository;
private final TargetRepository targetRepository;
private final VirtualPropertyReplacer virtualPropertyReplacer;
private final DistributionSetManagement distributionSetManagement;
private final QuotaManagement quotaManagement;
private final Database database;
JpaTargetFilterQueryManagement(final TargetFilterQueryRepository targetFilterQueryRepository,
final VirtualPropertyReplacer virtualPropertyReplacer,
final DistributionSetManagement distributionSetManagement, final Database database) {
final TargetRepository targetRepository, final VirtualPropertyReplacer virtualPropertyReplacer,
final DistributionSetManagement distributionSetManagement, final QuotaManagement quotaManagement,
final Database database) {
this.targetFilterQueryRepository = targetFilterQueryRepository;
this.targetRepository = targetRepository;
this.virtualPropertyReplacer = virtualPropertyReplacer;
this.distributionSetManagement = distributionSetManagement;
this.quotaManagement = quotaManagement;
this.database = database;
}
@@ -78,6 +87,10 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
public TargetFilterQuery create(final TargetFilterQueryCreate c) {
final JpaTargetFilterQueryCreate create = (JpaTargetFilterQueryCreate) c;
// enforce the 'max targets per auto assign' quota right here even if
// the result of the filter query can vary over time
create.getSet().flatMap(set -> create.getQuery()).ifPresent(this::assertMaxTargetsQuota);
return targetFilterQueryRepository.save(create.build());
}
@@ -187,7 +200,18 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
final JpaTargetFilterQuery targetFilterQuery = findTargetFilterQueryOrThrowExceptionIfNotFound(update.getId());
update.getName().ifPresent(targetFilterQuery::setName);
update.getQuery().ifPresent(targetFilterQuery::setQuery);
update.getQuery().ifPresent(query -> {
// enforce the 'max targets per auto assignment'-quota only if the
// query is going to change
if (targetFilterQuery.getAutoAssignDistributionSet() != null
&& !query.equals(targetFilterQuery.getQuery())) {
assertMaxTargetsQuota(query);
}
// set the new query
targetFilterQuery.setQuery(query);
});
return targetFilterQueryRepository.save(targetFilterQuery);
}
@@ -200,6 +224,13 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
targetFilterQuery.setAutoAssignDistributionSet(
Optional.ofNullable(dsId).map(this::findDistributionSetAndThrowExceptionIfNotFound).orElse(null));
// we cannot be sure that the quota was enforced at creation time
// because the Target Filter Query REST API does not allow to specify an
// auto-assign distribution set when creating a target filter query
if (dsId != null) {
assertMaxTargetsQuota(targetFilterQuery.getQuery());
}
return targetFilterQueryRepository.save(targetFilterQuery);
}
@@ -219,4 +250,10 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
return true;
}
private void assertMaxTargetsQuota(final String query) {
QuotaHelper.assertAssignmentQuota(
targetRepository.count(RSQLUtility.parse(query, TargetFields.class, virtualPropertyReplacer, database)),
quotaManagement.getMaxTargetsPerAutoAssignment(), Target.class, TargetFilterQuery.class);
}
}

View File

@@ -93,12 +93,23 @@ public interface LocalArtifactRepository extends BaseEntityRepository<JpaArtifac
*
* @param pageReq
* Pageable
* @param swId
* @param softwareModuleId
* software module id
*
* @return Page<Artifact>
*/
Page<Artifact> findBySoftwareModuleId(Pageable pageReq, final Long swId);
Page<Artifact> findBySoftwareModuleId(Pageable pageReq, Long softwareModuleId);
/**
* Count the artifacts that are associated with the given software module.
*
* @param softwareModuleId
* software module ID
*
* @return the current number of artifacts associated with the software
* module.
*/
long countBySoftwareModuleId(Long softwareModuleId);
/**
* Searches for a {@link Artifact} based user provided filename at upload
@@ -108,8 +119,9 @@ public interface LocalArtifactRepository extends BaseEntityRepository<JpaArtifac
* to search
* @param softwareModuleId
* selected software module id
*
* @return list of {@link Artifact}.
*/
Optional<Artifact> findFirstByFilenameAndSoftwareModuleId(final String filename, final Long softwareModuleId);
Optional<Artifact> findFirstByFilenameAndSoftwareModuleId(String filename, Long softwareModuleId);
}

View File

@@ -15,6 +15,7 @@ import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.RepositoryConstants;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor;
@@ -43,9 +44,9 @@ public class OfflineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
OfflineDsAssignmentStrategy(final TargetRepository targetRepository,
final AfterTransactionCommitExecutor afterCommit, final ApplicationEventPublisher eventPublisher,
final ApplicationContext applicationContext, final ActionRepository actionRepository,
final ActionStatusRepository actionStatusRepository) {
final ActionStatusRepository actionStatusRepository, final QuotaManagement quotaManagement) {
super(targetRepository, afterCommit, eventPublisher, applicationContext, actionRepository,
actionStatusRepository);
actionStatusRepository, quotaManagement);
}
@Override

View File

@@ -14,6 +14,7 @@ import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
@@ -41,9 +42,9 @@ public class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
OnlineDsAssignmentStrategy(final TargetRepository targetRepository,
final AfterTransactionCommitExecutor afterCommit, final ApplicationEventPublisher eventPublisher,
final ApplicationContext applicationContext, final ActionRepository actionRepository,
final ActionStatusRepository actionStatusRepository) {
final ActionStatusRepository actionStatusRepository, final QuotaManagement quotaManagement) {
super(targetRepository, afterCommit, eventPublisher, applicationContext, actionRepository,
actionStatusRepository);
actionStatusRepository, quotaManagement);
}
@Override

View File

@@ -23,6 +23,7 @@ import org.eclipse.hawkbit.repository.DistributionSetTagManagement;
import org.eclipse.hawkbit.repository.DistributionSetTypeManagement;
import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.PropertiesQuotaManagement;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.RepositoryDefaultConfiguration;
import org.eclipse.hawkbit.repository.RepositoryProperties;
import org.eclipse.hawkbit.repository.RolloutGroupManagement;
@@ -69,6 +70,7 @@ import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.eclipse.hawkbit.repository.model.helper.SystemManagementHolder;
import org.eclipse.hawkbit.repository.model.helper.TenantConfigurationManagementHolder;
@@ -376,7 +378,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
DistributionSetManagement distributionSetManagement(final EntityManager entityManager,
final DistributionSetRepository distributionSetRepository,
final DistributionSetTagManagement distributionSetTagManagement, final SystemManagement systemManagement,
final DistributionSetTypeManagement distributionSetTypeManagement,
final DistributionSetTypeManagement distributionSetTypeManagement, final QuotaManagement quotaManagement,
final DistributionSetMetadataRepository distributionSetMetadataRepository,
final TargetFilterQueryRepository targetFilterQueryRepository, final ActionRepository actionRepository,
final NoCountPagingRepository criteriaNoCountDao, final ApplicationEventPublisher eventPublisher,
@@ -386,7 +388,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
final DistributionSetTagRepository distributionSetTagRepository,
final AfterTransactionCommitExecutor afterCommit, final JpaProperties properties) {
return new JpaDistributionSetManagement(entityManager, distributionSetRepository, distributionSetTagManagement,
systemManagement, distributionSetTypeManagement, distributionSetMetadataRepository,
systemManagement, distributionSetTypeManagement, quotaManagement, distributionSetMetadataRepository,
targetFilterQueryRepository, actionRepository, criteriaNoCountDao, eventPublisher, applicationContext,
tenantAware, virtualPropertyReplacer, softwareModuleRepository, distributionSetTagRepository,
afterCommit, properties.getDatabase());
@@ -405,9 +407,10 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
final SoftwareModuleTypeRepository softwareModuleTypeRepository,
final DistributionSetRepository distributionSetRepository,
final VirtualPropertyReplacer virtualPropertyReplacer, final NoCountPagingRepository criteriaNoCountDao,
final JpaProperties properties) {
final JpaProperties properties, final QuotaManagement quotaManagement) {
return new JpaDistributionSetTypeManagement(distributionSetTypeRepository, softwareModuleTypeRepository,
distributionSetRepository, virtualPropertyReplacer, criteriaNoCountDao, properties.getDatabase());
distributionSetRepository, virtualPropertyReplacer, criteriaNoCountDao, properties.getDatabase(),
quotaManagement);
}
/**
@@ -457,22 +460,29 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
* {@link JpaTargetFilterQueryManagement} bean.
*
* @param targetFilterQueryRepository
* to query entity access
* holding {@link TargetFilterQuery} entities
* @param targetRepository
* holding {@link Target} entities
* @param virtualPropertyReplacer
* for RSQL handling
* @param distributionSetManagement
* for auto assign DS access
*
* @param quotaManagement
* to access quotas
* @param properties
* JPA properties
*
* @return a new {@link TargetFilterQueryManagement}
*/
@Bean
@ConditionalOnMissingBean
TargetFilterQueryManagement targetFilterQueryManagement(
final TargetFilterQueryRepository targetFilterQueryRepository,
final TargetFilterQueryRepository targetFilterQueryRepository, final TargetRepository targetRepository,
final VirtualPropertyReplacer virtualPropertyReplacer,
final DistributionSetManagement distributionSetManagement, final JpaProperties properties) {
return new JpaTargetFilterQueryManagement(targetFilterQueryRepository, virtualPropertyReplacer,
distributionSetManagement, properties.getDatabase());
final DistributionSetManagement distributionSetManagement, final QuotaManagement quotaManagement,
final JpaProperties properties) {
return new JpaTargetFilterQueryManagement(targetFilterQueryRepository, targetRepository,
virtualPropertyReplacer, distributionSetManagement, quotaManagement, properties.getDatabase());
}
/**
@@ -518,11 +528,11 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
final SoftwareModuleMetadataRepository softwareModuleMetadataRepository,
final SoftwareModuleTypeRepository softwareModuleTypeRepository,
final NoCountPagingRepository criteriaNoCountDao, final AuditorAware<String> auditorProvider,
final ArtifactManagement artifactManagement, final VirtualPropertyReplacer virtualPropertyReplacer,
final JpaProperties properties) {
final ArtifactManagement artifactManagement, final QuotaManagement quotaManagement,
final VirtualPropertyReplacer virtualPropertyReplacer, final JpaProperties properties) {
return new JpaSoftwareModuleManagement(entityManager, distributionSetRepository, softwareModuleRepository,
softwareModuleMetadataRepository, softwareModuleTypeRepository, criteriaNoCountDao, auditorProvider,
artifactManagement, virtualPropertyReplacer, properties.getDatabase());
artifactManagement, quotaManagement, virtualPropertyReplacer, properties.getDatabase());
}
/**
@@ -585,12 +595,12 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
final ApplicationEventPublisher eventPublisher, final ApplicationContext applicationContext,
final AfterTransactionCommitExecutor afterCommit, final VirtualPropertyReplacer virtualPropertyReplacer,
final PlatformTransactionManager txManager,
final TenantConfigurationManagement tenantConfigurationManagement,
final TenantConfigurationManagement tenantConfigurationManagement, final QuotaManagement quotaManagement,
final SystemSecurityContext systemSecurityContext, final JpaProperties properties) {
return new JpaDeploymentManagement(entityManager, actionRepository, distributionSetRepository, targetRepository,
actionStatusRepository, targetManagement, auditorProvider, eventPublisher, applicationContext,
afterCommit, virtualPropertyReplacer, txManager, tenantConfigurationManagement, systemSecurityContext,
properties.getDatabase());
afterCommit, virtualPropertyReplacer, txManager, tenantConfigurationManagement, quotaManagement,
systemSecurityContext, properties.getDatabase());
}
/**
@@ -609,9 +619,9 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
@ConditionalOnMissingBean
ArtifactManagement artifactManagement(final LocalArtifactRepository localArtifactRepository,
final SoftwareModuleRepository softwareModuleRepository, final ArtifactRepository artifactRepository,
final TenantAware tenantAware) {
final QuotaManagement quotaManagement, final TenantAware tenantAware) {
return new JpaArtifactManagement(localArtifactRepository, softwareModuleRepository, artifactRepository,
tenantAware);
quotaManagement, tenantAware);
}
/**

View File

@@ -30,11 +30,49 @@ public interface SoftwareModuleMetadataRepository
extends PagingAndSortingRepository<JpaSoftwareModuleMetadata, SwMetadataCompositeKey>,
JpaSpecificationExecutor<JpaSoftwareModuleMetadata> {
/**
* Locates the meta data entries that match the given software module ID and
* target visibility flag.
*
* @param page
* The pagination parameters.
* @param moduleId
* The ID of the software module.
* @param targetVisible
* The target visibility flag.
*
* @return A {@link Page} with the matching meta data entries.
*/
Page<JpaSoftwareModuleMetadata> findBySoftwareModuleIdAndTargetVisible(Pageable page, Long moduleId,
boolean targetVisible);
/**
* Locates the meta data entries that match the given software module IDs
* and target visibility flag.
*
* @param page
* The pagination parameters.
* @param moduleId
* List of software module IDs.
* @param targetVisible
* The target visibility flag.
*
* @return A {@link Page} with the matching meta data entries.
*/
@Query("SELECT smd.softwareModule.id, smd FROM JpaSoftwareModuleMetadata smd WHERE smd.softwareModule.id IN :moduleId AND smd.targetVisible = :targetVisible")
Page<Object[]> findBySoftwareModuleIdInAndTargetVisible(Pageable page, @Param("moduleId") Collection<Long> moduleId,
@Param("targetVisible") boolean targetVisible);
/**
* Counts the meta data entries that are associated with the addressed
* software module.
*
* @param moduleId
* The ID of the software module.
*
* @return The number of meta data entries associated with the software
* module.
*/
long countBySoftwareModuleId(@Param("moduleId") Long moduleId);
}

View File

@@ -87,6 +87,18 @@ public interface SoftwareModuleRepository
*/
Page<SoftwareModule> findByAssignedToId(Pageable pageable, Long setId);
/**
* Count the software modules which are assigned to the distribution set
* with the given ID.
*
* @param setId
* the distribution set ID
*
* @return the number of software modules matching the given distribution
* set ID.
*/
long countByAssignedToId(Long setId);
/**
* @param pageable
* the page request to page the result set

View File

@@ -65,9 +65,10 @@ public class AutoAssignScheduler {
LOGGER.debug("auto assign schedule checker has been triggered.");
// run this code in system code privileged to have the necessary
// permission to query and create entities.
systemSecurityContext.runAsSystem(() -> executeAutoAssign());
systemSecurityContext.runAsSystem(this::executeAutoAssign);
}
@SuppressWarnings("squid:S3516")
private Object executeAutoAssign() {
// workaround eclipselink that is currently not possible to
// execute a query without multitenancy if MultiTenant

View File

@@ -216,7 +216,7 @@ public final class RSQLUtility {
private final SimpleTypeConverter simpleTypeConverter;
final Database database;
private final Database database;
private JpqQueryRSQLVisitor(final Root<T> root, final CriteriaBuilder cb, final Class<A> enumType,
final VirtualPropertyReplacer virtualPropertyReplacer, final Database database) {

View File

@@ -0,0 +1,135 @@
/**
* Copyright (c) 2018 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa.utils;
import java.util.function.Function;
import javax.validation.constraints.NotNull;
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.validation.annotation.Validated;
/**
* Helper class to check assignment quotas.
*/
@Validated
public final class QuotaHelper {
/**
* Class logger
*/
private static final Logger LOG = LoggerFactory.getLogger(QuotaHelper.class);
private QuotaHelper() {
// no need to instantiate this class
}
/**
* Asserts the specified assignment quota.
*
* @param requested
* The number of entities that shall be assigned to the parent
* entity.
* @param limit
* The maximum number of entities that may be assigned to the
* parent entity.
* @param type
* The type of the entities that shall be assigned.
* @param parentType
* The type of the parent entity.
*
* @throws QuotaExceededException
* if the assignment operation would cause the quota to be
* exceeded
*/
public static void assertAssignmentQuota(final long requested, final long limit, @NotNull final Class<?> type,
@NotNull final Class<?> parentType) {
assertAssignmentQuota(null, requested, limit, type.getSimpleName(), parentType.getSimpleName(), null);
}
/**
* Asserts the specified assignment quota.
*
* @param parentId
* The ID of the parent entity.
* @param requested
* The number of entities that shall be assigned to the parent
* entity.
* @param limit
* The maximum number of entities that may be assigned to the
* parent entity.
* @param type
* The type of the entities that shall be assigned.
* @param parentType
* The type of the parent entity.
* @param countFct
* Function to count the entities that are currently assigned to
* the parent entity.
*
* @throws QuotaExceededException
* if the assignment operation would cause the quota to be
* exceeded
*/
public static void assertAssignmentQuota(final Long parentId, final long requested, final long limit,
@NotNull final Class<?> type, @NotNull final Class<?> parentType, final Function<Long, Long> countFct) {
assertAssignmentQuota(parentId, requested, limit, type.getSimpleName(), parentType.getSimpleName(), countFct);
}
/**
* Asserts the specified assignment quota.
*
* @param parentId
* The ID of the parent entity.
* @param requested
* The number of entities that shall be assigned to the parent
* entity.
* @param limit
* The maximum number of entities that may be assigned to the
* parent entity.
* @param type
* The type of the entities that shall be assigned.
* @param parentType
* The type of the parent entity.
* @param countFct
* Function to count the entities that are currently assigned to
* the parent entity.
*
* @throws QuotaExceededException
* if the assignment operation would cause the quota to be
* exceeded
*/
public static void assertAssignmentQuota(final Long parentId, final long requested, final long limit,
@NotNull final String type, @NotNull final String parentType, final Function<Long, Long> countFct) {
// check if the quota is unlimited
if (limit <= 0) {
LOG.debug("Quota 'Max {} entities per {}' is unlimited.", type, parentType);
return;
}
if (requested > limit) {
final String parentIdStr = parentId != null ? String.valueOf(parentId) : "<new>";
LOG.warn("Cannot assign {} {} entities to {} '{}' because of the configured quota limit {}.", requested,
type, parentType, parentIdStr, limit);
throw new QuotaExceededException(type, parentType, parentId, requested, limit);
}
if (parentId != null && countFct != null) {
final long currentCount = countFct.apply(parentId);
if (currentCount + requested > limit) {
LOG.warn(
"Cannot assign {} {} entities to {} '{}' because of the configured quota limit {}. Currently, there are {} {} entities assigned.",
requested, type, parentType, parentId, limit, currentCount, type);
throw new QuotaExceededException(type, parentType, parentId, requested, limit);
}
}
}
}

View File

@@ -9,6 +9,7 @@
package org.eclipse.hawkbit.repository.jpa;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -17,6 +18,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.RandomStringUtils;
@@ -25,6 +27,7 @@ import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.event.remote.SoftwareModuleDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent;
import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException;
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
import org.eclipse.hawkbit.repository.jpa.model.JpaArtifact;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule;
import org.eclipse.hawkbit.repository.model.Artifact;
@@ -35,6 +38,8 @@ import org.eclipse.hawkbit.repository.test.util.HashGeneratorUtils;
import org.eclipse.hawkbit.repository.test.util.WithUser;
import org.junit.Test;
import com.google.common.collect.Lists;
import ru.yandex.qatools.allure.annotations.Description;
import ru.yandex.qatools.allure.annotations.Features;
import ru.yandex.qatools.allure.annotations.Stories;
@@ -67,12 +72,13 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest {
@ExpectEvents({ @Expect(type = SoftwareModuleDeletedEvent.class, count = 0) })
public void entityQueriesReferringToNotExistingEntitiesThrowsException() throws URISyntaxException {
verifyThrownExceptionBy(() -> artifactManagement.create(IOUtils.toInputStream("test", "UTF-8"), NOT_EXIST_IDL,
"xxx", null, null, false, null), "SoftwareModule");
final String artifactData = "test";
final int artifactSize = artifactData.length();
verifyThrownExceptionBy(() -> artifactManagement.create(IOUtils.toInputStream(artifactData, "UTF-8"),
NOT_EXIST_IDL, "xxx", null, null, false, null, artifactSize), "SoftwareModule");
verifyThrownExceptionBy(
() -> artifactManagement.create(IOUtils.toInputStream("test", "UTF-8"), 1234L, "xxx", false),
"SoftwareModule");
verifyThrownExceptionBy(() -> artifactManagement.create(IOUtils.toInputStream(artifactData, "UTF-8"), 1234L,
"xxx", false, artifactSize), "SoftwareModule");
verifyThrownExceptionBy(() -> artifactManagement.delete(NOT_EXIST_IDL), "Artifact");
@@ -86,59 +92,128 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest {
@Test
@Description("Test if a local artifact can be created by API including metadata.")
public void createArtifact() throws NoSuchAlgorithmException, IOException {
// checkbaseline
// check baseline
assertThat(softwareModuleRepository.findAll()).hasSize(0);
assertThat(artifactRepository.findAll()).hasSize(0);
JpaSoftwareModule sm = new JpaSoftwareModule(osType, "name 1", "version 1", null, null);
sm = softwareModuleRepository.save(sm);
final JpaSoftwareModule sm = softwareModuleRepository
.save(new JpaSoftwareModule(osType, "name 1", "version 1", null, null));
final JpaSoftwareModule sm2 = softwareModuleRepository
.save(new JpaSoftwareModule(osType, "name 2", "version 2", null, null));
softwareModuleRepository.save(new JpaSoftwareModule(osType, "name 3", "version 3", null, null));
JpaSoftwareModule sm2 = new JpaSoftwareModule(osType, "name 2", "version 2", null, null);
sm2 = softwareModuleRepository.save(sm2);
final int artifactSize = 5 * 1024;
final byte random[] = RandomStringUtils.random(artifactSize).getBytes();
JpaSoftwareModule sm3 = new JpaSoftwareModule(osType, "name 3", "version 3", null, null);
sm3 = softwareModuleRepository.save(sm3);
try (final InputStream inputStream1 = new ByteArrayInputStream(random);
final InputStream inputStream2 = new ByteArrayInputStream(random);
final InputStream inputStream3 = new ByteArrayInputStream(random);
final InputStream inputStream4 = new ByteArrayInputStream(random);) {
final byte random[] = RandomStringUtils.random(5 * 1024).getBytes();
final Artifact result = artifactManagement.create(inputStream1, sm.getId(), "file1", false, artifactSize);
artifactManagement.create(inputStream2, sm.getId(), "file11", false, artifactSize);
artifactManagement.create(inputStream3, sm.getId(), "file12", false, artifactSize);
final Artifact result2 = artifactManagement.create(inputStream4, sm2.getId(), "file2", false, artifactSize);
final Artifact result = artifactManagement.create(new ByteArrayInputStream(random), sm.getId(), "file1", false);
artifactManagement.create(new ByteArrayInputStream(random), sm.getId(), "file11", false);
artifactManagement.create(new ByteArrayInputStream(random), sm.getId(), "file12", false);
final Artifact result2 = artifactManagement.create(new ByteArrayInputStream(random), sm2.getId(), "file2",
false);
assertThat(result).isInstanceOf(Artifact.class);
assertThat(result.getSoftwareModule().getId()).isEqualTo(sm.getId());
assertThat(result2.getSoftwareModule().getId()).isEqualTo(sm2.getId());
assertThat(((JpaArtifact) result).getFilename()).isEqualTo("file1");
assertThat(((JpaArtifact) result).getSha1Hash()).isNotNull();
assertThat(result).isNotEqualTo(result2);
assertThat(((JpaArtifact) result).getSha1Hash()).isEqualTo(((JpaArtifact) result2).getSha1Hash());
assertThat(result).isInstanceOf(Artifact.class);
assertThat(result.getSoftwareModule().getId()).isEqualTo(sm.getId());
assertThat(result2.getSoftwareModule().getId()).isEqualTo(sm2.getId());
assertThat(((JpaArtifact) result).getFilename()).isEqualTo("file1");
assertThat(((JpaArtifact) result).getSha1Hash()).isNotNull();
assertThat(result).isNotEqualTo(result2);
assertThat(((JpaArtifact) result).getSha1Hash()).isEqualTo(((JpaArtifact) result2).getSha1Hash());
assertThat(artifactManagement.getByFilename("file1").get().getSha1Hash())
.isEqualTo(HashGeneratorUtils.generateSHA1(random));
assertThat(artifactManagement.getByFilename("file1").get().getMd5Hash())
.isEqualTo(HashGeneratorUtils.generateMD5(random));
assertThat(artifactManagement.getByFilename("file1").get().getSha1Hash())
.isEqualTo(HashGeneratorUtils.generateSHA1(random));
assertThat(artifactManagement.getByFilename("file1").get().getMd5Hash())
.isEqualTo(HashGeneratorUtils.generateMD5(random));
assertThat(artifactRepository.findAll()).hasSize(4);
assertThat(softwareModuleRepository.findAll()).hasSize(3);
assertThat(artifactRepository.findAll()).hasSize(4);
assertThat(softwareModuleRepository.findAll()).hasSize(3);
assertThat(softwareModuleManagement.get(sm.getId()).get().getArtifacts()).hasSize(3);
}
assertThat(softwareModuleManagement.get(sm.getId()).get().getArtifacts()).hasSize(3);
}
@Test
@Description("Verifies that the quota specifying the maximum number of artifacts per software module is enforced.")
public void createArtifactsUntilQuotaIsExceeded() throws NoSuchAlgorithmException, IOException {
// create a software module
final JpaSoftwareModule sm1 = softwareModuleRepository
.save(new JpaSoftwareModule(osType, "sm1", "1.0", null, null));
// now create artifacts for this module until the quota is exceeded
final long maxArtifacts = quotaManagement.getMaxArtifactsPerSoftwareModule();
final List<Long> artifactIds = Lists.newArrayList();
final int artifactSize = 5 * 1024;
for (int i = 0; i < maxArtifacts; ++i) {
final byte random[] = RandomStringUtils.random(artifactSize).getBytes();
try (final InputStream inputStream = new ByteArrayInputStream(random)) {
artifactIds.add(
artifactManagement.create(inputStream, sm1.getId(), "file" + i, false, artifactSize).getId());
}
}
assertThat(artifactRepository.findBySoftwareModuleId(PAGE, sm1.getId()).getTotalElements())
.isEqualTo(maxArtifacts);
// create one mode to trigger the quota exceeded error
assertThatExceptionOfType(QuotaExceededException.class).isThrownBy(() -> {
final byte random[] = RandomStringUtils.random(artifactSize).getBytes();
try (final InputStream inputStream = new ByteArrayInputStream(random)) {
artifactManagement.create(inputStream, sm1.getId(), "file" + maxArtifacts, false, artifactSize);
}
});
// delete one of the artifacts
artifactManagement.delete(artifactIds.get(0));
assertThat(artifactRepository.findBySoftwareModuleId(PAGE, sm1.getId()).getTotalElements())
.isEqualTo(maxArtifacts - 1);
// now we should be able to create an artifact again
final byte random[] = RandomStringUtils.random(artifactSize).getBytes();
try (final InputStream inputStream = new ByteArrayInputStream(random)) {
artifactManagement.create(inputStream, sm1.getId(), "fileXYZ", false, artifactSize);
assertThat(artifactRepository.findBySoftwareModuleId(PAGE, sm1.getId()).getTotalElements())
.isEqualTo(maxArtifacts);
}
}
@Test
@Description("Verifies that you cannot create artifacts which exceed the configured maximum size.")
public void createArtifactFailsIfTooLarge() throws NoSuchAlgorithmException, IOException {
// create a software module
final JpaSoftwareModule sm1 = softwareModuleRepository
.save(new JpaSoftwareModule(osType, "sm1", "1.0", null, null));
// create an artifact that exceeds the configured quota
final long maxSize = quotaManagement.getMaxArtifactSize();
final int artifactSize = Math.toIntExact(maxSize) + 8;
final byte random[] = RandomStringUtils.random(artifactSize).getBytes();
try (final InputStream inputStream = new ByteArrayInputStream(random)) {
assertThatExceptionOfType(QuotaExceededException.class)
.isThrownBy(() -> artifactManagement.create(inputStream, sm1.getId(), "file", false, artifactSize));
}
}
@Test
@Description("Tests hard delete directly on repository.")
public void hardDeleteSoftwareModule() throws NoSuchAlgorithmException, IOException {
JpaSoftwareModule sm = new JpaSoftwareModule(osType, "name 1", "version 1", null, null);
sm = softwareModuleRepository.save(sm);
final byte random[] = RandomStringUtils.random(5 * 1024).getBytes();
final JpaSoftwareModule sm = softwareModuleRepository
.save(new JpaSoftwareModule(osType, "name 1", "version 1", null, null));
final int artifactSize = 5 * 1024;
final byte random[] = RandomStringUtils.random(artifactSize).getBytes();
try (final InputStream inputStream = new ByteArrayInputStream(random)) {
artifactManagement.create(inputStream, sm.getId(), "file1", false, artifactSize);
assertThat(artifactRepository.findAll()).hasSize(1);
artifactManagement.create(new ByteArrayInputStream(random), sm.getId(), "file1", false);
assertThat(artifactRepository.findAll()).hasSize(1);
softwareModuleRepository.deleteAll();
assertThat(artifactRepository.findAll()).hasSize(0);
softwareModuleRepository.deleteAll();
assertThat(artifactRepository.findAll()).hasSize(0);
}
}
/**
@@ -152,97 +227,109 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest {
@Test
@Description("Tests the deletion of a local artifact including metadata.")
public void deleteArtifact() throws NoSuchAlgorithmException, IOException {
JpaSoftwareModule sm = new JpaSoftwareModule(osType, "name 1", "version 1", null, null);
sm = softwareModuleRepository.save(sm);
JpaSoftwareModule sm2 = new JpaSoftwareModule(osType, "name 2", "version 2", null, null);
sm2 = softwareModuleRepository.save(sm2);
final JpaSoftwareModule sm = softwareModuleRepository
.save(new JpaSoftwareModule(osType, "name 1", "version 1", null, null));
final JpaSoftwareModule sm2 = softwareModuleRepository
.save(new JpaSoftwareModule(osType, "name 2", "version 2", null, null));
assertThat(artifactRepository.findAll()).isEmpty();
final Artifact result = artifactManagement.create(new RandomGeneratedInputStream(5 * 1024), sm.getId(), "file1",
false);
final Artifact result2 = artifactManagement.create(new RandomGeneratedInputStream(5 * 1024), sm2.getId(),
"file2", false);
final int artifactSize = 5 * 1024;
try (final InputStream inputStream1 = new RandomGeneratedInputStream(artifactSize);
final InputStream inputStream2 = new RandomGeneratedInputStream(artifactSize)) {
assertThat(artifactRepository.findAll()).hasSize(2);
final Artifact result = artifactManagement.create(inputStream1, sm.getId(), "file1", false, artifactSize);
final Artifact result2 = artifactManagement.create(inputStream2, sm2.getId(), "file2", false, artifactSize);
assertThat(result.getId()).isNotNull();
assertThat(result2.getId()).isNotNull();
assertThat(((JpaArtifact) result).getSha1Hash()).isNotEqualTo(((JpaArtifact) result2).getSha1Hash());
assertThat(artifactRepository.findAll()).hasSize(2);
assertThat(binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), result.getSha1Hash()))
.isNotNull();
assertThat(binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), result2.getSha1Hash()))
.isNotNull();
assertThat(result.getId()).isNotNull();
assertThat(result2.getId()).isNotNull();
assertThat(((JpaArtifact) result).getSha1Hash()).isNotEqualTo(((JpaArtifact) result2).getSha1Hash());
artifactManagement.delete(result.getId());
assertThat(binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), result.getSha1Hash()))
.isNotNull();
assertThat(
binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), result2.getSha1Hash()))
.isNotNull();
assertThat(binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), result.getSha1Hash()))
.isNull();
assertThat(binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), result2.getSha1Hash()))
.isNotNull();
artifactManagement.delete(result.getId());
artifactManagement.delete(result2.getId());
assertThat(binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), result2.getSha1Hash()))
.isNull();
assertThat(binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), result.getSha1Hash()))
.isNull();
assertThat(
binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), result2.getSha1Hash()))
.isNotNull();
assertThat(artifactRepository.findAll()).hasSize(0);
artifactManagement.delete(result2.getId());
assertThat(
binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), result2.getSha1Hash()))
.isNull();
assertThat(artifactRepository.findAll()).hasSize(0);
}
}
@Test
@Description("Test the deletion of an artifact metadata where the binary is still linked to another "
+ "metadata element. The expected result is that the metadata is deleted but the binary kept.")
public void deleteDuplicateArtifacts() throws NoSuchAlgorithmException, IOException {
JpaSoftwareModule sm = new JpaSoftwareModule(osType, "name 1", "version 1", null, null);
sm = softwareModuleRepository.save(sm);
JpaSoftwareModule sm2 = new JpaSoftwareModule(osType, "name 2", "version 2", null, null);
sm2 = softwareModuleRepository.save(sm2);
final JpaSoftwareModule sm = softwareModuleRepository
.save(new JpaSoftwareModule(osType, "name 1", "version 1", null, null));
final JpaSoftwareModule sm2 = softwareModuleRepository
.save(new JpaSoftwareModule(osType, "name 2", "version 2", null, null));
final byte random[] = RandomStringUtils.random(5 * 1024).getBytes();
final int artifactSize = 5 * 1024;
final byte random[] = RandomStringUtils.random(artifactSize).getBytes();
final Artifact result = artifactManagement.create(new ByteArrayInputStream(random), sm.getId(), "file1", false);
final Artifact result2 = artifactManagement.create(new ByteArrayInputStream(random), sm2.getId(), "file2",
false);
try (final InputStream inputStream1 = new ByteArrayInputStream(random);
final InputStream inputStream2 = new ByteArrayInputStream(random)) {
final Artifact result = artifactManagement.create(inputStream1, sm.getId(), "file1", false, artifactSize);
final Artifact result2 = artifactManagement.create(inputStream2, sm2.getId(), "file2", false, artifactSize);
assertThat(artifactRepository.findAll()).hasSize(2);
assertThat(result.getId()).isNotNull();
assertThat(result2.getId()).isNotNull();
assertThat(((JpaArtifact) result).getSha1Hash()).isEqualTo(((JpaArtifact) result2).getSha1Hash());
assertThat(artifactRepository.findAll()).hasSize(2);
assertThat(result.getId()).isNotNull();
assertThat(result2.getId()).isNotNull();
assertThat(((JpaArtifact) result).getSha1Hash()).isEqualTo(((JpaArtifact) result2).getSha1Hash());
assertThat(binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), result.getSha1Hash()))
.isNotNull();
artifactManagement.delete(result.getId());
assertThat(binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), result.getSha1Hash()))
.isNotNull();
assertThat(binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), result.getSha1Hash()))
.isNotNull();
artifactManagement.delete(result.getId());
assertThat(binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), result.getSha1Hash()))
.isNotNull();
artifactManagement.delete(result2.getId());
assertThat(binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), result.getSha1Hash()))
.isNull();
artifactManagement.delete(result2.getId());
assertThat(binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), result.getSha1Hash()))
.isNull();
}
}
@Test
@Description("Loads an local artifact based on given ID.")
public void findArtifact() throws NoSuchAlgorithmException, IOException {
final Artifact result = artifactManagement.create(new RandomGeneratedInputStream(5 * 1024),
testdataFactory.createSoftwareModuleOs().getId(), "file1", false);
assertThat(artifactManagement.get(result.getId()).get()).isEqualTo(result);
final int artifactSize = 5 * 1024;
try (final InputStream inputStream = new RandomGeneratedInputStream(artifactSize)) {
final Artifact result = artifactManagement.create(inputStream,
testdataFactory.createSoftwareModuleOs().getId(), "file1", false, artifactSize);
assertThat(artifactManagement.get(result.getId()).get()).isEqualTo(result);
}
}
@Test
@Description("Loads an artifact binary based on given ID.")
public void loadStreamOfArtifact() throws NoSuchAlgorithmException, IOException {
final byte random[] = RandomStringUtils.random(5 * 1024).getBytes();
final Artifact result = artifactManagement.create(new ByteArrayInputStream(random),
testdataFactory.createSoftwareModuleOs().getId(), "file1", false);
try (InputStream fileInputStream = artifactManagement.loadArtifactBinary(result.getSha1Hash()).get()
.getFileInputStream()) {
assertTrue("The stored binary matches the given binary",
IOUtils.contentEquals(new ByteArrayInputStream(random), fileInputStream));
final int artifactSize = 5 * 1024;
final byte random[] = RandomStringUtils.random(artifactSize).getBytes();
try (final InputStream input = new ByteArrayInputStream(random)) {
final Artifact result = artifactManagement.create(input, testdataFactory.createSoftwareModuleOs().getId(),
"file1", false, artifactSize);
try (final InputStream inputStream = artifactManagement.loadArtifactBinary(result.getSha1Hash()).get()
.getFileInputStream()) {
assertTrue("The stored binary matches the given binary",
IOUtils.contentEquals(new ByteArrayInputStream(random), inputStream));
}
}
}
@@ -260,27 +347,31 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest {
@Test
@Description("Searches an artifact through the relations of a software module.")
public void findArtifactBySoftwareModule() {
public void findArtifactBySoftwareModule() throws IOException {
final SoftwareModule sm = testdataFactory.createSoftwareModuleOs();
assertThat(artifactManagement.findBySoftwareModule(PAGE, sm.getId())).isEmpty();
artifactManagement.create(new RandomGeneratedInputStream(5 * 1024), sm.getId(), "file1", false);
assertThat(artifactManagement.findBySoftwareModule(PAGE, sm.getId())).hasSize(1);
final int artifactSize = 5 * 1024;
try (final InputStream input = new RandomGeneratedInputStream(artifactSize)) {
artifactManagement.create(input, sm.getId(), "file1", false, artifactSize);
assertThat(artifactManagement.findBySoftwareModule(PAGE, sm.getId())).hasSize(1);
}
}
@Test
@Description("Searches an artifact through the relations of a software module and the filename.")
public void findByFilenameAndSoftwareModule() {
public void findByFilenameAndSoftwareModule() throws IOException {
final SoftwareModule sm = testdataFactory.createSoftwareModuleOs();
assertThat(artifactManagement.getByFilenameAndSoftwareModule("file1", sm.getId())).isNotPresent();
artifactManagement.create(new RandomGeneratedInputStream(5 * 1024), sm.getId(), "file1", false);
artifactManagement.create(new RandomGeneratedInputStream(5 * 1024), sm.getId(), "file2", false);
assertThat(artifactManagement.getByFilenameAndSoftwareModule("file1", sm.getId())).isPresent();
final int artifactSize = 5 * 1024;
try (final InputStream inputStream1 = new RandomGeneratedInputStream(artifactSize);
final InputStream inputStream2 = new RandomGeneratedInputStream(artifactSize)) {
artifactManagement.create(inputStream1, sm.getId(), "file1", false, artifactSize);
artifactManagement.create(inputStream2, sm.getId(), "file2", false, artifactSize);
assertThat(artifactManagement.getByFilenameAndSoftwareModule("file1", sm.getId())).isPresent();
}
}
}

View File

@@ -20,6 +20,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import javax.validation.ConstraintViolationException;
@@ -430,7 +431,8 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
@Expect(type = SoftwareModuleCreatedEvent.class, count = 6),
@Expect(type = SoftwareModuleUpdatedEvent.class, count = 2) })
public void hasTargetArtifactAssignedIsTrueWithMultipleArtifacts() {
final byte[] random = RandomUtils.nextBytes(5 * 1024);
final int artifactSize = 5 * 1024;
final byte[] random = RandomUtils.nextBytes(artifactSize);
final DistributionSet ds = testdataFactory.createDistributionSet("");
final DistributionSet ds2 = testdataFactory.createDistributionSet("2");
@@ -438,9 +440,9 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
// create two artifacts with identical SHA1 hash
final Artifact artifact = artifactManagement.create(new ByteArrayInputStream(random),
ds.findFirstModuleByType(osType).get().getId(), "file1", false);
ds.findFirstModuleByType(osType).get().getId(), "file1", false, artifactSize);
final Artifact artifact2 = artifactManagement.create(new ByteArrayInputStream(random),
ds2.findFirstModuleByType(osType).get().getId(), "file1", false);
ds2.findFirstModuleByType(osType).get().getId(), "file1", false, artifactSize);
assertThat(artifact.getSha1Hash()).isEqualTo(artifact2.getSha1Hash());
assertThat(
@@ -793,7 +795,7 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
@Expect(type = TargetUpdatedEvent.class, count = 2) })
public void updateTargetAttributesFailsIfTooManyEntries() throws Exception {
final String controllerId = "test123";
final int allowedAttributes = 10;
final int allowedAttributes = quotaManagement.getMaxAttributeEntriesPerTarget();
testdataFactory.createTarget(controllerId);
assertThatExceptionOfType(QuotaExceededException.class).isThrownBy(() -> securityRule
@@ -880,4 +882,69 @@ public class ControllerManagementTest extends AbstractJpaIntegrationTest {
assertThat(messages.get(0)).as("Message of action-status").isEqualTo("proceeding message 2");
assertThat(messages.get(1)).as("Message of action-status").isEqualTo("proceeding message 1");
}
@Test
@Description("Verifies that the quota specifying the maximum number of status entries per action is enforced.")
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 2),
@Expect(type = DistributionSetCreatedEvent.class, count = 2),
@Expect(type = ActionCreatedEvent.class, count = 2), @Expect(type = TargetUpdatedEvent.class, count = 2),
@Expect(type = TargetAssignDistributionSetEvent.class, count = 2),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 6) })
public void addActionStatusUpdatesUntilQuotaIsExceeded() {
// any distribution set assignment causes 1 status entity to be created
final int maxStatusEntries = quotaManagement.getMaxStatusEntriesPerAction() - 1;
// test for informational status
final Long actionId1 = assignDistributionSet(testdataFactory.createDistributionSet("ds1"),
testdataFactory.createTargets(1, "t1")).getActions().get(0);
assertThat(actionId1).isNotNull();
for (int i = 0; i < maxStatusEntries; ++i) {
controllerManagement.addInformationalActionStatus(entityFactory.actionStatus().create(actionId1)
.status(Status.WARNING).message("Msg " + i).occurredAt(System.currentTimeMillis()));
}
assertThatExceptionOfType(QuotaExceededException.class).isThrownBy(() -> controllerManagement
.addInformationalActionStatus(entityFactory.actionStatus().create(actionId1).status(Status.WARNING)));
// test for update status (and mixed case)
final Long actionId2 = assignDistributionSet(testdataFactory.createDistributionSet("ds2"),
testdataFactory.createTargets(1, "t2")).getActions().get(0);
assertThat(actionId2).isNotEqualTo(actionId1);
for (int i = 0; i < maxStatusEntries; ++i) {
controllerManagement.addUpdateActionStatus(entityFactory.actionStatus().create(actionId2)
.status(Status.WARNING).message("Msg " + i).occurredAt(System.currentTimeMillis()));
}
assertThatExceptionOfType(QuotaExceededException.class).isThrownBy(() -> controllerManagement
.addInformationalActionStatus(entityFactory.actionStatus().create(actionId2).status(Status.WARNING)));
}
@Test
@Description("Verifies that the quota specifying the maximum number of messages per action status is enforced.")
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
@Expect(type = ActionCreatedEvent.class, count = 1), @Expect(type = TargetUpdatedEvent.class, count = 1),
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
@Expect(type = SoftwareModuleCreatedEvent.class, count = 3) })
public void createActionStatusWithTooManyMessages() {
final int maxMessages = quotaManagement.getMaxMessagesPerActionStatus();
final Long actionId = assignDistributionSet(testdataFactory.createDistributionSet("ds1"),
testdataFactory.createTargets(1)).getActions().get(0);
assertThat(actionId).isNotNull();
final List<String> messages = Lists.newArrayList();
IntStream.range(0, maxMessages).forEach(i -> messages.add(i, "msg"));
assertThat(controllerManagement.addInformationalActionStatus(
entityFactory.actionStatus().create(actionId).messages(messages).status(Status.WARNING))).isNotNull();
messages.add("msg");
assertThatExceptionOfType(QuotaExceededException.class)
.isThrownBy(() -> controllerManagement.addInformationalActionStatus(
entityFactory.actionStatus().create(actionId).messages(messages).status(Status.WARNING)));
}
}

View File

@@ -22,6 +22,7 @@ import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.eclipse.hawkbit.repository.ActionStatusFields;
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
@@ -35,6 +36,7 @@ import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.ForceQuitActionNotAllowedException;
import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException;
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus;
@@ -171,6 +173,37 @@ public class DeploymentManagementTest extends AbstractJpaIntegrationTest {
assertThat(actions.getContent().get(0).getId()).as("Action of target").isEqualTo(actionId);
}
@Test
@Description("Test verifies that the 'max actions per target' quota is enforced if the assigned distribution set is changed permanently.")
public void changeDistributionSetAssignmentUntilMaxActionsPerTargetQuotaIsExceeded() {
final int maxActions = quotaManagement.getMaxActionsPerTarget();
final List<Target> testTargets = testdataFactory.createTargets(1);
final DistributionSet ds1 = testdataFactory.createDistributionSet("ds1");
final DistributionSet ds2 = testdataFactory.createDistributionSet("ds2");
final DistributionSet ds3 = testdataFactory.createDistributionSet("ds3");
IntStream.range(0, maxActions).forEach(i -> {
assignDistributionSet(i % 2 == 0 ? ds1 : ds2, testTargets);
});
// change the distribution set one last time to trigger a quota hit
assertThatExceptionOfType(QuotaExceededException.class)
.isThrownBy(() -> assignDistributionSet(ds3, testTargets));
}
@Test
@Description("Assigns the same distribution set to many targets until the 'max targets per manual assignment' quota is exceeded.")
public void assignDistributionSetUntilQuotaIsExceeded() {
final int maxTargets = quotaManagement.getMaxTargetsPerManualAssignment();
final DistributionSet ds = testdataFactory.createDistributionSet();
assignDistributionSet(ds, testdataFactory.createTargets(maxTargets, "ok"));
assertThatExceptionOfType(QuotaExceededException.class)
.isThrownBy(() -> assignDistributionSet(ds, testdataFactory.createTargets(maxTargets + 1, "fail")));
}
@Test
@Description("Test verifies that action-states of an action are found by using id-based search.")
public void findActionStatusByActionId() {

View File

@@ -32,6 +32,7 @@ import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedE
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException;
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
import org.eclipse.hawkbit.repository.exception.UnsupportedSoftwareModuleForThisDistributionSetException;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetMetadata;
@@ -42,6 +43,7 @@ import org.eclipse.hawkbit.repository.model.DistributionSetFilter.DistributionSe
import org.eclipse.hawkbit.repository.model.DistributionSetMetadata;
import org.eclipse.hawkbit.repository.model.DistributionSetTag;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.MetaData;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.test.matcher.Expect;
@@ -333,6 +335,51 @@ public class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
assertThat(createdMetadata.getValue()).isEqualTo(knownValue);
}
@Test
@Description("Verifies the enforcement of the metadata quota per distribution set.")
public void createDistributionSetMetadataUntilQuotaIsExceeded() {
// add meta data one by one
final DistributionSet ds1 = testdataFactory.createDistributionSet("ds1");
final int maxMetaData = quotaManagement.getMaxMetaDataEntriesPerDistributionSet();
for (int i = 0; i < maxMetaData; ++i) {
assertThat((JpaDistributionSetMetadata) createDistributionSetMetadata(ds1.getId(),
new JpaDistributionSetMetadata("k" + i, ds1, "v" + i))).isNotNull();
}
// quota exceeded
assertThatExceptionOfType(QuotaExceededException.class)
.isThrownBy(() -> createDistributionSetMetadata(ds1.getId(), createDistributionSetMetadata(ds1.getId(),
new JpaDistributionSetMetadata("k" + maxMetaData, ds1, "v" + maxMetaData))));
// add multiple meta data entries at once
final DistributionSet ds2 = testdataFactory.createDistributionSet("ds2");
final List<MetaData> metaData2 = new ArrayList<>();
for (int i = 0; i < maxMetaData + 1; ++i) {
metaData2.add(new JpaDistributionSetMetadata("k" + i, ds2, "v" + i));
}
// verify quota is exceeded
assertThatExceptionOfType(QuotaExceededException.class)
.isThrownBy(() -> createDistributionSetMetadata(ds2.getId(), metaData2));
// add some meta data entries
final DistributionSet ds3 = testdataFactory.createDistributionSet("ds3");
final int firstHalf = Math.round(maxMetaData / 2);
for (int i = 0; i < firstHalf; ++i) {
createDistributionSetMetadata(ds3.getId(), new JpaDistributionSetMetadata("k" + i, ds3, "v" + i));
}
// add too many data entries
final int secondHalf = maxMetaData - firstHalf;
final List<MetaData> metaData3 = new ArrayList<>();
for (int i = 0; i < secondHalf + 1; ++i) {
metaData3.add(new JpaDistributionSetMetadata("kk" + i, ds3, "vv" + i));
}
// verify quota is exceeded
assertThatExceptionOfType(QuotaExceededException.class)
.isThrownBy(() -> createDistributionSetMetadata(ds3.getId(), metaData3));
}
@Test
@Description("Ensures that distribution sets can assigned and unassigned to a distribution set tag.")
public void assignAndUnassignDistributionSetToTag() {
@@ -400,8 +447,8 @@ public class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
@Description("Ensures that it is not possible to add a software module that is not defined of the DS's type.")
public void updateDistributionSetUnsupportedModuleFails() {
final DistributionSet set = distributionSetManagement
.create(entityFactory.distributionSet().create().name("agent-hub2")
.version(
.create(entityFactory
.distributionSet().create().name("agent-hub2").version(
"1.0.5")
.type(distributionSetTypeManagement.create(entityFactory.distributionSetType().create()
.key("test").name("test").mandatory(Arrays.asList(osType.getId()))).getKey()));
@@ -443,6 +490,46 @@ public class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
assertThat(ds.isRequiredMigrationStep()).isTrue();
}
@Test
@Description("Verifies the enforcement of the software module quota per distribution set.")
public void assignSoftwareModulesUntilQuotaIsExceeded() {
// create some software modules
final int maxModules = quotaManagement.getMaxSoftwareModulesPerDistributionSet();
final List<Long> modules = Lists.newArrayList();
for (int i = 0; i < maxModules + 1; ++i) {
modules.add(testdataFactory.createSoftwareModuleApp("sm" + i).getId());
}
// assign software modules one by one
final DistributionSet ds1 = testdataFactory.createDistributionSetWithNoSoftwareModules("ds1", "1.0");
for (int i = 0; i < maxModules; ++i) {
distributionSetManagement.assignSoftwareModules(ds1.getId(), Collections.singletonList(modules.get(i)));
}
// add one more to cause the quota to be exceeded
assertThatExceptionOfType(QuotaExceededException.class).isThrownBy(() -> {
distributionSetManagement.assignSoftwareModules(ds1.getId(),
Collections.singletonList(modules.get(maxModules)));
});
// assign all software modules at once
final DistributionSet ds2 = testdataFactory.createDistributionSetWithNoSoftwareModules("ds2", "1.0");
// verify quota is exceeded
assertThatExceptionOfType(QuotaExceededException.class)
.isThrownBy(() -> distributionSetManagement.assignSoftwareModules(ds2.getId(), modules));
// assign some software modules
final DistributionSet ds3 = testdataFactory.createDistributionSetWithNoSoftwareModules("ds3", "1.0");
final int firstHalf = Math.round(maxModules / 2);
for (int i = 0; i < firstHalf; ++i) {
distributionSetManagement.assignSoftwareModules(ds3.getId(), Collections.singletonList(modules.get(i)));
}
// assign the remaining modules to cause the quota to be exceeded
assertThatExceptionOfType(QuotaExceededException.class).isThrownBy(() -> distributionSetManagement
.assignSoftwareModules(ds3.getId(), modules.subList(firstHalf, modules.size())));
}
@Test
@WithUser(allSpPermissions = true)
@Description("Checks that metadata for a distribution set can be updated.")
@@ -491,7 +578,7 @@ public class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
final Iterator<DistributionSet> dsIterator = buildDistributionSets.iterator();
final Iterator<Target> tIterator = buildTargetFixtures.iterator();
final DistributionSet dsFirst = dsIterator.next();
dsIterator.next();
final DistributionSet dsSecond = dsIterator.next();
final DistributionSet dsThree = dsIterator.next();
final DistributionSet dsFour = dsIterator.next();
@@ -775,7 +862,7 @@ public class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
new JpaDistributionSetMetadata("key" + index, ds1, "value" + index));
}
for (int index = 0; index < 20; index++) {
for (int index = 0; index < 8; index++) {
createDistributionSetMetadata(ds2.getId(),
new JpaDistributionSetMetadata("key" + index, ds2, "value" + index));
}
@@ -789,8 +876,8 @@ public class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
assertThat(metadataOfDs1.getNumberOfElements()).isEqualTo(10);
assertThat(metadataOfDs1.getTotalElements()).isEqualTo(10);
assertThat(metadataOfDs2.getNumberOfElements()).isEqualTo(20);
assertThat(metadataOfDs2.getTotalElements()).isEqualTo(20);
assertThat(metadataOfDs2.getNumberOfElements()).isEqualTo(8);
assertThat(metadataOfDs2.getTotalElements()).isEqualTo(8);
}
@Test

View File

@@ -8,17 +8,26 @@
*/
package org.eclipse.hawkbit.repository.jpa;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.validation.ConstraintViolationException;
import org.apache.commons.lang3.RandomStringUtils;
import org.assertj.core.util.Lists;
import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.builder.SoftwareModuleTypeCreate;
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetTypeCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetUpdatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent;
import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException;
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
@@ -28,10 +37,6 @@ import org.junit.Test;
import com.google.common.collect.Sets;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import ru.yandex.qatools.allure.annotations.Description;
import ru.yandex.qatools.allure.annotations.Features;
import ru.yandex.qatools.allure.annotations.Step;
@@ -193,7 +198,7 @@ public class DistributionSetTypeManagementTest extends AbstractJpaIntegrationTes
}
@Test
@Description("Tests the successfull module update of unused distribution set type which is in fact allowed.")
@Description("Tests the successful module update of unused distribution set type which is in fact allowed.")
public void updateUnassignedDistributionSetTypeModules() {
final DistributionSetType updatableType = distributionSetTypeManagement
.create(entityFactory.distributionSetType().create().key("updatableType").name("to be deleted"));
@@ -217,6 +222,55 @@ public class DistributionSetTypeManagementTest extends AbstractJpaIntegrationTes
.containsOnly(runtimeType);
}
@Test
@Description("Verifies that the quota for software module types per distribution set type is enforced as expected.")
public void quotaMaxSoftwareModuleTypes() {
final int quota = quotaManagement.getMaxSoftwareModuleTypesPerDistributionSetType();
// create software module types
final List<Long> moduleTypeIds = Lists.newArrayList();
for (int i = 0; i < quota + 1; ++i) {
final SoftwareModuleTypeCreate smCreate = entityFactory.softwareModuleType().create().name("smType_" + i)
.description("smType_" + i).maxAssignments(1).colour("blue").key("smType_" + i);
moduleTypeIds.add(softwareModuleTypeManagement.create(smCreate).getId());
}
// assign all types at once
final DistributionSetType dsType1 = distributionSetTypeManagement
.create(entityFactory.distributionSetType().create().key("dst1").name("dst1"));
assertThatExceptionOfType(QuotaExceededException.class).isThrownBy(
() -> distributionSetTypeManagement.assignMandatorySoftwareModuleTypes(dsType1.getId(), moduleTypeIds));
assertThatExceptionOfType(QuotaExceededException.class).isThrownBy(
() -> distributionSetTypeManagement.assignOptionalSoftwareModuleTypes(dsType1.getId(), moduleTypeIds));
// assign as many mandatory modules as possible
final DistributionSetType dsType2 = distributionSetTypeManagement
.create(entityFactory.distributionSetType().create().key("dst2").name("dst2"));
distributionSetTypeManagement.assignMandatorySoftwareModuleTypes(dsType2.getId(),
moduleTypeIds.subList(0, quota));
assertThat(distributionSetTypeManagement.get(dsType2.getId())).isNotEmpty();
assertThat(distributionSetTypeManagement.get(dsType2.getId()).get().getMandatoryModuleTypes().size())
.isEqualTo(quota);
// assign one more to trigger the quota exceeded error
assertThatExceptionOfType(QuotaExceededException.class)
.isThrownBy(() -> distributionSetTypeManagement.assignMandatorySoftwareModuleTypes(dsType2.getId(),
Collections.singletonList(moduleTypeIds.get(quota))));
// assign as many optional modules as possible
final DistributionSetType dsType3 = distributionSetTypeManagement
.create(entityFactory.distributionSetType().create().key("dst3").name("dst3"));
distributionSetTypeManagement.assignOptionalSoftwareModuleTypes(dsType3.getId(),
moduleTypeIds.subList(0, quota));
assertThat(distributionSetTypeManagement.get(dsType3.getId())).isNotEmpty();
assertThat(distributionSetTypeManagement.get(dsType3.getId()).get().getOptionalModuleTypes().size())
.isEqualTo(quota);
// assign one more to trigger the quota exceeded error
assertThatExceptionOfType(QuotaExceededException.class)
.isThrownBy(() -> distributionSetTypeManagement.assignOptionalSoftwareModuleTypes(dsType3.getId(),
Collections.singletonList(moduleTypeIds.get(quota))));
}
@Test
@Description("Tests the successfull update of used distribution set type meta data which is in fact allowed.")
public void updateAssignedDistributionSetTypeMetaData() {

View File

@@ -8,6 +8,9 @@
*/
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.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -39,6 +42,7 @@ import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException;
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout;
@@ -70,9 +74,6 @@ import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import ru.yandex.qatools.allure.annotations.Description;
import ru.yandex.qatools.allure.annotations.Features;
import ru.yandex.qatools.allure.annotations.Step;
@@ -1257,6 +1258,87 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
validateRolloutActionStatus(myRollout.getId(), expectedTargetCountStatus);
}
@Test
@Description("Verify that a rollout cannot be created if the 'max targets per rollout group' quota is violated.")
public void createRolloutFailsIfQuotaGroupQuotaIsViolated() throws Exception {
final int maxTargets = quotaManagement.getMaxTargetsPerRolloutGroup();
final int amountTargetsForRollout = maxTargets + 1;
final int amountGroups = 1;
final String successCondition = "50";
final String errorCondition = "80";
final String rolloutName = "rolloutTest";
final String targetPrefixName = rolloutName;
final DistributionSet distributionSet = testdataFactory.createDistributionSet("dsFor" + rolloutName);
testdataFactory.createTargets(amountTargetsForRollout, targetPrefixName + "-", targetPrefixName);
final RolloutGroupConditions conditions = new RolloutGroupConditionBuilder().withDefaults()
.successCondition(RolloutGroupSuccessCondition.THRESHOLD, successCondition)
.errorCondition(RolloutGroupErrorCondition.THRESHOLD, errorCondition)
.errorAction(RolloutGroupErrorAction.PAUSE, null).build();
assertThatExceptionOfType(QuotaExceededException.class).isThrownBy(() -> rolloutManagement.create(
entityFactory.rollout().create().name(rolloutName).description(rolloutName)
.targetFilterQuery("controllerId==" + targetPrefixName + "-*").set(distributionSet),
amountGroups, conditions));
}
@Test
@Description("Verify that a rollout cannot be created based on group definitions if the 'max targets per rollout group' quota is violated for one of the groups.")
public void createRolloutWithGroupDefinitionsFailsIfQuotaGroupQuotaIsViolated() throws Exception {
final int maxTargets = quotaManagement.getMaxTargetsPerRolloutGroup();
final int amountTargetsForRollout = maxTargets * 2 + 2;
final String rolloutName = "rolloutTest";
final String targetPrefixName = rolloutName;
final DistributionSet distributionSet = testdataFactory.createDistributionSet("dsFor" + rolloutName);
testdataFactory.createTargets(amountTargetsForRollout, targetPrefixName + "-", targetPrefixName);
final RolloutGroupConditions conditions = new RolloutGroupConditionBuilder().withDefaults().build();
// create group definitions
final RolloutGroupCreate group1 = entityFactory.rolloutGroup().create().conditions(conditions).name("group1")
.targetPercentage(50.0F);
final RolloutGroupCreate group2 = entityFactory.rolloutGroup().create().conditions(conditions).name("group2")
.targetPercentage(100.0F);
// group1 exceeds the quota
assertThatExceptionOfType(QuotaExceededException.class).isThrownBy(() -> rolloutManagement.create(
entityFactory.rollout().create().name(rolloutName).description(rolloutName)
.targetFilterQuery("controllerId==" + targetPrefixName + "-*").set(distributionSet),
Arrays.asList(group1, group2), conditions));
// create group definitions
final RolloutGroupCreate group3 = entityFactory.rolloutGroup().create().conditions(conditions).name("group3")
.targetPercentage(1.0F);
final RolloutGroupCreate group4 = entityFactory.rolloutGroup().create().conditions(conditions).name("group4")
.targetPercentage(100.0F);
// group4 exceeds the quota
assertThatExceptionOfType(QuotaExceededException.class).isThrownBy(() -> rolloutManagement.create(
entityFactory.rollout().create().name(rolloutName).description(rolloutName)
.targetFilterQuery("controllerId==" + targetPrefixName + "-*").set(distributionSet),
Arrays.asList(group3, group4), conditions));
// create group definitions
final RolloutGroupCreate group5 = entityFactory.rolloutGroup().create().conditions(conditions).name("group5")
.targetPercentage(33.3F);
final RolloutGroupCreate group6 = entityFactory.rolloutGroup().create().conditions(conditions).name("group6")
.targetPercentage(66.6F);
final RolloutGroupCreate group7 = entityFactory.rolloutGroup().create().conditions(conditions).name("group7")
.targetPercentage(100.0F);
// should work fine
assertThat(rolloutManagement.create(
entityFactory.rollout().create().name(rolloutName).description(rolloutName)
.targetFilterQuery("controllerId==" + targetPrefixName + "-*").set(distributionSet),
Arrays.asList(group5, group6, group7), conditions)).isNotNull();
}
@Test
@Description("Verify the creation and the automatic start of a rollout.")
public void createAndAutoStartRollout() throws Exception {
@@ -1308,7 +1390,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
}
@Test
@Description("Verify the creation of a Rollout with a groups definition.")
@Description("Verify the creation of a rollout with a groups definition.")
public void createRolloutWithGroupDefinition() throws Exception {
final String rolloutName = "rolloutTest3";
@@ -1355,7 +1437,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
assertThat(myRollout.getTotalTargets()).isEqualTo(amountTargetsInGroup1and2 + amountTargetsInGroup1);
final List<RolloutGroup> groups = rolloutGroupManagement.findByRollout(PAGE, myRollout.getId()).getContent();
;
assertThat(groups.get(0).getStatus()).isEqualTo(RolloutGroupStatus.READY);
assertThat(groups.get(0).getTotalTargets()).isEqualTo(amountTargetsInGroup1);
@@ -1368,7 +1450,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
}
@Test
@Description("Verify Exception when a Rollout with Group definition is created that does not address all targets")
@Description("Verify rollout creation fails if group definition does not address all targets")
public void createRolloutWithGroupsNotMatchingTargets() throws Exception {
final String rolloutName = "rolloutTest4";
final int amountTargetsForRollout = 500;
@@ -1389,7 +1471,7 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
}
@Test
@Description("Verify Exception when a Rollout with Group definition is created that contains an illegal percentage")
@Description("Verify rollout creation fails if group definition specifies illegal target percentage")
public void createRolloutWithIllegalPercentage() throws Exception {
final String rolloutName = "rolloutTest6";
final int amountTargetsForRollout = 10;
@@ -1410,18 +1492,18 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
}
@Test
@Description("Verify Exception when a Rollout is created with too much groups")
@Description("Verify rollout creation fails if the 'max rollout groups per rollout' quota is violated.")
public void createRolloutWithIllegalAmountOfGroups() throws Exception {
final String rolloutName = "rolloutTest5";
final int amountTargetsForRollout = 10;
final int illegalGroupAmount = 501;
final int targets = 10;
final int maxGroups = quotaManagement.getMaxRolloutGroupsPerRollout();
final RolloutGroupConditions conditions = new RolloutGroupConditionBuilder().withDefaults().build();
final RolloutCreate myRollout = generateTargetsAndRollout(rolloutName, amountTargetsForRollout);
final RolloutCreate rollout = generateTargetsAndRollout(rolloutName, targets);
assertThatExceptionOfType(ValidationException.class)
.isThrownBy(() -> rolloutManagement.create(myRollout, illegalGroupAmount, conditions))
.withMessageContaining("not be greater than " + quotaManagement.getMaxRolloutGroupsPerRollout());
assertThatExceptionOfType(QuotaExceededException.class)
.isThrownBy(() -> rolloutManagement.create(rollout, maxGroups + 1, conditions))
.withMessageContaining("not be greater than " + maxGroups);
}

View File

@@ -15,6 +15,7 @@ import static org.junit.Assert.fail;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
@@ -23,6 +24,7 @@ import org.apache.commons.lang3.RandomUtils;
import org.eclipse.hawkbit.repository.builder.SoftwareModuleMetadataCreate;
import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleMetadata;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
@@ -361,13 +363,14 @@ public class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest {
public void deleteSoftwareModulesWithSharedArtifact() throws IOException {
// Init artifact binary data, target and DistributionSets
final byte[] source = RandomUtils.nextBytes(1024);
final int artifactSize = 1024;
final byte[] source = RandomUtils.nextBytes(artifactSize);
// [STEP1]: Create SoftwareModuleX and add a new ArtifactX
SoftwareModule moduleX = createSoftwareModuleWithArtifacts(osType, "modulex", "v1.0", 0);
// [STEP2]: Create newArtifactX and add it to SoftwareModuleX
artifactManagement.create(new ByteArrayInputStream(source), moduleX.getId(), "artifactx", false);
artifactManagement.create(new ByteArrayInputStream(source), moduleX.getId(), "artifactx", false, artifactSize);
moduleX = softwareModuleManagement.get(moduleX.getId()).get();
final Artifact artifactX = moduleX.getArtifacts().iterator().next();
@@ -375,7 +378,7 @@ public class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest {
SoftwareModule moduleY = createSoftwareModuleWithArtifacts(osType, "moduley", "v1.0", 0);
// [STEP4]: Assign the same ArtifactX to SoftwareModuleY
artifactManagement.create(new ByteArrayInputStream(source), moduleY.getId(), "artifactx", false);
artifactManagement.create(new ByteArrayInputStream(source), moduleY.getId(), "artifactx", false, artifactSize);
moduleY = softwareModuleManagement.get(moduleY.getId()).get();
final Artifact artifactY = moduleY.getArtifacts().iterator().next();
@@ -403,20 +406,21 @@ public class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest {
public void deleteMultipleSoftwareModulesWhichShareAnArtifact() throws IOException {
// Init artifact binary data, target and DistributionSets
final byte[] source = RandomUtils.nextBytes(1024);
final int artifactSize = 1024;
final byte[] source = RandomUtils.nextBytes(artifactSize);
final Target target = testdataFactory.createTarget();
// [STEP1]: Create SoftwareModuleX and add a new ArtifactX
SoftwareModule moduleX = createSoftwareModuleWithArtifacts(osType, "modulex", "v1.0", 0);
artifactManagement.create(new ByteArrayInputStream(source), moduleX.getId(), "artifactx", false);
artifactManagement.create(new ByteArrayInputStream(source), moduleX.getId(), "artifactx", false, artifactSize);
moduleX = softwareModuleManagement.get(moduleX.getId()).get();
final Artifact artifactX = moduleX.getArtifacts().iterator().next();
// [STEP2]: Create SoftwareModuleY and add the same ArtifactX
SoftwareModule moduleY = createSoftwareModuleWithArtifacts(osType, "moduley", "v1.0", 0);
artifactManagement.create(new ByteArrayInputStream(source), moduleY.getId(), "artifactx", false);
artifactManagement.create(new ByteArrayInputStream(source), moduleY.getId(), "artifactx", false, artifactSize);
moduleY = softwareModuleManagement.get(moduleY.getId()).get();
final Artifact artifactY = moduleY.getArtifacts().iterator().next();
@@ -462,9 +466,10 @@ public class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest {
SoftwareModule softwareModule = softwareModuleManagement.create(entityFactory.softwareModule().create()
.type(type).name(name).version(version).description("description of artifact " + name));
final int artifactSize = 5 * 1024;
for (int i = 0; i < numberArtifacts; i++) {
artifactManagement.create(new RandomGeneratedInputStream(5 * 1024), softwareModule.getId(),
"file" + (i + 1), false);
artifactManagement.create(new RandomGeneratedInputStream(artifactSize), softwareModule.getId(),
"file" + (i + 1), false, artifactSize);
}
// Verify correct Creation of SoftwareModule and corresponding artifacts
@@ -652,14 +657,58 @@ public class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest {
assertThat(softwareModuleMetadata.get(0).getEntityId()).isEqualTo(ah.getId());
}
@Test
@Description("Verifies the enforcement of the metadata quota per software module.")
public void createSoftwareModuleMetadataUntilQuotaIsExceeded() {
// add meta data one by one
final SoftwareModule module = testdataFactory.createSoftwareModuleApp("m1");
final int maxMetaData = quotaManagement.getMaxMetaDataEntriesPerSoftwareModule();
for (int i = 0; i < maxMetaData; ++i) {
softwareModuleManagement.createMetaData(
entityFactory.softwareModuleMetadata().create(module.getId()).key("k" + i).value("v" + i));
}
// quota exceeded
assertThatExceptionOfType(QuotaExceededException.class)
.isThrownBy(() -> softwareModuleManagement.createMetaData(entityFactory.softwareModuleMetadata()
.create(module.getId()).key("k" + maxMetaData).value("v" + maxMetaData)));
// add multiple meta data entries at once
final SoftwareModule module2 = testdataFactory.createSoftwareModuleApp("m2");
final List<SoftwareModuleMetadataCreate> create = new ArrayList<>();
for (int i = 0; i < maxMetaData + 1; ++i) {
create.add(entityFactory.softwareModuleMetadata().create(module2.getId()).key("k" + i).value("v" + i));
}
// quota exceeded
assertThatExceptionOfType(QuotaExceededException.class)
.isThrownBy(() -> softwareModuleManagement.createMetaData(create));
// add some meta data entries
final SoftwareModule module3 = testdataFactory.createSoftwareModuleApp("m3");
final int firstHalf = Math.round(maxMetaData / 2);
for (int i = 0; i < firstHalf; ++i) {
softwareModuleManagement.createMetaData(
entityFactory.softwareModuleMetadata().create(module3.getId()).key("k" + i).value("v" + i));
}
// add too many data entries
final int secondHalf = maxMetaData - firstHalf;
final List<SoftwareModuleMetadataCreate> create2 = new ArrayList<>();
for (int i = 0; i < secondHalf + 1; ++i) {
create2.add(entityFactory.softwareModuleMetadata().create(module3.getId()).key("kk" + i).value("vv" + i));
}
// quota exceeded
assertThatExceptionOfType(QuotaExceededException.class)
.isThrownBy(() -> softwareModuleManagement.createMetaData(create2));
}
@Test
@Description("Checks that metadata for a software module cannot be created for an existing key.")
public void createSoftwareModuleMetadataFailsIfKeyExists() {
final String knownKey1 = "myKnownKey1";
final String knownValue1 = "myKnownValue1";
final String knownValue2 = "myKnownValue2";
final SoftwareModule ah = testdataFactory.createSoftwareModuleApp();
softwareModuleManagement.createMetaData(entityFactory.softwareModuleMetadata().create(ah.getId()).key(knownKey1)
@@ -765,41 +814,43 @@ public class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest {
public void findAllSoftwareModuleMetadataBySwId() {
final SoftwareModule sw1 = testdataFactory.createSoftwareModuleApp();
final int metadataCountSw1 = 8;
final SoftwareModule sw2 = testdataFactory.createSoftwareModuleOs();
final int metadataCountSw2 = 10;
for (int index = 0; index < 10; index++) {
for (int index = 0; index < metadataCountSw1; index++) {
softwareModuleManagement.createMetaData(entityFactory.softwareModuleMetadata().create(sw1.getId())
.key("key" + index).value("value" + index).targetVisible(true));
}
for (int index = 0; index < 20; index++) {
for (int index = 0; index < metadataCountSw2; index++) {
softwareModuleManagement.createMetaData(entityFactory.softwareModuleMetadata().create(sw2.getId())
.key("key" + index).value("value" + index).targetVisible(false));
}
Page<SoftwareModuleMetadata> metadataOfSw1 = softwareModuleManagement
Page<SoftwareModuleMetadata> metadataSw1 = softwareModuleManagement
.findMetaDataBySoftwareModuleId(new PageRequest(0, 100), sw1.getId());
Page<SoftwareModuleMetadata> metadataOfSw2 = softwareModuleManagement
Page<SoftwareModuleMetadata> metadataSw2 = softwareModuleManagement
.findMetaDataBySoftwareModuleId(new PageRequest(0, 100), sw2.getId());
assertThat(metadataOfSw1.getNumberOfElements()).isEqualTo(10);
assertThat(metadataOfSw1.getTotalElements()).isEqualTo(10);
assertThat(metadataSw1.getNumberOfElements()).isEqualTo(metadataCountSw1);
assertThat(metadataSw1.getTotalElements()).isEqualTo(metadataCountSw1);
assertThat(metadataOfSw2.getNumberOfElements()).isEqualTo(20);
assertThat(metadataOfSw2.getTotalElements()).isEqualTo(20);
assertThat(metadataSw2.getNumberOfElements()).isEqualTo(metadataCountSw2);
assertThat(metadataSw2.getTotalElements()).isEqualTo(metadataCountSw2);
metadataOfSw1 = softwareModuleManagement.findMetaDataBySoftwareModuleIdAndTargetVisible(new PageRequest(0, 100),
metadataSw1 = softwareModuleManagement.findMetaDataBySoftwareModuleIdAndTargetVisible(new PageRequest(0, 100),
sw1.getId());
metadataOfSw2 = softwareModuleManagement.findMetaDataBySoftwareModuleIdAndTargetVisible(new PageRequest(0, 100),
metadataSw2 = softwareModuleManagement.findMetaDataBySoftwareModuleIdAndTargetVisible(new PageRequest(0, 100),
sw2.getId());
assertThat(metadataOfSw1.getNumberOfElements()).isEqualTo(10);
assertThat(metadataOfSw1.getTotalElements()).isEqualTo(10);
assertThat(metadataSw1.getNumberOfElements()).isEqualTo(metadataCountSw1);
assertThat(metadataSw1.getTotalElements()).isEqualTo(metadataCountSw1);
assertThat(metadataOfSw2.getNumberOfElements()).isEqualTo(0);
assertThat(metadataOfSw2.getTotalElements()).isEqualTo(0);
assertThat(metadataSw2.getNumberOfElements()).isEqualTo(0);
assertThat(metadataSw2.getTotalElements()).isEqualTo(0);
}
}

View File

@@ -134,13 +134,13 @@ public class SystemManagementTest extends AbstractJpaIntegrationTest {
private void createTestArtifact(final byte[] random) {
final SoftwareModule sm = testdataFactory.createSoftwareModuleOs();
artifactManagement.create(new ByteArrayInputStream(random), sm.getId(), "file1", false);
artifactManagement.create(new ByteArrayInputStream(random), sm.getId(), "file1", false, random.length);
}
private void createDeletedTestArtifact(final byte[] random) {
final DistributionSet ds = testdataFactory.createDistributionSet("deleted garbage", true);
ds.getModules().stream().forEach(module -> {
artifactManagement.create(new ByteArrayInputStream(random), module.getId(), "file1", false);
artifactManagement.create(new ByteArrayInputStream(random), module.getId(), "file1", false, random.length);
softwareModuleManagement.delete(module.getId());
});
}

View File

@@ -8,6 +8,8 @@
*/
package org.eclipse.hawkbit.repository.jpa;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -24,6 +26,7 @@ import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedE
import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.TargetFilterQueryCreatedEvent;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.QuotaExceededException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Target;
@@ -34,8 +37,6 @@ import org.junit.Test;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import static org.assertj.core.api.Assertions.assertThat;
import ru.yandex.qatools.allure.annotations.Description;
import ru.yandex.qatools.allure.annotations.Features;
import ru.yandex.qatools.allure.annotations.Stories;
@@ -97,6 +98,20 @@ public class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest
targetFilterQueryManagement.getByName(filterName).get());
}
@Test
@Description("Create a target filter query with an auto-assign distribution set and a query string that addresses too many targets.")
public void createTargetFilterQueryThatExceedsQuota() {
// create targets
final int maxTargets = quotaManagement.getMaxTargetsPerAutoAssignment();
testdataFactory.createTargets(maxTargets + 1, "target%s");
final DistributionSet set = testdataFactory.createDistributionSet();
// creation is supposed to work as there is no distribution set
assertThatExceptionOfType(QuotaExceededException.class).isThrownBy(() -> targetFilterQueryManagement.create(
entityFactory.targetFilterQuery().create().name("testfilter").set(set.getId()).query("name==target*")));
}
@Test
@Description("Test searching a target filter query.")
public void searchTargetFilterQuery() {
@@ -181,6 +196,43 @@ public class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest
}
@Test
@Description("Assigns a distribution set to an existing filter query and verifies that the quota 'max targets per auto assignment' is enforced.")
public void assignDistributionSetToTargetFilterQueryThatExceedsQuota() {
// create targets
final int maxTargets = quotaManagement.getMaxTargetsPerAutoAssignment();
testdataFactory.createTargets(maxTargets + 1, "target%s");
final DistributionSet distributionSet = testdataFactory.createDistributionSet();
// creation is supposed to work as there is no distribution set
final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement
.create(entityFactory.targetFilterQuery().create().name("testfilter").query("name==target*"));
// assigning a distribution set is supposed to fail as the query
// addresses too many targets
assertThatExceptionOfType(QuotaExceededException.class).isThrownBy(() -> targetFilterQueryManagement
.updateAutoAssignDS(targetFilterQuery.getId(), distributionSet.getId()));
}
@Test
@Description("Updates an existing filter query with a query string that addresses too many targets.")
public void updateTargetFilterQueryWithQueryThatExceedsQuota() {
// create targets
final int maxTargets = quotaManagement.getMaxTargetsPerAutoAssignment();
testdataFactory.createTargets(maxTargets + 1, "target%s");
final DistributionSet set = testdataFactory.createDistributionSet();
// creation is supposed to work as the query does not exceed the quota
final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement.create(
entityFactory.targetFilterQuery().create().name("testfilter").set(set.getId()).query("name==foo"));
// update with a query string that addresses too many targets
assertThatExceptionOfType(QuotaExceededException.class).isThrownBy(() -> targetFilterQueryManagement
.update(entityFactory.targetFilterQuery().update(targetFilterQuery.getId()).query("name==target*")));
}
@Test
@Description("Test removing distribution set while it has a relation to a target filter query")
public void removeAssignDistributionSet() {

View File

@@ -10,6 +10,15 @@
# Quota - START
hawkbit.server.security.dos.maxStatusEntriesPerAction=10
hawkbit.server.security.dos.maxAttributeEntriesPerTarget=10
hawkbit.server.security.dos.maxMetaDataEntriesPerSoftwareModule=10
hawkbit.server.security.dos.maxRolloutGroupsPerRollout=20
hawkbit.server.security.dos.maxMessagesPerActionStatus=10
hawkbit.server.security.dos.maxMetaDataEntriesPerDistributionSet=10
hawkbit.server.security.dos.maxSoftwareModuleTypesPerDistributionSetType=10
hawkbit.server.security.dos.maxSoftwareModulesPerDistributionSet=10
hawkbit.server.security.dos.maxArtifactsPerSoftwareModule=10
hawkbit.server.security.dos.maxTargetsPerRolloutGroup=1000
hawkbit.server.security.dos.maxArtifactSize=1000000
# Quota - END
# Debug utility functions - START

View File

@@ -276,7 +276,11 @@ public abstract class AbstractIntegrationTest {
}
protected DistributionSetMetadata createDistributionSetMetadata(final Long dsId, final MetaData md) {
return distributionSetManagement.createMetaData(dsId, Collections.singletonList(md)).get(0);
return createDistributionSetMetadata(dsId, Collections.singletonList(md)).get(0);
}
protected List<DistributionSetMetadata> createDistributionSetMetadata(final Long dsId, final List<MetaData> md) {
return distributionSetManagement.createMetaData(dsId, md);
}
protected Long getOsModule(final DistributionSet ds) {

View File

@@ -463,8 +463,10 @@ public class TestdataFactory {
public List<Artifact> createArtifacts(final Long moduleId) {
final List<Artifact> artifacts = new ArrayList<>();
for (int i = 0; i < 3; i++) {
final InputStream stubInputStream = IOUtils.toInputStream("some test data" + i, Charset.forName("UTF-8"));
artifacts.add(artifactManagement.create(stubInputStream, moduleId, "filename" + i, false));
final String artifactData = "some test data" + i;
final InputStream stubInputStream = IOUtils.toInputStream(artifactData, Charset.forName("UTF-8"));
artifacts.add(
artifactManagement.create(stubInputStream, moduleId, "filename" + i, false, artifactData.length()));
}