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