Revision 583c11f6

b/ncclient/transport/__init__.py
12 12
# See the License for the specific language governing permissions and
13 13
# limitations under the License.
14 14

  
15
"TODO: docstring"
15
"Transport layer"
16 16

  
17
from session import Session, SessionListener
17 18
from ssh import SSHSession
19
from errors import *
18 20

  
19 21
__all__ = [
22
    'Session',
23
    'SessionListener',
24
    'SSHSession',
20 25
    'TransportError',
21 26
    'AuthenticationError',
22 27
    'SessionCloseError',
23 28
    'SSHError',
24
    'SSHUnknownHostError',
25
    'SSHSession'
29
    'SSHUnknownHostError'
26 30
]
b/ncclient/transport/errors.py
12 12
# See the License for the specific language governing permissions and
13 13
# limitations under the License.
14 14

  
15
"TODO: docstrings"
16

  
17 15
from ncclient import NCClientError
18 16

  
19 17
class TransportError(NCClientError):
b/ncclient/transport/hello.py
14 14

  
15 15
"All to do with NETCONF <hello> messages"
16 16

  
17
from ncclient import content
18 17

  
19
class HelloHandler:
20
    
21
    def __init__(self, init_cb, error_cb):
22
        self._init_cb = init_cb
23
        self._error_cb = error_cb
24
    
25
    def callback(self, root, raw):
26
        if content.unqualify(root[0]) == 'hello':
27
            try:
28
                id, capabilities = HelloHandler.parse(raw)
29
            except Exception as e:
30
                self._error_cb(e)
31
            else:
32
                self._init_cb(id, capabilities)
33
    
34
    def errback(self, err):
35
        self._error_cb(err)
36
    
37
    @staticmethod
38
    def build(capabilities):
39
        "Given a list of capability URI's returns encoded <hello> message"
40
        spec = {
41
            'tag': content.qualify('hello'),
42
            'subtree': [{
43
                'tag': 'capabilities',
44
                'subtree': # this is fun :-)
45
                    [{'tag': 'capability', 'text': uri} for uri in capabilities]
46
                }]
47
            }
48
        return content.dtree2xml(spec)
49
    
50
    @staticmethod
51
    def parse(raw):
52
        "Returns tuple of ('session-id', ['capability_uri', ...])"
53
        sid, capabilities = 0, []
54
        root = content.xml2ele(raw)
55
        for child in root.getchildren():
56
            tag = content.unqualify(child.tag)
57
            if tag == 'session-id':
58
                sid = child.text
59
            elif tag == 'capabilities':
60
                for cap in child.getchildren():
61
                    if content.unqualify(cap.tag) == 'capability':
62
                        capabilities.append(cap.text)
63
        return sid, capabilities
64 18

  
65
'''
66
from ncclient.capabilities import CAPABILITIES
67
from ncclient.transport.hello import HelloHandler
19
from session import SessionListener
68 20

  
69
print HelloHandler.build(CAPABILITIES)
70
'''
b/ncclient/transport/session.py
15 15
from Queue import Queue
16 16
from threading import Thread, Lock, Event
17 17

  
18
from ncclient import content
18 19
from ncclient.capabilities import Capabilities
19
from ncclient.content import parse_root
20

  
21
from hello import HelloHandler
22 20

  
23 21
import logging
24 22
logger = logging.getLogger('ncclient.transport.session')
25 23

  
26

  
27 24
class Session(Thread):
28 25
    
29
    "TODO: docstring"
30
    
31 26
    def __init__(self, capabilities):
32
        "Subclass constructor should call this"
33 27
        Thread.__init__(self)
34
        self.setDaemon(True)
35
        self._listeners = set() # TODO(?) weakref
28
        self.set_daemon(True)
29
        self._listeners = set() # 3.0's weakset ideal
36 30
        self._lock = Lock()
37
        self.setName('session')
31
        self.set_name('session')
38 32
        self._q = Queue()
39 33
        self._client_capabilities = capabilities
40 34
        self._server_capabilities = None # yet
......
46 40
    def _dispatch_message(self, raw):
47 41
        "TODO: docstring"
48 42
        try:
49
            root = parse_root(raw)
43
            root = content.parse_root(raw)
50 44
        except Exception as e:
51 45
            logger.error('error parsing dispatch message: %s' % e)
52 46
            return
......
77 71
        # callbacks
78 72
        def ok_cb(id, capabilities):
79 73
            self._id = id
80
            self._server_capabilities = Capabilities(capabilities)
74
            self._server_capabilities = capabilities
81 75
            init_event.set()
82 76
        def err_cb(err):
83 77
            error[0] = err
......
97 91
                     (self._id, self._server_capabilities))
98 92
    
99 93
    def add_listener(self, listener):
100
        "TODO: docstring"
101 94
        logger.debug('installing listener %r' % listener)
95
        if not isinstance(listener, SessionListener):
96
            raise SessionError("Listener must be a SessionListener type")
102 97
        with self._lock:
103 98
            self._listeners.add(listener)
104 99
    
105 100
    def remove_listener(self, listener):
106
        "TODO: docstring"
107 101
        logger.debug('discarding listener %r' % listener)
108 102
        with self._lock:
109 103
            self._listeners.discard(listener)
110 104
    
111 105
    def get_listener_instance(self, cls):
112
        '''This is useful when we want to maintain one listener of a particular
113
        type per subject i.e. a multiton.
114
        '''
115 106
        with self._lock:
116 107
            for listener in self._listeners:
117 108
                if isinstance(listener, cls):
118 109
                    return listener
119 110
    
120 111
    def connect(self, *args, **kwds):
121
        "Subclass implements"
122 112
        raise NotImplementedError
123 113

  
124 114
    def run(self):
125
        "Subclass implements"
126 115
        raise NotImplementedError
127 116
    
128 117
    def send(self, message):
129
        "TODO: docstring"
130 118
        logger.debug('queueing %s' % message)
131 119
        self._q.put(message)
132 120
    
......
146 134
    
147 135
    @property
148 136
    def id(self):
137
        "`session-id` if session is initialized, :const:`None` otherwise"
149 138
        return self._id
150 139
    
151 140
    @property
152 141
    def can_pipeline(self):
153 142
        return True
143

  
144

  
145
class SessionListener(object):
146
    
147
    def callback(self, root, raw):
148
        raise NotImplementedError
149
    
150
    def errback(self, ex):
151
        raise NotImplementedError
152

  
153

  
154
class HelloHandler(SessionListener):
155
    
156
    def __init__(self, init_cb, error_cb):
157
        self._init_cb = init_cb
158
        self._error_cb = error_cb
159
    
160
    def callback(self, root, raw):
161
        if content.unqualify(root[0]) == 'hello':
162
            try:
163
                id, capabilities = HelloHandler.parse(raw)
164
            except Exception as e:
165
                self._error_cb(e)
166
            else:
167
                self._init_cb(id, capabilities)
168
    
169
    def errback(self, err):
170
        self._error_cb(err)
171
    
172
    @staticmethod
173
    def build(capabilities):
174
        "Given a list of capability URI's returns <hello> message XML string"
175
        spec = {
176
            'tag': content.qualify('hello'),
177
            'subtree': [{
178
                'tag': 'capabilities',
179
                'subtree': # this is fun :-)
180
                    [{'tag': 'capability', 'text': uri} for uri in capabilities]
181
                }]
182
            }
183
        return content.dtree2xml(spec)
184
    
185
    @staticmethod
186
    def parse(raw):
187
        "Returns tuple of (session-id (str), capabilities (Capabilities)"
188
        sid, capabilities = 0, []
189
        root = content.xml2ele(raw)
190
        for child in root.getchildren():
191
            tag = content.unqualify(child.tag)
192
            if tag == 'session-id':
193
                sid = child.text
194
            elif tag == 'capabilities':
195
                for cap in child.getchildren():
196
                    if content.unqualify(cap.tag) == 'capability':
197
                        capabilities.append(cap.text)
198
        return sid, Capabilities(capabilities)
b/ncclient/transport/ssh.py
32 32

  
33 33
class SSHSession(Session):
34 34
    
35
    def __init__(self, *args, **kwds):
36
        Session.__init__(self, *args, **kwds)
35
    "A NETCONF SSH session, per :rfc: 4742"
36
    
37
    def __init__(self, capabilities):
38
        Session.__init__(self, capabilities)
37 39
        self._host_keys = paramiko.HostKeys()
38 40
        self._system_host_keys = paramiko.HostKeys()
39 41
        self._transport = None
......
89 91
        self._parsing_state = expect
90 92
        self._parsing_pos = self._buffer.tell()
91 93
    
92
    def expect_close(self):
93
        self._expecting_close = True
94
    
95 94
    def load_system_host_keys(self, filename=None):
96 95
        if filename is None:
97 96
            filename = os.path.expanduser('~/.ssh/known_hosts')
......
115 114
    
116 115
    def save_host_keys(self, filename):
117 116
        f = open(filename, 'w')
118
        for hostname, keys in self._host_keys.iteritems():
117
        for host, keys in self._host_keys.iteritems():
119 118
            for keytype, key in keys.iteritems():
120
                f.write('%s %s %s\n' % (hostname, keytype, key.get_base64()))
119
                f.write('%s %s %s\n' % (host, keytype, key.get_base64()))
121 120
        f.close()    
122 121
    
123 122
    def close(self):
124
        self.expect_close()
123
        self._expecting_close = True
125 124
        if self._transport.is_active():
126 125
            self._transport.close()
127 126
        self._connected = False
128 127
    
129
    def connect(self, hostname, port=830, timeout=None,
128
    def connect(self, host, port=830, timeout=None,
130 129
                unknown_host_cb=None, username=None, password=None,
131 130
                key_filename=None, allow_agent=True, look_for_keys=True):
132
        
133 131
        assert(username is not None)
134 132
        
135 133
        for (family, socktype, proto, canonname, sockaddr) in \
136
        socket.getaddrinfo(hostname, port):
137
            if socktype==socket.SOCK_STREAM:
134
        socket.getaddrinfo(host, port):
135
            if socktype == socket.SOCK_STREAM:
138 136
                af = family
139 137
                addr = sockaddr
140 138
                break
141 139
        else:
142
            raise SSHError('No suitable address family for %s' % hostname)
140
            raise SSHError('No suitable address family for %s' % host)
143 141
        sock = socket.socket(af, socket.SOCK_STREAM)
144 142
        sock.settimeout(timeout)
145 143
        sock.connect(addr)
......
153 151
        
154 152
        # host key verification
155 153
        server_key = t.get_remote_server_key()
156
        known_host = self._host_keys.check(hostname, server_key) or \
157
                        self._system_host_keys.check(hostname, server_key)
154
        known_host = self._host_keys.check(host, server_key) or \
155
                        self._system_host_keys.check(host, server_key)
158 156
        
159 157
        if unknown_host_cb is None:
160 158
            unknown_host_cb = lambda *args: False
161
        if not known_host and not unknown_host_cb(hostname, server_key):
162
                raise SSHUnknownHostError(hostname, server_key)
159
        if not known_host and not unknown_host_cb(host, server_key):
160
                raise SSHUnknownHostError(host, server_key)
163 161
        
164 162
        if key_filename is None:
165 163
            key_filenames = []
......
283 281
    
284 282
    @property
285 283
    def transport(self):
286
        '''Get underlying paramiko transport object; this is provided so methods
287
        like set_keepalive can be called on it. See paramiko.Transport
288
        documentation for details.
289
        '''
290 284
        return self._transport
291 285
    
292 286
    @property

Also available in: Unified diff