aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSourabh Sourabh <sourabh.sourabh@est.tech>2024-08-29 09:48:19 +0000
committerGerrit Code Review <gerrit@onap.org>2024-08-29 09:48:19 +0000
commit5514e69a41af3b53997906fe77de9fd662ace568 (patch)
treee6528604680a702430388fb2da6e4b0265a5c640
parent607a8563a6e9b935c6feb2604ae0752a07a91895 (diff)
parenta05ca3d3152de44ac3078e2455834dedb5789b1b (diff)
Merge "Invoke Policy Executor and handle not-allowed response"
-rw-r--r--cps-application/src/main/resources/application.yml6
-rw-r--r--cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandler.java7
-rw-r--r--cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandlerSpec.groovy29
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/exceptions/PolicyExecutorException.java42
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/DmiDataOperations.java3
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutor.java122
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/http/RestServiceUrlTemplateBuilder.java6
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/DmiHttpClientConfigSpec.groovy2
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/PolicyExecutorHttpClientConfigSpec.groovy2
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/PolicyExecutorConfigurationSpec.groovy45
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/PolicyExecutorSpec.groovy124
-rw-r--r--cps-ncmp-service/src/test/java/org/onap/cps/ncmp/utils/WebClientBuilderTestConfig.java40
-rw-r--r--cps-ncmp-service/src/test/resources/application.yml4
-rw-r--r--docs/schemas/policy-executor/ncmp-create-schema-1.0.0.json2
-rw-r--r--docs/schemas/policy-executor/ncmp-delete-schema-1.0.0.json2
-rw-r--r--docs/schemas/policy-executor/ncmp-patch-schema-1.0.0.json2
-rw-r--r--docs/schemas/policy-executor/ncmp-update-schema-1.0.0.json2
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy13
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/base/DmiDispatcher.groovy18
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/base/PolicyDispatcher.groovy74
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/PolicyExecutorIntegrationSpec.groovy63
-rw-r--r--integration-test/src/test/resources/application.yml14
-rw-r--r--spotbugs/src/main/resources/spotbugs-exclude.xml1
23 files changed, 542 insertions, 81 deletions
diff --git a/cps-application/src/main/resources/application.yml b/cps-application/src/main/resources/application.yml
index dd4576ec02..8ca6b0fe02 100644
--- a/cps-application/src/main/resources/application.yml
+++ b/cps-application/src/main/resources/application.yml
@@ -189,10 +189,10 @@ logging:
cps: INFO
ncmp:
policy-executor:
- enabled: true
+ enabled: false
server:
- address: "http://localhost"
- port: "8785"
+ address: http://localhost
+ port: 8785
httpclient:
all-services:
maximumInMemorySizeInMegabytes: 16
diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandler.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandler.java
index d61d30dc2b..6910003c54 100644
--- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandler.java
+++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandler.java
@@ -31,6 +31,7 @@ import org.onap.cps.ncmp.api.exceptions.DmiRequestException;
import org.onap.cps.ncmp.api.exceptions.InvalidTopicException;
import org.onap.cps.ncmp.api.exceptions.NcmpException;
import org.onap.cps.ncmp.api.exceptions.PayloadTooLargeException;
+import org.onap.cps.ncmp.api.exceptions.PolicyExecutorException;
import org.onap.cps.ncmp.api.exceptions.ServerNcmpException;
import org.onap.cps.ncmp.rest.model.DmiErrorMessage;
import org.onap.cps.ncmp.rest.model.DmiErrorMessageDmiResponse;
@@ -84,8 +85,8 @@ public class NetworkCmProxyRestExceptionHandler {
return buildErrorResponse(HttpStatus.BAD_REQUEST, exception);
}
- @ExceptionHandler({AlreadyDefinedException.class})
- public static ResponseEntity<Object> handleAlreadyDefinedExceptions(final Exception exception) {
+ @ExceptionHandler({AlreadyDefinedException.class, PolicyExecutorException.class})
+ public static ResponseEntity<Object> handleConflictExceptions(final Exception exception) {
return buildErrorResponse(HttpStatus.CONFLICT, exception);
}
@@ -113,8 +114,6 @@ public class NetworkCmProxyRestExceptionHandler {
} else {
errorMessage.setDetails(CHECK_LOGS_FOR_DETAILS);
}
- errorMessage.setDetails(
- exception instanceof CpsException ? ((CpsException) exception).getDetails() : CHECK_LOGS_FOR_DETAILS);
return new ResponseEntity<>(errorMessage, status);
}
diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandlerSpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandlerSpec.groovy
index e6288ffbec..9d36d106c7 100644
--- a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandlerSpec.groovy
+++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandlerSpec.groovy
@@ -29,11 +29,13 @@ import org.onap.cps.ncmp.api.data.exceptions.OperationNotSupportedException
import org.onap.cps.ncmp.api.exceptions.DmiClientRequestException
import org.onap.cps.ncmp.api.exceptions.DmiRequestException
import org.onap.cps.ncmp.api.exceptions.PayloadTooLargeException
+import org.onap.cps.ncmp.api.exceptions.PolicyExecutorException
import org.onap.cps.ncmp.api.exceptions.ServerNcmpException
import org.onap.cps.ncmp.api.inventory.NetworkCmProxyInventoryFacade
import org.onap.cps.ncmp.impl.data.NcmpCachedResourceRequestHandler
import org.onap.cps.ncmp.impl.data.NcmpPassthroughResourceRequestHandler
import org.onap.cps.ncmp.impl.data.NetworkCmProxyFacade
+import org.onap.cps.ncmp.impl.data.policyexecutor.PolicyExecutor
import org.onap.cps.ncmp.impl.inventory.InventoryPersistence
import org.onap.cps.ncmp.rest.util.CmHandleStateMapper
import org.onap.cps.ncmp.rest.util.DataOperationRequestMapper
@@ -120,25 +122,26 @@ class NetworkCmProxyRestExceptionHandlerSpec extends Specification {
dataNodeBaseEndpointNcmpInventory = "$basePathNcmpInventory/v1"
}
- def 'Get request with #scenario exception returns correct HTTP Status with #scenario'() {
+ def 'Get request with #scenario exception returns correct HTTP Status with #scenario exception'() {
when: 'an exception is thrown by the service'
setupTestException(exception, NCMP)
def response = performTestRequest(NCMP)
then: 'an HTTP response is returned with correct message and details'
assertTestResponse(response, expectedErrorCode, expectedErrorMessage, expectedErrorDetails)
where:
- scenario | exception || expectedErrorCode | expectedErrorMessage | expectedErrorDetails
- 'CPS' | new CpsException(sampleErrorMessage, sampleErrorDetails) || INTERNAL_SERVER_ERROR | sampleErrorMessage | sampleErrorDetails
- 'NCMP-server' | new ServerNcmpException(sampleErrorMessage, sampleErrorDetails) || INTERNAL_SERVER_ERROR | sampleErrorMessage | null
- 'DMI Request' | new DmiRequestException(sampleErrorMessage, sampleErrorDetails) || BAD_REQUEST | sampleErrorMessage | null
- 'Invalid Operation' | new InvalidOperationException('some reason') || BAD_REQUEST | 'some reason' | null
- 'Unsupported Operation' | new OperationNotSupportedException('not yet') || BAD_REQUEST | 'not yet' | null
- 'DataNode Validation' | new DataNodeNotFoundException('myDataspaceName', 'myAnchorName') || NOT_FOUND | 'DataNode not found' | null
- 'other' | new IllegalStateException(sampleErrorMessage) || INTERNAL_SERVER_ERROR | sampleErrorMessage | null
- 'Data Node Not Found' | new DataNodeNotFoundException('myDataspaceName', 'myAnchorName') || NOT_FOUND | 'DataNode not found' | 'DataNode not found'
- 'Existing entry' | new AlreadyDefinedException('name',null) || CONFLICT | 'Already defined exception' | 'name already exists'
- 'Existing entries' | AlreadyDefinedException.forDataNodes(['A', 'B'], 'myAnchorName') || CONFLICT | 'Already defined exception' | '2 data node(s) already exist'
- 'Operation too large' | new PayloadTooLargeException(sampleErrorMessage) || PAYLOAD_TOO_LARGE | sampleErrorMessage | 'Check logs'
+ scenario | exception || expectedErrorCode | expectedErrorMessage | expectedErrorDetails
+ 'CPS' | new CpsException(sampleErrorMessage, sampleErrorDetails) || INTERNAL_SERVER_ERROR | sampleErrorMessage | sampleErrorDetails
+ 'NCMP-server' | new ServerNcmpException(sampleErrorMessage, sampleErrorDetails) || INTERNAL_SERVER_ERROR | sampleErrorMessage | null
+ 'DMI Request' | new DmiRequestException(sampleErrorMessage, sampleErrorDetails) || BAD_REQUEST | sampleErrorMessage | null
+ 'Invalid Operation' | new InvalidOperationException('some reason') || BAD_REQUEST | 'some reason' | null
+ 'Unsupported Operation' | new OperationNotSupportedException('not yet') || BAD_REQUEST | 'not yet' | null
+ 'DataNode Validation' | new DataNodeNotFoundException('myDataspaceName', 'myAnchorName') || NOT_FOUND | 'DataNode not found' | null
+ 'other' | new IllegalStateException(sampleErrorMessage) || INTERNAL_SERVER_ERROR | sampleErrorMessage | null
+ 'Data Node Not Found' | new DataNodeNotFoundException('myDataspaceName', 'myAnchorName') || NOT_FOUND | 'DataNode not found' | 'DataNode not found'
+ 'Existing entry' | new AlreadyDefinedException('name',null) || CONFLICT | 'Already defined exception' | 'name already exists'
+ 'Existing entries' | AlreadyDefinedException.forDataNodes(['A', 'B'], 'myAnchorName') || CONFLICT | 'Already defined exception' | '2 data node(s) already exist'
+ 'Operation too large' | new PayloadTooLargeException(sampleErrorMessage) || PAYLOAD_TOO_LARGE | sampleErrorMessage | 'Check logs'
+ 'Policy Executor' | new PolicyExecutorException(sampleErrorMessage, sampleErrorDetails) || CONFLICT | sampleErrorMessage | sampleErrorDetails
}
def 'Post request with exception returns correct HTTP Status.'() {
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/exceptions/PolicyExecutorException.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/exceptions/PolicyExecutorException.java
new file mode 100644
index 0000000000..333c12271b
--- /dev/null
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/exceptions/PolicyExecutorException.java
@@ -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=========================================================
+ */
+
+package org.onap.cps.ncmp.api.exceptions;
+
+import lombok.Getter;
+
+/**
+ * Exception to be used when policy execution fails or does not allow to proceed.
+ */
+@Getter
+public class PolicyExecutorException extends NcmpException {
+
+ private static final long serialVersionUID = 6659897770659834798L;
+
+ /**
+ * Constructor to form exception for policy executor responses.
+ *
+ * @param message response message
+ * @param details response details
+ */
+ public PolicyExecutorException(final String message, final String details) {
+ super(message, details);
+ }
+}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/DmiDataOperations.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/DmiDataOperations.java
index 6b813a6f69..301b8195e4 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/DmiDataOperations.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/DmiDataOperations.java
@@ -139,8 +139,7 @@ public class DmiDataOperations {
final String requestId,
final String authorization) {
- final Set<String> cmHandlesIds
- = getDistinctCmHandleIds(dataOperationRequest);
+ final Set<String> cmHandlesIds = getDistinctCmHandleIds(dataOperationRequest);
final Collection<YangModelCmHandle> yangModelCmHandles
= inventoryPersistence.getYangModelCmHandles(cmHandlesIds);
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutor.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutor.java
index 8e7620ccea..89b48f3755 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutor.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutor.java
@@ -20,12 +20,26 @@
package org.onap.cps.ncmp.impl.data.policyexecutor;
+import com.fasterxml.jackson.databind.JsonNode;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.onap.cps.ncmp.api.data.models.OperationType;
+import org.onap.cps.ncmp.api.exceptions.PolicyExecutorException;
+import org.onap.cps.ncmp.api.exceptions.ServerNcmpException;
import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle;
+import org.onap.cps.ncmp.impl.utils.http.RestServiceUrlTemplateBuilder;
+import org.onap.cps.ncmp.impl.utils.http.UrlTemplateParameters;
+import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
+import org.springframework.web.reactive.function.BodyInserters;
+import org.springframework.web.reactive.function.client.WebClient;
@Slf4j
@Service
@@ -41,7 +55,8 @@ public class PolicyExecutor {
@Value("${ncmp.policy-executor.server.port:8080}")
private String serverPort;
- private static final String PAYLOAD_TYPE_PREFIX = "cm_";
+ @Qualifier("policyExecutorWebClient")
+ private final WebClient policyExecutorWebClient;
/**
* Use the Policy Executor to check permission for a cm write operation.
@@ -58,17 +73,102 @@ public class PolicyExecutor {
final String authorization,
final String resourceIdentifier,
final String changeRequestAsJson) {
+ log.trace("Policy Executor Enabled: {}", enabled);
if (enabled) {
- final String payloadType = PAYLOAD_TYPE_PREFIX + operationType.getOperationName();
- log.info("Policy Executor Enabled");
- log.info("Address : {}", serverAddress);
- log.info("Port : {}", serverPort);
- log.info("Authorization : {}", authorization);
- log.info("Payload Type : {}", payloadType);
- log.info("Target FDN : {}", yangModelCmHandle.getAlternateId());
- log.info("CM Handle Id : {}", yangModelCmHandle.getId());
- log.info("Resource Identifier : {}", resourceIdentifier);
- log.info("Change Request (json) : {}", changeRequestAsJson);
+ final ResponseEntity<JsonNode> responseEntity =
+ getPolicyExecutorResponse(yangModelCmHandle, operationType, authorization, resourceIdentifier,
+ changeRequestAsJson);
+
+ if (responseEntity == null) {
+ log.warn("No valid response from policy, ignored");
+ return;
+ }
+
+ if (responseEntity.getStatusCode().is2xxSuccessful()) {
+ if (responseEntity.getBody() == null) {
+ log.warn("No valid response body from policy, ignored");
+ return;
+ }
+ processResponse(responseEntity.getBody());
+ } else {
+ log.warn("Policy Executor invocation failed with status {}",
+ responseEntity.getStatusCode().value());
+ throw new ServerNcmpException("Policy Executor invocation failed", "HTTP status code: "
+ + responseEntity.getStatusCode().value());
+ }
+ }
+ }
+
+ private Map<String, Object> getSingleRequestAsMap(final YangModelCmHandle yangModelCmHandle,
+ final OperationType operationType,
+ final String resourceIdentifier,
+ final String changeRequestAsJson) {
+ final Map<String, Object> data = new HashMap<>(4);
+ data.put("cmHandleId", yangModelCmHandle.getId());
+ data.put("resourceIdentifier", resourceIdentifier);
+ data.put("targetIdentifier", yangModelCmHandle.getAlternateId());
+ if (!OperationType.DELETE.equals(operationType)) {
+ data.put("cmChangeRequest", changeRequestAsJson);
+ }
+
+ final Map<String, Object> request = new HashMap<>(2);
+ request.put("schema", getAssociatedPolicyDataSchemaName(operationType));
+ request.put("data", data);
+ return request;
+ }
+
+ private static String getAssociatedPolicyDataSchemaName(final OperationType operationType) {
+ return "urn:cps:org.onap.cps.ncmp.policy-executor.ncmp-" + operationType.getOperationName() + "-schema:1.0.0";
+ }
+
+ private Object createBodyAsObject(final List<Object> requests) {
+ final Map<String, Object> bodyAsMap = new HashMap<>(2);
+ bodyAsMap.put("decisionType", "allow");
+ bodyAsMap.put("requests", requests);
+ return bodyAsMap;
+ }
+
+ private ResponseEntity<JsonNode> getPolicyExecutorResponse(final YangModelCmHandle yangModelCmHandle,
+ final OperationType operationType,
+ final String authorization,
+ final String resourceIdentifier,
+ final String changeRequestAsJson) {
+ final String serviceBaseUrl = serverAddress + ":" + serverPort;
+
+ final Map<String, Object> requestAsMap = getSingleRequestAsMap(yangModelCmHandle,
+ operationType,
+ resourceIdentifier,
+ changeRequestAsJson);
+
+ final Object bodyAsObject = createBodyAsObject(Collections.singletonList(requestAsMap));
+
+ final UrlTemplateParameters urlTemplateParameters = RestServiceUrlTemplateBuilder.newInstance()
+ .fixedPathSegment("execute")
+ .createUrlTemplateParameters(serviceBaseUrl, "");
+
+ return policyExecutorWebClient.post()
+ .uri(urlTemplateParameters.urlTemplate(), urlTemplateParameters.urlVariables())
+ .header(HttpHeaders.AUTHORIZATION, authorization)
+ .body(BodyInserters.fromValue(bodyAsObject))
+ .retrieve()
+ .toEntity(JsonNode.class)
+ .block();
+ }
+
+ private static void processResponse(final JsonNode responseBody) {
+ final String decisionId = responseBody.path("decisionId").asText("unknown id");
+ log.trace("Policy Executor Decision ID: {} ", decisionId);
+ final String decision = responseBody.path("decision").asText("unknown");
+ if ("allow".equals(decision)) {
+ log.trace("Policy Executor allows the operation");
+ } else {
+ log.warn("Policy Executor decision: {}", decision);
+ final String details = responseBody.path("message").asText();
+ log.warn("Policy Executor message: {}", details);
+ final String message = "Policy Executor did not allow request. Decision #"
+ + decisionId + " : " + decision;
+ throw new PolicyExecutorException(message, details);
}
}
+
}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/http/RestServiceUrlTemplateBuilder.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/http/RestServiceUrlTemplateBuilder.java
index fafb09007d..c850ca94a0 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/http/RestServiceUrlTemplateBuilder.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/http/RestServiceUrlTemplateBuilder.java
@@ -96,9 +96,7 @@ public class RestServiceUrlTemplateBuilder {
* @return a UrlTemplateParameters instance containing the complete URL template and URL variables
*/
public UrlTemplateParameters createUrlTemplateParameters(final String serviceBaseUrl, final String basePath) {
- this.uriComponentsBuilder.pathSegment(basePath)
- .pathSegment(VERSION_SEGMENT);
-
+ this.uriComponentsBuilder.pathSegment(basePath).pathSegment(VERSION_SEGMENT);
final Map<String, String> urlTemplateVariables = new HashMap<>();
pathSegments.forEach((pathSegmentName, variablePathValue) -> {
@@ -120,7 +118,7 @@ public class RestServiceUrlTemplateBuilder {
}
/**
- * Constructs a URL for DMI health check based on the given base URL.
+ * Constructs a URL for a spring actuator health check based on the given base URL.
*
* @param serviceBaseUrl the base URL of the service, e.g., "http://dmi-service.com".
* @return a {@link UrlTemplateParameters} instance containing the complete URL template and empty URL variables,
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/DmiHttpClientConfigSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/DmiHttpClientConfigSpec.groovy
index e0ae204c8d..23f5edd890 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/DmiHttpClientConfigSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/DmiHttpClientConfigSpec.groovy
@@ -29,7 +29,7 @@ import spock.lang.Specification
@SpringBootTest
@ContextConfiguration(classes = [DmiHttpClientConfig])
-@EnableConfigurationProperties(DmiHttpClientConfig)
+@EnableConfigurationProperties
class DmiHttpClientConfigSpec extends Specification {
@Autowired
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/PolicyExecutorHttpClientConfigSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/PolicyExecutorHttpClientConfigSpec.groovy
index 1946a452b1..ca71c345c1 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/PolicyExecutorHttpClientConfigSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/PolicyExecutorHttpClientConfigSpec.groovy
@@ -34,7 +34,7 @@ class PolicyExecutorHttpClientConfigSpec extends Specification {
@Autowired
PolicyExecutorHttpClientConfig policyExecutorHttpClientConfig
- def 'Test http client configuration properties of data with custom and default values'() {
+ def 'Http client configuration properties for policy executor http client.'() {
expect: 'properties are populated correctly for all services'
with(policyExecutorHttpClientConfig.allServices) {
assert maximumInMemorySizeInMegabytes == 31
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/PolicyExecutorConfigurationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/PolicyExecutorConfigurationSpec.groovy
new file mode 100644
index 0000000000..c086eab810
--- /dev/null
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/PolicyExecutorConfigurationSpec.groovy
@@ -0,0 +1,45 @@
+/*
+ * ============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.impl.data
+
+import org.onap.cps.ncmp.config.PolicyExecutorHttpClientConfig
+import org.onap.cps.ncmp.impl.data.policyexecutor.PolicyExecutor
+import org.onap.cps.ncmp.impl.policyexecutor.PolicyExecutorWebClientConfiguration
+import org.onap.cps.ncmp.utils.WebClientBuilderTestConfig
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.context.SpringBootTest
+import org.springframework.test.context.ContextConfiguration
+import spock.lang.Specification
+
+@SpringBootTest
+@ContextConfiguration(classes = [PolicyExecutor, PolicyExecutorWebClientConfiguration, PolicyExecutorHttpClientConfig, WebClientBuilderTestConfig ])
+class PolicyExecutorConfigurationSpec extends Specification {
+
+ @Autowired
+ PolicyExecutor objectUnderTest
+
+ def 'Policy executor configuration properties.'() {
+ expect: 'properties used from application.yml'
+ assert objectUnderTest.enabled
+ assert objectUnderTest.serverAddress == 'http://localhost'
+ assert objectUnderTest.serverPort == '8785'
+ }
+}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/PolicyExecutorSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/PolicyExecutorSpec.groovy
index 4b09afa2ae..a5776676dc 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/PolicyExecutorSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/PolicyExecutorSpec.groovy
@@ -1,69 +1,139 @@
+/*
+ * ============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.impl.data
import ch.qos.logback.classic.Level
import ch.qos.logback.classic.Logger
import ch.qos.logback.classic.spi.ILoggingEvent
import ch.qos.logback.core.read.ListAppender
+import com.fasterxml.jackson.databind.JsonNode
+import com.fasterxml.jackson.databind.ObjectMapper
+import org.onap.cps.ncmp.api.exceptions.PolicyExecutorException
+import org.onap.cps.ncmp.api.exceptions.ServerNcmpException
import org.onap.cps.ncmp.impl.data.policyexecutor.PolicyExecutor
import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle
import org.slf4j.LoggerFactory
-import org.springframework.beans.factory.annotation.Autowired
-import org.springframework.boot.test.context.SpringBootTest
-import org.springframework.test.context.ContextConfiguration
+import org.springframework.http.HttpStatus
+import org.springframework.http.ResponseEntity
+import org.springframework.web.reactive.function.client.WebClient
+import reactor.core.publisher.Mono
import spock.lang.Specification
+import static org.onap.cps.ncmp.api.data.models.OperationType.CREATE
+import static org.onap.cps.ncmp.api.data.models.OperationType.DELETE
import static org.onap.cps.ncmp.api.data.models.OperationType.PATCH
+import static org.onap.cps.ncmp.api.data.models.OperationType.UPDATE
-@SpringBootTest
-@ContextConfiguration(classes = [PolicyExecutor])
class PolicyExecutorSpec extends Specification {
- @Autowired
- PolicyExecutor objectUnderTest
+ def mockWebClient = Mock(WebClient)
+ def mockRequestBodyUriSpec = Mock(WebClient.RequestBodyUriSpec)
+ def mockResponseSpec = Mock(WebClient.ResponseSpec)
+
+ PolicyExecutor objectUnderTest = new PolicyExecutor(mockWebClient)
def logAppender = Spy(ListAppender<ILoggingEvent>)
+ ObjectMapper objectMapper = new ObjectMapper()
+
def setup() {
setupLogger()
+ objectUnderTest.enabled = true
+ mockWebClient.post() >> mockRequestBodyUriSpec
+ mockRequestBodyUriSpec.uri(*_) >> mockRequestBodyUriSpec
+ mockRequestBodyUriSpec.header(*_) >> mockRequestBodyUriSpec
+ mockRequestBodyUriSpec.body(*_) >> mockRequestBodyUriSpec
+ mockRequestBodyUriSpec.retrieve() >> mockResponseSpec
}
def cleanup() {
((Logger) LoggerFactory.getLogger(PolicyExecutor)).detachAndStopAllAppenders()
}
- def 'Configuration properties.'() {
- expect: 'properties used from application.yml'
- assert objectUnderTest.enabled
- assert objectUnderTest.serverAddress == 'http://localhost'
- assert objectUnderTest.serverPort == '8785'
+ def 'Permission check with allow response.'() {
+ given: 'allow response'
+ mockResponse([decision:'allow'], HttpStatus.OK)
+ when: 'permission is checked for an operation'
+ objectUnderTest.checkPermission(new YangModelCmHandle(), operationType, 'my credentials','my resource','my change')
+ then: 'system logs the operation is allowed'
+ assert getLogEntry(2) == 'Policy Executor allows the operation'
+ and: 'no exception occurs'
+ noExceptionThrown()
+ where: 'all write operations are tested'
+ operationType << [ CREATE, DELETE, PATCH, UPDATE ]
+ }
+
+ def 'Permission check with other response (not allowed).'() {
+ given: 'other response'
+ mockResponse([decision:'other', decisionId:123, message:'I dont like Mondays' ], HttpStatus.OK)
+ when: 'permission is checked for an operation'
+ objectUnderTest.checkPermission(new YangModelCmHandle(), PATCH, 'my credentials','my resource','my change')
+ then: 'Policy Executor exception is thrown'
+ def thrownException = thrown(PolicyExecutorException)
+ assert thrownException.message == 'Policy Executor did not allow request. Decision #123 : other'
+ assert thrownException.details == 'I dont like Mondays'
+ }
+
+ def 'Permission check with non 2xx response.'() {
+ given: 'other response'
+ mockResponse([], HttpStatus.I_AM_A_TEAPOT)
+ when: 'permission is checked for an operation'
+ objectUnderTest.checkPermission(new YangModelCmHandle(), PATCH, 'my credentials','my resource','my change')
+ then: 'Server Ncmp exception is thrown'
+ def thrownException = thrown(ServerNcmpException)
+ assert thrownException.message == 'Policy Executor invocation failed'
+ assert thrownException.details == 'HTTP status code: 418'
}
- def 'Permission check logging.'() {
+ def 'Permission check with invalid response from Policy Executor.'() {
+ given: 'invalid response from Policy executor'
+ mockResponseSpec.toEntity(*_) >> invalidResponse
when: 'permission is checked for an operation'
- def yangModelCmHandle = new YangModelCmHandle(id:'ch-1', alternateId:'fdn1')
- objectUnderTest.checkPermission(yangModelCmHandle, PATCH, 'my credentials','my resource','my change')
- then: 'correct details are logged '
- assert getLogEntry(0) == 'Policy Executor Enabled'
- assert getLogEntry(3).contains('my credentials')
- assert getLogEntry(4).contains('cm_patch')
- assert getLogEntry(5).contains('fdn1')
- assert getLogEntry(6).contains('ch-1')
- assert getLogEntry(7).contains('my resource')
- assert getLogEntry(8).contains('my change')
+ objectUnderTest.checkPermission(new YangModelCmHandle(), CREATE, 'my credentials','my resource','my change')
+ then: 'system logs the expected message'
+ assert getLogEntry(1) == expectedMessage
+ where: 'following invalid responses are received'
+ invalidResponse || expectedMessage
+ Mono.empty() || 'No valid response from policy, ignored'
+ Mono.just(new ResponseEntity<>(null, HttpStatus.OK)) || 'No valid response body from policy, ignored'
}
- def 'Permission check with feature disabled.'() {
+ def 'Permission check feature disabled.'() {
given: 'feature is disabled'
objectUnderTest.enabled = false
when: 'permission is checked for an operation'
objectUnderTest.checkPermission(new YangModelCmHandle(), PATCH, 'my credentials','my resource','my change')
- then: 'nothing is logged'
- assert logAppender.list.isEmpty()
+ then: 'system logs that the feature not enabled'
+ assert getLogEntry(0) == 'Policy Executor Enabled: false'
+ }
+
+ def mockResponse(mockResponseAsMap, httpStatus) {
+ JsonNode jsonNode = objectMapper.readTree(objectMapper.writeValueAsString(mockResponseAsMap))
+ def mono = Mono.just(new ResponseEntity<>(jsonNode, httpStatus))
+ mockResponseSpec.toEntity(*_) >> mono
}
def setupLogger() {
def logger = LoggerFactory.getLogger(PolicyExecutor)
- logger.setLevel(Level.DEBUG)
+ logger.setLevel(Level.TRACE)
logger.addAppender(logAppender)
logAppender.start()
}
diff --git a/cps-ncmp-service/src/test/java/org/onap/cps/ncmp/utils/WebClientBuilderTestConfig.java b/cps-ncmp-service/src/test/java/org/onap/cps/ncmp/utils/WebClientBuilderTestConfig.java
new file mode 100644
index 0000000000..2f6b270076
--- /dev/null
+++ b/cps-ncmp-service/src/test/java/org/onap/cps/ncmp/utils/WebClientBuilderTestConfig.java
@@ -0,0 +1,40 @@
+/*
+ * ============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.utils;
+
+import org.springframework.boot.test.context.TestConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.web.reactive.function.client.WebClient;
+
+@TestConfiguration
+public class WebClientBuilderTestConfig {
+
+ /**
+ * Configures and creates a web client builder bean to make it accessible for the Spring Boot Test Context.
+ *
+ * @return a WebClient Builder instance.
+ */
+ @Bean
+ public WebClient.Builder webClientBuilder() {
+ return WebClient.builder();
+ }
+
+}
diff --git a/cps-ncmp-service/src/test/resources/application.yml b/cps-ncmp-service/src/test/resources/application.yml
index 72d074ab5f..c76831da74 100644
--- a/cps-ncmp-service/src/test/resources/application.yml
+++ b/cps-ncmp-service/src/test/resources/application.yml
@@ -84,8 +84,8 @@ ncmp:
policy-executor:
enabled: true
server:
- address: "http://localhost"
- port: "8785"
+ address: http://localhost
+ port: 8785
httpclient:
all-services:
maximumInMemorySizeInMegabytes: 31
diff --git a/docs/schemas/policy-executor/ncmp-create-schema-1.0.0.json b/docs/schemas/policy-executor/ncmp-create-schema-1.0.0.json
index 2ec9daf949..4d98bc8632 100644
--- a/docs/schemas/policy-executor/ncmp-create-schema-1.0.0.json
+++ b/docs/schemas/policy-executor/ncmp-create-schema-1.0.0.json
@@ -1,6 +1,6 @@
{
"$schema": "https://json-schema.org/draft/2019-09/schema",
- "$id": "urn:cps:org.onap.cps.ncmp.policy-executor:ncmp-create-schema:1.0.0",
+ "$id": "urn:cps:org.onap.cps.ncmp.policy-executor.ncmp-create-schema:1.0.0",
"$ref": "#/definitions/NcmpCreate",
"definitions": {
"NcmpCreate": {
diff --git a/docs/schemas/policy-executor/ncmp-delete-schema-1.0.0.json b/docs/schemas/policy-executor/ncmp-delete-schema-1.0.0.json
index 5df0325e39..1246d9d815 100644
--- a/docs/schemas/policy-executor/ncmp-delete-schema-1.0.0.json
+++ b/docs/schemas/policy-executor/ncmp-delete-schema-1.0.0.json
@@ -1,6 +1,6 @@
{
"$schema": "https://json-schema.org/draft/2019-09/schema",
- "$id": "urn:cps:org.onap.cps.ncmp.policy-executor:ncmp-delete-schema:1.0.0",
+ "$id": "urn:cps:org.onap.cps.ncmp.policy-executor.ncmp-delete-schema:1.0.0",
"$ref": "#/definitions/NcmpDelete",
"definitions": {
"NcmpDelete": {
diff --git a/docs/schemas/policy-executor/ncmp-patch-schema-1.0.0.json b/docs/schemas/policy-executor/ncmp-patch-schema-1.0.0.json
index e26c244c94..4917aea146 100644
--- a/docs/schemas/policy-executor/ncmp-patch-schema-1.0.0.json
+++ b/docs/schemas/policy-executor/ncmp-patch-schema-1.0.0.json
@@ -1,6 +1,6 @@
{
"$schema": "https://json-schema.org/draft/2019-09/schema",
- "$id": "urn:cps:org.onap.cps.ncmp.policy-executor:ncmp-patch-schema:1.0.0",
+ "$id": "urn:cps:org.onap.cps.ncmp.policy-executor.ncmp-patch-schema:1.0.0",
"$ref": "#/definitions/NcmpPatch",
"definitions": {
"NcmpPatch": {
diff --git a/docs/schemas/policy-executor/ncmp-update-schema-1.0.0.json b/docs/schemas/policy-executor/ncmp-update-schema-1.0.0.json
index 0a497e38c5..831526cd52 100644
--- a/docs/schemas/policy-executor/ncmp-update-schema-1.0.0.json
+++ b/docs/schemas/policy-executor/ncmp-update-schema-1.0.0.json
@@ -1,6 +1,6 @@
{
"$schema": "https://json-schema.org/draft/2019-09/schema",
- "$id": "urn:cps:org.onap.cps.ncmp.policy-executor:ncmp-update-schema:1.0.0",
+ "$id": "urn:cps:org.onap.cps.ncmp.policy-executor.ncmp-update-schema:1.0.0",
"$ref": "#/definitions/NcmpUpdate",
"definitions": {
"NcmpUpdate": {
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy
index 5e46e95a0c..bd53c4ea13 100644
--- a/integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy
@@ -46,6 +46,7 @@ import org.onap.cps.spi.utils.SessionManager
import org.onap.cps.utils.ContentType
import org.onap.cps.utils.JsonObjectMapper
import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.autoconfigure.EnableAutoConfiguration
import org.springframework.boot.autoconfigure.domain.EntityScan
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
@@ -125,12 +126,19 @@ abstract class CpsIntegrationSpecBase extends Specification {
@Autowired
AlternateIdMatcher alternateIdMatcher
+
+ @Value('${ncmp.policy-executor.server.port:8080}')
+ private String policyServerPort;
+
MockWebServer mockDmiServer1 = new MockWebServer()
MockWebServer mockDmiServer2 = new MockWebServer()
+ MockWebServer mockPolicyServer = new MockWebServer()
DmiDispatcher dmiDispatcher1 = new DmiDispatcher()
DmiDispatcher dmiDispatcher2 = new DmiDispatcher()
+ PolicyDispatcher policyDispatcher = new PolicyDispatcher();
+
def DMI1_URL = null
def DMI2_URL = null
@@ -155,13 +163,18 @@ abstract class CpsIntegrationSpecBase extends Specification {
mockDmiServer2.setDispatcher(dmiDispatcher2)
mockDmiServer2.start()
+ mockPolicyServer.setDispatcher(policyDispatcher)
+ mockPolicyServer.start(Integer.valueOf(policyServerPort))
+
DMI1_URL = String.format("http://%s:%s", mockDmiServer1.getHostName(), mockDmiServer1.getPort())
DMI2_URL = String.format("http://%s:%s", mockDmiServer2.getHostName(), mockDmiServer2.getPort())
+
}
def cleanup() {
mockDmiServer1.shutdown()
mockDmiServer2.shutdown()
+ mockPolicyServer.shutdown()
}
def static readResourceDataFile(filename) {
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/base/DmiDispatcher.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/base/DmiDispatcher.groovy
index fcc23db782..35a7b6a7c2 100644
--- a/integration-test/src/test/groovy/org/onap/cps/integration/base/DmiDispatcher.groovy
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/base/DmiDispatcher.groovy
@@ -20,18 +20,18 @@
package org.onap.cps.integration.base
-import static org.onap.cps.integration.base.CpsIntegrationSpecBase.readResourceDataFile
-
import groovy.json.JsonSlurper
-import java.util.regex.Matcher
import okhttp3.mockwebserver.Dispatcher
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.RecordedRequest
-import org.onap.cps.ncmp.api.datajobs.models.SubJobWriteRequest
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
+import java.util.regex.Matcher
+
+import static org.onap.cps.integration.base.CpsIntegrationSpecBase.readResourceDataFile
+
/**
* This class simulates responses from the DMI server in NCMP integration tests.
*
@@ -117,32 +117,32 @@ class DmiDispatcher extends Dispatcher {
return mockResponseWithBody(HttpStatus.OK, response)
}
- private getModuleReferencesResponse(cmHandleId) {
+ def getModuleReferencesResponse(cmHandleId) {
def moduleReferences = '{"schemas":[' + getModuleNamesForCmHandle(cmHandleId).collect {
MODULE_REFERENCES_RESPONSE_TEMPLATE.replaceAll("<MODULE_NAME>", it)
}.join(',') + ']}'
return mockResponseWithBody(HttpStatus.OK, moduleReferences)
}
- private getModuleResourcesResponse(cmHandleId) {
+ def getModuleResourcesResponse(cmHandleId) {
def moduleResources = '[' + getModuleNamesForCmHandle(cmHandleId).collect {
MODULE_RESOURCES_RESPONSE_TEMPLATE.replaceAll("<MODULE_NAME>", it)
}.join(',') + ']'
return mockResponseWithBody(HttpStatus.OK, moduleResources)
}
- private getModuleNamesForCmHandle(cmHandleId) {
+ def getModuleNamesForCmHandle(cmHandleId) {
if (!moduleNamesPerCmHandleId.containsKey(cmHandleId)) {
throw new IllegalArgumentException('Mock DMI has no modules configured for ' + cmHandleId)
}
return moduleNamesPerCmHandleId.get(cmHandleId)
}
- private static mockResponse(status) {
+ def static mockResponse(status) {
return new MockResponse().setResponseCode(status.value())
}
- private static mockResponseWithBody(status, responseBody) {
+ def static mockResponseWithBody(status, responseBody) {
return new MockResponse()
.setResponseCode(status.value())
.addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/base/PolicyDispatcher.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/base/PolicyDispatcher.groovy
new file mode 100644
index 0000000000..27e7563e61
--- /dev/null
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/base/PolicyDispatcher.groovy
@@ -0,0 +1,74 @@
+/*
+ * ============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.integration.base
+
+
+import okhttp3.mockwebserver.Dispatcher
+import okhttp3.mockwebserver.MockResponse
+import okhttp3.mockwebserver.RecordedRequest
+import org.springframework.http.HttpHeaders
+import org.springframework.http.HttpStatus
+import org.springframework.http.MediaType
+import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper
+
+/**
+ * This class simulates responses from the Policy Execution server in NCMP integration tests.
+ */
+class PolicyDispatcher extends Dispatcher {
+
+ def objectMapper = new ObjectMapper()
+ def expectedAuthorizationToken = 'ABC'
+ def allowAll = true; // Prevents legacy test being affected
+
+ @Override
+ MockResponse dispatch(RecordedRequest recordedRequest) {
+
+ if (!allowAll && !recordedRequest.getHeader('Authorization').contains(expectedAuthorizationToken)) {
+ return new MockResponse().setResponseCode(401)
+ }
+
+ if (recordedRequest.path != '/v1/execute') {
+ return new MockResponse().setResponseCode(400)
+ }
+
+ def body = objectMapper.readValue(recordedRequest.getBody().readUtf8(), Map.class)
+ def targetIdentifier = body.get('requests').get(0).get('data').get('targetIdentifier')
+ def responseAsMap = [:]
+ responseAsMap.put('decisionId',1)
+ if (allowAll || targetIdentifier == 'fdn1') {
+ responseAsMap.put('decision','allow')
+ responseAsMap.put('message','')
+ } else {
+ responseAsMap.put('decision','deny')
+ responseAsMap.put('message','I only like fdn1')
+ }
+ def responseAsString = objectMapper.writeValueAsString(responseAsMap)
+
+ return mockResponseWithBody(HttpStatus.OK, responseAsString)
+ }
+
+ static mockResponseWithBody(status, responseBody) {
+ return new MockResponse()
+ .setResponseCode(status.value())
+ .addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
+ .setBody(responseBody)
+ }
+}
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/PolicyExecutorIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/PolicyExecutorIntegrationSpec.groovy
new file mode 100644
index 0000000000..99f245ae8c
--- /dev/null
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/PolicyExecutorIntegrationSpec.groovy
@@ -0,0 +1,63 @@
+/*
+ * ============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.integration.functional.ncmp
+
+import org.onap.cps.integration.base.CpsIntegrationSpecBase
+import org.springframework.http.HttpHeaders
+import org.springframework.http.MediaType
+
+import static org.springframework.http.HttpMethod.POST
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.request
+
+class PolicyExecutorIntegrationSpec extends CpsIntegrationSpecBase {
+
+ def setup() {
+ // Enable mocked policy executor logic
+ policyDispatcher.allowAll = false;
+ //minimum setup for 2 cm handles with alternate ids
+ dmiDispatcher1.moduleNamesPerCmHandleId = ['ch-1': [], 'ch-2': []]
+ registerCmHandle(DMI1_URL, 'ch-1', NO_MODULE_SET_TAG, 'fdn1')
+ registerCmHandle(DMI1_URL, 'ch-2', NO_MODULE_SET_TAG, 'fdn2')
+ }
+
+ def cleanup() {
+ deregisterCmHandle(DMI1_URL, 'ch-1')
+ deregisterCmHandle(DMI1_URL, 'ch-2')
+ }
+
+ def 'Policy Executor create request with #scenario.'() {
+ when: 'a pass-through write request is sent to NCMP'
+ def response = mvc.perform(request(POST, "/ncmp/v1/ch/$cmHandle/data/ds/ncmp-datastore:passthrough-running")
+ .queryParam('resourceIdentifier', 'my-resource-id')
+ .contentType(MediaType.APPLICATION_JSON)
+ .content('{ "some-json": "data" }')
+ .header(HttpHeaders.AUTHORIZATION, authorization))
+ .andReturn().response
+ then: 'the expected status code is returned'
+ response.getStatus() == execpectedStatusCode
+ where: 'following parameters are used'
+ scenario | cmHandle | authorization || execpectedStatusCode
+ 'accepted cm handle' | 'ch-1' | 'mock expects "ABC"' || 201
+ 'un-accepted cm handle' | 'ch-2' | 'mock expects "ABC"' || 409
+ 'invalid authorization' | 'ch-1' | 'something else' || 500
+ }
+
+}
diff --git a/integration-test/src/test/resources/application.yml b/integration-test/src/test/resources/application.yml
index fefae345e6..760dad01af 100644
--- a/integration-test/src/test/resources/application.yml
+++ b/integration-test/src/test/resources/application.yml
@@ -213,6 +213,20 @@ ncmp:
init:
mode: ALWAYS
+ policy-executor:
+ enabled: true
+ server:
+ address: http://localhost
+ port: 8790
+ httpclient:
+ all-services:
+ maximumInMemorySizeInMegabytes: 1
+ maximumConnectionsTotal: 10
+ pendingAcquireMaxCount: 10
+ connectionTimeoutInSeconds: 30
+ readTimeoutInSeconds: 30
+ writeTimeoutInSeconds: 30
+
hazelcast:
cluster-name: cps-and-ncmp-test-caches
mode:
diff --git a/spotbugs/src/main/resources/spotbugs-exclude.xml b/spotbugs/src/main/resources/spotbugs-exclude.xml
index 78f61d290e..23e62cdbc7 100644
--- a/spotbugs/src/main/resources/spotbugs-exclude.xml
+++ b/spotbugs/src/main/resources/spotbugs-exclude.xml
@@ -36,6 +36,7 @@
<Bug pattern="NP_NULL_PARAM_DEREF" />
<Bug pattern="NP_PARAMETER_MUST_BE_NONNULL_BUT_MARKED_AS_NULLABLE" />
<Bug pattern="RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE" />
+ <Bug pattern="NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE" />
<!-- https://stackoverflow.com/a/34674776. Doesn't detect Lombok All Args Constructor variables being used with map get key and value, which can lead to spotbugs being detected
on used fields -->