[#2429] Add completeness property for software modules (#2765)

* add `min artifacts` requirement on the Software Module Type level for Software Module completeness
* removed `complete` Distribution Set property from DB - calculated runtime
* Distribution Set and Software Module completeness is calcualted on demand in memory (TODO: implement cache)
* locking of Software Module now requires the software module to be `completed`
* removed 'complete' search field for DistributionSet type. Still keep (DEPRECATED) limited support for search with 'complete' -
  only on the first level of expression and with AND. I.e. complete==true, complete==false and id=in=(1, 3) is suppoted,
  while complete==false or id=in=(1, 3) and id=in(1, 3) and (type==os and complete==true) are not

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2025-10-22 09:57:45 +03:00
committed by GitHub
parent f1c3d0175e
commit e154e1b18a
32 changed files with 377 additions and 155 deletions

View File

@@ -31,7 +31,7 @@ class DistributionSetUpdatedEventTest extends AbstractRemoteEntityEventTest<Dist
@Override
protected RemoteEntityEvent<?> createRemoteEvent(final DistributionSet baseEntity, final Class<? extends RemoteEntityEvent<?>> eventType) {
return new DistributionSetUpdatedEvent(baseEntity, true);
return new DistributionSetUpdatedEvent(baseEntity);
}
@Override

View File

@@ -1357,8 +1357,7 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
List<? extends DistributionSet> allFoundDS = distributionSetManagement.findAll(PAGE).getContent();
assertThat(allFoundDS).as("no ds should be founded").isEmpty();
assertThat(distributionSetRepository.findAll(JpaManagementHelper.combineWithAnd(
List.of(DistributionSetSpecification.isDeleted(true), DistributionSetSpecification.isCompleted(true))), PAGE).getContent())
assertThat(distributionSetRepository.findAll(DistributionSetSpecification.isDeleted(true), PAGE).getContent())
.as("wrong size of founded ds").hasSize(noOfDistributionSets);
IntStream.range(0, deploymentResult.getDistributionSets().size()).forEach(i -> testdataFactory.sendUpdateActionStatusToTargets(
@@ -1369,9 +1368,8 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
// verify that the result is the same, even though distributionSet dsA has been installed
// successfully and no activeAction is referring to created distribution sets
allFoundDS = distributionSetManagement.findAll(pageRequest).getContent();
assertThat(allFoundDS).as("no ds should be founded").isEmpty();
assertThat(distributionSetRepository.findAll(JpaManagementHelper.combineWithAnd(
List.of(DistributionSetSpecification.isDeleted(true), DistributionSetSpecification.isCompleted(true))), PAGE).getContent())
assertThat(allFoundDS).as("no ds should be found").isEmpty();
assertThat(distributionSetRepository.findAll(DistributionSetSpecification.isDeleted(true), PAGE).getContent())
.as("wrong size of founded ds").hasSize(noOfDistributionSets);
}

View File

@@ -12,14 +12,15 @@ package org.eclipse.hawkbit.repository.jpa.management;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import jakarta.validation.ConstraintViolationException;
@@ -33,6 +34,7 @@ import org.eclipse.hawkbit.repository.DistributionSetTypeManagement;
import org.eclipse.hawkbit.repository.Identifiable;
import org.eclipse.hawkbit.repository.RepositoryProperties;
import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
import org.eclipse.hawkbit.repository.SoftwareModuleTypeManagement;
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetTagCreatedEvent;
@@ -49,13 +51,16 @@ import org.eclipse.hawkbit.repository.exception.UnsupportedSoftwareModuleForThis
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.ActionCancellationType;
import org.eclipse.hawkbit.repository.model.ArtifactUpload;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation;
import org.eclipse.hawkbit.repository.model.DistributionSetTag;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.NamedEntity;
import org.eclipse.hawkbit.repository.model.NamedVersionedEntity;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.repository.model.Statistic;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.test.matcher.Expect;
@@ -688,6 +693,95 @@ class DistributionSetManagementTest extends AbstractRepositoryManagementWithMeta
.isThrownBy(() -> distributionSetManagement.getValidAndComplete(distributionSetId));
}
/**
* Verifies that when no SoftwareModules are assigned to a Distribution then the DistributionSet is not complete.
*/
@Test
void incompleteIfNoSoftwareModulesAssigned() {
final SoftwareModuleType softwareModuleType = softwareModuleTypeManagement
.create(SoftwareModuleTypeManagement.Create.builder().key("newType").name("new Type").build());
final DistributionSetType distributionSetType = distributionSetTypeManagement
.create(DistributionSetTypeManagement.Create.builder()
.key("newType").name("new Type").optionalModuleTypes(Set.of(softwareModuleType)).build());
final DistributionSet distributionSetIncomplete = testdataFactory.createDistributionSet(
"DistributionOne", "3.1.2", distributionSetType, new ArrayList<>());
assertThat(distributionSetIncomplete.isComplete()).isFalse();
assertThatExceptionOfType(IncompleteDistributionSetException.class)
.isThrownBy(() -> distributionSetManagement.lock(distributionSetIncomplete));
final long dsId = distributionSetIncomplete.getId();
assertThatExceptionOfType(IncompleteDistributionSetException.class)
.isThrownBy(() -> distributionSetManagement.getValidAndComplete(dsId));
final SoftwareModule softwareModule = softwareModuleManagement
.create(SoftwareModuleManagement.Create.builder().name("ds").version("1.0.0").type(softwareModuleType).build());
assertThat(softwareModule.isComplete()).isTrue();
distributionSetManagement.assignSoftwareModules(distributionSetIncomplete.getId(), List.of(softwareModule.getId()));
final DistributionSet distributionSetComplete = distributionSetManagement.get(distributionSetIncomplete.getId());
assertThat(distributionSetComplete.isComplete()).isTrue();
assertThatNoException().isThrownBy(() -> distributionSetManagement.lock(distributionSetComplete));
assertThat(softwareModuleManagement.get(softwareModule.getId()).isComplete()).isTrue();
}
@Test
void incompleteIfNoSoftwareModuleOfMandatorySoftwareModuleTypeAssigned() {
final SoftwareModuleType softwareModuleType = softwareModuleTypeManagement
.create(SoftwareModuleTypeManagement.Create.builder().key("newType").name("new Type").build());
final DistributionSetType distributionSetType = distributionSetTypeManagement
.create(DistributionSetTypeManagement.Create.builder()
.key("newType").name("new Type").mandatoryModuleTypes(Set.of(softwareModuleType)).build());
final DistributionSet distributionSetIncomplete = testdataFactory.createDistributionSet(
"DistributionOne", "3.1.2", distributionSetType, new ArrayList<>());
assertThat(distributionSetIncomplete.isComplete()).isFalse();
assertThatExceptionOfType(IncompleteDistributionSetException.class)
.isThrownBy(() -> distributionSetManagement.lock(distributionSetIncomplete));
final long dsId = distributionSetIncomplete.getId();
assertThatExceptionOfType(IncompleteDistributionSetException.class)
.isThrownBy(() -> distributionSetManagement.getValidAndComplete(dsId));
final SoftwareModule softwareModule = softwareModuleManagement
.create(SoftwareModuleManagement.Create.builder().name("ds").version("1.0.0").type(softwareModuleType).build());
assertThat(softwareModule.isComplete()).isTrue();
distributionSetManagement.assignSoftwareModules(distributionSetIncomplete.getId(), List.of(softwareModule.getId()));
final DistributionSet distributionSetComplete = distributionSetManagement.get(distributionSetIncomplete.getId());
assertThat(distributionSetComplete.isComplete()).isTrue();
assertThatNoException().isThrownBy(() -> distributionSetManagement.lock(distributionSetComplete));
assertThat(softwareModuleManagement.get(softwareModule.getId()).isComplete()).isTrue();
}
@Test
void incompleteIfDistributionSetSoftwareModuleIsIncomplete() {
final SoftwareModuleType softwareModuleType = softwareModuleTypeManagement
.create(SoftwareModuleTypeManagement.Create.builder().key("newType").name("new Type").minArtifacts(1).build());
final SoftwareModule softwareModuleIncomplete = softwareModuleManagement
.create(SoftwareModuleManagement.Create.builder().name("ds").version("1.0.0").type(softwareModuleType).build());
assertThat(softwareModuleIncomplete.isComplete()).isFalse();
final DistributionSetType distributionSetType = distributionSetTypeManagement
.create(DistributionSetTypeManagement.Create.builder()
.key("newType").name("new Type").optionalModuleTypes(Set.of(softwareModuleType)).build());
final DistributionSet distributionSetIncomplete = testdataFactory.createDistributionSet(
"DistributionOne", "3.1.2", distributionSetType, new ArrayList<>());
assertThat(distributionSetIncomplete.isComplete()).isFalse(); // no software modules assigned yet
distributionSetManagement.assignSoftwareModules(distributionSetIncomplete.getId(), List.of(softwareModuleIncomplete.getId()));
assertThat(distributionSetIncomplete.isComplete()).isFalse(); // has software module assigned, but incomplete
// add artifact - so it should become complete
artifactManagement.create(new ArtifactUpload(
new ByteArrayInputStream(randomBytes(10)), null, 10, null,
softwareModuleIncomplete.getId(), "file1", false));
assertThat(softwareModuleManagement.get(softwareModuleIncomplete.getId()).isComplete()).isTrue();
final DistributionSet distributionSetComplete = distributionSetManagement.get(distributionSetIncomplete.getId());
assertThat(distributionSetComplete.isComplete()).isTrue();
assertThatNoException().isThrownBy(() -> distributionSetManagement.lock(distributionSetComplete));
assertThat(softwareModuleManagement.get(softwareModuleIncomplete.getId()).isComplete()).isTrue();
}
/**
* Get the Rollouts count by status statistics for a specific Distribution Set
*/

View File

@@ -108,18 +108,6 @@ class DistributionSetTypeManagementTest extends AbstractRepositoryManagementTest
assertThat(distributionSetTypeManagement.count()).isEqualTo(existing);
}
/**
* Verifies that when no SoftwareModules are assigned to a Distribution then the DistributionSet is not complete.
*/
@Test
void incompleteIfDistributionSetHasNoSoftwareModulesAssigned() {
final JpaDistributionSetType jpaDistributionSetType = (JpaDistributionSetType) distributionSetTypeManagement
.create(Create.builder().key("newType").name("new Type").build());
final DistributionSet distributionSet = testdataFactory.createDistributionSet(
"DistributionOne", "3.1.2", jpaDistributionSetType, new ArrayList<>());
assertThat(jpaDistributionSetType.checkComplete(distributionSet)).isFalse();
}
/**
* Verifies that the quota for software module types per distribution set type is enforced as expected.
*/

View File

@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository.jpa.management;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.ByteArrayInputStream;
@@ -25,9 +26,12 @@ import jakarta.validation.ConstraintViolationException;
import org.eclipse.hawkbit.artifact.exception.ArtifactBinaryNotFoundException;
import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
import org.eclipse.hawkbit.repository.SoftwareModuleManagement.Create;
import org.eclipse.hawkbit.repository.SoftwareModuleManagement.Update;
import org.eclipse.hawkbit.repository.SoftwareModuleTypeManagement;
import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent;
import org.eclipse.hawkbit.repository.exception.IncompleteSoftwareModuleException;
import org.eclipse.hawkbit.repository.exception.LockedException;
import org.eclipse.hawkbit.repository.jpa.RandomGeneratedInputStream;
import org.eclipse.hawkbit.repository.model.Artifact;
@@ -419,6 +423,30 @@ class SoftwareModuleManagementTest
verifyThrownExceptionBy(() -> softwareModuleManagement.update(Update.builder().id(NOT_EXIST_IDL).build()), "SoftwareModule");
}
/**
* Verifies that when no SoftwareModules are assigned to a Distribution then the DistributionSet is not complete.
*/
@Test
void incompleteIfSoftwareModule() {
final SoftwareModuleType softwareModuleType = softwareModuleTypeManagement
.create(SoftwareModuleTypeManagement.Create.builder().key("newType").name("new Type").minArtifacts(1).build());
final SoftwareModule softwareModuleIncomplete = softwareModuleManagement
.create(SoftwareModuleManagement.Create.builder().name("ds").version("1.0.0").type(softwareModuleType).build());
assertThat(softwareModuleIncomplete.isComplete()).isFalse();
assertThatExceptionOfType(IncompleteSoftwareModuleException.class)
.isThrownBy(() -> softwareModuleManagement.lock(softwareModuleIncomplete));
// add artifact - so it should become complete
artifactManagement.create(new ArtifactUpload(
new ByteArrayInputStream(randomBytes(10)), null, 10, null,
softwareModuleIncomplete.getId(), "file1", false));
final SoftwareModule softwareModuleComplete = softwareModuleManagement.get(softwareModuleIncomplete.getId());
assertThat(softwareModuleComplete.isComplete()).isTrue();
assertThatNoException().isThrownBy(() -> softwareModuleManagement.lock(softwareModuleComplete));
assertThat(softwareModuleManagement.get(softwareModuleIncomplete.getId()).isComplete()).isTrue();
}
private SoftwareModule createSoftwareModuleWithArtifacts(
final SoftwareModuleType type, final String name, final String version, final int numberArtifacts) {
final long countSoftwareModule = softwareModuleRepository.count();