Statistics
| Branch: | Tag: | Revision:

root / ncclient / operations / rpc.py @ e52e8478

History | View | Annotate | Download (8.5 kB)

1 a14c36f9 Shikhar Bhushan
# Copyright 2009 Shikhar Bhushan
2 a14c36f9 Shikhar Bhushan
#
3 a14c36f9 Shikhar Bhushan
# Licensed under the Apache License, Version 2.0 (the "License");
4 a14c36f9 Shikhar Bhushan
# you may not use this file except in compliance with the License.
5 a14c36f9 Shikhar Bhushan
# You may obtain a copy of the License at
6 a14c36f9 Shikhar Bhushan
#
7 a14c36f9 Shikhar Bhushan
#    http://www.apache.org/licenses/LICENSE-2.0
8 a14c36f9 Shikhar Bhushan
#
9 a14c36f9 Shikhar Bhushan
# Unless required by applicable law or agreed to in writing, software
10 a14c36f9 Shikhar Bhushan
# distributed under the License is distributed on an "AS IS" BASIS,
11 a14c36f9 Shikhar Bhushan
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 a14c36f9 Shikhar Bhushan
# See the License for the specific language governing permissions and
13 a14c36f9 Shikhar Bhushan
# limitations under the License.
14 a14c36f9 Shikhar Bhushan
15 a14c36f9 Shikhar Bhushan
from threading import Event, Lock
16 a14c36f9 Shikhar Bhushan
from uuid import uuid1
17 a14c36f9 Shikhar Bhushan
from weakref import WeakValueDictionary
18 a14c36f9 Shikhar Bhushan
19 65c6a607 Shikhar Bhushan
from ncclient import content
20 a14c36f9 Shikhar Bhushan
21 d771dffc Shikhar Bhushan
from errors import OperationError
22 a14c36f9 Shikhar Bhushan
23 a14c36f9 Shikhar Bhushan
import logging
24 a14c36f9 Shikhar Bhushan
logger = logging.getLogger('ncclient.rpc')
25 a14c36f9 Shikhar Bhushan
26 a14c36f9 Shikhar Bhushan
27 a14c36f9 Shikhar Bhushan
class RPCReply:
28 a14c36f9 Shikhar Bhushan
    
29 a14c36f9 Shikhar Bhushan
    'NOTES: memory considerations?? storing both raw xml + ET.Element'
30 a14c36f9 Shikhar Bhushan
    
31 a14c36f9 Shikhar Bhushan
    def __init__(self, raw):
32 a14c36f9 Shikhar Bhushan
        self._raw = raw
33 a14c36f9 Shikhar Bhushan
        self._parsed = False
34 a14c36f9 Shikhar Bhushan
        self._root = None
35 a14c36f9 Shikhar Bhushan
        self._errors = []
36 a14c36f9 Shikhar Bhushan
    
37 a14c36f9 Shikhar Bhushan
    def __repr__(self):
38 a14c36f9 Shikhar Bhushan
        return self._raw
39 a14c36f9 Shikhar Bhushan
    
40 e52e8478 Shikhar Bhushan
    def _parsing_hook(self, root):
41 e52e8478 Shikhar Bhushan
        pass
42 e52e8478 Shikhar Bhushan
    
43 a14c36f9 Shikhar Bhushan
    def parse(self):
44 65c6a607 Shikhar Bhushan
        if self._parsed:
45 65c6a607 Shikhar Bhushan
            return
46 65c6a607 Shikhar Bhushan
        root = self._root = content.xml2ele(self._raw) # <rpc-reply> element
47 a14c36f9 Shikhar Bhushan
        # per rfc 4741 an <ok/> tag is sent when there are no errors or warnings
48 d771dffc Shikhar Bhushan
        ok = content.find(root, 'ok')
49 a14c36f9 Shikhar Bhushan
        if ok is not None:
50 a14c36f9 Shikhar Bhushan
            logger.debug('parsed [%s]' % ok.tag)
51 a14c36f9 Shikhar Bhushan
        else: # create RPCError objects from <rpc-error> elements
52 d771dffc Shikhar Bhushan
            error = content.find(root, 'rpc-error')
53 a14c36f9 Shikhar Bhushan
            if error is not None:
54 a14c36f9 Shikhar Bhushan
                logger.debug('parsed [%s]' % error.tag)
55 a14c36f9 Shikhar Bhushan
                for err in root.getiterator(error.tag):
56 a14c36f9 Shikhar Bhushan
                    # process a particular <rpc-error>
57 a14c36f9 Shikhar Bhushan
                    d = {}
58 a14c36f9 Shikhar Bhushan
                    for err_detail in err.getchildren(): # <error-type> etc..
59 a14c36f9 Shikhar Bhushan
                        tag = content.unqualify(err_detail.tag)
60 d771dffc Shikhar Bhushan
                        if tag != 'error-info':
61 d771dffc Shikhar Bhushan
                            d[tag] = err_detail.text.strip()
62 d771dffc Shikhar Bhushan
                        else:
63 d771dffc Shikhar Bhushan
                            d[tag] = content.ele2xml(err_detail)
64 a14c36f9 Shikhar Bhushan
                    self._errors.append(RPCError(d))
65 a14c36f9 Shikhar Bhushan
        self._parsing_hook(root)
66 a14c36f9 Shikhar Bhushan
        self._parsed = True
67 a14c36f9 Shikhar Bhushan
    
68 a14c36f9 Shikhar Bhushan
    @property
69 a14c36f9 Shikhar Bhushan
    def xml(self):
70 a14c36f9 Shikhar Bhushan
        '<rpc-reply> as returned'
71 a14c36f9 Shikhar Bhushan
        return self._raw
72 a14c36f9 Shikhar Bhushan
    
73 a14c36f9 Shikhar Bhushan
    @property
74 a14c36f9 Shikhar Bhushan
    def ok(self):
75 a14c36f9 Shikhar Bhushan
        if not self._parsed:
76 a14c36f9 Shikhar Bhushan
            self.parse()
77 a14c36f9 Shikhar Bhushan
        return not self._errors # empty list => false
78 a14c36f9 Shikhar Bhushan
    
79 a14c36f9 Shikhar Bhushan
    @property
80 a14c36f9 Shikhar Bhushan
    def error(self):
81 a14c36f9 Shikhar Bhushan
        if not self._parsed:
82 a14c36f9 Shikhar Bhushan
            self.parse()
83 a14c36f9 Shikhar Bhushan
        if self._errors:
84 a14c36f9 Shikhar Bhushan
            return self._errors[0]
85 a14c36f9 Shikhar Bhushan
        else:
86 a14c36f9 Shikhar Bhushan
            return None
87 a14c36f9 Shikhar Bhushan
    
88 a14c36f9 Shikhar Bhushan
    @property
89 a14c36f9 Shikhar Bhushan
    def errors(self):
90 a14c36f9 Shikhar Bhushan
        'List of RPCError objects. Will be empty if no <rpc-error> elements in reply.'
91 a14c36f9 Shikhar Bhushan
        if not self._parsed:
92 a14c36f9 Shikhar Bhushan
            self.parse()
93 a14c36f9 Shikhar Bhushan
        return self._errors
94 a14c36f9 Shikhar Bhushan
95 a14c36f9 Shikhar Bhushan
96 d771dffc Shikhar Bhushan
class RPCError(OperationError): # raise it if you like
97 a14c36f9 Shikhar Bhushan
    
98 a14c36f9 Shikhar Bhushan
    def __init__(self, err_dict):
99 a14c36f9 Shikhar Bhushan
        self._dict = err_dict
100 a14c36f9 Shikhar Bhushan
        if self.message is not None:
101 d771dffc Shikhar Bhushan
            OperationError.__init__(self, self.message)
102 a14c36f9 Shikhar Bhushan
        else:
103 d771dffc Shikhar Bhushan
            OperationError.__init__(self)
104 a14c36f9 Shikhar Bhushan
    
105 a14c36f9 Shikhar Bhushan
    @property
106 a14c36f9 Shikhar Bhushan
    def type(self):
107 a14c36f9 Shikhar Bhushan
        return self.get('error-type', None)
108 a14c36f9 Shikhar Bhushan
    
109 a14c36f9 Shikhar Bhushan
    @property
110 a14c36f9 Shikhar Bhushan
    def severity(self):
111 a14c36f9 Shikhar Bhushan
        return self.get('error-severity', None)
112 a14c36f9 Shikhar Bhushan
    
113 a14c36f9 Shikhar Bhushan
    @property
114 a14c36f9 Shikhar Bhushan
    def tag(self):
115 a14c36f9 Shikhar Bhushan
        return self.get('error-tag', None)
116 a14c36f9 Shikhar Bhushan
    
117 a14c36f9 Shikhar Bhushan
    @property
118 a14c36f9 Shikhar Bhushan
    def path(self):
119 a14c36f9 Shikhar Bhushan
        return self.get('error-path', None)
120 a14c36f9 Shikhar Bhushan
    
121 a14c36f9 Shikhar Bhushan
    @property
122 a14c36f9 Shikhar Bhushan
    def message(self):
123 a14c36f9 Shikhar Bhushan
        return self.get('error-message', None)
124 a14c36f9 Shikhar Bhushan
    
125 a14c36f9 Shikhar Bhushan
    @property
126 a14c36f9 Shikhar Bhushan
    def info(self):
127 a14c36f9 Shikhar Bhushan
        return self.get('error-info', None)
128 a14c36f9 Shikhar Bhushan
129 a14c36f9 Shikhar Bhushan
    ## dictionary interface
130 a14c36f9 Shikhar Bhushan
    
131 a14c36f9 Shikhar Bhushan
    __getitem__ = lambda self, key: self._dict.__getitem__(key)
132 a14c36f9 Shikhar Bhushan
    
133 a14c36f9 Shikhar Bhushan
    __iter__ = lambda self: self._dict.__iter__()
134 a14c36f9 Shikhar Bhushan
    
135 a14c36f9 Shikhar Bhushan
    __contains__ = lambda self, key: self._dict.__contains__(key)
136 a14c36f9 Shikhar Bhushan
    
137 a14c36f9 Shikhar Bhushan
    keys = lambda self: self._dict.keys()
138 a14c36f9 Shikhar Bhushan
    
139 a14c36f9 Shikhar Bhushan
    get = lambda self, key, default: self._dict.get(key, default)
140 a14c36f9 Shikhar Bhushan
        
141 a14c36f9 Shikhar Bhushan
    iteritems = lambda self: self._dict.iteritems()
142 a14c36f9 Shikhar Bhushan
    
143 a14c36f9 Shikhar Bhushan
    iterkeys = lambda self: self._dict.iterkeys()
144 a14c36f9 Shikhar Bhushan
    
145 a14c36f9 Shikhar Bhushan
    itervalues = lambda self: self._dict.itervalues()
146 a14c36f9 Shikhar Bhushan
    
147 a14c36f9 Shikhar Bhushan
    values = lambda self: self._dict.values()
148 a14c36f9 Shikhar Bhushan
    
149 a14c36f9 Shikhar Bhushan
    items = lambda self: self._dict.items()
150 a14c36f9 Shikhar Bhushan
    
151 a14c36f9 Shikhar Bhushan
    __repr__ = lambda self: repr(self._dict)
152 a14c36f9 Shikhar Bhushan
153 a14c36f9 Shikhar Bhushan
154 4de03d63 Shikhar Bhushan
class RPCReplyListener(object):
155 a14c36f9 Shikhar Bhushan
    
156 a14c36f9 Shikhar Bhushan
    # one instance per session
157 a14c36f9 Shikhar Bhushan
    def __new__(cls, session):
158 a14c36f9 Shikhar Bhushan
        instance = session.get_listener_instance(cls)
159 a14c36f9 Shikhar Bhushan
        if instance is None:
160 a14c36f9 Shikhar Bhushan
            instance = object.__new__(cls)
161 a14c36f9 Shikhar Bhushan
            instance._lock = Lock()
162 a14c36f9 Shikhar Bhushan
            instance._id2rpc = WeakValueDictionary()
163 a14c36f9 Shikhar Bhushan
            instance._pipelined = session.can_pipeline
164 a14c36f9 Shikhar Bhushan
            instance._errback = None
165 a14c36f9 Shikhar Bhushan
            session.add_listener(instance)
166 a14c36f9 Shikhar Bhushan
        return instance
167 a14c36f9 Shikhar Bhushan
    
168 a14c36f9 Shikhar Bhushan
    def register(self, id, rpc):
169 a14c36f9 Shikhar Bhushan
        with self._lock:
170 a14c36f9 Shikhar Bhushan
            self._id2rpc[id] = rpc
171 a14c36f9 Shikhar Bhushan
172 a14c36f9 Shikhar Bhushan
    def set_errback(self, errback):
173 a14c36f9 Shikhar Bhushan
        self._errback = errback
174 a14c36f9 Shikhar Bhushan
    
175 a14c36f9 Shikhar Bhushan
    def callback(self, root, raw):
176 a14c36f9 Shikhar Bhushan
        tag, attrs = root
177 a14c36f9 Shikhar Bhushan
        if content.unqualify(tag) != 'rpc-reply':
178 a14c36f9 Shikhar Bhushan
            return
179 a14c36f9 Shikhar Bhushan
        rpc = None
180 a14c36f9 Shikhar Bhushan
        for key in attrs:
181 a14c36f9 Shikhar Bhushan
            if content.unqualify(key) == 'message-id':
182 a14c36f9 Shikhar Bhushan
                id = attrs[key]
183 a14c36f9 Shikhar Bhushan
                try:
184 a14c36f9 Shikhar Bhushan
                    with self._lock:
185 a14c36f9 Shikhar Bhushan
                        rpc = self._id2rpc.pop(id)
186 a14c36f9 Shikhar Bhushan
                except KeyError:
187 a14c36f9 Shikhar Bhushan
                    logger.warning('no object registered for message-id: [%s]' % id)
188 a14c36f9 Shikhar Bhushan
                except Exception as e:
189 a14c36f9 Shikhar Bhushan
                    logger.debug('error - %r' % e)
190 a14c36f9 Shikhar Bhushan
                break
191 a14c36f9 Shikhar Bhushan
        else:
192 a14c36f9 Shikhar Bhushan
            if not self._pipelined:
193 a14c36f9 Shikhar Bhushan
                with self._lock:
194 a14c36f9 Shikhar Bhushan
                    assert(len(self._id2rpc) == 1)
195 a14c36f9 Shikhar Bhushan
                    rpc = self._id2rpc.values()[0]
196 a14c36f9 Shikhar Bhushan
                    self._id2rpc.clear()
197 a14c36f9 Shikhar Bhushan
            else:
198 a14c36f9 Shikhar Bhushan
                logger.warning('<rpc-reply> without message-id received: %s' % raw)
199 a14c36f9 Shikhar Bhushan
        logger.debug('delivering to %r' % rpc)
200 a14c36f9 Shikhar Bhushan
        rpc.deliver(raw)
201 a14c36f9 Shikhar Bhushan
    
202 a14c36f9 Shikhar Bhushan
    def errback(self, err):
203 a14c36f9 Shikhar Bhushan
        if self._errback is not None:
204 a14c36f9 Shikhar Bhushan
            self._errback(err)
205 4de03d63 Shikhar Bhushan
206 4de03d63 Shikhar Bhushan
207 4de03d63 Shikhar Bhushan
class RPC(object):
208 4de03d63 Shikhar Bhushan
    
209 4de03d63 Shikhar Bhushan
    DEPENDS = []
210 4de03d63 Shikhar Bhushan
    REPLY_CLS = RPCReply
211 4de03d63 Shikhar Bhushan
    
212 4de03d63 Shikhar Bhushan
    def __init__(self, session, async=False, timeout=None):
213 4de03d63 Shikhar Bhushan
        if not session.can_pipeline:
214 4de03d63 Shikhar Bhushan
            raise UserWarning('Asynchronous mode not supported for this device/session')
215 4de03d63 Shikhar Bhushan
        self._session = session
216 4de03d63 Shikhar Bhushan
        try:
217 4de03d63 Shikhar Bhushan
            for cap in self.DEPENDS:
218 4de03d63 Shikhar Bhushan
                self._assert(cap)
219 4de03d63 Shikhar Bhushan
        except AttributeError:
220 4de03d63 Shikhar Bhushan
            pass        
221 4de03d63 Shikhar Bhushan
        self._async = async
222 4de03d63 Shikhar Bhushan
        self._timeout = timeout
223 4de03d63 Shikhar Bhushan
        # keeps things simple instead of having a class attr that has to be locked
224 4de03d63 Shikhar Bhushan
        self._id = uuid1().urn
225 4de03d63 Shikhar Bhushan
        # RPCReplyListener itself makes sure there isn't more than one instance -- i.e. multiton
226 4de03d63 Shikhar Bhushan
        self._listener = RPCReplyListener(session)
227 4de03d63 Shikhar Bhushan
        self._listener.register(self._id, self)
228 4de03d63 Shikhar Bhushan
        self._reply = None
229 4de03d63 Shikhar Bhushan
        self._reply_event = Event()
230 4de03d63 Shikhar Bhushan
    
231 e52e8478 Shikhar Bhushan
    def _build(self, opspec):
232 4de03d63 Shikhar Bhushan
        "TODO: docstring"
233 4de03d63 Shikhar Bhushan
        spec = {
234 4de03d63 Shikhar Bhushan
            'tag': content.qualify('rpc'),
235 4de03d63 Shikhar Bhushan
            'attributes': {'message-id': self._id},
236 4de03d63 Shikhar Bhushan
            'subtree': opspec
237 4de03d63 Shikhar Bhushan
            }
238 e52e8478 Shikhar Bhushan
        return content.dtree2xml(spec)
239 4de03d63 Shikhar Bhushan
    
240 4de03d63 Shikhar Bhushan
    def _request(self, op):
241 4de03d63 Shikhar Bhushan
        req = self._build(op)
242 4de03d63 Shikhar Bhushan
        self._session.send(req)
243 4de03d63 Shikhar Bhushan
        if self._async:
244 4de03d63 Shikhar Bhushan
            return self._reply_event
245 4de03d63 Shikhar Bhushan
        else:
246 4de03d63 Shikhar Bhushan
            self._reply_event.wait(self._timeout)
247 4de03d63 Shikhar Bhushan
            if self._reply_event.isSet():
248 4de03d63 Shikhar Bhushan
                self._reply.parse()
249 4de03d63 Shikhar Bhushan
                return self._reply
250 4de03d63 Shikhar Bhushan
            else:
251 4de03d63 Shikhar Bhushan
                raise ReplyTimeoutError
252 4de03d63 Shikhar Bhushan
    
253 4de03d63 Shikhar Bhushan
    def request(self):
254 4de03d63 Shikhar Bhushan
        return self._request(self.SPEC)
255 4de03d63 Shikhar Bhushan
    
256 4de03d63 Shikhar Bhushan
    def _delivery_hook(self):
257 4de03d63 Shikhar Bhushan
        'For subclasses'
258 4de03d63 Shikhar Bhushan
        pass
259 4de03d63 Shikhar Bhushan
    
260 4de03d63 Shikhar Bhushan
    def _assert(self, capability):
261 4de03d63 Shikhar Bhushan
        if capability not in self._session.server_capabilities:
262 4de03d63 Shikhar Bhushan
            raise MissingCapabilityError('Server does not support [%s]' % cap)
263 4de03d63 Shikhar Bhushan
    
264 4de03d63 Shikhar Bhushan
    def deliver(self, raw):
265 4de03d63 Shikhar Bhushan
        self._reply = self.REPLY_CLS(raw)
266 4de03d63 Shikhar Bhushan
        self._delivery_hook()
267 4de03d63 Shikhar Bhushan
        self._reply_event.set()
268 4de03d63 Shikhar Bhushan
    
269 4de03d63 Shikhar Bhushan
    @property
270 4de03d63 Shikhar Bhushan
    def has_reply(self):
271 4de03d63 Shikhar Bhushan
        return self._reply_event.isSet()
272 4de03d63 Shikhar Bhushan
    
273 4de03d63 Shikhar Bhushan
    @property
274 4de03d63 Shikhar Bhushan
    def reply(self):
275 4de03d63 Shikhar Bhushan
        return self._reply
276 4de03d63 Shikhar Bhushan
    
277 4de03d63 Shikhar Bhushan
    @property
278 4de03d63 Shikhar Bhushan
    def id(self):
279 4de03d63 Shikhar Bhushan
        return self._id
280 4de03d63 Shikhar Bhushan
    
281 4de03d63 Shikhar Bhushan
    @property
282 4de03d63 Shikhar Bhushan
    def session(self):
283 4de03d63 Shikhar Bhushan
        return self._session
284 4de03d63 Shikhar Bhushan
    
285 4de03d63 Shikhar Bhushan
    @property
286 4de03d63 Shikhar Bhushan
    def reply_event(self):
287 4de03d63 Shikhar Bhushan
        return self._reply_event
288 4de03d63 Shikhar Bhushan
    
289 4de03d63 Shikhar Bhushan
    def set_async(self, bool): self._async = bool
290 4de03d63 Shikhar Bhushan
    async = property(fget=lambda self: self._async, fset=set_async)
291 4de03d63 Shikhar Bhushan
    
292 4de03d63 Shikhar Bhushan
    def set_timeout(self, timeout): self._timeout = timeout
293 4de03d63 Shikhar Bhushan
    timeout = property(fget=lambda self: self._timeout, fset=set_timeout)