From 40a8211b8f8f4b244b43620776a71371bc5371d6 Mon Sep 17 00:00:00 2001 From: sourabh_sourabh Date: Mon, 20 Jun 2022 15:57:24 +0100 Subject: [PATCH 1/1] Data Sync Watchdog Process - Get all the Cm Handles state in READY and Operational datastores sync state in UNSYNCHRONIZED - Get a random Cm Handle - Get the first resource data from the node - Save the data in Cps Db - Update the Operational datastores sync state to SYNCHRONIZED Issue-ID: CPS-1052 Issue-ID: CPS-1053 Issue-ID: CPS-1054 Change-Id: I9a20391ef30e6d56c4d789a92b8bf42cd3756c62 Signed-off-by: Lathish Signed-off-by: sourabh_sourabh --- cps-application/src/main/resources/application.yml | 5 +- .../rest/mapper/RestOutputCmHandleStateMapper.java | 2 +- .../controller/NetworkCmProxyControllerSpec.groovy | 3 +- .../RestOutputCmHandleStateMapperTest.groovy | 5 +- .../api/impl/operations/DmiDataOperations.java | 83 +++++++++++------ .../cps/ncmp/api/inventory/CompositeState.java | 5 +- .../ncmp/api/inventory/CompositeStateBuilder.java | 12 +-- .../ncmp/api/inventory/InventoryPersistence.java | 33 ++++++- .../org/onap/cps/ncmp/api/inventory/SyncState.java | 25 +++++ .../ncmp/api/inventory/sync/DataSyncWatchdog.java | 74 +++++++++++++++ .../api/inventory/sync/ModuleSyncWatchdog.java | 2 +- .../cps/ncmp/api/inventory/sync/SyncUtils.java | 70 +++++++++++++- .../impl/operations/DmiDataOperationsSpec.groovy | 14 +++ .../api/inventory/CompositeStateBuilderSpec.groovy | 4 +- .../ncmp/api/inventory/CompositeStateSpec.groovy | 2 +- .../api/inventory/InventoryPersistenceSpec.groovy | 35 ++++++- .../ncmp/api/inventory/sync/DataSyncSpec.groovy | 101 +++++++++++++++++++++ .../ncmp/api/inventory/sync/ModuleSyncSpec.groovy | 2 +- .../ncmp/api/inventory/sync/SyncUtilsSpec.groovy | 48 +++++++++- .../ncmp/api/models/YangModelCmHandleSpec.groovy | 1 - .../java/org/onap/cps/utils/JsonObjectMapper.java | 17 ++++ .../org/onap/cps/utils/JsonObjectMapperSpec.groovy | 18 ++++ 22 files changed, 499 insertions(+), 62 deletions(-) create mode 100644 cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/SyncState.java create mode 100644 cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/DataSyncWatchdog.java create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/DataSyncSpec.groovy diff --git a/cps-application/src/main/resources/application.yml b/cps-application/src/main/resources/application.yml index 2b4dc4b7c..802a18b84 100644 --- a/cps-application/src/main/resources/application.yml +++ b/cps-application/src/main/resources/application.yml @@ -158,6 +158,7 @@ dmi: timers: advised-modules-sync: sleep-time-ms: 30000 - locked-modules-sync: - sleep-time-ms: 300000 \ No newline at end of file + sleep-time-ms: 300000 + cm-handle-data-sync: + sleep-time-ms: 30000 diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/mapper/RestOutputCmHandleStateMapper.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/mapper/RestOutputCmHandleStateMapper.java index 5f4b31118..afad1098c 100644 --- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/mapper/RestOutputCmHandleStateMapper.java +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/mapper/RestOutputCmHandleStateMapper.java @@ -55,7 +55,7 @@ public interface RestOutputCmHandleStateMapper { if (compositeStateDataStore.getOperationalDataStore() != null) { final SyncState operationalSyncState = new SyncState(); - operationalSyncState.setState(compositeStateDataStore.getOperationalDataStore().getSyncState()); + operationalSyncState.setState(compositeStateDataStore.getOperationalDataStore().getSyncState().name()); operationalSyncState.setLastSyncTime(compositeStateDataStore.getOperationalDataStore().getLastSyncTime()); dataStores.setOperational(operationalSyncState); } diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy index 036928fe3..71258102e 100644 --- a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy +++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy @@ -26,6 +26,7 @@ package org.onap.cps.ncmp.rest.controller import org.mapstruct.factory.Mappers import org.onap.cps.ncmp.api.inventory.CmHandleState import org.onap.cps.ncmp.api.inventory.CompositeState +import org.onap.cps.ncmp.api.inventory.SyncState import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle import org.onap.cps.ncmp.rest.mapper.RestOutputCmHandleStateMapper import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor @@ -392,7 +393,7 @@ class NetworkCmProxyControllerSpec extends Specification { def dataStores() { DataStores.builder() .operationalDataStore(Operational.builder() - .syncState('NONE_REQUESTED') + .syncState(SyncState.NONE_REQUESTED) .lastSyncTime(formattedDateAndTime.toString()).build()).build() } diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/mapper/RestOutputCmHandleStateMapperTest.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/mapper/RestOutputCmHandleStateMapperTest.groovy index 22c9fe605..695ca5ad6 100644 --- a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/mapper/RestOutputCmHandleStateMapperTest.groovy +++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/mapper/RestOutputCmHandleStateMapperTest.groovy @@ -24,6 +24,7 @@ import org.mapstruct.factory.Mappers import org.onap.cps.ncmp.api.inventory.CmHandleState import org.onap.cps.ncmp.api.inventory.CompositeStateBuilder import org.onap.cps.ncmp.api.inventory.LockReasonCategory +import org.onap.cps.ncmp.api.inventory.SyncState import org.onap.cps.ncmp.rest.model.RestOutputCmHandleState import spock.lang.Specification @@ -43,7 +44,7 @@ class RestOutputCmHandleStateMapperTest extends Specification { .withCmHandleState(CmHandleState.ADVISED) .withLastUpdatedTime(formattedDateAndTime.toString()) .withLockReason(LockReasonCategory.LOCKED_MISBEHAVING, 'locked other details') - .withOperationalDataStores('SYNCHRONIZED', formattedDateAndTime).build() + .withOperationalDataStores(SyncState.SYNCHRONIZED, formattedDateAndTime).build() compositeState.setDataSyncEnabled(false) when: 'mapper is called' def result = objectUnderTest.toRestOutputCmHandleState(compositeState) @@ -54,7 +55,7 @@ class RestOutputCmHandleStateMapperTest extends Specification { assert result.lastUpdateTime == formattedDateAndTime assert result.lockReason.reason == 'LOCKED_MISBEHAVING' assert result.lockReason.details == 'locked other details' - assert result.cmHandleState == CmHandleState.ADVISED.name() + assert result.cmHandleState == 'ADVISED' assert result.dataSyncState.operational.getState() != null } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java index d0e17d4c0..d46d63410 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java @@ -75,21 +75,33 @@ public class DmiDataOperations extends DmiOperations { final DataStoreEnum dataStore, final String requestId, final String topicParamInQuery) { - CpsValidator.validateNameCharacters(cmHandleId); - final YangModelCmHandle yangModelCmHandle = - inventoryPersistence.getYangModelCmHandle(cmHandleId); + final YangModelCmHandle yangModelCmHandle = getYangModelCmHandle(cmHandleId); + final String jsonBody = getDmiRequestBody(READ, requestId, null, null, yangModelCmHandle); + final String dmiResourceDataUrl = getDmiRequestUrl(cmHandleId, resourceId, optionsParamInQuery, dataStore, + topicParamInQuery, yangModelCmHandle); + final CmHandleState cmHandleState = yangModelCmHandle.getCompositeState().getCmHandleState(); + isCmHandleStateReady(yangModelCmHandle, cmHandleState); + return dmiRestClient.postOperationWithJsonData(dmiResourceDataUrl, jsonBody, READ); + } + + /** + * This method fetches all the resource data from operational data store for given cm handle + * identifier using dmi client. + * + * @param cmHandleId network resource identifier + * @param dataStore data store enum + * @param requestId requestId for async responses + * @return {@code ResponseEntity} response entity + */ + public ResponseEntity getResourceDataFromDmi(final String cmHandleId, + final DataStoreEnum dataStore, + final String requestId) { + final YangModelCmHandle yangModelCmHandle = getYangModelCmHandle(cmHandleId); + final String jsonBody = getDmiRequestBody(READ, requestId, null, null, yangModelCmHandle); + final String dmiResourceDataUrl = getDmiRequestUrl(cmHandleId, "/", null, dataStore, + null, yangModelCmHandle); final CmHandleState cmHandleState = yangModelCmHandle.getCompositeState().getCmHandleState(); isCmHandleStateReady(yangModelCmHandle, cmHandleState); - final DmiRequestBody dmiRequestBody = DmiRequestBody.builder() - .operation(READ) - .requestId(requestId) - .build(); - dmiRequestBody.asDmiProperties(yangModelCmHandle.getDmiProperties()); - final String jsonBody = jsonObjectMapper.asJsonString(dmiRequestBody); - final String dmiResourceDataUrl = dmiServiceUrlBuilder.getDmiDatastoreUrl( - dmiServiceUrlBuilder.populateQueryParams(resourceId, optionsParamInQuery, - topicParamInQuery), dmiServiceUrlBuilder.populateUriVariables( - yangModelCmHandle, cmHandleId, dataStore)); return dmiRestClient.postOperationWithJsonData(dmiResourceDataUrl, jsonBody, READ); } @@ -109,23 +121,42 @@ public class DmiDataOperations extends DmiOperations { final OperationEnum operation, final String requestData, final String dataType) { - CpsValidator.validateNameCharacters(cmHandleId); - final YangModelCmHandle yangModelCmHandle = - inventoryPersistence.getYangModelCmHandle(cmHandleId); + final YangModelCmHandle yangModelCmHandle = getYangModelCmHandle(cmHandleId); + final String jsonBody = getDmiRequestBody(operation, null, requestData, dataType, yangModelCmHandle); + final String dmiUrl = getDmiRequestUrl(cmHandleId, resourceId, null, PASSTHROUGH_RUNNING, + null, yangModelCmHandle); final CmHandleState cmHandleState = yangModelCmHandle.getCompositeState().getCmHandleState(); isCmHandleStateReady(yangModelCmHandle, cmHandleState); + return dmiRestClient.postOperationWithJsonData(dmiUrl, jsonBody, operation); + } + + private YangModelCmHandle getYangModelCmHandle(final String cmHandleId) { + CpsValidator.validateNameCharacters(cmHandleId); + return inventoryPersistence.getYangModelCmHandle(cmHandleId); + } + + private String getDmiRequestBody(final OperationEnum operation, final String requestId, final String requestData, + final String dataType, final YangModelCmHandle yangModelCmHandle) { final DmiRequestBody dmiRequestBody = DmiRequestBody.builder() - .operation(operation) - .data(requestData) - .dataType(dataType) - .build(); + .operation(operation) + .requestId(requestId) + .data(requestData) + .dataType(dataType) + .build(); dmiRequestBody.asDmiProperties(yangModelCmHandle.getDmiProperties()); - final String jsonBody = jsonObjectMapper.asJsonString(dmiRequestBody); - final String dmiUrl = - dmiServiceUrlBuilder.getDmiDatastoreUrl(dmiServiceUrlBuilder.populateQueryParams(resourceId, - null, null), - dmiServiceUrlBuilder.populateUriVariables(yangModelCmHandle, cmHandleId, PASSTHROUGH_RUNNING)); - return dmiRestClient.postOperationWithJsonData(dmiUrl, jsonBody, operation); + return jsonObjectMapper.asJsonString(dmiRequestBody); + } + + private String getDmiRequestUrl(final String cmHandleId, + final String resourceId, + final String optionsParamInQuery, + final DataStoreEnum dataStore, + final String topicParamInQuery, + final YangModelCmHandle yangModelCmHandle) { + return dmiServiceUrlBuilder.getDmiDatastoreUrl( + dmiServiceUrlBuilder.populateQueryParams(resourceId, optionsParamInQuery, + topicParamInQuery), dmiServiceUrlBuilder.populateUriVariables( + yangModelCmHandle, cmHandleId, dataStore)); } private void isCmHandleStateReady(final YangModelCmHandle yangModelCmHandle, final CmHandleState cmHandleState) { diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CompositeState.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CompositeState.java index eeaa4cd0b..df303b5da 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CompositeState.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CompositeState.java @@ -57,7 +57,8 @@ public class CompositeState { /** * Date and Time in the format of yyyy-MM-dd'T'HH:mm:ss.SSSZ */ - public static final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); + private static final DateTimeFormatter dateTimeFormatter = DateTimeFormatter + .ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); /** @@ -93,7 +94,7 @@ public class CompositeState { public static class Operational { @JsonProperty("sync-state") - private String syncState; + private SyncState syncState; @JsonProperty("last-sync-time") private String lastSyncTime; diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CompositeStateBuilder.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CompositeStateBuilder.java index 4ab0cecbf..f4d96389d 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CompositeStateBuilder.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CompositeStateBuilder.java @@ -98,7 +98,7 @@ public class CompositeStateBuilder { * @param lastSyncTime for the locked state * @return CompositeStateBuilder */ - public CompositeStateBuilder withOperationalDataStores(final String syncState, final String lastSyncTime) { + public CompositeStateBuilder withOperationalDataStores(final SyncState syncState, final String lastSyncTime) { this.datastores = DataStores.builder().operationalDataStore( Operational.builder().syncState(syncState).lastSyncTime(lastSyncTime).build()).build(); return this; @@ -111,20 +111,20 @@ public class CompositeStateBuilder { * @return CompositeState */ public CompositeStateBuilder fromDataNode(final DataNode dataNode) { - this.cmHandleState = CmHandleState.valueOf((String) dataNode.getLeaves() - .get("cm-handle-state")); + this.cmHandleState = CmHandleState.valueOf((String) dataNode.getLeaves() + .get("cm-handle-state")); for (final DataNode stateChildNode : dataNode.getChildDataNodes()) { if (stateChildNode.getXpath().endsWith("/lock-reason")) { this.lockReason = new LockReason(LockReasonCategory.valueOf( - (String) stateChildNode.getLeaves().get("reason")), - (String) stateChildNode.getLeaves().get("details")); + (String) stateChildNode.getLeaves().get("reason")), + (String) stateChildNode.getLeaves().get("details")); } if (stateChildNode.getXpath().endsWith("/datastores")) { for (final DataNode dataStoreNodes : stateChildNode.getChildDataNodes()) { Operational operationalDataStore = null; if (dataStoreNodes.getXpath().contains("/operational")) { operationalDataStore = Operational.builder() - .syncState((String) dataStoreNodes.getLeaves().get("sync-state")) + .syncState(SyncState.valueOf((String) dataStoreNodes.getLeaves().get("sync-state"))) .lastSyncTime((String) dataStoreNodes.getLeaves().get("last-sync-time")) .build(); } 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 ce34154b9..1985bd959 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 @@ -93,14 +93,39 @@ public class InventoryPersistence { } /** - * Method to return cm handles from the cps path. + * Method to return data nodes representing the cm handles. * * @param cpsPath cps path for which the cmHandle is requested - * @return a list of cm handles + * @return a list of data nodes representing the cm handles. */ - public List getCmHandlesByCpsPath(final String cpsPath) { + public List getCmHandleDataNodesByCpsPath(final String cpsPath) { return cpsDataPersistenceService.queryDataNodes( - NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, cpsPath, FetchDescendantsOption.OMIT_DESCENDANTS); + NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, cpsPath, FetchDescendantsOption.OMIT_DESCENDANTS); + } + + /** + * Method which returns cm handles by the cm handle id and state. + * @param cmHandleId cm handle id + * @param cmHandleState cm handle state + * @return a list of cm handles + */ + public List getCmHandlesByIdAndState(final String cmHandleId, final CmHandleState cmHandleState) { + return cpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, + NCMP_DMI_REGISTRY_ANCHOR, "//cm-handles[@id='" + cmHandleId + "']/state[@cm-handle-state=\"" + + cmHandleState + "\"]/ancestor::cm-handles", + FetchDescendantsOption.OMIT_DESCENDANTS); + } + + /** + * Method which returns cm handles by the operational sync state of cm handle. + * @param syncState sync state + * @return a list of cm handles + */ + public List getCmHandlesByOperationalSyncState(final SyncState syncState) { + return cpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, + NCMP_DMI_REGISTRY_ANCHOR, "//state/datastores" + + "/operational[@sync-state=\"" + syncState + "\"]/ancestor::cm-handles", + FetchDescendantsOption.OMIT_DESCENDANTS); } /** diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/SyncState.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/SyncState.java new file mode 100644 index 000000000..9c7a47618 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/SyncState.java @@ -0,0 +1,25 @@ +/* + * ============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.ncmp.api.inventory; + +public enum SyncState { + SYNCHRONIZED, UNSYNCHRONIZED, NONE_REQUESTED +} 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 new file mode 100644 index 000000000..553db65dd --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/DataSyncWatchdog.java @@ -0,0 +1,74 @@ +/* + * ============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.ncmp.api.inventory.sync; + +import java.time.OffsetDateTime; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.onap.cps.api.CpsDataService; +import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; +import org.onap.cps.ncmp.api.inventory.CompositeState; +import org.onap.cps.ncmp.api.inventory.InventoryPersistence; +import org.onap.cps.ncmp.api.inventory.SyncState; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +@Slf4j +@RequiredArgsConstructor +@Service +public class DataSyncWatchdog { + + private final InventoryPersistence inventoryPersistence; + + private final CpsDataService cpsDataService; + + private final SyncUtils syncUtils; + + /** + * Execute Cm Handle poll which queries the cm handle state in 'READY' and Operational Datastore Sync State in + * 'UNSYNCHRONIZED'. + */ + @Scheduled(fixedDelayString = "${timers.cm-handle-data-sync.sleep-time-ms:30000}") + public void executeUnSynchronizedReadyCmHandlePoll() { + YangModelCmHandle unSynchronizedReadyCmHandle = syncUtils.getAnUnSynchronizedReadyCmHandle(); + while (unSynchronizedReadyCmHandle != null) { + final String cmHandleId = unSynchronizedReadyCmHandle.getId(); + log.debug("Cm-Handles found in READY and UNSYNCHRONIZED state: {}", cmHandleId); + final CompositeState compositeState = inventoryPersistence + .getCmHandleState(cmHandleId); + final String resourceData = syncUtils.getResourceData(cmHandleId); + if (resourceData == null) { + log.debug("Error accessing the node for Cm-Handle: {}", cmHandleId); + } else { + cpsDataService.saveData("NFP-Operational", cmHandleId, + resourceData, OffsetDateTime.now()); + } + compositeState.setLastUpdateTimeNow(); + compositeState.getDataStores() + .setOperationalDataStore(CompositeState.Operational.builder() + .syncState(SyncState.SYNCHRONIZED) + .lastSyncTime(CompositeState.nowInSyncTimeFormat()).build()); + inventoryPersistence.saveCmHandleState(cmHandleId, compositeState); + unSynchronizedReadyCmHandle = syncUtils.getAnUnSynchronizedReadyCmHandle(); + } + log.debug("No Cm-Handles currently found in an READY State and Operational Sync State is UNSYNCHRONIZED"); + } +} 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 d6aaa32be..9cfa0a05c 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 @@ -86,7 +86,7 @@ public class ModuleSyncWatchdog { */ @Scheduled(fixedDelayString = "${timers.locked-modules-sync.sleep-time-ms:300000}") public void executeLockedMisbehavingCmHandlePoll() { - final List lockedMisbehavingCmHandles = syncUtils.getLockedMisbehavingCmHandles(); + final List lockedMisbehavingCmHandles = syncUtils.getLockedMisbehavingYangModelCmHandles(); for (final YangModelCmHandle lockedMisbehavingModelCmHandle: lockedMisbehavingCmHandles) { final CompositeState updatedCompositeState = lockedMisbehavingModelCmHandle.getCompositeState(); updatedCompositeState.setCmHandleState(CmHandleState.ADVISED); diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/SyncUtils.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/SyncUtils.java index 22eeabb0d..b5456ab14 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/SyncUtils.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/SyncUtils.java @@ -21,24 +21,35 @@ package org.onap.cps.ncmp.api.inventory.sync; +import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.collect.ImmutableMap; import java.security.SecureRandom; +import java.util.Collections; +import java.util.Iterator; import java.util.List; +import java.util.Map; +import java.util.UUID; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations; +import org.onap.cps.ncmp.api.impl.operations.DmiOperations; import org.onap.cps.ncmp.api.impl.utils.YangDataConverter; import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; import org.onap.cps.ncmp.api.inventory.CmHandleState; import org.onap.cps.ncmp.api.inventory.CompositeState; import org.onap.cps.ncmp.api.inventory.InventoryPersistence; import org.onap.cps.ncmp.api.inventory.LockReasonCategory; +import org.onap.cps.ncmp.api.inventory.SyncState; import org.onap.cps.spi.model.DataNode; -import org.springframework.stereotype.Component; +import org.onap.cps.utils.JsonObjectMapper; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; @Slf4j -@Component +@Service @RequiredArgsConstructor public class SyncUtils { @@ -46,6 +57,10 @@ public class SyncUtils { private final InventoryPersistence inventoryPersistence; + private final DmiDataOperations dmiDataOperations; + + private final JsonObjectMapper jsonObjectMapper; + private static final Pattern retryAttemptPattern = Pattern.compile("^Attempt #(\\d+) failed:"); /** @@ -64,14 +79,38 @@ public class SyncUtils { return inventoryPersistence.getYangModelCmHandle(cmHandleId); } + /** + * First query data nodes for cm handles with CM Handle Operational Sync State in "UNSYNCHRONIZED" and + * randomly select a CM Handle and query the data nodes for CM Handle State in "READY". + * + * @return a random yang model cm handle with State in READY and Operation Sync State in "UNSYNCHRONIZED", + * return null if not found + */ + public YangModelCmHandle getAnUnSynchronizedReadyCmHandle() { + final List unSynchronizedCmHandles = inventoryPersistence + .getCmHandlesByOperationalSyncState(SyncState.UNSYNCHRONIZED); + if (unSynchronizedCmHandles.isEmpty()) { + return null; + } + Collections.shuffle(unSynchronizedCmHandles); + for (final DataNode cmHandle : unSynchronizedCmHandles) { + final String cmHandleId = cmHandle.getLeaves().get("id").toString(); + final List readyCmHandles = inventoryPersistence + .getCmHandlesByIdAndState(cmHandleId, CmHandleState.READY); + if (!readyCmHandles.isEmpty()) { + return inventoryPersistence.getYangModelCmHandle(cmHandleId); + } + } + return null; + } /** * Query data nodes for cm handles with an "LOCKED" cm handle state with reason LOCKED_MISBEHAVING". * * @return a random yang model cm handle with an ADVISED state, return null if not found */ - public List getLockedMisbehavingCmHandles() { - final List lockedCmHandleAsDataNodeList = inventoryPersistence.getCmHandlesByCpsPath( + public List getLockedMisbehavingYangModelCmHandles() { + final List lockedCmHandleAsDataNodeList = inventoryPersistence.getCmHandleDataNodesByCpsPath( "//lock-reason[@reason=\"LOCKED_MISBEHAVING\"]/ancestor::cm-handles"); return lockedCmHandleAsDataNodeList.stream() .map(cmHandle -> YangDataConverter.convertCmHandleToYangModel(cmHandle, @@ -99,4 +138,27 @@ public class SyncUtils { .lockReasonCategory(lockReasonCategory).build()); } + /** + * Get the Resourece Data from Node through DMI Passthrough service. + * + * @param cmHandleId cm handle id + * @return optional string containing the resource data + */ + public String getResourceData(final String cmHandleId) { + final ResponseEntity resourceDataResponseEntity = dmiDataOperations.getResourceDataFromDmi( + cmHandleId, DmiOperations.DataStoreEnum.PASSTHROUGH_OPERATIONAL, + UUID.randomUUID().toString()); + if (resourceDataResponseEntity.getStatusCode().is2xxSuccessful()) { + return getFirstResource(resourceDataResponseEntity.getBody()); + } + return null; + } + + private String getFirstResource(final Object responseBody) { + final String jsonObjectAsString = jsonObjectMapper.asJsonString(responseBody); + final JsonNode overallJsonNode = jsonObjectMapper.convertToJsonNode(jsonObjectAsString); + final Iterator> overallJsonTreeMap = overallJsonNode.fields(); + final Map.Entry firstElement = overallJsonTreeMap.next(); + return jsonObjectMapper.asJsonString(ImmutableMap.of(firstElement.getKey(), firstElement.getValue())); + } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiDataOperationsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiDataOperationsSpec.groovy index b7ebf2965..03825c2bb 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiDataOperationsSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiDataOperationsSpec.groovy @@ -80,6 +80,20 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec { 'datastore running with properties' | [yangModelCmHandleProperty] | PASSTHROUGH_RUNNING | OPTIONS_PARAM || '{"operation":"read","cmHandleProperties":{"prop1":"val1"}}' | 'passthrough-running' | '&options=(a=1,b=2)' } + def 'call get all resource data.'() { + given: 'the system returns a cm handle with a sample property' + mockYangModelCmHandleRetrieval([yangModelCmHandleProperty]) + and: 'a positive response from DMI service when it is called with the expected parameters' + def responseFromDmi = new ResponseEntity(HttpStatus.OK) + def expectedUrl = dmiServiceBaseUrl + "passthrough-operational?resourceIdentifier=/" + mockDmiRestClient.postOperationWithJsonData(expectedUrl, '{"operation":"read","cmHandleProperties":{"prop1":"val1"}}', READ) >> responseFromDmi + dmiServiceUrlBuilder.getDmiDatastoreUrl(_, _) >> expectedUrl + when: 'get resource data is invoked' + def result = objectUnderTest.getResourceDataFromDmi(cmHandleId, PASSTHROUGH_OPERATIONAL, NO_REQUEST_ID) + then: 'the result is the response from the DMI service' + assert result == responseFromDmi + } + def 'Write data for pass-through:running datastore in DMI.'() { given: 'a cm handle for #cmHandleId' mockYangModelCmHandleRetrieval([yangModelCmHandleProperty]) diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/CompositeStateBuilderSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/CompositeStateBuilderSpec.groovy index d6f4ba6ed..60fec6f86 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/CompositeStateBuilderSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/CompositeStateBuilderSpec.groovy @@ -47,11 +47,11 @@ class CompositeStateBuilderSpec extends Specification { def "Composite State Specification"() { when: 'using composite state builder ' def compositeState = new CompositeStateBuilder().withCmHandleState(CmHandleState.ADVISED) - .withLockReason(LockReasonCategory.LOCKED_MISBEHAVING,"").withOperationalDataStores("UNSYNCHRONIZED", + .withLockReason(LockReasonCategory.LOCKED_MISBEHAVING,"").withOperationalDataStores(SyncState.UNSYNCHRONIZED, formattedDateAndTime.toString()).withLastUpdatedTime(formattedDateAndTime).build() then: 'it matches expected cm handle state and data store sync state' assert compositeState.cmHandleState == CmHandleState.ADVISED - assert compositeState.dataStores.operationalDataStore.syncState == 'UNSYNCHRONIZED' + assert compositeState.dataStores.operationalDataStore.syncState == SyncState.UNSYNCHRONIZED } def "Build composite state from DataNode "() { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/CompositeStateSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/CompositeStateSpec.groovy index 5387fc675..0a6f8c350 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/CompositeStateSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/CompositeStateSpec.groovy @@ -54,7 +54,7 @@ class CompositeStateSpec extends Specification { def dataStores() { DataStores.builder().operationalDataStore(Operational.builder() - .syncState('NONE_REQUESTED') + .syncState(SyncState.NONE_REQUESTED) .lastSyncTime(formattedDateAndTime.toString()).build()) .build() } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/InventoryPersistenceSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/InventoryPersistenceSpec.groovy index e6346cb02..578225e87 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/InventoryPersistenceSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/InventoryPersistenceSpec.groovy @@ -28,7 +28,6 @@ import org.onap.cps.spi.CpsDataPersistenceService import org.onap.cps.spi.FetchDescendantsOption import org.onap.cps.spi.exceptions.DataValidationException import org.onap.cps.spi.model.DataNode -import org.onap.cps.spi.model.DataNodeBuilder import org.onap.cps.utils.JsonObjectMapper import spock.lang.Shared import spock.lang.Specification @@ -71,6 +70,9 @@ class InventoryPersistenceSpec extends Specification { @Shared def childDataNodesForCmHandleWithState = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='some-cm-handle']/state", leaves: ['cm-handle-state': 'ADVISED'])] + @Shared + def static sampleDataNodes = [new DataNode()] + def "Retrieve CmHandle using datanode with #scenario."() { given: 'the cps data service returns a data node from the DMI registry' def dataNode = new DataNode(childDataNodes:childDataNodes, leaves: leaves) @@ -148,13 +150,36 @@ class InventoryPersistenceSpec extends Specification { given: 'a cm handle state to query' def cmHandleState = CmHandleState.ADVISED and: 'cps data service returns a list of data nodes' - def dataNodes = [new DataNode()] mockCpsDataPersistenceService.queryDataNodes('NCMP-Admin', 'ncmp-dmi-registry', - '//state[@cm-handle-state="ADVISED"]/ancestor::cm-handles', OMIT_DESCENDANTS) >> dataNodes + '//state[@cm-handle-state="ADVISED"]/ancestor::cm-handles', OMIT_DESCENDANTS) >> sampleDataNodes when: 'get cm handles by state is invoked' def result = objectUnderTest.getCmHandlesByState(cmHandleState) then: 'the returned result is a list of data nodes returned by cps data service' - assert result == dataNodes + assert result == sampleDataNodes + } + + def 'Get Cm Handles By State and Cm-Handle Id'() { + given: 'a cm handle state to query' + def cmHandleState = CmHandleState.READY + and: 'cps data service returns a list of data nodes' + mockCpsDataPersistenceService.queryDataNodes('NCMP-Admin', 'ncmp-dmi-registry', + '//cm-handles[@id=\'some-cm-handle\']/state[@cm-handle-state="'+ 'READY'+'"]/ancestor::cm-handles', OMIT_DESCENDANTS) >> sampleDataNodes + when: 'get cm handles by state and id is invoked' + def result = objectUnderTest.getCmHandlesByIdAndState(cmHandleId, cmHandleState) + then: 'the returned result is a list of data nodes returned by cps data service' + assert result == sampleDataNodes + } + + def 'Get Cm Handles By Operational Sync State : UNSYNCHRONIZED'() { + given: 'a cm handle state to query' + def cmHandleState = CmHandleState.READY + and: 'cps data service returns a list of data nodes' + mockCpsDataPersistenceService.queryDataNodes('NCMP-Admin', 'ncmp-dmi-registry', + '//state/datastores/operational[@sync-state="'+'UNSYNCHRONIZED'+'"]/ancestor::cm-handles', OMIT_DESCENDANTS) >> sampleDataNodes + when: 'get cm handles by operational sync state as UNSYNCHRONIZED is invoked' + def result = objectUnderTest.getCmHandlesByOperationalSyncState(SyncState.UNSYNCHRONIZED) + then: 'the returned result is a list of data nodes returned by cps data service' + assert result == sampleDataNodes } def 'Retrieve cm handle by cps path '() { @@ -166,7 +191,7 @@ class InventoryPersistenceSpec extends Specification { cpsPath, OMIT_DESCENDANTS) >> Arrays.asList(cmHandleDataNode) when: 'get cm handles by cps path is invoked' - def result = objectUnderTest.getCmHandlesByCpsPath(cpsPath) + def result = objectUnderTest.getCmHandleDataNodesByCpsPath(cpsPath) then: 'the returned result is a list of data nodes returned by cps data service' assert result.contains(cmHandleDataNode) } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/DataSyncSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/DataSyncSpec.groovy new file mode 100644 index 000000000..b062635db --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/DataSyncSpec.groovy @@ -0,0 +1,101 @@ +/* + * ============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.ncmp.api.inventory.sync + +import org.onap.cps.api.CpsDataService +import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle +import org.onap.cps.ncmp.api.inventory.CmHandleState +import org.onap.cps.ncmp.api.inventory.CompositeState +import org.onap.cps.ncmp.api.inventory.InventoryPersistence +import org.onap.cps.ncmp.api.inventory.SyncState +import spock.lang.Specification + +class DataSyncSpec extends Specification { + + def mockInventoryPersistence = Mock(InventoryPersistence) + + def mockCpsDataService = Mock(CpsDataService) + + def mockSyncUtils = Mock(SyncUtils) + + def jsonString = '{"stores:bookstore":{"categories":[{"code":"01"}]}}' + + def objectUnderTest = new DataSyncWatchdog(mockInventoryPersistence, mockCpsDataService, mockSyncUtils) + + def compositeState = getCompositeState() + + def yangModelCmHandle1 = createSampleYangModelCmHandle('some-cm-handle-1') + + def yangModelCmHandle2 = createSampleYangModelCmHandle('some-cm-handle-2') + + def 'Schedule Data Sync for Cm Handle State in READY and Operational Sync State in UNSYNCHRONIZED'() { + given: 'sample resource data' + def resourceData = jsonString; + and: 'sync utilities return a cm handle twice' + mockSyncUtils.getAnUnSynchronizedReadyCmHandle() >>> [yangModelCmHandle1, yangModelCmHandle2, null] + when: 'data sync poll is executed' + objectUnderTest.executeUnSynchronizedReadyCmHandlePoll() + then: 'the inventory persistence cm handle returns a composite state for the first cm handle' + 1 * mockInventoryPersistence.getCmHandleState('some-cm-handle-1') >> compositeState + and: 'the sync util returns first resource data' + 1 * mockSyncUtils.getResourceData('some-cm-handle-1') >> resourceData + and: 'the cm-handle data is saved' + 1 * mockCpsDataService.saveData('NFP-Operational', 'some-cm-handle-1', jsonString, _) + and: 'the first cm handle operational sync state is updated' + 1 * mockInventoryPersistence.saveCmHandleState('some-cm-handle-1', compositeState) + then: 'the inventory persistence cm handle returns a composite state for the second cm handle' + 1 * mockInventoryPersistence.getCmHandleState('some-cm-handle-2') >> compositeState + and: 'the sync util returns first resource data' + 1 * mockSyncUtils.getResourceData('some-cm-handle-2') >> resourceData + and: 'the cm-handle data is saved' + 1 * mockCpsDataService.saveData('NFP-Operational', 'some-cm-handle-2', jsonString, _) + and: 'the second cm handle operational sync state is updated from "UNSYNCHRONIZED" to "SYNCHRONIZED"' + 1 * mockInventoryPersistence.saveCmHandleState('some-cm-handle-2', compositeState) + } + + def 'Schedule Data Sync for Cm Handle State in READY and Operational Sync State in UNSYNCHRONIZED which return empty data from Node'() { + given: 'cm handles in an ready state and operational sync state in unsynchronized' + and: 'sync utilities return a cm handle twice' + mockSyncUtils.getAnUnSynchronizedReadyCmHandle() >>> [yangModelCmHandle1, null] + when: 'data sync poll is executed' + objectUnderTest.executeUnSynchronizedReadyCmHandlePoll() + then: 'the inventory persistence cm handle returns a composite state for the first cm handle' + 1 * mockInventoryPersistence.getCmHandleState('some-cm-handle-1') >> compositeState + and: 'the sync util returns first resource data' + 1 * mockSyncUtils.getResourceData('some-cm-handle-1') >> null + and: 'the cm-handle data is not saved' + 0 * mockCpsDataService.saveData('NFP-Operational', 'some-cm-handle-1', jsonString, _) + } + + def createSampleYangModelCmHandle(cmHandleId) { + def compositeState = getCompositeState() + return new YangModelCmHandle(id: cmHandleId, compositeState: compositeState) + } + + def getCompositeState() { + def cmHandleState = CmHandleState.READY + def compositeState = new CompositeState(cmHandleState: cmHandleState) + compositeState.setDataStores(CompositeState.DataStores.builder() + .operationalDataStore(CompositeState.Operational.builder().syncState(SyncState.SYNCHRONIZED) + .build()).build()) + return compositeState + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncSpec.groovy index 802035c72..8752bff58 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncSpec.groovy @@ -106,7 +106,7 @@ class ModuleSyncSpec extends Specification { .withLockReason(LockReasonCategory.LOCKED_MISBEHAVING, '').build() def yangModelCmHandle = new YangModelCmHandle(id: 'some-cm-handle', compositeState: compositeState) and: 'sync utilities return a cm handle twice' - mockSyncUtils.getLockedMisbehavingCmHandles() >> [yangModelCmHandle, yangModelCmHandle] + mockSyncUtils.getLockedMisbehavingYangModelCmHandles() >> [yangModelCmHandle, yangModelCmHandle] when: 'module sync poll is executed' objectUnderTest.executeLockedMisbehavingCmHandlePoll() then: 'the first cm handle is updated to state "ADVISED" from "READY"' diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/SyncUtilsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/SyncUtilsSpec.groovy index 15d1efe06..14f201575 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/SyncUtilsSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/SyncUtilsSpec.groovy @@ -21,11 +21,19 @@ package org.onap.cps.ncmp.api.inventory.sync +import com.fasterxml.jackson.databind.JsonNode +import com.fasterxml.jackson.databind.ObjectMapper +import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations +import org.onap.cps.ncmp.api.impl.operations.DmiOperations import org.onap.cps.ncmp.api.inventory.CmHandleState import org.onap.cps.ncmp.api.inventory.CompositeState import org.onap.cps.ncmp.api.inventory.InventoryPersistence import org.onap.cps.ncmp.api.inventory.LockReasonCategory +import org.onap.cps.ncmp.api.inventory.SyncState import org.onap.cps.spi.model.DataNode +import org.onap.cps.utils.JsonObjectMapper +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity import spock.lang.Shared import spock.lang.Specification @@ -33,7 +41,11 @@ class SyncUtilsSpec extends Specification{ def mockInventoryPersistence = Mock(InventoryPersistence) - def objectUnderTest = new SyncUtils(mockInventoryPersistence) + def mockDmiDataOperations = Mock(DmiDataOperations) + + def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper()) + + def objectUnderTest = new SyncUtils(mockInventoryPersistence, mockDmiDataOperations, jsonObjectMapper) @Shared def dataNode = new DataNode(leaves: ['id': 'cm-handle-123']) @@ -67,15 +79,45 @@ class SyncUtilsSpec extends Specification{ 'does not exist' | null || 'Attempt #1 failed: new error message' 'exists' | CompositeState.LockReason.builder().details("Attempt #2 failed: some error message").build() || 'Attempt #3 failed: new error message' } + def 'Get all locked Cm-Handle where Lock Reason is LOCKED_MISBEHAVING cm handle #scenario'() { given: 'the cps (persistence service) returns a collection of data nodes' - mockInventoryPersistence.getCmHandlesByCpsPath( + mockInventoryPersistence.getCmHandleDataNodesByCpsPath( '//lock-reason[@reason="LOCKED_MISBEHAVING"]/ancestor::cm-handles') >> [dataNode ] when: 'get locked Misbehaving cm handle is called' - def result = objectUnderTest.getLockedMisbehavingCmHandles() + def result = objectUnderTest.getLockedMisbehavingYangModelCmHandles() then: 'the returned cm handle collection is the correct size' result.size() == 1 and: 'the correct cm handle is returned' result[0].id == 'cm-handle-123' } + + def 'Get a Cm-Handle where Operational Sync state is UnSynchronized and Cm-handle state is READY and #scenario'() { + given: 'the inventory persistence service returns a collection of data nodes' + mockInventoryPersistence.getCmHandlesByOperationalSyncState(SyncState.UNSYNCHRONIZED) >> unSynchronizedDataNodes + mockInventoryPersistence.getCmHandlesByIdAndState("cm-handle-123", CmHandleState.READY) >> readyDataNodes + when: 'get advised cm handle is called' + objectUnderTest.getAnUnSynchronizedReadyCmHandle() + then: 'the returned data node collection is the correct size' + readyDataNodes.size() == expectedDataNodeSize + and: 'get yang model cm handles is invoked the correct number of times' + expectedCallsToGetYangModelCmHandle * mockInventoryPersistence.getYangModelCmHandle('cm-handle-123') + where: 'the following scenarios are used' + scenario | unSynchronizedDataNodes | readyDataNodes || expectedCallsToGetYangModelCmHandle | expectedDataNodeSize + 'exists' | [dataNode] | [dataNode] || 1 | 1 + 'unsynchronized exist but not ready' | [dataNode] | [] || 0 | 0 + 'does not exist' | [] | [] || 0 | 0 + } + + def 'Get resource data through DMI Operations #scenario'() { + given: 'the inventory persistence service returns a collection of data nodes' + def jsonString = '{"stores:bookstore":{"categories":[{"code":"01"}]}}' + JsonNode jsonNode = jsonObjectMapper.convertToJsonNode(jsonString); + def responseEntity = new ResponseEntity<>(jsonNode, HttpStatus.OK) + mockDmiDataOperations.getResourceDataFromDmi('cm-handle-123', DmiOperations.DataStoreEnum.PASSTHROUGH_OPERATIONAL, _) >> responseEntity + when: 'get resource data is called' + def result = objectUnderTest.getResourceData('cm-handle-123') + then: 'the returned data is correct' + result == jsonString + } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/YangModelCmHandleSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/YangModelCmHandleSpec.groovy index cdfcf59ef..3376691ee 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/YangModelCmHandleSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/YangModelCmHandleSpec.groovy @@ -22,7 +22,6 @@ package org.onap.cps.ncmp.api.models import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle import org.onap.cps.ncmp.api.inventory.CmHandleState -import org.onap.ncmp.cmhandle.lcm.event.Event import spock.lang.Specification import static org.onap.cps.ncmp.api.impl.operations.RequiredDmiService.DATA diff --git a/cps-service/src/main/java/org/onap/cps/utils/JsonObjectMapper.java b/cps-service/src/main/java/org/onap/cps/utils/JsonObjectMapper.java index 2459b51af..338a841a7 100644 --- a/cps-service/src/main/java/org/onap/cps/utils/JsonObjectMapper.java +++ b/cps-service/src/main/java/org/onap/cps/utils/JsonObjectMapper.java @@ -21,6 +21,7 @@ package org.onap.cps.utils; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -87,4 +88,20 @@ public class JsonObjectMapper { + "JSON content to specific class type.", e.getMessage()); } } + + /** + * Deserialize JSON content from given JSON content String to JsonNode. + * + * @param jsonContent JSON content + * @return a json node + */ + public JsonNode convertToJsonNode(final String jsonContent) { + try { + return objectMapper.readTree(jsonContent); + } catch (final JsonProcessingException e) { + log.error("Parsing error occurred while converting JSON content to Json Node."); + throw new DataValidationException("Parsing error occurred while converting " + + "JSON content to Json Node.", e.getMessage()); + } + } } diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/JsonObjectMapperSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/JsonObjectMapperSpec.groovy index f9b8febd4..acb52413f 100644 --- a/cps-service/src/test/groovy/org/onap/cps/utils/JsonObjectMapperSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/utils/JsonObjectMapperSpec.groovy @@ -84,4 +84,22 @@ class JsonObjectMapperSpec extends Specification { then: 'an exception is thrown' thrown(DataValidationException) } + + def 'Map a structurally compatible json String to JsonNode.'() { + given: 'Unstructured object' + def content = '{ "nest": { "birds": "bird" } }' + when: 'the object is mapped to string' + def result = jsonObjectMapper.convertToJsonNode(content); + then: 'the result is a valid JsonNode' + result.fieldNames().next() == "nest" + } + + def 'Map a unstructured json String to JsonNode.'() { + given: 'Unstructured object' + def content = '{ "nest": { "birds": "bird" }] }' + when: 'the object is mapped to string' + jsonObjectMapper.convertToJsonNode(content); + then: 'a data validation exception is thrown' + thrown(DataValidationException) + } } -- 2.16.6