[#1651] Add SoftwareModule and DistributionSet unlock (REST) (#1677)

Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2024-03-08 11:28:24 +02:00
committed by GitHub
parent 4d104873de
commit ce9918ce00
10 changed files with 57 additions and 65 deletions

View File

@@ -10,12 +10,20 @@
package org.eclipse.hawkbit.repository.builder; package org.eclipse.hawkbit.repository.builder;
import jakarta.annotation.Nullable; import jakarta.annotation.Nullable;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import lombok.experimental.Accessors;
import java.util.Optional; import java.util.Optional;
/** /**
* Update implementation. * Update implementation.
*/ */
@Data
@EqualsAndHashCode(callSuper = true)
@ToString
@Accessors(fluent = true)
public class GenericDistributionSetUpdate extends AbstractDistributionSetUpdateCreate<DistributionSetUpdate> public class GenericDistributionSetUpdate extends AbstractDistributionSetUpdateCreate<DistributionSetUpdate>
implements DistributionSetUpdate { implements DistributionSetUpdate {
@@ -25,17 +33,4 @@ public class GenericDistributionSetUpdate extends AbstractDistributionSetUpdateC
public GenericDistributionSetUpdate(final Long id) { public GenericDistributionSetUpdate(final Long id) {
super.id = id; super.id = id;
} }
public DistributionSetUpdate locked(@Nullable final Boolean locked) {
if (Boolean.FALSE.equals(locked)) {
this.locked = null;
} else {
this.locked = locked;
}
return this;
}
public Boolean getLocked() {
return locked;
}
} }

View File

@@ -10,12 +10,20 @@
package org.eclipse.hawkbit.repository.builder; package org.eclipse.hawkbit.repository.builder;
import jakarta.annotation.Nullable; import jakarta.annotation.Nullable;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import lombok.experimental.Accessors;
import java.util.Optional; import java.util.Optional;
/** /**
* Update implementation. * Update implementation.
*/ */
@Data
@EqualsAndHashCode(callSuper = true)
@ToString
@Accessors(fluent = true)
public class GenericSoftwareModuleUpdate extends AbstractSoftwareModuleUpdateCreate<SoftwareModuleUpdate> public class GenericSoftwareModuleUpdate extends AbstractSoftwareModuleUpdateCreate<SoftwareModuleUpdate>
implements SoftwareModuleUpdate { implements SoftwareModuleUpdate {
@@ -25,17 +33,4 @@ public class GenericSoftwareModuleUpdate extends AbstractSoftwareModuleUpdateCre
public GenericSoftwareModuleUpdate(final Long id) { public GenericSoftwareModuleUpdate(final Long id) {
super.id = id; super.id = id;
} }
public SoftwareModuleUpdate locked(@Nullable final Boolean locked) {
if (Boolean.FALSE.equals(locked)) {
this.locked = null;
} else {
this.locked = locked;
}
return this;
}
public Boolean getLocked() {
return locked;
}
} }

View File

@@ -286,8 +286,11 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
update.getDescription().ifPresent(set::setDescription); update.getDescription().ifPresent(set::setDescription);
update.getVersion().ifPresent(set::setVersion); update.getVersion().ifPresent(set::setVersion);
if (Boolean.TRUE.equals(update.getLocked()) && !set.isLocked()) { // lock/unlock ONLY if locked flag is present!
if (Boolean.TRUE.equals(update.locked())) {
set.lock(); set.lock();
} else if (Boolean.FALSE.equals(update.locked())) {
set.unlock();
} }
if (update.isRequiredMigrationStep() != null if (update.isRequiredMigrationStep() != null

View File

@@ -142,8 +142,12 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
update.getDescription().ifPresent(module::setDescription); update.getDescription().ifPresent(module::setDescription);
update.getVendor().ifPresent(module::setVendor); update.getVendor().ifPresent(module::setVendor);
if (Boolean.TRUE.equals(update.getLocked()) && !module.isLocked()) {
// lock/unlock ONLY if locked flag is present!
if (Boolean.TRUE.equals(update.locked())) {
module.lock(); module.lock();
} else if (Boolean.FALSE.equals(update.locked())) {
module.unlock();
} }
return softwareModuleRepository.save(module); return softwareModuleRepository.save(module);

View File

@@ -39,8 +39,11 @@ public class MgmtDistributionSetRequestBodyPut {
private String version; private String version;
@JsonProperty @JsonProperty
@Schema(description = "Put it to true only if want to lock the distribution set. Otherwise skip it. " + @Schema(description = """
"Shall not be false!", Should be set only if change of locked state is requested. If put, the distribution set locked flag will be
set to the requested. Note: unlock (i.e. set this property to false) with extreme care!
In general once distribution set is locked it shall not be unlocked. Note that it could have been assigned /
deployed to targets.""",
example = "true") example = "true")
private Boolean locked; private Boolean locked;

View File

@@ -30,8 +30,11 @@ public class MgmtSoftwareModuleRequestBodyPut {
private String vendor; private String vendor;
@JsonProperty @JsonProperty
@Schema(description = "Put it to true only if want to lock the software module. Otherwise skip it. " + @Schema(description = """
"Shall not be false!", Should be set only if change of locked state is requested. If put, the software module locked flag will be
set to the requested. Note: unlock (i.e. set this property to false) with extreme care!
In general once software module is locked it shall not be unlocked. Note that it could have been assigned /
deployed to targets.""",
example = "true") example = "true")
private Boolean locked; private Boolean locked;
} }

View File

@@ -46,44 +46,35 @@ public final class MgmtDistributionSetMapper {
/** /**
* {@link MgmtDistributionSetRequestBodyPost}s to {@link DistributionSet}s. * {@link MgmtDistributionSetRequestBodyPost}s to {@link DistributionSet}s.
* *
* @param sets * @param sets to convert
* to convert
* @return converted list of {@link DistributionSet}s * @return converted list of {@link DistributionSet}s
*/ */
static List<DistributionSetCreate> dsFromRequest(final Collection<MgmtDistributionSetRequestBodyPost> sets, static List<DistributionSetCreate> dsFromRequest(final Collection<MgmtDistributionSetRequestBodyPost> sets,
final EntityFactory entityFactory) { final EntityFactory entityFactory) {
return sets.stream().map(dsRest -> fromRequest(dsRest, entityFactory)).collect(Collectors.toList()); return sets.stream().map(dsRest -> fromRequest(dsRest, entityFactory)).collect(Collectors.toList());
} }
/** /**
* {@link MgmtDistributionSetRequestBodyPost} to {@link DistributionSet}. * {@link MgmtDistributionSetRequestBodyPost} to {@link DistributionSet}.
* *
* @param dsRest * @param dsRest to convert
* to convert
* @return converted {@link DistributionSet} * @return converted {@link DistributionSet}
*/ */
private static DistributionSetCreate fromRequest(final MgmtDistributionSetRequestBodyPost dsRest, private static DistributionSetCreate fromRequest(final MgmtDistributionSetRequestBodyPost dsRest,
final EntityFactory entityFactory) { final EntityFactory entityFactory) {
final List<Long> modules = new ArrayList<>(); final List<Long> modules = new ArrayList<>();
if (dsRest.getOs() != null) { if (dsRest.getOs() != null) {
modules.add(dsRest.getOs().getId()); modules.add(dsRest.getOs().getId());
} }
if (dsRest.getApplication() != null) { if (dsRest.getApplication() != null) {
modules.add(dsRest.getApplication().getId()); modules.add(dsRest.getApplication().getId());
} }
if (dsRest.getRuntime() != null) { if (dsRest.getRuntime() != null) {
modules.add(dsRest.getRuntime().getId()); modules.add(dsRest.getRuntime().getId());
} }
if (dsRest.getModules() != null) { if (dsRest.getModules() != null) {
dsRest.getModules().forEach(module -> modules.add(module.getId())); dsRest.getModules().forEach(module -> modules.add(module.getId()));
} }
return entityFactory.distributionSet().create().name(dsRest.getName()).version(dsRest.getVersion()) return entityFactory.distributionSet().create().name(dsRest.getName()).version(dsRest.getVersion())
.description(dsRest.getDescription()).type(dsRest.getType()).modules(modules) .description(dsRest.getDescription()).type(dsRest.getType()).modules(modules)
.requiredMigrationStep(dsRest.getRequiredMigrationStep()); .requiredMigrationStep(dsRest.getRequiredMigrationStep());
@@ -103,6 +94,7 @@ public final class MgmtDistributionSetMapper {
if (distributionSet == null) { if (distributionSet == null) {
return null; return null;
} }
final MgmtDistributionSet response = new MgmtDistributionSet(); final MgmtDistributionSet response = new MgmtDistributionSet();
MgmtRestModelMapper.mapNamedToNamed(response, distributionSet); MgmtRestModelMapper.mapNamedToNamed(response, distributionSet);
@@ -180,7 +172,6 @@ public final class MgmtDistributionSetMapper {
} }
static List<MgmtMetadata> toResponseDsMetadata(final List<DistributionSetMetadata> metadata) { static List<MgmtMetadata> toResponseDsMetadata(final List<DistributionSetMetadata> metadata) {
final List<MgmtMetadata> mappedList = new ArrayList<>(metadata.size()); final List<MgmtMetadata> mappedList = new ArrayList<>(metadata.size());
for (final DistributionSetMetadata distributionSetMetadata : metadata) { for (final DistributionSetMetadata distributionSetMetadata : metadata) {
mappedList.add(toResponseDsMetadata(distributionSetMetadata)); mappedList.add(toResponseDsMetadata(distributionSetMetadata));

View File

@@ -17,6 +17,8 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.eclipse.hawkbit.api.ApiType; import org.eclipse.hawkbit.api.ApiType;
import org.eclipse.hawkbit.api.ArtifactUrl; import org.eclipse.hawkbit.api.ArtifactUrl;
import org.eclipse.hawkbit.api.ArtifactUrlHandler; import org.eclipse.hawkbit.api.ArtifactUrlHandler;
@@ -42,12 +44,9 @@ import org.springframework.hateoas.Link;
/** /**
* A mapper which maps repository model to RESTful model representation and * A mapper which maps repository model to RESTful model representation and
* back. * back.
*
*/ */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class MgmtSoftwareModuleMapper { public final class MgmtSoftwareModuleMapper {
private MgmtSoftwareModuleMapper() {
// Utility class
}
private static SoftwareModuleCreate fromRequest(final EntityFactory entityFactory, private static SoftwareModuleCreate fromRequest(final EntityFactory entityFactory,
final MgmtSoftwareModuleRequestBodyPost smsRest) { final MgmtSoftwareModuleRequestBodyPost smsRest) {
@@ -157,7 +156,6 @@ public final class MgmtSoftwareModuleMapper {
} }
static void addLinks(final Artifact artifact, final MgmtArtifact response) { static void addLinks(final Artifact artifact, final MgmtArtifact response) {
response.add(linkTo(methodOn(MgmtDownloadArtifactResource.class) response.add(linkTo(methodOn(MgmtDownloadArtifactResource.class)
.downloadArtifact(artifact.getSoftwareModule().getId(), artifact.getId())).withRel("download") .downloadArtifact(artifact.getSoftwareModule().getId(), artifact.getId())).withRel("download")
.expand()); .expand());
@@ -165,7 +163,6 @@ public final class MgmtSoftwareModuleMapper {
static void addLinks(final Artifact artifact, final MgmtArtifact response, static void addLinks(final Artifact artifact, final MgmtArtifact response,
final ArtifactUrlHandler artifactUrlHandler, final SystemManagement systemManagement) { final ArtifactUrlHandler artifactUrlHandler, final SystemManagement systemManagement) {
final List<ArtifactUrl> urls = artifactUrlHandler.getUrls( final List<ArtifactUrl> urls = artifactUrlHandler.getUrls(
new URLPlaceholder(systemManagement.getTenantMetadata().getTenant(), new URLPlaceholder(systemManagement.getTenantMetadata().getTenant(),
systemManagement.getTenantMetadata().getId(), null, null, systemManagement.getTenantMetadata().getId(), null, null,
@@ -173,5 +170,4 @@ public final class MgmtSoftwareModuleMapper {
artifact.getId(), artifact.getSha1Hash())), ApiType.MGMT, null); artifact.getId(), artifact.getSha1Hash())), ApiType.MGMT, null);
urls.forEach(entry -> response.add(Link.of(entry.getRef()).withRel(entry.getRel()).expand())); urls.forEach(entry -> response.add(Link.of(entry.getRef()).withRel(entry.getRel()).expand()));
} }
} }

View File

@@ -1071,8 +1071,8 @@ public class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegr
assertThat(distributionSetManagement.count()).isEqualTo(1); assertThat(distributionSetManagement.count()).isEqualTo(1);
assertThat(set.isLocked()).as("Created distribution set should not be locked").isFalse(); assertThat(set.isLocked()).as("Created distribution set should not be locked").isFalse();
// lock
final String body = new JSONObject().put("locked", true).toString(); final String body = new JSONObject().put("locked", true).toString();
mvc.perform(put("/rest/v1/distributionsets/{dsId}", set.getId()).content(body) mvc.perform(put("/rest/v1/distributionsets/{dsId}", set.getId()).content(body)
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()) .andDo(MockMvcResultPrinter.print())
@@ -1084,8 +1084,8 @@ public class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegr
} }
@Test @Test
@Description("Tests the unlock. It is verified that the distribution set can't be unmarked as locked through update operation.") @Description("Tests the unlock.")
void unlockDistributionSetSkippedSilently() throws Exception { void unlockDistributionSet() throws Exception {
// prepare test data // prepare test data
assertThat(distributionSetManagement.findByCompleted(PAGE, true)).hasSize(0); assertThat(distributionSetManagement.findByCompleted(PAGE, true)).hasSize(0);
@@ -1094,19 +1094,19 @@ public class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegr
distributionSetManagement.lock(set.getId()); distributionSetManagement.lock(set.getId());
assertThat(distributionSetManagement.get(set.getId()) assertThat(distributionSetManagement.get(set.getId())
.orElseThrow(() -> new EntityNotFoundException(SoftwareModule.class, set.getId())).isLocked()) .orElseThrow(() -> new EntityNotFoundException(SoftwareModule.class, set.getId())).isLocked())
.as("Created software module should not be locked") .as("Distribution set should be locked")
.isTrue(); .isTrue();
// unlock
final String body = new JSONObject().put("locked", false).toString(); final String body = new JSONObject().put("locked", false).toString();
mvc.perform(put("/rest/v1/distributionsets/{dsId}", set.getId()).content(body) mvc.perform(put("/rest/v1/distributionsets/{dsId}", set.getId()).content(body)
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print()) .andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(jsonPath("$.locked", equalTo(true))); .andExpect(jsonPath("$.locked", equalTo(false)));
final DistributionSet updatedSet = distributionSetManagement.get(set.getId()).get(); final DistributionSet updatedSet = distributionSetManagement.get(set.getId()).get();
assertThat(updatedSet.isLocked()).isEqualTo(true); assertThat(updatedSet.isLocked()).isEqualTo(false);
} }
@Test @Test

View File

@@ -202,6 +202,7 @@ class MgmtSoftwareModuleResourceTest extends AbstractManagementApiIntegrationTes
.pollInterval(10L, TimeUnit.MILLISECONDS) .pollInterval(10L, TimeUnit.MILLISECONDS)
.until(() -> sm.getLastModifiedAt() > 0L && sm.getLastModifiedBy() != null); .until(() -> sm.getLastModifiedAt() > 0L && sm.getLastModifiedBy() != null);
// lock
final String body = new JSONObject().put("locked", true).toString(); final String body = new JSONObject().put("locked", true).toString();
final ResultActions resultActions = final ResultActions resultActions =
mvc.perform(put("/rest/v1/softwaremodules/{smId}", sm.getId()).content(body) mvc.perform(put("/rest/v1/softwaremodules/{smId}", sm.getId()).content(body)
@@ -221,15 +222,15 @@ class MgmtSoftwareModuleResourceTest extends AbstractManagementApiIntegrationTes
} }
@Test @Test
@Description("Tests the unlock. It is verified that the software module can't be unmarked as locked through update operation.") @Description("Tests the unlock.")
@WithUser(principal = "smUpdateTester", allSpPermissions = true) @WithUser(principal = "smUpdateTester", allSpPermissions = true)
void unlockSoftwareModuleSkippedSilently() throws Exception { void unlockSoftwareModule() throws Exception {
final SoftwareModule sm = softwareModuleManagement.create( final SoftwareModule sm = softwareModuleManagement.create(
entityFactory.softwareModule().create().type(osType).name("name1").version("version1")); entityFactory.softwareModule().create().type(osType).name("name1").version("version1"));
softwareModuleManagement.lock(sm.getId()); softwareModuleManagement.lock(sm.getId());
assertThat(softwareModuleManagement.get(sm.getId()) assertThat(softwareModuleManagement.get(sm.getId())
.orElseThrow(() -> new EntityNotFoundException(SoftwareModule.class, sm.getId())).isLocked()) .orElseThrow(() -> new EntityNotFoundException(SoftwareModule.class, sm.getId())).isLocked())
.as("Created software module should not be locked") .as("Software module is locked")
.isTrue(); .isTrue();
// ensures that we are not to fast so that last modified is not set correctly // ensures that we are not to fast so that last modified is not set correctly
Awaitility.await() Awaitility.await()
@@ -237,6 +238,7 @@ class MgmtSoftwareModuleResourceTest extends AbstractManagementApiIntegrationTes
.pollInterval(10L, TimeUnit.MILLISECONDS) .pollInterval(10L, TimeUnit.MILLISECONDS)
.until(() -> sm.getLastModifiedAt() > 0L && sm.getLastModifiedBy() != null); .until(() -> sm.getLastModifiedAt() > 0L && sm.getLastModifiedBy() != null);
// unlock
final String body = new JSONObject().put("locked", false).toString(); final String body = new JSONObject().put("locked", false).toString();
final ResultActions resultActions = final ResultActions resultActions =
mvc.perform(put("/rest/v1/softwaremodules/{smId}", sm.getId()).content(body) mvc.perform(put("/rest/v1/softwaremodules/{smId}", sm.getId()).content(body)
@@ -244,14 +246,14 @@ class MgmtSoftwareModuleResourceTest extends AbstractManagementApiIntegrationTes
final SoftwareModule updatedSm = softwareModuleManagement.get(sm.getId()).get(); final SoftwareModule updatedSm = softwareModuleManagement.get(sm.getId()).get();
assertThat(updatedSm.getLastModifiedBy()).isEqualTo("smUpdateTester"); assertThat(updatedSm.getLastModifiedBy()).isEqualTo("smUpdateTester");
assertThat(updatedSm.isLocked()).isTrue(); // not unlocked assertThat(updatedSm.isLocked()).isFalse(); // not unlocked
resultActions resultActions
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(jsonPath("$.id", equalTo(sm.getId().intValue()))) .andExpect(jsonPath("$.id", equalTo(sm.getId().intValue())))
.andExpect(jsonPath("$.lastModifiedBy", equalTo("smUpdateTester"))) .andExpect(jsonPath("$.lastModifiedBy", equalTo("smUpdateTester")))
.andExpect(jsonPath("$.lastModifiedAt", equalTo(updatedSm.getLastModifiedAt()))) .andExpect(jsonPath("$.lastModifiedAt", equalTo(updatedSm.getLastModifiedAt())))
.andExpect(jsonPath("$.locked", equalTo(true))); .andExpect(jsonPath("$.locked", equalTo(false)));
} }
@Test @Test