diff --git a/mantle/platform/machine/qemu/cluster.go b/mantle/platform/machine/qemu/cluster.go index 37ffcdafbc..d05b59bb58 100644 --- a/mantle/platform/machine/qemu/cluster.go +++ b/mantle/platform/machine/qemu/cluster.go @@ -20,8 +20,7 @@ import ( "path/filepath" "strconv" "strings" - - "sync" + "sync/atomic" "time" "github.com/pborman/uuid" @@ -40,8 +39,8 @@ type Cluster struct { *platform.BaseCluster flight *flight - mu sync.Mutex - tearingDown bool + // Use atomic.Bool to prevent race conditions + tearingDown atomic.Bool } func (qc *Cluster) NewMachine(userdata *conf.UserData) (platform.Machine, error) { @@ -66,16 +65,10 @@ func (qc *Cluster) NewMachineWithQemuOptions(userdata *conf.UserData, options pl return nil, err } - // hacky solution for cloud config ip substitution - // NOTE: escaping is not supported - qc.mu.Lock() - - conf, err := qc.RenderUserData(userdata, map[string]string{}) + config, configPath, err := qc.RenderUserDataAndWriteIgnitionFileToDir(userdata, dir) if err != nil { - qc.mu.Unlock() return nil, err } - qc.mu.Unlock() journal, err := platform.NewJournal(dir) if err != nil { @@ -95,23 +88,12 @@ func (qc *Cluster) NewMachineWithQemuOptions(userdata *conf.UserData, options pl } if qc.flight.opts.SecureExecution { - if err := builder.SetSecureExecution(qc.flight.opts.SecureExecutionIgnitionPubKey, qc.flight.opts.SecureExecutionHostKey, conf); err != nil { + if err := builder.SetSecureExecution(qc.flight.opts.SecureExecutionIgnitionPubKey, qc.flight.opts.SecureExecutionHostKey, config); err != nil { return nil, err } } - var confPath string - if conf.IsIgnition() { - confPath = filepath.Join(dir, "ignition.json") - if err := conf.WriteFile(confPath); err != nil { - return nil, err - } - } else if conf.IsEmpty() { - } else { - return nil, fmt.Errorf("qemu only supports Ignition or empty configs") - } - - builder.ConfigFile = confPath + builder.ConfigFile = configPath defer builder.Close() builder.UUID = qm.id if qc.flight.opts.Arch != "" { @@ -249,7 +231,7 @@ func (qc *Cluster) NewMachineWithQemuOptions(userdata *conf.UserData, options pl // it knows to stop the test once QEMU dies. go func() { err := inst.Wait() - if err != nil && !qc.tearingDown { + if err != nil && !qc.tearingDown.Load() { plog.Errorf("QEMU process finished abnormally: %v", err) } }() @@ -258,7 +240,38 @@ func (qc *Cluster) NewMachineWithQemuOptions(userdata *conf.UserData, options pl } func (qc *Cluster) Destroy() { - qc.tearingDown = true + qc.tearingDown.Store(true) qc.BaseCluster.Destroy() qc.flight.DelCluster(qc) } + +func (qc *Cluster) RenderUserDataAndWriteIgnitionFileToDir(userdata any, dir string) (*conf.Conf, string, error) { + var config *conf.Conf + var configPath string + var err error + // Some callers provide the config directly rather than something + // that needs to be rendered. Render the userdata into a config + // if needed. + switch c := userdata.(type) { + case *conf.UserData: + config, err = qc.RenderUserData(c, map[string]string{}) + if err != nil { + return nil, "", err + } + case *conf.Conf: + config = c // Already rendered. Just pass through what was provided. + default: + return nil, "", fmt.Errorf("unknown config pointer type: %T", c) + } + if config != nil { + if config.IsIgnition() { + configPath = filepath.Join(dir, "ignition.json") + if err = config.WriteFile(configPath); err != nil { + return nil, "", err + } + } else if !config.IsEmpty() { + return nil, "", fmt.Errorf("qemu only supports Ignition or empty configs") + } + } + return config, configPath, nil +} diff --git a/mantle/platform/machine/qemuiso/cluster.go b/mantle/platform/machine/qemuiso/cluster.go index b14df90ff7..35d2c07cae 100644 --- a/mantle/platform/machine/qemuiso/cluster.go +++ b/mantle/platform/machine/qemuiso/cluster.go @@ -19,8 +19,6 @@ import ( "os" "path/filepath" "strconv" - - "sync" "time" "github.com/pborman/uuid" @@ -38,8 +36,6 @@ import ( type Cluster struct { *platform.BaseCluster flight *flight - - mu sync.Mutex } func (qc *Cluster) NewMachine(userdata *conf.UserData) (platform.Machine, error) { @@ -66,16 +62,10 @@ func (qc *Cluster) NewMachineWithQemuOptions(userdata *conf.UserData, options pl return nil, err } - // hacky solution for cloud config ip substitution - // NOTE: escaping is not supported - qc.mu.Lock() - conf, err := qc.RenderUserData(userdata, map[string]string{}) if err != nil { - qc.mu.Unlock() return nil, err } - qc.mu.Unlock() var confPath string if conf.IsIgnition() {