From 18686cad503dc55c4ff34be3da97eaf9fa1f69b8 Mon Sep 17 00:00:00 2001 From: Rudrangi Anupriya Date: Wed, 9 Apr 2025 15:25:17 +0530 Subject: [PATCH] Enhance the Multipart Delta API to support JSON file Here we upload Json file as payloads instead of JSON data as text input Issue-ID: CPS-2657 Change-Id: Ibc9bbb99aabb07302366321c6d5c2eade0e7e5fb Signed-off-by: Rudrangi Anupriya --- cps-rest/docs/openapi/components.yml | 10 ++++ cps-rest/docs/openapi/cpsDelta.yml | 25 ++++----- .../cps/rest/controller/DeltaRestController.java | 14 ++--- .../rest/exceptions/CpsRestExceptionHandler.java | 5 +- .../org/onap/cps/rest/utils/MultipartFileUtil.java | 28 +++++++++- .../rest/controller/DeltaRestControllerSpec.groovy | 60 ++++++++++++++++++-- docs/api/swagger/cps/openapi.yaml | 25 ++++----- docs/api/swagger/ncmp/openapi.yaml | 65 ++++++++++------------ 8 files changed, 152 insertions(+), 80 deletions(-) diff --git a/cps-rest/docs/openapi/components.yml b/cps-rest/docs/openapi/components.yml index 43a311872a..939e2d7246 100644 --- a/cps-rest/docs/openapi/components.yml +++ b/cps-rest/docs/openapi/components.yml @@ -65,6 +65,16 @@ components: description: multipartFile format: binary + TargetDataAsJsonFile: + type: object + required: + - file + properties: + file: + type: string + description: multipartFile + format: binary + ModuleReferences: type: object title: Module reference object diff --git a/cps-rest/docs/openapi/cpsDelta.yml b/cps-rest/docs/openapi/cpsDelta.yml index 67535ce832..644dc27a9c 100644 --- a/cps-rest/docs/openapi/cpsDelta.yml +++ b/cps-rest/docs/openapi/cpsDelta.yml @@ -62,21 +62,18 @@ delta: 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 + targetDataAsJsonFile: + type: string + format: binary + example: + $ref: 'components.yml#/components/schemas/TargetDataAsJsonFile' + yangResourceFile: + type: string + format: binary + example: + $ref: 'components.yml#/components/schemas/MultipartFile' required: - - json + - targetDataAsJsonFile responses: '200': description: OK diff --git a/cps-rest/src/main/java/org/onap/cps/rest/controller/DeltaRestController.java b/cps-rest/src/main/java/org/onap/cps/rest/controller/DeltaRestController.java index f27346cfa7..641a4db366 100644 --- a/cps-rest/src/main/java/org/onap/cps/rest/controller/DeltaRestController.java +++ b/cps-rest/src/main/java/org/onap/cps/rest/controller/DeltaRestController.java @@ -32,6 +32,7 @@ import org.onap.cps.api.CpsDeltaService; import org.onap.cps.api.model.DeltaReport; import org.onap.cps.api.parameters.FetchDescendantsOption; import org.onap.cps.rest.api.CpsDeltaApi; +import org.onap.cps.rest.utils.MultipartFileUtil; import org.onap.cps.utils.JsonObjectMapper; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -69,21 +70,20 @@ public class DeltaRestController implements CpsDeltaApi { @Override public ResponseEntity getDeltaByDataspaceAnchorAndPayload(final String dataspaceName, final String sourceAnchorName, - final Object jsonPayload, + final MultipartFile targetDataAsJsonFile, final String xpath, - final MultipartFile multipartFile) { + final MultipartFile yangResourceFile) { final FetchDescendantsOption fetchDescendantsOption = FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS; - + final String targetData = MultipartFileUtil.extractJsonContent(targetDataAsJsonFile, jsonObjectMapper); final Map yangResourceMap; - if (multipartFile == null) { + if (yangResourceFile == null) { yangResourceMap = Collections.emptyMap(); } else { - yangResourceMap = extractYangResourcesMap(multipartFile); + yangResourceMap = extractYangResourcesMap(yangResourceFile); } final Collection deltaReports = Collections.unmodifiableList( cpsDeltaService.getDeltaByDataspaceAnchorAndPayload(dataspaceName, sourceAnchorName, - xpath, yangResourceMap, jsonPayload.toString(), fetchDescendantsOption)); + xpath, yangResourceMap, targetData, fetchDescendantsOption)); return new ResponseEntity<>(jsonObjectMapper.asJsonString(deltaReports), HttpStatus.OK); } - } diff --git a/cps-rest/src/main/java/org/onap/cps/rest/exceptions/CpsRestExceptionHandler.java b/cps-rest/src/main/java/org/onap/cps/rest/exceptions/CpsRestExceptionHandler.java index 7e1d641e0d..28d74aff3a 100755 --- a/cps-rest/src/main/java/org/onap/cps/rest/exceptions/CpsRestExceptionHandler.java +++ b/cps-rest/src/main/java/org/onap/cps/rest/exceptions/CpsRestExceptionHandler.java @@ -2,7 +2,7 @@ * ============LICENSE_START======================================================= * Copyright (C) 2020 Pantheon.tech * Modifications Copyright (C) 2021-2023 Nordix Foundation - * Modifications Copyright (C) 2022 TechMahindra Ltd. + * Modifications Copyright (C) 2022-2025 TechMahindra Ltd. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,6 +38,7 @@ import org.onap.cps.api.exceptions.ModelValidationException; import org.onap.cps.api.exceptions.NotFoundInDataspaceException; import org.onap.cps.rest.controller.AdminRestController; import org.onap.cps.rest.controller.DataRestController; +import org.onap.cps.rest.controller.DeltaRestController; import org.onap.cps.rest.controller.QueryRestController; import org.onap.cps.rest.model.ErrorMessage; import org.springframework.http.HttpMethod; @@ -49,7 +50,7 @@ import org.springframework.web.bind.annotation.RestControllerAdvice; @Slf4j @RestControllerAdvice(assignableTypes = {AdminRestController.class, DataRestController.class, - QueryRestController.class}) + DeltaRestController.class, QueryRestController.class}) @NoArgsConstructor(access = AccessLevel.PACKAGE) public class CpsRestExceptionHandler { diff --git a/cps-rest/src/main/java/org/onap/cps/rest/utils/MultipartFileUtil.java b/cps-rest/src/main/java/org/onap/cps/rest/utils/MultipartFileUtil.java index 49f4f32592..b41d78b21f 100644 --- a/cps-rest/src/main/java/org/onap/cps/rest/utils/MultipartFileUtil.java +++ b/cps-rest/src/main/java/org/onap/cps/rest/utils/MultipartFileUtil.java @@ -3,6 +3,7 @@ * Copyright (C) 2020 Pantheon.tech * Modifications Copyright (C) 2021 Bell Canada. * Modifications Copyright (C) 2023 Nordix Foundation. + * Modifications Copyright (C) 2025 TechMahindra Ltd. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +24,7 @@ package org.onap.cps.rest.utils; import static org.opendaylight.yangtools.yang.common.YangConstants.RFC6020_YANG_FILE_EXTENSION; +import com.fasterxml.jackson.databind.JsonNode; import com.google.common.collect.ImmutableMap; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -35,7 +37,9 @@ import java.util.zip.ZipInputStream; import lombok.AccessLevel; import lombok.NoArgsConstructor; import org.onap.cps.api.exceptions.CpsException; +import org.onap.cps.api.exceptions.DataValidationException; import org.onap.cps.api.exceptions.ModelValidationException; +import org.onap.cps.utils.JsonObjectMapper; import org.springframework.web.multipart.MultipartFile; @NoArgsConstructor(access = AccessLevel.PRIVATE) @@ -68,12 +72,34 @@ public class MultipartFileUtil { Arrays.asList(YANG_FILE_EXTENSION, ZIP_FILE_EXTENSION))); } + /** + * Extracts json content from multipart file instance. + * + * @param multipartFile the json file uploaded + * @return the string representation of the JSON content + * @throws IOException if the file is null or empty + */ + + public static String extractJsonContent(final MultipartFile multipartFile, final JsonObjectMapper + jsonObjectMapper) { + try { + if (multipartFile.isEmpty()) { + throw new IOException("JSON file is required"); + } + final String jsonContent = new String(multipartFile.getBytes(), StandardCharsets.UTF_8); + final JsonNode jsonNode = jsonObjectMapper.convertToJsonNode(jsonContent); + return jsonNode.toString(); + } catch (final IOException exception) { + throw new DataValidationException("Failed to read JSON file", exception.getMessage()); + } + } + private static Map extractYangResourcesMapFromZipArchive(final MultipartFile multipartFile) { final ImmutableMap.Builder yangResourceMapBuilder = ImmutableMap.builder(); final var zipFileSizeValidator = new ZipFileSizeValidator(); try ( final var inputStream = multipartFile.getInputStream(); - final var zipInputStream = new ZipInputStream(inputStream); + final var zipInputStream = new ZipInputStream(inputStream) ) { ZipEntry zipEntry; while ((zipEntry = zipInputStream.getNextEntry()) != null) { diff --git a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DeltaRestControllerSpec.groovy b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DeltaRestControllerSpec.groovy index 18c0f1369e..db4ef57cac 100644 --- a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DeltaRestControllerSpec.groovy +++ b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DeltaRestControllerSpec.groovy @@ -35,6 +35,10 @@ import org.springframework.test.web.servlet.MockMvc import org.springframework.web.multipart.MultipartFile import spock.lang.Shared import spock.lang.Specification +import java.nio.charset.StandardCharsets +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.StandardOpenOption import static org.onap.cps.api.parameters.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS import static org.onap.cps.api.parameters.FetchDescendantsOption.OMIT_DESCENDANTS @@ -65,10 +69,21 @@ class DeltaRestControllerSpec extends Specification { @Shared def expectedJsonData = '{"some-key":"some-value","categories":[{"books":[{"authors":["Iain M. Banks"]}]}]}' @Shared - static MultipartFile multipartYangFile = new MockMultipartFile('file', 'filename.yang', 'text/plain', 'content'.getBytes()) + static MultipartFile multipartYangFile = new MockMultipartFile('yangResourceFile', 'filename.yang', 'text/plain', 'content'.getBytes()) + @Shared + Path targetDataAsJsonFile + @Shared + MockMultipartFile multipartTargetDataAsJsonFile def setup() { dataNodeBaseEndpointV2 = "$basePath/v2/dataspaces/$dataspaceName/anchors/$anchorName/delta" + targetDataAsJsonFile = Files.createTempFile('requestBody', '.json') + Files.write(targetDataAsJsonFile, requestBodyJson.getBytes(StandardCharsets.UTF_8), StandardOpenOption.WRITE) + multipartTargetDataAsJsonFile = new MockMultipartFile('targetDataAsJsonFile', targetDataAsJsonFile.fileName.toString(), 'application/json', Files.readAllBytes(targetDataAsJsonFile)) + } + + def cleanup() { + Files.deleteIfExists(targetDataAsJsonFile) } def 'Get delta between two anchors'() { @@ -88,7 +103,7 @@ class DeltaRestControllerSpec extends Specification { assert response.contentAsString.contains('[{\"action\":\"replace\",\"xpath\":\"some xpath\",\"sourceData\":{\"some key\":\"some value\"},\"targetData\":{\"some key\":\"some value\"}}]') } - def 'Get delta between anchor and JSON payload with multipart file'() { + def 'Get delta between anchor and JSON payload with yangResourceFile'() { given: 'sample delta report, xpath, yang model file and json payload' def deltaReports = new DeltaReportBuilder().actionCreate().withXpath('some xpath').build() def xpath = 'some xpath' @@ -98,7 +113,7 @@ class DeltaRestControllerSpec extends Specification { def response = mvc.perform(multipart(dataNodeBaseEndpointV2) .file(multipartYangFile) - .param('json', requestBodyJson) + .file(multipartTargetDataAsJsonFile) .param('xpath', xpath) .contentType(MediaType.MULTIPART_FORM_DATA)) .andReturn().response @@ -108,7 +123,7 @@ class DeltaRestControllerSpec extends Specification { assert response.contentAsString.contains('[{\"action\":\"create\",\"xpath\":\"some xpath\"}]') } - def 'Get delta between anchor and JSON payload without multipart file'() { + def 'Get delta between anchor and JSON payload without yangResourceFile'() { given: 'sample delta report, xpath, and json payload' def deltaReports = new DeltaReportBuilder().actionRemove().withXpath('some xpath').build() def xpath = 'some xpath' @@ -117,7 +132,7 @@ class DeltaRestControllerSpec extends Specification { when: 'get delta request is performed using REST API' def response = mvc.perform(multipart(dataNodeBaseEndpointV2) - .param('json', requestBodyJson) + .file(multipartTargetDataAsJsonFile) .param('xpath', xpath) .contentType(MediaType.MULTIPART_FORM_DATA)) .andReturn().response @@ -126,4 +141,39 @@ class DeltaRestControllerSpec extends Specification { and: 'the response contains expected value' assert response.contentAsString.contains('[{\"action\":\"remove\",\"xpath\":\"some xpath\"}]') } + + def 'Attempt to get delta between anchor and JSON payload with an Empty File'() { + given: 'xpath, yang model file and empty json payload' + def xpath = 'some xpath' + def emptyTargetDataAsJsonFile = new MockMultipartFile('targetDataAsJsonFile', 'empty.json', 'application/json', new byte[0]) + when: 'get delta request is performed using REST API' + def response = mvc.perform(multipart(dataNodeBaseEndpointV2) + .file(emptyTargetDataAsJsonFile) + .param('xpath', xpath) + .contentType(MediaType.MULTIPART_FORM_DATA)) + .andReturn() + .response + then: 'expected response code is returned' + assert response.status == HttpStatus.BAD_REQUEST.value() + then: 'the response contains expected error message' + assert response.contentAsString.contains("JSON file is required") + } + + def 'Get delta between anchor and JSON payload with an invalid data'() { + given: 'xpath, yang model file and empty json payload' + def xpath = 'some xpath' + def invalidJsonContent = '{' + def invalidTargetDataAsJsonFile = new MockMultipartFile('targetDataAsJsonFile', 'invalid.json', 'application/json', invalidJsonContent.getBytes()) + when: 'get delta request is performed using REST API' + def response = mvc.perform(multipart(dataNodeBaseEndpointV2) + .file(invalidTargetDataAsJsonFile) + .param('xpath', xpath) + .contentType(MediaType.MULTIPART_FORM_DATA)) + .andReturn() + .response + then: 'expected response code is returned' + assert response.status == HttpStatus.BAD_REQUEST.value() + then: 'the response contains expected error message' + assert response.contentAsString.contains("Parsing error occurred while converting JSON content to Json Node") + } } diff --git a/docs/api/swagger/cps/openapi.yaml b/docs/api/swagger/cps/openapi.yaml index 154548797b..911fbc2ae6 100644 --- a/docs/api/swagger/cps/openapi.yaml +++ b/docs/api/swagger/cps/openapi.yaml @@ -19,6 +19,8 @@ tags: name: cps-admin - description: cps Data name: cps-data +- description: CPS Delta + name: cps-delta paths: /v1/dataspaces: post: @@ -2185,7 +2187,7 @@ paths: description: Internal Server Error summary: Get delta between anchors in the same dataspace tags: - - cps-data + - cps-delta x-codegen-request-body-name: xpath post: description: Get delta between an anchor in a dataspace and JSON payload @@ -2275,7 +2277,7 @@ paths: description: Internal Server Error summary: Get delta between an anchor and JSON payload tags: - - cps-data + - cps-delta /v1/dataspaces/{dataspace-name}/anchors/{anchor-name}/nodes/query: get: deprecated: true @@ -3180,21 +3182,16 @@ components: NotificationSubscriptionsDataSample: {} getDeltaByDataspaceAnchorAndPayload_request: properties: - json: - example: - test:bookstore: - bookstore-name: Chapters - categories: - - code: 1 - name: SciFi - - code: 2 - name: kids - type: object - file: + targetDataAsJsonFile: + example: "{$ref=components.yml#/components/schemas/TargetDataAsJsonFile}" + format: binary + type: string + yangResourceFile: + example: "{$ref=components.yml#/components/schemas/MultipartFile}" format: binary type: string required: - - json + - targetDataAsJsonFile type: object securitySchemes: basicAuth: diff --git a/docs/api/swagger/ncmp/openapi.yaml b/docs/api/swagger/ncmp/openapi.yaml index ec9b4fb251..1bc7083b85 100644 --- a/docs/api/swagger/ncmp/openapi.yaml +++ b/docs/api/swagger/ncmp/openapi.yaml @@ -32,11 +32,11 @@ paths: - description: | The `resourceIdentifier` parameter specifies the target resource in the GNBDUFunctionConfig model. For ONAP DMI Plugin, the format will follow RESTConf paths. Examples: - - All GNBDUFunctions: `'/ManagedElement=NRNode1/GNBDUFunction=1'` + - All GNBDUFunctions: `/ManagedElement=node1/GNBDUFunction=1` examples: sample 1: value: - resourceIdentifier: '/ManagedElement=NRNode1/GNBDUFunction=1' + resourceIdentifier: /ManagedElement=node1/GNBDUFunction=1 in: query name: resourceIdentifier required: true @@ -138,11 +138,11 @@ paths: - description: | The `resourceIdentifier` parameter specifies the target resource in the GNBDUFunctionConfig model. For ONAP DMI Plugin, the format will follow RESTConf paths. Examples: - - All GNBDUFunctions: `/ManagedElement=NRNode1/GNBDUFunction=1` + - All GNBDUFunctions: `/ManagedElement=node1/GNBDUFunction=1` examples: sample 1: value: - resourceIdentifier: '/ManagedElement=NRNode1/GNBDUFunction=1' + resourceIdentifier: /ManagedElement=node1/GNBDUFunction=1 in: query name: resourceIdentifier required: true @@ -152,18 +152,18 @@ paths: The `options` parameter specifies additional query options. It is mandatory to wrap key(s)=value(s) in parentheses `()`. Examples for GNBDUFunctionConfig queries: - Limit depth of returned sub-tree: `(depth=2)` - - Select specific fields: `(fields=id,gNBDUName)` - - Combine options: `(depth=3,fields=id,gNBDUName)` + - Select specific fields: `(fields=attributes(gNBId;gNBDUName))` + - Combine options: `(depth=3,fields=attributes(gNBId;gNBDUName))` examples: Limit Depth: value: options: (depth=2) Select Specific Fields: value: - options: "(fields=id,gNBDUName)" + options: (fields=attributes(gNBId;gNBDUName)) Combine Depth and Fields: value: - options: "(depth=3,fields=id,gNBDUName)" + options: "(depth=3,fields=attributes(gNBId;gNBDUName))" in: query name: options required: false @@ -271,11 +271,11 @@ paths: - description: | The `resourceIdentifier` parameter specifies the target resource in the GNBDUFunctionConfig model. For ONAP DMI Plugin, the format will follow RESTConf paths. Examples: - - All GNBDUFunctions: `/ManagedElement=NRNode1/GNBDUFunction=1` + - All GNBDUFunctions: `/ManagedElement=node1/GNBDUFunction=1` examples: sample 1: value: - resourceIdentifier: '/ManagedElement=NRNode1/GNBDUFunction=1' + resourceIdentifier: /ManagedElement=node1/GNBDUFunction=1 in: query name: resourceIdentifier required: true @@ -380,11 +380,11 @@ paths: - description: | The `resourceIdentifier` parameter specifies the target resource in the GNBDUFunctionConfig model. For ONAP DMI Plugin, the format will follow RESTConf paths. Examples: - - All GNBDUFunctions: `/ManagedElement=NRNode1/GNBDUFunction=1` + - All GNBDUFunctions: `/ManagedElement=node1/GNBDUFunction=1` examples: sample 1: value: - resourceIdentifier: '/ManagedElement=NRNode1/GNBDUFunction=1' + resourceIdentifier: /ManagedElement=node1/GNBDUFunction=1 in: query name: resourceIdentifier required: true @@ -494,11 +494,11 @@ paths: - description: | The `resourceIdentifier` parameter specifies the target resource in the GNBDUFunctionConfig model. For ONAP DMI Plugin, the format will follow RESTConf paths. Examples: - - All GNBDUFunctions: `/ManagedElement=NRNode1/GNBDUFunction=1` + - All GNBDUFunctions: `/ManagedElement=node1/GNBDUFunction=1` examples: sample 1: value: - resourceIdentifier: '/ManagedElement=NRNode1/GNBDUFunction=1' + resourceIdentifier: /ManagedElement=node1/GNBDUFunction=1 in: query name: resourceIdentifier required: true @@ -699,10 +699,10 @@ paths: schema: example: my-cm-handle type: string - - description: For more details on cps path, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html + - description: "For more details on cps path, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html" examples: container cps path: - value: '/GNBDUFunction' + value: //GNBDUFunction list attributes cps path: value: "//GNBDUFunction[@id='1001']" in: query @@ -723,7 +723,7 @@ paths: options: (depth=2) Select Specific Fields: value: - options: "(fields=attributes(gNBId;gNBDUName))" + options: (fields=attributes(gNBId;gNBDUName)) Combine Depth and Fields: value: options: "(depth=3,fields=attributes(gNBId;gNBDUName))" @@ -1478,11 +1478,11 @@ components: description: | The `resourceIdentifier` parameter specifies the target resource in the GNBDUFunctionConfig model. For ONAP DMI Plugin, the format will follow RESTConf paths. Examples: - - All GNBDUFunctions: `/ManagedElement=NRNode1/GNBDUFunction=1` + - All GNBDUFunctions: `/ManagedElement=node1/GNBDUFunction=1` examples: sample 1: value: - resourceIdentifier: '/ManagedElement=NRNode1/GNBDUFunction=1' + resourceIdentifier: /ManagedElement=node1/GNBDUFunction=1 in: query name: resourceIdentifier required: true @@ -1501,7 +1501,7 @@ components: options: (depth=2) Select Specific Fields: value: - options: "(fields=attributes(gNBId;gNBDUName))" + options: (fields=attributes(gNBId;gNBDUName)) Combine Depth and Fields: value: options: "(depth=3,fields=attributes(gNBId;gNBDUName))" @@ -1567,21 +1567,12 @@ components: example: my-cm-handle type: string cpsPathInQuery: - description: | - The `cps-path` parameter allows referencing elements in the GNBDUFunctionConfig data model. - For more details on cps path, please refer to: - [CPS Path Documentation](https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html). - Example paths: - - Root GNBDUFunction: `/GNBDUFunction` - - Specific gNB ID: `//GNBDUFunction[@id='1001']` - - RIM-RS Reporting Config: `//GNBDUFunction[@id='1001']/rimRSReportConf` + description: "For more details on cps path, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html" examples: - GNBDUFunction Root: - value: /GNBDUFunction - Specific gNB ID: + container cps path: + value: //GNBDUFunction + list attributes cps path: value: "//GNBDUFunction[@id='1001']" - RIM-RS Reporting Config: - value: "//GNBDUFunction[@id='1001']/rimRSReportConf" in: query name: cps-path required: false @@ -1719,7 +1710,7 @@ components: DataOperationRequest: example: operations: - - resourceIdentifier: '/ManagedElement=NRNode1/GNBDUFunction=1' + - resourceIdentifier: /ManagedElement=NRNode1/GNBDUFunction=1 targetIds: - "[\"da310eecdb8d44c2acc0ddaae01174b1\",\"c748c58f8e0b438f9fd1f28370b17d47\"\ ]" @@ -1729,7 +1720,7 @@ components: options: (fields=NRCellDU/attributes/cellLocalId) operationId: "12" operation: read - - resourceIdentifier: '/ManagedElement=NRNode1/GNBDUFunction=1' + - resourceIdentifier: /ManagedElement=NRNode1/GNBDUFunction=1 targetIds: - "[\"da310eecdb8d44c2acc0ddaae01174b1\",\"c748c58f8e0b438f9fd1f28370b17d47\"\ ]" @@ -1749,7 +1740,7 @@ components: type: object DataOperationDefinition: example: - resourceIdentifier: '/ManagedElement=NRNode1/GNBDUFunction=1' + resourceIdentifier: /ManagedElement=NRNode1/GNBDUFunction=1 targetIds: - "[\"da310eecdb8d44c2acc0ddaae01174b1\",\"c748c58f8e0b438f9fd1f28370b17d47\"\ ]" @@ -1773,7 +1764,7 @@ components: example: (fields=NRCellDU/attributes/cellLocalId) type: string resourceIdentifier: - example: '/ManagedElement=NRNode1/GNBDUFunction=1' + example: /ManagedElement=NRNode1/GNBDUFunction=1 type: string targetIds: items: -- 2.16.6