8272205d2a6082a4842ba0ff99df686d7574a376
[policy/drools-applications.git] /
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP
4  * ================================================================================
5  * Copyright (C) 2020-2022 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
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
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=========================================================
20  */
21
22 package org.onap.policy.controlloop.common.rules.test;
23
24 import static org.awaitility.Awaitility.await;
25 import static org.junit.jupiter.api.Assertions.assertEquals;
26 import static org.junit.jupiter.api.Assertions.assertNotNull;
27 import static org.junit.jupiter.api.Assertions.assertTrue;
28
29 import java.io.File;
30 import java.io.FileNotFoundException;
31 import java.io.IOException;
32 import java.util.Collections;
33 import java.util.List;
34 import java.util.Properties;
35 import java.util.concurrent.CountDownLatch;
36 import java.util.concurrent.TimeUnit;
37 import lombok.Getter;
38 import org.apache.commons.lang3.StringUtils;
39 import org.kie.api.event.rule.AfterMatchFiredEvent;
40 import org.kie.api.event.rule.BeforeMatchFiredEvent;
41 import org.kie.api.event.rule.DefaultAgendaEventListener;
42 import org.kie.api.event.rule.DefaultRuleRuntimeEventListener;
43 import org.kie.api.event.rule.MatchCancelledEvent;
44 import org.kie.api.event.rule.MatchCreatedEvent;
45 import org.kie.api.event.rule.ObjectDeletedEvent;
46 import org.kie.api.event.rule.ObjectInsertedEvent;
47 import org.kie.api.event.rule.ObjectUpdatedEvent;
48 import org.kie.api.event.rule.RuleRuntimeEventListener;
49 import org.kie.api.runtime.KieSession;
50 import org.onap.policy.common.utils.coder.CoderException;
51 import org.onap.policy.common.utils.coder.StandardCoder;
52 import org.onap.policy.common.utils.logging.LoggerUtils;
53 import org.onap.policy.common.utils.resources.ResourceUtils;
54 import org.onap.policy.controlloop.ControlLoopEvent;
55 import org.onap.policy.controlloop.drl.legacy.ControlLoopParams;
56 import org.onap.policy.controlloop.eventmanager.ControlLoopEventManager;
57 import org.onap.policy.drools.controller.DroolsController;
58 import org.onap.policy.drools.persistence.SystemPersistence;
59 import org.onap.policy.drools.persistence.SystemPersistenceConstants;
60 import org.onap.policy.drools.system.PolicyController;
61 import org.onap.policy.drools.system.PolicyControllerConstants;
62 import org.onap.policy.drools.system.PolicyControllerFactory;
63 import org.onap.policy.drools.system.PolicyEngine;
64 import org.onap.policy.drools.system.PolicyEngineConstants;
65 import org.onap.policy.drools.util.KieUtils;
66 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy;
67 import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate;
68 import org.slf4j.Logger;
69 import org.slf4j.LoggerFactory;
70
71 /**
72  * Mechanism by which junit tests can manage the rule engine.
73  */
74 public class Rules {
75     private static final Logger logger = LoggerFactory.getLogger(Rules.class);
76     private static final StandardCoder coder = new StandardCoder();
77     private static final String POLICY_MSG = "policy ";
78
79     /**
80      * PDP-D Engine.
81      */
82     @Getter
83     private final PolicyEngine pdpd = makeEngine();
84
85     /**
86      * PDP-D Configuration Repository.
87      */
88     @Getter
89     private final SystemPersistence pdpdRepo = makePdpdRepo();
90
91
92     @Getter
93     private final String controllerName;
94
95     @Getter
96     private PolicyController controller;
97
98
99     /**
100      * Constructs the object.
101      *
102      * @param controllerName name of the controller
103      */
104     public Rules(String controllerName) {
105         this.controllerName = controllerName;
106     }
107
108     /**
109      * Configures various items, including the PDP-D Engine.
110      *
111      * @param resourceDir path to resource directory
112      */
113     public void configure(String resourceDir) {
114         pdpdRepo.setConfigurationDir("src/test/resources/config");
115
116         try {
117             var kmoduleFile = new File(resourceDir + "/META-INF/kmodule.xml");
118             var pomFile = new File("src/test/resources/" + controllerName + ".pom");
119             var resourceDir2 = resourceDir + "/org/onap/policy/controlloop/";
120             var ruleFile = new File(resourceDir + File.separator + controllerName + ".drl");
121             List<File> ruleFiles = Collections.singletonList(ruleFile);
122
123             installArtifact(kmoduleFile, pomFile, resourceDir2, ruleFiles);
124         } catch (IOException e) {
125             throw new IllegalArgumentException("cannot configure KIE session for " + controllerName, e);
126         }
127
128         setupLogging();
129
130         pdpd.configure(new Properties());
131     }
132
133     /**
134      * Starts various items, including the PDP-D Engine.
135      */
136     public void start() {
137         controller = pdpd.createPolicyController(controllerName, pdpdRepo.getControllerProperties(controllerName));
138         pdpd.start();
139
140         setupDroolsLogging();
141     }
142
143     /**
144      * Stop PDP-D.
145      */
146     public void destroy() {
147         getControllerFactory().shutdown(controllerName);
148         pdpd.stop();
149     }
150
151     /**
152      * Removes various facts from working memory, including the Policy and Params, as well
153      * as any event managers and events.
154      */
155     public void resetFacts() {
156         List<Class<?>> classes = List.of(ToscaPolicy.class, ControlLoopParams.class, ControlLoopEventManager.class,
157                         ControlLoopEvent.class);
158
159         // delete all objects of the listed classes
160         DroolsController drools = controller.getDrools();
161         classes.forEach(drools::delete);
162
163         // wait for them to be deleted
164         for (Class<?> clazz : classes) {
165             await(clazz.getSimpleName()).atMost(5, TimeUnit.SECONDS)
166                             .until(() -> drools.facts(controllerName, clazz).isEmpty());
167         }
168     }
169
170     /**
171      * Installs a policy from policy/models (examples) repo.
172      */
173     public ToscaPolicy setupPolicyFromTemplate(String templatePath, String policyName) {
174         try {
175             return setupPolicy(getPolicyFromTemplate(templatePath, policyName));
176
177         } catch (CoderException e) {
178             throw new IllegalArgumentException(POLICY_MSG + policyName, e);
179
180         } catch (InterruptedException e) {
181             Thread.currentThread().interrupt();
182             throw new IllegalArgumentException(POLICY_MSG + policyName, e);
183         }
184     }
185
186     private ToscaPolicy getPolicyFromTemplate(String resourcePath, String policyName) throws CoderException {
187         var policyJson = ResourceUtils.getResourceAsString(resourcePath);
188         if (policyJson == null) {
189             throw new CoderException(new FileNotFoundException(resourcePath));
190         }
191
192         ToscaServiceTemplate serviceTemplate = coder.decode(policyJson, ToscaServiceTemplate.class);
193         ToscaPolicy policy = serviceTemplate.getToscaTopologyTemplate().getPolicies().get(0).get(policyName);
194         assertNotNull(policy);
195
196         /*
197          * name and version are used within a drl. api component and drools core will
198          * ensure that these are populated.
199          */
200         if (StringUtils.isBlank(policy.getName())) {
201             policy.setName(policyName);
202         }
203
204         if (StringUtils.isBlank(policy.getVersion())) {
205             policy.setVersion(policy.getTypeVersion());
206         }
207
208         return serviceTemplate.getToscaTopologyTemplate().getPolicies().get(0).get(policyName);
209     }
210
211     /**
212      * Installs a given policy.
213      */
214     public ToscaPolicy setupPolicyFromFile(String policyPath) {
215         try {
216             return setupPolicy(getPolicyFromFile(policyPath));
217
218         } catch (CoderException e) {
219             throw new IllegalArgumentException(POLICY_MSG + policyPath, e);
220
221         } catch (InterruptedException e) {
222             Thread.currentThread().interrupt();
223             throw new IllegalArgumentException(POLICY_MSG + policyPath, e);
224         }
225     }
226
227     /**
228      * Get policy from file.
229      */
230     public static ToscaPolicy getPolicyFromFile(String policyPath) throws CoderException {
231         var policyJson = ResourceUtils.getResourceAsString(policyPath);
232         if (policyJson == null) {
233             throw new CoderException(new FileNotFoundException(policyPath));
234         }
235
236         if (policyPath.startsWith("policies/")) {
237             // using policy/models examples where policies are wrapped with the ToscaServiceTemplate
238             // for API component provisioning
239             logger.info("retrieving policy from policy models examples");
240             ToscaServiceTemplate template = coder.decode(policyJson, ToscaServiceTemplate.class);
241             if (template.getToscaTopologyTemplate().getPolicies().size() == 1) {
242                 return template.getToscaTopologyTemplate().getPolicies().get(0).values().iterator().next();
243             }
244         }
245
246         return coder.decode(policyJson, ToscaPolicy.class);
247     }
248
249     protected ToscaPolicy setupPolicy(ToscaPolicy policy) throws InterruptedException {
250         final KieObjectExpectedCallback<?> policyTracker = new KieObjectInsertedExpectedCallback<>(policy);
251         final KieObjectExpectedCallback<?> paramsTracker =
252                         new KieClassInsertedExpectedCallback<>(ControlLoopParams.class);
253
254         controller.getDrools().offer(policy);
255
256         assertTrue(policyTracker.isNotified());
257         assertTrue(paramsTracker.isNotified());
258
259         assertEquals(1, controller.getDrools().facts(controllerName, ToscaPolicy.class).stream()
260                         .filter(anotherPolicy -> anotherPolicy == policy).count());
261
262         assertEquals(1, controller.getDrools().facts(controllerName, ControlLoopParams.class).stream()
263                         .filter(params -> params.getToscaPolicy() == policy).count());
264         return policy;
265     }
266
267     /**
268      * Sets up overall logging.
269      */
270     private void setupLogging() {
271         LoggerUtils.setLevel(LoggerUtils.ROOT_LOGGER, "WARN");
272         LoggerUtils.setLevel("org.eclipse.jetty", "WARN");
273         LoggerUtils.setLevel("org.onap.policy.controlloop", "INFO");
274         LoggerUtils.setLevel("network", "INFO");
275     }
276
277     /**
278      * Sets up Drools Logging for events of interest.
279      */
280     private void setupDroolsLogging() {
281         var session = getKieSession();
282
283         session.addEventListener(new RuleListenerLogger());
284         session.addEventListener(new AgendaListenerLogger());
285     }
286
287     /**
288      * Logs Modifications to Working Memory.
289      */
290     private static class RuleListenerLogger implements RuleRuntimeEventListener {
291         @Override
292         public void objectInserted(ObjectInsertedEvent event) {
293             String ruleName = (event.getRule() != null) ? event.getRule().getName() : "null";
294             logger.info("RULE {}: inserted {}", ruleName, event.getObject());
295         }
296
297         @Override
298         public void objectUpdated(ObjectUpdatedEvent event) {
299             String ruleName = (event.getRule() != null) ? event.getRule().getName() : "null";
300             logger.info("RULE {}: updated {}", ruleName, event.getObject());
301
302         }
303
304         @Override
305         public void objectDeleted(ObjectDeletedEvent event) {
306             String ruleName = (event.getRule() != null) ? event.getRule().getName() : "null";
307             logger.info("RULE {}: deleted {}", ruleName, event.getOldObject());
308         }
309     }
310
311     /**
312      * Logs Rule Matches.
313      */
314     private static class AgendaListenerLogger extends DefaultAgendaEventListener {
315         @Override
316         public void matchCreated(MatchCreatedEvent event) {
317             logger.info("RULE {}: match created", event.getMatch().getRule().getName());
318         }
319
320         @Override
321         public void matchCancelled(MatchCancelledEvent event) {
322             logger.info("RULE {}: match cancelled", event.getMatch().getRule().getName());
323         }
324
325         @Override
326         public void beforeMatchFired(BeforeMatchFiredEvent event) {
327             logger.info("RULE {}: before match fired", event.getMatch().getRule().getName());
328         }
329
330         @Override
331         public void afterMatchFired(AfterMatchFiredEvent event) {
332             logger.info("RULE {}: after match fired", event.getMatch().getRule().getName());
333         }
334     }
335
336     /**
337      * Base Class to track Working Memory updates for objects of type T.
338      */
339     private abstract class KieObjectExpectedCallback<T> extends DefaultRuleRuntimeEventListener {
340         protected T subject;
341
342         protected CountDownLatch countDownLatch = new CountDownLatch(1);
343
344         public KieObjectExpectedCallback(T affected) {
345             subject = affected;
346             register();
347         }
348
349         public boolean isNotified() throws InterruptedException {
350             return countDownLatch.await(9L, TimeUnit.SECONDS);
351         }
352
353         protected void callbacked() {
354             unregister();
355             countDownLatch.countDown();
356         }
357
358         public KieObjectExpectedCallback<T> register() {
359             getKieSession().addEventListener(this);
360             return this;
361         }
362
363         public KieObjectExpectedCallback<T> unregister() {
364             getKieSession().removeEventListener(this);
365             return this;
366         }
367     }
368
369     /**
370      * Tracks inserts in Working Memory for an object of type T.
371      */
372     private class KieObjectInsertedExpectedCallback<T> extends KieObjectExpectedCallback<T> {
373         public KieObjectInsertedExpectedCallback(T affected) {
374             super(affected);
375         }
376
377         @Override
378         public void objectInserted(ObjectInsertedEvent event) {
379             if (subject == event.getObject()) {
380                 callbacked();
381             }
382         }
383     }
384
385     /**
386      * Tracks inserts in Working Memory for any object of class T.
387      */
388     private class KieClassInsertedExpectedCallback<T> extends KieObjectInsertedExpectedCallback<T> {
389
390         public KieClassInsertedExpectedCallback(T affected) {
391             super(affected);
392         }
393
394         @Override
395         public void objectInserted(ObjectInsertedEvent event) {
396             if (subject == event.getObject().getClass()) {
397                 callbacked();
398             }
399         }
400     }
401
402     // these may be overridden by junit tests
403
404
405     protected PolicyEngine makeEngine() {
406         return PolicyEngineConstants.getManager();
407     }
408
409
410     protected SystemPersistence makePdpdRepo() {
411         return SystemPersistenceConstants.getManager();
412     }
413
414     protected KieSession getKieSession() {
415         return getControllerFactory().get(controllerName).getDrools().getContainer().getPolicySession(controllerName)
416                         .getKieSession();
417     }
418
419     protected PolicyControllerFactory getControllerFactory() {
420         return PolicyControllerConstants.getFactory();
421     }
422
423     protected void installArtifact(File kmoduleFile, File pomFile, String resourceDir, List<File> ruleFiles)
424                     throws IOException {
425
426         KieUtils.installArtifact(kmoduleFile, pomFile, resourceDir, ruleFiles);
427     }
428 }