Statistics
| Branch: | Tag: | Revision:

root / ncclient / transport / session.py @ dd225c7a

History | View | Annotate | Download (8 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 57b5f922 Shikhar Bhushan
from ncclient import xml_
19 2f8bc438 Shikhar Bhushan
from ncclient.capabilities import Capabilities
20 d095a59e Shikhar Bhushan
21 66cd54c8 Shikhar Bhushan
from errors import TransportError
22 66cd54c8 Shikhar Bhushan
23 41e2ed46 Shikhar Bhushan
import logging
24 41e2ed46 Shikhar Bhushan
logger = logging.getLogger('ncclient.transport.session')
25 41e2ed46 Shikhar Bhushan
26 a14c36f9 Shikhar Bhushan
class Session(Thread):
27 216bb34c Shikhar Bhushan
28 4f650d54 Shikhar Bhushan
    "Base class for use by transport protocol implementations."
29 4f650d54 Shikhar Bhushan
30 2f8bc438 Shikhar Bhushan
    def __init__(self, capabilities):
31 a14c36f9 Shikhar Bhushan
        Thread.__init__(self)
32 4f650d54 Shikhar Bhushan
        self.setDaemon(True)
33 4f650d54 Shikhar Bhushan
        self._listeners = set() # 3.0's weakset would be ideal
34 a14c36f9 Shikhar Bhushan
        self._lock = Lock()
35 4f650d54 Shikhar Bhushan
        self.setName('session')
36 d6688264 Shikhar Bhushan
        self._q = Queue()
37 2f8bc438 Shikhar Bhushan
        self._client_capabilities = capabilities
38 d095a59e Shikhar Bhushan
        self._server_capabilities = None # yet
39 d095a59e Shikhar Bhushan
        self._id = None # session-id
40 d095a59e Shikhar Bhushan
        self._connected = False # to be set/cleared by subclass implementation
41 41e2ed46 Shikhar Bhushan
        logger.debug('%r created: client_capabilities=%r' %
42 41e2ed46 Shikhar Bhushan
                     (self, self._client_capabilities))
43 4f650d54 Shikhar Bhushan
44 a14c36f9 Shikhar Bhushan
    def _dispatch_message(self, raw):
45 a14c36f9 Shikhar Bhushan
        try:
46 57b5f922 Shikhar Bhushan
            root = xml_.parse_root(raw)
47 a14c36f9 Shikhar Bhushan
        except Exception as e:
48 a14c36f9 Shikhar Bhushan
            logger.error('error parsing dispatch message: %s' % e)
49 a14c36f9 Shikhar Bhushan
            return
50 a14c36f9 Shikhar Bhushan
        with self._lock:
51 a14c36f9 Shikhar Bhushan
            listeners = list(self._listeners)
52 a14c36f9 Shikhar Bhushan
        for l in listeners:
53 a14c36f9 Shikhar Bhushan
            logger.debug('dispatching message to %r' % l)
54 a14c36f9 Shikhar Bhushan
            try:
55 a14c36f9 Shikhar Bhushan
                l.callback(root, raw)
56 a14c36f9 Shikhar Bhushan
            except Exception as e:
57 a14c36f9 Shikhar Bhushan
                logger.warning('[error] %r' % e)
58 4f650d54 Shikhar Bhushan
59 a14c36f9 Shikhar Bhushan
    def _dispatch_error(self, err):
60 a14c36f9 Shikhar Bhushan
        with self._lock:
61 a14c36f9 Shikhar Bhushan
            listeners = list(self._listeners)
62 a14c36f9 Shikhar Bhushan
        for l in listeners:
63 a14c36f9 Shikhar Bhushan
            logger.debug('dispatching error to %r' % l)
64 a14c36f9 Shikhar Bhushan
            try:
65 a14c36f9 Shikhar Bhushan
                l.errback(err)
66 a14c36f9 Shikhar Bhushan
            except Exception as e:
67 0b7d3b31 Shikhar Bhushan
                logger.warning('error dispatching to %r: %r' % (l, e))
68 4f650d54 Shikhar Bhushan
69 65c6a607 Shikhar Bhushan
    def _post_connect(self):
70 65c6a607 Shikhar Bhushan
        "Greeting stuff"
71 65c6a607 Shikhar Bhushan
        init_event = Event()
72 65c6a607 Shikhar Bhushan
        error = [None] # so that err_cb can bind error[0]. just how it is.
73 65c6a607 Shikhar Bhushan
        # callbacks
74 65c6a607 Shikhar Bhushan
        def ok_cb(id, capabilities):
75 65c6a607 Shikhar Bhushan
            self._id = id
76 583c11f6 Shikhar Bhushan
            self._server_capabilities = capabilities
77 65c6a607 Shikhar Bhushan
            init_event.set()
78 65c6a607 Shikhar Bhushan
        def err_cb(err):
79 65c6a607 Shikhar Bhushan
            error[0] = err
80 65c6a607 Shikhar Bhushan
            init_event.set()
81 65c6a607 Shikhar Bhushan
        listener = HelloHandler(ok_cb, err_cb)
82 65c6a607 Shikhar Bhushan
        self.add_listener(listener)
83 65c6a607 Shikhar Bhushan
        self.send(HelloHandler.build(self._client_capabilities))
84 65c6a607 Shikhar Bhushan
        logger.debug('starting main loop')
85 65c6a607 Shikhar Bhushan
        self.start()
86 65c6a607 Shikhar Bhushan
        # we expect server's hello message
87 65c6a607 Shikhar Bhushan
        init_event.wait()
88 65c6a607 Shikhar Bhushan
        # received hello message or an error happened
89 65c6a607 Shikhar Bhushan
        self.remove_listener(listener)
90 65c6a607 Shikhar Bhushan
        if error[0]:
91 65c6a607 Shikhar Bhushan
            raise error[0]
92 216bb34c Shikhar Bhushan
        #if ':base:1.0' not in self.server_capabilities:
93 216bb34c Shikhar Bhushan
        #    raise MissingCapabilityError(':base:1.0')
94 4f650d54 Shikhar Bhushan
        logger.info('initialized: session-id=%s | server_capabilities=%s' %
95 4f650d54 Shikhar Bhushan
                    (self._id, self._server_capabilities))
96 4f650d54 Shikhar Bhushan
97 a14c36f9 Shikhar Bhushan
    def add_listener(self, listener):
98 4f650d54 Shikhar Bhushan
        """Register a listener that will be notified of incoming messages and
99 4f650d54 Shikhar Bhushan
        errors.
100 4f650d54 Shikhar Bhushan

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

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

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

139 4f650d54 Shikhar Bhushan
        :arg message: an XML document
140 4f650d54 Shikhar Bhushan

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

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

191 66cd54c8 Shikhar Bhushan
        :arg root: 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)
192 216bb34c Shikhar Bhushan
        :type root: `tuple`
193 4f650d54 Shikhar Bhushan

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

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