summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cps-application/src/main/resources/application.yml13
-rw-r--r--cps-dependencies/pom.xml2
-rw-r--r--cps-ncmp-rest/docs/openapi/components.yaml4
-rw-r--r--cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpPassthroughResourceRequestHandler.java2
-rwxr-xr-xcps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandler.java22
-rw-r--r--cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandlerSpec.groovy5
-rw-r--r--cps-ncmp-service/pom.xml4
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/client/DmiRestClient.java84
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/DmiProperties.java55
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/DmiWebClientConfiguration.java70
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/HttpClientConfiguration.java37
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/NcmpConfiguration.java114
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/consumer/CmNotificationSubscriptionDmiOutEventConsumer.java2
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/consumer/CmNotificationSubscriptionNcmpInEventConsumer.java8
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/producer/CmNotificationSubscriptionDmiInEventProducer.java11
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/producer/CmNotificationSubscriptionNcmpOutEventProducer.java2
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/DmiClientRequestException.java54
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/InvalidDmiResourceUrlException.java (renamed from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/HttpClientRequestException.java)22
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperation.java2
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java115
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiModelOperations.java4
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiOperationCmHandle.java (renamed from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/CmHandle.java)15
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiOperations.java8
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilder.java23
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/data/operation/ResourceDataOperationRequestUtils.java19
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/client/DmiRestClientSpec.groovy124
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/DmiWebClientConfigurationSpec.groovy52
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/HttpClientConfigurationSpec.groovy19
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/NcmpConfigurationSpec.groovy70
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionNcmpInEventConsumerSpec.groovy2
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiDataOperationsSpec.groovy41
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiModelOperationsSpec.groovy4
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiOperationsBaseSpec.groovy6
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilderSpec.groovy29
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/data/operation/ResourceDataOperationRequestUtilsSpec.groovy23
-rw-r--r--cps-ncmp-service/src/test/resources/application.yml9
-rw-r--r--dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/java/org/onap/cps/ncmp/dmi/rest/stub/controller/DmiRestStubController.java13
-rw-r--r--dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/java/org/onap/cps/ncmp/dmi/rest/stub/model/data/operational/DataOperationRequest.java2
-rw-r--r--dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/java/org/onap/cps/ncmp/dmi/rest/stub/model/data/operational/DmiOperationCmHandle.java (renamed from dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/java/org/onap/cps/ncmp/dmi/rest/stub/model/data/operational/CmHandle.java)3
-rw-r--r--docs/api/swagger/ncmp/openapi.yaml34
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpRestApiSpec.groovy7
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/performance/ncmp/CmHandleQueryByAlternateIdPerfTest.groovy14
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/performance/ncmp/CmHandleQueryPerfTest.groovy3
-rw-r--r--integration-test/src/test/resources/application.yml9
-rw-r--r--k6-tests/README.md25
-rw-r--r--k6-tests/ncmp/1-create-cmhandles.js56
-rw-r--r--k6-tests/ncmp/10-mixed-load-test.js88
-rw-r--r--k6-tests/ncmp/11-delete-cmhandles.js48
-rw-r--r--k6-tests/ncmp/2-wait-for-cmhandles-to-be-ready.js64
-rw-r--r--k6-tests/ncmp/3-passthrough-read.js51
-rw-r--r--k6-tests/ncmp/4-id-search-no-filter.js35
-rw-r--r--k6-tests/ncmp/5-search-no-filter.js34
-rw-r--r--k6-tests/ncmp/6-id-search-public-property.js42
-rw-r--r--k6-tests/ncmp/7-search-public-property.js42
-rw-r--r--k6-tests/ncmp/8-id-search-module.js42
-rw-r--r--k6-tests/ncmp/9-search-module.js42
-rwxr-xr-xk6-tests/ncmp/run-all-tests.sh44
-rw-r--r--k6-tests/ncmp/search-base.js35
-rw-r--r--k6-tests/ncmp/utils.js41
-rwxr-xr-xk6-tests/run-k6-tests.sh39
-rwxr-xr-xk6-tests/setup.sh22
-rwxr-xr-xk6-tests/teardown.sh31
62 files changed, 1435 insertions, 507 deletions
diff --git a/cps-application/src/main/resources/application.yml b/cps-application/src/main/resources/application.yml
index 810068081e..e724ef4443 100644
--- a/cps-application/src/main/resources/application.yml
+++ b/cps-application/src/main/resources/application.yml
@@ -101,10 +101,10 @@ app:
async-m2m:
topic: ${NCMP_ASYNC_M2M_TOPIC:ncmp-async-m2m}
avc:
- subscription-topic: ${NCMP_CM_AVC_SUBSCRIPTION:subscription}
- subscription-forward-topic-prefix: ${NCMP_FORWARD_CM_AVC_SUBSCRIPTION:ncmp-dmi-cm-avc-subscription-}
- subscription-response-topic: ${NCMP_RESPONSE_CM_AVC_SUBSCRIPTION:dmi-ncmp-cm-avc-subscription}
- subscription-outcome-topic: ${NCMP_OUTCOME_CM_AVC_SUBSCRIPTION:subscription-response}
+ cm-subscription-ncmp-in: ${CM_SUBSCRIPTION_NCMP_IN_TOPIC:subscription}
+ cm-subscription-dmi-in: ${CM_SUBSCRIPTION_DMI_IN_TOPIC:ncmp-dmi-cm-avc-subscription}
+ cm-subscription-dmi-out: ${CM_SUBSCRIPTION_DMI_OUT_TOPIC:dmi-ncmp-cm-avc-subscription}
+ cm-subscription-ncmp-out: ${CM_SUBSCRIPTION_NCMP_OUT_TOPIC:subscription-response}
cm-events-topic: ${NCMP_CM_EVENTS_TOPIC:cm-events}
lcm:
events:
@@ -175,9 +175,10 @@ ncmp:
dmi:
httpclient:
connectionTimeoutInSeconds: 30
- maximumConnectionsPerRoute: 50
+ readTimeoutInSeconds: 30
+ writeTimeoutInSeconds: 30
maximumConnectionsTotal: 100
- idleConnectionEvictionThresholdInSeconds: 5
+ maximumInMemorySizeInMegabytes: 16
auth:
username: ${DMI_USERNAME:cpsuser}
password: ${DMI_PASSWORD:cpsr0cks!}
diff --git a/cps-dependencies/pom.xml b/cps-dependencies/pom.xml
index effaa06f62..f323cd7077 100644
--- a/cps-dependencies/pom.xml
+++ b/cps-dependencies/pom.xml
@@ -261,7 +261,7 @@
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
- <version>1.18.24</version>
+ <version>1.18.32</version>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
diff --git a/cps-ncmp-rest/docs/openapi/components.yaml b/cps-ncmp-rest/docs/openapi/components.yaml
index 8aa38c0f70..a9311d0861 100644
--- a/cps-ncmp-rest/docs/openapi/components.yaml
+++ b/cps-ncmp-rest/docs/openapi/components.yaml
@@ -578,7 +578,6 @@ components:
in: query
description: The format of resource identifier depend on the associated DMI Plugin implementation. For ONAP DMI Plugin it will be RESTConf paths but it can really be anything.
required: true
- allowReserved: true
schema:
type: string
examples:
@@ -598,7 +597,6 @@ components:
required: false
schema:
type: string
- allowReserved: true
examples:
sample 1:
value:
@@ -616,7 +614,6 @@ components:
required: false
schema:
type: string
- allowReserved: true
examples:
sample 1:
value:
@@ -628,7 +625,6 @@ components:
required: true
schema:
type: string
- allowReserved: true
examples:
sample 1:
value:
diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpPassthroughResourceRequestHandler.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpPassthroughResourceRequestHandler.java
index 1f87865f21..be5b93c47b 100644
--- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpPassthroughResourceRequestHandler.java
+++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpPassthroughResourceRequestHandler.java
@@ -48,7 +48,7 @@ public class NcmpPassthroughResourceRequestHandler extends NcmpDatastoreRequestH
private static final Object noReturn = null;
- private static final int MAXIMUM_CM_HANDLES_PER_OPERATION = 50000;
+ private static final int MAXIMUM_CM_HANDLES_PER_OPERATION = 200;
private static final String PAYLOAD_TOO_LARGE_TEMPLATE = "Operation '%s' affects too many (%d) cm handles";
diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandler.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandler.java
index d323691916..7263000896 100755
--- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandler.java
+++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandler.java
@@ -23,9 +23,10 @@ package org.onap.cps.ncmp.rest.exceptions;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.ncmp.api.impl.exception.DmiClientRequestException;
import org.onap.cps.ncmp.api.impl.exception.DmiRequestException;
-import org.onap.cps.ncmp.api.impl.exception.HttpClientRequestException;
import org.onap.cps.ncmp.api.impl.exception.InvalidDatastoreException;
+import org.onap.cps.ncmp.api.impl.exception.InvalidDmiResourceUrlException;
import org.onap.cps.ncmp.api.impl.exception.NcmpException;
import org.onap.cps.ncmp.api.impl.exception.ServerNcmpException;
import org.onap.cps.ncmp.rest.controller.NetworkCmProxyController;
@@ -69,14 +70,15 @@ public class NetworkCmProxyRestExceptionHandler {
return buildErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR, exception);
}
- @ExceptionHandler({HttpClientRequestException.class})
+ @ExceptionHandler({DmiClientRequestException.class})
public static ResponseEntity<Object> handleClientRequestExceptions(
- final HttpClientRequestException httpClientRequestException) {
- return wrapDmiErrorResponse(httpClientRequestException);
+ final DmiClientRequestException dmiClientRequestException) {
+ return wrapDmiErrorResponse(dmiClientRequestException);
}
@ExceptionHandler({DmiRequestException.class, DataValidationException.class, OperationNotSupportedException.class,
- HttpMessageNotReadableException.class, InvalidTopicException.class, InvalidDatastoreException.class})
+ HttpMessageNotReadableException.class, InvalidTopicException.class, InvalidDatastoreException.class,
+ InvalidDmiResourceUrlException.class})
public static ResponseEntity<Object> handleDmiRequestExceptions(final Exception exception) {
return buildErrorResponse(HttpStatus.BAD_REQUEST, exception);
}
@@ -115,13 +117,13 @@ public class NetworkCmProxyRestExceptionHandler {
return new ResponseEntity<>(errorMessage, status);
}
- private static ResponseEntity<Object> wrapDmiErrorResponse(final HttpClientRequestException
- httpClientRequestException) {
+ private static ResponseEntity<Object> wrapDmiErrorResponse(final DmiClientRequestException
+ dmiClientRequestException) {
final var dmiErrorMessage = new DmiErrorMessage();
final var dmiErrorResponse = new DmiErrorMessageDmiResponse();
- dmiErrorResponse.setHttpCode(httpClientRequestException.getHttpStatus());
- dmiErrorResponse.setBody(httpClientRequestException.getDetails());
- dmiErrorMessage.setMessage(httpClientRequestException.getMessage());
+ dmiErrorResponse.setHttpCode(dmiClientRequestException.getHttpStatusCode());
+ dmiErrorResponse.setBody(dmiClientRequestException.getResponseBodyAsString());
+ dmiErrorMessage.setMessage(dmiClientRequestException.getMessage());
dmiErrorMessage.setDmiResponse(dmiErrorResponse);
return new ResponseEntity<>(dmiErrorMessage, HttpStatus.BAD_GATEWAY);
}
diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandlerSpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandlerSpec.groovy
index a79ea25ab8..ea472cd931 100644
--- a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandlerSpec.groovy
+++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandlerSpec.groovy
@@ -31,13 +31,14 @@ import static org.onap.cps.ncmp.rest.exceptions.NetworkCmProxyRestExceptionHandl
import static org.onap.cps.ncmp.rest.exceptions.NetworkCmProxyRestExceptionHandlerSpec.ApiType.NCMPINVENTORY
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
+import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNABLE_TO_READ_RESOURCE_DATA
import groovy.json.JsonSlurper
import org.mapstruct.factory.Mappers
import org.onap.cps.TestUtils
import org.onap.cps.ncmp.api.NetworkCmProxyDataService
import org.onap.cps.ncmp.api.impl.exception.DmiRequestException
-import org.onap.cps.ncmp.api.impl.exception.HttpClientRequestException
+import org.onap.cps.ncmp.api.impl.exception.DmiClientRequestException
import org.onap.cps.ncmp.api.impl.exception.ServerNcmpException
import org.onap.cps.ncmp.rest.controller.NcmpRestInputMapper
import org.onap.cps.ncmp.rest.controller.handlers.NcmpCachedResourceRequestHandler
@@ -143,7 +144,7 @@ class NetworkCmProxyRestExceptionHandlerSpec extends Specification {
def 'Failing DMI Request - passthrough scenario'() {
given: 'failing DMI request'
- setupTestException(new HttpClientRequestException('Error Message Details NCMP', 'Bad Request from DMI', 400), NCMP)
+ setupTestException(new DmiClientRequestException(400, 'Error Message Details NCMP', 'Bad Request from DMI', UNABLE_TO_READ_RESOURCE_DATA), NCMP)
when: 'the DMI request is executed'
def response = performTestRequest(NCMP)
then: 'NCMP service responds with 502 Bad Gateway status'
diff --git a/cps-ncmp-service/pom.xml b/cps-ncmp-service/pom.xml
index fc41da3aee..77e13dbae8 100644
--- a/cps-ncmp-service/pom.xml
+++ b/cps-ncmp-service/pom.xml
@@ -70,8 +70,8 @@
<artifactId>mapstruct-processor</artifactId>
</dependency>
<dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-web</artifactId>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- T E S T - D E P E N D E N C I E S -->
<dependency>
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/client/DmiRestClient.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/client/DmiRestClient.java
index 798a280c8a..ef48c43d2a 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/client/DmiRestClient.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/client/DmiRestClient.java
@@ -1,6 +1,6 @@
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2021-2023 Nordix Foundation
+ * Copyright (C) 2021-2024 Nordix Foundation
* Modifications Copyright (C) 2022 Bell Canada
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -21,20 +21,31 @@
package org.onap.cps.ncmp.api.impl.client;
+import static org.onap.cps.ncmp.api.NcmpResponseStatus.DMI_SERVICE_NOT_RESPONDING;
+import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNABLE_TO_READ_RESOURCE_DATA;
+import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNKNOWN_ERROR;
+import static org.springframework.http.HttpStatus.BAD_REQUEST;
+import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR;
+
import com.fasterxml.jackson.databind.JsonNode;
+import java.net.URI;
+import java.net.URISyntaxException;
import java.util.Locale;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
-import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration.DmiProperties;
-import org.onap.cps.ncmp.api.impl.exception.HttpClientRequestException;
+import org.onap.cps.ncmp.api.impl.config.DmiProperties;
+import org.onap.cps.ncmp.api.impl.exception.DmiClientRequestException;
+import org.onap.cps.ncmp.api.impl.exception.InvalidDmiResourceUrlException;
import org.onap.cps.ncmp.api.impl.operations.OperationType;
-import org.springframework.http.HttpEntity;
+import org.onap.cps.utils.JsonObjectMapper;
import org.springframework.http.HttpHeaders;
-import org.springframework.http.MediaType;
+import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
-import org.springframework.web.client.HttpStatusCodeException;
-import org.springframework.web.client.RestTemplate;
+import org.springframework.web.client.HttpServerErrorException;
+import org.springframework.web.reactive.function.BodyInserters;
+import org.springframework.web.reactive.function.client.WebClient;
+import org.springframework.web.reactive.function.client.WebClientResponseException;
@Component
@RequiredArgsConstructor
@@ -43,8 +54,10 @@ public class DmiRestClient {
private static final String HEALTH_CHECK_URL_EXTENSION = "/actuator/health";
private static final String NOT_SPECIFIED = "";
- private final RestTemplate restTemplate;
+ private static final String NO_AUTHORIZATION = null;
+ private final WebClient webClient;
private final DmiProperties dmiProperties;
+ private final JsonObjectMapper jsonObjectMapper;
/**
* Sends POST operation to DMI with json body containing module references.
@@ -59,14 +72,16 @@ public class DmiRestClient {
final String requestBodyAsJsonString,
final OperationType operationType,
final String authorization) {
- final var httpEntity = new HttpEntity<>(requestBodyAsJsonString, configureHttpHeaders(new HttpHeaders(),
- authorization));
try {
- return restTemplate.postForEntity(dmiResourceUrl, httpEntity, Object.class);
- } catch (final HttpStatusCodeException httpStatusCodeException) {
- final String exceptionMessage = "Unable to " + operationType.toString() + " resource data.";
- throw new HttpClientRequestException(exceptionMessage, httpStatusCodeException.getResponseBodyAsString(),
- httpStatusCodeException.getStatusCode().value());
+ return ResponseEntity.ok(webClient.post().uri(toUri(dmiResourceUrl))
+ .headers(httpHeaders -> configureHttpHeaders(httpHeaders, authorization))
+ .body(BodyInserters.fromValue(requestBodyAsJsonString))
+ .retrieve()
+ .bodyToMono(Object.class)
+ .onErrorMap(httpError -> handleDmiClientException(httpError, operationType.getOperationName()))
+ .block());
+ } catch (final HttpServerErrorException e) {
+ throw handleDmiClientException(e, operationType.getOperationName());
}
}
@@ -77,13 +92,14 @@ public class DmiRestClient {
* @return plugin health status ("UP" is all OK, "" (not-specified) in case of any exception)
*/
public String getDmiHealthStatus(final String dmiPluginBaseUrl) {
- final HttpEntity<Object> httpHeaders = new HttpEntity<>(configureHttpHeaders(new HttpHeaders(), null));
try {
- final JsonNode responseHealthStatus =
- restTemplate.getForObject(dmiPluginBaseUrl + HEALTH_CHECK_URL_EXTENSION,
- JsonNode.class, httpHeaders);
+ final JsonNode responseHealthStatus = webClient.get()
+ .uri(toUri(dmiPluginBaseUrl + HEALTH_CHECK_URL_EXTENSION))
+ .headers(httpHeaders -> configureHttpHeaders(httpHeaders, NO_AUTHORIZATION))
+ .retrieve()
+ .bodyToMono(JsonNode.class).block();
return responseHealthStatus == null ? NOT_SPECIFIED :
- responseHealthStatus.get("status").asText();
+ responseHealthStatus.get("status").asText();
} catch (final Exception e) {
log.warn("Failed to retrieve health status from {}. Error Message: {}", dmiPluginBaseUrl, e.getMessage());
return NOT_SPECIFIED;
@@ -96,7 +112,33 @@ public class DmiRestClient {
} else if (authorization != null && authorization.toLowerCase(Locale.getDefault()).startsWith("bearer ")) {
httpHeaders.add(HttpHeaders.AUTHORIZATION, authorization);
}
- httpHeaders.setContentType(MediaType.APPLICATION_JSON);
return httpHeaders;
}
+
+ private static URI toUri(final String dmiResourceUrl) {
+ try {
+ return new URI(dmiResourceUrl);
+ } catch (final URISyntaxException e) {
+ throw new InvalidDmiResourceUrlException(dmiResourceUrl, BAD_REQUEST.value());
+ }
+ }
+
+ private DmiClientRequestException handleDmiClientException(final Throwable e, final String operationType) {
+ final String exceptionMessage = "Unable to " + operationType + " resource data.";
+ if (e instanceof WebClientResponseException wcre) {
+ if (wcre.getStatusCode().isSameCodeAs(HttpStatus.REQUEST_TIMEOUT)) {
+ throw new DmiClientRequestException(wcre.getStatusCode().value(), wcre.getMessage(),
+ jsonObjectMapper.asJsonString(wcre.getResponseBodyAsString()), DMI_SERVICE_NOT_RESPONDING);
+ }
+ throw new DmiClientRequestException(wcre.getStatusCode().value(), wcre.getMessage(),
+ jsonObjectMapper.asJsonString(wcre.getResponseBodyAsString()), UNABLE_TO_READ_RESOURCE_DATA);
+
+ }
+ if (e instanceof HttpServerErrorException httpServerErrorException) {
+ throw new DmiClientRequestException(httpServerErrorException.getStatusCode().value(), exceptionMessage,
+ httpServerErrorException.getResponseBodyAsString(), DMI_SERVICE_NOT_RESPONDING);
+ }
+ throw new DmiClientRequestException(INTERNAL_SERVER_ERROR.value(), exceptionMessage, e.getMessage(),
+ UNKNOWN_ERROR);
+ }
}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/DmiProperties.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/DmiProperties.java
new file mode 100644
index 0000000000..5453efecd0
--- /dev/null
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/DmiProperties.java
@@ -0,0 +1,55 @@
+/*
+ * ============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.api.impl.config;
+
+import lombok.AccessLevel;
+import lombok.Getter;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+@Getter
+@Component
+public class DmiProperties {
+ @Value("${ncmp.dmi.auth.username}")
+ private String authUsername;
+ @Value("${ncmp.dmi.auth.password}")
+ private String authPassword;
+ @Getter(AccessLevel.NONE)
+ @Value("${ncmp.dmi.api.base-path}")
+ private String dmiBasePath;
+ @Value("${ncmp.dmi.auth.enabled}")
+ private boolean dmiBasicAuthEnabled;
+
+ /**
+ * Removes both leading and trailing slashes if they are present.
+ *
+ * @return dmi base path without any slashes ("/")
+ */
+ public String getDmiBasePath() {
+ if (dmiBasePath.startsWith("/")) {
+ dmiBasePath = dmiBasePath.substring(1);
+ }
+ if (dmiBasePath.endsWith("/")) {
+ dmiBasePath = dmiBasePath.substring(0, dmiBasePath.length() - 1);
+ }
+ return dmiBasePath;
+ }
+}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/DmiWebClientConfiguration.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/DmiWebClientConfiguration.java
new file mode 100644
index 0000000000..2e84f7f69f
--- /dev/null
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/DmiWebClientConfiguration.java
@@ -0,0 +1,70 @@
+/*
+ * ============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.api.impl.config;
+
+import io.netty.channel.ChannelOption;
+import io.netty.handler.timeout.ReadTimeoutHandler;
+import io.netty.handler.timeout.WriteTimeoutHandler;
+import java.util.concurrent.TimeUnit;
+import lombok.RequiredArgsConstructor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.http.client.reactive.ReactorClientHttpConnector;
+import org.springframework.web.reactive.function.client.WebClient;
+import reactor.netty.http.client.HttpClient;
+import reactor.netty.resources.ConnectionProvider;
+
+@Configuration
+@RequiredArgsConstructor
+public class DmiWebClientConfiguration {
+
+ private final HttpClientConfiguration httpClientConfiguration;
+
+ /**
+ * Configures and create a WebClient bean that triggers an initialization (warmup) of the host name resolver and
+ * loads the necessary native libraries to avoid the extra time needed to load resources for first request.
+ *
+ * @return a WebClient instance.
+ */
+ @Bean
+ public WebClient webClient() {
+ final ConnectionProvider dmiWebClientConnectionProvider = ConnectionProvider.create(
+ "dmiWebClientConnectionPool", httpClientConfiguration.getMaximumConnectionsTotal());
+
+ final HttpClient httpClient = HttpClient.create(dmiWebClientConnectionProvider)
+ .option(ChannelOption.CONNECT_TIMEOUT_MILLIS,
+ httpClientConfiguration.getConnectionTimeoutInSeconds() * 1000)
+ .doOnConnected(connection -> connection.addHandlerLast(new ReadTimeoutHandler(
+ httpClientConfiguration.getReadTimeoutInSeconds(), TimeUnit.SECONDS))
+ .addHandlerLast(new WriteTimeoutHandler(
+ httpClientConfiguration.getWriteTimeoutInSeconds(), TimeUnit.SECONDS)));
+ httpClient.warmup().block();
+ return WebClient.builder()
+ .defaultHeaders(header -> header.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE))
+ .defaultHeaders(header -> header.set(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE))
+ .clientConnector(new ReactorClientHttpConnector(httpClient))
+ .codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(
+ httpClientConfiguration.getMaximumInMemorySizeInMegabytes() * 1024 * 1024))
+ .build();
+ }
+}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/HttpClientConfiguration.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/HttpClientConfiguration.java
index d547e31c68..d2521d9d1e 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/HttpClientConfiguration.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/HttpClientConfiguration.java
@@ -1,6 +1,6 @@
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2023 Nordix Foundation.
+ * Copyright (C) 2023-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.
@@ -20,38 +20,19 @@
package org.onap.cps.ncmp.api.impl.config;
-import java.time.Duration;
-import java.time.temporal.ChronoUnit;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
-import org.springframework.boot.convert.DurationUnit;
+import org.springframework.stereotype.Component;
@Getter
@Setter
-@ConfigurationProperties(prefix = "ncmp.dmi.httpclient", ignoreUnknownFields = true)
+@Component
+@ConfigurationProperties(prefix = "ncmp.dmi.httpclient")
public class HttpClientConfiguration {
-
- /**
- * The maximum time to establish a connection.
- */
- @DurationUnit(ChronoUnit.SECONDS)
- private Duration connectionTimeoutInSeconds = Duration.ofSeconds(180);
-
- /**
- * The maximum number of open connections per route.
- */
- private int maximumConnectionsPerRoute = 50;
-
- /**
- * The maximum total number of open connections.
- */
- private int maximumConnectionsTotal = maximumConnectionsPerRoute * 2;
-
- /**
- * The duration after which idle connections are evicted.
- */
- @DurationUnit(ChronoUnit.SECONDS)
- private Duration idleConnectionEvictionThresholdInSeconds = Duration.ofSeconds(5);
-
+ private Integer maximumConnectionsTotal = 100;
+ private Integer connectionTimeoutInSeconds = 30;
+ private Integer readTimeoutInSeconds = 30;
+ private Integer writeTimeoutInSeconds = 30;
+ private Integer maximumInMemorySizeInMegabytes = 1;
}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/NcmpConfiguration.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/NcmpConfiguration.java
deleted file mode 100644
index c6ff116a7f..0000000000
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/NcmpConfiguration.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * ============LICENSE_START=======================================================
- * Copyright (C) 2021-2023 Nordix Foundation
- * ================================================================================
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * SPDX-License-Identifier: Apache-2.0
- * ============LICENSE_END=========================================================
- */
-
-package org.onap.cps.ncmp.api.impl.config;
-
-import java.util.Arrays;
-import lombok.AccessLevel;
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.hc.client5.http.config.ConnectionConfig;
-import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
-import org.apache.hc.client5.http.impl.classic.HttpClients;
-import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
-import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
-import org.apache.hc.core5.util.TimeValue;
-import org.apache.hc.core5.util.Timeout;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.beans.factory.config.ConfigurableBeanFactory;
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
-import org.springframework.boot.web.client.RestTemplateBuilder;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Scope;
-import org.springframework.http.MediaType;
-import org.springframework.http.client.ClientHttpRequestFactory;
-import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
-import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
-import org.springframework.stereotype.Component;
-import org.springframework.web.client.RestTemplate;
-
-@Configuration
-@EnableConfigurationProperties(HttpClientConfiguration.class)
-@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
-public class NcmpConfiguration {
-
- @Getter
- @Component
- public static class DmiProperties {
- @Value("${ncmp.dmi.auth.username}")
- private String authUsername;
- @Value("${ncmp.dmi.auth.password}")
- private String authPassword;
- @Value("${ncmp.dmi.api.base-path}")
- private String dmiBasePath;
- @Value("${ncmp.dmi.auth.enabled}")
- private boolean dmiBasicAuthEnabled;
- }
-
- /**
- * Rest template bean.
- *
- * @param restTemplateBuilder the rest template builder
- * @param httpClientConfiguration the http client configuration
- * @return rest template instance
- */
- @Bean
- @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
- public static RestTemplate restTemplate(final RestTemplateBuilder restTemplateBuilder,
- final HttpClientConfiguration httpClientConfiguration) {
-
- final ConnectionConfig connectionConfig = ConnectionConfig.copy(ConnectionConfig.DEFAULT)
- .setConnectTimeout(Timeout.of(httpClientConfiguration.getConnectionTimeoutInSeconds()))
- .build();
-
- final PoolingHttpClientConnectionManager connectionManager = PoolingHttpClientConnectionManagerBuilder.create()
- .setDefaultConnectionConfig(connectionConfig)
- .setMaxConnTotal(httpClientConfiguration.getMaximumConnectionsTotal())
- .setMaxConnPerRoute(httpClientConfiguration.getMaximumConnectionsPerRoute())
- .build();
-
- final CloseableHttpClient httpClient = HttpClients.custom()
- .setConnectionManager(connectionManager)
- .evictExpiredConnections()
- .evictIdleConnections(
- TimeValue.of(httpClientConfiguration.getIdleConnectionEvictionThresholdInSeconds()))
- .build();
-
- final ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
-
- final RestTemplate restTemplate = restTemplateBuilder
- .requestFactory(() -> requestFactory)
- .setConnectTimeout(httpClientConfiguration.getConnectionTimeoutInSeconds())
- .build();
-
- setRestTemplateMessageConverters(restTemplate);
- return restTemplate;
- }
-
- private static void setRestTemplateMessageConverters(final RestTemplate restTemplate) {
- final MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter =
- new MappingJackson2HttpMessageConverter();
- mappingJackson2HttpMessageConverter.setSupportedMediaTypes(
- Arrays.asList(MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN));
- restTemplate.getMessageConverters().add(mappingJackson2HttpMessageConverter);
- }
-
-}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/consumer/CmNotificationSubscriptionDmiOutEventConsumer.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/consumer/CmNotificationSubscriptionDmiOutEventConsumer.java
index fb89aae3f8..3e072b0f91 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/consumer/CmNotificationSubscriptionDmiOutEventConsumer.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/consumer/CmNotificationSubscriptionDmiOutEventConsumer.java
@@ -51,7 +51,7 @@ public class CmNotificationSubscriptionDmiOutEventConsumer {
*
* @param cmNotificationSubscriptionDmiOutEventConsumerRecord the event to be consumed
*/
- @KafkaListener(topics = "${app.ncmp.avc.subscription-response-topic}",
+ @KafkaListener(topics = "${app.ncmp.avc.cm-subscription-dmi-out}",
containerFactory = "cloudEventConcurrentKafkaListenerContainerFactory")
public void consumeCmNotificationSubscriptionDmiOutEvent(
final ConsumerRecord<String, CloudEvent> cmNotificationSubscriptionDmiOutEventConsumerRecord) {
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/consumer/CmNotificationSubscriptionNcmpInEventConsumer.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/consumer/CmNotificationSubscriptionNcmpInEventConsumer.java
index 70135b3079..2c544b7b6a 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/consumer/CmNotificationSubscriptionNcmpInEventConsumer.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/consumer/CmNotificationSubscriptionNcmpInEventConsumer.java
@@ -28,7 +28,6 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.onap.cps.ncmp.api.impl.events.cmsubscription.service.CmNotificationSubscriptionHandlerService;
import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.client_to_ncmp.CmNotificationSubscriptionNcmpInEvent;
-import org.springframework.beans.factory.annotation.Value;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;
@@ -39,15 +38,12 @@ public class CmNotificationSubscriptionNcmpInEventConsumer {
private final CmNotificationSubscriptionHandlerService cmNotificationSubscriptionHandlerService;
- @Value("${notification.enabled:true}")
- private boolean notificationFeatureEnabled;
-
/**
* Consume the specified event.
*
* @param subscriptionEventConsumerRecord the event to be consumed
*/
- @KafkaListener(topics = "${app.ncmp.avc.subscription-topic}",
+ @KafkaListener(topics = "${app.ncmp.avc.cm-subscription-ncmp-in}",
containerFactory = "cloudEventConcurrentKafkaListenerContainerFactory")
public void consumeSubscriptionEvent(final ConsumerRecord<String, CloudEvent> subscriptionEventConsumerRecord) {
final CloudEvent cloudEvent = subscriptionEventConsumerRecord.value();
@@ -60,7 +56,7 @@ public class CmNotificationSubscriptionNcmpInEventConsumer {
if ("subscriptionCreateRequest".equals(cloudEvent.getType())) {
log.info("Subscription for source {} with subscription id {} ...", cloudEvent.getSource(), subscriptionId);
cmNotificationSubscriptionHandlerService.processSubscriptionCreateRequest(
- cmNotificationSubscriptionNcmpInEvent);
+ cmNotificationSubscriptionNcmpInEvent);
}
}
} \ No newline at end of file
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/producer/CmNotificationSubscriptionDmiInEventProducer.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/producer/CmNotificationSubscriptionDmiInEventProducer.java
index 9fbe26848f..3273c556c6 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/producer/CmNotificationSubscriptionDmiInEventProducer.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/producer/CmNotificationSubscriptionDmiInEventProducer.java
@@ -25,7 +25,6 @@ import io.cloudevents.core.builder.CloudEventBuilder;
import java.net.URI;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
import org.onap.cps.events.EventsPublisher;
import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.ncmp_to_dmi.CmNotificationSubscriptionDmiInEvent;
import org.onap.cps.utils.JsonObjectMapper;
@@ -34,7 +33,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
@Component
-@Slf4j
@RequiredArgsConstructor
@ConditionalOnProperty(name = "notification.enabled", havingValue = "true", matchIfMissing = true)
public class CmNotificationSubscriptionDmiInEventProducer {
@@ -42,7 +40,7 @@ public class CmNotificationSubscriptionDmiInEventProducer {
private final EventsPublisher<CloudEvent> eventsPublisher;
private final JsonObjectMapper jsonObjectMapper;
- @Value("${app.ncmp.avc.subscription-forward-topic-prefix}")
+ @Value("${app.ncmp.avc.cm-subscription-dmi-in}")
private String cmNotificationSubscriptionDmiInEventTopic;
/**
@@ -65,9 +63,10 @@ public class CmNotificationSubscriptionDmiInEventProducer {
final String dmiPluginName, final String eventType,
final CmNotificationSubscriptionDmiInEvent cmNotificationSubscriptionDmiInEvent) {
return CloudEventBuilder.v1().withId(UUID.randomUUID().toString()).withType(eventType)
- .withSource(URI.create("NCMP")).withDataSchema(URI.create("org.onap.ncmp.dmi.cm.subscription:1.0.0"))
- .withExtension("correlationid", subscriptionId.concat("#").concat(dmiPluginName))
- .withData(jsonObjectMapper.asJsonBytes(cmNotificationSubscriptionDmiInEvent)).build();
+ .withSource(URI.create("NCMP"))
+ .withDataSchema(URI.create("org.onap.ncmp.dmi.cm.subscription:1.0.0"))
+ .withExtension("correlationid", subscriptionId.concat("#").concat(dmiPluginName))
+ .withData(jsonObjectMapper.asJsonBytes(cmNotificationSubscriptionDmiInEvent)).build();
}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/producer/CmNotificationSubscriptionNcmpOutEventProducer.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/producer/CmNotificationSubscriptionNcmpOutEventProducer.java
index ac5de07f0a..ed7ed2a0ba 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/producer/CmNotificationSubscriptionNcmpOutEventProducer.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/cmsubscription/producer/CmNotificationSubscriptionNcmpOutEventProducer.java
@@ -48,7 +48,7 @@ import org.springframework.stereotype.Component;
@ConditionalOnProperty(name = "notification.enabled", havingValue = "true", matchIfMissing = true)
public class CmNotificationSubscriptionNcmpOutEventProducer {
- @Value("${app.ncmp.avc.subscription-outcome-topic}")
+ @Value("${app.ncmp.avc.cm-subscription-ncmp-out}")
private String cmNotificationSubscriptionNcmpOutEventTopic;
@Value("${ncmp.timers.subscription-forwarding.dmi-response-timeout-ms}")
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/DmiClientRequestException.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/DmiClientRequestException.java
new file mode 100644
index 0000000000..ab0fa68938
--- /dev/null
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/DmiClientRequestException.java
@@ -0,0 +1,54 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2022-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.api.impl.exception;
+
+import lombok.Getter;
+import org.onap.cps.ncmp.api.NcmpResponseStatus;
+
+/**
+ * Http Client Request exception from dmi service.
+ */
+@Getter
+public class DmiClientRequestException extends NcmpException {
+
+ private static final long serialVersionUID = 6659897770659834797L;
+ final NcmpResponseStatus ncmpResponseStatus;
+ final String message;
+ final String responseBodyAsString;
+ final int httpStatusCode;
+
+ /**
+ * Constructor to form exception for dmi service response.
+ *
+ * @param httpStatusCode http response code from the client
+ * @param message response message from the client
+ * @param responseBodyAsString response body from the client
+ * @param ncmpResponseStatus ncmp status message and code
+ */
+ public DmiClientRequestException(final int httpStatusCode, final String message, final String responseBodyAsString,
+ final NcmpResponseStatus ncmpResponseStatus) {
+ super(message, responseBodyAsString);
+ this.httpStatusCode = httpStatusCode;
+ this.message = message;
+ this.responseBodyAsString = responseBodyAsString;
+ this.ncmpResponseStatus = ncmpResponseStatus;
+ }
+}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/HttpClientRequestException.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/InvalidDmiResourceUrlException.java
index 9d307e5d2e..270988b63b 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/HttpClientRequestException.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/InvalidDmiResourceUrlException.java
@@ -1,6 +1,6 @@
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2022 Nordix Foundation
+ * 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.
@@ -22,24 +22,16 @@ package org.onap.cps.ncmp.api.impl.exception;
import lombok.Getter;
-/**
- * Http Client Request exception for passthrough scenarios.
- */
@Getter
-public class HttpClientRequestException extends NcmpException {
+public class InvalidDmiResourceUrlException extends RuntimeException {
+
+ private static final long serialVersionUID = 2928476384584894968L;
- private static final long serialVersionUID = 6659897770659834797L;
+ private static final String INVALID_DMI_URL = "Invalid dmi resource url";
final Integer httpStatus;
- /**
- * Constructor to form exception for passthrough scenarios.
- *
- * @param message message details from NCMP
- * @param details response body from the client available as details
- * @param httpStatus http status code from the client
- */
- public HttpClientRequestException(final String message, final String details, final Integer httpStatus) {
- super(message, details);
+ public InvalidDmiResourceUrlException(final String details, final Integer httpStatus) {
+ super(String.format(INVALID_DMI_URL + ": %s", details));
this.httpStatus = httpStatus;
}
}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperation.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperation.java
index 2e66ac0bf1..7baac34b1f 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperation.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperation.java
@@ -40,7 +40,7 @@ public class DmiDataOperation {
private String options;
private String resourceIdentifier;
- private final List<CmHandle> cmHandles = new ArrayList<>();
+ private final List<DmiOperationCmHandle> cmHandles = new ArrayList<>();
/**
* Create and initialise a (outgoing) DMI data operation.
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java
index a9ec1241bc..1e92bfe425 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java
@@ -21,8 +21,6 @@
package org.onap.cps.ncmp.api.impl.operations;
-import static org.onap.cps.ncmp.api.NcmpResponseStatus.DMI_SERVICE_NOT_RESPONDING;
-import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNABLE_TO_READ_RESOURCE_DATA;
import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING;
import static org.onap.cps.ncmp.api.impl.operations.OperationType.READ;
@@ -35,8 +33,8 @@ import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.onap.cps.ncmp.api.NcmpResponseStatus;
import org.onap.cps.ncmp.api.impl.client.DmiRestClient;
-import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration;
-import org.onap.cps.ncmp.api.impl.exception.HttpClientRequestException;
+import org.onap.cps.ncmp.api.impl.config.DmiProperties;
+import org.onap.cps.ncmp.api.impl.exception.DmiClientRequestException;
import org.onap.cps.ncmp.api.impl.inventory.CmHandleState;
import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence;
import org.onap.cps.ncmp.api.impl.utils.DmiServiceUrlBuilder;
@@ -61,7 +59,7 @@ public class DmiDataOperations extends DmiOperations {
public DmiDataOperations(final InventoryPersistence inventoryPersistence,
final JsonObjectMapper jsonObjectMapper,
- final NcmpConfiguration.DmiProperties dmiProperties,
+ final DmiProperties dmiProperties,
final DmiRestClient dmiRestClient,
final DmiServiceUrlBuilder dmiServiceUrlBuilder) {
super(inventoryPersistence, jsonObjectMapper, dmiProperties, dmiRestClient, dmiServiceUrlBuilder);
@@ -90,9 +88,14 @@ public class DmiDataOperations extends DmiOperations {
final CmHandleState cmHandleState = yangModelCmHandle.getCompositeState().getCmHandleState();
validateIfCmHandleStateReady(yangModelCmHandle, cmHandleState);
final String jsonRequestBody = getDmiRequestBody(READ, requestId, null, null, yangModelCmHandle);
- final String dmiResourceDataUrl = getDmiRequestUrl(cmResourceAddress.datastoreName(),
- cmResourceAddress.cmHandleId(), cmResourceAddress.resourceIdentifier(), optionsParamInQuery,
- topicParamInQuery, yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA));
+
+ final MultiValueMap<String, String> uriQueryParamsMap = getUriQueryParamsMap(
+ cmResourceAddress.resourceIdentifier(), optionsParamInQuery,
+ topicParamInQuery, yangModelCmHandle.getModuleSetTag());
+ final Map<String, Object> uriVariableParamsMap = getUriVariableParamsMap(cmResourceAddress.datastoreName(),
+ yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA), cmResourceAddress.cmHandleId());
+ final String dmiResourceDataUrl = getDmiRequestUrl(uriQueryParamsMap, uriVariableParamsMap);
+
return dmiRestClient.postOperationWithJsonData(dmiResourceDataUrl, jsonRequestBody, READ, authorization);
}
@@ -111,9 +114,13 @@ public class DmiDataOperations extends DmiOperations {
final YangModelCmHandle yangModelCmHandle = getYangModelCmHandle(cmHandleId);
final String jsonRequestBody = getDmiRequestBody(READ, requestId, null, null,
yangModelCmHandle);
- final String dmiResourceDataUrl = getDmiRequestUrl(dataStoreName, cmHandleId, "/",
- null, null,
- yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA));
+
+ final MultiValueMap<String, String> uriQueryParamsMap = getUriQueryParamsMap("/", null,
+ null, yangModelCmHandle.getModuleSetTag());
+ final Map<String, Object> uriVariableParamsMap = getUriVariableParamsMap(dataStoreName,
+ yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA), cmHandleId);
+ final String dmiResourceDataUrl = getDmiRequestUrl(uriQueryParamsMap, uriVariableParamsMap);
+
final CmHandleState cmHandleState = yangModelCmHandle.getCompositeState().getCmHandleState();
validateIfCmHandleStateReady(yangModelCmHandle, cmHandleState);
return dmiRestClient.postOperationWithJsonData(dmiResourceDataUrl, jsonRequestBody, READ, null);
@@ -168,9 +175,13 @@ public class DmiDataOperations extends DmiOperations {
final YangModelCmHandle yangModelCmHandle = getYangModelCmHandle(cmHandleId);
final String jsonRequestBody = getDmiRequestBody(operationType, null, requestData, dataType,
yangModelCmHandle);
- final String dmiUrl = getDmiRequestUrl(PASSTHROUGH_RUNNING.getDatastoreName(), cmHandleId, resourceId,
- null, null,
- yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA));
+
+ final MultiValueMap<String, String> uriQueryParamsMap = getUriQueryParamsMap(resourceId, null,
+ null, yangModelCmHandle.getModuleSetTag());
+ final Map<String, Object> uriVariableParamsMap = getUriVariableParamsMap(PASSTHROUGH_RUNNING.getDatastoreName(),
+ yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA), cmHandleId);
+ final String dmiUrl = getDmiRequestUrl(uriQueryParamsMap, uriVariableParamsMap);
+
final CmHandleState cmHandleState = yangModelCmHandle.getCompositeState().getCmHandleState();
validateIfCmHandleStateReady(yangModelCmHandle, cmHandleState);
return dmiRestClient.postOperationWithJsonData(dmiUrl, jsonRequestBody, operationType, authorization);
@@ -195,16 +206,23 @@ public class DmiDataOperations extends DmiOperations {
return jsonObjectMapper.asJsonString(dmiRequestBody);
}
- private String getDmiRequestUrl(final String dataStoreName,
- final String cmHandleId,
- final String resourceId,
- final String optionsParamInQuery,
- final String topicParamInQuery,
- final String dmiServiceName) {
- return dmiServiceUrlBuilder.getDmiDatastoreUrl(
- dmiServiceUrlBuilder.populateQueryParams(resourceId, optionsParamInQuery,
- topicParamInQuery), dmiServiceUrlBuilder.populateUriVariables(dataStoreName, dmiServiceName,
- cmHandleId));
+ private String getDmiRequestUrl(final MultiValueMap<String, String> uriQueryParamsMap,
+ final Map<String, Object> uriVariableParamsMap) {
+ return dmiServiceUrlBuilder.getDmiDatastoreUrl(uriQueryParamsMap, uriVariableParamsMap);
+ }
+
+ private MultiValueMap<String, String> getUriQueryParamsMap(final String resourceId,
+ final String optionsParamInQuery,
+ final String topicParamInQuery,
+ final String moduleSetTagParamInQuery) {
+ return dmiServiceUrlBuilder.populateQueryParams(resourceId, optionsParamInQuery,
+ topicParamInQuery, moduleSetTagParamInQuery);
+ }
+
+ private Map<String, Object> getUriVariableParamsMap(final String dataStoreName,
+ final String dmiServiceName,
+ final String cmHandleId) {
+ return dmiServiceUrlBuilder.populateUriVariables(dataStoreName, dmiServiceName, cmHandleId);
}
private String getDmiServiceDataOperationRequestUrl(final String dmiServiceName,
@@ -226,7 +244,7 @@ public class DmiDataOperations extends DmiOperations {
}
private static Set<String> getDistinctCmHandleIdsFromDataOperationRequest(final DataOperationRequest
- dataOperationRequest) {
+ dataOperationRequest) {
return dataOperationRequest.getDataOperationDefinitions().stream()
.flatMap(dataOperationDefinition ->
dataOperationDefinition.getCmHandleIds().stream()).collect(Collectors.toSet());
@@ -235,7 +253,7 @@ public class DmiDataOperations extends DmiOperations {
private void buildDataOperationRequestUrlAndSendToDmiService(final String topicParamInQuery,
final String requestId,
final Map<String, List<DmiDataOperation>>
- groupsOutPerDmiServiceName,
+ groupsOutPerDmiServiceName,
final String authorization) {
groupsOutPerDmiServiceName.forEach((dmiServiceName, dmiDataOperationRequestBodies) -> {
@@ -256,36 +274,29 @@ public class DmiDataOperations extends DmiOperations {
try {
dmiRestClient.postOperationWithJsonData(dataOperationResourceUrl, dmiDataOperationRequestAsJsonString, READ,
authorization);
- } catch (final Exception exception) {
- handleTaskCompletionException(exception, dataOperationResourceUrl, dmiDataOperationRequestBodies);
+ } catch (final DmiClientRequestException e) {
+ handleTaskCompletionException(e, dataOperationResourceUrl, dmiDataOperationRequestBodies);
}
}
- private void handleTaskCompletionException(final Throwable throwable,
+ private void handleTaskCompletionException(final DmiClientRequestException dmiClientRequestException,
final String dataOperationResourceUrl,
final List<DmiDataOperation> dmiDataOperationRequestBodies) {
- if (throwable != null) {
- final MultiValueMap<String, String> dataOperationResourceUrlParameters =
- UriComponentsBuilder.fromUriString(dataOperationResourceUrl).build().getQueryParams();
- final String topicName = dataOperationResourceUrlParameters.get("topic").get(0);
- final String requestId = dataOperationResourceUrlParameters.get("requestId").get(0);
-
- final MultiValueMap<DmiDataOperation, Map<NcmpResponseStatus, List<String>>>
- cmHandleIdsPerResponseCodesPerOperation = new LinkedMultiValueMap<>();
-
- dmiDataOperationRequestBodies.forEach(dmiDataOperationRequestBody -> {
- final List<String> cmHandleIds = dmiDataOperationRequestBody.getCmHandles().stream()
- .map(CmHandle::getId).toList();
- if (throwable.getCause() instanceof HttpClientRequestException) {
- cmHandleIdsPerResponseCodesPerOperation.add(dmiDataOperationRequestBody,
- Map.of(UNABLE_TO_READ_RESOURCE_DATA, cmHandleIds));
- } else {
- cmHandleIdsPerResponseCodesPerOperation.add(dmiDataOperationRequestBody,
- Map.of(DMI_SERVICE_NOT_RESPONDING, cmHandleIds));
- }
- });
- ResourceDataOperationRequestUtils.publishErrorMessageToClientTopic(topicName, requestId,
- cmHandleIdsPerResponseCodesPerOperation);
- }
+ final MultiValueMap<String, String> dataOperationResourceUrlParameters =
+ UriComponentsBuilder.fromUriString(dataOperationResourceUrl).build().getQueryParams();
+ final String topicName = dataOperationResourceUrlParameters.get("topic").get(0);
+ final String requestId = dataOperationResourceUrlParameters.get("requestId").get(0);
+
+ final MultiValueMap<DmiDataOperation, Map<NcmpResponseStatus, List<String>>>
+ cmHandleIdsPerResponseCodesPerOperation = new LinkedMultiValueMap<>();
+
+ dmiDataOperationRequestBodies.forEach(dmiDataOperationRequestBody -> {
+ final List<String> cmHandleIds = dmiDataOperationRequestBody.getCmHandles().stream()
+ .map(DmiOperationCmHandle::getId).toList();
+ cmHandleIdsPerResponseCodesPerOperation.add(dmiDataOperationRequestBody,
+ Map.of(dmiClientRequestException.getNcmpResponseStatus(), cmHandleIds));
+ });
+ ResourceDataOperationRequestUtils.publishErrorMessageToClientTopic(topicName, requestId,
+ cmHandleIdsPerResponseCodesPerOperation);
}
}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiModelOperations.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiModelOperations.java
index 798f6de810..d54dcb8deb 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiModelOperations.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiModelOperations.java
@@ -34,7 +34,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.onap.cps.ncmp.api.impl.client.DmiRestClient;
-import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration;
+import org.onap.cps.ncmp.api.impl.config.DmiProperties;
import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence;
import org.onap.cps.ncmp.api.impl.utils.DmiServiceUrlBuilder;
import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
@@ -57,7 +57,7 @@ public class DmiModelOperations extends DmiOperations {
*/
public DmiModelOperations(final InventoryPersistence inventoryPersistence,
final JsonObjectMapper jsonObjectMapper,
- final NcmpConfiguration.DmiProperties dmiProperties,
+ final DmiProperties dmiProperties,
final DmiRestClient dmiRestClient, final DmiServiceUrlBuilder dmiServiceUrlBuilder) {
super(inventoryPersistence, jsonObjectMapper, dmiProperties, dmiRestClient, dmiServiceUrlBuilder);
}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/CmHandle.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiOperationCmHandle.java
index 618da74543..1bf2b77dcc 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/CmHandle.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiOperationCmHandle.java
@@ -29,14 +29,21 @@ import lombok.Getter;
@JsonInclude(JsonInclude.Include.NON_NULL)
@Getter
@Builder
-public class CmHandle {
+public class DmiOperationCmHandle {
private String id;
@JsonProperty("cmHandleProperties")
private Map<String, String> dmiProperties;
+ private String moduleSetTag;
- public static CmHandle buildCmHandleWithProperties(final String cmHandleId,
- final Map<String, String> dmiProperties) {
- return CmHandle.builder().id(cmHandleId).dmiProperties(dmiProperties).build();
+ /**
+ * Builds Dmi Operation Cm Handle object with all its associated properties.
+ */
+ public static DmiOperationCmHandle buildDmiOperationCmHandle(final String cmHandleId,
+ final Map<String, String> dmiProperties,
+ final String moduleSetTag) {
+ return DmiOperationCmHandle.builder().id(cmHandleId)
+ .dmiProperties(dmiProperties).moduleSetTag(moduleSetTag)
+ .build();
}
}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiOperations.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiOperations.java
index c8d73eac63..c195ab3096 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiOperations.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiOperations.java
@@ -1,6 +1,6 @@
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2021-2023 Nordix Foundation
+ * Copyright (C) 2021-2024 Nordix Foundation
* Modifications Copyright (C) 2022 Bell Canada
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -23,7 +23,7 @@ package org.onap.cps.ncmp.api.impl.operations;
import lombok.RequiredArgsConstructor;
import org.onap.cps.ncmp.api.impl.client.DmiRestClient;
-import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration;
+import org.onap.cps.ncmp.api.impl.config.DmiProperties;
import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence;
import org.onap.cps.ncmp.api.impl.utils.DmiServiceUrlBuilder;
import org.onap.cps.utils.JsonObjectMapper;
@@ -35,7 +35,7 @@ public class DmiOperations {
protected final InventoryPersistence inventoryPersistence;
protected final JsonObjectMapper jsonObjectMapper;
- protected final NcmpConfiguration.DmiProperties dmiProperties;
+ protected final DmiProperties dmiProperties;
protected final DmiRestClient dmiRestClient;
protected final DmiServiceUrlBuilder dmiServiceUrlBuilder;
@@ -44,6 +44,4 @@ public class DmiOperations {
.pathSegment("{resourceName}")
.buildAndExpand(dmiServiceName, dmiProperties.getDmiBasePath(), cmHandle, resourceName).toUriString();
}
-
-
}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilder.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilder.java
index 04acaa5e9b..9234d3c2f1 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilder.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilder.java
@@ -20,12 +20,14 @@
package org.onap.cps.ncmp.api.impl.utils;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import org.apache.logging.log4j.util.Strings;
import org.apache.logging.log4j.util.TriConsumer;
-import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration;
+import org.onap.cps.ncmp.api.impl.config.DmiProperties;
import org.onap.cps.spi.utils.CpsValidator;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
@@ -35,8 +37,7 @@ import org.springframework.web.util.UriComponentsBuilder;
@Component
@RequiredArgsConstructor
public class DmiServiceUrlBuilder {
-
- private final NcmpConfiguration.DmiProperties dmiProperties;
+ private final DmiProperties dmiProperties;
private final CpsValidator cpsValidator;
/**
@@ -133,20 +134,24 @@ public class DmiServiceUrlBuilder {
* This method is used to populate map from query params.
*
* @param resourceId unique id of response for valid topic
- * @param optionsParamInQuery options into url param
- * @param topicParamInQuery topic into url param
+ * @param optionsParamInQuery options as provided by client
+ * @param topicParamInQuery topic as provided by client
+ * @param moduleSetTag module set tag associated with the given cm handle
* @return all valid query params as map
*/
public MultiValueMap<String, String> populateQueryParams(final String resourceId,
final String optionsParamInQuery,
- final String topicParamInQuery) {
+ final String topicParamInQuery,
+ final String moduleSetTag) {
final MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
- getQueryParamConsumer().accept("resourceIdentifier",
- resourceId, queryParams);
+ getQueryParamConsumer().accept("resourceIdentifier", resourceId, queryParams);
getQueryParamConsumer().accept("options", optionsParamInQuery, queryParams);
if (Strings.isNotEmpty(topicParamInQuery)) {
getQueryParamConsumer().accept("topic", topicParamInQuery, queryParams);
}
+ if (Strings.isNotEmpty(moduleSetTag)) {
+ getQueryParamConsumer().accept("moduleSetTag", moduleSetTag, queryParams);
+ }
return queryParams;
}
@@ -168,7 +173,7 @@ public class DmiServiceUrlBuilder {
private TriConsumer<String, String, MultiValueMap<String, String>> getQueryParamConsumer() {
return (paramName, paramValue, paramMap) -> {
if (Strings.isNotEmpty(paramValue)) {
- paramMap.add(paramName, paramValue);
+ paramMap.add(paramName, URLEncoder.encode(paramValue, StandardCharsets.UTF_8));
}
};
}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/data/operation/ResourceDataOperationRequestUtils.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/data/operation/ResourceDataOperationRequestUtils.java
index 4b016b37d1..dc4108cac0 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/data/operation/ResourceDataOperationRequestUtils.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/data/operation/ResourceDataOperationRequestUtils.java
@@ -39,8 +39,8 @@ import lombok.extern.slf4j.Slf4j;
import org.onap.cps.events.EventsPublisher;
import org.onap.cps.ncmp.api.NcmpResponseStatus;
import org.onap.cps.ncmp.api.impl.inventory.CmHandleState;
-import org.onap.cps.ncmp.api.impl.operations.CmHandle;
import org.onap.cps.ncmp.api.impl.operations.DmiDataOperation;
+import org.onap.cps.ncmp.api.impl.operations.DmiOperationCmHandle;
import org.onap.cps.ncmp.api.impl.utils.DmiServiceNameOrganizer;
import org.onap.cps.ncmp.api.impl.utils.context.CpsApplicationContext;
import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
@@ -81,6 +81,8 @@ public class ResourceDataOperationRequestUtils {
final Map<String, String> dmiServiceNamesPerCmHandleId =
getDmiServiceNamesPerCmHandleId(dmiPropertiesPerCmHandleIdPerServiceName);
+ final Map<String, String> moduleSetTagPerCmHandle = getModuleSetTagPerCmHandleId(yangModelCmHandles);
+
for (final DataOperationDefinition dataOperationDefinitionIn :
dataOperationRequestIn.getDataOperationDefinitions()) {
final List<String> nonExistingCmHandleIds = new ArrayList<>();
@@ -97,9 +99,10 @@ public class ResourceDataOperationRequestUtils {
} else {
final DmiDataOperation dmiBatchOperationOut = getOrAddDmiBatchOperation(dmiServiceName,
dataOperationDefinitionIn, dmiDataOperationsOutPerDmiServiceName);
- final CmHandle cmHandle = CmHandle.buildCmHandleWithProperties(cmHandleId,
- cmHandleIdProperties);
- dmiBatchOperationOut.getCmHandles().add(cmHandle);
+ final DmiOperationCmHandle dmiOperationCmHandle = DmiOperationCmHandle
+ .buildDmiOperationCmHandle(cmHandleId, cmHandleIdProperties,
+ moduleSetTagPerCmHandle.get(cmHandleId));
+ dmiBatchOperationOut.getCmHandles().add(dmiOperationCmHandle);
}
}
}
@@ -114,6 +117,14 @@ public class ResourceDataOperationRequestUtils {
return dmiDataOperationsOutPerDmiServiceName;
}
+ private static Map<String, String> getModuleSetTagPerCmHandleId(
+ final Collection<YangModelCmHandle> yangModelCmHandles) {
+ final Map<String, String> moduleSetTagPerCmHandle = new HashMap<>(yangModelCmHandles.size());
+ yangModelCmHandles.forEach(yangModelCmHandle ->
+ moduleSetTagPerCmHandle.put(yangModelCmHandle.getId(), yangModelCmHandle.getModuleSetTag()));
+ return moduleSetTagPerCmHandle;
+ }
+
/**
* Handles the async task completion for an entire data, publishing errors to client topic on task failure.
*
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/client/DmiRestClientSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/client/DmiRestClientSpec.groovy
index c8e34b1a5e..d2dce06b04 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/client/DmiRestClientSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/client/DmiRestClientSpec.groovy
@@ -1,6 +1,6 @@
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2021-2023 Nordix Foundation
+ * Copyright (C) 2021-2024 Nordix Foundation
* Modifications Copyright (C) 2022 Bell Canada
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -21,28 +21,33 @@
package org.onap.cps.ncmp.api.impl.client
+import static org.onap.cps.ncmp.api.impl.operations.OperationType.READ
+import static org.onap.cps.ncmp.api.impl.operations.OperationType.PATCH
+import static org.onap.cps.ncmp.api.impl.operations.OperationType.CREATE
+import static org.springframework.http.HttpStatus.SERVICE_UNAVAILABLE
+import static org.onap.cps.ncmp.api.NcmpResponseStatus.DMI_SERVICE_NOT_RESPONDING
+import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNABLE_TO_READ_RESOURCE_DATA
+
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.node.ObjectNode
-import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration
-import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration.DmiProperties;
-import org.onap.cps.ncmp.api.impl.exception.HttpClientRequestException
+import org.onap.cps.ncmp.api.impl.config.DmiWebClientConfiguration
+import org.onap.cps.ncmp.api.impl.exception.InvalidDmiResourceUrlException
+import org.onap.cps.ncmp.api.impl.exception.DmiClientRequestException
import org.onap.cps.ncmp.utils.TestUtils
+import org.onap.cps.utils.JsonObjectMapper
import org.spockframework.spring.SpringBean
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
-import org.springframework.http.HttpEntity
import org.springframework.http.HttpHeaders
-import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.test.context.ContextConfiguration
import org.springframework.web.client.HttpServerErrorException
-import org.springframework.web.client.RestTemplate
+import org.springframework.web.reactive.function.client.WebClient
+import reactor.core.publisher.Mono
import spock.lang.Specification
-
-import static org.onap.cps.ncmp.api.impl.operations.OperationType.READ
-import static org.onap.cps.ncmp.api.impl.operations.OperationType.PATCH
-import static org.onap.cps.ncmp.api.impl.operations.OperationType.CREATE
+import org.springframework.web.reactive.function.client.WebClientResponseException
+import org.onap.cps.ncmp.api.impl.config.DmiProperties
@SpringBootTest
@ContextConfiguration(classes = [DmiProperties, DmiRestClient, ObjectMapper])
@@ -52,61 +57,103 @@ class DmiRestClientSpec extends Specification {
static final BASIC_AUTH_HEADER = 'Basic c29tZS11c2VyOnNvbWUtcGFzc3dvcmQ='
static final BEARER_AUTH_HEADER = 'Bearer my-bearer-token'
- @SpringBean
- RestTemplate mockRestTemplate = Mock(RestTemplate)
-
@Autowired
- NcmpConfiguration.DmiProperties dmiProperties
+ DmiProperties dmiProperties
@Autowired
DmiRestClient objectUnderTest
- @Autowired
- ObjectMapper objectMapper
+ @SpringBean
+ WebClient mockWebClient = Mock(WebClient);
- def responseFromRestTemplate = Mock(ResponseEntity)
+ @SpringBean
+ JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
+
+ def mockRequestBodyUriSpec = Mock(WebClient.RequestBodyUriSpec)
+ def mockResponseSpec = Mock(WebClient.ResponseSpec)
+ def mockResponseEntity = Mock(ResponseEntity)
+
+ def setup() {
+ mockRequestBodyUriSpec.uri(_) >> mockRequestBodyUriSpec
+ mockRequestBodyUriSpec.headers(_) >> mockRequestBodyUriSpec
+ mockRequestBodyUriSpec.retrieve() >> mockResponseSpec
+ }
def 'DMI POST operation with JSON.'() {
- given: 'the rest template returns a valid response entity for the expected parameters'
- mockRestTemplate.postForEntity('my url', _ as HttpEntity, Object.class) >> responseFromRestTemplate
+ given: 'the web client returns a valid response entity for the expected parameters'
+ mockWebClient.post() >> mockRequestBodyUriSpec
+ mockRequestBodyUriSpec.body(_) >> mockRequestBodyUriSpec
+ def monoSpec = Mono.just(mockResponseEntity)
+ mockResponseSpec.bodyToMono(Object.class) >> monoSpec
+ monoSpec.block() >> mockResponseEntity
when: 'POST operation is invoked'
- def result = objectUnderTest.postOperationWithJsonData('my url', 'some json', READ, null)
+ def response = objectUnderTest.postOperationWithJsonData('/my/url', 'some json', READ, null)
then: 'the output of the method is equal to the output from the test template'
- result == responseFromRestTemplate
+ assert response.statusCode.value() == 200
+ assert response.hasBody()
}
- def 'Failing DMI POST operation.'() {
- given: 'the rest template returns a valid response entity'
- def serverResponse = 'server response'.getBytes()
- def httpServerErrorException = new HttpServerErrorException(HttpStatus.FORBIDDEN, 'status text', serverResponse, null)
- mockRestTemplate.postForEntity(*_) >> { throw httpServerErrorException }
+ def 'Failing DMI POST operation for server error'() {
+ given: 'the web client throws an exception'
+ mockWebClient.post() >> { throw new HttpServerErrorException(SERVICE_UNAVAILABLE, null, null, null) }
when: 'POST operation is invoked'
- def result = objectUnderTest.postOperationWithJsonData('some url', 'some json', operation, null)
- then: 'a Http Client Exception is thrown'
- def thrown = thrown(HttpClientRequestException)
+ objectUnderTest.postOperationWithJsonData('/some', 'some json', READ, null)
+ then: 'a http client exception is thrown'
+ def thrown = thrown(DmiClientRequestException)
+ and: 'the exception has the relevant details from the error response'
+ assert thrown.ncmpResponseStatus.code == '102'
+ assert thrown.httpStatusCode == 503
+ }
+
+ def 'Failing DMI POST operation due to invalid dmi resource url.'() {
+ when: 'POST operation is invoked with invalid dmi resource url'
+ objectUnderTest.postOperationWithJsonData('/invalid dmi url', null, null, null)
+ then: 'invalid dmi resource url exception is thrown'
+ def thrown = thrown(InvalidDmiResourceUrlException)
and: 'the exception has the relevant details from the error response'
- assert thrown.httpStatus == 403
- assert thrown.message == "Unable to ${operation} resource data."
- assert thrown.details == 'server response'
- where: 'the following operation is executed'
+ assert thrown.httpStatus == 400
+ assert thrown.message == 'Invalid dmi resource url: /invalid dmi url'
+ where: 'the following operations are executed'
operation << [CREATE, READ, PATCH]
}
+ def 'Dmi service sends client error response when #scenario'() {
+ given: 'the web client unable to return response entity but error'
+ mockWebClient.post() >> mockRequestBodyUriSpec
+ mockRequestBodyUriSpec.body(_) >> mockRequestBodyUriSpec
+ def monoSpec = Mono.error(new WebClientResponseException('message', httpStatusCode, null, null, null, null))
+ mockResponseSpec.bodyToMono(Object.class) >> monoSpec
+ when: 'POST operation is invoked'
+ objectUnderTest.postOperationWithJsonData('/my/url', 'some json', READ, null)
+ then: 'a http client exception is thrown'
+ def thrown = thrown(DmiClientRequestException)
+ and: 'the exception has the relevant details from the error response'
+ assert thrown.ncmpResponseStatus.code == expectedNcmpResponseStatusCode
+ assert thrown.httpStatusCode == httpStatusCode
+ where: 'the following errors occur'
+ scenario | httpStatusCode | expectedNcmpResponseStatusCode
+ 'dmi request timeout' | 408 | DMI_SERVICE_NOT_RESPONDING.code
+ 'other error code' | 500 | UNABLE_TO_READ_RESOURCE_DATA.code
+ }
+
def 'Dmi trust level is determined by spring boot health status'() {
given: 'a health check response'
def dmiPluginHealthCheckResponseJsonData = TestUtils.getResourceFileContent('dmiPluginHealthCheckResponse.json')
- def jsonNode = objectMapper.readValue(dmiPluginHealthCheckResponseJsonData, JsonNode.class)
+ def jsonNode = jsonObjectMapper.convertJsonString(dmiPluginHealthCheckResponseJsonData, JsonNode.class)
((ObjectNode) jsonNode).put('status', 'my status')
- mockRestTemplate.getForObject(*_) >> {jsonNode}
+ def monoResponse = Mono.just(jsonNode)
+ mockWebClient.get() >> mockRequestBodyUriSpec
+ mockResponseSpec.bodyToMono(_) >> monoResponse
+ monoResponse.block() >> jsonNode
when: 'get trust level of the dmi plugin'
- def result = objectUnderTest.getDmiHealthStatus('some url')
+ def result = objectUnderTest.getDmiHealthStatus('some/url')
then: 'the status value from the json is return'
assert result == 'my status'
}
def 'Failing to get dmi plugin health status #scenario'() {
given: 'rest template with #scenario'
- mockRestTemplate.getForObject(*_) >> healthStatusResponse
+ mockWebClient.get() >> healthStatusResponse
when: 'attempt to get health status of the dmi plugin'
def result = objectUnderTest.getDmiHealthStatus('some url')
then: 'result will be empty'
@@ -132,5 +179,4 @@ class DmiRestClientSpec extends Specification {
'DMI basic auth disabled, with NCMP bearer token' | false | BEARER_AUTH_HEADER || BEARER_AUTH_HEADER
'DMI basic auth disabled, with NCMP basic auth' | false | BASIC_AUTH_HEADER || NO_AUTH_HEADER
}
-
}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/DmiWebClientConfigurationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/DmiWebClientConfigurationSpec.groovy
new file mode 100644
index 0000000000..ee7ab3f28d
--- /dev/null
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/DmiWebClientConfigurationSpec.groovy
@@ -0,0 +1,52 @@
+/*-
+ * ============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.api.impl.config
+
+import org.springframework.boot.context.properties.EnableConfigurationProperties
+import org.springframework.boot.test.context.SpringBootTest
+import org.springframework.test.context.ContextConfiguration
+import org.springframework.test.context.TestPropertySource
+import org.springframework.web.reactive.function.client.WebClient
+import spock.lang.Specification
+
+@SpringBootTest
+@ContextConfiguration(classes = [HttpClientConfiguration])
+@TestPropertySource(properties = ['ncmp.dmi.httpclient.connectionTimeoutInSeconds=1', 'ncmp.dmi.httpclient.maximumInMemorySizeInMegabytes=1'])
+@EnableConfigurationProperties
+class DmiWebClientConfigurationSpec extends Specification {
+
+ def httpClientConfiguration = Spy(HttpClientConfiguration.class)
+
+ def objectUnderTest = new DmiWebClientConfiguration(httpClientConfiguration)
+
+ def 'Web Client Configuration construction.'() {
+ expect: 'the system can create an instance'
+ new DmiWebClientConfiguration(httpClientConfiguration) != null
+ }
+
+ def 'Creating a WebClient instance.'() {
+ given: 'WebClient configuration invoked'
+ def webClientInstance = objectUnderTest.webClient()
+ expect: 'the system can create an instance'
+ assert webClientInstance != null
+ assert webClientInstance instanceof WebClient
+ }
+}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/HttpClientConfigurationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/HttpClientConfigurationSpec.groovy
index 2c76b5bb44..4ede360e68 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/HttpClientConfigurationSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/HttpClientConfigurationSpec.groovy
@@ -1,6 +1,6 @@
/*-
* ============LICENSE_START=======================================================
- * Copyright (C) 2023 Nordix Foundation.
+ * Copyright (C) 2023-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.
@@ -17,32 +17,31 @@
* SPDX-License-Identifier: Apache-2.0
* ============LICENSE_END=========================================================
*/
+
package org.onap.cps.ncmp.api.impl.config
-import java.time.Duration
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.ContextConfiguration
import org.springframework.test.context.TestPropertySource
-import org.springframework.test.context.support.AnnotationConfigContextLoader
import spock.lang.Specification
@SpringBootTest
@ContextConfiguration(classes = [HttpClientConfiguration])
@EnableConfigurationProperties(HttpClientConfiguration.class)
-@TestPropertySource(properties = ["ncmp.dmi.httpclient.connectionTimeoutInSeconds=1", "ncmp.dmi.httpclient.maximumConnectionsTotal=200"])
+@TestPropertySource(properties = ["ncmp.dmi.httpclient.readTimeoutInSeconds=123", "ncmp.dmi.httpclient.maximumConnectionsTotal=200"])
class HttpClientConfigurationSpec extends Specification {
@Autowired
private HttpClientConfiguration httpClientConfiguration
def 'Test HttpClientConfiguration properties with custom and default values'() {
- expect: 'custom property values'
- assert httpClientConfiguration.getConnectionTimeoutInSeconds() == Duration.ofSeconds(1)
- assert httpClientConfiguration.getMaximumConnectionsTotal() == 200
- and: 'default property values'
- assert httpClientConfiguration.getMaximumConnectionsPerRoute() == 50
- assert httpClientConfiguration.getIdleConnectionEvictionThresholdInSeconds() == Duration.ofSeconds(5)
+ expect: 'properties are populated correctly'
+ assert httpClientConfiguration.connectionTimeoutInSeconds == 123
+ assert httpClientConfiguration.readTimeoutInSeconds == 123
+ assert httpClientConfiguration.writeTimeoutInSeconds == 30
+ assert httpClientConfiguration.maximumConnectionsTotal == 200
+ assert httpClientConfiguration.maximumInMemorySizeInMegabytes == 16
}
}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/NcmpConfigurationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/NcmpConfigurationSpec.groovy
deleted file mode 100644
index 74e3424054..0000000000
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/NcmpConfigurationSpec.groovy
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * ============LICENSE_START=======================================================
- * Copyright (C) 2021-2023 Nordix Foundation
- * ================================================================================
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * SPDX-License-Identifier: Apache-2.0
- * ============LICENSE_END=========================================================
- */
-package org.onap.cps.ncmp.api.impl.config
-
-import org.apache.hc.client5.http.impl.classic.CloseableHttpClient
-import org.springframework.beans.factory.annotation.Autowired
-import org.springframework.boot.test.context.SpringBootTest
-import org.springframework.boot.web.client.RestTemplateBuilder
-import org.springframework.http.MediaType
-import org.springframework.http.client.HttpComponentsClientHttpRequestFactory
-import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
-import org.springframework.test.context.ContextConfiguration
-import org.springframework.web.client.RestTemplate
-import spock.lang.Specification
-
-@SpringBootTest
-@ContextConfiguration(classes = [NcmpConfiguration.DmiProperties, HttpClientConfiguration])
-class NcmpConfigurationSpec extends Specification{
-
- @Autowired
- NcmpConfiguration.DmiProperties dmiProperties
-
- @Autowired
- HttpClientConfiguration httpClientConfiguration
-
- def mockRestTemplateBuilder = new RestTemplateBuilder()
-
- def 'NcmpConfiguration Construction.'() {
- expect: 'the system can create an instance'
- new NcmpConfiguration() != null
- }
-
- def 'DMI Properties.'() {
- expect: 'properties are set to values in test configuration yaml file'
- dmiProperties.authUsername == 'some-user'
- dmiProperties.authPassword == 'some-password'
- }
-
- def 'Rest Template creation with CloseableHttpClient and MappingJackson2HttpMessageConverter.'() {
- when: 'a rest template is created'
- def result = NcmpConfiguration.restTemplate(mockRestTemplateBuilder, httpClientConfiguration)
- then: 'the rest template is returned'
- assert result instanceof RestTemplate
- and: 'the rest template is created with httpclient5'
- assert result.getRequestFactory() instanceof HttpComponentsClientHttpRequestFactory
- assert ((HttpComponentsClientHttpRequestFactory) result.getRequestFactory()).getHttpClient() instanceof CloseableHttpClient;
- and: 'a jackson media converter has been added'
- def lastMessageConverter = result.getMessageConverters().get(result.getMessageConverters().size()-1)
- lastMessageConverter instanceof MappingJackson2HttpMessageConverter
- and: 'the jackson media converters supports the expected media types'
- lastMessageConverter.getSupportedMediaTypes() == [MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN];
- }
-}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionNcmpInEventConsumerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionNcmpInEventConsumerSpec.groovy
index 9c84c51b2d..f07f3c1e6f 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionNcmpInEventConsumerSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/CmNotificationSubscriptionNcmpInEventConsumerSpec.groovy
@@ -72,8 +72,6 @@ class CmNotificationSubscriptionNcmpInEventConsumerSpec extends MessagingBaseSpe
.withSource(URI.create('some-resource'))
.withExtension('correlationid', 'test-cmhandle1').build()
def consumerRecord = new ConsumerRecord<String, CloudEvent>('topic-name', 0, 0, 'event-key', testCloudEventSent)
- and: 'notifications are enabled'
- objectUnderTest.notificationFeatureEnabled = true
when: 'the valid event is consumed'
objectUnderTest.consumeSubscriptionEvent(consumerRecord)
then: 'an event is logged with level INFO'
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiDataOperationsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiDataOperationsSpec.groovy
index eb6c7a0f48..d04052f792 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiDataOperationsSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiDataOperationsSpec.groovy
@@ -21,10 +21,18 @@
package org.onap.cps.ncmp.api.impl.operations
+import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNABLE_TO_READ_RESOURCE_DATA
+import static org.onap.cps.ncmp.api.impl.events.mapper.CloudEventMapper.toTargetEvent
+import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL
+import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING
+import static org.onap.cps.ncmp.api.impl.operations.OperationType.CREATE
+import static org.onap.cps.ncmp.api.impl.operations.OperationType.READ
+import static org.onap.cps.ncmp.api.impl.operations.OperationType.UPDATE
+
import com.fasterxml.jackson.databind.ObjectMapper
import org.onap.cps.events.EventsPublisher
-import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration
-import org.onap.cps.ncmp.api.impl.exception.HttpClientRequestException
+import org.onap.cps.ncmp.api.impl.config.DmiProperties
+import org.onap.cps.ncmp.api.impl.exception.DmiClientRequestException
import org.onap.cps.ncmp.api.impl.utils.DmiServiceUrlBuilder
import org.onap.cps.ncmp.api.impl.utils.context.CpsApplicationContext
import org.onap.cps.ncmp.api.models.DataOperationRequest
@@ -40,19 +48,8 @@ import org.springframework.http.ResponseEntity
import org.springframework.test.context.ContextConfiguration
import spock.lang.Shared
-import java.util.concurrent.TimeoutException
-
-import static org.onap.cps.ncmp.api.NcmpResponseStatus.DMI_SERVICE_NOT_RESPONDING
-import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNABLE_TO_READ_RESOURCE_DATA
-import static org.onap.cps.ncmp.api.impl.events.mapper.CloudEventMapper.toTargetEvent
-import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL
-import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING
-import static org.onap.cps.ncmp.api.impl.operations.OperationType.CREATE
-import static org.onap.cps.ncmp.api.impl.operations.OperationType.READ
-import static org.onap.cps.ncmp.api.impl.operations.OperationType.UPDATE
-
@SpringBootTest
-@ContextConfiguration(classes = [EventsPublisher, CpsApplicationContext, NcmpConfiguration.DmiProperties, DmiDataOperations])
+@ContextConfiguration(classes = [EventsPublisher, CpsApplicationContext, DmiProperties, DmiDataOperations])
class DmiDataOperationsSpec extends DmiOperationsBaseSpec {
@SpringBean
@@ -105,7 +102,7 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec {
and: 'a positive response from DMI service when it is called with valid request parameters'
def responseFromDmi = new ResponseEntity<Object>(HttpStatus.ACCEPTED)
def expectedDmiBatchResourceDataUrl = "ncmp/v1/data/topic=my-topic-name"
- def expectedBatchRequestAsJson = '{"operations":[{"operation":"read","operationId":"operational-14","datastore":"ncmp-datastore:passthrough-operational","options":"some option","resourceIdentifier":"some resource identifier","cmHandles":[{"id":"some-cm-handle","cmHandleProperties":{"prop1":"val1"}}]}]}'
+ def expectedBatchRequestAsJson = '{"operations":[{"operation":"read","operationId":"operational-14","datastore":"ncmp-datastore:passthrough-operational","options":"some option","resourceIdentifier":"some resource identifier","cmHandles":[{"id":"some-cm-handle","moduleSetTag":"","cmHandleProperties":{"prop1":"val1"}}]}]}'
mockDmiRestClient.postOperationWithJsonData(expectedDmiBatchResourceDataUrl, _, READ.operationName, NO_AUTH_HEADER) >> responseFromDmi
dmiServiceUrlBuilder.getDataOperationRequestUrl(_, _) >> expectedDmiBatchResourceDataUrl
when: 'get resource data for group of cm handles are invoked'
@@ -114,26 +111,22 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec {
1 * mockDmiRestClient.postOperationWithJsonData(expectedDmiBatchResourceDataUrl, expectedBatchRequestAsJson, READ, NO_AUTH_HEADER)
}
- def 'Execute (async) data operation from DMI service for #scenario.'() {
+ def 'Execute (async) data operation from DMI service with dmi client exception.'() {
given: 'data operation request body and dmi resource url'
def dmiDataOperation = DmiDataOperation.builder().operationId('some-operation-id').build()
- dmiDataOperation.getCmHandles().add(CmHandle.builder().id('some-cm-handle-id').build())
+ dmiDataOperation.getCmHandles().add(DmiOperationCmHandle.builder().id('some-cm-handle-id').build())
def dmiDataOperationResourceDataUrl = "http://dmi-service-name:dmi-port/dmi/v1/data?topic=my-topic-name&requestId=some-request-id"
def actualDataOperationCloudEvent = null
when: 'exception occurs after sending request to dmi service'
- objectUnderTest.handleTaskCompletionException(new Throwable(exception), dmiDataOperationResourceDataUrl, List.of(dmiDataOperation))
+ objectUnderTest.handleTaskCompletionException(new DmiClientRequestException(123, 'message', 'details', UNABLE_TO_READ_RESOURCE_DATA), dmiDataOperationResourceDataUrl, List.of(dmiDataOperation))
then: 'a cloud event is published'
eventsPublisher.publishCloudEvent('my-topic-name', 'some-request-id', _) >> { args -> actualDataOperationCloudEvent = args[2] }
and: 'the event contains the expected error details'
def eventDataValue = extractDataValue(actualDataOperationCloudEvent)
assert eventDataValue.operationId == dmiDataOperation.operationId
assert eventDataValue.ids == dmiDataOperation.cmHandles.id
- assert eventDataValue.statusCode == responseCode.code
- assert eventDataValue.statusMessage == responseCode.message
- where: 'the following exceptions are occurred'
- scenario | exception || responseCode
- 'http client request exception' | new HttpClientRequestException('error-message', 'error-details', HttpStatus.SERVICE_UNAVAILABLE.value()) || UNABLE_TO_READ_RESOURCE_DATA
- 'timeout exception' | new TimeoutException() || DMI_SERVICE_NOT_RESPONDING
+ assert eventDataValue.statusCode == '103'
+ assert eventDataValue.statusMessage == UNABLE_TO_READ_RESOURCE_DATA.message
}
def 'call get all resource data.'() {
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiModelOperationsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiModelOperationsSpec.groovy
index 9aab467479..88af0479db 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiModelOperationsSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiModelOperationsSpec.groovy
@@ -23,7 +23,7 @@ package org.onap.cps.ncmp.api.impl.operations
import com.fasterxml.jackson.core.JsonProcessingException
import com.fasterxml.jackson.databind.ObjectMapper
-import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration
+import org.onap.cps.ncmp.api.impl.config.DmiProperties
import org.onap.cps.spi.model.ModuleReference
import org.onap.cps.utils.JsonObjectMapper
import org.spockframework.spring.SpringBean
@@ -37,7 +37,7 @@ import spock.lang.Shared
import static org.onap.cps.ncmp.api.impl.operations.OperationType.READ
@SpringBootTest
-@ContextConfiguration(classes = [NcmpConfiguration.DmiProperties, DmiModelOperations])
+@ContextConfiguration(classes = [DmiProperties, DmiModelOperations])
class DmiModelOperationsSpec extends DmiOperationsBaseSpec {
@Shared
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiOperationsBaseSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiOperationsBaseSpec.groovy
index 72a0f2f11a..3518440cab 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiOperationsBaseSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiOperationsBaseSpec.groovy
@@ -1,6 +1,6 @@
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2021-2023 Nordix Foundation
+ * Copyright (C) 2021-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.
@@ -22,7 +22,7 @@ package org.onap.cps.ncmp.api.impl.operations
import com.fasterxml.jackson.databind.ObjectMapper
import org.onap.cps.ncmp.api.impl.client.DmiRestClient
-import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration
+import org.onap.cps.ncmp.api.impl.config.DmiProperties
import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
import org.onap.cps.ncmp.api.impl.utils.DmiServiceUrlBuilder
import org.onap.cps.ncmp.api.impl.inventory.CmHandleState
@@ -50,7 +50,7 @@ abstract class DmiOperationsBaseSpec extends Specification {
ObjectMapper spyObjectMapper = Spy()
@SpringBean
- DmiServiceUrlBuilder dmiServiceUrlBuilder = new DmiServiceUrlBuilder(new NcmpConfiguration.DmiProperties(), mockCpsValidator)
+ DmiServiceUrlBuilder dmiServiceUrlBuilder = new DmiServiceUrlBuilder(new DmiProperties(), mockCpsValidator)
def yangModelCmHandle = new YangModelCmHandle()
def static dmiServiceName = 'some service name'
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilderSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilderSpec.groovy
index fbf2c3d78d..2c7fa654cf 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilderSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilderSpec.groovy
@@ -22,10 +22,10 @@ package org.onap.cps.ncmp.api.impl.utils
import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING
+import org.onap.cps.ncmp.api.impl.config.DmiProperties
import org.onap.cps.ncmp.api.impl.operations.RequiredDmiService
import org.onap.cps.spi.utils.CpsValidator
import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
-import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration
import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
import spock.lang.Specification
@@ -34,7 +34,7 @@ class DmiServiceUrlBuilderSpec extends Specification {
static YangModelCmHandle yangModelCmHandle = YangModelCmHandle.toYangModelCmHandle('dmiServiceName',
'dmiDataServiceName', 'dmiModuleServiceName', new NcmpServiceCmHandle(cmHandleId: 'some-cm-handle-id'),'my-module-set-tag', 'my-alternate-id', 'my-data-producer-identifier')
- NcmpConfiguration.DmiProperties dmiProperties = new NcmpConfiguration.DmiProperties()
+ DmiProperties dmiProperties = new DmiProperties()
def mockCpsValidator = Mock(CpsValidator)
@@ -48,17 +48,19 @@ class DmiServiceUrlBuilderSpec extends Specification {
given: 'uri variables'
def uriVars = objectUnderTest.populateUriVariables(PASSTHROUGH_RUNNING.datastoreName, yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA), 'cmHandle')
and: 'query params'
- def uriQueries = objectUnderTest.populateQueryParams(resourceId, 'optionsParamInQuery', topic)
+ def uriQueries = objectUnderTest.populateQueryParams(resourceId, 'optionsParamInQuery', topic, moduleSetTag)
when: 'a dmi datastore service url is generated'
def dmiServiceUrl = objectUnderTest.getDmiDatastoreUrl(uriQueries, uriVars)
then: 'service url is generated as expected'
assert dmiServiceUrl == expectedDmiServiceUrl
where: 'the following parameters are used'
- scenario | topic | resourceId || expectedDmiServiceUrl
- 'With valid resourceId' | 'topicParamInQuery' | 'resourceId' || 'dmiServiceName/dmi/v1/ch/cmHandle/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=resourceId&options=optionsParamInQuery&topic=topicParamInQuery'
- 'With Empty resourceId' | 'topicParamInQuery' | '' || 'dmiServiceName/dmi/v1/ch/cmHandle/data/ds/ncmp-datastore:passthrough-running?options=optionsParamInQuery&topic=topicParamInQuery'
- 'With Empty dmi base path' | 'topicParamInQuery' | 'resourceId' || 'dmiServiceName/dmi/v1/ch/cmHandle/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=resourceId&options=optionsParamInQuery&topic=topicParamInQuery'
- 'With Empty topicParamInQuery' | '' | 'resourceId' || 'dmiServiceName/dmi/v1/ch/cmHandle/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=resourceId&options=optionsParamInQuery'
+ scenario | topic | moduleSetTag | resourceId || expectedDmiServiceUrl
+ 'With valid resourceId' | 'topicParamInQuery' | '' | 'resourceId' || 'dmiServiceName/dmi/v1/ch/cmHandle/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=resourceId&options=optionsParamInQuery&topic=topicParamInQuery'
+ 'With Empty resourceId' | 'topicParamInQuery' | '' | '' || 'dmiServiceName/dmi/v1/ch/cmHandle/data/ds/ncmp-datastore:passthrough-running?options=optionsParamInQuery&topic=topicParamInQuery'
+ 'With valid moduleSetTag' | 'topicParamInQuery' | 'module-set-tag1' | 'resourceId' || 'dmiServiceName/dmi/v1/ch/cmHandle/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=resourceId&options=optionsParamInQuery&topic=topicParamInQuery&moduleSetTag=module-set-tag1'
+ 'With Empty moduleSetTag' | 'topicParamInQuery' | '' | 'resourceId' || 'dmiServiceName/dmi/v1/ch/cmHandle/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=resourceId&options=optionsParamInQuery&topic=topicParamInQuery'
+ 'With Empty dmi base path' | 'topicParamInQuery' | '' | 'resourceId' || 'dmiServiceName/dmi/v1/ch/cmHandle/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=resourceId&options=optionsParamInQuery&topic=topicParamInQuery'
+ 'With Empty topicParamInQuery' | '' | '' | 'resourceId' || 'dmiServiceName/dmi/v1/ch/cmHandle/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=resourceId&options=optionsParamInQuery'
}
def 'Populate dmi data store url #scenario.'() {
@@ -66,15 +68,16 @@ class DmiServiceUrlBuilderSpec extends Specification {
dmiProperties.dmiBasePath = dmiBasePath
def uriVars = objectUnderTest.populateUriVariables(PASSTHROUGH_RUNNING.datastoreName, yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA), 'cmHandle')
and: 'null query params'
- def uriQueries = objectUnderTest.populateQueryParams(null, null, null)
+ def uriQueries = objectUnderTest.populateQueryParams(null, null, null, null)
when: 'a dmi datastore service url is generated'
def dmiServiceUrl = objectUnderTest.getDmiDatastoreUrl(uriQueries, uriVars)
then: 'the created dmi service url matches the expected'
assert dmiServiceUrl == expectedDmiServiceUrl
where: 'the following parameters are used'
- scenario | decription | dmiBasePath || expectedDmiServiceUrl
- 'with base path / ' | 'Invalid base path as it starts with /' | '/dmi' || 'dmiServiceName//dmi/v1/ch/cmHandle/data/ds/ncmp-datastore:passthrough-running'
- 'without base path / ' | 'Valid path as it does not starts with /' | 'dmi' || 'dmiServiceName/dmi/v1/ch/cmHandle/data/ds/ncmp-datastore:passthrough-running'
+ scenario | decription | dmiBasePath || expectedDmiServiceUrl
+ 'base path starts with /' | 'Remove / from start of base path' | '/dmi' || 'dmiServiceName/dmi/v1/ch/cmHandle/data/ds/ncmp-datastore:passthrough-running'
+ 'base path ends with / ' | 'Remove / from end of base path' | 'dmi/' || 'dmiServiceName/dmi/v1/ch/cmHandle/data/ds/ncmp-datastore:passthrough-running'
+ 'base path without any / ' | 'base path does not contains any /' | 'dmi' || 'dmiServiceName/dmi/v1/ch/cmHandle/data/ds/ncmp-datastore:passthrough-running'
}
def 'Bath request Url creation.'() {
@@ -85,7 +88,7 @@ class DmiServiceUrlBuilderSpec extends Specification {
when: 'a URL is created'
def result = objectUnderTest.getDataOperationRequestUrl(batchRequestQueryParams, batchRequestUriVariables)
then: 'it is formed correctly'
- assert result.toString() == 'some-service/testBase/v1/data?topic=some topic&requestId=some id'
+ assert result.toString() == 'some-service/testBase/v1/data?topic=some+topic&requestId=some+id'
}
def 'Populate batch uri variables.'() {
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/data/operation/ResourceDataOperationRequestUtilsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/data/operation/ResourceDataOperationRequestUtilsSpec.groovy
index 8df27bb62c..9028b9e5ed 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/data/operation/ResourceDataOperationRequestUtilsSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/data/operation/ResourceDataOperationRequestUtilsSpec.groovy
@@ -90,6 +90,23 @@ class ResourceDataOperationRequestUtilsSpec extends MessagingBaseSpec {
'dmi2' | 2 || 'operational-15' | 'ncmp-datastore:passthrough-operational' | ['ch4-dmi2']
}
+ def 'Process one data operation request with #serviceName and Module Set Tag set.'() {
+ given: 'data operation request'
+ def dataOperationRequestJsonData = TestUtils.getResourceFileContent('dataOperationRequest.json')
+ def dataOperationRequest = jsonObjectMapper.convertJsonString(dataOperationRequestJsonData, DataOperationRequest.class)
+ and: '1 known cm handles: ch1-dmi1'
+ def yangModelCmHandles = getYangModelCmHandlesForOneCmHandle()
+ when: 'data operation request is processed'
+ def operationsOutPerDmiServiceName = ResourceDataOperationRequestUtils.processPerDefinitionInDataOperationsRequest(clientTopic,'request-id', dataOperationRequest, yangModelCmHandles)
+ and: 'converted to a json node'
+ def dmiDataOperationRequestBody = operationsOutPerDmiServiceName['dmi1']
+ def cmHandlesInRequestBody = dmiDataOperationRequestBody[0].cmHandles
+ then: 'it contains the correct operation details'
+ assert cmHandlesInRequestBody.size() == 1
+ assert cmHandlesInRequestBody[0].id == 'ch1-dmi1'
+ assert cmHandlesInRequestBody[0].moduleSetTag == 'module-set-tag1'
+ }
+
def 'Process per data operation request with non-ready, non-existing cm handle and publish event to client specified topic'() {
given: 'consumer subscribing to client topic'
def cloudEventKafkaConsumer = new KafkaConsumer<>(eventConsumerConfigProperties('test-1', CloudEventDeserializer))
@@ -156,4 +173,10 @@ class ResourceDataOperationRequestUtilsSpec extends MessagingBaseSpec {
new YangModelCmHandle(id: 'non-ready-cm-handle', dmiServiceName: 'dmi2', dmiProperties: dmiProperties, compositeState: advisedState)
]
}
+
+ static def getYangModelCmHandlesForOneCmHandle() {
+ def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')]
+ def readyState = new CompositeStateBuilder().withCmHandleState(CmHandleState.READY).withLastUpdatedTimeNow().build()
+ return [new YangModelCmHandle(id: 'ch1-dmi1', dmiServiceName: 'dmi1', moduleSetTag: 'module-set-tag1', dmiProperties: dmiProperties, compositeState: readyState)]
+ }
}
diff --git a/cps-ncmp-service/src/test/resources/application.yml b/cps-ncmp-service/src/test/resources/application.yml
index 574b49982b..2a93f40816 100644
--- a/cps-ncmp-service/src/test/resources/application.yml
+++ b/cps-ncmp-service/src/test/resources/application.yml
@@ -1,5 +1,5 @@
# ============LICENSE_START=======================================================
-# Copyright (C) 2021-2023 Nordix Foundation
+# Copyright (C) 2021-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.
@@ -30,14 +30,15 @@ app:
async-m2m:
topic: ncmp-async-m2m
avc:
- subscription-topic: subscription
+ cm-subscription-ncmp-in: subscription
cm-events-topic: cm-events
- subscription-forward-topic-prefix: ${NCMP_FORWARD_CM_AVC_SUBSCRIPTION:ncmp-dmi-cm-avc-subscription-}
+ cm-subscription-dmi-in: ${CM_SUBSCRIPTION_DMI_IN_TOPIC:ncmp-dmi-cm-avc-subscription}
ncmp:
dmi:
httpclient:
- connectionTimeoutInSeconds: 180
+ connectionTimeoutInSeconds: 123
+ maximumInMemorySizeInMegabytes: 16
auth:
username: some-user
password: some-password
diff --git a/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/java/org/onap/cps/ncmp/dmi/rest/stub/controller/DmiRestStubController.java b/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/java/org/onap/cps/ncmp/dmi/rest/stub/controller/DmiRestStubController.java
index f154be6dae..a596afdce2 100644
--- a/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/java/org/onap/cps/ncmp/dmi/rest/stub/controller/DmiRestStubController.java
+++ b/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/java/org/onap/cps/ncmp/dmi/rest/stub/controller/DmiRestStubController.java
@@ -37,9 +37,9 @@ import lombok.extern.slf4j.Slf4j;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import org.onap.cps.ncmp.api.impl.utils.EventDateTimeFormatter;
-import org.onap.cps.ncmp.dmi.rest.stub.model.data.operational.CmHandle;
import org.onap.cps.ncmp.dmi.rest.stub.model.data.operational.DataOperationRequest;
import org.onap.cps.ncmp.dmi.rest.stub.model.data.operational.DmiDataOperationRequest;
+import org.onap.cps.ncmp.dmi.rest.stub.model.data.operational.DmiOperationCmHandle;
import org.onap.cps.ncmp.dmi.rest.stub.utils.ResourceFileReaderUtil;
import org.onap.cps.ncmp.events.async1_0_0.Data;
import org.onap.cps.ncmp.events.async1_0_0.DataOperationEvent;
@@ -205,9 +205,12 @@ public class DmiRestStubController {
@RequestParam(value = "resourceIdentifier") final String resourceIdentifier,
@RequestParam(value = "options", required = false) final String options,
@RequestParam(value = "topic", required = false) final String topic,
+ @RequestParam(value = "moduleSetTag", required = false) final String moduleSetTag,
@RequestHeader(value = "Authorization", required = false) final String authorization) {
log.info("DMI AUTH HEADER: {}", authorization);
delay(dataForCmHandleDelayMs);
+ log.info("Module set tag received: {}", moduleSetTag);
+
final String sampleJson = ResourceFileReaderUtil.getResourceFileContent(applicationContext.getResource(
ResourceLoader.CLASSPATH_URL_PREFIX + "data/operational/ietf-network-topology-sample-rfc8345.json"));
return ResponseEntity.ok(sampleJson);
@@ -235,8 +238,9 @@ public class DmiRestStubController {
}
dmiDataOperationRequest.getOperations().forEach(dmiDataOperation -> {
final DataOperationEvent dataOperationEvent = getDataOperationEvent(dmiDataOperation);
- dmiDataOperation.getCmHandles().forEach(cmHandle -> {
- dataOperationEvent.getData().getResponses().get(0).setIds(List.of(cmHandle.getId()));
+ dmiDataOperation.getCmHandles().forEach(dmiOperationCmHandle -> {
+ log.info("Module Set Tag received: {}", dmiOperationCmHandle.getModuleSetTag());
+ dataOperationEvent.getData().getResponses().get(0).setIds(List.of(dmiOperationCmHandle.getId()));
final CloudEvent cloudEvent = buildAndGetCloudEvent(topic, requestId, dataOperationEvent);
cloudEventKafkaTemplate.send(ncmpAsyncM2mTopic, UUID.randomUUID().toString(), cloudEvent);
});
@@ -267,10 +271,11 @@ public class DmiRestStubController {
private DataOperationEvent getDataOperationEvent(final DataOperationRequest dataOperationRequest) {
final Response response = new Response();
+
response.setOperationId(dataOperationRequest.getOperationId());
response.setStatusCode(SUCCESS.getCode());
response.setStatusMessage(SUCCESS.getMessage());
- response.setIds(dataOperationRequest.getCmHandles().stream().map(CmHandle::getId).toList());
+ response.setIds(dataOperationRequest.getCmHandles().stream().map(DmiOperationCmHandle::getId).toList());
response.setResourceIdentifier(dataOperationRequest.getResourceIdentifier());
response.setOptions(dataOperationRequest.getOptions());
final String ietfNetworkTopologySample = ResourceFileReaderUtil
diff --git a/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/java/org/onap/cps/ncmp/dmi/rest/stub/model/data/operational/DataOperationRequest.java b/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/java/org/onap/cps/ncmp/dmi/rest/stub/model/data/operational/DataOperationRequest.java
index 85c649e233..410774932b 100644
--- a/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/java/org/onap/cps/ncmp/dmi/rest/stub/model/data/operational/DataOperationRequest.java
+++ b/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/java/org/onap/cps/ncmp/dmi/rest/stub/model/data/operational/DataOperationRequest.java
@@ -35,5 +35,5 @@ public class DataOperationRequest {
private String datastore;
private String options;
private String resourceIdentifier;
- private List<CmHandle> cmHandles = new ArrayList<>();
+ private List<DmiOperationCmHandle> cmHandles = new ArrayList<>();
}
diff --git a/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/java/org/onap/cps/ncmp/dmi/rest/stub/model/data/operational/CmHandle.java b/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/java/org/onap/cps/ncmp/dmi/rest/stub/model/data/operational/DmiOperationCmHandle.java
index 93a90c917d..5cb24bc398 100644
--- a/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/java/org/onap/cps/ncmp/dmi/rest/stub/model/data/operational/CmHandle.java
+++ b/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/src/main/java/org/onap/cps/ncmp/dmi/rest/stub/model/data/operational/DmiOperationCmHandle.java
@@ -27,7 +27,8 @@ import lombok.Setter;
@Setter
@Getter
-public class CmHandle {
+public class DmiOperationCmHandle {
private String id;
private Map<String, String> cmHandleProperties = new HashMap<>();
+ private String moduleSetTag;
}
diff --git a/docs/api/swagger/ncmp/openapi.yaml b/docs/api/swagger/ncmp/openapi.yaml
index 9f6a1b2b66..7b33fa156b 100644
--- a/docs/api/swagger/ncmp/openapi.yaml
+++ b/docs/api/swagger/ncmp/openapi.yaml
@@ -28,8 +28,7 @@ paths:
schema:
example: my-cm-handle
type: string
- - allowReserved: true
- description: The format of resource identifier depend on the associated DMI
+ - description: The format of resource identifier depend on the associated DMI
Plugin implementation. For ONAP DMI Plugin it will be RESTConf paths but
it can really be anything.
examples:
@@ -139,8 +138,7 @@ paths:
schema:
example: my-cm-handle
type: string
- - allowReserved: true
- description: The format of resource identifier depend on the associated DMI
+ - description: The format of resource identifier depend on the associated DMI
Plugin implementation. For ONAP DMI Plugin it will be RESTConf paths but
it can really be anything.
examples:
@@ -158,8 +156,7 @@ paths:
required: true
schema:
type: string
- - allowReserved: true
- description: "options parameter in query, it is mandatory to wrap key(s)=value(s)\
+ - description: "options parameter in query, it is mandatory to wrap key(s)=value(s)\
\ in parenthesis'()'. The format of options parameter depend on the associated\
\ DMI Plugin implementation."
examples:
@@ -177,8 +174,7 @@ paths:
required: false
schema:
type: string
- - allowReserved: true
- description: topic parameter in query.
+ - description: topic parameter in query.
examples:
sample 1:
value:
@@ -276,8 +272,7 @@ paths:
schema:
example: my-cm-handle
type: string
- - allowReserved: true
- description: The format of resource identifier depend on the associated DMI
+ - description: The format of resource identifier depend on the associated DMI
Plugin implementation. For ONAP DMI Plugin it will be RESTConf paths but
it can really be anything.
examples:
@@ -390,8 +385,7 @@ paths:
schema:
example: my-cm-handle
type: string
- - allowReserved: true
- description: The format of resource identifier depend on the associated DMI
+ - description: The format of resource identifier depend on the associated DMI
Plugin implementation. For ONAP DMI Plugin it will be RESTConf paths but
it can really be anything.
examples:
@@ -509,8 +503,7 @@ paths:
schema:
example: my-cm-handle
type: string
- - allowReserved: true
- description: The format of resource identifier depend on the associated DMI
+ - description: The format of resource identifier depend on the associated DMI
Plugin implementation. For ONAP DMI Plugin it will be RESTConf paths but
it can really be anything.
examples:
@@ -619,8 +612,7 @@ paths:
is supported.
operationId: executeDataOperationForCmHandles
parameters:
- - allowReserved: true
- description: mandatory topic parameter in query.
+ - description: mandatory topic parameter in query.
examples:
sample 1:
value:
@@ -736,8 +728,7 @@ paths:
schema:
default: /
type: string
- - allowReserved: true
- description: "options parameter in query, it is mandatory to wrap key(s)=value(s)\
+ - description: "options parameter in query, it is mandatory to wrap key(s)=value(s)\
\ in parenthesis'()'. The format of options parameter depend on the associated\
\ DMI Plugin implementation."
examples:
@@ -755,8 +746,7 @@ paths:
required: false
schema:
type: string
- - allowReserved: true
- description: topic parameter in query.
+ - description: topic parameter in query.
examples:
sample 1:
value:
@@ -1484,7 +1474,6 @@ components:
example: my-cm-handle
type: string
resourceIdentifierInQuery:
- allowReserved: true
description: The format of resource identifier depend on the associated DMI
Plugin implementation. For ONAP DMI Plugin it will be RESTConf paths but it
can really be anything.
@@ -1504,7 +1493,6 @@ components:
schema:
type: string
optionsParamInQuery:
- allowReserved: true
description: "options parameter in query, it is mandatory to wrap key(s)=value(s)\
\ in parenthesis'()'. The format of options parameter depend on the associated\
\ DMI Plugin implementation."
@@ -1524,7 +1512,6 @@ components:
schema:
type: string
topicParamInQuery:
- allowReserved: true
description: topic parameter in query.
examples:
sample 1:
@@ -1561,7 +1548,6 @@ components:
example: application/yang-data+json
type: string
requiredTopicParamInQuery:
- allowReserved: true
description: mandatory topic parameter in query.
examples:
sample 1:
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpRestApiSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpRestApiSpec.groovy
index 950cd65e72..5325f1a86e 100644
--- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpRestApiSpec.groovy
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpRestApiSpec.groovy
@@ -20,9 +20,6 @@
package org.onap.cps.integration.functional
-import org.onap.cps.integration.base.CpsIntegrationSpecBase
-import org.springframework.http.MediaType
-import spock.util.concurrent.PollingConditions
import static org.hamcrest.Matchers.containsInAnyOrder
import static org.hamcrest.Matchers.hasSize
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
@@ -30,6 +27,10 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
+import org.onap.cps.integration.base.CpsIntegrationSpecBase
+import org.springframework.http.MediaType
+import spock.util.concurrent.PollingConditions
+
class NcmpRestApiSpec extends CpsIntegrationSpecBase {
def 'Register CM Handles using REST API.'() {
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/performance/ncmp/CmHandleQueryByAlternateIdPerfTest.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/performance/ncmp/CmHandleQueryByAlternateIdPerfTest.groovy
index bbff5a83ff..4f33d030ed 100644
--- a/integration-test/src/test/groovy/org/onap/cps/integration/performance/ncmp/CmHandleQueryByAlternateIdPerfTest.groovy
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/performance/ncmp/CmHandleQueryByAlternateIdPerfTest.groovy
@@ -20,14 +20,16 @@
package org.onap.cps.integration.performance.ncmp
-import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
-
-import org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence
-import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence
import org.onap.cps.integration.ResourceMeter
import org.onap.cps.integration.performance.base.NcmpPerfTestBase
+import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence
+
import java.util.stream.Collectors
+import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME
+import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR
+import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
+
class CmHandleQueryByAlternateIdPerfTest extends NcmpPerfTestBase {
InventoryPersistence objectUnderTest
@@ -43,11 +45,11 @@ class CmHandleQueryByAlternateIdPerfTest extends NcmpPerfTestBase {
and: 'the ids of the result are extracted and converted to xpath'
def cpsXpaths = dataNodes.stream().map(dataNode -> "/dmi-registry/cm-handles[@id='${dataNode.leaves.id}']".toString() ).collect(Collectors.toSet())
and: 'a single get is executed to get all the parent objects and their descendants'
- cpsDataService.getDataNodesForMultipleXpaths(NcmpPersistence.NCMP_DATASPACE_NAME, NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR, cpsXpaths, OMIT_DESCENDANTS)
+ cpsDataService.getDataNodesForMultipleXpaths(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, cpsXpaths, OMIT_DESCENDANTS)
resourceMeter.stop()
def durationInSeconds = resourceMeter.getTotalTimeInSeconds()
print 'Total time in seconds to query ch handle by alternate id: ' + durationInSeconds
then: 'the required operations are performed within required time and memory limit'
- recordAndAssertResourceUsage('CpsPath Registry attributes Query', 1, durationInSeconds, 300, resourceMeter.getTotalMemoryUsageInMB())
+ recordAndAssertResourceUsage('Look up cm-handle by longest match alternate-id', 1, durationInSeconds, 300, resourceMeter.getTotalMemoryUsageInMB())
}
}
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/performance/ncmp/CmHandleQueryPerfTest.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/performance/ncmp/CmHandleQueryPerfTest.groovy
index d518234f5c..db0e24e938 100644
--- a/integration-test/src/test/groovy/org/onap/cps/integration/performance/ncmp/CmHandleQueryPerfTest.groovy
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/performance/ncmp/CmHandleQueryPerfTest.groovy
@@ -20,7 +20,6 @@
package org.onap.cps.integration.performance.ncmp
-
import org.onap.cps.api.CpsQueryService
import org.onap.cps.integration.ResourceMeter
import org.onap.cps.integration.performance.base.NcmpPerfTestBase
@@ -68,7 +67,7 @@ class CmHandleQueryPerfTest extends NcmpPerfTestBase {
resourceMeter.stop()
def durationInSeconds = resourceMeter.getTotalTimeInSeconds()
then: 'the required operations are performed within required time'
- recordAndAssertResourceUsage("CpsPath Registry attributes Query", 3.96, durationInSeconds, 300, resourceMeter.getTotalMemoryUsageInMB())
+ recordAndAssertResourceUsage("CpsPath Registry attributes Query", 3.96, durationInSeconds, 400, resourceMeter.getTotalMemoryUsageInMB())
and: 'all nodes are returned'
result.size() == TOTAL_CM_HANDLES
and: 'the tree contains all the expected descendants too'
diff --git a/integration-test/src/test/resources/application.yml b/integration-test/src/test/resources/application.yml
index 6fd3bcae4e..a4b9ea9c40 100644
--- a/integration-test/src/test/resources/application.yml
+++ b/integration-test/src/test/resources/application.yml
@@ -97,10 +97,10 @@ app:
async-m2m:
topic: ${NCMP_ASYNC_M2M_TOPIC:ncmp-async-m2m}
avc:
- subscription-topic: ${NCMP_CM_AVC_SUBSCRIPTION:subscription}
- subscription-forward-topic-prefix: ${NCMP_FORWARD_CM_AVC_SUBSCRIPTION:ncmp-dmi-cm-avc-subscription-}
- subscription-response-topic: ${NCMP_RESPONSE_CM_AVC_SUBSCRIPTION:dmi-ncmp-cm-avc-subscription}
- subscription-outcome-topic: ${NCMP_OUTCOME_CM_AVC_SUBSCRIPTION:subscription-response}
+ cm-subscription-ncmp-in: ${CM_SUBSCRIPTION_NCMP_IN_TOPIC:subscription}
+ cm-subscription-dmi-in: ${CM_SUBSCRIPTION_DMI_IN_TOPIC:ncmp-dmi-cm-avc-subscription}
+ cm-subscription-dmi-out: ${CM_SUBSCRIPTION_DMI_OUT_TOPIC:dmi-ncmp-cm-avc-subscription}
+ cm-subscription-ncmp-out: ${CM_SUBSCRIPTION_NCMP_OUT_TOPIC:subscription-response}
cm-events-topic: ${NCMP_CM_EVENTS_TOPIC:cm-events}
lcm:
events:
@@ -169,6 +169,7 @@ ncmp:
maximumConnectionsPerRoute: 50
maximumConnectionsTotal: 100
idleConnectionEvictionThresholdInSeconds: 5
+ maximumInMemorySizeInMegabytes: 16
auth:
username: dmi
password: dmi
diff --git a/k6-tests/README.md b/k6-tests/README.md
new file mode 100644
index 0000000000..e26b18609c
--- /dev/null
+++ b/k6-tests/README.md
@@ -0,0 +1,25 @@
+# k6 tests
+
+[k6](https://k6.io/) is used for performance tests.
+k6 tests are written in JavaScript.
+
+## k6 installation
+Follow the instructions in the [k6 installation guide](https://grafana.com/docs/k6/latest/set-up/install-k6/)
+to get started.
+
+## Running the k6 test suites
+Simply run the main script. (The script assumes k6 and docker-compose have been installed).
+```shell
+./run-k6-tests.sh
+```
+
+## Running k6 tests manually
+Before running tests, ensure CPS/NCMP is running:
+```shell
+docker-compose -f docker-compose/docker-compose.yml --profile dmi-stub up
+```
+
+To run an individual test from command line, use
+```shell
+k6 run ncmp/1-create-cmhandles.js
+```
diff --git a/k6-tests/ncmp/1-create-cmhandles.js b/k6-tests/ncmp/1-create-cmhandles.js
new file mode 100644
index 0000000000..c32cd2f03d
--- /dev/null
+++ b/k6-tests/ncmp/1-create-cmhandles.js
@@ -0,0 +1,56 @@
+/*
+ * ============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=========================================================
+ */
+
+import http from 'k6/http';
+import exec from 'k6/execution';
+import { check } from 'k6';
+import { NCMP_BASE_URL, DMI_PLUGIN_URL, TOTAL_CM_HANDLES, makeBatchOfCmHandleIds } from './utils.js';
+
+const BATCH_SIZE = 100;
+export const options = {
+ vus: 1,
+ iterations: Math.ceil(TOTAL_CM_HANDLES / BATCH_SIZE),
+ thresholds: {
+ http_req_failed: ['rate == 0'],
+ http_req_duration: ['avg <= 1000'],
+ },
+};
+
+export default function() {
+ const nextBatchOfCmHandleIds = makeBatchOfCmHandleIds(BATCH_SIZE, exec.scenario.iterationInTest);
+ const payload = {
+ "dmiPlugin": DMI_PLUGIN_URL,
+ "createdCmHandles": nextBatchOfCmHandleIds.map(cmHandleId => ({
+ "cmHandle": cmHandleId,
+ "cmHandleProperties": { "neType": "RadioNode" },
+ "publicCmHandleProperties": {
+ "Color": "yellow",
+ "Size": "small",
+ "Shape": "cube"
+ }
+ })),
+ };
+ const response = http.post(NCMP_BASE_URL + '/ncmpInventory/v1/ch', JSON.stringify(payload), {
+ headers: { 'Content-Type': 'application/json' },
+ });
+ check(response, {
+ 'status equals 200': (r) => r.status === 200,
+ });
+}
diff --git a/k6-tests/ncmp/10-mixed-load-test.js b/k6-tests/ncmp/10-mixed-load-test.js
new file mode 100644
index 0000000000..d13afa54ab
--- /dev/null
+++ b/k6-tests/ncmp/10-mixed-load-test.js
@@ -0,0 +1,88 @@
+/*
+ * ============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=========================================================
+ */
+
+import http from 'k6/http';
+import { check } from 'k6';
+import { NCMP_BASE_URL, getRandomCmHandleId } from './utils.js'
+import { searchRequest } from './search-base.js';
+
+export const options = {
+ scenarios: {
+ passthrough_read: {
+ executor: 'constant-vus',
+ exec: 'passthrough_read',
+ vus: 10,
+ duration: '1m',
+ },
+ id_search_module: {
+ executor: 'constant-vus',
+ exec: 'id_search_module',
+ vus: 5,
+ duration: '1m',
+ },
+ cm_search_module: {
+ executor: 'constant-vus',
+ exec: 'cm_search_module',
+ vus: 5,
+ duration: '1m',
+ },
+ },
+
+ thresholds: {
+ http_req_failed: ['rate==0'],
+ 'http_req_duration{scenario:passthrough_read}': ['avg <= 2540'], // DMI delay + 40 ms
+ 'http_req_duration{scenario:id_search_module}': ['avg <= 200'],
+ 'http_req_duration{scenario:cm_search_module}': ['avg <= 35_000'],
+ },
+};
+
+export function passthrough_read() {
+ const cmHandleId = getRandomCmHandleId();
+ const datastoreName = 'ncmp-datastore%3Apassthrough-operational';
+ const url = `${NCMP_BASE_URL}/ncmp/v1/ch/${cmHandleId}/data/ds/${datastoreName}?resourceIdentifier=x&include-descendants=true`
+ const response = http.get(url);
+ check(response, {
+ 'status equals 200': (r) => r.status === 200,
+ });
+}
+
+export function id_search_module() {
+ const search_filter = {
+ "cmHandleQueryParameters": [
+ {
+ "conditionName": "hasAllModules",
+ "conditionParameters": [ {"moduleName": "ietf-yang-types-1"} ]
+ }
+ ]
+ };
+ searchRequest('id-searches', JSON.stringify(search_filter));
+}
+
+export function cm_search_module() {
+ const search_filter = {
+ "cmHandleQueryParameters": [
+ {
+ "conditionName": "hasAllModules",
+ "conditionParameters": [ {"moduleName": "ietf-yang-types-1"} ]
+ }
+ ]
+ };
+ searchRequest('searches', JSON.stringify(search_filter));
+}
diff --git a/k6-tests/ncmp/11-delete-cmhandles.js b/k6-tests/ncmp/11-delete-cmhandles.js
new file mode 100644
index 0000000000..daced8ddc8
--- /dev/null
+++ b/k6-tests/ncmp/11-delete-cmhandles.js
@@ -0,0 +1,48 @@
+/*
+ * ============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=========================================================
+ */
+
+import http from 'k6/http';
+import exec from 'k6/execution';
+import { check } from 'k6';
+import { NCMP_BASE_URL, TOTAL_CM_HANDLES, makeBatchOfCmHandleIds } from './utils.js';
+
+const BATCH_SIZE = 100;
+export const options = {
+ vus: 1,
+ iterations: Math.ceil(TOTAL_CM_HANDLES / BATCH_SIZE),
+ thresholds: {
+ http_req_failed: ['rate == 0'],
+ http_req_duration: ['avg <= 1000'],
+ },
+};
+
+export default function() {
+ const nextBatchOfCmHandleIds = makeBatchOfCmHandleIds(BATCH_SIZE, exec.scenario.iterationInTest);
+ const payload = {
+ "dmiPlugin": "http://ncmp-dmi-plugin-demo-and-csit-stub:8092",
+ "removedCmHandles": nextBatchOfCmHandleIds,
+ };
+ const response = http.post(NCMP_BASE_URL + '/ncmpInventory/v1/ch', JSON.stringify(payload), {
+ headers: { 'Content-Type': 'application/json' },
+ });
+ check(response, {
+ 'status equals 200': (r) => r.status === 200,
+ });
+}
diff --git a/k6-tests/ncmp/2-wait-for-cmhandles-to-be-ready.js b/k6-tests/ncmp/2-wait-for-cmhandles-to-be-ready.js
new file mode 100644
index 0000000000..04a5c21dd6
--- /dev/null
+++ b/k6-tests/ncmp/2-wait-for-cmhandles-to-be-ready.js
@@ -0,0 +1,64 @@
+/*
+ * ============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=========================================================
+ */
+
+import http from 'k6/http';
+import { sleep, fail } from 'k6';
+import { NCMP_BASE_URL, TOTAL_CM_HANDLES } from './utils.js';
+
+export const options = {
+ vus: 1,
+ iterations: 1,
+ thresholds: {
+ http_req_failed: ['rate == 0'],
+ iteration_duration: ['max <= 300_000'], // 5 minutes
+ },
+};
+
+export default function() {
+ waitForCmHandlesToBeReady(TOTAL_CM_HANDLES);
+}
+
+function waitForCmHandlesToBeReady(totalCmHandles) {
+ const timeOutInSeconds = 6 * 60;
+ const pollingIntervalInSeconds = 10;
+ const maxRetries = Math.ceil(timeOutInSeconds / pollingIntervalInSeconds);
+ let cmHandlesReady = 0;
+ for (let currentTry = 0; currentTry <= maxRetries; currentTry++) {
+ sleep(pollingIntervalInSeconds);
+ try {
+ cmHandlesReady = getNumberOfReadyCmHandles();
+ } catch (error) {
+ console.error(`Attempt ${currentTry + 1} - Error fetching CM handles: ${error.message}`);
+ }
+ console.log(`Attempt ${currentTry + 1} - ${cmHandlesReady}/${totalCmHandles} CM handles are READY`);
+ if (cmHandlesReady === totalCmHandles) {
+ console.log(`All ${totalCmHandles} CM handles are READY`);
+ return;
+ }
+ }
+ fail(`Timed out after ${timeoutInSeconds} seconds waiting for ${totalCmHandles} CM handles to be READY`);
+}
+
+function getNumberOfReadyCmHandles() {
+ const endpointUrl = `${NCMP_BASE_URL}/cps/api/v2/dataspaces/NCMP-Admin/anchors/ncmp-dmi-registry/node?xpath=/dmi-registry&descendants=all`;
+ const jsonData = http.get(endpointUrl).json();
+ const cmHandles = jsonData[0]["dmi-reg:dmi-registry"]["cm-handles"];
+ return cmHandles.filter(cmhandle => cmhandle['state']['cm-handle-state'] === 'READY').length;
+}
diff --git a/k6-tests/ncmp/3-passthrough-read.js b/k6-tests/ncmp/3-passthrough-read.js
new file mode 100644
index 0000000000..912cee5997
--- /dev/null
+++ b/k6-tests/ncmp/3-passthrough-read.js
@@ -0,0 +1,51 @@
+/*
+ * ============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=========================================================
+ */
+
+import http from 'k6/http';
+import { check } from 'k6';
+import { Trend } from "k6/metrics";
+import { NCMP_BASE_URL, getRandomCmHandleId } from './utils.js'
+
+let ncmpOverheadTrend = new Trend("ncmp_overhead");
+
+export const options = {
+ vus: 12,
+ duration: '30s',
+ thresholds: {
+ http_req_failed: ['rate == 0'],
+ ncmp_overhead: ['avg <= 40'],
+ },
+};
+
+// The function that defines VU logic.
+export default function() {
+ const cmHandleId = getRandomCmHandleId();
+ const datastoreName = 'ncmp-datastore%3Apassthrough-operational';
+ const url = `${NCMP_BASE_URL}/ncmp/v1/ch/${cmHandleId}/data/ds/${datastoreName}?resourceIdentifier=x&include-descendants=true`
+ const response = http.get(url);
+ check(response, {
+ 'status equals 200': (r) => r.status === 200,
+ });
+
+ // Calculate overhead assuming DMI data delay is 2500ms.
+ const dmiDelay = 2500; // This should be same as value DATA_FOR_CM_HANDLE_DELAY_MS in docker-compose.yml
+ const overhead = response.timings.duration - dmiDelay;
+ ncmpOverheadTrend.add(overhead);
+}
diff --git a/k6-tests/ncmp/4-id-search-no-filter.js b/k6-tests/ncmp/4-id-search-no-filter.js
new file mode 100644
index 0000000000..774c609423
--- /dev/null
+++ b/k6-tests/ncmp/4-id-search-no-filter.js
@@ -0,0 +1,35 @@
+/*
+ * ============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=========================================================
+ */
+
+import { searchRequest } from './search-base.js';
+
+export const options = {
+ vus: 5,
+ duration: '60s',
+ thresholds: {
+ http_req_failed: ['rate == 0'],
+ http_req_duration: ['avg <= 800'],
+ },
+};
+
+// The function that defines VU logic.
+export default function() {
+ searchRequest('id-searches', '{}')
+}
diff --git a/k6-tests/ncmp/5-search-no-filter.js b/k6-tests/ncmp/5-search-no-filter.js
new file mode 100644
index 0000000000..fa08d0c40c
--- /dev/null
+++ b/k6-tests/ncmp/5-search-no-filter.js
@@ -0,0 +1,34 @@
+/*
+ * ============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=========================================================
+ */
+
+import { searchRequest } from './search-base.js';
+
+export const options = {
+ vus: 5,
+ duration: '60s',
+ thresholds: {
+ http_req_failed: ['rate == 0'],
+ http_req_duration: ['avg <= 35_000'],
+ },
+};
+
+export default function() {
+ searchRequest('searches', '{}')
+}
diff --git a/k6-tests/ncmp/6-id-search-public-property.js b/k6-tests/ncmp/6-id-search-public-property.js
new file mode 100644
index 0000000000..e8b584aecb
--- /dev/null
+++ b/k6-tests/ncmp/6-id-search-public-property.js
@@ -0,0 +1,42 @@
+/*
+ * ============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=========================================================
+ */
+
+import { searchRequest } from './search-base.js';
+
+export const options = {
+ vus: 5,
+ duration: '30s',
+ thresholds: {
+ http_req_failed: ['rate == 0'],
+ http_req_duration: ['avg <= 5000'],
+ },
+};
+
+export default function() {
+ const search_filter = {
+ "cmHandleQueryParameters": [
+ {
+ "conditionName": "hasAllProperties",
+ "conditionParameters": [ {"Color": "yellow"}, {"Size": "small"} ]
+ }
+ ]
+ };
+ searchRequest('id-searches', JSON.stringify(search_filter));
+}
diff --git a/k6-tests/ncmp/7-search-public-property.js b/k6-tests/ncmp/7-search-public-property.js
new file mode 100644
index 0000000000..3e052dabe0
--- /dev/null
+++ b/k6-tests/ncmp/7-search-public-property.js
@@ -0,0 +1,42 @@
+/*
+ * ============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=========================================================
+ */
+
+import { searchRequest } from './search-base.js';
+
+export const options = {
+ vus: 5,
+ duration: '30s',
+ thresholds: {
+ http_req_failed: ['rate == 0'],
+ http_req_duration: ['avg <= 35_000'],
+ },
+};
+
+export default function() {
+ const search_filter = {
+ "cmHandleQueryParameters": [
+ {
+ "conditionName": "hasAllProperties",
+ "conditionParameters": [ {"Color": "yellow"}, {"Size": "small"} ]
+ }
+ ]
+ };
+ searchRequest('searches', JSON.stringify(search_filter));
+}
diff --git a/k6-tests/ncmp/8-id-search-module.js b/k6-tests/ncmp/8-id-search-module.js
new file mode 100644
index 0000000000..e99fbfeb28
--- /dev/null
+++ b/k6-tests/ncmp/8-id-search-module.js
@@ -0,0 +1,42 @@
+/*
+ * ============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=========================================================
+ */
+
+import { searchRequest } from './search-base.js';
+
+export const options = {
+ vus: 5,
+ duration: '30s',
+ thresholds: {
+ http_req_failed: ['rate == 0'],
+ http_req_duration: ['avg <= 200'],
+ },
+};
+
+export default function() {
+ const search_filter = {
+ "cmHandleQueryParameters": [
+ {
+ "conditionName": "hasAllModules",
+ "conditionParameters": [ {"moduleName": "ietf-yang-types-1"} ]
+ }
+ ]
+ };
+ searchRequest('id-searches', JSON.stringify(search_filter));
+}
diff --git a/k6-tests/ncmp/9-search-module.js b/k6-tests/ncmp/9-search-module.js
new file mode 100644
index 0000000000..03eb9a67ab
--- /dev/null
+++ b/k6-tests/ncmp/9-search-module.js
@@ -0,0 +1,42 @@
+/*
+ * ============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=========================================================
+ */
+
+import { searchRequest } from './search-base.js';
+
+export const options = {
+ vus: 5,
+ duration: '60s',
+ thresholds: {
+ http_req_failed: ['rate == 0'],
+ http_req_duration: ['avg <= 35_000'],
+ },
+};
+
+export default function() {
+ const search_filter = {
+ "cmHandleQueryParameters": [
+ {
+ "conditionName": "hasAllModules",
+ "conditionParameters": [ {"moduleName": "ietf-yang-types-1"} ]
+ }
+ ]
+ };
+ searchRequest('searches', JSON.stringify(search_filter));
+}
diff --git a/k6-tests/ncmp/run-all-tests.sh b/k6-tests/ncmp/run-all-tests.sh
new file mode 100755
index 0000000000..f75cf2f701
--- /dev/null
+++ b/k6-tests/ncmp/run-all-tests.sh
@@ -0,0 +1,44 @@
+#!/bin/bash
+#
+# Copyright 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.
+#
+
+ALL_TEST_SCRIPTS=( \
+1-create-cmhandles.js \
+2-wait-for-cmhandles-to-be-ready.js \
+3-passthrough-read.js \
+4-id-search-no-filter.js \
+5-search-no-filter.js \
+6-id-search-public-property.js \
+7-search-public-property.js \
+8-id-search-module.js \
+9-search-module.js \
+10-mixed-load-test.js \
+11-delete-cmhandles.js \
+)
+
+pushd "$(dirname "$0")" || exit 1
+
+number_of_failures=0
+for test_script in "${ALL_TEST_SCRIPTS[@]}"; do
+ echo "k6 run $test_script"
+ k6 --quiet run "$test_script" || ((number_of_failures++))
+ echo
+done
+
+popd || exit 1
+
+echo "NCMP TEST FAILURES: $number_of_failures"
+exit $number_of_failures
diff --git a/k6-tests/ncmp/search-base.js b/k6-tests/ncmp/search-base.js
new file mode 100644
index 0000000000..9c1b247a9d
--- /dev/null
+++ b/k6-tests/ncmp/search-base.js
@@ -0,0 +1,35 @@
+/*
+ * ============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=========================================================
+ */
+
+import http from 'k6/http';
+import { check } from 'k6';
+import { NCMP_BASE_URL, TOTAL_CM_HANDLES } from './utils.js';
+
+export function searchRequest(searchType, searchFilter) {
+ const response = http.post(NCMP_BASE_URL + '/ncmp/v1/ch/' + searchType, searchFilter, {
+ headers: { 'Content-Type': 'application/json' },
+ });
+ check(response, {
+ 'status equals 200': (r) => r.status === 200,
+ });
+ check(JSON.parse(response.body), {
+ 'returned list has expected CM-handles': (arr) => arr.length === TOTAL_CM_HANDLES,
+ });
+}
diff --git a/k6-tests/ncmp/utils.js b/k6-tests/ncmp/utils.js
new file mode 100644
index 0000000000..3b61b217e6
--- /dev/null
+++ b/k6-tests/ncmp/utils.js
@@ -0,0 +1,41 @@
+/*
+ * ============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=========================================================
+ */
+
+export const NCMP_BASE_URL = 'http://localhost:8883';
+export const DMI_PLUGIN_URL = 'http://ncmp-dmi-plugin-demo-and-csit-stub:8092';
+export const TOTAL_CM_HANDLES = 20000
+
+/*
+ * Makes a batch of CM-handle IDs.
+ * Given a batchSize=100 and batchNumber=2, it will generate ['ch-201', 'ch-202' ... 'ch-300']
+ */
+export function makeBatchOfCmHandleIds(batchSize, batchNumber) {
+ const batchOfIds = [];
+ const startIndex = 1 + batchNumber * batchSize;
+ for (let i = 0; i < batchSize; i++) {
+ let cmHandleId = 'ch-' + (startIndex + i);
+ batchOfIds.push(cmHandleId);
+ }
+ return batchOfIds;
+}
+
+export function getRandomCmHandleId() {
+ return 'ch-' + (Math.floor(Math.random() * TOTAL_CM_HANDLES) + 1);
+}
diff --git a/k6-tests/run-k6-tests.sh b/k6-tests/run-k6-tests.sh
new file mode 100755
index 0000000000..9b8747b1ff
--- /dev/null
+++ b/k6-tests/run-k6-tests.sh
@@ -0,0 +1,39 @@
+#!/bin/bash
+#
+# Copyright 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.
+#
+
+set -o errexit # Exit on most errors
+set -o nounset # Disallow expansion of unset variables
+set -o pipefail # Use last non-zero exit code in a pipeline
+#set -o xtrace # Uncomment for debugging
+
+on_exit() {
+ rc=$?
+ ./teardown.sh
+ popd
+ echo "TEST FAILURES: $rc"
+ exit $rc
+}
+trap on_exit EXIT
+
+pushd "$(dirname "$0")" || exit 1
+
+./setup.sh
+./ncmp/run-all-tests.sh
+NCMP_RESULT=$?
+
+# Note that the final steps are done in on_exit function after this exit!
+exit $NCMP_RESULT
diff --git a/k6-tests/setup.sh b/k6-tests/setup.sh
new file mode 100755
index 0000000000..4805e2ea43
--- /dev/null
+++ b/k6-tests/setup.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+#
+# Copyright 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.
+#
+
+docker-compose -f ../docker-compose/docker-compose.yml --profile dmi-stub up -d
+
+echo "Waiting for CPS to start..."
+READY_MESSAGE='Processing module sync fetched 0 advised cm handles from DB'
+docker logs cps-and-ncmp -f | grep -m 1 "$READY_MESSAGE" >/dev/null || true
diff --git a/k6-tests/teardown.sh b/k6-tests/teardown.sh
new file mode 100755
index 0000000000..87cca72529
--- /dev/null
+++ b/k6-tests/teardown.sh
@@ -0,0 +1,31 @@
+#!/bin/bash
+#
+# Copyright 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.
+#
+
+echo '================================== docker info =========================='
+docker ps -a
+
+echo '================================== CPS-NCMP Logs ========================'
+docker logs cps-and-ncmp
+
+echo 'Stopping, Removing all running containers...'
+docker stop $(docker ps -aq) && docker rm $(docker ps -aq)
+
+echo 'Removing Volumes...'
+docker volume prune -f
+
+echo 'Removing Networks...'
+docker network prune -f