fixes after a session of testing
authorShikhar Bhushan <shikhar@schmizz.net>
Sun, 26 Apr 2009 06:46:02 +0000 (06:46 +0000)
committerShikhar Bhushan <shikhar@schmizz.net>
Sun, 26 Apr 2009 06:46:02 +0000 (06:46 +0000)
git-svn-id: http://ncclient.googlecode.com/svn/trunk@67 6dbcf712-26ac-11de-a2f3-1373824ab735

ncclient/content/common.py
ncclient/content/parsers.py
ncclient/operations/__init__.py
ncclient/operations/lock.py
ncclient/operations/rpc.py
ncclient/operations/session.py
ncclient/transport/errors.py
ncclient/transport/session.py
ncclient/transport/ssh.py
ncclient/transport/util.py

index 08d021f..a504963 100644 (file)
 # limitations under the License.
 
 BASE_NS = 'urn:ietf:params:xml:ns:netconf:base:1.0'
+CISCO_BS = 'urn:ietf:params:netconf:base:1.0'
 
 def qualify(tag, namespace=None):
     'Returns qualified name of form `{namespace}tag`'
-    if namespace is None:
-        return tag
-    else:
+    if namespace is not None:
         return '{%s}%s' % (namespace, tag)
+    else:
+        return tag
+
+unqualify = lambda tag: tag[tag.rfind('}')+1:]
index c094af6..78c2fab 100644 (file)
 # limitations under the License.
 
 from xml.etree import cElementTree as ET
+from cStringIO import StringIO
 
 from common import BASE_NS
 from common import qualify as _
+from common import unqualify as __
 
 class HelloParser:
 
@@ -24,50 +26,50 @@ class HelloParser:
         'Returns tuple of (session-id, ["capability_uri", ...])'
         sid, capabilities = 0, []
         root = ET.fromstring(raw)
-        # cisco spews un-namespaced xml
-        htag = ('hello', _('hello', BASE_NS))
-        stag = ('session-id', _('session-id', BASE_NS))
-        ctag = ('capabilities', _('capabilities', BASE_NS))
-        if root.tag in htag:
+        # cisco's too posh to namespace its hello
+        if __(root.tag) == 'hello':
             for child in root.getchildren():
-                if child.tag in stag:
+                if __(child.tag) == 'session-id':
                     sid = child.text
-                elif child.tag in ctag:
+                elif __(child.tag) == 'capabilities':
                     for cap in child.getiterator('capability'): 
                         capabilities.append(cap.text)
                     for cap in child.getiterator(_('capability', BASE_NS)):
                         capabilities.append(cap.text)
         return sid, capabilities
 
-
 class RootParser:
     '''Parser for the top-level element of an XML document. Does not look at any
     sub-elements. It is useful for efficiently determining the type of received
     messages.
     '''
     
-    def __init__(self, recognize=[]):
-        self._recognized = recognize
-    
-    def recognize(self, element):
-        '''Specify an element that should be successfully parsed.
-        
-        element should be a string that represents a qualified name of the form
-        `{namespace}tag`.
-        '''
-        self._recognized.append(element)
-    
-    def parse(self, raw):
+    @staticmethod
+    def parse(raw, recognized=[]):
         '''Parse the top-level element from a string representing an XML document.
         
+        recognized is a list of tag names that will be successfully parsed.
+        The tag names should not be qualified. This is for simplicity of parsing
+        where the NETCONF implementation is non-compliant (e.g. cisco's which 
+        uses an incorrect namespace)
+        
         Returns a `(tag, attributes)` tuple, where `tag` is a string representing
         the qualified name of the recognized element and `attributes` is an
         `{attribute: value}` dictionary.
         '''
         fp = StringIO(raw)
         for event, element in ET.iterparse(fp, events=('start',)):
-            for ele in self._recognized:
-                if element.tag == ele:
-                    return (element.tag, element.attrib)
+            for ele in recognized:
+                if __(element.tag) == ele:
+                    attrs = {}
+                    for attr in element.attrib:
+                        attrs[__(attr)] = element.attrib[attr]
+                    return (ele, attrs)
             break
-        return None
\ No newline at end of file
+
+
+class RPCReplyParser:
+    
+    @staticmethod
+    def parse(raw):
+        pass
index 86dce57..9dab7f2 100644 (file)
@@ -24,13 +24,13 @@ from lock import Lock, Unlock
 #from notification import CreateSubscription
 
 __all__ = [
-#    'Get',
-#    'GetConfig',
-#    'EditConfig',
-#    'DeleteConfig',
-#    'Lock',
-#    'Unlock',
+    #'Get',
+    #'GetConfig',
+    #'EditConfig',
+    #'DeleteConfig',
+    'Lock',
+    'Unlock',
     'CloseSession',
     'KillSession',
 #    'CreateSubscription',
-#    ]
\ No newline at end of file
+]
index 9af41f6..ed818a5 100644 (file)
@@ -19,26 +19,27 @@ from rpc import RPC
 
 class Lock(RPC):
     
-    def __init__(self):
-        RPC.__init__(self)
+    def __init__(self, session):
+        RPC.__init__(self, session)
         self.spec = {
             'tag': 'lock',
-            'children': [ { 'tag': 'target', 'text': None } ]
+            'children': { 'tag': 'target', 'children': {'tag': None} }
             }
     
     def request(self, target='running', reply_event=None):
-        self.spec['children'][0]['text'] = target
+        self.spec['children']['children']['tag'] = target
         self._do_request(self.spec, reply_event)
 
-class Unock(RPC):
+
+class Unlock(RPC):
     
-    def __init__(self):
-        RPC.__init__(self)
+    def __init__(self, session):
+        RPC.__init__(self, session)
         self.spec = {
             'tag': 'unlock',
-            'children': [ { 'tag': 'target', 'text': None } ]
+            'children': { 'tag': 'target', 'children': {'tag': None} }
             }
     
     def request(self, target='running', reply_event=None):
-        self.spec['children'][0]['text'] = target
+        self.spec['children']['children']['tag'] = target
         self._do_request(self.spec, reply_event)
index b82ab35..8cd1a93 100644 (file)
 
 from threading import Event, Lock
 from uuid import uuid1
-from weakref import WeakKeyDictionary, WeakValueDictionary
+from weakref import WeakKeyDictionary
 
-from listener import get_listener
+from listener import SessionListener
 from ncclient.content.builders import RPCBuilder
-from ncclient.content.parsers import RootParser
-from ncclient.content.common import qualify as _
-from ncclient.content.common import BASE_NS
 
 class RPC:
     
@@ -36,17 +33,17 @@ class RPC:
     @property
     def _listener(self):
         with self._lock:
-            return self._listeners.setdefault(self._session, MessageListener())
+            return self._listeners.setdefault(self._session, SessionListener())
     
-    def _response_cb(self, raw):
+    def deliver(self, raw):
         self._reply = RPCReply(raw)
-        reply_event.set()
+        self._reply_event.set()
     
     def _do_request(self, op, reply_event=None):
         self._id = uuid1().urn
         # get the listener instance for this session
         # <rpc-reply> with message id will reach response_cb
-        self._listener.register(self._id, self._response_cb)
+        self._listener.register(self._id, self)
         # only effective the first time, transport.session.Subject internally
         # uses a set type for listeners
         self._session.add_listener(self._listener)
@@ -92,10 +89,6 @@ class RPCReply:
     
     def __str__(self):
         return self._raw
-
-    @property
-    def raw(self):
-        return self._raw
     
     def parse(self):
         #errs = RPCParser.parse(self._raw)
@@ -104,12 +97,16 @@ class RPCReply:
         self._parsed = True
     
     @property
+    def raw(self):
+        return self._raw
+    
+    @property
     def parsed(self):
         return self._parsed
     
     @property
     def ok(self):
-        return True if self._parsed and not self._err else False
+        return True if self._parsed and not self._errs else False
     
     @property
     def errors(self):
@@ -159,78 +156,3 @@ class RPCError(Exception): # raise it if you like
     @property
     def info(self):
         return self._dict.get('info', None)
-
-
-class SessionListener:
-    
-    '''This is the glue between received data and the object it should be
-    forwarded to.
-    '''
-    
-    def __init__(self):
-        # this dictionary takes care of <rpc-reply> elements received
-        # { 'message-id': callback } dict
-        self._id2cb = WeakValueDictionary()
-        # this is a more generic dict takes care of other top-level elements
-        # that may be received, e.g. <notification>'s
-        # {'tag': callback} dict
-        self._tag2cb = WeakValueDictionary() 
-        # if we receive a SessionCloseError it might not be one we want to act on
-        self._expecting_close = False
-        self._errback = None # error event callback
-        self._lock = Lock()
-    
-    def __str__(self):
-        return 'SessionListener'
-    
-    def register(self, msgid, cb):
-        with self._lock:
-            self._id2cb[msgid] = cb
-    
-    def recognize(self, tag, cb):
-        with self._lock:
-            self._tag2cb[tag] = cb
-    
-    def expect_close(self):
-        self._expecting_close = True
-    
-    @property
-    def _recognized_elements(self):
-        elems = [_('rpc-reply', BASE_NS)]
-        with self._lock:
-            elems.extend(self._tag2cb.keys())
-        return elems
-    
-    def reply(self, raw):
-        tag, attrs = RootParser.parse(raw, self._recognized_elements)
-        try:
-            cb = None
-            if tag == _('rpc-reply', BASE_NS):
-                try:
-                    id = attrs[_('message-id', BASE_NS)]
-                except KeyError:
-                    logger.warning('<rpc-reply> w/o message-id attr received: %s'
-                                   % raw)
-                cb = self._id2cb.get(id, None)
-            else:
-                cb = self._tag2cb.get(tag, None)
-            if cb is not None:
-                cb(raw)
-        except Exception as e:
-            logger.warning('SessionListener.reply: %r' % e)
-    
-    def set_errback(self, errback):
-        self._errback = errback
-    
-    def error(self, err):
-        from ncclient.transport.error import SessionCloseError
-        act = True
-        if isinstance(err, SessionCloseError):
-            logger.debug('session closed, expecting_close=%s' %
-                         self._expecting_close)
-            if self._expecting_close:
-                act = False
-        if act:
-            logger.error('SessionListener.error: %r' % err)
-            if self._errback is not None:
-                errback(err)
index 6288443..313d7a2 100644 (file)
 
 'Session-related NETCONF operations'
 
-from ncclient.content.parsers import RPCParser
 from rpc import RPC
 
 
 class CloseSession(RPC):
     
-    def __init__(self):
-        RPC.__init__(self)
+    def __init__(self, session):
+        RPC.__init__(self, session)
         self.spec = { 'tag': 'close-session' }
     
-    def _response_cb(self, reply):
-        RPC._response_cb(self, reply)
-        if RPCParser.parse_ok(reply):
+    def deliver(self, reply):
+        RPC.deliver(self, reply)
+        self._reply.parse()
+        if self._reply.ok:
             self._listener.expect_close()
         self._session.close()
     
-    def request(self, *args, **kwds):
-        self._do_request(spec, *args, **kwds)
+    def request(self, reply_event=None):
+        self._do_request(self.spec, reply_event)
 
 
 class KillSession(RPC):
     
-    def __init__(self):
-        RPC.__init__(self)
+    def __init__(self, session):
+        RPC.__init__(self, session)
         self.spec = {
             'tag': 'kill-session',
             'children': [ { 'tag': 'session-id', 'text': None} ]
index 5246eb8..bddbb99 100644 (file)
@@ -19,9 +19,9 @@ class SessionCloseError(TransportError):
     def __init__(self, in_buf, out_buf=None):
         msg = 'Unexpected session close.'
         if in_buf:
-            msg += '.. IN_BUFFER: |%s| ' % in_buf
+            msg += ' .. IN_BUFFER: ||%s|| ' % in_buf
         if out_buf:
-            msg += '.. OUT_BUFFER: |%s|' % out_buf
+            msg += ' .. OUT_BUFFER: ||%s||' % out_buf
         SSHError.__init__(self, msg)
 
 class SSHError(TransportError):
index 46fc780..27e4d48 100644 (file)
@@ -43,11 +43,13 @@ class Subject:
         with self._lock:
             listeners = list(self._listeners)
         for l in listeners:
+            logger.debug('dispatching [%s] to [%s]' % (event, l))
             try:
-                logger.debug('dispatching [%s] to [%s]' % (event, l))
                 getattr(l, event)(*args, **kwds)
+            except AttributeError as e:
+                logger.debug('Subject.dispatch: %r' % e)
             except Exception as e:
-                pass # if a listener doesn't care for some event we don't care
+                logger.warning('Subject.dispatch: %r' % e)
 
 
 class Session(Thread, Subject):
@@ -90,7 +92,7 @@ class Session(Thread, Subject):
         logger.debug('queueing:%s' % message)
         self._q.put(message)
     
-    def connect(self):
+    def connect(self, *args, **kwds):
         raise NotImplementedError
 
     def run(self):
index 6fb96c5..eadf4b5 100644 (file)
@@ -74,8 +74,7 @@ class SSHSession(Session):
             else: # if we didn't break out of the loop, full delim was parsed
                 msg_till = buf.tell() - n
                 buf.seek(0)
-                msg = buf.read(msg_till)
-                self.dispatch('received', msg)
+                self.dispatch('received', buf.read(msg_till).strip())
                 buf.seek(n+1, os.SEEK_CUR)
                 rest = buf.read()
                 buf = StringIO()
index 77ceaee..b38a5b3 100644 (file)
@@ -20,7 +20,7 @@ class DebugListener:
         return 'DebugListener'
     
     def received(self, raw):
-        logger.debug('DebugListener:[received]:%s' % raw)
+        logger.debug('DebugListener:[received]:||%s||' % raw)
     
     def error(self, err):
         logger.debug('DebugListener:[error]:%r' % err)