* 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:
@@ -32,7 +32,6 @@ public enum DistributionSetFields implements QueryField {
|
|||||||
LASTMODIFIEDAT("lastModifiedAt"),
|
LASTMODIFIEDAT("lastModifiedAt"),
|
||||||
LASTMODIFIEDBY("lastModifiedBy"),
|
LASTMODIFIEDBY("lastModifiedBy"),
|
||||||
VERSION("version"),
|
VERSION("version"),
|
||||||
COMPLETE("complete"),
|
|
||||||
MODULE("modules", SoftwareModuleFields.ID.getJpaEntityFieldName(), SoftwareModuleFields.NAME.getJpaEntityFieldName()),
|
MODULE("modules", SoftwareModuleFields.ID.getJpaEntityFieldName(), SoftwareModuleFields.NAME.getJpaEntityFieldName()),
|
||||||
TAG("tags", "name"),
|
TAG("tags", "name"),
|
||||||
METADATA("metadata"),
|
METADATA("metadata"),
|
||||||
|
|||||||
@@ -147,11 +147,6 @@ public class MgmtDistributionSet extends MgmtNamedEntity {
|
|||||||
example = "OS (FW) mandatory, runtime (FW) and app (SW) optional")
|
example = "OS (FW) mandatory, runtime (FW) and app (SW) optional")
|
||||||
private String typeName;
|
private String typeName;
|
||||||
|
|
||||||
@Schema(description = """
|
|
||||||
True of the distribution set software module setup is complete as defined by the
|
|
||||||
distribution set type""", example = "true")
|
|
||||||
private Boolean complete;
|
|
||||||
|
|
||||||
@Schema(description = "If the distribution set is locked", example = "true")
|
@Schema(description = "If the distribution set is locked", example = "true")
|
||||||
private boolean locked;
|
private boolean locked;
|
||||||
|
|
||||||
@@ -167,4 +162,8 @@ public class MgmtDistributionSet extends MgmtNamedEntity {
|
|||||||
private boolean requiredMigrationStep;
|
private boolean requiredMigrationStep;
|
||||||
|
|
||||||
private List<MgmtSoftwareModule> modules = new ArrayList<>();
|
private List<MgmtSoftwareModule> modules = new ArrayList<>();
|
||||||
|
|
||||||
|
@Schema(description = "True of the distribution set software module setup is complete as defined by the distribution set type",
|
||||||
|
example = "true")
|
||||||
|
private Boolean complete;
|
||||||
}
|
}
|
||||||
@@ -93,4 +93,7 @@ public class MgmtSoftwareModule extends MgmtNamedEntity {
|
|||||||
|
|
||||||
@Schema(description = "If the software module is deleted", example = "false")
|
@Schema(description = "If the software module is deleted", example = "false")
|
||||||
private boolean deleted;
|
private boolean deleted;
|
||||||
|
|
||||||
|
@Schema(description = "True of the software module has sufficient number of artifacts", example = "true")
|
||||||
|
private Boolean complete;
|
||||||
}
|
}
|
||||||
@@ -40,6 +40,7 @@ import org.eclipse.hawkbit.mgmt.json.model.MgmtTypeEntity;
|
|||||||
"description" : "Updated description.",
|
"description" : "Updated description.",
|
||||||
"key" : "application",
|
"key" : "application",
|
||||||
"maxAssignments" : 2147483647,
|
"maxAssignments" : 2147483647,
|
||||||
|
"minArtifacts": 1,
|
||||||
"deleted" : false,
|
"deleted" : false,
|
||||||
"_links" : {
|
"_links" : {
|
||||||
"self" : {
|
"self" : {
|
||||||
@@ -54,7 +55,9 @@ public class MgmtSoftwareModuleType extends MgmtTypeEntity {
|
|||||||
@Schema(description = "The technical identifier of the entity", example = "83")
|
@Schema(description = "The technical identifier of the entity", example = "83")
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
@Schema(description = "Software modules of that type can be assigned at this maximum number " +
|
@Schema(description = "The minimum number of artifacts a software module if this type should have in order to be completed", example = "1")
|
||||||
"(e.g. operating system only once)", example = "1")
|
private int minArtifacts;
|
||||||
|
|
||||||
|
@Schema(description = "Software modules of this type can be assigned at this maximum number (e.g. operating system only once)", example = "1")
|
||||||
private int maxAssignments;
|
private int maxAssignments;
|
||||||
}
|
}
|
||||||
@@ -33,6 +33,9 @@ public class MgmtSoftwareModuleTypeRequestBodyPost extends MgmtSoftwareModuleTyp
|
|||||||
@Schema(example = "Example key")
|
@Schema(example = "Example key")
|
||||||
private String key;
|
private String key;
|
||||||
|
|
||||||
|
@Schema(description = "The minimum number of artifacts a software module if this type should have in order to be completed", example = "1")
|
||||||
|
private int minArtifacts;
|
||||||
|
|
||||||
@JsonProperty(required = true)
|
@JsonProperty(required = true)
|
||||||
@Schema(example = "1")
|
@Schema(example = "1")
|
||||||
private int maxAssignments;
|
private int maxAssignments;
|
||||||
|
|||||||
@@ -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.MgmtSoftwareModuleMapper;
|
||||||
import org.eclipse.hawkbit.mgmt.rest.resource.mapper.MgmtTargetFilterQueryMapper;
|
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.mapper.MgmtTargetMapper;
|
||||||
|
import org.eclipse.hawkbit.mgmt.rest.resource.util.LogUtility;
|
||||||
import org.eclipse.hawkbit.mgmt.rest.resource.util.PagingUtility;
|
import org.eclipse.hawkbit.mgmt.rest.resource.util.PagingUtility;
|
||||||
import org.eclipse.hawkbit.repository.DeploymentManagement;
|
import org.eclipse.hawkbit.repository.DeploymentManagement;
|
||||||
import org.eclipse.hawkbit.repository.DistributionSetInvalidationManagement;
|
import org.eclipse.hawkbit.repository.DistributionSetInvalidationManagement;
|
||||||
@@ -116,6 +117,9 @@ public class MgmtDistributionSetResource implements MgmtDistributionSetRestApi {
|
|||||||
@Override
|
@Override
|
||||||
public ResponseEntity<PagedList<MgmtDistributionSet>> getDistributionSets(
|
public ResponseEntity<PagedList<MgmtDistributionSet>> getDistributionSets(
|
||||||
final String rsqlParam, final int pagingOffsetParam, final int pagingLimitParam, final String sortParam) {
|
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 Pageable pageable = PagingUtility.toPageable(pagingOffsetParam, pagingLimitParam, sanitizeDistributionSetSortParam(sortParam));
|
||||||
final Page<? extends DistributionSet> findDsPage;
|
final Page<? extends DistributionSet> findDsPage;
|
||||||
if (rsqlParam != null) {
|
if (rsqlParam != null) {
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import java.util.HashSet;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
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.mgmt.rest.api.MgmtRestConstants;
|
||||||
import org.eclipse.hawkbit.repository.DistributionSetManagement;
|
import org.eclipse.hawkbit.repository.DistributionSetManagement;
|
||||||
import org.eclipse.hawkbit.repository.DistributionSetTagManagement;
|
import org.eclipse.hawkbit.repository.DistributionSetTagManagement;
|
||||||
import org.eclipse.hawkbit.repository.DistributionSetTypeManagement;
|
|
||||||
import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
|
import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
|
||||||
import org.eclipse.hawkbit.repository.SystemManagement;
|
|
||||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||||
import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult;
|
import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult;
|
||||||
@@ -60,7 +57,7 @@ public class MgmtDistributionSetMapper {
|
|||||||
public List<DistributionSetManagement.Create> fromRequest(
|
public List<DistributionSetManagement.Create> fromRequest(
|
||||||
final Collection<MgmtDistributionSetRequestBodyPost> sets,
|
final Collection<MgmtDistributionSetRequestBodyPost> sets,
|
||||||
final String defaultDsKey, final Map<String, DistributionSetType> dsTypeKeyToDsType) {
|
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<>();
|
final Set<Long> modules = new HashSet<>();
|
||||||
if (dsRest.getOs() != null) {
|
if (dsRest.getOs() != null) {
|
||||||
modules.add(dsRest.getOs().getId());
|
modules.add(dsRest.getOs().getId());
|
||||||
@@ -195,7 +192,7 @@ public class MgmtDistributionSetMapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Set<? extends SoftwareModule> findSoftwareModuleWithExceptionIfNotFound(final Set<Long> softwareModuleIds) {
|
private Set<? extends SoftwareModule> findSoftwareModuleWithExceptionIfNotFound(final Set<Long> softwareModuleIds) {
|
||||||
if (CollectionUtils.isEmpty(softwareModuleIds)) {
|
if (CollectionUtils.isEmpty(softwareModuleIds)) {
|
||||||
return Collections.emptySet();
|
return Collections.emptySet();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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.MgmtDownloadArtifactResource;
|
||||||
import org.eclipse.hawkbit.mgmt.rest.resource.MgmtSoftwareModuleResource;
|
import org.eclipse.hawkbit.mgmt.rest.resource.MgmtSoftwareModuleResource;
|
||||||
import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
|
import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
|
||||||
|
import org.eclipse.hawkbit.repository.SoftwareModuleManagement.Create;
|
||||||
import org.eclipse.hawkbit.repository.SoftwareModuleTypeManagement;
|
import org.eclipse.hawkbit.repository.SoftwareModuleTypeManagement;
|
||||||
import org.eclipse.hawkbit.repository.SystemManagement;
|
import org.eclipse.hawkbit.repository.SystemManagement;
|
||||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||||
@@ -63,17 +64,15 @@ public final class MgmtSoftwareModuleMapper {
|
|||||||
if (metadata == null) {
|
if (metadata == null) {
|
||||||
return Collections.emptyMap();
|
return Collections.emptyMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
return metadata.stream().collect(Collectors.toMap(
|
return metadata.stream().collect(Collectors.toMap(
|
||||||
MgmtSoftwareModuleMetadata::getKey,
|
MgmtSoftwareModuleMetadata::getKey,
|
||||||
metadataRest -> new MetadataValueCreate(metadataRest.getValue(), metadataRest.isTargetVisible())));
|
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) {
|
if (smsRest == null) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
return smsRest.stream().map(this::fromRequest).toList();
|
return smsRest.stream().map(this::fromRequest).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,7 +80,6 @@ public final class MgmtSoftwareModuleMapper {
|
|||||||
if (softwareModules == null) {
|
if (softwareModules == null) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ResponseList<>(softwareModules.stream().map(MgmtSoftwareModuleMapper::toResponse).toList());
|
return new ResponseList<>(softwareModules.stream().map(MgmtSoftwareModuleMapper::toResponse).toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,7 +87,6 @@ public final class MgmtSoftwareModuleMapper {
|
|||||||
if (metadata == null) {
|
if (metadata == null) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
return metadata.entrySet().stream().map(e -> toResponseSwMetadata(e.getKey(), e.getValue())).toList();
|
return metadata.entrySet().stream().map(e -> toResponseSwMetadata(e.getKey(), e.getValue())).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,7 +102,6 @@ public final class MgmtSoftwareModuleMapper {
|
|||||||
if (softwareModule == null) {
|
if (softwareModule == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final MgmtSoftwareModule response = new MgmtSoftwareModule();
|
final MgmtSoftwareModule response = new MgmtSoftwareModule();
|
||||||
MgmtRestModelMapper.mapNamedToNamed(response, softwareModule);
|
MgmtRestModelMapper.mapNamedToNamed(response, softwareModule);
|
||||||
response.setId(softwareModule.getId());
|
response.setId(softwareModule.getId());
|
||||||
@@ -116,10 +112,8 @@ public final class MgmtSoftwareModuleMapper {
|
|||||||
response.setLocked(softwareModule.isLocked());
|
response.setLocked(softwareModule.isLocked());
|
||||||
response.setDeleted(softwareModule.isDeleted());
|
response.setDeleted(softwareModule.isDeleted());
|
||||||
response.setEncrypted(softwareModule.isEncrypted());
|
response.setEncrypted(softwareModule.isEncrypted());
|
||||||
|
response.setComplete(softwareModule.isComplete());
|
||||||
response.add(linkTo(methodOn(MgmtSoftwareModuleRestApi.class).getSoftwareModule(response.getId()))
|
response.add(linkTo(methodOn(MgmtSoftwareModuleRestApi.class).getSoftwareModule(response.getId())).withSelfRel().expand());
|
||||||
.withSelfRel().expand());
|
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,16 +130,11 @@ public final class MgmtSoftwareModuleMapper {
|
|||||||
final MgmtArtifact artifactRest = new MgmtArtifact();
|
final MgmtArtifact artifactRest = new MgmtArtifact();
|
||||||
artifactRest.setId(artifact.getId());
|
artifactRest.setId(artifact.getId());
|
||||||
artifactRest.setSize(artifact.getSize());
|
artifactRest.setSize(artifact.getSize());
|
||||||
artifactRest.setHashes(
|
artifactRest.setHashes(new MgmtArtifactHash(artifact.getSha1Hash(), artifact.getMd5Hash(), artifact.getSha256Hash()));
|
||||||
new MgmtArtifactHash(artifact.getSha1Hash(), artifact.getMd5Hash(), artifact.getSha256Hash()));
|
|
||||||
|
|
||||||
artifactRest.setProvidedFilename(artifact.getFilename());
|
artifactRest.setProvidedFilename(artifact.getFilename());
|
||||||
|
|
||||||
MgmtRestModelMapper.mapBaseToBase(artifactRest, artifact);
|
MgmtRestModelMapper.mapBaseToBase(artifactRest, artifact);
|
||||||
|
|
||||||
artifactRest.add(linkTo(methodOn(MgmtSoftwareModuleRestApi.class)
|
artifactRest.add(linkTo(methodOn(MgmtSoftwareModuleRestApi.class)
|
||||||
.getArtifact(artifact.getSoftwareModule().getId(), artifact.getId(), null)).withSelfRel().expand());
|
.getArtifact(artifact.getSoftwareModule().getId(), artifact.getId(), null)).withSelfRel().expand());
|
||||||
|
|
||||||
return artifactRest;
|
return artifactRest;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,8 +154,8 @@ public final class MgmtSoftwareModuleMapper {
|
|||||||
urls.forEach(entry -> response.add(Link.of(entry.ref()).withRel(entry.rel()).expand()));
|
urls.forEach(entry -> response.add(Link.of(entry.ref()).withRel(entry.rel()).expand()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private SoftwareModuleManagement.Create fromRequest(final MgmtSoftwareModuleRequestBodyPost smsRest) {
|
private Create fromRequest(final MgmtSoftwareModuleRequestBodyPost smsRest) {
|
||||||
return SoftwareModuleManagement.Create.builder()
|
return Create.builder()
|
||||||
.type(getSoftwareModuleTypeFromKeyString(smsRest.getType()))
|
.type(getSoftwareModuleTypeFromKeyString(smsRest.getType()))
|
||||||
.name(smsRest.getName()).version(smsRest.getVersion()).description(smsRest.getDescription()).vendor(smsRest.getVendor())
|
.name(smsRest.getName()).version(smsRest.getVersion()).description(smsRest.getDescription()).vendor(smsRest.getVendor())
|
||||||
.encrypted(smsRest.isEncrypted())
|
.encrypted(smsRest.isEncrypted())
|
||||||
@@ -177,7 +166,6 @@ public final class MgmtSoftwareModuleMapper {
|
|||||||
if (type == null) {
|
if (type == null) {
|
||||||
throw new ValidationException("type cannot be null");
|
throw new ValidationException("type cannot be null");
|
||||||
}
|
}
|
||||||
|
|
||||||
return softwareModuleTypeManagement.findByKey(type.trim())
|
return softwareModuleTypeManagement.findByKey(type.trim())
|
||||||
.orElseThrow(() -> new EntityNotFoundException(SoftwareModuleType.class, type.trim()));
|
.orElseThrow(() -> new EntityNotFoundException(SoftwareModuleType.class, type.trim()));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
|
|||||||
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;
|
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import lombok.AccessLevel;
|
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.MgmtSoftwareModuleType;
|
||||||
import org.eclipse.hawkbit.mgmt.json.model.softwaremoduletype.MgmtSoftwareModuleTypeRequestBodyPost;
|
import org.eclipse.hawkbit.mgmt.json.model.softwaremoduletype.MgmtSoftwareModuleTypeRequestBodyPost;
|
||||||
import org.eclipse.hawkbit.mgmt.rest.api.MgmtSoftwareModuleTypeRestApi;
|
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.repository.model.SoftwareModuleType;
|
||||||
import org.eclipse.hawkbit.rest.json.model.ResponseList;
|
import org.eclipse.hawkbit.rest.json.model.ResponseList;
|
||||||
|
|
||||||
@@ -31,41 +30,37 @@ import org.eclipse.hawkbit.rest.json.model.ResponseList;
|
|||||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
public final class MgmtSoftwareModuleTypeMapper {
|
public final class MgmtSoftwareModuleTypeMapper {
|
||||||
|
|
||||||
public static List<SoftwareModuleTypeManagement.Create> smFromRequest(
|
public static List<Create> smFromRequest(final Collection<MgmtSoftwareModuleTypeRequestBodyPost> request) {
|
||||||
final Collection<MgmtSoftwareModuleTypeRequestBodyPost> smTypesRest) {
|
if (request == null) {
|
||||||
if (smTypesRest == null) {
|
return List.of();
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
}
|
||||||
|
return request.stream().map(MgmtSoftwareModuleTypeMapper::fromRequest).toList();
|
||||||
return smTypesRest.stream().map(MgmtSoftwareModuleTypeMapper::fromRequest).toList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<MgmtSoftwareModuleType> toTypesResponse(final Collection<? extends SoftwareModuleType> types) {
|
public static List<MgmtSoftwareModuleType> toTypesResponse(final Collection<? extends SoftwareModuleType> types) {
|
||||||
if (types == null) {
|
if (types == null) {
|
||||||
return Collections.emptyList();
|
return List.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ResponseList<>(types.stream().map(MgmtSoftwareModuleTypeMapper::toResponse).toList());
|
return new ResponseList<>(types.stream().map(MgmtSoftwareModuleTypeMapper::toResponse).toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static MgmtSoftwareModuleType toResponse(final SoftwareModuleType type) {
|
public static MgmtSoftwareModuleType toResponse(final SoftwareModuleType type) {
|
||||||
final MgmtSoftwareModuleType result = new MgmtSoftwareModuleType();
|
final MgmtSoftwareModuleType result = new MgmtSoftwareModuleType();
|
||||||
|
|
||||||
MgmtRestModelMapper.mapTypeToType(result, type);
|
MgmtRestModelMapper.mapTypeToType(result, type);
|
||||||
result.setMaxAssignments(type.getMaxAssignments());
|
result
|
||||||
result.setId(type.getId());
|
.setId(type.getId())
|
||||||
|
.setMinArtifacts(type.getMinArtifacts())
|
||||||
result.add(linkTo(methodOn(MgmtSoftwareModuleTypeRestApi.class).getSoftwareModuleType(result.getId()))
|
.setMaxAssignments(type.getMaxAssignments());
|
||||||
.withSelfRel().expand());
|
result.add(linkTo(methodOn(MgmtSoftwareModuleTypeRestApi.class).getSoftwareModuleType(result.getId())).withSelfRel().expand());
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static SoftwareModuleTypeManagement.Create fromRequest(final MgmtSoftwareModuleTypeRequestBodyPost smsRest) {
|
private static Create fromRequest(final MgmtSoftwareModuleTypeRequestBodyPost request) {
|
||||||
return SoftwareModuleTypeManagement.Create.builder()
|
return Create.builder()
|
||||||
.key(smsRest.getKey()).name(smsRest.getName())
|
.key(request.getKey()).name(request.getName())
|
||||||
.description(smsRest.getDescription()).colour(smsRest.getColour())
|
.description(request.getDescription()).colour(request.getColour())
|
||||||
.maxAssignments(smsRest.getMaxAssignments())
|
.minArtifacts(request.getMinArtifacts())
|
||||||
|
.maxAssignments(request.getMaxAssignments())
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -63,6 +63,8 @@ public interface SoftwareModuleTypeManagement<T extends SoftwareModuleType>
|
|||||||
|
|
||||||
@Builder.Default
|
@Builder.Default
|
||||||
private int maxAssignments = 1;
|
private int maxAssignments = 1;
|
||||||
|
@Builder.Default
|
||||||
|
private int minArtifacts;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuperBuilder
|
@SuperBuilder
|
||||||
|
|||||||
@@ -28,16 +28,7 @@ public class DistributionSetUpdatedEvent extends RemoteEntityEvent<DistributionS
|
|||||||
@Serial
|
@Serial
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
private boolean complete;
|
public DistributionSetUpdatedEvent(final DistributionSet distributionSet) {
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor.
|
|
||||||
*
|
|
||||||
* @param distributionSet Distribution Set
|
|
||||||
* @param complete <code>true</code> if {@link DistributionSet} is after the update {@link DistributionSet#isComplete()}
|
|
||||||
*/
|
|
||||||
public DistributionSetUpdatedEvent(final DistributionSet distributionSet, final boolean complete) {
|
|
||||||
super(distributionSet);
|
super(distributionSet);
|
||||||
this.complete = complete;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Contributors to the Eclipse Foundation
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made
|
||||||
|
* available under the terms of the Eclipse Public License 2.0
|
||||||
|
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.eclipse.hawkbit.repository.exception;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.ToString;
|
||||||
|
import org.eclipse.hawkbit.exception.AbstractServerRtException;
|
||||||
|
import org.eclipse.hawkbit.exception.SpServerError;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown if a software module is being locked while incomplete (i.e. not enough artifacts are assigned).
|
||||||
|
*/
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@ToString(callSuper = true)
|
||||||
|
public final class IncompleteSoftwareModuleException extends AbstractServerRtException {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public IncompleteSoftwareModuleException() {
|
||||||
|
super(SpServerError.SP_DS_INCOMPLETE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IncompleteSoftwareModuleException(final Throwable cause) {
|
||||||
|
super(SpServerError.SP_DS_INCOMPLETE, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IncompleteSoftwareModuleException(final String message) {
|
||||||
|
super(SpServerError.SP_DS_INCOMPLETE, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -39,12 +39,6 @@ public interface DistributionSet extends NamedVersionedEntity {
|
|||||||
*/
|
*/
|
||||||
Set<SoftwareModule> getModules();
|
Set<SoftwareModule> getModules();
|
||||||
|
|
||||||
/**
|
|
||||||
* @return <code>true</code> if all defined {@link DistributionSetType#getMandatoryModuleTypes()} of {@link #getType()} are present in
|
|
||||||
* this {@link DistributionSet}.
|
|
||||||
*/
|
|
||||||
boolean isComplete();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return <code>true</code> if this {@link DistributionSet} is locked. If so it's 'functional' properties (e.g. software modules) could not
|
* @return <code>true</code> if this {@link DistributionSet} is locked. If so it's 'functional' properties (e.g. software modules) could not
|
||||||
* be modified anymore.
|
* be modified anymore.
|
||||||
@@ -66,4 +60,12 @@ public interface DistributionSet extends NamedVersionedEntity {
|
|||||||
* active and not automatically canceled if overridden by a newer update.
|
* active and not automatically canceled if overridden by a newer update.
|
||||||
*/
|
*/
|
||||||
boolean isRequiredMigrationStep();
|
boolean isRequiredMigrationStep();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns if the distribution set could be assumed as completed. I.e. all requirements (e.g. mandatory software module types) are satisfied.
|
||||||
|
*
|
||||||
|
* @return <code>true</code> if all defined {@link DistributionSetType#getMandatoryModuleTypes()} of {@link #getType()} are present in
|
||||||
|
* this {@link DistributionSet}.
|
||||||
|
*/
|
||||||
|
boolean isComplete();
|
||||||
}
|
}
|
||||||
@@ -29,12 +29,6 @@ public interface DistributionSetType extends Type {
|
|||||||
*/
|
*/
|
||||||
Set<SoftwareModuleType> getOptionalModuleTypes();
|
Set<SoftwareModuleType> getOptionalModuleTypes();
|
||||||
|
|
||||||
/**
|
|
||||||
* @param distributionSet to check for completeness
|
|
||||||
* @return <code>true</code> if the all mandatory software module types are in the system.
|
|
||||||
*/
|
|
||||||
boolean checkComplete(DistributionSet distributionSet);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the given {@link SoftwareModuleType} is in this {@link DistributionSetType}.
|
* Checks if the given {@link SoftwareModuleType} is in this {@link DistributionSetType}.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -70,6 +70,13 @@ public interface SoftwareModule extends NamedVersionedEntity {
|
|||||||
*/
|
*/
|
||||||
boolean isDeleted();
|
boolean isDeleted();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns if the software module could be assumed as completed. I.e. all requirements (e.g. min artifacts) are satisfied.
|
||||||
|
*
|
||||||
|
* @return <code>true</code> if artifacts are more or equals to {@link SoftwareModuleType#getMinArtifacts()} if the software module type.
|
||||||
|
*/
|
||||||
|
boolean isComplete();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param artifactId to look for
|
* @param artifactId to look for
|
||||||
* @return found {@link Artifact}
|
* @return found {@link Artifact}
|
||||||
|
|||||||
@@ -16,7 +16,12 @@ package org.eclipse.hawkbit.repository.model;
|
|||||||
public interface SoftwareModuleType extends Type {
|
public interface SoftwareModuleType extends Type {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return maximum assignments of an {@link SoftwareModule} of this type to a {@link DistributionSet}.
|
* @return thet minimum number of artifacts a {@link SoftwareModule} of this type should have in order to be completed.
|
||||||
|
*/
|
||||||
|
int getMinArtifacts();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the maximum assignments of a {@link SoftwareModule} of this type to a {@link DistributionSet}.
|
||||||
*/
|
*/
|
||||||
int getMaxAssignments();
|
int getMaxAssignments();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
ALTER TABLE sp_software_module_type ADD COLUMN min_artifacts integer default 0 NOT NULL;
|
||||||
|
DROP INDEX sp_idx_distribution_set_01;
|
||||||
|
CREATE INDEX sp_idx_distribution_set_01 ON sp_distribution_set (tenant, deleted);
|
||||||
|
ALTER TABLE sp_distribution_set DROP COLUMN complete;
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
ALTER TABLE sp_software_module_type ADD COLUMN min_artifacts integer default 0 NOT NULL;
|
||||||
|
ALTER TABLE sp_distribution_set DROP INDEX sp_idx_distribution_set_01;
|
||||||
|
CREATE INDEX sp_idx_distribution_set_01 ON sp_distribution_set (tenant, deleted);
|
||||||
|
ALTER TABLE sp_distribution_set DROP COLUMN complete;
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
ALTER TABLE sp_software_module_type ADD COLUMN min_artifacts integer default 0 NOT NULL;
|
||||||
|
DROP INDEX sp_idx_distribution_set_01;
|
||||||
|
CREATE INDEX sp_idx_distribution_set_01 ON sp_distribution_set USING BTREE (tenant, deleted);
|
||||||
|
ALTER TABLE sp_distribution_set DROP COLUMN complete;
|
||||||
@@ -154,6 +154,10 @@ public class QLSupport implements ApplicationListener<ContextRefreshedEvent> {
|
|||||||
this.virtualPropertyResolver = virtualPropertyResolver;
|
this.virtualPropertyResolver = virtualPropertyResolver;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public <A extends Enum<A> & QueryField> Node parse(final String query, final Class<A> queryFieldType) {
|
||||||
|
return parser.parse(ignoreCase || caseInsensitiveDB ? query.toLowerCase() : query, queryFieldType);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds a JPA {@link Specification} which corresponds with the given RSQL query. The specification can be used to filter for JPA entities
|
* Builds a JPA {@link Specification} which corresponds with the given RSQL query. The specification can be used to filter for JPA entities
|
||||||
* with the given RSQL query.
|
* with the given RSQL query.
|
||||||
@@ -168,16 +172,20 @@ public class QLSupport implements ApplicationListener<ContextRefreshedEvent> {
|
|||||||
public <A extends Enum<A> & QueryField, T> Specification<T> buildSpec(final String query, final Class<A> queryFieldType) {
|
public <A extends Enum<A> & QueryField, T> Specification<T> buildSpec(final String query, final Class<A> queryFieldType) {
|
||||||
if (specBuilder == SpecBuilder.G3) {
|
if (specBuilder == SpecBuilder.G3) {
|
||||||
return new SpecificationBuilder<T>(ignoreCase && !caseInsensitiveDB , database)
|
return new SpecificationBuilder<T>(ignoreCase && !caseInsensitiveDB , database)
|
||||||
.specification(parseAndTransform(query, queryFieldType, ignoreCase || caseInsensitiveDB));
|
.specification(transform(parse(query, queryFieldType), queryFieldType));
|
||||||
} else {
|
} else {
|
||||||
return new SpecificationBuilderLegacy<A, T>(queryFieldType, virtualPropertyResolver, database).specification(query);
|
return new SpecificationBuilderLegacy<A, T>(queryFieldType, virtualPropertyResolver, database).specification(query);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public <A extends Enum<A> & QueryField, T> Specification<T> buildSpec(final Node query, final Class<A> queryFieldType) {
|
||||||
|
return new SpecificationBuilder<T>(ignoreCase && !caseInsensitiveDB , database)
|
||||||
|
.specification(transform(query, queryFieldType));
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("java:S1117") // it is again ignoreCase
|
@SuppressWarnings("java:S1117") // it is again ignoreCase
|
||||||
public <A extends Enum<A> & QueryField> EntityMatcher entityMatcher(final String query, final Class<A> queryFieldType) {
|
public <A extends Enum<A> & QueryField> EntityMatcher entityMatcher(final String query, final Class<A> queryFieldType) {
|
||||||
final boolean ignoreCase = this.ignoreCase || caseInsensitiveDB; // sync with DB and case sensitivity requirements
|
return EntityMatcher.of(transform(parse(query, queryFieldType), queryFieldType), ignoreCase || caseInsensitiveDB);
|
||||||
return EntityMatcher.of(parseAndTransform(query, queryFieldType, ignoreCase), ignoreCase);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -195,9 +203,7 @@ public class QLSupport implements ApplicationListener<ContextRefreshedEvent> {
|
|||||||
buildSpec(query, queryFieldType).toPredicate(criteriaQuery.from((Class) jpaType), criteriaQuery, criteriaBuilder);
|
buildSpec(query, queryFieldType).toPredicate(criteriaQuery.from((Class) jpaType), criteriaQuery, criteriaBuilder);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <A extends Enum<A> & QueryField> Node parseAndTransform(
|
private <A extends Enum<A> & QueryField> Node transform(Node node, final Class<A> queryFieldType) {
|
||||||
final String query, final Class<A> queryFieldType, final boolean ignoreCase) {
|
|
||||||
Node node = parser.parse(ignoreCase ? query.toLowerCase() : query, queryFieldType);
|
|
||||||
for (final NodeTransformer transformer : nodeTransformers) {
|
for (final NodeTransformer transformer : nodeTransformers) {
|
||||||
node = transformer.transform(node, queryFieldType);
|
node = transformer.transform(node, queryFieldType);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,18 +13,21 @@ import static org.eclipse.hawkbit.repository.jpa.configuration.Constants.TX_RT_D
|
|||||||
import static org.eclipse.hawkbit.repository.jpa.configuration.Constants.TX_RT_MAX;
|
import static org.eclipse.hawkbit.repository.jpa.configuration.Constants.TX_RT_MAX;
|
||||||
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.IMPLICIT_LOCK_ENABLED;
|
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.IMPLICIT_LOCK_ENABLED;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import jakarta.persistence.EntityManager;
|
import jakarta.persistence.EntityManager;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.eclipse.hawkbit.repository.DistributionSetFields;
|
import org.eclipse.hawkbit.repository.DistributionSetFields;
|
||||||
import org.eclipse.hawkbit.repository.DistributionSetManagement;
|
import org.eclipse.hawkbit.repository.DistributionSetManagement;
|
||||||
import org.eclipse.hawkbit.repository.DistributionSetTagManagement;
|
import org.eclipse.hawkbit.repository.DistributionSetTagManagement;
|
||||||
@@ -36,17 +39,20 @@ import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
|||||||
import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException;
|
import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException;
|
||||||
import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException;
|
import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException;
|
||||||
import org.eclipse.hawkbit.repository.exception.InvalidDistributionSetException;
|
import org.eclipse.hawkbit.repository.exception.InvalidDistributionSetException;
|
||||||
|
import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
|
||||||
import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper;
|
import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper;
|
||||||
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
|
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
|
||||||
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetTag;
|
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetTag;
|
||||||
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet_;
|
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet_;
|
||||||
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule;
|
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule;
|
||||||
|
import org.eclipse.hawkbit.repository.jpa.ql.Node;
|
||||||
|
import org.eclipse.hawkbit.repository.jpa.ql.QLSupport;
|
||||||
import org.eclipse.hawkbit.repository.jpa.repository.ActionRepository;
|
import org.eclipse.hawkbit.repository.jpa.repository.ActionRepository;
|
||||||
import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetRepository;
|
import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetRepository;
|
||||||
import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetTagRepository;
|
import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetTagRepository;
|
||||||
import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleRepository;
|
import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleRepository;
|
||||||
import org.eclipse.hawkbit.repository.jpa.repository.TargetFilterQueryRepository;
|
import org.eclipse.hawkbit.repository.jpa.repository.TargetFilterQueryRepository;
|
||||||
import org.eclipse.hawkbit.repository.jpa.ql.QLSupport;
|
import org.eclipse.hawkbit.repository.jpa.rsql.RsqlParser;
|
||||||
import org.eclipse.hawkbit.repository.jpa.specifications.DistributionSetSpecification;
|
import org.eclipse.hawkbit.repository.jpa.specifications.DistributionSetSpecification;
|
||||||
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
|
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
|
||||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||||
@@ -58,7 +64,9 @@ import org.eclipse.hawkbit.utils.TenantConfigHelper;
|
|||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
|
||||||
import org.springframework.dao.ConcurrencyFailureException;
|
import org.springframework.dao.ConcurrencyFailureException;
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.PageImpl;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.data.jpa.domain.Specification;
|
||||||
import org.springframework.retry.annotation.Backoff;
|
import org.springframework.retry.annotation.Backoff;
|
||||||
import org.springframework.retry.annotation.Retryable;
|
import org.springframework.retry.annotation.Retryable;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@@ -67,8 +75,10 @@ import org.springframework.util.ObjectUtils;
|
|||||||
|
|
||||||
@Service
|
@Service
|
||||||
@ConditionalOnBooleanProperty(prefix = "hawkbit.jpa", name = { "enabled", "distribution-set-management" }, matchIfMissing = true)
|
@ConditionalOnBooleanProperty(prefix = "hawkbit.jpa", name = { "enabled", "distribution-set-management" }, matchIfMissing = true)
|
||||||
|
@Slf4j
|
||||||
public class JpaDistributionSetManagement
|
public class JpaDistributionSetManagement
|
||||||
extends AbstractJpaRepositoryWithMetadataManagement<JpaDistributionSet, DistributionSetManagement.Create, DistributionSetManagement.Update, DistributionSetRepository, DistributionSetFields, String, String>
|
extends
|
||||||
|
AbstractJpaRepositoryWithMetadataManagement<JpaDistributionSet, DistributionSetManagement.Create, DistributionSetManagement.Update, DistributionSetRepository, DistributionSetFields, String, String>
|
||||||
implements DistributionSetManagement<JpaDistributionSet> {
|
implements DistributionSetManagement<JpaDistributionSet> {
|
||||||
|
|
||||||
private final DistributionSetTagManagement<JpaDistributionSetTag> distributionSetTagManagement;
|
private final DistributionSetTagManagement<JpaDistributionSetTag> distributionSetTagManagement;
|
||||||
@@ -104,6 +114,46 @@ public class JpaDistributionSetManagement
|
|||||||
this.repositoryProperties = repositoryProperties;
|
this.repositoryProperties = repositoryProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final String COMPLETE = "complete";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("java:S3776") // java:S3776 - just too complex
|
||||||
|
public Page<JpaDistributionSet> findByRsql(final String rsql, final Pageable pageable) {
|
||||||
|
if (rsql != null && rsql.toLowerCase().contains(COMPLETE)) {
|
||||||
|
// limited support for 'complete' - could be removed in future
|
||||||
|
final Node node = RsqlParser.parse(rsql);
|
||||||
|
final Specification<JpaDistributionSet> notDeleted = (root, query, cb) -> cb.equal(root.get(DELETED), false);
|
||||||
|
final List<Specification<JpaDistributionSet>> specList = new ArrayList<>();
|
||||||
|
specList.add(notDeleted);
|
||||||
|
final AtomicReference<Node.Comparison> completedComparison = new AtomicReference<>();
|
||||||
|
if (node instanceof Node.Comparison comparison && COMPLETE.equalsIgnoreCase(comparison.getKey())) {
|
||||||
|
// all not deleted, won't add anything to spec
|
||||||
|
completedComparison.set(comparison);
|
||||||
|
} else if (node instanceof Node.Logical logical && logical.getOp() == Node.Logical.Operator.AND) {
|
||||||
|
final List<Node> sanitizedChildren = new ArrayList<>();
|
||||||
|
logical.getChildren().forEach(child -> {
|
||||||
|
if (child instanceof Node.Comparison comparison && COMPLETE.equalsIgnoreCase(comparison.getKey())) {
|
||||||
|
if (completedComparison.get() != null) {
|
||||||
|
throw new RSQLParameterSyntaxException("Multiple 'complete' comparisons are not supported");
|
||||||
|
}
|
||||||
|
completedComparison.set(comparison);
|
||||||
|
} else {
|
||||||
|
sanitizedChildren.add(child);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
specList.add(QLSupport.getInstance()
|
||||||
|
.buildSpec(new Node.Logical(Node.Logical.Operator.AND, sanitizedChildren), DistributionSetFields.class));
|
||||||
|
}
|
||||||
|
if (completedComparison.get() != null) { // really a comparison
|
||||||
|
log.warn("Usage of 'complete' in RSQL is deprecated and will be removed in future: {}", node);
|
||||||
|
final boolean completed = completeComparison(completedComparison);
|
||||||
|
return filter(JpaManagementHelper.findAllWithCountBySpec(jpaRepository, specList, pageable), completed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.findByRsql(rsql, pageable);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JpaDistributionSet update(final Update update) {
|
public JpaDistributionSet update(final Update update) {
|
||||||
final JpaDistributionSet updated = super.update(update);
|
final JpaDistributionSet updated = super.update(update);
|
||||||
@@ -199,7 +249,7 @@ public class JpaDistributionSetManagement
|
|||||||
if (distributionSet.isLocked()) {
|
if (distributionSet.isLocked()) {
|
||||||
return jpaDistributionSet;
|
return jpaDistributionSet;
|
||||||
} else {
|
} else {
|
||||||
if (!jpaDistributionSet.isComplete()) {
|
if (!distributionSet.isComplete()) {
|
||||||
throw new IncompleteDistributionSetException("Could not be locked while incomplete!");
|
throw new IncompleteDistributionSetException("Could not be locked while incomplete!");
|
||||||
}
|
}
|
||||||
lockSoftwareModules(jpaDistributionSet);
|
lockSoftwareModules(jpaDistributionSet);
|
||||||
@@ -298,16 +348,13 @@ public class JpaDistributionSetManagement
|
|||||||
@Override
|
@Override
|
||||||
public JpaDistributionSet getValidAndComplete(final long id) {
|
public JpaDistributionSet getValidAndComplete(final long id) {
|
||||||
final JpaDistributionSet distributionSet = getValid0(id);
|
final JpaDistributionSet distributionSet = getValid0(id);
|
||||||
|
|
||||||
if (!distributionSet.isComplete()) {
|
if (!distributionSet.isComplete()) {
|
||||||
throw new IncompleteDistributionSetException(
|
throw new IncompleteDistributionSetException(
|
||||||
"Distribution set of type " + distributionSet.getType().getKey() + " is incomplete: " + distributionSet.getId());
|
"Distribution set of type " + distributionSet.getType().getKey() + " is incomplete: " + distributionSet.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (distributionSet.isDeleted()) {
|
if (distributionSet.isDeleted()) {
|
||||||
throw new DeletedException(DistributionSet.class, id);
|
throw new DeletedException(DistributionSet.class, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
return distributionSet;
|
return distributionSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -357,6 +404,24 @@ public class JpaDistributionSetManagement
|
|||||||
QuotaHelper.assertAssignmentQuota(requested, maxMetaData, String.class, DistributionSet.class);
|
QuotaHelper.assertAssignmentQuota(requested, maxMetaData, String.class, DistributionSet.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean completeComparison(final AtomicReference<Node.Comparison> completeComparison) {
|
||||||
|
final Node.Comparison comparison = completeComparison.get();
|
||||||
|
if (comparison.getOp() == Node.Comparison.Operator.EQ) {
|
||||||
|
return Boolean.parseBoolean(String.valueOf(comparison.getValue()));
|
||||||
|
} else if (comparison.getOp() == Node.Comparison.Operator.NE) {
|
||||||
|
return !Boolean.parseBoolean(String.valueOf(comparison.getValue()));
|
||||||
|
} else {
|
||||||
|
throw new RSQLParameterSyntaxException("Unsupported operator for 'complete': " + comparison.getOp());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Page<JpaDistributionSet> filter(final Page<JpaDistributionSet> page, final boolean completed) {
|
||||||
|
final List<JpaDistributionSet> filtered = page.getContent().stream()
|
||||||
|
.filter(ds -> ds.isComplete() == completed)
|
||||||
|
.toList();
|
||||||
|
return new PageImpl<>(filtered, page.getPageable(), page.getTotalElements());
|
||||||
|
}
|
||||||
|
|
||||||
private static Collection<Long> notFound(final Collection<Long> distributionSetIds, final List<JpaDistributionSet> foundDistributionSets) {
|
private static Collection<Long> notFound(final Collection<Long> distributionSetIds, final List<JpaDistributionSet> foundDistributionSets) {
|
||||||
final Map<Long, JpaDistributionSet> foundDistributionSetMap = foundDistributionSets.stream()
|
final Map<Long, JpaDistributionSet> foundDistributionSetMap = foundDistributionSets.stream()
|
||||||
.collect(Collectors.toMap(JpaDistributionSet::getId, Function.identity()));
|
.collect(Collectors.toMap(JpaDistributionSet::getId, Function.identity()));
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import org.eclipse.hawkbit.repository.QuotaManagement;
|
|||||||
import org.eclipse.hawkbit.repository.SoftwareModuleFields;
|
import org.eclipse.hawkbit.repository.SoftwareModuleFields;
|
||||||
import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
|
import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
|
||||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||||
|
import org.eclipse.hawkbit.repository.exception.IncompleteSoftwareModuleException;
|
||||||
import org.eclipse.hawkbit.repository.exception.LockedException;
|
import org.eclipse.hawkbit.repository.exception.LockedException;
|
||||||
import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper;
|
import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper;
|
||||||
import org.eclipse.hawkbit.repository.jpa.acm.AccessController;
|
import org.eclipse.hawkbit.repository.jpa.acm.AccessController;
|
||||||
@@ -142,6 +143,9 @@ public class JpaSoftwareModuleManagement extends
|
|||||||
if (jpaSoftwareModule.isLocked()) {
|
if (jpaSoftwareModule.isLocked()) {
|
||||||
return jpaSoftwareModule;
|
return jpaSoftwareModule;
|
||||||
} else {
|
} else {
|
||||||
|
if (!softwareModule.isComplete()) {
|
||||||
|
throw new IncompleteSoftwareModuleException("Could not be locked while incomplete!");
|
||||||
|
}
|
||||||
jpaSoftwareModule.lock();
|
jpaSoftwareModule.lock();
|
||||||
return jpaRepository.save(jpaSoftwareModule);
|
return jpaRepository.save(jpaSoftwareModule);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import java.io.Serial;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -45,13 +46,13 @@ import org.eclipse.hawkbit.repository.event.remote.DistributionSetDeletedEvent;
|
|||||||
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetCreatedEvent;
|
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetCreatedEvent;
|
||||||
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetUpdatedEvent;
|
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetUpdatedEvent;
|
||||||
import org.eclipse.hawkbit.repository.exception.DistributionSetTypeUndefinedException;
|
import org.eclipse.hawkbit.repository.exception.DistributionSetTypeUndefinedException;
|
||||||
import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException;
|
|
||||||
import org.eclipse.hawkbit.repository.exception.LockedException;
|
import org.eclipse.hawkbit.repository.exception.LockedException;
|
||||||
import org.eclipse.hawkbit.repository.exception.UnsupportedSoftwareModuleForThisDistributionSetException;
|
import org.eclipse.hawkbit.repository.exception.UnsupportedSoftwareModuleForThisDistributionSetException;
|
||||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||||
import org.eclipse.hawkbit.repository.model.DistributionSetTag;
|
import org.eclipse.hawkbit.repository.model.DistributionSetTag;
|
||||||
import org.eclipse.hawkbit.repository.model.DistributionSetType;
|
import org.eclipse.hawkbit.repository.model.DistributionSetType;
|
||||||
import org.eclipse.hawkbit.repository.model.SoftwareModule;
|
import org.eclipse.hawkbit.repository.model.SoftwareModule;
|
||||||
|
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
|
||||||
import org.springframework.context.ApplicationEvent;
|
import org.springframework.context.ApplicationEvent;
|
||||||
import org.springframework.core.annotation.Order;
|
import org.springframework.core.annotation.Order;
|
||||||
|
|
||||||
@@ -122,9 +123,6 @@ public class JpaDistributionSet
|
|||||||
@Column(name = "meta_value", length = DistributionSet.METADATA_MAX_VALUE_SIZE)
|
@Column(name = "meta_value", length = DistributionSet.METADATA_MAX_VALUE_SIZE)
|
||||||
private Map<String, String> metadata = new HashMap<>();
|
private Map<String, String> metadata = new HashMap<>();
|
||||||
|
|
||||||
@Column(name = "complete")
|
|
||||||
private boolean complete;
|
|
||||||
|
|
||||||
@Setter
|
@Setter
|
||||||
@Column(name = "locked")
|
@Column(name = "locked")
|
||||||
private boolean locked;
|
private boolean locked;
|
||||||
@@ -161,6 +159,20 @@ public class JpaDistributionSet
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isComplete() {
|
||||||
|
return Optional.ofNullable(type).map(dsType -> {
|
||||||
|
if (getModules().stream().anyMatch(module -> !module.isComplete())) {
|
||||||
|
return false; // incomplete module
|
||||||
|
}
|
||||||
|
final List<SoftwareModuleType> smTypes = getModules().stream()
|
||||||
|
.map(SoftwareModule::getType)
|
||||||
|
.distinct()
|
||||||
|
.toList();
|
||||||
|
return !smTypes.isEmpty() && new HashSet<>(smTypes).containsAll(dsType.getMandatoryModuleTypes());
|
||||||
|
}).orElse(true);
|
||||||
|
}
|
||||||
|
|
||||||
public void addModule(final SoftwareModule softwareModule) {
|
public void addModule(final SoftwareModule softwareModule) {
|
||||||
if (isLocked()) {
|
if (isLocked()) {
|
||||||
throw new LockedException(JpaDistributionSet.class, getId(), "ADD_SOFTWARE_MODULE");
|
throw new LockedException(JpaDistributionSet.class, getId(), "ADD_SOFTWARE_MODULE");
|
||||||
@@ -180,9 +192,7 @@ public class JpaDistributionSet
|
|||||||
.findAny().ifPresent(modules::remove);
|
.findAny().ifPresent(modules::remove);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modules.add(softwareModule)) {
|
modules.add(softwareModule);
|
||||||
complete = type.checkComplete(this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeModule(final SoftwareModule softwareModule) {
|
public void removeModule(final SoftwareModule softwareModule) {
|
||||||
@@ -190,8 +200,8 @@ public class JpaDistributionSet
|
|||||||
throw new LockedException(JpaDistributionSet.class, getId(), "REMOVE_SOFTWARE_MODULE");
|
throw new LockedException(JpaDistributionSet.class, getId(), "REMOVE_SOFTWARE_MODULE");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modules != null && modules.removeIf(m -> m.getId().equals(softwareModule.getId()))) {
|
if (modules != null) {
|
||||||
complete = type.checkComplete(this);
|
modules.removeIf(m -> m.getId().equals(softwareModule.getId()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,20 +209,17 @@ public class JpaDistributionSet
|
|||||||
return Collections.unmodifiableSet(tags);
|
return Collections.unmodifiableSet(tags);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean addTag(final DistributionSetTag tag) {
|
public void addTag(final DistributionSetTag tag) {
|
||||||
if (tags == null) {
|
if (tags == null) {
|
||||||
tags = new HashSet<>();
|
tags = new HashSet<>();
|
||||||
}
|
}
|
||||||
|
tags.add(tag);
|
||||||
return tags.add(tag);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean removeTag(final DistributionSetTag tag) {
|
public void removeTag(final DistributionSetTag tag) {
|
||||||
if (tags == null) {
|
if (tags != null) {
|
||||||
return false;
|
tags.remove(tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
return tags.remove(tag);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void invalidate() {
|
public void invalidate() {
|
||||||
@@ -226,7 +233,7 @@ public class JpaDistributionSet
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void fireUpdateEvent() {
|
public void fireUpdateEvent() {
|
||||||
publishEventWithEventPublisher(new DistributionSetUpdatedEvent(this, complete));
|
publishEventWithEventPublisher(new DistributionSetUpdatedEvent(this));
|
||||||
|
|
||||||
if (deleted) {
|
if (deleted) {
|
||||||
publishEventWithEventPublisher(new DistributionSetDeletedEvent(getTenant(), getId(), getClass()));
|
publishEventWithEventPublisher(new DistributionSetDeletedEvent(getTenant(), getId(), getClass()));
|
||||||
|
|||||||
@@ -112,15 +112,6 @@ public class JpaDistributionSetType extends AbstractJpaTypeEntity implements Dis
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean checkComplete(final DistributionSet distributionSet) {
|
|
||||||
final List<SoftwareModuleType> smTypes = distributionSet.getModules().stream()
|
|
||||||
.map(SoftwareModule::getType)
|
|
||||||
.distinct()
|
|
||||||
.toList();
|
|
||||||
return !smTypes.isEmpty() && new HashSet<>(smTypes).containsAll(getMandatoryModuleTypes());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "DistributionSetType [key=" + getKey() + ", isDeleted()=" + isDeleted() + ", getId()=" + getId() + "]";
|
return "DistributionSetType [key=" + getKey() + ", isDeleted()=" + isDeleted() + ", getId()=" + getId() + "]";
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import jakarta.persistence.CascadeType;
|
import jakarta.persistence.CascadeType;
|
||||||
import jakarta.persistence.CollectionTable;
|
import jakarta.persistence.CollectionTable;
|
||||||
@@ -146,6 +147,17 @@ public class JpaSoftwareModule
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isComplete() {
|
||||||
|
return Optional.ofNullable(type).map(smType -> {
|
||||||
|
if (smType.getMinArtifacts() > 0) {
|
||||||
|
return getArtifacts().size() >= smType.getMinArtifacts();
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}).orElse(true);
|
||||||
|
}
|
||||||
|
|
||||||
public void removeArtifact(final Artifact artifact) {
|
public void removeArtifact(final Artifact artifact) {
|
||||||
if (isLocked()) {
|
if (isLocked()) {
|
||||||
throw new LockedException(JpaSoftwareModule.class, getId(), "REMOVE_ARTIFACT");
|
throw new LockedException(JpaSoftwareModule.class, getId(), "REMOVE_ARTIFACT");
|
||||||
|
|||||||
@@ -45,6 +45,11 @@ public class JpaSoftwareModuleType extends AbstractJpaTypeEntity implements Soft
|
|||||||
@Serial
|
@Serial
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Setter(value = lombok.AccessLevel.PRIVATE) // used via reflection
|
||||||
|
@Column(name = "min_artifacts", nullable = false)
|
||||||
|
@Min(0)
|
||||||
|
private int minArtifacts;
|
||||||
|
|
||||||
@Setter(value = lombok.AccessLevel.PRIVATE) // used via reflection
|
@Setter(value = lombok.AccessLevel.PRIVATE) // used via reflection
|
||||||
@Column(name = "max_ds_assignments", nullable = false)
|
@Column(name = "max_ds_assignments", nullable = false)
|
||||||
@Min(1)
|
@Min(1)
|
||||||
|
|||||||
@@ -57,26 +57,6 @@ public final class DistributionSetSpecification {
|
|||||||
return (dsRoot, query, cb) -> cb.equal(dsRoot.get(JpaDistributionSet_.deleted), isDeleted);
|
return (dsRoot, query, cb) -> cb.equal(dsRoot.get(JpaDistributionSet_.deleted), isDeleted);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@link Specification} for retrieving {@link DistributionSet}s by its COMPLETED attribute.
|
|
||||||
*
|
|
||||||
* @param isCompleted TRUE/FALSE are compared to the attribute COMPLETED. If NULL the attribute is ignored
|
|
||||||
* @return the {@link DistributionSet} {@link Specification}
|
|
||||||
*/
|
|
||||||
public static Specification<JpaDistributionSet> isCompleted(final Boolean isCompleted) {
|
|
||||||
return (dsRoot, query, cb) -> cb.equal(dsRoot.get(JpaDistributionSet_.complete), isCompleted);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@link Specification} for retrieving {@link DistributionSet}s by its VALID attribute.
|
|
||||||
*
|
|
||||||
* @param isValid TRUE/FALSE are compared to the attribute VALID. If NULL the attribute is ignored
|
|
||||||
* @return the {@link DistributionSet} {@link Specification}
|
|
||||||
*/
|
|
||||||
public static Specification<JpaDistributionSet> isValid(final Boolean isValid) {
|
|
||||||
return (dsRoot, query, cb) -> cb.equal(dsRoot.get(JpaDistributionSet_.valid), isValid);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link Specification} for retrieving {@link DistributionSet} with given {@link DistributionSet#getId()}s.
|
* {@link Specification} for retrieving {@link DistributionSet} with given {@link DistributionSet#getId()}s.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class DistributionSetUpdatedEventTest extends AbstractRemoteEntityEventTest<Dist
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected RemoteEntityEvent<?> createRemoteEvent(final DistributionSet baseEntity, final Class<? extends RemoteEntityEvent<?>> eventType) {
|
protected RemoteEntityEvent<?> createRemoteEvent(final DistributionSet baseEntity, final Class<? extends RemoteEntityEvent<?>> eventType) {
|
||||||
return new DistributionSetUpdatedEvent(baseEntity, true);
|
return new DistributionSetUpdatedEvent(baseEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1357,8 +1357,7 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
List<? extends DistributionSet> allFoundDS = distributionSetManagement.findAll(PAGE).getContent();
|
List<? extends DistributionSet> allFoundDS = distributionSetManagement.findAll(PAGE).getContent();
|
||||||
assertThat(allFoundDS).as("no ds should be founded").isEmpty();
|
assertThat(allFoundDS).as("no ds should be founded").isEmpty();
|
||||||
|
|
||||||
assertThat(distributionSetRepository.findAll(JpaManagementHelper.combineWithAnd(
|
assertThat(distributionSetRepository.findAll(DistributionSetSpecification.isDeleted(true), PAGE).getContent())
|
||||||
List.of(DistributionSetSpecification.isDeleted(true), DistributionSetSpecification.isCompleted(true))), PAGE).getContent())
|
|
||||||
.as("wrong size of founded ds").hasSize(noOfDistributionSets);
|
.as("wrong size of founded ds").hasSize(noOfDistributionSets);
|
||||||
|
|
||||||
IntStream.range(0, deploymentResult.getDistributionSets().size()).forEach(i -> testdataFactory.sendUpdateActionStatusToTargets(
|
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
|
// verify that the result is the same, even though distributionSet dsA has been installed
|
||||||
// successfully and no activeAction is referring to created distribution sets
|
// successfully and no activeAction is referring to created distribution sets
|
||||||
allFoundDS = distributionSetManagement.findAll(pageRequest).getContent();
|
allFoundDS = distributionSetManagement.findAll(pageRequest).getContent();
|
||||||
assertThat(allFoundDS).as("no ds should be founded").isEmpty();
|
assertThat(allFoundDS).as("no ds should be found").isEmpty();
|
||||||
assertThat(distributionSetRepository.findAll(JpaManagementHelper.combineWithAnd(
|
assertThat(distributionSetRepository.findAll(DistributionSetSpecification.isDeleted(true), PAGE).getContent())
|
||||||
List.of(DistributionSetSpecification.isDeleted(true), DistributionSetSpecification.isCompleted(true))), PAGE).getContent())
|
|
||||||
.as("wrong size of founded ds").hasSize(noOfDistributionSets);
|
.as("wrong size of founded ds").hasSize(noOfDistributionSets);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,14 +12,15 @@ package org.eclipse.hawkbit.repository.jpa.management;
|
|||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
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 static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import jakarta.validation.ConstraintViolationException;
|
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.Identifiable;
|
||||||
import org.eclipse.hawkbit.repository.RepositoryProperties;
|
import org.eclipse.hawkbit.repository.RepositoryProperties;
|
||||||
import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
|
import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
|
||||||
|
import org.eclipse.hawkbit.repository.SoftwareModuleTypeManagement;
|
||||||
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
|
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
|
||||||
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetCreatedEvent;
|
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetCreatedEvent;
|
||||||
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetTagCreatedEvent;
|
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.jpa.model.JpaDistributionSet;
|
||||||
import org.eclipse.hawkbit.repository.model.Action.Status;
|
import org.eclipse.hawkbit.repository.model.Action.Status;
|
||||||
import org.eclipse.hawkbit.repository.model.ActionCancellationType;
|
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.DistributionSet;
|
||||||
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation;
|
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation;
|
||||||
import org.eclipse.hawkbit.repository.model.DistributionSetTag;
|
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.NamedEntity;
|
||||||
import org.eclipse.hawkbit.repository.model.NamedVersionedEntity;
|
import org.eclipse.hawkbit.repository.model.NamedVersionedEntity;
|
||||||
import org.eclipse.hawkbit.repository.model.Rollout;
|
import org.eclipse.hawkbit.repository.model.Rollout;
|
||||||
import org.eclipse.hawkbit.repository.model.SoftwareModule;
|
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.Statistic;
|
||||||
import org.eclipse.hawkbit.repository.model.Target;
|
import org.eclipse.hawkbit.repository.model.Target;
|
||||||
import org.eclipse.hawkbit.repository.test.matcher.Expect;
|
import org.eclipse.hawkbit.repository.test.matcher.Expect;
|
||||||
@@ -688,6 +693,95 @@ class DistributionSetManagementTest extends AbstractRepositoryManagementWithMeta
|
|||||||
.isThrownBy(() -> distributionSetManagement.getValidAndComplete(distributionSetId));
|
.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
|
* Get the Rollouts count by status statistics for a specific Distribution Set
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -108,18 +108,6 @@ class DistributionSetTypeManagementTest extends AbstractRepositoryManagementTest
|
|||||||
assertThat(distributionSetTypeManagement.count()).isEqualTo(existing);
|
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.
|
* Verifies that the quota for software module types per distribution set type is enforced as expected.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
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 static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
@@ -25,9 +26,12 @@ import jakarta.validation.ConstraintViolationException;
|
|||||||
|
|
||||||
import org.eclipse.hawkbit.artifact.exception.ArtifactBinaryNotFoundException;
|
import org.eclipse.hawkbit.artifact.exception.ArtifactBinaryNotFoundException;
|
||||||
import org.eclipse.hawkbit.repository.DistributionSetManagement;
|
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.Create;
|
||||||
import org.eclipse.hawkbit.repository.SoftwareModuleManagement.Update;
|
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.event.remote.entity.SoftwareModuleCreatedEvent;
|
||||||
|
import org.eclipse.hawkbit.repository.exception.IncompleteSoftwareModuleException;
|
||||||
import org.eclipse.hawkbit.repository.exception.LockedException;
|
import org.eclipse.hawkbit.repository.exception.LockedException;
|
||||||
import org.eclipse.hawkbit.repository.jpa.RandomGeneratedInputStream;
|
import org.eclipse.hawkbit.repository.jpa.RandomGeneratedInputStream;
|
||||||
import org.eclipse.hawkbit.repository.model.Artifact;
|
import org.eclipse.hawkbit.repository.model.Artifact;
|
||||||
@@ -419,6 +423,30 @@ class SoftwareModuleManagementTest
|
|||||||
verifyThrownExceptionBy(() -> softwareModuleManagement.update(Update.builder().id(NOT_EXIST_IDL).build()), "SoftwareModule");
|
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(
|
private SoftwareModule createSoftwareModuleWithArtifacts(
|
||||||
final SoftwareModuleType type, final String name, final String version, final int numberArtifacts) {
|
final SoftwareModuleType type, final String name, final String version, final int numberArtifacts) {
|
||||||
final long countSoftwareModule = softwareModuleRepository.count();
|
final long countSoftwareModule = softwareModuleRepository.count();
|
||||||
|
|||||||
Reference in New Issue
Block a user