From 5223ad9e7090fa452ffaf4478cd9a9df081913e1 Mon Sep 17 00:00:00 2001 From: Norio Nomura Date: Wed, 28 Aug 2024 18:54:26 +0900 Subject: [PATCH] vz: support kernel image Signed-off-by: Norio Nomura vz: change `linuxBootLoader()` to return error, then explicitly ignores the error on caller. Signed-off-by: Norio Nomura vz: add comment to `bootLoader()` Signed-off-by: Norio Nomura vz: add `errors.Is(err, os.ErrNotExist)` Signed-off-by: Norio Nomura vz: use `logrus.WithError(err)` Signed-off-by: Norio Nomura vz: return `err` if `linuxBootLoader()` returns error other than `os.ErrNotExist` Signed-off-by: Norio Nomura downloader: detect compression by reading magic Beacause the kernel file provided by Ubuntu is gzipped without `.gz` file extension. Signed-off-by: Norio Nomura inject-cmdline-to-template.sh: add check location existence Some ubuntu kernel images are provided without initrd image. e.g. https://cloud-images.ubuntu.com/minimal/releases/noble/release-20240823/unpacked/ Signed-off-by: Norio Nomura inject-cmdline-to-template.sh: use `-e` again Signed-off-by: Norio Nomura --- hack/inject-cmdline-to-template.sh | 29 +++++++++++------- pkg/downloader/downloader.go | 32 ++++++++++++++++++++ pkg/vz/disk.go | 22 ++++++++++++++ pkg/vz/vm_darwin.go | 48 ++++++++++++++++++++++++++---- 4 files changed, 115 insertions(+), 16 deletions(-) diff --git a/hack/inject-cmdline-to-template.sh b/hack/inject-cmdline-to-template.sh index 0ff941d69fd..04431e7d1ab 100755 --- a/hack/inject-cmdline-to-template.sh +++ b/hack/inject-cmdline-to-template.sh @@ -36,12 +36,17 @@ readonly yq_filter=" parsed=$(yq eval "${yq_filter}" "${template}") # 3. get the image location +function check_location() { + local location=$1 http_code + http_code=$(curl -sIL -w "%{http_code}" "${location}" -o /dev/null) + [[ ${http_code} -eq 200 ]] +} while IFS= read -r line; do arr+=("${line}"); done <<<"${parsed}" readonly locations=("${arr[@]}") for ((i = 0; i < ${#locations[@]}; i++)); do [[ ${locations[i]} != "null" ]] || continue - http_code=$(curl -sIL -w "%{http_code}" "${locations[i]}" -o /dev/null) - if [[ ${http_code} -eq 200 ]]; then + # shellcheck disable=SC2310 + if check_location "${locations[i]}"; then location=${locations[i]} index=${i} break @@ -78,14 +83,18 @@ initrd_digest=$(awk "/${initrd_basename}/{print \"sha256:\"\$1}" <<<"${sha256sum initrd_location="${location_dirname}/${initrd_basename}" # 6. inject the kernel and initrd location, digest, and cmdline to the template -yq -i eval " - [(.images.[] | select(.arch == \"${arch}\") | path)].[${index}] + \"kernel\" as \$path| - setpath(\$path; { \"location\": \"${kernel_location}\", \"digest\": \"${kernel_digest}\", \"cmdline\": \"${cmdline}\" }) -" "${template}" -yq -i eval " - [(.images.[] | select(.arch == \"${arch}\") | path)].[${index}] + \"initrd\" as \$path| - setpath(\$path ; { \"location\": \"${initrd_location}\", \"digest\": \"${initrd_digest}\" }) -" "${template}" +function inject_to() { + # shellcheck disable=SC2034 + local template=$1 arch=$2 index=$3 key=$4 location=$5 digest=$6 cmdline=${7:-} fields=() IFS=, + # shellcheck disable=SC2310 + check_location "${location}" || return 0 + for field_name in location digest cmdline; do + [[ -z ${!field_name} ]] || fields+=("\"${field_name}\": \"${!field_name}\"") + done + yq -i -I 2 eval "setpath([(.images[] | select(.arch == \"${arch}\") | path)].[${index}] + \"${key}\"; { ${fields[*]}})" "${template}" +} +inject_to "${template}" "${arch}" "${index}" "kernel" "${kernel_location}" "${kernel_digest}" "${cmdline}" +inject_to "${template}" "${arch}" "${index}" "initrd" "${initrd_location}" "${initrd_digest}" # 7. output kernel_location, kernel_digest, cmdline, initrd_location, initrd_digest readonly outputs=(kernel_location kernel_digest cmdline initrd_location initrd_digest) diff --git a/pkg/downloader/downloader.go b/pkg/downloader/downloader.go index bccb16708e8..d5732d559fb 100644 --- a/pkg/downloader/downloader.go +++ b/pkg/downloader/downloader.go @@ -408,6 +408,10 @@ func copyLocal(ctx context.Context, dst, src, ext string, decompress bool, descr if command != "" { return decompressLocal(ctx, command, dstPath, srcPath, ext, description) } + commandByMagic := decompressorByMagic(srcPath) + if commandByMagic != "" { + return decompressLocal(ctx, commandByMagic, dstPath, srcPath, ext, description) + } } // TODO: progress bar for copy return fs.CopyFile(dstPath, srcPath) @@ -428,6 +432,34 @@ func decompressor(ext string) string { } } +func decompressorByMagic(file string) string { + f, err := os.Open(file) + if err != nil { + return "" + } + defer f.Close() + header := make([]byte, 6) + if _, err := f.Read(header); err != nil { + return "" + } + if _, err := f.Seek(0, io.SeekStart); err != nil { + return "" + } + if bytes.HasPrefix(header, []byte{0x1f, 0x8b}) { + return "gzip" + } + if bytes.HasPrefix(header, []byte{0x42, 0x5a}) { + return "bzip2" + } + if bytes.HasPrefix(header, []byte{0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00}) { + return "xz" + } + if bytes.HasPrefix(header, []byte{0x28, 0xb5, 0x2f, 0xfd}) { + return "zstd" + } + return "" +} + func decompressLocal(ctx context.Context, decompressCmd, dst, src, ext, description string) error { logrus.Infof("decompressing %s with %v", ext, decompressCmd) diff --git a/pkg/vz/disk.go b/pkg/vz/disk.go index 0ee62d3805d..f465d6d74cc 100644 --- a/pkg/vz/disk.go +++ b/pkg/vz/disk.go @@ -23,6 +23,9 @@ func EnsureDisk(ctx context.Context, driver *driver.BaseDriver) error { } baseDisk := filepath.Join(driver.Instance.Dir, filenames.BaseDisk) + kernel := filepath.Join(driver.Instance.Dir, filenames.Kernel) + kernelCmdline := filepath.Join(driver.Instance.Dir, filenames.KernelCmdline) + initrd := filepath.Join(driver.Instance.Dir, filenames.Initrd) if _, err := os.Stat(baseDisk); errors.Is(err, os.ErrNotExist) { var ensuredBaseDisk bool errs := make([]error, len(driver.Yaml.Images)) @@ -31,6 +34,25 @@ func EnsureDisk(ctx context.Context, driver *driver.BaseDriver) error { errs[i] = err continue } + if f.Kernel != nil { + // ensure decompress kernel because vz expects it to be decompressed + if _, err := fileutils.DownloadFile(ctx, kernel, f.Kernel.File, true, "the kernel", *driver.Yaml.Arch); err != nil { + errs[i] = err + continue + } + if f.Kernel.Cmdline != "" { + if err := os.WriteFile(kernelCmdline, []byte(f.Kernel.Cmdline), 0o644); err != nil { + errs[i] = err + continue + } + } + } + if f.Initrd != nil { + if _, err := fileutils.DownloadFile(ctx, initrd, *f.Initrd, false, "the initrd", *driver.Yaml.Arch); err != nil { + errs[i] = err + continue + } + } ensuredBaseDisk = true break } diff --git a/pkg/vz/vm_darwin.go b/pkg/vz/vm_darwin.go index 74151f36711..4599e6f08e6 100644 --- a/pkg/vz/vm_darwin.go +++ b/pkg/vz/vm_darwin.go @@ -203,12 +203,7 @@ func createVM(driver *driver.BaseDriver) (*vz.VirtualMachine, error) { } func createInitialConfig(driver *driver.BaseDriver) (*vz.VirtualMachineConfiguration, error) { - efiVariableStore, err := getEFI(driver) - if err != nil { - return nil, err - } - - bootLoader, err := vz.NewEFIBootLoader(vz.WithEFIVariableStore(efiVariableStore)) + bootLoader, err := bootLoader(driver) if err != nil { return nil, err } @@ -686,6 +681,47 @@ func getMachineIdentifier(driver *driver.BaseDriver) (*vz.GenericMachineIdentifi return vz.NewGenericMachineIdentifierWithDataPath(identifier) } +func bootLoader(driver *driver.BaseDriver) (vz.BootLoader, error) { + linuxBootLoder, err := linuxBootLoader(driver) + if linuxBootLoder != nil { + return linuxBootLoder, nil + } else if !errors.Is(err, os.ErrNotExist) { + return nil, err + } + + efiVariableStore, err := getEFI(driver) + if err != nil { + return nil, err + } + logrus.Debugf("Using EFI Boot Loader") + return vz.NewEFIBootLoader(vz.WithEFIVariableStore(efiVariableStore)) +} + +func linuxBootLoader(driver *driver.BaseDriver) (*vz.LinuxBootLoader, error) { + kernel := filepath.Join(driver.Instance.Dir, filenames.Kernel) + kernelCmdline := filepath.Join(driver.Instance.Dir, filenames.KernelCmdline) + initrd := filepath.Join(driver.Instance.Dir, filenames.Initrd) + if _, err := os.Stat(kernel); err != nil { + if errors.Is(err, os.ErrNotExist) { + logrus.Debugf("Kernel file %q not found", kernel) + } else { + logrus.WithError(err).Debugf("Error while checking kernel file %q", kernel) + } + return nil, err + } + var opt []vz.LinuxBootLoaderOption + if b, err := os.ReadFile(kernelCmdline); err == nil { + logrus.Debugf("Using kernel command line %q", string(b)) + opt = append(opt, vz.WithCommandLine(string(b))) + } + if _, err := os.Stat(initrd); err == nil { + logrus.Debugf("Using initrd %q", initrd) + opt = append(opt, vz.WithInitrd(initrd)) + } + logrus.Debugf("Using Linux Boot Loader with kernel %q", kernel) + return vz.NewLinuxBootLoader(kernel, opt...) +} + func getEFI(driver *driver.BaseDriver) (*vz.EFIVariableStore, error) { efi := filepath.Join(driver.Instance.Dir, filenames.VzEfi) if _, err := os.Stat(efi); os.IsNotExist(err) {