Statistics
| Branch: | Tag: | Revision:

root / ncclient / transport / session.py @ 0b7d3b31

History | View | Annotate | Download (7.9 kB)

1 d095a59e Shikhar Bhushan
# Copyright 2009 Shikhar Bhushan
2 d095a59e Shikhar Bhushan
#
3 d095a59e Shikhar Bhushan
# Licensed under the Apache License, Version 2.0 (the "License");
4 d095a59e Shikhar Bhushan
# you may not use this file except in compliance with the License.
5 d095a59e Shikhar Bhushan
# You may obtain a copy of the License at
6 d095a59e Shikhar Bhushan
#
7 d095a59e Shikhar Bhushan
#    http://www.apache.org/licenses/LICENSE-2.0
8 d095a59e Shikhar Bhushan
#
9 d095a59e Shikhar Bhushan
# Unless required by applicable law or agreed to in writing, software
10 d095a59e Shikhar Bhushan
# distributed under the License is distributed on an "AS IS" BASIS,
11 d095a59e Shikhar Bhushan
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 d095a59e Shikhar Bhushan
# See the License for the specific language governing permissions and
13 d095a59e Shikhar Bhushan
# limitations under the License.
14 d095a59e Shikhar Bhushan
15 d6688264 Shikhar Bhushan
from Queue import Queue
16 4de03d63 Shikhar Bhushan
from threading import Thread, Lock, Event
17 e91a5349 Shikhar Bhushan
18 583c11f6 Shikhar Bhushan
from ncclient import content
19 2f8bc438 Shikhar Bhushan
from ncclient.capabilities import Capabilities
20 d095a59e Shikhar Bhushan
21 41e2ed46 Shikhar Bhushan
import logging
22 41e2ed46 Shikhar Bhushan
logger = logging.getLogger('ncclient.transport.session')
23 41e2ed46 Shikhar Bhushan
24 a14c36f9 Shikhar Bhushan
class Session(Thread):
25 216bb34c Shikhar Bhushan
26 4f650d54 Shikhar Bhushan
    "Base class for use by transport protocol implementations."
27 4f650d54 Shikhar Bhushan
28 2f8bc438 Shikhar Bhushan
    def __init__(self, capabilities):
29 a14c36f9 Shikhar Bhushan
        Thread.__init__(self)
30 4f650d54 Shikhar Bhushan
        self.setDaemon(True)
31 4f650d54 Shikhar Bhushan
        self._listeners = set() # 3.0's weakset would be ideal
32 a14c36f9 Shikhar Bhushan
        self._lock = Lock()
33 4f650d54 Shikhar Bhushan
        self.setName('session')
34 d6688264 Shikhar Bhushan
        self._q = Queue()
35 2f8bc438 Shikhar Bhushan
        self._client_capabilities = capabilities
36 d095a59e Shikhar Bhushan
        self._server_capabilities = None # yet
37 d095a59e Shikhar Bhushan
        self._id = None # session-id
38 d095a59e Shikhar Bhushan
        self._connected = False # to be set/cleared by subclass implementation
39 41e2ed46 Shikhar Bhushan
        logger.debug('%r created: client_capabilities=%r' %
40 41e2ed46 Shikhar Bhushan
                     (self, self._client_capabilities))
41 4f650d54 Shikhar Bhushan
42 a14c36f9 Shikhar Bhushan
    def _dispatch_message(self, raw):
43 a14c36f9 Shikhar Bhushan
        try:
44 583c11f6 Shikhar Bhushan
            root = content.parse_root(raw)
45 a14c36f9 Shikhar Bhushan
        except Exception as e:
46 a14c36f9 Shikhar Bhushan
            logger.error('error parsing dispatch message: %s' % e)
47 a14c36f9 Shikhar Bhushan
            return
48 a14c36f9 Shikhar Bhushan
        with self._lock:
49 a14c36f9 Shikhar Bhushan
            listeners = list(self._listeners)
50 a14c36f9 Shikhar Bhushan
        for l in listeners:
51 a14c36f9 Shikhar Bhushan
            logger.debug('dispatching message to %r' % l)
52 a14c36f9 Shikhar Bhushan
            try:
53 a14c36f9 Shikhar Bhushan
                l.callback(root, raw)
54 a14c36f9 Shikhar Bhushan
            except Exception as e:
55 a14c36f9 Shikhar Bhushan
                logger.warning('[error] %r' % e)
56 4f650d54 Shikhar Bhushan
57 a14c36f9 Shikhar Bhushan
    def _dispatch_error(self, err):
58 a14c36f9 Shikhar Bhushan
        with self._lock:
59 a14c36f9 Shikhar Bhushan
            listeners = list(self._listeners)
60 a14c36f9 Shikhar Bhushan
        for l in listeners:
61 a14c36f9 Shikhar Bhushan
            logger.debug('dispatching error to %r' % l)
62 a14c36f9 Shikhar Bhushan
            try:
63 a14c36f9 Shikhar Bhushan
                l.errback(err)
64 a14c36f9 Shikhar Bhushan
            except Exception as e:
65 0b7d3b31 Shikhar Bhushan
                logger.warning('error dispatching to %r: %r' % (l, e))
66 4f650d54 Shikhar Bhushan
67 65c6a607 Shikhar Bhushan
    def _post_connect(self):
68 65c6a607 Shikhar Bhushan
        "Greeting stuff"
69 65c6a607 Shikhar Bhushan
        init_event = Event()
70 65c6a607 Shikhar Bhushan
        error = [None] # so that err_cb can bind error[0]. just how it is.
71 65c6a607 Shikhar Bhushan
        # callbacks
72 65c6a607 Shikhar Bhushan
        def ok_cb(id, capabilities):
73 65c6a607 Shikhar Bhushan
            self._id = id
74 583c11f6 Shikhar Bhushan
            self._server_capabilities = capabilities
75 65c6a607 Shikhar Bhushan
            init_event.set()
76 65c6a607 Shikhar Bhushan
        def err_cb(err):
77 65c6a607 Shikhar Bhushan
            error[0] = err
78 65c6a607 Shikhar Bhushan
            init_event.set()
79 65c6a607 Shikhar Bhushan
        listener = HelloHandler(ok_cb, err_cb)
80 65c6a607 Shikhar Bhushan
        self.add_listener(listener)
81 65c6a607 Shikhar Bhushan
        self.send(HelloHandler.build(self._client_capabilities))
82 65c6a607 Shikhar Bhushan
        logger.debug('starting main loop')
83 65c6a607 Shikhar Bhushan
        self.start()
84 65c6a607 Shikhar Bhushan
        # we expect server's hello message
85 65c6a607 Shikhar Bhushan
        init_event.wait()
86 65c6a607 Shikhar Bhushan
        # received hello message or an error happened
87 65c6a607 Shikhar Bhushan
        self.remove_listener(listener)
88 65c6a607 Shikhar Bhushan
        if error[0]:
89 65c6a607 Shikhar Bhushan
            raise error[0]
90 216bb34c Shikhar Bhushan
        #if ':base:1.0' not in self.server_capabilities:
91 216bb34c Shikhar Bhushan
        #    raise MissingCapabilityError(':base:1.0')
92 4f650d54 Shikhar Bhushan
        logger.info('initialized: session-id=%s | server_capabilities=%s' %
93 4f650d54 Shikhar Bhushan
                    (self._id, self._server_capabilities))
94 4f650d54 Shikhar Bhushan
95 a14c36f9 Shikhar Bhushan
    def add_listener(self, listener):
96 4f650d54 Shikhar Bhushan
        """Register a listener that will be notified of incoming messages and
97 4f650d54 Shikhar Bhushan
        errors.
98 4f650d54 Shikhar Bhushan

99 216bb34c Shikhar Bhushan
        :type listener: :class:`SessionListener`
100 0cdb8b3c Shikhar Bhushan
        """
101 a14c36f9 Shikhar Bhushan
        logger.debug('installing listener %r' % listener)
102 583c11f6 Shikhar Bhushan
        if not isinstance(listener, SessionListener):
103 583c11f6 Shikhar Bhushan
            raise SessionError("Listener must be a SessionListener type")
104 a14c36f9 Shikhar Bhushan
        with self._lock:
105 a14c36f9 Shikhar Bhushan
            self._listeners.add(listener)
106 4f650d54 Shikhar Bhushan
107 a14c36f9 Shikhar Bhushan
    def remove_listener(self, listener):
108 4f650d54 Shikhar Bhushan
        """Unregister some listener; ignore if the listener was never
109 216bb34c Shikhar Bhushan
        registered.
110 216bb34c Shikhar Bhushan

111 216bb34c Shikhar Bhushan
        :type listener: :class:`SessionListener`
112 216bb34c Shikhar Bhushan
        """
113 a14c36f9 Shikhar Bhushan
        logger.debug('discarding listener %r' % listener)
114 a14c36f9 Shikhar Bhushan
        with self._lock:
115 a14c36f9 Shikhar Bhushan
            self._listeners.discard(listener)
116 4f650d54 Shikhar Bhushan
117 a14c36f9 Shikhar Bhushan
    def get_listener_instance(self, cls):
118 216bb34c Shikhar Bhushan
        """If a listener of the specified type is registered, returns the
119 216bb34c Shikhar Bhushan
        instance.
120 4f650d54 Shikhar Bhushan

121 216bb34c Shikhar Bhushan
        :type cls: :class:`SessionListener`
122 0cdb8b3c Shikhar Bhushan
        """
123 a14c36f9 Shikhar Bhushan
        with self._lock:
124 a14c36f9 Shikhar Bhushan
            for listener in self._listeners:
125 a14c36f9 Shikhar Bhushan
                if isinstance(listener, cls):
126 a14c36f9 Shikhar Bhushan
                    return listener
127 4f650d54 Shikhar Bhushan
128 0cdb8b3c Shikhar Bhushan
    def connect(self, *args, **kwds): # subclass implements
129 d095a59e Shikhar Bhushan
        raise NotImplementedError
130 d095a59e Shikhar Bhushan
131 0cdb8b3c Shikhar Bhushan
    def run(self): # subclass implements
132 d095a59e Shikhar Bhushan
        raise NotImplementedError
133 4f650d54 Shikhar Bhushan
134 d6688264 Shikhar Bhushan
    def send(self, message):
135 4f650d54 Shikhar Bhushan
        """Send the supplied *message* to NETCONF server.
136 4f650d54 Shikhar Bhushan

137 4f650d54 Shikhar Bhushan
        :arg message: an XML document
138 4f650d54 Shikhar Bhushan

139 216bb34c Shikhar Bhushan
        :type message: `string`
140 0cdb8b3c Shikhar Bhushan
        """
141 d6688264 Shikhar Bhushan
        logger.debug('queueing %s' % message)
142 d6688264 Shikhar Bhushan
        self._q.put(message)
143 4f650d54 Shikhar Bhushan
144 d095a59e Shikhar Bhushan
    ### Properties
145 0cdb8b3c Shikhar Bhushan
146 0cdb8b3c Shikhar Bhushan
    @property
147 0cdb8b3c Shikhar Bhushan
    def connected(self):
148 4f650d54 Shikhar Bhushan
        "Connection status of the session."
149 0cdb8b3c Shikhar Bhushan
        return self._connected
150 0cdb8b3c Shikhar Bhushan
151 d095a59e Shikhar Bhushan
    @property
152 d095a59e Shikhar Bhushan
    def client_capabilities(self):
153 4f650d54 Shikhar Bhushan
        "Client's :class:`Capabilities`"
154 d095a59e Shikhar Bhushan
        return self._client_capabilities
155 4f650d54 Shikhar Bhushan
156 d095a59e Shikhar Bhushan
    @property
157 d095a59e Shikhar Bhushan
    def server_capabilities(self):
158 4f650d54 Shikhar Bhushan
        "Server's :class:`Capabilities`"
159 d095a59e Shikhar Bhushan
        return self._server_capabilities
160 4f650d54 Shikhar Bhushan
161 d095a59e Shikhar Bhushan
    @property
162 d095a59e Shikhar Bhushan
    def id(self):
163 216bb34c Shikhar Bhushan
        """A `string` representing the `session-id`. If the session has not
164 4f650d54 Shikhar Bhushan
        been initialized it will be :const:`None`"""
165 d095a59e Shikhar Bhushan
        return self._id
166 4f650d54 Shikhar Bhushan
167 94803aaf Shikhar Bhushan
    @property
168 94803aaf Shikhar Bhushan
    def can_pipeline(self):
169 4f650d54 Shikhar Bhushan
        "Whether this session supports pipelining"
170 94803aaf Shikhar Bhushan
        return True
171 583c11f6 Shikhar Bhushan
172 583c11f6 Shikhar Bhushan
173 583c11f6 Shikhar Bhushan
class SessionListener(object):
174 4f650d54 Shikhar Bhushan
175 4f650d54 Shikhar Bhushan
    """Base class for :class:`Session` listeners, which are notified when a new
176 4f650d54 Shikhar Bhushan
    NETCONF message is received or an error occurs.
177 4f650d54 Shikhar Bhushan

178 0cdb8b3c Shikhar Bhushan
    .. note::
179 4f650d54 Shikhar Bhushan
        Avoid time-intensive tasks in a callback's context.
180 0cdb8b3c Shikhar Bhushan
    """
181 4f650d54 Shikhar Bhushan
182 583c11f6 Shikhar Bhushan
    def callback(self, root, raw):
183 4f650d54 Shikhar Bhushan
        """Called when a new XML document is received. The `root` argument
184 4f650d54 Shikhar Bhushan
        allows the callback to determine whether it wants to further process the
185 4f650d54 Shikhar Bhushan
        document.
186 4f650d54 Shikhar Bhushan

187 4f650d54 Shikhar Bhushan
        :arg root: is a tuple of `(tag, attributes)` where `tag` is the qualified name of the root element and `attributes` is a dictionary of its attributes (also qualified names)
188 216bb34c Shikhar Bhushan
        :type root: `tuple`
189 4f650d54 Shikhar Bhushan

190 4f650d54 Shikhar Bhushan
        :arg raw: XML document
191 216bb34c Shikhar Bhushan
        :type raw: `string`
192 0cdb8b3c Shikhar Bhushan
        """
193 583c11f6 Shikhar Bhushan
        raise NotImplementedError
194 4f650d54 Shikhar Bhushan
195 583c11f6 Shikhar Bhushan
    def errback(self, ex):
196 0cdb8b3c Shikhar Bhushan
        """Called when an error occurs.
197 4f650d54 Shikhar Bhushan

198 4f650d54 Shikhar Bhushan
        :type ex: :exc:`Exception`
199 0cdb8b3c Shikhar Bhushan
        """
200 583c11f6 Shikhar Bhushan
        raise NotImplementedError
201 583c11f6 Shikhar Bhushan
202 583c11f6 Shikhar Bhushan
203 583c11f6 Shikhar Bhushan
class HelloHandler(SessionListener):
204 4f650d54 Shikhar Bhushan
205 583c11f6 Shikhar Bhushan
    def __init__(self, init_cb, error_cb):
206 583c11f6 Shikhar Bhushan
        self._init_cb = init_cb
207 583c11f6 Shikhar Bhushan
        self._error_cb = error_cb
208 4f650d54 Shikhar Bhushan
209 583c11f6 Shikhar Bhushan
    def callback(self, root, raw):
210 583c11f6 Shikhar Bhushan
        if content.unqualify(root[0]) == 'hello':
211 583c11f6 Shikhar Bhushan
            try:
212 583c11f6 Shikhar Bhushan
                id, capabilities = HelloHandler.parse(raw)
213 583c11f6 Shikhar Bhushan
            except Exception as e:
214 583c11f6 Shikhar Bhushan
                self._error_cb(e)
215 583c11f6 Shikhar Bhushan
            else:
216 583c11f6 Shikhar Bhushan
                self._init_cb(id, capabilities)
217 4f650d54 Shikhar Bhushan
218 583c11f6 Shikhar Bhushan
    def errback(self, err):
219 583c11f6 Shikhar Bhushan
        self._error_cb(err)
220 4f650d54 Shikhar Bhushan
221 583c11f6 Shikhar Bhushan
    @staticmethod
222 583c11f6 Shikhar Bhushan
    def build(capabilities):
223 583c11f6 Shikhar Bhushan
        "Given a list of capability URI's returns <hello> message XML string"
224 583c11f6 Shikhar Bhushan
        spec = {
225 583c11f6 Shikhar Bhushan
            'tag': content.qualify('hello'),
226 583c11f6 Shikhar Bhushan
            'subtree': [{
227 583c11f6 Shikhar Bhushan
                'tag': 'capabilities',
228 583c11f6 Shikhar Bhushan
                'subtree': # this is fun :-)
229 583c11f6 Shikhar Bhushan
                    [{'tag': 'capability', 'text': uri} for uri in capabilities]
230 583c11f6 Shikhar Bhushan
                }]
231 583c11f6 Shikhar Bhushan
            }
232 583c11f6 Shikhar Bhushan
        return content.dtree2xml(spec)
233 4f650d54 Shikhar Bhushan
234 583c11f6 Shikhar Bhushan
    @staticmethod
235 583c11f6 Shikhar Bhushan
    def parse(raw):
236 583c11f6 Shikhar Bhushan
        "Returns tuple of (session-id (str), capabilities (Capabilities)"
237 583c11f6 Shikhar Bhushan
        sid, capabilities = 0, []
238 583c11f6 Shikhar Bhushan
        root = content.xml2ele(raw)
239 583c11f6 Shikhar Bhushan
        for child in root.getchildren():
240 583c11f6 Shikhar Bhushan
            tag = content.unqualify(child.tag)
241 583c11f6 Shikhar Bhushan
            if tag == 'session-id':
242 583c11f6 Shikhar Bhushan
                sid = child.text
243 583c11f6 Shikhar Bhushan
            elif tag == 'capabilities':
244 583c11f6 Shikhar Bhushan
                for cap in child.getchildren():
245 583c11f6 Shikhar Bhushan
                    if content.unqualify(cap.tag) == 'capability':
246 583c11f6 Shikhar Bhushan
                        capabilities.append(cap.text)
247 583c11f6 Shikhar Bhushan
        return sid, Capabilities(capabilities)