← Back to team overview

launchpad-reviewers team mailing list archive

Re: [Merge] lp:~cjwatson/launchpad/split-txpkgupload into lp:launchpad

 

Review: Approve code



Diff comments:

> === modified file 'buildout.cfg'
> --- buildout.cfg	2013-06-03 06:30:28 +0000
> +++ buildout.cfg	2015-01-13 16:03:26 +0000
> @@ -9,6 +9,7 @@
>      iharness
>      i18n
>      txlongpoll
> +    txpkgupload
>  unzip = true
>  eggs-directory = eggs
>  download-cache = download-cache
> @@ -97,3 +98,13 @@
>  initialization = ${scripts:initialization}
>  entry-points = twistd-for-txlongpoll=twisted.scripts.twistd:run
>  scripts = twistd-for-txlongpoll
> +
> +[txpkgupload]
> +recipe = z3c.recipe.scripts
> +eggs = ${scripts:eggs}
> +    txpkgupload
> +include-site-packages = false
> +allowed-eggs-from-site-packages =

Is overriding allowed-eggs-from-site-packages's default value of "*" useful when also unsetting include-site-packages? txlongpoll does it too, but I'm not sure there's any point.

> +initialization = ${scripts:initialization}
> +entry-points = twistd-for-txpkgupload=twisted.scripts.twistd:run
> +scripts = twistd-for-txpkgupload
> 
> === renamed file 'lib/lp/poppy/tests/poppy-sftp' => 'configs/development/txpkgupload-sftp'
> === renamed file 'lib/lp/poppy/tests/poppy-sftp.pub' => 'configs/development/txpkgupload-sftp.pub'
> === added file 'configs/development/txpkgupload.yaml'
> --- configs/development/txpkgupload.yaml	1970-01-01 00:00:00 +0000
> +++ configs/development/txpkgupload.yaml	2015-01-13 16:03:26 +0000
> @@ -0,0 +1,47 @@
> +##
> +## txpkgupload configuration.
> +##
> +
> +## The FTP service.
> +#
> +ftp:
> +  ## The port to run the FTP server on.
> +  port: 2121
> +
> +## The SFTP service.
> +#
> +sftp:
> +  ## The URL of the XML-RPC endpoint that handles authentication of SSH
> +  ## users.
> +  authentication_endpoint: "http://xmlrpc-private.launchpad.dev:8087/authserver";
> +  ## The absolute path to the private key used for the SFTP server.
> +  host_key_private: "configs/development/txpkgupload-sftp"
> +  ## The absolute path to the public key used for the SFTP server.
> +  host_key_public: "configs/development/txpkgupload-sftp.pub"
> +  ## An announcement printed to users when they connect.
> +  # banner: "hello"
> +  ## The port to run the SFTP server on, expressed in Twisted's "strports"
> +  ## mini-language.
> +  port: "tcp:5023"
> +
> +## OOPS configuration.
> +#
> +oops:
> +  ## Directory in which to place OOPS reports.
> +  # directory: ""
> +  ## The reporter used when generating OOPS reports.
> +  # reporter: "PKGUPLOAD"
> +
> +## The access log location.  Information such as connection, SSH login and
> +## session start times will be logged here.
> +access_log: "/tmp/txpkgupload-access.log"
> +
> +## Connections that are idle for more than this many seconds are
> +## disconnected.
> +# idle_timeout: 3600
> +
> +## Where on the filesystem do uploads live?
> +fsroot: "/var/tmp/txpkgupload/incoming"
> +
> +# If true, enable additional debug logging.
> +# debug: false
> 
> === removed file 'daemons/poppy-sftp.tac'
> --- daemons/poppy-sftp.tac	2015-01-12 18:53:31 +0000
> +++ daemons/poppy-sftp.tac	1970-01-01 00:00:00 +0000
> @@ -1,121 +0,0 @@
> -# Copyright 2010 Canonical Ltd.  This software is licensed under the
> -# GNU Affero General Public License version 3 (see the file LICENSE).
> -
> -# This is a Twisted application config file.  To run, use:
> -#     twistd -noy sftp.tac
> -# or similar.  Refer to the twistd(1) man page for details.
> -
> -import logging
> -
> -from lazr.sshserver.auth import (
> -    LaunchpadAvatar,
> -    PublicKeyFromLaunchpadChecker,
> -    )
> -from lazr.sshserver.service import SSHService
> -from lazr.sshserver.session import DoNothingSession
> -
> -from twisted.application import service
> -from twisted.conch.interfaces import ISession
> -from twisted.conch.ssh import filetransfer
> -from twisted.cred.portal import IRealm, Portal
> -from twisted.protocols.policies import TimeoutFactory
> -from twisted.python import components
> -from twisted.scripts.twistd import ServerOptions
> -from twisted.web.xmlrpc import Proxy
> -
> -from zope.interface import implements
> -
> -from lp.services.config import config
> -from lp.services.daemons import readyservice
> -
> -from lp.poppy import get_poppy_root
> -from lp.poppy.twistedftp import (
> -    FTPServiceFactory,
> -    )
> -from lp.poppy.twistedsftp import SFTPServer
> -from lp.services.twistedsupport.loggingsupport import set_up_oops_reporting
> -
> -
> -def make_portal():
> -    """Create and return a `Portal` for the SSH service.
> -
> -    This portal accepts SSH credentials and returns our customized SSH
> -    avatars (see `LaunchpadAvatar`).
> -    """
> -    authentication_proxy = Proxy(
> -        config.poppy.authentication_endpoint)
> -    portal = Portal(Realm(authentication_proxy))
> -    portal.registerChecker(
> -        PublicKeyFromLaunchpadChecker(authentication_proxy))
> -    return portal
> -
> -
> -class Realm:
> -    implements(IRealm)
> -
> -    def __init__(self, authentication_proxy):
> -        self.authentication_proxy = authentication_proxy
> -
> -    def requestAvatar(self, avatar_id, mind, *interfaces):
> -        # Fetch the user's details from the authserver
> -        deferred = mind.lookupUserDetails(
> -            self.authentication_proxy, avatar_id)
> -
> -        # Once all those details are retrieved, we can construct the avatar.
> -        def got_user_dict(user_dict):
> -            avatar = LaunchpadAvatar(user_dict)
> -            return interfaces[0], avatar, avatar.logout
> -
> -        return deferred.addCallback(got_user_dict)
> -
> -
> -def poppy_sftp_adapter(avatar):
> -    return SFTPServer(avatar, get_poppy_root())
> -
> -
> -# Force python logging to all go to the Twisted log.msg interface. The default
> -# - output on stderr - will not be watched by anyone.
> -from twisted.python import log
> -stream = log.StdioOnnaStick()
> -logging.basicConfig(stream=stream, level=logging.INFO)
> -
> -
> -components.registerAdapter(
> -    poppy_sftp_adapter, LaunchpadAvatar, filetransfer.ISFTPServer)
> -
> -components.registerAdapter(DoNothingSession, LaunchpadAvatar, ISession)
> -
> -
> -# ftpport defaults to 2121 in schema-lazr.conf
> -ftpservice = FTPServiceFactory.makeFTPService(port=config.poppy.ftp_port)
> -
> -# Construct an Application that has the Poppy SSH server,
> -# and the Poppy FTP server.
> -options = ServerOptions()
> -options.parseOptions()
> -application = service.Application('poppy-sftp')
> -observer = set_up_oops_reporting(
> -    'poppy-sftp', 'poppy', options.get('logfile'))
> -application.addComponent(observer, ignoreClass=1)
> -
> -ftpservice.setServiceParent(application)
> -
> -
> -def timeout_decorator(factory):
> -    """Add idle timeouts to a factory."""
> -    return TimeoutFactory(factory, timeoutPeriod=config.poppy.idle_timeout)
> -
> -svc = SSHService(
> -    portal=make_portal(),
> -    private_key_path=config.poppy.host_key_private,
> -    public_key_path=config.poppy.host_key_public,
> -    main_log='poppy',
> -    access_log='poppy.access',
> -    access_log_path=config.poppy.access_log,
> -    strport=config.poppy.port,
> -    factory_decorator=timeout_decorator,
> -    banner=config.poppy.banner)
> -svc.setServiceParent(application)
> -
> -# Service that announces when the daemon is ready
> -readyservice.ReadyService().setServiceParent(application)
> 
> === removed directory 'lib/lp/poppy'
> === removed file 'lib/lp/poppy/__init__.py'
> --- lib/lp/poppy/__init__.py	2011-12-29 05:29:36 +0000
> +++ lib/lp/poppy/__init__.py	1970-01-01 00:00:00 +0000
> @@ -1,20 +0,0 @@
> -# Copyright 2009-2011 Canonical Ltd.  This software is licensed under the
> -# GNU Affero General Public License version 3 (see the file LICENSE).
> -
> -# Make this directory into a Python package.
> -
> -import os
> -
> -from lp.services.config import config
> -
> -
> -def get_poppy_root():
> -    """Return the poppy root to use for this server.
> -
> -    If the POPPY_ROOT environment variable is set, use that. If not, use
> -    config.poppy.fsroot.
> -    """
> -    poppy_root = os.environ.get('POPPY_ROOT', None)
> -    if poppy_root:
> -        return poppy_root
> -    return config.poppy.fsroot
> 
> === removed file 'lib/lp/poppy/filesystem.py'
> --- lib/lp/poppy/filesystem.py	2010-08-20 20:31:18 +0000
> +++ lib/lp/poppy/filesystem.py	1970-01-01 00:00:00 +0000
> @@ -1,248 +0,0 @@
> -# Copyright 2009 Canonical Ltd.  This software is licensed under the
> -# GNU Affero General Public License version 3 (see the file LICENSE).
> -
> -__metaclass__ = type
> -__all__ = [
> -    'UploadFileSystem',
> -    ]
> -
> -import datetime
> -import os
> -import stat
> -
> -from zope.interface import implements
> -from zope.security.interfaces import Unauthorized
> -from zope.server.interfaces.ftp import IFileSystem
> -
> -
> -class UploadFileSystem:
> -
> -    implements(IFileSystem)
> -
> -    def __init__(self, rootpath):
> -        self.rootpath = rootpath
> -
> -    def _full(self, path):
> -        """Returns the full path name (i.e. rootpath + path)"""
> -        full_path = os.path.join(self.rootpath, path)
> -        if not os.path.realpath(full_path).startswith(self.rootpath):
> -            raise OSError("Path not allowed:", path)
> -        return full_path
> -
> -    def _sanitize(self, path):
> -        if path.startswith('/'):
> -            path = path[1:]
> -        path = os.path.normpath(path)
> -        return path
> -
> -    def type(self, path):
> -        """Return the file type at path
> -
> -        The 'type' command returns 'f' for a file, 'd' for a directory and
> -        None if there is no file.
> -        """
> -        path = self._sanitize(path)
> -        full_path = self._full(path)
> -        if os.path.exists(full_path):
> -            if os.path.isdir(full_path):
> -                return 'd'
> -            elif os.path.isfile(full_path):
> -                return 'f'
> -
> -    def names(self, path, filter=None):
> -        """Return a sequence of the names in a directory
> -
> -        If the filter is not None, include only those names for which
> -        the filter returns a true value.
> -        """
> -        path = self._sanitize(path)
> -        full_path = self._full(path)
> -        if not os.path.exists(full_path):
> -            raise OSError("Not exists:", path)
> -        filenames = os.listdir(os.path.join(self.rootpath, path))
> -        files = []
> -        for filename in filenames:
> -            if not filter or filter(filename):
> -                files.append(filename)
> -        return files
> -
> -    def ls(self, path, filter=None):
> -        """Return a sequence of information objects.
> -
> -        It considers the names in the given path (returned self.name())
> -        and builds file information using self.lsinfo().
> -        """
> -        return [self.lsinfo(name) for name in self.names(path, filter)]
> -
> -    def readfile(self, path, outstream, start=0, end=None):
> -        """Outputs the file at path to a stream.
> -
> -        Not allowed - see filesystem.txt.
> -        """
> -        raise Unauthorized
> -
> -    def lsinfo(self, path):
> -        """Return information for a unix-style ls listing for the path
> -
> -        See zope3's interfaces/ftp.py:IFileSystem for details of the
> -        dictionary's content.
> -        """
> -        path = self._sanitize(path)
> -        full_path = self._full(path)
> -        if not os.path.exists(full_path):
> -            raise OSError("Not exists:", path)
> -
> -        info = {"owner_name": "upload",
> -                "group_name": "upload",
> -                "name": path.split("/")[-1]}
> -
> -        s = os.stat(full_path)
> -
> -        info["owner_readable"] = bool(s.st_mode & stat.S_IRUSR)
> -        info["owner_writable"] = bool(s.st_mode & stat.S_IWUSR)
> -        info["owner_executable"] = bool(s.st_mode & stat.S_IXUSR)
> -        info["group_readable"] = bool(s.st_mode & stat.S_IRGRP)
> -        info["group_writable"] = bool(s.st_mode & stat.S_IWGRP)
> -        info["group_executable"] = bool(s.st_mode & stat.S_IXGRP)
> -        info["other_readable"] = bool(s.st_mode & stat.S_IROTH)
> -        info["other_writable"] = bool(s.st_mode & stat.S_IWOTH)
> -        info["other_executable"] = bool(s.st_mode & stat.S_IXOTH)
> -        info["mtime"] = datetime.datetime.fromtimestamp(self.mtime(path))
> -        info["size"] = self.size(path)
> -        info["type"] = self.type(path)
> -        info["nlinks"] = s.st_nlink
> -        return info
> -
> -    def mtime(self, path):
> -        """Return the modification time for the file"""
> -        path = self._sanitize(path)
> -        full_path = self._full(path)
> -        if os.path.exists(full_path):
> -            return os.path.getmtime(full_path)
> -
> -    def size(self, path):
> -        """Return the size of the file at path"""
> -        path = self._sanitize(path)
> -        full_path = self._full(path)
> -        if os.path.exists(full_path):
> -            return os.path.getsize(full_path)
> -
> -    def mkdir(self, path):
> -        """Create a directory."""
> -        path = self._sanitize(path)
> -        full_path = self._full(path)
> -        if os.path.exists(full_path):
> -            if os.path.isfile(full_path):
> -                raise OSError("File already exists:", path)
> -            elif os.path.isdir(full_path):
> -                raise OSError("Directory already exists:", path)
> -            raise OSError("OOPS, can't create:", path)
> -        else:
> -            old_mask = os.umask(0)
> -            try:
> -                os.makedirs(full_path, 0775)
> -            finally:
> -                os.umask(old_mask)
> -
> -    def remove(self, path):
> -        """Remove a file."""
> -        path = self._sanitize(path)
> -        full_path = self._full(path)
> -        if os.path.exists(full_path):
> -            if os.path.isfile(full_path):
> -                os.unlink(full_path)
> -            elif os.path.isdir(full_path):
> -                raise OSError("Is a directory:", path)
> -        else:
> -            raise OSError("Not exists:", path)
> -
> -    def rmdir(self, path):
> -        """Remove a directory.
> -
> -        Remove a target path recursively.
> -        """
> -        path = self._sanitize(path)
> -        full_path = self._full(path)
> -        if os.path.exists(full_path):
> -            os.rmdir(full_path)
> -        else:
> -            raise OSError("Not exists:", path)
> -
> -    def rename(self, old, new):
> -        """Rename a file."""
> -        old = self._sanitize(old)
> -        new = self._sanitize(new)
> -        full_old = self._full(old)
> -        full_new = self._full(new)
> -
> -        if os.path.isdir(full_new):
> -            raise OSError("Is a directory:", new)
> -
> -        if os.path.exists(full_old):
> -            if os.path.isfile(full_old):
> -                os.rename(full_old, full_new)
> -            elif os.path.isdir(full_old):
> -                raise OSError("Is a directory:", old)
> -        else:
> -            raise OSError("Not exists:", old)
> -
> -    def writefile(self, path, instream, start=None, end=None, append=False):
> -        """Write data to a file.
> -
> -        See zope3's interfaces/ftp.py:IFileSystem for details of the
> -        handling of the various arguments.
> -        """
> -        path = self._sanitize(path)
> -        full_path = self._full(path)
> -        if os.path.exists(full_path):
> -            if os.path.isdir(full_path):
> -                raise OSError("Is a directory:", path)
> -        else:
> -            dirname = os.path.dirname(full_path)
> -            if dirname:
> -                if not os.path.exists(dirname):
> -                    old_mask = os.umask(0)
> -                    try:
> -                        os.makedirs(dirname, 0775)
> -                    finally:
> -                        os.umask(old_mask)
> -
> -        if start and start < 0:
> -            raise ValueError("Negative start argument:", start)
> -        if end and end < 0:
> -            raise ValueError("Negative end argument:", end)
> -        if start and end and end <= start:
> -            return
> -        if append:
> -            open_flag = 'a'
> -        elif start or end:
> -            open_flag = "r+"
> -            if not os.path.exists(full_path):
> -                open(full_path, 'w')
> -
> -        else:
> -            open_flag = 'w'
> -        outstream = open(full_path, open_flag)
> -        if start:
> -            outstream.seek(start)
> -        chunk = instream.read()
> -        while chunk:
> -            outstream.write(chunk)
> -            chunk = instream.read()
> -        if not end:
> -            outstream.truncate()
> -        instream.close()
> -        outstream.close()
> -
> -    def writable(self, path):
> -        """Return boolean indicating whether a file at path is writable."""
> -        path = self._sanitize(path)
> -        full_path = self._full(path)
> -        if os.path.exists(full_path):
> -            if os.path.isfile(full_path):
> -                return True
> -            elif os.path.isdir(full_path):
> -                return False
> -        else:
> -            return True
> -
> 
> === removed file 'lib/lp/poppy/hooks.py'
> --- lib/lp/poppy/hooks.py	2012-06-29 08:40:05 +0000
> +++ lib/lp/poppy/hooks.py	1970-01-01 00:00:00 +0000
> @@ -1,164 +0,0 @@
> -# Copyright 2009-2010 Canonical Ltd.  This software is licensed under the
> -# GNU Affero General Public License version 3 (see the file LICENSE).
> -
> -__metaclass__ = type
> -
> -__all__ = [
> -    'Hooks',
> -    'PoppyInterfaceFailure',
> -    ]
> -
> -
> -import logging
> -import os
> -import shutil
> -import stat
> -import time
> -
> -from contrib.glock import GlobalLock
> -
> -
> -class PoppyInterfaceFailure(Exception):
> -    pass
> -
> -
> -class Hooks:
> -
> -    clients = {}
> -    LOG_MAGIC = "Post-processing finished"
> -    _targetcount = 0
> -
> -    def __init__(self, targetpath, logger, allow_user, cmd=None,
> -                 targetstart=0, perms=None, prefix=''):
> -        self.targetpath = targetpath
> -        self.logger = logging.getLogger("%s.Hooks" % logger.name)
> -        self.cmd = cmd
> -        self.allow_user = allow_user
> -        self.perms = perms
> -        self.prefix = prefix
> -
> -    @property
> -    def targetcount(self):
> -        """A guaranteed unique integer for ensuring unique upload dirs."""
> -        Hooks._targetcount += 1
> -        return Hooks._targetcount
> -
> -    def new_client_hook(self, fsroot, host, port):
> -        """Prepare a new client record indexed by fsroot..."""
> -        self.clients[fsroot] = {
> -            "host": host,
> -            "port": port
> -            }
> -        self.logger.debug("Accepting new session in fsroot: %s" % fsroot)
> -        self.logger.debug("Session from %s:%s" % (host, port))
> -
> -    def client_done_hook(self, fsroot, host, port):
> -        """A client has completed. If it authenticated then it stands a chance
> -        of having uploaded a file to the set. If not; then it is simply an
> -        aborted transaction and we remove the fsroot."""
> -
> -        if fsroot not in self.clients:
> -            raise PoppyInterfaceFailure("Unable to find fsroot in client set")
> -
> -        self.logger.debug("Processing session complete in %s" % fsroot)
> -
> -        client = self.clients[fsroot]
> -        if "distro" not in client:
> -            # Login username defines the distribution context of the upload.
> -            # So abort unauthenticated sessions by removing its contents
> -            shutil.rmtree(fsroot)
> -            return
> -
> -        # Protect from race condition between creating the directory
> -        # and creating the distro file, and also in cases where the
> -        # temporary directory and the upload directory are not in the
> -        # same filesystem (non-atomic "rename").
> -        lockfile_path = os.path.join(self.targetpath, ".lock")
> -        self.lock = GlobalLock(lockfile_path)
> -
> -        # XXX cprov 20071024 bug=156795: We try to acquire the lock as soon
> -        # as possible after creating the lockfile but are still open to
> -        # a race.
> -        self.lock.acquire(blocking=True)
> -        mode = stat.S_IMODE(os.stat(lockfile_path).st_mode)
> -
> -        # XXX cprov 20081024 bug=185731: The lockfile permission can only be
> -        # changed by its owner. Since we can't predict which process will
> -        # create it in production systems we simply ignore errors when trying
> -        # to grant the right permission. At least, one of the process will
> -        # be able to do so.
> -        try:
> -            os.chmod(lockfile_path, mode | stat.S_IWGRP)
> -        except OSError:
> -            pass
> -
> -        try:
> -            timestamp = time.strftime("%Y%m%d-%H%M%S")
> -            path = "upload%s-%s-%06d" % (
> -                self.prefix, timestamp, self.targetcount)
> -            target_fsroot = os.path.join(self.targetpath, path)
> -
> -            # Create file to store the distro used.
> -            self.logger.debug("Upload was targetted at %s" % client["distro"])
> -            distro_filename = target_fsroot + ".distro"
> -            distro_file = open(distro_filename, "w")
> -            distro_file.write(client["distro"])
> -            distro_file.close()
> -
> -            # Move the session directory to the target directory.
> -            if os.path.exists(target_fsroot):
> -                self.logger.warn("Targeted upload already present: %s" % path)
> -                self.logger.warn("System clock skewed ?")
> -            else:
> -                try:
> -                    shutil.move(fsroot, target_fsroot)
> -                except (OSError, IOError):
> -                    if not os.path.exists(target_fsroot):
> -                        raise
> -
> -            # XXX cprov 20071024: We should replace os.system call by os.chmod
> -            # and fix the default permission value accordingly in poppy-upload
> -            if self.perms is not None:
> -                os.system("chmod %s -R %s" % (self.perms, target_fsroot))
> -
> -            # Invoke processing script, if provided.
> -            if self.cmd:
> -                cmd = self.cmd
> -                cmd = cmd.replace("@fsroot@", target_fsroot)
> -                cmd = cmd.replace("@distro@", client["distro"])
> -                self.logger.debug("Running upload handler: %s" % cmd)
> -                os.system(cmd)
> -        finally:
> -            # We never delete the lockfile, this way the inode will be
> -            # constant while the machine is up. See comment on 'acquire'
> -            self.lock.release(skip_delete=True)
> -
> -        self.clients.pop(fsroot)
> -        # This is mainly done so that tests know when the
> -        # post-processing hook has finished.
> -        self.logger.info(self.LOG_MAGIC)
> -
> -    def auth_verify_hook(self, fsroot, user, password):
> -        """Verify that the username matches a distribution we care about.
> -
> -        The password is irrelevant to auth, as is the fsroot"""
> -        if fsroot not in self.clients:
> -            raise PoppyInterfaceFailure("Unable to find fsroot in client set")
> -
> -        # local authentication
> -        self.clients[fsroot]["distro"] = self.allow_user
> -        return True
> -
> -        # When we get on with the poppy path stuff, the below may be useful
> -        # and is thus left in rather than being removed.
> -
> -        #try:
> -        #    d = Distribution.byName(user)
> -        #    if d:
> -        #        self.logger.debug("Accepting login for %s" % user)
> -        #        self.clients[fsroot]["distro"] = user
> -        #        return True
> -        #except object as e:
> -        #    print e
> -        #return False
> -
> 
> === removed directory 'lib/lp/poppy/tests'
> === removed file 'lib/lp/poppy/tests/__init__.py'
> --- lib/lp/poppy/tests/__init__.py	2010-03-18 10:15:34 +0000
> +++ lib/lp/poppy/tests/__init__.py	1970-01-01 00:00:00 +0000
> @@ -1,5 +0,0 @@
> -# Copyright 2010 Canonical Ltd.  This software is licensed under the
> -# GNU Affero General Public License version 3 (see the file LICENSE).
> -
> -"""Tests for Poppy."""
> -
> 
> === removed file 'lib/lp/poppy/tests/filesystem.txt'
> --- lib/lp/poppy/tests/filesystem.txt	2010-03-17 11:04:20 +0000
> +++ lib/lp/poppy/tests/filesystem.txt	1970-01-01 00:00:00 +0000
> @@ -1,607 +0,0 @@
> -
> -This is an implementation of IFileSystem which the FTP Server in Zope3
> -uses to know what to do when people make FTP commands.
> -
> -    >>> from lp.poppy.filesystem import UploadFileSystem
> -
> -The UploadFileSystem class implements the interface IFileSystem.
> -
> -    >>> from zope.server.interfaces.ftp import IFileSystem
> -    >>> IFileSystem.implementedBy(UploadFileSystem)
> -    True
> -
> -First we need to setup our test environment.
> -
> -    >>> import os
> -    >>> import shutil
> -    >>> import tempfile
> -    >>> rootpath = tempfile.mkdtemp()
> -
> -    >>> testfile = "testfile"
> -    >>> full_testfile = os.path.join(rootpath, testfile)
> -    >>> testfile_contents = "contents of the file"
> -    >>> open(full_testfile, 'w').write(testfile_contents)
> -
> -    >>> testdir = "testdir"
> -    >>> full_testdir = os.path.join(rootpath, testdir)
> -    >>> os.mkdir(full_testdir)
> -    >>> propaganda = """
> -    ...    GNU is aimed initially at machines in the 68000/16000 class with
> -    ... virtual memory, because they are the easiest machines to make it run
> -    ... on.  The extra effort to make it run on smaller machines will be left
> -    ... to someone who wants to use it on them.
> -    ... """
> -
> -When you create an UploadFileSystem you pass it a directory location
> -to use.
> -
> -    >>> ufs = UploadFileSystem(rootpath)
> -
> -An UploadFileSystem object provides the interface IFileSystem.
> -
> -    >>> from zope.interface.verify import verifyObject
> -    >>> verifyObject(IFileSystem, ufs)
> -    True
> -
> -mkdir
> -=====
> -
> -"mkdir" should work as expected, directory will be created as
> -requested by the clients:
> -
> -    >>> ufs.mkdir("anything")
> -    >>> os.path.exists(os.path.join(rootpath, "anything"))
> -    True
> -
> -    >>> os.rmdir(os.path.join(rootpath, "anything"))
> -
> -It recursively creates directories:
> -
> -    >>> ufs.mkdir("anything/something/whatever")
> -
> -    >>> wanted_path = os.path.join(rootpath, "anything/something/whatever")
> -
> -    >>> os.path.exists(wanted_path)
> -    True
> -
> -    >>> oct(os.stat(wanted_path).st_mode)
> -    '040775'
> -
> -    >>> shutil.rmtree(os.path.join(rootpath, "anything"))
> -
> -rmdir
> -=====
> -
> -Check if it complains on removal request of an existent dir
> -
> -    >>> ufs.rmdir("does-not-exist")
> -    Traceback (most recent call last):
> -    ...
> -    OSError: [Errno Not exists:] does-not-exist
> -
> -Check if it works as expected after the directory creation:
> -
> -    >>> ufs.mkdir("new-dir")
> -    >>> ufs.rmdir("new-dir")
> -
> -    >>> os.path.exists(os.path.join(rootpath, "new-dir"))
> -    False
> -
> -
> -lsinfo
> -======
> -
> -Return information for a unix-style ls listing for the path.
> -
> -See zope3's interfaces/ftp.py:IFileSystem for details of the
> -dictionary's content.
> -
> -Setup a default dictionary used for generating the dictionaries we
> -expect lsinfo to return.
> -
> -    >>> def clean_mtime(stat_info):
> -    ...     """Return a datetime from an mtime, sans microseconds."""
> -    ...     mtime = stat_info.st_mtime
> -    ...     datestamp = datetime.datetime.fromtimestamp(mtime)
> -    ...     datestamp.replace(microsecond=0)
> -    ...     return datestamp
> -
> -    >>> import copy
> -    >>> import datetime
> -    >>> import stat
> -    >>> def_exp = {"type": 'f', 
> -    ...     "owner_name": "upload",
> -    ...     "owner_readable": True,
> -    ...     "owner_writable": True,
> -    ...	    "owner_executable": False,
> -    ...	    "group_name": "upload",
> -    ...	    "group_readable": True,
> -    ...     "group_writable": False,
> -    ...	    "group_executable": False,
> -    ...	    "other_readable": True,
> -    ...     "other_writable": False,
> -    ...	    "other_executable": False,
> -    ...     "nlinks": 1}
> -    ...
> -
> -    >>> os.chmod(full_testfile, stat.S_IRUSR | stat.S_IWUSR | \
> -    ...                         stat.S_IRGRP | stat.S_IROTH)
> -    >>> exp = copy.copy(def_exp)
> -    >>> s = os.stat(full_testfile)
> -    >>> exp["name"] = testfile
> -    >>> exp["mtime"] = clean_mtime(s)
> -    >>> exp["size"] = s[stat.ST_SIZE]
> -    >>> info = ufs.lsinfo(testfile)
> -    >>> info == exp
> -    True
> -
> -ls
> -==
> -
> -`ls` a sequence of item info objects (see ls_info) for the files in a
> -directory.
> -
> -    >>> expected = [exp]
> -    >>> for i in [ "foo", "bar" ]:
> -    ...     filename = os.path.join(rootpath, i)
> -    ...     x = open(filename, 'w')
> -    ...     os.chmod(filename, stat.S_IRUSR | stat.S_IWUSR | \
> -    ...                         stat.S_IRGRP | stat.S_IROTH)
> -    ...     exp = copy.copy(def_exp)
> -    ...	    s = os.stat(filename)
> -    ...	    exp["name"] = i
> -    ...	    exp["mtime"] = clean_mtime(s)
> -    ...     exp["size"] = s[stat.ST_SIZE]
> -    ...     expected.append(exp)
> -    ...
> -
> -    >>> dir_exp = copy.copy(def_exp)
> -    >>> s = os.stat(full_testdir)
> -    >>> dir_exp["type"] = "d"
> -    >>> dir_exp["name"] = testdir
> -    >>> dir_exp["mtime"] = clean_mtime(s)
> -    >>> dir_exp["size"] = s[stat.ST_SIZE]
> -    >>> dir_exp["nlinks"] = s[stat.ST_NLINK]
> -    >>> dir_exp["owner_executable"] = True
> -    >>> dir_exp["other_executable"] = True
> -    >>> dir_exp["group_executable"] = True
> -    >>> expected.append(dir_exp)
> -
> -We need a helper function to turn the returned and expected data into reliably
> -sorted orders for comparison.
> -
> -    >>> from operator import itemgetter
> -    >>> def sorted_listings(ls_infos):
> -    ...     # ls_infos will be a sequence of dictionaries.  They need to be
> -    ...     # sorted for the sequences to compare equal, so do that on the
> -    ...     # dictionary's 'name' key.  The equality test used here
> -    ...     # doesn't care about the sort order of the dictionaries.
> -    ...     return sorted(ls_infos, key=itemgetter('name'))
> -
> -    >>> expected.sort()
> -    >>> returned = ufs.ls(".")
> -    >>> returned.sort()
> -    >>> sorted_listings(expected) == sorted_listings(returned)
> -    True
> -
> -If `filter` is not None, include only those names for which `filter`
> -returns a true value.
> -
> -    >>> def always_false_filter(name):
> -    ...     return False
> -    >>> def always_true_filter(name):
> -    ...     return True
> -    >>> def arbitrary_filter(name):
> -    ...    if name == "foo" or name == "baz":
> -    ...        return True
> -    ...    else:
> -    ...        return False
> -    ...
> -    >>> for i in expected:
> -    ...     if i["name"] == "foo":
> -    ...         filtered_expected = [i];
> -    >>> returned = ufs.ls(".", always_true_filter)
> -    >>> returned.sort()
> -    >>> sorted_listings(expected) == sorted_listings(returned)
> -    True
> -    >>> returned = ufs.ls(".", always_false_filter)
> -    >>> returned.sort()
> -    >>> returned == []
> -    True
> -    >>> returned = ufs.ls(".", arbitrary_filter)
> -    >>> returned.sort()
> -    >>> sorted_listings(filtered_expected) == sorted_listings(returned)
> -    True
> -    >>> for i in [ "foo", "bar" ]:
> -    ...     ufs.remove(i)
> -    ...
> -
> -readfile
> -========
> -
> -We are not implementing `readfile` as a precautionary measure, i.e. in
> -case anyone bypasses the per-session separate directories they still
> -aren't able to read any other files and therefore can't abuse the
> -server for warez/child porn etc.
> -
> -Unlike `mkdir` and `rmdir` we will raise an exception so that the
> -server returns an error to the client and the client does not receive
> -bogus or empty data.
> -
> -    >>> ufs.readfile(testfile, None)
> -    Traceback (most recent call last):
> -    ...
> -    Unauthorized
> -
> -The 'type' command returns 'f' for a file, 'd' for a directory and
> -None if there is no file.
> - 
> -    >>> ufs.type(testfile)
> -    'f'
> -    
> -    >>> ufs.type(testdir)
> -    'd'
> -    
> -    >>> ufs.type("does-not-exist") is None
> -    True
> -
> -size
> -====
> -
> -The 'size' command returns the size of the file.  If the file does not
> -exist None is returned.
> -
> -    >>> ufs.size("does-not-exist") is None
> -    True
> -    
> -    >>> ufs.size(testfile) == os.path.getsize(full_testfile)
> -    True
> -    
> -    >>> ufs.size(testdir) == os.path.getsize(full_testdir)
> -    True
> -
> -mtime
> -=====
> -
> -The 'mtime' command returns the mtime of the file.  If the file does not
> -exist None is returned.
> -
> -    >>> ufs.size("does-not-exist") is None
> -    True
> -    
> -    >>> ufs.mtime(testfile) == os.path.getmtime(full_testfile)
> -    True
> -    
> -    >>> ufs.mtime(testdir) == os.path.getmtime(full_testdir)
> -    True
> -
> -remove
> -======
> -
> -The 'remove' command removes a file.  An exception is raised if the
> -file does not exist or is a directory.
> -
> -    >>> ufs.remove("does-not-exist")
> -    Traceback (most recent call last):
> -    ...
> -    OSError: [Errno Not exists:] does-not-exist
> -    
> -    >>> ufs.remove(testfile)
> -    >>> os.path.exists(full_testfile)
> -    False
> -    >>> open(full_testfile, 'w').write("contents of the file")
> -    
> -    >>> ufs.remove(testdir)
> -    Traceback (most recent call last):
> -    ...
> -    OSError: [Errno Is a directory:] testdir
> -
> -rename
> -======
> -
> -The 'rename' command renames a file.  An exception is raised if the
> -old filename doesn't exist or if the old or new filename is a
> -directory.
> -
> -    >>> new_testfile = "baz"
> -    >>> new_full_testfile = os.path.join(rootpath, new_testfile)
> -    
> -    >>> ufs.rename("does-not-exist", new_testfile)
> -    Traceback (most recent call last):
> -    ...
> -    OSError: [Errno Not exists:] does-not-exist
> -
> -    >>> new_testfile = "baz"
> -    >>> new_full_testfile = os.path.join(rootpath, new_testfile)
> -    >>> ufs.rename(testfile, new_testfile)
> -    >>> os.path.exists(full_testfile)
> -    False
> -    >>> os.path.exists(new_full_testfile)
> -    True
> -    >>> open(new_full_testfile).read() == testfile_contents
> -    True
> -    >>> ufs.rename(new_testfile, testfile)
> -    
> -    >>> ufs.rename(testdir, new_testfile)
> -    Traceback (most recent call last):
> -    ...
> -    OSError: [Errno Is a directory:] testdir
> -    
> -    >>> ufs.rename(testfile, testdir)
> -    Traceback (most recent call last):
> -    ...
> -    OSError: [Errno Is a directory:] testdir
> -
> -names
> -=====
> -
> -The `names` command returns a sequence of the names in the `path`.
> -
> -    >>> sorted(ufs.names("."))
> -    ['testdir', 'testfile']
> -
> -`path` is normalized before used.
> -
> -    >>> sorted(ufs.names("some-directory/..")) 
> -    ['testdir', 'testfile']
> -
> -'path' under the server root is not allowed:
> -
> -    >>> ufs.names("..")
> -    Traceback (most recent call last):
> -    ...
> -    OSError: [Errno Path not allowed:] ..
> -
> -
> -If the `filter` argument is provided, each name is only returned if
> -the given `filter` function returns True for it.
> -
> -    >>> ufs.names(".", always_false_filter)
> -    []
> -
> -    >>> sorted(ufs.names(".", always_true_filter))
> -    ['testdir', 'testfile']
> -
> -    >>> for i in [ "foo", "bar", "baz", "bat" ]:
> -    ...     x = open(os.path.join(rootpath, i), 'w')
> -    >>> names = ufs.names(".", arbitrary_filter)
> -    >>> names.sort()
> -    >>> names == ['baz', 'foo']
> -    True
> -    >>> for i in [ "foo", "bar", "baz", "bat" ]:
> -    ...     os.unlink(os.path.join(rootpath, i))
> -
> -writefile
> -=========
> -
> -`writefile` writes data to a file.
> -
> -    >>> from StringIO import StringIO
> -    >>> ufs.writefile("upload", StringIO(propaganda))
> -    >>> open(os.path.join(rootpath, "upload")).read() == propaganda
> -    True
> -    >>> ufs.remove("upload")
> -
> -If neither `start` nor `end` are specified, then the file contents
> -are overwritten.
> -
> -    >>> ufs.writefile(testfile, StringIO("MOO"))
> -    >>> open(full_testfile).read() == "MOO"
> -    True
> -    >>> ufs.writefile(testfile, StringIO(testfile_contents))
> -
> -If `start` or `end` are specified, they must be non-negative.
> -
> -    >>> ufs.writefile("upload", StringIO(propaganda), -37)
> -    Traceback (most recent call last):
> -    ...
> -    ValueError: ('Negative start argument:', -37)
> -
> -    >>> ufs.writefile("upload", StringIO(propaganda), 1, -43)
> -    Traceback (most recent call last):
> -    ...
> -    ValueError: ('Negative end argument:', -43)
> -
> -If `start` or `end` is not None, then only part of the file is
> -written. The remainder of the file is unchanged.
> -
> -    >>> ufs.writefile(testfile, StringIO("MOO"), 9, 12)
> -    >>> open(full_testfile).read() == "contents MOOthe file"
> -    True
> -    >>> ufs.writefile(testfile, StringIO(testfile_contents))
> -
> -If `end` is None, then the file is truncated after the data are
> -written.  
> -
> -    >>> ufs.writefile(testfile, StringIO("MOO"), 9)
> -    >>> open(full_testfile).read() == "contents MOO"
> -    True
> -    >>> ufs.writefile(testfile, StringIO(testfile_contents))
> -
> -If `start` is specified and the file doesn't exist or is shorter
> -than start, the file will contain undefined data before start.
> -
> -    >>> ufs.writefile("didnt-exist", StringIO("MOO"), 9)
> -    >>> open(os.path.join(rootpath, "didnt-exist")).read() == "\x00\x00\x00\x00\x00\x00\x00\x00\x00MOO"
> -    True
> -    >>> ufs.remove("didnt-exist")
> -
> -If `end` is not None and there isn't enough data in `instream` to fill
> -out the file, then the missing data is undefined.
> -
> -    >>> ufs.writefile(testfile, StringIO("MOO"), 9, 15)
> -    >>> open(full_testfile).read() == "contents MOOthe file"
> -    True
> -    >>> ufs.writefile(testfile, StringIO(testfile_contents))
> -
> -If `end` is less than or the same as `start no data is writen to the file.
> -
> -    >>> ufs.writefile(testfile, StringIO("MOO"), 9, 4)
> -    >>> open(full_testfile).read() == "contents of the file"
> -    True
> -
> -    >>> ufs.writefile(testfile, StringIO("MOO"), 9, 9)
> -    >>> open(full_testfile).read() == "contents of the file"
> -    True
> -
> -If `append` is true the file is appended to rather than being
> -overwritten.
> -
> -    >>> ufs.writefile(testfile, StringIO("MOO"), append=True)
> -    >>> open(full_testfile).read() == "contents of the fileMOO"
> -    True
> -    >>> ufs.writefile(testfile, StringIO(testfile_contents))
> -
> -Additionally, if `append` is true, `start` and `end` are ignored.
> -
> -    >>> ufs.writefile(testfile, StringIO("MOO"), 10, 13, append=True)
> -    >>> open(full_testfile).read() == "contents of the fileMOO"
> -    True
> -    >>> ufs.writefile(testfile, StringIO(testfile_contents))
> -
> -'writefile' is able to create inexistent directories in a requested
> -path:
> -
> -    >>> os.path.exists(os.path.join(rootpath, "foo"))
> -    False
> -    >>> ufs.writefile("foo/bar", StringIO("fake")) is None
> -    True
> -    >>> os.path.exists(os.path.join(rootpath, "foo/bar"))
> -    True
> -    >>> open(os.path.join(rootpath, "foo/bar")).read()
> -    'fake'
> -
> -
> -writable
> -========
> -
> -`writable` returns a boolean indicating whether `path` is writable or
> -not.
> -
> -    >>> ufs.writable(testfile)
> -    True
> -
> -`writable` returns True if `path` is a non-existent file.
> -
> -    >>> ufs.writable("does-not-exist")
> -    True
> -
> -`writable` returns False if `path` is a directory as we don't allow
> -the creation of sub-directories.
> -
> -    >>> ufs.writable(testdir)
> -    False
> -
> -path checking
> -=============
> -
> -`path` arguments must be normalized.
> -
> -    >>> ufs.type(os.path.join("non-existent-dir", "..", testfile))
> -    'f'
> -
> -
> -Cleanup the server root:
> -
> -    >>> for leaf in os.listdir(rootpath):
> -    ...     full_path = os.path.join(rootpath, leaf)
> -    ...     if os.path.isdir(full_path):
> -    ...          shutil.rmtree(full_path)
> -    ...     else:
> -    ...          os.remove(full_path)
> -
> -
> -Dealing with inexistent path:
> -
> -    >>> ufs.type("foo/bar") is None
> -    True
> -    >>> ufs.mtime("foo/bar") is None
> -    True
> -    >>> ufs.size("foo/bar") is None
> -    True
> -    >>> ufs.writable("foo/bar")
> -    True
> -    >>> ufs.names("foo/bar")
> -    Traceback (most recent call last):
> -    ...
> -    OSError: [Errno Not exists:] foo/bar
> -    >>> ufs.ls("foo/bar")
> -    Traceback (most recent call last):
> -    ...
> -    OSError: [Errno Not exists:] foo/bar
> -    >>> ufs.lsinfo("foo/bar")
> -    Traceback (most recent call last):
> -    ...
> -    OSError: [Errno Not exists:] foo/bar
> -    >>> ufs.remove("foo/bar")
> -    Traceback (most recent call last):
> -    ...
> -    OSError: [Errno Not exists:] foo/bar
> -    >>> ufs.rename("foo/bar", "baz")
> -    Traceback (most recent call last):
> -    ...
> -    OSError: [Errno Not exists:] foo/bar
> -    >>> ufs.rename("baz", "foo/bar")
> -    Traceback (most recent call last):
> -    ...
> -    OSError: [Errno Not exists:] baz
> -
> -
> -Dealing with paths outside the server root directory:
> -
> -    >>> ufs.type("..")
> -    Traceback (most recent call last):
> -    ...
> -    OSError: [Errno Path not allowed:] ..
> -    >>> ufs.mtime("..")
> -    Traceback (most recent call last):
> -    ...
> -    OSError: [Errno Path not allowed:] ..
> -    >>>	ufs.size("..")
> -    Traceback (most recent call last):
> -    ...
> -    OSError: [Errno Path not allowed:] ..
> -    >>> ufs.writable("..")
> -    Traceback (most recent call last):
> -    ...
> -    OSError: [Errno Path not allowed:] ..
> -    >>> ufs.names("..")
> -    Traceback (most recent call last):
> -    ...
> -    OSError: [Errno Path not allowed:] ..
> -    >>> ufs.ls("..")
> -    Traceback (most recent call last):
> -    ...
> -    OSError: [Errno Path not allowed:] ..
> -    >>> ufs.lsinfo("..")
> -    Traceback (most recent call last):
> -    ...
> -    OSError: [Errno Path not allowed:] ..
> -    >>> ufs.remove("..")
> -    Traceback (most recent call last):
> -    ...
> -    OSError: [Errno Path not allowed:] ..
> -    >>> ufs.rename("..", "baz")
> -    Traceback (most recent call last):
> -    ...
> -    OSError: [Errno Path not allowed:] ..
> -    >>> ufs.rename("baz", "..")
> -    Traceback (most recent call last):
> -    ...
> -    OSError: [Errno Path not allowed:] ..
> -    >>> ufs.mkdir("..")
> -    Traceback (most recent call last):
> -    ...
> -    OSError: [Errno Path not allowed:] ..
> -    >>> ufs.rmdir("..")
> -    Traceback (most recent call last):
> -    ...
> -    OSError: [Errno Path not allowed:] ..
> -
> -
> -------------------------------------------------------------------------
> -
> -Finally, cleanup after ourselves.
> -
> -    >>> shutil.rmtree(rootpath)
> -
> 
> === removed file 'lib/lp/poppy/tests/test_filesystem.py'
> --- lib/lp/poppy/tests/test_filesystem.py	2011-12-22 05:09:10 +0000
> +++ lib/lp/poppy/tests/test_filesystem.py	1970-01-01 00:00:00 +0000
> @@ -1,23 +0,0 @@
> -# Copyright 2009 Canonical Ltd.  This software is licensed under the
> -# GNU Affero General Public License version 3 (see the file LICENSE).
> -
> -__metaclass__ = type
> -
> -import os
> -
> -from lp.testing.systemdocs import LayeredDocFileSuite
> -
> -# The setUp() and tearDown() functions ensure that this doctest is not umask
> -# dependent.
> -def setUp(testobj):
> -    testobj._old_umask = os.umask(022)
> -
> -
> -def tearDown(testobj):
> -    os.umask(testobj._old_umask)
> -
> -
> -def test_suite():
> -    return LayeredDocFileSuite(
> -        "filesystem.txt",
> -        setUp=setUp, tearDown=tearDown, stdout_logging=False)
> 
> === removed file 'lib/lp/poppy/tests/test_poppy.py'
> --- lib/lp/poppy/tests/test_poppy.py	2012-03-26 05:50:20 +0000
> +++ lib/lp/poppy/tests/test_poppy.py	1970-01-01 00:00:00 +0000
> @@ -1,388 +0,0 @@
> -# Copyright 2009-2011 Canonical Ltd.  This software is licensed under the
> -# GNU Affero General Public License version 3 (see the file LICENSE).
> -
> -"""Functional tests for poppy FTP daemon."""
> -
> -__metaclass__ = type
> -
> -import os
> -import shutil
> -import stat
> -import StringIO
> -import tempfile
> -import time
> -import unittest
> -
> -from bzrlib.tests import (
> -    condition_id_re,
> -    exclude_tests_by_condition,
> -    multiply_tests,
> -    )
> -from bzrlib.transport import get_transport
> -from fixtures import (
> -    EnvironmentVariableFixture,
> -    Fixture,
> -    )
> -import transaction
> -from zope.component import getUtility
> -
> -from lp.poppy.hooks import Hooks
> -from lp.registry.interfaces.ssh import ISSHKeySet
> -from lp.services.config import config
> -from lp.services.daemons.tachandler import TacTestSetup
> -from lp.testing import TestCaseWithFactory
> -from lp.testing.layers import (
> -    ZopelessAppServerLayer,
> -    ZopelessDatabaseLayer,
> -    )
> -
> -
> -class FTPServer(Fixture):
> -    """This is an abstraction of connecting to an FTP server."""
> -
> -    def __init__(self, root_dir, factory):
> -        self.root_dir = root_dir
> -        self.port = 2121
> -
> -    def setUp(self):
> -        super(FTPServer, self).setUp()
> -        self.poppytac = self.useFixture(PoppyTac(self.root_dir))
> -
> -    def getAnonTransport(self):
> -        return get_transport(
> -            'ftp://anonymous:me@xxxxxxxxxxx@localhost:%s/' % (self.port,))
> -
> -    def getTransport(self):
> -        return get_transport('ftp://ubuntu:@localhost:%s/' % (self.port,))
> -
> -    def disconnect(self, transport):
> -        transport._get_connection().close()
> -
> -    def waitForStartUp(self):
> -        """Wait for the FTP server to start up."""
> -        pass
> -
> -    def waitForClose(self, number=1):
> -        """Wait for an FTP connection to close.
> -
> -        Poppy is configured to echo 'Post-processing finished' to stdout
> -        when a connection closes, so we wait for that to appear in its
> -        output as a way to tell that the server has finished with the
> -        connection.
> -        """
> -        self.poppytac.waitForPostProcessing(number)
> -
> -
> -class SFTPServer(Fixture):
> -    """This is an abstraction of connecting to an SFTP server."""
> -
> -    def __init__(self, root_dir, factory):
> -        self.root_dir = root_dir
> -        self._factory = factory
> -        self.port = int(config.poppy.port.partition(':')[2])
> -
> -    def addSSHKey(self, person, public_key_path):
> -        f = open(public_key_path, 'r')
> -        try:
> -            public_key = f.read()
> -        finally:
> -            f.close()
> -        sshkeyset = getUtility(ISSHKeySet)
> -        key = sshkeyset.new(person, public_key)
> -        transaction.commit()
> -        return key
> -
> -    def setUpUser(self, name):
> -        user = self._factory.makePerson(name=name)
> -        self.addSSHKey(
> -            user, os.path.join(os.path.dirname(__file__), 'poppy-sftp.pub'))
> -        # Set up a temporary home directory for Paramiko's sake
> -        self._home_dir = tempfile.mkdtemp()
> -        self.addCleanup(shutil.rmtree, self._home_dir)
> -        os.mkdir(os.path.join(self._home_dir, '.ssh'))
> -        os.symlink(
> -            os.path.join(os.path.dirname(__file__), 'poppy-sftp'),
> -            os.path.join(self._home_dir, '.ssh', 'id_rsa'))
> -        self.useFixture(EnvironmentVariableFixture('HOME', self._home_dir))
> -        self.useFixture(EnvironmentVariableFixture('SSH_AUTH_SOCK', None))
> -        self.useFixture(EnvironmentVariableFixture('BZR_SSH', 'paramiko'))
> -
> -    def setUp(self):
> -        super(SFTPServer, self).setUp()
> -        self.setUpUser('joe')
> -        self.poppytac = self.useFixture(PoppyTac(self.root_dir))
> -
> -    def disconnect(self, transport):
> -        transport._get_connection().close()
> -
> -    def waitForStartUp(self):
> -        pass
> -
> -    def waitForClose(self, number=1):
> -        self.poppytac.waitForPostProcessing(number)
> -
> -    def getTransport(self):
> -        return get_transport('sftp://joe@localhost:%s/' % (self.port,))
> -
> -
> -class PoppyTac(TacTestSetup):
> -    """A SFTP Poppy server fixture.
> -
> -    This class has two distinct roots:
> -     - the POPPY_ROOT where the test looks for uploaded output.
> -     - the server root where ssh keys etc go.
> -    """
> -
> -    def __init__(self, fs_root):
> -        self.fs_root = fs_root
> -        # The setUp check for stale pids races with self._root being assigned,
> -        # so store a plausible path temporarily. Once all fixtures use unique
> -        # environments this can go.
> -        self._root = '/var/does/not/exist'
> -
> -    def setUp(self):
> -        os.environ['POPPY_ROOT'] = self.fs_root
> -        super(PoppyTac, self).setUp(umask='0')
> -
> -    def setUpRoot(self):
> -        self._root = tempfile.mkdtemp()
> -        self.addCleanup(shutil.rmtree, self.root)
> -
> -    @property
> -    def root(self):
> -        return self._root
> -
> -    @property
> -    def tacfile(self):
> -        return os.path.abspath(
> -            os.path.join(config.root, 'daemons', 'poppy-sftp.tac'))
> -
> -    @property
> -    def logfile(self):
> -        return os.path.join(self.root, 'poppy-sftp.log')
> -
> -    @property
> -    def pidfile(self):
> -        return os.path.join(self.root, 'poppy-sftp.pid')
> -
> -    def waitForPostProcessing(self, number=1):
> -        now = time.time()
> -        deadline = now + 20
> -        while now < deadline and not self._hasPostProcessed(number):
> -            time.sleep(0.1)
> -            now = time.time()
> -
> -        if now >= deadline:
> -            raise Exception("Poppy post-processing did not complete")
> -
> -    def _hasPostProcessed(self, number):
> -        if os.path.exists(self.logfile):
> -            with open(self.logfile, "r") as logfile:
> -                occurrences = logfile.read().count(Hooks.LOG_MAGIC)
> -                return occurrences >= number
> -        else:
> -            return False
> -
> -
> -class TestPoppy(TestCaseWithFactory):
> -    """Test if poppy.py daemon works properly."""
> -
> -    def setUp(self):
> -        """Set up poppy in a temp dir."""
> -        super(TestPoppy, self).setUp()
> -        self.root_dir = self.makeTemporaryDirectory()
> -        self.server = self.server_factory(self.root_dir, self.factory)
> -        self.useFixture(self.server)
> -
> -    def _uploadPath(self, path):
> -        """Return system path of specified path inside an upload.
> -
> -        Only works for a single upload (poppy transaction).
> -        """
> -        contents = sorted(os.listdir(self.root_dir))
> -        upload_dir = contents[1]
> -        return os.path.join(self.root_dir, upload_dir, path)
> -
> -    def test_change_directory_anonymous(self):
> -        # Check that FTP access with an anonymous user works.
> -        transport = self.server.getAnonTransport()
> -        self.test_change_directory(transport)
> -
> -    def test_change_directory(self, transport=None):
> -        """Check automatic creation of directories 'cwd'ed in.
> -
> -        Also ensure they are created with proper permission (g+rwxs)
> -        """
> -        self.server.waitForStartUp()
> -
> -        if transport is None:
> -            transport = self.server.getTransport()
> -        transport.stat('foo/bar')  # .stat will implicity chdir for us
> -
> -        self.server.disconnect(transport)
> -        self.server.waitForClose()
> -
> -        wanted_path = self._uploadPath('foo/bar')
> -        self.assertTrue(os.path.exists(wanted_path))
> -        self.assertEqual(os.stat(wanted_path).st_mode, 042775)
> -
> -    def test_mkdir(self):
> -        # Creating directories on the server makes actual directories where we
> -        # expect them, and creates them with g+rwxs
> -        self.server.waitForStartUp()
> -
> -        transport = self.server.getTransport()
> -        transport.mkdir('foo/bar', mode=None)
> -
> -        self.server.disconnect(transport)
> -        self.server.waitForClose()
> -
> -        wanted_path = self._uploadPath('foo/bar')
> -        self.assertTrue(os.path.exists(wanted_path))
> -        self.assertEqual(os.stat(wanted_path).st_mode, 042775)
> -
> -    def test_rmdir(self):
> -        """Check recursive RMD (aka rmdir)"""
> -        self.server.waitForStartUp()
> -
> -        transport = self.server.getTransport()
> -        transport.mkdir('foo/bar')
> -        transport.rmdir('foo/bar')
> -        transport.rmdir('foo')
> -
> -        self.server.disconnect(transport)
> -        self.server.waitForClose()
> -
> -        wanted_path = self._uploadPath('foo')
> -        self.assertFalse(os.path.exists(wanted_path))
> -
> -    def test_single_upload(self):
> -        """Check if the parent directories are created during file upload.
> -
> -        The uploaded file permissions are also special (g+rwxs).
> -        """
> -        self.server.waitForStartUp()
> -
> -        transport = self.server.getTransport()
> -        fake_file = StringIO.StringIO("fake contents")
> -
> -        transport.put_file('foo/bar/baz', fake_file, mode=None)
> -
> -        self.server.disconnect(transport)
> -        self.server.waitForClose()
> -
> -        wanted_path = self._uploadPath('foo/bar/baz')
> -        fs_content = open(os.path.join(wanted_path)).read()
> -        self.assertEqual(fs_content, "fake contents")
> -        # Expected mode is -rw-rwSr--.
> -        self.assertEqual(
> -            os.stat(wanted_path).st_mode,
> -            stat.S_IROTH | stat.S_ISGID | stat.S_IRGRP | stat.S_IWGRP
> -            | stat.S_IWUSR | stat.S_IRUSR | stat.S_IFREG)
> -
> -    def test_full_source_upload(self):
> -        """Check that the connection will deal with multiple files being
> -        uploaded.
> -        """
> -        self.server.waitForStartUp()
> -
> -        transport = self.server.getTransport()
> -
> -        files = ['test-source_0.1.dsc',
> -                 'test-source_0.1.orig.tar.gz',
> -                 'test-source_0.1.diff.gz',
> -                 'test-source_0.1_source.changes']
> -
> -        for upload in files:
> -            fake_file = StringIO.StringIO(upload)
> -            file_to_upload = "~ppa-user/ppa/ubuntu/%s" % upload
> -            transport.put_file(file_to_upload, fake_file, mode=None)
> -
> -        self.server.disconnect(transport)
> -        self.server.waitForClose()
> -
> -        upload_path = self._uploadPath('')
> -        self.assertEqual(os.stat(upload_path).st_mode, 042770)
> -        dir_name = upload_path.split('/')[-2]
> -        if transport._user == 'joe':
> -            self.assertEqual(dir_name.startswith('upload-sftp-2'), True)
> -        elif transport._user == 'ubuntu':
> -            self.assertEqual(dir_name.startswith('upload-ftp-2'), True)
> -        for upload in files:
> -            wanted_path = self._uploadPath(
> -                "~ppa-user/ppa/ubuntu/%s" % upload)
> -            fs_content = open(os.path.join(wanted_path)).read()
> -            self.assertEqual(fs_content, upload)
> -            # Expected mode is -rw-rwSr--.
> -            self.assertEqual(
> -                os.stat(wanted_path).st_mode,
> -                stat.S_IROTH | stat.S_ISGID | stat.S_IRGRP | stat.S_IWGRP
> -                | stat.S_IWUSR | stat.S_IRUSR | stat.S_IFREG)
> -
> -    def test_upload_isolation(self):
> -        """Check if poppy isolates the uploads properly.
> -
> -        Upload should be done atomically, i.e., poppy should isolate the
> -        context according each connection/session.
> -        """
> -        # Perform a pair of sessions with distinct connections in time.
> -        self.server.waitForStartUp()
> -
> -        conn_one = self.server.getTransport()
> -        fake_file = StringIO.StringIO("ONE")
> -        conn_one.put_file('test', fake_file, mode=None)
> -        self.server.disconnect(conn_one)
> -        self.server.waitForClose(1)
> -
> -        conn_two = self.server.getTransport()
> -        fake_file = StringIO.StringIO("TWO")
> -        conn_two.put_file('test', fake_file, mode=None)
> -        self.server.disconnect(conn_two)
> -        self.server.waitForClose(2)
> -
> -        # Perform a pair of sessions with simultaneous connections.
> -        conn_three = self.server.getTransport()
> -        conn_four = self.server.getTransport()
> -
> -        fake_file = StringIO.StringIO("THREE")
> -        conn_three.put_file('test', fake_file, mode=None)
> -
> -        fake_file = StringIO.StringIO("FOUR")
> -        conn_four.put_file('test', fake_file, mode=None)
> -
> -        self.server.disconnect(conn_three)
> -        self.server.waitForClose(3)
> -
> -        self.server.disconnect(conn_four)
> -        self.server.waitForClose(4)
> -
> -        # Build a list of directories representing the 4 sessions.
> -        upload_dirs = [leaf for leaf in sorted(os.listdir(self.root_dir))
> -                       if not leaf.startswith(".") and
> -                       not leaf.endswith(".distro")]
> -        self.assertEqual(len(upload_dirs), 4)
> -
> -        # Check the contents of files on each session.
> -        expected_contents = ['ONE', 'TWO', 'THREE', 'FOUR']
> -        for index in range(4):
> -            content = open(os.path.join(
> -                self.root_dir, upload_dirs[index], "test")).read()
> -            self.assertEqual(content, expected_contents[index])
> -
> -
> -def test_suite():
> -    tests = unittest.TestLoader().loadTestsFromName(__name__)
> -    scenarios = [
> -        ('ftp', {'server_factory': FTPServer,
> -                 # XXX: In an ideal world, this would be in the UnitTests
> -                 # layer. Let's get one step closer to that ideal world.
> -                 'layer': ZopelessDatabaseLayer}),
> -        ('sftp', {'server_factory': SFTPServer,
> -                  'layer': ZopelessAppServerLayer}),
> -        ]
> -    suite = unittest.TestSuite()
> -    multiply_tests(tests, scenarios, suite)
> -    # SFTP doesn't have the concept of the server changing directories, since
> -    # clients will only send absolute paths, so drop that test.
> -    return exclude_tests_by_condition(
> -        suite, condition_id_re(r'test_change_directory.*\(sftp\)$'))
> 
> === removed file 'lib/lp/poppy/tests/test_twistedsftp.py'
> --- lib/lp/poppy/tests/test_twistedsftp.py	2015-01-06 12:47:59 +0000
> +++ lib/lp/poppy/tests/test_twistedsftp.py	1970-01-01 00:00:00 +0000
> @@ -1,70 +0,0 @@
> -# Copyright 2010-2011 Canonical Ltd.  This software is licensed under the
> -# GNU Affero General Public License version 3 (see the file LICENSE).
> -
> -"""Tests for twistedsftp."""
> -
> -__metaclass__ = type
> -
> -import os
> -
> -from fixtures import TempDir
> -from lazr.sshserver.sftp import FileIsADirectory
> -
> -from lp.poppy.twistedsftp import SFTPServer
> -from lp.testing import (
> -    NestedTempfile,
> -    TestCase,
> -    )
> -
> -
> -class TestSFTPServer(TestCase):
> -
> -    def setUp(self):
> -        self.useFixture(NestedTempfile())
> -        self.fs_root = self.useFixture(TempDir()).path
> -        self.sftp_server = SFTPServer(None, self.fs_root)
> -        super(TestSFTPServer, self).setUp()
> -
> -    def assertPermissions(self, expected, file_name):
> -        observed = os.stat(file_name).st_mode
> -        self.assertEqual(
> -            expected, observed, "Expected %07o, got %07o, for %s" % (
> -                expected, observed, file_name))
> -
> -    def test_gotVersion(self):
> -        # gotVersion always returns an empty dict, since the server does not
> -        # support any extended features. See ISFTPServer.
> -        extras = self.sftp_server.gotVersion(None, None)
> -        self.assertEquals(extras, {})
> -
> -    def test_mkdir_and_rmdir(self):
> -        self.sftp_server.makeDirectory('foo/bar', None)
> -        self.assertEqual(
> -            os.listdir(os.path.join(self.sftp_server._current_upload))[0],
> -            'foo')
> -        dir_name = os.path.join(self.sftp_server._current_upload, 'foo')
> -        self.assertEqual(os.listdir(dir_name)[0], 'bar')
> -        self.assertPermissions(040775, dir_name)
> -        self.sftp_server.removeDirectory('foo/bar')
> -        self.assertEqual(
> -            os.listdir(os.path.join(self.sftp_server._current_upload,
> -            'foo')), [])
> -        self.sftp_server.removeDirectory('foo')
> -        self.assertEqual(
> -            os.listdir(os.path.join(self.sftp_server._current_upload)), [])
> -
> -    def test_file_creation(self):
> -        upload_file = self.sftp_server.openFile('foo/bar', None, None)
> -        upload_file.writeChunk(0, "This is a test")
> -        file_name = os.path.join(self.sftp_server._current_upload, 'foo/bar')
> -        test_file = open(file_name, 'r')
> -        self.assertEqual(test_file.read(), "This is a test")
> -        test_file.close()
> -        self.assertPermissions(0100644, file_name)
> -        dir_name = os.path.join(self.sftp_server._current_upload, 'bar/foo')
> -        os.makedirs(dir_name)
> -        upload_file = self.sftp_server.openFile('bar/foo', None, None)
> -        self.assertRaisesWithContent(
> -            FileIsADirectory,
> -            "File is a directory: '%s'" % dir_name,
> -            upload_file.writeChunk, 0, "This is a test")
> 
> === removed file 'lib/lp/poppy/twistedftp.py'
> --- lib/lp/poppy/twistedftp.py	2012-03-26 07:14:35 +0000
> +++ lib/lp/poppy/twistedftp.py	1970-01-01 00:00:00 +0000
> @@ -1,160 +0,0 @@
> -# Copyright 2011 Canonical Ltd.  This software is licensed under the
> -# GNU Affero General Public License version 3 (see the file LICENSE).
> -
> -"""Twisted FTP implementation of the Poppy upload server."""
> -
> -__metaclass__ = type
> -__all__ = [
> -    'FTPRealm',
> -    'PoppyAnonymousShell',
> -    ]
> -
> -import logging
> -import os
> -import tempfile
> -
> -from twisted.application import (
> -    service,
> -    strports,
> -    )
> -from twisted.cred import (
> -    checkers,
> -    credentials,
> -    )
> -from twisted.cred.portal import (
> -    IRealm,
> -    Portal,
> -    )
> -from twisted.internet import defer
> -from twisted.protocols import ftp
> -from twisted.python import filepath
> -from zope.interface import implements
> -
> -from lp.poppy import get_poppy_root
> -from lp.poppy.filesystem import UploadFileSystem
> -from lp.poppy.hooks import Hooks
> -from lp.services.config import config
> -
> -
> -class PoppyAccessCheck:
> -    """An `ICredentialsChecker` for Poppy FTP sessions."""
> -    implements(checkers.ICredentialsChecker)
> -    credentialInterfaces = (
> -        credentials.IUsernamePassword, credentials.IAnonymous)
> -
> -    def requestAvatarId(self, credentials):
> -        # Poppy allows any credentials.  People can use "anonymous" if
> -        # they want but anything goes.  Thus, we don't actually *check* the
> -        # credentials, and we return the standard avatarId for 'anonymous'.
> -        return checkers.ANONYMOUS
> -
> -
> -class PoppyAnonymousShell(ftp.FTPShell):
> -    """The 'command' interface for sessions.
> -
> -    Roughly equivalent to the SFTPServer in the sftp side of things.
> -    """
> -
> -    def __init__(self, fsroot):
> -        self._fs_root = fsroot
> -        self.uploadfilesystem = UploadFileSystem(tempfile.mkdtemp())
> -        self._current_upload = self.uploadfilesystem.rootpath
> -        os.chmod(self._current_upload, 0770)
> -        self._log = logging.getLogger("poppy-sftp")
> -        self.hook = Hooks(
> -            self._fs_root, self._log, "ubuntu", perms='g+rws',
> -            prefix='-ftp')
> -        self.hook.new_client_hook(self._current_upload, 0, 0)
> -        self.hook.auth_verify_hook(self._current_upload, None, None)
> -        super(PoppyAnonymousShell, self).__init__(
> -            filepath.FilePath(self._current_upload))
> -
> -    def openForWriting(self, file_segments):
> -        """Write the uploaded file to disk, safely.
> -
> -        :param file_segments: A list containing string items, one for each
> -            path component of the file being uploaded.  The file referenced
> -            is relative to the temporary root for this session.
> -
> -        If the file path contains directories, we create them.
> -        """
> -        filename = os.sep.join(file_segments)
> -        self._create_missing_directories(filename)
> -        return super(PoppyAnonymousShell, self).openForWriting(file_segments)
> -
> -    def makeDirectory(self, path):
> -        """Make a directory using the secure `UploadFileSystem`."""
> -        path = os.sep.join(path)
> -        return defer.maybeDeferred(self.uploadfilesystem.mkdir, path)
> -
> -    def access(self, segments):
> -        """Permissive CWD that auto-creates target directories."""
> -        if segments:
> -            path = self._path(segments)
> -            path.makedirs()
> -        return super(PoppyAnonymousShell, self).access(segments)
> -
> -    def logout(self):
> -        """Called when the client disconnects.
> -
> -        We need to post-process the upload.
> -        """
> -        self.hook.client_done_hook(self._current_upload, 0, 0)
> -
> -    def _create_missing_directories(self, filename):
> -        # Same as SFTPServer
> -        new_dir, new_file = os.path.split(
> -            self.uploadfilesystem._sanitize(filename))
> -        if new_dir != '':
> -            if not os.path.exists(
> -                os.path.join(self._current_upload, new_dir)):
> -                self.uploadfilesystem.mkdir(new_dir)
> -
> -    def list(self, path_segments, attrs):
> -        return defer.fail(ftp.CmdNotImplementedError("LIST"))
> -
> -
> -class FTPRealm:
> -    """FTP Realm that lets anyone in."""
> -    implements(IRealm)
> -
> -    def __init__(self, root):
> -        self.root = root
> -
> -    def requestAvatar(self, avatarId, mind, *interfaces):
> -        """Return a Poppy avatar - that is, an "authorisation".
> -
> -        Poppy FTP avatars are totally fake, we don't care about credentials.
> -        See `PoppyAccessCheck` above.
> -        """
> -        for iface in interfaces:
> -            if iface is ftp.IFTPShell:
> -                avatar = PoppyAnonymousShell(self.root)
> -                return ftp.IFTPShell, avatar, getattr(
> -                    avatar, 'logout', lambda: None)
> -        raise NotImplementedError(
> -            "Only IFTPShell interface is supported by this realm")
> -
> -
> -class FTPServiceFactory(service.Service):
> -    """A factory that makes an `FTPService`"""
> -
> -    def __init__(self, port):
> -        realm = FTPRealm(get_poppy_root())
> -        portal = Portal(realm)
> -        portal.registerChecker(PoppyAccessCheck())
> -        factory = ftp.FTPFactory(portal)
> -
> -        factory.tld = get_poppy_root()
> -        factory.protocol = ftp.FTP
> -        factory.welcomeMessage = "Launchpad upload server"
> -        factory.timeOut = config.poppy.idle_timeout
> -
> -        self.ftpfactory = factory
> -        self.portno = port
> -
> -    @staticmethod
> -    def makeFTPService(port=2121):
> -        strport = "tcp:%s" % port
> -        factory = FTPServiceFactory(port)
> -        return strports.service(strport, factory.ftpfactory)
> 
> === removed file 'lib/lp/poppy/twistedsftp.py'
> --- lib/lp/poppy/twistedsftp.py	2015-01-06 12:47:59 +0000
> +++ lib/lp/poppy/twistedsftp.py	1970-01-01 00:00:00 +0000
> @@ -1,144 +0,0 @@
> -# Copyright 2010-2011 Canonical Ltd.  This software is licensed under the
> -# GNU Affero General Public License version 3 (see the file LICENSE).
> -
> -"""Twisted SFTP implementation of the Poppy upload server."""
> -
> -__metaclass__ = type
> -__all__ = [
> -    'SFTPFile',
> -    'SFTPServer',
> -    ]
> -
> -import errno
> -import logging
> -import os
> -import tempfile
> -
> -from lazr.sshserver.events import SFTPClosed
> -from lazr.sshserver.sftp import FileIsADirectory
> -from twisted.conch.interfaces import (
> -    ISFTPFile,
> -    ISFTPServer,
> -    )
> -from zope.component import (
> -    adapter,
> -    provideHandler,
> -    )
> -from zope.interface import implements
> -
> -from lp.poppy.filesystem import UploadFileSystem
> -from lp.poppy.hooks import Hooks
> -
> -
> -class SFTPServer:
> -    """An implementation of `ISFTPServer` that backs onto a Poppy filesystem.
> -    """
> -
> -    implements(ISFTPServer)
> -
> -    def __init__(self, avatar, fsroot):
> -        provideHandler(self.connectionClosed)
> -        self._avatar = avatar
> -        self._fs_root = fsroot
> -        self.uploadfilesystem = UploadFileSystem(tempfile.mkdtemp())
> -        self._current_upload = self.uploadfilesystem.rootpath
> -        os.chmod(self._current_upload, 0770)
> -        self._log = logging.getLogger("poppy-sftp")
> -        self.hook = Hooks(
> -            self._fs_root, self._log, "ubuntu", perms='g+rws', prefix='-sftp')
> -        self.hook.new_client_hook(self._current_upload, 0, 0)
> -        self.hook.auth_verify_hook(self._current_upload, None, None)
> -
> -    def gotVersion(self, other_version, ext_data):
> -        return {}
> -
> -    def openFile(self, filename, flags, attrs):
> -        self._create_missing_directories(filename)
> -        absfile = self._translate_path(filename)
> -        return SFTPFile(absfile)
> -
> -    def removeFile(self, filename):
> -        pass
> -
> -    def renameFile(self, old_path, new_path):
> -        abs_old = self._translate_path(old_path)
> -        abs_new = self._translate_path(new_path)
> -        os.rename(abs_old, abs_new)
> -
> -    def makeDirectory(self, path, attrs):
> -        # XXX: We ignore attrs here
> -        self.uploadfilesystem.mkdir(path)
> -
> -    def removeDirectory(self, path):
> -        self.uploadfilesystem.rmdir(path)
> -
> -    def openDirectory(self, path):
> -        pass
> -
> -    def getAttrs(self, path, follow_links):
> -        pass
> -
> -    def setAttrs(self, path, attrs):
> -        pass
> -
> -    def readLink(self, path):
> -        pass
> -
> -    def makeLink(self, link_path, target_path):
> -        pass
> -
> -    def realPath(self, path):
> -        return path
> -
> -    def extendedRequest(self, extended_name, extended_data):
> -        pass
> -
> -    def _create_missing_directories(self, filename):
> -        new_dir, new_file = os.path.split(
> -            self.uploadfilesystem._sanitize(filename))
> -        if new_dir != '':
> -            if not os.path.exists(
> -                os.path.join(self._current_upload, new_dir)):
> -                self.uploadfilesystem.mkdir(new_dir)
> -
> -    def _translate_path(self, filename):
> -        return self.uploadfilesystem._full(
> -            self.uploadfilesystem._sanitize(filename))
> -
> -    @adapter(SFTPClosed)
> -    def connectionClosed(self, event):
> -        if event.avatar is not self._avatar:
> -            return
> -        self.hook.client_done_hook(self._current_upload, 0, 0)
> -
> -
> -class SFTPFile:
> -
> -    implements(ISFTPFile)
> -
> -    def __init__(self, filename):
> -        self.filename = filename
> -
> -    def close(self):
> -        pass
> -
> -    def readChunk(self, offset, length):
> -        pass
> -
> -    def writeChunk(self, offset, data):
> -        try:
> -            chunk_file = os.open(
> -                self.filename, os.O_CREAT | os.O_WRONLY, 0644)
> -        except OSError as e:
> -            if e.errno != errno.EISDIR:
> -                raise
> -            raise FileIsADirectory(self.filename)
> -        os.lseek(chunk_file, offset, 0)
> -        os.write(chunk_file, data)
> -        os.close(chunk_file)
> -
> -    def getAttrs(self):
> -        pass
> -
> -    def setAttrs(self, attr):
> -        pass
> 
> === modified file 'lib/lp/testing/__init__.py'
> --- lib/lp/testing/__init__.py	2013-05-14 05:29:03 +0000
> +++ lib/lp/testing/__init__.py	2015-01-13 16:03:26 +0000
> @@ -1398,17 +1398,6 @@
>      return launchpad.load(canonical_url(obj, request=api_request))
>  
>  
> -class NestedTempfile(fixtures.Fixture):
> -    """Nest all temporary files and directories inside a top-level one."""
> -
> -    def setUp(self):
> -        super(NestedTempfile, self).setUp()
> -        tempdir = fixtures.TempDir()
> -        self.useFixture(tempdir)
> -        patch = fixtures.MonkeyPatch("tempfile.tempdir", tempdir.path)
> -        self.useFixture(patch)
> -
> -
>  @contextmanager
>  def monkey_patch(context, **kwargs):
>      """In the ContextManager scope, monkey-patch values.
> 
> === modified file 'lib/lp/testing/tests/test_testing.py'
> --- lib/lp/testing/tests/test_testing.py	2012-06-08 08:54:37 +0000
> +++ lib/lp/testing/tests/test_testing.py	2015-01-13 16:03:26 +0000
> @@ -6,7 +6,6 @@
>  __metaclass__ = type
>  
>  import os
> -import tempfile
>  
>  from lp.services.config import config
>  from lp.services.features import (
> @@ -15,7 +14,6 @@
>      )
>  from lp.testing import (
>      feature_flags,
> -    NestedTempfile,
>      set_feature_flag,
>      TestCase,
>      YUIUnitTestCase,
> @@ -62,31 +60,3 @@
>          test_path = os.path.join(config.root, "../bar/baz/../bob.html")
>          test.initialize(test_path)
>          self.assertEqual("../bar/bob.html", test.id())
> -
> -
> -class NestedTempfileTest(TestCase):
> -    """Tests for `NestedTempfile`."""
> -
> -    def test_normal(self):
> -        # The temp directory is removed when the context is exited.
> -        starting_tempdir = tempfile.gettempdir()
> -        with NestedTempfile():
> -            self.assertEqual(tempfile.tempdir, tempfile.gettempdir())
> -            self.assertNotEqual(tempfile.tempdir, starting_tempdir)
> -            self.assertTrue(os.path.isdir(tempfile.tempdir))
> -            nested_tempdir = tempfile.tempdir
> -        self.assertEqual(tempfile.tempdir, tempfile.gettempdir())
> -        self.assertEqual(starting_tempdir, tempfile.tempdir)
> -        self.assertFalse(os.path.isdir(nested_tempdir))
> -
> -    def test_exception(self):
> -        # The temp directory is removed when the context is exited, even if
> -        # the code running in context raises an exception.
> -        class ContrivedException(Exception):
> -            pass
> -        try:
> -            with NestedTempfile():
> -                nested_tempdir = tempfile.tempdir
> -                raise ContrivedException
> -        except ContrivedException:
> -            self.assertFalse(os.path.isdir(nested_tempdir))
> 
> === modified file 'utilities/snakefood/lp-sfood-packages'
> --- utilities/snakefood/lp-sfood-packages	2011-12-30 06:47:17 +0000
> +++ utilities/snakefood/lp-sfood-packages	2015-01-13 16:03:26 +0000
> @@ -5,7 +5,6 @@
>  lp/services
>  lp/scripts
>  lp/registry
> -lp/poppy
>  lp/hardwaredb
>  lp/coop/answersbugs
>  lp/codehosting
> 
> === modified file 'utilities/start-dev-soyuz.sh'
> --- utilities/start-dev-soyuz.sh	2011-12-08 01:38:24 +0000
> +++ utilities/start-dev-soyuz.sh	2015-01-13 16:03:26 +0000
> @@ -14,11 +14,23 @@
>          -y "$tac" $@
>  }
>  
> +start_twistd_plugin() {
> +    # Start twistd for plugin service $1.
> +    name=$1
> +    plugin=$2
> +    shift 2
> +    echo "Starting $name."
> +    "bin/twistd-for-$name" \
> +        --logfile "/var/tmp/development-$name.log" \
> +        --pidfile "/var/tmp/development-$name.pid" \
> +        "$plugin" "$@"
> +}
> +
>  start_twistd testkeyserver lib/lp/testing/keyserver/testkeyserver.tac
>  start_twistd buildd-manager daemons/buildd-manager.tac
> -mkdir -p /var/tmp/poppy/incoming
> -export POPPY_ROOT=/var/tmp/poppy/incoming
> -start_twistd poppy-sftp daemons/poppy-sftp.tac
> +mkdir -p /var/tmp/txpkgupload/incoming
> +start_twistd_plugin txpkgupload pkgupload \
> +    --config-file configs/development/txpkgupload.yaml
>  
>  
>  echo "Done."
> 
> === modified file 'versions.cfg'
> --- versions.cfg	2015-01-06 12:47:59 +0000
> +++ versions.cfg	2015-01-13 16:03:26 +0000
> @@ -32,6 +32,7 @@
>  FeedParser = 4.1
>  feedvalidator = 0.0.0DEV-r1049
>  fixtures = 0.3.9
> +FormEncode = 1.2.4
>  funkload = 1.16.1
>  grokcore.component = 1.6
>  html5browser = 0.0.9
> @@ -104,6 +105,7 @@
>  python-openid = 2.2.5-fix1034376
>  python-subunit = 0.0.8beta
>  python-swiftclient = 1.5.0
> +PyYAML = 3.10
>  rabbitfixture = 0.3.5
>  requests = 1.2.3
>  s4 = 0.1.2
> @@ -125,6 +127,7 @@
>  txfixtures = 0.1.4
>  txlongpoll = 0.2.12
>  txlongpollfixture = 0.1.3
> +txpkgupload = 0.1.1
>  unittest2 = 0.5.1
>  van.testing = 3.0.0
>  wadllib = 1.3.2
> 


-- 
https://code.launchpad.net/~cjwatson/launchpad/split-txpkgupload/+merge/246320
Your team Launchpad code reviewers is subscribed to branch lp:launchpad.


References