From a8b176a3ee608fb59a9c23f53a13d3090f4de2e9 Mon Sep 17 00:00:00 2001 From: Tony Tkacik Date: Wed, 25 Jun 2014 10:47:02 +0200 Subject: [PATCH] Bug 1236 - Documented Binding-aware RPC services of MD-SAL Documented Binding-aware RPC API contracts Added simple example / tutorial for routed RPCs. Change-Id: I0f3a08a392e148732dc218a8ac67bc6f90ea7b0e Signed-off-by: Tony Tkacik --- .../sal/binding/api/BindingAwareBroker.java | 15 ++ .../sal/binding/api/RpcConsumerRegistry.java | 56 +++- .../sal/binding/api/RpcProviderRegistry.java | 253 ++++++++++++++++-- .../sal/common/api/routing/RouteChange.java | 29 +- .../api/routing/RouteChangeListener.java | 17 +- .../api/routing/RouteChangePublisher.java | 6 + .../api/routing/RoutedRegistration.java | 22 ++ 7 files changed, 372 insertions(+), 26 deletions(-) diff --git a/opendaylight/md-sal/sal-binding-api/src/main/java/org/opendaylight/controller/sal/binding/api/BindingAwareBroker.java b/opendaylight/md-sal/sal-binding-api/src/main/java/org/opendaylight/controller/sal/binding/api/BindingAwareBroker.java index 453ff44911..a41186dae1 100644 --- a/opendaylight/md-sal/sal-binding-api/src/main/java/org/opendaylight/controller/sal/binding/api/BindingAwareBroker.java +++ b/opendaylight/md-sal/sal-binding-api/src/main/java/org/opendaylight/controller/sal/binding/api/BindingAwareBroker.java @@ -156,11 +156,26 @@ public interface BindingAwareBroker { void unregisterFunctionality(ProviderFunctionality functionality); } + /** + * Represents an RPC implementation registration. Users should call the + * {@link ObjectRegistration#close close} method when the registration is no longer needed. + * + * @param the implemented RPC service interface + */ public interface RpcRegistration extends ObjectRegistration { + /** + * Returns the implemented RPC service interface. + */ Class getServiceType(); } + /** + * Represents a routed RPC implementation registration. Users should call the + * {@link RoutedRegistration#close close} method when the registration is no longer needed. + * + * @param the implemented RPC service interface + */ public interface RoutedRpcRegistration extends RpcRegistration, RoutedRegistration, InstanceIdentifier, T> { diff --git a/opendaylight/md-sal/sal-binding-api/src/main/java/org/opendaylight/controller/sal/binding/api/RpcConsumerRegistry.java b/opendaylight/md-sal/sal-binding-api/src/main/java/org/opendaylight/controller/sal/binding/api/RpcConsumerRegistry.java index 7da0a48517..615acd3195 100644 --- a/opendaylight/md-sal/sal-binding-api/src/main/java/org/opendaylight/controller/sal/binding/api/RpcConsumerRegistry.java +++ b/opendaylight/md-sal/sal-binding-api/src/main/java/org/opendaylight/controller/sal/binding/api/RpcConsumerRegistry.java @@ -10,16 +10,60 @@ package org.opendaylight.controller.sal.binding.api; import org.opendaylight.yangtools.yang.binding.RpcService; /** - * Base interface defining contract for retrieving MD-SAL - * version of RpcServices + * Provides access to registered Remote Procedure Call (RPC) service implementations. The RPCs are + * defined in YANG models. + *

+ * RPC implementations are registered using the {@link RpcProviderRegistry}. * */ public interface RpcConsumerRegistry extends BindingAwareService { /** - * Returns a session specific instance (implementation) of requested - * YANG module implementation / service provided by consumer. + * Returns an implementation of a requested RPC service. * - * @return Session specific implementation of service + *

+ * The returned instance is not an actual implementation of the RPC service + * interface, but a proxy implementation of the interface that forwards to + * an actual implementation, if any. + *

+ * + * The following describes the behavior of the proxy when invoking RPC methods: + *

    + *
  • If an actual implementation is registered with the MD-SAL, all invocations are + * forwarded to the registered implementation.
  • + *
  • If no actual implementation is registered, all invocations will fail by + * throwing {@link IllegalStateException}.
  • + *
  • Prior to invoking the actual implementation, the method arguments are are validated. + * If any are invalid, an {@link IllegalArgumentException} is thrown. + *
+ * + * The returned proxy is automatically updated with the most recent + * registered implementation. + *

+ * The generated RPC method APIs require implementors to return a {@link java.util.concurrent.Future Future} + * instance that wraps the {@link org.opendaylight.yangtools.yang.common.RpcResult RpcResult}. Since + * RPC methods may be implemented asynchronously, callers should avoid blocking on the + * {@link java.util.concurrent.Future Future} result. Instead, it is recommended to use + * {@link com.google.common.util.concurrent.JdkFutureAdapters#listenInPoolThread(java.util.concurrent.Future)} + * or {@link com.google.common.util.concurrent.JdkFutureAdapters#listenInPoolThread(java.util.concurrent.Future, java.util.concurrent.Executor)} + * to listen for Rpc Result. This will asynchronously listen for future result in executor and + * will not block current thread. + * + *

+     *   final Future> future = someRpcService.someRpc( ... );
+     *   Futures.addCallback(JdkFutureAdapters.listenInThreadPool(future), new FutureCallback>() {
+     *
+     *       public void onSuccess(RpcResult result) {
+     *          // process result ...
+     *       }
+     *
+     *       public void onFailure(Throwable t) {
+     *          // RPC failed
+     *       }
+     *   );
+     * 
+ * @param serviceInterface the interface of the RPC Service. Typically this is an interface generated + * from a YANG model. + * @return the proxy for the requested RPC service. This method never returns null. */ - T getRpcService(Class module); + T getRpcService(Class serviceInterface); } diff --git a/opendaylight/md-sal/sal-binding-api/src/main/java/org/opendaylight/controller/sal/binding/api/RpcProviderRegistry.java b/opendaylight/md-sal/sal-binding-api/src/main/java/org/opendaylight/controller/sal/binding/api/RpcProviderRegistry.java index cdf55844b3..22db985ba9 100644 --- a/opendaylight/md-sal/sal-binding-api/src/main/java/org/opendaylight/controller/sal/binding/api/RpcProviderRegistry.java +++ b/opendaylight/md-sal/sal-binding-api/src/main/java/org/opendaylight/controller/sal/binding/api/RpcProviderRegistry.java @@ -15,39 +15,256 @@ import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.opendaylight.yangtools.yang.binding.RpcService; /** - * Interface defining provider's access to the Rpc Registry which could be used - * to register their implementations of service to the MD-SAL. + * Provides a registry for Remote Procedure Call (RPC) service implementations. The RPCs are + * defined in YANG models. + *

+ * There are 2 types of RPCs: + *

    + *
  • Global
  • + *
  • Routed
  • + *
* - * @author ttkacik + *

Global RPC

+ *

+ * An RPC is global if there is intended to be only 1 registered implementation. A global RPC is not + * explicitly declared as such, essentially any RPC that is not defined to be routed is considered global. + *

+ * Global RPCs are registered using the + * {@link #addRpcImplementation(Class, RpcService)} method. * + *

Routed RPC

+ *

+ * MD-SAL supports routing of RPC between multiple implementations where the appropriate + * implementation is selected at run time based on the content of the RPC message as described in + * YANG model. + *

+ * RPC routing is based on: + *

    + *
  • Route identifier - + * An {@link org.opendaylight.yangtools.yang.binding.InstanceIdentifier InstanceIdentifier} value + * which is part of the RPC input. This value is used to select the correct + * implementation at run time.
  • + *
  • Context Type - A YANG-defined construct which constrains the subset of + * valid route identifiers for a particular RPC.
  • + *
+ * + *

Context type

+ *

+ * A context type is modeled in YANG using a combination of a YANG identity + * and Opendaylight specific extensions from yang-ext module. These extensions are: + *

    + *
  • context-instance - This is used in the data tree part of a YANG model to + * define a context type that associates nodes with a specified context identity. + * Instance identifiers that reference these nodes are valid route identifiers for RPCs that + * reference this context type.
  • + *
  • context-reference - This is used in RPC input to mark a leaf of type + * instance-identifier as a reference to the particular context type defined by the + * specified context identity. The value of this + * leaf is used by the RPC broker at run time to route the RPC request to the correct implementation. + * Note that context-reference may only be used on leaf elements of type + * instance-identifier or a type derived from instance-identifier.
  • + *
+ * + * + *

Routed RPC example

+ *

+ *

1. Defining a Context Type
+ *

+ * The following snippet declares a simple YANG identity named example-context: + * + *

+ * module example {
+ *     ...
+ *     identity example-context {
+ *          description "Identity used to define an example-context type";
+ *     }
+ *     ...
+ * }
+ * 
+ *

+ * We then use the declared identity to define a context type by using it in combination + * with the context-instance YANG extension. We'll associate the context type + * with a list element in the data tree. This defines the set of nodes whose instance + * identifiers are valid for the example-context context type. + *

+ * The following YANG snippet imports the yang-ext module and defines the list + * element named item inside a container named foo: + * + *

+ * module foo {
+ *     ...
+ *     import yang-ext {prefix ext;}
+ *     ...
+ *     container foo {
+ *          list item {
+ *              key "id";
+ *              leaf id {type string;}
+ *              ext:context-instance "example-context";
+ *          }
+ *     }
+ *     ...
+ * }
+ * 
+ *

+ * The statement ext:context-instance "example-context"; inside the list element + * declares that any instance identifier referencing item in the data + * tree is valid for example-context. For example, the following instance + * identifier: + *

+ *     InstanceIdentifier.create(Foo.class).child(Item.class,new ItemKey("Foo"))
+ * 
+ * is valid for example-context. However the following: + *
+ *     InstanceIdentifier.create(Example.class)
+ * 
+ * is not valid. + *

+ * So using an identity in combination with context-instance we + * have effectively defined a context type that can be referenced in a YANG RPC input. + * + *

2. Defining an RPC to use the Context Type
+ *

+ * To define an RPC to be routed based on the context type we need to add an input leaf element + * that references the context type which will hold an instance identifier value to be + * used to route the RPC. + *

+ * The following snippet defines an RPC named show-item with 2 leaf elements + * as input: item of type instance-identifier and description: + * + *

+ * module foo {
+ *      ...
+ *      import yang-ext {prefix ext;}
+ *      ...
+ *      rpc show-item {
+ *          input {
+ *              leaf item {
+ *                  type instance-identifier;
+ *                  ext:context-reference example-context;
+ *              }
+ *              leaf description {
+ *                  type "string";
+ *              }
+ *          }
+ *      }
+ * }
+ * 
+ *

+ * We mark the item leaf with a context-reference statement that + * references the example-context context type. RPC calls will then be routed + * based on the instance identifier value contained in item. Only instance + * identifiers that point to a foo/item node are valid as input. + *

+ * The generated RPC Service interface for the module is: + * + *

+ * interface FooService implements RpcService {
+ *      Future<RpcResult<Void>> showItem(ShowItemInput input);
+ * }
+ * 
+ *

+ * For constructing the RPC input, there are generated classes ShowItemInput and ShowItemInputBuilder. + * + *

3. Registering a routed RPC implementation
+ *

+ * To register a routed implementation for the show-item RPC, we must use the + * {@link #addRoutedRpcImplementation(Class, RpcService)} method. This + * will return a {@link RoutedRpcRegistration} instance which can then be used to register / + * unregister routed paths associated with the registered implementation. + *

+ * The following snippet registers myImpl as the RPC implementation for an + * item with key "foo": + *

+ * // Create the instance identifier path for item "foo"
+ * InstanceIdentifier path = InstanceIdentifier.create(Foo.class).child(Item.class, new ItemKey("foo"));
+ *
+ * // Register myImpl as the implementation for the FooService RPC interface
+ * RoutedRpcRegistration reg = rpcRegistry.addRoutedRpcImplementation(FooService.class, myImpl);
+ *
+ * // Now register for the context type and specific path ID. The context type is specified by the
+ * // YANG-generated class for the example-context identity.
+ * reg.registerPath(ExampleContext.class, path);
+ * 
+ *

+ * It is also possible to register the same implementation for multiple paths: + * + *

+ * InstanceIdentifier one = InstanceIdentifier.create(Foo.class).child(Item.class, new ItemKey("One"));
+ * InstanceIdentifier two = InstanceIdentifier.create(Foo.class).child(Item.class, new ItemKey("Two"));
+ *
+ * RoutedRpcRegistration reg = rpcRegistry.addRoutedRpcImplementation(FooService.class, myImpl);
+ * reg.registerPath(ExampleContext.class, one);
+ * reg.registerPath(ExampleContext.class, two);
+ * 
+ * + *

+ * When another client invokes the showItem(ShowItemInput) method on the proxy instance + * retrieved via {@link RpcConsumerRegistry#getRpcService(Class)}, the proxy will inspect the + * arguments in ShowItemInput, extract the InstanceIdentifier value of the item leaf and select + * the implementation whose registered path matches the InstanceIdentifier value of the item leaf. + * + *

Notes for RPC Implementations

+ * + *

RpcResult

+ *

+ * The generated interfaces require implementors to return + * {@link java.util.concurrent.Future Future}<{@link org.opendaylight.yangtools.yang.common.RpcResult RpcResult}<{RpcName}Output>> instances. + * + * Implementations should do processing of RPC calls asynchronously and update the + * returned {@link java.util.concurrent.Future Future} instance when processing is complete. + * However using {@link com.google.common.util.concurrent.Futures#immediateFuture(Object) Futures.immediateFuture} + * is valid only if the result is immediately available and asynchronous processing is unnecessary and + * would only introduce additional complexity. + * + *

+ * The {@link org.opendaylight.yangtools.yang.common.RpcResult RpcResult} is a generic + * wrapper for the RPC output payload, if any, and also allows for attaching error or + * warning information (possibly along with the payload) should the RPC processing partially + * or completely fail. This is intended to provide additional human readable information + * for users of the API and to transfer warning / error information across the system + * so it may be visible via other external APIs such as Restconf. + *

+ * It is recommended to use the {@link org.opendaylight.yangtools.yang.common.RpcResult RpcResult} + * for conveying appropriate error information + * on failure rather than purposely throwing unchecked exceptions if at all possible. + * While unchecked exceptions will fail the returned {@link java.util.concurrent.Future Future}, + * using the intended RpcResult to convey the error information is more user-friendly. */ public interface RpcProviderRegistry extends // RpcConsumerRegistry, // RouteChangePublisher> { /** - * Registers a global RpcService implementation. + * Registers a global implementation of the provided RPC service interface. + * All methods of the interface are required to be implemented. + * + * @param serviceInterface the YANG-generated interface of the RPC Service for which to register. + * @param implementation "the implementation of the RPC service interface. + * @return an RpcRegistration instance that should be used to unregister the RPC implementation + * when no longer needed by calling {@link RpcRegistration#close()}. * - * @param type - * @param implementation - * @return + * @throws IllegalStateException + * if the supplied RPC interface is a routed RPC type. */ - RpcRegistration addRpcImplementation(Class type, T implementation) + RpcRegistration addRpcImplementation(Class serviceInterface, T implementation) throws IllegalStateException; /** + * Registers an implementation of the given routed RPC service interface. + *

+ * See the {@link RpcProviderRegistry class} documentation for information and example on + * how to use routed RPCs. * - * Register a Routed RpcService where routing is determined on annotated - * (in YANG model) context-reference and value of annotated leaf. - * - * @param type - * Type of RpcService, use generated interface class, not your - * implementation class - * @param implementation - * Implementation of RpcService - * @return Registration object for routed Rpc which could be used to unregister + * @param serviceInterface the YANG-generated interface of the RPC Service for which to register. + * @param implementation the implementation instance to register. + * @return a RoutedRpcRegistration instance which can be used to register paths for the RPC + * implementation via invoking {@link RoutedRpcRegistration#registerPath(....). + * {@link RoutedRpcRegistration#close()} should be called to unregister the implementation + * and all previously registered paths when no longer needed. * * @throws IllegalStateException + * if the supplied RPC interface is not a routed RPC type. */ - RoutedRpcRegistration addRoutedRpcImplementation(Class type, T implementation) + RoutedRpcRegistration addRoutedRpcImplementation(Class serviceInterface, + T implementation) throws IllegalStateException; } diff --git a/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/routing/RouteChange.java b/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/routing/RouteChange.java index 5f84ec579d..0c04b936b6 100644 --- a/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/routing/RouteChange.java +++ b/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/routing/RouteChange.java @@ -9,9 +9,36 @@ package org.opendaylight.controller.md.sal.common.api.routing; import java.util.Map; import java.util.Set; - +/** + * Event representing change in RPC routing table. + * + * + * @param Type, which is used to represent Routing context. + * @param

Type of data tree path, which is used to identify route. + */ public interface RouteChange { + /** + * + * Returns a map of removed routes in associated routing contexts. + *

+ * This map represents routes, which were withdrawn from broker local + * routing table and broker may need to forward RPC to other broker + * in order to process RPC request. + * + * @return Map of contexts and removed routes + */ Map> getRemovals(); + /** + * + * Returns a map of announced routes in associated routing contexts. + * + * This map represents routes, which were announced by broker + * and are present in broker's local routing table. This routes + * are processed by implementations which are registered + * to originating broker. + * + * @return Map of contexts and announced routes + */ Map> getAnnouncements(); } diff --git a/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/routing/RouteChangeListener.java b/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/routing/RouteChangeListener.java index 62206013f8..b3b6fe6ee9 100644 --- a/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/routing/RouteChangeListener.java +++ b/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/routing/RouteChangeListener.java @@ -8,8 +8,23 @@ package org.opendaylight.controller.md.sal.common.api.routing; import java.util.EventListener; - +/** + * + * Listener which is interested in receiving RouteChangeEvents + * for its local broker. + *

+ * Listener is registerd via {@link RouteChangePublisher#registerRouteChangeListener(RouteChangeListener)} + * + * + * @param Type, which is used to represent Routing context. + * @param

Type of data tree path, which is used to identify route. + */ public interface RouteChangeListener extends EventListener { + /** + * Callback which is invoked if there is an rpc routing table change. + * + * @param change Event representing change in local RPC routing table. + */ void onRouteChange(RouteChange change); } diff --git a/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/routing/RouteChangePublisher.java b/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/routing/RouteChangePublisher.java index 7bf61fab0b..dc6b6dd3b7 100644 --- a/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/routing/RouteChangePublisher.java +++ b/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/routing/RouteChangePublisher.java @@ -9,6 +9,12 @@ package org.opendaylight.controller.md.sal.common.api.routing; import org.opendaylight.yangtools.concepts.ListenerRegistration; +/** + * Publishes changes in local RPC routing table to registered listener. + * + * @param Type, which is used to represent Routing context. + * @param

Type of data tree path, which is used to identify route. + */ public interface RouteChangePublisher { > ListenerRegistration registerRouteChangeListener(L listener); diff --git a/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/routing/RoutedRegistration.java b/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/routing/RoutedRegistration.java index 6ce7b5a5c7..6fe8d69217 100644 --- a/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/routing/RoutedRegistration.java +++ b/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/routing/RoutedRegistration.java @@ -10,9 +10,31 @@ package org.opendaylight.controller.md.sal.common.api.routing; import org.opendaylight.yangtools.concepts.Path; import org.opendaylight.yangtools.concepts.Registration; +/** + * Base interface for a routed RPC RPC implementation registration. + * + * @param the context type used for routing + * @param

the path identifier type + * @param the RPC implementation type + */ public interface RoutedRegistration, S> extends Registration { + /** + * Registers the RPC implementation associated with this registration for the given path + * identifier and context. + * + * @param context the context used for routing RPCs to this implementation. + * @param path the path identifier for which to register. + */ void registerPath(C context, P path); + + /** + * Unregisters the RPC implementation associated with this registration for the given path + * identifier and context. + * + * @param context the context used for routing RPCs to this implementation. + * @param path the path identifier for which to unregister. + */ void unregisterPath(C context, P path); @Override -- 2.36.6