diff options
33 files changed, 517 insertions, 418 deletions
diff --git a/cps-application/src/main/resources/application.yml b/cps-application/src/main/resources/application.yml index 6f0807113d..83494d6545 100644 --- a/cps-application/src/main/resources/application.yml +++ b/cps-application/src/main/resources/application.yml @@ -160,6 +160,7 @@ cps: endpoint: ${ONAP_OTEL_EXPORTER_ENDPOINT:http://onap-otel-collector:4317} protocol: ${ONAP_OTEL_EXPORTER_PROTOCOL:grpc} enabled: ${ONAP_TRACING_ENABLED:false} + excluded-observation-names: ${ONAP_EXCLUDED_OBSERVATION_NAMES:tasks.scheduled.execution} # Actuator management: diff --git a/cps-ncmp-rest/docs/openapi/ncmp.yml b/cps-ncmp-rest/docs/openapi/ncmp.yml index d0b1f35eaa..6972463e3c 100755 --- a/cps-ncmp-rest/docs/openapi/ncmp.yml +++ b/cps-ncmp-rest/docs/openapi/ncmp.yml @@ -194,7 +194,7 @@ dataOperationForCmHandle: tags: - network-cm-proxy summary: Execute a data operation for group of cm handle ids - description: This request will be handled asynchronously using messaging to the supplied topic. The rest response will be an acknowledge with a requestId to identify the relevant messages. A maximum of 50 cm handles per operation is supported. + description: This request will be handled asynchronously using messaging to the supplied topic. The rest response will be an acknowledge with a requestId to identify the relevant messages. A maximum of 200 cm handles per operation is supported. operationId: executeDataOperationForCmHandles parameters: - $ref: 'components.yaml#/components/parameters/requiredTopicParamInQuery' diff --git a/cps-ncmp-rest/docs/openapi/openapi-inventory.yml b/cps-ncmp-rest/docs/openapi/openapi-inventory.yml index b794082875..d68cd8b141 100755 --- a/cps-ncmp-rest/docs/openapi/openapi-inventory.yml +++ b/cps-ncmp-rest/docs/openapi/openapi-inventory.yml @@ -21,7 +21,7 @@ openapi: 3.0.3 info: title: NCMP Inventory API description: NCMP Inventory API - version: "1.0" + version: "3.5.1" servers: - url: /ncmpInventory components: diff --git a/cps-ncmp-rest/docs/openapi/openapi.yml b/cps-ncmp-rest/docs/openapi/openapi.yml index dd6d7c8baa..bf23e4af4e 100755 --- a/cps-ncmp-rest/docs/openapi/openapi.yml +++ b/cps-ncmp-rest/docs/openapi/openapi.yml @@ -22,7 +22,7 @@ openapi: 3.0.3 info: title: NCMP to CPS Proxy API description: NCMP to CPS Proxy API - version: "1.0" + version: "3.5.1" servers: - url: /ncmp components: diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/config/OpenTelemetryConfig.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/config/OpenTelemetryConfig.java index cff3187966..a6a82b7936 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/config/OpenTelemetryConfig.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/config/OpenTelemetryConfig.java @@ -26,7 +26,11 @@ import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter; import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; import io.opentelemetry.sdk.extension.trace.jaeger.sampler.JaegerRemoteSampler; import io.opentelemetry.sdk.trace.samplers.Sampler; +import jakarta.annotation.PostConstruct; import java.time.Duration; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.actuate.autoconfigure.observation.ObservationRegistryCustomizer; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; @@ -37,11 +41,14 @@ import org.springframework.http.server.observation.ServerRequestObservationConte import org.springframework.util.AntPathMatcher; import org.springframework.util.PathMatcher; +/** + * Configuration class for setting up OpenTelemetry tracing in a Spring Boot application. + * This class provides beans for OTLP exporters (gRPC and HTTP), a Jaeger remote sampler, + * and customizes the ObservationRegistry to exclude certain endpoints from being observed. + */ @Configuration public class OpenTelemetryConfig { - public static final int JAEGER_REMOTE_SAMPLER_POLLING_INTERVAL_IN_SECOND = 30; - @Value("${spring.application.name:cps-application}") private String serviceId; @@ -51,9 +58,29 @@ public class OpenTelemetryConfig { @Value("${cps.tracing.sampler.jaeger_remote.endpoint:http://onap-otel-collector:14250}") private String jaegerRemoteSamplerUrl; + @Value("${cps.tracing.excluded-observation-names:tasks.scheduled.execution}") + private String excludedObservationNamesAsCsv; + + private static final int JAEGER_REMOTE_SAMPLER_POLLING_INTERVAL_IN_SECONDS = 30; + + private List<String> excludedObservationNames; + /** - * OTLP Exporter with Grpc exporter protocol. - */ + * Initializes the excludedObservationNames after the bean's properties have been set. + * This method is called by the Spring container during bean initialization. + */ + @PostConstruct + public void init() { + excludedObservationNames = Arrays.stream(excludedObservationNamesAsCsv.split(",")) + .map(String::trim) + .collect(Collectors.toList()); + } + + /** + * Creates an OTLP Exporter with gRPC protocol. + * + * @return OtlpGrpcSpanExporter bean if tracing is enabled and the exporter protocol is gRPC + */ @Bean @ConditionalOnExpression( "${cps.tracing.enabled} && 'grpc'.equals('${cps.tracing.exporter.protocol}')") @@ -62,7 +89,9 @@ public class OpenTelemetryConfig { } /** - * OTLP Exporter with HTTP exporter protocol. + * Creates an OTLP Exporter with HTTP protocol. + * + * @return OtlpHttpSpanExporter bean if tracing is enabled and the exporter protocol is HTTP */ @Bean @ConditionalOnExpression( @@ -72,39 +101,40 @@ public class OpenTelemetryConfig { } /** - * Jaeger Remote Sampler. + * Creates a Jaeger Remote Sampler. + * + * @return JaegerRemoteSampler bean if tracing is enabled */ @Bean @ConditionalOnProperty("cps.tracing.enabled") public JaegerRemoteSampler createJaegerRemoteSampler() { return JaegerRemoteSampler.builder() - .setEndpoint(jaegerRemoteSamplerUrl) - .setPollingInterval(Duration.ofSeconds(JAEGER_REMOTE_SAMPLER_POLLING_INTERVAL_IN_SECOND)) - .setInitialSampler(Sampler.alwaysOff()) - .setServiceName(serviceId) - .build(); + .setEndpoint(jaegerRemoteSamplerUrl) + .setPollingInterval(Duration.ofSeconds(JAEGER_REMOTE_SAMPLER_POLLING_INTERVAL_IN_SECONDS)) + .setInitialSampler(Sampler.alwaysOff()) + .setServiceName(serviceId) + .build(); } /** - * Excluding /actuator/** endpoints. - */ + * Customizes the ObservationRegistry to exclude /actuator/** endpoints from being observed. + * + * @return ObservationRegistryCustomizer bean if tracing is enabled + */ @Bean @ConditionalOnProperty("cps.tracing.enabled") - ObservationRegistryCustomizer<ObservationRegistry> skipActuatorEndpointsFromObservation() { + public ObservationRegistryCustomizer<ObservationRegistry> skipActuatorEndpointsFromObservation() { final PathMatcher pathMatcher = new AntPathMatcher("/"); return registry -> - registry.observationConfig().observationPredicate(observationPredicate(pathMatcher)); + registry.observationConfig().observationPredicate(observationPredicate(pathMatcher)); } - /** - * Excluding /actuator/** endpoints. - */ - static ObservationPredicate observationPredicate(final PathMatcher pathMatcher) { - return (name, context) -> { + private ObservationPredicate observationPredicate(final PathMatcher pathMatcher) { + return (observationName, context) -> { if (context instanceof ServerRequestObservationContext observationContext) { return !pathMatcher.match("/actuator/**", observationContext.getCarrier().getRequestURI()); } else { - return true; + return !excludedObservationNames.contains(observationName); } }; } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/utils/CmSubscriptionPersistenceService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/utils/CmSubscriptionPersistenceService.java index c71109013a..6b5ed908b8 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/utils/CmSubscriptionPersistenceService.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/utils/CmSubscriptionPersistenceService.java @@ -1,6 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2024 Nordix Foundation + * Modifications Copyright (C) 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. @@ -216,7 +217,7 @@ public class CmSubscriptionPersistenceService { cpsDataService.saveListElements(NCMP_DATASPACE_NAME, CM_SUBSCRIPTIONS_ANCHOR_NAME, CPS_PATH_QUERY_FOR_CM_SUBSCRIPTION_FILTERS_WITH_DATASTORE_AND_CMHANDLE.formatted( datastoreType.getDatastoreName(), cmHandleId), subscriptionDetailsAsJson, - OffsetDateTime.now()); + OffsetDateTime.now(), ContentType.JSON); } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImpl.java index cd1237b884..0ca2cd3407 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImpl.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImpl.java @@ -162,7 +162,7 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv Lists.partition(yangModelCmHandles, CMHANDLE_BATCH_SIZE)) { final String cmHandlesJsonData = createCmHandlesJsonData(yangModelCmHandleBatch); cpsDataService.saveListElements(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, - NCMP_DMI_REGISTRY_PARENT, cmHandlesJsonData, NO_TIMESTAMP); + NCMP_DMI_REGISTRY_PARENT, cmHandlesJsonData, NO_TIMESTAMP, ContentType.JSON); } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/OpenTelemetryCmNotificationSubscriptionConfigSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/OpenTelemetryCmNotificationSubscriptionConfigSpec.groovy deleted file mode 100644 index 0f6906942f..0000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/OpenTelemetryCmNotificationSubscriptionConfigSpec.groovy +++ /dev/null @@ -1,81 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2024 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.config - -import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter -import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter -import io.opentelemetry.sdk.extension.trace.jaeger.sampler.JaegerRemoteSampler -import org.spockframework.spring.SpringBean -import org.springframework.boot.actuate.autoconfigure.observation.ObservationRegistryCustomizer -import spock.lang.Shared -import spock.lang.Specification - -class OpenTelemetryConfigSpec extends Specification{ - - @Shared - @SpringBean - OpenTelemetryConfig openTelemetryConfig = new OpenTelemetryConfig() - - def setupSpec() { - openTelemetryConfig.tracingExporterEndpointUrl="http://tracingExporterEndpointUrl" - openTelemetryConfig.jaegerRemoteSamplerUrl="http://jaegerremotesamplerurl" - openTelemetryConfig.serviceId ="cps-application" - } - - def 'OpenTelemetryConfig Construction.'() { - expect: 'the system can create an instance' - new OpenTelemetryConfig() != null - } - - def 'OTLP Exporter creation with Grpc protocol'(){ - when: 'an OTLP exporter is created' - def result = openTelemetryConfig.createOtlpExporterGrpc() - then: 'an OTLP Exporter is created' - assert result instanceof OtlpGrpcSpanExporter - } - - def 'OTLP Exporter creation with HTTP protocol'(){ - when: 'an OTLP exporter is created' - def result = openTelemetryConfig.createOtlpExporterHttp() - then: 'an OTLP Exporter is created' - assert result instanceof OtlpHttpSpanExporter - and: - assert result.builder.endpoint=="http://tracingExporterEndpointUrl" - } - - def 'Jaeger Remote Sampler Creation'(){ - when: 'an OTLP exporter is created' - def result = openTelemetryConfig.createJaegerRemoteSampler() - then: 'an OTLP Exporter is created' - assert result instanceof JaegerRemoteSampler - and: - assert result.delegate.type=="remoteSampling" - and: - assert result.delegate.url.toString().startsWith("http://jaegerremotesamplerurl") - } - - def 'Skipping Acutator endpoints'(){ - when: 'an OTLP exporter is created' - def result = openTelemetryConfig.skipActuatorEndpointsFromObservation() - then: 'an OTLP Exporter is created' - assert result instanceof ObservationRegistryCustomizer - } -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/OpenTelemetryConfigSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/OpenTelemetryConfigSpec.groovy new file mode 100644 index 0000000000..cbff73113e --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/OpenTelemetryConfigSpec.groovy @@ -0,0 +1,113 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 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.config + +import io.micrometer.observation.ObservationPredicate +import io.micrometer.observation.ObservationRegistry +import io.micrometer.observation.ObservationRegistry.ObservationConfig +import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter +import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter +import io.opentelemetry.sdk.extension.trace.jaeger.sampler.JaegerRemoteSampler +import org.springframework.beans.factory.annotation.Value +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.http.server.observation.ServerRequestObservationContext +import org.springframework.mock.web.MockHttpServletRequest +import org.springframework.util.AntPathMatcher +import spock.lang.Specification + +@SpringBootTest(classes = [OpenTelemetryConfig]) +class OpenTelemetryConfigSpec extends Specification { + + def objectUnderTest + + @Value('${cps.tracing.exporter.endpoint}') + def tracingExporterEndpointUrl + + @Value('${cps.tracing.sampler.jaeger_remote.endpoint}') + def jaegerRemoteSamplerUrl + + def setup() { + objectUnderTest = new OpenTelemetryConfig( + serviceId: 'sample-app', + tracingExporterEndpointUrl: tracingExporterEndpointUrl, + jaegerRemoteSamplerUrl: jaegerRemoteSamplerUrl, + excludedObservationNames: ['excluded-task-name']) + } + + def 'OTLP exporter creation with Grpc protocol'() { + when: 'an OTLP exporter is created' + def result = objectUnderTest.createOtlpExporterGrpc() + then: 'expected an instance of OtlpGrpcSpanExporter' + assert result instanceof OtlpGrpcSpanExporter + } + + def 'OTLP exporter creation with HTTP protocol'() { + when: 'an OTLP exporter is created' + def result = objectUnderTest.createOtlpExporterHttp() + then: 'an OTLP Exporter is created' + assert result instanceof OtlpHttpSpanExporter + and: 'the endpoint is correctly set' + assert result.builder.endpoint == 'http://exporter-test-url' + } + + def 'Jaeger Remote Sampler Creation'() { + when: 'a Jaeger remote sampler is created' + def result = objectUnderTest.createJaegerRemoteSampler() + then: 'a Jaeger remote sampler is created' + assert result instanceof JaegerRemoteSampler + and: 'the sampler type is correct' + assert result.delegate.type == 'remoteSampling' + and: 'the sampler endpoint is correctly set' + assert result.delegate.url.toString().startsWith('http://jaeger-remote-test-url') + } + + def 'Skipping actuator endpoints'() { + given: 'a mocked observation registry and config' + def observationRegistry = Mock(ObservationRegistry.class) + def observationConfig = Mock(ObservationConfig.class) + observationRegistry.observationConfig() >> observationConfig + when: 'an observation registry customizer is created and applied' + def result = objectUnderTest.skipActuatorEndpointsFromObservation() + result.customize(observationRegistry) + then: 'the observation predicate is set correctly' + 1 * observationConfig.observationPredicate(_) >> { ObservationPredicate observationPredicate -> + def mockedHttpServletRequest = new MockHttpServletRequest(_ as String, requestUrl) + def serverRequestObservationContext = new ServerRequestObservationContext(mockedHttpServletRequest, null) + and: 'expected predicate for endpoint' + assert observationPredicate.test('some-name', serverRequestObservationContext) == expectedPredicate + } + where: 'the following parameters are used' + scenario | requestUrl || expectedPredicate + 'an actuator' | '/actuator' || false + 'a non actuator' | '/some-api' || true + } + + def 'Observation predicate is configured to filter out excluded tasks by name'() { + when: 'a path matcher and observation predicate' + def observationPredicate = objectUnderTest.observationPredicate(new AntPathMatcher('/')) + then: 'a task name is provided' + assert observationPredicate.test(taskName, null) == expectedPredicate + where: 'the following parameters are used' + taskName || expectedPredicate + 'excluded-task-name' || false + 'non-excluded-task-name' || true + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cmnotificationsubscription/utils/CmSubscriptionPersistenceServiceSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cmnotificationsubscription/utils/CmSubscriptionPersistenceServiceSpec.groovy index 354e2af937..2b91065592 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cmnotificationsubscription/utils/CmSubscriptionPersistenceServiceSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cmnotificationsubscription/utils/CmSubscriptionPersistenceServiceSpec.groovy @@ -142,7 +142,7 @@ class CmSubscriptionPersistenceServiceSpec extends Specification { 'NCMP-Admin', 'cm-data-subscriptions', parentNodeXpath.formatted(datastoreName, 'ch-1'), - objectUnderTest.getSubscriptionDetailsAsJson('/x/y', ['newSubId']), _) + objectUnderTest.getSubscriptionDetailsAsJson('/x/y', ['newSubId']), _, ContentType.JSON) where: scenario | datastoreType || datastoreName 'passthrough_running' | PASSTHROUGH_RUNNING || 'ncmp-datastore:passthrough-running' diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImplSpec.groovy index e098fb81d7..e60bacbdc5 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImplSpec.groovy @@ -232,7 +232,7 @@ class InventoryPersistenceImplSpec extends Specification { objectUnderTest.saveCmHandle(yangModelCmHandle) then: 'the data service method to save list elements is called once' 1 * mockCpsDataService.saveListElements(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, NCMP_DMI_REGISTRY_PARENT, - _,null) >> { + _,null, ContentType.JSON) >> { args -> { assert args[3].startsWith('{"cm-handles":[{"id":"cmhandle","additional-properties":[],"public-properties":[]}]}') } @@ -247,7 +247,7 @@ class InventoryPersistenceImplSpec extends Specification { objectUnderTest.saveCmHandleBatch([yangModelCmHandle1, yangModelCmHandle2]) then: 'CPS Data Service persists both cm handles as a batch' 1 * mockCpsDataService.saveListElements(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, - NCMP_DMI_REGISTRY_PARENT, _,null) >> { + NCMP_DMI_REGISTRY_PARENT, _,null, ContentType.JSON) >> { args -> { def jsonData = (args[3] as String) jsonData.contains('cmhandle1') diff --git a/cps-ncmp-service/src/test/resources/application.yml b/cps-ncmp-service/src/test/resources/application.yml index f0790dda4b..759de834ab 100644 --- a/cps-ncmp-service/src/test/resources/application.yml +++ b/cps-ncmp-service/src/test/resources/application.yml @@ -16,6 +16,15 @@ # SPDX-License-Identifier: Apache-2.0 # ============LICENSE_END========================================================= +cps: + tracing: + sampler: + jaeger_remote: + endpoint: http://jaeger-Remote-test-url + exporter: + endpoint: http://exporter-test-url + enabled: true + spring: kafka: producer: diff --git a/cps-rest/docs/openapi/cpsData.yml b/cps-rest/docs/openapi/cpsData.yml index 1e70ef60c8..4418a3b9b7 100644 --- a/cps-rest/docs/openapi/cpsData.yml +++ b/cps-rest/docs/openapi/cpsData.yml @@ -32,15 +32,24 @@ listElementByDataspaceAndAnchor: - $ref: 'components.yml#/components/parameters/anchorNameInPath' - $ref: 'components.yml#/components/parameters/requiredXpathInQuery' - $ref: 'components.yml#/components/parameters/observedTimestampInQuery' + - $ref: 'components.yml#/components/parameters/contentTypeInHeader' requestBody: required: true content: application/json: schema: - type: object + type: string examples: dataSample: $ref: 'components.yml#/components/examples/dataSample' + application/xml: + schema: + type: object + xml: + name: stores + examples: + dataSample: + $ref: 'components.yml#/components/examples/dataSampleXml' responses: '201': $ref: 'components.yml#/components/responses/Created' diff --git a/cps-rest/docs/openapi/openapi.yml b/cps-rest/docs/openapi/openapi.yml index b4e0b70408..ab841b280b 100644 --- a/cps-rest/docs/openapi/openapi.yml +++ b/cps-rest/docs/openapi/openapi.yml @@ -23,7 +23,7 @@ openapi: 3.0.3 info: title: ONAP Open API v3 Configuration Persistence Service description: Configuration Persistence Service is a Model Driven Generic Database - version: "1.0.0" + version: "3.5.1" contact: name: ONAP url: "https://onap.readthedocs.io" @@ -31,10 +31,6 @@ info: license: name: "Apache 2.0" url: "http://www.apache.org/licenses/LICENSE-2.0" - x-planned-retirement-date: "202212" - x-component: "Modeling" - x-logo: - url: "cps_logo.png" servers: - url: /cps/api 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 6100b7edd9..6015e0e3ae 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 @@ -95,9 +95,11 @@ public class DataRestController implements CpsDataApi { @Override public ResponseEntity<String> addListElements(final String apiVersion, final String dataspaceName, final String anchorName, final String parentNodeXpath, - final Object jsonData, final String observedTimestamp) { + final String contentTypeInHeader, final String nodeData, + final String observedTimestamp) { + final ContentType contentType = getContentTypeFromHeader(contentTypeInHeader); cpsDataService.saveListElements(dataspaceName, anchorName, parentNodeXpath, - jsonObjectMapper.asJsonString(jsonData), toOffsetDateTime(observedTimestamp)); + nodeData, toOffsetDateTime(observedTimestamp), contentType); return new ResponseEntity<>(HttpStatus.CREATED); } 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 205d85dc26..d8ab0d10eb 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 @@ -192,22 +192,25 @@ class DataRestControllerSpec extends Specification { def rootNodeXpath = '/' when: 'list-node endpoint is invoked with post (create) operation' def postRequestBuilder = post("$dataNodeBaseEndpointV1/anchors/$anchorName/list-nodes") - .contentType(MediaType.APPLICATION_JSON) + .contentType(contentType) .param('xpath', rootNodeXpath ) - .content(requestBodyJson) + .content(requestBody) if (observedTimestamp != null) postRequestBuilder.param('observed-timestamp', observedTimestamp) def response = mvc.perform(postRequestBuilder).andReturn().response then: 'a created response is returned' response.status == expectedHttpStatus.value() then: 'the java API was called with the correct parameters' - expectedApiCount * mockCpsDataService.saveListElements(dataspaceName, anchorName, rootNodeXpath, expectedJsonData, - { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) }) + expectedApiCount * mockCpsDataService.saveListElements(dataspaceName, anchorName, rootNodeXpath, expectedData, + { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) }, expectedContentType) where: - scenario | observedTimestamp || expectedApiCount | expectedHttpStatus - 'with observed-timestamp' | '2021-03-03T23:59:59.999-0400' || 1 | HttpStatus.CREATED - 'without observed-timestamp' | null || 1 | HttpStatus.CREATED - 'with invalid observed-timestamp' | 'invalid' || 0 | HttpStatus.BAD_REQUEST + scenario | observedTimestamp | contentType | requestBody || expectedApiCount | expectedHttpStatus | expectedData | expectedContentType + 'Content type JSON with observed-timestamp' | '2021-03-03T23:59:59.999-0400' | MediaType.APPLICATION_JSON | requestBodyJson || 1 | HttpStatus.CREATED | expectedJsonData | ContentType.JSON + 'Content type JSON without observed-timestamp' | null | MediaType.APPLICATION_JSON | requestBodyJson || 1 | HttpStatus.CREATED | expectedJsonData | ContentType.JSON + 'Content type JSON with invalid observed-timestamp' | 'invalid' | MediaType.APPLICATION_JSON | requestBodyJson || 0 | HttpStatus.BAD_REQUEST | expectedJsonData | ContentType.JSON + 'Content type XML with observed-timestamp' | '2021-03-03T23:59:59.999-0400' | MediaType.APPLICATION_XML | requestBodyXml || 1 | HttpStatus.CREATED | expectedXmlData | ContentType.XML + 'Content type XML without observed-timestamp' | null | MediaType.APPLICATION_XML | requestBodyXml || 1 | HttpStatus.CREATED | expectedXmlData | ContentType.XML + 'Content type XML with invalid observed-timestamp' | 'invalid' | MediaType.APPLICATION_XML | requestBodyXml || 0 | HttpStatus.BAD_REQUEST | expectedXmlData | ContentType.XML } def 'Save list elements #scenario.'() { @@ -215,22 +218,25 @@ class DataRestControllerSpec extends Specification { def parentNodeXpath = 'parent node xpath' when: 'list-node endpoint is invoked with post (create) operation' def postRequestBuilder = post("$dataNodeBaseEndpointV1/anchors/$anchorName/list-nodes") - .contentType(MediaType.APPLICATION_JSON) + .contentType(contentType) .param('xpath', parentNodeXpath) - .content(requestBodyJson) + .content(requestBody) if (observedTimestamp != null) postRequestBuilder.param('observed-timestamp', observedTimestamp) def response = mvc.perform(postRequestBuilder).andReturn().response then: 'a created response is returned' response.status == expectedHttpStatus.value() then: 'the java API was called with the correct parameters' - expectedApiCount * mockCpsDataService.saveListElements(dataspaceName, anchorName, parentNodeXpath, expectedJsonData, - { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) }) + expectedApiCount * mockCpsDataService.saveListElements(dataspaceName, anchorName, parentNodeXpath, expectedData, + { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) }, expectedContentType) where: - scenario | observedTimestamp || expectedApiCount | expectedHttpStatus - 'with observed-timestamp' | '2021-03-03T23:59:59.999-0400' || 1 | HttpStatus.CREATED - 'without observed-timestamp' | null || 1 | HttpStatus.CREATED - 'with invalid observed-timestamp' | 'invalid' || 0 | HttpStatus.BAD_REQUEST + scenario | observedTimestamp | contentType | requestBody || expectedApiCount | expectedHttpStatus | expectedData | expectedContentType + 'Content type JSON with observed-timestamp' | '2021-03-03T23:59:59.999-0400' | MediaType.APPLICATION_JSON | requestBodyJson || 1 | HttpStatus.CREATED | expectedJsonData | ContentType.JSON + 'Content type JSON without observed-timestamp' | null | MediaType.APPLICATION_JSON | requestBodyJson || 1 | HttpStatus.CREATED | expectedJsonData | ContentType.JSON + 'Content type JSON with invalid observed-timestamp' | 'invalid' | MediaType.APPLICATION_JSON | requestBodyJson || 0 | HttpStatus.BAD_REQUEST | expectedJsonData | ContentType.JSON + 'Content type XML with observed-timestamp' | '2021-03-03T23:59:59.999-0400' | MediaType.APPLICATION_XML | requestBodyXml || 1 | HttpStatus.CREATED | expectedXmlData | ContentType.XML + 'Content type XML without observed-timestamp' | null | MediaType.APPLICATION_XML | requestBodyXml || 1 | HttpStatus.CREATED | expectedXmlData | ContentType.XML + 'Content type XML with invalid observed-timestamp' | 'invalid' | MediaType.APPLICATION_XML | requestBodyXml || 0 | HttpStatus.BAD_REQUEST | expectedXmlData | ContentType.XML } def 'Get data node with leaves'() { diff --git a/cps-service/src/main/java/org/onap/cps/api/CpsDataService.java b/cps-service/src/main/java/org/onap/cps/api/CpsDataService.java index cfa5f2de20..68e1880d77 100644 --- a/cps-service/src/main/java/org/onap/cps/api/CpsDataService.java +++ b/cps-service/src/main/java/org/onap/cps/api/CpsDataService.java @@ -93,11 +93,12 @@ public interface CpsDataService { * @param dataspaceName dataspace name * @param anchorName anchor name * @param parentNodeXpath parent node xpath - * @param jsonData json data representing list element(s) + * @param nodeData node data representing list element(s) * @param observedTimestamp observedTimestamp + * @param contentType JSON/XML content type */ - void saveListElements(String dataspaceName, String anchorName, String parentNodeXpath, String jsonData, - OffsetDateTime observedTimestamp); + void saveListElements(String dataspaceName, String anchorName, String parentNodeXpath, String nodeData, + OffsetDateTime observedTimestamp, ContentType contentType); /** * Retrieves all the datanodes by XPath for given dataspace and anchor. diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java index c65bc5e0e9..165d62cede 100644 --- a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java +++ b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java @@ -119,12 +119,13 @@ public class CpsDataServiceImpl implements CpsDataService { @Override @Timed(value = "cps.data.service.list.element.save", description = "Time taken to save list elements") - public void saveListElements(final String dataspaceName, final String anchorName, final String parentNodeXpath, - final String jsonData, final OffsetDateTime observedTimestamp) { + public void saveListElements(final String dataspaceName, final String anchorName, + final String parentNodeXpath, final String nodeData, + final OffsetDateTime observedTimestamp, final ContentType contentType) { cpsValidator.validateNameCharacters(dataspaceName, anchorName); final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName); final Collection<DataNode> listElementDataNodeCollection = - buildDataNodesWithParentNodeXpath(anchor, parentNodeXpath, jsonData, ContentType.JSON); + buildDataNodesWithParentNodeXpath(anchor, parentNodeXpath, nodeData, contentType); if (isRootNodeXpath(parentNodeXpath)) { cpsDataPersistenceService.storeDataNodes(dataspaceName, anchorName, listElementDataNodeCollection); } else { diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy index 0d3c5aa834..a296716b59 100644 --- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy @@ -141,7 +141,7 @@ class CpsDataServiceImplSpec extends Specification { setupSchemaSetMocks('bookstore.yang') when: 'save data method is invoked with list element json data' def jsonData = '{"bookstore-address":[{"bookstore-name":"Easons","address":"Dublin,Ireland","postal-code":"D02HA21"}]}' - objectUnderTest.saveListElements(dataspaceName, anchorName, '/', jsonData, observedTimestamp) + objectUnderTest.saveListElements(dataspaceName, anchorName, '/', jsonData, observedTimestamp, ContentType.JSON) then: 'the persistence service method is invoked with correct parameters' 1 * mockCpsDataPersistenceService.storeDataNodes(dataspaceName, anchorName, { dataNodeCollection -> @@ -169,12 +169,11 @@ class CpsDataServiceImplSpec extends Specification { 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName) } - def 'Saving list element data fragment under existing node.'() { + def 'Saving list element data fragment under existing JSON/XML node.'() { given: 'schema set for given anchor and dataspace references test-tree model' setupSchemaSetMocks('test-tree.yang') - when: 'save data method is invoked with list element json data' - def jsonData = '{"branch": [{"name": "A"}, {"name": "B"}]}' - objectUnderTest.saveListElements(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp) + when: 'save data method is invoked with list element data' + objectUnderTest.saveListElements(dataspaceName, anchorName, '/test-tree', data, observedTimestamp, contentType) then: 'the persistence service method is invoked with correct parameters' 1 * mockCpsDataPersistenceService.addListElements(dataspaceName, anchorName, '/test-tree', { dataNodeCollection -> @@ -187,16 +186,23 @@ class CpsDataServiceImplSpec extends Specification { ) and: 'the CpsValidator is called on the dataspaceName and AnchorName' 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName) + where: + data | contentType + '{"branch": [{"name": "A"}, {"name": "B"}]}' | ContentType.JSON + '<test-tree xmlns="org:onap:cps:test:test-tree"><branch><name>A</name></branch><branch><name>B</name></branch></test-tree>' | ContentType.XML } - def 'Saving empty list element data fragment.'() { + def 'Saving empty list element data fragment for JSON/XML data.'() { given: 'schema set for given anchor and dataspace references test-tree model' setupSchemaSetMocks('test-tree.yang') when: 'save data method is invoked with an empty list' - def jsonData = '{"branch": []}' - objectUnderTest.saveListElements(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp) + objectUnderTest.saveListElements(dataspaceName, anchorName, '/test-tree', data, observedTimestamp, contentType) then: 'invalid data exception is thrown' thrown(DataValidationException) + where: + data | contentType + '{"branch": []}' | ContentType.JSON + '<test-tree><branch></branch></test-tree>' | ContentType.XML } def 'Get all data nodes #scenario.'() { diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index f537cfe43c..5af325a50b 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -20,6 +20,7 @@ services: ### docker-compose --profile dmi-service up -d -> run CPS services incl. dmi-plugin ### ### docker-compose --profile dmi-stub --profile monitoring up -d -> run CPS with stubbed dmi-plugin (for registration performance testing) + ### docker-compose --profile dmi-stub --profile tracing up -d -> run CPS with stubbed dmi-plugin (for open telemetry tracing testing make ONAP_TRACING_ENABLED "true" later "http://localhost:16686" can be accessed from browser) ### to disable notifications make notification.enabled to false & comment out kafka/zookeeper services ### dbpostgresql: @@ -54,6 +55,9 @@ services: DMI_PASSWORD: ${DMI_PASSWORD:-cpsr0cks!} KAFKA_BOOTSTRAP_SERVER: kafka:29092 notification.enabled: 'true' + ONAP_TRACING_ENABLED: 'false' + ONAP_OTEL_SAMPLER_JAEGER_REMOTE_ENDPOINT: http://jaeger-service:14250 + ONAP_OTEL_EXPORTER_ENDPOINT: http://jaeger-service:4317 restart: unless-stopped depends_on: - dbpostgresql @@ -193,5 +197,14 @@ services: profiles: - monitoring + jaeger-service: + container_name: jaeger-service + image: jaegertracing/all-in-one:latest + ports: + - 16686:16686 + restart: unless-stopped + profiles: + - tracing + volumes: grafana: diff --git a/docs/api/swagger/policy-executor/openapi.yaml b/docs/api/swagger/policy-executor/openapi.yaml index 98c5b1e79a..f80b998369 100644 --- a/docs/api/swagger/policy-executor/openapi.yaml +++ b/docs/api/swagger/policy-executor/openapi.yaml @@ -52,6 +52,8 @@ paths: $ref: '#/components/schemas/PolicyExecutionResponse' '400': $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '500': @@ -75,48 +77,34 @@ components: details: type: string - Payload: + Request: type: object properties: - targetFdn: + schema: type: string - description: "The complete FDN (Fully Distinguished Name) for the element to be changed" - example: "/Subnetwork=Ireland/MeContext=Athlone/ManagedElement=Athlone/SomeFunction=1/Cell=12" - cmHandleId: - type: string - description: "The CM handle ID (optional)" - example: "F811AF64F5146DFC545EC60B73DE948E" - resourceIdentifier: - type: string - description: "The resource identifier (optional)" - example: "ManagedElement=Athlone/SomeFunction=1/Cell=12" - cmChangeRequest: + description: "The schema for the data in this request. The schema name should include the type of operation" + example: "org.onap.cps.ncmp.policy-executor:ncmp-create-schema:1.0.0" + data: type: object - description: "The content of the change to be made" - example: '{"Cell":[{"id":"Cell-id","attributes":{"administrativeState":"UNLOCKED"}}]}' + description: "The data related to the request. The format of the object is determined by the schema" required: - - targetFdn - - cmChangeRequest + - schema + - data PolicyExecutionRequest: type: object properties: - payloadType: - type: string - description: "The type of payload. Currently supported options: 'cm_write'" - example: "cm_write" decisionType: type: string - description: "The type of decision. Currently supported options: 'permit'" - example: "permit" - payload: + description: "The type of decision. Currently supported options: 'allow'" + example: "allow" + requests: type: array items: - $ref: '#/components/schemas/Payload' + $ref: '#/components/schemas/Request' required: - - payloadType - decisionType - - payload + - requests PolicyExecutionResponse: type: object @@ -127,7 +115,7 @@ components: example: "550e8400-e29b-41d4-a716-446655440000" decision: type: string - description: "The decision outcome. Currently supported values: 'permit','deny'" + description: "The decision outcome. Currently supported values: 'allow','deny'" example: "deny" message: type: string @@ -139,16 +127,16 @@ components: - message responses: - NotFound: - description: "The specified resource was not found" + BadRequest: + description: "Bad request" content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' example: - status: 404 - message: "Resource Not Found" - details: "The requested resource is not found" + status: 400 + message: "Bad Request" + details: "The provided request is not valid" Unauthorized: description: "Unauthorized request" content: @@ -169,16 +157,6 @@ components: status: 403 message: "Request Forbidden" details: "This request is forbidden" - BadRequest: - description: "Bad request" - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorMessage' - example: - status: 400 - message: "Bad Request" - details: "The provided request is not valid" InternalServerError: description: "Internal server error" diff --git a/docs/schemas/policy-executor/ncmp-create-schema-1.0.0.json b/docs/schemas/policy-executor/ncmp-create-schema-1.0.0.json new file mode 100644 index 0000000000..2ec9daf949 --- /dev/null +++ b/docs/schemas/policy-executor/ncmp-create-schema-1.0.0.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "urn:cps:org.onap.cps.ncmp.policy-executor:ncmp-create-schema:1.0.0", + "$ref": "#/definitions/NcmpCreate", + "definitions": { + "NcmpCreate": { + "type": "object", + "additionalProperties": false, + "properties": { + "cmHandleId": { + "type": "string" + }, + "resourceIdentifier": { + "type": "string" + }, + "targetIdentifier": { + "type": "string" + }, + "cmChangeRequest": { + "type": "object" + } + }, + "required": [ + "targetIdentifier", + "cmChangeRequest" + ] + } + } +} diff --git a/docs/schemas/policy-executor/ncmp-delete-schema-1.0.0.json b/docs/schemas/policy-executor/ncmp-delete-schema-1.0.0.json new file mode 100644 index 0000000000..5df0325e39 --- /dev/null +++ b/docs/schemas/policy-executor/ncmp-delete-schema-1.0.0.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "urn:cps:org.onap.cps.ncmp.policy-executor:ncmp-delete-schema:1.0.0", + "$ref": "#/definitions/NcmpDelete", + "definitions": { + "NcmpDelete": { + "type": "object", + "additionalProperties": false, + "properties": { + "cmHandleId": { + "type": "string" + }, + "resourceIdentifier": { + "type": "string" + }, + "targetIdentifier": { + "type": "string" + } + }, + "required": [ + "targetIdentifier" + ] + } + } +} diff --git a/docs/schemas/policy-executor/ncmp-patch-schema-1.0.0.json b/docs/schemas/policy-executor/ncmp-patch-schema-1.0.0.json new file mode 100644 index 0000000000..e26c244c94 --- /dev/null +++ b/docs/schemas/policy-executor/ncmp-patch-schema-1.0.0.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "urn:cps:org.onap.cps.ncmp.policy-executor:ncmp-patch-schema:1.0.0", + "$ref": "#/definitions/NcmpPatch", + "definitions": { + "NcmpPatch": { + "type": "object", + "additionalProperties": false, + "properties": { + "cmHandleId": { + "type": "string" + }, + "resourceIdentifier": { + "type": "string" + }, + "targetIdentifier": { + "type": "string" + }, + "cmChangeRequest": { + "type": "object" + } + }, + "required": [ + "targetIdentifier", + "cmChangeRequest" + ] + } + } +} diff --git a/docs/schemas/policy-executor/ncmp-update-schema-1.0.0.json b/docs/schemas/policy-executor/ncmp-update-schema-1.0.0.json new file mode 100644 index 0000000000..0a497e38c5 --- /dev/null +++ b/docs/schemas/policy-executor/ncmp-update-schema-1.0.0.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "urn:cps:org.onap.cps.ncmp.policy-executor:ncmp-update-schema:1.0.0", + "$ref": "#/definitions/NcmpUpdate", + "definitions": { + "NcmpUpdate": { + "type": "object", + "additionalProperties": false, + "properties": { + "cmHandleId": { + "type": "string" + }, + "resourceIdentifier": { + "type": "string" + }, + "targetIdentifier": { + "type": "string" + }, + "cmChangeRequest": { + "type": "object" + } + }, + "required": [ + "targetIdentifier", + "cmChangeRequest" + ] + } + } +} diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/DataServiceIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/DataServiceIntegrationSpec.groovy index 869b72d8ce..b274324d71 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/DataServiceIntegrationSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/DataServiceIntegrationSpec.groovy @@ -224,7 +224,7 @@ class DataServiceIntegrationSpec extends FunctionalSpecBase { given: 'a new (multiple-data-tree:invoice) datanodes' def json = '{"bookstore-address":[{"bookstore-name":"Easons","address":"Bangalore,India","postal-code":"560043"}]}' when: 'the new list elements are saved' - objectUnderTest.saveListElements(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/', json, now) + objectUnderTest.saveListElements(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/', json, now, ContentType.JSON) then: 'they can be retrieved by their xpaths' objectUnderTest.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/bookstore-address[@bookstore-name="Easons"]', INCLUDE_ALL_DESCENDANTS) and: 'there is one extra datanode' @@ -239,7 +239,7 @@ class DataServiceIntegrationSpec extends FunctionalSpecBase { given: 'two new (categories) data nodes' def json = '{"categories": [ {"code":"new1"}, {"code":"new2" } ] }' when: 'the new list elements are saved' - objectUnderTest.saveListElements(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/bookstore', json, now) + objectUnderTest.saveListElements(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/bookstore', json, now, ContentType.JSON) then: 'they can be retrieved by their xpaths' objectUnderTest.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, '/bookstore/categories[@code="new1"]', DIRECT_CHILDREN_ONLY).size() == 1 objectUnderTest.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, '/bookstore/categories[@code="new2"]', DIRECT_CHILDREN_ONLY).size() == 1 @@ -256,7 +256,7 @@ class DataServiceIntegrationSpec extends FunctionalSpecBase { given: 'two (categories) data nodes, one new and one existing' def json = '{"categories": [ {"code":"1"}, {"code":"new1"} ] }' when: 'attempt to save the list element' - objectUnderTest.saveListElements(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/bookstore', json, now) + objectUnderTest.saveListElements(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/bookstore', json, now, ContentType.JSON) then: 'an exception that (one cps paths is) already defined is thrown ' def exceptionThrown = thrown(AlreadyDefinedException) exceptionThrown.alreadyDefinedObjectNames == ['/bookstore/categories[@code=\'1\']' ] as Set @@ -270,7 +270,7 @@ class DataServiceIntegrationSpec extends FunctionalSpecBase { given: 'a new (categories) data nodes' def json = '{"categories": [ {"code":"new1"} ] }' and: 'the new list element is saved' - objectUnderTest.saveListElements(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/bookstore', json, now) + objectUnderTest.saveListElements(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/bookstore', json, now, ContentType.JSON) when: 'the new element is deleted' objectUnderTest.deleteListOrListElement(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, '/bookstore/categories[@code="new1"]', now) then: 'the original number of data nodes is restored' @@ -281,7 +281,7 @@ class DataServiceIntegrationSpec extends FunctionalSpecBase { given: 'two new (categories) data nodes in a single batch' def json = '{"categories": [ {"code":"new1"}, {"code":"new2"} ] }' when: 'the batches of new list element(s) are saved' - objectUnderTest.saveListElements(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/bookstore', json, now) + objectUnderTest.saveListElements(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/bookstore', json, now, ContentType.JSON) then: 'they can be retrieved by their xpaths' assert objectUnderTest.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, '/bookstore/categories[@code="new1"]', DIRECT_CHILDREN_ONLY).size() == 1 assert objectUnderTest.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, '/bookstore/categories[@code="new2"]', DIRECT_CHILDREN_ONLY).size() == 1 @@ -298,7 +298,7 @@ class DataServiceIntegrationSpec extends FunctionalSpecBase { given: 'one existing and one new (categories) data nodes in a single batch' def json = '{"categories": [ {"code":"new1"}, {"code":"1"} ] }' when: 'the batches of new list element(s) are saved' - objectUnderTest.saveListElements(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/bookstore', json, now) + objectUnderTest.saveListElements(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/bookstore', json, now, ContentType.JSON) then: 'an already defined (batch) exception is thrown for the existing path' def exceptionThrown = thrown(AlreadyDefinedException) assert exceptionThrown.alreadyDefinedObjectNames == ['/bookstore/categories[@code=\'1\']' ] as Set diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/performance/base/NcmpPerfTestBase.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/performance/base/NcmpPerfTestBase.groovy index 0ca200211a..f6ae27d129 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/performance/base/NcmpPerfTestBase.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/performance/base/NcmpPerfTestBase.groovy @@ -1,6 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2023-2024 Nordix Foundation + * Modifications Copyright (C) 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. @@ -22,6 +23,7 @@ package org.onap.cps.integration.performance.base import org.onap.cps.integration.ResourceMeter import org.onap.cps.spi.FetchDescendantsOption +import org.onap.cps.utils.ContentType import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR @@ -79,7 +81,7 @@ class NcmpPerfTestBase extends PerfTestBase { def batchSize = 100 for (def i = 0; i < TOTAL_CM_HANDLES; i += batchSize) { def data = '{ "cm-handles": [' + (1..batchSize).collect { innerNodeJsonTemplate.replace('CMHANDLE_ID_HERE', (it + i).toString()) }.join(',') + ']}' - cpsDataService.saveListElements(NCMP_PERFORMANCE_TEST_DATASPACE, REGISTRY_ANCHOR, '/dmi-registry', data, now) + cpsDataService.saveListElements(NCMP_PERFORMANCE_TEST_DATASPACE, REGISTRY_ANCHOR, '/dmi-registry', data, now, ContentType.JSON) } } @@ -91,7 +93,7 @@ class NcmpPerfTestBase extends PerfTestBase { innerNodeJsonTemplate.replace('CM_HANDLE_ID_HERE', (it + i).toString()) .replace('ALTERNATE_ID_AS_PATH', (it + i).toString()) }.join(',') + ']}' - cpsDataService.saveListElements(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry', data, now) + cpsDataService.saveListElements(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry', data, now, ContentType.JSON) } } diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/WritePerfTest.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/WritePerfTest.groovy index 0195611740..9f6c78d5f5 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/WritePerfTest.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/WritePerfTest.groovy @@ -1,6 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2023-2024 Nordix Foundation + * Modifications Copyright (C) 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. @@ -20,6 +21,8 @@ package org.onap.cps.integration.performance.cps +import org.onap.cps.utils.ContentType + import java.time.OffsetDateTime import org.onap.cps.integration.performance.base.CpsPerfTestBase @@ -87,7 +90,7 @@ class WritePerfTest extends CpsPerfTestBase { ']}' when: 'device nodes are added' resourceMeter.start() - cpsDataService.saveListElements(CPS_PERFORMANCE_TEST_DATASPACE, WRITE_TEST_ANCHOR, '/openroadm-devices', jsonListData, OffsetDateTime.now()) + cpsDataService.saveListElements(CPS_PERFORMANCE_TEST_DATASPACE, WRITE_TEST_ANCHOR, '/openroadm-devices', jsonListData, OffsetDateTime.now(), ContentType.JSON) resourceMeter.stop() then: 'the operation takes less than #expectedDuration and memory used is within limit' recordAndAssertResourceUsage("Saving list of ${totalNodes} devices", diff --git a/policy-executor-stub/pom.xml b/policy-executor-stub/pom.xml index f076a2c55c..afdc1c7d3b 100644 --- a/policy-executor-stub/pom.xml +++ b/policy-executor-stub/pom.xml @@ -22,6 +22,11 @@ </properties> <dependencies> + <dependency> + <groupId>org.projectlombok</groupId> + <artifactId>lombok</artifactId> + <scope>provided</scope> + </dependency> <!-- S P R I N G D E P E N D E N C I E S --> <dependency> <groupId>org.springframework.boot</groupId> @@ -163,6 +168,20 @@ </execution> </executions> </plugin> + + <plugin> + <groupId>org.jsonschema2pojo</groupId> + <artifactId>jsonschema2pojo-maven-plugin</artifactId> + <configuration> + <useJakartaValidation>true</useJakartaValidation> + <sourceDirectory>${project.parent.basedir}/../docs/schemas/policy-executor</sourceDirectory> + <targetPackage>org.onap.cps.policyexecutor.stub.model</targetPackage> + <generateBuilders>true</generateBuilders> + <serializable>true</serializable> + <includeJsr303Annotations>true</includeJsr303Annotations> + </configuration> + </plugin> + </plugins> </build> diff --git a/policy-executor-stub/src/main/java/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubController.java b/policy-executor-stub/src/main/java/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubController.java index a5ec6dcac9..5b3a9931a0 100644 --- a/policy-executor-stub/src/main/java/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubController.java +++ b/policy-executor-stub/src/main/java/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubController.java @@ -20,12 +20,17 @@ package org.onap.cps.policyexecutor.stub.controller; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; +import lombok.RequiredArgsConstructor; import org.onap.cps.policyexecutor.stub.api.PolicyExecutorApi; +import org.onap.cps.policyexecutor.stub.model.NcmpDelete; import org.onap.cps.policyexecutor.stub.model.PolicyExecutionRequest; import org.onap.cps.policyexecutor.stub.model.PolicyExecutionResponse; +import org.onap.cps.policyexecutor.stub.model.Request; import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatusCode; import org.springframework.http.ResponseEntity; @@ -34,9 +39,13 @@ import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("${rest.api.policy-executor-base-path}") +@RequiredArgsConstructor public class PolicyExecutorStubController implements PolicyExecutorApi { - private final Pattern errorCodePattern = Pattern.compile("(\\d{3})"); + private final ObjectMapper objectMapper; + + private static final Pattern ERROR_CODE_PATTERN = Pattern.compile("(\\d{3})"); + private int decisionCounter = 0; @Override @@ -44,31 +53,55 @@ public class PolicyExecutorStubController implements PolicyExecutorApi { final String action, final PolicyExecutionRequest policyExecutionRequest, final String authorization) { - if (policyExecutionRequest.getPayload().isEmpty()) { + if (policyExecutionRequest.getRequests().isEmpty()) { + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + } + final Request firstRequest = policyExecutionRequest.getRequests().iterator().next(); + if ("ncmp-delete-schema:1.0.0".equals(firstRequest.getSchema())) { + return handleNcmpDeleteSchema(firstRequest); + } + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + } + + private ResponseEntity<PolicyExecutionResponse> handleNcmpDeleteSchema(final Request request) { + final NcmpDelete ncmpDelete; + try { + ncmpDelete = objectMapper.readValue((String) request.getData(), NcmpDelete.class); + } catch (final JsonProcessingException e) { return new ResponseEntity<>(HttpStatus.BAD_REQUEST); } - final String firstTargetFdn = policyExecutionRequest.getPayload().iterator().next().getTargetFdn(); + final String targetIdentifier = ncmpDelete.getTargetIdentifier(); + if (targetIdentifier == null) { + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + } - final Matcher matcher = errorCodePattern.matcher(firstTargetFdn); + final Matcher matcher = ERROR_CODE_PATTERN.matcher(targetIdentifier); if (matcher.find()) { final int errorCode = Integer.parseInt(matcher.group(1)); return new ResponseEntity<>(HttpStatusCode.valueOf(errorCode)); } + return createPolicyExecutionResponse(targetIdentifier); + } + + private ResponseEntity<PolicyExecutionResponse> createPolicyExecutionResponse(final String targetIdentifier) { final String decisionId = String.valueOf(++decisionCounter); final String decision; final String message; - if (firstTargetFdn.toLowerCase(Locale.getDefault()).contains("cps-is-great")) { - decision = "permit"; + if (targetIdentifier.toLowerCase(Locale.getDefault()).contains("cps-is-great")) { + decision = "allow"; message = "All good"; } else { decision = "deny"; - message = "Only FDNs containing 'cps-is-great' are permitted"; + message = "Only FDNs containing 'cps-is-great' are allowed"; } + final PolicyExecutionResponse policyExecutionResponse = new PolicyExecutionResponse(decisionId, decision, message); + return ResponseEntity.ok(policyExecutionResponse); } + } diff --git a/policy-executor-stub/src/test/groovy/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubControllerSpec.groovy b/policy-executor-stub/src/test/groovy/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubControllerSpec.groovy index 871db81ac8..efb12ac619 100644 --- a/policy-executor-stub/src/test/groovy/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubControllerSpec.groovy +++ b/policy-executor-stub/src/test/groovy/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubControllerSpec.groovy @@ -21,9 +21,10 @@ package org.onap.cps.policyexecutor.stub.controller import com.fasterxml.jackson.databind.ObjectMapper -import org.onap.cps.policyexecutor.stub.model.Payload +import org.onap.cps.policyexecutor.stub.model.NcmpDelete import org.onap.cps.policyexecutor.stub.model.PolicyExecutionRequest import org.onap.cps.policyexecutor.stub.model.PolicyExecutionResponse +import org.onap.cps.policyexecutor.stub.model.Request import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest import org.springframework.http.HttpStatus @@ -44,11 +45,9 @@ class PolicyExecutorStubControllerSpec extends Specification { def url = '/policy-executor/api/v1/some-action' - def 'Execute Policy Actions.'() { - given: 'a policy execution request with target fdn: #targetFdn' - def payload = new Payload(targetFdn, 'some change request') - def policyExecutionRequest = new PolicyExecutionRequest('some payload type','some decision type', [payload]) - def requestBody = objectMapper.writeValueAsString(policyExecutionRequest) + def 'Execute policy action.'() { + given: 'a policy execution request with target: #targetIdentifier' + def requestBody = createRequestBody(targetIdentifier) when: 'request is posted' def response = mockMvc.perform(post(url) .header('Authorization','some string') @@ -61,19 +60,17 @@ class PolicyExecutorStubControllerSpec extends Specification { def responseBody = response.contentAsString def policyExecutionResponse = objectMapper.readValue(responseBody, PolicyExecutionResponse.class) assert policyExecutionResponse.decisionId == expectedDecsisonId - assert policyExecutionResponse.decision == expectedDecsison + assert policyExecutionResponse.decision == expectedDecision assert policyExecutionResponse.message == expectedMessage where: 'the following targets are used' - targetFdn || expectedDecsisonId | expectedDecsison | expectedMessage - 'some fdn' || '1' | 'deny' | "Only FDNs containing 'cps-is-great' are permitted" - 'fdn with cps-is-great' || '2' | 'permit' | "All good" + targetIdentifier || expectedDecsisonId | expectedDecision | expectedMessage + 'some fdn' || '1' | 'deny' | "Only FDNs containing 'cps-is-great' are allowed" + 'fdn with cps-is-great' || '2' | 'allow' | 'All good' } - def 'Execute Policy Action with a HTTP Error Code.'() { + def 'Execute policy action with a HTTP error code.'() { given: 'a policy execution request with a target fdn with a 3-digit error code' - def payload = new Payload('fdn with error code 418', 'some change request') - def policyExecutionRequest = new PolicyExecutionRequest('some payload type','some decision type', [payload]) - def requestBody = objectMapper.writeValueAsString(policyExecutionRequest) + def requestBody = createRequestBody('target with error code 418') when: 'request is posted' def response = mockMvc.perform(post(url) .header('Authorization','some string') @@ -84,11 +81,9 @@ class PolicyExecutorStubControllerSpec extends Specification { assert response.status == 418 } - def 'Execute Policy Action without Authorization Header.'() { + def 'Execute policy action without authorization header.'() { given: 'a valid policy execution request' - def payload = new Payload('some fdn', 'some change request') - def policyExecutionRequest = new PolicyExecutionRequest('some payload type','some decision type', [payload]) - def requestBody = objectMapper.writeValueAsString(policyExecutionRequest) + def requestBody = createRequestBody('some target') when: 'request is posted without authorization header' def response = mockMvc.perform(post(url) .contentType(MediaType.APPLICATION_JSON) @@ -98,9 +93,9 @@ class PolicyExecutorStubControllerSpec extends Specification { assert response.status == HttpStatus.OK.value() } - def 'Execute Policy Action with Empty Payload.'() { - given: 'a policy execution request with empty payload list' - def policyExecutionRequest = new PolicyExecutionRequest('some payload type','some decision type', []) + def 'Execute policy action with no requests.'() { + given: 'a policy execution request' + def policyExecutionRequest = new PolicyExecutionRequest('some decision type', []) def requestBody = objectMapper.writeValueAsString(policyExecutionRequest) when: 'request is posted' def response = mockMvc.perform(post(url) @@ -112,26 +107,49 @@ class PolicyExecutorStubControllerSpec extends Specification { assert response.status == HttpStatus.BAD_REQUEST.value() } - def 'Execute Policy Action without other required attributes.'() { - given: 'a policy execution request with payloadType=#payloadType, decisionType=decisionType, targetFdn=#targetFdn, changeRequest=#changeRequest' - def payload = new Payload(targetFdn, changeRequest) - def policyExecutionRequest = new PolicyExecutionRequest(payloadType, decisionType, [payload]) + def 'Execute policy action with invalid json for request data.'() { + given: 'a policy execution request' + def request = new Request('ncmp-delete-schema:1.0.0', 'invalid json') + def policyExecutionRequest = new PolicyExecutionRequest('some decision type', [request]) def requestBody = objectMapper.writeValueAsString(policyExecutionRequest) when: 'request is posted' def response = mockMvc.perform(post(url) + .header('Authorization','some string') + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody)) + .andReturn().response + then: 'response status is Bad Request' + assert response.status == HttpStatus.BAD_REQUEST.value() + } + + def 'Execute policy action with missing or invalid attributes.'() { + given: 'a policy execution request with decisionType=#decisionType, schema=#schema, targetIdentifier=#targetIdentifier' + def requestBody = createRequestBody(decisionType, schema, targetIdentifier) + when: 'request is posted' + def response = mockMvc.perform(post(url) .header('Authorization','something') .contentType(MediaType.APPLICATION_JSON) .content(requestBody)) .andReturn().response then: 'response status as expected' assert response.status == expectedStatus.value() - where: 'following parameters are populated or not' - payloadType | decisionType | targetFdn | changeRequest || expectedStatus - 'something' | 'something' | 'something' | 'something' || HttpStatus.OK - null | 'something' | 'something' | 'something' || HttpStatus.BAD_REQUEST - 'something' | null | 'something' | 'something' || HttpStatus.BAD_REQUEST - 'something' | 'something' | null | 'something' || HttpStatus.BAD_REQUEST - 'something' | 'something' | 'something' | null || HttpStatus.BAD_REQUEST + where: 'following parameters are used' + decisionType | schema | targetIdentifier || expectedStatus + 'something' | 'ncmp-delete-schema:1.0.0' | 'something' || HttpStatus.OK + null | 'ncmp-delete-schema:1.0.0' | 'something' || HttpStatus.BAD_REQUEST + 'something' | 'other schema' | 'something' || HttpStatus.BAD_REQUEST + 'something' | 'ncmp-delete-schema:1.0.0' | null || HttpStatus.BAD_REQUEST + } + + def createRequestBody(decisionType, schema, targetIdentifier) { + def ncmpDelete = new NcmpDelete(targetIdentifier: targetIdentifier) + def request = new Request(schema, objectMapper.writeValueAsString(ncmpDelete)) + def policyExecutionRequest = new PolicyExecutionRequest(decisionType, [request]) + return objectMapper.writeValueAsString(policyExecutionRequest) + } + + def createRequestBody(targetIdentifier) { + return createRequestBody('some decision type', 'ncmp-delete-schema:1.0.0', targetIdentifier) } } diff --git a/postman-collections/CPS Environment.postman_environment.json b/postman-collections/CPS Environment.postman_environment.json index 84dcb642d7..7ee9c6779e 100644 --- a/postman-collections/CPS Environment.postman_environment.json +++ b/postman-collections/CPS Environment.postman_environment.json @@ -1,5 +1,5 @@ { - "id": "e8e90dd2-20be-49be-813f-07db44c6a4c2", + "id": "fd705c16-c17e-434d-ad2e-ba040a7ed062", "name": "CPS Environment", "values": [ { @@ -16,30 +16,18 @@ }, { "key": "DMI_HOST", - "value": "localhost", + "value": "ncmp-dmi-plugin-demo-and-csit-stub", "type": "default", "enabled": true }, { "key": "DMI_PORT", - "value": "8784", - "type": "default", - "enabled": true - }, - { - "key": "SDNC_HOST", - "value": "localhost", - "type": "default", - "enabled": true - }, - { - "key": "SDNC_PORT", - "value": "8282", + "value": "8092", "type": "default", "enabled": true } ], "_postman_variable_scope": "environment", - "_postman_exported_at": "2024-02-23T13:00:33.537Z", - "_postman_exported_using": "Postman/10.23.4" -}
\ No newline at end of file + "_postman_exported_at": "2024-07-30T15:54:00.831Z", + "_postman_exported_using": "Postman/11.5.1" +} diff --git a/postman-collections/DMI Stub.postman_collection.json b/postman-collections/DMI Stub.postman_collection.json deleted file mode 100644 index fe7250f7db..0000000000 --- a/postman-collections/DMI Stub.postman_collection.json +++ /dev/null @@ -1,161 +0,0 @@ -{ - "info": { - "_postman_id": "4baf7902-0f1e-49a9-9c6a-f68f412240af", - "name": "DMI Stub", - "description": "A collection of the DMI Stub endpoints.", - "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", - "_exporter_id": "17907116" - }, - "item": [ - { - "name": "Execute a data operation for group of cm handle ids by supplied operation details", - "request": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{ \"operations\":\n [\n {\n \"resourceIdentifier\": \"some resource identifier\",\n \"datastore\": \"ncmp-datastore:passthrough-operational\",\n \"options\": \"some option\",\n \"operationId\": \"12\",\n \"cmHandles\": [\n {\n \"id\": \"cmHandle123\",\n \"cmHandleProperties\": {\n \"myProp\": \"some value\",\n \"otherProp\": \"other value\"\n }\n },\n {\n \"id\": \"cmHandle123\",\n \"cmHandleProperties\": {\n \"myProp\": \"some value\",\n \"otherProp\": \"other value\"\n }\n }\n ],\n \"operation\": \"read\"\n }\n ]\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://{{DMI_HOST}}:{{DMI_PORT}}/dmi/v1/data?topic=ncmp-async-m2m&requestId=4753fc1f-7de2-449a-b306-a6204b5370b33", - "protocol": "http", - "host": [ - "{{DMI_HOST}}" - ], - "port": "{{DMI_PORT}}", - "path": [ - "dmi", - "v1", - "data" - ], - "query": [ - { - "key": "topic", - "value": "ncmp-async-m2m" - }, - { - "key": "requestId", - "value": "4753fc1f-7de2-449a-b306-a6204b5370b33" - } - ] - } - }, - "response": [] - }, - { - "name": "Retrieve module resources for one or more modules", - "request": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://{{DMI_HOST}}:{{DMI_PORT}}/dmi/v1/ch/cm-bookStore/moduleResources", - "protocol": "http", - "host": [ - "{{DMI_HOST}}" - ], - "port": "{{DMI_PORT}}", - "path": [ - "dmi", - "v1", - "ch", - "cm-bookStore", - "moduleResources" - ] - } - }, - "response": [] - }, - { - "name": "Get all modules for given cm handle", - "protocolProfileBehavior": { - "disabledSystemHeaders": { - "accept": true - } - }, - "request": { - "method": "POST", - "header": [ - { - "key": "Accept", - "value": "application/json", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://{{DMI_HOST}}:{{DMI_PORT}}/dmi/v1/ch/cm-bookStore/modules", - "protocol": "http", - "host": [ - "{{DMI_HOST}}" - ], - "port": "{{DMI_PORT}}", - "path": [ - "dmi", - "v1", - "ch", - "cm-bookStore", - "modules" - ] - } - }, - "response": [] - } - ], - "auth": { - "type": "basic", - "basic": [ - { - "key": "password", - "value": "cpsr0cks!", - "type": "string" - }, - { - "key": "username", - "value": "cpsuser", - "type": "string" - } - ] - }, - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - } - ] -}
\ No newline at end of file |