X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;ds=sidebyside;f=opendaylight%2Fblueprint%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fcontroller%2Fblueprint%2FBlueprintBundleTracker.java;h=6c267ac5fe62494793d29270420eccfe1685912d;hb=67ff0fc78b2933b8b4f5a8544c7639499824e622;hp=98e31e826b464741f5f0fe36a7f6a2c423245af3;hpb=0aabbfe6127ab85eedf576a82f95805ddfc051f0;p=controller.git diff --git a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/BlueprintBundleTracker.java b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/BlueprintBundleTracker.java index 98e31e826b..6c267ac5fe 100644 --- a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/BlueprintBundleTracker.java +++ b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/BlueprintBundleTracker.java @@ -7,18 +7,28 @@ */ package org.opendaylight.controller.blueprint; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.Collections; 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; @@ -37,15 +47,22 @@ import org.slf4j.LoggerFactory; * * @author Thomas Pantelis */ -public class BlueprintBundleTracker implements BundleActivator, BundleTrackerCustomizer, EventHandler { +public class BlueprintBundleTracker implements BundleActivator, BundleTrackerCustomizer, 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 serviceTracker; private BundleTracker 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; /** * Implemented from BundleActivator. @@ -54,11 +71,11 @@ public class BlueprintBundleTracker implements BundleActivator, BundleTrackerCus public void start(BundleContext context) { LOG.info("Starting {}", getClass().getSimpleName()); - // Register EventHandler for blueprint events + bundleContext = context; - Dictionary props = new Hashtable<>(); - props.put(org.osgi.service.event.EventConstants.EVENT_TOPIC, EventConstants.TOPIC_CREATED); - eventHandlerReg = context.registerService(EventHandler.class.getName(), this, props); + registerBlueprintEventHandler(context); + + registerNamespaceHandler(context); bundleTracker = new BundleTracker<>(context, Bundle.ACTIVE, this); @@ -70,8 +87,14 @@ public class BlueprintBundleTracker implements BundleActivator, BundleTrackerCus 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(), restartService, new Hashtable<>()); + return blueprintExtenderService; } @@ -88,6 +111,20 @@ public class BlueprintBundleTracker implements BundleActivator, BundleTrackerCus serviceTracker.open(); } + private void registerNamespaceHandler(BundleContext context) { + Dictionary props = new Hashtable<>(); + props.put("osgi.service.blueprint.namespace", OpendaylightNamespaceHandler.NAMESPACE_1_0_0); + namespaceReg = context.registerService(NamespaceHandler.class.getName(), + new OpendaylightNamespaceHandler(), props); + } + + private void registerBlueprintEventHandler(BundleContext context) { + Dictionary props = new Hashtable<>(); + props.put(org.osgi.service.event.EventConstants.EVENT_TOPIC, + new String[]{EventConstants.TOPIC_CREATED, EventConstants.TOPIC_FAILURE}); + eventHandlerReg = context.registerService(EventHandler.class.getName(), this, props); + } + /** * Implemented from BundleActivator. */ @@ -95,7 +132,22 @@ public class BlueprintBundleTracker implements BundleActivator, BundleTrackerCus public void stop(BundleContext context) { bundleTracker.close(); serviceTracker.close(); - eventHandlerReg.unregister(); + + AriesFrameworkUtil.safeUnregisterService(eventHandlerReg); + AriesFrameworkUtil.safeUnregisterService(namespaceReg); + 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(); + } } /** @@ -112,10 +164,14 @@ public class BlueprintBundleTracker implements BundleActivator, BundleTrackerCus */ @Override public void modifiedBundle(Bundle bundle, BundleEvent event, Bundle object) { - if(bundle.getState() == Bundle.ACTIVE) { + if (shuttingDown) { + return; + } + + if (bundle.getState() == Bundle.ACTIVE) { List paths = findBlueprintPaths(bundle); - if(!paths.isEmpty()) { + if (!paths.isEmpty()) { LOG.info("Creating blueprint container for bundle {} with paths {}", bundle, paths); blueprintExtenderService.createContainer(bundle, paths); @@ -134,23 +190,165 @@ public class BlueprintBundleTracker implements BundleActivator, BundleTrackerCus /** * Implemented from EventHandler to listen for blueprint events. * - * @param event + * @param event the event to handle */ @Override public void handleEvent(Event event) { - if(EventConstants.TOPIC_CREATED.equals(event.getTopic())) { + if (EventConstants.TOPIC_CREATED.equals(event.getTopic())) { LOG.info("Blueprint container for bundle {} was successfully created", event.getProperty(EventConstants.BUNDLE)); + } else if (EventConstants.TOPIC_FAILURE.equals(event.getTopic())) { + // If the container timed out waiting for dependencies, we'll destroy it and start it again. This + // is indicated via a non-null DEPENDENCIES property containing the missing dependencies. The + // default timeout is 5 min and ideally we would set this to infinite but the timeout can only + // be set at the bundle level in the manifest - there's no way to set it globally. + if (event.getProperty(EventConstants.DEPENDENCIES) != null) { + Bundle bundle = (Bundle) event.getProperty(EventConstants.BUNDLE); + + List paths = findBlueprintPaths(bundle); + if (!paths.isEmpty()) { + LOG.warn("Blueprint container for bundle {} timed out waiting for dependencies - restarting it", + event.getProperty(EventConstants.BUNDLE)); + + restartService.restartContainer(bundle, paths); + } + } } } @SuppressWarnings({ "rawtypes", "unchecked" }) static List findBlueprintPaths(Bundle bundle) { - Enumeration e = bundle.findEntries(BLUEPRINT_FILE_PATH, BLUEPRINT_FLE_PATTERN, false); - if(e == null) { + Enumeration rntries = bundle.findEntries(BLUEPRINT_FILE_PATH, BLUEPRINT_FLE_PATTERN, false); + if (rntries == null) { return Collections.emptyList(); } else { - return Collections.list((Enumeration)e); + return Collections.list((Enumeration)rntries); + } + } + + 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 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 getBundlesToDestroy(Collection containerBundles) { + List bundlesToDestroy = new ArrayList<>(); + + // 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, (b1, b2) -> (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 getOSGiService(Class serviceInterface) { + try { + ServiceReference 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; } }