2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2020-2021 AT&T Intellectual Property. All rights reserved.
6 * Modifications Copyright (C) 2023 Nordix Foundation.
7 * ================================================================================
8 * Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 * ============LICENSE_END=========================================================
22 package org.onap.policy.controlloop.common.rules.test;
24 import static org.assertj.core.api.Assertions.assertThat;
25 import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
26 import static org.junit.jupiter.api.Assertions.assertEquals;
27 import static org.junit.jupiter.api.Assertions.assertNotNull;
28 import static org.junit.jupiter.api.Assertions.assertSame;
29 import static org.junit.jupiter.api.Assertions.assertTrue;
30 import static org.mockito.ArgumentMatchers.any;
31 import static org.mockito.ArgumentMatchers.eq;
32 import static org.mockito.Mockito.doAnswer;
33 import static org.mockito.Mockito.mock;
34 import static org.mockito.Mockito.verify;
35 import static org.mockito.Mockito.when;
37 import ch.qos.logback.classic.Logger;
39 import java.util.LinkedList;
40 import java.util.List;
41 import java.util.Properties;
42 import java.util.concurrent.BlockingQueue;
43 import java.util.concurrent.LinkedBlockingQueue;
44 import java.util.concurrent.TimeUnit;
45 import java.util.stream.Collectors;
46 import org.junit.jupiter.api.AfterAll;
47 import org.junit.jupiter.api.BeforeAll;
48 import org.junit.jupiter.api.BeforeEach;
49 import org.junit.jupiter.api.Test;
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.onap.policy.common.utils.test.log.logback.ExtractAppender;
63 import org.onap.policy.controlloop.ControlLoopEvent;
64 import org.onap.policy.controlloop.drl.legacy.ControlLoopParams;
65 import org.onap.policy.controlloop.eventmanager.ControlLoopEventManager;
66 import org.onap.policy.drools.controller.DroolsController;
67 import org.onap.policy.drools.persistence.SystemPersistence;
68 import org.onap.policy.drools.system.PolicyController;
69 import org.onap.policy.drools.system.PolicyControllerFactory;
70 import org.onap.policy.drools.system.PolicyEngine;
71 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy;
72 import org.slf4j.LoggerFactory;
75 private static final String EXPECTED_EXCEPTION = "expected exception";
76 private static final String CONTROLLER_NAME = "rulesTest";
77 private static final String POLICY_FILE = "src/test/resources/tosca-policy.json";
78 private static final String MY_POLICY = "operational.restart";
79 private static final String RESOURCE_DIR = "src/test/resources";
80 private static final String MY_RULE_NAME = "my-rule-name";
81 private static final String MY_TEXT = "my text";
84 * Used to attach an appender to the class' logger.
86 private static final Logger logger = (Logger) LoggerFactory.getLogger(Rules.class);
87 private static final ExtractAppender appender = new ExtractAppender();
89 private final PolicyEngine engine = mock(PolicyEngine.class);
90 private final SystemPersistence repo = mock(SystemPersistence.class);
91 private final PolicyController controller = mock(PolicyController.class);
92 private final KieSession kieSession = mock(KieSession.class);
93 private final PolicyControllerFactory controllerFactory = mock(PolicyControllerFactory.class);
94 private final DroolsController drools = mock(DroolsController.class);
96 private List<Object> facts;
97 private List<RuleRuntimeEventListener> ruleListeners;
98 private List<AgendaEventListener> agendaListeners;
99 private Properties properties;
100 private boolean installed;
105 * Attaches the appender to the logger.
108 public static void setUpBeforeClass() throws Exception {
110 * Attach appender to the logger.
112 appender.setContext(logger.getLoggerContext());
115 logger.addAppender(appender);
119 * Stops the appender.
122 public static void tearDownAfterClass() {
130 public void setUp() {
131 facts = new LinkedList<>();
132 ruleListeners = new LinkedList<>();
133 agendaListeners = new LinkedList<>();
135 properties = new Properties();
137 when(engine.createPolicyController(any(), any())).thenReturn(controller);
138 when(repo.getControllerProperties(CONTROLLER_NAME)).thenReturn(properties);
139 when(controller.getDrools()).thenReturn(drools);
141 when(drools.facts(eq(CONTROLLER_NAME), any())).thenAnswer(args -> {
142 var clazz = args.getArgument(1);
143 return facts.stream().filter(obj -> obj.getClass() == clazz).collect(Collectors.toList());
146 // notify listeners when objects are added to drools
147 when(drools.offer(any())).thenAnswer(args -> {
148 var object = args.getArgument(0);
149 notifyInserted(object);
151 if (!(object instanceof ToscaPolicy)) {
155 // "insert" Params objects associated with the policy (i.e., mimic the rules)
156 var policy = (ToscaPolicy) object;
157 var params = new ControlLoopParams();
158 params.setToscaPolicy(policy);
159 notifyInserted(params);
164 when(drools.delete(any())).thenAnswer(args -> {
165 var clazz = args.getArgument(0);
166 facts.removeIf(obj -> obj.getClass() == clazz);
170 // handle rule listener registration and deregistration with the kieSession
172 ruleListeners.add(args.getArgument(0));
174 }).when(kieSession).addEventListener(any(RuleRuntimeEventListener.class));
177 ruleListeners.remove(args.getArgument(0));
179 }).when(kieSession).removeEventListener(any(RuleRuntimeEventListener.class));
181 // handle agenda listener registration and deregistration with the kieSession
183 agendaListeners.add(args.getArgument(0));
185 }).when(kieSession).addEventListener(any(AgendaEventListener.class));
187 rules = new MyRules();
189 rules.configure(RESOURCE_DIR);
195 assertEquals(CONTROLLER_NAME, rules.getControllerName());
197 assertSame(engine, rules.getPdpd());
198 assertSame(repo, rules.getPdpdRepo());
199 assertSame(controller, rules.getController());
203 void testStart() throws Exception {
204 verify(repo).setConfigurationDir("src/test/resources/config");
205 assertTrue(installed);
206 verify(engine).configure(any(Properties.class));
207 verify(engine).createPolicyController(CONTROLLER_NAME, properties);
208 verify(engine).start();
210 verify(kieSession).addEventListener(any(RuleRuntimeEventListener.class));
211 verify(kieSession).addEventListener(any(AgendaEventListener.class));
218 verify(controllerFactory).shutdown(CONTROLLER_NAME);
219 verify(engine).stop();
223 void testResetFacts() {
226 verify(drools).delete(ToscaPolicy.class);
227 verify(drools).delete(ControlLoopParams.class);
228 verify(drools).delete(ControlLoopEventManager.class);
229 verify(drools).delete(ControlLoopEvent.class);
233 void testSetupPolicyFromTemplate_testGetPolicyFromTemplate() throws InterruptedException {
234 rules.setupPolicyFromTemplate("tosca-template.json", MY_POLICY);
236 assertThatIllegalArgumentException()
237 .isThrownBy(() -> rules.setupPolicyFromTemplate("missing-file.json", "a-policy"));
239 // check interrupt case
240 checkInterrupt(() -> rules.setupPolicyFromTemplate("tosca-template.json", MY_POLICY),
241 "policy operational.restart");
245 void testSetupPolicyFromFile_testGetPolicyFromFile_testSetupPolicy() throws InterruptedException {
246 assertNotNull(rules.setupPolicyFromFile(POLICY_FILE));
248 assertThatIllegalArgumentException().isThrownBy(() -> rules.setupPolicyFromFile("missing-file.json"));
250 // check interrupt case
251 checkInterrupt(() -> rules.setupPolicyFromFile(POLICY_FILE), "policy " + POLICY_FILE);
255 void testRuleListenerLogger() {
256 var rule = mock(Rule.class);
257 when(rule.getName()).thenReturn(MY_RULE_NAME);
259 // insertions - with and without rule name
260 var insert = mock(ObjectInsertedEvent.class);
261 when(insert.getObject()).thenReturn(MY_TEXT);
262 checkLogging("inserted", () -> ruleListeners.forEach(listener -> listener.objectInserted(insert)));
263 when(insert.getRule()).thenReturn(rule);
264 checkLogging("inserted", () -> ruleListeners.forEach(listener -> listener.objectInserted(insert)));
266 // updates - with and without rule name
267 var update = mock(ObjectUpdatedEvent.class);
268 when(update.getObject()).thenReturn(MY_TEXT);
269 checkLogging("updated", () -> ruleListeners.forEach(listener -> listener.objectUpdated(update)));
270 when(update.getRule()).thenReturn(rule);
271 checkLogging("updated", () -> ruleListeners.forEach(listener -> listener.objectUpdated(update)));
273 // deletions - with and without rule name
274 var delete = mock(ObjectDeletedEvent.class);
275 when(delete.getOldObject()).thenReturn(MY_TEXT);
276 checkLogging("deleted", () -> ruleListeners.forEach(listener -> listener.objectDeleted(delete)));
277 when(delete.getRule()).thenReturn(rule);
278 checkLogging("deleted", () -> ruleListeners.forEach(listener -> listener.objectDeleted(delete)));
282 void testAgendaListenerLogger() {
283 var rule = mock(Rule.class);
284 when(rule.getName()).thenReturn(MY_RULE_NAME);
286 var match = mock(Match.class);
287 when(match.getRule()).thenReturn(rule);
290 var create = mock(MatchCreatedEvent.class);
291 when(create.getMatch()).thenReturn(match);
292 checkLogging("match created", () -> agendaListeners.forEach(listener -> listener.matchCreated(create)));
295 var cancel = mock(MatchCancelledEvent.class);
296 when(cancel.getMatch()).thenReturn(match);
297 checkLogging("match cancelled", () -> agendaListeners.forEach(listener -> listener.matchCancelled(cancel)));
300 var before = mock(BeforeMatchFiredEvent.class);
301 when(before.getMatch()).thenReturn(match);
303 checkLogging("before match fired",
304 () -> agendaListeners.forEach(listener -> listener.beforeMatchFired(before)));
308 var after = mock(AfterMatchFiredEvent.class);
309 when(after.getMatch()).thenReturn(match);
310 checkLogging("after match fired", () -> agendaListeners.forEach(listener -> listener.afterMatchFired(after)));
314 void testMakePdpd_testMakePdpdRepo() {
315 // need rules that makes real objects
316 rules = new Rules(CONTROLLER_NAME);
318 assertNotNull(rules.getPdpd());
319 assertNotNull(rules.getPdpdRepo());
322 private void checkInterrupt(Runnable command, String expectedMsg) throws InterruptedException {
323 rules = new MyRules() {
325 protected ToscaPolicy setupPolicy(ToscaPolicy policy) throws InterruptedException {
326 throw new InterruptedException(EXPECTED_EXCEPTION);
329 rules.configure(RESOURCE_DIR);
332 BlockingQueue<IllegalArgumentException> exceptions = new LinkedBlockingQueue<>();
334 var thread = new Thread(() -> {
337 } catch (IllegalArgumentException e) {
342 thread.setDaemon(true);
345 assertThat(exceptions.poll(10, TimeUnit.SECONDS)).isNotNull().hasMessage(expectedMsg)
346 .hasCauseInstanceOf(InterruptedException.class);
349 private void checkLogging(String expectedMsg, Runnable command) {
350 appender.clearExtractions();
352 var messages = appender.getExtracted();
353 assertEquals(1, messages.size());
354 assertThat(messages.get(0)).contains(expectedMsg);
357 protected void notifyInserted(Object object) {
358 // add it to our list
361 // increase code coverage by adding random objects
362 var event0 = mock(ObjectInsertedEvent.class);
363 when(event0.getObject()).thenReturn(new Object());
364 ruleListeners.forEach(listener -> listener.objectInserted(event0));
366 // increase code coverage by adding a random object
367 var event = mock(ObjectInsertedEvent.class);
368 when(event.getObject()).thenReturn(object);
370 if (object instanceof ToscaPolicy) {
371 // increase code coverage by associating it with a random rule
372 var rule = mock(Rule.class);
373 when(rule.getName()).thenReturn(MY_RULE_NAME);
374 when(event.getRule()).thenReturn(rule);
377 ruleListeners.forEach(listener -> listener.objectInserted(event));
380 private class MyRules extends Rules {
382 super(CONTROLLER_NAME);
386 protected PolicyEngine makeEngine() {
391 protected SystemPersistence makePdpdRepo() {
396 protected KieSession getKieSession() {
401 protected PolicyControllerFactory getControllerFactory() {
402 return controllerFactory;
406 protected void installArtifact(File kmoduleFile, File pomFile, String resourceDir, List<File> ruleFiles) {