a4767dfd992b91f44fc241e996f466d912fa1cc2
[policy/drools-applications.git] / controlloop / common / rules-test / src / test / java / org / onap / policy / controlloop / common / rules / test / RulesTest.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP
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
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
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=========================================================
19  */
20
21 package org.onap.policy.controlloop.common.rules.test;
22
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;
35
36 import ch.qos.logback.classic.Logger;
37 import java.io.File;
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;
75
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";
85
86     /**
87      * Used to attach an appender to the class' logger.
88      */
89     private static final Logger logger = (Logger) LoggerFactory.getLogger(Rules.class);
90     private static final ExtractAppender appender = new ExtractAppender();
91
92     @Mock
93     private PolicyEngine engine;
94     @Mock
95     private SystemPersistence repo;
96     @Mock
97     private PolicyController controller;
98     @Mock
99     private KieSession kieSession;
100     @Mock
101     private PolicyControllerFactory controllerFactory;
102     @Mock
103     private DroolsController drools;
104
105     private List<Object> facts;
106     private List<RuleRuntimeEventListener> ruleListeners;
107     private List<AgendaEventListener> agendaListeners;
108     private Properties properties;
109     private boolean installed;
110
111     private Rules rules;
112
113     /**
114      * Attaches the appender to the logger.
115      */
116     @BeforeClass
117     public static void setUpBeforeClass() throws Exception {
118         /*
119          * Attach appender to the logger.
120          */
121         appender.setContext(logger.getLoggerContext());
122         appender.start();
123
124         logger.addAppender(appender);
125     }
126
127     /**
128      * Stops the appender.
129      */
130     @AfterClass
131     public static void tearDownAfterClass() {
132         appender.stop();
133     }
134
135     /**
136      * Sets up.
137      */
138     @Before
139     public void setUp() {
140         facts = new LinkedList<>();
141         ruleListeners = new LinkedList<>();
142         agendaListeners = new LinkedList<>();
143         installed = false;
144         properties = new Properties();
145
146         when(engine.createPolicyController(any(), any())).thenReturn(controller);
147         when(repo.getControllerProperties(CONTROLLER_NAME)).thenReturn(properties);
148         when(controller.getDrools()).thenReturn(drools);
149
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());
153         });
154
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);
159
160             if (!(object instanceof ToscaPolicy)) {
161                 return true;
162             }
163
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);
169
170             return true;
171         });
172
173         when(drools.delete(any())).thenAnswer(args -> {
174             Class<?> clazz = args.getArgument(0);
175             facts.removeIf(obj -> obj.getClass() == clazz);
176             return null;
177         });
178
179         // handle rule listener registration and deregistration with the kieSession
180         doAnswer(args -> {
181             ruleListeners.add(args.getArgument(0));
182             return null;
183         }).when(kieSession).addEventListener(any(RuleRuntimeEventListener.class));
184
185         doAnswer(args -> {
186             ruleListeners.remove(args.getArgument(0));
187             return null;
188         }).when(kieSession).removeEventListener(any(RuleRuntimeEventListener.class));
189
190         // handle agenda listener registration and deregistration with the kieSession
191         doAnswer(args -> {
192             agendaListeners.add(args.getArgument(0));
193             return null;
194         }).when(kieSession).addEventListener(any(AgendaEventListener.class));
195
196         rules = new MyRules();
197
198         rules.configure(RESOURCE_DIR);
199         rules.start();
200     }
201
202     @Test
203     public void testRules() {
204         assertEquals(CONTROLLER_NAME, rules.getControllerName());
205
206         assertSame(engine, rules.getPdpd());
207         assertSame(repo, rules.getPdpdRepo());
208         assertSame(controller, rules.getController());
209     }
210
211     @Test
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();
218
219         verify(kieSession).addEventListener(any(RuleRuntimeEventListener.class));
220         verify(kieSession).addEventListener(any(AgendaEventListener.class));
221     }
222
223     @Test
224     public void testDestroy() {
225         rules.destroy();
226
227         verify(controllerFactory).shutdown(CONTROLLER_NAME);
228         verify(engine).stop();
229     }
230
231     @Test
232     public void testResetFacts() {
233         rules.resetFacts();
234
235         verify(drools).delete(ToscaPolicy.class);
236         verify(drools).delete(ControlLoopParams.class);
237         verify(drools).delete(ControlLoopEventManager.class);
238         verify(drools).delete(ControlLoopEvent.class);
239     }
240
241     @Test
242     public void testSetupPolicyFromTemplate_testGetPolicyFromTemplate() throws InterruptedException {
243         rules.setupPolicyFromTemplate("tosca-template.json", MY_POLICY);
244
245         assertThatIllegalArgumentException()
246                         .isThrownBy(() -> rules.setupPolicyFromTemplate("missing-file.json", "a-policy"));
247
248         // check interrupt case
249         checkInterrupt(() -> rules.setupPolicyFromTemplate("tosca-template.json", MY_POLICY),
250                         "policy operational.restart");
251     }
252
253     @Test
254     public void testSetupPolicyFromFile_testGetPolicyFromFile_testSetupPolicy() throws InterruptedException {
255         assertNotNull(rules.setupPolicyFromFile(POLICY_FILE));
256
257         assertThatIllegalArgumentException().isThrownBy(() -> rules.setupPolicyFromFile("missing-file.json"));
258
259         // check interrupt case
260         checkInterrupt(() -> rules.setupPolicyFromFile(POLICY_FILE), "policy " + POLICY_FILE);
261     }
262
263     @Test
264     public void testRuleListenerLogger() {
265         Rule rule = mock(Rule.class);
266         when(rule.getName()).thenReturn(MY_RULE_NAME);
267
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)));
274
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)));
281
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)));
288     }
289
290     @Test
291     public void testAgendaListenerLogger() {
292         Rule rule = mock(Rule.class);
293         when(rule.getName()).thenReturn(MY_RULE_NAME);
294
295         Match match = mock(Match.class);
296         when(match.getRule()).thenReturn(rule);
297
298         // create
299         MatchCreatedEvent create = mock(MatchCreatedEvent.class);
300         when(create.getMatch()).thenReturn(match);
301         checkLogging("match created", () -> agendaListeners.forEach(listener -> listener.matchCreated(create)));
302
303         // cancel
304         MatchCancelledEvent cancel = mock(MatchCancelledEvent.class);
305         when(cancel.getMatch()).thenReturn(match);
306         checkLogging("match cancelled", () -> agendaListeners.forEach(listener -> listener.matchCancelled(cancel)));
307
308         // before-fire
309         BeforeMatchFiredEvent before = mock(BeforeMatchFiredEvent.class);
310         when(before.getMatch()).thenReturn(match);
311         // @formatter:off
312         checkLogging("before match fired",
313             () -> agendaListeners.forEach(listener -> listener.beforeMatchFired(before)));
314         // @formatter:on
315
316         // after-fire
317         AfterMatchFiredEvent after = mock(AfterMatchFiredEvent.class);
318         when(after.getMatch()).thenReturn(match);
319         checkLogging("after match fired", () -> agendaListeners.forEach(listener -> listener.afterMatchFired(after)));
320     }
321
322     @Test
323     public void testMakePdpd_testMakePdpdRepo() {
324         // need rules that makes real objects
325         rules = new Rules(CONTROLLER_NAME);
326
327         assertNotNull(rules.getPdpd());
328         assertNotNull(rules.getPdpdRepo());
329     }
330
331     private void checkInterrupt(Runnable command, String expectedMsg) throws InterruptedException {
332         rules = new MyRules() {
333             @Override
334             protected ToscaPolicy setupPolicy(ToscaPolicy policy) throws InterruptedException {
335                 throw new InterruptedException(EXPECTED_EXCEPTION);
336             }
337         };
338         rules.configure(RESOURCE_DIR);
339         rules.start();
340
341         BlockingQueue<IllegalArgumentException> exceptions = new LinkedBlockingQueue<>();
342
343         Thread thread = new Thread(() -> {
344             try {
345                 command.run();
346             } catch (IllegalArgumentException e) {
347                 exceptions.add(e);
348             }
349         });
350
351         thread.setDaemon(true);
352         thread.start();
353
354         assertThat(exceptions.poll(10, TimeUnit.SECONDS)).isNotNull().hasMessage(expectedMsg)
355                         .hasCauseInstanceOf(InterruptedException.class);
356     }
357
358     private void checkLogging(String expectedMsg, Runnable command) {
359         appender.clearExtractions();
360         command.run();
361         List<String> messages = appender.getExtracted();
362         assertEquals(1, messages.size());
363         assertThat(messages.get(0)).contains(expectedMsg);
364     }
365
366     protected void notifyInserted(Object object) {
367         // add it to our list
368         facts.add(object);
369
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));
374
375         // increase code coverage by adding a random object
376         ObjectInsertedEvent event = mock(ObjectInsertedEvent.class);
377         when(event.getObject()).thenReturn(object);
378
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);
384         }
385
386         ruleListeners.forEach(listener -> listener.objectInserted(event));
387     }
388
389     private class MyRules extends Rules {
390         public MyRules() {
391             super(CONTROLLER_NAME);
392         }
393
394         @Override
395         protected PolicyEngine makeEngine() {
396             return engine;
397         }
398
399         @Override
400         protected SystemPersistence makePdpdRepo() {
401             return repo;
402         }
403
404         @Override
405         protected KieSession getKieSession() {
406             return kieSession;
407         }
408
409         @Override
410         protected PolicyControllerFactory getControllerFactory() {
411             return controllerFactory;
412         }
413
414         @Override
415         protected void installArtifact(File kmoduleFile, File pomFile, String resourceDir, List<File> ruleFiles) {
416             installed = true;
417         }
418     }
419 }