.. autofunction:: schemes
.. autoclass:: Capabilities
- :members:
\ No newline at end of file
+
+ :members:
+
+ .. describe:: ":cap" in caps
+
+ Check for the presence of capability. In addition to the URI, for capabilities of the form `urn:ietf:params:netconf:capability:$name:$version` their shorthand can be used as a key. For example, for `urn:ietf:params:netconf:capability:candidate:1.0` the shorthand would be `:candidate`. If version is significant, use `:candidate:1.0` as key.
+
+ .. describe:: iter(caps)
+
+ Return an iterator over the full URI's of capabilities represented by this object.
\ No newline at end of file
exclude_trees = []
# The reST default role (used for this markup: `text`) to use for all documents.
-default_role = 'class'
+#default_role = 'obj'
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
Welcome
=======
-``ncclient`` is a Python library for NETCONF clients. It aims to offer an intuitive API that
-sensibly maps the XML-encoded nature of NETCONF to Python constructs and idioms, and make writing
-network-management scripts easier. Other key features are:
+`ncclient` is a Python library for NETCONF clients. It aims to offer an intuitive API that sensibly maps the XML-encoded nature of NETCONF to Python constructs and idioms, and make writing network-management scripts easier. Other key features are:
* Supports all operations and capabilities defined in :rfc:`4741`.
* Request pipelining.
* Keeping XML out of the way unless really needed.
* Extensible. New transport mappings and capabilities/operations can be easily added.
-It is suitable for Python 2.6+ (not Python 3 yet, though), and depends on `paramiko
-<http://www.lag.net/paramiko/>`_, an SSH library.
+It is suitable for Python 2.6+ (not Python 3 yet, though), and depends on `paramiko <http://www.lag.net/paramiko/>`_, an SSH library.
-The best way to introduce is of course, through a simple code example::
+The best way to introduce is through a simple code example::
from ncclient import manager
manager
api
- extending
Indices and tables
------------------
.. automodule:: ncclient.manager
:synopsis: High-level API
-Module data
------------
-These attributes control what capabilties are exchanged with the NETCONF server and what operations
-are available through the `Manager` API.
+Customizing
+------------
+
+These attributes control what capabilties are exchanged with the NETCONF server and what operations are available through the :class:`Manager` API.
.. autodata:: OPERATIONS
.. autodata:: CAPABILITIES
+
Factory functions
-----------------
-A `Manager` instance is created using a factory function.
+A :class:`Manager` instance is created using a factory function.
-.. autofunction:: connect_ssh(host[, port=830, timeout=None, unknown_host_cb=default_unknown_host_cb, username=None, password, key_filename=None, allow_agent=True, look_for_keys=True])
+.. autofunction:: connect_ssh
.. autodata:: connect
Manager
-------
-Exposes an API for RPC operations as method calls. The return type of these methods depends on
-whether we are is in :attr:`asynchronous or synchronous mode <ncclient.manager.Manager.async_mode>`.
+Exposes an API for RPC operations as method calls. The return type of these methods depends on whether we are in :attr:`asynchronous or synchronous mode <ncclient.manager.Manager.async_mode>`.
-In synchronous mode replies are awaited and the corresponding `~ncclient.operations.RPCReply` object
-is returned. Depending on the :attr:`exception raising mode <ncclient.manager.Manager.raise_mode>`,
-an *rpc-error* in the reply may be raised as :exc:`RPCError` exceptions.
+In synchronous mode replies are awaited and the corresponding :class:`~ncclient.operations.RPCReply` object is returned. Depending on the :attr:`exception raising mode <ncclient.manager.Manager.raise_mode>`, an `rpc-error` in the reply may be raised as an :exc:`~ncclient.operations.RPCError` exception.
-However in asynchronous mode, operations return immediately with an `~ncclient.operations.RPC`
-object. Error handling and checking for whether a reply has been received must be dealt with
-manually. See the `~ncclient.operations.RPC` documentation for details.
+However in asynchronous mode, operations return immediately with the corresponding :class:`~ncclient.operations.RPC` object. Error handling and checking for whether a reply has been received must be dealt with manually. See the :class:`~ncclient.operations.RPC` documentation for details.
-Note that in case of the *get* and *get-config* operations, the reply is an instance of
-`~ncclient.operations.GetReply` which exposes the additional attributes
-:attr:`~ncclient.operations.GetReply.data` (as `~xml.etree.ElementTree.Element`) and
-:attr:`~ncclient.operations.GetReply.data_xml` (as `string`), which are of primary interest in case
-of these operations.
+Note that in case of the :meth:`~Manager.get` and :meth:`~Manager.get_config` operations, the reply is an instance of :class:`~ncclient.operations.GetReply` which exposes the additional attributes :attr:`~ncclient.operations.GetReply.data` (as :class:`~xml.etree.ElementTree.Element`) and :attr:`~ncclient.operations.GetReply.data_xml` (as a string), which are of primary interest in case of these operations.
-Presence of capabilities is verified to the extent possible, and you can expect a
-:exc:`~ncclient.operations.MissingCapabilityError` if something is amiss. In case of transport-layer
-errors, e.g. unexpected session close, :exc:`~ncclient.transport.TransportError` will be raised.
+Presence of capabilities is verified to the extent possible, and you can expect a :exc:`~ncclient.operations.MissingCapabilityError` if something is amiss. In case of transport-layer errors, e.g. unexpected session close, :exc:`~ncclient.transport.TransportError` will be raised.
-.. class:: Manager
-
- For details on the expected behavior of the operations and their parameters
- refer to :rfc:`4741`.
+.. autoclass:: Manager
- Manager instances are also context managers so you can use it like this::
+ .. automethod:: get_config(source, filter=None)
- with manager.connect("host") as m:
- # do your stuff
-
- ... or like this::
-
- m = manager.connect("host")
- try:
- # do your stuff
- finally:
- m.close()
-
- .. method:: get_config(source[, filter=None])
-
- Retrieve all or part of a specified configuration.
-
- :param source: name of the configuration datastore being queried
- :type source: `string`
-
- :param filter: portions of the device configuration to retrieve (by default entire configuration is retrieved)
- :type filter: :ref:`filter_params`
-
- .. method:: edit_config(target, config[, default_operation=None, test_option=None, error_option=None])
-
- Loads all or part of a specified configuration to the specified target configuration.
-
- The ``"rollback-on-error"`` *error_option* depends on the ``:rollback-on-error`` capability.
-
- :param target: name of the configuration datastore being edited
- :type target: `string`
-
- :param config: configuration (must be rooted in *<config> .. </config>*)
- :type config: `string` or `~xml.etree.ElementTree.Element`
-
- :param default_operation: one of { ``"merge"``, ``"replace"``, or ``"none"`` }
- :type default_operation: `string`
-
- :param test_option: one of { ``"test_then_set"``, ``"set"`` }
- :type test_option: `string`
-
- :param error_option: one of { ``"stop-on-error"``, ``"continue-on-error"``, ``"rollback-on-error"`` }
- :type error_option: `string`
-
- .. method:: copy_config(source, target)
-
- Create or replace an entire configuration datastore with the contents of another complete
- configuration datastore.
-
- :param source: configuration datastore to use as the source of the copy operation or *<config>* element containing the configuration subtree to copy
- :type source: :ref:`srctarget_params`
-
- :param target: configuration datastore to use as the destination of the copy operation
- :type target: :ref:`srctarget_params`
-
- .. method:: delete_config(target)
-
- Delete a configuration datastore.
-
- :param target: name or URL of configuration datastore to delete
- :type: :ref:`srctarget_params`
-
- .. method:: lock(target)
-
- Allows the client to lock the configuration system of a device.
-
- :param target: name of the configuration datastore to lock
- :type target: `string`
-
- .. method:: unlock(target)
-
- Release a configuration lock, previously obtained with the
- :meth:`~ncclient.manager.Manager.lock` operation.
-
- :param target: name of the configuration datastore to unlock
- :type target: `string`
-
- .. method:: locked(target)
-
- Returns a context manager for a lock on a datastore, e.g.::
-
- with m.locked("running"):
- # do your stuff
+ .. automethod:: edit_config(target, config, default_operation=None, test_option=None, error_option=None)
- ... instead of::
-
- m.lock("running")
- try:
- # do your stuff
- finally:
- m.unlock("running")
-
- :param target: name of configuration datastore to lock
- :type target: `string`
-
- :rtype: `~ncclient.operations.LockContext`
-
- .. method:: get([filter=None])
-
- Retrieve running configuration and device state information.
-
- :param filter: portions of the device configuration to retrieve (by default entire configuration is retrieved)
- :type filter: :ref:`filter_params`
-
- .. method:: close_session()
-
- Request graceful termination of the NETCONF session, and also close the transport.
-
- .. method:: kill_session(session_id)
-
- Force the termination of a NETCONF session (not the current one!).
-
- :param session_id: session identifier of the NETCONF session to be terminated
- :type session_id: `string`
-
- .. method:: commit([confirmed=False, timeout=None])
-
- Commit the candidate configuration as the device's new current configuration. Depends on the
- *:candidate* capability.
-
- A confirmed commit (i.e. if *confirmed* is :const:`True`) is reverted if there is no
- followup commit within the *timeout* interval. If no timeout is specified the confirm
- timeout defaults to 600 seconds (10 minutes). A confirming commit may have the *confirmed*
- parameter but this is not required. Depends on the *:confirmed-commit* capability.
-
- :param confirmed: whether this is a confirmed commit
- :type confirmed: `bool`
-
- :param timeout: confirm timeout in seconds
- :type timeout: `int`
-
- .. method:: discard_changes()
-
- Revert the candidate configuration to the currently running configuration. Any uncommitted
- changes are discarded.
-
- .. method:: validate(source)
-
- Validate the contents of the specified configuration.
-
- :param source: name of the configuration datastore being validated or *<config>* element containing the configuration subtree to be validated
- :type source: :ref:`srctarget_params`
-
- .. attribute:: async_mode
-
- Specify whether operations are executed asynchronously (:const:`True`)
- or synchronously (:const:`False`) (the default).
-
- .. attribute:: raise_mode
-
- Specify which errors are raised as :exc:`~ncclient.operations.RPCError` exceptions.
- Valid values:
-
- * ``"all"`` -- any kind of *rpc-error* (error or warning)
- * ``"errors"`` -- where the *error-type* element says it is an error
- * ``"none"`` -- neither
+ .. automethod:: copy_config(source, target)
+
+ .. automethod:: delete_config(target)
+
+ .. automethod:: lock(target)
- .. attribute:: client_capabilities
-
- `~ncclient.capabilities.Capabilities` object representing the client's capabilities.
-
- .. attribute:: server_capabilities
-
- `~ncclient.capabilities.Capabilities` object representing the server's capabilities.
-
- .. attribute:: session_id
-
- *session-id* assigned by the NETCONF server.
+ .. automethod:: unlock(target)
+
+ .. automethod:: locked(target)
+
+ .. automethod:: get()
- .. attribute:: connected
-
- Bolean value indicating whether currently connected to the NETCONF server.
+ .. automethod:: close_session()
+
+ .. automethod:: kill_session(session_id)
+
+ .. automethod:: commit(confirmed=False, timeout=None)
+
+ .. automethod:: discard_changes()
+
+ .. automethod:: validate(source)
+
+ .. autoattribute:: async_mode
+
+ .. autoattribute:: raise_mode
+
+ .. autoattribute:: client_capabilities
+
+ .. autoattribute:: server_capabilities
+
+ .. autoattribute:: session_id
+
+ .. autoattribute:: connected
Special kinds of parameters
Source and target parameters
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-Where an method takes a *source* or *target* argument, usually a datastore name or URL is expected.
-The latter depends on the ``:url`` capability and on whether the specific URL scheme is supported.
-Either must be specified as a `string`. For example, ``"running"``,
-``"ftp://user:pass@host/config"``.
+Where an method takes a *source* or *target* argument, usually a datastore name or URL is expected. The latter depends on the `:url` capability and on whether the specific URL scheme is supported. Either must be specified as a string. For example, `"running"`, `"ftp://user:pass@host/config"`.
-If the source may be a *<config>* element, e.g. as allowed for the *validate* RPC, it can also be
-specified as an XML string or an `~xml.etree.ElementTree.Element` object.
+If the source may be a `config` element, e.g. as allowed for the `validate` RPC, it can also be specified as an XML string or an :class:`~xml.etree.ElementTree.Element` object.
.. _filter_params:
Where a method takes a *filter* argument, it can take on the following types:
-* A ``tuple`` of *(type, criteria)*.
+* A tuple of *(type, criteria)*.
- Here *type* has to be one of ``"xpath"`` or ``"subtree"``.
+ Here *type* has to be one of `"xpath"` or `"subtree"`.
- * For ``"xpath"`` the *criteria* should be a `string` containing the XPath expression.
- * For ``"subtree"`` the *criteria* should be an XML string or an
- `~xml.etree.ElementTree.Element` object containing the criteria.
+ * For `"xpath"` the *criteria* should be a string containing the XPath expression.
+ * For `"subtree"` the *criteria* should be an XML string or an :class:`~xml.etree.ElementTree.Element` object containing the criteria.
-* A *<filter>* element as an XML string or an `~xml.etree.ElementTree.Element` object.
+* A `<filter>` element as an XML string or an :class:`~xml.etree.ElementTree.Element` object.
:mod:`~ncclient.operations` -- Everything RPC
=============================================
-Base classes
-------------
.. module:: ncclient.operations
:synopsis: Everything RPC
-.. autoclass:: RPC(session[, async=False, timeout=None, raise_mode="none"])
+.. autoclass:: RaiseMode
+ :members: NONE, ERRORS, ALL
+
+Base classes
+------------
+
+.. autoclass:: RPC(session, async=False, timeout=None, raise_mode="none")
:members: DEPENDS, REPLY_CLS, _assert, _request, request, event, error, reply, raise_mode, is_async, timeout
.. autoclass:: RPCReply
- :members: xml, ok, error, errors
+ :members: xml, ok, error, errors, _parsing_hook
.. autoexception:: RPCError
:show-inheritance:
Operations
----------
-*TODO*
-
-The operation classes are currently undocumented. See documentation of `~ncclient.manager.Manager`
-for methods that utilize the operation classes. The parameters accepted by :meth:`~RPC.request` for
-these classes are the same.
+The operation classes are currently undocumented. See documentation of :class:`~ncclient.manager.Manager` for methods that utilize the operation classes. The parameters accepted by :meth:`~RPC.request` for these classes are the same.
Replies with data
-----------------
:show-inheritance:
:members: load_known_hosts, close, transport
- .. automethod:: connect(host[, port=830, timeout=None, username=None, password=None, key_filename=None, allow_agent=True, look_for_keys=True])
+ .. automethod:: connect(host[, port=830, timeout=None, unknown_host_cb=default_unknown_host_cb, username=None, password=None, key_filename=None, allow_agent=True, look_for_keys=True])
Errors
------
:show-inheritance:
.. autoexception:: SSHUnknownHostError
- :show-inheritance:
-
+ :show-inheritance:
\ No newline at end of file
:mod:`~ncclient.xml_` -- XML handling
=====================================
-.. module:: ncclient.xml_
+.. automodule:: ncclient.xml_
:synopsis: XML handling
.. autoexception:: XMLError
.. autodata:: FLOWMON_1_0
-.. function:: register_namespace(prefix, uri)
-
- ElementTree's namespace map determines the prefixes for namespace URI's when serializing to XML.
- This method allows modifying this map to specify a prefix for a namespace URI.
+.. autofunction:: register_namespace(prefix, uri)
.. autofunction:: qualify
.. autofunction:: parse_root
-.. autofunction:: validated_element
-
-..
\ No newline at end of file
+.. autofunction:: validated_element
\ No newline at end of file
return []
def schemes(url_uri):
- """Given a URI that has a *scheme* query string (i.e. *:url* capability URI), will return a list
- of supported schemes.
- """
+ "Given a URI that has a *scheme* query string (i.e. `:url` capability URI), will return a list of supported schemes."
return url_uri.partition("?scheme=")[2].split(",")
class Capabilities:
- """Represents the set of capabilities available to a NETCONF client or server. It is initialized
- with a list of capability URI's. These can be iterated over.
-
- Presence of a capability can be checked with the *in* operation. In addition to the URI, for
- capabilities of the form *urn:ietf:params:netconf:capability:$name:$version* their shorthand can
- be used as a key. For example, for *urn:ietf:params:netconf:capability:candidate:1.0* the
- shorthand would be *:candidate*. If version is significant, use *:candidate:1.0* as key.
- """
+ "Represents the set of capabilities available to a NETCONF client or server. It is initialized with a list of capability URI's."
def __init__(self, capabilities):
self._dict = {}
# See the License for the specific language governing permissions and
# limitations under the License.
-"This module is a thin layer of abstraction around the library. It exposes all core functionality."
+"""This module is a thin layer of abstraction around the library. It exposes all core functionality."""
import capabilities
import operations
"urn:liberouter:params:netconf:capability:power-control:1.0"
"urn:ietf:params:netconf:capability:interleave:1.0"
]
-"A list of URI's representing the client's capabilities. This is used during the initial capability exchange. Modify this if you need to announce some capability not already included."
+"""A list of URI's representing the client's capabilities. This is used during the initial capability exchange. Modify this if you need to announce some capability not already included."""
OPERATIONS = {
"get": operations.Get,
"poweroff_machine": operations.PoweroffMachine,
"reboot_machine": operations.RebootMachine
}
-"""Dictionary of method names and corresponding `~ncclient.operations.RPC` subclasses. It is used to lookup operations, e.g. "get_config" is mapped to `~ncclient.operations.GetConfig`. It is thus possible to add additional operations to the `Manager` API."""
+"""Dictionary of method names and corresponding :class:`~ncclient.operations.RPC` subclasses. It is used to lookup operations, e.g. `get_config` is mapped to :class:`~ncclient.operations.GetConfig`. It is thus possible to add additional operations to the :class:`Manager` API."""
def connect_ssh(*args, **kwds):
- """Initializes a NETCONF session over SSH, and creates a connected `Manager` instance. *host* must be specified, all the other arguments are optional and depend on the kind of host key verification and user authentication you want to complete.
-
- For the purpose of host key verification, on -NIX systems a user's :file:`~/.ssh/known_hosts` file is automatically considered. The *unknown_host_cb* argument specifies a callback that will be invoked when the server's host key cannot be verified. See :func:`~ncclient.transport.ssh.default_unknown_host_cb` for function signature.
-
- First, ``publickey`` authentication is attempted. If a specific *key_filename* is specified, it
- will be loaded and authentication attempted using it. If *allow_agent* is :const:`True` and an
- SSH agent is running, the keys provided by the agent will be tried. If *look_for_keys* is
- :const:`True`, keys in the :file:`~/.ssh/id_rsa` and :file:`~.ssh/id_dsa` will be tried. In case
- an encrypted key file is encountered, the *password* argument will be used as a decryption
- passphrase.
-
- If ``publickey`` authentication fails and the *password* argument has been supplied, ``password`` / ``keyboard-interactive`` SSH authentication will be attempted.
-
- :param host: hostname or address on which to connect
- :type host: `string`
-
- :param port: port on which to connect
- :type port: `int`
-
- :param timeout: timeout for socket connect
- :type timeout: `int`
-
- :param unknown_host_cb: optional; callback that is invoked when host key verification fails
- :type unknown_host_cb: `function`
-
- :param username: username to authenticate with, if not specified the username of the logged-in user is used
- :type username: `string`
-
- :param password: password for ``password`` authentication or passphrase for decrypting private key files
- :type password: `string`
-
- :param key_filename: location of a private key file on the file system
- :type key_filename: `string`
-
- :param allow_agent: whether to try connecting to SSH agent for keys
- :type allow_agent: `bool`
-
- :param look_for_keys: whether to look in usual locations for keys
- :type look_for_keys: `bool`
-
- :raises: :exc:`~ncclient.transport.SSHUnknownHostError`
- :raises: :exc:`~ncclient.transport.AuthenticationError`
-
- :rtype: `Manager`
+ """Initialize a :class:`Manager` over the SSH transport. For documentation of arguments see :meth:`ncclient.transport.SSHSession.connect`.
+
+ The underlying :class:`ncclient.transport.SSHSession` is created with :data:`CAPABILITIES`. It is first instructed to :meth:`~ncclient.transport.SSHSession.load_known_hosts` and then all the provided arguments are passed directly to its implementation of :meth:`~ncclient.transport.SSHSession.connect`.
"""
session = transport.SSHSession(capabilities.Capabilities(CAPABILITIES))
session.load_known_hosts()
"Same as :func:`connect_ssh`, since SSH is the default (and currently, the only) transport."
class OpExecutor(type):
+
def __new__(cls, name, bases, attrs):
def make_wrapper(op_cls):
def wrapper(self, *args, **kwds):
class Manager(object):
- __metaclass__ = OpExecutor
+ """For details on the expected behavior of the operations and their parameters refer to :rfc:`4741`.
+
+ Manager instances are also context managers so you can use it like this::
+
+ with manager.connect("host") as m:
+ # do your stuff
- RAISE_NONE = 0
- RAISE_ERRORS = 1
- RAISE_ALL = 2
+ ... or like this::
- def __init__(self, session):
+ m = manager.connect("host")
+ try:
+ # do your stuff
+ finally:
+ m.close_session()
+ """
+
+ __metaclass__ = OpExecutor
+
+ def __init__(self, session, timeout=30):
self._session = session
self._async_mode = False
- self._timeout = None
- self._raise_mode = self.RAISE_ALL
+ self._timeout = timeout
+ self._raise_mode = operations.RaiseMode.ALL
def __enter__(self):
return self
self.close_session()
return False
+ def __set_async_mode(self, mode):
+ self._async_mode = mode
+
+ def __set_raise_mode(self, mode):
+ assert(choice in (operations.RaiseMode.NONE, operations.RaiseMode.ERRORS, operations.RaiseMode.ALL))
+ self._raise_mode = mode
+
def execute(self, cls, *args, **kwds):
return cls(self._session,
async=self._async_mode,
raise_mode=self._raise_mode).request(*args, **kwds)
def locked(self, target):
+ """Returns a context manager for a lock on a datastore, where *target* is the name of the configuration datastore to lock, e.g.::
+
+ with m.locked("running"):
+ # do your stuff
+
+ ... instead of::
+
+ m.lock("running")
+ try:
+ # do your stuff
+ finally:
+ m.unlock("running")
+ """
return operations.LockContext(self._session, target)
@property
def client_capabilities(self):
+ ":class:`~ncclient.capabilities.Capabilities` object representing the client's capabilities."
return self._session._client_capabilities
@property
def server_capabilities(self):
+ ":class:`~ncclient.capabilities.Capabilities` object representing the server's capabilities."
return self._session._server_capabilities
@property
def session_id(self):
+ "`session-id` assigned by the NETCONF server."
return self._session.id
@property
def connected(self):
+ "Whether currently connected to the NETCONF server."
return self._session.connected
- def set_async_mode(self, mode):
- self._async_mode = mode
-
- def set_raise_mode(self, mode):
- assert(choice in (self.RAISE_NONE, self.RAISE_ERRORS, self.RAISE_ALL))
- self._raise_mode = mode
+ async_mode = property(fget=lambda self: self._async_mode, fset=__set_async_mode)
+ "Specify whether operations are executed asynchronously (`True`) or synchronously (`False`) (the default)."
- async_mode = property(fget=lambda self: self._async_mode, fset=set_async_mode)
- raise_mode = property(fget=lambda self: self._raise_mode, fset=set_raise_mode)
+ raise_mode = property(fget=lambda self: self._raise_mode, fset=__set_raise_mode)
+ "Specify which errors are raised as :exc:`~ncclient.operations.RPCError` exceptions. Valid values are the constants defined in :class:`~ncclient.operations.RaiseMode`. The default value is :attr:`~ncclient.operations.RaiseMode.ALL`."
# limitations under the License.
from errors import OperationError, TimeoutExpiredError, MissingCapabilityError
-from rpc import RPC, RPCReply, RPCError
+from rpc import RPC, RPCReply, RPCError, RaiseMode
# rfc4741 ops
from retrieve import Get, GetConfig, GetReply
'RPC',
'RPCReply',
'RPCError',
+ 'RaiseMode',
'Get',
'GetConfig',
'GetReply',
import util
import logging
+
logger = logging.getLogger("ncclient.operations.edit")
"Operations related to changing device configuration"
class EditConfig(RPC):
+ "`edit-config` RPC"
- "*<edit-config>* RPC"
-
def request(self, target, config, default_operation=None, test_option=None, error_option=None):
+ """Loads all or part of the specified *config* to the *target* configuration datastore.
+
+ *target* is the name of the configuration datastore being edited
+
+ *config* is the configuration, which must be rooted in the `config` element. It can be specified either as a string or an :class:`~xml.etree.ElementTree.Element`.
+
+ *default_operation* if specified must be one of { `"merge"`, `"replace"`, or `"none"` }
+
+ *test_option* if specified must be one of { `"test_then_set"`, `"set"` }
+
+ *error_option* if specified must be one of { `"stop-on-error"`, `"continue-on-error"`, `"rollback-on-error"` }
+
+ The `"rollback-on-error"` *error_option* depends on the `:rollback-on-error` capability.
+ """
node = new_ele("edit-config")
node.append(util.datastore_or_url("target", target, self._assert))
if error_option is not None:
self._assert(':validate')
sub_ele(node, "test-option").text = test_option
if default_operation is not None:
- # TODO: check if it is a valid default-operation
+ # TODO: check if it is a valid default-operation
sub_ele(node, "default-operation").text = default_operation
node.append(validated_element(config, ("config", qualify("config"))))
return self._request(node)
class DeleteConfig(RPC):
-
- "*<delete-config>* RPC"
+ "`delete-config` RPC"
def request(self, target):
+ """Delete a configuration datastore.
+
+ :param target: name or URL of configuration datastore to delete
+ :type: :ref:`srctarget_params`"""
node = new_ele("delete-config")
node.append(util.datastore_or_url("target", target, self._assert))
return self._request(node)
class CopyConfig(RPC):
+ "`copy-config` RPC"
- "*<copy-config>* RPC"
-
def request(self, source, target):
+ """Create or replace an entire configuration datastore with the contents of another complete
+ configuration datastore.
+
+ :param source: configuration datastore to use as the source of the copy operation or `config` element containing the configuration subtree to copy
+ :type source: :ref:`srctarget_params`
+
+ :param target: configuration datastore to use as the destination of the copy operation
+ :type target: :ref:`srctarget_params`"""
node = new_ele("copy-config")
node.append(util.datastore_or_url("target", target, self._assert))
node.append(util.datastore_or_url("source", source, self._assert))
class Validate(RPC):
-
- "*<validate>* RPC. Depends on the *:validate* capability."
+ "`validate` RPC. Depends on the `:validate` capability."
DEPENDS = [':validate']
def request(self, source):
+ """Validate the contents of the specified configuration.
+
+ :param source: name of the configuration datastore being validated or `config` element containing the configuration subtree to be validated
+ :type source: :ref:`srctarget_params`"""
node = new_ele("validate")
try:
src = validated_element(source, ("config", qualify("config")))
class Commit(RPC):
-
- "*<commit>* RPC. Depends on the *:candidate* capability, and the *:confirmed-commit* "
+ "`commit` RPC. Depends on the `:candidate` capability, and the `:confirmed-commit`."
DEPENDS = [':candidate']
-
+
def request(self, confirmed=False, timeout=None):
+ """Commit the candidate configuration as the device's new current configuration. Depends on the `:candidate` capability.
+
+ A confirmed commit (i.e. if *confirmed* is `True`) is reverted if there is no followup commit within the *timeout* interval. If no timeout is specified the confirm timeout defaults to 600 seconds (10 minutes). A confirming commit may have the *confirmed* parameter but this is not required. Depends on the `:confirmed-commit` capability.
+
+ :param confirmed: whether this is a confirmed commit
+ :type confirmed: bool
+
+ :param timeout: confirm timeout in seconds
+ :type timeout: int"""
node = new_ele("commit")
if confirmed:
self._assert(":confirmed-commit")
class DiscardChanges(RPC):
-
- "*<discard-changes>* RPC. Depends on the *:candidate* capability."
+ "`discard-changes` RPC. Depends on the `:candidate` capability."
DEPENDS = [":candidate"]
def request(self):
+ """Revert the candidate configuration to the currently running configuration. Any uncommitted changes are discarded."""
return self._request(new_ele("discard-changes"))
\ No newline at end of file
# See the License for the specific language governing permissions and
# limitations under the License.
-'Locking-related NETCONF operations'
+"Locking-related NETCONF operations"
from ncclient.xml_ import *
-from rpc import RPC
+from rpc import RaiseMode, RPC
-# TODO:
-# should have some way to parse session-id from a lock-denied error, and raise
-# a tailored exception
+# TODO: parse session-id from a lock-denied error, and raise a tailored exception?
class Lock(RPC):
- "*<lock>* RPC"
+ "`lock` RPC"
def request(self, target):
+ """Allows the client to lock the configuration system of a device.
+
+ *target* is the name of the configuration datastore to lock
+ """
node = new_ele("lock")
sub_ele(sub_ele(node, "target"), target)
return self._request(node)
class Unlock(RPC):
- "*<unlock>* RPC"
+ "`unlock` RPC"
def request(self, target):
+ """Release a configuration lock, previously obtained with the lock operation.
+
+ *target* is the name of the configuration datastore to unlock
+ """
node = new_ele("unlock")
sub_ele(sub_ele(node, "target"), target)
return self._request(node)
class LockContext:
- """
- A context manager for the :class:`Lock` / :class:`Unlock` pair of RPC's.
-
- RPC errors are always raised as exceptions.
-
- Initialise with (:class:`Session <ncclient.transport.Session>`) instance
- and lock target.
+ """A context manager for the :class:`Lock` / :class:`Unlock` pair of RPC's.
+
+ Any `rpc-error` will be raised as an exception.
+
+ Initialise with (:class:`Session <ncclient.transport.Session>`) instance and lock target.
"""
def __init__(self, session, target):
self.target = target
def __enter__(self):
- Lock(self.session).request(self.target)
+ Lock(self.session, raise_mode=RaiseMode.ERRORS).request(self.target)
return self
def __exit__(self, *args):
- Unlock(self.session).request(self.target)
+ Unlock(self.session, raise_mode=RaiseMode.ERRORS).request(self.target)
return False
@property
def data_ele(self):
- "*data* element as an `~xml.etree.ElementTree.Element`"
+ "*data* element as an :class:`~xml.etree.ElementTree.Element`"
if not self._parsed:
self.parse()
return self._data
REPLY_CLS = GetReply
def request(self, filter=None):
+ """Retrieve running configuration and device state information.
+
+ :param filter: portions of the device configuration to retrieve (by default entire configuration is retrieved)
+ :type filter: :ref:`filter_params`
+ """
node = new_ele("get")
if filter is not None:
node.append(util.build_filter(filter))
REPLY_CLS = GetReply
def request(self, source, filter=None):
+ """Retrieve all or part of a specified configuration.
+
+ :param source: name of the configuration datastore being queried
+ :type source: string
+
+ :param filter: portions of the device configuration to retrieve (by default entire configuration is retrieved)
+ :type filter: :ref:`filter_params`"""
node = new_ele("get-config")
node.append(util.datastore_or_url("source", source, self._assert))
if filter is not None:
class RPCError(OperationError):
- """Represents an *rpc-error*. It is a type of :exc:`OperationError` and can be raised like any
- other exception."""
+ "Represents an `rpc-error`. It is a type of :exc:`OperationError` and can be raised as such."
tag_to_attr = {
qualify("error-type"): "_type",
@property
def xml(self):
- "*rpc-error* element as returned."
+ "The `rpc-error` element as returned in XML."
return self._raw
@property
def type(self):
- "`string` representing text of *error-type* element."
+ "The contents of the `error-type` element."
return self._type
@property
def tag(self):
- "`string` representing text of *error-tag* element."
+ "The contents of the `error-tag` element."
return self._tag
@property
def severity(self):
- "`string` representing text of *error-severity* element."
+ "The contents of the `error-severity` element."
return self._severity
@property
def path(self):
- "`string` or :const:`None`; representing text of *error-path* element."
+ "The contents of the `error-path` element if present or `None`."
return self._path
@property
def message(self):
- "`string` or :const:`None`; representing text of *error-message* element."
+ "The contents of the `error-message` element if present or `None`."
return self._message
@property
def info(self):
- "`string` (XML) or :const:`None`; representing *error-info* element."
+ "XML string or `None`; representing the `error-info` element."
return self._info
self._parsed = True
def _parsing_hook(self, root):
+ "No-op by default. Gets given the *root* element."
pass
@property
@property
def error(self):
- "Returns the first `RPCError` and :const:`None` if there were no errors."
+ "Returns the first :class:`RPCError` and `None` if there were no errors."
self.parse()
if self._errors:
return self._errors[0]
@property
def errors(self):
- """`list` of `RPCError` objects. Will be empty if there were no *rpc-error* elements in
- reply."""
+ "List of `RPCError` objects. Will be empty if there were no *rpc-error* elements in reply."
self.parse()
return self._errors
self._id2rpc.clear()
+class RaiseMode(object):
+
+ NONE = 0
+ "Don't attempt to raise any type of `rpc-error` as :exc:`RPCError`."
+
+ ERRORS = 1
+ "Raise only when the `error-type` indicates it is an honest-to-god error."
+
+ ALL = 2
+ "Don't look at the `error-type`, always raise."
+
+
class RPC(object):
"""Base class for all operations, directly corresponding to *rpc* requests. Handles making the
request, and taking delivery of the reply."""
-
+
DEPENDS = []
- """Subclasses can specify their dependencies on capabilities. List of URI's or abbreviated
- names, e.g. ':writable-running'. These are verified at the time of instantiation. If the
- capability is not available, a :exc:`MissingCapabilityError` is raised.
+ """Subclasses can specify their dependencies on capabilities. List of URI's or abbreviated names, e.g. ':writable-running'. These are verified at the time of instantiation. If the capability is not available, a :exc:`MissingCapabilityError` is raised.
"""
REPLY_CLS = RPCReply
"Subclasses can specify a different reply class, but it should be a subclass of `RPCReply`."
- def __init__(self, session, async=False, timeout=None, raise_mode="none"):
+ def __init__(self, session, async=False, timeout=30, raise_mode=RaiseMode.NONE):
self._session = session
try:
for cap in self.DEPENDS:
return to_xml(ele)
def _request(self, op):
- """Implementations of :meth:`request` call this method to send the request and process the
- reply.
+ """Implementations of :meth:`request` call this method to send the request and process the reply.
- In synchronous mode, blocks until the reply is received and returns `RPCReply`. Depending on
- the :attr:`raise_mode` a *rpc-error* element in the reply may lead to an :exc:`RPCError`
- exception.
+ In synchronous mode, blocks until the reply is received and returns :class:`RPCReply`. Depending on the :attr:`raise_mode` a `rpc-error` element in the reply may lead to an :exc:`RPCError` exception.
- In asynchronous mode, returns immediately, returning *self*. The :attr:`event` attribute
- will be set when the reply has been received (see :attr:`reply`) or an error occured (see
- :attr:`error`).
+ In asynchronous mode, returns immediately, returning *self*. The :attr:`event` attribute will be set when the reply has been received (see :attr:`reply`) or an error occured (see :attr:`error`).
- :param op: operation to be requested
+ :param op: operation to b\e requested
:type ops: `~xml.etree.ElementTree.Element`
:rtype: `RPCReply` (sync) or `RPC` (async)
self._reply.parse()
if self._reply.error is not None:
# <rpc-error>'s [ RPCError ]
- if self._raise_mode == "all":
+ if self._raise_mode == RaiseMode.ALL:
raise self._reply.error
- elif (self._raise_mode == "errors" and
- self._reply.error.type == "error"):
+ elif (self._raise_mode == RaiseMode.ERRORS and self._reply.error.type == "error"):
raise self._reply.error
return self._reply
else:
@property
def reply(self):
- "`RPCReply` element if reply has been received or :const:`None`"
+ ":class:`RPCReply` element if reply has been received or `None`"
return self._reply
@property
def error(self):
- """:exc:`Exception` type if an error occured or :const:`None`.
+ """:exc:`Exception` type if an error occured or `None`.
.. note::
This represents an error which prevented a reply from being received. An *rpc-error*
"""
return self._event
- def set_async(self, async=True):
+ def __set_async(self, async=True):
self._async = async
if async and not session.can_pipeline:
raise UserWarning('Asynchronous mode not supported for this device/session')
- def set_raise_mode(self, mode):
+ def __set_raise_mode(self, mode):
assert(choice in ("all", "errors", "none"))
self._raise_mode = mode
- def set_timeout(self, timeout):
+ def __set_timeout(self, timeout):
self._timeout = timeout
- raise_mode = property(fget=lambda self: self._raise_mode, fset=set_raise_mode)
- """Depending on this exception raising mode, an *rpc-error* in the reply may be raised as
- :exc:`RPCError` exceptions. Valid values:
+ raise_mode = property(fget=lambda self: self._raise_mode, fset=__set_raise_mode)
+ """Depending on this exception raising mode, an `rpc-error` in the reply may be raised as an :exc:`RPCError` exception. Valid values are the constants defined in :class:`RaiseMode`. """
- * ``"all"`` -- any kind of *rpc-error* (error or warning)
- * ``"errors"`` -- when the *error-type* element says it is an error
- * ``"none"`` -- neither
- """
-
- is_async = property(fget=lambda self: self._async, fset=set_async)
- """Specifies whether this RPC will be / was requested asynchronously. By default RPC's are
- synchronous.
- """
+ is_async = property(fget=lambda self: self._async, fset=__set_async)
+ """Specifies whether this RPC will be / was requested asynchronously. By default RPC's are synchronous. """
- timeout = property(fget=lambda self: self._timeout, fset=set_timeout)
- """Timeout in seconds for synchronous waiting defining how long the RPC request will block on a
- reply before raising :exc:`TimeoutExpiredError`. By default there is no timeout, represented by
- :const:`None`.
+ timeout = property(fget=lambda self: self._timeout, fset=__set_timeout)
+ """Timeout in seconds for synchronous waiting defining how long the RPC request will block on a reply before raising :exc:`TimeoutExpiredError`.
Irrelevant for asynchronous usage.
"""
# See the License for the specific language governing permissions and
# limitations under the License.
-'Session-related NETCONF operations'
+"Session-related NETCONF operations"
from ncclient.xml_ import *
class CloseSession(RPC):
- "*<close-session>* RPC. The connection to NETCONF server is also closed."
+ "`close-session` RPC. The connection to NETCONF server is also closed."
def request(self):
+ "Request graceful termination of the NETCONF session, and also close the transport."
try:
return self._request(new_ele("close-session"))
finally:
class KillSession(RPC):
- "*<kill-session>* RPC."
-
+ "`kill-session` RPC."
+
def request(self, session_id):
- """
- :arg session_id: *session-id* of NETCONF session to kill
- :type session_id: `string`
+ """Force the termination of a NETCONF session (not the current one!)
+
+ *session_id* is the session identifier of the NETCONF session to be terminated as a string
"""
node = new_ele("kill-session")
- if not isinstance(session_id, basestring): # make sure
- session_id = str(session_id)
sub_ele(node, "session-id").text = session_id
return self._request(node)
raise NotImplementedError
def send(self, message):
- """Send the supplied *message* to NETCONF server.
-
- :arg message: an XML document
-
- :type message: `string`
- """
+ """Send the supplied *message* (xml string) to NETCONF server."""
if not self.connected:
raise TransportError('Not connected to NETCONF server')
logger.debug('queueing %s' % message)
@property
def id(self):
- """A `string` representing the `session-id`. If the session has not
- been initialized it will be :const:`None`"""
+ """A string representing the `session-id`. If the session has not been initialized it will be `None`"""
return self._id
"""
def callback(self, root, raw):
- """Called when a new XML document is received. The `root` argument
- allows the callback to determine whether it wants to further process the
- document.
+ """Called when a new XML document is received. The *root* argument allows the callback to determine whether it wants to further process the document.
- :arg root: tuple of `(tag, attributes)` where `tag` is the qualified name of the root element and `attributes` is a dictionary of its attributes (also qualified names)
- :type root: `tuple`
+ Here, *root* is a tuple of *(tag, attributes)* where *tag* is the qualified name of the root element and *attributes* is a dictionary of its attributes (also qualified names).
- :arg raw: XML document
- :type raw: `string`
+ *raw* will contain the XML document as a string.
"""
raise NotImplementedError
TICK = 0.1
def default_unknown_host_cb(host, fingerprint):
- """An `unknown host callback` returns :const:`True` if it finds the key acceptable, and
- :const:`False` if not.
+ """An unknown host callback returns `True` if it finds the key acceptable, and `False` if not.
- This default callback always returns :const:`False`, which would lead to :meth:`connect` raising
- a :exc:`SSHUnknownHost` exception.
+ This default callback always returns `False`, which would lead to :meth:`connect` raising a :exc:`SSHUnknownHost` exception.
Supply another valid callback if you need to verify the host key programatically.
- :arg host: the hostname that needs to be verified
- :type host: `string`
+ *host* is the hostname that needs to be verified
- :arg fingerprint: a hex string representing the host key fingerprint, colon-delimited e.g. *4b:69:6c:72:6f:79:20:77:61:73:20:68:65:72:65:21*
- :type fingerprint: `string`
+ *fingerprint* is a hex string representing the host key fingerprint, colon-delimited e.g. `"4b:69:6c:72:6f:79:20:77:61:73:20:68:65:72:65:21"`
"""
return False
self._parsing_pos = 0
def _parse(self):
- """Messages ae delimited by MSG_DELIM. The buffer could have grown by a
- maximum of BUF_SIZE bytes everytime this method is called. Retains state
- across method calls and if a byte has been read it will not be
- considered again.
- """
+ "Messages ae delimited by MSG_DELIM. The buffer could have grown by a maximum of BUF_SIZE bytes everytime this method is called. Retains state across method calls and if a byte has been read it will not be considered again."
delim = MSG_DELIM
n = len(delim) - 1
expect = self._parsing_state
self._parsing_pos = self._buffer.tell()
def load_known_hosts(self, filename=None):
- """Load host keys from a :file:`known_hosts`-style file. Can be called multiple times.
+ """Load host keys from an openssh :file:`known_hosts`-style file. Can be called multiple times.
- If *filename* is not specified, looks in the default locations i.e.
- :file:`~/.ssh/known_hosts` and :file:`~/ssh/known_hosts` for Windows.
+ If *filename* is not specified, looks in the default locations i.e. :file:`~/.ssh/known_hosts` and :file:`~/ssh/known_hosts` for Windows.
"""
if filename is None:
filename = os.path.expanduser('~/.ssh/known_hosts')
self._transport.close()
self._connected = False
- def connect(self, host, port=830, timeout=None,
- unknown_host_cb=default_unknown_host_cb,
- username=None, password=None,
- key_filename=None, allow_agent=True, look_for_keys=True):
- """Connect via SSH and initialize the NETCONF session. First attempts the publickey
- authentication method and then password authentication.
+ # REMEMBER to update transport.rst if sig. changes, since it is hardcoded there
+ def connect(self, host, port=830, timeout=None, unknown_host_cb=default_unknown_host_cb,
+ username=None, password=None, key_filename=None, allow_agent=True, look_for_keys=True):
+ """Connect via SSH and initialize the NETCONF session. First attempts the publickey authentication method and then password authentication.
- To disable attemting publickey authentication altogether, call with *allow_agent* and
- *look_for_keys* as :const:`False`.
+ To disable attempting publickey authentication altogether, call with *allow_agent* and *look_for_keys* as `False`.
:arg host: the hostname or IP address to connect to
- :type host: `string`
+ :type host: string
:arg port: by default 830, but some devices use the default SSH port of 22 so this may need to be specified
- :type port: `int`
+ :type port: int
:arg timeout: an optional timeout for socket connect
- :type timeout: `int`
+ :type timeout: int
:arg unknown_host_cb: called when the server host key is not recognized
- :type unknown_host_cb: see :meth:`signature <ssh.default_unknown_host_cb>`
+ :type unknown_host_cb: see the signature of :func:`default_unknown_host_cb`
:arg username: the username to use for SSH authentication
- :type username: `string`
+ :type username: string
:arg password: the password used if using password authentication, or the passphrase to use for unlocking keys that require it
- :type password: `string`
+ :type password: string
:arg key_filename: a filename where a the private key to be used can be found
- :type key_filename: `string`
+ :type key_filename: string
:arg allow_agent: enables querying SSH agent (if found) for keys
- :type allow_agent: `bool`
+ :type allow_agent: bool
:arg look_for_keys: enables looking in the usual locations for ssh keys (e.g. :file:`~/.ssh/id_*`)
- :type look_for_keys: `bool`
+ :type look_for_keys: bool
"""
-
if username is None:
username = getpass.getuser()
q = self._q
try:
while True:
- # select on a paramiko ssh channel object does not ever return
- # it in the writable list, so it channel's don't exactly emulate
- # the socket api
+ # select on a paramiko ssh channel object does not ever return it in the writable list, so channels don't exactly emulate the socket api
r, w, e = select([chan], [], [], TICK)
- # will wakeup evey TICK seconds to check if something
- # to send, more if something to read (due to select returning
- # chan in readable list)
+ # will wakeup evey TICK seconds to check if something to send, more if something to read (due to select returning chan in readable list)
if r:
data = chan.recv(BUF_SIZE)
if data:
@property
def transport(self):
- """Underlying `paramiko.Transport
- <http://www.lag.net/paramiko/docs/paramiko.Transport-class.html>`_
- object. This makes it possible to call methods like set_keepalive on it.
- """
+ "Underlying `paramiko.Transport <http://www.lag.net/paramiko/docs/paramiko.Transport-class.html>`_ object. This makes it possible to call methods like set_keepalive on it."
return self._transport
from xml.etree import ElementTree
# cElementTree uses ElementTree's _namespace_map, so that's ok
ElementTree._namespace_map[uri] = prefix
+register_namespace.func_doc = "ElementTree's namespace map determines the prefixes for namespace URI's when serializing to XML. This method allows modifying this map to specify a prefix for a namespace URI."
for (ns, pre) in {
BASE_NS_1_0: 'nc',
}.items(): register_namespace(pre, ns)
qualify = lambda tag, ns=BASE_NS_1_0: tag if ns is None else "{%s}%s" % (ns, tag)
-"""Qualify a tag name with a namespace, in :mod:`~xml.etree.ElementTree` fashion i.e. *{namespace}tagname*.
-
-:arg tag: name of the tag
-:type arg: `string`
-
-:arg ns: namespace to qualify with
-:type ns: `string`
-"""
+"""Qualify a *tag* name with a *namespace*, in :mod:`~xml.etree.ElementTree` fashion i.e. *{namespace}tagname*."""
def to_xml(ele, encoding="UTF-8"):
- """Convert an `~xml.etree.ElementTree.Element` to XML.
-
- :arg ele: the `~xml.etree.ElementTree.Element`
- :arg encoding: character encoding
- :rtype: `string`
- """
+ "Convert and return the XML for an *ele* (:class:`~xml.etree.ElementTree.Element`) with specified *encoding*."
xml = ET.tostring(ele, encoding)
return xml if xml.startswith('<?xml') else '<?xml version="1.0" encoding="%s"?>%s' % (encoding, xml)
def to_ele(x):
- """Convert XML to `~xml.etree.ElementTree.Element`. If passed an `~xml.etree.ElementTree.Element` simply returns that.
-
- :arg x: the XML document or element
- :type x: `string` or `~xml.etree.ElementTree.Element`
- :rtype: `~xml.etree.ElementTree.Element`
- """
+ "Convert and return the :class:`~xml.etree.ElementTree.Element` for the XML document *x*. If *x* is already an :class:`~xml.etree.ElementTree.Element` simply returns that."
return x if ET.iselement(x) else ET.fromstring(x)
def parse_root(raw):
- """Efficiently parses the root element of an XML document.
-
- :arg raw: XML document
- :returns: a tuple of ``(tag, attrib)``, where *tag* is the (qualified) name of the element and *attrib* is a dictionary of its attributes.
- :rtype: `tuple`
- """
+ "Efficiently parses the root element of a *raw* XML document, returning a tuple of its qualified name and attribute dictionary."
fp = StringIO(raw)
for event, element in ET.iterparse(fp, events=('start',)):
return (element.tag, element.attrib)
def validated_element(x, tags=None, attrs=None):
"""Checks if the root element of an XML document or Element meets the supplied criteria.
- :arg tags: allowable tag name or sequence of allowable alternatives
- :type tags: `string` or sequence of strings
-
- :arg attrs: list of required attributes, each of which may be a sequence of several allowable alternatives
- :type attrs: sequence of strings or sequence of sequences of strings
-
- :raises: `XMLError` if the requirements are not met
+ *tags* if specified is either a single allowable tag name or sequence of allowable alternatives
+
+ *attrs* if specified is a sequence of required attributes, each of which may be a sequence of several allowable alternatives
+
+ Raises :exc:`XMLError` if the requirements are not met.
"""
ele = to_ele(x)
if tags: