diff options
-rw-r--r-- | cmd/podman/machine/init.go | 43 | ||||
-rw-r--r-- | cmd/podman/machine/rm.go (renamed from cmd/podman/machine/remove.go) | 28 | ||||
-rw-r--r-- | cmd/podman/machine/ssh.go | 12 | ||||
-rw-r--r-- | cmd/podman/machine/start.go | 12 | ||||
-rw-r--r-- | cmd/podman/machine/stop.go | 12 | ||||
-rw-r--r-- | docs/source/machine.rst | 2 | ||||
-rw-r--r-- | docs/source/markdown/podman-machine-init.1.md | 4 | ||||
-rw-r--r-- | docs/source/markdown/podman-machine-rm.1.md (renamed from docs/source/markdown/podman-machine-remove.1.md) | 10 | ||||
-rw-r--r-- | docs/source/markdown/podman-machine-ssh.1.md | 2 | ||||
-rw-r--r-- | docs/source/markdown/podman-machine-start.1.md | 2 | ||||
-rw-r--r-- | docs/source/markdown/podman-machine-stop.1.md | 2 | ||||
-rw-r--r-- | docs/source/markdown/podman-machine.1.md | 8 | ||||
-rw-r--r-- | pkg/machine/config.go | 14 | ||||
-rw-r--r-- | pkg/machine/ignition.go | 51 | ||||
-rw-r--r-- | pkg/machine/qemu/machine.go | 61 |
15 files changed, 180 insertions, 83 deletions
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/rm.go index f6ce9e326..cd2cc84f2 100644 --- a/cmd/podman/machine/remove.go +++ b/cmd/podman/machine/rm.go @@ -17,13 +17,13 @@ import ( ) var ( - removeCmd = &cobra.Command{ - Use: "remove [options] NAME", + rmCmd = &cobra.Command{ + Use: "rm [options] [NAME]", Short: "Remove an existing machine", Long: "Remove an existing machine ", - RunE: remove, - Args: cobra.ExactArgs(1), - Example: `podman machine remove myvm`, + RunE: rm, + Args: cobra.MaximumNArgs(1), + Example: `podman machine rm myvm`, ValidArgsFunction: completion.AutocompleteNone, } ) @@ -35,13 +35,13 @@ var ( func init() { registry.Commands = append(registry.Commands, registry.CliCommand{ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, - Command: removeCmd, + Command: rmCmd, Parent: machineCmd, }) - flags := removeCmd.Flags() + flags := rmCmd.Flags() formatFlagName := "force" - flags.BoolVar(&destoryOptions.Force, formatFlagName, false, "Do not prompt before removeing") + 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") @@ -53,20 +53,24 @@ func init() { flags.BoolVar(&destoryOptions.SaveImage, imageFlagName, false, "Do not delete the image file") } -func remove(cmd *cobra.Command, args []string) error { +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(args[0]) + vm, err = qemu.LoadVMByName(vmName) } if err != nil { return err } - confirmationMessage, doIt, err := vm.Remove(args[0], machine.RemoveOptions{}) + confirmationMessage, remove, err := vm.Remove(vmName, machine.RemoveOptions{}) if err != nil { return err } @@ -84,5 +88,5 @@ func remove(cmd *cobra.Command, args []string) error { return nil } } - return doIt() + 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 <markdown/podman-machine-init.1>` Initialize a new virtual machine -:doc:`remove <markdown/podman-machine-remove.1>` Remove a virtual machine +:doc:`rm <markdown/podman-machine-rm.1>` Remove a virtual machine :doc:`ssh <markdown/podman-machine-ssh.1>` SSH into a virtual machine :doc:`start <markdown/podman-machine-start.1>` Start a virtual machine :doc:`stop <markdown/podman-machine-stop.1>` 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-rm.1.md index 07763741d..4da17fdcb 100644 --- a/docs/source/markdown/podman-machine-remove.1.md +++ b/docs/source/markdown/podman-machine-rm.1.md @@ -1,17 +1,17 @@ -% podman-machine-remove(1) +% podman-machine-rm(1) ## NAME -podman\-machine\-remove - Remove a virtual machine +podman\-machine\-rm - Remove a virtual machine ## SYNOPSIS -**podman machine remove** [*options*] *name* +**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 removeed as is its image file on the filesystem. +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. @@ -45,7 +45,7 @@ deleted. Remove a VM named "test1" ``` -$ podman machine remove test1 +$ podman machine rm test1 The following files will be deleted: 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 } |