aboutsummaryrefslogtreecommitdiffstats
path: root/controlloop/common/rules-test/src/main/java
diff options
context:
space:
mode:
authorJim Hahn <jrh3@att.com>2020-03-09 15:15:05 -0400
committerJim Hahn <jrh3@att.com>2020-03-12 12:12:48 -0400
commitaa8225b5211485b3c1150c21e51fd3e93b7f31d3 (patch)
tree922b78d2c8a4c657d4c32604a0b73df40d2fb330 /controlloop/common/rules-test/src/main/java
parent0e658768fc0573bf6acf7f849a49c9da98c8e47f (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/src/main/java')
-rw-r--r--controlloop/common/rules-test/src/main/java/org/onap/policy/controlloop/common/rules/test/BaseRuleTest.java496
-rw-r--r--controlloop/common/rules-test/src/main/java/org/onap/policy/controlloop/common/rules/test/HttpClients.java67
-rw-r--r--controlloop/common/rules-test/src/main/java/org/onap/policy/controlloop/common/rules/test/Listener.java180
-rw-r--r--controlloop/common/rules-test/src/main/java/org/onap/policy/controlloop/common/rules/test/NamedRunner.java85
-rw-r--r--controlloop/common/rules-test/src/main/java/org/onap/policy/controlloop/common/rules/test/Rules.java413
-rw-r--r--controlloop/common/rules-test/src/main/java/org/onap/policy/controlloop/common/rules/test/SimulatorException.java44
-rw-r--r--controlloop/common/rules-test/src/main/java/org/onap/policy/controlloop/common/rules/test/Simulators.java85
-rw-r--r--controlloop/common/rules-test/src/main/java/org/onap/policy/controlloop/common/rules/test/TestNames.java44
-rw-r--r--controlloop/common/rules-test/src/main/java/org/onap/policy/controlloop/common/rules/test/TopicException.java44
-rw-r--r--controlloop/common/rules-test/src/main/java/org/onap/policy/controlloop/common/rules/test/Topics.java176
10 files changed, 1634 insertions, 0 deletions
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();
+ }
+ };
+ }
+}