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