~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/stub_sftp.py

  • Committer: Jelmer Vernooij
  • Date: 2010-04-10 01:22:00 UTC
  • mto: This revision was merged to the branch mainline in revision 5143.
  • Revision ID: jelmer@samba.org-20100410012200-y089oi8jwvx9khyh
Add test for Repository.get_known_graph_ancestry().

Show diffs side-by-side

added added

removed removed

Lines of Context:
23
23
import paramiko
24
24
import select
25
25
import socket
26
 
import SocketServer
27
26
import sys
28
27
import threading
29
28
import time
39
38
from bzrlib.tests import test_server
40
39
 
41
40
 
42
 
class StubServer(paramiko.ServerInterface):
 
41
class StubServer (paramiko.ServerInterface):
43
42
 
44
 
    def __init__(self, test_case_server):
 
43
    def __init__(self, test_case):
45
44
        paramiko.ServerInterface.__init__(self)
46
 
        self.log = test_case_server.log
 
45
        self._test_case = test_case
47
46
 
48
47
    def check_auth_password(self, username, password):
49
48
        # all are allowed
50
 
        self.log('sftpserver - authorizing: %s' % (username,))
 
49
        self._test_case.log('sftpserver - authorizing: %s' % (username,))
51
50
        return paramiko.AUTH_SUCCESSFUL
52
51
 
53
52
    def check_channel_request(self, kind, chanid):
54
 
        self.log('sftpserver - channel request: %s, %s' % (kind, chanid))
 
53
        self._test_case.log(
 
54
            'sftpserver - channel request: %s, %s' % (kind, chanid))
55
55
        return paramiko.OPEN_SUCCEEDED
56
56
 
57
57
 
58
 
class StubSFTPHandle(paramiko.SFTPHandle):
59
 
 
 
58
class StubSFTPHandle (paramiko.SFTPHandle):
60
59
    def stat(self):
61
60
        try:
62
61
            return paramiko.SFTPAttributes.from_stat(
74
73
            return paramiko.SFTPServer.convert_errno(e.errno)
75
74
 
76
75
 
77
 
class StubSFTPServer(paramiko.SFTPServerInterface):
 
76
class StubSFTPServer (paramiko.SFTPServerInterface):
78
77
 
79
78
    def __init__(self, server, root, home=None):
80
79
        paramiko.SFTPServerInterface.__init__(self, server)
91
90
            self.home = home[len(self.root):]
92
91
        if self.home.startswith('/'):
93
92
            self.home = self.home[1:]
94
 
        server.log('sftpserver - new connection')
 
93
        server._test_case.log('sftpserver - new connection')
95
94
 
96
95
    def _realpath(self, path):
97
96
        # paths returned from self.canonicalize() always start with
136
135
        try:
137
136
            out = [ ]
138
137
            # TODO: win32 incorrectly lists paths with non-ascii if path is not
139
 
            # unicode. However on unix the server should only deal with
 
138
            # unicode. However on Linux the server should only deal with
140
139
            # bytestreams and posix.listdir does the right thing
141
140
            if sys.platform == 'win32':
142
141
                flist = [f.encode('utf8') for f in os.listdir(path)]
242
241
    # removed: chattr, symlink, readlink
243
242
    # (nothing in bzr's sftp transport uses those)
244
243
 
245
 
 
246
244
# ------------- server test implementation --------------
247
245
 
248
246
STUB_SERVER_KEY = """
264
262
"""
265
263
 
266
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):
 
286
        while True:
 
287
            readable, writable_unused, exception_unused = \
 
288
                select.select([self._socket], [], [], 0.1)
 
289
            if self._stop_event.isSet():
 
290
                return
 
291
            if len(readable) == 0:
 
292
                continue
 
293
            try:
 
294
                s, addr_unused = self._socket.accept()
 
295
                # because the loopback socket is inline, and transports are
 
296
                # never explicitly closed, best to launch a new thread.
 
297
                threading.Thread(target=self._callback, args=(s,)).start()
 
298
            except socket.error, x:
 
299
                sys.excepthook(*sys.exc_info())
 
300
                trace.warning('Socket error during accept() '
 
301
                              'within unit test server thread: %r' % x)
 
302
            except Exception, x:
 
303
                # probably a failed test; unit test thread will log the
 
304
                # failure/error
 
305
                sys.excepthook(*sys.exc_info())
 
306
                trace.warning(
 
307
                    'Exception from within unit test server thread: %r' % x)
 
308
 
 
309
 
267
310
class SocketDelay(object):
268
311
    """A socket decorator to make TCP appear slower.
269
312
 
339
382
        return bytes_sent
340
383
 
341
384
 
342
 
class TestingSFTPConnectionHandler(SocketServer.BaseRequestHandler):
343
 
 
344
 
    def setup(self):
345
 
        self.wrap_for_latency()
346
 
        tcs = self.server.test_case_server
347
 
        ptrans = paramiko.Transport(self.request)
348
 
        self.paramiko_transport = ptrans
349
 
        # Set it to a channel under 'bzr' so that we get debug info
350
 
        ptrans.set_log_channel('bzr.paramiko.transport')
351
 
        ptrans.add_server_key(tcs.get_host_key())
352
 
        ptrans.set_subsystem_handler('sftp', paramiko.SFTPServer,
353
 
                                     StubSFTPServer, root=tcs._root,
354
 
                                     home=tcs._server_homedir)
355
 
        server = tcs._server_interface(tcs)
356
 
        # This blocks until the key exchange has been done
357
 
        ptrans.start_server(None, server)
358
 
 
359
 
    def finish(self):
360
 
        # Wait for the conversation to finish, when the paramiko.Transport
361
 
        # thread finishes
362
 
        # TODO: Consider timing out after XX seconds rather than hanging.
363
 
        #       Also we could check paramiko_transport.active and possibly
364
 
        #       paramiko_transport.getException().
365
 
        self.paramiko_transport.join()
366
 
 
367
 
    def wrap_for_latency(self):
368
 
        tcs = self.server.test_case_server
369
 
        if tcs.add_latency:
370
 
            # Give the socket (which the request really is) a latency adding
371
 
            # decorator.
372
 
            self.request = SocketDelay(self.request, tcs.add_latency)
373
 
 
374
 
 
375
 
class TestingSFTPWithoutSSHConnectionHandler(TestingSFTPConnectionHandler):
376
 
 
377
 
    def setup(self):
378
 
        self.wrap_for_latency()
379
 
        # Re-import these as locals, so that they're still accessible during
380
 
        # interpreter shutdown (when all module globals get set to None, leading
381
 
        # to confusing errors like "'NoneType' object has no attribute 'error'".
382
 
        class FakeChannel(object):
383
 
            def get_transport(self):
384
 
                return self
385
 
            def get_log_channel(self):
386
 
                return 'bzr.paramiko'
387
 
            def get_name(self):
388
 
                return '1'
389
 
            def get_hexdump(self):
390
 
                return False
391
 
            def close(self):
392
 
                pass
393
 
 
394
 
        tcs = self.server.test_case_server
395
 
        sftp_server = paramiko.SFTPServer(
396
 
            FakeChannel(), 'sftp', StubServer(tcs), StubSFTPServer,
397
 
            root=tcs._root, home=tcs._server_homedir)
398
 
        self.sftp_server = sftp_server
399
 
        sys_stderr = sys.stderr # Used in error reporting during shutdown
400
 
        try:
401
 
            sftp_server.start_subsystem(
402
 
                'sftp', None, ssh.SocketAsChannelAdapter(self.request))
403
 
        except socket.error, e:
404
 
            if (len(e.args) > 0) and (e.args[0] == errno.EPIPE):
405
 
                # it's okay for the client to disconnect abruptly
406
 
                # (bug in paramiko 1.6: it should absorb this exception)
407
 
                pass
408
 
            else:
409
 
                raise
410
 
        except Exception, e:
411
 
            # This typically seems to happen during interpreter shutdown, so
412
 
            # most of the useful ways to report this error won't work.
413
 
            # Writing the exception type, and then the text of the exception,
414
 
            # seems to be the best we can do.
415
 
            # FIXME: All interpreter shutdown errors should have been related
416
 
            # to daemon threads, cleanup needed -- vila 20100623
417
 
            sys_stderr.write('\nEXCEPTION %r: ' % (e.__class__,))
418
 
            sys_stderr.write('%s\n\n' % (e,))
419
 
 
420
 
    def finish(self):
421
 
        self.sftp_server.finish_subsystem()
422
 
 
423
 
 
424
 
class TestingSFTPServer(test_server.TestingThreadingTCPServer):
425
 
 
426
 
    def __init__(self, server_address, request_handler_class, test_case_server):
427
 
        test_server.TestingThreadingTCPServer.__init__(
428
 
            self, server_address, request_handler_class)
429
 
        self.test_case_server = test_case_server
430
 
 
431
 
 
432
 
class SFTPServer(test_server.TestingTCPServerInAThread):
 
385
class SFTPServer(test_server.TestServer):
433
386
    """Common code for SFTP server facilities."""
434
387
 
435
388
    def __init__(self, server_interface=StubServer):
436
 
        self.host = '127.0.0.1'
437
 
        self.port = 0
438
 
        super(SFTPServer, self).__init__((self.host, self.port),
439
 
                                         TestingSFTPServer,
440
 
                                         TestingSFTPConnectionHandler)
441
389
        self._original_vendor = None
 
390
        self._homedir = None
 
391
        self._server_homedir = None
 
392
        self._listener = None
 
393
        self._root = None
442
394
        self._vendor = ssh.ParamikoVendor()
443
395
        self._server_interface = server_interface
444
 
        self._host_key = None
 
396
        # sftp server logs
445
397
        self.logs = []
446
398
        self.add_latency = 0
447
 
        self._homedir = None
448
 
        self._server_homedir = None
449
 
        self._root = None
450
399
 
451
400
    def _get_sftp_url(self, path):
452
401
        """Calculate an sftp url to this server for path."""
453
 
        return "sftp://foo:bar@%s:%s/%s" % (self.host, self.port, path)
 
402
        return 'sftp://foo:bar@%s:%d/%s' % (self._listener.host,
 
403
                                            self._listener.port, path)
454
404
 
455
405
    def log(self, message):
456
406
        """StubServer uses this to log when a new server is created."""
457
407
        self.logs.append(message)
458
408
 
459
 
    def create_server(self):
460
 
        server = self.server_class((self.host, self.port),
461
 
                                   self.request_handler_class,
462
 
                                   self)
463
 
        return server
464
 
 
465
 
    def get_host_key(self):
466
 
        if self._host_key is None:
467
 
            key_file = osutils.pathjoin(self._homedir, 'test_rsa.key')
468
 
            f = open(key_file, 'w')
469
 
            try:
470
 
                f.write(STUB_SERVER_KEY)
471
 
            finally:
472
 
                f.close()
473
 
            self._host_key = paramiko.RSAKey.from_private_key_file(key_file)
474
 
        return self._host_key
 
409
    def _run_server_entry(self, sock):
 
410
        """Entry point for all implementations of _run_server.
 
411
 
 
412
        If self.add_latency is > 0.000001 then sock is given a latency adding
 
413
        decorator.
 
414
        """
 
415
        if self.add_latency > 0.000001:
 
416
            sock = SocketDelay(sock, self.add_latency)
 
417
        return self._run_server(sock)
 
418
 
 
419
    def _run_server(self, s):
 
420
        ssh_server = paramiko.Transport(s)
 
421
        key_file = osutils.pathjoin(self._homedir, 'test_rsa.key')
 
422
        f = open(key_file, 'w')
 
423
        f.write(STUB_SERVER_KEY)
 
424
        f.close()
 
425
        host_key = paramiko.RSAKey.from_private_key_file(key_file)
 
426
        ssh_server.add_server_key(host_key)
 
427
        server = self._server_interface(self)
 
428
        ssh_server.set_subsystem_handler('sftp', paramiko.SFTPServer,
 
429
                                         StubSFTPServer, root=self._root,
 
430
                                         home=self._server_homedir)
 
431
        event = threading.Event()
 
432
        ssh_server.start_server(event, server)
 
433
        event.wait(5.0)
475
434
 
476
435
    def start_server(self, backing_server=None):
477
436
        # XXX: TODO: make sftpserver back onto backing_server rather than local
483
442
                'the local current working directory.' % (backing_server,))
484
443
        self._original_vendor = ssh._ssh_vendor_manager._cached_ssh_vendor
485
444
        ssh._ssh_vendor_manager._cached_ssh_vendor = self._vendor
 
445
        # FIXME: the following block should certainly just be self._homedir =
 
446
        # osutils.getcwd() but that fails badly on Unix -- vila 20100224
486
447
        if sys.platform == 'win32':
487
448
            # Win32 needs to use the UNICODE api
488
449
            self._homedir = os.getcwdu()
489
 
            # Normalize the path or it will be wrongly escaped
490
 
            self._homedir = osutils.normpath(self._homedir)
491
450
        else:
492
 
            # But unix SFTP servers should just deal in bytestreams
 
451
            # But Linux SFTP servers should just deal in bytestreams
493
452
            self._homedir = os.getcwd()
494
453
        if self._server_homedir is None:
495
454
            self._server_homedir = self._homedir
496
455
        self._root = '/'
497
456
        if sys.platform == 'win32':
498
457
            self._root = ''
499
 
        super(SFTPServer, self).start_server()
 
458
        self._listener = SocketListener(self._run_server_entry)
 
459
        self._listener.setDaemon(True)
 
460
        self._listener.start()
500
461
 
501
462
    def stop_server(self):
502
 
        try:
503
 
            super(SFTPServer, self).stop_server()
504
 
        finally:
505
 
            ssh._ssh_vendor_manager._cached_ssh_vendor = self._original_vendor
 
463
        self._listener.stop()
 
464
        ssh._ssh_vendor_manager._cached_ssh_vendor = self._original_vendor
506
465
 
507
466
    def get_bogus_url(self):
508
467
        """See bzrlib.transport.Server.get_bogus_url."""
509
 
        # this is chosen to try to prevent trouble with proxies, weird dns, etc
 
468
        # this is chosen to try to prevent trouble with proxies, wierd dns, etc
510
469
        # we bind a random socket, so that we get a guaranteed unused port
511
470
        # we just never listen on that port
512
471
        s = socket.socket()
532
491
    def __init__(self):
533
492
        super(SFTPServerWithoutSSH, self).__init__()
534
493
        self._vendor = ssh.LoopbackVendor()
535
 
        self.request_handler_class = TestingSFTPWithoutSSHConnectionHandler
536
 
 
537
 
    def get_host_key():
538
 
        return None
 
494
 
 
495
    def _run_server(self, sock):
 
496
        # Re-import these as locals, so that they're still accessible during
 
497
        # interpreter shutdown (when all module globals get set to None, leading
 
498
        # to confusing errors like "'NoneType' object has no attribute 'error'".
 
499
        class FakeChannel(object):
 
500
            def get_transport(self):
 
501
                return self
 
502
            def get_log_channel(self):
 
503
                return 'paramiko'
 
504
            def get_name(self):
 
505
                return '1'
 
506
            def get_hexdump(self):
 
507
                return False
 
508
            def close(self):
 
509
                pass
 
510
 
 
511
        server = paramiko.SFTPServer(
 
512
            FakeChannel(), 'sftp', StubServer(self), StubSFTPServer,
 
513
            root=self._root, home=self._server_homedir)
 
514
        try:
 
515
            server.start_subsystem(
 
516
                'sftp', None, ssh.SocketAsChannelAdapter(sock))
 
517
        except socket.error, e:
 
518
            if (len(e.args) > 0) and (e.args[0] == errno.EPIPE):
 
519
                # it's okay for the client to disconnect abruptly
 
520
                # (bug in paramiko 1.6: it should absorb this exception)
 
521
                pass
 
522
            else:
 
523
                raise
 
524
        except Exception, e:
 
525
            # This typically seems to happen during interpreter shutdown, so
 
526
            # most of the useful ways to report this error are won't work.
 
527
            # Writing the exception type, and then the text of the exception,
 
528
            # seems to be the best we can do.
 
529
            import sys
 
530
            sys.stderr.write('\nEXCEPTION %r: ' % (e.__class__,))
 
531
            sys.stderr.write('%s\n\n' % (e,))
 
532
        server.finish_subsystem()
539
533
 
540
534
 
541
535
class SFTPAbsoluteServer(SFTPServerWithoutSSH):
564
558
    It does this by serving from a deeply-nested directory that doesn't exist.
565
559
    """
566
560
 
567
 
    def create_server(self):
568
 
        # FIXME: Can't we do that in a cleaner way ? -- vila 20100623
569
 
        server = super(SFTPSiblingAbsoluteServer, self).create_server()
570
 
        server._server_homedir = '/dev/noone/runs/tests/here'
571
 
        return server
 
561
    def start_server(self, backing_server=None):
 
562
        self._server_homedir = '/dev/noone/runs/tests/here'
 
563
        super(SFTPSiblingAbsoluteServer, self).start_server(backing_server)
572
564