[#1580] Software Module & Distribution Set lock: apply (#1648)

forbid software modules / artifacts modification for locked distribution
sets / software modules respectively

Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2024-02-15 15:56:01 +02:00
committed by GitHub
parent 5c38af2772
commit 94576bd6fe
7 changed files with 135 additions and 3 deletions

View File

@@ -205,6 +205,10 @@ public enum SpServerError {
*/
SP_DS_INCOMPLETE("hawkbit.server.error.distributionset.incomplete",
"Distribution set is assigned/locked to a target that is incomplete (i.e. mandatory modules are missing)"),
/**
*
*/
SP_LOCKED("hawkbit.server.error.locked", "Entry is locked. Could not be modified"),
/**
*

View File

@@ -9,6 +9,7 @@
*/
package org.eclipse.hawkbit.repository.exception;
import java.io.Serial;
import java.util.Collection;
import java.util.stream.Collectors;
@@ -23,7 +24,9 @@ import org.eclipse.hawkbit.repository.model.MetaData;
*/
public class EntityNotFoundException extends AbstractServerRtException {
@Serial
private static final long serialVersionUID = 1L;
private static final SpServerError THIS_ERROR = SpServerError.SP_REPO_ENTITY_NOT_EXISTS;
/**

View File

@@ -0,0 +1,36 @@
/**
* Copyright (c) 2024 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 org.eclipse.hawkbit.exception.AbstractServerRtException;
import org.eclipse.hawkbit.exception.SpServerError;
import org.eclipse.hawkbit.repository.model.BaseEntity;
import java.io.Serial;
/**
* Thrown if assignment quota is exceeded
*/
public class LockedException extends AbstractServerRtException {
@Serial
private static final long serialVersionUID = 1L;
private static final String ASSIGNMENT_QUOTA_EXCEEDED_MESSAGE = "Quota exceeded: Cannot assign %s more %s entities to %s '%s'. The maximum is %s.";
private static final SpServerError THIS_ERROR = SpServerError.SP_LOCKED;
public LockedException(
final Class<? extends BaseEntity> type, final Object entityId, final String operation) {
super(
type.getSimpleName() + " with given identifier {" + entityId + "} is locked and " + operation +
" is forbidden!",
THIS_ERROR);
}
}

View File

@@ -44,6 +44,7 @@ import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetCreated
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetUpdatedEvent;
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.UnsupportedSoftwareModuleForThisDistributionSetException;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.DistributionSet;
@@ -185,6 +186,10 @@ public class JpaDistributionSet extends AbstractJpaNamedVersionedEntity implemen
}
public boolean addModule(final SoftwareModule softwareModule) {
if (isLocked()) {
throw new LockedException(JpaDistributionSet.class, getId(), "ADD_SOFTWARE_MODULE");
}
if (modules == null) {
modules = new HashSet<>();
}
@@ -215,6 +220,10 @@ public class JpaDistributionSet extends AbstractJpaNamedVersionedEntity implemen
}
public void removeModule(final SoftwareModule softwareModule) {
if (isLocked()) {
throw new LockedException(JpaDistributionSet.class, getId(), "REMOVE_SOFTWARE_MODULE");
}
if (modules != null && modules.removeIf(m -> m.getId().equals(softwareModule.getId()))) {
complete = type.checkComplete(this);
}
@@ -253,7 +262,7 @@ public class JpaDistributionSet extends AbstractJpaNamedVersionedEntity implemen
}
public void lock() {
if (!complete) {
if (!isComplete()) {
throw new IncompleteDistributionSetException("Could not be locked while incomplete!");
}
locked = true;

View File

@@ -39,6 +39,7 @@ import lombok.ToString;
import org.eclipse.hawkbit.repository.event.remote.SoftwareModuleDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleUpdatedEvent;
import org.eclipse.hawkbit.repository.exception.LockedException;
import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
@@ -140,6 +141,10 @@ public class JpaSoftwareModule extends AbstractJpaNamedVersionedEntity implement
}
public void addArtifact(final Artifact artifact) {
if (isLocked()) {
throw new LockedException(JpaSoftwareModule.class, getId(), "ADD_ARTIFACT");
}
if (artifacts == null) {
artifacts = new ArrayList<>(4);
artifacts.add((JpaArtifact) artifact);
@@ -155,6 +160,10 @@ public class JpaSoftwareModule extends AbstractJpaNamedVersionedEntity implement
* @param artifact is removed from the assigned {@link Artifact}s.
*/
public void removeArtifact(final Artifact artifact) {
if (isLocked()) {
throw new LockedException(JpaSoftwareModule.class, getId(), "REMOVE_ARTIFACT");
}
if (artifacts != null) {
artifacts.remove(artifact);
}

View File

@@ -43,6 +43,7 @@ import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException;
import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException;
import org.eclipse.hawkbit.repository.exception.InvalidDistributionSetException;
import org.eclipse.hawkbit.repository.exception.LockedException;
import org.eclipse.hawkbit.repository.exception.UnsupportedSoftwareModuleForThisDistributionSetException;
import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
@@ -1010,7 +1011,39 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
}
@Test
@Description("Locks an incomplete DS. Expected behaviour is to throw exception and to do not lock it.")
@Description("Software modules of a locked DS can't be modified. Expected behaviour is to throw an exception and to do not modify them.")
void lockDistributionSetApplied() {
final DistributionSet distributionSet = testdataFactory.createDistributionSet("ds-1");
final int softwareModuleCount = distributionSet.getModules().size();
assertThat(softwareModuleCount).isNotEqualTo(0);
distributionSetManagement.lock(distributionSet.getId());
assertThat(
distributionSetManagement.get(distributionSet.getId()).map(DistributionSet::isLocked)
.orElse(false))
.isTrue();
// try add
assertThatExceptionOfType(LockedException.class)
.as("Attempt to modify a locked DS software modules should throw an exception")
.isThrownBy(() -> distributionSetManagement.assignSoftwareModules(
distributionSet.getId(), List.of(testdataFactory.createSoftwareModule("sm-1").getId())));
assertThat(distributionSetManagement.get(distributionSet.getId()).get().getModules().size())
.as("Software module shall not be added to a locked DS.")
.isEqualTo(softwareModuleCount);
// try remove
assertThatExceptionOfType(LockedException.class)
.as("Attempt to modify a locked DS software modules should throw an exception")
.isThrownBy(() -> distributionSetManagement.unassignSoftwareModule(
distributionSet.getId(), distributionSet.getModules().stream().findFirst().get().getId()));
assertThat(distributionSetManagement.get(distributionSet.getId()).get().getModules().size())
.as("Software module shall not be removed from a locked DS.")
.isEqualTo(softwareModuleCount);
}
@Test
@Description("Locks an incomplete DS. Expected behaviour is to throw an exception and to do not lock it.")
void lockIncompleteDistributionSetFails() {
final DistributionSet incompleteDistributionSet = testdataFactory.createIncompleteDistributionSet();
assertThatExceptionOfType(IncompleteDistributionSetException.class)

View File

@@ -28,6 +28,7 @@ import org.eclipse.hawkbit.repository.builder.SoftwareModuleMetadataCreate;
import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.LockedException;
import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest;
import org.eclipse.hawkbit.repository.jpa.RandomGeneratedInputStream;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction_;
@@ -824,7 +825,7 @@ public class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest {
@Test
@Description("Locks a SM.")
void lockSoftwareModule() {
final SoftwareModule softwareModule = testdataFactory.createSoftwareModule("ds-1");
final SoftwareModule softwareModule = testdataFactory.createSoftwareModule("sm-1");
assertThat(
softwareModuleManagement.get(softwareModule.getId()).map(SoftwareModule::isLocked).orElse(true))
.isFalse();
@@ -834,6 +835,43 @@ public class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest {
.isTrue();
}
@Test
@Description("Artifacts of a locked SM can't be modified. Expected behaviour is to throw an exception and to do not modify them.")
void lockSoftwareModuleApplied() {
final SoftwareModule softwareModule = testdataFactory.createSoftwareModule("sm-1");
artifactManagement.create(
new ArtifactUpload(new ByteArrayInputStream(new byte[] {1}), softwareModule.getId(),
"artifact1", false, 1));
final int artifactCount = softwareModuleManagement.get(softwareModule.getId()).get().getArtifacts().size();
assertThat(artifactCount).isNotEqualTo(0);
softwareModuleManagement.lock(softwareModule.getId());
assertThat(
softwareModuleManagement.get(softwareModule.getId()).map(SoftwareModule::isLocked).orElse(false))
.isTrue();
// try add
assertThatExceptionOfType(LockedException.class)
.as("Attempt to modify a locked SM artifacts should throw an exception")
.isThrownBy(() -> artifactManagement.create(
new ArtifactUpload(new ByteArrayInputStream(new byte[] {2}), softwareModule.getId(),
"artifact2", false, 1)));
assertThat(softwareModuleManagement.get(softwareModule.getId()).get().getArtifacts().size())
.as("Artifacts shall not be added to a locked SM.")
.isEqualTo(artifactCount);
// try remove
final long artifactId = softwareModuleManagement.get(softwareModule.getId()).get()
.getArtifacts().stream().findFirst().get().getId();
assertThatExceptionOfType(LockedException.class)
.as("Attempt to modify a locked DS software modules should throw an exception")
.isThrownBy(() -> artifactManagement.delete(artifactId));
assertThat(softwareModuleManagement.get(softwareModule.getId()).get().getArtifacts().size())
.as("Software module shall not be removed from a locked DS.")
.isEqualTo(artifactCount);
assertThat(artifactManagement.get(artifactId)).isPresent();
}
@Test
@Description("Verifies that non existing metadata find results in exception.")
public void findSoftwareModuleMetadataFailsIfEntryDoesNotExist() {