Action history cleanup/purge initial (#2728)

* Action history cleanup/purge initial

Signed-off-by: strailov <Stanislav.Trailov@bosch.io>

* apply changes after review

Signed-off-by: strailov <Stanislav.Trailov@bosch.io>

* fix hibernate build by annotating delete methods with transactional annotation

Signed-off-by: strailov <Stanislav.Trailov@bosch.io>

* changes after review and new test cases for new requirements

* accept 0 for keep last

Signed-off-by: strailov <Stanislav.Trailov@bosch.io>

* Fix ManagementSecurityTest

Signed-off-by: strailov <Stanislav.Trailov@bosch.io>

* apply object utils check

Signed-off-by: strailov <Stanislav.Trailov@bosch.io>

* fix for oldestAction deletion

Signed-off-by: strailov <Stanislav.Trailov@bosch.io>

* remove unused comment

Signed-off-by: strailov <Stanislav.Trailov@bosch.io>

* rename action ids variable

Signed-off-by: strailov <Stanislav.Trailov@bosch.io>

* Fix access control handling

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>

---------

Signed-off-by: strailov <Stanislav.Trailov@bosch.io>
Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
Co-authored-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Stanislav Trailov
2025-10-21 10:34:58 +03:00
committed by GitHub
parent 9984c89183
commit f1c3d0175e
21 changed files with 662 additions and 44 deletions

View File

@@ -14,6 +14,7 @@ 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 static org.junit.jupiter.api.Assertions.assertThrows;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
@@ -34,6 +35,7 @@ import org.assertj.core.api.Assertions;
import org.eclipse.hawkbit.repository.ActionStatusFields;
import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.DistributionSetTagManagement;
import org.eclipse.hawkbit.repository.Identifiable;
import org.eclipse.hawkbit.repository.event.remote.CancelTargetAssignmentEvent;
import org.eclipse.hawkbit.repository.event.remote.MultiActionAssignEvent;
import org.eclipse.hawkbit.repository.event.remote.MultiActionCancelEvent;
@@ -75,6 +77,7 @@ 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.Rollout;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetType;
@@ -1673,6 +1676,105 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
.isThrownBy(() -> deploymentManagement.assignDistributionSets(deploymentRequests));
}
@Test
void testManualAssignmentsActionsPurge() {
Target target = testdataFactory.createTarget();
for (int i = 0; i < 20; i++) {
DistributionSet distributionSet = testdataFactory.createDistributionSet("ds_" + i);
assignDistributionSet(distributionSet.getId(), target.getControllerId());
}
long actions = deploymentManagement.countActionsByTarget(target.getControllerId());
// quota in tests is set to 20 ...
assertEquals(20, actions);
// extract the first 5 action ids
List<Action> firstSample = deploymentManagement.findActionsByTarget(target.getControllerId(), PAGE).getContent();
List<Long> shouldBePurgedActionsList = firstSample.stream().map(Identifiable::getId).limit(5).toList();
DistributionSet exceededQuotaDsAssign = testdataFactory.createDistributionSet("exceededQuotaAssignment");
// should throw quota exception if not explicitly configured to purge actions
assertThrows(AssignmentQuotaExceededException.class,
() -> assignDistributionSet(exceededQuotaDsAssign.getId(), target.getControllerId()));
// set purge config to 25 %
tenantConfigurationManagement.addOrUpdateConfiguration("actions.cleanup.onQuotaHit.percent", 25);
// assign again
assignDistributionSet(exceededQuotaDsAssign.getId(), target.getControllerId());
// 16 actions should be present
actions = deploymentManagement.countActionsByTarget(target.getControllerId());
assertEquals(16, actions);
List<Action> actionsList = deploymentManagement.findActionsByTarget(target.getControllerId(), PAGE).getContent();
// first 5 should have been purged so the first actionId should be the last purged action id + 1
assertEquals(shouldBePurgedActionsList.get(shouldBePurgedActionsList.size() - 1) + 1, actionsList.get(0).getId());
assertEquals(firstSample.get(firstSample.size() - 1).getId() + 1, actionsList.get(15).getId());
}
@Test
void testRolloutAssignmentsActionsPurge() {
final Target target = testdataFactory.createTarget();
for (int i = 0; i < 20; i++) {
DistributionSet distributionSet = testdataFactory.createDistributionSet();
Rollout rollout = testdataFactory.createRolloutByVariables("rollout-" + i, "Description", 1, "controllerId==" + target
.getControllerId(),
distributionSet, "50", "50");
rolloutManagement.start(rollout.getId());
rolloutHandler.handleAll();
}
assertEquals(20, deploymentManagement.countActionsByTarget(target.getControllerId()));
List<Action> firstSample = deploymentManagement.findActionsByTarget(target.getControllerId(), PAGE).getContent();
List<Long> shouldBePurgedActionsList = firstSample.stream().map(Identifiable::getId).limit(5).toList();
DistributionSet distributionSet = testdataFactory.createDistributionSet();
Rollout rollout = testdataFactory.createRolloutByVariables("rollout-quota", "Description", 1, "controllerId==" + target
.getControllerId(),
distributionSet, "50", "50");
rolloutManagement.start(rollout.getId());
// don't assert quota exception here because rollout executor does not throw such in order to not interrupt other executions
rolloutHandler.handleAll();
//check that the old number of actions remain instead
assertEquals(20, deploymentManagement.countActionsByTarget(target.getControllerId()));
// set purge config to 25 %
tenantConfigurationManagement.addOrUpdateConfiguration("actions.cleanup.onQuotaHit.percent", 25);
rolloutHandler.handleAll();
assertEquals(16, deploymentManagement.countActionsByTarget(target.getControllerId()));
List<Action> actionsList = deploymentManagement.findActionsByTarget(target.getControllerId(), PAGE).getContent();
// first 5 should have been purged so the first actionId should be the last purged action id + 1
assertEquals(shouldBePurgedActionsList.get(shouldBePurgedActionsList.size() - 1) + 1, actionsList.get(0).getId());
assertEquals(firstSample.get(firstSample.size() - 1).getId() + 1, actionsList.get(15).getId());
}
@Test
void testThatOnlyNeededNumberOfActionsIsPurged() {
final Target target = testdataFactory.createTarget();
for (int i = 0; i < 18; i++) {
DistributionSet distributionSet = testdataFactory.createDistributionSet();
Rollout rollout = testdataFactory.createRolloutByVariables("rollout-" + i, "Description", 1, "controllerId==" + target
.getControllerId(),
distributionSet, "50", "50");
rolloutManagement.start(rollout.getId());
rolloutHandler.handleAll();
}
tenantConfigurationManagement.addOrUpdateConfiguration("actions.cleanup.onQuotaHit.percent", 25);
deploymentManagement.handleMaxAssignmentsExceeded(target.getId(), 5L, new AssignmentQuotaExceededException());
// only 3 actions should be deleted in such case :
assertEquals(15, deploymentManagement.countActionsByTarget(target.getControllerId()));
// should throw the quota exception if requested is bigger than the configured limit of actions purge
assertThrows(AssignmentQuotaExceededException.class, () ->
deploymentManagement.handleMaxAssignmentsExceeded(target.getId(), 10L, new AssignmentQuotaExceededException()));
}
private List<DeploymentRequest> createAssignmentRequests(
final Collection<DistributionSet> distributionSets, final Collection<Target> targets, final int weight) {
return createAssignmentRequests(distributionSets, targets, weight, false);

View File

@@ -139,6 +139,10 @@ class ManagementSecurityTest extends AbstractJpaIntegrationTest {
method.getReturnType() != String.class)
// jacoco adds some methods with bytecode instrumentation
.filter(method -> !"$jacocoInit".equals(method.getName()))
// skip maxAssignmentsExceededHandler in DeploymentManagement since it throws quota exception
// because of actions.cleanup.onQuotaHit.percent not configured
// other option would be to configure it for all tests
.filter(method -> !"handleMaxAssignmentsExceeded".equals(method.getName()))
.map(method -> Arguments.of(clazz, method)))
// consumes the stream because scan result couldn't be used after being closed
.toList()