From 6c8865b7937fd5a301d9066e4bf938129eec1fbe Mon Sep 17 00:00:00 2001 From: Thanh Ha Date: Sat, 8 Sep 2018 20:48:24 -0400 Subject: [PATCH] Migrate controller docs to the controller project Change-Id: Ic6ad9bade1240b08cc112f275ea2d1b4516d5446 Signed-off-by: Thanh Ha --- docs/dev-guide.rst | 1907 ++++++++++++++++++++++++++++++++++++++++++++ docs/index.rst | 5 + 2 files changed, 1912 insertions(+) create mode 100644 docs/dev-guide.rst diff --git a/docs/dev-guide.rst b/docs/dev-guide.rst new file mode 100644 index 0000000000..58e3b0df68 --- /dev/null +++ b/docs/dev-guide.rst @@ -0,0 +1,1907 @@ +.. _controller-dev-guide: + +Controller +========== + +Overview +-------- + +OpenDaylight Controller is Java-based, model-driven controller using +YANG as its modeling language for various aspects of the system and +applications and with its components serves as a base platform for other +OpenDaylight applications. + +The OpenDaylight Controller relies on the following technologies: + +- **OSGI** - This framework is the back-end of OpenDaylight as it + allows dynamically loading of bundles and packages JAR files, and + binding bundles together for exchanging information. + +- **Karaf** - Application container built on top of OSGI, which + simplifies operational aspects of packaging and installing + applications. + +- **YANG** - a data modeling language used to model configuration and + state data manipulated by the applications, remote procedure calls, + and notifications. + +The OpenDaylight Controller provides following model-driven subsystems +as a foundation for Java applications: + +- :ref:`config_subsystem` - an activation, + dependency-injection and configuration framework, which allows + two-phase commits of configuration and dependency-injection, and + allows for run-time rewiring. + +- :ref:`MD-SAL ` - messaging and data storage + functionality for data, notifications and RPCs modeled by application + developers. MD-SAL uses YANG as the modeling for both interface and + data definitions, and provides a messaging and data-centric runtime + for such services based on YANG modeling. + +- **MD-SAL Clustering** - enables cluster support for core MD-SAL + functionality and provides location-transparent accesss to + YANG-modeled data. + +The OpenDaylight Controller supports external access to applications and +data using following model-driven protocols: + +- **NETCONF** - XML-based RPC protocol, which provides abilities for + client to invoke YANG-modeled RPCs, receive notifications and to + read, modify and manipulate YANG modeled data. + +- **RESTCONF** - HTTP-based protocol, which provides REST-like APIs to + manipulate YANG modeled data and invoke YANG modeled RPCs, using XML + or JSON as payload format. + +.. _mdsal_dev_guide: + +MD-SAL Overview +--------------- + +The Model-Driven Service Adaptation Layer (MD-SAL) is message-bus +inspired extensible middleware component that provides messaging and +data storage functionality based on data and interface models defined by +application developers (i.e. user-defined models). + +The MD-SAL: + +- Defines a **common-layer, concepts, data model building blocks and + messaging patterns** and provides infrastructure / framework for + applications and inter-application communication. + +- Provide common support for user-defined transport and payload + formats, including payload serialization and adaptation (e.g. binary, + XML or JSON). + +The MD-SAL uses **YANG** as the modeling language for both interface and +data definitions, and provides a messaging and data-centric runtime for +such services based on YANG modeling. + +| The MD-SAL provides two different API types (flavours): + +- **MD-SAL Binding:** MD-SAL APIs which extensively uses APIs and + classes generated from YANG models, which provides compile-time + safety. + +- **MD-SAL DOM:** (Document Object Model) APIs which uses DOM-like + representation of data, which makes them more powerful, but provides + less compile-time safety. + +.. note:: + + Model-driven nature of the MD-SAL and **DOM**-based APIs allows for + behind-the-scene API and payload type mediation and transformation + to facilitate seamless communication between applications - this + enables for other components and applications to provide connectors + / expose different set of APIs and derive most of its functionality + purely from models, which all existing code can benefit from without + modification. For example **RESTCONF Connector** is an application + built on top of MD-SAL and exposes YANG-modeled application APIs + transparently via HTTP and adds support for XML and JSON payload + type. + +Basic concepts +~~~~~~~~~~~~~~ + +Basic concepts are building blocks which are used by applications, and +from which MD-SAL uses to define messaging patterns and to provide +services and behavior based on developer-supplied YANG models. + +Data Tree + All state-related data are modeled and represented as data tree, + with possibility to address any element / subtree + + - **Operational Data Tree** - Reported state of the system, + published by the providers using MD-SAL. Represents a feedback + loop for applications to observe state of the network / system. + + - **Configuration Data Tree** - Intended state of the system or + network, populated by consumers, which expresses their intention. + +Instance Identifier + Unique identifier of node / subtree in data tree, which provides + unambiguous information, how to reference and retrieve node / + subtree from conceptual data trees. + +Notification + Asynchronous transient event which may be consumed by subscribers + and they may act upon it + +RPC + asynchronous request-reply message pair, when request is triggered + by consumer, send to the provider, which in future replies with + reply message. + + .. note:: + + In MD-SAL terminology, the term *RPC* is used to define the + input and output for a procedure (function) that is to be + provided by a provider, and mediated by the MD-SAL, that means + it may not result in remote call. + +Messaging Patterns +~~~~~~~~~~~~~~~~~~ + +MD-SAL provides several messaging patterns using broker derived from +basic concepts, which are intended to transfer YANG modeled data between +applications to provide data-centric integration between applications +instead of API-centric integration. + +- **Unicast communication** + + - **Remote Procedure Calls** - unicast between consumer and + provider, where consumer sends **request** message to provider, + which asynchronously responds with **reply** message + +- **Publish / Subscribe** + + - **Notifications** - multicast transient message which is published + by provider and is delivered to subscribers + + - **Data Change Events** - multicast asynchronous event, which is + sent by data broker if there is change in conceptual data tree, + and is delivered to subscribers + +- **Transactional access to Data Tree** + + - Transactional **reads** from conceptual **data tree** - read-only + transactions with isolation from other running transactions. + + - Transactional **modification** to conceptual **data tree** - write + transactions with isolation from other running transactions. + + - **Transaction chaining** + +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** provide :ref:`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. + +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 an 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 the **data broker** itself or using +**transaction chain**. In the case of **transaction chain**, the new +allocated transaction is not based on current state of data tree, but +rather on state introduced by previous transaction from the same chain, +even if the commit for previous transaction has not yet occurred (but +transaction was submitted). + +Write-Only & Read-Write Transaction +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Write-Only and Read-Write transactions provide modification capabilities +for the conceptual data trees. + +1. application allocates new transactions using + ``newWriteOnlyTransaction()`` or ``newReadWriteTransaction()``. + +2. application `modifies data tree <#_modification_of_data_tree>`__ + using ``put``, ``merge`` and/or ``delete``. + +3. application finishes transaction using + ``submit()``, which :ref:`seals transaction + and submits ` it to be processed. + +4. application observes the result of the transaction commit using + either blocking or asynchronous calls. + +The **initial state** of the write transaction is a **stable snapshot** +of the 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 :ref:`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 :ref:`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 + .. code:: java + + void put(LogicalDatastoreType store, InstanceIdentifier 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 + .. code:: java + + void merge(LogicalDatastoreType store, InstanceIdentifier 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 + .. code:: java + + void delete(LogicalDatastoreType store, InstanceIdentifier path); + + Removes a whole subtree from a specified path. + +.. _submitting_transaction: + +Submitting transaction +^^^^^^^^^^^^^^^^^^^^^^ + +Transaction is submitted to be processed and committed using following +method: + +.. code:: java + + CheckedFuture submit(); + +Applications publish 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. The +``submit()`` method 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, the future will fail with +``TransactionFailedException``. + +Application may listen on commit state asynchronously using +``ListenableFuture``. + +.. code:: java + + Futures.addCallback( writeTx.submit(), new FutureCallback() { + public void onSuccess( Void result ) { + LOG.debug("Transaction committed successfully."); + } + + public void onFailure( Throwable t ) { + LOG.error("Commit failed.",e); + } + }); + +- Submits ``writeTx`` and registers application provided + ``FutureCallback`` on returned future. + +- Invoked when future completed successfully - transaction ``writeTx`` + was successfully committed to data tree. + +- 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. + +.. code:: java + + try { + writeTx.submit().checkedGet(); + } catch (TransactionCommitFailedException e) { + LOG.error("Commit failed.",e); + } + +- Submits ``writeTx`` and blocks till commit of ``writeTx`` is + finished. If commit fails ``TransactionCommitFailedException`` will + be thrown. + +- Catches ``TransactionCommitFailedException`` and logs it. + +.. _transaction_local_state: + +Transaction local state +^^^^^^^^^^^^^^^^^^^^^^^ + +Read-Write transactions maintain 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``. + +.. code:: java + + ReadWriteTransaction rwTx = broker.newReadWriteTransaction(); + + rwRx.read(OPERATIONAL,PATH).get(); + rwRx.put(OPERATIONAL,PATH,B); + rwRx.read(OPERATIONAL,PATH).get(); + rwRx.put(OPERATIONAL,PATH,C); + rwRx.read(OPERATIONAL,PATH).get(); + +- Allocates new ``ReadWriteTransaction``. + +- Read from ``rwTx`` will return value ``A`` for ``PATH``. + +- Writes value ``B`` to ``PATH`` using ``rwTx``. + +- Read will return value ``B`` for ``PATH``, since previous write + occurred in same transaction. + +- Writes value ``C`` to ``PATH`` using ``rwTx``. + +- Read will return value ``C`` for ``PATH``, since previous write + occurred in same transaction. + +.. _transaction_isolation: + +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``. + +.. code:: java + + ReadOnlyTransaction txRead = broker.newReadOnlyTransaction(); + ReadWriteTransaction txWrite = broker.newReadWriteTransaction(); + + txRead.read(OPERATIONAL,PATH).get(); + txWrite.put(OPERATIONAL,PATH,B); + txWrite.read(OPERATIONAL,PATH).get(); + txWrite.submit().get(); + txRead.read(OPERATIONAL,PATH).get(); + txAfterCommit = broker.newReadOnlyTransaction(); + txAfterCommit.read(OPERATIONAL,PATH).get(); + +- Allocates read only transaction, which is based on data tree which + contains value ``A`` for ``PATH``. + +- Allocates read write transaction, which is based on data tree which + contains value ``A`` for ``PATH``. + +- Read from read-only transaction returns value ``A`` for ``PATH``. + +- Data tree is updated using read-write transaction, ``PATH`` contains + ``B``. Change is not public and only local to transaction. + +- Read from read-write transaction returns value ``B`` for ``PATH``. + +- Submits changes in read-write transaction to be committed 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. + +- Read from previously allocated read-only transaction still returns + value ``A`` for ``PATH``, since it provides stable and isolated view. + +- Allocates new read-only transaction, which is based on data tree, + which contains value ``B`` for ``PATH``. + +- Read from new read-only transaction return value ``B`` for ``PATH`` + since read-write transaction was committed. + +.. note:: + + Examples contain blocking calls on future only to illustrate that + action happened after other asynchronous action. The use of the + 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: + +Commit failure scenarios +~~~~~~~~~~~~~~~~~~~~~~~~ + +A 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**. The 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 + The data change introduced by this transaction **did not pass + validation** by commit handlers or data was incorrectly structured. + The 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. + +.. code:: java + + WriteTransaction txA = broker.newWriteTransaction(); + WriteTransaction txB = broker.newWriteTransaction(); + + txA.put(CONFIGURATION, PATH, A); + txB.put(CONFIGURATION, PATH, B); + + CheckedFuture futureA = txA.submit(); + CheckedFuture futureB = txB.submit(); + +- Updates ``PATH`` to value ``A`` using ``txA`` + +- Updates ``PATH`` to value ``B`` using ``txB`` + +- Seals & submits ``txA``. The commit will be processed asynchronously + and data tree will be updated to contain value ``A`` for ``PATH``. + The returned ‘ListenableFuture’ will complete successfully once state + is applied to data tree. + +- 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. The returned + ``ListenableFuture`` will fail with ``OptimisticLockFailedException`` + exception, which indicates that concurrent transaction prevented the + submitted transaction from being applied. + +Example asynchronous retry-loop +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: java + + private void doWrite( final int tries ) { + WriteTransaction writeTx = dataBroker.newWriteOnlyTransaction(); + + MyDataObject data = ...; + InstanceIdentifier path = ...; + writeTx.put( LogicalDatastoreType.OPERATIONAL, path, data ); + + Futures.addCallback( writeTx.submit(), new FutureCallback() { + 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``. + +INFO: Following tables stores numeric values and shows data using +``toString()`` to simplify examples. + ++--------------------+--------------------+--------------------+--------------------+ +| 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`` | ++--------------------+--------------------+--------------------+--------------------+ + +Table: Concurrent change resolution for leaves and leaf-list items + ++--------------------+--------------------+--------------------+--------------------+ +| 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] | ++--------------------+--------------------+--------------------+--------------------+ + +Table: Concurrent change resolution for containers, lists, list items + +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 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 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: + + 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. + +.. code:: + + 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. + +.. code:: + + 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. + +Implementing a routed RPC +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Implementation + +Registering implementations +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +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 +(`RoutedServiceTest.java `__ +): + +.. code:: 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 + +.. code:: 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. + +RPCs and cluster +^^^^^^^^^^^^^^^^ + +In case there is is only a single provider of an RPC in the cluster +the RPC registration is propagated to other nodes via Gossip protocol +and the RPC calls from other nodes are correctly routed to the +provider. Since the registrations are not expected to change rapidly +there is a latency of about 1 second until the registration is reflected +on the remote nodes. + + +OpenDaylight Controller MD-SAL: RESTCONF +---------------------------------------- + +RESTCONF operations overview +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +| RESTCONF allows access to datastores in the controller. +| There are two datastores: + +- Config: Contains data inserted via controller + +- Operational: Contains other data + +.. note:: + + | Each request must start with the URI /restconf. + | RESTCONF listens on port 8080 for HTTP requests. + +RESTCONF supports **OPTIONS**, **GET**, **PUT**, **POST**, and +**DELETE** operations. Request and response data can either be in the +XML or JSON format. XML structures according to yang are defined at: +`XML-YANG `__. JSON structures are +defined at: +`JSON-YANG `__. +Data in the request must have a correctly set **Content-Type** field in +the http header with the allowed value of the media type. The media type +of the requested data has to be set in the **Accept** field. Get the +media types for each resource by calling the OPTIONS operation. Most of +the paths of the pathsRestconf endpoints use `Instance +Identifier `__. +```` is used in the explanation of the operations. + +| **** + +- It must start with : where is a + name of the module and is the name of a node in the + module. It is sufficient to just use after + :. Each has to be separated by /. + +- can represent a data node which is a list or container + yang built-in type. If the data node is a list, there must be defined + keys of the list behind the data node name for example, + //. + +- | The format : has to be used in this case as + well: + | Module A has node A1. Module B augments node A1 by adding node X. + Module C augments node A1 by adding node X. For clarity, it has to + be known which node is X (for example: C:X). For more details about + encoding, see: `RESTCONF 02 - Encoding YANG Instance Identifiers in + the Request + URI. `__ + +Mount point +~~~~~~~~~~~ + +| A Node can be behind a mount point. In this case, the URI has to be in + format /**yang-ext:mount**/. The first + is the path to a mount point and the second + is the path to a node behind the mount point. A URI can end in a mount + point itself by using /**yang-ext:mount**. +| More information on how to actually use mountpoints is available at: + `OpenDaylight + Controller:Config:Examples:Netconf `__. + +HTTP methods +~~~~~~~~~~~~ + +OPTIONS /restconf +^^^^^^^^^^^^^^^^^ + +- Returns the XML description of the resources with the required + request and response media types in Web Application Description + Language (WADL) + +GET /restconf/config/ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Returns a data node from the Config datastore. + +- points to a data node which must be retrieved. + +GET /restconf/operational/ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Returns the value of the data node from the Operational datastore. + +- points to a data node which must be retrieved. + +PUT /restconf/config/ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Updates or creates data in the Config datastore and returns the state + about success. + +- points to a data node which must be stored. + +| **Example:** + +:: + + PUT http://:8080/restconf/config/module1:foo/bar + Content-Type: applicaton/xml + + … + + +| **Example with mount point:** + +:: + + PUT http://:8080/restconf/config/module1:foo1/foo2/yang-ext:mount/module2:foo/bar + Content-Type: applicaton/xml + + … + + +POST /restconf/config +^^^^^^^^^^^^^^^^^^^^^ + +- Creates the data if it does not exist + +| For example: + +:: + + POST URL: http://localhost:8080/restconf/config/ + content-type: application/yang.data+json + JSON payload: + + { + "toaster:toaster" : + { + "toaster:toasterManufacturer" : "General Electric", + "toaster:toasterModelNumber" : "123", + "toaster:toasterStatus" : "up" + } + } + +POST /restconf/config/ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Creates the data if it does not exist in the Config datastore, and + returns the state about success. + +- points to a data node where data must be stored. + +- The root element of data must have the namespace (data are in XML) or + module name (data are in JSON.) + +| **Example:** + +:: + + POST http://:8080/restconf/config/module1:foo + Content-Type: applicaton/xml/ + + … + + +**Example with mount point:** + +:: + + http://:8080/restconf/config/module1:foo1/foo2/yang-ext:mount/module2:foo + Content-Type: applicaton/xml + + … + + +POST /restconf/operations/: +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Invokes RPC. + +- : - is the name of the module and + is the name of the RPC in this module. + +- The Root element of the data sent to RPC must have the name “input”. + +- The result can be the status code or the retrieved data having the + root element “output”. + +| **Example:** + +:: + + POST http://:8080/restconf/operations/module1:fooRpc + Content-Type: applicaton/xml + Accept: applicaton/xml + + … + + + The answer from the server could be: + + … + + +| **An example using a JSON payload:** + +:: + + POST http://localhost:8080/restconf/operations/toaster:make-toast + Content-Type: application/yang.data+json + { + "input" : + { + "toaster:toasterDoneness" : "10", + "toaster:toasterToastType":"wheat-bread" + } + } + +.. note:: + + Even though this is a default for the toasterToastType value in the + yang, you still need to define it. + +DELETE /restconf/config/ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Removes the data node in the Config datastore and returns the state + about success. + +- points to a data node which must be removed. + +More information is available in the `RESTCONF +RFC `__. + +How RESTCONF works +~~~~~~~~~~~~~~~~~~ + +| RESTCONF uses these base classes: + +InstanceIdentifier + Represents the path in the data tree + +ConsumerSession + Used for invoking RPCs + +DataBrokerService + Offers manipulation with transactions and reading data from the + datastores + +SchemaContext + Holds information about yang modules + +MountService + Returns MountInstance based on the InstanceIdentifier pointing to a + mount point + +MountInstace + Contains the SchemaContext behind the mount point + +DataSchemaNode + Provides information about the schema node + +SimpleNode + Possesses the same name as the schema node, and contains the value + representing the data node value + +CompositeNode + Can contain CompositeNode-s and SimpleNode-s + +GET in action +~~~~~~~~~~~~~ + +Figure 1 shows the GET operation with URI restconf/config/M:N where M is +the module name, and N is the node name. + +.. figure:: ./images/Get.png + :alt: Get + + Get + +1. The requested URI is translated into the InstanceIdentifier which + points to the data node. During this translation, the DataSchemaNode + that conforms to the data node is obtained. If the data node is + behind the mount point, the MountInstance is obtained as well. + +2. RESTCONF asks for the value of the data node from DataBrokerService + based on InstanceIdentifier. + +3. DataBrokerService returns CompositeNode as data. + +4. StructuredDataToXmlProvider or StructuredDataToJsonProvider is called + based on the **Accept** field from the http request. These two + providers can transform CompositeNode regarding DataSchemaNode to an + XML or JSON document. + +5. XML or JSON is returned as the answer on the request from the client. + +PUT in action +~~~~~~~~~~~~~ + +Figure 2 shows the PUT operation with the URI restconf/config/M:N where +M is the module name, and N is the node name. Data is sent in the +request either in the XML or JSON format. + +.. figure:: ./images/Put.png + :alt: Put + + Put + +1. Input data is sent to JsonToCompositeNodeProvider or + XmlToCompositeNodeProvider. The correct provider is selected based on + the Content-Type field from the http request. These two providers can + transform input data to CompositeNode. However, this CompositeNode + does not contain enough information for transactions. + +2. The requested URI is translated into InstanceIdentifier which points + to the data node. DataSchemaNode conforming to the data node is + obtained during this translation. If the data node is behind the + mount point, the MountInstance is obtained as well. + +3. CompositeNode can be normalized by adding additional information from + DataSchemaNode. + +4. RESTCONF begins the transaction, and puts CompositeNode with + InstanceIdentifier into it. The response on the request from the + client is the status code which depends on the result from the + transaction. + +Something practical +~~~~~~~~~~~~~~~~~~~ + +1. Create a new flow on the switch openflow:1 in table 2. + +| **HTTP request** + +:: + + Operation: POST + URI: http://192.168.11.1:8080/restconf/config/opendaylight-inventory:nodes/node/openflow:1/table/2 + Content-Type: application/xml + +:: + + + + false + + + 1 + + + 1 + + + + + + 2 + 111 + 10 + 10 + false + 2 + + + + 2048 + + + 10.0.0.1/24 + + 0 + 10 + 0 + FooXf22 + 2 + false + + +| **HTTP response** + +:: + + Status: 204 No Content + +1. Change *strict* to *true* in the previous flow. + +| **HTTP request** + +:: + + Operation: PUT + URI: http://192.168.11.1:8080/restconf/config/opendaylight-inventory:nodes/node/openflow:1/table/2/flow/111 + Content-Type: application/xml + +:: + + + + true + + + 1 + + + 1 + + + + + + 2 + 111 + 10 + 10 + false + 2 + + + + 2048 + + + 10.0.0.1/24 + + 0 + 10 + 0 + FooXf22 + 2 + false + + +| **HTTP response** + +:: + + Status: 200 OK + +1. Show flow: check that *strict* is *true*. + +| **HTTP request** + +:: + + Operation: GET + URI: http://192.168.11.1:8080/restconf/config/opendaylight-inventory:nodes/node/openflow:1/table/2/flow/111 + Accept: application/xml + +| **HTTP response** + +:: + + Status: 200 OK + +:: + + + + true + + + 1 + + + 1 + + + + + + 2 + 111 + 10 + 10 + false + 2 + + + + 2048 + + + 10.0.0.1/24 + + 0 + 10 + 0 + FooXf22 + 2 + false + + +1. Delete the flow created. + +| **HTTP request** + +:: + + Operation: DELETE + URI: http://192.168.11.1:8080/restconf/config/opendaylight-inventory:nodes/node/openflow:1/table/2/flow/111 + +| **HTTP response** + +:: + + Status: 200 OK + +Websocket change event notification subscription tutorial +--------------------------------------------------------- + +Subscribing to data change notifications makes it possible to obtain +notifications about data manipulation (insert, change, delete) which are +done on any specified **path** of any specified **datastore** with +specific **scope**. In following examples *{odlAddress}* is address of +server where ODL is running and *{odlPort}* is port on which +OpenDaylight is running. + +Websocket notifications subscription process +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In this section we will learn what steps need to be taken in order to +successfully subscribe to data change event notifications. + +Create stream +^^^^^^^^^^^^^ + +In order to use event notifications you first need to call RPC that +creates notification stream that you can later listen to. You need to +provide three parameters to this RPC: + +- **path**: data store path that you plan to listen to. You can + register listener on containers, lists and leaves. + +- **datastore**: data store type. *OPERATIONAL* or *CONFIGURATION*. + +- **scope**: Represents scope of data change. Possible options are: + + - BASE: only changes directly to the data tree node specified in the + path will be reported + + - ONE: changes to the node and to direct child nodes will be + reported + + - SUBTREE: changes anywhere in the subtree starting at the node will + be reported + +The RPC to create the stream can be invoked via RESTCONF like this: + +- URI: + http://{odlAddress}:{odlPort}/restconf/operations/sal-remote:create-data-change-event-subscription + +- HEADER: Content-Type=application/json + +- OPERATION: POST + +- DATA: + + .. code:: json + + { + "input": { + "path": "/toaster:toaster/toaster:toasterStatus", + "sal-remote-augment:datastore": "OPERATIONAL", + "sal-remote-augment:scope": "ONE" + } + } + +The response should look something like this: + +.. code:: json + + { + "output": { + "stream-name": "data-change-event-subscription/toaster:toaster/toaster:toasterStatus/datastore=CONFIGURATION/scope=SUBTREE" + } + } + +**stream-name** is important because you will need to use it when you +subscribe to the stream in the next step. + +.. note:: + + Internally, this will create a new listener for *stream-name* if it + did not already exist. + +Subscribe to stream +^^^^^^^^^^^^^^^^^^^ + +In order to subscribe to stream and obtain WebSocket location you need +to call *GET* on your stream path. The URI should generally be +http://{odlAddress}:{odlPort}/restconf/streams/stream/{streamName}, +where *{streamName}* is the *stream-name* parameter contained in +response from *create-data-change-event-subscription* RPC from the +previous step. + +- URI: + http://{odlAddress}:{odlPort}/restconf/streams/stream/data-change-event-subscription/toaster:toaster/datastore=CONFIGURATION/scope=SUBTREE + +- OPERATION: GET + +The subscription call may be modified with the following query parameters defined in the RESTCONF RFC: + +- `filter `__ + +- `start-time `__ + +- `end-time `__ + +In addition, the following ODL extension query parameter is supported: + +:odl-leaf-nodes-only: + If this parameter is set to "true", create and update notifications will only + contain the leaf nodes modified instead of the entire subscription subtree. + This can help in reducing the size of the notifications. + +The expected response status is 200 OK and response body should be +empty. You will get your WebSocket location from **Location** header of +response. For example in our particular toaster example location header +would have this value: +*ws://{odlAddress}:8185/toaster:toaster/datastore=CONFIGURATION/scope=SUBTREE* + +.. note:: + + During this phase there is an internal check for to see if a + listener for the *stream-name* from the URI exists. If not, new a + new listener is registered with the DOM data broker. + +Receive notifications +^^^^^^^^^^^^^^^^^^^^^ + +You should now have a data change notification stream created and have +location of a WebSocket. You can use this WebSocket to listen to data +change notifications. To listen to notifications you can use a +JavaScript client or if you are using chrome browser you can use the +`Simple WebSocket +Client `__. + +Also, for testing purposes, there is simple Java application named +WebSocketClient. The application is placed in the +*-sal-rest-connector-classes.class* project. It accepts a WebSocket URI +as and input parameter. After starting the utility (WebSocketClient +class directly in Eclipse/InteliJ Idea) received notifications should be +displayed in console. + +Notifications are always in XML format and look like this: + +.. code:: xml + + + 2014-09-11T09:58:23+02:00 + + + /meae:toaster + updated + + + + + + + +Example use case +~~~~~~~~~~~~~~~~ + +The typical use case is listening to data change events to update web +page data in real-time. In this tutorial we will be using toaster as the +base. + +When you call *make-toast* RPC, it sets *toasterStatus* to "down" to +reflect that the toaster is busy making toast. When it finishes, +*toasterStatus* is set to "up" again. We will listen to this toaster +status changes in data store and will reflect it on our web page in +real-time thanks to WebSocket data change notification. + +Simple javascript client implementation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +We will create simple JavaScript web application that will listen +updates on *toasterStatus* leaf and update some element of our web page +according to new toaster status state. + +Create stream +^^^^^^^^^^^^^ + +First you need to create stream that you are planing to subscribe to. +This can be achieved by invoking "create-data-change-event-subscription" +RPC on RESTCONF via AJAX request. You need to provide data store +**path** that you plan to listen on, **data store type** and **scope**. +If the request is successful you can extract the **stream-name** from +the response and use that to subscribe to the newly created stream. The +*{username}* and *{password}* fields represent your credentials that you +use to connect to OpenDaylight via RESTCONF: + +.. note:: + + The default user name and password are "admin". + +.. code:: javascript + + function createStream() { + $.ajax( + { + url: 'http://{odlAddress}:{odlPort}/restconf/operations/sal-remote:create-data-change-event-subscription', + type: 'POST', + headers: { + 'Authorization': 'Basic ' + btoa('{username}:{password}'), + 'Content-Type': 'application/json' + }, + data: JSON.stringify( + { + 'input': { + 'path': '/toaster:toaster/toaster:toasterStatus', + 'sal-remote-augment:datastore': 'OPERATIONAL', + 'sal-remote-augment:scope': 'ONE' + } + } + ) + }).done(function (data) { + // this function will be called when ajax call is executed successfully + subscribeToStream(data.output['stream-name']); + }).fail(function (data) { + // this function will be called when ajax call fails + console.log("Create stream call unsuccessful"); + }) + } + +Subscribe to stream +^^^^^^^^^^^^^^^^^^^ + +The Next step is to subscribe to the stream. To subscribe to the stream +you need to call *GET* on +*http://{odlAddress}:{odlPort}/restconf/streams/stream/{stream-name}*. +If the call is successful, you get WebSocket address for this stream in +**Location** parameter inside response header. You can get response +header by calling *getResponseHeader(\ *Location*)* on HttpRequest +object inside *done()* function call: + +.. code:: javascript + + function subscribeToStream(streamName) { + $.ajax( + { + url: 'http://{odlAddress}:{odlPort}/restconf/streams/stream/' + streamName; + type: 'GET', + headers: { + 'Authorization': 'Basic ' + btoa('{username}:{password}'), + } + } + ).done(function (data, textStatus, httpReq) { + // we need function that has http request object parameter in order to access response headers. + listenToNotifications(httpReq.getResponseHeader('Location')); + }).fail(function (data) { + console.log("Subscribe to stream call unsuccessful"); + }); + } + +Receive notifications +^^^^^^^^^^^^^^^^^^^^^ + +Once you got WebSocket server location you can now connect to it and +start receiving data change events. You need to define functions that +will handle events on WebSocket. In order to process incoming events +from OpenDaylight you need to provide a function that will handle +*onmessage* events. The function must have one parameter that represents +the received event object. The event data will be stored in +*event.data*. The data will be in an XML format that you can then easily +parse using jQuery. + +.. code:: javascript + + function listenToNotifications(socketLocation) { + try { + var notificatinSocket = new WebSocket(socketLocation); + + notificatinSocket.onmessage = function (event) { + // we process our received event here + console.log('Received toaster data change event.'); + $($.parseXML(event.data)).find('data-change-event').each( + function (index) { + var operation = $(this).find('operation').text(); + if (operation == 'updated') { + // toaster status was updated so we call function that gets the value of toasterStatus leaf + updateToasterStatus(); + return false; + } + } + ); + } + notificatinSocket.onerror = function (error) { + console.log("Socket error: " + error); + } + notificatinSocket.onopen = function (event) { + console.log("Socket connection opened."); + } + notificatinSocket.onclose = function (event) { + console.log("Socket connection closed."); + } + // if there is a problem on socket creation we get exception (i.e. when socket address is incorrect) + } catch(e) { + alert("Error when creating WebSocket" + e ); + } + } + +The *updateToasterStatus()* function represents function that calls +*GET* on the path that was modified and sets toaster status in some web +page element according to received data. After the WebSocket connection +has been established you can test events by calling make-toast RPC via +RESTCONF. + +.. note:: + + for more information about WebSockets in JavaScript visit `Writing + WebSocket client + applications `__ + +.. _config_subsystem: + +Config Subsystem +---------------- + +Overview +~~~~~~~~ + +The Controller configuration operation has three stages: + +- First, a Proposed configuration is created. Its target is to replace + the old configuration. + +- Second, the Proposed configuration is validated, and then committed. + If it passes validation successfully, the Proposed configuration + state will be changed to Validated. + +- Finally, a Validated configuration can be Committed, and the affected + modules can be reconfigured. + +In fact, each configuration operation is wrapped in a transaction. Once +a transaction is created, it can be configured, that is to say, a user +can abort the transaction during this stage. After the transaction +configuration is done, it is committed to the validation stage. In this +stage, the validation procedures are invoked. If one or more validations +fail, the transaction can be reconfigured. Upon success, the second +phase commit is invoked. If this commit is successful, the transaction +enters the last stage, committed. After that, the desired modules are +reconfigured. If the second phase commit fails, it means that the +transaction is unhealthy - basically, a new configuration instance +creation failed, and the application can be in an inconsistent state. + +.. figure:: ./images/configuration.jpg + :alt: Configuration states + + Configuration states + +.. figure:: ./images/Transaction.jpg + :alt: Transaction states + + Transaction states + +Validation +~~~~~~~~~~ + +To secure the consistency and safety of the new configuration and to +avoid conflicts, the configuration validation process is necessary. +Usually, validation checks the input parameters of a new configuration, +and mostly verifies module-specific relationships. The validation +procedure results in a decision on whether the proposed configuration is +healthy. + +Dependency resolver +~~~~~~~~~~~~~~~~~~~ + +Since there can be dependencies between modules, a change in a module +configuration can affect the state of other modules. Therefore, we need +to verify whether dependencies on other modules can be resolved. The +Dependency Resolver acts in a manner similar to dependency injectors. +Basically, a dependency tree is built. + +APIs and SPIs +~~~~~~~~~~~~~ + +This section describes configuration system APIs and SPIs. + +SPIs +^^^^ + +**Module** org.opendaylight.controller.config.spi. Module is the common +interface for all modules: every module must implement it. The module is +designated to hold configuration attributes, validate them, and create +instances of service based on the attributes. This instance must +implement the AutoCloseable interface, owing to resources clean up. If +the module was created from an already running instance, it contains an +old instance of the module. A module can implement multiple services. If +the module depends on other modules, setters need to be annotated with +@RequireInterface. + +**Module creation** + +1. The module needs to be configured, set with all required attributes. + +2. The module is then moved to the commit stage for validation. If the + validation fails, the module attributes can be reconfigured. + Otherwise, a new instance is either created, or an old instance is + reconfigured. A module instance is identified by ModuleIdentifier, + consisting of the factory name and instance name. + +| **ModuleFactory** org.opendaylight.controller.config.spi. The + ModuleFactory interface must be implemented by each module factory. +| A module factory can create a new module instance in two ways: + +- From an existing module instance + +- | An entirely new instance + | ModuleFactory can also return default modules, useful for + populating registry with already existing configurations. A module + factory implementation must have a globally unique name. + +APIs +^^^^ + ++--------------------------------------+--------------------------------------+ +| ConfigRegistry | Represents functionality provided by | +| | a configuration transaction (create, | +| | destroy module, validate, or abort | +| | transaction). | ++--------------------------------------+--------------------------------------+ +| ConfigTransactionController | Represents functionality for | +| | manipulating with configuration | +| | transactions (begin, commit config). | ++--------------------------------------+--------------------------------------+ +| RuntimeBeanRegistratorAwareConfiBean | The module implementing this | +| | interface will receive | +| | RuntimeBeanRegistrator before | +| | getInstance is invoked. | ++--------------------------------------+--------------------------------------+ + +Runtime APIs +^^^^^^^^^^^^ + ++--------------------------------------+--------------------------------------+ +| RuntimeBean | Common interface for all runtime | +| | beans | ++--------------------------------------+--------------------------------------+ +| RootRuntimeBeanRegistrator | Represents functionality for root | +| | runtime bean registration, which | +| | subsequently allows hierarchical | +| | registrations | ++--------------------------------------+--------------------------------------+ +| HierarchicalRuntimeBeanRegistration | Represents functionality for runtime | +| | bean registration and | +| | unreregistration from hierarchy | ++--------------------------------------+--------------------------------------+ + +JMX APIs +^^^^^^^^ + +| JMX API is purposed as a transition between the Client API and the JMX + platform. + ++--------------------------------------+--------------------------------------+ +| ConfigTransactionControllerMXBean | Extends ConfigTransactionController, | +| | executed by Jolokia clients on | +| | configuration transaction. | ++--------------------------------------+--------------------------------------+ +| ConfigRegistryMXBean | Represents entry point of | +| | configuration management for | +| | MXBeans. | ++--------------------------------------+--------------------------------------+ +| Object names | Object Name is the pattern used in | +| | JMX to locate JMX beans. It consists | +| | of domain and key properties (at | +| | least one key-value pair). Domain is | +| | defined as | +| | "org.opendaylight.controller". The | +| | only mandatory property is "type". | ++--------------------------------------+--------------------------------------+ + +Use case scenarios +^^^^^^^^^^^^^^^^^^ + +| A few samples of successful and unsuccessful transaction scenarios + follow: + +**Successful commit scenario** + +1. The user creates a transaction calling creteTransaction() method on + ConfigRegistry. + +2. ConfigRegisty creates a transaction controller, and registers the + transaction as a new bean. + +3. Runtime configurations are copied to the transaction. The user can + create modules and set their attributes. + +4. The configuration transaction is to be committed. + +5. The validation process is performed. + +6. After successful validation, the second phase commit begins. + +7. Modules proposed to be destroyed are destroyed, and their service + instances are closed. + +8. Runtime beans are set to registrator. + +9. The transaction controller invokes the method getInstance on each + module. + +10. The transaction is committed, and resources are either closed or + released. + +| **Validation failure scenario** +| The transaction is the same as the previous case until the validation + process. + +1. If validation fails, (that is to day, illegal input attributes values + or dependency resolver failure), the validationException is thrown + and exposed to the user. + +2. The user can decide to reconfigure the transaction and commit again, + or abort the current transaction. + +3. On aborted transactions, TransactionController and JMXRegistrator are + properly closed. + +4. Unregistration event is sent to ConfigRegistry. + +Default module instances +^^^^^^^^^^^^^^^^^^^^^^^^ + +The configuration subsystem provides a way for modules to create default +instances. A default instance is an instance of a module, that is +created at the module bundle start-up (module becomes visible for +configuration subsystem, for example, its bundle is activated in the +OSGi environment). By default, no default instances are produced. + +The default instance does not differ from instances created later in the +module life-cycle. The only difference is that the configuration for the +default instance cannot be provided by the configuration subsystem. The +module has to acquire the configuration for these instances on its own. +It can be acquired from, for example, environment variables. After the +creation of a default instance, it acts as a regular instance and fully +participates in the configuration subsystem (It can be reconfigured or +deleted in following transactions.). diff --git a/docs/index.rst b/docs/index.rst index ffe79afd65..71cfb2b37d 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -3,3 +3,8 @@ ######################## Controller Documentation ######################## + +.. toctree:: + :maxdepth: 1 + + dev-guide.rst -- 2.36.6