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