diff options
author | IWASE Yusuke <iwase.yusuke0@gmail.com> | 2016-10-28 10:47:59 +0900 |
---|---|---|
committer | FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp> | 2016-11-04 09:51:27 +0900 |
commit | 7e91a55a5689d78378f4c88b0bbefdcbd0ab701e (patch) | |
tree | e749a7bfafe9baac5776ed142ee3256639751355 | |
parent | fba95e2afaa314572fe49961fe9961a072ff1902 (diff) |
contrib/ncclient: Remove embedded ncclient, use upstream
Signed-off-by: IWASE Yusuke <iwase.yusuke0@gmail.com>
Signed-off-by: FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>
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 |