[MERGE] [0.11] Working bzr+ssh://, make "bzr serve --inet" read-only by default.

Andrew Bennetts andrew at canonical.com
Tue Sep 19 03:49:23 BST 2006


John Arbash Meinel wrote:
> Andrew Bennetts wrote:
> > Hi all,
> > 
> > http://people.ubuntu.com/~andrew/bzr/bzr-ssh-testing/ has some changes I'd like
> > to see merged, ideally for 0.11 despite the freeze:
[...]
> 
> First, a small comment that if you want to get something reviewed
> quickly, it really is easier to review if you include at least a diff.
> (A bundle can be nice too, since we can directly merge from that, but a
> diff at a minimum, so that we can make comments directly, rather than
> looking at it separately.)

Oops.  I meant to do that, but I forgot.

> Can we ensure that doing 'del transport' actually cleans up the
> connection? (I assume you are doing this because you want to avoid
> getting an exception from the Transport object, which thinks it is still
> connected).
> 
> I'm fine with doing it, but it seems like we may have an api deficiency
> if you need to do it.

Unfortunately, there's no good way to inform a transport that it's no longer
wanted, and so should release any resources it may have acquired (like
connections to remote hosts).

I don't think the 'del transport' statements are really necessary, though.
assertInetServerShutsdownCleanly will forcefully disconnect.  Removing the del
lines doesn't alter the results of the test run.

I'll check with Robert; I know they make Robert a bit comfortable, because it's
the closest thing we have to a "close this transport" API, so it's arguably more
correct, but I'd be happy to see those statements disappear.

> The ugly hack that is StubSSHServer...
> 
> Using 1 thread per file is how subprocess.py does I/O in subprocess.py
> (if running under windows). So I don't know that there is much else we
> can do. I know paramiko uses threads as well, so it is easily possible
> that this is the best we can do.

I'm not so much bothered by the gratuitous threads (which I agree are the
easiest way to do this portably) or even the way it operates on single bytes, as
much as the fact I have to write it at all.  I would have thought that paramiko
itself would already have implemented, or at least an example of, a non-trivial
check_channel_exec_request like this.  But I could find no code in paramiko to
help me here, which seems to be a strange lack -- surely hooking up a subprocess
to a channel is a common enough use-case?  I guess not.

Basically, aside from the initial line where I keep track of the command the
server was asked to execute, I want the rest of check_channel_exec_request to
just invoke the default implementation.  It's just that apparently no such thing
exists!

> I'm a little concerned that you combine stdout with stderr and send them
> both over the pipe. I would assume real ssh uses a separate channel for
> stderr. This is probably okay for now, but I have the feeling it
> indicates a limitation of our model if we expect it to work that way.

Oops, that wasn't intentional.  I've fixed that; the implementation of
check_channel_exec_request is now:

    def check_channel_exec_request(self, channel, command):
        [...]
        
        # XXX: horribly inefficient, not to mention ugly.
        # Start a thread for each of stdin/out/err, and relay bytes from
        # the subprocess to channel and vice versa.
        def ferry_bytes(read, write, close):
            while True:
                bytes = read(1)
                if bytes == '':
                    close()
                    break
                write(bytes)

        file_functions = [
            (channel.recv, proc.stdin.write, proc.stdin.close),
            (proc.stdout.read, channel.sendall, channel.close),
            (proc.stderr.read, channel.sendall_stderr, channel.close)]
        for read, write, close in file_functions:
            t = threading.Thread(
                target=ferry_bytes, args=(read, write, close))
            t.start()

        return True

> It seems like 'self.command_executed' should actually be a list, so we
> can tell if there are multiple connection attempts.

Good idea.

> Also, it would probably be better to have the StubSSHServer not defined
> inside the test function, so that it could be accessed by other tests in
> the future.

That would be nice, but it's awkward.  StubSSHServer needs paramiko to be
present, and it needs a reference to the test case.  So it'd need to be guarded
from ImportErrors (not needed inside the test method, because it will raise
TestSkipped).  Also, it needs a reference to the test case to stash the
command_executed value, and despite the 'test_case' parameter in its __init__,
it's actually passed an SFTPServer when constructed.  Oh, I guess I can use
'logs' attribute of SFTPServer instead.

Ok, I've made that change.

> Why are we translating ReadOnlyError => TransportNotPossible. It seems
> like we would want to tell the user that the remote directory is readonly.

I'll let Robert answer this.

> How do you handle ssh connecting to the local SSH connection without a
> password?

The same way as for SFTP -- the authentication code paths are basically the
same.  I only specify a username and password in the test because that's what
the StubServer expects -- I considered allowing 'none' auth instead of
'password' in the test, but I'm not sure how to make paramiko perform 'none'
auth as a client.

> Anyway, the changes look good. And while I don't think it is a trivial
> change, it does fit Robert's new definition of trivial (does not
> destabilize existing apis).
> 
> So I have some comments, but nothing that would really block it from
> 0.11. (+0.9, which you can upgrade to 1.0 if you like)

I hope the changes I've made earn the extra +0.1 ;)

Thanks for the review!

-Andrew.





More information about the bazaar mailing list