Merge lp:~vila/bzr/443041-ftp-append-bytes into lp:bzr
- 443041-ftp-append-bytes
- Merge into bzr.dev
Status: | Merged |
---|---|
Approved by: | John A Meinel |
Approved revision: | no longer in the source branch. |
Merged at revision: | not available |
Proposed branch: | lp:~vila/bzr/443041-ftp-append-bytes |
Merge into: | lp:bzr |
Diff against target: |
242 lines 4 files modified
NEWS (+4/-0) bzrlib/tests/ftp_server/pyftpdlib_based.py (+7/-4) bzrlib/transport/ftp/__init__.py (+21/-22) bzrlib/transport/ftp/_gssapi.py (+9/-39) |
To merge this branch: | bzr merge lp:~vila/bzr/443041-ftp-append-bytes |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
John A Meinel | Approve | ||
Review via email: mp+12909@code.launchpad.net |
This proposal supersedes a proposal from 2009-10-05.
Commit message
Description of the change
Vincent Ladeuil (vila) wrote : Posted in a previous version of this proposal | # |
Vincent Ladeuil (vila) wrote : Posted in a previous version of this proposal | # |
I also cleaned up some useless imports.
John A Meinel (jameinel) wrote : Posted in a previous version of this proposal | # |
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
Vincent Ladeuil wrote:
> Vincent Ladeuil has proposed merging lp:~vila/bzr/443041-ftp-append-bytes into lp:bzr.
>
> Requested reviews:
> bzr-core (bzr-core)
>
>
> The bug occurred when I installed pyftpdlib on the jaunty slave.
>
> Before that, no ftp server was available so we didn't have the same
> coverage (hopefully medusa is installed on pqm... anybody can confirm ?)
>
> Anyway, I also fixed a spurious warning from the server thread that rarely occurs
> (but was present in the babune failure).
>
> The root cause was that apparently we tried to issue a SIZE command while being in ASCII mode
> instead of BINARY mode, separating the transfer command (and using ftplib facility for that
> instead of redoing that ourself) from the size command avoided the issue.
>
> I also avoid reading the whole text to be appended, our ftp transport is already pretty bad
> are buffering the whole file when reading, no need to make it worse than necessary here.
I disagree with changing to use a file pointer rather than the raw text.
Mostly because we never actually call append with a file pointer, but we
call it with bytes to be appended. I think the FTP layering has it
wrong, and the 'file' form should be calling the 'bytes' form, rather
than the other way around...
I was originally concerned that you require '.tell' and '.seek' or the
file-like objects, as there are a lot of file-like objects that don't
implement it. (urllib's socket handler, reading from stdin, etc.)
Ultimately, we won't be buffering any less with your patch, it just
means we'll be reading from the StringIO wrapper more...
I'm also having some difficulty sorting out what your actual fix was,
because of all the other noise. Something about not actually returning
the start bytes?
- - def _fallback_
+ def _fallback_
remote = self.get(relpath)
- - remote.seek(0, 2)
- - remote.write(text)
- - remote.seek(0, 0)
+ remote.seek(0, os.SEEK_END)
+ osutils.
+ remote.seek(0)
return self.put_
^- I guess I didn't realize this was going on, but writing to the
file-like object that you get back from "get()" is rather ugly.... I
suppose if we know that \.get() returns a StringIO() we can do it.
Though note that doing .pumpfile() introduces a memory thrashing
condition, where we append bytes to an existing string N/32kB times, and
thus suffer that many reallocations. (Pushing up 100MB is thus 3200
reallocations of 100MB of data...)
Though I guess if we are worried about FTP performance, this is the
least of our worries. When we push up a 100MB pack, we are likely going
to do it 1MB at a time, and since upper level code assumes that .append
is available, we are going to:
create the file
read an empty file
add 1MB
put the 1MB file back up
read the 1MB file
add 1MB
put the 2MB file back up
...
Which I think means we read (1-100MB) 5GB and write 5GB... Regardless of
the reallocations we do in memory to append 1MB to the big buffer, 32k...
Vincent Ladeuil (vila) wrote : Posted in a previous version of this proposal | # |
>>>>> "jam" == John A Meinel <email address hidden> writes:
<snip/>
jam> I disagree with changing to use a file pointer rather than the raw text.
You should have read the code more closely, all the modified
calls happen under append_file, which receives a file-like object
(that the defined *API*).
jam> Mostly because we never actually call append with a file
jam> pointer, but we call it with bytes to be appended.
No. append_file() is called with a file.
jam> I think the FTP layering has it wrong, and the 'file'
jam> form should be calling the 'bytes' form, rather than the
jam> other way around...
ESCOPE.
jam> I was originally concerned that you require '.tell' and
jam> '.seek' or the file-like objects, as there are a lot of
jam> file-like objects that don't implement it. (urllib's
jam> socket handler, reading from stdin, etc.)
seek() is already needed, AFAIK tell() is always implemented when seek() is.
jam> Ultimately, we won't be buffering any less with your patch, it just
jam> means we'll be reading from the StringIO wrapper more...
Yes. Eliminating an *additional* buffering layer.
jam> I'm also having some difficulty sorting out what your
jam> actual fix was, because of all the other
jam> noise. Something about not actually returning the start
jam> bytes?
Ha, right, my bug report was a bit sparse, sorry.
What happened was that we couldn't get the size of the file (that
we want to return after appending) so we returned 0 instead and
that made the test fail.
We couldn't get the size because the mode was ASCII and you can't
issue a SIZE command then.
The mode was ASCII because the APPE command was not implemented
using ftp.transfercmd().
ftp.transfercmd() wants a file-like object.
The file-like object has been transformed into its content by the
caller for no apparent good reasons.
I reversed that transformation to be able to still use the
file-like object provided by the caller of my caller.
And then you vetoed that :-D
jam> - def _fallback_
jam> + def _fallback_
jam> remote = self.get(relpath)
jam> - remote.seek(0, 2)
jam> - remote.write(text)
jam> - remote.seek(0, 0)
jam> + remote.seek(0, os.SEEK_END)
jam> + osutils.
jam> + remote.seek(0)
jam> return self.put_
jam> ^- I guess I didn't realize this was going on, but writing to the
jam> file-like object that you get back from "get()" is rather ugly....
to say the least, but ESCOPE.
<snip/>
jam> So... "we already buffer to much", arguably we don't buffer enough yet...
I reduced the buffering and stands by that sentence :D
jam> What we need is a custom FTPStreamWriter that when it
jam> realizes the remote site doesn't support APPE then
jam> buffers to a temp file on disk, and pushes up the whole
jam> content once we have finished.
Different bug....
jam> Anyway, I'm not sure I can approve this as a bug fix,
jam> because I can't really s...
John A Meinel (jameinel) wrote : Posted in a previous version of this proposal | # |
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
...
>
> jam> Mostly because we never actually call append with a file
> jam> pointer, but we call it with bytes to be appended.
>
> No. append_file() is called with a file.
FTPTransport.
the compatibility thunk defined in Transport:
def append_bytes(self, relpath, bytes, mode=None):
...
if not isinstance(bytes, str):
raise TypeError(
return self.append_
>
> jam> I think the FTP layering has it wrong, and the 'file'
> jam> form should be calling the 'bytes' form, rather than the
> jam> other way around...
>
> ESCOPE.
My very specific point is that our code *never* calls append_file
directly, and *always* calls append_bytes. The fact that you have a
'file-like' object is bogus, because we really passed in a string, that
we then faked. (Originally, all of Transport was file-like object based,
until we realized that a lot of things were done with raw bytes and we
were wasting a lot of time translating.)
>
> jam> I was originally concerned that you require '.tell' and
> jam> '.seek' or the file-like objects, as there are a lot of
> jam> file-like objects that don't implement it. (urllib's
> jam> socket handler, reading from stdin, etc.)
>
> seek() is already needed, AFAIK tell() is always implemented when seek() is.
>
> jam> Ultimately, we won't be buffering any less with your patch, it just
> jam> means we'll be reading from the StringIO wrapper more...
>
> Yes. Eliminating an *additional* buffering layer.
Sort of. In that "cStringIO.
But completely not in that we didn't just implement "append_bytes()" in
the first place.
...
>
> ftp.transfercmd() wants a file-like object.
>
> The file-like object has been transformed into its content by the
> caller for no apparent good reasons.
>
> I reversed that transformation to be able to still use the
> file-like object provided by the caller of my caller.
>
> And then you vetoed that :-D
>
So there isn't a form that takes a plain string? Certainly we *don't*
want to be appending in ASCII mode, given that we are usually appending
compressed bytes.
>
> jam> Anyway, I'm not sure I can approve this as a bug fix,
> jam> because I can't really see the bug fix. Changing from
> jam> taking 'text' to taking a file-like object I think is
> jam> the wrong fix,
>
> You don't look at the right part. I don't think using a file-like
> object that produced the string is not wrong.
Well, only that I was thinking up at the higher level.
John
=:->
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://
iEYEARECAAYFAkr
3uwAn1TIFXn7EYM
=TbDg
-----END PGP SIGNATURE-----
Vincent Ladeuil (vila) wrote : Posted in a previous version of this proposal | # |
John, I'm sorry if my last email wasn't clear.
Many points in your analysis are correct and I agreed with
them. You outlined at least 2 bugs in our current ftp
implementation.
The bug I am fixing is a regression. Our test suite is not
passing anymore on jaunty with pyftpdlib installed.
I can uninstall it or fix that bug.
<snip/>
jam> My very specific point is that our code *never* calls
jam> append_file directly, and *always* calls
jam> append_bytes. The fact that you have a 'file-like'
jam> object is bogus,
That's bug #1.
<snip/>
jam> Sort of. In that "cStringIO.
Yes. Not even a fraction of the other bugs you're pointing.
jam> But completely not in that we didn't just implement
jam> "append_bytes()" in the first place.
True.
<snip/>
jam> So there isn't a form that takes a plain string?
At that layer. No. append_bytes() is implemented with
append_file().
That's still bug #1.
jam> Certainly we *don't* want to be appending in ASCII mode,
jam> given that we are usually appending compressed bytes.
Obviously. We *are* transferring bytes. But somehow the mode ends
up being ASCII *anyway* (the log reveals that the server errors
on 'Can't use SIZE in ASCII mode (or something like that, I don't
have a copy at hand anymore). That's the only thing I want to fix
here so the core of the fix is:
- cmd = "APPE %s" % abspath
- conn = ftp.transfercmd
- conn.sendall(text)
- conn.close()
+ starting_at = fp.tell()
+ ftp.storbinary(
self.
- ftp.getresp()
The _setmode() and the transfercmd() were mixed, the getresp()
call is part of the ftplib.storbinary() implementation.
Just swapping the two lines can be enough. But we are at the
other hand of the consequences of bug #1, ftplib really want a
file-like object.... and that's from where I went up back to the
previous transformation of the file-like object required by the
implementation.
That's why "changing from taking 'text' to taking a file-like
object" is the right fix, because it eliminates a file ->
text -> file transformation.
Did you say ironic ?
:-D
<snip/>
jam> Well, only that I was thinking up at the higher level.
No doubt and that was nice catches.
bug #2 is that we shouldn't use the result of get() to call
_put_file(). At a minimum the file should be downloaded once and
once only. We may use a StringIO() up to some threshold (64K, 1M
?) and degrade to a temp file.
But those two bugs were there before this bug, let's address it
first.
John A Meinel (jameinel) wrote : Posted in a previous version of this proposal | # |
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
...
>
> jam> So there isn't a form that takes a plain string?
>
> At that layer. No. append_bytes() is implemented with
> append_file().
>
> That's still bug #1.
>
> jam> Certainly we *don't* want to be appending in ASCII mode,
> jam> given that we are usually appending compressed bytes.
>
> Obviously. We *are* transferring bytes. But somehow the mode ends
> up being ASCII *anyway* (the log reveals that the server errors
> on 'Can't use SIZE in ASCII mode (or something like that, I don't
> have a copy at hand anymore). That's the only thing I want to fix
> here so the core of the fix is:
>
> - cmd = "APPE %s" % abspath
> - conn = ftp.transfercmd
> - conn.sendall(text)
> - conn.close()
> + starting_at = fp.tell()
> + ftp.storbinary(
> self._setmode(
> - ftp.getresp()
>
> The _setmode() and the transfercmd() were mixed, the getresp()
> call is part of the ftplib.storbinary() implementation.
>
> Just swapping the two lines can be enough. But we are at the
> other hand of the consequences of bug #1, ftplib really want a
> file-like object.... and that's from where I went up back to the
> previous transformation of the file-like object required by the
> implementation.
If you look at the details of "storbinary" you can see:
def storbinary(self, cmd, fp, blocksize=8192, callback=None):
...
conn = self.transfercm
while 1:
buf = fp.read(blocksize)
if not buf: break
if callback: callback(buf)
return self.voidresp()
^- So I would say the appropriate fix is not to change to calling
storbinary, but instead to just add:
self.voidcmd('TYPE I')
just before the existing
conn = ftp.transfercmd
My actual guess is that if the transmission is being done in ASCII mode,
then we have a different bug, possibly just in the test suite. (Meaning
this bug doesn't actually cause problem is the wild, but is exposed when
running the test suite.)
Most likely, we do something like "os.list_dir()" in the test just
before the "append" test, and that the "list_dir()" is setting the
socket into ascii mode, and doesn't set the mode back into binary for
the file transfer.
I'm not sure if we always want to set binary even when we don't need to,
but as long as it doesn't actually incur a full round trip, it is
probably fine. But no..
def voidcmd(self, cmd):
"""Send a command and expect a response beginning with '2'."""
return self.voidresp()
^- So it means that if we don't track the binary/ascii ourselves, then
using storbinary() actually triggers an *extra* round-trip per APPE call.
...
> But those two bugs were there before this bug, let's address it
> first.
There are a lot of bugs/gremlins in this code... *I* have never taken it
all that seriously versus sftp/ssh support.
What I also don't understand is this code by Vincent Ladeuil on line 557:
finally:
# Restore binary mode as nlst switch to ascii mode to
r...
Vincent Ladeuil (vila) wrote : Posted in a previous version of this proposal | # |
>>>>> "jam" == John A Meinel <email address hidden> writes:
<snip/>
>> The _setmode() and the transfercmd() were mixed, the getresp()
>> call is part of the ftplib.storbinary() implementation.
While it's a bit ugly that wasn't the real root cause.
<snip/>
jam> If you look at the details of "storbinary" you can see:
jam> def storbinary(self, cmd, fp, blocksize=8192, callback=None):
jam> ...
jam> self.voidcmd('TYPE I')
jam> conn = self.transfercm
jam> while 1:
jam> buf = fp.read(blocksize)
jam> if not buf: break
jam> conn.sendall(buf)
jam> if callback: callback(buf)
jam> conn.close()
jam> return self.voidresp()
jam> ^- So I would say the appropriate fix is not to change to calling
jam> storbinary,
No comment about the fact that storbinary() implements *some*
buffering where we don't ?
No fear about sendall() blowing up with too big strings ?
Ok, let's called that bug #3 since I reverted that part.
jam> but instead to just add:
jam> self.voidcmd('TYPE I')
jam> just before the existing
jam> conn = ftp.transfercmd
I deleted that line you want to add to avoid the incurred full
round-trip when I better tracked the binary mode (you refer to
that later, that's revno 4063 in bzr.dev).
That's where I (but all of us with me) fall into the trap of
untested code.
Namely, the GSSAPI ftp implementation is not actually tested
because we don't have a kerberos test server.
Yet, the way it's implemented is that we create a GSSAPI ftp
client if the python kerberos module is present (it is on my
jaunty host), try to authenticate with that but fallback to the
regular authentication if we can't.
And that's all that is tested, the fallback, but *only* if the
kerberos module is present !
That module wasn't installed when I landed revno 4063 (on neither
my test host nor pqm, nor anybody running the full test suite
with an ftp test server, any takers ?), so I didn't test the
combination kerberos+
then pyftpdlib (since only one is used I had to setup the three
test envs: no ftp server, medusa, pyftpdlib).
jam> My actual guess is that if the transmission is being
jam> done in ASCII mode, then we have a different bug,
jam> possibly just in the test suite. (Meaning this bug
jam> doesn't actually cause problem is the wild, but is
jam> exposed when running the test suite.)
jam> Most likely, we do something like "os.list_dir()" in the
jam> test just before the "append" test, and that the
jam> "list_dir()" is setting the socket into ascii mode, and
jam> doesn't set the mode back into binary for the file
jam> transfer.
Quite the contrary, invoking list_dir() force the mode back to
binary because that's the only command that requires the ASCII
mode. So any call to list_dir() would have *avoided* the bug not
revealed it.
<snip/>
jam> ^- So it means that if we don't track the binary/ascii
jam> ourselves, then using storbinary() actually triggers an
jam> *ext...
John A Meinel (jameinel) wrote : | # |
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
Vincent Ladeuil wrote:
> Vincent Ladeuil has proposed merging lp:~vila/bzr/443041-ftp-append-bytes into lp:bzr.
>
> Requested reviews:
> John A Meinel (jameinel)
>
>
So as I understand it, the main change is that _create_connection for
the regular ftp server was calling:
# binary mode is the default
And the implementation of the GSSAPI ftp server was not. So you
refactored the code such that only "_login()" is overridden, rather than
all of "_create_
Less code duplication is very nice.
review: approve
merge: approve
John
=:->
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://
iEYEARECAAYFAkr
IyUAn0xCfFiFsT6
=X5qe
-----END PGP SIGNATURE-----
Vincent Ladeuil (vila) wrote : | # |
>>>>> "jam" == John A Meinel <email address hidden> writes:
jam> Review: Approve
jam> Vincent Ladeuil wrote:
>> Vincent Ladeuil has proposed merging lp:~vila/bzr/443041-ftp-append-bytes into lp:bzr.
>>
>> Requested reviews:
>> John A Meinel (jameinel)
>>
>>
jam> So as I understand it, the main change is that _create_connection for
jam> the regular ftp server was calling:
jam> # binary mode is the default
jam> connection.
jam> And the implementation of the GSSAPI ftp server was not. So you
jam> refactored the code such that only "_login()" is overridden, rather than
jam> all of "_create_
Yes. _login and connection_class.
jam> Less code duplication is very nice.
And means less bugs an less discussions :-D
jam> review: approve
jam> merge: approve
Thanks for your patience
Preview Diff
1 | === modified file 'NEWS' |
2 | --- NEWS 2009-10-06 02:38:44 +0000 |
3 | +++ NEWS 2009-10-06 08:55:22 +0000 |
4 | @@ -104,6 +104,10 @@ |
5 | with some combinations of remote and local formats. This was causing |
6 | "unknown object type identifier 60" errors. (Andrew Bennetts, #427736) |
7 | |
8 | +* ftp transports were built differently when the kerberos python module was |
9 | + present leading to obscure failures related to ASCII/BINARY modes. |
10 | + (Vincent Ladeuil, #443041) |
11 | + |
12 | * Network streams now decode adjacent records of the same type into a |
13 | single stream, reducing layering churn. (Robert Collins) |
14 | |
15 | |
16 | === modified file 'bzrlib/tests/ftp_server/pyftpdlib_based.py' |
17 | --- bzrlib/tests/ftp_server/pyftpdlib_based.py 2009-03-23 14:59:43 +0000 |
18 | +++ bzrlib/tests/ftp_server/pyftpdlib_based.py 2009-10-06 08:55:22 +0000 |
19 | @@ -84,7 +84,7 @@ |
20 | line = self.fs.fs2ftp(path) |
21 | if self.fs.isfile(self.fs.realpath(path)): |
22 | why = "Not a directory: %s" % line |
23 | - self.log('FAIL SIZE "%s". %s.' % (line, why)) |
24 | + self.log('FAIL NLST "%s". %s.' % (line, why)) |
25 | self.respond("550 %s." %why) |
26 | else: |
27 | ftpserver.FTPHandler.ftp_NLST(self, path) |
28 | @@ -180,8 +180,7 @@ |
29 | self._port = self._ftp_server.socket.getsockname()[1] |
30 | self._ftpd_starting = threading.Lock() |
31 | self._ftpd_starting.acquire() # So it can be released by the server |
32 | - self._ftpd_thread = threading.Thread( |
33 | - target=self._run_server,) |
34 | + self._ftpd_thread = threading.Thread(target=self._run_server,) |
35 | self._ftpd_thread.start() |
36 | # Wait for the server thread to start (i.e release the lock) |
37 | self._ftpd_starting.acquire() |
38 | @@ -202,7 +201,11 @@ |
39 | self._ftpd_running = True |
40 | self._ftpd_starting.release() |
41 | while self._ftpd_running: |
42 | - self._ftp_server.serve_forever(timeout=0.1, count=1) |
43 | + try: |
44 | + self._ftp_server.serve_forever(timeout=0.1, count=1) |
45 | + except select.error, e: |
46 | + if e.args[0] != errno.EBADF: |
47 | + raise |
48 | self._ftp_server.close_all(ignore_all=True) |
49 | |
50 | def add_user(self, user, password): |
51 | |
52 | === modified file 'bzrlib/transport/ftp/__init__.py' |
53 | --- bzrlib/transport/ftp/__init__.py 2009-07-22 06:51:13 +0000 |
54 | +++ bzrlib/transport/ftp/__init__.py 2009-10-06 08:55:22 +0000 |
55 | @@ -25,17 +25,13 @@ |
56 | """ |
57 | |
58 | from cStringIO import StringIO |
59 | -import errno |
60 | import ftplib |
61 | import getpass |
62 | import os |
63 | -import os.path |
64 | -import urlparse |
65 | import random |
66 | import socket |
67 | import stat |
68 | import time |
69 | -from warnings import warn |
70 | |
71 | from bzrlib import ( |
72 | config, |
73 | @@ -51,8 +47,6 @@ |
74 | register_urlparse_netloc_protocol, |
75 | Server, |
76 | ) |
77 | -from bzrlib.transport.local import LocalURLServer |
78 | -import bzrlib.ui |
79 | |
80 | |
81 | register_urlparse_netloc_protocol('aftp') |
82 | @@ -99,8 +93,9 @@ |
83 | self.is_active = True |
84 | else: |
85 | self.is_active = False |
86 | - |
87 | - # Most modern FTP servers support the APPE command. If ours doesn't, we (re)set this flag accordingly later. |
88 | + |
89 | + # Most modern FTP servers support the APPE command. If ours doesn't, we |
90 | + # (re)set this flag accordingly later. |
91 | self._has_append = True |
92 | |
93 | def _get_FTP(self): |
94 | @@ -113,6 +108,8 @@ |
95 | self._set_connection(connection, credentials) |
96 | return connection |
97 | |
98 | + connection_class = ftplib.FTP |
99 | + |
100 | def _create_connection(self, credentials=None): |
101 | """Create a new connection with the provided credentials. |
102 | |
103 | @@ -138,13 +135,9 @@ |
104 | ((self._host, self._port, user, '********', |
105 | self.is_active),)) |
106 | try: |
107 | - connection = ftplib.FTP() |
108 | + connection = self.connection_class() |
109 | connection.connect(host=self._host, port=self._port) |
110 | - if user and user != 'anonymous' and \ |
111 | - password is None: # '' is a valid password |
112 | - password = auth.get_password('ftp', self._host, user, |
113 | - port=self._port) |
114 | - connection.login(user=user, passwd=password) |
115 | + self._login(connection, auth, user, password) |
116 | connection.set_pasv(not self.is_active) |
117 | # binary mode is the default |
118 | connection.voidcmd('TYPE I') |
119 | @@ -157,6 +150,13 @@ |
120 | " %s" % str(e), orig_error=e) |
121 | return connection, (user, password) |
122 | |
123 | + def _login(self, connection, auth, user, password): |
124 | + # '' is a valid password |
125 | + if user and user != 'anonymous' and password is None: |
126 | + password = auth.get_password('ftp', self._host, |
127 | + user, port=self._port) |
128 | + connection.login(user=user, passwd=password) |
129 | + |
130 | def _reconnect(self): |
131 | """Create a new connection with the previously used credentials""" |
132 | credentials = self._get_credentials() |
133 | @@ -391,7 +391,6 @@ |
134 | location. |
135 | """ |
136 | text = f.read() |
137 | - |
138 | abspath = self._remote_path(relpath) |
139 | if self.has(relpath): |
140 | ftp = self._get_FTP() |
141 | @@ -426,18 +425,18 @@ |
142 | except ftplib.error_perm, e: |
143 | # Check whether the command is not supported (reply code 502) |
144 | if str(e).startswith('502 '): |
145 | - warning("FTP server does not support file appending natively. " \ |
146 | - "Performance may be severely degraded! (%s)", e) |
147 | + warning("FTP server does not support file appending natively. " |
148 | + "Performance may be severely degraded! (%s)", e) |
149 | self._has_append = False |
150 | self._fallback_append(relpath, text, mode) |
151 | else: |
152 | self._translate_perm_error(e, abspath, extra='error appending', |
153 | unknown_exc=errors.NoSuchFile) |
154 | - |
155 | except ftplib.error_temp, e: |
156 | if retries > _number_of_retries: |
157 | - raise errors.TransportError("FTP temporary error during APPEND %s." \ |
158 | - "Aborting." % abspath, orig_error=e) |
159 | + raise errors.TransportError( |
160 | + "FTP temporary error during APPEND %s. Aborting." |
161 | + % abspath, orig_error=e) |
162 | else: |
163 | warning("FTP temporary error: %s. Retrying.", str(e)) |
164 | self._reconnect() |
165 | @@ -445,9 +444,9 @@ |
166 | |
167 | def _fallback_append(self, relpath, text, mode = None): |
168 | remote = self.get(relpath) |
169 | - remote.seek(0, 2) |
170 | + remote.seek(0, os.SEEK_END) |
171 | remote.write(text) |
172 | - remote.seek(0, 0) |
173 | + remote.seek(0) |
174 | return self.put_file(relpath, remote, mode) |
175 | |
176 | def _setmode(self, relpath, mode): |
177 | |
178 | === modified file 'bzrlib/transport/ftp/_gssapi.py' |
179 | --- bzrlib/transport/ftp/_gssapi.py 2009-09-18 01:25:08 +0000 |
180 | +++ bzrlib/transport/ftp/_gssapi.py 2009-10-06 08:55:22 +0000 |
181 | @@ -97,52 +97,22 @@ |
182 | |
183 | """ |
184 | |
185 | - def _create_connection(self, credentials=None): |
186 | - """Create a new connection with the provided credentials. |
187 | - |
188 | - :param credentials: The credentials needed to establish the connection. |
189 | - |
190 | - :return: The created connection and its associated credentials. |
191 | - |
192 | - The credentials are a tuple with the username and password. The |
193 | - password is used if GSSAPI Authentication is not available. |
194 | + connection_class = GSSAPIFtp |
195 | + |
196 | + def _login(self, connection, auth, user, password): |
197 | + """Login with GSSAPI Authentication. |
198 | + |
199 | + The password is used if GSSAPI Authentication is not available. |
200 | |
201 | The username and password can both be None, in which case the |
202 | credentials specified in the URL or provided by the |
203 | AuthenticationConfig() are used. |
204 | """ |
205 | - if credentials is None: |
206 | - user, password = self._user, self._password |
207 | - else: |
208 | - user, password = credentials |
209 | - |
210 | - auth = config.AuthenticationConfig() |
211 | - if user is None: |
212 | - user = auth.get_user('ftp', self._host, port=self._port, |
213 | - default=getpass.getuser()) |
214 | - mutter("Constructing FTP instance against %r" % |
215 | - ((self._host, self._port, user, '********', |
216 | - self.is_active),)) |
217 | try: |
218 | - connection = GSSAPIFtp() |
219 | - connection.connect(host=self._host, port=self._port) |
220 | - try: |
221 | - connection.gssapi_login(user=user) |
222 | - except ftplib.error_perm, e: |
223 | - if user and user != 'anonymous' and \ |
224 | - password is None: # '' is a valid password |
225 | - password = auth.get_password('ftp', self._host, user, |
226 | - port=self._port) |
227 | - connection.login(user=user, passwd=password) |
228 | - connection.set_pasv(not self.is_active) |
229 | - except socket.error, e: |
230 | - raise errors.SocketConnectionError(self._host, self._port, |
231 | - msg='Unable to connect to', |
232 | - orig_error= e) |
233 | + connection.gssapi_login(user=user) |
234 | except ftplib.error_perm, e: |
235 | - raise errors.TransportError(msg="Error setting up connection:" |
236 | - " %s" % str(e), orig_error=e) |
237 | - return connection, (user, password) |
238 | + super(GSSAPIFtpTransport, self)._login(connection, auth, |
239 | + user, password) |
240 | |
241 | |
242 | def get_test_permutations(): |
The bug occurred when I installed pyftpdlib on the jaunty slave.
Before that, no ftp server was available so we didn't have the same
coverage (hopefully medusa is installed on pqm... anybody can confirm ?)
Anyway, I also fixed a spurious warning from the server thread that rarely occurs
(but was present in the babune failure).
The root cause was that apparently we tried to issue a SIZE command while being in ASCII mode
instead of BINARY mode, separating the transfer command (and using ftplib facility for that
instead of redoing that ourself) from the size command avoided the issue.
I also avoid reading the whole text to be appended, our ftp transport is already pretty bad
are buffering the whole file when reading, no need to make it worse than necessary here.