Merge pull request #9836 from baude/vmcreateresize

Podman machine enhancements
This commit is contained in:
OpenShift Merge Robot
2021-03-28 10:55:45 +00:00
committed by GitHub
15 changed files with 180 additions and 83 deletions

View File

@ -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)
}

View File

@ -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()
}

View File

@ -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)
}

View File

@ -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{})
}

View File

@ -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{})
}

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -4,7 +4,7 @@
podman\-machine\-start - Start a virtual machine
## SYNOPSIS
**podman machine start** *name*
**podman machine start** [*name*]
## DESCRIPTION

View File

@ -4,7 +4,7 @@
podman\-machine\-stop - Stop a virtual machine
## SYNOPSIS
**podman machine stop** *name*
**podman machine stop** [*name*]
## DESCRIPTION

View File

@ -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)

View File

@ -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 {

View File

@ -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 {

View File

@ -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
}