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