diff options
17 files changed, 405 insertions, 83 deletions
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandlerQueryServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandlerQueryServiceImpl.java index f8836e6bf8..1674c52fc9 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandlerQueryServiceImpl.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandlerQueryServiceImpl.java @@ -21,6 +21,7 @@ package org.onap.cps.ncmp.api.impl; import static org.onap.cps.ncmp.api.impl.utils.YangDataConverter.convertYangModelCmHandleToNcmpServiceCmHandle; +import static org.onap.cps.spi.FetchDescendantsOption.FETCH_DIRECT_CHILDREN_ONLY; import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS; import static org.onap.cps.utils.CmHandleQueryRestParametersValidator.validateCpsPathConditionProperties; import static org.onap.cps.utils.CmHandleQueryRestParametersValidator.validateModuleNameConditionProperties; @@ -68,14 +69,14 @@ public class NetworkCmProxyCmHandlerQueryServiceImpl implements NetworkCmProxyCm */ @Override public Set<NcmpServiceCmHandle> queryCmHandles( - final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) { + final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) { if (cmHandleQueryServiceParameters.getCmHandleQueryParameters().isEmpty()) { return getAllCmHandles(); } final Map<String, NcmpServiceCmHandle> combinedQueryResult = executeInventoryQueries( - cmHandleQueryServiceParameters); + cmHandleQueryServiceParameters); return new HashSet<>(combineWithModuleNameQuery(cmHandleQueryServiceParameters, combinedQueryResult).values()); } @@ -88,17 +89,17 @@ public class NetworkCmProxyCmHandlerQueryServiceImpl implements NetworkCmProxyCm */ @Override public Set<String> queryCmHandleIds( - final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) { + final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) { if (cmHandleQueryServiceParameters.getCmHandleQueryParameters().isEmpty()) { return getAllCmHandleIds(); } final Map<String, NcmpServiceCmHandle> combinedQueryResult = executeInventoryQueries( - cmHandleQueryServiceParameters); + cmHandleQueryServiceParameters); final Collection<String> moduleNamesForQuery = - getModuleNamesForQuery(cmHandleQueryServiceParameters.getCmHandleQueryParameters()); + getModuleNamesForQuery(cmHandleQueryServiceParameters.getCmHandleQueryParameters()); if (moduleNamesForQuery.isEmpty()) { return combinedQueryResult.keySet(); } @@ -113,10 +114,10 @@ public class NetworkCmProxyCmHandlerQueryServiceImpl implements NetworkCmProxyCm } private Map<String, NcmpServiceCmHandle> combineWithModuleNameQuery( - final CmHandleQueryServiceParameters cmHandleQueryServiceParameters, - final Map<String, NcmpServiceCmHandle> previousQueryResult) { + final CmHandleQueryServiceParameters cmHandleQueryServiceParameters, + final Map<String, NcmpServiceCmHandle> previousQueryResult) { final Collection<String> moduleNamesForQuery = - getModuleNamesForQuery(cmHandleQueryServiceParameters.getCmHandleQueryParameters()); + getModuleNamesForQuery(cmHandleQueryServiceParameters.getCmHandleQueryParameters()); if (moduleNamesForQuery.isEmpty()) { return previousQueryResult; } @@ -138,7 +139,7 @@ public class NetworkCmProxyCmHandlerQueryServiceImpl implements NetworkCmProxyCm } private Map<String, NcmpServiceCmHandle> executeInventoryQueries( - final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) { + final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) { final Map<String, String> cpsPath = getCpsPath(cmHandleQueryServiceParameters.getCmHandleQueryParameters()); if (!validateCpsPathConditionProperties(cpsPath)) { return Collections.emptyMap(); @@ -149,13 +150,13 @@ public class NetworkCmProxyCmHandlerQueryServiceImpl implements NetworkCmProxyCm } else { try { cpsPathQueryResult = cmHandleQueries.queryCmHandleDataNodesByCpsPath( - cpsPath.get("cpsPath"), INCLUDE_ALL_DESCENDANTS) - .stream().map(this::createNcmpServiceCmHandle) - .collect(Collectors.toMap(NcmpServiceCmHandle::getCmHandleId, - Function.identity())); + cpsPath.get("cpsPath"), INCLUDE_ALL_DESCENDANTS) + .stream().map(this::createNcmpServiceCmHandle) + .collect(Collectors.toMap(NcmpServiceCmHandle::getCmHandleId, + Function.identity())); } catch (final PathParsingException pathParsingException) { throw new DataValidationException(pathParsingException.getMessage(), pathParsingException.getDetails(), - pathParsingException); + pathParsingException); } if (cpsPathQueryResult.isEmpty()) { return Collections.emptyMap(); @@ -163,9 +164,9 @@ public class NetworkCmProxyCmHandlerQueryServiceImpl implements NetworkCmProxyCm } final Map<String, String> publicPropertyQueryPairs = - getPublicPropertyPairs(cmHandleQueryServiceParameters.getCmHandleQueryParameters()); + getPublicPropertyPairs(cmHandleQueryServiceParameters.getCmHandleQueryParameters()); final Map<String, NcmpServiceCmHandle> propertiesQueryResult = publicPropertyQueryPairs.isEmpty() - ? NO_QUERY_TO_EXECUTE : cmHandleQueries.queryCmHandlePublicProperties(publicPropertyQueryPairs); + ? NO_QUERY_TO_EXECUTE : cmHandleQueries.queryCmHandlePublicProperties(publicPropertyQueryPairs); return cmHandleQueries.combineCmHandleQueries(cpsPathQueryResult, propertiesQueryResult); } @@ -190,14 +191,14 @@ public class NetworkCmProxyCmHandlerQueryServiceImpl implements NetworkCmProxyCm private Map<String, String> getCpsPath(final List<ConditionProperties> conditionProperties) { final Map<String, String> result = new HashMap<>(); getConditions(conditionProperties, ValidQueryProperties.WITH_CPS_PATH.getQueryProperty()).forEach( - result::putAll); + result::putAll); return result; } private Map<String, String> getPublicPropertyPairs(final List<ConditionProperties> conditionProperties) { final Map<String, String> result = new HashMap<>(); getConditions(conditionProperties, - ValidQueryProperties.HAS_ALL_PROPERTIES.getQueryProperty()).forEach(result::putAll); + ValidQueryProperties.HAS_ALL_PROPERTIES.getQueryProperty()).forEach(result::putAll); return result; } @@ -213,17 +214,17 @@ public class NetworkCmProxyCmHandlerQueryServiceImpl implements NetworkCmProxyCm private Set<NcmpServiceCmHandle> getAllCmHandles() { return inventoryPersistence.getDataNode("/dmi-registry") - .getChildDataNodes().stream().map(this::createNcmpServiceCmHandle).collect(Collectors.toSet()); + .getChildDataNodes().stream().map(this::createNcmpServiceCmHandle).collect(Collectors.toSet()); } private Set<String> getAllCmHandleIds() { - return inventoryPersistence.getDataNode("/dmi-registry") - .getChildDataNodes().stream().map(dataNode -> dataNode.getLeaves().get("id").toString()) - .collect(Collectors.toSet()); + return inventoryPersistence.getDataNode("/dmi-registry", FETCH_DIRECT_CHILDREN_ONLY) + .getChildDataNodes().stream().map(dataNode -> dataNode.getLeaves().get("id").toString()) + .collect(Collectors.toSet()); } private NcmpServiceCmHandle createNcmpServiceCmHandle(final DataNode dataNode) { return convertYangModelCmHandleToNcmpServiceCmHandle(YangDataConverter - .convertCmHandleToYangModel(dataNode, dataNode.getLeaves().get("id").toString())); + .convertCmHandleToYangModel(dataNode, dataNode.getLeaves().get("id").toString())); } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/embeddedcache/SynchronizationCacheConfig.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/embeddedcache/SynchronizationCacheConfig.java index c89388b291..5154be7990 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/embeddedcache/SynchronizationCacheConfig.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/embeddedcache/SynchronizationCacheConfig.java @@ -28,7 +28,6 @@ import com.hazelcast.core.Hazelcast; import com.hazelcast.core.HazelcastInstance; import com.hazelcast.map.IMap; import java.util.concurrent.BlockingQueue; -import java.util.concurrent.TimeUnit; import org.onap.cps.spi.model.DataNode; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -39,11 +38,12 @@ import org.springframework.context.annotation.Configuration; @Configuration public class SynchronizationCacheConfig { + public static final int MODULE_SYNC_STARTED_TTL_SECS = 60; + public static final int DATA_SYNC_SEMAPHORE_TTL_SECS = 1800; + private static final QueueConfig commonQueueConfig = createQueueConfig(); - private static final MapConfig moduleSyncStartedConfig = - createMapConfig("moduleSyncStartedConfig", TimeUnit.MINUTES.toSeconds(1)); - private static final MapConfig dataSyncSemaphoresConfig = - createMapConfig("dataSyncSemaphoresConfig", TimeUnit.MINUTES.toSeconds(30)); + private static final MapConfig moduleSyncStartedConfig = createMapConfig("moduleSyncStartedConfig"); + private static final MapConfig dataSyncSemaphoresConfig = createMapConfig("dataSyncSemaphoresConfig"); /** * Module Sync Distributed Queue Instance. @@ -102,11 +102,10 @@ public class SynchronizationCacheConfig { return commonQueueConfig; } - private static MapConfig createMapConfig(final String configName, final long timeToLiveSeconds) { + private static MapConfig createMapConfig(final String configName) { final MapConfig mapConfig = new MapConfig(configName); mapConfig.setBackupCount(3); mapConfig.setAsyncBackupCount(3); - mapConfig.setTimeToLiveSeconds((int) timeToLiveSeconds); return mapConfig; } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistence.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistence.java index bfc3a9ac06..b29825e7c0 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistence.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistence.java @@ -23,6 +23,7 @@ package org.onap.cps.ncmp.api.inventory; import java.util.Collection; import java.util.Map; import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; +import org.onap.cps.spi.FetchDescendantsOption; import org.onap.cps.spi.model.Anchor; import org.onap.cps.spi.model.DataNode; import org.onap.cps.spi.model.ModuleDefinition; @@ -114,6 +115,15 @@ public interface InventoryPersistence { DataNode getDataNode(String xpath); /** + * Get data node via xpath. + * + * @param xpath xpath + * @param fetchDescendantsOption fetch descendants option + * @return data node + */ + DataNode getDataNode(String xpath, FetchDescendantsOption fetchDescendantsOption); + + /** * Get data node of given cm handle. * * @param cmHandleId cmHandle ID diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistenceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistenceImpl.java index 99edfdb0f1..eed47eddab 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistenceImpl.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistenceImpl.java @@ -76,18 +76,18 @@ public class InventoryPersistenceImpl implements InventoryPersistence { @Override public CompositeState getCmHandleState(final String cmHandleId) { final DataNode stateAsDataNode = cpsDataService.getDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, - String.format(CM_HANDLE_XPATH_TEMPLATE, cmHandleId) + "/state", - FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS); + String.format(CM_HANDLE_XPATH_TEMPLATE, cmHandleId) + "/state", + FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS); return new CompositeStateBuilder().fromDataNode(stateAsDataNode).build(); } @Override public void saveCmHandleState(final String cmHandleId, final CompositeState compositeState) { final String cmHandleJsonData = String.format("{\"state\":%s}", - jsonObjectMapper.asJsonString(compositeState)); + jsonObjectMapper.asJsonString(compositeState)); cpsDataService.updateDataNodeAndDescendants(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, - String.format(CM_HANDLE_XPATH_TEMPLATE, cmHandleId), - cmHandleJsonData, OffsetDateTime.now()); + String.format(CM_HANDLE_XPATH_TEMPLATE, cmHandleId), + cmHandleJsonData, OffsetDateTime.now()); } @Override @@ -153,8 +153,13 @@ public class InventoryPersistenceImpl implements InventoryPersistence { @Override public DataNode getDataNode(final String xpath) { + return getDataNode(xpath, INCLUDE_ALL_DESCENDANTS); + } + + @Override + public DataNode getDataNode(final String xpath, final FetchDescendantsOption fetchDescendantsOption) { return cpsDataPersistenceService.getDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, - xpath, INCLUDE_ALL_DESCENDANTS); + xpath, fetchDescendantsOption); } @Override @@ -164,7 +169,7 @@ public class InventoryPersistenceImpl implements InventoryPersistence { @Override public Collection<Anchor> queryAnchors(final Collection<String> moduleNamesForQuery) { - return cpsAdminPersistenceService.queryAnchors(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, moduleNamesForQuery); + return cpsAdminPersistenceService.queryAnchors(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, moduleNamesForQuery); } @Override diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/DataSyncWatchdog.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/DataSyncWatchdog.java index 9336c3b218..9fa75a0d07 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/DataSyncWatchdog.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/DataSyncWatchdog.java @@ -20,12 +20,14 @@ package org.onap.cps.ncmp.api.inventory.sync; +import com.hazelcast.map.IMap; import java.time.OffsetDateTime; -import java.util.Map; +import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.onap.cps.api.CpsDataService; +import org.onap.cps.ncmp.api.impl.config.embeddedcache.SynchronizationCacheConfig; import org.onap.cps.ncmp.api.inventory.CompositeState; import org.onap.cps.ncmp.api.inventory.DataStoreSyncState; import org.onap.cps.ncmp.api.inventory.InventoryPersistence; @@ -46,7 +48,7 @@ public class DataSyncWatchdog { private final SyncUtils syncUtils; - private final Map<String, Boolean> dataSyncSemaphores; + private final IMap<String, Boolean> dataSyncSemaphores; /** * Execute Cm Handle poll which queries the cm handle state in 'READY' and Operational Datastore Sync State in @@ -92,6 +94,7 @@ public class DataSyncWatchdog { } private boolean hasPushedIntoSemaphoreMap(final String cmHandleId) { - return dataSyncSemaphores.putIfAbsent(cmHandleId, DATA_SYNC_IN_PROGRESS) == null; + return dataSyncSemaphores.putIfAbsent(cmHandleId, DATA_SYNC_IN_PROGRESS, + SynchronizationCacheConfig.DATA_SYNC_SEMAPHORE_TTL_SECS, TimeUnit.SECONDS) == null; } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncWatchdog.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncWatchdog.java index b96889fc58..f629b71d26 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncWatchdog.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncWatchdog.java @@ -31,6 +31,7 @@ import java.util.concurrent.atomic.AtomicInteger; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.onap.cps.ncmp.api.impl.config.embeddedcache.SynchronizationCacheConfig; import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; import org.onap.cps.ncmp.api.inventory.sync.executor.AsyncTaskExecutor; import org.onap.cps.spi.model.DataNode; @@ -117,8 +118,9 @@ public class ModuleSyncWatchdog { log.debug("nextBatchCandidates size : {}", nextBatchCandidates.size()); for (final DataNode batchCandidate : nextBatchCandidates) { final String cmHandleId = String.valueOf(batchCandidate.getLeaves().get("id")); - final boolean alreadyAddedToInProgressMap = VALUE_FOR_HAZELCAST_IN_PROGRESS_MAP - .equals(moduleSyncStartedOnCmHandles.putIfAbsent(cmHandleId, VALUE_FOR_HAZELCAST_IN_PROGRESS_MAP)); + final boolean alreadyAddedToInProgressMap = VALUE_FOR_HAZELCAST_IN_PROGRESS_MAP.equals( + moduleSyncStartedOnCmHandles.putIfAbsent(cmHandleId, VALUE_FOR_HAZELCAST_IN_PROGRESS_MAP, + SynchronizationCacheConfig.MODULE_SYNC_STARTED_TTL_SECS, TimeUnit.SECONDS)); if (alreadyAddedToInProgressMap) { log.debug("module sync for {} already in progress by other instance", cmHandleId); } else { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandlerQueryServiceSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandlerQueryServiceSpec.groovy index f76316f9cc..eea53e82d9 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandlerQueryServiceSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandlerQueryServiceSpec.groovy @@ -153,7 +153,9 @@ class NetworkCmProxyCmHandlerQueryServiceSpec extends Specification { def 'Retrieve cm handles when the query is empty.'() { given: 'We use an empty query' def cmHandleQueryParameters = new CmHandleQueryServiceParameters() - and: 'the inventory persistence returns the dmi registry datanode' + and: 'the inventory persistence returns the dmi registry datanode with just ids' + inventoryPersistence.getDataNode("/dmi-registry", FetchDescendantsOption.FETCH_DIRECT_CHILDREN_ONLY) >> dmiRegistry + and: 'the inventory persistence returns the dmi registry datanode with data' inventoryPersistence.getDataNode("/dmi-registry") >> dmiRegistry when: 'the query is executed for both cm handle ids and details' def returnedCmHandlesJustIds = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters) diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/SynchronizationCacheConfigSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/SynchronizationCacheConfigSpec.groovy index 4cfc02b9e7..c16d6b69b6 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/SynchronizationCacheConfigSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/SynchronizationCacheConfigSpec.groovy @@ -28,6 +28,7 @@ import org.springframework.boot.test.context.SpringBootTest import org.springframework.test.context.ContextConfiguration import spock.lang.Specification import java.util.concurrent.BlockingQueue +import java.util.concurrent.TimeUnit @SpringBootTest @ContextConfiguration(classes = [SynchronizationCacheConfig]) @@ -40,7 +41,7 @@ class SynchronizationCacheConfigSpec extends Specification { private IMap<String, Object> moduleSyncStartedOnCmHandles @Autowired - private Map<String, Boolean> dataSyncSemaphores + private IMap<String, Boolean> dataSyncSemaphores def 'Embedded (hazelcast) Caches for Module and Data Sync.'() { expect: 'system is able to create an instance of the Module Sync Work Queue' @@ -54,4 +55,36 @@ class SynchronizationCacheConfigSpec extends Specification { and: 'they have the correct names (in any order)' assert Hazelcast.allHazelcastInstances.name.containsAll('moduleSyncWorkQueue', 'moduleSyncStartedOnCmHandles', 'dataSyncSemaphores' ) } + + def 'Verify configs for Distributed objects'(){ + given: 'the Module Sync Work Queue config' + def queueConfig = Hazelcast.getHazelcastInstanceByName('moduleSyncWorkQueue').config.queueConfigs.get('defaultQueueConfig') + and: 'the Module Sync Started Cm Handle Map config' + def moduleSyncStartedOnCmHandlesConfig = Hazelcast.getHazelcastInstanceByName('moduleSyncStartedOnCmHandles').config.mapConfigs.get('moduleSyncStartedConfig') + and: 'the Data Sync Semaphores Map config' + def dataSyncSemaphoresConfig = Hazelcast.getHazelcastInstanceByName('dataSyncSemaphores').config.mapConfigs.get('dataSyncSemaphoresConfig') + expect: 'system created instance with correct config of Module Sync Work Queue' + assert queueConfig.backupCount == 3 + assert queueConfig.asyncBackupCount == 3 + and: 'Module Sync Started Cm Handle Map has the correct settings' + assert moduleSyncStartedOnCmHandlesConfig.backupCount == 3 + assert moduleSyncStartedOnCmHandlesConfig.asyncBackupCount == 3 + and: 'Data Sync Semaphore Map has the correct settings' + assert dataSyncSemaphoresConfig.backupCount == 3 + assert dataSyncSemaphoresConfig.asyncBackupCount == 3 + } + + def 'Time to Live Verify for Module Sync and Data Sync Semaphore'() { + when: 'the keys are inserted with a TTL' + moduleSyncStartedOnCmHandles.put('testKeyModuleSync', 'toBeExpired' as Object, 1000, TimeUnit.MILLISECONDS) + dataSyncSemaphores.put('testKeyDataSync', Boolean.TRUE, 1000, TimeUnit.MILLISECONDS) + then: 'the entries are present in the map' + assert moduleSyncStartedOnCmHandles.get('testKeyModuleSync') != null + assert dataSyncSemaphores.get('testKeyDataSync') != null + and: 'we wait for the key expiration' + sleep(1500) + and: 'the keys should be expired as TTL elapsed' + assert moduleSyncStartedOnCmHandles.get('testKeyModuleSync') == null + assert dataSyncSemaphores.get('testKeyDataSync') == null + } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/DataSyncWatchdogSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/DataSyncWatchdogSpec.groovy index 605381970d..707f3ea3ea 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/DataSyncWatchdogSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/DataSyncWatchdogSpec.groovy @@ -20,6 +20,7 @@ package org.onap.cps.ncmp.api.inventory.sync +import com.hazelcast.map.IMap import org.onap.cps.api.CpsDataService import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle import org.onap.cps.ncmp.api.inventory.CmHandleState @@ -27,8 +28,6 @@ import org.onap.cps.ncmp.api.inventory.CompositeState import org.onap.cps.ncmp.api.inventory.InventoryPersistence import org.onap.cps.ncmp.api.inventory.DataStoreSyncState import spock.lang.Specification -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.ConcurrentMap class DataSyncWatchdogSpec extends Specification { @@ -38,11 +37,11 @@ class DataSyncWatchdogSpec extends Specification { def mockSyncUtils = Mock(SyncUtils) - def stubbedMap = Stub(ConcurrentMap) + def mockDataSyncSemaphoreMap = Mock(IMap<String,Boolean>) def jsonString = '{"stores:bookstore":{"categories":[{"code":"01"}]}}' - def objectUnderTest = new DataSyncWatchdog(mockInventoryPersistence, mockCpsDataService, mockSyncUtils, stubbedMap as ConcurrentHashMap) + def objectUnderTest = new DataSyncWatchdog(mockInventoryPersistence, mockCpsDataService, mockSyncUtils, mockDataSyncSemaphoreMap) def compositeState = getCompositeState() diff --git a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java index c13422dc4d..ebc851a443 100644 --- a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java @@ -22,8 +22,6 @@ package org.onap.cps.spi.impl; -import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS; - import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet.Builder; import java.util.ArrayList; @@ -102,7 +100,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService @Override public void addMultipleLists(final String dataspaceName, final String anchorName, final String parentNodeXpath, - final Collection<Collection<DataNode>> newLists) { + final Collection<Collection<DataNode>> newLists) { final Collection<String> failedXpaths = new HashSet<>(); newLists.forEach(newList -> { try { @@ -119,7 +117,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService } private void addNewChildDataNode(final String dataspaceName, final String anchorName, - final String parentNodeXpath, final DataNode newChild) { + final String parentNodeXpath, final DataNode newChild) { final FragmentEntity parentFragmentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath); final FragmentEntity newChildAsFragmentEntity = convertToFragmentWithAllDescendants(parentFragmentEntity.getDataspace(), @@ -134,7 +132,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService } private void addChildrenDataNodes(final String dataspaceName, final String anchorName, final String parentNodeXpath, - final Collection<DataNode> newChildren) { + final Collection<DataNode> newChildren) { final FragmentEntity parentFragmentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath); final List<FragmentEntity> fragmentEntities = new ArrayList<>(newChildren.size()); try { @@ -154,7 +152,8 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService } private void retrySavingEachChildIndividually(final String dataspaceName, final String anchorName, - final String parentNodeXpath, final Collection<DataNode> newChildren) { + final String parentNodeXpath, + final Collection<DataNode> newChildren) { final Collection<String> failedXpaths = new HashSet<>(); for (final DataNode newChild : newChildren) { try { @@ -191,7 +190,8 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService * @return a Fragment built from current DataNode */ private FragmentEntity convertToFragmentWithAllDescendants(final DataspaceEntity dataspaceEntity, - final AnchorEntity anchorEntity, final DataNode dataNodeToBeConverted) { + final AnchorEntity anchorEntity, + final DataNode dataNodeToBeConverted) { final FragmentEntity parentFragment = toFragmentEntity(dataspaceEntity, anchorEntity, dataNodeToBeConverted); final Builder<FragmentEntity> childFragmentsImmutableSetBuilder = ImmutableSet.builder(); for (final DataNode childDataNode : dataNodeToBeConverted.getChildDataNodes()) { @@ -226,7 +226,8 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName); final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName); if (isRootXpath(xpath)) { - return fragmentRepository.findFirstRootByDataspaceAndAnchor(dataspaceEntity, anchorEntity); + return fragmentRepository.findFirstRootByDataspaceAndAnchor( + dataspaceEntity, anchorEntity); } else { final String normalizedXpath; try { @@ -235,7 +236,8 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService throw new CpsPathException(e.getMessage()); } - return fragmentRepository.getByDataspaceAndAnchorAndXpath(dataspaceEntity, anchorEntity, normalizedXpath); + return fragmentRepository.getByDataspaceAndAnchorAndXpath( + dataspaceEntity, anchorEntity, normalizedXpath); } } @@ -319,10 +321,10 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService private List<DataNode> getChildDataNodes(final FragmentEntity fragmentEntity, final FetchDescendantsOption fetchDescendantsOption) { - if (fetchDescendantsOption == INCLUDE_ALL_DESCENDANTS) { + if (fetchDescendantsOption.hasNext()) { return fragmentEntity.getChildFragments().stream() - .map(childFragmentEntity -> toDataNode(childFragmentEntity, fetchDescendantsOption)) - .collect(Collectors.toUnmodifiableList()); + .map(childFragmentEntity -> toDataNode(childFragmentEntity, fetchDescendantsOption.next())) + .collect(Collectors.toList()); } return Collections.emptyList(); } @@ -355,10 +357,11 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService final List<DataNode> dataNodes) { final Map<DataNode, FragmentEntity> dataNodeFragmentEntityMap = dataNodes.stream() - .collect(Collectors.toMap( - dataNode -> dataNode, dataNode -> getFragmentByXpath(dataspaceName, anchorName, dataNode.getXpath()))); + .collect(Collectors.toMap( + dataNode -> dataNode, + dataNode -> getFragmentByXpath(dataspaceName, anchorName, dataNode.getXpath()))); dataNodeFragmentEntityMap.forEach( - (dataNode, fragmentEntity) -> updateFragmentEntityAndDescendantsWithDataNode(fragmentEntity, dataNode)); + (dataNode, fragmentEntity) -> updateFragmentEntityAndDescendantsWithDataNode(fragmentEntity, dataNode)); try { fragmentRepository.saveAll(dataNodeFragmentEntityMap.values()); } catch (final StaleStateException staleStateException) { @@ -367,7 +370,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService } private void retryUpdateDataNodesIndividually(final String dataspaceName, final String anchorName, - final Collection<FragmentEntity> fragmentEntities) { + final Collection<FragmentEntity> fragmentEntities) { final Collection<String> failedXpaths = new HashSet<>(); fragmentEntities.forEach(dataNodeFragment -> { diff --git a/cps-ri/src/main/resources/changelog/db/changes/16-insert-cm-handle-state-forward.sql b/cps-ri/src/main/resources/changelog/db/changes/16-insert-cm-handle-state-forward.sql index 64b185f3b2..01d441f460 100644 --- a/cps-ri/src/main/resources/changelog/db/changes/16-insert-cm-handle-state-forward.sql +++ b/cps-ri/src/main/resources/changelog/db/changes/16-insert-cm-handle-state-forward.sql @@ -1,3 +1,137 @@ -create view cmHandles as select * from fragment where xpath ~* '^/dmi-registry/cm-handles\[@id=''[\w\-]+''\]$'; -insert into fragment(xpath, attributes, anchor_id, parent_id, dataspace_id, schema_node_id) select concat(xpath, '/state'), to_jsonb(concat('{"cm-handle-state": "ADVISED", "last-update-time": "', to_char(now(), 'YYYY-MM-DD"T"HH24:MI:SS.MSTZHTZM'), '"}')::json), anchor_id, id, dataspace_id, schema_node_id from cmHandles; -drop view cmHandles;
\ No newline at end of file +INSERT INTO + fragment( + xpath, + attributes, + anchor_id, + parent_id, + dataspace_id, + schema_node_id + ) +SELECT + concat(cmHandles.xpath, '/state') AS xpath, + to_jsonb( + concat( + '{"cm-handle-state": "READY", "last-update-time": "', + to_char( + now(), + 'YYYY-MM-DD"T"HH24:MI:SS.MSTZHTZM' + ), + '", "data-sync-enabled": false}' + ) :: json + ) AS attributes, + cmHandles.anchor_id, + cmHandles.id, + cmHandles.dataspace_id, + cmHandles.schema_node_id +FROM + ( + SELECT + id, + xpath, + anchor_id, + dataspace_id, + schema_node_id + FROM + fragment + WHERE + xpath ~* '^/dmi-registry/cm-handles\[@id=''[\w\-]+''\]$' + AND xpath NOT IN ( + SELECT + SUBSTRING( + xpath + FROM + '^/dmi-registry/cm-handles\[@id=''[\w\-]+''\]' + ) + FROM + fragment + WHERE + xpath ~* '^/dmi-registry/cm-handles\[@id=''[\w\-]+''\]/state$' + ) + ) AS cmHandles; +INSERT INTO + fragment( + xpath, + attributes, + anchor_id, + parent_id, + dataspace_id, + schema_node_id + ) +SELECT + concat(cmHandlesStates.xpath, '/datastores'), + to_jsonb('{}' :: json), + cmHandlesStates.anchor_id, + cmHandlesStates.id, + cmHandlesStates.dataspace_id, + cmHandlesStates.schema_node_id +FROM + ( + SELECT + id, + xpath, + anchor_id, + dataspace_id, + schema_node_id + FROM + fragment + WHERE + xpath ~* '^/dmi-registry/cm-handles\[@id=''[\w\-]+''\]/state$' + AND xpath NOT IN ( + SELECT + SUBSTRING( + xpath + FROM + '^/dmi-registry/cm-handles\[@id=''[\w\-]+''\]/state' + ) + FROM + fragment + WHERE + xpath ~* '^/dmi-registry/cm-handles\[@id=''[\w\-]+''\]/state/datastores$' + ) + ) AS cmHandlesStates; +INSERT INTO + fragment( + xpath, + attributes, + anchor_id, + parent_id, + dataspace_id, + schema_node_id + ) +SELECT + concat( + cmHandlesDatastores.xpath, + '/operational' + ), + to_jsonb( + concat('{"sync-state": "NONE_REQUESTED"}') :: json + ), + cmHandlesDatastores.anchor_id, + cmHandlesDatastores.id, + cmHandlesDatastores.dataspace_id, + cmHandlesDatastores.schema_node_id +FROM + ( + SELECT + id, + xpath, + anchor_id, + dataspace_id, + schema_node_id + FROM + fragment + WHERE + xpath ~* '^/dmi-registry/cm-handles\[@id=''[\w\-]+''\]/state/datastores$' + AND xpath NOT IN ( + SELECT + SUBSTRING( + xpath + FROM + '^/dmi-registry/cm-handles\[@id=''[\w\-]+''\]/state/datastores' + ) + FROM + fragment + WHERE + xpath ~* '^/dmi-registry/cm-handles\[@id=''[\w\-]+''\]/state/datastores/operational$' + ) + ) AS cmHandlesDatastores;
\ No newline at end of file diff --git a/cps-ri/src/main/resources/changelog/db/changes/16-insert-cm-handle-state-rollback.sql b/cps-ri/src/main/resources/changelog/db/changes/16-insert-cm-handle-state-rollback.sql index aaf05a24cb..4b006ef0e2 100644 --- a/cps-ri/src/main/resources/changelog/db/changes/16-insert-cm-handle-state-rollback.sql +++ b/cps-ri/src/main/resources/changelog/db/changes/16-insert-cm-handle-state-rollback.sql @@ -1,4 +1,4 @@ -delete from fragment where xpath ~* '^/dmi-registry/cm-handles\[@id=''[\w\-]+''\]/state/lock-reason$'; -delete from fragment where xpath ~* '^/dmi-registry/cm-handles\[@id=''[\w\-]+''\]/state/datastores/operational$'; -delete from fragment where xpath ~* '^/dmi-registry/cm-handles\[@id=''[\w\-]+''\]/state/datastores$'; -delete from fragment where xpath ~* '^/dmi-registry/cm-handles\[@id=''[\w\-]+''\]/state$';
\ No newline at end of file +DELETE FROM fragment WHERE xpath ~* '^/dmi-registry/cm-handles\[@id=''[\w\-]+''\]/state/lock-reason$'; +DELETE FROM fragment WHERE xpath ~* '^/dmi-registry/cm-handles\[@id=''[\w\-]+''\]/state/datastores/operational$'; +DELETE FROM fragment WHERE xpath ~* '^/dmi-registry/cm-handles\[@id=''[\w\-]+''\]/state/datastores$'; +DELETE FROM fragment WHERE xpath ~* '^/dmi-registry/cm-handles\[@id=''[\w\-]+''\]/state$';
\ No newline at end of file diff --git a/cps-service/src/main/java/org/onap/cps/spi/FetchDescendantsOption.java b/cps-service/src/main/java/org/onap/cps/spi/FetchDescendantsOption.java index 0c994d8d7b..b80054ac3b 100644 --- a/cps-service/src/main/java/org/onap/cps/spi/FetchDescendantsOption.java +++ b/cps-service/src/main/java/org/onap/cps/spi/FetchDescendantsOption.java @@ -1,6 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2021 Pantheon.tech + * Copyright (C) 2022 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +20,48 @@ package org.onap.cps.spi; -public enum FetchDescendantsOption { - OMIT_DESCENDANTS, - INCLUDE_ALL_DESCENDANTS +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class FetchDescendantsOption { + + public static final FetchDescendantsOption FETCH_DIRECT_CHILDREN_ONLY = new FetchDescendantsOption(1); + public static final FetchDescendantsOption OMIT_DESCENDANTS = new FetchDescendantsOption(0); + public static final FetchDescendantsOption INCLUDE_ALL_DESCENDANTS = new FetchDescendantsOption(-1); + + private final int depth; + + /** + * Has next depth. + * + * @return true if next level of depth is available + * @throws IllegalArgumentException when depth less than -1 + */ + public boolean hasNext() { + validateDepth(depth); + return depth > 0 || this.depth == INCLUDE_ALL_DESCENDANTS.depth; + } + + /** + * Next fetch descendants option. + * + * @return the next fetch descendants option + * @throws IllegalArgumentException when depth less than -1 or 0 + */ + public FetchDescendantsOption next() { + if (depth == 0) { + throw new IllegalArgumentException("Do not use next() method with zero depth"); + } + final FetchDescendantsOption nextDescendantsOption = this.depth == INCLUDE_ALL_DESCENDANTS.depth + ? INCLUDE_ALL_DESCENDANTS : new FetchDescendantsOption(depth - 1); + validateDepth(nextDescendantsOption.depth); + return nextDescendantsOption; + } + + private static void validateDepth(final int depth) { + if (depth < -1) { + throw new IllegalArgumentException("A depth of less than minus one is not allowed"); + } + } + } diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy index 3f28f0ac8d..a53706a06b 100644 --- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy @@ -188,7 +188,7 @@ class CpsDataServiceImplSpec extends Specification { expect: 'service returns same data if uses same parameters' objectUnderTest.getDataNode(dataspaceName, anchorName, xpath, fetchDescendantsOption) == dataNode where: 'all fetch options are supported' - fetchDescendantsOption << FetchDescendantsOption.values() + fetchDescendantsOption << [FetchDescendantsOption.OMIT_DESCENDANTS, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS] } def 'Get data node with option invalid #scenario.'() { diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsQueryServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsQueryServiceImplSpec.groovy index 55a252c27d..b7fec85119 100644 --- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsQueryServiceImplSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsQueryServiceImplSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2021 Nordix Foundation + * Copyright (C) 2021-2022 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -44,7 +44,7 @@ class CpsQueryServiceImplSpec extends Specification { then: 'the persistence service is called once with the correct parameters' 1 * mockCpsDataPersistenceService.queryDataNodes(dataspaceName, anchorName, cpsPath, fetchDescendantsOption) where: 'all fetch descendants options are supported' - fetchDescendantsOption << FetchDescendantsOption.values() + fetchDescendantsOption << [FetchDescendantsOption.OMIT_DESCENDANTS, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS] } def 'Query data nodes by cps path with invalid #scenario.'() { diff --git a/cps-service/src/test/groovy/org/onap/cps/spi/FetchDescendantsOptionSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/spi/FetchDescendantsOptionSpec.groovy new file mode 100644 index 0000000000..627383561a --- /dev/null +++ b/cps-service/src/test/groovy/org/onap/cps/spi/FetchDescendantsOptionSpec.groovy @@ -0,0 +1,75 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.spi + + +import spock.lang.Specification + +class FetchDescendantsOptionSpec extends Specification { + def 'Check has next descendant for fetch descendant option: #scenario'() { + when: 'fetch descendant option with #depth depth' + def fetchDescendantsOption = new FetchDescendantsOption(depth) + then: 'next level descendants available: #expectedHasNext' + fetchDescendantsOption.hasNext() == expectedHasNext + where: 'following parameters are used' + scenario | depth || expectedHasNext + 'omit descendants' | 0 || false + 'first child' | 1 || true + 'second child' | 2 || true + 'include all descendants' | -1 || true + } + + def 'Check has next descendant for fetch descendant option: invalid depth'() { + given: 'fetch descendant option with -2 depth' + def fetchDescendantsOption = new FetchDescendantsOption(-2) + when: 'next level descendants not available' + fetchDescendantsOption.hasNext() + then: 'exception thrown' + thrown IllegalArgumentException + } + + def 'Get next descendant for fetch descendant option: #scenario'() { + when: 'fetch descendant option with #depth depth' + def fetchDescendantsOption = new FetchDescendantsOption(depth) + then: 'the next level of depth is as expected' + fetchDescendantsOption.next().depth == depth - 1 + where: 'following parameters are used' + scenario | depth + 'first child' | 1 + 'second child' | 2 + } + + def 'Get next descendant for fetch descendant option: include all descendants'() { + when: 'fetch descendant option with -1 depth' + def fetchDescendantsOption = new FetchDescendantsOption(-1) + then: 'the next level of depth is as expected' + fetchDescendantsOption.next().depth == -1 + } + + def 'Get next descendant for fetch descendant option: omit descendants'() { + given: 'fetch descendant option with 0 depth' + def fetchDescendantsOption = new FetchDescendantsOption(0) + when: 'the next level of depth is not allowed' + fetchDescendantsOption.next() + then: 'exception thrown' + thrown IllegalArgumentException + } +} diff --git a/docs/release-notes.rst b/docs/release-notes.rst index 8c90a1ffc7..aec3f3b1f2 100755 --- a/docs/release-notes.rst +++ b/docs/release-notes.rst @@ -12,11 +12,11 @@ CPS Release Notes :depth: 2 .. -.. ==================== -.. * * * KOHN * * * -.. ==================== +.. ====================== +.. * * * LONDON * * * +.. ====================== -Version: 3.1.5 (not yet released) +Version: 3.2.0 (not yet released) ================================= Release Data @@ -26,20 +26,34 @@ Release Data | **CPS Project** | | | | | +--------------------------------------+--------------------------------------------------------+ -| **Docker images** | onap/cps-and-ncmp:3.1.5 | +| **Docker images** | onap/cps-and-ncmp:3.2.0 | | | | +--------------------------------------+--------------------------------------------------------+ -| **Release designation** | 3.1.5 Kohn | +| **Release designation** | 3.2.0 London | | | | +--------------------------------------+--------------------------------------------------------+ | **Release date** | (not yet released) | | | | +--------------------------------------+--------------------------------------------------------+ +Features +-------- + - `CPS-1185 <https://jira.onap.org/browse/CPS-1185>`_ Get all dataspaces + - `CPS-1186 <https://jira.onap.org/browse/CPS-1186>`_ Get all schema sets for a dataspace + - `CPS-1187 <https://jira.onap.org/browse/CPS-1187>`_ Get single dataspace + - `CPS-1189 <https://jira.onap.org/browse/CPS-1189>`_ Various create endpoints should return 201 response with empty body Bug Fixes --------- - `CPS-1312 <https://jira.onap.org/browse/CPS-1312>`_ CPS(/NCMP) does not have version control +Known Limitations, Issues and Workarounds +----------------------------------------- + +*System Limitations* + +For upgrading, CPS uses Liquibase for database upgrades. CPS currently only supports upgrading from Liquibase changelog 11 to Liquibase changelog 16. +This is from commit CPS-506: List all known modules and revision to CPS-1312: Default CMHandles to READY during upgrade or from ONAP release Honolulu to London. + Version: 3.1.4 ============== |