Merge "Get Node API fix for attribute values with '/'"
authorSourabh Sourabh <sourabh.sourabh@est.tech>
Mon, 19 Sep 2022 10:08:46 +0000 (10:08 +0000)
committerGerrit Code Review <gerrit@onap.org>
Mon, 19 Sep 2022 10:08:46 +0000 (10:08 +0000)
39 files changed:
INFO.yaml
cps-application/src/main/resources/application.yml
cps-ncmp-rest-stub/src/main/java/org/onap/cps/ncmp/rest/stub/controller/NetworkCmProxyStubController.java
cps-ncmp-rest/docs/openapi/ncmp.yml
cps-ncmp-rest/docs/openapi/openapi.yml
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/DatastoreType.java
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/InvalidDatastoreException.java [new file with mode: 0644]
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandler.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/exceptions/NetworkCmProxyRestExceptionHandlerSpec.groovy
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/yangmodels/YangModelCmHandle.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistence.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistenceImpl.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncTasks.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncWatchdog.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponse.java
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/InventoryPersistenceImplSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/InventoryPersistenceSpec.groovy with 99% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncWatchdogSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponseSpec.groovy
cps-rest/src/main/java/org/onap/cps/rest/exceptions/CpsRestExceptionHandler.java
cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java
cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryCpsPathQueryImpl.java
cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy
cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy
cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java
cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java
cps-service/src/main/java/org/onap/cps/spi/exceptions/AlreadyDefinedExceptionBatch.java [new file with mode: 0644]
cps-service/src/main/java/org/onap/cps/spi/exceptions/ConcurrencyException.java
cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy
docs/api/swagger/cps/openapi.yaml
docs/api/swagger/ncmp/openapi-inventory.yaml
docs/api/swagger/ncmp/openapi.yaml
docs/release-notes.rst
releases/3.1.0-container.yaml [new file with mode: 0644]
releases/3.1.0.yaml [new file with mode: 0644]

index a411e37..ca4f250 100755 (executable)
--- a/INFO.yaml
+++ b/INFO.yaml
@@ -52,11 +52,6 @@ committers:
       company: 'Bell Canada'
       id: 'puthuparambil.aditya'
       timezone: 'Europe/Dublin'
-    - name: 'Renu Kumari'
-      email: 'renu.kumari@bell.ca'
-      company: 'Bell Canada'
-      id: 'renukumari'
-      timezone: 'America/Toronto'
     - name: 'Joseph Keenan'
       email: 'joseph.keenan@est.tech'
       company: 'Ericsson Software Technology'
index 9b6f41e..f7a06c5 100644 (file)
@@ -45,13 +45,12 @@ spring:
         username: ${DB_USERNAME}\r
         password: ${DB_PASSWORD}\r
         driverClassName: org.postgresql.Driver\r
-        initialization-mode: always\r
         hikari:\r
             minimumIdle: 5\r
             maximumPoolSize: 80\r
-            idleTimeout: 120000\r
-            connectionTimeout: 300000\r
-            leakDetectionThreshold: 300000\r
+            idleTimeout: 60000\r
+            connectionTimeout: 120000\r
+            leakDetectionThreshold: 30000\r
             pool-name: CpsDatabasePool\r
 \r
     cache:\r
@@ -91,6 +90,9 @@ spring:
       default-property-inclusion: NON_NULL\r
       serialization:\r
         FAIL_ON_EMPTY_BEANS: false\r
+    sql:\r
+      init:\r
+        mode: ALWAYS\r
 app:\r
     ncmp:\r
         async-m2m:\r
index ac5337d..bb919b5 100644 (file)
@@ -57,13 +57,13 @@ public class NetworkCmProxyStubController implements NetworkCmProxyApi {
     private String pathToResponseFiles;
 
     @Override
-    public ResponseEntity<Object> getResourceDataForCmHandle(final String dataStoreType,
+    public ResponseEntity<Object> getResourceDataForCmHandle(final String dataStoreName,
                                                              final String cmHandle,
                                                              final String resourceIdentifier,
                                                              final String optionsParamInQuery,
                                                              final String topicParamInQuery,
                                                              final Boolean includeDescendants) {
-        if (DatastoreType.PASSTHROUGH_OPERATIONAL == DatastoreType.fromDatastoreName(dataStoreType)) {
+        if (DatastoreType.PASSTHROUGH_OPERATIONAL == DatastoreType.fromDatastoreName(dataStoreName)) {
             final ResponseEntity<Map<String, Object>> asyncResponse = populateAsyncResponse(topicParamInQuery);
             final Map<String, Object> asyncResponseData = asyncResponse.getBody();
             Object responseObject = null;
@@ -87,7 +87,8 @@ public class NetworkCmProxyStubController implements NetworkCmProxyApi {
     }
 
     @Override
-    public ResponseEntity<Void> createResourceDataRunningForCmHandle(final String resourceIdentifier,
+    public ResponseEntity<Void> createResourceDataRunningForCmHandle(final String datastoreName,
+                                                                     final String resourceIdentifier,
                                                                      final String cmHandleId,
                                                                      final Object body,
                                                                      final String contentType) {
@@ -95,7 +96,8 @@ public class NetworkCmProxyStubController implements NetworkCmProxyApi {
     }
 
     @Override
-    public ResponseEntity<Void> deleteResourceDataRunningForCmHandle(final String cmHandleId,
+    public ResponseEntity<Void> deleteResourceDataRunningForCmHandle(final String datastoreName,
+                                                                     final String cmHandleId,
                                                                      final String resourceIdentifier,
                                                                      final String contentType) {
         return new ResponseEntity<>(HttpStatus.NO_CONTENT);
@@ -152,7 +154,8 @@ public class NetworkCmProxyStubController implements NetworkCmProxyApi {
     }
 
     @Override
-    public ResponseEntity<Object> patchResourceDataRunningForCmHandle(final String resourceIdentifier,
+    public ResponseEntity<Object> patchResourceDataRunningForCmHandle(final String datastoreName,
+                                                                      final String resourceIdentifier,
                                                                       final String cmHandleId,
                                                                       final Object body,
                                                                       final String contentType) {
@@ -165,7 +168,8 @@ public class NetworkCmProxyStubController implements NetworkCmProxyApi {
     }
 
     @Override
-    public ResponseEntity<Object> updateResourceDataRunningForCmHandle(final String resourceIdentifier,
+    public ResponseEntity<Object> updateResourceDataRunningForCmHandle(final String datastoreName,
+                                                                       final String resourceIdentifier,
                                                                        final String cmHandleId,
                                                                        final Object body,
                                                                        final String contentType) {
index 5e22f77..38db26f 100755 (executable)
@@ -18,7 +18,7 @@
 #  SPDX-License-Identifier: Apache-2.0
 #  ============LICENSE_END=========================================================
 
-getResourceDataForCmHandle:
+resourceDataForCmHandle:
   get:
     tags:
       - network-cm-proxy
@@ -53,7 +53,6 @@ getResourceDataForCmHandle:
       502:
         $ref: 'components.yaml#/components/responses/BadGateway'
 
-resourceDataForPassthroughRunning:
   post:
     tags:
       - network-cm-proxy
@@ -61,6 +60,7 @@ resourceDataForPassthroughRunning:
     description: create resource data from pass-through running for given cm handle
     operationId: createResourceDataRunningForCmHandle
     parameters:
+      - $ref: 'components.yaml#/components/parameters/datastoreName'
       - $ref: 'components.yaml#/components/parameters/cmHandleInPath'
       - $ref: 'components.yaml#/components/parameters/resourceIdentifierInQuery'
       - $ref: 'components.yaml#/components/parameters/contentParamInHeader'
@@ -100,6 +100,7 @@ resourceDataForPassthroughRunning:
     description: Update resource data from pass-through running for the given cm handle
     operationId: updateResourceDataRunningForCmHandle
     parameters:
+      - $ref: 'components.yaml#/components/parameters/datastoreName'
       - $ref: 'components.yaml#/components/parameters/cmHandleInPath'
       - $ref: 'components.yaml#/components/parameters/resourceIdentifierInQuery'
       - $ref: 'components.yaml#/components/parameters/contentParamInHeader'
@@ -139,6 +140,7 @@ resourceDataForPassthroughRunning:
     description: Patch resource data from pass-through running for the given cm handle
     operationId: patchResourceDataRunningForCmHandle
     parameters:
+      - $ref: 'components.yaml#/components/parameters/datastoreName'
       - $ref: 'components.yaml#/components/parameters/cmHandleInPath'
       - $ref: 'components.yaml#/components/parameters/resourceIdentifierInQuery'
       - $ref: 'components.yaml#/components/parameters/contentParamInHeader'
@@ -172,6 +174,7 @@ resourceDataForPassthroughRunning:
     description: Delete resource data from pass-through running for a given cm handle
     operationId: deleteResourceDataRunningForCmHandle
     parameters:
+      - $ref: 'components.yaml#/components/parameters/datastoreName'
       - $ref: 'components.yaml#/components/parameters/cmHandleInPath'
       - $ref: 'components.yaml#/components/parameters/resourceIdentifierInQuery'
       - $ref: 'components.yaml#/components/parameters/contentParamInHeader'
index ed15fcd..4c546be 100755 (executable)
@@ -27,10 +27,7 @@ servers:
   - url: /ncmp
 paths:
   /v1/ch/{cm-handle}/data/ds/{ncmp-datastore-name}:
-    $ref: 'ncmp.yml#/getResourceDataForCmHandle'
-
-  /v1/ch/{cm-handle}/data/ds/ncmp-datastore:passthrough-running:
-    $ref: 'ncmp.yml#/resourceDataForPassthroughRunning'
+    $ref: 'ncmp.yml#/resourceDataForCmHandle'
 
   /v1/ch/{cm-handle}/modules:
     $ref: 'ncmp.yml#/fetchModuleReferencesByCmHandle'
index 9aa8263..2f6668a 100755 (executable)
@@ -41,6 +41,7 @@ import org.onap.cps.ncmp.rest.api.NetworkCmProxyApi;
 import org.onap.cps.ncmp.rest.controller.handlers.DatastoreType;
 import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastoreResourceRequestHandler;
 import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastoreResourceRequestHandlerFactory;
+import org.onap.cps.ncmp.rest.exceptions.InvalidDatastoreException;
 import org.onap.cps.ncmp.rest.mapper.CmHandleStateMapper;
 import org.onap.cps.ncmp.rest.model.CmHandlePublicProperties;
 import org.onap.cps.ncmp.rest.model.CmHandleQueryParameters;
@@ -98,11 +99,26 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
                 optionsParamInQuery, topicParamInQuery, includeDescendants);
     }
 
+    /**
+     * Patch resource data from passthrough-running.
+     *
+     * @param resourceIdentifier resource identifier
+     * @param datastoreName      name of the datastore
+     * @param cmHandle           cm handle identifier
+     * @param requestBody        the request body
+     * @param contentType        content type of body
+     * @return {@code ResponseEntity} response from dmi plugin
+     */
+
     @Override
     public ResponseEntity<Object> patchResourceDataRunningForCmHandle(final String resourceIdentifier,
+                                                                      final String datastoreName,
                                                                       final String cmHandle,
                                                                       final Object requestBody,
                                                                       final String contentType) {
+
+        acceptPassthroughRunningOnly(datastoreName);
+
         final Object responseObject = networkCmProxyDataService
                 .writeResourceDataPassThroughRunningForCmHandle(
                         cmHandle, resourceIdentifier, PATCH,
@@ -114,6 +130,7 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
      * Create resource data in datastore pass-through running for given cm-handle.
      *
      * @param resourceIdentifier resource identifier
+     * @param datastoreName      name of the datastore
      * @param cmHandle           cm handle identifier
      * @param requestBody        the request body
      * @param contentType        content type of body
@@ -121,9 +138,13 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
      */
     @Override
     public ResponseEntity<Void> createResourceDataRunningForCmHandle(final String resourceIdentifier,
+                                                                     final String datastoreName,
                                                                      final String cmHandle,
                                                                      final Object requestBody,
                                                                      final String contentType) {
+
+        acceptPassthroughRunningOnly(datastoreName);
+
         networkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle(cmHandle,
                 resourceIdentifier, CREATE, jsonObjectMapper.asJsonString(requestBody), contentType);
         return new ResponseEntity<>(HttpStatus.CREATED);
@@ -133,34 +154,43 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
      * Update resource data in datastore pass-through running for given cm-handle.
      *
      * @param resourceIdentifier resource identifier
+     * @param datastoreName      name of the datastore
      * @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 datastoreName,
                                                                        final String cmHandle,
                                                                        final Object requestBody,
                                                                        final String contentType) {
+        acceptPassthroughRunningOnly(datastoreName);
+
         networkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle(cmHandle,
                 resourceIdentifier, UPDATE, jsonObjectMapper.asJsonString(requestBody), contentType);
         return new ResponseEntity<>(HttpStatus.OK);
     }
 
-
     /**
      * Delete resource data in datastore pass-through running for a given cm-handle.
      *
-     * @param resourceIdentifier resource identifier
+     * @param datastoreName      name of the datastore
      * @param cmHandle           cm handle identifier
+     * @param resourceIdentifier resource identifier
      * @param contentType        content type of the body
      * @return response entity no content if request is successful
      */
     @Override
-    public ResponseEntity<Void> deleteResourceDataRunningForCmHandle(final String cmHandle,
+    public ResponseEntity<Void> deleteResourceDataRunningForCmHandle(final String datastoreName,
+                                                                     final String cmHandle,
                                                                      final String resourceIdentifier,
                                                                      final String contentType) {
+
+        acceptPassthroughRunningOnly(datastoreName);
+
         networkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle(cmHandle,
                 resourceIdentifier, DELETE, NO_BODY, contentType);
         return new ResponseEntity<>(HttpStatus.NO_CONTENT);
@@ -290,6 +320,7 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
         return new ResponseEntity<>(HttpStatus.OK);
     }
 
+
     private RestOutputCmHandle toRestOutputCmHandle(final NcmpServiceCmHandle ncmpServiceCmHandle) {
         final RestOutputCmHandle restOutputCmHandle = new RestOutputCmHandle();
         final CmHandlePublicProperties cmHandlePublicProperties = new CmHandlePublicProperties();
@@ -301,6 +332,12 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
         return restOutputCmHandle;
     }
 
+    private void acceptPassthroughRunningOnly(final String datastoreName) {
+        final DatastoreType datastoreType = DatastoreType.fromDatastoreName(datastoreName);
 
+        if (DatastoreType.PASSTHROUGH_RUNNING != datastoreType) {
+            throw new InvalidDatastoreException(datastoreName + " is not supported");
+        }
+    }
 }
 
index 959c85d..e8ab997 100644 (file)
@@ -25,6 +25,7 @@ import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
 import lombok.Getter;
+import org.onap.cps.ncmp.rest.exceptions.InvalidDatastoreException;
 
 @Getter
 public enum DatastoreType {
@@ -45,8 +46,21 @@ public enum DatastoreType {
                 type -> datastoreNameToDatastoreType.put(type.getDatastoreName(), type));
     }
 
+    /**
+     * From datastore name get datastore type.
+     *
+     * @param datastoreName the datastore name
+     * @return the datastore type
+     */
     public static DatastoreType fromDatastoreName(final String datastoreName) {
-        return datastoreNameToDatastoreType.get(datastoreName);
+
+        final DatastoreType datastoreType = datastoreNameToDatastoreType.get(datastoreName);
+
+        if (null == datastoreType) {
+            throw new InvalidDatastoreException(datastoreName + " is an invalid datastore name");
+        }
+
+        return datastoreType;
     }
 
 }
diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/InvalidDatastoreException.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/InvalidDatastoreException.java
new file mode 100644 (file)
index 0000000..ff13a93
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2022 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.ncmp.rest.exceptions;
+
+public class InvalidDatastoreException extends RuntimeException {
+    /**
+     * Instantiates a new Invalid datastore exception.
+     *
+     * @param message the message
+     */
+    public InvalidDatastoreException(final String message) {
+        super(message);
+    }
+}
index 98d7f6f..58a60d2 100755 (executable)
@@ -32,6 +32,8 @@ import org.onap.cps.ncmp.rest.controller.NetworkCmProxyInventoryController;
 import org.onap.cps.ncmp.rest.model.DmiErrorMessage;
 import org.onap.cps.ncmp.rest.model.DmiErrorMessageDmiresponse;
 import org.onap.cps.ncmp.rest.model.ErrorMessage;
+import org.onap.cps.spi.exceptions.AlreadyDefinedException;
+import org.onap.cps.spi.exceptions.AlreadyDefinedExceptionBatch;
 import org.onap.cps.spi.exceptions.CpsException;
 import org.onap.cps.spi.exceptions.DataNodeNotFoundException;
 import org.onap.cps.spi.exceptions.DataValidationException;
@@ -59,7 +61,7 @@ public class NetworkCmProxyRestExceptionHandler {
      */
     @ExceptionHandler
     public static ResponseEntity<Object> handleInternalServerErrorExceptions(
-        final Exception exception) {
+            final Exception exception) {
         return buildErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR, exception);
     }
 
@@ -74,12 +76,17 @@ public class NetworkCmProxyRestExceptionHandler {
         return wrapDmiErrorResponse(HttpStatus.BAD_GATEWAY, httpClientRequestException);
     }
 
-    @ExceptionHandler({DmiRequestException.class, DataValidationException.class, HttpMessageNotReadableException.class,
-            InvalidTopicException.class})
+    @ExceptionHandler({DmiRequestException.class, DataValidationException.class,
+            HttpMessageNotReadableException.class, InvalidTopicException.class, InvalidDatastoreException.class})
     public static ResponseEntity<Object> handleDmiRequestExceptions(final Exception exception) {
         return buildErrorResponse(HttpStatus.BAD_REQUEST, exception);
     }
 
+    @ExceptionHandler({AlreadyDefinedException.class, AlreadyDefinedExceptionBatch.class })
+    public static ResponseEntity<Object> handleAlreadyDefinedExceptions(final Exception exception) {
+        return buildErrorResponse(HttpStatus.CONFLICT, exception);
+    }
+
     @ExceptionHandler({DataNodeNotFoundException.class})
     public static ResponseEntity<Object> handleNotFoundExceptions(final CpsException exception) {
         return buildErrorResponse(HttpStatus.NOT_FOUND, exception);
@@ -104,7 +111,8 @@ public class NetworkCmProxyRestExceptionHandler {
         return new ResponseEntity<>(errorMessage, status);
     }
 
-    private static ResponseEntity<Object> wrapDmiErrorResponse(final HttpStatus httpStatus,
+    private static ResponseEntity<Object> wrapDmiErrorResponse(
+            final HttpStatus httpStatus,
             final HttpClientRequestException httpClientRequestException) {
         final var dmiErrorMessage = new DmiErrorMessage();
         final var dmiErrorResponse = new DmiErrorMessageDmiresponse();
index 6e461fa..b6194bc 100644 (file)
@@ -51,6 +51,7 @@ import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
 import org.springframework.http.HttpStatus
 import org.springframework.http.MediaType
 import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder
 import spock.lang.Shared
 import spock.lang.Specification
 
@@ -242,7 +243,6 @@ class NetworkCmProxyControllerSpec extends Specification {
         given: 'resource data url'
             def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" +
                 "?resourceIdentifier=parent/child"
-            def requestBody = '{"some-key":"some-value"}'
         when: 'create resource request is performed'
             def response = mvc.perform(
                 post(url)
@@ -476,38 +476,63 @@ class NetworkCmProxyControllerSpec extends Specification {
             'disabled' | false
     }
 
-    def 'Get Resource Data from operational without descendants.'() {
-        given: 'resource data url'
+    def 'Get Resource Data from operational with or without descendants'() {
+        given: 'resource data url with descendants #enabled'
             def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:operational" +
-                "?resourceIdentifier=parent/child&include-descendants=false"
+                "?resourceIdentifier=parent/child&include-descendants=${enabled}"
         when: 'get data resource request is performed'
             def response = mvc.perform(
                 get(getUrl)
                     .contentType(MediaType.APPLICATION_JSON)
             ).andReturn().response
-        then: 'the NCMP data service is called with getResourceDataOperational'
+        then: 'the NCMP data service is called with getResourceDataOperational with #descendantsOption'
             1 * mockNetworkCmProxyDataService.getResourceDataOperational('testCmHandle',
                 'parent/child',
-                FetchDescendantsOption.OMIT_DESCENDANTS)
+                descendantsOption)
         and: 'response status is Ok'
             response.status == HttpStatus.OK.value()
+        where: 'the following parameters are used'
+            enabled | descendantsOption
+            false   | FetchDescendantsOption.OMIT_DESCENDANTS
+            true    | FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
     }
 
-    def 'Get Resource Data from operational including descendants.'() {
+    def 'Attempt execute #operation rest operation on resource data with #scenario'() {
         given: 'resource data url'
-            def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:operational" +
-                "?resourceIdentifier=parent/child&include-descendants=true"
-        when: 'get data resource request is performed'
+            def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/${datastoreInUrl}?resourceIdentifier=parent/child"
+        when: 'selected request for data resource is performed on url'
             def response = mvc.perform(
-                get(getUrl)
-                    .contentType(MediaType.APPLICATION_JSON)
-            ).andReturn().response
-        then: 'the NCMP data service is called with getResourceDataOperational'
-            1 * mockNetworkCmProxyDataService.getResourceDataOperational('testCmHandle',
-                'parent/child',
-                FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS)
-        and: 'response status is Ok'
-            response.status == HttpStatus.OK.value()
+                executeRestOperation(operation, url))
+                .andReturn().response
+        then: 'the response status is as expected'
+            assert response.status == HttpStatus.BAD_REQUEST.value()
+        and: 'the response is as expected'
+            assert response.getContentAsString().contains(datastoreInUrl)
+        where: 'the following parameters are used'
+            scenario                | operation | datastoreInUrl
+            'unsupported datastore' | 'POST'    | 'ncmp-datastore:operational'
+            'invalid datastore'     | 'POST'    | 'invalid'
+            'unsupported datastore' | 'PUT'     | 'ncmp-datastore:operational'
+            'invalid datastore'     | 'PUT'     | 'invalid'
+            'unsupported datastore' | 'PATCH'   | 'ncmp-datastore:operational'
+            'invalid datastore'     | 'PATCH'   | 'invalid'
+            'unsupported datastore' | 'DELETE'  | 'ncmp-datastore:operational'
+            'invalid datastore'     | 'DELETE'  | 'invalid'
+    }
+
+    def executeRestOperation(operation, url) {
+        if (operation == 'POST') {
+            return post(url).contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)
+        }
+        if (operation == 'PUT') {
+            return put(url).contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)
+        }
+        if (operation == 'PATCH') {
+            return patch(url).contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)
+        }
+        if (operation == 'DELETE') {
+            return delete(url).contentType(MediaType.APPLICATION_JSON_VALUE)
+        }
     }
 
     def dataStores() {
index b8b7fe3..9d1077f 100644 (file)
@@ -33,6 +33,8 @@ import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastoreResourceRequestHa
 import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor
 import org.onap.cps.ncmp.rest.mapper.CmHandleStateMapper
 import org.onap.cps.ncmp.rest.util.DeprecationHelper
+import org.onap.cps.spi.exceptions.AlreadyDefinedException
+import org.onap.cps.spi.exceptions.AlreadyDefinedExceptionBatch
 import org.onap.cps.spi.exceptions.CpsException
 import org.onap.cps.spi.exceptions.DataNodeNotFoundException
 import org.onap.cps.spi.exceptions.DataValidationException
@@ -106,13 +108,15 @@ class NetworkCmProxyRestExceptionHandlerSpec extends Specification {
         then: 'an HTTP response is returned with correct message and details'
             assertTestResponse(response, expectedErrorCode, expectedErrorMessage, expectedErrorDetails)
         where:
-            scenario              | exception                                                        || expectedErrorDetails | expectedErrorMessage | expectedErrorCode
-            'CPS'                 | new CpsException(sampleErrorMessage, sampleErrorDetails)         || sampleErrorDetails   | sampleErrorMessage   | INTERNAL_SERVER_ERROR
-            'NCMP-server'         | new ServerNcmpException(sampleErrorMessage, sampleErrorDetails)  || null                 | sampleErrorMessage   | INTERNAL_SERVER_ERROR
-            'NCMP-client'         | new DmiRequestException(sampleErrorMessage, sampleErrorDetails)  || null                 | sampleErrorMessage   | BAD_REQUEST
-            'DataNode Validation' | new DataNodeNotFoundException('myDataspaceName', 'myAnchorName') || null                 | 'DataNode not found' | NOT_FOUND
-            'other'               | new IllegalStateException(sampleErrorMessage)                    || null                 | sampleErrorMessage   | INTERNAL_SERVER_ERROR
-            'Data Node Not Found' | new DataNodeNotFoundException('myDataspaceName', 'myAnchorName') || 'DataNode not found' | 'DataNode not found' | NOT_FOUND
+            scenario              | exception                                                        || expectedErrorDetails     | expectedErrorMessage        | expectedErrorCode
+            'CPS'                 | new CpsException(sampleErrorMessage, sampleErrorDetails)         || sampleErrorDetails       | sampleErrorMessage          | INTERNAL_SERVER_ERROR
+            'NCMP-server'         | new ServerNcmpException(sampleErrorMessage, sampleErrorDetails)  || null                     | sampleErrorMessage          | INTERNAL_SERVER_ERROR
+            'NCMP-client'         | new DmiRequestException(sampleErrorMessage, sampleErrorDetails)  || null                     | sampleErrorMessage          | BAD_REQUEST
+            'DataNode Validation' | new DataNodeNotFoundException('myDataspaceName', 'myAnchorName') || null                     | 'DataNode not found'        | NOT_FOUND
+            'other'               | new IllegalStateException(sampleErrorMessage)                    || null                     | sampleErrorMessage          | INTERNAL_SERVER_ERROR
+            'Data Node Not Found' | new DataNodeNotFoundException('myDataspaceName', 'myAnchorName') || 'DataNode not found'     | 'DataNode not found'        | NOT_FOUND
+            'Existing entry'      | new AlreadyDefinedException('name',null)                         || 'name already exists'    | 'Already defined exception' | CONFLICT
+            'Existing entries'    | new AlreadyDefinedExceptionBatch(["x[@id='abc']"])               || 'Check logs for details' | null                        | CONFLICT
     }
 
     def 'Post request with exception returns correct HTTP Status.'() {
@@ -156,7 +160,7 @@ class NetworkCmProxyRestExceptionHandlerSpec extends Specification {
         assert response.status == expectedStatus.value()
         def content = new JsonSlurper().parseText(response.contentAsString)
         assert content['status'].toString().contains(expectedStatus.toString())
-        assert content['message'].toString().contains(expectedErrorMessage)
+        assert expectedErrorMessage == null || content['message'].toString().contains(expectedErrorMessage)
         assert expectedErrorDetails == null || content['details'].toString().contains(expectedErrorDetails)
     }
 
index 209ade9..3f440d6 100755 (executable)
@@ -59,7 +59,7 @@ import org.onap.cps.ncmp.api.models.DmiPluginRegistration;
 import org.onap.cps.ncmp.api.models.DmiPluginRegistrationResponse;
 import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle;
 import org.onap.cps.spi.FetchDescendantsOption;
-import org.onap.cps.spi.exceptions.AlreadyDefinedException;
+import org.onap.cps.spi.exceptions.AlreadyDefinedExceptionBatch;
 import org.onap.cps.spi.exceptions.CpsException;
 import org.onap.cps.spi.exceptions.DataNodeNotFoundException;
 import org.onap.cps.spi.exceptions.DataValidationException;
@@ -365,12 +365,12 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
         try {
             lcmEventsCmHandleStateHandler.updateCmHandleStateBatch(cmHandleStatePerCmHandle);
             return CmHandleRegistrationResponse.createSuccessResponses(cmHandleIds);
-        } catch (final AlreadyDefinedException alreadyDefinedException) {
-            return List.of(CmHandleRegistrationResponse.createFailureResponse(
-                    String.join(",", cmHandleIds), RegistrationError.CM_HANDLE_ALREADY_EXIST));
+        } catch (final AlreadyDefinedExceptionBatch alreadyDefinedExceptionBatch) {
+            return CmHandleRegistrationResponse.createFailureResponses(
+                    alreadyDefinedExceptionBatch.getAlreadyDefinedXpaths(),
+                    RegistrationError.CM_HANDLE_ALREADY_EXIST);
         } catch (final Exception exception) {
-            return List.of(CmHandleRegistrationResponse.createFailureResponse(String.join(",", cmHandleIds),
-                    exception));
+            return CmHandleRegistrationResponse.createFailureResponses(cmHandleIds, exception);
         }
     }
 }
index d457f26..d5b459b 100644 (file)
@@ -56,7 +56,7 @@ public class DmiRestClient {
         } catch (final HttpStatusCodeException httpStatusCodeException) {
             final String exceptionMessage = "Unable to " + operation.toString() + " resource data.";
             throw new HttpClientRequestException(exceptionMessage, httpStatusCodeException.getResponseBodyAsString(),
-                httpStatusCodeException.getRawStatusCode());
+                    httpStatusCodeException.getRawStatusCode());
         }
     }
 
index 45e2754..f842ddb 100644 (file)
@@ -30,6 +30,7 @@ import java.util.List;
 import java.util.Map;
 import lombok.AllArgsConstructor;
 import lombok.Data;
+import lombok.EqualsAndHashCode;
 import lombok.Getter;
 import lombok.NoArgsConstructor;
 import lombok.Setter;
@@ -46,8 +47,10 @@ import org.onap.cps.utils.CpsValidator;
 @Setter
 @NoArgsConstructor
 @JsonInclude(Include.NON_NULL)
+@EqualsAndHashCode(onlyExplicitlyIncluded = true)
 public class YangModelCmHandle {
 
+    @EqualsAndHashCode.Include
     private String id;
 
     @JsonProperty("dmi-service-name")
index 9174dc7..bfc3a9a 100644 (file)
@@ -1,7 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2022 Nordix Foundation
- *  Modifications Copyright (C) 2022 Bell Canada
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
 
 package org.onap.cps.ncmp.api.inventory;
 
-import static org.onap.cps.ncmp.api.impl.constants.DmiRegistryConstants.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME;
-import static org.onap.cps.ncmp.api.impl.constants.DmiRegistryConstants.NO_TIMESTAMP;
-import static org.onap.cps.spi.CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED;
-import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS;
-
-import java.time.OffsetDateTime;
-import java.util.ArrayList;
 import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.onap.cps.api.CpsDataService;
-import org.onap.cps.api.CpsModuleService;
-import org.onap.cps.ncmp.api.impl.utils.YangDataConverter;
 import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
-import org.onap.cps.spi.CpsAdminPersistenceService;
-import org.onap.cps.spi.CpsDataPersistenceService;
-import org.onap.cps.spi.FetchDescendantsOption;
-import org.onap.cps.spi.exceptions.SchemaSetNotFoundException;
 import org.onap.cps.spi.model.Anchor;
 import org.onap.cps.spi.model.DataNode;
 import org.onap.cps.spi.model.ModuleDefinition;
 import org.onap.cps.spi.model.ModuleReference;
-import org.onap.cps.utils.CpsValidator;
-import org.onap.cps.utils.JsonObjectMapper;
-import org.springframework.stereotype.Component;
-
-@Slf4j
-@RequiredArgsConstructor
-@Component
-public class InventoryPersistence {
-
-    private static final String NCMP_DATASPACE_NAME = "NCMP-Admin";
-
-    private static final String NCMP_DMI_REGISTRY_ANCHOR = "ncmp-dmi-registry";
-
-    private static final String NCMP_DMI_REGISTRY_PARENT = "/dmi-registry";
-
-    private static final String CM_HANDLE_XPATH_TEMPLATE = "/dmi-registry/cm-handles[@id='" + "%s" + "']";
 
-    private final JsonObjectMapper jsonObjectMapper;
-
-    private final CpsDataService cpsDataService;
-
-    private final CpsModuleService cpsModuleService;
-
-    private final CpsDataPersistenceService cpsDataPersistenceService;
-
-    private final CpsAdminPersistenceService cpsAdminPersistenceService;
+public interface InventoryPersistence {
 
     /**
      * Get the Cm Handle Composite State from the data node.
@@ -79,50 +36,30 @@ public class InventoryPersistence {
      * @param cmHandleId cm handle id
      * @return the cm handle composite state
      */
-    public CompositeState getCmHandleState(final String cmHandleId) {
-        final DataNode stateAsDataNode = cpsDataService.getDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
-            String.format(CM_HANDLE_XPATH_TEMPLATE, cmHandleId) + "/state",
-            FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS);
-        return new CompositeStateBuilder().fromDataNode(stateAsDataNode).build();
-    }
+    CompositeState getCmHandleState(String cmHandleId);
 
     /**
      * Save the cm handles state.
      *
-     * @param cmHandleId    cm handle id
+     * @param cmHandleId     cm handle id
      * @param compositeState composite state
      */
-    public void saveCmHandleState(final String cmHandleId, final CompositeState compositeState) {
-        final String cmHandleJsonData = String.format("{\"state\":%s}",
-            jsonObjectMapper.asJsonString(compositeState));
-        cpsDataService.updateDataNodeAndDescendants(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
-            String.format(CM_HANDLE_XPATH_TEMPLATE, cmHandleId),
-            cmHandleJsonData, OffsetDateTime.now());
-    }
+    void saveCmHandleState(String cmHandleId, CompositeState compositeState);
 
     /**
      * Save all cm handles states in batch.
      *
      * @param cmHandleStatePerCmHandleId contains cm handle id and updated state
      */
-    public void saveCmHandleStateBatch(final Map<String, CompositeState> cmHandleStatePerCmHandleId) {
-        final Map<String, String> cmHandlesJsonDataMap = new HashMap<>();
-        cmHandleStatePerCmHandleId.forEach((cmHandleId, compositeState) -> cmHandlesJsonDataMap.put(
-                String.format(CM_HANDLE_XPATH_TEMPLATE, cmHandleId),
-                String.format("{\"state\":%s}", jsonObjectMapper.asJsonString(compositeState))));
-        cpsDataService.updateDataNodesAndDescendants(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
-                cmHandlesJsonDataMap, OffsetDateTime.now());
-    }
+    void saveCmHandleStateBatch(Map<String, CompositeState> cmHandleStatePerCmHandleId);
 
     /**
      * This method retrieves DMI service name, DMI properties and the state for a given cm handle.
+     *
      * @param cmHandleId the id of the cm handle
      * @return yang model cm handle
      */
-    public YangModelCmHandle getYangModelCmHandle(final String cmHandleId) {
-        CpsValidator.validateNameCharacters(cmHandleId);
-        return YangDataConverter.convertCmHandleToYangModel(getCmHandleDataNode(cmHandleId), cmHandleId);
-    }
+    YangModelCmHandle getYangModelCmHandle(String cmHandleId);
 
     /**
      * Method to return module definitions by cmHandleId.
@@ -130,9 +67,7 @@ public class InventoryPersistence {
      * @param cmHandleId cm handle ID
      * @return a collection of module definitions (moduleName, revision and yang resource content)
      */
-    public Collection<ModuleDefinition> getModuleDefinitionsByCmHandleId(final String cmHandleId) {
-        return cpsModuleService.getModuleDefinitionsByAnchorName(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, cmHandleId);
-    }
+    Collection<ModuleDefinition> getModuleDefinitionsByCmHandleId(String cmHandleId);
 
     /**
      * Method to return module references by cmHandleId.
@@ -140,60 +75,35 @@ public class InventoryPersistence {
      * @param cmHandleId cm handle ID
      * @return a collection of module references (moduleName and revision)
      */
-    public Collection<ModuleReference> getYangResourcesModuleReferences(final String cmHandleId) {
-        CpsValidator.validateNameCharacters(cmHandleId);
-        return cpsModuleService.getYangResourcesModuleReferences(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, cmHandleId);
-    }
+    Collection<ModuleReference> getYangResourcesModuleReferences(String cmHandleId);
 
     /**
      * Method to save cmHandle.
      *
      * @param yangModelCmHandle cmHandle represented as Yang Model
      */
-    public void saveCmHandle(final YangModelCmHandle yangModelCmHandle) {
-        final String cmHandleJsonData =
-                String.format("{\"cm-handles\":[%s]}", jsonObjectMapper.asJsonString(yangModelCmHandle));
-        cpsDataService.saveListElements(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, NCMP_DMI_REGISTRY_PARENT,
-                cmHandleJsonData, NO_TIMESTAMP);
-    }
+    void saveCmHandle(YangModelCmHandle yangModelCmHandle);
 
     /**
      * Method to save batch of cm handles.
      *
      * @param yangModelCmHandles cm handle represented as Yang Models
      */
-    public void saveCmHandleBatch(final Collection<YangModelCmHandle> yangModelCmHandles) {
-        final List<String> cmHandlesJsonData = new ArrayList<>();
-        yangModelCmHandles.forEach(yangModelCmHandle -> cmHandlesJsonData.add(
-                String.format("{\"cm-handles\":[%s]}", jsonObjectMapper.asJsonString(yangModelCmHandle))));
-        cpsDataService.saveListElementsBatch(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
-                NCMP_DMI_REGISTRY_PARENT, cmHandlesJsonData, NO_TIMESTAMP);
-    }
+    void saveCmHandleBatch(Collection<YangModelCmHandle> yangModelCmHandles);
 
     /**
      * Method to delete a list or a list element.
      *
      * @param listElementXpath list element xPath
      */
-    public void deleteListOrListElement(final String listElementXpath) {
-        cpsDataService.deleteListOrListElement(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
-                listElementXpath, NO_TIMESTAMP);
-    }
+    void deleteListOrListElement(String listElementXpath);
 
     /**
      * Method to delete a schema set.
      *
      * @param schemaSetName schema set name
      */
-    public void deleteSchemaSetWithCascade(final String schemaSetName) {
-        try {
-            CpsValidator.validateNameCharacters(schemaSetName);
-            cpsModuleService.deleteSchemaSet(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, schemaSetName,
-                    CASCADE_DELETE_ALLOWED);
-        } catch (final SchemaSetNotFoundException schemaSetNotFoundException) {
-            log.warn("Schema set {} does not exist or already deleted", schemaSetName);
-        }
-    }
+    void deleteSchemaSetWithCascade(String schemaSetName);
 
     /**
      * Get data node via xpath.
@@ -201,10 +111,7 @@ public class InventoryPersistence {
      * @param xpath xpath
      * @return data node
      */
-    public DataNode getDataNode(final String xpath) {
-        return cpsDataPersistenceService.getDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
-                xpath, INCLUDE_ALL_DESCENDANTS);
-    }
+    DataNode getDataNode(String xpath);
 
     /**
      * Get data node of given cm handle.
@@ -212,9 +119,7 @@ public class InventoryPersistence {
      * @param cmHandleId cmHandle ID
      * @return data node
      */
-    public DataNode getCmHandleDataNode(final String cmHandleId) {
-        return this.getDataNode(String.format(CM_HANDLE_XPATH_TEMPLATE, cmHandleId));
-    }
+    DataNode getCmHandleDataNode(String cmHandleId);
 
     /**
      * Query anchors via module names.
@@ -222,37 +127,27 @@ public class InventoryPersistence {
      * @param moduleNamesForQuery module names
      * @return Collection of anchors
      */
-    public Collection<Anchor> queryAnchors(final Collection<String> moduleNamesForQuery) {
-        return  cpsAdminPersistenceService.queryAnchors(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, moduleNamesForQuery);
-    }
+    Collection<Anchor> queryAnchors(Collection<String> moduleNamesForQuery);
 
     /**
      * Method to get all anchors.
      *
      * @return Collection of anchors
      */
-    public Collection<Anchor> getAnchors() {
-        return cpsAdminPersistenceService.getAnchors(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME);
-    }
+    Collection<Anchor> getAnchors();
 
     /**
      * Replaces list content by removing all existing elements and inserting the given new elements as data nodes.
      *
-     * @param parentNodeXpath   parent node xpath
-     * @param dataNodes         datanodes representing the updated data
+     * @param parentNodeXpath parent node xpath
+     * @param dataNodes       datanodes representing the updated data
      */
-    public void replaceListContent(final String parentNodeXpath, final Collection<DataNode> dataNodes) {
-        cpsDataService.replaceListContent(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
-                parentNodeXpath, dataNodes, NO_TIMESTAMP);
-    }
+    void replaceListContent(String parentNodeXpath, Collection<DataNode> dataNodes);
 
     /**
      * Deletes data node for given anchor and dataspace.
      *
      * @param dataNodeXpath data node xpath
      */
-    public void deleteDataNode(final String dataNodeXpath) {
-        cpsDataService.deleteDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, dataNodeXpath,
-                NO_TIMESTAMP);
-    }
-}
\ No newline at end of file
+    void deleteDataNode(String dataNodeXpath);
+}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistenceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistenceImpl.java
new file mode 100644 (file)
index 0000000..99edfdb
--- /dev/null
@@ -0,0 +1,186 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2022 Nordix Foundation
+ *  Modifications Copyright (C) 2022 Bell Canada
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.ncmp.api.inventory;
+
+import static org.onap.cps.ncmp.api.impl.constants.DmiRegistryConstants.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME;
+import static org.onap.cps.ncmp.api.impl.constants.DmiRegistryConstants.NO_TIMESTAMP;
+import static org.onap.cps.spi.CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED;
+import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS;
+
+import java.time.OffsetDateTime;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.api.CpsDataService;
+import org.onap.cps.api.CpsModuleService;
+import org.onap.cps.ncmp.api.impl.utils.YangDataConverter;
+import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
+import org.onap.cps.spi.CpsAdminPersistenceService;
+import org.onap.cps.spi.CpsDataPersistenceService;
+import org.onap.cps.spi.FetchDescendantsOption;
+import org.onap.cps.spi.exceptions.SchemaSetNotFoundException;
+import org.onap.cps.spi.model.Anchor;
+import org.onap.cps.spi.model.DataNode;
+import org.onap.cps.spi.model.ModuleDefinition;
+import org.onap.cps.spi.model.ModuleReference;
+import org.onap.cps.utils.CpsValidator;
+import org.onap.cps.utils.JsonObjectMapper;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@RequiredArgsConstructor
+@Component
+public class InventoryPersistenceImpl implements InventoryPersistence {
+
+    private static final String NCMP_DATASPACE_NAME = "NCMP-Admin";
+
+    private static final String NCMP_DMI_REGISTRY_ANCHOR = "ncmp-dmi-registry";
+
+    private static final String NCMP_DMI_REGISTRY_PARENT = "/dmi-registry";
+
+    private static final String CM_HANDLE_XPATH_TEMPLATE = "/dmi-registry/cm-handles[@id='" + "%s" + "']";
+
+    private final JsonObjectMapper jsonObjectMapper;
+
+    private final CpsDataService cpsDataService;
+
+    private final CpsModuleService cpsModuleService;
+
+    private final CpsDataPersistenceService cpsDataPersistenceService;
+
+    private final CpsAdminPersistenceService cpsAdminPersistenceService;
+
+    @Override
+    public CompositeState getCmHandleState(final String cmHandleId) {
+        final DataNode stateAsDataNode = cpsDataService.getDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
+            String.format(CM_HANDLE_XPATH_TEMPLATE, cmHandleId) + "/state",
+            FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS);
+        return new CompositeStateBuilder().fromDataNode(stateAsDataNode).build();
+    }
+
+    @Override
+    public void saveCmHandleState(final String cmHandleId, final CompositeState compositeState) {
+        final String cmHandleJsonData = String.format("{\"state\":%s}",
+            jsonObjectMapper.asJsonString(compositeState));
+        cpsDataService.updateDataNodeAndDescendants(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
+            String.format(CM_HANDLE_XPATH_TEMPLATE, cmHandleId),
+            cmHandleJsonData, OffsetDateTime.now());
+    }
+
+    @Override
+    public void saveCmHandleStateBatch(final Map<String, CompositeState> cmHandleStatePerCmHandleId) {
+        final Map<String, String> cmHandlesJsonDataMap = new HashMap<>();
+        cmHandleStatePerCmHandleId.forEach((cmHandleId, compositeState) -> cmHandlesJsonDataMap.put(
+                String.format(CM_HANDLE_XPATH_TEMPLATE, cmHandleId),
+                String.format("{\"state\":%s}", jsonObjectMapper.asJsonString(compositeState))));
+        cpsDataService.updateDataNodesAndDescendants(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
+                cmHandlesJsonDataMap, OffsetDateTime.now());
+    }
+
+    @Override
+    public YangModelCmHandle getYangModelCmHandle(final String cmHandleId) {
+        CpsValidator.validateNameCharacters(cmHandleId);
+        return YangDataConverter.convertCmHandleToYangModel(getCmHandleDataNode(cmHandleId), cmHandleId);
+    }
+
+    @Override
+    public Collection<ModuleDefinition> getModuleDefinitionsByCmHandleId(final String cmHandleId) {
+        return cpsModuleService.getModuleDefinitionsByAnchorName(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, cmHandleId);
+    }
+
+    @Override
+    public Collection<ModuleReference> getYangResourcesModuleReferences(final String cmHandleId) {
+        CpsValidator.validateNameCharacters(cmHandleId);
+        return cpsModuleService.getYangResourcesModuleReferences(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, cmHandleId);
+    }
+
+    @Override
+    public void saveCmHandle(final YangModelCmHandle yangModelCmHandle) {
+        final String cmHandleJsonData =
+                String.format("{\"cm-handles\":[%s]}", jsonObjectMapper.asJsonString(yangModelCmHandle));
+        cpsDataService.saveListElements(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, NCMP_DMI_REGISTRY_PARENT,
+                cmHandleJsonData, NO_TIMESTAMP);
+    }
+
+    @Override
+    public void saveCmHandleBatch(final Collection<YangModelCmHandle> yangModelCmHandles) {
+        final List<String> cmHandlesJsonData = new ArrayList<>();
+        yangModelCmHandles.forEach(yangModelCmHandle -> cmHandlesJsonData.add(
+                String.format("{\"cm-handles\":[%s]}", jsonObjectMapper.asJsonString(yangModelCmHandle))));
+        cpsDataService.saveListElementsBatch(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
+                NCMP_DMI_REGISTRY_PARENT, cmHandlesJsonData, NO_TIMESTAMP);
+    }
+
+    @Override
+    public void deleteListOrListElement(final String listElementXpath) {
+        cpsDataService.deleteListOrListElement(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
+                listElementXpath, NO_TIMESTAMP);
+    }
+
+    @Override
+    public void deleteSchemaSetWithCascade(final String schemaSetName) {
+        try {
+            CpsValidator.validateNameCharacters(schemaSetName);
+            cpsModuleService.deleteSchemaSet(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, schemaSetName,
+                    CASCADE_DELETE_ALLOWED);
+        } catch (final SchemaSetNotFoundException schemaSetNotFoundException) {
+            log.warn("Schema set {} does not exist or already deleted", schemaSetName);
+        }
+    }
+
+    @Override
+    public DataNode getDataNode(final String xpath) {
+        return cpsDataPersistenceService.getDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
+                xpath, INCLUDE_ALL_DESCENDANTS);
+    }
+
+    @Override
+    public DataNode getCmHandleDataNode(final String cmHandleId) {
+        return this.getDataNode(String.format(CM_HANDLE_XPATH_TEMPLATE, cmHandleId));
+    }
+
+    @Override
+    public Collection<Anchor> queryAnchors(final Collection<String> moduleNamesForQuery) {
+        return  cpsAdminPersistenceService.queryAnchors(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, moduleNamesForQuery);
+    }
+
+    @Override
+    public Collection<Anchor> getAnchors() {
+        return cpsAdminPersistenceService.getAnchors(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME);
+    }
+
+    @Override
+    public void replaceListContent(final String parentNodeXpath, final Collection<DataNode> dataNodes) {
+        cpsDataService.replaceListContent(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
+                parentNodeXpath, dataNodes, NO_TIMESTAMP);
+    }
+
+    @Override
+    public void deleteDataNode(final String dataNodeXpath) {
+        cpsDataService.deleteDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, dataNodeXpath,
+                NO_TIMESTAMP);
+    }
+}
\ No newline at end of file
index ada3dc6..f914547 100644 (file)
@@ -71,6 +71,7 @@ public class ModuleSyncTasks {
                     moduleSyncService.syncAndCreateSchemaSetAndAnchor(yangModelCmHandle);
                     cmHandelStatePerCmHandle.put(yangModelCmHandle, CmHandleState.READY);
                 } catch (final Exception e) {
+                    log.warn("Processing module sync batch failed.");
                     syncUtils.updateLockReasonDetailsAndAttempts(compositeState,
                             LockReasonCategory.LOCKED_MODULE_SYNC_FAILED, e.getMessage());
                     setCmHandleStateLocked(yangModelCmHandle, compositeState.getLockReason());
@@ -81,6 +82,7 @@ public class ModuleSyncTasks {
             lcmEventsCmHandleStateHandler.updateCmHandleStateBatch(cmHandelStatePerCmHandle);
         } finally {
             batchCounter.getAndDecrement();
+            log.info("Processing module sync batch finished. {} batch(es) active.", batchCounter.get());
         }
         return COMPLETED_FUTURE;
     }
index 73954c3..64d111f 100644 (file)
@@ -62,16 +62,22 @@ public class ModuleSyncWatchdog {
      */
     @Scheduled(fixedDelayString = "${timers.advised-modules-sync.sleep-time-ms:5000}")
     public void moduleSyncAdvisedCmHandles() {
+        log.info("Processing module sync watchdog waking up.");
         populateWorkQueueIfNeeded();
         final int asyncTaskParallelismLevel = asyncTaskExecutor.getAsyncTaskParallelismLevel();
-        while (!moduleSyncWorkQueue.isEmpty() && batchCounter.get() <= asyncTaskParallelismLevel) {
-            batchCounter.getAndIncrement();
-            final Collection<DataNode> nextBatch = prepareNextBatch();
-            asyncTaskExecutor.executeTask(() ->
-                            moduleSyncTasks.performModuleSync(nextBatch, batchCounter),
-                    ASYNC_TASK_TIMEOUT_IN_MILLISECONDS
-            );
-            preventBusyWait();
+        while (!moduleSyncWorkQueue.isEmpty()) {
+            if (batchCounter.get() <= asyncTaskParallelismLevel) {
+                final Collection<DataNode> nextBatch = prepareNextBatch();
+                log.debug("Processing module sync batch of {}. {} batch(es) active.",
+                        nextBatch.size(), batchCounter.get());
+                asyncTaskExecutor.executeTask(() ->
+                                moduleSyncTasks.performModuleSync(nextBatch, batchCounter),
+                        ASYNC_TASK_TIMEOUT_IN_MILLISECONDS
+                );
+                batchCounter.getAndIncrement();
+            } else {
+                preventBusyWait();
+            }
         }
     }
 
@@ -80,6 +86,7 @@ public class ModuleSyncWatchdog {
      */
     @Scheduled(fixedDelayString = "${timers.locked-modules-sync.sleep-time-ms:300000}")
     public void resetPreviouslyFailedCmHandles() {
+        log.info("Processing module sync retry-watchdog waking up.");
         final List<YangModelCmHandle> failedCmHandles = syncUtils.getModuleSyncFailedCmHandles();
         moduleSyncTasks.resetFailedCmHandles(failedCmHandles);
     }
@@ -95,6 +102,7 @@ public class ModuleSyncWatchdog {
     private void populateWorkQueueIfNeeded() {
         if (moduleSyncWorkQueue.isEmpty()) {
             final List<DataNode> advisedCmHandles = syncUtils.getAdvisedCmHandles();
+            log.info("Processing module sync fetched {} advised cm handles from DB", advisedCmHandles.size());
             for (final DataNode advisedCmHandle : advisedCmHandles) {
                 if (!moduleSyncWorkQueue.offer(advisedCmHandle)) {
                     log.warn("Unable to add cm handle {} to the work queue", advisedCmHandle.getLeaves().get("id"));
index b7faf09..d5b27b6 100644 (file)
 
 package org.onap.cps.ncmp.api.models;
 
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 import lombok.Builder;
 import lombok.Data;
 import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 
 @Data
 @Builder
+@Slf4j
 public class CmHandleRegistrationResponse {
 
     private final String cmHandle;
@@ -36,16 +42,19 @@ public class CmHandleRegistrationResponse {
     private RegistrationError registrationError;
     private String errorText;
 
+    private static final Pattern cmHandleIdInXpathPattern = Pattern.compile("\\[@id='(.*?)']");
+
     /**
      * Creates a failure response based on exception.
      *
-     * @param cmHandle  cmHandle
+     * @param cmHandleId  cmHandleId
      * @param exception exception
      * @return CmHandleRegistrationResponse
      */
-    public static CmHandleRegistrationResponse createFailureResponse(final String cmHandle, final Exception exception) {
+    public static CmHandleRegistrationResponse createFailureResponse(final String cmHandleId,
+                                                                     final Exception exception) {
         return CmHandleRegistrationResponse.builder()
-            .cmHandle(cmHandle)
+            .cmHandle(cmHandleId)
             .status(Status.FAILURE)
             .registrationError(RegistrationError.UNKNOWN_ERROR)
             .errorText(exception.getMessage()).build();
@@ -54,19 +63,55 @@ public class CmHandleRegistrationResponse {
     /**
      * Creates a failure response based on registration error.
      *
-     * @param cmHandle          cmHandle
+     * @param cmHandleId          cmHandleId
      * @param registrationError registrationError
      * @return CmHandleRegistrationResponse
      */
-    public static CmHandleRegistrationResponse createFailureResponse(final String cmHandle,
+    public static CmHandleRegistrationResponse createFailureResponse(final String cmHandleId,
         final RegistrationError registrationError) {
-        return CmHandleRegistrationResponse.builder().cmHandle(cmHandle)
+        return CmHandleRegistrationResponse.builder().cmHandle(cmHandleId)
             .status(Status.FAILURE)
             .registrationError(registrationError)
             .errorText(registrationError.errorText)
             .build();
     }
 
+    /**
+     * Creates a failure response based on registration error.
+     *
+     * @param failedXpaths       list of failed Xpaths
+     * @param registrationError enum describing the type of registration error
+     * @return CmHandleRegistrationResponse
+     */
+    public static List<CmHandleRegistrationResponse> createFailureResponses(final Collection<String> failedXpaths,
+            final RegistrationError registrationError) {
+        final List<CmHandleRegistrationResponse> cmHandleRegistrationResponses = new ArrayList<>(failedXpaths.size());
+        for (final String xpath : failedXpaths) {
+            final Matcher matcher = cmHandleIdInXpathPattern.matcher(xpath);
+            if (matcher.find()) {
+                cmHandleRegistrationResponses.add(
+                    CmHandleRegistrationResponse.createFailureResponse(matcher.group(1), registrationError));
+            } else {
+                log.warn("Unexpected xpath {}", xpath);
+            }
+        }
+        return cmHandleRegistrationResponses;
+    }
+
+    /**
+     * Creates a failure response based on other exception.
+     *
+     * @param cmHandleIds list of failed cmHandleIds
+     * @param exception   exception caught during the registration
+     * @return CmHandleRegistrationResponse
+     */
+    public static List<CmHandleRegistrationResponse> createFailureResponses(final Collection<String> cmHandleIds,
+            final Exception exception) {
+        return cmHandleIds.stream()
+                .map(cmHandleId -> CmHandleRegistrationResponse.createFailureResponse(cmHandleId, exception))
+                .collect(Collectors.toList());
+    }
+
     public static CmHandleRegistrationResponse createSuccessResponse(final String cmHandle) {
         return CmHandleRegistrationResponse.builder().cmHandle(cmHandle)
             .status(Status.SUCCESS).build();
@@ -92,4 +137,4 @@ public class CmHandleRegistrationResponse {
         public final String errorText;
 
     }
-}
\ No newline at end of file
+}
index 86a32a1..0b58d44 100644 (file)
@@ -28,14 +28,13 @@ import org.onap.cps.ncmp.api.NetworkCmProxyCmHandlerQueryService
 import org.onap.cps.ncmp.api.impl.event.lcm.LcmEventsCmHandleStateHandler
 import org.onap.cps.ncmp.api.impl.exception.DmiRequestException
 import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations
-import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
 import org.onap.cps.ncmp.api.inventory.CmHandleQueries
 import org.onap.cps.ncmp.api.inventory.CmHandleState
 import org.onap.cps.ncmp.api.inventory.InventoryPersistence
 import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse
 import org.onap.cps.ncmp.api.models.DmiPluginRegistration
 import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
-import org.onap.cps.spi.exceptions.AlreadyDefinedException
+import org.onap.cps.spi.exceptions.AlreadyDefinedExceptionBatch
 import org.onap.cps.spi.exceptions.DataNodeNotFoundException
 import org.onap.cps.spi.exceptions.DataValidationException
 import org.onap.cps.spi.exceptions.SchemaSetNotFoundException
@@ -185,20 +184,18 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
                                        new NcmpServiceCmHandle(cmHandleId: 'cmhandle2'),
                                        new NcmpServiceCmHandle(cmHandleId: 'cmhandle3')])
         and: 'cm-handle creation is successful for 1st and 3rd; failed for 2nd'
-            mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(*_) >> { throw new RuntimeException("Failed") }
+            def xpath = "somePathWithId[@id='cmhandle2']"
+            mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(*_) >> { throw new AlreadyDefinedExceptionBatch([xpath]) }
         when: 'registration is updated to create cm-handles'
             def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
         then: 'a response is received for all cm-handles'
             response.getCreatedCmHandles().size() == 1
         and: 'all cm-handles creation fails'
-            with(response.getCreatedCmHandles().get(0)) {
+            response.getCreatedCmHandles().each {
+                assert it.cmHandle == 'cmhandle2'
                 assert it.status == Status.FAILURE
-                assert it.registrationError == UNKNOWN_ERROR
-                assert it.errorText == 'Failed'
-                def sortedCmHandles = it.cmHandle.split(',').sort()
-                assert sortedCmHandles[0] == 'cmhandle1'
-                assert sortedCmHandles[1] == 'cmhandle2'
-                assert sortedCmHandles[2] == 'cmhandle3'
+                assert it.registrationError == CM_HANDLE_ALREADY_EXIST
+                assert it.errorText == 'cm-handle already exists'
             }
     }
 
@@ -219,10 +216,10 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
                 assert it.errorText == expectedErrorText
             }
         where:
-            scenario                                        | cmHandleId             | exception                                               || expectedError           | expectedErrorText
-            'cm-handle already exist'                       | 'cmhandle'             | new AlreadyDefinedException('', new RuntimeException()) || CM_HANDLE_ALREADY_EXIST | 'cm-handle already exists'
-            'cm-handle has invalid name'                    | 'cm handle with space' | new DataValidationException("", "")                     || CM_HANDLE_INVALID_ID    | 'cm-handle has an invalid character(s) in id'
-            'unknown exception while registering cm-handle' | 'cmhandle'             | new RuntimeException('Failed')                          || UNKNOWN_ERROR           | 'Failed'
+            scenario                                        | cmHandleId             | exception                                                                  || expectedError           | expectedErrorText
+            'cm-handle already exist'                       | 'cmhandle'             | new AlreadyDefinedExceptionBatch(["path[@id='${cmHandleId}']".toString()]) || CM_HANDLE_ALREADY_EXIST | 'cm-handle already exists'
+            'cm-handle has invalid name'                    | 'cm handle with space' | new DataValidationException("", "")                                        || CM_HANDLE_INVALID_ID    | 'cm-handle has an invalid character(s) in id'
+            'unknown exception while registering cm-handle' | 'cmhandle'             | new RuntimeException('Failed')                                             || UNKNOWN_ERROR           | 'Failed'
     }
 
     def 'Update CM-Handle: Update Operation Response is added to the response'() {
@@ -44,7 +44,7 @@ import java.time.format.DateTimeFormatter
 import static org.onap.cps.ncmp.api.impl.constants.DmiRegistryConstants.NO_TIMESTAMP
 import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
 
-class InventoryPersistenceSpec extends Specification {
+class InventoryPersistenceImplSpec extends Specification {
 
     def spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper()))
 
@@ -56,7 +56,7 @@ class InventoryPersistenceSpec extends Specification {
 
     def mockCpsAdminPersistenceService = Mock(CpsAdminPersistenceService)
 
-    def objectUnderTest = new InventoryPersistence(spiedJsonObjectMapper, mockCpsDataService, mockCpsModuleService,
+    def objectUnderTest = new InventoryPersistenceImpl(spiedJsonObjectMapper, mockCpsDataService, mockCpsModuleService,
             mockCpsDataPersistenceService, mockCpsAdminPersistenceService)
 
     def formattedDateAndTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ")
index e5240c0..dd989bf 100644 (file)
@@ -52,20 +52,19 @@ class ModuleSyncWatchdogSpec extends Specification {
     def 'Module sync advised cm handles with #scenario.'() {
         given: 'sync utilities returns #numberOfAdvisedCmHandles advised cm handles'
             mockSyncUtils.getAdvisedCmHandles() >> createDataNodes(numberOfAdvisedCmHandles)
-        and: 'the executor has #parallelismLevel available threads'
-            spiedAsyncTaskExecutor.getAsyncTaskParallelismLevel() >> parallelismLevel
+        and: 'the executor has enough available threads'
+            spiedAsyncTaskExecutor.getAsyncTaskParallelismLevel() >> 3
         when: ' module sync is started'
             objectUnderTest.moduleSyncAdvisedCmHandles()
         then: 'it performs #expectedNumberOfTaskExecutions tasks'
             expectedNumberOfTaskExecutions * spiedAsyncTaskExecutor.executeTask(*_)
         where: ' the following parameter are used'
-            scenario              | parallelismLevel | numberOfAdvisedCmHandles                                          || expectedNumberOfTaskExecutions
-            'less then 1 batch'   | 9                | 1                                                                 || 1
-            'exactly 1 batch'     | 9                | ModuleSyncWatchdog.MODULE_SYNC_BATCH_SIZE                         || 1
-            '2 batches'           | 9                | 2 * ModuleSyncWatchdog.MODULE_SYNC_BATCH_SIZE                     || 2
-            'queue capacity'      | 9                | testQueueCapacity                                                 || 3
-            'over queue capacity' | 9                | testQueueCapacity + 2 * ModuleSyncWatchdog.MODULE_SYNC_BATCH_SIZE || 3
-            'not enough threads'  | 2                | testQueueCapacity                                                 || 2
+            scenario              | numberOfAdvisedCmHandles                                          || expectedNumberOfTaskExecutions
+            'less then 1 batch'   | 1                                                                 || 1
+            'exactly 1 batch'     | ModuleSyncWatchdog.MODULE_SYNC_BATCH_SIZE                         || 1
+            '2 batches'           | 2 * ModuleSyncWatchdog.MODULE_SYNC_BATCH_SIZE                     || 2
+            'queue capacity'      | testQueueCapacity                                                 || 3
+            'over queue capacity' | testQueueCapacity + 2 * ModuleSyncWatchdog.MODULE_SYNC_BATCH_SIZE || 3
     }
 
     def 'Reset failed cm handles.'() {
index 4476998..dba2934 100644 (file)
@@ -24,6 +24,8 @@ import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationErr
 import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.Status
 import spock.lang.Specification
 
+import java.util.stream.Collectors
+
 class CmHandleRegistrationResponseSpec extends Specification {
 
     def 'Successful cm-handle Registration Response'() {
@@ -68,4 +70,25 @@ class CmHandleRegistrationResponseSpec extends Specification {
             'cm-handle id is invalid'  | 'cm handle' | RegistrationError.CM_HANDLE_INVALID_ID
     }
 
+    def 'Failed cm-handle Registration with multiple responses.'() {
+        when: 'cm-handle failure response is created for 2 xpaths'
+            def cmHandleRegistrationResponses =
+                CmHandleRegistrationResponse.createFailureResponses(["somePathWithId[@id='123']","somePathWithId[@id='456']"], RegistrationError.CM_HANDLE_ALREADY_EXIST)
+        then: 'the response has the correct cm handle ids'
+            assert cmHandleRegistrationResponses.size() == 2
+            assert cmHandleRegistrationResponses.stream().map(it -> it.cmHandle).collect(Collectors.toList())
+                .containsAll(['123','456'])
+    }
+
+    def 'Failed cm-handle Registration with multiple responses with an unexpected xpath.'() {
+        when: 'cm-handle failure response is created for one valid and one unexpected xpath'
+            def cmHandleRegistrationResponses =
+                CmHandleRegistrationResponse.createFailureResponses(["somePathWithId[@id='123']","valid/xpath/without-id[@key='123']"], RegistrationError.CM_HANDLE_ALREADY_EXIST)
+        then: 'the response has only one entry'
+            assert cmHandleRegistrationResponses.size() == 1
+    }
+
+
+
+
 }
index 9495b3d..93233d9 100755 (executable)
@@ -31,6 +31,7 @@ import org.onap.cps.rest.controller.DataRestController;
 import org.onap.cps.rest.controller.QueryRestController;
 import org.onap.cps.rest.model.ErrorMessage;
 import org.onap.cps.spi.exceptions.AlreadyDefinedException;
+import org.onap.cps.spi.exceptions.AlreadyDefinedExceptionBatch;
 import org.onap.cps.spi.exceptions.CpsAdminException;
 import org.onap.cps.spi.exceptions.CpsException;
 import org.onap.cps.spi.exceptions.CpsPathException;
@@ -75,7 +76,7 @@ public class CpsRestExceptionHandler {
             ? HttpStatus.NOT_FOUND : HttpStatus.BAD_REQUEST, exception);
     }
 
-    @ExceptionHandler({DataInUseException.class, AlreadyDefinedException.class})
+    @ExceptionHandler({DataInUseException.class, AlreadyDefinedException.class, AlreadyDefinedExceptionBatch.class})
     public static ResponseEntity<Object> handleDataInUseException(final Exception exception) {
         return buildErrorResponse(HttpStatus.CONFLICT, exception);
     }
index 61e1d5b..c13422d 100644 (file)
@@ -52,6 +52,7 @@ import org.onap.cps.spi.entities.FragmentEntity;
 import org.onap.cps.spi.entities.SchemaSetEntity;
 import org.onap.cps.spi.entities.YangResourceEntity;
 import org.onap.cps.spi.exceptions.AlreadyDefinedException;
+import org.onap.cps.spi.exceptions.AlreadyDefinedExceptionBatch;
 import org.onap.cps.spi.exceptions.ConcurrencyException;
 import org.onap.cps.spi.exceptions.CpsAdminException;
 import org.onap.cps.spi.exceptions.CpsPathException;
@@ -88,48 +89,82 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
             Pattern.compile("\\[(\\@([^\\/]{0,9999}))\\]$");
 
     @Override
-    @Transactional
     public void addChildDataNode(final String dataspaceName, final String anchorName, final String parentNodeXpath,
                                  final DataNode newChildDataNode) {
-        addChildDataNodes(dataspaceName, anchorName, parentNodeXpath, Collections.singleton(newChildDataNode));
+        addNewChildDataNode(dataspaceName, anchorName, parentNodeXpath, newChildDataNode);
     }
 
     @Override
-    @Transactional
     public void addListElements(final String dataspaceName, final String anchorName, final String parentNodeXpath,
                                 final Collection<DataNode> newListElements) {
-        addChildDataNodes(dataspaceName, anchorName, parentNodeXpath, newListElements);
+        addChildrenDataNodes(dataspaceName, anchorName, parentNodeXpath, newListElements);
     }
 
     @Override
-    @Transactional
-    public void addListElementsBatch(final String dataspaceName, final String anchorName, final String parentNodeXpath,
-            final Collection<Collection<DataNode>> newListsElements) {
+    public void addMultipleLists(final String dataspaceName, final String anchorName, final String parentNodeXpath,
+            final Collection<Collection<DataNode>> newLists) {
+        final Collection<String> failedXpaths = new HashSet<>();
+        newLists.forEach(newList -> {
+            try {
+                addChildrenDataNodes(dataspaceName, anchorName, parentNodeXpath, newList);
+            } catch (final AlreadyDefinedExceptionBatch e) {
+                failedXpaths.addAll(e.getAlreadyDefinedXpaths());
+            }
+        });
 
-        newListsElements.forEach(
-                newListElement -> addListElements(dataspaceName, anchorName, parentNodeXpath, newListElement));
+        if (!failedXpaths.isEmpty()) {
+            throw new AlreadyDefinedExceptionBatch(failedXpaths);
+        }
 
     }
 
-    private void addChildDataNodes(final String dataspaceName, final String anchorName, final String parentNodeXpath,
-                                   final Collection<DataNode> newChildren) {
+    private void addNewChildDataNode(final String dataspaceName, final String anchorName,
+            final String parentNodeXpath, final DataNode newChild) {
         final FragmentEntity parentFragmentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath);
+        final FragmentEntity newChildAsFragmentEntity =
+                convertToFragmentWithAllDescendants(parentFragmentEntity.getDataspace(),
+                        parentFragmentEntity.getAnchor(), newChild);
+        newChildAsFragmentEntity.setParentId(parentFragmentEntity.getId());
+        try {
+            fragmentRepository.save(newChildAsFragmentEntity);
+        } catch (final DataIntegrityViolationException e) {
+            throw AlreadyDefinedException.forDataNode(newChild.getXpath(), anchorName, e);
+        }
+
+    }
+
+    private void addChildrenDataNodes(final String dataspaceName, final String anchorName, final String parentNodeXpath,
+            final Collection<DataNode> newChildren) {
+        final FragmentEntity parentFragmentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath);
+        final List<FragmentEntity> fragmentEntities = new ArrayList<>(newChildren.size());
         try {
-            final List<FragmentEntity> fragmentEntities = new ArrayList<>();
             newChildren.forEach(newChildAsDataNode -> {
-                final FragmentEntity newChildAsFragmentEntity = convertToFragmentWithAllDescendants(
-                    parentFragmentEntity.getDataspace(),
-                    parentFragmentEntity.getAnchor(),
-                    newChildAsDataNode);
+                final FragmentEntity newChildAsFragmentEntity =
+                        convertToFragmentWithAllDescendants(parentFragmentEntity.getDataspace(),
+                                parentFragmentEntity.getAnchor(), newChildAsDataNode);
                 newChildAsFragmentEntity.setParentId(parentFragmentEntity.getId());
                 fragmentEntities.add(newChildAsFragmentEntity);
             });
             fragmentRepository.saveAll(fragmentEntities);
-        } catch (final DataIntegrityViolationException exception) {
-            final List<String> conflictXpaths = newChildren.stream()
-                    .map(DataNode::getXpath)
-                    .collect(Collectors.toList());
-            throw AlreadyDefinedException.forDataNodes(conflictXpaths, anchorName, exception);
+        } catch (final DataIntegrityViolationException e) {
+            log.warn("Exception occurred : {} , While saving : {} children, retrying using individual save operations",
+                    e, fragmentEntities.size());
+            retrySavingEachChildIndividually(dataspaceName, anchorName, parentNodeXpath, newChildren);
+        }
+    }
+
+    private void retrySavingEachChildIndividually(final String dataspaceName, final String anchorName,
+            final String parentNodeXpath, final Collection<DataNode> newChildren) {
+        final Collection<String> failedXpaths = new HashSet<>();
+        for (final DataNode newChild : newChildren) {
+            try {
+                addNewChildDataNode(dataspaceName, anchorName, parentNodeXpath, newChild);
+            } catch (final AlreadyDefinedException e) {
+                failedXpaths.add(newChild.getXpath());
+            }
+        }
+        if (!failedXpaths.isEmpty()) {
+            throw new AlreadyDefinedExceptionBatch(failedXpaths);
         }
     }
 
@@ -199,8 +234,8 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
             } catch (final PathParsingException e) {
                 throw new CpsPathException(e.getMessage());
             }
-            return fragmentRepository.getByDataspaceAndAnchorAndXpath(dataspaceEntity, anchorEntity,
-                    normalizedXpath);
+
+            return fragmentRepository.getByDataspaceAndAnchorAndXpath(dataspaceEntity, anchorEntity, normalizedXpath);
         }
     }
 
@@ -310,8 +345,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
         } catch (final StaleStateException staleStateException) {
             throw new ConcurrencyException("Concurrent Transactions",
                     String.format("dataspace :'%s', Anchor : '%s' and xpath: '%s' is updated by another transaction.",
-                            dataspaceName, anchorName, dataNode.getXpath()),
-                    staleStateException);
+                            dataspaceName, anchorName, dataNode.getXpath()));
         }
     }
 
@@ -319,6 +353,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
     public void updateDataNodesAndDescendants(final String dataspaceName,
                                               final String anchorName,
                                               final List<DataNode> dataNodes) {
+
         final Map<DataNode, FragmentEntity> dataNodeFragmentEntityMap = dataNodes.stream()
             .collect(Collectors.toMap(
                 dataNode -> dataNode, dataNode -> getFragmentByXpath(dataspaceName, anchorName, dataNode.getXpath())));
@@ -327,10 +362,27 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
         try {
             fragmentRepository.saveAll(dataNodeFragmentEntityMap.values());
         } catch (final StaleStateException staleStateException) {
-            throw new ConcurrencyException("Concurrent Transactions",
-                String.format("A data node in dataspace :'%s' with Anchor : '%s' is updated by another transaction.",
-                    dataspaceName, anchorName),
-                staleStateException);
+            retryUpdateDataNodesIndividually(dataspaceName, anchorName, dataNodeFragmentEntityMap.values());
+        }
+    }
+
+    private void retryUpdateDataNodesIndividually(final String dataspaceName, final String anchorName,
+            final Collection<FragmentEntity> fragmentEntities) {
+        final Collection<String> failedXpaths = new HashSet<>();
+
+        fragmentEntities.forEach(dataNodeFragment -> {
+            try {
+                fragmentRepository.save(dataNodeFragment);
+            } catch (final StaleStateException e) {
+                failedXpaths.add(dataNodeFragment.getXpath());
+            }
+        });
+
+        if (!failedXpaths.isEmpty()) {
+            final String failedXpathsConcatenated = String.join(",", failedXpaths);
+            throw new ConcurrencyException("Concurrent Transactions", String.format(
+                    "DataNodes : %s in Dataspace :'%s' with Anchor : '%s'  are updated by another transaction.",
+                    failedXpathsConcatenated, dataspaceName, anchorName));
         }
     }
 
index 4489cdd..654c1c0 100644 (file)
@@ -20,7 +20,6 @@
 
 package org.onap.cps.spi.repository;
 
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -29,12 +28,14 @@ import javax.persistence.PersistenceContext;
 import javax.persistence.Query;
 import javax.transaction.Transactional;
 import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.onap.cps.cpspath.parser.CpsPathPrefixType;
 import org.onap.cps.cpspath.parser.CpsPathQuery;
 import org.onap.cps.spi.entities.FragmentEntity;
 import org.onap.cps.utils.JsonObjectMapper;
 
 @RequiredArgsConstructor
+@Slf4j
 public class FragmentRepositoryCpsPathQueryImpl implements FragmentRepositoryCpsPathQuery {
 
     public static final String REGEX_ABSOLUTE_PATH_PREFIX = ".*\\/";
@@ -62,16 +63,8 @@ public class FragmentRepositoryCpsPathQueryImpl implements FragmentRepositoryCps
         addTextFunctionCondition(cpsPathQuery, sqlStringBuilder, queryParameters);
         final Query query = entityManager.createNativeQuery(sqlStringBuilder.toString(), FragmentEntity.class);
         setQueryParameters(query, queryParameters);
-        return getFragmentEntitiesAsStream(query);
-    }
-
-    private List<FragmentEntity> getFragmentEntitiesAsStream(final Query query) {
-        final List<FragmentEntity> fragmentEntities = new ArrayList<>();
-        query.getResultStream().forEach(fragmentEntity -> {
-            fragmentEntities.add((FragmentEntity) fragmentEntity);
-            entityManager.detach(fragmentEntity);
-        });
-
+        final List<FragmentEntity> fragmentEntities = query.getResultList();
+        log.debug("Fetched {} fragment entities by anchor and cps path.", fragmentEntities.size());
         return fragmentEntities;
     }
 
index acc243b..5e15ca7 100755 (executable)
@@ -27,6 +27,7 @@ import org.onap.cps.cpspath.parser.PathParsingException
 import org.onap.cps.spi.CpsDataPersistenceService
 import org.onap.cps.spi.entities.FragmentEntity
 import org.onap.cps.spi.exceptions.AlreadyDefinedException
+import org.onap.cps.spi.exceptions.AlreadyDefinedExceptionBatch
 import org.onap.cps.spi.exceptions.AnchorNotFoundException
 import org.onap.cps.spi.exceptions.CpsAdminException
 import org.onap.cps.spi.exceptions.CpsPathException
@@ -48,6 +49,7 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
     CpsDataPersistenceService objectUnderTest
 
     static final JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
+    static final DataNodeBuilder dataNodeBuilder = new DataNodeBuilder()
 
     static final String SET_DATA = '/data/fragment.sql'
     static final int DATASPACE_1001_ID = 1001L
@@ -165,7 +167,7 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
             def grandChild = buildDataNode('/parent-201/child-204[@key="NEW1"]/grand-child-204[@key2="NEW1-CHILD"]', [leave:'value'], [])
             listElements[0].childDataNodes = [grandChild]
         when: 'the new data node (list elements) are added to an existing parent node'
-            objectUnderTest.addListElementsBatch(DATASPACE_NAME, ANCHOR_NAME3, '/parent-201', [listElements])
+            objectUnderTest.addMultipleLists(DATASPACE_NAME, ANCHOR_NAME3, '/parent-201', [listElements])
         then: 'new entries are successfully persisted, parent node now contains 5 children (2 new + 3 existing before)'
             def parentFragment = fragmentRepository.getById(LIST_DATA_NODE_PARENT201_FRAGMENT_ID)
             def allChildXpaths = parentFragment.childFragments.collect { it.xpath }
@@ -178,18 +180,42 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
             assert grandChildFragmentEntity.isPresent()
     }
 
+    @Sql([CLEAR_DATA, SET_DATA])
+    def 'Add multiple list with a mix of existing and new elements'() {
+        given: 'two new child list elements for an existing parent'
+            def existingDataNode = dataNodeBuilder.withXpath('/parent-100/child-001').withLeaves(['id': '001']).build()
+            def newDataNode1 = dataNodeBuilder.withXpath('/parent-100/child-new1').withLeaves(['id': 'new1']).build()
+            def newDataNode2 = dataNodeBuilder.withXpath('/parent-200/child-new2').withLeaves(['id': 'new2']).build()
+            def dataNodeList1 = [existingDataNode, newDataNode1]
+            def dataNodeList2 = [newDataNode2]
+        when: 'duplicate data node is requested to be added'
+            objectUnderTest.addMultipleLists(DATASPACE_NAME, ANCHOR_NAME3, '/', [dataNodeList1,dataNodeList2])
+        then: 'already defined batch exception is thrown'
+            def thrown = thrown(AlreadyDefinedExceptionBatch)
+        and: 'it only contains the xpath(s) of the duplicated elements'
+            assert thrown.alreadyDefinedXpaths.size() == 1
+            assert thrown.alreadyDefinedXpaths.contains('/parent-100/child-001')
+        and: 'it does NOT contains the xpaths of the new element that were not combined with existing elements'
+            assert !thrown.alreadyDefinedXpaths.contains('/parent-100/child-new1')
+            assert !thrown.alreadyDefinedXpaths.contains('/parent-100/child-new1')
+        and: 'the new entity is inserted correctly'
+            def dataspaceEntity = dataspaceRepository.getByName(DATASPACE_NAME)
+            def anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, ANCHOR_NAME3)
+            fragmentRepository.findByDataspaceAndAnchorAndXpath(dataspaceEntity, anchorEntity, '/parent-200/child-new2').isPresent()
+    }
+
     @Sql([CLEAR_DATA, SET_DATA])
     def 'Add list element error scenario: #scenario.'() {
         given: 'list element as a collection of data nodes'
-            def listElementCollection = toDataNodes(listElementXpaths)
+            def listElements = toDataNodes(listElementXpaths)
         when: 'attempt to add list elements to parent node'
-            objectUnderTest.addListElements(DATASPACE_NAME, ANCHOR_NAME3, parentNodeXpath, listElementCollection)
+            objectUnderTest.addListElements(DATASPACE_NAME, ANCHOR_NAME3, parentNodeXpath, listElements)
         then: 'a #expectedException is thrown'
             thrown(expectedException)
         where: 'following parameters were used'
             scenario                        | parentNodeXpath | listElementXpaths                   || expectedException
             'parent node does not exist'    | '/unknown'      | ['irrelevant']                      || DataNodeNotFoundException
-            'data fragment already exists'  | '/parent-201'   | ["/parent-201/child-204[@key='A']"] || AlreadyDefinedException
+            'data fragment already exists'  | '/parent-201'   | ["/parent-201/child-204[@key='A']"] || AlreadyDefinedExceptionBatch
     }
 
     @Sql([CLEAR_DATA, SET_DATA])
@@ -559,8 +585,9 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
         return xpaths.collect { new DataNodeBuilder().withXpath(it).build() }
     }
 
+
     static DataNode buildDataNode(xpath, leaves, childDataNodes) {
-        return new DataNodeBuilder().withXpath(xpath).withLeaves(leaves).withChildDataNodes(childDataNodes).build()
+        return dataNodeBuilder.withXpath(xpath).withLeaves(leaves).withChildDataNodes(childDataNodes).build()
     }
 
     static Map<String, Object> getLeavesMap(FragmentEntity fragmentEntity) {
index 1bbf358..470b03a 100644 (file)
@@ -86,17 +86,25 @@ class CpsDataPersistenceServiceSpec extends Specification {
             assert concurrencyException.getDetails().contains('/some/xpath')
     }
 
-    def 'Handling of StaleStateException (caused by concurrent updates) during  update data nodes and descendants.'() {
-        given: 'the fragment repository returns a list of fragment entities'
-            mockFragmentRepository.getByDataspaceAndAnchorAndXpath(*_) >> new FragmentEntity()
-        and: 'a data node is concurrently updated by another transaction'
+    def 'Handling of StaleStateException (caused by concurrent updates) during update data nodes and descendants.'() {
+        given: 'the system contains and can update one datanode'
+            def dataNode1 = mockDataNodeAndFragmentEntity('/node1', 'OK')
+        and: 'the system contains two more datanodes that throw an exception while updating'
+            def dataNode2 = mockDataNodeAndFragmentEntity('/node2', 'EXCEPTION')
+            def dataNode3 = mockDataNodeAndFragmentEntity('/node3', 'EXCEPTION')
+        and: 'the batch update will therefore also fail'
             mockFragmentRepository.saveAll(*_) >> { throw new StaleStateException("concurrent updates") }
-        when: 'attempt to update data node with submitted data nodes'
-            objectUnderTest.updateDataNodesAndDescendants('some-dataspace', 'some-anchor', [])
+        when: 'attempt batch update data nodes'
+            objectUnderTest.updateDataNodesAndDescendants('some-dataspace', 'some-anchor', [dataNode1, dataNode2, dataNode3])
         then: 'concurrency exception is thrown'
-            def concurrencyException = thrown(ConcurrencyException)
-            assert concurrencyException.getDetails().contains('some-dataspace')
-            assert concurrencyException.getDetails().contains('some-anchor')
+            def thrown = thrown(ConcurrencyException)
+            assert thrown.message == 'Concurrent Transactions'
+        and: 'it does not contain the successfull datanode'
+            assert !thrown.details.contains('/node1')
+        and: 'it contains the failed datanodes'
+            assert thrown.details.contains('/node2')
+            assert thrown.details.contains('/node3')
+
     }
 
     def 'Retrieving a data node with a property JSON value of #scenario'() {
@@ -193,4 +201,14 @@ class CpsDataPersistenceServiceSpec extends Specification {
                 assert fragmentEntities.size() == 2
             }})
     }
+
+    def mockDataNodeAndFragmentEntity(xpath, scenario) {
+        def dataNode = new DataNodeBuilder().withXpath(xpath).build()
+        def fragmentEntity = new FragmentEntity(xpath: xpath, childFragments: [])
+        mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, xpath) >> fragmentEntity
+        if ('EXCEPTION' == scenario) {
+            mockFragmentRepository.save(fragmentEntity) >> { throw new StaleStateException("concurrent updates") }
+        }
+        return dataNode
+    }
 }
\ No newline at end of file
index 6bf4935..b6aa04b 100755 (executable)
@@ -97,7 +97,7 @@ public class CpsDataServiceImpl implements CpsDataService {
         CpsValidator.validateNameCharacters(dataspaceName, anchorName);
         final Collection<Collection<DataNode>> listElementDataNodeCollections =
                 buildDataNodes(dataspaceName, anchorName, parentNodeXpath, jsonDataList);
-        cpsDataPersistenceService.addListElementsBatch(dataspaceName, anchorName, parentNodeXpath,
+        cpsDataPersistenceService.addMultipleLists(dataspaceName, anchorName, parentNodeXpath,
                 listElementDataNodeCollections);
         processDataUpdatedEventAsync(dataspaceName, anchorName, parentNodeXpath, UPDATE, observedTimestamp);
     }
index 8b45ae7..cd0cefc 100644 (file)
@@ -66,15 +66,15 @@ public interface CpsDataPersistenceService {
         Collection<DataNode> listElementsCollection);
 
     /**
-     * Adds list child elements to a Fragment.
+     * Add multiple lists of data nodes to a parent node at the same time.
      *
      * @param dataspaceName           dataspace name
      * @param anchorName              anchor name
      * @param parentNodeXpath         parent node xpath
-     * @param listElementsCollections collections of data nodes representing list elements
+     * @param newLists collections of lists of data nodes representing list elements
      */
-    void addListElementsBatch(String dataspaceName, String anchorName, String parentNodeXpath,
-            Collection<Collection<DataNode>> listElementsCollections);
+    void addMultipleLists(String dataspaceName, String anchorName, String parentNodeXpath,
+            Collection<Collection<DataNode>> newLists);
 
     /**
      * Retrieves datanode by XPath for given dataspace and anchor.
diff --git a/cps-service/src/main/java/org/onap/cps/spi/exceptions/AlreadyDefinedExceptionBatch.java b/cps-service/src/main/java/org/onap/cps/spi/exceptions/AlreadyDefinedExceptionBatch.java
new file mode 100644 (file)
index 0000000..0ba656a
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2022 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.spi.exceptions;
+
+import java.util.Collection;
+import lombok.Getter;
+
+public class AlreadyDefinedExceptionBatch extends RuntimeException {
+
+    @Getter
+    private final Collection<String> alreadyDefinedXpaths;
+
+    public AlreadyDefinedExceptionBatch(final Collection<String> alreadyDefinedXPaths) {
+        this.alreadyDefinedXpaths = alreadyDefinedXPaths;
+    }
+}
index 3a8a94b..b5eae93 100644 (file)
@@ -20,8 +20,8 @@ package org.onap.cps.spi.exceptions;
 
 public class ConcurrencyException extends CpsException {
 
-    public ConcurrencyException(final String message, final String details, final Throwable cause) {
-        super(message, details, cause);
+    public ConcurrencyException(final String message, final String details) {
+        super(message, details);
     }
 
 }
index ab960df..3f28f0a 100644 (file)
@@ -143,7 +143,7 @@ class CpsDataServiceImplSpec extends Specification {
             def jsonData = '{"branch": [{"name": "A"}, {"name": "B"}]}'
             objectUnderTest.saveListElementsBatch(dataspaceName, anchorName, '/test-tree', [jsonData], observedTimestamp)
         then: 'the persistence service method is invoked with correct parameters'
-            1 * mockCpsDataPersistenceService.addListElementsBatch(dataspaceName, anchorName, '/test-tree',_) >> {
+            1 * mockCpsDataPersistenceService.addMultipleLists(dataspaceName, anchorName, '/test-tree',_) >> {
                 args -> {
                     def listElementsCollection = args[3] as Collection<Collection<DataNode>>
                     assert listElementsCollection.size() == 1
index 953c82b..874f2a0 100644 (file)
@@ -15,28 +15,28 @@ info:
   x-logo:
     url: cps_logo.png
 servers:
-  - url: /cps/api
+- url: /cps/api
 tags:
-  - name: cps-admin
-    description: cps Admin
-  - name: cps-data
-    description: cps Data
+- name: cps-admin
+  description: cps Admin
+- name: cps-data
+  description: cps Data
 paths:
   /v1/dataspaces:
     post:
       tags:
-        - cps-admin
+      - cps-admin
       summary: Create a dataspace
       description: Create a new dataspace
       operationId: createDataspace
       parameters:
-        - name: dataspace-name
-          in: query
-          description: dataspace-name
-          required: true
-          schema:
-            type: string
-            example: my-dataspace
+      - name: dataspace-name
+        in: query
+        description: dataspace-name
+        required: true
+        schema:
+          type: string
+          example: my-dataspace
       responses:
         "201":
           description: Created
@@ -97,18 +97,18 @@ paths:
                 details: Internal Server Error occurred
     delete:
       tags:
-        - cps-admin
+      - cps-admin
       summary: Delete a dataspace
       description: Delete a dataspace
       operationId: deleteDataspace
       parameters:
-        - name: dataspace-name
-          in: query
-          description: dataspace-name
-          required: true
-          schema:
-            type: string
-            example: my-dataspace
+      - name: dataspace-name
+        in: query
+        description: dataspace-name
+        required: true
+        schema:
+          type: string
+          example: my-dataspace
       responses:
         "204":
           description: No Content
@@ -166,18 +166,18 @@ paths:
   /v1/dataspaces/{dataspace-name}/anchors:
     get:
       tags:
-        - cps-admin
+      - cps-admin
       summary: Get anchors
       description: "Read all anchors, given a dataspace"
       operationId: getAnchors
       parameters:
-        - name: dataspace-name
-          in: path
-          description: dataspace-name
-          required: true
-          schema:
-            type: string
-            example: my-dataspace
+      - name: dataspace-name
+        in: path
+        description: dataspace-name
+        required: true
+        schema:
+          type: string
+          example: my-dataspace
       responses:
         "200":
           description: OK
@@ -229,32 +229,32 @@ paths:
                 details: Internal Server Error occurred
     post:
       tags:
-        - cps-admin
+      - cps-admin
       summary: Create an anchor
       description: Create a new anchor in the given dataspace
       operationId: createAnchor
       parameters:
-        - name: dataspace-name
-          in: path
-          description: dataspace-name
-          required: true
-          schema:
-            type: string
-            example: my-dataspace
-        - name: schema-set-name
-          in: query
-          description: schema-set-name
-          required: true
-          schema:
-            type: string
-            example: my-schema-set
-        - name: anchor-name
-          in: query
-          description: anchor-name
-          required: true
-          schema:
-            type: string
-            example: my-anchor
+      - name: dataspace-name
+        in: path
+        description: dataspace-name
+        required: true
+        schema:
+          type: string
+          example: my-dataspace
+      - name: schema-set-name
+        in: query
+        description: schema-set-name
+        required: true
+        schema:
+          type: string
+          example: my-schema-set
+      - name: anchor-name
+        in: query
+        description: anchor-name
+        required: true
+        schema:
+          type: string
+          example: my-anchor
       responses:
         "201":
           description: Created
@@ -316,25 +316,25 @@ paths:
   /v1/dataspaces/{dataspace-name}/anchors/{anchor-name}:
     get:
       tags:
-        - cps-admin
+      - cps-admin
       summary: Get an anchor
       description: Read an anchor given an anchor name and a dataspace
       operationId: getAnchor
       parameters:
-        - name: dataspace-name
-          in: path
-          description: dataspace-name
-          required: true
-          schema:
-            type: string
-            example: my-dataspace
-        - name: anchor-name
-          in: path
-          description: anchor-name
-          required: true
-          schema:
-            type: string
-            example: my-anchor
+      - name: dataspace-name
+        in: path
+        description: dataspace-name
+        required: true
+        schema:
+          type: string
+          example: my-dataspace
+      - name: anchor-name
+        in: path
+        description: anchor-name
+        required: true
+        schema:
+          type: string
+          example: my-anchor
       responses:
         "200":
           description: OK
@@ -384,25 +384,25 @@ paths:
                 details: Internal Server Error occurred
     delete:
       tags:
-        - cps-admin
+      - cps-admin
       summary: Delete an anchor
       description: Delete an anchor given an anchor name and a dataspace
       operationId: deleteAnchor
       parameters:
-        - name: dataspace-name
-          in: path
-          description: dataspace-name
-          required: true
-          schema:
-            type: string
-            example: my-dataspace
-        - name: anchor-name
-          in: path
-          description: anchor-name
-          required: true
-          schema:
-            type: string
-            example: my-anchor
+      - name: dataspace-name
+        in: path
+        description: dataspace-name
+        required: true
+        schema:
+          type: string
+          example: my-dataspace
+      - name: anchor-name
+        in: path
+        description: anchor-name
+        required: true
+        schema:
+          type: string
+          example: my-anchor
       responses:
         "204":
           description: No Content
@@ -450,25 +450,25 @@ paths:
   /v1/dataspaces/{dataspace-name}/schema-sets:
     post:
       tags:
-        - cps-admin
+      - cps-admin
       summary: Create a schema set
       description: Create a new schema set in the given dataspace
       operationId: createSchemaSet
       parameters:
-        - name: dataspace-name
-          in: path
-          description: dataspace-name
-          required: true
-          schema:
-            type: string
-            example: my-dataspace
-        - name: schema-set-name
-          in: query
-          description: schema-set-name
-          required: true
-          schema:
-            type: string
-            example: my-schema-set
+      - name: dataspace-name
+        in: path
+        description: dataspace-name
+        required: true
+        schema:
+          type: string
+          example: my-dataspace
+      - name: schema-set-name
+        in: query
+        description: schema-set-name
+        required: true
+        schema:
+          type: string
+          example: my-schema-set
       requestBody:
         content:
           multipart/form-data:
@@ -536,25 +536,25 @@ paths:
   /v1/dataspaces/{dataspace-name}/schema-sets/{schema-set-name}:
     get:
       tags:
-        - cps-admin
+      - cps-admin
       summary: Get a schema set
       description: Read a schema set given a schema set name and a dataspace
       operationId: getSchemaSet
       parameters:
-        - name: dataspace-name
-          in: path
-          description: dataspace-name
-          required: true
-          schema:
-            type: string
-            example: my-dataspace
-        - name: schema-set-name
-          in: path
-          description: schema-set-name
-          required: true
-          schema:
-            type: string
-            example: my-schema-set
+      - name: dataspace-name
+        in: path
+        description: dataspace-name
+        required: true
+        schema:
+          type: string
+          example: my-dataspace
+      - name: schema-set-name
+        in: path
+        description: schema-set-name
+        required: true
+        schema:
+          type: string
+          example: my-schema-set
       responses:
         "200":
           description: OK
@@ -604,25 +604,25 @@ paths:
                 details: Internal Server Error occurred
     delete:
       tags:
-        - cps-admin
+      - cps-admin
       summary: Delete a schema set
       description: Delete a schema set given a schema set name and a dataspace
       operationId: deleteSchemaSet
       parameters:
-        - name: dataspace-name
-          in: path
-          description: dataspace-name
-          required: true
-          schema:
-            type: string
-            example: my-dataspace
-        - name: schema-set-name
-          in: path
-          description: schema-set-name
-          required: true
-          schema:
-            type: string
-            example: my-schema-set
+      - name: dataspace-name
+        in: path
+        description: dataspace-name
+        required: true
+        schema:
+          type: string
+          example: my-dataspace
+      - name: schema-set-name
+        in: path
+        description: schema-set-name
+        required: true
+        schema:
+          type: string
+          example: my-schema-set
       responses:
         "204":
           description: No Content
@@ -680,46 +680,46 @@ paths:
   /v1/dataspaces/{dataspace-name}/anchors/{anchor-name}/node:
     get:
       tags:
-        - cps-data
+      - cps-data
       summary: Get a node
       description: Get a node with an option to retrieve all the children for a given
         anchor and dataspace
       operationId: getNodeByDataspaceAndAnchor
       parameters:
-        - name: dataspace-name
-          in: path
-          description: dataspace-name
-          required: true
-          schema:
-            type: string
-            example: my-dataspace
-        - name: anchor-name
-          in: path
-          description: anchor-name
-          required: true
-          schema:
-            type: string
-            example: my-anchor
-        - name: xpath
-          in: query
-          description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html"
-          required: false
-          schema:
-            type: string
-            default: /
-          examples:
-            container xpath:
-              value: /shops/bookstore
-            list attributes xpath:
-              value: "/shops/bookstore/categories[@code=1]"
-        - name: include-descendants
-          in: query
-          description: include-descendants
-          required: false
-          schema:
-            type: boolean
-            example: false
-            default: false
+      - name: dataspace-name
+        in: path
+        description: dataspace-name
+        required: true
+        schema:
+          type: string
+          example: my-dataspace
+      - name: anchor-name
+        in: path
+        description: anchor-name
+        required: true
+        schema:
+          type: string
+          example: my-anchor
+      - name: xpath
+        in: query
+        description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html"
+        required: false
+        schema:
+          type: string
+          default: /
+        examples:
+          container xpath:
+            value: /shops/bookstore
+          list attributes xpath:
+            value: "/shops/bookstore/categories[@code=1]"
+      - name: include-descendants
+        in: query
+        description: include-descendants
+        required: false
+        schema:
+          type: boolean
+          example: false
+          default: false
       responses:
         "200":
           description: OK
@@ -774,45 +774,45 @@ paths:
   /v1/dataspaces/{dataspace-name}/anchors/{anchor-name}/nodes:
     put:
       tags:
-        - cps-data
+      - cps-data
       summary: Replace a node with descendants
       description: "Replace a node with descendants for a given dataspace, anchor\
         \ and a parent node xpath"
       operationId: replaceNode
       parameters:
-        - name: dataspace-name
-          in: path
-          description: dataspace-name
-          required: true
-          schema:
-            type: string
-            example: my-dataspace
-        - name: anchor-name
-          in: path
-          description: anchor-name
-          required: true
-          schema:
-            type: string
-            example: my-anchor
-        - name: xpath
-          in: query
-          description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html"
-          required: false
-          schema:
-            type: string
-            default: /
-          examples:
-            container xpath:
-              value: /shops/bookstore
-            list attributes xpath:
-              value: "/shops/bookstore/categories[@code=1]"
-        - name: observed-timestamp
-          in: query
-          description: observed-timestamp
-          required: false
-          schema:
-            type: string
-            example: 2021-03-21T00:10:34.030-0100
+      - name: dataspace-name
+        in: path
+        description: dataspace-name
+        required: true
+        schema:
+          type: string
+          example: my-dataspace
+      - name: anchor-name
+        in: path
+        description: anchor-name
+        required: true
+        schema:
+          type: string
+          example: my-anchor
+      - name: xpath
+        in: query
+        description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html"
+        required: false
+        schema:
+          type: string
+          default: /
+        examples:
+          container xpath:
+            value: /shops/bookstore
+          list attributes xpath:
+            value: "/shops/bookstore/categories[@code=1]"
+      - name: observed-timestamp
+        in: query
+        description: observed-timestamp
+        required: false
+        schema:
+          type: string
+          example: 2021-03-21T00:10:34.030-0100
       requestBody:
         content:
           application/json:
@@ -874,44 +874,44 @@ paths:
                 details: Internal Server Error occurred
     post:
       tags:
-        - cps-data
+      - cps-data
       summary: Create a node
       description: Create a node for a given anchor and dataspace
       operationId: createNode
       parameters:
-        - name: dataspace-name
-          in: path
-          description: dataspace-name
-          required: true
-          schema:
-            type: string
-            example: my-dataspace
-        - name: anchor-name
-          in: path
-          description: anchor-name
-          required: true
-          schema:
-            type: string
-            example: my-anchor
-        - name: xpath
-          in: query
-          description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html"
-          required: false
-          schema:
-            type: string
-            default: /
-          examples:
-            container xpath:
-              value: /shops/bookstore
-            list attributes xpath:
-              value: "/shops/bookstore/categories[@code=1]"
-        - name: observed-timestamp
-          in: query
-          description: observed-timestamp
-          required: false
-          schema:
-            type: string
-            example: 2021-03-21T00:10:34.030-0100
+      - name: dataspace-name
+        in: path
+        description: dataspace-name
+        required: true
+        schema:
+          type: string
+          example: my-dataspace
+      - name: anchor-name
+        in: path
+        description: anchor-name
+        required: true
+        schema:
+          type: string
+          example: my-anchor
+      - name: xpath
+        in: query
+        description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html"
+        required: false
+        schema:
+          type: string
+          default: /
+        examples:
+          container xpath:
+            value: /shops/bookstore
+          list attributes xpath:
+            value: "/shops/bookstore/categories[@code=1]"
+      - name: observed-timestamp
+        in: query
+        description: observed-timestamp
+        required: false
+        schema:
+          type: string
+          example: 2021-03-21T00:10:34.030-0100
       requestBody:
         content:
           application/json:
@@ -981,45 +981,45 @@ paths:
                 details: Internal Server Error occurred
     delete:
       tags:
-        - cps-data
+      - cps-data
       summary: Delete a data node
       description: Delete a datanode for a given dataspace and anchor given a node
         xpath.
       operationId: deleteDataNode
       parameters:
-        - name: dataspace-name
-          in: path
-          description: dataspace-name
-          required: true
-          schema:
-            type: string
-            example: my-dataspace
-        - name: anchor-name
-          in: path
-          description: anchor-name
-          required: true
-          schema:
-            type: string
-            example: my-anchor
-        - name: xpath
-          in: query
-          description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html"
-          required: false
-          schema:
-            type: string
-            default: /
-          examples:
-            container xpath:
-              value: /shops/bookstore
-            list attributes xpath:
-              value: "/shops/bookstore/categories[@code=1]"
-        - name: observed-timestamp
-          in: query
-          description: observed-timestamp
-          required: false
-          schema:
-            type: string
-            example: 2021-03-21T00:10:34.030-0100
+      - name: dataspace-name
+        in: path
+        description: dataspace-name
+        required: true
+        schema:
+          type: string
+          example: my-dataspace
+      - name: anchor-name
+        in: path
+        description: anchor-name
+        required: true
+        schema:
+          type: string
+          example: my-anchor
+      - name: xpath
+        in: query
+        description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html"
+        required: false
+        schema:
+          type: string
+          default: /
+        examples:
+          container xpath:
+            value: /shops/bookstore
+          list attributes xpath:
+            value: "/shops/bookstore/categories[@code=1]"
+      - name: observed-timestamp
+        in: query
+        description: observed-timestamp
+        required: false
+        schema:
+          type: string
+          example: 2021-03-21T00:10:34.030-0100
       responses:
         "204":
           description: No Content
@@ -1066,45 +1066,45 @@ paths:
                 details: Internal Server Error occurred
     patch:
       tags:
-        - cps-data
+      - cps-data
       summary: Update node leaves
       description: Update a data node leaves for a given dataspace and anchor and
         a parent node xpath
       operationId: updateNodeLeaves
       parameters:
-        - name: dataspace-name
-          in: path
-          description: dataspace-name
-          required: true
-          schema:
-            type: string
-            example: my-dataspace
-        - name: anchor-name
-          in: path
-          description: anchor-name
-          required: true
-          schema:
-            type: string
-            example: my-anchor
-        - name: xpath
-          in: query
-          description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html"
-          required: false
-          schema:
-            type: string
-            default: /
-          examples:
-            container xpath:
-              value: /shops/bookstore
-            list attributes xpath:
-              value: "/shops/bookstore/categories[@code=1]"
-        - name: observed-timestamp
-          in: query
-          description: observed-timestamp
-          required: false
-          schema:
-            type: string
-            example: 2021-03-21T00:10:34.030-0100
+      - name: dataspace-name
+        in: path
+        description: dataspace-name
+        required: true
+        schema:
+          type: string
+          example: my-dataspace
+      - name: anchor-name
+        in: path
+        description: anchor-name
+        required: true
+        schema:
+          type: string
+          example: my-anchor
+      - name: xpath
+        in: query
+        description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html"
+        required: false
+        schema:
+          type: string
+          default: /
+        examples:
+          container xpath:
+            value: /shops/bookstore
+          list attributes xpath:
+            value: "/shops/bookstore/categories[@code=1]"
+      - name: observed-timestamp
+        in: query
+        description: observed-timestamp
+        required: false
+        schema:
+          type: string
+          example: 2021-03-21T00:10:34.030-0100
       requestBody:
         content:
           application/json:
@@ -1167,43 +1167,43 @@ paths:
   /v1/dataspaces/{dataspace-name}/anchors/{anchor-name}/list-nodes:
     put:
       tags:
-        - cps-data
+      - cps-data
       summary: Replace list content
       description: "Replace list content under a given parent, anchor and dataspace"
       operationId: replaceListContent
       parameters:
-        - name: dataspace-name
-          in: path
-          description: dataspace-name
-          required: true
-          schema:
-            type: string
-            example: my-dataspace
-        - name: anchor-name
-          in: path
-          description: anchor-name
-          required: true
-          schema:
-            type: string
-            example: my-anchor
-        - name: xpath
-          in: query
-          description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html"
-          required: true
-          schema:
-            type: string
-          examples:
-            container xpath:
-              value: /shops/bookstore
-            list attributes xpath:
-              value: "/shops/bookstore/categories[@code=1]"
-        - name: observed-timestamp
-          in: query
-          description: observed-timestamp
-          required: false
-          schema:
-            type: string
-            example: 2021-03-21T00:10:34.030-0100
+      - name: dataspace-name
+        in: path
+        description: dataspace-name
+        required: true
+        schema:
+          type: string
+          example: my-dataspace
+      - name: anchor-name
+        in: path
+        description: anchor-name
+        required: true
+        schema:
+          type: string
+          example: my-anchor
+      - name: xpath
+        in: query
+        description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html"
+        required: true
+        schema:
+          type: string
+        examples:
+          container xpath:
+            value: /shops/bookstore
+          list attributes xpath:
+            value: "/shops/bookstore/categories[@code=1]"
+      - name: observed-timestamp
+        in: query
+        description: observed-timestamp
+        required: false
+        schema:
+          type: string
+          example: 2021-03-21T00:10:34.030-0100
       requestBody:
         content:
           application/json:
@@ -1265,43 +1265,43 @@ paths:
                 details: Internal Server Error occurred
     post:
       tags:
-        - cps-data
+      - cps-data
       summary: Add list element(s)
       description: Add list element(s) to a list for a given anchor and dataspace
       operationId: addListElements
       parameters:
-        - name: dataspace-name
-          in: path
-          description: dataspace-name
-          required: true
-          schema:
-            type: string
-            example: my-dataspace
-        - name: anchor-name
-          in: path
-          description: anchor-name
-          required: true
-          schema:
-            type: string
-            example: my-anchor
-        - name: xpath
-          in: query
-          description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html"
-          required: true
-          schema:
-            type: string
-          examples:
-            container xpath:
-              value: /shops/bookstore
-            list attributes xpath:
-              value: "/shops/bookstore/categories[@code=1]"
-        - name: observed-timestamp
-          in: query
-          description: observed-timestamp
-          required: false
-          schema:
-            type: string
-            example: 2021-03-21T00:10:34.030-0100
+      - name: dataspace-name
+        in: path
+        description: dataspace-name
+        required: true
+        schema:
+          type: string
+          example: my-dataspace
+      - name: anchor-name
+        in: path
+        description: anchor-name
+        required: true
+        schema:
+          type: string
+          example: my-anchor
+      - name: xpath
+        in: query
+        description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html"
+        required: true
+        schema:
+          type: string
+        examples:
+          container xpath:
+            value: /shops/bookstore
+          list attributes xpath:
+            value: "/shops/bookstore/categories[@code=1]"
+      - name: observed-timestamp
+        in: query
+        description: observed-timestamp
+        required: false
+        schema:
+          type: string
+          example: 2021-03-21T00:10:34.030-0100
       requestBody:
         content:
           application/json:
@@ -1361,43 +1361,43 @@ paths:
                 details: Internal Server Error occurred
     delete:
       tags:
-        - cps-data
+      - cps-data
       summary: Delete one or all list element(s)
       description: Delete one or all list element(s) for a given anchor and dataspace
       operationId: deleteListOrListElement
       parameters:
-        - name: dataspace-name
-          in: path
-          description: dataspace-name
-          required: true
-          schema:
-            type: string
-            example: my-dataspace
-        - name: anchor-name
-          in: path
-          description: anchor-name
-          required: true
-          schema:
-            type: string
-            example: my-anchor
-        - name: xpath
-          in: query
-          description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html"
-          required: true
-          schema:
-            type: string
-          examples:
-            container xpath:
-              value: /shops/bookstore
-            list attributes xpath:
-              value: "/shops/bookstore/categories[@code=1]"
-        - name: observed-timestamp
-          in: query
-          description: observed-timestamp
-          required: false
-          schema:
-            type: string
-            example: 2021-03-21T00:10:34.030-0100
+      - name: dataspace-name
+        in: path
+        description: dataspace-name
+        required: true
+        schema:
+          type: string
+          example: my-dataspace
+      - name: anchor-name
+        in: path
+        description: anchor-name
+        required: true
+        schema:
+          type: string
+          example: my-anchor
+      - name: xpath
+        in: query
+        description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html"
+        required: true
+        schema:
+          type: string
+        examples:
+          container xpath:
+            value: /shops/bookstore
+          list attributes xpath:
+            value: "/shops/bookstore/categories[@code=1]"
+      - name: observed-timestamp
+        in: query
+        description: observed-timestamp
+        required: false
+        schema:
+          type: string
+          example: 2021-03-21T00:10:34.030-0100
       responses:
         "204":
           description: No Content
@@ -1446,45 +1446,45 @@ paths:
   /v1/dataspaces/{dataspace-name}/anchors/{anchor-name}/nodes/query:
     get:
       tags:
-        - cps-query
+      - cps-query
       summary: Query data nodes
       description: Query data nodes for the given dataspace and anchor using CPS path
       operationId: getNodesByDataspaceAndAnchorAndCpsPath
       parameters:
-        - name: dataspace-name
-          in: path
-          description: dataspace-name
-          required: true
-          schema:
-            type: string
-            example: my-dataspace
-        - name: anchor-name
-          in: path
-          description: anchor-name
-          required: true
-          schema:
-            type: string
-            example: my-anchor
-        - name: cps-path
-          in: query
-          description: "For more details on cps path, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html"
-          required: false
-          schema:
-            type: string
-            default: /
-          examples:
-            container cps path:
-              value: //bookstore
-            list attributes cps path:
-              value: "//categories[@code=1]"
-        - name: include-descendants
-          in: query
-          description: include-descendants
-          required: false
-          schema:
-            type: boolean
-            example: false
-            default: false
+      - name: dataspace-name
+        in: path
+        description: dataspace-name
+        required: true
+        schema:
+          type: string
+          example: my-dataspace
+      - name: anchor-name
+        in: path
+        description: anchor-name
+        required: true
+        schema:
+          type: string
+          example: my-anchor
+      - name: cps-path
+        in: query
+        description: "For more details on cps path, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html"
+        required: false
+        schema:
+          type: string
+          default: /
+        examples:
+          container cps path:
+            value: //bookstore
+          list attributes cps path:
+            value: "//categories[@code=1]"
+      - name: include-descendants
+        in: query
+        description: include-descendants
+        required: false
+        schema:
+          type: boolean
+          example: false
+          default: false
       responses:
         "200":
           description: OK
@@ -1563,7 +1563,7 @@ components:
           example: my-schema-set
     MultipartFile:
       required:
-        - file
+      - file
       type: object
       properties:
         file:
@@ -1573,7 +1573,7 @@ components:
     SchemaSetDetails:
       title: Schema set details by dataspace and schemasetName
       required:
-        - moduleReferences
+      - moduleReferences
       type: object
       properties:
         dataspaceName:
@@ -1605,7 +1605,7 @@ components:
         test:bookstore:
           bookstore-name: Chapters
           categories:
-            - code: 1
-              name: SciFi
-            - code: 2
-              name: kids
+          - code: 1
+            name: SciFi
+          - code: 2
+            name: kids
index f203eac..2d34f0a 100644 (file)
@@ -4,12 +4,12 @@ info:
   description: NCMP Inventory API
   version: "1.0"
 servers:
-  - url: /ncmpInventory
+- url: /ncmpInventory
 paths:
   /v1/ch:
     post:
       tags:
-        - network-cm-proxy-inventory
+      - network-cm-proxy-inventory
       summary: DMI notifies NCMP of new CM Handles
       description: "Register a DMI Plugin with any new, updated or removed CM Handles."
       operationId: updateDmiPluginRegistration
@@ -63,51 +63,51 @@ paths:
                 $ref: '#/components/schemas/DmiPluginRegistrationErrorResponse'
               example:
                 failedCreatedCmHandles:
-                  - cmHandle: my-cm-handle-01
-                    errorCode: "00"
-                    errorText: Unknown error. <error-details>
-                  - cmHandle: my-cm-handle-02
-                    errorCode: "01"
-                    errorText: cm-handle already exists
-                  - cmHandle: my-cm-handle-03
-                    errorCode: "03"
-                    errorText: cm-handle has an invalid character(s) in id
+                - cmHandle: my-cm-handle-01
+                  errorCode: "00"
+                  errorText: Unknown error. <error-details>
+                - cmHandle: my-cm-handle-02
+                  errorCode: "01"
+                  errorText: cm-handle already exists
+                - cmHandle: my-cm-handle-03
+                  errorCode: "03"
+                  errorText: cm-handle has an invalid character(s) in id
                 failedUpdatedCmHandles:
-                  - cmHandle: my-cm-handle-01
-                    errorCode: "00"
-                    errorText: Unknown error. <error-details>
-                  - cmHandle: my-cm-handle-02
-                    errorCode: "02"
-                    errorText: cm-handle does not exist
-                  - cmHandle: my-cm-handle-03
-                    errorCode: "03"
-                    errorText: cm-handle has an invalid character(s) in id
+                - cmHandle: my-cm-handle-01
+                  errorCode: "00"
+                  errorText: Unknown error. <error-details>
+                - cmHandle: my-cm-handle-02
+                  errorCode: "02"
+                  errorText: cm-handle does not exist
+                - cmHandle: my-cm-handle-03
+                  errorCode: "03"
+                  errorText: cm-handle has an invalid character(s) in id
                 failedRemovedCmHandles:
-                  - cmHandle: my-cm-handle-01
-                    errorCode: "00"
-                    errorText: Unknown error. <error-details>
-                  - cmHandle: my-cm-handle-02
-                    errorCode: "02"
-                    errorText: cm-handle does not exists
-                  - cmHandle: my-cm-handle-03
-                    errorCode: "03"
-                    errorText: cm-handle has an invalid character(s) in id
+                - cmHandle: my-cm-handle-01
+                  errorCode: "00"
+                  errorText: Unknown error. <error-details>
+                - cmHandle: my-cm-handle-02
+                  errorCode: "02"
+                  errorText: cm-handle does not exists
+                - cmHandle: my-cm-handle-03
+                  errorCode: "03"
+                  errorText: cm-handle has an invalid character(s) in id
   /v1/ch/cmHandles:
     get:
       tags:
-        - network-cm-proxy-inventory
+      - network-cm-proxy-inventory
       summary: "Get all cm handle IDs for a registered DMI plugin (DMI plugin, DMI\
         \ data plugin, DMI model plugin)"
       description: Get all cm handle IDs for a registered DMI plugin
       operationId: getAllCmHandleIdsForRegisteredDmi
       parameters:
-        - name: dmi-plugin-identifier
-          in: query
-          description: dmi-plugin-identifier
-          required: true
-          schema:
-            type: string
-            example: my-dmi-plugin
+      - name: dmi-plugin-identifier
+        in: query
+        description: dmi-plugin-identifier
+        required: true
+        schema:
+          type: string
+          example: my-dmi-plugin
       responses:
         "200":
           description: OK
@@ -175,14 +175,14 @@ components:
         removedCmHandles:
           type: array
           example:
-            - my-cm-handle1
-            - my-cm-handle2
-            - my-cm-handle3
+          - my-cm-handle1
+          - my-cm-handle2
+          - my-cm-handle3
           items:
             type: string
     RestInputCmHandle:
       required:
-        - cmHandle
+      - cmHandle
       type: object
       properties:
         cmHandle:
index 5040791..44cc2b8 100644 (file)
@@ -4,87 +4,87 @@ info:
   description: NCMP to CPS Proxy API
   version: "1.0"
 servers:
-  - url: /ncmp
+- url: /ncmp
 paths:
   /v1/ch/{cm-handle}/data/ds/{ncmp-datastore-name}:
     get:
       tags:
-        - network-cm-proxy
+      - network-cm-proxy
       summary: Get resource data for cm handle
       description: Get resource data for given cm handle
       operationId: getResourceDataForCmHandle
       parameters:
-        - name: ncmp-datastore-name
-          in: path
-          description: The type of the requested data
-          required: true
-          schema:
-            type: string
-            example: ncmp-datastore:operational
-        - name: cm-handle
-          in: path
-          description: "The identifier for a network function, network element, subnetwork\
+      - name: ncmp-datastore-name
+        in: path
+        description: The type of the requested data
+        required: true
+        schema:
+          type: string
+          example: ncmp-datastore:operational
+      - name: cm-handle
+        in: path
+        description: "The identifier for a network function, network element, subnetwork\
           \ or any other cm object by managed Network CM Proxy"
-          required: true
-          schema:
-            type: string
-            example: my-cm-handle
-        - name: resourceIdentifier
-          in: query
-          description: The format of resource identifier depend on the associated DMI
-            Plugin implementation. For ONAP DMI Plugin it will be RESTConf paths but
-            it can really be anything.
-          required: true
-          allowReserved: true
-          schema:
-            type: string
-          examples:
-            sample 1:
-              value:
-                resourceIdentifier: \shops\bookstore
-            sample 2:
-              value:
-                resourceIdentifier: "\\shops\\bookstore\\categories[@code=1]"
-            sample 3:
-              value:
-                resourceIdentifier: "parent=shops,child=bookstore"
-        - name: options
-          in: query
-          description: "options parameter in query, it is mandatory to wrap key(s)=value(s)\
+        required: true
+        schema:
+          type: string
+          example: my-cm-handle
+      - name: resourceIdentifier
+        in: query
+        description: The format of resource identifier depend on the associated DMI
+          Plugin implementation. For ONAP DMI Plugin it will be RESTConf paths but
+          it can really be anything.
+        required: true
+        allowReserved: true
+        schema:
+          type: string
+        examples:
+          sample 1:
+            value:
+              resourceIdentifier: \shops\bookstore
+          sample 2:
+            value:
+              resourceIdentifier: "\\shops\\bookstore\\categories[@code=1]"
+          sample 3:
+            value:
+              resourceIdentifier: "parent=shops,child=bookstore"
+      - name: options
+        in: query
+        description: "options parameter in query, it is mandatory to wrap key(s)=value(s)\
           \ in parenthesis'()'. The format of options parameter depend on the associated\
           \ DMI Plugin implementation."
-          required: false
-          allowReserved: true
-          schema:
-            type: string
-          examples:
-            sample 1:
-              value:
-                options: (depth=3)
-            sample 2:
-              value:
-                options: (fields=book)
-            sample 3:
-              value:
-                options: "(depth=2,fields=book/authors)"
-        - name: topic
-          in: query
-          description: topic parameter in query.
-          required: false
-          allowReserved: true
-          schema:
-            type: string
-          examples:
-            sample 1:
-              value:
-                topic: my-topic-name
-        - name: include-descendants
-          in: query
-          description: Determines if descendants are included in response
-          required: false
-          schema:
-            type: boolean
-            default: false
+        required: false
+        allowReserved: true
+        schema:
+          type: string
+        examples:
+          sample 1:
+            value:
+              options: (depth=3)
+          sample 2:
+            value:
+              options: (fields=book)
+          sample 3:
+            value:
+              options: "(depth=2,fields=book/authors)"
+      - name: topic
+        in: query
+        description: topic parameter in query.
+        required: false
+        allowReserved: true
+        schema:
+          type: string
+        examples:
+          sample 1:
+            value:
+              topic: my-topic-name
+      - name: include-descendants
+        in: query
+        description: Determines if descendants are included in response
+        required: false
+        schema:
+          type: boolean
+          default: false
       responses:
         "200":
           description: OK
@@ -146,51 +146,57 @@ paths:
                 dmi-response:
                   http-code: 400
                   body: Bad Request
-  /v1/ch/{cm-handle}/data/ds/ncmp-datastore:passthrough-running:
     put:
       tags:
-        - network-cm-proxy
+      - network-cm-proxy
       summary: Update resource data from pass-through running for a cm handle
       description: Update resource data from pass-through running for the given cm
         handle
       operationId: updateResourceDataRunningForCmHandle
       parameters:
-        - name: cm-handle
-          in: path
-          description: "The identifier for a network function, network element, subnetwork\
+      - name: ncmp-datastore-name
+        in: path
+        description: The type of the requested data
+        required: true
+        schema:
+          type: string
+          example: ncmp-datastore:operational
+      - name: cm-handle
+        in: path
+        description: "The identifier for a network function, network element, subnetwork\
           \ or any other cm object by managed Network CM Proxy"
-          required: true
-          schema:
-            type: string
-            example: my-cm-handle
-        - name: resourceIdentifier
-          in: query
-          description: The format of resource identifier depend on the associated DMI
-            Plugin implementation. For ONAP DMI Plugin it will be RESTConf paths but
-            it can really be anything.
-          required: true
-          allowReserved: true
-          schema:
-            type: string
-          examples:
-            sample 1:
-              value:
-                resourceIdentifier: \shops\bookstore
-            sample 2:
-              value:
-                resourceIdentifier: "\\shops\\bookstore\\categories[@code=1]"
-            sample 3:
-              value:
-                resourceIdentifier: "parent=shops,child=bookstore"
-        - name: Content-Type
-          in: header
-          description: "Content parameter for request, if content parameter is null,\
+        required: true
+        schema:
+          type: string
+          example: my-cm-handle
+      - name: resourceIdentifier
+        in: query
+        description: The format of resource identifier depend on the associated DMI
+          Plugin implementation. For ONAP DMI Plugin it will be RESTConf paths but
+          it can really be anything.
+        required: true
+        allowReserved: true
+        schema:
+          type: string
+        examples:
+          sample 1:
+            value:
+              resourceIdentifier: \shops\bookstore
+          sample 2:
+            value:
+              resourceIdentifier: "\\shops\\bookstore\\categories[@code=1]"
+          sample 3:
+            value:
+              resourceIdentifier: "parent=shops,child=bookstore"
+      - name: Content-Type
+        in: header
+        description: "Content parameter for request, if content parameter is null,\
           \ default value is application/json."
-          required: false
-          schema:
-            type: string
-            example: application/yang-data+json
-            default: application/json
+        required: false
+        schema:
+          type: string
+          example: application/yang-data+json
+          default: application/json
       requestBody:
         content:
           application/json:
@@ -266,47 +272,54 @@ paths:
                   body: Bad Request
     post:
       tags:
-        - network-cm-proxy
+      - network-cm-proxy
       summary: create resource data from pass-through running for cm handle
       description: create resource data from pass-through running for given cm handle
       operationId: createResourceDataRunningForCmHandle
       parameters:
-        - name: cm-handle
-          in: path
-          description: "The identifier for a network function, network element, subnetwork\
+      - name: ncmp-datastore-name
+        in: path
+        description: The type of the requested data
+        required: true
+        schema:
+          type: string
+          example: ncmp-datastore:operational
+      - name: cm-handle
+        in: path
+        description: "The identifier for a network function, network element, subnetwork\
           \ or any other cm object by managed Network CM Proxy"
-          required: true
-          schema:
-            type: string
-            example: my-cm-handle
-        - name: resourceIdentifier
-          in: query
-          description: The format of resource identifier depend on the associated DMI
-            Plugin implementation. For ONAP DMI Plugin it will be RESTConf paths but
-            it can really be anything.
-          required: true
-          allowReserved: true
-          schema:
-            type: string
-          examples:
-            sample 1:
-              value:
-                resourceIdentifier: \shops\bookstore
-            sample 2:
-              value:
-                resourceIdentifier: "\\shops\\bookstore\\categories[@code=1]"
-            sample 3:
-              value:
-                resourceIdentifier: "parent=shops,child=bookstore"
-        - name: Content-Type
-          in: header
-          description: "Content parameter for request, if content parameter is null,\
+        required: true
+        schema:
+          type: string
+          example: my-cm-handle
+      - name: resourceIdentifier
+        in: query
+        description: The format of resource identifier depend on the associated DMI
+          Plugin implementation. For ONAP DMI Plugin it will be RESTConf paths but
+          it can really be anything.
+        required: true
+        allowReserved: true
+        schema:
+          type: string
+        examples:
+          sample 1:
+            value:
+              resourceIdentifier: \shops\bookstore
+          sample 2:
+            value:
+              resourceIdentifier: "\\shops\\bookstore\\categories[@code=1]"
+          sample 3:
+            value:
+              resourceIdentifier: "parent=shops,child=bookstore"
+      - name: Content-Type
+        in: header
+        description: "Content parameter for request, if content parameter is null,\
           \ default value is application/json."
-          required: false
-          schema:
-            type: string
-            example: application/yang-data+json
-            default: application/json
+        required: false
+        schema:
+          type: string
+          example: application/yang-data+json
+          default: application/json
       requestBody:
         content:
           application/json:
@@ -379,47 +392,54 @@ paths:
                   body: Bad Request
     delete:
       tags:
-        - network-cm-proxy
+      - network-cm-proxy
       summary: Delete resource data
       description: Delete resource data from pass-through running for a given cm handle
       operationId: deleteResourceDataRunningForCmHandle
       parameters:
-        - name: cm-handle
-          in: path
-          description: "The identifier for a network function, network element, subnetwork\
+      - name: ncmp-datastore-name
+        in: path
+        description: The type of the requested data
+        required: true
+        schema:
+          type: string
+          example: ncmp-datastore:operational
+      - name: cm-handle
+        in: path
+        description: "The identifier for a network function, network element, subnetwork\
           \ or any other cm object by managed Network CM Proxy"
-          required: true
-          schema:
-            type: string
-            example: my-cm-handle
-        - name: resourceIdentifier
-          in: query
-          description: The format of resource identifier depend on the associated DMI
-            Plugin implementation. For ONAP DMI Plugin it will be RESTConf paths but
-            it can really be anything.
-          required: true
-          allowReserved: true
-          schema:
-            type: string
-          examples:
-            sample 1:
-              value:
-                resourceIdentifier: \shops\bookstore
-            sample 2:
-              value:
-                resourceIdentifier: "\\shops\\bookstore\\categories[@code=1]"
-            sample 3:
-              value:
-                resourceIdentifier: "parent=shops,child=bookstore"
-        - name: Content-Type
-          in: header
-          description: "Content parameter for request, if content parameter is null,\
+        required: true
+        schema:
+          type: string
+          example: my-cm-handle
+      - name: resourceIdentifier
+        in: query
+        description: The format of resource identifier depend on the associated DMI
+          Plugin implementation. For ONAP DMI Plugin it will be RESTConf paths but
+          it can really be anything.
+        required: true
+        allowReserved: true
+        schema:
+          type: string
+        examples:
+          sample 1:
+            value:
+              resourceIdentifier: \shops\bookstore
+          sample 2:
+            value:
+              resourceIdentifier: "\\shops\\bookstore\\categories[@code=1]"
+          sample 3:
+            value:
+              resourceIdentifier: "parent=shops,child=bookstore"
+      - name: Content-Type
+        in: header
+        description: "Content parameter for request, if content parameter is null,\
           \ default value is application/json."
-          required: false
-          schema:
-            type: string
-            example: application/yang-data+json
-            default: application/json
+        required: false
+        schema:
+          type: string
+          example: application/yang-data+json
+          default: application/json
       responses:
         "204":
           description: No Content
@@ -487,48 +507,55 @@ paths:
                   body: Bad Request
     patch:
       tags:
-        - network-cm-proxy
+      - network-cm-proxy
       summary: Patch resource data from pass-through running
       description: Patch resource data from pass-through running for the given cm
         handle
       operationId: patchResourceDataRunningForCmHandle
       parameters:
-        - name: cm-handle
-          in: path
-          description: "The identifier for a network function, network element, subnetwork\
+      - name: ncmp-datastore-name
+        in: path
+        description: The type of the requested data
+        required: true
+        schema:
+          type: string
+          example: ncmp-datastore:operational
+      - name: cm-handle
+        in: path
+        description: "The identifier for a network function, network element, subnetwork\
           \ or any other cm object by managed Network CM Proxy"
-          required: true
-          schema:
-            type: string
-            example: my-cm-handle
-        - name: resourceIdentifier
-          in: query
-          description: The format of resource identifier depend on the associated DMI
-            Plugin implementation. For ONAP DMI Plugin it will be RESTConf paths but
-            it can really be anything.
-          required: true
-          allowReserved: true
-          schema:
-            type: string
-          examples:
-            sample 1:
-              value:
-                resourceIdentifier: \shops\bookstore
-            sample 2:
-              value:
-                resourceIdentifier: "\\shops\\bookstore\\categories[@code=1]"
-            sample 3:
-              value:
-                resourceIdentifier: "parent=shops,child=bookstore"
-        - name: Content-Type
-          in: header
-          description: "Content parameter for request, if content parameter is null,\
+        required: true
+        schema:
+          type: string
+          example: my-cm-handle
+      - name: resourceIdentifier
+        in: query
+        description: The format of resource identifier depend on the associated DMI
+          Plugin implementation. For ONAP DMI Plugin it will be RESTConf paths but
+          it can really be anything.
+        required: true
+        allowReserved: true
+        schema:
+          type: string
+        examples:
+          sample 1:
+            value:
+              resourceIdentifier: \shops\bookstore
+          sample 2:
+            value:
+              resourceIdentifier: "\\shops\\bookstore\\categories[@code=1]"
+          sample 3:
+            value:
+              resourceIdentifier: "parent=shops,child=bookstore"
+      - name: Content-Type
+        in: header
+        description: "Content parameter for request, if content parameter is null,\
           \ default value is application/json."
-          required: false
-          schema:
-            type: string
-            example: application/yang-data+json
-            default: application/json
+        required: false
+        schema:
+          type: string
+          example: application/yang-data+json
+          default: application/json
       requestBody:
         content:
           '*/*':
@@ -599,20 +626,20 @@ paths:
   /v1/ch/{cm-handle}/modules:
     get:
       tags:
-        - network-cm-proxy
+      - network-cm-proxy
       summary: Fetch all module references (name and revision) for a given cm handle
       description: fetch all module references (name and revision) for a given cm
         handle
       operationId: getModuleReferencesByCmHandle
       parameters:
-        - name: cm-handle
-          in: path
-          description: "The identifier for a network function, network element, subnetwork\
+      - name: cm-handle
+        in: path
+        description: "The identifier for a network function, network element, subnetwork\
           \ or any other cm object by managed Network CM Proxy"
-          required: true
-          schema:
-            type: string
-            example: my-cm-handle
+        required: true
+        schema:
+          type: string
+          example: my-cm-handle
       responses:
         "200":
           description: OK
@@ -665,21 +692,21 @@ paths:
   /v1/ch/{cm-handle}/modules/definitions:
     get:
       tags:
-        - network-cm-proxy
+      - network-cm-proxy
       summary: "Fetch all module definitions (name, revision, yang resource) for a\
         \ given cm handle"
       description: "Fetch all module definitions (name, revision, yang resource) for\
         \ a given cm handle"
       operationId: getModuleDefinitionsByCmHandleId
       parameters:
-        - name: cm-handle
-          in: path
-          description: "The identifier for a network function, network element, subnetwork\
+      - name: cm-handle
+        in: path
+        description: "The identifier for a network function, network element, subnetwork\
           \ or any other cm object by managed Network CM Proxy"
-          required: true
-          schema:
-            type: string
-            example: my-cm-handle
+        required: true
+        schema:
+          type: string
+          example: my-cm-handle
       responses:
         "200":
           description: OK
@@ -722,7 +749,7 @@ paths:
   /v1/ch/searches:
     post:
       tags:
-        - network-cm-proxy
+      - network-cm-proxy
       summary: Execute cm handle search using the available conditions
       description: Execute cm handle query search and return a list of cm handle details.
         Any number of conditions can be applied. To be included in the result a cm-handle
@@ -804,19 +831,19 @@ paths:
   /v1/ch/{cm-handle}:
     get:
       tags:
-        - network-cm-proxy
+      - network-cm-proxy
       summary: Retrieve CM handle details
       description: Retrieve CM handle details and properties by cm handle id
       operationId: retrieveCmHandleDetailsById
       parameters:
-        - name: cm-handle
-          in: path
-          description: "The identifier for a network function, network element, subnetwork\
+      - name: cm-handle
+        in: path
+        description: "The identifier for a network function, network element, subnetwork\
           \ or any other cm object by managed Network CM Proxy"
-          required: true
-          schema:
-            type: string
-            example: my-cm-handle
+        required: true
+        schema:
+          type: string
+          example: my-cm-handle
       responses:
         "200":
           description: OK
@@ -867,19 +894,19 @@ paths:
   /v1/ch/{cm-handle}/properties:
     get:
       tags:
-        - network-cm-proxy
+      - network-cm-proxy
       summary: Get CM handle properties
       description: Get CM handle properties by cm handle id
       operationId: getCmHandlePublicPropertiesByCmHandleId
       parameters:
-        - name: cm-handle
-          in: path
-          description: "The identifier for a network function, network element, subnetwork\
+      - name: cm-handle
+        in: path
+        description: "The identifier for a network function, network element, subnetwork\
           \ or any other cm object by managed Network CM Proxy"
-          required: true
-          schema:
-            type: string
-            example: my-cm-handle
+        required: true
+        schema:
+          type: string
+          example: my-cm-handle
       responses:
         "200":
           description: OK
@@ -930,7 +957,7 @@ paths:
   /v1/ch/id-searches:
     post:
       tags:
-        - network-cm-proxy
+      - network-cm-proxy
       summary: Execute cm handle query upon a given set of query parameters
       description: Execute cm handle query search and return a list of cm handle ids.
         Any number of conditions can be applied. To be included in the result a cm-handle
@@ -1022,19 +1049,19 @@ paths:
   /v1/ch/{cm-handle}/state:
     get:
       tags:
-        - network-cm-proxy
+      - network-cm-proxy
       summary: Get CM handle state
       description: Get CM handle state by cm handle id
       operationId: getCmHandleStateByCmHandleId
       parameters:
-        - name: cm-handle
-          in: path
-          description: "The identifier for a network function, network element, subnetwork\
+      - name: cm-handle
+        in: path
+        description: "The identifier for a network function, network element, subnetwork\
           \ or any other cm object by managed Network CM Proxy"
-          required: true
-          schema:
-            type: string
-            example: my-cm-handle
+        required: true
+        schema:
+          type: string
+          example: my-cm-handle
       responses:
         "200":
           description: OK
@@ -1085,28 +1112,28 @@ paths:
   /v1/ch/{cm-handle}/data-sync:
     put:
       tags:
-        - network-cm-proxy
+      - network-cm-proxy
       summary: Set the Data Sync Enabled Flag
       description: Set the data sync enabled flag to true or false for a specified
         Cm-Handle. This will in turn set the data sync state to UNSYNCHRONIZED and
         NONE_REQUESTED respectfully.
       operationId: setDataSyncEnabledFlagForCmHandle
       parameters:
-        - name: cm-handle
-          in: path
-          description: "The identifier for a network function, network element, subnetwork\
+      - name: cm-handle
+        in: path
+        description: "The identifier for a network function, network element, subnetwork\
           \ or any other cm object by managed Network CM Proxy"
-          required: true
-          schema:
-            type: string
-            example: my-cm-handle
-        - name: dataSyncEnabled
-          in: query
-          description: Is used to enable or disable the data synchronization flag
-          required: true
-          schema:
-            type: boolean
-            example: true
+        required: true
+        schema:
+          type: string
+          example: my-cm-handle
+      - name: dataSyncEnabled
+        in: query
+        description: Is used to enable or disable the data synchronization flag
+        required: true
+        schema:
+          type: boolean
+          example: true
       responses:
         "200":
           description: OK
@@ -1334,17 +1361,17 @@ components:
       value:
         bookstore:
           categories:
-            - code: "01"
-              books:
-                - authors:
-                    - Iain M. Banks
-                    - Ursula K. Le Guin
-              name: SciFi
-            - code: "02"
-              books:
-                - authors:
-                    - Philip Pullman
-              name: kids
+          - code: "01"
+            books:
+            - authors:
+              - Iain M. Banks
+              - Ursula K. Le Guin
+            name: SciFi
+          - code: "02"
+            books:
+            - authors:
+              - Philip Pullman
+            name: kids
     dataSampleRequest:
       summary: Sample request
       description: Sample request body
@@ -1352,17 +1379,17 @@ components:
         test:bookstore:
           bookstore-name: Chapters
           categories:
-            - code: "01"
-              name: SciFi
-              books:
-                - authors:
-                    - Iain M. Banks
-                    - Ursula K. Le Guin
-            - code: "02"
-              name: kids
-              books:
-                - authors:
-                    - Philip Pullman
+          - code: "01"
+            name: SciFi
+            books:
+            - authors:
+              - Iain M. Banks
+              - Ursula K. Le Guin
+          - code: "02"
+            name: kids
+            books:
+            - authors:
+              - Philip Pullman
     dataSamplePatchRequest:
       summary: Sample patch request
       description: Sample patch request body
@@ -1370,83 +1397,83 @@ components:
         ietf-restconf:yang-patch:
           patch-id: patch-1
           edit:
-            - edit-id: edit1
-              operation: merge
-              target: /
-              value:
-                test:bookstore:
-                  bookstore-name: Chapters
-                  categories:
-                    - code: "01"
-                      name: Science
-                      books:
-                        - authors:
-                            - Author1
-                            - Author2
-                    - code: "02"
-                      name: Arts
-                      books:
-                        - authors:
-                            - Author3
-            - edit-id: edit2
-              operation: merge
-              target: /
-              value:
-                test:bookstore:
-                  bookstore-name: Novels
-                  categories:
-                    - code: "03"
-                      name: History
-                      books:
-                        - authors:
-                            - Iain M. Banks
-                            - Ursula K. Le Guin
-                    - code: "04"
-                      name: Fiction
-                      books:
-                        - authors:
-                            - Philip Pullman
+          - edit-id: edit1
+            operation: merge
+            target: /
+            value:
+              test:bookstore:
+                bookstore-name: Chapters
+                categories:
+                - code: "01"
+                  name: Science
+                  books:
+                  - authors:
+                    - Author1
+                    - Author2
+                - code: "02"
+                  name: Arts
+                  books:
+                  - authors:
+                    - Author3
+          - edit-id: edit2
+            operation: merge
+            target: /
+            value:
+              test:bookstore:
+                bookstore-name: Novels
+                categories:
+                - code: "03"
+                  name: History
+                  books:
+                  - authors:
+                    - Iain M. Banks
+                    - Ursula K. Le Guin
+                - code: "04"
+                  name: Fiction
+                  books:
+                  - authors:
+                    - Philip Pullman
     pubPropCmHandleQueryParameters:
       value:
         cmHandleQueryParameters:
-          - conditionName: hasAllProperties
-            conditionParameters:
-              - Color: yellow
-              - Shape: circle
-              - Size: small
+        - conditionName: hasAllProperties
+          conditionParameters:
+          - Color: yellow
+          - Shape: circle
+          - Size: small
     modulesCmHandleQueryParameters:
       value:
         cmHandleQueryParameters:
-          - conditionName: hasAllModules
-            conditionParameters:
-              - moduleName: my-module-1
-              - moduleName: my-module-2
-              - moduleName: my-module-3
+        - conditionName: hasAllModules
+          conditionParameters:
+          - moduleName: my-module-1
+          - moduleName: my-module-2
+          - moduleName: my-module-3
     allCmHandleQueryParameters:
       value:
         cmHandleQueryParameters:
-          - conditionName: hasAllModules
-            conditionParameters:
-              - moduleName: my-module-1
-              - moduleName: my-module-2
-              - moduleName: my-module-3
-          - conditionName: hasAllProperties
-            conditionParameters:
-              - Color: yellow
-              - Shape: circle
-              - Size: small
-          - conditionName: cmHandleWithCpsPath
-            conditionParameters:
-              - cpsPath: "//state[@cm-handle-state='ADVISED']"
+        - conditionName: hasAllModules
+          conditionParameters:
+          - moduleName: my-module-1
+          - moduleName: my-module-2
+          - moduleName: my-module-3
+        - conditionName: hasAllProperties
+          conditionParameters:
+          - Color: yellow
+          - Shape: circle
+          - Size: small
+        - conditionName: cmHandleWithCpsPath
+          conditionParameters:
+          - cpsPath: "//state[@cm-handle-state='ADVISED']"
     cpsPathCmHandleStateQueryParameters:
       value:
         cmHandleQueryParameters:
-          - conditionName: cmHandleWithCpsPath
-            conditionParameters:
-              - cpsPath: "//state[@cm-handle-state='LOCKED']"
+        - conditionName: cmHandleWithCpsPath
+          conditionParameters:
+          - cpsPath: "//state[@cm-handle-state='LOCKED']"
     cpsPathCmHandleDataSyncQueryParameters:
       value:
         cmHandleQueryParameters:
-          - conditionName: cmHandleWithCpsPath
-            conditionParameters:
-              - cpsPath: "//state[@data-sync-enabled='true']"
+        - conditionName: cmHandleWithCpsPath
+          conditionParameters:
+          - cpsPath: "//state[@data-sync-enabled='true']"
index 177d8d6..d4f9843 100755 (executable)
@@ -40,6 +40,7 @@ Features
 --------
    - `CPS-322 <https://jira.onap.org/browse/CPS-322>`_  Implement additional validation for names and identifiers
    - `CPS-1136 <https://jira.onap.org/browse/CPS-1136>`_  Get all cm handles by DMI plugin Identifier
+   - `CPS-1001 <https://jira.onap.org/browse/CPS-1001>`_  Add CPS-E-05 endpoint for Read data, NCMP-Operational Datastore
 
 Bug Fixes
 ---------
diff --git a/releases/3.1.0-container.yaml b/releases/3.1.0-container.yaml
new file mode 100644 (file)
index 0000000..cc11ed2
--- /dev/null
@@ -0,0 +1,8 @@
+distribution_type: container
+container_release_tag: 3.1.0
+project: cps
+log_dir: cps-maven-docker-stage-master/690/
+ref: 9697e76c319e4cf59fc494216a720393545503a9
+containers:
+  - name: 'cps-and-ncmp'
+    version: '3.1.0-20220914T140025Z'
diff --git a/releases/3.1.0.yaml b/releases/3.1.0.yaml
new file mode 100644 (file)
index 0000000..1258b26
--- /dev/null
@@ -0,0 +1,4 @@
+distribution_type: maven
+log_dir: cps-maven-stage-master/696/
+project: cps
+version: 3.1.0