Revision e91a5349

b/ncclient/content.py
24 24
# cisco returns incorrectly namespaced xml
25 25
CISCO_BS = 'urn:ietf:params:netconf:base:1.0'
26 26

  
27
# we'd like BASE_NS to be prefixed as "netconf"
28 27
try:
29 28
    register_namespace = ET.register_namespace
30 29
except AttributeError:
......
33 32
        # cElementTree uses ElementTree's _namespace_map, so that's ok
34 33
        ElementTree._namespace_map[uri] = prefix
35 34

  
35
# we'd like BASE_NS to be prefixed as "netconf"
36 36
register_namespace('netconf', BASE_NS)
37 37

  
38 38
qualify = lambda tag, ns: '{%s}%s' % (namespace, tag)
39 39

  
40 40
unqualify = lambda tag: tag[tag.rfind('}')+1:]
41 41

  
42

  
43 42
################################################################################
44 43
# Build XML using Python data structures :-)
45 44

  
b/ncclient/glue.py
36 36

  
37 37
    def __init__(self):
38 38
        "TODO: docstring"
39
        self._q = Queue()
39 40
        self._listeners = set([])
40
        self._outQ = Queue()
41 41
        self._lock = Lock()
42

  
42
    
43 43
    def _dispatch_received(self, raw):
44 44
        "TODO: docstring"
45 45
        root = parse_root(raw)
......
68 68
    def send(self, message):
69 69
        "TODO: docstring"
70 70
        logger.debug('queueing:%s' % message)
71
        self._outQ.put(message)
71
        self._q.put(message)
72 72

  
73 73

  
74 74
class Listener:
/dev/null
1
# Copyright 2009 Shikhar Bhushan
2
#
3
# Licensed under the Apache License, Version 2.0 (the "License");
4
# you may not use this file except in compliance with the License.
5
# You may obtain a copy of the License at
6
#
7
#    http://www.apache.org/licenses/LICENSE-2.0
8
#
9
# Unless required by applicable law or agreed to in writing, software
10
# distributed under the License is distributed on an "AS IS" BASIS,
11
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
# See the License for the specific language governing permissions and
13
# limitations under the License.
14

  
15
from threading import Lock
16
from weakref import WeakValueDictionary
17

  
18
from . import logger
19
from ncclient.content.parsers import RootParser
20
from ncclient.content.common import qualify as _
21
from ncclient.content.common import unqualify as __
22
from ncclient.content.common import BASE_NS, CISCO_BS
23

  
24
q_rpcreply = [_('rpc-reply', BASE_NS), _('rpc-reply', CISCO_BS)]
25

  
26
class SessionListener:
27
    
28
    '''This is the glue between received data and the object it should be
29
    forwarded to.
30
    '''
31
    
32
    def __init__(self):
33
        # this dictionary takes care of <rpc-reply> elements received
34
        # { 'message-id': obj } dict
35
        self._id2rpc = WeakValueDictionary()
36
        # this is a more generic dict takes care of other top-level elements
37
        # that may be received, e.g. <notification>'s
38
        # {'tag': obj} dict
39
        self._tag2obj = WeakValueDictionary() 
40
        # if we receive a SessionCloseError it might not be one we want to act on
41
        self._expecting_close = False
42
        self._errback = None # error event callback
43
        self._lock = Lock()
44
    
45
    def __str__(self):
46
        return 'SessionListener'
47
    
48
    def register(self, msgid, rpc):
49
        with self._lock:
50
            self._id2rpc[msgid] = rpc
51
    
52
    def recognize(self, tag, obj):
53
        with self._lock:
54
            self._tag2obj[tag] = obj
55
    
56
    def expect_close(self):
57
        self._expecting_close = True
58
    
59
    @property
60
    def _recognized_elements(self):
61
        elems = q_rpcreply
62
        with self._lock:
63
            elems.extend(self._tag2obj.keys())
64
        return elems
65
    
66
    def set_errback(self, errback):
67
        self._errback = errback
68
    
69
    def received(self, raw):
70
        res = RootParser.parse(raw, self._recognized_elements)
71
        if res is not None:
72
            tag, attrs = res # unpack
73
        else:
74
            return
75
        logger.debug('SessionListener.reply: parsed (%r, %r)' % res)
76
        try:
77
            obj = None
78
            if tag in q_rpcreply:
79
                for key in attrs:
80
                    if __(key) == 'message-id':
81
                        id = attrs[key]
82
                        break
83
                else:
84
                    logger.warning('<rpc-reply> without message-id received: %s'
85
                                   % raw)
86
                obj = self._id2rpc.get(id, None)
87
            else:
88
                obj = self._tag2obj.get(tag, None)
89
            if obj is not None:
90
                obj.deliver(raw)
91
        except Exception as e:
92
            logger.warning('SessionListener.reply: %r' % e)
93
    
94
    def error(self, err):
95
        from ncclient.transport.errors import SessionCloseError
96
        act = True
97
        if isinstance(err, SessionCloseError):
98
            logger.debug('session closed, expecting_close=%s' %
99
                         self._expecting_close)
100
            if self._expecting_close:
101
                act = False
102
        if act:
103
            logger.error('SessionListener.error: %r' % err)
104
            if self._errback is not None:
105
                errback(err)
b/ncclient/operations/listeners.py
1
# Copyright 2009 Shikhar Bhushan
2
#
3
# Licensed under the Apache License, Version 2.0 (the "License");
4
# you may not use this file except in compliance with the License.
5
# You may obtain a copy of the License at
6
#
7
#    http://www.apache.org/licenses/LICENSE-2.0
8
#
9
# Unless required by applicable law or agreed to in writing, software
10
# distributed under the License is distributed on an "AS IS" BASIS,
11
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
# See the License for the specific language governing permissions and
13
# limitations under the License.
14

  
15
from threading import Lock
16
from weakref import WeakValueDictionary
17

  
18
from . import logger
19

  
20

  
21
class RPCReplyListener(Listener):
22
    
23
    '''This is the glue between received data and the object it should be
24
    forwarded to.
25
    '''
26
    
27
    def __init__(self):
28
        # this dictionary takes care of <rpc-reply> elements received
29
        # { 'message-id': obj } dict
30
        self._id2rpc = WeakValueDictionary()
31
        # this is a more generic dict takes care of other top-level elements
32
        # that may be received, e.g. <notification>'s
33
        # {'tag': obj} dict
34
        self._tag2obj = WeakValueDictionary() 
35
        # if we receive a SessionCloseError it might not be one we want to act on
36
        self._expecting_close = False
37
        self._errback = None # error event callback
38
        self._lock = Lock()
39
    
40
    def __str__(self):
41
        return 'SessionListener'
42
    
43
    def register(self, msgid, rpc):
44
        with self._lock:
45
            self._id2rpc[msgid] = rpc
46
    
47
    def recognize(self, tag, obj):
48
        with self._lock:
49
            self._tag2obj[tag] = obj
50
    
51
    def expect_close(self):
52
        self._expecting_close = True
53
    
54
    @property
55
    def _recognized_elements(self):
56
        elems = q_rpcreply
57
        with self._lock:
58
            elems.extend(self._tag2obj.keys())
59
        return elems
60
    
61
    def set_errback(self, errback):
62
        self._errback = errback
63
    
64
    def received(self, raw):
65
        res = RootParser.parse(raw, self._recognized_elements)
66
        if res is not None:
67
            tag, attrs = res # unpack
68
        else:
69
            return
70
        logger.debug('SessionListener.reply: parsed (%r, %r)' % res)
71
        try:
72
            obj = None
73
            if tag in q_rpcreply:
74
                for key in attrs:
75
                    if __(key) == 'message-id':
76
                        id = attrs[key]
77
                        break
78
                else:
79
                    logger.warning('<rpc-reply> without message-id received: %s'
80
                                   % raw)
81
                obj = self._id2rpc.get(id, None)
82
            else:
83
                obj = self._tag2obj.get(tag, None)
84
            if obj is not None:
85
                obj.deliver(raw)
86
        except Exception as e:
87
            logger.warning('SessionListener.reply: %r' % e)
88
    
89
    def error(self, err):
90
        from ncclient.transport.errors import SessionCloseError
91
        act = True
92
        if isinstance(err, SessionCloseError):
93
            logger.debug('session closed, expecting_close=%s' %
94
                         self._expecting_close)
95
            if self._expecting_close:
96
                act = False
97
        if act:
98
            logger.error('SessionListener.error: %r' % err)
99
            if self._errback is not None:
100
                errback(err)
101

  
102

  
103
class NotificationListener(Listener):
104
    
105
    pass
b/ncclient/operations/rpc.py
16 16
from uuid import uuid1
17 17
from weakref import WeakKeyDictionary
18 18

  
19
from . import logger
19 20
from listener import SessionListener
20 21
from ncclient.content.builders import RPCBuilder
21 22
from ncclient.content.parsers import RPCReplyParser
22 23

  
24
_listeners = WeakKeyDictionary()
25
_lock = Lock()
26

  
27
def get_listener(session):
28
    with self._lock:
29
        return _listeners.setdefault(session, ReplyListener())
30

  
23 31
class RPC:
24
    
25
    _listeners = WeakKeyDictionary()
26
    _lock = Lock()
27
    
32
        
28 33
    def __init__(self, session):
29 34
        self._session = session
30 35
        self._id = None
......
32 37
        self._reply_event = None
33 38
    
34 39
    @property
35
    def _listener(self):
36
        with self._lock:
37
            return self._listeners.setdefault(self._session, SessionListener())
38 40
    
39
    def deliver(self, raw):
40
        self._reply = RPCReply(raw)
41
        self._reply_event.set()
42
    
43
    def _do_request(self, op, reply_event=None):
41
    def _request(self, op):
44 42
        self._id = uuid1().urn
43
        self._reply = RPCReply()
45 44
        # get the listener instance for this session
46 45
        # <rpc-reply> with message id will reach response_cb
47 46
        self._listener.register(self._id, self)
......
56 55
            self._reply_event = Event()
57 56
            self._reply_event.wait()
58 57
            self._reply.parse()
59
            return self._reply
58
        return self._reply
60 59
    
61 60
    def request(self, *args, **kwds):
62 61
        raise NotImplementedError
......
82 81

  
83 82
class RPCReply:
84 83
    
85
    def __init__(self, raw):
86
        self._raw = raw
87
        self._parsed = False
88
        self._errs = []
84
    def __init__(self, event):
85
        self._delivery_event = event
86
        self._raw = None
87
        self._errs = None
89 88
    
90 89
    def __str__(self):
91 90
        return self._raw
......
97 96
                self._errs.append(RPCError(raw, err_dict))
98 97
            self._parsed = True
99 98
    
99
    def deliver(self, raw):
100
        self._raw = raw
101
        self._delivery_event.set()
102
    
103
    def received(self, timeout=None):
104
        self._delivery_event.wait(timeout)
105
        return True
106
    
100 107
    @property
101 108
    def raw(self):
102 109
        return self._raw
......
153 160
    @property
154 161
    def info(self):
155 162
        return self._dict.get('info', None)
163

  
164
class Notification:
165
    
166
    pass
167

  
168

  
169

  
170
from builder import TreeBuilder
171
from common import BASE_NS
172
from common import qualify as _
173

  
174
################################################################################
175

  
176
_ = qualify
177

  
178
def build(msgid, op, encoding='utf-8'):
179
    "TODO: docstring"
180
    if isinstance(op, basestring):
181
        return RPCBuilder.build_from_string(msgid, op, encoding)
182
    else:
183
        return RPCBuilder.build_from_spec(msgid, op, encoding)
184

  
185
def build_from_spec(msgid, opspec, encoding='utf-8'):
186
    "TODO: docstring"
187
    spec = {
188
        'tag': _('rpc', BASE_NS),
189
        'attributes': {'message-id': msgid},
190
        'children': opspec
191
        }
192
    return TreeBuilder(spec).to_string(encoding)
193

  
194
def build_from_string(msgid, opstr, encoding='utf-8'):
195
    "TODO: docstring"
196
    decl = '<?xml version="1.0" encoding="%s"?>' % encoding
197
    doc = (u'''<rpc message-id="%s" xmlns="%s">%s</rpc>''' %
198
           (msgid, BASE_NS, opstr)).encode(encoding)
199
    return (decl + doc)
200

  
201
################################################################################
202

  
203
# parsing stuff TODO
204

  
205

  
b/ncclient/transport/__init__.py
15 15
"TODO: docstring"
16 16

  
17 17
import logging
18
logger = logging.getLogger('ncclient.transport')
18
logger = logging.getLogger('ncclient.transport')
b/ncclient/transport/errors.py
12 12
# See the License for the specific language governing permissions and
13 13
# limitations under the License.
14 14

  
15
"TODO: docstrings"
16

  
15 17
from ncclient import TransportError
16 18

  
19
class AuthenticationError(TransportError):
20
    pass
21

  
17 22
class SessionCloseError(TransportError):
18 23
    
19 24
    def __init__(self, in_buf, out_buf=None):
20 25
        msg = 'Unexpected session close.'
21 26
        if in_buf:
22
            msg += ' .. IN_BUFFER: ||%s|| ' % in_buf
27
            msg += ' IN_BUFFER: {%s}' % in_buf
23 28
        if out_buf:
24
            msg += ' .. OUT_BUFFER: ||%s||' % out_buf
29
            msg += ' OUT_BUFFER: {%s}' % out_buf
25 30
        SSHError.__init__(self, msg)
26 31

  
27 32
class SSHError(TransportError):
28 33
    pass
29 34

  
30
class AuthenticationError(TransportError):
31
    pass
32

  
33 35
class SSHUnknownHostError(SSHError):
34 36
    
35 37
    def __init__(self, hostname, key):
b/ncclient/transport/hello.py
16 16

  
17 17
from xml.etree import cElementTree as ET
18 18

  
19
from ncclient.content import TreeBuilder
20
from ncclient.content import BASE_NS
19
from ncclient.glue import Listener
20
from ncclient.content import TreeBuilder, BASE_NS
21 21
from ncclient.content import qualify as _
22

  
22
from ncclient.content import unqualify as __
23 23

  
24 24
def build(capabilities, encoding='utf-8'):
25 25
    "Given a list of capability URI's returns encoded <hello> message"
b/ncclient/transport/session.py
13 13
# limitations under the License.
14 14

  
15 15
from threading import Thread, Event
16

  
16 17
from ncclient.capabilities import Capabilities, CAPABILITIES
18
from ncclient.glue import Subject
19
from ncclient.transport import logger
17 20

  
18 21
import hello
19
from . import logger
20
from ncclient.glue import Subject
21 22

  
22 23
class Session(Thread, Subject):
23 24
    
b/ncclient/transport/ssh.py
29 29
TICK = 0.1
30 30

  
31 31
class SSHSession(Session):
32

  
32
    
33 33
    def __init__(self):
34 34
        Session.__init__(self)
35 35
        self._host_keys = paramiko.HostKeys()
......
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
                self.dispatch('received', buf.read(msg_till).strip())
77
                self._dispatch_received(buf.read(msg_till).strip())
78 78
                buf.seek(n+1, os.SEEK_CUR)
79 79
                rest = buf.read()
80 80
                buf = StringIO()
......
268 268
        except Exception as e:
269 269
            self.close()
270 270
            logger.debug('*** broke out of main loop ***')
271
            self.dispatch('error', e)
271
            self._dispatch_error(e)
272 272
    
273 273
    @property
274 274
    def transport(self):
275
        '''Get underlying paramiko.transport object; this is provided so methods
276
        like transport.set_keepalive can be called.
275
        '''Get underlying paramiko transport object; this is provided so methods
276
        like set_keepalive can be called on it. See paramiko.Transport
277
        documentation for details.
277 278
        '''
278 279
        return self._transport

Also available in: Unified diff