44b07a5c4b4ca983d935d937f9d85e8d17a214d2
[policy/drools-pdp.git] /
1 /*
2  * ============LICENSE_START=======================================================
3  * ONAP
4  * ================================================================================
5  * Copyright (C) 2017-2021 AT&T Intellectual Property. All rights reserved.
6  * Modifications Copyright (C) 2021 Nordix Foundation.
7  * ================================================================================
8  * Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  * ============LICENSE_END=========================================================
20  */
21
22 package org.onap.policy.drools.system.internal;
23
24 import com.google.re2j.Pattern;
25 import java.util.ArrayList;
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.ToscaConceptIdentifier;
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     private static final Pattern COMMA_SPACE_PAT = Pattern.compile("\\s*,\\s*");
68
69     /**
70      * identifier for this policy controller.
71      */
72     private final String name;
73
74     /**
75      * Abstracted Event Sources List regardless communication technology.
76      */
77     protected final List<TopicSource> sources;
78
79     /**
80      * Abstracted Event Sinks List regardless communication technology.
81      */
82     protected final List<TopicSink> sinks;
83
84     /**
85      * Mapping topics to sinks.
86      */
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     protected 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 final List<ToscaConceptIdentifier> 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 controller 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<ToscaConceptIdentifier> getPolicyTypes() {
153         if (!policyTypes.isEmpty()) {
154             return policyTypes;
155         }
156
157         return droolsController
158                 .get()
159                 .getBaseDomainNames()
160                 .stream()
161                 .map(d -> new ToscaConceptIdentifier(d,
162                                 DroolsPropertyConstants.DEFAULT_CONTROLLER_POLICY_TYPE_VERSION))
163                 .collect(Collectors.toList());
164     }
165
166     protected List<ToscaConceptIdentifier> getPolicyTypesFromProperties() {
167         List<ToscaConceptIdentifier> policyTypeIds = new ArrayList<>();
168
169         String ptiPropValue = properties.getProperty(DroolsPropertyConstants.PROPERTY_CONTROLLER_POLICY_TYPES);
170         if (ptiPropValue == null) {
171             return policyTypeIds;
172         }
173
174         for (String pti : COMMA_SPACE_PAT.split(ptiPropValue)) {
175             String[] ptv = pti.split(":");
176             if (ptv.length == 1) {
177                 policyTypeIds.add(new ToscaConceptIdentifier(ptv[0],
178                     DroolsPropertyConstants.DEFAULT_CONTROLLER_POLICY_TYPE_VERSION));
179             } else if (ptv.length == 2) {
180                 policyTypeIds.add(new ToscaConceptIdentifier(ptv[0], ptv[1]));
181             }
182         }
183
184         return policyTypeIds;
185     }
186
187     /**
188      * initialize drools layer.
189      *
190      * @throws IllegalArgumentException if invalid parameters are passed in
191      */
192     protected void initDrools(Properties properties) {
193         try {
194             // Register with drools infrastructure
195             this.droolsController.set(getDroolsFactory().build(properties, sources, sinks));
196         } catch (Exception | LinkageError e) {
197             logger.error("{}: cannot init-drools", this);
198             throw new IllegalArgumentException(e);
199         }
200     }
201
202     /**
203      * initialize sinks.
204      *
205      * @throws IllegalArgumentException if invalid parameters are passed in
206      */
207     private void initSinks() {
208         this.topic2Sinks.clear();
209         for (TopicSink sink : sinks) {
210             this.topic2Sinks.put(sink.getTopic(), sink);
211         }
212     }
213
214     /**
215      * {@inheritDoc}.
216      */
217     @Override
218     public boolean updateDrools(DroolsConfiguration newDroolsConfiguration) {
219         DroolsController controller = this.droolsController.get();
220         var oldDroolsConfiguration = new DroolsConfiguration(controller.getArtifactId(),
221                 controller.getGroupId(), controller.getVersion());
222
223         if (oldDroolsConfiguration.getGroupId().equalsIgnoreCase(newDroolsConfiguration.getGroupId())
224                 && oldDroolsConfiguration.getArtifactId().equalsIgnoreCase(newDroolsConfiguration.getArtifactId())
225                 && oldDroolsConfiguration.getVersion().equalsIgnoreCase(newDroolsConfiguration.getVersion())) {
226             logger.warn("{}: cannot update-drools: identical configuration {} vs {}", this, oldDroolsConfiguration,
227                     newDroolsConfiguration);
228             return true;
229         }
230
231         if (FeatureApiUtils.apply(getProviders(),
232             feature -> feature.beforePatch(this, oldDroolsConfiguration, newDroolsConfiguration),
233             (feature, ex) -> logger.error("{}: feature {} before-patch failure because of {}", this,
234                         feature.getClass().getName(), ex.getMessage(), ex))) {
235             return true;
236         }
237
238         if (controller.isBrained()
239             && (newDroolsConfiguration.getArtifactId() == null
240                 || DroolsControllerConstants.NO_ARTIFACT_ID.equals(newDroolsConfiguration.getArtifactId()))) {
241             // detach maven artifact
242             DroolsControllerConstants.getFactory().destroy(controller);
243         }
244
245         var success = true;
246         try {
247             this.properties.setProperty(DroolsPropertyConstants.RULES_GROUPID, newDroolsConfiguration.getGroupId());
248             this.properties.setProperty(DroolsPropertyConstants.RULES_ARTIFACTID,
249                             newDroolsConfiguration.getArtifactId());
250             this.properties.setProperty(DroolsPropertyConstants.RULES_VERSION, newDroolsConfiguration.getVersion());
251             getPersistenceManager().storeController(name, this.properties);
252
253             this.initDrools(this.properties);
254
255             // have a new controller now - get it
256             controller = this.droolsController.get();
257
258             if (isLocked()) {
259                 controller.lock();
260             } else {
261                 controller.unlock();
262             }
263
264             if (isAlive()) {
265                 controller.start();
266             } else {
267                 controller.stop();
268             }
269         } catch (RuntimeException e) {
270             logger.error("{}: cannot update-drools because of {}", this, e.getMessage(), e);
271             success = false;
272         }
273
274         boolean finalSuccess = success;
275         FeatureApiUtils.apply(getProviders(),
276             feature -> feature.afterPatch(this, oldDroolsConfiguration, newDroolsConfiguration, finalSuccess),
277             (feature, ex) -> logger.error("{}: feature {} after-patch failure because of {}", this,
278                         feature.getClass().getName(), ex.getMessage(), ex));
279
280         return finalSuccess;
281     }
282
283     /**
284      * {@inheritDoc}.
285      */
286     @Override
287     public String getName() {
288         return this.name;
289     }
290
291     /**
292      * {@inheritDoc}.
293      */
294     @Override
295     public boolean start() {
296         logger.info("{}: start", this);
297
298         if (FeatureApiUtils.apply(getProviders(),
299             feature -> feature.beforeStart(this),
300             (feature, ex) -> logger.error("{}: feature {} before-start failure because of {}", this,
301                             feature.getClass().getName(), ex.getMessage(), ex))) {
302             return true;
303         }
304
305         if (this.isLocked()) {
306             throw new IllegalStateException("Policy Controller " + name + " is locked");
307         }
308
309         synchronized (this) {
310             if (this.alive) {
311                 return true;
312             }
313
314             this.alive = true;
315         }
316
317         final boolean success = this.droolsController.get().start();
318
319         // register for events
320
321         for (TopicSource source : sources) {
322             source.register(this);
323         }
324
325         for (TopicSink sink : sinks) {
326             try {
327                 sink.start();
328             } catch (Exception e) {
329                 logger.error("{}: cannot start {} because of {}", this, sink, e.getMessage(), e);
330             }
331         }
332
333         FeatureApiUtils.apply(getProviders(),
334             feature -> feature.afterStart(this),
335             (feature, ex) -> logger.error("{}: feature {} after-start failure because of {}", this,
336                             feature.getClass().getName(), ex.getMessage(), ex));
337
338         return success;
339     }
340
341     /**
342      * {@inheritDoc}.
343      */
344     @Override
345     public boolean stop() {
346         logger.info("{}: stop", this);
347
348         if (FeatureApiUtils.apply(getProviders(),
349             feature -> feature.beforeStop(this),
350             (feature, ex) -> logger.error("{}: feature {} before-stop failure because of {}", this,
351                             feature.getClass().getName(), ex.getMessage(), ex))) {
352             return true;
353         }
354
355         /* stop regardless locked state */
356
357         synchronized (this) {
358             if (!this.alive) {
359                 return true;
360             }
361
362             this.alive = false;
363         }
364
365         // 1. Stop registration
366
367         for (TopicSource source : sources) {
368             source.unregister(this);
369         }
370
371         boolean success = this.droolsController.get().stop();
372
373         FeatureApiUtils.apply(getProviders(),
374             feature -> feature.afterStop(this),
375             (feature, ex) -> logger.error("{}: feature {} after-stop failure because of {}", this,
376                             feature.getClass().getName(), ex.getMessage(), ex));
377
378         return success;
379     }
380
381     /**
382      * {@inheritDoc}.
383      */
384     @Override
385     public void shutdown() {
386         logger.info("{}: shutdown", this);
387
388         if (FeatureApiUtils.apply(getProviders(),
389             feature -> feature.beforeShutdown(this),
390             (feature, ex) -> logger.error("{}: feature {} before-shutdown failure because of {}", this,
391                             feature.getClass().getName(), ex.getMessage(), ex))) {
392             return;
393         }
394
395         this.stop();
396
397         getDroolsFactory().shutdown(this.droolsController.get());
398
399         FeatureApiUtils.apply(getProviders(),
400             feature -> feature.afterShutdown(this),
401             (feature, ex) -> logger.error("{}: feature {} after-shutdown failure because of {}", this,
402                             feature.getClass().getName(), ex.getMessage(), ex));
403     }
404
405     /**
406      * {@inheritDoc}.
407      */
408     @Override
409     public void halt() {
410         logger.info("{}: halt", this);
411
412         if (FeatureApiUtils.apply(getProviders(),
413             feature -> feature.beforeHalt(this),
414             (feature, ex) -> logger.error("{}: feature {} before-halt failure because of {}", this,
415                             feature.getClass().getName(), ex.getMessage(), ex))) {
416             return;
417         }
418
419         this.stop();
420         getDroolsFactory().destroy(this.droolsController.get());
421         getPersistenceManager().deleteController(this.name);
422
423         FeatureApiUtils.apply(getProviders(),
424             feature -> feature.afterHalt(this),
425             (feature, ex) -> logger.error("{}: feature {} after-halt failure because of {}", this,
426                             feature.getClass().getName(), ex.getMessage(), ex));
427     }
428
429     /**
430      * {@inheritDoc}.
431      */
432     @Override
433     public void onTopicEvent(Topic.CommInfrastructure commType, String topic, String event) {
434         logger.debug("{}: raw event offered from {}:{}: {}", this, commType, topic, event);
435
436         if (skipOffer()) {
437             return;
438         }
439
440         if (FeatureApiUtils.apply(getProviders(),
441             feature -> feature.beforeOffer(this, commType, topic, event),
442             (feature, ex) -> logger.error(BEFORE_OFFER_FAILURE, this,
443                             feature.getClass().getName(), ex.getMessage(), ex))) {
444             return;
445         }
446
447         boolean success = this.droolsController.get().offer(topic, event);
448
449         FeatureApiUtils.apply(getProviders(),
450             feature -> feature.afterOffer(this, commType, topic, event, success),
451             (feature, ex) -> logger.error(AFTER_OFFER_FAILURE, this,
452                             feature.getClass().getName(), ex.getMessage(), ex));
453     }
454
455     @Override
456     public <T> boolean offer(T event) {
457         logger.debug("{}: event offered: {}", this, event);
458
459         if (skipOffer()) {
460             return true;
461         }
462
463         if (FeatureApiUtils.apply(getProviders(),
464             feature -> feature.beforeOffer(this, event),
465             (feature, ex) -> logger.error(BEFORE_OFFER_FAILURE, this,
466                             feature.getClass().getName(), ex.getMessage(), ex))) {
467             return true;
468         }
469
470         boolean success = this.droolsController.get().offer(event);
471
472         FeatureApiUtils.apply(getProviders(),
473             feature -> feature.afterOffer(this, event, success),
474             (feature, ex) -> logger.error(AFTER_OFFER_FAILURE, this,
475                             feature.getClass().getName(), ex.getMessage(), ex));
476
477         return success;
478     }
479
480     private boolean skipOffer() {
481         return isLocked() || !isAlive();
482     }
483
484     /**
485      * {@inheritDoc}.
486      */
487     @Override
488     public boolean deliver(Topic.CommInfrastructure commType, String topic, Object event) {
489
490         logger.debug("{}: deliver event to {}:{}: {}", this, commType, topic, event);
491
492         if (FeatureApiUtils.apply(getProviders(),
493             feature -> feature.beforeDeliver(this, commType, topic, event),
494             (feature, ex) -> logger.error("{}: feature {} before-deliver failure because of {}", this,
495                             feature.getClass().getName(), ex.getMessage(), ex))) {
496             return true;
497         }
498
499         if (topic == null || topic.isEmpty()) {
500             throw new IllegalArgumentException("Invalid Topic");
501         }
502
503         if (event == null) {
504             throw new IllegalArgumentException("Invalid Event");
505         }
506
507         if (!this.isAlive()) {
508             throw new IllegalStateException("Policy Engine is stopped");
509         }
510
511         if (this.isLocked()) {
512             throw new IllegalStateException("Policy Engine is locked");
513         }
514
515         if (!this.topic2Sinks.containsKey(topic)) {
516             logger.warn("{}: cannot deliver event because the sink {}:{} is not registered: {}", this, commType, topic,
517                     event);
518             throw new IllegalArgumentException("Unsupported topic " + topic + " for delivery");
519         }
520
521         boolean success = this.droolsController.get().deliver(this.topic2Sinks.get(topic), event);
522
523         FeatureApiUtils.apply(getProviders(),
524             feature -> feature.afterDeliver(this, commType, topic, event, success),
525             (feature, ex) -> logger.error("{}: feature {} after-deliver failure because of {}", this,
526                             feature.getClass().getName(), ex.getMessage(), ex));
527
528         return success;
529     }
530
531     /**
532      * {@inheritDoc}.
533      */
534     @Override
535     public boolean isAlive() {
536         return this.alive;
537     }
538
539     /**
540      * {@inheritDoc}.
541      */
542     @Override
543     public boolean lock() {
544         logger.info("{}: lock", this);
545
546         if (FeatureApiUtils.apply(getProviders(),
547             feature -> feature.beforeLock(this),
548             (feature, ex) -> logger.error("{}: feature {} before-lock failure because of {}", this,
549                             feature.getClass().getName(), ex.getMessage(), ex))) {
550             return true;
551         }
552
553         synchronized (this) {
554             if (this.locked) {
555                 return true;
556             }
557
558             this.locked = true;
559         }
560
561         // it does not affect associated sources/sinks, they are
562         // autonomous entities
563
564         boolean success = this.droolsController.get().lock();
565
566         FeatureApiUtils.apply(getProviders(),
567             feature -> feature.afterLock(this),
568             (feature, ex) -> logger.error("{}: feature {} after-lock failure because of {}", this,
569                             feature.getClass().getName(), ex.getMessage(), ex));
570
571         return success;
572     }
573
574     /**
575      * {@inheritDoc}.
576      */
577     @Override
578     public boolean unlock() {
579
580         logger.info("{}: unlock", this);
581
582         if (FeatureApiUtils.apply(getProviders(),
583             feature -> feature.beforeUnlock(this),
584             (feature, ex) -> logger.error("{}: feature {} before-unlock failure because of {}", this,
585                             feature.getClass().getName(), ex.getMessage(), ex))) {
586             return true;
587         }
588
589         synchronized (this) {
590             if (!this.locked) {
591                 return true;
592             }
593
594             this.locked = false;
595         }
596
597         boolean success = this.droolsController.get().unlock();
598
599         FeatureApiUtils.apply(getProviders(),
600             feature -> feature.afterUnlock(this),
601             (feature, ex) -> logger.error("{}: feature {} after-unlock failure because of {}", this,
602                             feature.getClass().getName(), ex.getMessage(), ex));
603
604         return success;
605     }
606
607     /**
608      * {@inheritDoc}.
609      */
610     @Override
611     public boolean isLocked() {
612         return this.locked;
613     }
614
615     /**
616      * {@inheritDoc}.
617      */
618     @Override
619     public List<TopicSource> getTopicSources() {
620         return this.sources;
621     }
622
623     /**
624      * {@inheritDoc}.
625      */
626     @Override
627     public List<TopicSink> getTopicSinks() {
628         return this.sinks;
629     }
630
631     /**
632      * {@inheritDoc}.
633      */
634     @Override
635     public DroolsController getDrools() {
636         return this.droolsController.get();
637     }
638
639
640     /**
641      * {@inheritDoc}.
642      */
643     @Override
644     @GsonJsonIgnore
645     public Properties getProperties() {
646         return this.properties;
647     }
648
649     @Override
650     public String toString() {
651         return "AggregatedPolicyController [name=" + name + ", alive=" + alive
652                 + ", locked=" + locked + ", droolsController=" + droolsController + "]";
653     }
654
655     // the following methods may be overridden by junit tests
656
657     protected SystemPersistence getPersistenceManager() {
658         return SystemPersistenceConstants.getManager();
659     }
660
661     protected TopicEndpoint getEndpointManager() {
662         return TopicEndpointManager.getManager();
663     }
664
665     protected DroolsControllerFactory getDroolsFactory() {
666         return DroolsControllerConstants.getFactory();
667     }
668
669     protected List<PolicyControllerFeatureApi> getProviders() {
670         return PolicyControllerFeatureApiConstants.getProviders().getList();
671     }
672 }
673