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