From cf2120b333e7ad2eaeffb4f8b0686454fe9598d8 Mon Sep 17 00:00:00 2001 From: Devin Avery Date: Mon, 16 Jun 2014 18:52:14 +0200 Subject: [PATCH] API Clarity: Documented Async Data Broker APIs. Documented AsyncDataBroker APIs, which are base abstract APIs from which Binding and DOM data broker APIs are derived. Introduced code-examples illustrating transaction isollation and conflict detection. Change-Id: I3c881b1fceb0b68c0cc28a1caa7780b9b6c2af3f Signed-off-by: Tony Tkacik Signed-off-by: Devin Avery --- .../sal/common/api/data/AsyncDataBroker.java | 202 ++++++++++-- .../common/api/data/AsyncDataChangeEvent.java | 111 +++++-- .../api/data/AsyncDataChangeListener.java | 33 +- .../api/data/AsyncDataTransactionFactory.java | 100 +++++- .../common/api/data/AsyncReadTransaction.java | 60 +++- .../api/data/AsyncReadWriteTransaction.java | 122 +++++++- .../sal/common/api/data/AsyncTransaction.java | 12 +- .../api/data/AsyncWriteTransaction.java | 289 +++++++++++++++--- 8 files changed, 816 insertions(+), 113 deletions(-) diff --git a/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/data/AsyncDataBroker.java b/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/data/AsyncDataBroker.java index 87bbfd3d06..fb429e5fd1 100644 --- a/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/data/AsyncDataBroker.java +++ b/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/data/AsyncDataBroker.java @@ -10,6 +10,52 @@ package org.opendaylight.controller.md.sal.common.api.data; import org.opendaylight.yangtools.concepts.ListenerRegistration; import org.opendaylight.yangtools.concepts.Path; +/** + * + * Provides access to a conceptual data tree store. + * + *

+ * Also provides the ability to subscribe for changes to data under a given + * branch of the tree. + * + *

+ * All operations on data tree are performed via one of the transactions: + *

    + *
  • Read-Only - allocated using {@link #newReadOnlyTransaction()} + *
  • Write-Only - allocated using {@link #newWriteOnlyTransaction()} + *
  • Read-Write - allocated using {@link #newReadWriteTransaction()} + *
+ * + *

+ * These transactions provide a stable isolated view of data tree, which is + * guaranteed to be not affected by other concurrent transactions, until + * transaction is committed. + * + *

+ * For a detailed explanation of how transaction are isolated and how transaction-local + * changes are committed to global data tree, see + * {@link AsyncReadTransaction}, {@link AsyncWriteTransaction}, + * {@link AsyncReadWriteTransaction} and {@link AsyncWriteTransaction#commit()}. + * + * + *

+ * It is strongly recommended to use the type of transaction, which + * provides only the minimal capabilities you need. This allows for + * optimizations at the data broker / data store level. For example, + * implementations may optimize the transaction for reading if they know ahead + * of time that you only need to read data - such as not keeping additional meta-data, + * which may be required for write transactions. + * + *

+ * Implementation Note: This interface is not intended to be implemented + * by users of MD-SAL, but only to be consumed by them. + * + * @param

+ * Type of path (subtree identifier), which represents location in + * tree + * @param + * Type of data (payload), which represents data payload + */ public interface AsyncDataBroker

, D, L extends AsyncDataChangeListener> extends // AsyncDataTransactionFactory { @@ -17,52 +63,150 @@ public interface AsyncDataBroker

, D, L extends AsyncDataChange * * Scope of Data Change * + *

* Represents scope of data change (addition, replacement, deletion). * - * The terminology for types is reused from LDAP + * The terminology for scope types is reused from LDAP. + * + *

Examples

+ * + * Following is an example model with comments describing what notifications + * you would receive based on the scope you specify, when you are + * registering for changes on container a. * - * @see http://www.idevelopment.info/data/LDAP/LDAP_Resources/SEARCH_Setting_the_SCOPE_Parameter.shtml + *
+     * container a              // scope BASE, ONE, SUBTREE
+     *    leaf "foo"            // scope ONE, SUBTREE
+     *    container             // scope ONE, SUBTREE
+     *       leaf  "bar"        // scope SUBTREE
+     *    list list             // scope ONE, SUBTREE
+     *      list [a]            // scope SUBTREE
+     *        id "a"            // scope SUBTREE
+     *      list [b]            // scope SUBTREE
+     *        id "b"            // scope SUBTREE
+     * 
+ * + * Following is an example model with comments describing what notifications + * you would receive based on the scope you specify, when you are + * registering for changes on list list (without specifying concrete item in + * the list). + * + *
+     *  list list               // scope BASE, ONE, SUBTREE
+     *      list [a]            // scope ONE, SUBTREE
+     *        id "a"            // scope SUBTREE
+     *      list [b]            // scope ONE, SUBTREE
+     *        id "b"            // scope SUBTREE
+     * 
+ * + * + * @see http://www.idevelopment.info/data/LDAP/LDAP_Resources/ + * SEARCH_Setting_the_SCOPE_Parameter.shtml */ public enum DataChangeScope { - /** - * Represents only a direct change of the node, such as replacement of node, - * addition or deletion. - * - */ - BASE, - /** - * Represent a change (addition,replacement,deletion) - * of the node or one of it's direct childs. - * - */ - ONE, - /** - * Represents a change of the node or any of it's child nodes. - * - */ - SUBTREE + /** + * Represents only a direct change of the node, such as replacement of a + * node, addition or deletion. + * + */ + BASE, + /** + * Represent a change (addition,replacement,deletion) of the node or one + * of its direct children. + * + * This scope is superset of {@link #BASE}. + * + */ + ONE, + /** + * Represents a change of the node or any of or any of its child nodes, + * direct and nested. + * + * This scope is superset of {@link #ONE} and {@link #BASE}. + * + */ + SUBTREE } + /** + * {@inheritDoc} + */ @Override public AsyncReadTransaction newReadOnlyTransaction(); + /** + * {@inheritDoc} + */ @Override - public AsyncReadWriteTransaction newReadWriteTransaction(); + public AsyncReadWriteTransaction newReadWriteTransaction(); + /** + * {@inheritDoc} + */ @Override public AsyncWriteTransaction newWriteOnlyTransaction(); /** - * Registers {@link DataChangeListener} for Data Change callbacks - * which will be triggered on which will be triggered on the store + * Registers a {@link AsyncDataChangeListener} to receive + * notifications when data changes under a given path in the conceptual data + * tree. + *

+ * You are able to register for notifications for any node or subtree + * which can be reached via the supplied path. + *

+ * If path type P allows it, you may specify paths up to the leaf nodes + * then it is possible to listen on leaf nodes. + *

+ * You are able to register for data change notifications for a subtree even + * if it does not exist. You will receive notification once that node is + * created. + *

+ * If there is any preexisting data in data tree on path for which you are + * registering, you will receive initial data change event, which will + * contain all preexisting data, marked as created. + * + *

+ * You are also able to specify the scope of the changes you want to be + * notified. + *

+ * Supported scopes are: + *

    + *
  • {@link DataChangeScope#BASE} - notification events will only be + * triggered when a node referenced by path is created, removed or replaced. + *
  • {@link DataChangeScope#ONE} - notifications events will only be + * triggered when a node referenced by path is created, removed or replaced, + * or any or any of its immediate children are created, updated or removed. + *
  • {@link DataChangeScope#SUBTREE} - notification events will be + * triggered when a node referenced by the path is created, removed + * or replaced or any of the children in its subtree are created, removed + * or replaced. + *
+ * See {@link DataChangeScope} for examples. + *

+ * This method returns a {@link ListenerRegistration} object. To + * "unregister" your listener for changes call the "close" method on this + * returned object. + *

+ * You MUST call close when you no longer need to receive notifications + * (such as during shutdown or for example if your bundle is shutting down). * - * @param store Logical store in which listener is registered. - * @param path Path (subtree identifier) on which client listener will be invoked. - * @param listener Instance of listener which should be invoked on - * @param triggeringScope Scope of change which triggers callback. - * @return Listener registration of the listener, call {@link ListenerRegistration#close()} - * to stop delivery of change events. + * @param store + * Logical Data Store - Logical Datastore you want to listen for + * changes in. For example + * {@link LogicalDatastoreType#OPERATIONAL} or + * {@link LogicalDatastoreType#CONFIGURATION} + * @param path + * Path (subtree identifier) on which client listener will be + * invoked. + * @param listener + * Instance of listener which should be invoked on + * @param triggeringScope + * Scope of change which triggers callback. + * @return Listener registration object, which may be used to unregister + * your listener using {@link ListenerRegistration#close()} to stop + * delivery of change events. */ - ListenerRegistration registerDataChangeListener(LogicalDatastoreType store, P path, L listener, DataChangeScope triggeringScope); + ListenerRegistration registerDataChangeListener(LogicalDatastoreType store, P path, L listener, + DataChangeScope triggeringScope); } diff --git a/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/data/AsyncDataChangeEvent.java b/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/data/AsyncDataChangeEvent.java index f612e51747..29c9bae31f 100644 --- a/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/data/AsyncDataChangeEvent.java +++ b/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/data/AsyncDataChangeEvent.java @@ -13,63 +13,128 @@ import java.util.Set; import org.opendaylight.yangtools.concepts.Immutable; import org.opendaylight.yangtools.concepts.Path; -public interface AsyncDataChangeEvent

,D> extends Immutable { +/** + * + * An event which contains a capture of changes in a data subtree + * + *

+ * Represents a notification indicating that some data at or under a particular + * path has changed. The notification contains a capture of the changes in the data + * subtree. This event is triggered by successful application of modifications + * from a transaction on the global data tree. Use the + * {@link AsyncDataBroker#registerDataChangeListener(LogicalDatastoreType, Path, AsyncDataChangeListener, AsyncDataBroker.DataChangeScope)} + * method to register a listener for data change events. + * + *

+ * A listener will only receive notifications for changes to data under the path + * they register for. See + * {@link AsyncDataBroker#registerDataChangeListener(LogicalDatastoreType, Path, AsyncDataChangeListener, AsyncDataBroker.DataChangeScope)} + * to learn more about registration scopes. + * + *

+ * The entire subtree under the path will be provided via instance methods of Data + * Change Event even if just a leaf node changes. + * + *

+ * Implementation Note: This interface is not intended to be implemented + * by users of MD-SAL, but only to be consumed by them. + * + * @param

+ * Type of path (subtree identifier), which represents location in + * tree + * @param + * Type of data (payload), which represents data payload + */ +public interface AsyncDataChangeEvent

, D> extends Immutable { /** - * Returns a immutable map of paths and newly created objects + * Returns a map of paths and newly created objects, which were introduced by + * this change into conceptual data tree, if no new objects were introduced + * this map will be empty. + *

+ * This map contains all data tree nodes (and paths to them) which were created + * and are in the scope of listener registration. The data tree nodes + * contain their whole subtree with their current state. * * @return map of paths and newly created objects */ Map getCreatedData(); /** - * Returns a immutable map of paths and respective updated objects after update. - * - * Original state of the object is in - * {@link #getOriginalData()} + * Returns a map of paths and objects which were updated by this change in the + * conceptual data tree if no existing objects were updated + * this map will be empty. + *

+ * This map contains all data tree nodes (and paths to them) which were updated + * and are in the scope of listener registration. The data tree nodes + * contain their whole subtree with their current state. + *

+ * A Node is considered updated if it contents were replaced or one of its + * children was created, removed or updated. + *

+ * Original state of the updated data tree nodes is in + * {@link #getOriginalData()} stored with same path. * * @return map of paths and newly created objects */ Map getUpdatedData(); /** - * Returns a immutable set of removed paths. - * - * Original state of the object is in - * {@link #getOriginalData()} + * Returns an immutable set of removed paths. + *

+ * This set contains the paths to the data tree nodes which are in the scope + * of the listener registration that have been removed. + *

+ * Original state of the removed data tree nodes is in + * {@link #getOriginalData()} stored with same path. * * @return set of removed paths */ Set

getRemovedPaths(); /** - * Return a immutable map of paths and original state of updated and removed objects. + * Returns an immutable map of updated or removed paths and their original + * states prior to this change. * - * This map is populated if at changed path was previous object, and captures - * state of previous object. + *

+ * This map contains the original version of the data tree nodes (and paths + * to them), which are in the scope of the listener registration. * * @return map of paths and original state of updated and removed objects. */ Map getOriginalData(); /** - * Returns a immutable stable view of data state, which - * captures state of data store before the reported change. + * Returns an immutable stable view of data state, which captures the state of + * data store before the reported change. * + *

+ * The view is rooted at the point where the listener, to which the event is + * being delivered, was registered. + *

+ * If listener used a wildcarded path (if supported by path type) during + * registration for change listeners this method returns null, and original + * state can be accessed only via {@link #getOriginalData()} * - * The view is rooted at the point where the listener, to which the event is being delivered, was registered. - * - * @return Stable view of data before the change happened, rooted at the listener registration path. + * @return Stable view of data before the change happened, rooted at the + * listener registration path. * */ D getOriginalSubtree(); /** - * Returns a immutable stable view of data, which captures state of data store - * after the reported change. - * - * The view is rooted at the point where the listener, to which the event is being delivered, was registered. + * Returns an immutable stable view of data, which captures the state of data + * store after the reported change. + *

+ * The view is rooted at the point where the listener, to which the event is + * being delivered, was registered. + *

+ * If listener used a wildcarded path (if supported by path type) during + * registration for change listeners this method returns null, and state + * can be accessed only via {@link #getCreatedData()}, + * {@link #getUpdatedData()}, {@link #getRemovedPaths()} * - * @return Stable view of data after the change happened, rooted at the listener registration path. + * @return Stable view of data after the change happened, rooted at the + * listener registration path. */ D getUpdatedSubtree(); } diff --git a/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/data/AsyncDataChangeListener.java b/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/data/AsyncDataChangeListener.java index 49f07bc52b..dca5200d39 100644 --- a/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/data/AsyncDataChangeListener.java +++ b/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/data/AsyncDataChangeListener.java @@ -11,13 +11,42 @@ import java.util.EventListener; import org.opendaylight.yangtools.concepts.Path; +/** + * Listener of data change events on particular subtree. + * + *

+ * User-supplied implementations of this listener interface MUST register via + * {@link AsyncDataBroker#registerDataChangeListener(LogicalDatastoreType, Path, AsyncDataChangeListener, AsyncDataBroker.DataChangeScope)} + * in order to start receiving data change events, which capture state changes + * in a subtree. + * + *

+ * Implementation Note: This interface is intended to be implemented + * by users of MD-SAL. + * + * @param

+ * Type of path (subtree identifier), which represents location in + * tree + * @param + * Type of data (payload), which represents data payload + */ public interface AsyncDataChangeListener

, D> extends EventListener { /** - * Note that this method may be invoked from a shared thread pool, so + * + * Invoked when there is data change for the particular path, which was used to + * register this listener. + *

+ * This method may be also invoked during registration of the listener if + * there is any preexisting data in the conceptual data tree for supplied path. + * This initial event will contain all preexisting data as created. + * + *

+ * Note that this method may be invoked from a shared thread pool, so * implementations SHOULD NOT perform CPU-intensive operations and they * definitely MUST NOT invoke any potentially blocking operations. * - * @param change Data Change Event being delivered. + * @param change + * Data Change Event being delivered. */ void onDataChanged(AsyncDataChangeEvent change); } diff --git a/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/data/AsyncDataTransactionFactory.java b/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/data/AsyncDataTransactionFactory.java index 732fed0f3f..cedd883b22 100644 --- a/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/data/AsyncDataTransactionFactory.java +++ b/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/data/AsyncDataTransactionFactory.java @@ -9,12 +9,110 @@ package org.opendaylight.controller.md.sal.common.api.data; import org.opendaylight.yangtools.concepts.Path; +/** + * A factory which allocates new transactions to operate on the data + * tree. + * + *

+ * Note: This interface is not intended to be used directly, but rather + * via subinterfaces which introduces additional semantics to allocated + * transactions. + *

    + *
  • {@link AsyncDataBroker} + *
  • {@link TransactionChain} + *
+ * + *

+ * All operations on the data tree are performed via one of the transactions: + *

    + *
  • Read-Only - allocated using {@link #newReadOnlyTransaction()} + *
  • Write-Only - allocated using {@link #newWriteOnlyTransaction()} + *
  • Read-Write - allocated using {@link #newReadWriteTransaction()} + *
+ * + *

+ * These transactions provides a stable isolated view of the data tree, which is + * guaranteed to be not affected by other concurrent transactions, until + * transaction is committed. + * + *

+ * For a detailed explanation of how transaction are isolated and how transaction-local + * changes are committed to global data tree, see + * {@link AsyncReadTransaction}, {@link AsyncWriteTransaction}, + * {@link AsyncReadWriteTransaction} and {@link AsyncWriteTransaction#commit()}. + * + *

+ * It is strongly recommended to use the type of transaction, which + * provides only the minimal capabilities you need. This allows for + * optimizations at the data broker / data store level. For example, + * implementations may optimize the transaction for reading if they know ahead + * of time that you only need to read data - such as not keeping additional meta-data, + * which may be required for write transactions. + *

+ * Implementation Note: This interface is not intended to be implemented + * by users of MD-SAL, but only to be consumed by them. + * + * @see AsyncDataBroker + * @see TransactionChain + * + * @param

+ * Type of path (subtree identifier), which represents location in + * tree + * @param + * Type of data (payload), which represents data payload + */ public interface AsyncDataTransactionFactory

, D> { + /** + * Allocates a new read-only transaction which provides an immutable snapshot of + * the data tree. + *

+ * The view of data tree is an immutable snapshot of current data tree state when + * transaction was allocated. + * + * @return new read-only transaction + */ AsyncReadTransaction newReadOnlyTransaction(); + /** + * Allocates new read-write transaction which provides a mutable view of the data + * tree. + * + *

+ * Preconditions for mutation of data tree are captured from the snapshot of + * data tree state, when the transaction is allocated. If data was + * changed during transaction in an incompatible way then the commit of this transaction + * will fail. See {@link AsyncWriteTransaction#commit()} for more + * details about conflicting and not-conflicting changes and + * failure scenarios. + * + * @return new read-write transaction + */ AsyncReadWriteTransaction newReadWriteTransaction(); - AsyncWriteTransaction newWriteOnlyTransaction(); + /** + * Allocates new write-only transaction based on latest state of data + * tree. + * + *

+ * Preconditions for mutation of data tree are captured from the snapshot of + * data tree state, when the transaction is allocated. If data was + * changed during transaction in an incompatible way then the commit of this transaction + * will fail. See {@link AsyncWriteTransaction#commit()} for more + * details about conflicting and not-conflicting changes and + * failure scenarios. + * + *

+ * Since this transaction does not provide a view of the data it SHOULD BE + * used only by callers which are exclusive writers (exporters of data) + * to the subtree they modify. This prevents optimistic + * lock failures as described in {@link AsyncWriteTransaction#commit()}. + *

+ * Exclusivity of writers to particular subtree SHOULD BE enforced by + * external locking mechanism. + * + * @return new write-only transaction + */ + AsyncWriteTransaction newWriteOnlyTransaction(); } diff --git a/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/data/AsyncReadTransaction.java b/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/data/AsyncReadTransaction.java index 7744f71888..6cf5a5b532 100644 --- a/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/data/AsyncReadTransaction.java +++ b/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/data/AsyncReadTransaction.java @@ -12,12 +12,61 @@ import org.opendaylight.yangtools.concepts.Path; import com.google.common.base.Optional; import com.google.common.util.concurrent.ListenableFuture; +/** + * + * Provides a stateful read-only view of the data tree. + * + *

+ * View of the data tree is a stable point-in-time snapshot of the current data tree state when + * the transaction was created. It's state and underlying data tree + * is not affected by other concurrently running transactions. + * + *

+ * Implementation Note: This interface is not intended to be implemented + * by users of MD-SAL, but only to be consumed by them. + * + *

Transaction isolation example

Lest assume initial state of data tree + * for PATH is A. + * + *
+ * txRead = broker.newReadOnlyTransaction();   // read Transaction is snapshot of data
+ * txWrite = broker.newReadWriteTransactoin(); // concurrent write transaction
+ *
+ * txRead.read(OPERATIONAL,PATH).get();        // will return Optional containing A
+ * txWrite = broker.put(OPERATIONAL,PATH,B);   // writes B to PATH
+ *
+ * txRead.read(OPERATIONAL,PATH).get();        // still returns Optional containing A
+ *
+ * txWrite.commit().get();                     // data tree is updated, PATH contains B
+ * txRead.read(OPERATIONAL,PATH).get();        // still returns Optional containing A
+ *
+ * txAfterCommit = broker.newReadOnlyTransaction(); // read Transaction is snapshot of new state
+ * txAfterCommit.read(OPERATIONAL,PATH).get(); // returns Optional containing B;
+ * 
+ * + *

+ * Note: example contains blocking calls on future only to illustrate + * that action happened after other asynchronous action. Use of blocking call + * {@link ListenableFuture#get()} is discouraged for most uses and you should + * use + * {@link com.google.common.util.concurrent.Futures#addCallback(ListenableFuture, com.google.common.util.concurrent.FutureCallback)} + * or other functions from {@link com.google.common.util.concurrent.Futures} to + * register more specific listeners. + * + * @param

+ * Type of path (subtree identifier), which represents location in + * tree + * @param + * Type of data (payload), which represents data payload + */ public interface AsyncReadTransaction

, D> extends AsyncTransaction { /** * - * Reads data from provided logical data store located at provided path - * + * Reads data from provided logical data store located at the provided path. + *

+ * If the target is a subtree, then the whole subtree is read (and will be + * accessible from the returned data object). * * @param store * Logical data store from which read should occur. @@ -26,10 +75,11 @@ public interface AsyncReadTransaction

, D> extends AsyncTransac * read * @return Listenable Future which contains read result *

    - *
  • If data at supplied path exists the {@link Future#get()} - * returns Optional object containing data + *
  • If data at supplied path exists the + * {@link ListeblaFuture#get()} returns Optional object containing + * data once read is done. *
  • If data at supplied path does not exists the - * {@link Future#get()} returns {@link Optional#absent()}. + * {@link ListenbleFuture#get()} returns {@link Optional#absent()}. *
*/ ListenableFuture> read(LogicalDatastoreType store, P path); diff --git a/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/data/AsyncReadWriteTransaction.java b/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/data/AsyncReadWriteTransaction.java index ce740bf41d..34101366c8 100644 --- a/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/data/AsyncReadWriteTransaction.java +++ b/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/data/AsyncReadWriteTransaction.java @@ -10,12 +10,126 @@ package org.opendaylight.controller.md.sal.common.api.data; import org.opendaylight.yangtools.concepts.Path; /** - * Transaction enabling client to have combined transaction, - * which provides read and write capabilities. + * Transaction enabling a client to have a combined read/write capabilities. * + *

+ * 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. * - * @param

Path Type - * @param Data Type + *

+ * Write transactions are isolated from other concurrent write transactions. All + * writes are local to the transaction and represents only a proposal of state + * change for data tree and it is not visible to any other concurrently running + * transactions. + * + *

+ * Applications publish the changes proposed in the transaction by calling {@link #commit} + * 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. + * + *

+ * The transaction commit may fail due to a concurrent transaction modifying and committing data in + * an incompatible way. See {@link #commit()} for more concrete commit failure examples. + * + * Implementation Note: This interface is not intended to be implemented + * by users of MD-SAL, but only to be consumed by them. + * + *

Examples

+ * + *

Transaction local state

+ * + * Let assume initial state of data tree for PATH is A + * . + * + *
+ * txWrite = broker.newReadWriteTransaction(); // concurrent write transaction
+ *
+ * txWrite.read(OPERATIONAL,PATH).get()        // will return Optional containing A
+ * txWrite.put(OPERATIONAL,PATH,B);            // writes B to PATH
+ * txWrite.read(OPERATIONAL,PATH).get()        // will return Optional Containing B
+ *
+ * txWrite.commit().get();                     // data tree is updated, PATH contains B
+ *
+ * tx1afterCommit = broker.newReadOnlyTransaction(); // read Transaction is snapshot of new state
+ * tx1afterCommit.read(OPERATIONAL,PATH).get(); // returns Optional containing B
+ * 
+ * + * As you could see read-write transaction provides capabilities as + * {@link AsyncWriteTransaction} but also allows for reading proposed changes as + * if they already happened. + * + *

Transaction isolation (read transaction, read-write transaction)

Let + * assume initial state of data tree for PATH is A. + * + *
+ * txRead = broker.newReadOnlyTransaction();   // read Transaction is snapshot of data
+ * txWrite = broker.newReadWriteTransaction(); // concurrent write transaction
+ *
+ * txRead.read(OPERATIONAL,PATH).get();        // will return Optional containing A
+ * txWrite.read(OPERATIONAL,PATH).get()        // will return Optional containing A
+ *
+ * txWrite.put(OPERATIONAL,PATH,B);            // writes B to PATH
+ * txWrite.read(OPERATIONAL,PATH).get()        // will return Optional Containing B
+ *
+ * txRead.read(OPERATIONAL,PATH).get();        // concurrent read transaction still returns
+ *                                             // Optional containing A
+ *
+ * txWrite.commit().get();                     // data tree is updated, PATH contains B
+ * txRead.read(OPERATIONAL,PATH).get();        // still returns Optional containing A
+ *
+ * tx1afterCommit = broker.newReadOnlyTransaction(); // read Transaction is snapshot of new state
+ * tx1afterCommit.read(OPERATIONAL,PATH).get(); // returns Optional containing B
+ * 
+ * + *

Transaction isolation (2 concurrent read-write transactions)

Let + * assume initial state of data tree for PATH is A. + * + *
+ * tx1 = broker.newReadWriteTransaction(); // read Transaction is snapshot of data
+ * tx2 = broker.newReadWriteTransaction(); // concurrent write transaction
+ *
+ * tx1.read(OPERATIONAL,PATH).get();       // will return Optional containing A
+ * tx2.read(OPERATIONAL,PATH).get()        // will return Optional containing A
+ *
+ * tx2.put(OPERATIONAL,PATH,B);            // writes B to PATH
+ * tx2.read(OPERATIONAL,PATH).get()        // will return Optional Containing B
+ *
+ * tx1.read(OPERATIONAL,PATH).get();       // tx1 read-write transaction still sees Optional
+ *                                         // containing A since is isolated from tx2
+ * tx1.put(OPERATIONAL,PATH,C);            // writes C to PATH
+ * tx1.read(OPERATIONAL,PATH).get()        // will return Optional Containing C
+ *
+ * tx2.read(OPERATIONAL,PATH).get()        // tx2 read-write transaction still sees Optional
+ *                                         // containing B since is isolated from tx1
+ *
+ * tx2.commit().get();                     // data tree is updated, PATH contains B
+ * tx1.read(OPERATIONAL,PATH).get();       // still returns Optional containing C since is isolated from tx2
+ *
+ * tx1afterCommit = broker.newReadOnlyTransaction(); // read Transaction is snapshot of new state
+ * tx1afterCommit.read(OPERATIONAL,PATH).get(); // returns Optional containing B
+ *
+ * tx1.commit()                            // Will fail with OptimisticLockFailedException
+ *                                         // which means concurrent transaction changed the same PATH
+ *
+ * 
+ * + *

+ * Note: examples contains blocking calls on future only to illustrate + * that action happened after other asynchronous action. Use of blocking call + * {@link com.google.common.util.concurrent.ListenableFuture#get()} is discouraged for most uses and you should + * use + * {@link com.google.common.util.concurrent.Futures#addCallback(com.google.common.util.concurrent.ListenableFuture, com.google.common.util.concurrent.FutureCallback)} + * or other functions from {@link com.google.common.util.concurrent.Futures} to + * register more specific listeners. + * + * + * @param

+ * Type of path (subtree identifier), which represents location in + * tree + * @param + * Type of data (payload), which represents data payload */ public interface AsyncReadWriteTransaction

, D> extends AsyncReadTransaction, AsyncWriteTransaction { diff --git a/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/data/AsyncTransaction.java b/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/data/AsyncTransaction.java index 23ca275ef2..c7cc91528b 100644 --- a/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/data/AsyncTransaction.java +++ b/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/data/AsyncTransaction.java @@ -13,7 +13,17 @@ import org.opendaylight.yangtools.concepts.Path; /** * - * @author + * A common parent for all transactions which operate on a conceptual data tree. + * + * See derived transaction types for more concrete behavior: + *

    + *
  • {@link AsyncReadTransaction} - Read capabilities, user is able to read data from data tree
  • + *
  • {@link AsyncWriteTransaction} - Write capabilities, user is able to propose changes to data tree
  • + *
  • {@link AsyncReadWriteTransaction} - Read and Write capabilities, user is able to read state and to propose changes of state.
  • + *
+ * + * Implementation Note: This interface is not intended to be implemented + * by users of MD-SAL. * * @param

Type of path (subtree identifier), which represents location in tree * @param Type of data (payload), which represents data payload diff --git a/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/data/AsyncWriteTransaction.java b/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/data/AsyncWriteTransaction.java index 82c48d2ddb..e2734eaddc 100644 --- a/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/data/AsyncWriteTransaction.java +++ b/opendaylight/md-sal/sal-common-api/src/main/java/org/opendaylight/controller/md/sal/common/api/data/AsyncWriteTransaction.java @@ -13,56 +13,118 @@ import org.opendaylight.yangtools.yang.common.RpcResult; import com.google.common.util.concurrent.ListenableFuture; -public interface AsyncWriteTransaction

, D> extends AsyncTransaction { +/** + * Write transaction provides mutation capabilities for a data tree. + * + *

+ * Initial state of write transaction is a stable snapshot of the current data tree. + * The state is captured when the transaction is created and its state and underlying + * data tree are not affected by other concurrently running transactions. + *

+ * Write transactions are isolated from other concurrent write transactions. All + * writes are local to the transaction and represent only a proposal of state + * change for the data tree and it is not visible to any other concurrently running + * transaction. + *

+ * Applications publish the changes proposed in the transaction by calling {@link #commit} + * 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. + *

+ * The transaction commit may fail due to a concurrent transaction modifying and committing data in + * an incompatible way. See {@link #commit()} for more concrete commit failure examples. + * + * + *

+ * Implementation Note: This interface is not intended to be implemented + * by users of MD-SAL, but only to be consumed by them. + * + * @param

+ * Type of path (subtree identifier), which represents location in + * tree + * @param + * Type of data (payload), which represents data payload + */ +public interface AsyncWriteTransaction

, D> extends AsyncTransaction { /** - * Cancels transaction. + * Cancels the transaction. * - * Transaction could be only cancelled if it's status - * is {@link TransactionStatus#NEW} or {@link TransactionStatus#SUBMITED} + * Transactions can only be cancelled if it's status is + * {@link TransactionStatus#NEW} or {@link TransactionStatus#SUBMITED} * - * Invoking cancel() on {@link TransactionStatus#FAILED} or {@link TransactionStatus#CANCELED} - * will have no effect. + * Invoking cancel() on {@link TransactionStatus#FAILED} or + * {@link TransactionStatus#CANCELED} will have no effect. * - * @throws IllegalStateException If transaction status is {@link TransactionStatus#COMMITED} + * @throws IllegalStateException + * If transaction status is {@link TransactionStatus#COMMITED} * */ public void cancel(); /** - * Store a piece of data at specified path. This acts as a add / replace operation, - * which is to say that whole subtree will be replaced by specified path. + * Store a piece of data at specified path. This acts as an add / replace + * operation, which is to say that whole subtree will be replaced by + * specified path. Performing the following put operations: + * + *

+     * 1) container { list [ a ] }
+     * 2) container { list [ b ] }
+     * 
+ * + * will result in the following data being present: + * + *
+     * container { list [ b ] }
+     * 
* - * If you need add or merge of current object with specified use {@link #merge(LogicalDatastoreType, Path, Object)} * - * @param store Logical data store which should be modified - * @param path Data object path - * @param data Data object to be written to specified path - * @throws IllegalStateException if the transaction is no longer {@link TransactionStatus#NEW} + * If you need to make sure that a parent object exists, but you do not want modify + * its preexisting state by using put, consider using + * {@link #merge(LogicalDatastoreType, Path, Object)} + * + * @param store + * Logical data store which should be modified + * @param path + * Data object path + * @param data + * Data object to be written to specified path + * @throws IllegalStateException + * if the transaction is no longer {@link TransactionStatus#NEW} */ public void put(LogicalDatastoreType store, P path, D data); /** - * Store a piece of data at specified path. This acts as a merge operation, + * Store a piece of data at the specified path. This acts as a merge operation, * which is to say that any pre-existing data which is not explicitly * overwritten will be preserved. This means that if you store a container, - * its child lists will be merged. Performing the following put operations: + * its child lists will be merged. Performing the following merge + * operations: * + *
      * 1) container { list [ a ] }
      * 2) container { list [ b ] }
+     * 
* * will result in the following data being present: * + *
      * container { list [ a, b ] }
+     * 
* - * This also means that storing the container will preserve any augmentations - * which have been attached to it. - * - * If you require an explicit replace operation, use {@link #put(LogicalDatastoreType, Path, Object)} instead. + * This also means that storing the container will preserve any + * augmentations which have been attached to it. + *

+ * If you require an explicit replace operation, use + * {@link #put(LogicalDatastoreType, Path, Object)} instead. * - * @param store Logical data store which should be modified - * @param path Data object path - * @param data Data object to be written to specified path - * @throws IllegalStateException if the transaction is no longer {@link TransactionStatus#NEW} + * @param store + * Logical data store which should be modified + * @param path + * Data object path + * @param data + * Data object to be written to specified path + * @throws IllegalStateException + * if the transaction is no longer {@link TransactionStatus#NEW} */ public void merge(LogicalDatastoreType store, P path, D data); @@ -70,9 +132,12 @@ public interface AsyncWriteTransaction

, D> extends AsyncTrans * Remove a piece of data from specified path. This operation does not fail * if the specified path does not exist. * - * @param store Logical data store which should be modified - * @param path Data object path - * @throws IllegalStateException if the transaction is no longer {@link TransactionStatus#NEW} + * @param store + * Logical data store which should be modified + * @param path + * Data object path + * @throws IllegalStateException + * if the transaction is no longer {@link TransactionStatus#NEW} */ public void delete(LogicalDatastoreType store, P path); @@ -80,41 +145,169 @@ public interface AsyncWriteTransaction

, D> extends AsyncTrans * * Closes transaction and resources allocated to the transaction. * - * This call does not change Transaction status. Client SHOULD - * explicitly {@link #commit()} or {@link #cancel()} transaction. + * This call does not change Transaction status. Client SHOULD explicitly + * {@link #commit()} or {@link #cancel()} transaction. * - * @throws IllegalStateException if the transaction has not been - * updated by invoking {@link #commit()} or {@link #cancel()}. + * @throws IllegalStateException + * if the transaction has not been updated by invoking + * {@link #commit()} or {@link #cancel()}. */ @Override public void close(); /** - * Initiates a commit of modification. This call logically seals the - * transaction, preventing any the client from interacting with the - * data stores. The transaction is marked as {@link TransactionStatus#SUBMITED} - * and enqueued into the data store backed for processing. + * Submits transaction to be applied to update logical data tree. + *

+ * This call logically seals the transaction, which prevents the client from + * further changing data tree using this transaction. Any subsequent calls to + * {@link #put(LogicalDatastoreType, Path, Object)}, + * {@link #merge(LogicalDatastoreType, Path, Object)} or + * {@link #delete(LogicalDatastoreType, Path)} will fail with + * {@link IllegalStateException}. + * + * The transaction is marked as {@link TransactionStatus#SUBMITED} and + * enqueued into the data store backed for processing. * *

- * The successful commit changes the state of the system and may affect - * several components. + * Whether or not the commit is successful is determined by versioning + * of data tree and validation of registered commit participants + * {@link AsyncConfigurationCommitHandler} + * if transaction changes {@link LogicalDatastoreType#CONFIGURATION} data tree. + *

+ * The effects of successful commit of data depends on + * other data change listeners {@link AsyncDataChangeListener} and + * {@link AsyncConfigurationCommitHandler}, which was registered to the + * same {@link AsyncDataBroker}, to which this transaction belongs. * + *

Failure scenarios

*

- * The effects of successful commit of data are described in the - * specifications and YANG models describing the Provider components of - * controller. It is assumed that Consumer has an understanding of this - * changes. - * - * @see DataCommitHandler for further information how two-phase commit is - * processed. - * @param store Identifier of the store, where commit should occur. + * Transaction may fail because of multiple reasons, such as + *

    + *
  • Another transaction finished earlier and modified the same node in + * non-compatible way (see below). In this case the returned future will fail with + * {@link 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.
  • + *
  • Data change introduced by this transaction did not pass validation by + * commit handlers or data was incorrectly structured. Returned future will + * fail with {@link DataValidationFailedException}. User should not retry to + * create new transaction with same data, since it probably will fail again. + *
  • + *
+ * + *

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. + * + *

Change compatibility of leafs, leaf-list items

+ * + * Following table shows state changes and failures between two concurrent transactions, + * which are based on same initial state, Tx 1 completes successfully + * before Tx 2 is submitted. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Initial stateTx 1Tx 2Result
Emptyput(A,1)put(A,2)Tx 2 will fail, state is A=1
Emptyput(A,1)merge(A,2)A=2
Emptymerge(A,1)put(A,2)Tx 2 will fail, state is A=1
Emptymerge(A,1)merge(A,2)A=2
A=0put(A,1)put(A,2)Tx 2 will fail, A=1
A=0put(A,1)merge(A,2)A=2
A=0merge(A,1)put(A,2)Tx 2 will fail, A=1
A=0merge(A,1)merge(A,2)A=2
A=0delete(A)put(A,2)Tx 2 will fail, A does not exists
A=0delete(A)merge(A,2)A=2
+ * + *

Change compatibility of subtrees

+ * + * Following table shows state changes and failures between two concurrent transactions, + * which are based on same initial state, Tx 1 completes successfully + * before Tx 2 is submitted. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Initial stateTx 1Tx 2Result
Emptyput(TOP,[])put(TOP,[])Tx 2 will fail, state is TOP=[]
Emptyput(TOP,[])merge(TOP,[])TOP=[]
Emptyput(TOP,[FOO=1])put(TOP,[BAR=1])Tx 2 will fail, state is TOP=[FOO=1]
Emptyput(TOP,[FOO=1])merge(TOP,[BAR=1])TOP=[FOO=1,BAR=1]
Emptymerge(TOP,[FOO=1])put(TOP,[BAR=1])Tx 2 will fail, state is TOP=[FOO=1]
Emptymerge(TOP,[FOO=1])merge(TOP,[BAR=1])TOP=[FOO=1,BAR=1]
TOP=[]put(TOP,[FOO=1])put(TOP,[BAR=1])Tx 2 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])Tx 2 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])Tx 2 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)Tx 2 will fail, state is empty store
TOP=[]delete(TOP)merge(TOP/BAR,1]Tx 2 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]
+ * + * + *

Examples of failure scenarios

+ * + *

Conflict of two transactions

+ * + * This example illustrates two concurrent transactions, which derived from + * same initial state of data tree and proposes conflicting modifications. + * + *
+     * txA = broker.newWriteTransaction(); // allocates new transaction, data tree is empty
+     * txB = broker.newWriteTransaction(); // allocates new transaction, data tree is empty
+     *
+     * txA.put(CONFIGURATION, PATH, A);    // writes to PATH value A
+     * txB.put(CONFIGURATION, PATH, B)     // writes to PATH value B
+     *
+     * ListenableFuture futureA = txA.commit(); // transaction A is sealed and committed
+     * ListenebleFuture futureB = txB.commit(); // transaction B is sealed and committed
+     * 
+ * + * Commit of transaction A will be processed asynchronously and data tree + * will be updated to contain value A for PATH. + * Returned {@link ListenableFuture} will successfully complete once + * state is applied to data tree. + * + * Commit of Transaction B will fail, because previous transaction also + * modified path in a concurrent way. The state introduced by transaction B + * will not be applied. Returned {@link ListenableFuture} object will fail + * with {@link OptimisticLockFailedException} exception, which indicates to + * client that concurrent transaction prevented the submitted transaction from being + * applied. + * * @return Result of the Commit, containing success information or list of * encountered errors, if commit was not successful. The Future * blocks until {@link TransactionStatus#COMMITED} is reached. - * Future will fail with {@link TransactionCommitFailedException} - * if Commit of this transaction failed. + * Future will fail with {@link TransactionCommitFailedException} if + * Commit of this transaction failed. TODO: Usability: Consider + * change from ListenableFuture to + * {@link com.google.common.util.concurrent.CheckedFuture} which + * will throw {@link TransactionCommitFailedException}. * - * @throws IllegalStateException if the transaction is not {@link TransactionStatus#NEW} + * @throws IllegalStateException + * if the transaction is not {@link TransactionStatus#NEW} */ public ListenableFuture> commit(); -- 2.36.6