~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/stub_sftp.py

  • Committer: Patch Queue Manager
  • Date: 2015-09-30 16:43:21 UTC
  • mfrom: (6603.2.2 fix-keep-dirty)
  • Revision ID: pqm@pqm.ubuntu.com-20150930164321-ct2v2qnmvimqt8qf
(vila) Avoid associating dirty patch headers with the previous file in the
 patch. (Colin Watson)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2008, 2009, 2010 Robey Pointer <robey@lag.net>, Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2008-2011 Robey Pointer <robey@lag.net>, Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
21
21
 
22
22
import os
23
23
import paramiko
24
 
import select
25
24
import socket
 
25
import SocketServer
26
26
import sys
27
 
import threading
28
27
import time
29
28
 
30
29
from bzrlib import (
38
37
from bzrlib.tests import test_server
39
38
 
40
39
 
41
 
class StubServer (paramiko.ServerInterface):
 
40
class StubServer(paramiko.ServerInterface):
42
41
 
43
 
    def __init__(self, test_case):
 
42
    def __init__(self, test_case_server):
44
43
        paramiko.ServerInterface.__init__(self)
45
 
        self._test_case = test_case
 
44
        self.log = test_case_server.log
46
45
 
47
46
    def check_auth_password(self, username, password):
48
47
        # all are allowed
49
 
        self._test_case.log('sftpserver - authorizing: %s' % (username,))
 
48
        self.log('sftpserver - authorizing: %s' % (username,))
50
49
        return paramiko.AUTH_SUCCESSFUL
51
50
 
52
51
    def check_channel_request(self, kind, chanid):
53
 
        self._test_case.log(
54
 
            'sftpserver - channel request: %s, %s' % (kind, chanid))
 
52
        self.log('sftpserver - channel request: %s, %s' % (kind, chanid))
55
53
        return paramiko.OPEN_SUCCEEDED
56
54
 
57
55
 
58
 
class StubSFTPHandle (paramiko.SFTPHandle):
 
56
class StubSFTPHandle(paramiko.SFTPHandle):
 
57
 
59
58
    def stat(self):
60
59
        try:
61
60
            return paramiko.SFTPAttributes.from_stat(
73
72
            return paramiko.SFTPServer.convert_errno(e.errno)
74
73
 
75
74
 
76
 
class StubSFTPServer (paramiko.SFTPServerInterface):
 
75
class StubSFTPServer(paramiko.SFTPServerInterface):
77
76
 
78
77
    def __init__(self, server, root, home=None):
79
78
        paramiko.SFTPServerInterface.__init__(self, server)
90
89
            self.home = home[len(self.root):]
91
90
        if self.home.startswith('/'):
92
91
            self.home = self.home[1:]
93
 
        server._test_case.log('sftpserver - new connection')
 
92
        server.log('sftpserver - new connection')
94
93
 
95
94
    def _realpath(self, path):
96
95
        # paths returned from self.canonicalize() always start with
119
118
    else:
120
119
        def canonicalize(self, path):
121
120
            if os.path.isabs(path):
122
 
                return os.path.normpath(path)
 
121
                return osutils.normpath(path)
123
122
            else:
124
 
                return os.path.normpath('/' + os.path.join(self.home, path))
 
123
                return osutils.normpath('/' + os.path.join(self.home, path))
125
124
 
126
125
    def chattr(self, path, attr):
127
126
        try:
135
134
        try:
136
135
            out = [ ]
137
136
            # TODO: win32 incorrectly lists paths with non-ascii if path is not
138
 
            # unicode. However on Linux the server should only deal with
 
137
            # unicode. However on unix the server should only deal with
139
138
            # bytestreams and posix.listdir does the right thing
140
139
            if sys.platform == 'win32':
141
140
                flist = [f.encode('utf8') for f in os.listdir(path)]
241
240
    # removed: chattr, symlink, readlink
242
241
    # (nothing in bzr's sftp transport uses those)
243
242
 
 
243
 
244
244
# ------------- server test implementation --------------
245
245
 
246
246
STUB_SERVER_KEY = """
262
262
"""
263
263
 
264
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
 
 
310
265
class SocketDelay(object):
311
266
    """A socket decorator to make TCP appear slower.
312
267
 
382
337
        return bytes_sent
383
338
 
384
339
 
385
 
class SFTPServer(test_server.TestServer):
 
340
class TestingSFTPConnectionHandler(SocketServer.BaseRequestHandler):
 
341
 
 
342
    def setup(self):
 
343
        self.wrap_for_latency()
 
344
        tcs = self.server.test_case_server
 
345
        ptrans = paramiko.Transport(self.request)
 
346
        self.paramiko_transport = ptrans
 
347
        # Set it to a channel under 'bzr' so that we get debug info
 
348
        ptrans.set_log_channel('bzr.paramiko.transport')
 
349
        ptrans.add_server_key(tcs.get_host_key())
 
350
        ptrans.set_subsystem_handler('sftp', paramiko.SFTPServer,
 
351
                                     StubSFTPServer, root=tcs._root,
 
352
                                     home=tcs._server_homedir)
 
353
        server = tcs._server_interface(tcs)
 
354
        # This blocks until the key exchange has been done
 
355
        ptrans.start_server(None, server)
 
356
 
 
357
    def finish(self):
 
358
        # Wait for the conversation to finish, when the paramiko.Transport
 
359
        # thread finishes
 
360
        # TODO: Consider timing out after XX seconds rather than hanging.
 
361
        #       Also we could check paramiko_transport.active and possibly
 
362
        #       paramiko_transport.getException().
 
363
        self.paramiko_transport.join()
 
364
 
 
365
    def wrap_for_latency(self):
 
366
        tcs = self.server.test_case_server
 
367
        if tcs.add_latency:
 
368
            # Give the socket (which the request really is) a latency adding
 
369
            # decorator.
 
370
            self.request = SocketDelay(self.request, tcs.add_latency)
 
371
 
 
372
 
 
373
class TestingSFTPWithoutSSHConnectionHandler(TestingSFTPConnectionHandler):
 
374
 
 
375
    def setup(self):
 
376
        self.wrap_for_latency()
 
377
        # Re-import these as locals, so that they're still accessible during
 
378
        # interpreter shutdown (when all module globals get set to None, leading
 
379
        # to confusing errors like "'NoneType' object has no attribute 'error'".
 
380
        class FakeChannel(object):
 
381
            def get_transport(self):
 
382
                return self
 
383
            def get_log_channel(self):
 
384
                return 'bzr.paramiko'
 
385
            def get_name(self):
 
386
                return '1'
 
387
            def get_hexdump(self):
 
388
                return False
 
389
            def close(self):
 
390
                pass
 
391
 
 
392
        tcs = self.server.test_case_server
 
393
        sftp_server = paramiko.SFTPServer(
 
394
            FakeChannel(), 'sftp', StubServer(tcs), StubSFTPServer,
 
395
            root=tcs._root, home=tcs._server_homedir)
 
396
        self.sftp_server = sftp_server
 
397
        sys_stderr = sys.stderr # Used in error reporting during shutdown
 
398
        try:
 
399
            sftp_server.start_subsystem(
 
400
                'sftp', None, ssh.SocketAsChannelAdapter(self.request))
 
401
        except socket.error, e:
 
402
            if (len(e.args) > 0) and (e.args[0] == errno.EPIPE):
 
403
                # it's okay for the client to disconnect abruptly
 
404
                # (bug in paramiko 1.6: it should absorb this exception)
 
405
                pass
 
406
            else:
 
407
                raise
 
408
        except Exception, e:
 
409
            # This typically seems to happen during interpreter shutdown, so
 
410
            # most of the useful ways to report this error won't work.
 
411
            # Writing the exception type, and then the text of the exception,
 
412
            # seems to be the best we can do.
 
413
            # FIXME: All interpreter shutdown errors should have been related
 
414
            # to daemon threads, cleanup needed -- vila 20100623
 
415
            sys_stderr.write('\nEXCEPTION %r: ' % (e.__class__,))
 
416
            sys_stderr.write('%s\n\n' % (e,))
 
417
 
 
418
    def finish(self):
 
419
        self.sftp_server.finish_subsystem()
 
420
 
 
421
 
 
422
class TestingSFTPServer(test_server.TestingThreadingTCPServer):
 
423
 
 
424
    def __init__(self, server_address, request_handler_class, test_case_server):
 
425
        test_server.TestingThreadingTCPServer.__init__(
 
426
            self, server_address, request_handler_class)
 
427
        self.test_case_server = test_case_server
 
428
 
 
429
 
 
430
class SFTPServer(test_server.TestingTCPServerInAThread):
386
431
    """Common code for SFTP server facilities."""
387
432
 
388
433
    def __init__(self, server_interface=StubServer):
 
434
        self.host = '127.0.0.1'
 
435
        self.port = 0
 
436
        super(SFTPServer, self).__init__((self.host, self.port),
 
437
                                         TestingSFTPServer,
 
438
                                         TestingSFTPConnectionHandler)
389
439
        self._original_vendor = None
390
 
        self._homedir = None
391
 
        self._server_homedir = None
392
 
        self._listener = None
393
 
        self._root = None
394
440
        self._vendor = ssh.ParamikoVendor()
395
441
        self._server_interface = server_interface
396
 
        # sftp server logs
 
442
        self._host_key = None
397
443
        self.logs = []
398
444
        self.add_latency = 0
 
445
        self._homedir = None
 
446
        self._server_homedir = None
 
447
        self._root = None
399
448
 
400
449
    def _get_sftp_url(self, path):
401
450
        """Calculate an sftp url to this server for path."""
402
 
        return 'sftp://foo:bar@%s:%d/%s' % (self._listener.host,
403
 
                                            self._listener.port, path)
 
451
        return "sftp://foo:bar@%s:%s/%s" % (self.host, self.port, path)
404
452
 
405
453
    def log(self, message):
406
454
        """StubServer uses this to log when a new server is created."""
407
455
        self.logs.append(message)
408
456
 
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)
 
457
    def create_server(self):
 
458
        server = self.server_class((self.host, self.port),
 
459
                                   self.request_handler_class,
 
460
                                   self)
 
461
        return server
 
462
 
 
463
    def get_host_key(self):
 
464
        if self._host_key is None:
 
465
            key_file = osutils.pathjoin(self._homedir, 'test_rsa.key')
 
466
            f = open(key_file, 'w')
 
467
            try:
 
468
                f.write(STUB_SERVER_KEY)
 
469
            finally:
 
470
                f.close()
 
471
            self._host_key = paramiko.RSAKey.from_private_key_file(key_file)
 
472
        return self._host_key
434
473
 
435
474
    def start_server(self, backing_server=None):
436
475
        # XXX: TODO: make sftpserver back onto backing_server rather than local
442
481
                'the local current working directory.' % (backing_server,))
443
482
        self._original_vendor = ssh._ssh_vendor_manager._cached_ssh_vendor
444
483
        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
447
484
        if sys.platform == 'win32':
448
485
            # Win32 needs to use the UNICODE api
449
486
            self._homedir = os.getcwdu()
 
487
            # Normalize the path or it will be wrongly escaped
 
488
            self._homedir = osutils.normpath(self._homedir)
450
489
        else:
451
 
            # But Linux SFTP servers should just deal in bytestreams
 
490
            # But unix SFTP servers should just deal in bytestreams
452
491
            self._homedir = os.getcwd()
453
492
        if self._server_homedir is None:
454
493
            self._server_homedir = self._homedir
455
494
        self._root = '/'
456
495
        if sys.platform == 'win32':
457
496
            self._root = ''
458
 
        self._listener = SocketListener(self._run_server_entry)
459
 
        self._listener.setDaemon(True)
460
 
        self._listener.start()
 
497
        super(SFTPServer, self).start_server()
461
498
 
462
499
    def stop_server(self):
463
 
        self._listener.stop()
464
 
        ssh._ssh_vendor_manager._cached_ssh_vendor = self._original_vendor
 
500
        try:
 
501
            super(SFTPServer, self).stop_server()
 
502
        finally:
 
503
            ssh._ssh_vendor_manager._cached_ssh_vendor = self._original_vendor
465
504
 
466
505
    def get_bogus_url(self):
467
506
        """See bzrlib.transport.Server.get_bogus_url."""
468
 
        # this is chosen to try to prevent trouble with proxies, wierd dns, etc
 
507
        # this is chosen to try to prevent trouble with proxies, weird dns, etc
469
508
        # we bind a random socket, so that we get a guaranteed unused port
470
509
        # we just never listen on that port
471
510
        s = socket.socket()
491
530
    def __init__(self):
492
531
        super(SFTPServerWithoutSSH, self).__init__()
493
532
        self._vendor = ssh.LoopbackVendor()
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()
 
533
        self.request_handler_class = TestingSFTPWithoutSSHConnectionHandler
 
534
 
 
535
    def get_host_key():
 
536
        return None
533
537
 
534
538
 
535
539
class SFTPAbsoluteServer(SFTPServerWithoutSSH):
549
553
 
550
554
    def get_url(self):
551
555
        """See bzrlib.transport.Server.get_url."""
552
 
        return self._get_sftp_url("~/")
 
556
        return self._get_sftp_url("%7E/")
553
557
 
554
558
 
555
559
class SFTPSiblingAbsoluteServer(SFTPAbsoluteServer):
558
562
    It does this by serving from a deeply-nested directory that doesn't exist.
559
563
    """
560
564
 
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)
 
565
    def create_server(self):
 
566
        # FIXME: Can't we do that in a cleaner way ? -- vila 20100623
 
567
        server = super(SFTPSiblingAbsoluteServer, self).create_server()
 
568
        server._server_homedir = '/dev/noone/runs/tests/here'
 
569
        return server
564
570