diff --git a/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResource.java b/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResource.java index e8acdb84c..f7f74dde4 100644 --- a/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResource.java +++ b/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResource.java @@ -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 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 createdDSets = distributionSetManagement .create(MgmtDistributionSetMapper.dsFromRequest(sets, entityFactory)); diff --git a/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtSoftwareModuleResource.java b/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtSoftwareModuleResource.java index a9bb366ff..d7b0682dd 100644 --- a/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtSoftwareModuleResource.java +++ b/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtSoftwareModuleResource.java @@ -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 softwareModules) { LOG.debug("creating {} softwareModules", softwareModules.size()); + + for (MgmtSoftwareModuleRequestBodyPost sm : softwareModules) { + final Optional 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 createdSoftwareModules = softwareModuleManagement .create(MgmtSoftwareModuleMapper.smFromRequest(entityFactory, softwareModules)); LOG.debug("{} softwareModules created, return status {}", softwareModules.size(), HttpStatus.CREATED); diff --git a/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResourceTest.java b/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResourceTest.java index 3a0663854..383f2f2db 100644 --- a/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResourceTest.java +++ b/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResourceTest.java @@ -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 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); diff --git a/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtSoftwareModuleResourceTest.java b/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtSoftwareModuleResourceTest.java index 5237b92eb..a8f2a12c6 100644 --- a/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtSoftwareModuleResourceTest.java +++ b/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtSoftwareModuleResourceTest.java @@ -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 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 {