Merge pull request #20274 from ashley-cui/cleanup

Machine: Teardown on init failure
This commit is contained in:
openshift-ci[bot]
2023-10-13 14:22:46 +00:00
committed by GitHub
7 changed files with 276 additions and 24 deletions

View File

@ -192,7 +192,15 @@ func (m *MacMachine) Init(opts machine.InitOptions) (bool, error) {
var ( var (
key string key string
virtiofsMnts []machine.VirtIoFs virtiofsMnts []machine.VirtIoFs
err error
) )
// cleanup half-baked files if init fails at any point
callbackFuncs := machine.InitCleanup()
defer callbackFuncs.CleanIfErr(&err)
go callbackFuncs.CleanOnSignal()
callbackFuncs.Add(m.ConfigPath.Delete)
dataDir, err := machine.GetDataDir(machine.AppleHvVirt) dataDir, err := machine.GetDataDir(machine.AppleHvVirt)
if err != nil { if err != nil {
return false, err return false, err
@ -211,6 +219,7 @@ func (m *MacMachine) Init(opts machine.InitOptions) (bool, error) {
if err != nil { if err != nil {
return false, err return false, err
} }
callbackFuncs.Add(imagePath.Delete)
// Set the values for imagePath and strm // Set the values for imagePath and strm
m.ImagePath = *imagePath m.ImagePath = *imagePath
@ -220,6 +229,8 @@ func (m *MacMachine) Init(opts machine.InitOptions) (bool, error) {
if err != nil { if err != nil {
return false, err return false, err
} }
callbackFuncs.Add(logPath.Delete)
m.LogPath = *logPath m.LogPath = *logPath
runtimeDir, err := m.getRuntimeDir() runtimeDir, err := m.getRuntimeDir()
if err != nil { if err != nil {
@ -232,11 +243,11 @@ func (m *MacMachine) Init(opts machine.InitOptions) (bool, error) {
} }
m.ReadySocket = *readySocket m.ReadySocket = *readySocket
if err := m.setGVProxyInfo(runtimeDir); err != nil { if err = m.setGVProxyInfo(runtimeDir); err != nil {
return false, err return false, err
} }
if err := m.setVfkitInfo(cfg, m.ReadySocket); err != nil { if err = m.setVfkitInfo(cfg, m.ReadySocket); err != nil {
return false, err return false, err
} }
@ -252,7 +263,7 @@ func (m *MacMachine) Init(opts machine.InitOptions) (bool, error) {
} }
m.Port = sshPort m.Port = sshPort
if err := m.addMountsToVM(opts, &virtiofsMnts); err != nil { if err = m.addMountsToVM(opts, &virtiofsMnts); err != nil {
return false, err return false, err
} }
@ -267,22 +278,23 @@ func (m *MacMachine) Init(opts machine.InitOptions) (bool, error) {
if err != nil { if err != nil {
return false, err return false, err
} }
callbackFuncs.Add(m.removeSystemConnections)
logrus.Debugf("resizing disk to %d GiB", opts.DiskSize) logrus.Debugf("resizing disk to %d GiB", opts.DiskSize)
if err := m.resizeDisk(strongunits.GiB(opts.DiskSize)); err != nil { if err = m.resizeDisk(strongunits.GiB(opts.DiskSize)); err != nil {
return false, err return false, err
} }
if err := m.writeConfig(); err != nil { if err = m.writeConfig(); err != nil {
return false, err return false, err
} }
if len(opts.IgnitionPath) < 1 { if len(opts.IgnitionPath) < 1 {
var err error
key, err = machine.CreateSSHKeys(m.IdentityPath) key, err = machine.CreateSSHKeys(m.IdentityPath)
if err != nil { if err != nil {
return false, err return false, err
} }
callbackFuncs.Add(m.removeSSHKeys)
} }
if len(opts.IgnitionPath) > 0 { if len(opts.IgnitionPath) > 0 {
@ -294,9 +306,22 @@ func (m *MacMachine) Init(opts machine.InitOptions) (bool, error) {
} }
// TODO Ignition stuff goes here // TODO Ignition stuff goes here
err = m.writeIgnitionConfigFile(opts, key, &virtiofsMnts) err = m.writeIgnitionConfigFile(opts, key, &virtiofsMnts)
callbackFuncs.Add(m.IgnitionFile.Delete)
return err == nil, err return err == nil, err
} }
func (m *MacMachine) removeSSHKeys() error {
if err := os.Remove(fmt.Sprintf("%s.pub", m.IdentityPath)); err != nil {
logrus.Error(err)
}
return os.Remove(m.IdentityPath)
}
func (m *MacMachine) removeSystemConnections() error {
return machine.RemoveConnections(m.Name, fmt.Sprintf("%s-root", m.Name))
}
func (m *MacMachine) Inspect() (*machine.InspectInfo, error) { func (m *MacMachine) Inspect() (*machine.InspectInfo, error) {
vmState, err := m.Vfkit.state() vmState, err := m.Vfkit.state()
if err != nil { if err != nil {

68
pkg/machine/cleanup.go Normal file
View File

@ -0,0 +1,68 @@
package machine
import (
"os"
"os/signal"
"sync"
"syscall"
"github.com/sirupsen/logrus"
)
type CleanupCallback struct {
Funcs []func() error
mu sync.Mutex
}
func (c *CleanupCallback) CleanIfErr(err *error) {
// Do not remove created files if the init is successful
if *err == nil {
return
}
c.clean()
}
func (c *CleanupCallback) CleanOnSignal() {
ch := make(chan os.Signal, 1)
signal.Notify(ch, os.Interrupt, syscall.SIGTERM)
_, ok := <-ch
if !ok {
return
}
c.clean()
os.Exit(1)
}
func (c *CleanupCallback) clean() {
c.mu.Lock()
// Claim exclusive usage by copy and resetting to nil
funcs := c.Funcs
c.Funcs = nil
c.mu.Unlock()
// Already claimed or none set
if funcs == nil {
return
}
// Cleanup functions can now exclusively be run
for _, cleanfunc := range funcs {
if err := cleanfunc(); err != nil {
logrus.Error(err)
}
}
}
func InitCleanup() CleanupCallback {
return CleanupCallback{
Funcs: []func() error{},
}
}
func (c *CleanupCallback) Add(anotherfunc func() error) {
c.mu.Lock()
c.Funcs = append(c.Funcs, anotherfunc)
c.mu.Unlock()
}

View File

@ -232,7 +232,7 @@ var _ = Describe("podman machine init", func() {
Expect(output).To(Equal("/run/podman/podman.sock")) Expect(output).To(Equal("/run/podman/podman.sock"))
}) })
It("init with user mode networking ", func() { It("init with user mode networking", func() {
if testProvider.VMType() != machine.WSLVirt { if testProvider.VMType() != machine.WSLVirt {
Skip("test is only supported by WSL") Skip("test is only supported by WSL")
} }
@ -249,4 +249,58 @@ var _ = Describe("podman machine init", func() {
Expect(inspectSession).To(Exit(0)) Expect(inspectSession).To(Exit(0))
Expect(inspectSession.outputToString()).To(Equal("true")) Expect(inspectSession.outputToString()).To(Equal("true"))
}) })
It("init should cleanup on failure", func() {
i := new(initMachine)
name := randomString()
session, err := mb.setName(name).setCmd(i.withImagePath(mb.imagePath)).run()
Expect(err).ToNot(HaveOccurred())
Expect(session).To(Exit(0))
inspect := new(inspectMachine)
inspect = inspect.withFormat("{{.ConfigPath.Path}}")
inspectSession, err := mb.setCmd(inspect).run()
Expect(err).ToNot(HaveOccurred())
cfgpth := inspectSession.outputToString()
inspect = inspect.withFormat("{{.Image.IgnitionFile.Path}}")
inspectSession, err = mb.setCmd(inspect).run()
Expect(err).ToNot(HaveOccurred())
ign := inspectSession.outputToString()
inspect = inspect.withFormat("{{.Image.ImagePath.Path}}")
inspectSession, err = mb.setCmd(inspect).run()
Expect(err).ToNot(HaveOccurred())
img := inspectSession.outputToString()
rm := rmMachine{}
removeSession, err := mb.setCmd(rm.withForce().withSaveKeys()).run()
Expect(err).ToNot(HaveOccurred())
Expect(removeSession).To(Exit(0))
// Inspecting a non-existent machine should fail
// which means it is gone
_, ec, err := mb.toQemuInspectInfo()
Expect(err).ToNot(HaveOccurred())
Expect(ec).To(Equal(125))
// Clashing keys - init fails
i = new(initMachine)
session, err = mb.setName(name).setCmd(i.withImagePath(mb.imagePath)).run()
Expect(err).ToNot(HaveOccurred())
Expect(session).To(Exit(125))
// ensure files created by init are cleaned up on init failure
_, err = os.Stat(img)
Expect(err).To(HaveOccurred())
_, err = os.Stat(cfgpth)
Expect(err).To(HaveOccurred())
// WSL does not use ignition
if testProvider.VMType() != machine.WSLVirt {
_, err = os.Stat(ign)
Expect(err).To(HaveOccurred())
}
})
}) })

View File

@ -74,7 +74,6 @@ var _ = Describe("podman machine rm", func() {
}) })
It("machine rm --save-keys, --save-ignition, --save-image", func() { It("machine rm --save-keys, --save-ignition, --save-image", func() {
i := new(initMachine) i := new(initMachine)
session, err := mb.setCmd(i.withImagePath(mb.imagePath)).run() session, err := mb.setCmd(i.withImagePath(mb.imagePath)).run()
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
@ -120,6 +119,5 @@ var _ = Describe("podman machine rm", func() {
} }
_, err = os.Stat(img) _, err = os.Stat(img)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
}) })
}) })

View File

@ -203,11 +203,25 @@ func (m *HyperVMachine) readAndSplitIgnition() error {
func (m *HyperVMachine) Init(opts machine.InitOptions) (bool, error) { func (m *HyperVMachine) Init(opts machine.InitOptions) (bool, error) {
var ( var (
key string key string
err error
) )
if err := m.addNetworkAndReadySocketsToRegistry(); err != nil { // cleanup half-baked files if init fails at any point
callbackFuncs := machine.InitCleanup()
defer callbackFuncs.CleanIfErr(&err)
go callbackFuncs.CleanOnSignal()
callbackFuncs.Add(m.ImagePath.Delete)
callbackFuncs.Add(m.ConfigPath.Delete)
callbackFuncs.Add(m.unregisterMachine)
if err = m.addNetworkAndReadySocketsToRegistry(); err != nil {
return false, err return false, err
} }
callbackFuncs.Add(func() error {
m.removeNetworkAndReadySocketsFromRegistry()
return nil
})
m.IdentityPath = util.GetIdentityPath(m.Name) m.IdentityPath = util.GetIdentityPath(m.Name)
@ -233,13 +247,14 @@ func (m *HyperVMachine) Init(opts machine.InitOptions) (bool, error) {
if err != nil { if err != nil {
return false, err return false, err
} }
callbackFuncs.Add(m.removeSystemConnections)
if len(opts.IgnitionPath) < 1 { if len(opts.IgnitionPath) < 1 {
var err error
key, err = machine.CreateSSHKeys(m.IdentityPath) key, err = machine.CreateSSHKeys(m.IdentityPath)
if err != nil { if err != nil {
return false, err return false, err
} }
callbackFuncs.Add(m.removeSSHKeys)
} }
m.ResourceConfig = machine.ResourceConfig{ m.ResourceConfig = machine.ResourceConfig{
@ -258,6 +273,7 @@ func (m *HyperVMachine) Init(opts machine.InitOptions) (bool, error) {
} }
return false, os.WriteFile(m.IgnitionFile.GetPath(), inputIgnition, 0644) return false, os.WriteFile(m.IgnitionFile.GetPath(), inputIgnition, 0644)
} }
callbackFuncs.Add(m.IgnitionFile.Delete)
if err := m.writeConfig(); err != nil { if err := m.writeConfig(); err != nil {
return false, err return false, err
@ -268,7 +284,7 @@ func (m *HyperVMachine) Init(opts machine.InitOptions) (bool, error) {
return false, err return false, err
} }
if err := m.resizeDisk(strongunits.GiB(opts.DiskSize)); err != nil { if err = m.resizeDisk(strongunits.GiB(opts.DiskSize)); err != nil {
return false, err return false, err
} }
// The ignition file has been written. We now need to // The ignition file has been written. We now need to
@ -277,6 +293,26 @@ func (m *HyperVMachine) Init(opts machine.InitOptions) (bool, error) {
return err == nil, err return err == nil, err
} }
func (m *HyperVMachine) unregisterMachine() error {
vmm := hypervctl.NewVirtualMachineManager()
vm, err := vmm.GetMachine(m.Name)
if err != nil {
logrus.Error(err)
}
return vm.Remove("")
}
func (m *HyperVMachine) removeSSHKeys() error {
if err := os.Remove(fmt.Sprintf("%s.pub", m.IdentityPath)); err != nil {
logrus.Error(err)
}
return os.Remove(m.IdentityPath)
}
func (m *HyperVMachine) removeSystemConnections() error {
return machine.RemoveConnections(m.Name, fmt.Sprintf("%s-root", m.Name))
}
func (m *HyperVMachine) Inspect() (*machine.InspectInfo, error) { func (m *HyperVMachine) Inspect() (*machine.InspectInfo, error) {
vm, err := hypervctl.NewVirtualMachineManager().GetMachine(m.Name) vm, err := hypervctl.NewVirtualMachineManager().GetMachine(m.Name)
if err != nil { if err != nil {

View File

@ -249,7 +249,14 @@ RequiredBy=default.target
func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) { func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
var ( var (
key string key string
err error
) )
// cleanup half-baked files if init fails at any point
callbackFuncs := machine.InitCleanup()
defer callbackFuncs.CleanIfErr(&err)
go callbackFuncs.CleanOnSignal()
v.IdentityPath = util.GetIdentityPath(v.Name) v.IdentityPath = util.GetIdentityPath(v.Name)
v.Rootful = opts.Rootful v.Rootful = opts.Rootful
@ -262,12 +269,13 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
if err != nil { if err != nil {
return false, err return false, err
} }
callbackFuncs.Add(imagePath.Delete)
// Assign values about the download // Assign values about the download
v.ImagePath = *imagePath v.ImagePath = *imagePath
v.ImageStream = strm.String() v.ImageStream = strm.String()
if err := v.addMountsToVM(opts); err != nil { if err = v.addMountsToVM(opts); err != nil {
return false, err return false, err
} }
@ -276,7 +284,7 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
// Add location of bootable image // Add location of bootable image
v.CmdLine.SetBootableImage(v.getImageFile()) v.CmdLine.SetBootableImage(v.getImageFile())
if err := machine.AddSSHConnectionsToPodmanSocket( if err = machine.AddSSHConnectionsToPodmanSocket(
v.UID, v.UID,
v.Port, v.Port,
v.IdentityPath, v.IdentityPath,
@ -286,23 +294,25 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
); err != nil { ); err != nil {
return false, err return false, err
} }
callbackFuncs.Add(v.removeSystemConnections)
// Write the JSON file // Write the JSON file
if err := v.writeConfig(); err != nil { if err = v.writeConfig(); err != nil {
return false, fmt.Errorf("writing JSON file: %w", err) return false, fmt.Errorf("writing JSON file: %w", err)
} }
callbackFuncs.Add(v.ConfigPath.Delete)
// User has provided ignition file so keygen // User has provided ignition file so keygen
// will be skipped. // will be skipped.
if len(opts.IgnitionPath) < 1 { if len(opts.IgnitionPath) < 1 {
var err error
key, err = machine.CreateSSHKeys(v.IdentityPath) key, err = machine.CreateSSHKeys(v.IdentityPath)
if err != nil { if err != nil {
return false, err return false, err
} }
callbackFuncs.Add(v.removeSSHKeys)
} }
// Run arch specific things that need to be done // Run arch specific things that need to be done
if err := v.prepare(); err != nil { if err = v.prepare(); err != nil {
return false, err return false, err
} }
originalDiskSize, err := getDiskSize(v.getImageFile()) originalDiskSize, err := getDiskSize(v.getImageFile())
@ -310,7 +320,7 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
return false, err return false, err
} }
if err := v.resizeDisk(opts.DiskSize, originalDiskSize>>(10*3)); err != nil { if err = v.resizeDisk(opts.DiskSize, originalDiskSize>>(10*3)); err != nil {
return false, err return false, err
} }
@ -329,9 +339,22 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
} }
err = v.writeIgnitionConfigFile(opts, key) err = v.writeIgnitionConfigFile(opts, key)
callbackFuncs.Add(v.IgnitionFile.Delete)
return err == nil, err return err == nil, err
} }
func (v *MachineVM) removeSSHKeys() error {
if err := os.Remove(fmt.Sprintf("%s.pub", v.IdentityPath)); err != nil {
logrus.Error(err)
}
return os.Remove(v.IdentityPath)
}
func (v *MachineVM) removeSystemConnections() error {
return machine.RemoveConnections(v.Name, fmt.Sprintf("%s-root", v.Name))
}
func (v *MachineVM) Set(_ string, opts machine.SetOptions) ([]error, error) { func (v *MachineVM) Set(_ string, opts machine.SetOptions) ([]error, error) {
// If one setting fails to be applied, the others settings will not fail and still be applied. // If one setting fails to be applied, the others settings will not fail and still be applied.
// The setting(s) that failed to be applied will have its errors returned in setErrors // The setting(s) that failed to be applied will have its errors returned in setErrors

View File

@ -394,6 +394,14 @@ func getLegacyLastStart(vm *MachineVM) time.Time {
// Init writes the json configuration file to the filesystem for // Init writes the json configuration file to the filesystem for
// other verbs (start, stop) // other verbs (start, stop)
func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) { func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
var (
err error
)
// cleanup half-baked files if init fails at any point
callbackFuncs := machine.InitCleanup()
defer callbackFuncs.CleanIfErr(&err)
go callbackFuncs.CleanOnSignal()
if cont, err := checkAndInstallWSL(opts); !cont { if cont, err := checkAndInstallWSL(opts); !cont {
appendOutputIfError(opts.ReExec, err) appendOutputIfError(opts.ReExec, err)
return cont, err return cont, err
@ -405,20 +413,22 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
v.Version = currentMachineVersion v.Version = currentMachineVersion
if v.UserModeNetworking { if v.UserModeNetworking {
if err := verifyWSLUserModeCompat(); err != nil { if err = verifyWSLUserModeCompat(); err != nil {
return false, err return false, err
} }
} }
if err := downloadDistro(v, opts); err != nil { if err = downloadDistro(v, opts); err != nil {
return false, err return false, err
} }
callbackFuncs.Add(v.removeMachineImage)
const prompt = "Importing operating system into WSL (this may take a few minutes on a new WSL install)..." const prompt = "Importing operating system into WSL (this may take a few minutes on a new WSL install)..."
dist, err := provisionWSLDist(v.Name, v.ImagePath, prompt) dist, err := provisionWSLDist(v.Name, v.ImagePath, prompt)
if err != nil { if err != nil {
return false, err return false, err
} }
callbackFuncs.Add(v.unprovisionWSL)
if v.UserModeNetworking { if v.UserModeNetworking {
if err = installUserModeDist(dist, v.ImagePath); err != nil { if err = installUserModeDist(dist, v.ImagePath); err != nil {
@ -439,21 +449,59 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
if err = createKeys(v, dist); err != nil { if err = createKeys(v, dist); err != nil {
return false, err return false, err
} }
callbackFuncs.Add(v.removeSSHKeys)
// Cycle so that user change goes into effect // Cycle so that user change goes into effect
_ = terminateDist(dist) _ = terminateDist(dist)
if err := v.writeConfig(); err != nil { if err = v.writeConfig(); err != nil {
return false, err return false, err
} }
callbackFuncs.Add(v.removeMachineConfig)
if err := setupConnections(v, opts); err != nil { if err = setupConnections(v, opts); err != nil {
return false, err return false, err
} }
callbackFuncs.Add(v.removeSystemConnections)
return true, nil return true, nil
} }
func (v *MachineVM) unprovisionWSL() error {
if err := terminateDist(toDist(v.Name)); err != nil {
logrus.Error(err)
}
if err := unregisterDist(toDist(v.Name)); err != nil {
logrus.Error(err)
}
vmDataDir, err := machine.GetDataDir(vmtype)
if err != nil {
return err
}
distDir := filepath.Join(vmDataDir, "wsldist")
distTarget := filepath.Join(distDir, v.Name)
return machine.GuardedRemoveAll(distTarget)
}
func (v *MachineVM) removeMachineConfig() error {
return machine.GuardedRemoveAll(v.ConfigPath)
}
func (v *MachineVM) removeMachineImage() error {
return machine.GuardedRemoveAll(v.ImagePath)
}
func (v *MachineVM) removeSSHKeys() error {
if err := machine.GuardedRemoveAll(fmt.Sprintf("%s.pub", v.IdentityPath)); err != nil {
logrus.Error(err)
}
return machine.GuardedRemoveAll(v.IdentityPath)
}
func (v *MachineVM) removeSystemConnections() error {
return machine.RemoveConnections(v.Name, fmt.Sprintf("%s-root", v.Name))
}
func downloadDistro(v *MachineVM, opts machine.InitOptions) error { func downloadDistro(v *MachineVM, opts machine.InitOptions) error {
var ( var (
dd machine.DistributionDownload dd machine.DistributionDownload
@ -1006,7 +1054,7 @@ func wslPipe(input string, dist string, arg ...string) error {
} }
func wslCreateKeys(identityPath string, dist string) (string, error) { func wslCreateKeys(identityPath string, dist string) (string, error) {
return machine.CreateSSHKeysPrefix(identityPath, true, true, "wsl", "-u", "root", "-d", dist) return machine.CreateSSHKeysPrefix(identityPath, true, false, "wsl", "-u", "root", "-d", dist)
} }
func runCmdPassThrough(name string, arg ...string) error { func runCmdPassThrough(name string, arg ...string) error {