[#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

@@ -45,6 +45,7 @@ import org.eclipse.hawkbit.mgmt.rest.resource.mapper.MgmtRestModelMapper;
import org.eclipse.hawkbit.mgmt.rest.resource.mapper.MgmtSoftwareModuleMapper;
import org.eclipse.hawkbit.mgmt.rest.resource.mapper.MgmtTargetFilterQueryMapper;
import org.eclipse.hawkbit.mgmt.rest.resource.mapper.MgmtTargetMapper;
import org.eclipse.hawkbit.mgmt.rest.resource.util.LogUtility;
import org.eclipse.hawkbit.mgmt.rest.resource.util.PagingUtility;
import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.DistributionSetInvalidationManagement;
@@ -116,6 +117,9 @@ public class MgmtDistributionSetResource implements MgmtDistributionSetRestApi {
@Override
public ResponseEntity<PagedList<MgmtDistributionSet>> getDistributionSets(
final String rsqlParam, final int pagingOffsetParam, final int pagingLimitParam, final String sortParam) {
if (rsqlParam != null && rsqlParam.toLowerCase().contains("complete")) {
LogUtility.logDeprecated("Usage of MgmtDistributionSetResource.getActions with 'complete': 'complete' distribution set search field may be removed.");
}
final Pageable pageable = PagingUtility.toPageable(pagingOffsetParam, pagingLimitParam, sanitizeDistributionSetSortParam(sortParam));
final Page<? extends DistributionSet> findDsPage;
if (rsqlParam != null) {

View File

@@ -18,7 +18,6 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
@@ -33,9 +32,7 @@ import org.eclipse.hawkbit.mgmt.rest.api.MgmtDistributionSetTypeRestApi;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.DistributionSetTagManagement;
import org.eclipse.hawkbit.repository.DistributionSetTypeManagement;
import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult;
@@ -60,7 +57,7 @@ public class MgmtDistributionSetMapper {
public List<DistributionSetManagement.Create> fromRequest(
final Collection<MgmtDistributionSetRequestBodyPost> sets,
final String defaultDsKey, final Map<String, DistributionSetType> dsTypeKeyToDsType) {
return sets.stream().<DistributionSetManagement.Create>map(dsRest -> {
return sets.stream().<DistributionSetManagement.Create> map(dsRest -> {
final Set<Long> modules = new HashSet<>();
if (dsRest.getOs() != null) {
modules.add(dsRest.getOs().getId());
@@ -195,7 +192,7 @@ public class MgmtDistributionSetMapper {
}
private Set<? extends SoftwareModule> findSoftwareModuleWithExceptionIfNotFound(final Set<Long> softwareModuleIds) {
if (CollectionUtils.isEmpty(softwareModuleIds)) {
if (CollectionUtils.isEmpty(softwareModuleIds)) {
return Collections.emptySet();
}

View File

@@ -34,6 +34,7 @@ import org.eclipse.hawkbit.mgmt.rest.api.MgmtSoftwareModuleTypeRestApi;
import org.eclipse.hawkbit.mgmt.rest.resource.MgmtDownloadArtifactResource;
import org.eclipse.hawkbit.mgmt.rest.resource.MgmtSoftwareModuleResource;
import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
import org.eclipse.hawkbit.repository.SoftwareModuleManagement.Create;
import org.eclipse.hawkbit.repository.SoftwareModuleTypeManagement;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
@@ -63,17 +64,15 @@ public final class MgmtSoftwareModuleMapper {
if (metadata == null) {
return Collections.emptyMap();
}
return metadata.stream().collect(Collectors.toMap(
MgmtSoftwareModuleMetadata::getKey,
metadataRest -> new MetadataValueCreate(metadataRest.getValue(), metadataRest.isTargetVisible())));
}
public List<SoftwareModuleManagement.Create> smFromRequest(final Collection<MgmtSoftwareModuleRequestBodyPost> smsRest) {
public List<Create> smFromRequest(final Collection<MgmtSoftwareModuleRequestBodyPost> smsRest) {
if (smsRest == null) {
return Collections.emptyList();
}
return smsRest.stream().map(this::fromRequest).toList();
}
@@ -81,7 +80,6 @@ public final class MgmtSoftwareModuleMapper {
if (softwareModules == null) {
return Collections.emptyList();
}
return new ResponseList<>(softwareModules.stream().map(MgmtSoftwareModuleMapper::toResponse).toList());
}
@@ -89,7 +87,6 @@ public final class MgmtSoftwareModuleMapper {
if (metadata == null) {
return Collections.emptyList();
}
return metadata.entrySet().stream().map(e -> toResponseSwMetadata(e.getKey(), e.getValue())).toList();
}
@@ -105,7 +102,6 @@ public final class MgmtSoftwareModuleMapper {
if (softwareModule == null) {
return null;
}
final MgmtSoftwareModule response = new MgmtSoftwareModule();
MgmtRestModelMapper.mapNamedToNamed(response, softwareModule);
response.setId(softwareModule.getId());
@@ -116,10 +112,8 @@ public final class MgmtSoftwareModuleMapper {
response.setLocked(softwareModule.isLocked());
response.setDeleted(softwareModule.isDeleted());
response.setEncrypted(softwareModule.isEncrypted());
response.add(linkTo(methodOn(MgmtSoftwareModuleRestApi.class).getSoftwareModule(response.getId()))
.withSelfRel().expand());
response.setComplete(softwareModule.isComplete());
response.add(linkTo(methodOn(MgmtSoftwareModuleRestApi.class).getSoftwareModule(response.getId())).withSelfRel().expand());
return response;
}
@@ -136,16 +130,11 @@ public final class MgmtSoftwareModuleMapper {
final MgmtArtifact artifactRest = new MgmtArtifact();
artifactRest.setId(artifact.getId());
artifactRest.setSize(artifact.getSize());
artifactRest.setHashes(
new MgmtArtifactHash(artifact.getSha1Hash(), artifact.getMd5Hash(), artifact.getSha256Hash()));
artifactRest.setHashes(new MgmtArtifactHash(artifact.getSha1Hash(), artifact.getMd5Hash(), artifact.getSha256Hash()));
artifactRest.setProvidedFilename(artifact.getFilename());
MgmtRestModelMapper.mapBaseToBase(artifactRest, artifact);
artifactRest.add(linkTo(methodOn(MgmtSoftwareModuleRestApi.class)
.getArtifact(artifact.getSoftwareModule().getId(), artifact.getId(), null)).withSelfRel().expand());
return artifactRest;
}
@@ -165,8 +154,8 @@ public final class MgmtSoftwareModuleMapper {
urls.forEach(entry -> response.add(Link.of(entry.ref()).withRel(entry.rel()).expand()));
}
private SoftwareModuleManagement.Create fromRequest(final MgmtSoftwareModuleRequestBodyPost smsRest) {
return SoftwareModuleManagement.Create.builder()
private Create fromRequest(final MgmtSoftwareModuleRequestBodyPost smsRest) {
return Create.builder()
.type(getSoftwareModuleTypeFromKeyString(smsRest.getType()))
.name(smsRest.getName()).version(smsRest.getVersion()).description(smsRest.getDescription()).vendor(smsRest.getVendor())
.encrypted(smsRest.isEncrypted())
@@ -177,7 +166,6 @@ public final class MgmtSoftwareModuleMapper {
if (type == null) {
throw new ValidationException("type cannot be null");
}
return softwareModuleTypeManagement.findByKey(type.trim())
.orElseThrow(() -> new EntityNotFoundException(SoftwareModuleType.class, type.trim()));
}

View File

@@ -13,7 +13,6 @@ import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import lombok.AccessLevel;
@@ -21,7 +20,7 @@ import lombok.NoArgsConstructor;
import org.eclipse.hawkbit.mgmt.json.model.softwaremoduletype.MgmtSoftwareModuleType;
import org.eclipse.hawkbit.mgmt.json.model.softwaremoduletype.MgmtSoftwareModuleTypeRequestBodyPost;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtSoftwareModuleTypeRestApi;
import org.eclipse.hawkbit.repository.SoftwareModuleTypeManagement;
import org.eclipse.hawkbit.repository.SoftwareModuleTypeManagement.Create;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.rest.json.model.ResponseList;
@@ -31,41 +30,37 @@ import org.eclipse.hawkbit.rest.json.model.ResponseList;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class MgmtSoftwareModuleTypeMapper {
public static List<SoftwareModuleTypeManagement.Create> smFromRequest(
final Collection<MgmtSoftwareModuleTypeRequestBodyPost> smTypesRest) {
if (smTypesRest == null) {
return Collections.emptyList();
public static List<Create> smFromRequest(final Collection<MgmtSoftwareModuleTypeRequestBodyPost> request) {
if (request == null) {
return List.of();
}
return smTypesRest.stream().map(MgmtSoftwareModuleTypeMapper::fromRequest).toList();
return request.stream().map(MgmtSoftwareModuleTypeMapper::fromRequest).toList();
}
public static List<MgmtSoftwareModuleType> toTypesResponse(final Collection<? extends SoftwareModuleType> types) {
if (types == null) {
return Collections.emptyList();
return List.of();
}
return new ResponseList<>(types.stream().map(MgmtSoftwareModuleTypeMapper::toResponse).toList());
}
public static MgmtSoftwareModuleType toResponse(final SoftwareModuleType type) {
final MgmtSoftwareModuleType result = new MgmtSoftwareModuleType();
MgmtRestModelMapper.mapTypeToType(result, type);
result.setMaxAssignments(type.getMaxAssignments());
result.setId(type.getId());
result.add(linkTo(methodOn(MgmtSoftwareModuleTypeRestApi.class).getSoftwareModuleType(result.getId()))
.withSelfRel().expand());
result
.setId(type.getId())
.setMinArtifacts(type.getMinArtifacts())
.setMaxAssignments(type.getMaxAssignments());
result.add(linkTo(methodOn(MgmtSoftwareModuleTypeRestApi.class).getSoftwareModuleType(result.getId())).withSelfRel().expand());
return result;
}
private static SoftwareModuleTypeManagement.Create fromRequest(final MgmtSoftwareModuleTypeRequestBodyPost smsRest) {
return SoftwareModuleTypeManagement.Create.builder()
.key(smsRest.getKey()).name(smsRest.getName())
.description(smsRest.getDescription()).colour(smsRest.getColour())
.maxAssignments(smsRest.getMaxAssignments())
private static Create fromRequest(final MgmtSoftwareModuleTypeRequestBodyPost request) {
return Create.builder()
.key(request.getKey()).name(request.getName())
.description(request.getDescription()).colour(request.getColour())
.minArtifacts(request.getMinArtifacts())
.maxAssignments(request.getMaxAssignments())
.build();
}
}