feat: provide option to run Talos under UEFI in QEMU

This also adds integration pipeline tests for UEFI.

Signed-off-by: Andrey Smirnov <smirnov.andrey@gmail.com>
This commit is contained in:
Andrey Smirnov 2020-08-27 23:23:20 +03:00 committed by talos-bot
parent d820f08966
commit 59adf7315d
10 changed files with 88 additions and 36 deletions

View File

@ -227,7 +227,7 @@ local image_vmware = Step("image-vmware", depends_on=[image_gcp]);
local unit_tests = Step("unit-tests", depends_on=[initramfs]);
local unit_tests_race = Step("unit-tests-race", depends_on=[initramfs]);
local e2e_docker = Step("e2e-docker-short", depends_on=[talos, talosctl_linux, unit_tests, unit_tests_race], target="e2e-docker", environment={"SHORT_INTEGRATION_TEST": "yes"});
local e2e_qemu = Step("e2e-qemu-short", privileged=true, target="e2e-qemu", depends_on=[talosctl_linux, initramfs, kernel, installer, unit_tests, unit_tests_race], environment={"FIRECRACKER_GO_SDK_REQUEST_TIMEOUT_MILLISECONDS": "2000", "SHORT_INTEGRATION_TEST": "yes"}, when={event: ['pull_request']});
local e2e_qemu = Step("e2e-qemu-short", privileged=true, target="e2e-qemu", depends_on=[talosctl_linux, initramfs, kernel, installer, unit_tests, unit_tests_race], environment={"SHORT_INTEGRATION_TEST": "yes"}, when={event: ['pull_request']});
local coverage = {
name: 'coverage',
@ -334,15 +334,18 @@ local default_pipeline = Pipeline('default', default_steps) + default_trigger;
// Full integration pipeline.
local integration_qemu = Step("e2e-qemu", privileged=true, depends_on=[initramfs, talosctl_linux, kernel, installer, unit_tests, unit_tests_race], environment={"FIRECRACKER_GO_SDK_REQUEST_TIMEOUT_MILLISECONDS": "2000"});
local integration_qemu = Step("e2e-qemu", privileged=true, depends_on=[initramfs, talosctl_linux, kernel, installer, unit_tests, unit_tests_race]);
local integration_provision_tests_prepare = Step("provision-tests-prepare", privileged=true, depends_on=[initramfs, talosctl_linux, kernel, installer, unit_tests, unit_tests_race, e2e_qemu, e2e_docker]);
local integration_provision_tests_track_0 = Step("provision-tests-track-0", privileged=true, depends_on=[integration_provision_tests_prepare], environment={"FIRECRACKER_GO_SDK_REQUEST_TIMEOUT_MILLISECONDS": "2000"});
local integration_provision_tests_track_1 = Step("provision-tests-track-1", privileged=true, depends_on=[integration_provision_tests_prepare], environment={"FIRECRACKER_GO_SDK_REQUEST_TIMEOUT_MILLISECONDS": "2000"});
local integration_provision_tests_track_0 = Step("provision-tests-track-0", privileged=true, depends_on=[integration_provision_tests_prepare]);
local integration_provision_tests_track_1 = Step("provision-tests-track-1", privileged=true, depends_on=[integration_provision_tests_prepare]);
local integration_cilium = Step("e2e-cilium-1.8.0", target="e2e-qemu", privileged=true, depends_on=[integration_qemu], environment={
"FIRECRACKER_GO_SDK_REQUEST_TIMEOUT_MILLISECONDS": "2000",
"SHORT_INTEGRATION_TEST": "yes",
"CUSTOM_CNI_URL": "https://raw.githubusercontent.com/cilium/cilium/v1.8.0/install/kubernetes/quick-install.yaml",
});
local integration_uefi = Step("e2e-uefi", target="e2e-qemu", privileged=true, depends_on=[integration_cilium], environment={
"SHORT_INTEGRATION_TEST": "yes",
"WITH_UEFI": "true",
});
local integration_steps = default_steps + [
@ -351,6 +354,7 @@ local integration_steps = default_steps + [
integration_provision_tests_track_0,
integration_provision_tests_track_1,
integration_cilium,
integration_uefi,
];
local integration_trigger = {

View File

@ -42,6 +42,7 @@ var (
nodeVmlinuzPath string
nodeInitramfsPath string
bootloaderEnabled bool
uefiEnabled bool
configDebug bool
networkCIDR string
networkMTU int
@ -164,6 +165,7 @@ func create(ctx context.Context) (err error) {
provisionOptions := []provision.Option{
provision.WithDockerPortsHostIP(dockerHostIP),
provision.WithBootlader(bootloaderEnabled),
provision.WithUEFI(uefiEnabled),
provision.WithTargetArch(targetArch),
}
configBundleOpts := []bundle.Option{}
@ -368,6 +370,7 @@ func init() {
createCmd.Flags().StringVar(&nodeVmlinuzPath, "vmlinuz-path", helpers.ArtifactPath(constants.KernelAsset), "the compressed kernel image to use")
createCmd.Flags().StringVar(&nodeInitramfsPath, "initrd-path", helpers.ArtifactPath(constants.InitramfsAsset), "the uncompressed kernel image to use")
createCmd.Flags().BoolVar(&bootloaderEnabled, "with-bootloader", true, "enable bootloader to load kernel and initramfs from disk image after install")
createCmd.Flags().BoolVar(&uefiEnabled, "with-uefi", false, "enable UEFI on x86_64 architecture (always enabled for arm64)")
createCmd.Flags().StringSliceVar(&registryMirrors, "registry-mirror", []string{}, "list of registry mirrors to use in format: <registry host>=<mirror URL>")
createCmd.Flags().BoolVar(&configDebug, "with-debug", false, "enable debug in Talos config to send service logs to the console")
createCmd.Flags().IntVar(&networkMTU, "mtu", 1500, "MTU of the cluster network")

View File

@ -45,6 +45,7 @@ talosctl cluster create [flags]
--with-bootloader enable bootloader to load kernel and initramfs from disk image after install (default true)
--with-debug enable debug in Talos config to send service logs to the console
--with-init-node create the cluster with an init node
--with-uefi enable UEFI on x86_64 architecture (always enabled for arm64)
--workers int the number of workers to create (default 1)
```

View File

@ -31,6 +31,12 @@ case "${CUSTOM_CNI_URL:-false}" in
;;
esac
case "${WITH_UEFI:-false}" in
true)
QEMU_FLAGS="${QEMU_FLAGS} --with-uefi"
;;
esac
function create_cluster {
build_registry_mirrors

View File

@ -61,6 +61,15 @@ func WithBootlader(enabled bool) Option {
}
}
// WithUEFI enables or disables UEFI boot on amd64 (default for amd64 is BIOS boot).
func WithUEFI(enabled bool) Option {
return func(o *Options) error {
o.UEFIEnabled = enabled
return nil
}
}
// WithTargetArch specifies target architecture for the cluster.
func WithTargetArch(arch string) Option {
return func(o *Options) error {
@ -99,6 +108,9 @@ type Options struct {
// Enable bootloader by booting from disk image after install.
BootloaderEnabled bool
// Enable UEFI (for amd64), arm64 can only boot UEFI
UEFIEnabled bool
// Expose ports to worker machines in docker provisioner
DockerPorts []string
DockerPortsHostIP string

View File

@ -61,25 +61,34 @@ func (arch Arch) Console() string {
// PFlash for UEFI boot.
type PFlash struct {
Size int64
SourcePath string
Size int64
SourcePaths []string
}
// PFlash returns settings for parallel flash.
func (arch Arch) PFlash() []PFlash {
func (arch Arch) PFlash(uefiEnabled bool) []PFlash {
switch arch {
case ArchArm64:
return []PFlash{
{
Size: 64 * 1024 * 1024,
SourcePath: "/usr/share/qemu-efi-aarch64/QEMU_EFI.fd",
Size: 64 * 1024 * 1024,
SourcePaths: []string{"/usr/share/qemu-efi-aarch64/QEMU_EFI.fd", "/usr/share/OVMF/QEMU_EFI.fd"},
},
{
Size: 64 * 1024 * 1024,
},
}
case ArchAmd64:
fallthrough
if !uefiEnabled {
return nil
}
return []PFlash{
{
Size: 0,
SourcePaths: []string{"/usr/share/ovmf/OVMF.fd", "/usr/share/OVMF/OVMF.fd"},
},
}
default:
return nil
}

View File

@ -43,14 +43,6 @@ func (p *provisioner) Create(ctx context.Context, request provision.ClusterReque
return nil, err
}
if pflashSpec := arch.PFlash(); pflashSpec != nil {
fmt.Fprintln(options.LogWriter, "creating flash images")
if err = p.createPFlashImages(state, pflashSpec); err != nil {
return nil, fmt.Errorf("error creating flash images: %w", err)
}
}
fmt.Fprintln(options.LogWriter, "creating network", request.Network.Name)
if err = p.CreateNetwork(ctx, state, request.Network); err != nil {

View File

@ -31,6 +31,16 @@ func (p *provisioner) createNode(state *vm.State, clusterReq provision.ClusterRe
arch := Arch(opts.TargetArch)
pidPath := state.GetRelativePath(fmt.Sprintf("%s.pid", nodeReq.Name))
var pflashImages []string
if pflashSpec := arch.PFlash(opts.UEFIEnabled); pflashSpec != nil {
var err error
if pflashImages, err = p.createPFlashImages(state, nodeReq.Name, pflashSpec); err != nil {
return provision.NodeInfo{}, fmt.Errorf("error creating flash images: %w", err)
}
}
vcpuCount := int64(math.RoundToEven(float64(nodeReq.NanoCPUs) / 1000 / 1000 / 1000))
if vcpuCount < 2 {
vcpuCount = 1
@ -81,7 +91,7 @@ func (p *provisioner) createNode(state *vm.State, clusterReq provision.ClusterRe
MemSize: memSize,
KernelArgs: cmdline.String(),
MachineType: arch.QemuMachine(),
PFlashImages: state.PFlashImages,
PFlashImages: pflashImages,
EnableKVM: opts.TargetArch == runtime.GOARCH,
BootloaderEnabled: opts.BootloaderEnabled,
NodeUUID: nodeUUID,

View File

@ -12,42 +12,59 @@ import (
"github.com/talos-systems/talos/pkg/provision/providers/vm"
)
func (p *provisioner) createPFlashImages(state *vm.State, pflashSpec []PFlash) error {
//nolint: gocyclo
func (p *provisioner) createPFlashImages(state *vm.State, nodeName string, pflashSpec []PFlash) ([]string, error) {
var images []string
for i, pflash := range pflashSpec {
if err := func(i int, pflash PFlash) error {
path := state.GetRelativePath(fmt.Sprintf("flash%d.img", i))
path := state.GetRelativePath(fmt.Sprintf("%s-flash%d.img", nodeName, i))
f, err := os.Create(path)
if err != nil {
return nil
return err
}
defer f.Close() //nolint: errcheck
if err := f.Truncate(pflash.Size); err != nil {
if err = f.Truncate(pflash.Size); err != nil {
return err
}
if pflash.SourcePath != "" {
src, err := os.Open(pflash.SourcePath)
if err != nil {
return nil
if pflash.SourcePaths != nil {
for _, sourcePath := range pflash.SourcePaths {
var src *os.File
src, err = os.Open(sourcePath)
if err != nil {
if os.IsNotExist(err) {
continue
}
return err
}
defer src.Close() //nolint: errcheck
if _, err = io.Copy(f, src); err != nil {
return err
}
break
}
defer src.Close() //nolint: errcheck
if _, err := io.Copy(f, src); err != nil {
if err != nil {
return err
}
}
state.PFlashImages = append(state.PFlashImages, path)
images = append(images, path)
return nil
}(i, pflash); err != nil {
return err
return nil, err
}
}
return nil
return images, nil
}

View File

@ -25,8 +25,6 @@ type State struct {
VMCNIConfig *libcni.NetworkConfigList
PFlashImages []string
statePath string
}