Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (7.6 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 0cdb8b3c Shikhar Bhushan
    "This is a base class for use by protocol implementations"
26 d095a59e Shikhar Bhushan
    
27 2f8bc438 Shikhar Bhushan
    def __init__(self, capabilities):
28 a14c36f9 Shikhar Bhushan
        Thread.__init__(self)
29 583c11f6 Shikhar Bhushan
        self.set_daemon(True)
30 583c11f6 Shikhar Bhushan
        self._listeners = set() # 3.0's weakset ideal
31 a14c36f9 Shikhar Bhushan
        self._lock = Lock()
32 583c11f6 Shikhar Bhushan
        self.set_name('session')
33 d6688264 Shikhar Bhushan
        self._q = Queue()
34 2f8bc438 Shikhar Bhushan
        self._client_capabilities = capabilities
35 d095a59e Shikhar Bhushan
        self._server_capabilities = None # yet
36 d095a59e Shikhar Bhushan
        self._id = None # session-id
37 d095a59e Shikhar Bhushan
        self._connected = False # to be set/cleared by subclass implementation
38 41e2ed46 Shikhar Bhushan
        logger.debug('%r created: client_capabilities=%r' %
39 41e2ed46 Shikhar Bhushan
                     (self, self._client_capabilities))
40 d095a59e Shikhar Bhushan
    
41 a14c36f9 Shikhar Bhushan
    def _dispatch_message(self, raw):
42 a14c36f9 Shikhar Bhushan
        try:
43 583c11f6 Shikhar Bhushan
            root = content.parse_root(raw)
44 a14c36f9 Shikhar Bhushan
        except Exception as e:
45 a14c36f9 Shikhar Bhushan
            logger.error('error parsing dispatch message: %s' % e)
46 a14c36f9 Shikhar Bhushan
            return
47 a14c36f9 Shikhar Bhushan
        with self._lock:
48 a14c36f9 Shikhar Bhushan
            listeners = list(self._listeners)
49 a14c36f9 Shikhar Bhushan
        for l in listeners:
50 a14c36f9 Shikhar Bhushan
            logger.debug('dispatching message to %r' % l)
51 a14c36f9 Shikhar Bhushan
            try:
52 a14c36f9 Shikhar Bhushan
                l.callback(root, raw)
53 a14c36f9 Shikhar Bhushan
            except Exception as e:
54 a14c36f9 Shikhar Bhushan
                logger.warning('[error] %r' % e)
55 a14c36f9 Shikhar Bhushan
    
56 a14c36f9 Shikhar Bhushan
    def _dispatch_error(self, err):
57 a14c36f9 Shikhar Bhushan
        with self._lock:
58 a14c36f9 Shikhar Bhushan
            listeners = list(self._listeners)
59 a14c36f9 Shikhar Bhushan
        for l in listeners:
60 a14c36f9 Shikhar Bhushan
            logger.debug('dispatching error to %r' % l)
61 a14c36f9 Shikhar Bhushan
            try:
62 a14c36f9 Shikhar Bhushan
                l.errback(err)
63 a14c36f9 Shikhar Bhushan
            except Exception as e:
64 a14c36f9 Shikhar Bhushan
                logger.warning('error %r' % e)
65 a14c36f9 Shikhar Bhushan
    
66 65c6a607 Shikhar Bhushan
    def _post_connect(self):
67 65c6a607 Shikhar Bhushan
        "Greeting stuff"
68 65c6a607 Shikhar Bhushan
        init_event = Event()
69 65c6a607 Shikhar Bhushan
        error = [None] # so that err_cb can bind error[0]. just how it is.
70 65c6a607 Shikhar Bhushan
        # callbacks
71 65c6a607 Shikhar Bhushan
        def ok_cb(id, capabilities):
72 65c6a607 Shikhar Bhushan
            self._id = id
73 583c11f6 Shikhar Bhushan
            self._server_capabilities = capabilities
74 65c6a607 Shikhar Bhushan
            init_event.set()
75 65c6a607 Shikhar Bhushan
        def err_cb(err):
76 65c6a607 Shikhar Bhushan
            error[0] = err
77 65c6a607 Shikhar Bhushan
            init_event.set()
78 65c6a607 Shikhar Bhushan
        listener = HelloHandler(ok_cb, err_cb)
79 65c6a607 Shikhar Bhushan
        self.add_listener(listener)
80 65c6a607 Shikhar Bhushan
        self.send(HelloHandler.build(self._client_capabilities))
81 65c6a607 Shikhar Bhushan
        logger.debug('starting main loop')
82 65c6a607 Shikhar Bhushan
        self.start()
83 65c6a607 Shikhar Bhushan
        # we expect server's hello message
84 65c6a607 Shikhar Bhushan
        init_event.wait()
85 65c6a607 Shikhar Bhushan
        # received hello message or an error happened
86 65c6a607 Shikhar Bhushan
        self.remove_listener(listener)
87 65c6a607 Shikhar Bhushan
        if error[0]:
88 65c6a607 Shikhar Bhushan
            raise error[0]
89 0cdb8b3c Shikhar Bhushan
        logger.info('initialized: session-id=%s | server_capabilities=%s' % (self._id, self._server_capabilities))
90 65c6a607 Shikhar Bhushan
    
91 a14c36f9 Shikhar Bhushan
    def add_listener(self, listener):
92 0cdb8b3c Shikhar Bhushan
        """Register a listener that will be notified of incoming messages and errors.
93 0cdb8b3c Shikhar Bhushan
        
94 0cdb8b3c Shikhar Bhushan
        :type listener: :class:`SessionListener`
95 0cdb8b3c Shikhar Bhushan
        """
96 a14c36f9 Shikhar Bhushan
        logger.debug('installing listener %r' % listener)
97 583c11f6 Shikhar Bhushan
        if not isinstance(listener, SessionListener):
98 583c11f6 Shikhar Bhushan
            raise SessionError("Listener must be a SessionListener type")
99 a14c36f9 Shikhar Bhushan
        with self._lock:
100 a14c36f9 Shikhar Bhushan
            self._listeners.add(listener)
101 a14c36f9 Shikhar Bhushan
    
102 a14c36f9 Shikhar Bhushan
    def remove_listener(self, listener):
103 0cdb8b3c Shikhar Bhushan
        "Unregister some listener; ignoring if the listener was never registered."
104 a14c36f9 Shikhar Bhushan
        logger.debug('discarding listener %r' % listener)
105 a14c36f9 Shikhar Bhushan
        with self._lock:
106 a14c36f9 Shikhar Bhushan
            self._listeners.discard(listener)
107 a14c36f9 Shikhar Bhushan
    
108 a14c36f9 Shikhar Bhushan
    def get_listener_instance(self, cls):
109 0cdb8b3c Shikhar Bhushan
        """If a listener of the specified type is registered, returns it. This is useful when it is desirable to have only one instance of a particular type per session, i.e. a multiton.
110 0cdb8b3c Shikhar Bhushan
        
111 0cdb8b3c Shikhar Bhushan
        :type cls: :class:`type`
112 0cdb8b3c Shikhar Bhushan
        :rtype: :class:`SessionListener` or :const:`None`
113 0cdb8b3c Shikhar Bhushan
        """
114 a14c36f9 Shikhar Bhushan
        with self._lock:
115 a14c36f9 Shikhar Bhushan
            for listener in self._listeners:
116 a14c36f9 Shikhar Bhushan
                if isinstance(listener, cls):
117 a14c36f9 Shikhar Bhushan
                    return listener
118 a14c36f9 Shikhar Bhushan
    
119 0cdb8b3c Shikhar Bhushan
    def connect(self, *args, **kwds): # subclass implements
120 d095a59e Shikhar Bhushan
        raise NotImplementedError
121 d095a59e Shikhar Bhushan
122 0cdb8b3c Shikhar Bhushan
    def run(self): # subclass implements
123 d095a59e Shikhar Bhushan
        raise NotImplementedError
124 d095a59e Shikhar Bhushan
    
125 d6688264 Shikhar Bhushan
    def send(self, message):
126 0cdb8b3c Shikhar Bhushan
        """
127 0cdb8b3c Shikhar Bhushan
        :param message: XML document
128 0cdb8b3c Shikhar Bhushan
        :type message: string
129 0cdb8b3c Shikhar Bhushan
        """
130 d6688264 Shikhar Bhushan
        logger.debug('queueing %s' % message)
131 d6688264 Shikhar Bhushan
        self._q.put(message)
132 d6688264 Shikhar Bhushan
    
133 d095a59e Shikhar Bhushan
    ### Properties
134 0cdb8b3c Shikhar Bhushan
135 0cdb8b3c Shikhar Bhushan
    @property
136 0cdb8b3c Shikhar Bhushan
    def connected(self):
137 0cdb8b3c Shikhar Bhushan
        ":rtype: bool"
138 0cdb8b3c Shikhar Bhushan
        return self._connected
139 0cdb8b3c Shikhar Bhushan
140 d095a59e Shikhar Bhushan
    @property
141 d095a59e Shikhar Bhushan
    def client_capabilities(self):
142 0cdb8b3c Shikhar Bhushan
        ":rtype: :class:`Capabilities`"
143 d095a59e Shikhar Bhushan
        return self._client_capabilities
144 d095a59e Shikhar Bhushan
    
145 d095a59e Shikhar Bhushan
    @property
146 d095a59e Shikhar Bhushan
    def server_capabilities(self):
147 0cdb8b3c Shikhar Bhushan
        ":rtype: :class:`Capabilities` or :const:`None`"
148 d095a59e Shikhar Bhushan
        return self._server_capabilities
149 d095a59e Shikhar Bhushan
    
150 d095a59e Shikhar Bhushan
    @property
151 d095a59e Shikhar Bhushan
    def id(self):
152 0cdb8b3c Shikhar Bhushan
        ":rtype: :obj:`string` or :const:`None`"
153 d095a59e Shikhar Bhushan
        return self._id
154 94803aaf Shikhar Bhushan
    
155 94803aaf Shikhar Bhushan
    @property
156 94803aaf Shikhar Bhushan
    def can_pipeline(self):
157 0cdb8b3c Shikhar Bhushan
        ":rtype: :obj:`bool`"
158 94803aaf Shikhar Bhushan
        return True
159 583c11f6 Shikhar Bhushan
160 583c11f6 Shikhar Bhushan
161 583c11f6 Shikhar Bhushan
class SessionListener(object):
162 583c11f6 Shikhar Bhushan
    
163 0cdb8b3c Shikhar Bhushan
    """'Listen' to incoming messages on a NETCONF :class:`Session`
164 0cdb8b3c Shikhar Bhushan
    
165 0cdb8b3c Shikhar Bhushan
    .. note::
166 0cdb8b3c Shikhar Bhushan
        Avoid computationally intensive tasks in the callbacks.
167 0cdb8b3c Shikhar Bhushan
    """
168 0cdb8b3c Shikhar Bhushan
    
169 583c11f6 Shikhar Bhushan
    def callback(self, root, raw):
170 0cdb8b3c Shikhar Bhushan
        """Called when a new XML document is received. The `root` argument allows the callback to determine whether it wants to further process the document.
171 0cdb8b3c Shikhar Bhushan
        
172 0cdb8b3c Shikhar Bhushan
        :param root: tuple of (tag, attrs) where tag is the qualified name of the root element and attrs is a dictionary of its attributes (also qualified names)
173 0cdb8b3c Shikhar Bhushan
        :param raw: XML document
174 0cdb8b3c Shikhar Bhushan
        :type raw: string
175 0cdb8b3c Shikhar Bhushan
        """
176 583c11f6 Shikhar Bhushan
        raise NotImplementedError
177 583c11f6 Shikhar Bhushan
    
178 583c11f6 Shikhar Bhushan
    def errback(self, ex):
179 0cdb8b3c Shikhar Bhushan
        """Called when an error occurs.
180 0cdb8b3c Shikhar Bhushan
        
181 0cdb8b3c Shikhar Bhushan
        :type ex: :class:`Exception`
182 0cdb8b3c Shikhar Bhushan
        """
183 583c11f6 Shikhar Bhushan
        raise NotImplementedError
184 583c11f6 Shikhar Bhushan
185 583c11f6 Shikhar Bhushan
186 583c11f6 Shikhar Bhushan
class HelloHandler(SessionListener):
187 583c11f6 Shikhar Bhushan
    
188 583c11f6 Shikhar Bhushan
    def __init__(self, init_cb, error_cb):
189 583c11f6 Shikhar Bhushan
        self._init_cb = init_cb
190 583c11f6 Shikhar Bhushan
        self._error_cb = error_cb
191 583c11f6 Shikhar Bhushan
    
192 583c11f6 Shikhar Bhushan
    def callback(self, root, raw):
193 583c11f6 Shikhar Bhushan
        if content.unqualify(root[0]) == 'hello':
194 583c11f6 Shikhar Bhushan
            try:
195 583c11f6 Shikhar Bhushan
                id, capabilities = HelloHandler.parse(raw)
196 583c11f6 Shikhar Bhushan
            except Exception as e:
197 583c11f6 Shikhar Bhushan
                self._error_cb(e)
198 583c11f6 Shikhar Bhushan
            else:
199 583c11f6 Shikhar Bhushan
                self._init_cb(id, capabilities)
200 583c11f6 Shikhar Bhushan
    
201 583c11f6 Shikhar Bhushan
    def errback(self, err):
202 583c11f6 Shikhar Bhushan
        self._error_cb(err)
203 583c11f6 Shikhar Bhushan
    
204 583c11f6 Shikhar Bhushan
    @staticmethod
205 583c11f6 Shikhar Bhushan
    def build(capabilities):
206 583c11f6 Shikhar Bhushan
        "Given a list of capability URI's returns <hello> message XML string"
207 583c11f6 Shikhar Bhushan
        spec = {
208 583c11f6 Shikhar Bhushan
            'tag': content.qualify('hello'),
209 583c11f6 Shikhar Bhushan
            'subtree': [{
210 583c11f6 Shikhar Bhushan
                'tag': 'capabilities',
211 583c11f6 Shikhar Bhushan
                'subtree': # this is fun :-)
212 583c11f6 Shikhar Bhushan
                    [{'tag': 'capability', 'text': uri} for uri in capabilities]
213 583c11f6 Shikhar Bhushan
                }]
214 583c11f6 Shikhar Bhushan
            }
215 583c11f6 Shikhar Bhushan
        return content.dtree2xml(spec)
216 583c11f6 Shikhar Bhushan
    
217 583c11f6 Shikhar Bhushan
    @staticmethod
218 583c11f6 Shikhar Bhushan
    def parse(raw):
219 583c11f6 Shikhar Bhushan
        "Returns tuple of (session-id (str), capabilities (Capabilities)"
220 583c11f6 Shikhar Bhushan
        sid, capabilities = 0, []
221 583c11f6 Shikhar Bhushan
        root = content.xml2ele(raw)
222 583c11f6 Shikhar Bhushan
        for child in root.getchildren():
223 583c11f6 Shikhar Bhushan
            tag = content.unqualify(child.tag)
224 583c11f6 Shikhar Bhushan
            if tag == 'session-id':
225 583c11f6 Shikhar Bhushan
                sid = child.text
226 583c11f6 Shikhar Bhushan
            elif tag == 'capabilities':
227 583c11f6 Shikhar Bhushan
                for cap in child.getchildren():
228 583c11f6 Shikhar Bhushan
                    if content.unqualify(cap.tag) == 'capability':
229 583c11f6 Shikhar Bhushan
                        capabilities.append(cap.text)
230 583c11f6 Shikhar Bhushan
        return sid, Capabilities(capabilities)