Feature/handle soft deleted type creation (#1335)

* Handle creation of entities from already marked as deleted types
* Remove unused comment
* Remove wildcard imports
* Changes after comments

Signed-off-by: Stanislav Trailov <stanislav.trailov@bosch.io>
This commit is contained in:
Stanislav Trailov
2023-03-20 16:40:46 +02:00
committed by GitHub
parent d567b32280
commit 5af1fa1e1c
4 changed files with 132 additions and 4 deletions

View File

@@ -8,14 +8,17 @@
*/
package org.eclipse.hawkbit.mgmt.rest.resource;
import java.text.MessageFormat;
import java.util.AbstractMap.SimpleEntry;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.stream.Collectors;
import javax.validation.Valid;
import javax.validation.ValidationException;
import org.eclipse.hawkbit.mgmt.json.model.MgmtMetadata;
import org.eclipse.hawkbit.mgmt.json.model.MgmtMetadataBodyPut;
@@ -35,6 +38,7 @@ import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.DistributionSetInvalidationManagement;
import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.DistributionSetTypeManagement;
import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.OffsetBasedPageRequest;
import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
@@ -45,6 +49,7 @@ import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.model.DeploymentRequest;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation;
import org.eclipse.hawkbit.repository.model.DistributionSetMetadata;
@@ -87,6 +92,8 @@ public class MgmtDistributionSetResource implements MgmtDistributionSetRestApi {
private final DistributionSetManagement distributionSetManagement;
private final DistributionSetTypeManagement distributionSetTypeManagement;
private final SystemSecurityContext systemSecurityContext;
private final DistributionSetInvalidationManagement distributionSetInvalidationManagement;
@@ -97,7 +104,7 @@ public class MgmtDistributionSetResource implements MgmtDistributionSetRestApi {
final TargetManagement targetManagement, final TargetFilterQueryManagement targetFilterQueryManagement,
final DeploymentManagement deployManagament, final SystemManagement systemManagement,
final EntityFactory entityFactory, final DistributionSetManagement distributionSetManagement,
final SystemSecurityContext systemSecurityContext,
final DistributionSetTypeManagement distributionSetTypeManagement, final SystemSecurityContext systemSecurityContext,
final DistributionSetInvalidationManagement distributionSetInvalidationManagement,
final TenantConfigurationManagement tenantConfigurationManagement) {
this.softwareModuleManagement = softwareModuleManagement;
@@ -107,6 +114,7 @@ public class MgmtDistributionSetResource implements MgmtDistributionSetRestApi {
this.systemManagement = systemManagement;
this.entityFactory = entityFactory;
this.distributionSetManagement = distributionSetManagement;
this.distributionSetTypeManagement = distributionSetTypeManagement;
this.systemSecurityContext = systemSecurityContext;
this.distributionSetInvalidationManagement = distributionSetInvalidationManagement;
this.tenantConfigHelper = TenantConfigHelper.usingContext(systemSecurityContext, tenantConfigurationManagement);
@@ -159,6 +167,18 @@ public class MgmtDistributionSetResource implements MgmtDistributionSetRestApi {
.runAsSystem(systemManagement.getTenantMetadata().getDefaultDsType()::getKey);
sets.stream().filter(ds -> ds.getType() == null).forEach(ds -> ds.setType(defaultDsKey));
//check if there is already deleted DS Type
for (MgmtDistributionSetRequestBodyPost ds : sets) {
final Optional<DistributionSetType> opt = distributionSetTypeManagement.getByKey(ds.getType());
opt.ifPresent(dsType -> {
if (dsType.isDeleted()) {
final String text = "Cannot create Distribution Set from type with key {0}. Distribution Set Type already deleted!";
final String message = MessageFormat.format(text, dsType.getKey());
throw new ValidationException(message);
}
});
}
final Collection<DistributionSet> createdDSets = distributionSetManagement
.create(MgmtDistributionSetMapper.dsFromRequest(sets, entityFactory));

View File

@@ -10,8 +10,10 @@ package org.eclipse.hawkbit.mgmt.rest.resource;
import java.io.IOException;
import java.io.InputStream;
import java.text.MessageFormat;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import org.eclipse.hawkbit.mgmt.json.model.PagedList;
import org.eclipse.hawkbit.mgmt.json.model.artifact.MgmtArtifact;
@@ -26,11 +28,13 @@ import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.OffsetBasedPageRequest;
import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
import org.eclipse.hawkbit.repository.SoftwareModuleTypeManagement;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.ArtifactUpload;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
@@ -47,6 +51,8 @@ import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.validation.ValidationException;
/**
* REST Resource handling for {@link SoftwareModule} and related
* {@link Artifact} CRUD operations.
@@ -60,12 +66,15 @@ public class MgmtSoftwareModuleResource implements MgmtSoftwareModuleRestApi {
private final SoftwareModuleManagement softwareModuleManagement;
private final SoftwareModuleTypeManagement softwareModuleTypeManagement;
private final EntityFactory entityFactory;
MgmtSoftwareModuleResource(final ArtifactManagement artifactManagement,
final SoftwareModuleManagement softwareModuleManagement, final EntityFactory entityFactory) {
MgmtSoftwareModuleResource(final ArtifactManagement artifactManagement, final SoftwareModuleManagement softwareModuleManagement,
final SoftwareModuleTypeManagement softwareModuleTypeManagement, final EntityFactory entityFactory) {
this.artifactManagement = artifactManagement;
this.softwareModuleManagement = softwareModuleManagement;
this.softwareModuleTypeManagement = softwareModuleTypeManagement;
this.entityFactory = entityFactory;
}
@@ -182,6 +191,17 @@ public class MgmtSoftwareModuleResource implements MgmtSoftwareModuleRestApi {
@RequestBody final List<MgmtSoftwareModuleRequestBodyPost> softwareModules) {
LOG.debug("creating {} softwareModules", softwareModules.size());
for (MgmtSoftwareModuleRequestBodyPost sm : softwareModules) {
final Optional<SoftwareModuleType> opt = softwareModuleTypeManagement.getByKey(sm.getType());
opt.ifPresent(smType -> {
if (smType.isDeleted()) {
final String text = "Cannot create Software Module from type with key {0}. Software Module Type already deleted!";
final String message = MessageFormat.format(text, smType.getKey());
throw new ValidationException(message);
}
});
}
final Collection<SoftwareModule> createdSoftwareModules = softwareModuleManagement
.create(MgmtSoftwareModuleMapper.smFromRequest(entityFactory, softwareModules));
LOG.debug("{} softwareModules created, return status {}", softwareModules.size(), HttpStatus.CREATED);

View File

@@ -12,6 +12,8 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.hasSize;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
@@ -26,6 +28,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
@@ -40,6 +43,7 @@ import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetMetadata;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.NamedEntity;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus;
@@ -49,6 +53,7 @@ import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.repository.test.util.TestdataFactory;
import org.eclipse.hawkbit.repository.test.util.WithUser;
import org.eclipse.hawkbit.rest.json.model.ExceptionInfo;
import org.eclipse.hawkbit.rest.util.JsonBuilder;
import org.eclipse.hawkbit.rest.util.MockMvcResultPrinter;
import org.json.JSONArray;
@@ -70,6 +75,9 @@ import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.qameta.allure.Step;
import io.qameta.allure.Story;
import org.springframework.util.Assert;
import javax.validation.constraints.AssertTrue;
@Feature("Component Tests - Management API")
@Story("Distribution Set Resource")
@@ -299,6 +307,44 @@ public class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegr
// we just need to make sure that no error 500 is returned
}
@Test
@Description("Trying to create a DS from already marked as deleted type - should get as response 400 Bad Request")
public void createDsFromAlreadyMarkedAsDeletedType() throws Exception {
final SoftwareModule softwareModule = testdataFactory.createSoftwareModule("exampleKey");
final DistributionSetType type = testdataFactory.findOrCreateDistributionSetType(
"testKey", "testType", Collections.singletonList(softwareModule.getType()),
Collections.singletonList(softwareModule.getType()));
final DistributionSet ds = testdataFactory.createDistributionSet("dsName", "dsVersion", type, Collections.singletonList(softwareModule));
final Target target = testdataFactory.createTarget("exampleControllerId");
assignDistributionSet(ds, target);
//soft delete ds type
distributionSetTypeManagement.delete(type.getId());
// check if the ds type is marked as deleted
final Optional<DistributionSetType> opt = distributionSetTypeManagement.getByKey(type.getKey());
if (opt.isEmpty()) {
throw new AssertionError("The Optional object of distribution set type should not be empty!");
}
final DistributionSetType reloaded = opt.get();
Assert.isTrue(reloaded.isDeleted(), "Distribution Set Type not marked as deleted!");
//request for ds creation of type which is already marked as deleted - should return bad request
final DistributionSet generated = testdataFactory.generateDistributionSet(
"stanTest", "2", reloaded, Collections.singletonList(softwareModule));
final MvcResult mvcResult = mvc
.perform(post("/rest/v1/distributionsets/")
.content(JsonBuilder.distributionSets(Arrays.asList(generated)))
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isBadRequest()).andReturn();
final ExceptionInfo exceptionInfo = ResourceUtility.convertException(mvcResult.getResponse().getContentAsString());
assertEquals("javax.validation.ValidationException", exceptionInfo.getExceptionClass());
assertTrue(exceptionInfo.getMessage().contains("Distribution Set Type already deleted"));
}
@Test
@Description("Ensures that multi target assignment is protected by our getMaxTargetDistributionSetAssignmentsPerManualAssignment quota.")
public void assignMultipleTargetsToDistributionSetUntilQuotaIsExceeded() throws Exception {
@@ -765,7 +811,6 @@ public class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegr
@Description("Ensures that multipe DS posted to API are created in the repository.")
public void createDistributionSets() throws Exception {
assertThat(distributionSetManagement.findByCompleted(PAGE, true)).hasSize(0);
final SoftwareModule ah = testdataFactory.createSoftwareModule(TestdataFactory.SM_TYPE_APP);
final SoftwareModule jvm = testdataFactory.createSoftwareModule(TestdataFactory.SM_TYPE_RT);
final SoftwareModule os = testdataFactory.createSoftwareModule(TestdataFactory.SM_TYPE_OS);

View File

@@ -14,6 +14,7 @@ import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.hasSize;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
@@ -31,6 +32,7 @@ import java.io.InputStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.IOUtils;
@@ -47,8 +49,11 @@ import org.eclipse.hawkbit.repository.exception.StorageQuotaExceededException;
import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.ArtifactUpload;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.test.util.HashGeneratorUtils;
import org.eclipse.hawkbit.repository.test.util.WithUser;
import org.eclipse.hawkbit.rest.json.model.ExceptionInfo;
@@ -65,6 +70,7 @@ import org.springframework.http.MediaType;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RestController;
import com.jayway.jsonpath.JsonPath;
@@ -350,6 +356,43 @@ class MgmtSoftwareModuleResourceTest extends AbstractManagementApiIntegrationTes
assertThat(artifactManagement.getByFilename("customFilename")).as("Local artifact is wrong").isPresent();
}
@Test
@Description("Trying to create a SM from already marked as deleted type - should get as response 400 Bad Request")
public void createSMFromAlreadyMarkedAsDeletedType() throws Exception {
final String SM_TYPE = "someSmType";
final SoftwareModule sm = testdataFactory.createSoftwareModule(SM_TYPE);
final DistributionSetType t = testdataFactory.findOrCreateDistributionSetType(
"testKey", "testType", Collections.singletonList(sm.getType()),
Collections.singletonList(sm.getType()));
final DistributionSetType type = testdataFactory.findOrCreateDistributionSetType("testKey", "testType");
final DistributionSet ds = testdataFactory.createDistributionSet("name", "version", type, Collections.singletonList(sm));
final Target target = testdataFactory.createTarget("test");
assignDistributionSet(ds, target);
//delete sm type
softwareModuleTypeManagement.delete(sm.getType().getId());
//check if it is marked as deleted
final Optional<SoftwareModuleType> opt = softwareModuleTypeManagement.getByKey(SM_TYPE);
if (opt.isEmpty()) {
throw new AssertionError("The Optional object of software module type should not be empty!");
}
final SoftwareModuleType smType = opt.get();
Assert.isTrue(smType.isDeleted(), "Software Module Type not marked as deleted!");
//check if we'll get bad request if we try to create module from the deleted type
final MvcResult mvcResult = mvc.perform(post("/rest/v1/softwaremodules")
.content("[{\"description\":\"someDescription\",\"key\":\"someTestKey\", \"type\":\"" + SM_TYPE + "\"}]")
.contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print())
.andExpect(status().isBadRequest())
.andReturn();
final ExceptionInfo exceptionInfo = ResourceUtility.convertException(mvcResult.getResponse().getContentAsString());
assertEquals("javax.validation.ValidationException", exceptionInfo.getExceptionClass());
assertTrue(exceptionInfo.getMessage().contains("Software Module Type already deleted"));
}
@Test
@Description("Verifies that the system refuses upload of an artifact where the provided hash sums do not match. Expected result: BAD REQUEST")
void uploadArtifactWithHashCheck() throws Exception {