2 * Copyright (c) 2016 Brocade Communications Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.controller.blueprint;
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;
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.
50 * @author Thomas Pantelis
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;
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;
72 * Implemented from BundleActivator.
75 public void start(final BundleContext context) {
76 LOG.info("Starting {}", getClass().getSimpleName());
78 restartService = new BlueprintContainerRestartServiceImpl();
80 bundleContext = context;
82 registerBlueprintEventHandler(context);
84 registerNamespaceHandler(context);
86 bundleTracker = new BundleTracker<>(context, Bundle.ACTIVE, this);
88 blueprintExtenderServiceTracker = new ServiceTracker<>(context, BlueprintExtenderService.class.getName(),
89 new ServiceTrackerCustomizer<BlueprintExtenderService, BlueprintExtenderService>() {
91 public BlueprintExtenderService addingService(
92 final ServiceReference<BlueprintExtenderService> reference) {
93 return onBlueprintExtenderServiceAdded(reference);
97 public void modifiedService(final ServiceReference<BlueprintExtenderService> reference,
98 final BlueprintExtenderService service) {
102 public void removedService(final ServiceReference<BlueprintExtenderService> reference,
103 final BlueprintExtenderService service) {
106 blueprintExtenderServiceTracker.open();
108 quiesceParticipantTracker = new ServiceTracker<>(context, QuiesceParticipant.class.getName(),
109 new ServiceTrackerCustomizer<QuiesceParticipant, QuiesceParticipant>() {
111 public QuiesceParticipant addingService(
112 final ServiceReference<QuiesceParticipant> reference) {
113 return onQuiesceParticipantAdded(reference);
117 public void modifiedService(final ServiceReference<QuiesceParticipant> reference,
118 final QuiesceParticipant service) {
122 public void removedService(final ServiceReference<QuiesceParticipant> reference,
123 final QuiesceParticipant service) {
126 quiesceParticipantTracker.open();
129 private QuiesceParticipant onQuiesceParticipantAdded(final ServiceReference<QuiesceParticipant> reference) {
130 quiesceParticipant = reference.getBundle().getBundleContext().getService(reference);
132 LOG.debug("Got QuiesceParticipant");
134 restartService.setQuiesceParticipant(quiesceParticipant);
136 return quiesceParticipant;
139 private BlueprintExtenderService onBlueprintExtenderServiceAdded(
140 final ServiceReference<BlueprintExtenderService> reference) {
141 blueprintExtenderService = reference.getBundle().getBundleContext().getService(reference);
142 bundleTracker.open();
144 bundleContext.addBundleListener(BlueprintBundleTracker.this);
146 LOG.debug("Got BlueprintExtenderService");
148 restartService.setBlueprintExtenderService(blueprintExtenderService);
150 blueprintContainerRestartReg = bundleContext.registerService(
151 BlueprintContainerRestartService.class.getName(), restartService, new Hashtable<>());
153 return blueprintExtenderService;
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);
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);
171 * Implemented from BundleActivator.
174 public void stop(final BundleContext context) {
175 bundleTracker.close();
176 blueprintExtenderServiceTracker.close();
177 quiesceParticipantTracker.close();
179 AriesFrameworkUtil.safeUnregisterService(eventHandlerReg);
180 AriesFrameworkUtil.safeUnregisterService(namespaceReg);
181 AriesFrameworkUtil.safeUnregisterService(blueprintContainerRestartReg);
185 * Implemented from SynchronousBundleListener.
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();
197 * Implemented from BundleActivator.
200 public Bundle addingBundle(final Bundle bundle, final BundleEvent event) {
201 modifiedBundle(bundle, event, bundle);
206 * Implemented from BundleTrackerCustomizer.
209 public void modifiedBundle(final Bundle bundle, final BundleEvent event, final Bundle object) {
214 if (bundle.getState() == Bundle.ACTIVE) {
215 List<Object> paths = findBlueprintPaths(bundle);
217 if (!paths.isEmpty()) {
218 LOG.info("Creating blueprint container for bundle {} with paths {}", bundle, paths);
220 blueprintExtenderService.createContainer(bundle, paths);
226 * Implemented from BundleTrackerCustomizer.
229 public void removedBundle(final Bundle bundle, final BundleEvent event, final Bundle object) {
230 // BlueprintExtenderService will handle this.
234 * Implemented from EventHandler to listen for blueprint events.
236 * @param event the event to handle
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));
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);
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));
259 restartService.restartContainer(bundle, paths);
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();
270 return Collections.list((Enumeration)rntries);
274 private void shutdownAllContainers() {
277 restartService.close();
279 // Close all CSS modules first.
280 ConfigSystemService configSystem = getOSGiService(ConfigSystemService.class);
281 if (configSystem != null) {
282 configSystem.closeAllConfigModules();
285 LOG.info("Shutting down all blueprint containers...");
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);
300 LOG.info("Shutdown of blueprint containers complete");
303 private List<Bundle> getBundlesToDestroy(final Collection<Bundle> containerBundles) {
304 List<Bundle> bundlesToDestroy = new ArrayList<>();
306 // Find all container bundles that either have no registered services or whose services are no
308 for (Bundle bundle : containerBundles) {
309 ServiceReference<?>[] references = bundle.getRegisteredServices();
311 if (references != null) {
312 for (ServiceReference<?> reference : references) {
313 usage += getServiceUsage(reference);
317 LOG.debug("Usage for bundle {} is {}", bundle, usage);
319 bundlesToDestroy.add(bundle);
323 if (!bundlesToDestroy.isEmpty()) {
324 Collections.sort(bundlesToDestroy, (b1, b2) -> (int) (b2.getLastModified() - b1.getLastModified()));
326 LOG.debug("Selected bundles {} for destroy (no services in use)", bundlesToDestroy);
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.
336 Bundle bundle = findBundleWithHighestUsedServiceId(containerBundles);
337 if (bundle != null) {
338 bundlesToDestroy.add(bundle);
341 LOG.debug("Selected bundle {} for destroy (lowest ranking service or highest service ID)",
345 return bundlesToDestroy;
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) {
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) {
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;
373 return highestServiceRef == null ? null : highestServiceRef.getBundle();
376 private static int getServiceUsage(final ServiceReference<?> ref) {
377 Bundle[] usingBundles = ref.getUsingBundles();
378 return usingBundles != null ? usingBundles.length : 0;
381 private <T> T getOSGiService(final Class<T> serviceInterface) {
383 ServiceReference<T> serviceReference = bundleContext.getServiceReference(serviceInterface);
384 if (serviceReference == null) {
385 LOG.warn("{} service reference not found", serviceInterface.getSimpleName());
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());
396 } catch (final IllegalStateException e) {
397 // This is thrown if the BundleContext is no longer valid which is possible on shutdown so we
399 LOG.debug("Error obtaining OSGi service {}", serviceInterface.getSimpleName(), e);