get resource data for operational passthrough 82/123282/4
authortragait <rahul.tyagi@est.tech>
Mon, 16 Aug 2021 14:12:36 +0000 (15:12 +0100)
committertragait <rahul.tyagi@est.tech>
Thu, 19 Aug 2021 11:46:37 +0000 (12:46 +0100)
Issue-ID: CPS-487
Signed-off-by: tragait <rahul.tyagi@est.tech>
Change-Id: Id1b761f3f6a388556d0cc334fd6f196c78badc39

20 files changed:
cps-application/src/main/resources/application.yml
cps-ncmp-rest/docs/openapi/components.yaml
cps-ncmp-rest/docs/openapi/ncmproxy.yml
cps-ncmp-rest/docs/openapi/openapi.yml
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandler.java
cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy
cps-ncmp-service/pom.xml
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyDataService.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/client/DmiRestClient.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/NcmpConfiguration.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/NcmpException.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operation/DmiOperations.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/GenericRequestBody.java [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/client/DmiRestClientSpec.groovy [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operation/DmiOperationsSpec.groovy [new file with mode: 0644]
cps-parent/pom.xml
docker-compose/docker-compose.yml

index ac620f6..fcd91c2 100644 (file)
@@ -105,3 +105,8 @@ logging:
     level:\r
         org:\r
             springframework: INFO\r
+\r
+dmi:\r
+    auth:\r
+        username: ${DMI_USERNAME}\r
+        password: ${DMI_PASSWORD}\r
index 9921041..4f5a6a1 100644 (file)
@@ -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:
index 3ec7bfd..ede0ec6 100755 (executable)
@@ -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
index 64de922..5b7c8d2 100755 (executable)
@@ -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
index 3d771b6..b35b245 100755 (executable)
@@ -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<Object> 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) {
index c91b821..6729329 100755 (executable)
@@ -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<Object> handleNcmpExceptions(final NcmpException exception) {
+        return buildErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR, exception);
+    }
+
     private static ResponseEntity<Object> 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);
index f537980..b2a060c 100644 (file)
@@ -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()
+    }
+
 }
 
index c6bcc0a..f786b80 100644 (file)
@@ -62,5 +62,9 @@
             </exclusion>
         </exclusions>
     </dependency>
+    <dependency>
+        <groupId>org.springframework</groupId>
+        <artifactId>spring-web</artifactId>
+    </dependency>
 </dependencies>
 </project>
index 6038ea4..82be7bf 100644 (file)
@@ -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);
 }
index d6d1ec7..84dcc77 100755 (executable)
@@ -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<DataNode> 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<DataNode> additionalPropsList = dataNode.getChildDataNodes();
+        final String jsonBody = prepareOperationBody(GenericRequestBody.OperationEnum.READ, additionalPropsList);
+        final ResponseEntity<Object> 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<DataNode> additionalPropertyList) {
+        final GenericRequestBody requestBody = new GenericRequestBody();
+        final Map<String, String> 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<String, String> getAdditionalPropertiesMap(final Collection<DataNode> additionalPropertyList) {
+        if (additionalPropertyList == null || additionalPropertyList.size() == 0) {
+            return null;
+        }
+        final Map<String, String> 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<Object> 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 (file)
index 0000000..3e4f03d
--- /dev/null
@@ -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<Object> 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 (file)
index 0000000..a834bfc
--- /dev/null
@@ -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 (file)
index 0000000..ff53464
--- /dev/null
@@ -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 (file)
index 0000000..c7554bc
--- /dev/null
@@ -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<Object> 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 (file)
index 0000000..5b82c51
--- /dev/null
@@ -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<String, String> cmHandleProperties;
+}
index 6d53e40..65d96a4 100644 (file)
  *  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 (file)
index 0000000..98bbe87
--- /dev/null
@@ -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 (file)
index 0000000..75b5383
--- /dev/null
@@ -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
index 938e75e..46594da 100755 (executable)
                         <exclude>org/onap/cps/rest/model/*</exclude>
                         <exclude>org/onap/cps/cpspath/parser/antlr4/*</exclude>
                         <exclude>org/onap/cps/ncmp/rest/model/*</exclude>
+                        <exclude>org/onap/cps/ncmp/api/models/*</exclude>
                     </excludes>
                 </configuration>
                 <executions>
index e2185f6..e659b08 100755 (executable)
@@ -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