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