diff options
author | Arpit Singh <as00745003@techmahindra.com> | 2023-09-07 17:05:37 +0530 |
---|---|---|
committer | Arpit Singh <as00745003@techmahindra.com> | 2024-06-06 14:13:32 +0530 |
commit | d7fa9601a1409ee3a156ac2f6a6ec11853989cd7 (patch) | |
tree | 1967c1bebdfca2e116bdd6acf630d87e39ccf3ae /cps-rest | |
parent | f8ca09bf4e5d76fb95bd1eda17f15a7fd92d0f63 (diff) |
CPS Delta API 2: Delta between anchor and payload
- Second API to get Delta between an anchor and JSON payload
- added new API getDeltaByDataspaceAnchorAndPayload
- added controller and service layer methods
getDeltaByDataspaceAnchorAndPayload
- Core Delta algorithm remains same as the first API.
getDeltaByDataspaceAnchorAndPayload will call getDeltaBetweenDataNodes
Issue-ID: CPS-1836
Signed-off-by: Arpit Singh <as00745003@techmahindra.com>
Change-Id: Id74cd930ce48e5cb414aa62c5381b79675788a37
Diffstat (limited to 'cps-rest')
4 files changed, 135 insertions, 7 deletions
diff --git a/cps-rest/docs/openapi/cpsDataV2.yml b/cps-rest/docs/openapi/cpsDataV2.yml index cbb5ce4104..a1433badf6 100644 --- a/cps-rest/docs/openapi/cpsDataV2.yml +++ b/cps-rest/docs/openapi/cpsDataV2.yml @@ -1,5 +1,5 @@ # ============LICENSE_START======================================================= -# Copyright (c) 2022-2023 TechMahindra Ltd. +# Copyright (c) 2022-2024 TechMahindra Ltd. # ================================================================================ # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -75,4 +75,55 @@ deltaByDataspaceAndAnchors: $ref: 'components.yml#/components/responses/Forbidden' '500': $ref: 'components.yml#/components/responses/InternalServerError' - x-codegen-request-body-name: xpath
\ No newline at end of file + x-codegen-request-body-name: xpath + +deltaByDataspaceAnchorAndPayload: + post: + description: Get delta between an anchor in a dataspace and JSON payload + tags: + - cps-data + summary: Get delta between an anchor and JSON payload + operationId: getDeltaByDataspaceAnchorAndPayload + parameters: + - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' + - $ref: 'components.yml#/components/parameters/anchorNameInPath' + - $ref: 'components.yml#/components/parameters/xpathInQuery' + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + json: + type: object + example: + test:bookstore: + bookstore-name: Chapters + categories: + - code: 01 + name: SciFi + - code: 02 + name: kids + file: + type: string + format: binary + required: + - json + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + examples: + dataSample: + $ref: 'components.yml#/components/examples/deltaReportSample' + '400': + $ref: 'components.yml#/components/responses/BadRequest' + '401': + $ref: 'components.yml#/components/responses/Unauthorized' + '403': + $ref: 'components.yml#/components/responses/Forbidden' + '500': + $ref: 'components.yml#/components/responses/InternalServerError'
\ No newline at end of file diff --git a/cps-rest/docs/openapi/openapi.yml b/cps-rest/docs/openapi/openapi.yml index f29335a0a9..b4e0b70408 100644 --- a/cps-rest/docs/openapi/openapi.yml +++ b/cps-rest/docs/openapi/openapi.yml @@ -2,7 +2,7 @@ # Copyright (C) 2021-2023 Nordix Foundation # Modifications Copyright (C) 2021 Pantheon.tech # Modifications Copyright (C) 2021 Bell Canada. -# Modifications Copyright (C) 2022-2023 TechMahindra Ltd. +# Modifications Copyright (C) 2022-2024 TechMahindra Ltd. # ================================================================================ # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -104,9 +104,12 @@ paths: /{apiVersion}/dataspaces/{dataspace-name}/anchors/{anchor-name}/list-nodes: $ref: 'cpsData.yml#/listElementByDataspaceAndAnchor' - /v2/dataspaces/{dataspace-name}/anchors/{anchor-name}/delta: + /v2/dataspaces/{dataspace-name}/anchors/{anchor-name}/deltaAnchors: $ref: 'cpsDataV2.yml#/deltaByDataspaceAndAnchors' + /v2/dataspaces/{dataspace-name}/anchors/{anchor-name}/deltaPayload: + $ref: 'cpsDataV2.yml#/deltaByDataspaceAnchorAndPayload' + /v1/dataspaces/{dataspace-name}/anchors/{anchor-name}/nodes/query: $ref: 'cpsQueryV1Deprecated.yml#/nodesByDataspaceAndAnchorAndCpsPath' diff --git a/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java b/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java index 310171b309..f579c82d25 100755 --- a/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java +++ b/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java @@ -24,12 +24,15 @@ package org.onap.cps.rest.controller; +import static org.onap.cps.rest.utils.MultipartFileUtil.extractYangResourcesMap; + import io.micrometer.core.annotation.Timed; import jakarta.validation.ValidationException; import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Map; import lombok.RequiredArgsConstructor; @@ -49,6 +52,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; @RestController @RequestMapping("${rest.api.cps-base-path}") @@ -172,6 +176,27 @@ public class DataRestController implements CpsDataApi { } @Override + public ResponseEntity<Object> getDeltaByDataspaceAnchorAndPayload(final String dataspaceName, + final String sourceAnchorName, + final Object jsonPayload, + final String xpath, + final MultipartFile multipartFile) { + final FetchDescendantsOption fetchDescendantsOption = FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS; + + final Map<String, String> yangResourceMap; + if (multipartFile == null) { + yangResourceMap = Collections.emptyMap(); + } else { + yangResourceMap = extractYangResourcesMap(multipartFile); + } + final Collection<DeltaReport> deltaReports = Collections.unmodifiableList( + cpsDataService.getDeltaByDataspaceAnchorAndPayload(dataspaceName, sourceAnchorName, + xpath, yangResourceMap, jsonPayload.toString(), fetchDescendantsOption)); + + return new ResponseEntity<>(jsonObjectMapper.asJsonString(deltaReports), HttpStatus.OK); + } + + @Override @Timed(value = "cps.data.controller.get.delta", description = "Time taken to get delta between anchors") public ResponseEntity<Object> getDeltaByDataspaceAndAnchors(final String dataspaceName, diff --git a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy index 3f5dcf2633..317b9c5b7c 100755 --- a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy +++ b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy @@ -41,7 +41,9 @@ import org.springframework.beans.factory.annotation.Value import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest import org.springframework.http.HttpStatus import org.springframework.http.MediaType +import org.springframework.mock.web.MockMultipartFile import org.springframework.test.web.servlet.MockMvc +import org.springframework.web.multipart.MultipartFile import spock.lang.Shared import spock.lang.Specification @@ -49,6 +51,7 @@ import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put @@ -101,6 +104,10 @@ class DataRestControllerSpec extends Specification { static DataNode dataNodeWithChild = new DataNodeBuilder().withXpath('/parent') .withChildDataNodes([new DataNodeBuilder().withXpath("/parent/child").build()]).build() + @Shared + static MultipartFile multipartYangFile = new MockMultipartFile("file", 'filename.yang', "text/plain", 'content'.getBytes()) + + def setup() { dataNodeBaseEndpointV1 = "$basePath/v1/dataspaces/$dataspaceName" dataNodeBaseEndpointV2 = "$basePath/v2/dataspaces/$dataspaceName" @@ -337,9 +344,9 @@ class DataRestControllerSpec extends Specification { def 'Get delta between two anchors'() { given: 'the service returns a list containing delta reports' - def deltaReports = new DeltaReportBuilder().actionAdd().withXpath('/bookstore').withSourceData('bookstore-name': 'Easons').withTargetData('bookstore-name': 'Easons').build() + def deltaReports = new DeltaReportBuilder().actionUpdate().withXpath('some xpath').withSourceData('some key': 'some value').withTargetData('some key': 'some value').build() def xpath = 'some xpath' - def endpoint = "$dataNodeBaseEndpointV2/anchors/sourceAnchor/delta" + def endpoint = "$dataNodeBaseEndpointV2/anchors/sourceAnchor/deltaAnchors" mockCpsDataService.getDeltaByDataspaceAndAnchors(dataspaceName, 'sourceAnchor', 'targetAnchor', xpath, OMIT_DESCENDANTS) >> [deltaReports] when: 'get delta request is performed using REST API' def response = @@ -350,7 +357,48 @@ class DataRestControllerSpec extends Specification { then: 'expected response code is returned' assert response.status == HttpStatus.OK.value() and: 'the response contains expected value' - assert response.contentAsString.contains("[{\"action\":\"add\",\"xpath\":\"/bookstore\",\"sourceData\":{\"bookstore-name\":\"Easons\"},\"targetData\":{\"bookstore-name\":\"Easons\"}}]") + assert response.contentAsString.contains("[{\"action\":\"update\",\"xpath\":\"some xpath\",\"sourceData\":{\"some key\":\"some value\"},\"targetData\":{\"some key\":\"some value\"}}]") + } + + def 'Get delta between anchor and JSON payload with multipart file'() { + given: 'sample delta report, xpath, yang model file and json payload' + def deltaReports = new DeltaReportBuilder().actionAdd().withXpath('some xpath').build() + def xpath = 'some xpath' + def endpoint = "$dataNodeBaseEndpointV2/anchors/$anchorName/deltaPayload" + and: 'the service layer returns a list containing delta reports' + mockCpsDataService.getDeltaByDataspaceAnchorAndPayload(dataspaceName, anchorName, xpath, ['filename.yang':'content'], expectedJsonData, INCLUDE_ALL_DESCENDANTS) >> [deltaReports] + when: 'get delta request is performed using REST API' + def response = + mvc.perform(multipart(endpoint) + .file(multipartYangFile) + .param("json", requestBodyJson) + .param('xpath', xpath) + .contentType(MediaType.MULTIPART_FORM_DATA)) + .andReturn().response + then: 'expected response code is returned' + assert response.status == HttpStatus.OK.value() + and: 'the response contains expected value' + assert response.contentAsString.contains("[{\"action\":\"add\",\"xpath\":\"some xpath\"}]") + } + + def 'Get delta between anchor and JSON payload without multipart file'() { + given: 'sample delta report, xpath, and json payload' + def deltaReports = new DeltaReportBuilder().actionRemove().withXpath('some xpath').build() + def xpath = 'some xpath' + def endpoint = "$dataNodeBaseEndpointV2/anchors/$anchorName/deltaPayload" + and: 'the service layer returns a list containing delta reports' + mockCpsDataService.getDeltaByDataspaceAnchorAndPayload(dataspaceName, anchorName, xpath, [:], expectedJsonData, INCLUDE_ALL_DESCENDANTS) >> [deltaReports] + when: 'get delta request is performed using REST API' + def response = + mvc.perform(multipart(endpoint) + .param("json", requestBodyJson) + .param('xpath', xpath) + .contentType(MediaType.MULTIPART_FORM_DATA)) + .andReturn().response + then: 'expected response code is returned' + assert response.status == HttpStatus.OK.value() + and: 'the response contains expected value' + assert response.contentAsString.contains("[{\"action\":\"remove\",\"xpath\":\"some xpath\"}]") } def 'Update data node leaves: #scenario.'() { @@ -507,4 +555,5 @@ class DataRestControllerSpec extends Specification { 'without observed timestamp' | null || 1 | HttpStatus.NO_CONTENT 'with invalid observed timestamp' | 'invalid' || 0 | HttpStatus.BAD_REQUEST } + } |