diff options
author | 2020-03-09 15:15:05 -0400 | |
---|---|---|
committer | 2020-03-12 12:12:48 -0400 | |
commit | aa8225b5211485b3c1150c21e51fd3e93b7f31d3 (patch) | |
tree | 922b78d2c8a4c657d4c32604a0b73df40d2fb330 /controlloop/common/rules-test/src/main/java | |
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/src/main/java')
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(); + } + }; + } +} |