From 2f8bc4385d6570b6134487535e1b08f7166e362f Mon Sep 17 00:00:00 2001 From: Shikhar Bhushan Date: Wed, 6 May 2009 22:30:31 +0000 Subject: [PATCH] fixes git-svn-id: http://ncclient.googlecode.com/svn/trunk@98 6dbcf712-26ac-11de-a2f3-1373824ab735 --- ncclient/__init__.py | 2 +- ncclient/content.py | 19 +++-- ncclient/manager.py | 82 +++++++++++++------- ncclient/operations/__init__.py | 13 +++- ncclient/operations/edit.py | 68 +++++++--------- ncclient/operations/retrieve.py | 14 ++-- .../operations/{notification.py => subscribe.py} | 2 +- ncclient/operations/util.py | 7 +- ncclient/rpc/__init__.py | 8 +- ncclient/rpc/listener.py | 3 + ncclient/rpc/reply.py | 16 ++-- ncclient/rpc/rpc.py | 6 +- ncclient/transport/__init__.py | 9 ++- ncclient/transport/errors.py | 5 +- ncclient/transport/hello.py | 6 +- ncclient/transport/session.py | 6 +- ncclient/transport/ssh.py | 4 +- 17 files changed, 151 insertions(+), 119 deletions(-) rename ncclient/operations/{notification.py => subscribe.py} (97%) diff --git a/ncclient/__init__.py b/ncclient/__init__.py index d8e0543..80d2732 100644 --- a/ncclient/__init__.py +++ b/ncclient/__init__.py @@ -38,7 +38,7 @@ class NCClientError(Exception): class TransportError(NCClientError): pass -class OperationError(NCClientError): +class RPCError(NCClientError): pass class OperationError(NCClientError): diff --git a/ncclient/content.py b/ncclient/content.py index 8bcb75c..8ce888b 100644 --- a/ncclient/content.py +++ b/ncclient/content.py @@ -16,6 +16,7 @@ from xml.etree import cElementTree as ET +iselement = ET.iselement ### Namespace-related ### @@ -58,11 +59,8 @@ class XMLConverter: xml = ET.tostring(self._root, encoding) # some etree versions don't include xml decl with utf-8 # this is a problem with some devices - if encoding == 'utf-8': - return ((u'' - % encoding).encode(encoding) + xml) - else: - return xml + return (xml if xml.startswith('%s' % (encoding, xml)) @property def tree(self): @@ -76,14 +74,15 @@ class XMLConverter: return spec elif isinstance(spec, basestring): return ET.XML(spec) - # assume isinstance(spec, dict) - elif 'tag' in spec: + ## assume isinstance(spec, dict) + if 'tag' in spec: ele = ET.Element(spec.get('tag'), spec.get('attributes', {})) - ele.text = str(spec.get('text', '')) + ele.text = spec.get('text', '') children = spec.get('children', []) - if isinstance(children, dict): children = [children] + if isinstance(children, dict): + children = [children] for child in children: - ET.SubElement(ele, TreeBuilder.build(child)) + ele.append(XMLConverter.build(child)) return ele elif 'comment' in spec: return ET.Comment(spec.get('comment')) diff --git a/ncclient/manager.py b/ncclient/manager.py index 8dbc141..4cc6018 100644 --- a/ncclient/manager.py +++ b/ncclient/manager.py @@ -16,10 +16,6 @@ import capabilities import operations import transport -SESSION_TYPES = { - 'ssh': transport.SSHSession -} - OPERATIONS = { 'get': operations.Get, 'get-config': operations.GetConfig, @@ -35,36 +31,66 @@ OPERATIONS = { 'kill-session': operations.KillSession, } -class Manager(type): +def connect_ssh(*args, **kwds): + session = transport.SSHSession(capabilities.CAPABILITIES) + session.load_system_host_keys() + session.connect(*args, **kwds) + return Manager(session) + +connect = connect_ssh # default + +class Manager: 'Facade for the API' - def connect(self, session_type, *args, **kwds): - self._session = SESSION_TYPES[session_type](capabilities.CAPABILITIES) - self._session.connect(*args, **kwds) - - def __getattr__(self, name): - name = name.replace('_', '-') - if name in OPERATIONS: - return OPERATIONS[name](self._session).request - else: - raise AttributeError - - def get(self, *args, **kwds): - g = operations.Get(self._session) - reply = g.request(*args, **kwds) - if reply.errors: - raise RPCError(reply.errors) - else: - return reply.data + def __init__(self, session): + self._session = session - def get_config(self, *args, **kwds): - gc = operations.GetConfig(self._session) - reply = gc.request(*args, **kwds) - if reply.errors: - raise RPCError(reply.errors) + def _get(self, type, *args, **kwds): + op = OPERATIONS[type](self._session) + reply = op.request(*args, **kwds) + if not reply.ok: + raise reply.errors[0] else: return reply.data + def request(op, *args, **kwds): + op = OPERATIONS[op](self._session) + reply = op.request(*args, **kwds) + if not reply.ok: + raise reply.errors[0] + return reply + def locked(self, target='running'): return LockContext(self._session, target) + + get = lambda self, *args, **kwds: self._get('get') + + get_config = lambda self, *args, **kwds: self._get('get-config') + + edit_config = lambda self, *args, **kwds: self.request('edit-config', *args, **kwds) + + copy_config = lambda self, *args, **kwds: self.request('copy-config', *args, **kwds) + + validate = lambda self, *args, **kwds: self.request('validate', *args, **kwds) + + commit = lambda self, *args, **kwds: self.request('commit', *args, **kwds) + + discard_changes = lambda self, *args, **kwds: self.request('discard-changes', *args, **kwds) + + delete_config = lambda self, *args, **kwds: self.request('delete-config', *args, **kwds) + + lock = lambda self, *args, **kwds: self.request('lock', *args, **kwds) + + unlock = lambda self, *args, **kwds: self.request('unlock', *args, **kwds) + + close_session = lambda self, *args, **kwds: self.request('close-session', *args, **kwds) + + kill_session = lambda self, *args, **kwds: self.request('kill-session', *args, **kwds) + + def close(self): + try: + self.close_session() + except: + self._session.expect_close() + self._session.close() diff --git a/ncclient/operations/__init__.py b/ncclient/operations/__init__.py index 65cde12..3c3e60d 100644 --- a/ncclient/operations/__init__.py +++ b/ncclient/operations/__init__.py @@ -14,8 +14,13 @@ 'NETCONF protocol operations' -import logging -logger = logging.getLogger('ncclient.operations') +from ncclient import NCClientError + +class OperationError(NCClientError): + pass + +class MissingCapabilityError(OperationError): + pass from retrieve import Get, GetConfig from edit import EditConfig, CopyConfig, DeleteConfig, Validate, Commit, DiscardChanges @@ -23,7 +28,10 @@ from session import CloseSession, KillSession from lock import Lock, Unlock from subscribe import CreateSubscription + __all__ = [ + 'OperationError', + 'MissingCapabilityError', 'Get', 'GetConfig', 'EditConfig', @@ -37,5 +45,4 @@ __all__ = [ 'CloseSession', 'KillSession', 'CreateSubscription', - # hmm ] diff --git a/ncclient/operations/edit.py b/ncclient/operations/edit.py index 1d66afb..00dc3a5 100644 --- a/ncclient/operations/edit.py +++ b/ncclient/operations/edit.py @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -from ncclient.capabilities import URI from ncclient.rpc import RPC +from ncclient.content import iselement import util @@ -21,16 +21,24 @@ class EditConfig(RPC): SPEC = { 'tag': 'edit-config', - 'children': [ - { 'target': None } - ] + 'children': [ ] } - def request(self): - pass + def request(self, target=None, target_url=None, config=None, + default_operation=None, test_option=None, error_option=None): + util.one_of(target, target_url) + spec = EditConfig.SPEC.copy() + params = spec['children'] + params.append({'tag': 'target', 'children': util.store_or_url(target, target_url)}) + params.append({'tag': 'config', 'children': config}) + if default_operation is not None: + params.append({'tag': 'default-operation', 'text': default_operation}) + if test_option is not None: + params.append({'tag': 'test-option', 'text': test_option}) + if error_option is not None: + params.append({'tag': 'test-option', 'text': test_option}) - -class DeleteConfig(RPC): # x +class DeleteConfig(RPC): SPEC = { 'tag': 'delete-config', @@ -43,7 +51,7 @@ class DeleteConfig(RPC): # x return self._request(spec) -class CopyConfig(RPC): # x +class CopyConfig(RPC): SPEC = { 'tag': 'copy-config', @@ -60,7 +68,9 @@ class CopyConfig(RPC): # x return self._request(spec) -class Validate(RPC): # xxxxx +class Validate(RPC): + + 'config attr shd not include root' DEPENDS = [':validate'] @@ -70,42 +80,18 @@ class Validate(RPC): # xxxxx } def request(self, source=None, config=None): - #self.either_or(source, config) - # - #if source is None and config is None: - # raise OperationError('Insufficient parameters') - #if source is not None and config is not None: - # raise OperationError('Too many parameters') - #spec = Validate.SPEC.copy() - # util.one_of(source, capability) + spec = SPEC.copy() if source is not None: spec['children'].append({ - 'tag': 'source', - 'children': {'tag': source} + 'tag': 'source', 'children': {'tag': source} }) - # - #else: - # if isinstance(config, dict): - # if config['tag'] != 'config': - # child['tag'] = 'config' - # child['children'] = config - # else: - # child = config - # elif isinstance(config, Element): - # pass - # else: - # from xml.etree import cElementTree as ET - # ele = ET.XML(unicode(config)) - # if __(ele.tag) != 'config': - # pass - # else: - # pass - # spec['children'].append(child) - # + else: + spec['children'].append({'tag': 'config', 'children': config}) return self._request(spec) -class Commit(RPC): # x + +class Commit(RPC): DEPENDS = [':candidate'] @@ -128,7 +114,7 @@ class Commit(RPC): # x return self._request(Commit.SPEC) -class DiscardChanges(RPC): # x +class DiscardChanges(RPC): DEPENDS = [':candidate'] diff --git a/ncclient/operations/retrieve.py b/ncclient/operations/retrieve.py index 8c2e62a..e747d1f 100644 --- a/ncclient/operations/retrieve.py +++ b/ncclient/operations/retrieve.py @@ -25,7 +25,12 @@ def build_filter(spec, type, criteria): filter['attributes']['select'] = criteria return filter -class Get(RPC): # xx +class GetReply(RPCReply): + + def parse(self): + RPCReply.parse(self) + +class Get(RPC): SPEC = { 'tag': 'get', @@ -54,7 +59,7 @@ class GetConfig(RPC): # xx 'children': [ { 'tag': 'source', 'children': {'tag': None } } ] } - REPLY_CLS = GetConfigReply + REPLY_CLS = GetReply def request(self, source=None, source_url=None, filter=None): self._one_of(source, source_url) @@ -70,8 +75,3 @@ class GetConfig(RPC): # xx # self._assert(':xpath') spec['children'].append(build_filter(*filter)) return self._request(spec) - -class GetReply(RPCReply): - - def parse(self): - RPCReply.parse(self) diff --git a/ncclient/operations/notification.py b/ncclient/operations/subscribe.py similarity index 97% rename from ncclient/operations/notification.py rename to ncclient/operations/subscribe.py index c4c82fb..b98d870 100644 --- a/ncclient/operations/notification.py +++ b/ncclient/operations/subscribe.py @@ -14,7 +14,7 @@ # TODO when can actually test it... -from rpc import RPC +from ncclient.rpc import RPC from ncclient.glue import Listener from ncclient.content import qualify as _ diff --git a/ncclient/operations/util.py b/ncclient/operations/util.py index abe86a3..653640c 100644 --- a/ncclient/operations/util.py +++ b/ncclient/operations/util.py @@ -1,11 +1,10 @@ #!/usr/bin/env python -'boilerplate' +'Boilerplate' from ncclient import OperationError -class MissingCapabilityError(OperationError): - pass +from . import MissingCapabilityError def one_of(self, *args): for i, arg in enumerate(args): @@ -20,7 +19,7 @@ def one_of(self, *args): def assert_capability(key, capabilities): if key not in capabilities: - raise MissingCapabilityError + raise MissingCapabilityError('[%s] capability is required for this operation' % key) def store_or_url(store, url): diff --git a/ncclient/rpc/__init__.py b/ncclient/rpc/__init__.py index b0de5ad..c6fccea 100644 --- a/ncclient/rpc/__init__.py +++ b/ncclient/rpc/__init__.py @@ -13,14 +13,16 @@ # limitations under the License. from rpc import RPC -from reply import RPCReply +from reply import RPCReply, RPCError -from ncclient import RPCError +import ncclient -class ReplyTimeoutError(RPCError): pass +class ReplyTimeoutError(ncclient.RPCError): + pass __all__ = [ 'RPC', 'RPCReply', + 'RPCError', 'ReplyTimeoutError' ] diff --git a/ncclient/rpc/listener.py b/ncclient/rpc/listener.py index 965bf85..c25a1a8 100644 --- a/ncclient/rpc/listener.py +++ b/ncclient/rpc/listener.py @@ -15,6 +15,9 @@ from threading import Lock from weakref import WeakValueDictionary +from ncclient.glue import Listener +from ncclient.content import unqualify as __ + import logging logger = logging.getLogger('ncclient.rpc.listener') diff --git a/ncclient/rpc/reply.py b/ncclient/rpc/reply.py index 47b9725..1d56dc6 100644 --- a/ncclient/rpc/reply.py +++ b/ncclient/rpc/reply.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +import ncclient + from xml.etree import cElementTree as ET from ncclient.content import multiqualify as _ @@ -32,7 +34,9 @@ class RPCReply: return self._raw def parse(self): - if self._parsed: return + if self._parsed: + return + root = self._root = ET.fromstring(self._raw) # element if __(root.tag) != 'rpc-reply': @@ -60,10 +64,6 @@ class RPCReply: if self._errors: break - if self.ok: - # TODO: store children in some way... - pass - self._parsed = True @property @@ -86,14 +86,14 @@ class RPCReply: return self._errors -class RPCError(Exception): # raise it if you like +class RPCError(ncclient.RPCError): # raise it if you like def __init__(self, err_dict): self._dict = err_dict if self.message is not None: - Exception.__init__(self, self.message) + ncclient.RPCError.__init__(self, self.message) else: - Exception.__init__(self) + ncclient.RPCError.__init__(self) @property def raw(self): diff --git a/ncclient/rpc/rpc.py b/ncclient/rpc/rpc.py index 2c5c202..83e31f7 100644 --- a/ncclient/rpc/rpc.py +++ b/ncclient/rpc/rpc.py @@ -16,7 +16,7 @@ from threading import Event, Lock from uuid import uuid1 from weakref import WeakValueDictionary -from ncclient.content import TreeBuilder +from ncclient.content import XMLConverter from ncclient.content import qualify as _ from ncclient.content import unqualify as __ from ncclient.glue import Listener @@ -49,14 +49,14 @@ class RPC(object): self._reply = None self._reply_event = Event() - def _build(opspec, encoding='utf-8'): + def _build(self, opspec, encoding='utf-8'): "TODO: docstring" spec = { 'tag': _('rpc'), 'attributes': {'message-id': self._id}, 'children': opspec } - return TreeBuilder(spec).to_string(encoding) + return XMLConverter(spec).to_string(encoding) def _request(self, op): req = self._build(op) diff --git a/ncclient/transport/__init__.py b/ncclient/transport/__init__.py index 1e3fa8c..024b8fd 100644 --- a/ncclient/transport/__init__.py +++ b/ncclient/transport/__init__.py @@ -16,4 +16,11 @@ from ssh import SSHSession -__all__ = ['SSHSession'] \ No newline at end of file +__all__ = [ + 'TransportError', + 'AuthenticationError', + 'SessionCloseError', + 'SSHError', + 'SSHUnknownHostError', + 'SSHSession' +] \ No newline at end of file diff --git a/ncclient/transport/errors.py b/ncclient/transport/errors.py index 70e91bd..bc96f2b 100644 --- a/ncclient/transport/errors.py +++ b/ncclient/transport/errors.py @@ -14,7 +14,10 @@ "TODO: docstrings" -from ncclient import TransportError +from ncclient import NCClientError + +class TransportError(NCClientError): + pass class AuthenticationError(TransportError): pass diff --git a/ncclient/transport/hello.py b/ncclient/transport/hello.py index 576be48..7bcde2c 100644 --- a/ncclient/transport/hello.py +++ b/ncclient/transport/hello.py @@ -17,7 +17,7 @@ from xml.etree import cElementTree as ET from ncclient.glue import Listener -from ncclient.content import TreeBuilder, BASE_NS +from ncclient.content import XMLConverter, BASE_NS from ncclient.content import qualify as _ from ncclient.content import unqualify as __ @@ -43,7 +43,7 @@ class HelloHandler(Listener): self._error_cb(err) @staticmethod - def build(capabilities, encoding='utf-8'): + def build(capabilities): "Given a list of capability URI's returns encoded message" spec = { 'tag': _('hello', BASE_NS), @@ -53,7 +53,7 @@ class HelloHandler(Listener): [{ 'tag': 'capability', 'text': uri} for uri in capabilities] }] } - return TreeBuilder(spec).to_string(encoding) + return XMLConverter(spec).to_string() @staticmethod def parse(raw): diff --git a/ncclient/transport/session.py b/ncclient/transport/session.py index 56c57bb..c8de673 100644 --- a/ncclient/transport/session.py +++ b/ncclient/transport/session.py @@ -15,7 +15,7 @@ from threading import Event from Queue import Queue -from ncclient.capabilities import Capabilities, CAPABILITIES +from ncclient.capabilities import Capabilities from ncclient.glue import Subject from hello import HelloHandler @@ -27,12 +27,12 @@ class Session(Subject): "TODO: docstring" - def __init__(self): + def __init__(self, capabilities): "Subclass constructor should call this" Subject.__init__(self) self.setName('session') self._q = Queue() - self._client_capabilities = CAPABILITIES + self._client_capabilities = capabilities self._server_capabilities = None # yet self._id = None # session-id self._connected = False # to be set/cleared by subclass implementation diff --git a/ncclient/transport/ssh.py b/ncclient/transport/ssh.py index eef0421..ee8f95e 100644 --- a/ncclient/transport/ssh.py +++ b/ncclient/transport/ssh.py @@ -32,8 +32,8 @@ TICK = 0.1 class SSHSession(Session): - def __init__(self): - Session.__init__(self) + def __init__(self, *args, **kwds): + Session.__init__(self, *args, **kwds) self._host_keys = paramiko.HostKeys() self._system_host_keys = paramiko.HostKeys() self._transport = None -- 1.7.10.4