From c328551bdfd069343cc4c4e0249516d07938c78a Mon Sep 17 00:00:00 2001 From: tragait Date: Mon, 16 Aug 2021 15:12:36 +0100 Subject: [PATCH] get resource data for operational passthrough Issue-ID: CPS-487 Signed-off-by: tragait Change-Id: Id1b761f3f6a388556d0cc334fd6f196c78badc39 --- cps-application/src/main/resources/application.yml | 5 + cps-ncmp-rest/docs/openapi/components.yaml | 30 ++++++ cps-ncmp-rest/docs/openapi/ncmproxy.yml | 27 ++++- cps-ncmp-rest/docs/openapi/openapi.yml | 7 +- .../rest/controller/NetworkCmProxyController.java | 24 +++++ .../NetworkCmProxyRestExceptionHandler.java | 14 +++ .../controller/NetworkCmProxyControllerSpec.groovy | 20 ++++ cps-ncmp-service/pom.xml | 4 + .../cps/ncmp/api/NetworkCmProxyDataService.java | 16 +++ .../api/impl/NetworkCmProxyDataServiceImpl.java | 94 +++++++++++++++++- .../cps/ncmp/api/impl/client/DmiRestClient.java | 52 ++++++++++ .../ncmp/api/impl/config/NcmpConfiguration.java | 47 +++++++++ .../cps/ncmp/api/impl/exception/NcmpException.java | 59 +++++++++++ .../cps/ncmp/api/impl/operation/DmiOperations.java | 109 +++++++++++++++++++++ .../cps/ncmp/api/models/GenericRequestBody.java | 53 ++++++++++ .../impl/NetworkCmProxyDataServiceImplSpec.groovy | 92 ++++++++++++++++- .../ncmp/api/impl/client/DmiRestClientSpec.groovy | 55 +++++++++++ .../api/impl/operation/DmiOperationsSpec.groovy | 58 +++++++++++ cps-parent/pom.xml | 1 + docker-compose/docker-compose.yml | 4 + 20 files changed, 763 insertions(+), 8 deletions(-) create mode 100644 cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/client/DmiRestClient.java create mode 100644 cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/NcmpConfiguration.java create mode 100644 cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/NcmpException.java create mode 100644 cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operation/DmiOperations.java create mode 100644 cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/GenericRequestBody.java create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/client/DmiRestClientSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operation/DmiOperationsSpec.groovy diff --git a/cps-application/src/main/resources/application.yml b/cps-application/src/main/resources/application.yml index ac620f6cb..fcd91c26d 100644 --- a/cps-application/src/main/resources/application.yml +++ b/cps-application/src/main/resources/application.yml @@ -105,3 +105,8 @@ logging: level: org: springframework: INFO + +dmi: + auth: + username: ${DMI_USERNAME} + password: ${DMI_PASSWORD} diff --git a/cps-ncmp-rest/docs/openapi/components.yaml b/cps-ncmp-rest/docs/openapi/components.yaml index 9921041cf..4f5a6a13e 100644 --- a/cps-ncmp-rest/docs/openapi/components.yaml +++ b/cps-ncmp-rest/docs/openapi/components.yaml @@ -96,6 +96,36 @@ components: schema: type: string default: / + resourceIdentifierInPath: + name: resourceIdentifier + in: path + description: Resource identifier to get/set the resource data + required: true + schema: + type: string + acceptParamInHeader: + name: accept + in: header + required: false + description: Accept parameter for response, if accept parameter is null, that means client can accept any format. + schema: + type: string + enum: [ application/json, application/yang-data+json ] + fieldsParamInQuery: + name: fields + in: query + description: Fields parameter to filter resource + required: false + schema: + type: string + depthParamInQuery: + name: depth + in: query + description: Depth parameter for response + required: false + schema: + type: integer + minimum: 1 responses: diff --git a/cps-ncmp-rest/docs/openapi/ncmproxy.yml b/cps-ncmp-rest/docs/openapi/ncmproxy.yml index 3ec7bfd11..ede0ec62f 100755 --- a/cps-ncmp-rest/docs/openapi/ncmproxy.yml +++ b/cps-ncmp-rest/docs/openapi/ncmproxy.yml @@ -200,4 +200,29 @@ updateDmiRegistration: 401: $ref: 'components.yaml#/components/responses/Unauthorized' 403: - $ref: 'components.yaml#/components/responses/Forbidden' \ No newline at end of file + $ref: 'components.yaml#/components/responses/Forbidden' + +getResourceDataForPassthroughOperational: + get: + tags: + - network-cm-proxy + summary: Get resource data from pass-through operational for cm handle + description: Get resource data from pass-through operational for given cm handle + operationId: getResourceDataOperationalForCmHandle + parameters: + - $ref: 'components.yaml#/components/parameters/cmHandleInPath' + - $ref: 'components.yaml#/components/parameters/resourceIdentifierInPath' + - $ref: 'components.yaml#/components/parameters/acceptParamInHeader' + - $ref: 'components.yaml#/components/parameters/fieldsParamInQuery' + - $ref: 'components.yaml#/components/parameters/depthParamInQuery' + 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 diff --git a/cps-ncmp-rest/docs/openapi/openapi.yml b/cps-ncmp-rest/docs/openapi/openapi.yml index 64de9223f..5b7c8d205 100755 --- a/cps-ncmp-rest/docs/openapi/openapi.yml +++ b/cps-ncmp-rest/docs/openapi/openapi.yml @@ -38,4 +38,9 @@ paths: $ref: 'ncmproxy.yml#/nodesByCmHandleAndXpath' /ncmp-dmi/v1/ch: - $ref: 'ncmproxy.yml#/updateDmiRegistration' \ No newline at end of file + $ref: 'ncmproxy.yml#/updateDmiRegistration' + + /v1/ch/{cm-handle}/data/ds/ncmp-datastore:passthrough-operational/{resourceIdentifier}: + $ref: 'ncmproxy.yml#/getResourceDataForPassthroughOperational' + + \ No newline at end of file diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java index 3d771b6c5..b35b245aa 100755 --- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java @@ -27,6 +27,7 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import java.util.Collection; import javax.validation.Valid; +import javax.validation.constraints.Min; import javax.validation.constraints.NotNull; import org.onap.cps.ncmp.api.NetworkCmProxyDataService; import org.onap.cps.ncmp.api.models.DmiPluginRegistration; @@ -151,6 +152,29 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { return new ResponseEntity<>(HttpStatus.OK); } + /** + * Get resource data for for operational datastore. + * + * @param cmHandle cm handle identifier + * @param resourceIdentifier resource identifier + * @param accept accept header parameter + * @param fields fields query parameter + * @param depth depth query parameter + * @return {@code ResponseEntity} response from dmi plugin + */ + @Override + public ResponseEntity getResourceDataOperationalForCmHandle(final String cmHandle, + final String resourceIdentifier, + final String accept, + final @Valid String fields, + final @Min(1) @Valid Integer depth) { + final var responseObject = networkCmProxyDataService.getResourceDataOperationalFoCmHandle(cmHandle, + resourceIdentifier, + accept, + fields, + depth); + return ResponseEntity.ok(responseObject); + } private DmiPluginRegistration convertRestObjectToJavaApiObject( final RestDmiPluginRegistration restDmiPluginRegistration) { diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandler.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandler.java index c91b821d6..672932977 100755 --- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandler.java +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandler.java @@ -1,6 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2021 Pantheon.tech + * Modifications 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. @@ -22,6 +23,7 @@ package org.onap.cps.ncmp.rest.exceptions; import lombok.AccessLevel; import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.onap.cps.ncmp.api.impl.exception.NcmpException; import org.onap.cps.ncmp.rest.controller.NetworkCmProxyController; import org.onap.cps.ncmp.rest.model.ErrorMessage; import org.onap.cps.spi.exceptions.CpsException; @@ -57,6 +59,11 @@ public class NetworkCmProxyRestExceptionHandler { return buildErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR, exception); } + @ExceptionHandler({NcmpException.class}) + public static ResponseEntity handleNcmpExceptions(final NcmpException exception) { + return buildErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR, exception); + } + private static ResponseEntity buildErrorResponse(final HttpStatus status, final Exception exception) { if (exception.getCause() != null || !(exception instanceof CpsException)) { log.error("Exception occurred", exception); @@ -64,6 +71,13 @@ public class NetworkCmProxyRestExceptionHandler { final var errorMessage = new ErrorMessage(); errorMessage.setStatus(status.toString()); errorMessage.setMessage(exception.getMessage()); + if (exception instanceof CpsException) { + errorMessage.setDetails(((CpsException) exception).getDetails()); + } else if (exception instanceof NcmpException) { + errorMessage.setDetails(((NcmpException) exception).getDetails()); + } else { + errorMessage.setDetails(CHECK_LOGS_FOR_DETAILS); + } errorMessage.setDetails(exception instanceof CpsException ? ((CpsException) exception).getDetails() : CHECK_LOGS_FOR_DETAILS); return new ResponseEntity<>(errorMessage, status); diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy index f537980ac..b2a060c27 100644 --- a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy +++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy @@ -196,5 +196,25 @@ class NetworkCmProxyControllerSpec extends Specification { response.status == HttpStatus.CREATED.value() } + def 'Get Resource Data from pass-through operational.' () { + given: 'resource data url' + def getUrl = "$basePath/v1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-operational" + + "/testResourceIdentifier?fields=testFields&depth=5" + when: 'get data resource request is performed' + def response = mvc.perform( + get(getUrl) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON_VALUE) + ).andReturn().response + then: 'the NCMP data service is called with getResourceDataOperationalFoCmHandle' + 1 * mockNetworkCmProxyDataService.getResourceDataOperationalFoCmHandle('testCmHandle', + 'testResourceIdentifier', + 'application/json', + 'testFields', + 5) + and: 'response status is Ok' + response.status == HttpStatus.OK.value() + } + } diff --git a/cps-ncmp-service/pom.xml b/cps-ncmp-service/pom.xml index c6bcc0afb..f786b8056 100644 --- a/cps-ncmp-service/pom.xml +++ b/cps-ncmp-service/pom.xml @@ -62,5 +62,9 @@ + + org.springframework + spring-web + diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyDataService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyDataService.java index 6038ea455..82be7bf55 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyDataService.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyDataService.java @@ -106,4 +106,20 @@ public interface NetworkCmProxyDataService { */ void updateDmiPluginRegistration(DmiPluginRegistration dmiPluginRegistration); + /** + * Get resource data for data store pass-through operational + * using dmi. + * + * @param cmHandle cm handle + * @param resourceIdentifier resource identifier + * @param accept accept param + * @param fields fields query + * @param depth depth query + * @return {@code Object} resource data + */ + Object getResourceDataOperationalFoCmHandle(@NonNull String cmHandle, + @NonNull String resourceIdentifier, + String accept, + String fields, + Integer depth); } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java index d6d1ec75c..84dcc770d 100755 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java @@ -26,21 +26,29 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.ArrayList; import java.util.Collection; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import lombok.extern.slf4j.Slf4j; import org.onap.cps.api.CpsDataService; import org.onap.cps.api.CpsQueryService; import org.onap.cps.ncmp.api.NetworkCmProxyDataService; +import org.onap.cps.ncmp.api.impl.exception.NcmpException; +import org.onap.cps.ncmp.api.impl.operation.DmiOperations; import org.onap.cps.ncmp.api.models.CmHandle; import org.onap.cps.ncmp.api.models.DmiPluginRegistration; +import org.onap.cps.ncmp.api.models.GenericRequestBody; import org.onap.cps.ncmp.api.models.PersistenceCmHandle; import org.onap.cps.ncmp.api.models.PersistenceCmHandlesList; import org.onap.cps.spi.FetchDescendantsOption; import org.onap.cps.spi.exceptions.DataValidationException; import org.onap.cps.spi.model.DataNode; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; + @Slf4j @Service public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService { @@ -49,7 +57,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService private static final String NCMP_DATASPACE_NAME = "NCMP-Admin"; - private static final String NCMP_ANCHOR_NAME = "ncmp-dmi-registry"; + private static final String NCMP_DMI_REGISTRY_ANCHOR = "ncmp-dmi-registry"; private CpsDataService cpsDataService; @@ -57,14 +65,18 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService private CpsQueryService cpsQueryService; + private DmiOperations dmiOperations; + /** * Constructor Injection for Dependencies. + * @param dmiOperations dmi operation * @param cpsDataService Data Service Interface * @param cpsQueryService Query Service Interface * @param objectMapper Object Mapper */ - public NetworkCmProxyDataServiceImpl(final CpsDataService cpsDataService, + public NetworkCmProxyDataServiceImpl(final DmiOperations dmiOperations, final CpsDataService cpsDataService, final CpsQueryService cpsQueryService, final ObjectMapper objectMapper) { + this.dmiOperations = dmiOperations; this.cpsDataService = cpsDataService; this.cpsQueryService = cpsQueryService; this.objectMapper = objectMapper; @@ -82,7 +94,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService @Override public Collection queryDataNodes(final String cmHandle, final String cpsPath, - final FetchDescendantsOption fetchDescendantsOption) { + final FetchDescendantsOption fetchDescendantsOption) { return cpsQueryService.queryDataNodes(getDataspaceName(), cmHandle, cpsPath, fetchDescendantsOption); } @@ -124,8 +136,10 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService } final var persistenceCmHandlesList = new PersistenceCmHandlesList(); persistenceCmHandlesList.setCmHandles(persistenceCmHandles); - final var cmHandleJsonData = objectMapper.writeValueAsString(persistenceCmHandlesList); - cpsDataService.saveListNodeData(NCMP_DATASPACE_NAME, NCMP_ANCHOR_NAME, "/dmi-registry", + final String cmHandleJsonData = objectMapper.writeValueAsString(persistenceCmHandlesList); + cpsDataService.saveListNodeData(NCMP_DATASPACE_NAME, + NCMP_DMI_REGISTRY_ANCHOR, + "/dmi-registry", cmHandleJsonData); } catch (final JsonProcessingException e) { throw new DataValidationException( @@ -133,4 +147,74 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService .getMessage(), e); } } + + @Override + public Object getResourceDataOperationalFoCmHandle(final String cmHandle, + final String resourceIdentifier, + final String acceptParam, + final String fieldsQueryParam, + final Integer depthQueryParam) { + + final DataNode dataNode = fetchDataNodeFromDmiRegistryForCmHandle(cmHandle); + final String dmiServiceName = String.valueOf(dataNode.getLeaves().get("dmi-service-name")); + final Collection additionalPropsList = dataNode.getChildDataNodes(); + final String jsonBody = prepareOperationBody(GenericRequestBody.OperationEnum.READ, additionalPropsList); + final ResponseEntity response = dmiOperations.getResouceDataFromDmi(dmiServiceName, + cmHandle, + resourceIdentifier, + fieldsQueryParam, + depthQueryParam, + acceptParam, + jsonBody); + return handleResponse(response); + } + + private DataNode fetchDataNodeFromDmiRegistryForCmHandle(final String cmHandle) { + final String xpathForDmiRegistryToFetchCmHandle = "/dmi-registry/cm-handles[@id='" + cmHandle + "']"; + final var dataNode = cpsDataService.getDataNode(NCMP_DATASPACE_NAME, + NCMP_DMI_REGISTRY_ANCHOR, + xpathForDmiRegistryToFetchCmHandle, + FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS); + return dataNode; + } + + private String prepareOperationBody(final GenericRequestBody.OperationEnum operation, + final Collection additionalPropertyList) { + final GenericRequestBody requestBody = new GenericRequestBody(); + final Map additionalPropertyMap = getAdditionalPropertiesMap(additionalPropertyList); + requestBody.setOperation(GenericRequestBody.OperationEnum.READ); + requestBody.setCmHandleProperties(additionalPropertyMap); + try { + final String requestJson = objectMapper.writeValueAsString(requestBody); + return requestJson; + } catch (final JsonProcessingException je) { + log.error("Parsing error occurred while converting Object to JSON."); + throw new NcmpException("Parsing error occurred while converting given object to JSON.", + je.getMessage()); + } + } + + private Map getAdditionalPropertiesMap(final Collection additionalPropertyList) { + if (additionalPropertyList == null || additionalPropertyList.size() == 0) { + return null; + } + final Map additionalPropertyMap = new LinkedHashMap<>(); + for (final DataNode node: additionalPropertyList) { + additionalPropertyMap.put(String.valueOf(node.getLeaves().get("name")), + String.valueOf(node.getLeaves().get("value"))); + } + return additionalPropertyMap; + } + + private Object handleResponse(final ResponseEntity responseEntity) { + if (responseEntity.getStatusCode() == HttpStatus.OK) { + return responseEntity.getBody(); + } else { + throw new NcmpException("Not able to get resource data.", + "DMI status code: " + responseEntity.getStatusCodeValue() + + ", DMI response body: " + responseEntity.getBody()); + } + } + + } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/client/DmiRestClient.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/client/DmiRestClient.java new file mode 100644 index 000000000..3e4f03dfd --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/client/DmiRestClient.java @@ -0,0 +1,52 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.impl.client; + +import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration.DmiProperties; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +@Component +public class DmiRestClient { + + private RestTemplate restTemplate; + private DmiProperties dmiProperties; + + public DmiRestClient(final RestTemplate restTemplate, final DmiProperties dmiProperties) { + this.restTemplate = restTemplate; + this.dmiProperties = dmiProperties; + } + + public ResponseEntity putOperationWithJsonData(final String dmiResourceUrl, + final String jsonData, final HttpHeaders headers) { + final var httpEntity = new HttpEntity<>(jsonData, configureHttpHeaders(headers)); + return restTemplate.exchange(dmiResourceUrl, HttpMethod.PUT, httpEntity, Object.class); + } + + private HttpHeaders configureHttpHeaders(final HttpHeaders httpHeaders) { + httpHeaders.setBasicAuth(dmiProperties.getAuthUsername(), dmiProperties.getAuthPassword()); + return httpHeaders; + } +} \ No newline at end of file diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/NcmpConfiguration.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/NcmpConfiguration.java new file mode 100644 index 000000000..a834bfcd9 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/NcmpConfiguration.java @@ -0,0 +1,47 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.impl.config; + +import lombok.Getter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +@Configuration +public class NcmpConfiguration { + + @Getter + @Component + public static class DmiProperties { + @Value("${dmi.auth.username}") + private String authUsername; + @Value("${dmi.auth.password}") + private String authPassword; + } + + @Bean + public RestTemplate restTemplate(final RestTemplateBuilder restTemplateBuilder) { + return restTemplateBuilder.build(); + } +} \ No newline at end of file diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/NcmpException.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/NcmpException.java new file mode 100644 index 000000000..ff5346409 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/NcmpException.java @@ -0,0 +1,59 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.impl.exception; + +import lombok.Getter; + +/** + * Network CM Proxy exception. + */ +public class NcmpException extends RuntimeException { + + private static final long serialVersionUID = 1482619410918497467L; + + @Getter + final String details; + + /** + * Constructor. + * + * @param message the error message + * @param details the error details + */ + public NcmpException(final String message, final String details) { + super(message); + this.details = details; + } + + /** + * Constructor. + * + * @param message the error message + * @param details the error details + * @param cause the cause of the exception + */ + public NcmpException(final String message, final String details, final Throwable cause) { + super(message, cause); + this.details = details; + } + +} + diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operation/DmiOperations.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operation/DmiOperations.java new file mode 100644 index 000000000..c7554bc48 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operation/DmiOperations.java @@ -0,0 +1,109 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.impl.operation; + +import org.jetbrains.annotations.NotNull; +import org.onap.cps.ncmp.api.impl.client.DmiRestClient; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; + +@Component +public class DmiOperations { + + private DmiRestClient dmiRestClient; + private static final String GET_RESOURCE_DATA_FOR_PASSTHROUGH_OPERATIONAL = + "/v1/ch/{cmHandle}/data/ds/ncmp-datastore:passthrough-operational/"; + private int indexCmHandleForGetOperational; + + /** + * Constructor for {@code DmiOperations}. This method also manipulates url properties. + * + * @param dmiRestClient {@code DmiRestClient} + */ + public DmiOperations(final DmiRestClient dmiRestClient) { + this.dmiRestClient = dmiRestClient; + indexCmHandleForGetOperational = GET_RESOURCE_DATA_FOR_PASSTHROUGH_OPERATIONAL.indexOf("{cmHandle}"); + } + + /** + * This method fetches the resource data for given cm handle identifier on given resource + * using dmi client. + * + * @param dmiBasePath dmi base path + * @param cmHandle network resource identifier + * @param resourceId resource identifier + * @param fieldsQuery fields query + * @param depthQuery depth query + * @param acceptParam accept parameter + * @param jsonBody json body for put operation + * @return {@code ResponseEntity} response entity + */ + public ResponseEntity getResouceDataFromDmi(final String dmiBasePath, + final String cmHandle, + final String resourceId, + final String fieldsQuery, + final Integer depthQuery, + final String acceptParam, + final String jsonBody) { + final StringBuilder builder = getDmiResourceDataUrl(dmiBasePath, cmHandle, resourceId, fieldsQuery, depthQuery); + final HttpHeaders httpHeaders = prepareHeader(acceptParam); + return dmiRestClient.putOperationWithJsonData(builder.toString(), jsonBody, httpHeaders); + } + + @NotNull + private StringBuilder getDmiResourceDataUrl(final String dmiBasePath, + final String cmHandle, + final String resourceId, + final String fieldsQuery, + final Integer depthQuery) { + final StringBuilder builder = new StringBuilder(GET_RESOURCE_DATA_FOR_PASSTHROUGH_OPERATIONAL); + builder.replace(indexCmHandleForGetOperational, + indexCmHandleForGetOperational + "{cmHandle}".length(), cmHandle); + builder.insert(builder.length(), resourceId); + appendFieldsAndDepth(fieldsQuery, depthQuery, builder); + builder.insert(0, dmiBasePath); + return builder; + } + + private void appendFieldsAndDepth(final String fieldsQuery, final Integer depthQuery, final StringBuilder builder) { + final boolean doesFieldExists = (fieldsQuery != null && !fieldsQuery.isEmpty()); + if (doesFieldExists) { + builder.append("?").append("fields=").append(fieldsQuery); + } + if (depthQuery != null) { + if (!doesFieldExists) { + builder.append("?"); + } else { + builder.append("&"); + } + builder.append("depth=").append(depthQuery); + } + } + + private HttpHeaders prepareHeader(final String acceptParam) { + final HttpHeaders httpHeaders = new HttpHeaders(); + if (acceptParam != null && !acceptParam.isEmpty()) { + httpHeaders.set(HttpHeaders.ACCEPT, acceptParam); + } + return httpHeaders; + } +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/GenericRequestBody.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/GenericRequestBody.java new file mode 100644 index 000000000..5b82c51b3 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/GenericRequestBody.java @@ -0,0 +1,53 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.models; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonValue; +import java.util.Map; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@JsonInclude(Include.NON_NULL) +public class GenericRequestBody { + public enum OperationEnum { + READ("read"); + private String value; + + OperationEnum(final String value) { + this.value = value; + } + + @Override + @JsonValue + public String toString() { + return String.valueOf(value); + } + } + + private OperationEnum operation; + private String dataType; + private String data; + private Map cmHandleProperties; +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy index 6d53e4067..65d96a429 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy @@ -18,21 +18,29 @@ * SPDX-License-Identifier: Apache-2.0 * ============LICENSE_END========================================================= */ + package org.onap.cps.ncmp.api.impl +import com.fasterxml.jackson.core.JsonProcessingException import com.fasterxml.jackson.databind.ObjectMapper import org.onap.cps.api.CpsDataService import org.onap.cps.api.CpsQueryService +import org.onap.cps.ncmp.api.impl.exception.NcmpException +import org.onap.cps.ncmp.api.impl.operation.DmiOperations import org.onap.cps.ncmp.api.models.CmHandle import org.onap.cps.ncmp.api.models.DmiPluginRegistration import org.onap.cps.spi.FetchDescendantsOption +import org.onap.cps.spi.model.DataNode +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity import spock.lang.Specification class NetworkCmProxyDataServiceImplSpec extends Specification { def mockCpsDataService = Mock(CpsDataService) def mockCpsQueryService = Mock(CpsQueryService) - def objectUnderTest = new NetworkCmProxyDataServiceImpl(mockCpsDataService, mockCpsQueryService, new ObjectMapper()) + def mockDmiOperations = Mock(DmiOperations) + def objectUnderTest = new NetworkCmProxyDataServiceImpl(mockDmiOperations, mockCpsDataService, mockCpsQueryService, new ObjectMapper()) def cmHandle = 'some handle' def expectedDataspaceName = 'NFP-Operational' @@ -108,4 +116,86 @@ class NetworkCmProxyDataServiceImplSpec extends Specification { then: 'the CPS service method is invoked once with the expected parameters' 1 * mockCpsDataService.saveListNodeData('NCMP-Admin', 'ncmp-dmi-registry', '/dmi-registry', expectedJsonData) } + def 'Get resource data for pass-through operational from dmi.'() { + given: 'xpath' + def xpath = "/dmi-registry/cm-handles[@id='testCmHandle']" + and: 'data node' + def dataNode = new DataNode() + dataNode.leaves = ['dmi-service-name':'testDmiService'] + def childDataNode = new DataNode() + childDataNode.leaves = ['name':'testName','value':'testValue'] + dataNode.childDataNodes = [childDataNode] + when: 'get resource data is called' + def response = objectUnderTest.getResourceDataOperationalFoCmHandle('testCmHandle', + 'testResourceId', + 'testAcceptParam', + 'testFieldQuery', + 5) + then: 'cps data service is being called once to get data node' + 1 * mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry', + xpath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode + and: 'dmi operation is being calle to get resource data' + 1 * mockDmiOperations.getResouceDataFromDmi('testDmiService', + 'testCmHandle', + 'testResourceId', + 'testFieldQuery', + 5, + 'testAcceptParam', + '{"operation":"read","cmHandleProperties":{"testName":"testValue"}}') >> new ResponseEntity<>('result-json', HttpStatus.OK) + and: 'dmi returns ok response' + response == 'result-json' + } + def 'Get resource data for pass-through operational from dmi threw parsing exception.'() { + given: 'xpath' + def xpath = "/dmi-registry/cm-handles[@id='testCmHandle']" + and: 'data node' + def dataNode = new DataNode() + dataNode.leaves = ['dmi-service-name':'testDmiService'] + def childDataNode = new DataNode() + childDataNode.leaves = ['name':'testName','value':'testValue'] + dataNode.childDataNodes = [childDataNode] + mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry', + xpath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode + and: 'objectMapper not able to parse object' + def mockObjectMapper = Mock(ObjectMapper) + objectUnderTest.objectMapper = mockObjectMapper + mockObjectMapper.writeValueAsString(_) >> { throw new JsonProcessingException("testException") } + when: 'get resource data is called' + def response = objectUnderTest.getResourceDataOperationalFoCmHandle('testCmHandle', + 'testResourceId', + 'testAcceptParam', + 'testFieldQuery', + 5) + then: 'exception is thrown' + thrown(NcmpException.class) + } + def 'Get resource data for pass-through operational from dmi return NOK response.'() { + given: 'xpath' + def xpath = "/dmi-registry/cm-handles[@id='testCmHandle']" + and: 'data node' + def dataNode = new DataNode() + dataNode.leaves = ['dmi-service-name':'testDmiService'] + def childDataNode = new DataNode() + childDataNode.leaves = ['name':'testName','value':'testValue'] + dataNode.childDataNodes = [childDataNode] + mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry', + xpath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode + and: 'dmi returns NOK response' + mockDmiOperations.getResouceDataFromDmi('testDmiService', + 'testCmHandle', + 'testResourceId', + 'testFieldQuery', + 5, + 'testAcceptParam', + '{"operation":"read","cmHandleProperties":{"testName":"testValue"}}') + >> new ResponseEntity<>('NOK-json', HttpStatus.NOT_FOUND) + when: 'get resource data is called' + def response = objectUnderTest.getResourceDataOperationalFoCmHandle('testCmHandle', + 'testResourceId', + 'testAcceptParam', + 'testFieldQuery', + 5) + then: 'exception is thrown' + thrown(NcmpException.class) + } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/client/DmiRestClientSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/client/DmiRestClientSpec.groovy new file mode 100644 index 000000000..98bbe8748 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/client/DmiRestClientSpec.groovy @@ -0,0 +1,55 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.impl.client + +import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration +import org.springframework.http.HttpEntity +import org.springframework.http.HttpHeaders +import org.springframework.http.ResponseEntity +import org.springframework.web.client.RestTemplate +import spock.lang.Specification +import org.springframework.http.HttpMethod + +class DmiRestClientSpec extends Specification { + + def mockDmiProperties = Mock(NcmpConfiguration.DmiProperties) + def mockRestTemplate = Mock(RestTemplate) + def objectUnderTest = new DmiRestClient(mockRestTemplate, mockDmiProperties) + + def 'DMI PUT operation.'() { + given: 'a get url' + def getResourceDataUrl = 'http://some-uri/getResourceDataUrl' + and: 'dmi properties' + setupTestConfigurationData() + and: 'the rest template returns a valid response entity' + def mockResponseEntity = Mock(ResponseEntity) + mockRestTemplate.exchange(getResourceDataUrl, HttpMethod.PUT, _ as HttpEntity, Object.class) >> mockResponseEntity + when: 'PUT operation is invoked' + def result = objectUnderTest.putOperationWithJsonData(getResourceDataUrl, 'json-data', new HttpHeaders()) + then: 'the output of the method is equal to the output from the test template' + result == mockResponseEntity + } + + def setupTestConfigurationData() { + mockDmiProperties.authUsername >> 'some-username' + mockDmiProperties.authPassword >> 'some-password' + } +} \ No newline at end of file diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operation/DmiOperationsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operation/DmiOperationsSpec.groovy new file mode 100644 index 000000000..75b5383d8 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operation/DmiOperationsSpec.groovy @@ -0,0 +1,58 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.impl.operation + +import org.onap.cps.ncmp.api.impl.client.DmiRestClient +import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration +import org.onap.cps.ncmp.api.impl.operation.DmiOperations +import org.spockframework.spring.SpringBean +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.http.HttpHeaders +import org.springframework.test.context.ContextConfiguration +import spock.lang.Specification + +@SpringBootTest +@ContextConfiguration(classes = [NcmpConfiguration.DmiProperties, DmiOperations]) +class DmiOperationsSpec extends Specification { + + @SpringBean + DmiRestClient mockDmiRestClient = Mock() + + @Autowired + DmiOperations objectUnderTest = new DmiOperations(mockDmiRestClient) + + def 'call get resource data for pass-through:operational datastore from dmi.'() { + given: 'expected url' + def expectedUrl = 'testDmiBasePath/v1/ch/testCmhandle/data/ds' + + '/ncmp-datastore:passthrough-operational/testResourceId?fields=testFieldsQuery&depth=10' + when: 'get resource data is called to dmi' + objectUnderTest.getResouceDataFromDmi('testDmiBasePath', + 'testCmhandle', + 'testResourceId', + 'testFieldsQuery', + 10, + 'testAcceptJson', + 'testJsonbody') + then: 'the put operation is executed with the correct URL' + 1 * mockDmiRestClient.putOperationWithJsonData(expectedUrl, 'testJsonbody', _ as HttpHeaders) + } +} \ No newline at end of file diff --git a/cps-parent/pom.xml b/cps-parent/pom.xml index 938e75eb4..46594dabe 100755 --- a/cps-parent/pom.xml +++ b/cps-parent/pom.xml @@ -336,6 +336,7 @@ org/onap/cps/rest/model/* org/onap/cps/cpspath/parser/antlr4/* org/onap/cps/ncmp/rest/model/* + org/onap/cps/ncmp/api/models/* diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index e2185f6de..e659b0879 100755 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -49,6 +49,8 @@ services: # DB_HOST: dbpostgresql # DB_USERNAME: ${DB_USERNAME:-cps} # DB_PASSWORD: ${DB_PASSWORD:-cps} + # DMI_USERNAME: ${DMI_USERNAME:-cpsuser} + # DMI_PASSWORD: ${DMI_PASSWORD:-cpsr0cks!} # #KAFKA_BOOTSTRAP_SERVER: kafka:9092 # #notification.data-updated.enabled: 'true' # restart: unless-stopped @@ -67,6 +69,8 @@ services: DB_HOST: dbpostgresql DB_USERNAME: ${DB_USERNAME:-cps} DB_PASSWORD: ${DB_PASSWORD:-cps} + DMI_USERNAME: ${DMI_USERNAME:-cpsuser} + DMI_PASSWORD: ${DMI_PASSWORD:-cpsr0cks!} #KAFKA_BOOTSTRAP_SERVER: kafka:9092 #notification.data-updated.enabled: 'true' restart: unless-stopped -- 2.16.6