b80f4c860fc02ec0c8c0f7a6e621043d31361aed
[policy/drools-pdp.git] /
1 /*
2  * ============LICENSE_START=======================================================
3  * ONAP
4  * ================================================================================
5  * Copyright (C) 2017-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.drools.system.internal;
22
23 import com.fasterxml.jackson.annotation.JsonIgnore;
24 import java.util.ArrayList;
25 import java.util.Arrays;
26 import java.util.HashMap;
27 import java.util.List;
28 import java.util.Properties;
29 import java.util.stream.Collectors;
30 import org.onap.policy.common.endpoints.event.comm.Topic;
31 import org.onap.policy.common.endpoints.event.comm.TopicEndpoint;
32 import org.onap.policy.common.endpoints.event.comm.TopicEndpointManager;
33 import org.onap.policy.common.endpoints.event.comm.TopicListener;
34 import org.onap.policy.common.endpoints.event.comm.TopicSink;
35 import org.onap.policy.common.endpoints.event.comm.TopicSource;
36 import org.onap.policy.common.gson.annotation.GsonJsonIgnore;
37 import org.onap.policy.common.utils.services.FeatureApiUtils;
38 import org.onap.policy.drools.controller.DroolsController;
39 import org.onap.policy.drools.controller.DroolsControllerConstants;
40 import org.onap.policy.drools.controller.DroolsControllerFactory;
41 import org.onap.policy.drools.features.PolicyControllerFeatureApi;
42 import org.onap.policy.drools.features.PolicyControllerFeatureApiConstants;
43 import org.onap.policy.drools.persistence.SystemPersistence;
44 import org.onap.policy.drools.persistence.SystemPersistenceConstants;
45 import org.onap.policy.drools.properties.DroolsPropertyConstants;
46 import org.onap.policy.drools.protocol.configuration.DroolsConfiguration;
47 import org.onap.policy.drools.system.PolicyController;
48 import org.onap.policy.drools.utils.PropertyUtil;
49 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyTypeIdentifier;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
52
53 /**
54  * This implementation of the Policy Controller merely aggregates and tracks for management purposes
55  * all underlying resources that this controller depends upon.
56  */
57 public class AggregatedPolicyController implements PolicyController, TopicListener {
58
59     private static final String BEFORE_OFFER_FAILURE = "{}: feature {} before-offer failure because of {}";
60     private static final String AFTER_OFFER_FAILURE = "{}: feature {} after-offer failure because of {}";
61
62     /**
63      * Logger.
64      */
65     private static final Logger logger = LoggerFactory.getLogger(AggregatedPolicyController.class);
66
67     /**
68      * identifier for this policy controller.
69      */
70     private final String name;
71
72     /**
73      * Abstracted Event Sources List regardless communication technology.
74      */
75     private final List<TopicSource> sources;
76
77     /**
78      * Abstracted Event Sinks List regardless communication technology.
79      */
80     private final List<TopicSink> sinks;
81
82     /**
83      * Mapping topics to sinks.
84      */
85     @JsonIgnore
86     @GsonJsonIgnore
87     private final HashMap<String, TopicSink> topic2Sinks = new HashMap<>();
88
89     /**
90      * Is this Policy Controller running (alive) ? reflects invocation of start()/stop() only.
91      */
92     private volatile boolean alive;
93
94     /**
95      * Is this Policy Controller locked ? reflects if i/o controller related operations and start
96      * are permitted, more specifically: start(), deliver() and onTopicEvent(). It does not affect
97      * the ability to stop the underlying drools infrastructure
98      */
99     private volatile boolean locked;
100
101     /**
102      * Policy Drools Controller.
103      */
104     private volatile DroolsController droolsController;
105
106     /**
107      * Properties used to initialize controller.
108      */
109     private final Properties properties;
110
111     /**
112      * Policy Types.
113      */
114     private List<ToscaPolicyTypeIdentifier> policyTypes;
115
116     /**
117      * Constructor version mainly used for bootstrapping at initialization time a policy engine
118      * controller.
119      *
120      * @param name controller name
121      * @param properties
122      *
123      * @throws IllegalArgumentException when invalid arguments are provided
124      */
125     public AggregatedPolicyController(String name, Properties properties) {
126
127         this.name = name;
128
129         /*
130          * 1. Register read topics with network infrastructure (ueb, dmaap, rest) 2. Register write
131          * topics with network infrastructure (ueb, dmaap, rest) 3. Register with drools
132          * infrastructure
133          */
134
135         // Create/Reuse Readers/Writers for all event sources endpoints
136
137         this.sources = getEndpointManager().addTopicSources(properties);
138         this.sinks = getEndpointManager().addTopicSinks(properties);
139
140         initDrools(properties);
141         initSinks();
142
143         /* persist new properties */
144         getPersistenceManager().storeController(name, properties);
145         this.properties = PropertyUtil.getInterpolatedProperties(properties);
146
147         this.policyTypes = getPolicyTypesFromProperties();
148     }
149
150     @Override
151     public List<ToscaPolicyTypeIdentifier> getPolicyTypes() {
152         if (!policyTypes.isEmpty()) {
153             return policyTypes;
154         }
155
156         return droolsController
157                 .getBaseDomainNames()
158                 .stream()
159                 .map(d -> new ToscaPolicyTypeIdentifier(d,
160                                 DroolsPropertyConstants.DEFAULT_CONTROLLER_POLICY_TYPE_VERSION))
161                 .collect(Collectors.toList());
162     }
163
164     protected List<ToscaPolicyTypeIdentifier> getPolicyTypesFromProperties() {
165         List<ToscaPolicyTypeIdentifier> policyTypeIds = new ArrayList<>();
166
167         String ptiPropValue = properties.getProperty(DroolsPropertyConstants.PROPERTY_CONTROLLER_POLICY_TYPES);
168         if (ptiPropValue == null) {
169             return policyTypeIds;
170         }
171
172         List<String> ptiPropList = new ArrayList<>(Arrays.asList(ptiPropValue.split("\\s*,\\s*")));
173         for (String pti : ptiPropList) {
174             String[] ptv = pti.split(":");
175             if (ptv.length == 1) {
176                 policyTypeIds.add(new ToscaPolicyTypeIdentifier(ptv[0],
177                     DroolsPropertyConstants.DEFAULT_CONTROLLER_POLICY_TYPE_VERSION));
178             } else if (ptv.length == 2) {
179                 policyTypeIds.add(new ToscaPolicyTypeIdentifier(ptv[0], ptv[1]));
180             }
181         }
182
183         return policyTypeIds;
184     }
185
186     /**
187      * initialize drools layer.
188      *
189      * @throws IllegalArgumentException if invalid parameters are passed in
190      */
191     private void initDrools(Properties properties) {
192         try {
193             // Register with drools infrastructure
194             this.droolsController = getDroolsFactory().build(properties, sources, sinks);
195         } catch (Exception | LinkageError e) {
196             logger.error("{}: cannot init-drools because of {}", this, e.getMessage(), e);
197             throw new IllegalArgumentException(e);
198         }
199     }
200
201     /**
202      * initialize sinks.
203      *
204      * @throws IllegalArgumentException if invalid parameters are passed in
205      */
206     private void initSinks() {
207         this.topic2Sinks.clear();
208         for (TopicSink sink : sinks) {
209             this.topic2Sinks.put(sink.getTopic(), sink);
210         }
211     }
212
213     /**
214      * {@inheritDoc}.
215      */
216     @Override
217     public boolean updateDrools(DroolsConfiguration newDroolsConfiguration) {
218         DroolsConfiguration oldDroolsConfiguration = new DroolsConfiguration(this.droolsController.getArtifactId(),
219                 this.droolsController.getGroupId(), this.droolsController.getVersion());
220
221         if (oldDroolsConfiguration.getGroupId().equalsIgnoreCase(newDroolsConfiguration.getGroupId())
222                 && oldDroolsConfiguration.getArtifactId().equalsIgnoreCase(newDroolsConfiguration.getArtifactId())
223                 && oldDroolsConfiguration.getVersion().equalsIgnoreCase(newDroolsConfiguration.getVersion())) {
224             logger.warn("{}: cannot update-drools: identical configuration {} vs {}", this, oldDroolsConfiguration,
225                     newDroolsConfiguration);
226             return true;
227         }
228
229         if (FeatureApiUtils.apply(getProviders(),
230             feature -> feature.beforePatch(this, newDroolsConfiguration),
231             (feature, ex) -> logger.error("{}: feature {} before-patch failure because of {}", this,
232                         feature.getClass().getName(), ex.getMessage(), ex))) {
233             return true;
234         }
235
236         if (droolsController.isBrained()
237             && (newDroolsConfiguration.getArtifactId() == null
238                 || DroolsControllerConstants.NO_ARTIFACT_ID.equals(newDroolsConfiguration.getArtifactId()))) {
239             // detach maven artifact
240             DroolsControllerConstants.getFactory().destroy(this.droolsController);
241         }
242
243         boolean success = true;
244         try {
245             this.properties.setProperty(DroolsPropertyConstants.RULES_GROUPID, newDroolsConfiguration.getGroupId());
246             this.properties.setProperty(DroolsPropertyConstants.RULES_ARTIFACTID,
247                             newDroolsConfiguration.getArtifactId());
248             this.properties.setProperty(DroolsPropertyConstants.RULES_VERSION, newDroolsConfiguration.getVersion());
249             getPersistenceManager().storeController(name, this.properties);
250
251             this.initDrools(this.properties);
252
253             if (isLocked()) {
254                 droolsController.lock();
255             } else {
256                 droolsController.unlock();
257             }
258
259             if (isAlive()) {
260                 droolsController.start();
261             } else {
262                 droolsController.stop();
263             }
264         } catch (RuntimeException e) {
265             logger.error("{}: cannot update-drools because of {}", this, e.getMessage(), e);
266             success = false;
267         }
268
269         boolean finalSuccess = success;
270         FeatureApiUtils.apply(getProviders(),
271             feature -> feature.afterPatch(this, finalSuccess),
272             (feature, ex) -> logger.error("{}: feature {} after-patch failure because of {}", this,
273                         feature.getClass().getName(), ex.getMessage(), ex));
274
275         return finalSuccess;
276     }
277
278     /**
279      * {@inheritDoc}.
280      */
281     @Override
282     public String getName() {
283         return this.name;
284     }
285
286     /**
287      * {@inheritDoc}.
288      */
289     @Override
290     public boolean start() {
291         logger.info("{}: start", this);
292
293         if (FeatureApiUtils.apply(getProviders(),
294             feature -> feature.beforeStart(this),
295             (feature, ex) -> logger.error("{}: feature {} before-start failure because of {}", this,
296                             feature.getClass().getName(), ex.getMessage(), ex))) {
297             return true;
298         }
299
300         if (this.isLocked()) {
301             throw new IllegalStateException("Policy Controller " + name + " is locked");
302         }
303
304         synchronized (this) {
305             if (this.alive) {
306                 return true;
307             }
308
309             this.alive = true;
310         }
311
312         final boolean success = this.droolsController.start();
313
314         // register for events
315
316         for (TopicSource source : sources) {
317             source.register(this);
318         }
319
320         for (TopicSink sink : sinks) {
321             try {
322                 sink.start();
323             } catch (Exception e) {
324                 logger.error("{}: cannot start {} because of {}", this, sink, e.getMessage(), e);
325             }
326         }
327
328         FeatureApiUtils.apply(getProviders(),
329             feature -> feature.afterStart(this),
330             (feature, ex) -> logger.error("{}: feature {} after-start failure because of {}", this,
331                             feature.getClass().getName(), ex.getMessage(), ex));
332
333         return success;
334     }
335
336     /**
337      * {@inheritDoc}.
338      */
339     @Override
340     public boolean stop() {
341         logger.info("{}: stop", this);
342
343         if (FeatureApiUtils.apply(getProviders(),
344             feature -> feature.beforeStop(this),
345             (feature, ex) -> logger.error("{}: feature {} before-stop failure because of {}", this,
346                             feature.getClass().getName(), ex.getMessage(), ex))) {
347             return true;
348         }
349
350         /* stop regardless locked state */
351
352         synchronized (this) {
353             if (!this.alive) {
354                 return true;
355             }
356
357             this.alive = false;
358         }
359
360         // 1. Stop registration
361
362         for (TopicSource source : sources) {
363             source.unregister(this);
364         }
365
366         boolean success = this.droolsController.stop();
367
368         FeatureApiUtils.apply(getProviders(),
369             feature -> feature.afterStop(this),
370             (feature, ex) -> logger.error("{}: feature {} after-stop failure because of {}", this,
371                             feature.getClass().getName(), ex.getMessage(), ex));
372
373         return success;
374     }
375
376     /**
377      * {@inheritDoc}.
378      */
379     @Override
380     public void shutdown() {
381         logger.info("{}: shutdown", this);
382
383         if (FeatureApiUtils.apply(getProviders(),
384             feature -> feature.beforeShutdown(this),
385             (feature, ex) -> logger.error("{}: feature {} before-shutdown failure because of {}", this,
386                             feature.getClass().getName(), ex.getMessage(), ex))) {
387             return;
388         }
389
390         this.stop();
391
392         getDroolsFactory().shutdown(this.droolsController);
393
394         FeatureApiUtils.apply(getProviders(),
395             feature -> feature.afterShutdown(this),
396             (feature, ex) -> logger.error("{}: feature {} after-shutdown failure because of {}", this,
397                             feature.getClass().getName(), ex.getMessage(), ex));
398     }
399
400     /**
401      * {@inheritDoc}.
402      */
403     @Override
404     public void halt() {
405         logger.info("{}: halt", this);
406
407         if (FeatureApiUtils.apply(getProviders(),
408             feature -> feature.beforeHalt(this),
409             (feature, ex) -> logger.error("{}: feature {} before-halt failure because of {}", this,
410                             feature.getClass().getName(), ex.getMessage(), ex))) {
411             return;
412         }
413
414         this.stop();
415         getDroolsFactory().destroy(this.droolsController);
416         getPersistenceManager().deleteController(this.name);
417
418         FeatureApiUtils.apply(getProviders(),
419             feature -> feature.afterHalt(this),
420             (feature, ex) -> logger.error("{}: feature {} after-halt failure because of {}", this,
421                             feature.getClass().getName(), ex.getMessage(), ex));
422     }
423
424     /**
425      * {@inheritDoc}.
426      */
427     @Override
428     public void onTopicEvent(Topic.CommInfrastructure commType, String topic, String event) {
429         logger.debug("{}: raw event offered from {}:{}: {}", this, commType, topic, event);
430
431         if (skipOffer()) {
432             return;
433         }
434
435         if (FeatureApiUtils.apply(getProviders(),
436             feature -> feature.beforeOffer(this, commType, topic, event),
437             (feature, ex) -> logger.error(BEFORE_OFFER_FAILURE, this,
438                             feature.getClass().getName(), ex.getMessage(), ex))) {
439             return;
440         }
441
442         boolean success = this.droolsController.offer(topic, event);
443
444         FeatureApiUtils.apply(getProviders(),
445             feature -> feature.afterOffer(this, commType, topic, event, success),
446             (feature, ex) -> logger.error(AFTER_OFFER_FAILURE, this,
447                             feature.getClass().getName(), ex.getMessage(), ex));
448     }
449
450     @Override
451     public <T> boolean offer(T event) {
452         logger.debug("{}: event offered: {}", this, event);
453
454         if (skipOffer()) {
455             return true;
456         }
457
458         if (FeatureApiUtils.apply(getProviders(),
459             feature -> feature.beforeOffer(this, event),
460             (feature, ex) -> logger.error(BEFORE_OFFER_FAILURE, this,
461                             feature.getClass().getName(), ex.getMessage(), ex))) {
462             return true;
463         }
464
465         boolean success = this.droolsController.offer(event);
466
467         FeatureApiUtils.apply(getProviders(),
468             feature -> feature.afterOffer(this, event, success),
469             (feature, ex) -> logger.error(AFTER_OFFER_FAILURE, this,
470                             feature.getClass().getName(), ex.getMessage(), ex));
471
472         return success;
473     }
474
475     private boolean skipOffer() {
476         return isLocked() || !isAlive();
477     }
478
479     /**
480      * {@inheritDoc}.
481      */
482     @Override
483     public boolean deliver(Topic.CommInfrastructure commType, String topic, Object event) {
484
485         logger.debug("{}: deliver event to {}:{}: {}", this, commType, topic, event);
486
487         if (FeatureApiUtils.apply(getProviders(),
488             feature -> feature.beforeDeliver(this, commType, topic, event),
489             (feature, ex) -> logger.error("{}: feature {} before-deliver failure because of {}", this,
490                             feature.getClass().getName(), ex.getMessage(), ex))) {
491             return true;
492         }
493
494         if (topic == null || topic.isEmpty()) {
495             throw new IllegalArgumentException("Invalid Topic");
496         }
497
498         if (event == null) {
499             throw new IllegalArgumentException("Invalid Event");
500         }
501
502         if (!this.isAlive()) {
503             throw new IllegalStateException("Policy Engine is stopped");
504         }
505
506         if (this.isLocked()) {
507             throw new IllegalStateException("Policy Engine is locked");
508         }
509
510         if (!this.topic2Sinks.containsKey(topic)) {
511             logger.warn("{}: cannot deliver event because the sink {}:{} is not registered: {}", this, commType, topic,
512                     event);
513             throw new IllegalArgumentException("Unsupported topic " + topic + " for delivery");
514         }
515
516         boolean success = this.droolsController.deliver(this.topic2Sinks.get(topic), event);
517
518         FeatureApiUtils.apply(getProviders(),
519             feature -> feature.afterDeliver(this, commType, topic, event, success),
520             (feature, ex) -> logger.error("{}: feature {} after-deliver failure because of {}", this,
521                             feature.getClass().getName(), ex.getMessage(), ex));
522
523         return success;
524     }
525
526     /**
527      * {@inheritDoc}.
528      */
529     @Override
530     public boolean isAlive() {
531         return this.alive;
532     }
533
534     /**
535      * {@inheritDoc}.
536      */
537     @Override
538     public boolean lock() {
539         logger.info("{}: lock", this);
540
541         if (FeatureApiUtils.apply(getProviders(),
542             feature -> feature.beforeLock(this),
543             (feature, ex) -> logger.error("{}: feature {} before-lock failure because of {}", this,
544                             feature.getClass().getName(), ex.getMessage(), ex))) {
545             return true;
546         }
547
548         synchronized (this) {
549             if (this.locked) {
550                 return true;
551             }
552
553             this.locked = true;
554         }
555
556         // it does not affect associated sources/sinks, they are
557         // autonomous entities
558
559         boolean success = this.droolsController.lock();
560
561         FeatureApiUtils.apply(getProviders(),
562             feature -> feature.afterLock(this),
563             (feature, ex) -> logger.error("{}: feature {} after-lock failure because of {}", this,
564                             feature.getClass().getName(), ex.getMessage(), ex));
565
566         return success;
567     }
568
569     /**
570      * {@inheritDoc}.
571      */
572     @Override
573     public boolean unlock() {
574
575         logger.info("{}: unlock", this);
576
577         if (FeatureApiUtils.apply(getProviders(),
578             feature -> feature.beforeUnlock(this),
579             (feature, ex) -> logger.error("{}: feature {} before-unlock failure because of {}", this,
580                             feature.getClass().getName(), ex.getMessage(), ex))) {
581             return true;
582         }
583
584         synchronized (this) {
585             if (!this.locked) {
586                 return true;
587             }
588
589             this.locked = false;
590         }
591
592         boolean success = this.droolsController.unlock();
593
594         FeatureApiUtils.apply(getProviders(),
595             feature -> feature.afterUnlock(this),
596             (feature, ex) -> logger.error("{}: feature {} after-unlock failure because of {}", this,
597                             feature.getClass().getName(), ex.getMessage(), ex));
598
599         return success;
600     }
601
602     /**
603      * {@inheritDoc}.
604      */
605     @Override
606     public boolean isLocked() {
607         return this.locked;
608     }
609
610     /**
611      * {@inheritDoc}.
612      */
613     @Override
614     public List<TopicSource> getTopicSources() {
615         return this.sources;
616     }
617
618     /**
619      * {@inheritDoc}.
620      */
621     @Override
622     public List<TopicSink> getTopicSinks() {
623         return this.sinks;
624     }
625
626     /**
627      * {@inheritDoc}.
628      */
629     @Override
630     public DroolsController getDrools() {
631         return this.droolsController;
632     }
633
634
635     /**
636      * {@inheritDoc}.
637      */
638     @Override
639     @JsonIgnore
640     @GsonJsonIgnore
641     public Properties getProperties() {
642         return this.properties;
643     }
644
645     @Override
646     public String toString() {
647         return "AggregatedPolicyController [name=" + name + ", alive=" + alive
648                 + ", locked=" + locked + ", droolsController=" + droolsController + "]";
649     }
650
651     // the following methods may be overridden by junit tests
652
653     protected SystemPersistence getPersistenceManager() {
654         return SystemPersistenceConstants.getManager();
655     }
656
657     protected TopicEndpoint getEndpointManager() {
658         return TopicEndpointManager.getManager();
659     }
660
661     protected DroolsControllerFactory getDroolsFactory() {
662         return DroolsControllerConstants.getFactory();
663     }
664
665     protected List<PolicyControllerFeatureApi> getProviders() {
666         return PolicyControllerFeatureApiConstants.getProviders().getList();
667     }
668 }
669