Merge lp:~cjwatson/launchpad/livefs-garbo into lp:launchpad

Proposed by Colin Watson
Status: Merged
Approved by: William Grant
Approved revision: no longer in the source branch.
Merged at revision: 17056
Proposed branch: lp:~cjwatson/launchpad/livefs-garbo
Merge into: lp:launchpad
Prerequisite: lp:~cjwatson/launchpad/livefs
Diff against target: 145 lines (+69/-4)
2 files modified
lib/lp/scripts/garbo.py (+25/-1)
lib/lp/scripts/tests/test_garbo.py (+44/-3)
To merge this branch: bzr merge lp:~cjwatson/launchpad/livefs-garbo
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+217784@code.launchpad.net

Commit message

Add a garbo job to remove old LiveFSFile records.

Description of the change

== Summary ==

Next stage of bug 1247461, following https://code.launchpad.net/~cjwatson/launchpad/livefs/+merge/217261. This adds a garbo job so that we don't keep large livefs images around for long.

== Proposed fix ==

I explored various options including keeping the current image for each LiveFS, but that turned out to be cumbersome to get right: we'd have had to do something special to avoid images eventually piling up for old series that we don't build for any more. In the end I decided to keep it simple for now: remove any images more than a day old, which will be more than enough time for cdimage to fetch them and build ISOs out of them or publish them somewhere persistent. We can always tweak this later.

This will require cdimage to use its locally-cached copy if somebody tries to do an image build that intentionally doesn't rebuild the livefs. But the rearrangements for this will be a good thing anyway: in particular they will arrange that we don't do an image build if the corresponding livefs build failed, which has been a long-standing problem in cdimage that causes confusion from time to time (at present we only fail the image build if livefs builds on all architectures fail, and otherwise carry on merrily using an old livefs on some architectures).

== LOC Rationale ==

+65; same rationale as https://code.launchpad.net/~cjwatson/launchpad/livefs/+merge/217261.

== Tests ==

bin/test -vvct LiveFSFilePruner

== Demo and Q/A ==

Build an image, wait a day (or hack the DB to lie about its date_finished), run garbo, and check that the appropriate LiveFSBuild's binary LiveFSFile artifacts are pruned.

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/scripts/garbo.py'
2--- lib/lp/scripts/garbo.py 2014-03-11 06:14:19 +0000
3+++ lib/lp/scripts/garbo.py 2014-05-08 14:44:44 +0000
4@@ -1,4 +1,4 @@
5-# Copyright 2009-2013 Canonical Ltd. This software is licensed under the
6+# Copyright 2009-2014 Canonical Ltd. This software is licensed under the
7 # GNU Affero General Public License version 3 (see the file LICENSE).
8
9 """Database garbage collection."""
10@@ -118,6 +118,7 @@
11 from lp.services.session.model import SessionData
12 from lp.services.verification.model.logintoken import LoginToken
13 from lp.soyuz.model.archive import Archive
14+from lp.soyuz.model.livefsbuild import LiveFSFile
15 from lp.soyuz.model.publishing import SourcePackagePublishingHistory
16 from lp.soyuz.model.reporting import LatestPersonSourcePackageReleaseCache
17 from lp.soyuz.model.sourcepackagerelease import SourcePackageRelease
18@@ -1363,6 +1364,28 @@
19 transaction.commit()
20
21
22+class LiveFSFilePruner(BulkPruner):
23+ """A BulkPruner to remove old `LiveFSFile`s.
24+
25+ We remove binary files attached to `LiveFSBuild`s that are more than a
26+ day old; these files are very large and are only useful for builds in
27+ progress. Text files are typically small (<1MiB) and useful for
28+ retrospective analysis, so we preserve those indefinitely.
29+ """
30+ target_table_class = LiveFSFile
31+ ids_to_prune_query = """
32+ SELECT DISTINCT LiveFSFile.id
33+ FROM LiveFSFile, LiveFSBuild, LibraryFileAlias
34+ WHERE
35+ LiveFSFile.livefsbuild = LiveFSBuild.id
36+ AND LiveFSFile.libraryfile = LibraryFileAlias.id
37+ AND LiveFSBuild.date_finished <
38+ CURRENT_TIMESTAMP AT TIME ZONE 'UTC'
39+ - CAST('1 day' AS interval)
40+ AND LibraryFileAlias.mimetype != 'text/plain'
41+ """
42+
43+
44 class BaseDatabaseGarbageCollector(LaunchpadCronScript):
45 """Abstract base class to run a collection of TunableLoops."""
46 script_name = None # Script name for locking and database user. Override.
47@@ -1642,6 +1665,7 @@
48 CodeImportEventPruner,
49 CodeImportResultPruner,
50 HWSubmissionEmailLinker,
51+ LiveFSFilePruner,
52 LoginTokenPruner,
53 ObsoleteBugAttachmentPruner,
54 OldTimeLimitedTokenDeleter,
55
56=== modified file 'lib/lp/scripts/tests/test_garbo.py'
57--- lib/lp/scripts/tests/test_garbo.py 2014-03-10 19:30:37 +0000
58+++ lib/lp/scripts/tests/test_garbo.py 2014-05-08 14:44:44 +0000
59@@ -1,4 +1,4 @@
60-# Copyright 2009-2013 Canonical Ltd. This software is licensed under the
61+# Copyright 2009-2014 Canonical Ltd. This software is licensed under the
62 # GNU Affero General Public License version 3 (see the file LICENSE).
63
64 """Test the database garbage collector."""
65@@ -41,6 +41,7 @@
66 BugNotification,
67 BugNotificationRecipient,
68 )
69+from lp.buildmaster.enums import BuildStatus
70 from lp.code.bzr import (
71 BranchFormat,
72 RepositoryFormat,
73@@ -87,6 +88,7 @@
74 )
75 from lp.services.database.interfaces import IMasterStore
76 from lp.services.features.model import FeatureFlag
77+from lp.services.features.testing import FeatureFixture
78 from lp.services.identity.interfaces.account import AccountStatus
79 from lp.services.identity.interfaces.emailaddress import EmailAddressStatus
80 from lp.services.job.model.job import Job
81@@ -109,6 +111,8 @@
82 from lp.services.verification.model.logintoken import LoginToken
83 from lp.services.worlddata.interfaces.language import ILanguageSet
84 from lp.soyuz.enums import PackagePublishingStatus
85+from lp.soyuz.interfaces.livefs import LIVEFS_FEATURE_FLAG
86+from lp.soyuz.model.livefsbuild import LiveFSFile
87 from lp.soyuz.model.reporting import LatestPersonSourcePackageReleaseCache
88 from lp.testing import (
89 feature_flags,
90@@ -668,9 +672,9 @@
91 switch_dbuser('testadmin')
92 mp1 = self.factory.makeBranchMergeProposal()
93 now = datetime.now(UTC)
94- mp1_diff_comment = self.factory.makePreviewDiff(
95+ mp1_diff_comment = self.factory.makePreviewDiff(
96 merge_proposal=mp1, date_created=now - timedelta(hours=2))
97- mp1_diff_draft = self.factory.makePreviewDiff(
98+ mp1_diff_draft = self.factory.makePreviewDiff(
99 merge_proposal=mp1, date_created=now - timedelta(hours=1))
100 mp1_diff = self.factory.makePreviewDiff(merge_proposal=mp1)
101 # Enabled 'inline_diff_comments' feature flag and attach comments
102@@ -1317,6 +1321,43 @@
103 'PopulateLatestPersonSourcePackageReleaseCache')
104 self.assertEqual(spph_2.id, job_data['last_spph_id'])
105
106+ def _test_LiveFSFilePruner(self, content_type, interval, expected_count=0):
107+ # Garbo should (or should not, if `expected_count=1`) remove LiveFS
108+ # files of MIME type `content_type` that finished more than
109+ # `interval` days ago.
110+ now = datetime.now(UTC)
111+ switch_dbuser('testadmin')
112+ self.useFixture(FeatureFixture({LIVEFS_FEATURE_FLAG: u'on'}))
113+ store = IMasterStore(LiveFSFile)
114+
115+ db_build = self.factory.makeLiveFSBuild(
116+ date_created=now - timedelta(days=interval, minutes=15),
117+ status=BuildStatus.FULLYBUILT, duration=timedelta(minutes=10))
118+ db_lfa = self.factory.makeLibraryFileAlias(content_type=content_type)
119+ db_file = self.factory.makeLiveFSFile(
120+ livefsbuild=db_build, libraryfile=db_lfa)
121+ Store.of(db_file).flush()
122+ self.assertEqual(1, store.find(LiveFSFile).count())
123+
124+ self.runDaily()
125+
126+ switch_dbuser('testadmin')
127+ self.assertEqual(expected_count, store.find(LiveFSFile).count())
128+
129+ def test_LiveFSFilePruner_old_binary_files(self):
130+ # LiveFS binary files attached to builds over a day old are pruned.
131+ self._test_LiveFSFilePruner('application/octet-stream', 1)
132+
133+ def test_LiveFSFilePruner_old_text_files(self):
134+ # LiveFS text files attached to builds over a day old are retained.
135+ self._test_LiveFSFilePruner('text/plain', 1, expected_count=1)
136+
137+ def test_LiveFSFilePruner_recent_binary_files(self):
138+ # LiveFS binary files attached to builds less than a day old are
139+ # retained.
140+ self._test_LiveFSFilePruner(
141+ 'application/octet-stream', 0, expected_count=1)
142+
143
144 class TestGarboTasks(TestCaseWithFactory):
145 layer = LaunchpadZopelessLayer