From b9040c52d74cf4c16aeb16774f2c1056589fb203 Mon Sep 17 00:00:00 2001 From: Tom Pantelis Date: Mon, 13 Jun 2016 18:37:58 -0400 Subject: [PATCH] Add "specific-reference-list" blueprint extension Added a blueprint extension, "specific-reference-list", that obtains a specific list of service instances from the OSGi registry for a given interface. The specific list is learned by first extracting the list of expected service types by inspecting RESOLVED bundles for a resource file under META-INF/services with the same name as the given interface. The type(s) listed in the resource file must match the "type" property of the advertised service(s). In this manner, an app bundle announces the service type(s) that it will advertise so that the extension knows which services to expect up front. Once all the expected services are obtained, the container is notified that all dependencies are satisfied. This new extension will initially be used by the bgpcep project. Change-Id: I3bc6a72134b33c744fbb48fd645dd3a0ca54673d Signed-off-by: Tom Pantelis --- ...ractDependentComponentFactoryMetadata.java | 54 ++++- .../ext/DataStoreAppConfigMetadata.java | 35 --- .../ext/OpendaylightNamespaceHandler.java | 14 +- .../blueprint/ext/RpcServiceMetadata.java | 2 + .../ext/SpecificReferenceListMetadata.java | 220 ++++++++++++++++++ .../opendaylight-blueprint-ext-1.0.0.xsd | 6 + 6 files changed, 292 insertions(+), 39 deletions(-) create mode 100644 opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/SpecificReferenceListMetadata.java diff --git a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/AbstractDependentComponentFactoryMetadata.java b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/AbstractDependentComponentFactoryMetadata.java index bdcc1ee86d..a84b8b68ea 100644 --- a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/AbstractDependentComponentFactoryMetadata.java +++ b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/AbstractDependentComponentFactoryMetadata.java @@ -13,11 +13,14 @@ import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; +import javax.annotation.Nullable; import org.apache.aries.blueprint.di.AbstractRecipe; import org.apache.aries.blueprint.di.ExecutionContext; import org.apache.aries.blueprint.di.Recipe; import org.apache.aries.blueprint.ext.DependentComponentFactoryMetadata; import org.apache.aries.blueprint.services.ExtendedBlueprintContainer; +import org.opendaylight.controller.blueprint.BlueprintContainerRestartService; +import org.osgi.framework.ServiceReference; import org.osgi.service.blueprint.container.ComponentDefinitionException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,10 +33,12 @@ abstract class AbstractDependentComponentFactoryMetadata implements DependentCom private final String id; private final AtomicBoolean started = new AtomicBoolean(); private final AtomicBoolean satisfied = new AtomicBoolean(); + private final AtomicBoolean restarting = new AtomicBoolean(); private final List serviceRecipes = new ArrayList<>(); private volatile ExtendedBlueprintContainer container; private volatile SatisfactionCallback satisfactionCallback; private volatile String failureMessage; + private volatile Throwable failureCause; private volatile String dependendencyDesc; protected AbstractDependentComponentFactoryMetadata(String id) { @@ -68,7 +73,12 @@ abstract class AbstractDependentComponentFactoryMetadata implements DependentCom protected abstract void startTracking(); protected void setFailureMessage(String failureMessage) { + setFailure(failureMessage, null); + } + + protected void setFailure(String failureMessage, Throwable failureCause) { this.failureMessage = failureMessage; + this.failureCause = failureCause; } protected void setDependendencyDesc(String dependendencyDesc) { @@ -80,8 +90,9 @@ abstract class AbstractDependentComponentFactoryMetadata implements DependentCom } protected void setSatisfied() { - satisfied.set(true); - satisfactionCallback.notifyChanged(); + if(satisfied.compareAndSet(false, true)) { + satisfactionCallback.notifyChanged(); + } } protected void retrieveService(String name, Class interfaceClass, Consumer onServiceRetrieved) { @@ -107,7 +118,7 @@ abstract class AbstractDependentComponentFactoryMetadata implements DependentCom protected void onCreate() throws ComponentDefinitionException { if(failureMessage != null) { - throw new ComponentDefinitionException(failureMessage); + throw new ComponentDefinitionException(failureMessage, failureCause); } // The following code is a bit odd so requires some explanation. A little background... If a bean @@ -179,4 +190,41 @@ abstract class AbstractDependentComponentFactoryMetadata implements DependentCom serviceRecipes.clear(); } + + protected void restartContainer() { + if(restarting.compareAndSet(false, true)) { + BlueprintContainerRestartService restartService = getOSGiService(BlueprintContainerRestartService.class); + if(restartService != null) { + log.debug("{}: Restarting container", logName()); + restartService.restartContainerAndDependents(container().getBundleContext().getBundle()); + } + } + } + + @SuppressWarnings("unchecked") + @Nullable + protected T getOSGiService(Class serviceInterface) { + try { + ServiceReference serviceReference = + container().getBundleContext().getServiceReference(serviceInterface); + if(serviceReference == null) { + log.warn("{}: {} reference not found", logName(), serviceInterface.getSimpleName()); + return null; + } + + T service = (T)container().getService(serviceReference); + if(service == null) { + // This could happen on shutdown if the service was already unregistered so we log as debug. + log.debug("{}: {} was not found", logName(), 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 {}", logName(), serviceInterface.getSimpleName(), e); + } + + return null; + } } diff --git a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/DataStoreAppConfigMetadata.java b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/DataStoreAppConfigMetadata.java index c1f28428c9..f8a6127f0a 100644 --- a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/DataStoreAppConfigMetadata.java +++ b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/DataStoreAppConfigMetadata.java @@ -21,7 +21,6 @@ import java.util.concurrent.atomic.AtomicBoolean; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.apache.aries.blueprint.services.ExtendedBlueprintContainer; -import org.opendaylight.controller.blueprint.BlueprintContainerRestartService; import org.opendaylight.controller.md.sal.binding.api.ClusteredDataTreeChangeListener; import org.opendaylight.controller.md.sal.binding.api.DataBroker; import org.opendaylight.controller.md.sal.binding.api.DataObjectModification; @@ -50,7 +49,6 @@ import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; import org.opendaylight.yangtools.yang.model.api.Module; import org.opendaylight.yangtools.yang.model.api.SchemaContext; -import org.osgi.framework.ServiceReference; import org.osgi.service.blueprint.container.ComponentDefinitionException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -336,39 +334,6 @@ public class DataStoreAppConfigMetadata extends AbstractDependentComponentFactor return dataNode; } - private void restartContainer() { - BlueprintContainerRestartService restartService = getOSGiService(BlueprintContainerRestartService.class); - if(restartService != null) { - restartService.restartContainerAndDependents(container().getBundleContext().getBundle()); - } - } - - @SuppressWarnings("unchecked") - @Nullable - private T getOSGiService(Class serviceInterface) { - try { - ServiceReference serviceReference = - container().getBundleContext().getServiceReference(serviceInterface); - if(serviceReference == null) { - LOG.warn("{}: {} reference not found", logName(), serviceInterface.getSimpleName()); - return null; - } - - T service = (T)container().getService(serviceReference); - if(service == null) { - // This could happen on shutdown if the service was already unregistered so we log as debug. - LOG.debug("{}: {} was not found", logName(), 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 {}", logName(), serviceInterface.getSimpleName(), e); - } - - return null; - } @Override public void destroy(Object instance) { 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 index 7a1c86ba19..ab9e216d03 100644 --- 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 @@ -53,17 +53,18 @@ public class OpendaylightNamespaceHandler implements NamespaceHandler { static final String ROUTED_RPC_REG_CONVERTER_NAME = "org.opendaylight.blueprint.RoutedRpcRegConverter"; static final String RPC_REGISTRY_NAME = "org.opendaylight.blueprint.RpcRegistry"; static final String NOTIFICATION_SERVICE_NAME = "org.opendaylight.blueprint.NotificationService"; + static final String TYPE_ATTR = "type"; 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"; private static final String USE_DEFAULT_FOR_REFERENCE_TYPES = "use-default-for-reference-types"; private static final String CLUSTERED_APP_CONFIG = "clustered-app-config"; - private static final String TYPE_ATTR = "type"; private static final String INTERFACE = "interface"; private static final String REF_ATTR = "ref"; private static final String ID_ATTR = "id"; private static final String RPC_SERVICE = "rpc-service"; + private static final String SPECIFIC_SERVICE_REF_LIST = "specific-reference-list"; @SuppressWarnings("rawtypes") @Override @@ -96,6 +97,8 @@ public class OpendaylightNamespaceHandler implements NamespaceHandler { return parseNotificationListener(element, context); } else if (nodeNameEquals(element, CLUSTERED_APP_CONFIG)) { return parseClusteredAppConfig(element, context); + } else if (nodeNameEquals(element, SPECIFIC_SERVICE_REF_LIST)) { + return parseSpecificReferenceList(element, context); } throw new ComponentDefinitionException("Unsupported standalone element: " + element.getNodeName()); @@ -346,6 +349,15 @@ public class OpendaylightNamespaceHandler implements NamespaceHandler { DataStoreAppConfigMetadata.LIST_KEY_VALUE), defaultAppConfigElement); } + private Metadata parseSpecificReferenceList(Element element, ParserContext context) { + ComponentFactoryMetadata metadata = new SpecificReferenceListMetadata(getId(context, element), + element.getAttribute(INTERFACE)); + + LOG.debug("parseSpecificReferenceList returning {}", metadata); + + return metadata; + } + private Element parseXML(String name, String xml) { DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance(); builderFactory.setNamespaceAware(true); diff --git a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/RpcServiceMetadata.java b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/RpcServiceMetadata.java index e2260a6d74..82b1b6be15 100644 --- a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/RpcServiceMetadata.java +++ b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/RpcServiceMetadata.java @@ -130,6 +130,8 @@ class RpcServiceMetadata extends AbstractDependentComponentFactoryMetadata { public Object create() throws ComponentDefinitionException { LOG.debug("{}: In create: interfaceName: {}", logName(), interfaceName); + super.onCreate(); + try { RpcService rpcService = rpcRegistry.getRpcService(rpcInterface); diff --git a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/SpecificReferenceListMetadata.java b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/SpecificReferenceListMetadata.java new file mode 100644 index 0000000000..1aef141129 --- /dev/null +++ b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/SpecificReferenceListMetadata.java @@ -0,0 +1,220 @@ +/* + * 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 com.google.common.base.Charsets; +import com.google.common.collect.ImmutableList; +import com.google.common.io.Resources; +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.ConcurrentSkipListSet; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleEvent; +import org.osgi.framework.ServiceReference; +import org.osgi.service.blueprint.container.ComponentDefinitionException; +import org.osgi.util.tracker.BundleTracker; +import org.osgi.util.tracker.BundleTrackerCustomizer; +import org.osgi.util.tracker.ServiceTracker; +import org.osgi.util.tracker.ServiceTrackerCustomizer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Factory metadata corresponding to the "specific-reference-list" element that obtains a specific list + * of service instances from the OSGi registry for a given interface. The specific list is learned by first + * extracting the list of expected service types by inspecting RESOLVED bundles for a resource file under + * META-INF/services with the same name as the given interface. The type(s) listed in the resource file + * must match the "type" property of the advertised service(s). In this manner, an app bundle announces the + * service type(s) that it will advertise so that this class knows which services to expect up front. Once + * all the expected services are obtained, the container is notified that all dependencies of this component + * factory are satisfied. + * + * @author Thomas Pantelis + */ +class SpecificReferenceListMetadata extends AbstractDependentComponentFactoryMetadata { + private static final Logger LOG = LoggerFactory.getLogger(SpecificReferenceListMetadata.class); + + private final String interfaceName; + private final String serviceResourcePath; + private final Collection expectedServiceTypes = new ConcurrentSkipListSet<>(); + private final Collection retrievedServiceTypes = new ConcurrentSkipListSet<>(); + private final Collection retrievedServices = Collections.synchronizedList(new ArrayList<>()); + private volatile BundleTracker bundleTracker; + private volatile ServiceTracker serviceTracker; + + SpecificReferenceListMetadata(String id, String interfaceName) { + super(id); + this.interfaceName = interfaceName; + serviceResourcePath = "META-INF/services/" + interfaceName; + } + + @Override + protected void startTracking() { + BundleTrackerCustomizer bundleListener = new BundleTrackerCustomizer() { + @Override + public Bundle addingBundle(Bundle bundle, BundleEvent event) { + bundleAdded(bundle); + return bundle; + } + + @Override + public void modifiedBundle(Bundle bundle, BundleEvent event, Bundle object) { + } + + @Override + public void removedBundle(Bundle bundle, BundleEvent event, Bundle object) { + } + }; + + bundleTracker = new BundleTracker<>(container().getBundleContext(), Bundle.RESOLVED | Bundle.STARTING | + Bundle.STOPPING | Bundle.ACTIVE, bundleListener); + + // This will get the list of all current RESOLVED+ bundles. + bundleTracker.open(); + + if(expectedServiceTypes.isEmpty()) { + setSatisfied(); + return; + } + + ServiceTrackerCustomizer serviceListener = new ServiceTrackerCustomizer() { + @Override + public Object addingService(ServiceReference reference) { + return serviceAdded(reference); + } + + @Override + public void modifiedService(ServiceReference reference, Object service) { + } + + @Override + public void removedService(ServiceReference reference, Object service) { + container().getBundleContext().ungetService(reference); + } + }; + + setDependendencyDesc(interfaceName + " services with types " + expectedServiceTypes); + + serviceTracker = new ServiceTracker<>(container().getBundleContext(), interfaceName, serviceListener); + serviceTracker.open(); + } + + private void bundleAdded(Bundle bundle) { + URL resource = bundle.getEntry(serviceResourcePath); + if(resource == null) { + return; + } + + LOG.debug("{}: Found {} resource in bundle {}", logName(), resource, bundle.getSymbolicName()); + + try { + for(String line : Resources.readLines(resource, Charsets.UTF_8)) { + int ci = line.indexOf('#'); + if(ci >= 0) { + line = line.substring(0, ci); + } + + line = line.trim(); + if(line.isEmpty()) { + continue; + } + + String serviceType = line; + LOG.debug("{}: Retrieved service type {}", logName(), serviceType); + expectedServiceTypes.add(serviceType); + } + } catch(IOException e) { + setFailure(String.format("%s: Error reading resource %s from bundle %s", logName(), resource, + bundle.getSymbolicName()), e); + } + } + + private Object serviceAdded(ServiceReference reference) { + Object service = container().getBundleContext().getService(reference); + Object serviceType = reference.getProperty(OpendaylightNamespaceHandler.TYPE_ATTR); + + LOG.debug("{}: Service type {} added from bundle {}", logName(), serviceType, + reference.getBundle().getSymbolicName()); + + if(serviceType == null) { + LOG.error("{}: Missing OSGi service property '{}' for service interface {} in bundle {}", logName(), + OpendaylightNamespaceHandler.TYPE_ATTR, interfaceName, reference.getBundle().getSymbolicName()); + return service; + } + + if(!expectedServiceTypes.contains(serviceType)) { + LOG.error("{}: OSGi service property '{}' for service interface {} in bundle {} was not found in the " + + "expected service types {} obtained via {} bundle resources. Is the bundle resource missing or the service type misspelled?", + logName(), OpendaylightNamespaceHandler.TYPE_ATTR, interfaceName, reference.getBundle().getSymbolicName(), + expectedServiceTypes, serviceResourcePath); + return service; + } + + // If already satisfied, meaning we got all initial services, then a new bundle must've been + // dynamically installed or a prior service's blueprint container was restarted, in which case we + // restart our container. + if(isSatisfied()) { + restartContainer(); + } else { + retrievedServiceTypes.add(serviceType.toString()); + retrievedServices.add(service); + + if(retrievedServiceTypes.equals(expectedServiceTypes)) { + LOG.debug("{}: Got all expected service types", logName()); + setSatisfied(); + } else { + Set remaining = new HashSet<>(expectedServiceTypes); + remaining.removeAll(retrievedServiceTypes); + setDependendencyDesc(interfaceName + " services with types " + remaining); + } + } + + return service; + } + + @Override + public Object create() throws ComponentDefinitionException { + LOG.debug("{}: In create: interfaceName: {}", logName(), interfaceName); + + super.onCreate(); + + LOG.debug("{}: create returning service list {}", logName(), retrievedServices); + + synchronized(retrievedServices) { + return ImmutableList.copyOf(retrievedServices); + } + } + + @Override + public void destroy(Object instance) { + super.destroy(instance); + + if(bundleTracker != null) { + bundleTracker.close(); + bundleTracker = null; + } + + if(serviceTracker != null) { + serviceTracker.close(); + serviceTracker = null; + } + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("SpecificReferenceListMetadata [interfaceName=").append(interfaceName) + .append(", serviceResourcePath=").append(serviceResourcePath).append("]"); + return builder.toString(); + } +} 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 index 6ae63956a0..2a2189d615 100644 --- 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 @@ -42,4 +42,10 @@ + + + + + + \ No newline at end of file -- 2.36.6