Merge lp:~cjwatson/launchpad/livefs-keep-binary-files-interval into lp:launchpad

Proposed by Colin Watson
Status: Merged
Merged at revision: 19005
Proposed branch: lp:~cjwatson/launchpad/livefs-keep-binary-files-interval
Merge into: lp:launchpad
Diff against target: 322 lines (+116/-20)
7 files modified
lib/lp/scripts/garbo.py (+10/-6)
lib/lp/scripts/tests/test_garbo.py (+34/-4)
lib/lp/soyuz/browser/livefs.py (+8/-2)
lib/lp/soyuz/interfaces/livefs.py (+8/-1)
lib/lp/soyuz/model/livefs.py (+31/-4)
lib/lp/soyuz/tests/test_livefs.py (+21/-1)
lib/lp/testing/factory.py (+4/-2)
To merge this branch: bzr merge lp:~cjwatson/launchpad/livefs-keep-binary-files-interval
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+368703@code.launchpad.net

Commit message

Allow configuring the binary file retention period of a LiveFS.

Description of the change

This makes it more practical to deal with live filesystems that are expected to produce base images for use by Launchpad itself. Setting the base image of course ensures that the corresponding LFA stays around, but we may very well want to set a base image from a live filesystem build that's more than a day old.

https://code.launchpad.net/~cjwatson/launchpad/db-livefs-keep-binary-files-interval/+merge/368702 is the corresponding DB change.

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
=== modified file 'lib/lp/scripts/garbo.py'
--- lib/lp/scripts/garbo.py 2019-01-30 11:23:33 +0000
+++ lib/lp/scripts/garbo.py 2019-06-12 12:20:38 +0000
@@ -1566,21 +1566,25 @@
1566class LiveFSFilePruner(BulkPruner):1566class LiveFSFilePruner(BulkPruner):
1567 """A BulkPruner to remove old `LiveFSFile`s.1567 """A BulkPruner to remove old `LiveFSFile`s.
15681568
1569 We remove binary files attached to `LiveFSBuild`s that are more than a1569 We remove binary files attached to `LiveFSBuild`s that are more than
1570 day old; these files are very large and are only useful for builds in1570 `LiveFS.keep_binary_files_interval` old; these files are very large and
1571 progress. Text files are typically small (<1MiB) and useful for1571 are only useful for builds in progress. Text files are typically small
1572 retrospective analysis, so we preserve those indefinitely.1572 (<1MiB) and useful for retrospective analysis, so we preserve those
1573 indefinitely.
1573 """1574 """
1574 target_table_class = LiveFSFile1575 target_table_class = LiveFSFile
1576 # Note that a NULL keep_binary_files_interval disables pruning, due to
1577 # SQL NULL propagation.
1575 ids_to_prune_query = """1578 ids_to_prune_query = """
1576 SELECT DISTINCT LiveFSFile.id1579 SELECT DISTINCT LiveFSFile.id
1577 FROM LiveFSFile, LiveFSBuild, LibraryFileAlias1580 FROM LiveFSFile, LiveFSBuild, LiveFS, LibraryFileAlias
1578 WHERE1581 WHERE
1579 LiveFSFile.livefsbuild = LiveFSBuild.id1582 LiveFSFile.livefsbuild = LiveFSBuild.id
1583 AND LiveFSBuild.livefs = LiveFS.id
1580 AND LiveFSFile.libraryfile = LibraryFileAlias.id1584 AND LiveFSFile.libraryfile = LibraryFileAlias.id
1581 AND LiveFSBuild.date_finished <1585 AND LiveFSBuild.date_finished <
1582 CURRENT_TIMESTAMP AT TIME ZONE 'UTC'1586 CURRENT_TIMESTAMP AT TIME ZONE 'UTC'
1583 - CAST('1 day' AS interval)1587 - LiveFS.keep_binary_files_interval
1584 AND LibraryFileAlias.mimetype != 'text/plain'1588 AND LibraryFileAlias.mimetype != 'text/plain'
1585 """1589 """
15861590
15871591
=== modified file 'lib/lp/scripts/tests/test_garbo.py'
--- lib/lp/scripts/tests/test_garbo.py 2019-01-30 11:23:33 +0000
+++ lib/lp/scripts/tests/test_garbo.py 2019-06-12 12:20:38 +0000
@@ -154,6 +154,9 @@
154 )154 )
155155
156156
157_default = object()
158
159
157class TestGarboScript(TestCase):160class TestGarboScript(TestCase):
158 layer = LaunchpadScriptLayer161 layer = LaunchpadScriptLayer
159162
@@ -1513,18 +1516,25 @@
1513 'PopulateLatestPersonSourcePackageReleaseCache')1516 'PopulateLatestPersonSourcePackageReleaseCache')
1514 self.assertEqual(spph_2.id, job_data['last_spph_id'])1517 self.assertEqual(spph_2.id, job_data['last_spph_id'])
15151518
1516 def _test_LiveFSFilePruner(self, content_type, interval, expected_count=0):1519 def _test_LiveFSFilePruner(self, content_type, interval,
1520 keep_binary_files_days=_default,
1521 expected_count=0):
1517 # Garbo should (or should not, if `expected_count=1`) remove LiveFS1522 # Garbo should (or should not, if `expected_count=1`) remove LiveFS
1518 # files of MIME type `content_type` that finished more than1523 # files of MIME type `content_type` that finished more than
1519 # `interval` days ago.1524 # `interval` days ago. If `keep_binary_files_days` is given, set
1525 # that on the test LiveFS.
1520 now = datetime.now(UTC)1526 now = datetime.now(UTC)
1521 switch_dbuser('testadmin')1527 switch_dbuser('testadmin')
1522 self.useFixture(FeatureFixture({LIVEFS_FEATURE_FLAG: u'on'}))1528 self.useFixture(FeatureFixture({LIVEFS_FEATURE_FLAG: u'on'}))
1523 store = IMasterStore(LiveFSFile)1529 store = IMasterStore(LiveFSFile)
15241530
1531 livefs_kwargs = {}
1532 if keep_binary_files_days is not _default:
1533 livefs_kwargs['keep_binary_files_days'] = keep_binary_files_days
1525 db_build = self.factory.makeLiveFSBuild(1534 db_build = self.factory.makeLiveFSBuild(
1526 date_created=now - timedelta(days=interval, minutes=15),1535 date_created=now - timedelta(days=interval, minutes=15),
1527 status=BuildStatus.FULLYBUILT, duration=timedelta(minutes=10))1536 status=BuildStatus.FULLYBUILT, duration=timedelta(minutes=10),
1537 **livefs_kwargs)
1528 db_lfa = self.factory.makeLibraryFileAlias(content_type=content_type)1538 db_lfa = self.factory.makeLibraryFileAlias(content_type=content_type)
1529 db_file = self.factory.makeLiveFSFile(1539 db_file = self.factory.makeLiveFSFile(
1530 livefsbuild=db_build, libraryfile=db_lfa)1540 livefsbuild=db_build, libraryfile=db_lfa)
@@ -1537,7 +1547,8 @@
1537 self.assertEqual(expected_count, store.find(LiveFSFile).count())1547 self.assertEqual(expected_count, store.find(LiveFSFile).count())
15381548
1539 def test_LiveFSFilePruner_old_binary_files(self):1549 def test_LiveFSFilePruner_old_binary_files(self):
1540 # LiveFS binary files attached to builds over a day old are pruned.1550 # By default, LiveFS binary files attached to builds over a day old
1551 # are pruned.
1541 self._test_LiveFSFilePruner('application/octet-stream', 1)1552 self._test_LiveFSFilePruner('application/octet-stream', 1)
15421553
1543 def test_LiveFSFilePruner_old_text_files(self):1554 def test_LiveFSFilePruner_old_text_files(self):
@@ -1550,6 +1561,25 @@
1550 self._test_LiveFSFilePruner(1561 self._test_LiveFSFilePruner(
1551 'application/octet-stream', 0, expected_count=1)1562 'application/octet-stream', 0, expected_count=1)
15521563
1564 def test_LiveFSFilePruner_custom_interval_old_binary_files(self):
1565 # If a custom retention interval is set, LiveFS binary files
1566 # attached to builds over that interval old are pruned.
1567 self._test_LiveFSFilePruner(
1568 'application/octet-stream', 7, keep_binary_files_days=7)
1569
1570 def test_LiveFSFilePruner_custom_interval_recent_binary_files(self):
1571 # If a custom retention interval is set, LiveFS binary files
1572 # attached to builds less than that interval old are pruned.
1573 self._test_LiveFSFilePruner(
1574 'application/octet-stream', 6, keep_binary_files_days=7,
1575 expected_count=1)
1576
1577 def test_LiveFSFilePruner_null_interval_disables_pruning(self):
1578 # A null retention interval disables pruning.
1579 self._test_LiveFSFilePruner(
1580 'application/octet-stream', 100, keep_binary_files_days=None,
1581 expected_count=1)
1582
1553 def _test_SnapFilePruner(self, filename, job_status, interval,1583 def _test_SnapFilePruner(self, filename, job_status, interval,
1554 expected_count=0):1584 expected_count=0):
1555 # Garbo should (or should not, if `expected_count=1`) remove snap1585 # Garbo should (or should not, if `expected_count=1`) remove snap
15561586
=== modified file 'lib/lp/soyuz/browser/livefs.py'
--- lib/lp/soyuz/browser/livefs.py 2018-07-16 00:51:23 +0000
+++ lib/lp/soyuz/browser/livefs.py 2019-06-12 12:20:38 +0000
@@ -1,4 +1,4 @@
1# Copyright 2014-2018 Canonical Ltd. This software is licensed under the1# Copyright 2014-2019 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""LiveFS views."""4"""LiveFS views."""
@@ -181,6 +181,7 @@
181 'name',181 'name',
182 'require_virtualized',182 'require_virtualized',
183 'relative_build_score',183 'relative_build_score',
184 'keep_binary_files_days',
184 ])185 ])
185 distro_series = Choice(186 distro_series = Choice(
186 vocabulary='BuildableDistroSeries', title=u'Distribution series')187 vocabulary='BuildableDistroSeries', title=u'Distribution series')
@@ -282,13 +283,18 @@
282283
283 label = title284 label = title
284285
285 field_names = ['require_virtualized', 'relative_build_score']286 field_names = [
287 'require_virtualized',
288 'relative_build_score',
289 'keep_binary_files_days',
290 ]
286291
287 @property292 @property
288 def initial_values(self):293 def initial_values(self):
289 return {294 return {
290 'require_virtualized': self.context.require_virtualized,295 'require_virtualized': self.context.require_virtualized,
291 'relative_build_score': self.context.relative_build_score,296 'relative_build_score': self.context.relative_build_score,
297 'keep_binary_files_days': self.context.keep_binary_files_days,
292 }298 }
293299
294300
295301
=== modified file 'lib/lp/soyuz/interfaces/livefs.py'
--- lib/lp/soyuz/interfaces/livefs.py 2017-07-18 16:22:03 +0000
+++ lib/lp/soyuz/interfaces/livefs.py 2019-06-12 12:20:38 +0000
@@ -1,4 +1,4 @@
1# Copyright 2014-2017 Canonical Ltd. This software is licensed under the1# Copyright 2014-2019 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Live filesystem interfaces."""4"""Live filesystem interfaces."""
@@ -262,6 +262,13 @@
262 "A delta to apply to all build scores for the live filesystem. "262 "A delta to apply to all build scores for the live filesystem. "
263 "Builds with a higher score will build sooner.")))263 "Builds with a higher score will build sooner.")))
264264
265 keep_binary_files_days = exported(Int(
266 title=_("Binary file retention period"),
267 required=False, readonly=False,
268 description=_(
269 "Keep binary files attached to builds of this live filesystem "
270 "for at least this many days. If unset, disable pruning.")))
271
265272
266class ILiveFSAdminAttributes(Interface):273class ILiveFSAdminAttributes(Interface):
267 """`ILiveFS` attributes that can be edited by admins.274 """`ILiveFS` attributes that can be edited by admins.
268275
=== modified file 'lib/lp/soyuz/model/livefs.py'
--- lib/lp/soyuz/model/livefs.py 2017-07-18 16:22:03 +0000
+++ lib/lp/soyuz/model/livefs.py 2019-06-12 12:20:38 +0000
@@ -1,4 +1,4 @@
1# Copyright 2014-2017 Canonical Ltd. This software is licensed under the1# Copyright 2014-2019 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4__metaclass__ = type4__metaclass__ = type
@@ -6,6 +6,9 @@
6 'LiveFS',6 'LiveFS',
7 ]7 ]
88
9from datetime import timedelta
10import math
11
9import pytz12import pytz
10from storm.locals import (13from storm.locals import (
11 Bool,14 Bool,
@@ -17,6 +20,7 @@
17 Reference,20 Reference,
18 Store,21 Store,
19 Storm,22 Storm,
23 TimeDelta,
20 Unicode,24 Unicode,
21 )25 )
22from zope.component import getUtility26from zope.component import getUtility
@@ -113,8 +117,12 @@
113117
114 relative_build_score = Int(name='relative_build_score', allow_none=False)118 relative_build_score = Int(name='relative_build_score', allow_none=False)
115119
120 keep_binary_files_interval = TimeDelta(
121 name='keep_binary_files_interval', allow_none=True)
122
116 def __init__(self, registrant, owner, distro_series, name,123 def __init__(self, registrant, owner, distro_series, name,
117 metadata, require_virtualized, date_created):124 metadata, require_virtualized, keep_binary_files_days,
125 date_created):
118 """Construct a `LiveFS`."""126 """Construct a `LiveFS`."""
119 if not getFeatureFlag(LIVEFS_FEATURE_FLAG):127 if not getFeatureFlag(LIVEFS_FEATURE_FLAG):
120 raise LiveFSFeatureDisabled128 raise LiveFSFeatureDisabled
@@ -128,6 +136,24 @@
128 self.relative_build_score = 0136 self.relative_build_score = 0
129 self.date_created = date_created137 self.date_created = date_created
130 self.date_last_modified = date_created138 self.date_last_modified = date_created
139 self.keep_binary_files_days = keep_binary_files_days
140
141 @property
142 def keep_binary_files_days(self):
143 """See `ILiveFS`."""
144 # Rounding up preserves the "at least this many days" part of the
145 # contract, and makes the interface simpler.
146 if self.keep_binary_files_interval is not None:
147 return int(math.ceil(
148 self.keep_binary_files_interval.total_seconds() / 86400))
149 else:
150 return None
151
152 @keep_binary_files_days.setter
153 def keep_binary_files_days(self, days):
154 """See `ILiveFS`."""
155 self.keep_binary_files_interval = (
156 timedelta(days=days) if days is not None else None)
131157
132 def requestBuild(self, requester, archive, distro_arch_series, pocket,158 def requestBuild(self, requester, archive, distro_arch_series, pocket,
133 unique_key=None, metadata_override=None, version=None):159 unique_key=None, metadata_override=None, version=None):
@@ -230,7 +256,8 @@
230 """See `ILiveFSSet`."""256 """See `ILiveFSSet`."""
231257
232 def new(self, registrant, owner, distro_series, name, metadata,258 def new(self, registrant, owner, distro_series, name, metadata,
233 require_virtualized=True, date_created=DEFAULT):259 require_virtualized=True, keep_binary_files_days=1,
260 date_created=DEFAULT):
234 """See `ILiveFSSet`."""261 """See `ILiveFSSet`."""
235 if not registrant.inTeam(owner):262 if not registrant.inTeam(owner):
236 if owner.is_team:263 if owner.is_team:
@@ -248,7 +275,7 @@
248 store = IMasterStore(LiveFS)275 store = IMasterStore(LiveFS)
249 livefs = LiveFS(276 livefs = LiveFS(
250 registrant, owner, distro_series, name, metadata,277 registrant, owner, distro_series, name, metadata,
251 require_virtualized, date_created)278 require_virtualized, keep_binary_files_days, date_created)
252 store.add(livefs)279 store.add(livefs)
253280
254 return livefs281 return livefs
255282
=== modified file 'lib/lp/soyuz/tests/test_livefs.py'
--- lib/lp/soyuz/tests/test_livefs.py 2019-01-25 11:47:20 +0000
+++ lib/lp/soyuz/tests/test_livefs.py 2019-06-12 12:20:38 +0000
@@ -1,4 +1,4 @@
1# Copyright 2014-2018 Canonical Ltd. This software is licensed under the1# Copyright 2014-2019 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Test live filesystems."""4"""Test live filesystems."""
@@ -122,6 +122,26 @@
122 with celebrity_logged_in("buildd_admin"):122 with celebrity_logged_in("buildd_admin"):
123 livefs.relative_build_score = 100123 livefs.relative_build_score = 100
124124
125 def test_keep_binary_files_days(self):
126 # Buildd admins can change the binary file retention period of a
127 # LiveFS, but ordinary users cannot.
128 livefs = self.factory.makeLiveFS()
129 self.assertEqual(1, livefs.keep_binary_files_days)
130 with person_logged_in(livefs.owner):
131 self.assertRaises(
132 Unauthorized, setattr, livefs, "keep_binary_files_days", 2)
133 with celebrity_logged_in("buildd_admin"):
134 livefs.keep_binary_files_days = 2
135 self.assertEqual(2, livefs.keep_binary_files_days)
136 self.assertEqual(
137 timedelta(days=2),
138 removeSecurityProxy(livefs).keep_binary_files_interval)
139 with celebrity_logged_in("buildd_admin"):
140 livefs.keep_binary_files_days = None
141 self.assertIsNone(livefs.keep_binary_files_days)
142 self.assertIsNone(
143 removeSecurityProxy(livefs).keep_binary_files_interval)
144
125 def test_requestBuild(self):145 def test_requestBuild(self):
126 # requestBuild creates a new LiveFSBuild.146 # requestBuild creates a new LiveFSBuild.
127 livefs = self.factory.makeLiveFS()147 livefs = self.factory.makeLiveFS()
128148
=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py 2019-05-06 14:22:34 +0000
+++ lib/lp/testing/factory.py 2019-06-12 12:20:38 +0000
@@ -4628,7 +4628,7 @@
46284628
4629 def makeLiveFS(self, registrant=None, owner=None, distroseries=None,4629 def makeLiveFS(self, registrant=None, owner=None, distroseries=None,
4630 name=None, metadata=None, require_virtualized=True,4630 name=None, metadata=None, require_virtualized=True,
4631 date_created=DEFAULT):4631 keep_binary_files_days=1, date_created=DEFAULT):
4632 """Make a new LiveFS."""4632 """Make a new LiveFS."""
4633 if registrant is None:4633 if registrant is None:
4634 registrant = self.makePerson()4634 registrant = self.makePerson()
@@ -4642,7 +4642,9 @@
4642 metadata = {}4642 metadata = {}
4643 livefs = getUtility(ILiveFSSet).new(4643 livefs = getUtility(ILiveFSSet).new(
4644 registrant, owner, distroseries, name, metadata,4644 registrant, owner, distroseries, name, metadata,
4645 require_virtualized=require_virtualized, date_created=date_created)4645 require_virtualized=require_virtualized,
4646 keep_binary_files_days=keep_binary_files_days,
4647 date_created=date_created)
4646 IStore(livefs).flush()4648 IStore(livefs).flush()
4647 return livefs4649 return livefs
46484650