~bzr/ubuntu/lucid/bzr/beta-ppa

« back to all changes in this revision

Viewing changes to bzrlib/tests/ftp_server/medusa_based.py

  • Committer: Martin Pool
  • Date: 2010-07-02 07:29:40 UTC
  • mfrom: (129.1.7 packaging-karmic)
  • Revision ID: mbp@sourcefrog.net-20100702072940-hpzq5elg8wjve8rh
* PPA rebuild.
* PPA rebuild for Karmic.
* PPA rebuild for Jaunty.
* PPA rebuild for Hardy.
* From postinst, actually remove the example bash completion scripts.
  (LP: #249452)
* New upstream release.
* New upstream release.
* New upstream release.
* Revert change to Build-depends: Dapper does not have python-central.
  Should be python-support..
* Target ppa..
* Target ppa..
* Target ppa..
* Target ppa..
* New upstream release.
* Switch to dpkg-source 3.0 (quilt) format.
* Bump standards version to 3.8.4.
* Remove embedded copy of python-configobj. Closes: #555336
* Remove embedded copy of python-elementtree. Closes: #555343
* Change section from 'Devel' to 'Vcs'..
* Change section from 'Devel' to 'Vcs'..
* Change section from 'Devel' to 'Vcs'..
* Change section from 'Devel' to 'Vcs'..
* Change section from 'Devel' to 'Vcs'..
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* debian/control: Fix obsolete-relation-form-in-source
  lintian warning. 
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* Split out docs into bzr-doc package.
* New upstream release.
* Added John Francesco Ferlito to Uploaders.
* Fix install path to quick-reference guide
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* Fix FTBFS due to path changes, again.
* Fix FTBFS due to doc paths changing
* New upstream release.
* Fix FTBFS due to path changes, again.
* Fix FTBFS due to doc paths changing
* New upstream release.
* Fix FTBFS due to path changes, again.
* Fix FTBFS due to doc paths changing
* New upstream release.
* Fix FTBFS due to path changes, again, again.
* Fix FTBFS due to path changes, again.
* Fix FTBFS due to path changes.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* Bump standards version to 3.8.3.
* Remove unused patch system.
* New upstream release.
* New upstream release.
* New upstream release.
* Fix copy and paste tab error in .install file
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
 + Fixes compatibility with Python 2.4. Closes: #537708
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream version.
* Bump standards version to 3.8.2.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* Add python-pyrex to build-deps to ensure C extensions are always build.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* Split documentation into bzr-doc package. ((LP: #385074)
* Multiple packaging changes to make us more linitan clean.
* New upstream release.
* Split documentation into bzr-doc package. ((LP: #385074)
* Multiple packaging changes to make us more linitan clean.
* New upstream release.
* Split documentation into bzr-doc package. ((LP: #385074)
* Multiple packaging changes to make us more linitan clean.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* Fix API compatibility version. (Closes: #526233)
* New upstream release.
  + Fixes default format for upgrade command. (Closes: #464688)
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* Add missing dependency on zlib development library. (Closes:
  #523595)
* Add zlib build-depends.
* Add zlib build-depends.
* Add zlib build-depends.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* Move to section vcs.
* Bump standards version to 3.8.1.
* New upstream release.
* Remove temporary patch for missing .c files from distribution
* New upstream release.
* Remove temporary patch for missing .c files from distribution
* New upstream release.
* Remove temporary patch for missing .c files from distribution
* Add temporary patch for missing .c files from distribution
* Add temporary patch for missing .c files from distribution
* Add temporary patch for missing .c files from distribution
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* Recommend ca-certificates. (Closes: #452024)
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* Update watch file. bazaar now uses launchpad to host its sources.
* Remove patch for inventory root revision copy, applied upstream.
* New upstream release.
* New upstream release.
* New upstream release
* Force removal of files installed in error to /etc/bash_completion.d/
  (LP: #249452)
* New upstream release.
* New upstream release
* New upstream release.
* Bump standards version.
* Include patch for inventory root revision copy, required for bzr-svn.
* New upstream release.
* Remove unused lintian overrides.
* Correct the package version not to be native.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* New upstream release.
* Final 1.5 release.
* New upstream release.
* New upstream release.
* New upstream release.
* Add myself as a co-maintainer.
* Add a Dm-Upload-Allowed: yes header.
* New upstream bugfix release.
* New upstream release.
* Final 1.3 release.
* New upstream release.
* First release candidate of the upcoming 1.3 release.
* Rebuild to fix the problem caused by a build with a broken python-central.
* New upstream release.
* Rebuild for dapper PPA.
* Apply Lamont's patches to fix build-dependencies on dapper.
  (See: https://bugs.launchpad.net/bzr/+bug/189915)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2007-2010 Canonical Ltd
 
2
#
 
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.
 
7
#
 
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.
 
12
#
 
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
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
"""
 
17
FTP test server.
 
18
 
 
19
Based on medusa: http://www.amk.ca/python/code/medusa.html
 
20
"""
 
21
 
 
22
import asyncore
 
23
import errno
 
24
import os
 
25
import select
 
26
import stat
 
27
import threading
 
28
 
 
29
import medusa
 
30
import medusa.filesys
 
31
import medusa.ftp_server
 
32
 
 
33
from bzrlib import (
 
34
    tests,
 
35
    trace,
 
36
    transport,
 
37
    )
 
38
 
 
39
 
 
40
class test_filesystem(medusa.filesys.os_filesystem):
 
41
    """A custom filesystem wrapper to add missing functionalities."""
 
42
 
 
43
    def chmod(self, path, mode):
 
44
        p = self.normalize(self.path_module.join (self.wd, path))
 
45
        return os.chmod(self.translate(p), mode)
 
46
 
 
47
 
 
48
class test_authorizer(object):
 
49
    """A custom Authorizer object for running the test suite.
 
50
 
 
51
    The reason we cannot use dummy_authorizer, is because it sets the
 
52
    channel to readonly, which we don't always want to do.
 
53
    """
 
54
 
 
55
    def __init__(self, root):
 
56
        self.root = root
 
57
        # If secured_user is set secured_password will be checked
 
58
        self.secured_user = None
 
59
        self.secured_password = None
 
60
 
 
61
    def authorize(self, channel, username, password):
 
62
        """Return (success, reply_string, filesystem)"""
 
63
        channel.persona = -1, -1
 
64
        if username == 'anonymous':
 
65
            channel.read_only = 1
 
66
        else:
 
67
            channel.read_only = 0
 
68
 
 
69
        # Check secured_user if set
 
70
        if (self.secured_user is not None
 
71
            and username == self.secured_user
 
72
            and password != self.secured_password):
 
73
            return 0, 'Password invalid.', None
 
74
        else:
 
75
            return 1, 'OK.', test_filesystem(self.root)
 
76
 
 
77
 
 
78
class ftp_channel(medusa.ftp_server.ftp_channel):
 
79
    """Customized ftp channel"""
 
80
 
 
81
    def log(self, message):
 
82
        """Redirect logging requests."""
 
83
        trace.mutter('ftp_channel: %s', message)
 
84
 
 
85
    def log_info(self, message, type='info'):
 
86
        """Redirect logging requests."""
 
87
        trace.mutter('ftp_channel %s: %s', type, message)
 
88
 
 
89
    def cmd_rnfr(self, line):
 
90
        """Prepare for renaming a file."""
 
91
        self._renaming = line[1]
 
92
        self.respond('350 Ready for RNTO')
 
93
        # TODO: jam 20060516 in testing, the ftp server seems to
 
94
        #       check that the file already exists, or it sends
 
95
        #       550 RNFR command failed
 
96
 
 
97
    def cmd_rnto(self, line):
 
98
        """Rename a file based on the target given.
 
99
 
 
100
        rnto must be called after calling rnfr.
 
101
        """
 
102
        if not self._renaming:
 
103
            self.respond('503 RNFR required first.')
 
104
        pfrom = self.filesystem.translate(self._renaming)
 
105
        self._renaming = None
 
106
        pto = self.filesystem.translate(line[1])
 
107
        if os.path.exists(pto):
 
108
            self.respond('550 RNTO failed: file exists')
 
109
            return
 
110
        try:
 
111
            os.rename(pfrom, pto)
 
112
        except (IOError, OSError), e:
 
113
            # TODO: jam 20060516 return custom responses based on
 
114
            #       why the command failed
 
115
            # (bialix 20070418) str(e) on Python 2.5 @ Windows
 
116
            # sometimes don't provide expected error message;
 
117
            # so we obtain such message via os.strerror()
 
118
            self.respond('550 RNTO failed: %s' % os.strerror(e.errno))
 
119
        except:
 
120
            self.respond('550 RNTO failed')
 
121
            # For a test server, we will go ahead and just die
 
122
            raise
 
123
        else:
 
124
            self.respond('250 Rename successful.')
 
125
 
 
126
    def cmd_size(self, line):
 
127
        """Return the size of a file
 
128
 
 
129
        This is overloaded to help the test suite determine if the
 
130
        target is a directory.
 
131
        """
 
132
        filename = line[1]
 
133
        if not self.filesystem.isfile(filename):
 
134
            if self.filesystem.isdir(filename):
 
135
                self.respond('550 "%s" is a directory' % (filename,))
 
136
            else:
 
137
                self.respond('550 "%s" is not a file' % (filename,))
 
138
        else:
 
139
            self.respond('213 %d'
 
140
                % (self.filesystem.stat(filename)[stat.ST_SIZE]),)
 
141
 
 
142
    def cmd_mkd(self, line):
 
143
        """Create a directory.
 
144
 
 
145
        Overloaded because default implementation does not distinguish
 
146
        *why* it cannot make a directory.
 
147
        """
 
148
        if len (line) != 2:
 
149
            self.command_not_understood(''.join(line))
 
150
        else:
 
151
            path = line[1]
 
152
            try:
 
153
                self.filesystem.mkdir (path)
 
154
                self.respond ('257 MKD command successful.')
 
155
            except (IOError, OSError), e:
 
156
                # (bialix 20070418) str(e) on Python 2.5 @ Windows
 
157
                # sometimes don't provide expected error message;
 
158
                # so we obtain such message via os.strerror()
 
159
                self.respond ('550 error creating directory: %s' %
 
160
                              os.strerror(e.errno))
 
161
            except:
 
162
                self.respond ('550 error creating directory.')
 
163
 
 
164
    def cmd_site(self, line):
 
165
        """Site specific commands."""
 
166
        command, args = line[1].split(' ', 1)
 
167
        if command.lower() == 'chmod':
 
168
            try:
 
169
                mode, path = args.split()
 
170
                mode = int(mode, 8)
 
171
            except ValueError:
 
172
                # We catch both malformed line and malformed mode with the same
 
173
                # ValueError.
 
174
                self.command_not_understood(' '.join(line))
 
175
                return
 
176
            try:
 
177
                # Yes path and mode are reversed
 
178
                self.filesystem.chmod(path, mode)
 
179
                self.respond('200 SITE CHMOD command successful')
 
180
            except AttributeError:
 
181
                # The chmod method is not available in read-only and will raise
 
182
                # AttributeError since a different filesystem is used in that
 
183
                # case
 
184
                self.command_not_authorized(' '.join(line))
 
185
        else:
 
186
            # Another site specific command was requested. We don't know that
 
187
            # one
 
188
            self.command_not_understood(' '.join(line))
 
189
 
 
190
 
 
191
class ftp_server(medusa.ftp_server.ftp_server):
 
192
    """Customize the behavior of the Medusa ftp_server.
 
193
 
 
194
    There are a few warts on the ftp_server, based on how it expects
 
195
    to be used.
 
196
    """
 
197
    _renaming = None
 
198
    ftp_channel_class = ftp_channel
 
199
 
 
200
    def __init__(self, *args, **kwargs):
 
201
        trace.mutter('Initializing ftp_server: %r, %r', args, kwargs)
 
202
        medusa.ftp_server.ftp_server.__init__(self, *args, **kwargs)
 
203
 
 
204
    def log(self, message):
 
205
        """Redirect logging requests."""
 
206
        trace.mutter('ftp_server: %s', message)
 
207
 
 
208
    def log_info(self, message, type='info'):
 
209
        """Override the asyncore.log_info so we don't stipple the screen."""
 
210
        trace.mutter('ftp_server %s: %s', type, message)
 
211
 
 
212
 
 
213
class FTPTestServer(transport.Server):
 
214
    """Common code for FTP server facilities."""
 
215
 
 
216
    no_unicode_support = True
 
217
 
 
218
    def __init__(self):
 
219
        self._root = None
 
220
        self._ftp_server = None
 
221
        self._port = None
 
222
        self._async_thread = None
 
223
        # ftp server logs
 
224
        self.logs = []
 
225
 
 
226
    def get_url(self):
 
227
        """Calculate an ftp url to this server."""
 
228
        return 'ftp://foo:bar@localhost:%d/' % (self._port)
 
229
 
 
230
    def get_bogus_url(self):
 
231
        """Return a URL which cannot be connected to."""
 
232
        return 'ftp://127.0.0.1:1'
 
233
 
 
234
    def log(self, message):
 
235
        """This is used by medusa.ftp_server to log connections, etc."""
 
236
        self.logs.append(message)
 
237
 
 
238
    def start_server(self, vfs_server=None):
 
239
        from bzrlib.transport.local import LocalURLServer
 
240
        if not (vfs_server is None or isinstance(vfs_server, LocalURLServer)):
 
241
            raise AssertionError(
 
242
                "FTPServer currently assumes local transport, got %s" % vfs_server)
 
243
        self._root = os.getcwdu()
 
244
        self._ftp_server = ftp_server(
 
245
            authorizer=test_authorizer(root=self._root),
 
246
            ip='localhost',
 
247
            port=0, # bind to a random port
 
248
            resolver=None,
 
249
            logger_object=self # Use FTPServer.log() for messages
 
250
            )
 
251
        self._port = self._ftp_server.getsockname()[1]
 
252
        # Don't let it loop forever, or handle an infinite number of requests.
 
253
        # In this case it will run for 1000s, or 10000 requests
 
254
        self._async_thread = threading.Thread(
 
255
                target=FTPTestServer._asyncore_loop_ignore_EBADF,
 
256
                kwargs={'timeout':0.1, 'count':10000})
 
257
        self._async_thread.setDaemon(True)
 
258
        self._async_thread.start()
 
259
 
 
260
    def stop_server(self):
 
261
        self._ftp_server.close()
 
262
        asyncore.close_all()
 
263
        self._async_thread.join()
 
264
 
 
265
    @staticmethod
 
266
    def _asyncore_loop_ignore_EBADF(*args, **kwargs):
 
267
        """Ignore EBADF during server shutdown.
 
268
 
 
269
        We close the socket to get the server to shutdown, but this causes
 
270
        select.select() to raise EBADF.
 
271
        """
 
272
        try:
 
273
            asyncore.loop(*args, **kwargs)
 
274
            # FIXME: If we reach that point, we should raise an exception
 
275
            # explaining that the 'count' parameter in setUp is too low or
 
276
            # testers may wonder why their test just sits there waiting for a
 
277
            # server that is already dead. Note that if the tester waits too
 
278
            # long under pdb the server will also die.
 
279
        except select.error, e:
 
280
            if e.args[0] != errno.EBADF:
 
281
                raise
 
282
 
 
283
    def add_user(self, user, password):
 
284
        """Add a user with write access."""
 
285
        authorizer = server = self._ftp_server.authorizer
 
286
        authorizer.secured_user = user
 
287
        authorizer.secured_password = password
 
288