Merge lp://staging/~cody-somerville/launchpad/ppa-deletion into lp://staging/launchpad/db-devel

Proposed by Cody A.W. Somerville
Status: Merged
Approved by: Julian Edwards
Approved revision: no longer in the source branch.
Merged at revision: not available
Proposed branch: lp://staging/~cody-somerville/launchpad/ppa-deletion
Merge into: lp://staging/launchpad/db-devel
Diff against target: 272 lines (+142/-23)
5 files modified
lib/lp/archivepublisher/publishing.py (+40/-1)
lib/lp/archivepublisher/scripts/generate_ppa_htaccess.py (+8/-1)
lib/lp/archivepublisher/tests/test_generate_ppa_htaccess.py (+42/-0)
lib/lp/archivepublisher/tests/test_publisher.py (+19/-1)
lib/lp/soyuz/scripts/publishdistro.py (+33/-20)
To merge this branch: bzr merge lp://staging/~cody-somerville/launchpad/ppa-deletion
Reviewer Review Type Date Requested Status
Julian Edwards (community) Approve
Review via email: mp+22176@code.staging.launchpad.net

Commit message

Add support to publisher for deleting PPAs; and update generate_ppa_htaccess to skip generation/updating of deleted or disabled P3As.

Description of the change

Add a deleteArchive method to the publisher which will be called by the publishdistro script when an archive (for now just PPAs) has a status of ArchiveStatus.DELETING. This method will physical remove the archive from disk and set the archive's status to ArchiveStatus.DELETED.

To post a comment you must log in.
Revision history for this message
Julian Edwards (julian-edwards) wrote :
Download full text (9.4 KiB)

> === modified file 'lib/lp/archivepublisher/publishing.py'
> --- lib/lp/archivepublisher/publishing.py 2010-02-09 00:17:40 +0000
> +++ lib/lp/archivepublisher/publishing.py 2010-03-25 20:23:14 +0000
> @@ -12,9 +12,11 @@
> import hashlib
> import logging
> import os
> +import shutil
>
> from datetime import datetime
>
> +from storm.store import Store
> from zope.component import getUtility
>
> from lp.archivepublisher import HARDCODED_COMPONENT_ORDER
> @@ -29,13 +31,15 @@
> from canonical.database.sqlbase import sqlvalues
> from lp.registry.interfaces.pocket import (
> PackagePublishingPocket, pocketsuffix)
> -from lp.soyuz.interfaces.archive import ArchivePurpose
> +from lp.soyuz.interfaces.archive import ArchivePurpose, ArchiveStatus
> from lp.soyuz.interfaces.binarypackagerelease import (
> BinaryPackageFormat)
> from lp.soyuz.interfaces.component import IComponentSet
> from lp.soyuz.interfaces.publishing import PackagePublishingStatus
>
> from canonical.librarian.client import LibrarianClient
> +from canonical.launchpad.webapp.errorlog import (
> + ErrorReportingUtility, ScriptRequest)
>
> suffixpocket = dict((v, k) for (k, v) in pocketsuffix.items())
>
> @@ -596,3 +600,37 @@
> in_file.close()
>
> out_file.write(" %s % 16d %s\n" % (checksum, length, file_name))
> +
> + def deleteArchive(self):
> + """Delete the archive.
> +
> + Physically remove the entire archive from disk and set the
archive's
> + status to DELETED.
> +
> + Any errors encountered while removing the archive from disk will
> + be caught and an OOPS report generated.
> + """
> +
> + self.log.info(
> + "Attempting to delete archive '%s/%s' at '%s'." % (
> + self.archive.owner.name, self.archive.name,
> + self._config.archiveroot))
> +
> + # Attempt to rmdir if the path to the root of the archive exists.
> + if os.path.exists(self._config.archiveroot):
> + try:
> + shutil.rmtree(self._config.archiveroot)
> + except:
> + message = 'Exception while deleting archive root %s' % (
> + self._config.archiveroot)
> + request = ScriptRequest([('error-explanation', message)])
> + ErrorReportingUtility().raising(sys.exc_info(), request)
> + self.log.error('%s (%s)' % (message, request.oopsid))
> + else:
> + self.log.warning(
> + "Root directory '%s' for archive '%s/%s' does not exist." %
(
> + self._config.archiveroot, self.archive.owner.name,
> + self.archive.name))
> +
> + # Set archive's status to DELETED.
> + self.archive.status = ArchiveStatus.DELETED

This is all good. I was trying to think of failure scenarios with it but I
think you have them all covered!

>
> === modified file 'lib/lp/archivepublisher/tests/test_publisher.py'
> --- lib/lp/archivepublisher/tests/test_publisher.py 2010-02-10 00:25:55
+0000
> +++ lib/lp/archivepublisher/tests/test_publisher.py 2010-03-25 20:23:14
+0000
> @@ -26,7 +2...

Read more...

Revision history for this message
Cody A.W. Somerville (cody-somerville) wrote :
Download full text (10.1 KiB)

On Thu, Mar 25, 2010 at 5:48 PM, Julian Edwards <
<email address hidden>> wrote:

> >
> > === modified file 'lib/lp/archivepublisher/tests/test_publisher.py'
> > --- lib/lp/archivepublisher/tests/test_publisher.py 2010-02-10 00:25:55
> +0000
> > +++ lib/lp/archivepublisher/tests/test_publisher.py 2010-03-25 20:23:14
> +0000
> > @@ -26,7 +26,7 @@
> > from canonical.database.constants import UTC_NOW
> > from canonical.launchpad.ftests.keys_for_tests import gpgkeysdir
> > from lp.soyuz.interfaces.archive import (
> > - ArchivePurpose, IArchiveSet)
> > + ArchivePurpose, ArchiveStatus, IArchiveSet)
> > from lp.soyuz.interfaces.binarypackagerelease import (
> > BinaryPackageFormat)
> > from lp.registry.interfaces.distribution import IDistributionSet
> > @@ -95,6 +95,31 @@
> > foo_path = "%s/main/f/foo/foo_666.dsc" % self.pool_dir
> > self.assertEqual(open(foo_path).read().strip(), 'Hello world')
> >
> > + def testDeletingPPA(self):
> > + """Test deleting a PPA"""
> > + ubuntu_team = getUtility(IPersonSet).getByName('ubuntu-team')
> > + test_archive = getUtility(IArchiveSet).new(
> > + distribution=self.ubuntutest, owner=ubuntu_team,
> > + purpose=ArchivePurpose.PPA)
> > +
> > + publisher = getPublisher(test_archive, None, self.logger)
> > +
> > + pub_source = self.getPubSource(
> > + sourcename="foo", filename="foo_1.dsc",
> > + filecontent='I am a file.',
> > + status=PackagePublishingStatus.PENDING,
> archive=test_archive)
> > +
> > + publisher.A_publish(False)
> > + self.layer.txn.commit()
> > + publisher.C_writeIndexes(False)
> > + publisher.D_writeReleaseFiles(False)
> > + pub_source.sync()
>
> You don't need to do these last 5 lines, nor the getPubSource.
>
> Doing more than necessary makes for a fragile test if other parts of the
> system change. A better test would be to throw some random files into the
> root directory of the archive and then assert that they get deleted when
> you call deleteArchive, along with the status change.
>

You bring up a good point. However, I think what we need to do to
successfully test our ability to delete an archive and not just our ability
to delete a directory with some files in it is to attempt deleting a simple
but typical archive. If there is higher level way of achieving the same
result instead of calling those publisher methods manually then I think that
would be a good compromise. What do you think?

> > +
> > + self.assertTrue(os.path.exists(publisher._config.archiveroot))
> > + publisher.deleteArchive()
> > + self.assertTrue(not
> os.path.exists(publisher._config.archiveroot))
>
> How about assertFalse?
>

ACK.

<snip>

> === modified file 'lib/lp/soyuz/scripts/publishdistro.py'
> > --- lib/lp/soyuz/scripts/publishdistro.py 2010-02-24 11:53:22 +0000
> > +++ lib/lp/soyuz/scripts/publishdistro.py 2010-03-25 20:23:14 +0000
> > @@ -16,7 +16,7 @@
> > from canonical.database.sqlbase import (
> > clear_current_connection_cache, flush_database_updates)
> > from lp.soyuz.interfaces.archive import (
>...

Revision history for this message
Julian Edwards (julian-edwards) wrote :
Download full text (6.5 KiB)

On Friday 26 March 2010 01:15:27 Cody A.W. Somerville wrote:
> > Doing more than necessary makes for a fragile test if other parts of the
> > system change. A better test would be to throw some random files into
> > the root directory of the archive and then assert that they get deleted
> > when you call deleteArchive, along with the status change.
>
> You bring up a good point. However, I think what we need to do to
> successfully test our ability to delete an archive and not just our ability
> to delete a directory with some files in it is to attempt deleting a simple
> but typical archive. If there is higher level way of achieving the same
> result instead of calling those publisher methods manually then I think
> that would be a good compromise. What do you think?

In the nicest possible way, I think it's crazy.

The code you're testing doesn't care what you're deleting, it just does a
shutil.rmtree(). It's totally pointless putting any kind of data structure in
there because you're not using it; it just wastes test suite time. If we were
testing deleting of only certain parts of the archive, then of course it would
be correct to set up a real archive. But we're not.

Seriously, just poke some files in the archive root and test that they get
removed.

> > This code has got a subtle bug - it will only process archives marked
> > with publishing enabled because of the filter just above the context of
> > the diff.
>
> This was actually an intentional decision. Deleting an archive is a
> function of the publisher. If the publisher is disabled for an archive, I
> don't think we should process requests to delete the archive just like we
> wouldn't process pending publications or requests to copy a package into
> the archive.
>
> You're welcome to disagree on me this. I suspect that you might reply with
> that the flag's actual intention is to 'disable publishing for the
> archive', not 'disable the publisher for that archive'. However, I think

Correct :)

> even if this is the case then we should consider re-purposing it to
> include this slightly broader scope of functionality. If you're interested
> in my rationale or some potential use cases for this, I'd be happy to
> discuss it further with you on IRC.
>
> However, looking at your UI branch, it seems that you disable the PPA as
> well as set the status to ArchiveStatus.DELETING which of course puts a
> 'wrench' in this plan. See below for further thoughts related to this.

Think about this from the user's point of view (and the user's PoV should
drive everything). They're clicking a button that says to delete the PPA.
This is an action that has a certain amount of finality about it! Having to
then go and discover that it didn't really delete because you forgot to tick a
"publish" checkbox on the +edit page is rather weird, don't you think?

> > What do you think to adding IArchiveSet.getByStatus()? Then at the end
> > of the publisher script we can grab a list of archives that are in the
> > DELETING state and simply call publisher.deleteArchive(). The new method
> > on IArchiveSet would require a simple new test.
>
> Could you elaborate on what exactly y...

Read more...

Revision history for this message
Julian Edwards (julian-edwards) wrote :

Oh one more thing:

lib/lp/archivepublisher/scripts/generate_ppa_htaccess.py

This needs changing so it ignores publish=False and status=DELETED otherwise I expect it'll blow up when the directory gets removed.

Revision history for this message
Cody A.W. Somerville (cody-somerville) wrote :
Download full text (4.8 KiB)

> On Friday 26 March 2010 01:15:27 Cody A.W. Somerville wrote:
> > > Doing more than necessary makes for a fragile test if other parts of the
> > > system change. A better test would be to throw some random files into
> > > the root directory of the archive and then assert that they get deleted
> > > when you call deleteArchive, along with the status change.
> >
> > You bring up a good point. However, I think what we need to do to
> > successfully test our ability to delete an archive and not just our ability
> > to delete a directory with some files in it is to attempt deleting a simple
> > but typical archive. If there is higher level way of achieving the same
> > result instead of calling those publisher methods manually then I think
> > that would be a good compromise. What do you think?
>
> In the nicest possible way, I think it's crazy.
>
> The code you're testing doesn't care what you're deleting, it just does a
> shutil.rmtree(). It's totally pointless putting any kind of data structure in
> there because you're not using it; it just wastes test suite time. If we were
> testing deleting of only certain parts of the archive, then of course it would
> be correct to set up a real archive. But we're not.
>
> Seriously, just poke some files in the archive root and test that they get
> removed.

Okay.

> > > This code has got a subtle bug - it will only process archives marked
> > > with publishing enabled because of the filter just above the context of
> > > the diff.
> >
> > This was actually an intentional decision. Deleting an archive is a
> > function of the publisher. If the publisher is disabled for an archive, I
> > don't think we should process requests to delete the archive just like we
> > wouldn't process pending publications or requests to copy a package into
> > the archive.
> >
> > You're welcome to disagree on me this. I suspect that you might reply with
> > that the flag's actual intention is to 'disable publishing for the
> > archive', not 'disable the publisher for that archive'. However, I think
>
> Correct :)
>
> > even if this is the case then we should consider re-purposing it to
> > include this slightly broader scope of functionality. If you're interested
> > in my rationale or some potential use cases for this, I'd be happy to
> > discuss it further with you on IRC.
> >
> > However, looking at your UI branch, it seems that you disable the PPA as
> > well as set the status to ArchiveStatus.DELETING which of course puts a
> > 'wrench' in this plan. See below for further thoughts related to this.
>
> Think about this from the user's point of view (and the user's PoV should
> drive everything). They're clicking a button that says to delete the PPA.
> This is an action that has a certain amount of finality about it! Having to
> then go and discover that it didn't really delete because you forgot to tick a
> "publish" checkbox on the +edit page is rather weird, don't you think?

I don't feel strongly about this. However, the publish bool is not exposed to users (or on the form at all for anyone IIRC) so they'd only run into this if an admin intentionally disabled publishing for an archive. This I wo...

Read more...

Revision history for this message
Cody A.W. Somerville (cody-somerville) wrote :

> Oh one more thing:
>
> lib/lp/archivepublisher/scripts/generate_ppa_htaccess.py
>
> This needs changing so it ignores publish=False and status=DELETED otherwise I
> expect it'll blow up when the directory gets removed.

Why ignore archives that have publish=False?

Also, should I delete all subscriptions when we delete the archive or maybe update this script to do it? Or do you just want me to update it to ignore writing .htaccess file for deleted archives?

Revision history for this message
Julian Edwards (julian-edwards) wrote :

Cody, thanks for all these changes it's looking great! There's just a couple of minor things to fix:

In lib/lp/archivepublisher/tests/test_publisher.py:

101 + open(os.path.join(
102 + publisher._config.archiveroot, 'test_file'), 'w').close()

Please only tab in 4 chars, not 8.

In lib/lp/soyuz/scripts/publishdistro.py:

132 + archives = [archive for archive in archives if archive.publish
133 + or archive.status == ArchiveStatus.DELETING]

Our coding guidelines say to have a newline after the opening "["

So format it like this, which I think is more readable:

    archives = [
        archive for archive in archives
        if archive.publish or archive.status == ArchiveStatus.DELETING]

There's also a bug as I discussed on IRC:

158 + try_and_commit("deleting archive", publisher.deleteArchive,
159 + options.careful or options.careful_publishing)

Should not have the extra args for deleteArchive().

Finally:

160 + else:
161 + # Other types of archives do not currently support deletion.
162 + pass

Please log a warning here.

After these changes it's good to land, thanks for the great job!

J

review: Approve
Revision history for this message
Julian Edwards (julian-edwards) wrote :

On Thursday 15 April 2010 06:20:42 you wrote:
> > Oh one more thing:
> >
> > lib/lp/archivepublisher/scripts/generate_ppa_htaccess.py
> >
> > This needs changing so it ignores publish=False and status=DELETED
> > otherwise I expect it'll blow up when the directory gets removed.
>
> Why ignore archives that have publish=False?

Right, it should ignore archives that have disabled=True. Can you make that
change please :)

> Also, should I delete all subscriptions when we delete the archive or maybe
> update this script to do it? Or do you just want me to update it to ignore
> writing .htaccess file for deleted archives?

It just needs to ignore writing .htaccess, because the archiveroot won't be
there any more.

We can remove the subscriptions etc in a later branch when we do more of the
cleaning up.

Cheers.

Revision history for this message
Julian Edwards (julian-edwards) wrote :

Cody, one last thing, the code you added in generate_ppa_htaccess.py should have a test.

See lib/lp/archivepublisher/tests/test_generate_ppa_htaccess.py

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
The diff is not available at this time. You can reload the page or download it.

Subscribers

People subscribed via source and target branches

to status/vote changes: