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) |