summaryrefslogtreecommitdiff
path: root/pkg/machine
diff options
context:
space:
mode:
authorbaude <bbaude@redhat.com>2021-03-25 10:35:43 -0500
committerbaude <bbaude@redhat.com>2021-03-27 10:08:11 -0500
commit7a79f708a4521ba7c42da83a204a01ace010ace3 (patch)
tree42e4128908fa127d158ad0673b033740d416d1f4 /pkg/machine
parentec47312eebf11abcf74b5bf06df19ee2fb7b8afd (diff)
downloadpodman-7a79f708a4521ba7c42da83a204a01ace010ace3.tar.gz
podman-7a79f708a4521ba7c42da83a204a01ace010ace3.tar.bz2
podman-7a79f708a4521ba7c42da83a204a01ace010ace3.zip
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 <bbaude@redhat.com>
Diffstat (limited to 'pkg/machine')
-rw-r--r--pkg/machine/config.go14
-rw-r--r--pkg/machine/ignition.go51
-rw-r--r--pkg/machine/qemu/machine.go61
3 files changed, 103 insertions, 23 deletions
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
}