Support user consent flow (#1293)
* Introduce user consent flow * Add permissions to confirmation management * rename from consent to confirmation * Reformat code. Remove unused imports. Change and add permission checks when configuring auto-confirmation. * Do not include null values for DDI confirmation base endpoint * fix confirmation required checkbox id * Remove unused import. Fix consume/produce type of new API's. * Change term processing to proceeding when activating user consent flow * Align formatting and extend integration test cases for DMF and DDI. * Extend DMF test cases to consider auto-confirmation * Refactor action management to fix problem of handling action status updates on closed actions. * remove unsupported validation * use new confirmation api for DMF. Extend test cases., * Remove unnecessary fields. * Extend API documentation for DDI and MGMT API. * adapt ddi api docs adoc file * Fixed the duplicate migration version for db files * fix method to support confirmation * Fixed PR comments * Addressed PR comments * Fixed after merge compilation issue * Fixed after merge compilation issue * Fix failing tests in MgmtRolloutResourceTest * Fixed the permissions issue reflected by integration tests * Added back the missing line of code lost during merge * Fix the failing test on Jenkins Signed-off-by: Stanislav Trailov <stanislav.trailov@bosch.io> Signed-off-by: Dimitar Shterev <dimitar.shterev@bosch.io> Signed-off-by: Michael Herdt <Michael.Herdt@bosch.io> Signed-off-by: Shruthi Manavalli Ramanna <shruthimanavalli.ramanna@bosch-si.com> Co-authored-by: Shruthi Manavalli Ramanna <shruthimanavalli.ramanna@bosch-si.com>
This commit is contained in:
@@ -44,8 +44,8 @@ public class RolloutEventTest extends AbstractRemoteEntityEventTest<Rollout> {
|
||||
.type("os").modules(Collections.singletonList(module.getId())));
|
||||
|
||||
return rolloutManagement.create(
|
||||
entityFactory.rollout().create().name("exampleRollout").targetFilterQuery("controllerId==*").set(ds),
|
||||
5, new RolloutGroupConditionBuilder().withDefaults()
|
||||
entityFactory.rollout().create().name("exampleRollout").targetFilterQuery("controllerId==*").set(ds), 5,
|
||||
false, new RolloutGroupConditionBuilder().withDefaults()
|
||||
.successCondition(RolloutGroupSuccessCondition.THRESHOLD, "10").build());
|
||||
}
|
||||
|
||||
|
||||
@@ -84,8 +84,8 @@ public class RolloutGroupEventTest extends AbstractRemoteEntityEventTest<Rollout
|
||||
.type("os").modules(Collections.singletonList(module.getId())));
|
||||
|
||||
final Rollout entity = rolloutManagement.create(
|
||||
entityFactory.rollout().create().name("exampleRollout").targetFilterQuery("controllerId==*").set(ds),
|
||||
5, new RolloutGroupConditionBuilder().withDefaults()
|
||||
entityFactory.rollout().create().name("exampleRollout").targetFilterQuery("controllerId==*").set(ds), 5,
|
||||
false, new RolloutGroupConditionBuilder().withDefaults()
|
||||
.successCondition(RolloutGroupSuccessCondition.THRESHOLD, "10").build());
|
||||
|
||||
return rolloutGroupManagement.findByRollout(PAGE, entity.getId()).getContent().get(0);
|
||||
|
||||
@@ -117,7 +117,7 @@ public class ConcurrentDistributionSetInvalidationTest extends AbstractJpaIntegr
|
||||
return rolloutManagement.create(entityFactory.rollout().create()
|
||||
.name("verifyInvalidateDistributionSetWithLargeRolloutThrowsException").description("desc")
|
||||
.targetFilterQuery("name==*").set(distributionSet).actionType(ActionType.FORCED),
|
||||
quotaManagement.getMaxRolloutGroupsPerRollout(), conditions);
|
||||
quotaManagement.getMaxRolloutGroupsPerRollout(), false, conditions);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,315 @@
|
||||
/**
|
||||
* Copyright (c) 2022 Bosch.IO GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.jpa;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.hawkbit.repository.exception.AutoConfirmationAlreadyActiveException;
|
||||
import org.eclipse.hawkbit.repository.exception.InvalidConfirmationFeedbackException;
|
||||
import org.eclipse.hawkbit.repository.model.Action;
|
||||
import org.eclipse.hawkbit.repository.model.Action.Status;
|
||||
import org.eclipse.hawkbit.repository.model.DeploymentRequest;
|
||||
import org.eclipse.hawkbit.repository.model.DeploymentRequestBuilder;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||
import org.eclipse.hawkbit.repository.model.Target;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import io.qameta.allure.Description;
|
||||
import io.qameta.allure.Feature;
|
||||
import io.qameta.allure.Story;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
/**
|
||||
* Test class testing the functionality of triggering a deployment of
|
||||
* {@link DistributionSet}s to {@link Target}s with AutoConfirmation active.
|
||||
*
|
||||
*/
|
||||
@Feature("Component Tests - Repository")
|
||||
@Story("Confirmation Management")
|
||||
class ConfirmationManagementTest extends AbstractJpaIntegrationTest {
|
||||
|
||||
@Test
|
||||
@Description("Verify 'findActiveActionsWaitingConfirmation' method is filtering like expected")
|
||||
void retrieveActionsWithConfirmationState() {
|
||||
enableConfirmationFlow();
|
||||
|
||||
final String controllerId = testdataFactory.createTarget().getControllerId();
|
||||
final Long dsId = testdataFactory.createDistributionSet().getId();
|
||||
|
||||
final List<Action> actions = assignDistributionSet(dsId, controllerId).getAssignedEntity();
|
||||
assertThat(actions).hasSize(1);
|
||||
|
||||
assertThat(confirmationManagement.findActiveActionsWaitingConfirmation(controllerId)).hasSize(1)
|
||||
.allMatch(action -> action.getStatus() == Status.WAIT_FOR_CONFIRMATION);
|
||||
|
||||
final Long dsId2 = testdataFactory.createDistributionSet().getId();
|
||||
// ds1 will be in canceling state afterwards
|
||||
assignDistributionSet(dsId2, controllerId);
|
||||
|
||||
assertThat(confirmationManagement.findActiveActionsWaitingConfirmation(controllerId)).hasSize(1)
|
||||
.allMatch(action -> action.getStatus() == Status.WAIT_FOR_CONFIRMATION);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verify 'findActiveActionsWaitingConfirmation' method is filtering like expected with multi assignment active")
|
||||
void retrieveActionsWithConfirmationStateInMultiAssignment() {
|
||||
enableMultiAssignments();
|
||||
enableConfirmationFlow();
|
||||
|
||||
final String controllerId = testdataFactory.createTarget().getControllerId();
|
||||
final Long dsId = testdataFactory.createDistributionSet().getId();
|
||||
|
||||
final List<Action> actions = assignDistributionSet(dsId, controllerId).getAssignedEntity();
|
||||
assertThat(actions).hasSize(1);
|
||||
|
||||
assertThat(confirmationManagement.findActiveActionsWaitingConfirmation(controllerId)).hasSize(1)
|
||||
.allMatch(action -> action.getStatus() == Status.WAIT_FOR_CONFIRMATION);
|
||||
|
||||
final Long dsId2 = testdataFactory.createDistributionSet().getId();
|
||||
assignDistributionSet(dsId2, controllerId);
|
||||
|
||||
assertThat(confirmationManagement.findActiveActionsWaitingConfirmation(controllerId)).hasSize(2)
|
||||
.allMatch(action -> action.getStatus() == Status.WAIT_FOR_CONFIRMATION);
|
||||
|
||||
confirmationManagement.confirmAction(actions.get(0).getId(), null, null);
|
||||
|
||||
assertThat(confirmationManagement.findActiveActionsWaitingConfirmation(controllerId)).hasSize(1)
|
||||
.allMatch(action -> action.getStatus() == Status.WAIT_FOR_CONFIRMATION
|
||||
&& Objects.equals(action.getDistributionSet().getId(), dsId2));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verify confirming an action will put it to the running state")
|
||||
void confirmedActionWillSwitchToRunningState() {
|
||||
enableConfirmationFlow();
|
||||
|
||||
final String controllerId = testdataFactory.createTarget().getControllerId();
|
||||
final Long dsId = testdataFactory.createDistributionSet().getId();
|
||||
|
||||
final List<Action> actions = assignDistributionSet(dsId, controllerId).getAssignedEntity();
|
||||
assertThat(actions).hasSize(1).allMatch(action -> action.getStatus() == Status.WAIT_FOR_CONFIRMATION);
|
||||
|
||||
assertThat(confirmationManagement.findActiveActionsWaitingConfirmation(controllerId)).hasSize(1)
|
||||
.allMatch(action -> action.getStatus() == Status.WAIT_FOR_CONFIRMATION);
|
||||
assertThat(controllerManagement.findActionStatusByAction(PAGE, actions.get(0).getId())).hasSize(1)
|
||||
.allMatch(status -> status.getStatus() == Status.WAIT_FOR_CONFIRMATION);
|
||||
|
||||
final Action newAction = confirmationManagement.confirmAction(actions.get(0).getId(), null, null);
|
||||
|
||||
assertThat(confirmationManagement.findActiveActionsWaitingConfirmation(controllerId)).isEmpty();
|
||||
|
||||
// verify action in RUNNING state
|
||||
assertThat(newAction.getStatus()).isEqualTo(Status.RUNNING);
|
||||
|
||||
// status entry RUNNING should be present in status history
|
||||
assertThat(controllerManagement.findActionStatusByAction(PAGE, newAction.getId())).hasSize(2)
|
||||
.anyMatch(status -> status.getStatus() == Status.RUNNING);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verify confirming an confirmed action will lead to a specific failure")
|
||||
void confirmedActionCannotBeConfirmedAgain() {
|
||||
enableConfirmationFlow();
|
||||
|
||||
final String controllerId = testdataFactory.createTarget().getControllerId();
|
||||
final Long dsId = testdataFactory.createDistributionSet().getId();
|
||||
|
||||
final List<Action> actions = assignDistributionSet(dsId, controllerId).getAssignedEntity();
|
||||
assertThat(actions).hasSize(1).allMatch(action -> action.getStatus() == Status.WAIT_FOR_CONFIRMATION);
|
||||
final Action newAction = confirmationManagement.confirmAction(actions.get(0).getId(), null, null);
|
||||
// verify action in RUNNING state
|
||||
assertThat(newAction.getStatus()).isEqualTo(Status.RUNNING);
|
||||
|
||||
assertThatThrownBy(() -> confirmationManagement.confirmAction(actions.get(0).getId(), null, null))
|
||||
.isInstanceOf(InvalidConfirmationFeedbackException.class)
|
||||
.matches(e -> ((InvalidConfirmationFeedbackException) e)
|
||||
.getReason() == InvalidConfirmationFeedbackException.Reason.NOT_AWAITING_CONFIRMATION);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verify confirming a closed action will lead to a specific failure")
|
||||
void confirmedActionCannotBeGivenOnFinishedAction() {
|
||||
enableConfirmationFlow();
|
||||
final Action action = prepareFinishedUpdate();
|
||||
|
||||
assertThatThrownBy(() -> confirmationManagement.confirmAction(action.getId(), null, null))
|
||||
.isInstanceOf(InvalidConfirmationFeedbackException.class)
|
||||
.matches(e -> ((InvalidConfirmationFeedbackException) e)
|
||||
.getReason() == InvalidConfirmationFeedbackException.Reason.ACTION_CLOSED);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verify denying an action will leave it in WFC state")
|
||||
void deniedActionWillStayInWfcState() {
|
||||
enableConfirmationFlow();
|
||||
|
||||
final String controllerId = testdataFactory.createTarget().getControllerId();
|
||||
final Long dsId = testdataFactory.createDistributionSet().getId();
|
||||
|
||||
final List<Action> actions = assignDistributionSet(dsId, controllerId).getAssignedEntity();
|
||||
assertThat(actions).hasSize(1).allMatch(action -> action.getStatus() == Status.WAIT_FOR_CONFIRMATION);
|
||||
assertThat(confirmationManagement.findActiveActionsWaitingConfirmation(controllerId)).hasSize(1)
|
||||
.allMatch(action -> action.getStatus() == Status.WAIT_FOR_CONFIRMATION);
|
||||
assertThat(controllerManagement.findActionStatusByAction(PAGE, actions.get(0).getId())).hasSize(1)
|
||||
.allMatch(status -> status.getStatus() == Status.WAIT_FOR_CONFIRMATION);
|
||||
|
||||
final Action newAction = confirmationManagement.denyAction(actions.get(0).getId(), null, null);
|
||||
|
||||
assertThat(confirmationManagement.findActiveActionsWaitingConfirmation(controllerId)).hasSize(1)
|
||||
.allMatch(action -> action.getStatus() == Status.WAIT_FOR_CONFIRMATION);
|
||||
|
||||
// verify action still in WFC state
|
||||
assertThat(newAction.getStatus()).isEqualTo(Status.WAIT_FOR_CONFIRMATION);
|
||||
|
||||
// no status entry RUNNING should be present in status history
|
||||
assertThat(controllerManagement.findActionStatusByAction(PAGE, newAction.getId())).hasSize(2)
|
||||
.noneMatch(status -> status.getStatus() == Status.RUNNING);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verify multiple actions in WFC state will be transferred in RUNNING state in case auto-confirmation is activated.")
|
||||
void activateAutoConfirmationInMultiAssignment() {
|
||||
enableMultiAssignments();
|
||||
enableConfirmationFlow();
|
||||
|
||||
final String controllerId = testdataFactory.createTarget().getControllerId();
|
||||
final Long dsId = testdataFactory.createDistributionSet().getId();
|
||||
final Long dsId2 = testdataFactory.createDistributionSet().getId();
|
||||
|
||||
final List<Action> actions = assignDistributionSets(
|
||||
Arrays.asList(toDeploymentRequest(controllerId, dsId), toDeploymentRequest(controllerId, dsId2)))
|
||||
.stream().flatMap(s -> s.getAssignedEntity().stream()).collect(Collectors.toList());
|
||||
assertThat(actions).hasSize(2);
|
||||
|
||||
assertThat(confirmationManagement.findActiveActionsWaitingConfirmation(controllerId)).hasSize(2)
|
||||
.allMatch(action -> action.getStatus() == Status.WAIT_FOR_CONFIRMATION);
|
||||
|
||||
confirmationManagement.activateAutoConfirmation(controllerId, null, null);
|
||||
|
||||
assertThat(confirmationManagement.findActiveActionsWaitingConfirmation(controllerId)).isEmpty();
|
||||
|
||||
assertThat(deploymentManagement.findActionsByTarget(controllerId, PAGE).getContent()).hasSize(2)
|
||||
.allMatch(action -> action.getStatus() == Status.RUNNING);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verify action in WFC state will be transferred in RUNNING state in case auto-confirmation is activated.")
|
||||
void activateAutoConfirmationOnActiveAction() {
|
||||
enableConfirmationFlow();
|
||||
|
||||
final String controllerId = testdataFactory.createTarget().getControllerId();
|
||||
final Long dsId = testdataFactory.createDistributionSet().getId();
|
||||
|
||||
// do assignment and verify
|
||||
assertThat(assignDistributionSet(dsId, controllerId).getAssignedEntity()).hasSize(1);
|
||||
|
||||
assertThat(confirmationManagement.findActiveActionsWaitingConfirmation(controllerId)).hasSize(1)
|
||||
.allMatch(action -> action.getStatus() == Status.WAIT_FOR_CONFIRMATION);
|
||||
|
||||
confirmationManagement.activateAutoConfirmation(controllerId, null, null);
|
||||
|
||||
assertThat(confirmationManagement.findActiveActionsWaitingConfirmation(controllerId)).isEmpty();
|
||||
|
||||
assertThat(deploymentManagement.findActionsByTarget(controllerId, PAGE).getContent()).hasSize(1)
|
||||
.allMatch(action -> action.getStatus() == Status.RUNNING);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verify created action after activating auto confirmation is directly in running state.")
|
||||
void activateAutoConfirmationAndCreateAction() {
|
||||
enableConfirmationFlow();
|
||||
|
||||
final String controllerId = testdataFactory.createTarget().getControllerId();
|
||||
final Long dsId = testdataFactory.createDistributionSet().getId();
|
||||
|
||||
assertThat(confirmationManagement.findActiveActionsWaitingConfirmation(controllerId)).isEmpty();
|
||||
|
||||
confirmationManagement.activateAutoConfirmation(controllerId, null, null);
|
||||
|
||||
// do assignment and verify
|
||||
assertThat(assignDistributionSet(dsId, controllerId).getAssignedEntity()).hasSize(1);
|
||||
|
||||
assertThat(confirmationManagement.findActiveActionsWaitingConfirmation(controllerId)).isEmpty();
|
||||
|
||||
assertThat(deploymentManagement.findActionsByTarget(controllerId, PAGE).getContent()).hasSize(1)
|
||||
.allMatch(action -> action.getStatus() == Status.RUNNING);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("getAutoConfirmationArguments")
|
||||
@Description("Verify activating auto confirmation with different parameters")
|
||||
void verifyAutoConfirmationActivationValues(final String initiator, final String remark) {
|
||||
final String controllerId = testdataFactory.createTarget().getControllerId();
|
||||
confirmationManagement.activateAutoConfirmation(controllerId, initiator, remark);
|
||||
|
||||
assertThat(targetManagement.getByControllerID(controllerId)).hasValueSatisfying(target -> {
|
||||
assertThat(target.getAutoConfirmationStatus()).isNotNull()
|
||||
.matches(status -> status.getTarget().getControllerId().equals(controllerId))
|
||||
.matches(status -> Objects.equals(status.getInitiator(), initiator))
|
||||
.matches(status -> Objects.equals(status.getCreatedBy(), "bumlux"))
|
||||
.matches(status -> Objects.equals(status.getRemark(), remark)).satisfies(status -> {
|
||||
final Instant activationTime = Instant.ofEpochMilli(status.getActivatedAt());
|
||||
assertThat(activationTime).isAfterOrEqualTo(activationTime.minusSeconds(3L));
|
||||
});
|
||||
});
|
||||
|
||||
confirmationManagement.deactivateAutoConfirmation(controllerId);
|
||||
verifyAutoConfirmationIsDisabled(controllerId);
|
||||
}
|
||||
|
||||
private static Stream<Arguments> getAutoConfirmationArguments() {
|
||||
return Stream.of(Arguments.of("TestUser", "TestRemark"), Arguments.of("TestUser", null),
|
||||
Arguments.of(null, "TestRemark"), Arguments.of(null, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verify activating already active auto confirmation will throw exception.")
|
||||
void verifyActivateAlreadyActiveAutoConfirmationThrowException() {
|
||||
final String controllerId = testdataFactory.createTarget().getControllerId();
|
||||
|
||||
confirmationManagement.activateAutoConfirmation(controllerId, "any", "any");
|
||||
assertThat(targetManagement.getByControllerID(controllerId))
|
||||
.hasValueSatisfying(target -> assertThat(target.getAutoConfirmationStatus()).isNotNull());
|
||||
|
||||
assertThatThrownBy(() -> confirmationManagement.activateAutoConfirmation(controllerId, "any", "any"))
|
||||
.isInstanceOf(AutoConfirmationAlreadyActiveException.class)
|
||||
.hasMessage("Auto confirmation is already active for device " + controllerId);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verify disabling already disabled auto confirmation will not have any affect.")
|
||||
void disableAlreadyDisabledAutoConfirmationHaveNoAffect() {
|
||||
final String controllerId = testdataFactory.createTarget().getControllerId();
|
||||
|
||||
verifyAutoConfirmationIsDisabled(controllerId);
|
||||
confirmationManagement.deactivateAutoConfirmation(controllerId);
|
||||
verifyAutoConfirmationIsDisabled(controllerId);
|
||||
}
|
||||
|
||||
private void verifyAutoConfirmationIsDisabled(final String controllerId) {
|
||||
assertThat(targetManagement.getByControllerID(controllerId))
|
||||
.hasValueSatisfying(target -> assertThat(target.getAutoConfirmationStatus()).isNull());
|
||||
}
|
||||
|
||||
private static DeploymentRequest toDeploymentRequest(final String controllerId, final Long distributionSetId) {
|
||||
return new DeploymentRequestBuilder(controllerId, distributionSetId).setConfirmationRequired(true).build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -796,6 +796,8 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest {
|
||||
assertThat(targetManagement.getByControllerID(DEFAULT_CONTROLLER_ID).get().getUpdateStatus())
|
||||
.isEqualTo(TargetUpdateStatus.IN_SYNC);
|
||||
|
||||
assertThat(actionRepository.findById(action.getId()))
|
||||
.hasValueSatisfying(a -> assertThat(a.getStatus()).isEqualTo(Status.FINISHED));
|
||||
assertThat(actionStatusRepository.count()).isEqualTo(3);
|
||||
assertThat(controllerManagement.findActionStatusByAction(PAGE, action.getId()).getNumberOfElements())
|
||||
.isEqualTo(3);
|
||||
@@ -806,7 +808,8 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest {
|
||||
+ "configured to accept them.")
|
||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
|
||||
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
|
||||
@Expect(type = ActionCreatedEvent.class, count = 1), @Expect(type = ActionUpdatedEvent.class, count = 1),
|
||||
@Expect(type = ActionCreatedEvent.class, count = 1),
|
||||
@Expect(type = ActionUpdatedEvent.class, count = 1),
|
||||
@Expect(type = TargetUpdatedEvent.class, count = 2),
|
||||
@Expect(type = TargetAssignDistributionSetEvent.class, count = 1),
|
||||
@Expect(type = TargetAttributesRequestedEvent.class, count = 1),
|
||||
|
||||
@@ -10,6 +10,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.eclipse.hawkbit.repository.model.Action.Status.RUNNING;
|
||||
import static org.eclipse.hawkbit.repository.model.Action.Status.WAIT_FOR_CONFIRMATION;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.util.AbstractMap.SimpleEntry;
|
||||
@@ -19,8 +21,10 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.validation.ConstraintViolationException;
|
||||
|
||||
@@ -58,6 +62,7 @@ import org.eclipse.hawkbit.repository.model.Action.ActionType;
|
||||
import org.eclipse.hawkbit.repository.model.Action.Status;
|
||||
import org.eclipse.hawkbit.repository.model.ActionStatus;
|
||||
import org.eclipse.hawkbit.repository.model.DeploymentRequest;
|
||||
import org.eclipse.hawkbit.repository.model.DeploymentRequestBuilder;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSetTag;
|
||||
@@ -70,6 +75,8 @@ import org.eclipse.hawkbit.repository.test.matcher.Expect;
|
||||
import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents;
|
||||
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
@@ -284,7 +291,8 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
|
||||
assignDistributionSet(cancelDs, targets).getAssignedEntity();
|
||||
assertThat(deploymentManagement.countActionsAll()).isEqualTo(quotaManagement.getMaxTargetsPerAutoAssignment());
|
||||
assignDistributionSet(cancelDs2, targets).getAssignedEntity();
|
||||
assertThat(deploymentManagement.countActionsAll()).isEqualTo(2L * quotaManagement.getMaxTargetsPerAutoAssignment());
|
||||
assertThat(deploymentManagement.countActionsAll())
|
||||
.isEqualTo(2L * quotaManagement.getMaxTargetsPerAutoAssignment());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -546,13 +554,13 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
|
||||
final DistributionSet ds1 = testdataFactory.createDistributionSet("1");
|
||||
assignDistributionSet(ds1, targets);
|
||||
|
||||
assertDsExclusivelyAssignedToTargets(targets, ds1.getId(), STATE_ACTIVE, Status.RUNNING);
|
||||
assertDsExclusivelyAssignedToTargets(targets, ds1.getId(), STATE_ACTIVE, RUNNING);
|
||||
|
||||
// Second assignment
|
||||
final DistributionSet ds2 = testdataFactory.createDistributionSet("2");
|
||||
assignDistributionSet(ds2, targets);
|
||||
|
||||
assertDsExclusivelyAssignedToTargets(targets, ds2.getId(), STATE_ACTIVE, Status.RUNNING);
|
||||
assertDsExclusivelyAssignedToTargets(targets, ds2.getId(), STATE_ACTIVE, RUNNING);
|
||||
assertDsExclusivelyAssignedToTargets(targets, ds1.getId(), STATE_INACTIVE, Status.CANCELED);
|
||||
|
||||
assertThat(targetManagement.findByAssignedDistributionSet(PAGE, ds2.getId()).getContent()).hasSize(10)
|
||||
@@ -583,14 +591,14 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
|
||||
final DistributionSet ds1 = testdataFactory.createDistributionSet("Multi-assign-1");
|
||||
assignDistributionSet(ds1.getId(), targetIds, 77);
|
||||
|
||||
assertDsExclusivelyAssignedToTargets(targets, ds1.getId(), STATE_ACTIVE, Status.RUNNING);
|
||||
assertDsExclusivelyAssignedToTargets(targets, ds1.getId(), STATE_ACTIVE, RUNNING);
|
||||
|
||||
// Second assignment
|
||||
final DistributionSet ds2 = testdataFactory.createDistributionSet("Multi-assign-2");
|
||||
assignDistributionSet(ds2.getId(), targetIds, 45);
|
||||
|
||||
assertDsExclusivelyAssignedToTargets(targets, ds2.getId(), STATE_ACTIVE, Status.RUNNING);
|
||||
assertDsExclusivelyAssignedToTargets(targets, ds1.getId(), STATE_ACTIVE, Status.RUNNING);
|
||||
assertDsExclusivelyAssignedToTargets(targets, ds2.getId(), STATE_ACTIVE, RUNNING);
|
||||
assertDsExclusivelyAssignedToTargets(targets, ds1.getId(), STATE_ACTIVE, RUNNING);
|
||||
}
|
||||
|
||||
private void assertDsExclusivelyAssignedToTargets(final List<Target> targets, final long dsId, final boolean active,
|
||||
@@ -651,7 +659,8 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
|
||||
void cancelMultiAssignmentActions() {
|
||||
final List<Target> targets = testdataFactory.createTargets(1);
|
||||
final List<DistributionSet> distributionSets = testdataFactory.createDistributionSets(4);
|
||||
final List<DeploymentRequest> deploymentRequests = createAssignmentRequests(distributionSets, targets, 34);
|
||||
final List<DeploymentRequest> deploymentRequests = createAssignmentRequests(distributionSets, targets, 34,
|
||||
false);
|
||||
|
||||
enableMultiAssignments();
|
||||
final List<DistributionSetAssignmentResult> results = deploymentManagement
|
||||
@@ -672,9 +681,15 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
|
||||
|
||||
protected List<DeploymentRequest> createAssignmentRequests(final Collection<DistributionSet> distributionSets,
|
||||
final Collection<Target> targets, final int weight) {
|
||||
return createAssignmentRequests(distributionSets, targets, weight, false);
|
||||
}
|
||||
|
||||
protected List<DeploymentRequest> createAssignmentRequests(final Collection<DistributionSet> distributionSets,
|
||||
final Collection<Target> targets, final int weight, final boolean confirmationRequired) {
|
||||
final List<DeploymentRequest> deploymentRequests = new ArrayList<>();
|
||||
distributionSets.forEach(ds -> targets.forEach(target -> deploymentRequests.add(DeploymentManagement
|
||||
.deploymentRequest(target.getControllerId(), ds.getId()).setWeight(weight).build())));
|
||||
distributionSets.forEach(ds -> targets.forEach(target -> deploymentRequests
|
||||
.add(DeploymentManagement.deploymentRequest(target.getControllerId(), ds.getId()).setWeight(weight)
|
||||
.setConfirmationRequired(confirmationRequired).build())));
|
||||
return deploymentRequests;
|
||||
}
|
||||
|
||||
@@ -713,7 +728,7 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
|
||||
knownTargetIds.add(notExistingId);
|
||||
|
||||
final List<DistributionSetAssignmentResult> assignDistributionSetsResults = assignDistributionSetToTargets(
|
||||
createdDs, knownTargetIds);
|
||||
createdDs, knownTargetIds, false);
|
||||
|
||||
for (final DistributionSetAssignmentResult assignDistributionSetsResult : assignDistributionSetsResults) {
|
||||
assertThat(assignDistributionSetsResult.getAlreadyAssigned()).isZero();
|
||||
@@ -722,12 +737,152 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = { true, false })
|
||||
@Description("Assignments with confirmation flow active will result in actions in 'WAIT_FOR_CONFIRMATION' state")
|
||||
void assignmentWithConfirmationFlowActive(final boolean confirmationRequired) {
|
||||
final List<String> controllerIds = testdataFactory.createTargets(1).stream().map(Target::getControllerId)
|
||||
.collect(Collectors.toList());
|
||||
final DistributionSet distributionSet = testdataFactory.createDistributionSet();
|
||||
|
||||
enableConfirmationFlow();
|
||||
List<DistributionSetAssignmentResult> results = assignDistributionSetToTargets(distributionSet, controllerIds,
|
||||
confirmationRequired);
|
||||
|
||||
assertThat(getResultingActionCount(results)).isEqualTo(controllerIds.size());
|
||||
|
||||
controllerIds.forEach(controllerId -> {
|
||||
actionRepository.findByTargetControllerId(PAGE, controllerId).forEach(action -> {
|
||||
assertThat(action.getDistributionSet().getId()).isIn(distributionSet.getId());
|
||||
assertThat(action.getInitiatedBy()).as("Should be Initiated by current user")
|
||||
.isEqualTo(tenantAware.getCurrentUsername());
|
||||
if (confirmationRequired) {
|
||||
assertThat(action.getStatus()).isEqualTo(Status.WAIT_FOR_CONFIRMATION);
|
||||
} else {
|
||||
assertThat(action.getStatus()).isEqualTo(RUNNING);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = { true, false })
|
||||
@Description("Verify auto confirmation assignments and check action status with messages")
|
||||
void assignmentWithAutoConfirmationWillBeHandledCorrectly(final boolean confirmationRequired) {
|
||||
enableConfirmationFlow();
|
||||
|
||||
final Target target = testdataFactory.createTarget();
|
||||
assertThat(target.getAutoConfirmationStatus()).isNull();
|
||||
|
||||
confirmationManagement.activateAutoConfirmation(target.getControllerId(), "not_bumlux", "my personal remark");
|
||||
|
||||
assertThat(targetManagement.getByControllerID(target.getControllerId()))
|
||||
.hasValueSatisfying(t -> assertThat(t.getAutoConfirmationStatus()).isNotNull());
|
||||
|
||||
final DistributionSet distributionSet = testdataFactory.createDistributionSet();
|
||||
|
||||
assignDistributionSets(Collections
|
||||
.singletonList(new DeploymentRequestBuilder(target.getControllerId(), distributionSet.getId())
|
||||
.setConfirmationRequired(confirmationRequired).build()));
|
||||
|
||||
assertThat(deploymentManagement.findActionsByTarget(target.getControllerId(), PAGE).getContent()).hasSize(1)
|
||||
.allSatisfy(action -> {
|
||||
assertThat(action.getStatus()).isEqualTo(RUNNING);
|
||||
assertThat(actionStatusRepository.getByActionId(PAGE, action.getId())).hasSize(1)
|
||||
.allSatisfy(status -> {
|
||||
final JpaActionStatus actionStatus = (JpaActionStatus) status;
|
||||
assertThat(actionStatus.getStatus()).isEqualTo(WAIT_FOR_CONFIRMATION);
|
||||
if (confirmationRequired) {
|
||||
// confirmation of assignment is basically required, but active
|
||||
// auto-confirmation will perform the confirmation
|
||||
assertThat(actionStatus.getMessages())
|
||||
.contains("Assignment initiated by user 'bumlux'")
|
||||
.contains("Assignment automatically confirmed by initiator 'not_bumlux'. \n"
|
||||
+ "\n" + "Auto confirmation activated by system user: 'bumlux' \n"
|
||||
+ "\n" + "Remark: my personal remark");
|
||||
} else {
|
||||
// assignment never required confirmation, auto-confirmation will not be
|
||||
// applied.
|
||||
// assignment initiator has confirmed the action already
|
||||
assertThat(actionStatus.getMessages())
|
||||
.contains("Assignment initiated by user 'bumlux'")
|
||||
.contains("Assignment confirmed by initiator [bumlux].");
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Multiple assignments with confirmation flow active will result in correct cancel behaviour")
|
||||
void multipleAssignmentWithConfirmationFlowActiveVerifyCancelBehaviour() {
|
||||
final Target target = testdataFactory.createTarget("firstDevice");
|
||||
final DistributionSet firstDs = testdataFactory.createDistributionSet();
|
||||
final DistributionSet secondDs = testdataFactory.createDistributionSet();
|
||||
|
||||
enableConfirmationFlow();
|
||||
final List<Action> resultActions = assignDistributionSet(firstDs.getId(), target.getControllerId())
|
||||
.getAssignedEntity();
|
||||
assertThat(resultActions).hasSize(1);
|
||||
|
||||
assertThat(resultActions.get(0)).satisfies(action -> {
|
||||
assertThat(action.getDistributionSet().getId()).isEqualTo(firstDs.getId());
|
||||
assertThat(action.getStatus()).isEqualTo(Status.WAIT_FOR_CONFIRMATION);
|
||||
});
|
||||
|
||||
final List<Action> resultActions2 = assignDistributionSet(secondDs.getId(), target.getControllerId())
|
||||
.getAssignedEntity();
|
||||
|
||||
assertThat(resultActions2).hasSize(1);
|
||||
assertThat(resultActions2.get(0)).satisfies(action -> {
|
||||
assertThat(action.getDistributionSet().getId()).isEqualTo(secondDs.getId());
|
||||
assertThat(action.getStatus()).isEqualTo(Status.WAIT_FOR_CONFIRMATION);
|
||||
});
|
||||
|
||||
final List<Action> actions = actionRepository.findByTargetControllerId(PAGE, target.getControllerId())
|
||||
.getContent();
|
||||
assertThat(actions).hasSize(2)
|
||||
.anyMatch(action -> Objects.equals(action.getDistributionSet().getId(), firstDs.getId())
|
||||
&& action.getStatus() == Status.CANCELING)
|
||||
.anyMatch(action -> Objects.equals(action.getDistributionSet().getId(), secondDs.getId())
|
||||
&& action.getStatus() == Status.WAIT_FOR_CONFIRMATION);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Assignments with confirmation flow deactivated will result in actions in only in 'RUNNING' state")
|
||||
void verifyConfirmationRequiredFlagHaveNoInfluenceIfFlowIsDeactivated() {
|
||||
final List<String> targets1 = testdataFactory.createTargets("group1", 1).stream().map(Target::getControllerId)
|
||||
.collect(Collectors.toList());
|
||||
final List<String> targets2 = testdataFactory.createTargets("group2", 1).stream().map(Target::getControllerId)
|
||||
.collect(Collectors.toList());
|
||||
final DistributionSet distributionSet = testdataFactory.createDistributionSet();
|
||||
|
||||
final List<DistributionSetAssignmentResult> results = Stream
|
||||
.concat(assignDistributionSetToTargets(distributionSet, targets1, true).stream(), //
|
||||
assignDistributionSetToTargets(distributionSet, targets2, false).stream()) //
|
||||
.collect(Collectors.toList());
|
||||
|
||||
final List<String> controllerIds = Stream.concat(targets1.stream(), targets2.stream())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
assertThat(getResultingActionCount(results)).isEqualTo(controllerIds.size());
|
||||
|
||||
controllerIds.forEach(controllerId -> {
|
||||
actionRepository.findByTargetControllerId(PAGE, controllerId).forEach(action -> {
|
||||
assertThat(action.getDistributionSet().getId()).isIn(distributionSet.getId());
|
||||
assertThat(action.getInitiatedBy()).as("Should be Initiated by current user")
|
||||
.isEqualTo(tenantAware.getCurrentUsername());
|
||||
assertThat(action.getStatus()).isEqualTo(RUNNING);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private List<DistributionSetAssignmentResult> assignDistributionSetToTargets(final DistributionSet distributionSet,
|
||||
final Iterable<String> targetIds) {
|
||||
final Iterable<String> targetIds, final boolean confirmationRequired) {
|
||||
final List<DeploymentRequest> deploymentRequests = new ArrayList<>();
|
||||
for (final String controllerId : targetIds) {
|
||||
deploymentRequests.add(new DeploymentRequest(controllerId, distributionSet.getId(), ActionType.FORCED, 0,
|
||||
null, null, null, null));
|
||||
null, null, null, null, confirmationRequired));
|
||||
}
|
||||
return deploymentManagement.assignDistributionSets(deploymentRequests);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.validation.ConstraintViolationException;
|
||||
import javax.validation.ValidationException;
|
||||
@@ -31,6 +32,7 @@ import org.assertj.core.api.Assertions;
|
||||
import org.assertj.core.api.Condition;
|
||||
import org.awaitility.Awaitility;
|
||||
import org.awaitility.Duration;
|
||||
import org.eclipse.hawkbit.repository.Identifiable;
|
||||
import org.eclipse.hawkbit.repository.OffsetBasedPageRequest;
|
||||
import org.eclipse.hawkbit.repository.builder.RolloutCreate;
|
||||
import org.eclipse.hawkbit.repository.builder.RolloutGroupCreate;
|
||||
@@ -81,6 +83,9 @@ import org.eclipse.hawkbit.repository.test.util.WithSpringAuthorityRule;
|
||||
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
@@ -139,6 +144,42 @@ class RolloutManagementTest extends AbstractJpaIntegrationTest {
|
||||
assertThat(rolloutCreatedAction.getStatus()).isEqualTo(Status.FINISHED);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("simpleRolloutsPossibilities")
|
||||
@Description("Verifies that action states are correctly initialized after starting a rollout with different options in regard to the confirmation.")
|
||||
void runRolloutWithConfirmationFlagAndCoonfirmationFlowOptions(final boolean confirmationFlowActive,
|
||||
final boolean confirmationRequired, final Status expectedStatus) {
|
||||
// manually assign distribution set to target
|
||||
final String knownControllerId = "controller12345";
|
||||
final DistributionSet knownDistributionSet = testdataFactory.createDistributionSet();
|
||||
testdataFactory.createTarget(knownControllerId);
|
||||
|
||||
if (confirmationFlowActive) {
|
||||
enableConfirmationFlow();
|
||||
}
|
||||
|
||||
// create rollout with the same distribution set already assigned
|
||||
// start rollout
|
||||
final Rollout rollout = testdataFactory.createRolloutByVariables("rolloutNotCancelRunningAction", "description",
|
||||
1, "name==*", knownDistributionSet, "50", "5", confirmationRequired);
|
||||
rolloutManagement.start(rollout.getId());
|
||||
rolloutManagement.handleRollouts();
|
||||
|
||||
// verify that manually created action is still running and action
|
||||
// created from rollout is finished
|
||||
final List<Action> actionsByKnownTarget = deploymentManagement.findActionsByTarget(knownControllerId, PAGE)
|
||||
.getContent();
|
||||
assertThat(actionsByKnownTarget).hasSize(1);
|
||||
assertThat(actionsByKnownTarget.get(0).getStatus()).isEqualTo(expectedStatus);
|
||||
}
|
||||
|
||||
private static Stream<Arguments> simpleRolloutsPossibilities() {
|
||||
return Stream.of(Arguments.of(true, true, Status.WAIT_FOR_CONFIRMATION), //
|
||||
Arguments.of(true, false, Status.RUNNING), //
|
||||
Arguments.of(false, true, Status.RUNNING), //
|
||||
Arguments.of(false, false, Status.RUNNING));//
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verifies that a running action is auto canceled by a rollout which assigns another distribution-set.")
|
||||
void rolloutAssignsNewDistributionSetAndAutoCloseActiveActions() {
|
||||
@@ -1369,7 +1410,7 @@ class RolloutManagementTest extends AbstractJpaIntegrationTest {
|
||||
.targetFilterQuery("controllerId==" + targetPrefixName + "-*").set(distributionSet);
|
||||
|
||||
assertThatExceptionOfType(AssignmentQuotaExceededException.class)
|
||||
.isThrownBy(() -> rolloutManagement.create(rollout, amountGroups, conditions));
|
||||
.isThrownBy(() -> rolloutManagement.create(rollout, amountGroups, false, conditions));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1540,6 +1581,117 @@ class RolloutManagementTest extends AbstractJpaIntegrationTest {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verify rollout execution with advanced group definition and confirmation flow active.")
|
||||
void createRolloutWithGroupDefinitionAndConfirmationFlowActive() {
|
||||
final String rolloutName = "rolloutTest4";
|
||||
|
||||
final int amountTargetsInGroup1 = 10;
|
||||
final int amountTargetsInGroup2 = 20;
|
||||
|
||||
final RolloutGroupConditions conditions = new RolloutGroupConditionBuilder().withDefaults().build();
|
||||
// Generate Targets for group 2
|
||||
final RolloutCreate rolloutcreate = generateTargetsAndRollout(rolloutName, amountTargetsInGroup2);
|
||||
|
||||
// Generate Targets for group 1
|
||||
testdataFactory.createTargets(amountTargetsInGroup1, rolloutName + "-gr1-", rolloutName);
|
||||
|
||||
final List<RolloutGroupCreate> rolloutGroups = new ArrayList<>(3);
|
||||
rolloutGroups.add(generateRolloutGroup(0, 100, "id==" + rolloutName + "-gr1-*", true));
|
||||
rolloutGroups.add(generateRolloutGroup(1, 100, null, false));
|
||||
|
||||
// enable confirmation flow
|
||||
enableConfirmationFlow();
|
||||
|
||||
final Long rolloutId = rolloutManagement.create(rolloutcreate, rolloutGroups, conditions).getId();
|
||||
|
||||
assertThat(getRollout(rolloutId)).satisfies(rollout -> {
|
||||
assertThat(rollout.getStatus()).isEqualTo(RolloutStatus.CREATING);
|
||||
for (final RolloutGroup group : rolloutGroupManagement.findByRollout(PAGE, rollout.getId()).getContent()) {
|
||||
assertThat(group.getStatus()).isEqualTo(RolloutGroupStatus.CREATING);
|
||||
}
|
||||
});
|
||||
|
||||
// first handle iteration will put rollout in ready state
|
||||
rolloutManagement.handleRollouts();
|
||||
|
||||
assertThat(getRollout(rolloutId)).satisfies(rollout -> {
|
||||
assertThat(rollout.getStatus()).isEqualTo(RolloutStatus.READY);
|
||||
assertThat(rollout.getTotalTargets()).isEqualTo(amountTargetsInGroup1 + amountTargetsInGroup2);
|
||||
});
|
||||
|
||||
// verify created rollout groups
|
||||
final List<Long> rolloutGroupIds = rolloutGroupManagement.findByRollout(PAGE, rolloutId).getContent().stream()
|
||||
.map(Identifiable::getId).collect(Collectors.toList());
|
||||
assertThat(rolloutGroupIds).hasSize(2);
|
||||
assertRolloutGroup(rolloutGroupIds.get(0), RolloutGroupStatus.READY, true, amountTargetsInGroup1, null);
|
||||
assertRolloutGroup(rolloutGroupIds.get(1), RolloutGroupStatus.READY, false, amountTargetsInGroup2, null);
|
||||
|
||||
// start rollout
|
||||
rolloutManagement.start(rolloutId);
|
||||
rolloutManagement.handleRollouts();
|
||||
|
||||
// verify rollout started. Check groups are in right state.
|
||||
// Group 1 should be in WFC state, since confirmation is required here.
|
||||
assertRolloutGroup(rolloutGroupIds.get(0), RolloutGroupStatus.RUNNING, true, amountTargetsInGroup1,
|
||||
Status.WAIT_FOR_CONFIRMATION);
|
||||
assertRolloutGroup(rolloutGroupIds.get(1), RolloutGroupStatus.SCHEDULED, false, amountTargetsInGroup2,
|
||||
Status.SCHEDULED);
|
||||
|
||||
// cancel execution of all action of group 1 to trigger second group
|
||||
forceQuitAllActionsOfRolloutGroup(rolloutGroupIds.get(0));
|
||||
|
||||
rolloutManagement.handleRollouts();
|
||||
assertRolloutGroup(rolloutGroupIds.get(0), RolloutGroupStatus.FINISHED, true, amountTargetsInGroup1,
|
||||
Status.CANCELED);
|
||||
assertRolloutGroup(rolloutGroupIds.get(1), RolloutGroupStatus.SCHEDULED, false, amountTargetsInGroup2,
|
||||
Status.SCHEDULED);
|
||||
|
||||
// verify actions of second rule are directly in RUNNING state, since
|
||||
// confirmation is not required for this group
|
||||
rolloutManagement.handleRollouts();
|
||||
assertRolloutGroup(rolloutGroupIds.get(0), RolloutGroupStatus.FINISHED, true, amountTargetsInGroup1,
|
||||
Status.CANCELED);
|
||||
assertRolloutGroup(rolloutGroupIds.get(1), RolloutGroupStatus.RUNNING, false, amountTargetsInGroup2,
|
||||
Status.RUNNING);
|
||||
|
||||
}
|
||||
|
||||
private void assertRolloutGroup(final long rolloutGroupId, final RolloutGroupStatus status,
|
||||
final boolean isConfirmationRequired, final long totalTargets, final Status actionStatusToCheck) {
|
||||
assertThat(rolloutGroupManagement.get(rolloutGroupId)).hasValueSatisfying(rolloutGroup -> {
|
||||
assertThat(rolloutGroup.getStatus()).isEqualTo(status);
|
||||
assertThat(rolloutGroup.isConfirmationRequired()).isEqualTo(isConfirmationRequired);
|
||||
assertThat(rolloutGroup.getTotalTargets()).isEqualTo(totalTargets);
|
||||
if (actionStatusToCheck != null) {
|
||||
assertAllActionOfRolloutGroupHavingStatus(rolloutGroup.getId(), actionStatusToCheck);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void assertAllActionOfRolloutGroupHavingStatus(final long rolloutGroupId, final Status status) {
|
||||
final List<Target> targets = rolloutGroupManagement.findTargetsOfRolloutGroup(PAGE, rolloutGroupId)
|
||||
.getContent();
|
||||
targets.forEach(target -> {
|
||||
final List<Action> activeActions = deploymentManagement
|
||||
.findActionsByTarget(target.getControllerId(), PAGE).getContent();
|
||||
assertThat(activeActions).hasSize(1);
|
||||
assertThat(activeActions.get(0).getStatus()).isEqualTo(status);
|
||||
});
|
||||
}
|
||||
|
||||
private void forceQuitAllActionsOfRolloutGroup(final long rolloutGroupId) {
|
||||
final List<Target> targets = rolloutGroupManagement.findTargetsOfRolloutGroup(PAGE, rolloutGroupId)
|
||||
.getContent();
|
||||
targets.forEach(target -> {
|
||||
deploymentManagement.findActiveActionsByTarget(PAGE, target.getControllerId()).getContent().stream().map(Identifiable::getId)
|
||||
.forEach(actionId -> {
|
||||
deploymentManagement.cancelAction(actionId);
|
||||
deploymentManagement.forceQuitAction(actionId);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verify rollout creation fails if group definition does not address all targets")
|
||||
void createRolloutWithGroupsNotMatchingTargets() {
|
||||
@@ -1593,7 +1745,7 @@ class RolloutManagementTest extends AbstractJpaIntegrationTest {
|
||||
final RolloutCreate rollout = generateTargetsAndRollout(rolloutName, targets);
|
||||
|
||||
assertThatExceptionOfType(AssignmentQuotaExceededException.class)
|
||||
.isThrownBy(() -> rolloutManagement.create(rollout, maxGroups + 1, conditions))
|
||||
.isThrownBy(() -> rolloutManagement.create(rollout, maxGroups + 1, false, conditions))
|
||||
.withMessageContaining("not be greater than " + maxGroups);
|
||||
}
|
||||
|
||||
@@ -1615,7 +1767,7 @@ class RolloutManagementTest extends AbstractJpaIntegrationTest {
|
||||
final RolloutCreate rolloutToCreate = entityFactory.rollout().create().name(rolloutName)
|
||||
.description("some description").targetFilterQuery("id==" + rolloutName + "-*").set(distributionSet);
|
||||
|
||||
Rollout myRollout = rolloutManagement.create(rolloutToCreate, amountGroups, conditions);
|
||||
Rollout myRollout = rolloutManagement.create(rolloutToCreate, amountGroups, false, conditions);
|
||||
myRollout = getRollout(myRollout.getId());
|
||||
|
||||
assertThat(myRollout.getStatus()).isEqualTo(RolloutStatus.CREATING);
|
||||
@@ -1787,7 +1939,7 @@ class RolloutManagementTest extends AbstractJpaIntegrationTest {
|
||||
.name(prefixRolloutRunning + "-testRollout").targetFilterQuery("name==" + randomString + "*")
|
||||
.set(testDs);
|
||||
|
||||
Rollout rolloutRunning = rolloutManagement.create(rolloutRunningCreate, 1, conditions);
|
||||
Rollout rolloutRunning = rolloutManagement.create(rolloutRunningCreate, 1, false, conditions);
|
||||
// Let the executor handle created Rollout
|
||||
rolloutManagement.handleRollouts();
|
||||
// start the rollout, so it has active running actions and a group which
|
||||
@@ -1799,7 +1951,7 @@ class RolloutManagementTest extends AbstractJpaIntegrationTest {
|
||||
final String prefixRolloutReady = randomString + "2";
|
||||
final RolloutCreate rolloutReadyCreate = entityFactory.rollout().create()
|
||||
.name(prefixRolloutReady + "-testRollout").targetFilterQuery("name==" + randomString + "*").set(testDs);
|
||||
Rollout rolloutReady = rolloutManagement.create(rolloutReadyCreate, 1, conditions);
|
||||
Rollout rolloutReady = rolloutManagement.create(rolloutReadyCreate, 1, false, conditions);
|
||||
// Let the executor handle created Rollout
|
||||
rolloutManagement.handleRollouts();
|
||||
rolloutReady = reloadRollout(rolloutReady);
|
||||
@@ -1959,7 +2111,7 @@ class RolloutManagementTest extends AbstractJpaIntegrationTest {
|
||||
final RolloutCreate rolloutToCreate = entityFactory.rollout().create().name(rolloutName)
|
||||
.targetFilterQuery("name==*").set(testDs);
|
||||
|
||||
final Rollout createdRollout = rolloutManagement.create(rolloutToCreate, 1, conditions);
|
||||
final Rollout createdRollout = rolloutManagement.create(rolloutToCreate, 1, false, conditions);
|
||||
|
||||
// Let the executor handle created Rollout
|
||||
rolloutManagement.handleRollouts();
|
||||
@@ -1982,8 +2134,14 @@ class RolloutManagementTest extends AbstractJpaIntegrationTest {
|
||||
|
||||
private RolloutGroupCreate generateRolloutGroup(final int index, final Integer percentage,
|
||||
final String targetFilter) {
|
||||
return generateRolloutGroup(index, percentage, targetFilter, false);
|
||||
}
|
||||
|
||||
private RolloutGroupCreate generateRolloutGroup(final int index, final Integer percentage,
|
||||
final String targetFilter, final boolean confirmationRequired) {
|
||||
return entityFactory.rolloutGroup().create().name("Group" + index).description("Group" + index + "desc")
|
||||
.targetPercentage(Float.valueOf(percentage)).targetFilterQuery(targetFilter);
|
||||
.targetPercentage(Float.valueOf(percentage)).targetFilterQuery(targetFilter)
|
||||
.confirmationRequired(confirmationRequired);
|
||||
}
|
||||
|
||||
private RolloutCreate generateTargetsAndRollout(final String rolloutName, final int amountTargetsForRollout) {
|
||||
@@ -2033,7 +2191,7 @@ class RolloutManagementTest extends AbstractJpaIntegrationTest {
|
||||
testdataFactory.createTargets(amountTargetsForRollout, targetPrefixName + "-", targetPrefixName);
|
||||
return testdataFactory.createRolloutByVariables(rolloutName, rolloutName + "description", groupSize,
|
||||
"controllerId==" + targetPrefixName + "-*", dsForRolloutTwo, successCondition, errorCondition,
|
||||
Action.ActionType.FORCED, weight);
|
||||
Action.ActionType.FORCED, weight, false);
|
||||
}
|
||||
|
||||
private int changeStatusForAllRunningActions(final Rollout rollout, final Status status) {
|
||||
|
||||
@@ -33,6 +33,9 @@ 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.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Slice;
|
||||
@@ -174,6 +177,69 @@ class AutoAssignCheckerIntTest extends AbstractJpaIntegrationTest {
|
||||
verifyThatTargetsNotHaveDistributionSetAssignment(toAssignDs, targets.subList(1, 25));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("confirmationOptions")
|
||||
@Description("Test auto assignment of a DS to filtered targets with different confirmation options")
|
||||
void checkAutoAssignWithConfirmationOptions(final boolean confirmationFlowActive, final boolean confirmationRequired,
|
||||
final Action.Status expectedStatus) {
|
||||
|
||||
final DistributionSet distributionSet = testdataFactory.createDistributionSet("dsA");
|
||||
|
||||
if (confirmationFlowActive) {
|
||||
enableConfirmationFlow();
|
||||
}
|
||||
|
||||
final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement.updateAutoAssignDS(entityFactory
|
||||
.targetFilterQuery()
|
||||
.updateAutoAssign(targetFilterQueryManagement
|
||||
.create(entityFactory.targetFilterQuery().create().name("filterA").query("name==*")).getId())
|
||||
.ds(distributionSet.getId()).confirmationRequired(confirmationRequired));
|
||||
|
||||
final String targetDsAIdPref = "targ";
|
||||
final List<Target> targets = testdataFactory.createTargets(20, targetDsAIdPref,
|
||||
targetDsAIdPref.concat(" description"));
|
||||
|
||||
// Run the check
|
||||
autoAssignChecker.checkAllTargets();
|
||||
|
||||
verifyThatTargetsHaveDistributionSetAssignedAndActionStatus(distributionSet, targets, expectedStatus);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("confirmationOptions")
|
||||
@Description("Test auto assignment of a DS for a specific device with different confirmation options")
|
||||
void checkAutoAssignmentForDeviceWithConfirmationRequired(final boolean confirmationFlowActive,
|
||||
final boolean confirmationRequired, final Action.Status expectedStatus) {
|
||||
|
||||
final DistributionSet toAssignDs = testdataFactory.createDistributionSet();
|
||||
|
||||
if (confirmationFlowActive) {
|
||||
enableConfirmationFlow();
|
||||
}
|
||||
|
||||
// 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()).confirmationRequired(confirmationRequired));
|
||||
|
||||
final List<Target> targets = testdataFactory.createTargets(25);
|
||||
|
||||
// Run the check
|
||||
autoAssignChecker.checkSingleTarget(targets.get(0).getControllerId());
|
||||
verifyThatTargetsHaveDistributionSetAssignedAndActionStatus(toAssignDs, targets.subList(0, 1), expectedStatus);
|
||||
|
||||
verifyThatTargetsNotHaveDistributionSetAssignment(toAssignDs, targets.subList(1, 25));
|
||||
}
|
||||
|
||||
private static Stream<Arguments> confirmationOptions() {
|
||||
return Stream.of( //
|
||||
Arguments.of(true, true, Status.WAIT_FOR_CONFIRMATION), //
|
||||
Arguments.of(true, false, Status.RUNNING), //
|
||||
Arguments.of(false, true, Status.RUNNING), //
|
||||
Arguments.of(false, false, Status.RUNNING));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Test auto assignment of an incomplete DS to filtered targets, that causes failures")
|
||||
void checkAutoAssignWithFailures() {
|
||||
@@ -246,6 +312,22 @@ class AutoAssignCheckerIntTest extends AbstractJpaIntegrationTest {
|
||||
|
||||
}
|
||||
|
||||
@Step
|
||||
private void verifyThatTargetsHaveDistributionSetAssignedAndActionStatus(final DistributionSet set,
|
||||
final List<Target> targets, final Action.Status status) {
|
||||
final List<String> targetIds = targets.stream().map(Target::getControllerId).collect(Collectors.toList());
|
||||
final List<Target> targetsWithAssignedDS = targetManagement.findByAssignedDistributionSet(PAGE, set.getId())
|
||||
.getContent();
|
||||
assertThat(targetsWithAssignedDS).isNotEmpty();
|
||||
assertThat(targetsWithAssignedDS).allMatch(target -> targetIds.contains(target.getControllerId()));
|
||||
|
||||
final List<Action> actionsByDs = deploymentManagement.findActionsByDistributionSet(PAGE, set.getId())
|
||||
.getContent();
|
||||
|
||||
assertThat(actionsByDs).hasSize(targets.size());
|
||||
assertThat(actionsByDs).allMatch(action -> action.getStatus() == status);
|
||||
}
|
||||
|
||||
@Step
|
||||
private void verifyThatTargetsNotHaveDistributionSetAssignment(final DistributionSet set,
|
||||
final List<Target> targets) {
|
||||
|
||||
@@ -99,7 +99,7 @@ public class RSQLRolloutGroupFieldTest extends AbstractJpaIntegrationTest {
|
||||
return rolloutManagement.create(
|
||||
entityFactory.rollout().create().set(distributionSetManagement.get(distributionSetId).get()).name(name)
|
||||
.targetFilterQuery(targetFilterQuery),
|
||||
amountGroups, new RolloutGroupConditionBuilder().withDefaults()
|
||||
amountGroups, false, new RolloutGroupConditionBuilder().withDefaults()
|
||||
.successCondition(RolloutGroupSuccessCondition.THRESHOLD, "100").build());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user