*/
package org.opendaylight.controller.blueprint;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
+import java.util.Comparator;
import java.util.Dictionary;
import java.util.Enumeration;
+import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import org.apache.aries.blueprint.NamespaceHandler;
import org.apache.aries.blueprint.services.BlueprintExtenderService;
import org.apache.aries.util.AriesFrameworkUtil;
import org.opendaylight.controller.blueprint.ext.OpendaylightNamespaceHandler;
+import org.opendaylight.controller.config.api.ConfigSystemService;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
+import org.osgi.framework.SynchronousBundleListener;
+import org.osgi.service.blueprint.container.BlueprintContainer;
import org.osgi.service.blueprint.container.EventConstants;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventHandler;
*
* @author Thomas Pantelis
*/
-public class BlueprintBundleTracker implements BundleActivator, BundleTrackerCustomizer<Bundle>, EventHandler {
+public class BlueprintBundleTracker implements BundleActivator, BundleTrackerCustomizer<Bundle>, EventHandler,
+ SynchronousBundleListener {
private static final Logger LOG = LoggerFactory.getLogger(BlueprintBundleTracker.class);
private static final String BLUEPRINT_FILE_PATH = "org/opendaylight/blueprint/";
private static final String BLUEPRINT_FLE_PATTERN = "*.xml";
+ private static final long SYSTEM_BUNDLE_ID = 0;
private ServiceTracker<BlueprintExtenderService, BlueprintExtenderService> serviceTracker;
private BundleTracker<Bundle> bundleTracker;
+ private BundleContext bundleContext;
private volatile BlueprintExtenderService blueprintExtenderService;
private volatile ServiceRegistration<?> blueprintContainerRestartReg;
+ private volatile BlueprintContainerRestartServiceImpl restartService;
+ private volatile boolean shuttingDown;
private ServiceRegistration<?> eventHandlerReg;
private ServiceRegistration<?> namespaceReg;
public void start(BundleContext context) {
LOG.info("Starting {}", getClass().getSimpleName());
+ bundleContext = context;
+
registerBlueprintEventHandler(context);
registerNamespaceHandler(context);
blueprintExtenderService = reference.getBundle().getBundleContext().getService(reference);
bundleTracker.open();
+ context.addBundleListener(BlueprintBundleTracker.this);
+
LOG.debug("Got BlueprintExtenderService");
+ restartService = new BlueprintContainerRestartServiceImpl(blueprintExtenderService);
blueprintContainerRestartReg = context.registerService(
- BlueprintContainerRestartService.class.getName(),
- new BlueprintContainerRestartServiceImpl(blueprintExtenderService), new Hashtable<>());
+ BlueprintContainerRestartService.class.getName(), restartService, new Hashtable<>());
return blueprintExtenderService;
}
AriesFrameworkUtil.safeUnregisterService(blueprintContainerRestartReg);
}
+ /**
+ * Implemented from SynchronousBundleListener.
+ */
+ @Override
+ public void bundleChanged(BundleEvent event) {
+ // If the system bundle (id 0) is stopping, do an orderly shutdown of all blueprint containers. On
+ // shutdown the system bundle is stopped first.
+ if(event.getBundle().getBundleId() == SYSTEM_BUNDLE_ID && event.getType() == BundleEvent.STOPPING) {
+ shutdownAllContainers();
+ }
+ }
+
/**
* Implemented from BundleActivator.
*/
*/
@Override
public void modifiedBundle(Bundle bundle, BundleEvent event, Bundle object) {
+ if(shuttingDown) {
+ return;
+ }
+
if(bundle.getState() == Bundle.ACTIVE) {
List<Object> paths = findBlueprintPaths(bundle);
return Collections.list((Enumeration)e);
}
}
+
+ private void shutdownAllContainers() {
+ shuttingDown = true;
+
+ restartService.close();
+
+ // Close all CSS modules first.
+ ConfigSystemService configSystem = getOSGiService(ConfigSystemService.class);
+ if(configSystem != null) {
+ configSystem.closeAllConfigModules();
+ }
+
+ LOG.info("Shutting down all blueprint containers...");
+
+ Collection<Bundle> containerBundles = new HashSet<>(Arrays.asList(bundleContext.getBundles()));
+ while(!containerBundles.isEmpty()) {
+ // For each iteration of getBundlesToDestroy, as containers are destroyed, other containers become
+ // eligible to be destroyed. We loop until we've destroyed them all.
+ for(Bundle bundle : getBundlesToDestroy(containerBundles)) {
+ containerBundles.remove(bundle);
+ BlueprintContainer container = blueprintExtenderService.getContainer(bundle);
+ if(container != null) {
+ blueprintExtenderService.destroyContainer(bundle, container);
+ }
+ }
+ }
+
+ LOG.info("Shutdown of blueprint containers complete");
+ }
+
+ private List<Bundle> getBundlesToDestroy(Collection<Bundle> containerBundles) {
+ List<Bundle> bundlesToDestroy = new ArrayList<Bundle>();
+
+ // Find all container bundles that either have no registered services or whose services are no
+ // longer in use.
+ for(Bundle bundle : containerBundles) {
+ ServiceReference<?>[] references = bundle.getRegisteredServices();
+ int usage = 0;
+ if(references != null) {
+ for(ServiceReference<?> reference : references) {
+ usage += getServiceUsage(reference);
+ }
+ }
+
+ LOG.debug("Usage for bundle {} is {}", bundle, usage);
+ if(usage == 0) {
+ bundlesToDestroy.add(bundle);
+ }
+ }
+
+ if(!bundlesToDestroy.isEmpty()) {
+ Collections.sort(bundlesToDestroy, new Comparator<Bundle>() {
+ @Override
+ public int compare(Bundle b1, Bundle b2) {
+ return (int) (b2.getLastModified() - b1.getLastModified());
+ }
+ });
+
+ LOG.debug("Selected bundles {} for destroy (no services in use)", bundlesToDestroy);
+ } else {
+ // There's either no more container bundles or they all have services being used. For
+ // the latter it means there's either circular service usage or a service is being used
+ // by a non-container bundle. But we need to make progress so we pick the bundle with a
+ // used service with the highest service ID. Each service is assigned a monotonically
+ // increasing ID as they are registered. By picking the bundle with the highest service
+ // ID, we're picking the bundle that was (likely) started after all the others and thus
+ // is likely the safest to destroy at this point.
+
+ ServiceReference<?> ref = null;
+ for(Bundle bundle : containerBundles) {
+ ServiceReference<?>[] references = bundle.getRegisteredServices();
+ if(references == null) {
+ continue;
+ }
+
+ for(ServiceReference<?> reference : references) {
+ // We did check the service usage above but it's possible the usage has changed since
+ // then,
+ if(getServiceUsage(reference) == 0) {
+ continue;
+ }
+
+ // Choose 'reference' if it has a lower service ranking or, if the rankings are equal
+ // which is usually the case, if it has a higher service ID. For the latter the < 0
+ // check looks backwards but that's how ServiceReference#compareTo is documented to work.
+ if(ref == null || reference.compareTo(ref) < 0) {
+ LOG.debug("Currently selecting bundle {} for destroy (with reference {})", bundle, reference);
+ ref = reference;
+ }
+ }
+ }
+
+ if(ref != null) {
+ bundlesToDestroy.add(ref.getBundle());
+ }
+
+ LOG.debug("Selected bundle {} for destroy (lowest ranking service or highest service ID)",
+ bundlesToDestroy);
+ }
+
+ return bundlesToDestroy;
+ }
+
+ private static int getServiceUsage(ServiceReference<?> ref) {
+ Bundle[] usingBundles = ref.getUsingBundles();
+ return usingBundles != null ? usingBundles.length : 0;
+ }
+
+ private <T> T getOSGiService(Class<T> serviceInterface) {
+ try {
+ ServiceReference<T> serviceReference = bundleContext.getServiceReference(serviceInterface);
+ if(serviceReference == null) {
+ LOG.warn("{} service reference not found", serviceInterface.getSimpleName());
+ return null;
+ }
+
+ T service = bundleContext.getService(serviceReference);
+ if(service == null) {
+ // This could happen on shutdown if the service was already unregistered so we log as debug.
+ LOG.debug("{} service instance was not found", serviceInterface.getSimpleName());
+ }
+
+ return service;
+ } catch(IllegalStateException e) {
+ // This is thrown if the BundleContext is no longer valid which is possible on shutdown so we
+ // log as debug.
+ LOG.debug("Error obtaining OSGi service {}", serviceInterface.getSimpleName(), e);
+ }
+
+ return null;
+ }
}