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