diff options
author | Lee Anjella Macabuhay <lee.anjella.macabuhay@est.tech> | 2024-12-11 12:38:53 +0000 |
---|---|---|
committer | Gerrit Code Review <gerrit@onap.org> | 2024-12-11 12:38:53 +0000 |
commit | 29061930922eeb686da7f91ec7852b7ee875c739 (patch) | |
tree | 13be256aa3aefa8fdb36ead38c4e87d394008a4f | |
parent | b3af94c350bbba26463435671f8b6774e37b0ba4 (diff) | |
parent | 9c4745535aeb1e68e1a3c8fdda358dbcbb673362 (diff) |
Merge "Policy Executor API Review Board Comments"
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) } } |