Merge lp:~mwhudson/ubuntu-cdimage/netboot-tarball into lp:ubuntu-cdimage

Proposed by Michael Hudson-Doyle
Status: Merged
Approved by: Łukasz Zemczak
Approved revision: 2053
Merged at revision: 2065
Proposed branch: lp:~mwhudson/ubuntu-cdimage/netboot-tarball
Merge into: lp:ubuntu-cdimage
Diff against target: 384 lines (+190/-3)
5 files modified
lib/cdimage/build.py (+18/-0)
lib/cdimage/livefs.py (+2/-1)
lib/cdimage/tests/test_livefs.py (+2/-0)
lib/cdimage/tests/test_tree.py (+45/-0)
lib/cdimage/tree.py (+123/-2)
To merge this branch: bzr merge lp:~mwhudson/ubuntu-cdimage/netboot-tarball
Reviewer Review Type Date Requested Status
Łukasz Zemczak Approve
Review via email: mp+423215@code.launchpad.net

Commit message

Publish netboot tarballs from livecd-rootfs

Description of the change

This is the companion part to https://code.launchpad.net/~mwhudson/livecd-rootfs/+git/livecd-rootfs/+merge/423209.

It downloads the netboot tarball that livecd-rootfs change makes and publishes it alongside the ISO, rewriting the config files to refer to where the ISO is being published. It turns out that if you name various files *just so* then a lot of things are taken care of by general machinery, but that general machinery is pretty opaque. Oh well!

I've tried to keep the code fairly generic so if we want, say, the desktop build to start spitting out netboot tarballs no changes should be required here.

The glaring thing this is missing that has to be addressed before merge is release publication, which will have to rewrite the config files again (which probably means we should put the unmodified netboot tarball file in the published directory too but hide it from the index... hmm...).

The other thing that I want to do is generate some nice headers and footers for the netboot/ directory and generate some snippets somewhere that have example dhcpd and dnsmasq config.

I've tested code like this, but I refactored a bit before review and haven't tested exactly this code. So it might be a bit broken.

To post a comment you must log in.
2040. By Michael Hudson-Doyle

download netboot tarball from livefs

2041. By Michael Hudson-Doyle

copy netboot tarball to location suitable for publication to find it

2042. By Michael Hudson-Doyle

copy any netboot tarball to target directory when publishing

2043. By Michael Hudson-Doyle

rewrite #ISOURL# to the url to the iso when publishing netboot tarball

2044. By Michael Hudson-Doyle

unpack netboot tarball during publication as well

Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote :

I've tested this now and it seems to work! I uploaded the result of a run to https://people.canonical.com/~mwh/20220525/ and it all looks fairly plausible to me (don't try to use the ISOs in that page, I shrank them to 100M before upload)

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

In general this feels good. Of course the re-tarring of the tarball is a bit controversial, but makes sense in this case - especially that per the specification we also want the contents to be untarred in the netboot/ directory anyway.

Left a few inline comments with some things that need changing though. Another thing that needs fixing is: we need unit tests for this new case. The cdimage codebase has usually quite a good test suit, so whenever we add something new, we should make sure it's tested as well. This way we can know if it works-ish.

That being said, I think currently the unit tests are failing! Mostly because of some issues with the simplestreams tests. I have a ticket open for that, so I'll try to fix those in the nearest week. For now, I'd recommend to just running a clean cdimage test run, noting which tests fail and then running with the new codebase + new tests ;p Sorry about that.

review: Needs Fixing
2045. By Michael Hudson-Doyle

add simple test to test_livefs.py

2046. By Michael Hudson-Doyle

add tests for and comments to publish_netboot

Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote :

> In general this feels good.

Thanks!

> Of course the re-tarring of the tarball is a bit
> controversial, but makes sense in this case - especially that per the
> specification we also want the contents to be untarred in the netboot/
> directory anyway.

Yeah, that's definitely feels a bit unusual. One consequence is that we'll have to do it again when doing the release publication -- not sure the code is set up for that though, does it recompute checksums as part of release publication? (I guess given that in general, not every arch gets published, it must?)

> Left a few inline comments with some things that need changing though. Another
> thing that needs fixing is: we need unit tests for this new case. The cdimage
> codebase has usually quite a good test suit, so whenever we add something new,
> we should make sure it's tested as well. This way we can know if it works-ish.

Good point. I've added test cases to test_livefs.py and test_tree.py. Couldn't figure out how to test the build.py change!

Revision history for this message
Michael Hudson-Doyle (mwhudson) :
2047. By Michael Hudson-Doyle

release publication for netboot tarballs, maybe?

Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote :

I added some stuff around release publication but it doesn't get the release URLs right. I don't think I understand how the directories release publication puts things in map to URLs on releases.ubuntu.com yet...

2048. By Michael Hudson-Doyle

a little refactoring. add a comment where there is something wrong

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

This looks okay. There's two inline comments that can be addressed but don't have to be.

As for the publish bits - without any test coverage for those, it's hard to see if the release URLs are right or not. Can you maybe paste what end URLs are you getting in the tarball with the current changes? It would make it much easier for me to figure out how to make the mapping better.

Let's merge this tomorrow after we discuss those ^

review: Approve
2049. By Michael Hudson-Doyle

fix tarball save path

2050. By Michael Hudson-Doyle

more fixes

2051. By Michael Hudson-Doyle

rewrite SimpleReleaseTree.url_for_path a bit

2052. By Michael Hudson-Doyle

logging, dry run

Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote :

> This looks okay. There's two inline comments that can be addressed but don't
> have to be.

Thanks. I actually hadn't pushed my most recent commit and it was interesting coming back to this code after a week or two, I think it helped me see the wood for the trees a bit.

> As for the publish bits - without any test coverage for those, it's hard to
> see if the release URLs are right or not. Can you maybe paste what end URLs
> are you getting in the tarball with the current changes? It would make it much
> easier for me to figure out how to make the mapping better.

I think I have this right now, lets see, for daily publication:

SUBPROJECT=live DIST=jammy PROJECT=ubuntu-server-live for-project ubuntu-server cron.daily-live

gives in the log:

Rewriting /home/ubuntu/cdimage/www/full/ubuntu-server/jammy/daily-live/20220621.6/.jammy-netboot-amd64.tar.gz to /home/ubuntu/cdimage/www/full/ubuntu-server/jammy/daily-live/20220621.6/jammy-netboot-amd64.tar.gz with iso_url=https://cdimage.ubuntu.com/ubuntu-server/jammy/daily-live/20220621.6/jammy-live-server-amd64.iso

And publish-release seems to do the right thing now:

DIST=jammy for-project ubuntu publish-release --dry-run ubuntu-server/daily-live 20220621.5 live-server yes
...
Rewriting /home/ubuntu/cdimage/www/full/ubuntu-server/jammy/daily-live/20220621.5/.jammy-netboot-amd64.tar.gz to /home/ubuntu/cdimage/www/simple/jammy/ubuntu-22.04-netboot-amd64.tar.gz with iso_url=https://releases.ubuntu.com/22.04/ubuntu-22.04-live-server-amd64.iso

DIST=jammy for-project ubuntu publish-release --dry-run ubuntu-server/daily-live 20220621.5 live-server named
...
Rewriting /home/ubuntu/cdimage/www/full/ubuntu-server/jammy/daily-live/20220621.5/.jammy-netboot-amd64.tar.gz to /home/ubuntu/cdimage/www/full/releases/jammy/release/ubuntu-22.04-netboot-amd64.tar.gz with iso_url=https://cdimage.ubuntu.com/releases/jammy/release/ubuntu-22.04-live-server-amd64.iso

> Let's merge this tomorrow after we discuss those ^

Looking forward to it :-)

Revision history for this message
Michael Hudson-Doyle (mwhudson) :
2053. By Michael Hudson-Doyle

sigh

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

Okay, I think this looks good now. So merging this branch causes some tests to fail, but I can fix those after merging this. They're trivial to fix it seems. So don't worry about those! But a note for the future: even if tests are not 100% green, be sure to check if your branch doesn't cause new test failures ;p (the test case should be much happier now though)

Revision history for this message
Łukasz Zemczak (sil2100) :
review: Approve
Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote :

Thanks for the review and merge and sorry about the tests! I should see if I can write some tests for the release publication I guess.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/cdimage/build.py'
2--- lib/cdimage/build.py 2022-06-02 05:07:57 +0000
3+++ lib/cdimage/build.py 2022-06-21 04:29:40 +0000
4@@ -523,6 +523,23 @@
5 snap, "%s.%s.snap" % (output_prefix, snaptype))
6
7
8+def copy_netboot_tarballs(config):
9+ # cp $scratch/$arch.netboot.tar.gz $output/$series-netboot-$arch.tar.gz
10+ scratch_dir = os.path.join(
11+ config.root, "scratch", config.project, config.full_series,
12+ config.image_type)
13+ for arch in config.arches:
14+ netboot_path = os.path.join(
15+ live_output_directory(config),
16+ '%s.netboot.tar.gz' % (arch,))
17+ if os.path.exists(netboot_path):
18+ output_dir = os.path.join(scratch_dir, "debian-cd", arch)
19+ shutil.copy2(
20+ netboot_path, os.path.join(
21+ output_dir,
22+ '%s-netboot-%s.tar.gz' % (config.series, arch)))
23+
24+
25 def _debootstrap_script(config):
26 return "usr/share/debootstrap/scripts/%s" % config.series
27
28@@ -724,6 +741,7 @@
29 configure_splash(config)
30
31 run_debian_cd(config)
32+ copy_netboot_tarballs(config)
33 fix_permissions(config)
34
35 # Temporarily turned off for live builds.
36
37=== modified file 'lib/cdimage/livefs.py'
38--- lib/cdimage/livefs.py 2022-06-10 12:55:35 +0000
39+++ lib/cdimage/livefs.py 2022-06-21 04:29:40 +0000
40@@ -560,7 +560,7 @@
41 "azure.device.tar.gz", "raspi2.device.tar.gz", "plano.device.tar.gz",
42 "tar.xz", "iso", "os.snap", "kernel.snap", "disk1.img.xz",
43 "dragonboard.kernel.snap", "raspi2.kernel.snap",
44- "img.xz", "model-assertion", "qcow2", "yaml",
45+ "img.xz", "model-assertion", "qcow2", "yaml", "netboot.tar.gz"
46 ):
47 if item == "ext4" and arch == "armhf+nexus7":
48 for url in urls_for(
49@@ -793,6 +793,7 @@
50 elif download_live_items(config, arch, "squashfs"):
51 download_live_items(config, arch, "modules.squashfs")
52 download_live_items(config, arch, "yaml")
53+ download_live_items(config, arch, "netboot.tar.gz")
54 got_image = True
55 elif download_live_items(config, arch, "rootfs.tar.gz"):
56 got_image = True
57
58=== modified file 'lib/cdimage/tests/test_livefs.py'
59--- lib/cdimage/tests/test_livefs.py 2022-05-29 19:28:43 +0000
60+++ lib/cdimage/tests/test_livefs.py 2022-06-21 04:29:40 +0000
61@@ -1272,6 +1272,8 @@
62 "bionic", "squashfs", ["installer.squashfs"])
63 self.assert_server_live_download_items(
64 "bionic", "yaml", ["installer.yaml"])
65+ self.assert_server_live_download_items(
66+ "bionic", "netboot.tar.gz", ["netboot.tar.gz"])
67
68 def test_download_live_server_boot_items(self):
69 self.assert_server_live_download_items(
70
71=== modified file 'lib/cdimage/tests/test_tree.py'
72--- lib/cdimage/tests/test_tree.py 2022-05-29 19:39:25 +0000
73+++ lib/cdimage/tests/test_tree.py 2022-06-21 04:29:40 +0000
74@@ -24,10 +24,12 @@
75 from html.parser import HTMLParser
76 except ImportError:
77 from HTMLParser import HTMLParser
78+import io
79 import os
80 import shutil
81 import sys
82 from textwrap import dedent
83+import tarfile
84 import traceback
85
86 try:
87@@ -891,6 +893,46 @@
88 target_dir, "%s-desktop-i386.iso.zsync" % self.config.series),
89 "%s-desktop-i386.iso" % self.config.series)
90
91+ def test_publish_netboot(self):
92+ publisher = self.make_publisher("ubuntu-server", "daily-live")
93+ source_dir = publisher.image_output("amd64")
94+ tarname = "%s-netboot-amd64.tar.gz" % self.config.series
95+ isoname = "%s-live-server-amd64.iso" % self.config.series
96+ date = "20201215"
97+ os.makedirs(source_dir)
98+ with tarfile.open(os.path.join(source_dir, tarname), 'w:gz') as tf:
99+ ti = tarfile.TarInfo('config.in')
100+ content = b'v=#ISOURL#'
101+ ti.size = len(content)
102+ tf.addfile(ti, io.BytesIO(content))
103+ target_dir = os.path.join(publisher.publish_base, date)
104+ target_image = os.path.join(target_dir, isoname)
105+ touch(target_image)
106+
107+ publisher.publish_netboot("amd64", target_image)
108+
109+ self.assertCountEqual([
110+ tarname,
111+ isoname,
112+ "netboot",
113+ ], os.listdir(target_dir))
114+ self.assertCountEqual([
115+ "config",
116+ ], os.listdir(os.path.join(target_dir, "netboot")))
117+ with open(os.path.join(target_dir, "netboot/config")) as fp:
118+ self.assertEqual(
119+ 'v=https://%s/ubuntu-server/daily-live/%s/%s' % (
120+ publisher.tree.site_name, date, isoname),
121+ fp.read())
122+ with tarfile.open(os.path.join(target_dir, tarname), 'r:gz') as tf:
123+ for ti in tf:
124+ self.assertEqual(ti.name, 'config')
125+ self.assertEqual(
126+ ('v=https://%s/ubuntu-server/daily-live/%s/%s' % (
127+ publisher.tree.site_name, date, isoname)
128+ ).encode('utf-8'),
129+ tf.extractfile(ti).read())
130+
131 @mock.patch("cdimage.osextras.find_on_path", return_value=True)
132 @mock.patch("cdimage.tree.DailyTreePublisher.detect_image_extension",
133 return_value="img.xz")
134@@ -2084,6 +2126,9 @@
135 def test_publish_core_binary(self):
136 pass
137
138+ def test_publish_netboot(self):
139+ pass
140+
141 def test_publish_appliance_binary(self):
142 pass
143
144
145=== modified file 'lib/cdimage/tree.py'
146--- lib/cdimage/tree.py 2022-06-08 08:29:32 +0000
147+++ lib/cdimage/tree.py 2022-06-21 04:29:40 +0000
148@@ -20,6 +20,7 @@
149 from __future__ import print_function
150
151 import errno
152+import io
153 from itertools import count
154 from optparse import OptionParser
155 import os
156@@ -33,6 +34,7 @@
157 import stat
158 import subprocess
159 import sys
160+import tarfile
161 from textwrap import dedent
162 import time
163 import traceback
164@@ -93,6 +95,35 @@
165 subprocess.check_call(command)
166
167
168+def rewrite_and_unpack_tarball(dry_run, source_path, target_path, iso_url):
169+ logger.info(
170+ "Rewriting %s to %s with iso_url=%s",
171+ source_path, target_path, iso_url)
172+ if dry_run:
173+ return
174+ iso_url_b = iso_url.encode('utf-8')
175+ netboot_dir = os.path.join(os.path.dirname(target_path), 'netboot')
176+ osextras.ensuredir(netboot_dir)
177+
178+ with tarfile.open(source_path) as inf:
179+ with tarfile.open(target_path, 'w:gz') as outf:
180+ for ti in inf:
181+ if ti.name.endswith('.in'):
182+ new_ti = inf.getmember(ti.name)
183+ new_ti.name = ti.name[:-3]
184+ content = inf.extractfile(ti).read()
185+ content = content.replace(b"#ISOURL#", iso_url_b)
186+ new_ti.size = len(content)
187+ with open(
188+ os.path.join(netboot_dir, new_ti.name),
189+ 'wb') as fp:
190+ fp.write(content)
191+ outf.addfile(new_ti, io.BytesIO(content))
192+ else:
193+ inf.extract(ti, netboot_dir)
194+ outf.addfile(ti, inf.extractfile(ti))
195+
196+
197 class Tree:
198 """A publication tree."""
199
200@@ -168,6 +199,13 @@
201 """Return the public host name corresponding to this tree."""
202 raise NotImplementedError
203
204+ def url_for_path(self, path):
205+ """Return the public URL for the file at `path`.
206+
207+ `path` must be under self.directory.
208+ """
209+ raise NotImplementedError
210+
211 def path_to_manifest(self, path):
212 """Return a manifest file entry for a tree-relative path.
213
214@@ -487,6 +525,8 @@
215 return "classroom server %s" % cd
216 else:
217 return "server install %s" % cd
218+ elif publish_type == "netboot":
219+ return "netboot tarball"
220 elif publish_type == "legacy-server":
221 return "legacy server install %s" % cd
222 elif publish_type == "serveraddon":
223@@ -579,6 +619,10 @@
224 sentences.append(
225 "The install %s allows you to install %s permanently on a "
226 "computer." % (cd, capproject))
227+ elif publish_type == "netboot":
228+ sentences.append(
229+ "The netboot tarball contains files needed to boot the %s "
230+ "installer over the network." % (capproject,))
231 elif publish_type == "alternate":
232 sentences.append(
233 "The alternate install %s allows you to perform certain "
234@@ -1121,6 +1165,7 @@
235 all_publish_types = (
236 "live", "desktop",
237 "live-server",
238+ "netboot",
239 "legacy-server",
240 "server", "install", "alternate",
241 "serveraddon", "addon",
242@@ -1653,7 +1698,8 @@
243 print("ReadmeName FOOTER.html", file=htaccess)
244 print(
245 "IndexIgnore .htaccess HEADER.html FOOTER.html "
246- "published-ec2-daily.txt published-ec2-release.txt",
247+ "published-ec2-daily.txt published-ec2-release.txt "
248+ ".*.tar.gz",
249 file=htaccess)
250 print(
251 "IndexOptions NameWidth=* DescriptionWidth=* "
252@@ -1760,6 +1806,16 @@
253 def site_name(self):
254 return "cdimage.ubuntu.com"
255
256+ def url_for_path(self, path):
257+ logger.info(
258+ "url_for_path(%s), self.directory = %s", path, self.directory)
259+ if not path.startswith(self.directory):
260+ raise Exception(
261+ "url_for_path(%r) did not start with self.directory (%r)"
262+ % (path, self.directory))
263+ url_path = path[len(self.directory):].lstrip('/')
264+ return "https://%s/%s" % (self.site_name, url_path)
265+
266 def manifest_files(self):
267 """Yield all the files to include in a manifest of this tree."""
268 seen_inodes = []
269@@ -2002,6 +2058,30 @@
270 for line in jigdo_in:
271 jigdo_out.write(line.replace(from_line, to_line))
272
273+ def publish_netboot(self, arch, image_path):
274+ # Publishing a netboot tarball is a bit more complicated than
275+ # just copying it into place, as we also unpack it into a
276+ # netboot/ directory and replace rewrite any foo.cfg.in files
277+ # referencing #ISOURL# to foo.cfg referencing the actual URL
278+ # of the image.
279+ #
280+ # We also save a copy of the netboot tarball so we can rewrite
281+ # it again during release publication.
282+ tarname = "%s-netboot-%s.tar.gz" % (self.config.series, arch)
283+ source_path = os.path.join(self.image_output(arch), tarname)
284+ if not os.path.exists(source_path):
285+ return
286+
287+ save_target_path = os.path.join(
288+ os.path.dirname(image_path), '.' + tarname)
289+ target_path = os.path.join(os.path.dirname(image_path), tarname)
290+
291+ shutil.move(source_path, save_target_path)
292+
293+ rewrite_and_unpack_tarball(
294+ False, save_target_path, target_path,
295+ self.tree.url_for_path(image_path))
296+
297 def publish_binary(self, publish_type, arch, date):
298 in_prefix = "%s-%s-%s" % (self.config.series, publish_type, arch)
299 if publish_type == "live-core":
300@@ -2024,9 +2104,11 @@
301 logger.info("Publishing %s ..." % arch)
302 osextras.ensuredir(target_dir)
303 extension = self.detect_image_extension(source_prefix)
304+ target_path = "%s.%s" % (target_prefix, extension)
305 shutil.move(
306 "%s.%s" % (source_prefix, self.source_extension),
307- "%s.%s" % (target_prefix, extension))
308+ target_path)
309+ self.publish_netboot(arch, target_path)
310 if os.path.exists("%s.list" % source_prefix):
311 shutil.move("%s.list" % source_prefix, "%s.list" % target_prefix)
312 self.checksum_dirs.append(source_dir)
313@@ -2938,6 +3020,12 @@
314 directory = os.path.join(config.root, "www", "simple")
315 super(SimpleReleaseTree, self).__init__(config, directory)
316
317+ def url_for_path(self, path):
318+ series = self.config["DIST"]
319+ version = getattr(series, "pointversion", series.version)
320+ basename = os.path.basename(path)
321+ return "https://%s/%s/%s" % (self.site_name, version, basename)
322+
323 def get_publisher(self, image_type, official, status=None, dry_run=False):
324 return SimpleReleasePublisher(
325 self, image_type, official, status=status, dry_run=dry_run)
326@@ -3200,6 +3288,24 @@
327 def want_torrent(self, publish_type):
328 raise NotImplementedError
329
330+ def publish_release_netboot(self, daily_dir, prefix, arch, image_path):
331+ """Publish release images for a single architecture."""
332+ logger.info("Copying netboot-%s image ..." % (arch, ))
333+
334+ source_tarname = ".%s-netboot-%s.tar.gz" % (self.config.series, arch)
335+ source_tarpath = os.path.join(daily_dir, source_tarname)
336+
337+ if not os.path.exists(source_tarpath):
338+ return
339+
340+ target_tarname = "%s-netboot-%s.tar.gz" % (prefix, arch)
341+ target_tarpath = os.path.join(
342+ os.path.dirname(image_path), target_tarname)
343+
344+ rewrite_and_unpack_tarball(
345+ self.dry_run, source_tarpath, target_tarpath,
346+ self.tree.url_for_path(image_path))
347+
348 def publish_release_arch(self, source, date, publish_type, arch):
349 """Publish release images for a single architecture."""
350 logger.info("Copying %s-%s image ..." % (publish_type, arch))
351@@ -3236,9 +3342,12 @@
352 return os.path.join(
353 torrent_dir, "%s%s%s" % (base_plain, sep, ext))
354
355+ main_img = None
356+
357 for ext in ("iso", "img", "img.gz", "img.xz", "tar.gz", "img.tar.gz",
358 "tar.xz"):
359 if os.path.exists(daily(ext)):
360+ main_img = daily(ext)
361 break
362 else:
363 return
364@@ -3254,8 +3363,20 @@
365 self.copy(daily(ext), pool(ext))
366 if self.want_dist:
367 self.symlink(pool(ext), dist(ext))
368+ if daily(ext) == main_img:
369+ self.publish_release_netboot(
370+ os.path.dirname(daily(ext)),
371+ prefix_status,
372+ arch,
373+ dist(ext))
374 if self.want_full:
375 self.copy(daily(ext), full(ext))
376+ if daily(ext) == main_img:
377+ self.publish_release_netboot(
378+ os.path.dirname(daily(ext)),
379+ prefix,
380+ arch,
381+ full(ext))
382
383 for ext in (
384 "initrd-ec2", "initrd-virtual", "vmlinuz-ec2", "vmlinuz-virtual",

Subscribers

People subscribed via source and target branches