CPS-314: Delete Dataspace 95/125795/4
authorniamhcore <niamh.core@est.tech>
Mon, 22 Nov 2021 11:44:38 +0000 (11:44 +0000)
committerniamhcore <niamh.core@est.tech>
Tue, 23 Nov 2021 10:53:39 +0000 (10:53 +0000)
Issue-ID: CPS-314
Change-Id: I778e2b784c7b1ff3fecc1036425708dc4ec73227
Signed-off-by: niamhcore <niamh.core@est.tech>
15 files changed:
cps-rest/docs/openapi/cpsAdmin.yml
cps-rest/src/main/java/org/onap/cps/rest/controller/AdminRestController.java
cps-rest/src/test/groovy/org/onap/cps/rest/controller/AdminRestControllerSpec.groovy
cps-rest/src/test/groovy/org/onap/cps/rest/exceptions/CpsRestExceptionHandlerSpec.groovy
cps-ri/src/main/java/org/onap/cps/spi/impl/CpsAdminPersistenceServiceImpl.java
cps-ri/src/main/java/org/onap/cps/spi/repository/AnchorRepository.java
cps-ri/src/main/java/org/onap/cps/spi/repository/SchemaSetRepository.java
cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsAdminPersistenceServiceSpec.groovy
cps-ri/src/test/resources/data/anchor.sql
cps-service/src/main/java/org/onap/cps/api/CpsAdminService.java
cps-service/src/main/java/org/onap/cps/api/impl/CpsAdminServiceImpl.java
cps-service/src/main/java/org/onap/cps/spi/CpsAdminPersistenceService.java
cps-service/src/main/java/org/onap/cps/spi/exceptions/DataspaceInUseException.java [new file with mode: 0644]
cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAdminServiceImplSpec.groovy
cps-service/src/test/groovy/org/onap/cps/spi/exceptions/CpsExceptionsSpec.groovy

index a022ef1..869cb6e 100644 (file)
@@ -35,6 +35,26 @@ dataspaces:
       '403':
         $ref: 'components.yml#/components/responses/Forbidden'
 
+  delete:
+    description: Delete a dataspace
+    tags:
+      - cps-admin
+    summary: Delete a dataspace
+    operationId: deleteDataspace
+    parameters:
+      - $ref: 'components.yml#/components/parameters/dataspaceNameInQuery'
+    responses:
+      '204':
+        $ref: 'components.yml#/components/responses/NoContent'
+      '400':
+        $ref: 'components.yml#/components/responses/BadRequest'
+      '401':
+        $ref: 'components.yml#/components/responses/Unauthorized'
+      '403':
+        $ref: 'components.yml#/components/responses/Forbidden'
+      '409':
+        $ref: 'components.yml#/components/responses/Conflict'
+
 schemaSet:
   post:
     description: Create a new schema set in the given dataspace
index 55fdbbe..52e64a9 100755 (executable)
@@ -70,6 +70,18 @@ public class AdminRestController implements CpsAdminApi {
         return new ResponseEntity<>(dataspaceName, HttpStatus.CREATED);
     }
 
+    /**
+     * Delete a dataspace.
+     *
+     * @param dataspaceName name of dataspace to be deleted
+     * @return a {@Link ResponseEntity} of {@link HttpStatus} NO_CONTENT
+     */
+    @Override
+    public ResponseEntity<Void> deleteDataspace(final String dataspaceName) {
+        cpsAdminService.deleteDataspace(dataspaceName);
+        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
+    }
+
     /**
      * Create a {@link SchemaSet}.
      *
index 84da2db..e8cfcfb 100755 (executable)
@@ -311,6 +311,18 @@ class AdminRestControllerSpec extends Specification {
             response.status == HttpStatus.NO_CONTENT.value()
     }
 
+    def 'Delete dataspace.'() {
+        given: 'an endpoint'
+            def dataspaceEndpoint = "$basePath/v1/dataspaces"
+        when: 'delete dataspace endpoint is invoked'
+            def response = mvc.perform(delete(dataspaceEndpoint)
+                .param('dataspace-name', dataspaceName))
+                .andReturn().response
+        then: 'associated service method is invoked with expected parameter'
+            1 * mockCpsAdminService.deleteDataspace(dataspaceName)
+        and: 'response code indicates success'
+            response.status == HttpStatus.NO_CONTENT.value()
+    }
 
     def createMultipartFile(filename, content) {
         return new MockMultipartFile("file", filename, "text/plain", content.getBytes())
@@ -333,4 +345,5 @@ class AdminRestControllerSpec extends Specification {
         multipartFile.getInputStream() >> { throw new IOException() }
         return multipartFile
     }
+
 }
index 079a59c..f596844 100644 (file)
@@ -37,6 +37,7 @@ import org.onap.cps.spi.exceptions.DataValidationException
 import org.onap.cps.spi.exceptions.ModelValidationException
 import org.onap.cps.spi.exceptions.NotFoundInDataspaceException
 import org.onap.cps.spi.exceptions.SchemaSetInUseException
+import org.onap.cps.spi.exceptions.DataspaceInUseException
 import org.spockframework.spring.SpringBean
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.beans.factory.annotation.Value
@@ -145,7 +146,8 @@ class CpsRestExceptionHandlerSpec extends Specification {
             assertTestResponse(response, CONFLICT, exceptionThrown.getMessage(), exceptionThrown.getDetails())
         where: 'the following exceptions are thrown'
             exceptionThrown << [new DataInUseException(dataspaceName, existingObjectName),
-                                new SchemaSetInUseException(dataspaceName, existingObjectName)]
+                                new SchemaSetInUseException(dataspaceName, existingObjectName),
+                                new DataspaceInUseException(dataspaceName, errorDetails)]
     }
 
     /*
index b1bd03c..9c69006 100755 (executable)
@@ -32,6 +32,7 @@ import org.onap.cps.spi.entities.AnchorEntity;
 import org.onap.cps.spi.entities.DataspaceEntity;
 import org.onap.cps.spi.entities.YangResourceModuleReference;
 import org.onap.cps.spi.exceptions.AlreadyDefinedException;
+import org.onap.cps.spi.exceptions.DataspaceInUseException;
 import org.onap.cps.spi.exceptions.ModuleNamesNotFoundException;
 import org.onap.cps.spi.model.Anchor;
 import org.onap.cps.spi.repository.AnchorRepository;
@@ -70,6 +71,22 @@ public class CpsAdminPersistenceServiceImpl implements CpsAdminPersistenceServic
         }
     }
 
+    @Override
+    public void deleteDataspace(final String dataspaceName) {
+        final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
+        final int numberOfAssociatedAnchors = anchorRepository.countByDataspace(dataspaceEntity);
+        if (numberOfAssociatedAnchors != 0) {
+            throw new DataspaceInUseException(dataspaceName,
+                    String.format("Dataspace contains %d anchor(s)", numberOfAssociatedAnchors));
+        }
+        final int numberOfAssociatedSchemaSets = schemaSetRepository.countByDataspace(dataspaceEntity);
+        if (numberOfAssociatedSchemaSets != 0) {
+            throw new DataspaceInUseException(dataspaceName,
+                    String.format("Dataspace contains %d schemaset(s)", numberOfAssociatedSchemaSets));
+        }
+        dataspaceRepository.delete(dataspaceEntity);
+    }
+
     @Override
     public void createAnchor(final String dataspaceName, final String schemaSetName, final String anchorName) {
         final var dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
index 5870fd9..471f175 100755 (executable)
@@ -45,6 +45,8 @@ public interface AnchorRepository extends JpaRepository<AnchorEntity, Integer> {
 
     Collection<AnchorEntity> findAllBySchemaSet(@NotNull SchemaSetEntity schemaSetEntity);
 
+    Integer countByDataspace(@NotNull DataspaceEntity dataspaceEntity);
+
     @Query(value = "SELECT anchor.* FROM yang_resource\n"
         + "JOIN schema_set_yang_resources ON schema_set_yang_resources.yang_resource_id = yang_resource.id\n"
         + "JOIN schema_set ON schema_set.id = schema_set_yang_resources.schema_set_id\n"
index 7b56f93..a15ce62 100644 (file)
@@ -19,7 +19,6 @@
 
 package org.onap.cps.spi.repository;
 
-import java.util.List;
 import java.util.Optional;
 import javax.validation.constraints.NotNull;
 import org.onap.cps.spi.entities.DataspaceEntity;
@@ -31,11 +30,11 @@ import org.springframework.stereotype.Repository;
 @Repository
 public interface SchemaSetRepository extends JpaRepository<SchemaSetEntity, Integer> {
 
-    List<SchemaSetEntity> findAllByDataspace(@NotNull DataspaceEntity dataspaceEntity);
-
     Optional<SchemaSetEntity> findByDataspaceAndName(@NotNull DataspaceEntity dataspaceEntity,
         @NotNull String schemaSetName);
 
+    Integer countByDataspace(@NotNull DataspaceEntity dataspaceEntity);
+
     /**
      * Gets a schema set by dataspace and schema set name.
      *
index a0df2b1..4b5b116 100644 (file)
@@ -24,6 +24,7 @@ package org.onap.cps.spi.impl
 import org.onap.cps.spi.CpsAdminPersistenceService
 import org.onap.cps.spi.exceptions.AlreadyDefinedException
 import org.onap.cps.spi.exceptions.AnchorNotFoundException
+import org.onap.cps.spi.exceptions.DataspaceInUseException
 import org.onap.cps.spi.exceptions.DataspaceNotFoundException
 import org.onap.cps.spi.exceptions.SchemaSetNotFoundException
 import org.onap.cps.spi.exceptions.ModuleNamesNotFoundException
@@ -36,10 +37,9 @@ class CpsAdminPersistenceServiceSpec extends CpsPersistenceSpecBase {
     @Autowired
     CpsAdminPersistenceService objectUnderTest
 
-
     static final String SET_DATA = '/data/anchor.sql'
     static final String SAMPLE_DATA_FOR_ANCHORS_WITH_MODULES = '/data/anchors-schemaset-modules.sql'
-    static final String EMPTY_DATASPACE_NAME = 'DATASPACE-002'
+    static final String DATASPACE_WITH_NO_DATA = 'DATASPACE-002'
     static final Integer DELETED_ANCHOR_ID = 3001
     static final Long DELETED_FRAGMENT_ID = 4001
 
@@ -111,7 +111,7 @@ class CpsAdminPersistenceServiceSpec extends CpsPersistenceSpecBase {
             dataspaceName        || expectedAnchors
             DATASPACE_NAME       || [Anchor.builder().name(ANCHOR_NAME1).schemaSetName(SCHEMA_SET_NAME1).dataspaceName(DATASPACE_NAME).build(),
                                      Anchor.builder().name(ANCHOR_NAME2).schemaSetName(SCHEMA_SET_NAME2).dataspaceName(DATASPACE_NAME).build()]
-            EMPTY_DATASPACE_NAME || []
+            DATASPACE_WITH_NO_DATA || []
     }
 
     @Sql(CLEAR_DATA)
@@ -173,4 +173,27 @@ class CpsAdminPersistenceServiceSpec extends CpsPersistenceSpecBase {
     def buildAnchor(def anchorName, def dataspaceName, def SchemaSetName) {
         return Anchor.builder().name(anchorName).dataspaceName(dataspaceName).schemaSetName(SchemaSetName).build()
     }
+
+    @Sql([CLEAR_DATA, SET_DATA])
+    def 'Delete dataspace.'() {
+        when: 'delete dataspace action is invoked'
+            objectUnderTest.deleteDataspace(DATASPACE_WITH_NO_DATA)
+        then: 'dataspace is deleted'
+            assert dataspaceRepository.findByName(DATASPACE_WITH_NO_DATA).isEmpty();
+    }
+
+    @Sql([CLEAR_DATA, SET_DATA])
+    def 'Delete dataspace when #scenario.'() {
+        when: 'delete dataspace action is invoked'
+            objectUnderTest.deleteDataspace(dataspaceName)
+        then: 'the correct exception is thrown with the relevant details'
+            def thrownException = thrown(expectedException)
+            thrownException.details.contains(expectedMessageDetails)
+        where: 'the following data is used'
+            scenario                          | dataspaceName       || expectedException            | expectedMessageDetails
+            'dataspace name does not exist'   | 'unknown'           || DataspaceNotFoundException   | 'unknown does not exist'
+            'dataspace contains an anchor'    | 'DATASPACE-001'     || DataspaceInUseException      | 'contains 2 anchor(s)'
+            'dataspace contains schemasets'   | 'DATASPACE-003'     || DataspaceInUseException      | 'contains 1 schemaset(s)'
+    }
+
 }
index dbf1a6a..c9240f7 100644 (file)
 */
 
 INSERT INTO DATASPACE (ID, NAME) VALUES
-    (1001, 'DATASPACE-001'), (1002, 'DATASPACE-002');
+    (1001, 'DATASPACE-001'),
+    (1002, 'DATASPACE-002'),
+    (1003, 'DATASPACE-003');
 
 INSERT INTO SCHEMA_SET (ID, NAME, DATASPACE_ID) VALUES
-    (2001, 'SCHEMA-SET-001', 1001), (2002, 'SCHEMA-SET-002', 1001);
+    (2001, 'SCHEMA-SET-001', 1001),
+    (2002, 'SCHEMA-SET-002', 1001),
+    (2003, 'SCHEMA-SET-002', 1003);
 
 INSERT INTO ANCHOR (ID, NAME, DATASPACE_ID, SCHEMA_SET_ID) VALUES
     (3001, 'ANCHOR-001', 1001, 2001),
index 1d08cde..7ba9599 100755 (executable)
@@ -41,6 +41,13 @@ public interface CpsAdminService {
      */
     void createDataspace(@NonNull String dataspaceName);
 
+    /**
+     * Delete dataspace.
+     *
+     * @param dataspaceName the name of the dataspace to delete
+     */
+    void deleteDataspace(@NonNull String dataspaceName);
+
     /**
      * Create an Anchor.
      *
index faff7b6..d831793 100755 (executable)
@@ -41,6 +41,11 @@ public class CpsAdminServiceImpl implements CpsAdminService {
         cpsAdminPersistenceService.createDataspace(dataspaceName);
     }
 
+    @Override
+    public void deleteDataspace(final String dataspaceName) {
+        cpsAdminPersistenceService.deleteDataspace(dataspaceName);
+    }
+
     @Override
     public void createAnchor(final String dataspaceName, final String schemaSetName, final String anchorName) {
         cpsAdminPersistenceService.createAnchor(dataspaceName, schemaSetName, anchorName);
index 104ac4f..9553700 100755 (executable)
@@ -40,6 +40,13 @@ public interface CpsAdminPersistenceService {
      */
     void createDataspace(@NonNull String dataspaceName);
 
+    /**
+     * Delete dataspace.
+     *
+     * @param dataspaceName the name of the dataspace to delete
+     */
+    void deleteDataspace(@NonNull String dataspaceName);
+
     /**
      * Create an Anchor.
      *
diff --git a/cps-service/src/main/java/org/onap/cps/spi/exceptions/DataspaceInUseException.java b/cps-service/src/main/java/org/onap/cps/spi/exceptions/DataspaceInUseException.java
new file mode 100644 (file)
index 0000000..7889301
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation.
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.spi.exceptions;
+
+/**
+ * Runtime exception.
+ * Thrown when given dataspace name is rejected to be deleted because it has anchor or schemasets associated.
+ */
+
+public class DataspaceInUseException extends DataInUseException {
+
+    private static final long serialVersionUID = 4531370947720760347L;
+
+    /**
+     * Constructor.
+     *
+     * @param dataspaceName dataspace name
+     * @param details error message details
+     */
+    public DataspaceInUseException(final String dataspaceName, final String details) {
+        super(String.format("Dataspace with name %s is being used.", dataspaceName), details);
+    }
+}
index 95afeb4..6d1f586 100755 (executable)
@@ -78,4 +78,12 @@ class CpsAdminServiceImplSpec extends Specification {
             objectUnderTest.queryAnchorNames('some-dataspace-name', ['some-module-name']) == ['some-anchor-identifier']
 
     }
+
+    def 'Delete dataspace.'() {
+        when: 'delete dataspace is invoked'
+            objectUnderTest.deleteDataspace('someDataspace')
+        then: 'associated persistence service method is invoked with correct parameter'
+            1 * mockCpsAdminPersistenceService.deleteDataspace('someDataspace')
+    }
+
 }
index 4243c18..5bd3678 100755 (executable)
@@ -170,4 +170,12 @@ class CpsExceptionsSpec extends Specification {
         expect: 'the exception has the provided details'
             exception.details == providedDetails
     }
+
+    def 'Creating an exception that the dataspace is being used and cannot be deleted.'() {
+        given: 'a dataspace in use exception is created'
+            def exception = new DataspaceInUseException(dataspaceName,providedDetails)
+        expect: 'the exception has the correct message with dataspace name and provided details'
+            exception.message == "Dataspace with name ${dataspaceName} is being used."
+            exception.details == providedDetails
+    }
 }