diff options
author | ToineSiebelink <toine.siebelink@est.tech> | 2024-07-29 17:45:52 +0100 |
---|---|---|
committer | ToineSiebelink <toine.siebelink@est.tech> | 2024-07-31 17:18:58 +0100 |
commit | 53375821fa2d156785a92ef57ef7948389260cc1 (patch) | |
tree | f7b63a0db182791f3fa2b73ea97355e692939e41 | |
parent | 9d8e6852b2ebfffca8204557a005aba27c1fc581 (diff) |
Refactor OpenAPI Policy Executor
- replace payload with request(s)
- replace payloadType with schema (one schema for each operation)
- include conflict error response in OpenAPI
- introduce 4 schemas in NCMP (doc module) for create, update, patch & delete
- udpate stub & test to follow new API and use one schema for testign purposes
Issue-ID: CPS-2335
Change-Id: Ifc40062ae83429a9ffba350ec3bcc28cb7147293
Signed-off-by: ToineSiebelink <toine.siebelink@est.tech>
8 files changed, 249 insertions, 77 deletions
diff --git a/docs/api/swagger/policy-executor/openapi.yaml b/docs/api/swagger/policy-executor/openapi.yaml index 98c5b1e79a..58ca5acfc5 100644 --- a/docs/api/swagger/policy-executor/openapi.yaml +++ b/docs/api/swagger/policy-executor/openapi.yaml @@ -52,8 +52,12 @@ paths: $ref: '#/components/schemas/PolicyExecutionResponse' '400': $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' + '409': + $ref: '#/components/responses/Conflict' '500': $ref: '#/components/responses/InternalServerError' @@ -75,48 +79,34 @@ components: details: type: string - Payload: + Request: type: object properties: - targetFdn: + schema: type: string - description: "The complete FDN (Fully Distinguished Name) for the element to be changed" - example: "/Subnetwork=Ireland/MeContext=Athlone/ManagedElement=Athlone/SomeFunction=1/Cell=12" - cmHandleId: - type: string - description: "The CM handle ID (optional)" - example: "F811AF64F5146DFC545EC60B73DE948E" - resourceIdentifier: - type: string - description: "The resource identifier (optional)" - example: "ManagedElement=Athlone/SomeFunction=1/Cell=12" - cmChangeRequest: + description: "The schema for the data in this request. The schema name should include the type of operation" + example: "org.onap.cps.ncmp.policy-executor:ncmp-create-schema:1.0.0" + data: type: object - description: "The content of the change to be made" - example: '{"Cell":[{"id":"Cell-id","attributes":{"administrativeState":"UNLOCKED"}}]}' + description: "The data related to the request. The format of the object is determined by the schema" required: - - targetFdn - - cmChangeRequest + - schema + - data PolicyExecutionRequest: type: object properties: - payloadType: - type: string - description: "The type of payload. Currently supported options: 'cm_write'" - example: "cm_write" decisionType: type: string - description: "The type of decision. Currently supported options: 'permit'" - example: "permit" - payload: + description: "The type of decision. Currently supported options: 'allow'" + example: "allow" + requests: type: array items: - $ref: '#/components/schemas/Payload' + $ref: '#/components/schemas/Request' required: - - payloadType - decisionType - - payload + - requests PolicyExecutionResponse: type: object @@ -127,7 +117,7 @@ components: example: "550e8400-e29b-41d4-a716-446655440000" decision: type: string - description: "The decision outcome. Currently supported values: 'permit','deny'" + description: "The decision outcome. Currently supported values: 'allow','deny'" example: "deny" message: type: string @@ -139,16 +129,16 @@ components: - message responses: - NotFound: - description: "The specified resource was not found" + BadRequest: + description: "Bad request" content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' example: - status: 404 - message: "Resource Not Found" - details: "The requested resource is not found" + status: 400 + message: "Bad Request" + details: "The provided request is not valid" Unauthorized: description: "Unauthorized request" content: @@ -169,16 +159,16 @@ components: status: 403 message: "Request Forbidden" details: "This request is forbidden" - BadRequest: - description: "Bad request" + Conflict: + description: "Conflict" content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' example: - status: 400 - message: "Bad Request" - details: "The provided request is not valid" + status: 409 + message: "Conflict" + details: "The provided request violates a policy rule" InternalServerError: description: "Internal server error" diff --git a/docs/schemas/policy-executor/ncmp-create-schema-1.0.0.json b/docs/schemas/policy-executor/ncmp-create-schema-1.0.0.json new file mode 100644 index 0000000000..2ec9daf949 --- /dev/null +++ b/docs/schemas/policy-executor/ncmp-create-schema-1.0.0.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "urn:cps:org.onap.cps.ncmp.policy-executor:ncmp-create-schema:1.0.0", + "$ref": "#/definitions/NcmpCreate", + "definitions": { + "NcmpCreate": { + "type": "object", + "additionalProperties": false, + "properties": { + "cmHandleId": { + "type": "string" + }, + "resourceIdentifier": { + "type": "string" + }, + "targetIdentifier": { + "type": "string" + }, + "cmChangeRequest": { + "type": "object" + } + }, + "required": [ + "targetIdentifier", + "cmChangeRequest" + ] + } + } +} diff --git a/docs/schemas/policy-executor/ncmp-delete-schema-1.0.0.json b/docs/schemas/policy-executor/ncmp-delete-schema-1.0.0.json new file mode 100644 index 0000000000..5df0325e39 --- /dev/null +++ b/docs/schemas/policy-executor/ncmp-delete-schema-1.0.0.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "urn:cps:org.onap.cps.ncmp.policy-executor:ncmp-delete-schema:1.0.0", + "$ref": "#/definitions/NcmpDelete", + "definitions": { + "NcmpDelete": { + "type": "object", + "additionalProperties": false, + "properties": { + "cmHandleId": { + "type": "string" + }, + "resourceIdentifier": { + "type": "string" + }, + "targetIdentifier": { + "type": "string" + } + }, + "required": [ + "targetIdentifier" + ] + } + } +} diff --git a/docs/schemas/policy-executor/ncmp-patch-schema-1.0.0.json b/docs/schemas/policy-executor/ncmp-patch-schema-1.0.0.json new file mode 100644 index 0000000000..e26c244c94 --- /dev/null +++ b/docs/schemas/policy-executor/ncmp-patch-schema-1.0.0.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "urn:cps:org.onap.cps.ncmp.policy-executor:ncmp-patch-schema:1.0.0", + "$ref": "#/definitions/NcmpPatch", + "definitions": { + "NcmpPatch": { + "type": "object", + "additionalProperties": false, + "properties": { + "cmHandleId": { + "type": "string" + }, + "resourceIdentifier": { + "type": "string" + }, + "targetIdentifier": { + "type": "string" + }, + "cmChangeRequest": { + "type": "object" + } + }, + "required": [ + "targetIdentifier", + "cmChangeRequest" + ] + } + } +} diff --git a/docs/schemas/policy-executor/ncmp-update-schema-1.0.0.json b/docs/schemas/policy-executor/ncmp-update-schema-1.0.0.json new file mode 100644 index 0000000000..0a497e38c5 --- /dev/null +++ b/docs/schemas/policy-executor/ncmp-update-schema-1.0.0.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "urn:cps:org.onap.cps.ncmp.policy-executor:ncmp-update-schema:1.0.0", + "$ref": "#/definitions/NcmpUpdate", + "definitions": { + "NcmpUpdate": { + "type": "object", + "additionalProperties": false, + "properties": { + "cmHandleId": { + "type": "string" + }, + "resourceIdentifier": { + "type": "string" + }, + "targetIdentifier": { + "type": "string" + }, + "cmChangeRequest": { + "type": "object" + } + }, + "required": [ + "targetIdentifier", + "cmChangeRequest" + ] + } + } +} diff --git a/policy-executor-stub/pom.xml b/policy-executor-stub/pom.xml index f076a2c55c..afdc1c7d3b 100644 --- a/policy-executor-stub/pom.xml +++ b/policy-executor-stub/pom.xml @@ -22,6 +22,11 @@ </properties> <dependencies> + <dependency> + <groupId>org.projectlombok</groupId> + <artifactId>lombok</artifactId> + <scope>provided</scope> + </dependency> <!-- S P R I N G D E P E N D E N C I E S --> <dependency> <groupId>org.springframework.boot</groupId> @@ -163,6 +168,20 @@ </execution> </executions> </plugin> + + <plugin> + <groupId>org.jsonschema2pojo</groupId> + <artifactId>jsonschema2pojo-maven-plugin</artifactId> + <configuration> + <useJakartaValidation>true</useJakartaValidation> + <sourceDirectory>${project.parent.basedir}/../docs/schemas/policy-executor</sourceDirectory> + <targetPackage>org.onap.cps.policyexecutor.stub.model</targetPackage> + <generateBuilders>true</generateBuilders> + <serializable>true</serializable> + <includeJsr303Annotations>true</includeJsr303Annotations> + </configuration> + </plugin> + </plugins> </build> diff --git a/policy-executor-stub/src/main/java/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubController.java b/policy-executor-stub/src/main/java/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubController.java index a5ec6dcac9..5b3a9931a0 100644 --- a/policy-executor-stub/src/main/java/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubController.java +++ b/policy-executor-stub/src/main/java/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubController.java @@ -20,12 +20,17 @@ package org.onap.cps.policyexecutor.stub.controller; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; +import lombok.RequiredArgsConstructor; import org.onap.cps.policyexecutor.stub.api.PolicyExecutorApi; +import org.onap.cps.policyexecutor.stub.model.NcmpDelete; import org.onap.cps.policyexecutor.stub.model.PolicyExecutionRequest; import org.onap.cps.policyexecutor.stub.model.PolicyExecutionResponse; +import org.onap.cps.policyexecutor.stub.model.Request; import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatusCode; import org.springframework.http.ResponseEntity; @@ -34,9 +39,13 @@ import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("${rest.api.policy-executor-base-path}") +@RequiredArgsConstructor public class PolicyExecutorStubController implements PolicyExecutorApi { - private final Pattern errorCodePattern = Pattern.compile("(\\d{3})"); + private final ObjectMapper objectMapper; + + private static final Pattern ERROR_CODE_PATTERN = Pattern.compile("(\\d{3})"); + private int decisionCounter = 0; @Override @@ -44,31 +53,55 @@ public class PolicyExecutorStubController implements PolicyExecutorApi { final String action, final PolicyExecutionRequest policyExecutionRequest, final String authorization) { - if (policyExecutionRequest.getPayload().isEmpty()) { + if (policyExecutionRequest.getRequests().isEmpty()) { + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + } + final Request firstRequest = policyExecutionRequest.getRequests().iterator().next(); + if ("ncmp-delete-schema:1.0.0".equals(firstRequest.getSchema())) { + return handleNcmpDeleteSchema(firstRequest); + } + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + } + + private ResponseEntity<PolicyExecutionResponse> handleNcmpDeleteSchema(final Request request) { + final NcmpDelete ncmpDelete; + try { + ncmpDelete = objectMapper.readValue((String) request.getData(), NcmpDelete.class); + } catch (final JsonProcessingException e) { return new ResponseEntity<>(HttpStatus.BAD_REQUEST); } - final String firstTargetFdn = policyExecutionRequest.getPayload().iterator().next().getTargetFdn(); + final String targetIdentifier = ncmpDelete.getTargetIdentifier(); + if (targetIdentifier == null) { + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + } - final Matcher matcher = errorCodePattern.matcher(firstTargetFdn); + final Matcher matcher = ERROR_CODE_PATTERN.matcher(targetIdentifier); if (matcher.find()) { final int errorCode = Integer.parseInt(matcher.group(1)); return new ResponseEntity<>(HttpStatusCode.valueOf(errorCode)); } + return createPolicyExecutionResponse(targetIdentifier); + } + + private ResponseEntity<PolicyExecutionResponse> createPolicyExecutionResponse(final String targetIdentifier) { final String decisionId = String.valueOf(++decisionCounter); final String decision; final String message; - if (firstTargetFdn.toLowerCase(Locale.getDefault()).contains("cps-is-great")) { - decision = "permit"; + if (targetIdentifier.toLowerCase(Locale.getDefault()).contains("cps-is-great")) { + decision = "allow"; message = "All good"; } else { decision = "deny"; - message = "Only FDNs containing 'cps-is-great' are permitted"; + message = "Only FDNs containing 'cps-is-great' are allowed"; } + final PolicyExecutionResponse policyExecutionResponse = new PolicyExecutionResponse(decisionId, decision, message); + return ResponseEntity.ok(policyExecutionResponse); } + } diff --git a/policy-executor-stub/src/test/groovy/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubControllerSpec.groovy b/policy-executor-stub/src/test/groovy/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubControllerSpec.groovy index 871db81ac8..efb12ac619 100644 --- a/policy-executor-stub/src/test/groovy/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubControllerSpec.groovy +++ b/policy-executor-stub/src/test/groovy/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubControllerSpec.groovy @@ -21,9 +21,10 @@ package org.onap.cps.policyexecutor.stub.controller import com.fasterxml.jackson.databind.ObjectMapper -import org.onap.cps.policyexecutor.stub.model.Payload +import org.onap.cps.policyexecutor.stub.model.NcmpDelete import org.onap.cps.policyexecutor.stub.model.PolicyExecutionRequest import org.onap.cps.policyexecutor.stub.model.PolicyExecutionResponse +import org.onap.cps.policyexecutor.stub.model.Request import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest import org.springframework.http.HttpStatus @@ -44,11 +45,9 @@ class PolicyExecutorStubControllerSpec extends Specification { def url = '/policy-executor/api/v1/some-action' - def 'Execute Policy Actions.'() { - given: 'a policy execution request with target fdn: #targetFdn' - def payload = new Payload(targetFdn, 'some change request') - def policyExecutionRequest = new PolicyExecutionRequest('some payload type','some decision type', [payload]) - def requestBody = objectMapper.writeValueAsString(policyExecutionRequest) + def 'Execute policy action.'() { + given: 'a policy execution request with target: #targetIdentifier' + def requestBody = createRequestBody(targetIdentifier) when: 'request is posted' def response = mockMvc.perform(post(url) .header('Authorization','some string') @@ -61,19 +60,17 @@ class PolicyExecutorStubControllerSpec extends Specification { def responseBody = response.contentAsString def policyExecutionResponse = objectMapper.readValue(responseBody, PolicyExecutionResponse.class) assert policyExecutionResponse.decisionId == expectedDecsisonId - assert policyExecutionResponse.decision == expectedDecsison + assert policyExecutionResponse.decision == expectedDecision assert policyExecutionResponse.message == expectedMessage where: 'the following targets are used' - targetFdn || expectedDecsisonId | expectedDecsison | expectedMessage - 'some fdn' || '1' | 'deny' | "Only FDNs containing 'cps-is-great' are permitted" - 'fdn with cps-is-great' || '2' | 'permit' | "All good" + targetIdentifier || expectedDecsisonId | expectedDecision | expectedMessage + 'some fdn' || '1' | 'deny' | "Only FDNs containing 'cps-is-great' are allowed" + 'fdn with cps-is-great' || '2' | 'allow' | 'All good' } - def 'Execute Policy Action with a HTTP Error Code.'() { + def 'Execute policy action with a HTTP error code.'() { given: 'a policy execution request with a target fdn with a 3-digit error code' - def payload = new Payload('fdn with error code 418', 'some change request') - def policyExecutionRequest = new PolicyExecutionRequest('some payload type','some decision type', [payload]) - def requestBody = objectMapper.writeValueAsString(policyExecutionRequest) + def requestBody = createRequestBody('target with error code 418') when: 'request is posted' def response = mockMvc.perform(post(url) .header('Authorization','some string') @@ -84,11 +81,9 @@ class PolicyExecutorStubControllerSpec extends Specification { assert response.status == 418 } - def 'Execute Policy Action without Authorization Header.'() { + def 'Execute policy action without authorization header.'() { given: 'a valid policy execution request' - def payload = new Payload('some fdn', 'some change request') - def policyExecutionRequest = new PolicyExecutionRequest('some payload type','some decision type', [payload]) - def requestBody = objectMapper.writeValueAsString(policyExecutionRequest) + def requestBody = createRequestBody('some target') when: 'request is posted without authorization header' def response = mockMvc.perform(post(url) .contentType(MediaType.APPLICATION_JSON) @@ -98,9 +93,9 @@ class PolicyExecutorStubControllerSpec extends Specification { assert response.status == HttpStatus.OK.value() } - def 'Execute Policy Action with Empty Payload.'() { - given: 'a policy execution request with empty payload list' - def policyExecutionRequest = new PolicyExecutionRequest('some payload type','some decision type', []) + def 'Execute policy action with no requests.'() { + given: 'a policy execution request' + def policyExecutionRequest = new PolicyExecutionRequest('some decision type', []) def requestBody = objectMapper.writeValueAsString(policyExecutionRequest) when: 'request is posted' def response = mockMvc.perform(post(url) @@ -112,26 +107,49 @@ class PolicyExecutorStubControllerSpec extends Specification { assert response.status == HttpStatus.BAD_REQUEST.value() } - def 'Execute Policy Action without other required attributes.'() { - given: 'a policy execution request with payloadType=#payloadType, decisionType=decisionType, targetFdn=#targetFdn, changeRequest=#changeRequest' - def payload = new Payload(targetFdn, changeRequest) - def policyExecutionRequest = new PolicyExecutionRequest(payloadType, decisionType, [payload]) + def 'Execute policy action with invalid json for request data.'() { + given: 'a policy execution request' + def request = new Request('ncmp-delete-schema:1.0.0', 'invalid json') + def policyExecutionRequest = new PolicyExecutionRequest('some decision type', [request]) def requestBody = objectMapper.writeValueAsString(policyExecutionRequest) when: 'request is posted' def response = mockMvc.perform(post(url) + .header('Authorization','some string') + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody)) + .andReturn().response + then: 'response status is Bad Request' + assert response.status == HttpStatus.BAD_REQUEST.value() + } + + def 'Execute policy action with missing or invalid attributes.'() { + given: 'a policy execution request with decisionType=#decisionType, schema=#schema, targetIdentifier=#targetIdentifier' + def requestBody = createRequestBody(decisionType, schema, targetIdentifier) + when: 'request is posted' + def response = mockMvc.perform(post(url) .header('Authorization','something') .contentType(MediaType.APPLICATION_JSON) .content(requestBody)) .andReturn().response then: 'response status as expected' assert response.status == expectedStatus.value() - where: 'following parameters are populated or not' - payloadType | decisionType | targetFdn | changeRequest || expectedStatus - 'something' | 'something' | 'something' | 'something' || HttpStatus.OK - null | 'something' | 'something' | 'something' || HttpStatus.BAD_REQUEST - 'something' | null | 'something' | 'something' || HttpStatus.BAD_REQUEST - 'something' | 'something' | null | 'something' || HttpStatus.BAD_REQUEST - 'something' | 'something' | 'something' | null || HttpStatus.BAD_REQUEST + where: 'following parameters are used' + decisionType | schema | targetIdentifier || expectedStatus + 'something' | 'ncmp-delete-schema:1.0.0' | 'something' || HttpStatus.OK + null | 'ncmp-delete-schema:1.0.0' | 'something' || HttpStatus.BAD_REQUEST + 'something' | 'other schema' | 'something' || HttpStatus.BAD_REQUEST + 'something' | 'ncmp-delete-schema:1.0.0' | null || HttpStatus.BAD_REQUEST + } + + def createRequestBody(decisionType, schema, targetIdentifier) { + def ncmpDelete = new NcmpDelete(targetIdentifier: targetIdentifier) + def request = new Request(schema, objectMapper.writeValueAsString(ncmpDelete)) + def policyExecutionRequest = new PolicyExecutionRequest(decisionType, [request]) + return objectMapper.writeValueAsString(policyExecutionRequest) + } + + def createRequestBody(targetIdentifier) { + return createRequestBody('some decision type', 'ncmp-delete-schema:1.0.0', targetIdentifier) } } |