From 58d75102cad7c359dee7e63b27b49ec8b91cb783 Mon Sep 17 00:00:00 2001 From: benduncan <5178243+benduncan@users.noreply.github.com> Date: Tue, 25 Nov 2025 19:49:22 +1100 Subject: [PATCH] Added EFI for ARM, RunInstances tweak for provisining disks after instance response --- hive/daemon/daemon.go | 8 +++++ hive/handlers/ec2/instance/service_impl.go | 37 +++++++++++++--------- hive/vm/vm.go | 13 ++++++++ 3 files changed, 43 insertions(+), 15 deletions(-) diff --git a/hive/daemon/daemon.go b/hive/daemon/daemon.go index d2e6a1f25..5f0cb5c1c 100644 --- a/hive/daemon/daemon.go +++ b/hive/daemon/daemon.go @@ -963,6 +963,14 @@ func (d *Daemon) handleEC2RunInstances(msg *nats.Msg) { } msg.Respond(jsonResponse) + // Next, prepare the root volume, cloud-init, EFI drives via NBD (AMI clone to new volume) + err = d.instanceService.GenerateVolumes(runInstancesInput, instance) + if err != nil { + slog.Error("handleEC2RunInstances GenerateVolumes failed", "err", err) + d.resourceMgr.deallocate(instanceType) + return + } + // Launch the instance infrastructure (QEMU, QMP, NATS subscriptions), this can take sometime err = d.LaunchInstance(instance) diff --git a/hive/handlers/ec2/instance/service_impl.go b/hive/handlers/ec2/instance/service_impl.go index b4d5e11b5..3fc75baf1 100644 --- a/hive/handlers/ec2/instance/service_impl.go +++ b/hive/handlers/ec2/instance/service_impl.go @@ -103,7 +103,6 @@ func NewInstanceServiceImpl(cfg *config.Config, instanceTypes map[string]Instanc // This prepares all volumes (root, EFI, cloud-init) and returns the instance ready to launch func (s *InstanceServiceImpl) RunInstances(input *ec2.RunInstancesInput) (*vm.VM, ec2.Reservation, error) { var reservation ec2.Reservation - var err error // Validate input (validation is already done in daemon.handleEC2Launch) // We can skip re-validation here for performance @@ -114,13 +113,6 @@ func (s *InstanceServiceImpl) RunInstances(input *ec2.RunInstancesInput) (*vm.VM return nil, reservation, errors.New(awserrors.ErrorInvalidInstanceType) } - var size int = 4 * 1024 * 1024 * 1024 // 4GB default size - var deviceName string - var volumeType string - var iops int - var imageId string - var snapshotId string - instanceId := vm.GenerateEC2InstanceID() // Create new instance structure @@ -156,6 +148,20 @@ func (s *InstanceServiceImpl) RunInstances(input *ec2.RunInstancesInput) (*vm.VM instance.Reservation = &reservation instance.Instance = ec2Instance + // Return instance attributes, defer disk preparation to later step + + return instance, reservation, nil +} + +func (s *InstanceServiceImpl) GenerateVolumes(input *ec2.RunInstancesInput, instance *vm.VM) (err error) { + + var size int = 4 * 1024 * 1024 * 1024 // 4GB default size + var deviceName string + var volumeType string + var iops int + var imageId string + var snapshotId string + // Handle block device mappings if input.BlockDeviceMappings != nil && len(input.BlockDeviceMappings) > 0 { size = int(*input.BlockDeviceMappings[0].Ebs.VolumeSize) @@ -188,24 +194,25 @@ func (s *InstanceServiceImpl) RunInstances(input *ec2.RunInstancesInput) (*vm.VM // Step 1: Create or validate root volume err = s.prepareRootVolume(input, imageId, size, volumeConfig, instance) if err != nil { - return nil, reservation, err + return err } // Step 2: Create EFI partition err = s.prepareEFIVolume(imageId, volumeConfig, instance) if err != nil { - return nil, reservation, err + return err } // Step 3: Create cloud-init volume if needed if input.KeyName != nil && *input.KeyName != "" || (input.UserData != nil && *input.UserData != "") { err = s.prepareCloudInitVolume(input, imageId, volumeConfig, instance) if err != nil { - return nil, reservation, err + return err } } - return instance, reservation, nil + return nil + } // prepareRootVolume handles creation/cloning of the root volume @@ -234,7 +241,7 @@ func (s *InstanceServiceImpl) prepareRootVolume(input *ec2.RunInstancesInput, im return errors.New(awserrors.ErrorServerInternal) } - vb.SetDebug(true) + vb.SetDebug(false) // Initialize the backend err = vb.Backend.Init() @@ -412,7 +419,7 @@ func (s *InstanceServiceImpl) prepareEFIVolume(imageId string, volumeConfig vipe return errors.New(awserrors.ErrorServerInternal) } - efiVb.SetDebug(true) + efiVb.SetDebug(false) // Initialize the backend slog.Debug("Initializing EFI Viperblock store backend") @@ -503,7 +510,7 @@ func (s *InstanceServiceImpl) prepareCloudInitVolume(input *ec2.RunInstancesInpu return errors.New(awserrors.ErrorServerInternal) } - cloudInitVb.SetDebug(true) + cloudInitVb.SetDebug(false) // Initialize the backend slog.Debug("Initializing cloud-init Viperblock store backend") diff --git a/hive/vm/vm.go b/hive/vm/vm.go index 176442fe3..4f75be53c 100644 --- a/hive/vm/vm.go +++ b/hive/vm/vm.go @@ -214,6 +214,19 @@ func (cfg *Config) Execute() (*exec.Cmd, error) { // Note, require `-M` machine type for ARM (virt) if set to q35 (incompatible) if cfg.Architecture == "arm64" && cfg.MachineType == "q35" { args = append(args, "-M", "virt") + + // For ARM, when using virt, preload the firmware file + // Note: this requires the `qemu-efi-aarch64` package to be installed on the host + uefiPath := "/usr/share/qemu-efi-aarch64/QEMU_EFI.fd" + + // TODO: Use EFI via NBD for state persistence + if _, err := os.Stat(uefiPath); err == nil { + args = append(args, "-bios", uefiPath) + } else { + slog.Warn("UEFI firmware file not found for ARM virt machine. Ensure qemu-efi-aarch64 package is installed.", "path", uefiPath) + return nil, fmt.Errorf("UEFI firmware file not found for ARM virt machine") + } + } else if cfg.MachineType != "" { args = append(args, "-M", cfg.MachineType) }