aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLee Anjella Macabuhay <lee.anjella.macabuhay@est.tech>2024-12-11 12:38:53 +0000
committerGerrit Code Review <gerrit@onap.org>2024-12-11 12:38:53 +0000
commit29061930922eeb686da7f91ec7852b7ee875c739 (patch)
tree13be256aa3aefa8fdb36ead38c4e87d394008a4f
parentb3af94c350bbba26463435671f8b6774e37b0ba4 (diff)
parent9c4745535aeb1e68e1a3c8fdda358dbcbb673362 (diff)
Merge "Policy Executor API Review Board Comments"
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutor.java73
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutorSpec.groovy4
-rw-r--r--docs/api/swagger/policy-executor/openapi.yaml369
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/base/PolicyDispatcher.groovy10
-rw-r--r--policy-executor-stub/src/main/java/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubController.java67
-rw-r--r--policy-executor-stub/src/test/groovy/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubControllerSpec.groovy83
6 files changed, 291 insertions, 315 deletions
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 af4331893d..38105329d1 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
@@ -26,9 +26,9 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.UnknownHostException;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
-import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeoutException;
import lombok.RequiredArgsConstructor;
@@ -68,6 +68,10 @@ public class PolicyExecutor {
@Value("${ncmp.policy-executor.httpclient.all-services.readTimeoutInSeconds:30}")
private long readTimeoutInSeconds;
+ private static final String CHANGE_REQUEST_FORMAT = "cm-legacy";
+ private static final String PERMISSION_BASE_PATH = "operation-permission";
+ private static final String REQUEST_PATH = "permissions";
+
@Qualifier("policyExecutorWebClient")
private final WebClient policyExecutorWebClient;
@@ -110,38 +114,33 @@ public class PolicyExecutor {
}
}
- 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());
+ private Map<String, Object> getSingleOperationAsMap(final YangModelCmHandle yangModelCmHandle,
+ final OperationType operationType,
+ final String resourceIdentifier,
+ final String changeRequestAsJson) {
+ final Map<String, Object> operationAsMap = new HashMap<>(5);
+ operationAsMap.put("operation", operationType.getOperationName());
+ operationAsMap.put("entityHandleId", yangModelCmHandle.getId());
+ operationAsMap.put("resourceIdentifier", resourceIdentifier);
+ operationAsMap.put("targetIdentifier", yangModelCmHandle.getAlternateId());
if (!OperationType.DELETE.equals(operationType)) {
try {
final Object changeRequestAsObject = objectMapper.readValue(changeRequestAsJson, Object.class);
- data.put("cmChangeRequest", changeRequestAsObject);
+ operationAsMap.put("changeRequest", changeRequestAsObject);
} catch (final JsonProcessingException e) {
throw new NcmpException("Cannot convert Change Request data to Object",
"Invalid Json: " + 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";
+ return operationAsMap;
}
- 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 Object createBodyAsObject(final Map<String, Object> operationAsMap) {
+ final Collection<Map<String, Object>> operations = Collections.singletonList(operationAsMap);
+ final Map<String, Object> permissionRequestAsMap = new HashMap<>(2);
+ permissionRequestAsMap.put("changeRequestFormat", CHANGE_REQUEST_FORMAT);
+ permissionRequestAsMap.put("operations", operations);
+ return permissionRequestAsMap;
}
private ResponseEntity<JsonNode> getPolicyExecutorResponse(final YangModelCmHandle yangModelCmHandle,
@@ -149,17 +148,16 @@ public class PolicyExecutor {
final String authorization,
final String resourceIdentifier,
final String changeRequestAsJson) {
- final Map<String, Object> requestAsMap = getSingleRequestAsMap(yangModelCmHandle,
+ final Map<String, Object> operationAsMap = getSingleOperationAsMap(yangModelCmHandle,
operationType,
resourceIdentifier,
changeRequestAsJson);
- final Object bodyAsObject = createBodyAsObject(Collections.singletonList(requestAsMap));
+ final Object bodyAsObject = createBodyAsObject(operationAsMap);
final UrlTemplateParameters urlTemplateParameters = RestServiceUrlTemplateBuilder.newInstance()
- .fixedPathSegment("execute")
- .createUrlTemplateParameters(String.format("%s:%s", serverAddress, serverPort),
- "policy-executor/api");
+ .fixedPathSegment(REQUEST_PATH)
+ .createUrlTemplateParameters(String.format("%s:%s", serverAddress, serverPort), PERMISSION_BASE_PATH);
return policyExecutorWebClient.post()
.uri(urlTemplateParameters.urlTemplate(), urlTemplateParameters.urlVariables())
@@ -172,23 +170,23 @@ public class PolicyExecutor {
}
private static void processSuccessResponse(final JsonNode responseBody) {
- final String decisionId = responseBody.path("decisionId").asText("unknown id");
- final String decision = responseBody.path("decision").asText("unknown");
+ final String id = responseBody.path("id").asText("unknown id");
+ final String permissionResult = responseBody.path("permissionResult").asText("unknown");
final String messageFromPolicyExecutor = responseBody.path("message").asText();
- processDecision(decisionId, decision, messageFromPolicyExecutor, NO_ERROR);
+ processDecision(id, permissionResult, messageFromPolicyExecutor, NO_ERROR);
}
- private static void processDecision(final String decisionId,
- final String decision,
+ private static void processDecision(final String id,
+ final String permissionResult,
final String details,
final Throwable optionalCauseOfError) {
- log.trace("Policy Executor decision id: {} ", decisionId);
- if ("allow".equals(decision)) {
+ log.trace("Policy Executor Decision id: {} ", id);
+ if ("allow".equals(permissionResult)) {
log.trace("Operation allowed.");
} else {
- log.warn("Policy Executor decision: {}", decision);
+ log.warn("Policy Executor permission result: {}", permissionResult);
log.warn("Policy Executor message: {}", details);
- final String message = "Operation not allowed. Decision id " + decisionId + " : " + decision;
+ final String message = "Operation not allowed. Decision id " + id + " : " + permissionResult;
throw new PolicyExecutorException(message, details, optionalCauseOfError);
}
}
@@ -196,6 +194,7 @@ public class PolicyExecutor {
private void processException(final RuntimeException runtimeException) {
if (runtimeException instanceof WebClientResponseException) {
final WebClientResponseException webClientResponseException = (WebClientResponseException) runtimeException;
+ log.warn("HTTP Error Message: {}", webClientResponseException.getMessage());
final int httpStatusCode = webClientResponseException.getStatusCode().value();
processFallbackResponse("Policy Executor returned HTTP Status code " + httpStatusCode + ".",
webClientResponseException);
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutorSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutorSpec.groovy
index 33dcf5d623..9423246134 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutorSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutorSpec.groovy
@@ -75,7 +75,7 @@ class PolicyExecutorSpec extends Specification {
def 'Permission check with "allow" decision.'() {
given: 'allow response'
- mockResponse([decision:'allow'], HttpStatus.OK)
+ mockResponse([permissionResult:'allow'], HttpStatus.OK)
when: 'permission is checked for an operation'
objectUnderTest.checkPermission(new YangModelCmHandle(), operationType, 'my credentials','my resource',someValidJson)
then: 'system logs the operation is allowed'
@@ -88,7 +88,7 @@ class PolicyExecutorSpec extends Specification {
def 'Permission check with "other" decision (not allowed).'() {
given: 'other response'
- mockResponse([decision:'other', decisionId:123, message:'I dont like Mondays' ], HttpStatus.OK)
+ mockResponse([permissionResult:'other', id:123, message:'I dont like Mondays' ], HttpStatus.OK)
when: 'permission is checked for an operation'
objectUnderTest.checkPermission(new YangModelCmHandle(), PATCH, 'my credentials','my resource',someValidJson)
then: 'Policy Executor exception is thrown'
diff --git a/docs/api/swagger/policy-executor/openapi.yaml b/docs/api/swagger/policy-executor/openapi.yaml
index 1248c0d08b..ba341b2d57 100644
--- a/docs/api/swagger/policy-executor/openapi.yaml
+++ b/docs/api/swagger/policy-executor/openapi.yaml
@@ -18,229 +18,179 @@
openapi: 3.0.3
info:
- description: Allows NCMP to execute a policy defined by a third party implementation
- before proceeding with a CM operation
- title: Policy Executor
- version: 1.0.0
+ title: Operation permission API
+ description: "Allows a client application to execute a permission request defined by a third party implementation before proceeding with an operation. As an example, a permission can be requested before performing any configuration management operation."
+ version: 1.0.0-alpha.1+1
+ contact:
+ name: CPS team
+ url: https://lf-onap.atlassian.net/wiki/spaces/DW/pages/16442177/Configuration+Persistence+Service+Developer+s+Landing+Page
+ email: cpsteam@est.tech
+ license:
+ name: Copyright (C) 2024 Nordix Foundation
+ x-audience: external-partner
+ x-api-id: c7fc2f5b-16bd-4bcb-8ac8-ea8d543fcc15
+tags:
+ - name: Operation permission
+ description: "Initiate a permission request on an operation."
servers:
-- url: /
+ - url: http://{hostname}/operation-permission/v1
security:
-- bearerAuth: []
-tags:
-- description: Execute all your policies
- name: policy-executor
+ - bearerAuth: []
paths:
- /policy-executor/api/v1/{action}:
+ /permissions:
post:
- description: Fire a Policy action
- operationId: executePolicyAction
+ description: "Initiate permission request"
+ operationId: initiatePermissionRequest
parameters:
- - description: Bearer token may be used to identify client as part of a policy
- explode: false
- in: header
- name: Authorization
- required: false
- schema:
- type: string
- style: simple
- - description: "The policy action. Currently supported options: 'execute'"
- explode: false
- in: path
- name: action
- required: true
- schema:
- example: execute
- type: string
- style: simple
+ - name: Content-Type
+ description: This specifies the media type of the request sent by the client to the server
+ in: header
+ required: true
+ schema:
+ type: string
+ default: application/json
+ - name: Accept
+ description: Indicates the response media type accepted by the client.
+ in: header
+ required: false
+ schema:
+ type: string
+ default: application/json
+ - description: Bearer token may be used to identify client as part of a policy
+ explode: false
+ in: header
+ name: Authorization
+ required: false
+ schema:
+ type: string
+ style: simple
requestBody:
content:
application/json:
schema:
- $ref: '#/components/schemas/PolicyExecutionRequest'
- description: The action request body
+ $ref: '#/components/schemas/PermissionRequest'
+ description: "The permission request body"
required: true
responses:
- "200":
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/PolicyExecutionResponse'
- description: Successful policy execution
- "400":
+ '200':
+ description: "OK"
content:
application/json:
- example:
- status: 400
- message: Bad Request
- details: The provided request is not valid
schema:
- $ref: '#/components/schemas/ErrorMessage'
- description: Bad request
- "401":
- content:
- application/json:
- example:
- status: 401
- message: Unauthorized request
- details: This request is unauthorized
- schema:
- $ref: '#/components/schemas/ErrorMessage'
- description: Unauthorized request
- "403":
- content:
- application/json:
- example:
- status: 403
- message: Request Forbidden
- details: This request is forbidden
- schema:
- $ref: '#/components/schemas/ErrorMessage'
- description: Request forbidden
- "500":
- content:
- application/json:
- example:
- status: 500
- message: Internal Server Error
- details: Internal server error occurred
- schema:
- $ref: '#/components/schemas/ErrorMessage'
- description: Internal server error
+ $ref: '#/components/schemas/PermissionResponse'
+ '400':
+ $ref: '#/components/responses/BadRequest'
+ '401':
+ $ref: '#/components/responses/Unauthorized'
+ '403':
+ $ref: '#/components/responses/Forbidden'
+ '500':
+ $ref: '#/components/responses/InternalServerError'
tags:
- - policy-executor
+ - Operation permission
components:
- parameters:
- actionInPath:
- description: "The policy action. Currently supported options: 'execute'"
- explode: false
- in: path
- name: action
- required: true
- schema:
- example: execute
- type: string
- style: simple
- authorizationInHeader:
- description: Bearer token may be used to identify client as part of a policy
- explode: false
- in: header
- name: Authorization
- required: false
- schema:
- type: string
- style: simple
- responses:
- BadRequest:
- content:
- application/json:
- example:
- status: 400
- message: Bad Request
- details: The provided request is not valid
- schema:
- $ref: '#/components/schemas/ErrorMessage'
- description: Bad request
- Unauthorized:
- content:
- application/json:
- example:
- status: 401
- message: Unauthorized request
- details: This request is unauthorized
- schema:
- $ref: '#/components/schemas/ErrorMessage'
- description: Unauthorized request
- Forbidden:
- content:
- application/json:
- example:
- status: 403
- message: Request Forbidden
- details: This request is forbidden
- schema:
- $ref: '#/components/schemas/ErrorMessage'
- description: Request forbidden
- InternalServerError:
- content:
- application/json:
- example:
- status: 500
- message: Internal Server Error
- details: Internal server error occurred
- schema:
- $ref: '#/components/schemas/ErrorMessage'
- description: Internal server error
- NotImplemented:
- content:
- application/json:
- example:
- status: 501
- message: Not Implemented
- details: Method not implemented
- schema:
- $ref: '#/components/schemas/ErrorMessage'
- description: Method not (yet) implemented
+ securitySchemes:
+ bearerAuth:
+ type: http
+ description: "Bearer token (from a client),used by policies to identify the client"
+ scheme: bearer
schemas:
ErrorMessage:
+ type: object
+ title: Error
properties:
status:
type: string
- message:
+ title:
type: string
details:
type: string
- title: Error
- type: object
- Request:
+ Operation:
example:
- schema: org.onap.cps.ncmp.policy-executor:ncmp-create-schema:1.0.0
- data: "{}"
+ operation: update
+ entityHandleId: ABCD123450d7A822AB27B386829FD9E12
+ resourceIdentifier: ManagedElement=Kista/GNBDUFunction=1/UECC=1
+ targetIdentifier: MEContext=RadioNode-K6_0001,ManagedElement=RadioNode-K6_0001
+ changeRequest:
+ Cell:
+ - id: Cell-id
+ attributes:
+ administrativeState: UNLOCKED
properties:
- schema:
- description: The schema for the data in this request. The schema name should
- include the type of operation
- example: org.onap.cps.ncmp.policy-executor:ncmp-create-schema:1.0.0
+ operation:
+ description: Currently supported operations are 'create', 'update', 'patch', 'delete'. For other possible operation types see the client documentation.
+ example: update
+ type: string
+ entityHandleId:
+ description: A unique identifier for the network element.
+ example: ABCD123450d7A822AB27B386829FD9E12
type: string
- data:
- description: The data related to the request. The format of the object is
- determined by the schema
+ resourceIdentifier:
+ description: Identifies the object in the node model. Currently supported separators are '/' and ','. For other possible format see the client documentation.
+ example: ManagedElement=Kista/GNBDUFunction=1/UECC=1
+ type: string
+ targetIdentifier:
+ description: FDN of the target node. Currently supported separators are '/' and ','. For other possible format see the client documentation.
+ example: MEContext=RadioNode-K6_0001/ManagedElement=RadioNode-K6_0001
+ type: string
+ changeRequest:
+ description: All the information that is required to identify which parameters and attributes of the network is changing.
+ example:
+ Cell:
+ - id: Cell-id
+ attributes:
+ administrativeState: UNLOCKED
type: object
required:
- - data
- - schema
+ - operation
+ - targetIdentifier
type: object
- PolicyExecutionRequest:
+ PermissionRequest:
example:
- decisionType: allow
- requests:
- - schema: org.onap.cps.ncmp.policy-executor:ncmp-create-schema:1.0.0
- data: "{}"
- - schema: org.onap.cps.ncmp.policy-executor:ncmp-create-schema:1.0.0
- data: "{}"
+ permissionId: 550e8400-e29b-41d4-a716-446655440000
+ changeRequestFormat: cm-legacy
+ operations:
+ - operation: update
+ entityHandleId: ABCD123450d7A822AB27B386829FD9E12
+ resourceIdentifier: ManagedElement=Kista/GNBDUFunction=1/UECC=1
+ targetIdentifier: MEContext=RadioNode-K6_0001/ManagedElement=RadioNode-K6_0001
+ changeRequest:
+ Cell:
+ - id: Cell-id
+ attributes:
+ administrativeState: UNLOCKED
+ - operation: delete
+ entityHandleId: DCBA123450d7A822AB27B386829FD9E12
+ resourceIdentifier: ManagedElement=Kista/GNBDUFunction=1/UECC=1
+ targetIdentifier: MEContext=RadioNode-K6_0002/ManagedElement=RadioNode-K6_0002
properties:
- decisionType:
- description: "The type of decision. Currently supported options: 'allow'"
- example: allow
+ permissionId:
+ description: Unique ID for the permission request (for auditing purposes)
+ example: 550e8400-e29b-41d4-a716-446655440000
+ type: string
+ changeRequestFormat:
+ description: Format of the change request. Currently supported 'cm-legacy'. For other possible formats see the client documentation.
+ example: cm-legacy
type: string
- requests:
+ operations:
items:
- $ref: '#/components/schemas/Request'
+ $ref: '#/components/schemas/Operation'
type: array
required:
- - decisionType
- - requests
+ - operations
+ - changeRequestFormat
type: object
- PolicyExecutionResponse:
+ PermissionResponse:
example:
- decision: deny
- decisionId: 550e8400-e29b-41d4-a716-446655440000
- message: Object locked due to recent change
+ id: 550e8400-e29b-41d4-a716-446655440000
+ permissionResult: deny
+ message: Object locked due to recent changes
properties:
- decisionId:
- description: Unique ID for the decision (for auditing purposes)
+ id:
+ description: Unique ID for the permission request (for auditing purposes)
example: 550e8400-e29b-41d4-a716-446655440000
type: string
- decision:
+ permissionResult:
description: "The decision outcome. Currently supported values: 'allow','deny'"
example: deny
type: string
@@ -249,13 +199,50 @@ components:
example: Object locked due to recent change
type: string
required:
- - decision
- - decisionId
- - message
+ - id
+ - permissionResult
+ - message
type: object
- securitySchemes:
- bearerAuth:
- description: "Bearer token (from client that called CPS-NCMP),used by policies\
- \ to identify the client"
- scheme: bearer
- type: http
+
+ responses:
+ BadRequest:
+ description: "Bad Request"
+ content:
+ application/problem+json:
+ schema:
+ $ref: '#/components/schemas/ErrorMessage'
+ example:
+ status: '400'
+ title: "Bad Request"
+ details: "The provided request is not valid"
+ Unauthorized:
+ description: "Unauthorized request"
+ content:
+ application/problem+json:
+ schema:
+ $ref: '#/components/schemas/ErrorMessage'
+ example:
+ status: '401'
+ title: "Unauthorized request"
+ details: "This request is unauthorized"
+ Forbidden:
+ description: "Forbidden"
+ content:
+ application/problem+json:
+ schema:
+ $ref: '#/components/schemas/ErrorMessage'
+ example:
+ status: '403'
+ title: "Request Forbidden"
+ details: "This request is forbidden"
+
+ InternalServerError:
+ description: "Internal Server Error"
+ content:
+ application/problem+json:
+ schema:
+ $ref: '#/components/schemas/ErrorMessage'
+ example:
+ status: '500'
+ title: "Internal Server Error"
+ details: "Internal server error occurred"
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
index b08d1c1548..f1e5449476 100644
--- 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
@@ -46,22 +46,22 @@ class PolicyDispatcher extends Dispatcher {
return new MockResponse().setResponseCode(401)
}
- if (recordedRequest.path != '/policy-executor/api/v1/execute') {
+ if (recordedRequest.path != '/operation-permission/v1/permissions') {
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 targetIdentifier = body.get('operations').get(0).get('targetIdentifier')
def responseAsMap = [:]
- responseAsMap.put('decisionId',1)
+ responseAsMap.put('id',1)
if (targetIdentifier == "mock slow response") {
TimeUnit.SECONDS.sleep(2) // One second more then configured readTimeoutInSeconds
}
if (allowAll || targetIdentifier == 'fdn1') {
- responseAsMap.put('decision','allow')
+ responseAsMap.put('permissionResult','allow')
responseAsMap.put('message','')
} else {
- responseAsMap.put('decision','deny from mock server (dispatcher)')
+ responseAsMap.put('permissionResult','deny from mock server (dispatcher)')
responseAsMap.put('message','I only like fdn1')
}
def responseAsString = objectMapper.writeValueAsString(responseAsMap)
diff --git a/policy-executor-stub/src/main/java/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubController.java b/policy-executor-stub/src/main/java/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubController.java
index 88073c0a0f..aef27a6389 100644
--- a/policy-executor-stub/src/main/java/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubController.java
+++ b/policy-executor-stub/src/main/java/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubController.java
@@ -20,72 +20,66 @@
package org.onap.cps.policyexecutor.stub.controller;
-import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
-import org.onap.cps.policyexecutor.stub.api.PolicyExecutorApi;
-import org.onap.cps.policyexecutor.stub.model.NcmpDelete;
-import org.onap.cps.policyexecutor.stub.model.PolicyExecutionRequest;
-import org.onap.cps.policyexecutor.stub.model.PolicyExecutionResponse;
-import org.onap.cps.policyexecutor.stub.model.Request;
+import org.onap.cps.policyexecutor.stub.api.OperationPermissionApi;
+import org.onap.cps.policyexecutor.stub.model.Operation;
+import org.onap.cps.policyexecutor.stub.model.PermissionRequest;
+import org.onap.cps.policyexecutor.stub.model.PermissionResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
+@RequestMapping("/operation-permission/v1")
@RequiredArgsConstructor
@Slf4j
-public class PolicyExecutorStubController implements PolicyExecutorApi {
+public class PolicyExecutorStubController implements OperationPermissionApi {
private final Sleeper sleeper;
- private final ObjectMapper objectMapper;
private static final Pattern ERROR_CODE_PATTERN = Pattern.compile("(\\d{3})");
private int decisionCounter = 0;
private static int slowResponseTimeInSeconds = 40;
@Override
- public ResponseEntity<PolicyExecutionResponse> executePolicyAction(
- final String action,
- final PolicyExecutionRequest policyExecutionRequest,
- final String authorization) {
- log.info("Stub Policy Executor Invoked (only supports 'delete' operations)");
- if (policyExecutionRequest.getRequests().isEmpty()) {
+ public ResponseEntity<PermissionResponse> initiatePermissionRequest(final String contentType,
+ final PermissionRequest permissionRequest,
+ final String accept,
+ final String authorization) {
+ log.info("Stub Policy Executor Invoked");
+ if (permissionRequest.getOperations().isEmpty()) {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
- final Request firstRequest = policyExecutionRequest.getRequests().iterator().next();
- log.info("1st Request Schema:{}", firstRequest.getSchema());
- if (firstRequest.getSchema().contains("ncmp-delete-schema:1.0.0")) {
- return handleNcmpDeleteSchema(firstRequest);
+ final Operation firstOperation = permissionRequest.getOperations().iterator().next();
+ log.info("1st Operation: {}", firstOperation.getOperation());
+ if (!"delete".equals(firstOperation.getOperation()) && firstOperation.getChangeRequest() == null) {
+ log.warn("Change Request is required for " + firstOperation.getOperation() + " operations");
+ return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
- log.warn("This stub only supports 'delete' operations");
- return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
+ return handleOperation(firstOperation);
}
- private ResponseEntity<PolicyExecutionResponse> handleNcmpDeleteSchema(final Request request) {
- final NcmpDelete ncmpDelete = objectMapper.convertValue(request.getData(), NcmpDelete.class);
-
- final String targetIdentifier = ncmpDelete.getTargetIdentifier();
-
- if (targetIdentifier == null) {
- return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
- }
+ private ResponseEntity<PermissionResponse> handleOperation(final Operation operation) {
+ final String targetIdentifier = operation.getTargetIdentifier();
final Matcher matcher = ERROR_CODE_PATTERN.matcher(targetIdentifier);
if (matcher.find()) {
final int errorCode = Integer.parseInt(matcher.group(1));
+ log.warn("Stub is mocking an error response, code: " + errorCode);
return new ResponseEntity<>(HttpStatusCode.valueOf(errorCode));
}
return createPolicyExecutionResponse(targetIdentifier);
}
- private ResponseEntity<PolicyExecutionResponse> createPolicyExecutionResponse(final String targetIdentifier) {
- final String decisionId = String.valueOf(++decisionCounter);
- final String decision;
+ private ResponseEntity<PermissionResponse> createPolicyExecutionResponse(final String targetIdentifier) {
+ final String id = String.valueOf(++decisionCounter);
+ final String permissionResult;
final String message;
if (targetIdentifier.toLowerCase(Locale.getDefault()).contains("slow")) {
try {
@@ -96,17 +90,14 @@ public class PolicyExecutorStubController implements PolicyExecutorApi {
}
}
if (targetIdentifier.toLowerCase(Locale.getDefault()).contains("cps-is-great")) {
- decision = "allow";
+ permissionResult = "allow";
message = "All good";
} else {
- decision = "deny";
+ permissionResult = "deny";
message = "Only FDNs containing 'cps-is-great' are allowed";
}
- log.info("Decision: {} ({})", decision, message);
- final PolicyExecutionResponse policyExecutionResponse =
- new PolicyExecutionResponse(decisionId, decision, message);
-
- return ResponseEntity.ok(policyExecutionResponse);
+ log.info("Decision: {} ({})", permissionResult, message);
+ return ResponseEntity.ok(new PermissionResponse(id, permissionResult, message));
}
}
diff --git a/policy-executor-stub/src/test/groovy/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubControllerSpec.groovy b/policy-executor-stub/src/test/groovy/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubControllerSpec.groovy
index 44460daa7e..75bd676b2f 100644
--- a/policy-executor-stub/src/test/groovy/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubControllerSpec.groovy
+++ b/policy-executor-stub/src/test/groovy/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubControllerSpec.groovy
@@ -21,10 +21,9 @@
package org.onap.cps.policyexecutor.stub.controller
import com.fasterxml.jackson.databind.ObjectMapper
-import org.onap.cps.policyexecutor.stub.model.NcmpDelete
-import org.onap.cps.policyexecutor.stub.model.PolicyExecutionRequest
-import org.onap.cps.policyexecutor.stub.model.PolicyExecutionResponse
-import org.onap.cps.policyexecutor.stub.model.Request
+import org.onap.cps.policyexecutor.stub.model.Operation
+import org.onap.cps.policyexecutor.stub.model.PermissionRequest
+import org.onap.cps.policyexecutor.stub.model.PermissionResponse
import org.spockframework.spring.SpringBean
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
@@ -47,14 +46,14 @@ class PolicyExecutorStubControllerSpec extends Specification {
@SpringBean
Sleeper sleeper = Spy()
- def url = '/policy-executor/api/v1/some-action'
+ def url = '/operation-permission/v1/permissions'
def setup() {
PolicyExecutorStubController.slowResponseTimeInSeconds = 1
}
- def 'Execute policy action.'() {
- given: 'a policy execution request with target: #targetIdentifier'
+ def 'Permission request with #targetIdentifier.'() {
+ given: 'a permission request with target: #targetIdentifier'
def requestBody = createRequestBody(targetIdentifier)
when: 'request is posted'
def response = mockMvc.perform(post(url)
@@ -66,19 +65,19 @@ class PolicyExecutorStubControllerSpec extends Specification {
assert response.status == HttpStatus.OK.value()
and: 'the response body has the expected decision details'
def responseBody = response.contentAsString
- def policyExecutionResponse = objectMapper.readValue(responseBody, PolicyExecutionResponse.class)
- assert policyExecutionResponse.decisionId == expectedDecsisonId
- assert policyExecutionResponse.decision == expectedDecision
- assert policyExecutionResponse.message == expectedMessage
+ def permissionResponse = objectMapper.readValue(responseBody, PermissionResponse.class)
+ assert permissionResponse.id == expectedId
+ assert permissionResponse.permissionResult == expectedResult
+ assert permissionResponse.message == expectedMessage
where: 'the following targets are used'
- targetIdentifier || expectedDecsisonId | expectedDecision | expectedMessage
- 'some fdn' || '1' | 'deny' | "Only FDNs containing 'cps-is-great' are allowed"
- 'fdn with cps-is-great' || '2' | 'allow' | 'All good'
- 'slow' || '3' | 'deny' | "Only FDNs containing 'cps-is-great' are allowed"
+ targetIdentifier || expectedId | expectedResult | expectedMessage
+ 'some fdn' || '1' | 'deny' | "Only FDNs containing 'cps-is-great' are allowed"
+ 'fdn with cps-is-great' || '2' | 'allow' | 'All good'
+ 'slow' || '3' | 'deny' | "Only FDNs containing 'cps-is-great' are allowed"
}
- def 'Execute policy action with a HTTP error code.'() {
- given: 'a policy execution request with a target fdn with a 3-digit error code'
+ def 'Permission request with a HTTP error code.'() {
+ given: 'a permission request with a target fdn with a 3-digit error code'
def requestBody = createRequestBody('target with error code 418')
when: 'request is posted'
def response = mockMvc.perform(post(url)
@@ -90,8 +89,8 @@ class PolicyExecutorStubControllerSpec extends Specification {
assert response.status == 418
}
- def 'Execute policy action without authorization header.'() {
- given: 'a valid policy execution request'
+ def 'Permission request without authorization header.'() {
+ given: 'a valid permission request'
def requestBody = createRequestBody('some target')
when: 'request is posted without authorization header'
def response = mockMvc.perform(post(url)
@@ -102,10 +101,10 @@ class PolicyExecutorStubControllerSpec extends Specification {
assert response.status == HttpStatus.OK.value()
}
- def 'Execute policy action with no requests.'() {
- given: 'a policy execution request'
- def policyExecutionRequest = new PolicyExecutionRequest('some decision type', [])
- def requestBody = objectMapper.writeValueAsString(policyExecutionRequest)
+ def 'Permission request with no operations.'() {
+ given: 'a permission request with no operations'
+ def permissionRequest = new PermissionRequest('some decision type', [])
+ def requestBody = objectMapper.writeValueAsString(permissionRequest)
when: 'request is posted'
def response = mockMvc.perform(post(url)
.header('Authorization','some string')
@@ -116,8 +115,8 @@ class PolicyExecutorStubControllerSpec extends Specification {
assert response.status == HttpStatus.BAD_REQUEST.value()
}
- def 'Execute policy action with invalid json for request data.'() {
- when: 'request is posted'
+ def 'Request with invalid json for request data.'() {
+ when: 'request with invalid json is posted'
def response = mockMvc.perform(post(url)
.header('Authorization','some string')
.contentType(MediaType.APPLICATION_JSON)
@@ -127,8 +126,8 @@ class PolicyExecutorStubControllerSpec extends Specification {
assert response.status == HttpStatus.BAD_REQUEST.value()
}
- def 'Execute policy action with interrupted exception during slow response.'() {
- given: 'a policy execution request with target: "slow"'
+ def 'Permission request with interrupted exception during slow response.'() {
+ given: 'a permission request with target: "slow" (stub will be slow)'
def requestBody = createRequestBody('slow')
sleeper.haveALittleRest(_) >> { throw new InterruptedException() }
when: 'request is posted'
@@ -140,9 +139,9 @@ class PolicyExecutorStubControllerSpec extends Specification {
noExceptionThrown()
}
- def 'Execute policy action with missing or invalid attributes.'() {
- given: 'a policy execution request with decisionType=#decisionType, schema=#schema, targetIdentifier=#targetIdentifier'
- def requestBody = createRequestBody(decisionType, schema, targetIdentifier)
+ def 'Permission request with missing or invalid attributes.'() {
+ given: 'Permission request with operation=#operation and targetIdentifier=#targetIdentifier'
+ def requestBody = createRequestBody(operation, targetIdentifier, changeRequest)
when: 'request is posted'
def response = mockMvc.perform(post(url)
.header('Authorization','something')
@@ -152,22 +151,22 @@ class PolicyExecutorStubControllerSpec extends Specification {
then: 'response status as expected'
assert response.status == expectedStatus.value()
where: 'following parameters are used'
- decisionType | schema | targetIdentifier || expectedStatus
- 'something' | 'ncmp-delete-schema:1.0.0' | 'something' || HttpStatus.OK
- null | 'ncmp-delete-schema:1.0.0' | 'something' || HttpStatus.BAD_REQUEST
- 'something' | 'other schema' | 'something' || HttpStatus.BAD_REQUEST
- 'something' | 'ncmp-delete-schema:1.0.0' | null || HttpStatus.BAD_REQUEST
+ operation | targetIdentifier | changeRequest || expectedStatus
+ 'delete' | 'something' | null || HttpStatus.OK
+ 'other' | 'something' | '{}' || HttpStatus.OK
+ 'delete' | null | null || HttpStatus.BAD_REQUEST
+ 'other' | 'something' | null || HttpStatus.BAD_REQUEST
}
- def createRequestBody(decisionType, schema, targetIdentifier) {
- def ncmpDelete = new NcmpDelete(targetIdentifier: targetIdentifier)
- def request = new Request(schema, ncmpDelete)
- def policyExecutionRequest = new PolicyExecutionRequest(decisionType, [request])
- return objectMapper.writeValueAsString(policyExecutionRequest)
+ def createRequestBody(targetIdentifier) {
+ return createRequestBody('delete', targetIdentifier, '{}')
}
- def createRequestBody(targetIdentifier) {
- return createRequestBody('some decision type', 'ncmp-delete-schema:1.0.0', targetIdentifier)
+ def createRequestBody(operationName, targetIdentifier, changeRequest) {
+ def operation = new Operation(operationName, targetIdentifier)
+ operation.setChangeRequest(changeRequest)
+ def permissionRequest = new PermissionRequest('cm-legacy', [operation])
+ return objectMapper.writeValueAsString(permissionRequest)
}
}