diff --git a/internal/qemu/start_linux.go b/internal/qemu/start_linux.go index ae03254..a7869a2 100644 --- a/internal/qemu/start_linux.go +++ b/internal/qemu/start_linux.go @@ -65,18 +65,29 @@ func Start(cfg Config) error { sorted := make([]DiskConfig, len(cfg.Disks)) copy(sorted, cfg.Disks) - sort.Slice(sorted, func(i, j int) bool { return sorted[i].Dev < sorted[j].Dev }) + // vd* avant sd* : les disques virtio-blk bootent en premier. + // À lettre égale de type, ordre alphabétique. + sort.Slice(sorted, func(i, j int) bool { + iVirtio := strings.HasPrefix(sorted[i].Dev, "vd") + jVirtio := strings.HasPrefix(sorted[j].Dev, "vd") + if iVirtio != jVirtio { + return iVirtio + } + return sorted[i].Dev < sorted[j].Dev + }) - for _, d := range sorted { + for idx, d := range sorted { + bootindex := idx + 1 if strings.HasPrefix(d.Dev, "sd") { scsiID := int(d.Dev[2] - 'a') args = append(args, "-drive", fmt.Sprintf("file=%s,if=none,id=%s", d.Path, d.Dev), - "-device", fmt.Sprintf("scsi-hd,drive=%s,bus=scsi0.0,scsi-id=%d", d.Dev, scsiID), + "-device", fmt.Sprintf("scsi-hd,drive=%s,bus=scsi0.0,scsi-id=%d,bootindex=%d", d.Dev, scsiID, bootindex), ) } else { args = append(args, - "-drive", fmt.Sprintf("file=%s,if=virtio,id=%s", d.Path, d.Dev), + "-drive", fmt.Sprintf("file=%s,if=none,id=%s", d.Path, d.Dev), + "-device", fmt.Sprintf("virtio-blk-pci,drive=%s,bootindex=%d", d.Dev, bootindex), ) } } diff --git a/internal/vm/delete.go b/internal/vm/delete.go index 7e47f3f..3d808e6 100644 --- a/internal/vm/delete.go +++ b/internal/vm/delete.go @@ -33,25 +33,18 @@ func StopVM(db *badger.DB, name string, cfg *configuration.Config) error { socketPath := filepath.Join(cfg.QEMU.QMPDir, name+".sock") - if _, err := qmp.Send(socketPath, []string{`{"execute":"system_powerdown"}`}); err != nil { - return fmt.Errorf("qmp system_powerdown: %w", err) - } - - // attendre l'arrêt effectif de la VM ; forcer via quit après timeout - timeout := time.After(time.Duration(cfg.Dispatcher.TimeoutSeconds) * time.Second) - poll := time.Duration(cfg.Dispatcher.PollSeconds) * time.Second - stopped := false - for !stopped { - select { - case <-timeout: - qmp.Send(socketPath, []string{`{"execute":"quit"}`}) - stopped = true - case <-time.After(poll): - if _, err := qmp.Send(socketPath, nil); err != nil { - stopped = true - } + if _, err := os.Stat(socketPath); err == nil { + // socket présent : tenter l'arrêt gracieux + if _, err := qmp.Send(socketPath, []string{`{"execute":"system_powerdown"}`}); err == nil { + waitQMPDead(socketPath, + time.Duration(cfg.Dispatcher.TimeoutSeconds)*time.Second, + time.Duration(cfg.Dispatcher.PollSeconds)*time.Second, + ) } + // connexion QMP échouée : QEMU déjà mort } + // socket absent ou QEMU déjà arrêté : cleanup direct + if err := netns.Call(d.vpcName, func() error { return iptables.DeleteMetadataRedirect(d.ip, d.interfaceIP, d.metadataPort) @@ -74,3 +67,18 @@ func StopVM(db *badger.DB, name string, cfg *configuration.Config) error { return kv.AddInDB(db, "vm/"+name+"/state", "stopped") } + +func waitQMPDead(socketPath string, timeout, poll time.Duration) { + timer := time.After(timeout) + for { + select { + case <-timer: + qmp.Send(socketPath, []string{`{"execute":"quit"}`}) + return + case <-time.After(poll): + if _, err := qmp.Send(socketPath, nil); err != nil { + return + } + } + } +}