Updated implementation of internal RPC Router for Binding-Aware Broker and added... 33/1933/4
authorTony Tkacik <ttkacik@cisco.com>
Wed, 16 Oct 2013 15:01:47 +0000 (17:01 +0200)
committerGerrit Code Review <gerrit@opendaylight.org>
Wed, 16 Oct 2013 21:58:03 +0000 (21:58 +0000)
sal-binding-broker:
    Updated implementation of runtime code generator to adhere to RpcRouter contract,
    which is used by binding aware broker for introspection and configuration of runtime generated
    instances without need to use Reflection.

sal-binding-it:
    Added end-to-end integration test which tests following scenarios:
      - Registration of 2 Providers of Flow Service, 1 Consumer
        after registration 4 instances of Flow Service are existing:
         MD-SAL Northbound (runtime generated) - returned to the consumer.
         Optimized RpcRouter (runtime generated) - internal to MD-SAL
         2 implementations supplied by Provider.

      - Test verifies that implementations are not leaked to the Consumer

      - Routing of messages (calls):
         1. Provider 1. registers as provider for Node One, Consumer sends message to Node One, Provider 1. is invoked.
         2. Provider 2. registers as provider for Node Rwo, consumer sends message to Node Two, Provider 2. is invoked.
 3. Provider 1. unregisters as provider for Node One, Provider 2. registers as provider  for Node One,
            Consumer sends message to Node One, Provider 2. is invoked.

Change-Id: I101e26c190cd1770aaff1db974f9b0c341506482
Signed-off-by: Tony Tkacik <ttkacik@cisco.com>
16 files changed:
opendaylight/md-sal/pom.xml
opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/RuntimeCodeGenerator.java
opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/impl/RoutingPair.xtend
opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/impl/RpcRouterCodegenInstance.xtend [new file with mode: 0644]
opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/impl/RpcRoutingTableImpl.xtend [new file with mode: 0644]
opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/impl/RuntimeCodeGenerator.xtend
opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/impl/XtendHelper.java [new file with mode: 0644]
opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/impl/BindingAwareBrokerImpl.xtend
opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/impl/OsgiProviderContext.xtend
opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/impl/osgi/ClassLoaderUtils.java [new file with mode: 0644]
opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/spi/RpcRouter.java
opendaylight/md-sal/sal-binding-broker/src/test/java/org/opendaylight/controller/sal/binding/test/RuntimeCodeGeneratorTest.java
opendaylight/md-sal/sal-binding-it/pom.xml [new file with mode: 0644]
opendaylight/md-sal/sal-binding-it/src/test/java/org/opendaylight/controller/test/sal/binding/it/AbstractTest.java [new file with mode: 0644]
opendaylight/md-sal/sal-binding-it/src/test/java/org/opendaylight/controller/test/sal/binding/it/AbstractTestProvider.java [new file with mode: 0644]
opendaylight/md-sal/sal-binding-it/src/test/java/org/opendaylight/controller/test/sal/binding/it/RoutedServiceTest.java [new file with mode: 0644]

index 60e5a3daf858632cc3ad91e24072e718f4496cad..478bba81c7175dce24261f38f0e354d487957aa6 100644 (file)
@@ -26,6 +26,7 @@
         <!-- Binding Aware -->
         <module>sal-binding-api</module>
         <module>sal-binding-broker</module>
+        <module>sal-binding-it</module>
 
         <!-- Samples -->
         <module>samples</module>
index 0d78109a69c50e12a6d6c226465511f47f7b6b45..c8d6bcf3b1b06d48d5b9bfddb68fb5b0ac267131 100644 (file)
@@ -84,5 +84,5 @@ public interface RuntimeCodeGenerator {
      * @return Instance of RpcService of provided serviceType which implements
      *         also {@link RpcRouter}<T> and {@link DelegateProxy}
      */
-    <T extends RpcService> T getRouterFor(Class<T> serviceType) throws IllegalArgumentException;
+    <T extends RpcService> RpcRouter<T> getRouterFor(Class<T> serviceType) throws IllegalArgumentException;
 }
index 4324b4eb9aef03f65c4d3ba3a603d84574db635f..f60816d9544f91ad196130d52467b3246795af6b 100644 (file)
@@ -18,4 +18,7 @@ class RoutingPair {
     val Class<? extends BaseIdentity> context;
     @Property
     val CtMethod getter;
+    
+    @Property
+    val boolean encapsulated;
 }
diff --git a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/impl/RpcRouterCodegenInstance.xtend b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/impl/RpcRouterCodegenInstance.xtend
new file mode 100644 (file)
index 0000000..b255504
--- /dev/null
@@ -0,0 +1,52 @@
+package org.opendaylight.controller.sal.binding.codegen.impl
+
+import org.opendaylight.yangtools.yang.binding.RpcService
+import org.opendaylight.controller.sal.binding.spi.RpcRouter
+import org.opendaylight.yangtools.yang.binding.BaseIdentity
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier
+import static extension org.opendaylight.controller.sal.binding.codegen.RuntimeCodeHelper.*
+import java.util.Set
+import java.util.HashMap
+import org.opendaylight.controller.sal.binding.spi.RpcRoutingTable
+import org.opendaylight.yangtools.yang.binding.DataObject
+import static org.opendaylight.controller.sal.binding.codegen.impl.XtendHelper.*
+
+class RpcRouterCodegenInstance<T extends RpcService> implements RpcRouter<T> {
+    
+    @Property
+    val T invocationProxy
+    
+    @Property
+    val Class<T> rpcServiceType
+    
+    @Property
+    val Set<Class<? extends BaseIdentity>> contexts
+    
+    val routingTables = new HashMap<Class<? extends BaseIdentity>,RpcRoutingTableImpl<? extends BaseIdentity,?>>;
+    
+    
+    
+    @Property
+    var T defaultService
+    
+    new(Class<T> type,T routerImpl,Set<Class<? extends BaseIdentity>> contexts) {
+        _rpcServiceType = type
+        _invocationProxy = routerImpl
+        _contexts = contexts
+        
+        for(ctx : contexts) {
+            val table = XtendHelper.createRoutingTable(ctx)
+            invocationProxy.setRoutingTable(ctx,table.routes);
+            routingTables.put(ctx,table);
+        }
+    }
+    
+    override <C extends BaseIdentity> getRoutingTable(Class<C> table) {
+        routingTables.get(table) as RpcRoutingTable<C,T>
+    }
+    
+    override getService(Class<? extends BaseIdentity> context, InstanceIdentifier<?> path) {
+        val table = getRoutingTable(context);
+        return table.getRoute(path);
+    }
+}
\ No newline at end of file
diff --git a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/impl/RpcRoutingTableImpl.xtend b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/impl/RpcRoutingTableImpl.xtend
new file mode 100644 (file)
index 0000000..02da174
--- /dev/null
@@ -0,0 +1,49 @@
+package org.opendaylight.controller.sal.binding.codegen.impl
+
+import org.opendaylight.controller.sal.binding.spi.RpcRoutingTable
+import org.opendaylight.yangtools.yang.binding.BaseIdentity
+import org.opendaylight.yangtools.yang.binding.RpcService
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier
+import java.util.Map
+import org.opendaylight.yangtools.yang.binding.DataObject
+import java.util.HashMap
+
+class RpcRoutingTableImpl<C extends BaseIdentity,S extends RpcService> implements RpcRoutingTable<C,S>{
+    
+    @Property
+    val Class<C> identifier;
+    
+    @Property
+    var S defaultRoute;
+    
+    @Property
+    val Map<InstanceIdentifier<? extends DataObject>,S> routes;
+    
+    new(Class<C> ident, Map<InstanceIdentifier<? extends DataObject>,S> route) {
+        _identifier = ident
+        _routes = route
+    }
+    
+    new(Class<C> ident) {
+        _identifier = ident
+        _routes = new HashMap
+    }
+    
+    
+    override getRoute(InstanceIdentifier nodeInstance) {
+        val ret = routes.get(nodeInstance);
+        if(ret !== null) {
+            return ret;
+        }
+        return defaultRoute;
+    }
+    
+    override removeRoute(InstanceIdentifier<? extends Object> path) {
+        routes.remove(path);
+    }
+    
+    @SuppressWarnings("rawtypes")
+    override updateRoute(InstanceIdentifier<? extends Object> path, S service) {
+        routes.put(path as InstanceIdentifier,service);
+    }
+}
\ No newline at end of file
index 3b3f4190cfb4716a04ecfbc7808e5bbcb06acc95..90be6f3476841bf2f20e53f9cc66438148722d60 100644 (file)
@@ -32,8 +32,11 @@ import java.util.Arrays
 
 import static extension org.opendaylight.controller.sal.binding.codegen.YangtoolsMappingHelper.*
 import static extension org.opendaylight.controller.sal.binding.codegen.RuntimeCodeSpecification.*
+import java.util.HashSet
+import java.io.ObjectOutputStream.PutField
+import static org.opendaylight.controller.sal.binding.impl.osgi.ClassLoaderUtils.*
 
-class RuntimeCodeGenerator {
+class RuntimeCodeGenerator implements org.opendaylight.controller.sal.binding.codegen.RuntimeCodeGenerator {
 
     val ClassPool classPool;
 
@@ -41,7 +44,7 @@ class RuntimeCodeGenerator {
         classPool = pool;
     }
 
-    def <T extends RpcService> Class<? extends T> generateDirectProxy(Class<T> iface) {
+    override <T extends RpcService> getDirectProxyFor(Class<T> iface) {
         val supertype = iface.asCtClass
         val targetCls = createClass(iface.directProxyName, supertype) [
             field(DELEGATE_FIELD, iface);
@@ -49,43 +52,53 @@ class RuntimeCodeGenerator {
                 body = '''return ($r) «DELEGATE_FIELD».«it.name»($$);'''
             ]
         ]
-        return targetCls.toClass(iface.classLoader)
+        return targetCls.toClass(iface.classLoader).newInstance as T
     }
 
-    def <T extends RpcService> Class<? extends T> generateRouter(Class<T> iface) {
-        val supertype = iface.asCtClass
-        val targetCls = createClass(iface.routerName, supertype) [
-            //field(ROUTING_TABLE_FIELD,Map)
-            field(DELEGATE_FIELD, iface)
-            val contexts = new HashMap<String, Class<? extends BaseIdentity>>();
-            // We search for routing pairs and add fields
-            supertype.methods.filter[declaringClass == supertype && parameterTypes.size === 1].forEach [ method |
-                val routingPair = method.routingContextInput;
-                if (routingPair !== null)
-                    contexts.put(routingPair.context.routingTableField, routingPair.context);
-            ]
-            for (ctx : contexts.entrySet) {
-                field(ctx.key, Map)
-            }
-            implementMethodsFrom(supertype) [
-                if (parameterTypes.size === 1) {
-                    val routingPair = routingContextInput;
-                    val bodyTmp = '''
-                    {
-                        final «InstanceIdentifier.name» identifier = $1.«routingPair.getter.name»();
-                        «supertype.name» instance = («supertype.name») «routingPair.context.routingTableField».get(identifier);
-                        if(instance == null) {
-                           instance = «DELEGATE_FIELD»;
-                        }
-                        return ($r) instance.«it.name»($$);
-                    }'''
-                    body = bodyTmp
-                } else if (parameterTypes.size === 0) {
-                    body = '''return ($r) «DELEGATE_FIELD».«it.name»($$);'''
+    override <T extends RpcService> getRouterFor(Class<T> iface) {
+        val contexts = new HashSet<Class<? extends BaseIdentity>>
+
+        val instance = <T>withClassLoader(iface.classLoader) [ |
+            val supertype = iface.asCtClass
+            val targetCls = createClass(iface.routerName, supertype) [
+                //field(ROUTING_TABLE_FIELD,Map)
+                field(DELEGATE_FIELD, iface)
+                val ctxMap = new HashMap<String, Class<? extends BaseIdentity>>();
+                // We search for routing pairs and add fields
+                supertype.methods.filter[declaringClass == supertype && parameterTypes.size === 1].forEach [ method |
+                    val routingPair = method.routingContextInput;
+                    if (routingPair !== null) {
+                        ctxMap.put(routingPair.context.routingTableField, routingPair.context);
+                        contexts.add(routingPair.context)
+                    }
+                ]
+                for (ctx : ctxMap.entrySet) {
+                    field(ctx.key, Map)
                 }
+                implementMethodsFrom(supertype) [
+                    if (parameterTypes.size === 1) {
+                        val routingPair = routingContextInput;
+                        val bodyTmp = '''
+                        {
+                            final «InstanceIdentifier.name» identifier = $1.«routingPair.getter.name»()«IF routingPair.encapsulated».getValue()«ENDIF»;
+                            «supertype.name» instance = («supertype.name») «routingPair.context.routingTableField».get(identifier);
+                            if(instance == null) {
+                               instance = «DELEGATE_FIELD»;
+                            }
+                            if(instance == null) {
+                                throw new java.lang.IllegalStateException("No provider is processing supplied message");
+                            }
+                            return ($r) instance.«it.name»($$);
+                        }'''
+                        body = bodyTmp
+                    } else if (parameterTypes.size === 0) {
+                        body = '''return ($r) «DELEGATE_FIELD».«it.name»($$);'''
+                    }
+                ]
             ]
-        ]
-        return targetCls.toClass(iface.classLoader)
+            return targetCls.toClass(iface.classLoader).newInstance as T
+        ];
+        return new RpcRouterCodegenInstance(iface, instance, contexts);
     }
 
     def Class<?> generateListenerInvoker(Class<? extends NotificationListener> iface) {
@@ -121,17 +134,19 @@ class RuntimeCodeGenerator {
 
     private def RoutingPair getContextInstance(CtClass dataClass) {
         for (method : dataClass.methods) {
-            if (method.parameterTypes.size === 0 && method.name.startsWith("get")) {
+            if (method.name.startsWith("get") && method.parameterTypes.size === 0) {
                 for (annotation : method.availableAnnotations) {
                     if (annotation instanceof RoutingContext) {
-                        return new RoutingPair((annotation as RoutingContext).value, method)
+                        val encapsulated = !method.returnType.equals(InstanceIdentifier.asCtClass);
+                        
+                        return new RoutingPair((annotation as RoutingContext).value, method,encapsulated);
                     }
                 }
             }
         }
         for (iface : dataClass.interfaces) {
             val ret = getContextInstance(iface);
-            if (ret != null) return ret;
+            if(ret != null) return ret;
         }
         return null;
     }
diff --git a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/impl/XtendHelper.java b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/impl/XtendHelper.java
new file mode 100644 (file)
index 0000000..21b48bb
--- /dev/null
@@ -0,0 +1,16 @@
+package org.opendaylight.controller.sal.binding.codegen.impl;
+
+import org.opendaylight.yangtools.yang.binding.BaseIdentity;
+import org.opendaylight.yangtools.yang.binding.RpcService;
+
+public class XtendHelper {
+
+    public static <C extends BaseIdentity> RpcRoutingTableImpl createRoutingTable(
+            Class<C> cls) {
+        return new RpcRoutingTableImpl<>(cls);
+    }
+    
+    public static String foo() {
+        return "Foo";
+    }
+}
index 2bae9f515f40bfe4ed25e95ccd4426493c413a22..88e3e62f392163e192fab5e21faf74f611a0480e 100644 (file)
@@ -33,13 +33,35 @@ import org.opendaylight.controller.sal.binding.api.BindingAwareBroker.RoutedRpcR
 import org.opendaylight.controller.sal.binding.api.BindingAwareBroker.RpcRegistration
 import org.opendaylight.controller.sal.binding.api.data.DataProviderService
 import org.opendaylight.controller.sal.binding.api.data.DataBrokerService
+import org.opendaylight.controller.sal.binding.spi.RpcRouter
+import java.util.concurrent.ConcurrentHashMap
+import static com.google.common.base.Preconditions.*
+import org.opendaylight.yangtools.concepts.AbstractObjectRegistration
+import org.opendaylight.yangtools.yang.binding.BaseIdentity
+import com.google.common.collect.Multimap
+import com.google.common.collect.HashMultimap
+import static org.opendaylight.controller.sal.binding.impl.osgi.ClassLoaderUtils.*
 
 class BindingAwareBrokerImpl implements BindingAwareBroker {
     private static val log = LoggerFactory.getLogger(BindingAwareBrokerImpl)
 
     private val clsPool = ClassPool.getDefault()
     private var RuntimeCodeGenerator generator;
-    private Map<Class<? extends RpcService>, RpcProxyContext> managedProxies = new HashMap();
+
+    /**
+     * Map of all Managed Direct Proxies
+     * 
+     */
+    private val Map<Class<? extends RpcService>, RpcProxyContext> managedProxies = new ConcurrentHashMap();
+
+    /**
+     * 
+     * Map of all available Rpc Routers
+     * 
+     * 
+     */
+    private val Map<Class<? extends RpcService>, RpcRouter<? extends RpcService>> rpcRouters = new ConcurrentHashMap();
+
     private var NotificationBrokerImpl notifyBroker
     private var DataBrokerImpl dataBroker
     private var ServiceRegistration<NotificationProviderService> notifyBrokerRegistration
@@ -57,9 +79,9 @@ class BindingAwareBrokerImpl implements BindingAwareBroker {
         notifyBrokerRegistration = brokerBundleContext.registerService(NotificationProviderService, notifyBroker,
             brokerProperties)
         brokerBundleContext.registerService(NotificationService, notifyBroker, brokerProperties)
-        brokerBundleContext.registerService(DataProviderService,dataBroker,brokerProperties)
-        brokerBundleContext.registerService(DataBrokerService,dataBroker,brokerProperties)
-        
+        brokerBundleContext.registerService(DataProviderService, dataBroker, brokerProperties)
+        brokerBundleContext.registerService(DataBrokerService, dataBroker, brokerProperties)
+
     }
 
     def initGenerator() {
@@ -101,16 +123,16 @@ class BindingAwareBrokerImpl implements BindingAwareBroker {
      * 
      * If proxy class does not exist for supplied service class it will be generated automatically.
      */
-    def <T extends RpcService> getManagedDirectProxy(Class<T> service) {
+    private def <T extends RpcService> getManagedDirectProxy(Class<T> service) {
 
         var RpcProxyContext existing = null
-        if((existing = managedProxies.get(service)) != null) {
+        if ((existing = managedProxies.get(service)) != null) {
             return existing.proxy
         }
-        val proxyClass = generator.generateDirectProxy(service)
-        val rpcProxyCtx = new RpcProxyContext(proxyClass)
+        val proxyInstance = generator.getDirectProxyFor(service)
+        val rpcProxyCtx = new RpcProxyContext(proxyInstance.class)
         val properties = new Hashtable<String, String>()
-        rpcProxyCtx.proxy = proxyClass.newInstance as RpcService
+        rpcProxyCtx.proxy = proxyInstance as RpcService
 
         properties.salServiceType = SAL_SERVICE_TYPE_CONSUMER_PROXY
         rpcProxyCtx.registration = brokerBundleContext.registerService(service, rpcProxyCtx.proxy as T, properties)
@@ -125,22 +147,138 @@ class BindingAwareBrokerImpl implements BindingAwareBroker {
     def <T extends RpcService> registerRpcImplementation(Class<T> type, T service, OsgiProviderContext context,
         Hashtable<String, String> properties) {
         val proxy = getManagedDirectProxy(type)
-        if(proxy.delegate != null) {
-            throw new IllegalStateException("Service " + type + "is already registered");
-        }
+        checkState(proxy.delegate === null, "The Service for type {} is already registered", type)
+
         val osgiReg = context.bundleContext.registerService(type, service, properties);
         proxy.delegate = service;
         return new RpcServiceRegistrationImpl<T>(type, service, osgiReg);
     }
 
-    def <T extends RpcService> RpcRegistration<T> registerMountedRpcImplementation(Class<T> tyoe, T service, InstanceIdentifier<?> identifier,
-        OsgiProviderContext context, Hashtable<String, String> properties) {
+    def <T extends RpcService> RpcRegistration<T> registerMountedRpcImplementation(Class<T> type, T service,
+        InstanceIdentifier<?> identifier, OsgiProviderContext context) {
         throw new UnsupportedOperationException("TODO: auto-generated method stub")
     }
 
-    def <T extends RpcService> RoutedRpcRegistration<T> registerRoutedRpcImplementation(Class<T> type, T service, OsgiProviderContext context,
-        Hashtable<String, String> properties) {
-        throw new UnsupportedOperationException("TODO: auto-generated method stub")
+    def <T extends RpcService> RoutedRpcRegistration<T> registerRoutedRpcImplementation(Class<T> type, T service,
+        OsgiProviderContext context) {
+        val router = resolveRpcRouter(type);
+        checkState(router !== null)
+        return new RoutedRpcRegistrationImpl<T>(service, router, this)
+    }
+
+    private def <T extends RpcService> RpcRouter<T> resolveRpcRouter(Class<T> type) {
+
+        val router = rpcRouters.get(type);
+        if (router !== null) {
+            return router as RpcRouter<T>;
+        }
+
+        // We created Router
+        val newRouter = generator.getRouterFor(type);
+        checkState(newRouter !== null);
+        rpcRouters.put(type, newRouter);
+
+        // We create / update Direct Proxy for router
+        val proxy = getManagedDirectProxy(type);
+        proxy.delegate = newRouter.invocationProxy
+        return newRouter;
+
+    }
+
+    protected def <T extends RpcService> void registerPath(RoutedRpcRegistrationImpl<T> registration,
+        Class<? extends BaseIdentity> context, InstanceIdentifier<? extends Object> path) {
+
+        val router = registration.router;
+        val paths = registration.registeredPaths;
+
+        val routingTable = router.getRoutingTable(context)
+        checkState(routingTable != null);
+
+        // Updating internal structure of registration
+        routingTable.updateRoute(path, registration.instance)
+        val success = paths.put(context, path);
+    }
+
+    protected def <T extends RpcService> void unregisterPath(RoutedRpcRegistrationImpl<T> registration,
+        Class<? extends BaseIdentity> context, InstanceIdentifier<? extends Object> path) {
+
+        val router = registration.router;
+        val paths = registration.registeredPaths;
+
+        val routingTable = router.getRoutingTable(context)
+        checkState(routingTable != null);
+
+        // Updating internal structure of registration
+        val target = routingTable.getRoute(path)
+        checkState(target === registration.instance)
+        routingTable.removeRoute(path)
+        checkState(paths.remove(context, path));
+    }
+
+    protected def <T extends RpcService> void unregisterRoutedRpcService(RoutedRpcRegistrationImpl<T> registration) {
+
+        val router = registration.router;
+        val paths = registration.registeredPaths;
+
+        for (ctxMap : registration.registeredPaths.entries) {
+            val context = ctxMap.key
+            val routingTable = router.getRoutingTable(context)
+            val path = ctxMap.value
+            routingTable.removeRoute(path)
+
+        }
+    }
+}
+
+class RoutedRpcRegistrationImpl<T extends RpcService> extends AbstractObjectRegistration<T> implements RoutedRpcRegistration<T> {
+
+    @Property
+    private val BindingAwareBrokerImpl broker;
+
+    @Property
+    private val RpcRouter<T> router;
+
+    @Property
+    private val Multimap<Class<? extends BaseIdentity>, InstanceIdentifier<?>> registeredPaths = HashMultimap.create();
+
+    private var closed = false;
+
+    new(T instance, RpcRouter<T> backingRouter, BindingAwareBrokerImpl broker) {
+        super(instance)
+        _router = backingRouter;
+        _broker = broker;
+    }
+
+    override protected removeRegistration() {
+        closed = true
+        broker.unregisterRoutedRpcService(this)
+    }
+
+    override registerInstance(Class<? extends BaseIdentity> context, InstanceIdentifier<? extends Object> instance) {
+        registerPath(context, instance);
+    }
+
+    override unregisterInstance(Class<? extends BaseIdentity> context, InstanceIdentifier<? extends Object> instance) {
+        unregisterPath(context, instance);
+    }
+
+    override getService() {
+        return instance;
+    }
+
+    override registerPath(Class<? extends BaseIdentity> context, InstanceIdentifier<? extends Object> path) {
+        checkClosed()
+        broker.registerPath(this, context, path);
+    }
+
+    override unregisterPath(Class<? extends BaseIdentity> context, InstanceIdentifier<? extends Object> path) {
+        checkClosed()
+        broker.unregisterPath(this, context, path);
+    }
+
+    private def checkClosed() {
+        if (closed)
+            throw new IllegalStateException("Registration was closed.");
     }
 
 }
index 29c3845004ee2390f33f1b02f2d167ad7c173dea..1be19c0213e089fb0b87ffe56c23d70509c58337 100644 (file)
@@ -20,6 +20,7 @@ import static extension org.opendaylight.controller.sal.binding.impl.osgi.Proper
 import org.opendaylight.controller.sal.binding.api.BindingAwareBroker.RpcRegistration
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier
 import org.opendaylight.controller.sal.binding.api.BindingAwareProvider.ProviderFunctionality
+import static com.google.common.base.Preconditions.*
 
 class OsgiProviderContext extends OsgiConsumerContext implements ProviderContext {
 
@@ -44,30 +45,33 @@ class OsgiProviderContext extends OsgiConsumerContext implements ProviderContext
     }
 
     override <T extends RpcService> addMountRpcImplementation(Class<T> type, InstanceIdentifier<?> mount, T implementation) throws IllegalStateException {
+        checkNotNull(type, "Service type should not be null")
+        checkNotNull(mount,"Path to the mount should not be null")
+        checkNotNull(implementation, "Service instance should not be null")
 
         val properties = new Hashtable<String, String>();
         properties.salServiceType = SAL_SERVICE_TYPE_PROVIDER
 
         // Fill requirements
-        val salReg = broker.registerMountedRpcImplementation(type, implementation, mount, this, properties)
+        val salReg = broker.registerMountedRpcImplementation(type, implementation, mount, this)
         registeredServices.put(type, salReg)
         return salReg;
     }
 
     override <T extends RpcService> addRoutedRpcImplementation(Class<T> type, T implementation) throws IllegalStateException {
-        val properties = new Hashtable<String, String>();
-        properties.salServiceType = SAL_SERVICE_TYPE_PROVIDER
-
-        // Fill requirements
-        val salReg = broker.registerRoutedRpcImplementation(type, implementation, this, properties)
+        checkNotNull(type, "Service type should not be null")
+        checkNotNull(implementation, "Service type should not be null")
+        
+        val salReg = broker.registerRoutedRpcImplementation(type, implementation, this)
         registeredServices.put(type, salReg)
         return salReg;
     }
 
     override registerFunctionality(ProviderFunctionality functionality) {
-    
+        // NOOP for now
     }
 
     override unregisterFunctionality(ProviderFunctionality functionality) {
+        // NOOP for now
     }
 }
diff --git a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/impl/osgi/ClassLoaderUtils.java b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/impl/osgi/ClassLoaderUtils.java
new file mode 100644 (file)
index 0000000..ad0473e
--- /dev/null
@@ -0,0 +1,24 @@
+package org.opendaylight.controller.sal.binding.impl.osgi;
+
+
+
+import java.util.concurrent.Callable;
+import static com.google.common.base.Preconditions.*;
+
+public class ClassLoaderUtils {
+    
+    public static <V> V withClassLoader(ClassLoader cls,Callable<V> function) throws Exception {
+        checkNotNull(cls);
+        checkNotNull(function);
+        ClassLoader oldCls = Thread.currentThread().getContextClassLoader();
+        try {
+            Thread.currentThread().setContextClassLoader(cls);
+            V result = function.call();
+            Thread.currentThread().setContextClassLoader(oldCls);
+            return result;
+        } catch (Exception e) {
+            Thread.currentThread().setContextClassLoader(oldCls);
+            throw new Exception(e);
+        }
+    }
+}
\ No newline at end of file
index b7a42eda1f51db83ad4f147676f0aa128a681dcc..38e309f46d4d30e2c2c3eac25dfb2bcfcb21fa2c 100644 (file)
@@ -7,6 +7,8 @@
  */
 package org.opendaylight.controller.sal.binding.spi;
 
+import java.util.Set;
+
 import org.opendaylight.yangtools.yang.binding.BaseIdentity;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 import org.opendaylight.yangtools.yang.binding.RpcService;
@@ -31,6 +33,16 @@ public interface RpcRouter<T extends RpcService> {
      * @return type of RpcService which is served by this instance of router.
      */
     Class<T> getRpcServiceType();
+    
+    
+    /**
+     * Returns a instance of T which is associated with this router instance
+     * and routes messages based on routing tables.
+     * 
+     * @return type of RpcService which is served by this instance of router.
+     */
+    T getInvocationProxy();
+    
 
     /**
      * Returns a routing table for particular route context
@@ -64,6 +76,8 @@ public interface RpcRouter<T extends RpcService> {
     /**
      * 
      */
-    void setDefaultService();
+    void setDefaultService(T service);
+
+    Set<Class<? extends BaseIdentity>> getContexts();
 
 }
index e0808b2d82d7667e2923f17ca347b2ae249f74c0..95ac22c6b37c31dbd7bbb65fafd35f5e78e3b5d6 100644 (file)
@@ -13,6 +13,8 @@ import org.junit.Test;
 import static org.opendaylight.controller.sal.binding.codegen.RuntimeCodeHelper.*;
 
 import org.opendaylight.controller.sal.binding.codegen.impl.RuntimeCodeGenerator;
+import org.opendaylight.controller.sal.binding.spi.RpcRouter;
+import org.opendaylight.controller.sal.binding.spi.RpcRoutingTable;
 import org.opendaylight.controller.sal.binding.test.mock.FooService;
 import org.opendaylight.controller.sal.binding.test.mock.ReferencableObject;
 import org.opendaylight.controller.sal.binding.test.mock.ReferencableObjectKey;
@@ -39,25 +41,25 @@ public class RuntimeCodeGeneratorTest {
     
     @Test
     public void testGenerateDirectProxy() {
-        Class<? extends FooService> product = codeGenerator.generateDirectProxy(FooService.class);
+        FooService product = codeGenerator.getDirectProxyFor(FooService.class);
         assertNotNull(product);
     }
 
     @Test
     public void testGenerateRouter() throws Exception {
-        Class<? extends FooService> product = codeGenerator.generateRouter(FooService.class);
+        RpcRouter<FooService> product = codeGenerator.getRouterFor(FooService.class);
         assertNotNull(product);
-        assertNotNull(product.getSimpleName());
-        assertEquals("2 fields should be generated.",2,product.getFields().length);
+        assertNotNull(product.getInvocationProxy());
         
-        verifyRouting(product.newInstance());
+        assertEquals("2 fields should be generated.",2,product.getInvocationProxy().getClass().getFields().length);
+        
+        verifyRouting(product);
     }
 
-    private void verifyRouting(FooService product) {
-        Map<InstanceIdentifier<? extends Object>,FooService> routingTable = new HashMap<>();
-        setRoutingTable(product, BaseIdentity.class, routingTable);
+    private void verifyRouting(RpcRouter<FooService> product) {
+        assertNotNull("Routing table should be initialized",product.getRoutingTable(BaseIdentity.class));
         
-        assertSame("Returned routing table should be same instance",routingTable,getRoutingTable(product, BaseIdentity.class));
+        RpcRoutingTable<BaseIdentity, FooService> routingTable = product.getRoutingTable(BaseIdentity.class);
         
         int servicesCount = 2;
         int instancesPerService = 3;
@@ -70,11 +72,11 @@ public class RuntimeCodeGeneratorTest {
         
         for(int i = 0;i<service.length;i++) {
             for (InstanceIdentifier<?> instance : identifiers[i]) {
-                routingTable.put(instance, service[i]);
+                routingTable.updateRoute(instance, service[i]);
             }
         }
         
-        assertEquals("All instances should be registered.", servicesCount*instancesPerService, routingTable.size());
+        assertEquals("All instances should be registered.", servicesCount*instancesPerService, routingTable.getRoutes().size());
         
         SimpleInput[] instance_0_input = new SimpleInputImpl[] {
             new SimpleInputImpl(identifiers[0][0]),
@@ -90,16 +92,19 @@ public class RuntimeCodeGeneratorTest {
         
         // We test sending mock messages
         
-        product.simple(instance_0_input[0]);
+        product.getInvocationProxy().simple(instance_0_input[0]);
         verify(service[0]).simple(instance_0_input[0]);
         
-        product.simple(instance_0_input[1]);
-        product.simple(instance_0_input[2]);
+        product.getInvocationProxy().simple(instance_0_input[1]);
+        product.getInvocationProxy().simple(instance_0_input[2]);
         
         verify(service[0]).simple(instance_0_input[1]);
         verify(service[0]).simple(instance_0_input[2]);
         
-        product.simple(instance_1_input[0]);
+        
+        product.getInvocationProxy().simple(instance_1_input[0]);
+        
+        // We should have call to instance 1
         verify(service[1]).simple(instance_1_input[0]);
     }
 
diff --git a/opendaylight/md-sal/sal-binding-it/pom.xml b/opendaylight/md-sal/sal-binding-it/pom.xml
new file mode 100644 (file)
index 0000000..86b6626
--- /dev/null
@@ -0,0 +1,141 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <artifactId>sal-parent</artifactId>
+    <groupId>org.opendaylight.controller</groupId>
+    <version>1.0-SNAPSHOT</version>
+  </parent>
+  <artifactId>sal-binding-it</artifactId>
+  <scm>
+    <connection>scm:git:ssh://git.opendaylight.org:29418/controller.git</connection>
+    <developerConnection>scm:git:ssh://git.opendaylight.org:29418/controller.git</developerConnection>
+    <url>https://wiki.opendaylight.org/view/OpenDaylight_Controller:MD-SAL</url>
+  </scm>
+
+  <properties>
+    <exam.version>3.0.0</exam.version>
+    <url.version>1.5.0</url.version>
+  </properties>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.ops4j.pax.exam</groupId>
+        <artifactId>maven-paxexam-plugin</artifactId>
+        <version>1.2.4</version>
+        <executions>
+          <execution>
+            <id>generate-config</id>
+            <goals>
+              <goal>generate-depends-file</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+    <pluginManagement>
+      <plugins>
+        <!--This plugin's configuration is used to store Eclipse m2e settings 
+          only. It has no influence on the Maven build itself. -->
+        <plugin>
+          <groupId>org.eclipse.m2e</groupId>
+          <artifactId>lifecycle-mapping</artifactId>
+          <version>1.0.0</version>
+          <configuration>
+            <lifecycleMappingMetadata>
+              <pluginExecutions>
+                <pluginExecution>
+                  <pluginExecutionFilter>
+                    <groupId>
+                      org.ops4j.pax.exam
+                    </groupId>
+                    <artifactId>
+                      maven-paxexam-plugin
+                    </artifactId>
+                    <versionRange>
+                      [1.2.4,)
+                    </versionRange>
+                    <goals>
+                      <goal>
+                        generate-depends-file
+                      </goal>
+                    </goals>
+                  </pluginExecutionFilter>
+                  <action>
+                    <ignore></ignore>
+                  </action>
+                </pluginExecution>
+              </pluginExecutions>
+            </lifecycleMappingMetadata>
+          </configuration>
+        </plugin>
+      </plugins>
+    </pluginManagement>
+  </build>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.opendaylight.yangtools.thirdparty</groupId>
+      <artifactId>xtend-lib-osgi</artifactId>
+      <version>2.4.3</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>sal-binding-broker-impl</artifactId>
+      <version>1.0-SNAPSHOT</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.ops4j.pax.exam</groupId>
+      <artifactId>pax-exam-container-native</artifactId>
+      <version>${exam.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.ops4j.pax.exam</groupId>
+      <artifactId>pax-exam-junit4</artifactId>
+      <version>${exam.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.ops4j.pax.exam</groupId>
+      <artifactId>pax-exam-link-mvn</artifactId>
+      <version>${exam.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>equinoxSDK381</groupId>
+      <artifactId>org.eclipse.osgi</artifactId>
+      <version>3.8.1.v20120830-144521</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>log4j-over-slf4j</artifactId>
+      <version>1.7.2</version>
+    </dependency>
+    <dependency>
+      <groupId>ch.qos.logback</groupId>
+      <artifactId>logback-core</artifactId>
+      <version>1.0.9</version>
+    </dependency>
+    <dependency>
+      <groupId>ch.qos.logback</groupId>
+      <artifactId>logback-classic</artifactId>
+      <version>1.0.9</version>
+    </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-all</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller.model</groupId>
+      <artifactId>model-flow-service</artifactId>
+      <version>1.0-SNAPSHOT</version>
+      <scope>provided</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/opendaylight/md-sal/sal-binding-it/src/test/java/org/opendaylight/controller/test/sal/binding/it/AbstractTest.java b/opendaylight/md-sal/sal-binding-it/src/test/java/org/opendaylight/controller/test/sal/binding/it/AbstractTest.java
new file mode 100644 (file)
index 0000000..cddf7e8
--- /dev/null
@@ -0,0 +1,131 @@
+package org.opendaylight.controller.test.sal.binding.it;
+
+import static org.ops4j.pax.exam.CoreOptions.*;
+
+import javax.inject.Inject;
+
+import org.junit.runner.RunWith;
+import org.opendaylight.controller.sal.binding.api.BindingAwareBroker;
+import org.ops4j.pax.exam.Configuration;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.ops4j.pax.exam.options.DefaultCompositeOption;
+import org.osgi.framework.BundleContext;
+
+@RunWith(PaxExam.class)
+public abstract class AbstractTest {
+
+    public static final String CONTROLLER = "org.opendaylight.controller";
+    public static final String YANGTOOLS = "org.opendaylight.yangtools";
+    
+    public static final String CONTROLLER_MODELS = "org.opendaylight.controller.model";
+    public static final String YANGTOOLS_MODELS = "org.opendaylight.yangtools.model";
+    
+    
+    @Inject
+    BindingAwareBroker broker;
+
+    @Inject
+    BundleContext bundleContext;
+
+    
+    
+    public BindingAwareBroker getBroker() {
+        return broker;
+    }
+
+
+
+    public void setBroker(BindingAwareBroker broker) {
+        this.broker = broker;
+    }
+
+
+
+    public BundleContext getBundleContext() {
+        return bundleContext;
+    }
+
+
+
+    public void setBundleContext(BundleContext bundleContext) {
+        this.bundleContext = bundleContext;
+    }
+
+
+
+    @Configuration
+    public Option[] config() {
+        return options(systemProperty("osgi.console").value("2401"), 
+                mavenBundle("org.slf4j", "slf4j-api").versionAsInProject(), //
+                mavenBundle("org.slf4j", "log4j-over-slf4j").versionAsInProject(), //
+                mavenBundle("ch.qos.logback", "logback-core").versionAsInProject(), //
+                mavenBundle("ch.qos.logback", "logback-classic").versionAsInProject(), //
+                
+                // MD-SAL Dependencies
+                mavenBundle(YANGTOOLS, "concepts").versionAsInProject(),
+                mavenBundle(YANGTOOLS, "yang-binding").versionAsInProject(), //
+                mavenBundle(YANGTOOLS, "yang-common").versionAsInProject(), //
+                
+                mavenBundle(YANGTOOLS+".thirdparty", "xtend-lib-osgi").versionAsInProject(),
+                mavenBundle("com.google.guava", "guava").versionAsInProject(), //
+                mavenBundle("org.javassist", "javassist").versionAsInProject(),
+                
+                // MD SAL
+                mavenBundle(CONTROLLER, "sal-binding-api").versionAsInProject(), //
+                mavenBundle(CONTROLLER, "sal-binding-broker-impl").versionAsInProject(), //
+                mavenBundle(CONTROLLER, "sal-common").versionAsInProject(), //
+                mavenBundle(CONTROLLER, "sal-common-api").versionAsInProject(),
+                mavenBundle(CONTROLLER, "sal-common-util").versionAsInProject(), //
+                
+                // BASE Models
+                mavenBundle(YANGTOOLS_MODELS,"yang-ext").versionAsInProject(),
+                mavenBundle(YANGTOOLS_MODELS,"ietf-inet-types").versionAsInProject(),
+                mavenBundle(YANGTOOLS_MODELS,"ietf-yang-types").versionAsInProject(),
+                mavenBundle(YANGTOOLS_MODELS,"opendaylight-l2-types").versionAsInProject(),
+                mavenBundle(CONTROLLER_MODELS,"model-flow-base").versionAsInProject(),
+                mavenBundle(CONTROLLER_MODELS,"model-flow-service").versionAsInProject(),
+                mavenBundle(CONTROLLER_MODELS,"model-inventory").versionAsInProject(),
+                junitAndMockitoBundles()
+                );
+    }
+    
+    
+    public static Option junitAndMockitoBundles() {
+        return new DefaultCompositeOption(
+                // Repository required to load harmcrest (OSGi-fied version).
+                repository("http://repository.springsource.com/maven/bundles/external").id(
+                        "com.springsource.repository.bundles.external"),
+
+                // Mockito without Hamcrest and Objenesis
+                mavenBundle("org.mockito", "mockito-all", "1.9.5"),
+
+                // Hamcrest with a version matching the range expected by Mockito
+                //mavenBundle("org.hamcrest", "com.springsource.org.hamcrest.core", "1.1.0"),
+
+                // Objenesis with a version matching the range expected by Mockito
+                //wrappedBundle(mavenBundle("org.objenesis", "objenesis", "1.2"))
+                 //       .exports("*;version=1.2"),
+
+                // The default JUnit bundle also exports Hamcrest, but with an (incorrect) version of
+                // 4.9 which does not match the Mockito import. When deployed after the hamcrest bundles, it gets
+                // resolved correctly.
+                junitBundles(),
+
+                /*
+                 * Felix has implicit boot delegation enabled by default. It conflicts with Mockito:
+                 * java.lang.LinkageError: loader constraint violation in interface itable initialization:
+                 * when resolving method "org.osgi.service.useradmin.User$$EnhancerByMockitoWithCGLIB$$dd2f81dc
+                 * .newInstance(Lorg/mockito/cglib/proxy/Callback;)Ljava/lang/Object;" the class loader
+                 * (instance of org/mockito/internal/creation/jmock/SearchingClassLoader) of the current class,
+                 * org/osgi/service/useradmin/User$$EnhancerByMockitoWithCGLIB$$dd2f81dc, and the class loader
+                 * (instance of org/apache/felix/framework/BundleWiringImpl$BundleClassLoaderJava5) for interface
+                 * org/mockito/cglib/proxy/Factory have different Class objects for the type org/mockito/cglib/
+                 * proxy/Callback used in the signature
+                 *
+                 * So we disable the bootdelegation. this property has no effect on the other OSGi implementation.
+                 */
+                frameworkProperty("felix.bootdelegation.implicit").value("false")
+        );
+    }
+}
diff --git a/opendaylight/md-sal/sal-binding-it/src/test/java/org/opendaylight/controller/test/sal/binding/it/AbstractTestProvider.java b/opendaylight/md-sal/sal-binding-it/src/test/java/org/opendaylight/controller/test/sal/binding/it/AbstractTestProvider.java
new file mode 100644 (file)
index 0000000..6f1551f
--- /dev/null
@@ -0,0 +1,29 @@
+package org.opendaylight.controller.test.sal.binding.it;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import org.opendaylight.controller.sal.binding.api.BindingAwareBroker.ConsumerContext;
+import org.opendaylight.controller.sal.binding.api.BindingAwareBroker.ProviderContext;
+import org.opendaylight.controller.sal.binding.api.BindingAwareProvider;
+import org.opendaylight.yangtools.yang.binding.RpcService;
+
+public abstract class AbstractTestProvider implements BindingAwareProvider {
+
+    @Override
+    public Collection<? extends RpcService> getImplementations() {
+        return Collections.emptySet();
+    }
+
+    @Override
+    public Collection<? extends ProviderFunctionality> getFunctionality() {
+        return Collections.emptySet();
+    }
+
+    @Override
+    public void onSessionInitialized(ConsumerContext session) {
+        // Noop
+
+    }
+
+}
diff --git a/opendaylight/md-sal/sal-binding-it/src/test/java/org/opendaylight/controller/test/sal/binding/it/RoutedServiceTest.java b/opendaylight/md-sal/sal-binding-it/src/test/java/org/opendaylight/controller/test/sal/binding/it/RoutedServiceTest.java
new file mode 100644 (file)
index 0000000..ef60a2c
--- /dev/null
@@ -0,0 +1,199 @@
+package org.opendaylight.controller.test.sal.binding.it;
+
+import static org.junit.Assert.*;
+
+import java.math.BigInteger;
+import java.util.Collection;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.controller.sal.binding.api.BindingAwareBroker;
+import org.opendaylight.controller.sal.binding.api.BindingAwareBroker.ConsumerContext;
+import org.opendaylight.controller.sal.binding.api.BindingAwareBroker.ProviderContext;
+import org.opendaylight.controller.sal.binding.api.BindingAwareBroker.RoutedRpcRegistration;
+import org.opendaylight.controller.sal.binding.api.BindingAwareConsumer;
+import org.opendaylight.controller.sal.binding.api.BindingAwareProvider.ProviderFunctionality;
+import org.opendaylight.controller.sal.binding.api.BindingAwareProvider;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.AddFlowInput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.AddFlowInputBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.SalFlowService;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeContext;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeRef;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.binding.RpcService;
+
+import com.google.inject.Inject;
+
+import static org.mockito.Mockito.*;
+
+public class RoutedServiceTest extends AbstractTest {
+
+    private SalFlowService first;
+    private SalFlowService second;
+
+    private SalFlowService consumerService;
+
+    private RoutedRpcRegistration<SalFlowService> firstReg;
+    private RoutedRpcRegistration<SalFlowService> secondReg;
+
+    @Before
+    public void setUp() throws Exception {
+        first = mock(SalFlowService.class,"First Flow Service");
+        second = mock(SalFlowService.class,"Second Flow Service");
+    }
+
+    @Test
+    public void testServiceRegistration() {
+
+        assertNotNull(getBroker());
+
+        BindingAwareProvider provider1 = new AbstractTestProvider() {
+
+            @Override
+            public void onSessionInitiated(ProviderContext session) {
+                assertNotNull(session);
+                firstReg = session.addRoutedRpcImplementation(SalFlowService.class, first);
+            }
+        };
+
+        /**
+         * Register first provider, which register first implementation of 
+         * SalFlowService
+         * 
+         */
+        getBroker().registerProvider(provider1, getBundleContext());
+        assertNotNull("Registration should not be null", firstReg);
+        assertSame(first, firstReg.getInstance());
+        
+        BindingAwareProvider provider2 = new AbstractTestProvider() {
+
+            @Override
+            public void onSessionInitiated(ProviderContext session) {
+                assertNotNull(session);
+                secondReg = session.addRoutedRpcImplementation(SalFlowService.class, second);
+            }
+        };
+        getBroker().registerProvider(provider2, getBundleContext());
+        assertNotNull("Registration should not be null", firstReg);
+        assertNotSame(secondReg, firstReg);
+
+
+        BindingAwareConsumer consumer = new BindingAwareConsumer() {
+            @Override
+            public void onSessionInitialized(ConsumerContext session) {
+                consumerService = session.getRpcService(SalFlowService.class);
+            }
+        };
+        broker.registerConsumer(consumer, getBundleContext());
+
+        assertNotNull("MD-SAL instance of Flow Service should be returned", consumerService);
+        assertNotSame("Provider instance and consumer instance should not be same.", first, consumerService);
+
+        NodeRef nodeOne = createNodeRef("foo:node:1");
+
+
+        /**
+         * Provider 1 - register itself as provider for SalFlowService
+         * for nodeOne
+         */
+        firstReg.registerPath(NodeContext.class, nodeOne.getValue());
+
+        /**
+         * Consumer creates addFlow Message for node one and sends
+         * it to the MD-SAL
+         * 
+         */
+        AddFlowInput firstMessage = createSampleAddFlow(nodeOne,1);
+        consumerService.addFlow(firstMessage);
+        
+        /**
+         * We verify if implementation of first provider received same
+         * message from MD-SAL.
+         * 
+         */
+        verify(first).addFlow(firstMessage);
+        
+        /**
+         * Verifies that second instance was not invoked with first
+         * message
+         * 
+         */
+        verify(second, times(0)).addFlow(firstMessage);
+        
+        /**
+         * Second provider registers as provider for nodeTwo
+         * 
+         */
+        NodeRef nodeTwo = createNodeRef("foo:node:2");
+        secondReg.registerPath(NodeContext.class, nodeTwo.getValue());
+        
+        
+        /**
+         * Consumer sends message to nodeTwo for three times.
+         * Should be processed by second instance.
+         */
+        AddFlowInput secondMessage = createSampleAddFlow(nodeTwo,2);
+        consumerService.addFlow(secondMessage);
+        consumerService.addFlow(secondMessage);
+        consumerService.addFlow(secondMessage);
+        
+        /**
+         * We verify that second was invoked 3 times, with message
+         * two as parameter, first was invoked 0 times.
+         * 
+         */
+        verify(second, times(3)).addFlow(secondMessage);
+        verify(first, times(0)).addFlow(secondMessage);
+        
+        
+        /**
+         * First provider unregisters as implementation of FlowService
+         * for node one
+         * 
+         */
+        firstReg.unregisterPath(NodeContext.class, nodeOne.getValue());
+        
+        
+        /**
+         * Second provider registers as implementation for FlowService
+         * for node one
+         * 
+         */
+        secondReg.registerPath(NodeContext.class, nodeOne.getValue());
+        
+        /**
+         * Consumer sends third message to Node 1, should be processed
+         * by second instance.
+         * 
+         */
+        AddFlowInput thirdMessage = createSampleAddFlow(nodeOne,3);
+        consumerService.addFlow(thirdMessage);
+        
+        /**
+         * We verify that first provider was invoked 0 times,
+         * second provider 1 time.
+         */
+        verify(first,times(0)).addFlow(thirdMessage);
+        verify(second).addFlow(thirdMessage);
+        
+    }
+
+    private static NodeRef createNodeRef(String string) {
+        NodeKey key = new NodeKey(new NodeId(string));
+        InstanceIdentifier<Node> path = InstanceIdentifier.builder().node(Nodes.class).node(Node.class, key)
+                .toInstance();
+
+        return new NodeRef(path);
+    }
+
+    static AddFlowInput createSampleAddFlow(NodeRef node,int cookie) {
+        AddFlowInputBuilder ret = new AddFlowInputBuilder();
+        ret.setNode(node);
+        ret.setCookie(BigInteger.valueOf(cookie));
+        return ret.build();
+    }
+}