diff options
author | Jim Hahn <jrh3@att.com> | 2020-03-09 15:15:05 -0400 |
---|---|---|
committer | Jim Hahn <jrh3@att.com> | 2020-03-12 12:12:48 -0400 |
commit | aa8225b5211485b3c1150c21e51fd3e93b7f31d3 (patch) | |
tree | 922b78d2c8a4c657d4c32604a0b73df40d2fb330 /controlloop/common/rules-test | |
parent | 0e658768fc0573bf6acf7f849a49c9da98c8e47f (diff) |
Retool rules tests
Extracted common code from various XxxBaseTest classes into:
- Topics class to manage messages for test topics
- HttpClients class to manage HttpClient objects for tests
- Simulators class to manage simulators for tests
- Rules class to manage start up and shutdown of rules
Merged remaining code from XxxBaseTest classes into a single
class. Modified the Frankfurt and Usescases tests to subclass
from this new class and specify just the relevant tests to be
executed.
Issue-ID: POLICY-2385
Signed-off-by: Jim Hahn <jrh3@att.com>
Change-Id: Iaf83c9d2b205a4c343e0dde23ec86508f5773693
Diffstat (limited to 'controlloop/common/rules-test')
53 files changed, 4295 insertions, 0 deletions
diff --git a/controlloop/common/rules-test/pom.xml b/controlloop/common/rules-test/pom.xml new file mode 100644 index 000000000..ddd897c35 --- /dev/null +++ b/controlloop/common/rules-test/pom.xml @@ -0,0 +1,165 @@ +<!-- + ============LICENSE_START======================================================= + ONAP + ================================================================================ + Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + ================================================================================ + 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. + ============LICENSE_END========================================================= + --> + +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + + <groupId>org.onap.policy.drools-applications.controlloop.common</groupId> + <artifactId>drools-applications-common</artifactId> + <version>1.6.0-SNAPSHOT</version> + </parent> + + <artifactId>rules-test</artifactId> + <description>Common Utilities to facilitate testing via JUnit</description> + <packaging>jar</packaging> + + <properties> + <powermock.version>2.0.4</powermock.version> + </properties> + + <dependencies> + <dependency> + <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId> + <artifactId>events</artifactId> + <version>${policy.models.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId> + <artifactId>appc</artifactId> + <version>${policy.models.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId> + <artifactId>appclcm</artifactId> + <version>${policy.models.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.projectlombok</groupId> + <artifactId>lombok</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.awaitility</groupId> + <artifactId>awaitility</artifactId> + <scope>compile</scope> + </dependency> + <dependency> + <groupId>org.assertj</groupId> + <artifactId>assertj-core</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>ch.qos.logback</groupId> + <artifactId>logback-classic</artifactId> + </dependency> + <dependency> + <groupId>org.onap.policy.common</groupId> + <artifactId>utils</artifactId> + <version>${version.policy.common}</version> + </dependency> + <dependency> + <groupId>org.onap.policy.common</groupId> + <artifactId>policy-endpoints</artifactId> + <version>${version.policy.common}</version> + </dependency> + <dependency> + <groupId>org.onap.policy.models.policy-models-interactions</groupId> + <artifactId>simulators</artifactId> + <version>${policy.models.version}</version> + </dependency> + <dependency> + <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId> + <artifactId>events</artifactId> + <version>${policy.models.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.drools-applications.controlloop.common</groupId> + <artifactId>eventmanager</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.drools-pdp</groupId> + <artifactId>policy-management</artifactId> + <version>${version.policy.drools-pdp}</version> + <scope>provided</scope> + <optional>true</optional> + </dependency> + <dependency> + <groupId>org.powermock</groupId> + <artifactId>powermock-api-mockito2</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.common</groupId> + <artifactId>utils-test</artifactId> + <version>${version.policy.common}</version> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <pluginManagement> + <plugins> + <!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven + build itself. --> + <plugin> + <groupId>org.eclipse.m2e</groupId> + <artifactId>lifecycle-mapping</artifactId> + <version>1.0.0</version> + <configuration> + <lifecycleMappingMetadata> + <pluginExecutions> + <pluginExecution> + <pluginExecutionFilter> + <groupId>org.jacoco</groupId> + <artifactId> + jacoco-maven-plugin + </artifactId> + <versionRange> + [0.7.1.201405082137,) + </versionRange> + <goals> + <goal>prepare-agent</goal> + </goals> + </pluginExecutionFilter> + <action> + <ignore /> + </action> + </pluginExecution> + </pluginExecutions> + </lifecycleMappingMetadata> + </configuration> + </plugin> + </plugins> + </pluginManagement> + </build> +</project> diff --git a/controlloop/common/rules-test/src/main/java/org/onap/policy/controlloop/common/rules/test/BaseRuleTest.java b/controlloop/common/rules-test/src/main/java/org/onap/policy/controlloop/common/rules/test/BaseRuleTest.java new file mode 100644 index 000000000..711a61738 --- /dev/null +++ b/controlloop/common/rules-test/src/main/java/org/onap/policy/controlloop/common/rules/test/BaseRuleTest.java @@ -0,0 +1,496 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.common.rules.test; + +import static org.junit.Assert.assertEquals; + +import java.util.List; +import java.util.UUID; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import lombok.AccessLevel; +import lombok.Getter; +import org.junit.Test; +import org.onap.policy.appc.Request; +import org.onap.policy.appclcm.AppcLcmDmaapWrapper; +import org.onap.policy.common.utils.coder.Coder; +import org.onap.policy.common.utils.coder.StandardCoder; +import org.onap.policy.common.utils.coder.StandardCoderInstantAsMillis; +import org.onap.policy.controlloop.ControlLoopNotificationType; +import org.onap.policy.controlloop.VirtualControlLoopNotification; +import org.onap.policy.drools.system.PolicyController; +import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy; + +/** + * Superclass used for rule tests. + */ +public abstract class BaseRuleTest { + /* + * Canonical Topic Names. + */ + protected static final String DCAE_TOPIC = "DCAE_TOPIC"; + protected static final String APPC_LCM_WRITE_TOPIC = "APPC-LCM-WRITE"; + protected static final String POLICY_CL_MGT_TOPIC = "POLICY-CL-MGT"; + protected static final String APPC_LCM_READ_TOPIC = "APPC-LCM-READ"; + protected static final String APPC_CL_TOPIC = "APPC-CL"; + + /* + * Constants for each test case. + */ + + // service123 (i.e., multi-operation policy) + private static final String SERVICE123_TOSCA_COMPLIANT_POLICY = "service123/tosca-compliant-service123.json"; + private static final String SERVICE123_ONSET = "service123/service123.onset.json"; + private static final String SERVICE123_APPC_RESTART_FAILURE = "service123/service123.appc.restart.failure.json"; + private static final String SERVICE123_APPC_REBUILD_FAILURE = "service123/service123.appc.rebuild.failure.json"; + private static final String SERVICE123_APPC_MIGRATE_SUCCESS = "service123/service123.appc.migrate.success.json"; + + // duplicates (i.e., mutliple events in the engine at the same time) + private static final String DUPLICATES_TOSCA_COMPLIANT_POLICY = "duplicates/tosca-compliant-duplicates.json"; + private static final String DUPLICATES_ONSET_1 = "duplicates/duplicates.onset.1.json"; + private static final String DUPLICATES_ONSET_2 = "duplicates/duplicates.onset.2.json"; + private static final String DUPLICATES_APPC_SUCCESS = "duplicates/duplicates.appc.success.json"; + + // VCPE + private static final String VCPE_TOSCA_LEGACY_POLICY = "vcpe/tosca-legacy-vcpe.json"; + private static final String VCPE_TOSCA_COMPLIANT_POLICY = "vcpe/tosca-compliant-vcpe.json"; + private static final String VCPE_ONSET_1 = "vcpe/vcpe.onset.1.json"; + private static final String VCPE_ONSET_2 = "vcpe/vcpe.onset.2.json"; + private static final String VCPE_ONSET_3 = "vcpe/vcpe.onset.3.json"; + private static final String VCPE_APPC_SUCCESS = "vcpe/vcpe.appc.success.json"; + + // VDNS + private static final String VDNS_TOSCA_COMPLIANT_POLICY = "vdns/tosca-compliant-vdns.json"; + private static final String VDNS_ONSET = "vdns/vdns.onset.json"; + + // VFW + private static final String VFW_TOSCA_LEGACY_POLICY = "vfw/tosca-vfw.json"; + private static final String VFW_TOSCA_COMPLIANT_POLICY = "vfw/tosca-compliant-vfw.json"; + private static final String VFW_ONSET = "vfw/vfw.onset.json"; + private static final String VFW_APPC_SUCCESS = "vfw/vfw.appc.success.json"; + + // VLB + private static final String VLB_TOSCA_LEGACY_POLICY = "vlb/tosca-vlb.json"; + private static final String VLB_TOSCA_COMPLIANT_POLICY = "vlb/tosca-compliant-vlb.json"; + private static final String VLB_ONSET = "vlb/vlb.onset.json"; + + /* + * Coders used to decode requests and responses. + */ + private static final Coder APPC_LEGACY_CODER = new StandardCoderInstantAsMillis(); + private static final Coder APPC_LCM_CODER = new StandardCoder(); + + // these may be overridden by junit tests + private static Function<String, Rules> ruleMaker = Rules::new; + private static Supplier<HttpClients> httpClientMaker = HttpClients::new; + private static Supplier<Simulators> simMaker = Simulators::new; + private static Supplier<Topics> topicMaker = Topics::new; + + protected static Rules rules; + protected static HttpClients httpClients; + protected static Simulators simulators; + + + // used to inject and wait for messages + @Getter(AccessLevel.PROTECTED) + private Topics topics; + + // used to wait for messages on SINK topics + protected Listener<VirtualControlLoopNotification> policyClMgt; + protected Listener<Request> appcClSink; + protected Listener<AppcLcmDmaapWrapper> appcLcmRead; + + protected PolicyController controller; + + /* + * Tosca Policy that was loaded. + */ + protected ToscaPolicy policy; + + + /** + * Initializes {@link #rules}, {@link #httpClients}, and {@link #simulators}. + * + * @param controllerName the rule controller name + */ + public static void initStatics(String controllerName) { + rules = ruleMaker.apply(controllerName); + httpClients = httpClientMaker.get(); + simulators = simMaker.get(); + } + + /** + * Destroys {@link #httpClients}, {@link #simulators}, and {@link #rules}. + */ + public static void finishStatics() { + httpClients.destroy(); + simulators.destroy(); + rules.destroy(); + } + + /** + * Initializes {@link #topics} and {@link #controller}. + */ + public void init() { + topics = topicMaker.get(); + controller = rules.getController(); + } + + /** + * Destroys {@link #topics} and resets the rule facts. + */ + public void finish() { + topics.destroy(); + rules.resetFacts(); + } + + // Service123 (i.e., Policy with multiple operations) + + /** + * Service123 with Tosca Compliant Policy. + */ + @Test + public void testService123Compliant() { + policyClMgt = topics.createListener(POLICY_CL_MGT_TOPIC, VirtualControlLoopNotification.class, controller); + appcLcmRead = topics.createListener(APPC_LCM_READ_TOPIC, AppcLcmDmaapWrapper.class, APPC_LCM_CODER); + + assertEquals(0, controller.getDrools().factCount(rules.getControllerName())); + policy = rules.setupPolicyFromFile(SERVICE123_TOSCA_COMPLIANT_POLICY); + assertEquals(2, controller.getDrools().factCount(rules.getControllerName())); + + // inject an ONSET event over the DCAE topic + topics.inject(DCAE_TOPIC, SERVICE123_ONSET); + + /* Wait to acquire a LOCK and a PDP-X PERMIT */ + waitForLockAndPermit(policy, policyClMgt); + + // restart request should be sent and fail four times (i.e., because retry=3) + for (int count = 0; count < 4; ++count) { + AppcLcmDmaapWrapper appcreq = appcLcmRead.await(req -> "restart".equals(req.getRpcName())); + + topics.inject(APPC_LCM_WRITE_TOPIC, SERVICE123_APPC_RESTART_FAILURE, + appcreq.getBody().getInput().getCommonHeader().getSubRequestId()); + } + + // rebuild request should be sent and fail once + AppcLcmDmaapWrapper appcreq = appcLcmRead.await(req -> "rebuild".equals(req.getRpcName())); + + topics.inject(APPC_LCM_WRITE_TOPIC, SERVICE123_APPC_REBUILD_FAILURE, + appcreq.getBody().getInput().getCommonHeader().getSubRequestId()); + + // migrate request should be sent and succeed + appcreq = appcLcmRead.await(req -> "migrate".equals(req.getRpcName())); + + topics.inject(APPC_LCM_WRITE_TOPIC, SERVICE123_APPC_MIGRATE_SUCCESS, + appcreq.getBody().getInput().getCommonHeader().getSubRequestId()); + + /* --- Operation Completed --- */ + + waitForOperationSuccess(); + + /* --- Transaction Completed --- */ + waitForFinalSuccess(policy, policyClMgt); + } + + // Duplicate events + + /** + * This test case tests the scenario where 3 events occur and 2 of the requests refer + * to the same target entity while the 3rd is for another entity. The expected result + * is that the event with the duplicate target entity will have a final success result + * for one of the events, and a rejected message for the one that was unable to obtain + * the lock. The event that is referring to a different target entity should be able + * to obtain a lock since it is a different target. After processing of all events + * there should only be the policy and params objects left in memory. + */ + @Test + public void testDuplicatesEvents() { + policyClMgt = topics.createListener(POLICY_CL_MGT_TOPIC, VirtualControlLoopNotification.class, controller); + appcLcmRead = topics.createListener(APPC_LCM_READ_TOPIC, AppcLcmDmaapWrapper.class, APPC_LCM_CODER); + + assertEquals(0, controller.getDrools().factCount(rules.getControllerName())); + policy = rules.setupPolicyFromFile(DUPLICATES_TOSCA_COMPLIANT_POLICY); + assertEquals(2, controller.getDrools().factCount(rules.getControllerName())); + + /* + * Inject ONSET events over the DCAE topic. First and last have the same target + * entity, but different request IDs - only one should succeed. The middle one is + * for a different target entity, so it should succeed. + */ + topics.inject(DCAE_TOPIC, DUPLICATES_ONSET_1, UUID.randomUUID().toString()); + topics.inject(DCAE_TOPIC, DUPLICATES_ONSET_2); + topics.inject(DCAE_TOPIC, DUPLICATES_ONSET_1, UUID.randomUUID().toString()); + + // one should immediately generate a FINAL failure + waitForFinal(policy, policyClMgt, ControlLoopNotificationType.FINAL_FAILURE); + + // should see two restarts + for (int count = 0; count < 2; ++count) { + AppcLcmDmaapWrapper appcreq = appcLcmRead.await(req -> "restart".equals(req.getRpcName())); + + // indicate success + topics.inject(APPC_LCM_WRITE_TOPIC, DUPLICATES_APPC_SUCCESS, + appcreq.getBody().getInput().getCommonHeader().getSubRequestId()); + } + + // should see two FINAL successes + VirtualControlLoopNotification notif1 = waitForFinalSuccess(policy, policyClMgt); + VirtualControlLoopNotification notif2 = waitForFinalSuccess(policy, policyClMgt); + + // get the list of target names so we can ensure there's one of each + List<String> actual = List.of(notif1, notif2).stream().map(notif -> notif.getAai().get("generic-vnf.vnf-id")) + .sorted().collect(Collectors.toList()); + + assertEquals(List.of("duplicate-VNF", "vCPE_Infrastructure_vGMUX_demo_app").toString(), actual.toString()); + } + + // VCPE + + /** + * Sunny Day with Legacy Tosca Policy. + */ + @Test + public void testVcpeSunnyDayLegacy() { + appcLcmSunnyDay(VCPE_TOSCA_LEGACY_POLICY, VCPE_ONSET_1, "restart"); + } + + /** + * Sunny Day with Tosca Compliant Policy. + */ + @Test + public void testVcpeSunnyDayCompliant() { + appcLcmSunnyDay(VCPE_TOSCA_COMPLIANT_POLICY, VCPE_ONSET_1, "restart"); + } + + /** + * An ONSET flood prevention test that injects a few ONSETs at once. It attempts to + * simulate the flooding behavior of the DCAE TCA microservice. TCA could blast tens + * or hundreds of ONSETs within sub-second intervals. + */ + @Test + public void testVcpeOnsetFloodPrevention() { + appcLcmSunnyDay(VCPE_TOSCA_COMPLIANT_POLICY, List.of(VCPE_ONSET_1, VCPE_ONSET_2, VCPE_ONSET_3), "restart"); + } + + // VDNS + + /** + * Sunny Day with Tosca Compliant Policy. + */ + @Test + public void testVdnsSunnyDayCompliant() { + httpSunnyDay(VDNS_TOSCA_COMPLIANT_POLICY, VDNS_ONSET); + } + + // VFW + + /** + * VFW Sunny Day with Legacy Tosca Policy. + */ + @Test + public void testVfwSunnyDayLegacy() { + appcLegacySunnyDay(VFW_TOSCA_LEGACY_POLICY, VFW_ONSET, "ModifyConfig"); + } + + /** + * VFW Sunny Day with Tosca Compliant Policy. + */ + @Test + public void testVfwSunnyDayCompliant() { + appcLegacySunnyDay(VFW_TOSCA_COMPLIANT_POLICY, VFW_ONSET, "ModifyConfig"); + } + + // VLB + + /** + * Sunny Day with Legacy Tosca Policy. + */ + @Test + public void testVlbSunnyDayLegacy() { + httpSunnyDay(VLB_TOSCA_LEGACY_POLICY, VLB_ONSET); + } + + /** + * Sunny Day with Tosca Compliant Policy. + */ + @Test + public void testVlbSunnyDayCompliant() { + httpSunnyDay(VLB_TOSCA_COMPLIANT_POLICY, VLB_ONSET); + } + + /** + * Sunny day scenario for use cases that use APPC-LCM. + * + * @param policyFile file containing the ToscaPolicy to be loaded + * @param onsetFile file containing the ONSET to be injected + * @param operation expected APPC operation request + */ + protected void appcLcmSunnyDay(String policyFile, String onsetFile, String operation) { + appcLcmSunnyDay(policyFile, List.of(onsetFile), operation); + } + + /** + * Sunny day scenario for use cases that use APPC-LCM. + * + * @param policyFile file containing the ToscaPolicy to be loaded + * @param onsetFiles list of files containing the ONSET to be injected + * @param operation expected APPC operation request + */ + protected void appcLcmSunnyDay(String policyFile, List<String> onsetFiles, String operation) { + policyClMgt = topics.createListener(POLICY_CL_MGT_TOPIC, VirtualControlLoopNotification.class, controller); + appcLcmRead = topics.createListener(APPC_LCM_READ_TOPIC, AppcLcmDmaapWrapper.class, APPC_LCM_CODER); + + assertEquals(0, controller.getDrools().factCount(rules.getControllerName())); + policy = rules.setupPolicyFromFile(policyFile); + assertEquals(2, controller.getDrools().factCount(rules.getControllerName())); + + // inject several ONSET events over the DCAE topic + for (String onsetFile : onsetFiles) { + topics.inject(DCAE_TOPIC, onsetFile); + } + + /* Wait to acquire a LOCK and a PDP-X PERMIT */ + waitForLockAndPermit(policy, policyClMgt); + + /* + * Ensure that an APPC RESTART request was sent in response to the matching ONSET + */ + AppcLcmDmaapWrapper appcreq = appcLcmRead.await(req -> operation.equals(req.getRpcName())); + + /* + * Inject a 400 APPC Response Return over the APPC topic, with appropriate + * subRequestId + */ + topics.inject(APPC_LCM_WRITE_TOPIC, VCPE_APPC_SUCCESS, + appcreq.getBody().getInput().getCommonHeader().getSubRequestId()); + + /* --- Operation Completed --- */ + + waitForOperationSuccess(); + + /* --- Transaction Completed --- */ + waitForFinalSuccess(policy, policyClMgt); + } + + /** + * Sunny day scenario for use cases that use Legacy APPC. + * + * @param policyFile file containing the ToscaPolicy to be loaded + * @param onsetFile file containing the ONSET to be injected + * @param operation expected APPC operation request + */ + protected void appcLegacySunnyDay(String policyFile, String onsetFile, String operation) { + policyClMgt = topics.createListener(POLICY_CL_MGT_TOPIC, VirtualControlLoopNotification.class, controller); + appcClSink = topics.createListener(APPC_CL_TOPIC, Request.class, APPC_LEGACY_CODER); + + assertEquals(0, controller.getDrools().factCount(rules.getControllerName())); + policy = rules.setupPolicyFromFile(policyFile); + assertEquals(2, controller.getDrools().factCount(rules.getControllerName())); + + /* Inject an ONSET event over the DCAE topic */ + topics.inject(DCAE_TOPIC, onsetFile); + + /* Wait to acquire a LOCK and a PDP-X PERMIT */ + waitForLockAndPermit(policy, policyClMgt); + + /* + * Ensure that an APPC RESTART request was sent in response to the matching ONSET + */ + Request appcreq = appcClSink.await(req -> operation.equals(req.getAction())); + + /* + * Inject a 400 APPC Response Return over the APPC topic, with appropriate + * subRequestId + */ + topics.inject(APPC_CL_TOPIC, VFW_APPC_SUCCESS, appcreq.getCommonHeader().getSubRequestId()); + + /* --- Operation Completed --- */ + + waitForOperationSuccess(); + + /* --- Transaction Completed --- */ + waitForFinalSuccess(policy, policyClMgt); + } + + /** + * Sunny day scenario for use cases that use an HTTP simulator. + * + * @param policyFile file containing the ToscaPolicy to be loaded + * @param onsetFile file containing the ONSET to be injected + * @param operation expected APPC operation request + */ + protected void httpSunnyDay(String policyFile, String onsetFile) { + policyClMgt = topics.createListener(POLICY_CL_MGT_TOPIC, VirtualControlLoopNotification.class, controller); + + assertEquals(0, controller.getDrools().factCount(rules.getControllerName())); + policy = rules.setupPolicyFromFile(policyFile); + assertEquals(2, controller.getDrools().factCount(rules.getControllerName())); + + /* Inject an ONSET event over the DCAE topic */ + topics.inject(DCAE_TOPIC, onsetFile); + + /* Wait to acquire a LOCK and a PDP-X PERMIT */ + waitForLockAndPermit(policy, policyClMgt); + + /* --- Operation Completed --- */ + + waitForOperationSuccess(); + + /* --- Transaction Completed --- */ + waitForFinalSuccess(policy, policyClMgt); + } + + /** + * Waits for a OPERATION SUCCESS transaction notification. + */ + protected void waitForOperationSuccess() { + policyClMgt.await(notif -> notif.getNotification() == ControlLoopNotificationType.OPERATION_SUCCESS); + } + + /** + * Waits for a FINAL SUCCESS transaction notification. + * + * @return the FINAL SUCCESS notification + */ + protected VirtualControlLoopNotification waitForFinalSuccess(ToscaPolicy policy, + Listener<VirtualControlLoopNotification> policyClMgt) { + + return this.waitForFinal(policy, policyClMgt, ControlLoopNotificationType.FINAL_SUCCESS); + } + + /** + * Waits for notifications for LOCK acquisition and GUARD Permit so that event + * processing may proceed. + */ + protected abstract void waitForLockAndPermit(ToscaPolicy policy, + Listener<VirtualControlLoopNotification> policyClMgt); + + /** + * Waits for a FINAL transaction notification. + * + * @param finalType FINAL_xxx type for which to wait + * + * @return the FINAL notification + */ + protected abstract VirtualControlLoopNotification waitForFinal(ToscaPolicy policy, + Listener<VirtualControlLoopNotification> policyClMgt, ControlLoopNotificationType finalType); +} diff --git a/controlloop/common/rules-test/src/main/java/org/onap/policy/controlloop/common/rules/test/HttpClients.java b/controlloop/common/rules-test/src/main/java/org/onap/policy/controlloop/common/rules/test/HttpClients.java new file mode 100644 index 000000000..eb729ad6d --- /dev/null +++ b/controlloop/common/rules-test/src/main/java/org/onap/policy/controlloop/common/rules/test/HttpClients.java @@ -0,0 +1,67 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.common.rules.test; + +import org.onap.policy.common.endpoints.http.client.HttpClientConfigException; +import org.onap.policy.common.endpoints.http.client.HttpClientFactory; +import org.onap.policy.common.endpoints.http.client.HttpClientFactoryInstance; +import org.onap.policy.drools.persistence.SystemPersistenceConstants; + +/** + * Mechanism by which junit tests can manage HTTP Clients. + */ +public class HttpClients { + + /** + * Constructs the object. + */ + public HttpClients() { + super(); + } + + + /** + * Adds Http Clients specified in the property file. + * + * @param propFilePrefix prefix prepended to "-http-client.properties" to yield the + * full name of the property file containing http client properties + */ + public void addClients(String propFilePrefix) { + try { + getClientFactory().build(SystemPersistenceConstants.getManager().getHttpClientProperties(propFilePrefix)); + } catch (HttpClientConfigException e) { + throw new IllegalArgumentException("cannot initialize HTTP clients", e); + } + } + + /** + * Destroys all Http Clients. + */ + public void destroy() { + getClientFactory().destroy(); + } + + // these methods may be overridden by junit tests + + protected HttpClientFactory getClientFactory() { + return HttpClientFactoryInstance.getClientFactory(); + } +} diff --git a/controlloop/common/rules-test/src/main/java/org/onap/policy/controlloop/common/rules/test/Listener.java b/controlloop/common/rules-test/src/main/java/org/onap/policy/controlloop/common/rules/test/Listener.java new file mode 100644 index 000000000..5042f54c6 --- /dev/null +++ b/controlloop/common/rules-test/src/main/java/org/onap/policy/controlloop/common/rules/test/Listener.java @@ -0,0 +1,180 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.common.rules.test; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.function.Function; +import java.util.function.Predicate; +import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure; +import org.onap.policy.common.endpoints.event.comm.TopicEndpoint; +import org.onap.policy.common.endpoints.event.comm.TopicEndpointManager; +import org.onap.policy.common.endpoints.event.comm.TopicListener; +import org.onap.policy.common.endpoints.event.comm.TopicSink; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Listener for messages published on a topic SINK. + * + * @param <T> message type + */ +public class Listener<T> implements TopicListener { + private static final Logger logger = LoggerFactory.getLogger(Listener.class); + public static long DEFAULT_WAIT_SEC = 5L; + + private final TopicSink sink; + private final Function<String, T> decoder; + private final BlockingQueue<T> messages = new LinkedBlockingQueue<>(); + + /** + * Constructs the object. + * + * @param topicName name of the NOOP topic SINK on which to listen + * @param decoder function that takes a topic name and a message and decodes it into + * the desired type + */ + public Listener(String topicName, Function<String, T> decoder) { + this.sink = getTopicManager().getNoopTopicSink(topicName); + this.decoder = decoder; + this.sink.register(this); + } + + /** + * Determines if there are any messages waiting. + * + * @return {@code true} if there are no messages waiting, {@code false} otherwise + */ + public boolean isEmpty() { + return messages.isEmpty(); + } + + /** + * Waits, for the default amount of time, for a message to be published to the topic. + * + * @return the message that was published + * @throws TopicException if interrupted or no message is received within the + * specified time + */ + public T await() { + return await(DEFAULT_WAIT_SEC, TimeUnit.SECONDS); + } + + /** + * Waits, for the specified period of time, for a message to be published to the + * topic. + * + * @param twait maximum time to wait + * @param unit time unit + * @return the message that was published + * @throws TopicException if interrupted or no message is received within the + * specified time + */ + public T await(long twait, TimeUnit unit) { + return await(twait, unit, msg -> true); + } + + /** + * Waits, for the default amount of time, for a message to be published to the topic. + * + * @param filter filter used to select the message of interest; preceding messages + * that do not pass the filter are discarded + * @return the message that was published + * @throws TopicException if interrupted or no message is received within the + * specified time + */ + public T await(Predicate<T> filter) { + return await(DEFAULT_WAIT_SEC, TimeUnit.SECONDS, filter); + } + + /** + * Waits, for the specified period of time, for a message to be published to the + * topic. + * + * @param twait maximum time to wait + * @param unit time unit + * @param filter filter used to select the message of interest; preceding messages + * that do not pass the filter are discarded + * @return the message that was published + * @throws TopicException if interrupted or no message is received within the + * specified time + */ + public T await(long twait, TimeUnit unit, Predicate<T> filter) { + long endMs = System.currentTimeMillis() + TimeUnit.MILLISECONDS.convert(twait, unit); + + for (;;) { + try { + long remainingMs = endMs - System.currentTimeMillis(); + if (remainingMs < 0) { + throw new TimeoutException(); + } + + T msg = pollMessage(remainingMs); + if (msg == null) { + throw new TimeoutException(); + } + + if (filter.test(msg)) { + return msg; + } + + logger.info("message discarded by the filter on topic {}", sink.getTopic()); + + } catch (InterruptedException e) { + logger.warn("'await' interrupted on topic {}", sink.getTopic()); + Thread.currentThread().interrupt(); + throw new TopicException(e); + + } catch (TimeoutException e) { + logger.warn("'await' timed out on topic {}", sink.getTopic()); + throw new TopicException(e); + } + } + } + + /** + * Unregisters the listener. + */ + public void unregister() { + sink.unregister(this); + } + + @Override + public void onTopicEvent(CommInfrastructure commType, String topic, String event) { + try { + messages.add(decoder.apply(event)); + } catch (RuntimeException e) { + logger.warn("cannot decode message on topic {} for event {}", topic, event, e); + } + } + + // these methods may be overridden by junit tests + + protected TopicEndpoint getTopicManager() { + return TopicEndpointManager.getManager(); + } + + protected T pollMessage(long remainingMs) throws InterruptedException { + return messages.poll(remainingMs, TimeUnit.MILLISECONDS); + } +} diff --git a/controlloop/common/rules-test/src/main/java/org/onap/policy/controlloop/common/rules/test/NamedRunner.java b/controlloop/common/rules-test/src/main/java/org/onap/policy/controlloop/common/rules/test/NamedRunner.java new file mode 100644 index 000000000..1cbe5a56a --- /dev/null +++ b/controlloop/common/rules-test/src/main/java/org/onap/policy/controlloop/common/rules/test/NamedRunner.java @@ -0,0 +1,85 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.common.rules.test; + +import org.junit.Ignore; +import org.junit.runner.Description; +import org.junit.runner.notification.RunNotifier; +import org.junit.runners.BlockJUnit4ClassRunner; +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.InitializationError; + +/** + * Runs tests listed via the {@link TestNames} annotation. + */ +public class NamedRunner extends BlockJUnit4ClassRunner { + + /** + * Constructs the object. + */ + public NamedRunner(Class<?> testClass) throws InitializationError { + super(testClass); + } + + @Override + protected void runChild(final FrameworkMethod method, RunNotifier notifier) { + Description description = describeChild(method); + + if (method.getAnnotation(Ignore.class) != null) { + notifier.fireTestIgnored(description); + + } else if (!isNamed(description.getTestClass(), method.getName())) { + notifier.fireTestIgnored(description); + + } else { + runLeaf(methodBlock(method), description, notifier); + } + } + + /** + * Determines if the test is in the list of tests to be included. + * + * @param testClass class under test + * @param testName name of the test of interest + * @return {@code true} if the test is in the list, {@code false} otherwise + */ + private boolean isNamed(Class<?> testClass, String testName) { + TestNames annot = testClass.getAnnotation(TestNames.class); + if (annot == null) { + // no annotation - everything passes + return true; + } + + for (String name : annot.names()) { + if (testName.equals(name)) { + return true; + } + } + + for (String prefix : annot.prefixes()) { + if (testName.startsWith(prefix)) { + return true; + } + } + + return false; + } +} diff --git a/controlloop/common/rules-test/src/main/java/org/onap/policy/controlloop/common/rules/test/Rules.java b/controlloop/common/rules-test/src/main/java/org/onap/policy/controlloop/common/rules/test/Rules.java new file mode 100644 index 000000000..7549c6be6 --- /dev/null +++ b/controlloop/common/rules-test/src/main/java/org/onap/policy/controlloop/common/rules/test/Rules.java @@ -0,0 +1,413 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.common.rules.test; + +import static org.awaitility.Awaitility.await; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.Properties; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import lombok.Getter; +import org.apache.commons.lang3.StringUtils; +import org.kie.api.event.rule.AfterMatchFiredEvent; +import org.kie.api.event.rule.BeforeMatchFiredEvent; +import org.kie.api.event.rule.DefaultAgendaEventListener; +import org.kie.api.event.rule.DefaultRuleRuntimeEventListener; +import org.kie.api.event.rule.MatchCancelledEvent; +import org.kie.api.event.rule.MatchCreatedEvent; +import org.kie.api.event.rule.ObjectDeletedEvent; +import org.kie.api.event.rule.ObjectInsertedEvent; +import org.kie.api.event.rule.ObjectUpdatedEvent; +import org.kie.api.event.rule.RuleRuntimeEventListener; +import org.kie.api.runtime.KieSession; +import org.onap.policy.common.utils.coder.CoderException; +import org.onap.policy.common.utils.coder.StandardCoder; +import org.onap.policy.common.utils.resources.ResourceUtils; +import org.onap.policy.controlloop.ControlLoopEvent; +import org.onap.policy.controlloop.drl.legacy.ControlLoopParams; +import org.onap.policy.controlloop.eventmanager.ControlLoopEventManager; +import org.onap.policy.controlloop.eventmanager.ControlLoopEventManager2; +import org.onap.policy.drools.controller.DroolsController; +import org.onap.policy.drools.persistence.SystemPersistence; +import org.onap.policy.drools.persistence.SystemPersistenceConstants; +import org.onap.policy.drools.system.PolicyController; +import org.onap.policy.drools.system.PolicyControllerConstants; +import org.onap.policy.drools.system.PolicyControllerFactory; +import org.onap.policy.drools.system.PolicyEngine; +import org.onap.policy.drools.system.PolicyEngineConstants; +import org.onap.policy.drools.util.KieUtils; +import org.onap.policy.drools.utils.logging.LoggerUtil; +import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy; +import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Mechanism by which junit tests can manage the rule engine. + */ +public class Rules { + private static final Logger logger = LoggerFactory.getLogger(Rules.class); + private static final StandardCoder coder = new StandardCoder(); + + /** + * PDP-D Engine. + */ + @Getter + private final PolicyEngine pdpd = makeEngine(); + + /** + * PDP-D Configuration Repository. + */ + @Getter + private final SystemPersistence pdpdRepo = makePdpdRepo(); + + + @Getter + private final String controllerName; + + @Getter + private PolicyController controller; + + + /** + * Constructs the object. + * + * @param controllerName name of the controller + */ + public Rules(String controllerName) { + this.controllerName = controllerName; + } + + /** + * Configures various items, including the PDP-D Engine. + * + * @param resourceDir path to resource directory + */ + public void configure(String resourceDir) { + pdpdRepo.setConfigurationDir("src/test/resources/config"); + + try { + File kmoduleFile = new File(resourceDir + "/META-INF/kmodule.xml"); + File pomFile = new File("src/test/resources/" + controllerName + ".pom"); + String resourceDir2 = resourceDir + "/org/onap/policy/controlloop/"; + File ruleFile = new File(resourceDir + "/" + controllerName + ".drl"); + List<File> ruleFiles = Collections.singletonList(ruleFile); + + installArtifact(kmoduleFile, pomFile, resourceDir2, ruleFiles); + } catch (IOException e) { + throw new IllegalArgumentException("cannot configure KIE session for " + controllerName, e); + } + + setupLogging(); + + pdpd.configure(new Properties()); + } + + /** + * Starts various items, including the PDP-D Engine. + */ + public void start() { + controller = pdpd.createPolicyController(controllerName, pdpdRepo.getControllerProperties(controllerName)); + pdpd.start(); + + setupDroolsLogging(); + } + + /** + * Stop PDP-D. + */ + public void destroy() { + getControllerFactory().shutdown(controllerName); + pdpd.stop(); + } + + /** + * Removes various facts from working memory, including the Policy and Params, as well + * as any event managers and events. + */ + public void resetFacts() { + List<Class<?>> classes = List.of(ToscaPolicy.class, ControlLoopParams.class, ControlLoopEventManager2.class, + ControlLoopEvent.class); + + // delete all objects of the listed classes + DroolsController drools = controller.getDrools(); + classes.forEach(drools::delete); + + // wait for them to be deleted + for (Class<?> clazz : classes) { + await(clazz.getSimpleName()).atMost(5, TimeUnit.SECONDS) + .until(() -> drools.facts(controllerName, clazz).isEmpty()); + } + + /* + * We can't delete this class directly; we have to wait for the rules to clean it + * up, because the rule also cleans up a number of other associated objects. + */ + Class<?> clazz = ControlLoopEventManager.class; + await(clazz.getSimpleName()).atMost(5, TimeUnit.SECONDS) + .until(() -> drools.facts(controllerName, clazz).isEmpty()); + } + + /** + * Installs a policy from policy/models (examples) repo. + */ + public ToscaPolicy setupPolicyFromTemplate(String templatePath, String policyName) { + try { + return setupPolicy(getPolicyFromTemplate(templatePath, policyName)); + + } catch (InterruptedException | CoderException e) { + throw new IllegalArgumentException("policy " + policyName, e); + } + } + + private ToscaPolicy getPolicyFromTemplate(String resourcePath, String policyName) throws CoderException { + String policyJson = ResourceUtils.getResourceAsString(resourcePath); + if (policyJson == null) { + throw new CoderException(new FileNotFoundException(resourcePath)); + } + + ToscaServiceTemplate serviceTemplate = coder.decode(policyJson, ToscaServiceTemplate.class); + ToscaPolicy policy = serviceTemplate.getToscaTopologyTemplate().getPolicies().get(0).get(policyName); + assertNotNull(policy); + + /* + * name and version are used within a drl. api component and drools core will + * ensure that these are populated. + */ + if (StringUtils.isBlank(policy.getName())) { + policy.setName(policyName); + } + + if (StringUtils.isBlank(policy.getVersion())) { + policy.setVersion(policy.getTypeVersion()); + } + + return serviceTemplate.getToscaTopologyTemplate().getPolicies().get(0).get(policyName); + } + + /** + * Installs a given policy. + */ + public ToscaPolicy setupPolicyFromFile(String policyPath) { + try { + return setupPolicy(getPolicyFromFile(policyPath)); + + } catch (InterruptedException | IOException | CoderException e) { + throw new IllegalArgumentException("policy " + policyPath, e); + } + } + + private ToscaPolicy getPolicyFromFile(String policyPath) throws IOException, CoderException { + String policyJson = ResourceUtils.getResourceAsString(policyPath); + if (policyJson == null) { + throw new CoderException(new FileNotFoundException(policyPath)); + } + + return coder.decode(policyJson, ToscaPolicy.class); + } + + private ToscaPolicy setupPolicy(ToscaPolicy policy) throws InterruptedException { + final KieObjectExpectedCallback<?> policyTracker = new KieObjectInsertedExpectedCallback<>(policy); + final KieObjectExpectedCallback<?> paramsTracker = + new KieClassInsertedExpectedCallback<>(ControlLoopParams.class); + + controller.getDrools().offer(policy); + + assertTrue(policyTracker.isNotified()); + assertTrue(paramsTracker.isNotified()); + + assertEquals(1, controller.getDrools().facts(controllerName, ToscaPolicy.class).stream() + .filter((anotherPolicy) -> anotherPolicy == policy).count()); + + assertEquals(1, controller.getDrools().facts(controllerName, ControlLoopParams.class).stream() + .filter((params) -> params.getToscaPolicy() == policy).count()); + return policy; + } + + /** + * Sets up overall logging. + */ + private void setupLogging() { + LoggerUtil.setLevel(LoggerUtil.ROOT_LOGGER, "WARN"); + LoggerUtil.setLevel("org.eclipse.jetty", "WARN"); + LoggerUtil.setLevel("org.onap.policy.controlloop", "INFO"); + LoggerUtil.setLevel("network", "INFO"); + } + + /** + * Sets up Drools Logging for events of interest. + */ + private void setupDroolsLogging() { + KieSession session = getKieSession(); + + session.addEventListener(new RuleListenerLogger()); + session.addEventListener(new AgendaListenerLogger()); + } + + /** + * Logs Modifications to Working Memory. + */ + private static class RuleListenerLogger implements RuleRuntimeEventListener { + @Override + public void objectInserted(ObjectInsertedEvent event) { + String ruleName = (event.getRule() != null) ? event.getRule().getName() : "null"; + logger.info("RULE {}: inserted {}", ruleName, event.getObject()); + } + + @Override + public void objectUpdated(ObjectUpdatedEvent event) { + String ruleName = (event.getRule() != null) ? event.getRule().getName() : "null"; + logger.info("RULE {}: updated {}", ruleName, event.getObject()); + + } + + @Override + public void objectDeleted(ObjectDeletedEvent event) { + String ruleName = (event.getRule() != null) ? event.getRule().getName() : "null"; + logger.info("RULE {}: deleted {}", ruleName, event.getOldObject()); + } + } + + /** + * Logs Rule Matches. + */ + private static class AgendaListenerLogger extends DefaultAgendaEventListener { + @Override + public void matchCreated(MatchCreatedEvent event) { + logger.info("RULE {}: match created", event.getMatch().getRule().getName()); + } + + @Override + public void matchCancelled(MatchCancelledEvent event) { + logger.info("RULE {}: match cancelled", event.getMatch().getRule().getName()); + } + + @Override + public void beforeMatchFired(BeforeMatchFiredEvent event) { + logger.info("RULE {}: before match fired", event.getMatch().getRule().getName()); + } + + @Override + public void afterMatchFired(AfterMatchFiredEvent event) { + logger.info("RULE {}: after match fired", event.getMatch().getRule().getName()); + } + } + + /** + * Base Class to track Working Memory updates for objects of type T. + */ + private abstract class KieObjectExpectedCallback<T> extends DefaultRuleRuntimeEventListener { + protected T subject; + + protected CountDownLatch countDownLatch = new CountDownLatch(1); + + public KieObjectExpectedCallback(T affected) { + subject = affected; + register(); + } + + public boolean isNotified() throws InterruptedException { + return countDownLatch.await(9L, TimeUnit.SECONDS); + } + + protected void callbacked() { + unregister(); + countDownLatch.countDown(); + } + + public KieObjectExpectedCallback<T> register() { + getKieSession().addEventListener(this); + return this; + } + + public KieObjectExpectedCallback<T> unregister() { + getKieSession().removeEventListener(this); + return this; + } + } + + /** + * Tracks inserts in Working Memory for an object of type T. + */ + private class KieObjectInsertedExpectedCallback<T> extends KieObjectExpectedCallback<T> { + public KieObjectInsertedExpectedCallback(T affected) { + super(affected); + } + + @Override + public void objectInserted(ObjectInsertedEvent event) { + if (subject == event.getObject()) { + callbacked(); + } + } + } + + /** + * Tracks inserts in Working Memory for any object of class T. + */ + private class KieClassInsertedExpectedCallback<T> extends KieObjectInsertedExpectedCallback<T> { + + public KieClassInsertedExpectedCallback(T affected) { + super(affected); + } + + public void objectInserted(ObjectInsertedEvent event) { + if (subject == event.getObject().getClass()) { + callbacked(); + } + } + } + + // these may be overridden by junit tests + + + protected PolicyEngine makeEngine() { + return PolicyEngineConstants.getManager(); + } + + + protected SystemPersistence makePdpdRepo() { + return SystemPersistenceConstants.getManager(); + } + + protected KieSession getKieSession() { + return getControllerFactory().get(controllerName).getDrools().getContainer().getPolicySession(controllerName) + .getKieSession(); + } + + protected PolicyControllerFactory getControllerFactory() { + return PolicyControllerConstants.getFactory(); + } + + protected void installArtifact(File kmoduleFile, File pomFile, String resourceDir, List<File> ruleFiles) + throws IOException { + + KieUtils.installArtifact(kmoduleFile, pomFile, resourceDir, ruleFiles); + } +} diff --git a/controlloop/common/rules-test/src/main/java/org/onap/policy/controlloop/common/rules/test/SimulatorException.java b/controlloop/common/rules-test/src/main/java/org/onap/policy/controlloop/common/rules/test/SimulatorException.java new file mode 100644 index 000000000..f9880d07a --- /dev/null +++ b/controlloop/common/rules-test/src/main/java/org/onap/policy/controlloop/common/rules/test/SimulatorException.java @@ -0,0 +1,44 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.common.rules.test; + +/** + * Exception thrown by <i>Simulators</i>. + */ +public class SimulatorException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public SimulatorException() { + super(); + } + + public SimulatorException(String message) { + super(message); + } + + public SimulatorException(Throwable cause) { + super(cause); + } + + public SimulatorException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/controlloop/common/rules-test/src/main/java/org/onap/policy/controlloop/common/rules/test/Simulators.java b/controlloop/common/rules-test/src/main/java/org/onap/policy/controlloop/common/rules/test/Simulators.java new file mode 100644 index 000000000..4a325a447 --- /dev/null +++ b/controlloop/common/rules-test/src/main/java/org/onap/policy/controlloop/common/rules/test/Simulators.java @@ -0,0 +1,85 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.common.rules.test; + +import java.util.LinkedList; +import java.util.List; +import lombok.AccessLevel; +import lombok.Getter; +import org.onap.policy.common.endpoints.http.server.HttpServletServer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Simulator container. + */ +public class Simulators { + private static final Logger logger = LoggerFactory.getLogger(Simulators.class); + + @Getter(AccessLevel.PROTECTED) + private final List<HttpServletServer> servers = new LinkedList<>(); + + /** + * Constructs the object. + */ + public Simulators() { + super(); + } + + /** + * Invokes the given functions to start the simulators. Destroys <i>all</i> of the + * simulators if any fail to start. + * + * @param builders functions to invoke to build the simulators + */ + public void start(SimulatorBuilder... builders) { + try { + for (SimulatorBuilder builder : builders) { + servers.add(builder.build()); + } + } catch (InterruptedException e) { + logger.warn("interrupted building the simulators"); + destroy(); + Thread.currentThread().interrupt(); + throw new SimulatorException(e); + } + } + + /** + * Stops all of the simulators. + */ + public void destroy() { + for (HttpServletServer server : servers) { + try { + server.shutdown(); + } catch (RuntimeException e) { + logger.warn("error stopping simulator {}", server.getName(), e); + } + } + + servers.clear(); + } + + @FunctionalInterface + public static interface SimulatorBuilder { + public HttpServletServer build() throws InterruptedException; + } +} diff --git a/controlloop/common/rules-test/src/main/java/org/onap/policy/controlloop/common/rules/test/TestNames.java b/controlloop/common/rules-test/src/main/java/org/onap/policy/controlloop/common/rules/test/TestNames.java new file mode 100644 index 000000000..5f3d856ef --- /dev/null +++ b/controlloop/common/rules-test/src/main/java/org/onap/policy/controlloop/common/rules/test/TestNames.java @@ -0,0 +1,44 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.common.rules.test; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation for a test CLASS. Used to identify the names of the test methods to be + * included. + */ +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface TestNames { + /** + * Test method names to be included. + */ + String[] names() default {}; + + /** + * Prefixes of test method names to be included. + */ + String[] prefixes() default {}; +} diff --git a/controlloop/common/rules-test/src/main/java/org/onap/policy/controlloop/common/rules/test/TopicException.java b/controlloop/common/rules-test/src/main/java/org/onap/policy/controlloop/common/rules/test/TopicException.java new file mode 100644 index 000000000..01a866966 --- /dev/null +++ b/controlloop/common/rules-test/src/main/java/org/onap/policy/controlloop/common/rules/test/TopicException.java @@ -0,0 +1,44 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.common.rules.test; + +/** + * Exception thrown by <i>Topics</i>. + */ +public class TopicException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public TopicException() { + super(); + } + + public TopicException(String message) { + super(message); + } + + public TopicException(Throwable cause) { + super(cause); + } + + public TopicException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/controlloop/common/rules-test/src/main/java/org/onap/policy/controlloop/common/rules/test/Topics.java b/controlloop/common/rules-test/src/main/java/org/onap/policy/controlloop/common/rules/test/Topics.java new file mode 100644 index 000000000..f71559acb --- /dev/null +++ b/controlloop/common/rules-test/src/main/java/org/onap/policy/controlloop/common/rules/test/Topics.java @@ -0,0 +1,176 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.common.rules.test; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; +import java.util.function.Function; +import org.onap.policy.common.endpoints.event.comm.TopicEndpoint; +import org.onap.policy.common.endpoints.event.comm.TopicEndpointManager; +import org.onap.policy.common.utils.coder.Coder; +import org.onap.policy.common.utils.coder.CoderException; +import org.onap.policy.common.utils.resources.ResourceUtils; +import org.onap.policy.drools.protocol.coders.EventProtocolCoder; +import org.onap.policy.drools.protocol.coders.EventProtocolCoderConstants; +import org.onap.policy.drools.system.PolicyController; + +/** + * Mechanism by which junit tests can manage topic messages. + */ +public class Topics { + /** + * Wherever this string appears within an input file, it is replaced by a value passed + * as a parameter to the {@link #inject(String, String, String)} method. + */ + private static final String REPLACE_ME = "${replaceMe}"; + + /** + * Listeners that have been created and registered by "this" object. + */ + private final List<Listener<?>> listeners = new LinkedList<>(); + + + /** + * Constructs the object. + */ + public Topics() { + super(); + } + + /** + * Unregisters all of the listeners. + */ + public void destroy() { + listeners.forEach(Listener::unregister); + } + + /** + * Injects the content of the given file onto a NOOP topic SOURCE. + * + * @param topicName topic on which to inject + * @param file file whose content is to be injected + */ + public void inject(String topicName, String file) { + inject(topicName, file, REPLACE_ME); + } + + /** + * Injects the content of the given file onto a NOOP topic SOURCE, with the given + * substitution. + * + * @param topicName topic on which to inject + * @param file file whose content is to be injected + * @param newText text to be substituted for occurrences of "${replaceMe}" in the + * source file + */ + public void inject(String topicName, String file, String newText) { + try { + String text = ResourceUtils.getResourceAsString(file); + if (text == null) { + throw new FileNotFoundException(file); + } + text = text.replace(REPLACE_ME, newText); + getTopicManager().getNoopTopicSource(topicName).offer(text); + } catch (IOException e) { + throw new TopicException(e); + } + } + + /** + * Creates a listener for messages published on a NOOP topic SINK. Messages are + * decoded using the coder associated with the controller. + * + * @param <T> message type + * @param topicName name of the topic on which to listen + * @param expectedClass type of message expected + * @param controller controller whose decoders are to be used + * @return a new listener + */ + public <T> Listener<T> createListener(String topicName, Class<T> expectedClass, PolicyController controller) { + EventProtocolCoder mgr = getProtocolCoder(); + String groupId = controller.getDrools().getGroupId(); + String artifactId = controller.getDrools().getArtifactId(); + + // @formatter:off + return createListener(topicName, + event -> expectedClass.cast(mgr.decode(groupId, artifactId, topicName, event))); + // @formatter:on + } + + /** + * Creates a listener for messages published on a NOOP topic SINK. Messages are + * decoded using the specified coder. + * + * @param <T> message type + * @param topicName name of the topic on which to listen + * @param expectedClass type of message expected + * @param coder coder to decode the messages + * @return a new listener + */ + public <T> Listener<T> createListener(String topicName, Class<T> expectedClass, Coder coder) { + Function<String, T> decoder = event -> { + try { + return coder.decode(event, expectedClass); + } catch (CoderException e) { + throw new IllegalArgumentException("cannot decode message", e); + } + }; + + return createListener(topicName, decoder); + } + + /** + * Creates a listener for messages published on a NOOP topic SINK. Messages are + * decoded using the specified decoder. + * + * @param <T> message type + * @param topicName name of the topic on which to listen + * @param decoder function that takes a message and decodes it into the desired type + * @return a new listener + */ + public <T> Listener<T> createListener(String topicName, Function<String, T> decoder) { + Listener<T> listener = makeListener(topicName, decoder); + listeners.add(listener); + + return listener; + } + + // these methods may be overridden by junit tests + + protected TopicEndpoint getTopicManager() { + return TopicEndpointManager.getManager(); + } + + protected EventProtocolCoder getProtocolCoder() { + return EventProtocolCoderConstants.getManager(); + } + + protected <T> Listener<T> makeListener(String topicName, Function<String, T> decoder) { + return new Listener<>(topicName, decoder) { + @Override + protected TopicEndpoint getTopicManager() { + return Topics.this.getTopicManager(); + } + }; + } +} diff --git a/controlloop/common/rules-test/src/main/resources/duplicates/duplicates.appc.success.json b/controlloop/common/rules-test/src/main/resources/duplicates/duplicates.appc.success.json new file mode 100644 index 000000000..331021798 --- /dev/null +++ b/controlloop/common/rules-test/src/main/resources/duplicates/duplicates.appc.success.json @@ -0,0 +1,22 @@ +{ + "body": { + "output": { + "common-header": { + "timestamp": "2017-08-25T21:06:23.037Z", + "api-ver": "5.00", + "originator-id": "664be3d2-6c12-4f4b-a3e7-c349acced200", + "request-id": "664be3d2-6c12-4f4b-a3e7-c349acced200", + "sub-request-id": "${replaceMe}", + "flags": {} + }, + "status": { + "code": 400, + "message": "Restart Successful" + } + } + }, + "version": "2.0", + "rpc-name": "restart", + "correlation-id": "664be3d2-6c12-4f4b-a3e7-c349acced200-1", + "type": "response" +} diff --git a/controlloop/common/rules-test/src/main/resources/duplicates/duplicates.onset.1.json b/controlloop/common/rules-test/src/main/resources/duplicates/duplicates.onset.1.json new file mode 100644 index 000000000..70f48442f --- /dev/null +++ b/controlloop/common/rules-test/src/main/resources/duplicates/duplicates.onset.1.json @@ -0,0 +1,16 @@ +{ + "closedLoopControlName": "ControlLoop-Duplicates-48f0c2c3-a172-4192-9ae3-052274181b6e", + "closedLoopAlarmStart": 1463679805324, + "closedLoopEventClient": "DCAE_INSTANCE_ID.dcae-tca", + "closedLoopEventStatus": "ONSET", + "requestID": "${replaceMe}", + "target_type": "VNF", + "target": "generic-vnf.vnf-id", + "AAI": { + "vserver.is-closed-loop-disabled": "false", + "vserver.prov-status": "ACTIVE", + "generic-vnf.vnf-id": "duplicate-VNF" + }, + "from": "DCAE", + "version": "1.0.2" +} diff --git a/controlloop/common/rules-test/src/main/resources/duplicates/duplicates.onset.2.json b/controlloop/common/rules-test/src/main/resources/duplicates/duplicates.onset.2.json new file mode 100644 index 000000000..046243609 --- /dev/null +++ b/controlloop/common/rules-test/src/main/resources/duplicates/duplicates.onset.2.json @@ -0,0 +1,16 @@ +{ + "closedLoopControlName": "ControlLoop-Duplicates-48f0c2c3-a172-4192-9ae3-052274181b6e", + "closedLoopAlarmStart": 1463679805324, + "closedLoopEventClient": "DCAE_INSTANCE_ID.dcae-tca", + "closedLoopEventStatus": "ONSET", + "requestID": "664be3d2-6c12-4f4b-a3e7-c349acced200", + "target_type": "VNF", + "target": "generic-vnf.vnf-id", + "AAI": { + "vserver.is-closed-loop-disabled": "false", + "vserver.prov-status": "ACTIVE", + "generic-vnf.vnf-id": "vCPE_Infrastructure_vGMUX_demo_app" + }, + "from": "DCAE", + "version": "1.0.2" +} diff --git a/controlloop/common/rules-test/src/main/resources/duplicates/tosca-compliant-duplicates.json b/controlloop/common/rules-test/src/main/resources/duplicates/tosca-compliant-duplicates.json new file mode 100644 index 000000000..d67472743 --- /dev/null +++ b/controlloop/common/rules-test/src/main/resources/duplicates/tosca-compliant-duplicates.json @@ -0,0 +1,37 @@ +{ + "type": "onap.policies.controlloop.operational.common.Drools", + "type_version": "1.0.0", + "version": "1.0.0", + "name": "operational.restart", + "metadata": { + "policy-id": "operational.restart" + }, + "properties": { + "controllerName": "usecases", + "id": "ControlLoop-Duplicates-48f0c2c3-a172-4192-9ae3-052274181b6e", + "timeout": 3600, + "abatement": false, + "trigger": "unique-policy-id-1-restart", + "operations": [ + { + "id": "unique-policy-id-1-restart", + "description": "Restart the VM", + "operation": { + "actor": "APPC", + "operation": "Restart", + "target": { + "targetType": "VM" + } + }, + "timeout": 1200, + "retries": 3, + "success": "final_success", + "failure": "final_failure", + "failure_timeout": "final_failure_timeout", + "failure_retries": "final_failure_retries", + "failure_exception": "final_failure_exception", + "failure_guard": "final_failure_guard" + } + ] + } +} diff --git a/controlloop/common/rules-test/src/main/resources/service123/service123.appc.migrate.success.json b/controlloop/common/rules-test/src/main/resources/service123/service123.appc.migrate.success.json new file mode 100644 index 000000000..563e2c762 --- /dev/null +++ b/controlloop/common/rules-test/src/main/resources/service123/service123.appc.migrate.success.json @@ -0,0 +1,22 @@ +{ + "body": { + "output": { + "common-header": { + "timestamp": "2017-08-25T21:06:23.037Z", + "api-ver": "5.00", + "originator-id": "664be3d2-6c12-4f4b-a3e7-c349acced200", + "request-id": "664be3d2-6c12-4f4b-a3e7-c349acced200", + "sub-request-id": "${replaceMe}", + "flags": {} + }, + "status": { + "code": 400, + "message": "Migrate Successful" + } + } + }, + "version": "2.0", + "rpc-name": "migrate", + "correlation-id": "664be3d2-6c12-4f4b-a3e7-c349acced200-1", + "type": "response" +} diff --git a/controlloop/common/rules-test/src/main/resources/service123/service123.appc.rebuild.failure.json b/controlloop/common/rules-test/src/main/resources/service123/service123.appc.rebuild.failure.json new file mode 100644 index 000000000..88e70ef7a --- /dev/null +++ b/controlloop/common/rules-test/src/main/resources/service123/service123.appc.rebuild.failure.json @@ -0,0 +1,22 @@ +{ + "body": { + "output": { + "common-header": { + "timestamp": "2017-08-25T21:06:23.037Z", + "api-ver": "5.00", + "originator-id": "664be3d2-6c12-4f4b-a3e7-c349acced200", + "request-id": "664be3d2-6c12-4f4b-a3e7-c349acced200", + "sub-request-id": "${replaceMe}", + "flags": {} + }, + "status": { + "code": 401, + "message": "Rebuild Failed" + } + } + }, + "version": "2.0", + "rpc-name": "rebuild", + "correlation-id": "664be3d2-6c12-4f4b-a3e7-c349acced200-1", + "type": "response" +} diff --git a/controlloop/common/rules-test/src/main/resources/service123/service123.appc.restart.failure.json b/controlloop/common/rules-test/src/main/resources/service123/service123.appc.restart.failure.json new file mode 100644 index 000000000..7376c8ad3 --- /dev/null +++ b/controlloop/common/rules-test/src/main/resources/service123/service123.appc.restart.failure.json @@ -0,0 +1,22 @@ +{ + "body": { + "output": { + "common-header": { + "timestamp": "2017-08-25T21:06:23.037Z", + "api-ver": "5.00", + "originator-id": "664be3d2-6c12-4f4b-a3e7-c349acced200", + "request-id": "664be3d2-6c12-4f4b-a3e7-c349acced200", + "sub-request-id": "${replaceMe}", + "flags": {} + }, + "status": { + "code": 401, + "message": "Restart Failed" + } + } + }, + "version": "2.0", + "rpc-name": "restart", + "correlation-id": "664be3d2-6c12-4f4b-a3e7-c349acced200-1", + "type": "response" +} diff --git a/controlloop/common/rules-test/src/main/resources/service123/service123.onset.json b/controlloop/common/rules-test/src/main/resources/service123/service123.onset.json new file mode 100644 index 000000000..a78cb5337 --- /dev/null +++ b/controlloop/common/rules-test/src/main/resources/service123/service123.onset.json @@ -0,0 +1,17 @@ +{ + "closedLoopControlName": "ControlLoop-Service123-cbed919f-2212-4ef7-8051-fe6308da1bda", + "closedLoopAlarmStart": 1463679805324, + "closedLoopEventClient": "DCAE_INSTANCE_ID.dcae-tca", + "closedLoopEventStatus": "ONSET", + "requestID": "664be3d2-6c12-4f4b-a3e7-c349acced200", + "target_type": "VNF", + "target": "generic-vnf.vnf-name", + "AAI": { + "vserver.is-closed-loop-disabled": "false", + "vserver.prov-status": "ACTIVE", + "generic-vnf.vnf-name": "fw0002vm002fw002", + "vserver.vserver-name": "OzVServer" + }, + "from": "DCAE", + "version": "1.0.2" +} diff --git a/controlloop/common/rules-test/src/main/resources/service123/tosca-compliant-service123.json b/controlloop/common/rules-test/src/main/resources/service123/tosca-compliant-service123.json new file mode 100644 index 000000000..4eaa9e70a --- /dev/null +++ b/controlloop/common/rules-test/src/main/resources/service123/tosca-compliant-service123.json @@ -0,0 +1,75 @@ +{ + "type": "onap.policies.controlloop.operational.common.Drools", + "type_version": "1.0.0", + "version": "1.0.0", + "name": "operational.service123", + "metadata": { + "policy-id": "operational.service123" + }, + "properties": { + "controllerName": "usecases", + "id": "ControlLoop-Service123-cbed919f-2212-4ef7-8051-fe6308da1bda", + "timeout": 60, + "abatement": true, + "trigger": "unique-policy-id-1-restart", + "operations": [ + { + "id": "unique-policy-id-1-restart", + "description": "Restart the VM", + "operation": { + "actor": "APPC", + "operation": "Restart", + "target": { + "targetType": "VM" + } + }, + "timeout": 20, + "retries": 3, + "success": "final_success", + "failure": "unique-policy-id-2-rebuild", + "failure_timeout": "unique-policy-id-2-rebuild", + "failure_retries": "unique-policy-id-2-rebuild", + "failure_guard": "unique-policy-id-2-rebuild", + "failure_exception": "final_failure_exception" + }, + { + "id": "unique-policy-id-2-rebuild", + "name": "Rebuild Policy", + "operation": { + "actor": "APPC", + "operation": "Rebuild", + "target": { + "targetType": "VM" + } + }, + "timeout": 10, + "retries": 0, + "success": "final_success", + "failure": "unique-policy-id-3-migrate", + "failure_timeout": "unique-policy-id-3-migrate", + "failure_retries": "unique-policy-id-3-migrate", + "failure_guard": "unique-policy-id-3-migrate", + "failure_exception": "final_failure_exception" + }, + { + "id": "unique-policy-id-3-migrate", + "name": "Migrate Policy", + "operation": { + "actor": "APPC", + "operation": "Migrate", + "target": { + "targetType": "VM" + } + }, + "timeout": 30, + "retries": 0, + "success": "final_success", + "failure": "final_failure", + "failure_timeout": "final_failure_timeout", + "failure_retries": "final_failure_retries", + "failure_guard": "final_failure_guard", + "failure_exception": "final_failure_exception" + } + ] + } +} diff --git a/controlloop/common/rules-test/src/main/resources/vcpe/tosca-compliant-vcpe.json b/controlloop/common/rules-test/src/main/resources/vcpe/tosca-compliant-vcpe.json new file mode 100644 index 000000000..b70145b68 --- /dev/null +++ b/controlloop/common/rules-test/src/main/resources/vcpe/tosca-compliant-vcpe.json @@ -0,0 +1,37 @@ +{ + "type": "onap.policies.controlloop.operational.common.Drools", + "type_version": "1.0.0", + "version": "1.0.0", + "name": "operational.restart", + "metadata": { + "policy-id": "operational.restart" + }, + "properties": { + "controllerName": "usecases", + "id": "ControlLoop-vCPE-48f0c2c3-a172-4192-9ae3-052274181b6e", + "timeout": 3600, + "abatement": false, + "trigger": "unique-policy-id-1-restart", + "operations": [ + { + "id": "unique-policy-id-1-restart", + "description": "Restart the VM", + "operation": { + "actor": "APPC", + "operation": "Restart", + "target": { + "targetType": "VM" + } + }, + "timeout": 1200, + "retries": 3, + "success": "final_success", + "failure": "final_failure", + "failure_timeout": "final_failure_timeout", + "failure_retries": "final_failure_retries", + "failure_exception": "final_failure_exception", + "failure_guard": "final_failure_guard" + } + ] + } +} diff --git a/controlloop/common/rules-test/src/main/resources/vcpe/tosca-legacy-vcpe.json b/controlloop/common/rules-test/src/main/resources/vcpe/tosca-legacy-vcpe.json new file mode 100644 index 000000000..cb0806e16 --- /dev/null +++ b/controlloop/common/rules-test/src/main/resources/vcpe/tosca-legacy-vcpe.json @@ -0,0 +1,9 @@ +{ + "type": "onap.policies.controlloop.Operational", + "type_version": "1.0.0", + "properties": { + "content": "controlLoop%3A%0A%20%20version%3A%202.0.0%0A%20%20controlLoopName%3A%20ControlLoop-vCPE-48f0c2c3-a172-4192-9ae3-052274181b6e%0A%20%20trigger_policy%3A%20unique-policy-id-1-restart%0A%20%20timeout%3A%203600%0A%20%20abatement%3A%20false%0A%20%0Apolicies%3A%0A%20%20-%20id%3A%20unique-policy-id-1-restart%0A%20%20%20%20name%3A%20Restart%20the%20VM%0A%20%20%20%20description%3A%0A%20%20%20%20actor%3A%20APPC%0A%20%20%20%20recipe%3A%20Restart%0A%20%20%20%20target%3A%0A%20%20%20%20%20%20type%3A%20VM%0A%20%20%20%20retry%3A%203%0A%20%20%20%20timeout%3A%201200%0A%20%20%20%20success%3A%20final_success%0A%20%20%20%20failure%3A%20final_failure%0A%20%20%20%20failure_timeout%3A%20final_failure_timeout%0A%20%20%20%20failure_retries%3A%20final_failure_retries%0A%20%20%20%20failure_exception%3A%20final_failure_exception%0A%20%20%20%20failure_guard%3A%20final_failure_guard" + }, + "name": "vcpe", + "version": "1.0.0" +} diff --git a/controlloop/common/rules-test/src/main/resources/vcpe/vcpe.appc.success.json b/controlloop/common/rules-test/src/main/resources/vcpe/vcpe.appc.success.json new file mode 100644 index 000000000..331021798 --- /dev/null +++ b/controlloop/common/rules-test/src/main/resources/vcpe/vcpe.appc.success.json @@ -0,0 +1,22 @@ +{ + "body": { + "output": { + "common-header": { + "timestamp": "2017-08-25T21:06:23.037Z", + "api-ver": "5.00", + "originator-id": "664be3d2-6c12-4f4b-a3e7-c349acced200", + "request-id": "664be3d2-6c12-4f4b-a3e7-c349acced200", + "sub-request-id": "${replaceMe}", + "flags": {} + }, + "status": { + "code": 400, + "message": "Restart Successful" + } + } + }, + "version": "2.0", + "rpc-name": "restart", + "correlation-id": "664be3d2-6c12-4f4b-a3e7-c349acced200-1", + "type": "response" +} diff --git a/controlloop/common/rules-test/src/main/resources/vcpe/vcpe.onset.1.json b/controlloop/common/rules-test/src/main/resources/vcpe/vcpe.onset.1.json new file mode 100644 index 000000000..d08ee47cd --- /dev/null +++ b/controlloop/common/rules-test/src/main/resources/vcpe/vcpe.onset.1.json @@ -0,0 +1,16 @@ +{ + "closedLoopControlName": "ControlLoop-vCPE-48f0c2c3-a172-4192-9ae3-052274181b6e", + "closedLoopAlarmStart": 1463679805324, + "closedLoopEventClient": "DCAE_INSTANCE_ID.dcae-tca", + "closedLoopEventStatus": "ONSET", + "requestID": "664be3d2-6c12-4f4b-a3e7-c349acced200", + "target_type": "VNF", + "target": "generic-vnf.vnf-id", + "AAI": { + "vserver.is-closed-loop-disabled": "false", + "vserver.prov-status": "ACTIVE", + "generic-vnf.vnf-id": "vCPE_Infrastructure_vGMUX_demo_app" + }, + "from": "DCAE", + "version": "1.0.2" +}
\ No newline at end of file diff --git a/controlloop/common/rules-test/src/main/resources/vcpe/vcpe.onset.2.json b/controlloop/common/rules-test/src/main/resources/vcpe/vcpe.onset.2.json new file mode 100644 index 000000000..b8c76514e --- /dev/null +++ b/controlloop/common/rules-test/src/main/resources/vcpe/vcpe.onset.2.json @@ -0,0 +1,16 @@ +{ + "closedLoopControlName": "ControlLoop-vCPE-48f0c2c3-a172-4192-9ae3-052274181b6e", + "closedLoopAlarmStart": 1463679805324, + "closedLoopEventClient": "DCAE_INSTANCE_ID.dcae-tca", + "closedLoopEventStatus": "ONSET", + "requestID": "8cf3cd05-1218-4224-931b-601494ffe55b", + "target_type": "VNF", + "target": "generic-vnf.vnf-id", + "AAI": { + "vserver.is-closed-loop-disabled": "false", + "vserver.prov-status": "ACTIVE", + "generic-vnf.vnf-id": "vCPE_Infrastructure_vGMUX_demo_app" + }, + "from": "DCAE", + "version": "1.0.2" +}
\ No newline at end of file diff --git a/controlloop/common/rules-test/src/main/resources/vcpe/vcpe.onset.3.json b/controlloop/common/rules-test/src/main/resources/vcpe/vcpe.onset.3.json new file mode 100644 index 000000000..40f29b3cf --- /dev/null +++ b/controlloop/common/rules-test/src/main/resources/vcpe/vcpe.onset.3.json @@ -0,0 +1,17 @@ +{ + "closedLoopControlName": "ControlLoop-vCPE-48f0c2c3-a172-4192-9ae3-052274181b6e", + "closedLoopAlarmStart": 1570722876324905, + "closedLoopAlarmEnd": 1570722876324999, + "closedLoopEventClient": "DCAE_INSTANCE_ID.dcae-tca", + "closedLoopEventStatus": "ONSET", + "requestID": "8cf3cd05-1218-4224-931b-601494ffe55b", + "target_type": "VNF", + "target": "generic-vnf.vnf-id", + "AAI": { + "vserver.is-closed-loop-disabled": "false", + "vserver.prov-status": "ACTIVE", + "generic-vnf.vnf-id": "vCPE_Infrastructure_vGMUX_demo_app" + }, + "from": "DCAE", + "version": "1.0.2" +}
\ No newline at end of file diff --git a/controlloop/common/rules-test/src/main/resources/vdns/tosca-compliant-vdns.json b/controlloop/common/rules-test/src/main/resources/vdns/tosca-compliant-vdns.json new file mode 100644 index 000000000..918e92770 --- /dev/null +++ b/controlloop/common/rules-test/src/main/resources/vdns/tosca-compliant-vdns.json @@ -0,0 +1,48 @@ +{ + "type": "onap.policies.controlloop.operational.common.Drools", + "type_version": "1.0.0", + "version": "1.0.0", + "name": "operational.scale.up", + "metadata": { + "policy-id": "operational.scale.up" + }, + "properties": { + "controllerName": "usecases", + "id": "ControlLoop-vDNS-6f37f56d-a87d-4b85-b6a9-cc953cf779b3", + "timeout": 60, + "abatement": false, + "trigger": "unique-policy-id-1-scale-up", + "operations": [ + { + "id": "unique-policy-id-1-scale-up", + "description": "Scale up", + "operation": { + "actor": "SO", + "operation": "VF Module Create", + "target": { + "targetType": "VFMODULE", + "entityIds": { + "modelInvariantId": "e6130d03-56f1-4b0a-9a1d-e1b2ebc30e0e", + "modelVersionId": "94b18b1d-cc91-4f43-911a-e6348665f292", + "modelName": "VfwclVfwsnkBbefb8ce2bde..base_vfw..module-0", + "modelVersion": 1, + "modelCustomizationId": "47958575-138f-452a-8c8d-d89b595f8164" + } + }, + "payload": { + "requestParameters": "{\"usePreload\":true,\"userParams\":[]}", + "configurationParameters": "[{\"ip-addr\":\"$.vf-module-topology.vf-module-parameters.param[9]\",\"oam-ip-addr\":\"$.vf-module-topology.vf-module-parameters.param[16]\",\"enabled\":\"$.vf-module-topology.vf-module-parameters.param[23]\"}]" + } + }, + "timeout": 30, + "retries": 0, + "success": "final_success", + "failure": "final_failure", + "failure_timeout": "final_failure_timeout", + "failure_retries": "final_failure_retries", + "failure_exception": "final_failure_exception", + "failure_guard": "final_failure_guard" + } + ] + } +} diff --git a/controlloop/common/rules-test/src/main/resources/vdns/vdns.onset.json b/controlloop/common/rules-test/src/main/resources/vdns/vdns.onset.json new file mode 100644 index 000000000..13f690952 --- /dev/null +++ b/controlloop/common/rules-test/src/main/resources/vdns/vdns.onset.json @@ -0,0 +1,16 @@ +{ + "closedLoopControlName": "ControlLoop-vDNS-6f37f56d-a87d-4b85-b6a9-cc953cf779b3", + "closedLoopAlarmStart": 1463679805324, + "closedLoopEventClient": "microservice.stringmatcher", + "closedLoopEventStatus": "ONSET", + "requestID": "c7c6a4aa-bb61-4a15-b831-ba1472dd4a65", + "target_type": "VNF", + "target": "vserver.vserver-name", + "AAI": { + "vserver.is-closed-loop-disabled": "false", + "vserver.prov-status": "ACTIVE", + "vserver.vserver-name": "OzVServer" + }, + "from": "DCAE", + "version": "1.0.2" +} diff --git a/controlloop/common/rules-test/src/main/resources/vfw/tosca-compliant-vfw.json b/controlloop/common/rules-test/src/main/resources/vfw/tosca-compliant-vfw.json new file mode 100644 index 000000000..f55145658 --- /dev/null +++ b/controlloop/common/rules-test/src/main/resources/vfw/tosca-compliant-vfw.json @@ -0,0 +1,40 @@ +{ + "type": "onap.policies.controlloop.operational.common.Drools", + "type_version": "1.0.0", + "name": "operational.modifyconfig", + "version": "1.0.0", + "metadata": { + "policy-id": "operational.modifyconfig" + }, + "properties": { + "controllerName": "usecases", + "id": "ControlLoop-vFirewall-d0a1dfc6-94f5-4fd4-a5b5-4630b438850a", + "timeout": 60, + "abatement": false, + "trigger": "unique-policy-id-1-modifyConfig", + "operations": [ + { + "id": "unique-policy-id-1-modifyConfig", + "description": "Modify the packet generator", + "operation": { + "actor": "APPC", + "operation": "ModifyConfig", + "target": { + "targetType": "VNF", + "entityIds": { + "resourceID": "bbb3cefd-01c8-413c-9bdd-2b92f9ca3d38" + } + } + }, + "timeout": 300, + "retries": 0, + "success": "final_success", + "failure": "final_failure", + "failure_timeout": "final_failure_timeout", + "failure_retries": "final_failure_retries", + "failure_exception": "final_failure_exception", + "failure_guard": "final_failure_guard" + } + ] + } +} diff --git a/controlloop/common/rules-test/src/main/resources/vfw/tosca-vfw.json b/controlloop/common/rules-test/src/main/resources/vfw/tosca-vfw.json new file mode 100644 index 000000000..35a839698 --- /dev/null +++ b/controlloop/common/rules-test/src/main/resources/vfw/tosca-vfw.json @@ -0,0 +1,9 @@ +{ + "type": "onap.policies.controlloop.Operational", + "type_version": "1.0.0", + "properties": { + "content": "controlLoop%3A%0A%20%20version%3A%202.0.0%0A%20%20controlLoopName%3A%20ControlLoop-vFirewall-d0a1dfc6-94f5-4fd4-a5b5-4630b438850a%0A%20%20services%3A%0A%20%20%20%20-%20serviceInvariantUUID%3A%20f6937c86-584c-47ae-ad29-8d41d6f0cc7c%0A%20%20%20%20%20%20serviceUUID%3A%207be584e2-0bb2-4126-adaf-ced2c77ca0b3%0A%20%20%20%20%20%20serviceName%3A%20Service_Ete_Name7ba1fbde-6187-464a-a62d-d9dd25bdf4e8%0A%20%20trigger_policy%3A%20unique-policy-id-1-modifyConfig%0A%20%20timeout%3A%2060%0A%20%20abatement%3A%20false%0A%20%0Apolicies%3A%0A%20%20-%20id%3A%20unique-policy-id-1-modifyConfig%0A%20%20%20%20name%3A%20modify%20packet%20gen%20config%0A%20%20%20%20description%3A%0A%20%20%20%20actor%3A%20APPC%0A%20%20%20%20recipe%3A%20ModifyConfig%0A%20%20%20%20target%3A%0A%20%20%20%20%20%20resourceID%3A%20bbb3cefd-01c8-413c-9bdd-2b92f9ca3d38%0A%20%20%20%20%20%20type%3A%20VNF%0A%20%20%20%20retry%3A%200%0A%20%20%20%20timeout%3A%2030%0A%20%20%20%20success%3A%20final_success%0A%20%20%20%20failure%3A%20final_failure%0A%20%20%20%20failure_timeout%3A%20final_failure_timeout%0A%20%20%20%20failure_retries%3A%20final_failure_retries%0A%20%20%20%20failure_exception%3A%20final_failure_exception%0A%20%20%20%20failure_guard%3A%20final_failure_guard" + }, + "name": "vfw", + "version": "1.0.0" +} diff --git a/controlloop/common/rules-test/src/main/resources/vfw/vfw.appc.success.json b/controlloop/common/rules-test/src/main/resources/vfw/vfw.appc.success.json new file mode 100644 index 000000000..eb19c6832 --- /dev/null +++ b/controlloop/common/rules-test/src/main/resources/vfw/vfw.appc.success.json @@ -0,0 +1,17 @@ +{ + "CommonHeader": { + "TimeStamp": 1506051879001, + "APIver": "1.01", + "RequestID": "c7c6a4aa-bb61-4a15-b831-ba1472dd4a65", + "SubRequestID": "${replaceMe}", + "RequestTrack": [], + "Flags": [] + }, + "Status": { + "Code": 400, + "Value": "SUCCESS" + }, + "Payload": { + "generic-vnf.vnf-id": "jimmy-test-vnf2" + } +} diff --git a/controlloop/common/rules-test/src/main/resources/vfw/vfw.onset.json b/controlloop/common/rules-test/src/main/resources/vfw/vfw.onset.json new file mode 100644 index 000000000..7782867a1 --- /dev/null +++ b/controlloop/common/rules-test/src/main/resources/vfw/vfw.onset.json @@ -0,0 +1,17 @@ +{ + "closedLoopControlName": "ControlLoop-vFirewall-d0a1dfc6-94f5-4fd4-a5b5-4630b438850a", + "closedLoopAlarmStart": 1463679805324, + "closedLoopEventClient": "microservice.stringmatcher", + "closedLoopEventStatus": "ONSET", + "requestID": "c7c6a4aa-bb61-4a15-b831-ba1472dd4a65", + "target_type": "VNF", + "target": "generic-vnf.vnf-name", + "AAI": { + "vserver.is-closed-loop-disabled": "false", + "vserver.prov-status": "ACTIVE", + "generic-vnf.vnf-name": "fw0002vm002fw002", + "vserver.vserver-name": "OzVServer" + }, + "from": "DCAE", + "version": "1.0.2" +} diff --git a/controlloop/common/rules-test/src/main/resources/vlb/tosca-compliant-vlb.json b/controlloop/common/rules-test/src/main/resources/vlb/tosca-compliant-vlb.json new file mode 100644 index 000000000..8a3b64a0a --- /dev/null +++ b/controlloop/common/rules-test/src/main/resources/vlb/tosca-compliant-vlb.json @@ -0,0 +1,48 @@ +{ + "type": "onap.policies.controlloop.operational.common.Drools", + "type_version": "1.0.0", + "name": "operational.scaleout", + "version": "1.0.0", + "metadata": { + "policy-id": "operational.scaleout" + }, + "properties": { + "controllerName": "usecases", + "id": "ControlLoop-vDNS-6f37f56d-a87d-4b85-b6a9-cc953cf779b3", + "timeout": 1200, + "abatement": false, + "trigger": "unique-policy-id-1-scale-up", + "operations": [ + { + "id": "unique-policy-id-1-scale-up", + "description": "Create a new VF Module", + "operation": { + "actor": "SO", + "operation": "VF Module Create", + "target": { + "targetType": "VFMODULE", + "entityIds": { + "modelInvariantId": "e6130d03-56f1-4b0a-9a1d-e1b2ebc30e0e", + "modelVersionId": "94b18b1d-cc91-4f43-911a-e6348665f292", + "modelName": "VfwclVfwsnkBbefb8ce2bde..base_vfw..module-0", + "modelVersion": 1, + "modelCustomizationId": "47958575-138f-452a-8c8d-d89b595f8164" + } + }, + "payload": { + "requestParameters": "{\"usePreload\":true,\"userParams\":[]}", + "configurationParameters": "[{\"ip-addr\":\"$.vf-module-topology.vf-module-parameters.param[9]\",\"oam-ip-addr\":\"$.vf-module-topology.vf-module-parameters.param[16]\",\"enabled\":\"$.vf-module-topology.vf-module-parameters.param[23]\"}]" + } + }, + "timeout": 1200, + "retries": 0, + "success": "final_success", + "failure": "final_failure", + "failure_timeout": "final_failure_timeout", + "failure_retries": "final_failure_retries", + "failure_exception": "final_failure_exception", + "failure_guard": "final_failure_guard" + } + ] + } +} diff --git a/controlloop/common/rules-test/src/main/resources/vlb/tosca-vlb.json b/controlloop/common/rules-test/src/main/resources/vlb/tosca-vlb.json new file mode 100644 index 000000000..c4f2b8179 --- /dev/null +++ b/controlloop/common/rules-test/src/main/resources/vlb/tosca-vlb.json @@ -0,0 +1,9 @@ +{ + "type": "onap.policies.controlloop.Operational", + "type_version": "1.0.0", + "properties": { + "content": "controlLoop%3A%0A%20%20version%3A%202.0.0%0A%20%20controlLoopName%3A%20ControlLoop-vDNS-6f37f56d-a87d-4b85-b6a9-cc953cf779b3%0A%20%20services%3A%0A%20%20%20%20-%20serviceName%3A%20d4738992-6497-4dca-9db9%0A%20%20%20%20%20%20serviceInvariantUUID%3A%20dc112d6e-7e73-4777-9c6f-1a7fb5fd1b6f%0A%20%20%20%20%20%20serviceUUID%3A%202eea06c6-e1d3-4c3a-b9c4-478c506eeedf%0A%20%20trigger_policy%3A%20unique-policy-id-1-scale-up%0A%20%20timeout%3A%2060%0A%0Apolicies%3A%0A%20%20-%20id%3A%20unique-policy-id-1-scale-up%0A%20%20%20%20name%3A%20Create%20a%20new%20VF%20Module%0A%20%20%20%20description%3A%0A%20%20%20%20actor%3A%20SO%0A%20%20%20%20recipe%3A%20VF%20Module%20Create%0A%20%20%20%20target%3A%0A%20%20%20%20%20%20type%3A%20VFMODULE%0A%20%20%20%20%20%20modelInvariantId%3A%20e6130d03-56f1-4b0a-9a1d-e1b2ebc30e0e%0A%20%20%20%20%20%20modelVersionId%3A%2094b18b1d-cc91-4f43-911a-e6348665f292%0A%20%20%20%20%20%20modelName%3A%20VfwclVfwsnkBbefb8ce2bde..base_vfw..module-0%0A%20%20%20%20%20%20modelVersion%3A%201%0A%20%20%20%20%20%20modelCustomizationId%3A%2047958575-138f-452a-8c8d-d89b595f8164%0A%20%20%20%20payload%3A%0A%20%20%20%20%20%20requestParameters%3A%20%27%7B%22usePreload%22%3Atrue%2C%22userParams%22%3A%5B%5D%7D%27%0A%20%20%20%20%20%20configurationParameters%3A%20%27%5B%7B%22ip-addr%22%3A%22%24.vf-module-topology.vf-module-parameters.param%5B9%5D%22%2C%22oam-ip-addr%22%3A%22%24.vf-module-topology.vf-module-parameters.param%5B16%5D%22%2C%22enabled%22%3A%22%24.vf-module-topology.vf-module-parameters.param%5B23%5D%22%7D%5D%27%0A%20%20%20%20retry%3A%200%0A%20%20%20%20timeout%3A%2030%0A%20%20%20%20success%3A%20final_success%0A%20%20%20%20failure%3A%20final_failure%0A%20%20%20%20failure_timeout%3A%20final_failure_timeout%0A%20%20%20%20failure_retries%3A%20final_failure_retries%0A%20%20%20%20failure_exception%3A%20final_failure_exception%0A%20%20%20%20failure_guard%3A%20final_failure_guard%0A" + }, + "name": "vlb", + "version": "1.0.0" +} diff --git a/controlloop/common/rules-test/src/main/resources/vlb/vlb.onset.json b/controlloop/common/rules-test/src/main/resources/vlb/vlb.onset.json new file mode 100644 index 000000000..23ad03cbb --- /dev/null +++ b/controlloop/common/rules-test/src/main/resources/vlb/vlb.onset.json @@ -0,0 +1,16 @@ +{ + "closedLoopControlName": "ControlLoop-vDNS-6f37f56d-a87d-4b85-b6a9-cc953cf779b3", + "closedLoopAlarmStart": 1484677482204798, + "closedLoopEventClient": "DCAE_INSTANCE_ID.dcae-tca", + "closedLoopEventStatus": "ONSET", + "requestID": "e4f95e0c-a013-4530-8e59-c5c5f9e539b6", + "target_type": "VNF", + "target": "vserver.vserver-name", + "AAI": { + "vserver.is-closed-loop-disabled": "false", + "vserver.prov-status": "ACTIVE", + "vserver.vserver-name": "OzVServer" + }, + "from": "DCAE", + "version": "1.0.2" +} diff --git a/controlloop/common/rules-test/src/test/java/org/onap/policy/controlloop/common/rules/test/BaseRuleTestTest.java b/controlloop/common/rules-test/src/test/java/org/onap/policy/controlloop/common/rules/test/BaseRuleTestTest.java new file mode 100644 index 000000000..745013b3b --- /dev/null +++ b/controlloop/common/rules-test/src/test/java/org/onap/policy/controlloop/common/rules/test/BaseRuleTestTest.java @@ -0,0 +1,459 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.common.rules.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.LinkedList; +import java.util.Map; +import java.util.Queue; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.onap.policy.appc.CommonHeader; +import org.onap.policy.appc.Request; +import org.onap.policy.appclcm.AppcLcmBody; +import org.onap.policy.appclcm.AppcLcmCommonHeader; +import org.onap.policy.appclcm.AppcLcmDmaapWrapper; +import org.onap.policy.appclcm.AppcLcmInput; +import org.onap.policy.common.utils.coder.StandardCoder; +import org.onap.policy.common.utils.coder.StandardCoderInstantAsMillis; +import org.onap.policy.controlloop.ControlLoopNotificationType; +import org.onap.policy.controlloop.VirtualControlLoopNotification; +import org.onap.policy.drools.controller.DroolsController; +import org.onap.policy.drools.system.PolicyController; +import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy; +import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyIdentifier; +import org.powermock.reflect.Whitebox; + +public class BaseRuleTestTest { + private static final String CONTROLLER_NAME = "my-controller-name"; + private static final String POLICY_NAME = "my-policy-name"; + + // saved values + private static Function<String, Rules> ruleMaker; + private static Supplier<HttpClients> httpClientMaker; + private static Supplier<Simulators> simMaker; + private static Supplier<Topics> topicMaker; + + private BaseRuleTest base; + private LinkedList<VirtualControlLoopNotification> clMgtQueue; + private Queue<AppcLcmDmaapWrapper> appcLcmQueue; + private Queue<Request> appcLegacyQueue; + private int permitCount; + private int finalCount; + + @Mock + private PolicyController controller; + @Mock + private Rules rules; + @Mock + private HttpClients httpClients; + @Mock + private Simulators simulators; + @Mock + private Topics topics; + @Mock + private Listener<VirtualControlLoopNotification> policyClMgt; + @Mock + private Listener<Request> appcClSink; + @Mock + private Listener<AppcLcmDmaapWrapper> appcLcmRead; + @Mock + private DroolsController drools; + @Mock + private ToscaPolicy policy; + @Mock + private ToscaPolicyIdentifier policyIdent; + + + /** + * Saves static values from the class. + */ + @BeforeClass + public static void setUpBeforeClass() { + ruleMaker = Whitebox.getInternalState(BaseRuleTest.class, "ruleMaker"); + httpClientMaker = Whitebox.getInternalState(BaseRuleTest.class, "httpClientMaker"); + simMaker = Whitebox.getInternalState(BaseRuleTest.class, "simMaker"); + topicMaker = Whitebox.getInternalState(BaseRuleTest.class, "topicMaker"); + } + + /** + * Restores static values. + */ + @AfterClass + public static void tearDownAfterClass() { + Whitebox.setInternalState(BaseRuleTest.class, "ruleMaker", ruleMaker); + Whitebox.setInternalState(BaseRuleTest.class, "httpClientMaker", httpClientMaker); + Whitebox.setInternalState(BaseRuleTest.class, "simMaker", simMaker); + Whitebox.setInternalState(BaseRuleTest.class, "topicMaker", topicMaker); + } + + /** + * Sets up. + */ + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + when(policy.getIdentifier()).thenReturn(policyIdent); + when(policyIdent.getName()).thenReturn(POLICY_NAME); + + when(drools.factCount(CONTROLLER_NAME)).thenReturn(0L); + when(controller.getDrools()).thenReturn(drools); + + when(rules.getControllerName()).thenReturn(CONTROLLER_NAME); + when(rules.getController()).thenReturn(controller); + when(rules.setupPolicyFromFile(any())).thenAnswer(args -> { + when(drools.factCount(CONTROLLER_NAME)).thenReturn(2L); + return policy; + }); + + when(topics.createListener(BaseRuleTest.POLICY_CL_MGT_TOPIC, VirtualControlLoopNotification.class, controller)) + .thenReturn(policyClMgt); + when(topics.createListener(eq(BaseRuleTest.APPC_LCM_READ_TOPIC), eq(AppcLcmDmaapWrapper.class), + any(StandardCoder.class))).thenReturn(appcLcmRead); + when(topics.createListener(eq(BaseRuleTest.APPC_CL_TOPIC), eq(Request.class), + any(StandardCoderInstantAsMillis.class))).thenReturn(appcClSink); + + Function<String, Rules> ruleMaker = this::makeRules; + Supplier<HttpClients> httpClientMaker = this::makeHttpClients; + Supplier<Simulators> simMaker = this::makeSim; + Supplier<Topics> topicMaker = this::makeTopics; + + Whitebox.setInternalState(BaseRuleTest.class, "ruleMaker", ruleMaker); + Whitebox.setInternalState(BaseRuleTest.class, "httpClientMaker", httpClientMaker); + Whitebox.setInternalState(BaseRuleTest.class, "simMaker", simMaker); + Whitebox.setInternalState(BaseRuleTest.class, "topicMaker", topicMaker); + + clMgtQueue = new LinkedList<>(); + appcLcmQueue = new LinkedList<>(); + appcLegacyQueue = new LinkedList<>(); + + when(policyClMgt.await(any())).thenAnswer(args -> { + VirtualControlLoopNotification notif = clMgtQueue.remove(); + Predicate<VirtualControlLoopNotification> pred = args.getArgument(0); + assertTrue(pred.test(notif)); + return notif; + }); + + when(appcLcmRead.await(any())).thenAnswer(args -> { + AppcLcmDmaapWrapper req = appcLcmQueue.remove(); + Predicate<AppcLcmDmaapWrapper> pred = args.getArgument(0); + assertTrue(pred.test(req)); + return req; + }); + + when(appcClSink.await(any())).thenAnswer(args -> { + Request req = appcLegacyQueue.remove(); + Predicate<Request> pred = args.getArgument(0); + assertTrue(pred.test(req)); + return req; + }); + + permitCount = 0; + finalCount = 0; + + base = new MyTest(); + + BaseRuleTest.initStatics(CONTROLLER_NAME); + base.init(); + } + + @Test + public void testInitStatics() { + assertSame(rules, BaseRuleTest.rules); + assertSame(httpClients, BaseRuleTest.httpClients); + assertSame(simulators, BaseRuleTest.simulators); + } + + @Test + public void testFinishStatics() { + BaseRuleTest.finishStatics(); + + verify(rules).destroy(); + verify(httpClients).destroy(); + verify(simulators).destroy(); + } + + @Test + public void testInit() { + assertSame(topics, base.getTopics()); + assertSame(controller, base.controller); + } + + @Test + public void testFinish() { + base.finish(); + + verify(topics).destroy(); + verify(rules).resetFacts(); + } + + @Test + public void testTestService123Compliant() { + enqueueAppcLcm("restart", "restart", "restart", "restart", "rebuild", "migrate"); + enqueueClMgt(ControlLoopNotificationType.OPERATION_SUCCESS); + enqueueClMgt(ControlLoopNotificationType.FINAL_SUCCESS); + + base.testService123Compliant(); + + assertEquals(1, permitCount); + assertEquals(1, finalCount); + + assertTrue(appcLcmQueue.isEmpty()); + assertTrue(clMgtQueue.isEmpty()); + + // initial event + verify(topics).inject(eq(BaseRuleTest.DCAE_TOPIC), any()); + + // replies to each APPC request + verify(topics, times(6)).inject(eq(BaseRuleTest.APPC_LCM_WRITE_TOPIC), any(), any()); + } + + @Test + public void testTestDuplicatesEvents() { + enqueueAppcLcm("restart", "restart"); + enqueueClMgt(ControlLoopNotificationType.FINAL_FAILURE); + enqueueClMgt(ControlLoopNotificationType.FINAL_SUCCESS); + enqueueClMgt(ControlLoopNotificationType.FINAL_SUCCESS); + + clMgtQueue.get(1).setAai(Map.of("generic-vnf.vnf-id", "duplicate-VNF")); + clMgtQueue.get(2).setAai(Map.of("generic-vnf.vnf-id", "vCPE_Infrastructure_vGMUX_demo_app")); + + base.testDuplicatesEvents(); + + assertEquals(0, permitCount); + assertEquals(3, finalCount); + + assertTrue(appcLcmQueue.isEmpty()); + assertTrue(clMgtQueue.isEmpty()); + + // initial events + verify(topics).inject(eq(BaseRuleTest.DCAE_TOPIC), any()); + verify(topics, times(2)).inject(eq(BaseRuleTest.DCAE_TOPIC), any(), any()); + + // two restarts + verify(topics, times(2)).inject(eq(BaseRuleTest.APPC_LCM_WRITE_TOPIC), any(), any()); + } + + @Test + public void testTestVcpeSunnyDayLegacy() { + checkAppcLcmPolicy("restart", base::testVcpeSunnyDayLegacy); + } + + @Test + public void testTestVcpeSunnyDayCompliant() { + checkAppcLcmPolicy("restart", base::testVcpeSunnyDayCompliant); + } + + @Test + public void testTestVcpeOnsetFloodPrevention() { + enqueueAppcLcm("restart"); + enqueueClMgt(ControlLoopNotificationType.OPERATION_SUCCESS); + enqueueClMgt(ControlLoopNotificationType.FINAL_SUCCESS); + + base.testVcpeOnsetFloodPrevention(); + + assertEquals(1, permitCount); + assertEquals(1, finalCount); + + assertTrue(appcLcmQueue.isEmpty()); + assertTrue(clMgtQueue.isEmpty()); + + // initial events + verify(topics, times(3)).inject(eq(BaseRuleTest.DCAE_TOPIC), any()); + + // one restart + verify(topics).inject(eq(BaseRuleTest.APPC_LCM_WRITE_TOPIC), any(), any()); + } + + @Test + public void testTestVdnsSunnyDayCompliant() { + checkHttpPolicy(base::testVdnsSunnyDayCompliant); + } + + @Test + public void testTestVfwSunnyDayLegacy() { + checkAppcLegacyPolicy("ModifyConfig", base::testVfwSunnyDayLegacy); + } + + @Test + public void testTestVfwSunnyDayCompliant() { + checkAppcLegacyPolicy("ModifyConfig", base::testVfwSunnyDayCompliant); + } + + @Test + public void testTestVlbSunnyDayLegacy() { + checkHttpPolicy(base::testVlbSunnyDayLegacy); + } + + @Test + public void testTestVlbSunnyDayCompliant() { + checkHttpPolicy(base::testVlbSunnyDayCompliant); + } + + protected void checkAppcLcmPolicy(String operation, Runnable test) { + enqueueAppcLcm(operation); + enqueueClMgt(ControlLoopNotificationType.OPERATION_SUCCESS); + enqueueClMgt(ControlLoopNotificationType.FINAL_SUCCESS); + + test.run(); + + assertEquals(1, permitCount); + assertEquals(1, finalCount); + + assertTrue(appcLcmQueue.isEmpty()); + assertTrue(clMgtQueue.isEmpty()); + + // initial event + verify(topics).inject(eq(BaseRuleTest.DCAE_TOPIC), any()); + + // reply to each APPC request + verify(topics).inject(eq(BaseRuleTest.APPC_LCM_WRITE_TOPIC), any(), any()); + } + + protected void checkAppcLegacyPolicy(String operation, Runnable test) { + enqueueAppcLegacy(operation); + enqueueClMgt(ControlLoopNotificationType.OPERATION_SUCCESS); + enqueueClMgt(ControlLoopNotificationType.FINAL_SUCCESS); + + test.run(); + + assertEquals(1, permitCount); + assertEquals(1, finalCount); + + assertTrue(appcLcmQueue.isEmpty()); + assertTrue(clMgtQueue.isEmpty()); + + // initial event + verify(topics).inject(eq(BaseRuleTest.DCAE_TOPIC), any()); + + // reply to each APPC request + verify(topics).inject(eq(BaseRuleTest.APPC_CL_TOPIC), any(), any()); + } + + protected void checkHttpPolicy(Runnable test) { + enqueueClMgt(ControlLoopNotificationType.OPERATION_SUCCESS); + enqueueClMgt(ControlLoopNotificationType.FINAL_SUCCESS); + + test.run(); + + assertEquals(1, permitCount); + assertEquals(1, finalCount); + + assertTrue(clMgtQueue.isEmpty()); + + // initial event + verify(topics).inject(eq(BaseRuleTest.DCAE_TOPIC), any()); + } + + private void enqueueClMgt(ControlLoopNotificationType type) { + VirtualControlLoopNotification notif = new VirtualControlLoopNotification(); + notif.setNotification(type); + notif.setPolicyName(POLICY_NAME + ".EVENT.MANAGER.FINAL"); + + clMgtQueue.add(notif); + } + + private void enqueueAppcLcm(String... operationNames) { + for (String oper : operationNames) { + AppcLcmDmaapWrapper req = new AppcLcmDmaapWrapper(); + req.setRpcName(oper); + + AppcLcmBody body = new AppcLcmBody(); + req.setBody(body); + + AppcLcmInput input = new AppcLcmInput(); + body.setInput(input); + + AppcLcmCommonHeader header = new AppcLcmCommonHeader(); + input.setCommonHeader(header); + + header.setSubRequestId("my-subrequest-id"); + + appcLcmQueue.add(req); + } + } + + private void enqueueAppcLegacy(String... operationNames) { + for (String oper : operationNames) { + Request req = new Request(); + req.setAction(oper); + + CommonHeader header = new CommonHeader(); + req.setCommonHeader(header); + + header.setSubRequestId("my-subrequest-id"); + + appcLegacyQueue.add(req); + } + } + + private Rules makeRules(String controllerName) { + return rules; + } + + private HttpClients makeHttpClients() { + return httpClients; + } + + private Simulators makeSim() { + return simulators; + } + + private Topics makeTopics() { + return topics; + } + + /* + * We don't want junit trying to run this, so it's marked "Ignore". + */ + @Ignore + private class MyTest extends BaseRuleTest { + + @Override + protected void waitForLockAndPermit(ToscaPolicy policy, Listener<VirtualControlLoopNotification> policyClMgt) { + permitCount++; + } + + @Override + protected VirtualControlLoopNotification waitForFinal(ToscaPolicy policy, + Listener<VirtualControlLoopNotification> policyClMgt, ControlLoopNotificationType finalType) { + finalCount++; + return policyClMgt.await(notif -> notif.getNotification() == finalType); + } + } +} diff --git a/controlloop/common/rules-test/src/test/java/org/onap/policy/controlloop/common/rules/test/ExceptionsTest.java b/controlloop/common/rules-test/src/test/java/org/onap/policy/controlloop/common/rules/test/ExceptionsTest.java new file mode 100644 index 000000000..a42ff6f5e --- /dev/null +++ b/controlloop/common/rules-test/src/test/java/org/onap/policy/controlloop/common/rules/test/ExceptionsTest.java @@ -0,0 +1,38 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.common.rules.test; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.onap.policy.common.utils.test.ExceptionsTester; +import org.onap.policy.controlloop.common.rules.test.SimulatorException; +import org.onap.policy.controlloop.common.rules.test.TopicException; + +public class ExceptionsTest { + + @Test + public void test() { + assertEquals(4, new ExceptionsTester().testAllException(TopicException.class)); + + assertEquals(4, new ExceptionsTester().testAllException(SimulatorException.class)); + } +} diff --git a/controlloop/common/rules-test/src/test/java/org/onap/policy/controlloop/common/rules/test/HttpClientsTest.java b/controlloop/common/rules-test/src/test/java/org/onap/policy/controlloop/common/rules/test/HttpClientsTest.java new file mode 100644 index 000000000..5db835343 --- /dev/null +++ b/controlloop/common/rules-test/src/test/java/org/onap/policy/controlloop/common/rules/test/HttpClientsTest.java @@ -0,0 +1,94 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.common.rules.test; + +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.junit.Assert.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import java.util.Properties; +import org.junit.After; +import org.junit.BeforeClass; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.onap.policy.common.endpoints.http.client.HttpClient; +import org.onap.policy.common.endpoints.http.client.HttpClientConfigException; +import org.onap.policy.common.endpoints.http.client.HttpClientFactory; +import org.onap.policy.common.endpoints.http.client.HttpClientFactoryInstance; +import org.onap.policy.controlloop.common.rules.test.HttpClients; +import org.onap.policy.drools.persistence.SystemPersistenceConstants; + +public class HttpClientsTest { + private static final String CLIENT_NAME = "MY-CLIENT"; + + @Mock + private HttpClientFactory factory; + + + @BeforeClass + public static void setUpBeforeClass() { + SystemPersistenceConstants.getManager().setConfigurationDir("src/test/resources"); + } + + @After + public void tearDown() { + HttpClientFactoryInstance.getClientFactory().destroy(); + } + + @Test + public void test() throws HttpClientConfigException { + MockitoAnnotations.initMocks(this); + + HttpClientFactoryInstance.getClientFactory().destroy(); + + HttpClients clients = new HttpClients(); + + clients.addClients("my"); + + // should find the client now + HttpClient client = HttpClientFactoryInstance.getClientFactory().get(CLIENT_NAME); + assertNotNull(client); + + clients.destroy(); + + // destroyed - should NOT find the client + assertThatIllegalArgumentException() + .isThrownBy(() -> HttpClientFactoryInstance.getClientFactory().get(CLIENT_NAME)); + + // unknown property file + assertThatIllegalArgumentException().isThrownBy(() -> clients.addClients("unknown")); + + // force exception from builder + HttpClients clients2 = new HttpClients() { + @Override + protected HttpClientFactory getClientFactory() { + return factory; + } + }; + + when(factory.build(any(Properties.class))).thenThrow(new HttpClientConfigException("expected exception")); + + assertThatIllegalArgumentException().isThrownBy(() -> clients2.addClients("my")) + .withMessage("cannot initialize HTTP clients"); + } +} diff --git a/controlloop/common/rules-test/src/test/java/org/onap/policy/controlloop/common/rules/test/ListenerTest.java b/controlloop/common/rules-test/src/test/java/org/onap/policy/controlloop/common/rules/test/ListenerTest.java new file mode 100644 index 000000000..07ccc405b --- /dev/null +++ b/controlloop/common/rules-test/src/test/java/org/onap/policy/controlloop/common/rules/test/ListenerTest.java @@ -0,0 +1,202 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.common.rules.test; + +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure; +import org.onap.policy.common.endpoints.event.comm.TopicEndpoint; +import org.onap.policy.common.endpoints.event.comm.TopicEndpointManager; +import org.onap.policy.common.endpoints.event.comm.bus.NoopTopicSink; +import org.onap.policy.common.endpoints.parameters.TopicParameters; +import org.onap.policy.controlloop.common.rules.test.Listener; +import org.onap.policy.controlloop.common.rules.test.TopicException; + +public class ListenerTest { + private static final String EXPECTED_EXCEPTION = "expected exception"; + private static final String MY_TOPIC = "my-topic"; + private static final String MESSAGE = "the-message"; + private static final String MESSAGE2 = "other-message"; + private static final String MSG_SUFFIX = "s"; + private static final String DECODED_MESSAGE = MESSAGE + MSG_SUFFIX; + + @Mock + private NoopTopicSink sink; + @Mock + private TopicEndpoint mgr; + + private Listener<String> listener; + + /** + * Creates topics. + */ + @BeforeClass + public static void setUpBeforeClass() { + TopicEndpointManager.getManager().shutdown(); + + TopicParameters params = new TopicParameters(); + params.setTopic(MY_TOPIC); + params.setManaged(true); + params.setTopicCommInfrastructure("NOOP"); + + TopicEndpointManager.getManager().addTopicSinks(List.of(params)); + } + + @AfterClass + public static void tearDownAfterClass() { + TopicEndpointManager.getManager().shutdown(); + } + + /** + * Sets up. + */ + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + when(mgr.getNoopTopicSink(MY_TOPIC)).thenReturn(sink); + + listener = new Listener<>(MY_TOPIC, msg -> msg + MSG_SUFFIX) { + @Override + protected TopicEndpoint getTopicManager() { + return mgr; + } + }; + } + + @Test + public void testListener() { + verify(sink).register(listener); + } + + @Test + public void testAwait_testAwaitLongTimeUnit_testIsEmpty() { + assertTrue(listener.isEmpty()); + + listener.onTopicEvent(CommInfrastructure.NOOP, MY_TOPIC, MESSAGE); + assertFalse(listener.isEmpty()); + + assertEquals(DECODED_MESSAGE, listener.await()); + + assertTrue(listener.isEmpty()); + } + + @Test + public void testAwaitPredicateOfT() { + listener.onTopicEvent(CommInfrastructure.NOOP, MY_TOPIC, MESSAGE); + listener.onTopicEvent(CommInfrastructure.NOOP, MY_TOPIC, MESSAGE2); + assertEquals(MESSAGE2 + MSG_SUFFIX, listener.await(msg -> msg.startsWith("other-"))); + } + + /** + * Tests await() when the remaining time is negative. + */ + @Test + public void testAwaitLongTimeUnitPredicateNoTime() { + assertThatThrownBy(() -> listener.await(-1, TimeUnit.SECONDS)).isInstanceOf(TopicException.class); + } + + /** + * Tests await() when the poll() returns {@code null}. + */ + @Test + public void testAwaitLongTimeUnitPredicateNoMessage() { + assertThatThrownBy(() -> listener.await(1, TimeUnit.MILLISECONDS)).isInstanceOf(TopicException.class); + } + + /** + * Tests await() when the poll() is interrupted. + */ + @Test + public void testAwaitLongTimeUnitPredicateInterrupted() throws InterruptedException { + listener = new Listener<String>(MY_TOPIC, msg -> msg) { + @Override + protected String pollMessage(long remainingMs) throws InterruptedException { + throw new InterruptedException(EXPECTED_EXCEPTION); + } + }; + + AtomicReference<TopicException> exref = new AtomicReference<>(); + CountDownLatch interrupted = new CountDownLatch(1); + + Thread thread = new Thread() { + @Override + public void run() { + try { + listener.await(); + } catch (TopicException e) { + exref.set(e); + } + + if (Thread.currentThread().isInterrupted()) { + interrupted.countDown(); + } + } + }; + + thread.start(); + assertTrue(interrupted.await(5, TimeUnit.SECONDS)); + assertNotNull(exref.get()); + } + + @Test + public void testUnregister() { + listener.unregister(); + verify(sink).unregister(listener); + } + + @Test + public void testOnTopicEvent() { + listener = new Listener<>(MY_TOPIC, msg -> { + throw new IllegalArgumentException(EXPECTED_EXCEPTION); + }); + + // onTopicEvent() should not throw an exception + assertThatCode(() -> listener.onTopicEvent(CommInfrastructure.NOOP, MY_TOPIC, MESSAGE)) + .doesNotThrowAnyException(); + + // should not have queued a message + assertTrue(listener.isEmpty()); + } + + @Test + public void testGetTopicManager() { + // use a listener with a real manager + assertNotNull(new Listener<>(MY_TOPIC, msg -> msg).getTopicManager()); + } +} diff --git a/controlloop/common/rules-test/src/test/java/org/onap/policy/controlloop/common/rules/test/NamedRunnerTest.java b/controlloop/common/rules-test/src/test/java/org/onap/policy/controlloop/common/rules/test/NamedRunnerTest.java new file mode 100644 index 000000000..fe9ff8083 --- /dev/null +++ b/controlloop/common/rules-test/src/test/java/org/onap/policy/controlloop/common/rules/test/NamedRunnerTest.java @@ -0,0 +1,87 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.common.rules.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import org.junit.AfterClass; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; + + +/** + * Tests NamedRunner. The tests don't do much, because all we really want to check is + * which tests are executed based on the {@link TestNames} annotation. + */ +@RunWith(NamedRunner.class) +@TestNames(names = {"testAbc", "testDef", "testIgnore"}, prefixes = {"testGhi", "testJkl"}) +public class NamedRunnerTest { + + private static int testCount = 0; + + @AfterClass + public static void tearDownAfterClass() { + assertEquals(5, testCount); + } + + @Test + public void testAbc() { + checkTest(); + } + + @Test + public void testAbc2() { + fail("should not run"); + } + + @Test + public void testDef() { + checkTest(); + } + + @Test + @Ignore + public void testIgnore() { + fail("should not run"); + } + + @Test + public void testGhi1() { + checkTest(); + } + + @Test + public void testGhi2() { + checkTest(); + } + + @Test + public void testJkl() { + checkTest(); + } + + + private static void checkTest() { + ++testCount; + } +} diff --git a/controlloop/common/rules-test/src/test/java/org/onap/policy/controlloop/common/rules/test/NamedRunnerTest2.java b/controlloop/common/rules-test/src/test/java/org/onap/policy/controlloop/common/rules/test/NamedRunnerTest2.java new file mode 100644 index 000000000..1ed5b20bc --- /dev/null +++ b/controlloop/common/rules-test/src/test/java/org/onap/policy/controlloop/common/rules/test/NamedRunnerTest2.java @@ -0,0 +1,58 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.common.rules.test; + +import static org.junit.Assert.assertEquals; + +import org.junit.AfterClass; +import org.junit.Test; +import org.junit.runner.RunWith; + + +/** + * Tests NamedRunner when the TestNames annotation is missing - all tests should be + * executed. + */ +@RunWith(NamedRunner.class) +public class NamedRunnerTest2 { + + private static int testCount = 0; + + @AfterClass + public static void tearDownAfterClass() { + assertEquals(2, testCount); + } + + @Test + public void testAbc() { + checkTest(); + } + + @Test + public void testDef() { + checkTest(); + } + + + private static void checkTest() { + ++testCount; + } +} diff --git a/controlloop/common/rules-test/src/test/java/org/onap/policy/controlloop/common/rules/test/RulesTest.java b/controlloop/common/rules-test/src/test/java/org/onap/policy/controlloop/common/rules/test/RulesTest.java new file mode 100644 index 000000000..28cb977fc --- /dev/null +++ b/controlloop/common/rules-test/src/test/java/org/onap/policy/controlloop/common/rules/test/RulesTest.java @@ -0,0 +1,341 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.common.rules.test; + +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.util.LinkedList; +import java.util.List; +import java.util.Properties; +import java.util.stream.Collectors; +import org.junit.Before; +import org.junit.Test; +import org.kie.api.definition.rule.Rule; +import org.kie.api.event.rule.AfterMatchFiredEvent; +import org.kie.api.event.rule.AgendaEventListener; +import org.kie.api.event.rule.BeforeMatchFiredEvent; +import org.kie.api.event.rule.MatchCancelledEvent; +import org.kie.api.event.rule.MatchCreatedEvent; +import org.kie.api.event.rule.ObjectDeletedEvent; +import org.kie.api.event.rule.ObjectInsertedEvent; +import org.kie.api.event.rule.ObjectUpdatedEvent; +import org.kie.api.event.rule.RuleRuntimeEventListener; +import org.kie.api.runtime.KieSession; +import org.kie.api.runtime.rule.Match; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.onap.policy.controlloop.ControlLoopEvent; +import org.onap.policy.controlloop.drl.legacy.ControlLoopParams; +import org.onap.policy.controlloop.eventmanager.ControlLoopEventManager2; +import org.onap.policy.drools.controller.DroolsController; +import org.onap.policy.drools.persistence.SystemPersistence; +import org.onap.policy.drools.system.PolicyController; +import org.onap.policy.drools.system.PolicyControllerFactory; +import org.onap.policy.drools.system.PolicyEngine; +import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy; + +public class RulesTest { + private static final String CONTROLLER_NAME = "rulesTest"; + private static final String POLICY_FILE = "src/test/resources/tosca-policy.json"; + private static final String MY_POLICY = "operational.restart"; + private static final String RESOURCE_DIR = "src/test/resources"; + private static final String MY_RULE_NAME = "my-rule-name"; + private static final String MY_TEXT = "my text"; + + @Mock + private PolicyEngine engine; + @Mock + private SystemPersistence repo; + @Mock + private PolicyController controller; + @Mock + private KieSession kieSession; + @Mock + private PolicyControllerFactory controllerFactory; + @Mock + private DroolsController drools; + + private List<Object> facts; + private List<RuleRuntimeEventListener> ruleListeners; + private List<AgendaEventListener> agendaListeners; + private Properties properties; + private boolean installed; + + private Rules rules; + + /** + * Sets up. + */ + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + facts = new LinkedList<>(); + ruleListeners = new LinkedList<>(); + agendaListeners = new LinkedList<>(); + installed = false; + properties = new Properties(); + + when(engine.createPolicyController(any(), any())).thenReturn(controller); + when(repo.getControllerProperties(CONTROLLER_NAME)).thenReturn(properties); + when(controller.getDrools()).thenReturn(drools); + + when(drools.facts(eq(CONTROLLER_NAME), any())).thenAnswer(args -> { + Class<?> clazz = args.getArgument(1); + return facts.stream().filter(obj -> obj.getClass() == clazz).collect(Collectors.toList()); + }); + + // notify listeners when objects are added to drools + when(drools.offer(any())).thenAnswer(args -> { + Object object = args.getArgument(0); + notifyInserted(object); + + if (!(object instanceof ToscaPolicy)) { + return true; + } + + // "insert" Params objects associated with the policy (i.e., mimic the rules) + ToscaPolicy policy = (ToscaPolicy) object; + ControlLoopParams params = new ControlLoopParams(); + params.setToscaPolicy(policy); + notifyInserted(params); + + return true; + }); + + when(drools.delete(any())).thenAnswer(args -> { + Class<?> clazz = args.getArgument(0); + facts.removeIf(obj -> obj.getClass() == clazz); + return null; + }); + + // handle rule listener registration and deregistration with the kieSession + doAnswer(args -> { + ruleListeners.add(args.getArgument(0)); + return null; + }).when(kieSession).addEventListener(any(RuleRuntimeEventListener.class)); + + doAnswer(args -> { + ruleListeners.remove(args.getArgument(0)); + return null; + }).when(kieSession).removeEventListener(any(RuleRuntimeEventListener.class)); + + // handle agenda listener registration and deregistration with the kieSession + doAnswer(args -> { + agendaListeners.add(args.getArgument(0)); + return null; + }).when(kieSession).addEventListener(any(AgendaEventListener.class)); + + doAnswer(args -> { + agendaListeners.remove(args.getArgument(0)); + return null; + }).when(kieSession).removeEventListener(any(AgendaEventListener.class)); + + rules = new MyRules(); + + rules.configure(RESOURCE_DIR); + rules.start(); + } + + @Test + public void testRules() { + assertEquals(CONTROLLER_NAME, rules.getControllerName()); + + assertSame(engine, rules.getPdpd()); + assertSame(repo, rules.getPdpdRepo()); + assertSame(controller, rules.getController()); + } + + @Test + public void testStart() throws Exception { + verify(repo).setConfigurationDir("src/test/resources/config"); + assertTrue(installed); + verify(engine).configure(any(Properties.class)); + verify(engine).createPolicyController(CONTROLLER_NAME, properties); + verify(engine).start(); + + verify(kieSession).addEventListener(any(RuleRuntimeEventListener.class)); + verify(kieSession).addEventListener(any(AgendaEventListener.class)); + } + + @Test + public void testDestroy() { + rules.destroy(); + + verify(controllerFactory).shutdown(CONTROLLER_NAME); + verify(engine).stop(); + } + + @Test + public void testResetFacts() { + rules.resetFacts(); + + verify(drools).delete(ToscaPolicy.class); + verify(drools).delete(ControlLoopParams.class); + verify(drools).delete(ControlLoopEventManager2.class); + verify(drools).delete(ControlLoopEvent.class); + } + + @Test + public void testSetupPolicyFromTemplate_testGetPolicyFromTemplate() { + rules.setupPolicyFromTemplate("tosca-template.json", MY_POLICY); + + assertThatIllegalArgumentException() + .isThrownBy(() -> rules.setupPolicyFromTemplate("missing-file.json", "a-policy")); + } + + @Test + public void testSetupPolicyFromFile_testGetPolicyFromFile_testSetupPolicy() { + assertNotNull(rules.setupPolicyFromFile(POLICY_FILE)); + + assertThatIllegalArgumentException().isThrownBy(() -> rules.setupPolicyFromFile("missing-file.json")); + } + + @Test + public void testRuleListenerLogger() { + Rule rule = mock(Rule.class); + when(rule.getName()).thenReturn(MY_RULE_NAME); + + // insertions - with and without rule name + ObjectInsertedEvent insert = mock(ObjectInsertedEvent.class); + when(insert.getObject()).thenReturn(MY_TEXT); + ruleListeners.forEach(listener -> listener.objectInserted(insert)); + when(insert.getRule()).thenReturn(rule); + ruleListeners.forEach(listener -> listener.objectInserted(insert)); + + // updates - with and without rule name + ObjectUpdatedEvent update = mock(ObjectUpdatedEvent.class); + when(update.getObject()).thenReturn(MY_TEXT); + ruleListeners.forEach(listener -> listener.objectUpdated(update)); + when(update.getRule()).thenReturn(rule); + ruleListeners.forEach(listener -> listener.objectUpdated(update)); + + // deletions - with and without rule name + ObjectDeletedEvent delete = mock(ObjectDeletedEvent.class); + when(delete.getOldObject()).thenReturn(MY_TEXT); + ruleListeners.forEach(listener -> listener.objectDeleted(delete)); + when(delete.getRule()).thenReturn(rule); + ruleListeners.forEach(listener -> listener.objectDeleted(delete)); + } + + @Test + public void testAgendaListenerLogger() { + Rule rule = mock(Rule.class); + when(rule.getName()).thenReturn(MY_RULE_NAME); + + Match match = mock(Match.class); + when(match.getRule()).thenReturn(rule); + + // create + MatchCreatedEvent create = mock(MatchCreatedEvent.class); + when(create.getMatch()).thenReturn(match); + agendaListeners.forEach(listener -> listener.matchCreated(create)); + + // cancel + MatchCancelledEvent cancel = mock(MatchCancelledEvent.class); + when(cancel.getMatch()).thenReturn(match); + agendaListeners.forEach(listener -> listener.matchCancelled(cancel)); + + // before-fire + BeforeMatchFiredEvent before = mock(BeforeMatchFiredEvent.class); + when(before.getMatch()).thenReturn(match); + agendaListeners.forEach(listener -> listener.beforeMatchFired(before)); + + // after-fire + AfterMatchFiredEvent after = mock(AfterMatchFiredEvent.class); + when(after.getMatch()).thenReturn(match); + agendaListeners.forEach(listener -> listener.afterMatchFired(after)); + } + + @Test + public void testMakePdpd_testMakePdpdRepo() { + // need rules that makes real objects + rules = new Rules(CONTROLLER_NAME); + + assertNotNull(rules.getPdpd()); + assertNotNull(rules.getPdpdRepo()); + } + + protected void notifyInserted(Object object) { + // add it to our list + facts.add(object); + + // increase code coverage by adding random objects + ObjectInsertedEvent event0 = mock(ObjectInsertedEvent.class); + when(event0.getObject()).thenReturn(new Object()); + ruleListeners.forEach(listener -> listener.objectInserted(event0)); + + // increase code coverage by adding a random object + ObjectInsertedEvent event = mock(ObjectInsertedEvent.class); + when(event.getObject()).thenReturn(object); + + if (object instanceof ToscaPolicy) { + // increase code coverage by associating it with a random rule + Rule rule = mock(Rule.class); + when(rule.getName()).thenReturn(MY_RULE_NAME); + when(event.getRule()).thenReturn(rule); + } + + ruleListeners.forEach(listener -> listener.objectInserted(event)); + } + + private class MyRules extends Rules { + public MyRules() { + super(CONTROLLER_NAME); + } + + @Override + protected PolicyEngine makeEngine() { + return engine; + } + + @Override + protected SystemPersistence makePdpdRepo() { + return repo; + } + + @Override + protected KieSession getKieSession() { + return kieSession; + } + + @Override + protected PolicyControllerFactory getControllerFactory() { + return controllerFactory; + } + + @Override + protected void installArtifact(File kmoduleFile, File pomFile, String resourceDir, List<File> ruleFiles) { + installed = true; + } + } +} diff --git a/controlloop/common/rules-test/src/test/java/org/onap/policy/controlloop/common/rules/test/SimulatorsTest.java b/controlloop/common/rules-test/src/test/java/org/onap/policy/controlloop/common/rules/test/SimulatorsTest.java new file mode 100644 index 000000000..8a87c4f26 --- /dev/null +++ b/controlloop/common/rules-test/src/test/java/org/onap/policy/controlloop/common/rules/test/SimulatorsTest.java @@ -0,0 +1,105 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.common.rules.test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import java.util.List; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.onap.policy.common.endpoints.http.server.HttpServletServer; +import org.onap.policy.controlloop.common.rules.test.SimulatorException; +import org.onap.policy.controlloop.common.rules.test.Simulators; +import org.onap.policy.controlloop.common.rules.test.Simulators.SimulatorBuilder; + +public class SimulatorsTest { + private static final String EXPECTED_EXCEPTION = "expected exception"; + + @Mock + private HttpServletServer server1; + @Mock + private HttpServletServer server2; + @Mock + private HttpServletServer server3; + + private Simulators simulators; + + /** + * Sets up. + */ + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + simulators = new Simulators(); + } + + @Test + public void testStart() { + simulators.start(() -> server1, () -> server2); + assertEquals(List.of(server1, server2), simulators.getServers()); + + verify(server1, never()).shutdown(); + verify(server2, never()).shutdown(); + } + + /** + * Tests start() when one of the builders throws an exception. + */ + @Test + public void testStartException() { + SimulatorBuilder exbuilder = () -> { + throw new InterruptedException(EXPECTED_EXCEPTION); + }; + + assertThatThrownBy(() -> simulators.start(() -> server1, () -> server2, exbuilder, () -> server3)) + .isInstanceOf(SimulatorException.class); + + assertTrue(simulators.getServers().isEmpty()); + + verify(server1).shutdown(); + verify(server2).shutdown(); + + // shouldn't have reached this builder, so nothing to shut down + verify(server3, never()).shutdown(); + } + + @Test + public void testDestroy() { + simulators.start(() -> server1, () -> server2, () -> server3); + + doThrow(new IllegalStateException(EXPECTED_EXCEPTION)).when(server2).shutdown(); + + simulators.destroy(); + + verify(server1).shutdown(); + verify(server3).shutdown(); + + assertTrue(simulators.getServers().isEmpty()); + } +} diff --git a/controlloop/common/rules-test/src/test/java/org/onap/policy/controlloop/common/rules/test/TopicsTest.java b/controlloop/common/rules-test/src/test/java/org/onap/policy/controlloop/common/rules/test/TopicsTest.java new file mode 100644 index 000000000..6ed8c3828 --- /dev/null +++ b/controlloop/common/rules-test/src/test/java/org/onap/policy/controlloop/common/rules/test/TopicsTest.java @@ -0,0 +1,230 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.common.rules.test; + +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; +import java.util.concurrent.TimeUnit; +import lombok.ToString; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure; +import org.onap.policy.common.endpoints.event.comm.TopicEndpoint; +import org.onap.policy.common.endpoints.event.comm.TopicEndpointManager; +import org.onap.policy.common.endpoints.event.comm.bus.NoopTopicSink; +import org.onap.policy.common.endpoints.event.comm.bus.NoopTopicSource; +import org.onap.policy.common.endpoints.parameters.TopicParameters; +import org.onap.policy.common.utils.coder.CoderException; +import org.onap.policy.common.utils.coder.StandardCoder; +import org.onap.policy.drools.controller.DroolsController; +import org.onap.policy.drools.protocol.coders.EventProtocolCoder; +import org.onap.policy.drools.system.PolicyController; +import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy; + +public class TopicsTest { + private static final String EXPECTED_EXCEPTION = "expected exception"; + private static final String MY_SOURCE_TOPIC = "my-source-topic"; + private static final String MY_SINK_TOPIC = "my-sink-topic"; + private static final String MY_GROUP = "my-group"; + private static final String MY_ARTIFACT = "my-artifact"; + private static final String MESSAGE = "{\"text\": \"hello\"}"; + private static final String TEXT = "hello"; + private static final String INJECT_FILE = "src/test/resources/topics.json"; + private static final String POLICY_NAME = "my-policy"; + + @Mock + private DroolsController drools; + @Mock + private PolicyController controller; + @Mock + private EventProtocolCoder protocolCoder; + @Mock + private NoopTopicSink sink; + @Mock + private NoopTopicSource source; + @Mock + private TopicEndpoint mgr; + + private ToscaPolicy policy; + + private Topics topics; + + /** + * Creates topics. + */ + @BeforeClass + public static void setUpBeforeClass() { + TopicEndpointManager.getManager().shutdown(); + + TopicParameters params = new TopicParameters(); + params.setTopic(MY_SOURCE_TOPIC); + params.setManaged(true); + params.setTopicCommInfrastructure("NOOP"); + TopicEndpointManager.getManager().addTopicSources(List.of(params)); + } + + @AfterClass + public static void tearDownAfterClass() { + TopicEndpointManager.getManager().shutdown(); + } + + /** + * Sets up. + */ + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + policy = new ToscaPolicy(); + policy.setName(POLICY_NAME); + policy.setVersion("1.0.0"); + + when(drools.getGroupId()).thenReturn(MY_GROUP); + when(drools.getArtifactId()).thenReturn(MY_ARTIFACT); + + when(controller.getDrools()).thenReturn(drools); + + when(protocolCoder.decode(MY_GROUP, MY_ARTIFACT, MY_SINK_TOPIC, MESSAGE)).thenReturn(TEXT); + + when(mgr.getNoopTopicSink(MY_SINK_TOPIC)).thenReturn(sink); + when(mgr.getNoopTopicSource(MY_SOURCE_TOPIC)).thenReturn(source); + + topics = new Topics() { + @Override + protected TopicEndpoint getTopicManager() { + return mgr; + } + + @Override + protected EventProtocolCoder getProtocolCoder() { + return protocolCoder; + } + }; + } + + @Test + public void testDestroy() { + Listener<String> listener1 = topics.createListener(MY_SINK_TOPIC, msg -> msg); + Listener<String> listener2 = topics.createListener(MY_SINK_TOPIC, msg -> msg + "a suffix"); + + topics.destroy(); + + verify(sink).unregister(listener1); + verify(sink).unregister(listener2); + } + + @Test + public void testInjectStringFile() throws IOException { + topics.inject(MY_SOURCE_TOPIC, INJECT_FILE); + + // nothing should have been replaced + String expected = new String(Files.readAllBytes(Paths.get(INJECT_FILE))); + verify(source).offer(expected); + } + + @Test + public void testInjectStringFileString() throws IOException { + topics.inject(MY_SOURCE_TOPIC, INJECT_FILE, "hello"); + + // text should have been replaced with "hello" + String expected = new String(Files.readAllBytes(Paths.get("src", "test", "resources", "topicsReplaced.json"))); + verify(source).offer(expected); + + // exception reading file + assertThatThrownBy(() -> topics.inject(MY_SOURCE_TOPIC, "missing-file.json", "some text")) + .isInstanceOf(TopicException.class); + } + + @Test + public void testCreateListenerStringClassOfTPolicyController() { + Listener<String> listener = topics.createListener(MY_SINK_TOPIC, String.class, controller); + listener.onTopicEvent(CommInfrastructure.NOOP, MY_SINK_TOPIC, MESSAGE); + + assertEquals(TEXT, listener.await()); + } + + @Test + public void testCreateListenerStringClassOfTCoder() { + Listener<Data> listener = topics.createListener(MY_SINK_TOPIC, Data.class, new StandardCoder()); + listener.onTopicEvent(CommInfrastructure.NOOP, MY_SINK_TOPIC, MESSAGE); + + Data expected = new Data(); + expected.text = TEXT; + assertEquals(expected.toString(), listener.await().toString()); + } + + /** + * Tests createListener() when the coder throws an exception. + */ + @Test + public void testCreateListenerStringClassOfTCoderException() { + StandardCoder coder = new StandardCoder() { + @Override + public <T> T decode(String arg0, Class<T> arg1) throws CoderException { + throw new CoderException(EXPECTED_EXCEPTION); + } + }; + + Listener<Data> listener = topics.createListener(MY_SINK_TOPIC, Data.class, coder); + + // onTopicEvent() should not throw an exception + assertThatCode(() -> listener.onTopicEvent(CommInfrastructure.NOOP, MY_SINK_TOPIC, MESSAGE)) + .doesNotThrowAnyException(); + + // should not have queued a message + assertThatThrownBy(() -> listener.await(0, TimeUnit.MILLISECONDS)).isInstanceOf(TopicException.class); + } + + @Test + public void testCreateListenerStringFunctionOfStringT() { + Listener<String> listener = topics.createListener(MY_SINK_TOPIC, msg -> msg); + listener.onTopicEvent(CommInfrastructure.NOOP, MY_SINK_TOPIC, MESSAGE); + + assertEquals(MESSAGE, listener.await()); + } + + @Test + public void testGetTopicManager_testGetProtocolCoder() { + // use a topic with a real manager + topics = new Topics(); + + assertNotNull(topics.getTopicManager()); + assertNotNull(topics.getProtocolCoder()); + } + + @ToString + private static class Data { + private String text; + } +} diff --git a/controlloop/common/rules-test/src/test/resources/META-INF/kmodule.xml b/controlloop/common/rules-test/src/test/resources/META-INF/kmodule.xml new file mode 100644 index 000000000..07041c6b1 --- /dev/null +++ b/controlloop/common/rules-test/src/test/resources/META-INF/kmodule.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ============LICENSE_START======================================================= + ONAP + ================================================================================ + Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + ================================================================================ + 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. + ============LICENSE_END========================================================= + --> +<kmodule xmlns="http://jboss.org/kie/6.0.0/kmodule"> + <kbase name="onap.policies.controlloop.operational.common.Drools" default="false" equalsBehavior="equality"/> + <kbase name="onap.policies.controlloop.Operational" equalsBehavior="equality" + packages="org.onap.policy.controlloop" includes="onap.policies.controlloop.operational.common.Drools"> + <ksession name="rulesTest"/> + </kbase> +</kmodule> diff --git a/controlloop/common/rules-test/src/test/resources/config/rulesTest-controller.properties b/controlloop/common/rules-test/src/test/resources/config/rulesTest-controller.properties new file mode 100644 index 000000000..11df5e99e --- /dev/null +++ b/controlloop/common/rules-test/src/test/resources/config/rulesTest-controller.properties @@ -0,0 +1,25 @@ +# +# ============LICENSE_START======================================================= +# ONAP +# ================================================================================ +# Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. +# ================================================================================ +# 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. +# ============LICENSE_END========================================================= +# + +controller.name=rulesTest + +rules.groupId=org.onap.policy.controlloop +rules.artifactId=rulesTest +rules.version=1.0.0 diff --git a/controlloop/common/rules-test/src/test/resources/my-http-client.properties b/controlloop/common/rules-test/src/test/resources/my-http-client.properties new file mode 100644 index 000000000..7d4ef90b9 --- /dev/null +++ b/controlloop/common/rules-test/src/test/resources/my-http-client.properties @@ -0,0 +1,26 @@ +# +# ============LICENSE_START======================================================= +# ONAP +# ================================================================================ +# Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. +# ================================================================================ +# 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. +# ============LICENSE_END========================================================= +# + +http.client.services=MY-CLIENT + +http.client.services.MY-CLIENT.managed=true +http.client.services.MY-CLIENT.host=localhost +http.client.services.MY-CLIENT.port=6669 +http.client.services.MY-CLIENT.contextUriPath=some/path diff --git a/controlloop/common/rules-test/src/test/resources/rulesTest.drl b/controlloop/common/rules-test/src/test/resources/rulesTest.drl new file mode 100644 index 000000000..11e99e960 --- /dev/null +++ b/controlloop/common/rules-test/src/test/resources/rulesTest.drl @@ -0,0 +1,96 @@ +/* + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop; + +import org.onap.policy.controlloop.drl.legacy.ControlLoopParams; +import org.onap.policy.controlloop.CanonicalOnset; +import org.onap.policy.controlloop.VirtualControlLoopEvent; +import org.onap.policy.controlloop.VirtualControlLoopNotification; +import org.onap.policy.controlloop.ControlLoopNotificationType; +import org.onap.policy.controlloop.policy.Policy; +import org.onap.policy.controlloop.eventmanager.ControlLoopEventManager2; +import org.onap.policy.controlloop.eventmanager.ControlLoopEventManager2.NewEventStatus; +import org.onap.policy.controlloop.eventmanager.ControlLoopOperationManager2; +import org.onap.policy.controlloop.utils.ControlLoopUtils; +import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy; + +import org.slf4j.LoggerFactory; +import org.slf4j.Logger; + +import org.onap.policy.drools.system.PolicyEngineConstants; + +rule "STARTED" + when + then + System.out.println(drools.getRule().getName()); +end + +/* +* +* Called when the ControlLoopParams object has been inserted into working memory from the BRMSGW. +* +*/ +rule "INSERT.PARAMS" + when + $params : ControlLoopParams() + then + + Logger logger = LoggerFactory.getLogger(drools.getRule().getPackage()); + logger.info("{}: {} : TOSCA-POLICY=[{}]", $params.getClosedLoopControlName(), $params.getPolicyName() + "." + + drools.getRule().getName(), $params.getToscaPolicy()); +end + +/* +* +* Called when a Tosca Policy is present. +* +*/ +rule "NEW.TOSCA.POLICY" + when + $policy : ToscaPolicy() + then + + Logger logger = LoggerFactory.getLogger(drools.getRule().getPackage()); + logger.info("{}: [{}|{}|{}|{}]: CONTENT: {}", drools.getRule().getName(), + $policy.getType(), $policy.getTypeVersion(), $policy.getName(), + $policy.getVersion(), $policy); + + ControlLoopParams params = ControlLoopUtils.toControlLoopParams($policy); + if (params != null) { + insert(params); + } +end + +/* + * Remove Control Loop Parameters. + */ +rule "REMOVE.PARAMS" + when + $params : ControlLoopParams( $policyName : getPolicyName(), $policyVersion : getPolicyVersion() ) + not ( ToscaPolicy( getName() == $policyName, getVersion() == $policyVersion ) ) + then + + Logger logger = LoggerFactory.getLogger(drools.getRule().getPackage()); + logger.info("{}: [{}|{}|{}]", drools.getRule().getName(), + $params.getPolicyScope(), $params.getPolicyName(), $params.getPolicyVersion()); + + retract($params); +end diff --git a/controlloop/common/rules-test/src/test/resources/rulesTest.pom b/controlloop/common/rules-test/src/test/resources/rulesTest.pom new file mode 100644 index 000000000..625e736a4 --- /dev/null +++ b/controlloop/common/rules-test/src/test/resources/rulesTest.pom @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ============LICENSE_START======================================================= + ONAP + ================================================================================ + Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + ================================================================================ + 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. + ============LICENSE_END========================================================= + --> + +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + <modelVersion>4.0.0</modelVersion> + + <groupId>org.onap.policy.controlloop</groupId> + <artifactId>rulesTest</artifactId> + <version>1.0.0</version> +</project> diff --git a/controlloop/common/rules-test/src/test/resources/topics.json b/controlloop/common/rules-test/src/test/resources/topics.json new file mode 100644 index 000000000..4e08bee83 --- /dev/null +++ b/controlloop/common/rules-test/src/test/resources/topics.json @@ -0,0 +1,5 @@ +{ + "closedLoopControlName": "ControlLoop-vCPE-48f0c2c3-a172-4192-9ae3-052274181b6e", + "text": "${replaceMe}", + "moreText": "${replaceMe}" +} diff --git a/controlloop/common/rules-test/src/test/resources/topicsReplaced.json b/controlloop/common/rules-test/src/test/resources/topicsReplaced.json new file mode 100644 index 000000000..743ef4c71 --- /dev/null +++ b/controlloop/common/rules-test/src/test/resources/topicsReplaced.json @@ -0,0 +1,5 @@ +{ + "closedLoopControlName": "ControlLoop-vCPE-48f0c2c3-a172-4192-9ae3-052274181b6e", + "text": "hello", + "moreText": "hello" +} diff --git a/controlloop/common/rules-test/src/test/resources/tosca-policy.json b/controlloop/common/rules-test/src/test/resources/tosca-policy.json new file mode 100644 index 000000000..42f54ab12 --- /dev/null +++ b/controlloop/common/rules-test/src/test/resources/tosca-policy.json @@ -0,0 +1,37 @@ +{ + "type": "onap.policies.controlloop.operational.common.Drools", + "type_version": "1.0.0", + "version": "1.0.0", + "name": "operational.restart", + "metadata": { + "policy-id": "operational.restart" + }, + "properties": { + "controllerName": "rulesTest", + "id": "ControlLoop-vCPE-48f0c2c3-a172-4192-9ae3-052274181b6e", + "timeout": 3600, + "abatement": false, + "trigger": "unique-policy-id-1-restart", + "operations": [ + { + "id": "unique-policy-id-1-restart", + "description": "Restart the VM", + "operation": { + "actor": "APPC", + "operation": "Restart", + "target": { + "targetType": "VM" + } + }, + "timeout": 1200, + "retries": 3, + "success": "final_success", + "failure": "final_failure", + "failure_timeout": "final_failure_timeout", + "failure_retries": "final_failure_retries", + "failure_exception": "final_failure_exception", + "failure_guard": "final_failure_guard" + } + ] + } +} diff --git a/controlloop/common/rules-test/src/test/resources/tosca-template.json b/controlloop/common/rules-test/src/test/resources/tosca-template.json new file mode 100644 index 000000000..305756064 --- /dev/null +++ b/controlloop/common/rules-test/src/test/resources/tosca-template.json @@ -0,0 +1,45 @@ +{ + "tosca_definitions_version": "tosca_simple_yaml_1_1_0", + "topology_template": { + "policies": [ + { + "operational.restart": { + "type": "onap.policies.controlloop.operational.common.Drools", + "type_version": "1.0.0", + "version": "1.0.0", + "metadata": { + "policy-id": "operational.restart" + }, + "properties": { + "id": "ControlLoop-vCPE-48f0c2c3-a172-4192-9ae3-052274181b6e", + "timeout": 3600, + "abatement": true, + "trigger": "unique-policy-id-1-restart", + "operations": [ + { + "id": "unique-policy-id-1-restart", + "description": "Restart the VM", + "operation": { + "actor": "APPC", + "operation": "Restart", + "target": { + "targetType": "VNF" + } + }, + "timeout": 1200, + "retries": 3, + "success": "final_success", + "failure": "final_failure", + "failure_timeout": "final_failure_timeout", + "failure_retries": "final_failure_retries", + "failure_exception": "final_failure_exception", + "failure_guard": "final_failure_guard" + } + ], + "controllerName": "usecases" + } + } + } + ] + } +} |