From e41b96adc85177b252b3a47816f9f9f6b5571362 Mon Sep 17 00:00:00 2001 From: Tony Tkacik Date: Wed, 16 Oct 2013 17:01:47 +0200 Subject: [PATCH] Updated implementation of internal RPC Router for Binding-Aware Broker and added IT to it 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 --- opendaylight/md-sal/pom.xml | 1 + .../binding/codegen/RuntimeCodeGenerator.java | 2 +- .../binding/codegen/impl/RoutingPair.xtend | 3 + .../impl/RpcRouterCodegenInstance.xtend | 52 +++++ .../codegen/impl/RpcRoutingTableImpl.xtend | 49 +++++ .../codegen/impl/RuntimeCodeGenerator.xtend | 91 ++++---- .../sal/binding/codegen/impl/XtendHelper.java | 16 ++ .../binding/impl/BindingAwareBrokerImpl.xtend | 172 +++++++++++++-- .../binding/impl/OsgiProviderContext.xtend | 18 +- .../binding/impl/osgi/ClassLoaderUtils.java | 24 +++ .../controller/sal/binding/spi/RpcRouter.java | 16 +- .../test/RuntimeCodeGeneratorTest.java | 35 +-- opendaylight/md-sal/sal-binding-it/pom.xml | 141 +++++++++++++ .../test/sal/binding/it/AbstractTest.java | 131 ++++++++++++ .../sal/binding/it/AbstractTestProvider.java | 29 +++ .../sal/binding/it/RoutedServiceTest.java | 199 ++++++++++++++++++ 16 files changed, 900 insertions(+), 79 deletions(-) create mode 100644 opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/impl/RpcRouterCodegenInstance.xtend create mode 100644 opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/impl/RpcRoutingTableImpl.xtend create mode 100644 opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/impl/XtendHelper.java create mode 100644 opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/impl/osgi/ClassLoaderUtils.java create mode 100644 opendaylight/md-sal/sal-binding-it/pom.xml create mode 100644 opendaylight/md-sal/sal-binding-it/src/test/java/org/opendaylight/controller/test/sal/binding/it/AbstractTest.java create mode 100644 opendaylight/md-sal/sal-binding-it/src/test/java/org/opendaylight/controller/test/sal/binding/it/AbstractTestProvider.java create mode 100644 opendaylight/md-sal/sal-binding-it/src/test/java/org/opendaylight/controller/test/sal/binding/it/RoutedServiceTest.java diff --git a/opendaylight/md-sal/pom.xml b/opendaylight/md-sal/pom.xml index 60e5a3daf8..478bba81c7 100644 --- a/opendaylight/md-sal/pom.xml +++ b/opendaylight/md-sal/pom.xml @@ -26,6 +26,7 @@ sal-binding-api sal-binding-broker + sal-binding-it samples diff --git a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/RuntimeCodeGenerator.java b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/RuntimeCodeGenerator.java index 0d78109a69..c8d6bcf3b1 100644 --- a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/RuntimeCodeGenerator.java +++ b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/RuntimeCodeGenerator.java @@ -84,5 +84,5 @@ public interface RuntimeCodeGenerator { * @return Instance of RpcService of provided serviceType which implements * also {@link RpcRouter} and {@link DelegateProxy} */ - T getRouterFor(Class serviceType) throws IllegalArgumentException; + RpcRouter getRouterFor(Class serviceType) throws IllegalArgumentException; } diff --git a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/impl/RoutingPair.xtend b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/impl/RoutingPair.xtend index 4324b4eb9a..f60816d954 100644 --- a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/impl/RoutingPair.xtend +++ b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/impl/RoutingPair.xtend @@ -18,4 +18,7 @@ class RoutingPair { val Class 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 index 0000000000..b255504a00 --- /dev/null +++ b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/impl/RpcRouterCodegenInstance.xtend @@ -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 implements RpcRouter { + + @Property + val T invocationProxy + + @Property + val Class rpcServiceType + + @Property + val Set> contexts + + val routingTables = new HashMap,RpcRoutingTableImpl>; + + + + @Property + var T defaultService + + new(Class type,T routerImpl,Set> 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 getRoutingTable(Class table) { + routingTables.get(table) as RpcRoutingTable + } + + override getService(Class 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 index 0000000000..02da174656 --- /dev/null +++ b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/impl/RpcRoutingTableImpl.xtend @@ -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 implements RpcRoutingTable{ + + @Property + val Class identifier; + + @Property + var S defaultRoute; + + @Property + val Map,S> routes; + + new(Class ident, Map,S> route) { + _identifier = ident + _routes = route + } + + new(Class 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 path) { + routes.remove(path); + } + + @SuppressWarnings("rawtypes") + override updateRoute(InstanceIdentifier path, S service) { + routes.put(path as InstanceIdentifier,service); + } +} \ 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/RuntimeCodeGenerator.xtend b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/impl/RuntimeCodeGenerator.xtend index 3b3f4190cf..90be6f3476 100644 --- a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/impl/RuntimeCodeGenerator.xtend +++ b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/impl/RuntimeCodeGenerator.xtend @@ -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 Class generateDirectProxy(Class iface) { + override getDirectProxyFor(Class 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 Class generateRouter(Class iface) { - val supertype = iface.asCtClass - val targetCls = createClass(iface.routerName, supertype) [ - //field(ROUTING_TABLE_FIELD,Map) - field(DELEGATE_FIELD, iface) - val contexts = new HashMap>(); - // 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 getRouterFor(Class iface) { + val contexts = new HashSet> + + val instance = 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>(); + // 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 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 index 0000000000..21b48bb475 --- /dev/null +++ b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/impl/XtendHelper.java @@ -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 RpcRoutingTableImpl createRoutingTable( + Class cls) { + return new RpcRoutingTableImpl<>(cls); + } + + public static String foo() { + return "Foo"; + } +} diff --git a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/impl/BindingAwareBrokerImpl.xtend b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/impl/BindingAwareBrokerImpl.xtend index 2bae9f515f..88e3e62f39 100644 --- a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/impl/BindingAwareBrokerImpl.xtend +++ b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/impl/BindingAwareBrokerImpl.xtend @@ -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, RpcProxyContext> managedProxies = new HashMap(); + + /** + * Map of all Managed Direct Proxies + * + */ + private val Map, RpcProxyContext> managedProxies = new ConcurrentHashMap(); + + /** + * + * Map of all available Rpc Routers + * + * + */ + private val Map, RpcRouter> rpcRouters = new ConcurrentHashMap(); + private var NotificationBrokerImpl notifyBroker private var DataBrokerImpl dataBroker private var ServiceRegistration 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 getManagedDirectProxy(Class service) { + private def getManagedDirectProxy(Class 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() - 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 registerRpcImplementation(Class type, T service, OsgiProviderContext context, Hashtable 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(type, service, osgiReg); } - def RpcRegistration registerMountedRpcImplementation(Class tyoe, T service, InstanceIdentifier identifier, - OsgiProviderContext context, Hashtable properties) { + def RpcRegistration registerMountedRpcImplementation(Class type, T service, + InstanceIdentifier identifier, OsgiProviderContext context) { throw new UnsupportedOperationException("TODO: auto-generated method stub") } - def RoutedRpcRegistration registerRoutedRpcImplementation(Class type, T service, OsgiProviderContext context, - Hashtable properties) { - throw new UnsupportedOperationException("TODO: auto-generated method stub") + def RoutedRpcRegistration registerRoutedRpcImplementation(Class type, T service, + OsgiProviderContext context) { + val router = resolveRpcRouter(type); + checkState(router !== null) + return new RoutedRpcRegistrationImpl(service, router, this) + } + + private def RpcRouter resolveRpcRouter(Class type) { + + val router = rpcRouters.get(type); + if (router !== null) { + return router as RpcRouter; + } + + // 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 void registerPath(RoutedRpcRegistrationImpl registration, + Class context, InstanceIdentifier 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 void unregisterPath(RoutedRpcRegistrationImpl registration, + Class context, InstanceIdentifier 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 void unregisterRoutedRpcService(RoutedRpcRegistrationImpl 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 extends AbstractObjectRegistration implements RoutedRpcRegistration { + + @Property + private val BindingAwareBrokerImpl broker; + + @Property + private val RpcRouter router; + + @Property + private val Multimap, InstanceIdentifier> registeredPaths = HashMultimap.create(); + + private var closed = false; + + new(T instance, RpcRouter backingRouter, BindingAwareBrokerImpl broker) { + super(instance) + _router = backingRouter; + _broker = broker; + } + + override protected removeRegistration() { + closed = true + broker.unregisterRoutedRpcService(this) + } + + override registerInstance(Class context, InstanceIdentifier instance) { + registerPath(context, instance); + } + + override unregisterInstance(Class context, InstanceIdentifier instance) { + unregisterPath(context, instance); + } + + override getService() { + return instance; + } + + override registerPath(Class context, InstanceIdentifier path) { + checkClosed() + broker.registerPath(this, context, path); + } + + override unregisterPath(Class context, InstanceIdentifier path) { + checkClosed() + broker.unregisterPath(this, context, path); + } + + private def checkClosed() { + if (closed) + throw new IllegalStateException("Registration was closed."); } } diff --git a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/impl/OsgiProviderContext.xtend b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/impl/OsgiProviderContext.xtend index 29c3845004..1be19c0213 100644 --- a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/impl/OsgiProviderContext.xtend +++ b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/impl/OsgiProviderContext.xtend @@ -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 addMountRpcImplementation(Class 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(); 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 addRoutedRpcImplementation(Class type, T implementation) throws IllegalStateException { - val properties = new Hashtable(); - 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 index 0000000000..ad0473e8a2 --- /dev/null +++ b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/impl/osgi/ClassLoaderUtils.java @@ -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 withClassLoader(ClassLoader cls,Callable 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 diff --git a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/spi/RpcRouter.java b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/spi/RpcRouter.java index b7a42eda1f..38e309f46d 100644 --- a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/spi/RpcRouter.java +++ b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/spi/RpcRouter.java @@ -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 { * @return type of RpcService which is served by this instance of router. */ Class 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 { /** * */ - void setDefaultService(); + void setDefaultService(T service); + + Set> getContexts(); } diff --git a/opendaylight/md-sal/sal-binding-broker/src/test/java/org/opendaylight/controller/sal/binding/test/RuntimeCodeGeneratorTest.java b/opendaylight/md-sal/sal-binding-broker/src/test/java/org/opendaylight/controller/sal/binding/test/RuntimeCodeGeneratorTest.java index e0808b2d82..95ac22c6b3 100644 --- a/opendaylight/md-sal/sal-binding-broker/src/test/java/org/opendaylight/controller/sal/binding/test/RuntimeCodeGeneratorTest.java +++ b/opendaylight/md-sal/sal-binding-broker/src/test/java/org/opendaylight/controller/sal/binding/test/RuntimeCodeGeneratorTest.java @@ -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 product = codeGenerator.generateDirectProxy(FooService.class); + FooService product = codeGenerator.getDirectProxyFor(FooService.class); assertNotNull(product); } @Test public void testGenerateRouter() throws Exception { - Class product = codeGenerator.generateRouter(FooService.class); + RpcRouter 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,FooService> routingTable = new HashMap<>(); - setRoutingTable(product, BaseIdentity.class, routingTable); + private void verifyRouting(RpcRouter 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 routingTable = product.getRoutingTable(BaseIdentity.class); int servicesCount = 2; int instancesPerService = 3; @@ -70,11 +72,11 @@ public class RuntimeCodeGeneratorTest { for(int i = 0;i 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 index 0000000000..86b6626303 --- /dev/null +++ b/opendaylight/md-sal/sal-binding-it/pom.xml @@ -0,0 +1,141 @@ + + 4.0.0 + + sal-parent + org.opendaylight.controller + 1.0-SNAPSHOT + + sal-binding-it + + scm:git:ssh://git.opendaylight.org:29418/controller.git + scm:git:ssh://git.opendaylight.org:29418/controller.git + https://wiki.opendaylight.org/view/OpenDaylight_Controller:MD-SAL + + + + 3.0.0 + 1.5.0 + + + + + + org.ops4j.pax.exam + maven-paxexam-plugin + 1.2.4 + + + generate-config + + generate-depends-file + + + + + + + + + + org.eclipse.m2e + lifecycle-mapping + 1.0.0 + + + + + + + org.ops4j.pax.exam + + + maven-paxexam-plugin + + + [1.2.4,) + + + + generate-depends-file + + + + + + + + + + + + + + + + + + org.opendaylight.yangtools.thirdparty + xtend-lib-osgi + 2.4.3 + test + + + org.opendaylight.controller + sal-binding-broker-impl + 1.0-SNAPSHOT + provided + + + org.ops4j.pax.exam + pax-exam-container-native + ${exam.version} + test + + + org.ops4j.pax.exam + pax-exam-junit4 + ${exam.version} + test + + + org.ops4j.pax.exam + pax-exam-link-mvn + ${exam.version} + test + + + equinoxSDK381 + org.eclipse.osgi + 3.8.1.v20120830-144521 + test + + + org.slf4j + log4j-over-slf4j + 1.7.2 + + + ch.qos.logback + logback-core + 1.0.9 + + + ch.qos.logback + logback-classic + 1.0.9 + + + org.mockito + mockito-all + test + + + org.opendaylight.controller.model + model-flow-service + 1.0-SNAPSHOT + provided + + + 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 index 0000000000..cddf7e8330 --- /dev/null +++ b/opendaylight/md-sal/sal-binding-it/src/test/java/org/opendaylight/controller/test/sal/binding/it/AbstractTest.java @@ -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 index 0000000000..6f1551f12d --- /dev/null +++ b/opendaylight/md-sal/sal-binding-it/src/test/java/org/opendaylight/controller/test/sal/binding/it/AbstractTestProvider.java @@ -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 getImplementations() { + return Collections.emptySet(); + } + + @Override + public Collection 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 index 0000000000..ef60a2c15c --- /dev/null +++ b/opendaylight/md-sal/sal-binding-it/src/test/java/org/opendaylight/controller/test/sal/binding/it/RoutedServiceTest.java @@ -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 firstReg; + private RoutedRpcRegistration 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 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(); + } +} -- 2.36.6