2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2020-2022 AT&T Intellectual Property. All rights reserved.
6 * Modifications Copyright (C) 2023-2024 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.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;
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;
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;
72 * Mechanism by which junit tests can manage the rule engine.
76 private static final Logger logger = LoggerFactory.getLogger(Rules.class);
77 private static final StandardCoder coder = new StandardCoder();
78 private static final String POLICY_MSG = "policy ";
83 private final PolicyEngine pdpd = makeEngine();
86 * PDP-D Configuration Repository.
88 private final SystemPersistence pdpdRepo = makePdpdRepo();
91 private final String controllerName;
93 private PolicyController controller;
97 * Constructs the object.
99 * @param controllerName name of the controller
101 public Rules(String controllerName) {
102 this.controllerName = controllerName;
106 * Configures various items, including the PDP-D Engine.
108 * @param resourceDir path to resource directory
110 public void configure(String resourceDir) {
111 pdpdRepo.setConfigurationDir("src/test/resources/config");
114 var kmoduleFile = new File(resourceDir + "/META-INF/kmodule.xml");
115 var pomFile = new File("src/test/resources/" + controllerName + ".pom");
116 var resourceDir2 = resourceDir + "/org/onap/policy/controlloop/";
117 var ruleFile = new File(resourceDir + File.separator + controllerName + ".drl");
118 List<File> ruleFiles = Collections.singletonList(ruleFile);
120 installArtifact(kmoduleFile, pomFile, resourceDir2, ruleFiles);
121 } catch (IOException e) {
122 throw new IllegalArgumentException("cannot configure KIE session for " + controllerName, e);
127 pdpd.configure(new Properties());
131 * Starts various items, including the PDP-D Engine.
133 public void start() {
134 controller = pdpd.createPolicyController(controllerName, pdpdRepo.getControllerProperties(controllerName));
137 setupDroolsLogging();
143 public void destroy() {
144 getControllerFactory().shutdown(controllerName);
149 * Removes various facts from working memory, including the Policy and Params, as well
150 * as any event managers and events.
152 public void resetFacts() {
153 List<Class<?>> classes = List.of(ToscaPolicy.class, ControlLoopParams.class, ControlLoopEventManager.class,
154 ControlLoopEvent.class);
156 // delete all objects of the listed classes
157 DroolsController drools = controller.getDrools();
158 classes.forEach(drools::delete);
160 // wait for them to be deleted
161 for (Class<?> clazz : classes) {
162 await(clazz.getSimpleName()).atMost(5, TimeUnit.SECONDS)
163 .until(() -> drools.facts(controllerName, clazz).isEmpty());
168 * Installs a policy from policy/models (examples) repo.
170 public ToscaPolicy setupPolicyFromTemplate(String templatePath, String policyName) {
172 return setupPolicy(getPolicyFromTemplate(templatePath, policyName));
174 } catch (CoderException e) {
175 throw new IllegalArgumentException(POLICY_MSG + policyName, e);
177 } catch (InterruptedException e) {
178 Thread.currentThread().interrupt();
179 throw new IllegalArgumentException(POLICY_MSG + policyName, e);
183 private ToscaPolicy getPolicyFromTemplate(String resourcePath, String policyName) throws CoderException {
184 var policyJson = ResourceUtils.getResourceAsString(resourcePath);
185 if (policyJson == null) {
186 throw new CoderException(new FileNotFoundException(resourcePath));
189 ToscaServiceTemplate serviceTemplate = coder.decode(policyJson, ToscaServiceTemplate.class);
190 ToscaPolicy policy = serviceTemplate.getToscaTopologyTemplate().getPolicies().get(0).get(policyName);
191 assertNotNull(policy);
194 * name and version are used within a drl. api component and drools core will
195 * ensure that these are populated.
197 if (StringUtils.isBlank(policy.getName())) {
198 policy.setName(policyName);
201 if (StringUtils.isBlank(policy.getVersion())) {
202 policy.setVersion(policy.getTypeVersion());
205 return serviceTemplate.getToscaTopologyTemplate().getPolicies().get(0).get(policyName);
209 * Installs a given policy.
211 public ToscaPolicy setupPolicyFromFile(String policyPath) {
213 return setupPolicy(getPolicyFromFile(policyPath));
215 } catch (CoderException e) {
216 throw new IllegalArgumentException(POLICY_MSG + policyPath, e);
218 } catch (InterruptedException e) {
219 Thread.currentThread().interrupt();
220 throw new IllegalArgumentException(POLICY_MSG + policyPath, e);
225 * Get policy from file.
227 public static ToscaPolicy getPolicyFromFile(String policyPath) throws CoderException {
228 var policyJson = ResourceUtils.getResourceAsString(policyPath);
229 if (policyJson == null) {
230 throw new CoderException(new FileNotFoundException(policyPath));
233 if (policyPath.startsWith("policies/")) {
234 // using policy/models examples where policies are wrapped with the ToscaServiceTemplate
235 // for API component provisioning
236 logger.info("retrieving policy from policy models examples");
237 ToscaServiceTemplate template = coder.decode(policyJson, ToscaServiceTemplate.class);
238 if (template.getToscaTopologyTemplate().getPolicies().size() == 1) {
239 return template.getToscaTopologyTemplate().getPolicies().get(0).values().iterator().next();
243 return coder.decode(policyJson, ToscaPolicy.class);
246 protected ToscaPolicy setupPolicy(ToscaPolicy policy) throws InterruptedException {
247 final KieObjectExpectedCallback<?> policyTracker = new KieObjectInsertedExpectedCallback<>(policy);
248 final KieObjectExpectedCallback<?> paramsTracker =
249 new KieClassInsertedExpectedCallback<>(ControlLoopParams.class);
251 controller.getDrools().offer(policy);
253 assertTrue(policyTracker.isNotified());
254 assertTrue(paramsTracker.isNotified());
256 assertEquals(1, controller.getDrools().facts(controllerName, ToscaPolicy.class).stream()
257 .filter(anotherPolicy -> anotherPolicy == policy).count());
259 assertEquals(1, controller.getDrools().facts(controllerName, ControlLoopParams.class).stream()
260 .filter(params -> params.getToscaPolicy() == policy).count());
265 * Sets up overall logging.
267 private void setupLogging() {
268 LoggerUtils.setLevel(LoggerUtils.ROOT_LOGGER, "WARN");
269 LoggerUtils.setLevel("org.eclipse.jetty", "WARN");
270 LoggerUtils.setLevel("org.onap.policy.controlloop", "INFO");
271 LoggerUtils.setLevel("network", "INFO");
275 * Sets up Drools Logging for events of interest.
277 private void setupDroolsLogging() {
278 var session = getKieSession();
280 session.addEventListener(new RuleListenerLogger());
281 session.addEventListener(new AgendaListenerLogger());
285 * Logs Modifications to Working Memory.
287 private static class RuleListenerLogger implements RuleRuntimeEventListener {
289 public void objectInserted(ObjectInsertedEvent event) {
290 String ruleName = (event.getRule() != null) ? event.getRule().getName() : "null";
291 logger.info("RULE {}: inserted {}", ruleName, event.getObject());
295 public void objectUpdated(ObjectUpdatedEvent event) {
296 String ruleName = (event.getRule() != null) ? event.getRule().getName() : "null";
297 logger.info("RULE {}: updated {}", ruleName, event.getObject());
302 public void objectDeleted(ObjectDeletedEvent event) {
303 String ruleName = (event.getRule() != null) ? event.getRule().getName() : "null";
304 logger.info("RULE {}: deleted {}", ruleName, event.getOldObject());
311 private static class AgendaListenerLogger extends DefaultAgendaEventListener {
313 public void matchCreated(MatchCreatedEvent event) {
314 logger.info("RULE {}: match created", event.getMatch().getRule().getName());
318 public void matchCancelled(MatchCancelledEvent event) {
319 logger.info("RULE {}: match cancelled", event.getMatch().getRule().getName());
323 public void beforeMatchFired(BeforeMatchFiredEvent event) {
324 logger.info("RULE {}: before match fired", event.getMatch().getRule().getName());
328 public void afterMatchFired(AfterMatchFiredEvent event) {
329 logger.info("RULE {}: after match fired", event.getMatch().getRule().getName());
334 * Base Class to track Working Memory updates for objects of type T.
336 private abstract class KieObjectExpectedCallback<T> extends DefaultRuleRuntimeEventListener {
339 protected CountDownLatch countDownLatch = new CountDownLatch(1);
341 public KieObjectExpectedCallback(T affected) {
346 public boolean isNotified() throws InterruptedException {
347 return countDownLatch.await(9L, TimeUnit.SECONDS);
350 protected void callbacked() {
352 countDownLatch.countDown();
355 public KieObjectExpectedCallback<T> register() {
356 getKieSession().addEventListener(this);
360 public KieObjectExpectedCallback<T> unregister() {
361 getKieSession().removeEventListener(this);
367 * Tracks inserts in Working Memory for an object of type T.
369 private class KieObjectInsertedExpectedCallback<T> extends KieObjectExpectedCallback<T> {
370 public KieObjectInsertedExpectedCallback(T affected) {
375 public void objectInserted(ObjectInsertedEvent event) {
376 if (subject == event.getObject()) {
383 * Tracks inserts in Working Memory for any object of class T.
385 private class KieClassInsertedExpectedCallback<T> extends KieObjectInsertedExpectedCallback<T> {
387 public KieClassInsertedExpectedCallback(T affected) {
392 public void objectInserted(ObjectInsertedEvent event) {
393 if (subject == event.getObject().getClass()) {
399 // these may be overridden by junit tests
402 protected PolicyEngine makeEngine() {
403 return PolicyEngineConstants.getManager();
407 protected SystemPersistence makePdpdRepo() {
408 return SystemPersistenceConstants.getManager();
411 protected KieSession getKieSession() {
412 return getControllerFactory().get(controllerName).getDrools().getContainer().getPolicySession(controllerName)
416 protected PolicyControllerFactory getControllerFactory() {
417 return PolicyControllerConstants.getFactory();
420 protected void installArtifact(File kmoduleFile, File pomFile, String resourceDir, List<File> ruleFiles)
423 KieUtils.installArtifact(kmoduleFile, pomFile, resourceDir, ruleFiles);