From 7a79f708a4521ba7c42da83a204a01ace010ace3 Mon Sep 17 00:00:00 2001 From: baude Date: Thu, 25 Mar 2021 10:35:43 -0500 Subject: Podman machine enhancements Podman machine remove is now called `rm`. Podman machine create now supports resizing the image to the value of --disk-size as provided. The default is to 10G. Added systemd unit file on guest via ignition that sends a Ready message to the host over a virtio-socket so that we know when the VM is booted and ready for use. Podman machine commands no longer require a VM name as an argument. A default VM name is defined and if no VM name is provided as a arg, the default will be used. [NO TESTS NEEDED] Signed-off-by: baude --- cmd/podman/machine/init.go | 43 ++++++------ cmd/podman/machine/remove.go | 88 ----------------------- cmd/podman/machine/rm.go | 92 +++++++++++++++++++++++++ cmd/podman/machine/ssh.go | 12 ++-- cmd/podman/machine/start.go | 12 ++-- cmd/podman/machine/stop.go | 12 ++-- docs/source/machine.rst | 2 +- docs/source/markdown/podman-machine-init.1.md | 4 ++ docs/source/markdown/podman-machine-remove.1.md | 65 ----------------- docs/source/markdown/podman-machine-rm.1.md | 65 +++++++++++++++++ docs/source/markdown/podman-machine-ssh.1.md | 2 +- docs/source/markdown/podman-machine-start.1.md | 2 +- docs/source/markdown/podman-machine-stop.1.md | 2 +- docs/source/markdown/podman-machine.1.md | 8 +-- pkg/machine/config.go | 14 ++-- pkg/machine/ignition.go | 51 +++++++++++--- pkg/machine/qemu/machine.go | 61 ++++++++++++++-- 17 files changed, 316 insertions(+), 219 deletions(-) delete mode 100644 cmd/podman/machine/remove.go create mode 100644 cmd/podman/machine/rm.go delete mode 100644 docs/source/markdown/podman-machine-remove.1.md create mode 100644 docs/source/markdown/podman-machine-rm.1.md diff --git a/cmd/podman/machine/init.go b/cmd/podman/machine/init.go index 900f67e2f..05474fd89 100644 --- a/cmd/podman/machine/init.go +++ b/cmd/podman/machine/init.go @@ -8,6 +8,7 @@ import ( "github.com/containers/podman/v3/pkg/domain/entities" "github.com/containers/podman/v3/pkg/machine" "github.com/containers/podman/v3/pkg/machine/qemu" + "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -23,17 +24,8 @@ var ( } ) -type InitCLIOptions struct { - CPUS uint64 - Memory uint64 - Devices []string - ImagePath string - IgnitionPath string - Name string -} - var ( - initOpts = InitCLIOptions{} + initOpts = machine.InitOptions{} defaultMachineName string = "podman-machine-default" ) @@ -53,6 +45,15 @@ func init() { ) _ = initCmd.RegisterFlagCompletionFunc(cpusFlagName, completion.AutocompleteNone) + diskSizeFlagName := "disk-size" + flags.Uint64Var( + &initOpts.DiskSize, + diskSizeFlagName, 10, + "Disk size in GB", + ) + + _ = initCmd.RegisterFlagCompletionFunc(diskSizeFlagName, completion.AutocompleteNone) + memoryFlagName := "memory" flags.Uint64VarP( &initOpts.Memory, @@ -72,28 +73,24 @@ func init() { // TODO should we allow for a users to append to the qemu cmdline? func initMachine(cmd *cobra.Command, args []string) error { - initOpts.Name = defaultMachineName - if len(args) > 0 { - initOpts.Name = args[0] - } - vmOpts := machine.InitOptions{ - CPUS: initOpts.CPUS, - Memory: initOpts.Memory, - IgnitionPath: initOpts.IgnitionPath, - ImagePath: initOpts.ImagePath, - Name: initOpts.Name, - } var ( vm machine.VM vmType string err error ) + initOpts.Name = defaultMachineName + if len(args) > 0 { + initOpts.Name = args[0] + } switch vmType { default: // qemu is the default - vm, err = qemu.NewMachine(vmOpts) + if _, err := qemu.LoadVMByName(initOpts.Name); err == nil { + return errors.Wrap(machine.ErrVMAlreadyExists, initOpts.Name) + } + vm, err = qemu.NewMachine(initOpts) } if err != nil { return err } - return vm.Init(vmOpts) + return vm.Init(initOpts) } diff --git a/cmd/podman/machine/remove.go b/cmd/podman/machine/remove.go deleted file mode 100644 index f6ce9e326..000000000 --- a/cmd/podman/machine/remove.go +++ /dev/null @@ -1,88 +0,0 @@ -// +build amd64,linux amd64,darwin arm64,darwin - -package machine - -import ( - "bufio" - "fmt" - "os" - "strings" - - "github.com/containers/common/pkg/completion" - "github.com/containers/podman/v3/cmd/podman/registry" - "github.com/containers/podman/v3/pkg/domain/entities" - "github.com/containers/podman/v3/pkg/machine" - "github.com/containers/podman/v3/pkg/machine/qemu" - "github.com/spf13/cobra" -) - -var ( - removeCmd = &cobra.Command{ - Use: "remove [options] NAME", - Short: "Remove an existing machine", - Long: "Remove an existing machine ", - RunE: remove, - Args: cobra.ExactArgs(1), - Example: `podman machine remove myvm`, - ValidArgsFunction: completion.AutocompleteNone, - } -) - -var ( - destoryOptions machine.RemoveOptions -) - -func init() { - registry.Commands = append(registry.Commands, registry.CliCommand{ - Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, - Command: removeCmd, - Parent: machineCmd, - }) - - flags := removeCmd.Flags() - formatFlagName := "force" - flags.BoolVar(&destoryOptions.Force, formatFlagName, false, "Do not prompt before removeing") - - keysFlagName := "save-keys" - flags.BoolVar(&destoryOptions.SaveKeys, keysFlagName, false, "Do not delete SSH keys") - - ignitionFlagName := "save-ignition" - flags.BoolVar(&destoryOptions.SaveIgnition, ignitionFlagName, false, "Do not delete ignition file") - - imageFlagName := "save-image" - flags.BoolVar(&destoryOptions.SaveImage, imageFlagName, false, "Do not delete the image file") -} - -func remove(cmd *cobra.Command, args []string) error { - var ( - err error - vm machine.VM - vmType string - ) - switch vmType { - default: - vm, err = qemu.LoadVMByName(args[0]) - } - if err != nil { - return err - } - confirmationMessage, doIt, err := vm.Remove(args[0], machine.RemoveOptions{}) - if err != nil { - return err - } - - if !destoryOptions.Force { - // Warn user - fmt.Println(confirmationMessage) - reader := bufio.NewReader(os.Stdin) - fmt.Print("Are you sure you want to continue? [y/N] ") - answer, err := reader.ReadString('\n') - if err != nil { - return err - } - if strings.ToLower(answer)[0] != 'y' { - return nil - } - } - return doIt() -} diff --git a/cmd/podman/machine/rm.go b/cmd/podman/machine/rm.go new file mode 100644 index 000000000..cd2cc84f2 --- /dev/null +++ b/cmd/podman/machine/rm.go @@ -0,0 +1,92 @@ +// +build amd64,linux amd64,darwin arm64,darwin + +package machine + +import ( + "bufio" + "fmt" + "os" + "strings" + + "github.com/containers/common/pkg/completion" + "github.com/containers/podman/v3/cmd/podman/registry" + "github.com/containers/podman/v3/pkg/domain/entities" + "github.com/containers/podman/v3/pkg/machine" + "github.com/containers/podman/v3/pkg/machine/qemu" + "github.com/spf13/cobra" +) + +var ( + rmCmd = &cobra.Command{ + Use: "rm [options] [NAME]", + Short: "Remove an existing machine", + Long: "Remove an existing machine ", + RunE: rm, + Args: cobra.MaximumNArgs(1), + Example: `podman machine rm myvm`, + ValidArgsFunction: completion.AutocompleteNone, + } +) + +var ( + destoryOptions machine.RemoveOptions +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: rmCmd, + Parent: machineCmd, + }) + + flags := rmCmd.Flags() + formatFlagName := "force" + flags.BoolVar(&destoryOptions.Force, formatFlagName, false, "Do not prompt before rming") + + keysFlagName := "save-keys" + flags.BoolVar(&destoryOptions.SaveKeys, keysFlagName, false, "Do not delete SSH keys") + + ignitionFlagName := "save-ignition" + flags.BoolVar(&destoryOptions.SaveIgnition, ignitionFlagName, false, "Do not delete ignition file") + + imageFlagName := "save-image" + flags.BoolVar(&destoryOptions.SaveImage, imageFlagName, false, "Do not delete the image file") +} + +func rm(cmd *cobra.Command, args []string) error { + var ( + err error + vm machine.VM + vmType string + ) + vmName := defaultMachineName + if len(args) > 0 && len(args[0]) > 0 { + vmName = args[0] + } + switch vmType { + default: + vm, err = qemu.LoadVMByName(vmName) + } + if err != nil { + return err + } + confirmationMessage, remove, err := vm.Remove(vmName, machine.RemoveOptions{}) + if err != nil { + return err + } + + if !destoryOptions.Force { + // Warn user + fmt.Println(confirmationMessage) + reader := bufio.NewReader(os.Stdin) + fmt.Print("Are you sure you want to continue? [y/N] ") + answer, err := reader.ReadString('\n') + if err != nil { + return err + } + if strings.ToLower(answer)[0] != 'y' { + return nil + } + } + return remove() +} diff --git a/cmd/podman/machine/ssh.go b/cmd/podman/machine/ssh.go index a7111a195..879122a14 100644 --- a/cmd/podman/machine/ssh.go +++ b/cmd/podman/machine/ssh.go @@ -14,11 +14,11 @@ import ( var ( sshCmd = &cobra.Command{ - Use: "ssh [options] NAME [COMMAND [ARG ...]]", + Use: "ssh [options] [NAME] [COMMAND [ARG ...]]", Short: "SSH into a virtual machine", Long: "SSH into a virtual machine ", RunE: ssh, - Args: cobra.MinimumNArgs(1), + Args: cobra.MaximumNArgs(1), Example: `podman machine ssh myvm podman machine ssh -e myvm echo hello`, @@ -48,6 +48,10 @@ func ssh(cmd *cobra.Command, args []string) error { vm machine.VM vmType string ) + vmName := defaultMachineName + if len(args) > 0 && len(args[0]) > 1 { + vmName = args[0] + } sshOpts.Args = args[1:] // Error if no execute but args given @@ -61,10 +65,10 @@ func ssh(cmd *cobra.Command, args []string) error { switch vmType { default: - vm, err = qemu.LoadVMByName(args[0]) + vm, err = qemu.LoadVMByName(vmName) } if err != nil { return errors.Wrapf(err, "vm %s not found", args[0]) } - return vm.SSH(args[0], sshOpts) + return vm.SSH(vmName, sshOpts) } diff --git a/cmd/podman/machine/start.go b/cmd/podman/machine/start.go index 44ade2850..80fd77102 100644 --- a/cmd/podman/machine/start.go +++ b/cmd/podman/machine/start.go @@ -13,11 +13,11 @@ import ( var ( startCmd = &cobra.Command{ - Use: "start NAME", + Use: "start [NAME]", Short: "Start an existing machine", Long: "Start an existing machine ", RunE: start, - Args: cobra.ExactArgs(1), + Args: cobra.MaximumNArgs(1), Example: `podman machine start myvm`, ValidArgsFunction: completion.AutocompleteNone, } @@ -37,12 +37,16 @@ func start(cmd *cobra.Command, args []string) error { vm machine.VM vmType string ) + vmName := defaultMachineName + if len(args) > 0 && len(args[0]) > 0 { + vmName = args[0] + } switch vmType { default: - vm, err = qemu.LoadVMByName(args[0]) + vm, err = qemu.LoadVMByName(vmName) } if err != nil { return err } - return vm.Start(args[0], machine.StartOptions{}) + return vm.Start(vmName, machine.StartOptions{}) } diff --git a/cmd/podman/machine/stop.go b/cmd/podman/machine/stop.go index 35fd4ff95..4fcb065a3 100644 --- a/cmd/podman/machine/stop.go +++ b/cmd/podman/machine/stop.go @@ -13,11 +13,11 @@ import ( var ( stopCmd = &cobra.Command{ - Use: "stop NAME", + Use: "stop [NAME]", Short: "Stop an existing machine", Long: "Stop an existing machine ", RunE: stop, - Args: cobra.ExactArgs(1), + Args: cobra.MaximumNArgs(1), Example: `podman machine stop myvm`, ValidArgsFunction: completion.AutocompleteNone, } @@ -38,12 +38,16 @@ func stop(cmd *cobra.Command, args []string) error { vm machine.VM vmType string ) + vmName := defaultMachineName + if len(args) > 0 && len(args[0]) > 0 { + vmName = args[0] + } switch vmType { default: - vm, err = qemu.LoadVMByName(args[0]) + vm, err = qemu.LoadVMByName(vmName) } if err != nil { return err } - return vm.Stop(args[0], machine.StopOptions{}) + return vm.Stop(vmName, machine.StopOptions{}) } diff --git a/docs/source/machine.rst b/docs/source/machine.rst index 55df29667..be9ef1e95 100644 --- a/docs/source/machine.rst +++ b/docs/source/machine.rst @@ -3,7 +3,7 @@ Machine :doc:`init ` Initialize a new virtual machine -:doc:`remove ` Remove a virtual machine +:doc:`rm ` Remove a virtual machine :doc:`ssh ` SSH into a virtual machine :doc:`start ` Start a virtual machine :doc:`stop ` Stop a virtual machine diff --git a/docs/source/markdown/podman-machine-init.1.md b/docs/source/markdown/podman-machine-init.1.md index 5ff07de03..be07a7bd5 100644 --- a/docs/source/markdown/podman-machine-init.1.md +++ b/docs/source/markdown/podman-machine-init.1.md @@ -22,6 +22,10 @@ tied to the Linux kernel. Number of CPUs. +#### **--disk-size**=*number* + +Size of the disk for the guest VM in GB. + #### **--ignition-path** Fully qualified path of the ignition file diff --git a/docs/source/markdown/podman-machine-remove.1.md b/docs/source/markdown/podman-machine-remove.1.md deleted file mode 100644 index 07763741d..000000000 --- a/docs/source/markdown/podman-machine-remove.1.md +++ /dev/null @@ -1,65 +0,0 @@ -% podman-machine-remove(1) - -## NAME -podman\-machine\-remove - Remove a virtual machine - -## SYNOPSIS -**podman machine remove** [*options*] *name* - -## DESCRIPTION - -Remove a virtual machine and its related files. What is actually deleted -depends on the virtual machine type. For all virtual machines, the generated -SSH keys and the podman system connection are deleted. The ignition files -generated for that VM are also removeed as is its image file on the filesystem. - -Users get a display of what will be deleted and are required to confirm unless the option `--force` -is used. - - -## OPTIONS - -#### **--help** - -Print usage statement. - -#### **--force** - -Delete without confirmation - -#### **--save-ignition** - -Do not delete the generated ignition file - -#### **--save-image** - -Do not delete the VM image - -#### **--save-keys** - -Do not delete the SSH keys for the VM. The system connection is always -deleted. - -## EXAMPLES - -Remove a VM named "test1" - -``` -$ podman machine remove test1 - -The following files will be deleted: - -/home/user/.ssh/test1 -/home/user/.ssh/test1.pub -/home/user/.config/containers/podman/machine/qemu/test1.ign -/home/user/.local/share/containers/podman/machine/qemu/test1_fedora-coreos-33.20210315.1.0-qemu.x86_64.qcow2 -/home/user/.config/containers/podman/machine/qemu/test1.json - -Are you sure you want to continue? [y/N] y -``` - -## SEE ALSO -podman-machine (1) - -## HISTORY -March 2021, Originally compiled by Ashley Cui diff --git a/docs/source/markdown/podman-machine-rm.1.md b/docs/source/markdown/podman-machine-rm.1.md new file mode 100644 index 000000000..4da17fdcb --- /dev/null +++ b/docs/source/markdown/podman-machine-rm.1.md @@ -0,0 +1,65 @@ +% podman-machine-rm(1) + +## NAME +podman\-machine\-rm - Remove a virtual machine + +## SYNOPSIS +**podman machine rm** [*options*] [*name*] + +## DESCRIPTION + +Remove a virtual machine and its related files. What is actually deleted +depends on the virtual machine type. For all virtual machines, the generated +SSH keys and the podman system connection are deleted. The ignition files +generated for that VM are also removed as is its image file on the filesystem. + +Users get a display of what will be deleted and are required to confirm unless the option `--force` +is used. + + +## OPTIONS + +#### **--help** + +Print usage statement. + +#### **--force** + +Delete without confirmation + +#### **--save-ignition** + +Do not delete the generated ignition file + +#### **--save-image** + +Do not delete the VM image + +#### **--save-keys** + +Do not delete the SSH keys for the VM. The system connection is always +deleted. + +## EXAMPLES + +Remove a VM named "test1" + +``` +$ podman machine rm test1 + +The following files will be deleted: + +/home/user/.ssh/test1 +/home/user/.ssh/test1.pub +/home/user/.config/containers/podman/machine/qemu/test1.ign +/home/user/.local/share/containers/podman/machine/qemu/test1_fedora-coreos-33.20210315.1.0-qemu.x86_64.qcow2 +/home/user/.config/containers/podman/machine/qemu/test1.json + +Are you sure you want to continue? [y/N] y +``` + +## SEE ALSO +podman-machine (1) + +## HISTORY +March 2021, Originally compiled by Ashley Cui diff --git a/docs/source/markdown/podman-machine-ssh.1.md b/docs/source/markdown/podman-machine-ssh.1.md index bcecd1010..01cec1f57 100644 --- a/docs/source/markdown/podman-machine-ssh.1.md +++ b/docs/source/markdown/podman-machine-ssh.1.md @@ -4,7 +4,7 @@ podman\-machine\-ssh - SSH into a virtual machine ## SYNOPSIS -**podman machine ssh** [*options*] *name* [*command* [*arg* ...]] +**podman machine ssh** [*options*] [*name*] [*command* [*arg* ...]] ## DESCRIPTION diff --git a/docs/source/markdown/podman-machine-start.1.md b/docs/source/markdown/podman-machine-start.1.md index 511296b11..7f3a9f592 100644 --- a/docs/source/markdown/podman-machine-start.1.md +++ b/docs/source/markdown/podman-machine-start.1.md @@ -4,7 +4,7 @@ podman\-machine\-start - Start a virtual machine ## SYNOPSIS -**podman machine start** *name* +**podman machine start** [*name*] ## DESCRIPTION diff --git a/docs/source/markdown/podman-machine-stop.1.md b/docs/source/markdown/podman-machine-stop.1.md index 62439cbb1..f4be54511 100644 --- a/docs/source/markdown/podman-machine-stop.1.md +++ b/docs/source/markdown/podman-machine-stop.1.md @@ -4,7 +4,7 @@ podman\-machine\-stop - Stop a virtual machine ## SYNOPSIS -**podman machine stop** *name* +**podman machine stop** [*name*] ## DESCRIPTION diff --git a/docs/source/markdown/podman-machine.1.md b/docs/source/markdown/podman-machine.1.md index 0e3c1ca34..a5d3b78df 100644 --- a/docs/source/markdown/podman-machine.1.md +++ b/docs/source/markdown/podman-machine.1.md @@ -14,10 +14,10 @@ podman\-machine - Manage Podman's virtual machine | Command | Man Page | Description | | ------- | ------------------------------------------------------- | --------------------------------- | | init | [podman-machine-init(1)](podman-machine-init.1.md) | Initialize a new virtual machine | -| remove | [podman-machine-remove(1)](podman-machine-remove.1.md) | Remove a virtual machine | -| ssh | [podman-machine-ssh(1)](podman-machine-ssh.1.md) | SSH into a virtual machine | -| start | [podman-machine-start(1)](podman-machine-start.1.md) | Start a virtual machine | -| stop | [podman-machine-stop(1)](podman-machine-stop.1.md) | Stop a virtual machine | +| rm | [podman-machine-rm(1)](podman-machine-rm.1.md)| Remove a virtual machine | +| ssh | [podman-machine-ssh(1)](podman-machine-ssh.1.md) | SSH into a virtual machine | +| start | [podman-machine-start(1)](podman-machine-start.1.md) | Start a virtual machine | +| stop | [podman-machine-stop(1)](podman-machine-stop.1.md) | Stop a virtual machine | ## SEE ALSO podman(1) diff --git a/pkg/machine/config.go b/pkg/machine/config.go index 4933deee8..273deca00 100644 --- a/pkg/machine/config.go +++ b/pkg/machine/config.go @@ -7,19 +7,19 @@ import ( "path/filepath" "github.com/containers/storage/pkg/homedir" + "github.com/pkg/errors" ) type InitOptions struct { - Name string CPUS uint64 - Memory uint64 + DiskSize uint64 IgnitionPath string ImagePath string - Username string - URI url.URL IsDefault bool - //KernelPath string - //Devices []VMDevices + Memory uint64 + Name string + URI url.URL + Username string } type RemoteConnectionType string @@ -27,6 +27,8 @@ type RemoteConnectionType string var ( SSHRemoteConnection RemoteConnectionType = "ssh" DefaultIgnitionUserName = "core" + ErrNoSuchVM = errors.New("VM does not exist") + ErrVMAlreadyExists = errors.New("VM already exists") ) type Download struct { diff --git a/pkg/machine/ignition.go b/pkg/machine/ignition.go index ff79d5afb..a68d68ac3 100644 --- a/pkg/machine/ignition.go +++ b/pkg/machine/ignition.go @@ -2,6 +2,7 @@ package machine import ( "encoding/json" + "fmt" "io/ioutil" ) @@ -37,10 +38,17 @@ func getNodeGrp(grpName string) NodeGroup { return NodeGroup{Name: &grpName} } +type DynamicIgnition struct { + Name string + Key string + VMName string + WritePath string +} + // NewIgnitionFile -func NewIgnitionFile(name, key, writePath string) error { - if len(name) < 1 { - name = DefaultIgnitionUserName +func NewIgnitionFile(ign DynamicIgnition) error { + if len(ign.Name) < 1 { + ign.Name = DefaultIgnitionUserName } ignVersion := Ignition{ Version: "3.2.0", @@ -48,23 +56,44 @@ func NewIgnitionFile(name, key, writePath string) error { ignPassword := Passwd{ Users: []PasswdUser{{ - Name: name, - SSHAuthorizedKeys: []SSHAuthorizedKey{SSHAuthorizedKey(key)}, + Name: ign.Name, + SSHAuthorizedKeys: []SSHAuthorizedKey{SSHAuthorizedKey(ign.Key)}, }}, } ignStorage := Storage{ - Directories: getDirs(name), - Files: getFiles(name), - Links: getLinks(name), + Directories: getDirs(ign.Name), + Files: getFiles(ign.Name), + Links: getLinks(ign.Name), } + + // ready is a unit file that sets up the virtual serial device + // where when the VM is done configuring, it will send an ack + // so a listening host knows it can being interacting with it + ready := `[Unit] +Requires=dev-virtio\\x2dports-%s.device +OnFailure=emergency.target +OnFailureJobMode=isolate +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=/bin/sh -c '/usr/bin/echo Ready >/dev/%s' +[Install] +RequiredBy=multi-user.target +` + _ = ready ignSystemd := Systemd{ Units: []Unit{ { Enabled: boolToPtr(true), Name: "podman.socket", - }}} - + }, + { + Enabled: boolToPtr(true), + Name: "ready.service", + Contents: strToPtr(fmt.Sprintf(ready, "vport1p1", "vport1p1")), + }, + }} ignConfig := Config{ Ignition: ignVersion, Passwd: ignPassword, @@ -75,7 +104,7 @@ func NewIgnitionFile(name, key, writePath string) error { if err != nil { return err } - return ioutil.WriteFile(writePath, b, 0644) + return ioutil.WriteFile(ign.WritePath, b, 0644) } func getDirs(usrName string) []Directory { diff --git a/pkg/machine/qemu/machine.go b/pkg/machine/qemu/machine.go index b97eb991a..fe155750f 100644 --- a/pkg/machine/qemu/machine.go +++ b/pkg/machine/qemu/machine.go @@ -1,9 +1,11 @@ package qemu import ( + "bufio" "encoding/json" "fmt" "io/ioutil" + "net" "os" "os/exec" "path/filepath" @@ -22,9 +24,6 @@ import ( var ( // vmtype refers to qemu (vs libvirt, krun, etc) vmtype = "qemu" - // qemuCommon are the common command line arguments between the arches - //qemuCommon = []string{"-cpu", "host", "-qmp", "unix://tmp/qmp.sock,server,nowait"} - //qemuCommon = []string{"-cpu", "host", "-qmp", "tcp:localhost:4444,server,nowait"} ) // NewMachine initializes an instance of a virtual machine based on the qemu @@ -89,6 +88,16 @@ func NewMachine(opts machine.InitOptions) (machine.VM, error) { // Add network cmd = append(cmd, "-nic", "user,model=virtio,hostfwd=tcp::"+strconv.Itoa(vm.Port)+"-:22") + socketPath, err := getSocketDir() + if 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,nowait,id=" + vm.Name + "_ready", + "-device", "virtserialport,chardev=" + vm.Name + "_ready" + ",name=org.fedoraproject.port.0"}...) vm.CmdLine = cmd return vm, nil } @@ -96,13 +105,15 @@ func NewMachine(opts machine.InitOptions) (machine.VM, error) { // LoadByName reads a json file that describes a known qemu vm // and returns a vm instance func LoadVMByName(name string) (machine.VM, error) { - // TODO need to define an error relating to ErrMachineNotFound vm := new(MachineVM) vmConfigDir, err := machine.GetConfDir(vmtype) if err != nil { return nil, err } b, err := ioutil.ReadFile(filepath.Join(vmConfigDir, name+".json")) + if os.IsNotExist(err) { + return nil, errors.Wrap(machine.ErrNoSuchVM, name) + } if err != nil { return nil, err } @@ -159,14 +170,28 @@ func (v *MachineVM) Init(opts machine.InitOptions) error { if err := v.prepare(); err != nil { return err } + + // Resize the disk image to input disk size + resize := exec.Command("qemu-img", []string{"resize", v.ImagePath, strconv.Itoa(int(opts.DiskSize)) + "G"}...) + if err := resize.Run(); err != nil { + return errors.Errorf("error resizing image: %q", err) + } // Write the ignition file - return machine.NewIgnitionFile(opts.Username, key, v.IgnitionFilePath) + ign := machine.DynamicIgnition{ + Name: opts.Username, + Key: key, + VMName: v.Name, + WritePath: v.IgnitionFilePath, + } + return machine.NewIgnitionFile(ign) } // Start executes the qemu command line and forks it func (v *MachineVM) Start(name string, _ machine.StartOptions) error { var ( - err error + conn net.Conn + err error + wait time.Duration = time.Millisecond * 500 ) attr := new(os.ProcAttr) files := []*os.File{os.Stdin, os.Stdout, os.Stderr} @@ -181,6 +206,30 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error { } _, err = os.StartProcess(v.CmdLine[0], cmd, attr) + if err != nil { + return err + } + fmt.Println("Waiting for VM ...") + socketPath, err := getSocketDir() + if err != nil { + return err + } + + // The socket is not made until the qemu process is running so here + // we do a backoff waiting for it. Once we have a conn, we break and + // then wait to read it. + for i := 0; i < 6; i++ { + conn, err = net.Dial("unix", filepath.Join(socketPath, "podman", v.Name+"_ready.sock")) + if err == nil { + break + } + time.Sleep(wait) + wait++ + } + if err != nil { + return err + } + _, err = bufio.NewReader(conn).ReadString('\n') return err } -- cgit v1.2.3-54-g00ecf