Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

buildextend-live: add --miniso switch #2466

Merged
merged 3 commits into from
Oct 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 21 additions & 5 deletions mantle/cmd/kola/testiso.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ const (
scenarioPXEInstall = "pxe-install"
scenarioISOInstall = "iso-install"

scenarioMinISOInstall = "miniso-install"

scenarioPXEOfflineInstall = "pxe-offline-install"
scenarioISOOfflineInstall = "iso-offline-install"

Expand All @@ -82,6 +84,7 @@ var allScenarios = map[string]bool{
scenarioPXEOfflineInstall: true,
scenarioISOInstall: true,
scenarioISOOfflineInstall: true,
scenarioMinISOInstall: true,
scenarioISOLiveLogin: true,
scenarioISOAsDisk: true,
}
Expand Down Expand Up @@ -187,7 +190,8 @@ func init() {
cmdTestIso.Flags().BoolVar(&console, "console", false, "Connect qemu console to terminal, turn off automatic initramfs failure checking")
cmdTestIso.Flags().BoolVar(&pxeAppendRootfs, "pxe-append-rootfs", false, "Append rootfs to PXE initrd instead of fetching at runtime")
cmdTestIso.Flags().StringSliceVar(&pxeKernelArgs, "pxe-kargs", nil, "Additional kernel arguments for PXE")
cmdTestIso.Flags().StringSliceVar(&scenarios, "scenarios", []string{scenarioPXEInstall, scenarioISOOfflineInstall, scenarioPXEOfflineInstall, scenarioISOLiveLogin, scenarioISOAsDisk}, fmt.Sprintf("Test scenarios (also available: %v)", []string{scenarioISOInstall}))
// XXX: add scenarioMinISOInstall to the default set once the feature is stable
cmdTestIso.Flags().StringSliceVar(&scenarios, "scenarios", []string{scenarioPXEInstall, scenarioISOOfflineInstall, scenarioPXEOfflineInstall, scenarioISOLiveLogin, scenarioISOAsDisk}, fmt.Sprintf("Test scenarios (also available: %v)", []string{scenarioISOInstall, scenarioMinISOInstall}))
cmdTestIso.Args = cobra.ExactArgs(0)

root.AddCommand(cmdTestIso)
Expand Down Expand Up @@ -299,6 +303,7 @@ func runTestIso(cmd *cobra.Command, args []string) error {
if noiso {
delete(targetScenarios, scenarioISOInstall)
delete(targetScenarios, scenarioISOOfflineInstall)
delete(targetScenarios, scenarioMinISOInstall)
delete(targetScenarios, scenarioISOLiveLogin)
}

Expand Down Expand Up @@ -375,7 +380,7 @@ func runTestIso(cmd *cobra.Command, args []string) error {
}
ranTest = true
instIso := baseInst // Pretend this is Rust and I wrote .copy()
if err := testLiveIso(ctx, instIso, filepath.Join(outputDir, scenarioISOInstall), false); err != nil {
if err := testLiveIso(ctx, instIso, filepath.Join(outputDir, scenarioISOInstall), false, false); err != nil {
return errors.Wrapf(err, "scenario %s", scenarioISOInstall)
}
printSuccess(scenarioISOInstall)
Expand All @@ -386,7 +391,7 @@ func runTestIso(cmd *cobra.Command, args []string) error {
}
ranTest = true
instIso := baseInst // Pretend this is Rust and I wrote .copy()
if err := testLiveIso(ctx, instIso, filepath.Join(outputDir, scenarioISOOfflineInstall), true); err != nil {
if err := testLiveIso(ctx, instIso, filepath.Join(outputDir, scenarioISOOfflineInstall), true, false); err != nil {
return errors.Wrapf(err, "scenario %s", scenarioISOOfflineInstall)
}
printSuccess(scenarioISOOfflineInstall)
Expand Down Expand Up @@ -417,6 +422,17 @@ func runTestIso(cmd *cobra.Command, args []string) error {
fmt.Printf("%s unsupported on %s; skipping\n", scenarioISOAsDisk, system.RpmArch())
}
}
if _, ok := targetScenarios[scenarioMinISOInstall]; ok {
if kola.CosaBuild.Meta.BuildArtifacts.LiveIso == nil {
return fmt.Errorf("build %s has no live ISO", kola.CosaBuild.Meta.Name)
}
ranTest = true
instIso := baseInst // Pretend this is Rust and I wrote .copy()
if err := testLiveIso(ctx, instIso, filepath.Join(outputDir, scenarioMinISOInstall), false, true); err != nil {
return errors.Wrapf(err, "scenario %s", scenarioMinISOInstall)
}
printSuccess(scenarioMinISOInstall)
}

if !ranTest {
panic("Nothing was tested!")
Expand Down Expand Up @@ -552,7 +568,7 @@ func testPXE(ctx context.Context, inst platform.Install, outdir string, offline
return awaitCompletion(ctx, mach.QemuInst, outdir, completionChannel, mach.BootStartedErrorChannel, []string{liveOKSignal, signalCompleteString})
}

func testLiveIso(ctx context.Context, inst platform.Install, outdir string, offline bool) error {
func testLiveIso(ctx context.Context, inst platform.Install, outdir string, offline, minimal bool) error {
tmpd, err := ioutil.TempDir("", "kola-testiso")
if err != nil {
return err
Expand Down Expand Up @@ -589,7 +605,7 @@ func testLiveIso(ctx context.Context, inst platform.Install, outdir string, offl
targetConfig.AddSystemdUnit("coreos-test-installer-multipathed.service", multipathedRoot, conf.Enable)
}

mach, err := inst.InstallViaISOEmbed(nil, liveConfig, targetConfig, outdir, offline)
mach, err := inst.InstallViaISOEmbed(nil, liveConfig, targetConfig, outdir, offline, minimal)
if err != nil {
return errors.Wrapf(err, "running iso install")
}
Expand Down
34 changes: 32 additions & 2 deletions mantle/platform/metal.go
Original file line number Diff line number Diff line change
Expand Up @@ -541,7 +541,7 @@ func (inst *Install) runPXE(kern *kernelSetup, offline bool) (*InstalledMachine,
return &instmachine, nil
}

func (inst *Install) InstallViaISOEmbed(kargs []string, liveIgnition, targetIgnition conf.Conf, outdir string, offline bool) (*InstalledMachine, error) {
func (inst *Install) InstallViaISOEmbed(kargs []string, liveIgnition, targetIgnition conf.Conf, outdir string, offline, minimal bool) (*InstalledMachine, error) {
if !inst.Native4k && inst.CosaBuild.Meta.BuildArtifacts.Metal == nil {
return nil, fmt.Errorf("Build %s must have a `metal` artifact", inst.CosaBuild.Meta.OstreeVersion)
} else if inst.Native4k && inst.CosaBuild.Meta.BuildArtifacts.Metal4KNative == nil {
Expand All @@ -550,6 +550,9 @@ func (inst *Install) InstallViaISOEmbed(kargs []string, liveIgnition, targetIgni
if inst.CosaBuild.Meta.BuildArtifacts.LiveIso == nil {
return nil, fmt.Errorf("Build %s must have a live ISO", inst.CosaBuild.Meta.Name)
}
if minimal && offline { // ideally this'd be one enum parameter
panic("Can't run minimal install offline")
}

// XXX: we do support this now, via `coreos-installer iso kargs`
if len(inst.kargs) > 0 {
Expand Down Expand Up @@ -623,7 +626,34 @@ func (inst *Install) InstallViaISOEmbed(kargs []string, liveIgnition, targetIgni
http.Serve(listener, mux)
}()
baseurl := fmt.Sprintf("http://%s:%d", defaultQemuHostIPv4, port)
srcOpt = fmt.Sprintf("--image-url %s/%s", baseurl, metalname)

// This is subtle but: for the minimal case, while we need networking to fetch the
// rootfs, the primary install flow will still rely on osmet. So let's keep srcOpt
// as "" to exercise that path. In the future, this could be a separate scenario
// (likely we should drop the "offline" naming and have a "remote" tag on the
// opposite scenarios instead which fetch the metal image, so then we'd have
// "[min]iso-install" and "[min]iso-remote-install").
if !minimal {
srcOpt = fmt.Sprintf("--image-url %s/%s", baseurl, metalname)
}

if minimal {
minisopath := filepath.Join(tempdir, "minimal.iso")
// This is obviously also available in the build dir, but to be realistic,
// let's take it from --rootfs-output
rootfs_path := filepath.Join(tempdir, "rootfs.img")
// Ideally we'd use the coreos-installer of the target build here, because it's part
// of the test workflow, but that's complex... Sadly, probably easiest is to spin up
// a VM just to get the minimal ISO.
cmd := exec.Command("coreos-installer", "iso", "extract", "minimal-iso", srcisopath,
"--output", minisopath, "--rootfs-output", rootfs_path,
"--rootfs-url", baseurl+"/rootfs.img")
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return nil, errors.Wrapf(err, "running coreos-installer iso extract minimal")
}
srcisopath = minisopath
}

// In this case; the target config is jut a tiny wrapper that wants to
// fetch our hosted target.ign config
Expand Down
46 changes: 34 additions & 12 deletions src/cmd-buildextend-live
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ parser.add_argument("--fast", action='store_true', default=False,
help="Reduce compression for development (FCOS only)")
parser.add_argument("--force", action='store_true', default=False,
help="Overwrite previously generated installer")
parser.add_argument("--miniso", action='store_true', default=False,
help="Enable minimal ISO packing (temporary)")
args = parser.parse_args()

# Identify the builds and target the latest build if none provided
Expand Down Expand Up @@ -117,9 +119,11 @@ for d in (tmpdir, tmpisoroot, tmpisocoreos, tmpisoimages, tmpisoimagespxe,
tmpisoisolinux, tmpinitrd_base, tmpinitrd_rootfs):
os.mkdir(d)

# Number of padding bytes at the end of the ISO initramfs for embedding
# an Ignition config
initrd_ignition_padding = 256 * 1024
# Size of file used to embed an Ignition config within a CPIO.
ignition_img_size = 256 * 1024

# Size of the file used to embed miniso data.
miniso_data_file_size = 16 * 1024


# The kernel requires that uncompressed cpio archives appended to an initrd
Expand Down Expand Up @@ -265,9 +269,9 @@ def generate_iso():
with open(stamppath, 'w') as fh:
fh.write(args.build + '\n')

# Add Ignition padding file to ISO image
# Add placeholder for Ignition CPIO file
with open(os.path.join(tmpisoimages, ignition_img), 'wb') as fdst:
fdst.write(bytes(initrd_ignition_padding))
fdst.write(bytes(ignition_img_size))

# Add osmet files
tmp_osmet = os.path.join(tmpinitrd_rootfs, img_metal_obj['path'] + '.osmet')
Expand Down Expand Up @@ -582,19 +586,32 @@ boot
fh.write('\n')

# Define inputs and outputs
genisoargs += ['-o', tmpisofile, tmpisoroot]
genisoargs_final = genisoargs + ['-o', tmpisofile, tmpisoroot]

if args.miniso:
miniso_data = os.path.join(tmpisocoreos, "miniso.dat")
with open(miniso_data, 'wb') as f:
f.truncate(miniso_data_file_size)

run_verbose(genisoargs)
run_verbose(genisoargs_final)

# Add MBR, and GPT with ESP, for x86_64 BIOS/UEFI boot when ISO is
# copied to a USB stick
if basearch == "x86_64":
run_verbose(['/usr/bin/isohybrid', '--uefi', tmpisofile])

if args.miniso:
genisoargs_minimal = genisoargs + ['-o', f'{tmpisofile}.minimal', tmpisoroot]
os.unlink(iso_rootfs)
os.unlink(miniso_data)
run_verbose(genisoargs_minimal)
if basearch == "x86_64":
run_verbose(['/usr/bin/isohybrid', '--uefi', f'{tmpisofile}.minimal'])

isoinfo = run_verbose(['isoinfo', '-lR', '-i', tmpisofile],
stdout=subprocess.PIPE, text=True).stdout

# We've already created a file in the ISO with initrd_ignition_padding
# We've already created a file in the ISO with ignition_img_size
# bytes of zeroes. Find the byte offset of that file within the ISO
# image and write it into a custom header at the end of the ISO 9660
# System Area, which is 32 KB at the start of the image "reserved for
Expand Down Expand Up @@ -642,8 +659,8 @@ boot
with open(tmpisofile, 'r+b') as isofh:
# Verify that the calculated byte range is empty
isofh.seek(offset)
if isofh.read(initrd_ignition_padding) != bytes(initrd_ignition_padding):
raise Exception(f'ISO image {initrd_ignition_padding} bytes at {offset} are not zero')
if isofh.read(ignition_img_size) != bytes(ignition_img_size):
raise Exception(f'ISO image {ignition_img_size} bytes at {offset} are not zero')

# Write header at the end of the System Area
isofh.seek(ISO_SYSTEM_AREA_SIZE - (struct.calcsize(INITRDFMT) +
Expand All @@ -658,8 +675,13 @@ boot
offsets[i + 1] = file_offset_in_iso(isoinfo, os.path.basename(fn)) + offset_in_file
isofh.write(struct.pack(KARGSFMT, b'coreKarg', karg_embed_area_length, *offsets))
# Magic number + offset + length
isofh.write(struct.pack(INITRDFMT, b'coreiso+', offset, initrd_ignition_padding))
print(f'Embedded {initrd_ignition_padding} bytes Ignition config space at {offset}')
isofh.write(struct.pack(INITRDFMT, b'coreiso+', offset, ignition_img_size))
print(f'Embedded {ignition_img_size} bytes Ignition config space at {offset}')

if args.miniso:
# this consumes the minimal image
run_verbose(['coreos-installer', 'iso', 'extract', 'pack-minimal-iso',
tmpisofile, f'{tmpisofile}.minimal', "--consume"])

buildmeta['images'].update({
'live-iso': {
Expand Down