Restart BP container after dependency wait time out
[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.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;
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> 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;
67
68     /**
69      * Implemented from BundleActivator.
70      */
71     @Override
72     public void start(BundleContext context) {
73         LOG.info("Starting {}", getClass().getSimpleName());
74
75         bundleContext = context;
76
77         registerBlueprintEventHandler(context);
78
79         registerNamespaceHandler(context);
80
81         bundleTracker = new BundleTracker<>(context, Bundle.ACTIVE, this);
82
83         serviceTracker = new ServiceTracker<>(context, BlueprintExtenderService.class.getName(),
84                 new ServiceTrackerCustomizer<BlueprintExtenderService, BlueprintExtenderService>() {
85                     @Override
86                     public BlueprintExtenderService addingService(
87                             ServiceReference<BlueprintExtenderService> reference) {
88                         blueprintExtenderService = reference.getBundle().getBundleContext().getService(reference);
89                         bundleTracker.open();
90
91                         context.addBundleListener(BlueprintBundleTracker.this);
92
93                         LOG.debug("Got BlueprintExtenderService");
94
95                         restartService = new BlueprintContainerRestartServiceImpl(blueprintExtenderService);
96                         blueprintContainerRestartReg = context.registerService(
97                                 BlueprintContainerRestartService.class.getName(), restartService, new Hashtable<>());
98
99                         return blueprintExtenderService;
100                     }
101
102                     @Override
103                     public void modifiedService(ServiceReference<BlueprintExtenderService> reference,
104                             BlueprintExtenderService service) {
105                     }
106
107                     @Override
108                     public void removedService(ServiceReference<BlueprintExtenderService> reference,
109                             BlueprintExtenderService service) {
110                     }
111                 });
112         serviceTracker.open();
113     }
114
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);
120     }
121
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);
127     }
128
129     /**
130      * Implemented from BundleActivator.
131      */
132     @Override
133     public void stop(BundleContext context) {
134         bundleTracker.close();
135         serviceTracker.close();
136
137         AriesFrameworkUtil.safeUnregisterService(eventHandlerReg);
138         AriesFrameworkUtil.safeUnregisterService(namespaceReg);
139         AriesFrameworkUtil.safeUnregisterService(blueprintContainerRestartReg);
140     }
141
142     /**
143      * Implemented from SynchronousBundleListener.
144      */
145     @Override
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();
151         }
152     }
153
154     /**
155      * Implemented from BundleActivator.
156      */
157     @Override
158     public Bundle addingBundle(Bundle bundle, BundleEvent event) {
159         modifiedBundle(bundle, event, bundle);
160         return bundle;
161     }
162
163     /**
164      * Implemented from BundleActivator.
165      */
166     @Override
167     public void modifiedBundle(Bundle bundle, BundleEvent event, Bundle object) {
168         if(shuttingDown) {
169             return;
170         }
171
172         if(bundle.getState() == Bundle.ACTIVE) {
173             List<Object> paths = findBlueprintPaths(bundle);
174
175             if(!paths.isEmpty()) {
176                 LOG.info("Creating blueprint container for bundle {} with paths {}", bundle, paths);
177
178                 blueprintExtenderService.createContainer(bundle, paths);
179             }
180         }
181     }
182
183     /**
184      * Implemented from BundleActivator.
185      */
186     @Override
187     public void removedBundle(Bundle bundle, BundleEvent event, Bundle object) {
188         // BlueprintExtenderService will handle this.
189     }
190
191     /**
192      * Implemented from EventHandler to listen for blueprint events.
193      *
194      * @param event
195      */
196     @Override
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);
208
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));
213
214                     restartService.restartContainer(bundle, paths);
215                 }
216             }
217         }
218     }
219
220     @SuppressWarnings({ "rawtypes", "unchecked" })
221     static List<Object> findBlueprintPaths(Bundle bundle) {
222         Enumeration<?> e = bundle.findEntries(BLUEPRINT_FILE_PATH, BLUEPRINT_FLE_PATTERN, false);
223         if(e == null) {
224             return Collections.emptyList();
225         } else {
226             return Collections.list((Enumeration)e);
227         }
228     }
229
230     private void shutdownAllContainers() {
231         shuttingDown = true;
232
233         restartService.close();
234
235         // Close all CSS modules first.
236         ConfigSystemService configSystem = getOSGiService(ConfigSystemService.class);
237         if(configSystem != null) {
238             configSystem.closeAllConfigModules();
239         }
240
241         LOG.info("Shutting down all blueprint containers...");
242
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);
252                 }
253             }
254         }
255
256         LOG.info("Shutdown of blueprint containers complete");
257     }
258
259     private List<Bundle> getBundlesToDestroy(Collection<Bundle> containerBundles) {
260         List<Bundle> bundlesToDestroy = new ArrayList<Bundle>();
261
262         // Find all container bundles that either have no registered services or whose services are no
263         // longer in use.
264         for(Bundle bundle : containerBundles) {
265             ServiceReference<?>[] references = bundle.getRegisteredServices();
266             int usage = 0;
267             if(references != null) {
268                 for(ServiceReference<?> reference : references) {
269                     usage += getServiceUsage(reference);
270                 }
271             }
272
273             LOG.debug("Usage for bundle {} is {}", bundle, usage);
274             if(usage == 0) {
275                 bundlesToDestroy.add(bundle);
276             }
277         }
278
279         if(!bundlesToDestroy.isEmpty()) {
280             Collections.sort(bundlesToDestroy, new Comparator<Bundle>() {
281                 @Override
282                 public int compare(Bundle b1, Bundle b2) {
283                     return (int) (b2.getLastModified() - b1.getLastModified());
284                 }
285             });
286
287             LOG.debug("Selected bundles {} for destroy (no services in use)", bundlesToDestroy);
288         } else {
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.
296
297             ServiceReference<?> ref = null;
298             for(Bundle bundle : containerBundles) {
299                 ServiceReference<?>[] references = bundle.getRegisteredServices();
300                 if(references == null) {
301                     continue;
302                 }
303
304                 for(ServiceReference<?> reference : references) {
305                     // We did check the service usage above but it's possible the usage has changed since
306                     // then,
307                     if(getServiceUsage(reference) == 0) {
308                         continue;
309                     }
310
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);
316                         ref = reference;
317                     }
318                 }
319             }
320
321             if(ref != null) {
322                 bundlesToDestroy.add(ref.getBundle());
323             }
324
325             LOG.debug("Selected bundle {} for destroy (lowest ranking service or highest service ID)",
326                     bundlesToDestroy);
327         }
328
329         return bundlesToDestroy;
330     }
331
332     private static int getServiceUsage(ServiceReference<?> ref) {
333         Bundle[] usingBundles = ref.getUsingBundles();
334         return usingBundles != null ? usingBundles.length : 0;
335     }
336
337     private <T> T getOSGiService(Class<T> serviceInterface) {
338         try {
339             ServiceReference<T> serviceReference = bundleContext.getServiceReference(serviceInterface);
340             if(serviceReference == null) {
341                 LOG.warn("{} service reference not found", serviceInterface.getSimpleName());
342                 return null;
343             }
344
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());
349             }
350
351             return service;
352         } catch(IllegalStateException e) {
353             // This is thrown if the BundleContext is no longer valid which is possible on shutdown so we
354             // log as debug.
355             LOG.debug("Error obtaining OSGi service {}", serviceInterface.getSimpleName(), e);
356         }
357
358         return null;
359     }
360 }