diff options
24 files changed, 389 insertions, 235 deletions
diff --git a/cps-application/src/main/java/org/onap/cps/config/WebSecurityConfig.java b/cps-application/src/main/java/org/onap/cps/config/WebSecurityConfig.java index 120e0f3658..0b6d0dba1e 100644 --- a/cps-application/src/main/java/org/onap/cps/config/WebSecurityConfig.java +++ b/cps-application/src/main/java/org/onap/cps/config/WebSecurityConfig.java @@ -53,7 +53,7 @@ public class WebSecurityConfig { * @param password password */ public WebSecurityConfig( - @Autowired @Value("${permit-uri}") final String permitUris, + @Autowired @Value("${security.permit-uri}") final String permitUris, @Autowired @Value("${security.auth.username}") final String username, @Autowired @Value("${security.auth.password}") final String password ) { diff --git a/cps-application/src/main/resources/application.yml b/cps-application/src/main/resources/application.yml index e84b1937b6..89969b9c6a 100644 --- a/cps-application/src/main/resources/application.yml +++ b/cps-application/src/main/resources/application.yml @@ -139,10 +139,11 @@ springdoc: - name: cps-ncmp-inventory url: /api-docs/cps-ncmp/openapi-inventory.yaml -permit-uri: /actuator/**,/swagger-ui.html,/swagger-ui/**,/swagger-resources/**,/api-docs/** + security: # comma-separated uri patterns which do not require authorization + permit-uri: /actuator/**,/swagger-ui.html,/swagger-ui/**,/swagger-resources/**,/api-docs/** auth: username: ${CPS_USERNAME} password: ${CPS_PASSWORD} diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/mapper/CmHandleStateMapperSpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/mapper/CmHandleStateMapperSpec.groovy index f394f91938..b5f7f0edeb 100644 --- a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/mapper/CmHandleStateMapperSpec.groovy +++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/mapper/CmHandleStateMapperSpec.groovy @@ -28,9 +28,7 @@ import org.onap.cps.ncmp.api.impl.inventory.CmHandleState import org.onap.cps.ncmp.api.impl.inventory.CompositeStateBuilder import org.onap.cps.ncmp.rest.model.CmHandleCompositeState import org.onap.cps.ncmp.api.impl.inventory.DataStoreSyncState -import spock.lang.Ignore import spock.lang.Specification - import java.time.OffsetDateTime import java.time.ZoneOffset import java.time.format.DateTimeFormatter @@ -62,10 +60,9 @@ class CmHandleStateMapperSpec extends Specification { assert result.dataSyncState.operational.getSyncState() != null } - @Ignore def 'Handling null state.'() { expect: 'converting null returns null' - objectUnderTest.toDataStores(null) == null + CmHandleStateMapper.toDataStores(null) == null } def 'Internal to External Lock Reason Mapping of #scenario'() { diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java index 2714c1dafb..1afe5c7114 100755 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java @@ -60,6 +60,7 @@ import org.onap.cps.ncmp.api.impl.inventory.CompositeStateBuilder; import org.onap.cps.ncmp.api.impl.inventory.CompositeStateUtils; import org.onap.cps.ncmp.api.impl.inventory.DataStoreSyncState; import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence; +import org.onap.cps.ncmp.api.impl.inventory.sync.ModuleOperationsUtils; 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; @@ -413,7 +414,8 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService final NcmpServiceCmHandle ncmpServiceCmHandle = new NcmpServiceCmHandle(); ncmpServiceCmHandle.setCmHandleId(cmHandleId); final String moduleSetTag = dmiPluginRegistration.getUpgradedCmHandles().getModuleSetTag(); - final String lockReasonWithModuleSetTag = MessageFormat.format("ModuleSetTag: {0}", moduleSetTag); + final String lockReasonWithModuleSetTag = MessageFormat.format( + ModuleOperationsUtils.MODULE_SET_TAG_MESSAGE_FORMAT, moduleSetTag); ncmpServiceCmHandle.setCompositeState(new CompositeStateBuilder().withCmHandleState(CmHandleState.READY) .withLockReason(MODULE_UPGRADE, lockReasonWithModuleSetTag).build()); return YangModelCmHandle.toYangModelCmHandle(dmiPluginRegistration.getDmiPlugin(), diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/sync/DataSyncWatchdog.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/sync/DataSyncWatchdog.java index 49804adc13..6f089a57fb 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/sync/DataSyncWatchdog.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/sync/DataSyncWatchdog.java @@ -48,7 +48,7 @@ public class DataSyncWatchdog { private final CpsDataService cpsDataService; - private final SyncUtils syncUtils; + private final ModuleOperationsUtils moduleOperationsUtils; private final IMap<String, Boolean> dataSyncSemaphores; @@ -58,13 +58,13 @@ public class DataSyncWatchdog { */ @Scheduled(fixedDelayString = "${ncmp.timers.cm-handle-data-sync.sleep-time-ms:30000}") public void executeUnSynchronizedReadyCmHandlePoll() { - syncUtils.getUnsynchronizedReadyCmHandles().forEach(unSynchronizedReadyCmHandle -> { + moduleOperationsUtils.getUnsynchronizedReadyCmHandles().forEach(unSynchronizedReadyCmHandle -> { final String cmHandleId = unSynchronizedReadyCmHandle.getId(); if (hasPushedIntoSemaphoreMap(cmHandleId)) { log.debug("Executing data sync on {}", cmHandleId); final CompositeState compositeState = inventoryPersistence .getCmHandleState(cmHandleId); - final String resourceData = syncUtils.getResourceData(cmHandleId); + final String resourceData = moduleOperationsUtils.getResourceData(cmHandleId); if (resourceData == null) { log.debug("Error retrieving resource data for Cm-Handle: {}", cmHandleId); } else { diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/sync/SyncUtils.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleOperationsUtils.java index ab85c2127e..25ea3bc1e6 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/sync/SyncUtils.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleOperationsUtils.java @@ -29,6 +29,7 @@ import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -54,12 +55,18 @@ import org.springframework.stereotype.Service; @Slf4j @Service @RequiredArgsConstructor -public class SyncUtils { +public class ModuleOperationsUtils { private final CmHandleQueries cmHandleQueries; private final DmiDataOperations dmiDataOperations; private final JsonObjectMapper jsonObjectMapper; - private static final Pattern retryAttemptPattern = Pattern.compile("^Attempt #(\\d+) failed:"); + 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 UPGRADE_FAILED_FORMAT = UPGRADE_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+)"); /** * Query data nodes for cm handles with an "ADVISED" cm handle state. @@ -87,14 +94,11 @@ public class SyncUtils { for (final DataNode unsynchronizedCmHandle : unsynchronizedCmHandles) { final String cmHandleId = unsynchronizedCmHandle.getLeaves().get("id").toString(); if (cmHandleQueries.cmHandleHasState(cmHandleId, CmHandleState.READY)) { - yangModelCmHandles.addAll( - convertCmHandlesDataNodesToYangModelCmHandles( + yangModelCmHandles.addAll(convertCmHandlesDataNodesToYangModelCmHandles( Collections.singletonList(unsynchronizedCmHandle))); } } - Collections.shuffle(yangModelCmHandles); - return yangModelCmHandles; } @@ -121,17 +125,43 @@ public class SyncUtils { final LockReasonCategory lockReasonCategory, final String errorMessage) { int attempt = 1; - if (compositeState.getLockReason() != null) { - final Matcher matcher = retryAttemptPattern.matcher(compositeState.getLockReason().getDetails()); - if (matcher.find()) { - attempt = 1 + Integer.parseInt(matcher.group(1)); - } + final Map<String, String> compositeStateDetails + = getLockedCompositeStateDetails(compositeState.getLockReason()); + if (!compositeStateDetails.isEmpty()) { + attempt = 1 + Integer.parseInt(compositeStateDetails.get(RETRY_ATTEMPT_KEY)); } compositeState.setLockReason(CompositeState.LockReason.builder() - .details(String.format("Attempt #%d failed: %s", attempt, errorMessage)) + .details(String.format(UPGRADE_FAILED_FORMAT, + compositeStateDetails.get(MODULE_SET_TAG_KEY), attempt, errorMessage)) .lockReasonCategory(lockReasonCategory).build()); } + /** + * Extract lock reason details as key-value pair. + * + * @param compositeStateLockReason lock reason having all the details + * @return a map of lock reason details + */ + public static Map<String, String> getLockedCompositeStateDetails(final CompositeState.LockReason + compositeStateLockReason) { + if (compositeStateLockReason != null) { + final Map<String, String> compositeStateDetails = new HashMap<>(2); + final String lockedCompositeStateReasonDetails = compositeStateLockReason.getDetails(); + final Matcher retryAttemptMatcher = retryAttemptPattern.matcher(lockedCompositeStateReasonDetails); + if (retryAttemptMatcher.find()) { + final int attemptsRegexGroupId = 1; + compositeStateDetails.put(RETRY_ATTEMPT_KEY, retryAttemptMatcher.group(attemptsRegexGroupId)); + } + final Matcher moduleSetTagMatcher = moduleSetTagPattern.matcher(lockedCompositeStateReasonDetails); + if (moduleSetTagMatcher.find()) { + final int moduleSetTagRegexGroupId = 1; + compositeStateDetails.put(MODULE_SET_TAG_KEY, moduleSetTagMatcher.group(moduleSetTagRegexGroupId)); + } + return compositeStateDetails; + } + return Collections.emptyMap(); + } + /** * Check if a module sync retry is needed. @@ -146,29 +176,12 @@ public class SyncUtils { final boolean failedDuringModuleSync = LockReasonCategory.MODULE_SYNC_FAILED == lockReason.getLockReasonCategory(); - final boolean moduleUpgrade = LockReasonCategory.MODULE_UPGRADE + final boolean failedDuringModuleUpgrade = LockReasonCategory.MODULE_UPGRADE_FAILED == lockReason.getLockReasonCategory(); - if (failedDuringModuleSync) { - final int timeInMinutesUntilNextAttempt; - final Matcher matcher = retryAttemptPattern.matcher(lockReason.getDetails()); - if (matcher.find()) { - timeInMinutesUntilNextAttempt = (int) Math.pow(2, Integer.parseInt(matcher.group(1))); - } else { - timeInMinutesUntilNextAttempt = 1; - log.info("First Attempt: no current attempts found."); - } - final int timeSinceLastAttempt = (int) Duration.between(time, OffsetDateTime.now()).toMinutes(); - if (timeInMinutesUntilNextAttempt >= timeSinceLastAttempt) { - log.info("Time until next attempt is {} minutes: ", - timeInMinutesUntilNextAttempt - timeSinceLastAttempt); - return false; - } - log.info("Retry due now"); - return true; - } else if (moduleUpgrade) { - log.info("Locked for module upgrade."); - return true; + if (failedDuringModuleSync || failedDuringModuleUpgrade) { + log.info("Locked for module {}.", failedDuringModuleSync ? "sync" : "upgrade"); + return isRetryDue(lockReason, time); } log.info("Locked for other reason"); return false; @@ -191,6 +204,19 @@ public class SyncUtils { return null; } + /** + * Checks if cm handle state module is in upgrade or upgrade failed. + * + * @param compositeState current lock reason of cm handle + * @return true or false based on lock reason category + */ + public static boolean isInUpgradeOrUpgradeFailed(final CompositeState compositeState) { + return compositeState.getLockReason() != null + && (LockReasonCategory.MODULE_UPGRADE.equals(compositeState.getLockReason().getLockReasonCategory()) + || LockReasonCategory.MODULE_UPGRADE_FAILED.equals(compositeState.getLockReason() + .getLockReasonCategory())); + } + private String getFirstResource(final Object responseBody) { final String jsonObjectAsString = jsonObjectMapper.asJsonString(responseBody); final JsonNode overallJsonNode = jsonObjectMapper.convertToJsonNode(jsonObjectAsString); @@ -199,10 +225,29 @@ public class SyncUtils { return jsonObjectMapper.asJsonString(Map.of(firstElement.getKey(), firstElement.getValue())); } - private static List<YangModelCmHandle> convertCmHandlesDataNodesToYangModelCmHandles( + private List<YangModelCmHandle> convertCmHandlesDataNodesToYangModelCmHandles( final List<DataNode> cmHandlesAsDataNodeList) { return cmHandlesAsDataNodeList.stream() .map(cmHandle -> YangDataConverter.convertCmHandleToYangModel(cmHandle, cmHandle.getLeaves().get("id").toString())).toList(); } + + private boolean isRetryDue(final CompositeState.LockReason compositeStateLockReason, final OffsetDateTime time) { + final int timeInMinutesUntilNextAttempt; + final Map<String, String> compositeStateDetails = getLockedCompositeStateDetails(compositeStateLockReason); + if (compositeStateDetails.isEmpty()) { + timeInMinutesUntilNextAttempt = 1; + log.info("First Attempt: no current attempts found."); + } else { + timeInMinutesUntilNextAttempt = (int) Math.pow(2, Integer.parseInt(compositeStateDetails + .get(RETRY_ATTEMPT_KEY))); + } + final int timeSinceLastAttempt = (int) Duration.between(time, OffsetDateTime.now()).toMinutes(); + if (timeInMinutesUntilNextAttempt >= timeSinceLastAttempt) { + log.info("Time until next attempt is {} minutes: ", timeInMinutesUntilNextAttempt - timeSinceLastAttempt); + return false; + } + log.info("Retry due now"); + return true; + } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncService.java index d191a54677..841368c0db 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncService.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncService.java @@ -41,7 +41,6 @@ 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.inventory.LockReasonCategory; 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; @@ -77,10 +76,11 @@ public class ModuleSyncService { final String moduleSetTag; final String cmHandleId = yangModelCmHandle.getId(); final CompositeState compositeState = yangModelCmHandle.getCompositeState(); - final boolean inUpgrade = isInUpgrade(compositeState); + final boolean inUpgrade = ModuleOperationsUtils.isInUpgradeOrUpgradeFailed(compositeState); if (inUpgrade) { - moduleSetTag = extractModuleSetTag(compositeState); + moduleSetTag = ModuleOperationsUtils.getLockedCompositeStateDetails(compositeState.getLockReason()) + .get(ModuleOperationsUtils.MODULE_SET_TAG_KEY); } else { moduleSetTag = yangModelCmHandle.getModuleSetTag(); } @@ -174,12 +174,4 @@ public class ModuleSyncService { moduleSetTagCache.put(moduleSetTag, moduleReferencesFromExistingCmHandle); } - private static String extractModuleSetTag(final CompositeState compositeState) { - return compositeState.getLockReason().getDetails().split(":")[1].trim(); - } - - private static boolean isInUpgrade(final CompositeState compositeState) { - return compositeState.getLockReason() != null && LockReasonCategory.MODULE_UPGRADE.equals( - compositeState.getLockReason().getLockReasonCategory()); - } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncTasks.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncTasks.java index facaf155fe..896316a496 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncTasks.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncTasks.java @@ -44,7 +44,7 @@ import org.springframework.stereotype.Component; @Slf4j public class ModuleSyncTasks { private final InventoryPersistence inventoryPersistence; - private final SyncUtils syncUtils; + private final ModuleOperationsUtils moduleOperationsUtils; private final ModuleSyncService moduleSyncService; private final LcmEventsCmHandleStateHandler lcmEventsCmHandleStateHandler; private final IMap<String, Object> moduleSyncStartedOnCmHandles; @@ -73,9 +73,14 @@ public class ModuleSyncTasks { yangModelCmHandle.getCompositeState().setLockReason(null); cmHandelStatePerCmHandle.put(yangModelCmHandle, CmHandleState.READY); } catch (final Exception e) { - log.warn("Processing of {} module sync failed due to reason {}.", cmHandleId, e.getMessage()); - syncUtils.updateLockReasonDetailsAndAttempts(compositeState, LockReasonCategory.MODULE_SYNC_FAILED, - e.getMessage()); + log.warn("Processing of {} module failed due to reason {}.", cmHandleId, e.getMessage()); + if (ModuleOperationsUtils.isInUpgradeOrUpgradeFailed(compositeState)) { + moduleOperationsUtils.updateLockReasonDetailsAndAttempts(compositeState, + LockReasonCategory.MODULE_UPGRADE_FAILED, e.getMessage()); + } else { + moduleOperationsUtils.updateLockReasonDetailsAndAttempts(compositeState, + LockReasonCategory.MODULE_SYNC_FAILED, e.getMessage()); + } setCmHandleStateLocked(yangModelCmHandle, compositeState.getLockReason()); cmHandelStatePerCmHandle.put(yangModelCmHandle, CmHandleState.LOCKED); } @@ -98,7 +103,7 @@ public class ModuleSyncTasks { final Map<YangModelCmHandle, CmHandleState> cmHandleStatePerCmHandle = new HashMap<>(failedCmHandles.size()); for (final YangModelCmHandle failedCmHandle : failedCmHandles) { final CompositeState compositeState = failedCmHandle.getCompositeState(); - final boolean isReadyForRetry = syncUtils.needsModuleSyncRetryOrUpgrade(compositeState); + final boolean isReadyForRetry = moduleOperationsUtils.needsModuleSyncRetryOrUpgrade(compositeState); log.info("Retry for cmHandleId : {} is {}", failedCmHandle.getId(), isReadyForRetry); if (isReadyForRetry) { final String resetCmHandleId = failedCmHandle.getId(); diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncWatchdog.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncWatchdog.java index bf005051f9..249232d230 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncWatchdog.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncWatchdog.java @@ -43,7 +43,7 @@ import org.springframework.stereotype.Service; @Service public class ModuleSyncWatchdog { - private final SyncUtils syncUtils; + private final ModuleOperationsUtils moduleOperationsUtils; private final BlockingQueue<DataNode> moduleSyncWorkQueue; private final IMap<String, Object> moduleSyncStartedOnCmHandles; private final ModuleSyncTasks moduleSyncTasks; @@ -88,7 +88,8 @@ public class ModuleSyncWatchdog { @Scheduled(fixedDelayString = "${ncmp.timers.locked-modules-sync.sleep-time-ms:300000}") public void resetPreviouslyFailedCmHandles() { log.info("Processing module sync retry-watchdog waking up."); - final List<YangModelCmHandle> failedCmHandles = syncUtils.getCmHandlesThatFailedModelSyncOrUpgrade(); + final List<YangModelCmHandle> failedCmHandles + = moduleOperationsUtils.getCmHandlesThatFailedModelSyncOrUpgrade(); log.info("Retrying {} cmHandles", failedCmHandles.size()); moduleSyncTasks.resetFailedCmHandles(failedCmHandles); } @@ -104,7 +105,7 @@ public class ModuleSyncWatchdog { private void populateWorkQueueIfNeeded() { if (moduleSyncWorkQueue.isEmpty()) { - final List<DataNode> advisedCmHandles = syncUtils.getAdvisedCmHandles(); + final List<DataNode> advisedCmHandles = moduleOperationsUtils.getAdvisedCmHandles(); log.info("Processing module sync fetched {} advised cm handles from DB", advisedCmHandles.size()); for (final DataNode advisedCmHandle : advisedCmHandles) { if (!moduleSyncWorkQueue.offer(advisedCmHandle)) { diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/AbstractModelLoader.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/AbstractModelLoader.java index cb2e15a3ce..fd5f2b0ed6 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/AbstractModelLoader.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/AbstractModelLoader.java @@ -24,6 +24,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.time.OffsetDateTime; +import java.util.HashMap; import java.util.Map; import lombok.NonNull; import lombok.RequiredArgsConstructor; @@ -85,10 +86,10 @@ abstract class AbstractModelLoader implements ModelLoader { } } - void createSchemaSet(final String dataspaceName, final String schemaSetName, final String resourceName) { + void createSchemaSet(final String dataspaceName, final String schemaSetName, final String... resourceNames) { try { - final Map<String, String> yangResourceContentMap = createYangResourceToContentMap(resourceName); - cpsModuleService.createSchemaSet(dataspaceName, schemaSetName, yangResourceContentMap); + final Map<String, String> yangResourcesContentMap = createYangResourcesToContentMap(resourceNames); + cpsModuleService.createSchemaSet(dataspaceName, schemaSetName, yangResourcesContentMap); } catch (final AlreadyDefinedException alreadyDefinedException) { log.warn("Creating new schema set failed as schema set already exists"); } catch (final Exception exception) { @@ -140,8 +141,12 @@ abstract class AbstractModelLoader implements ModelLoader { } } - Map<String, String> createYangResourceToContentMap(final String resourceName) { - return Map.of(resourceName, getFileContentAsString("models/" + resourceName)); + Map<String, String> createYangResourcesToContentMap(final String... resourceNames) { + final Map<String, String> yangResourcesToContentMap = new HashMap<>(); + for (final String resourceName: resourceNames) { + yangResourcesToContentMap.put(resourceName, getFileContentAsString("models/" + resourceName)); + } + return yangResourcesToContentMap; } private String getFileContentAsString(final String fileName) { diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/SubscriptionModelLoader.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/CmDataSubscriptionModelLoader.java index 4d1a91ca99..ade31e9ce6 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/SubscriptionModelLoader.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/CmDataSubscriptionModelLoader.java @@ -31,16 +31,23 @@ import org.springframework.stereotype.Service; @Slf4j @Service -public class SubscriptionModelLoader extends AbstractModelLoader { +public class CmDataSubscriptionModelLoader extends AbstractModelLoader { - private static final String MODEL_FILENAME = "subscription.yang"; - private static final String ANCHOR_NAME = "AVC-Subscriptions"; - private static final String SCHEMASET_NAME = "subscriptions"; - private static final String REGISTRY_DATANODE_NAME = "subscription-registry"; + private static final String MODEL_FILENAME = "cm-data-subscriptions@2023-11-13.yang"; + private static final String SCHEMASET_NAME = "cm-data-subscriptions"; + private static final String ANCHOR_NAME = "cm-data-subscriptions"; + private static final String REGISTRY_DATANODE_NAME = "datastores"; - public SubscriptionModelLoader(final CpsAdminService cpsAdminService, - final CpsModuleService cpsModuleService, - final CpsDataService cpsDataService) { + private static final String DEPRECATED_MODEL_FILENAME = "subscription.yang"; + private static final String DEPRECATED_ANCHOR_NAME = "AVC-Subscriptions"; + private static final String DEPRECATED_SCHEMASET_NAME = "subscriptions"; + private static final String DEPRECATED_REGISTRY_DATANODE_NAME = "subscription-registry"; + + + + public CmDataSubscriptionModelLoader(final CpsAdminService cpsAdminService, + final CpsModuleService cpsModuleService, + final CpsDataService cpsDataService) { super(cpsAdminService, cpsModuleService, cpsDataService); } @@ -51,17 +58,20 @@ public class SubscriptionModelLoader extends AbstractModelLoader { public void onboardOrUpgradeModel() { if (subscriptionModelLoaderEnabled) { waitUntilDataspaceIsAvailable(NCMP_DATASPACE_NAME); - onboardSubscriptionModel(); - log.info("Subscription Model onboarded successfully"); + onboardSubscriptionModels(); + log.info("Subscription Models onboarded successfully"); } else { log.info("Subscription Model Loader is disabled"); } } - private void onboardSubscriptionModel() { + private void onboardSubscriptionModels() { + createSchemaSet(NCMP_DATASPACE_NAME, DEPRECATED_SCHEMASET_NAME, DEPRECATED_MODEL_FILENAME); + createAnchor(NCMP_DATASPACE_NAME, DEPRECATED_SCHEMASET_NAME, DEPRECATED_ANCHOR_NAME); + createTopLevelDataNode(NCMP_DATASPACE_NAME, DEPRECATED_ANCHOR_NAME, DEPRECATED_REGISTRY_DATANODE_NAME); + createSchemaSet(NCMP_DATASPACE_NAME, SCHEMASET_NAME, MODEL_FILENAME); createAnchor(NCMP_DATASPACE_NAME, SCHEMASET_NAME, ANCHOR_NAME); createTopLevelDataNode(NCMP_DATASPACE_NAME, ANCHOR_NAME, REGISTRY_DATANODE_NAME); } - } diff --git a/cps-ncmp-service/src/main/resources/models/cm-data-subscriptions@2023-11-13.yang b/cps-ncmp-service/src/main/resources/models/cm-data-subscriptions@2023-11-13.yang new file mode 100644 index 0000000000..de675b117c --- /dev/null +++ b/cps-ncmp-service/src/main/resources/models/cm-data-subscriptions@2023-11-13.yang @@ -0,0 +1,49 @@ +module cm-data-subscriptions { + yang-version 1.1; + namespace "org:onap:cps:ncmp"; + + prefix cmds; + + revision "2023-11-13" { + description + "First release of cm data (notification) subscriptions model"; + } + + container datastores { + + list datastore { + key "name"; + + leaf name { + type string; + } + + container cm-handles { + + list cm-handle { + key "id"; + + leaf id { + type string; + } + + container filters { + + list filter { + key "xpath"; + + leaf xpath { + type string; + } + + leaf-list subscribers { + type string; + } + + } + } + } + } + } + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/EventPublisherSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/EventPublisherSpec.groovy deleted file mode 100644 index d0f1afd5d5..0000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/EventPublisherSpec.groovy +++ /dev/null @@ -1,89 +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.events - -import ch.qos.logback.classic.Level -import ch.qos.logback.classic.Logger -import ch.qos.logback.core.read.ListAppender -import org.apache.kafka.clients.producer.ProducerRecord -import org.apache.kafka.clients.producer.RecordMetadata -import org.apache.kafka.common.TopicPartition -import org.onap.cps.ncmp.init.SubscriptionModelLoader -import org.slf4j.LoggerFactory -import org.springframework.kafka.support.SendResult -import spock.lang.Ignore -import spock.lang.Specification - -class EventPublisherSpec extends Specification { - - def objectUnderTest = new EventsPublisher(null, null) - def logger = (Logger) LoggerFactory.getLogger(objectUnderTest.getClass()) - def loggingListAppender - - void setup() { - logger.setLevel(Level.DEBUG) - loggingListAppender = new ListAppender() - logger.addAppender(loggingListAppender) - loggingListAppender.start() - } - - void cleanup() { - ((Logger) LoggerFactory.getLogger(SubscriptionModelLoader.class)).detachAndStopAllAppenders() - } - - @Ignore - def 'Callback handling on success.'() { - given: 'a send result' - def producerRecord = new ProducerRecord('topic-1', 'my value') - def topicPartition = new TopicPartition('topic-2', 0) - def recordMetadata = new RecordMetadata(topicPartition, 0, 0, 0, 0, 0) - def sendResult = new SendResult(producerRecord, recordMetadata) - when: 'the callback handler processes success' - def callbackHandler = objectUnderTest.handleCallback('topic-3') - callbackHandler.onSuccess(sendResult) - then: 'an event is logged with level DEBUG' - def loggingEvent = getLoggingEvent() - loggingEvent.level == Level.DEBUG - and: 'it contains the topic (from the record metadata) and the "value" (from the producer record)' - loggingEvent.formattedMessage.contains('topic-2') - loggingEvent.formattedMessage.contains('my value') - } - - - @Ignore - def 'Callback handling on failure.'() { - when: 'the callback handler processes a failure' - def callbackHandler = objectUnderTest.handleCallback('my topic') - callbackHandler.onFailure(new Exception('my exception')) - then: 'an event is logged with level ERROR' - def loggingEvent = getLoggingEvent() - loggingEvent.level == Level.ERROR - and: 'it contains the topic and exception message' - loggingEvent.formattedMessage.contains('my topic') - loggingEvent.formattedMessage.contains('my exception') - } - - def getLoggingEvent() { - return loggingListAppender.list[0] - } - - -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/DataSyncWatchdogSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/DataSyncWatchdogSpec.groovy index 65dfc05791..0ffb567916 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/DataSyncWatchdogSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/DataSyncWatchdogSpec.groovy @@ -24,8 +24,6 @@ import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NFP_OPE import com.hazelcast.map.IMap import org.onap.cps.api.CpsDataService -import org.onap.cps.ncmp.api.impl.inventory.sync.DataSyncWatchdog -import org.onap.cps.ncmp.api.impl.inventory.sync.SyncUtils import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle import org.onap.cps.ncmp.api.impl.inventory.CmHandleState import org.onap.cps.ncmp.api.impl.inventory.CompositeState @@ -39,7 +37,7 @@ class DataSyncWatchdogSpec extends Specification { def mockCpsDataService = Mock(CpsDataService) - def mockSyncUtils = Mock(SyncUtils) + def mockSyncUtils = Mock(ModuleOperationsUtils) def mockDataSyncSemaphores = Mock(IMap<String,Boolean>) diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/SyncUtilsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleOperationsUtilsSpec.groovy index 025d9482d6..099fc5ac18 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/SyncUtilsSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleOperationsUtilsSpec.groovy @@ -22,15 +22,14 @@ package org.onap.cps.ncmp.api.impl.inventory.sync import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.LOCKED_MISBEHAVING +import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.MODULE_UPGRADE import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.MODULE_SYNC_FAILED -import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.MODULE_UPGRADE import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.MODULE_UPGRADE_FAILED import ch.qos.logback.classic.Level import ch.qos.logback.classic.Logger import ch.qos.logback.core.read.ListAppender -import org.onap.cps.ncmp.api.impl.inventory.sync.SyncUtils import org.slf4j.LoggerFactory import org.springframework.context.annotation.AnnotationConfigApplicationContext import com.fasterxml.jackson.databind.JsonNode @@ -51,7 +50,7 @@ import java.time.OffsetDateTime import java.time.format.DateTimeFormatter import java.util.stream.Collectors -class SyncUtilsSpec extends Specification{ +class ModuleOperationsUtilsSpec extends Specification{ def mockCmHandleQueries = Mock(CmHandleQueries) @@ -59,7 +58,7 @@ class SyncUtilsSpec extends Specification{ def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper()) - def objectUnderTest = new SyncUtils(mockCmHandleQueries, mockDmiDataOperations, jsonObjectMapper) + def objectUnderTest = new ModuleOperationsUtils(mockCmHandleQueries, mockDmiDataOperations, jsonObjectMapper) def static neverUpdatedBefore = '1900-01-01T00:00:00.000+0100' @@ -71,7 +70,7 @@ class SyncUtilsSpec extends Specification{ def applicationContext = new AnnotationConfigApplicationContext() - def logger = (Logger) LoggerFactory.getLogger(SyncUtils) + def logger = (Logger) LoggerFactory.getLogger(ModuleOperationsUtils) def loggingListAppender void setup() { @@ -83,7 +82,7 @@ class SyncUtilsSpec extends Specification{ } void cleanup() { - ((Logger) LoggerFactory.getLogger(SyncUtils.class)).detachAndStopAllAppenders() + ((Logger) LoggerFactory.getLogger(ModuleOperationsUtils.class)).detachAndStopAllAppenders() applicationContext.close() } @@ -107,7 +106,7 @@ class SyncUtilsSpec extends Specification{ objectUnderTest.updateLockReasonDetailsAndAttempts(compositeState, MODULE_SYNC_FAILED, 'new error message') then: 'the composite state lock reason and details are updated' assert compositeState.lockReason.lockReasonCategory == MODULE_SYNC_FAILED - assert compositeState.lockReason.details == expectedDetails + assert compositeState.lockReason.details.contains(expectedDetails) where: scenario | lockReason || expectedDetails 'does not exist' | null || 'Attempt #1 failed: new error message' @@ -143,28 +142,28 @@ class SyncUtilsSpec extends Specification{ def logs = loggingListAppender.list.toString() assert logs.contains(logReason) where: 'the following parameters are used' - scenario | lastUpdateMinutesAgo | lockDetails | logReason || retryExpected - 'never attempted before' | -1 | 'fist attempt:' | 'First Attempt:' || true - '1st attempt, last attempt > 2 minute ago' | 3 | 'Attempt #1 failed:' | 'Retry due now' || true - '2nd attempt, last attempt < 4 minutes ago' | 1 | 'Attempt #2 failed:' | 'Time until next attempt is 3 minutes:' || false - '2nd attempt, last attempt > 4 minutes ago' | 5 | 'Attempt #2 failed:' | 'Retry due now' || true + scenario | lastUpdateMinutesAgo | lockDetails | logReason || retryExpected + 'never attempted before' | -1 | 'Fist attempt:' | 'First Attempt:' || true + '1st attempt, last attempt > 2 minute ago' | 3 | 'Attempt #1 failed: some error' | 'Retry due now' || true + '2nd attempt, last attempt < 4 minutes ago' | 1 | 'Attempt #2 failed: some error' | 'Time until next attempt is 3 minutes:' || false + '2nd attempt, last attempt > 4 minutes ago' | 5 | 'Attempt #2 failed: some error' | 'Retry due now' || true } - def 'Retry Locked Cm-Handle with other lock reasons (category) #lockReasonCategory'() { + def 'Retry Locked Cm-Handle with lock reasons (category) #lockReasonCategory'() { when: 'checking to see if cm handle is ready for retry' - def result = objectUnderTest.needsModuleSyncRetryOrUpgrade(new CompositeStateBuilder() + def result = objectUnderTest.needsModuleSyncRetryOrUpgrade(new CompositeStateBuilder() .withLockReason(lockReasonCategory, 'some details') .withLastUpdatedTime(nowAsString).build()) then: 'verify retry attempts' - assert result == retryAttempt + assert !result and: 'logs contain related information' - def logs = loggingListAppender.list.toString() - assert logs.contains(logReason) + def logs = loggingListAppender.list.toString() + assert logs.contains(logReason) where: 'the following lock reasons occurred' - scenario | lockReasonCategory || logReason | retryAttempt - 'module upgrade' | MODULE_UPGRADE || 'Locked for module upgrade.' | true - 'module sync failed' | MODULE_SYNC_FAILED || 'First Attempt:' | false - 'lock misbehaving' | LOCKED_MISBEHAVING || 'Locked for other reason' | false + scenario | lockReasonCategory || logReason + 'module upgrade' | MODULE_UPGRADE_FAILED || 'First Attempt:' + 'module sync failed' | MODULE_SYNC_FAILED || 'First Attempt:' + 'lock misbehaving' | LOCKED_MISBEHAVING || 'Locked for other reason' } def 'Get a Cm-Handle where #scenario'() { @@ -195,4 +194,18 @@ class SyncUtilsSpec extends Specification{ then: 'the returned data is correct' result == jsonString } + + def 'Extract module set tag and number of attempt when lock reason contains #scenario'() { + expect: 'lock reason details are extracted correctly' + def result = objectUnderTest.getLockedCompositeStateDetails(new CompositeStateBuilder().withLockReason(MODULE_UPGRADE, lockReasonDetails).build().lockReason) + and: 'the result contains the correct moduleSetTag' + assert result['moduleSetTag'] == expectedModuleSetTag + and: 'the result contains the correct number of attempts' + assert result['attempt'] == expectedNumberOfAttempts + where: 'the following scenarios are used' + scenario | lockReasonDetails || expectedModuleSetTag | expectedNumberOfAttempts + 'module set tag only' | 'Upgrade to ModuleSetTag: targetModuleSetTag' || 'targetModuleSetTag' | null + 'number of attempts only' | 'Attempt #1 failed: some error' || null | '1' + 'number of attempts and module set tag both' | 'Upgrade to ModuleSetTag: targetModuleSetTag Attempt #1 failed: some error' || 'targetModuleSetTag' | '1' + } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncServiceSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncServiceSpec.groovy index 18b87af9a1..5384f31cc1 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncServiceSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncServiceSpec.groovy @@ -20,7 +20,6 @@ package org.onap.cps.ncmp.api.impl.inventory.sync -import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.LOCKED_MISBEHAVING import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.MODULE_UPGRADE @@ -31,7 +30,6 @@ import org.onap.cps.api.CpsAdminService import org.onap.cps.api.CpsDataService import org.onap.cps.api.CpsModuleService import org.onap.cps.spi.model.DataNodeBuilder -import org.onap.cps.ncmp.api.impl.inventory.sync.ModuleSyncService import org.onap.cps.ncmp.api.impl.operations.DmiModelOperations import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle import org.onap.cps.ncmp.api.impl.inventory.CmHandleQueries @@ -90,7 +88,7 @@ class ModuleSyncServiceSpec extends Specification { def 'Upgrade model for an existing cm handle with Module Set Tag where the modules are #scenario'() { given: 'a cm handle being upgraded to module set tag: tag-1' def ncmpServiceCmHandle = new NcmpServiceCmHandle() - ncmpServiceCmHandle.setCompositeState(new CompositeStateBuilder().withLockReason(MODULE_UPGRADE, 'new moduleSetTag: tag-1').build()) + ncmpServiceCmHandle.setCompositeState(new CompositeStateBuilder().withLockReason(MODULE_UPGRADE, 'Upgrade to ModuleSetTag: tag-1').build()) def dmiServiceName = 'some service name' ncmpServiceCmHandle.cmHandleId = 'upgraded-ch' def yangModelCmHandle = YangModelCmHandle.toYangModelCmHandle(dmiServiceName, '', '', ncmpServiceCmHandle,'tag-1') @@ -98,7 +96,7 @@ class ModuleSyncServiceSpec extends Specification { 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) + mockModuleSetTagCache.put('tag-1', moduleReferences) } else { mockDmiModelOperations.getModuleReferences(yangModelCmHandle) >> moduleReferences } @@ -127,7 +125,7 @@ class ModuleSyncServiceSpec extends Specification { given: 'a cm handle that is ready but locked for upgrade' def ncmpServiceCmHandle = new NcmpServiceCmHandle() ncmpServiceCmHandle.setCompositeState(new CompositeStateBuilder() - .withLockReason(MODULE_UPGRADE, 'new moduleSetTag: targetModuleSetTag').build()) + .withLockReason(MODULE_UPGRADE, 'Upgrade to ModuleSetTag: targetModuleSetTag').build()) ncmpServiceCmHandle.setCmHandleId('cmHandleId-1') def yangModelCmHandle = YangModelCmHandle.toYangModelCmHandle('some service name', '', '', ncmpServiceCmHandle, 'targetModuleSetTag') mockCmHandleQueries.cmHandleHasState('cmHandleId-1', CmHandleState.READY) >> true @@ -170,11 +168,6 @@ class ModuleSyncServiceSpec extends Specification { result == unsupportedOperationException } - def 'Extract module set tag'() { - expect: 'the module set tag is extracted correctly' - assert 'targetModuleSetTag' == objectUnderTest.extractModuleSetTag(new CompositeStateBuilder().withLockReason(MODULE_UPGRADE, 'new moduleSetTag: targetModuleSetTag').build()) - } - def toModuleReferences(moduleReferenceAsMap) { def moduleReferences = [].withDefault { [:] } moduleReferenceAsMap.forEach(property -> diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncTasksSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncTasksSpec.groovy index 0d927551d0..3bdac1855c 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncTasksSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncTasksSpec.groovy @@ -21,6 +21,10 @@ package org.onap.cps.ncmp.api.impl.inventory.sync + +import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.MODULE_SYNC_FAILED +import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.MODULE_UPGRADE_FAILED + import ch.qos.logback.classic.Level import ch.qos.logback.classic.Logger import ch.qos.logback.classic.spi.ILoggingEvent @@ -31,9 +35,6 @@ import com.hazelcast.map.IMap import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.onap.cps.ncmp.api.impl.events.lcm.LcmEventsCmHandleStateHandler -import org.onap.cps.ncmp.api.impl.inventory.sync.ModuleSyncService -import org.onap.cps.ncmp.api.impl.inventory.sync.ModuleSyncTasks -import org.onap.cps.ncmp.api.impl.inventory.sync.SyncUtils import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle import org.onap.cps.ncmp.api.impl.inventory.CmHandleState import org.onap.cps.ncmp.api.impl.inventory.CompositeState @@ -62,7 +63,7 @@ class ModuleSyncTasksSpec extends Specification { def mockInventoryPersistence = Mock(InventoryPersistence) - def mockSyncUtils = Mock(SyncUtils) + def mockSyncUtils = Mock(ModuleOperationsUtils) def mockModuleSyncService = Mock(ModuleSyncService) @@ -79,8 +80,8 @@ class ModuleSyncTasksSpec extends Specification { def 'Module Sync ADVISED cm handles.'() { given: 'cm handles in an ADVISED state' - def cmHandle1 = advisedCmHandleAsDataNode('cm-handle-1') - def cmHandle2 = advisedCmHandleAsDataNode('cm-handle-2') + def cmHandle1 = cmHandleAsDataNodeByIdAndState('cm-handle-1', CmHandleState.ADVISED) + def cmHandle2 = cmHandleAsDataNodeByIdAndState('cm-handle-2', CmHandleState.ADVISED) and: 'the inventory persistence cm handle returns a ADVISED state for the any handle' mockInventoryPersistence.getCmHandleState(_) >> new CompositeState(cmHandleState: CmHandleState.ADVISED) when: 'module sync poll is executed' @@ -101,7 +102,7 @@ class ModuleSyncTasksSpec extends Specification { def 'Module Sync ADVISED cm handle with failure during sync.'() { given: 'a cm handle in an ADVISED state' - def cmHandle = advisedCmHandleAsDataNode('cm-handle') + def cmHandle = cmHandleAsDataNodeByIdAndState('cm-handle', CmHandleState.ADVISED) and: 'the inventory persistence cm handle returns a ADVISED state for the cm handle' def cmHandleState = new CompositeState(cmHandleState: CmHandleState.ADVISED) 1 * mockInventoryPersistence.getCmHandleState('cm-handle') >> cmHandleState @@ -110,7 +111,7 @@ class ModuleSyncTasksSpec extends Specification { when: 'module sync is executed' objectUnderTest.performModuleSync([cmHandle], batchCount) then: 'update lock reason, details and attempts is invoked' - 1 * mockSyncUtils.updateLockReasonDetailsAndAttempts(cmHandleState, LockReasonCategory.MODULE_SYNC_FAILED, 'some exception') + 1 * mockSyncUtils.updateLockReasonDetailsAndAttempts(cmHandleState, MODULE_SYNC_FAILED, 'some exception') and: 'the state handler is called to update the state to LOCKED' 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(_) >> { args -> assertBatch(args, ['cm-handle'], CmHandleState.LOCKED) @@ -119,6 +120,25 @@ class ModuleSyncTasksSpec extends Specification { assert batchCount.get() == 4 } + def 'Failed cm handle during #scenario.'() { + given: 'a cm handle in LOCKED state' + def cmHandle = cmHandleAsDataNodeByIdAndState('cm-handle', CmHandleState.LOCKED) + and: 'the inventory persistence cm handle returns a LOCKED state with reason for the cm handle' + def expectedCmHandleState = new CompositeState(cmHandleState: cmHandleState, lockReason: CompositeState + .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') } + when: 'module sync is executed' + objectUnderTest.performModuleSync([cmHandle], batchCount) + then: 'update lock reason, details and attempts is invoked' + 1 * mockSyncUtils.updateLockReasonDetailsAndAttempts(expectedCmHandleState, expectedLockReasonCategory, 'some exception') + where: + scenario | cmHandleState | lockReasonCategory | lockReasonDetails || expectedLockReasonCategory + 'module upgrade' | CmHandleState.LOCKED | MODULE_UPGRADE_FAILED | 'Upgrade to ModuleSetTag: some-module-set-tag' || MODULE_UPGRADE_FAILED + 'module sync' | CmHandleState.LOCKED | MODULE_SYNC_FAILED | 'some lock details' || MODULE_SYNC_FAILED + } + def 'Reset failed CM Handles #scenario.'() { given: 'cm handles in an locked state' def lockedState = new CompositeStateBuilder().withCmHandleState(CmHandleState.LOCKED) @@ -147,7 +167,7 @@ class ModuleSyncTasksSpec extends Specification { def 'Module Sync ADVISED cm handle without entry in progress map.'() { given: 'cm handles in an ADVISED state' - def cmHandle1 = advisedCmHandleAsDataNode('cm-handle-1') + def cmHandle1 = cmHandleAsDataNodeByIdAndState('cm-handle-1', CmHandleState.ADVISED) and: 'the inventory persistence cm handle returns a ADVISED state for the any handle' mockInventoryPersistence.getCmHandleState(_) >> new CompositeState(cmHandleState: CmHandleState.ADVISED) and: 'entry in progress map for other cm handle' @@ -183,8 +203,8 @@ class ModuleSyncTasksSpec extends Specification { assert loggingEvent == null } - def advisedCmHandleAsDataNode(cmHandleId) { - return new DataNode(anchorName: cmHandleId, leaves: ['id': cmHandleId, 'cm-handle-state': 'ADVISED']) + def cmHandleAsDataNodeByIdAndState(cmHandleId, cmHandleState) { + return new DataNode(anchorName: cmHandleId, leaves: ['id': cmHandleId, 'cm-handle-state': cmHandleState]) } def assertYamgModelCmHandleArgument(args, expectedCmHandleId) { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncWatchdogSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncWatchdogSpec.groovy index 7425a52ff1..1752a17a19 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncWatchdogSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncWatchdogSpec.groovy @@ -22,9 +22,6 @@ package org.onap.cps.ncmp.api.impl.inventory.sync import com.hazelcast.map.IMap -import org.onap.cps.ncmp.api.impl.inventory.sync.ModuleSyncTasks -import org.onap.cps.ncmp.api.impl.inventory.sync.ModuleSyncWatchdog -import org.onap.cps.ncmp.api.impl.inventory.sync.SyncUtils import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle import org.onap.cps.ncmp.api.impl.inventory.sync.executor.AsyncTaskExecutor import java.util.concurrent.ArrayBlockingQueue @@ -33,7 +30,7 @@ import spock.lang.Specification class ModuleSyncWatchdogSpec extends Specification { - def mockSyncUtils = Mock(SyncUtils) + def mockSyncUtils = Mock(ModuleOperationsUtils) def static testQueueCapacity = 50 + 2 * ModuleSyncWatchdog.MODULE_SYNC_BATCH_SIZE diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/AbstractModelLoaderSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/AbstractModelLoaderSpec.groovy index 28eae8df14..e5ed21f1c4 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/AbstractModelLoaderSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/AbstractModelLoaderSpec.groovy @@ -49,7 +49,7 @@ class AbstractModelLoaderSpec extends Specification { def loggingListAppender void setup() { - yangResourceToContentMap = objectUnderTest.createYangResourceToContentMap('subscription.yang') + yangResourceToContentMap = objectUnderTest.createYangResourcesToContentMap('subscription.yang') logger.setLevel(Level.DEBUG) loggingListAppender = new ListAppender() logger.addAppender(loggingListAppender) @@ -58,7 +58,7 @@ class AbstractModelLoaderSpec extends Specification { } void cleanup() { - ((Logger) LoggerFactory.getLogger(SubscriptionModelLoader.class)).detachAndStopAllAppenders() + ((Logger) LoggerFactory.getLogger(CmDataSubscriptionModelLoader.class)).detachAndStopAllAppenders() applicationContext.close() } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/SubscriptionModelLoaderSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/CmDataSubscriptionModelLoaderSpec.groovy index 3486316a0a..06627129a9 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/SubscriptionModelLoaderSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/CmDataSubscriptionModelLoaderSpec.groovy @@ -34,21 +34,21 @@ import org.springframework.boot.context.event.ApplicationReadyEvent import org.springframework.context.annotation.AnnotationConfigApplicationContext import spock.lang.Specification -class SubscriptionModelLoaderSpec extends Specification { +class CmDataSubscriptionModelLoaderSpec extends Specification { def mockCpsAdminService = Mock(CpsAdminService) def mockCpsModuleService = Mock(CpsModuleService) def mockCpsDataService = Mock(CpsDataService) - def objectUnderTest = new SubscriptionModelLoader(mockCpsAdminService, mockCpsModuleService, mockCpsDataService) + def objectUnderTest = new CmDataSubscriptionModelLoader(mockCpsAdminService, mockCpsModuleService, mockCpsDataService) def applicationContext = new AnnotationConfigApplicationContext() - def expectedYangResourceToContentMap + def expectedYangResourcesToContentMap def logger = (Logger) LoggerFactory.getLogger(objectUnderTest.class) def loggingListAppender void setup() { - expectedYangResourceToContentMap = objectUnderTest.createYangResourceToContentMap('subscription.yang') + expectedYangResourcesToContentMap = objectUnderTest.createYangResourcesToContentMap('cm-data-subscriptions@2023-11-13.yang') logger.setLevel(Level.DEBUG) loggingListAppender = new ListAppender() logger.addAppender(loggingListAppender) @@ -57,7 +57,7 @@ class SubscriptionModelLoaderSpec extends Specification { } void cleanup() { - ((Logger) LoggerFactory.getLogger(SubscriptionModelLoader.class)).detachAndStopAllAppenders() + ((Logger) LoggerFactory.getLogger(CmDataSubscriptionModelLoader.class)).detachAndStopAllAppenders() applicationContext.close() } @@ -69,11 +69,11 @@ class SubscriptionModelLoaderSpec extends Specification { when: 'the application is ready' objectUnderTest.onApplicationEvent(Mock(ApplicationReadyEvent)) then: 'the module service to create schema set is called once' - 1 * mockCpsModuleService.createSchemaSet(NCMP_DATASPACE_NAME, 'subscriptions', expectedYangResourceToContentMap) + 1 * mockCpsModuleService.createSchemaSet(NCMP_DATASPACE_NAME, 'cm-data-subscriptions', expectedYangResourcesToContentMap) and: 'the admin service to create an anchor set is called once' - 1 * mockCpsAdminService.createAnchor(NCMP_DATASPACE_NAME, 'subscriptions', 'AVC-Subscriptions') + 1 * mockCpsAdminService.createAnchor(NCMP_DATASPACE_NAME, 'cm-data-subscriptions', 'cm-data-subscriptions') and: 'the data service to create a top level datanode is called once' - 1 * mockCpsDataService.saveData(NCMP_DATASPACE_NAME, 'AVC-Subscriptions', '{"subscription-registry":{}}', _) + 1 * mockCpsDataService.saveData(NCMP_DATASPACE_NAME, 'cm-data-subscriptions', '{"datastores":{}}', _) } def 'Subscription model loader disabled.' () { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/InventoryModelLoaderSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/InventoryModelLoaderSpec.groovy index 16ab0b8846..43e0f69b3f 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/InventoryModelLoaderSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/InventoryModelLoaderSpec.groovy @@ -49,7 +49,7 @@ class InventoryModelLoaderSpec extends Specification { def loggingListAppender void setup() { - expectedYangResourceToContentMap = objectUnderTest.createYangResourceToContentMap('dmi-registry@2023-08-23.yang') + expectedYangResourceToContentMap = objectUnderTest.createYangResourcesToContentMap('dmi-registry@2023-08-23.yang') logger.setLevel(Level.DEBUG) loggingListAppender = new ListAppender() logger.addAppender(loggingListAppender) @@ -58,7 +58,7 @@ class InventoryModelLoaderSpec extends Specification { } void cleanup() { - ((Logger) LoggerFactory.getLogger(SubscriptionModelLoader.class)).detachAndStopAllAppenders() + ((Logger) LoggerFactory.getLogger(CmDataSubscriptionModelLoader.class)).detachAndStopAllAppenders() applicationContext.close() } diff --git a/docs/release-notes.rst b/docs/release-notes.rst index 02d36a867d..fbf4b34d14 100755 --- a/docs/release-notes.rst +++ b/docs/release-notes.rst @@ -39,6 +39,11 @@ Release Data Bug Fixes --------- +Features +-------- + - CPS-Temporal is no longer supported and any related documentation has been removed. + + Version: 3.4.0 ============== diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/ResourceMeterPerfTest.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/ResourceMeterPerfTest.groovy new file mode 100644 index 0000000000..c42bfd7be6 --- /dev/null +++ b/integration-test/src/test/groovy/org/onap/cps/integration/ResourceMeterPerfTest.groovy @@ -0,0 +1,83 @@ +/* + * ============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.integration + +import java.util.concurrent.TimeUnit +import spock.lang.Specification + +class ResourceMeterPerfTest extends Specification { + + final int MEGABYTE = 1_000_000 + + def resourceMeter = new ResourceMeter() + + def 'ResourceMeter accurately measures duration'() { + when: 'we measure how long a known operation takes' + resourceMeter.start() + TimeUnit.SECONDS.sleep(2) + resourceMeter.stop() + then: 'ResourceMeter reports a duration within 10ms of the expected duration' + assert resourceMeter.getTotalTimeInSeconds() >= 2 + assert resourceMeter.getTotalTimeInSeconds() <= 2.01 + } + + def 'ResourceMeter reports memory usage when allocating a large byte array'() { + when: 'the resource meter is started' + resourceMeter.start() + and: 'some memory is allocated' + byte[] array = new byte[50 * MEGABYTE] + and: 'the resource meter is stopped' + resourceMeter.stop() + then: 'the reported memory usage is close to the amount of memory allocated' + assert resourceMeter.getTotalMemoryUsageInMB() >= 50 + assert resourceMeter.getTotalMemoryUsageInMB() <= 55 + } + + def 'ResourceMeter measures PEAK memory usage when garbage collector runs'() { + when: 'the resource meter is started' + resourceMeter.start() + and: 'some memory is allocated' + byte[] array = new byte[50 * MEGABYTE] + and: 'the memory is garbage collected' + array = null + ResourceMeter.performGcAndWait() + and: 'the resource meter is stopped' + resourceMeter.stop() + then: 'the reported memory usage is close to the peak amount of memory allocated' + assert resourceMeter.getTotalMemoryUsageInMB() >= 50 + assert resourceMeter.getTotalMemoryUsageInMB() <= 55 + } + + def 'ResourceMeter measures memory increase only during measurement'() { + given: '50 megabytes is allocated before measurement' + byte[] arrayBefore = new byte[50 * MEGABYTE] + when: 'memory is allocated during measurement' + resourceMeter.start() + byte[] arrayDuring = new byte[40 * MEGABYTE] + resourceMeter.stop() + and: '50 megabytes is allocated after measurement' + byte[] arrayAfter = new byte[50 * MEGABYTE] + then: 'the reported memory usage is close to the amount allocated DURING measurement' + assert resourceMeter.getTotalMemoryUsageInMB() >= 40 + assert resourceMeter.getTotalMemoryUsageInMB() <= 45 + } + +} diff --git a/integration-test/src/test/java/org/onap/cps/integration/ResourceMeter.java b/integration-test/src/test/java/org/onap/cps/integration/ResourceMeter.java index c7d96c4c2b..f8a2ecb4df 100644 --- a/integration-test/src/test/java/org/onap/cps/integration/ResourceMeter.java +++ b/integration-test/src/test/java/org/onap/cps/integration/ResourceMeter.java @@ -20,6 +20,10 @@ package org.onap.cps.integration; +import java.lang.management.GarbageCollectorMXBean; +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryPoolMXBean; +import java.lang.management.MemoryType; import org.springframework.util.StopWatch; /** @@ -34,8 +38,9 @@ public class ResourceMeter { * Start measurement. */ public void start() { - System.gc(); - memoryUsedBefore = getCurrentMemoryUsage(); + performGcAndWait(); + resetPeakHeapUsage(); + memoryUsedBefore = getPeakHeapUsage(); stopWatch.start(); } @@ -44,7 +49,7 @@ public class ResourceMeter { */ public void stop() { stopWatch.stop(); - memoryUsedAfter = getCurrentMemoryUsage(); + memoryUsedAfter = getPeakHeapUsage(); } /** @@ -63,8 +68,30 @@ public class ResourceMeter { return (memoryUsedAfter - memoryUsedBefore) / 1_000_000.0; } - private static long getCurrentMemoryUsage() { - return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); + static void performGcAndWait() { + final long gcCountBefore = getGcCount(); + System.gc(); + while (getGcCount() == gcCountBefore) {} + } + + private static long getGcCount() { + return ManagementFactory.getGarbageCollectorMXBeans().stream() + .mapToLong(GarbageCollectorMXBean::getCollectionCount) + .filter(gcCount -> gcCount != -1) + .sum(); + } + + private static long getPeakHeapUsage() { + return ManagementFactory.getMemoryPoolMXBeans().stream() + .filter(pool -> pool.getType() == MemoryType.HEAP) + .mapToLong(pool -> pool.getPeakUsage().getUsed()) + .sum(); + } + + private static void resetPeakHeapUsage() { + ManagementFactory.getMemoryPoolMXBeans().stream() + .filter(pool -> pool.getType() == MemoryType.HEAP) + .forEach(MemoryPoolMXBean::resetPeakUsage); } } |