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