Retrieve yang-resources for one or more modules 52/123052/11
authorniamhcore <niamh.core@est.tech>
Fri, 30 Jul 2021 15:25:16 +0000 (16:25 +0100)
committerniamhcore <niamh.core@est.tech>
Tue, 10 Aug 2021 12:33:52 +0000 (13:33 +0100)
Updating openapi to add a new rest endpoint
Updating restconf client to support post with json
Adding a ModuleResourceNotFound exception
Adding a test util class
Fixing merge conflict
Refactoring SDNC operations

Issue-ID: CPS-484
Signed-off-by: niamhcore <niamh.core@est.tech>
Change-Id: Id76dfe4cb12053771883e0271153d7bf7cd98548

18 files changed:
docs/openapi/components.yml
docs/openapi/openapi.yml
pom.xml
src/main/java/org/onap/cps/ncmp/dmi/exception/DmiExceptionHandler.java
src/main/java/org/onap/cps/ncmp/dmi/exception/ModuleResourceNotFoundException.java [new file with mode: 0644]
src/main/java/org/onap/cps/ncmp/dmi/model/ModuleReference.java [new file with mode: 0644]
src/main/java/org/onap/cps/ncmp/dmi/rest/controller/DmiRestController.java
src/main/java/org/onap/cps/ncmp/dmi/service/DmiService.java
src/main/java/org/onap/cps/ncmp/dmi/service/DmiServiceImpl.java
src/main/java/org/onap/cps/ncmp/dmi/service/client/SdncRestconfClient.java
src/main/java/org/onap/cps/ncmp/dmi/service/operation/SdncOperations.java
src/test/groovy/org/onap/cps/ncmp/dmi/rest/controller/DmiRestControllerSpec.groovy
src/test/groovy/org/onap/cps/ncmp/dmi/service/DmiServiceImplSpec.groovy
src/test/groovy/org/onap/cps/ncmp/dmi/service/client/SdncRestconfClientSpec.groovy
src/test/groovy/org/onap/cps/ncmp/dmi/service/operation/SdncOperationsSpec.groovy
src/test/java/org/onap/cps/ncmp/dmi/TestUtils.java
src/test/resources/GetModules.json [new file with mode: 0644]
src/test/resources/application.yml

index f38ed64..c67dad6 100644 (file)
@@ -19,6 +19,30 @@ components:
           items:
             type: string
 
+    ModuleRequestParent:
+      type: object
+      properties:
+        operation:
+          type: string
+          enum: [read]
+        data:
+          type: object
+          properties:
+            modules:
+              type: array
+              items:
+                type: object
+                properties:
+                  name:
+                    type: string
+                  revision:
+                    type: string
+        cmHandleProperties:
+          type: object
+          additionalProperties:
+            type: string
+            example: system-001
+
   responses:
     NotFound:
       description: The specified resource was not found
@@ -65,3 +89,12 @@ components:
     NoContent:
       description: No Content
       content: {}
+
+  parameters:
+    cmHandleInPath:
+      name: cmHandle
+      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
\ No newline at end of file
index 44747a9..f261c0d 100644 (file)
@@ -97,4 +97,29 @@ paths:
         '401':
           $ref: 'components.yml#/components/responses/Unauthorized'
         '403':
-          $ref: 'components.yml#/components/responses/Forbidden'
\ No newline at end of file
+          $ref: 'components.yml#/components/responses/Forbidden'
+
+  /v1/ch/{cmHandle}/moduleResources:
+    post:
+      description: Retrieve module resources for one or more modules
+      tags:
+        - dmi-plugin
+      summary: Retrieve module resources
+      operationId: retrieveModuleResources
+      parameters:
+        - $ref: 'components.yml#/components/parameters/cmHandleInPath'
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: 'components.yml#/components/schemas/ModuleRequestParent'
+      responses:
+        '200':
+          $ref: 'components.yml#/components/responses/Ok'
+        '400':
+          $ref: 'components.yml#/components/responses/BadRequest'
+        '401':
+          $ref: 'components.yml#/components/responses/Unauthorized'
+        '403':
+          $ref: 'components.yml#/components/responses/Forbidden'
diff --git a/pom.xml b/pom.xml
index fd1f189..ac00de8 100644 (file)
--- a/pom.xml
+++ b/pom.xml
             <groupId>io.micrometer</groupId>
             <artifactId>micrometer-registry-prometheus</artifactId>
         </dependency>
+        <dependency>
+            <groupId>net.minidev</groupId>
+            <artifactId>json-smart</artifactId>
+        </dependency>
     </dependencies>
     <build>
         <resources>
index a6ec6df..49db7d8 100644 (file)
@@ -46,7 +46,7 @@ public class DmiExceptionHandler {
         return buildErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR, exception);
     }
 
-    @ExceptionHandler({ModulesNotFoundException.class})
+    @ExceptionHandler({ModulesNotFoundException.class, ModuleResourceNotFoundException.class})
     public static ResponseEntity<Object> handleNotFoundExceptions(final DmiException exception) {
         return buildErrorResponse(HttpStatus.NOT_FOUND, exception);
     }
diff --git a/src/main/java/org/onap/cps/ncmp/dmi/exception/ModuleResourceNotFoundException.java b/src/main/java/org/onap/cps/ncmp/dmi/exception/ModuleResourceNotFoundException.java
new file mode 100644 (file)
index 0000000..65db271
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.ncmp.dmi.exception;
+
+public class ModuleResourceNotFoundException extends DmiException {
+
+    private static final long serialVersionUID = 4764849097602543408L;
+
+    private static final String ERROR_MESSAGE = "Module resource not found for given cmHandle: ";
+
+    /**
+     * Constructor.
+     *
+     * @param cmHandle the cm handle
+     * @param details the details of the error
+     */
+    public ModuleResourceNotFoundException(final String cmHandle, final String details) {
+        super(ERROR_MESSAGE + cmHandle, details);
+    }
+}
diff --git a/src/main/java/org/onap/cps/ncmp/dmi/model/ModuleReference.java b/src/main/java/org/onap/cps/ncmp/dmi/model/ModuleReference.java
new file mode 100644 (file)
index 0000000..cb9b7cb
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.ncmp.dmi.model;
+
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * Module Reference.
+ */
+@Getter
+@Setter
+@EqualsAndHashCode
+public class ModuleReference {
+
+    private String name;
+    private String revision;
+}
index 0e1d3d6..5725f09 100644 (file)
 
 package org.onap.cps.ncmp.dmi.rest.controller;
 
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import java.util.List;
 import javax.validation.Valid;
 import lombok.extern.slf4j.Slf4j;
 import org.onap.cps.ncmp.dmi.model.CmHandles;
+import org.onap.cps.ncmp.dmi.model.ModuleReference;
+import org.onap.cps.ncmp.dmi.model.ModuleRequestParent;
 import org.onap.cps.ncmp.dmi.rest.api.DmiPluginApi;
 import org.onap.cps.ncmp.dmi.rest.api.DmiPluginInternalApi;
 import org.onap.cps.ncmp.dmi.service.DmiService;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.RequestMapping;
@@ -40,9 +43,11 @@ public class DmiRestController implements DmiPluginApi, DmiPluginInternalApi {
 
     private DmiService dmiService;
 
-    @Autowired
-    public DmiRestController(final DmiService dmiService) {
+    private ObjectMapper objectMapper;
+
+    public DmiRestController(final DmiService dmiService, final ObjectMapper objectMapper) {
         this.dmiService = dmiService;
+        this.objectMapper = objectMapper;
     }
 
     @Override
@@ -52,11 +57,25 @@ public class DmiRestController implements DmiPluginApi, DmiPluginInternalApi {
         return new ResponseEntity<>(modulesListAsJson, HttpStatus.OK);
     }
 
+    @Override
+    public ResponseEntity<Object> retrieveModuleResources(@Valid final ModuleRequestParent moduleRequestParent,
+        final String cmHandle) {
+        if (moduleRequestParent.getOperation().toString().equals("read")) {
+            final var moduleReferenceList = convertRestObjectToJavaApiObject(moduleRequestParent);
+            final var response = dmiService.getModuleResources(cmHandle, moduleReferenceList);
+            if (response.isEmpty()) {
+                return new ResponseEntity<>(response, HttpStatus.NOT_FOUND);
+            }
+            return new ResponseEntity<>(response, HttpStatus.OK);
+        }
+        return new ResponseEntity<>("Unsupported operation", HttpStatus.CONFLICT);
+    }
+
     /**
      * This method register given list of cm-handles to ncmp.
      *
      * @param cmHandles list of cm-handles
-     * @return (@code ResponseEntity) response entity
+     * @return (@ code ResponseEntity) response entity
      */
     public ResponseEntity<String> registerCmHandles(final @Valid CmHandles cmHandles) {
         final List<String> cmHandlesList = cmHandles.getCmHandles();
@@ -66,4 +85,9 @@ public class DmiRestController implements DmiPluginApi, DmiPluginInternalApi {
         dmiService.registerCmHandles(cmHandlesList);
         return new ResponseEntity<>("cm-handle registered successfully.", HttpStatus.CREATED);
     }
+
+    private List<ModuleReference> convertRestObjectToJavaApiObject(final ModuleRequestParent moduleRequestParent) {
+        return objectMapper
+            .convertValue(moduleRequestParent.getData().getModules(), new TypeReference<List<ModuleReference>>() {});
+    }
 }
index d9196ec..aeff3dc 100644 (file)
@@ -22,6 +22,7 @@ package org.onap.cps.ncmp.dmi.service;
 
 import java.util.List;
 import org.onap.cps.ncmp.dmi.exception.DmiException;
+import org.onap.cps.ncmp.dmi.model.ModuleReference;
 
 /**
  * Interface for handling Dmi plugin Data.
@@ -45,4 +46,12 @@ public interface DmiService {
      */
     void registerCmHandles(List<String> cmHandles);
 
+    /**
+     * Get module resources for the given cm handle and modules.
+     *
+     * @param cmHandle cmHandle
+     * @param modules a list of module data
+     * @return returns all module resources
+     */
+    String getModuleResources(String cmHandle, List<ModuleReference> modules);
 }
index 990a421..bf0689c 100644 (file)
@@ -23,15 +23,19 @@ package org.onap.cps.ncmp.dmi.service;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import java.util.ArrayList;
+import java.util.LinkedHashMap;
 import java.util.List;
 import lombok.extern.slf4j.Slf4j;
+import net.minidev.json.JSONArray;
 import org.apache.groovy.parser.antlr4.util.StringUtils;
 import org.onap.cps.ncmp.dmi.config.DmiPluginConfig.DmiPluginProperties;
 import org.onap.cps.ncmp.dmi.exception.CmHandleRegistrationException;
 import org.onap.cps.ncmp.dmi.exception.DmiException;
+import org.onap.cps.ncmp.dmi.exception.ModuleResourceNotFoundException;
 import org.onap.cps.ncmp.dmi.exception.ModulesNotFoundException;
 import org.onap.cps.ncmp.dmi.model.CmHandleOperation;
 import org.onap.cps.ncmp.dmi.model.CreatedCmHandle;
+import org.onap.cps.ncmp.dmi.model.ModuleReference;
 import org.onap.cps.ncmp.dmi.service.client.NcmpRestClient;
 import org.onap.cps.ncmp.dmi.service.operation.SdncOperations;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -39,7 +43,6 @@ import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.stereotype.Service;
 
-
 @Service
 @Slf4j
 public class DmiServiceImpl implements DmiService {
@@ -53,19 +56,19 @@ public class DmiServiceImpl implements DmiService {
      * Constructor.
      *
      * @param dmiPluginProperties dmiPluginProperties
-     * @param ncmpRestClient ncmpRestClient
-     * @param objectMapper objectMapper
-     * @param sdncOperations sdncOperations
+     * @param ncmpRestClient      ncmpRestClient
+     * @param sdncOperations      sdncOperations
+     * @param objectMapper        objectMapper
      */
     @Autowired
     public DmiServiceImpl(final DmiPluginProperties dmiPluginProperties,
-                          final NcmpRestClient ncmpRestClient,
-                          final ObjectMapper objectMapper,
-                          final SdncOperations sdncOperations) {
+        final NcmpRestClient ncmpRestClient,
+        final SdncOperations sdncOperations, final ObjectMapper objectMapper) {
         this.dmiPluginProperties = dmiPluginProperties;
         this.ncmpRestClient = ncmpRestClient;
         this.objectMapper = objectMapper;
         this.sdncOperations = sdncOperations;
+        this.objectMapper = objectMapper;
     }
 
     @Override
@@ -79,16 +82,33 @@ public class DmiServiceImpl implements DmiService {
             return responseBody;
         } else {
             throw new DmiException("SDNC is not able to process request.",
-                    "response code : " + responseEntity.getStatusCode() + " message : " + responseEntity.getBody());
+                "response code : " + responseEntity.getStatusCode() + " message : " + responseEntity.getBody());
         }
     }
 
+    @Override
+    public String getModuleResources(final String cmHandle, final List<ModuleReference> moduleReferences) {
+        final JSONArray getModuleResponses = new JSONArray();
+        for (final var moduleReference : moduleReferences) {
+            final var moduleRequest = createModuleRequest(moduleReference);
+            final var responseEntity = sdncOperations.getModuleResource(cmHandle, moduleRequest);
+            if (responseEntity.getStatusCode() == HttpStatus.OK) {
+                getModuleResponses.add(responseEntity.getBody());
+            } else {
+                log.error("SDNC did not return a module resource for the given cmHandle {}", cmHandle);
+                throw new ModuleResourceNotFoundException(cmHandle,
+                    "SDNC did not return a module resource for the given cmHandle.");
+            }
+        }
+        return getModuleResponses.toJSONString();
+    }
+
     @Override
     public void registerCmHandles(final List<String> cmHandles) {
         final CmHandleOperation cmHandleOperation = new CmHandleOperation();
         cmHandleOperation.setDmiPlugin(dmiPluginProperties.getDmiServiceName());
         final List<CreatedCmHandle> createdCmHandleList = new ArrayList<>();
-        for (final String cmHandle: cmHandles) {
+        for (final String cmHandle : cmHandles) {
             final CreatedCmHandle createdCmHandle = new CreatedCmHandle();
             createdCmHandle.setCmHandle(cmHandle);
             createdCmHandleList.add(createdCmHandle);
@@ -100,7 +120,7 @@ public class DmiServiceImpl implements DmiService {
         } catch (final JsonProcessingException e) {
             log.error("Parsing error occurred while converting cm-handles to JSON {}", cmHandles);
             throw new DmiException("Internal Server Error.",
-                    "Parsing error occurred while converting given cm-handles object list to JSON ");
+                "Parsing error occurred while converting given cm-handles object list to JSON ");
         }
         final ResponseEntity<String> responseEntity = ncmpRestClient.registerCmHandlesWithNcmp(cmHandlesJson);
         if (!(responseEntity.getStatusCode() == HttpStatus.CREATED)) {
@@ -108,4 +128,20 @@ public class DmiServiceImpl implements DmiService {
         }
     }
 
+    private String createModuleRequest(final ModuleReference moduleReference) {
+        final var ietfNetconfModuleReferences = new LinkedHashMap<>();
+        ietfNetconfModuleReferences.put("ietf-netconf-monitoring:identifier", moduleReference.getName());
+        ietfNetconfModuleReferences.put("ietf-netconf-monitoring:version", moduleReference.getRevision());
+        final var writer = objectMapper.writer().withRootName("ietf-netconf-monitoring:input");
+        final String moduleRequest;
+        try {
+            moduleRequest = writer.writeValueAsString(ietfNetconfModuleReferences);
+        } catch (final JsonProcessingException e) {
+            log.error("JSON exception occurred when creating the module request for the given module reference {}",
+                moduleReference.getName());
+            throw new DmiException("Unable to process JSON.",
+                "JSON exception occurred when creating the module request.", e);
+        }
+        return moduleRequest;
+    }
 }
index cf7c50a..adac5e6 100644 (file)
@@ -30,6 +30,7 @@ import org.springframework.web.client.RestTemplate;
 
 @Component
 public class SdncRestconfClient {
+
     private SdncProperties sdncProperties;
     private RestTemplate restTemplate;
 
@@ -42,16 +43,34 @@ public class SdncRestconfClient {
      * restconf get operation on sdnc.
      *
      * @param getResourceUrl sdnc get url
-     *
      * @return the response entity
      */
     public ResponseEntity<String> getOperation(final String getResourceUrl) {
         final String sdncBaseUrl = sdncProperties.getBaseUrl();
         final String sdncRestconfUrl = sdncBaseUrl.concat(getResourceUrl);
+        final var httpEntity = new HttpEntity<>(configureHttpHeaders());
+        return restTemplate.getForEntity(sdncRestconfUrl, String.class, httpEntity);
+    }
+
+    /**
+     * restconf post operation on sdnc.
+     *
+     * @param postResourceUrl sdnc post resource url
+     * @param jsonData        json data
+     * @return the response entity
+     */
+    public ResponseEntity<String> postOperationWithJsonData(final String postResourceUrl,
+        final String jsonData) {
+        final var sdncBaseUrl = sdncProperties.getBaseUrl();
+        final var sdncRestconfUrl = sdncBaseUrl.concat(postResourceUrl);
+        final var httpEntity = new HttpEntity<>(jsonData, configureHttpHeaders());
+        return restTemplate.postForEntity(sdncRestconfUrl, httpEntity, String.class);
+    }
+
+    private HttpHeaders configureHttpHeaders() {
         final var httpHeaders = new HttpHeaders();
         httpHeaders.setBasicAuth(sdncProperties.getAuthUsername(), sdncProperties.getAuthPassword());
-        httpHeaders.set(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON.toString());
-        final var httpEntity = new HttpEntity<>(httpHeaders);
-        return restTemplate.getForEntity(sdncRestconfUrl, String.class, httpEntity);
+        httpHeaders.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
+        return httpHeaders;
     }
 }
index 4e4e721..0d1c343 100644 (file)
@@ -20,7 +20,6 @@
 
 package org.onap.cps.ncmp.dmi.service.operation;
 
-import org.jetbrains.annotations.NotNull;
 import org.onap.cps.ncmp.dmi.config.DmiConfiguration.SdncProperties;
 import org.onap.cps.ncmp.dmi.service.client.SdncRestconfClient;
 import org.springframework.http.ResponseEntity;
@@ -29,29 +28,32 @@ import org.springframework.stereotype.Component;
 @Component
 public class SdncOperations {
 
-    private static final String TOPOLOGY_URL_TEMPLATE = "/rests/data/network-topology:network-topology"
-            + "/topology={topologyId}";
+
+    private static final String TOPOLOGY_URL_TEMPLATE_DATA =
+        "/rests/data/network-topology:network-topology/topology={topologyId}";
+    private static final String TOPOLOGY_URL_TEMPLATE_OPERATIONAL =
+        "/rests/operations/network-topology:network-topology/topology={topologyId}";
     private static final String MOUNT_URL_TEMPLATE = "/node={nodeId}/yang-ext:mount";
     private static final String GET_SCHEMA_URL = "/ietf-netconf-monitoring:netconf-state/schemas";
+    private static final String GET_SCHEMA_SOURCES_URL = "/ietf-netconf-monitoring:get-schema";
 
     private SdncProperties sdncProperties;
     private SdncRestconfClient sdncRestconfClient;
-    private final String topologyUrl;
-    private final String topologyMountUrlTemplate;
+    private final String topologyUrlData;
+    private final String topologyUrlOperational;
 
     /**
-     * Constructor for {@code SdncOperations}. This method also manipulates
-     * url properties.
+     * Constructor for {@code SdncOperations}. This method also manipulates url properties.
      *
-     * @param sdncProperties {@code SdncProperties}
+     * @param sdncProperties     {@code SdncProperties}
      * @param sdncRestconfClient {@code SdncRestconfClient}
      */
-
     public SdncOperations(final SdncProperties sdncProperties, final SdncRestconfClient sdncRestconfClient) {
         this.sdncProperties = sdncProperties;
         this.sdncRestconfClient = sdncRestconfClient;
-        topologyUrl = TOPOLOGY_URL_TEMPLATE.replace("{topologyId}", this.sdncProperties.getTopologyId());
-        topologyMountUrlTemplate = topologyUrl + MOUNT_URL_TEMPLATE;
+        topologyUrlOperational =
+            TOPOLOGY_URL_TEMPLATE_OPERATIONAL.replace("{topologyId}", this.sdncProperties.getTopologyId());
+        topologyUrlData = TOPOLOGY_URL_TEMPLATE_DATA.replace("{topologyId}", this.sdncProperties.getTopologyId());
     }
 
     /**
@@ -65,11 +67,28 @@ public class SdncOperations {
         return sdncRestconfClient.getOperation(urlWithNodeId);
     }
 
-    @NotNull
+    /**
+     * Get module schema.
+     *
+     * @param nodeId           node ID
+     * @param moduleProperties module properties
+     * @return response entity
+     */
+    public ResponseEntity<String> getModuleResource(final String nodeId, final String moduleProperties) {
+        final String getYangResourceUrl = prepareGetOperationSchemaUrl(nodeId);
+        return sdncRestconfClient.postOperationWithJsonData(getYangResourceUrl, moduleProperties);
+    }
+
     private String prepareGetSchemaUrl(final String nodeId) {
-        final String topologyMountUrl = topologyMountUrlTemplate;
+        final var topologyMountUrl = topologyUrlData + MOUNT_URL_TEMPLATE;
         final String topologyMountUrlWithNodeId = topologyMountUrl.replace("{nodeId}", nodeId);
         final String resourceUrl = topologyMountUrlWithNodeId.concat(GET_SCHEMA_URL);
         return resourceUrl;
     }
+
+    private String prepareGetOperationSchemaUrl(final String nodeId) {
+        final var topologyMountUrl = topologyUrlOperational + MOUNT_URL_TEMPLATE;
+        final var topologyMountUrlWithNodeId = topologyMountUrl.replace("{nodeId}", nodeId);
+        return topologyMountUrlWithNodeId.concat(GET_SCHEMA_SOURCES_URL);
+    }
 }
index 993b80c..03bffe4 100644 (file)
 
 package org.onap.cps.ncmp.dmi.rest.controller
 
+import org.onap.cps.ncmp.dmi.TestUtils
 import org.onap.cps.ncmp.dmi.exception.DmiException
+import org.onap.cps.ncmp.dmi.exception.ModuleResourceNotFoundException
 import org.onap.cps.ncmp.dmi.exception.ModulesNotFoundException
+import org.onap.cps.ncmp.dmi.model.ModuleReference
 import org.onap.cps.ncmp.dmi.service.DmiService
 import org.spockframework.spring.SpringBean
 import org.springframework.beans.factory.annotation.Autowired
@@ -55,7 +58,7 @@ class DmiRestControllerSpec extends Specification {
             def someJson = 'some-json'
             mockDmiService.getModulesForCmHandle('node1') >> someJson
         when: 'post is being called'
-            def response = mvc.perform( post(getModuleUrl)
+            def response = mvc.perform(post(getModuleUrl)
                     .contentType(MediaType.APPLICATION_JSON))
                     .andReturn().response
         then: 'status is OK'
@@ -70,16 +73,16 @@ class DmiRestControllerSpec extends Specification {
         and: 'get modules for cm-handle throws #exceptionClass'
             mockDmiService.getModulesForCmHandle('node1') >> { throw Mock(exceptionClass) }
         when: 'post is invoked'
-            def response = mvc.perform( post(getModuleUrl)
+            def response = mvc.perform(post(getModuleUrl)
                     .contentType(MediaType.APPLICATION_JSON))
                     .andReturn().response
         then: 'response status is #expectedResponse'
             response.status == expectedResponse
         where: 'the scenario is #scenario'
-            scenario                       |  exceptionClass                 || expectedResponse
-            'dmi service exception'        |  DmiException.class             || HttpStatus.INTERNAL_SERVER_ERROR.value()
-            'no modules found'             |  ModulesNotFoundException.class || HttpStatus.NOT_FOUND.value()
-            'any other runtime exception'  |  RuntimeException.class         || HttpStatus.INTERNAL_SERVER_ERROR.value()
+            scenario                      | exceptionClass                 || expectedResponse
+            'dmi service exception'       | DmiException.class             || HttpStatus.INTERNAL_SERVER_ERROR.value()
+            'no modules found'            | ModulesNotFoundException.class || HttpStatus.NOT_FOUND.value()
+            'any other runtime exception' | RuntimeException.class         || HttpStatus.INTERNAL_SERVER_ERROR.value()
     }
 
     def 'Register given list of cm handles.'() {
@@ -112,4 +115,41 @@ class DmiRestControllerSpec extends Specification {
         and: 'dmi service is not called'
             0 * mockDmiService.registerCmHandles(_)
     }
+
+    def 'Retrieve module resources.'() {
+        given: 'an endpoint and json data'
+            def getModulesEndpoint = "$basePathV1/ch/some-cm-handle/moduleResources"
+            def jsonData = TestUtils.getResourceFileContent('GetModules.json')
+        and: 'the DMI service returns some json data'
+            ModuleReference moduleReference1 = new ModuleReference()
+            moduleReference1.name = 'ietf-yang-library'
+            moduleReference1.revision = '2016-06-21'
+            ModuleReference moduleReference2 = new ModuleReference()
+            moduleReference2.name = 'nc-notifications'
+            moduleReference2.revision = '2008-07-14'
+            def moduleReferences = [moduleReference1, moduleReference2]
+            mockDmiService.getModuleResources('some-cm-handle', moduleReferences ) >> '{some-json}'
+        when: 'get module resource api is invoked'
+            def response = mvc.perform(post(getModulesEndpoint)
+                    .contentType(MediaType.APPLICATION_JSON)
+                    .content(jsonData)).andReturn().response
+        then:'a OK status is returned'
+            response.status == HttpStatus.OK.value()
+        and: 'the expected response is returned'
+            response.getContentAsString() == '{some-json}'
+    }
+
+    def 'Retrieve module resources with exception handling.'() {
+        given: 'an endpoint and json data'
+            def getModulesEndpoint = "$basePathV1/ch/some-cm-handle/moduleResources"
+            def jsonData = TestUtils.getResourceFileContent('GetModules.json')
+        and: 'the service method is invoked to get module resources and throws an exception'
+            mockDmiService.getModuleResources('some-cm-handle', _) >> { throw Mock(ModuleResourceNotFoundException.class) }
+        when: 'get module resource api is invoked'
+            def response = mvc.perform(post(getModulesEndpoint)
+                    .contentType(MediaType.APPLICATION_JSON)
+                    .content(jsonData)).andReturn().response
+        then: 'a not found status is returned'
+            response.status == HttpStatus.NOT_FOUND.value()
+    }
 }
index 9d6bc35..1854b24 100644 (file)
@@ -25,7 +25,9 @@ import com.fasterxml.jackson.databind.ObjectMapper
 import org.onap.cps.ncmp.dmi.config.DmiPluginConfig
 import org.onap.cps.ncmp.dmi.exception.CmHandleRegistrationException
 import org.onap.cps.ncmp.dmi.exception.DmiException
+import org.onap.cps.ncmp.dmi.exception.ModuleResourceNotFoundException
 import org.onap.cps.ncmp.dmi.exception.ModulesNotFoundException
+import org.onap.cps.ncmp.dmi.model.ModuleReference
 import org.onap.cps.ncmp.dmi.service.client.NcmpRestClient
 import org.onap.cps.ncmp.dmi.service.operation.SdncOperations
 import org.springframework.http.HttpStatus
@@ -40,7 +42,7 @@ class DmiServiceImplSpec extends Specification {
     def objectMapper = new ObjectMapper()
     def mockObjectMapper = Mock(ObjectMapper)
     def mockSdncOperations = Mock(SdncOperations)
-    def objectUnderTest = new DmiServiceImpl(mockDmiPluginProperties, mockNcmpRestClient, objectMapper, mockSdncOperations)
+    def objectUnderTest = new DmiServiceImpl(mockDmiPluginProperties, mockNcmpRestClient, mockSdncOperations, objectMapper)
 
     def 'Call get modules for cm-handle on dmi Service.'() {
         given: 'cm handle id'
@@ -62,7 +64,7 @@ class DmiServiceImplSpec extends Specification {
         when: 'get modules for cm-handle is called'
             objectUnderTest.getModulesForCmHandle(cmHandle)
         then: 'dmi exception is thrown'
-            thrown( DmiException )
+            thrown(DmiException)
     }
 
     def 'Call get modules for cm-handle and SDNC returns OK with empty body.'() {
@@ -73,7 +75,7 @@ class DmiServiceImplSpec extends Specification {
         when: 'get modules for cm-handle is called'
             objectUnderTest.getModulesForCmHandle(cmHandle)
         then: 'ModulesNotFoundException is thrown'
-            thrown( ModulesNotFoundException )
+            thrown(ModulesNotFoundException)
     }
 
     def 'Register cm handles with ncmp.'() {
@@ -100,9 +102,9 @@ class DmiServiceImplSpec extends Specification {
         then: 'a registration exception is thrown'
             thrown(CmHandleRegistrationException.class)
         where: 'given #scenario'
-            scenario                                        |   responseEntity
-            'ncmp rest client returns bad request'          |   new ResponseEntity<>(HttpStatus.BAD_REQUEST)
-            'ncmp rest client returns internal server error'|   new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR)
+            scenario                                         | responseEntity
+            'ncmp rest client returns bad request'           | new ResponseEntity<>(HttpStatus.BAD_REQUEST)
+            'ncmp rest client returns internal server error' | new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR)
     }
 
     def 'Register cm handles with ncmp with wrong data.'() {
@@ -116,4 +118,34 @@ class DmiServiceImplSpec extends Specification {
         then: 'a dmi exception is thrown'
             thrown(DmiException.class)
     }
+
+    def 'Get module resources.'() {
+        given: 'cmHandle, expected jsons and module reference list'
+            def cmHandle = 'some-cmHandle'
+            def excpectedModulesJson1 = '{"ietf-netconf-monitoring:input":{"ietf-netconf-monitoring:identifier":"mRef1","ietf-netconf-monitoring:version":"mRefV1"}}'
+            def excpectedModulesJson2 = '{"ietf-netconf-monitoring:input":{"ietf-netconf-monitoring:identifier":"mRef2","ietf-netconf-monitoring:version":"mRefV2"}}'
+            def mRef1 = Mock(ModuleReference.class)
+            def mRef2 = Mock(ModuleReference.class)
+            mRef1.getName() >> 'mRef1'
+            mRef1.getRevision() >> 'mRefV1'
+            mRef2.getName() >> 'mRef2'
+            mRef2.getRevision() >> 'mRefV2'
+            def moduleList = [mRef1, mRef2] as LinkedList<ModuleReference>
+        when: 'get module resources is invoked with the given cm handle and a module list'
+            def response = objectUnderTest.getModuleResources(cmHandle, moduleList)
+        then: 'then get modules resources called correctly'
+            1 * mockSdncOperations.getModuleResource(cmHandle,excpectedModulesJson1) >> new ResponseEntity<String>('response-body1', HttpStatus.OK)
+            1 * mockSdncOperations.getModuleResource(cmHandle,excpectedModulesJson2) >> new ResponseEntity<String>('response-body2', HttpStatus.OK)
+        then: 'the response contains the expected response body'
+            response.contains('["response-body1","response-body2"]')
+    }
+
+    def 'Get module resources for a failed get module schema request.'() {
+        given: 'get module schema is invoked and returns not found'
+            mockSdncOperations.getModuleResource(_ as String, _ as String) >> new ResponseEntity<String>('some-response-body', HttpStatus.BAD_REQUEST)
+        when: 'get module resources is invoked with the given cm handle and a module list'
+            objectUnderTest.getModuleResources('some-cmHandle', [new ModuleReference()] as LinkedList<ModuleReference> )
+        then: 'ModuleResourceNotFoundException is thrown'
+            thrown(ModuleResourceNotFoundException)
+    }
 }
index 0b192f0..6d9445c 100644 (file)
@@ -22,7 +22,6 @@ package org.onap.cps.ncmp.dmi.service.client
 
 import org.onap.cps.ncmp.dmi.config.DmiConfiguration
 import org.springframework.http.HttpEntity
-import org.springframework.http.HttpMethod
 import org.springframework.http.ResponseEntity
 import org.springframework.web.client.RestTemplate
 import spock.lang.Specification
@@ -37,16 +36,38 @@ class SdncRestconfClientSpec extends Specification {
         given: 'a get url'
             def getResourceUrl = '/getResourceUrl'
         and: 'sdnc properties'
-            mockSdncProperties.baseUrl >> 'http://test-sdnc-uri'
-            mockSdncProperties.authUsername >> 'test-username'
-            mockSdncProperties.authPassword >> 'test-password'
-            mockSdncProperties.topologyId >> 'testTopologyId'
+            setupTestConfigurationData()
         and: 'the rest template returns a valid response entity'
             def mockResponseEntity = Mock(ResponseEntity)
-            mockRestTemplate.getForEntity({ it.toString() == 'http://test-sdnc-uri/getResourceUrl' }, String.class, _ as HttpEntity) >> mockResponseEntity
+            mockRestTemplate.getForEntity({ it.toString() == 'http://some-uri/getResourceUrl' }, String.class, _ as HttpEntity) >> mockResponseEntity
         when: 'GET operation is invoked'
             def result = objectUnderTest.getOperation(getResourceUrl)
         then: 'the output of the method is equal to the output from the test template'
             result == mockResponseEntity
     }
+
+    def 'SDNC POST operation called.'() {
+        given: 'json data'
+            def jsonData = 'some-json'
+        and: 'a url for get module resources'
+            def getModuleResourceUrl = '/getModuleResourceUrl'
+        and: 'configuration data'
+            setupTestConfigurationData()
+        and: 'the rest template returns a valid response entity'
+            def mockResponseEntity = Mock(ResponseEntity)
+        when: 'get module resources is invoked'
+            def result = objectUnderTest.postOperationWithJsonData(getModuleResourceUrl, jsonData)
+        then: 'the rest template is called with the correct uri and json in the body'
+            1 * mockRestTemplate.postForEntity({ it.toString() == 'http://some-uri/getModuleResourceUrl' },
+                    { it.body.contains(jsonData) }, String.class) >> mockResponseEntity
+        and: 'the output of the method is the same as the output from the test template'
+            result == mockResponseEntity
+    }
+
+    def setupTestConfigurationData() {
+        mockSdncProperties.baseUrl >> 'http://some-uri'
+        mockSdncProperties.authUsername >> 'some-username'
+        mockSdncProperties.authPassword >> 'some-password'
+        mockSdncProperties.topologyId >> 'some-topology-id'
+    }
 }
\ No newline at end of file
index 956834a..9b07d68 100644 (file)
@@ -22,23 +22,38 @@ package org.onap.cps.ncmp.dmi.service.operation
 
 import org.onap.cps.ncmp.dmi.config.DmiConfiguration
 import org.onap.cps.ncmp.dmi.service.client.SdncRestconfClient
-import org.springframework.http.HttpStatus
-import org.springframework.http.ResponseEntity
+import org.spockframework.spring.SpringBean
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.context.SpringBootTest
+import org.springframework.test.context.ContextConfiguration
 import spock.lang.Specification
 
-class SdncOperationsSpec extends  Specification {
-    def mockSdncProperties = Mock(DmiConfiguration.SdncProperties)
-    def mockSdncRestClient = Mock(SdncRestconfClient)
+@SpringBootTest
+@ContextConfiguration(classes = [DmiConfiguration.SdncProperties, SdncOperations])
+class SdncOperationsSpec extends Specification {
+
+    @SpringBean
+    SdncRestconfClient mockSdncRestClient = Mock()
+    @Autowired
+    SdncOperations objectUnderTest
 
     def 'call get modules from node to SDNC.'() {
-        given: 'nodeid, topology-id, responseentity'
+        given: 'node id and url'
             def nodeId = 'node1'
             def expectedUrl = '/rests/data/network-topology:network-topology/topology=test-topology/node=node1/yang-ext:mount/ietf-netconf-monitoring:netconf-state/schemas'
-            mockSdncProperties.getTopologyId() >> 'test-topology'
-            def objectUnderTest = new SdncOperations(mockSdncProperties, mockSdncRestClient)
         when: 'called get modules from node'
             objectUnderTest.getModulesFromNode(nodeId)
         then: 'the get operation is executed with the correct URL'
             1 * mockSdncRestClient.getOperation(expectedUrl)
     }
+
+    def 'Get module resources from SDNC.'() {
+        given: 'node id and url'
+            def nodeId = 'some-node'
+            def expectedUrl = '/rests/operations/network-topology:network-topology/topology=test-topology/node=some-node/yang-ext:mount/ietf-netconf-monitoring:get-schema'
+        when: 'get module resources is called with the expected parameters'
+            objectUnderTest.getModuleResource(nodeId, 'some-json-data')
+        then: 'the SDNC Rest client is invoked with the correct URL and json data'
+            1 * mockSdncRestClient.postOperationWithJsonData(expectedUrl, 'some-json-data')
+    }
 }
index b82a6f5..c10d91a 100644 (file)
@@ -7,6 +7,7 @@
  *  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.
diff --git a/src/test/resources/GetModules.json b/src/test/resources/GetModules.json
new file mode 100644 (file)
index 0000000..23fe77c
--- /dev/null
@@ -0,0 +1,18 @@
+{
+  "operation": "read",
+  "data": {
+    "modules": [
+      {
+        "name": "ietf-yang-library",
+        "revision": "2016-06-21"
+      },
+      {
+        "name": "nc-notifications",
+        "revision": "2008-07-14"
+      }
+    ]
+  },
+  "cmHandleProperties": {
+    "subsystemId": "system-001"
+  }
+}
\ No newline at end of file
index 8ef864b..b7c5bab 100644 (file)
@@ -24,3 +24,10 @@ security:
   auth:
     username: cpsuser
     password: cpsr0cks!
+
+sdnc:
+  baseUrl: http://test
+  topologyId: test-topology
+  auth:
+    username: test
+    password: test