One SchemaSet per moduleSetTag 05/139805/6
authorToineSiebelink <toine.siebelink@est.tech>
Thu, 16 Jan 2025 14:41:18 +0000 (14:41 +0000)
committerToineSiebelink <toine.siebelink@est.tech>
Thu, 16 Jan 2025 14:41:27 +0000 (14:41 +0000)
- Registration: create and upgrade cases.
- Handle moduleSetTag deletion (all orphans) for testware
- Unit tests updated
- additional logging of details for upgrade scenarios
- Integration Tests updated
- Remove cache for module sets being processed
- Removed DbCleaner (startup)
- Removed redundant methods in NCMP Inventory for deleting schema set(s)
- Removed validation check for all schema set interactions
- Updated some schema set tests to use special characters previously not allowed
- Checked integration test scenarios for upgrades with and without tags: all scenarios covered!

TODO
- REST endpoint to remove orphaned schema set data, separate story: CPS-2554
- Investigate exception handling regarding DuplicateYangResourceException: CPS-2555

Issue-ID: CPS-2540
Signed-off-by: ToineSiebelink <toine.siebelink@est.tech>
Change-Id: Iaa59cbdb86b7a4a8044624829bc002506ff40cc7

39 files changed:
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationService.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImpl.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/NcmpPersistence.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/NcmpPersistenceImpl.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleOperationsUtils.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncService.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasks.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/SynchronizationCacheConfig.java
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServiceSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImplSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncServiceSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasksSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/SynchronizationCacheConfigSpec.groovy
cps-ri/src/main/java/org/onap/cps/ri/CpsModulePersistenceServiceImpl.java
cps-ri/src/main/java/org/onap/cps/ri/repository/ModuleReferenceQuery.java
cps-ri/src/main/java/org/onap/cps/ri/repository/ModuleReferenceRepositoryImpl.java
cps-ri/src/main/java/org/onap/cps/ri/repository/SchemaSetRepository.java
cps-ri/src/main/java/org/onap/cps/ri/repository/SchemaSetYangResourceRepository.java
cps-ri/src/main/java/org/onap/cps/ri/repository/YangResourceRepository.java
cps-service/src/main/java/org/onap/cps/api/CpsModuleService.java
cps-service/src/main/java/org/onap/cps/api/impl/CpsAnchorServiceImpl.java
cps-service/src/main/java/org/onap/cps/api/impl/CpsModuleServiceImpl.java
cps-service/src/main/java/org/onap/cps/api/impl/YangTextSchemaSourceSetCache.java
cps-service/src/main/java/org/onap/cps/init/DbCleaner.java [deleted file]
cps-service/src/main/java/org/onap/cps/spi/CpsModulePersistenceService.java
cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAnchorServiceImplSpec.groovy
cps-service/src/test/groovy/org/onap/cps/api/impl/CpsModuleServiceImplSpec.groovy
cps-service/src/test/groovy/org/onap/cps/api/impl/YangTextSchemaSourceSetCacheSpec.groovy
cps-service/src/test/groovy/org/onap/cps/init/DbCleanerSpec.groovy [deleted file]
docker-compose/docker-compose.yml
docs/deployment.rst
integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy
integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/AnchorServiceIntegrationSpec.groovy
integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/DataspaceServiceIntegrationSpec.groovy
integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/ModuleServiceIntegrationSpec.groovy
integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleUpgradeSpec.groovy
integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/ModuleSyncWatchdogIntegrationSpec.groovy
integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/YangModulesSpec.groovy
integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/ModuleQueryPerfTest.groovy

index fc215c9..e7fd247 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2024 Nordix Foundation
+ *  Copyright (C) 2021-2025 Nordix Foundation
  *  Modifications Copyright (C) 2021 Pantheon.tech
  *  Modifications Copyright (C) 2021-2022 Bell Canada
  *  Modifications Copyright (C) 2023 TechMahindra Ltd.
@@ -138,17 +138,20 @@ public class CmHandleRegistrationService {
 
     protected void processRemovedCmHandles(final DmiPluginRegistration dmiPluginRegistration,
                                            final DmiPluginRegistrationResponse dmiPluginRegistrationResponse) {
-        final List<String> tobeRemovedCmHandleIds = dmiPluginRegistration.getRemovedCmHandles();
+        final List<String> toBeRemovedCmHandleIds = dmiPluginRegistration.getRemovedCmHandles();
+        if (toBeRemovedCmHandleIds.isEmpty()) {
+            return;
+        }
         final List<CmHandleRegistrationResponse> cmHandleRegistrationResponses =
-            new ArrayList<>(tobeRemovedCmHandleIds.size());
+            new ArrayList<>(toBeRemovedCmHandleIds.size());
         final Collection<YangModelCmHandle> yangModelCmHandles =
-            inventoryPersistence.getYangModelCmHandles(tobeRemovedCmHandleIds);
+            inventoryPersistence.getYangModelCmHandles(toBeRemovedCmHandleIds);
         updateCmHandleStateBatch(yangModelCmHandles, CmHandleState.DELETING);
 
         final Set<String> notDeletedCmHandles = new HashSet<>();
-        for (final List<String> tobeRemovedCmHandleBatch : Lists.partition(tobeRemovedCmHandleIds, DELETE_BATCH_SIZE)) {
+        for (final List<String> tobeRemovedCmHandleBatch : Lists.partition(toBeRemovedCmHandleIds, DELETE_BATCH_SIZE)) {
             try {
-                batchDeleteCmHandlesFromDbAndCaches(tobeRemovedCmHandleBatch);
+                deleteCmHandlesFromDbAndCaches(tobeRemovedCmHandleBatch);
                 tobeRemovedCmHandleBatch.forEach(cmHandleId ->
                     cmHandleRegistrationResponses.add(CmHandleRegistrationResponse.createSuccessResponse(cmHandleId)));
 
@@ -201,8 +204,9 @@ public class CmHandleRegistrationService {
 
     protected void processUpdatedCmHandles(final DmiPluginRegistration dmiPluginRegistration,
                                            final DmiPluginRegistrationResponse dmiPluginRegistrationResponse) {
-        dmiPluginRegistrationResponse.setUpdatedCmHandles(cmHandleRegistrationServicePropertyHandler
-            .updateCmHandleProperties(dmiPluginRegistration.getUpdatedCmHandles()));
+        final List<CmHandleRegistrationResponse> updatedCmHandles = cmHandleRegistrationServicePropertyHandler
+            .updateCmHandleProperties(dmiPluginRegistration.getUpdatedCmHandles());
+        dmiPluginRegistrationResponse.setUpdatedCmHandles(updatedCmHandles);
     }
 
     protected void processUpgradedCmHandles(
@@ -275,7 +279,7 @@ public class CmHandleRegistrationService {
 
     private CmHandleRegistrationResponse deleteCmHandleAndGetCmHandleRegistrationResponse(final String cmHandleId) {
         try {
-            deleteCmHandleFromDbAndCaches(cmHandleId);
+            deleteCmHandlesFromDbAndCaches(Collections.singletonList(cmHandleId));
             return CmHandleRegistrationResponse.createSuccessResponse(cmHandleId);
         } catch (final DataNodeNotFoundException dataNodeNotFoundException) {
             log.error("Unable to find dataNode for cmHandleId : {} , caused by : {}",
@@ -298,16 +302,9 @@ public class CmHandleRegistrationService {
         lcmEventsCmHandleStateHandler.updateCmHandleStateBatch(cmHandleStatePerCmHandle);
     }
 
-    private void deleteCmHandleFromDbAndCaches(final String cmHandleId) {
-        inventoryPersistence.deleteSchemaSetWithCascade(cmHandleId);
-        inventoryPersistence.deleteDataNode(NCMP_DMI_REGISTRY_PARENT + "/cm-handles[@id='" + cmHandleId + "']");
-        trustLevelManager.removeCmHandles(Collections.singleton(cmHandleId));
-        removeDeletedCmHandleFromModuleSyncMap(cmHandleId);
-    }
-
-    private void batchDeleteCmHandlesFromDbAndCaches(final Collection<String> cmHandleIds) {
-        inventoryPersistence.deleteSchemaSetsWithCascade(cmHandleIds);
+    private void deleteCmHandlesFromDbAndCaches(final Collection<String> cmHandleIds) {
         inventoryPersistence.deleteDataNodes(mapCmHandleIdsToXpaths(cmHandleIds));
+        inventoryPersistence.deleteAnchors(cmHandleIds);
         trustLevelManager.removeCmHandles(cmHandleIds);
         cmHandleIds.forEach(this::removeDeletedCmHandleFromModuleSyncMap);
     }
@@ -326,8 +323,11 @@ public class CmHandleRegistrationService {
 
     private List<CmHandleRegistrationResponse> upgradeCmHandles(final Map<YangModelCmHandle, CmHandleState>
                                                                     cmHandleStatePerCmHandle) {
+        if (cmHandleStatePerCmHandle.isEmpty()) {
+            return Collections.emptyList();
+        }
         final List<String> cmHandleIds = getCmHandleIds(cmHandleStatePerCmHandle);
-        log.info("Moving cm handles : {} into locked (for upgrade) state.", cmHandleIds);
+        log.info("Moving {} cm handles into locked (for upgrade) state: {} ", cmHandleIds.size(), cmHandleIds);
         try {
             lcmEventsCmHandleStateHandler.updateCmHandleStateBatch(cmHandleStatePerCmHandle);
             return CmHandleRegistrationResponse.createSuccessResponses(cmHandleIds);
@@ -384,5 +384,4 @@ public class CmHandleRegistrationService {
             ncmpServiceCmHandle.getDataProducerIdentifier());
     }
 
-
 }
index d566ae4..e7ec9cd 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2022-2024 Nordix Foundation
+ *  Copyright (C) 2022-2025 Nordix Foundation
  *  Modifications Copyright (C) 2022 Bell Canada
  *  Modifications Copyright (C) 2024 TechMahindra Ltd.
  *  ================================================================================
@@ -62,26 +62,27 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv
     private static final int CMHANDLE_BATCH_SIZE = 100;
 
     private final CpsModuleService cpsModuleService;
-    private final CpsAnchorService cpsAnchorService;
     private final CpsValidator cpsValidator;
     private final CmHandleQueryService cmHandleQueryService;
 
     /**
      * initialize an inventory persistence object.
      *
-     * @param jsonObjectMapper json mapper object
-     * @param cpsDataService   cps data service instance
-     * @param cpsModuleService cps module service instance
-     * @param cpsValidator     cps validation service instance
-     * @param cpsAnchorService  cps anchor service instance
+     * @param cpsValidator         cps validation service instance
+     * @param jsonObjectMapper     json mapper object
+     * @param cpsAnchorService     cps anchor service instance
+     * @param cpsModuleService     cps module service instance
+     * @param cpsDataService       cps data service instance
+     * @param cmHandleQueryService cm handle query service instance
      */
-    public InventoryPersistenceImpl(final JsonObjectMapper jsonObjectMapper, final CpsDataService cpsDataService,
-                                    final CpsModuleService cpsModuleService, final CpsValidator cpsValidator,
+    public InventoryPersistenceImpl(final CpsValidator cpsValidator,
+                                    final JsonObjectMapper jsonObjectMapper,
                                     final CpsAnchorService cpsAnchorService,
+                                    final CpsModuleService cpsModuleService,
+                                    final CpsDataService cpsDataService,
                                     final CmHandleQueryService cmHandleQueryService) {
-        super(jsonObjectMapper, cpsDataService, cpsModuleService, cpsValidator);
+        super(jsonObjectMapper, cpsAnchorService, cpsDataService);
         this.cpsModuleService = cpsModuleService;
-        this.cpsAnchorService = cpsAnchorService;
         this.cpsValidator = cpsValidator;
         this.cmHandleQueryService = cmHandleQueryService;
     }
index 714a7ca..f327eda 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * ============LICENSE_START=======================================================
- * Copyright (C) 2022-2023 Nordix Foundation
+ * Copyright (C) 2022-2025 Nordix Foundation
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -25,9 +25,6 @@ import java.util.Collection;
 import org.onap.cps.api.model.DataNode;
 import org.onap.cps.api.parameters.FetchDescendantsOption;
 
-/**
- * DmiRegistryConstants class to be strictly used for DMI Related constants only.
- */
 public interface NcmpPersistence {
 
     String NCMP_DATASPACE_NAME = "NCMP-Admin";
@@ -43,20 +40,6 @@ public interface NcmpPersistence {
      */
     void deleteListOrListElement(String listElementXpath);
 
-    /**
-     * Method to delete a schema set.
-     *
-     * @param schemaSetName schema set name
-     */
-    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.
      *
@@ -113,4 +96,12 @@ public interface NcmpPersistence {
      * @param dataNodeXpaths data node xpaths
      */
     void deleteDataNodes(Collection<String> dataNodeXpaths);
+
+    /**
+     * Deletes multiple anchors identified by their IDs.
+     *
+     * @param anchorIds ids of the anchors to be deleted
+     */
+    void deleteAnchors(Collection<String> anchorIds);
+
 }
index 6092d8b..2232d7c 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2023 Nordix Foundation
+ *  Copyright (C) 2023-2025 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
 
 package org.onap.cps.ncmp.impl.inventory;
 
-import static org.onap.cps.api.parameters.CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED;
 import static org.onap.cps.api.parameters.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS;
 
 import io.micrometer.core.annotation.Timed;
 import java.util.Collection;
 import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.api.CpsAnchorService;
 import org.onap.cps.api.CpsDataService;
-import org.onap.cps.api.CpsModuleService;
-import org.onap.cps.api.exceptions.SchemaSetNotFoundException;
 import org.onap.cps.api.model.DataNode;
 import org.onap.cps.api.parameters.FetchDescendantsOption;
-import org.onap.cps.impl.utils.CpsValidator;
 import org.onap.cps.utils.JsonObjectMapper;
 import org.springframework.stereotype.Component;
 
-@Slf4j
 @RequiredArgsConstructor
 @Component
 public class NcmpPersistenceImpl implements NcmpPersistence {
 
     protected final JsonObjectMapper jsonObjectMapper;
+    protected final CpsAnchorService cpsAnchorService;
     protected final CpsDataService cpsDataService;
-    private final CpsModuleService cpsModuleService;
-    private final CpsValidator cpsValidator;
 
     @Override
     public void deleteListOrListElement(final String listElementXpath) {
@@ -52,27 +46,6 @@ public class NcmpPersistenceImpl implements NcmpPersistence {
                 NO_TIMESTAMP);
     }
 
-    @Override
-    @Timed(value = "cps.ncmp.inventory.persistence.schemaset.delete",
-            description = "Time taken to delete a schemaset")
-    public void deleteSchemaSetWithCascade(final String schemaSetName) {
-        try {
-            cpsValidator.validateNameCharacters(schemaSetName);
-            cpsModuleService.deleteSchemaSet(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, schemaSetName,
-                    CASCADE_DELETE_ALLOWED);
-        } catch (final SchemaSetNotFoundException schemaSetNotFoundException) {
-            log.warn("Schema set {} does not exist or already deleted", schemaSetName);
-        }
-    }
-
-    @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)")
@@ -116,4 +89,9 @@ public class NcmpPersistenceImpl implements NcmpPersistence {
         cpsDataService.deleteDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, dataNodeXpaths, NO_TIMESTAMP);
     }
 
+    @Override
+    public void deleteAnchors(final Collection<String> anchorIds) {
+        cpsAnchorService.deleteAnchors(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, anchorIds);
+    }
+
 }
index 994ca80..e9f3d9b 100644 (file)
@@ -189,7 +189,7 @@ public class ModuleOperationsUtils {
                 .getLockReasonCategory()));
     }
 
-    public static String getUpgradedModuleSetTagFromLockReason(final CompositeState.LockReason lockReason) {
+    public static String getTargetModuleSetTagFromLockReason(final CompositeState.LockReason lockReason) {
         return getLockedCompositeStateDetails(lockReason).getOrDefault(MODULE_SET_TAG_KEY, "");
     }
 
index 3f92dc7..9534cf3 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2022-2024 Nordix Foundation
+ *  Copyright (C) 2022-2025 Nordix Foundation
  *  Modifications Copyright (C) 2024 TechMahindra Ltd.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
@@ -26,24 +26,18 @@ import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY
 import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT;
 import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME;
 
-import com.hazelcast.collection.ISet;
 import java.time.OffsetDateTime;
 import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
 import java.util.Map;
 import lombok.AllArgsConstructor;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import org.apache.logging.log4j.util.Strings;
 import org.onap.cps.api.CpsAnchorService;
 import org.onap.cps.api.CpsDataService;
 import org.onap.cps.api.CpsModuleService;
-import org.onap.cps.api.exceptions.SchemaSetNotFoundException;
+import org.onap.cps.api.exceptions.AlreadyDefinedException;
+import org.onap.cps.api.exceptions.DuplicatedYangResourceException;
 import org.onap.cps.api.model.ModuleReference;
-import org.onap.cps.api.parameters.CascadeDeleteAllowed;
-import org.onap.cps.ncmp.api.exceptions.NcmpException;
-import org.onap.cps.ncmp.api.inventory.models.CmHandleState;
 import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle;
 import org.onap.cps.utils.ContentType;
 import org.onap.cps.utils.JsonObjectMapper;
@@ -54,15 +48,11 @@ import org.springframework.stereotype.Service;
 @RequiredArgsConstructor
 public class ModuleSyncService {
 
-    private static final Map<String, String> NO_NEW_MODULES = Collections.emptyMap();
-
     private final DmiModelOperations dmiModelOperations;
     private final CpsModuleService cpsModuleService;
     private final CpsDataService cpsDataService;
     private final CpsAnchorService cpsAnchorService;
     private final JsonObjectMapper jsonObjectMapper;
-    private final ISet<String> moduleSetTagsBeingProcessed;
-    private final Map<String, ModuleDelta> privateModuleSetCache = new HashMap<>();
 
     @AllArgsConstructor
     private static final class ModuleDelta {
@@ -71,42 +61,16 @@ public class ModuleSyncService {
     }
 
     /**
-     * This method creates a cm handle and initiates modules sync.
+     * Creates a CM handle and initiates the synchronization of modules to create a schema set and anchor.
      *
      * @param yangModelCmHandle the yang model of cm handle.
      */
     public void syncAndCreateSchemaSetAndAnchor(final YangModelCmHandle yangModelCmHandle) {
+        final String cmHandleId = yangModelCmHandle.getId();
         final String moduleSetTag = yangModelCmHandle.getModuleSetTag();
-        final ModuleDelta moduleDelta;
-        boolean isNewModuleSetTag = Strings.isNotBlank(moduleSetTag);
-        try {
-            if (privateModuleSetCache.containsKey(moduleSetTag)) {
-                moduleDelta = privateModuleSetCache.get(moduleSetTag);
-            } else {
-                if (isNewModuleSetTag) {
-                    if (moduleSetTagsBeingProcessed.add(moduleSetTag)) {
-                        log.info("Processing new module set tag {}", moduleSetTag);
-                    } else {
-                        isNewModuleSetTag = false;
-                        throw new NcmpException("Concurrent processing of module set tag " + moduleSetTag,
-                            moduleSetTag + " already being processed for cm handle " + yangModelCmHandle.getId());
-                    }
-                }
-                moduleDelta = getModuleDelta(yangModelCmHandle, moduleSetTag);
-            }
-            final String cmHandleId = yangModelCmHandle.getId();
-            cpsModuleService.createSchemaSetFromModules(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, cmHandleId,
-                moduleDelta.newModuleNameToContentMap, moduleDelta.allModuleReferences);
-            cpsAnchorService.createAnchor(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, cmHandleId, cmHandleId);
-            if (isNewModuleSetTag) {
-                final ModuleDelta noModuleDelta = new ModuleDelta(moduleDelta.allModuleReferences, NO_NEW_MODULES);
-                privateModuleSetCache.put(moduleSetTag, noModuleDelta);
-            }
-        } finally {
-            if (isNewModuleSetTag) {
-                moduleSetTagsBeingProcessed.remove(moduleSetTag);
-            }
-        }
+        final String schemaSetName = getSchemaSetName(cmHandleId, moduleSetTag);
+        syncAndCreateSchemaSet(yangModelCmHandle, schemaSetName);
+        cpsAnchorService.createAnchor(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, schemaSetName, cmHandleId);
     }
 
     /**
@@ -115,55 +79,61 @@ public class ModuleSyncService {
      * @param yangModelCmHandle the yang model of cm handle.
      */
     public void syncAndUpgradeSchemaSet(final YangModelCmHandle yangModelCmHandle) {
-        final String upgradedModuleSetTag = ModuleOperationsUtils.getUpgradedModuleSetTagFromLockReason(
+        final String cmHandleId = yangModelCmHandle.getId();
+        final String sourceModuleSetTag = yangModelCmHandle.getModuleSetTag();
+        final String targetModuleSetTag = ModuleOperationsUtils.getTargetModuleSetTagFromLockReason(
                 yangModelCmHandle.getCompositeState().getLockReason());
-        final ModuleDelta moduleDelta = getModuleDelta(yangModelCmHandle, upgradedModuleSetTag);
-        cpsModuleService.upgradeSchemaSetFromModules(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME,
-                yangModelCmHandle.getId(), moduleDelta.newModuleNameToContentMap, moduleDelta.allModuleReferences);
-        setCmHandleModuleSetTag(yangModelCmHandle, upgradedModuleSetTag);
+        if (sourceModuleSetTag.isEmpty() && targetModuleSetTag.isEmpty()) {
+            final ModuleDelta moduleDelta = getModuleDelta(yangModelCmHandle);
+            cpsModuleService.upgradeSchemaSetFromModules(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME,
+                    cmHandleId, moduleDelta.newModuleNameToContentMap, moduleDelta.allModuleReferences);
+        } else {
+            final String targetSchemaSetName = getSchemaSetName(cmHandleId, targetModuleSetTag);
+            syncAndCreateSchemaSet(yangModelCmHandle, targetSchemaSetName);
+            cpsAnchorService.updateAnchorSchemaSet(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, cmHandleId,
+                    targetSchemaSetName);
+            setCmHandleModuleSetTag(yangModelCmHandle, targetModuleSetTag);
+            log.info("Upgrading schema set for CM handle ID: {}, Source Tag: {}, Target Tag: {}",
+                cmHandleId, sourceModuleSetTag, targetModuleSetTag);
+        }
     }
 
-    /**
-     * Deletes the SchemaSet for schema set id if the SchemaSet Exists.
-     *
-     * @param schemaSetId the schema set id to be deleted
-     */
-    public void deleteSchemaSetIfExists(final String schemaSetId) {
-        try {
-            cpsModuleService.deleteSchemaSet(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, schemaSetId,
-                CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED);
-            log.debug("SchemaSet for {} has been deleted. Ready to be recreated.", schemaSetId);
-        } catch (final SchemaSetNotFoundException e) {
-            log.debug("No SchemaSet for {}. Assuming CmHandle has not been previously Module Synced.", schemaSetId);
+    private void syncAndCreateSchemaSet(final YangModelCmHandle yangModelCmHandle, final String schemaSetName) {
+        if (isNewSchemaSet(schemaSetName)) {
+            final ModuleDelta moduleDelta = getModuleDelta(yangModelCmHandle);
+            try {
+                log.info("Creating Schema Set {} for CM Handle {}", schemaSetName, yangModelCmHandle.getId());
+                cpsModuleService.createSchemaSetFromModules(
+                        NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME,
+                        schemaSetName,
+                        moduleDelta.newModuleNameToContentMap,
+                        moduleDelta.allModuleReferences
+                );
+                log.info("Successfully created Schema Set {} for CM Handle {}",
+                    schemaSetName, yangModelCmHandle.getId());
+            } catch (final AlreadyDefinedException | DuplicatedYangResourceException exception) {
+                log.warn("Schema Set {} already exists, no need to (re)create it for {}",
+                    schemaSetName, yangModelCmHandle.getId());
+            }
         }
     }
 
-    public void clearPrivateModuleSetCache() {
-        privateModuleSetCache.clear();
+    private boolean isNewSchemaSet(final String schemaSetName) {
+        return !cpsModuleService.schemaSetExists(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, schemaSetName);
     }
 
-    private ModuleDelta getModuleDelta(final YangModelCmHandle yangModelCmHandle, final String targetModuleSetTag) {
-        final Map<String, String> newYangResources;
-        Collection<ModuleReference> allModuleReferences = getModuleReferencesByModuleSetTag(targetModuleSetTag);
-        if (allModuleReferences.isEmpty()) {
-            allModuleReferences = dmiModelOperations.getModuleReferences(yangModelCmHandle);
-            newYangResources = dmiModelOperations.getNewYangResourcesFromDmi(yangModelCmHandle,
-                    cpsModuleService.identifyNewModuleReferences(allModuleReferences));
-        } else {
-            log.info("Found other cm handle having same module set tag: {}", targetModuleSetTag);
-            newYangResources = NO_NEW_MODULES;
-        }
+    private ModuleDelta getModuleDelta(final YangModelCmHandle yangModelCmHandle) {
+        final Collection<ModuleReference> allModuleReferences =
+            dmiModelOperations.getModuleReferences(yangModelCmHandle);
+        final Collection<ModuleReference> newModuleReferences =
+                cpsModuleService.identifyNewModuleReferences(allModuleReferences);
+        final Map<String, String> newYangResources = dmiModelOperations.getNewYangResourcesFromDmi(yangModelCmHandle,
+                newModuleReferences);
+        log.debug("Module delta calculated for CM handle ID: {}. All references: {}. New modules: {}",
+            yangModelCmHandle.getId(), allModuleReferences, newYangResources.keySet());
         return new ModuleDelta(allModuleReferences, newYangResources);
     }
 
-    private Collection<ModuleReference> getModuleReferencesByModuleSetTag(final String moduleSetTag) {
-        if (Strings.isBlank(moduleSetTag)) {
-            return Collections.emptyList();
-        }
-        return cpsModuleService.getModuleReferencesByAttribute(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
-                Map.of("module-set-tag", moduleSetTag), Map.of("cm-handle-state", CmHandleState.READY.name()));
-    }
-
     private void setCmHandleModuleSetTag(final YangModelCmHandle yangModelCmHandle, final String newModuleSetTag) {
         final String jsonForUpdate = jsonObjectMapper.asJsonString(Map.of(
                 "cm-handles", Map.of("id", yangModelCmHandle.getId(), "module-set-tag", newModuleSetTag)));
@@ -171,4 +141,8 @@ public class ModuleSyncService {
                 jsonForUpdate, OffsetDateTime.now(), ContentType.JSON);
     }
 
+    private static String getSchemaSetName(final String cmHandleId, final String moduleSetTag) {
+        return moduleSetTag.isEmpty() ? cmHandleId : moduleSetTag;
+    }
+
 }
index 5f289c2..40404b7 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2022-2024 Nordix Foundation
+ *  Copyright (C) 2022-2025 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -109,13 +109,12 @@ public class ModuleSyncTasks {
             if (inUpgrade) {
                 moduleSyncService.syncAndUpgradeSchemaSet(yangModelCmHandle);
             } else {
-                moduleSyncService.deleteSchemaSetIfExists(yangModelCmHandle.getId());
                 moduleSyncService.syncAndCreateSchemaSetAndAnchor(yangModelCmHandle);
             }
             compositeState.setLockReason(null);
             return CmHandleState.READY;
         } catch (final Exception e) {
-            log.warn("Processing of {} module failed due to reason {}.", yangModelCmHandle.getId(), e.getMessage());
+            log.warn("Processing of {} failed,reason : {}.", yangModelCmHandle.getId(), e.getMessage());
             final LockReasonCategory lockReasonCategory = inUpgrade
                     ? LockReasonCategory.MODULE_UPGRADE_FAILED
                     : LockReasonCategory.MODULE_SYNC_FAILED;
index d6ac242..c05944f 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * ============LICENSE_START========================================================
- *  Copyright (C) 2022-2024 Nordix Foundation
+ *  Copyright (C) 2022-2025 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
 
 package org.onap.cps.ncmp.impl.inventory.sync;
 
-import com.hazelcast.collection.ISet;
 import com.hazelcast.config.MapConfig;
 import com.hazelcast.config.QueueConfig;
-import com.hazelcast.config.SetConfig;
 import com.hazelcast.map.IMap;
 import java.util.concurrent.BlockingQueue;
-import lombok.extern.slf4j.Slf4j;
 import org.onap.cps.ncmp.impl.cache.HazelcastCacheConfig;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
@@ -34,7 +31,6 @@ import org.springframework.context.annotation.Configuration;
 /**
  * Core infrastructure of the hazelcast distributed caches for Module Sync and Data Sync use cases.
  */
-@Slf4j
 @Configuration
 public class SynchronizationCacheConfig extends HazelcastCacheConfig {
 
@@ -45,8 +41,6 @@ public class SynchronizationCacheConfig extends HazelcastCacheConfig {
     private static final MapConfig moduleSyncStartedConfig =
             createMapConfigWithTimeToLiveInSeconds("moduleSyncStartedConfig", MODULE_SYNC_STARTED_TTL_SECS);
     private static final MapConfig dataSyncSemaphoresConfig = createMapConfig("dataSyncSemaphoresConfig");
-    private static final SetConfig moduleSetTagsBeingProcessedConfig
-        = createSetConfig("moduleSetTagsBeingProcessedConfig");
 
     /**
      * Module Sync Distributed Queue Instance.
@@ -78,14 +72,4 @@ public class SynchronizationCacheConfig extends HazelcastCacheConfig {
         return getOrCreateHazelcastInstance(dataSyncSemaphoresConfig).getMap("dataSyncSemaphores");
     }
 
-    /**
-     * Collection of (new) module set tags being processed.
-     * To prevent processing on multiple threads of same tag
-     *
-     * @return set of module set tags being processed
-     */
-    @Bean
-    public ISet<String> moduleSetTagsBeingProcessed() {
-        return getOrCreateHazelcastInstance(moduleSetTagsBeingProcessedConfig).getSet("moduleSetTagsBeingProcessed");
-    }
 }
index ff190cc..953e1c7 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2024 Nordix Foundation
+ *  Copyright (C) 2021-2025 Nordix Foundation
  *  Modifications Copyright (C) 2022 Bell Canada
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
@@ -23,7 +23,10 @@ package org.onap.cps.ncmp.impl.inventory
 
 import com.hazelcast.map.IMap
 import org.onap.cps.api.CpsDataService
-import org.onap.cps.api.CpsModuleService
+import org.onap.cps.api.exceptions.AlreadyDefinedException
+import org.onap.cps.api.exceptions.CpsException
+import org.onap.cps.api.exceptions.DataNodeNotFoundException
+import org.onap.cps.api.exceptions.DataValidationException
 import org.onap.cps.ncmp.api.exceptions.DmiRequestException
 import org.onap.cps.ncmp.api.inventory.DataStoreSyncState
 import org.onap.cps.ncmp.api.inventory.models.CmHandleRegistrationResponse
@@ -36,11 +39,6 @@ import org.onap.cps.ncmp.api.inventory.models.CmHandleState
 import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle
 import org.onap.cps.ncmp.impl.inventory.sync.lcm.LcmEventsCmHandleStateHandler
 import org.onap.cps.ncmp.impl.inventory.trustlevel.TrustLevelManager
-import org.onap.cps.api.exceptions.AlreadyDefinedException
-import org.onap.cps.api.exceptions.CpsException
-import org.onap.cps.api.exceptions.DataNodeNotFoundException
-import org.onap.cps.api.exceptions.DataValidationException
-import org.onap.cps.api.exceptions.SchemaSetNotFoundException
 import spock.lang.Specification
 
 import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLES_NOT_FOUND
@@ -53,7 +51,6 @@ import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NFP_OPERATIONAL_D
 class CmHandleRegistrationServiceSpec extends Specification {
 
     def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: 'some-cm-handle-id')
-    def mockCpsModuleService = Mock(CpsModuleService)
     def mockNetworkCmProxyDataServicePropertyHandler = Mock(CmHandleRegistrationServicePropertyHandler)
     def mockInventoryPersistence = Mock(InventoryPersistence)
     def mockCmHandleQueries = Mock(CmHandleQueryService)
@@ -80,33 +77,43 @@ class CmHandleRegistrationServiceSpec extends Specification {
             def dmiRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server')
             dmiRegistration.setCreatedCmHandles([new NcmpServiceCmHandle(cmHandleId: 'cmhandle-1', publicProperties: ['publicProp1': 'value'], dmiProperties: [:])])
             dmiRegistration.setUpdatedCmHandles([new NcmpServiceCmHandle(cmHandleId: 'cmhandle-2', publicProperties: ['publicProp1': 'value'], dmiProperties: [:])])
-            dmiRegistration.setRemovedCmHandles(['cmhandle-2'])
-            dmiRegistration.setUpgradedCmHandles(new UpgradedCmHandles(cmHandles: ['cmhandle-3'], moduleSetTag: 'some-module-set-tag'))
-        and: 'cm handles are persisted'
+            dmiRegistration.setRemovedCmHandles(['cmhandle-3'])
+            dmiRegistration.setUpgradedCmHandles(new UpgradedCmHandles(cmHandles: ['cmhandle-4', 'cmhandle-5'], moduleSetTag: moduleSetTagForUpgrade))
+        and: 'cm handles 2,3 and 4 already exist in the inventory'
             mockInventoryPersistence.getYangModelCmHandles(['cmhandle-2']) >> [new YangModelCmHandle()]
-            mockInventoryPersistence.getYangModelCmHandle('cmhandle-3') >> new YangModelCmHandle(id: 'cmhandle-3', moduleSetTag: '', compositeState: new CompositeState(cmHandleState: CmHandleState.READY))
-        and: 'cm handle is in READY state'
-            mockCmHandleQueries.cmHandleHasState('cmhandle-3', CmHandleState.READY) >> true
-        and: 'cm handles is present in in-progress map'
-            mockModuleSyncStartedOnCmHandles.containsKey('cmhandle-2') >> true
+            mockInventoryPersistence.getYangModelCmHandles(['cmhandle-3']) >> [new YangModelCmHandle()]
+            mockInventoryPersistence.getYangModelCmHandle('cmhandle-4') >> new YangModelCmHandle(id: 'cmhandle-4', moduleSetTag: '', compositeState: new CompositeState(cmHandleState: CmHandleState.READY))
+        and: 'cm handle 5 also exist but already has the new module set tag (upgrade to)'
+            mockInventoryPersistence.getYangModelCmHandle('cmhandle-5') >> new YangModelCmHandle(id: 'cmhandle-5', moduleSetTag: moduleSetTagForUpgrade , compositeState: new CompositeState(cmHandleState: CmHandleState.READY))
+        and: 'all cm handles are in READY state'
+            mockCmHandleQueries.cmHandleHasState(_, CmHandleState.READY) >> true
+        and: 'cm handle to be removed is in progress map'
+            mockModuleSyncStartedOnCmHandles.containsKey('cmhandle-3') >> true
         when: 'registration is processed'
-            objectUnderTest.updateDmiRegistration(dmiRegistration)
+            def result = objectUnderTest.updateDmiRegistration(dmiRegistration)
         then: 'cm-handles are removed first'
             1 * objectUnderTest.processRemovedCmHandles(*_)
         and: 'de-registered cm handle entry is removed from in progress map'
-            1 * mockModuleSyncStartedOnCmHandles.removeAsync('cmhandle-2')
-        then: 'cm-handles are updated'
+            1 * mockModuleSyncStartedOnCmHandles.removeAsync('cmhandle-3')
+        then: 'updated cm handles are processed by the property handler service'
             1 * objectUnderTest.processUpdatedCmHandles(*_)
-            1 * mockNetworkCmProxyDataServicePropertyHandler.updateCmHandleProperties(*_) >> []
+            1 * mockNetworkCmProxyDataServicePropertyHandler.updateCmHandleProperties(*_) >> [CmHandleRegistrationResponse.createSuccessResponse('cmhandle-2')]
         then: 'cm-handles are upgraded'
             1 * objectUnderTest.processUpgradedCmHandles(*_)
+        and: 'result contains the correct cm handles for each operation'
+            assert result.createdCmHandles.cmHandle == ['cmhandle-1']
+            assert result.updatedCmHandles.cmHandle == ['cmhandle-2']
+            assert result.removedCmHandles.cmHandle == ['cmhandle-3']
+            assert result.upgradedCmHandles.cmHandle as Set == ['cmhandle-4', 'cmhandle-5'] as Set
+        where: 'upgrade with and without module set tag'
+            moduleSetTagForUpgrade << ['some tag', '']
     }
 
     def 'DMI Registration upgrade operation with upgrade node state #scenario'() {
         given: 'a registration with upgrade operation'
             def dmiRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server')
             dmiRegistration.setUpgradedCmHandles(new UpgradedCmHandles(cmHandles: ['cmhandle-3'], moduleSetTag: 'some-module-set-tag'))
-        and: 'exception while checking cm handle state'
+        and: 'cm handle has the state #cmHandleState'
             mockInventoryPersistence.getYangModelCmHandle('cmhandle-3') >> new YangModelCmHandle(id: 'cmhandle-3', moduleSetTag: '', compositeState: new CompositeState(cmHandleState: cmHandleState))
         when: 'registration is processed'
             def result = objectUnderTest.updateDmiRegistration(dmiRegistration)
@@ -134,6 +141,21 @@ class CmHandleRegistrationServiceSpec extends Specification {
             'cm handle is invalid' | new DataValidationException('some error message', 'some error details')  || '110'
     }
 
+    def 'DMI Registration upgrade with exception while updating CM-handle state'() {
+        given: 'a registration with upgrade operation'
+            def dmiRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server')
+            dmiRegistration.setUpgradedCmHandles(new UpgradedCmHandles(cmHandles: ['cmhandle-3'], moduleSetTag: 'some-module-set-tag'))
+        and: 'cm handle has the state READY'
+            mockInventoryPersistence.getYangModelCmHandle('cmhandle-3') >> new YangModelCmHandle(id: 'cmhandle-3', moduleSetTag: '', compositeState: new CompositeState(cmHandleState: CmHandleState.READY))
+        and: 'exception will occur while updating cm handle state to LOCKED for upgrade'
+            mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(_) >> { throw new RuntimeException() }
+        when: 'registration is processed'
+            def result = objectUnderTest.updateDmiRegistration(dmiRegistration)
+        then: 'upgrade operation contains expected error code'
+            assert result.upgradedCmHandles[0].status == Status.FAILURE
+            assert result.upgradedCmHandles[0].ncmpResponseStatus == UNKNOWN_ERROR
+    }
+
     def 'Create CM-handle Validation: Registration with valid Service names: #scenario'() {
         given: 'a registration '
             def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: dmiPlugin, dmiModelPlugin: dmiModelPlugin,
@@ -276,18 +298,15 @@ class CmHandleRegistrationServiceSpec extends Specification {
             assert response.updatedCmHandles.containsAll(updateOperationResponse)
     }
 
-    def 'Remove CmHandle Successfully: #scenario'() {
-        given: 'a registration'
+    def 'Remove CmHandle Successfully'() {
+        given: 'a registration update to delete a cm handle'
             def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server', removedCmHandles: ['cmhandle'])
-        and: '#scenario'
-            mockCpsModuleService.deleteSchemaSetsWithCascade(_, ['cmhandle']) >>  { if (!schemaSetExist) { throw new SchemaSetNotFoundException('', '') } }
-        when: 'registration is updated to delete cmhandle'
+        when: 'the registration is updated'
             def response = objectUnderTest.updateDmiRegistration(dmiPluginRegistration)
-        then: 'the cmHandle state is updated to "DELETING"'
-            1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(_) >>
-                { args -> args[0].values()[0] == CmHandleState.DELETING }
-        then: 'method to delete relevant schema set is called once'
-            1 * mockInventoryPersistence.deleteSchemaSetsWithCascade(_)
+        then: 'the cmHandle state is set to "DELETING"'
+            1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(_) >> { args -> args[0].values()[0] == CmHandleState.DELETING }
+        then: 'method to delete anchors is called once'
+            1 * mockInventoryPersistence.deleteAnchors(_)
         and: 'method to delete relevant list/list element is called once'
             1 * mockInventoryPersistence.deleteDataNodes(_)
         and: 'successful response is received'
@@ -297,14 +316,7 @@ class CmHandleRegistrationServiceSpec extends Specification {
                 assert it.cmHandle == 'cmhandle'
             }
         and: 'the cmHandle state is updated to "DELETED"'
-            1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(_) >>
-                { args -> args[0].values()[0] == CmHandleState.DELETED }
-        and: 'No cm handles state updates for "upgraded cm handles"'
-            1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch([:])
-        where:
-            scenario                                            | schemaSetExist
-            'schema-set exists and can be deleted successfully' | true
-            'schema-set does not exist'                         | false
+            1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(_) >>  { args -> args[0].values()[0] == CmHandleState.DELETED }
     }
 
     def 'Remove CmHandle: Partial Success'() {
@@ -314,10 +326,11 @@ class CmHandleRegistrationServiceSpec extends Specification {
         and: 'cm handles to be deleted in the progress map'
             mockModuleSyncStartedOnCmHandles.containsKey("cmhandle1") >> true
             mockModuleSyncStartedOnCmHandles.containsKey("cmhandle3") >> true
-        and: 'cm-handle deletion fails on batch'
-            mockInventoryPersistence.deleteDataNodes(_) >> { throw new RuntimeException("Failed") }
-        and: 'cm-handle deletion is successful for 1st and 3rd; failed for 2nd'
-            mockInventoryPersistence.deleteDataNode("/dmi-registry/cm-handles[@id='cmhandle2']") >> { throw new RuntimeException("Failed") }
+        and: 'delete fails for batch. Retry only fails for and cm handle 2'
+            mockInventoryPersistence.deleteDataNodes(_) >> { throw new RuntimeException("Batch Failed") }
+                                                        >> { /* cm handle 1 is OK */ }
+                                                        >> { throw new RuntimeException("Cm handle 2 Failed")}
+                                                        >> { /* cm handle 3 is OK */ }
         when: 'registration is updated to delete cmhandles'
             def response = objectUnderTest.updateDmiRegistration(dmiPluginRegistration)
         then: 'the cmHandle states are all updated to "DELETING"'
@@ -343,7 +356,7 @@ class CmHandleRegistrationServiceSpec extends Specification {
             with(response.removedCmHandles[1]) {
                 assert it.status == Status.FAILURE
                 assert it.ncmpResponseStatus == UNKNOWN_ERROR
-                assert it.errorText == 'Failed'
+                assert it.errorText == 'Cm handle 2 Failed'
                 assert it.cmHandle == 'cmhandle2'
             }
         and: 'the cmHandle state is updated to DELETED for 1st and 3rd'
@@ -351,40 +364,11 @@ class CmHandleRegistrationServiceSpec extends Specification {
                 assert it.size() == 2
                 assert it.every { entry -> entry.value == CmHandleState.DELETED }
             })
-        and: 'No cm handles state updates for "upgraded cm handles"'
-            1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch([:])
-    }
-
-    def 'Remove CmHandle Error Handling: Schema Set Deletion failed'() {
-        given: 'a registration'
-            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server',
-                removedCmHandles: ['cmhandle'])
-        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.updateDmiRegistration(dmiPluginRegistration)
-        then: 'no exception is thrown'
-            noExceptionThrown()
-        and: 'cm-handle is not deleted'
-            0 * mockInventoryPersistence.deleteDataNodes(_)
-        and: 'the cmHandle state is not updated to "DELETED"'
-            0 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch([yangModelCmHandle: CmHandleState.DELETED])
-        and: 'a failure response is received'
-            assert response.removedCmHandles.size() == 1
-            with(response.removedCmHandles[0]) {
-                assert it.status == Status.FAILURE
-                assert it.cmHandle == 'cmhandle'
-                assert it.errorText == 'Failed'
-                assert it.ncmpResponseStatus == UNKNOWN_ERROR
-            }
     }
 
     def 'Remove CmHandle Error Handling: #scenario'() {
         given: 'a registration'
-            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server',
-                removedCmHandles: ['cmhandle'])
+            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server', removedCmHandles: ['cmhandle'])
         and: 'cm-handle deletion fails on batch'
             mockInventoryPersistence.deleteDataNodes(_) >> { throw deleteListElementException }
         and: 'cm-handle deletion fails on individual delete'
@@ -408,7 +392,7 @@ class CmHandleRegistrationServiceSpec extends Specification {
         'an unexpected exception'    | new RuntimeException('Failed')            || UNKNOWN_ERROR        | 'Failed'
     }
 
-    def 'Set Cm Handle Data Sync Enabled Flag where data sync flag is  #scenario'() {
+    def 'Set Cm Handle Data Sync Enabled Flag where data sync flag is #scenario'() {
         given: 'an existing cm handle composite state'
             def compositeState = new CompositeState(cmHandleState: CmHandleState.READY, dataSyncEnabled: initialDataSyncEnabledFlag,
                 dataStores: CompositeState.DataStores.builder()
@@ -429,7 +413,7 @@ class CmHandleRegistrationServiceSpec extends Specification {
             saveCmHandleStateExpectedNumberOfInvocations * mockInventoryPersistence.saveCmHandleState('some-cm-handle-id', compositeState)
         where: 'the following data sync enabled flag is used'
             scenario                                              | dataSyncEnabledFlag | initialDataSyncEnabledFlag | initialDataSyncState               || expectedDataStoreSyncState         | deleteDataNodeExpectedNumberOfInvocation | saveCmHandleStateExpectedNumberOfInvocations
-            'enabled'                                             | true                | false                      | DataStoreSyncState.NONE_REQUESTED || DataStoreSyncState.UNSYNCHRONIZED | 0 | 1
+            'enabled'                                             | true                | false                      | DataStoreSyncState.NONE_REQUESTED  || DataStoreSyncState.UNSYNCHRONIZED  | 0                                        | 1
             'disabled'                                            | false               | true                       | DataStoreSyncState.UNSYNCHRONIZED  || DataStoreSyncState.NONE_REQUESTED  | 0                                        | 1
             'disabled where sync-state is currently SYNCHRONIZED' | false               | true                       | DataStoreSyncState.SYNCHRONIZED    || DataStoreSyncState.NONE_REQUESTED  | 1                                        | 1
             'is set to existing flag state'                       | true                | true                       | DataStoreSyncState.UNSYNCHRONIZED  || DataStoreSyncState.UNSYNCHRONIZED  | 0                                        | 0
@@ -448,6 +432,4 @@ class CmHandleRegistrationServiceSpec extends Specification {
             0 * mockInventoryPersistence.saveCmHandleState(_, _)
     }
 
-
-
 }
index 619da70..d8d92e9 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2022-2024 Nordix Foundation
+ *  Copyright (C) 2022-2025 Nordix Foundation
  *  Modifications Copyright (C) 2022 Bell Canada
  *  Modifications Copyright (C) 2024 TechMahindra Ltd.
  *  ================================================================================
 package org.onap.cps.ncmp.impl.inventory
 
 import com.fasterxml.jackson.databind.ObjectMapper
+import java.time.OffsetDateTime
+import java.time.ZoneOffset
+import java.time.format.DateTimeFormatter
 import org.onap.cps.api.CpsAnchorService
 import org.onap.cps.api.CpsDataService
 import org.onap.cps.api.CpsModuleService
 import org.onap.cps.api.exceptions.DataNodeNotFoundException
 import org.onap.cps.api.exceptions.DataValidationException
+import org.onap.cps.api.model.DataNode
+import org.onap.cps.api.model.ModuleDefinition
+import org.onap.cps.api.model.ModuleReference
+import org.onap.cps.api.parameters.FetchDescendantsOption
 import org.onap.cps.impl.utils.CpsValidator
 import org.onap.cps.ncmp.api.exceptions.CmHandleNotFoundException
 import org.onap.cps.ncmp.api.inventory.models.CompositeState
 import org.onap.cps.ncmp.api.inventory.models.CmHandleState
 import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle
 import org.onap.cps.ncmp.impl.utils.YangDataConverter
-import org.onap.cps.api.parameters.CascadeDeleteAllowed
-import org.onap.cps.api.parameters.FetchDescendantsOption
-import org.onap.cps.api.model.DataNode
-import org.onap.cps.api.model.ModuleDefinition
-import org.onap.cps.api.model.ModuleReference
 import org.onap.cps.utils.ContentType
 import org.onap.cps.utils.JsonObjectMapper
 import spock.lang.Shared
 import spock.lang.Specification
 
-import java.time.OffsetDateTime
-import java.time.ZoneOffset
-import java.time.format.DateTimeFormatter
-
+import static org.onap.cps.api.parameters.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
+import static org.onap.cps.api.parameters.FetchDescendantsOption.OMIT_DESCENDANTS
 import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME
 import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR
 import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT
 import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME
 import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NO_TIMESTAMP
-import static org.onap.cps.api.parameters.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
-import static org.onap.cps.api.parameters.FetchDescendantsOption.OMIT_DESCENDANTS
 
 class InventoryPersistenceImplSpec extends Specification {
 
@@ -72,8 +70,7 @@ class InventoryPersistenceImplSpec extends Specification {
 
     def mockYangDataConverter = Mock(YangDataConverter)
 
-    def objectUnderTest = new InventoryPersistenceImpl(spiedJsonObjectMapper, mockCpsDataService, mockCpsModuleService,
-            mockCpsValidator, mockCpsAnchorService, mockCmHandleQueries)
+    def objectUnderTest = new InventoryPersistenceImpl(mockCpsValidator, spiedJsonObjectMapper, mockCpsAnchorService, mockCpsModuleService, mockCpsDataService, mockCmHandleQueries)
 
     def formattedDateAndTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ")
             .format(OffsetDateTime.of(2022, 12, 31, 20, 30, 40, 1, ZoneOffset.UTC))
@@ -294,24 +291,6 @@ class InventoryPersistenceImplSpec extends Specification {
             1 * mockCpsDataService.deleteListOrListElement(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,'sample xPath',null)
     }
 
-    def 'Delete schema set with a valid schema set name'() {
-        when: 'the method to delete schema set is called with valid schema set name'
-            objectUnderTest.deleteSchemaSetWithCascade('validSchemaSetName')
-        then: 'the module service to delete schemaSet is invoked once'
-            1 * mockCpsModuleService.deleteSchemaSet(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'validSchemaSetName', CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED)
-        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_DATASTORE_DATASPACE_NAME, ['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 67cc4ed..f8adfe5 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2022-2024 Nordix Foundation
+ *  Copyright (C) 2022-2025 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
 
 package org.onap.cps.ncmp.impl.inventory.sync
 
-import com.hazelcast.collection.ISet
 import org.onap.cps.api.CpsAnchorService
 import org.onap.cps.api.CpsDataService
 import org.onap.cps.api.CpsModuleService
-import org.onap.cps.ncmp.api.exceptions.NcmpException
+import org.onap.cps.api.exceptions.AlreadyDefinedException
+import org.onap.cps.api.exceptions.DuplicatedYangResourceException
+import org.onap.cps.api.model.ModuleReference
 import org.onap.cps.ncmp.api.inventory.models.CompositeStateBuilder
 import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle
 import org.onap.cps.ncmp.impl.inventory.CmHandleQueryService
 import org.onap.cps.ncmp.api.inventory.models.CmHandleState
 import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle
-import org.onap.cps.ncmp.impl.inventory.sync.ModuleSyncService.ModuleDelta
-import org.onap.cps.api.parameters.CascadeDeleteAllowed
-import org.onap.cps.api.exceptions.SchemaSetNotFoundException
-import org.onap.cps.api.model.ModuleReference
 import org.onap.cps.utils.JsonObjectMapper
 import spock.lang.Specification
 
@@ -48,18 +45,8 @@ class ModuleSyncServiceSpec extends Specification {
     def mockCmHandleQueries = Mock(CmHandleQueryService)
     def mockCpsDataService = Mock(CpsDataService)
     def mockJsonObjectMapper = Mock(JsonObjectMapper)
-    def mockModuleSetTagsBeingProcessed = Mock(ISet<String>);
 
-    def objectUnderTest = new ModuleSyncService(mockDmiModelOperations, mockCpsModuleService, mockCpsDataService, mockCpsAnchorService, mockJsonObjectMapper, mockModuleSetTagsBeingProcessed)
-
-    def expectedDataspaceName = NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME
-
-    def setup() {
-        // Allow tags for al test except 'duplicate-processing-tag' to be added to processing semaphore
-        mockModuleSetTagsBeingProcessed.add('new-tag') >> true
-        mockModuleSetTagsBeingProcessed.add('same-tag') >> true
-        mockModuleSetTagsBeingProcessed.add('cached-tag') >> true
-    }
+    def objectUnderTest = new ModuleSyncService(mockDmiModelOperations, mockCpsModuleService, mockCpsDataService, mockCpsAnchorService, mockJsonObjectMapper)
 
     def 'Sync models for a NEW cm handle using module set tags: #scenario.'() {
         given: 'a cm handle to be synced'
@@ -71,26 +58,24 @@ class ModuleSyncServiceSpec extends Specification {
             mockDmiModelOperations.getNewYangResourcesFromDmi(yangModelCmHandle, identifiedNewModuleReferences) >> newModuleNameContentToMap
         and: 'the module service identifies #identifiedNewModuleReferences.size() new modules'
             mockCpsModuleService.identifyNewModuleReferences(moduleReferences) >> identifiedNewModuleReferences
-        and: 'the service returns a list of module references when queried with the specified attributes'
-            mockCpsModuleService.getModuleReferencesByAttribute(*_) >> existingModuleReferences
         when: 'module sync is triggered'
             objectUnderTest.syncAndCreateSchemaSetAndAnchor(yangModelCmHandle)
         then: 'create schema set from module is invoked with correct parameters'
-            1 * mockCpsModuleService.createSchemaSetFromModules(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'ch-1', newModuleNameContentToMap, moduleReferences)
+            1 * mockCpsModuleService.createSchemaSetFromModules(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, expectedSchemaSetName, newModuleNameContentToMap, moduleReferences)
         and: 'anchor is created with the correct parameters'
-            1 * mockCpsAnchorService.createAnchor(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'ch-1', 'ch-1')
+            1 * mockCpsAnchorService.createAnchor(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, expectedSchemaSetName, 'ch-1')
         where: 'the following parameters are used'
-            scenario                  | identifiedNewModuleReferences         | newModuleNameContentToMap     | moduleSetTag | existingModuleReferences
-            'one new module, new tag' | [new ModuleReference('module1', '1')] | [module1: 'some yang source'] | ''           | []
-            'no new module, new tag'  | []                                    | [:]                           | 'new-tag'    | []
-            'same tag'                | []                                    | [:]                           | 'same-tag'   | [new ModuleReference('module1', '1'), new ModuleReference('module2', '2')]
+            scenario                  | identifiedNewModuleReferences         | newModuleNameContentToMap     | moduleSetTag | existingModuleReferences                                                   || expectedSchemaSetName
+            'one new module, new tag' | [new ModuleReference('module1', '1')] | [module1: 'some yang source'] | ''           | []                                                                         || 'ch-1'
+            'no new module, new tag'  | []                                    | [:]                           | 'new-tag'    | []                                                                         || 'new-tag'
+            'same tag'                | []                                    | [:]                           | 'same-tag'   | [new ModuleReference('module1', '1'), new ModuleReference('module2', '2')] || 'same-tag'
     }
 
-    def 'Attempt Sync models for a cm handle with exception and #scenario module set tag'() {
+    def 'Attempt Sync models for a cm handle with exception and #scenario module set tag.'() {
         given: 'a cm handle to be synced'
             def yangModelCmHandle = createAdvisedCmHandle(moduleSetTag)
-        and: 'the service returns a list of module references when queried with the specified attributes'
-            mockCpsModuleService.getModuleReferencesByAttribute(*_) >> [new ModuleReference('module1', '1')]
+        and: 'dmi returns no new yang resources'
+            mockDmiModelOperations.getNewYangResourcesFromDmi(*_) >> [:]
         and: 'exception occurs when trying to store result'
             def testException = new RuntimeException('test')
             mockCpsModuleService.createSchemaSetFromModules(*_) >> { throw testException }
@@ -99,130 +84,75 @@ class ModuleSyncServiceSpec extends Specification {
         then: 'the same exception is thrown up'
             def exceptionThrown = thrown(Exception)
             assert testException == exceptionThrown
-        and: 'module set tag is removed from processing semaphores only when needed'
-            expectedCallsToRemoveTag * mockModuleSetTagsBeingProcessed.remove('new-tag')
         where: 'following module set tags are used'
-            scenario  | moduleSetTag || expectedCallsToRemoveTag
-            'with'    | 'new-tag'    || 1
-            'without' | ' '          || 0
-    }
-
-    def 'Sync models for a cm handle with previously cached module set tag.'() {
-        given: 'a cm handle to be synced'
-            def yangModelCmHandle = createAdvisedCmHandle('cached-tag')
-        and: 'The module set tag exists in the private cache'
-            def moduleReferences = [ new ModuleReference('module1','1') ]
-            def cachedModuleDelta = new ModuleDelta(moduleReferences, [:])
-            objectUnderTest.privateModuleSetCache.put('cached-tag', cachedModuleDelta)
-        when: 'module sync is triggered'
-            objectUnderTest.syncAndCreateSchemaSetAndAnchor(yangModelCmHandle)
-        then: 'create schema set from module is invoked with correct parameters'
-            1 * mockCpsModuleService.createSchemaSetFromModules(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'ch-1', [:], moduleReferences)
-        and: 'anchor is created with the correct parameters'
-            1 * mockCpsAnchorService.createAnchor(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'ch-1', 'ch-1')
+            scenario  | moduleSetTag
+            'with'    | 'new-tag'
+            'without' | ''
     }
 
-    def 'Attempt to sync using a module set tag already being processed by a different instance or thread.'() {
+    def 'Attempt Sync models for a cm handle with existing schema set (#exception).'() {
         given: 'a cm handle to be synced'
-            def yangModelCmHandle = createAdvisedCmHandle('duplicateTag')
-        and: 'The module set tag already exists in the processing semaphore set'
-            mockModuleSetTagsBeingProcessed.add('duplicate-processing-tag') > false
+            def yangModelCmHandle = createAdvisedCmHandle('existing tag')
+        and: 'dmi returns no new yang resources'
+            mockDmiModelOperations.getNewYangResourcesFromDmi(*_) >> [:]
+        and: 'already defined exception occurs when creating schema (existing)'
+            mockCpsModuleService.createSchemaSetFromModules(*_) >> { throw exception  }
         when: 'module sync is triggered'
             objectUnderTest.syncAndCreateSchemaSetAndAnchor(yangModelCmHandle)
-        then: 'a ncmp exception is thrown with the relevant details'
-            def exceptionThrown = thrown(NcmpException)
-            assert exceptionThrown.message.contains('duplicateTag')
-            assert exceptionThrown.details.contains('duplicateTag')
-            assert exceptionThrown.details.contains('ch-1')
+        then: 'no exception is thrown up'
+            noExceptionThrown()
+        where: 'following exceptions occur'
+            exception << [ AlreadyDefinedException.forSchemaSet('', '', null),
+                           new DuplicatedYangResourceException('', '', null) ]
     }
 
-    def 'Upgrade model for an existing cm handle with Module Set Tag where the modules are #scenario'() {
-        given: 'a cm handle being upgraded to module set tag: tag-1'
+    def 'Model upgrade without using Module Set Tags (legacy) where the modules are in database.'() {
+        given: 'a cm handle being upgraded without using module set tags'
             def ncmpServiceCmHandle = new NcmpServiceCmHandle()
-            ncmpServiceCmHandle.setCompositeState(new CompositeStateBuilder().withLockReason(MODULE_UPGRADE, 'Upgrade to ModuleSetTag: tag-1').build())
+            ncmpServiceCmHandle.setCompositeState(new CompositeStateBuilder().withLockReason(MODULE_UPGRADE, '').build())
             def dmiServiceName = 'some service name'
             ncmpServiceCmHandle.cmHandleId = 'upgraded-ch'
-            def yangModelCmHandle = YangModelCmHandle.toYangModelCmHandle(dmiServiceName, '', '', ncmpServiceCmHandle,'tag-1', '', '')
-        and: 'some module references'
-            def moduleReferences =  [ new ModuleReference('module1','1') ]
+            def yangModelCmHandle = YangModelCmHandle.toYangModelCmHandle(dmiServiceName, '', '', ncmpServiceCmHandle,'', '', '')
         and: 'DMI operations returns some module references for upgraded cm handle'
+            def moduleReferences =  [ new ModuleReference('module1','1') ]
             mockDmiModelOperations.getModuleReferences(yangModelCmHandle) >> moduleReferences
             mockDmiModelOperations.getNewYangResourcesFromDmi(_, []) >> [:]
-        and: 'none of these module references are new (unknown to the system)'
+        and: 'none of these module references are new (all already known to the system)'
             mockCpsModuleService.identifyNewModuleReferences(_) >> []
-        and: 'CPS-Core returns list of existing module resources for TBD'
-            mockCpsModuleService.getYangResourcesModuleReferences(*_) >> [ new ModuleReference('module1','1') ]
-        and: 'the service returns a list of module references when queried with the specified attributes'
-            mockCpsModuleService.getModuleReferencesByAttribute(*_) >> existingModuleReferences
-        and: 'the other cm handle is a state ready'
-            mockCmHandleQueries.cmHandleHasState('otherId', CmHandleState.READY) >> true
         when: 'module sync is triggered'
             objectUnderTest.syncAndUpgradeSchemaSet(yangModelCmHandle)
         then: 'update schema set from module is invoked for the upgraded cm handle'
             1 * mockCpsModuleService.upgradeSchemaSetFromModules(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'upgraded-ch', [:], moduleReferences)
-        and: 'create schema set from module is not invoked for the upgraded cm handle'
-            0 * mockCpsModuleService.createSchemaSetFromModules(*_)
         and: 'No anchor is created for the upgraded cm handle'
             0 * mockCpsAnchorService.createAnchor(*_)
-        where: 'the following parameters are used'
-            scenario      | existingModuleReferences
-            'new'         | []
-            'in database' | [new ModuleReference('module1', '1')]
     }
 
-    def 'upgrade model for an existing cm handle'() {
+    def 'Model upgrade using to existing Module Set Tag'() {
         given: 'a cm handle that is ready but locked for upgrade'
             def ncmpServiceCmHandle = new NcmpServiceCmHandle()
-            ncmpServiceCmHandle.setCompositeState(new CompositeStateBuilder()
-                .withLockReason(MODULE_UPGRADE, 'Upgrade to ModuleSetTag: targetModuleSetTag').build())
+            ncmpServiceCmHandle.setCompositeState(new CompositeStateBuilder().withLockReason(MODULE_UPGRADE, 'Upgrade to ModuleSetTag: ' + tagTo).build())
             ncmpServiceCmHandle.setCmHandleId('cmHandleId-1')
-            def yangModelCmHandle = YangModelCmHandle.toYangModelCmHandle('some service name', '', '', ncmpServiceCmHandle, 'targetModuleSetTag', '', '')
+            def yangModelCmHandle = YangModelCmHandle.toYangModelCmHandle('some service name', '', '', ncmpServiceCmHandle, tagFrom, '', '')
             mockCmHandleQueries.cmHandleHasState('cmHandleId-1', CmHandleState.READY) >> true
-        and: 'the module service returns some module references'
-            def moduleReferences = [new ModuleReference('module1', '1'), new ModuleReference('module2', '2')]
-            mockCpsModuleService.getYangResourcesModuleReferences(*_)>> moduleReferences
-        and: 'the service returns a list of module references when queried with the specified attributes'
-            mockCpsModuleService.getModuleReferencesByAttribute(*_) >> moduleReferences
+        and: 'the module tag (schemaset) exists is #schemaExists'
+            mockCpsModuleService.schemaSetExists(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, tagTo) >> schemaExists
+        and: 'DMI operations returns some module references for upgraded cm handle'
+            def moduleReferences =  [ new ModuleReference('module1','1') ]
+            expectedCallsToDmi * mockDmiModelOperations.getModuleReferences(yangModelCmHandle) >> moduleReferences
+        and: 'dmi returns no new yang resources'
+            mockDmiModelOperations.getNewYangResourcesFromDmi(*_) >> [:]
+        and: 'none of these module references are new (all already known to the system)'
+            expectedCallsToModuleService * mockCpsModuleService.identifyNewModuleReferences(_) >> []
         when: 'module upgrade is triggered'
             objectUnderTest.syncAndUpgradeSchemaSet(yangModelCmHandle)
-        then: 'the upgrade is delegated to the module service (with the correct parameters)'
-            1 * mockCpsModuleService.upgradeSchemaSetFromModules(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'cmHandleId-1', Collections.emptyMap(), moduleReferences)
-    }
-
-    def 'Delete Schema Set for CmHandle'() {
-        when: 'delete schema set if exists is called'
-            objectUnderTest.deleteSchemaSetIfExists('some-cmhandle-id')
-        then: 'the module service is invoked to delete the correct schema set'
-            1 * mockCpsModuleService.deleteSchemaSet(expectedDataspaceName, 'some-cmhandle-id', CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED)
-    }
-
-    def 'Delete a non-existing Schema Set for CmHandle' () {
-        given: 'the DB throws an exception because its Schema Set does not exist'
-           mockCpsModuleService.deleteSchemaSet(*_) >> { throw new SchemaSetNotFoundException('some-dataspace-name', 'some-cmhandle-id') }
-        when: 'delete schema set if exists is called'
-            objectUnderTest.deleteSchemaSetIfExists('some-cmhandle-id')
-        then: 'the exception from the DB is ignored; there are no exceptions'
-            noExceptionThrown()
-    }
-
-    def 'Delete Schema Set for CmHandle with other exception' () {
-        given: 'an exception other than SchemaSetNotFoundException is thrown'
-            UnsupportedOperationException unsupportedOperationException = new UnsupportedOperationException();
-            1 * mockCpsModuleService.deleteSchemaSet(*_) >> { throw unsupportedOperationException }
-        when: 'delete schema set if exists is called'
-            objectUnderTest.deleteSchemaSetIfExists('some-cmhandle-id')
-        then: 'an exception is thrown'
-            def result = thrown(UnsupportedOperationException)
-            result == unsupportedOperationException
-    }
-
-    def 'Clear module set cache.'() {
-        given: 'something in the module set cache'
-            objectUnderTest.privateModuleSetCache.put('test',new ModuleDelta([],[:]))
-        when: 'the cache is cleared'
-            objectUnderTest.clearPrivateModuleSetCache()
-        then: 'the cache is empty'
-            objectUnderTest.privateModuleSetCache.isEmpty()
+        then: 'the upgrade is delegated to the anchor service (with the correct parameters) except when new tag is blank'
+            expectedCallsToAnchorService * mockCpsAnchorService.updateAnchorSchemaSet(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'cmHandleId-1', tagTo)
+        where: 'with or without from tag'
+            scenario                         | schemaExists | tagFrom  | tagTo  || expectedCallsToDmi | expectedCallsToModuleService | expectedCallsToAnchorService
+            'from no tag to existing tag'    | true         | ''       | 'tagTo'|| 0                  | 0                            | 1
+            'from tag to other existing tag' | true         | 'oldTag' | 'tagTo'|| 0                  | 0                            | 1
+            'to new tag'                     | false        | 'oldTag' | 'tagTo'|| 1                  | 1                            | 1
+            'to NO tag'                      | true         | 'oldTag' | ''     || 1                  | 1                            | 0
     }
 
     def createAdvisedCmHandle(moduleSetTag) {
index 556ed0b..92f4b38 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2022-2024 Nordix Foundation
+ *  Copyright (C) 2022-2025 Nordix Foundation
  *  Modifications Copyright (C) 2022 Bell Canada
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
@@ -29,15 +29,16 @@ import com.hazelcast.config.Config
 import com.hazelcast.core.Hazelcast
 import com.hazelcast.instance.impl.HazelcastInstanceFactory
 import com.hazelcast.map.IMap
+import org.onap.cps.api.exceptions.DataNodeNotFoundException
+import org.onap.cps.ncmp.api.inventory.models.CmHandleState
 import org.onap.cps.ncmp.api.inventory.models.CompositeState
 import org.onap.cps.ncmp.api.inventory.models.CompositeStateBuilder
 import org.onap.cps.ncmp.impl.inventory.InventoryPersistence
-import org.onap.cps.ncmp.api.inventory.models.CmHandleState
 import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle
 import org.onap.cps.ncmp.impl.inventory.sync.lcm.LcmEventsCmHandleStateHandler
-import org.onap.cps.api.exceptions.DataNodeNotFoundException
 import org.slf4j.LoggerFactory
 import spock.lang.Specification
+
 import java.util.concurrent.atomic.AtomicInteger
 
 import static org.onap.cps.ncmp.api.inventory.models.LockReasonCategory.MODULE_SYNC_FAILED
@@ -87,10 +88,7 @@ class ModuleSyncTasksSpec extends Specification {
             mockInventoryPersistence.getYangModelCmHandle('cm-handle-2') >> cmHandle2
         when: 'module sync poll is executed'
             objectUnderTest.performModuleSync(['cm-handle-1', 'cm-handle-2'], batchCount)
-        then: 'module sync service deletes schemas set of each cm handle if it already exists'
-            1 * mockModuleSyncService.deleteSchemaSetIfExists('cm-handle-1')
-            1 * mockModuleSyncService.deleteSchemaSetIfExists('cm-handle-2')
-        and: 'module sync service is invoked for each cm handle'
+        then: 'module sync service is invoked for each cm handle'
             1 * mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(_) >> { args -> assert args[0].id == 'cm-handle-1' }
             1 * mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(_) >> { args -> assert args[0].id == 'cm-handle-2' }
         and: 'the state handler is called for the both cm handles'
index 3213e5d..7db9e5a 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * ============LICENSE_START========================================================
- *  Copyright (C) 2022-2024 Nordix Foundation
+ *  Copyright (C) 2022-2025 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -20,7 +20,6 @@
 
 package org.onap.cps.ncmp.impl.inventory.sync
 
-import com.hazelcast.collection.ISet
 import com.hazelcast.config.Config
 import com.hazelcast.core.Hazelcast
 import com.hazelcast.map.IMap
@@ -45,9 +44,6 @@ class SynchronizationCacheConfigSpec extends Specification {
     @Autowired
     IMap<String, Boolean> dataSyncSemaphores
 
-    @Autowired
-    ISet<String> moduleSetTagsBeingProcessed
-
     def cleanupSpec() {
         Hazelcast.getHazelcastInstanceByName('cps-and-ncmp-hazelcast-instance-test-config').shutdown()
     }
@@ -59,8 +55,6 @@ class SynchronizationCacheConfigSpec extends Specification {
             assert null != moduleSyncStartedOnCmHandles
         and: 'system is able to create an instance of a map to hold data sync semaphores'
             assert null != dataSyncSemaphores
-        and: 'system is able to create an instance of a set to hold module set tags being processed'
-            assert null != moduleSetTagsBeingProcessed
         and: 'there is only one instance with the correct name'
             assert Hazelcast.allHazelcastInstances.size() == 1
             assert Hazelcast.allHazelcastInstances.name[0] == 'cps-and-ncmp-hazelcast-instance-test-config'
index 023e76e..cf9fb02 100755 (executable)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2020-2024 Nordix Foundation
+ *  Copyright (C) 2020-2025 Nordix Foundation
  *  Modifications Copyright (C) 2020-2022 Bell Canada.
  *  Modifications Copyright (C) 2021 Pantheon.tech
  *  Modifications Copyright (C) 2022 TechMahindra Ltd.
@@ -173,6 +173,12 @@ public class CpsModulePersistenceServiceImpl implements CpsModulePersistenceServ
         }
     }
 
+    @Override
+    public boolean schemaSetExists(final String dataspaceName, final String schemaSetName) {
+        final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
+        return schemaSetRepository.existsByDataspaceAndName(dataspaceEntity, schemaSetName);
+    }
+
     @Override
     public Collection<SchemaSet> getSchemaSetsByDataspaceName(final String dataspaceName) {
         final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
@@ -217,7 +223,6 @@ public class CpsModulePersistenceServiceImpl implements CpsModulePersistenceServ
         schemaSetRepository.deleteByDataspaceAndNameIn(dataspaceEntity, schemaSetNames);
     }
 
-
     @Override
     @Transactional
     public void updateSchemaSetFromModules(final String dataspaceName, final String schemaSetName,
@@ -232,8 +237,9 @@ public class CpsModulePersistenceServiceImpl implements CpsModulePersistenceServ
 
     @Override
     @Transactional
-    public void deleteUnusedYangResourceModules() {
-        yangResourceRepository.deleteOrphans();
+    public void deleteAllUnusedYangModuleData() {
+        schemaSetRepository.deleteOrphanedSchemaSets();
+        yangResourceRepository.deleteOrphanedYangResources();
     }
 
     @Override
@@ -242,15 +248,6 @@ public class CpsModulePersistenceServiceImpl implements CpsModulePersistenceServ
         return moduleReferenceRepository.identifyNewModuleReferences(moduleReferencesToCheck);
     }
 
-    @Override
-    public Collection<ModuleReference> getModuleReferencesByAttribute(final String dataspaceName,
-                                                                      final String anchorName,
-                                                                      final Map<String, String> parentAttributes,
-                                                                      final Map<String, String> childAttributes) {
-        return moduleReferenceRepository.findModuleReferences(dataspaceName, anchorName, parentAttributes,
-                childAttributes);
-    }
-
     private Set<YangResourceEntity> synchronizeYangResources(
         final Map<String, String> moduleReferenceNameToContentMap) {
         final Map<String, YangResourceEntity> checksumToEntityMap = moduleReferenceNameToContentMap.entrySet().stream()
index 85d0e43..c91e8de 100644 (file)
@@ -1,6 +1,6 @@
 /*-
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2022-2024 Nordix Foundation.
+ *  Copyright (C) 2022-2025 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,7 +21,6 @@
 package org.onap.cps.ri.repository;
 
 import java.util.Collection;
-import java.util.Map;
 import org.onap.cps.api.model.ModuleReference;
 
 /**
@@ -31,7 +30,4 @@ public interface ModuleReferenceQuery {
 
     Collection<ModuleReference> identifyNewModuleReferences(final Collection<ModuleReference> moduleReferencesToCheck);
 
-    Collection<ModuleReference> findModuleReferences(final String dataspaceName, final String anchorName,
-                                                     final Map<String, String> parentAttributes,
-                                                     final Map<String, String> childAttributes);
 }
index b98696c..7611dcd 100644 (file)
@@ -1,6 +1,6 @@
 /*-
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2022 Nordix Foundation.
+ *  Copyright (C) 2022-2025 Nordix Foundation.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 
 package org.onap.cps.ri.repository;
 
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 import jakarta.persistence.EntityManager;
 import jakarta.persistence.PersistenceContext;
-import jakarta.persistence.Query;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
 import lombok.RequiredArgsConstructor;
 import lombok.SneakyThrows;
-import lombok.extern.slf4j.Slf4j;
 import org.onap.cps.api.model.ModuleReference;
 import org.springframework.transaction.annotation.Transactional;
 
-@Slf4j
 @Transactional
 @RequiredArgsConstructor
 public class ModuleReferenceRepositoryImpl implements ModuleReferenceQuery {
@@ -70,104 +64,14 @@ public class ModuleReferenceRepositoryImpl implements ModuleReferenceQuery {
         return identifyNewModuleReferencesForCmHandle(tempTableName);
     }
 
-    /**
-     * Finds module references based on specified dataspace, anchor, and attribute filters.
-     * This method constructs and executes a SQL query to retrieve module references. The query applies filters to
-     * parent and child fragments using the provided attribute maps. The `parentAttributes` are used to filter
-     * parent fragments, while `childAttributes` filter child fragments.
-     *
-     * @param dataspaceName    the name of the dataspace to filter on.
-     * @param anchorName       the name of the anchor to filter on.
-     * @param parentAttributes a map of attributes for filtering parent fragments.
-     * @param childAttributes  a map of attributes for filtering child fragments.
-     * @return a collection of {@link ModuleReference} objects that match the specified filters.
-     */
-    @Transactional
-    @SuppressWarnings("unchecked")
-    @Override
-    public Collection<ModuleReference> findModuleReferences(final String dataspaceName, final String anchorName,
-                                                            final Map<String, String> parentAttributes,
-                                                            final Map<String, String> childAttributes) {
-
-        final String parentFragmentWhereClause = buildWhereClause(childAttributes, "parentFragment");
-        final String childFragmentWhereClause = buildWhereClause(parentAttributes, "childFragment");
-
-        final String moduleReferencesSqlQuery = buildModuleReferencesSqlQuery(parentFragmentWhereClause,
-                childFragmentWhereClause);
-
-        final Query query = entityManager.createNativeQuery(moduleReferencesSqlQuery);
-        setQueryParameters(query, parentAttributes, childAttributes, anchorName, dataspaceName);
-        return processQueryResults(query.getResultList());
-    }
-
-    private String buildWhereClause(final Map<String, String> attributes, final String alias) {
-        return attributes.keySet().stream()
-                .map(attributeName -> String.format("%s.attributes->>'%s' = ?", alias, attributeName))
-                .collect(Collectors.joining(" AND "));
-    }
-
-    private void setQueryParameters(final Query query, final Map<String, String> parentAttributes,
-                                    final Map<String, String> childAttributes, final String anchorName,
-                                    final String dataspaceName) {
-        final String childAttributeValue = childAttributes.entrySet().iterator().next().getValue();
-        query.setParameter(1, childAttributeValue);
-
-        final String parentAttributeValue = parentAttributes.entrySet().iterator().next().getValue();
-        query.setParameter(2, parentAttributeValue);
-
-        query.setParameter(3, anchorName);
-        query.setParameter(4, dataspaceName);
-    }
-
-    @SuppressFBWarnings(value = "VA_FORMAT_STRING_USES_NEWLINE", justification = "no \n in string just in file format")
-    private String buildModuleReferencesSqlQuery(final String parentFragmentClause, final String childFragmentClause) {
-        return """
-                WITH Fragment AS (
-                    SELECT childFragment.attributes->>'id' AS schema_set_name
-                    FROM fragment parentFragment
-                    JOIN fragment childFragment ON parentFragment.parent_id = childFragment.id
-                    JOIN anchor anchorInfo ON parentFragment.anchor_id = anchorInfo.id
-                    JOIN dataspace dataspaceInfo ON anchorInfo.dataspace_id = dataspaceInfo.id
-                    WHERE %s
-                    AND %s
-                    AND anchorInfo.name = ?
-                    AND dataspaceInfo.name = ?
-                    LIMIT 1
-                ),
-                SchemaSet AS (
-                    SELECT id
-                    FROM schema_set
-                    WHERE name = (SELECT schema_set_name FROM Fragment)
-                )
-                SELECT yangResource.module_name, yangResource.revision
-                FROM yang_resource yangResource
-                JOIN schema_set_yang_resources schemaSetYangResources
-                ON yangResource.id = schemaSetYangResources.yang_resource_id
-                WHERE schemaSetYangResources.schema_set_id = (SELECT id FROM SchemaSet);
-                """.formatted(parentFragmentClause, childFragmentClause);
-    }
-
-    private Collection<ModuleReference> processQueryResults(final List<Object[]> queryResults) {
-        if (queryResults.isEmpty()) {
-            log.info("No module references found for the provided attributes.");
-            return Collections.emptyList();
-        }
-        return queryResults.stream()
-                .map(queryResult -> {
-                    final String name = (String) queryResult[0];
-                    final String revision = (String) queryResult[1];
-                    return new ModuleReference(name, revision);
-                })
-                .collect(Collectors.toList());
-    }
-
     private Collection<ModuleReference> identifyNewModuleReferencesForCmHandle(final String tempTableName) {
-        final String sql = String.format(
-                "SELECT %1$s.module_name, %1$s.revision"
-                        + " FROM %1$s LEFT JOIN yang_resource"
-                        + " ON yang_resource.module_name=%1$s.module_name"
-                        + " AND yang_resource.revision=%1$s.revision"
-                        + " WHERE yang_resource.module_name IS NULL;", tempTableName);
+        final String sql = """
+                SELECT %1$s.module_name, %1$s.revision
+                FROM %1$s
+                LEFT JOIN yang_resource
+                ON yang_resource.module_name=%1$s.module_name AND yang_resource.revision=%1$s.revision
+                WHERE yang_resource.module_name IS NULL;
+                """.formatted(tempTableName);
 
         @SuppressWarnings("unchecked")
         final List<Object[]> resultsAsObjects = entityManager.createNativeQuery(sql).getResultList();
index 4e4948e..b8dd7b7 100644 (file)
@@ -2,7 +2,7 @@
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2020 Pantheon.tech
  *  Modifications Copyright (C) 2022 TechMahindra Ltd.
- *  Modifications Copyright (C) 2023-2024 Nordix Foundation
+ *  Modifications Copyright (C) 2023-2025 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -36,6 +36,8 @@ import org.springframework.stereotype.Repository;
 @Repository
 public interface SchemaSetRepository extends JpaRepository<SchemaSetEntity, Integer> {
 
+    boolean existsByDataspaceAndName(DataspaceEntity dataspaceEntity, String schemaSetName);
+
     Optional<SchemaSetEntity> findByDataspaceAndName(DataspaceEntity dataspaceEntity, String schemaSetName);
 
     /**
@@ -76,4 +78,13 @@ public interface SchemaSetRepository extends JpaRepository<SchemaSetEntity, Inte
         deleteByDataspaceIdAndNameIn(dataspaceEntity.getId(), schemaSetNames);
     }
 
+    /**
+     * Delete any schema set no longer used by any anchor.
+     */
+    @Modifying
+    @Query(value = """
+           DELETE FROM schema_set WHERE NOT EXISTS
+           (SELECT 1 FROM anchor WHERE anchor.schema_set_id = schema_set.id)
+           """, nativeQuery = true)
+    void deleteOrphanedSchemaSets();
 }
index 8350d57..410dcc2 100644 (file)
@@ -1,6 +1,6 @@
 /*-
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2023 Nordix Foundation.
+ *  Copyright (C) 2021-2025 Nordix Foundation.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -24,6 +24,13 @@ import java.util.List;
 
 public interface SchemaSetYangResourceRepository {
 
+
+    /**
+     * Link yang resources (ids) with a schema set (id).
+     *
+     * @param schemaSetId     the schema set id
+     * @param yangResourceIds list of yang resource ids
+     */
     void insertSchemaSetIdYangResourceId(final Integer schemaSetId, final List<Integer> yangResourceIds);
 
 }
index 831766c..628502f 100644 (file)
@@ -1,7 +1,7 @@
 /*
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2020 Pantheon.tech
- *  Modifications Copyright (C) 2021-2024 Nordix Foundation
+ *  Modifications Copyright (C) 2021-2025 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -92,7 +92,9 @@ public interface YangResourceRepository extends JpaRepository<YangResourceEntity
     void deleteSchemaSetYangResourceForSchemaSetId(@Param("schemaSetId") int schemaSetId);
 
     @Modifying
-    @Query(value = "DELETE FROM yang_resource yr WHERE NOT EXISTS "
-        + "(SELECT 1 FROM schema_set_yang_resources ssyr WHERE ssyr.yang_resource_id = yr.id)", nativeQuery = true)
-    void deleteOrphans();
+    @Query(value = """
+            DELETE FROM yang_resource WHERE NOT EXISTS (SELECT 1 FROM schema_set_yang_resources
+            WHERE schema_set_yang_resources.yang_resource_id = yang_resource.id)
+          """, nativeQuery = true)
+    void deleteOrphanedYangResources();
 }
index e71b44c..81b6439 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2020-2024 Nordix Foundation
+ *  Copyright (C) 2020-2025 Nordix Foundation
  *  Modifications Copyright (C) 2020-2021 Pantheon.tech
  *  Modifications Copyright (C) 2022 TechMahindra Ltd.
  *  ================================================================================
@@ -57,6 +57,15 @@ public interface CpsModuleService {
                                     Map<String, String> newModuleNameToContentMap,
                                     Collection<ModuleReference> allModuleReferences);
 
+    /**
+     * Check if a schema set exist in the given dataspace.
+     *
+     * @param dataspaceName  Dataspace name
+     * @param schemaSetName  Schema set name
+     * @return boolean, true if a schema set with the given name exist in the given dataspace
+     */
+    boolean schemaSetExists(String dataspaceName, String schemaSetName);
+
     /**
      * Read schema set in the given dataspace.
      *
@@ -150,47 +159,13 @@ public interface CpsModuleService {
      * The system will ignore the namespace of all module references.
      *
      * @param moduleReferencesToCheck the moduleReferencesToCheck
-     * @returns collection of module references (namespace will be always blank)
-     */
-    Collection<ModuleReference> identifyNewModuleReferences(
-        Collection<ModuleReference> moduleReferencesToCheck);
-
-    /**
-     * Retrieves module references based on the provided dataspace name, anchor name and attribute filters
-     * for both parent and child fragments.
-
-     * This method constructs and executes a SQL query to find module references from a database, using
-     * the specified `dataspaceName`, `anchorName` and two sets of attribute filters: one for parent fragments
-     * and one for child fragments. The method applies these filters to identify the appropriate fragments
-     * and schema sets, and then retrieves the corresponding module references.
-
-     * The SQL query is dynamically built based on the provided attribute filters:
-     * - The `parentAttributes` map is used to filter the parent fragments. The entries in this map are
-     * converted into a WHERE clause for the parent fragments.
-     * - The `childAttributes` map is used to filter the child fragments. This is applied to the child fragments
-     * after filtering the parent fragments.
-     *
-     * @param dataspaceName    the name of the dataspace to filter on. It is used to locate the relevant dataspace
-     *                         in the database.
-     * @param anchorName       the name of the anchor to filter on. It is used to locate the relevant anchor within
-     *                         the dataspace.
-     * @param parentAttributes a map of attributes to filter parent fragments. Each entry in this map represents
-     *                         an attribute key-value pair used in the WHERE clause for parent fragments.
-     * @param childAttributes  a map of attributes to filter child fragments. Each entry in this map represents
-     *                         an attribute key-value pair used in the WHERE clause for child fragments.
-     * @return a collection of {@link ModuleReference} objects that match the given criteria.
-     *     Each {@code ModuleReference} contains information about a module's name and revision.
-     * @implNote The method assumes that both `parentAttributes` and `childAttributes` maps contain at least
-     *     one entry. The first entry from `parentAttributes` is used to filter parent fragments,
-     *     and the first entry from `childAttributes` is used to filter child fragments.
+     * @return collection of module references (namespace will be always blank)
      */
-    Collection<ModuleReference> getModuleReferencesByAttribute(final String dataspaceName, final String anchorName,
-                                                               final Map<String, String> parentAttributes,
-                                                               final Map<String, String> childAttributes);
+    Collection<ModuleReference> identifyNewModuleReferences(Collection<ModuleReference> moduleReferencesToCheck);
 
     /**
-     * Remove any Yang Resource Modules from the DB that are no longer referenced by any schema set.
+     * Remove any Yang Resource Modules and Schema Sets from the DB that are no longer referenced by any anchor.
      */
-    void deleteUnusedYangResourceModules();
+    void deleteAllUnusedYangModuleData();
 
 }
index 1bd2b6a..2a2ddc6 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2023-2024 Nordix Foundation
+ *  Copyright (C) 2023-2025 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -39,7 +39,7 @@ public class CpsAnchorServiceImpl implements CpsAnchorService {
 
     @Override
     public void createAnchor(final String dataspaceName, final String schemaSetName, final String anchorName) {
-        cpsValidator.validateNameCharacters(dataspaceName, schemaSetName, anchorName);
+        cpsValidator.validateNameCharacters(dataspaceName, anchorName);
         cpsAdminPersistenceService.createAnchor(dataspaceName, schemaSetName, anchorName);
     }
 
@@ -64,7 +64,7 @@ public class CpsAnchorServiceImpl implements CpsAnchorService {
 
     @Override
     public Collection<Anchor> getAnchorsBySchemaSetName(final String dataspaceName, final String schemaSetName) {
-        cpsValidator.validateNameCharacters(dataspaceName, schemaSetName);
+        cpsValidator.validateNameCharacters(dataspaceName);
         return cpsAdminPersistenceService.getAnchorsBySchemaSetName(dataspaceName, schemaSetName);
     }
 
@@ -72,7 +72,6 @@ public class CpsAnchorServiceImpl implements CpsAnchorService {
     public Collection<Anchor> getAnchorsBySchemaSetNames(final String dataspaceName,
                                                          final Collection<String> schemaSetNames) {
         cpsValidator.validateNameCharacters(dataspaceName);
-        cpsValidator.validateNameCharacters(schemaSetNames);
         return cpsAdminPersistenceService.getAnchorsBySchemaSetNames(dataspaceName, schemaSetNames);
     }
 
index 9f5c0a3..6740262 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2020-2024 Nordix Foundation
+ *  Copyright (C) 2020-2025 Nordix Foundation
  *  Modifications Copyright (C) 2020-2021 Pantheon.tech
  *  Modifications Copyright (C) 2022 Bell Canada
  *  Modifications Copyright (C) 2022 TechMahindra Ltd
@@ -58,7 +58,7 @@ public class CpsModuleServiceImpl implements CpsModuleService {
         description = "Time taken to create (and store) a schemaset")
     public void createSchemaSet(final String dataspaceName, final String schemaSetName,
         final Map<String, String> yangResourcesNameToContentMap) {
-        cpsValidator.validateNameCharacters(dataspaceName, schemaSetName);
+        cpsValidator.validateNameCharacters(dataspaceName);
         cpsModulePersistenceService.storeSchemaSet(dataspaceName, schemaSetName, yangResourcesNameToContentMap);
         final YangTextSchemaSourceSet yangTextSchemaSourceSet =
             timedYangTextSchemaSourceSetBuilder.getYangTextSchemaSourceSet(yangResourcesNameToContentMap);
@@ -69,14 +69,20 @@ public class CpsModuleServiceImpl implements CpsModuleService {
     public void createSchemaSetFromModules(final String dataspaceName, final String schemaSetName,
                                            final Map<String, String> newModuleNameToContentMap,
                                            final Collection<ModuleReference> allModuleReferences) {
-        cpsValidator.validateNameCharacters(dataspaceName, schemaSetName);
+        cpsValidator.validateNameCharacters(dataspaceName);
         cpsModulePersistenceService.storeSchemaSetFromModules(dataspaceName, schemaSetName,
             newModuleNameToContentMap, allModuleReferences);
     }
 
+    @Override
+    public boolean schemaSetExists(final String dataspaceName, final String schemaSetName) {
+        cpsValidator.validateNameCharacters(dataspaceName);
+        return cpsModulePersistenceService.schemaSetExists(dataspaceName, schemaSetName);
+    }
+
     @Override
     public SchemaSet getSchemaSet(final String dataspaceName, final String schemaSetName) {
-        cpsValidator.validateNameCharacters(dataspaceName, schemaSetName);
+        cpsValidator.validateNameCharacters(dataspaceName);
         final var yangTextSchemaSourceSet = yangTextSchemaSourceSetCache
             .get(dataspaceName, schemaSetName);
         return SchemaSet.builder().name(schemaSetName).dataspaceName(dataspaceName)
@@ -96,7 +102,7 @@ public class CpsModuleServiceImpl implements CpsModuleService {
     @Transactional
     public void deleteSchemaSet(final String dataspaceName, final String schemaSetName,
                                 final CascadeDeleteAllowed cascadeDeleteAllowed) {
-        cpsValidator.validateNameCharacters(dataspaceName, schemaSetName);
+        cpsValidator.validateNameCharacters(dataspaceName);
         final Collection<Anchor> anchors = cpsAnchorService.getAnchorsBySchemaSetName(dataspaceName, schemaSetName);
         if (!anchors.isEmpty() && isCascadeDeleteProhibited(cascadeDeleteAllowed)) {
             throw new SchemaSetInUseException(dataspaceName, schemaSetName);
@@ -112,7 +118,6 @@ public class CpsModuleServiceImpl implements CpsModuleService {
     @Transactional
     public void deleteSchemaSetsWithCascade(final String dataspaceName, final Collection<String> schemaSetNames) {
         cpsValidator.validateNameCharacters(dataspaceName);
-        cpsValidator.validateNameCharacters(schemaSetNames);
         final Collection<String> anchorNames =
                 cpsAnchorService.getAnchorsBySchemaSetNames(dataspaceName, schemaSetNames)
                         .stream().map(Anchor::getName).collect(Collectors.toSet());
@@ -127,7 +132,7 @@ public class CpsModuleServiceImpl implements CpsModuleService {
     public void upgradeSchemaSetFromModules(final String dataspaceName, final String schemaSetName,
                                             final Map<String, String> newModuleNameToContentMap,
                                             final Collection<ModuleReference> allModuleReferences) {
-        cpsValidator.validateNameCharacters(dataspaceName, schemaSetName);
+        cpsValidator.validateNameCharacters(dataspaceName);
         cpsModulePersistenceService.updateSchemaSetFromModules(dataspaceName, schemaSetName,
                 newModuleNameToContentMap, allModuleReferences);
         yangTextSchemaSourceSetCache.removeFromCache(dataspaceName, schemaSetName);
@@ -169,20 +174,9 @@ public class CpsModuleServiceImpl implements CpsModuleService {
         return cpsModulePersistenceService.identifyNewModuleReferences(moduleReferencesToCheck);
     }
 
-    @Timed(value = "cps.module.service.module.reference.query.by.attribute",
-            description = "Time taken to query list of module references by attribute (e.g moduleSetTag)")
-    @Override
-    public Collection<ModuleReference> getModuleReferencesByAttribute(final String dataspaceName,
-                                                                      final String anchorName,
-                                                                      final Map<String, String> parentAttributes,
-                                                                      final Map<String, String> childAttributes) {
-        return cpsModulePersistenceService.getModuleReferencesByAttribute(dataspaceName, anchorName, parentAttributes,
-                childAttributes);
-    }
-
     @Override
-    public void deleteUnusedYangResourceModules() {
-        cpsModulePersistenceService.deleteUnusedYangResourceModules();
+    public void deleteAllUnusedYangModuleData() {
+        cpsModulePersistenceService.deleteAllUnusedYangModuleData();
     }
 
     private boolean isCascadeDeleteProhibited(final CascadeDeleteAllowed cascadeDeleteAllowed) {
index 8b85dfc..b893bce 100644 (file)
@@ -2,7 +2,7 @@
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2021 Pantheon.tech
  *  Modifications Copyright (C) 2022 Bell Canada
- *  Modifications Copyright (C) 2022-2023 Nordix Foundation
+ *  Modifications Copyright (C) 2022-2025 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -60,7 +60,7 @@ public class YangTextSchemaSourceSetCache {
      */
     @Cacheable(key = "#p0.concat('-').concat(#p1)")
     public YangTextSchemaSourceSet get(final String dataspaceName, final String schemaSetName) {
-        cpsValidator.validateNameCharacters(dataspaceName, schemaSetName);
+        cpsValidator.validateNameCharacters(dataspaceName);
         final Map<String, String> yangResourceNameToContent =
                 cpsModulePersistenceService.getYangSchemaResources(dataspaceName, schemaSetName);
         return YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent);
@@ -78,7 +78,7 @@ public class YangTextSchemaSourceSetCache {
     @CanIgnoreReturnValue
     public YangTextSchemaSourceSet updateCache(final String dataspaceName, final String schemaSetName,
             final YangTextSchemaSourceSet yangTextSchemaSourceSet) {
-        cpsValidator.validateNameCharacters(dataspaceName, schemaSetName);
+        cpsValidator.validateNameCharacters(dataspaceName);
         yangSchemaCacheCounter.incrementAndGet();
         return yangTextSchemaSourceSet;
     }
@@ -91,9 +91,8 @@ public class YangTextSchemaSourceSetCache {
      */
     @CacheEvict(key = "#p0.concat('-').concat(#p1)")
     public void removeFromCache(final String dataspaceName, final String schemaSetName) {
-        cpsValidator.validateNameCharacters(dataspaceName, schemaSetName);
+        cpsValidator.validateNameCharacters(dataspaceName);
         yangSchemaCacheCounter.decrementAndGet();
-        // Spring provides implementation for removing object from cache
     }
 
 }
diff --git a/cps-service/src/main/java/org/onap/cps/init/DbCleaner.java b/cps-service/src/main/java/org/onap/cps/init/DbCleaner.java
deleted file mode 100644 (file)
index 6bd3e1f..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- *  ============LICENSE_START=======================================================
- *  Copyright (C) 2024 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.init;
-
-import java.util.concurrent.TimeUnit;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.onap.cps.api.CpsModuleService;
-import org.springframework.scheduling.annotation.Scheduled;
-import org.springframework.stereotype.Service;
-
-@Slf4j
-@RequiredArgsConstructor
-@Service
-public class DbCleaner {
-
-    private final CpsModuleService cpsModuleService;
-
-    /**
-     * This method will clean up the db during application start up.
-     * It wil run once and currently only removes unused yang resource modules.
-     *
-     */
-    @Scheduled(initialDelay = 1, timeUnit = TimeUnit.SECONDS)
-    public void cleanDbAtStartUp() {
-        log.info("CPS Application started, commencing DB clean up");
-        cpsModuleService.deleteUnusedYangResourceModules();
-        log.info("DB clean up completed");
-    }
-}
index 4cfc287..b1f8aad 100755 (executable)
@@ -1,6 +1,6 @@
 /*
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2020-2024 Nordix Foundation
+ *  Copyright (C) 2020-2025 Nordix Foundation
  *  Modifications Copyright (C) 2020-2022 Bell Canada.
  *  Modifications Copyright (C) 2022 TechMahindra Ltd.
  *  ================================================================================
@@ -65,6 +65,14 @@ public interface CpsModulePersistenceService {
                                     final Map<String, String> newModuleNameToContentMap,
                                     final Collection<ModuleReference> allModuleReferences);
 
+    /**
+     * Checks whether a schema set exists in the specified dataspace.
+     *
+     * @param dataspaceName dataspace name
+     * @param schemaSetName schema set name
+     * @return {@code true} if the schema set exists in the given dataspace; {@code false} otherwise
+     */
+    boolean schemaSetExists(String dataspaceName, String schemaSetName);
 
     /**
      * Get all schema sets for a given dataspace.
@@ -138,35 +146,18 @@ public interface CpsModulePersistenceService {
                                                                              String moduleName, String moduleRevision);
 
     /**
-     * Remove unused Yang Resource Modules.
+     * Remove any unused Yang Resource Modules and Schema Sets.
      */
-    void deleteUnusedYangResourceModules();
+    void deleteAllUnusedYangModuleData();
 
     /**
      * Identify new module references from those returned by a node compared to what is in CPS already.
      * The system will ignore the namespace of all module references.
      *
      * @param moduleReferencesToCheck the module references ot check
-     * @returns Collection of {@link ModuleReference} (namespace will be always blank)
-     *
-     */
-    Collection<ModuleReference> identifyNewModuleReferences(
-        Collection<ModuleReference> moduleReferencesToCheck);
-
-    /**
-     * Retrieves module references based on the specified dataspace, anchor, and attribute filters.
-
-     * Constructs and executes a SQL query to find module references by applying filters for parent and child fragments.
-     * Uses `parentAttributes` for filtering parent fragments and `childAttributes` for filtering child fragments.
+     * @return Collection of {@link ModuleReference} (namespace will be always blank)
      *
-     * @param dataspaceName    the name of the dataspace to filter on.
-     * @param anchorName       the name of the anchor to filter on.
-     * @param parentAttributes a map of attributes for filtering parent fragments.
-     * @param childAttributes  a map of attributes for filtering child fragments.
-     * @return a collection of {@link ModuleReference} objects matching the criteria.
      */
-    Collection<ModuleReference> getModuleReferencesByAttribute(final String dataspaceName, final String anchorName,
-                                                               final Map<String, String> parentAttributes,
-                                                               final Map<String, String> childAttributes);
+    Collection<ModuleReference> identifyNewModuleReferences(Collection<ModuleReference> moduleReferencesToCheck);
 
 }
index e8617d4..223ae71 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2023-2024 Nordix Foundation
+ *  Copyright (C) 2023-2025 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -40,8 +40,8 @@ class CpsAnchorServiceImplSpec extends Specification {
             objectUnderTest.createAnchor('someDataspace', 'someSchemaSet', 'someAnchorName')
         then: 'the persistence service method is invoked with same parameters'
             1 * mockCpsAdminPersistenceService.createAnchor('someDataspace', 'someSchemaSet', 'someAnchorName')
-        and: 'the CpsValidator is called on the dataspaceName, schemaSetName and anchorName'
-            1 * mockCpsValidator.validateNameCharacters('someDataspace', 'someSchemaSet', 'someAnchorName')
+        and: 'the CpsValidator is called on the dataspaceName and anchorName'
+            1 * mockCpsValidator.validateNameCharacters('someDataspace', 'someAnchorName')
     }
 
     def 'Retrieve all anchors for dataspace.'() {
@@ -64,8 +64,8 @@ class CpsAnchorServiceImplSpec extends Specification {
             def result = objectUnderTest.getAnchorsBySchemaSetName('someDataspace', 'someSchemaSet')
         then: 'the collection provided by persistence service is returned as result'
             result == anchors
-        and: 'the CpsValidator is called on the dataspaceName, schemaSetName'
-            1 * mockCpsValidator.validateNameCharacters('someDataspace', 'someSchemaSet')
+        and: 'the CpsValidator is called on the dataspaceName'
+            1 * mockCpsValidator.validateNameCharacters('someDataspace')
     }
 
     def 'Retrieve all anchors for multiple schema-sets.'() {
@@ -76,9 +76,8 @@ class CpsAnchorServiceImplSpec extends Specification {
             def result = objectUnderTest.getAnchorsBySchemaSetNames('someDataspace', ['schemaSet1', 'schemaSet2'])
         then: 'the collection provided by persistence service is returned as result'
             result == anchors
-        and: 'the CpsValidator is called on the dataspace name and schema-set names'
+        and: 'the CpsValidator is called on the dataspace name'
             1 * mockCpsValidator.validateNameCharacters('someDataspace')
-            1 * mockCpsValidator.validateNameCharacters(_)
     }
 
     def 'Retrieve anchor for dataspace and provided anchor name.'() {
index 97b9f7f..d1101fc 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2020-2024 Nordix Foundation
+ *  Copyright (C) 2020-2025 Nordix Foundation
  *  Modifications Copyright (C) 2020-2021 Pantheon.tech
  *  Modifications Copyright (C) 2020-2022 Bell Canada.
  *  Modifications Copyright (C) 2022 TechMahindra Ltd.
@@ -54,23 +54,23 @@ class CpsModuleServiceImplSpec extends Specification {
 
     def 'Create schema set.'() {
         when: 'Create schema set method is invoked'
-            objectUnderTest.createSchemaSet('someDataspace', 'someSchemaSet', [:])
+            objectUnderTest.createSchemaSet('someDataspace', 'schemaSetName@with Special!Characters', [:])
         then: 'Parameters are validated and processing is delegated to persistence service'
-            1 * mockCpsModulePersistenceService.storeSchemaSet('someDataspace', 'someSchemaSet', [:])
-        and: 'the CpsValidator is called on the dataspaceName and schemaSetName'
-            1 * mockCpsValidator.validateNameCharacters('someDataspace', 'someSchemaSet')
+            1 * mockCpsModulePersistenceService.storeSchemaSet('someDataspace', 'schemaSetName@with Special!Characters', [:])
+        and: 'the CpsValidator is called on the dataspaceName'
+            1 * mockCpsValidator.validateNameCharacters('someDataspace')
     }
 
     def 'Create schema set from new modules and existing modules.'() {
         given: 'a list of existing modules module reference'
-            def moduleReferenceForExistingModule = new ModuleReference('test',  '2021-10-12','test.org')
+            def moduleReferenceForExistingModule = new ModuleReference('test', '2021-10-12', 'test.org')
             def listOfExistingModulesModuleReference = [moduleReferenceForExistingModule]
         when: 'create schema set from modules method is invoked'
             objectUnderTest.createSchemaSetFromModules('someDataspaceName', 'someSchemaSetName', [newModule: 'newContent'], listOfExistingModulesModuleReference)
         then: 'processing is delegated to persistence service'
             1 * mockCpsModulePersistenceService.storeSchemaSetFromModules('someDataspaceName', 'someSchemaSetName', [newModule: 'newContent'], listOfExistingModulesModuleReference)
-        and: 'the CpsValidator is called on the dataspaceName and schemaSetName'
-            1 * mockCpsValidator.validateNameCharacters('someDataspaceName', 'someSchemaSetName')
+        and: 'the CpsValidator is called on the dataspaceName'
+            1 * mockCpsValidator.validateNameCharacters('someDataspaceName')
     }
 
     def 'Create schema set from invalid resources'() {
@@ -100,15 +100,15 @@ class CpsModuleServiceImplSpec extends Specification {
         given: 'an already present schema set'
             def yangResourcesNameToContentMap = TestUtils.getYangResourcesAsMap('bookstore.yang')
         and: 'yang resource cache returns the expected schema set'
-            mockYangTextSchemaSourceSetCache.get('someDataspace', 'someSchemaSet') >> YangTextSchemaSourceSetBuilder.of(yangResourcesNameToContentMap)
+            mockYangTextSchemaSourceSetCache.get('someDataspace', 'schemaSetName@with Special!Characters') >> YangTextSchemaSourceSetBuilder.of(yangResourcesNameToContentMap)
         when: 'get schema set method is invoked'
-            def result = objectUnderTest.getSchemaSet('someDataspace', 'someSchemaSet')
+            def result = objectUnderTest.getSchemaSet('someDataspace', 'schemaSetName@with Special!Characters')
         then: 'the correct schema set is returned'
-            result.getName().contains('someSchemaSet')
+            result.getName().contains('schemaSetName@with Special!Characters')
             result.getDataspaceName().contains('someDataspace')
             result.getModuleReferences().contains(new ModuleReference('stores', '2020-09-15', 'org:onap:ccsdk:sample'))
-        and: 'the CpsValidator is called on the dataspaceName and schemaSetName'
-            1 * mockCpsValidator.validateNameCharacters('someDataspace', 'someSchemaSet')
+        and: 'the CpsValidator is called on the dataspaceName'
+            1 * mockCpsValidator.validateNameCharacters('someDataspace')
     }
 
     def 'Get schema sets by dataspace name.'() {
@@ -142,8 +142,8 @@ class CpsModuleServiceImplSpec extends Specification {
             1 * mockCpsModulePersistenceService.deleteSchemaSet('my-dataspace', 'my-schemaset')
         and: 'schema set will be removed from the cache'
             1 * mockYangTextSchemaSourceSetCache.removeFromCache('my-dataspace', 'my-schemaset')
-        and: 'the CpsValidator is called on the dataspaceName and schemaSetName'
-            1 * mockCpsValidator.validateNameCharacters('my-dataspace', _)
+        and: 'the CpsValidator is called on the dataspaceName'
+            1 * mockCpsValidator.validateNameCharacters('my-dataspace')
         where: 'following parameters are used'
             numberOfAnchors << [0, 3]
     }
@@ -159,8 +159,8 @@ class CpsModuleServiceImplSpec extends Specification {
             1 * mockCpsModulePersistenceService.deleteSchemaSet('my-dataspace', 'my-schemaset')
         and: 'schema set will be removed from the cache'
             1 * mockYangTextSchemaSourceSetCache.removeFromCache('my-dataspace', 'my-schemaset')
-        and: 'the CpsValidator is called on the dataspaceName and schemaSetName'
-            1 * mockCpsValidator.validateNameCharacters('my-dataspace', 'my-schemaset')
+        and: 'the CpsValidator is called on the dataspaceName'
+            1 * mockCpsValidator.validateNameCharacters('my-dataspace')
     }
 
     def 'Delete schema-set when cascade is prohibited and schema-set has anchors.'() {
@@ -185,17 +185,15 @@ class CpsModuleServiceImplSpec extends Specification {
             2 * mockYangTextSchemaSourceSetCache.removeFromCache('my-dataspace', _)
         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 'Upgrade existing schema set'() {
         when: 'schema set update is requested'
-        objectUnderTest.upgradeSchemaSetFromModules('my-dataspace', 'my-schemaset', [:], moduleReferences)
+            objectUnderTest.upgradeSchemaSetFromModules('my-dataspace', 'my-schemaset', [:], moduleReferences)
         then: 'no exception is thrown '
-        noExceptionThrown()
+            noExceptionThrown()
     }
 
     def 'Get all yang resources module references.'() {
@@ -206,7 +204,7 @@ class CpsModuleServiceImplSpec extends Specification {
             def result = objectUnderTest.getYangResourceModuleReferences('someDataspaceName')
         then: 'the list provided by persistence service is returned as result'
             result == moduleReferences
-        and: 'the CpsValidator is called on the dataspaceName and schemaSetName'
+        and: 'the CpsValidator is called on the dataspaceName'
             1 * mockCpsValidator.validateNameCharacters('someDataspaceName')
     }
 
@@ -218,7 +216,7 @@ class CpsModuleServiceImplSpec extends Specification {
             def result = objectUnderTest.getYangResourcesModuleReferences('someDataspaceName', 'someAnchorName')
         then: 'the list provided by persistence service is returned as result'
             result == moduleReferences
-        and: 'the CpsValidator is called on the dataspaceName and schemaSetName'
+        and: 'the CpsValidator is called on the dataspaceName and anchorName'
             1 * mockCpsValidator.validateNameCharacters('someDataspaceName', 'someAnchorName')
     }
 
@@ -231,22 +229,6 @@ class CpsModuleServiceImplSpec extends Specification {
             1 * mockCpsModulePersistenceService.identifyNewModuleReferences(moduleReferencesToCheck)
     }
 
-    def 'Get module references when queried by attributes'() {
-        given: 'a valid dataspace name and anchor name'
-            def dataspaceName = 'someDataspace'
-            def anchorName = 'someAnchor'
-        and: 'a set of parent attributes and child attributes used for filtering'
-            def parentAttributes = ['some-property-key1': 'some-property-val1']
-            def childAttributes = ['some-property-key2': 'some-property-val2']
-        and: 'a list of expected module references returned by the persistence service'
-            def expectedModuleReferences = [new ModuleReference(moduleName: 'some-name', revision: 'some-revision')]
-            mockCpsModulePersistenceService.getModuleReferencesByAttribute(dataspaceName, anchorName, parentAttributes, childAttributes) >> expectedModuleReferences
-        when: 'the method is invoked to retrieve module references by attributes'
-            def actualModuleReferences = objectUnderTest.getModuleReferencesByAttribute(dataspaceName, anchorName, parentAttributes, childAttributes)
-        then: 'the retrieved module references should match the expected module references'
-            assert actualModuleReferences == expectedModuleReferences
-    }
-
 
     def 'Getting module definitions with module name'() {
         given: 'module persistence service returns module definitions for module name'
@@ -270,11 +252,18 @@ class CpsModuleServiceImplSpec extends Specification {
             1 * mockCpsValidator.validateNameCharacters('some-dataspace-name', 'some-anchor-name')
     }
 
-    def 'Delete unused yang resource modules.'() {
-        when: 'deleting unused yang resource modules'
-            objectUnderTest.deleteUnusedYangResourceModules()
+    def 'Delete all unused yang module data.'() {
+        when: 'deleting unused yang module data'
+            objectUnderTest.deleteAllUnusedYangModuleData()
         then: 'it is delegated to the module persistence service'
-            1 * mockCpsModulePersistenceService.deleteUnusedYangResourceModules()
+            1 * mockCpsModulePersistenceService.deleteAllUnusedYangModuleData()
+    }
+
+    def 'Schema set exists.'() {
+        when: 'checking if schema set exists'
+            objectUnderTest.schemaSetExists('some-dataspace-name', 'some-schema-set-name')
+        then: 'the call is delegated to the module persistence service'
+            1 * mockCpsModulePersistenceService.schemaSetExists('some-dataspace-name', 'some-schema-set-name')
     }
 
     def getModuleReferences() {
index 189e285..5b9d11f 100644 (file)
@@ -1,7 +1,7 @@
 /*
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2022 Bell Canada
- *  Modifications Copyright (C) 2022 Nordix Foundation
+ *  Modifications Copyright (C) 2022-2025 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -77,8 +77,8 @@ class YangTextSchemaSourceSetCacheSpec extends Specification {
             assert cachedValue.getModuleReferences() == expectedYangTextSchemaSourceSet.getModuleReferences()
         and: 'the response is as expected'
             assert result.getModuleReferences() == expectedYangTextSchemaSourceSet.getModuleReferences()
-        and: 'the CpsValidator is called on the dataspaceName and schemaSetName'
-            1 * mockCpsValidator.validateNameCharacters('my-dataspace', 'my-schemaset')
+        and: 'the CpsValidator is called on the dataspaceName'
+            1 * mockCpsValidator.validateNameCharacters('my-dataspace')
     }
 
     def 'Cache Hit: Respond from cache'() {
@@ -104,8 +104,8 @@ class YangTextSchemaSourceSetCacheSpec extends Specification {
         then: 'cached value is same as expected'
             def cachedValue = getCachedValue('my-dataspace', 'my-schemaset')
             cachedValue.getModuleReferences() == yangTextSchemaSourceSet.getModuleReferences()
-        and: 'the CpsValidator is called on the dataspaceName and schemaSetName'
-            1 * mockCpsValidator.validateNameCharacters('my-dataspace', 'my-schemaset')
+        and: 'the CpsValidator is called on the dataspaceName'
+            1 * mockCpsValidator.validateNameCharacters('my-dataspace')
     }
 
     def 'Cache Evict:with invalid #scenario'() {
@@ -119,8 +119,8 @@ class YangTextSchemaSourceSetCacheSpec extends Specification {
             objectUnderTest.removeFromCache('my-dataspace', 'my-schemaset')
         then: 'cached does not have value'
             assert getCachedValue('my-dataspace', 'my-schemaset') == null
-        and: 'the CpsValidator is called on the dataspaceName and schemaSetName'
-            1 * mockCpsValidator.validateNameCharacters('my-dataspace', 'my-schemaset')
+        and: 'the CpsValidator is called on the dataspaceName'
+            1 * mockCpsValidator.validateNameCharacters('my-dataspace')
     }
 
     def 'Cache Evict: remove when does not exist'() {
@@ -130,8 +130,8 @@ class YangTextSchemaSourceSetCacheSpec extends Specification {
             objectUnderTest.removeFromCache('my-dataspace', 'my-schemaset')
         then: 'cached does not have value'
             assert getCachedValue('my-dataspace', 'my-schemaset') == null
-        and: 'the CpsValidator is called on the dataspaceName and schemaSetName'
-            1 * mockCpsValidator.validateNameCharacters('my-dataspace', 'my-schemaset')
+        and: 'the CpsValidator is called on the dataspaceName'
+            1 * mockCpsValidator.validateNameCharacters('my-dataspace')
     }
 
     def getCachedValue(dataSpace, schemaSet) {
@@ -142,5 +142,4 @@ class YangTextSchemaSourceSetCacheSpec extends Specification {
         return new String("${dataSpace}-${schemaSet}")
     }
 
-
 }
diff --git a/cps-service/src/test/groovy/org/onap/cps/init/DbCleanerSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/init/DbCleanerSpec.groovy
deleted file mode 100644 (file)
index 5106d29..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- *  ============LICENSE_START=======================================================
- *  Copyright (C) 2024 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.init
-
-import org.onap.cps.api.CpsModuleService
-import spock.lang.Specification
-
-class DbCleanerSpec extends Specification {
-
-    def mockCpsModuleService = Mock(CpsModuleService)
-
-    def objectUnderTest = new DbCleaner(mockCpsModuleService)
-
-    def 'DB clean up.'() {
-        when: 'scheduled method is triggered'
-            objectUnderTest.cleanDbAtStartUp()
-        then: 'the unused yang resource modules are deleted'
-            1 * mockCpsModuleService.deleteUnusedYangResourceModules()
-    }
-}
index 1e3b9ab..bcb2c2b 100644 (file)
@@ -1,7 +1,7 @@
 # ============LICENSE_START=======================================================
 # Copyright (c) 2020 Pantheon.tech.
 # Modifications Copyright (C) 2021 Bell Canada.
-# Modifications Copyright (C) 2022-2024 Nordix Foundation.
+# Modifications Copyright (C) 2022-2025 Nordix Foundation.
 # ================================================================================
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -51,6 +51,8 @@ services:
     image: ${DOCKER_REPO:-nexus3.onap.org:10003}/onap/cps-and-ncmp:${CPS_VERSION:-latest}
     ports:
       - ${CPS_PORT_RANGE:-8698-8699}:8080
+      ### DEBUG: Uncomment next line to enable java debugging (ensure 'ports' aligns with 'deploy')
+      ### - ${CPS_CORE_DEBUG_PORT:-5005}:5005-
     environment:
       CPS_USERNAME: ${CPS_CORE_USERNAME:-cpsuser}
       CPS_PASSWORD: ${CPS_CORE_PASSWORD:-cpsr0cks!}
@@ -66,9 +68,9 @@ services:
       ONAP_OTEL_EXPORTER_ENDPOINT: http://jaeger-service:4317
       POLICY_SERVICE_ENABLED: 'false'
       POLICY_SERVICE_DEFAULT_DECISION: 'deny from env'
-      JAVA_TOOL_OPTIONS: "-XX:InitialRAMPercentage=75.0 -XX:MaxRAMPercentage=75.0"
+      ####JAVA_TOOL_OPTIONS: "-XX:InitialRAMPercentage=75.0 -XX:MaxRAMPercentage=75.0"
       ### DEBUG: Uncomment next line to enable java debugging
-      ### DEBUG: JAVA_TOOL_OPTIONS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
+      ### JAVA_TOOL_OPTIONS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
     restart: unless-stopped
     depends_on:
       - dbpostgresql
@@ -79,9 +81,6 @@ services:
         limits:
           cpus: '3'
           memory: 2G
-    ### DEBUG: Uncomment next 2 lines to enable java debugging (ensure 'ports' aligns with 'deploy')
-    ### DEBUG ports:
-    ### DEBUG  - ${CPS_CORE_DEBUG_PORT:-5005}:5005
 
   nginx:
     container_name: ${NGINX_CONTAINER_NAME:-nginx-loadbalancer}
index 2a17e30..940bc50 100644 (file)
@@ -1,6 +1,6 @@
 .. This work is licensed under a Creative Commons Attribution 4.0 International License.
 .. http://creativecommons.org/licenses/by/4.0
-.. Copyright (C) 2021-2024 Nordix Foundation
+.. Copyright (C) 2021-2025 Nordix Foundation
 .. Modifications Copyright (C) 2021 Bell Canada.
 
 .. DO NOT CHANGE THIS LABEL FOR RELEASE NOTES - EVEN THOUGH IT GIVES A WARNING
@@ -354,10 +354,7 @@ Below are the list of distributed datastructures that we have.
 +--------------+------------------------------------+-----------------------------------------------------------+
 | cps-ncmp     | cmNotificationSubscriptionCache    | Stores and tracks cm notification subscription requests.  |
 +--------------+------------------------------------+-----------------------------------------------------------+
-| cps-ncmp     | moduleSetTagsBeingProcessed        | Track module set tags which are processed to prevent      |
-|              |                                    | multiple threads working with same tag.                   |
-+--------------+------------------------------------+-----------------------------------------------------------+
 | cps-ncmp     | cpsAndNcmpLock                     | Cps and NCMP distributed lock for various use cases.      |
 +--------------+------------------------------------+-----------------------------------------------------------+
 
-Total number of caches : 8
+Total number of caches : 7
index fa65d9d..f3cca80 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2023-2024 Nordix Foundation
+ *  Copyright (C) 2023-2025 Nordix Foundation
  *  Modifications Copyright (C) 2024-2025 TechMahindra Ltd.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the 'License');
@@ -21,7 +21,6 @@
 
 package org.onap.cps.integration.base
 
-import com.hazelcast.collection.ISet
 import com.hazelcast.map.IMap
 import okhttp3.mockwebserver.MockWebServer
 import org.onap.cps.api.CpsAnchorService
@@ -29,24 +28,23 @@ import org.onap.cps.api.CpsDataService
 import org.onap.cps.api.CpsDataspaceService
 import org.onap.cps.api.CpsModuleService
 import org.onap.cps.api.CpsQueryService
+import org.onap.cps.api.exceptions.DataspaceNotFoundException
+import org.onap.cps.api.model.DataNode
 import org.onap.cps.integration.DatabaseTestContainer
 import org.onap.cps.integration.KafkaTestContainer
-import org.onap.cps.ncmp.impl.NetworkCmProxyInventoryFacadeImpl
+import org.onap.cps.ncmp.api.inventory.models.CmHandleState
 import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistration
 import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle
+import org.onap.cps.ncmp.impl.NetworkCmProxyInventoryFacadeImpl
 import org.onap.cps.ncmp.impl.data.NetworkCmProxyFacade
 import org.onap.cps.ncmp.impl.data.NetworkCmProxyQueryService
 import org.onap.cps.ncmp.impl.inventory.InventoryPersistence
 import org.onap.cps.ncmp.impl.inventory.ParameterizedCmHandleQueryService
-import org.onap.cps.ncmp.api.inventory.models.CmHandleState
 import org.onap.cps.ncmp.impl.inventory.sync.ModuleSyncService
 import org.onap.cps.ncmp.impl.inventory.sync.ModuleSyncWatchdog
 import org.onap.cps.ncmp.impl.utils.AlternateIdMatcher
 import org.onap.cps.ri.repository.DataspaceRepository
 import org.onap.cps.ri.utils.SessionManager
-import org.onap.cps.api.exceptions.DataspaceNotFoundException
-import org.onap.cps.api.model.DataNode
-import static org.onap.cps.utils.ContentType.*
 import org.onap.cps.utils.JsonObjectMapper
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.beans.factory.annotation.Value
@@ -136,9 +134,6 @@ abstract class CpsIntegrationSpecBase extends Specification {
     @Autowired
     AlternateIdMatcher alternateIdMatcher
 
-    @Autowired
-    ISet<String> moduleSetTagsBeingProcessed
-
     @Value('${ncmp.policy-executor.server.port:8080}')
     private String policyServerPort;
 
@@ -158,7 +153,7 @@ abstract class CpsIntegrationSpecBase extends Specification {
     static NO_ALTERNATE_ID = ''
     static GENERAL_TEST_DATASPACE = 'generalTestDataspace'
     static BOOKSTORE_SCHEMA_SET = 'bookstoreSchemaSet'
-    static MODULE_SYNC_WAIT_TIME_IN_SECONDS = 10
+    static MODULE_SYNC_WAIT_TIME_IN_SECONDS = 2
 
     static initialized = false
     def now = OffsetDateTime.now()
@@ -167,6 +162,7 @@ abstract class CpsIntegrationSpecBase extends Specification {
         if (!initialized) {
             cpsDataspaceService.createDataspace(GENERAL_TEST_DATASPACE)
             createStandardBookStoreSchemaSet(GENERAL_TEST_DATASPACE)
+            cpsAnchorService.createAnchor(GENERAL_TEST_DATASPACE, BOOKSTORE_SCHEMA_SET, 'owner-of-bookstore-schema-set-do-not-delete')
             initialized = true
         }
         mockDmiServer1.setDispatcher(dmiDispatcher1)
@@ -186,7 +182,7 @@ abstract class CpsIntegrationSpecBase extends Specification {
         mockDmiServer1.shutdown()
         mockDmiServer2.shutdown()
         mockPolicyServer.shutdown()
-        moduleSetTagsBeingProcessed.clear()
+        cpsModuleService.deleteAllUnusedYangModuleData()
     }
 
     def static readResourceDataFile(filename) {
index 257f10b..2bd5a4a 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2023-2024 Nordix Foundation
+ *  Copyright (C) 2023-2025 Nordix Foundation
  *  Modifications Copyright (C) 2024 TechMahindra Ltd.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the 'License');
@@ -22,7 +22,6 @@
 package org.onap.cps.integration.functional.cps
 
 import java.time.OffsetDateTime
-
 import org.onap.cps.api.CpsAnchorService
 import org.onap.cps.integration.base.FunctionalSpecBase
 import org.onap.cps.api.parameters.FetchDescendantsOption
@@ -60,17 +59,17 @@ class AnchorServiceIntegrationSpec extends FunctionalSpecBase {
         and: '1 anchor with "other" schema set is created'
             createStandardBookStoreSchemaSet(GENERAL_TEST_DATASPACE, 'otherSchemaSet')
             objectUnderTest.createAnchor(GENERAL_TEST_DATASPACE, 'otherSchemaSet', 'anchor3')
-        then: 'there are 3 anchors in the general test database'
-            assert objectUnderTest.getAnchors(GENERAL_TEST_DATASPACE).size() == 3
-        and: 'there are 2 anchors associated with bookstore schema set'
-            assert objectUnderTest.getAnchorsBySchemaSetName(GENERAL_TEST_DATASPACE, BOOKSTORE_SCHEMA_SET).size() == 2
+        then: 'there are 4 anchors in the general test database'
+            assert objectUnderTest.getAnchors(GENERAL_TEST_DATASPACE).size() == 4
+        and: 'there are 3 anchors associated with bookstore schema set'
+            assert objectUnderTest.getAnchorsBySchemaSetName(GENERAL_TEST_DATASPACE, BOOKSTORE_SCHEMA_SET).size() == 3
         and: 'there is 1 anchor associated with other schema set'
             assert objectUnderTest.getAnchorsBySchemaSetName(GENERAL_TEST_DATASPACE, 'otherSchemaSet').size() == 1
     }
 
     def 'Querying anchor(name)s (depends on previous test!).'() {
-        expect: 'there are now 3 anchors using the "stores" module (both schema sets use the same modules) '
-            assert objectUnderTest.queryAnchorNames(GENERAL_TEST_DATASPACE, ['stores', 'bookstore-types']).size() == 3
+        expect: 'there are now 4 anchors using the "stores" module (both schema sets use the same modules) '
+            assert objectUnderTest.queryAnchorNames(GENERAL_TEST_DATASPACE, ['stores', 'bookstore-types']).size() == 4
         and: 'there are no anchors using both "stores" and a "unused-model"'
             assert objectUnderTest.queryAnchorNames(GENERAL_TEST_DATASPACE, ['stores', 'unused-model']).size() == 0
     }
index 6cd7f21..178b022 100644 (file)
@@ -32,7 +32,7 @@ class DataspaceServiceIntegrationSpec extends FunctionalSpecBase {
 
     def setup() { objectUnderTest = cpsDataspaceService }
 
-    def cleanup() { cpsModuleService.deleteUnusedYangResourceModules() }
+    def cleanup() { cpsModuleService.deleteAllUnusedYangModuleData() }
 
     def 'Dataspace CRUD operations.'() {
         when: 'a dataspace is created'
index efdd71d..d801087 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2023-2024 Nordix Foundation
+ *  Copyright (C) 2023-2025 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the 'License');
  *  you may not use this file except in compliance with the License.
@@ -41,17 +41,17 @@ class ModuleServiceIntegrationSpec extends FunctionalSpecBase {
     private static def bookStoreTypesModuleReference = new ModuleReference('bookstore-types','2024-01-30')
     private static def bookStoreTypesModuleReferenceWithNamespace = new ModuleReference('bookstore-types','2024-01-30', 'org:onap:cps:types:sample')
     static def NEW_RESOURCE_REVISION = '2023-05-10'
-    static def NEW_RESOURCE_CONTENT = 'module test_module {\n' +
-        '    yang-version 1.1;\n' +
-        '    namespace "org:onap:ccsdk:sample";\n' +
-        '\n' +
-        '    prefix book-store;\n' +
-        '\n' +
-        '    revision "2023-05-10" {\n' +
-        '        description\n' +
-        '        "Sample Model";\n' +
-        '    }' +
-        '}'
+    static def NEW_RESOURCE_CONTENT = """
+        module test_module {
+            yang-version 1.1;
+            namespace "org:onap:ccsdk:sample";
+            prefix book-store;
+            revision "2023-05-10" {
+                description
+                "Sample Model";
+            }
+        }
+        """
 
     def newYangResourcesNameToContentMap = [:]
     def moduleReferences = []
@@ -61,7 +61,7 @@ class ModuleServiceIntegrationSpec extends FunctionalSpecBase {
 
     def setup() { objectUnderTest = cpsModuleService }
 
-    def cleanup() { objectUnderTest.deleteUnusedYangResourceModules() }
+    def cleanup() { objectUnderTest.deleteAllUnusedYangModuleData() }
 
     /*
         C R E A T E   S C H E M A   S E T   U S E - C A S E S
index 11a4f2c..28714fd 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2024 Nordix Foundation
+ *  Copyright (C) 2024-2025 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the 'License');
  *  you may not use this file except in compliance with the License.
 package org.onap.cps.integration.functional.ncmp
 
 import org.onap.cps.integration.base.CpsIntegrationSpecBase
-import org.onap.cps.ncmp.impl.NetworkCmProxyInventoryFacadeImpl
 import org.onap.cps.ncmp.api.inventory.models.CmHandleRegistrationResponse
-import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistration
-import org.onap.cps.ncmp.api.inventory.models.UpgradedCmHandles
 import org.onap.cps.ncmp.api.inventory.models.CmHandleState
+import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistration
 import org.onap.cps.ncmp.api.inventory.models.LockReasonCategory
+import org.onap.cps.ncmp.api.inventory.models.UpgradedCmHandles
+import org.onap.cps.ncmp.impl.NetworkCmProxyInventoryFacadeImpl
 import spock.util.concurrent.PollingConditions
 
 class CmHandleUpgradeSpec extends CpsIntegrationSpecBase {
@@ -38,10 +38,9 @@ class CmHandleUpgradeSpec extends CpsIntegrationSpecBase {
 
     def setup() {
         objectUnderTest = networkCmProxyInventoryFacade
-        moduleSyncService.clearPrivateModuleSetCache()
     }
 
-    def 'Upgrade CM-handle with new moduleSetTag or no moduleSetTag.'() {
+    def 'Upgrade CM-handle with and without (new) module set tags.'() {
         given: 'a CM-handle is created with expected initial modules: M1 and M2'
             dmiDispatcher1.moduleNamesPerCmHandleId[cmHandleId] = ['M1', 'M2']
             registerCmHandle(DMI1_URL, cmHandleId, initialModuleSetTag)
@@ -78,15 +77,16 @@ class CmHandleUpgradeSpec extends CpsIntegrationSpecBase {
         and: 'CM-handle has expected updated modules: M1 and M3'
             assert ['M1', 'M3'] == objectUnderTest.getYangResourcesModuleReferences(cmHandleId).moduleName.sort()
 
-        cleanup: 'deregister CM-handle'
+        cleanup: 'deregister CM-handle and remove all associated module resources'
             deregisterCmHandle(DMI1_URL, cmHandleId)
+            cpsModuleService.deleteAllUnusedYangModuleData()
 
         where: 'following module set tags are used'
             initialModuleSetTag | updatedModuleSetTag
             NO_MODULE_SET_TAG   | NO_MODULE_SET_TAG
-            NO_MODULE_SET_TAG   | 'new'
-            'initial'           | NO_MODULE_SET_TAG
-            'initial'           | 'new'
+            NO_MODULE_SET_TAG   | 'new@set'
+            'initial set'       | NO_MODULE_SET_TAG
+            'initial set'       | 'new@set'
     }
 
     def 'Upgrade CM-handle with existing moduleSetTag.'() {
@@ -127,8 +127,8 @@ class CmHandleUpgradeSpec extends CpsIntegrationSpecBase {
 
         where:
             initialModuleSetTag | updatedModuleSetTag
-            NO_MODULE_SET_TAG   | 'moduleSet2'
-            'moduleSet1'        | 'moduleSet2'
+            NO_MODULE_SET_TAG   | 'module@Set2'
+            'module@Set1'       | 'module@Set2'
     }
 
     def 'Skip upgrade of CM-handle with same moduleSetTag as before.'() {
index a6e56ab..43db9b2 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2024 Nordix Foundation
+ *  Copyright (C) 2024-2025 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the 'License');
  *  you may not use this file except in compliance with the License.
 package org.onap.cps.integration.functional.ncmp
 
 import io.micrometer.core.instrument.MeterRegistry
+import spock.lang.Ignore
+
+import java.util.concurrent.Executors
+import java.util.concurrent.TimeUnit
 import org.onap.cps.integration.base.CpsIntegrationSpecBase
 import org.onap.cps.ncmp.impl.inventory.sync.ModuleSyncWatchdog
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.util.StopWatch
 import spock.util.concurrent.PollingConditions
 
-import java.util.concurrent.Executors
-import java.util.concurrent.TimeUnit
-
 class ModuleSyncWatchdogIntegrationSpec extends CpsIntegrationSpecBase {
 
     ModuleSyncWatchdog objectUnderTest
@@ -46,7 +47,6 @@ class ModuleSyncWatchdogIntegrationSpec extends CpsIntegrationSpecBase {
 
     def cleanup() {
         try {
-            deregisterSequenceOfCmHandles(DMI1_URL, PARALLEL_SYNC_SAMPLE_SIZE, 1)
             moduleSyncWorkQueue.clear()
         } finally {
             executorService.shutdownNow()
@@ -54,14 +54,21 @@ class ModuleSyncWatchdogIntegrationSpec extends CpsIntegrationSpecBase {
     }
 
     def 'Watchdog is disabled for test.'() {
-        given:
+        given: 'some cm handles are registered'
             registerSequenceOfCmHandlesWithManyModuleReferencesButDoNotWaitForReady(DMI1_URL, NO_MODULE_SET_TAG, PARALLEL_SYNC_SAMPLE_SIZE, 1)
         when: 'wait a while but less then the initial delay of 10 minutes'
             Thread.sleep(3000)
         then: 'the work queue remains empty'
             assert moduleSyncWorkQueue.isEmpty()
+        cleanup: 'remove advised cm handles'
+            deregisterSequenceOfCmHandles(DMI1_URL, PARALLEL_SYNC_SAMPLE_SIZE, 1)
     }
 
+    @Ignore
+    /** this test has intermittent failures, due to timeouts.
+     *  Ignored but left here as it might be valuable to further optimization investigations.
+     **/
+
     def 'CPS-2478 Highlight (and improve) module sync inefficiencies.'() {
         given: 'register 250 cm handles with module set tag cps-2478-A'
             def numberOfTags = 2
@@ -78,34 +85,32 @@ class ModuleSyncWatchdogIntegrationSpec extends CpsIntegrationSpecBase {
         when: 'sync all advised cm handles'
             objectUnderTest.moduleSyncAdvisedCmHandles()
             Thread.sleep(100)
-        then: 'retry until all schema sets are stored in db (1 schema set  for each cm handle)'
+        then: 'retry until both schema sets are stored in db (1 schema set for each module set tag)'
             def dbSchemaSetStorageTimer = meterRegistry.get('cps.module.persistence.schemaset.store').timer()
             new PollingConditions().within(10, () -> {
                 objectUnderTest.moduleSyncAdvisedCmHandles()
                 Thread.sleep(100)
-                assert dbSchemaSetStorageTimer.count() >= 500
+                assert dbSchemaSetStorageTimer.count() == 2
             })
         then: 'wait till at least 5 batches of state updates are done (often more because of retries of locked cm handles)'
             def dbStateUpdateTimer = meterRegistry.get('cps.ncmp.cmhandle.state.update.batch').timer()
             new PollingConditions().within(10, () -> {
                 assert dbStateUpdateTimer.count() >= minimumBatches
             })
-        and: 'the db has been queried for tags exactly 2 times.'
-            def dbModuleQueriesTimer = meterRegistry.get('cps.module.service.module.reference.query.by.attribute').timer()
-            assert dbModuleQueriesTimer.count() == 2
-        and: 'exactly 2 calls to DMI to get module references'
+        and: 'one call to DMI per module set tag to get module references (may be more due to parallel processing of batches)'
             def dmiModuleRetrievalTimer = meterRegistry.get('cps.ncmp.inventory.module.references.from.dmi').timer()
-            assert dmiModuleRetrievalTimer.count() == 2
+            assert dmiModuleRetrievalTimer.count() >= numberOfTags && dmiModuleRetrievalTimer.count() <= minimumBatches
+
         and: 'log the relevant instrumentation'
-            logInstrumentation(dbModuleQueriesTimer,    'query module references')
             logInstrumentation(dmiModuleRetrievalTimer, 'get modules from DMI   ')
             logInstrumentation(dbSchemaSetStorageTimer, 'store schema sets      ')
             logInstrumentation(dbStateUpdateTimer,      'batch state updates    ')
-        cleanup: 'remove all cm handles'
+        cleanup: 'remove all test cm handles'
             // To properly measure performance the sample-size should be increased to 20,000 cm handles or higher (10,000 per tag)
             def stopWatch = new StopWatch()
             stopWatch.start()
             deregisterSequenceOfCmHandles(DMI1_URL, totalCmHandles, 1)
+            cpsModuleService.deleteAllUnusedYangModuleData()
             stopWatch.stop()
             println "*** CPS-2478, Deletion of $totalCmHandles cm handles took ${stopWatch.getTotalTimeMillis()} milliseconds"
     }
@@ -122,6 +127,8 @@ class ModuleSyncWatchdogIntegrationSpec extends CpsIntegrationSpecBase {
             Thread.sleep(50)
         then: 'the queue size is exactly the sample size'
             assert moduleSyncWorkQueue.size() == PARALLEL_SYNC_SAMPLE_SIZE
+        cleanup: 'remove all test cm handles'
+            deregisterSequenceOfCmHandles(DMI1_URL, PARALLEL_SYNC_SAMPLE_SIZE, 1)
     }
 
     def 'Populate module sync work queue on two parallel threads with a slight difference in start time.'() {
@@ -136,6 +143,8 @@ class ModuleSyncWatchdogIntegrationSpec extends CpsIntegrationSpecBase {
             Thread.sleep(50)
         then: 'the queue size is exactly the sample size'
             assert moduleSyncWorkQueue.size() == PARALLEL_SYNC_SAMPLE_SIZE
+        cleanup: 'remove all test cm handles'
+            deregisterSequenceOfCmHandles(DMI1_URL, PARALLEL_SYNC_SAMPLE_SIZE, 1)
     }
 
     def logInstrumentation(timer, description) {
@@ -151,15 +160,6 @@ class ModuleSyncWatchdogIntegrationSpec extends CpsIntegrationSpecBase {
         }
     }
 
-    def populateQueueWithoutDelayCallable = () -> {
-        try {
-            objectUnderTest.populateWorkQueueIfNeeded()
-            return 'task acquired the lock first'
-        } catch (InterruptedException e) {
-            e.printStackTrace()
-        }
-    }
-
     def populateQueueWithDelay = () -> {
         try {
             Thread.sleep(10)
index 4492e3d..e01af55 100644 (file)
@@ -38,14 +38,16 @@ class YangModulesSpec extends CpsIntegrationSpecBase {
     def setup() {
         dmiDispatcher1.moduleNamesPerCmHandleId['ch-1'] = ['M1', 'M2']
         dmiDispatcher1.moduleNamesPerCmHandleId['ch-2'] = ['M1', 'M3']
+        dmiDispatcher1.moduleNamesPerCmHandleId['ch-3'] = ['M4']
         registerCmHandle(DMI1_URL, 'ch-1', NO_MODULE_SET_TAG, 'alt-1')
         registerCmHandle(DMI1_URL, 'ch-2', NO_MODULE_SET_TAG, 'alt-2')
+        registerCmHandle(DMI1_URL, 'ch-3', 'my-module-set-tag', 'alt-3')
         // Note DMI dispatcher is not configured to return modules for this handle, so module sync will fail
         registerCmHandleWithoutWaitForReady(DMI1_URL, 'not-ready-id', NO_MODULE_SET_TAG, NO_ALTERNATE_ID)
     }
 
     def cleanup() {
-        deregisterCmHandles(DMI1_URL, ['ch-1', 'ch-2', 'not-ready-id'])
+        deregisterCmHandles(DMI1_URL, ['ch-1', 'ch-2', 'ch-3', 'not-ready-id'])
     }
 
     def 'Get yang module references returns expected modules with #scenario.'() {
@@ -56,11 +58,12 @@ class YangModulesSpec extends CpsIntegrationSpecBase {
                     .andExpect(jsonPath('$[*].moduleName', containsInAnyOrder(expectedModuleNames.toArray())))
                     .andExpect(jsonPath('$[*].revision', everyItem(equalTo('2024-01-01'))))
         where: 'following scenarios are applied'
-            scenario                 | cmHandleReference || expectedModuleNames
-            'cm-handle id'           | 'ch-1'            || ['M1', 'M2']
-            'alternate id'           | 'alt-2'           || ['M1', 'M3']
-            'not ready CM handle'    | 'not-ready-id'    || []
-            'non-existing CM handle' | 'non-existing'    || []
+            scenario                        | cmHandleReference || expectedModuleNames
+            'cm-handle id'                  | 'ch-1'            || ['M1', 'M2']
+            'alternate id'                  | 'alt-2'           || ['M1', 'M3']
+            'CM handle with module set tag' | 'ch-3'            || ['M4']
+            'not ready CM handle'           | 'not-ready-id'    || []
+            'non-existing CM handle'        | 'non-existing'    || []
     }
 
     def 'Get yang module definitions returns expected modules with #scenario.'() {
@@ -72,11 +75,12 @@ class YangModulesSpec extends CpsIntegrationSpecBase {
                     .andExpect(jsonPath('$[*].revision', everyItem(equalTo('2024-01-01'))))
                     .andExpect(jsonPath('$[*].content', not(is(emptyString()))))
         where: 'following scenarios are applied'
-            scenario                 | cmHandleReference || expectedModuleNames
-            'cm-handle id'           | 'ch-1'            || ['M1', 'M2']
-            'alternate id'           | 'alt-2'           || ['M1', 'M3']
-            'not ready CM handle'    | 'not-ready-id'    || []
-            'non-existing CM handle' | 'non-existing'    || []
+            scenario                        | cmHandleReference || expectedModuleNames
+            'cm-handle id'                  | 'ch-1'            || ['M1', 'M2']
+            'alternate id'                  | 'alt-2'           || ['M1', 'M3']
+            'CM handle with module set tag' | 'ch-3'            || ['M4']
+            'not ready CM handle'           | 'not-ready-id'    || []
+            'non-existing CM handle'        | 'non-existing'    || []
     }
 
     def 'Get yang module definition for specific module with #scenario.'() {
index b239a78..613f760 100644 (file)
@@ -100,7 +100,7 @@ class ModuleQueryPerfTest extends CpsPerfTestBase {
                 cpsModuleService.deleteSchemaSetsWithCascade(CPS_PERFORMANCE_TEST_DATASPACE, (i..i+100).collect {SCHEMA_SET_PREFIX + it})
             }
             cpsModuleService.deleteSchemaSetsWithCascade(CPS_PERFORMANCE_TEST_DATASPACE, [SCHEMA_SET_PREFIX + '0'])
-            cpsModuleService.deleteUnusedYangResourceModules()
+            cpsModuleService.deleteAllUnusedYangModuleData()
     }
 
     // This makes a Yang module of approximately target length in bytes by padding the description field with many '*'