Watchdog-process that changes CM Handles state 18/128718/17
authorDylanB95EST <dylan.byrne@est.tech>
Wed, 20 Apr 2022 09:35:11 +0000 (10:35 +0100)
committerDylanB95EST <dylan.byrne@est.tech>
Fri, 29 Apr 2022 10:59:29 +0000 (11:59 +0100)
Add a fixed delay scheduler to switch cm-handles found in an ADVISED
state to a READY state
Scheduler currently runs every 30 seconds
Will only update a single cm-handle at a time
Queries CM-Handle with Advised States Only using CPS Path.
Will choose cm handle at random

Issue-ID: CPS-875
Change-Id: Ie1b49c89a0350d20e14748a65f9c1d260d8502d2
Signed-off-by: DylanB95EST <dylan.byrne@est.tech>
12 files changed:
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/YangModelCmHandleRetriever.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandle.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncWatchdog.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/SyncUtils.java [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiOperationsBaseSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/YangModelCmHandleRetrieverSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncSpec.groovy [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/SyncUtilsSpec.groovy [new file with mode: 0644]
cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java

index e624953..c0f73d9 100755 (executable)
@@ -184,7 +184,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
         CpsValidator.validateNameCharacters(cmHandleId);
         final NcmpServiceCmHandle ncmpServiceCmHandle = new NcmpServiceCmHandle();
         final YangModelCmHandle yangModelCmHandle =
-            yangModelCmHandleRetriever.getDmiServiceNamesAndProperties(cmHandleId);
+            yangModelCmHandleRetriever.getYangModelCmHandle(cmHandleId);
         final List<YangModelCmHandle.Property> dmiProperties = yangModelCmHandle.getDmiProperties();
         final List<YangModelCmHandle.Property> publicProperties = yangModelCmHandle.getPublicProperties();
         ncmpServiceCmHandle.setCmHandleId(yangModelCmHandle.getId());
index ad85edd..f145379 100644 (file)
@@ -72,7 +72,7 @@ public class DmiDataOperations extends DmiOperations {
                                                          final String topicParamInQuery) {
         CpsValidator.validateNameCharacters(cmHandleId);
         final YangModelCmHandle yangModelCmHandle =
-                yangModelCmHandleRetriever.getDmiServiceNamesAndProperties(cmHandleId);
+                yangModelCmHandleRetriever.getYangModelCmHandle(cmHandleId);
         final DmiRequestBody dmiRequestBody = DmiRequestBody.builder()
             .operation(READ)
             .requestId(requestId)
@@ -104,7 +104,7 @@ public class DmiDataOperations extends DmiOperations {
                                                                              final String dataType) {
         CpsValidator.validateNameCharacters(cmHandleId);
         final YangModelCmHandle yangModelCmHandle =
-            yangModelCmHandleRetriever.getDmiServiceNamesAndProperties(cmHandleId);
+            yangModelCmHandleRetriever.getYangModelCmHandle(cmHandleId);
         final DmiRequestBody dmiRequestBody = DmiRequestBody.builder()
             .operation(operation)
             .data(requestData)
index 0efe8d5..b1ac91d 100644 (file)
@@ -48,7 +48,7 @@ public class YangModelCmHandleRetriever {
      * @param cmHandleId the id of the cm handle
      * @return yang model cm handle
      */
-    public YangModelCmHandle getDmiServiceNamesAndProperties(final String cmHandleId) {
+    public YangModelCmHandle getYangModelCmHandle(final String cmHandleId) {
         CpsValidator.validateNameCharacters(cmHandleId);
         final DataNode cmHandleDataNode = getCmHandleDataNode(cmHandleId);
         final NcmpServiceCmHandle ncmpServiceCmHandle = new NcmpServiceCmHandle();
index fd35281..289d782 100644 (file)
@@ -55,6 +55,9 @@ public class YangModelCmHandle {
     @JsonProperty("dmi-data-service-name")
     private String dmiDataServiceName;
 
+    @JsonProperty("state")
+    private String cmHandleState;
+
     @JsonProperty("dmi-model-service-name")
     private String dmiModelServiceName;
 
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
new file mode 100644 (file)
index 0000000..6941317
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * ============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 lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@EnableScheduling
+@RequiredArgsConstructor
+@Component
+public class ModuleSyncWatchdog {
+
+    private final SyncUtils syncUtils;
+
+    /**
+     * Execute Cm Handle poll which changes the cm handle state from 'ADVISED' to 'READY'.
+     */
+    @Scheduled(fixedDelay = 30000)
+    public void executeAdvisedCmHandlePoll() {
+        YangModelCmHandle newAdvisedCmHandle = syncUtils.getAnAdvisedCmHandle();
+        while (newAdvisedCmHandle != null) {
+            // ToDo When Cm-Handle in the 'ADVISED' state is Retrieved, Set CM-Handle state to 'LOCKED'
+            //  and give lock reason
+            // ToDo if lock fails, move to next cm handle.
+            // ToDo Update last update time with a timestamp everytime Cm-handle state is changed
+            syncUtils.updateCmHandleState(newAdvisedCmHandle, "READY");
+            log.info("{} is now in READY state", newAdvisedCmHandle.getId());
+            newAdvisedCmHandle = syncUtils.getAnAdvisedCmHandle();
+        }
+        log.debug("No Cm-Handles currently found in an ADVISED state");
+    }
+
+}
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
new file mode 100644 (file)
index 0000000..9245464
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * ============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 static org.onap.cps.ncmp.api.impl.constants.DmiRegistryConstants.NCMP_DATASPACE_NAME;
+import static org.onap.cps.ncmp.api.impl.constants.DmiRegistryConstants.NCMP_DMI_REGISTRY_ANCHOR;
+import static org.onap.cps.ncmp.api.impl.constants.DmiRegistryConstants.NCMP_DMI_REGISTRY_PARENT;
+
+import java.security.SecureRandom;
+import java.time.OffsetDateTime;
+import java.util.List;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.api.CpsDataService;
+import org.onap.cps.ncmp.api.impl.operations.YangModelCmHandleRetriever;
+import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
+import org.onap.cps.spi.CpsDataPersistenceService;
+import org.onap.cps.spi.FetchDescendantsOption;
+import org.onap.cps.spi.model.DataNode;
+import org.onap.cps.utils.JsonObjectMapper;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class SyncUtils {
+
+    private static final SecureRandom secureRandom = new SecureRandom();
+    private final CpsDataService cpsDataService;
+
+    private final CpsDataPersistenceService cpsDataPersistenceService;
+
+    private final JsonObjectMapper jsonObjectMapper;
+
+    private final YangModelCmHandleRetriever yangModelCmHandleRetriever;
+
+    /**
+     * Query data nodes for cm handles with an "ADVISED" cm handle state, and select a random entry for processing.
+     *
+     * @return a random yang model cm handle with an ADVISED state, return null if not found
+     */
+    public YangModelCmHandle getAnAdvisedCmHandle() {
+        final List<DataNode> advisedCmHandles = cpsDataPersistenceService.queryDataNodes("NCMP-Admin",
+            "ncmp-dmi-registry", "//cm-handles[@state=\"ADVISED\"]",
+            FetchDescendantsOption.OMIT_DESCENDANTS);
+        if (advisedCmHandles.isEmpty()) {
+            return null;
+        }
+        final int randomElementIndex = secureRandom.nextInt(advisedCmHandles.size());
+        final String cmHandleId = advisedCmHandles.get(randomElementIndex).getLeaves()
+            .get("id").toString();
+        return yangModelCmHandleRetriever.getYangModelCmHandle(cmHandleId);
+    }
+
+    /**
+     * Update the Cm Handle state to "READY".
+     *
+     * @param yangModelCmHandle yang model cm handle
+     * @param state cm handle state
+     */
+    public void updateCmHandleState(final YangModelCmHandle yangModelCmHandle, final String state) {
+        yangModelCmHandle.setCmHandleState(state);
+        final String cmHandleJsonData = String.format("{\"cm-handles\":[%s]}",
+            jsonObjectMapper.asJsonString(yangModelCmHandle));
+        cpsDataService.updateNodeLeaves(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, NCMP_DMI_REGISTRY_PARENT,
+            cmHandleJsonData, OffsetDateTime.now());
+    }
+
+}
index 7629500..65f007d 100644 (file)
@@ -74,7 +74,7 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
 
     def cmHandleXPath = "/dmi-registry/cm-handles[@id='testCmHandle']"
 
-    def dataNode = new DataNode(leaves: ['dmi-service-name': 'testDmiService'])
+    def dataNode = new DataNode(leaves: ['id': 'Some-Cm-Handle', 'dmi-service-name': 'testDmiService'])
 
     def 'Write resource data for pass-through running from DMI using POST #scenario cm handle properties.'() {
         given: 'cpsDataService returns valid datanode'
@@ -285,7 +285,7 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
             def dmiProperties = [new YangModelCmHandle.Property('Book', 'Romance Novel')]
             def publicProperties = [new YangModelCmHandle.Property('Public Book', 'Public Romance Novel')]
             def yangModelCmHandle = new YangModelCmHandle(id:'Some-Cm-Handle', dmiServiceName: dmiServiceName, dmiProperties: dmiProperties, publicProperties: publicProperties)
-            1 * mockYangModelCmHandleRetriever.getDmiServiceNamesAndProperties('Some-Cm-Handle') >> yangModelCmHandle
+            1 * mockYangModelCmHandleRetriever.getYangModelCmHandle('Some-Cm-Handle') >> yangModelCmHandle
         when: 'getting cm handle details for a given cm handle id from ncmp service'
             def result = objectUnderTest.getNcmpServiceCmHandle('Some-Cm-Handle')
         then: 'the result returns the correct data'
@@ -301,7 +301,7 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
         then: 'an exception is thrown'
             thrown(DataValidationException)
         and: 'the yang model cm handle retriever is not invoked'
-            0 * mockYangModelCmHandleRetriever.getDmiServiceNamesAndProperties(_)
+            0 * mockYangModelCmHandleRetriever.getYangModelCmHandle(_)
     }
 
     def 'Update resource data for pass-through running from dmi using POST #scenario DMI properties.'() {
index 563116f..dae2bcc 100644 (file)
@@ -56,6 +56,6 @@ abstract class DmiOperationsBaseSpec extends Specification {
         yangModelCmHandle.dmiServiceName = dmiServiceName
         yangModelCmHandle.dmiProperties = dmiProperties
         yangModelCmHandle.id = cmHandleId
-        mockCmHandlePropertiesRetriever.getDmiServiceNamesAndProperties(cmHandleId) >> yangModelCmHandle
+        mockCmHandlePropertiesRetriever.getYangModelCmHandle(cmHandleId) >> yangModelCmHandle
     }
 }
index bc30c9c..beea1fa 100644 (file)
@@ -54,7 +54,7 @@ class YangModelCmHandleRetrieverSpec extends Specification {
             def dataNode = new DataNode(childDataNodes:childDataNodes, leaves: leaves)
             mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry', xpath, INCLUDE_ALL_DESCENDANTS) >> dataNode
         when: 'retrieving the yang modelled cm handle'
-            def result = objectUnderTest.getDmiServiceNamesAndProperties(cmHandleId)
+            def result = objectUnderTest.getYangModelCmHandle(cmHandleId)
         then: 'the result has the correct id and service names'
             result.id == cmHandleId
             result.dmiServiceName == 'common service name'
@@ -73,7 +73,7 @@ class YangModelCmHandleRetrieverSpec extends Specification {
 
     def "Retrieve CmHandle using datanode with invalid CmHandle id."() {
         when: 'retrieving the yang modelled cm handle with an invalid id'
-            def result = objectUnderTest.getDmiServiceNamesAndProperties('cm handle id with spaces')
+            def result = objectUnderTest.getYangModelCmHandle('cm handle id with spaces')
         then: 'a data validation exception is thrown'
             thrown(DataValidationException)
         and: 'the result is not returned'
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
new file mode 100644 (file)
index 0000000..bcc6bb4
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * ============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.ncmp.api.impl.yangmodels.YangModelCmHandle
+import spock.lang.Specification
+
+class ModuleSyncSpec extends Specification {
+
+    def mockSyncUtils = Mock(SyncUtils)
+
+    def objectUnderTest = new ModuleSyncWatchdog(mockSyncUtils)
+
+    def 'Schedule a Cm-Handle Sync for ADVISED Cm-Handles'() {
+        given: 'a cm handle'
+            def yangModelCmHandle1 = new YangModelCmHandle()
+            def yangModelCmHandle2 = new YangModelCmHandle()
+        and: 'sync utilities return a cm handle twice'
+            mockSyncUtils.getAnAdvisedCmHandle() >>> [yangModelCmHandle1, yangModelCmHandle2, null]
+        when: 'module sync poll is executed'
+            objectUnderTest.executeAdvisedCmHandlePoll()
+        then: 'each cm handle is updated to state "READY"'
+            1 * mockSyncUtils.updateCmHandleState(yangModelCmHandle1, 'READY')
+            1 * mockSyncUtils.updateCmHandleState(yangModelCmHandle2, '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
new file mode 100644 (file)
index 0000000..04b2d55
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * ============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 com.fasterxml.jackson.databind.ObjectMapper
+import org.onap.cps.api.CpsDataService
+import org.onap.cps.ncmp.api.impl.operations.YangModelCmHandleRetriever
+import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
+import org.onap.cps.spi.CpsDataPersistenceService
+import org.onap.cps.spi.FetchDescendantsOption
+import org.onap.cps.spi.model.DataNode
+import org.onap.cps.utils.JsonObjectMapper
+import spock.lang.Shared
+import spock.lang.Specification
+
+import java.time.OffsetDateTime
+
+class SyncUtilsSpec extends Specification{
+
+    def mockCpsDataService = Mock(CpsDataService)
+    def mockCpsDataPersistenceService = Mock(CpsDataPersistenceService)
+    def spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper()))
+    def mockYangModelCmHandleRetriever = Mock(YangModelCmHandleRetriever)
+
+    def objectUnderTest = new SyncUtils(mockCpsDataService, mockCpsDataPersistenceService, spiedJsonObjectMapper, mockYangModelCmHandleRetriever)
+
+    @Shared
+    def dataNode = new DataNode(leaves: ['id': 'cm-handle-123'])
+
+
+
+    def 'Get an advised Cm-Handle where ADVISED cm handle #scenario'() {
+        given: 'the cps (persistence service) returns a collection of data nodes'
+            mockCpsDataPersistenceService.queryDataNodes('NCMP-Admin',
+                'ncmp-dmi-registry', '//cm-handles[@state=\"ADVISED\"]',
+                FetchDescendantsOption.OMIT_DESCENDANTS) >> dataNodeCollection
+        when: 'get advised cm handle is called'
+            objectUnderTest.getAnAdvisedCmHandle()
+        then: 'the returned data node collection is the correct size'
+            dataNodeCollection.size() == expectedDataNodeSize
+        and: 'get yang model cm handles is invoked the correct number of times'
+           expectedCallsToGetYangModelCmHandle * mockYangModelCmHandleRetriever.getYangModelCmHandle('cm-handle-123')
+        where: 'the following scenarios are used'
+            scenario         | dataNodeCollection || expectedCallsToGetYangModelCmHandle | expectedDataNodeSize
+            'exists'         | [ dataNode ]       || 1                                   | 1
+            'does not exist' | [ ]                || 0                                   | 0
+
+    }
+
+    def 'Update cm handle state from Advised to Ready'() {
+        given: 'a yang model cm handle and the expected json data'
+            def yangModelCmHandle = new YangModelCmHandle('id': 'Some-Cm-Handle', 'cmHandleState': 'ADVISED')
+            def expectedJsonData = '{"cm-handles":[{"id":"Some-Cm-Handle","state":"READY"}]}'
+        when: 'update cm handle state is called'
+            objectUnderTest.updateCmHandleState(yangModelCmHandle, 'READY')
+        then: 'update data note leaves is invoked with the correct params'
+            1 * mockCpsDataService.updateNodeLeaves('NCMP-Admin', 'ncmp-dmi-registry', '/dmi-registry', expectedJsonData, _ as OffsetDateTime)
+    }
+
+}
index fdcf15b..06da3ff 100644 (file)
@@ -23,6 +23,7 @@
 package org.onap.cps.spi;
 
 import java.util.Collection;
+import java.util.List;
 import java.util.Map;
 import org.onap.cps.spi.model.DataNode;
 
@@ -145,8 +146,8 @@ public interface CpsDataPersistenceService {
      *                               included in the output
      * @return the data nodes found i.e. 0 or more data nodes
      */
-    Collection<DataNode> queryDataNodes(String dataspaceName, String anchorName,
-        String cpsPath, FetchDescendantsOption fetchDescendantsOption);
+    List<DataNode> queryDataNodes(String dataspaceName, String anchorName,
+                                  String cpsPath, FetchDescendantsOption fetchDescendantsOption);
 
     /**
      * Starts a session which allows use of locks and batch interaction with the persistence service.