Bulk delete schemasets in CM handle deregistration 56/133256/3
authordanielhanrahan <daniel.hanrahan@est.tech>
Tue, 14 Feb 2023 13:24:40 +0000 (13:24 +0000)
committerdanielhanrahan <daniel.hanrahan@est.tech>
Wed, 15 Feb 2023 14:19:42 +0000 (14:19 +0000)
- Batch delete schema sets in single query
- Call deleteUnusedYangResourceModules once per batch, not per CM handle
- Results for deregistering 10k: 14 mins before; 6 mins after

Issue-ID: CPS-1423
Signed-off-by: danielhanrahan <daniel.hanrahan@est.tech>
Change-Id: Ia3a86a0dc88677323e2f386253a99022a7f02603

15 files changed:
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistence.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistenceImpl.java
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/InventoryPersistenceImplSpec.groovy
cps-ri/src/main/java/org/onap/cps/spi/impl/CpsModulePersistenceServiceImpl.java
cps-ri/src/main/java/org/onap/cps/spi/impl/utils/CpsValidatorImpl.java
cps-ri/src/main/java/org/onap/cps/spi/repository/SchemaSetRepository.java
cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsModulePersistenceServiceIntegrationSpec.groovy
cps-ri/src/test/groovy/org/onap/cps/spi/impl/utils/CpsValidatorSpec.groovy
cps-service/src/main/java/org/onap/cps/api/CpsModuleService.java
cps-service/src/main/java/org/onap/cps/api/impl/CpsModuleServiceImpl.java
cps-service/src/main/java/org/onap/cps/spi/CpsModulePersistenceService.java
cps-service/src/main/java/org/onap/cps/spi/utils/CpsValidator.java
cps-service/src/test/groovy/org/onap/cps/api/impl/CpsModuleServiceImplSpec.groovy

index e71b72a..508acdc 100755 (executable)
@@ -392,7 +392,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
     }
 
     private void batchDeleteCmHandlesFromDbAndModuleSyncMap(final Collection<String> tobeRemovedCmHandles) {
-        tobeRemovedCmHandles.forEach(inventoryPersistence::deleteSchemaSetWithCascade);
+        inventoryPersistence.deleteSchemaSetsWithCascade(tobeRemovedCmHandles);
         inventoryPersistence.deleteDataNodes(mapCmHandleIdsToXpaths(tobeRemovedCmHandles));
         tobeRemovedCmHandles.forEach(this::removeDeletedCmHandleFromModuleSyncMap);
     }
index 10227cf..73acf43 100644 (file)
@@ -113,6 +113,13 @@ public interface InventoryPersistence {
      */
     void deleteSchemaSetWithCascade(String schemaSetName);
 
+    /**
+     * Method to delete multiple schema sets.
+     *
+     * @param schemaSetNames schema set names
+     */
+    void deleteSchemaSetsWithCascade(Collection<String> schemaSetNames);
+
     /**
      * Get data node via xpath.
      *
index 4d1202b..2c97895 100644 (file)
@@ -166,6 +166,14 @@ public class InventoryPersistenceImpl implements InventoryPersistence {
         }
     }
 
+    @Override
+    @Timed(value = "cps.ncmp.inventory.persistence.schemaset.delete.batch",
+        description = "Time taken to delete multiple schemaset")
+    public void deleteSchemaSetsWithCascade(final Collection<String> schemaSetNames) {
+        cpsValidator.validateNameCharacters(schemaSetNames);
+        cpsModuleService.deleteSchemaSetsWithCascade(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, schemaSetNames);
+    }
+
     @Override
     @Timed(value = "cps.ncmp.inventory.persistence.datanode.get",
             description = "Time taken to get a data node (from ncmp dmi registry)")
@@ -237,4 +245,4 @@ public class InventoryPersistenceImpl implements InventoryPersistence {
     private static String createCmHandleJsonData(final String cmHandleId) {
         return "{\"cm-handles\":[" + cmHandleId + "]}";
     }
-}
\ No newline at end of file
+}
index cf34549..272030b 100644 (file)
@@ -249,14 +249,14 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
             def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server',
                 removedCmHandles: ['cmhandle'])
         and: '#scenario'
-            mockCpsModuleService.deleteSchemaSet(_, 'cmhandle', CASCADE_DELETE_ALLOWED) >>
+            mockCpsModuleService.deleteSchemaSetsWithCascade(_, ['cmhandle']) >>
                 { if (!schemaSetExist) { throw new SchemaSetNotFoundException("", "") } }
         when: 'registration is updated to delete cmhandle'
             def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
         then: 'the cmHandle state is updated to "DELETING"'
             1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(_)
         and: 'method to delete relevant schema set is called once'
-            1 * mockInventoryPersistence.deleteSchemaSetWithCascade(_)
+            1 * mockInventoryPersistence.deleteSchemaSetsWithCascade(_)
         and: 'method to delete relevant list/list element is called once'
             1 * mockInventoryPersistence.deleteDataNodes(_)
         and: 'successful response is received'
@@ -322,7 +322,9 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
             addPersistedYangModelCmHandles(['cmhandle'])
             def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server',
                 removedCmHandles: ['cmhandle'])
-        and: 'schema set deletion failed with unknown error'
+        and: 'schema set batch deletion failed with unknown error'
+            mockInventoryPersistence.deleteSchemaSetsWithCascade(_) >> { throw new RuntimeException('Failed') }
+        and: 'schema set single deletion failed with unknown error'
             mockInventoryPersistence.deleteSchemaSetWithCascade(_) >> { throw new RuntimeException('Failed') }
         when: 'registration is updated to delete cmhandle'
             def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
index 929ea84..d01df3a 100644 (file)
@@ -243,10 +243,19 @@ class InventoryPersistenceImplSpec extends Specification {
             objectUnderTest.deleteSchemaSetWithCascade('validSchemaSetName')
         then: 'the module service to delete schemaSet is invoked once'
             1 * mockCpsModuleService.deleteSchemaSet('NFP-Operational', 'validSchemaSetName', CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED)
-        and: 'the CM Handle ID is validated'
+        and: 'the schema set name is validated'
             1 * mockCpsValidator.validateNameCharacters('validSchemaSetName')
     }
 
+    def 'Delete multiple schema sets with valid schema set names'() {
+        when: 'the method to delete schema sets is called with valid schema set names'
+            objectUnderTest.deleteSchemaSetsWithCascade(['validSchemaSetName1', 'validSchemaSetName2'])
+        then: 'the module service to delete schema sets is invoked once'
+            1 * mockCpsModuleService.deleteSchemaSetsWithCascade('NFP-Operational', ['validSchemaSetName1', 'validSchemaSetName2'])
+        and: 'the schema set names are validated'
+            1 * mockCpsValidator.validateNameCharacters(['validSchemaSetName1', 'validSchemaSetName2'])
+    }
+
     def 'Get data node via xPath'() {
         when: 'the method to get data nodes is called'
             objectUnderTest.getDataNode('sample xPath')
index c9f9a78..b4366de 100755 (executable)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2020-2022 Nordix Foundation
+ *  Copyright (C) 2020-2023 Nordix Foundation
  *  Modifications Copyright (C) 2020-2022 Bell Canada.
  *  Modifications Copyright (C) 2021 Pantheon.tech
  *  Modifications Copyright (C) 2022 TechMahindra Ltd.
@@ -191,6 +191,13 @@ public class CpsModulePersistenceServiceImpl implements CpsModulePersistenceServ
         schemaSetRepository.delete(schemaSetEntity);
     }
 
+    @Override
+    @Transactional
+    public void deleteSchemaSets(final String dataspaceName, final Collection<String> schemaSetNames) {
+        final var dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
+        schemaSetRepository.deleteByDataspaceAndNameIn(dataspaceEntity, schemaSetNames);
+    }
+
     @Override
     @Transactional
     public void deleteUnusedYangResourceModules() {
index 0645831..1f61ee3 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2022 Nordix Foundation
+ *  Copyright (C) 2022-2023 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@
 package org.onap.cps.spi.impl.utils;
 
 import com.google.common.collect.Lists;
+import java.util.Arrays;
 import java.util.Collection;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
@@ -37,13 +38,18 @@ public class CpsValidatorImpl implements CpsValidator {
 
     @Override
     public void validateNameCharacters(final String... names) {
+        validateNameCharacters(Arrays.asList(names));
+    }
+
+    @Override
+    public void validateNameCharacters(final Iterable<String> names) {
         for (final String name : names) {
             final Collection<Character> charactersOfName = Lists.charactersOf(name);
             for (final char unsupportedCharacter : UNSUPPORTED_NAME_CHARACTERS) {
                 if (charactersOfName.contains(unsupportedCharacter)) {
                     throw new DataValidationException("Name or ID Validation Error.",
-                            name + " invalid token encountered at position "
-                                    + (name.indexOf(unsupportedCharacter) + 1));
+                        name + " invalid token encountered at position "
+                            + (name.indexOf(unsupportedCharacter) + 1));
                 }
             }
         }
index 8cecb0a..98d4420 100644 (file)
@@ -2,6 +2,7 @@
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2020 Pantheon.tech
  *  Modifications Copyright (C) 2022 TechMahindra Ltd.
+ *  Modifications Copyright (C) 2023 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -29,6 +30,9 @@ import org.onap.cps.spi.entities.DataspaceEntity;
 import org.onap.cps.spi.entities.SchemaSetEntity;
 import org.onap.cps.spi.exceptions.SchemaSetNotFoundException;
 import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
 import org.springframework.stereotype.Repository;
 
 @Repository
@@ -70,4 +74,14 @@ public interface SchemaSetRepository extends JpaRepository<SchemaSetEntity, Inte
     default List<SchemaSetEntity> getByDataspace(@NotNull final DataspaceEntity dataspaceEntity) {
         return findByDataspace(dataspaceEntity).stream().collect(Collectors.toList());
     }
+
+    /**
+     * Delete multiple schema sets in a given dataspace.
+     * @param dataspaceEntity dataspace entity
+     * @param schemaSetNames  schema set names
+     */
+    @Modifying
+    @Query("DELETE FROM SchemaSetEntity s WHERE s.dataspace = :dataspaceEntity AND s.name IN (:schemaSetNames)")
+    void deleteByDataspaceAndNameIn(@NotNull @Param("dataspaceEntity") final DataspaceEntity dataspaceEntity,
+                                    @NotNull @Param("schemaSetNames") final Collection<String> schemaSetNames);
 }
index 4c67f7e..864b3e3 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2022 Nordix Foundation
+ *  Copyright (C) 2021-2023 Nordix Foundation
  *  Modifications Copyright (C) 2021-2022 Bell Canada.
  *  Modifications Copyright (C) 2022 TechMahindra Ltd.
  *  ================================================================================
@@ -223,7 +223,16 @@ class CpsModulePersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase
         when: 'a schema set is deleted with cascade-prohibited option'
             objectUnderTest.deleteSchemaSet(DATASPACE_NAME, SCHEMA_SET_NAME_NO_ANCHORS)
         then: 'the schema set has been deleted'
-            schemaSetRepository.findByDataspaceAndName(dataspaceEntity, SCHEMA_SET_NAME_NO_ANCHORS).isPresent() == false
+            !schemaSetRepository.findByDataspaceAndName(dataspaceEntity, SCHEMA_SET_NAME_NO_ANCHORS).isPresent()
+    }
+
+    @Sql([CLEAR_DATA, SET_DATA])
+    def 'Delete schema sets'() {
+        when: 'schema sets are deleted'
+            objectUnderTest.deleteSchemaSets(DATASPACE_NAME, ['SCHEMA-SET-001', 'SCHEMA-SET-002'])
+        then: 'the schema sets have been deleted'
+            !schemaSetRepository.findByDataspaceAndName(dataspaceEntity, 'SCHEMA-SET-001').isPresent()
+            !schemaSetRepository.findByDataspaceAndName(dataspaceEntity, 'SCHEMA-SET-002').isPresent()
     }
 
     @Sql([CLEAR_DATA, SET_DATA])
index ae2ff16..345089c 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2022 Nordix Foundation
+ *  Copyright (C) 2022-2023 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -46,4 +46,22 @@ class CpsValidatorSpec extends Specification {
             'position 5' | 'name with spaces' || 'name with spaces invalid token encountered at position 5'
             'position 9' | 'nameWith Space'   || 'nameWith Space invalid token encountered at position 9'
     }
+
+    def 'Validating a list of valid names.'() {
+        given: 'a list of valid names'
+            def names = ['valid-name', 'another-valid-name']
+        when: 'a list of strings is validated'
+            objectUnderTest.validateNameCharacters(names)
+        then: 'no exception is thrown'
+            noExceptionThrown()
+    }
+
+    def 'Validating a list of names with invalid names.'() {
+        given: 'a list of names with an invalid name'
+            def names = ['valid-name', 'name with spaces']
+        when: 'a list of strings is validated'
+            objectUnderTest.validateNameCharacters(names)
+        then: 'a data validation exception is thrown'
+            thrown(DataValidationException)
+    }
 }
index b1f90d6..5ff08c9 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2020-2022 Nordix Foundation
+ *  Copyright (C) 2020-2023 Nordix Foundation
  *  Modifications Copyright (C) 2020-2021 Pantheon.tech
  *  Modifications Copyright (C) 2022 TechMahindra Ltd.
  *  ================================================================================
@@ -80,12 +80,20 @@ public interface CpsModuleService {
      * @param dataspaceName        dataspace name
      * @param schemaSetName        schema set name
      * @param cascadeDeleteAllowed indicates the allowance to remove associated anchors and data if exist
-     * @throws DataInUseException if cascadeDeleteAllowed is set to CASCADE_DELETE_PROHIBITED and there
-     *                           is associated anchor record exists in database
+     * @throws DataInUseException  if cascadeDeleteAllowed is set to CASCADE_DELETE_PROHIBITED and there
+     *                             is associated anchor record exists in database
      */
     void deleteSchemaSet(String dataspaceName, String schemaSetName,
                          CascadeDeleteAllowed cascadeDeleteAllowed);
 
+    /**
+     * Deletes Schema Sets with cascade.
+     *
+     * @param dataspaceName        dataspace name
+     * @param schemaSetNames       schema set names
+     */
+    void deleteSchemaSetsWithCascade(String dataspaceName, Collection<String> schemaSetNames);
+
     /**
      * Retrieve module references for the given dataspace name.
      *
index ccd0fcc..e71e6ce 100644 (file)
@@ -95,7 +95,7 @@ public class CpsModuleServiceImpl implements CpsModuleService {
     @Override
     @Transactional
     public void deleteSchemaSet(final String dataspaceName, final String schemaSetName,
-        final CascadeDeleteAllowed cascadeDeleteAllowed) {
+                                final CascadeDeleteAllowed cascadeDeleteAllowed) {
         cpsValidator.validateNameCharacters(dataspaceName, schemaSetName);
         final Collection<Anchor> anchors = cpsAdminService.getAnchors(dataspaceName, schemaSetName);
         if (!anchors.isEmpty() && isCascadeDeleteProhibited(cascadeDeleteAllowed)) {
@@ -109,6 +109,24 @@ public class CpsModuleServiceImpl implements CpsModuleService {
         cpsModulePersistenceService.deleteUnusedYangResourceModules();
     }
 
+    @Override
+    @Transactional
+    public void deleteSchemaSetsWithCascade(final String dataspaceName, final Collection<String> schemaSetNames) {
+        cpsValidator.validateNameCharacters(dataspaceName);
+        cpsValidator.validateNameCharacters(schemaSetNames);
+        for (final String schemaSetName : schemaSetNames) {
+            final Collection<Anchor> anchors = cpsAdminService.getAnchors(dataspaceName, schemaSetName);
+            for (final Anchor anchor : anchors) {
+                cpsAdminService.deleteAnchor(dataspaceName, anchor.getName());
+            }
+        }
+        cpsModulePersistenceService.deleteUnusedYangResourceModules();
+        cpsModulePersistenceService.deleteSchemaSets(dataspaceName, schemaSetNames);
+        for (final String schemaSetName : schemaSetNames) {
+            yangTextSchemaSourceSetCache.removeFromCache(dataspaceName, schemaSetName);
+        }
+    }
+
     @Override
     public Collection<ModuleReference> getYangResourceModuleReferences(final String dataspaceName) {
         cpsValidator.validateNameCharacters(dataspaceName);
index f5dc8ac..40d4002 100755 (executable)
@@ -1,6 +1,6 @@
 /*
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2020-2022 Nordix Foundation
+ *  Copyright (C) 2020-2023 Nordix Foundation
  *  Modifications Copyright (C) 2020-2022 Bell Canada.
  *  Modifications Copyright (C) 2022 TechMahindra Ltd.
  *  ================================================================================
@@ -69,6 +69,14 @@ public interface CpsModulePersistenceService {
      */
     void deleteSchemaSet(String dataspaceName, String schemaSetName);
 
+    /**
+     * Deletes Schema Sets.
+     *
+     * @param dataspaceName  dataspace name
+     * @param schemaSetNames schema set names
+     */
+    void deleteSchemaSets(String dataspaceName, Collection<String> schemaSetNames);
+
     /**
      * Returns YANG resources per specific dataspace / schemaSetName.
      *
index c7ce8fc..231094c 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2022 Nordix Foundation
+ *  Copyright (C) 2022-2023 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -28,4 +28,11 @@ public interface CpsValidator {
      * @param names names of data to be validated
      */
     void validateNameCharacters(final String... names);
+
+    /**
+     * Validate characters in names within cps.
+     *
+     * @param names names of data to be validated
+     */
+    void validateNameCharacters(final Iterable<String> names);
 }
index 2bb0e63..615d3af 100644 (file)
@@ -167,6 +167,28 @@ class CpsModuleServiceImplSpec extends Specification {
         return anchors
     }
 
+    def 'Delete multiple schema-sets when cascade is allowed.'() {
+        given: '#numberOfAnchors anchors are associated with each schemaset'
+            mockCpsAdminService.getAnchors('my-dataspace', 'my-schemaset1') >> createAnchors(numberOfAnchors)
+            mockCpsAdminService.getAnchors('my-dataspace', 'my-schemaset2') >> createAnchors(numberOfAnchors)
+        when: 'schema set deletion is requested with cascade allowed'
+            objectUnderTest.deleteSchemaSetsWithCascade('my-dataspace', ['my-schemaset1', 'my-schemaset2'])
+        then: 'anchor deletion is called 2 * #numberOfAnchors times'
+            (2 * numberOfAnchors) * mockCpsAdminService.deleteAnchor('my-dataspace', _)
+        and: 'persistence service method is invoked with same parameters'
+            mockCpsModulePersistenceService.deleteSchemaSets('my-dataspace', _)
+        and: 'schema sets will be removed from the cache'
+            2 * mockYangTextSchemaSourceSetCache.removeFromCache('my-dataspace', _)
+        and: 'orphan yang resources are deleted'
+            1 * mockCpsModulePersistenceService.deleteUnusedYangResourceModules()
+        and: 'the CpsValidator is called on the dataspaceName'
+            1 * mockCpsValidator.validateNameCharacters('my-dataspace')
+        and: 'the CpsValidator is called on the schemaSetNames'
+            1 * mockCpsValidator.validateNameCharacters(_)
+        where: 'following parameters are used'
+            numberOfAnchors << [0, 3]
+    }
+
     def 'Get all yang resources module references.'() {
         given: 'an already present module reference'
             def moduleReferences = [new ModuleReference('some module name','some revision name')]