Statistics
| Branch: | Tag: | Revision:

root / ncclient / operations / rpc.py @ 6a10f112

History | View | Annotate | Download (5 kB)

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

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

    
112

    
113
class RPCReplyListener(Listener):
114
    
115
    # TODO - determine if need locking
116
    
117
    # one instance per session
118
    def __new__(cls, session):
119
        instance = session.get_listener_instance(cls)
120
        if instance is None:
121
            instance = object.__new__(cls)
122
            instance._id2rpc = WeakValueDictionary()
123
            instance._cisco = session.is_remote_cisco
124
            instance._errback = None
125
            session.add_listener(instance)
126
        return instance
127
    
128
    def __str__(self):
129
        return 'RPCReplyListener'
130
    
131
    def set_errback(self, errback):
132
        self._errback = errback
133

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