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 edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
11 import java.util.ArrayList;
12 import java.util.Arrays;
13 import java.util.Collection;
14 import java.util.Collections;
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.quiesce.participant.QuiesceParticipant;
23 import org.apache.aries.util.AriesFrameworkUtil;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.gaul.modernizer_maven_annotations.SuppressModernizer;
26 import org.opendaylight.controller.blueprint.ext.OpendaylightNamespaceHandler;
27 import org.opendaylight.yangtools.util.xml.UntrustedXML;
28 import org.osgi.framework.Bundle;
29 import org.osgi.framework.BundleActivator;
30 import org.osgi.framework.BundleContext;
31 import org.osgi.framework.BundleEvent;
32 import org.osgi.framework.ServiceReference;
33 import org.osgi.framework.ServiceRegistration;
34 import org.osgi.framework.SynchronousBundleListener;
35 import org.osgi.service.blueprint.container.BlueprintContainer;
36 import org.osgi.service.blueprint.container.BlueprintEvent;
37 import org.osgi.service.blueprint.container.BlueprintListener;
38 import org.osgi.util.tracker.BundleTracker;
39 import org.osgi.util.tracker.BundleTrackerCustomizer;
40 import org.osgi.util.tracker.ServiceTracker;
41 import org.osgi.util.tracker.ServiceTrackerCustomizer;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
46 * This class is created in bundle activation and scans ACTIVE bundles for blueprint XML files located under
47 * the well-known org/opendaylight/blueprint/ path and deploys the XML files via the Aries
48 * BlueprintExtenderService. This path differs from the standard OSGI-INF/blueprint path to allow for
49 * controlled deployment of blueprint containers in an orderly manner.
51 * @author Thomas Pantelis
53 public class BlueprintBundleTracker implements BundleActivator, BundleTrackerCustomizer<Bundle>, BlueprintListener,
54 SynchronousBundleListener {
55 private static final Logger LOG = LoggerFactory.getLogger(BlueprintBundleTracker.class);
56 private static final String ODL_CUSTOM_BLUEPRINT_FILE_PATH = "org/opendaylight/blueprint/";
57 private static final String STANDARD_BLUEPRINT_FILE_PATH = "OSGI-INF/blueprint/";
58 private static final String BLUEPRINT_FLE_PATTERN = "*.xml";
59 private static final long SYSTEM_BUNDLE_ID = 0;
61 private ServiceTracker<BlueprintExtenderService, BlueprintExtenderService> blueprintExtenderServiceTracker;
62 private ServiceTracker<QuiesceParticipant, QuiesceParticipant> quiesceParticipantTracker;
63 private BundleTracker<Bundle> bundleTracker;
64 private BundleContext bundleContext;
65 private volatile BlueprintExtenderService blueprintExtenderService;
66 private volatile QuiesceParticipant quiesceParticipant;
67 private volatile ServiceRegistration<?> blueprintContainerRestartReg;
68 private volatile BlueprintContainerRestartServiceImpl restartService;
69 private volatile boolean shuttingDown;
70 private ServiceRegistration<?> eventHandlerReg;
71 private ServiceRegistration<?> namespaceReg;
74 * Implemented from BundleActivator.
77 public void start(final BundleContext context) {
78 LOG.info("Starting {}", getClass().getSimpleName());
80 // CONTROLLER-1867: force UntrustedXML initialization, so that it uses our TCCL to initialize
81 UntrustedXML.newDocumentBuilder();
83 restartService = new BlueprintContainerRestartServiceImpl();
85 bundleContext = context;
87 registerBlueprintEventHandler(context);
89 registerNamespaceHandler(context);
91 bundleTracker = new BundleTracker<>(context, Bundle.ACTIVE, this);
93 blueprintExtenderServiceTracker = new ServiceTracker<>(context, BlueprintExtenderService.class,
94 new ServiceTrackerCustomizer<BlueprintExtenderService, BlueprintExtenderService>() {
96 public BlueprintExtenderService addingService(
97 final ServiceReference<BlueprintExtenderService> reference) {
98 return onBlueprintExtenderServiceAdded(reference);
102 public void modifiedService(final ServiceReference<BlueprintExtenderService> reference,
103 final BlueprintExtenderService service) {
107 public void removedService(final ServiceReference<BlueprintExtenderService> reference,
108 final BlueprintExtenderService service) {
111 blueprintExtenderServiceTracker.open();
113 quiesceParticipantTracker = new ServiceTracker<>(context, QuiesceParticipant.class,
114 new ServiceTrackerCustomizer<QuiesceParticipant, QuiesceParticipant>() {
116 public QuiesceParticipant addingService(
117 final ServiceReference<QuiesceParticipant> reference) {
118 return onQuiesceParticipantAdded(reference);
122 public void modifiedService(final ServiceReference<QuiesceParticipant> reference,
123 final QuiesceParticipant service) {
127 public void removedService(final ServiceReference<QuiesceParticipant> reference,
128 final QuiesceParticipant service) {
131 quiesceParticipantTracker.open();
134 @SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD",
135 justification = "https://github.com/spotbugs/spotbugs/issues/811")
136 private QuiesceParticipant onQuiesceParticipantAdded(final ServiceReference<QuiesceParticipant> reference) {
137 quiesceParticipant = reference.getBundle().getBundleContext().getService(reference);
139 LOG.debug("Got QuiesceParticipant");
141 restartService.setQuiesceParticipant(quiesceParticipant);
143 return quiesceParticipant;
146 @SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD",
147 justification = "https://github.com/spotbugs/spotbugs/issues/811")
148 private BlueprintExtenderService onBlueprintExtenderServiceAdded(
149 final ServiceReference<BlueprintExtenderService> reference) {
150 blueprintExtenderService = reference.getBundle().getBundleContext().getService(reference);
151 bundleTracker.open();
153 bundleContext.addBundleListener(BlueprintBundleTracker.this);
155 LOG.debug("Got BlueprintExtenderService");
157 restartService.setBlueprintExtenderService(blueprintExtenderService);
159 blueprintContainerRestartReg = bundleContext.registerService(BlueprintContainerRestartService.class,
160 restartService, null);
162 return blueprintExtenderService;
165 private void registerNamespaceHandler(final BundleContext context) {
166 Dictionary<String, Object> props = emptyDict();
167 props.put("osgi.service.blueprint.namespace", OpendaylightNamespaceHandler.NAMESPACE_1_0_0);
168 namespaceReg = context.registerService(NamespaceHandler.class, new OpendaylightNamespaceHandler(), props);
171 private void registerBlueprintEventHandler(final BundleContext context) {
172 eventHandlerReg = context.registerService(BlueprintListener.class, this, null);
176 private static Dictionary<String, Object> emptyDict() {
177 return new Hashtable<>();
181 * Implemented from BundleActivator.
184 public void stop(final BundleContext context) {
185 bundleTracker.close();
186 blueprintExtenderServiceTracker.close();
187 quiesceParticipantTracker.close();
189 AriesFrameworkUtil.safeUnregisterService(eventHandlerReg);
190 AriesFrameworkUtil.safeUnregisterService(namespaceReg);
191 AriesFrameworkUtil.safeUnregisterService(blueprintContainerRestartReg);
195 * Implemented from SynchronousBundleListener.
198 public void bundleChanged(final BundleEvent event) {
199 // If the system bundle (id 0) is stopping, do an orderly shutdown of all blueprint containers. On
200 // shutdown the system bundle is stopped first.
201 if (event.getBundle().getBundleId() == SYSTEM_BUNDLE_ID && event.getType() == BundleEvent.STOPPING) {
202 shutdownAllContainers();
207 * Implemented from BundleActivator.
210 public Bundle addingBundle(final Bundle bundle, final BundleEvent event) {
211 modifiedBundle(bundle, event, bundle);
216 * Implemented from BundleTrackerCustomizer.
219 public void modifiedBundle(final Bundle bundle, final BundleEvent event, final Bundle object) {
224 if (bundle.getState() == Bundle.ACTIVE) {
225 List<Object> paths = findBlueprintPaths(bundle, ODL_CUSTOM_BLUEPRINT_FILE_PATH);
227 if (!paths.isEmpty()) {
228 LOG.info("Creating blueprint container for bundle {} with paths {}", bundle, paths);
230 blueprintExtenderService.createContainer(bundle, paths);
236 * Implemented from BundleTrackerCustomizer.
239 public void removedBundle(final Bundle bundle, final BundleEvent event, final Bundle object) {
240 // BlueprintExtenderService will handle this.
244 * Implemented from BlueprintListener to listen for blueprint events.
246 * @param event the event to handle
249 public void blueprintEvent(final BlueprintEvent event) {
250 if (event.getType() == BlueprintEvent.CREATED) {
251 LOG.info("Blueprint container for bundle {} was successfully created", event.getBundle());
255 // If the container timed out waiting for dependencies, we'll destroy it and start it again. This
256 // is indicated via a non-null DEPENDENCIES property containing the missing dependencies. The
257 // default timeout is 5 min and ideally we would set this to infinite but the timeout can only
258 // be set at the bundle level in the manifest - there's no way to set it globally.
259 if (event.getType() == BlueprintEvent.FAILURE && event.getDependencies() != null) {
260 Bundle bundle = event.getBundle();
262 List<Object> paths = findBlueprintPaths(bundle);
263 if (!paths.isEmpty()) {
264 LOG.warn("Blueprint container for bundle {} timed out waiting for dependencies - restarting it",
267 restartService.restartContainer(bundle, paths);
272 static List<Object> findBlueprintPaths(final Bundle bundle) {
273 List<Object> paths = findBlueprintPaths(bundle, STANDARD_BLUEPRINT_FILE_PATH);
274 return !paths.isEmpty() ? paths : findBlueprintPaths(bundle, ODL_CUSTOM_BLUEPRINT_FILE_PATH);
277 @SuppressWarnings({ "rawtypes", "unchecked" })
278 private static List<Object> findBlueprintPaths(final Bundle bundle, final String path) {
279 Enumeration<?> rntries = bundle.findEntries(path, BLUEPRINT_FLE_PATTERN, false);
280 if (rntries == null) {
281 return Collections.emptyList();
283 return Collections.list((Enumeration)rntries);
287 private void shutdownAllContainers() {
290 restartService.close();
292 LOG.info("Shutting down all blueprint containers...");
294 Collection<Bundle> containerBundles = new HashSet<>(Arrays.asList(bundleContext.getBundles()));
295 while (!containerBundles.isEmpty()) {
296 // For each iteration of getBundlesToDestroy, as containers are destroyed, other containers become
297 // eligible to be destroyed. We loop until we've destroyed them all.
298 for (Bundle bundle : getBundlesToDestroy(containerBundles)) {
299 containerBundles.remove(bundle);
300 BlueprintContainer container = blueprintExtenderService.getContainer(bundle);
301 if (container != null) {
302 blueprintExtenderService.destroyContainer(bundle, container);
307 LOG.info("Shutdown of blueprint containers complete");
310 private static List<Bundle> getBundlesToDestroy(final Collection<Bundle> containerBundles) {
311 List<Bundle> bundlesToDestroy = new ArrayList<>();
313 // Find all container bundles that either have no registered services or whose services are no
315 for (Bundle bundle : containerBundles) {
316 ServiceReference<?>[] references = bundle.getRegisteredServices();
318 if (references != null) {
319 for (ServiceReference<?> reference : references) {
320 usage += getServiceUsage(reference);
324 LOG.debug("Usage for bundle {} is {}", bundle, usage);
326 bundlesToDestroy.add(bundle);
330 if (!bundlesToDestroy.isEmpty()) {
331 bundlesToDestroy.sort((b1, b2) -> (int) (b2.getLastModified() - b1.getLastModified()));
333 LOG.debug("Selected bundles {} for destroy (no services in use)", bundlesToDestroy);
335 // There's either no more container bundles or they all have services being used. For
336 // the latter it means there's either circular service usage or a service is being used
337 // by a non-container bundle. But we need to make progress so we pick the bundle with a
338 // used service with the highest service ID. Each service is assigned a monotonically
339 // increasing ID as they are registered. By picking the bundle with the highest service
340 // ID, we're picking the bundle that was (likely) started after all the others and thus
341 // is likely the safest to destroy at this point.
343 Bundle bundle = findBundleWithHighestUsedServiceId(containerBundles);
344 if (bundle != null) {
345 bundlesToDestroy.add(bundle);
348 LOG.debug("Selected bundle {} for destroy (lowest ranking service or highest service ID)",
352 return bundlesToDestroy;
355 private static @Nullable Bundle findBundleWithHighestUsedServiceId(final Collection<Bundle> containerBundles) {
356 ServiceReference<?> highestServiceRef = null;
357 for (Bundle bundle : containerBundles) {
358 ServiceReference<?>[] references = bundle.getRegisteredServices();
359 if (references == null) {
363 for (ServiceReference<?> reference : references) {
364 // We did check the service usage previously but it's possible the usage has changed since then.
365 if (getServiceUsage(reference) == 0) {
369 // Choose 'reference' if it has a lower service ranking or, if the rankings are equal
370 // which is usually the case, if it has a higher service ID. For the latter the < 0
371 // check looks backwards but that's how ServiceReference#compareTo is documented to work.
372 if (highestServiceRef == null || reference.compareTo(highestServiceRef) < 0) {
373 LOG.debug("Currently selecting bundle {} for destroy (with reference {})", bundle, reference);
374 highestServiceRef = reference;
379 return highestServiceRef == null ? null : highestServiceRef.getBundle();
382 private static int getServiceUsage(final ServiceReference<?> ref) {
383 Bundle[] usingBundles = ref.getUsingBundles();
384 return usingBundles != null ? usingBundles.length : 0;