Statistics
| Branch: | Tag: | Revision:

root / ncclient / operations / rpc.py @ 4f650d54

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