CPS-240 - Create REST End-point on NF-Proxy for DataNode Update & cpsPath Query 00/118600/4
authorniamhcore <niamh.core@est.tech>
Fri, 26 Feb 2021 10:13:48 +0000 (10:13 +0000)
committerniamhcore <niamh.core@est.tech>
Fri, 26 Feb 2021 17:21:02 +0000 (17:21 +0000)
Issue-ID: CPS-240
Signed-off-by: niamhcore <niamh.core@est.tech>
Change-Id: I2aed92f8ab34282b12e23ae7807a391446165eb0

cps-nf-proxy-rest/docs/openapi/components.yaml
cps-nf-proxy-rest/docs/openapi/openapi.yml
cps-nf-proxy-rest/docs/openapi/xnfProxy.yml
cps-nf-proxy-rest/src/main/java/org/onap/cps/nfproxy/rest/controller/NfProxyController.java
cps-nf-proxy-rest/src/test/groovy/org/onap/cps/nfproxy/rest/controller/NfProxyControllerSpec.groovy
cps-nf-proxy-service/pom.xml
cps-nf-proxy-service/src/main/java/org/onap/cps/nfproxy/api/NfProxyDataService.java
cps-nf-proxy-service/src/main/java/org/onap/cps/nfproxy/api/impl/NfProxyDataServiceImpl.java
cps-nf-proxy-service/src/test/groovy/org/onap/cps/api/impl/NfProxyDataServiceImplSpec.groovy [new file with mode: 0644]

index 0b5d52a..af95723 100644 (file)
@@ -43,6 +43,15 @@ components:
       schema:
         type: boolean
         default: false
+    cpsPathInQuery:
+      name: cps-path
+      in: query
+      description: cps-path
+      required: false
+      schema:
+        type: string
+        default: /
+
 
   responses:
     NotFound:
index efa8e72..a6d0949 100755 (executable)
@@ -9,3 +9,8 @@ paths:
   /v1/cm-handles/{cm-handle}/node:
     $ref: 'xnfProxy.yml#/nodeByCmHandleAndXpath'
 
+  /v1/cm-handles/{cm-handle}/nodes/query:
+    $ref: 'xnfProxy.yml#/nodesByCmHandleAndCpsPath'
+
+  /v1/cm-handles/{cm-handle}/nodes:
+    $ref: 'xnfProxy.yml#/nodesByCmHandleAndXpath'
\ No newline at end of file
index 4abe81a..c39d2df 100644 (file)
@@ -22,3 +22,80 @@ nodeByCmHandleAndXpath:
         $ref: 'components.yaml#/components/responses/NotFound'
       501:
         $ref: 'components.yaml#/components/responses/NotImplemented'
+
+nodesByCmHandleAndCpsPath:
+  get:
+    description: Query nodes for the given cps path and cm Handle
+    tags:
+      - nf-proxy
+    summary: Query data nodes
+    operationId: queryNodesByCmHandleAndCpsPath
+    parameters:
+      - $ref: 'components.yaml#/components/parameters/cmHandleInPath'
+      - $ref: 'components.yaml#/components/parameters/cpsPathInQuery'
+    responses:
+      200:
+        $ref: 'components.yaml#/components/responses/Ok'
+      400:
+        $ref: 'components.yaml#/components/responses/BadRequest'
+      401:
+        $ref: 'components.yaml#/components/responses/Unauthorized'
+      403:
+        $ref: 'components.yaml#/components/responses/Forbidden'
+      404:
+        $ref: 'components.yaml#/components/responses/NotFound'
+
+nodesByCmHandleAndXpath:
+  patch:
+    description: Update node leaves for the given cps path and cm Handle
+    tags:
+      - nf-proxy
+    summary: Update node leaves
+    operationId: updateNodeLeaves
+    parameters:
+      - $ref: 'components.yaml#/components/parameters/cmHandleInPath'
+      - $ref: 'components.yaml#/components/parameters/xpathInQuery'
+    requestBody:
+      required: true
+      content:
+        application/json:
+          schema:
+            type: string
+    responses:
+      200:
+        $ref: 'components.yaml#/components/responses/Ok'
+      400:
+        $ref: 'components.yaml#/components/responses/BadRequest'
+      401:
+        $ref: 'components.yaml#/components/responses/Unauthorized'
+      403:
+        $ref: 'components.yaml#/components/responses/Forbidden'
+      404:
+        $ref: 'components.yaml#/components/responses/NotFound'
+
+  put:
+    description: Replace a node with descendants for the given cps path and cm Handle
+    tags:
+      - nf-proxy
+    summary: Replace a node with descendants
+    operationId: replaceNode
+    parameters:
+      - $ref: 'components.yaml#/components/parameters/cmHandleInPath'
+      - $ref: 'components.yaml#/components/parameters/xpathInQuery'
+    requestBody:
+      required: true
+      content:
+        application/json:
+          schema:
+            type: string
+    responses:
+      200:
+        $ref: 'components.yaml#/components/responses/Ok'
+      400:
+        $ref: 'components.yaml#/components/responses/BadRequest'
+      401:
+        $ref: 'components.yaml#/components/responses/Unauthorized'
+      403:
+        $ref: 'components.yaml#/components/responses/Forbidden'
+      404:
+        $ref: 'components.yaml#/components/responses/NotFound'
\ No newline at end of file
index 494e7f6..93ed065 100644 (file)
@@ -1,7 +1,7 @@
 /*
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2021 Pantheon.tech
- *  ================================================================================
+ *  Modifications (C) 2021 Nordix Foundation
  *  Modification Copyright (C) 2021 highstreet technologies GmbH
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
@@ -21,6 +21,9 @@
 
 package org.onap.cps.nfproxy.rest.controller;
 
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import java.util.Collection;
 import javax.validation.Valid;
 import org.onap.cps.nfproxy.api.NfProxyDataService;
 import org.onap.cps.nfproxy.rest.api.NfProxyApi;
@@ -38,6 +41,7 @@ import org.springframework.web.bind.annotation.RestController;
 @RequestMapping("${rest.api.xnf-base-path}")
 public class NfProxyController implements NfProxyApi {
 
+    private static final Gson GSON = new GsonBuilder().create();
     private static final String XPATH_ROOT = "/";
 
     @Autowired
@@ -45,7 +49,7 @@ public class NfProxyController implements NfProxyApi {
 
     @Override
     public ResponseEntity<Object> getNodeByCmHandleAndXpath(final String cmHandle, @Valid final String xpath,
-                                                            @Valid final Boolean includeDescendants) {
+        @Valid final Boolean includeDescendants) {
         if (XPATH_ROOT.equals(xpath)) {
             return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
         }
@@ -54,4 +58,24 @@ public class NfProxyController implements NfProxyApi {
         final DataNode dataNode = nfProxyDataService.getDataNode(cmHandle, xpath, fetchDescendantsOption);
         return new ResponseEntity<>(DataMapUtils.toDataMap(dataNode), HttpStatus.OK);
     }
+
+    @Override
+    public ResponseEntity<Object> queryNodesByCmHandleAndCpsPath(final String cmHandle, @Valid final String cpsPath) {
+        final Collection<DataNode> dataNodes = nfProxyDataService.queryDataNodes(cmHandle, cpsPath);
+        return new ResponseEntity<>(GSON.toJson(dataNodes), HttpStatus.OK);
+    }
+
+    @Override
+    public ResponseEntity<Object> replaceNode(@Valid final String jsonData, final String cmHandle,
+        @Valid final String parentNodeXpath) {
+        nfProxyDataService.replaceNodeTree(cmHandle, parentNodeXpath, jsonData);
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+    @Override
+    public ResponseEntity<Object> updateNodeLeaves(@Valid final String jsonData, final String cmHandle,
+        @Valid final String parentNodeXpath) {
+        nfProxyDataService.updateNodeLeaves(cmHandle, parentNodeXpath, jsonData);
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
 }
index 3cd6b9a..742a643 100644 (file)
@@ -1,8 +1,8 @@
 /*
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2021 Pantheon.tech
- *  ================================================================================
  *  Modification Copyright (C) 2021 highstreet technologies GmbH
+ *  Modification Copyright (C) 2021 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
 
 package org.onap.cps.nfproxy.rest.controller
 
+import com.google.common.collect.ImmutableMap
+import com.google.gson.Gson
 import org.onap.cps.nfproxy.api.NfProxyDataService
+import org.onap.cps.spi.model.DataNode
 import org.onap.cps.spi.model.DataNodeBuilder
 import org.spockframework.spring.SpringBean
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.beans.factory.annotation.Value
 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 spock.lang.Specification
-import spock.lang.Unroll
 
 import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*
 
 @WebMvcTest
 class NfProxyControllerSpec extends Specification {
@@ -53,14 +56,70 @@ class NfProxyControllerSpec extends Specification {
         dataNodeBaseEndpoint = "$basePath/v1"
     }
 
-    @Unroll
+    def cmHandle = 'some handle'
+    def xpath = 'some xpath'
+
+    def 'Query data node by cps path for the given cm handle.'() {
+        given: 'service method returns a list containing a data node'
+            def cpsPath = '/xpath/leaves[@leaf=\'value\']'
+            def dataNode = new DataNodeBuilder().withXpath("/xpath")
+                    .withLeaves(ImmutableMap.of("leaf", "value")).build()
+            ArrayList<DataNode> dataNodeList = new ArrayList();
+            dataNodeList.add(dataNode)
+            mockNfProxyDataService.queryDataNodes(cmHandle, cpsPath) >> dataNodeList
+        and: 'the query endpoint'
+            def dataNodeEndpoint = "$dataNodeBaseEndpoint/cm-handles/$cmHandle/nodes/query"
+        when: 'query data nodes API is invoked'
+            def response = mvc.perform(get(dataNodeEndpoint).param('cps-path', cpsPath)).andReturn().response
+        then: 'the response contains the the datanode in json format'
+            response.status == HttpStatus.OK.value()
+            def expectedJsonContent = new Gson().toJson(dataNode)
+            response.getContentAsString().contains(expectedJsonContent)
+    }
+
+    def 'Update data node leaves.'() {
+        given: 'json data'
+            def jsonData = 'json data'
+        and: 'the query endpoint'
+            def endpoint = "$dataNodeBaseEndpoint/cm-handles/$cmHandle/nodes"
+        when: 'patch request is performed'
+            def response = mvc.perform(
+                    patch(endpoint)
+                            .contentType(MediaType.APPLICATION_JSON)
+                            .content(jsonData)
+                            .param('xpath', xpath)
+            ).andReturn().response
+        then: 'the service method is invoked once with expected parameters'
+            1 * mockNfProxyDataService.updateNodeLeaves(cmHandle, xpath, jsonData)
+        and: 'response status indicates success'
+            response.status == HttpStatus.OK.value()
+    }
+
+    def 'Replace data node tree.'() {
+        given: 'json data'
+            def jsonData = 'json data'
+        and: 'the query endpoint'
+            def endpoint = "$dataNodeBaseEndpoint/cm-handles/$cmHandle/nodes"
+        when: 'put request is performed'
+            def response = mvc.perform(
+                    put(endpoint)
+                            .contentType(MediaType.APPLICATION_JSON)
+                            .content(jsonData)
+                            .param('xpath', xpath)
+            ).andReturn().response
+        then: 'the service method is invoked once with expected parameters'
+            1 * mockNfProxyDataService.replaceNodeTree(cmHandle, xpath, jsonData)
+        and: 'response status indicates success'
+            response.status == HttpStatus.OK.value()
+    }
+
     def 'Get data node.'() {
         given: 'the service returns a data node'
             def xpath = 'some xpath'
-            def cmHandle = 'some handle'
             def dataNode = new DataNodeBuilder().withXpath(xpath).withLeaves(["leaf": "value"]).build()
-            def endpoint = "$dataNodeBaseEndpoint/cm-handles/$cmHandle/node"
             mockNfProxyDataService.getDataNode(cmHandle, xpath, OMIT_DESCENDANTS) >> dataNode
+        and: 'the query endpoint'
+            def endpoint = "$dataNodeBaseEndpoint/cm-handles/$cmHandle/node"
         when: 'get request is performed through REST API'
             def response = mvc.perform(get(endpoint).param('xpath', xpath)).andReturn().response
         then: 'a success response is returned'
@@ -69,3 +128,4 @@ class NfProxyControllerSpec extends Specification {
             response.contentAsString.contains('"leaf":"value"')
     }
 }
+
index 593d6cf..a7f7fd6 100644 (file)
             <groupId>${project.groupId}</groupId>
             <artifactId>cps-service</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.spockframework</groupId>
+            <artifactId>spock-core</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 </project>
\ No newline at end of file
index 644dfab..ce47d70 100644 (file)
@@ -1,6 +1,7 @@
 /*
  * ============LICENSE_START=======================================================
  *  Copyright (C) 2021 highstreet technologies GmbH
+ *  Copyright (C) 2021 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -19,6 +20,7 @@
 
 package org.onap.cps.nfproxy.api;
 
+import java.util.Collection;
 import org.checkerframework.checker.nullness.qual.NonNull;
 import org.onap.cps.spi.FetchDescendantsOption;
 import org.onap.cps.spi.model.DataNode;
@@ -31,14 +33,44 @@ public interface NfProxyDataService {
     /**
      * Retrieves datanode by XPath for a given cm handle.
      *
-     * @param cmHandle               The identifier for a network function, network element, subnetwork or any other
-     *                               cm object by managed NF-Proxy
+     * @param cmHandle               The identifier for a network function, network element, subnetwork or any other cm
+     *                               object by managed NF-Proxy
      * @param xpath                  xpath
      * @param fetchDescendantsOption defines the scope of data to fetch: either single node or all the descendant nodes
      *                               (recursively) as well
      * @return data node object
      */
     DataNode getDataNode(@NonNull String cmHandle, @NonNull String xpath,
-                         @NonNull FetchDescendantsOption fetchDescendantsOption);
+        @NonNull FetchDescendantsOption fetchDescendantsOption);
+
+    /**
+     * Get datanodes for the given cm handle by cps path.
+     *
+     * @param cmHandle The identifier for a network function, network element, subnetwork or any other cm object by
+     *                 managed NF-Proxy
+     * @param cpsPath  cps path
+     * @return a collection of datanodes
+     */
+    Collection<DataNode> queryDataNodes(@NonNull String cmHandle, @NonNull String cpsPath);
+
+    /**
+     * Updates data node for given cm handle using xpath to parent node.
+     *
+     * @param cmHandle        The identifier for a network function, network element, subnetwork or any other cm object
+     *                        by managed NF-Proxy
+     * @param parentNodeXpath xpath to parent node
+     * @param jsonData        json data
+     */
+    void updateNodeLeaves(@NonNull String cmHandle, @NonNull String parentNodeXpath, @NonNull String jsonData);
+
+    /**
+     * Replaces existing data node content including descendants.
+     *
+     * @param cmHandle        The identifier for a network function, network element, subnetwork or any other cm object
+     *                        by managed NF-Proxy
+     * @param parentNodeXpath xpath to parent node
+     * @param jsonData        json data
+     */
+    void replaceNodeTree(@NonNull String cmHandle, @NonNull String parentNodeXpath, @NonNull String jsonData);
 
 }
index d30702e..bb15591 100755 (executable)
@@ -1,6 +1,7 @@
 /*
  * ============LICENSE_START=======================================================
  *  Copyright (C) 2021 highstreet technologies GmbH
+ *  Copyright (C) 2021 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -19,8 +20,9 @@
 
 package org.onap.cps.nfproxy.api.impl;
 
-import org.checkerframework.checker.nullness.qual.NonNull;
+import java.util.Collection;
 import org.onap.cps.api.CpsDataService;
+import org.onap.cps.api.CpsQueryService;
 import org.onap.cps.nfproxy.api.NfProxyDataService;
 import org.onap.cps.spi.FetchDescendantsOption;
 import org.onap.cps.spi.model.DataNode;
@@ -35,9 +37,27 @@ public class NfProxyDataServiceImpl implements NfProxyDataService {
     @Autowired
     private CpsDataService cpsDataService;
 
+    @Autowired
+    private CpsQueryService cpsQueryService;
+
     @Override
-    public DataNode getDataNode(@NonNull final String cmHandle, @NonNull final String xpath,
-                                @NonNull final FetchDescendantsOption fetchDescendantsOption) {
+    public DataNode getDataNode(final String cmHandle, final String xpath,
+        final FetchDescendantsOption fetchDescendantsOption) {
         return cpsDataService.getDataNode(NF_PROXY_DATASPACE_NAME, cmHandle, xpath, fetchDescendantsOption);
     }
+
+    @Override
+    public Collection<DataNode> queryDataNodes(final String cmHandle, final String cpsPath) {
+        return cpsQueryService.queryDataNodes(NF_PROXY_DATASPACE_NAME, cmHandle, cpsPath);
+    }
+
+    @Override
+    public void updateNodeLeaves(final String cmHandle, final String parentNodeXpath, final String jsonData) {
+        cpsDataService.updateNodeLeaves(NF_PROXY_DATASPACE_NAME, cmHandle, parentNodeXpath, jsonData);
+    }
+
+    @Override
+    public void replaceNodeTree(final String cmHandle, final String parentNodeXpath, final String jsonData) {
+        cpsDataService.replaceNodeTree(NF_PROXY_DATASPACE_NAME, cmHandle, parentNodeXpath, jsonData);
+    }
 }
diff --git a/cps-nf-proxy-service/src/test/groovy/org/onap/cps/api/impl/NfProxyDataServiceImplSpec.groovy b/cps-nf-proxy-service/src/test/groovy/org/onap/cps/api/impl/NfProxyDataServiceImplSpec.groovy
new file mode 100644 (file)
index 0000000..f89f80e
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.api.impl
+
+import org.onap.cps.api.CpsDataService
+import org.onap.cps.api.CpsQueryService
+import org.onap.cps.nfproxy.api.impl.NfProxyDataServiceImpl
+import spock.lang.Specification
+
+class NfProxyDataServiceImplSpec extends Specification {
+    def objectUnderTest = new NfProxyDataServiceImpl()
+    def mockcpsDataService = Mock(CpsDataService)
+    def mockcpsQueryService = Mock(CpsQueryService)
+
+    def setup() {
+        objectUnderTest.cpsDataService = mockcpsDataService
+        objectUnderTest.cpsQueryService = mockcpsQueryService
+    }
+
+    def cmHandle = 'some handle'
+    def expectedDataspaceName = 'NFP-Operational'
+
+    def 'Query data nodes by cps path.'() {
+        given: 'a cm Handle and a cps path'
+            def cpsPath = '/cps-path'
+        when: 'queryDataNodes is invoked'
+            objectUnderTest.queryDataNodes(cmHandle, cpsPath)
+        then: 'the persistence service is called once with the correct parameters'
+            1 * mockcpsQueryService.queryDataNodes(expectedDataspaceName, cmHandle, cpsPath)
+    }
+
+    def 'Update data node leaves.'() {
+        given: 'a cm Handle and a cps path'
+            def xpath = '/xpath'
+            def jsonData = 'some json'
+        when: 'updateNodeLeaves is invoked'
+            objectUnderTest.updateNodeLeaves(cmHandle, xpath, jsonData)
+        then: 'the persistence service is called once with the correct parameters'
+            1 * mockcpsDataService.updateNodeLeaves(expectedDataspaceName, cmHandle, xpath, jsonData)
+    }
+
+    def 'Replace data node tree.'() {
+        given: 'a cm Handle and a cps path'
+            def xpath = '/xpath'
+            def jsonData = 'some json'
+        when: 'replaceNodeTree is invoked'
+            objectUnderTest.replaceNodeTree(cmHandle, xpath, jsonData)
+        then: 'the persistence service is called once with the correct parameters'
+            1 * mockcpsDataService.replaceNodeTree(expectedDataspaceName, cmHandle, xpath, jsonData)
+    }
+}