~bzr-pqm/bzr/bzr.dev

4988.10.5 by John Arbash Meinel
Merge bzr.dev 5021 to resolve NEWS
1
# Copyright (C) 2005, 2006, 2008, 2009, 2010 Robey Pointer <robey@lag.net>, Canonical Ltd
1887.1.1 by Adeodato Simó
Do not separate paragraphs in the copyright statement with blank lines,
2
#
1185.16.127 by Martin Pool
[patch] paramiko sftp tests (robey)
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
1887.1.1 by Adeodato Simó
Do not separate paragraphs in the copyright statement with blank lines,
7
#
1185.16.127 by Martin Pool
[patch] paramiko sftp tests (robey)
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
1887.1.1 by Adeodato Simó
Do not separate paragraphs in the copyright statement with blank lines,
12
#
1185.16.127 by Martin Pool
[patch] paramiko sftp tests (robey)
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
4183.7.1 by Sabin Iacob
update FSF mailing address
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
1185.16.127 by Martin Pool
[patch] paramiko sftp tests (robey)
16
17
"""
18
A stub SFTP server for loopback SFTP testing.
19
Adapted from the one in paramiko's unit tests.
20
"""
21
22
import os
4797.11.1 by Vincent Ladeuil
Fix test.stub_sftp imports.
23
import paramiko
4797.11.2 by Vincent Ladeuil
Stop requiring testtools for sftp use.
24
import select
25
import socket
1711.5.1 by John Arbash Meinel
Get most SFTP tests to pass. StubSFTPServer now talks the same path protocol that SFTPTransport talks. on win32
26
import sys
4797.11.2 by Vincent Ladeuil
Stop requiring testtools for sftp use.
27
import threading
28
import time
1666.1.6 by Robert Collins
Make knit the default format.
29
4797.11.1 by Vincent Ladeuil
Fix test.stub_sftp imports.
30
from bzrlib import (
31
    osutils,
32
    trace,
4797.11.2 by Vincent Ladeuil
Stop requiring testtools for sftp use.
33
    urlutils,
34
    )
35
from bzrlib.transport import (
36
    ssh,
37
    )
5017.3.34 by Vincent Ladeuil
-s bt.per_branch passing
38
from bzrlib.tests import test_server
39
4797.11.1 by Vincent Ladeuil
Fix test.stub_sftp imports.
40
41
class StubServer (paramiko.ServerInterface):
1666.1.6 by Robert Collins
Make knit the default format.
42
1185.49.10 by John Arbash Meinel
Use a weakref dictionary to enable re-use of a connection (for sftp).
43
    def __init__(self, test_case):
4797.11.1 by Vincent Ladeuil
Fix test.stub_sftp imports.
44
        paramiko.ServerInterface.__init__(self)
1185.49.10 by John Arbash Meinel
Use a weakref dictionary to enable re-use of a connection (for sftp).
45
        self._test_case = test_case
46
1185.16.127 by Martin Pool
[patch] paramiko sftp tests (robey)
47
    def check_auth_password(self, username, password):
48
        # all are allowed
1185.49.10 by John Arbash Meinel
Use a weakref dictionary to enable re-use of a connection (for sftp).
49
        self._test_case.log('sftpserver - authorizing: %s' % (username,))
4797.11.1 by Vincent Ladeuil
Fix test.stub_sftp imports.
50
        return paramiko.AUTH_SUCCESSFUL
1185.16.127 by Martin Pool
[patch] paramiko sftp tests (robey)
51
52
    def check_channel_request(self, kind, chanid):
4797.11.1 by Vincent Ladeuil
Fix test.stub_sftp imports.
53
        self._test_case.log(
54
            'sftpserver - channel request: %s, %s' % (kind, chanid))
55
        return paramiko.OPEN_SUCCEEDED
56
57
58
class StubSFTPHandle (paramiko.SFTPHandle):
1185.16.127 by Martin Pool
[patch] paramiko sftp tests (robey)
59
    def stat(self):
60
        try:
4797.11.1 by Vincent Ladeuil
Fix test.stub_sftp imports.
61
            return paramiko.SFTPAttributes.from_stat(
62
                os.fstat(self.readfile.fileno()))
1185.16.127 by Martin Pool
[patch] paramiko sftp tests (robey)
63
        except OSError, e:
4797.11.1 by Vincent Ladeuil
Fix test.stub_sftp imports.
64
            return paramiko.SFTPServer.convert_errno(e.errno)
1185.16.127 by Martin Pool
[patch] paramiko sftp tests (robey)
65
66
    def chattr(self, attr):
67
        # python doesn't have equivalents to fchown or fchmod, so we have to
68
        # use the stored filename
4797.11.1 by Vincent Ladeuil
Fix test.stub_sftp imports.
69
        trace.mutter('Changing permissions on %s to %s', self.filename, attr)
1185.16.127 by Martin Pool
[patch] paramiko sftp tests (robey)
70
        try:
4797.11.1 by Vincent Ladeuil
Fix test.stub_sftp imports.
71
            paramiko.SFTPServer.set_file_attr(self.filename, attr)
1185.16.127 by Martin Pool
[patch] paramiko sftp tests (robey)
72
        except OSError, e:
4797.11.1 by Vincent Ladeuil
Fix test.stub_sftp imports.
73
            return paramiko.SFTPServer.convert_errno(e.errno)
74
75
76
class StubSFTPServer (paramiko.SFTPServerInterface):
1666.1.6 by Robert Collins
Make knit the default format.
77
1524.1.1 by Robert Collins
Test sftp with relative, absolute-in-homedir and absolute-not-in-homedir
78
    def __init__(self, server, root, home=None):
4797.11.1 by Vincent Ladeuil
Fix test.stub_sftp imports.
79
        paramiko.SFTPServerInterface.__init__(self, server)
1711.5.1 by John Arbash Meinel
Get most SFTP tests to pass. StubSFTPServer now talks the same path protocol that SFTPTransport talks. on win32
80
        # All paths are actually relative to 'root'.
81
        # this is like implementing chroot().
1185.16.127 by Martin Pool
[patch] paramiko sftp tests (robey)
82
        self.root = root
1524.1.1 by Robert Collins
Test sftp with relative, absolute-in-homedir and absolute-not-in-homedir
83
        if home is None:
1711.5.4 by John Arbash Meinel
Update stub_sftp based on Robey's comments.
84
            self.home = ''
1524.1.1 by Robert Collins
Test sftp with relative, absolute-in-homedir and absolute-not-in-homedir
85
        else:
3376.2.4 by Martin Pool
Remove every assert statement from bzrlib!
86
            if not home.startswith(self.root):
87
                raise AssertionError(
88
                    "home must be a subdirectory of root (%s vs %s)"
89
                    % (home, root))
1524.1.1 by Robert Collins
Test sftp with relative, absolute-in-homedir and absolute-not-in-homedir
90
            self.home = home[len(self.root):]
1711.5.1 by John Arbash Meinel
Get most SFTP tests to pass. StubSFTPServer now talks the same path protocol that SFTPTransport talks. on win32
91
        if self.home.startswith('/'):
1524.1.1 by Robert Collins
Test sftp with relative, absolute-in-homedir and absolute-not-in-homedir
92
            self.home = self.home[1:]
1547.1.1 by Robey Pointer
modify the sftp unit tests to perform sftp over a direct localhost socket instead of over an actual ssh2 transport
93
        server._test_case.log('sftpserver - new connection')
1524.1.1 by Robert Collins
Test sftp with relative, absolute-in-homedir and absolute-not-in-homedir
94
1185.16.127 by Martin Pool
[patch] paramiko sftp tests (robey)
95
    def _realpath(self, path):
1711.5.4 by John Arbash Meinel
Update stub_sftp based on Robey's comments.
96
        # paths returned from self.canonicalize() always start with
97
        # a path separator. So if 'root' is just '/', this would cause
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
98
        # a double slash at the beginning '//home/dir'.
1711.5.4 by John Arbash Meinel
Update stub_sftp based on Robey's comments.
99
        if self.root == '/':
100
            return self.canonicalize(path)
101
        return self.root + self.canonicalize(path)
102
103
    if sys.platform == 'win32':
104
        def canonicalize(self, path):
1711.5.1 by John Arbash Meinel
Get most SFTP tests to pass. StubSFTPServer now talks the same path protocol that SFTPTransport talks. on win32
105
            # Win32 sftp paths end up looking like
1711.5.4 by John Arbash Meinel
Update stub_sftp based on Robey's comments.
106
            #     sftp://host@foo/h:/foo/bar
107
            # which means absolute paths look like:
108
            #     /h:/foo/bar
109
            # and relative paths stay the same:
110
            #     foo/bar
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
111
            # win32 needs to use the Unicode APIs. so we require the
1711.5.4 by John Arbash Meinel
Update stub_sftp based on Robey's comments.
112
            # paths to be utf8 (Linux just uses bytestreams)
1711.5.1 by John Arbash Meinel
Get most SFTP tests to pass. StubSFTPServer now talks the same path protocol that SFTPTransport talks. on win32
113
            thispath = path.decode('utf8')
114
            if path.startswith('/'):
1711.5.4 by John Arbash Meinel
Update stub_sftp based on Robey's comments.
115
                # Abspath H:/foo/bar
116
                return os.path.normpath(thispath[1:])
117
            else:
118
                return os.path.normpath(os.path.join(self.home, thispath))
119
    else:
120
        def canonicalize(self, path):
121
            if os.path.isabs(path):
122
                return os.path.normpath(path)
123
            else:
124
                return os.path.normpath('/' + os.path.join(self.home, path))
1524.1.1 by Robert Collins
Test sftp with relative, absolute-in-homedir and absolute-not-in-homedir
125
1185.58.2 by John Arbash Meinel
Added mode to the appropriate transport functions, and tests to make sure they work.
126
    def chattr(self, path, attr):
127
        try:
4797.11.1 by Vincent Ladeuil
Fix test.stub_sftp imports.
128
            paramiko.SFTPServer.set_file_attr(path, attr)
1185.58.2 by John Arbash Meinel
Added mode to the appropriate transport functions, and tests to make sure they work.
129
        except OSError, e:
4797.11.1 by Vincent Ladeuil
Fix test.stub_sftp imports.
130
            return paramiko.SFTPServer.convert_errno(e.errno)
131
        return paramiko.SFTP_OK
1185.58.2 by John Arbash Meinel
Added mode to the appropriate transport functions, and tests to make sure they work.
132
1185.16.127 by Martin Pool
[patch] paramiko sftp tests (robey)
133
    def list_folder(self, path):
134
        path = self._realpath(path)
135
        try:
136
            out = [ ]
1685.1.72 by Wouter van Heyst
StubSFTPServer should use bytestreams rather than unicode
137
            # TODO: win32 incorrectly lists paths with non-ascii if path is not
5278.1.2 by Martin Pool
Don't say 'Linux' except when specifically talking about the kernel
138
            # unicode. However on unix the server should only deal with
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
139
            # bytestreams and posix.listdir does the right thing
1711.5.1 by John Arbash Meinel
Get most SFTP tests to pass. StubSFTPServer now talks the same path protocol that SFTPTransport talks. on win32
140
            if sys.platform == 'win32':
141
                flist = [f.encode('utf8') for f in os.listdir(path)]
142
            else:
143
                flist = os.listdir(path)
1185.16.127 by Martin Pool
[patch] paramiko sftp tests (robey)
144
            for fname in flist:
4797.11.1 by Vincent Ladeuil
Fix test.stub_sftp imports.
145
                attr = paramiko.SFTPAttributes.from_stat(
146
                    os.stat(osutils.pathjoin(path, fname)))
1185.16.127 by Martin Pool
[patch] paramiko sftp tests (robey)
147
                attr.filename = fname
148
                out.append(attr)
149
            return out
150
        except OSError, e:
4797.11.1 by Vincent Ladeuil
Fix test.stub_sftp imports.
151
            return paramiko.SFTPServer.convert_errno(e.errno)
1185.16.127 by Martin Pool
[patch] paramiko sftp tests (robey)
152
153
    def stat(self, path):
154
        path = self._realpath(path)
155
        try:
4797.11.1 by Vincent Ladeuil
Fix test.stub_sftp imports.
156
            return paramiko.SFTPAttributes.from_stat(os.stat(path))
1185.16.127 by Martin Pool
[patch] paramiko sftp tests (robey)
157
        except OSError, e:
4797.11.1 by Vincent Ladeuil
Fix test.stub_sftp imports.
158
            return paramiko.SFTPServer.convert_errno(e.errno)
1185.16.127 by Martin Pool
[patch] paramiko sftp tests (robey)
159
160
    def lstat(self, path):
161
        path = self._realpath(path)
162
        try:
4797.11.1 by Vincent Ladeuil
Fix test.stub_sftp imports.
163
            return paramiko.SFTPAttributes.from_stat(os.lstat(path))
1185.16.127 by Martin Pool
[patch] paramiko sftp tests (robey)
164
        except OSError, e:
4797.11.1 by Vincent Ladeuil
Fix test.stub_sftp imports.
165
            return paramiko.SFTPServer.convert_errno(e.errno)
1185.16.127 by Martin Pool
[patch] paramiko sftp tests (robey)
166
167
    def open(self, path, flags, attr):
168
        path = self._realpath(path)
169
        try:
1963.2.4 by Robey Pointer
remove usage of hasattr
170
            flags |= getattr(os, 'O_BINARY', 0)
1540.1.9 by John Arbash Meinel
Cleanup getattr() code, since getattr(None, '', None) still works
171
            if getattr(attr, 'st_mode', None):
1185.58.10 by John Arbash Meinel
[patch] Robey Pointer to fix sftp server using umask for files (failing tests for directories)
172
                fd = os.open(path, flags, attr.st_mode)
173
            else:
1988.1.1 by John Arbash Meinel
Restore mode bit tests for sftp, and track down bugs
174
                # os.open() defaults to 0777 which is
175
                # an odd default mode for files
176
                fd = os.open(path, flags, 0666)
1185.16.127 by Martin Pool
[patch] paramiko sftp tests (robey)
177
        except OSError, e:
4797.11.1 by Vincent Ladeuil
Fix test.stub_sftp imports.
178
            return paramiko.SFTPServer.convert_errno(e.errno)
1685.1.72 by Wouter van Heyst
StubSFTPServer should use bytestreams rather than unicode
179
1185.16.127 by Martin Pool
[patch] paramiko sftp tests (robey)
180
        if (flags & os.O_CREAT) and (attr is not None):
1185.58.10 by John Arbash Meinel
[patch] Robey Pointer to fix sftp server using umask for files (failing tests for directories)
181
            attr._flags &= ~attr.FLAG_PERMISSIONS
4797.11.1 by Vincent Ladeuil
Fix test.stub_sftp imports.
182
            paramiko.SFTPServer.set_file_attr(path, attr)
1185.16.127 by Martin Pool
[patch] paramiko sftp tests (robey)
183
        if flags & os.O_WRONLY:
1185.31.51 by John Arbash Meinel
Setting binary flags for sftp.
184
            fstr = 'wb'
1185.16.127 by Martin Pool
[patch] paramiko sftp tests (robey)
185
        elif flags & os.O_RDWR:
1185.31.51 by John Arbash Meinel
Setting binary flags for sftp.
186
            fstr = 'rb+'
1185.16.127 by Martin Pool
[patch] paramiko sftp tests (robey)
187
        else:
188
            # O_RDONLY (== 0)
1185.31.51 by John Arbash Meinel
Setting binary flags for sftp.
189
            fstr = 'rb'
1185.16.127 by Martin Pool
[patch] paramiko sftp tests (robey)
190
        try:
191
            f = os.fdopen(fd, fstr)
1711.3.1 by John Arbash Meinel
opening a dir instead of a file raises an IOError, not OSError
192
        except (IOError, OSError), e:
4797.11.1 by Vincent Ladeuil
Fix test.stub_sftp imports.
193
            return paramiko.SFTPServer.convert_errno(e.errno)
1185.16.127 by Martin Pool
[patch] paramiko sftp tests (robey)
194
        fobj = StubSFTPHandle()
195
        fobj.filename = path
196
        fobj.readfile = f
197
        fobj.writefile = f
198
        return fobj
199
200
    def remove(self, path):
201
        path = self._realpath(path)
202
        try:
203
            os.remove(path)
204
        except OSError, e:
4797.11.1 by Vincent Ladeuil
Fix test.stub_sftp imports.
205
            return paramiko.SFTPServer.convert_errno(e.errno)
206
        return paramiko.SFTP_OK
1185.16.127 by Martin Pool
[patch] paramiko sftp tests (robey)
207
208
    def rename(self, oldpath, newpath):
209
        oldpath = self._realpath(oldpath)
210
        newpath = self._realpath(newpath)
211
        try:
212
            os.rename(oldpath, newpath)
213
        except OSError, e:
4797.11.1 by Vincent Ladeuil
Fix test.stub_sftp imports.
214
            return paramiko.SFTPServer.convert_errno(e.errno)
215
        return paramiko.SFTP_OK
1185.16.127 by Martin Pool
[patch] paramiko sftp tests (robey)
216
217
    def mkdir(self, path, attr):
218
        path = self._realpath(path)
219
        try:
1540.1.5 by John Arbash Meinel
Bugfix to allow using paramiko > 1.5.2
220
            # Using getattr() in case st_mode is None or 0
221
            # both evaluate to False
1540.1.9 by John Arbash Meinel
Cleanup getattr() code, since getattr(None, '', None) still works
222
            if getattr(attr, 'st_mode', None):
1185.58.11 by John Arbash Meinel
Made the StubSFTPServer use umask even for mkdir()
223
                os.mkdir(path, attr.st_mode)
224
            else:
225
                os.mkdir(path)
1185.16.127 by Martin Pool
[patch] paramiko sftp tests (robey)
226
            if attr is not None:
1185.58.11 by John Arbash Meinel
Made the StubSFTPServer use umask even for mkdir()
227
                attr._flags &= ~attr.FLAG_PERMISSIONS
4797.11.1 by Vincent Ladeuil
Fix test.stub_sftp imports.
228
                paramiko.SFTPServer.set_file_attr(path, attr)
1185.16.127 by Martin Pool
[patch] paramiko sftp tests (robey)
229
        except OSError, e:
4797.11.1 by Vincent Ladeuil
Fix test.stub_sftp imports.
230
            return paramiko.SFTPServer.convert_errno(e.errno)
231
        return paramiko.SFTP_OK
1185.16.127 by Martin Pool
[patch] paramiko sftp tests (robey)
232
233
    def rmdir(self, path):
234
        path = self._realpath(path)
235
        try:
236
            os.rmdir(path)
237
        except OSError, e:
4797.11.1 by Vincent Ladeuil
Fix test.stub_sftp imports.
238
            return paramiko.SFTPServer.convert_errno(e.errno)
239
        return paramiko.SFTP_OK
1185.16.127 by Martin Pool
[patch] paramiko sftp tests (robey)
240
241
    # removed: chattr, symlink, readlink
242
    # (nothing in bzr's sftp transport uses those)
4797.11.2 by Vincent Ladeuil
Stop requiring testtools for sftp use.
243
244
# ------------- server test implementation --------------
245
246
STUB_SERVER_KEY = """
247
-----BEGIN RSA PRIVATE KEY-----
248
MIICWgIBAAKBgQDTj1bqB4WmayWNPB+8jVSYpZYk80Ujvj680pOTh2bORBjbIAyz
249
oWGW+GUjzKxTiiPvVmxFgx5wdsFvF03v34lEVVhMpouqPAYQ15N37K/ir5XY+9m/
250
d8ufMCkjeXsQkKqFbAlQcnWMCRnOoPHS3I4vi6hmnDDeeYTSRvfLbW0fhwIBIwKB
251
gBIiOqZYaoqbeD9OS9z2K9KR2atlTxGxOJPXiP4ESqP3NVScWNwyZ3NXHpyrJLa0
252
EbVtzsQhLn6rF+TzXnOlcipFvjsem3iYzCpuChfGQ6SovTcOjHV9z+hnpXvQ/fon
253
soVRZY65wKnF7IAoUwTmJS9opqgrN6kRgCd3DASAMd1bAkEA96SBVWFt/fJBNJ9H
254
tYnBKZGw0VeHOYmVYbvMSstssn8un+pQpUm9vlG/bp7Oxd/m+b9KWEh2xPfv6zqU
255
avNwHwJBANqzGZa/EpzF4J8pGti7oIAPUIDGMtfIcmqNXVMckrmzQ2vTfqtkEZsA
256
4rE1IERRyiJQx6EJsz21wJmGV9WJQ5kCQQDwkS0uXqVdFzgHO6S++tjmjYcxwr3g
257
H0CoFYSgbddOT6miqRskOQF3DZVkJT3kyuBgU2zKygz52ukQZMqxCb1fAkASvuTv
258
qfpH87Qq5kQhNKdbbwbmd2NxlNabazPijWuphGTdW0VfJdWfklyS2Kr+iqrs/5wV
259
HhathJt636Eg7oIjAkA8ht3MQ+XSl9yIJIS8gVpbPxSw5OMfw0PjVE7tBdQruiSc
260
nvuQES5C9BMHjF39LZiGH1iLQy7FgdHyoP+eodI7
261
-----END RSA PRIVATE KEY-----
262
"""
263
264
265
class SocketListener(threading.Thread):
266
267
    def __init__(self, callback):
268
        threading.Thread.__init__(self)
269
        self._callback = callback
270
        self._socket = socket.socket()
271
        self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
272
        self._socket.bind(('localhost', 0))
273
        self._socket.listen(1)
274
        self.host, self.port = self._socket.getsockname()[:2]
275
        self._stop_event = threading.Event()
276
277
    def stop(self):
278
        # called from outside this thread
279
        self._stop_event.set()
280
        # use a timeout here, because if the test fails, the server thread may
281
        # never notice the stop_event.
282
        self.join(5.0)
283
        self._socket.close()
284
285
    def run(self):
5218.1.1 by Andrew Bennetts
Add more logging that will hopefully help diagnose intermittent failures in test_bzr_connect_to_bzr_ssh.
286
        trace.mutter('SocketListener %r has started', self)
4797.11.2 by Vincent Ladeuil
Stop requiring testtools for sftp use.
287
        while True:
288
            readable, writable_unused, exception_unused = \
289
                select.select([self._socket], [], [], 0.1)
290
            if self._stop_event.isSet():
5218.1.1 by Andrew Bennetts
Add more logging that will hopefully help diagnose intermittent failures in test_bzr_connect_to_bzr_ssh.
291
                trace.mutter('SocketListener %r has stopped', self)
4797.11.2 by Vincent Ladeuil
Stop requiring testtools for sftp use.
292
                return
293
            if len(readable) == 0:
294
                continue
295
            try:
296
                s, addr_unused = self._socket.accept()
5218.1.1 by Andrew Bennetts
Add more logging that will hopefully help diagnose intermittent failures in test_bzr_connect_to_bzr_ssh.
297
                trace.mutter('SocketListener %r has accepted connection %r',
298
                    self, s)
4797.11.2 by Vincent Ladeuil
Stop requiring testtools for sftp use.
299
                # because the loopback socket is inline, and transports are
300
                # never explicitly closed, best to launch a new thread.
301
                threading.Thread(target=self._callback, args=(s,)).start()
302
            except socket.error, x:
303
                sys.excepthook(*sys.exc_info())
5055.1.1 by Vincent Ladeuil
Fix bug #526221 and #526353.
304
                trace.warning('Socket error during accept() '
305
                              'within unit test server thread: %r' % x)
4797.11.2 by Vincent Ladeuil
Stop requiring testtools for sftp use.
306
            except Exception, x:
307
                # probably a failed test; unit test thread will log the
308
                # failure/error
309
                sys.excepthook(*sys.exc_info())
5055.1.1 by Vincent Ladeuil
Fix bug #526221 and #526353.
310
                trace.warning(
311
                    'Exception from within unit test server thread: %r' % x)
4797.11.2 by Vincent Ladeuil
Stop requiring testtools for sftp use.
312
313
314
class SocketDelay(object):
315
    """A socket decorator to make TCP appear slower.
316
317
    This changes recv, send, and sendall to add a fixed latency to each python
318
    call if a new roundtrip is detected. That is, when a recv is called and the
319
    flag new_roundtrip is set, latency is charged. Every send and send_all
320
    sets this flag.
321
322
    In addition every send, sendall and recv sleeps a bit per character send to
323
    simulate bandwidth.
324
325
    Not all methods are implemented, this is deliberate as this class is not a
326
    replacement for the builtin sockets layer. fileno is not implemented to
327
    prevent the proxy being bypassed.
328
    """
329
330
    simulated_time = 0
331
    _proxied_arguments = dict.fromkeys([
332
        "close", "getpeername", "getsockname", "getsockopt", "gettimeout",
333
        "setblocking", "setsockopt", "settimeout", "shutdown"])
334
335
    def __init__(self, sock, latency, bandwidth=1.0,
336
                 really_sleep=True):
337
        """
338
        :param bandwith: simulated bandwith (MegaBit)
339
        :param really_sleep: If set to false, the SocketDelay will just
340
        increase a counter, instead of calling time.sleep. This is useful for
341
        unittesting the SocketDelay.
342
        """
343
        self.sock = sock
344
        self.latency = latency
345
        self.really_sleep = really_sleep
346
        self.time_per_byte = 1 / (bandwidth / 8.0 * 1024 * 1024)
347
        self.new_roundtrip = False
348
349
    def sleep(self, s):
350
        if self.really_sleep:
351
            time.sleep(s)
352
        else:
353
            SocketDelay.simulated_time += s
354
355
    def __getattr__(self, attr):
356
        if attr in SocketDelay._proxied_arguments:
357
            return getattr(self.sock, attr)
358
        raise AttributeError("'SocketDelay' object has no attribute %r" %
359
                             attr)
360
361
    def dup(self):
362
        return SocketDelay(self.sock.dup(), self.latency, self.time_per_byte,
363
                           self._sleep)
364
365
    def recv(self, *args):
366
        data = self.sock.recv(*args)
367
        if data and self.new_roundtrip:
368
            self.new_roundtrip = False
369
            self.sleep(self.latency)
370
        self.sleep(len(data) * self.time_per_byte)
371
        return data
372
373
    def sendall(self, data, flags=0):
374
        if not self.new_roundtrip:
375
            self.new_roundtrip = True
376
            self.sleep(self.latency)
377
        self.sleep(len(data) * self.time_per_byte)
378
        return self.sock.sendall(data, flags)
379
380
    def send(self, data, flags=0):
381
        if not self.new_roundtrip:
382
            self.new_roundtrip = True
383
            self.sleep(self.latency)
384
        bytes_sent = self.sock.send(data, flags)
385
        self.sleep(bytes_sent * self.time_per_byte)
386
        return bytes_sent
387
388
5017.3.34 by Vincent Ladeuil
-s bt.per_branch passing
389
class SFTPServer(test_server.TestServer):
4797.11.2 by Vincent Ladeuil
Stop requiring testtools for sftp use.
390
    """Common code for SFTP server facilities."""
391
392
    def __init__(self, server_interface=StubServer):
393
        self._original_vendor = None
394
        self._homedir = None
395
        self._server_homedir = None
396
        self._listener = None
397
        self._root = None
398
        self._vendor = ssh.ParamikoVendor()
399
        self._server_interface = server_interface
400
        # sftp server logs
401
        self.logs = []
402
        self.add_latency = 0
403
404
    def _get_sftp_url(self, path):
405
        """Calculate an sftp url to this server for path."""
406
        return 'sftp://foo:bar@%s:%d/%s' % (self._listener.host,
407
                                            self._listener.port, path)
408
409
    def log(self, message):
410
        """StubServer uses this to log when a new server is created."""
411
        self.logs.append(message)
412
413
    def _run_server_entry(self, sock):
414
        """Entry point for all implementations of _run_server.
415
416
        If self.add_latency is > 0.000001 then sock is given a latency adding
417
        decorator.
418
        """
419
        if self.add_latency > 0.000001:
420
            sock = SocketDelay(sock, self.add_latency)
421
        return self._run_server(sock)
422
423
    def _run_server(self, s):
424
        ssh_server = paramiko.Transport(s)
425
        key_file = osutils.pathjoin(self._homedir, 'test_rsa.key')
426
        f = open(key_file, 'w')
427
        f.write(STUB_SERVER_KEY)
428
        f.close()
429
        host_key = paramiko.RSAKey.from_private_key_file(key_file)
430
        ssh_server.add_server_key(host_key)
431
        server = self._server_interface(self)
432
        ssh_server.set_subsystem_handler('sftp', paramiko.SFTPServer,
433
                                         StubSFTPServer, root=self._root,
434
                                         home=self._server_homedir)
435
        event = threading.Event()
436
        ssh_server.start_server(event, server)
437
        event.wait(5.0)
438
439
    def start_server(self, backing_server=None):
440
        # XXX: TODO: make sftpserver back onto backing_server rather than local
441
        # disk.
442
        if not (backing_server is None or
5017.3.34 by Vincent Ladeuil
-s bt.per_branch passing
443
                isinstance(backing_server, test_server.LocalURLServer)):
4797.11.2 by Vincent Ladeuil
Stop requiring testtools for sftp use.
444
            raise AssertionError(
5055.1.2 by Vincent Ladeuil
Add a FIXME about using osutils.getcwd()
445
                'backing_server should not be %r, because this can only serve '
446
                'the local current working directory.' % (backing_server,))
4797.11.2 by Vincent Ladeuil
Stop requiring testtools for sftp use.
447
        self._original_vendor = ssh._ssh_vendor_manager._cached_ssh_vendor
448
        ssh._ssh_vendor_manager._cached_ssh_vendor = self._vendor
449
        if sys.platform == 'win32':
450
            # Win32 needs to use the UNICODE api
5055.1.1 by Vincent Ladeuil
Fix bug #526221 and #526353.
451
            self._homedir = os.getcwdu()
5229.1.9 by Vincent Ladeuil
Fix sftp homedir path handling on windows.
452
            # Normalize the path or it will be wrongly escaped
5229.1.7 by Vincent Ladeuil
Fix sftp paths for windows.
453
            self._homedir = osutils.normpath(self._homedir)
4797.11.2 by Vincent Ladeuil
Stop requiring testtools for sftp use.
454
        else:
5278.1.2 by Martin Pool
Don't say 'Linux' except when specifically talking about the kernel
455
            # But unix SFTP servers should just deal in bytestreams
4797.11.2 by Vincent Ladeuil
Stop requiring testtools for sftp use.
456
            self._homedir = os.getcwd()
457
        if self._server_homedir is None:
458
            self._server_homedir = self._homedir
459
        self._root = '/'
460
        if sys.platform == 'win32':
461
            self._root = ''
462
        self._listener = SocketListener(self._run_server_entry)
463
        self._listener.setDaemon(True)
464
        self._listener.start()
465
466
    def stop_server(self):
467
        self._listener.stop()
468
        ssh._ssh_vendor_manager._cached_ssh_vendor = self._original_vendor
469
470
    def get_bogus_url(self):
471
        """See bzrlib.transport.Server.get_bogus_url."""
472
        # this is chosen to try to prevent trouble with proxies, wierd dns, etc
473
        # we bind a random socket, so that we get a guaranteed unused port
474
        # we just never listen on that port
475
        s = socket.socket()
476
        s.bind(('localhost', 0))
477
        return 'sftp://%s:%s/' % s.getsockname()
478
479
480
class SFTPFullAbsoluteServer(SFTPServer):
481
    """A test server for sftp transports, using absolute urls and ssh."""
482
483
    def get_url(self):
484
        """See bzrlib.transport.Server.get_url."""
485
        homedir = self._homedir
486
        if sys.platform != 'win32':
487
            # Remove the initial '/' on all platforms but win32
488
            homedir = homedir[1:]
489
        return self._get_sftp_url(urlutils.escape(homedir))
490
491
492
class SFTPServerWithoutSSH(SFTPServer):
493
    """An SFTP server that uses a simple TCP socket pair rather than SSH."""
494
495
    def __init__(self):
496
        super(SFTPServerWithoutSSH, self).__init__()
497
        self._vendor = ssh.LoopbackVendor()
498
499
    def _run_server(self, sock):
500
        # Re-import these as locals, so that they're still accessible during
501
        # interpreter shutdown (when all module globals get set to None, leading
502
        # to confusing errors like "'NoneType' object has no attribute 'error'".
503
        class FakeChannel(object):
504
            def get_transport(self):
505
                return self
506
            def get_log_channel(self):
507
                return 'paramiko'
508
            def get_name(self):
509
                return '1'
510
            def get_hexdump(self):
511
                return False
512
            def close(self):
513
                pass
514
515
        server = paramiko.SFTPServer(
516
            FakeChannel(), 'sftp', StubServer(self), StubSFTPServer,
517
            root=self._root, home=self._server_homedir)
518
        try:
519
            server.start_subsystem(
520
                'sftp', None, ssh.SocketAsChannelAdapter(sock))
521
        except socket.error, e:
522
            if (len(e.args) > 0) and (e.args[0] == errno.EPIPE):
523
                # it's okay for the client to disconnect abruptly
524
                # (bug in paramiko 1.6: it should absorb this exception)
525
                pass
526
            else:
527
                raise
528
        except Exception, e:
529
            # This typically seems to happen during interpreter shutdown, so
530
            # most of the useful ways to report this error are won't work.
531
            # Writing the exception type, and then the text of the exception,
532
            # seems to be the best we can do.
533
            import sys
534
            sys.stderr.write('\nEXCEPTION %r: ' % (e.__class__,))
535
            sys.stderr.write('%s\n\n' % (e,))
536
        server.finish_subsystem()
537
538
539
class SFTPAbsoluteServer(SFTPServerWithoutSSH):
540
    """A test server for sftp transports, using absolute urls."""
541
542
    def get_url(self):
543
        """See bzrlib.transport.Server.get_url."""
544
        homedir = self._homedir
545
        if sys.platform != 'win32':
546
            # Remove the initial '/' on all platforms but win32
547
            homedir = homedir[1:]
548
        return self._get_sftp_url(urlutils.escape(homedir))
549
550
551
class SFTPHomeDirServer(SFTPServerWithoutSSH):
552
    """A test server for sftp transports, using homedir relative urls."""
553
554
    def get_url(self):
555
        """See bzrlib.transport.Server.get_url."""
556
        return self._get_sftp_url("~/")
557
558
559
class SFTPSiblingAbsoluteServer(SFTPAbsoluteServer):
560
    """A test server for sftp transports where only absolute paths will work.
561
562
    It does this by serving from a deeply-nested directory that doesn't exist.
563
    """
564
565
    def start_server(self, backing_server=None):
566
        self._server_homedir = '/dev/noone/runs/tests/here'
567
        super(SFTPSiblingAbsoluteServer, self).start_server(backing_server)
568