diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java index 814917fe0..9b8dbbf52 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java @@ -800,6 +800,25 @@ public interface TargetManagement { @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) boolean existsByControllerId(@NotEmpty String controllerId); + /** + * Verify if a target matches a specific target filter query, does not have + * a specific DS already assigned and is compatible with it. + * + * @param controllerId + * of the {@link org.eclipse.hawkbit.repository.model.Target} to + * check + * @param distributionSetId + * of the + * {@link org.eclipse.hawkbit.repository.model.DistributionSet} + * to consider + * @param targetFilterQuery + * to execute + * @return true if it matches + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_READ_TARGET) + boolean isTargetMatchingQueryAndDSNotAssignedAndCompatible(@NotNull String controllerId, long distributionSetId, + @NotNull String targetFilterQuery); + /** * Creates a list of target meta data entries. * diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/autoassign/AutoAssignExecutor.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/autoassign/AutoAssignExecutor.java index b97f2f063..16d52bd3c 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/autoassign/AutoAssignExecutor.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/autoassign/AutoAssignExecutor.java @@ -12,7 +12,6 @@ package org.eclipse.hawkbit.repository.autoassign; * An interface declaration which contains the check for the auto assignment * logic. */ -@FunctionalInterface public interface AutoAssignExecutor { /** @@ -20,6 +19,14 @@ public interface AutoAssignExecutor { * triggers the check and assignment to targets that don't have the design DS * yet */ - void check(); + void checkAllTargets(); + + /** + * Method performs an auto assign check for a specific device only + * + * @param controllerId + * of the device to check + */ + void checkSingleTarget(String controllerId); } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java index 9d1881e93..5867894df 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java @@ -16,6 +16,7 @@ import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; @@ -53,6 +54,7 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaTargetType; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_; import org.eclipse.hawkbit.repository.jpa.model.TargetMetadataCompositeKey; import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility; +import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder; import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications; import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper; import org.eclipse.hawkbit.repository.model.DistributionSet; @@ -853,6 +855,25 @@ public class JpaTargetManagement implements TargetManagement { return targetRepository.exists(TargetSpecifications.hasControllerId(controllerId)); } + @Override + public boolean isTargetMatchingQueryAndDSNotAssignedAndCompatible(final String controllerId, + final long distributionSetId, final String targetFilterQuery) { + RSQLUtility.validateRsqlFor(targetFilterQuery, TargetFields.class); + final DistributionSet ds = distributionSetManagement.get(distributionSetId) + .orElseThrow(() -> new EntityNotFoundException(DistributionSet.class, distributionSetId)); + final Long distSetTypeId = ds.getType().getId(); + final List> specList = Arrays.asList( + RSQLUtility.buildRsqlSpecification(targetFilterQuery, TargetFields.class, virtualPropertyReplacer, + database), + TargetSpecifications.hasNotDistributionSetInActions(distributionSetId), + TargetSpecifications.isCompatibleWithDistributionSetType(distSetTypeId), + TargetSpecifications.hasControllerId(controllerId)); + + final Specification combinedSpecification = Objects + .requireNonNull(SpecificationsBuilder.combineWithAnd(specList)); + return targetRepository.exists(combinedSpecification); + } + @Override public Page findByControllerAttributesRequested(final Pageable pageReq) { return JpaManagementHelper.findAllWithCountBySpec(targetRepository, pageReq, diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignChecker.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignChecker.java index 27c324106..cda9e8295 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignChecker.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignChecker.java @@ -8,6 +8,7 @@ */ package org.eclipse.hawkbit.repository.jpa.autoassign; +import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @@ -64,12 +65,21 @@ public class AutoAssignChecker extends AbstractAutoAssignExecutor { @Override @Transactional(propagation = Propagation.REQUIRES_NEW) - public void check() { + public void checkAllTargets() { LOGGER.debug("Auto assign check call for tenant {} started", getTenantAware().getCurrentTenant()); forEachFilterWithAutoAssignDS(this::checkByTargetFilterQueryAndAssignDS); LOGGER.debug("Auto assign check call for tenant {} finished", getTenantAware().getCurrentTenant()); } + @Override + public void checkSingleTarget(String controllerId) { + LOGGER.debug("Auto assign check call for tenant {} and device {} started", getTenantAware().getCurrentTenant(), + controllerId); + forEachFilterWithAutoAssignDS(filter -> checkForDevice(controllerId, filter)); + LOGGER.debug("Auto assign check call for tenant {} and device {} finished", getTenantAware().getCurrentTenant(), + controllerId); + } + /** * Fetches the distribution set, gets all controllerIds and assigns the DS * to them. Catches PersistenceException and own exceptions derived from @@ -104,4 +114,23 @@ public class AutoAssignChecker extends AbstractAutoAssignExecutor { LOGGER.debug("Auto assign check call for tenant {} and target filter query id {} finished", getTenantAware().getCurrentTenant(), targetFilterQuery.getId()); } + + private void checkForDevice(final String controllerId, final TargetFilterQuery targetFilterQuery) { + LOGGER.debug("Auto assign check call for tenant {} and target filter query id {} for device {} started", + getTenantAware().getCurrentTenant(), targetFilterQuery.getId(), controllerId); + try { + final boolean controllerIdMatches = targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatible( + controllerId, targetFilterQuery.getAutoAssignDistributionSet().getId(), + targetFilterQuery.getQuery()); + + if (controllerIdMatches) { + runTransactionalAssignment(targetFilterQuery, Collections.singletonList(controllerId)); + } + + } catch (final PersistenceException | AbstractServerRtException e) { + LOGGER.error("Error during auto assign check of target filter query id {}", targetFilterQuery.getId(), e); + } + LOGGER.debug("Auto assign check call for tenant {} and target filter query id {} finished", + getTenantAware().getCurrentTenant(), targetFilterQuery.getId()); + } } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignScheduler.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignScheduler.java index 4b3b12c0a..1ea0d6429 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignScheduler.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignScheduler.java @@ -83,7 +83,7 @@ public class AutoAssignScheduler { try { LOGGER.debug("Auto assign scheduled execution has aquired lock and started for each tenant."); - systemManagement.forEachTenant(tenant -> autoAssignExecutor.check()); + systemManagement.forEachTenant(tenant -> autoAssignExecutor.checkAllTargets()); } finally { lock.unlock(); LOGGER.debug("Auto assign scheduled execution has released lock and finished."); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementTest.java index 4a6de4d87..3ffd3d2b7 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementTest.java @@ -48,6 +48,8 @@ import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.InvalidTargetAddressException; +import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException; +import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException; import org.eclipse.hawkbit.repository.exception.TenantNotExistException; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; import org.eclipse.hawkbit.repository.jpa.model.JpaTargetMetadata; @@ -1256,6 +1258,83 @@ class TargetManagementTest extends AbstractJpaIntegrationTest { validateFoundTargetsByRsql(rsqlOrControllerIdNotEqualFilter, controllerId1, controllerId2); } + @Test + @Description("Target matches filter.") + void matchesFilter() { + final Target target = createTargetWithMetadata("target1", 2); + final DistributionSet ds = testdataFactory.createDistributionSet(); + final String filter = "metadata.key1==target1-value1"; + + assertThat(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatible(target.getControllerId(), + ds.getId(), filter)).isTrue(); + } + + @Test + @Description("Target does not matches filter.") + void matchesFilterWrongFilter() { + final Target target = testdataFactory.createTarget(); + final DistributionSet ds = testdataFactory.createDistributionSet(); + final String filter = "metadata.key==not_existing"; + + assertThat(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatible(target.getControllerId(), + ds.getId(), filter)).isFalse(); + } + + @Test + @Description("Target matches filter but DS already assigned.") + void matchesFilterDsAssigned() { + final Target target = testdataFactory.createTarget(); + final DistributionSet ds1 = testdataFactory.createDistributionSet(); + final DistributionSet ds2 = testdataFactory.createDistributionSet(); + assignDistributionSet(ds1, target); + assignDistributionSet(ds2, target); + final String filter = "name==*"; + + assertThat(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatible(target.getControllerId(), + ds1.getId(), filter)).isFalse(); + } + + @Test + @Description("Target matches filter for DS with wrong type.") + void matchesFilterWrongType() { + final TargetType type = testdataFactory.createTargetType("type", Collections.emptyList()); + final Target target = testdataFactory.createTarget("target", "target", type.getId()); + final DistributionSet ds = testdataFactory.createDistributionSet(); + + assertThat(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatible(target.getControllerId(), + ds.getId(), "name==*")).isFalse(); + } + + @Test + @Description("Target matches filter that is invalid.") + void matchesFilterInvalidFilter() { + final String target = testdataFactory.createTarget().getControllerId(); + final Long ds = testdataFactory.createDistributionSet().getId(); + + assertThatExceptionOfType(RSQLParameterSyntaxException.class).isThrownBy(() -> targetManagement + .isTargetMatchingQueryAndDSNotAssignedAndCompatible(target, ds, "invalid_syntax")); + assertThatExceptionOfType(RSQLParameterUnsupportedFieldException.class).isThrownBy(() -> targetManagement + .isTargetMatchingQueryAndDSNotAssignedAndCompatible(target, ds, "invalid_field==1")); + } + + @Test + @Description("Target matches filter for not existing target.") + void matchesFilterTargetNotExists() { + final DistributionSet ds = testdataFactory.createDistributionSet(); + + assertThat(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatible("notExisting", ds.getId(), + "name==*")).isFalse(); + } + + @Test + @Description("Target matches filter for not existing DS.") + void matchesFilterDsNotExists() { + final String target = testdataFactory.createTarget().getControllerId(); + + assertThatExceptionOfType(EntityNotFoundException.class).isThrownBy( + () -> targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatible(target, 123, "name==*")); + } + private void validateFoundTargetsByRsql(final String rsqlFilter, final String... controllerIds) { final Slice foundTargetsByMetadataAndControllerId = targetManagement.findByRsql(PAGE, rsqlFilter); final long foundTargetsByMetadataAndControllerIdCount = targetManagement.countByRsql(rsqlFilter); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignCheckerIntTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignCheckerIntTest.java new file mode 100644 index 000000000..4068d9b3f --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignCheckerIntTest.java @@ -0,0 +1,411 @@ +/** + * Copyright (c) 2015 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.autoassign; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException; +import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; +import org.eclipse.hawkbit.repository.jpa.ActionRepository; +import org.eclipse.hawkbit.repository.model.Action; +import org.eclipse.hawkbit.repository.model.Action.ActionType; +import org.eclipse.hawkbit.repository.model.Action.Status; +import org.eclipse.hawkbit.repository.model.DistributionSet; +import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult; +import org.eclipse.hawkbit.repository.model.DistributionSetType; +import org.eclipse.hawkbit.repository.model.Target; +import org.eclipse.hawkbit.repository.model.TargetFilterQuery; +import org.eclipse.hawkbit.repository.model.TargetType; +import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; + +import io.qameta.allure.Description; +import io.qameta.allure.Feature; +import io.qameta.allure.Step; +import io.qameta.allure.Story; + +/** + * Test class for {@link AutoAssignChecker}. + * + */ +@Feature("Component Tests - Repository") +@Story("Auto assign checker") +class AutoAssignCheckerIntTest extends AbstractJpaIntegrationTest { + + @Autowired + private AutoAssignChecker autoAssignChecker; + + @Autowired + private ActionRepository actionRepository; + + @Test + @Description("Verifies that a running action is auto canceled by a AutoAssignment which assigns another distribution-set.") + void autoAssignDistributionSetAndAutoCloseOldActions() { + + tenantConfigurationManagement + .addOrUpdateConfiguration(TenantConfigurationKey.REPOSITORY_ACTIONS_AUTOCLOSE_ENABLED, true); + + try { + final String knownControllerId = "controller12345"; + final DistributionSet firstDistributionSet = testdataFactory.createDistributionSet(); + final DistributionSet secondDistributionSet = testdataFactory.createDistributionSet("second"); + testdataFactory.createTarget(knownControllerId); + final DistributionSetAssignmentResult assignmentResult = assignDistributionSet(firstDistributionSet.getId(), + knownControllerId); + final Long manuallyAssignedActionId = getFirstAssignedActionId(assignmentResult); + + // target filter query that matches all targets + final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement + .create(entityFactory.targetFilterQuery().create().name("filterA").query("name==*")); + targetFilterQueryManagement.updateAutoAssignDS(entityFactory.targetFilterQuery() + .updateAutoAssign(targetFilterQuery.getId()).ds(secondDistributionSet.getId())); + // Run the check + autoAssignChecker.checkAllTargets(); + + // verify that manually created action is canceled and action + // created from AutoAssign is running + final List actionsByKnownTarget = deploymentManagement.findActionsByTarget(knownControllerId, PAGE) + .getContent(); + // should be 2 actions, one manually and one from the AutoAssign + assertThat(actionsByKnownTarget).hasSize(2); + // verify that manually assigned action is still running + assertThat(deploymentManagement.findAction(manuallyAssignedActionId).get().getStatus()) + .isEqualTo(Status.CANCELED); + // verify that AutoAssign created action is running + final Action rolloutCreatedAction = actionsByKnownTarget.stream() + .filter(action -> !action.getId().equals(manuallyAssignedActionId)).findAny().get(); + assertThat(rolloutCreatedAction.getStatus()).isEqualTo(Status.RUNNING); + assertThat(rolloutCreatedAction.getActionType()).isEqualTo(ActionType.FORCED); + } finally { + tenantConfigurationManagement + .addOrUpdateConfiguration(TenantConfigurationKey.REPOSITORY_ACTIONS_AUTOCLOSE_ENABLED, false); + } + } + + @Test + @Description("Test auto assignment of a DS to filtered targets") + void checkAutoAssign() { + + final DistributionSet setA = testdataFactory.createDistributionSet("dsA"); // will + // be + // auto + // assigned + final DistributionSet setB = testdataFactory.createDistributionSet("dsB"); + + // target filter query that matches all targets + final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement.updateAutoAssignDS(entityFactory + .targetFilterQuery() + .updateAutoAssign(targetFilterQueryManagement + .create(entityFactory.targetFilterQuery().create().name("filterA").query("name==*")).getId()) + .ds(setA.getId())); + + final String targetDsAIdPref = "targ"; + final List targets = testdataFactory.createTargets(25, targetDsAIdPref, + targetDsAIdPref.concat(" description")); + final int targetsCount = targets.size(); + + // assign set A to first 10 targets + assignDistributionSet(setA, targets.subList(0, 10)); + verifyThatTargetsHaveDistributionSetAssignment(setA, targets.subList(0, 10), targetsCount); + + // assign set B to first 5 targets + // they have now 2 DS in their action history and should not get updated + // with dsA + assignDistributionSet(setB, targets.subList(0, 5)); + verifyThatTargetsHaveDistributionSetAssignment(setB, targets.subList(0, 5), targetsCount); + + // assign set B to next 10 targets + assignDistributionSet(setB, targets.subList(10, 20)); + verifyThatTargetsHaveDistributionSetAssignment(setB, targets.subList(10, 20), targetsCount); + + // Count the number of targets that will be assigned with setA + assertThat(targetManagement.countByRsqlAndNonDSAndCompatible(setA.getId(), targetFilterQuery.getQuery())) + .isEqualTo(15); + + // Run the check + autoAssignChecker.checkAllTargets(); + + verifyThatTargetsHaveDistributionSetAssignment(setA, targets.subList(5, 25), targetsCount); + + // first 5 should keep their dsB, because they already had the dsA once + verifyThatTargetsHaveDistributionSetAssignment(setB, targets.subList(0, 5), targetsCount); + + verifyThatCreatedActionsAreInitiatedByCurrentUser(targetFilterQuery, setA, targets); + } + + @Test + @Description("Test auto assignment of a DS for a specific device") + void checkAutoAssignmentForDevice() { + + final DistributionSet toAssignDs = testdataFactory.createDistributionSet(); + + // target filter query that matches all targets + targetFilterQueryManagement.updateAutoAssignDS(entityFactory.targetFilterQuery() + .updateAutoAssign(targetFilterQueryManagement + .create(entityFactory.targetFilterQuery().create().name("filterA").query("name==*")).getId()) + .ds(toAssignDs.getId())); + + final List targets = testdataFactory.createTargets(25); + final int targetsCount = targets.size(); + + // Run the check + autoAssignChecker.checkSingleTarget(targets.get(0).getControllerId()); + + verifyThatTargetsHaveDistributionSetAssignment(toAssignDs, targets.subList(0, 1), targetsCount); + + verifyThatTargetsNotHaveDistributionSetAssignment(toAssignDs, targets.subList(1, 25)); + } + + @Test + @Description("Test auto assignment of an incomplete DS to filtered targets, that causes failures") + void checkAutoAssignWithFailures() { + + // incomplete distribution set that will be assigned + final DistributionSet setF = distributionSetManagement.create(entityFactory.distributionSet().create() + .name("dsA").version("1").type(testdataFactory.findOrCreateDefaultTestDsType())); + final DistributionSet setA = testdataFactory.createDistributionSet("dsA"); + final DistributionSet setB = testdataFactory.createDistributionSet("dsB"); + + final String targetDsAIdPref = "targA"; + final String targetDsFIdPref = "targB"; + + // target filter query that matches first bunch of targets, that should + // fail + assertThatExceptionOfType(IncompleteDistributionSetException.class).isThrownBy(() -> { + final Long filterId = targetFilterQueryManagement.create( + entityFactory.targetFilterQuery().create().name("filterA").query("id==" + targetDsFIdPref + "*")) + .getId(); + targetFilterQueryManagement + .updateAutoAssignDS(entityFactory.targetFilterQuery().updateAutoAssign(filterId).ds(setF.getId())); + }); + // target filter query that matches failed bunch of targets + targetFilterQueryManagement.create(entityFactory.targetFilterQuery().create().name("filterB") + .query("id==" + targetDsAIdPref + "*").autoAssignDistributionSet(setA.getId())); + + final List targetsF = testdataFactory.createTargets(10, targetDsFIdPref, + targetDsFIdPref.concat(" description")); + + final List targetsA = testdataFactory.createTargets(10, targetDsAIdPref, + targetDsAIdPref.concat(" description")); + + final int targetsCount = targetsA.size() + targetsF.size(); + + // assign set B to first 5 targets of fail group + assignDistributionSet(setB, targetsF.subList(0, 5)); + verifyThatTargetsHaveDistributionSetAssignment(setB, targetsF.subList(0, 5), targetsCount); + + // Run the check + autoAssignChecker.checkAllTargets(); + + // first 5 targets of the fail group should still have setB + verifyThatTargetsHaveDistributionSetAssignment(setB, targetsF.subList(0, 5), targetsCount); + + // all targets of A group should have received setA + verifyThatTargetsHaveDistributionSetAssignment(setA, targetsA, targetsCount); + + } + + /** + * @param set + * the expected distribution set + * @param targets + * the targets that should have it + */ + @Step + private void verifyThatTargetsHaveDistributionSetAssignment(final DistributionSet set, final List targets, + final int count) { + final List targetIds = targets.stream().map(Target::getId).collect(Collectors.toList()); + + final Slice targetsAll = targetManagement.findAll(PAGE); + assertThat(targetsAll).as("Count of targets").hasSize(count); + + for (final Target target : targetsAll) { + if (targetIds.contains(target.getId())) { + assertThat(deploymentManagement.getAssignedDistributionSet(target.getControllerId()).get()) + .as("assigned DS").isEqualTo(set); + } + } + + } + + @Step + private void verifyThatTargetsNotHaveDistributionSetAssignment(final DistributionSet set, + final List targets) { + final List targetIds = targets.stream().map(Target::getId).collect(Collectors.toList()); + + final Slice targetsAll = targetManagement.findAll(PAGE); + + for (final Target target : targetsAll) { + if (targetIds.contains(target.getId())) { + assertThat(deploymentManagement.getAssignedDistributionSet(target.getControllerId())).isEmpty(); + } + } + + } + + @Step + private void verifyThatCreatedActionsAreInitiatedByCurrentUser(final TargetFilterQuery targetFilterQuery, + final DistributionSet distributionSet, final List targets) { + final Set targetIds = targets.stream().map(Target::getControllerId).collect(Collectors.toSet()); + + actionRepository.findByDistributionSetId(Pageable.unpaged(), distributionSet.getId()).stream() + .filter(a -> targetIds.contains(a.getTarget().getControllerId())) + .forEach(a -> assertThat(a.getInitiatedBy()) + .as("Action should be initiated by the user who initiated the auto assignment") + .isEqualTo(targetFilterQuery.getAutoAssignInitiatedBy())); + } + + @Test + @Description("Test auto assignment of a distribution set with FORCED, SOFT and DOWNLOAD_ONLY action types") + void checkAutoAssignWithDifferentActionTypes() { + final DistributionSet distributionSet = testdataFactory.createDistributionSet(); + final String targetDsAIdPref = "A"; + final String targetDsBIdPref = "B"; + final String targetDsCIdPref = "C"; + + final List targetsA = createTargetsAndAutoAssignDistSet(targetDsAIdPref, 5, distributionSet, + ActionType.FORCED); + final List targetsB = createTargetsAndAutoAssignDistSet(targetDsBIdPref, 10, distributionSet, + ActionType.SOFT); + final List targetsC = createTargetsAndAutoAssignDistSet(targetDsCIdPref, 10, distributionSet, + ActionType.DOWNLOAD_ONLY); + + final int targetsCount = targetsA.size() + targetsB.size() + targetsC.size(); + + autoAssignChecker.checkAllTargets(); + + verifyThatTargetsHaveDistributionSetAssignment(distributionSet, targetsA, targetsCount); + verifyThatTargetsHaveDistributionSetAssignment(distributionSet, targetsB, targetsCount); + verifyThatTargetsHaveDistributionSetAssignment(distributionSet, targetsC, targetsCount); + + verifyThatTargetsHaveAssignmentActionType(ActionType.FORCED, targetsA); + verifyThatTargetsHaveAssignmentActionType(ActionType.SOFT, targetsB); + verifyThatTargetsHaveAssignmentActionType(ActionType.DOWNLOAD_ONLY, targetsC); + } + + @Step + private List createTargetsAndAutoAssignDistSet(final String prefix, final int targetCount, + final DistributionSet distributionSet, final ActionType actionType) { + + final List targets = testdataFactory.createTargets(targetCount, "target" + prefix, + prefix.concat(" description")); + targetFilterQueryManagement.create( + entityFactory.targetFilterQuery().create().name("filter" + prefix).query("id==target" + prefix + "*") + .autoAssignDistributionSet(distributionSet).autoAssignActionType(actionType)); + + return targets; + } + + @Step + private void verifyThatTargetsHaveAssignmentActionType(final ActionType actionType, final List targets) { + final List actions = targets.stream().map(Target::getControllerId).flatMap( + controllerId -> deploymentManagement.findActionsByTarget(controllerId, PAGE).getContent().stream()) + .collect(Collectors.toList()); + + assertThat(actions).hasSize(targets.size()); + assertThat(actions).allMatch(action -> action.getActionType().equals(actionType)); + } + + @Test + @Description("An auto assignment target filter with weight creates actions with weights") + void actionsWithWeightAreCreated() throws Exception { + final int amountOfTargets = 5; + final DistributionSet ds = testdataFactory.createDistributionSet(); + final int weight = 32; + enableMultiAssignments(); + + targetFilterQueryManagement.create(entityFactory.targetFilterQuery().create().name("a").query("name==*") + .autoAssignDistributionSet(ds).autoAssignWeight(weight)); + testdataFactory.createTargets(amountOfTargets); + autoAssignChecker.checkAllTargets(); + + final List actions = deploymentManagement.findActionsAll(PAGE).getContent(); + assertThat(actions).hasSize(amountOfTargets); + assertThat(actions).allMatch(action -> action.getWeight().get() == weight); + } + + @Test + @Description("An auto assignment target filter without weight still works after multi assignment is enabled") + void filterWithoutWeightWorksInMultiAssignmentMode() throws Exception { + final int amountOfTargets = 5; + final DistributionSet ds = testdataFactory.createDistributionSet(); + targetFilterQueryManagement.create( + entityFactory.targetFilterQuery().create().name("a").query("name==*").autoAssignDistributionSet(ds)); + enableMultiAssignments(); + + testdataFactory.createTargets(amountOfTargets); + autoAssignChecker.checkAllTargets(); + + final List actions = deploymentManagement.findActionsAll(PAGE).getContent(); + assertThat(actions).hasSize(amountOfTargets); + assertThat(actions).allMatch(action -> !action.getWeight().isPresent()); + } + + @Test + @Description("Verifies an auto assignment only creates actions for compatible targets") + void checkAutoAssignmentWithIncompatibleTargets() { + final int TARGET_COUNT = 5; + + final DistributionSet testDs = testdataFactory.createDistributionSet(); + final DistributionSetType incompatibleDsType1 = testdataFactory + .findOrCreateDistributionSetType("incompatibleDsType1", "incompDsType1"); + final DistributionSetType incompatibleDsType2 = testdataFactory + .findOrCreateDistributionSetType("incompatibleDsType2", "incompDsType2"); + final TargetFilterQuery testFilter = targetFilterQueryManagement.create(entityFactory.targetFilterQuery() + .create().name("test-filter").query("name==*").autoAssignDistributionSet(testDs)); + + final TargetType incompatibleEmptyType = testdataFactory.createTargetType("incompatibleEmptyType", + Collections.emptyList()); + final TargetType incompatibleSingleType = testdataFactory.createTargetType("incompatibleSingleType", + Collections.singletonList(incompatibleDsType1)); + final TargetType incompatibleMultiType = testdataFactory.createTargetType("incompatibleMultiType", + Arrays.asList(incompatibleDsType1, incompatibleDsType2)); + final TargetType compatibleSingleType = testdataFactory.createTargetType("compatibleSingleType", + Collections.singletonList(testDs.getType())); + final TargetType compatibleMultiType = testdataFactory.createTargetType("compatibleMultiType", + Arrays.asList(testDs.getType(), incompatibleDsType1)); + + testdataFactory.createTargetsWithType(TARGET_COUNT, "incompatibleEmpty", incompatibleEmptyType); + testdataFactory.createTargetsWithType(TARGET_COUNT, "incompatibleSingle", incompatibleSingleType); + testdataFactory.createTargetsWithType(TARGET_COUNT, "incompatibleMulti", incompatibleMultiType); + + final List compatibleTargetsSingleType = testdataFactory.createTargetsWithType(TARGET_COUNT, + "compatibleSingle", compatibleSingleType); + final List compatibleTargetsMultiType = testdataFactory.createTargetsWithType(TARGET_COUNT, + "compatibleMulti", compatibleMultiType); + final List compatibleTargetsWithoutType = testdataFactory.createTargets(TARGET_COUNT, + "compatibleSingleWithoutType"); + + final List compatibleTargets = Stream + .of(compatibleTargetsSingleType, compatibleTargetsMultiType, compatibleTargetsWithoutType) + .flatMap(Collection::stream).map(Target::getId).collect(Collectors.toList()); + final long compatibleCount = targetManagement.countByRsqlAndNonDSAndCompatible(testDs.getId(), + testFilter.getQuery()); + assertThat(compatibleCount).isEqualTo(compatibleTargets.size()); + + autoAssignChecker.checkAllTargets(); + + final List actions = deploymentManagement.findActionsAll(Pageable.unpaged()).getContent(); + assertThat(actions).hasSize(compatibleTargets.size()); + final List actionTargets = actions.stream().map(a -> a.getTarget().getId()).collect(Collectors.toList()); + assertThat(actionTargets).containsExactlyInAnyOrderElementsOf(compatibleTargets); + } +} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignCheckerTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignCheckerTest.java index be08e6e99..8bce83958 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignCheckerTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignCheckerTest.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * Copyright (c) 2022 Bosch.IO GmbH and others. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -8,366 +8,113 @@ */ package org.eclipse.hawkbit.repository.jpa.autoassign; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; +import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; -import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException; -import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; -import org.eclipse.hawkbit.repository.jpa.ActionRepository; -import org.eclipse.hawkbit.repository.model.Action; -import org.eclipse.hawkbit.repository.model.Action.ActionType; -import org.eclipse.hawkbit.repository.model.Action.Status; +import org.eclipse.hawkbit.repository.DeploymentManagement; +import org.eclipse.hawkbit.repository.TargetFilterQueryManagement; +import org.eclipse.hawkbit.repository.TargetManagement; +import org.eclipse.hawkbit.repository.model.DeploymentRequest; import org.eclipse.hawkbit.repository.model.DistributionSet; -import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult; -import org.eclipse.hawkbit.repository.model.DistributionSetType; -import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TargetFilterQuery; -import org.eclipse.hawkbit.repository.model.TargetType; -import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey; +import org.eclipse.hawkbit.tenancy.TenantAware; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Slice; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentMatcher; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.SliceImpl; +import org.springframework.transaction.PlatformTransactionManager; import io.qameta.allure.Description; import io.qameta.allure.Feature; -import io.qameta.allure.Step; import io.qameta.allure.Story; -/** - * Test class for {@link AutoAssignChecker}. - * - */ -@Feature("Component Tests - Repository") +@Feature("Unit Tests - Repository") @Story("Auto assign checker") -class AutoAssignCheckerTest extends AbstractJpaIntegrationTest { +@ExtendWith(MockitoExtension.class) +class AutoAssignCheckerTest { + @Mock + private TargetFilterQueryManagement targetFilterQueryManagement; + @Mock + private TargetManagement targetManagement; + @Mock + private DeploymentManagement deploymentManagement; + @Mock + private PlatformTransactionManager transactionManager; + @Mock + private TenantAware tenantAware; - @Autowired - private AutoAssignChecker autoAssignChecker; + private AutoAssignChecker sut; - @Autowired - private ActionRepository actionRepository; - - @Test - @Description("Verifies that a running action is auto canceled by a AutoAssignment which assigns another distribution-set.") - void autoAssignDistributionSetAndAutoCloseOldActions() { - - tenantConfigurationManagement - .addOrUpdateConfiguration(TenantConfigurationKey.REPOSITORY_ACTIONS_AUTOCLOSE_ENABLED, true); - - try { - final String knownControllerId = "controller12345"; - final DistributionSet firstDistributionSet = testdataFactory.createDistributionSet(); - final DistributionSet secondDistributionSet = testdataFactory.createDistributionSet("second"); - testdataFactory.createTarget(knownControllerId); - final DistributionSetAssignmentResult assignmentResult = assignDistributionSet(firstDistributionSet.getId(), - knownControllerId); - final Long manuallyAssignedActionId = getFirstAssignedActionId(assignmentResult); - - // target filter query that matches all targets - final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement - .create(entityFactory.targetFilterQuery().create().name("filterA").query("name==*")); - targetFilterQueryManagement.updateAutoAssignDS(entityFactory.targetFilterQuery() - .updateAutoAssign(targetFilterQuery.getId()).ds(secondDistributionSet.getId())); - // Run the check - autoAssignChecker.check(); - - // verify that manually created action is canceled and action - // created from AutoAssign is running - final List actionsByKnownTarget = deploymentManagement.findActionsByTarget(knownControllerId, PAGE) - .getContent(); - // should be 2 actions, one manually and one from the AutoAssign - assertThat(actionsByKnownTarget).hasSize(2); - // verify that manually assigned action is still running - assertThat(deploymentManagement.findAction(manuallyAssignedActionId).get().getStatus()) - .isEqualTo(Status.CANCELED); - // verify that AutoAssign created action is running - final Action rolloutCreatedAction = actionsByKnownTarget.stream() - .filter(action -> !action.getId().equals(manuallyAssignedActionId)).findAny().get(); - assertThat(rolloutCreatedAction.getStatus()).isEqualTo(Status.RUNNING); - assertThat(rolloutCreatedAction.getActionType()).isEqualTo(ActionType.FORCED); - } finally { - tenantConfigurationManagement - .addOrUpdateConfiguration(TenantConfigurationKey.REPOSITORY_ACTIONS_AUTOCLOSE_ENABLED, false); - } + @BeforeEach + void before() { + sut = new AutoAssignChecker(targetFilterQueryManagement, targetManagement, deploymentManagement, + transactionManager, tenantAware); } @Test - @Description("Test auto assignment of a DS to filtered targets") - void checkAutoAssign() { + @Description("Single device check triggers update for matching auto assignment filter.") + void checkForDevice() { + mockRunningAsNonSystem(); + final String target = getRandomString(); + final long ds = getRandomLong(); + final TargetFilterQuery matching = mockFilterQuery(ds); + final TargetFilterQuery notMatching = mockFilterQuery(ds); + when(targetFilterQueryManagement.findWithAutoAssignDS(any())) + .thenReturn(new SliceImpl<>(Arrays.asList(notMatching, matching))); - final DistributionSet setA = testdataFactory.createDistributionSet("dsA"); // will - // be - // auto - // assigned - final DistributionSet setB = testdataFactory.createDistributionSet("dsB"); + when(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatible(target, ds, matching.getQuery())) + .thenReturn(true); + when(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatible(target, ds, + notMatching.getQuery())).thenReturn(false); - // target filter query that matches all targets - final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement.updateAutoAssignDS(entityFactory - .targetFilterQuery() - .updateAutoAssign(targetFilterQueryManagement - .create(entityFactory.targetFilterQuery().create().name("filterA").query("name==*")).getId()) - .ds(setA.getId())); + sut.checkSingleTarget(target); - final String targetDsAIdPref = "targ"; - final List targets = testdataFactory.createTargets(25, targetDsAIdPref, - targetDsAIdPref.concat(" description")); - final int targetsCount = targets.size(); - - // assign set A to first 10 targets - assignDistributionSet(setA, targets.subList(0, 10)); - verifyThatTargetsHaveDistributionSetAssignment(setA, targets.subList(0, 10), targetsCount); - - // assign set B to first 5 targets - // they have now 2 DS in their action history and should not get updated - // with dsA - assignDistributionSet(setB, targets.subList(0, 5)); - verifyThatTargetsHaveDistributionSetAssignment(setB, targets.subList(0, 5), targetsCount); - - // assign set B to next 10 targets - assignDistributionSet(setB, targets.subList(10, 20)); - verifyThatTargetsHaveDistributionSetAssignment(setB, targets.subList(10, 20), targetsCount); - - // Count the number of targets that will be assigned with setA - assertThat(targetManagement.countByRsqlAndNonDSAndCompatible(setA.getId(), targetFilterQuery.getQuery())) - .isEqualTo(15); - - // Run the check - autoAssignChecker.check(); - - verifyThatTargetsHaveDistributionSetAssignment(setA, targets.subList(5, 25), targetsCount); - - // first 5 should keep their dsB, because they already had the dsA once - verifyThatTargetsHaveDistributionSetAssignment(setB, targets.subList(0, 5), targetsCount); - - verifyThatCreatedActionsAreInitiatedByCurrentUser(targetFilterQuery, setA, targets); + verify(deploymentManagement).assignDistributionSets(eq(matching.getAutoAssignInitiatedBy()), + Mockito.argThat(deployReqMatcher(target, ds)), any()); + Mockito.verifyNoMoreInteractions(deploymentManagement); } - @Test - @Description("Test auto assignment of an incomplete DS to filtered targets, that causes failures") - void checkAutoAssignWithFailures() { - - // incomplete distribution set that will be assigned - final DistributionSet setF = distributionSetManagement.create(entityFactory.distributionSet().create() - .name("dsA").version("1").type(testdataFactory.findOrCreateDefaultTestDsType())); - final DistributionSet setA = testdataFactory.createDistributionSet("dsA"); - final DistributionSet setB = testdataFactory.createDistributionSet("dsB"); - - final String targetDsAIdPref = "targA"; - final String targetDsFIdPref = "targB"; - - // target filter query that matches first bunch of targets, that should - // fail - assertThatExceptionOfType(IncompleteDistributionSetException.class).isThrownBy(() -> { - final Long filterId = targetFilterQueryManagement.create( - entityFactory.targetFilterQuery().create().name("filterA").query("id==" + targetDsFIdPref + "*")) - .getId(); - targetFilterQueryManagement - .updateAutoAssignDS(entityFactory.targetFilterQuery().updateAutoAssign(filterId).ds(setF.getId())); - }); - // target filter query that matches failed bunch of targets - targetFilterQueryManagement.create(entityFactory.targetFilterQuery().create().name("filterB") - .query("id==" + targetDsAIdPref + "*").autoAssignDistributionSet(setA.getId())); - - final List targetsF = testdataFactory.createTargets(10, targetDsFIdPref, - targetDsFIdPref.concat(" description")); - - final List targetsA = testdataFactory.createTargets(10, targetDsAIdPref, - targetDsAIdPref.concat(" description")); - - final int targetsCount = targetsA.size() + targetsF.size(); - - // assign set B to first 5 targets of fail group - assignDistributionSet(setB, targetsF.subList(0, 5)); - verifyThatTargetsHaveDistributionSetAssignment(setB, targetsF.subList(0, 5), targetsCount); - - // Run the check - autoAssignChecker.check(); - - // first 5 targets of the fail group should still have setB - verifyThatTargetsHaveDistributionSetAssignment(setB, targetsF.subList(0, 5), targetsCount); - - // all targets of A group should have received setA - verifyThatTargetsHaveDistributionSetAssignment(setA, targetsA, targetsCount); - + private ArgumentMatcher> deployReqMatcher(final String target, final long ds) { + return requests -> { + final DeploymentRequest request = requests.get(0); + return requests.size() == 1 && request.getDistributionSetId() == ds && request.getControllerId() == target; + }; } - /** - * @param set - * the expected distribution set - * @param targets - * the targets that should have it - */ - @Step - private void verifyThatTargetsHaveDistributionSetAssignment(final DistributionSet set, final List targets, - final int count) { - final List targetIds = targets.stream().map(Target::getId).collect(Collectors.toList()); - - final Slice targetsAll = targetManagement.findAll(PAGE); - assertThat(targetsAll).as("Count of targets").hasSize(count); - - for (final Target target : targetsAll) { - if (targetIds.contains(target.getId())) { - assertThat(deploymentManagement.getAssignedDistributionSet(target.getControllerId()).get()) - .as("assigned DS").isEqualTo(set); - } - } - + private static TargetFilterQuery mockFilterQuery(final long dsId) { + final DistributionSet ds = mock(DistributionSet.class); + when(ds.getId()).thenReturn(dsId); + final TargetFilterQuery filter = mock(TargetFilterQuery.class); + when(filter.getId()).thenReturn(getRandomLong()); + when(filter.getQuery()).thenReturn(getRandomString()); + lenient().when(filter.getAutoAssignInitiatedBy()).thenReturn(getRandomString()); + when(filter.getAutoAssignDistributionSet()).thenReturn(ds); + return filter; } - @Step - private void verifyThatCreatedActionsAreInitiatedByCurrentUser(final TargetFilterQuery targetFilterQuery, - final DistributionSet distributionSet, final List targets) { - final Set targetIds = targets.stream().map(Target::getControllerId).collect(Collectors.toSet()); - - actionRepository.findByDistributionSetId(Pageable.unpaged(), distributionSet.getId()).stream() - .filter(a -> targetIds.contains(a.getTarget().getControllerId())) - .forEach(a -> assertThat(a.getInitiatedBy()) - .as("Action should be initiated by the user who initiated the auto assignment") - .isEqualTo(targetFilterQuery.getAutoAssignInitiatedBy())); + private void mockRunningAsNonSystem() { + when(tenantAware.getCurrentUsername()).thenReturn(getRandomString()); } - @Test - @Description("Test auto assignment of a distribution set with FORCED, SOFT and DOWNLOAD_ONLY action types") - void checkAutoAssignWithDifferentActionTypes() { - final DistributionSet distributionSet = testdataFactory.createDistributionSet(); - final String targetDsAIdPref = "A"; - final String targetDsBIdPref = "B"; - final String targetDsCIdPref = "C"; - - final List targetsA = createTargetsAndAutoAssignDistSet(targetDsAIdPref, 5, distributionSet, - ActionType.FORCED); - final List targetsB = createTargetsAndAutoAssignDistSet(targetDsBIdPref, 10, distributionSet, - ActionType.SOFT); - final List targetsC = createTargetsAndAutoAssignDistSet(targetDsCIdPref, 10, distributionSet, - ActionType.DOWNLOAD_ONLY); - - final int targetsCount = targetsA.size() + targetsB.size() + targetsC.size(); - - autoAssignChecker.check(); - - verifyThatTargetsHaveDistributionSetAssignment(distributionSet, targetsA, targetsCount); - verifyThatTargetsHaveDistributionSetAssignment(distributionSet, targetsB, targetsCount); - verifyThatTargetsHaveDistributionSetAssignment(distributionSet, targetsC, targetsCount); - - verifyThatTargetsHaveAssignmentActionType(ActionType.FORCED, targetsA); - verifyThatTargetsHaveAssignmentActionType(ActionType.SOFT, targetsB); - verifyThatTargetsHaveAssignmentActionType(ActionType.DOWNLOAD_ONLY, targetsC); + private static long getRandomLong() { + return ThreadLocalRandom.current().nextLong(); } - @Step - private List createTargetsAndAutoAssignDistSet(final String prefix, final int targetCount, - final DistributionSet distributionSet, final ActionType actionType) { - - final List targets = testdataFactory.createTargets(targetCount, "target" + prefix, - prefix.concat(" description")); - targetFilterQueryManagement.create( - entityFactory.targetFilterQuery().create().name("filter" + prefix).query("id==target" + prefix + "*") - .autoAssignDistributionSet(distributionSet).autoAssignActionType(actionType)); - - return targets; + private static String getRandomString() { + return UUID.randomUUID().toString(); } - @Step - private void verifyThatTargetsHaveAssignmentActionType(final ActionType actionType, final List targets) { - final List actions = targets.stream().map(Target::getControllerId).flatMap( - controllerId -> deploymentManagement.findActionsByTarget(controllerId, PAGE).getContent().stream()) - .collect(Collectors.toList()); - - assertThat(actions).hasSize(targets.size()); - assertThat(actions).allMatch(action -> action.getActionType().equals(actionType)); - } - - @Test - @Description("An auto assignment target filter with weight creates actions with weights") - void actionsWithWeightAreCreated() throws Exception { - final int amountOfTargets = 5; - final DistributionSet ds = testdataFactory.createDistributionSet(); - final int weight = 32; - enableMultiAssignments(); - - targetFilterQueryManagement.create(entityFactory.targetFilterQuery().create().name("a").query("name==*") - .autoAssignDistributionSet(ds).autoAssignWeight(weight)); - testdataFactory.createTargets(amountOfTargets); - autoAssignChecker.check(); - - final List actions = deploymentManagement.findActionsAll(PAGE).getContent(); - assertThat(actions).hasSize(amountOfTargets); - assertThat(actions).allMatch(action -> action.getWeight().get() == weight); - } - - @Test - @Description("An auto assignment target filter without weight still works after multi assignment is enabled") - void filterWithoutWeightWorksInMultiAssignmentMode() throws Exception { - final int amountOfTargets = 5; - final DistributionSet ds = testdataFactory.createDistributionSet(); - targetFilterQueryManagement.create( - entityFactory.targetFilterQuery().create().name("a").query("name==*").autoAssignDistributionSet(ds)); - enableMultiAssignments(); - - testdataFactory.createTargets(amountOfTargets); - autoAssignChecker.check(); - - final List actions = deploymentManagement.findActionsAll(PAGE).getContent(); - assertThat(actions).hasSize(amountOfTargets); - assertThat(actions).allMatch(action -> !action.getWeight().isPresent()); - } - - @Test - @Description("Verifies an auto assignment only creates actions for compatible targets") - void checkAutoAssignmentWithIncompatibleTargets() { - final int TARGET_COUNT = 5; - - final DistributionSet testDs = testdataFactory.createDistributionSet(); - final DistributionSetType incompatibleDsType1 = testdataFactory - .findOrCreateDistributionSetType("incompatibleDsType1", "incompDsType1"); - final DistributionSetType incompatibleDsType2 = testdataFactory - .findOrCreateDistributionSetType("incompatibleDsType2", "incompDsType2"); - final TargetFilterQuery testFilter = targetFilterQueryManagement.create(entityFactory.targetFilterQuery() - .create().name("test-filter").query("name==*").autoAssignDistributionSet(testDs)); - - final TargetType incompatibleEmptyType = testdataFactory.createTargetType("incompatibleEmptyType", - Collections.emptyList()); - final TargetType incompatibleSingleType = testdataFactory.createTargetType("incompatibleSingleType", - Collections.singletonList(incompatibleDsType1)); - final TargetType incompatibleMultiType = testdataFactory.createTargetType("incompatibleMultiType", - Arrays.asList(incompatibleDsType1, incompatibleDsType2)); - final TargetType compatibleSingleType = testdataFactory.createTargetType("compatibleSingleType", - Collections.singletonList(testDs.getType())); - final TargetType compatibleMultiType = testdataFactory.createTargetType("compatibleMultiType", - Arrays.asList(testDs.getType(), incompatibleDsType1)); - - testdataFactory.createTargetsWithType(TARGET_COUNT, "incompatibleEmpty", incompatibleEmptyType); - testdataFactory.createTargetsWithType(TARGET_COUNT, "incompatibleSingle", incompatibleSingleType); - testdataFactory.createTargetsWithType(TARGET_COUNT, "incompatibleMulti", incompatibleMultiType); - - final List compatibleTargetsSingleType = testdataFactory.createTargetsWithType(TARGET_COUNT, - "compatibleSingle", compatibleSingleType); - final List compatibleTargetsMultiType = testdataFactory.createTargetsWithType(TARGET_COUNT, - "compatibleMulti", compatibleMultiType); - final List compatibleTargetsWithoutType = testdataFactory.createTargets(TARGET_COUNT, - "compatibleSingleWithoutType"); - - final List compatibleTargets = Stream - .of(compatibleTargetsSingleType, compatibleTargetsMultiType, compatibleTargetsWithoutType) - .flatMap(Collection::stream).map(Target::getId).collect(Collectors.toList()); - final long compatibleCount = targetManagement.countByRsqlAndNonDSAndCompatible(testDs.getId(), - testFilter.getQuery()); - assertThat(compatibleCount).isEqualTo(compatibleTargets.size()); - - autoAssignChecker.check(); - - final List actions = deploymentManagement.findActionsAll(Pageable.unpaged()).getContent(); - assertThat(actions).hasSize(compatibleTargets.size()); - final List actionTargets = actions.stream().map(a -> a.getTarget().getId()).collect(Collectors.toList()); - assertThat(actionTargets).containsExactlyInAnyOrderElementsOf(compatibleTargets); - } }