Revision 6625258b

b/ncclient/content/common.py
13 13
# limitations under the License.
14 14

  
15 15
BASE_NS = 'urn:ietf:params:xml:ns:netconf:base:1.0'
16
CISCO_BS = 'urn:ietf:params:netconf:base:1.0'
16 17

  
17 18
def qualify(tag, namespace=None):
18 19
    'Returns qualified name of form `{namespace}tag`'
19
    if namespace is None:
20
        return tag
21
    else:
20
    if namespace is not None:
22 21
        return '{%s}%s' % (namespace, tag)
22
    else:
23
        return tag
24

  
25
unqualify = lambda tag: tag[tag.rfind('}')+1:]
b/ncclient/content/parsers.py
13 13
# limitations under the License.
14 14

  
15 15
from xml.etree import cElementTree as ET
16
from cStringIO import StringIO
16 17

  
17 18
from common import BASE_NS
18 19
from common import qualify as _
20
from common import unqualify as __
19 21

  
20 22
class HelloParser:
21 23

  
......
24 26
        'Returns tuple of (session-id, ["capability_uri", ...])'
25 27
        sid, capabilities = 0, []
26 28
        root = ET.fromstring(raw)
27
        # cisco spews un-namespaced xml
28
        htag = ('hello', _('hello', BASE_NS))
29
        stag = ('session-id', _('session-id', BASE_NS))
30
        ctag = ('capabilities', _('capabilities', BASE_NS))
31
        if root.tag in htag:
29
        # cisco's too posh to namespace its hello
30
        if __(root.tag) == 'hello':
32 31
            for child in root.getchildren():
33
                if child.tag in stag:
32
                if __(child.tag) == 'session-id':
34 33
                    sid = child.text
35
                elif child.tag in ctag:
34
                elif __(child.tag) == 'capabilities':
36 35
                    for cap in child.getiterator('capability'): 
37 36
                        capabilities.append(cap.text)
38 37
                    for cap in child.getiterator(_('capability', BASE_NS)):
39 38
                        capabilities.append(cap.text)
40 39
        return sid, capabilities
41 40

  
42

  
43 41
class RootParser:
44 42
    '''Parser for the top-level element of an XML document. Does not look at any
45 43
    sub-elements. It is useful for efficiently determining the type of received
46 44
    messages.
47 45
    '''
48 46
    
49
    def __init__(self, recognize=[]):
50
        self._recognized = recognize
51
    
52
    def recognize(self, element):
53
        '''Specify an element that should be successfully parsed.
54
        
55
        element should be a string that represents a qualified name of the form
56
        `{namespace}tag`.
57
        '''
58
        self._recognized.append(element)
59
    
60
    def parse(self, raw):
47
    @staticmethod
48
    def parse(raw, recognized=[]):
61 49
        '''Parse the top-level element from a string representing an XML document.
62 50
        
51
        recognized is a list of tag names that will be successfully parsed.
52
        The tag names should not be qualified. This is for simplicity of parsing
53
        where the NETCONF implementation is non-compliant (e.g. cisco's which 
54
        uses an incorrect namespace)
55
        
63 56
        Returns a `(tag, attributes)` tuple, where `tag` is a string representing
64 57
        the qualified name of the recognized element and `attributes` is an
65 58
        `{attribute: value}` dictionary.
66 59
        '''
67 60
        fp = StringIO(raw)
68 61
        for event, element in ET.iterparse(fp, events=('start',)):
69
            for ele in self._recognized:
70
                if element.tag == ele:
71
                    return (element.tag, element.attrib)
62
            for ele in recognized:
63
                if __(element.tag) == ele:
64
                    attrs = {}
65
                    for attr in element.attrib:
66
                        attrs[__(attr)] = element.attrib[attr]
67
                    return (ele, attrs)
72 68
            break
73
        return None
69

  
70

  
71
class RPCReplyParser:
72
    
73
    @staticmethod
74
    def parse(raw):
75
        pass
b/ncclient/operations/__init__.py
24 24
#from notification import CreateSubscription
25 25

  
26 26
__all__ = [
27
#    'Get',
28
#    'GetConfig',
29
#    'EditConfig',
30
#    'DeleteConfig',
31
#    'Lock',
32
#    'Unlock',
27
    #'Get',
28
    #'GetConfig',
29
    #'EditConfig',
30
    #'DeleteConfig',
31
    'Lock',
32
    'Unlock',
33 33
    'CloseSession',
34 34
    'KillSession',
35 35
#    'CreateSubscription',
36
#    ]
36
]
b/ncclient/operations/lock.py
19 19

  
20 20
class Lock(RPC):
21 21
    
22
    def __init__(self):
23
        RPC.__init__(self)
22
    def __init__(self, session):
23
        RPC.__init__(self, session)
24 24
        self.spec = {
25 25
            'tag': 'lock',
26
            'children': [ { 'tag': 'target', 'text': None } ]
26
            'children': { 'tag': 'target', 'children': {'tag': None} }
27 27
            }
28 28
    
29 29
    def request(self, target='running', reply_event=None):
30
        self.spec['children'][0]['text'] = target
30
        self.spec['children']['children']['tag'] = target
31 31
        self._do_request(self.spec, reply_event)
32 32

  
33
class Unock(RPC):
33

  
34
class Unlock(RPC):
34 35
    
35
    def __init__(self):
36
        RPC.__init__(self)
36
    def __init__(self, session):
37
        RPC.__init__(self, session)
37 38
        self.spec = {
38 39
            'tag': 'unlock',
39
            'children': [ { 'tag': 'target', 'text': None } ]
40
            'children': { 'tag': 'target', 'children': {'tag': None} }
40 41
            }
41 42
    
42 43
    def request(self, target='running', reply_event=None):
43
        self.spec['children'][0]['text'] = target
44
        self.spec['children']['children']['tag'] = target
44 45
        self._do_request(self.spec, reply_event)
b/ncclient/operations/rpc.py
14 14

  
15 15
from threading import Event, Lock
16 16
from uuid import uuid1
17
from weakref import WeakKeyDictionary, WeakValueDictionary
17
from weakref import WeakKeyDictionary
18 18

  
19
from listener import get_listener
19
from listener import SessionListener
20 20
from ncclient.content.builders import RPCBuilder
21
from ncclient.content.parsers import RootParser
22
from ncclient.content.common import qualify as _
23
from ncclient.content.common import BASE_NS
24 21

  
25 22
class RPC:
26 23
    
......
36 33
    @property
37 34
    def _listener(self):
38 35
        with self._lock:
39
            return self._listeners.setdefault(self._session, MessageListener())
36
            return self._listeners.setdefault(self._session, SessionListener())
40 37
    
41
    def _response_cb(self, raw):
38
    def deliver(self, raw):
42 39
        self._reply = RPCReply(raw)
43
        reply_event.set()
40
        self._reply_event.set()
44 41
    
45 42
    def _do_request(self, op, reply_event=None):
46 43
        self._id = uuid1().urn
47 44
        # get the listener instance for this session
48 45
        # <rpc-reply> with message id will reach response_cb
49
        self._listener.register(self._id, self._response_cb)
46
        self._listener.register(self._id, self)
50 47
        # only effective the first time, transport.session.Subject internally
51 48
        # uses a set type for listeners
52 49
        self._session.add_listener(self._listener)
......
92 89
    
93 90
    def __str__(self):
94 91
        return self._raw
95

  
96
    @property
97
    def raw(self):
98
        return self._raw
99 92
    
100 93
    def parse(self):
101 94
        #errs = RPCParser.parse(self._raw)
......
104 97
        self._parsed = True
105 98
    
106 99
    @property
100
    def raw(self):
101
        return self._raw
102
    
103
    @property
107 104
    def parsed(self):
108 105
        return self._parsed
109 106
    
110 107
    @property
111 108
    def ok(self):
112
        return True if self._parsed and not self._err else False
109
        return True if self._parsed and not self._errs else False
113 110
    
114 111
    @property
115 112
    def errors(self):
......
159 156
    @property
160 157
    def info(self):
161 158
        return self._dict.get('info', None)
162

  
163

  
164
class SessionListener:
165
    
166
    '''This is the glue between received data and the object it should be
167
    forwarded to.
168
    '''
169
    
170
    def __init__(self):
171
        # this dictionary takes care of <rpc-reply> elements received
172
        # { 'message-id': callback } dict
173
        self._id2cb = WeakValueDictionary()
174
        # this is a more generic dict takes care of other top-level elements
175
        # that may be received, e.g. <notification>'s
176
        # {'tag': callback} dict
177
        self._tag2cb = WeakValueDictionary() 
178
        # if we receive a SessionCloseError it might not be one we want to act on
179
        self._expecting_close = False
180
        self._errback = None # error event callback
181
        self._lock = Lock()
182
    
183
    def __str__(self):
184
        return 'SessionListener'
185
    
186
    def register(self, msgid, cb):
187
        with self._lock:
188
            self._id2cb[msgid] = cb
189
    
190
    def recognize(self, tag, cb):
191
        with self._lock:
192
            self._tag2cb[tag] = cb
193
    
194
    def expect_close(self):
195
        self._expecting_close = True
196
    
197
    @property
198
    def _recognized_elements(self):
199
        elems = [_('rpc-reply', BASE_NS)]
200
        with self._lock:
201
            elems.extend(self._tag2cb.keys())
202
        return elems
203
    
204
    def reply(self, raw):
205
        tag, attrs = RootParser.parse(raw, self._recognized_elements)
206
        try:
207
            cb = None
208
            if tag == _('rpc-reply', BASE_NS):
209
                try:
210
                    id = attrs[_('message-id', BASE_NS)]
211
                except KeyError:
212
                    logger.warning('<rpc-reply> w/o message-id attr received: %s'
213
                                   % raw)
214
                cb = self._id2cb.get(id, None)
215
            else:
216
                cb = self._tag2cb.get(tag, None)
217
            if cb is not None:
218
                cb(raw)
219
        except Exception as e:
220
            logger.warning('SessionListener.reply: %r' % e)
221
    
222
    def set_errback(self, errback):
223
        self._errback = errback
224
    
225
    def error(self, err):
226
        from ncclient.transport.error import SessionCloseError
227
        act = True
228
        if isinstance(err, SessionCloseError):
229
            logger.debug('session closed, expecting_close=%s' %
230
                         self._expecting_close)
231
            if self._expecting_close:
232
                act = False
233
        if act:
234
            logger.error('SessionListener.error: %r' % err)
235
            if self._errback is not None:
236
                errback(err)
b/ncclient/operations/session.py
14 14

  
15 15
'Session-related NETCONF operations'
16 16

  
17
from ncclient.content.parsers import RPCParser
18 17
from rpc import RPC
19 18

  
20 19

  
21 20
class CloseSession(RPC):
22 21
    
23
    def __init__(self):
24
        RPC.__init__(self)
22
    def __init__(self, session):
23
        RPC.__init__(self, session)
25 24
        self.spec = { 'tag': 'close-session' }
26 25
    
27
    def _response_cb(self, reply):
28
        RPC._response_cb(self, reply)
29
        if RPCParser.parse_ok(reply):
26
    def deliver(self, reply):
27
        RPC.deliver(self, reply)
28
        self._reply.parse()
29
        if self._reply.ok:
30 30
            self._listener.expect_close()
31 31
        self._session.close()
32 32
    
33
    def request(self, *args, **kwds):
34
        self._do_request(spec, *args, **kwds)
33
    def request(self, reply_event=None):
34
        self._do_request(self.spec, reply_event)
35 35

  
36 36

  
37 37
class KillSession(RPC):
38 38
    
39
    def __init__(self):
40
        RPC.__init__(self)
39
    def __init__(self, session):
40
        RPC.__init__(self, session)
41 41
        self.spec = {
42 42
            'tag': 'kill-session',
43 43
            'children': [ { 'tag': 'session-id', 'text': None} ]
b/ncclient/transport/errors.py
19 19
    def __init__(self, in_buf, out_buf=None):
20 20
        msg = 'Unexpected session close.'
21 21
        if in_buf:
22
            msg += '.. IN_BUFFER: |%s| ' % in_buf
22
            msg += ' .. IN_BUFFER: ||%s|| ' % in_buf
23 23
        if out_buf:
24
            msg += '.. OUT_BUFFER: |%s|' % out_buf
24
            msg += ' .. OUT_BUFFER: ||%s||' % out_buf
25 25
        SSHError.__init__(self, msg)
26 26

  
27 27
class SSHError(TransportError):
b/ncclient/transport/session.py
43 43
        with self._lock:
44 44
            listeners = list(self._listeners)
45 45
        for l in listeners:
46
            logger.debug('dispatching [%s] to [%s]' % (event, l))
46 47
            try:
47
                logger.debug('dispatching [%s] to [%s]' % (event, l))
48 48
                getattr(l, event)(*args, **kwds)
49
            except AttributeError as e:
50
                logger.debug('Subject.dispatch: %r' % e)
49 51
            except Exception as e:
50
                pass # if a listener doesn't care for some event we don't care
52
                logger.warning('Subject.dispatch: %r' % e)
51 53

  
52 54

  
53 55
class Session(Thread, Subject):
......
90 92
        logger.debug('queueing:%s' % message)
91 93
        self._q.put(message)
92 94
    
93
    def connect(self):
95
    def connect(self, *args, **kwds):
94 96
        raise NotImplementedError
95 97

  
96 98
    def run(self):
b/ncclient/transport/ssh.py
74 74
            else: # if we didn't break out of the loop, full delim was parsed
75 75
                msg_till = buf.tell() - n
76 76
                buf.seek(0)
77
                msg = buf.read(msg_till)
78
                self.dispatch('received', msg)
77
                self.dispatch('received', buf.read(msg_till).strip())
79 78
                buf.seek(n+1, os.SEEK_CUR)
80 79
                rest = buf.read()
81 80
                buf = StringIO()
b/ncclient/transport/util.py
20 20
        return 'DebugListener'
21 21
    
22 22
    def received(self, raw):
23
        logger.debug('DebugListener:[received]:%s' % raw)
23
        logger.debug('DebugListener:[received]:||%s||' % raw)
24 24
    
25 25
    def error(self, err):
26 26
        logger.debug('DebugListener:[error]:%r' % err)

Also available in: Unified diff