Fix sonar warnings in blueprint bundle
[controller.git] / opendaylight / blueprint / src / main / java / org / opendaylight / controller / blueprint / BlueprintBundleTracker.java
1 /*
2  * Copyright (c) 2016 Brocade Communications Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.controller.blueprint;
9
10 import java.util.ArrayList;
11 import java.util.Arrays;
12 import java.util.Collection;
13 import java.util.Collections;
14 import java.util.Dictionary;
15 import java.util.Enumeration;
16 import java.util.HashSet;
17 import java.util.Hashtable;
18 import java.util.List;
19 import javax.annotation.Nullable;
20 import org.apache.aries.blueprint.NamespaceHandler;
21 import org.apache.aries.blueprint.services.BlueprintExtenderService;
22 import org.apache.aries.quiesce.participant.QuiesceParticipant;
23 import org.apache.aries.util.AriesFrameworkUtil;
24 import org.opendaylight.controller.blueprint.ext.OpendaylightNamespaceHandler;
25 import org.opendaylight.controller.config.api.ConfigSystemService;
26 import org.osgi.framework.Bundle;
27 import org.osgi.framework.BundleActivator;
28 import org.osgi.framework.BundleContext;
29 import org.osgi.framework.BundleEvent;
30 import org.osgi.framework.ServiceReference;
31 import org.osgi.framework.ServiceRegistration;
32 import org.osgi.framework.SynchronousBundleListener;
33 import org.osgi.service.blueprint.container.BlueprintContainer;
34 import org.osgi.service.blueprint.container.EventConstants;
35 import org.osgi.service.event.Event;
36 import org.osgi.service.event.EventHandler;
37 import org.osgi.util.tracker.BundleTracker;
38 import org.osgi.util.tracker.BundleTrackerCustomizer;
39 import org.osgi.util.tracker.ServiceTracker;
40 import org.osgi.util.tracker.ServiceTrackerCustomizer;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43
44 /**
45  * This class is created in bundle activation and scans ACTIVE bundles for blueprint XML files located under
46  * the well-known org/opendaylight/blueprint/ path and deploys the XML files via the Aries
47  * BlueprintExtenderService. This path differs from the standard OSGI-INF/blueprint path to allow for
48  * controlled deployment of blueprint containers in an orderly manner.
49  *
50  * @author Thomas Pantelis
51  */
52 public class BlueprintBundleTracker implements BundleActivator, BundleTrackerCustomizer<Bundle>, EventHandler,
53         SynchronousBundleListener {
54     private static final Logger LOG = LoggerFactory.getLogger(BlueprintBundleTracker.class);
55     private static final String BLUEPRINT_FILE_PATH = "org/opendaylight/blueprint/";
56     private static final String BLUEPRINT_FLE_PATTERN = "*.xml";
57     private static final long SYSTEM_BUNDLE_ID = 0;
58
59     private ServiceTracker<BlueprintExtenderService, BlueprintExtenderService> blueprintExtenderServiceTracker;
60     private ServiceTracker<QuiesceParticipant, QuiesceParticipant> quiesceParticipantTracker;
61     private BundleTracker<Bundle> bundleTracker;
62     private BundleContext bundleContext;
63     private volatile BlueprintExtenderService blueprintExtenderService;
64     private volatile QuiesceParticipant quiesceParticipant;
65     private volatile ServiceRegistration<?> blueprintContainerRestartReg;
66     private volatile BlueprintContainerRestartServiceImpl restartService;
67     private volatile boolean shuttingDown;
68     private ServiceRegistration<?> eventHandlerReg;
69     private ServiceRegistration<?> namespaceReg;
70
71     /**
72      * Implemented from BundleActivator.
73      */
74     @Override
75     public void start(final BundleContext context) {
76         LOG.info("Starting {}", getClass().getSimpleName());
77
78         restartService = new BlueprintContainerRestartServiceImpl();
79
80         bundleContext = context;
81
82         registerBlueprintEventHandler(context);
83
84         registerNamespaceHandler(context);
85
86         bundleTracker = new BundleTracker<>(context, Bundle.ACTIVE, this);
87
88         blueprintExtenderServiceTracker = new ServiceTracker<>(context, BlueprintExtenderService.class.getName(),
89                 new ServiceTrackerCustomizer<BlueprintExtenderService, BlueprintExtenderService>() {
90                     @Override
91                     public BlueprintExtenderService addingService(
92                             final ServiceReference<BlueprintExtenderService> reference) {
93                         return onBlueprintExtenderServiceAdded(reference);
94                     }
95
96                     @Override
97                     public void modifiedService(final ServiceReference<BlueprintExtenderService> reference,
98                             final BlueprintExtenderService service) {
99                     }
100
101                     @Override
102                     public void removedService(final ServiceReference<BlueprintExtenderService> reference,
103                             final BlueprintExtenderService service) {
104                     }
105                 });
106         blueprintExtenderServiceTracker.open();
107
108         quiesceParticipantTracker = new ServiceTracker<>(context, QuiesceParticipant.class.getName(),
109                 new ServiceTrackerCustomizer<QuiesceParticipant, QuiesceParticipant>() {
110                     @Override
111                     public QuiesceParticipant addingService(
112                             final ServiceReference<QuiesceParticipant> reference) {
113                         return onQuiesceParticipantAdded(reference);
114                     }
115
116                     @Override
117                     public void modifiedService(final ServiceReference<QuiesceParticipant> reference,
118                                                 final QuiesceParticipant service) {
119                     }
120
121                     @Override
122                     public void removedService(final ServiceReference<QuiesceParticipant> reference,
123                                                final QuiesceParticipant service) {
124                     }
125                 });
126         quiesceParticipantTracker.open();
127     }
128
129     private QuiesceParticipant onQuiesceParticipantAdded(final ServiceReference<QuiesceParticipant> reference) {
130         quiesceParticipant = reference.getBundle().getBundleContext().getService(reference);
131
132         LOG.debug("Got QuiesceParticipant");
133
134         restartService.setQuiesceParticipant(quiesceParticipant);
135
136         return quiesceParticipant;
137     }
138
139     private BlueprintExtenderService onBlueprintExtenderServiceAdded(
140             final ServiceReference<BlueprintExtenderService> reference) {
141         blueprintExtenderService = reference.getBundle().getBundleContext().getService(reference);
142         bundleTracker.open();
143
144         bundleContext.addBundleListener(BlueprintBundleTracker.this);
145
146         LOG.debug("Got BlueprintExtenderService");
147
148         restartService.setBlueprintExtenderService(blueprintExtenderService);
149
150         blueprintContainerRestartReg = bundleContext.registerService(
151                 BlueprintContainerRestartService.class.getName(), restartService, new Hashtable<>());
152
153         return blueprintExtenderService;
154     }
155
156     private void registerNamespaceHandler(final BundleContext context) {
157         Dictionary<String, Object> props = new Hashtable<>();
158         props.put("osgi.service.blueprint.namespace", OpendaylightNamespaceHandler.NAMESPACE_1_0_0);
159         namespaceReg = context.registerService(NamespaceHandler.class.getName(),
160                 new OpendaylightNamespaceHandler(), props);
161     }
162
163     private void registerBlueprintEventHandler(final BundleContext context) {
164         Dictionary<String, Object> props = new Hashtable<>();
165         props.put(org.osgi.service.event.EventConstants.EVENT_TOPIC,
166                 new String[]{EventConstants.TOPIC_CREATED, EventConstants.TOPIC_FAILURE});
167         eventHandlerReg = context.registerService(EventHandler.class.getName(), this, props);
168     }
169
170     /**
171      * Implemented from BundleActivator.
172      */
173     @Override
174     public void stop(final BundleContext context) {
175         bundleTracker.close();
176         blueprintExtenderServiceTracker.close();
177         quiesceParticipantTracker.close();
178
179         AriesFrameworkUtil.safeUnregisterService(eventHandlerReg);
180         AriesFrameworkUtil.safeUnregisterService(namespaceReg);
181         AriesFrameworkUtil.safeUnregisterService(blueprintContainerRestartReg);
182     }
183
184     /**
185      * Implemented from SynchronousBundleListener.
186      */
187     @Override
188     public void bundleChanged(final BundleEvent event) {
189         // If the system bundle (id 0) is stopping, do an orderly shutdown of all blueprint containers. On
190         // shutdown the system bundle is stopped first.
191         if (event.getBundle().getBundleId() == SYSTEM_BUNDLE_ID && event.getType() == BundleEvent.STOPPING) {
192             shutdownAllContainers();
193         }
194     }
195
196     /**
197      * Implemented from BundleActivator.
198      */
199     @Override
200     public Bundle addingBundle(final Bundle bundle, final BundleEvent event) {
201         modifiedBundle(bundle, event, bundle);
202         return bundle;
203     }
204
205     /**
206      * Implemented from BundleTrackerCustomizer.
207      */
208     @Override
209     public void modifiedBundle(final Bundle bundle, final BundleEvent event, final Bundle object) {
210         if (shuttingDown) {
211             return;
212         }
213
214         if (bundle.getState() == Bundle.ACTIVE) {
215             List<Object> paths = findBlueprintPaths(bundle);
216
217             if (!paths.isEmpty()) {
218                 LOG.info("Creating blueprint container for bundle {} with paths {}", bundle, paths);
219
220                 blueprintExtenderService.createContainer(bundle, paths);
221             }
222         }
223     }
224
225     /**
226      * Implemented from BundleTrackerCustomizer.
227      */
228     @Override
229     public void removedBundle(final Bundle bundle, final BundleEvent event, final Bundle object) {
230         // BlueprintExtenderService will handle this.
231     }
232
233     /**
234      * Implemented from EventHandler to listen for blueprint events.
235      *
236      * @param event the event to handle
237      */
238     @Override
239     public void handleEvent(final Event event) {
240         if (EventConstants.TOPIC_CREATED.equals(event.getTopic())) {
241             LOG.info("Blueprint container for bundle {} was successfully created",
242                     event.getProperty(EventConstants.BUNDLE));
243             return;
244         }
245
246         // If the container timed out waiting for dependencies, we'll destroy it and start it again. This
247         // is indicated via a non-null DEPENDENCIES property containing the missing dependencies. The
248         // default timeout is 5 min and ideally we would set this to infinite but the timeout can only
249         // be set at the bundle level in the manifest - there's no way to set it globally.
250         if (EventConstants.TOPIC_FAILURE.equals(event.getTopic())
251                 && event.getProperty(EventConstants.DEPENDENCIES) != null) {
252             Bundle bundle = (Bundle) event.getProperty(EventConstants.BUNDLE);
253
254             List<Object> paths = findBlueprintPaths(bundle);
255             if (!paths.isEmpty()) {
256                 LOG.warn("Blueprint container for bundle {} timed out waiting for dependencies - restarting it",
257                         event.getProperty(EventConstants.BUNDLE));
258
259                 restartService.restartContainer(bundle, paths);
260             }
261         }
262     }
263
264     @SuppressWarnings({ "rawtypes", "unchecked" })
265     static List<Object> findBlueprintPaths(final Bundle bundle) {
266         Enumeration<?> rntries = bundle.findEntries(BLUEPRINT_FILE_PATH, BLUEPRINT_FLE_PATTERN, false);
267         if (rntries == null) {
268             return Collections.emptyList();
269         } else {
270             return Collections.list((Enumeration)rntries);
271         }
272     }
273
274     private void shutdownAllContainers() {
275         shuttingDown = true;
276
277         restartService.close();
278
279         // Close all CSS modules first.
280         ConfigSystemService configSystem = getOSGiService(ConfigSystemService.class);
281         if (configSystem != null) {
282             configSystem.closeAllConfigModules();
283         }
284
285         LOG.info("Shutting down all blueprint containers...");
286
287         Collection<Bundle> containerBundles = new HashSet<>(Arrays.asList(bundleContext.getBundles()));
288         while (!containerBundles.isEmpty()) {
289             // For each iteration of getBundlesToDestroy, as containers are destroyed, other containers become
290             // eligible to be destroyed. We loop until we've destroyed them all.
291             for (Bundle bundle : getBundlesToDestroy(containerBundles)) {
292                 containerBundles.remove(bundle);
293                 BlueprintContainer container = blueprintExtenderService.getContainer(bundle);
294                 if (container != null) {
295                     blueprintExtenderService.destroyContainer(bundle, container);
296                 }
297             }
298         }
299
300         LOG.info("Shutdown of blueprint containers complete");
301     }
302
303     private List<Bundle> getBundlesToDestroy(final Collection<Bundle> containerBundles) {
304         List<Bundle> bundlesToDestroy = new ArrayList<>();
305
306         // Find all container bundles that either have no registered services or whose services are no
307         // longer in use.
308         for (Bundle bundle : containerBundles) {
309             ServiceReference<?>[] references = bundle.getRegisteredServices();
310             int usage = 0;
311             if (references != null) {
312                 for (ServiceReference<?> reference : references) {
313                     usage += getServiceUsage(reference);
314                 }
315             }
316
317             LOG.debug("Usage for bundle {} is {}", bundle, usage);
318             if (usage == 0) {
319                 bundlesToDestroy.add(bundle);
320             }
321         }
322
323         if (!bundlesToDestroy.isEmpty()) {
324             Collections.sort(bundlesToDestroy, (b1, b2) -> (int) (b2.getLastModified() - b1.getLastModified()));
325
326             LOG.debug("Selected bundles {} for destroy (no services in use)", bundlesToDestroy);
327         } else {
328             // There's either no more container bundles or they all have services being used. For
329             // the latter it means there's either circular service usage or a service is being used
330             // by a non-container bundle. But we need to make progress so we pick the bundle with a
331             // used service with the highest service ID. Each service is assigned a monotonically
332             // increasing ID as they are registered. By picking the bundle with the highest service
333             // ID, we're picking the bundle that was (likely) started after all the others and thus
334             // is likely the safest to destroy at this point.
335
336             Bundle bundle = findBundleWithHighestUsedServiceId(containerBundles);
337             if (bundle != null) {
338                 bundlesToDestroy.add(bundle);
339             }
340
341             LOG.debug("Selected bundle {} for destroy (lowest ranking service or highest service ID)",
342                     bundlesToDestroy);
343         }
344
345         return bundlesToDestroy;
346     }
347
348     @Nullable
349     private Bundle findBundleWithHighestUsedServiceId(final Collection<Bundle> containerBundles) {
350         ServiceReference<?> highestServiceRef = null;
351         for (Bundle bundle : containerBundles) {
352             ServiceReference<?>[] references = bundle.getRegisteredServices();
353             if (references == null) {
354                 continue;
355             }
356
357             for (ServiceReference<?> reference : references) {
358                 // We did check the service usage previously but it's possible the usage has changed since then.
359                 if (getServiceUsage(reference) == 0) {
360                     continue;
361                 }
362
363                 // Choose 'reference' if it has a lower service ranking or, if the rankings are equal
364                 // which is usually the case, if it has a higher service ID. For the latter the < 0
365                 // check looks backwards but that's how ServiceReference#compareTo is documented to work.
366                 if (highestServiceRef == null || reference.compareTo(highestServiceRef) < 0) {
367                     LOG.debug("Currently selecting bundle {} for destroy (with reference {})", bundle, reference);
368                     highestServiceRef = reference;
369                 }
370             }
371         }
372
373         return highestServiceRef == null ? null : highestServiceRef.getBundle();
374     }
375
376     private static int getServiceUsage(final ServiceReference<?> ref) {
377         Bundle[] usingBundles = ref.getUsingBundles();
378         return usingBundles != null ? usingBundles.length : 0;
379     }
380
381     private <T> T getOSGiService(final Class<T> serviceInterface) {
382         try {
383             ServiceReference<T> serviceReference = bundleContext.getServiceReference(serviceInterface);
384             if (serviceReference == null) {
385                 LOG.warn("{} service reference not found", serviceInterface.getSimpleName());
386                 return null;
387             }
388
389             T service = bundleContext.getService(serviceReference);
390             if (service == null) {
391                 // This could happen on shutdown if the service was already unregistered so we log as debug.
392                 LOG.debug("{} service instance was not found", serviceInterface.getSimpleName());
393             }
394
395             return service;
396         } catch (final IllegalStateException e) {
397             // This is thrown if the BundleContext is no longer valid which is possible on shutdown so we
398             // log as debug.
399             LOG.debug("Error obtaining OSGi service {}", serviceInterface.getSimpleName(), e);
400         }
401
402         return null;
403     }
404 }