diff options
Diffstat (limited to 'policy-executor-stub/src')
6 files changed, 404 insertions, 0 deletions
diff --git a/policy-executor-stub/src/main/java/org/onap/cps/policyexecutor/stub/PolicyExecutorApplication.java b/policy-executor-stub/src/main/java/org/onap/cps/policyexecutor/stub/PolicyExecutorApplication.java new file mode 100644 index 0000000000..367a470e2d --- /dev/null +++ b/policy-executor-stub/src/main/java/org/onap/cps/policyexecutor/stub/PolicyExecutorApplication.java @@ -0,0 +1,31 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.policyexecutor.stub; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class PolicyExecutorApplication { + public static void main(final String[] args) { + SpringApplication.run(PolicyExecutorApplication.class, args); + } +} 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 new file mode 100644 index 0000000000..88073c0a0f --- /dev/null +++ b/policy-executor-stub/src/main/java/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubController.java @@ -0,0 +1,112 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.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.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@Slf4j +public class PolicyExecutorStubController implements PolicyExecutorApi { + + 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()) { + 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); + } + log.warn("This stub only supports 'delete' operations"); + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + } + + 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); + } + + 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 (targetIdentifier.toLowerCase(Locale.getDefault()).contains("slow")) { + try { + sleeper.haveALittleRest(slowResponseTimeInSeconds); + } catch (final InterruptedException e) { + log.trace("Sleep interrupted, re-interrupting the thread"); + Thread.currentThread().interrupt(); // Re-interrupt the thread + } + } + 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 allowed"; + } + log.info("Decision: {} ({})", decision, message); + final PolicyExecutionResponse policyExecutionResponse = + new PolicyExecutionResponse(decisionId, decision, message); + + return ResponseEntity.ok(policyExecutionResponse); + } + +} diff --git a/policy-executor-stub/src/main/java/org/onap/cps/policyexecutor/stub/controller/Sleeper.java b/policy-executor-stub/src/main/java/org/onap/cps/policyexecutor/stub/controller/Sleeper.java new file mode 100644 index 0000000000..789201ffce --- /dev/null +++ b/policy-executor-stub/src/main/java/org/onap/cps/policyexecutor/stub/controller/Sleeper.java @@ -0,0 +1,35 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.policyexecutor.stub.controller; + +import java.util.concurrent.TimeUnit; +import org.springframework.stereotype.Service; + +/** + * This class is to extract out sleep functionality so the interrupted exception handling can + * be covered with a test (e.g. using spy on Sleeper) and help to get too 100% code coverage. + */ +@Service +public class Sleeper { + public void haveALittleRest(final int timeInSeconds) throws InterruptedException { + TimeUnit.SECONDS.sleep(timeInSeconds); + } +} diff --git a/policy-executor-stub/src/main/resources/application.yml b/policy-executor-stub/src/main/resources/application.yml new file mode 100644 index 0000000000..8ed5742a41 --- /dev/null +++ b/policy-executor-stub/src/main/resources/application.yml @@ -0,0 +1,19 @@ +# ============LICENSE_START======================================================= +# Copyright (C) 2024 Nordix +# ================================================================================ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +server: + port: 8093 diff --git a/policy-executor-stub/src/test/groovy/org/onap/cps/policyexecutor/stub/PolicyExecutorApplicationSpec.groovy b/policy-executor-stub/src/test/groovy/org/onap/cps/policyexecutor/stub/PolicyExecutorApplicationSpec.groovy new file mode 100644 index 0000000000..565932d9f0 --- /dev/null +++ b/policy-executor-stub/src/test/groovy/org/onap/cps/policyexecutor/stub/PolicyExecutorApplicationSpec.groovy @@ -0,0 +1,34 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.policyexecutor.stub + +import spock.lang.Specification + +class PolicyExecutorApplicationSpec extends Specification { + + def 'Execute Policy Action.'() { + when: 'Starting the application (for coverage)' + PolicyExecutorApplication.main() + then: 'all goes well' + noExceptionThrown() + } + +} 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 new file mode 100644 index 0000000000..44460daa7e --- /dev/null +++ b/policy-executor-stub/src/test/groovy/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubControllerSpec.groovy @@ -0,0 +1,173 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.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.spockframework.spring.SpringBean +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest +import org.springframework.http.HttpStatus +import org.springframework.http.MediaType +import org.springframework.test.web.servlet.MockMvc +import spock.lang.Specification + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post + +@WebMvcTest(PolicyExecutorStubController) +class PolicyExecutorStubControllerSpec extends Specification { + + @Autowired + MockMvc mockMvc + + @Autowired + ObjectMapper objectMapper + + @SpringBean + Sleeper sleeper = Spy() + + def url = '/policy-executor/api/v1/some-action' + + def setup() { + PolicyExecutorStubController.slowResponseTimeInSeconds = 1 + } + + 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') + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody)) + .andReturn().response + then: 'response status is Ok' + 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 + 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" + } + + 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 requestBody = createRequestBody('target with error code 418') + 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 the same error code as in target fdn' + assert response.status == 418 + } + + def 'Execute policy action without authorization header.'() { + given: 'a valid policy execution request' + def requestBody = createRequestBody('some target') + when: 'request is posted without authorization header' + def response = mockMvc.perform(post(url) + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody)) + .andReturn().response + then: 'response status is OK' + 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) + 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 invalid json for request data.'() { + when: 'request is posted' + def response = mockMvc.perform(post(url) + .header('Authorization','some string') + .contentType(MediaType.APPLICATION_JSON) + .content('invalid json')) + .andReturn().response + then: 'response status is Bad Request' + 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 requestBody = createRequestBody('slow') + sleeper.haveALittleRest(_) >> { throw new InterruptedException() } + when: 'request is posted' + mockMvc.perform(post(url) + .header('Authorization','some string') + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody)) + then: 'response status is Bad Request' + 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) + 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 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, 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) + } + +} |