Merge topic 'controller'
authorColin Dixon <colin@colindixon.com>
Tue, 2 Jun 2015 02:52:57 +0000 (02:52 +0000)
committerGerrit Code Review <gerrit@opendaylight.org>
Tue, 2 Jun 2015 02:52:58 +0000 (02:52 +0000)
* changes:
  Added MD-SAL RPC Routing section.
  Added data transaction API explanation

manuals/developer-guide/src/main/asciidoc/controller/controller.adoc
manuals/developer-guide/src/main/asciidoc/controller/md-sal-data-tx.adoc [new file with mode: 0644]
manuals/developer-guide/src/main/asciidoc/controller/md-sal-rpc-routing.adoc [new file with mode: 0644]

index e8b49a124033f309412d87d5c1e0954ed04bc091..8bcbd57c216f4b25f328d510d6eab35ca2dfc7b4 100644 (file)
@@ -43,6 +43,10 @@ following model-driven protocols:
 
 include::md-sal-overview.adoc[MD-SAL]
 
+include::md-sal-data-tx.adoc[]
+
+include::md-sal-rpc-routing.adoc[MD-SAL Rpc Routing]
+
 include::restconf.adoc[RESTCONF]
 
 include::config.adoc[Config Subsystem]
diff --git a/manuals/developer-guide/src/main/asciidoc/controller/md-sal-data-tx.adoc b/manuals/developer-guide/src/main/asciidoc/controller/md-sal-data-tx.adoc
new file mode 100644 (file)
index 0000000..4684022
--- /dev/null
@@ -0,0 +1,423 @@
+=== MD-SAL Data Transactions
+
+MD-SAL *Data Broker* provides transactional access to conceptual *data trees*
+representing configuration and operational state.
+
+NOTE: *Data tree* usually represents state of the modeled data, usually this
+      is state of controller, applications and also external systems (network
+      devices).
+
+*Transactions* provides *<<_transaction_isolation, stable and isolated view>>*
+from other currently running transactions. The state of running transaction and
+underlying data tree is not affected by other concurrently running transactions.
+
+.Transaction Types
+Write-Only::
+    Transaction provides only modification capabilities, but does not provide
+    read capabilities. Write-only transaction is allocated using
+    `newWriteOnlyTransaction()`.
++
+NOTE: This allows less state tracking for
+      write-only transactions and allows MD-SAL Clustering to optimize
+      internal representation of transaction in cluster.
+Read-Write::
+    Transaction provides both read and write capabilities. It is allocated using
+    `newReadWriteTransaction()`.
+Read-Only::
+    Transaction provides stable read-only view based on current data tree.
+    Read-only view is not affected by any subsequent write transactions.
+    Read-only transaction is allocated using `newReadOnlyTransaction()`.
++
+NOTE: If application needs to observe changes itself in data tree, it should use
+*data tree listeners* instead of read-only transactions and polling data tree.
+
+Transactions may be allocated using *data broker* itself or using
+*transaction chain*. In the case of *transaction chain*, new allocated transaction
+is not based on current state of data tree, but rather on state introduced by
+previous transaction from same chain, even if the commit for previous transaction
+did not yet occured (but transaction was submitted).
+
+
+==== Write-Only & Read-Write Transaction
+
+Write-Only and Read-Write transaction provides modification capabilities for
+conceptual data trees.
+
+.Usual workflow for data tree modifications
+1. application allocates new transactions using `newWriteOnlyTransaction()`
+   or `newReadWriteTransaction()`.
+2. application <<_modification_of_data_tree,modifies data tree>> using `put`,
+   `merge` and `delete`.
+3. application finishes transaction using <<_submitting_transaction,`submit()`>>,
+   which seals transaction and submits it to be processed.
+4. application observes result of transaction commit using blocking or asynchronous
+   way.
+
+The *initial state* of the write transaction is *stable snapshot* of current
+data tree state captured when transaction was created and it's state and
+underlying data tree are not affected by other concurrently running transactions.
+
+Write transactions are *isolated* from other concurrent write transactions. All
+*<<_transaction_local_state,writes are local>>* to the transaction and
+represents only a *proposal of state change* for data tree and *are not visible*
+to any other concurrently running transactions (including read-only transactions).
+
+The transaction *<<_commit_failure_scenarios,commit may fail>>* due to failing
+verification of data or concurrent transaction modifying and affected data
+in an incompatible way.
+
+===== Modification of Data Tree
+
+Write-only and read-write transaction provides following methods to modify
+data tree:
+
+put::
++
+[source, java]
+----
+<T> void put(LogicalDatastoreType store, InstanceIdentifier<T> path, T data);
+----
++
+Stores a piece of data at a specified path. This acts as an *add / replace*
+operation, which is to say that whole subtree will be replaced by the
+specified data.
+
+
+merge::
++
+[source, java]
+----
+<T> void merge(LogicalDatastoreType store, InstanceIdentifier<T> path, T data);
+----
++
+Merges a piece of data with the existing data at a specified path.
+Any *pre-existing data* which are not explicitly overwritten *will be preserved*.
+This means that if you store a container, its child subtrees will be merged.
+
+delete::
++
+[source, java]
+----
+void delete(LogicalDatastoreType store, InstanceIdentifier<?> path);
+----
++
+Removes a whole subtree from a specified path.
+
+===== Submitting transaction
+
+Transaction is submitted to be processed and commited using following method:
+
+[source, java]
+----
+CheckedFuture<Void,TransactionCommitFailedException> submit();
+----
+
+Applications publishes the changes proposed in the transaction by calling `submit()`
+on the transaction.
+This *seals the transaction* (preventing any further writes using this transaction)
+and submits it to be processed and applied to global conceptual data tree.
+Method `submit()` does not block, but rather returns `ListenableFuture`, which
+will complete successfully once processing of transaction is finished and changes
+are applied to data tree. If *commit* of data failed, future will fail with
+`TransactionFailedException`.
+
+Application may listen on commit state asynchronously using `ListenableFuture`.
+
+[source, java]
+----
+Futures.addCallback( writeTx.submit(), new FutureCallback<Void>() { <1>
+        public void onSuccess( Void result ) { // <2>
+            LOG.debug("Transaction commited successfuly.");
+        }
+
+        public void onFailure( Throwable t ) { // <3>
+            LOG.error("Commit failed.",e);
+        }
+    });
+----
+
+<1> Submits `writeTx` and registers application provided `FutureCallback`
+    on returned future.
+<2> Invoked when future completed successfully - transaction `writeTx` was
+    successfully commited to data tree.
+<3> Invoked when future failed - commit of transaction `writeTx` failed.
+    Supplied exception provides additional details and cause of failure.
+
+If application need to block till commit is finished it may use `checkedGet()`
+to wait till commit is finished.
+
+[source, java]
+----
+try {
+    writeTx.submit().checkedGet(); // <1>
+} catch (TransactionCommitFailedException e) { // <2>
+    LOG.error("Commit failed.",e);
+}
+----
+
+<1> Submits `writeTx` and blocks till commit of `writeTx` is finished. If
+    commit fails `TransactionCommitFailedException` will be thrown.
+<2> Catches `TransactionCommitFailedException` and logs it.
+
+===== Transaction local state
+
+Read-Write transaction maintains transaction-local state, which renders all
+modifications as if they happened, but this is only local to transaction.
+
+Reads from the transaction returns data as if the previous modifications in
+transaction already happened.
+
+Let assume initial state of data tree for `PATH` is `A`.
+[source, java]
+----
+ReadWriteTransaction rwTx = broker.newReadWriteTransaction(); // <1>
+
+rwRx.read(OPERATIONAL,PATH).get(); // <2>
+rwRx.put(OPERATIONAL,PATH,B); // <3>
+rwRx.read(OPERATIONAL,PATH).get(); // <4>
+rwRx.put(OPERATIONAL,PATH,C); // <5>
+rwRx.read(OPERATIONAL,PATH).get(); // <6>
+
+----
+
+<1> Allocates new `ReadWriteTransaction`.
+<2> Read from `rwTx` will return value `A` for `PATH`.
+<3> Writes value `B` to `PATH` using `rwTx`.
+<4> Read will return value `B` for `PATH`, since previous write occurred in same
+    transaction.
+<5> Writes value `C` to `PATH` using `rwTx`.
+<6> Read will return value `C` for `PATH`, since previous write occurred in same
+    transaction.
+
+==== Transaction isolation
+
+Running (not submitted) transactions are isolated from each other and changes
+done in one transaction are not observable in other currently running
+transaction.
+
+Lets assume initial state of data tree for `PATH` is `A`.
+
+[source, java]
+----
+ReadOnlyTransaction txRead = broker.newReadOnlyTransaction(); // <1>
+ReadWriteTransaction txWrite = broker.newReadWriteTransaction(); // <2>
+
+txRead.read(OPERATIONAL,PATH).get(); // <3>
+txWrite.put(OPERATIONAL,PATH,B); // <4>
+txWrite.read(OPERATIONAL,PATH).get(); // <5>
+txWrite.submit().get(); // <6>
+txRead.read(OPERATIONAL,PATH).get(); // <7>
+txAfterCommit = broker.newReadOnlyTransaction(); // <8>
+txAfterCommit.read(OPERATIONAL,PATH).get(); // <9>
+----
+
+<1> Allocates read only transaction, which is based on data tree which
+    contains value  `A` for `PATH`.
+<2> Allocates read write transaction, which is based on data tree which
+    contains value `A` for `PATH`.
+<3> Read from read-only transaction returns value `A` for `PATH`.
+<4> Data tree is updated using read-write transaction, `PATH` contains `B`.
+    Change is not public and only local to transaction.
+<5> Read from read-write transaction returns value `B` for `PATH`.
+<6> Submits changes in read-write transaction to be commited to data tree.
+    Once commit will finish, changes will be published and `PATH` will be
+    updated for value `B`. Previously allocated transactions are not affected by
+    this change.
+<7> Read from previously allocated read-only transaction still returns value `A`
+    for `PATH`, since it provides stable and isolated view.
+<8> Allocates new read-only transaction, which is based on data tree,
+    which contains value `B` for `PATH`.
+<9> Read from new read-only transaction return value `B` for `PATH` since
+    read-write transaction was commited.
+
+NOTE: Examples contains blocking calls on future only to illustrate
+that action happened after other asynchronous action. Use of blocking call
+`ListenableFuture#get()` is discouraged for most use-cases and you should use
+`Futures#addCallback(ListenableFuture, FutureCallback)` to listen asynchronously
+for result.
+
+
+==== Commit failure scenarios
+
+Transaction commit may fail because of following reasons:
+
+Optimistic Lock Failure::
+Another transaction finished earlier and *modified the same node in a
+non-compatible way*. Commit (and the returned future) will fail
+with an `OptimisticLockFailedException`.
++
+It is the responsibility of the
+caller to create a new transaction and submit the same modification again in
+order to update data tree.
++
+[WARNING]
+====
+`OptimisticLockFailedException` usually exposes *multiple writers* to
+the same data subtree, which may conflict on same resources.
+
+In most cases, retrying may result in a probability of success.
+
+There are scenarios, albeit unusual, where any number of retries will
+not succeed. Therefore it is strongly recommended to limit the number of
+retries (2 or 3) to avoid an endless loop.
+====
+
+Data Validation::
+Data change introduced by this transaction *did not pass validation* by
+commit handlers or data was incorrectly structured. Returned future will
+fail with a `DataValidationFailedException`. User *should not retry* to
+create new transaction with same data, since it probably will fail again.
+
+====== Example conflict of two transactions
+
+This example illustrates two concurrent transactions, which derived from
+same initial state of data tree and proposes conflicting modifications.
+
+[source, java]
+----
+WriteTransaction txA = broker.newWriteTransaction();
+WriteTransaction txB = broker.newWriteTransaction();
+
+txA.put(CONFIGURATION, PATH, A);    // <1>
+txB.put(CONFIGURATION, PATH, B);     // <2>
+
+CheckedFuture<?,?> futureA = txA.submit(); // <3>
+CheckedFuture<?,?> futureB = txB.submit(); // <4>
+----
+
+<1> Updates `PATH` to value `A` using `txA`
+<2> Updates `PATH` to value `B` using `txB`
+<3> Seals & submits `txA`. Commit will be processed asynchronously and
+    data tree will be updated to contain value `A` for `PATH`.
+    Returned {@link ListenableFuture} will complete successfully once
+    state is applied to data tree.
+<4> Seals & submits `txB`. Commit of `txB` will fail, because previous transaction
+    also modified path in a concurrent way. The state introduced by `txB` will
+    not be applied. Returned `ListenableFuture` will fail
+    with `OptimisticLockFailedException` exception, which indicates to client
+    that concurrent transaction prevented the submitted transaction from being
+    applied.
+
+===== Example asynchronous retry-loop
+
+[source, java]
+----
+private void doWrite( final int tries ) {
+    WriteTransaction writeTx = dataBroker.newWriteOnlyTransaction();
+
+    MyDataObject data = ...;
+    InstanceIdentifier<MyDataObject> path = ...;
+    writeTx.put( LogicalDatastoreType.OPERATIONAL, path, data );
+
+    Futures.addCallback( writeTx.submit(), new FutureCallback<Void>() {
+        public void onSuccess( Void result ) {
+            // succeeded
+        }
+
+        public void onFailure( Throwable t ) {
+            if( t instanceof OptimisticLockFailedException && (( tries - 1 ) > 0)) {
+                doWrite( tries - 1 );
+            }
+        }
+      });
+}
+...
+doWrite( 2 );
+----
+
+==== Concurrent change compatibility
+
+There are several sets of changes which could be considered incompatible
+between two transactions which are derived from same initial state.
+Rules for conflict detection applies recursively for each subtree
+level.
+
+Following table shows  state changes and failures between two concurrent
+transactions, which are based on same initial state, `tx1` is submitted before
+`tx2`.
+
+// FIXME: Providing model and concrete data structures will be probably better.
+
+INFO: Following tables stores numeric values and shows data using `toString()`
+to simplify examples.
+
+.Concurrent change resolution for leaves and leaf-list items
+[cols=",,,",options="header",]
+|===========================================================
+|Initial state | tx1  | tx2 | Observable Result
+|Empty |`put(A,1)` |`put(A,2)` |`tx2` will fail, value of `A` is `1`
+|Empty |`put(A,1)` |`merge(A,2)` |value of `A` is `2`
+|Empty |`merge(A,1)` |`put(A,2)` |`tx2` will fail, value of `A` is `1`
+|Empty |`merge(A,1)` |`merge(A,2)` |`A` is `2`
+|A=0 |`put(A,1)` |`put(A,2)` |`tx2` will fail, `A` is `1`
+|A=0 |`put(A,1)` |`merge(A,2)` |`A` is `2`
+|A=0 |`merge(A,1)` |`put(A,2)` |`tx2` will fail, value of `A` is `1`
+|A=0 |`merge(A,1)` |`merge(A,2)` |`A` is `2`
+|A=0 |`delete(A)` |`put(A,2)` |`tx2` will fail, `A` does not exists
+|A=0 |`delete(A)` |`merge(A,2)` |`A` is `2`
+|===========================================================
+
+.Concurrent change resolution for containers, lists, list items
+[cols=",,,",options="header",]
+|=======================================================================
+|Initial state |`tx1` |`tx2` |Result
+|Empty |put(TOP,[]) |put(TOP,[]) |`tx2` will fail, state is TOP=[]
+
+|Empty |put(TOP,[]) |merge(TOP,[]) |TOP=[]
+
+|Empty |put(TOP,[FOO=1]) |put(TOP,[BAR=1]) |`tx2` will fail, state is
+TOP=[FOO=1]
+
+|Empty |put(TOP,[FOO=1]) |merge(TOP,[BAR=1]) |TOP=[FOO=1,BAR=1]
+
+|Empty |merge(TOP,[FOO=1]) |put(TOP,[BAR=1]) |`tx2` will fail, state is
+TOP=[FOO=1]
+
+|Empty |merge(TOP,[FOO=1]) |merge(TOP,[BAR=1]) |TOP=[FOO=1,BAR=1]
+
+|TOP=[] |put(TOP,[FOO=1]) |put(TOP,[BAR=1]) |`tx2` will fail, state is
+TOP=[FOO=1]
+
+|TOP=[] |put(TOP,[FOO=1]) |merge(TOP,[BAR=1]) |state is
+TOP=[FOO=1,BAR=1]
+
+|TOP=[] |merge(TOP,[FOO=1]) |put(TOP,[BAR=1]) |`tx2` will fail, state is
+TOP=[FOO=1]
+
+|TOP=[] |merge(TOP,[FOO=1]) |merge(TOP,[BAR=1]) |state is
+TOP=[FOO=1,BAR=1]
+
+|TOP=[] |delete(TOP) |put(TOP,[BAR=1]) |`tx2` will fail, state is empty
+store
+
+|TOP=[] |delete(TOP) |merge(TOP,[BAR=1]) |state is TOP=[BAR=1]
+
+|TOP=[] |put(TOP/FOO,1) |put(TOP/BAR,1]) |state is TOP=[FOO=1,BAR=1]
+
+|TOP=[] |put(TOP/FOO,1) |merge(TOP/BAR,1) |state is TOP=[FOO=1,BAR=1]
+
+|TOP=[] |merge(TOP/FOO,1) |put(TOP/BAR,1) |state is TOP=[FOO=1,BAR=1]
+
+|TOP=[] |merge(TOP/FOO,1) |merge(TOP/BAR,1) |state is TOP=[FOO=1,BAR=1]
+
+|TOP=[] |delete(TOP) |put(TOP/BAR,1) |`tx2` will fail, state is empty
+store
+
+|TOP=[] |delete(TOP) |merge(TOP/BAR,1] |`tx2` will fail, state is empty
+store
+
+|TOP=[FOO=1] |put(TOP/FOO,2) |put(TOP/BAR,1) |state is TOP=[FOO=2,BAR=1]
+
+|TOP=[FOO=1] |put(TOP/FOO,2) |merge(TOP/BAR,1) |state is
+TOP=[FOO=2,BAR=1]
+
+|TOP=[FOO=1] |merge(TOP/FOO,2) |put(TOP/BAR,1) |state is
+TOP=[FOO=2,BAR=1]
+
+|TOP=[FOO=1] |merge(TOP/FOO,2) |merge(TOP/BAR,1) |state is
+TOP=[FOO=2,BAR=1]
+
+|TOP=[FOO=1] |delete(TOP/FOO) |put(TOP/BAR,1) |state is TOP=[BAR=1]
+
+|TOP=[FOO=1] |delete(TOP/FOO) |merge(TOP/BAR,1] |state is TOP=[BAR=1]
+|=======================================================================
diff --git a/manuals/developer-guide/src/main/asciidoc/controller/md-sal-rpc-routing.adoc b/manuals/developer-guide/src/main/asciidoc/controller/md-sal-rpc-routing.adoc
new file mode 100644 (file)
index 0000000..0b09dba
--- /dev/null
@@ -0,0 +1,168 @@
+// Source: https://ask.opendaylight.org/question/99/how-does-request-routing-works/
+=== MD-SAL RPC routing
+
+The MD-SAL provides a way to deliver Remote Procedure Calls (RPCs) to a
+particular implementation based on content in the input as it is modeled in
+YANG. This part of the the RPC input is referred to as a *context reference*.
+
+The MD-SAL does not dictate the name of the leaf which is used for this RPC
+routing, but provides necessary functionality for YANG model author to define
+their *context reference* in their model of RPCs.
+
+MD-SAL routing behavior is modeled using following terminology and its
+application to YANG models:
+
+Context Type::
+  Logical type of RPC routing. Context type is modeled as YANG `identity`
+  and is referenced in model to provide scoping information.
+Context Instance::
+  Conceptual location in data tree, which represents context in which RPC
+  could be executed. Context instance usually represent logical point
+  to which RPC execution is attached.
+Context Reference::
+  Field of RPC input payload which contains Instance Identifier referencing
+  *context instance*  in which the RPC should be executed.
+
+==== Modeling a routed RPC
+
+In order to define routed RPCs, the YANG model author needs to declare (or
+reuse) a *context type*, set of possible *context instances* and finally RPCs
+which will contain *context reference* on which they will be routed.
+
+===== Declaring a routing context type
+
+[source,yang]
+----
+identity node-context {
+    description "Identity used to mark node context";
+}
+----
+
+This declares an identity named `node-context`, which is used as marker
+for node-based routing and is used in other places to reference that routing
+type.
+
+===== Declaring possible context instances
+
+In order to define possible values of *context instances* for routed RPCs, we
+need to model that set accordingly using `context-instance` extension from the
+`yang-ext` model.
+
+[source,yang]
+----
+import yang-ext { prefix ext; }
+
+/** Base structure **/
+container nodes {
+    list node {
+        key "id";
+        ext:context-instance "node-context";
+        // other node-related fields would go here
+    }
+}
+----
+
+The statement `ext:context-instance "node-context";` marks any element of the
+`list node` as a possible valid *context instance* in `node-context` based
+routing.
+
+[NOTE]
+====
+The existence of a *context instance* node in operational or config data tree
+is not strongly tied to existence of RPC implementation.
+
+For most routed RPC models, there is relationship between the data present in
+operational data tree and RPC implementation availability, but this is
+not enforced by MD-SAL. This provides some flexibility for YANG model writers
+to better specify their routing model and requirements for implementations.
+Details when RPC implementations are available should be documented in YANG model.
+
+If user invokes RPC with a *context instance* that has no registered
+implementation, the RPC invocation will fail with the exception
+`DOMRpcImplementationNotAvailableException`.
+====
+
+===== Declaring a routed RPC
+
+To declare RPC to be routed based on `node-context` we need to add leaf
+of `instance-identifier` type (or type derived from `instance-identifier`)
+to the RPC and mark it as *context reference*.
+
+This is achieved using YANG extension `context-reference` from `yang-ext` model
+on leaf, which will be used for RPC routing.
+
+[source,yang]
+----
+rpc example-routed-rpc  {
+    input {
+        leaf node {
+            ext:context-reference "node-context";
+            type "instance-identifier";
+        }
+        // other input to the RPC would go here
+    }
+}
+----
+
+The statement `ext:context-reference "node-context"` marks `leaf node` as
+*context reference* of type `node-context`. The value of this leaf, will be used
+by the MD-SAL to select the particular RPC implementation that registered itself
+as the implementation of the RPC for particular *context instance*.
+
+==== Using routed RPCs
+
+From a user perspective (e.g. invoking RPCs) there is no difference between
+routed and non-routed RPCs. Routing information is just an additional leaf in
+RPC which must be populated.
+
+// TODO: Add simple snippet of invoking such RPC even if it does not differ
+// from normal one.
+
+==== Implementing a routed RPC
+
+// TODO: Update this section to show some other example model
+// along with binding and DOM implementations
+
+Implementation
+
+===== Registering implementations
+
+// FIXME: Clean up bit wording in following section, use different example
+
+Implementations of a routed RPC (e.g., southbound plugins) will specify an
+instance-identifier for the *context reference* (in this case a node) for which
+they want to provide an implementation during registration. Consumers, e.g.,
+those calling the RPC are required to specify that instance-identifier (in this
+case the identifier of a node) when invoking RPC.
+
+Simple code which showcases that for add-flow via Binding-Aware APIs
+(https://git.opendaylight.org/gerrit/gitweb?p=controller.git;a=blob;f=opendaylight/md-sal/sal-binding-it/src/test/java/org/opendaylight/controller/test/sal/binding/it/RoutedServiceTest.java;h=d49d6f0e25e271e43c8550feb5eef63d96301184;hb=HEAD[RoutedServiceTest.java]
+):
+
+[source, java]
+----
+ 61  @Override
+ 62  public void onSessionInitiated(ProviderContext session) {
+ 63      assertNotNull(session);
+ 64      firstReg = session.addRoutedRpcImplementation(SalFlowService.class, salFlowService1);
+ 65  }
+----
+Line 64: We are registering salFlowService1 as implementation of
+SalFlowService RPC
+
+[source, java]
+----
+107  NodeRef nodeOne = createNodeRef("foo:node:1");
+109  /**
+110   * Provider 1 registers path of node 1
+111   */
+112  firstReg.registerPath(NodeContext.class, nodeOne);
+----
+
+Line 107: We are creating NodeRef (encapsulation of InstanceIdentifier)
+for "foo:node:1".
+
+Line 112: We register salFlowService1 as implementation for nodeOne.
+
+The salFlowService1 will be executed only for RPCs which contains
+Instance Identifier for foo:node:1.