diff options
author | baude <bbaude@redhat.com> | 2021-03-25 10:35:43 -0500 |
---|---|---|
committer | baude <bbaude@redhat.com> | 2021-03-27 10:08:11 -0500 |
commit | 7a79f708a4521ba7c42da83a204a01ace010ace3 (patch) | |
tree | 42e4128908fa127d158ad0673b033740d416d1f4 /pkg/machine | |
parent | ec47312eebf11abcf74b5bf06df19ee2fb7b8afd (diff) | |
download | podman-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.go | 14 | ||||
-rw-r--r-- | pkg/machine/ignition.go | 51 | ||||
-rw-r--r-- | pkg/machine/qemu/machine.go | 61 |
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 } |