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