diff options
Diffstat (limited to 'pkg/machine/qemu/machine.go')
-rw-r--r-- | pkg/machine/qemu/machine.go | 575 |
1 files changed, 414 insertions, 161 deletions
diff --git a/pkg/machine/qemu/machine.go b/pkg/machine/qemu/machine.go index 43a79dae8..e849bae3f 100644 --- a/pkg/machine/qemu/machine.go +++ b/pkg/machine/qemu/machine.go @@ -34,7 +34,7 @@ import ( var ( qemuProvider = &Provider{} - // vmtype refers to qemu (vs libvirt, krun, etc) + // vmtype refers to qemu (vs libvirt, krun, etc). vmtype = "qemu" ) @@ -71,10 +71,17 @@ func (p *Provider) NewMachine(opts machine.InitOptions) (machine.VM, error) { if len(opts.Name) > 0 { vm.Name = opts.Name } - ignitionFile := filepath.Join(vmConfigDir, vm.Name+".ign") - vm.IgnitionFilePath = ignitionFile + ignitionFile, err := NewMachineFile(filepath.Join(vmConfigDir, vm.Name+".ign"), nil) + if err != nil { + return nil, err + } + vm.IgnitionFilePath = *ignitionFile - vm.ImagePath = opts.ImagePath + imagePath, err := NewMachineFile(opts.ImagePath, nil) + if err != nil { + return nil, err + } + vm.ImagePath = *imagePath vm.RemoteUsername = opts.Username // Add a random port for ssh @@ -98,55 +105,133 @@ func (p *Provider) NewMachine(opts machine.InitOptions) (machine.VM, error) { return nil, err } - cmd := append([]string{execPath}) + cmd := []string{execPath} // Add memory cmd = append(cmd, []string{"-m", strconv.Itoa(int(vm.Memory))}...) // Add cpus cmd = append(cmd, []string{"-smp", strconv.Itoa(int(vm.CPUs))}...) // Add ignition file - cmd = append(cmd, []string{"-fw_cfg", "name=opt/com.coreos/config,file=" + vm.IgnitionFilePath}...) + cmd = append(cmd, []string{"-fw_cfg", "name=opt/com.coreos/config,file=" + vm.IgnitionFilePath.GetPath()}...) // Add qmp socket monitor, err := NewQMPMonitor("unix", vm.Name, defaultQMPTimeout) if err != nil { return nil, err } vm.QMPMonitor = monitor - cmd = append(cmd, []string{"-qmp", monitor.Network + ":/" + monitor.Address + ",server=on,wait=off"}...) + cmd = append(cmd, []string{"-qmp", monitor.Network + ":/" + monitor.Address.GetPath() + ",server=on,wait=off"}...) // Add network // Right now the mac address is hardcoded so that the host networking gives it a specific IP address. This is // why we can only run one vm at a time right now cmd = append(cmd, []string{"-netdev", "socket,id=vlan,fd=3", "-device", "virtio-net-pci,netdev=vlan,mac=5a:94:ef:e4:0c:ee"}...) - socketPath, err := getRuntimeDir() - if err != nil { + if err := vm.setReadySocket(); err != nil { return nil, err } - virtualSocketPath := filepath.Join(socketPath, "podman", vm.Name+"_ready.sock") + // Add serial port for readiness cmd = append(cmd, []string{ "-device", "virtio-serial", - "-chardev", "socket,path=" + virtualSocketPath + ",server=on,wait=off,id=" + vm.Name + "_ready", + // qemu needs to establish the long name; other connections can use the symlink'd + "-chardev", "socket,path=" + vm.ReadySocket.Path + ",server=on,wait=off,id=" + vm.Name + "_ready", "-device", "virtserialport,chardev=" + vm.Name + "_ready" + ",name=org.fedoraproject.port.0"}...) vm.CmdLine = cmd + if err := vm.setPIDSocket(); err != nil { + return nil, err + } return vm, nil } -// LoadByName reads a json file that describes a known qemu vm -// and returns a vm instance -func (p *Provider) LoadVMByName(name string) (machine.VM, error) { - vm := new(MachineVM) - vmConfigDir, err := machine.GetConfDir(vmtype) +// migrateVM takes the old configuration structure and migrates it +// to the new structure and writes it to the filesystem +func migrateVM(configPath string, config []byte, vm *MachineVM) error { + fmt.Printf("Migrating machine %q\n", vm.Name) + var old MachineVMV1 + err := json.Unmarshal(config, &old) if err != nil { - return nil, err + return err + } + // Looks like we loaded the older structure; now we need to migrate + // from the old structure to the new structure + _, pidFile, err := vm.getSocketandPid() + if err != nil { + return err } - b, err := ioutil.ReadFile(filepath.Join(vmConfigDir, name+".json")) - if os.IsNotExist(err) { - return nil, errors.Wrap(machine.ErrNoSuchVM, name) + + pidFilePath := MachineFile{Path: pidFile} + qmpMonitor := Monitor{ + Address: MachineFile{Path: old.QMPMonitor.Address}, + Network: old.QMPMonitor.Network, + Timeout: old.QMPMonitor.Timeout, } + socketPath, err := getRuntimeDir() if err != nil { + return err + } + virtualSocketPath := filepath.Join(socketPath, "podman", vm.Name+"_ready.sock") + readySocket := MachineFile{Path: virtualSocketPath} + + vm.HostUser = HostUser{} + vm.ImageConfig = ImageConfig{} + vm.ResourceConfig = ResourceConfig{} + vm.SSHConfig = SSHConfig{} + + ignitionFilePath, err := NewMachineFile(old.IgnitionFilePath, nil) + if err != nil { + return err + } + imagePath, err := NewMachineFile(old.ImagePath, nil) + if err != nil { + return err + } + + // setReadySocket will stick the entry into the new struct + if err := vm.setReadySocket(); err != nil { + return err + } + + vm.CPUs = old.CPUs + vm.CmdLine = old.CmdLine + vm.DiskSize = old.DiskSize + vm.IdentityPath = old.IdentityPath + vm.IgnitionFilePath = *ignitionFilePath + vm.ImagePath = *imagePath + vm.ImageStream = old.ImageStream + vm.Memory = old.Memory + vm.Mounts = old.Mounts + vm.Name = old.Name + vm.PidFilePath = pidFilePath + vm.Port = old.Port + vm.QMPMonitor = qmpMonitor + vm.ReadySocket = readySocket + vm.RemoteUsername = old.RemoteUsername + vm.Rootful = old.Rootful + vm.UID = old.UID + + // Backup the original config file + if err := os.Rename(configPath, configPath+".orig"); err != nil { + return err + } + // Write the config file + if err := vm.writeConfig(); err != nil { + // If the config file fails to be written, put the origina + // config file back before erroring + if renameError := os.Rename(configPath+".orig", configPath); renameError != nil { + logrus.Warn(renameError) + } + return err + } + // Remove the backup file + return os.Remove(configPath + ".orig") +} + +// LoadVMByName reads a json file that describes a known qemu vm +// and returns a vm instance +func (p *Provider) LoadVMByName(name string) (machine.VM, error) { + vm := &MachineVM{Name: name} + vm.HostUser = HostUser{UID: -1} // posix reserves -1, so use it to signify undefined + if err := vm.update(); err != nil { return nil, err } - err = json.Unmarshal(b, vm) // It is here for providing the ability to propagate // proxy settings (e.g. HTTP_PROXY and others) on a start @@ -162,7 +247,7 @@ func (p *Provider) LoadVMByName(name string) (machine.VM, error) { } logrus.Debug(vm.CmdLine) - return vm, err + return vm, nil } // Init writes the json configuration file to the filesystem for @@ -176,7 +261,7 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) { v.Rootful = opts.Rootful switch opts.ImagePath { - case "testing", "next", "stable", "": + case Testing, Next, Stable, "": // Get image as usual v.ImageStream = opts.ImagePath dd, err := machine.NewFcosDownloader(vmtype, v.Name, opts.ImagePath) @@ -184,7 +269,11 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) { if err != nil { return false, err } - v.ImagePath = dd.Get().LocalUncompressedFile + uncompressedFile, err := NewMachineFile(dd.Get().LocalUncompressedFile, nil) + if err != nil { + return false, err + } + v.ImagePath = *uncompressedFile if err := machine.DownloadImage(dd); err != nil { return false, err } @@ -196,14 +285,17 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) { if err != nil { return false, err } - v.ImagePath = g.Get().LocalUncompressedFile + imagePath, err := NewMachineFile(g.Get().LocalUncompressedFile, nil) + if err != nil { + return false, err + } + v.ImagePath = *imagePath if err := machine.DownloadImage(g); err != nil { return false, err } } // Add arch specific options including image location v.CmdLine = append(v.CmdLine, v.addArchOptions()...) - var volumeType string switch opts.VolumeDriver { case "virtfs": @@ -253,7 +345,7 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) { v.UID = os.Getuid() // Add location of bootable image - v.CmdLine = append(v.CmdLine, "-drive", "if=virtio,file="+v.ImagePath) + v.CmdLine = append(v.CmdLine, "-drive", "if=virtio,file="+v.getImageFile()) // This kind of stinks but no other way around this r/n if len(opts.IgnitionPath) < 1 { uri := machine.SSHRemoteConnection.MakeSSHURL("localhost", fmt.Sprintf("/run/user/%d/podman/podman.sock", v.UID), strconv.Itoa(v.Port), v.RemoteUsername) @@ -278,7 +370,9 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) { fmt.Println("An ignition path was provided. No SSH connection was added to Podman") } // Write the JSON file - v.writeConfig() + if err := v.writeConfig(); err != nil { + return false, fmt.Errorf("writing JSON file: %w", err) + } // User has provided ignition file so keygen // will be skipped. @@ -294,7 +388,7 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) { return false, err } - originalDiskSize, err := getDiskSize(v.ImagePath) + originalDiskSize, err := getDiskSize(v.getImageFile()) if err != nil { return false, err } @@ -311,11 +405,11 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) { if err != nil { return false, err } - resize := exec.Command(resizePath, []string{"resize", v.ImagePath, strconv.Itoa(int(opts.DiskSize)) + "G"}...) + resize := exec.Command(resizePath, []string{"resize", v.getImageFile(), strconv.Itoa(int(opts.DiskSize)) + "G"}...) resize.Stdout = os.Stdout resize.Stderr = os.Stderr if err := resize.Run(); err != nil { - return false, errors.Errorf("error resizing image: %q", err) + return false, errors.Errorf("resizing image: %q", err) } } // If the user provides an ignition file, we need to @@ -325,7 +419,7 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) { if err != nil { return false, err } - return false, ioutil.WriteFile(v.IgnitionFilePath, inputIgnition, 0644) + return false, ioutil.WriteFile(v.getIgnitionFile(), inputIgnition, 0644) } // Write the ignition file ign := machine.DynamicIgnition{ @@ -333,18 +427,31 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) { Key: key, VMName: v.Name, TimeZone: opts.TimeZone, - WritePath: v.IgnitionFilePath, + WritePath: v.getIgnitionFile(), UID: v.UID, } err = machine.NewIgnitionFile(ign) return err == nil, err } -func (v *MachineVM) Set(name string, opts machine.SetOptions) error { +func (v *MachineVM) Set(_ string, opts machine.SetOptions) error { if v.Rootful == opts.Rootful { return nil } + running, err := v.isRunning() + if err != nil { + return err + } + + if running { + suffix := "" + if v.Name != machine.DefaultMachineName { + suffix = " " + v.Name + } + return errors.Errorf("cannot change setting while the vm is running, run 'podman machine stop%s' first", suffix) + } + changeCon, err := machine.AnyConnectionDefault(v.Name, v.Name+"-root") if err != nil { return err @@ -370,9 +477,13 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error { conn net.Conn err error qemuSocketConn net.Conn - wait time.Duration = time.Millisecond * 500 + wait = time.Millisecond * 500 ) + if v.isIncompatible() { + logrus.Errorf("machine %q is incompatible with this release of podman and needs to be recreated, starting for recovery only", v.Name) + } + forwardSock, forwardState, err := v.startHostNetworking() if err != nil { return errors.Errorf("unable to start host networking: %q", err) @@ -390,17 +501,13 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error { return err } } - qemuSocketPath, _, err := v.getSocketandPid() - if err != nil { - return err - } // If the qemusocketpath exists and the vm is off/down, we should rm // it before the dial as to avoid a segv - if err := os.Remove(qemuSocketPath); err != nil && !errors.Is(err, os.ErrNotExist) { - logrus.Warn(err) + if err := v.QMPMonitor.Address.Delete(); err != nil { + return err } for i := 0; i < 6; i++ { - qemuSocketConn, err = net.Dial("unix", qemuSocketPath) + qemuSocketConn, err = net.Dial("unix", v.QMPMonitor.Address.GetPath()) if err == nil { break } @@ -424,13 +531,29 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error { // Disable graphic window when not in debug mode // Done in start, so we're not suck with the debug level we used on init - if logrus.GetLevel() != logrus.DebugLevel { + if !logrus.IsLevelEnabled(logrus.DebugLevel) { cmd = append(cmd, "-display", "none") } _, err = os.StartProcess(v.CmdLine[0], cmd, attr) if err != nil { - return err + // check if qemu was not found + if !errors.Is(err, os.ErrNotExist) { + return err + } + // lookup qemu again maybe the path was changed, https://github.com/containers/podman/issues/13394 + cfg, err := config.Default() + if err != nil { + return err + } + cmd[0], err = cfg.FindHelperBinary(QemuCommand, true) + if err != nil { + return err + } + _, err = os.StartProcess(cmd[0], cmd, attr) + if err != nil { + return err + } } fmt.Println("Waiting for VM ...") socketPath, err := getRuntimeDir() @@ -506,7 +629,7 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error { } } - waitAPIAndPrintInfo(forwardState, forwardSock, v.Rootful, v.Name) + v.waitAPIAndPrintInfo(forwardState, forwardSock) return nil } @@ -551,15 +674,15 @@ func (v *MachineVM) checkStatus(monitor *qmp.SocketMonitor) (machine.QemuMachine } // Stop uses the qmp monitor to call a system_powerdown -func (v *MachineVM) Stop(name string, _ machine.StopOptions) error { +func (v *MachineVM) Stop(_ string, _ machine.StopOptions) error { var disconnected bool // check if the qmp socket is there. if not, qemu instance is gone - if _, err := os.Stat(v.QMPMonitor.Address); os.IsNotExist(err) { + if _, err := os.Stat(v.QMPMonitor.Address.GetPath()); os.IsNotExist(err) { // Right now it is NOT an error to stop a stopped machine logrus.Debugf("QMP monitor socket %v does not exist", v.QMPMonitor.Address) return nil } - qmpMonitor, err := qmp.NewSocketMonitor(v.QMPMonitor.Network, v.QMPMonitor.Address, v.QMPMonitor.Timeout) + qmpMonitor, err := qmp.NewSocketMonitor(v.QMPMonitor.Network, v.QMPMonitor.Address.GetPath(), v.QMPMonitor.Timeout) if err != nil { return err } @@ -588,14 +711,10 @@ func (v *MachineVM) Stop(name string, _ machine.StopOptions) error { return err } - qemuSocketFile, pidFile, err := v.getSocketandPid() - if err != nil { - return err - } - if _, err := os.Stat(pidFile); os.IsNotExist(err) { + if _, err := os.Stat(v.PidFilePath.GetPath()); os.IsNotExist(err) { return nil } - pidString, err := ioutil.ReadFile(pidFile) + pidString, err := v.PidFilePath.Read() if err != nil { return err } @@ -613,16 +732,17 @@ func (v *MachineVM) Stop(name string, _ machine.StopOptions) error { return err } // Remove the pidfile - if err := os.Remove(pidFile); err != nil && !errors.Is(err, os.ErrNotExist) { - logrus.Warn(err) + if err := v.PidFilePath.Delete(); err != nil { + return err } // Remove socket - if err := os.Remove(qemuSocketFile); err != nil { + if err := v.QMPMonitor.Address.Delete(); err != nil { return err } if err := qmpMonitor.Disconnect(); err != nil { - return nil + // FIXME: this error should probably be returned + return nil // nolint: nilerr } disconnected = true @@ -639,7 +759,7 @@ func (v *MachineVM) Stop(name string, _ machine.StopOptions) error { waitInternal = waitInternal * 2 } - return nil + return v.ReadySocket.Delete() } // NewQMPMonitor creates the monitor subsection of our vm @@ -661,25 +781,30 @@ func NewQMPMonitor(network, name string, timeout time.Duration) (Monitor, error) if timeout == 0 { timeout = defaultQMPTimeout } + address, err := NewMachineFile(filepath.Join(rtDir, "qmp_"+name+".sock"), nil) + if err != nil { + return Monitor{}, err + } monitor := Monitor{ Network: network, - Address: filepath.Join(rtDir, "qmp_"+name+".sock"), + Address: *address, Timeout: timeout, } return monitor, nil } -func (v *MachineVM) Remove(name string, opts machine.RemoveOptions) (string, func() error, error) { +// Remove deletes all the files associated with a machine including ssh keys, the image itself +func (v *MachineVM) Remove(_ string, opts machine.RemoveOptions) (string, func() error, error) { var ( files []string ) - // cannot remove a running vm + // cannot remove a running vm unless --force is used running, err := v.isRunning() if err != nil { return "", nil, err } - if running { + if running && !opts.Force { return "", nil, errors.Errorf("running vm %q cannot be destroyed", v.Name) } @@ -688,16 +813,19 @@ func (v *MachineVM) Remove(name string, opts machine.RemoveOptions) (string, fun files = append(files, v.IdentityPath, v.IdentityPath+".pub") } if !opts.SaveIgnition { - files = append(files, v.IgnitionFilePath) + files = append(files, v.getIgnitionFile()) } if !opts.SaveImage { - files = append(files, v.ImagePath) + files = append(files, v.getImageFile()) } - socketPath, err := v.getForwardSocketPath() + socketPath, err := v.forwardSocketPath() if err != nil { - logrus.Error(err) + return "", nil, err + } + if socketPath.Symlink != nil { + files = append(files, *socketPath.Symlink) } - files = append(files, socketPath) + files = append(files, socketPath.Path) files = append(files, v.archRemovalFiles()...) if err := machine.RemoveConnection(v.Name); err != nil { @@ -717,19 +845,14 @@ func (v *MachineVM) Remove(name string, opts machine.RemoveOptions) (string, fun confirmationMessage += msg + "\n" } - // Get path to socket and pidFile before we do any cleanups - qemuSocketFile, pidFile, errSocketFile := v.getSocketandPid() - //silently try to delete socket and pid file //remove socket and pid file if any: warn at low priority if things fail - if errSocketFile == nil { - // Remove the pidfile - if err := os.Remove(pidFile); err != nil && !errors.Is(err, os.ErrNotExist) { - logrus.Debugf("Error while removing pidfile: %v", err) - } - // Remove socket - if err := os.Remove(qemuSocketFile); err != nil && !errors.Is(err, os.ErrNotExist) { - logrus.Debugf("Error while removing podman-machine-socket: %v", err) - } + // Remove the pidfile + if err := v.PidFilePath.Delete(); err != nil { + logrus.Debugf("Error while removing pidfile: %v", err) + } + // Remove socket + if err := v.QMPMonitor.Address.Delete(); err != nil { + logrus.Debugf("Error while removing podman-machine-socket: %v", err) } confirmationMessage += "\n" @@ -745,13 +868,14 @@ func (v *MachineVM) Remove(name string, opts machine.RemoveOptions) (string, fun func (v *MachineVM) isRunning() (bool, error) { // Check if qmp socket path exists - if _, err := os.Stat(v.QMPMonitor.Address); os.IsNotExist(err) { + if _, err := os.Stat(v.QMPMonitor.Address.GetPath()); os.IsNotExist(err) { return false, nil } // Check if we can dial it - monitor, err := qmp.NewSocketMonitor(v.QMPMonitor.Network, v.QMPMonitor.Address, v.QMPMonitor.Timeout) + monitor, err := qmp.NewSocketMonitor(v.QMPMonitor.Network, v.QMPMonitor.Address.GetPath(), v.QMPMonitor.Timeout) if err != nil { - return false, nil + // FIXME: this error should probably be returned + return false, nil // nolint: nilerr } if err := monitor.Connect(); err != nil { return false, err @@ -774,7 +898,7 @@ func (v *MachineVM) isRunning() (bool, error) { func (v *MachineVM) isListening() bool { // Check if we can dial it - conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", "localhost", v.Port), 10*time.Millisecond) + conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", "127.0.0.1", v.Port), 10*time.Millisecond) if err != nil { return false } @@ -784,7 +908,7 @@ func (v *MachineVM) isListening() bool { // SSH opens an interactive SSH session to the vm specified. // Added ssh function to VM interface: pkg/machine/config/go : line 58 -func (v *MachineVM) SSH(name string, opts machine.SSHOptions) error { +func (v *MachineVM) SSH(_ string, opts machine.SSHOptions) error { running, err := v.isRunning() if err != nil { return err @@ -860,10 +984,10 @@ func getDiskSize(path string) (uint64, error) { // List lists all vm's that use qemu virtualization func (p *Provider) List(_ machine.ListOptions) ([]*machine.ListResponse, error) { - return GetVMInfos() + return getVMInfos() } -func GetVMInfos() ([]*machine.ListResponse, error) { +func getVMInfos() ([]*machine.ListResponse, error) { vmConfigDir, err := machine.GetConfDir(vmtype) if err != nil { return nil, err @@ -871,17 +995,22 @@ func GetVMInfos() ([]*machine.ListResponse, error) { var listed []*machine.ListResponse - if err = filepath.Walk(vmConfigDir, func(path string, info os.FileInfo, err error) error { + if err = filepath.WalkDir(vmConfigDir, func(path string, d fs.DirEntry, err error) error { vm := new(MachineVM) - if strings.HasSuffix(info.Name(), ".json") { - fullPath := filepath.Join(vmConfigDir, info.Name()) + if strings.HasSuffix(d.Name(), ".json") { + fullPath := filepath.Join(vmConfigDir, d.Name()) b, err := ioutil.ReadFile(fullPath) if err != nil { return err } err = json.Unmarshal(b, vm) if err != nil { - return err + // Checking if the file did not unmarshal because it is using + // the deprecated config file format. + migrateErr := migrateVM(fullPath, b, vm) + if migrateErr != nil { + return migrateErr + } } listEntry := new(machine.ListResponse) @@ -900,7 +1029,7 @@ func GetVMInfos() ([]*machine.ListResponse, error) { } listEntry.CreatedAt = fi.ModTime() - fi, err = os.Stat(vm.ImagePath) + fi, err = os.Stat(vm.getImageFile()) if err != nil { return err } @@ -923,7 +1052,7 @@ func GetVMInfos() ([]*machine.ListResponse, error) { } func (p *Provider) IsValidVMName(name string) (bool, error) { - infos, err := GetVMInfos() + infos, err := getVMInfos() if err != nil { return false, err } @@ -938,7 +1067,7 @@ func (p *Provider) IsValidVMName(name string) (bool, error) { // CheckExclusiveActiveVM checks if there is a VM already running // that does not allow other VMs to be running func (p *Provider) CheckExclusiveActiveVM() (bool, string, error) { - vms, err := GetVMInfos() + vms, err := getVMInfos() if err != nil { return false, "", errors.Wrap(err, "error checking VM active") } @@ -962,20 +1091,20 @@ func (v *MachineVM) startHostNetworking() (string, apiForwardingState, error) { return "", noForwarding, err } - qemuSocket, pidFile, err := v.getSocketandPid() - if err != nil { - return "", noForwarding, err - } attr := new(os.ProcAttr) // Pass on stdin, stdout, stderr files := []*os.File{os.Stdin, os.Stdout, os.Stderr} attr.Files = files cmd := []string{binary} - cmd = append(cmd, []string{"-listen-qemu", fmt.Sprintf("unix://%s", qemuSocket), "-pid-file", pidFile}...) + cmd = append(cmd, []string{"-listen-qemu", fmt.Sprintf("unix://%s", v.QMPMonitor.Address.GetPath()), "-pid-file", v.PidFilePath.GetPath()}...) // Add the ssh port cmd = append(cmd, []string{"-ssh-port", fmt.Sprintf("%d", v.Port)}...) - cmd, forwardSock, state := v.setupAPIForwarding(cmd) + var forwardSock string + var state apiForwardingState + if !v.isIncompatible() { + cmd, forwardSock, state = v.setupAPIForwarding(cmd) + } if logrus.GetLevel() == logrus.DebugLevel { cmd = append(cmd, "--debug") @@ -986,7 +1115,7 @@ func (v *MachineVM) startHostNetworking() (string, apiForwardingState, error) { } func (v *MachineVM) setupAPIForwarding(cmd []string) ([]string, string, apiForwardingState) { - socket, err := v.getForwardSocketPath() + socket, err := v.forwardSocketPath() if err != nil { return cmd, "", noForwarding @@ -1000,58 +1129,126 @@ func (v *MachineVM) setupAPIForwarding(cmd []string) ([]string, string, apiForwa forwardUser = "root" } - cmd = append(cmd, []string{"-forward-sock", socket}...) + cmd = append(cmd, []string{"-forward-sock", socket.GetPath()}...) cmd = append(cmd, []string{"-forward-dest", destSock}...) cmd = append(cmd, []string{"-forward-user", forwardUser}...) cmd = append(cmd, []string{"-forward-identity", v.IdentityPath}...) - link := filepath.Join(filepath.Dir(filepath.Dir(socket)), "podman.sock") // The linking pattern is /var/run/docker.sock -> user global sock (link) -> machine sock (socket) // This allows the helper to only have to maintain one constant target to the user, which can be // repositioned without updating docker.sock. + + link, err := v.userGlobalSocketLink() + if err != nil { + return cmd, socket.GetPath(), machineLocal + } + if !dockerClaimSupported() { - return cmd, socket, claimUnsupported + return cmd, socket.GetPath(), claimUnsupported } if !dockerClaimHelperInstalled() { - return cmd, socket, notInstalled + return cmd, socket.GetPath(), notInstalled } - if !alreadyLinked(socket, link) { + if !alreadyLinked(socket.GetPath(), link) { if checkSockInUse(link) { - return cmd, socket, machineLocal + return cmd, socket.GetPath(), machineLocal } _ = os.Remove(link) - if err = os.Symlink(socket, link); err != nil { + if err = os.Symlink(socket.GetPath(), link); err != nil { logrus.Warnf("could not create user global API forwarding link: %s", err.Error()) - return cmd, socket, machineLocal + return cmd, socket.GetPath(), machineLocal } } if !alreadyLinked(link, dockerSock) { if checkSockInUse(dockerSock) { - return cmd, socket, machineLocal + return cmd, socket.GetPath(), machineLocal } if !claimDockerSock() { logrus.Warn("podman helper is installed, but was not able to claim the global docker sock") - return cmd, socket, machineLocal + return cmd, socket.GetPath(), machineLocal } } return cmd, dockerSock, dockerGlobal } -func (v *MachineVM) getForwardSocketPath() (string, error) { +func (v *MachineVM) isIncompatible() bool { + return v.UID == -1 +} + +func (v *MachineVM) userGlobalSocketLink() (string, error) { + path, err := machine.GetDataDir(v.Name) + if err != nil { + logrus.Errorf("Resolving data dir: %s", err.Error()) + return "", err + } + // User global socket is located in parent directory of machine dirs (one per user) + return filepath.Join(filepath.Dir(path), "podman.sock"), err +} + +func (v *MachineVM) forwardSocketPath() (*MachineFile, error) { + sockName := "podman.sock" path, err := machine.GetDataDir(v.Name) if err != nil { - logrus.Errorf("Error resolving data dir: %s", err.Error()) - return "", nil + logrus.Errorf("Resolving data dir: %s", err.Error()) + return nil, err + } + return NewMachineFile(filepath.Join(path, sockName), &sockName) +} + +func (v *MachineVM) setConfigPath() error { + vmConfigDir, err := machine.GetConfDir(vmtype) + if err != nil { + return err + } + + configPath, err := NewMachineFile(filepath.Join(vmConfigDir, v.Name)+".json", nil) + if err != nil { + return err + } + v.ConfigPath = *configPath + return nil +} + +func (v *MachineVM) setReadySocket() error { + readySocketName := v.Name + "_ready.sock" + rtPath, err := getRuntimeDir() + if err != nil { + return err + } + virtualSocketPath, err := NewMachineFile(filepath.Join(rtPath, "podman", readySocketName), &readySocketName) + if err != nil { + return err + } + v.ReadySocket = *virtualSocketPath + return nil +} + +func (v *MachineVM) setPIDSocket() error { + rtPath, err := getRuntimeDir() + if err != nil { + return err + } + if !rootless.IsRootless() { + rtPath = "/run" + } + pidFileName := fmt.Sprintf("%s.pid", v.Name) + socketDir := filepath.Join(rtPath, "podman") + pidFilePath, err := NewMachineFile(filepath.Join(socketDir, pidFileName), &pidFileName) + if err != nil { + return err } - return filepath.Join(path, "podman.sock"), nil + v.PidFilePath = *pidFilePath + return nil } +// Deprecated: getSocketandPid is being replace by setPIDSocket and +// machinefiles. func (v *MachineVM) getSocketandPid() (string, string, error) { rtPath, err := getRuntimeDir() if err != nil { @@ -1085,10 +1282,13 @@ func waitAndPingAPI(sock string) { Transport: &http.Transport{ DialContext: func(context.Context, string, string) (net.Conn, error) { con, err := net.DialTimeout("unix", sock, apiUpTimeout) - if err == nil { - con.SetDeadline(time.Now().Add(apiUpTimeout)) + if err != nil { + return nil, err } - return con, err + if err := con.SetDeadline(time.Now().Add(apiUpTimeout)); err != nil { + return nil, err + } + return con, nil }, }, } @@ -1102,66 +1302,119 @@ func waitAndPingAPI(sock string) { } } -func waitAPIAndPrintInfo(forwardState apiForwardingState, forwardSock string, rootFul bool, name string) { - if forwardState != noForwarding { - waitAndPingAPI(forwardSock) - if !rootFul { - fmt.Printf("\nThis machine is currently configured in rootless mode. If your containers\n") - fmt.Printf("require root permissions (e.g. ports < 1024), or if you run into compatibility\n") - fmt.Printf("issues with non-podman clients, you can switch using the following command: \n") - - suffix := "" - if name != machine.DefaultMachineName { - suffix = " " + name +func (v *MachineVM) waitAPIAndPrintInfo(forwardState apiForwardingState, forwardSock string) { + suffix := "" + if v.Name != machine.DefaultMachineName { + suffix = " " + v.Name + } + + if v.isIncompatible() { + fmt.Fprintf(os.Stderr, "\n!!! ACTION REQUIRED: INCOMPATIBLE MACHINE !!!\n") + + fmt.Fprintf(os.Stderr, "\nThis machine was created by an older podman release that is incompatible\n") + fmt.Fprintf(os.Stderr, "with this release of podman. It has been started in a limited operational\n") + fmt.Fprintf(os.Stderr, "mode to allow you to copy any necessary files before recreating it. This\n") + fmt.Fprintf(os.Stderr, "can be accomplished with the following commands:\n\n") + fmt.Fprintf(os.Stderr, "\t# Login and copy desired files (Optional)\n") + fmt.Fprintf(os.Stderr, "\t# podman machine ssh%s tar cvPf - /path/to/files > backup.tar\n\n", suffix) + fmt.Fprintf(os.Stderr, "\t# Recreate machine (DESTRUCTIVE!) \n") + fmt.Fprintf(os.Stderr, "\tpodman machine stop%s\n", suffix) + fmt.Fprintf(os.Stderr, "\tpodman machine rm -f%s\n", suffix) + fmt.Fprintf(os.Stderr, "\tpodman machine init --now%s\n\n", suffix) + fmt.Fprintf(os.Stderr, "\t# Copy back files (Optional)\n") + fmt.Fprintf(os.Stderr, "\t# cat backup.tar | podman machine ssh%s tar xvPf - \n\n", suffix) + } + + if forwardState == noForwarding { + return + } + + waitAndPingAPI(forwardSock) + if !v.Rootful { + fmt.Printf("\nThis machine is currently configured in rootless mode. If your containers\n") + fmt.Printf("require root permissions (e.g. ports < 1024), or if you run into compatibility\n") + fmt.Printf("issues with non-podman clients, you can switch using the following command: \n") + fmt.Printf("\n\tpodman machine set --rootful%s\n\n", suffix) + } + + fmt.Printf("API forwarding listening on: %s\n", forwardSock) + if forwardState == dockerGlobal { + fmt.Printf("Docker API clients default to this address. You do not need to set DOCKER_HOST.\n\n") + } else { + stillString := "still " + switch forwardState { + case notInstalled: + fmt.Printf("\nThe system helper service is not installed; the default Docker API socket\n") + fmt.Printf("address can't be used by podman. ") + if helper := findClaimHelper(); len(helper) > 0 { + fmt.Printf("If you would like to install it run the\nfollowing commands:\n") + fmt.Printf("\n\tsudo %s install\n", helper) + fmt.Printf("\tpodman machine stop%s; podman machine start%s\n\n", suffix, suffix) } - fmt.Printf("\n\tpodman machine set --rootful%s\n\n", suffix) + case machineLocal: + fmt.Printf("\nAnother process was listening on the default Docker API socket address.\n") + case claimUnsupported: + fallthrough + default: + stillString = "" } - fmt.Printf("API forwarding listening on: %s\n", forwardSock) - if forwardState == dockerGlobal { - fmt.Printf("Docker API clients default to this address. You do not need to set DOCKER_HOST.\n\n") - } else { - stillString := "still " - switch forwardState { - case notInstalled: - fmt.Printf("\nThe system helper service is not installed; the default Docker API socket\n") - fmt.Printf("address can't be used by podman. ") - if helper := findClaimHelper(); len(helper) > 0 { - fmt.Printf("If you would like to install it run the\nfollowing command:\n") - fmt.Printf("\n\tsudo %s install\n\n", helper) - } - case machineLocal: - fmt.Printf("\nAnother process was listening on the default Docker API socket address.\n") - case claimUnsupported: - fallthrough - default: - stillString = "" - } + fmt.Printf("You can %sconnect Docker API clients by setting DOCKER_HOST using the\n", stillString) + fmt.Printf("following command in your terminal session:\n") + fmt.Printf("\n\texport DOCKER_HOST='unix://%s'\n\n", forwardSock) + } +} - fmt.Printf("You can %sconnect Docker API clients by setting DOCKER_HOST using the\n", stillString) - fmt.Printf("following command in your terminal session:\n") - fmt.Printf("\n\texport DOCKER_HOST='unix://%s'\n\n", forwardSock) +// update returns the content of the VM's +// configuration file in json +func (v *MachineVM) update() error { + if err := v.setConfigPath(); err != nil { + return err + } + b, err := v.ConfigPath.Read() + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return errors.Wrap(machine.ErrNoSuchVM, v.Name) + } + return err + } + if err != nil { + return err + } + err = json.Unmarshal(b, v) + if err != nil { + err = migrateVM(v.ConfigPath.GetPath(), b, v) + if err != nil { + return err } } + return err } func (v *MachineVM) writeConfig() error { - // GetConfDir creates the directory so no need to check for - // its existence - vmConfigDir, err := machine.GetConfDir(vmtype) - if err != nil { + // Set the path of the configfile before writing to make + // life easier down the line + if err := v.setConfigPath(); err != nil { return err } - - jsonFile := filepath.Join(vmConfigDir, v.Name) + ".json" // Write the JSON file b, err := json.MarshalIndent(v, "", " ") if err != nil { return err } - if err := ioutil.WriteFile(jsonFile, b, 0644); err != nil { + if err := ioutil.WriteFile(v.ConfigPath.GetPath(), b, 0644); err != nil { return err } - return nil } + +// getImageFile wrapper returns the path to the image used +// to boot the VM +func (v *MachineVM) getImageFile() string { + return v.ImagePath.GetPath() +} + +// getIgnitionFile wrapper returns the path to the ignition file +func (v *MachineVM) getIgnitionFile() string { + return v.IgnitionFilePath.GetPath() +} |