Statistics
| Branch: | Tag: | Revision:

root / ncclient / rpc / rpc.py @ cc8de468

History | View | Annotate | Download (5.1 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

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

    
33
def cisco_check(session):
34
    try:
35
        return session.is_remote_cisco
36
    except AttributeError:
37
        return False
38

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

    
119

    
120
class RPCReplyListener(Listener):
121
    
122
    # TODO - determine if need locking
123
    
124
    # one instance per session
125
    def __new__(cls, session):
126
        instance = session.get_listener_instance(cls)
127
        if instance is None:
128
            instance = object.__new__(cls)
129
            instance._id2rpc = WeakValueDictionary()
130
            instance._cisco = cisco_check(session)
131
            instance._errback = None
132
            session.add_listener(instance)
133
        return instance
134
    
135
    def __str__(self):
136
        return 'RPCReplyListener'
137
    
138
    def set_errback(self, errback):
139
        self._errback = errback
140

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