Revision cc8de468

b/ncclient/content.py
54 54
    def __init__(self, spec):
55 55
        "TODO: docstring"
56 56
        self._root = TreeBuilder.build(spec)
57
        
57
    
58 58
    def to_string(self, encoding='utf-8'):
59 59
        "TODO: docstring"
60 60
        xml = ET.tostring(self._root, encoding)
......
86 86
            return ET.Comment(spec.get('comment'))
87 87
        else:
88 88
            raise ValueError('Invalid tree spec')
89

  
90
class Parser:
91
    pass
92

  
93
class PartialParser(Parser):
94
    
95
    pass
96

  
97
class RootParser(Parser):
98
    
99
    pass
/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

  
16
class Notification:
17
    
18
    pass
/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 xml.etree import cElementTree as ET
16

  
17
from ncclient.content import multiqualify as _
18
from ncclient.content import unqualify as __
19

  
20
import logging
21
logger = logging.getLogger('ncclient.operations.reply')
22

  
23
class RPCReply:
24
    
25
    def __init__(self, raw):
26
        self._raw = raw
27
        self._parsed = False
28
        self._errors = []
29
    
30
    def __repr__(self):
31
        return self._raw
32
    
33
    def parse(self):
34
        if self._parsed: return
35
        root = ET.fromstring(self._raw) # <rpc-reply> element
36
        
37
        if __(root.tag) != 'rpc-reply':
38
            raise ValueError('Root element is not RPC reply')
39
        
40
        # per rfc 4741 an <ok/> tag is sent when there are no errors or warnings
41
        oktags = _('ok')
42
        for oktag in oktags:
43
            if root.find(oktag) is not None:
44
                logger.debug('parsed [%s]' % oktag)
45
                self._parsed = True
46
                return
47
        
48
        # create RPCError objects from <rpc-error> elements
49
        errtags = _('rpc-error')
50
        for errtag in errtags:
51
            for err in root.getiterator(errtag): # a particular <rpc-error>
52
                logger.debug('parsed [%s]' % errtag)
53
                d = {}
54
                for err_detail in err.getchildren(): # <error-type> etc..
55
                    tag = __(err_detail.tag)
56
                    d[tag] = (err_detail.text.strip() if tag != 'error-info'
57
                              else ET.tostring(err_detail, 'utf-8'))
58
                self._errors.append(RPCError(d))
59
            if self._errors:
60
                break
61
        
62
        self._parsed = True
63
    
64
    @property
65
    def raw(self):
66
        return self._raw
67
    
68
    @property
69
    def ok(self):
70
        if not self._parsed: self.parse()
71
        return not self._errors # empty list => false
72
    
73
    @property
74
    def errors(self):
75
        'List of RPCError objects. Will be empty if no <rpc-error> elements in reply.'
76
        if not self._parsed: self.parse()
77
        return self._errors
78

  
79

  
80
class RPCError(Exception): # raise it if you like
81
    
82
    def __init__(self, err_dict):
83
        self._dict = err_dict
84
        if self.message is not None:
85
            Exception.__init__(self, self.message)
86
        else:
87
            Exception.__init__(self)
88
    
89
    @property
90
    def raw(self):
91
        return self._element.tostring()
92
    
93
    @property
94
    def type(self):
95
        return self.get('error-type', None)
96
    
97
    @property
98
    def severity(self):
99
        return self.get('error-severity', None)
100
    
101
    @property
102
    def tag(self):
103
        return self.get('error-tag', None)
104
    
105
    @property
106
    def path(self):
107
        return self.get('error-path', None)
108
    
109
    @property
110
    def message(self):
111
        return self.get('error-message', None)
112
    
113
    @property
114
    def info(self):
115
        return self.get('error-info', None)
116

  
117
    ## dictionary interface
118
    
119
    __getitem__ = lambda self, key: self._dict.__getitem__(key)
120
    
121
    __iter__ = lambda self: self._dict.__iter__()
122
    
123
    __contains__ = lambda self, key: self._dict.__contains__(key)
124
    
125
    keys = lambda self: self._dict.keys()
126
    
127
    get = lambda self, key, default: self._dict.get(key, default)
128
        
129
    iteritems = lambda self: self._dict.iteritems()
130
    
131
    iterkeys = lambda self: self._dict.iterkeys()
132
    
133
    itervalues = lambda self: self._dict.itervalues()
134
    
135
    values = lambda self: self._dict.values()
136
    
137
    items = lambda self: self._dict.items()
138
    
139
    __repr__ = lambda self: repr(self._dict)
/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 Event, Lock
16
from uuid import uuid1
17
from weakref import WeakValueDictionary
18

  
19
from ncclient.content import TreeBuilder
20
from ncclient.content import qualify as _
21
from ncclient.content import unqualify as __
22
from ncclient.glue import Listener
23

  
24
from . import logger
25
from reply import RPCReply
26

  
27

  
28
# Cisco does not include message-id attribute in <rpc-reply> in case of an error.
29
# This is messed up however we have to deal with it.
30
# So essentially, there can be only one operation at a time if we are talking to
31
# a Cisco device.
32

  
33
def cisco_check(session):
34
    try:
35
        return session.is_remote_cisco
36
    except AttributeError:
37
        return False
38

  
39
class RPC(object):
40
    
41
    def __init__(self, session, async=False):
42
        if cisco_check(session) and async:
43
            raise UserWarning('Asynchronous mode not supported for Cisco devices')
44
        self._session = session
45
        self._async = async
46
        self._id = uuid1().urn
47
        self._listener = RPCReplyListener(session)
48
        self._listener.register(self._id, self)
49
        self._reply = None
50
        self._reply_event = Event()
51
    
52
    def _build(self, op, encoding='utf-8'):
53
        if isinstance(op, dict):
54
            return self.build_from_spec(self._id, op, encoding)
55
        else:
56
            return self.build_from_string(self._id, op, encoding)
57
    
58
    def _request(self, op):
59
        req = self._build(op)
60
        self._session.send(req)
61
        if self._async:
62
            return self._reply_event
63
        else:
64
            self._reply_event.wait()
65
            self._reply.parse()
66
            return self._reply
67
    
68
    def _delivery_hook(self):
69
        'For subclasses'
70
        pass
71
    
72
    def deliver(self, raw):
73
        self._reply = RPCReply(raw)
74
        self._delivery_hook()
75
        self._reply_event.set()
76
    
77
    @property
78
    def has_reply(self):
79
        return self._reply_event.isSet()
80
    
81
    @property
82
    def reply(self):
83
        return self._reply
84
    
85
    @property
86
    def is_async(self):
87
        return self._async
88
    
89
    @property
90
    def id(self):
91
        return self._id
92
    
93
    @property
94
    def session(self):
95
        return self._session
96
    
97
    @property
98
    def reply_event(self):
99
        return self._reply_event
100
    
101
    @staticmethod
102
    def build_from_spec(msgid, opspec, encoding='utf-8'):
103
        "TODO: docstring"
104
        spec = {
105
            'tag': _('rpc'),
106
            'attributes': {'message-id': msgid},
107
            'children': opspec
108
            }
109
        return TreeBuilder(spec).to_string(encoding)
110
    
111
    @staticmethod
112
    def build_from_string(msgid, opstr, encoding='utf-8'):
113
        "TODO: docstring"
114
        decl = '<?xml version="1.0" encoding="%s"?>' % encoding
115
        doc = (u'<rpc message-id="%s" xmlns="%s">%s</rpc>' %
116
               (msgid, BASE_NS, opstr)).encode(encoding)
117
        return '%s%s' % (decl, doc)
118

  
119

  
120
class RPCReplyListener(Listener):
121
    
122
    # TODO - determine if need locking
123
    
124
    # one instance per session
125
    def __new__(cls, session):
126
        instance = session.get_listener_instance(cls)
127
        if instance is None:
128
            instance = object.__new__(cls)
129
            instance._id2rpc = WeakValueDictionary()
130
            instance._cisco = cisco_check(session)
131
            instance._errback = None
132
            session.add_listener(instance)
133
        return instance
134
    
135
    def __str__(self):
136
        return 'RPCReplyListener'
137
    
138
    def set_errback(self, errback):
139
        self._errback = errback
140

  
141
    def register(self, id, rpc):
142
        self._id2rpc[id] = rpc
143
    
144
    def callback(self, root, raw):
145
        tag, attrs = root
146
        if __(tag) != 'rpc-reply':
147
            return
148
        rpc = None
149
        for key in attrs:
150
            if __(key) == 'message-id':
151
                id = attrs[key]
152
                try:
153
                    rpc = self._id2rpc.pop(id)
154
                except KeyError:
155
                    logger.warning('[RPCReplyListener.callback] no object '
156
                                   + 'registered for message-id: [%s]' % id)
157
                except Exception as e:
158
                    logger.debug('[RPCReplyListener.callback] error - %r' % e)
159
                break
160
        else:
161
            if self._cisco:
162
                assert(len(self._id2rpc) == 1)
163
                rpc = self._id2rpc.values()[0]
164
                self._id2rpc.clear()
165
            else:
166
                logger.warning('<rpc-reply> without message-id received: %s' % raw)
167
        logger.debug('[RPCReplyListener.callback] delivering to %r' % rpc)
168
        rpc.deliver(raw)
169
    
170
    def errback(self, err):
171
        if self._errback is not None:
172
            self._errback(err)
b/ncclient/operations/subscribe.py
12 12
# See the License for the specific language governing permissions and
13 13
# limitations under the License.
14 14

  
15
# TODO...
15
# TODO when can actually test it...
16 16

  
17 17
from rpc import RPC
18 18

  
......
28 28
        'stream': None
29 29
    }
30 30

  
31
class Notification:
32
    pass
31 33

  
32 34
class NotificationListener(Listener):
33
    
34 35
    pass
b/ncclient/rpc/reply.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 xml.etree import cElementTree as ET
16

  
17
from ncclient.content import multiqualify as _
18
from ncclient.content import unqualify as __
19

  
20
import logging
21
logger = logging.getLogger('ncclient.operations.reply')
22

  
23
class RPCReply:
24
    
25
    def __init__(self, raw):
26
        self._raw = raw
27
        self._parsed = False
28
        self._errors = []
29
    
30
    def __repr__(self):
31
        return self._raw
32
    
33
    def parse(self):
34
        if self._parsed: return
35
        root = ET.fromstring(self._raw) # <rpc-reply> element
36
        
37
        if __(root.tag) != 'rpc-reply':
38
            raise ValueError('Root element is not RPC reply')
39
        
40
        # per rfc 4741 an <ok/> tag is sent when there are no errors or warnings
41
        oktags = _('ok')
42
        for oktag in oktags:
43
            if root.find(oktag) is not None:
44
                logger.debug('parsed [%s]' % oktag)
45
                self._parsed = True
46
                return
47
        
48
        # create RPCError objects from <rpc-error> elements
49
        errtags = _('rpc-error')
50
        for errtag in errtags:
51
            for err in root.getiterator(errtag): # a particular <rpc-error>
52
                logger.debug('parsed [%s]' % errtag)
53
                d = {}
54
                for err_detail in err.getchildren(): # <error-type> etc..
55
                    tag = __(err_detail.tag)
56
                    d[tag] = (err_detail.text.strip() if tag != 'error-info'
57
                              else ET.tostring(err_detail, 'utf-8'))
58
                self._errors.append(RPCError(d))
59
            if self._errors:
60
                break
61
        
62
        self._parsed = True
63
    
64
    @property
65
    def raw(self):
66
        return self._raw
67
    
68
    @property
69
    def ok(self):
70
        if not self._parsed: self.parse()
71
        return not self._errors # empty list => false
72
    
73
    @property
74
    def errors(self):
75
        'List of RPCError objects. Will be empty if no <rpc-error> elements in reply.'
76
        if not self._parsed: self.parse()
77
        return self._errors
78

  
79

  
80
class RPCError(Exception): # raise it if you like
81
    
82
    def __init__(self, err_dict):
83
        self._dict = err_dict
84
        if self.message is not None:
85
            Exception.__init__(self, self.message)
86
        else:
87
            Exception.__init__(self)
88
    
89
    @property
90
    def raw(self):
91
        return self._element.tostring()
92
    
93
    @property
94
    def type(self):
95
        return self.get('error-type', None)
96
    
97
    @property
98
    def severity(self):
99
        return self.get('error-severity', None)
100
    
101
    @property
102
    def tag(self):
103
        return self.get('error-tag', None)
104
    
105
    @property
106
    def path(self):
107
        return self.get('error-path', None)
108
    
109
    @property
110
    def message(self):
111
        return self.get('error-message', None)
112
    
113
    @property
114
    def info(self):
115
        return self.get('error-info', None)
116

  
117
    ## dictionary interface
118
    
119
    __getitem__ = lambda self, key: self._dict.__getitem__(key)
120
    
121
    __iter__ = lambda self: self._dict.__iter__()
122
    
123
    __contains__ = lambda self, key: self._dict.__contains__(key)
124
    
125
    keys = lambda self: self._dict.keys()
126
    
127
    get = lambda self, key, default: self._dict.get(key, default)
128
        
129
    iteritems = lambda self: self._dict.iteritems()
130
    
131
    iterkeys = lambda self: self._dict.iterkeys()
132
    
133
    itervalues = lambda self: self._dict.itervalues()
134
    
135
    values = lambda self: self._dict.values()
136
    
137
    items = lambda self: self._dict.items()
138
    
139
    __repr__ = lambda self: repr(self._dict)
b/ncclient/rpc/rpc.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 Event, Lock
16
from uuid import uuid1
17
from weakref import WeakValueDictionary
18

  
19
from ncclient.content import TreeBuilder
20
from ncclient.content import qualify as _
21
from ncclient.content import unqualify as __
22
from ncclient.glue import Listener
23

  
24
from . import logger
25
from reply import RPCReply
26

  
27

  
28
# Cisco does not include message-id attribute in <rpc-reply> in case of an error.
29
# This is messed up however we have to deal with it.
30
# So essentially, there can be only one operation at a time if we are talking to
31
# a Cisco device.
32

  
33
def cisco_check(session):
34
    try:
35
        return session.is_remote_cisco
36
    except AttributeError:
37
        return False
38

  
39
class RPC(object):
40
    
41
    def __init__(self, session, async=False):
42
        if cisco_check(session) and async:
43
            raise UserWarning('Asynchronous mode not supported for Cisco devices')
44
        self._session = session
45
        self._async = async
46
        self._id = uuid1().urn
47
        self._listener = RPCReplyListener(session)
48
        self._listener.register(self._id, self)
49
        self._reply = None
50
        self._reply_event = Event()
51
    
52
    def _build(self, op, encoding='utf-8'):
53
        if isinstance(op, dict):
54
            return self.build_from_spec(self._id, op, encoding)
55
        else:
56
            return self.build_from_string(self._id, op, encoding)
57
    
58
    def _request(self, op):
59
        req = self._build(op)
60
        self._session.send(req)
61
        if self._async:
62
            return self._reply_event
63
        else:
64
            self._reply_event.wait()
65
            self._reply.parse()
66
            return self._reply
67
    
68
    def _delivery_hook(self):
69
        'For subclasses'
70
        pass
71
    
72
    def deliver(self, raw):
73
        self._reply = RPCReply(raw)
74
        self._delivery_hook()
75
        self._reply_event.set()
76
    
77
    @property
78
    def has_reply(self):
79
        return self._reply_event.isSet()
80
    
81
    @property
82
    def reply(self):
83
        return self._reply
84
    
85
    @property
86
    def is_async(self):
87
        return self._async
88
    
89
    @property
90
    def id(self):
91
        return self._id
92
    
93
    @property
94
    def session(self):
95
        return self._session
96
    
97
    @property
98
    def reply_event(self):
99
        return self._reply_event
100
    
101
    @staticmethod
102
    def build_from_spec(msgid, opspec, encoding='utf-8'):
103
        "TODO: docstring"
104
        spec = {
105
            'tag': _('rpc'),
106
            'attributes': {'message-id': msgid},
107
            'children': opspec
108
            }
109
        return TreeBuilder(spec).to_string(encoding)
110
    
111
    @staticmethod
112
    def build_from_string(msgid, opstr, encoding='utf-8'):
113
        "TODO: docstring"
114
        decl = '<?xml version="1.0" encoding="%s"?>' % encoding
115
        doc = (u'<rpc message-id="%s" xmlns="%s">%s</rpc>' %
116
               (msgid, BASE_NS, opstr)).encode(encoding)
117
        return '%s%s' % (decl, doc)
118

  
119

  
120
class RPCReplyListener(Listener):
121
    
122
    # TODO - determine if need locking
123
    
124
    # one instance per session
125
    def __new__(cls, session):
126
        instance = session.get_listener_instance(cls)
127
        if instance is None:
128
            instance = object.__new__(cls)
129
            instance._id2rpc = WeakValueDictionary()
130
            instance._cisco = cisco_check(session)
131
            instance._errback = None
132
            session.add_listener(instance)
133
        return instance
134
    
135
    def __str__(self):
136
        return 'RPCReplyListener'
137
    
138
    def set_errback(self, errback):
139
        self._errback = errback
140

  
141
    def register(self, id, rpc):
142
        self._id2rpc[id] = rpc
143
    
144
    def callback(self, root, raw):
145
        tag, attrs = root
146
        if __(tag) != 'rpc-reply':
147
            return
148
        rpc = None
149
        for key in attrs:
150
            if __(key) == 'message-id':
151
                id = attrs[key]
152
                try:
153
                    rpc = self._id2rpc.pop(id)
154
                except KeyError:
155
                    logger.warning('[RPCReplyListener.callback] no object '
156
                                   + 'registered for message-id: [%s]' % id)
157
                except Exception as e:
158
                    logger.debug('[RPCReplyListener.callback] error - %r' % e)
159
                break
160
        else:
161
            if self._cisco:
162
                assert(len(self._id2rpc) == 1)
163
                rpc = self._id2rpc.values()[0]
164
                self._id2rpc.clear()
165
            else:
166
                logger.warning('<rpc-reply> without message-id received: %s' % raw)
167
        logger.debug('[RPCReplyListener.callback] delivering to %r' % rpc)
168
        rpc.deliver(raw)
169
    
170
    def errback(self, err):
171
        if self._errback is not None:
172
            self._errback(err)

Also available in: Unified diff