From: Tom Pantelis Date: Sat, 19 Mar 2016 20:51:29 +0000 (-0400) Subject: Add restart-dependents-on-update blueprint extension X-Git-Tag: release/boron~241 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=controller.git;a=commitdiff_plain;h=36de1fd1c214d98ae97e6d7725f323dd01e34977 Add restart-dependents-on-update blueprint extension Added an attribute extension to the element that adds a bean processor that scans for any "property-placeholder" elements and reacts to changes to the corresponding cfg file by restarting this blueprint container and any dependent containers that consume OSGi services provided by this container in an atomic and orderly manner. This mimics the module restart functionality provided by the CSS. A new OSGi service, BlueprintContainerRestartService, was added that performs the orderly restart. This service is created and registered by the BlueprintBundleTracker. Change-Id: I5f463303c3a8aba35b3ed914268bdc67ac795a5a Signed-off-by: Tom Pantelis --- 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..5727c13ba1 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 @@ -12,7 +12,10 @@ import java.util.Dictionary; import java.util.Enumeration; 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.osgi.framework.Bundle; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; @@ -45,7 +48,9 @@ public class BlueprintBundleTracker implements BundleActivator, BundleTrackerCus private ServiceTracker serviceTracker; private BundleTracker bundleTracker; private volatile BlueprintExtenderService blueprintExtenderService; + private volatile ServiceRegistration blueprintContainerRestartReg; private ServiceRegistration eventHandlerReg; + private ServiceRegistration namespaceReg; /** * Implemented from BundleActivator. @@ -54,11 +59,9 @@ public class BlueprintBundleTracker implements BundleActivator, BundleTrackerCus public void start(BundleContext context) { LOG.info("Starting {}", getClass().getSimpleName()); - // Register EventHandler for blueprint events + registerBlueprintEventHandler(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); + registerNamespaceHandler(context); bundleTracker = new BundleTracker<>(context, Bundle.ACTIVE, this); @@ -72,6 +75,10 @@ public class BlueprintBundleTracker implements BundleActivator, BundleTrackerCus LOG.debug("Got BlueprintExtenderService"); + blueprintContainerRestartReg = context.registerService( + BlueprintContainerRestartService.class.getName(), + new BlueprintContainerRestartServiceImpl(blueprintExtenderService), new Hashtable<>()); + return blueprintExtenderService; } @@ -88,6 +95,19 @@ 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, EventConstants.TOPIC_CREATED); + eventHandlerReg = context.registerService(EventHandler.class.getName(), this, props); + } + /** * Implemented from BundleActivator. */ @@ -95,7 +115,10 @@ 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); } /** diff --git a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/BlueprintContainerRestartService.java b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/BlueprintContainerRestartService.java new file mode 100644 index 0000000000..522415b6fb --- /dev/null +++ b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/BlueprintContainerRestartService.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2016 Brocade Communications Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.blueprint; + +import org.osgi.framework.Bundle; + +/** + * Interface that restarts blueprint containers. + * + * @author Thomas Pantelis + */ +public interface BlueprintContainerRestartService { + + /** + * Restarts the blueprint container for the given bundle and all its dependent containers in an atomic + * and orderly manner. The dependent containers are identified by walking the OSGi service dependency + * hierarchies for the service(s) provided by the given bundle. + * + * @param bundle the bundle to restart + */ + void restartContainerAndDependents(Bundle bundle); +} \ No newline at end of file diff --git a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/BlueprintContainerRestartServiceImpl.java b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/BlueprintContainerRestartServiceImpl.java new file mode 100644 index 0000000000..3e04f21349 --- /dev/null +++ b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/BlueprintContainerRestartServiceImpl.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2016 Brocade Communications Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.blueprint; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import org.apache.aries.blueprint.services.BlueprintExtenderService; +import org.osgi.framework.Bundle; +import org.osgi.framework.ServiceReference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Implementation of the BlueprintContainerRestartService. + * + * @author Thomas Pantelis + */ +class BlueprintContainerRestartServiceImpl implements AutoCloseable, BlueprintContainerRestartService { + private static final Logger LOG = LoggerFactory.getLogger(BlueprintContainerRestartServiceImpl.class); + + private final ExecutorService restartExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder(). + setDaemon(true).setNameFormat("BlueprintContainerRestartService").build()); + private final BlueprintExtenderService blueprintExtenderService; + + BlueprintContainerRestartServiceImpl(BlueprintExtenderService blueprintExtenderService) { + this.blueprintExtenderService = blueprintExtenderService; + } + + @Override + public void restartContainerAndDependents(final Bundle bundle) { + LOG.debug("restartContainerAndDependents for bundle {}", bundle); + + restartExecutor.execute(new Runnable() { + @Override + public void run() { + restartContainerAndDependentsInternal(bundle); + + } + }); + } + + private void restartContainerAndDependentsInternal(Bundle forBundle) { + Set containerBundlesSet = new LinkedHashSet<>(); + findDependentContainersRecursively(forBundle, containerBundlesSet); + + List containerBundles = new ArrayList<>(containerBundlesSet); + + LOG.info("Restarting blueprint containers for bundle {} and its dependent bundles {}", forBundle, + containerBundles.subList(1, containerBundles.size())); + + // Destroy the containers in reverse order with 'forBundle' last, ie bottom-up in the service tree. + for(int i = containerBundles.size() - 1; i >= 0; i--) { + Bundle bundle = containerBundles.get(i); + blueprintExtenderService.destroyContainer(bundle, blueprintExtenderService.getContainer(bundle)); + } + + // Restart the containers top-down starting with 'forBundle'. + for(Bundle bundle: containerBundles) { + List paths = BlueprintBundleTracker.findBlueprintPaths(bundle); + + LOG.info("Restarting blueprint container for bundle {} with paths {}", bundle, paths); + + blueprintExtenderService.createContainer(bundle, paths); + } + } + + /** + * Recursively finds the services registered by the given bundle and the bundles using those services. + * User bundles that have an associated blueprint container are added to containerBundles. + * + * @param bundle the bundle to traverse + * @param containerBundles the current set of bundles containing blueprint containers + */ + private void findDependentContainersRecursively(Bundle bundle, Set containerBundles) { + if(containerBundles.contains(bundle)) { + return; + } + + containerBundles.add(bundle); + ServiceReference[] references = bundle.getRegisteredServices(); + if (references != null) { + for (ServiceReference reference : references) { + Bundle[] usingBundles = reference.getUsingBundles(); + if(usingBundles != null) { + for(Bundle usingBundle: usingBundles) { + if(blueprintExtenderService.getContainer(usingBundle) != null) { + findDependentContainersRecursively(usingBundle, containerBundles); + } + } + } + } + } + } + + @Override + public void close() { + restartExecutor.shutdownNow(); + } +} diff --git a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/ComponentProcessor.java b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/ComponentProcessor.java new file mode 100644 index 0000000000..ed87e7093b --- /dev/null +++ b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/ComponentProcessor.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2016 Brocade Communications Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.blueprint.ext; + +import java.util.ArrayList; +import java.util.Dictionary; +import java.util.Hashtable; +import java.util.List; +import org.apache.aries.blueprint.ComponentDefinitionRegistry; +import org.apache.aries.blueprint.ComponentDefinitionRegistryProcessor; +import org.apache.aries.blueprint.ext.AbstractPropertyPlaceholder; +import org.apache.aries.blueprint.mutable.MutableBeanMetadata; +import org.apache.aries.util.AriesFrameworkUtil; +import org.opendaylight.controller.blueprint.BlueprintContainerRestartService; +import org.osgi.framework.Bundle; +import org.osgi.framework.Constants; +import org.osgi.framework.ServiceRegistration; +import org.osgi.service.blueprint.reflect.BeanProperty; +import org.osgi.service.blueprint.reflect.ComponentMetadata; +import org.osgi.service.blueprint.reflect.ValueMetadata; +import org.osgi.service.cm.ManagedService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The singleton component processor that is invoked by the blueprint container to perform operations on + * various component definitions prior to component creation. + * + * @author Thomas Pantelis + */ +public class ComponentProcessor implements ComponentDefinitionRegistryProcessor { + private static final Logger LOG = LoggerFactory.getLogger(ComponentProcessor.class); + private static final String CM_PERSISTENT_ID_PROPERTY = "persistentId"; + + private final List> managedServiceRegs = new ArrayList<>(); + private Bundle bundle; + private BlueprintContainerRestartService blueprintContainerRestartService; + private boolean restartDependentsOnUpdates; + + public void setBundle(Bundle bundle) { + this.bundle = bundle; + } + + public void setBlueprintContainerRestartService(BlueprintContainerRestartService restartService) { + this.blueprintContainerRestartService = restartService; + } + + public void setRestartDependentsOnUpdates(boolean restartDependentsOnUpdates) { + this.restartDependentsOnUpdates = restartDependentsOnUpdates; + } + + public void destroy() { + for(ServiceRegistration reg: managedServiceRegs) { + AriesFrameworkUtil.safeUnregisterService(reg); + } + } + + @Override + public void process(ComponentDefinitionRegistry registry) { + LOG.debug("{}: In process", bundle.getSymbolicName()); + + for(String name : registry.getComponentDefinitionNames()) { + ComponentMetadata component = registry.getComponentDefinition(name); + if(component instanceof MutableBeanMetadata) { + processMutableBeanMetadata((MutableBeanMetadata)component); + } + } + } + + private void processMutableBeanMetadata(MutableBeanMetadata bean) { + if(restartDependentsOnUpdates && bean.getRuntimeClass() != null && + AbstractPropertyPlaceholder.class.isAssignableFrom(bean.getRuntimeClass())) { + LOG.debug("{}: Found PropertyPlaceholder bean: {}, runtime {}", bundle.getSymbolicName(), bean.getId(), + bean.getRuntimeClass()); + + for(BeanProperty prop: bean.getProperties()) { + if(CM_PERSISTENT_ID_PROPERTY.equals(prop.getName())) { + if(prop.getValue() instanceof ValueMetadata) { + ValueMetadata persistentId = (ValueMetadata)prop.getValue(); + + LOG.debug("{}: Found {} property, value : {}", bundle.getSymbolicName(), + CM_PERSISTENT_ID_PROPERTY, persistentId.getStringValue()); + + registerManagedService(persistentId.getStringValue()); + } else { + LOG.debug("{}: {} property metadata {} is not instanceof ValueMetadata", + bundle.getSymbolicName(), CM_PERSISTENT_ID_PROPERTY, prop.getValue()); + } + + break; + } + } + } + } + + private void registerManagedService(final String persistentId) { + // Register a ManagedService so we git updates from the ConfigAdmin when the cfg file corresponding + // to the persistentId changes. + ManagedService managedService = new ManagedService() { + private volatile boolean initialUpdate = true; + + @Override + public void updated(Dictionary properties) { + LOG.debug("{}: ManagedService updated for persistentId {}, properties: {}, initialUpdate: {}", + bundle.getSymbolicName(), persistentId, properties, initialUpdate); + + // The first update occurs when the service is registered so ignore it as we want subsequent + // updates when it changes. The ConfigAdmin will send an update even if the cfg file doesn't + // yet exist. + if(initialUpdate) { + initialUpdate = false; + } else { + blueprintContainerRestartService.restartContainerAndDependents(bundle); + } + } + }; + + Dictionary props = new Hashtable<>(); + props.put(Constants.SERVICE_PID, persistentId); + props.put(Constants.BUNDLE_SYMBOLICNAME, bundle.getSymbolicName()); + props.put(Constants.BUNDLE_VERSION, bundle.getHeaders().get(Constants.BUNDLE_VERSION)); + managedServiceRegs.add(bundle.getBundleContext().registerService(ManagedService.class.getName(), + managedService, props)); + } +} diff --git a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/OpendaylightNamespaceHandler.java b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/OpendaylightNamespaceHandler.java new file mode 100644 index 0000000000..89bea93182 --- /dev/null +++ b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/OpendaylightNamespaceHandler.java @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2016 Brocade Communications Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.blueprint.ext; + +import java.net.URL; +import java.util.Collections; +import java.util.Set; +import org.apache.aries.blueprint.ComponentDefinitionRegistry; +import org.apache.aries.blueprint.NamespaceHandler; +import org.apache.aries.blueprint.ParserContext; +import org.apache.aries.blueprint.mutable.MutableBeanMetadata; +import org.apache.aries.blueprint.mutable.MutableRefMetadata; +import org.apache.aries.blueprint.mutable.MutableReferenceMetadata; +import org.apache.aries.blueprint.mutable.MutableValueMetadata; +import org.opendaylight.controller.blueprint.BlueprintContainerRestartService; +import org.osgi.service.blueprint.container.ComponentDefinitionException; +import org.osgi.service.blueprint.reflect.BeanMetadata; +import org.osgi.service.blueprint.reflect.ComponentMetadata; +import org.osgi.service.blueprint.reflect.Metadata; +import org.osgi.service.blueprint.reflect.RefMetadata; +import org.osgi.service.blueprint.reflect.ReferenceMetadata; +import org.osgi.service.blueprint.reflect.ValueMetadata; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Attr; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +/** + * The NamespaceHandler for Opendaylight blueprint extensions. + * + * @author Thomas Pantelis + */ +public class OpendaylightNamespaceHandler implements NamespaceHandler { + public static final String NAMESPACE_1_0_0 = "http://opendaylight.org/xmlns/blueprint/v1.0.0"; + + private static final Logger LOG = LoggerFactory.getLogger(OpendaylightNamespaceHandler.class); + private static final String COMPONENT_PROCESSOR_NAME = ComponentProcessor.class.getName(); + private static final String RESTART_DEPENDENTS_ON_UPDATES = "restart-dependents-on-updates"; + + @SuppressWarnings("rawtypes") + @Override + public Set getManagedClasses() { + return Collections.emptySet(); + } + + @Override + public URL getSchemaLocation(String namespace) { + if(NAMESPACE_1_0_0.equals(namespace)) { + URL url = getClass().getResource("/opendaylight-blueprint-ext-1.0.0.xsd"); + LOG.debug("getSchemaLocation for {} returning URL {}", namespace, url); + return url; + } + + return null; + } + + @Override + public Metadata parse(Element element, ParserContext context) { + LOG.debug("In parse for {}", element); + throw new ComponentDefinitionException("Unsupported standalone element: " + element.getNodeName()); + } + + @Override + public ComponentMetadata decorate(Node node, ComponentMetadata component, ParserContext context) { + if(node instanceof Attr) { + if (nodeNameEquals(node, RESTART_DEPENDENTS_ON_UPDATES)) { + return decorateRestartDependentsOnUpdates((Attr)node, component, context); + } + + throw new ComponentDefinitionException("Unsupported attribute: " + node.getNodeName()); + } else { + throw new ComponentDefinitionException("Unsupported node type: " + node); + } + } + + private static ComponentMetadata decorateRestartDependentsOnUpdates(Attr attr, ComponentMetadata component, + ParserContext context) { + if(component != null) { + throw new ComponentDefinitionException("Attribute " + attr.getNodeName() + + " can only be used on the root element"); + } + + LOG.debug("decorateRestartDependentsOnUpdates: {}", attr.getValue()); + + if(!Boolean.TRUE.equals(Boolean.valueOf(attr.getValue()))) { + return component; + } + + MutableBeanMetadata metadata = registerComponentProcessor(context); + metadata.addProperty("restartDependentsOnUpdates", createValue(context, "true")); + + return component; + } + + private static MutableBeanMetadata registerComponentProcessor(ParserContext context) { + ComponentDefinitionRegistry registry = context.getComponentDefinitionRegistry(); + MutableBeanMetadata metadata = (MutableBeanMetadata) registry.getComponentDefinition(COMPONENT_PROCESSOR_NAME); + if(metadata == null) { + metadata = context.createMetadata(MutableBeanMetadata.class); + metadata.setProcessor(true); + metadata.setId(COMPONENT_PROCESSOR_NAME); + metadata.setActivation(BeanMetadata.ACTIVATION_EAGER); + metadata.setScope(BeanMetadata.SCOPE_SINGLETON); + metadata.setRuntimeClass(ComponentProcessor.class); + metadata.setDestroyMethod("destroy"); + metadata.addProperty("bundle", createRef(context, "blueprintBundle")); + metadata.addProperty("blueprintContainerRestartService", createServiceRef(context, + BlueprintContainerRestartService.class, null)); + + LOG.debug("Registering ComponentProcessor bean: {}", metadata); + + registry.registerComponentDefinition(metadata); + } + + return metadata; + } + + private static ValueMetadata createValue(ParserContext context, String value) { + MutableValueMetadata m = context.createMetadata(MutableValueMetadata.class); + m.setStringValue(value); + return m; + } + + private static MutableReferenceMetadata createServiceRef(ParserContext context, Class cls, String filter) { + MutableReferenceMetadata m = context.createMetadata(MutableReferenceMetadata.class); + m.setRuntimeInterface(cls); + m.setInterface(cls.getName()); + m.setActivation(ReferenceMetadata.ACTIVATION_EAGER); + m.setAvailability(ReferenceMetadata.AVAILABILITY_MANDATORY); + + if(filter != null) { + m.setFilter(filter); + } + + return m; + } + + private static RefMetadata createRef(ParserContext context, String value) { + MutableRefMetadata metadata = context.createMetadata(MutableRefMetadata.class); + metadata.setComponentId(value); + return metadata; + } + + private static boolean nodeNameEquals(Node node, String name) { + return name.equals(node.getNodeName()) || name.equals(node.getLocalName()); + } +} diff --git a/opendaylight/blueprint/src/main/resources/opendaylight-blueprint-ext-1.0.0.xsd b/opendaylight/blueprint/src/main/resources/opendaylight-blueprint-ext-1.0.0.xsd new file mode 100644 index 0000000000..f83434f563 --- /dev/null +++ b/opendaylight/blueprint/src/main/resources/opendaylight-blueprint-ext-1.0.0.xsd @@ -0,0 +1,10 @@ + + + + + + + \ No newline at end of file