Merge "Refactor OpenAPI Policy Executor"
diff --git a/docs/api/swagger/policy-executor/openapi.yaml b/docs/api/swagger/policy-executor/openapi.yaml
index 98c5b1e..58ca5ac 100644
--- a/docs/api/swagger/policy-executor/openapi.yaml
+++ b/docs/api/swagger/policy-executor/openapi.yaml
@@ -52,8 +52,12 @@
$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 @@
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 @@
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 @@
- 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 @@
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 0000000..2ec9daf
--- /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 0000000..5df0325
--- /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 0000000..e26c244
--- /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 0000000..0a497e3
--- /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 f076a2c..afdc1c7 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 a5ec6dc..5b3a993 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 @@
@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 @@
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 871db81..efb12ac 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 @@
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 @@
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 @@
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 @@
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 @@
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)
}
}