mirror of https://github.com/containers/podman.git
Merge pull request #13674 from baude/refactor1
Machine refactor - part 1
This commit is contained in:
commit
9cfa9695a0
|
@ -31,6 +31,10 @@ var (
|
||||||
now bool
|
now bool
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// maxMachineNameSize is set to thirty to limit huge machine names primarily
|
||||||
|
// because macos has a much smaller file size limit.
|
||||||
|
const maxMachineNameSize = 30
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
registry.Commands = append(registry.Commands, registry.CliCommand{
|
registry.Commands = append(registry.Commands, registry.CliCommand{
|
||||||
Command: initCmd,
|
Command: initCmd,
|
||||||
|
@ -111,10 +115,12 @@ func initMachine(cmd *cobra.Command, args []string) error {
|
||||||
vm machine.VM
|
vm machine.VM
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
provider := getSystemDefaultProvider()
|
provider := getSystemDefaultProvider()
|
||||||
initOpts.Name = defaultMachineName
|
initOpts.Name = defaultMachineName
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
|
if len(args[0]) > maxMachineNameSize {
|
||||||
|
return errors.New("machine name must be 30 characters or less")
|
||||||
|
}
|
||||||
initOpts.Name = args[0]
|
initOpts.Name = args[0]
|
||||||
}
|
}
|
||||||
if _, err := provider.LoadVMByName(initOpts.Name); err == nil {
|
if _, err := provider.LoadVMByName(initOpts.Name); err == nil {
|
||||||
|
|
|
@ -29,7 +29,7 @@ type InitOptions struct {
|
||||||
Username string
|
Username string
|
||||||
ReExec bool
|
ReExec bool
|
||||||
Rootful bool
|
Rootful bool
|
||||||
// The numberical userid of the user that called machine
|
// The numerical userid of the user that called machine
|
||||||
UID string
|
UID string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,6 +128,7 @@ type DistributionDownload interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rc RemoteConnectionType) MakeSSHURL(host, path, port, userName string) url.URL {
|
func (rc RemoteConnectionType) MakeSSHURL(host, path, port, userName string) url.URL {
|
||||||
|
//TODO Should this function have input verification?
|
||||||
userInfo := url.User(userName)
|
userInfo := url.User(userName)
|
||||||
uri := url.URL{
|
uri := url.URL{
|
||||||
Scheme: "ssh",
|
Scheme: "ssh",
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
package machine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRemoteConnectionType_MakeSSHURL(t *testing.T) {
|
||||||
|
var (
|
||||||
|
host = "foobar"
|
||||||
|
path = "/path/to/socket"
|
||||||
|
rc = "ssh"
|
||||||
|
username = "core"
|
||||||
|
)
|
||||||
|
type args struct {
|
||||||
|
host string
|
||||||
|
path string
|
||||||
|
port string
|
||||||
|
userName string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
rc RemoteConnectionType
|
||||||
|
args args
|
||||||
|
want url.URL
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Good no port",
|
||||||
|
rc: "ssh",
|
||||||
|
args: args{
|
||||||
|
host: host,
|
||||||
|
path: path,
|
||||||
|
port: "",
|
||||||
|
userName: username,
|
||||||
|
},
|
||||||
|
want: url.URL{
|
||||||
|
Scheme: rc,
|
||||||
|
User: url.User(username),
|
||||||
|
Host: host,
|
||||||
|
Path: path,
|
||||||
|
ForceQuery: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Good with port",
|
||||||
|
rc: "ssh",
|
||||||
|
args: args{
|
||||||
|
host: host,
|
||||||
|
path: path,
|
||||||
|
port: "222",
|
||||||
|
userName: username,
|
||||||
|
},
|
||||||
|
want: url.URL{
|
||||||
|
Scheme: rc,
|
||||||
|
User: url.User(username),
|
||||||
|
Host: net.JoinHostPort(host, "222"),
|
||||||
|
Path: path,
|
||||||
|
ForceQuery: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := tt.rc.MakeSSHURL(tt.args.host, tt.args.path, tt.args.port, tt.args.userName); !reflect.DeepEqual(got, tt.want) { //nolint: scopelint
|
||||||
|
t.Errorf("MakeSSHURL() = %v, want %v", got, tt.want) //nolint: scopelint
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,12 +4,28 @@
|
||||||
package qemu
|
package qemu
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// FCOS streams
|
||||||
|
// Testing FCOS stream
|
||||||
|
Testing string = "testing"
|
||||||
|
// Next FCOS stream
|
||||||
|
Next string = "next"
|
||||||
|
// Stable FCOS stream
|
||||||
|
Stable string = "stable"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Provider struct{}
|
type Provider struct{}
|
||||||
|
|
||||||
type MachineVM struct {
|
// Deprecated: MachineVMV1 is being deprecated in favor a more flexible and informative
|
||||||
|
// structure
|
||||||
|
type MachineVMV1 struct {
|
||||||
// CPUs to be assigned to the VM
|
// CPUs to be assigned to the VM
|
||||||
CPUs uint64
|
CPUs uint64
|
||||||
// The command line representation of the qemu command
|
// The command line representation of the qemu command
|
||||||
|
@ -42,6 +58,74 @@ type MachineVM struct {
|
||||||
UID int
|
UID int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MachineVM struct {
|
||||||
|
// The command line representation of the qemu command
|
||||||
|
CmdLine []string
|
||||||
|
// HostUser contains info about host user
|
||||||
|
HostUser
|
||||||
|
// ImageConfig describes the bootable image
|
||||||
|
ImageConfig
|
||||||
|
// Mounts is the list of remote filesystems to mount
|
||||||
|
Mounts []Mount
|
||||||
|
// Name of VM
|
||||||
|
Name string
|
||||||
|
// PidFilePath is the where the PID file lives
|
||||||
|
PidFilePath MachineFile
|
||||||
|
// QMPMonitor is the qemu monitor object for sending commands
|
||||||
|
QMPMonitor Monitor
|
||||||
|
// ReadySocket tells host when vm is booted
|
||||||
|
ReadySocket MachineFile
|
||||||
|
// ResourceConfig is physical attrs of the VM
|
||||||
|
ResourceConfig
|
||||||
|
// SSHConfig for accessing the remote vm
|
||||||
|
SSHConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImageConfig describes the bootable image for the VM
|
||||||
|
type ImageConfig struct {
|
||||||
|
IgnitionFilePath string
|
||||||
|
// ImageStream is the update stream for the image
|
||||||
|
ImageStream string
|
||||||
|
// ImagePath is the fq path to
|
||||||
|
ImagePath string
|
||||||
|
}
|
||||||
|
|
||||||
|
// HostUser describes the host user
|
||||||
|
type HostUser struct {
|
||||||
|
// Whether this machine should run in a rootful or rootless manner
|
||||||
|
Rootful bool
|
||||||
|
// UID is the numerical id of the user that called machine
|
||||||
|
UID int
|
||||||
|
}
|
||||||
|
|
||||||
|
// SSHConfig contains remote access information for SSH
|
||||||
|
type SSHConfig struct {
|
||||||
|
// IdentityPath is the fq path to the ssh priv key
|
||||||
|
IdentityPath string
|
||||||
|
// SSH port for user networking
|
||||||
|
Port int
|
||||||
|
// RemoteUsername of the vm user
|
||||||
|
RemoteUsername string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResourceConfig describes physical attributes of the machine
|
||||||
|
type ResourceConfig struct {
|
||||||
|
// CPUs to be assigned to the VM
|
||||||
|
CPUs uint64
|
||||||
|
// Memory in megabytes assigned to the vm
|
||||||
|
Memory uint64
|
||||||
|
// Disk size in gigabytes assigned to the vm
|
||||||
|
DiskSize uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
type MachineFile struct {
|
||||||
|
// Path is the fully qualified path to a file
|
||||||
|
Path string
|
||||||
|
// Symlink is a shortened version of Path by using
|
||||||
|
// a symlink
|
||||||
|
Symlink *string
|
||||||
|
}
|
||||||
|
|
||||||
type Mount struct {
|
type Mount struct {
|
||||||
Type string
|
Type string
|
||||||
Tag string
|
Tag string
|
||||||
|
@ -52,7 +136,7 @@ type Mount struct {
|
||||||
|
|
||||||
type Monitor struct {
|
type Monitor struct {
|
||||||
// Address portion of the qmp monitor (/tmp/tmp.sock)
|
// Address portion of the qmp monitor (/tmp/tmp.sock)
|
||||||
Address string
|
Address MachineFile
|
||||||
// Network portion of the qmp monitor (unix)
|
// Network portion of the qmp monitor (unix)
|
||||||
Network string
|
Network string
|
||||||
// Timeout in seconds for qmp monitor transactions
|
// Timeout in seconds for qmp monitor transactions
|
||||||
|
@ -64,3 +148,37 @@ var (
|
||||||
// qmp monitor interactions.
|
// qmp monitor interactions.
|
||||||
defaultQMPTimeout time.Duration = 2 * time.Second
|
defaultQMPTimeout time.Duration = 2 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// GetPath returns the working path for a machinefile. it returns
|
||||||
|
// the symlink unless one does not exist
|
||||||
|
func (m *MachineFile) GetPath() string {
|
||||||
|
if m.Symlink == nil {
|
||||||
|
return m.Path
|
||||||
|
}
|
||||||
|
return *m.Symlink
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete removes the machinefile symlink (if it exists) and
|
||||||
|
// the actual path
|
||||||
|
func (m *MachineFile) Delete() error {
|
||||||
|
if m.Symlink != nil {
|
||||||
|
if err := os.Remove(*m.Symlink); err != nil {
|
||||||
|
logrus.Errorf("unable to remove symlink %q", *m.Symlink)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return os.Remove(m.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMachineFile is a constructor for MachineFile
|
||||||
|
func NewMachineFile(path string, symlink *string) (*MachineFile, error) {
|
||||||
|
if len(path) < 1 {
|
||||||
|
return nil, errors.New("invalid machine file path")
|
||||||
|
}
|
||||||
|
if symlink != nil && len(*symlink) < 1 {
|
||||||
|
return nil, errors.New("invalid symlink path")
|
||||||
|
}
|
||||||
|
return &MachineFile{
|
||||||
|
Path: path,
|
||||||
|
Symlink: symlink,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,103 @@
|
||||||
|
package qemu
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMachineFile_GetPath(t *testing.T) {
|
||||||
|
path := "/var/tmp/podman/my.sock"
|
||||||
|
sym := "/tmp/podman/my.sock"
|
||||||
|
type fields struct {
|
||||||
|
Path string
|
||||||
|
Symlink *string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Original path",
|
||||||
|
fields: fields{path, nil},
|
||||||
|
want: path,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Symlink over path",
|
||||||
|
fields: fields{
|
||||||
|
Path: path,
|
||||||
|
Symlink: &sym,
|
||||||
|
},
|
||||||
|
want: sym,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
m := &MachineFile{
|
||||||
|
Path: tt.fields.Path, //nolint: scopelint
|
||||||
|
Symlink: tt.fields.Symlink, //nolint: scopelint
|
||||||
|
}
|
||||||
|
if got := m.GetPath(); got != tt.want { //nolint: scopelint
|
||||||
|
t.Errorf("GetPath() = %v, want %v", got, tt.want) //nolint: scopelint
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewMachineFile(t *testing.T) {
|
||||||
|
p := "/var/tmp/podman/my.sock"
|
||||||
|
sym := "/tmp/podman/my.sock"
|
||||||
|
empty := ""
|
||||||
|
|
||||||
|
m := MachineFile{
|
||||||
|
Path: p,
|
||||||
|
Symlink: nil,
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
path string
|
||||||
|
symlink *string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want *MachineFile
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Good",
|
||||||
|
args: args{path: p},
|
||||||
|
want: &m,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Good with Symlink",
|
||||||
|
args: args{p, &sym},
|
||||||
|
want: &MachineFile{p, &sym},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Bad path name",
|
||||||
|
args: args{empty, nil},
|
||||||
|
want: nil,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Bad symlink name",
|
||||||
|
args: args{p, &empty},
|
||||||
|
want: nil,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := NewMachineFile(tt.args.path, tt.args.symlink) //nolint: scopelint
|
||||||
|
if (err != nil) != tt.wantErr { //nolint: scopelint
|
||||||
|
t.Errorf("NewMachineFile() error = %v, wantErr %v", err, tt.wantErr) //nolint: scopelint
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.want) { //nolint: scopelint
|
||||||
|
t.Errorf("NewMachineFile() got = %v, want %v", got, tt.want) //nolint: scopelint
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -111,7 +111,7 @@ func (p *Provider) NewMachine(opts machine.InitOptions) (machine.VM, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
vm.QMPMonitor = monitor
|
vm.QMPMonitor = monitor
|
||||||
cmd = append(cmd, []string{"-qmp", monitor.Network + ":/" + monitor.Address + ",server=on,wait=off"}...)
|
cmd = append(cmd, []string{"-qmp", monitor.Network + ":/" + monitor.Address.GetPath() + ",server=on,wait=off"}...)
|
||||||
|
|
||||||
// Add network
|
// Add network
|
||||||
// Right now the mac address is hardcoded so that the host networking gives it a specific IP address. This is
|
// Right now the mac address is hardcoded so that the host networking gives it a specific IP address. This is
|
||||||
|
@ -134,7 +134,8 @@ func (p *Provider) NewMachine(opts machine.InitOptions) (machine.VM, error) {
|
||||||
// LoadByName reads a json file that describes a known qemu vm
|
// LoadByName reads a json file that describes a known qemu vm
|
||||||
// and returns a vm instance
|
// and returns a vm instance
|
||||||
func (p *Provider) LoadVMByName(name string) (machine.VM, error) {
|
func (p *Provider) LoadVMByName(name string) (machine.VM, error) {
|
||||||
vm := &MachineVM{UID: -1} // posix reserves -1, so use it to signify undefined
|
vm := &MachineVM{Name: name}
|
||||||
|
vm.HostUser = HostUser{UID: -1} // posix reserves -1, so use it to signify undefined
|
||||||
vmConfigDir, err := machine.GetConfDir(vmtype)
|
vmConfigDir, err := machine.GetConfDir(vmtype)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -176,7 +177,7 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
|
||||||
v.Rootful = opts.Rootful
|
v.Rootful = opts.Rootful
|
||||||
|
|
||||||
switch opts.ImagePath {
|
switch opts.ImagePath {
|
||||||
case "testing", "next", "stable", "":
|
case Testing, Next, Stable, "":
|
||||||
// Get image as usual
|
// Get image as usual
|
||||||
v.ImageStream = opts.ImagePath
|
v.ImageStream = opts.ImagePath
|
||||||
dd, err := machine.NewFcosDownloader(vmtype, v.Name, opts.ImagePath)
|
dd, err := machine.NewFcosDownloader(vmtype, v.Name, opts.ImagePath)
|
||||||
|
@ -576,12 +577,12 @@ func (v *MachineVM) checkStatus(monitor *qmp.SocketMonitor) (machine.QemuMachine
|
||||||
func (v *MachineVM) Stop(name string, _ machine.StopOptions) error {
|
func (v *MachineVM) Stop(name string, _ machine.StopOptions) error {
|
||||||
var disconnected bool
|
var disconnected bool
|
||||||
// check if the qmp socket is there. if not, qemu instance is gone
|
// check if the qmp socket is there. if not, qemu instance is gone
|
||||||
if _, err := os.Stat(v.QMPMonitor.Address); os.IsNotExist(err) {
|
if _, err := os.Stat(v.QMPMonitor.Address.GetPath()); os.IsNotExist(err) {
|
||||||
// Right now it is NOT an error to stop a stopped machine
|
// Right now it is NOT an error to stop a stopped machine
|
||||||
logrus.Debugf("QMP monitor socket %v does not exist", v.QMPMonitor.Address)
|
logrus.Debugf("QMP monitor socket %v does not exist", v.QMPMonitor.Address)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
qmpMonitor, err := qmp.NewSocketMonitor(v.QMPMonitor.Network, v.QMPMonitor.Address, v.QMPMonitor.Timeout)
|
qmpMonitor, err := qmp.NewSocketMonitor(v.QMPMonitor.Network, v.QMPMonitor.Address.GetPath(), v.QMPMonitor.Timeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -684,20 +685,25 @@ func NewQMPMonitor(network, name string, timeout time.Duration) (Monitor, error)
|
||||||
if timeout == 0 {
|
if timeout == 0 {
|
||||||
timeout = defaultQMPTimeout
|
timeout = defaultQMPTimeout
|
||||||
}
|
}
|
||||||
|
address, err := NewMachineFile(filepath.Join(rtDir, "qmp+"+name+".sock"), nil)
|
||||||
|
if err != nil {
|
||||||
|
return Monitor{}, err
|
||||||
|
}
|
||||||
monitor := Monitor{
|
monitor := Monitor{
|
||||||
Network: network,
|
Network: network,
|
||||||
Address: filepath.Join(rtDir, "qmp_"+name+".sock"),
|
Address: *address,
|
||||||
Timeout: timeout,
|
Timeout: timeout,
|
||||||
}
|
}
|
||||||
return monitor, nil
|
return monitor, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove deletes all the files associated with a machine including ssh keys, the image itself
|
||||||
func (v *MachineVM) Remove(name string, opts machine.RemoveOptions) (string, func() error, error) {
|
func (v *MachineVM) Remove(name string, opts machine.RemoveOptions) (string, func() error, error) {
|
||||||
var (
|
var (
|
||||||
files []string
|
files []string
|
||||||
)
|
)
|
||||||
|
|
||||||
// cannot remove a running vm
|
// cannot remove a running vm unless --force is used
|
||||||
running, err := v.isRunning()
|
running, err := v.isRunning()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return "", nil, err
|
||||||
|
@ -768,11 +774,11 @@ func (v *MachineVM) Remove(name string, opts machine.RemoveOptions) (string, fun
|
||||||
|
|
||||||
func (v *MachineVM) isRunning() (bool, error) {
|
func (v *MachineVM) isRunning() (bool, error) {
|
||||||
// Check if qmp socket path exists
|
// Check if qmp socket path exists
|
||||||
if _, err := os.Stat(v.QMPMonitor.Address); os.IsNotExist(err) {
|
if _, err := os.Stat(v.QMPMonitor.Address.GetPath()); os.IsNotExist(err) {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
// Check if we can dial it
|
// Check if we can dial it
|
||||||
monitor, err := qmp.NewSocketMonitor(v.QMPMonitor.Network, v.QMPMonitor.Address, v.QMPMonitor.Timeout)
|
monitor, err := qmp.NewSocketMonitor(v.QMPMonitor.Network, v.QMPMonitor.Address.GetPath(), v.QMPMonitor.Timeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// FIXME: this error should probably be returned
|
// FIXME: this error should probably be returned
|
||||||
return false, nil // nolint: nilerr
|
return false, nil // nolint: nilerr
|
||||||
|
|
Loading…
Reference in New Issue