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