0ff3de505a68d5eb4426e5a51ce4bd87904df77a
[policy/drools-applications.git] /
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP
4  * ================================================================================
5  * Copyright (C) 2020 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;
22
23 import static org.assertj.core.api.Assertions.assertThat;
24 import static org.awaitility.Awaitility.await;
25 import static org.junit.Assert.assertEquals;
26 import static org.junit.Assert.assertNotNull;
27 import static org.junit.Assert.assertTrue;
28
29 import java.io.IOException;
30 import java.nio.charset.StandardCharsets;
31 import java.nio.file.Files;
32 import java.nio.file.Path;
33 import java.nio.file.Paths;
34 import java.util.Collections;
35 import java.util.LinkedList;
36 import java.util.Properties;
37 import java.util.Queue;
38 import java.util.concurrent.CountDownLatch;
39 import java.util.concurrent.TimeUnit;
40 import lombok.Getter;
41 import org.apache.commons.io.IOUtils;
42 import org.apache.commons.lang3.StringUtils;
43 import org.kie.api.event.rule.AfterMatchFiredEvent;
44 import org.kie.api.event.rule.BeforeMatchFiredEvent;
45 import org.kie.api.event.rule.DefaultAgendaEventListener;
46 import org.kie.api.event.rule.DefaultRuleRuntimeEventListener;
47 import org.kie.api.event.rule.MatchCancelledEvent;
48 import org.kie.api.event.rule.MatchCreatedEvent;
49 import org.kie.api.event.rule.ObjectDeletedEvent;
50 import org.kie.api.event.rule.ObjectInsertedEvent;
51 import org.kie.api.event.rule.ObjectUpdatedEvent;
52 import org.kie.api.event.rule.RuleRuntimeEventListener;
53 import org.kie.api.runtime.KieSession;
54 import org.onap.policy.common.endpoints.event.comm.Topic;
55 import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
56 import org.onap.policy.common.endpoints.event.comm.TopicEndpointManager;
57 import org.onap.policy.common.endpoints.event.comm.TopicListener;
58 import org.onap.policy.common.endpoints.http.client.HttpClientConfigException;
59 import org.onap.policy.common.endpoints.http.client.HttpClientFactoryInstance;
60 import org.onap.policy.common.endpoints.http.server.HttpServletServerFactoryInstance;
61 import org.onap.policy.common.utils.coder.Coder;
62 import org.onap.policy.common.utils.coder.CoderException;
63 import org.onap.policy.common.utils.coder.StandardCoder;
64 import org.onap.policy.common.utils.resources.ResourceUtils;
65 import org.onap.policy.controlloop.drl.legacy.ControlLoopParams;
66 import org.onap.policy.controlloop.eventmanager.ControlLoopEventManager2;
67 import org.onap.policy.drools.controller.DroolsController;
68 import org.onap.policy.drools.persistence.SystemPersistence;
69 import org.onap.policy.drools.persistence.SystemPersistenceConstants;
70 import org.onap.policy.drools.protocol.coders.EventProtocolCoderConstants;
71 import org.onap.policy.drools.system.PolicyController;
72 import org.onap.policy.drools.system.PolicyControllerConstants;
73 import org.onap.policy.drools.system.PolicyEngine;
74 import org.onap.policy.drools.system.PolicyEngineConstants;
75 import org.onap.policy.drools.util.KieUtils;
76 import org.onap.policy.drools.utils.logging.LoggerUtil;
77 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy;
78 import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate;
79 import org.onap.policy.simulators.Util;
80 import org.slf4j.Logger;
81 import org.slf4j.LoggerFactory;
82
83 /**
84  * Use Cases Tests Framework.
85  */
86 public abstract class FrankfurtBase {
87
88     private static final Logger logger = LoggerFactory.getLogger(FrankfurtBase.class);
89     private static final StandardCoder coder = new StandardCoder();
90
91     /**
92      * PDP-D Engine.
93      */
94     protected static final PolicyEngine pdpD = PolicyEngineConstants.getManager();
95
96     /**
97      * PDP-D Configuration Repository.
98      */
99     protected static final SystemPersistence repo = SystemPersistenceConstants.getManager();
100
101     /**
102      * Frankfurt controller and session name.
103      */
104     protected static final String CONTROLLER_NAME = "frankfurt";
105
106     /**
107      * Frankfurt controller.
108      */
109     protected static PolicyController controller;
110
111     /*
112      * Canonical Topic Names.
113      */
114     protected static final String DCAE_TOPIC = "DCAE_TOPIC";
115     protected static final String APPC_LCM_WRITE_TOPIC = "APPC-LCM-WRITE";
116     protected static final String POLICY_CL_MGT_TOPIC = "POLICY-CL-MGT";
117     protected static final String APPC_LCM_READ_TOPIC = "APPC-LCM-READ";
118     protected static final String APPC_CL_TOPIC = "APPC-CL";
119
120     protected static void initConfigDir() {
121         SystemPersistenceConstants.getManager().setConfigurationDir("src/test/resources/config");
122     }
123
124     protected void resetFacts() {
125         DroolsController drools = controller.getDrools();
126         drools.delete(ToscaPolicy.class);
127         drools.delete(ControlLoopParams.class);
128         drools.delete(ControlLoopEventManager2.class);
129         drools.delete(VirtualControlLoopEvent.class);
130     }
131
132     /**
133      * Sets up overall logging.
134      */
135     protected static void setupLogging() {
136         LoggerUtil.setLevel(LoggerUtil.ROOT_LOGGER, "WARN");
137         LoggerUtil.setLevel("org.eclipse.jetty", "WARN");
138         LoggerUtil.setLevel("org.onap.policy.controlloop", "INFO");
139         LoggerUtil.setLevel("network", "INFO");
140     }
141
142     /**
143      * Sets up Drools Logging for events of interest.
144      */
145     protected static void setupDroolsLogging() {
146         KieSession session = PolicyControllerConstants.getFactory().get(CONTROLLER_NAME).getDrools().getContainer()
147                         .getPolicySession(CONTROLLER_NAME).getKieSession();
148
149         session.addEventListener(new RuleListenerLogger());
150         session.addEventListener(new AgendaListenerLogger());
151     }
152
153     /**
154      * Sets up Http Clients specified in the property file.
155      */
156     protected static void setUpHttpClients() {
157         try {
158             HttpClientFactoryInstance.getClientFactory().build(
159                             SystemPersistenceConstants.getManager().getHttpClientProperties("frankfurt"));
160         } catch (HttpClientConfigException e) {
161             throw new IllegalArgumentException("cannot initialize HTTP clients", e);
162         }
163     }
164
165     /**
166      * Sets up Simulators for use case testing.
167      */
168     protected static void setupSimulators() throws InterruptedException {
169         Util.buildAaiSim();
170         Util.buildSoSim();
171         Util.buildVfcSim();
172         Util.buildGuardSim();
173         Util.buildSdncSim();
174     }
175
176     /**
177      * Returns the runtime Control Loop Parameters associated with a Tosca Policy.
178      */
179     protected ControlLoopParams clParameters(ToscaPolicy policy) {
180         return controller.getDrools().facts(CONTROLLER_NAME, ControlLoopParams.class).stream()
181                         .filter((params) -> params.getToscaPolicy() == policy).findFirst().get();
182     }
183
184     protected ToscaPolicy getPolicyFromResource(String resourcePath, String policyName) throws CoderException {
185         String policyJson = ResourceUtils.getResourceAsString(resourcePath);
186         ToscaServiceTemplate serviceTemplate = coder.decode(policyJson, ToscaServiceTemplate.class);
187         ToscaPolicy policy = serviceTemplate.getToscaTopologyTemplate().getPolicies().get(0).get(policyName);
188         assertNotNull(policy);
189
190         /*
191          * name and version are used within a drl. api component and drools core will
192          * ensure that these are populated.
193          */
194         if (StringUtils.isBlank(policy.getName())) {
195             policy.setName(policyName);
196         }
197
198         if (StringUtils.isBlank(policy.getVersion())) {
199             policy.setVersion(policy.getTypeVersion());
200         }
201
202         return serviceTemplate.getToscaTopologyTemplate().getPolicies().get(0).get(policyName);
203     }
204
205     protected ToscaPolicy getPolicyFromFile(String policyPath) throws IOException, CoderException {
206         String rawPolicy = new String(Files.readAllBytes(Paths.get(policyPath)));
207         return coder.decode(rawPolicy, ToscaPolicy.class);
208     }
209
210     private ToscaPolicy setupPolicy(ToscaPolicy policy) throws InterruptedException {
211         final KieObjectExpectedCallback<?> policyTracker = new KieObjectInsertedExpectedCallback<>(policy);
212         final KieObjectExpectedCallback<?> paramsTracker =
213                         new KieClassInsertedExpectedCallback<>(ControlLoopParams.class);
214
215         controller.getDrools().offer(policy);
216
217         assertTrue(policyTracker.isNotified());
218         assertTrue(paramsTracker.isNotified());
219
220         assertEquals(1, controller.getDrools().facts(CONTROLLER_NAME, ToscaPolicy.class).stream()
221                         .filter((anotherPolicy) -> anotherPolicy == policy).count());
222
223         assertEquals(1, controller.getDrools().facts(CONTROLLER_NAME, ControlLoopParams.class).stream()
224                         .filter((params) -> params.getToscaPolicy() == policy).count());
225         return policy;
226     }
227
228     /**
229      * Installs a policy from policy/models (examples) repo.
230      */
231     protected ToscaPolicy setupPolicyFromResource(String resourcePath, String policyName)
232                     throws CoderException, InterruptedException {
233         return setupPolicy(getPolicyFromResource(resourcePath, policyName));
234     }
235
236
237     /**
238      * Installs a given policy.
239      */
240     protected ToscaPolicy setupPolicyFromFile(String policyPath)
241                     throws IOException, CoderException, InterruptedException {
242         return setupPolicy(getPolicyFromFile(policyPath));
243     }
244
245     /**
246      * Deletes a policy.
247      */
248     protected void deletePolicy(ToscaPolicy policy) throws InterruptedException {
249         ControlLoopParams clParams = clParameters(policy);
250         assertNotNull(clParams);
251
252         final KieObjectExpectedCallback<?> policyTracker = new KieObjectDeletedExpectedCallback<>(policy);
253         final KieObjectExpectedCallback<?> clParamsTracker = new KieObjectDeletedExpectedCallback<>(clParams);
254
255         controller.getDrools().delete(CONTROLLER_NAME, policy);
256         assertTrue(policyTracker.isNotified());
257         assertTrue(clParamsTracker.isNotified());
258
259         assertEquals(0, controller.getDrools().facts(CONTROLLER_NAME, ToscaPolicy.class).stream()
260                         .filter((anotherPolicy) -> anotherPolicy == policy).count());
261
262         assertEquals(0, controller.getDrools().facts(CONTROLLER_NAME, ControlLoopParams.class).stream()
263                         .filter((params) -> params.getPolicyName() == policy.getName()).count());
264     }
265
266     /**
267      * Prepare a PDP-D to test the Use Cases.
268      */
269     protected static void preparePdpD() throws IOException {
270         KieUtils.installArtifact(Paths.get("src/main/resources/META-INF/kmodule.xml").toFile(),
271                         Paths.get("src/test/resources/frankfurt.pom").toFile(),
272                         "src/main/resources/org/onap/policy/controlloop/",
273                         Collections.singletonList(Paths.get("src/main/resources/frankfurt.drl").toFile()));
274
275         repo.setConfigurationDir("src/test/resources/config");
276         pdpD.configure(new Properties());
277
278         controller = pdpD.createPolicyController(CONTROLLER_NAME, repo.getControllerProperties(CONTROLLER_NAME));
279         pdpD.start();
280
281         setupDroolsLogging();
282     }
283
284     /**
285      * Stop PDP-D.
286      */
287     protected static void stopPdpD() {
288         PolicyControllerConstants.getFactory().shutdown(CONTROLLER_NAME);
289         pdpD.stop();
290     }
291
292     /**
293      * Stops the http clients.
294      */
295     protected static void stopHttpClients() {
296         HttpClientFactoryInstance.getClientFactory().destroy();
297     }
298
299     /**
300      * Stop Simulators.
301      */
302     protected static void stopSimulators() {
303         HttpServletServerFactoryInstance.getServerFactory().destroy();
304     }
305
306     /**
307      * Creates a Topic Sink Callback tracker.
308      */
309     protected <T> TopicCallback<T> createTopicSinkCallback(String topicName, Class<T> clazz) {
310         return new TopicCallback<>(TopicEndpointManager.getManager().getNoopTopicSink(topicName), clazz);
311     }
312
313     /**
314      * Creates a Topic Sink Callback tracker.
315      */
316     protected <T> TopicCallback<T> createTopicSinkCallbackPlain(String topicName, Class<T> clazz, Coder coder) {
317         return new TopicCallbackCoder<>(TopicEndpointManager.getManager().getNoopTopicSink(topicName), clazz, coder);
318     }
319
320     /**
321      * Creates a Topic Source Callback tracker.
322      */
323     protected <T> TopicCallback<T> createTopicSourceCallback(String topicName, Class<T> clazz) {
324         return new TopicCallback<>(TopicEndpointManager.getManager().getNoopTopicSource(topicName), clazz);
325     }
326
327     /**
328      * Injects a message on a Topic Source.
329      */
330     protected void injectOnTopic(String topicName, Path onsetPath) throws IOException {
331         TopicEndpointManager.getManager().getNoopTopicSource(topicName)
332                         .offer(new String(Files.readAllBytes(onsetPath)));
333     }
334
335     /**
336      * Injects a message on a Topic Source, with the given substitution..
337      */
338     protected void injectOnTopic(String topicName, Path path, String newText) throws IOException {
339         String text = IOUtils.toString(path.toUri(), StandardCharsets.UTF_8);
340         text = text.replace("${replaceMe}", newText);
341         TopicEndpointManager.getManager().getNoopTopicSource(topicName).offer(text);
342     }
343
344     /**
345      * Waits for LOCK acquisition and getting a Permit from PDP-X to proceed.
346      */
347     protected void waitForLockAndPermit(ToscaPolicy policy, TopicCallback<VirtualControlLoopNotification> policyClMgt) {
348         String policyName = policy.getIdentifier().getName();
349
350         // TODO register a topic listener instead of using await() ?
351
352         await().until(() -> !policyClMgt.getMessages().isEmpty());
353         VirtualControlLoopNotification notif = policyClMgt.getMessages().remove();
354         assertEquals(ControlLoopNotificationType.ACTIVE, notif.getNotification());
355         assertEquals(policyName + ".EVENT", notif.getPolicyName());
356
357         await().until(() -> !policyClMgt.getMessages().isEmpty());
358         notif = policyClMgt.getMessages().remove();
359         assertEquals(ControlLoopNotificationType.OPERATION, notif.getNotification());
360         assertEquals(policyName + ".EVENT.MANAGER.PROCESSING", notif.getPolicyName());
361         assertThat(notif.getMessage()).startsWith("Sending guard query");
362
363         await().until(() -> !policyClMgt.getMessages().isEmpty());
364         notif = policyClMgt.getMessages().remove();
365         assertEquals(ControlLoopNotificationType.OPERATION, notif.getNotification());
366         assertEquals(policyName + ".EVENT.MANAGER.PROCESSING", notif.getPolicyName());
367         assertThat(notif.getMessage()).startsWith("Guard result").endsWith("Permit");
368
369         await().until(() -> !policyClMgt.getMessages().isEmpty());
370         notif = policyClMgt.getMessages().remove();
371         assertEquals(ControlLoopNotificationType.OPERATION, notif.getNotification());
372         assertEquals(policyName + ".EVENT.MANAGER.PROCESSING", notif.getPolicyName());
373         assertThat(notif.getMessage()).startsWith("actor=");
374     }
375
376     /**
377      * Waits for a FINAL SUCCESS transaction notification.
378      */
379     protected void waitForFinalSuccess(ToscaPolicy policy, TopicCallback<VirtualControlLoopNotification> policyClMgt) {
380         await().until(() -> !policyClMgt.getMessages().isEmpty());
381         assertEquals(ControlLoopNotificationType.FINAL_SUCCESS, policyClMgt.getMessages().peek().getNotification());
382         assertEquals(policy.getIdentifier().getName() + ".EVENT.MANAGER.FINAL",
383                         policyClMgt.getMessages().remove().getPolicyName());
384     }
385
386     /**
387      * Logs Modifications to Working Memory.
388      */
389     static class RuleListenerLogger implements RuleRuntimeEventListener {
390         @Override
391         public void objectInserted(ObjectInsertedEvent event) {
392             String ruleName = (event.getRule() != null) ? event.getRule().getName() : "null";
393             logger.info("RULE {}: inserted {}", ruleName, event.getObject());
394         }
395
396         @Override
397         public void objectUpdated(ObjectUpdatedEvent event) {
398             String ruleName = (event.getRule() != null) ? event.getRule().getName() : "null";
399             logger.info("RULE {}: updated {}", ruleName, event.getObject());
400
401         }
402
403         @Override
404         public void objectDeleted(ObjectDeletedEvent event) {
405             String ruleName = (event.getRule() != null) ? event.getRule().getName() : "null";
406             logger.info("RULE {}: deleted {}", ruleName, event.getOldObject());
407         }
408     }
409
410     /**
411      * Logs Rule Matches.
412      */
413     static class AgendaListenerLogger extends DefaultAgendaEventListener {
414         @Override
415         public void matchCreated(MatchCreatedEvent event) {
416             logger.info("RULE {}: match created", event.getMatch().getRule().getName());
417         }
418
419         @Override
420         public void matchCancelled(MatchCancelledEvent event) {
421             logger.info("RULE {}: match cancelled", event.getMatch().getRule().getName());
422         }
423
424         @Override
425         public void beforeMatchFired(BeforeMatchFiredEvent event) {
426             logger.info("RULE {}: before match fired", event.getMatch().getRule().getName());
427         }
428
429         @Override
430         public void afterMatchFired(AfterMatchFiredEvent event) {
431             logger.info("RULE {}: after match fired", event.getMatch().getRule().getName());
432         }
433     }
434
435     /**
436      * Base Class to track Working Memory updates for objects of type T.
437      */
438     abstract class KieObjectExpectedCallback<T> extends DefaultRuleRuntimeEventListener {
439         protected T subject;
440
441         protected CountDownLatch countDownLatch = new CountDownLatch(1);
442
443         public KieObjectExpectedCallback(T affected) {
444             subject = affected;
445             register();
446         }
447
448         public boolean isNotified() throws InterruptedException {
449             return countDownLatch.await(9L, TimeUnit.SECONDS);
450         }
451
452         protected void callbacked() {
453             unregister();
454             countDownLatch.countDown();
455         }
456
457         public KieObjectExpectedCallback<T> register() {
458             controller.getDrools().getContainer().getPolicySession(CONTROLLER_NAME).getKieSession()
459                             .addEventListener(this);
460             return this;
461         }
462
463         public KieObjectExpectedCallback<T> unregister() {
464             controller.getDrools().getContainer().getPolicySession(CONTROLLER_NAME).getKieSession()
465                             .removeEventListener(this);
466             return this;
467         }
468     }
469
470     /**
471      * Tracks inserts in Working Memory for an object of type T.
472      */
473     class KieObjectInsertedExpectedCallback<T> extends KieObjectExpectedCallback<T> {
474         public KieObjectInsertedExpectedCallback(T affected) {
475             super(affected);
476         }
477
478         @Override
479         public void objectInserted(ObjectInsertedEvent event) {
480             if (subject == event.getObject()) {
481                 callbacked();
482             }
483         }
484     }
485
486     /**
487      * Tracks deletes in Working Memory of an object of type T.
488      */
489     class KieObjectDeletedExpectedCallback<T> extends KieObjectExpectedCallback<T> {
490         public KieObjectDeletedExpectedCallback(T affected) {
491             super(affected);
492         }
493
494         @Override
495         public void objectDeleted(ObjectDeletedEvent event) {
496             if (subject == event.getOldObject()) {
497                 callbacked();
498             }
499         }
500     }
501
502     /**
503      * Tracks inserts in Working Memory for any object of class T.
504      */
505     class KieClassInsertedExpectedCallback<T> extends KieObjectInsertedExpectedCallback<T> {
506
507         public KieClassInsertedExpectedCallback(T affected) {
508             super(affected);
509         }
510
511         public void objectInserted(ObjectInsertedEvent event) {
512             if (subject == event.getObject().getClass()) {
513                 callbacked();
514             }
515         }
516     }
517
518     /**
519      * Tracks callbacks from topics.
520      */
521     class TopicCallback<T> implements TopicListener {
522         protected final Topic topic;
523         protected final Class<T> expectedClass;
524
525         @Getter
526         protected Queue<T> messages = new LinkedList<>();
527
528         public TopicCallback(Topic topic, Class<T> expectedClass) {
529             this.topic = topic;
530             this.expectedClass = expectedClass;
531             this.topic.register(this);
532         }
533
534         public TopicCallback<T> register() {
535             this.topic.register(this);
536             return this;
537         }
538
539         public TopicCallback<T> unregister() {
540             this.topic.unregister(this);
541             return this;
542         }
543
544         @Override
545         @SuppressWarnings("unchecked")
546         public void onTopicEvent(CommInfrastructure comm, String topic, String event) {
547             try {
548                 messages.add((T) EventProtocolCoderConstants.getManager().decode(controller.getDrools().getGroupId(),
549                                 controller.getDrools().getArtifactId(), topic, event));
550             } catch (Exception e) {
551                 logger.warn("invalid mapping in topic {} for event {}", topic, event, e);
552             }
553         }
554     }
555
556     class TopicCallbackCoder<T> extends TopicCallback<T> {
557         private final Coder coder;
558
559         public TopicCallbackCoder(Topic topic, Class<T> expectedClass, Coder coder) {
560             super(topic, expectedClass);
561             this.coder = coder;
562         }
563
564         @Override
565         public void onTopicEvent(CommInfrastructure comm, String topic, String event) {
566             try {
567                 messages.add((T) coder.decode(event, expectedClass));
568             } catch (Exception e) {
569                 logger.warn("invalid mapping in topic {} for event {}", topic, event, e);
570             }
571         }
572
573     }
574 }