2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2020-2021 AT&T Intellectual Property. All rights reserved.
6 * ================================================================================
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 * ============LICENSE_END=========================================================
21 package org.onap.policy.controlloop.common.rules.test;
23 import static org.assertj.core.api.Assertions.assertThat;
24 import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
25 import static org.junit.Assert.assertEquals;
26 import static org.junit.Assert.assertNotNull;
27 import static org.junit.Assert.assertSame;
28 import static org.junit.Assert.assertTrue;
29 import static org.mockito.ArgumentMatchers.any;
30 import static org.mockito.ArgumentMatchers.eq;
31 import static org.mockito.Mockito.doAnswer;
32 import static org.mockito.Mockito.mock;
33 import static org.mockito.Mockito.verify;
34 import static org.mockito.Mockito.when;
36 import ch.qos.logback.classic.Logger;
38 import java.util.LinkedList;
39 import java.util.List;
40 import java.util.Properties;
41 import java.util.concurrent.BlockingQueue;
42 import java.util.concurrent.LinkedBlockingQueue;
43 import java.util.concurrent.TimeUnit;
44 import java.util.stream.Collectors;
45 import org.junit.AfterClass;
46 import org.junit.Before;
47 import org.junit.BeforeClass;
48 import org.junit.Test;
49 import org.junit.runner.RunWith;
50 import org.kie.api.definition.rule.Rule;
51 import org.kie.api.event.rule.AfterMatchFiredEvent;
52 import org.kie.api.event.rule.AgendaEventListener;
53 import org.kie.api.event.rule.BeforeMatchFiredEvent;
54 import org.kie.api.event.rule.MatchCancelledEvent;
55 import org.kie.api.event.rule.MatchCreatedEvent;
56 import org.kie.api.event.rule.ObjectDeletedEvent;
57 import org.kie.api.event.rule.ObjectInsertedEvent;
58 import org.kie.api.event.rule.ObjectUpdatedEvent;
59 import org.kie.api.event.rule.RuleRuntimeEventListener;
60 import org.kie.api.runtime.KieSession;
61 import org.kie.api.runtime.rule.Match;
62 import org.mockito.Mock;
63 import org.mockito.junit.MockitoJUnitRunner;
64 import org.onap.policy.common.utils.test.log.logback.ExtractAppender;
65 import org.onap.policy.controlloop.ControlLoopEvent;
66 import org.onap.policy.controlloop.drl.legacy.ControlLoopParams;
67 import org.onap.policy.controlloop.eventmanager.ControlLoopEventManager;
68 import org.onap.policy.drools.controller.DroolsController;
69 import org.onap.policy.drools.persistence.SystemPersistence;
70 import org.onap.policy.drools.system.PolicyController;
71 import org.onap.policy.drools.system.PolicyControllerFactory;
72 import org.onap.policy.drools.system.PolicyEngine;
73 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy;
74 import org.slf4j.LoggerFactory;
76 @RunWith(MockitoJUnitRunner.class)
77 public class RulesTest {
78 private static final String EXPECTED_EXCEPTION = "expected exception";
79 private static final String CONTROLLER_NAME = "rulesTest";
80 private static final String POLICY_FILE = "src/test/resources/tosca-policy.json";
81 private static final String MY_POLICY = "operational.restart";
82 private static final String RESOURCE_DIR = "src/test/resources";
83 private static final String MY_RULE_NAME = "my-rule-name";
84 private static final String MY_TEXT = "my text";
87 * Used to attach an appender to the class' logger.
89 private static final Logger logger = (Logger) LoggerFactory.getLogger(Rules.class);
90 private static final ExtractAppender appender = new ExtractAppender();
93 private PolicyEngine engine;
95 private SystemPersistence repo;
97 private PolicyController controller;
99 private KieSession kieSession;
101 private PolicyControllerFactory controllerFactory;
103 private DroolsController drools;
105 private List<Object> facts;
106 private List<RuleRuntimeEventListener> ruleListeners;
107 private List<AgendaEventListener> agendaListeners;
108 private Properties properties;
109 private boolean installed;
114 * Attaches the appender to the logger.
117 public static void setUpBeforeClass() throws Exception {
119 * Attach appender to the logger.
121 appender.setContext(logger.getLoggerContext());
124 logger.addAppender(appender);
128 * Stops the appender.
131 public static void tearDownAfterClass() {
139 public void setUp() {
140 facts = new LinkedList<>();
141 ruleListeners = new LinkedList<>();
142 agendaListeners = new LinkedList<>();
144 properties = new Properties();
146 when(engine.createPolicyController(any(), any())).thenReturn(controller);
147 when(repo.getControllerProperties(CONTROLLER_NAME)).thenReturn(properties);
148 when(controller.getDrools()).thenReturn(drools);
150 when(drools.facts(eq(CONTROLLER_NAME), any())).thenAnswer(args -> {
151 Class<?> clazz = args.getArgument(1);
152 return facts.stream().filter(obj -> obj.getClass() == clazz).collect(Collectors.toList());
155 // notify listeners when objects are added to drools
156 when(drools.offer(any())).thenAnswer(args -> {
157 Object object = args.getArgument(0);
158 notifyInserted(object);
160 if (!(object instanceof ToscaPolicy)) {
164 // "insert" Params objects associated with the policy (i.e., mimic the rules)
165 ToscaPolicy policy = (ToscaPolicy) object;
166 ControlLoopParams params = new ControlLoopParams();
167 params.setToscaPolicy(policy);
168 notifyInserted(params);
173 when(drools.delete(any())).thenAnswer(args -> {
174 Class<?> clazz = args.getArgument(0);
175 facts.removeIf(obj -> obj.getClass() == clazz);
179 // handle rule listener registration and deregistration with the kieSession
181 ruleListeners.add(args.getArgument(0));
183 }).when(kieSession).addEventListener(any(RuleRuntimeEventListener.class));
186 ruleListeners.remove(args.getArgument(0));
188 }).when(kieSession).removeEventListener(any(RuleRuntimeEventListener.class));
190 // handle agenda listener registration and deregistration with the kieSession
192 agendaListeners.add(args.getArgument(0));
194 }).when(kieSession).addEventListener(any(AgendaEventListener.class));
196 rules = new MyRules();
198 rules.configure(RESOURCE_DIR);
203 public void testRules() {
204 assertEquals(CONTROLLER_NAME, rules.getControllerName());
206 assertSame(engine, rules.getPdpd());
207 assertSame(repo, rules.getPdpdRepo());
208 assertSame(controller, rules.getController());
212 public void testStart() throws Exception {
213 verify(repo).setConfigurationDir("src/test/resources/config");
214 assertTrue(installed);
215 verify(engine).configure(any(Properties.class));
216 verify(engine).createPolicyController(CONTROLLER_NAME, properties);
217 verify(engine).start();
219 verify(kieSession).addEventListener(any(RuleRuntimeEventListener.class));
220 verify(kieSession).addEventListener(any(AgendaEventListener.class));
224 public void testDestroy() {
227 verify(controllerFactory).shutdown(CONTROLLER_NAME);
228 verify(engine).stop();
232 public void testResetFacts() {
235 verify(drools).delete(ToscaPolicy.class);
236 verify(drools).delete(ControlLoopParams.class);
237 verify(drools).delete(ControlLoopEventManager.class);
238 verify(drools).delete(ControlLoopEvent.class);
242 public void testSetupPolicyFromTemplate_testGetPolicyFromTemplate() throws InterruptedException {
243 rules.setupPolicyFromTemplate("tosca-template.json", MY_POLICY);
245 assertThatIllegalArgumentException()
246 .isThrownBy(() -> rules.setupPolicyFromTemplate("missing-file.json", "a-policy"));
248 // check interrupt case
249 checkInterrupt(() -> rules.setupPolicyFromTemplate("tosca-template.json", MY_POLICY),
250 "policy operational.restart");
254 public void testSetupPolicyFromFile_testGetPolicyFromFile_testSetupPolicy() throws InterruptedException {
255 assertNotNull(rules.setupPolicyFromFile(POLICY_FILE));
257 assertThatIllegalArgumentException().isThrownBy(() -> rules.setupPolicyFromFile("missing-file.json"));
259 // check interrupt case
260 checkInterrupt(() -> rules.setupPolicyFromFile(POLICY_FILE), "policy " + POLICY_FILE);
264 public void testRuleListenerLogger() {
265 Rule rule = mock(Rule.class);
266 when(rule.getName()).thenReturn(MY_RULE_NAME);
268 // insertions - with and without rule name
269 ObjectInsertedEvent insert = mock(ObjectInsertedEvent.class);
270 when(insert.getObject()).thenReturn(MY_TEXT);
271 checkLogging("inserted", () -> ruleListeners.forEach(listener -> listener.objectInserted(insert)));
272 when(insert.getRule()).thenReturn(rule);
273 checkLogging("inserted", () -> ruleListeners.forEach(listener -> listener.objectInserted(insert)));
275 // updates - with and without rule name
276 ObjectUpdatedEvent update = mock(ObjectUpdatedEvent.class);
277 when(update.getObject()).thenReturn(MY_TEXT);
278 checkLogging("updated", () -> ruleListeners.forEach(listener -> listener.objectUpdated(update)));
279 when(update.getRule()).thenReturn(rule);
280 checkLogging("updated", () -> ruleListeners.forEach(listener -> listener.objectUpdated(update)));
282 // deletions - with and without rule name
283 ObjectDeletedEvent delete = mock(ObjectDeletedEvent.class);
284 when(delete.getOldObject()).thenReturn(MY_TEXT);
285 checkLogging("deleted", () -> ruleListeners.forEach(listener -> listener.objectDeleted(delete)));
286 when(delete.getRule()).thenReturn(rule);
287 checkLogging("deleted", () -> ruleListeners.forEach(listener -> listener.objectDeleted(delete)));
291 public void testAgendaListenerLogger() {
292 Rule rule = mock(Rule.class);
293 when(rule.getName()).thenReturn(MY_RULE_NAME);
295 Match match = mock(Match.class);
296 when(match.getRule()).thenReturn(rule);
299 MatchCreatedEvent create = mock(MatchCreatedEvent.class);
300 when(create.getMatch()).thenReturn(match);
301 checkLogging("match created", () -> agendaListeners.forEach(listener -> listener.matchCreated(create)));
304 MatchCancelledEvent cancel = mock(MatchCancelledEvent.class);
305 when(cancel.getMatch()).thenReturn(match);
306 checkLogging("match cancelled", () -> agendaListeners.forEach(listener -> listener.matchCancelled(cancel)));
309 BeforeMatchFiredEvent before = mock(BeforeMatchFiredEvent.class);
310 when(before.getMatch()).thenReturn(match);
312 checkLogging("before match fired",
313 () -> agendaListeners.forEach(listener -> listener.beforeMatchFired(before)));
317 AfterMatchFiredEvent after = mock(AfterMatchFiredEvent.class);
318 when(after.getMatch()).thenReturn(match);
319 checkLogging("after match fired", () -> agendaListeners.forEach(listener -> listener.afterMatchFired(after)));
323 public void testMakePdpd_testMakePdpdRepo() {
324 // need rules that makes real objects
325 rules = new Rules(CONTROLLER_NAME);
327 assertNotNull(rules.getPdpd());
328 assertNotNull(rules.getPdpdRepo());
331 private void checkInterrupt(Runnable command, String expectedMsg) throws InterruptedException {
332 rules = new MyRules() {
334 protected ToscaPolicy setupPolicy(ToscaPolicy policy) throws InterruptedException {
335 throw new InterruptedException(EXPECTED_EXCEPTION);
338 rules.configure(RESOURCE_DIR);
341 BlockingQueue<IllegalArgumentException> exceptions = new LinkedBlockingQueue<>();
343 Thread thread = new Thread(() -> {
346 } catch (IllegalArgumentException e) {
351 thread.setDaemon(true);
354 assertThat(exceptions.poll(10, TimeUnit.SECONDS)).isNotNull().hasMessage(expectedMsg)
355 .hasCauseInstanceOf(InterruptedException.class);
358 private void checkLogging(String expectedMsg, Runnable command) {
359 appender.clearExtractions();
361 List<String> messages = appender.getExtracted();
362 assertEquals(1, messages.size());
363 assertThat(messages.get(0)).contains(expectedMsg);
366 protected void notifyInserted(Object object) {
367 // add it to our list
370 // increase code coverage by adding random objects
371 ObjectInsertedEvent event0 = mock(ObjectInsertedEvent.class);
372 when(event0.getObject()).thenReturn(new Object());
373 ruleListeners.forEach(listener -> listener.objectInserted(event0));
375 // increase code coverage by adding a random object
376 ObjectInsertedEvent event = mock(ObjectInsertedEvent.class);
377 when(event.getObject()).thenReturn(object);
379 if (object instanceof ToscaPolicy) {
380 // increase code coverage by associating it with a random rule
381 Rule rule = mock(Rule.class);
382 when(rule.getName()).thenReturn(MY_RULE_NAME);
383 when(event.getRule()).thenReturn(rule);
386 ruleListeners.forEach(listener -> listener.objectInserted(event));
389 private class MyRules extends Rules {
391 super(CONTROLLER_NAME);
395 protected PolicyEngine makeEngine() {
400 protected SystemPersistence makePdpdRepo() {
405 protected KieSession getKieSession() {
410 protected PolicyControllerFactory getControllerFactory() {
411 return controllerFactory;
415 protected void installArtifact(File kmoduleFile, File pomFile, String resourceDir, List<File> ruleFiles) {