Skip to content

Commit

Permalink
Prevent two podman machines running on darwin
Browse files Browse the repository at this point in the history
As issue containers#25112 points out, it was possible to start a machine on one of the darwin providers and then switch providers and start another one with a different name.  This PR firstly prevents that use which is a forbidden use case.

Secondarily, performed some minor cleanup on the error messages being used so that the error would be specific to this condition.

This bug fix is for darwin only.  In the case of Windows, we probably need to answer the question I raised in containers#24067 first, which is whether we want to stop allowing WSL to run multiple machines.

Fixes containers#25112

Signed-off-by: Brent Baude <[email protected]>
  • Loading branch information
baude committed Jan 27, 2025
1 parent 89814fb commit e4bb002
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 16 deletions.
30 changes: 24 additions & 6 deletions pkg/machine/define/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,9 @@ import (
)

var (
ErrNoSuchVM = errors.New("VM does not exist")
ErrWrongState = errors.New("VM in wrong state to perform action")
ErrVMAlreadyExists = errors.New("VM already exists")
ErrVMAlreadyRunning = errors.New("VM already running or starting")
ErrMultipleActiveVM = errors.New("only one VM can be active at a time")
ErrNotImplemented = errors.New("functionality not implemented")
ErrWrongState = errors.New("VM in wrong state to perform action")
ErrVMAlreadyExists = errors.New("VM already exists")
ErrNotImplemented = errors.New("functionality not implemented")
)

type ErrVMRunningCannotDestroyed struct {
Expand Down Expand Up @@ -49,3 +46,24 @@ type ErrIncompatibleMachineConfig struct {
func (err *ErrIncompatibleMachineConfig) Error() string {
return fmt.Sprintf("incompatible machine config %q (%s) for this version of Podman", err.Path, err.Name)
}

type ErrVMAlreadyRunning struct {
Name string
}

func (err *ErrVMAlreadyRunning) Error() string {
return fmt.Sprintf("%s already starting or running", err.Name)
}

type ErrMultipleActiveVM struct {
Name string
Provider *string
}

func (err *ErrMultipleActiveVM) Error() string {
errMsg := fmt.Sprintf("a machine %q is already running", err.Name)
if err.Provider == nil {
return errMsg
}
return fmt.Sprintf("%s on the %q provider", errMsg, *err.Provider)
}
38 changes: 28 additions & 10 deletions pkg/machine/shim/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ func VMExists(name string, vmstubbers []vmconfigs.VMProvider) (*vmconfigs.Machin
return nil, false, err
}
if mc, found := mcs[name]; found {
return mc, true, nil
return mc.MachineConfig, true, nil
}
// Check with the provider hypervisor
for _, vmstubber := range vmstubbers {
Expand All @@ -281,31 +281,46 @@ func VMExists(name string, vmstubbers []vmconfigs.VMProvider) (*vmconfigs.Machin
}

// checkExclusiveActiveVM checks if any of the machines are already running
func checkExclusiveActiveVM(provider vmconfigs.VMProvider, mc *vmconfigs.MachineConfig) error {
func checkExclusiveActiveVM(currentProvider vmconfigs.VMProvider, mc *vmconfigs.MachineConfig) error {
providers := provider.GetAll()
// Check if any other machines are running; if so, we error
localMachines, err := getMCsOverProviders([]vmconfigs.VMProvider{provider})
localMachines, err := getMCsOverProviders(providers)
if err != nil {
return err
}

for name, localMachine := range localMachines {
state, err := provider.State(localMachine, false)
state, err := localMachine.Provider.State(localMachine.MachineConfig, false)
if err != nil {
return err
}
if state == machineDefine.Running || state == machineDefine.Starting {
if mc.Name == name {
return fmt.Errorf("unable to start %q: machine %s: %w", mc.Name, name, machineDefine.ErrVMAlreadyRunning)
return fmt.Errorf("unable to start %q: already running", mc.Name)
}

// A machine is running in the current provider
if currentProvider == localMachine.Provider {
fail := machineDefine.ErrVMAlreadyRunning{Name: mc.Name}
return fmt.Errorf("unable to start %q: %w", mc.Name, &fail)
}
return fmt.Errorf("unable to start %q: machine %s is already running: %w", mc.Name, name, machineDefine.ErrMultipleActiveVM)
// A machine is running in an alternate provider
providerRunningMachine := localMachine.Provider.VMType().String()
return &machineDefine.ErrMultipleActiveVM{Name: name, Provider: &providerRunningMachine}
}
}
return nil
}

type knownMachineConfig struct {
Provider vmconfigs.VMProvider
MachineConfig *vmconfigs.MachineConfig
}

// getMCsOverProviders loads machineconfigs from a config dir derived from the "provider". it returns only what is known on
// disk so things like status may be incomplete or inaccurate
func getMCsOverProviders(vmstubbers []vmconfigs.VMProvider) (map[string]*vmconfigs.MachineConfig, error) {
mcs := make(map[string]*vmconfigs.MachineConfig)
func getMCsOverProviders(vmstubbers []vmconfigs.VMProvider) (map[string]knownMachineConfig, error) {
mcs := make(map[string]knownMachineConfig)
for _, stubber := range vmstubbers {
dirs, err := env.GetMachineDirs(stubber.VMType())
if err != nil {
Expand All @@ -320,7 +335,10 @@ func getMCsOverProviders(vmstubbers []vmconfigs.VMProvider) (map[string]*vmconfi
// iterate known mcs and add the stubbers
for mcName, mc := range stubberMCs {
if _, ok := mcs[mcName]; !ok {
mcs[mcName] = mc
mcs[mcName] = knownMachineConfig{
Provider: stubber,
MachineConfig: mc,
}
}
}
}
Expand Down Expand Up @@ -414,7 +432,7 @@ func Start(mc *vmconfigs.MachineConfig, mp vmconfigs.VMProvider, dirs *machineDe
}

if state == machineDefine.Running || state == machineDefine.Starting {
return fmt.Errorf("machine %s: %w", mc.Name, machineDefine.ErrVMAlreadyRunning)
return fmt.Errorf("unable to start %q: already running", mc.Name)
}
}

Expand Down

0 comments on commit e4bb002

Please sign in to comment.