Allow separate registration of DMIDataPlugin and DmiModelPugin 20/125720/12
authorJosephKeenan <joseph.keenan@est.tech>
Tue, 23 Nov 2021 12:18:28 +0000 (12:18 +0000)
committerJosephKeenan <joseph.keenan@est.tech>
Thu, 25 Nov 2021 15:40:10 +0000 (15:40 +0000)
Moved relevant code from NetworkCmProxyDataServiceImp to DmiOperations
Split DmiOperations into DMiData... and DMIModelOperations
Merged update-operation changes
Added tests for error message validation in NetworkCmProxyDataServiceImplSpec
Removede @Service from DMIOperations and added @component to
DmiDataOperations & DmiModelOperations
Verify sync robot test is now hardened
Added exitonfailure so robot tests stop after first encountered failed
test

Issue-ID: CPS-736
Change-Id: I0b40931cc8cd4fc0452328a0a7e0f60e6fc38d0a
Signed-off-by: JosephKeenan <joseph.keenan@est.tech>
Signed-off-by: ToineSiebelink <toine.siebelink@est.tech>
Signed-off-by: DylanB95EST <dylan.byrne@est.tech>
36 files changed:
cps-application/src/main/resources/application.yml
cps-ncmp-rest/docs/openapi/components.yaml
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java
cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy
cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryControllerSpec.groovy
cps-ncmp-rest/src/test/resources/dmi_registration_combined_valid.json [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyDataService.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/client/DmiRestClient.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/NcmpConfiguration.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operation/DmiOperations.java [deleted file]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiModelOperations.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiOperations.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiRequestBody.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/GenericRequestBody.java with 57% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/PersistenceCmHandleRetriever.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/RequiredDmiService.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmHandle.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/DmiPluginRegistration.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/PersistenceCmHandle.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/PersistenceCmHandlesList.java
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplModelSyncSpec.groovy [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy [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/operation/DmiOperationsSpec.groovy [deleted file]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiDataOperationsSpec.groovy [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiModelOperationsSpec.groovy [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiOperationsBaseSpec.groovy [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/PersistenceCmHandleRetrieverSpec.groovy [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/PersistenceCmHandleSpec.groovy
cps-ncmp-service/src/test/resources/application.yml
cps-ri/src/main/resources/changelog/db/changes/09-loadData-dmi-registry-schema-set.yaml
cps-ri/src/main/resources/changelog/db/changes/data/dmi/schema_set_yang_resources@2021-10-20.csv [new file with mode: 0644]
cps-ri/src/main/resources/changelog/db/changes/data/dmi/yang_resource@2021-10-20.csv [new file with mode: 0644]
csit/plans/cps/setup.sh
csit/tests/cps-model-sync/cps-model-sync.robot

index 6717d4e..1411f31 100644 (file)
@@ -130,3 +130,5 @@ dmi:
     auth:\r
         username: ${DMI_USERNAME}\r
         password: ${DMI_PASSWORD}\r
+    api:\r
+        base-path: /dmi\r
index 7c9e1e8..da1878f 100644 (file)
@@ -38,6 +38,12 @@ components:
         dmiPlugin:
           type: string
           example: onap-dmi-plugin
+        dmiDataPlugin:
+          type: string
+          example: onap-dmi-data-plugin
+        dmiModelPlugin:
+          type: string
+          example: onap-dmi-model-plugin
         createdCmHandles:
           type: array
           items:
index 449a434..222957c 100755 (executable)
@@ -22,6 +22,9 @@
 
 package org.onap.cps.ncmp.rest.controller;
 
+import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.CREATE;
+import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.UPDATE;
+
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
 import java.util.ArrayList;
@@ -132,14 +135,6 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
         return new ResponseEntity<>(HttpStatus.OK);
     }
 
-    @Override
-    public ResponseEntity<Object> updateResourceDataRunningForCmHandle(final String resourceIdentifier,
-        final String cmHandle, final String requestBody, final String contentType) {
-        networkCmProxyDataService.updateResourceDataPassThroughRunningForCmHandle(cmHandle,
-            resourceIdentifier, requestBody, contentType);
-        return new ResponseEntity<>(HttpStatus.OK);
-    }
-
     /**
      * Update Node Leaves.
      * @deprecated This Method is no longer used as part of NCMP.
@@ -195,25 +190,49 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
     }
 
     /**
-     * Create resource data in datastore pass through running
-     * for given cm-handle.
+     * Create resource data in datastore pass through running for given cm-handle.
      *
      * @param resourceIdentifier resource identifier
      * @param cmHandle cm handle identifier
-     * @param requestBody requestBody
+     * @param requestBody the request body
      * @param contentType content type of body
-     * @return {@code ResponseEntity} response from dmi plugi
+     * @return {@code ResponseEntity} response from dmi plugin
      */
     @Override
     public ResponseEntity<Void> createResourceDataRunningForCmHandle(final String resourceIdentifier,
                                                                      final String cmHandle,
                                                                      final String requestBody,
                                                                      final String contentType) {
-        networkCmProxyDataService.createResourceDataPassThroughRunningForCmHandle(cmHandle,
-                resourceIdentifier, requestBody, contentType);
+        networkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle(cmHandle,
+                resourceIdentifier, CREATE, requestBody, contentType);
         return new ResponseEntity<>(HttpStatus.CREATED);
     }
 
+    /**
+     * Update resource data in datastore pass through running for given cm-handle.
+     *
+     * @param resourceIdentifier resource identifier
+     * @param cmHandle cm handle identifier
+     * @param requestBody the request body
+     * @param contentType content type of the body
+     * @return response entity
+     */
+    @Override
+    public ResponseEntity<Object> updateResourceDataRunningForCmHandle(final String resourceIdentifier,
+                                                                       final String cmHandle,
+                                                                       final String requestBody,
+                                                                       final String contentType) {
+        networkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle(cmHandle,
+            resourceIdentifier, UPDATE, requestBody, contentType);
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+    /**
+     * Execute cm handle search.
+     *
+     * @param conditions the conditions
+     * @return cm handles returned from search.
+     */
     @Override
     public ResponseEntity<CmHandles> executeCmHandleSearch(final Conditions conditions) {
         final List<ConditionProperties> conditionProperties =
@@ -223,6 +242,12 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
         return ResponseEntity.ok(cmHandles);
     }
 
+    /**
+     * Return module references for a cm handle.
+     *
+     * @param cmHandle the cm handle
+     * @return module references for cm handle
+     */
     @Override
     public ResponseEntity<Object> getModuleReferencesByCmHandle(final String cmHandle) {
         final Collection<ModuleReference>
@@ -264,6 +289,4 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
         }
         return cmHandleProperties;
     }
-
-
 }
index e96b27d..436f22b 100644 (file)
@@ -31,6 +31,8 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put
+import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.CREATE
+import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.UPDATE
 
 import com.google.gson.Gson
 import org.onap.cps.ncmp.api.NetworkCmProxyDataService
@@ -219,19 +221,36 @@ class NetworkCmProxyControllerSpec extends Specification {
             '? needs to be encoded as %3F' | 'idWith%3F'
     }
 
+    def 'Update resource data from passthrough running.' () {
+        given: 'update resource data url'
+            def updateUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" +
+                "?resourceIdentifier=parent/child"
+        when: 'update data resource request is performed'
+            def response = mvc.perform(
+                put(updateUrl)
+                    .contentType(MediaType.APPLICATION_JSON_VALUE)
+                    .accept(MediaType.APPLICATION_JSON_VALUE).content('some-request-body')
+            ).andReturn().response
+        then: 'ncmp service method to update resource is called'
+            1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
+                'parent/child', UPDATE,'some-request-body', 'application/json;charset=UTF-8')
+        and: 'the response status is OK'
+            response.status == HttpStatus.OK.value()
+    }
+
     def 'Create Resource Data from passthrough running with #scenario.' () {
         given: 'resource data url'
-            def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" +
+            def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" +
                     "?resourceIdentifier=parent/child"
-        when: 'get data resource request is performed'
+        when: 'create resource request is performed'
             def response = mvc.perform(
-                    post(getUrl)
+                    post(url)
                             .contentType(MediaType.APPLICATION_JSON_VALUE)
                             .accept(MediaType.APPLICATION_JSON_VALUE).content(requestBody)
             ).andReturn().response
         then: 'ncmp service method to create resource called'
-            1 * mockNetworkCmProxyDataService.createResourceDataPassThroughRunningForCmHandle('testCmHandle',
-                    'parent/child', requestBody, 'application/json;charset=UTF-8')
+            1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
+                'parent/child', CREATE, requestBody, 'application/json;charset=UTF-8')
         and: 'resource is created'
             response.status == HttpStatus.CREATED.value()
         where: 'given request body'
@@ -282,21 +301,5 @@ class NetworkCmProxyControllerSpec extends Specification {
             response.contentAsString == '{"cmHandles":[]}'
     }
 
-    def 'Update resource data from passthrough running.' () {
-        given: 'update resource data url'
-            def updateUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" +
-                    "?resourceIdentifier=parent/child"
-        when: 'update data resource request is performed'
-            def response = mvc.perform(
-                    put(updateUrl)
-                            .contentType(MediaType.APPLICATION_JSON_VALUE)
-                            .accept(MediaType.APPLICATION_JSON_VALUE).content('some-request-body')
-            ).andReturn().response
-        then: 'ncmp service method to update resource is called'
-            1 * mockNetworkCmProxyDataService.updateResourceDataPassThroughRunningForCmHandle('testCmHandle',
-                    'parent/child', 'some-request-body', 'application/json;charset=UTF-8')
-        and: 'the response status is OK'
-            response.status == HttpStatus.OK.value()
-    }
 }
 
index e558ac4..4addf7b 100644 (file)
@@ -22,6 +22,10 @@ package org.onap.cps.ncmp.rest.controller
 import com.fasterxml.jackson.databind.ObjectMapper
 import org.onap.cps.TestUtils
 import org.onap.cps.ncmp.api.NetworkCmProxyDataService
+import org.onap.cps.ncmp.api.impl.NetworkCmProxyDataServiceImpl
+import org.onap.cps.ncmp.api.models.CmHandle
+import org.onap.cps.ncmp.api.models.DmiPluginRegistration
+import org.onap.cps.ncmp.api.models.PersistenceCmHandle
 import org.spockframework.spring.SpringBean
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.beans.factory.annotation.Value
@@ -61,5 +65,28 @@ class NetworkCmProxyInventoryControllerSpec extends Specification {
             response.status == HttpStatus.CREATED.value()
     }
 
+    def 'Dmi plugin registration with #scenario' () {
+        given: 'jsonData, cmHandle, & DmiPluginRegistration'
+            def jsonData = TestUtils.getResourceFileContent('dmi_registration_combined_valid.json' )
+            def cmHandle = new CmHandle(cmHandleID : 'example-name')
+            def expectedDmiPluginRegistration = new DmiPluginRegistration(
+                dmiPlugin: 'service1',
+                dmiDataPlugin: '',
+                dmiModelPlugin: '',
+                createdCmHandles: [cmHandle])
+        when: 'post request is performed & registration is called with correct DMI plugin information'
+            def response = mvc.perform(
+                post("$ncmpBasePathV1/ch")
+                    .contentType(MediaType.APPLICATION_JSON)
+                    .content(jsonData)
+            ).andReturn().response
+        then: 'no NcmpException is thrown & updateDmiRegistrationAndSyncModule is called with correct parameters'
+            1 * mockNetworkCmProxyDataService.updateDmiRegistrationAndSyncModule({
+                it.getDmiPlugin() == expectedDmiPluginRegistration.getDmiPlugin()
+                it.getDmiDataPlugin() == expectedDmiPluginRegistration.getDmiDataPlugin()
+                it.getDmiModelPlugin() == expectedDmiPluginRegistration.getDmiModelPlugin()
+                it.getCreatedCmHandles().get(0).getCmHandleID() == expectedDmiPluginRegistration.getCreatedCmHandles().get(0).getCmHandleID()
+            })
+    }
 }
 
diff --git a/cps-ncmp-rest/src/test/resources/dmi_registration_combined_valid.json b/cps-ncmp-rest/src/test/resources/dmi_registration_combined_valid.json
new file mode 100644 (file)
index 0000000..bded4f2
--- /dev/null
@@ -0,0 +1,8 @@
+{
+  "dmiPlugin": "service1",
+  "dmiDataPlugin": "",
+  "dmiModelPlugin": "",
+  "createdCmHandles": [{
+    "cmHandle": "example-name"
+  }]
+}
\ No newline at end of file
index 45d5bd9..ec816ed 100644 (file)
@@ -22,8 +22,9 @@
 
 package org.onap.cps.ncmp.api;
 
+import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum;
+
 import java.util.Collection;
-import javax.validation.constraints.NotNull;
 import org.checkerframework.checker.nullness.qual.NonNull;
 import org.onap.cps.ncmp.api.models.DmiPluginRegistration;
 import org.onap.cps.spi.FetchDescendantsOption;
@@ -118,8 +119,8 @@ public interface NetworkCmProxyDataService {
      * @param optionsParamInQuery options query
      * @return {@code Object} resource data
      */
-    Object getResourceDataOperationalForCmHandle(@NotNull String cmHandle,
-                                                 @NotNull String resourceIdentifier,
+    Object getResourceDataOperationalForCmHandle(String cmHandle,
+                                                 String resourceIdentifier,
                                                  String acceptParamInHeader,
                                                  String optionsParamInQuery);
 
@@ -133,24 +134,25 @@ public interface NetworkCmProxyDataService {
      * @param optionsParamInQuery options query
      * @return {@code Object} resource data
      */
-    Object getResourceDataPassThroughRunningForCmHandle(@NotNull String cmHandle,
-                                                        @NotNull String resourceIdentifier,
+    Object getResourceDataPassThroughRunningForCmHandle(String cmHandle,
+                                                        String resourceIdentifier,
                                                         String acceptParamInHeader,
                                                         String optionsParamInQuery);
 
     /**
-     * Create resource data for data store pass-through running
+     * Write resource data for data store pass-through running
      * using dmi for given cm-handle.
-     *
-     * @param cmHandle cm handle
+     *  @param cmHandle cm handle
      * @param resourceIdentifier resource identifier
+     * @param operation required operation
      * @param requestBody request body to create resource
      * @param contentType content type in body
      */
-    void createResourceDataPassThroughRunningForCmHandle(@NotNull String cmHandle,
-                                                         @NotNull String resourceIdentifier,
-                                                         @NotNull String requestBody,
-                                                         String contentType);
+    void writeResourceDataPassThroughRunningForCmHandle(String cmHandle,
+                                                        String resourceIdentifier,
+                                                        OperationEnum operation,
+                                                        String requestBody,
+                                                        String contentType);
 
     /**
      * Retrieve module references for the given cm handle.
@@ -158,7 +160,7 @@ public interface NetworkCmProxyDataService {
      * @param cmHandle cm handle
      * @return a collection of modules names and revisions
      */
-    Collection<ModuleReference> getYangResourcesModuleReferences(@NotNull String cmHandle);
+    Collection<ModuleReference> getYangResourcesModuleReferences(String cmHandle);
 
     /**
      * Query cm handle identifiers for the given collection of module names.
@@ -169,14 +171,4 @@ public interface NetworkCmProxyDataService {
      */
     Collection<String> executeCmHandleHasAllModulesSearch(Collection<String> moduleNames);
 
-    /**
-     * Update resource data for data store pass-through running using dmi for the given cm-handle.
-     *
-     * @param cmHandle cm handle
-     * @param resourceIdentifier resource identifier
-     * @param requestBody request body to create resource
-     * @param contentType content type in body
-     */
-    void updateResourceDataPassThroughRunningForCmHandle(String cmHandle, String resourceIdentifier,
-        String requestBody, String contentType);
 }
index 80cd297..fb929a2 100755 (executable)
@@ -23,6 +23,8 @@
 
 package org.onap.cps.ncmp.api.impl;
 
+import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum;
+
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.google.gson.Gson;
@@ -32,12 +34,9 @@ import com.google.gson.JsonObject;
 import java.time.OffsetDateTime;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.HashMap;
-import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
-import javax.validation.constraints.NotNull;
 import lombok.extern.slf4j.Slf4j;
 import org.onap.cps.api.CpsAdminService;
 import org.onap.cps.api.CpsDataService;
@@ -45,13 +44,12 @@ import org.onap.cps.api.CpsModuleService;
 import org.onap.cps.api.CpsQueryService;
 import org.onap.cps.ncmp.api.NetworkCmProxyDataService;
 import org.onap.cps.ncmp.api.impl.exception.NcmpException;
-import org.onap.cps.ncmp.api.impl.operation.DmiOperations;
+import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations;
+import org.onap.cps.ncmp.api.impl.operations.DmiModelOperations;
+import org.onap.cps.ncmp.api.impl.operations.DmiOperations;
 import org.onap.cps.ncmp.api.models.CmHandle;
 import org.onap.cps.ncmp.api.models.DmiPluginRegistration;
-import org.onap.cps.ncmp.api.models.GenericRequestBody;
-import org.onap.cps.ncmp.api.models.GenericRequestBody.OperationEnum;
 import org.onap.cps.ncmp.api.models.PersistenceCmHandle;
-import org.onap.cps.ncmp.api.models.PersistenceCmHandle.AdditionalProperty;
 import org.onap.cps.ncmp.api.models.PersistenceCmHandlesList;
 import org.onap.cps.ncmp.api.models.YangResource;
 import org.onap.cps.spi.FetchDescendantsOption;
@@ -59,7 +57,6 @@ import org.onap.cps.spi.exceptions.DataNodeNotFoundException;
 import org.onap.cps.spi.exceptions.DataValidationException;
 import org.onap.cps.spi.model.DataNode;
 import org.onap.cps.spi.model.ModuleReference;
-import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.stereotype.Service;
 import org.springframework.util.StringUtils;
@@ -76,17 +73,15 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
 
     private static final OffsetDateTime NO_TIMESTAMP = null;
 
-    private static  final String NCMP_DMI_SERVICE_NAME = "dmi-service-name";
-
-    private  static final String REVISION = "revision";
-
     private CpsDataService cpsDataService;
 
     private ObjectMapper objectMapper;
 
     private CpsQueryService cpsQueryService;
 
-    private DmiOperations dmiOperations;
+    private DmiDataOperations dmiDataOperations;
+
+    private DmiModelOperations dmiModelOperations;
 
     private CpsModuleService cpsModuleService;
 
@@ -94,18 +89,20 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
 
     /**
      * Constructor Injection for Dependencies.
-     * @param dmiOperations DMI operation
+     * @param dmiDataOperations DMI operation
      * @param cpsDataService Data Service Interface
      * @param cpsQueryService Query Service Interface
      * @param objectMapper Object Mapper
      */
-    public NetworkCmProxyDataServiceImpl(final DmiOperations dmiOperations,
-        final CpsModuleService cpsModuleService,
-        final CpsDataService cpsDataService,
-        final CpsQueryService cpsQueryService,
-        final CpsAdminService cpsAdminService,
-        final ObjectMapper objectMapper) {
-        this.dmiOperations = dmiOperations;
+    public NetworkCmProxyDataServiceImpl(final DmiDataOperations dmiDataOperations,
+                                         final DmiModelOperations dmiModelOperations,
+                                         final CpsModuleService cpsModuleService,
+                                         final CpsDataService cpsDataService,
+                                         final CpsQueryService cpsQueryService,
+                                         final CpsAdminService cpsAdminService,
+                                         final ObjectMapper objectMapper) {
+        this.dmiDataOperations = dmiDataOperations;
+        this.dmiModelOperations = dmiModelOperations;
         this.cpsModuleService = cpsModuleService;
         this.cpsDataService = cpsDataService;
         this.cpsQueryService = cpsQueryService;
@@ -158,9 +155,10 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
 
     @Override
     public void updateDmiRegistrationAndSyncModule(final DmiPluginRegistration dmiPluginRegistration) {
+        dmiPluginRegistration.validateDmiPluginRegistration();
         try {
             if (dmiPluginRegistration.getCreatedCmHandles() != null) {
-                parseAndCreateCmHandlesInDmiRegistrationAndSyncModule(dmiPluginRegistration);
+                parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(dmiPluginRegistration);
             }
             if (dmiPluginRegistration.getUpdatedCmHandles() != null) {
                 parseAndUpdateCmHandlesInDmiRegistration(dmiPluginRegistration);
@@ -174,64 +172,44 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
     }
 
     @Override
-    public Object getResourceDataOperationalForCmHandle(final @NotNull String cmHandle,
-                                                        final @NotNull String resourceIdentifier,
+    public Object getResourceDataOperationalForCmHandle(final String cmHandle,
+                                                        final String resourceIdentifier,
                                                         final String acceptParamInHeader,
                                                         final String optionsParamInQuery) {
-
-        final DataNode cmHandleDataNode = fetchDataNodeFromDmiRegistryForCmHandle(cmHandle);
-        final String dmiServiceName = String.valueOf(cmHandleDataNode.getLeaves().get(NCMP_DMI_SERVICE_NAME));
-        final String dmiRequestBody = getGenericRequestBody(cmHandleDataNode);
-        final ResponseEntity<Object> response = dmiOperations.getResourceDataOperationalFromDmi(dmiServiceName,
-                cmHandle,
-                resourceIdentifier,
-                optionsParamInQuery,
-                acceptParamInHeader,
-                dmiRequestBody);
-        return handleResponse(response);
+        return handleResponse(dmiDataOperations.getResourceDataFromDmi(
+            cmHandle,
+            resourceIdentifier,
+            optionsParamInQuery,
+            acceptParamInHeader,
+            DmiOperations.DataStoreEnum.PASSTHROUGH_OPERATIONAL), "Not able to get resource data.");
     }
 
     @Override
-    public Object getResourceDataPassThroughRunningForCmHandle(final @NotNull String cmHandle,
-                                                               final @NotNull String resourceIdentifier,
+    public Object getResourceDataPassThroughRunningForCmHandle(final String cmHandle,
+                                                               final String resourceIdentifier,
                                                                final String acceptParamInHeader,
                                                                final String optionsParamInQuery) {
-        final DataNode cmHandleDataNode = fetchDataNodeFromDmiRegistryForCmHandle(cmHandle);
-        final String dmiServiceName = String.valueOf(cmHandleDataNode.getLeaves().get(NCMP_DMI_SERVICE_NAME));
-        final String dmiRequestBody = getGenericRequestBody(cmHandleDataNode);
-        final ResponseEntity<Object> response = dmiOperations.getResourceDataPassThroughRunningFromDmi(dmiServiceName,
-                cmHandle,
-                resourceIdentifier,
-                optionsParamInQuery,
-                acceptParamInHeader,
-                dmiRequestBody);
-        return handleResponse(response);
+        return handleResponse(dmiDataOperations.getResourceDataFromDmi(
+            cmHandle,
+            resourceIdentifier,
+            optionsParamInQuery,
+            acceptParamInHeader,
+            DmiOperations.DataStoreEnum.PASSTHROUGH_RUNNING), "Not able to get resource data.");
     }
 
     @Override
-    public void createResourceDataPassThroughRunningForCmHandle(final @NotNull String cmHandle,
-                                                                final @NotNull String resourceIdentifier,
-                                                                final @NotNull String requestBody,
-                                                                final String contentType) {
-        final DataNode cmHandleDataNode = fetchDataNodeFromDmiRegistryForCmHandle(cmHandle);
-        final String dmiServiceName = String.valueOf(cmHandleDataNode.getLeaves().get(NCMP_DMI_SERVICE_NAME));
-        final Collection<DataNode> cmHandlePropertiesAsDataNodes     = cmHandleDataNode.getChildDataNodes();
-        final Map<String, String> cmHandlePropertiesAsMap = getCmHandlePropertiesAsMap(cmHandlePropertiesAsDataNodes);
-        final GenericRequestBody dmiRequestBodyObject = GenericRequestBody.builder()
-                .operation(GenericRequestBody.OperationEnum.CREATE)
-                .dataType(contentType)
-                .data(requestBody)
-                .cmHandleProperties(cmHandlePropertiesAsMap)
-                .build();
-        final String dmiRequestBody = prepareOperationBody(dmiRequestBodyObject);
-        final ResponseEntity<String> responseEntity = dmiOperations
-                .createResourceDataPassThroughRunningFromDmi(dmiServiceName,
-                        cmHandle,
-                        resourceIdentifier,
-                        dmiRequestBody);
-        handleResponseFromDmi(responseEntity, "Not able to create resource data.");
+    public void writeResourceDataPassThroughRunningForCmHandle(final String cmHandle,
+                                                               final String resourceIdentifier,
+                                                               final OperationEnum operation,
+                                                               final String requestData,
+                                                               final String dataType) {
+        handleResponse(
+            dmiDataOperations.writeResourceDataPassThroughRunningFromDmi(
+                cmHandle, resourceIdentifier, operation, requestData, dataType),
+            "Not able to " + operation + " resource data.");
     }
 
+
     @Override
     public Collection<ModuleReference> getYangResourcesModuleReferences(final String cmHandle) {
         return cpsModuleService.getYangResourcesModuleReferences(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, cmHandle);
@@ -249,134 +227,46 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
     }
 
     /**
-     * Update resource data for data store pass-through running using dmi for given cm-handle.
+     * THis method registers a cm handle and intiates modules sync.
      *
-     * @param cmHandle           cm handle
-     * @param resourceIdentifier resource identifier
-     * @param requestBody        request body to create resource
-     * @param contentType        content type in body
+     * @param dmiPluginRegistration dmi plugin registration information.
+     * @throws JsonProcessingException thrown if json is malformed or missing.
      */
-    @Override
-    public void updateResourceDataPassThroughRunningForCmHandle(final String cmHandle, final String resourceIdentifier,
-        final String requestBody, final String contentType) {
-        final DataNode cmHandleDataNode = fetchDataNodeFromDmiRegistryForCmHandle(cmHandle);
-        final String dmiServiceName = String.valueOf(cmHandleDataNode.getLeaves().get(NCMP_DMI_SERVICE_NAME));
-        final Collection<DataNode> cmHandlePropertiesAsDataNodes = cmHandleDataNode.getChildDataNodes();
-        final Map<String, String> cmHandlePropertiesAsMap = getCmHandlePropertiesAsMap(cmHandlePropertiesAsDataNodes);
-        final GenericRequestBody dmiRequestBodyObject = GenericRequestBody.builder()
-            .operation(OperationEnum.UPDATE)
-            .dataType(contentType)
-            .data(requestBody)
-            .cmHandleProperties(cmHandlePropertiesAsMap)
-            .build();
-        final String dmiRequestBody = prepareOperationBody(dmiRequestBodyObject);
-        final ResponseEntity<String> responseEntity = dmiOperations
-            .updateResourceDataPassThroughRunningFromDmi(dmiServiceName,
-                cmHandle,
-                resourceIdentifier,
-                dmiRequestBody);
-        handleResponseFromDmi(responseEntity, "Unable to replace resource data.");
-    }
-
-    private DataNode fetchDataNodeFromDmiRegistryForCmHandle(final String cmHandle) {
-        final String xpathForDmiRegistryToFetchCmHandle = "/dmi-registry/cm-handles[@id='" + cmHandle + "']";
-        return cpsDataService.getDataNode(NCMP_DATASPACE_NAME,
-                NCMP_DMI_REGISTRY_ANCHOR,
-                xpathForDmiRegistryToFetchCmHandle,
-                FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS);
-    }
-
-    private String prepareOperationBody(final GenericRequestBody requestBodyObject) {
-        try {
-            return objectMapper.writeValueAsString(requestBodyObject);
-        } catch (final JsonProcessingException e) {
-            log.error("Parsing error occurred while converting Object to JSON.");
-            throw new NcmpException("Parsing error occurred while converting given object to JSON.",
-                e.getMessage());
-        }
-    }
-
-    private static Map<String, String> getCmHandlePropertiesAsMap(
-            final Collection<DataNode> cmHandlePropertiesAsDataNode) {
-        if (cmHandlePropertiesAsDataNode.isEmpty()) {
-            return Collections.emptyMap();
-        }
-        final Map<String, String> cmHandlePropertiesAsMap = new LinkedHashMap<>();
-        for (final DataNode dataNode: cmHandlePropertiesAsDataNode) {
-            cmHandlePropertiesAsMap.put(String.valueOf(dataNode.getLeaves().get("name")),
-                    String.valueOf(dataNode.getLeaves().get("value")));
-        }
-        return cmHandlePropertiesAsMap;
-    }
-
-    private static Map<String, String> getCmHandlePropertiesAsMap(
-            final List<AdditionalProperty> cmHandlePropertiesAsList) {
-        if (cmHandlePropertiesAsList == null || cmHandlePropertiesAsList.isEmpty()) {
-            return Collections.emptyMap();
-        }
-        final Map<String, String> cmHandlePropertiesAsMap = new LinkedHashMap<>();
-        for (final AdditionalProperty additionalProperty: cmHandlePropertiesAsList) {
-            cmHandlePropertiesAsMap.put(additionalProperty.getName(),
-                    additionalProperty.getValue());
-        }
-        return cmHandlePropertiesAsMap;
+    public void parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(
+        final DmiPluginRegistration dmiPluginRegistration) throws JsonProcessingException {
+        final PersistenceCmHandlesList createdPersistenceCmHandlesList =
+            getUpdatedPersistenceCmHandlesList(dmiPluginRegistration, dmiPluginRegistration.getCreatedCmHandles());
+        registerAndSyncNewCmHandles(createdPersistenceCmHandlesList);
     }
 
-    private static Object handleResponse(final @NotNull ResponseEntity<Object> responseEntity) {
-        if (responseEntity.getStatusCode() == HttpStatus.OK) {
+    private static Object handleResponse(final ResponseEntity<?> responseEntity,
+                                         final String exceptionMessage) {
+        if (responseEntity.getStatusCode().is2xxSuccessful()) {
             return responseEntity.getBody();
         } else {
-            throw new NcmpException("Not able to get resource data.",
+            throw new NcmpException(exceptionMessage,
                     "DMI status code: " + responseEntity.getStatusCodeValue()
                             + ", DMI response body: " + responseEntity.getBody());
         }
     }
 
-    private static void handleResponseFromDmi(final @NotNull ResponseEntity<String> responseEntity,
-        final String exceptionMessage) {
-        if (!HttpStatus.valueOf(responseEntity.getStatusCodeValue()).is2xxSuccessful()) {
-            throw new NcmpException(exceptionMessage,
-                "DMI status code: " + responseEntity.getStatusCodeValue()
-                    + ", DMI response body: " + responseEntity.getBody());
-        }
-    }
-
-    private String getGenericRequestBody(final DataNode cmHandleDataNode) {
-        final Collection<DataNode> cmHandlePropertiesAsDataNodes = cmHandleDataNode.getChildDataNodes();
-        final Map<String, String> cmHandlePropertiesAsMap = getCmHandlePropertiesAsMap(cmHandlePropertiesAsDataNodes);
-        final GenericRequestBody requestBodyObject = GenericRequestBody.builder()
-                .operation(GenericRequestBody.OperationEnum.READ)
-                .cmHandleProperties(cmHandlePropertiesAsMap)
-                .build();
-        return prepareOperationBody(requestBodyObject);
-    }
-
     private void parseAndUpdateCmHandlesInDmiRegistration(final DmiPluginRegistration dmiPluginRegistration)
         throws JsonProcessingException {
-        final PersistenceCmHandlesList updatedPersistenceCmHandlesList = toPersistenceCmHandlesList(
-            dmiPluginRegistration.getDmiPlugin(),
-            dmiPluginRegistration.getUpdatedCmHandles());
+        final PersistenceCmHandlesList updatedPersistenceCmHandlesList =
+            getUpdatedPersistenceCmHandlesList(dmiPluginRegistration, dmiPluginRegistration.getUpdatedCmHandles());
         final String cmHandlesAsJson = objectMapper.writeValueAsString(updatedPersistenceCmHandlesList);
         cpsDataService.updateNodeLeavesAndExistingDescendantLeaves(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
                 "/dmi-registry", cmHandlesAsJson, NO_TIMESTAMP);
     }
 
-    private void parseAndCreateCmHandlesInDmiRegistrationAndSyncModule(
-        final DmiPluginRegistration dmiPluginRegistration) throws JsonProcessingException {
-        final PersistenceCmHandlesList createdPersistenceCmHandlesList = toPersistenceCmHandlesList(
+    private PersistenceCmHandlesList getUpdatedPersistenceCmHandlesList(
+        final DmiPluginRegistration dmiPluginRegistration,
+        final List<CmHandle> updatedCmHandles) {
+        return PersistenceCmHandlesList.toPersistenceCmHandlesList(
             dmiPluginRegistration.getDmiPlugin(),
-            dmiPluginRegistration.getCreatedCmHandles());
-        registerAndSyncNewCmHandles(createdPersistenceCmHandlesList);
-    }
-
-    private static PersistenceCmHandlesList toPersistenceCmHandlesList(final String dmiPlugin,
-                                                                       final Collection<CmHandle> cmHandles) {
-        final PersistenceCmHandlesList persistenceCmHandlesList = new PersistenceCmHandlesList();
-        for (final CmHandle cmHandle : cmHandles) {
-            final PersistenceCmHandle persistenceCmHandle = toPersistenceCmHandle(dmiPlugin, cmHandle);
-            persistenceCmHandlesList.add(persistenceCmHandle);
-        }
-        return persistenceCmHandlesList;
+            dmiPluginRegistration.getDmiDataPlugin(),
+            dmiPluginRegistration.getDmiModelPlugin(),
+            updatedCmHandles);
     }
 
     private static void handleJsonProcessingException(final DmiPluginRegistration dmiPluginRegistration,
@@ -403,19 +293,6 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
         createAnchor(persistenceCmHandle);
     }
 
-    private static PersistenceCmHandle toPersistenceCmHandle(final String dmiPluginService,
-                                                             final CmHandle cmHandle) {
-        final PersistenceCmHandle persistenceCmHandle = new PersistenceCmHandle();
-        persistenceCmHandle.setDmiServiceName(dmiPluginService);
-        persistenceCmHandle.setId(cmHandle.getCmHandleID());
-        if (cmHandle.getCmHandleProperties() == null) {
-            persistenceCmHandle.setAdditionalProperties(Collections.emptyMap());
-        } else {
-            persistenceCmHandle.setAdditionalProperties(cmHandle.getCmHandleProperties());
-        }
-        return persistenceCmHandle;
-    }
-
     private void parseAndRemoveCmHandlesInDmiRegistration(final DmiPluginRegistration dmiPluginRegistration) {
         for (final String cmHandle : dmiPluginRegistration.getRemovedCmHandles()) {
             try {
@@ -428,11 +305,9 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
     }
 
     private void fetchAndSyncModules(final PersistenceCmHandle persistenceCmHandle) {
-        final Map<String, String> cmHandlePropertiesAsMap = getCmHandlePropertiesAsMap(
-            persistenceCmHandle.getAdditionalProperties());
 
         final List<ModuleReference> moduleReferencesFromCmHandle =
-            fetchModuleReferencesFromDmi(persistenceCmHandle, cmHandlePropertiesAsMap);
+            toModuleReferences(dmiModelOperations.getModuleReferences(persistenceCmHandle));
         final List<ModuleReference> existingModuleReferences = new ArrayList<>();
         final List<ModuleReference> unknownModuleReferences = new ArrayList<>();
         prepareModuleSubsets(moduleReferencesFromCmHandle, existingModuleReferences, unknownModuleReferences);
@@ -442,7 +317,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
             newYangResourcesModuleNameToContentMap = new HashMap<>();
         } else {
             newYangResourcesModuleNameToContentMap = getNewYangResourcesFromDmi(persistenceCmHandle,
-                unknownModuleReferences, cmHandlePropertiesAsMap);
+                unknownModuleReferences);
         }
         cpsModuleService
             .createSchemaSetFromModules(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, persistenceCmHandle.getId(),
@@ -465,60 +340,17 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
         }
     }
 
-    private List<ModuleReference> fetchModuleReferencesFromDmi(final PersistenceCmHandle persistenceCmHandle,
-                                                               final Map<String, String> cmHandlePropertiesAsMap) {
-        final GenericRequestBody genericRequestBody = GenericRequestBody.builder()
-                .cmHandleProperties(cmHandlePropertiesAsMap)
-                .build();
-        final String jsonBodyWithOnlyCmHandleProperties = prepareOperationBody(genericRequestBody);
-        final ResponseEntity<String> dmiFetchModulesResponseEntity =
-            dmiOperations.getResourceFromDmiWithJsonData(persistenceCmHandle.getDmiServiceName(),
-                    jsonBodyWithOnlyCmHandleProperties, persistenceCmHandle.getId(), "modules");
-        return toModuleReferences(dmiFetchModulesResponseEntity);
-    }
-
     private void createAnchor(final PersistenceCmHandle persistenceCmHandle) {
         cpsAdminService.createAnchor(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, persistenceCmHandle.getId(),
             persistenceCmHandle.getId());
     }
 
-    private String getRequestBodyToFetchYangResourceFromDmi(final List<ModuleReference> unknownModuleReferences,
-                                                            final Map<String, String> cmHandlePropertiesAsMap) {
-        final JsonArray moduleReferencesAsJson = getModuleReferencesAsJson(unknownModuleReferences);
-        final JsonObject data = new JsonObject();
-        data.add("modules", moduleReferencesAsJson);
-        final JsonObject jsonRequestObject = new JsonObject();
-        jsonRequestObject.add("data", data);
-        final Gson gson = new Gson();
-        jsonRequestObject.add("cmHandleProperties", gson.toJsonTree(cmHandlePropertiesAsMap));
-        return jsonRequestObject.toString();
-    }
-
-    private static JsonArray getModuleReferencesAsJson(final List<ModuleReference> unknownModuleReferences) {
-        final JsonArray moduleReferences = new JsonArray();
-
-        for (final ModuleReference moduleReference : unknownModuleReferences) {
-            final JsonObject moduleReferenceAsJson = new JsonObject();
-            moduleReferenceAsJson.addProperty("name", moduleReference.getModuleName());
-            moduleReferenceAsJson.addProperty(REVISION, moduleReference.getRevision());
-            moduleReferences.add(moduleReferenceAsJson);
-        }
-        return moduleReferences;
-    }
-
     private Map<String, String> getNewYangResourcesFromDmi(final PersistenceCmHandle persistenceCmHandle,
-                                                           final List<ModuleReference> unknownModuleReferences,
-                                                           final Map<String, String> cmHandlePropertiesAsMap) {
-        final String jsonDataWithDataAndCmHandleProperties = getRequestBodyToFetchYangResourceFromDmi(
-                unknownModuleReferences, cmHandlePropertiesAsMap);
-
-        final ResponseEntity<String> moduleResourcesAsJsonString =  dmiOperations.getResourceFromDmiWithJsonData(
-                persistenceCmHandle.getDmiServiceName(),
-                jsonDataWithDataAndCmHandleProperties,
-                persistenceCmHandle.getId(),
-                "moduleResources");
-
-        final JsonArray moduleResources = new Gson().fromJson(moduleResourcesAsJsonString.getBody(),
+                                                           final List<ModuleReference> unknownModuleReferences) {
+        final ResponseEntity<String> responseEntity =
+            dmiModelOperations.getNewYangResourcesFromDmi(persistenceCmHandle, unknownModuleReferences);
+
+        final JsonArray moduleResources = new Gson().fromJson(responseEntity.getBody(),
             JsonArray.class);
         final Map<String, String> newYangResourcesModuleNameToContentMap = new HashMap<>();
 
@@ -532,7 +364,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
     private static YangResource toYangResource(final JsonObject yangResourceAsJson) {
         final YangResource yangResource = new YangResource();
         yangResource.setModuleName(yangResourceAsJson.get("moduleName").getAsString());
-        yangResource.setRevision(yangResourceAsJson.get(REVISION).getAsString());
+        yangResource.setRevision(yangResourceAsJson.get("revision").getAsString());
         final String yangSourceJson = yangResourceAsJson.get("yangSource").getAsString();
 
         String yangSource = JsonUtils.removeWrappingTokens(yangSourceJson);
@@ -558,7 +390,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
     private static ModuleReference toModuleReference(final JsonObject moduleReferenceAsJson) {
         final ModuleReference moduleReference = new ModuleReference();
         moduleReference.setModuleName(moduleReferenceAsJson.get("moduleName").getAsString());
-        moduleReference.setRevision(moduleReferenceAsJson.get(REVISION).getAsString());
+        moduleReference.setRevision(moduleReferenceAsJson.get("revision").getAsString());
         return moduleReference;
     }
 }
index fc70708..62eca94 100644 (file)
@@ -35,13 +35,28 @@ public class DmiRestClient {
     private RestTemplate restTemplate;
     private DmiProperties dmiProperties;
 
+    /**
+     * Constructor injection for DmiRestClient objects.
+     *
+     * @param restTemplate the rest template
+     * @param dmiProperties the DMI properties
+     */
     public DmiRestClient(final RestTemplate restTemplate, final DmiProperties dmiProperties) {
         this.restTemplate = restTemplate;
         this.dmiProperties = dmiProperties;
     }
 
+    /**
+     * Sends a PUT operation to DMI with JSON payload.
+     *
+     * @param dmiResourceUrl the DMI resource URL
+     * @param jsonData the JSON payload
+     * @param headers the HTTP headers
+     * @return response entity of type Object
+     */
     public ResponseEntity<Object> putOperationWithJsonData(final String dmiResourceUrl,
                                                             final String jsonData, final HttpHeaders headers) {
+        //TODO Toine Siebelink, should we use POST operation below instead (and return a String-Entity!)
         final var httpEntity = new HttpEntity<>(jsonData, configureHttpHeaders(headers));
         return restTemplate.exchange(dmiResourceUrl, HttpMethod.PUT, httpEntity, Object.class);
     }
@@ -76,4 +91,4 @@ public class DmiRestClient {
         final var httpEntity = new HttpEntity<>(configureHttpHeaders(httpHeaders));
         return restTemplate.exchange(dmiResourceUrl, HttpMethod.POST, httpEntity, String.class);
     }
-}
\ No newline at end of file
+}
index c4e82d3..81c9dff 100644 (file)
@@ -38,6 +38,8 @@ public class NcmpConfiguration {
         private String authUsername;
         @Value("${dmi.auth.password}")
         private String authPassword;
+        @Value("${dmi.api.base-path}")
+        private String dmiBasePath;
     }
 
     @Bean
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operation/DmiOperations.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operation/DmiOperations.java
deleted file mode 100644 (file)
index 40a47ec..0000000
+++ /dev/null
@@ -1,229 +0,0 @@
-/*
- *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021 Nordix Foundation
- *  ================================================================================
- *  Licensed under the Apache License, Version 2.0 (the "License");
- *  you may not use this file except in compliance with the License.
- *  You may obtain a copy of the License at
- *
- *        http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- *
- *  SPDX-License-Identifier: Apache-2.0
- * ============LICENSE_END=========================================================
- */
-
-package org.onap.cps.ncmp.api.impl.operation;
-
-import com.fasterxml.jackson.annotation.JsonValue;
-import lombok.Getter;
-import org.onap.cps.ncmp.api.impl.client.DmiRestClient;
-import org.springframework.http.HttpHeaders;
-import org.springframework.http.ResponseEntity;
-import org.springframework.stereotype.Component;
-
-@Component
-public class DmiOperations {
-    @Getter
-    public enum DataStoreEnum {
-        PASSTHROUGH_OPERATIONAL("ncmp-datastore:passthrough-operational"),
-        PASSTHROUGH_RUNNING("ncmp-datastore:passthrough-running");
-        private String value;
-
-        DataStoreEnum(final String value) {
-            this.value = value;
-        }
-
-        @Override
-        @JsonValue
-        public String toString() {
-            return value;
-        }
-    }
-
-    private DmiRestClient dmiRestClient;
-    private static final String DMI_API_PATH = "/dmi";
-    private static final String DMI_CM_HANDLE_PATH = "/v1/ch/{cmHandle}";
-    private static final String DMI_CM_HANDLE_DATASTORE_PATH = DMI_CM_HANDLE_PATH + "/data/ds";
-    private static final String URL_SEPARATOR = "/";
-    private static final String RESOURCE_IDENTIFIER = "resourceIdentifier";
-    private static final String OPTIONS_QUERY_KEY = "options";
-
-
-    /**
-     * Constructor for {@code DmiOperations}. This method also manipulates url properties.
-     *
-     * @param dmiRestClient {@code DmiRestClient}
-     */
-    public DmiOperations(final DmiRestClient dmiRestClient) {
-        this.dmiRestClient = dmiRestClient;
-    }
-
-    /**
-     * Get resources from DMI.
-     *
-     * @param dmiServiceName dmi service name
-     * @param cmHandle cmHandle
-     * @param resourceName name of the resource(s)
-     * @return {@code ResponseEntity} response entity
-     */
-    public ResponseEntity<String> getResourceFromDmi(final String dmiServiceName,
-                                                     final String cmHandle,
-                                                     final String resourceName) {
-        final var dmiResourceDataUrl = getDmiResourceUrl(dmiServiceName, cmHandle, resourceName);
-        final var httpHeaders = new HttpHeaders();
-        return dmiRestClient.postOperation(dmiResourceDataUrl, httpHeaders);
-    }
-
-    /**
-     * Get resources from DMI for modules.
-     *
-     * @param dmiServiceName dmi service name
-     * @param jsonData module names and revisions as JSON
-     * @param cmHandle cmHandle
-     * @param resourceName name of the resource(s)
-     * @return {@code ResponseEntity} response entity
-     */
-    public ResponseEntity<String> getResourceFromDmiWithJsonData(final String dmiServiceName,
-                                                               final String jsonData,
-                                                               final String cmHandle,
-                                                               final String resourceName) {
-        final String dmiResourceDataUrl = getDmiResourceUrl(dmiServiceName, cmHandle, resourceName);
-        return dmiRestClient.postOperationWithJsonData(dmiResourceDataUrl, jsonData, new HttpHeaders());
-    }
-
-    /**
-     * This method fetches the resource data from operational data store for given cm handle
-     * identifier on given resource using dmi client.
-     *
-     * @param dmiServiceName dmi service name
-     * @param cmHandle    network resource identifier
-     * @param resourceId  resource identifier
-     * @param optionsParamInQuery options query
-     * @param acceptParamInHeader accept parameter
-     * @param jsonBody    json body for put operation
-     * @return {@code ResponseEntity} response entity
-     */
-    public ResponseEntity<Object> getResourceDataOperationalFromDmi(final String dmiServiceName,
-                                                                    final String cmHandle,
-                                                                    final String resourceId,
-                                                                    final String optionsParamInQuery,
-                                                                    final String acceptParamInHeader,
-                                                                    final String jsonBody) {
-        final var dmiResourceDataUrl = getDmiDatastoreUrl(dmiServiceName, cmHandle, resourceId,
-            optionsParamInQuery, DataStoreEnum.PASSTHROUGH_OPERATIONAL);
-        final var httpHeaders = prepareHeader(acceptParamInHeader);
-        return dmiRestClient.putOperationWithJsonData(dmiResourceDataUrl, jsonBody, httpHeaders);
-    }
-
-    /**
-     * This method fetches the resource data from pass-through running data store for given cm handle
-     * identifier on given resource using dmi client.
-     *
-     * @param dmiServiceName dmi service name
-     * @param cmHandle    network resource identifier
-     * @param resourceId  resource identifier
-     * @param optionsParamInQuery fields query
-     * @param acceptParamInHeader accept parameter
-     * @param jsonBody    json body for put operation
-     * @return {@code ResponseEntity} response entity
-     */
-    public ResponseEntity<Object> getResourceDataPassThroughRunningFromDmi(final String dmiServiceName,
-                                                                           final String cmHandle,
-                                                                           final String resourceId,
-                                                                           final String optionsParamInQuery,
-                                                                           final String acceptParamInHeader,
-                                                                           final String jsonBody) {
-        final var dmiResourceDataUrl = getDmiDatastoreUrl(dmiServiceName, cmHandle, resourceId,
-            optionsParamInQuery, DataStoreEnum.PASSTHROUGH_RUNNING);
-        final var httpHeaders = prepareHeader(acceptParamInHeader);
-        return dmiRestClient.putOperationWithJsonData(dmiResourceDataUrl, jsonBody, httpHeaders);
-    }
-
-    /**
-     * This method creates the resource data from pass-through running data store for given cm handle
-     * identifier on given resource using dmi client.
-     *
-     * @param dmiServiceName dmi service name
-     * @param cmHandle    network resource identifier
-     * @param resourceId  resource identifier
-     * @param jsonBody    json body for put operation
-     * @return {@code ResponseEntity} response entity
-     */
-    public ResponseEntity<String> createResourceDataPassThroughRunningFromDmi(final String dmiServiceName,
-                                                                            final String cmHandle,
-                                                                            final String resourceId,
-                                                                            final String jsonBody) {
-        final var stringBuilder = getStringBuilderForPassThroughUrl(dmiServiceName,
-            cmHandle, resourceId, DataStoreEnum.PASSTHROUGH_RUNNING);
-        return dmiRestClient.postOperationWithJsonData(stringBuilder.toString(), jsonBody, new HttpHeaders());
-    }
-
-    private String getDmiResourceUrl(final String dmiServiceName,
-                                     final String cmHandle,
-                                     final String resourceName) {
-        final var stringBuilder = new StringBuilder(dmiServiceName);
-        stringBuilder.append(DMI_API_PATH);
-        stringBuilder.append(DMI_CM_HANDLE_PATH.replace("{cmHandle}", cmHandle));
-        stringBuilder.append(URL_SEPARATOR + resourceName);
-        return stringBuilder.toString();
-    }
-
-    /**
-     * This method updates the resource data from pass-through running data store for the cm handle identifier on given
-     * resource using dmi client.
-     *
-     * @param dmiServiceName dmi service name
-     * @param cmHandle    network resource identifier
-     * @param resourceId  resource identifier
-     * @param jsonBody    json body for put operation
-     * @return {@code ResponseEntity} response entity
-     */
-    public ResponseEntity<String> updateResourceDataPassThroughRunningFromDmi(final String dmiServiceName,
-        final String cmHandle, final String resourceId, final String jsonBody) {
-        final StringBuilder stringBuilder =
-            getStringBuilderForPassThroughUrl(dmiServiceName, cmHandle, resourceId, DataStoreEnum.PASSTHROUGH_RUNNING);
-        return dmiRestClient.postOperationWithJsonData(stringBuilder.toString(), jsonBody, new HttpHeaders());
-    }
-
-    private String getDmiDatastoreUrl(final String dmiServiceName,
-                                      final String cmHandle,
-                                      final String resourceId,
-                                      final String optionsParamInQuery,
-                                      final DataStoreEnum dataStoreEnum) {
-        final var stringBuilder = getStringBuilderForPassThroughUrl(dmiServiceName,
-            cmHandle, resourceId, dataStoreEnum);
-        appendOptionsQuery(stringBuilder, optionsParamInQuery);
-        return stringBuilder.toString();
-    }
-
-    private StringBuilder getStringBuilderForPassThroughUrl(final String dmiServiceName,
-                                                            final String cmHandle,
-                                                            final String resourceId,
-                                                            final DataStoreEnum dataStoreEnum) {
-        final var stringBuilder = new StringBuilder(dmiServiceName);
-        stringBuilder.append(DMI_API_PATH);
-        stringBuilder.append(DMI_CM_HANDLE_DATASTORE_PATH.replace("{cmHandle}", cmHandle));
-        stringBuilder.append(URL_SEPARATOR + dataStoreEnum.getValue());
-        stringBuilder.append("?" + RESOURCE_IDENTIFIER + "=" + resourceId);
-        return stringBuilder;
-    }
-
-    private void appendOptionsQuery(final StringBuilder stringBuilder,
-                                    final String optionsParamInQuery) {
-        if (optionsParamInQuery != null) {
-            stringBuilder.append("&").append(OPTIONS_QUERY_KEY).append("=").append(optionsParamInQuery);
-        }
-    }
-
-    private HttpHeaders prepareHeader(final String acceptParam) {
-        final var httpHeaders = new HttpHeaders();
-        httpHeaders.set(HttpHeaders.ACCEPT, acceptParam);
-        return httpHeaders;
-    }
-}
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
new file mode 100644 (file)
index 0000000..b833645
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.ncmp.api.impl.operations;
+
+import static org.onap.cps.ncmp.api.impl.operations.DmiOperations.DataStoreEnum.PASSTHROUGH_RUNNING;
+import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum;
+import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.READ;
+import static org.onap.cps.ncmp.api.impl.operations.RequiredDmiService.DATA;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.onap.cps.ncmp.api.impl.client.DmiRestClient;
+import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration;
+import org.onap.cps.ncmp.api.models.PersistenceCmHandle;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Component;
+
+/**
+ * Operations class for DMI data.
+ */
+@Component
+public class DmiDataOperations extends DmiOperations {
+
+    /**
+     * Constructor for {@code DmiOperations}. This method also manipulates url properties.
+     *
+     * @param dmiRestClient {@code DmiRestClient}
+     */
+    public DmiDataOperations(final PersistenceCmHandleRetriever cmHandlePropertiesRetriever,
+                             final ObjectMapper objectMapper,
+                             final NcmpConfiguration.DmiProperties dmiProperties,
+                             final DmiRestClient dmiRestClient) {
+        super(cmHandlePropertiesRetriever, objectMapper, dmiProperties, dmiRestClient);
+    }
+
+    /**
+     * This method fetches the resource data from operational data store for given cm handle
+     * identifier on given resource using dmi client.
+     *
+     * @param cmHandle    network resource identifier
+     * @param resourceId  resource identifier
+     * @param optionsParamInQuery options query
+     * @param acceptParamInHeader accept parameter
+     * @param dataStore  data store enum
+     * @return {@code ResponseEntity} response entity
+     */
+    public ResponseEntity<Object> getResourceDataFromDmi(final String cmHandle,
+                                                          final String resourceId,
+                                                          final String optionsParamInQuery,
+                                                          final String acceptParamInHeader,
+                                                          final DataStoreEnum dataStore) {
+        final PersistenceCmHandle persistenceCmHandle =
+            cmHandlePropertiesRetriever.retrieveCmHandleDmiServiceNameAndProperties(cmHandle);
+        final DmiRequestBody dmiRequestBody = DmiRequestBody.builder()
+            .operation(READ)
+            .build();
+        dmiRequestBody.asCmHandleProperties(persistenceCmHandle.getAdditionalProperties());
+        final String jsonBody = getDmiRequestBodyAsString(dmiRequestBody);
+
+        final var dmiResourceDataUrl = getDmiDatastoreUrlWithOptions(
+            persistenceCmHandle.resolveDmiServiceName(DATA), cmHandle, resourceId,
+            optionsParamInQuery, dataStore);
+        final var httpHeaders = prepareHeader(acceptParamInHeader);
+        return dmiRestClient.putOperationWithJsonData(dmiResourceDataUrl, jsonBody, httpHeaders);
+    }
+
+    /**
+     * This method creates the resource data from pass-through running data store for given cm handle
+     * identifier on given resource using dmi client.
+     *
+     * @param cmHandle    network resource identifier
+     * @param resourceId  resource identifier
+     * @param operation   operation enum
+     * @param requestData the request data
+     * @param dataType    data type
+     * @return {@code ResponseEntity} response entity
+     */
+    public ResponseEntity<String> writeResourceDataPassThroughRunningFromDmi(final String cmHandle,
+                                                                             final String resourceId,
+                                                                             final OperationEnum operation,
+                                                                             final String requestData,
+                                                                             final String dataType) {
+        final PersistenceCmHandle persistenceCmHandle =
+            cmHandlePropertiesRetriever.retrieveCmHandleDmiServiceNameAndProperties(cmHandle);
+        final DmiRequestBody dmiRequestBody = DmiRequestBody.builder()
+            .operation(operation)
+            .data(requestData)
+            .dataType(dataType)
+            .build();
+        dmiRequestBody.asCmHandleProperties(persistenceCmHandle.getAdditionalProperties());
+        final String jsonBody = getDmiRequestBodyAsString(dmiRequestBody);
+        final String dmiUrl =
+            getResourceInDataStoreUrl(persistenceCmHandle.resolveDmiServiceName(DATA),
+            cmHandle, resourceId, PASSTHROUGH_RUNNING);
+        return dmiRestClient.postOperationWithJsonData(dmiUrl, jsonBody, new HttpHeaders());
+    }
+
+    private String getResourceInDataStoreUrl(final String dmiServiceName,
+                                             final String cmHandle,
+                                             final String resourceId,
+                                             final DataStoreEnum dataStoreEnum) {
+        return getCmHandleUrl(dmiServiceName, cmHandle)
+            + "data"
+            + URL_SEPARATOR
+            + "ds"
+            + URL_SEPARATOR
+            + dataStoreEnum.getValue()
+            + "?resourceIdentifier="
+            + resourceId;
+    }
+
+    private String getDmiDatastoreUrlWithOptions(final String dmiServiceName,
+                                                 final String cmHandle,
+                                                 final String resourceId,
+                                                 final String optionsParamInQuery,
+                                                 final DataStoreEnum dataStoreEnum) {
+        final String resourceInDataStoreUrl = getResourceInDataStoreUrl(dmiServiceName,
+            cmHandle, resourceId, dataStoreEnum);
+        return appendOptionsQuery(resourceInDataStoreUrl, optionsParamInQuery);
+    }
+
+}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiModelOperations.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiModelOperations.java
new file mode 100644 (file)
index 0000000..c582584
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.ncmp.api.impl.operations;
+
+import static org.onap.cps.ncmp.api.impl.operations.RequiredDmiService.MODEL;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import java.util.List;
+import org.onap.cps.ncmp.api.impl.client.DmiRestClient;
+import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration;
+import org.onap.cps.ncmp.api.models.PersistenceCmHandle;
+import org.onap.cps.spi.model.ModuleReference;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Component;
+
+@Component
+public class DmiModelOperations extends DmiOperations {
+
+    /**
+     * Constructor for {@code DmiOperations}. This method also manipulates url properties.
+     *
+     * @param dmiRestClient {@code DmiRestClient}
+     */
+    public DmiModelOperations(final PersistenceCmHandleRetriever cmHandlePropertiesRetriever,
+                              final ObjectMapper objectMapper,
+                              final NcmpConfiguration.DmiProperties dmiProperties,
+                              final DmiRestClient dmiRestClient) {
+        super(cmHandlePropertiesRetriever, objectMapper, dmiProperties, dmiRestClient);
+    }
+
+    /**
+     * Retrieves module references.
+     *
+     * @param persistenceCmHandle the persistence cm handle
+     * @return module references
+     */
+    public ResponseEntity<String> getModuleReferences(final PersistenceCmHandle persistenceCmHandle) {
+        final DmiRequestBody dmiRequestBody = DmiRequestBody.builder()
+            .build();
+        dmiRequestBody.asCmHandleProperties(persistenceCmHandle.getAdditionalProperties());
+        return getResourceFromDmiWithJsonData(persistenceCmHandle.resolveDmiServiceName(MODEL),
+            getDmiRequestBodyAsString(dmiRequestBody), persistenceCmHandle.getId(), "modules");
+    }
+
+    /**
+     * Retrieve yang resources from dmi for any modules that CPS-NCMP hasn't cached before.
+     *
+     * @param persistenceCmHandle the persistenceCmHandle
+     * @param unknownModuleReferences the unknown module references
+     * @return yang resources
+     */
+    public ResponseEntity<String>  getNewYangResourcesFromDmi(final PersistenceCmHandle persistenceCmHandle,
+                                                              final List<ModuleReference> unknownModuleReferences) {
+        final String jsonDataWithDataAndCmHandleProperties = getRequestBodyToFetchYangResources(
+            unknownModuleReferences, persistenceCmHandle.getAdditionalProperties());
+        return getResourceFromDmiWithJsonData(
+            persistenceCmHandle.resolveDmiServiceName(MODEL),
+            jsonDataWithDataAndCmHandleProperties,
+            persistenceCmHandle.getId(),
+            "moduleResources");
+    }
+
+    /**
+     * Get resources from DMI for modules.
+     *
+     * @param dmiServiceName dmi service name
+     * @param jsonData module names and revisions as JSON
+     * @param cmHandle cmHandle
+     * @param resourceName name of the resource(s)
+     * @return {@code ResponseEntity} response entity
+     */
+    private ResponseEntity<String> getResourceFromDmiWithJsonData(final String dmiServiceName,
+                                                                  final String jsonData,
+                                                                  final String cmHandle,
+                                                                  final String resourceName) {
+        final String dmiResourceDataUrl = getDmiResourceUrl(dmiServiceName, cmHandle, resourceName);
+        return dmiRestClient.postOperationWithJsonData(dmiResourceDataUrl, jsonData, new HttpHeaders());
+    }
+
+    private static String getRequestBodyToFetchYangResources(final List<ModuleReference> unknownModuleReferences,
+        final List<PersistenceCmHandle.AdditionalProperty> cmHandleProperties) {
+        final JsonArray moduleReferencesAsJson = getModuleReferencesAsJson(unknownModuleReferences);
+        final JsonObject data = new JsonObject();
+        data.add("modules", moduleReferencesAsJson);
+        final JsonObject jsonRequestObject = new JsonObject();
+        jsonRequestObject.add("data", data);
+        jsonRequestObject.add("cmHandleProperties", toJsonObject(cmHandleProperties));
+        return jsonRequestObject.toString();
+    }
+
+    private static JsonArray getModuleReferencesAsJson(final List<ModuleReference> unknownModuleReferences) {
+        final JsonArray moduleReferences = new JsonArray();
+
+        for (final ModuleReference moduleReference : unknownModuleReferences) {
+            final JsonObject moduleReferenceAsJson = new JsonObject();
+            moduleReferenceAsJson.addProperty("name", moduleReference.getModuleName());
+            moduleReferenceAsJson.addProperty("revision", moduleReference.getRevision());
+            moduleReferences.add(moduleReferenceAsJson);
+        }
+        return moduleReferences;
+    }
+
+    private static JsonObject toJsonObject(final List<PersistenceCmHandle.AdditionalProperty> cmHandleProperties) {
+        //TODO Toine/Joe Double check format with existing test data
+        final JsonObject asJsonObject = new JsonObject();
+        for (final PersistenceCmHandle.AdditionalProperty additionalProperty : cmHandleProperties) {
+            asJsonObject.addProperty(additionalProperty.getName(), additionalProperty.getValue());
+        }
+        return asJsonObject;
+    }
+
+}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiOperations.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiOperations.java
new file mode 100644 (file)
index 0000000..5097280
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.ncmp.api.impl.operations;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.base.Strings;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.ncmp.api.impl.client.DmiRestClient;
+import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration;
+import org.onap.cps.ncmp.api.impl.exception.NcmpException;
+import org.springframework.http.HttpHeaders;
+
+@Slf4j
+public class DmiOperations {
+
+    @Getter
+    public enum DataStoreEnum {
+        PASSTHROUGH_OPERATIONAL("ncmp-datastore:passthrough-operational"),
+        PASSTHROUGH_RUNNING("ncmp-datastore:passthrough-running");
+        private String value;
+
+        DataStoreEnum(final String value) {
+            this.value = value;
+        }
+    }
+
+    protected ObjectMapper objectMapper;
+    protected PersistenceCmHandleRetriever cmHandlePropertiesRetriever;
+    protected DmiRestClient dmiRestClient;
+    protected NcmpConfiguration.DmiProperties dmiProperties;
+
+    static final String URL_SEPARATOR = "/";
+
+    /**
+     * Constructor for {@code DmiOperations}. This method also manipulates url properties.
+     *
+     * @param dmiRestClient {@code DmiRestClient}
+     */
+    public DmiOperations(final PersistenceCmHandleRetriever cmHandlePropertiesRetriever,
+                         final ObjectMapper objectMapper,
+                         final NcmpConfiguration.DmiProperties dmiProperties,
+                         final DmiRestClient dmiRestClient) {
+        this.cmHandlePropertiesRetriever = cmHandlePropertiesRetriever;
+        this.objectMapper = objectMapper;
+        this.dmiRestClient = dmiRestClient;
+        this.dmiProperties = dmiProperties;
+    }
+
+    String getCmHandleUrl(final String dmiServiceName, final String cmHandle) {
+        return dmiServiceName
+            + dmiProperties.getDmiBasePath()
+            + URL_SEPARATOR
+            + "v1"
+            + URL_SEPARATOR
+            + "ch"
+            + URL_SEPARATOR
+            + cmHandle
+            + URL_SEPARATOR;
+    }
+
+    String getDmiResourceUrl(final String dmiServiceName, final String cmHandle, final String resourceName) {
+        return getCmHandleUrl(dmiServiceName, cmHandle) + resourceName;
+    }
+
+    static String appendOptionsQuery(final String url, final String optionsParamInQuery) {
+        if (Strings.isNullOrEmpty(optionsParamInQuery)) {
+            return url;
+        }
+        return url + "&options=" + optionsParamInQuery;
+    }
+
+    static HttpHeaders prepareHeader(final String acceptParam) {
+        final var httpHeaders = new HttpHeaders();
+        httpHeaders.set(HttpHeaders.ACCEPT, acceptParam);
+        return httpHeaders;
+    }
+
+    /**
+     * Convert DmiRequestBody to JSON.
+     *
+     * @param dmiRequestBody the dmi request body
+     * @return DmiRequestBody as JSON
+     */
+    String getDmiRequestBodyAsString(final DmiRequestBody dmiRequestBody) {
+        try {
+            return objectMapper.writeValueAsString(dmiRequestBody);
+        } catch (final JsonProcessingException e) {
+            log.error("Parsing error occurred while converting Object to JSON.");
+            throw new NcmpException("Parsing error occurred while converting given object to JSON.",
+                e.getMessage());
+        }
+    }
+
+}
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.models;
+package org.onap.cps.ncmp.api.impl.operations;
 
 import com.fasterxml.jackson.annotation.JsonInclude;
-import com.fasterxml.jackson.annotation.JsonInclude.Include;
 import com.fasterxml.jackson.annotation.JsonValue;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Map;
 import lombok.Builder;
 import lombok.Getter;
+import org.onap.cps.ncmp.api.models.PersistenceCmHandle;
 
-@JsonInclude(Include.NON_NULL)
+@JsonInclude(JsonInclude.Include.NON_NULL)
 @Getter
 @Builder
-public class GenericRequestBody   {
+public class DmiRequestBody {
     public enum OperationEnum {
         READ("read"),
         CREATE("create"),
@@ -52,4 +55,25 @@ public class GenericRequestBody   {
     private String dataType;
     private String data;
     private Map<String, String> cmHandleProperties;
+
+    /**
+     * Set CmHandleProperties by converting a list of PersistenceCmHandle.AdditionalProperty objects.
+     *
+     * @param cmHandlePropertiesAsList the cm handle additional properties
+     */
+    public void asCmHandleProperties(
+        final List<PersistenceCmHandle.AdditionalProperty> cmHandlePropertiesAsList) {
+        final boolean isCmHandlePropertiesNullOrEmpty =
+            cmHandlePropertiesAsList == null || cmHandlePropertiesAsList.isEmpty();
+        if (isCmHandlePropertiesNullOrEmpty) {
+            cmHandleProperties = Collections.emptyMap();
+        } else {
+            cmHandleProperties = new LinkedHashMap<>();
+            for (final PersistenceCmHandle.AdditionalProperty additionalProperty : cmHandlePropertiesAsList) {
+                cmHandleProperties.put(additionalProperty.getName(),
+                    additionalProperty.getValue());
+            }
+        }
+    }
+
 }
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/PersistenceCmHandleRetriever.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/PersistenceCmHandleRetriever.java
new file mode 100644 (file)
index 0000000..78b5c31
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.ncmp.api.impl.operations;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import org.onap.cps.api.CpsDataService;
+import org.onap.cps.ncmp.api.models.CmHandle;
+import org.onap.cps.ncmp.api.models.PersistenceCmHandle;
+import org.onap.cps.spi.FetchDescendantsOption;
+import org.onap.cps.spi.model.DataNode;
+import org.springframework.stereotype.Component;
+
+/**
+ * Retrieves PersistenceCmHandles & properties.
+ */
+@Component
+public class PersistenceCmHandleRetriever {
+
+    private static final String NCMP_DATASPACE_NAME = "NCMP-Admin";
+    private static final String NCMP_DMI_REGISTRY_ANCHOR = "ncmp-dmi-registry";
+
+    private CpsDataService cpsDataService;
+
+    /**
+     * Constructor for PersistenceCmHandleRetriever.
+     * 
+     * @param cpsDataService the cps data service.
+     */
+    public PersistenceCmHandleRetriever(final CpsDataService cpsDataService) {
+        this.cpsDataService = cpsDataService;
+    }
+
+    /**
+     * This method retieves dmi service name and properties for a given cm handle.
+     * @param cmHandleId the id of the cm handle
+     * @return persistence cm handle
+     */
+    public PersistenceCmHandle retrieveCmHandleDmiServiceNameAndProperties(final String cmHandleId) {
+        final DataNode cmHandleDataNode = getCmHandleDataNode(cmHandleId);
+        final CmHandle cmHandle = new CmHandle(cmHandleId, getCmHandleProperties(cmHandleDataNode));
+        return PersistenceCmHandle.toPersistenceCmHandle(
+            String.valueOf(cmHandleDataNode.getLeaves().get("dmi-service-name")),
+            String.valueOf(cmHandleDataNode.getLeaves().get("dmi-data-service-name")),
+            String.valueOf(cmHandleDataNode.getLeaves().get("dmi-model-service-name")),
+            cmHandle
+        );
+    }
+
+    private DataNode getCmHandleDataNode(final String cmHandle) {
+        final String xpathForDmiRegistryToFetchCmHandle = "/dmi-registry/cm-handles[@id='" + cmHandle + "']";
+        return cpsDataService.getDataNode(NCMP_DATASPACE_NAME,
+            NCMP_DMI_REGISTRY_ANCHOR,
+            xpathForDmiRegistryToFetchCmHandle,
+            FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS);
+    }
+
+    private static Map<String, String> getCmHandleProperties(final DataNode cmHandleDataNode) {
+        if (cmHandleDataNode.getChildDataNodes().isEmpty()) {
+            return Collections.emptyMap();
+        }
+        final Map<String, String> cmHandlePropertiesAsMap = new LinkedHashMap<>();
+        for (final DataNode childDataNode: cmHandleDataNode.getChildDataNodes()) {
+            cmHandlePropertiesAsMap.put(String.valueOf(childDataNode.getLeaves().get("name")),
+                String.valueOf(childDataNode.getLeaves().get("value")));
+        }
+        return cmHandlePropertiesAsMap;
+    }
+
+}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/RequiredDmiService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/RequiredDmiService.java
new file mode 100644 (file)
index 0000000..7e39766
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.ncmp.api.impl.operations;
+
+/**
+ * Enmm to determine if the required service is for a data or model operation.
+ */
+public enum RequiredDmiService {
+    DATA, MODEL
+}
index 84227ed..a38442b 100644 (file)
@@ -22,7 +22,9 @@ package org.onap.cps.ncmp.api.models;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
 import java.util.Map;
+import lombok.AllArgsConstructor;
 import lombok.Getter;
+import lombok.NoArgsConstructor;
 import lombok.Setter;
 import org.springframework.validation.annotation.Validated;
 
@@ -32,6 +34,8 @@ import org.springframework.validation.annotation.Validated;
 @Validated
 @Getter
 @Setter
+@AllArgsConstructor
+@NoArgsConstructor
 public class CmHandle {
 
     @JsonProperty("cmHandle")
index f5a0d79..a604f34 100644 (file)
  *  ============LICENSE_END=========================================================
  */
 
-
 package org.onap.cps.ncmp.api.models;
 
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.annotation.JsonSetter;
+import com.fasterxml.jackson.annotation.Nulls;
+import com.google.common.base.Strings;
 import java.util.List;
 import lombok.Getter;
 import lombok.Setter;
+import org.onap.cps.ncmp.api.impl.exception.NcmpException;
 
 /**
  * Dmi Registry request object.
@@ -35,12 +38,53 @@ import lombok.Setter;
 @JsonInclude(Include.NON_NULL)
 public class DmiPluginRegistration {
 
+    @JsonSetter(nulls = Nulls.AS_EMPTY)
     private String dmiPlugin;
 
+    @JsonSetter(nulls = Nulls.AS_EMPTY)
+    private String dmiDataPlugin;
+
+    @JsonSetter(nulls = Nulls.AS_EMPTY)
+    private String dmiModelPlugin;
+
     private List<CmHandle> createdCmHandles;
 
     private List<CmHandle> updatedCmHandles;
 
     private List<String> removedCmHandles;
 
+    public static final String PLEASE_SUPPLY_CORRECT_PLUGIN_INFORMATION = "Please supply correct plugin information.";
+
+    /**
+     * Validates plugin service names.
+     *
+     * @throws NcmpException if validation fails.
+     */
+    public void validateDmiPluginRegistration() throws NcmpException {
+        final String combinedServiceName = dmiPlugin;
+        final String dataServiceName = dmiDataPlugin;
+        final String modelsServiceName = dmiModelPlugin;
+
+        String errorMessage = null;
+
+        if (isNullEmptyOrBlank(combinedServiceName)
+            && isNullEmptyOrBlank(dataServiceName)
+            && isNullEmptyOrBlank(modelsServiceName)) {
+            errorMessage = "No DMI plugin service names";
+        }
+
+        if (!isNullEmptyOrBlank(combinedServiceName)
+            && (!isNullEmptyOrBlank(dataServiceName) || !isNullEmptyOrBlank(modelsServiceName))) {
+            errorMessage = "Invalid combination of plugin service names";
+        }
+
+        if (errorMessage != null) {
+            throw new NcmpException(errorMessage, PLEASE_SUPPLY_CORRECT_PLUGIN_INFORMATION);
+        }
+    }
+
+    private static boolean isNullEmptyOrBlank(final String serviceName) {
+        return Strings.isNullOrEmpty(serviceName) || serviceName.isBlank();
+    }
+
 }
index 88d97d2..8b959e3 100644 (file)
 package org.onap.cps.ncmp.api.models;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.Strings;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import lombok.AllArgsConstructor;
+import lombok.Data;
 import lombok.Getter;
 import lombok.NoArgsConstructor;
 import lombok.Setter;
+import org.onap.cps.ncmp.api.impl.operations.RequiredDmiService;
 
 /**
  * DmiRegistry.
@@ -43,22 +47,72 @@ public class PersistenceCmHandle {
     @JsonProperty("dmi-service-name")
     private String dmiServiceName;
 
+    @JsonProperty("dmi-data-service-name")
+    private String dmiDataServiceName;
+
+    @JsonProperty("dmi-model-service-name")
+    private String dmiModelServiceName;
+
     @JsonProperty("additional-properties")
     private List<AdditionalProperty> additionalProperties;
 
+    /**
+     * Create a persistenceCmHandle.
+     * @param dmiServiceName dmi service name
+     * @param dmiDataServiceName dmi data service name
+     * @param dmiModelServiceName dmi model service name
+     * @param cmHandle the cm handle
+     * @return instance of persistenceCmHandle
+     */
+    public static PersistenceCmHandle toPersistenceCmHandle(final String dmiServiceName,
+                                                            final String dmiDataServiceName,
+                                                            final String dmiModelServiceName,
+                                                            final CmHandle cmHandle) {
+        final PersistenceCmHandle persistenceCmHandle = new PersistenceCmHandle();
+        persistenceCmHandle.setId(cmHandle.getCmHandleID());
+        persistenceCmHandle.setDmiServiceName(dmiServiceName);
+        persistenceCmHandle.setDmiDataServiceName(dmiDataServiceName);
+        persistenceCmHandle.setDmiModelServiceName(dmiModelServiceName);
+        if (cmHandle.getCmHandleProperties() == null) {
+            persistenceCmHandle.asAdditionalProperties(Collections.emptyMap());
+        } else {
+            persistenceCmHandle.asAdditionalProperties(cmHandle.getCmHandleProperties());
+        }
+        return persistenceCmHandle;
+    }
+
     /**
      * Set Additional Properties map, key and value pair.
      * @param additionalPropertiesAsMap Map of Additional Properties
      */
-    public void setAdditionalProperties(final Map<String, String> additionalPropertiesAsMap) {
+    public void asAdditionalProperties(final Map<String, String> additionalPropertiesAsMap) {
         additionalProperties = new ArrayList<>(additionalPropertiesAsMap.size());
         for (final Map.Entry<String, String> entry : additionalPropertiesAsMap.entrySet()) {
             additionalProperties.add(new AdditionalProperty(entry.getKey(), entry.getValue()));
         }
     }
 
+    /**
+     * Resolve a dmi service name.
+     * @param requiredService indicates what typo of service is required
+     * @return dmi service name
+     */
+    public String resolveDmiServiceName(final RequiredDmiService requiredService) {
+        if (isNullEmptyOrBlank(dmiServiceName)) {
+            if (RequiredDmiService.DATA.equals(requiredService)) {
+                return dmiDataServiceName;
+            }
+            return dmiModelServiceName;
+        }
+        return dmiServiceName;
+    }
+
+    private static boolean isNullEmptyOrBlank(final String serviceName) {
+        return Strings.isNullOrEmpty(serviceName) || serviceName.isBlank();
+    }
+
     @AllArgsConstructor
-    @Getter
+    @Data
     public static class AdditionalProperty {
 
         @JsonProperty()
index f35abf6..d4f6e95 100644 (file)
@@ -22,6 +22,7 @@ package org.onap.cps.ncmp.api.models;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 import lombok.Getter;
 
@@ -31,6 +32,31 @@ public class PersistenceCmHandlesList {
     @JsonProperty("cm-handles")
     private List<PersistenceCmHandle> persistenceCmHandles = new ArrayList<>();
 
+    /**
+     * Create a PersistenceCmHandleList given all service names and a collection of cmHandles.
+     * @param dmiServiceName the dmi service name
+     * @param dmiDataServiceName the dmi data service name
+     * @param dmiModelServiceName the dmi model service name
+     * @param cmHandles cm handles
+     * @return instance of PersistenceCmHandleList
+     */
+    public static PersistenceCmHandlesList toPersistenceCmHandlesList(final String dmiServiceName,
+                                                                      final String dmiDataServiceName,
+                                                                      final String dmiModelServiceName,
+                                                                      final Collection<CmHandle> cmHandles) {
+        final PersistenceCmHandlesList persistenceCmHandlesList = new PersistenceCmHandlesList();
+        for (final CmHandle cmHandle : cmHandles) {
+            final PersistenceCmHandle persistenceCmHandle =
+                PersistenceCmHandle.toPersistenceCmHandle(
+                    dmiServiceName,
+                    dmiDataServiceName,
+                    dmiModelServiceName,
+                    cmHandle);
+            persistenceCmHandlesList.add(persistenceCmHandle);
+        }
+        return persistenceCmHandlesList;
+    }
+
     /**
      * Add a persistenceCmHandle.
      *
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplModelSyncSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplModelSyncSpec.groovy
new file mode 100644 (file)
index 0000000..2a85a4a
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.ncmp.api.impl
+
+
+import org.onap.cps.api.CpsAdminService
+import org.onap.cps.api.CpsModuleService
+import org.onap.cps.ncmp.api.impl.operations.DmiModelOperations
+import org.onap.cps.ncmp.api.models.PersistenceCmHandle
+import org.onap.cps.ncmp.utils.TestUtils
+import org.onap.cps.spi.model.ModuleReference
+import org.springframework.http.HttpStatus
+import org.springframework.http.ResponseEntity
+import spock.lang.Specification
+
+class NetworkCmProxyDataServiceImplModelSyncSpec extends Specification {
+
+    def mockCpsModuleService = Mock(CpsModuleService)
+    def mockCpsAdminService = Mock(CpsAdminService)
+    def mockDmiModelOperations = Mock(DmiModelOperations)
+
+    def objectUnderTest = new NetworkCmProxyDataServiceImpl(null, mockDmiModelOperations,
+        mockCpsModuleService, null, null, mockCpsAdminService, null)
+
+    def expectedDataspaceName = 'NFP-Operational'
+
+    def 'Sync model for a (new) cm handle with #scenario'() {
+        given: 'persistence cm handle is given'
+            def cmHandleForModelSync = new PersistenceCmHandle(id:'some cm handle', dmiServiceName: 'some service name')
+        and: 'additional properties are set as required'
+            if (additionalProperties!=null) {
+                cmHandleForModelSync.asAdditionalProperties(additionalProperties)
+            }
+        and: 'dmi operations returns some module references'
+            def jsonData = TestUtils.getResourceFileContent('cmHandleModules.json')
+            def moduleReferencesFromCmHandleAsJson = new ResponseEntity<String>(jsonData, HttpStatus.OK)
+            mockDmiModelOperations.getModuleReferences(cmHandleForModelSync) >> moduleReferencesFromCmHandleAsJson
+        and: 'CPS-Core returns list of existing module resources'
+            mockCpsModuleService.getYangResourceModuleReferences(expectedDataspaceName) >> existingModuleResourcesInCps
+        and: 'DMI-Plugin returns resource(s) for "new" module(s)'
+            def moduleResources = new ResponseEntity<String>(sdncReponseBody, HttpStatus.OK)
+            mockDmiModelOperations.getNewYangResourcesFromDmi(cmHandleForModelSync, [new ModuleReference('module1', '1')]) >> moduleResources
+        when: 'module sync is triggered'
+            objectUnderTest.syncModulesAndCreateAnchor(cmHandleForModelSync)
+        then: 'the CPS module service is called once with the correct parameters'
+            1 * mockCpsModuleService.createSchemaSetFromModules(expectedDataspaceName, cmHandleForModelSync.getId(), expectedYangResourceToContentMap, expectedKnownModules)
+        and: 'admin service create anchor method has been called with correct parameters'
+            1 * mockCpsAdminService.createAnchor(expectedDataspaceName, cmHandleForModelSync.getId(), cmHandleForModelSync.getId())
+        where: 'the following parameters are used'
+            scenario                        | additionalProperties | existingModuleResourcesInCps                                               | sdncReponseBody                                                                     || expectedYangResourceToContentMap | expectedKnownModules                                                       | expectedJsonForAdditionalProperties
+            'one unknown module'            | ['name1': 'value1']  | [new ModuleReference('module2', '2'), new ModuleReference('module3', '3')] | '[{"moduleName" : "module1", "revision" : "1","yangSource": "[some yang source]"}]' || [module1: 'some yang source']    | [new ModuleReference('module2', '2')]                                      | '{"name1":"value1"}'
+            'no add. properties'            | [:]                  | [new ModuleReference('module2', '2'), new ModuleReference('module3', '3')] | '[{"moduleName" : "module1", "revision" : "1","yangSource": "[some yang source]"}]' || [module1: 'some yang source']    | [new ModuleReference('module2', '2')]                                      | '{}'
+            'additional properties is null' | null                 | [new ModuleReference('module2', '2'), new ModuleReference('module3', '3')] | '[{"moduleName" : "module1", "revision" : "1","yangSource": "[some yang source]"}]' || [module1: 'some yang source']    | [new ModuleReference('module2', '2')]                                      | '{}'
+            'no unknown module'             | [:]                  | [new ModuleReference('module1', '1'), new ModuleReference('module2', '2')] | '[]'                                                                                || [:]                              | [new ModuleReference('module1', '1'), new ModuleReference('module2', '2')] | '{}'
+    }
+}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy
new file mode 100644 (file)
index 0000000..86c01b4
--- /dev/null
@@ -0,0 +1,167 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.ncmp.api.impl
+
+import com.fasterxml.jackson.core.JsonProcessingException
+import com.fasterxml.jackson.databind.ObjectMapper
+import org.onap.cps.api.CpsDataService
+import org.onap.cps.ncmp.api.impl.exception.NcmpException
+import org.onap.cps.ncmp.api.models.CmHandle
+import org.onap.cps.ncmp.api.models.DmiPluginRegistration
+import org.onap.cps.spi.exceptions.DataNodeNotFoundException
+import org.onap.cps.spi.exceptions.DataValidationException
+import spock.lang.Shared
+import spock.lang.Specification
+
+class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
+
+    @Shared
+    def persistenceCmHandle = new CmHandle()
+
+    @Shared
+    def cmHandlesArray = ['cmHandle001']
+
+    def mockCpsDataService = Mock(CpsDataService)
+    def spyObjectMapper = Spy(ObjectMapper)
+
+    def noTimestamp = null
+
+    def 'Register or re-register a DMI Plugin for the given cm-handle(s) with #scenario process.'() {
+        given: 'a registration'
+            def objectUnderTest = getObjectUnderTestWithModelSyncDisabled()
+            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin:'my-server')
+            persistenceCmHandle.cmHandleID = '123'
+            persistenceCmHandle.cmHandleProperties = [name1: 'value1', name2: 'value2']
+            dmiPluginRegistration.createdCmHandles = createdCmHandles
+            dmiPluginRegistration.updatedCmHandles = updatedCmHandles
+            dmiPluginRegistration.removedCmHandles = removedCmHandles
+            def expectedJsonData = '{"cm-handles":[{"id":"123","dmi-service-name":"my-server","dmi-data-service-name":null,"dmi-model-service-name":null,"additional-properties":[{"name":"name1","value":"value1"},{"name":"name2","value":"value2"}]}]}'
+        when: 'registration is updated and modules are synced'
+            objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
+        then: 'save list elements is invoked with the expected parameters'
+            expectedCallsToSaveNode * mockCpsDataService.saveListElements('NCMP-Admin', 'ncmp-dmi-registry',
+                    '/dmi-registry', expectedJsonData, noTimestamp)
+        and: 'update node and child data nodes is invoked with correct parameters'
+            expectedCallsToUpdateNode * mockCpsDataService.updateNodeLeavesAndExistingDescendantLeaves('NCMP-Admin',
+                    'ncmp-dmi-registry', '/dmi-registry', expectedJsonData, noTimestamp)
+        and : 'delete list or list element is invoked with the correct parameters'
+            expectedCallsToDeleteListElement * mockCpsDataService.deleteListOrListElement('NCMP-Admin',
+                    'ncmp-dmi-registry', "/dmi-registry/cm-handles[@id='cmHandle001']", noTimestamp)
+        where:
+            scenario                        | createdCmHandles      | updatedCmHandles      | removedCmHandles || expectedCallsToSaveNode   | expectedCallsToUpdateNode | expectedCallsToDeleteListElement
+            'create'                        | [persistenceCmHandle] | []                    | []               || 1                         | 0                         | 0
+            'update'                        | []                    | [persistenceCmHandle] | []               || 0                         | 1                         | 0
+            'delete'                        | []                    | []                    | cmHandlesArray   || 0                         | 0                         | 1
+            'create, update and delete'     | [persistenceCmHandle] | [persistenceCmHandle] | cmHandlesArray   || 1                         | 1                         | 1
+            'no valid data'                 | null                  | null                  |  null            || 0                         | 0                         | 0
+    }
+
+    def 'Register a DMI Plugin for the given cm-handle(s) without additional properties.'() {
+        given: 'a registration without cm-handle properties'
+            NetworkCmProxyDataServiceImpl objectUnderTest = getObjectUnderTestWithModelSyncDisabled()
+            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin:'my-server')
+            persistenceCmHandle.cmHandleID = '123'
+            persistenceCmHandle.cmHandleProperties = null
+            dmiPluginRegistration.createdCmHandles = [persistenceCmHandle]
+            def expectedJsonData = '{"cm-handles":[{"id":"123","dmi-service-name":"my-server","dmi-data-service-name":null,"dmi-model-service-name":null,"additional-properties":[]}]}'
+        when: 'registration is updated'
+            objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
+        then: 'save list elements is invoked with the expected parameters'
+            1 * mockCpsDataService.saveListElements('NCMP-Admin', 'ncmp-dmi-registry',
+                    '/dmi-registry', expectedJsonData, noTimestamp)
+    }
+
+    def 'Register a DMI Plugin for a given cm-handle(s) with JSON processing errors during #scenario process.'() {
+        given: 'a registration without cm-handle properties '
+            NetworkCmProxyDataServiceImpl objectUnderTest = getObjectUnderTestWithModelSyncDisabled()
+            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin:'some-plugin')
+            dmiPluginRegistration.createdCmHandles = createdCmHandles
+            dmiPluginRegistration.updatedCmHandles = updatedCmHandles
+        and: 'an json processing exception occurs'
+            spyObjectMapper.writeValueAsString(_) >> { throw (new JsonProcessingException('')) }
+        when: 'registration is updated and modules are synced'
+            objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
+        then: 'a data validation exception is thrown'
+            thrown(DataValidationException)
+        where:
+            scenario | createdCmHandles      | updatedCmHandles
+            'create' | [persistenceCmHandle] | []
+            'update' | []                    | [persistenceCmHandle]
+    }
+
+    def 'Register a DMI Plugin for the given cm-handle(s) with no data found during delete process.'() {
+        given: 'a registration without cm-handle properties '
+            NetworkCmProxyDataServiceImpl objectUnderTest = getObjectUnderTestWithModelSyncDisabled()
+            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin:'some-plugin')
+            dmiPluginRegistration.removedCmHandles = ['some cm handle']
+        and: 'an json processing exception occurs during delete process'
+            mockCpsDataService.deleteListOrListElement(*_) >>  { throw (new DataNodeNotFoundException('','')) }
+        when: 'registration is updated and modules are synced'
+            objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
+        then: 'no exception is thrown'
+            noExceptionThrown()
+    }
+
+    def 'Dmi plugin registration with #scenario'() {
+        given: 'a registration '
+            def objectUnderTest = getObjectUnderTestWithModelSyncDisabled()
+            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin:dmiPlugin, dmiModelPlugin:dmiModelPlugin,
+                    dmiDataPlugin:dmiDataPlugin)
+            dmiPluginRegistration.createdCmHandles = [persistenceCmHandle]
+        when: 'update registration and sync module is called with correct DMI plugin information'
+            objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
+        then: 'create cm handles registration and sync modules is called with the correct plugin information'
+            1 * objectUnderTest.parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(dmiPluginRegistration)
+        where:
+            scenario                          | dmiPlugin  | dmiModelPlugin | dmiDataPlugin
+            'combined DMI plugin'             | 'service1' | ''             | ''
+            'data & model DMI plugins'        | ''         | 'service1'     | 'service2'
+            'data & model using same service' | ''         | 'service1'     | 'service1'
+    }
+
+    def 'Invalid dmi plugin registration with #scenario'() {
+        given: 'a registration '
+            def objectUnderTest = getObjectUnderTestWithModelSyncDisabled()
+            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin:dmiPlugin, dmiModelPlugin:dmiModelPlugin,
+                    dmiDataPlugin:dmiDataPlugin)
+            dmiPluginRegistration.createdCmHandles = [persistenceCmHandle]
+        when: 'registration is called with incorrect DMI plugin information'
+            objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
+        then: 'an NcmpException is thrown with correct message details'
+            def exceptionThrown = thrown(NcmpException)
+            assert exceptionThrown.getMessage().contains(expectedMessageDetails)
+        and: 'registration is not called'
+            0 * objectUnderTest.parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(dmiPluginRegistration)
+        where:
+            scenario              | dmiPlugin  | dmiModelPlugin | dmiDataPlugin || expectedMessageDetails
+            'no DMI plugin'       | ''         | ''             | ''            || 'No DMI plugin service names'
+            'all DMI plugins'     | 'service1' | 'service2'     | 'service3'    || 'Invalid combination of plugin service names'
+            'no model DMI plugin' | 'service1' | ''             | 'service2'    || 'Invalid combination of plugin service names'
+            'no data DMI plugin'  | 'service1' | 'service2'     | ''            || 'Invalid combination of plugin service names'
+    }
+
+    def getObjectUnderTestWithModelSyncDisabled() {
+        def objectUnderTest = Spy(new NetworkCmProxyDataServiceImpl(null, null, null,
+                mockCpsDataService, null, null, spyObjectMapper))
+        objectUnderTest.syncModulesAndCreateAnchor(*_) >> null
+        return objectUnderTest
+    }
+}
index b0c447e..8bb0ee2 100644 (file)
 
 package org.onap.cps.ncmp.api.impl
 
+import org.onap.cps.ncmp.api.impl.client.DmiRestClient
+import org.onap.cps.ncmp.api.impl.operations.DmiRequestBody
+import org.springframework.http.HttpHeaders
+
+import static org.onap.cps.ncmp.api.impl.operations.DmiOperations.DataStoreEnum.PASSTHROUGH_OPERATIONAL
+import static org.onap.cps.ncmp.api.impl.operations.DmiOperations.DataStoreEnum.PASSTHROUGH_RUNNING
+import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.CREATE
+import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.READ
+import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.UPDATE
+
 import com.fasterxml.jackson.core.JsonProcessingException
 import com.fasterxml.jackson.databind.ObjectMapper
 import org.onap.cps.api.CpsAdminService
 import org.onap.cps.api.CpsDataService
 import org.onap.cps.api.CpsModuleService
 import org.onap.cps.api.CpsQueryService
-import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration
 import org.onap.cps.ncmp.api.impl.exception.NcmpException
-import org.onap.cps.ncmp.api.impl.operation.DmiOperations
-import org.onap.cps.ncmp.api.models.CmHandle
-import org.onap.cps.ncmp.api.models.DmiPluginRegistration
-import org.onap.cps.ncmp.api.models.PersistenceCmHandle
-import org.onap.cps.ncmp.utils.TestUtils
+import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations
+import org.onap.cps.ncmp.api.impl.operations.DmiModelOperations
 import org.onap.cps.spi.FetchDescendantsOption
-import org.onap.cps.spi.exceptions.DataNodeNotFoundException
-import org.onap.cps.spi.exceptions.DataValidationException
 import org.onap.cps.spi.model.DataNode
-import org.onap.cps.spi.model.ModuleReference
 import org.springframework.http.HttpStatus
 import org.springframework.http.ResponseEntity
-import spock.lang.Shared
 import spock.lang.Specification
 
 class NetworkCmProxyDataServiceImplSpec extends Specification {
 
-    @Shared
-    def persistenceCmHandle = new CmHandle()
-    @Shared
-    def cmHandlesArray = ['cmHandle001']
-
     def mockCpsDataService = Mock(CpsDataService)
     def mockCpsQueryService = Mock(CpsQueryService)
-    def mockDmiOperations = Mock(DmiOperations)
     def mockCpsModuleService = Mock(CpsModuleService)
     def mockCpsAdminService = Mock(CpsAdminService)
-    def mockDmiProperties = Mock(NcmpConfiguration.DmiProperties)
     def spyObjectMapper = Spy(ObjectMapper)
+    def mockDmiDataOperations = Mock(DmiDataOperations)
 
-    def objectUnderTest = new NetworkCmProxyDataServiceImpl(mockDmiOperations, mockCpsModuleService,
-            mockCpsDataService, mockCpsQueryService, mockCpsAdminService, spyObjectMapper)
+    def objectUnderTest = new NetworkCmProxyDataServiceImpl(mockDmiDataOperations, null,
+        mockCpsModuleService, mockCpsDataService, mockCpsQueryService, mockCpsAdminService, spyObjectMapper)
 
     def cmHandle = 'some handle'
     def noTimestamp = null
     def cmHandleXPath = "/dmi-registry/cm-handles[@id='testCmHandle']"
     def expectedDataspaceName = 'NFP-Operational'
 
-
-    def 'Get data node.'() {
-        when: 'queryDataNodes is invoked'
-            objectUnderTest.getDataNode(cmHandle, 'some xpath', fetchDescendantsOption)
-        then: 'the persistence data service is called once with the correct parameters'
-            1 * mockCpsDataService.getDataNode(expectedDataspaceName, cmHandle, 'some xpath', fetchDescendantsOption)
-        where: 'all fetch descendants options are supported'
-            fetchDescendantsOption << FetchDescendantsOption.values()
-    }
-
-    def 'Query data nodes by cps path with #fetchDescendantsOption.'() {
-        given: 'a cm Handle and a cps path'
-            def cpsPath = '/cps-path'
-        when: 'queryDataNodes is invoked'
-            objectUnderTest.queryDataNodes(cmHandle, cpsPath, fetchDescendantsOption)
-        then: 'the persistence query service is called once with the correct parameters'
-            1 * mockCpsQueryService.queryDataNodes(expectedDataspaceName, cmHandle, cpsPath, fetchDescendantsOption)
-        where: 'all fetch descendants options are supported'
-            fetchDescendantsOption << FetchDescendantsOption.values()
-    }
-
     def 'Create full data node: #scenario.'() {
-        given: 'a cm handle and root xpath'
+        given: 'json data'
             def jsonData = 'some json'
         when: 'createDataNode is invoked'
             objectUnderTest.createDataNode(cmHandle, xpath, jsonData)
-        then: 'the CPS service method is invoked once with the expected parameters'
+        then: 'save data is invoked once with the expected parameters'
             1 * mockCpsDataService.saveData(expectedDataspaceName, cmHandle, jsonData, noTimestamp)
         where: 'following parameters were used'
             scenario           | xpath
@@ -103,334 +78,189 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
     }
 
     def 'Create child data node.'() {
-        given: 'a cm handle and parent node xpath'
+        given: 'json data and xpath'
             def jsonData = 'some json'
             def xpath = '/test-node'
-        when: 'createDataNode is invoked'
+        when: 'create data node is invoked'
             objectUnderTest.createDataNode(cmHandle, xpath, jsonData)
-        then: 'the CPS service method is invoked once with the expected parameters'
+        then: 'save data is invoked once with the expected parameters'
             1 * mockCpsDataService.saveData(expectedDataspaceName, cmHandle, xpath, jsonData, noTimestamp)
     }
 
     def 'Add list-node elements.'() {
-        given: 'a cm handle and parent node xpath'
+        given: 'json data and xpath'
             def jsonData = 'some json'
             def xpath = '/test-node'
-        when: 'addListNodeElements is invoked'
+        when: 'add list node element is invoked'
             objectUnderTest.addListNodeElements(cmHandle, xpath, jsonData)
-        then: 'the CPS service method is invoked once with the expected parameters'
+        then: 'the save list elements is invoked once with the expected parameters'
             1 * mockCpsDataService.saveListElements(expectedDataspaceName, cmHandle, xpath, jsonData, noTimestamp)
     }
 
-    def 'Update data node leaves.'() {
-        given: 'a cm Handle and a cps path'
-            def xpath = '/xpath'
-            def jsonData = 'some json'
-        when: 'updateNodeLeaves is invoked'
-            objectUnderTest.updateNodeLeaves(cmHandle, xpath, jsonData)
-        then: 'the persistence service is called once with the correct parameters'
-            1 * mockCpsDataService.updateNodeLeaves(expectedDataspaceName, cmHandle, xpath, jsonData, noTimestamp)
-    }
-
-    def 'Replace data node tree.'() {
-        given: 'a cm Handle and a cps path'
-            def xpath = '/xpath'
-            def jsonData = 'some json'
-        when: 'replaceNodeTree is invoked'
-            objectUnderTest.replaceNodeTree(cmHandle, xpath, jsonData)
-        then: 'the persistence service is called once with the correct parameters'
-            1 * mockCpsDataService.replaceNodeTree(expectedDataspaceName, cmHandle, xpath, jsonData, noTimestamp)
-    }
-
-    def 'Register or re-register a DMI Plugin with #scenario cm handles.'() {
-        given: 'a registration '
-            def objectUnderTest = getObjectUnderTestWithModelSyncDisabled()
-            def dmiPluginRegistration = new DmiPluginRegistration()
-            dmiPluginRegistration.dmiPlugin = 'my-server'
-            persistenceCmHandle.cmHandleID = '123'
-            persistenceCmHandle.cmHandleProperties = [name1: 'value1', name2: 'value2']
-            dmiPluginRegistration.createdCmHandles = createdCmHandles
-            dmiPluginRegistration.updatedCmHandles = updatedCmHandles
-            dmiPluginRegistration.removedCmHandles = removedCmHandles
-            def expectedJsonData = '{"cm-handles":[{"id":"123","dmi-service-name":"my-server","additional-properties":[{"name":"name1","value":"value1"},{"name":"name2","value":"value2"}]}]}'
-        when: 'registration is updated'
-            objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
-        then: 'cps save list elements is invoked with the expected parameters'
-            expectedCallsToSaveNode * mockCpsDataService.saveListElements('NCMP-Admin', 'ncmp-dmi-registry',
-                '/dmi-registry', expectedJsonData, noTimestamp)
-        and: 'update node and child data nodes is invoked with correct parameters'
-            expectedCallsToUpdateNode * mockCpsDataService.updateNodeLeavesAndExistingDescendantLeaves('NCMP-Admin',
-                'ncmp-dmi-registry', '/dmi-registry', expectedJsonData, noTimestamp)
-        and : 'delete list or list element is invoked with the correct parameters'
-            expectedCallsToDeleteListElement * mockCpsDataService.deleteListOrListElement('NCMP-Admin',
-                'ncmp-dmi-registry', "/dmi-registry/cm-handles[@id='cmHandle001']", noTimestamp)
-
+    def 'Write resource data for passthrough running from dmi using POST #scenario cm handle properties.'() {
+        given: 'a data node'
+            def dataNode = getDataNode(includeCmHandleProperties)
+        and: 'cpsDataService returns valid datanode'
+            mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
+                cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
+        when: 'get resource data is called'
+            objectUnderTest.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
+                'testResourceId', CREATE,
+                '{some-json}', 'application/json')
+        then: 'dmi called with correct data'
+            1 * mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi('testCmHandle', 'testResourceId',
+                CREATE, '{some-json}', 'application/json')
+                >> { new ResponseEntity<>(HttpStatus.CREATED) }
         where:
-            scenario                        | createdCmHandles      | updatedCmHandles      | removedCmHandles || expectedCallsToSaveNode   | expectedCallsToUpdateNode | expectedCallsToDeleteListElement
-            'create'                        | [persistenceCmHandle] | []                    | []               || 1                         | 0                         | 0
-            'update'                        | []                    | [persistenceCmHandle] | []               || 0                         | 1                         | 0
-            'delete'                        | []                    | []                    | cmHandlesArray   || 0                         | 0                         | 1
-            'create, update and delete'     | [persistenceCmHandle] | [persistenceCmHandle] | cmHandlesArray   || 1                         | 1                         | 1
-            'no valid data'                 | null                  | null                  |  null            || 0                         | 0                         | 0
-    }
-
-    def 'Register a DMI Plugin for the given cmHandle without additional properties.'() {
-        given: 'a registration without cmHandle properties '
-            NetworkCmProxyDataServiceImpl objectUnderTest = getObjectUnderTestWithModelSyncDisabled()
-            def dmiPluginRegistration = new DmiPluginRegistration()
-            dmiPluginRegistration.dmiPlugin = 'my-server'
-            persistenceCmHandle.cmHandleID = '123'
-            persistenceCmHandle.cmHandleProperties = null
-            dmiPluginRegistration.createdCmHandles = [persistenceCmHandle]
-            def expectedJsonData = '{"cm-handles":[{"id":"123","dmi-service-name":"my-server","additional-properties":[]}]}'
-        when: 'registration is updated'
-            objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
-        then: 'the cps save list element is invoked with the expected parameters'
-            1 * mockCpsDataService.saveListElements('NCMP-Admin', 'ncmp-dmi-registry',
-                '/dmi-registry', expectedJsonData, noTimestamp)
+            scenario  | includeCmHandleProperties || expectedJsonForCmhandleProperties
+            'with'    | true                      || '{"testName":"testValue"}'
+            'without' | false                     || '{}'
     }
 
-    def 'Register a DMI Plugin with JSON processing errors during #scenario.'() {
-        given: 'a registration without cmHandle properties '
-            NetworkCmProxyDataServiceImpl objectUnderTest = getObjectUnderTestWithModelSyncDisabled()
-            def dmiPluginRegistration = new DmiPluginRegistration()
-            dmiPluginRegistration.createdCmHandles = createdCmHandles
-            dmiPluginRegistration.updatedCmHandles = updatedCmHandles
-        and: 'an JSON processing exception occurs'
-            spyObjectMapper.writeValueAsString(_) >> { throw (new JsonProcessingException('')) }
-        when: 'registration is updated'
-            objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
-        then: 'a data validation exception is thrown'
-            thrown(DataValidationException)
-        where:
-            scenario | createdCmHandles      | updatedCmHandles
-            'create' | [persistenceCmHandle] | []
-            'update' | []                    | [persistenceCmHandle]
+    def 'Write resource data for passthrough running from dmi using POST "not found" response (from DMI).'() {
+        given: 'a data node'
+            def dataNode = getDataNode(true)
+        and: 'cpsDataService returns valid dataNode'
+            mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
+                cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
+        and: 'dmi returns a response with 404 status code'
+            mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi('testCmHandle',
+                'testResourceId', CREATE,
+                '{some-json}', 'application/json')
+                >> { new ResponseEntity<>(HttpStatus.NOT_FOUND) }
+        when: 'write resource data is called'
+            objectUnderTest.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
+                'testResourceId', CREATE,
+                '{some-json}', 'application/json')
+        then: 'exception is thrown'
+            def exceptionThrown = thrown(NcmpException.class)
+        and: 'details contains (not found) error code: 404'
+            exceptionThrown.details.contains('404')
     }
 
-    def 'Register a DMI Plugin with no data found during delete.'() {
-        given: 'a registration without cmHandle properties '
-            NetworkCmProxyDataServiceImpl objectUnderTest = getObjectUnderTestWithModelSyncDisabled()
-            def dmiPluginRegistration = new DmiPluginRegistration()
-            dmiPluginRegistration.removedCmHandles = ['some cm handle']
-        and: 'an JSON processing exception occurs'
-            mockCpsDataService.deleteListOrListElement(*_) >>  { throw (new DataNodeNotFoundException('','')) }
-        when: 'registration is updated'
-            objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
-        then: 'no exception is thrown'
-            noExceptionThrown()
+    def 'Get data node.'() {
+        when: 'get data node is invoked'
+            objectUnderTest.getDataNode(cmHandle, 'some xpath', fetchDescendantsOption)
+        then: 'the persistence data service is called once with the correct parameters'
+            1 * mockCpsDataService.getDataNode(expectedDataspaceName, cmHandle, 'some xpath', fetchDescendantsOption)
+        where: 'all fetch descendants options are supported'
+            fetchDescendantsOption << FetchDescendantsOption.values()
     }
 
-    def 'Get resource data for pass-through operational from dmi.'() {
-        given: 'data node representing cmHandle and its properties'
-            def cmHandleDataNode = getCmHandleDataNodeForTest(true)
-        and: 'data node is got from data service'
+    def 'Get resource data for passthrough operational from dmi.'() {
+        given: 'a data node'
+            def dataNode = getDataNode(true)
+        and: 'get data node is called'
             mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
-                cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> cmHandleDataNode
-        and: 'resource data is got from DMI'
-            mockDmiOperations.getResourceDataOperationalFromDmi('testDmiService',
+                cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
+        and: 'get resource data from dmi is called'
+            mockDmiDataOperations.getResourceDataFromDmi(
                 'testCmHandle',
                 'testResourceId',
                 '(a=1,b=2)',
-                'testAcceptParam',
-                '{"operation":"read","cmHandleProperties":{"testName":"testValue"}}') >> new ResponseEntity<>('result-json', HttpStatus.OK)
-        when: 'get resource data is called'
+                'testAcceptParam' ,
+                PASSTHROUGH_OPERATIONAL) >> new ResponseEntity<>('result-json', HttpStatus.OK)
+        when: 'get resource data operational for cm-handle is called'
             def response = objectUnderTest.getResourceDataOperationalForCmHandle('testCmHandle',
-            'testResourceId',
-            'testAcceptParam',
-            '(a=1,b=2)')
-        then: 'dmi returns ok response'
+                'testResourceId',
+                'testAcceptParam',
+                '(a=1,b=2)')
+        then: 'dmi returns a json response'
             response == 'result-json'
     }
 
-    def 'Get resource data for pass-through operational from dmi threw parsing exception.'() {
-        given: 'data node representing cmHandle and its properties'
-            def cmHandleDataNode = getCmHandleDataNodeForTest(true)
-        and: 'cps data service returns valid cmHandle data node'
+    def 'Get resource data for passthrough operational from dmi with Json Processing Exception.'() {
+        given: 'a data node'
+            def dataNode = getDataNode(true)
+        and: 'cps data service returns valid data node'
             mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
-                    cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> cmHandleDataNode
+                cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
         and: 'objectMapper not able to parse object'
             def mockObjectMapper = Mock(ObjectMapper)
             objectUnderTest.objectMapper = mockObjectMapper
             mockObjectMapper.writeValueAsString(_) >> { throw new JsonProcessingException('testException') }
+        and: 'dmi returns NOK response'
+            mockDmiDataOperations.getResourceDataFromDmi(*_)
+                >> new ResponseEntity<>('NOK-json', HttpStatus.NOT_FOUND)
         when: 'get resource data is called'
-            def response = objectUnderTest.getResourceDataOperationalForCmHandle('testCmHandle',
-                    'testResourceId',
-                    'testAcceptParam',
-                    '(a=1,b=2)')
+            objectUnderTest.getResourceDataOperationalForCmHandle('testCmHandle',
+                'testResourceId',
+                'testAcceptParam',
+                '(a=1,b=2)')
         then: 'exception is thrown with the expected details'
             def exceptionThrown = thrown(NcmpException.class)
-            exceptionThrown.details == 'testException'
+            exceptionThrown.details == 'DMI status code: 404, DMI response body: NOK-json'
     }
 
-    def 'Get resource data for pass-through operational from dmi return NOK response.'() {
-        given: 'data node representing cmHandle and its properties'
-            def cmHandleDataNode = getCmHandleDataNodeForTest(true)
-        and: 'cps data service returns valid cmHandle data node'
+    def 'Get resource data for passthrough operational from dmi return NOK response.'() {
+        given: 'a data node'
+            def dataNode = getDataNode(true)
+        and: 'cps data service returns valid data node'
             mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
-                    cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> cmHandleDataNode
+                cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
         and: 'dmi returns NOK response'
-            mockDmiOperations.getResourceDataOperationalFromDmi('testDmiService',
-                    'testCmHandle',
-                    'testResourceId',
-                    '(a=1,b=2)',
-                    'testAcceptParam',
-                    '{"operation":"read","cmHandleProperties":{"testName":"testValue"}}')
-                    >> new ResponseEntity<>('NOK-json', HttpStatus.NOT_FOUND)
+            mockDmiDataOperations.getResourceDataFromDmi('testCmHandle',
+                'testResourceId',
+                '(a=1,b=2)',
+                'testAcceptParam',
+                PASSTHROUGH_OPERATIONAL)
+                >> new ResponseEntity<>('NOK-json', HttpStatus.NOT_FOUND)
         when: 'get resource data is called'
-            def response = objectUnderTest.getResourceDataOperationalForCmHandle('testCmHandle',
-                    'testResourceId',
-                    'testAcceptParam',
-                    '(a=1,b=2)')
+            objectUnderTest.getResourceDataOperationalForCmHandle('testCmHandle',
+                'testResourceId',
+                'testAcceptParam',
+                '(a=1,b=2)')
         then: 'exception is thrown'
             def exceptionThrown = thrown(NcmpException.class)
         and: 'details contains the original response'
             exceptionThrown.details.contains('NOK-json')
     }
 
-    def 'Get resource data for pass-through running from dmi.'() {
-        given: 'data node representing cmHandle and its properties'
-            def cmHandleDataNode = getCmHandleDataNodeForTest(true)
-        and: 'cpsDataService returns valid dataNode'
+    def 'Get resource data for passthrough running from dmi.'() {
+        given: 'a data node'
+            def dataNode = getDataNode(true)
+        and: 'cpsDataService returns valid data node'
             mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
-                    cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> cmHandleDataNode
+                cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
         and: 'dmi returns valid response and data'
-            mockDmiOperations.getResourceDataPassThroughRunningFromDmi('testDmiService',
-                    'testCmHandle',
-                    'testResourceId',
-                    '(a=1,b=2)',
-                    'testAcceptParam',
-                    '{"operation":"read","cmHandleProperties":{"testName":"testValue"}}') >> new ResponseEntity<>('{result-json}', HttpStatus.OK)
+            mockDmiDataOperations.getResourceDataFromDmi('testCmHandle',
+                'testResourceId',
+                '(a=1,b=2)',
+                'testAcceptParam',
+                PASSTHROUGH_RUNNING) >> new ResponseEntity<>('{result-json}', HttpStatus.OK)
         when: 'get resource data is called'
             def response = objectUnderTest.getResourceDataPassThroughRunningForCmHandle('testCmHandle',
-                    'testResourceId',
-                    'testAcceptParam',
-                    '(a=1,b=2)')
+                'testResourceId',
+                'testAcceptParam',
+                '(a=1,b=2)')
         then: 'get resource data returns expected response'
             response == '{result-json}'
     }
 
-    def 'Get resource data for pass-through running from dmi threw parsing exception.'() {
-        given: 'data node representing cmHandle and its properties'
-            def cmHandleDataNode = getCmHandleDataNodeForTest(true)
-        and: 'cpsDataService returns valid dataNode'
-            mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
-                    cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> cmHandleDataNode
-        and: 'objectMapper not able to parse object'
-            def mockObjectMapper = Mock(ObjectMapper)
-            objectUnderTest.objectMapper = mockObjectMapper
-            mockObjectMapper.writeValueAsString(_) >> { throw new JsonProcessingException('testException') }
-        when: 'get resource data is called'
-            def response = objectUnderTest.getResourceDataPassThroughRunningForCmHandle('testCmHandle',
-                    'testResourceId',
-                    'testAcceptParam',
-                    '(a=1,b=2)')
-        then: 'exception is thrown with the expected details'
-            def exceptionThrown = thrown(NcmpException.class)
-            exceptionThrown.details == 'testException'
-    }
-
-    def 'Get resource data for pass-through running from dmi return NOK response.'() {
-        given: 'data node representing cmHandle and its properties'
-            def cmHandleDataNode = getCmHandleDataNodeForTest(true)
+    def 'Get resource data for passthrough running from dmi return NOK response.'() {
+        given: 'a data node'
+            def dataNode = getDataNode(true)
         and: 'cpsDataService returns valid dataNode'
             mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
-                    cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> cmHandleDataNode
+                cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
         and: 'dmi returns NOK response'
-            mockDmiOperations.getResourceDataPassThroughRunningFromDmi('testDmiService',
-                    'testCmHandle',
-                    'testResourceId',
-                    '(a=1,b=2)',
-                    'testAcceptParam',
-                    '{"operation":"read","cmHandleProperties":{"testName":"testValue"}}')
-                    >> new ResponseEntity<>('NOK-json', HttpStatus.NOT_FOUND)
+            mockDmiDataOperations.getResourceDataFromDmi('testCmHandle',
+                'testResourceId',
+                '(a=1,b=2)',
+                'testAcceptParam',
+                PASSTHROUGH_RUNNING)
+                >> new ResponseEntity<>('NOK-json', HttpStatus.NOT_FOUND)
         when: 'get resource data is called'
-            def response = objectUnderTest.getResourceDataPassThroughRunningForCmHandle('testCmHandle',
-                    'testResourceId',
-                    'testAcceptParam',
-                    '(a=1,b=2)')
+            objectUnderTest.getResourceDataPassThroughRunningForCmHandle('testCmHandle',
+                'testResourceId',
+                'testAcceptParam',
+                '(a=1,b=2)')
         then: 'exception is thrown'
             def exceptionThrown = thrown(NcmpException.class)
         and: 'details contains the original response'
             exceptionThrown.details.contains('NOK-json')
     }
 
-    def 'Write resource data for pass-through running from dmi using POST #scenario cm handle properties.'() {
-        given: 'data node representing cmHandle #scenario cm handle properties'
-            def cmHandleDataNode = getCmHandleDataNodeForTest(includeCmHandleProperties)
-        and: 'cpsDataService returns valid cm-handle datanode'
-            mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
-                    cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> cmHandleDataNode
-        when: 'get resource data is called'
-            objectUnderTest.createResourceDataPassThroughRunningForCmHandle('testCmHandle',
-                    'testResourceId',
-                    '{some-json}', 'application/json')
-        then: 'dmi called with correct data'
-            1 * mockDmiOperations.createResourceDataPassThroughRunningFromDmi('testDmiService',
-                'testCmHandle',
-                'testResourceId',
-                '{"operation":"create","dataType":"application/json","data":"{some-json}","cmHandleProperties":'
-                + expectedJsonForCmhandleProperties+ '}')
-                >> { new ResponseEntity<>(HttpStatus.CREATED) }
-        where:
-            scenario  | includeCmHandleProperties || expectedJsonForCmhandleProperties
-            'with'    | true                      || '{"testName":"testValue"}'
-            'without' | false                     || '{}'
-    }
-
-    def 'Write resource data for pass-through running from dmi using POST "not found" response (from DMI).'() {
-        given: 'data node representing cmHandle and its properties'
-            def cmHandleDataNode = getCmHandleDataNodeForTest(true)
-        and: 'cpsDataService returns valid dataNode'
-            mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
-                    cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> cmHandleDataNode
-        and: 'dmi throws exception'
-            mockDmiOperations.createResourceDataPassThroughRunningFromDmi(_ as String, _ as String, _ as String, _ as String)
-                    >> { new ResponseEntity<>(HttpStatus.NOT_FOUND) }
-        when: 'get resource data is called'
-            objectUnderTest.createResourceDataPassThroughRunningForCmHandle('testCmHandle',
-                    'testResourceId',
-                    '{some-json}', 'application/json')
-        then: 'exception is thrown'
-            def exceptionThrown = thrown(NcmpException.class)
-        and: 'details contains (not found) error code: 404'
-            exceptionThrown.details.contains('404')
-    }
-
-    def 'Sync model for a (new) cm handle with #scenario'() {
-        given: 'persistence cm handle is given'
-            def cmHandleForModelSync = new PersistenceCmHandle(id:'some cm handle', dmiServiceName: 'some service name')
-        and: 'additional properties are set as required'
-            if (additionalProperties!=null) {
-                cmHandleForModelSync.setAdditionalProperties(additionalProperties)
-            }
-        and: 'dmi operations returns some module references'
-            def jsonData = TestUtils.getResourceFileContent('cmHandleModules.json')
-            def expectedJsonBody = '{"cmHandleProperties":' + expectedJsonForAdditionalProperties + '}'
-            mockDmiProperties.getAuthUsername() >> 'someUser'
-            mockDmiProperties.getAuthPassword() >> 'somePassword'
-            def moduleReferencesFromCmHandleAsJson = new ResponseEntity<String>(jsonData, HttpStatus.OK)
-            mockDmiOperations.getResourceFromDmiWithJsonData('some service name', expectedJsonBody, 'some cm handle', 'modules') >> moduleReferencesFromCmHandleAsJson
-        and: 'CPS-Core returns list of known modules'
-            mockCpsModuleService.getYangResourceModuleReferences(_) >> existingModuleResourcesInCps
-        and: 'DMI-Plugin returns resource(s) for "new" module(s)'
-            def moduleResources = new ResponseEntity<String>(sdncReponseBody, HttpStatus.OK)
-            def jsonDataToFetchYangResource = '{"data":{"modules":[{"name":"module1","revision":"1"}]},"cmHandleProperties":' + expectedJsonForAdditionalProperties + '}'
-            mockDmiOperations.getResourceFromDmiWithJsonData('some service name', jsonDataToFetchYangResource, 'some cm handle', 'moduleResources') >> moduleResources
-        when: 'module Sync is triggered'
-            objectUnderTest.syncModulesAndCreateAnchor(cmHandleForModelSync)
-        then: 'the CPS module service is called once with the correct parameters'
-            1 * mockCpsModuleService.createSchemaSetFromModules(expectedDataspaceName, cmHandleForModelSync.getId(), expectedYangResourceToContentMap, expectedKnownModules)
-        and: 'admin service create anchor method has been called with correct parameters'
-            1 * mockCpsAdminService.createAnchor(expectedDataspaceName, cmHandleForModelSync.getId(), cmHandleForModelSync.getId())
-        where: 'the following responses are received from SDNC'
-            scenario                         | additionalProperties | existingModuleResourcesInCps                                                  | sdncReponseBody                                                                     || expectedYangResourceToContentMap | expectedKnownModules                                                       | expectedJsonForAdditionalProperties
-            'one unknown module'             | ['name1':'value1']   | [new ModuleReference('module2', '2'), new ModuleReference('module3', '3')]    | '[{"moduleName" : "module1", "revision" : "1","yangSource": "[some yang source]"}]' || [module1: 'some yang source']    | [new ModuleReference('module2', '2')]                                      |'{"name1":"value1"}'
-            'no add. properties'             | [:]                  | [new ModuleReference('module2', '2'), new ModuleReference('module3', '3')]    | '[{"moduleName" : "module1", "revision" : "1","yangSource": "[some yang source]"}]' || [module1: 'some yang source']    | [new ModuleReference('module2', '2')]                                      |'{}'
-            'additional properties is null'  | null                 | [new ModuleReference('module2', '2'), new ModuleReference('module3', '3')]    | '[{"moduleName" : "module1", "revision" : "1","yangSource": "[some yang source]"}]' || [module1: 'some yang source']    | [new ModuleReference('module2', '2')]                                      |'{}'
-            'no unknown module'              | [:]                  | [new ModuleReference('module1', '1'),    new ModuleReference('module2', '2')] | '[]'                                                                                || [:]                              | [new ModuleReference('module1', '1'), new ModuleReference('module2', '2')] |'{}'
-    }
-
     def 'Getting Yang Resources.'() {
         when: 'yang resources is called'
             objectUnderTest.getYangResourcesModuleReferences('some cm handle')
@@ -438,18 +268,6 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
             1 * mockCpsModuleService.getYangResourcesModuleReferences('NFP-Operational','some cm handle')
     }
 
-    def 'Create the request body to get yang resources from DMI.'() {
-        given: 'the expected json request'
-            def expectedRequestBody = '{"data":{"modules":[{"name":"module1","revision":"1"},{"name":"module2","revision":"2"}]},"cmHandleProperties":{"name1":"value1"}}'
-        and: 'module references and cm handle properties'
-            def moduleReferences = [new ModuleReference('module1', '1'),new ModuleReference('module2', '2')]
-            def cmHandleProperties = ['name1':'value1']
-        when: 'get request body to fetch yang resources from DMI is called'
-            def result = objectUnderTest.getRequestBodyToFetchYangResourceFromDmi(moduleReferences, cmHandleProperties)
-        then: 'the result is the same as the expected request body'
-            result == expectedRequestBody
-    }
-
     def 'Get cm handle identifiers for the given module names.'() {
         when: 'execute a cm handle search for the given module names'
             objectUnderTest.executeCmHandleHasAllModulesSearch(['some-module-name'])
@@ -457,45 +275,86 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
             1 * mockCpsAdminService.queryAnchorNames('NFP-Operational', ['some-module-name'])
     }
 
-    def 'Update resource data for pass-through running from dmi using POST #scenario cm handle properties.'() {
-        given: 'data node representing cmHandle #scenario cm handle properties'
-            def cmHandleDataNode = getCmHandleDataNodeForTest(includeCmHandleProperties)
-        and: 'cpsDataService returns valid cm-handle datanode'
+    def 'Update data node leaves.'() {
+        given: 'json data and xpath'
+            def jsonData = 'some json'
+            def xpath = '/xpath'
+        when: 'update node leaves is invoked'
+            objectUnderTest.updateNodeLeaves(cmHandle, xpath, jsonData)
+        then: 'the persistence service is called once with the correct parameters'
+            1 * mockCpsDataService.updateNodeLeaves(expectedDataspaceName, cmHandle, xpath, jsonData, noTimestamp)
+    }
+
+    def 'Replace data node tree.'() {
+        given: 'json data and xpath'
+            def jsonData = 'some json'
+            def xpath = '/xpath'
+        when: 'replace node tree is invoked'
+            objectUnderTest.replaceNodeTree(cmHandle, xpath, jsonData)
+        then: 'the persistence service is called once with the correct parameters'
+            1 * mockCpsDataService.replaceNodeTree(expectedDataspaceName, cmHandle, xpath, jsonData, noTimestamp)
+    }
+
+    def 'Update resource data for passthrough running from dmi using POST #scenario cm handle properties.'() {
+        given: 'a data node'
+            def dataNode = getDataNode(includeCmHandleProperties)
+        and: 'cpsDataService returns valid datanode'
             mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
-                    cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> cmHandleDataNode
-        when: 'update resource data is called'
-            objectUnderTest.updateResourceDataPassThroughRunningForCmHandle('testCmHandle',
-                    'testResourceId',
-                    '{some-json}', 'application/json')
+                cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
+        when: 'get resource data is called'
+            objectUnderTest.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
+                'testResourceId', UPDATE,
+                '{some-json}', 'application/json')
         then: 'dmi called with correct data'
-            1 * mockDmiOperations.updateResourceDataPassThroughRunningFromDmi('testDmiService',
-                    'testCmHandle',
-                    'testResourceId',
-                    '{"operation":"update","dataType":"application/json","data":"{some-json}","cmHandleProperties":'
-                            + expectedJsonForCmhandleProperties + '}')
-                    >> new ResponseEntity<>(HttpStatus.OK)
+            1 * mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi('testCmHandle', 'testResourceId',
+                UPDATE, '{some-json}', 'application/json')
+                >> { new ResponseEntity<>(HttpStatus.OK) }
         where:
             scenario  | includeCmHandleProperties || expectedJsonForCmhandleProperties
             'with'    | true                      || '{"testName":"testValue"}'
             'without' | false                     || '{}'
     }
 
-    def getObjectUnderTestWithModelSyncDisabled() {
-        def objectUnderTest = Spy(new NetworkCmProxyDataServiceImpl(mockDmiOperations, mockCpsModuleService,
-                mockCpsDataService, mockCpsQueryService, mockCpsAdminService, spyObjectMapper))
-        objectUnderTest.syncModulesAndCreateAnchor(_) >> null
-        return objectUnderTest
+    def 'Verify error message from handleResponse is correct for #scenario operation.'() {
+        given: 'writeResourceDataPassThroughRunningFromDmi fails to return OK HttpStatus'
+            mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi(*_)
+                >> new ResponseEntity<>(HttpStatus.NOT_FOUND)
+        when: 'get resource data is called'
+            def response = objectUnderTest.writeResourceDataPassThroughRunningForCmHandle(
+                'testCmHandle',
+                'testResourceId',
+                givenOperation,
+                '{some-json}',
+                'application/json')
+        then: 'an exception is thrown with the expected error message detailsd with correct operation'
+            def exceptionThrown = thrown(NcmpException.class)
+            exceptionThrown.getMessage().contains(expectedResponseMessage)
+        where:
+            scenario | givenOperation || expectedResponseMessage
+            'CREATE' | CREATE         || 'Not able to create resource data.'
+            'READ'   | READ           || 'Not able to read resource data.'
+            'UPDATE' | UPDATE         || 'Not able to update resource data.'
     }
 
-    def getCmHandleDataNodeForTest(boolean includeCmHandleProperties) {
-        def cmHandleDataNode = new DataNode()
-        cmHandleDataNode.leaves = ['dmi-service-name': 'testDmiService']
+    def 'Query data nodes by cps path with #fetchDescendantsOption.'() {
+        given: 'a cps path'
+            def cpsPath = '/cps-path'
+        when: 'query data nodes is invoked'
+            objectUnderTest.queryDataNodes(cmHandle, cpsPath, fetchDescendantsOption)
+        then: 'the persistence query service is called once with the correct parameters'
+            1 * mockCpsQueryService.queryDataNodes(expectedDataspaceName, cmHandle, cpsPath, fetchDescendantsOption)
+        where: 'all fetch descendants options are supported'
+            fetchDescendantsOption << FetchDescendantsOption.values()
+    }
+
+    def getDataNode(boolean includeCmHandleProperties) {
+        def dataNode = new DataNode()
+        dataNode.leaves = ['dmi-service-name': 'testDmiService']
         if (includeCmHandleProperties) {
             def cmHandlePropertyDataNode = new DataNode()
             cmHandlePropertyDataNode.leaves = ['name': 'testName', 'value': 'testValue']
-            cmHandleDataNode.childDataNodes = [cmHandlePropertyDataNode]
+            dataNode.childDataNodes = [cmHandlePropertyDataNode]
         }
-        return cmHandleDataNode
+        return dataNode
     }
-
 }
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operation/DmiOperationsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operation/DmiOperationsSpec.groovy
deleted file mode 100644 (file)
index 44d4f0c..0000000
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021 Nordix Foundation
- *  ================================================================================
- *  Licensed under the Apache License, Version 2.0 (the "License");
- *  you may not use this file except in compliance with the License.
- *  You may obtain a copy of the License at
- *
- *        http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- *
- *  SPDX-License-Identifier: Apache-2.0
- *  ============LICENSE_END=========================================================
- */
-
-package org.onap.cps.ncmp.api.impl.operation
-
-import org.onap.cps.ncmp.api.impl.client.DmiRestClient
-import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration
-import org.spockframework.spring.SpringBean
-import org.springframework.beans.factory.annotation.Autowired
-import org.springframework.boot.test.context.SpringBootTest
-import org.springframework.http.HttpHeaders
-import org.springframework.test.context.ContextConfiguration
-import spock.lang.Specification
-
-@SpringBootTest
-@ContextConfiguration(classes = [NcmpConfiguration.DmiProperties, DmiOperations])
-class DmiOperationsSpec extends Specification {
-
-    @SpringBean
-    DmiRestClient mockDmiRestClient = Mock()
-
-    @Autowired
-    DmiOperations objectUnderTest = new DmiOperations(mockDmiRestClient)
-
-    def 'call get resource data for pass-through:operational datastore from DMI.'() {
-        given: 'expected url'
-            def expectedUrl = 'testDmiBasePath/dmi/v1/ch/testCmhandle/data/ds' +
-                    '/ncmp-datastore:passthrough-operational?resourceIdentifier=parent/child&options=(a=1,b=2)'
-        when: 'get resource data is called to DMI'
-            objectUnderTest.getResourceDataOperationalFromDmi('testDmiBasePath',
-                    'testCmhandle',
-                    'parent/child',
-                    '(a=1,b=2)',
-                    'testAcceptJson',
-                    'testJsonbody')
-        then: 'the put operation is executed with the correct URL'
-            1 * mockDmiRestClient.putOperationWithJsonData(expectedUrl, 'testJsonbody', _ as HttpHeaders)
-    }
-    def 'call get resource data for pass-through:running datastore from DMI.'() {
-        given: 'expected url'
-            def expectedUrl = 'testDmiBasePath/dmi/v1/ch/testCmhandle/data/ds' +
-                    '/ncmp-datastore:passthrough-running?resourceIdentifier=parent/child&options=(a=1,b=2)'
-        when: 'get resource data is called to DMI'
-            objectUnderTest.getResourceDataPassThroughRunningFromDmi('testDmiBasePath',
-                    'testCmhandle',
-                    'parent/child',
-                    '(a=1,b=2)',
-                    'testAcceptJson',
-                    'testJsonbody')
-        then: 'the put operation is executed with the correct URL'
-            1 * mockDmiRestClient.putOperationWithJsonData(expectedUrl, 'testJsonbody', _ as HttpHeaders)
-    }
-    def 'call get resource data for pass-through:operational datastore from DMI when options is null.'() {
-        given: 'expected url'
-        def expectedUrl = 'testDmiBasePath/dmi/v1/ch/testCmhandle/data/ds' +
-                '/ncmp-datastore:passthrough-operational?resourceIdentifier=parent/child'
-        when: 'get resource data is called to DMI'
-        objectUnderTest.getResourceDataOperationalFromDmi('testDmiBasePath',
-                'testCmhandle',
-                'parent/child',
-                null,
-                'testAcceptJson',
-                'testJsonbody')
-        then: 'the put operation is executed with the correct URL'
-        1 * mockDmiRestClient.putOperationWithJsonData(expectedUrl, 'testJsonbody', _ as HttpHeaders)
-    }
-    def 'call create resource data for pass-through:running datastore from DMI.'() {
-        given: 'expected url'
-            def expectedUrl = 'testDmiBasePath/dmi/v1/ch/testCmhandle/data/ds' +
-                    '/ncmp-datastore:passthrough-running?resourceIdentifier=parent/child'
-        when: 'get resource data is called to DMI'
-            objectUnderTest.createResourceDataPassThroughRunningFromDmi('testDmiBasePath',
-                    'testCmhandle',
-                    'parent/child',
-                    'testJsonbody')
-        then: 'the put operation is executed with the correct URL'
-            1 * mockDmiRestClient.postOperationWithJsonData(expectedUrl, 'testJsonbody', _ as HttpHeaders)
-    }
-
-    def 'Call get resource from dmi.'() {
-        given: 'expected url'
-            def expectedUrl = 'testDmiBasePath/dmi/v1/ch/testCmhandle/modules'
-        when: 'get resource data is called to dmi'
-            objectUnderTest.getResourceFromDmi('testDmiBasePath',
-                    'testCmhandle',
-                    'modules')
-        then: 'the post operation is executed with the correct URL'
-            1 * mockDmiRestClient.postOperation(expectedUrl, _ as HttpHeaders)
-    }
-
-    def 'Call get resource from dmi with json data.'() {
-        given: 'expected url & json data'
-            def requestBody = 'some json'
-            def expectedUrl = 'testDmiBasePath/dmi/v1/ch/testCmHandle/modules'
-            def expectedHttpHeaders = new HttpHeaders()
-        when: 'get resource data is called to dmi'
-            objectUnderTest.getResourceFromDmiWithJsonData('testDmiBasePath',
-                    requestBody,
-                    'testCmHandle',
-                    'modules')
-        then: 'the post operation is executed with the correct URL and json data'
-            1 * mockDmiRestClient.postOperationWithJsonData(expectedUrl, requestBody, expectedHttpHeaders)
-    }
-
-    def 'Update resource data for pass-through:running datastore from DMI.'() {
-        given: 'the expected url'
-            def cmHandle = 'some-cmhandle'
-            def resourceIdentifier = 'parent/child'
-            def expectedUrl = 'some-dmi-service-name/dmi/v1/ch/' + cmHandle + '/data/ds' +
-                    '/ncmp-datastore:passthrough-running?resourceIdentifier=' + resourceIdentifier
-        when: 'replace resource data is called for DMI'
-            objectUnderTest.updateResourceDataPassThroughRunningFromDmi('some-dmi-service-name',
-                    cmHandle,
-                    resourceIdentifier,
-                    'some-json-body')
-        then: 'the post operation is executed with the correct URL'
-            1 * mockDmiRestClient.postOperationWithJsonData(expectedUrl, 'some-json-body', _ as HttpHeaders)
-    }
-}
\ No newline at end of file
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
new file mode 100644 (file)
index 0000000..674a442
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.ncmp.api.impl.operations
+
+import org.onap.cps.ncmp.api.impl.client.DmiRestClient
+import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.context.SpringBootTest
+import org.springframework.http.ResponseEntity
+import org.springframework.test.context.ContextConfiguration
+
+import static org.onap.cps.ncmp.api.impl.operations.DmiOperations.DataStoreEnum.PASSTHROUGH_OPERATIONAL
+import static org.onap.cps.ncmp.api.impl.operations.DmiOperations.DataStoreEnum.PASSTHROUGH_RUNNING
+import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.CREATE
+import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.UPDATE
+import org.springframework.http.HttpStatus
+
+@SpringBootTest
+@ContextConfiguration(classes = [NcmpConfiguration.DmiProperties, DmiDataOperations])
+class DmiDataOperationsSpec extends DmiOperationsBaseSpec {
+
+    @Autowired
+    DmiDataOperations objectUnderTest
+
+    def 'call get resource data for #expectedDatastoreInUrl from DMI #scenario.'() {
+        given: 'a persistence cm handle for #cmHandleId'
+            mockPersistenceCmHandleRetrieval(additionalProperties)
+        and: 'a positive response from dmi service when it is called with the expected parameters'
+            def responseFromDmi = new ResponseEntity<Object>(HttpStatus.OK)
+            mockDmiRestClient.putOperationWithJsonData(
+                "${dmiServiceName}/dmi/v1/ch/${cmHandleId}/data/ds/ncmp-datastore:${expectedDatastoreInUrl}?resourceIdentifier=${resourceIdentifier}${expectedOptionsInUrl}",
+                expectedJson, [Accept:['sample accept header']]) >> responseFromDmi
+        when: 'get resource data is invoked'
+            def result = objectUnderTest.getResourceDataFromDmi(cmHandleId,resourceIdentifier, options,'sample accept header', dataStore)
+        then: 'the result is the response from the dmi service'
+            assert result == responseFromDmi
+        where: 'the following parameters are used'
+            scenario             | additionalProperties       | dataStore               | options     || expectedJson                                                 | expectedDatastoreInUrl    | expectedOptionsInUrl
+            'without properties' | []                         | PASSTHROUGH_OPERATIONAL | '(a=1,b=2)' || '{"operation":"read","cmHandleProperties":{}}'               | 'passthrough-operational' | '&options=(a=1,b=2)'
+            'null properties'    | null                       | PASSTHROUGH_OPERATIONAL | '(a=1,b=2)' || '{"operation":"read","cmHandleProperties":{}}'               | 'passthrough-operational' | '&options=(a=1,b=2)'
+            'with properties'    | [sampleAdditionalProperty] | PASSTHROUGH_OPERATIONAL | '(a=1,b=2)' || '{"operation":"read","cmHandleProperties":{"prop1":"val1"}}' | 'passthrough-operational' | '&options=(a=1,b=2)'
+            'null options'       | [sampleAdditionalProperty] | PASSTHROUGH_OPERATIONAL | null        || '{"operation":"read","cmHandleProperties":{"prop1":"val1"}}' | 'passthrough-operational' | ''
+            'empty options'      | [sampleAdditionalProperty] | PASSTHROUGH_OPERATIONAL | ''          || '{"operation":"read","cmHandleProperties":{"prop1":"val1"}}' | 'passthrough-operational' | ''
+            'datastore running'  | []                         | PASSTHROUGH_RUNNING     | '(a=1,b=2)' || '{"operation":"read","cmHandleProperties":{}}'               | 'passthrough-running'     | '&options=(a=1,b=2)'
+    }
+
+    def 'Write data for pass-through:running datastore in DMI.'() {
+        given: 'a persistence cm handle for #cmHandleId'
+            mockPersistenceCmHandleRetrieval([sampleAdditionalProperty])
+        and: 'a positive response from dmi service when it is called with the expected parameters'
+            def expectedUrl = "${dmiServiceName}/dmi/v1/ch/${cmHandleId}/data/ds" +
+                "/ncmp-datastore:passthrough-running?resourceIdentifier=${resourceIdentifier}"
+            def expectedJson = '{"operation":"' + expectedOperationInUrl + '","dataType":"some data type","data":"requestData","cmHandleProperties":{"prop1":"val1"}}'
+            def responseFromDmi = new ResponseEntity<Object>(HttpStatus.OK)
+            mockDmiRestClient.postOperationWithJsonData(expectedUrl, expectedJson, [:]) >> responseFromDmi
+        when: 'write resource method is invoked'
+            def result = objectUnderTest.writeResourceDataPassThroughRunningFromDmi(cmHandleId,'parent/child', operation, 'requestData', 'some data type')
+        then: 'the result is the response from the dmi service'
+            assert result == responseFromDmi
+        where: 'the following operation is performed'
+            operation || expectedOperationInUrl
+            CREATE    || 'create'
+            UPDATE    || 'update'
+    }
+
+}
\ No newline at end of file
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiModelOperationsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiModelOperationsSpec.groovy
new file mode 100644 (file)
index 0000000..d9d1271
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.ncmp.api.impl.operations
+
+import com.fasterxml.jackson.core.JsonProcessingException
+import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration
+import org.onap.cps.ncmp.api.impl.exception.NcmpException
+import org.onap.cps.spi.model.ModuleReference
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.context.SpringBootTest
+import org.springframework.http.HttpStatus
+import org.springframework.http.ResponseEntity
+import org.springframework.test.context.ContextConfiguration
+import spock.lang.Shared
+
+@SpringBootTest
+@ContextConfiguration(classes = [NcmpConfiguration.DmiProperties, DmiModelOperations])
+class DmiModelOperationsSpec extends DmiOperationsBaseSpec {
+
+    @Shared
+    def newModuleReferences = [new ModuleReference('mod1','A'), new ModuleReference('mod2','X')]
+
+    @Autowired
+    DmiModelOperations objectUnderTest
+
+    def 'Module references for a persistence cm handle #scenario.'() {
+        given: 'a persistence cm handle for #cmHandleId'
+            mockPersistenceCmHandleRetrieval(additionalPropertiesObject)
+        and: 'a positive response from dmi service when it is called with tha expected parameters'
+            def responseFromDmi = new ResponseEntity<String>(HttpStatus.OK)
+            mockDmiRestClient.postOperationWithJsonData("${dmiServiceName}/dmi/v1/ch/${cmHandleId}/modules",
+                '{"cmHandleProperties":' + expectedAdditionalPropertiesInRequest + '}', [:]) >> responseFromDmi
+        when: 'a get module references is called'
+            def result = objectUnderTest.getModuleReferences(persistenceCmHandle)
+        then: 'the result is the response from dmi service'
+            assert result == responseFromDmi
+        where:
+            scenario               | additionalPropertiesObject || expectedAdditionalPropertiesInRequest
+            'with properties'      | [sampleAdditionalProperty] || '{"prop1":"val1"}'
+            'with null properties' | null                       || "{}"
+            'without properties'   | []                         || "{}"
+    }
+
+    def 'New yang resources from dmi using persistence cm handle #scenario.'() {
+        given: 'a persistence cm handle for #cmHandleId'
+            mockPersistenceCmHandleRetrieval(additionalPropertiesObject)
+        and: 'a positive response from dmi service when it is called with tha expected parameters'
+            def responseFromDmi = new ResponseEntity<String>(HttpStatus.OK)
+            mockDmiRestClient.postOperationWithJsonData("${dmiServiceName}/dmi/v1/ch/${cmHandleId}/moduleResources",
+            '{"data":{"modules":[' + expectedModuleReferencesInRequest + ']},"cmHandleProperties":'+expectedAdditionalPropertiesInRequest+'}',
+            [:]) >> responseFromDmi
+        when: 'get new yang resources from dmi service'
+            def result = objectUnderTest.getNewYangResourcesFromDmi(persistenceCmHandle, unknownModuleReferences)
+        then: 'the result is the response from dmi service'
+            assert result == responseFromDmi
+        where:
+            scenario                                | additionalPropertiesObject | unknownModuleReferences || expectedAdditionalPropertiesInRequest | expectedModuleReferencesInRequest
+            'with module references and properties' | [sampleAdditionalProperty] | newModuleReferences     || '{"prop1":"val1"}'                    | '{"name":"mod1","revision":"A"},{"name":"mod2","revision":"X"}'
+            'without module references'             | [sampleAdditionalProperty] | []                      || '{"prop1":"val1"}'                    | ''
+            'without properties'                    | []                         | newModuleReferences     || '{}'                                  | '{"name":"mod1","revision":"A"},{"name":"mod2","revision":"X"}'
+    }
+
+    def 'New yang resources from dmi with additional properties null'() {
+        given: 'a persistence cm handle for #cmHandleId'
+            mockPersistenceCmHandleRetrieval(null)
+        when: 'a get new yang resources from dmi is called'
+            objectUnderTest.getNewYangResourcesFromDmi(persistenceCmHandle, [])
+        then: 'a null pointer is thrown (we might need to address this later)'
+            thrown(NullPointerException)
+    }
+
+    def 'Json Processing Exception'() {
+        given: 'a persistence cm handle for #cmHandleId'
+            mockPersistenceCmHandleRetrieval([])
+        and: 'a Json processing exception occurs'
+            spyObjectMapper.writeValueAsString(_) >> {throw (new JsonProcessingException(''))}
+        when: 'a dmi operation is executed'
+            objectUnderTest.getModuleReferences(persistenceCmHandle)
+        then: 'an ncmp exception is thrown'
+            def exceptionThrown = thrown(NcmpException)
+        and: 'the message indicates a parsing error'
+            exceptionThrown.message.toLowerCase().contains("parsing error")
+    }
+
+}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiOperationsBaseSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiOperationsBaseSpec.groovy
new file mode 100644 (file)
index 0000000..4bf7dad
--- /dev/null
@@ -0,0 +1,36 @@
+package org.onap.cps.ncmp.api.impl.operations
+
+import com.fasterxml.jackson.databind.ObjectMapper
+import org.onap.cps.ncmp.api.impl.client.DmiRestClient
+import org.onap.cps.ncmp.api.models.PersistenceCmHandle
+import org.spockframework.spring.SpringBean
+import spock.lang.Shared
+import spock.lang.Specification
+
+abstract class DmiOperationsBaseSpec extends Specification {
+
+    @Shared
+    def sampleAdditionalProperty = new PersistenceCmHandle.AdditionalProperty('prop1', 'val1')
+
+    @SpringBean
+    DmiRestClient mockDmiRestClient = Mock()
+
+    @SpringBean
+    PersistenceCmHandleRetriever mockCmHandlePropertiesRetriever = Mock()
+
+    @SpringBean
+    ObjectMapper spyObjectMapper = Spy()
+
+    def persistenceCmHandle = new PersistenceCmHandle()
+    def static dmiServiceName = 'some service name'
+    def static cmHandleId = 'some cm handle'
+    def static resourceIdentifier = 'parent/child'
+
+    def mockPersistenceCmHandleRetrieval(additionalProperties) {
+        persistenceCmHandle.dmiDataServiceName = dmiServiceName
+        persistenceCmHandle.dmiServiceName = dmiServiceName
+        persistenceCmHandle.additionalProperties = additionalProperties
+        persistenceCmHandle.id = cmHandleId
+        mockCmHandlePropertiesRetriever.retrieveCmHandleDmiServiceNameAndProperties(cmHandleId) >> persistenceCmHandle
+    }
+}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/PersistenceCmHandleRetrieverSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/PersistenceCmHandleRetrieverSpec.groovy
new file mode 100644 (file)
index 0000000..3ab9266
--- /dev/null
@@ -0,0 +1,44 @@
+package org.onap.cps.ncmp.api.impl.operations
+
+import org.onap.cps.api.CpsDataService
+import org.onap.cps.ncmp.api.models.PersistenceCmHandle
+import spock.lang.Shared
+
+import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
+import org.onap.cps.spi.model.DataNode
+import spock.lang.Specification
+
+class PersistenceCmHandleRetrieverSpec extends Specification {
+
+    def mockCpsDataService = Mock(CpsDataService)
+
+    def objectUnderTest = new PersistenceCmHandleRetriever(mockCpsDataService)
+
+    def cmHandleId = 'some cm handle'
+    def leaves = ["dmi-service-name":"common service name","dmi-data-service-name":"data service name","dmi-model-service-name":"model service name"]
+    def xpath = "/dmi-registry/cm-handles[@id='some cm handle']"
+
+    @Shared
+    def childDataNodesForCmHandleProperties = [new DataNode(leaves: ["name":"name1","value":"value1"]),
+                                               new DataNode(leaves: ["name":"name2","value":"value2"])]
+
+    def "Retrieve CmHandle using datanode #scenario."() {
+        given: 'the cps data service returns a data node from the dmi registry'
+            def dataNode = new DataNode(childDataNodes:childDataNodes, leaves: leaves)
+            mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry', xpath, INCLUDE_ALL_DESCENDANTS) >> dataNode
+        when: 'retrieving the persisted cm handle'
+            def result = objectUnderTest.retrieveCmHandleDmiServiceNameAndProperties(cmHandleId)
+        then: 'the result has the correct id and service names'
+            result.id == cmHandleId
+            result.dmiServiceName == 'common service name'
+            result.dmiDataServiceName == 'data service name'
+            result.dmiModelServiceName == 'model service name'
+        and: 'the expected additional properties'
+            result.additionalProperties == expectedCmHandleProperties
+        where: 'the following parameters are used'
+            scenario                        | childDataNodes                      || expectedCmHandleProperties
+            'without additional properties' | []                                  || []
+            'with additional properties'    | childDataNodesForCmHandleProperties || [new PersistenceCmHandle.AdditionalProperty("name1", "value1"),
+                                                                                      new PersistenceCmHandle.AdditionalProperty("name2", "value2")]
+    }
+}
index bfed795..c66eaa9 100644 (file)
@@ -21,13 +21,15 @@ package org.onap.cps.ncmp.api.models
 
 import spock.lang.Specification
 
-class PersistenceCmHandleSpec extends Specification {
+import static org.onap.cps.ncmp.api.impl.operations.RequiredDmiService.DATA
+import static org.onap.cps.ncmp.api.impl.operations.RequiredDmiService.MODEL
 
-    def objectUnderTest = new PersistenceCmHandle()
+class PersistenceCmHandleSpec extends Specification {
 
     def 'Setting and getting additional properties.'() {
         given: 'a map of one property is added'
-            objectUnderTest.setAdditionalProperties([myProperty: 'some value'])
+            def objectUnderTest = new PersistenceCmHandle()
+            objectUnderTest.asAdditionalProperties([myProperty: 'some value'])
         when: 'the additional properties are retrieved'
             def result = objectUnderTest.getAdditionalProperties()
         then: 'the result has the right size'
@@ -39,4 +41,23 @@ class PersistenceCmHandleSpec extends Specification {
             assert actualAdditionalProperty.value == expectedAdditionalProperty.value
     }
 
+    def 'Resolve dmi service name: #scenario and #requiredService service require.'() {
+        given: 'a Persistence CM Handle'
+            def objectUnderTest = PersistenceCmHandle.toPersistenceCmHandle(dmiServiceName, dmiDataServiceName, dmiModelServiceName, new CmHandle('some id', null))
+        expect:
+            assert objectUnderTest.resolveDmiServiceName(requiredService) == expectedService
+        where:
+            scenario                        | dmiServiceName     | dmiDataServiceName | dmiModelServiceName | requiredService || expectedService
+            'common service registered'     | 'common service'   | 'does not matter'  | 'does not matter'   | DATA            || 'common service'
+            'common service registered'     | 'common service'   | 'does not matter'  | 'does not matter'   | MODEL           || 'common service'
+            'common service empty'          | ''                 | 'data service'     | 'does not matter'   | DATA            || 'data service'
+            'common service empty'          | ''                 | 'does not matter'  | 'model service'     | MODEL           || 'model service'
+            'common service blank'          | '   '              | 'data service'     | 'does not matter'   | DATA            || 'data service'
+            'common service blank'          | '   '              | 'does not matter'  | 'model service'     | MODEL           || 'model service'
+            'common service null '          | null               | 'data service'     | 'does not matter'   | DATA            || 'data service'
+            'common service null'           | null               | 'does not matter'  | 'model service'     | MODEL           || 'model service'
+            'only model service registered' | null               | null               | 'does not matter'   | DATA            || null
+            'only data service registered'  | null               | 'does not matter'  | null                | MODEL           || null
+    }
+
 }
index 71ac2c9..d8fbb64 100644 (file)
@@ -20,4 +20,6 @@ dmi:
     auth:
         username: some-user
         password: some-password
+    api:
+        base-path: /dmi
 
index d9be36a..fe2428e 100644 (file)
@@ -111,4 +111,52 @@ databaseChangeLog:
         tableName: 'anchor'
       rollback:
         - sql:
-            sql: delete from anchor where name = 'ncmp-dmi-registry'
\ No newline at end of file
+            sql: delete from anchor where name = 'ncmp-dmi-registry'
+
+  - changeSet:
+      author: cps
+      label: dmi-registry-schema-preload
+      id: 9.5
+      loadUpdateData:
+        encoding: UTF-8
+        file: 'changelog/db/changes/data/dmi/yang_resource@2021-10-20.csv'
+        onlyUpdate: 'false'
+        primaryKey: 'id'
+        quotchar: '"'
+        separator: '|'
+        tableName: 'yang_resource'
+        columns:
+          - column:
+              header:  name
+              name:  name
+              type:  STRING
+          - column:
+              header:  content
+              name: content
+              type: STRING
+          - column:
+              header:  checksum
+              name: checksum
+              type: STRING
+      rollback:
+        - sql:
+            sql: delete from yang_resource where name = 'dmi-registry@2021-10-20.yang'
+
+  - changeSet:
+      author: cps
+      label: dmi-registry-schema-preload
+      id: 9.6
+      loadUpdateData:
+        encoding: UTF-8
+        file: 'changelog/db/changes/data/dmi/schema_set_yang_resources@2021-10-20.csv'
+        quotchar: '"'
+        primaryKey: 'schema_set_id,yang_resource_id'
+        separator: '|'
+        tableName: 'schema_set_yang_resources'
+        usePreparedStatements:  true
+      rollback:
+        - sql:
+            sql: >
+              delete from schema_set_yang_resources
+              where schema_set_id = (select id from schema_set where name = 'ncmp-dmi-registry-model')
+              and yang_resource_id = (select id from yang_resource where name = 'dmi-registry@2021-10-20.yang')
diff --git a/cps-ri/src/main/resources/changelog/db/changes/data/dmi/schema_set_yang_resources@2021-10-20.csv b/cps-ri/src/main/resources/changelog/db/changes/data/dmi/schema_set_yang_resources@2021-10-20.csv
new file mode 100644 (file)
index 0000000..d419fc9
--- /dev/null
@@ -0,0 +1,3 @@
+schema_set_id|yang_resource_id
+(select id from schema_set where name='ncmp-dmi-registry-model')|(select id from yang_resource where name='dmi-registry@2021-10-20.yang')
+
diff --git a/cps-ri/src/main/resources/changelog/db/changes/data/dmi/yang_resource@2021-10-20.csv b/cps-ri/src/main/resources/changelog/db/changes/data/dmi/yang_resource@2021-10-20.csv
new file mode 100644 (file)
index 0000000..80caea2
--- /dev/null
@@ -0,0 +1,53 @@
+name|content|checksum
+dmi-registry@2021-10-20.yang|"module dmi-registry {
+
+  yang-version 1.1;
+
+  namespace \"org:onap:cps:ncmp\";
+
+  prefix dmi-reg;
+
+  organization \"Nordix Foundation\";
+
+  contact \"rahul.tyagi@est.tech\";
+
+  revision \"2021-10-20\" {
+   description
+   \"Added dmi-data-service-name & dmi-model-service-name to allow separate DMI instances for each responsibility\";
+  }
+
+  container dmi-registry {
+
+    list cm-handles {
+
+      key \"id\";
+
+      leaf id {
+        type string;
+      }
+
+      leaf dmi-service-name {
+        type string;
+      }
+
+      leaf dmi-data-service-name {
+        type string;
+      }
+
+      leaf dmi-model-service-name {
+        type string;
+      }
+
+      list additional-properties {
+        key \"name\";
+        leaf name {
+          type string;
+        }
+        leaf value {
+          type string;
+        }
+      }
+    }
+  }
+}
+"|f9145662dfc97363297dc1c06b8bde3a8621c658abefd56946faaab240714181
index e4bdd47..1e27fb9 100755 (executable)
 
 check_health()
 {
-TIME_OUT=120
-INTERVAL=5
-TICKER=0
+  TIME_OUT=120
+  INTERVAL=5
+  TICKER=0
 
-while [ "$TICKER" -le "$TIME_OUT" ]; do
+  while [ "$TICKER" -le "$TIME_OUT" ]; do
 
-  RESPONSE=$(curl --location --request GET 'http://'$1'/manage/health/readiness')
+    RESPONSE=$(curl --location --request GET 'http://'$1'/manage/health/readiness')
 
-  if [[ "$RESPONSE" == *"UP"* ]]; then
-    echo "$2 started in $TICKER"
-    break;
-  fi
+    if [[ "$RESPONSE" == *"UP"* ]]; then
+      echo "$2 started in $TICKER"
+      break;
+    fi
 
-  sleep $INTERVAL
-  TICKER=$((TICKER + INTERVAL))
+    sleep $INTERVAL
+    TICKER=$((TICKER + INTERVAL))
 
-done
-
-if [ "$TICKER" -ge "$TIME_OUT" ]; then
-  echo TIME OUT: $2 session not started in $TIME_OUT seconds... Could cause problems for testing activities...
-fi
+  done
 
+  if [ "$TICKER" -ge "$TIME_OUT" ]; then
+    echo TIME OUT: $2 session not started in $TIME_OUT seconds... Could cause problems for testing activities...
+  fi
 }
 
 ###################### setup env ############################
@@ -143,4 +142,4 @@ check_health $DMI_HOST:$DMI_MANAGEMENT_PORT 'dmi-plugin'
 
 ###################### ROBOT Configurations ##########################
 # Pass variables required for Robot test suites in ROBOT_VARIABLES
-ROBOT_VARIABLES="-v CPS_CORE_HOST:$CPS_CORE_HOST -v CPS_CORE_PORT:$CPS_CORE_PORT -v DMI_HOST:$LOCAL_IP -v DMI_PORT:$DMI_PORT -v CPS_CORE_MANAGEMENT_PORT:$CPS_CORE_MANAGEMENT_PORT -v DATADIR:$WORKSPACE/data"
\ No newline at end of file
+ROBOT_VARIABLES="-v CPS_CORE_HOST:$CPS_CORE_HOST -v CPS_CORE_PORT:$CPS_CORE_PORT -v DMI_HOST:$LOCAL_IP -v DMI_PORT:$DMI_PORT -v CPS_CORE_MANAGEMENT_PORT:$CPS_CORE_MANAGEMENT_PORT -v DATADIR:$WORKSPACE/data --exitonfailure"
\ No newline at end of file
index 340577e..31931c1 100644 (file)
@@ -19,7 +19,7 @@
  */
 
 *** Settings ***
-Documentation         NCMP-DMI - Model Sync
+Documentation         NCMP-DMI - Registration & Model Sync
 
 Library               Collections
 Library               OperatingSystem
@@ -33,7 +33,6 @@ Suite Setup           Create Session     DMI_URL    http://${DMI_HOST}:${DMI_POR
 ${auth}            Basic Y3BzdXNlcjpjcHNyMGNrcyE=
 ${basePath}        /dmi
 
-
 *** Test Cases ***
 Register node & sync models
     ${uri}=              Set Variable       ${basePath}/v1/inventory/cmHandles
@@ -48,11 +47,10 @@ Verify Sync
     ${jsonData}=         Get Binary File    ${DATADIR}${/}postModuleRequestBody.json
     ${response}=         POST On Session    DMI_URL   ${uri}   headers=${headers}   data=${jsonData}
     ${responseJson}=     Set Variable       ${response.json()}
-    ${moduleCount}=      Get length         ${responseJson['schemas']}
     Should Be Equal As Strings              ${response.status_code}   200
     FOR   ${item}   IN  @{responseJson['schemas']}
-        IF   "${item}.get('moduleName')" == "stores"
-            Should Be Equal As Strings              "${item}.get('revision')"   2020-09-15
-            Should Be Equal As Strings              "${item}.get('namespace')"   org:onap:ccsdk:sample
+            IF   "${item['moduleName']}" == "stores"
+                Should Be Equal As Strings              "${item['revision']}"   "2020-09-15"
+                Should Be Equal As Strings              "${item['namespace']}"  "org:onap:ccsdk:sample"
+            END
         END
-    END