Merge lp:~jelmer/brz/medusa into lp:brz

Proposed by Jelmer Vernooij
Status: Merged
Approved by: Jelmer Vernooij
Approved revision: no longer in the source branch.
Merge reported by: The Breezy Bot
Merged at revision: not available
Proposed branch: lp:~jelmer/brz/medusa
Merge into: lp:brz
Diff against target: 550 lines (+56/-349)
5 files modified
breezy/tests/ftp_server/__init__.py (+6/-24)
breezy/tests/ftp_server/medusa_based.py (+0/-295)
breezy/tests/ftp_server/pyftpdlib_based.py (+44/-29)
doc/en/release-notes/brz-3.0.txt (+6/-0)
setup.py (+0/-1)
To merge this branch: bzr merge lp:~jelmer/brz/medusa
Reviewer Review Type Date Requested Status
Martin Packman Approve
Review via email: mp+325457@code.launchpad.net

Commit message

Drop support for medusa, support newer versions of pyftpdlib.

Description of the change

Drop support for medusa.

Support newer versions of pyftpdlib.

To post a comment you must log in.
Revision history for this message
Martin Packman (gz) wrote :

Looks good to me.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'breezy/tests/ftp_server/__init__.py'
--- breezy/tests/ftp_server/__init__.py 2017-05-21 18:10:28 +0000
+++ breezy/tests/ftp_server/__init__.py 2017-06-11 13:38:25 +0000
@@ -27,27 +27,11 @@
2727
2828
29try:29try:
30 from breezy.tests.ftp_server import medusa_based30 import pyftpdlib
31 # medusa is bogus starting with python2.6, since we don't support earlier
32 # pythons anymore, it's currently useless. There is hope though that the
33 # unicode bugs get fixed in the future so we leave it disabled until
34 # then. Keeping the framework in place means that only the following line
35 # will need to be changed. The last tests were conducted with medusa-2.0
36 # -- vila 20110607
37 medusa_available = False
38except ImportError:
39 medusa_available = False
40
41
42try:
43 from breezy.tests.ftp_server import pyftpdlib_based
44 if pyftpdlib_based.pyftplib_version >= (0, 7, 0):
45 pyftpdlib_available = True
46 else:
47 # 0.6.0 breaks SITE CHMOD
48 pyftpdlib_available = False
49except ImportError:31except ImportError:
50 pyftpdlib_available = False32 pyftpdlib_available = False
33else:
34 pyftpdlib_available = True
5135
5236
53class _FTPServerFeature(features.Feature):37class _FTPServerFeature(features.Feature):
@@ -56,12 +40,11 @@
56 Right now, the only way this is available is if one of the following is40 Right now, the only way this is available is if one of the following is
57 installed:41 installed:
5842
59 - 'medusa': http://www.amk.ca/python/code/medusa.html
60 - 'pyftpdlib': http://code.google.com/p/pyftpdlib/43 - 'pyftpdlib': http://code.google.com/p/pyftpdlib/
61 """44 """
6245
63 def _probe(self):46 def _probe(self):
64 return medusa_available or pyftpdlib_available47 return pyftpdlib_available
6548
66 def feature_name(self):49 def feature_name(self):
67 return 'FTPServer'50 return 'FTPServer'
@@ -92,9 +75,8 @@
92 raise tests.UnavailableFeature(FTPServerFeature)75 raise tests.UnavailableFeature(FTPServerFeature)
9376
9477
95if medusa_available:78if pyftpdlib_available:
96 FTPTestServer = medusa_based.FTPTestServer79 from . import pyftpdlib_based
97elif pyftpdlib_available:
98 FTPTestServer = pyftpdlib_based.FTPTestServer80 FTPTestServer = pyftpdlib_based.FTPTestServer
99else:81else:
100 FTPTestServer = UnavailableFTPTestServer82 FTPTestServer = UnavailableFTPTestServer
10183
=== removed file 'breezy/tests/ftp_server/medusa_based.py'
--- breezy/tests/ftp_server/medusa_based.py 2017-05-22 00:56:52 +0000
+++ breezy/tests/ftp_server/medusa_based.py 1970-01-01 00:00:00 +0000
@@ -1,295 +0,0 @@
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"""
17FTP test server.
18
19Based on medusa: http://www.amk.ca/python/code/medusa.html
20"""
21
22import asyncore
23import errno
24import os
25import select
26import stat
27import threading
28
29import medusa
30import medusa.filesys
31import medusa.ftp_server
32
33from ... import (
34 osutils,
35 tests,
36 trace,
37 )
38from .. import test_server
39
40
41class test_filesystem(medusa.filesys.os_filesystem):
42 """A custom filesystem wrapper to add missing functionalities."""
43
44 def chmod(self, path, mode):
45 p = self.normalize(self.path_module.join (self.wd, path))
46 return os.chmod(self.translate(p), mode)
47
48
49class test_authorizer(object):
50 """A custom Authorizer object for running the test suite.
51
52 The reason we cannot use dummy_authorizer, is because it sets the
53 channel to readonly, which we don't always want to do.
54 """
55
56 def __init__(self, root):
57 self.root = root
58 # If secured_user is set secured_password will be checked
59 self.secured_user = None
60 self.secured_password = None
61
62 def authorize(self, channel, username, password):
63 """Return (success, reply_string, filesystem)"""
64 channel.persona = -1, -1
65 if username == 'anonymous':
66 channel.read_only = 1
67 else:
68 channel.read_only = 0
69
70 # Check secured_user if set
71 if (self.secured_user is not None
72 and username == self.secured_user
73 and password != self.secured_password):
74 return 0, 'Password invalid.', None
75 else:
76 return 1, 'OK.', test_filesystem(self.root)
77
78
79class ftp_channel(medusa.ftp_server.ftp_channel):
80 """Customized ftp channel"""
81
82 def log(self, message):
83 """Redirect logging requests."""
84 trace.mutter('ftp_channel: %s', message)
85
86 def log_info(self, message, type='info'):
87 """Redirect logging requests."""
88 trace.mutter('ftp_channel %s: %s', type, message)
89
90 def cmd_rnfr(self, line):
91 """Prepare for renaming a file."""
92 self._renaming = line[1]
93 self.respond('350 Ready for RNTO')
94 # TODO: jam 20060516 in testing, the ftp server seems to
95 # check that the file already exists, or it sends
96 # 550 RNFR command failed
97
98 def cmd_rnto(self, line):
99 """Rename a file based on the target given.
100
101 rnto must be called after calling rnfr.
102 """
103 if not self._renaming:
104 self.respond('503 RNFR required first.')
105 pfrom = self.filesystem.translate(self._renaming)
106 self._renaming = None
107 pto = self.filesystem.translate(line[1])
108 if os.path.exists(pto):
109 self.respond('550 RNTO failed: file exists')
110 return
111 try:
112 os.rename(pfrom, pto)
113 except (IOError, OSError) as e:
114 # TODO: jam 20060516 return custom responses based on
115 # why the command failed
116 # (bialix 20070418) str(e) on Python 2.5 @ Windows
117 # sometimes don't provide expected error message;
118 # so we obtain such message via os.strerror()
119 self.respond('550 RNTO failed: %s' % os.strerror(e.errno))
120 except:
121 self.respond('550 RNTO failed')
122 # For a test server, we will go ahead and just die
123 raise
124 else:
125 self.respond('250 Rename successful.')
126
127 def cmd_size(self, line):
128 """Return the size of a file
129
130 This is overloaded to help the test suite determine if the
131 target is a directory.
132 """
133 filename = line[1]
134 if not self.filesystem.isfile(filename):
135 if self.filesystem.isdir(filename):
136 self.respond('550 "%s" is a directory' % (filename,))
137 else:
138 self.respond('550 "%s" is not a file' % (filename,))
139 else:
140 self.respond('213 %d'
141 % (self.filesystem.stat(filename)[stat.ST_SIZE]),)
142
143 def cmd_mkd(self, line):
144 """Create a directory.
145
146 Overloaded because default implementation does not distinguish
147 *why* it cannot make a directory.
148 """
149 if len (line) != 2:
150 self.command_not_understood(''.join(line))
151 else:
152 path = line[1]
153 try:
154 self.filesystem.mkdir (path)
155 self.respond ('257 MKD command successful.')
156 except (IOError, OSError) as e:
157 # (bialix 20070418) str(e) on Python 2.5 @ Windows
158 # sometimes don't provide expected error message;
159 # so we obtain such message via os.strerror()
160 self.respond ('550 error creating directory: %s' %
161 os.strerror(e.errno))
162 except:
163 self.respond ('550 error creating directory.')
164
165 def cmd_site(self, line):
166 """Site specific commands."""
167 command, args = line[1].split(' ', 1)
168 if command.lower() == 'chmod':
169 try:
170 mode, path = args.split()
171 mode = int(mode, 8)
172 except ValueError:
173 # We catch both malformed line and malformed mode with the same
174 # ValueError.
175 self.command_not_understood(' '.join(line))
176 return
177 try:
178 # Yes path and mode are reversed
179 self.filesystem.chmod(path, mode)
180 self.respond('200 SITE CHMOD command successful')
181 except AttributeError:
182 # The chmod method is not available in read-only and will raise
183 # AttributeError since a different filesystem is used in that
184 # case
185 self.command_not_authorized(' '.join(line))
186 else:
187 # Another site specific command was requested. We don't know that
188 # one
189 self.command_not_understood(' '.join(line))
190
191
192class ftp_server(medusa.ftp_server.ftp_server):
193 """Customize the behavior of the Medusa ftp_server.
194
195 There are a few warts on the ftp_server, based on how it expects
196 to be used.
197 """
198 _renaming = None
199 ftp_channel_class = ftp_channel
200
201 def __init__(self, *args, **kwargs):
202 trace.mutter('Initializing ftp_server: %r, %r', args, kwargs)
203 medusa.ftp_server.ftp_server.__init__(self, *args, **kwargs)
204
205 def log(self, message):
206 """Redirect logging requests."""
207 trace.mutter('ftp_server: %s', message)
208
209 def log_info(self, message, type='info'):
210 """Override the asyncore.log_info so we don't stipple the screen."""
211 trace.mutter('ftp_server %s: %s', type, message)
212
213
214class FTPTestServer(test_server.TestServer):
215 """Common code for FTP server facilities."""
216
217 no_unicode_support = True
218
219 def __init__(self):
220 self._root = None
221 self._ftp_server = None
222 self._port = None
223 self._async_thread = None
224 # ftp server logs
225 self.logs = []
226
227 def get_url(self):
228 """Calculate an ftp url to this server."""
229 return 'ftp://foo:bar@localhost:%d/' % (self._port)
230
231 def get_bogus_url(self):
232 """Return a URL which cannot be connected to."""
233 return 'ftp://127.0.0.1:1'
234
235 def log(self, message):
236 """This is used by medusa.ftp_server to log connections, etc."""
237 self.logs.append(message)
238
239 def start_server(self, vfs_server=None):
240 if not (vfs_server is None or isinstance(vfs_server,
241 test_server.LocalURLServer)):
242 raise AssertionError(
243 "FTPServer currently assumes local transport, got %s" % vfs_server)
244 self._root = osutils.getcwd()
245 self._ftp_server = ftp_server(
246 authorizer=test_authorizer(root=self._root),
247 ip='localhost',
248 port=0, # bind to a random port
249 resolver=None,
250 logger_object=self # Use FTPServer.log() for messages
251 )
252 self._port = self._ftp_server.getsockname()[1]
253 # Don't let it loop forever, or handle an infinite number of requests.
254 # In this case it will run for 1000s, or 10000 requests
255 self._async_thread = threading.Thread(
256 target=FTPTestServer._asyncore_loop_ignore_EBADF,
257 kwargs={'timeout':0.1, 'count':10000})
258 if 'threads' in tests.selftest_debug_flags:
259 sys.stderr.write('Thread started: %s\n'
260 % (self._async_thread.ident,))
261 self._async_thread.setDaemon(True)
262 self._async_thread.start()
263
264 def stop_server(self):
265 self._ftp_server.close()
266 asyncore.close_all()
267 self._async_thread.join()
268 if 'threads' in tests.selftest_debug_flags:
269 sys.stderr.write('Thread joined: %s\n'
270 % (self._async_thread.ident,))
271
272 @staticmethod
273 def _asyncore_loop_ignore_EBADF(*args, **kwargs):
274 """Ignore EBADF during server shutdown.
275
276 We close the socket to get the server to shutdown, but this causes
277 select.select() to raise EBADF.
278 """
279 try:
280 asyncore.loop(*args, **kwargs)
281 # FIXME: If we reach that point, we should raise an exception
282 # explaining that the 'count' parameter in setUp is too low or
283 # testers may wonder why their test just sits there waiting for a
284 # server that is already dead. Note that if the tester waits too
285 # long under pdb the server will also die.
286 except select.error as e:
287 if e.args[0] != errno.EBADF:
288 raise
289
290 def add_user(self, user, password):
291 """Add a user with write access."""
292 authorizer = server = self._ftp_server.authorizer
293 authorizer.secured_user = user
294 authorizer.secured_password = password
295
2960
=== modified file 'breezy/tests/ftp_server/pyftpdlib_based.py'
--- breezy/tests/ftp_server/pyftpdlib_based.py 2017-05-22 00:56:52 +0000
+++ breezy/tests/ftp_server/pyftpdlib_based.py 2017-06-11 13:38:25 +0000
@@ -20,8 +20,20 @@
20"""20"""
2121
22import errno22import errno
23import logging
23import os24import os
24from pyftpdlib import ftpserver25import pyftpdlib
26import sys
27from pyftpdlib.authorizers import (
28 AuthorizerError,
29 DummyAuthorizer,
30 )
31from pyftpdlib.filesystems import AbstractedFS
32from pyftpdlib.handlers import (
33 FTPHandler,
34 proto_cmds,
35 )
36from pyftpdlib.servers import FTPServer
25import select37import select
26import threading38import threading
2739
@@ -34,22 +46,31 @@
34from breezy.tests import test_server46from breezy.tests import test_server
3547
3648
49class NullHandler(logging.Handler):
50
51 def emit(self, record):
52 pass
53
54# Shut up very verbose pyftpdlib
55logging.getLogger('pyftpdlib').addHandler(NullHandler())
56
57
37# Convert the pyftplib string version into a tuple to avoid traps in string58# Convert the pyftplib string version into a tuple to avoid traps in string
38# comparison.59# comparison.
39pyftplib_version = tuple(map(int, ftpserver.__ver__.split('.')))60pyftplib_version = tuple(map(int, pyftpdlib.__ver__.split('.')))
4061
4162
42class AnonymousWithWriteAccessAuthorizer(ftpserver.DummyAuthorizer):63class AnonymousWithWriteAccessAuthorizer(DummyAuthorizer):
4364
44 def _check_permissions(self, username, perm):65 def _check_permissions(self, username, perm):
45 # Like base implementation but don't warn about write permissions66 # Like base implementation but don't warn about write permissions
46 # assigned to anonymous, since that's exactly our purpose.67 # assigned to anonymous, since that's exactly our purpose.
47 for p in perm:68 for p in perm:
48 if p not in self.read_perms + self.write_perms:69 if p not in self.read_perms + self.write_perms:
49 raise ftpserver.AuthorizerError('No such permission "%s"' %p)70 raise AuthorizerError('No such permission "%s"' %p)
5071
5172
52class BzrConformingFS(ftpserver.AbstractedFS):73class BzrConformingFS(AbstractedFS):
5374
54 def chmod(self, path, mode):75 def chmod(self, path, mode):
55 return os.chmod(path, mode)76 return os.chmod(path, mode)
@@ -59,19 +80,20 @@
59 return [osutils.safe_utf8(s) for s in os.listdir(path)]80 return [osutils.safe_utf8(s) for s in os.listdir(path)]
6081
61 def fs2ftp(self, fspath):82 def fs2ftp(self, fspath):
62 p = ftpserver.AbstractedFS.fs2ftp(self, osutils.safe_unicode(fspath))83 p = AbstractedFS.fs2ftp(self, osutils.safe_unicode(fspath))
63 return osutils.safe_utf8(p)84 return osutils.safe_utf8(p)
6485
65 def ftp2fs(self, ftppath):86 def ftp2fs(self, ftppath):
66 p = osutils.safe_unicode(ftppath)87 p = osutils.safe_unicode(ftppath)
67 return ftpserver.AbstractedFS.ftp2fs(self, p)88 return AbstractedFS.ftp2fs(self, p)
6889
69class BzrConformingFTPHandler(ftpserver.FTPHandler):90
91class BzrConformingFTPHandler(FTPHandler):
7092
71 abstracted_fs = BzrConformingFS93 abstracted_fs = BzrConformingFS
7294
73 def __init__(self, conn, server):95 def __init__(self, conn, server, ioloop=None):
74 ftpserver.FTPHandler.__init__(self, conn, server)96 FTPHandler.__init__(self, conn, server)
75 self.authorizer = server.authorizer97 self.authorizer = server.authorizer
7698
77 def ftp_SIZE(self, path):99 def ftp_SIZE(self, path):
@@ -83,7 +105,7 @@
83 self.log('FAIL SIZE "%s". %s.' % (line, why))105 self.log('FAIL SIZE "%s". %s.' % (line, why))
84 self.respond("550 %s." %why)106 self.respond("550 %s." %why)
85 else:107 else:
86 ftpserver.FTPHandler.ftp_SIZE(self, path)108 FTPHandler.ftp_SIZE(self, path)
87109
88 def ftp_NLST(self, path):110 def ftp_NLST(self, path):
89 # bzr is overly picky here, but we want to make the test suite pass111 # bzr is overly picky here, but we want to make the test suite pass
@@ -94,7 +116,7 @@
94 self.log('FAIL NLST "%s". %s.' % (line, why))116 self.log('FAIL NLST "%s". %s.' % (line, why))
95 self.respond("550 %s." %why)117 self.respond("550 %s." %why)
96 else:118 else:
97 ftpserver.FTPHandler.ftp_NLST(self, path)119 FTPHandler.ftp_NLST(self, path)
98120
99 def log_cmd(self, cmd, arg, respcode, respstr):121 def log_cmd(self, cmd, arg, respcode, respstr):
100 # base class version choke on unicode, the alternative is to just122 # base class version choke on unicode, the alternative is to just
@@ -107,12 +129,12 @@
107129
108130
109# An empty password is valid, hence the arg is neither mandatory nor forbidden131# An empty password is valid, hence the arg is neither mandatory nor forbidden
110ftpserver.proto_cmds['PASS']['arg'] = None132proto_cmds['PASS']['arg'] = None
111133
112class ftp_server(ftpserver.FTPServer):134class ftp_server(FTPServer):
113135
114 def __init__(self, address, handler, authorizer):136 def __init__(self, address, handler, authorizer):
115 ftpserver.FTPServer.__init__(self, address, handler)137 FTPServer.__init__(self, address, handler)
116 self.authorizer = authorizer138 self.authorizer = authorizer
117 # Worth backporting upstream ?139 # Worth backporting upstream ?
118 self.addr = self.socket.getsockname()140 self.addr = self.socket.getsockname()
@@ -155,13 +177,6 @@
155 authorizer.add_anonymous(self._root, perm='elradfmwM')177 authorizer.add_anonymous(self._root, perm='elradfmwM')
156 self._ftp_server = ftp_server(address, BzrConformingFTPHandler,178 self._ftp_server = ftp_server(address, BzrConformingFTPHandler,
157 authorizer)179 authorizer)
158 # This is hacky as hell, will not work if we need two servers working
159 # at the same time, but that's the best we can do so far...
160 # FIXME: At least log and logline could be overriden in the handler ?
161 # -- vila 20090227
162 ftpserver.log = self.log
163 ftpserver.logline = self.log
164 ftpserver.logerror = self.log
165180
166 self._port = self._ftp_server.socket.getsockname()[1]181 self._port = self._ftp_server.socket.getsockname()[1]
167 self._ftpd_starting = threading.Lock()182 self._ftpd_starting = threading.Lock()
@@ -196,11 +211,11 @@
196 self._ftpd_starting.release()211 self._ftpd_starting.release()
197 while self._ftpd_running:212 while self._ftpd_running:
198 try:213 try:
199 self._ftp_server.serve_forever(timeout=0.1, count=1)214 self._ftp_server.serve_forever(timeout=0.1)
200 except select.error as e:215 except select.error as e:
201 if e.args[0] != errno.EBADF:216 if e.args[0] != errno.EBADF:
202 raise217 raise
203 self._ftp_server.close_all(ignore_all=True)218 self._ftp_server.close_all()
204219
205 def add_user(self, user, password):220 def add_user(self, user, password):
206 """Add a user with write access."""221 """Add a user with write access."""
207222
=== modified file 'doc/en/release-notes/brz-3.0.txt'
--- doc/en/release-notes/brz-3.0.txt 2017-06-11 01:27:46 +0000
+++ doc/en/release-notes/brz-3.0.txt 2017-06-11 13:38:25 +0000
@@ -41,6 +41,9 @@
41 URL schemes "http+pycurl://" and "https+pycurl://" has been dropped.41 URL schemes "http+pycurl://" and "https+pycurl://" has been dropped.
42 (Jelmer Vernooij, #82086, #377389, #122258, #516222, #545776, #1696602)42 (Jelmer Vernooij, #82086, #377389, #122258, #516222, #545776, #1696602)
4343
44 * Support for medusa for FTP tests has been dropped, only
45 pyftpdlib is now supported. (Jelmer Vernooij)
46
44New Features47New Features
45************48************
4649
@@ -135,5 +138,8 @@
135 which caused ``output_encoding = iso-8859-1`` to be added to the138 which caused ``output_encoding = iso-8859-1`` to be added to the
136 users' bazaar.conf. (Jelmer Vernooij)139 users' bazaar.conf. (Jelmer Vernooij)
137140
141 * Newer versions of ``pyftpdlib`` are now supported for running FTP tests.
142 (Jelmer Vernooij)
143
138..144..
139 vim: tw=74 ft=rst ff=unix145 vim: tw=74 ft=rst ff=unix
140146
=== modified file 'setup.py'
--- setup.py 2017-06-07 00:34:37 +0000
+++ setup.py 2017-06-11 13:38:25 +0000
@@ -587,7 +587,6 @@
587 excludes = """Tkinter psyco ElementPath r_hmac587 excludes = """Tkinter psyco ElementPath r_hmac
588 ImaginaryModule cElementTree elementtree.ElementTree588 ImaginaryModule cElementTree elementtree.ElementTree
589 Crypto.PublicKey._fastmath589 Crypto.PublicKey._fastmath
590 medusa medusa.filesys medusa.ftp_server
591 tools590 tools
592 resource validate""".split()591 resource validate""".split()
593 dll_excludes = []592 dll_excludes = []

Subscribers

People subscribed via source and target branches