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.Comparator;
15 import java.util.Dictionary;
16 import java.util.Enumeration;
17 import java.util.HashSet;
18 import java.util.Hashtable;
19 import java.util.List;
20 import org.apache.aries.blueprint.NamespaceHandler;
21 import org.apache.aries.blueprint.services.BlueprintExtenderService;
22 import org.apache.aries.util.AriesFrameworkUtil;
23 import org.opendaylight.controller.blueprint.ext.OpendaylightNamespaceHandler;
24 import org.opendaylight.controller.config.api.ConfigSystemService;
25 import org.osgi.framework.Bundle;
26 import org.osgi.framework.BundleActivator;
27 import org.osgi.framework.BundleContext;
28 import org.osgi.framework.BundleEvent;
29 import org.osgi.framework.ServiceReference;
30 import org.osgi.framework.ServiceRegistration;
31 import org.osgi.framework.SynchronousBundleListener;
32 import org.osgi.service.blueprint.container.BlueprintContainer;
33 import org.osgi.service.blueprint.container.EventConstants;
34 import org.osgi.service.event.Event;
35 import org.osgi.service.event.EventHandler;
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;
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.
49 * @author Thomas Pantelis
51 public class BlueprintBundleTracker implements BundleActivator, BundleTrackerCustomizer<Bundle>, EventHandler,
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;
58 private ServiceTracker<BlueprintExtenderService, BlueprintExtenderService> serviceTracker;
59 private BundleTracker<Bundle> bundleTracker;
60 private BundleContext bundleContext;
61 private volatile BlueprintExtenderService blueprintExtenderService;
62 private volatile ServiceRegistration<?> blueprintContainerRestartReg;
63 private volatile BlueprintContainerRestartServiceImpl restartService;
64 private volatile boolean shuttingDown;
65 private ServiceRegistration<?> eventHandlerReg;
66 private ServiceRegistration<?> namespaceReg;
69 * Implemented from BundleActivator.
72 public void start(BundleContext context) {
73 LOG.info("Starting {}", getClass().getSimpleName());
75 bundleContext = context;
77 registerBlueprintEventHandler(context);
79 registerNamespaceHandler(context);
81 bundleTracker = new BundleTracker<>(context, Bundle.ACTIVE, this);
83 serviceTracker = new ServiceTracker<>(context, BlueprintExtenderService.class.getName(),
84 new ServiceTrackerCustomizer<BlueprintExtenderService, BlueprintExtenderService>() {
86 public BlueprintExtenderService addingService(
87 ServiceReference<BlueprintExtenderService> reference) {
88 blueprintExtenderService = reference.getBundle().getBundleContext().getService(reference);
91 context.addBundleListener(BlueprintBundleTracker.this);
93 LOG.debug("Got BlueprintExtenderService");
95 restartService = new BlueprintContainerRestartServiceImpl(blueprintExtenderService);
96 blueprintContainerRestartReg = context.registerService(
97 BlueprintContainerRestartService.class.getName(), restartService, new Hashtable<>());
99 return blueprintExtenderService;
103 public void modifiedService(ServiceReference<BlueprintExtenderService> reference,
104 BlueprintExtenderService service) {
108 public void removedService(ServiceReference<BlueprintExtenderService> reference,
109 BlueprintExtenderService service) {
112 serviceTracker.open();
115 private void registerNamespaceHandler(BundleContext context) {
116 Dictionary<String, Object> props = new Hashtable<>();
117 props.put("osgi.service.blueprint.namespace", OpendaylightNamespaceHandler.NAMESPACE_1_0_0);
118 namespaceReg = context.registerService(NamespaceHandler.class.getName(),
119 new OpendaylightNamespaceHandler(), props);
122 private void registerBlueprintEventHandler(BundleContext context) {
123 Dictionary<String, Object> props = new Hashtable<>();
124 props.put(org.osgi.service.event.EventConstants.EVENT_TOPIC,
125 new String[]{EventConstants.TOPIC_CREATED, EventConstants.TOPIC_FAILURE});
126 eventHandlerReg = context.registerService(EventHandler.class.getName(), this, props);
130 * Implemented from BundleActivator.
133 public void stop(BundleContext context) {
134 bundleTracker.close();
135 serviceTracker.close();
137 AriesFrameworkUtil.safeUnregisterService(eventHandlerReg);
138 AriesFrameworkUtil.safeUnregisterService(namespaceReg);
139 AriesFrameworkUtil.safeUnregisterService(blueprintContainerRestartReg);
143 * Implemented from SynchronousBundleListener.
146 public void bundleChanged(BundleEvent event) {
147 // If the system bundle (id 0) is stopping, do an orderly shutdown of all blueprint containers. On
148 // shutdown the system bundle is stopped first.
149 if(event.getBundle().getBundleId() == SYSTEM_BUNDLE_ID && event.getType() == BundleEvent.STOPPING) {
150 shutdownAllContainers();
155 * Implemented from BundleActivator.
158 public Bundle addingBundle(Bundle bundle, BundleEvent event) {
159 modifiedBundle(bundle, event, bundle);
164 * Implemented from BundleActivator.
167 public void modifiedBundle(Bundle bundle, BundleEvent event, Bundle object) {
172 if(bundle.getState() == Bundle.ACTIVE) {
173 List<Object> paths = findBlueprintPaths(bundle);
175 if(!paths.isEmpty()) {
176 LOG.info("Creating blueprint container for bundle {} with paths {}", bundle, paths);
178 blueprintExtenderService.createContainer(bundle, paths);
184 * Implemented from BundleActivator.
187 public void removedBundle(Bundle bundle, BundleEvent event, Bundle object) {
188 // BlueprintExtenderService will handle this.
192 * Implemented from EventHandler to listen for blueprint events.
197 public void handleEvent(Event event) {
198 if(EventConstants.TOPIC_CREATED.equals(event.getTopic())) {
199 LOG.info("Blueprint container for bundle {} was successfully created",
200 event.getProperty(EventConstants.BUNDLE));
201 } else if(EventConstants.TOPIC_FAILURE.equals(event.getTopic())) {
202 // If the container timed out waiting for dependencies, we'll destroy it and start it again. This
203 // is indicated via a non-null DEPENDENCIES property containing the missing dependencies. The
204 // default timeout is 5 min and ideally we would set this to infinite but the timeout can only
205 // be set at the bundle level in the manifest - there's no way to set it globally.
206 if(event.getProperty(EventConstants.DEPENDENCIES) != null) {
207 Bundle bundle = (Bundle) event.getProperty(EventConstants.BUNDLE);
209 List<Object> paths = findBlueprintPaths(bundle);
210 if(!paths.isEmpty()) {
211 LOG.warn("Blueprint container for bundle {} timed out waiting for dependencies - restarting it",
212 event.getProperty(EventConstants.BUNDLE));
214 restartService.restartContainer(bundle, paths);
220 @SuppressWarnings({ "rawtypes", "unchecked" })
221 static List<Object> findBlueprintPaths(Bundle bundle) {
222 Enumeration<?> e = bundle.findEntries(BLUEPRINT_FILE_PATH, BLUEPRINT_FLE_PATTERN, false);
224 return Collections.emptyList();
226 return Collections.list((Enumeration)e);
230 private void shutdownAllContainers() {
233 restartService.close();
235 // Close all CSS modules first.
236 ConfigSystemService configSystem = getOSGiService(ConfigSystemService.class);
237 if(configSystem != null) {
238 configSystem.closeAllConfigModules();
241 LOG.info("Shutting down all blueprint containers...");
243 Collection<Bundle> containerBundles = new HashSet<>(Arrays.asList(bundleContext.getBundles()));
244 while(!containerBundles.isEmpty()) {
245 // For each iteration of getBundlesToDestroy, as containers are destroyed, other containers become
246 // eligible to be destroyed. We loop until we've destroyed them all.
247 for(Bundle bundle : getBundlesToDestroy(containerBundles)) {
248 containerBundles.remove(bundle);
249 BlueprintContainer container = blueprintExtenderService.getContainer(bundle);
250 if(container != null) {
251 blueprintExtenderService.destroyContainer(bundle, container);
256 LOG.info("Shutdown of blueprint containers complete");
259 private List<Bundle> getBundlesToDestroy(Collection<Bundle> containerBundles) {
260 List<Bundle> bundlesToDestroy = new ArrayList<>();
262 // Find all container bundles that either have no registered services or whose services are no
264 for(Bundle bundle : containerBundles) {
265 ServiceReference<?>[] references = bundle.getRegisteredServices();
267 if(references != null) {
268 for(ServiceReference<?> reference : references) {
269 usage += getServiceUsage(reference);
273 LOG.debug("Usage for bundle {} is {}", bundle, usage);
275 bundlesToDestroy.add(bundle);
279 if(!bundlesToDestroy.isEmpty()) {
280 Collections.sort(bundlesToDestroy, new Comparator<Bundle>() {
282 public int compare(Bundle b1, Bundle b2) {
283 return (int) (b2.getLastModified() - b1.getLastModified());
287 LOG.debug("Selected bundles {} for destroy (no services in use)", bundlesToDestroy);
289 // There's either no more container bundles or they all have services being used. For
290 // the latter it means there's either circular service usage or a service is being used
291 // by a non-container bundle. But we need to make progress so we pick the bundle with a
292 // used service with the highest service ID. Each service is assigned a monotonically
293 // increasing ID as they are registered. By picking the bundle with the highest service
294 // ID, we're picking the bundle that was (likely) started after all the others and thus
295 // is likely the safest to destroy at this point.
297 ServiceReference<?> ref = null;
298 for(Bundle bundle : containerBundles) {
299 ServiceReference<?>[] references = bundle.getRegisteredServices();
300 if(references == null) {
304 for(ServiceReference<?> reference : references) {
305 // We did check the service usage above but it's possible the usage has changed since
307 if(getServiceUsage(reference) == 0) {
311 // Choose 'reference' if it has a lower service ranking or, if the rankings are equal
312 // which is usually the case, if it has a higher service ID. For the latter the < 0
313 // check looks backwards but that's how ServiceReference#compareTo is documented to work.
314 if(ref == null || reference.compareTo(ref) < 0) {
315 LOG.debug("Currently selecting bundle {} for destroy (with reference {})", bundle, reference);
322 bundlesToDestroy.add(ref.getBundle());
325 LOG.debug("Selected bundle {} for destroy (lowest ranking service or highest service ID)",
329 return bundlesToDestroy;
332 private static int getServiceUsage(ServiceReference<?> ref) {
333 Bundle[] usingBundles = ref.getUsingBundles();
334 return usingBundles != null ? usingBundles.length : 0;
337 private <T> T getOSGiService(Class<T> serviceInterface) {
339 ServiceReference<T> serviceReference = bundleContext.getServiceReference(serviceInterface);
340 if(serviceReference == null) {
341 LOG.warn("{} service reference not found", serviceInterface.getSimpleName());
345 T service = bundleContext.getService(serviceReference);
346 if(service == null) {
347 // This could happen on shutdown if the service was already unregistered so we log as debug.
348 LOG.debug("{} service instance was not found", serviceInterface.getSimpleName());
352 } catch(IllegalStateException e) {
353 // This is thrown if the BundleContext is no longer valid which is possible on shutdown so we
355 LOG.debug("Error obtaining OSGi service {}", serviceInterface.getSimpleName(), e);