Merge lp:~sil2100/ubuntu-cdimage/purge-core-images into lp:ubuntu-cdimage

Proposed by Łukasz Zemczak
Status: Merged
Merged at revision: 1790
Proposed branch: lp:~sil2100/ubuntu-cdimage/purge-core-images
Merge into: lp:ubuntu-cdimage
Diff against target: 285 lines (+150/-30)
4 files modified
etc/purge-count (+2/-0)
etc/purge-days (+1/-1)
lib/cdimage/tests/test_tree.py (+97/-8)
lib/cdimage/tree.py (+50/-21)
To merge this branch: bzr merge lp:~sil2100/ubuntu-cdimage/purge-core-images
Reviewer Review Type Date Requested Status
Steve Langasek Approve
Review via email: mp+356193@code.launchpad.net

Commit message

Add support for another retention policy: purge-count. Refactor some of the purge code to support this better. Add tests. By default keep up to 5 core images.

Description of the change

Add support for another retention policy: purge-count. Refactor some of the purge code to support this better. Add tests. By default keep up to 5 core images.

This is my proposition of adding an additional purge policy to cdimage. The way it works is basically if no image-count based set is defined, the purge mechanics should work as they did before. One can define only one of them or both at once. In case a combination of age-based and count-based purging is defined, the first one to cause a purge wins. Example:

 * If one defines keeping 3 days worth of images and up to 5 images, and 4 days-worth of images appear, the oldest one will be purged (leaving 3 images per age-count).
 * If one defines keeping 5 days worth of images and up to 3 images, and 4 days-worth of images appear, the oldest one will be purged (leaving 3 images per image-count).

This can be useful in some future cases where we want to have this mixture of 'keep 3 days worth of images but not more than 5 at once'.

To post a comment you must log in.
Revision history for this message
Steve Langasek (vorlon) wrote :

Mostly looks good, one comment inline, the other issue is that I don't actually see anywhere that you've implemented the "keep up to 5 core images" part of this.

review: Needs Fixing
Revision history for this message
Łukasz Zemczak (sil2100) wrote :

eeek, I think I forgot a bzr add for the up-to-5-days. Let me find the bzr branch and re-push. Thanks for the review as well! Let me also look into your inline proposition.

Revision history for this message
Łukasz Zemczak (sil2100) wrote :

Commented inline. Should I rewrite the part to handle both cases as an error, or should we just go with the approach presented here? Thanks for the review!

1750. By Łukasz Zemczak

bzr add the missint etc/purge-count file.

Revision history for this message
Steve Langasek (vorlon) wrote :

It is still my preference that we error out instead of pruning images according to rules in two separate files. Better to have an error than data loss; and we can always revisit later if someone has a use case for pruning based on the intersection of the two rules. (Today, we have no such use case.)

1751. By Łukasz Zemczak

As per Steve's request, do not support stating both purge-count and purge-days for a project - treat it as an error. Add test.

Revision history for this message
Łukasz Zemczak (sil2100) wrote :

Ok, this should be good for a final re-review. Thanks!

Revision history for this message
Steve Langasek (vorlon) wrote :

LGTM.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'etc/purge-count'
2--- etc/purge-count 1970-01-01 00:00:00 +0000
3+++ etc/purge-count 2019-03-06 17:57:04 +0000
4@@ -0,0 +1,2 @@
5+# Keep up to 5 images for Ubuntu Core projects
6+ubuntu-core 5
7\ No newline at end of file
8
9=== modified file 'etc/purge-days'
10--- etc/purge-days 2018-04-20 01:15:18 +0000
11+++ etc/purge-days 2019-03-06 17:57:04 +0000
12@@ -5,7 +5,7 @@
13 dvd 2
14 # Only purge TheOpenCDv3 images manually.
15 tocd3/daily-live 0
16-# Only purge ubuntu-core images manually.
17+# Ubuntu Core images are not purged per age
18 ubuntu-core 0
19 ubuntu-mid 3
20 ubuntu-netbook 3
21
22=== modified file 'lib/cdimage/tests/test_tree.py'
23--- lib/cdimage/tests/test_tree.py 2019-02-27 21:21:04 +0000
24+++ lib/cdimage/tests/test_tree.py 2019-03-06 17:57:04 +0000
25@@ -1540,11 +1540,11 @@
26 "20120807",
27 ["ubuntu/daily-live/%s-desktop-i386" % self.config.series])
28
29- def test_get_purge_days_no_config(self):
30+ def test_get_purge_data_no_config(self):
31 publisher = self.make_publisher("ubuntu", "daily")
32- self.assertIsNone(publisher.get_purge_days("daily"))
33+ self.assertIsNone(publisher.get_purge_data("daily", "purge-days"))
34
35- def test_get_purge_days(self):
36+ def test_get_purge_data(self):
37 publisher = self.make_publisher("ubuntu", "daily")
38 with mkfile(os.path.join(
39 self.temp_dir, "etc", "purge-days")) as purge_days:
40@@ -1553,9 +1553,28 @@
41
42 daily 1
43 daily-live 2"""), file=purge_days)
44- self.assertEqual(1, publisher.get_purge_days("daily"))
45- self.assertEqual(2, publisher.get_purge_days("daily-live"))
46- self.assertIsNone(publisher.get_purge_days("dvd"))
47+ self.assertEqual(1, publisher.get_purge_data("daily", "purge-days"))
48+ self.assertEqual(2, publisher.get_purge_data(
49+ "daily-live", "purge-days"))
50+ self.assertIsNone(publisher.get_purge_data("dvd", "purge-days"))
51+
52+ def test_get_purge_count_no_config(self):
53+ publisher = self.make_publisher("ubuntu", "daily")
54+ self.assertIsNone(publisher.get_purge_data("daily", "purge-count"))
55+
56+ def test_get_purge_count(self):
57+ publisher = self.make_publisher("ubuntu", "daily")
58+ with mkfile(os.path.join(
59+ self.temp_dir, "etc", "purge-count")) as purge_count:
60+ print(dedent("""\
61+ # comment
62+
63+ daily 1
64+ daily-live 2"""), file=purge_count)
65+ self.assertEqual(1, publisher.get_purge_data("daily", "purge-count"))
66+ self.assertEqual(2, publisher.get_purge_data(
67+ "daily-live", "purge-count"))
68+ self.assertIsNone(publisher.get_purge_data("dvd", "purge-count"))
69
70 @mock.patch("time.time", return_value=date_to_time("20130321"))
71 def test_purge_removes_old(self, *args):
72@@ -1575,8 +1594,8 @@
73 purge_desc = project
74 self.assertLogEqual([
75 "Purging %s/daily images older than 1 day ..." % project,
76+ "Purging %s/daily/20130319" % purge_desc,
77 "Purging %s/daily/20130318" % purge_desc,
78- "Purging %s/daily/20130319" % purge_desc,
79 ])
80 self.assertCountEqual(
81 ["20130320", "20130321"], os.listdir(publisher.publish_base))
82@@ -1673,11 +1692,81 @@
83 purge_desc = project
84 self.assertLogEqual([
85 "Purging %s/daily images older than 1 day ..." % project,
86- "Purging %s/daily/20130319" % purge_desc,
87 "Purging %s/daily/20130319.1" % purge_desc,
88+ "Purging %s/daily/20130319" % purge_desc,
89 ])
90 self.assertEqual([], os.listdir(publisher.publish_base))
91
92+ def test_purge_leaves_count(self, *args):
93+ publisher = self.make_publisher("ubuntu", "daily")
94+ for name in "20130318", "20130319", "20130320", "20130321":
95+ touch(os.path.join(publisher.publish_base, name, "file"))
96+ with mkfile(os.path.join(
97+ self.temp_dir, "etc", "purge-count")) as purge_count:
98+ print("daily 2", file=purge_count)
99+ self.capture_logging()
100+ publisher.purge()
101+ if self.config["UBUNTU_DEFAULTS_LOCALE"]:
102+ project = "ubuntu-zh_CN"
103+ purge_desc = "%s/%s" % (project, self.config.series)
104+ else:
105+ project = "ubuntu"
106+ purge_desc = project
107+ self.assertLogEqual([
108+ "Purging %s/daily images to leave only the latest 2 images "
109+ "..." % project,
110+ "Purging %s/daily/20130319" % purge_desc,
111+ "Purging %s/daily/20130318" % purge_desc,
112+ ])
113+ self.assertCountEqual(
114+ ["20130320", "20130321"], os.listdir(publisher.publish_base))
115+
116+ @mock.patch("time.time", return_value=date_to_time("20130321"))
117+ def test_purge_both_days_and_count_raises(self, *args):
118+ publisher = self.make_publisher("ubuntu", "daily")
119+ for name in "20130321", "20130321.1", "20130321.2", "20130321.3":
120+ touch(os.path.join(publisher.publish_base, name, "file"))
121+ with mkfile(os.path.join(
122+ self.temp_dir, "etc", "purge-days")) as purge_days:
123+ print("daily 1", file=purge_days)
124+ with mkfile(os.path.join(
125+ self.temp_dir, "etc", "purge-count")) as purge_count:
126+ print("daily 3", file=purge_count)
127+ if self.config["UBUNTU_DEFAULTS_LOCALE"]:
128+ project = "ubuntu-zh_CN"
129+ else:
130+ project = "ubuntu"
131+ self.capture_logging()
132+ self.assertRaisesRegex(
133+ Exception, r"Both purge-days and purge-count are defined for "
134+ "%s/daily. Such scenario is currently "
135+ "unsupported." % project,
136+ publisher.purge)
137+ self.assertCountEqual(
138+ ["20130321", "20130321.1", "20130321.2", "20130321.3"],
139+ os.listdir(publisher.publish_base))
140+
141+ @mock.patch("time.time", return_value=date_to_time("20130321"))
142+ def test_purge_no_purge(self, *args):
143+ publisher = self.make_publisher("ubuntu", "daily")
144+ for name in "20130318", "20130319", "20130320", "20130321":
145+ touch(os.path.join(publisher.publish_base, name, "file"))
146+ with mkfile(os.path.join(
147+ self.temp_dir, "etc", "purge-days")) as purge_days:
148+ print("daily 0", file=purge_days)
149+ self.capture_logging()
150+ publisher.purge()
151+ if self.config["UBUNTU_DEFAULTS_LOCALE"]:
152+ project = "ubuntu-zh_CN"
153+ else:
154+ project = "ubuntu"
155+ self.assertLogEqual([
156+ "Not purging images for %s/daily" % project,
157+ ])
158+ self.assertCountEqual(
159+ ["20130318", "20130319", "20130320", "20130321"],
160+ os.listdir(publisher.publish_base))
161+
162
163 class TestChinaDailyTree(TestDailyTree):
164 def setUp(self):
165
166=== modified file 'lib/cdimage/tree.py'
167--- lib/cdimage/tree.py 2019-02-27 21:10:40 +0000
168+++ lib/cdimage/tree.py 2019-03-06 17:57:04 +0000
169@@ -2679,8 +2679,8 @@
170
171 self.post_qa(date, published)
172
173- def get_purge_days(self, key):
174- path = os.path.join(self.config.root, "etc", "purge-days")
175+ def get_purge_data(self, key, purge_type):
176+ path = os.path.join(self.config.root, "etc", purge_type)
177 try:
178 with open(path) as purge_days:
179 for line in purge_days:
180@@ -2697,7 +2697,7 @@
181 raise
182 return None
183
184- def purge(self, days=None):
185+ def purge(self, days=None, count=None):
186 project = self.project
187 if self.config["UBUNTU_DEFAULTS_LOCALE"]:
188 project = "-".join(
189@@ -2705,25 +2705,48 @@
190 project_image_type = "%s/%s" % (project, self.image_type)
191
192 if days is None:
193- days = self.get_purge_days(project)
194- if days is None:
195- days = self.get_purge_days(project_image_type)
196- if days is None:
197- days = self.get_purge_days(self.image_type)
198- if days is None:
199- logger.info("No purge time configured for %s" % project_image_type)
200- return
201- if days == 0:
202+ days = self.get_purge_data(project, "purge-days")
203+ if days is None:
204+ days = self.get_purge_data(project_image_type, "purge-days")
205+ if days is None:
206+ days = self.get_purge_data(self.image_type, "purge-days")
207+
208+ if count is None:
209+ count = self.get_purge_data(project, "purge-count")
210+ if count is None:
211+ count = self.get_purge_data(project_image_type, "purge-count")
212+ if count is None:
213+ count = self.get_purge_data(self.image_type, "purge-count")
214+
215+ if not days and not count:
216 logger.info("Not purging images for %s" % project_image_type)
217 return
218- logger.info(
219- "Purging %s images older than %d %s ..." %
220- (project_image_type, days, "day" if days == 1 else "days"))
221- oldest = time.strftime(
222- "%Y%m%d", time.gmtime(time.time() - 60 * 60 * 24 * days))
223+ elif days and count:
224+ raise Exception("Both purge-days and purge-count are defined for "
225+ "%s. Such scenario is currently unsupported." %
226+ project_image_type)
227+
228+ image_count = 0
229+ oldest = 0
230+
231+ if days:
232+ logger.info(
233+ "Purging %s images older than %d %s ..." %
234+ (project_image_type, days, "day" if days == 1 else "days"))
235+ oldest = int(time.strftime(
236+ "%Y%m%d", time.gmtime(time.time() - 60 * 60 * 24 * days)))
237+ elif count:
238+ logger.info(
239+ "Purging %s images to leave only the latest %d %s ..." %
240+ (project_image_type, count,
241+ "image" if count == 1 else "images"))
242+
243 to_purge = []
244+ publish_pending = os.path.join(self.publish_base, "pending")
245+ publish_current = os.path.join(self.publish_base, "current")
246
247- for entry in sorted(osextras.listdir_force(self.publish_base)):
248+ for entry in sorted(
249+ osextras.listdir_force(self.publish_base), reverse=True):
250 entry_path = os.path.join(self.publish_base, entry)
251
252 # Directory?
253@@ -2734,16 +2757,21 @@
254 if not entry[0].isdigit():
255 continue
256
257+ image_count += 1
258+
259 # Older than cut-off date?
260- if int(oldest) <= int(entry.split(".", 1)[0]):
261+ # Did we leave enough images already?
262+ # In the case where both cut-off date and image count have been
263+ # defined, we purge anything that doesn't satisfy both of the above
264+ # conditions at once
265+ if ((not days or oldest <= int(entry.split(".", 1)[0])) and
266+ (not count or image_count <= count)):
267 continue
268
269 # Pointed to by "pending" or "current" symlink?
270- publish_pending = os.path.join(self.publish_base, "pending")
271 if (os.path.islink(publish_pending) and
272 os.readlink(publish_pending) == entry):
273 continue
274- publish_current = os.path.join(self.publish_base, "current")
275 if os.path.islink(publish_current):
276 if os.readlink(publish_current) == entry:
277 continue
278@@ -2763,6 +2791,7 @@
279 break
280 if found_current:
281 continue
282+
283 to_purge.append((entry, entry_path))
284
285 for entry, entry_path in to_purge:

Subscribers

People subscribed via source and target branches