Merge "NCMP to DMI CmSubscription Event"
authorLee Anjella Macabuhay <lee.anjella.macabuhay@est.tech>
Fri, 23 Feb 2024 10:24:09 +0000 (10:24 +0000)
committerGerrit Code Review <gerrit@onap.org>
Fri, 23 Feb 2024 10:24:09 +0000 (10:24 +0000)
44 files changed:
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandleQueryServiceImpl.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandler.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/embeddedcache/AlternateIdCacheConfig.java [deleted file]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/embeddedcache/ModuleSetTagCacheConfig.java [deleted file]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionNcmpOutEventProducer.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceService.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceServiceImpl.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/InventoryPersistence.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/InventoryPersistenceImpl.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleOperationsUtils.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncService.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncTasks.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiModelOperations.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/AlternateIdChecker.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/CmHandleIdMapper.java [deleted file]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/YangDataConverter.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandle.java
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandlerSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/AlternateIdCacheConfigSpec.groovy [deleted file]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/ModuleSetTagCacheConfigSpec.groovy [deleted file]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionNcmpOutEventProducerSpec.groovy [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/service/CmNotificationSubscriptionPersistenceServiceImplSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/InventoryPersistenceImplSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleOperationsUtilsSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncServiceSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncTasksSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiModelOperationsSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/AlternateIdCheckerSpec.groovy [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/CmHandleIdMapperSpec.groovy [deleted file]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/YangDataConverterSpec.groovy
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/NcmpCmHandleCreateSpec.groovy [new file with mode: 0644]
integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmHandleRegistrationSpec.groovy [deleted file]
integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmHandleUpgradeSpec.groovy [new file with mode: 0644]
integration-test/src/test/resources/data/mock-dmi-responses/bookStoreAWithModules_M1_M2_ResourcesResponse.json [new file with mode: 0644]
integration-test/src/test/resources/data/mock-dmi-responses/bookStoreAWithModules_M1_M2_Response.json [new file with mode: 0644]
integration-test/src/test/resources/data/mock-dmi-responses/bookStoreBWithModules_M1_M3_ResourcesResponse.json [new file with mode: 0644]
integration-test/src/test/resources/data/mock-dmi-responses/bookStoreBWithModules_M1_M3_Response.json [new file with mode: 0644]
integration-test/src/test/resources/data/mock-dmi-responses/ietfYangModuleResourcesResponse.json [deleted file]
integration-test/src/test/resources/data/mock-dmi-responses/ietfYangModuleResponse.json [deleted file]

index 6f92a9e..8890d14 100644 (file)
@@ -241,8 +241,7 @@ public class NetworkCmProxyCmHandleQueryServiceImpl implements NetworkCmProxyCmH
     }
 
     private NcmpServiceCmHandle createNcmpServiceCmHandle(final DataNode dataNode) {
-        return convertYangModelCmHandleToNcmpServiceCmHandle(YangDataConverter
-                .convertCmHandleToYangModel(dataNode, dataNode.getLeaves().get("id").toString()));
+        return convertYangModelCmHandleToNcmpServiceCmHandle(YangDataConverter.convertCmHandleToYangModel(dataNode));
     }
 
     private Collection<String> executeQueries(final CmHandleQueryServiceParameters cmHandleQueryServiceParameters,
index 05b83b9..1f2748b 100755 (executable)
@@ -35,11 +35,9 @@ import static org.onap.cps.ncmp.api.impl.utils.RestQueryParametersValidator.vali
 
 import com.google.common.collect.Lists;
 import com.hazelcast.map.IMap;
-import java.text.MessageFormat;
 import java.time.OffsetDateTime;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.EnumMap;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -50,7 +48,6 @@ import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.onap.cps.api.CpsDataService;
-import org.onap.cps.ncmp.api.NcmpResponseStatus;
 import org.onap.cps.ncmp.api.NetworkCmProxyCmHandleQueryService;
 import org.onap.cps.ncmp.api.NetworkCmProxyDataService;
 import org.onap.cps.ncmp.api.impl.events.lcm.LcmEventsCmHandleStateHandler;
@@ -66,7 +63,7 @@ import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations;
 import org.onap.cps.ncmp.api.impl.operations.OperationType;
 import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevel;
 import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevelManager;
-import org.onap.cps.ncmp.api.impl.utils.CmHandleIdMapper;
+import org.onap.cps.ncmp.api.impl.utils.AlternateIdChecker;
 import org.onap.cps.ncmp.api.impl.utils.CmHandleQueryConditions;
 import org.onap.cps.ncmp.api.impl.utils.InventoryQueryConditions;
 import org.onap.cps.ncmp.api.impl.utils.YangDataConverter;
@@ -106,37 +103,24 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
     private final IMap<String, Object> moduleSyncStartedOnCmHandles;
     private final Map<String, TrustLevel> trustLevelPerDmiPlugin;
     private final TrustLevelManager trustLevelManager;
-    private final CmHandleIdMapper cmHandleIdMapper;
-    private final Map<String, Collection<ModuleReference>> moduleSetTagCache;
+    private final AlternateIdChecker alternateIdChecker;
 
     @Override
     public DmiPluginRegistrationResponse updateDmiRegistrationAndSyncModule(
         final DmiPluginRegistration dmiPluginRegistration) {
-        cacheAlternateIds(dmiPluginRegistration.getCreatedCmHandles());
+
         dmiPluginRegistration.validateDmiPluginRegistration();
         final DmiPluginRegistrationResponse dmiPluginRegistrationResponse = new DmiPluginRegistrationResponse();
 
         setTrustLevelPerDmiPlugin(dmiPluginRegistration);
 
-        if (!dmiPluginRegistration.getRemovedCmHandles().isEmpty()) {
-            dmiPluginRegistrationResponse.setRemovedCmHandles(
-                parseAndProcessDeletedCmHandlesInRegistration(dmiPluginRegistration.getRemovedCmHandles()));
-        }
+        processRemovedCmHandles(dmiPluginRegistration, dmiPluginRegistrationResponse);
 
-        if (!dmiPluginRegistration.getCreatedCmHandles().isEmpty()) {
-            dmiPluginRegistrationResponse.setCreatedCmHandles(
-                parseAndProcessCreatedCmHandlesInRegistration(dmiPluginRegistration));
-        }
-        if (!dmiPluginRegistration.getUpdatedCmHandles().isEmpty()) {
-            dmiPluginRegistrationResponse.setUpdatedCmHandles(
-                networkCmProxyDataServicePropertyHandler
-                    .updateCmHandleProperties(dmiPluginRegistration.getUpdatedCmHandles()));
-        }
-        if (dmiPluginRegistration.getUpgradedCmHandles() != null
-            && !dmiPluginRegistration.getUpgradedCmHandles().getCmHandles().isEmpty()) {
-            dmiPluginRegistrationResponse.setUpgradedCmHandles(
-                parseAndProcessUpgradedCmHandlesInRegistration(dmiPluginRegistration));
-        }
+        processCreatedCmHandles(dmiPluginRegistration, dmiPluginRegistrationResponse);
+
+        processUpdatedCmHandles(dmiPluginRegistration, dmiPluginRegistrationResponse);
+
+        processUpgradedCmHandles(dmiPluginRegistration, dmiPluginRegistrationResponse);
 
         return dmiPluginRegistrationResponse;
     }
@@ -329,21 +313,24 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
      * @return cm-handle registration response for create cm-handle requests.
      */
     public List<CmHandleRegistrationResponse> parseAndProcessCreatedCmHandlesInRegistration(
-        final DmiPluginRegistration dmiPluginRegistration) {
+        final DmiPluginRegistration dmiPluginRegistration, final Collection<String> acceptedCmHandleIds) {
         final List<NcmpServiceCmHandle> cmHandlesToBeCreated = dmiPluginRegistration.getCreatedCmHandles();
         final Map<String, TrustLevel> initialTrustLevelPerCmHandleId = new HashMap<>(cmHandlesToBeCreated.size());
         final List<YangModelCmHandle> yangModelCmHandles = new ArrayList<>(cmHandlesToBeCreated.size());
         cmHandlesToBeCreated
                 .forEach(cmHandle -> {
-                    final YangModelCmHandle yangModelCmHandle = YangModelCmHandle.toYangModelCmHandle(
-                            dmiPluginRegistration.getDmiPlugin(),
-                            dmiPluginRegistration.getDmiDataPlugin(),
-                            dmiPluginRegistration.getDmiModelPlugin(),
-                            cmHandle,
-                            cmHandle.getModuleSetTag(),
-                            cmHandle.getAlternateId());
-                    yangModelCmHandles.add(yangModelCmHandle);
-                    initialTrustLevelPerCmHandleId.put(cmHandle.getCmHandleId(), cmHandle.getRegistrationTrustLevel());
+                    if (acceptedCmHandleIds.contains(cmHandle.getCmHandleId())) {
+                        final YangModelCmHandle yangModelCmHandle = YangModelCmHandle.toYangModelCmHandle(
+                                dmiPluginRegistration.getDmiPlugin(),
+                                dmiPluginRegistration.getDmiDataPlugin(),
+                                dmiPluginRegistration.getDmiModelPlugin(),
+                                cmHandle,
+                                cmHandle.getModuleSetTag(),
+                                cmHandle.getAlternateId());
+                        yangModelCmHandles.add(yangModelCmHandle);
+                        initialTrustLevelPerCmHandleId.put(cmHandle.getCmHandleId(),
+                            cmHandle.getRegistrationTrustLevel());
+                    }
                 });
         return registerNewCmHandles(yangModelCmHandles, initialTrustLevelPerCmHandleId);
     }
@@ -354,16 +341,12 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
             new ArrayList<>(tobeRemovedCmHandles.size());
         final Collection<YangModelCmHandle> yangModelCmHandles =
             inventoryPersistence.getYangModelCmHandles(tobeRemovedCmHandles);
-        final Set<String> moduleSetTags = yangModelCmHandles.stream().map(YangModelCmHandle::getModuleSetTag)
-                .collect(Collectors.toSet());
         updateCmHandleStateBatch(yangModelCmHandles, CmHandleState.DELETING);
 
         final Set<String> notDeletedCmHandles = new HashSet<>();
         for (final List<String> tobeRemovedCmHandleBatch : Lists.partition(tobeRemovedCmHandles, DELETE_BATCH_SIZE)) {
             try {
                 batchDeleteCmHandlesFromDbAndModuleSyncMap(tobeRemovedCmHandleBatch);
-                moduleSetTags.forEach(moduleSetTagCache::remove);
-                log.debug("Removed module set tags: {}", moduleSetTags);
                 tobeRemovedCmHandleBatch.forEach(cmHandleId ->
                     cmHandleRegistrationResponses.add(CmHandleRegistrationResponse.createSuccessResponse(cmHandleId)));
 
@@ -382,68 +365,100 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
 
         yangModelCmHandles.removeIf(yangModelCmHandle -> notDeletedCmHandles.contains(yangModelCmHandle.getId()));
         updateCmHandleStateBatch(yangModelCmHandles, CmHandleState.DELETED);
-        removeEntriesFromAlternateIdCache(yangModelCmHandles);
 
         return cmHandleRegistrationResponses;
     }
 
-    private void removeEntriesFromAlternateIdCache(final Collection<YangModelCmHandle> yangModelCmHandles) {
-        for (final YangModelCmHandle yangModelCmHandle : yangModelCmHandles) {
-            cmHandleIdMapper.removeMapping(yangModelCmHandle.getId());
+    private void processRemovedCmHandles(final DmiPluginRegistration dmiPluginRegistration,
+                                         final DmiPluginRegistrationResponse dmiPluginRegistrationResponse) {
+        if (!dmiPluginRegistration.getRemovedCmHandles().isEmpty()) {
+            dmiPluginRegistrationResponse.setRemovedCmHandles(
+                parseAndProcessDeletedCmHandlesInRegistration(dmiPluginRegistration.getRemovedCmHandles()));
+        }
+    }
+
+    private void processCreatedCmHandles(final DmiPluginRegistration dmiPluginRegistration,
+                                         final DmiPluginRegistrationResponse dmiPluginRegistrationResponse) {
+        final Collection<String> acceptedCmHandleIds = alternateIdChecker
+            .getIdsOfCmHandlesWithAcceptableAlternateId(dmiPluginRegistration.getCreatedCmHandles());
+        if (!acceptedCmHandleIds.isEmpty()) {
+            dmiPluginRegistrationResponse.setCreatedCmHandles(
+                parseAndProcessCreatedCmHandlesInRegistration(dmiPluginRegistration, acceptedCmHandleIds));
+        }
+    }
+
+    private void processUpdatedCmHandles(final DmiPluginRegistration dmiPluginRegistration,
+                                         final DmiPluginRegistrationResponse dmiPluginRegistrationResponse) {
+        if (!dmiPluginRegistration.getUpdatedCmHandles().isEmpty()) {
+            dmiPluginRegistrationResponse.setUpdatedCmHandles(
+                networkCmProxyDataServicePropertyHandler
+                    .updateCmHandleProperties(dmiPluginRegistration.getUpdatedCmHandles()));
         }
     }
 
+    private void processUpgradedCmHandles(final DmiPluginRegistration dmiPluginRegistration,
+                                          final DmiPluginRegistrationResponse dmiPluginRegistrationResponse) {
+        if (dmiPluginRegistration.getUpgradedCmHandles() != null
+            && !dmiPluginRegistration.getUpgradedCmHandles().getCmHandles().isEmpty()) {
+            dmiPluginRegistrationResponse.setUpgradedCmHandles(
+                parseAndProcessUpgradedCmHandlesInRegistration(dmiPluginRegistration));
+        }
+    }
+
+
     protected List<CmHandleRegistrationResponse> parseAndProcessUpgradedCmHandlesInRegistration(
         final DmiPluginRegistration dmiPluginRegistration) {
 
         final List<String> upgradedCmHandleIds = dmiPluginRegistration.getUpgradedCmHandles().getCmHandles();
-
+        final String upgradedModuleSetTag = dmiPluginRegistration.getUpgradedCmHandles().getModuleSetTag();
         final Map<YangModelCmHandle, CmHandleState> acceptedCmHandleStatePerCmHandle
                 = new HashMap<>(upgradedCmHandleIds.size());
+        final List<CmHandleRegistrationResponse> cmHandleUpgradeResponses = new ArrayList<>(upgradedCmHandleIds.size());
 
-        final Map<NcmpResponseStatus, List<String>> failedCmHandlesPerResponseStatus
-                = new EnumMap<>(NcmpResponseStatus.class);
-        final List<String> nonExistingCmHandleIds = new ArrayList<>();
-        final List<String> nonReadyCmHandleIds = new ArrayList<>();
-        final List<String> invalidCmHandleIds = new ArrayList<>();
-
-        upgradedCmHandleIds.forEach(cmHandleId -> {
+        for (final String cmHandleId : upgradedCmHandleIds) {
             try {
-                if (cmHandleQueries.cmHandleHasState(cmHandleId, CmHandleState.READY)) {
-                    final YangModelCmHandle yangModelCmHandleWithUpgradeDetails
-                            = createYangModelCmHandle(dmiPluginRegistration, cmHandleId);
-                    acceptedCmHandleStatePerCmHandle.put(yangModelCmHandleWithUpgradeDetails, CmHandleState.LOCKED);
+                final YangModelCmHandle yangModelCmHandle = inventoryPersistence.getYangModelCmHandle(cmHandleId);
+                if (yangModelCmHandle.getCompositeState().getCmHandleState() == CmHandleState.READY) {
+                    if (moduleUpgradeCanBeSkipped(yangModelCmHandle, upgradedModuleSetTag)) {
+                        cmHandleUpgradeResponses.add(CmHandleRegistrationResponse.createSuccessResponse(cmHandleId));
+                    } else {
+                        updateYangModelCmHandleForUpgrade(yangModelCmHandle, upgradedModuleSetTag);
+                        acceptedCmHandleStatePerCmHandle.put(yangModelCmHandle, CmHandleState.LOCKED);
+                    }
                 } else {
-                    nonReadyCmHandleIds.add(cmHandleId);
+                    cmHandleUpgradeResponses.add(
+                            CmHandleRegistrationResponse.createFailureResponse(cmHandleId, CM_HANDLES_NOT_READY));
                 }
             } catch (final DataNodeNotFoundException dataNodeNotFoundException) {
                 log.error("Unable to find data node for cm handle id : {} , caused by : {}",
                         cmHandleId, dataNodeNotFoundException.getMessage());
-                nonExistingCmHandleIds.add(cmHandleId);
+                cmHandleUpgradeResponses.add(
+                        CmHandleRegistrationResponse.createFailureResponse(cmHandleId, CM_HANDLES_NOT_FOUND));
             } catch (final DataValidationException dataValidationException) {
                 log.error("Unable to upgrade cm handle id: {}, caused by : {}",
                         cmHandleId, dataValidationException.getMessage());
-                invalidCmHandleIds.add(cmHandleId);
+                cmHandleUpgradeResponses.add(
+                        CmHandleRegistrationResponse.createFailureResponse(cmHandleId, CM_HANDLE_INVALID_ID));
             }
-        });
-        failedCmHandlesPerResponseStatus.put(CM_HANDLES_NOT_READY, nonReadyCmHandleIds);
-        failedCmHandlesPerResponseStatus.put(CM_HANDLES_NOT_FOUND, nonExistingCmHandleIds);
-        failedCmHandlesPerResponseStatus.put(CM_HANDLE_INVALID_ID, invalidCmHandleIds);
-        return mergeAllCmHandleResponses(acceptedCmHandleStatePerCmHandle, failedCmHandlesPerResponseStatus);
-    }
-
-    private static YangModelCmHandle createYangModelCmHandle(final DmiPluginRegistration dmiPluginRegistration,
-                                                             final String cmHandleId) {
-        final NcmpServiceCmHandle ncmpServiceCmHandle = new NcmpServiceCmHandle();
-        ncmpServiceCmHandle.setCmHandleId(cmHandleId);
-        final String moduleSetTag = dmiPluginRegistration.getUpgradedCmHandles().getModuleSetTag();
-        final String lockReasonWithModuleSetTag = MessageFormat.format(
-                ModuleOperationsUtils.MODULE_SET_TAG_MESSAGE_FORMAT, moduleSetTag);
-        ncmpServiceCmHandle.setCompositeState(new CompositeStateBuilder().withCmHandleState(CmHandleState.READY)
+        }
+        cmHandleUpgradeResponses.addAll(upgradeCmHandles(acceptedCmHandleStatePerCmHandle));
+        return cmHandleUpgradeResponses;
+    }
+
+    private static boolean moduleUpgradeCanBeSkipped(final YangModelCmHandle yangModelCmHandle,
+                                                     final String upgradedModuleSetTag) {
+        if (StringUtils.isBlank(upgradedModuleSetTag)) {
+            return false;
+        }
+        return yangModelCmHandle.getModuleSetTag().equals(upgradedModuleSetTag);
+    }
+
+    private static void updateYangModelCmHandleForUpgrade(final YangModelCmHandle yangModelCmHandle,
+                                                          final String upgradedModuleSetTag) {
+        final String lockReasonWithModuleSetTag = String.format(ModuleOperationsUtils.MODULE_SET_TAG_MESSAGE_FORMAT,
+                upgradedModuleSetTag);
+        yangModelCmHandle.setCompositeState(new CompositeStateBuilder().withCmHandleState(CmHandleState.READY)
                 .withLockReason(MODULE_UPGRADE, lockReasonWithModuleSetTag).build());
-        return YangModelCmHandle.toYangModelCmHandle(dmiPluginRegistration.getDmiPlugin(),
-                dmiPluginRegistration.getDmiDataPlugin(), dmiPluginRegistration.getDmiModelPlugin(),
-                ncmpServiceCmHandle, moduleSetTag, ncmpServiceCmHandle.getAlternateId());
     }
 
     private CmHandleRegistrationResponse deleteCmHandleAndGetCmHandleRegistrationResponse(final String cmHandleId) {
@@ -513,17 +528,6 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
         }
     }
 
-    private List<CmHandleRegistrationResponse> mergeAllCmHandleResponses(
-            final Map<YangModelCmHandle, CmHandleState> acceptedCmHandleStatePerCmHandle,
-            final Map<NcmpResponseStatus, List<String>> failedCmHandlesPerResponseStatus) {
-        final List<CmHandleRegistrationResponse> cmHandleUpgradeResponses
-                = upgradeCmHandles(acceptedCmHandleStatePerCmHandle);
-        failedCmHandlesPerResponseStatus.forEach((ncmpResponseStatus, cmHandleIds) ->
-            cmHandleIds.forEach(cmHandleId -> cmHandleUpgradeResponses.add(CmHandleRegistrationResponse
-                .createFailureResponse(cmHandleId, ncmpResponseStatus))));
-        return cmHandleUpgradeResponses;
-    }
-
     private List<CmHandleRegistrationResponse> upgradeCmHandles(final Map<YangModelCmHandle, CmHandleState>
                                                                     cmHandleStatePerCmHandle) {
         final List<String> cmHandleIds = getCmHandleIds(cmHandleStatePerCmHandle);
@@ -549,12 +553,6 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
         }
     }
 
-    private void cacheAlternateIds(final Collection<NcmpServiceCmHandle> ncmpServiceCmHandles) {
-        for (final NcmpServiceCmHandle ncmpServiceCmHandle : ncmpServiceCmHandles) {
-            if (!StringUtils.isEmpty(ncmpServiceCmHandle.getAlternateId())) {
-                cmHandleIdMapper.addMapping(ncmpServiceCmHandle.getCmHandleId(), ncmpServiceCmHandle.getAlternateId());
-            }
-        }
-    }
+
 
 }
index 13b3fca..1478c86 100644 (file)
@@ -45,7 +45,7 @@ import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.onap.cps.api.CpsDataService;
 import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence;
-import org.onap.cps.ncmp.api.impl.utils.CmHandleIdMapper;
+import org.onap.cps.ncmp.api.impl.utils.AlternateIdChecker;
 import org.onap.cps.ncmp.api.impl.utils.YangDataConverter;
 import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
 import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse;
@@ -67,7 +67,7 @@ public class NetworkCmProxyDataServicePropertyHandler {
     private final InventoryPersistence inventoryPersistence;
     private final CpsDataService cpsDataService;
     private final JsonObjectMapper jsonObjectMapper;
-    private final CmHandleIdMapper cmHandleIdMapper;
+    private final AlternateIdChecker alternateIdChecker;
 
     /**
      * Iterates over incoming ncmpServiceCmHandles and update the dataNodes based on the updated attributes.
@@ -81,8 +81,8 @@ public class NetworkCmProxyDataServicePropertyHandler {
         for (final NcmpServiceCmHandle ncmpServiceCmHandle : ncmpServiceCmHandles) {
             final String cmHandleId = ncmpServiceCmHandle.getCmHandleId();
             try {
-                final DataNode existingCmHandleDataNode = inventoryPersistence.getCmHandleDataNode(cmHandleId)
-                        .iterator().next();
+                final DataNode existingCmHandleDataNode = inventoryPersistence
+                    .getCmHandleDataNodeByCmHandleId(cmHandleId).iterator().next();
                 updateAlternateId(existingCmHandleDataNode, ncmpServiceCmHandle);
                 processUpdates(existingCmHandleDataNode, ncmpServiceCmHandle);
                 cmHandleRegistrationResponses.add(CmHandleRegistrationResponse.createSuccessResponse(cmHandleId));
@@ -105,17 +105,13 @@ public class NetworkCmProxyDataServicePropertyHandler {
 
     private void updateAlternateId(final DataNode existingCmHandleDataNode,
                                    final NcmpServiceCmHandle ncmpServiceCmHandle) {
+        final YangModelCmHandle yangModelCmHandle =
+            YangDataConverter.convertCmHandleToYangModel(existingCmHandleDataNode);
+        final String currentAlternateId = yangModelCmHandle.getAlternateId();
         final String newAlternateId = ncmpServiceCmHandle.getAlternateId();
-        if (cmHandleIdMapper.addMapping(ncmpServiceCmHandle.getCmHandleId(), newAlternateId)) {
-            try {
-                final YangModelCmHandle yangModelCmHandle =
-                        YangDataConverter.convertCmHandleToYangModel(existingCmHandleDataNode,
-                                ncmpServiceCmHandle.getCmHandleId());
-                setAndUpdateAlternateId(yangModelCmHandle, newAlternateId);
-            } catch (final Exception e) {
-                cmHandleIdMapper.removeMapping(ncmpServiceCmHandle.getCmHandleId());
-                throw e;
-            }
+        if (alternateIdChecker.canApplyAlternateId(ncmpServiceCmHandle.getCmHandleId(),
+            currentAlternateId, newAlternateId)) {
+            setAndUpdateAlternateId(yangModelCmHandle, newAlternateId);
         }
     }
 
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/embeddedcache/AlternateIdCacheConfig.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/embeddedcache/AlternateIdCacheConfig.java
deleted file mode 100644 (file)
index a69d144..0000000
+++ /dev/null
@@ -1,59 +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.ncmp.api.impl.config.embeddedcache;
-
-import com.hazelcast.config.MapConfig;
-import java.util.Map;
-import org.onap.cps.cache.HazelcastCacheConfig;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-@Configuration
-public class AlternateIdCacheConfig extends HazelcastCacheConfig {
-
-    private static final MapConfig alternateIdPerCmHandleIdMapConfig =
-            createMapConfig("alternateIdPerCmHandleIdMapConfig");
-
-    private static final MapConfig cmHandleIdPerAlternateIdMapConfig =
-            createMapConfig("cmHandleIdPerAlternateIdMapConfig");
-
-    /**
-     * Distributed instance of alternate id cache containing the alternate id per cm handle id.
-     *
-     * @return the cached map.
-     */
-    @Bean
-    public Map<String, String> alternateIdPerCmHandleId() {
-        return createHazelcastInstance("hazelcastInstanceAlternateIdPerCmHandleIdMap",
-                alternateIdPerCmHandleIdMapConfig).getMap("alternateIdPerCmHandleId");
-    }
-
-    /**
-     * Distributed instance of alternate id cache containing the cm handle id per alternate id.
-     *
-     * @return the cached map.
-     */
-    @Bean
-    public Map<String, String> cmHandleIdPerAlternateId() {
-        return createHazelcastInstance("hazelcastInstanceCmHandleIdPerAlternateIdMap",
-                cmHandleIdPerAlternateIdMapConfig).getMap("cmHandleIdPerAlternateId");
-    }
-}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/embeddedcache/ModuleSetTagCacheConfig.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/embeddedcache/ModuleSetTagCacheConfig.java
deleted file mode 100644 (file)
index a791a3b..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- *  ============LICENSE_START=======================================================
- *  Copyright (C) 2023 Nordix Foundation
- *  ================================================================================
- *  Licensed under the Apache License, Version 2.0 (the "License");
- *  you may not use this file except in compliance with the License.
- *  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.ncmp.api.impl.config.embeddedcache;
-
-import com.hazelcast.config.MapConfig;
-import java.util.Collection;
-import java.util.Map;
-import org.onap.cps.cache.HazelcastCacheConfig;
-import org.onap.cps.spi.model.ModuleReference;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-@Configuration
-public class ModuleSetTagCacheConfig extends HazelcastCacheConfig {
-
-    private static final MapConfig moduleSetTagCacheMapConfig = createMapConfig("moduleSetTagCacheMapConfig");
-
-    /**
-     * Map instance for cached ModulesSetTags.
-     *
-     * @return configured map of ModuleSetTags
-     */
-    @Bean
-    public Map<String, Collection<ModuleReference>> moduleSetTagCache() {
-        return createHazelcastInstance("moduleSetTags", moduleSetTagCacheMapConfig)
-                .getMap("moduleSetTagCache");
-    }
-}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionNcmpOutEventProducer.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionNcmpOutEventProducer.java
new file mode 100644 (file)
index 0000000..21468c3
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * ============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.ncmp.api.impl.events.cmsubscription;
+
+import io.cloudevents.CloudEvent;
+import io.cloudevents.core.builder.CloudEventBuilder;
+import java.net.URI;
+import java.util.UUID;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.events.EventsPublisher;
+import org.onap.cps.ncmp.events.cmsubscription_merge1_0_0.ncmp_to_client.CmNotificationSubscriptionNcmpOutEvent;
+import org.onap.cps.utils.JsonObjectMapper;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.stereotype.Component;
+
+@Component
+@Slf4j
+@RequiredArgsConstructor
+@ConditionalOnProperty(name = "notification.enabled", havingValue = "true", matchIfMissing = true)
+public class CmNotificationSubscriptionNcmpOutEventProducer {
+
+    private final EventsPublisher<CloudEvent> eventsPublisher;
+    private final JsonObjectMapper jsonObjectMapper;
+
+    @Value("${app.ncmp.avc.subscription-outcome-topic}")
+    private String cmNotificationSubscriptionNcmpOutEventTopic;
+
+    /**
+     * Publish the event to the client who requested the subscription with key as subscription id and event is Cloud
+     * Event compliant.
+     *
+     * @param subscriptionId                         Cm Subscription Id
+     * @param eventType                              Type of event
+     * @param cmNotificationSubscriptionNcmpOutEvent Cm Notification Subscription Event for the client
+     */
+    public void publishCmNotificationSubscriptionNcmpOutEvent(final String subscriptionId, final String eventType,
+            final CmNotificationSubscriptionNcmpOutEvent cmNotificationSubscriptionNcmpOutEvent) {
+
+        eventsPublisher.publishCloudEvent(cmNotificationSubscriptionNcmpOutEventTopic, subscriptionId,
+                buildAndGetCmNotificationNcmpOutEventAsCloudEvent(subscriptionId, eventType,
+                        cmNotificationSubscriptionNcmpOutEvent));
+
+    }
+
+    private CloudEvent buildAndGetCmNotificationNcmpOutEventAsCloudEvent(final String subscriptionId,
+            final String eventType,
+            final CmNotificationSubscriptionNcmpOutEvent cmNotificationSubscriptionNcmpOutEvent) {
+
+        return CloudEventBuilder.v1().withId(UUID.randomUUID().toString()).withType(eventType)
+                .withSource(URI.create("NCMP")).withDataSchema(URI.create("org.onap.ncmp.cm.subscription:1.0.0"))
+                .withExtension("correlationid", subscriptionId)
+                .withData(jsonObjectMapper.asJsonBytes(cmNotificationSubscriptionNcmpOutEvent)).build();
+    }
+
+}
index 189fbb5..38f3db9 100644 (file)
@@ -39,6 +39,14 @@ public interface CmNotificationSubscriptionPersistenceService {
     boolean isOngoingCmNotificationSubscription(final DatastoreType datastoreType, final String cmHandleId,
             final String xpath);
 
+    /**
+     * Check if the subscription ID is unique against ongoing subscriptions.
+     *
+     * @param subscriptionId subscription ID
+     * @return true if subscriptionId is not used in active subscriptions, otherwise false
+     */
+    boolean isUniqueSubscriptionId(final String subscriptionId);
+
     /**
      * Get all ongoing cm notification subscription based on the parameters.
      *
index 63f12ad..6e4997a 100644 (file)
@@ -36,9 +36,13 @@ import org.springframework.stereotype.Service;
 @RequiredArgsConstructor
 public class CmNotificationSubscriptionPersistenceServiceImpl implements CmNotificationSubscriptionPersistenceService {
 
+    private static final String SUBSCRIPTION_ANCHOR_NAME = "cm-data-subscriptions";
     private static final String IS_ONGOING_CM_SUBSCRIPTION_CPS_PATH_QUERY = """
             /datastores/datastore[@name='%s']/cm-handles/cm-handle[@id='%s']/filters/filter[@xpath='%s']
             """.trim();
+    private static final String SUBSCRIPTION_IDS_CPS_PATH_QUERY = """
+            //filter/subscriptionIds[text()='%s']
+            """.trim();
 
     private final CpsQueryService cpsQueryService;
 
@@ -48,6 +52,13 @@ public class CmNotificationSubscriptionPersistenceServiceImpl implements CmNotif
         return !getOngoingCmNotificationSubscriptionIds(datastoreType, cmHandleId, xpath).isEmpty();
     }
 
+    @Override
+    public boolean isUniqueSubscriptionId(final String subscriptionId) {
+        return cpsQueryService.queryDataNodes(NCMP_DATASPACE_NAME, SUBSCRIPTION_ANCHOR_NAME,
+                SUBSCRIPTION_IDS_CPS_PATH_QUERY.formatted(subscriptionId),
+                FetchDescendantsOption.OMIT_DESCENDANTS).isEmpty();
+    }
+
     @Override
     public Collection<String> getOngoingCmNotificationSubscriptionIds(final DatastoreType datastoreType,
             final String cmHandleId, final String xpath) {
index dcd0368..e230b3f 100644 (file)
@@ -114,12 +114,20 @@ public interface InventoryPersistence extends NcmpPersistence {
     void saveCmHandleBatch(List<YangModelCmHandle> yangModelCmHandles);
 
     /**
-     * Get data node of given cm handle.
+     * Get data node with the given cm handle id.
      *
      * @param cmHandleId cmHandle ID
      * @return data node
      */
-    Collection<DataNode> getCmHandleDataNode(String cmHandleId);
+    Collection<DataNode> getCmHandleDataNodeByCmHandleId(String cmHandleId);
+
+    /**
+     * Get data node with the given alternate id.
+     *
+     * @param alternateId alternate ID
+     * @return data node
+     */
+    DataNode getCmHandleDataNodeByAlternateId(String alternateId);
 
     /**
      * Get collection of data nodes of given cm handles.
index 3b70786..3ae3dd9 100644 (file)
@@ -22,6 +22,8 @@
 
 package org.onap.cps.ncmp.api.impl.inventory;
 
+import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS;
+
 import com.google.common.collect.Lists;
 import java.time.OffsetDateTime;
 import java.util.ArrayList;
@@ -37,6 +39,7 @@ import org.onap.cps.api.CpsModuleService;
 import org.onap.cps.ncmp.api.impl.utils.YangDataConverter;
 import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
 import org.onap.cps.spi.FetchDescendantsOption;
+import org.onap.cps.spi.exceptions.DataNodeNotFoundException;
 import org.onap.cps.spi.exceptions.DataValidationException;
 import org.onap.cps.spi.model.DataNode;
 import org.onap.cps.spi.model.ModuleDefinition;
@@ -54,6 +57,7 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv
     private final CpsModuleService cpsModuleService;
     private final CpsAnchorService cpsAnchorService;
     private final CpsValidator cpsValidator;
+    private final CmHandleQueries cmHandleQueries;
 
     /**
      * initialize an inventory persistence object.
@@ -66,18 +70,19 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv
      */
     public InventoryPersistenceImpl(final JsonObjectMapper jsonObjectMapper, final CpsDataService cpsDataService,
                                     final CpsModuleService cpsModuleService, final CpsValidator cpsValidator,
-                                    final CpsAnchorService cpsAnchorService) {
+                                    final CpsAnchorService cpsAnchorService, final CmHandleQueries cmHandleQueries) {
         super(jsonObjectMapper, cpsDataService, cpsModuleService, cpsValidator);
         this.cpsModuleService = cpsModuleService;
         this.cpsAnchorService = cpsAnchorService;
         this.cpsValidator = cpsValidator;
+        this.cmHandleQueries = cmHandleQueries;
     }
 
 
     @Override
     public CompositeState getCmHandleState(final String cmHandleId) {
         final DataNode stateAsDataNode = cpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
-                        createCmHandleXPath(cmHandleId) + "/state", FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS)
+                        getXPathForCmHandleById(cmHandleId) + "/state", FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS)
                 .iterator().next();
         cpsValidator.validateNameCharacters(cmHandleId);
         return new CompositeStateBuilder().fromDataNode(stateAsDataNode).build();
@@ -87,14 +92,14 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv
     public void saveCmHandleState(final String cmHandleId, final CompositeState compositeState) {
         final String cmHandleJsonData = createStateJsonData(jsonObjectMapper.asJsonString(compositeState));
         cpsDataService.updateDataNodeAndDescendants(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
-                createCmHandleXPath(cmHandleId), cmHandleJsonData, OffsetDateTime.now());
+                getXPathForCmHandleById(cmHandleId), cmHandleJsonData, OffsetDateTime.now());
     }
 
     @Override
     public void saveCmHandleStateBatch(final Map<String, CompositeState> cmHandleStatePerCmHandleId) {
         final Map<String, String> cmHandlesJsonDataMap = new HashMap<>();
         cmHandleStatePerCmHandleId.forEach((cmHandleId, compositeState) -> cmHandlesJsonDataMap.put(
-                createCmHandleXPath(cmHandleId),
+                getXPathForCmHandleById(cmHandleId),
                 createStateJsonData(jsonObjectMapper.asJsonString(compositeState))));
         cpsDataService.updateDataNodesAndDescendants(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
                 cmHandlesJsonDataMap, OffsetDateTime.now());
@@ -103,8 +108,8 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv
     @Override
     public YangModelCmHandle getYangModelCmHandle(final String cmHandleId) {
         cpsValidator.validateNameCharacters(cmHandleId);
-        final DataNode dataNode = getCmHandleDataNode(cmHandleId).iterator().next();
-        return YangDataConverter.convertCmHandleToYangModel(dataNode, cmHandleId);
+        final DataNode dataNode = getCmHandleDataNodeByCmHandleId(cmHandleId).iterator().next();
+        return YangDataConverter.convertCmHandleToYangModel(dataNode);
     }
 
     @Override
@@ -158,14 +163,26 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv
     }
 
     @Override
-    public Collection<DataNode> getCmHandleDataNode(final String cmHandleId) {
-        return this.getDataNode(createCmHandleXPath(cmHandleId));
+    public Collection<DataNode> getCmHandleDataNodeByCmHandleId(final String cmHandleId) {
+        return this.getDataNode(getXPathForCmHandleById(cmHandleId));
+    }
+
+    @Override
+    public DataNode getCmHandleDataNodeByAlternateId(final String alternateId) {
+        final String xPathForCmHandleByAlternateId = getXPathForCmHandleByAlternateId(alternateId);
+        final Collection<DataNode> dataNodes = cmHandleQueries
+            .queryNcmpRegistryByCpsPath(xPathForCmHandleByAlternateId, OMIT_DESCENDANTS);
+        if (dataNodes.isEmpty()) {
+            throw new DataNodeNotFoundException(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
+                xPathForCmHandleByAlternateId);
+        }
+        return dataNodes.iterator().next();
     }
 
     @Override
     public Collection<DataNode> getCmHandleDataNodes(final Collection<String> cmHandleIds) {
         final Collection<String> xpaths = new ArrayList<>(cmHandleIds.size());
-        cmHandleIds.forEach(cmHandleId -> xpaths.add(createCmHandleXPath(cmHandleId)));
+        cmHandleIds.forEach(cmHandleId -> xpaths.add(getXPathForCmHandleById(cmHandleId)));
         return this.getDataNodes(xpaths);
     }
 
@@ -174,10 +191,14 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv
         return cpsAnchorService.queryAnchorNames(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, moduleNamesForQuery);
     }
 
-    private static String createCmHandleXPath(final String cmHandleId) {
+    private static String getXPathForCmHandleById(final String cmHandleId) {
         return NCMP_DMI_REGISTRY_PARENT + "/cm-handles[@id='" + cmHandleId + "']";
     }
 
+    private static String getXPathForCmHandleByAlternateId(final String alternateId) {
+        return NCMP_DMI_REGISTRY_PARENT + "/cm-handles[@alternate-id='" + alternateId + "']";
+    }
+
     private static String createStateJsonData(final String state) {
         return "{\"state\":" + state + "}";
     }
index 22bbc73..794ca5b 100644 (file)
@@ -38,7 +38,6 @@ import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.lang3.StringUtils;
 import org.onap.cps.ncmp.api.impl.inventory.CmHandleQueries;
 import org.onap.cps.ncmp.api.impl.inventory.CmHandleState;
 import org.onap.cps.ncmp.api.impl.inventory.CompositeState;
@@ -62,10 +61,10 @@ public class ModuleOperationsUtils {
     private final DmiDataOperations dmiDataOperations;
     private final JsonObjectMapper jsonObjectMapper;
     private static final String RETRY_ATTEMPT_KEY = "attempt";
-    public static final String MODULE_SET_TAG_KEY = "moduleSetTag";
-    public static final String MODULE_SET_TAG_MESSAGE_FORMAT = "Upgrade to ModuleSetTag: {0}";
-    private static final String UPGRADE_FORMAT = "Upgrade to ModuleSetTag: %s";
-    private static final String LOCK_REASON_DETAILS_MSG_FORMAT = UPGRADE_FORMAT + " Attempt #%d failed: %s";
+    private static final String MODULE_SET_TAG_KEY = "moduleSetTag";
+    public static final String MODULE_SET_TAG_MESSAGE_FORMAT = "Upgrade to ModuleSetTag: %s";
+    private static final String LOCK_REASON_DETAILS_MSG_FORMAT =
+            MODULE_SET_TAG_MESSAGE_FORMAT + " Attempt #%d failed: %s";
     private static final Pattern retryAttemptPattern = Pattern.compile("Attempt #(\\d+) failed:.+");
     private static final Pattern moduleSetTagPattern = Pattern.compile("Upgrade to ModuleSetTag: (\\S+)");
     private static final String CPS_PATH_CM_HANDLES_MODEL_SYNC_FAILED_OR_UPGRADE = """
@@ -134,10 +133,10 @@ public class ModuleOperationsUtils {
         if (!compositeStateDetails.isEmpty() && compositeStateDetails.containsKey(RETRY_ATTEMPT_KEY)) {
             attempt = 1 + Integer.parseInt(compositeStateDetails.get(RETRY_ATTEMPT_KEY));
         }
-        final String moduleSetTag = compositeStateDetails.get(MODULE_SET_TAG_KEY);
+        final String moduleSetTag = compositeStateDetails.getOrDefault(MODULE_SET_TAG_KEY, "");
         compositeState.setLockReason(CompositeState.LockReason.builder()
-                .details(String.format(LOCK_REASON_DETAILS_MSG_FORMAT, StringUtils.isNotBlank(moduleSetTag)
-                        ? moduleSetTag : "not-specified", attempt, errorMessage)).lockReasonCategory(lockReasonCategory)
+                .details(String.format(LOCK_REASON_DETAILS_MSG_FORMAT, moduleSetTag, attempt, errorMessage))
+                .lockReasonCategory(lockReasonCategory)
                 .build());
     }
 
@@ -221,13 +220,17 @@ public class ModuleOperationsUtils {
      * @param compositeState current lock reason of  cm handle
      * @return true or false based on lock reason category
      */
-    public static boolean isInUpgradeOrUpgradeFailed(final CompositeState compositeState) {
+    public static boolean inUpgradeOrUpgradeFailed(final CompositeState compositeState) {
         return compositeState.getLockReason() != null
                 && (LockReasonCategory.MODULE_UPGRADE.equals(compositeState.getLockReason().getLockReasonCategory())
                 || LockReasonCategory.MODULE_UPGRADE_FAILED.equals(compositeState.getLockReason()
                 .getLockReasonCategory()));
     }
 
+    public static String getUpgradedModuleSetTagFromLockReason(final CompositeState.LockReason lockReason) {
+        return getLockedCompositeStateDetails(lockReason).getOrDefault(MODULE_SET_TAG_KEY, "");
+    }
+
     private String getFirstResource(final Object responseBody) {
         final String jsonObjectAsString = jsonObjectMapper.asJsonString(responseBody);
         final JsonNode overallJsonNode = jsonObjectMapper.convertToJsonNode(jsonObjectAsString);
@@ -238,9 +241,7 @@ public class ModuleOperationsUtils {
 
     private List<YangModelCmHandle> convertCmHandlesDataNodesToYangModelCmHandles(
             final List<DataNode> cmHandlesAsDataNodeList) {
-        return cmHandlesAsDataNodeList.stream()
-                .map(cmHandle -> YangDataConverter.convertCmHandleToYangModel(cmHandle,
-                        cmHandle.getLeaves().get("id").toString())).toList();
+        return cmHandlesAsDataNodeList.stream().map(YangDataConverter::convertCmHandleToYangModel).toList();
     }
 
     private boolean isRetryDue(final CompositeState.LockReason compositeStateLockReason, final OffsetDateTime time) {
index 72d3226..e257112 100644 (file)
@@ -28,10 +28,9 @@ import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NFP_OPE
 import java.time.OffsetDateTime;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Optional;
+import lombok.AllArgsConstructor;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
@@ -40,7 +39,6 @@ import org.onap.cps.api.CpsDataService;
 import org.onap.cps.api.CpsModuleService;
 import org.onap.cps.ncmp.api.impl.inventory.CmHandleQueries;
 import org.onap.cps.ncmp.api.impl.inventory.CmHandleState;
-import org.onap.cps.ncmp.api.impl.inventory.CompositeState;
 import org.onap.cps.ncmp.api.impl.operations.DmiModelOperations;
 import org.onap.cps.ncmp.api.impl.utils.YangDataConverter;
 import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
@@ -63,54 +61,39 @@ public class ModuleSyncService {
     private final CpsDataService cpsDataService;
     private final CpsAnchorService cpsAnchorService;
     private final JsonObjectMapper jsonObjectMapper;
-    private final Map<String, Collection<ModuleReference>> moduleSetTagCache;
     private static final Map<String, String> NO_NEW_MODULES = Collections.emptyMap();
 
+    @AllArgsConstructor
+    private static final class ModuleDelta {
+        Collection<ModuleReference> allModuleReferences;
+        Map<String, String> newModuleNameToContentMap;
+    }
+
     /**
-     * This method registers a cm handle and initiates modules sync.
+     * This method creates a cm handle and initiates modules sync.
      *
      * @param yangModelCmHandle the yang model of cm handle.
      */
-    public void syncAndCreateOrUpgradeSchemaSetAndAnchor(final YangModelCmHandle yangModelCmHandle) {
-
+    public void syncAndCreateSchemaSetAndAnchor(final YangModelCmHandle yangModelCmHandle) {
+        final ModuleDelta moduleDelta = getModuleDelta(yangModelCmHandle, yangModelCmHandle.getModuleSetTag());
         final String cmHandleId = yangModelCmHandle.getId();
-        final CompositeState compositeState = yangModelCmHandle.getCompositeState();
-        final boolean inUpgrade = ModuleOperationsUtils.isInUpgradeOrUpgradeFailed(compositeState);
-        final String moduleSetTag = getModuleSetTag(yangModelCmHandle, compositeState, inUpgrade);
-
-        final Collection<ModuleReference> moduleReferencesFromCache = moduleSetTagCache.get(moduleSetTag);
-
-        if (moduleReferencesFromCache == null) {
-            final Optional<DataNode> existingCmHandleWithSameModuleSetTag
-                    = getFirstReadyDataNodeWithModuleSetTag(moduleSetTag);
+        cpsModuleService.createSchemaSetFromModules(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, cmHandleId,
+                moduleDelta.newModuleNameToContentMap, moduleDelta.allModuleReferences);
+        cpsAnchorService.createAnchor(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, cmHandleId, cmHandleId);
+    }
 
-            if (existingCmHandleWithSameModuleSetTag.isPresent()) {
-                final String existingAnchorName = existingCmHandleWithSameModuleSetTag.get().getAnchorName();
-                final Collection<ModuleReference> moduleReferencesFromExistingCmHandle =
-                        upgradeOrCreateSchemaSetUsingModuleSetTag(yangModelCmHandle.getId(), moduleSetTag,
-                                existingAnchorName, inUpgrade);
-                updateModuleSetTagCache(moduleSetTag, moduleReferencesFromExistingCmHandle);
-            } else {
-                if (inUpgrade) {
-                    deleteSchemaSetIfExists(cmHandleId);
-                }
-                final Collection<ModuleReference> allModuleReferencesFromCmHandle
-                        = syncAndCreateSchemaSet(yangModelCmHandle);
-                updateModuleSetTagCache(moduleSetTag, allModuleReferencesFromCmHandle);
-            }
-        } else {
-            if (inUpgrade) {
-                cpsModuleService.upgradeSchemaSetFromModules(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, cmHandleId,
-                        NO_NEW_MODULES, moduleReferencesFromCache);
-            } else {
-                cpsModuleService.createSchemaSetFromModules(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME,
-                        cmHandleId, NO_NEW_MODULES, moduleReferencesFromCache);
-            }
-        }
-        if (!inUpgrade) {
-            cpsAnchorService.createAnchor(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, cmHandleId, cmHandleId);
-        }
-        setCmHandleModuleSetTag(yangModelCmHandle, moduleSetTag);
+    /**
+     * This method upgrades a cm handle and initiates modules sync.
+     *
+     * @param yangModelCmHandle the yang model of cm handle.
+     */
+    public void syncAndUpgradeSchemaSet(final YangModelCmHandle yangModelCmHandle) {
+        final String upgradedModuleSetTag = ModuleOperationsUtils.getUpgradedModuleSetTagFromLockReason(
+                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);
     }
 
     /**
@@ -128,76 +111,41 @@ public class ModuleSyncService {
         }
     }
 
-    private Optional<DataNode> getFirstReadyDataNodeWithModuleSetTag(final String moduleSetTag) {
-        final List<DataNode> dataNodes = StringUtils.isNotBlank(moduleSetTag) ? cmHandleQueries
-                .queryNcmpRegistryByCpsPath("//cm-handles[@module-set-tag='" + moduleSetTag + "']",
-                        FetchDescendantsOption.OMIT_DESCENDANTS) : Collections.emptyList();
-        return dataNodes.stream().filter(dataNode -> {
-            final String cmHandleId = YangDataConverter.extractCmHandleIdFromXpath(dataNode.getXpath());
-            return cmHandleQueries.cmHandleHasState(cmHandleId, CmHandleState.READY);
-        }).findFirst();
-    }
-
-    private void setCmHandleModuleSetTag(final YangModelCmHandle upgradedCmHandle, final String moduleSetTag) {
-        final Map<String, Map<String, String>> dmiRegistryProperties = new HashMap<>(1);
-        final Map<String, String> cmHandleProperties = new HashMap<>(2);
-        cmHandleProperties.put("id", upgradedCmHandle.getId());
-        cmHandleProperties.put("module-set-tag", moduleSetTag);
-        dmiRegistryProperties.put("cm-handles", cmHandleProperties);
-        cpsDataService.updateNodeLeaves(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, NCMP_DMI_REGISTRY_PARENT,
-                jsonObjectMapper.asJsonString(dmiRegistryProperties), OffsetDateTime.now());
-    }
-
-    private Collection<ModuleReference> syncAndCreateSchemaSet(final YangModelCmHandle yangModelCmHandle) {
-        final Collection<ModuleReference> allModuleReferencesFromCmHandle =
-                dmiModelOperations.getModuleReferences(yangModelCmHandle);
-        final Collection<ModuleReference> identifiedNewModuleReferencesFromCmHandle = cpsModuleService
-                .identifyNewModuleReferences(allModuleReferencesFromCmHandle);
-        final Map<String, String> newModuleNameToContentMap;
-        if (identifiedNewModuleReferencesFromCmHandle.isEmpty()) {
-            newModuleNameToContentMap = NO_NEW_MODULES;
-        } else {
-            newModuleNameToContentMap = dmiModelOperations.getNewYangResourcesFromDmi(yangModelCmHandle,
-                    identifiedNewModuleReferencesFromCmHandle);
-        }
-        cpsModuleService.createSchemaSetFromModules(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME,
-                yangModelCmHandle.getId(), newModuleNameToContentMap, allModuleReferencesFromCmHandle);
-        return allModuleReferencesFromCmHandle;
-    }
+    private ModuleDelta getModuleDelta(final YangModelCmHandle yangModelCmHandle, final String targetModuleSetTag) {
+        final Collection<ModuleReference> allModuleReferences;
+        final Map<String, String> newYangResources;
 
-    private Collection<ModuleReference> upgradeOrCreateSchemaSetUsingModuleSetTag(final String schemaSetName,
-                                                                                  final String moduleSetTag,
-                                                                                  final String existingAnchorName,
-                                                                                  final boolean inUpgrade) {
-        log.info("Found cm handle having module set tag: {}", moduleSetTag);
-        final Collection<ModuleReference> moduleReferencesFromExistingCmHandle =
-                cpsModuleService.getYangResourcesModuleReferences(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME,
-                        existingAnchorName);
-        if (inUpgrade) {
-            cpsModuleService.upgradeSchemaSetFromModules(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, schemaSetName,
-                    NO_NEW_MODULES, moduleReferencesFromExistingCmHandle);
+        final YangModelCmHandle cmHandleWithSameModuleSetTag = getAnyReadyCmHandleByModuleSetTag(targetModuleSetTag);
+        if (cmHandleWithSameModuleSetTag == null) {
+            allModuleReferences = dmiModelOperations.getModuleReferences(yangModelCmHandle);
+            newYangResources = dmiModelOperations.getNewYangResourcesFromDmi(yangModelCmHandle,
+                    cpsModuleService.identifyNewModuleReferences(allModuleReferences));
         } else {
-            cpsModuleService.createSchemaSetFromModules(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME,
-                    schemaSetName, NO_NEW_MODULES, moduleReferencesFromExistingCmHandle);
+            log.info("Found other cm handle having same module set tag: {}", targetModuleSetTag);
+            allModuleReferences = cpsModuleService.getYangResourcesModuleReferences(
+                    NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, cmHandleWithSameModuleSetTag.getId());
+            newYangResources = NO_NEW_MODULES;
         }
-        return moduleReferencesFromExistingCmHandle;
+        return new ModuleDelta(allModuleReferences, newYangResources);
     }
 
-    private String getModuleSetTag(final YangModelCmHandle yangModelCmHandle,
-                                   final CompositeState compositeState,
-                                   final boolean inUpgrade) {
-        if (inUpgrade) {
-            return ModuleOperationsUtils.getLockedCompositeStateDetails(compositeState.getLockReason())
-                    .get(ModuleOperationsUtils.MODULE_SET_TAG_KEY);
+    private YangModelCmHandle getAnyReadyCmHandleByModuleSetTag(final String moduleSetTag) {
+        if (StringUtils.isBlank(moduleSetTag)) {
+            return null;
         }
-        return yangModelCmHandle.getModuleSetTag();
+        final String escapedModuleSetTag = moduleSetTag.replace("'", "''");
+        final List<DataNode> dataNodes = cmHandleQueries.queryNcmpRegistryByCpsPath(
+                NCMP_DMI_REGISTRY_PARENT + "/cm-handles[@module-set-tag='" + escapedModuleSetTag + "']",
+                FetchDescendantsOption.DIRECT_CHILDREN_ONLY);
+        return dataNodes.stream().map(YangDataConverter::convertCmHandleToYangModel)
+                .filter(cmHandle -> cmHandle.getCompositeState().getCmHandleState() == CmHandleState.READY)
+                .findFirst().orElse(null);
     }
 
-    private void updateModuleSetTagCache(final String moduleSetTag,
-                                         final Collection<ModuleReference> allModuleReferencesFromCmHandle) {
-        if (StringUtils.isNotBlank(moduleSetTag)) {
-            moduleSetTagCache.putIfAbsent(moduleSetTag, allModuleReferencesFromCmHandle);
-        }
+    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)));
+        cpsDataService.updateNodeLeaves(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, NCMP_DMI_REGISTRY_PARENT,
+                jsonForUpdate, OffsetDateTime.now());
     }
-
 }
index e214044..590cb56 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2022-2023 Nordix Foundation
+ *  Copyright (C) 2022-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.
@@ -65,14 +65,16 @@ public class ModuleSyncTasks {
             for (final DataNode cmHandleAsDataNode : cmHandlesAsDataNodes) {
                 final String cmHandleId = String.valueOf(cmHandleAsDataNode.getLeaves().get("id"));
                 final YangModelCmHandle yangModelCmHandle =
-                        YangDataConverter.convertCmHandleToYangModel(cmHandleAsDataNode, cmHandleId);
+                        YangDataConverter.convertCmHandleToYangModel(cmHandleAsDataNode);
                 final CompositeState compositeState = inventoryPersistence.getCmHandleState(cmHandleId);
-                final boolean inUpgrade = ModuleOperationsUtils.isInUpgradeOrUpgradeFailed(compositeState);
+                final boolean inUpgrade = ModuleOperationsUtils.inUpgradeOrUpgradeFailed(compositeState);
                 try {
-                    if (!inUpgrade) {
+                    if (inUpgrade) {
+                        moduleSyncService.syncAndUpgradeSchemaSet(yangModelCmHandle);
+                    } else {
                         moduleSyncService.deleteSchemaSetIfExists(cmHandleId);
+                        moduleSyncService.syncAndCreateSchemaSetAndAnchor(yangModelCmHandle);
                     }
-                    moduleSyncService.syncAndCreateOrUpgradeSchemaSetAndAnchor(yangModelCmHandle);
                     yangModelCmHandle.getCompositeState().setLockReason(null);
                     cmHandelStatePerCmHandle.put(yangModelCmHandle, CmHandleState.READY);
                 } catch (final Exception e) {
index 32b5cb7..dbe386d 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2023 Nordix Foundation
+ *  Copyright (C) 2021-2024 Nordix Foundation
  *  Modifications Copyright (C) 2022 Bell Canada
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
@@ -27,6 +27,7 @@ import com.google.gson.JsonArray;
 import com.google.gson.JsonObject;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -83,6 +84,9 @@ public class DmiModelOperations extends DmiOperations {
      */
     public Map<String, String> getNewYangResourcesFromDmi(final YangModelCmHandle yangModelCmHandle,
                                                           final Collection<ModuleReference> newModuleReferences) {
+        if (newModuleReferences.isEmpty()) {
+            return Collections.emptyMap();
+        }
         final String jsonWithDataAndDmiProperties = getRequestBodyToFetchYangResources(
             newModuleReferences, yangModelCmHandle.getDmiProperties());
         final ResponseEntity<Object> responseEntity = getResourceFromDmiWithJsonData(
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/AlternateIdChecker.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/AlternateIdChecker.java
new file mode 100644 (file)
index 0000000..1be1a90
--- /dev/null
@@ -0,0 +1,142 @@
+/*
+ * ============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.ncmp.api.impl.utils;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence;
+import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
+import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle;
+import org.onap.cps.spi.exceptions.DataNodeNotFoundException;
+import org.springframework.stereotype.Service;
+
+@Service
+@Slf4j
+@RequiredArgsConstructor
+public class AlternateIdChecker {
+
+    private final InventoryPersistence inventoryPersistence;
+
+    private static final String NO_CURRENT_ALTERNATE_ID = "";
+
+    /**
+     * Check if the alternate can be applied to the given cm handle (id).
+     * Conditions:
+     * - proposed alternate is blank (it wil be ignored)
+     * - proposed alternate is same as current (no change)
+     * - proposed alternate is not in use for a different cm handle (in the DB)
+     *
+     * @param cmHandleId cm handle id
+     * @param proposedAlternateId proposed alternate id
+     * @return true if the new alternate id not in use or equal to current alternate id, false otherwise
+     */
+    public boolean canApplyAlternateId(final String cmHandleId, final String proposedAlternateId) {
+        String currentAlternateId = "";
+        try {
+            final YangModelCmHandle yangModelCmHandle = inventoryPersistence.getYangModelCmHandle(cmHandleId);
+            currentAlternateId = yangModelCmHandle.getAlternateId();
+        } catch (final DataNodeNotFoundException dataNodeNotFoundException) {
+            // work with blank current alternate id
+        }
+        return this.canApplyAlternateId(cmHandleId, currentAlternateId, proposedAlternateId);
+    }
+
+    /**
+     * Check if the alternate can be applied to the given cm handle.
+     * Conditions:
+     * - proposed alternate is blank (it wil be ignored)
+     * - proposed alternate is same as current (no change)
+     * - proposed alternate is not in use for a different cm handle (in the DB)
+     *
+     * @param cmHandleId   cm handle id
+     * @param currentAlternateId current alternate id
+     * @param proposedAlternateId new alternate id
+     * @return true if the new alternate id not in use or equal to current alternate id, false otherwise
+     */
+    public boolean canApplyAlternateId(final String cmHandleId,
+                                       final String currentAlternateId,
+                                       final String proposedAlternateId) {
+        if (StringUtils.isBlank(currentAlternateId)) {
+            if (alternateIdAlreadyInDb(proposedAlternateId)) {
+                log.warn("Alternate id update ignored, cannot update cm handle {}, alternate id is already "
+                    + "assigned to a different cm handle", cmHandleId);
+                return false;
+            }
+            return true;
+        }
+        if (currentAlternateId.equals(proposedAlternateId)) {
+            return true;
+        }
+        log.warn("Alternate id update ignored, cannot update cm handle {}, already has an alternate id of {}",
+            cmHandleId, currentAlternateId);
+        return false;
+    }
+
+    /**
+     * Check all alternate ids of a batch of NEW cm handles.
+     * Includes cross-checks in the batch itself for duplicates. Only the first entry encountered wil be accepted.
+     * This method can only be used for NEW cm handle registrations NOT for updating existing ones
+     *
+     * @param newNcmpServiceCmHandles the proposed new cm handles
+     * @return collection of cm handles ids which are acceptable
+     */
+    public Collection<String> getIdsOfCmHandlesWithAcceptableAlternateId(
+                                    final Collection<NcmpServiceCmHandle> newNcmpServiceCmHandles) {
+        final Set<String> acceptedAlternateIds = new HashSet<>(newNcmpServiceCmHandles.size());
+        final Collection<String> acceptedCmHandleIds = new ArrayList<>(newNcmpServiceCmHandles.size());
+        for (final NcmpServiceCmHandle ncmpServiceCmHandle : newNcmpServiceCmHandles) {
+            final String cmHandleId = ncmpServiceCmHandle.getCmHandleId();
+            final String proposedAlternateId = ncmpServiceCmHandle.getAlternateId();
+            final boolean isAcceptable;
+            if (StringUtils.isEmpty(proposedAlternateId)) {
+                isAcceptable = true;
+            } else {
+                if (acceptedAlternateIds.contains(proposedAlternateId)) {
+                    isAcceptable = false;
+                    log.warn("Alternate id update ignored, cannot update cm handle {}, alternate id is already "
+                        + "assigned to a different cm handle (in this batch)", cmHandleId);
+                } else {
+                    isAcceptable = canApplyAlternateId(cmHandleId, NO_CURRENT_ALTERNATE_ID, proposedAlternateId);
+                }
+            }
+            if (isAcceptable) {
+                acceptedAlternateIds.add(proposedAlternateId);
+                acceptedCmHandleIds.add(cmHandleId);
+            }
+        }
+        return acceptedCmHandleIds;
+    }
+
+    private boolean alternateIdAlreadyInDb(final String alternateId) {
+        try {
+            inventoryPersistence.getCmHandleDataNodeByAlternateId(alternateId);
+        } catch (final DataNodeNotFoundException dataNodeNotFoundException) {
+            return false;
+        }
+        return true;
+    }
+
+}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/CmHandleIdMapper.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/CmHandleIdMapper.java
deleted file mode 100644 (file)
index a88adbd..0000000
+++ /dev/null
@@ -1,95 +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.ncmp.api.impl.utils;
-
-import java.util.Map;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.lang3.StringUtils;
-import org.onap.cps.ncmp.api.NetworkCmProxyCmHandleQueryService;
-import org.springframework.stereotype.Service;
-
-@Service
-@Slf4j
-@RequiredArgsConstructor
-public class CmHandleIdMapper {
-
-    private final Map<String, String> alternateIdPerCmHandleId;
-    private final Map<String, String> cmHandleIdPerAlternateId;
-    private final NetworkCmProxyCmHandleQueryService networkCmProxyCmHandleQueryService;
-
-    private boolean cacheIsInitialized = false;
-
-    public String cmHandleIdToAlternateId(final String cmHandleId) {
-        initializeCache();
-        return alternateIdPerCmHandleId.get(cmHandleId);
-    }
-
-    public String alternateIdToCmHandleId(final String alternateId) {
-        initializeCache();
-        return cmHandleIdPerAlternateId.get(alternateId);
-    }
-
-    public boolean addMapping(final String cmHandleId, final String alternateId) {
-        initializeCache();
-        return addMappingWithValidation(cmHandleId, alternateId);
-    }
-
-
-    private boolean addMappingWithValidation(final String cmHandleId, final String alternateId) {
-        if (alternateIdPerCmHandleId.containsKey(cmHandleId)) {
-            final String originalAlternateId = alternateIdPerCmHandleId.get(cmHandleId);
-            if (!originalAlternateId.equals(alternateId)) {
-                log.warn("Alternate id update ignored, cannot update cm handle {}, already has an alternate id of {}",
-                        cmHandleId, originalAlternateId);
-            }
-            return false;
-        }
-        if (StringUtils.isBlank(alternateId)) {
-            return false;
-        }
-        alternateIdPerCmHandleId.put(cmHandleId, alternateId);
-        cmHandleIdPerAlternateId.put(alternateId, cmHandleId);
-        return true;
-    }
-
-    public void removeMapping(final String cmHandleId) {
-        final String alternateId = alternateIdPerCmHandleId.remove(cmHandleId);
-        removeAlternateIdWithValidation(alternateId);
-    }
-
-    private void removeAlternateIdWithValidation(final String alternateId) {
-        if (alternateId != null) {
-            cmHandleIdPerAlternateId.remove(alternateId);
-        }
-    }
-
-    private void initializeCache() {
-        if (!cacheIsInitialized) {
-            networkCmProxyCmHandleQueryService.getAllCmHandles().forEach(cmHandle ->
-                addMappingWithValidation(cmHandle.getCmHandleId(), cmHandle.getAlternateId())
-            );
-            log.info("Alternate ID cache initialized from DB with {} cm handle/alternate id pairs ",
-                    alternateIdPerCmHandleId.size());
-            cacheIsInitialized = true;
-        }
-    }
-}
index 3be97e8..3954142 100644 (file)
 
 package org.onap.cps.ncmp.api.impl.utils;
 
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import java.util.stream.Collectors;
 import lombok.AccessLevel;
 import lombok.NoArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
@@ -54,6 +54,7 @@ public class YangDataConverter {
         final List<YangModelCmHandle.Property> publicProperties = yangModelCmHandle.getPublicProperties();
         ncmpServiceCmHandle.setCmHandleId(yangModelCmHandle.getId());
         ncmpServiceCmHandle.setCompositeState(yangModelCmHandle.getCompositeState());
+        ncmpServiceCmHandle.setModuleSetTag(yangModelCmHandle.getModuleSetTag());
         ncmpServiceCmHandle.setAlternateId(yangModelCmHandle.getAlternateId());
         setDmiProperties(dmiProperties, ncmpServiceCmHandle);
         setPublicProperties(publicProperties, ncmpServiceCmHandle);
@@ -75,12 +76,11 @@ public class YangDataConverter {
     /**
      * This method convert cm handle data node to yang model cm handle.
      * @param cmHandleDataNode the datanode of the cm handle
-     * @param cmHandleId the id of the cm handle
      * @return yang model cm handle
      */
-    public static YangModelCmHandle convertCmHandleToYangModel(final DataNode cmHandleDataNode,
-                                                               final String cmHandleId) {
+    public static YangModelCmHandle convertCmHandleToYangModel(final DataNode cmHandleDataNode) {
         final NcmpServiceCmHandle ncmpServiceCmHandle = new NcmpServiceCmHandle();
+        final String cmHandleId = cmHandleDataNode.getLeaves().get("id").toString();
         ncmpServiceCmHandle.setCmHandleId(cmHandleId);
         populateCmHandleDetails(cmHandleDataNode, ncmpServiceCmHandle);
         return YangModelCmHandle.toYangModelCmHandle(
@@ -100,12 +100,8 @@ public class YangDataConverter {
      */
     public static Collection<YangModelCmHandle> convertDataNodesToYangModelCmHandles(
             final Collection<DataNode> cmHandleDataNodes) {
-        final Collection<YangModelCmHandle> yangModelCmHandles = new ArrayList<>(cmHandleDataNodes.size());
-        cmHandleDataNodes.forEach(dataNode -> {
-            final String cmHandleId = extractCmHandleIdFromXpath(dataNode.getXpath());
-            yangModelCmHandles.add(convertCmHandleToYangModel(dataNode, cmHandleId));
-        });
-        return yangModelCmHandles;
+        return cmHandleDataNodes.stream().map(YangDataConverter::convertCmHandleToYangModel)
+                .collect(Collectors.toList());
     }
 
     /**
index 03e53fc..b2758d9 100644 (file)
@@ -24,7 +24,6 @@ package org.onap.cps.ncmp.api.impl.yangmodels;
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonInclude.Include;
 import com.fasterxml.jackson.annotation.JsonProperty;
-import com.google.common.base.Strings;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
@@ -94,6 +93,7 @@ public class YangModelCmHandle {
         copy.dmiProperties = original.getDmiProperties() == null ? null : new ArrayList<>(original.getDmiProperties());
         copy.publicProperties =
                 original.getPublicProperties() == null ? null : new ArrayList<>(original.getPublicProperties());
+        copy.moduleSetTag = original.getModuleSetTag();
         copy.alternateId = original.getAlternateId();
         return copy;
     }
@@ -134,7 +134,7 @@ public class YangModelCmHandle {
      * @return dmi service name
      */
     public String resolveDmiServiceName(final RequiredDmiService requiredService) {
-        if (isNullEmptyOrBlank(dmiServiceName)) {
+        if (StringUtils.isBlank(dmiServiceName)) {
             if (RequiredDmiService.DATA.equals(requiredService)) {
                 return dmiDataServiceName;
             }
@@ -151,10 +151,6 @@ public class YangModelCmHandle {
         return yangModelCmHandleProperties;
     }
 
-    private static boolean isNullEmptyOrBlank(final String serviceName) {
-        return Strings.isNullOrEmpty(serviceName) || serviceName.isBlank();
-    }
-
     @AllArgsConstructor
     @Data
     public static class Property {
index c7ac8ab..313d1b4 100644 (file)
@@ -27,8 +27,10 @@ import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLE_ALREADY_EXIST
 import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLE_INVALID_ID
 import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNKNOWN_ERROR
 
+import java.util.stream.Collectors
+import org.onap.cps.ncmp.api.impl.inventory.CompositeState
 import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevelManager
-import org.onap.cps.ncmp.api.impl.utils.CmHandleIdMapper
+import org.onap.cps.ncmp.api.impl.utils.AlternateIdChecker
 import org.onap.cps.ncmp.api.models.UpgradedCmHandles
 import com.fasterxml.jackson.databind.ObjectMapper
 import com.hazelcast.map.IMap
@@ -68,9 +70,14 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
     def mockModuleSyncStartedOnCmHandles = Mock(IMap<String, Object>)
     def trustLevelPerDmiPlugin = [:]
     def mockTrustLevelManager = Mock(TrustLevelManager)
-    def mockCmHandleIdMapper = Mock(CmHandleIdMapper)
+    def mockAlternateIdChecker = Mock(AlternateIdChecker)
     def objectUnderTest = getObjectUnderTest()
-    def mockModuleSetTagCache = [:]
+
+    def setup() {
+        // always accept all cm handles
+        mockAlternateIdChecker.getIdsOfCmHandlesWithAcceptableAlternateId(_) >>
+            { args -> args[0].stream().map(it -> it.cmHandleId).collect(Collectors.toList()) }
+    }
 
     def 'DMI Registration: Create, Update, Delete & Upgrade operations are processed in the right order'() {
         given: 'a registration with operations of all types'
@@ -81,6 +88,7 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
             dmiRegistration.setUpgradedCmHandles(new UpgradedCmHandles(cmHandles: ['cmhandle-3'], moduleSetTag: 'some-module-set-tag'))
         and: 'cm handles are persisted'
             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
         when: 'registration is processed'
@@ -88,7 +96,7 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
         then: 'cm-handles are removed first'
             1 * objectUnderTest.parseAndProcessDeletedCmHandlesInRegistration(*_)
         and: 'de-registered cm handle entry is removed from in progress map'
-            2 * mockModuleSyncStartedOnCmHandles.remove('cmhandle-2')
+            1 * mockModuleSyncStartedOnCmHandles.remove('cmhandle-2')
         then: 'cm-handles are created'
             1 * objectUnderTest.parseAndProcessCreatedCmHandlesInRegistration(*_)
         then: 'cm-handles are updated'
@@ -102,15 +110,15 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
             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'
-            mockCmHandleQueries.cmHandleHasState('cmhandle-3', CmHandleState.READY) >> isReady
+            mockInventoryPersistence.getYangModelCmHandle('cmhandle-3') >> new YangModelCmHandle(id: 'cmhandle-3', moduleSetTag: '', compositeState: new CompositeState(cmHandleState: cmHandleState))
         when: 'registration is processed'
             def result = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiRegistration)
         then: 'upgrade operation contains expected error code'
-            assert result.upgradedCmHandles.status[0] == expectedResponseStatus
+            assert result.upgradedCmHandles[0].status == expectedResponseStatus
         where: 'the following parameters are used'
-            scenario    | isReady || expectedResponseStatus
-            'READY'     | true    || Status.SUCCESS
-            'Not READY' | false   || Status.FAILURE
+            scenario    | cmHandleState        || expectedResponseStatus
+            'READY'     | CmHandleState.READY  || Status.SUCCESS
+            'Not READY' | CmHandleState.LOCKED || Status.FAILURE
     }
 
     def 'DMI Registration upgrade with exception #scenario'() {
@@ -118,7 +126,7 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
             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'
-            mockCmHandleQueries.cmHandleHasState('cmhandle-3', CmHandleState.READY) >> { throw exception }
+            mockInventoryPersistence.getYangModelCmHandle('cmhandle-3') >> { throw exception }
         when: 'registration is processed'
             def result = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiRegistration)
         then: 'upgrade operation contains expected error code'
@@ -160,7 +168,7 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
         when: 'update registration and sync module is called with correct DMI plugin information'
             objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
         then: 'create cm handles registration and sync modules is called with the correct plugin information'
-            1 * objectUnderTest.parseAndProcessCreatedCmHandlesInRegistration(dmiPluginRegistration)
+            1 * objectUnderTest.parseAndProcessCreatedCmHandlesInRegistration(dmiPluginRegistration, _)
         and: 'dmi is added to the dmi trustLevel map'
             assert trustLevelPerDmiPlugin.size() == 1
             assert trustLevelPerDmiPlugin.containsKey(expectedDmiPluginRegisteredName)
@@ -433,19 +441,19 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
 
     def 'Adding data to alternate id caches.'() {
         given: 'a registration with three CM Handles to be created'
-            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server',
-                    createdCmHandles: [new NcmpServiceCmHandle(cmHandleId: 'cmhandle1', alternateId: 'my-alternate-id-1')])
+            def ncmpServiceCmHandles = [new NcmpServiceCmHandle(cmHandleId: 'cmhandle1', alternateId: 'my-alternate-id-1')]
+            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server', createdCmHandles: ncmpServiceCmHandles)
         when: 'the DMI plugin registration happens'
             objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
         then: 'the new alternate id is added to the cache'
-            1 * mockCmHandleIdMapper.addMapping('cmhandle1', 'my-alternate-id-1')
+            1 * mockAlternateIdChecker.getIdsOfCmHandlesWithAcceptableAlternateId(ncmpServiceCmHandles) >> ['cmhandle1']
     }
 
     def getObjectUnderTest() {
         return Spy(new NetworkCmProxyDataServiceImpl(spiedJsonObjectMapper, mockDmiDataOperations,
                 mockNetworkCmProxyDataServicePropertyHandler, mockInventoryPersistence, mockCmHandleQueries,
                 stubbedNetworkCmProxyCmHandlerQueryService, mockLcmEventsCmHandleStateHandler, mockCpsDataService,
-                mockModuleSyncStartedOnCmHandles, trustLevelPerDmiPlugin, mockTrustLevelManager, mockCmHandleIdMapper, mockModuleSetTagCache))
+                mockModuleSyncStartedOnCmHandles, trustLevelPerDmiPlugin, mockTrustLevelManager, mockAlternateIdChecker))
     }
 
     def addPersistedYangModelCmHandles(ids) {
index c835056..fc548eb 100644 (file)
@@ -32,7 +32,7 @@ import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RU
 import static org.onap.cps.ncmp.api.impl.operations.OperationType.CREATE
 import static org.onap.cps.ncmp.api.impl.operations.OperationType.UPDATE
 
-import org.onap.cps.ncmp.api.impl.utils.CmHandleIdMapper
+import org.onap.cps.ncmp.api.impl.utils.AlternateIdChecker
 import com.hazelcast.map.IMap
 import org.onap.cps.ncmp.api.NetworkCmProxyCmHandleQueryService
 import org.onap.cps.ncmp.api.impl.events.lcm.LcmEventsCmHandleStateHandler
@@ -54,7 +54,7 @@ import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
 import org.onap.cps.ncmp.api.models.DataOperationRequest
 import org.onap.cps.spi.exceptions.CpsException
 import org.onap.cps.spi.model.ConditionProperties
-import spock.lang.Shared
+
 import java.util.stream.Collectors
 import org.onap.cps.utils.JsonObjectMapper
 import com.fasterxml.jackson.databind.ObjectMapper
@@ -80,14 +80,11 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
     def stubModuleSyncStartedOnCmHandles = Stub(IMap<String, Object>)
     def stubTrustLevelPerDmiPlugin = Stub(Map<String, TrustLevel>)
     def mockTrustLevelManager = Mock(TrustLevelManager)
-    def mockCmHandleIdMapper = Mock(CmHandleIdMapper)
-    def mockModuleSetTagCache = [:]
+    def mockAlternateIdChecker = Mock(AlternateIdChecker)
 
     def NO_TOPIC = null
     def NO_REQUEST_ID = null
-    @Shared
     def OPTIONS_PARAM = '(a=1,b=2)'
-    @Shared
     def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: 'test-cm-handle-id')
 
     def objectUnderTest = new NetworkCmProxyDataServiceImpl(
@@ -102,8 +99,7 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
             stubModuleSyncStartedOnCmHandles,
             stubTrustLevelPerDmiPlugin,
             mockTrustLevelManager,
-            mockCmHandleIdMapper,
-            mockModuleSetTagCache)
+            mockAlternateIdChecker)
 
     def cmHandleXPath = "/dmi-registry/cm-handles[@id='testCmHandle']"
 
@@ -177,14 +173,17 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
         given: 'the system returns a yang modelled cm handle'
             def dmiServiceName = 'some service name'
             def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED,
-                lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.MODULE_SYNC_FAILED).details("lock details").build(),
-                lastUpdateTime: 'some-timestamp',
-                dataSyncEnabled: false,
-                dataStores: dataStores())
+                    lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.MODULE_SYNC_FAILED).details("lock details").build(),
+                    lastUpdateTime: 'some-timestamp',
+                    dataSyncEnabled: false,
+                    dataStores: dataStores())
             def dmiProperties = [new YangModelCmHandle.Property('Book', 'Romance Novel')]
             def publicProperties = [new YangModelCmHandle.Property('Public Book', 'Public Romance Novel')]
+            def moduleSetTag = 'some-module-set-tag'
+            def alternateId = 'some-alternate-id'
             def yangModelCmHandle = new YangModelCmHandle(id: 'some-cm-handle', dmiServiceName: dmiServiceName,
-                dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState)
+                    dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState,
+                    moduleSetTag: moduleSetTag, alternateId: alternateId)
             1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle
         when: 'getting cm handle details for a given cm handle id from ncmp service'
             def result = objectUnderTest.getNcmpServiceCmHandle('some-cm-handle')
@@ -192,13 +191,16 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
             result.class == NcmpServiceCmHandle.class
         and: 'the cm handle contains the cm handle id'
             result.cmHandleId == 'some-cm-handle'
+        and: 'the cm handle contains the alternate id'
+            result.alternateId == 'some-alternate-id'
+        and: 'the cm handle contains the module-set-tag'
+            result.moduleSetTag == 'some-module-set-tag'
         and: 'the cm handle contains the DMI Properties'
             result.dmiProperties ==[ Book:'Romance Novel' ]
         and: 'the cm handle contains the public Properties'
             result.publicProperties == [ "Public Book":'Public Romance Novel' ]
         and: 'the cm handle contains the cm handle composite state'
             result.compositeState == compositeState
-
     }
 
     def 'Get cm handle public properties'() {
@@ -269,7 +271,7 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
             dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle]
             mockDmiPluginRegistration.getCreatedCmHandles() >> [ncmpServiceCmHandle]
         when: 'parse and create cm handle in dmi registration then sync module'
-            objectUnderTest.parseAndProcessCreatedCmHandlesInRegistration(mockDmiPluginRegistration)
+            objectUnderTest.parseAndProcessCreatedCmHandlesInRegistration(mockDmiPluginRegistration, ['test-cm-handle-id'])
         then: 'system persists the cm handle state'
             1 * mockLcmEventsCmHandleStateHandler.initiateStateAdvised(_) >> {
                 args -> {
@@ -280,7 +282,7 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
                     }
             }
     }
-    
+
     def 'Execute cm handle id search'() {
         given: 'valid CmHandleQueryApiParameters input'
             def cmHandleQueryApiParameters = new CmHandleQueryApiParameters()
index f94c34c..e00a426 100644 (file)
 
 package org.onap.cps.ncmp.api.impl
 
-
 import com.fasterxml.jackson.databind.ObjectMapper
-import org.onap.cps.ncmp.api.impl.utils.CmHandleIdMapper
-
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR
-import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLES_NOT_FOUND
-import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLE_INVALID_ID
-import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNKNOWN_ERROR
-import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.Status
-
 import org.onap.cps.api.CpsDataService
-import org.onap.cps.utils.JsonObjectMapper
 import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence
-import org.onap.cps.spi.exceptions.DataValidationException
+import org.onap.cps.ncmp.api.impl.utils.AlternateIdChecker
 import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
 import org.onap.cps.spi.exceptions.DataNodeNotFoundException
+import org.onap.cps.spi.exceptions.DataValidationException
 import org.onap.cps.spi.model.DataNode
 import org.onap.cps.spi.model.DataNodeBuilder
+import org.onap.cps.utils.JsonObjectMapper
 import spock.lang.Specification
 
+import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLES_NOT_FOUND
+import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLE_INVALID_ID
+import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNKNOWN_ERROR
+import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME
+import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR
+import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.Status
+
 class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification {
 
     def mockInventoryPersistence = Mock(InventoryPersistence)
     def mockCpsDataService = Mock(CpsDataService)
     def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
-    def mockCmHandleIdMapper = Mock(CmHandleIdMapper)
+    def mockAlternateIdChecker = Mock(AlternateIdChecker)
 
-    def objectUnderTest = new NetworkCmProxyDataServicePropertyHandler(mockInventoryPersistence, mockCpsDataService, jsonObjectMapper, mockCmHandleIdMapper)
+    def objectUnderTest = new NetworkCmProxyDataServicePropertyHandler(mockInventoryPersistence, mockCpsDataService, jsonObjectMapper, mockAlternateIdChecker)
     def static cmHandleId = 'myHandle1'
     def static cmHandleXpath = "/dmi-registry/cm-handles[@id='${cmHandleId}']"
 
@@ -58,11 +56,11 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification {
                                     new DataNodeBuilder().withXpath("/dmi-registry/cm-handles[@id='${cmHandleId}']/additional-properties[@name='additionalProp2']").withLeaves(['name': 'additionalProp2', 'value': 'additionalValue2']).build(),
                                     new DataNodeBuilder().withXpath("/dmi-registry/cm-handles[@id='${cmHandleId}']/public-properties[@name='publicProp3']").withLeaves(['name': 'publicProp3', 'value': 'publicValue3']).build(),
                                     new DataNodeBuilder().withXpath("/dmi-registry/cm-handles[@id='${cmHandleId}']/public-properties[@name='publicProp4']").withLeaves(['name': 'publicProp4', 'value': 'publicValue4']).build()]
-    def static cmHandleDataNodeAsCollection = [new DataNode(xpath: cmHandleXpath, childDataNodes: propertyDataNodes)]
+    def static cmHandleDataNodeAsCollection = [new DataNode(xpath: cmHandleXpath, childDataNodes: propertyDataNodes, leaves: ['id': cmHandleId])]
 
     def 'Update CM Handle Public Properties: #scenario'() {
         given: 'the CPS service return a CM handle'
-            mockInventoryPersistence.getCmHandleDataNode(cmHandleId) >> cmHandleDataNodeAsCollection
+            mockInventoryPersistence.getCmHandleDataNodeByCmHandleId(cmHandleId) >> cmHandleDataNodeAsCollection
         and: 'an update cm handle request with public properties updates'
             def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleId: cmHandleId, publicProperties: updatedPublicProperties)]
         when: 'update data node leaves is called with the update request'
@@ -84,7 +82,7 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification {
 
     def 'Update DMI Properties: #scenario'() {
         given: 'the CPS service return a CM handle'
-            mockInventoryPersistence.getCmHandleDataNode(cmHandleId) >> cmHandleDataNodeAsCollection
+            mockInventoryPersistence.getCmHandleDataNodeByCmHandleId(cmHandleId) >> cmHandleDataNodeAsCollection
         and: 'an update cm handle request with DMI properties updates'
             def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleId: cmHandleId, dmiProperties: updatedDmiProperties)]
         when: 'update data node leaves is called with the update request'
@@ -107,8 +105,8 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification {
 
     def 'Update CM Handle Properties, remove all properties: #scenario'() {
         given: 'the CPS service return a CM handle'
-            def cmHandleDataNode = new DataNode(xpath: cmHandleXpath, childDataNodes: originalPropertyDataNodes)
-            mockInventoryPersistence.getCmHandleDataNode(cmHandleId) >> [cmHandleDataNode]
+            def cmHandleDataNode = new DataNode(xpath: cmHandleXpath, leaves: ['id': cmHandleId], childDataNodes: originalPropertyDataNodes)
+            mockInventoryPersistence.getCmHandleDataNodeByCmHandleId(cmHandleId) >> [cmHandleDataNode]
         and: 'an update cm handle request that removes all public properties(existing and non-existing)'
             def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleId: cmHandleId, publicProperties: ['publicProp3': null, 'publicProp4': null])]
         when: 'update data node leaves is called with the update request'
@@ -131,7 +129,7 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification {
         given: 'cm handles request'
             def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleId: cmHandleId, publicProperties: [:], dmiProperties: [:])]
         and: 'data node cannot be found'
-            mockInventoryPersistence.getCmHandleDataNode(*_) >> { throw exception }
+            mockInventoryPersistence.getCmHandleDataNodeByCmHandleId(*_) >> { throw exception }
         when: 'update data node leaves is called using correct parameters'
             def response = objectUnderTest.updateCmHandleProperties(cmHandleUpdateRequest)
         then: 'one failed registration response'
@@ -156,7 +154,7 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification {
                                          new NcmpServiceCmHandle(cmHandleId: cmHandleId, publicProperties: ['publicProp1': "value"], dmiProperties: [:]),
                                          new NcmpServiceCmHandle(cmHandleId: cmHandleId, publicProperties: ['publicProp1': "value"], dmiProperties: [:])]
         and: 'data node can be found for 1st and 3rd cm-handle but not for 2nd cm-handle'
-            mockInventoryPersistence.getCmHandleDataNode(*_) >> cmHandleDataNodeAsCollection >> {
+            mockInventoryPersistence.getCmHandleDataNodeByCmHandleId(*_) >> cmHandleDataNodeAsCollection >> {
                 throw new DataNodeNotFoundException(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR) } >> cmHandleDataNodeAsCollection
         when: 'update data node leaves is called using correct parameters'
             def cmHandleResponseList = objectUnderTest.updateCmHandleProperties(cmHandleUpdateRequest)
@@ -184,9 +182,9 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification {
 
     def 'Update CM Handle Alternate ID with #scenario'() {
         given: 'an existing cm handle'
-            DataNode existingCmHandleDataNode = new DataNode(xpath: cmHandleXpath)
+            DataNode existingCmHandleDataNode = new DataNode(xpath: cmHandleXpath, leaves: ['id': cmHandleId])
         and: 'an update request with an alternate id'
-            def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: cmHandleId, alternateId: 'alt-1' )
+            def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: cmHandleId, alternateId: 'alt-1')
         when: 'update alternate id method is called with the update request'
             objectUnderTest.updateAlternateId(existingCmHandleDataNode, ncmpServiceCmHandle)
         then: 'the update node leaves method is invoked as many times as expected'
@@ -194,7 +192,7 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification {
                     { args ->
                         assert args[3].contains('alt-1')
                     }
-            mockCmHandleIdMapper.addMapping(cmHandleId, 'alt-1') >> isNewMapping
+            mockAlternateIdChecker.canApplyAlternateId(cmHandleId, '','alt-1') >> isNewMapping
         where: 'following updates are attempted'
             scenario                | isNewMapping || callsToDataService
             'new alternate id   '   | true         || 1
@@ -204,9 +202,9 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification {
     def 'Alternate ID removed from cache when persisting fails.'() {
         given: 'an existing data node and an update request with an alternate id'
             def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: cmHandleId, alternateId: 'alt-1')
-            DataNode existingCmHandleDataNode = new DataNode(xpath: cmHandleXpath, leaves: ['alternate-id': null])
-        and: 'a new mapping is added'
-            mockCmHandleIdMapper.addMapping(cmHandleId, 'alt-1') >> true
+            DataNode existingCmHandleDataNode = new DataNode(xpath: cmHandleXpath, leaves: ['id': cmHandleId, 'alternate-id': null])
+        and: 'an applicable alternate id for the cm handle'
+            mockAlternateIdChecker.canApplyAlternateId(cmHandleId, '','alt-1') >> true
         and: 'but an exception occurs while saving'
             def originalException = new NullPointerException('some exception')
             mockCpsDataService.updateNodeLeaves(*_) >> { throw originalException }
@@ -215,8 +213,6 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification {
         then: 'the original exception is thrown up'
             def thrownException = thrown(NullPointerException)
             assert thrownException == originalException
-        and: 'the mapping is removed from the cache'
-            1 * mockCmHandleIdMapper.removeMapping(cmHandleId)
     }
 
     def convertToProperties(expectedPropertiesAfterUpdateAsMap) {
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/AlternateIdCacheConfigSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/AlternateIdCacheConfigSpec.groovy
deleted file mode 100644 (file)
index 71c5293..0000000
+++ /dev/null
@@ -1,59 +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.ncmp.api.impl.config.embeddedcache
-
-import com.hazelcast.core.Hazelcast
-import org.springframework.beans.factory.annotation.Autowired
-import org.springframework.boot.test.context.SpringBootTest
-import spock.lang.Specification
-
-@SpringBootTest(classes = [AlternateIdCacheConfig])
-class AlternateIdCacheConfigSpec extends Specification {
-
-    @Autowired
-    private Map<String, String> cmHandleIdPerAlternateId
-    @Autowired
-    private Map<String, String> alternateIdPerCmHandleId
-
-    def 'Embedded (hazelcast) cache for alternate id - cm handle id caches.'() {
-        expect: 'system is able to create an instance of the Alternate ID Cache'
-            assert null != cmHandleIdPerAlternateId
-            assert null != alternateIdPerCmHandleId
-        and: 'there are at least 2 instances'
-            assert Hazelcast.allHazelcastInstances.size() > 1
-        and: 'Alternate ID Caches are present'
-            assert Hazelcast.allHazelcastInstances.name.contains('hazelcastInstanceAlternateIdPerCmHandleIdMap')
-                    && Hazelcast.allHazelcastInstances.name.contains('hazelcastInstanceCmHandleIdPerAlternateIdMap')
-    }
-
-    def 'Verify configs of the alternate id distributed objects.'(){
-        when: 'retrieving the map config of module set tag'
-            def alternateIdConfig =  Hazelcast.getHazelcastInstanceByName('hazelcastInstanceAlternateIdPerCmHandleIdMap').config
-            def alternateIdMapConfig = alternateIdConfig.mapConfigs.get('alternateIdPerCmHandleIdMapConfig')
-            def cmHandleIdConfig =  Hazelcast.getHazelcastInstanceByName('hazelcastInstanceCmHandleIdPerAlternateIdMap').config
-            def cmHandleIdIdMapConfig = cmHandleIdConfig.mapConfigs.get('cmHandleIdPerAlternateIdMapConfig')
-        then: 'the map configs have the correct number of backups'
-            assert alternateIdMapConfig.backupCount == 3
-            assert alternateIdMapConfig.asyncBackupCount == 3
-            assert cmHandleIdIdMapConfig.backupCount == 3
-            assert cmHandleIdIdMapConfig.asyncBackupCount == 3
-    }
-}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/ModuleSetTagCacheConfigSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/ModuleSetTagCacheConfigSpec.groovy
deleted file mode 100644 (file)
index 8a6a32b..0000000
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- *  ============LICENSE_START=======================================================
- *  Copyright (C) 2023 Nordix Foundation
- *  ================================================================================
- *  Licensed under the Apache License, Version 2.0 (the "License");
- *  you may not use this file except in compliance with the License.
- *  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.ncmp.api.impl.config.embeddedcache
-
-import com.hazelcast.config.Config
-import com.hazelcast.core.Hazelcast
-import org.onap.cps.spi.model.ModuleReference
-import org.springframework.beans.factory.annotation.Autowired
-import org.springframework.boot.test.context.SpringBootTest
-import spock.lang.Specification
-
-@SpringBootTest(classes = [ModuleSetTagCacheConfig])
-class ModuleSetTagCacheConfigSpec extends Specification {
-
-    @Autowired
-    private Map<String, Collection<ModuleReference>> moduleSetTagCache
-
-    def 'Embedded (hazelcast) caches for module set tag.'() {
-        expect: 'system is able to create an instance of a map to hold module set tags'
-            assert null != moduleSetTagCache
-        and: 'there is at least 1 instance'
-            assert Hazelcast.allHazelcastInstances.size() > 0
-        and: 'hazelcast instance name of module set tag is correct'
-            assert Hazelcast.allHazelcastInstances.name.contains('moduleSetTags')
-    }
-
-    def 'Verify configs of module set tag distributed object.'(){
-        when: 'retrieving the map config of module set tag'
-            def cacheConfig =  Hazelcast.getHazelcastInstanceByName('moduleSetTags').config
-            def moduleSetTagMapConfig = cacheConfig.mapConfigs.get('moduleSetTagCacheMapConfig')
-        then: 'the module set tag map config has correct backup counts'
-            assert moduleSetTagMapConfig.backupCount == 3
-            assert moduleSetTagMapConfig.asyncBackupCount == 3
-    }
-
-    def 'Verify deployment network configs of distributed cache of module set tag object.'() {
-        given: 'network config of module set tag cache'
-            def moduleSetTagNetworkConfig = Hazelcast.getHazelcastInstanceByName('moduleSetTags').config.networkConfig
-        expect: 'module set tag cache has the correct settings'
-            assert moduleSetTagNetworkConfig.join.autoDetectionConfig.enabled
-            assert !moduleSetTagNetworkConfig.join.kubernetesConfig.enabled
-    }
-
-    def 'Verify network of module set tag cache'() {
-        given: 'Synchronization config object and test configuration'
-        def objectUnderTest = new ModuleSetTagCacheConfig()
-        def testConfig = new Config()
-        when: 'kubernetes properties are enabled'
-            objectUnderTest.cacheKubernetesEnabled = true
-            objectUnderTest.cacheKubernetesServiceName = 'test-service-name'
-        and: 'method called to update the discovery mode'
-            objectUnderTest.updateDiscoveryMode(testConfig)
-        then: 'applied properties are reflected'
-            assert testConfig.networkConfig.join.kubernetesConfig.enabled
-            assert testConfig.networkConfig.join.kubernetesConfig.properties.get('service-name') == 'test-service-name'
-
-    }
-}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionNcmpOutEventProducerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionNcmpOutEventProducerSpec.groovy
new file mode 100644 (file)
index 0000000..7c1a148
--- /dev/null
@@ -0,0 +1,44 @@
+package org.onap.cps.ncmp.api.impl.events.cmsubscription
+
+import com.fasterxml.jackson.databind.ObjectMapper
+import io.cloudevents.CloudEvent
+import org.onap.cps.events.EventsPublisher
+import org.onap.cps.ncmp.api.impl.events.mapper.CloudEventMapper
+import org.onap.cps.ncmp.events.cmsubscription_merge1_0_0.ncmp_to_client.CmNotificationSubscriptionNcmpOutEvent
+import org.onap.cps.ncmp.events.cmsubscription_merge1_0_0.ncmp_to_client.Data
+import org.onap.cps.utils.JsonObjectMapper
+import spock.lang.Specification
+
+class CmNotificationSubscriptionNcmpOutEventProducerSpec extends Specification {
+
+    def mockEventsPublisher = Mock(EventsPublisher)
+    def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
+
+    def objectUnderTest = new CmNotificationSubscriptionNcmpOutEventProducer(mockEventsPublisher, jsonObjectMapper)
+
+    def 'Create and Publish Cm Notification Subscription DMI In Event'() {
+        given: 'a cm subscription response for the client'
+            def subscriptionId = 'test-subscription-id'
+            def eventType = 'subscriptionCreateResponse'
+            def cmNotificationSubscriptionNcmpOutEvent = new CmNotificationSubscriptionNcmpOutEvent(data: new Data(subscriptionId: 'sub-1', acceptedTargets: ['ch-1', 'ch-2']))
+        and: 'also we have target topic for publishing to client'
+            objectUnderTest.cmNotificationSubscriptionNcmpOutEventTopic = 'client-test-topic'
+        when: 'the event is published'
+            objectUnderTest.publishCmNotificationSubscriptionNcmpOutEvent(subscriptionId, eventType, cmNotificationSubscriptionNcmpOutEvent)
+        then: 'the event contains the required attributes'
+            1 * mockEventsPublisher.publishCloudEvent(_, _, _) >> {
+                args ->
+                    {
+                        assert args[0] == 'client-test-topic'
+                        assert args[1] == subscriptionId
+                        def cmNotificationSubscriptionNcmpOutEventAsCloudEvent = (args[2] as CloudEvent)
+                        assert cmNotificationSubscriptionNcmpOutEventAsCloudEvent.getExtension('correlationid') == subscriptionId
+                        assert cmNotificationSubscriptionNcmpOutEventAsCloudEvent.type == 'subscriptionCreateResponse'
+                        assert cmNotificationSubscriptionNcmpOutEventAsCloudEvent.source.toString() == 'NCMP'
+                        assert CloudEventMapper.toTargetEvent(cmNotificationSubscriptionNcmpOutEventAsCloudEvent, CmNotificationSubscriptionNcmpOutEvent) == cmNotificationSubscriptionNcmpOutEvent
+                    }
+            }
+    }
+
+
+}
index e951c31..eb0e110 100644 (file)
@@ -38,7 +38,7 @@ class CmNotificationSubscriptionPersistenceServiceImplSpec extends Specification
             def cpsPathQuery = "/datastores/datastore[@name='ncmp-datastore:passthrough-running']/cm-handles/cm-handle[@id='ch-1']/filters/filter[@xpath='/cps/path']";
         and: 'datanodes optionally returned'
             1 * mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions',
-                cpsPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS) >> dataNode
+                    cpsPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS) >> dataNode
         when: 'we check for an ongoing cm subscription'
             def response = objectUnderTest.isOngoingCmNotificationSubscription(DatastoreType.PASSTHROUGH_RUNNING, 'ch-1', '/cps/path')
         then: 'we get expected response'
@@ -48,4 +48,20 @@ class CmNotificationSubscriptionPersistenceServiceImplSpec extends Specification
             'valid datanodes present' | [new DataNode(xpath: '/cps/path', leaves: ['subscriptionIds': ['sub-1', 'sub-2']])] || true
             'no datanodes present'    | []                                                                                  || false
     }
-}
+
+    def 'Checking uniqueness of incoming subscription ID'() {
+        given: 'a cps path with a subscription ID for querying'
+            def cpsPathQuery = objectUnderTest.SUBSCRIPTION_IDS_CPS_PATH_QUERY.formatted('some-sub')
+        and: 'relevant datanodes are returned'
+            1 * mockCpsQueryService.queryDataNodes('NCMP-Admin', 'cm-data-subscriptions', cpsPathQuery, FetchDescendantsOption.OMIT_DESCENDANTS) >>
+                    dataNodes
+        when: 'a subscription ID is tested for uniqueness'
+            def result = objectUnderTest.isUniqueSubscriptionId('some-sub')
+        then: 'result is as expected'
+            assert result == isValidSubscriptionId
+        where: 'following scenarios are used'
+            scenario               | dataNodes        || isValidSubscriptionId
+            'datanodes present'    | [new DataNode()] || false
+            'no datanodes present' | []               || true
+    }
+}
\ No newline at end of file
index a3b923f..cbc985f 100644 (file)
 
 package org.onap.cps.ncmp.api.impl.inventory
 
-import org.onap.cps.api.CpsAnchorService
-
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT
-import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NO_TIMESTAMP
-import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
-
 import com.fasterxml.jackson.databind.ObjectMapper
+import org.onap.cps.api.CpsAnchorService
 import org.onap.cps.api.CpsDataService
 import org.onap.cps.api.CpsModuleService
 import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
 import org.onap.cps.spi.CascadeDeleteAllowed
 import org.onap.cps.spi.FetchDescendantsOption
+import org.onap.cps.spi.exceptions.DataNodeNotFoundException
 import org.onap.cps.spi.model.DataNode
 import org.onap.cps.spi.model.ModuleDefinition
 import org.onap.cps.spi.model.ModuleReference
-import org.onap.cps.utils.JsonObjectMapper
 import org.onap.cps.spi.utils.CpsValidator
+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.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME
+import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR
+import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT
+import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME
+import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NO_TIMESTAMP
+import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
+import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
+
 class InventoryPersistenceImplSpec extends Specification {
 
     def spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper()))
@@ -60,14 +62,16 @@ class InventoryPersistenceImplSpec extends Specification {
 
     def mockCpsValidator = Mock(CpsValidator)
 
+    def mockCmHandleQueries = Mock(CmHandleQueries)
+
     def objectUnderTest = new InventoryPersistenceImpl(spiedJsonObjectMapper, mockCpsDataService, mockCpsModuleService,
-            mockCpsValidator, mockCpsAnchorService)
+            mockCpsValidator, mockCpsAnchorService, 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))
 
     def cmHandleId = 'some-cm-handle'
-    def leaves = ["dmi-service-name":"common service name","dmi-data-service-name":"data service name","dmi-model-service-name":"model service name"]
+    def leaves = ["id":cmHandleId,"dmi-service-name":"common service name","dmi-data-service-name":"data service name","dmi-model-service-name":"model service name"]
     def xpath = "/dmi-registry/cm-handles[@id='some-cm-handle']"
 
     def cmHandleId2 = 'another-cm-handle'
@@ -115,7 +119,7 @@ class InventoryPersistenceImplSpec extends Specification {
 
     def "Handling missing service names as null."() {
         given: 'the cps data service returns a data node from the DMI registry with empty child and leaf attributes'
-            def dataNode = new DataNode(childDataNodes:[], leaves: [:])
+            def dataNode = new DataNode(childDataNodes:[], leaves: ['id':cmHandleId])
             mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, xpath, INCLUDE_ALL_DESCENDANTS) >> [dataNode]
         when: 'retrieving the yang modelled cm handle'
             def result = objectUnderTest.getYangModelCmHandle(cmHandleId)
@@ -129,7 +133,7 @@ class InventoryPersistenceImplSpec extends Specification {
 
     def "Retrieve multiple YangModelCmHandles"() {
         given: 'the cps data service returns 2 data nodes from the DMI registry'
-            def dataNodes = [new DataNode(xpath: xpath), new DataNode(xpath: xpath2)]
+            def dataNodes = [new DataNode(xpath: xpath, leaves: ['id': cmHandleId]), new DataNode(xpath: xpath2, leaves: ['id': cmHandleId2])]
             mockCpsDataService.getDataNodesForMultipleXpaths(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, [xpath, xpath2] , INCLUDE_ALL_DESCENDANTS) >> dataNodes
         when: 'retrieving the yang modelled cm handle'
             def results = objectUnderTest.getYangModelCmHandles([cmHandleId, cmHandleId2])
@@ -283,13 +287,32 @@ class InventoryPersistenceImplSpec extends Specification {
 
     def 'Get cmHandle data node'() {
         given: 'expected xPath to get cmHandle data node'
-            def expectedXPath = '/dmi-registry/cm-handles[@id=\'sample cmHandleId\']';
+            def expectedXPath = '/dmi-registry/cm-handles[@id=\'sample cmHandleId\']'
         when: 'the method to get data nodes is called'
-            objectUnderTest.getCmHandleDataNode('sample cmHandleId')
+            objectUnderTest.getCmHandleDataNodeByCmHandleId('sample cmHandleId')
         then: 'the data persistence service method to get cmHandle data node is invoked once with expected xPath'
             1 * mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, expectedXPath, INCLUDE_ALL_DESCENDANTS)
     }
 
+    def 'Get cm handle data node'() {
+        given: 'expected xPath to get cmHandle data node'
+            def expectedXPath = '/dmi-registry/cm-handles[@alternate-id=\'alternate id\']'
+        and: 'query service is invoked with expected xpath'
+            mockCmHandleQueries.queryNcmpRegistryByCpsPath(expectedXPath, OMIT_DESCENDANTS) >> [new DataNode()]
+        expect: 'getting the cm handle data node'
+            assert objectUnderTest.getCmHandleDataNodeByAlternateId('alternate id') == new DataNode()
+    }
+
+    def 'Attempt to get non existing cm handle data node by alternate id'() {
+        given: 'query service is invoked and returns empty collection of data nodes'
+            mockCmHandleQueries.queryNcmpRegistryByCpsPath(*_) >> []
+        when: 'getting the cm handle data node'
+            objectUnderTest.getCmHandleDataNodeByAlternateId('alternate id')
+        then: 'no data found exception thrown'
+            def thrownException = thrown(DataNodeNotFoundException)
+            assert thrownException.getMessage().contains('DataNode not found')
+    }
+
     def 'Get CM handles that has given module names'() {
         when: 'the method to get cm handles is called'
             objectUnderTest.getCmHandleIdsWithGivenModules(['sample-module-name'])
index 827a548..44bc182 100644 (file)
@@ -125,7 +125,7 @@ class ModuleOperationsUtilsSpec extends Specification{
         where:
             scenario               | moduleSetTag       || expectedDetails
             'a module set tag'     | 'someModuleSetTag' || 'someModuleSetTag'
-            'empty module set tag' | ''                 || 'not-specified'
+            'empty module set tag' | ''                 || ''
     }
 
     def 'Get all locked cm-Handles where lock reasons are model sync failed or upgrade'() {
index de783ed..0c60e88 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2022-2023 Nordix Foundation
+ *  Copyright (C) 2022-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.
@@ -26,7 +26,6 @@ import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NFP_OPE
 import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.MODULE_UPGRADE
 
 import org.onap.cps.ncmp.api.impl.inventory.CmHandleState
-import org.onap.cps.spi.FetchDescendantsOption
 import org.onap.cps.spi.model.DataNode
 import org.onap.cps.api.CpsDataService
 import org.onap.cps.api.CpsModuleService
@@ -50,16 +49,18 @@ class ModuleSyncServiceSpec extends Specification {
     def mockCmHandleQueries = Mock(CmHandleQueries)
     def mockCpsDataService = Mock(CpsDataService)
     def mockJsonObjectMapper = Mock(JsonObjectMapper)
-    def mockModuleSetTagCache = [:]
 
     def objectUnderTest = new ModuleSyncService(mockDmiModelOperations, mockCpsModuleService,
-            mockCmHandleQueries, mockCpsDataService, mockCpsAnchorService, mockJsonObjectMapper, mockModuleSetTagCache)
+            mockCmHandleQueries, mockCpsDataService, mockCpsAnchorService, mockJsonObjectMapper)
 
     def expectedDataspaceName = NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME
-    def static cmHandleWithModuleSetTag = new DataNodeBuilder().withXpath("//cm-handles[@module-set-tag='tag-1'][@id='otherId']").withAnchor('otherId').build()
+    def static cmHandleWithModuleSetTag = new DataNodeBuilder()
+            .withXpath("/dmi-registry/cm-handles[@id='otherId']")
+            .withLeaves(['id': 'otherId', 'module-set-tag': 'tag-1'])
+            .withAnchor('otherId').build()
 
-    def 'Sync model for a NEW cm handle without cache with #scenario'() {
-        given: 'a cm handle having some state'
+    def 'Sync model for a NEW cm handle using module set tags: #scenario.'() {
+        given: 'a cm handle state to be synced'
             def ncmpServiceCmHandle = new NcmpServiceCmHandle()
             ncmpServiceCmHandle.setCompositeState(new CompositeStateBuilder().withCmHandleState(CmHandleState.ADVISED).build())
             ncmpServiceCmHandle.cmHandleId = 'ch-1'
@@ -68,22 +69,22 @@ class ModuleSyncServiceSpec extends Specification {
             def moduleReferences =  [ new ModuleReference('module1','1'), new ModuleReference('module2','2') ]
             mockDmiModelOperations.getModuleReferences(yangModelCmHandle) >> moduleReferences
         and: 'DMI-Plugin returns resource(s) for "new" module(s)'
-            mockDmiModelOperations.getNewYangResourcesFromDmi(yangModelCmHandle, [new ModuleReference('module1', '1')]) >> newModuleNameContentToMap
+            mockDmiModelOperations.getNewYangResourcesFromDmi(yangModelCmHandle, identifiedNewModuleReferences) >> newModuleNameContentToMap
         and: 'the module service identifies #identifiedNewModuleReferences.size() new modules'
-            mockCpsModuleService.identifyNewModuleReferences(moduleReferences) >> toModuleReferences(identifiedNewModuleReferences)
-        and: 'system contains #existingCmHandlesWithSameTag.size() cm handles with same tag'
-            mockCmHandleQueries.queryNcmpRegistryByCpsPath("//cm-handles[@module-set-tag='new-tag-1']",
-                    FetchDescendantsOption.OMIT_DESCENDANTS) >> []
+            mockCpsModuleService.identifyNewModuleReferences(moduleReferences) >> identifiedNewModuleReferences
+        and: 'system contains other cm handle with "same tag" (that is READY)'
+            mockCmHandleQueries.queryNcmpRegistryByCpsPath(*_) >> existingCmHandlesWithSameTag
         when: 'module sync is triggered'
-            objectUnderTest.syncAndCreateOrUpgradeSchemaSetAndAnchor(yangModelCmHandle)
+            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)
         and: 'anchor is created with the correct parameters'
             1 * mockCpsAnchorService.createAnchor(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'ch-1', 'ch-1')
         where: 'the following parameters are used'
-            scenario         | existingModuleResourcesInCps         | identifiedNewModuleReferences | newModuleNameContentToMap     | moduleSetTag
-            'one new module' | [['module2': '2'], ['module3': '3']] | [['module1': '1']]            | [module1: 'some yang source'] | ''
-            'no new module'  | [['module1': '1'], ['module2': '2']] | []                            | [:]                           | 'new-tag-1'
+            scenario                  | existingModuleResourcesInCps         | identifiedNewModuleReferences         | newModuleNameContentToMap     | moduleSetTag | existingCmHandlesWithSameTag
+            'one new module, new tag' | [['module2': '2'], ['module3': '3']] | [new ModuleReference('module1', '1')] | [module1: 'some yang source'] | ''           | []
+            'no new module, new tag'  | [['module1': '1'], ['module2': '2']] | []                                    | [:]                           | 'new-tag-1'  | []
+            'same tag'                | [['module1': '1'], ['module2': '2']] | []                                    | [:]                           | 'same-tag'   | [cmHandleWithModuleSetTag]
     }
 
     def 'Upgrade model for an existing cm handle with Module Set Tag where the modules are #scenario'() {
@@ -95,33 +96,29 @@ class ModuleSyncServiceSpec extends Specification {
             def yangModelCmHandle = YangModelCmHandle.toYangModelCmHandle(dmiServiceName, '', '', ncmpServiceCmHandle,'tag-1', '')
         and: 'some module references'
             def moduleReferences =  [ new ModuleReference('module1','1') ]
-        and: 'cache or DMI operations returns some module references for upgraded cm handle'
-            if (populateCache) {
-                mockModuleSetTagCache.put('tag-1', moduleReferences)
-            } else {
-                mockDmiModelOperations.getModuleReferences(yangModelCmHandle) >> moduleReferences
-            }
+        and: 'DMI operations returns some module references for upgraded cm handle'
+            mockDmiModelOperations.getModuleReferences(yangModelCmHandle) >> moduleReferences
+            mockDmiModelOperations.getNewYangResourcesFromDmi(_, []) >> [:]
         and: 'none of these module references are new (unknown to the system)'
-            mockCpsModuleService.identifyNewModuleReferences(moduleReferences) >> []
+            mockCpsModuleService.identifyNewModuleReferences(_) >> []
         and: 'CPS-Core returns list of existing module resources for TBD'
             mockCpsModuleService.getYangResourcesModuleReferences(*_) >> [ new ModuleReference('module1','1') ]
         and: 'system contains #existingCmHandlesWithSameTag.size() cm handles with same tag'
-            mockCmHandleQueries.queryNcmpRegistryByCpsPath("//cm-handles[@module-set-tag='tag-1']", FetchDescendantsOption.OMIT_DESCENDANTS) >> existingCmHandlesWithSameTag
+            mockCmHandleQueries.queryNcmpRegistryByCpsPath(*_) >> existingCmHandlesWithSameTag
         and: 'the other cm handle is a state ready'
             mockCmHandleQueries.cmHandleHasState('otherId', CmHandleState.READY) >> true
         when: 'module sync is triggered'
-            objectUnderTest.syncAndCreateOrUpgradeSchemaSetAndAnchor(yangModelCmHandle)
+            objectUnderTest.syncAndUpgradeSchemaSet(yangModelCmHandle)
         then: 'update schema set from module is invoked for the upgraded cm handle'
-            expectedCallsToUpgradeSchemaSet * mockCpsModuleService.upgradeSchemaSetFromModules(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'upgraded-ch', [:], moduleReferences)
-        and: 'create schema set from module is invoked for the upgraded cm handle'
-            expectedCallsToCeateSchemaSet * mockCpsModuleService.createSchemaSetFromModules(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'upgraded-ch', [:], moduleReferences)
+            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      | populateCache | existingCmHandlesWithSameTag || expectedCallsToUpgradeSchemaSet | expectedCallsToCeateSchemaSet
-            'new'         | false         | []                           || 0                               | 1
-            'in cache'    | true          | []                           || 1                               | 0
-            'in database' | false         | [cmHandleWithModuleSetTag]   || 1                               | 0
+            scenario      | existingCmHandlesWithSameTag
+            'new'         | []
+            'in database' | [cmHandleWithModuleSetTag]
     }
 
     def 'upgrade model for a existing cm handle'() {
@@ -136,10 +133,10 @@ class ModuleSyncServiceSpec extends Specification {
             def moduleReferences = [new ModuleReference('module1', '1'), new ModuleReference('module2', '2')]
             mockCpsModuleService.getYangResourcesModuleReferences(*_)>> moduleReferences
         and: 'a cm handle with the same moduleSetTag can be found in the registry'
-            mockCmHandleQueries.queryNcmpRegistryByCpsPath("//cm-handles[@module-set-tag='targetModuleSetTag']",
-                FetchDescendantsOption.OMIT_DESCENDANTS) >> [new DataNode(xpath: '/dmi-registry/cm-handles[@id=\'cmHandleId-1\']', leaves: ['id': 'cmHandleId-1', 'cm-handle-state': 'READY'])]
+            mockCmHandleQueries.queryNcmpRegistryByCpsPath(*_) >> [new DataNode(xpath: '/dmi-registry/cm-handles[@id=\'cmHandleId-1\']', leaves: ['id': 'cmHandleId-1'],
+                    childDataNodes: [new DataNode(xpath: '/dmi-registry/cm-handles[@id=\'cmHandleId-1\']/state', leaves: ['cm-handle-state': 'READY'])])]
         when: 'module upgrade is triggered'
-            objectUnderTest.syncAndCreateOrUpgradeSchemaSetAndAnchor(yangModelCmHandle)
+            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)
     }
@@ -171,13 +168,4 @@ class ModuleSyncServiceSpec extends Specification {
             result == unsupportedOperationException
     }
 
-    def toModuleReferences(moduleReferenceAsMap) {
-        def moduleReferences = [].withDefault { [:] }
-        moduleReferenceAsMap.forEach(property ->
-            property.forEach((moduleName, revision) -> {
-                moduleReferences.add(new ModuleReference('moduleName' : moduleName, 'revision' : revision))
-            }))
-        return moduleReferences
-    }
-
 }
index ece2d9f..6c0d6df 100644 (file)
@@ -85,8 +85,8 @@ class ModuleSyncTasksSpec extends Specification {
             1 * mockModuleSyncService.deleteSchemaSetIfExists('cm-handle-1')
             1 * mockModuleSyncService.deleteSchemaSetIfExists('cm-handle-2')
         and: 'module sync service is invoked for each cm handle'
-            1 * mockModuleSyncService.syncAndCreateOrUpgradeSchemaSetAndAnchor(_) >> { args -> assertYamgModelCmHandleArgument(args, 'cm-handle-1') }
-            1 * mockModuleSyncService.syncAndCreateOrUpgradeSchemaSetAndAnchor(_) >> { args -> assertYamgModelCmHandleArgument(args, 'cm-handle-2') }
+            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'
             1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(_) >> { args ->
                 assertBatch(args, ['cm-handle-1', 'cm-handle-2'], CmHandleState.READY)
@@ -102,7 +102,7 @@ class ModuleSyncTasksSpec extends Specification {
             def cmHandleState = new CompositeState(cmHandleState: CmHandleState.ADVISED)
             1 * mockInventoryPersistence.getCmHandleState('cm-handle') >> cmHandleState
         and: 'module sync service attempts to sync the cm handle and throws an exception'
-            1 * mockModuleSyncService.syncAndCreateOrUpgradeSchemaSetAndAnchor(*_) >> { throw new Exception('some exception') }
+            1 * mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(*_) >> { throw new Exception('some exception') }
         when: 'module sync is executed'
             objectUnderTest.performModuleSync([cmHandle], batchCount)
         then: 'update lock reason, details and attempts is invoked'
@@ -123,7 +123,8 @@ class ModuleSyncTasksSpec extends Specification {
                 .LockReason.builder().lockReasonCategory(lockReasonCategory).details(lockReasonDetails).build())
             1 * mockInventoryPersistence.getCmHandleState('cm-handle') >> expectedCmHandleState
         and: 'module sync service attempts to sync/upgrade the cm handle and throws an exception'
-            1 * mockModuleSyncService.syncAndCreateOrUpgradeSchemaSetAndAnchor(*_) >> { throw new Exception('some exception') }
+            mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(*_) >> { throw new Exception('some exception') }
+            mockModuleSyncService.syncAndUpgradeSchemaSet(*_) >> { throw new Exception('some exception') }
         when: 'module sync is executed'
             objectUnderTest.performModuleSync([cmHandle], batchCount)
         then: 'update lock reason, details and attempts is invoked'
@@ -170,7 +171,7 @@ class ModuleSyncTasksSpec extends Specification {
         when: 'module sync poll is executed'
             objectUnderTest.performModuleSync([cmHandle1], batchCount)
         then: 'module sync service is invoked for cm handle'
-            1 * mockModuleSyncService.syncAndCreateOrUpgradeSchemaSetAndAnchor(_) >> { args -> assertYamgModelCmHandleArgument(args, 'cm-handle-1') }
+            1 * mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(_) >> { args -> assertYamgModelCmHandleArgument(args, 'cm-handle-1') }
         and: 'the entry for other cm handle is still in the progress map'
             assert moduleSyncStartedOnCmHandles.get('other-cm-handle') != null
     }
@@ -202,14 +203,6 @@ class ModuleSyncTasksSpec extends Specification {
         return new DataNode(anchorName: cmHandleId, leaves: ['id': cmHandleId, 'cm-handle-state': cmHandleState])
     }
 
-    def assertYamgModelCmHandleArgument(args, expectedCmHandleId) {
-        {
-            def yangModelCmHandle = args[0]
-            assert yangModelCmHandle.id == expectedCmHandleId
-        }
-        return true
-    }
-
     def assertBatch(args, expectedCmHandleStatePerCmHandleIds, expectedCmHandleState) {
         {
             Map<YangModelCmHandle, CmHandleState> actualCmHandleStatePerCmHandle = args[0]
index d1025f9..a105f84 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2023 Nordix Foundation
+ *  Copyright (C) 2021-2024 Nordix Foundation
  *  Modifications Copyright (C) 2022 Bell Canada
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
@@ -51,12 +51,6 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec {
     @SpringBean
     JsonObjectMapper spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper()))
 
-    @SpringBean
-    TaskExecutor stubbedTaskExecutor = Stub()
-
-    @SpringBean
-    DmiServiceNameOrganizer stubbedDmiServiceNameOrganizer = Stub()
-
     def 'Retrieving module references.'() {
         given: 'a cm handle'
             mockYangModelCmHandleRetrieval([])
@@ -148,23 +142,33 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec {
         and: 'a positive response from DMI service when it is called with the expected parameters'
             def responseFromDmi = new ResponseEntity<>([[moduleName: 'mod1', revision: 'A', yangSource: 'some yang source']], HttpStatus.OK)
             mockDmiRestClient.postOperationWithJsonData("${dmiServiceName}/dmi/v1/ch/${cmHandleId}/moduleResources",
-                    '{"data":{"modules":[' + expectedModuleReferencesInRequest + ']},"cmHandleProperties":' + expectedAdditionalPropertiesInRequest + '}', READ) >> responseFromDmi
+                    '{"data":{"modules":[{"name":"mod1","revision":"A"},{"name":"mod2","revision":"X"}]},"cmHandleProperties":' + expectedAdditionalPropertiesInRequest + '}', READ) >> responseFromDmi
         when: 'get new yang resources from DMI service'
-            def result = objectUnderTest.getNewYangResourcesFromDmi(yangModelCmHandle, unknownModuleReferences)
+            def result = objectUnderTest.getNewYangResourcesFromDmi(yangModelCmHandle, newModuleReferences)
         then: 'the result is the response from DMI service'
             assert result == [mod1:'some yang source']
         where: 'the following DMI properties are used'
-            scenario                                | dmiProperties               | unknownModuleReferences || expectedAdditionalPropertiesInRequest | expectedModuleReferencesInRequest
-            'with module references and properties' | [yangModelCmHandleProperty] | newModuleReferences     || '{"prop1":"val1"}'                    | '{"name":"mod1","revision":"A"},{"name":"mod2","revision":"X"}'
-            'without module references'             | [yangModelCmHandleProperty] | []                      || '{"prop1":"val1"}'                    | ''
-            'without properties'                    | []                          | newModuleReferences     || '{}'                                  | '{"name":"mod1","revision":"A"},{"name":"mod2","revision":"X"}'
+            scenario                                | dmiProperties               || expectedAdditionalPropertiesInRequest
+            'with module references and properties' | [yangModelCmHandleProperty] || '{"prop1":"val1"}'
+            'without properties'                    | []                          || '{}'
+    }
+
+    def 'Retrieving yang resources from DMI with no module references.'() {
+        given: 'a cm handle'
+            mockYangModelCmHandleRetrieval([])
+        when: 'a get new yang resources from DMI is called with no module references'
+            def result = objectUnderTest.getNewYangResourcesFromDmi(yangModelCmHandle, [])
+        then: 'no resources are returned'
+            assert result == [:]
+        and: 'no request is sent to DMI'
+            0 * mockDmiRestClient.postOperationWithJsonData(*_)
     }
 
     def 'Retrieving yang resources from DMI with null DMI properties.'() {
         given: 'a cm handle'
             mockYangModelCmHandleRetrieval(null)
         when: 'a get new yang resources from DMI is called'
-            objectUnderTest.getNewYangResourcesFromDmi(yangModelCmHandle, [])
+            objectUnderTest.getNewYangResourcesFromDmi(yangModelCmHandle, [new ModuleReference('mod1', 'A')])
         then: 'a null pointer is thrown (we might need to address this later)'
             thrown(NullPointerException)
     }
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/AlternateIdCheckerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/AlternateIdCheckerSpec.groovy
new file mode 100644 (file)
index 0000000..f41fd6c
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ * ============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.ncmp.api.impl.utils
+
+
+import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence
+import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
+import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
+import org.onap.cps.spi.exceptions.DataNodeNotFoundException
+import org.onap.cps.spi.model.DataNode
+import org.onap.cps.spi.model.DataNodeBuilder
+import spock.lang.Specification
+
+class AlternateIdCheckerSpec extends Specification {
+
+    def mockInventoryPersistenceService = Mock(InventoryPersistence)
+    def someDataNode = new DataNodeBuilder().build()
+    def dataNodeFoundException = new DataNodeNotFoundException('', '')
+
+    def objectUnderTest = new AlternateIdChecker(mockInventoryPersistenceService)
+
+    def 'Check new cm handle with new alternate id.'() {
+        given: 'inventory persistence can not find cm handle id'
+            mockInventoryPersistenceService.getYangModelCmHandle('ch 1') >> {throw dataNodeFoundException}
+        and: 'inventory persistence can not find alternate id'
+            mockInventoryPersistenceService.getCmHandleDataNodeByAlternateId('alternate id') >> {throw dataNodeFoundException}
+        expect: 'mapping can be added'
+             assert objectUnderTest.canApplyAlternateId('ch 1', 'alternate id')
+    }
+
+    def 'Check new cm handle with used alternate id.'() {
+        given: 'inventory persistence can not find cm handle id'
+            mockInventoryPersistenceService.getYangModelCmHandle('ch 1') >> {throw dataNodeFoundException}
+        and: 'inventory persistence can find alternate id'
+            mockInventoryPersistenceService.getCmHandleDataNodeByAlternateId('alternate id') >> { someDataNode }
+        expect: 'mapping can not be added'
+            assert objectUnderTest.canApplyAlternateId('ch 1', 'alternate id') == false
+    }
+
+    def 'Check for existing cm handle with #currentAlternateId.'() {
+        given: 'a cm handle with the #currentAlternateId'
+            def yangModelCmHandle = new YangModelCmHandle(alternateId: currentAlternateId)
+        and: 'inventory service finds the cm handle'
+            mockInventoryPersistenceService.getYangModelCmHandle('my cm handle') >> yangModelCmHandle
+        expect: 'add mapping returns expected result'
+            assert canAdd == objectUnderTest.canApplyAlternateId('my cm handle', 'same alternate id')
+        where: 'following alternate ids is used'
+            currentAlternateId   || canAdd
+            'same alternate id'  || true
+            'other alternate id' || false
+    }
+
+    def 'Check a batch of NEW cm handles with #scenario.'() {
+        given: 'a batch of 2 new cm handles alternate id ids #alt1 and #alt2'
+            def batch = [new NcmpServiceCmHandle(cmHandleId: 'ch-1', alternateId: alt1),
+                         new NcmpServiceCmHandle(cmHandleId: 'ch-2', alternateId: alt2)]
+        and: 'the database already contains cm handle(s) with these alternate ids: #alreadyinDb'
+            mockInventoryPersistenceService.getCmHandleDataNodeByAlternateId(_) >>
+                {  args -> altAlreadyInDb.contains(args[0]) ? new DataNode() : throwDataNodeNotFoundException() }
+        when: 'the batch of new cm handles is checked'
+            def result = objectUnderTest.getIdsOfCmHandlesWithAcceptableAlternateId(batch)
+        then: 'the result only contains the ids of the acceptable cm handles'
+            assert result.contains('ch-1') == acceptCh1
+            assert result.contains('ch-2') == acceptCh2
+        where: 'the following alternate ids are used'
+            scenario                          | alt1   | alt2   | altAlreadyInDb  || acceptCh1 | acceptCh2
+            'no alternate ids'                | ''     | ''     | ['dont matter'] || true      | true
+            'new alternate ids'               | 'fdn1' | 'fdn2' | ['other fdn']   || true      | true
+            'one already used alternate id'   | 'fdn1' | 'fdn2' | ['fdn1']        || false     | true
+            'two already used alternate ids'  | 'fdn1' | 'fdn2' | ['fdn1','fdn2'] || false     | false
+            'duplicate alternate id in batch' | 'fdn1' | 'fdn1' | ['dont matter'] || true      | false
+    }
+
+    def throwDataNodeNotFoundException() {
+        // cannot 'return' an exception in conditional stub behavior, so hence a method call that will always throw this exception
+        throw dataNodeFoundException
+    }
+
+}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/CmHandleIdMapperSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/CmHandleIdMapperSpec.groovy
deleted file mode 100644 (file)
index 55ccdf3..0000000
+++ /dev/null
@@ -1,126 +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.ncmp.api.impl.utils
-
-import ch.qos.logback.classic.Level
-import ch.qos.logback.classic.Logger
-import ch.qos.logback.classic.spi.ILoggingEvent
-import ch.qos.logback.core.read.ListAppender
-import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
-import org.slf4j.LoggerFactory
-import org.onap.cps.ncmp.api.NetworkCmProxyCmHandleQueryService
-import spock.lang.Specification
-
-class CmHandleIdMapperSpec extends Specification {
-
-    def alternateIdPerCmHandle = new HashMap<String, String>()
-    def cmHandlePerAlternateId = new HashMap<String, String>()
-    def mockCpsCmHandlerQueryService = Mock(NetworkCmProxyCmHandleQueryService)
-
-    def objectUnderTest = new CmHandleIdMapper(alternateIdPerCmHandle, cmHandlePerAlternateId, mockCpsCmHandlerQueryService)
-
-    def logger = Spy(ListAppender<ILoggingEvent>)
-
-    def setup() {
-        ((Logger) LoggerFactory.getLogger(CmHandleIdMapper.class)).addAppender(logger)
-        logger.start()
-        mockCpsCmHandlerQueryService.getAllCmHandles() >> []
-        assert objectUnderTest.addMapping('my cmhandle id', 'my alternate id')
-    }
-
-    void cleanup() {
-        ((Logger) LoggerFactory.getLogger(CmHandleIdMapper.class)).detachAndStopAllAppenders()
-    }
-
-    def 'Checking entries in the cache.'() {
-        expect: 'the alternate id can be converted to cmhandle id'
-            assert objectUnderTest.alternateIdToCmHandleId('my alternate id') == 'my cmhandle id'
-        and: 'the cmhandle id can be converted to alternate id'
-            assert objectUnderTest.cmHandleIdToAlternateId('my cmhandle id') == 'my alternate id'
-    }
-
-    def 'Attempt adding #scenario alternate id.'() {
-        expect: 'cmhandle id - alternate id mapping fails'
-            assert objectUnderTest.addMapping('ch-1', alternateId) == false
-        and: 'alternate id looked up by cmhandle id unsuccessfully'
-            assert objectUnderTest.cmHandleIdToAlternateId('ch-1') == null
-        where: 'alternate id has an invalid value'
-            scenario | alternateId
-            'empty'  | ''
-            'blank'  | '  '
-            'null'   | null
-    }
-
-    def 'Remove an entry from the cache.'() {
-        when: 'removing an entry'
-            objectUnderTest.removeMapping('my cmhandle id')
-        then: 'converting alternate id returns null'
-            assert objectUnderTest.alternateIdToCmHandleId('my alternate id') == null
-        and: 'converting cmhandle id returns null'
-            assert objectUnderTest.cmHandleIdToAlternateId('my cmhandle id') == null
-    }
-
-    def 'Attempt to remove a non-existing entry from the cache.'() {
-        when: 'removing an entry that is not cached'
-            objectUnderTest.removeMapping('non-cached cmhandle id')
-        then: 'deleting from the cmhandle cache returns null'
-            assert alternateIdPerCmHandle.remove('non-cached cmhandle id') == null
-        and: 'removal from the alternate id cache is skipped'
-            0 * cmHandlePerAlternateId.remove(_)
-    }
-
-    def 'Cannot update existing alternate id.'() {
-        given: 'attempt to update an existing alternate id'
-            objectUnderTest.addMapping('my cmhandle id', 'other id')
-        expect: 'still returns the original alternate id'
-            assert objectUnderTest.cmHandleIdToAlternateId('my cmhandle id') == 'my alternate id'
-        and: 'converting other alternate id returns null'
-            assert objectUnderTest.alternateIdToCmHandleId('other id') == null
-        and: 'a warning is logged with the original alternate id'
-            def lastLoggingEvent = logger.list[1]
-            assert lastLoggingEvent.level == Level.WARN
-            assert lastLoggingEvent.formattedMessage.contains('my alternate id')
-    }
-
-    def 'Update existing alternate id with the same value.'() {
-        expect: 'update an existing alternate id with the same value returns false (no update)'
-            assert objectUnderTest.addMapping('my cmhandle id', 'my alternate id') == false
-        and: 'conversion still returns the original alternate id'
-            assert objectUnderTest.cmHandleIdToAlternateId('my cmhandle id') == 'my alternate id'
-    }
-
-    def 'Initializing cache #scenario.'() {
-        when: 'the cache is (re-)initialized'
-            objectUnderTest.cacheIsInitialized = false
-            objectUnderTest.initializeCache()
-        then: 'the alternate id can be converted to cmhandle id'
-            assert objectUnderTest.alternateIdToCmHandleId('alt-1') == convertedCmHandleId
-        and: 'the cm handle id can be converted to alternate id'
-            assert objectUnderTest.cmHandleIdToAlternateId('ch-1') == convertedAlternatId
-        and: 'the query service is called to get the initial data'
-            1 * mockCpsCmHandlerQueryService.getAllCmHandles() >> persistedCmHandles
-        where: 'the initial data has a cm handle #scenario'
-            scenario                  | persistedCmHandles                                                  || convertedAlternatId | convertedCmHandleId
-            'with alternate id'       | [new NcmpServiceCmHandle(cmHandleId: 'ch-1', alternateId: 'alt-1')] || 'alt-1'             | 'ch-1'
-            'without alternate id'    | [new NcmpServiceCmHandle(cmHandleId: 'ch-1')]                       || null                | null
-            'with blank alternate id' | [new NcmpServiceCmHandle(cmHandleId: 'ch-1', alternateId: ' ')]     || null                | null
-    }
-}
\ No newline at end of file
index 6d733dc..0ae348c 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * ============LICENSE_START========================================================
- *  Copyright (C) 2022-2023 Nordix Foundation
+ *  Copyright (C) 2022-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.
@@ -31,9 +31,10 @@ class YangDataConverterSpec extends Specification{
                     leaves: ['name': 'dmiProp1', 'value': 'dmiValue1'])
             def dataNodePublicProperties = new DataNode(xpath:'/public-properties[@name="pubProp1"]',
                     leaves: ['name': 'pubProp1', 'value': 'pubValue1'])
-            def dataNodeCmHandle = new DataNode(childDataNodes:[dataNodeAdditionalProperties, dataNodePublicProperties])
+            def dataNodeCmHandle = new DataNode(leaves:['id':'sample-id'], childDataNodes:[dataNodeAdditionalProperties, dataNodePublicProperties])
         when: 'the dataNode is converted'
-            def yangModelCmHandle = YangDataConverter.convertCmHandleToYangModel(dataNodeCmHandle,'sample-id')
+            def yangModelCmHandle =
+                    YangDataConverter.convertCmHandleToYangModel(dataNodeCmHandle)
         then: 'the converted object has the correct id'
             assert yangModelCmHandle.id == 'sample-id'
         and: 'the additional (dmi, private) properties are included'
@@ -46,8 +47,8 @@ class YangDataConverterSpec extends Specification{
 
     def 'Convert multiple cm handle data nodes'(){
         given: 'two data nodes in a collection'
-            def dataNodes = [new DataNode(xpath:'/dmi-registry/cm-handles[@id=\'some-cm-handle\']'),
-                             new DataNode(xpath:'/dmi-registry/cm-handles[@id=\'another-cm-handle\']')]
+            def dataNodes = [new DataNode(xpath:'/dmi-registry/cm-handles[@id=\'some-cm-handle\']', leaves: ['id':'some-cm-handle']),
+                             new DataNode(xpath:'/dmi-registry/cm-handles[@id=\'another-cm-handle\']', leaves: ['id':'another-cm-handle'])]
         when: 'the data nodes are converted'
             def yangModelCmHandles = YangDataConverter.convertDataNodesToYangModelCmHandles(dataNodes)
         then: 'verify both have returned and CmHandleIds are correct'
index 3b5aad1..c1ee1eb 100644 (file)
@@ -335,9 +335,5 @@ Below are the list of distributed datastructures that we have.
 +--------------+------------------------------------+-----------------------------------------------------------+
 | cps-ncmp     | cmNotificationSubscriptionCache    | Stores and tracks cm notification subscription requests.  |
 +--------------+------------------------------------+-----------------------------------------------------------+
-| cps-ncmp     | alternateIdPerCmHandleId           | Stores the alternate id for each cm handle id.            |
-+--------------+------------------------------------+-----------------------------------------------------------+
-| cps-ncmp     | cmHandleIdPerAlternateId           | Stores the cm handle id for each alternate id.            |
-+--------------+------------------------------------+-----------------------------------------------------------+
 
-Total number of caches : 10
+Total number of caches : 8
index ecf7d67..23504e4 100644 (file)
@@ -30,6 +30,10 @@ import org.onap.cps.integration.DatabaseTestContainer
 import org.onap.cps.ncmp.api.NetworkCmProxyCmHandleQueryService
 import org.onap.cps.ncmp.api.NetworkCmProxyDataService
 import org.onap.cps.ncmp.api.NetworkCmProxyQueryService
+import org.onap.cps.ncmp.api.impl.inventory.CmHandleState
+import org.onap.cps.ncmp.api.impl.inventory.sync.ModuleSyncWatchdog
+import org.onap.cps.ncmp.api.models.DmiPluginRegistration
+import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
 import org.onap.cps.spi.exceptions.DataspaceNotFoundException
 import org.onap.cps.spi.model.DataNode
 import org.onap.cps.spi.repository.DataspaceRepository
@@ -40,10 +44,17 @@ import org.springframework.boot.autoconfigure.domain.EntityScan
 import org.springframework.boot.test.context.SpringBootTest
 import org.springframework.context.annotation.ComponentScan
 import org.springframework.data.jpa.repository.config.EnableJpaRepositories
+import org.springframework.http.HttpStatus
+import org.springframework.http.MediaType
+import org.springframework.test.web.client.MockRestServiceServer
 import org.springframework.web.client.RestTemplate
 import org.testcontainers.spock.Testcontainers
 import spock.lang.Shared
 import spock.lang.Specification
+import spock.util.concurrent.PollingConditions
+
+import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo
+import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus
 
 @SpringBootTest(classes = [CpsDataspaceService])
 @Testcontainers
@@ -74,9 +85,6 @@ abstract class CpsIntegrationSpecBase extends Specification {
     @Autowired
     SessionManager sessionManager
 
-    @Autowired
-    RestTemplate restTemplate
-
     @Autowired
     NetworkCmProxyCmHandleQueryService networkCmProxyCmHandleQueryService
 
@@ -86,6 +94,16 @@ abstract class CpsIntegrationSpecBase extends Specification {
     @Autowired
     NetworkCmProxyQueryService networkCmProxyQueryService
 
+    @Autowired
+    RestTemplate restTemplate
+
+    @Autowired
+    ModuleSyncWatchdog moduleSyncWatchdog
+
+    MockRestServiceServer mockDmiServer = null
+
+    static final DMI_URL = 'http://mock-dmi-server'
+
     def static GENERAL_TEST_DATASPACE = 'generalTestDataspace'
     def static BOOKSTORE_SCHEMA_SET = 'bookstoreSchemaSet'
 
@@ -98,8 +116,19 @@ abstract class CpsIntegrationSpecBase extends Specification {
             createStandardBookStoreSchemaSet(GENERAL_TEST_DATASPACE)
             initialized = true
         }
+        mockDmiServer = MockRestServiceServer.createServer(restTemplate)
+    }
+
+    def cleanup() {
+        mockDmiServer.reset()
     }
 
+    def static readResourceDataFile(filename) {
+        return new File('src/test/resources/data/' + filename).text
+    }
+
+    // *** CPS Integration Test Utilities ***
+
     def static countDataNodesInTree(DataNode dataNode) {
         return 1 + countDataNodesInTree(dataNode.getChildDataNodes())
     }
@@ -112,10 +141,6 @@ abstract class CpsIntegrationSpecBase extends Specification {
         return nodeCount
     }
 
-    def static readResourceDataFile(filename) {
-        return new File('src/test/resources/data/' + filename).text
-    }
-
     def getBookstoreYangResourcesNameToContentMap() {
         def bookstoreModelFileContent = readResourceDataFile('bookstore/bookstore.yang')
         def bookstoreTypesFileContent = readResourceDataFile('bookstore/bookstore-types.yang')
@@ -158,4 +183,35 @@ abstract class CpsIntegrationSpecBase extends Specification {
         return '"' + name + '":[' + innerJson + ']'
     }
 
+    // *** NCMP Integration Test Utilities ***
+
+    def registerCmHandle(dmiPlugin, cmHandleId, moduleSetTag, dmiModuleReferencesResponse, dmiModuleResourcesResponse) {
+        def cmHandleToCreate = new NcmpServiceCmHandle(cmHandleId: cmHandleId, moduleSetTag: moduleSetTag)
+        networkCmProxyDataService.updateDmiRegistrationAndSyncModule(new DmiPluginRegistration(dmiPlugin: dmiPlugin, createdCmHandles: [cmHandleToCreate]))
+        mockDmiResponsesForRegistration(dmiPlugin, cmHandleId, dmiModuleReferencesResponse, dmiModuleResourcesResponse)
+        moduleSyncWatchdog.moduleSyncAdvisedCmHandles()
+        new PollingConditions().within(3, () -> {
+            CmHandleState.READY == networkCmProxyDataService.getCmHandleCompositeState(cmHandleId).cmHandleState
+        })
+        mockDmiServer.reset()
+    }
+
+    def deregisterCmHandle(dmiPlugin, cmHandleId) {
+        deregisterCmHandles(dmiPlugin, [cmHandleId])
+    }
+
+    def deregisterCmHandles(dmiPlugin, cmHandleIds) {
+        networkCmProxyDataService.updateDmiRegistrationAndSyncModule(new DmiPluginRegistration(dmiPlugin: dmiPlugin, removedCmHandles: cmHandleIds))
+    }
+
+    def mockDmiResponsesForRegistration(dmiPlugin, cmHandleId, dmiModuleReferencesResponse, dmiModuleResourcesResponse) {
+        if (dmiModuleReferencesResponse != null) {
+            mockDmiServer.expect(requestTo("${dmiPlugin}/dmi/v1/ch/${cmHandleId}/modules"))
+                    .andRespond(withStatus(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON).body(dmiModuleReferencesResponse))
+        }
+        if (dmiModuleResourcesResponse != null) {
+            mockDmiServer.expect(requestTo("${dmiPlugin}/dmi/v1/ch/${cmHandleId}/moduleResources"))
+                    .andRespond(withStatus(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON).body(dmiModuleResourcesResponse))
+        }
+    }
 }
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmHandleCreateSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmHandleCreateSpec.groovy
new file mode 100644 (file)
index 0000000..4369b79
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+ *  ============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.integration.functional
+
+import org.onap.cps.integration.base.CpsIntegrationSpecBase
+import org.onap.cps.ncmp.api.NetworkCmProxyDataService
+import org.onap.cps.ncmp.api.impl.inventory.CmHandleState
+import org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory
+import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse
+import org.onap.cps.ncmp.api.models.DmiPluginRegistration
+import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
+import org.springframework.http.HttpStatus
+import spock.util.concurrent.PollingConditions
+
+import static org.springframework.test.web.client.match.MockRestRequestMatchers.anything
+import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus
+
+class NcmpCmHandleCreateSpec extends CpsIntegrationSpecBase {
+
+    NetworkCmProxyDataService objectUnderTest
+
+    static final MODULE_REFERENCES_RESPONSE_A = readResourceDataFile('mock-dmi-responses/bookStoreAWithModules_M1_M2_Response.json')
+    static final MODULE_RESOURCES_RESPONSE_A = readResourceDataFile('mock-dmi-responses/bookStoreAWithModules_M1_M2_ResourcesResponse.json')
+    static final MODULE_REFERENCES_RESPONSE_B = readResourceDataFile('mock-dmi-responses/bookStoreBWithModules_M1_M3_Response.json')
+    static final MODULE_RESOURCES_RESPONSE_B = readResourceDataFile('mock-dmi-responses/bookStoreBWithModules_M1_M3_ResourcesResponse.json')
+
+    def setup() {
+        objectUnderTest = networkCmProxyDataService
+    }
+
+    def 'CM Handle registration is successful.'() {
+        given: 'DMI will return modules when requested'
+            mockDmiResponsesForRegistration(DMI_URL, 'ch-1', MODULE_REFERENCES_RESPONSE_A, MODULE_RESOURCES_RESPONSE_A)
+
+        when: 'a CM-handle is registered for creation'
+            def cmHandleToCreate = new NcmpServiceCmHandle(cmHandleId: 'ch-1')
+            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: DMI_URL, createdCmHandles: [cmHandleToCreate])
+            def dmiPluginRegistrationResponse = networkCmProxyDataService.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
+
+        then: 'registration gives successful response'
+            assert dmiPluginRegistrationResponse.createdCmHandles == [CmHandleRegistrationResponse.createSuccessResponse('ch-1')]
+
+        and: 'CM-handle is initially in ADVISED state'
+            assert CmHandleState.ADVISED == objectUnderTest.getCmHandleCompositeState('ch-1').cmHandleState
+
+        when: 'module sync runs'
+            moduleSyncWatchdog.moduleSyncAdvisedCmHandles()
+
+        then: 'CM-handle goes to READY state'
+            new PollingConditions().within(3, () -> {
+                assert CmHandleState.READY == objectUnderTest.getCmHandleCompositeState('ch-1').cmHandleState
+            })
+
+        and: 'the CM-handle has expected modules'
+            assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences('ch-1').moduleName.sort()
+
+        and: 'DMI received expected requests'
+            mockDmiServer.verify()
+
+        cleanup: 'deregister CM handle'
+            deregisterCmHandle(DMI_URL, 'ch-1')
+    }
+
+    def 'CM Handle goes to LOCKED state when DMI gives error during module sync.'() {
+        given: 'DMI is not available to handle requests'
+            mockDmiServer.expect(anything()).andRespond(withStatus(HttpStatus.SERVICE_UNAVAILABLE))
+
+        when: 'a CM-handle is registered for creation'
+            def cmHandleToCreate = new NcmpServiceCmHandle(cmHandleId: 'ch-1')
+            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: DMI_URL, createdCmHandles: [cmHandleToCreate])
+            networkCmProxyDataService.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
+
+        and: 'module sync runs'
+            moduleSyncWatchdog.moduleSyncAdvisedCmHandles()
+
+        then: 'CM-handle goes to LOCKED state with reason MODULE_SYNC_FAILED'
+            new PollingConditions().within(3, () -> {
+                def cmHandleCompositeState = objectUnderTest.getCmHandleCompositeState('ch-1')
+                assert cmHandleCompositeState.cmHandleState == CmHandleState.LOCKED
+                assert cmHandleCompositeState.lockReason.lockReasonCategory == LockReasonCategory.MODULE_SYNC_FAILED
+            })
+
+        and: 'CM-handle has no modules'
+            assert objectUnderTest.getYangResourcesModuleReferences('ch-1').empty
+
+        cleanup: 'deregister CM handle'
+            deregisterCmHandle(DMI_URL, 'ch-1')
+    }
+
+    def 'Create a CM-handle with existing moduleSetTag.'() {
+        given: 'existing CM-handles cm-1 with moduleSetTag "A", and cm-2 with moduleSetTag "B"'
+            registerCmHandle(DMI_URL, 'ch-1', 'A', MODULE_REFERENCES_RESPONSE_A, MODULE_RESOURCES_RESPONSE_A)
+            registerCmHandle(DMI_URL, 'ch-2', 'B', MODULE_REFERENCES_RESPONSE_B, MODULE_RESOURCES_RESPONSE_B)
+            assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences('ch-1').moduleName.sort()
+            assert ['M1', 'M3'] == objectUnderTest.getYangResourcesModuleReferences('ch-2').moduleName.sort()
+
+        when: 'a CM-handle is registered for creation with moduleSetTag "B"'
+            def cmHandleToCreate = new NcmpServiceCmHandle(cmHandleId: 'ch-3', moduleSetTag: 'B')
+            networkCmProxyDataService.updateDmiRegistrationAndSyncModule(new DmiPluginRegistration(dmiPlugin: DMI_URL, createdCmHandles: [cmHandleToCreate]))
+
+        then: 'the CM-handle goes to READY state after module sync'
+            moduleSyncWatchdog.moduleSyncAdvisedCmHandles()
+            new PollingConditions().within(3, () -> {
+                assert CmHandleState.READY == objectUnderTest.getCmHandleCompositeState('ch-3').cmHandleState
+            })
+
+        and: 'the CM-handle has expected moduleSetTag'
+            assert objectUnderTest.getNcmpServiceCmHandle('ch-3').moduleSetTag == 'B'
+
+        and: 'the CM-handle has expected modules from module set "B": M1 and M3'
+            assert ['M1', 'M3'] == objectUnderTest.getYangResourcesModuleReferences('ch-3').moduleName.sort()
+
+        cleanup: 'deregister CM handles'
+            deregisterCmHandles(DMI_URL, ['ch-1', 'ch-2', 'ch-3'])
+    }
+}
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmHandleRegistrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmHandleRegistrationSpec.groovy
deleted file mode 100644 (file)
index 9cd4d7c..0000000
+++ /dev/null
@@ -1,141 +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.integration.functional
-
-import org.onap.cps.integration.base.CpsIntegrationSpecBase
-import org.onap.cps.ncmp.api.NetworkCmProxyDataService
-import org.onap.cps.ncmp.api.impl.inventory.CmHandleState
-import org.onap.cps.ncmp.api.impl.inventory.sync.ModuleSyncWatchdog
-import org.onap.cps.ncmp.api.models.DmiPluginRegistration
-import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
-import org.onap.cps.spi.exceptions.DataNodeNotFoundException
-import org.springframework.beans.factory.annotation.Autowired
-import org.springframework.http.HttpStatus
-import org.springframework.http.MediaType
-import org.springframework.test.web.client.MockRestServiceServer
-import spock.util.concurrent.PollingConditions
-
-import static org.springframework.test.web.client.match.MockRestRequestMatchers.anything
-import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo
-import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus
-
-class NcmpCmHandleRegistrationSpec extends CpsIntegrationSpecBase {
-
-    NetworkCmProxyDataService objectUnderTest
-
-    @Autowired
-    ModuleSyncWatchdog moduleSyncWatchdog
-
-    static final DMI_URL = 'http://mock-dmi-server'
-    def mockDmiServer
-    def moduleReferencesResponse
-    def moduleResourcesResponse
-
-    def setup() {
-        objectUnderTest = networkCmProxyDataService
-        mockDmiServer = MockRestServiceServer.createServer(restTemplate)
-        moduleReferencesResponse = readResourceDataFile('mock-dmi-responses/ietfYangModuleResponse.json')
-        moduleResourcesResponse = readResourceDataFile('mock-dmi-responses/ietfYangModuleResourcesResponse.json')
-    }
-
-    def 'CM Handle is READY when Registration is successful.'() {
-        given: 'a CM handle to create'
-            def cmHandlesToCreate = [new NcmpServiceCmHandle(cmHandleId: 'cm-1')]
-
-        and: 'DMI registration params'
-            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: DMI_URL, createdCmHandles: cmHandlesToCreate)
-            dmiPluginRegistration.validateDmiPluginRegistration()
-
-        and: 'DMI returns modules'
-            mockDmiServer.expect(requestTo("${DMI_URL}/dmi/v1/ch/cm-1/modules"))
-                    .andRespond(withStatus(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON).body(moduleReferencesResponse))
-            mockDmiServer.expect(requestTo("${DMI_URL}/dmi/v1/ch/cm-1/moduleResources"))
-                    .andRespond(withStatus(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON).body(moduleResourcesResponse))
-
-        when: 'a CM-handle is registered'
-            def dmiPluginRegistrationResponse = networkCmProxyDataService.updateDmiRegistrationAndSyncModule(dmiPluginRegistration);
-
-        then: 'registration gives expected response'
-            assert dmiPluginRegistrationResponse.createdCmHandles.size() == 1
-
-        and: 'CM-handle is initially in ADVISED state'
-            assert CmHandleState.ADVISED == objectUnderTest.getCmHandleCompositeState('cm-1').cmHandleState
-
-        when: 'module sync runs'
-            moduleSyncWatchdog.moduleSyncAdvisedCmHandles()
-
-        then: 'CM-handle goes to READY state'
-            new PollingConditions().within(3, () -> {
-                assert CmHandleState.READY == objectUnderTest.getCmHandleCompositeState('cm-1').cmHandleState
-            })
-
-        and: 'DMI received expected requests'
-            mockDmiServer.verify()
-    }
-
-    def 'CM Handle goes to LOCKED state when DMI gives error during module sync.'() {
-        given: 'a CM handle to create'
-            def cmHandlesToCreate = [new NcmpServiceCmHandle(cmHandleId: 'cm-2')]
-
-        and: 'DMI registration params'
-            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: DMI_URL, createdCmHandles: cmHandlesToCreate)
-            dmiPluginRegistration.validateDmiPluginRegistration()
-
-        and: 'DMI returns error code'
-            mockDmiServer.expect(anything()).andRespond(withStatus(HttpStatus.SERVICE_UNAVAILABLE))
-
-        when: 'a CM-handle is registered'
-            def dmiPluginRegistrationResponse = networkCmProxyDataService.updateDmiRegistrationAndSyncModule(dmiPluginRegistration);
-
-        then: 'registration gives expected response'
-            assert dmiPluginRegistrationResponse.createdCmHandles.size() == 1
-
-        and: 'CM-handle is initially in ADVISED state'
-            assert CmHandleState.ADVISED == objectUnderTest.getCmHandleCompositeState('cm-2').cmHandleState
-
-        when: 'module sync runs'
-            moduleSyncWatchdog.moduleSyncAdvisedCmHandles()
-
-        then: 'CM-handle goes to LOCKED state'
-            new PollingConditions().within(3, () -> {
-                assert CmHandleState.LOCKED == objectUnderTest.getCmHandleCompositeState('cm-2').cmHandleState
-            })
-
-        and: 'DMI received expected requests'
-            mockDmiServer.verify()
-    }
-
-    def 'Deregister CM-handles.'() {
-        given: 'a list of CM handles to remove'
-            def cmHandlesToRemove = ['cm-1', 'cm-2']
-
-        and: 'DMI registration parameters are set'
-            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: DMI_URL, removedCmHandles: cmHandlesToRemove)
-            dmiPluginRegistration.validateDmiPluginRegistration()
-
-        when: 'the CM-handles are deregistered'
-            networkCmProxyDataService.updateDmiRegistrationAndSyncModule(dmiPluginRegistration);
-
-        then: 'the CM-handles no longer exists'
-            assert !objectUnderTest.getAllCmHandleIdsByDmiPluginIdentifier(DMI_URL).contains('cm-1')
-            assert !objectUnderTest.getAllCmHandleIdsByDmiPluginIdentifier(DMI_URL).contains('cm-2')
-    }
-}
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmHandleUpgradeSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmHandleUpgradeSpec.groovy
new file mode 100644 (file)
index 0000000..ffa4382
--- /dev/null
@@ -0,0 +1,196 @@
+/*
+ *  ============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.integration.functional
+
+import org.onap.cps.integration.base.CpsIntegrationSpecBase
+import org.onap.cps.ncmp.api.NetworkCmProxyDataService
+import org.onap.cps.ncmp.api.impl.inventory.CmHandleState
+import org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory
+import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse
+import org.onap.cps.ncmp.api.models.DmiPluginRegistration
+import org.onap.cps.ncmp.api.models.UpgradedCmHandles
+import org.springframework.http.HttpStatus
+import spock.util.concurrent.PollingConditions
+
+import static org.springframework.test.web.client.match.MockRestRequestMatchers.anything
+import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus
+
+class NcmpCmHandleUpgradeSpec extends CpsIntegrationSpecBase {
+
+    NetworkCmProxyDataService objectUnderTest
+
+    static final INITIAL_MODULE_REFERENCES_RESPONSE = readResourceDataFile('mock-dmi-responses/bookStoreAWithModules_M1_M2_Response.json')
+    static final INITIAL_MODULE_RESOURCES_RESPONSE = readResourceDataFile('mock-dmi-responses/bookStoreAWithModules_M1_M2_ResourcesResponse.json')
+    static final UPDATED_MODULE_REFERENCES_RESPONSE = readResourceDataFile('mock-dmi-responses/bookStoreBWithModules_M1_M3_Response.json')
+    static final UPDATED_MODULE_RESOURCES_RESPONSE = readResourceDataFile('mock-dmi-responses/bookStoreBWithModules_M1_M3_ResourcesResponse.json')
+    static final NO_MODULE_SET_TAG = ''
+    static final CM_HANDLE_ID = 'ch-1'
+    static final CM_HANDLE_ID_WITH_EXISTING_MODULE_SET_TAG = 'ch-2'
+
+    def setup() {
+        objectUnderTest = networkCmProxyDataService
+    }
+
+    def 'Upgrade CM-handle with new moduleSetTag or no moduleSetTag.'() {
+        given: 'an existing CM-handle with expected initial modules: M1 and M2'
+            registerCmHandle(DMI_URL, CM_HANDLE_ID, initialModuleSetTag, INITIAL_MODULE_REFERENCES_RESPONSE, INITIAL_MODULE_RESOURCES_RESPONSE)
+            assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences(CM_HANDLE_ID).moduleName.sort()
+
+        and: 'DMI returns different modules for upgrade'
+            mockDmiResponsesForRegistration(DMI_URL, CM_HANDLE_ID, UPDATED_MODULE_REFERENCES_RESPONSE, UPDATED_MODULE_RESOURCES_RESPONSE)
+
+        when: "CM-handle is upgraded with given moduleSetTag '${updatedModuleSetTag}'"
+            def cmHandlesToUpgrade = new UpgradedCmHandles(cmHandles: [CM_HANDLE_ID], moduleSetTag: updatedModuleSetTag)
+            def dmiPluginRegistrationResponse = networkCmProxyDataService.updateDmiRegistrationAndSyncModule(
+                    new DmiPluginRegistration(dmiPlugin: DMI_URL, upgradedCmHandles: cmHandlesToUpgrade))
+
+        then: 'registration gives successful response'
+            assert dmiPluginRegistrationResponse.upgradedCmHandles == [CmHandleRegistrationResponse.createSuccessResponse(CM_HANDLE_ID)]
+
+        and: 'CM-handle is in LOCKED state due to MODULE_UPGRADE'
+            def cmHandleCompositeState = objectUnderTest.getCmHandleCompositeState(CM_HANDLE_ID)
+            assert cmHandleCompositeState.cmHandleState == CmHandleState.LOCKED
+            assert cmHandleCompositeState.lockReason.lockReasonCategory == LockReasonCategory.MODULE_UPGRADE
+            assert cmHandleCompositeState.lockReason.details == "Upgrade to ModuleSetTag: ${updatedModuleSetTag}"
+
+        when: 'module sync runs'
+            moduleSyncWatchdog.resetPreviouslyFailedCmHandles()
+            moduleSyncWatchdog.moduleSyncAdvisedCmHandles()
+
+        then: 'CM-handle goes to READY state'
+            new PollingConditions().within(3, () -> {
+                assert CmHandleState.READY == objectUnderTest.getCmHandleCompositeState(CM_HANDLE_ID).cmHandleState
+            })
+
+        and: 'the CM-handle has expected moduleSetTag'
+            assert objectUnderTest.getNcmpServiceCmHandle(CM_HANDLE_ID).moduleSetTag == updatedModuleSetTag
+
+        and: 'CM-handle has expected updated modules: M1 and M3'
+            assert ['M1', 'M3'] == objectUnderTest.getYangResourcesModuleReferences(CM_HANDLE_ID).moduleName.sort()
+
+        and: 'DMI received expected requests'
+            mockDmiServer.verify()
+
+        cleanup: 'deregister CM-handle'
+            deregisterCmHandle(DMI_URL, CM_HANDLE_ID)
+
+        where:
+            initialModuleSetTag | updatedModuleSetTag
+            NO_MODULE_SET_TAG   | NO_MODULE_SET_TAG
+            NO_MODULE_SET_TAG   | 'new'
+            'initial'           | NO_MODULE_SET_TAG
+            'initial'           | 'new'
+    }
+
+    def 'Upgrade CM-handle with existing moduleSetTag.'() {
+        given: "an existing CM-handle handle with moduleSetTag '${updatedModuleSetTag}'"
+            registerCmHandle(DMI_URL, CM_HANDLE_ID_WITH_EXISTING_MODULE_SET_TAG, updatedModuleSetTag, UPDATED_MODULE_REFERENCES_RESPONSE, UPDATED_MODULE_RESOURCES_RESPONSE)
+            assert ['M1', 'M3'] == objectUnderTest.getYangResourcesModuleReferences(CM_HANDLE_ID_WITH_EXISTING_MODULE_SET_TAG).moduleName.sort()
+        and: "a CM-handle with moduleSetTag '${initialModuleSetTag}' which will be upgraded"
+            registerCmHandle(DMI_URL, CM_HANDLE_ID, initialModuleSetTag, INITIAL_MODULE_REFERENCES_RESPONSE, INITIAL_MODULE_RESOURCES_RESPONSE)
+            assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences(CM_HANDLE_ID).moduleName.sort()
+
+        when: "CM-handle is upgraded to moduleSetTag '${updatedModuleSetTag}'"
+            def cmHandlesToUpgrade = new UpgradedCmHandles(cmHandles: [CM_HANDLE_ID], moduleSetTag: updatedModuleSetTag)
+            def dmiPluginRegistrationResponse = networkCmProxyDataService.updateDmiRegistrationAndSyncModule(
+                    new DmiPluginRegistration(dmiPlugin: DMI_URL, upgradedCmHandles: cmHandlesToUpgrade))
+
+        then: 'registration gives successful response'
+            assert dmiPluginRegistrationResponse.upgradedCmHandles == [CmHandleRegistrationResponse.createSuccessResponse(CM_HANDLE_ID)]
+
+        when: 'module sync runs'
+            moduleSyncWatchdog.resetPreviouslyFailedCmHandles()
+            moduleSyncWatchdog.moduleSyncAdvisedCmHandles()
+
+        then: 'CM-handle goes to READY state'
+            new PollingConditions().within(3, () -> {
+                assert CmHandleState.READY == objectUnderTest.getCmHandleCompositeState(CM_HANDLE_ID).cmHandleState
+            })
+
+        and: 'the CM-handle has expected moduleSetTag'
+            assert objectUnderTest.getNcmpServiceCmHandle(CM_HANDLE_ID).moduleSetTag == updatedModuleSetTag
+
+        and: 'CM-handle has expected updated modules: M1 and M3'
+            assert ['M1', 'M3'] == objectUnderTest.getYangResourcesModuleReferences(CM_HANDLE_ID).moduleName.sort()
+
+        cleanup: 'deregister CM-handle'
+            deregisterCmHandles(DMI_URL, [CM_HANDLE_ID, CM_HANDLE_ID_WITH_EXISTING_MODULE_SET_TAG])
+
+        where:
+            initialModuleSetTag | updatedModuleSetTag
+            NO_MODULE_SET_TAG   | 'moduleSet2'
+            'moduleSet1'        | 'moduleSet2'
+    }
+
+    def 'Skip upgrade of CM-handle with same moduleSetTag as before.'() {
+        given: 'an existing CM-handle with expected initial modules: M1 and M2'
+            registerCmHandle(DMI_URL, CM_HANDLE_ID, 'same', INITIAL_MODULE_REFERENCES_RESPONSE, INITIAL_MODULE_RESOURCES_RESPONSE)
+            assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences(CM_HANDLE_ID).moduleName.sort()
+
+        when: 'CM-handle is upgraded with the same moduleSetTag'
+            def cmHandlesToUpgrade = new UpgradedCmHandles(cmHandles: [CM_HANDLE_ID], moduleSetTag: 'same')
+            networkCmProxyDataService.updateDmiRegistrationAndSyncModule(
+                    new DmiPluginRegistration(dmiPlugin: DMI_URL, upgradedCmHandles: cmHandlesToUpgrade))
+
+        then: 'CM-handle remains in READY state'
+            assert CmHandleState.READY == objectUnderTest.getCmHandleCompositeState(CM_HANDLE_ID).cmHandleState
+
+        and: 'the CM-handle has same moduleSetTag as before'
+            assert objectUnderTest.getNcmpServiceCmHandle(CM_HANDLE_ID).moduleSetTag == 'same'
+
+        then: 'CM-handle has same modules as before: M1 and M2'
+            assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences(CM_HANDLE_ID).moduleName.sort()
+
+        cleanup: 'deregister CM-handle'
+            deregisterCmHandle(DMI_URL, CM_HANDLE_ID)
+    }
+
+    def 'Upgrade of CM-handle fails due to DMI error.'() {
+        given: 'an existing CM-handle'
+            registerCmHandle(DMI_URL, CM_HANDLE_ID, 'oldTag', INITIAL_MODULE_REFERENCES_RESPONSE, INITIAL_MODULE_RESOURCES_RESPONSE)
+
+        and: 'DMI returns error code'
+            mockDmiServer.expect(anything()).andRespond(withStatus(HttpStatus.SERVICE_UNAVAILABLE))
+
+        when: "CM-handle is upgraded"
+            def cmHandlesToUpgrade = new UpgradedCmHandles(cmHandles: [CM_HANDLE_ID], moduleSetTag: 'newTag')
+            networkCmProxyDataService.updateDmiRegistrationAndSyncModule(
+                    new DmiPluginRegistration(dmiPlugin: DMI_URL, upgradedCmHandles: cmHandlesToUpgrade))
+
+        and: 'module sync runs'
+            moduleSyncWatchdog.resetPreviouslyFailedCmHandles()
+            moduleSyncWatchdog.moduleSyncAdvisedCmHandles()
+
+        then: 'CM-handle goes to LOCKED state with reason MODULE_UPGRADE_FAILED'
+            new PollingConditions().within(3, () -> {
+                def cmHandleCompositeState = objectUnderTest.getCmHandleCompositeState(CM_HANDLE_ID)
+                assert cmHandleCompositeState.cmHandleState == CmHandleState.LOCKED
+                assert cmHandleCompositeState.lockReason.lockReasonCategory == LockReasonCategory.MODULE_UPGRADE_FAILED
+            })
+
+        and: 'the CM-handle has same moduleSetTag as before'
+            assert objectUnderTest.getNcmpServiceCmHandle(CM_HANDLE_ID).moduleSetTag == 'oldTag'
+
+        cleanup: 'deregister CM-handle'
+            deregisterCmHandle(DMI_URL, CM_HANDLE_ID)
+    }
+
+}
diff --git a/integration-test/src/test/resources/data/mock-dmi-responses/bookStoreAWithModules_M1_M2_ResourcesResponse.json b/integration-test/src/test/resources/data/mock-dmi-responses/bookStoreAWithModules_M1_M2_ResourcesResponse.json
new file mode 100644 (file)
index 0000000..5d71391
--- /dev/null
@@ -0,0 +1,12 @@
+[
+       {
+               "moduleName": "M1",
+               "revision": "2024-01-01",
+               "yangSource": "module M1 {\n\n  namespace \"urn:ietf:params:xml:ns:yang:M1\";\n  prefix \"yang\";\n\n  organization\n   \"IETF NETMOD (NETCONF Data Modeling Language) Working Group\";\n\n  contact\n   \"WG Web:   <http://tools.ietf.org/wg/netmod/>\n    WG List:  <mailto:netmod@ietf.org>\n\n    WG Chair: David Kessens\n              <mailto:david.kessens@nsn.com>\n\n    WG Chair: Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\n\n    Editor:   Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\";\n\n  description\n   \"This module contains a collection of generally useful derived\n    YANG data types.\n\n    Copyright (c) 2013 IETF Trust and the persons identified as\n    authors of the code.  All rights reserved.\n\n    Redistribution and use in source and binary forms, with or\n    without modification, is permitted pursuant to, and subject\n    to the license terms contained in, the Simplified BSD License\n    set forth in Section 4.c of the IETF Trust's Legal Provisions\n    Relating to IETF Documents\n    (http://trustee.ietf.org/license-info).\n\n    This version of this YANG module is part of RFC 6991; see\n    the RFC itself for full legal notices.\";\n\n  revision 2024-01-01 {\n    description\n     \"This revision adds the following new data types:\n      - yang-identifier\n      - hex-string\n      - uuid\n      - dotted-quad\";\n    reference\n     \"RFC 6991: Common YANG Data Types\";\n  }\n\n  revision 2010-09-24 {\n    description\n     \"Initial revision.\";\n    reference\n     \"RFC 6021: Common YANG Data Types\";\n  }\n\n  /*** collection of counter and gauge types ***/\n\n  typedef counter32 {\n    type uint32;\n    description\n     \"The counter32 type represents a non-negative integer\n      that monotonically increases until it reaches a\n      maximum value of 2^32-1 (4294967295 decimal), when it\n      wraps around and starts increasing again from zero.\n\n      Counters have no defined 'initial' value, and thus, a\n      single value of a counter has (in general) no information\n      content.  Discontinuities in the monotonically increasing\n      value normally occur at re-initialization of the\n      management system, and at other times as specified in the\n      description of a schema node using this type.  If such\n      other times can occur, for example, the creation of\n      a schema node of type counter32 at times other than\n      re-initialization, then a corresponding schema node\n      should be defined, with an appropriate type, to indicate\n      the last discontinuity.\n\n      The counter32 type should not be used for configuration\n      schema nodes.  A default statement SHOULD NOT be used in\n      combination with the type counter32.\n\n      In the value set and its semantics, this type is equivalent\n      to the Counter32 type of the SMIv2.\";\n    reference\n     \"RFC 2578: Structure of Management Information Version 2\n                (SMIv2)\";\n  }\n}\n"
+       },
+       {
+               "moduleName": "M2",
+               "revision": "2024-01-02",
+               "yangSource": "module M2 {\n\n  namespace \"urn:ietf:params:xml:ns:yang:M2\";\n  prefix \"yang\";\n\n  organization\n   \"IETF NETMOD (NETCONF Data Modeling Language) Working Group\";\n\n  contact\n   \"WG Web:   <http://tools.ietf.org/wg/netmod/>\n    WG List:  <mailto:netmod@ietf.org>\n\n    WG Chair: David Kessens\n              <mailto:david.kessens@nsn.com>\n\n    WG Chair: Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\n\n    Editor:   Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\";\n\n  description\n   \"This module contains a collection of generally useful derived\n    YANG data types.\n\n    Copyright (c) 2013 IETF Trust and the persons identified as\n    authors of the code.  All rights reserved.\n\n    Redistribution and use in source and binary forms, with or\n    without modification, is permitted pursuant to, and subject\n    to the license terms contained in, the Simplified BSD License\n    set forth in Section 4.c of the IETF Trust's Legal Provisions\n    Relating to IETF Documents\n    (http://trustee.ietf.org/license-info).\n\n    This version of this YANG module is part of RFC 6991; see\n    the RFC itself for full legal notices.\";\n\n  revision 2024-01-02 {\n    description\n     \"This revision adds the following new data types:\n      - yang-identifier\n      - hex-string\n      - uuid\n      - dotted-quad\";\n    reference\n     \"RFC 6991: Common YANG Data Types\";\n  }\n\n  revision 2010-09-24 {\n    description\n     \"Initial revision.\";\n    reference\n     \"RFC 6021: Common YANG Data Types\";\n  }\n\n  /*** collection of counter and gauge types ***/\n\n  typedef counter32 {\n    type uint32;\n    description\n     \"The counter32 type represents a non-negative integer\n      that monotonically increases until it reaches a\n      maximum value of 2^32-1 (4294967295 decimal), when it\n      wraps around and starts increasing again from zero.\n\n      Counters have no defined 'initial' value, and thus, a\n      single value of a counter has (in general) no information\n      content.  Discontinuities in the monotonically increasing\n      value normally occur at re-initialization of the\n      management system, and at other times as specified in the\n      description of a schema node using this type.  If such\n      other times can occur, for example, the creation of\n      a schema node of type counter32 at times other than\n      re-initialization, then a corresponding schema node\n      should be defined, with an appropriate type, to indicate\n      the last discontinuity.\n\n      The counter32 type should not be used for configuration\n      schema nodes.  A default statement SHOULD NOT be used in\n      combination with the type counter32.\n\n      In the value set and its semantics, this type is equivalent\n      to the Counter32 type of the SMIv2.\";\n    reference\n     \"RFC 2578: Structure of Management Information Version 2\n                (SMIv2)\";\n  }\n}\n"
+       }
+]
\ No newline at end of file
diff --git a/integration-test/src/test/resources/data/mock-dmi-responses/bookStoreAWithModules_M1_M2_Response.json b/integration-test/src/test/resources/data/mock-dmi-responses/bookStoreAWithModules_M1_M2_Response.json
new file mode 100644 (file)
index 0000000..9f20564
--- /dev/null
@@ -0,0 +1,12 @@
+{
+       "schemas": [
+               {
+                       "moduleName": "M1",
+                       "revision": "2024-01-01"
+               },
+               {
+                       "moduleName": "M2",
+                       "revision": "2024-01-02"
+               }
+       ]
+}
\ No newline at end of file
diff --git a/integration-test/src/test/resources/data/mock-dmi-responses/bookStoreBWithModules_M1_M3_ResourcesResponse.json b/integration-test/src/test/resources/data/mock-dmi-responses/bookStoreBWithModules_M1_M3_ResourcesResponse.json
new file mode 100644 (file)
index 0000000..ef9b85f
--- /dev/null
@@ -0,0 +1,12 @@
+[
+       {
+               "moduleName": "M1",
+               "revision": "2024-01-01",
+               "yangSource": "module M1 {\n\n  namespace \"urn:ietf:params:xml:ns:yang:M1\";\n  prefix \"yang\";\n\n  organization\n   \"IETF NETMOD (NETCONF Data Modeling Language) Working Group\";\n\n  contact\n   \"WG Web:   <http://tools.ietf.org/wg/netmod/>\n    WG List:  <mailto:netmod@ietf.org>\n\n    WG Chair: David Kessens\n              <mailto:david.kessens@nsn.com>\n\n    WG Chair: Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\n\n    Editor:   Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\";\n\n  description\n   \"This module contains a collection of generally useful derived\n    YANG data types.\n\n    Copyright (c) 2013 IETF Trust and the persons identified as\n    authors of the code.  All rights reserved.\n\n    Redistribution and use in source and binary forms, with or\n    without modification, is permitted pursuant to, and subject\n    to the license terms contained in, the Simplified BSD License\n    set forth in Section 4.c of the IETF Trust's Legal Provisions\n    Relating to IETF Documents\n    (http://trustee.ietf.org/license-info).\n\n    This version of this YANG module is part of RFC 6991; see\n    the RFC itself for full legal notices.\";\n\n  revision 2024-01-01 {\n    description\n     \"This revision adds the following new data types:\n      - yang-identifier\n      - hex-string\n      - uuid\n      - dotted-quad\";\n    reference\n     \"RFC 6991: Common YANG Data Types\";\n  }\n\n  revision 2010-09-24 {\n    description\n     \"Initial revision.\";\n    reference\n     \"RFC 6021: Common YANG Data Types\";\n  }\n\n  /*** collection of counter and gauge types ***/\n\n  typedef counter32 {\n    type uint32;\n    description\n     \"The counter32 type represents a non-negative integer\n      that monotonically increases until it reaches a\n      maximum value of 2^32-1 (4294967295 decimal), when it\n      wraps around and starts increasing again from zero.\n\n      Counters have no defined 'initial' value, and thus, a\n      single value of a counter has (in general) no information\n      content.  Discontinuities in the monotonically increasing\n      value normally occur at re-initialization of the\n      management system, and at other times as specified in the\n      description of a schema node using this type.  If such\n      other times can occur, for example, the creation of\n      a schema node of type counter32 at times other than\n      re-initialization, then a corresponding schema node\n      should be defined, with an appropriate type, to indicate\n      the last discontinuity.\n\n      The counter32 type should not be used for configuration\n      schema nodes.  A default statement SHOULD NOT be used in\n      combination with the type counter32.\n\n      In the value set and its semantics, this type is equivalent\n      to the Counter32 type of the SMIv2.\";\n    reference\n     \"RFC 2578: Structure of Management Information Version 2\n                (SMIv2)\";\n  }\n}\n"
+       },
+       {
+               "moduleName": "M3",
+               "revision": "2024-01-03",
+               "yangSource": "module M3 {\n\n  namespace \"urn:ietf:params:xml:ns:yang:M3\";\n  prefix \"yang\";\n\n  organization\n   \"IETF NETMOD (NETCONF Data Modeling Language) Working Group\";\n\n  contact\n   \"WG Web:   <http://tools.ietf.org/wg/netmod/>\n    WG List:  <mailto:netmod@ietf.org>\n\n    WG Chair: David Kessens\n              <mailto:david.kessens@nsn.com>\n\n    WG Chair: Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\n\n    Editor:   Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\";\n\n  description\n   \"This module contains a collection of generally useful derived\n    YANG data types.\n\n    Copyright (c) 2013 IETF Trust and the persons identified as\n    authors of the code.  All rights reserved.\n\n    Redistribution and use in source and binary forms, with or\n    without modification, is permitted pursuant to, and subject\n    to the license terms contained in, the Simplified BSD License\n    set forth in Section 4.c of the IETF Trust's Legal Provisions\n    Relating to IETF Documents\n    (http://trustee.ietf.org/license-info).\n\n    This version of this YANG module is part of RFC 6991; see\n    the RFC itself for full legal notices.\";\n\n  revision 2024-01-03 {\n    description\n     \"This revision adds the following new data types:\n      - yang-identifier\n      - hex-string\n      - uuid\n      - dotted-quad\";\n    reference\n     \"RFC 6991: Common YANG Data Types\";\n  }\n\n  revision 2010-09-24 {\n    description\n     \"Initial revision.\";\n    reference\n     \"RFC 6021: Common YANG Data Types\";\n  }\n\n  /*** collection of counter and gauge types ***/\n\n  typedef counter32 {\n    type uint32;\n    description\n     \"The counter32 type represents a non-negative integer\n      that monotonically increases until it reaches a\n      maximum value of 2^32-1 (4294967295 decimal), when it\n      wraps around and starts increasing again from zero.\n\n      Counters have no defined 'initial' value, and thus, a\n      single value of a counter has (in general) no information\n      content.  Discontinuities in the monotonically increasing\n      value normally occur at re-initialization of the\n      management system, and at other times as specified in the\n      description of a schema node using this type.  If such\n      other times can occur, for example, the creation of\n      a schema node of type counter32 at times other than\n      re-initialization, then a corresponding schema node\n      should be defined, with an appropriate type, to indicate\n      the last discontinuity.\n\n      The counter32 type should not be used for configuration\n      schema nodes.  A default statement SHOULD NOT be used in\n      combination with the type counter32.\n\n      In the value set and its semantics, this type is equivalent\n      to the Counter32 type of the SMIv2.\";\n    reference\n     \"RFC 2578: Structure of Management Information Version 2\n                (SMIv2)\";\n  }\n}\n"
+       }
+]
\ No newline at end of file
diff --git a/integration-test/src/test/resources/data/mock-dmi-responses/bookStoreBWithModules_M1_M3_Response.json b/integration-test/src/test/resources/data/mock-dmi-responses/bookStoreBWithModules_M1_M3_Response.json
new file mode 100644 (file)
index 0000000..513c749
--- /dev/null
@@ -0,0 +1,12 @@
+{
+       "schemas": [
+               {
+                       "moduleName": "M1",
+                       "revision": "2024-01-01"
+               },
+               {
+                       "moduleName": "M3",
+                       "revision": "2024-01-03"
+               }
+       ]
+}
\ No newline at end of file
diff --git a/integration-test/src/test/resources/data/mock-dmi-responses/ietfYangModuleResourcesResponse.json b/integration-test/src/test/resources/data/mock-dmi-responses/ietfYangModuleResourcesResponse.json
deleted file mode 100644 (file)
index 4326733..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-[
-       {
-               "moduleName": "ietf-yang-types-1",
-               "revision": "2013-07-15",
-               "yangSource": "module ietf-yang-types-1 {\n\n  namespace \"urn:ietf:params:xml:ns:yang:ietf-yang-types-1\";\n  prefix \"yang\";\n\n  organization\n   \"IETF NETMOD (NETCONF Data Modeling Language) Working Group\";\n\n  contact\n   \"WG Web:   <http://tools.ietf.org/wg/netmod/>\n    WG List:  <mailto:netmod@ietf.org>\n\n    WG Chair: David Kessens\n              <mailto:david.kessens@nsn.com>\n\n    WG Chair: Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\n\n    Editor:   Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\";\n\n  description\n   \"This module contains a collection of generally useful derived\n    YANG data types.\n\n    Copyright (c) 2013 IETF Trust and the persons identified as\n    authors of the code.  All rights reserved.\n\n    Redistribution and use in source and binary forms, with or\n    without modification, is permitted pursuant to, and subject\n    to the license terms contained in, the Simplified BSD License\n    set forth in Section 4.c of the IETF Trust's Legal Provisions\n    Relating to IETF Documents\n    (http://trustee.ietf.org/license-info).\n\n    This version of this YANG module is part of RFC 6991; see\n    the RFC itself for full legal notices.\";\n\n  revision 2013-07-15 {\n    description\n     \"This revision adds the following new data types:\n      - yang-identifier\n      - hex-string\n      - uuid\n      - dotted-quad\";\n    reference\n     \"RFC 6991: Common YANG Data Types\";\n  }\n\n  revision 2010-09-24 {\n    description\n     \"Initial revision.\";\n    reference\n     \"RFC 6021: Common YANG Data Types\";\n  }\n\n  /*** collection of counter and gauge types ***/\n\n  typedef counter32 {\n    type uint32;\n    description\n     \"The counter32 type represents a non-negative integer\n      that monotonically increases until it reaches a\n      maximum value of 2^32-1 (4294967295 decimal), when it\n      wraps around and starts increasing again from zero.\n\n      Counters have no defined 'initial' value, and thus, a\n      single value of a counter has (in general) no information\n      content.  Discontinuities in the monotonically increasing\n      value normally occur at re-initialization of the\n      management system, and at other times as specified in the\n      description of a schema node using this type.  If such\n      other times can occur, for example, the creation of\n      a schema node of type counter32 at times other than\n      re-initialization, then a corresponding schema node\n      should be defined, with an appropriate type, to indicate\n      the last discontinuity.\n\n      The counter32 type should not be used for configuration\n      schema nodes.  A default statement SHOULD NOT be used in\n      combination with the type counter32.\n\n      In the value set and its semantics, this type is equivalent\n      to the Counter32 type of the SMIv2.\";\n    reference\n     \"RFC 2578: Structure of Management Information Version 2\n                (SMIv2)\";\n  }\n}\n"
-       },
-       {
-               "moduleName": "ietf-yang-types-2",
-               "revision": "2013-07-16",
-               "yangSource": "module ietf-yang-types-2 {\n\n  namespace \"urn:ietf:params:xml:ns:yang:ietf-yang-types-2\";\n  prefix \"yang\";\n\n  organization\n   \"IETF NETMOD (NETCONF Data Modeling Language) Working Group\";\n\n  contact\n   \"WG Web:   <http://tools.ietf.org/wg/netmod/>\n    WG List:  <mailto:netmod@ietf.org>\n\n    WG Chair: David Kessens\n              <mailto:david.kessens@nsn.com>\n\n    WG Chair: Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\n\n    Editor:   Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\";\n\n  description\n   \"This module contains a collection of generally useful derived\n    YANG data types.\n\n    Copyright (c) 2013 IETF Trust and the persons identified as\n    authors of the code.  All rights reserved.\n\n    Redistribution and use in source and binary forms, with or\n    without modification, is permitted pursuant to, and subject\n    to the license terms contained in, the Simplified BSD License\n    set forth in Section 4.c of the IETF Trust's Legal Provisions\n    Relating to IETF Documents\n    (http://trustee.ietf.org/license-info).\n\n    This version of this YANG module is part of RFC 6991; see\n    the RFC itself for full legal notices.\";\n\n  revision 2013-07-16 {\n    description\n     \"This revision adds the following new data types:\n      - yang-identifier\n      - hex-string\n      - uuid\n      - dotted-quad\";\n    reference\n     \"RFC 6991: Common YANG Data Types\";\n  }\n\n  revision 2010-09-24 {\n    description\n     \"Initial revision.\";\n    reference\n     \"RFC 6021: Common YANG Data Types\";\n  }\n\n  /*** collection of counter and gauge types ***/\n\n  typedef counter32 {\n    type uint32;\n    description\n     \"The counter32 type represents a non-negative integer\n      that monotonically increases until it reaches a\n      maximum value of 2^32-1 (4294967295 decimal), when it\n      wraps around and starts increasing again from zero.\n\n      Counters have no defined 'initial' value, and thus, a\n      single value of a counter has (in general) no information\n      content.  Discontinuities in the monotonically increasing\n      value normally occur at re-initialization of the\n      management system, and at other times as specified in the\n      description of a schema node using this type.  If such\n      other times can occur, for example, the creation of\n      a schema node of type counter32 at times other than\n      re-initialization, then a corresponding schema node\n      should be defined, with an appropriate type, to indicate\n      the last discontinuity.\n\n      The counter32 type should not be used for configuration\n      schema nodes.  A default statement SHOULD NOT be used in\n      combination with the type counter32.\n\n      In the value set and its semantics, this type is equivalent\n      to the Counter32 type of the SMIv2.\";\n    reference\n     \"RFC 2578: Structure of Management Information Version 2\n                (SMIv2)\";\n  }\n}\n"
-       },
-       {
-               "moduleName": "ietf-yang-types-3",
-               "revision": "2013-07-17",
-               "yangSource": "module ietf-yang-types-3 {\n\n  namespace \"urn:ietf:params:xml:ns:yang:ietf-yang-types-3\";\n  prefix \"yang\";\n\n  organization\n   \"IETF NETMOD (NETCONF Data Modeling Language) Working Group\";\n\n  contact\n   \"WG Web:   <http://tools.ietf.org/wg/netmod/>\n    WG List:  <mailto:netmod@ietf.org>\n\n    WG Chair: David Kessens\n              <mailto:david.kessens@nsn.com>\n\n    WG Chair: Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\n\n    Editor:   Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\";\n\n  description\n   \"This module contains a collection of generally useful derived\n    YANG data types.\n\n    Copyright (c) 2013 IETF Trust and the persons identified as\n    authors of the code.  All rights reserved.\n\n    Redistribution and use in source and binary forms, with or\n    without modification, is permitted pursuant to, and subject\n    to the license terms contained in, the Simplified BSD License\n    set forth in Section 4.c of the IETF Trust's Legal Provisions\n    Relating to IETF Documents\n    (http://trustee.ietf.org/license-info).\n\n    This version of this YANG module is part of RFC 6991; see\n    the RFC itself for full legal notices.\";\n\n  revision 2013-07-17 {\n    description\n     \"This revision adds the following new data types:\n      - yang-identifier\n      - hex-string\n      - uuid\n      - dotted-quad\";\n    reference\n     \"RFC 6991: Common YANG Data Types\";\n  }\n\n  revision 2010-09-24 {\n    description\n     \"Initial revision.\";\n    reference\n     \"RFC 6021: Common YANG Data Types\";\n  }\n\n  /*** collection of counter and gauge types ***/\n\n  typedef counter32 {\n    type uint32;\n    description\n     \"The counter32 type represents a non-negative integer\n      that monotonically increases until it reaches a\n      maximum value of 2^32-1 (4294967295 decimal), when it\n      wraps around and starts increasing again from zero.\n\n      Counters have no defined 'initial' value, and thus, a\n      single value of a counter has (in general) no information\n      content.  Discontinuities in the monotonically increasing\n      value normally occur at re-initialization of the\n      management system, and at other times as specified in the\n      description of a schema node using this type.  If such\n      other times can occur, for example, the creation of\n      a schema node of type counter32 at times other than\n      re-initialization, then a corresponding schema node\n      should be defined, with an appropriate type, to indicate\n      the last discontinuity.\n\n      The counter32 type should not be used for configuration\n      schema nodes.  A default statement SHOULD NOT be used in\n      combination with the type counter32.\n\n      In the value set and its semantics, this type is equivalent\n      to the Counter32 type of the SMIv2.\";\n    reference\n     \"RFC 2578: Structure of Management Information Version 2\n                (SMIv2)\";\n  }\n}\n"
-       },
-       {
-               "moduleName": "ietf-yang-types-4",
-               "revision": "2013-07-18",
-               "yangSource": "module ietf-yang-types-4 {\n\n  namespace \"urn:ietf:params:xml:ns:yang:ietf-yang-types-4\";\n  prefix \"yang\";\n\n  organization\n   \"IETF NETMOD (NETCONF Data Modeling Language) Working Group\";\n\n  contact\n   \"WG Web:   <http://tools.ietf.org/wg/netmod/>\n    WG List:  <mailto:netmod@ietf.org>\n\n    WG Chair: David Kessens\n              <mailto:david.kessens@nsn.com>\n\n    WG Chair: Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\n\n    Editor:   Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\";\n\n  description\n   \"This module contains a collection of generally useful derived\n    YANG data types.\n\n    Copyright (c) 2013 IETF Trust and the persons identified as\n    authors of the code.  All rights reserved.\n\n    Redistribution and use in source and binary forms, with or\n    without modification, is permitted pursuant to, and subject\n    to the license terms contained in, the Simplified BSD License\n    set forth in Section 4.c of the IETF Trust's Legal Provisions\n    Relating to IETF Documents\n    (http://trustee.ietf.org/license-info).\n\n    This version of this YANG module is part of RFC 6991; see\n    the RFC itself for full legal notices.\";\n\n  revision 2013-07-18 {\n    description\n     \"This revision adds the following new data types:\n      - yang-identifier\n      - hex-string\n      - uuid\n      - dotted-quad\";\n    reference\n     \"RFC 6991: Common YANG Data Types\";\n  }\n\n  revision 2010-09-24 {\n    description\n     \"Initial revision.\";\n    reference\n     \"RFC 6021: Common YANG Data Types\";\n  }\n\n  /*** collection of counter and gauge types ***/\n\n  typedef counter32 {\n    type uint32;\n    description\n     \"The counter32 type represents a non-negative integer\n      that monotonically increases until it reaches a\n      maximum value of 2^32-1 (4294967295 decimal), when it\n      wraps around and starts increasing again from zero.\n\n      Counters have no defined 'initial' value, and thus, a\n      single value of a counter has (in general) no information\n      content.  Discontinuities in the monotonically increasing\n      value normally occur at re-initialization of the\n      management system, and at other times as specified in the\n      description of a schema node using this type.  If such\n      other times can occur, for example, the creation of\n      a schema node of type counter32 at times other than\n      re-initialization, then a corresponding schema node\n      should be defined, with an appropriate type, to indicate\n      the last discontinuity.\n\n      The counter32 type should not be used for configuration\n      schema nodes.  A default statement SHOULD NOT be used in\n      combination with the type counter32.\n\n      In the value set and its semantics, this type is equivalent\n      to the Counter32 type of the SMIv2.\";\n    reference\n     \"RFC 2578: Structure of Management Information Version 2\n                (SMIv2)\";\n  }\n}\n"
-       },
-       {
-               "moduleName": "ietf-yang-types-5",
-               "revision": "2013-07-19",
-               "yangSource": "module ietf-yang-types-5 {\n\n  namespace \"urn:ietf:params:xml:ns:yang:ietf-yang-types-5\";\n  prefix \"yang\";\n\n  organization\n   \"IETF NETMOD (NETCONF Data Modeling Language) Working Group\";\n\n  contact\n   \"WG Web:   <http://tools.ietf.org/wg/netmod/>\n    WG List:  <mailto:netmod@ietf.org>\n\n    WG Chair: David Kessens\n              <mailto:david.kessens@nsn.com>\n\n    WG Chair: Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\n\n    Editor:   Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\";\n\n  description\n   \"This module contains a collection of generally useful derived\n    YANG data types.\n\n    Copyright (c) 2013 IETF Trust and the persons identified as\n    authors of the code.  All rights reserved.\n\n    Redistribution and use in source and binary forms, with or\n    without modification, is permitted pursuant to, and subject\n    to the license terms contained in, the Simplified BSD License\n    set forth in Section 4.c of the IETF Trust's Legal Provisions\n    Relating to IETF Documents\n    (http://trustee.ietf.org/license-info).\n\n    This version of this YANG module is part of RFC 6991; see\n    the RFC itself for full legal notices.\";\n\n  revision 2013-07-19 {\n    description\n     \"This revision adds the following new data types:\n      - yang-identifier\n      - hex-string\n      - uuid\n      - dotted-quad\";\n    reference\n     \"RFC 6991: Common YANG Data Types\";\n  }\n\n  revision 2010-09-24 {\n    description\n     \"Initial revision.\";\n    reference\n     \"RFC 6021: Common YANG Data Types\";\n  }\n\n  /*** collection of counter and gauge types ***/\n\n  typedef counter32 {\n    type uint32;\n    description\n     \"The counter32 type represents a non-negative integer\n      that monotonically increases until it reaches a\n      maximum value of 2^32-1 (4294967295 decimal), when it\n      wraps around and starts increasing again from zero.\n\n      Counters have no defined 'initial' value, and thus, a\n      single value of a counter has (in general) no information\n      content.  Discontinuities in the monotonically increasing\n      value normally occur at re-initialization of the\n      management system, and at other times as specified in the\n      description of a schema node using this type.  If such\n      other times can occur, for example, the creation of\n      a schema node of type counter32 at times other than\n      re-initialization, then a corresponding schema node\n      should be defined, with an appropriate type, to indicate\n      the last discontinuity.\n\n      The counter32 type should not be used for configuration\n      schema nodes.  A default statement SHOULD NOT be used in\n      combination with the type counter32.\n\n      In the value set and its semantics, this type is equivalent\n      to the Counter32 type of the SMIv2.\";\n    reference\n     \"RFC 2578: Structure of Management Information Version 2\n                (SMIv2)\";\n  }\n}\n"
-       },
-       {
-               "moduleName": "ietf-yang-types-6",
-               "revision": "2013-07-20",
-               "yangSource": "module ietf-yang-types-6 {\n\n  namespace \"urn:ietf:params:xml:ns:yang:ietf-yang-types-6\";\n  prefix \"yang\";\n\n  organization\n   \"IETF NETMOD (NETCONF Data Modeling Language) Working Group\";\n\n  contact\n   \"WG Web:   <http://tools.ietf.org/wg/netmod/>\n    WG List:  <mailto:netmod@ietf.org>\n\n    WG Chair: David Kessens\n              <mailto:david.kessens@nsn.com>\n\n    WG Chair: Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\n\n    Editor:   Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\";\n\n  description\n   \"This module contains a collection of generally useful derived\n    YANG data types.\n\n    Copyright (c) 2013 IETF Trust and the persons identified as\n    authors of the code.  All rights reserved.\n\n    Redistribution and use in source and binary forms, with or\n    without modification, is permitted pursuant to, and subject\n    to the license terms contained in, the Simplified BSD License\n    set forth in Section 4.c of the IETF Trust's Legal Provisions\n    Relating to IETF Documents\n    (http://trustee.ietf.org/license-info).\n\n    This version of this YANG module is part of RFC 6991; see\n    the RFC itself for full legal notices.\";\n\n  revision 2013-07-20 {\n    description\n     \"This revision adds the following new data types:\n      - yang-identifier\n      - hex-string\n      - uuid\n      - dotted-quad\";\n    reference\n     \"RFC 6991: Common YANG Data Types\";\n  }\n\n  revision 2010-09-24 {\n    description\n     \"Initial revision.\";\n    reference\n     \"RFC 6021: Common YANG Data Types\";\n  }\n\n  /*** collection of counter and gauge types ***/\n\n  typedef counter32 {\n    type uint32;\n    description\n     \"The counter32 type represents a non-negative integer\n      that monotonically increases until it reaches a\n      maximum value of 2^32-1 (4294967295 decimal), when it\n      wraps around and starts increasing again from zero.\n\n      Counters have no defined 'initial' value, and thus, a\n      single value of a counter has (in general) no information\n      content.  Discontinuities in the monotonically increasing\n      value normally occur at re-initialization of the\n      management system, and at other times as specified in the\n      description of a schema node using this type.  If such\n      other times can occur, for example, the creation of\n      a schema node of type counter32 at times other than\n      re-initialization, then a corresponding schema node\n      should be defined, with an appropriate type, to indicate\n      the last discontinuity.\n\n      The counter32 type should not be used for configuration\n      schema nodes.  A default statement SHOULD NOT be used in\n      combination with the type counter32.\n\n      In the value set and its semantics, this type is equivalent\n      to the Counter32 type of the SMIv2.\";\n    reference\n     \"RFC 2578: Structure of Management Information Version 2\n                (SMIv2)\";\n  }\n}\n"
-       },
-       {
-               "moduleName": "ietf-yang-types-7",
-               "revision": "2013-07-21",
-               "yangSource": "module ietf-yang-types-7 {\n\n  namespace \"urn:ietf:params:xml:ns:yang:ietf-yang-types-7\";\n  prefix \"yang\";\n\n  organization\n   \"IETF NETMOD (NETCONF Data Modeling Language) Working Group\";\n\n  contact\n   \"WG Web:   <http://tools.ietf.org/wg/netmod/>\n    WG List:  <mailto:netmod@ietf.org>\n\n    WG Chair: David Kessens\n              <mailto:david.kessens@nsn.com>\n\n    WG Chair: Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\n\n    Editor:   Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\";\n\n  description\n   \"This module contains a collection of generally useful derived\n    YANG data types.\n\n    Copyright (c) 2013 IETF Trust and the persons identified as\n    authors of the code.  All rights reserved.\n\n    Redistribution and use in source and binary forms, with or\n    without modification, is permitted pursuant to, and subject\n    to the license terms contained in, the Simplified BSD License\n    set forth in Section 4.c of the IETF Trust's Legal Provisions\n    Relating to IETF Documents\n    (http://trustee.ietf.org/license-info).\n\n    This version of this YANG module is part of RFC 6991; see\n    the RFC itself for full legal notices.\";\n\n  revision 2013-07-21 {\n    description\n     \"This revision adds the following new data types:\n      - yang-identifier\n      - hex-string\n      - uuid\n      - dotted-quad\";\n    reference\n     \"RFC 6991: Common YANG Data Types\";\n  }\n\n  revision 2010-09-24 {\n    description\n     \"Initial revision.\";\n    reference\n     \"RFC 6021: Common YANG Data Types\";\n  }\n\n  /*** collection of counter and gauge types ***/\n\n  typedef counter32 {\n    type uint32;\n    description\n     \"The counter32 type represents a non-negative integer\n      that monotonically increases until it reaches a\n      maximum value of 2^32-1 (4294967295 decimal), when it\n      wraps around and starts increasing again from zero.\n\n      Counters have no defined 'initial' value, and thus, a\n      single value of a counter has (in general) no information\n      content.  Discontinuities in the monotonically increasing\n      value normally occur at re-initialization of the\n      management system, and at other times as specified in the\n      description of a schema node using this type.  If such\n      other times can occur, for example, the creation of\n      a schema node of type counter32 at times other than\n      re-initialization, then a corresponding schema node\n      should be defined, with an appropriate type, to indicate\n      the last discontinuity.\n\n      The counter32 type should not be used for configuration\n      schema nodes.  A default statement SHOULD NOT be used in\n      combination with the type counter32.\n\n      In the value set and its semantics, this type is equivalent\n      to the Counter32 type of the SMIv2.\";\n    reference\n     \"RFC 2578: Structure of Management Information Version 2\n                (SMIv2)\";\n  }\n}\n"
-       },
-       {
-               "moduleName": "ietf-yang-types-8",
-               "revision": "2013-07-22",
-               "yangSource": "module ietf-yang-types-8 {\n\n  namespace \"urn:ietf:params:xml:ns:yang:ietf-yang-types-8\";\n  prefix \"yang\";\n\n  organization\n   \"IETF NETMOD (NETCONF Data Modeling Language) Working Group\";\n\n  contact\n   \"WG Web:   <http://tools.ietf.org/wg/netmod/>\n    WG List:  <mailto:netmod@ietf.org>\n\n    WG Chair: David Kessens\n              <mailto:david.kessens@nsn.com>\n\n    WG Chair: Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\n\n    Editor:   Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\";\n\n  description\n   \"This module contains a collection of generally useful derived\n    YANG data types.\n\n    Copyright (c) 2013 IETF Trust and the persons identified as\n    authors of the code.  All rights reserved.\n\n    Redistribution and use in source and binary forms, with or\n    without modification, is permitted pursuant to, and subject\n    to the license terms contained in, the Simplified BSD License\n    set forth in Section 4.c of the IETF Trust's Legal Provisions\n    Relating to IETF Documents\n    (http://trustee.ietf.org/license-info).\n\n    This version of this YANG module is part of RFC 6991; see\n    the RFC itself for full legal notices.\";\n\n  revision 2013-07-22 {\n    description\n     \"This revision adds the following new data types:\n      - yang-identifier\n      - hex-string\n      - uuid\n      - dotted-quad\";\n    reference\n     \"RFC 6991: Common YANG Data Types\";\n  }\n\n  revision 2010-09-24 {\n    description\n     \"Initial revision.\";\n    reference\n     \"RFC 6021: Common YANG Data Types\";\n  }\n\n  /*** collection of counter and gauge types ***/\n\n  typedef counter32 {\n    type uint32;\n    description\n     \"The counter32 type represents a non-negative integer\n      that monotonically increases until it reaches a\n      maximum value of 2^32-1 (4294967295 decimal), when it\n      wraps around and starts increasing again from zero.\n\n      Counters have no defined 'initial' value, and thus, a\n      single value of a counter has (in general) no information\n      content.  Discontinuities in the monotonically increasing\n      value normally occur at re-initialization of the\n      management system, and at other times as specified in the\n      description of a schema node using this type.  If such\n      other times can occur, for example, the creation of\n      a schema node of type counter32 at times other than\n      re-initialization, then a corresponding schema node\n      should be defined, with an appropriate type, to indicate\n      the last discontinuity.\n\n      The counter32 type should not be used for configuration\n      schema nodes.  A default statement SHOULD NOT be used in\n      combination with the type counter32.\n\n      In the value set and its semantics, this type is equivalent\n      to the Counter32 type of the SMIv2.\";\n    reference\n     \"RFC 2578: Structure of Management Information Version 2\n                (SMIv2)\";\n  }\n}\n"
-       },
-       {
-               "moduleName": "ietf-yang-types-9",
-               "revision": "2013-07-23",
-               "yangSource": "module ietf-yang-types-9 {\n\n  namespace \"urn:ietf:params:xml:ns:yang:ietf-yang-types-9\";\n  prefix \"yang\";\n\n  organization\n   \"IETF NETMOD (NETCONF Data Modeling Language) Working Group\";\n\n  contact\n   \"WG Web:   <http://tools.ietf.org/wg/netmod/>\n    WG List:  <mailto:netmod@ietf.org>\n\n    WG Chair: David Kessens\n              <mailto:david.kessens@nsn.com>\n\n    WG Chair: Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\n\n    Editor:   Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\";\n\n  description\n   \"This module contains a collection of generally useful derived\n    YANG data types.\n\n    Copyright (c) 2013 IETF Trust and the persons identified as\n    authors of the code.  All rights reserved.\n\n    Redistribution and use in source and binary forms, with or\n    without modification, is permitted pursuant to, and subject\n    to the license terms contained in, the Simplified BSD License\n    set forth in Section 4.c of the IETF Trust's Legal Provisions\n    Relating to IETF Documents\n    (http://trustee.ietf.org/license-info).\n\n    This version of this YANG module is part of RFC 6991; see\n    the RFC itself for full legal notices.\";\n\n  revision 2013-07-23 {\n    description\n     \"This revision adds the following new data types:\n      - yang-identifier\n      - hex-string\n      - uuid\n      - dotted-quad\";\n    reference\n     \"RFC 6991: Common YANG Data Types\";\n  }\n\n  revision 2010-09-24 {\n    description\n     \"Initial revision.\";\n    reference\n     \"RFC 6021: Common YANG Data Types\";\n  }\n\n  /*** collection of counter and gauge types ***/\n\n  typedef counter32 {\n    type uint32;\n    description\n     \"The counter32 type represents a non-negative integer\n      that monotonically increases until it reaches a\n      maximum value of 2^32-1 (4294967295 decimal), when it\n      wraps around and starts increasing again from zero.\n\n      Counters have no defined 'initial' value, and thus, a\n      single value of a counter has (in general) no information\n      content.  Discontinuities in the monotonically increasing\n      value normally occur at re-initialization of the\n      management system, and at other times as specified in the\n      description of a schema node using this type.  If such\n      other times can occur, for example, the creation of\n      a schema node of type counter32 at times other than\n      re-initialization, then a corresponding schema node\n      should be defined, with an appropriate type, to indicate\n      the last discontinuity.\n\n      The counter32 type should not be used for configuration\n      schema nodes.  A default statement SHOULD NOT be used in\n      combination with the type counter32.\n\n      In the value set and its semantics, this type is equivalent\n      to the Counter32 type of the SMIv2.\";\n    reference\n     \"RFC 2578: Structure of Management Information Version 2\n                (SMIv2)\";\n  }\n}\n"
-       },
-       {
-               "moduleName": "ietf-yang-types-10",
-               "revision": "2013-07-24",
-               "yangSource": "module ietf-yang-types-10 {\n\n  namespace \"urn:ietf:params:xml:ns:yang:ietf-yang-types-10\";\n  prefix \"yang\";\n\n  organization\n   \"IETF NETMOD (NETCONF Data Modeling Language) Working Group\";\n\n  contact\n   \"WG Web:   <http://tools.ietf.org/wg/netmod/>\n    WG List:  <mailto:netmod@ietf.org>\n\n    WG Chair: David Kessens\n              <mailto:david.kessens@nsn.com>\n\n    WG Chair: Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\n\n    Editor:   Juergen Schoenwaelder\n              <mailto:j.schoenwaelder@jacobs-university.de>\";\n\n  description\n   \"This module contains a collection of generally useful derived\n    YANG data types.\n\n    Copyright (c) 2013 IETF Trust and the persons identified as\n    authors of the code.  All rights reserved.\n\n    Redistribution and use in source and binary forms, with or\n    without modification, is permitted pursuant to, and subject\n    to the license terms contained in, the Simplified BSD License\n    set forth in Section 4.c of the IETF Trust's Legal Provisions\n    Relating to IETF Documents\n    (http://trustee.ietf.org/license-info).\n\n    This version of this YANG module is part of RFC 6991; see\n    the RFC itself for full legal notices.\";\n\n  revision 2013-07-24 {\n    description\n     \"This revision adds the following new data types:\n      - yang-identifier\n      - hex-string\n      - uuid\n      - dotted-quad\";\n    reference\n     \"RFC 6991: Common YANG Data Types\";\n  }\n\n  revision 2010-09-24 {\n    description\n     \"Initial revision.\";\n    reference\n     \"RFC 6021: Common YANG Data Types\";\n  }\n\n  /*** collection of counter and gauge types ***/\n\n  typedef counter32 {\n    type uint32;\n    description\n     \"The counter32 type represents a non-negative integer\n      that monotonically increases until it reaches a\n      maximum value of 2^32-1 (4294967295 decimal), when it\n      wraps around and starts increasing again from zero.\n\n      Counters have no defined 'initial' value, and thus, a\n      single value of a counter has (in general) no information\n      content.  Discontinuities in the monotonically increasing\n      value normally occur at re-initialization of the\n      management system, and at other times as specified in the\n      description of a schema node using this type.  If such\n      other times can occur, for example, the creation of\n      a schema node of type counter32 at times other than\n      re-initialization, then a corresponding schema node\n      should be defined, with an appropriate type, to indicate\n      the last discontinuity.\n\n      The counter32 type should not be used for configuration\n      schema nodes.  A default statement SHOULD NOT be used in\n      combination with the type counter32.\n\n      In the value set and its semantics, this type is equivalent\n      to the Counter32 type of the SMIv2.\";\n    reference\n     \"RFC 2578: Structure of Management Information Version 2\n                (SMIv2)\";\n  }\n}\n"
-       }
-]
\ No newline at end of file
diff --git a/integration-test/src/test/resources/data/mock-dmi-responses/ietfYangModuleResponse.json b/integration-test/src/test/resources/data/mock-dmi-responses/ietfYangModuleResponse.json
deleted file mode 100644 (file)
index 2cbd8d1..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-{
-       "schemas": [
-               {
-                       "moduleName": "ietf-yang-types-1",
-                       "revision": "2013-07-15"
-               },
-               {
-                       "moduleName": "ietf-yang-types-2",
-                       "revision": "2013-07-16"
-               },
-               {
-                       "moduleName": "ietf-yang-types-3",
-                       "revision": "2013-07-17"
-               },
-               {
-                       "moduleName": "ietf-yang-types-4",
-                       "revision": "2013-07-18"
-               },
-               {
-                       "moduleName": "ietf-yang-types-5",
-                       "revision": "2013-07-19"
-               },
-               {
-                       "moduleName": "ietf-yang-types-6",
-                       "revision": "2013-07-20"
-               },
-               {
-                       "moduleName": "ietf-yang-types-7",
-                       "revision": "2013-07-21"
-               },
-               {
-                       "moduleName": "ietf-yang-types-8",
-                       "revision": "2013-07-22"
-               },
-               {
-                       "moduleName": "ietf-yang-types-9",
-                       "revision": "2013-07-23"
-               },
-               {
-                       "moduleName": "ietf-yang-types-10",
-                       "revision": "2013-07-24"
-               }
-       ]
-}
\ No newline at end of file