summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--ryu/contrib/ncclient/__init__.py22
-rw-r--r--ryu/contrib/ncclient/capabilities.py68
-rw-r--r--ryu/contrib/ncclient/debug.py24
-rw-r--r--ryu/contrib/ncclient/manager.py177
-rw-r--r--ryu/contrib/ncclient/operations/__init__.py51
-rw-r--r--ryu/contrib/ncclient/operations/edit.py143
-rw-r--r--ryu/contrib/ncclient/operations/errors.py24
-rw-r--r--ryu/contrib/ncclient/operations/flowmon.py39
-rw-r--r--ryu/contrib/ncclient/operations/lock.py70
-rw-r--r--ryu/contrib/ncclient/operations/retrieve.py127
-rw-r--r--ryu/contrib/ncclient/operations/rpc.py373
-rw-r--r--ryu/contrib/ncclient/operations/session.py44
-rw-r--r--ryu/contrib/ncclient/operations/subscribe.py24
-rw-r--r--ryu/contrib/ncclient/operations/util.py65
-rw-r--r--ryu/contrib/ncclient/transport/__init__.py30
-rw-r--r--ryu/contrib/ncclient/transport/errors.py41
-rw-r--r--ryu/contrib/ncclient/transport/session.py229
-rw-r--r--ryu/contrib/ncclient/transport/ssh.py312
-rw-r--r--ryu/contrib/ncclient/xml_.py108
-rw-r--r--tools/test-requires1
20 files changed, 1 insertions, 1971 deletions
diff --git a/ryu/contrib/ncclient/__init__.py b/ryu/contrib/ncclient/__init__.py
deleted file mode 100644
index 3d806d69..00000000
--- a/ryu/contrib/ncclient/__init__.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Copyright 2009 Shikhar Bhushan
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import sys
-
-if sys.version_info < (2, 6):
- raise RuntimeError('You need Python 2.6+ for this module.')
-
-class NCClientError(Exception):
- "Base type for all NCClient errors"
- pass
diff --git a/ryu/contrib/ncclient/capabilities.py b/ryu/contrib/ncclient/capabilities.py
deleted file mode 100644
index 9a7f2476..00000000
--- a/ryu/contrib/ncclient/capabilities.py
+++ /dev/null
@@ -1,68 +0,0 @@
-# Copyright 2009 Shikhar Bhushan
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-def _abbreviate(uri):
- if uri.startswith("urn:ietf:params") and ":netconf:" in uri:
- splitted = uri.split(":")
- if ":capability:" in uri:
- if uri.startswith("urn:ietf:params:xml:ns:netconf"):
- name, version = splitted[7], splitted[8]
- else:
- name, version = splitted[5], splitted[6]
- return [ ":" + name, ":" + name + ":" + version ]
- elif ":base:" in uri:
- if uri.startswith("urn:ietf:params:xml:ns:netconf"):
- return [ ":base", ":base" + ":" + splitted[7] ]
- else:
- return [ ":base", ":base" + ":" + splitted[5] ]
- 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."
- 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."
-
- def __init__(self, capabilities):
- self._dict = {}
- for uri in capabilities:
- self._dict[uri] = _abbreviate(uri)
-
- def __contains__(self, key):
- if key in self._dict:
- return True
- for abbrs in self._dict.values():
- if key in abbrs:
- return True
- return False
-
- def __len__(self):
- return len(self._dict)
-
- def __iter__(self):
- return self._dict.iterkeys()
-
- def __repr__(self):
- return repr(self._dict.keys())
-
- def add(self, uri):
- "Add a capability."
- self._dict[uri] = _abbreviate(uri)
-
- def remove(self, uri):
- "Remove a capability."
- if key in self._dict:
- del self._dict[key] \ No newline at end of file
diff --git a/ryu/contrib/ncclient/debug.py b/ryu/contrib/ncclient/debug.py
deleted file mode 100644
index 65429cfe..00000000
--- a/ryu/contrib/ncclient/debug.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# Copyright 2009 Shikhar Bhushan
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from ncclient.transport import SessionListener
-
-class PrintListener(SessionListener):
-
- def callback(self, root, raw):
- print('\n# RECEIVED MESSAGE with root=[tag=%r, attrs=%r] #\n%r\n' %
- (root[0], root[1], raw))
-
- def errback(self, err):
- print('\n# RECEIVED ERROR #\n%r\n' % err)
diff --git a/ryu/contrib/ncclient/manager.py b/ryu/contrib/ncclient/manager.py
deleted file mode 100644
index f695465b..00000000
--- a/ryu/contrib/ncclient/manager.py
+++ /dev/null
@@ -1,177 +0,0 @@
-# Copyright 2009 Shikhar Bhushan
-# Copyright 2011 Leonidas Poulopoulos
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# 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."""
-
-import capabilities
-import operations
-import transport
-
-import logging
-
-logger = logging.getLogger('ncclient.manager')
-
-CAPABILITIES = [
- "urn:ietf:params:netconf:base:1.0",
- "urn:ietf:params:netconf:capability:writable-running:1.0",
- "urn:ietf:params:netconf:capability:candidate:1.0",
- "urn:ietf:params:netconf:capability:confirmed-commit:1.0",
- "urn:ietf:params:netconf:capability:rollback-on-error:1.0",
- "urn:ietf:params:netconf:capability:startup:1.0",
- "urn:ietf:params:netconf:capability:url:1.0?scheme=http,ftp,file,https,sftp",
- "urn:ietf:params:netconf:capability:validate:1.0",
- "urn:ietf:params:netconf:capability:xpath:1.0",
- "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."""
-
-OPERATIONS = {
- "get": operations.Get,
- "get_config": operations.GetConfig,
- "dispatch": operations.Dispatch,
- "edit_config": operations.EditConfig,
- "copy_config": operations.CopyConfig,
- "validate": operations.Validate,
- "commit": operations.Commit,
- "discard_changes": operations.DiscardChanges,
- "delete_config": operations.DeleteConfig,
- "lock": operations.Lock,
- "unlock": operations.Unlock,
- "close_session": operations.CloseSession,
- "kill_session": operations.KillSession,
- "poweroff_machine": operations.PoweroffMachine,
- "reboot_machine": operations.RebootMachine
-}
-"""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):
- """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()
- session.connect(*args, **kwds)
- return Manager(session)
-
-connect = connect_ssh
-"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):
- return self.execute(op_cls, *args, **kwds)
- wrapper.func_doc = op_cls.request.func_doc
- return wrapper
- for op_name, op_cls in OPERATIONS.iteritems():
- attrs[op_name] = make_wrapper(op_cls)
- return super(OpExecutor, cls).__new__(cls, name, bases, attrs)
-
-class Manager(object):
-
- """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
-
- ... or like this::
-
- 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 = timeout
- self._raise_mode = operations.RaiseMode.ALL
-
- def __enter__(self):
- return self
-
- def __exit__(self, *args):
- self.close_session()
- return False
-
- def __set_timeout(self, timeout):
- self._timeout = timeout
-
- def __set_async_mode(self, mode):
- self._async_mode = mode
-
- def __set_raise_mode(self, mode):
- assert(mode 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,
- timeout=self._timeout,
- 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
-
- 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)."
-
- timeout = property(fget=lambda self: self._timeout, fset=__set_timeout)
- "Specify the timeout for synchronous RPC requests."
-
- 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`."
diff --git a/ryu/contrib/ncclient/operations/__init__.py b/ryu/contrib/ncclient/operations/__init__.py
deleted file mode 100644
index 1f56b2f0..00000000
--- a/ryu/contrib/ncclient/operations/__init__.py
+++ /dev/null
@@ -1,51 +0,0 @@
-# Copyright 2009 Shikhar Bhushan
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from errors import OperationError, TimeoutExpiredError, MissingCapabilityError
-from rpc import RPC, RPCReply, RPCError, RaiseMode
-
-# rfc4741 ops
-from retrieve import Get, GetConfig, GetReply, Dispatch
-from edit import EditConfig, CopyConfig, DeleteConfig, Validate, Commit, DiscardChanges
-from session import CloseSession, KillSession
-from lock import Lock, Unlock, LockContext
-# others...
-from flowmon import PoweroffMachine, RebootMachine
-
-__all__ = [
- 'RPC',
- 'RPCReply',
- 'RPCError',
- 'RaiseMode',
- 'Get',
- 'GetConfig',
- 'Dispatch',
- 'GetReply',
- 'EditConfig',
- 'CopyConfig',
- 'Validate',
- 'Commit',
- 'DiscardChanges',
- 'DeleteConfig',
- 'Lock',
- 'Unlock',
- 'PoweroffMachine',
- 'RebootMachine',
- 'LockContext',
- 'CloseSession',
- 'KillSession',
- 'OperationError',
- 'TimeoutExpiredError',
- 'MissingCapabilityError'
-]
diff --git a/ryu/contrib/ncclient/operations/edit.py b/ryu/contrib/ncclient/operations/edit.py
deleted file mode 100644
index a2dbd942..00000000
--- a/ryu/contrib/ncclient/operations/edit.py
+++ /dev/null
@@ -1,143 +0,0 @@
-# Copyright 2009 Shikhar Bhushan
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from ncclient.xml_ import *
-
-from rpc import RPC
-
-import util
-
-import logging
-
-logger = logging.getLogger("ncclient.operations.edit")
-
-"Operations related to changing device configuration"
-
-class EditConfig(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:
- if error_option == "rollback-on-error":
- self._assert(":rollback-on-error")
- sub_ele(node, "error-option").text = error_option
- if test_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
- 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"
-
- def request(self, target):
- """Delete a configuration datastore.
-
- *target* specifies the name or URL of configuration datastore to delete
-
- :seealso: :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"
-
- def request(self, source, target):
- """Create or replace an entire configuration datastore with the contents of another complete
- configuration datastore.
-
- *source* is the name of the configuration datastore to use as the source of the copy operation or `config` element containing the configuration subtree to copy
-
- *target* is the name of the configuration datastore to use as the destination of the copy operation
-
- :seealso: :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))
- return self._request(node)
-
-
-class Validate(RPC):
- "`validate` RPC. Depends on the `:validate` capability."
-
- DEPENDS = [':validate']
-
- def request(self, source):
- """Validate the contents of the specified configuration.
-
- *source* is the name of the configuration datastore being validated or `config` element containing the configuration subtree to be validated
-
- :seealso: :ref:`srctarget_params`"""
- node = new_ele("validate")
- try:
- src = validated_element(source, ("config", qualify("config")))
- except Exception as e:
- logger.debug(e)
- src = util.datastore_or_url("source", source, self._assert)
- (node if src.tag == "source" else sub_ele(node, "source")).append(src)
- return self._request(node)
-
-
-class Commit(RPC):
- "`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.
-
- *confirmed* whether this is a confirmed commit
-
- *timeout* specifies the confirm timeout in seconds"""
- node = new_ele("commit")
- if confirmed:
- self._assert(":confirmed-commit")
- sub_ele(node, "confirmed")
- if timeout is not None:
- sub_ele(node, "confirm-timeout").text = timeout
- return self._request(node)
-
-
-class DiscardChanges(RPC):
- "`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
diff --git a/ryu/contrib/ncclient/operations/errors.py b/ryu/contrib/ncclient/operations/errors.py
deleted file mode 100644
index 623abeda..00000000
--- a/ryu/contrib/ncclient/operations/errors.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# Copyright 2009 Shikhar Bhushan
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from ncclient import NCClientError
-
-class OperationError(NCClientError):
- pass
-
-class TimeoutExpiredError(NCClientError):
- pass
-
-class MissingCapabilityError(NCClientError):
- pass
diff --git a/ryu/contrib/ncclient/operations/flowmon.py b/ryu/contrib/ncclient/operations/flowmon.py
deleted file mode 100644
index 76759865..00000000
--- a/ryu/contrib/ncclient/operations/flowmon.py
+++ /dev/null
@@ -1,39 +0,0 @@
-# Copyright 2h009 Shikhar Bhushan
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-'Power-control operations'
-
-from ncclient.xml_ import *
-
-from rpc import RPC
-
-PC_URN = "urn:liberouter:params:xml:ns:netconf:power-control:1.0"
-
-class PoweroffMachine(RPC):
-
- "*poweroff-machine* RPC (flowmon)"
-
- DEPENDS = ["urn:liberouter:param:netconf:capability:power-control:1.0"]
-
- def request(self):
- return self._request(new_ele(qualify("poweroff-machine", PC_URN)))
-
-class RebootMachine(RPC):
-
- "*reboot-machine* RPC (flowmon)"
-
- DEPENDS = ["urn:liberouter:params:netconf:capability:power-control:1.0"]
-
- def request(self):
- return self._request(new_ele(qualify("reboot-machine", PC_URN)))
diff --git a/ryu/contrib/ncclient/operations/lock.py b/ryu/contrib/ncclient/operations/lock.py
deleted file mode 100644
index 13f5bdb0..00000000
--- a/ryu/contrib/ncclient/operations/lock.py
+++ /dev/null
@@ -1,70 +0,0 @@
-# Copyright 2h009 Shikhar Bhushan
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"Locking-related NETCONF operations"
-
-from ncclient.xml_ import *
-
-from rpc import RaiseMode, RPC
-
-# TODO: parse session-id from a lock-denied error, and raise a tailored exception?
-
-class 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"
-
- 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.
-
- 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.session = session
- self.target = target
-
- def __enter__(self):
- Lock(self.session, raise_mode=RaiseMode.ERRORS).request(self.target)
- return self
-
- def __exit__(self, *args):
- Unlock(self.session, raise_mode=RaiseMode.ERRORS).request(self.target)
- return False
diff --git a/ryu/contrib/ncclient/operations/retrieve.py b/ryu/contrib/ncclient/operations/retrieve.py
deleted file mode 100644
index e7fe8dce..00000000
--- a/ryu/contrib/ncclient/operations/retrieve.py
+++ /dev/null
@@ -1,127 +0,0 @@
-# Copyright 2009 Shikhar Bhushan
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from rpc import RPC, RPCReply
-
-from ncclient.xml_ import *
-
-import util
-
-class GetReply(RPCReply):
-
- """Adds attributes for the *data* element to `RPCReply`."""
-
- def _parsing_hook(self, root):
- self._data = None
- if not self._errors:
- self._data = root.find(qualify("data"))
-
- @property
- def data_ele(self):
- "*data* element as an :class:`~xml.etree.ElementTree.Element`"
- if not self._parsed:
- self.parse()
- return self._data
-
- @property
- def data_xml(self):
- "*data* element as an XML string"
- if not self._parsed:
- self.parse()
- return to_xml(self._data)
-
- data = data_ele
- "Same as :attr:`data_ele`"
-
-
-class Get(RPC):
-
- "The *get* RPC."
-
- REPLY_CLS = GetReply
- "See :class:`GetReply`."
-
- def request(self, filter=None):
- """Retrieve running configuration and device state information.
-
- *filter* specifies the portion of the configuration to retrieve (by default entire configuration is retrieved)
-
- :seealso: :ref:`filter_params`
- """
- node = new_ele("get")
- if filter is not None:
- node.append(util.build_filter(filter))
- return self._request(node)
-
-
-class GetConfig(RPC):
-
- "The *get-config* RPC."
-
- REPLY_CLS = GetReply
- "See :class:`GetReply`."
-
- def request(self, source, filter=None):
- """Retrieve all or part of a specified configuration.
-
- *source* name of the configuration datastore being queried
-
- *filter* specifies the portion of the configuration to retrieve (by default entire configuration is retrieved)
-
- :seealso: :ref:`filter_params`"""
- node = new_ele("get-config")
- node.append(util.datastore_or_url("source", source, self._assert))
- if filter is not None:
- node.append(util.build_filter(filter))
- return self._request(node)
-
-class Dispatch(RPC):
-
- "Generic retrieving wrapper"
-
- REPLY_CLS = GetReply
- "See :class:`GetReply`."
-
- def request(self, rpc_command, source=None, filter=None):
- """
- *rpc_command* specifies rpc command to be dispatched either in plain text or in xml element format (depending on command)
-
- *source* name of the configuration datastore being queried
-
- *filter* specifies the portion of the configuration to retrieve (by default entire configuration is retrieved)
-
- :seealso: :ref:`filter_params`
-
- Examples of usage::
-
- dispatch('clear-arp-table')
-
- or dispatch element like ::
-
- xsd_fetch = new_ele('get-xnm-information')
- sub_ele(xsd_fetch, 'type').text="xml-schema"
- sub_ele(xsd_fetch, 'namespace').text="junos-configuration"
- dispatch(xsd_fetch)
- """
-
- if ET.iselement(rpc_command):
- node = rpc_command
- else:
- node = new_ele(rpc_command)
- if source is not None:
- node.append(util.datastore_or_url("source", source, self._assert))
- if filter is not None:
- node.append(util.build_filter(filter))
- return self._request(node)
-
diff --git a/ryu/contrib/ncclient/operations/rpc.py b/ryu/contrib/ncclient/operations/rpc.py
deleted file mode 100644
index d371cd24..00000000
--- a/ryu/contrib/ncclient/operations/rpc.py
+++ /dev/null
@@ -1,373 +0,0 @@
-# Copyright 2009 Shikhar Bhushan
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from threading import Event, Lock
-from uuid import uuid1
-
-from ncclient.xml_ import *
-from ncclient.transport import SessionListener
-
-from errors import OperationError, TimeoutExpiredError, MissingCapabilityError
-
-import logging
-logger = logging.getLogger("ncclient.operations.rpc")
-
-
-class RPCError(OperationError):
-
- "Represents an `rpc-error`. It is a type of :exc:`OperationError` and can be raised as such."
-
- tag_to_attr = {
- qualify("error-type"): "_type",
- qualify("error-tag"): "_tag",
- qualify("error-severity"): "_severity",
- qualify("error-info"): "_info",
- qualify("error-path"): "_path",
- qualify("error-message"): "_message"
- }
-
- def __init__(self, raw):
- self._raw = raw
- for attr in RPCError.tag_to_attr.values():
- setattr(self, attr, None)
- for subele in raw:
- attr = RPCError.tag_to_attr.get(subele.tag, None)
- if attr is not None:
- setattr(self, attr, subele.text if attr != "_info" else to_xml(subele) )
- if self.message is not None:
- OperationError.__init__(self, self.message)
- else:
- OperationError.__init__(self, self.to_dict())
-
- def to_dict(self):
- return dict([ (attr[1:], getattr(self, attr)) for attr in RPCError.tag_to_attr.values() ])
-
- @property
- def xml(self):
- "The `rpc-error` element as returned in XML."
- return self._raw
-
- @property
- def type(self):
- "The contents of the `error-type` element."
- return self._type
-
- @property
- def tag(self):
- "The contents of the `error-tag` element."
- return self._tag
-
- @property
- def severity(self):
- "The contents of the `error-severity` element."
- return self._severity
-
- @property
- def path(self):
- "The contents of the `error-path` element if present or `None`."
- return self._path
-
- @property
- def message(self):
- "The contents of the `error-message` element if present or `None`."
- return self._message
-
- @property
- def info(self):
- "XML string or `None`; representing the `error-info` element."
- return self._info
-
-
-class RPCReply:
-
- """Represents an *rpc-reply*. Only concerns itself with whether the operation was successful.
-
- .. note::
- If the reply has not yet been parsed there is an implicit, one-time parsing overhead to
- accessing some of the attributes defined by this class.
- """
-
- ERROR_CLS = RPCError
- "Subclasses can specify a different error class, but it should be a subclass of `RPCError`."
-
- def __init__(self, raw):
- self._raw = raw
- self._parsed = False
- self._root = None
- self._errors = []
-
- def __repr__(self):
- return self._raw
-
- def parse(self):
- "Parses the *rpc-reply*."
- if self._parsed: return
- root = self._root = to_ele(self._raw) # The <rpc-reply> element
- # Per RFC 4741 an <ok/> tag is sent when there are no errors or warnings
- ok = root.find(qualify("ok"))
- if ok is None:
- # Create RPCError objects from <rpc-error> elements
- error = root.find(qualify("rpc-error"))
- if error is not None:
- for err in root.getiterator(error.tag):
- # Process a particular <rpc-error>
- self._errors.append(self.ERROR_CLS(err))
- self._parsing_hook(root)
- self._parsed = True
-
- def _parsing_hook(self, root):
- "No-op by default. Gets passed the *root* element for the reply."
- pass
-
- @property
- def xml(self):
- "*rpc-reply* element as returned."
- return self._raw
-
- @property
- def ok(self):
- "Boolean value indicating if there were no errors."
- return not self.errors # empty list => false
-
- @property
- def error(self):
- "Returns the first :class:`RPCError` and `None` if there were no errors."
- self.parse()
- if self._errors:
- return self._errors[0]
- else:
- return None
-
- @property
- def errors(self):
- "List of `RPCError` objects. Will be empty if there were no *rpc-error* elements in reply."
- self.parse()
- return self._errors
-
-
-class RPCReplyListener(SessionListener): # internal use
-
- creation_lock = Lock()
-
- # one instance per session -- maybe there is a better way??
- def __new__(cls, session):
- with RPCReplyListener.creation_lock:
- instance = session.get_listener_instance(cls)
- if instance is None:
- instance = object.__new__(cls)
- instance._lock = Lock()
- instance._id2rpc = {}
- #instance._pipelined = session.can_pipeline
- session.add_listener(instance)
- return instance
-
- def register(self, id, rpc):
- with self._lock:
- self._id2rpc[id] = rpc
-
- def callback(self, root, raw):
- tag, attrs = root
- if tag != qualify("rpc-reply"):
- return
- for key in attrs: # in the <rpc-reply> attributes
- if key == "message-id": # if we found msgid attr
- id = attrs[key] # get the msgid
- with self._lock:
- try:
- rpc = self._id2rpc[id] # the corresponding rpc
- logger.debug("Delivering to %r", rpc)
- rpc.deliver_reply(raw)
- except KeyError:
- raise OperationError("Unknown 'message-id': %s", id)
- # no catching other exceptions, fail loudly if must
- else:
- # if no error delivering, can del the reference to the RPC
- del self._id2rpc[id]
- break
- else:
- raise OperationError("Could not find 'message-id' attribute in <rpc-reply>")
-
- def errback(self, err):
- try:
- for rpc in self._id2rpc.values():
- rpc.deliver_error(err)
- finally:
- 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 as a 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, :exc:`MissingCapabilityError` is raised."""
-
- REPLY_CLS = RPCReply
- "By default :class:`RPCReply`. Subclasses can specify a :class:`RPCReply` subclass."
-
- def __init__(self, session, async=False, timeout=30, raise_mode=RaiseMode.NONE):
- """
- *session* is the :class:`~ncclient.transport.Session` instance
-
- *async* specifies whether the request is to be made asynchronously, see :attr:`is_async`
-
- *timeout* is the timeout for a synchronous request, see :attr:`timeout`
-
- *raise_mode* specifies the exception raising mode, see :attr:`raise_mode`
- """
- self._session = session
- try:
- for cap in self.DEPENDS:
- self._assert(cap)
- except AttributeError:
- pass
- self._async = async
- self._timeout = timeout
- self._raise_mode = raise_mode
- self._id = uuid1().urn # Keeps things simple instead of having a class attr with running ID that has to be locked
- self._listener = RPCReplyListener(session)
- self._listener.register(self._id, self)
- self._reply = None
- self._error = None
- self._event = Event()
-
- def _wrap(self, subele):
- # internal use
- ele = new_ele("rpc", {"message-id": self._id})
- ele.append(subele)
- return to_xml(ele)
-
- def _request(self, op):
- """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 :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`).
-
- *op* is the operation to be requested as an :class:`~xml.etree.ElementTree.Element`
- """
- logger.info('Requesting %r', self.__class__.__name__)
- req = self._wrap(op)
- self._session.send(req)
- if self._async:
- logger.debug('Async request, returning %r', self)
- return self
- else:
- logger.debug('Sync request, will wait for timeout=%r', self._timeout)
- self._event.wait(self._timeout)
- if self._event.isSet():
- if self._error:
- # Error that prevented reply delivery
- raise self._error
- self._reply.parse()
- if self._reply.error is not None:
- # <rpc-error>'s [ RPCError ]
- if self._raise_mode == RaiseMode.ALL:
- raise self._reply.error
- elif (self._raise_mode == RaiseMode.ERRORS and self._reply.error.type == "error"):
- raise self._reply.error
- return self._reply
- else:
- raise TimeoutExpiredError
-
- def request(self):
- """Subclasses must implement this method. Typically only the request needs to be built as an
- :class:`~xml.etree.ElementTree.Element` and everything else can be handed off to
- :meth:`_request`."""
- pass
-
- def _assert(self, capability):
- """Subclasses can use this method to verify that a capability is available with the NETCONF
- server, before making a request that requires it. A :exc:`MissingCapabilityError` will be
- raised if the capability is not available."""
- if capability not in self._session.server_capabilities:
- raise MissingCapabilityError('Server does not support [%s]' % capability)
-
- def deliver_reply(self, raw):
- # internal use
- self._reply = self.REPLY_CLS(raw)
- self._event.set()
-
- def deliver_error(self, err):
- # internal use
- self._error = err
- self._event.set()
-
- @property
- def reply(self):
- ":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 `None`.
-
- .. note::
- This represents an error which prevented a reply from being received. An *rpc-error*
- does not fall in that category -- see `RPCReply` for that.
- """
- return self._error
-
- @property
- def id(self):
- "The *message-id* for this RPC."
- return self._id
-
- @property
- def session(self):
- "The `~ncclient.transport.Session` object associated with this RPC."
- return self._session
-
- @property
- def event(self):
- """:class:`~threading.Event` that is set when reply has been received or when an error preventing
- delivery of the reply occurs.
- """
- return self._event
-
- 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):
- assert(choice in ("all", "errors", "none"))
- self._raise_mode = mode
-
- 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 an :exc:`RPCError` exception. Valid values are the constants defined in :class:`RaiseMode`. """
-
- 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`.
-
- Irrelevant for asynchronous usage.
- """
diff --git a/ryu/contrib/ncclient/operations/session.py b/ryu/contrib/ncclient/operations/session.py
deleted file mode 100644
index 0afa3072..00000000
--- a/ryu/contrib/ncclient/operations/session.py
+++ /dev/null
@@ -1,44 +0,0 @@
-# Copyright 2009 Shikhar Bhushan
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"Session-related NETCONF operations"
-
-from ncclient.xml_ import *
-
-from rpc import RPC
-
-class CloseSession(RPC):
-
- "`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:
- self.session.close()
-
-
-class KillSession(RPC):
-
- "`kill-session` RPC."
-
- def request(self, session_id):
- """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")
- sub_ele(node, "session-id").text = session_id
- return self._request(node)
diff --git a/ryu/contrib/ncclient/operations/subscribe.py b/ryu/contrib/ncclient/operations/subscribe.py
deleted file mode 100644
index 44635d34..00000000
--- a/ryu/contrib/ncclient/operations/subscribe.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# Copyright 2009 Shikhar Bhushan
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# TODO
-
-class Notification:
- pass
-
-class CreateSubscription:
- pass
-
-class NotificationListener:
- pass
diff --git a/ryu/contrib/ncclient/operations/util.py b/ryu/contrib/ncclient/operations/util.py
deleted file mode 100644
index e11ae598..00000000
--- a/ryu/contrib/ncclient/operations/util.py
+++ /dev/null
@@ -1,65 +0,0 @@
-# Copyright 2009 Shikhar Bhushan
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-'Boilerplate ugliness'
-
-from ncclient.xml_ import *
-
-from errors import OperationError, MissingCapabilityError
-
-def one_of(*args):
- "Verifies that only one of the arguments is not None"
- for i, arg in enumerate(args):
- if arg is not None:
- for argh in args[i+1:]:
- if argh is not None:
- raise OperationError("Too many parameters")
- else:
- return
- raise OperationError("Insufficient parameters")
-
-def datastore_or_url(wha, loc, capcheck=None):
- node = new_ele(wha)
- if "://" in loc: # e.g. http://, file://, ftp://
- if capcheck is not None:
- capcheck(":url") # url schema check at some point!
- sub_ele(node, "url").text = loc
- else:
- #if loc == 'candidate':
- # capcheck(':candidate')
- #elif loc == 'startup':
- # capcheck(':startup')
- #elif loc == 'running' and wha == 'target':
- # capcheck(':writable-running')
- sub_ele(node, loc)
- return node
-
-def build_filter(spec, capcheck=None):
- type = None
- if isinstance(spec, tuple):
- type, criteria = spec
- rep = new_ele("filter", type=type)
- if type == "xpath":
- rep.attrib["select"] = criteria
- elif type == "subtree":
- rep.append(to_ele(criteria))
- else:
- raise OperationError("Invalid filter type")
- else:
- rep = validated_element(spec, ("filter", qualify("filter")),
- attrs=("type",))
- # TODO set type var here, check if select attr present in case of xpath..
- if type == "xpath" and capcheck is not None:
- capcheck(":xpath")
- return rep
diff --git a/ryu/contrib/ncclient/transport/__init__.py b/ryu/contrib/ncclient/transport/__init__.py
deleted file mode 100644
index 51c4a155..00000000
--- a/ryu/contrib/ncclient/transport/__init__.py
+++ /dev/null
@@ -1,30 +0,0 @@
-# Copyright 2009 Shikhar Bhushan
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"Transport layer"
-
-from session import Session, SessionListener
-from ssh import SSHSession
-from errors import *
-
-__all__ = [
- 'Session',
- 'SessionListener',
- 'SSHSession',
- 'TransportError',
- 'AuthenticationError',
- 'SessionCloseError',
- 'SSHError',
- 'SSHUnknownHostError'
-] \ No newline at end of file
diff --git a/ryu/contrib/ncclient/transport/errors.py b/ryu/contrib/ncclient/transport/errors.py
deleted file mode 100644
index ec95c682..00000000
--- a/ryu/contrib/ncclient/transport/errors.py
+++ /dev/null
@@ -1,41 +0,0 @@
-# Copyright 2009 Shikhar Bhushan
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from ncclient import NCClientError
-
-class TransportError(NCClientError):
- pass
-
-class AuthenticationError(TransportError):
- pass
-
-class SessionCloseError(TransportError):
-
- def __init__(self, in_buf, out_buf=None):
- msg = 'Unexpected session close'
- if in_buf:
- msg += '\nIN_BUFFER: `%s`' % in_buf
- if out_buf:
- msg += ' OUT_BUFFER: `%s`' % out_buf
- SSHError.__init__(self, msg)
-
-class SSHError(TransportError):
- pass
-
-class SSHUnknownHostError(SSHError):
-
- def __init__(self, host, fingerprint):
- SSHError.__init__(self, 'Unknown host key [%s] for [%s]' % (fingerprint, host))
- self.host = host
- self.fingerprint = fingerprint
diff --git a/ryu/contrib/ncclient/transport/session.py b/ryu/contrib/ncclient/transport/session.py
deleted file mode 100644
index d33bfabf..00000000
--- a/ryu/contrib/ncclient/transport/session.py
+++ /dev/null
@@ -1,229 +0,0 @@
-# Copyright 2009 Shikhar Bhushan
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from Queue import Queue
-from threading import Thread, Lock, Event
-
-from ncclient.xml_ import *
-from ncclient.capabilities import Capabilities
-
-from errors import TransportError
-
-import logging
-logger = logging.getLogger('ncclient.transport.session')
-
-class Session(Thread):
-
- "Base class for use by transport protocol implementations."
-
- def __init__(self, capabilities):
- Thread.__init__(self)
- self.setDaemon(True)
- self._listeners = set()
- self._lock = Lock()
- self.setName('session')
- self._q = Queue()
- self._client_capabilities = capabilities
- self._server_capabilities = None # yet
- self._id = None # session-id
- self._connected = False # to be set/cleared by subclass implementation
- logger.debug('%r created: client_capabilities=%r',
- self, self._client_capabilities)
-
- def _dispatch_message(self, raw):
- try:
- root = parse_root(raw)
- except Exception as e:
- logger.error('error parsing dispatch message: %s', e)
- return
- with self._lock:
- listeners = list(self._listeners)
- for l in listeners:
- logger.debug('dispatching message to %r: %s', l, raw)
- l.callback(root, raw) # no try-except; fail loudly if you must!
-
- def _dispatch_error(self, err):
- with self._lock:
- listeners = list(self._listeners)
- for l in listeners:
- logger.debug('dispatching error to %r', l)
- try: # here we can be more considerate with catching exceptions
- l.errback(err)
- except Exception as e:
- logger.warning('error dispatching to %r: %r', l, e)
-
- def _post_connect(self):
- "Greeting stuff"
- init_event = Event()
- error = [None] # so that err_cb can bind error[0]. just how it is.
- # callbacks
- def ok_cb(id, capabilities):
- self._id = id
- self._server_capabilities = capabilities
- init_event.set()
- def err_cb(err):
- error[0] = err
- init_event.set()
- listener = HelloHandler(ok_cb, err_cb)
- self.add_listener(listener)
- self.send(HelloHandler.build(self._client_capabilities))
- logger.debug('starting main loop')
- self.start()
- # we expect server's hello message
- init_event.wait()
- # received hello message or an error happened
- self.remove_listener(listener)
- if error[0]:
- raise error[0]
- #if ':base:1.0' not in self.server_capabilities:
- # raise MissingCapabilityError(':base:1.0')
- logger.info('initialized: session-id=%s | server_capabilities=%s',
- self._id, self._server_capabilities)
-
- def add_listener(self, listener):
- """Register a listener that will be notified of incoming messages and
- errors.
-
- :type listener: :class:`SessionListener`
- """
- logger.debug('installing listener %r', listener)
- if not isinstance(listener, SessionListener):
- raise SessionError("Listener must be a SessionListener type")
- with self._lock:
- self._listeners.add(listener)
-
- def remove_listener(self, listener):
- """Unregister some listener; ignore if the listener was never
- registered.
-
- :type listener: :class:`SessionListener`
- """
- logger.debug('discarding listener %r', listener)
- with self._lock:
- self._listeners.discard(listener)
-
- def get_listener_instance(self, cls):
- """If a listener of the specified type is registered, returns the
- instance.
-
- :type cls: :class:`SessionListener`
- """
- with self._lock:
- for listener in self._listeners:
- if isinstance(listener, cls):
- return listener
-
- def connect(self, *args, **kwds): # subclass implements
- raise NotImplementedError
-
- def run(self): # subclass implements
- raise NotImplementedError
-
- def send(self, message):
- """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)
- self._q.put(message)
-
- ### Properties
-
- @property
- def connected(self):
- "Connection status of the session."
- return self._connected
-
- @property
- def client_capabilities(self):
- "Client's :class:`Capabilities`"
- return self._client_capabilities
-
- @property
- def server_capabilities(self):
- "Server's :class:`Capabilities`"
- return self._server_capabilities
-
- @property
- def id(self):
- """A string representing the `session-id`. If the session has not been initialized it will be `None`"""
- return self._id
-
-
-class SessionListener(object):
-
- """Base class for :class:`Session` listeners, which are notified when a new
- NETCONF message is received or an error occurs.
-
- .. note::
- Avoid time-intensive tasks in a callback's context.
- """
-
- 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.
-
- 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).
-
- *raw* will contain the XML document as a string.
- """
- raise NotImplementedError
-
- def errback(self, ex):
- """Called when an error occurs.
-
- :type ex: :exc:`Exception`
- """
- raise NotImplementedError
-
-
-class HelloHandler(SessionListener):
-
- def __init__(self, init_cb, error_cb):
- self._init_cb = init_cb
- self._error_cb = error_cb
-
- def callback(self, root, raw):
- tag, attrs = root
- if (tag == qualify("hello")) or (tag == "hello"):
- try:
- id, capabilities = HelloHandler.parse(raw)
- except Exception as e:
- self._error_cb(e)
- else:
- self._init_cb(id, capabilities)
-
- def errback(self, err):
- self._error_cb(err)
-
- @staticmethod
- def build(capabilities):
- "Given a list of capability URI's returns <hello> message XML string"
- hello = new_ele("hello")
- caps = sub_ele(hello, "capabilities")
- def fun(uri): sub_ele(caps, "capability").text = uri
- map(fun, capabilities)
- return to_xml(hello)
-
- @staticmethod
- def parse(raw):
- "Returns tuple of (session-id (str), capabilities (Capabilities)"
- sid, capabilities = 0, []
- root = to_ele(raw)
- for child in root.getchildren():
- if child.tag == qualify("session-id") or child.tag == "session-id":
- sid = child.text
- elif child.tag == qualify("capabilities") or child.tag == "capabilities" :
- for cap in child.getchildren():
- if cap.tag == qualify("capability") or cap.tag == "capability":
- capabilities.append(cap.text)
- return sid, Capabilities(capabilities)
diff --git a/ryu/contrib/ncclient/transport/ssh.py b/ryu/contrib/ncclient/transport/ssh.py
deleted file mode 100644
index ad3d549d..00000000
--- a/ryu/contrib/ncclient/transport/ssh.py
+++ /dev/null
@@ -1,312 +0,0 @@
-# Copyright 2009 Shikhar Bhushan
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import os
-import socket
-import getpass
-from binascii import hexlify
-from cStringIO import StringIO
-from select import select
-
-import paramiko
-
-from errors import AuthenticationError, SessionCloseError, SSHError, SSHUnknownHostError
-from session import Session
-
-import logging
-logger = logging.getLogger("ncclient.transport.ssh")
-
-BUF_SIZE = 4096
-MSG_DELIM = "]]>]]>"
-TICK = 0.1
-
-def default_unknown_host_cb(host, fingerprint):
- """An unknown host callback returns `True` if it finds the key acceptable, and `False` if not.
-
- 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.
-
- *host* is the hostname that needs to be verified
-
- *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
-
-def _colonify(fp):
- finga = fp[:2]
- for idx in range(2, len(fp), 2):
- finga += ":" + fp[idx:idx+2]
- return finga
-
-class SSHSession(Session):
-
- "Implements a :rfc:`4742` NETCONF session over SSH."
-
- def __init__(self, capabilities):
- Session.__init__(self, capabilities)
- self._host_keys = paramiko.HostKeys()
- self._transport = None
- self._connected = False
- self._channel = None
- self._buffer = StringIO() # for incoming data
- # parsing-related, see _parse()
- self._parsing_state = 0
- 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."
- delim = MSG_DELIM
- n = len(delim) - 1
- expect = self._parsing_state
- buf = self._buffer
- buf.seek(self._parsing_pos)
- while True:
- x = buf.read(1)
- if not x: # done reading
- break
- elif x == delim[expect]: # what we expected
- expect += 1 # expect the next delim char
- else:
- expect = 0
- continue
- # loop till last delim char expected, break if other char encountered
- for i in range(expect, n):
- x = buf.read(1)
- if not x: # done reading
- break
- if x == delim[expect]: # what we expected
- expect += 1 # expect the next delim char
- else:
- expect = 0 # reset
- break
- else: # if we didn't break out of the loop, full delim was parsed
- msg_till = buf.tell() - n
- buf.seek(0)
- logger.debug('parsed new message')
- self._dispatch_message(buf.read(msg_till).strip())
- buf.seek(n+1, os.SEEK_CUR)
- rest = buf.read()
- buf = StringIO()
- buf.write(rest)
- buf.seek(0)
- expect = 0
- self._buffer = buf
- self._parsing_state = expect
- self._parsing_pos = self._buffer.tell()
-
- def load_known_hosts(self, filename=None):
- """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 None:
- filename = os.path.expanduser('~/.ssh/known_hosts')
- try:
- self._host_keys.load(filename)
- except IOError:
- # for windows
- filename = os.path.expanduser('~/ssh/known_hosts')
- try:
- self._host_keys.load(filename)
- except IOError:
- pass
- else:
- self._host_keys.load(filename)
-
- def close(self):
- if self._transport.is_active():
- self._transport.close()
- self._connected = False
-
- # 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 attempting publickey authentication altogether, call with *allow_agent* and *look_for_keys* as `False`.
-
- *host* is the hostname or IP address to connect to
-
- *port* is by default 830, but some devices use the default SSH port of 22 so this may need to be specified
-
- *timeout* is an optional timeout for socket connect
-
- *unknown_host_cb* is called when the server host key is not recognized. It takes two arguments, the hostname and the fingerprint (see the signature of :func:`default_unknown_host_cb`)
-
- *username* is the username to use for SSH authentication
-
- *password* is the password used if using password authentication, or the passphrase to use for unlocking keys that require it
-
- *key_filename* is a filename where a the private key to be used can be found
-
- *allow_agent* enables querying SSH agent (if found) for keys
-
- *look_for_keys* enables looking in the usual locations for ssh keys (e.g. :file:`~/.ssh/id_*`)
- """
- if username is None:
- username = getpass.getuser()
-
- sock = None
- for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC, socket.SOCK_STREAM):
- af, socktype, proto, canonname, sa = res
- try:
- sock = socket.socket(af, socktype, proto)
- sock.settimeout(timeout)
- except socket.error:
- continue
- try:
- sock.connect(sa)
- except socket.error:
- sock.close()
- continue
- break
- else:
- raise SSHError("Could not open socket to %s:%s" % (host, port))
-
- t = self._transport = paramiko.Transport(sock)
- t.set_log_channel(logger.name)
-
- try:
- t.start_client()
- except paramiko.SSHException:
- raise SSHError('Negotiation failed')
-
- # host key verification
- server_key = t.get_remote_server_key()
- known_host = self._host_keys.check(host, server_key)
-
- fingerprint = _colonify(hexlify(server_key.get_fingerprint()))
-
- if not known_host and not unknown_host_cb(host, fingerprint):
- raise SSHUnknownHostError(host, fingerprint)
-
- if key_filename is None:
- key_filenames = []
- elif isinstance(key_filename, basestring):
- key_filenames = [ key_filename ]
- else:
- key_filenames = key_filename
-
- self._auth(username, password, key_filenames, allow_agent, look_for_keys)
-
- self._connected = True # there was no error authenticating
-
- c = self._channel = self._transport.open_session()
- c.set_name("netconf")
- c.invoke_subsystem("netconf")
-
- self._post_connect()
-
- # on the lines of paramiko.SSHClient._auth()
- def _auth(self, username, password, key_filenames, allow_agent,
- look_for_keys):
- saved_exception = None
-
- for key_filename in key_filenames:
- for cls in (paramiko.RSAKey, paramiko.DSSKey):
- try:
- key = cls.from_private_key_file(key_filename, password)
- logger.debug("Trying key %s from %s",
- hexlify(key.get_fingerprint()), key_filename)
- self._transport.auth_publickey(username, key)
- return
- except Exception as e:
- saved_exception = e
- logger.debug(e)
-
- if allow_agent:
- for key in paramiko.Agent().get_keys():
- try:
- logger.debug("Trying SSH agent key %s",
- hexlify(key.get_fingerprint()))
- self._transport.auth_publickey(username, key)
- return
- except Exception as e:
- saved_exception = e
- logger.debug(e)
-
- keyfiles = []
- if look_for_keys:
- rsa_key = os.path.expanduser("~/.ssh/id_rsa")
- dsa_key = os.path.expanduser("~/.ssh/id_dsa")
- if os.path.isfile(rsa_key):
- keyfiles.append((paramiko.RSAKey, rsa_key))
- if os.path.isfile(dsa_key):
- keyfiles.append((paramiko.DSSKey, dsa_key))
- # look in ~/ssh/ for windows users:
- rsa_key = os.path.expanduser("~/ssh/id_rsa")
- dsa_key = os.path.expanduser("~/ssh/id_dsa")
- if os.path.isfile(rsa_key):
- keyfiles.append((paramiko.RSAKey, rsa_key))
- if os.path.isfile(dsa_key):
- keyfiles.append((paramiko.DSSKey, dsa_key))
-
- for cls, filename in keyfiles:
- try:
- key = cls.from_private_key_file(filename, password)
- logger.debug("Trying discovered key %s in %s",
- hexlify(key.get_fingerprint()), filename)
- self._transport.auth_publickey(username, key)
- return
- except Exception as e:
- saved_exception = e
- logger.debug(e)
-
- if password is not None:
- try:
- self._transport.auth_password(username, password)
- return
- except Exception as e:
- saved_exception = e
- logger.debug(e)
-
- if saved_exception is not None:
- # need pep-3134 to do this right
- raise AuthenticationError(repr(saved_exception))
-
- raise AuthenticationError("No authentication methods available")
-
- def run(self):
- chan = self._channel
- q = self._q
- try:
- while True:
- # 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)
- if r:
- data = chan.recv(BUF_SIZE)
- if data:
- self._buffer.write(data)
- self._parse()
- else:
- raise SessionCloseError(self._buffer.getvalue())
- if not q.empty() and chan.send_ready():
- logger.debug("Sending message")
- data = q.get() + MSG_DELIM
- while data:
- n = chan.send(data)
- if n <= 0:
- raise SessionCloseError(self._buffer.getvalue(), data)
- data = data[n:]
- except Exception as e:
- logger.debug("Broke out of main loop, error=%r", e)
- self.close()
- self._dispatch_error(e)
-
- @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 :meth:`~paramiko.Transport.set_keepalive` on it."
- return self._transport
diff --git a/ryu/contrib/ncclient/xml_.py b/ryu/contrib/ncclient/xml_.py
deleted file mode 100644
index 9e94ef0a..00000000
--- a/ryu/contrib/ncclient/xml_.py
+++ /dev/null
@@ -1,108 +0,0 @@
-# Copyright 2009 Shikhar Bhushan
-# Copyright 2011 Leonidas Poulopoulos
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"Methods for creating, parsing, and dealing with XML and ElementTree objects."
-
-from cStringIO import StringIO
-from xml.etree import cElementTree as ET
-
-# In case issues come up with XML generation/parsing
-# make sure you have the ElementTree v1.2.7+ lib
-
-from ncclient import NCClientError
-
-class XMLError(NCClientError): pass
-
-### Namespace-related
-
-#: Base NETCONF namespace
-BASE_NS_1_0 = "urn:ietf:params:xml:ns:netconf:base:1.0"
-#: Namespace for Tail-f core data model
-TAILF_AAA_1_1 = "http://tail-f.com/ns/aaa/1.1"
-#: Namespace for Tail-f execd data model
-TAILF_EXECD_1_1 = "http://tail-f.com/ns/execd/1.1"
-#: Namespace for Cisco data model
-CISCO_CPI_1_0 = "http://www.cisco.com/cpi_10/schema"
-#: Namespace for Flowmon data model
-FLOWMON_1_0 = "http://www.liberouter.org/ns/netopeer/flowmon/1.0"
-#: Namespace for Juniper 9.6R4. Tested with Junos 9.6R4+
-JUNIPER_1_1 = "http://xml.juniper.net/xnm/1.1/xnm"
-#
-try:
- register_namespace = ET.register_namespace
-except AttributeError:
- def register_namespace(prefix, uri):
- 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',
- TAILF_AAA_1_1: 'aaa',
- TAILF_EXECD_1_1: 'execd',
- CISCO_CPI_1_0: 'cpi',
- FLOWMON_1_0: 'fm',
- JUNIPER_1_1: 'junos',
-}.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*."""
-
-def to_xml(ele, encoding="UTF-8"):
- "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 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 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.
-
- *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:
- if isinstance(tags, basestring):
- tags = [tags]
- if ele.tag not in tags:
- raise XMLError("Element [%s] does not meet requirement" % ele.tag)
- if attrs:
- for req in attrs:
- if isinstance(req, basestring): req = [req]
- for alt in req:
- if alt in ele.attrib:
- break
- else:
- raise XMLError("Element [%s] does not have required attributes" % ele.tag)
- return ele
-
-new_ele = lambda tag, attrs={}, **extra: ET.Element(qualify(tag), attrs, **extra)
-
-sub_ele = lambda parent, tag, attrs={}, **extra: ET.SubElement(parent, qualify(tag), attrs, **extra)
-
diff --git a/tools/test-requires b/tools/test-requires
index 9153f066..8510ede1 100644
--- a/tools/test-requires
+++ b/tools/test-requires
@@ -9,3 +9,4 @@ lxml==3.4.0; platform_python_implementation == 'PyPy'
cryptography==1.5
paramiko # NETCONF, BGP speaker
tinyrpc # RPC
+ncclient # OF-Config