From 3139ece993c68ce7e40d166acdd5d9572a3a8a1e Mon Sep 17 00:00:00 2001 From: niamhcore Date: Fri, 30 Jul 2021 16:25:16 +0100 Subject: Retrieve yang-resources for one or more modules 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 Change-Id: Id76dfe4cb12053771883e0271153d7bf7cd98548 --- docs/openapi/components.yml | 33 +++++++++++++ docs/openapi/openapi.yml | 27 ++++++++++- pom.xml | 4 ++ .../ncmp/dmi/exception/DmiExceptionHandler.java | 2 +- .../exception/ModuleResourceNotFoundException.java | 38 +++++++++++++++ .../onap/cps/ncmp/dmi/model/ModuleReference.java | 37 ++++++++++++++ .../dmi/rest/controller/DmiRestController.java | 32 +++++++++++-- .../org/onap/cps/ncmp/dmi/service/DmiService.java | 9 ++++ .../onap/cps/ncmp/dmi/service/DmiServiceImpl.java | 56 ++++++++++++++++++---- .../dmi/service/client/SdncRestconfClient.java | 27 +++++++++-- .../ncmp/dmi/service/operation/SdncOperations.java | 45 ++++++++++++----- .../rest/controller/DmiRestControllerSpec.groovy | 52 +++++++++++++++++--- .../cps/ncmp/dmi/service/DmiServiceImplSpec.groovy | 44 ++++++++++++++--- .../service/client/SdncRestconfClientSpec.groovy | 33 ++++++++++--- .../service/operation/SdncOperationsSpec.groovy | 31 ++++++++---- src/test/java/org/onap/cps/ncmp/dmi/TestUtils.java | 1 + src/test/resources/GetModules.json | 18 +++++++ src/test/resources/application.yml | 7 +++ 18 files changed, 437 insertions(+), 59 deletions(-) create mode 100644 src/main/java/org/onap/cps/ncmp/dmi/exception/ModuleResourceNotFoundException.java create mode 100644 src/main/java/org/onap/cps/ncmp/dmi/model/ModuleReference.java create mode 100644 src/test/resources/GetModules.json diff --git a/docs/openapi/components.yml b/docs/openapi/components.yml index f38ed64b..c67dad69 100644 --- a/docs/openapi/components.yml +++ b/docs/openapi/components.yml @@ -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 diff --git a/docs/openapi/openapi.yml b/docs/openapi/openapi.yml index 44747a9e..f261c0da 100644 --- a/docs/openapi/openapi.yml +++ b/docs/openapi/openapi.yml @@ -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 fd1f1894..ac00de88 100644 --- a/pom.xml +++ b/pom.xml @@ -124,6 +124,10 @@ io.micrometer micrometer-registry-prometheus + + net.minidev + json-smart + diff --git a/src/main/java/org/onap/cps/ncmp/dmi/exception/DmiExceptionHandler.java b/src/main/java/org/onap/cps/ncmp/dmi/exception/DmiExceptionHandler.java index a6ec6dfa..49db7d8b 100644 --- a/src/main/java/org/onap/cps/ncmp/dmi/exception/DmiExceptionHandler.java +++ b/src/main/java/org/onap/cps/ncmp/dmi/exception/DmiExceptionHandler.java @@ -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 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 index 00000000..65db2712 --- /dev/null +++ b/src/main/java/org/onap/cps/ncmp/dmi/exception/ModuleResourceNotFoundException.java @@ -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 index 00000000..cb9b7cbb --- /dev/null +++ b/src/main/java/org/onap/cps/ncmp/dmi/model/ModuleReference.java @@ -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; +} diff --git a/src/main/java/org/onap/cps/ncmp/dmi/rest/controller/DmiRestController.java b/src/main/java/org/onap/cps/ncmp/dmi/rest/controller/DmiRestController.java index 0e1d3d67..5725f094 100644 --- a/src/main/java/org/onap/cps/ncmp/dmi/rest/controller/DmiRestController.java +++ b/src/main/java/org/onap/cps/ncmp/dmi/rest/controller/DmiRestController.java @@ -20,14 +20,17 @@ 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 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 registerCmHandles(final @Valid CmHandles cmHandles) { final List 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 convertRestObjectToJavaApiObject(final ModuleRequestParent moduleRequestParent) { + return objectMapper + .convertValue(moduleRequestParent.getData().getModules(), new TypeReference>() {}); + } } diff --git a/src/main/java/org/onap/cps/ncmp/dmi/service/DmiService.java b/src/main/java/org/onap/cps/ncmp/dmi/service/DmiService.java index d9196ecb..aeff3dc6 100644 --- a/src/main/java/org/onap/cps/ncmp/dmi/service/DmiService.java +++ b/src/main/java/org/onap/cps/ncmp/dmi/service/DmiService.java @@ -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 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 modules); } diff --git a/src/main/java/org/onap/cps/ncmp/dmi/service/DmiServiceImpl.java b/src/main/java/org/onap/cps/ncmp/dmi/service/DmiServiceImpl.java index 990a421e..bf0689ca 100644 --- a/src/main/java/org/onap/cps/ncmp/dmi/service/DmiServiceImpl.java +++ b/src/main/java/org/onap/cps/ncmp/dmi/service/DmiServiceImpl.java @@ -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 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 cmHandles) { final CmHandleOperation cmHandleOperation = new CmHandleOperation(); cmHandleOperation.setDmiPlugin(dmiPluginProperties.getDmiServiceName()); final List 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 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; + } } diff --git a/src/main/java/org/onap/cps/ncmp/dmi/service/client/SdncRestconfClient.java b/src/main/java/org/onap/cps/ncmp/dmi/service/client/SdncRestconfClient.java index cf7c50a5..adac5e64 100644 --- a/src/main/java/org/onap/cps/ncmp/dmi/service/client/SdncRestconfClient.java +++ b/src/main/java/org/onap/cps/ncmp/dmi/service/client/SdncRestconfClient.java @@ -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 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 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; } } diff --git a/src/main/java/org/onap/cps/ncmp/dmi/service/operation/SdncOperations.java b/src/main/java/org/onap/cps/ncmp/dmi/service/operation/SdncOperations.java index 4e4e7217..0d1c3438 100644 --- a/src/main/java/org/onap/cps/ncmp/dmi/service/operation/SdncOperations.java +++ b/src/main/java/org/onap/cps/ncmp/dmi/service/operation/SdncOperations.java @@ -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 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); + } } diff --git a/src/test/groovy/org/onap/cps/ncmp/dmi/rest/controller/DmiRestControllerSpec.groovy b/src/test/groovy/org/onap/cps/ncmp/dmi/rest/controller/DmiRestControllerSpec.groovy index 993b80c6..03bffe4b 100644 --- a/src/test/groovy/org/onap/cps/ncmp/dmi/rest/controller/DmiRestControllerSpec.groovy +++ b/src/test/groovy/org/onap/cps/ncmp/dmi/rest/controller/DmiRestControllerSpec.groovy @@ -20,8 +20,11 @@ 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() + } } diff --git a/src/test/groovy/org/onap/cps/ncmp/dmi/service/DmiServiceImplSpec.groovy b/src/test/groovy/org/onap/cps/ncmp/dmi/service/DmiServiceImplSpec.groovy index 9d6bc358..1854b24d 100644 --- a/src/test/groovy/org/onap/cps/ncmp/dmi/service/DmiServiceImplSpec.groovy +++ b/src/test/groovy/org/onap/cps/ncmp/dmi/service/DmiServiceImplSpec.groovy @@ -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 + 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('response-body1', HttpStatus.OK) + 1 * mockSdncOperations.getModuleResource(cmHandle,excpectedModulesJson2) >> new ResponseEntity('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('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 ) + then: 'ModuleResourceNotFoundException is thrown' + thrown(ModuleResourceNotFoundException) + } } diff --git a/src/test/groovy/org/onap/cps/ncmp/dmi/service/client/SdncRestconfClientSpec.groovy b/src/test/groovy/org/onap/cps/ncmp/dmi/service/client/SdncRestconfClientSpec.groovy index 0b192f05..6d9445c1 100644 --- a/src/test/groovy/org/onap/cps/ncmp/dmi/service/client/SdncRestconfClientSpec.groovy +++ b/src/test/groovy/org/onap/cps/ncmp/dmi/service/client/SdncRestconfClientSpec.groovy @@ -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 diff --git a/src/test/groovy/org/onap/cps/ncmp/dmi/service/operation/SdncOperationsSpec.groovy b/src/test/groovy/org/onap/cps/ncmp/dmi/service/operation/SdncOperationsSpec.groovy index 956834ad..9b07d68e 100644 --- a/src/test/groovy/org/onap/cps/ncmp/dmi/service/operation/SdncOperationsSpec.groovy +++ b/src/test/groovy/org/onap/cps/ncmp/dmi/service/operation/SdncOperationsSpec.groovy @@ -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') + } } diff --git a/src/test/java/org/onap/cps/ncmp/dmi/TestUtils.java b/src/test/java/org/onap/cps/ncmp/dmi/TestUtils.java index b82a6f5c..c10d91a5 100644 --- a/src/test/java/org/onap/cps/ncmp/dmi/TestUtils.java +++ b/src/test/java/org/onap/cps/ncmp/dmi/TestUtils.java @@ -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 index 00000000..23fe77c2 --- /dev/null +++ b/src/test/resources/GetModules.json @@ -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 diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index 8ef864bd..b7c5bab9 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -24,3 +24,10 @@ security: auth: username: cpsuser password: cpsr0cks! + +sdnc: + baseUrl: http://test + topologyId: test-topology + auth: + username: test + password: test -- cgit 1.2.3-korg