Statistics
| Branch: | Tag: | Revision:

root / ncclient / operations / rpc.py @ 0cdb8b3c

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