podman/pkg/machine/hyperv/machine.go

994 lines
25 KiB
Go

//go:build windows
// +build windows
package hyperv
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/fs"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/Microsoft/go-winio"
"github.com/containers/common/pkg/config"
gvproxy "github.com/containers/gvisor-tap-vsock/pkg/types"
"github.com/containers/libhvee/pkg/hypervctl"
"github.com/containers/podman/v4/pkg/machine"
"github.com/containers/podman/v4/pkg/machine/define"
"github.com/containers/podman/v4/pkg/strongunits"
"github.com/containers/podman/v4/pkg/util"
"github.com/containers/podman/v4/utils"
"github.com/containers/storage/pkg/lockfile"
"github.com/sirupsen/logrus"
)
var (
// vmtype refers to qemu (vs libvirt, krun, etc).
vmtype = machine.HyperVVirt
)
const (
// Some of this will need to change when we are closer to having
// working code.
VolumeTypeVirtfs = "virtfs"
MountType9p = "9p"
dockerSockPath = "/var/run/docker.sock"
dockerConnectTimeout = 5 * time.Second
apiUpTimeout = 20 * time.Second
)
// hyperVReadyUnit 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 begin interacting with it
//
// VSOCK-CONNECT:2 <- shortcut to connect to the hostvm
const hyperVReadyUnit = `[Unit]
After=remove-moby.service sshd.socket sshd.service
After=systemd-user-sessions.service
OnFailure=emergency.target
OnFailureJobMode=isolate
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/bin/sh -c '/usr/bin/echo Ready | socat - VSOCK-CONNECT:2:%d'
[Install]
RequiredBy=default.target
`
// hyperVVsockNetUnit is a systemd unit file that calls the vm helper utility
// needed to take traffic from a network vsock0 device to the actual vsock
// and onto the host
const hyperVVsockNetUnit = `
[Unit]
Description=vsock_network
After=NetworkManager.service
[Service]
ExecStart=/usr/libexec/podman/gvforwarder -preexisting -iface vsock0 -url vsock://2:%d/connect
ExecStartPost=/usr/bin/nmcli c up vsock0
[Install]
WantedBy=multi-user.target
`
const hyperVVsockNMConnection = `
[connection]
id=vsock0
type=tun
interface-name=vsock0
[tun]
mode=2
[802-3-ethernet]
cloned-mac-address=5A:94:EF:E4:0C:EE
[ipv4]
method=auto
[proxy]
`
type HyperVMachine struct {
// ConfigPath is the fully qualified path to the configuration file
ConfigPath define.VMFile
// HostUser contains info about host user
machine.HostUser
// ImageConfig describes the bootable image
machine.ImageConfig
// Mounts is the list of remote filesystems to mount
Mounts []machine.Mount
// Name of VM
Name string
// NetworkVSock is for the user networking
NetworkHVSock HVSockRegistryEntry
// ReadySocket tells host when vm is booted
ReadyHVSock HVSockRegistryEntry
// ResourceConfig is physical attrs of the VM
machine.ResourceConfig
// SSHConfig for accessing the remote vm
machine.SSHConfig
// Starting tells us whether the machine is running or if we have just dialed it to start it
Starting bool
// Created contains the original created time instead of querying the file mod time
Created time.Time
// LastUp contains the last recorded uptime
LastUp time.Time
// GVProxy will write its PID here
GvProxyPid define.VMFile
// MountVsocks contains the currently-active vsocks, mapped to the
// directory they should be mounted on.
MountVsocks map[string]uint64
// Used at runtime for serializing write operations
lock *lockfile.LockFile
}
// addNetworkAndReadySocketsToRegistry adds the Network and Ready sockets to the
// Windows registry
func (m *HyperVMachine) addNetworkAndReadySocketsToRegistry() error {
networkHVSock, err := NewHVSockRegistryEntry(m.Name, Network)
if err != nil {
return err
}
eventHVSocket, err := NewHVSockRegistryEntry(m.Name, Events)
if err != nil {
return err
}
m.NetworkHVSock = *networkHVSock
m.ReadyHVSock = *eventHVSocket
return nil
}
// readAndSplitIgnition reads the ignition file and splits it into key:value pairs
func (m *HyperVMachine) readAndSplitIgnition() error {
ignFile, err := m.IgnitionFile.Read()
if err != nil {
return err
}
reader := bytes.NewReader(ignFile)
vm, err := hypervctl.NewVirtualMachineManager().GetMachine(m.Name)
if err != nil {
return err
}
return vm.SplitAndAddIgnition("ignition.config.", reader)
}
func (m *HyperVMachine) Init(opts machine.InitOptions) (bool, error) {
var (
key string
err error
)
// cleanup half-baked files if init fails at any point
callbackFuncs := machine.InitCleanup()
defer callbackFuncs.CleanIfErr(&err)
go callbackFuncs.CleanOnSignal()
callbackFuncs.Add(m.ImagePath.Delete)
callbackFuncs.Add(m.ConfigPath.Delete)
callbackFuncs.Add(m.unregisterMachine)
// Parsing here is confusing.
// Basically, we have two paths: a source path, on the Windows machine,
// with all that entails (drive letter, backslash separator, etc) and a
// dest path, in the Linux machine, normal Unix semantics. They are
// separated by a : character, with source path first, dest path second.
// So we split on :, first two parts are guaranteed to be Windows (the
// drive letter and file path), next one is Linux. Options, when we get
// around to those, would be another : after that.
// TODO: Need to support options here
for _, mount := range opts.Volumes {
newMount := machine.Mount{}
splitMount := strings.Split(mount, ":")
if len(splitMount) < 3 {
return false, fmt.Errorf("volumes must be specified as source:destination and must be absolute")
}
newMount.Target = splitMount[2]
newMount.Source = strings.Join(splitMount[:2], ":")
if len(splitMount) > 3 {
return false, fmt.Errorf("volume options are not presently supported on Hyper-V")
}
m.Mounts = append(m.Mounts, newMount)
}
if err = m.addNetworkAndReadySocketsToRegistry(); err != nil {
return false, err
}
callbackFuncs.Add(func() error {
m.removeNetworkAndReadySocketsFromRegistry()
return nil
})
m.IdentityPath = util.GetIdentityPath(m.Name)
if m.UID == 0 {
m.UID = 1000
}
sshPort, err := utils.GetRandomPort()
if err != nil {
return false, err
}
m.Port = sshPort
m.RemoteUsername = opts.Username
err = machine.AddSSHConnectionsToPodmanSocket(
m.UID,
m.Port,
m.IdentityPath,
m.Name,
m.RemoteUsername,
opts,
)
if err != nil {
return false, err
}
callbackFuncs.Add(m.removeSystemConnections)
if len(opts.IgnitionPath) < 1 {
key, err = machine.CreateSSHKeys(m.IdentityPath)
if err != nil {
return false, err
}
callbackFuncs.Add(m.removeSSHKeys)
}
m.ResourceConfig = machine.ResourceConfig{
CPUs: opts.CPUS,
DiskSize: opts.DiskSize,
Memory: opts.Memory,
}
m.Rootful = opts.Rootful
builder := machine.NewIgnitionBuilder(machine.DynamicIgnition{
Name: m.RemoteUsername,
Key: key,
VMName: m.Name,
VMType: machine.HyperVVirt,
TimeZone: opts.TimeZone,
WritePath: m.IgnitionFile.GetPath(),
UID: m.UID,
Rootful: m.Rootful,
})
// If the user provides an ignition file, we need to
// copy it into the conf dir
if len(opts.IgnitionPath) > 0 {
return false, builder.BuildWithIgnitionFile(opts.IgnitionPath)
}
callbackFuncs.Add(m.IgnitionFile.Delete)
if err := m.writeConfig(); err != nil {
return false, err
}
if err := builder.GenerateIgnitionConfig(); err != nil {
return false, err
}
builder.WithUnit(machine.Unit{
Enabled: machine.BoolToPtr(true),
Name: "ready.service",
Contents: machine.StrToPtr(fmt.Sprintf(hyperVReadyUnit, m.ReadyHVSock.Port)),
})
builder.WithUnit(machine.Unit{
Contents: machine.StrToPtr(fmt.Sprintf(hyperVVsockNetUnit, m.NetworkHVSock.Port)),
Enabled: machine.BoolToPtr(true),
Name: "vsock-network.service",
})
builder.WithFile(machine.File{
Node: machine.Node{
Path: "/etc/NetworkManager/system-connections/vsock0.nmconnection",
},
FileEmbedded1: machine.FileEmbedded1{
Append: nil,
Contents: machine.Resource{
Source: machine.EncodeDataURLPtr(hyperVVsockNMConnection),
},
Mode: machine.IntToPtr(0600),
},
})
if err := builder.Build(); err != nil {
return false, err
}
if err = m.resizeDisk(strongunits.GiB(opts.DiskSize)); err != nil {
return false, err
}
// The ignition file has been written. We now need to
// read it so that we can put it into key-value pairs
err = m.readAndSplitIgnition()
return err == nil, err
}
func (m *HyperVMachine) unregisterMachine() error {
vmm := hypervctl.NewVirtualMachineManager()
vm, err := vmm.GetMachine(m.Name)
if err != nil {
logrus.Error(err)
}
return vm.Remove("")
}
func (m *HyperVMachine) removeSSHKeys() error {
if err := os.Remove(fmt.Sprintf("%s.pub", m.IdentityPath)); err != nil {
logrus.Error(err)
}
return os.Remove(m.IdentityPath)
}
func (m *HyperVMachine) removeSystemConnections() error {
return machine.RemoveConnections(m.Name, fmt.Sprintf("%s-root", m.Name))
}
func (m *HyperVMachine) Inspect() (*machine.InspectInfo, error) {
vm, err := hypervctl.NewVirtualMachineManager().GetMachine(m.Name)
if err != nil {
return nil, err
}
cfg, err := vm.GetConfig(m.ImagePath.GetPath())
if err != nil {
return nil, err
}
vmState, err := stateConversion(vm.State())
if err != nil {
return nil, err
}
podmanSocket, err := m.forwardSocketPath()
if err != nil {
return nil, err
}
return &machine.InspectInfo{
ConfigPath: m.ConfigPath,
ConnectionInfo: machine.ConnectionConfig{
PodmanSocket: podmanSocket,
},
Created: m.Created,
Image: machine.ImageConfig{
IgnitionFile: m.IgnitionFile,
ImageStream: "",
ImagePath: m.ImagePath,
},
LastUp: m.LastUp,
Name: m.Name,
Resources: machine.ResourceConfig{
CPUs: uint64(cfg.Hardware.CPUs),
DiskSize: 0,
Memory: cfg.Hardware.Memory,
},
SSHConfig: m.SSHConfig,
State: string(vmState),
Rootful: m.Rootful,
}, nil
}
// collectFilesToDestroy retrieves the files that will be destroyed by `Remove`
func (m *HyperVMachine) collectFilesToDestroy(opts machine.RemoveOptions, diskPath *string) []string {
files := []string{}
if !opts.SaveKeys {
files = append(files, m.IdentityPath, m.IdentityPath+".pub")
}
if !opts.SaveIgnition {
files = append(files, m.IgnitionFile.GetPath())
}
if !opts.SaveImage {
*diskPath = m.ImagePath.GetPath()
files = append(files, *diskPath)
}
files = append(files, m.ConfigPath.GetPath())
return files
}
// removeNetworkAndReadySocketsFromRegistry removes the Network and Ready sockets
// from the Windows Registry
func (m *HyperVMachine) removeNetworkAndReadySocketsFromRegistry() {
// Remove the HVSOCK for networking
if err := m.NetworkHVSock.Remove(); err != nil {
logrus.Errorf("unable to remove registry entry for %s: %q", m.NetworkHVSock.KeyName, err)
}
// Remove the HVSOCK for events
if err := m.ReadyHVSock.Remove(); err != nil {
logrus.Errorf("unable to remove registry entry for %s: %q", m.ReadyHVSock.KeyName, err)
}
}
func (m *HyperVMachine) Remove(_ string, opts machine.RemoveOptions) (string, func() error, error) {
var (
files []string
diskPath string
)
m.lock.Lock()
defer m.lock.Unlock()
vmm := hypervctl.NewVirtualMachineManager()
vm, err := vmm.GetMachine(m.Name)
if err != nil {
return "", nil, fmt.Errorf("getting virtual machine: %w", err)
}
// In hyperv, they call running 'enabled'
if vm.State() == hypervctl.Enabled {
if !opts.Force {
return "", nil, &machine.ErrVMRunningCannotDestroyed{Name: m.Name}
}
// force stop bc we are destroying
if err := vm.StopWithForce(); err != nil {
return "", nil, fmt.Errorf("stopping virtual machine: %w", err)
}
// Update state on the VM by pulling its info again
vm, err = vmm.GetMachine(m.Name)
if err != nil {
return "", nil, fmt.Errorf("getting VM: %w", err)
}
}
// Tear down vsocks
if err := m.removeShares(); err != nil {
logrus.Errorf("Error removing vsock: %w", err)
}
// Collect all the files that need to be destroyed
files = m.collectFilesToDestroy(opts, &diskPath)
confirmationMessage := "\nThe following files will be deleted:\n\n"
for _, msg := range files {
confirmationMessage += msg + "\n"
}
confirmationMessage += "\n"
return confirmationMessage, func() error {
machine.RemoveFilesAndConnections(files, m.Name, m.Name+"-root")
m.removeNetworkAndReadySocketsFromRegistry()
if err := vm.Remove(""); err != nil {
return fmt.Errorf("removing virtual machine: %w", err)
}
return nil
}, nil
}
func (m *HyperVMachine) Set(name string, opts machine.SetOptions) ([]error, error) {
var (
cpuChanged, memoryChanged bool
setErrors []error
)
m.lock.Lock()
defer m.lock.Unlock()
vmm := hypervctl.NewVirtualMachineManager()
// Considering this a hard return if we cannot lookup the machine
vm, err := vmm.GetMachine(m.Name)
if err != nil {
return setErrors, fmt.Errorf("getting machine: %w", err)
}
if vm.State() != hypervctl.Disabled {
return nil, errors.New("unable to change settings unless vm is stopped")
}
if opts.Rootful != nil && m.Rootful != *opts.Rootful {
if err := m.setRootful(*opts.Rootful); err != nil {
setErrors = append(setErrors, fmt.Errorf("failed to set rootful option: %w", err))
} else {
m.Rootful = *opts.Rootful
}
}
if opts.DiskSize != nil && m.DiskSize != *opts.DiskSize {
newDiskSize := strongunits.GiB(*opts.DiskSize)
if err := m.resizeDisk(newDiskSize); err != nil {
setErrors = append(setErrors, err)
}
}
if opts.CPUs != nil && m.CPUs != *opts.CPUs {
m.CPUs = *opts.CPUs
cpuChanged = true
}
if opts.Memory != nil && m.Memory != *opts.Memory {
m.Memory = *opts.Memory
memoryChanged = true
}
if opts.USBs != nil {
setErrors = append(setErrors, errors.New("changing USBs not supported for hyperv machines"))
}
if cpuChanged || memoryChanged {
err := vm.UpdateProcessorMemSettings(func(ps *hypervctl.ProcessorSettings) {
if cpuChanged {
ps.VirtualQuantity = m.CPUs
}
}, func(ms *hypervctl.MemorySettings) {
if memoryChanged {
ms.DynamicMemoryEnabled = false
ms.VirtualQuantity = m.Memory
ms.Limit = m.Memory
ms.Reservation = m.Memory
}
})
if err != nil {
setErrors = append(setErrors, fmt.Errorf("setting CPU and Memory for VM: %w", err))
}
}
if len(setErrors) > 0 {
return setErrors, setErrors[0]
}
// Write the new JSON out
// considering this a hard return if we cannot write the JSON file.
return setErrors, m.writeConfig()
}
func (m *HyperVMachine) SSH(name string, opts machine.SSHOptions) error {
state, err := m.State(false)
if err != nil {
return err
}
if state != machine.Running {
return fmt.Errorf("vm %q is not running", m.Name)
}
username := opts.Username
if username == "" {
username = m.RemoteUsername
}
return machine.CommonSSH(username, m.IdentityPath, m.Name, m.Port, opts.Args)
}
func (m *HyperVMachine) Start(name string, opts machine.StartOptions) error {
m.lock.Lock()
defer m.lock.Unlock()
// Start 9p shares
shares, err := m.createShares()
if err != nil {
return err
}
m.MountVsocks = shares
if err := m.writeConfig(); err != nil {
return err
}
vmm := hypervctl.NewVirtualMachineManager()
vm, err := vmm.GetMachine(m.Name)
if err != nil {
return err
}
if vm.State() != hypervctl.Disabled {
return hypervctl.ErrMachineStateInvalid
}
_, _, err = m.startHostNetworking()
if err != nil {
return fmt.Errorf("unable to start host networking: %q", err)
}
// The "starting" status from hyper v is a very small windows and not really
// the same as what we want. so modeling starting behaviour after qemu
m.Starting = true
if err := m.writeConfig(); err != nil {
return fmt.Errorf("writing JSON file: %w", err)
}
if err := vm.Start(); err != nil {
return err
}
// Wait on notification from the guest
if err := m.ReadyHVSock.Listen(); err != nil {
return err
}
// set starting back false now that we are running
m.Starting = false
if err := m.startShares(); err != nil {
return err
}
if m.HostUser.Modified {
if machine.UpdatePodmanDockerSockService(m, name, m.UID, m.Rootful) == nil {
// Reset modification state if there are no errors, otherwise ignore errors
// which are already logged
m.HostUser.Modified = false
}
}
// Write the config with updated starting status and hostuser modification
return m.writeConfig()
}
func (m *HyperVMachine) State(_ bool) (machine.Status, error) {
vmm := hypervctl.NewVirtualMachineManager()
vm, err := vmm.GetMachine(m.Name)
if err != nil {
return "", err
}
if vm.IsStarting() {
return machine.Starting, nil
}
if vm.State() == hypervctl.Enabled {
return machine.Running, nil
}
// Following QEMU pattern here where only three
// states seem valid
return machine.Stopped, nil
}
func (m *HyperVMachine) Stop(name string, opts machine.StopOptions) error {
m.lock.Lock()
defer m.lock.Unlock()
vmm := hypervctl.NewVirtualMachineManager()
vm, err := vmm.GetMachine(m.Name)
if err != nil {
return fmt.Errorf("getting virtual machine: %w", err)
}
vmState := vm.State()
if vm.State() == hypervctl.Disabled {
return nil
}
if vmState != hypervctl.Enabled { // more states could be provided as well
return hypervctl.ErrMachineStateInvalid
}
if err := machine.CleanupGVProxy(m.GvProxyPid); err != nil {
logrus.Error(err)
}
if err := vm.Stop(); err != nil {
return fmt.Errorf("stopping virtual machine: %w", err)
}
// keep track of last up
m.LastUp = time.Now()
return m.writeConfig()
}
func (m *HyperVMachine) jsonConfigPath() (string, error) {
configDir, err := machine.GetConfDir(machine.HyperVVirt)
if err != nil {
return "", err
}
return getVMConfigPath(configDir, m.Name), nil
}
func (m *HyperVMachine) loadFromFile() (*HyperVMachine, error) {
if len(m.Name) < 1 {
return nil, errors.New("encountered machine with no name")
}
jsonPath, err := m.jsonConfigPath()
if err != nil {
return nil, err
}
mm := HyperVMachine{}
if err := mm.loadHyperVMachineFromJSON(jsonPath); err != nil {
if errors.Is(err, machine.ErrNoSuchVM) {
return nil, &machine.ErrVMDoesNotExist{Name: m.Name}
}
return nil, err
}
lock, err := machine.GetLock(mm.Name, vmtype)
if err != nil {
return nil, err
}
mm.lock = lock
vmm := hypervctl.NewVirtualMachineManager()
vm, err := vmm.GetMachine(m.Name)
if err != nil {
return nil, err
}
cfg, err := vm.GetConfig(mm.ImagePath.GetPath())
if err != nil {
return nil, err
}
// If the machine is on, we can get what it is actually using
if cfg.Hardware.CPUs > 0 {
mm.CPUs = uint64(cfg.Hardware.CPUs)
}
// Same for memory
if cfg.Hardware.Memory > 0 {
mm.Memory = uint64(cfg.Hardware.Memory)
}
return &mm, nil
}
// getVMConfigPath is a simple wrapper for getting the fully-qualified
// path of the vm json config file. It should be used to get conformity
func getVMConfigPath(configDir, vmName string) string {
return filepath.Join(configDir, fmt.Sprintf("%s.json", vmName))
}
func (m *HyperVMachine) loadHyperVMachineFromJSON(fqConfigPath string) error {
b, err := os.ReadFile(fqConfigPath)
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
return machine.ErrNoSuchVM
}
return err
}
return json.Unmarshal(b, m)
}
func (m *HyperVMachine) startHostNetworking() (string, machine.APIForwardingState, error) {
var (
forwardSock string
state machine.APIForwardingState
)
cfg, err := config.Default()
if err != nil {
return "", machine.NoForwarding, err
}
executable, err := os.Executable()
if err != nil {
return "", 0, fmt.Errorf("unable to locate executable: %w", err)
}
gvproxyBinary, err := cfg.FindHelperBinary("gvproxy.exe", false)
if err != nil {
return "", 0, err
}
cmd := gvproxy.NewGvproxyCommand()
cmd.SSHPort = m.Port
cmd.AddEndpoint(fmt.Sprintf("vsock://%s", m.NetworkHVSock.KeyName))
cmd.PidFile = m.GvProxyPid.GetPath()
cmd, forwardSock, state = m.setupAPIForwarding(cmd)
if logrus.IsLevelEnabled(logrus.DebugLevel) {
cmd.Debug = true
}
c := cmd.Cmd(gvproxyBinary)
if logrus.IsLevelEnabled(logrus.DebugLevel) {
if err := logCommandToFile(c, "gvproxy.log"); err != nil {
return "", 0, err
}
}
logrus.Debugf("Starting gvproxy with command: %s %v", gvproxyBinary, c.Args)
if err := c.Start(); err != nil {
return "", 0, fmt.Errorf("unable to execute: %s: %w", cmd.ToCmdline(), err)
}
logrus.Debugf("Got gvproxy PID as %d", c.Process.Pid)
if len(m.MountVsocks) == 0 {
return forwardSock, state, nil
}
// Start the 9p server in the background
args := []string{}
if logrus.IsLevelEnabled(logrus.DebugLevel) {
args = append(args, "--log-level=debug")
}
args = append(args, "machine", "server9p")
for dir, vsock := range m.MountVsocks {
for _, mount := range m.Mounts {
if mount.Target == dir {
args = append(args, "--serve", fmt.Sprintf("%s:%s", mount.Source, winio.VsockServiceID(uint32(vsock)).String()))
break
}
}
}
args = append(args, fmt.Sprintf("%d", c.Process.Pid))
logrus.Debugf("Going to start 9p server using command: %s %v", executable, args)
fsCmd := exec.Command(executable, args...)
if logrus.IsLevelEnabled(logrus.DebugLevel) {
if err := logCommandToFile(fsCmd, "podman-machine-server9.log"); err != nil {
return "", 0, err
}
}
if err := fsCmd.Start(); err != nil {
return "", 0, fmt.Errorf("unable to execute: %s %v: %w", executable, args, err)
}
logrus.Infof("Started podman 9p server as PID %d", fsCmd.Process.Pid)
return forwardSock, state, nil
}
func logCommandToFile(c *exec.Cmd, filename string) error {
dir, err := machine.GetDataDir(machine.HyperVVirt)
if err != nil {
return fmt.Errorf("obtain machine dir: %w", err)
}
path := filepath.Join(dir, filename)
logrus.Infof("Going to log to %s", path)
log, err := os.Create(path)
if err != nil {
return fmt.Errorf("create log file: %w", err)
}
defer log.Close()
c.Stdout = log
c.Stderr = log
return nil
}
func (m *HyperVMachine) setupAPIForwarding(cmd gvproxy.GvproxyCommand) (gvproxy.GvproxyCommand, string, machine.APIForwardingState) {
socket, err := m.forwardSocketPath()
if err != nil {
return cmd, "", machine.NoForwarding
}
destSock := fmt.Sprintf("/run/user/%d/podman/podman.sock", m.UID)
forwardUser := "core"
if m.Rootful {
destSock = "/run/podman/podman.sock"
forwardUser = "root"
}
cmd.AddForwardSock(socket.GetPath())
cmd.AddForwardDest(destSock)
cmd.AddForwardUser(forwardUser)
cmd.AddForwardIdentity(m.IdentityPath)
return cmd, "", machine.MachineLocal
}
func (m *HyperVMachine) dockerSock() (string, error) {
dd, err := machine.GetDataDir(machine.HyperVVirt)
if err != nil {
return "", err
}
return filepath.Join(dd, "podman.sock"), nil
}
func (m *HyperVMachine) forwardSocketPath() (*define.VMFile, error) {
sockName := "podman.sock"
path, err := machine.GetDataDir(machine.HyperVVirt)
if err != nil {
return nil, fmt.Errorf("Resolving data dir: %s", err.Error())
}
return define.NewMachineFile(filepath.Join(path, sockName), &sockName)
}
func (m *HyperVMachine) writeConfig() error {
// Write the JSON file
return machine.WriteConfig(m.ConfigPath.Path, m)
}
func (m *HyperVMachine) setRootful(rootful bool) error {
if err := machine.SetRootful(rootful, m.Name, m.Name+"-root"); err != nil {
return err
}
m.HostUser.Modified = true
return nil
}
func (m *HyperVMachine) resizeDisk(newSize strongunits.GiB) error {
if m.DiskSize > uint64(newSize) {
return &machine.ErrNewDiskSizeTooSmall{OldSize: strongunits.ToGiB(strongunits.B(m.DiskSize)), NewSize: newSize}
}
resize := exec.Command("powershell", []string{"-command", fmt.Sprintf("Resize-VHD %s %d", m.ImagePath.GetPath(), newSize.ToBytes())}...)
resize.Stdout = os.Stdout
resize.Stderr = os.Stderr
if err := resize.Run(); err != nil {
return fmt.Errorf("resizing image: %q", err)
}
return nil
}
func (m *HyperVMachine) isStarting() bool {
return m.Starting
}
func (m *HyperVMachine) createShares() (_ map[string]uint64, defErr error) {
toReturn := make(map[string]uint64)
for _, mount := range m.Mounts {
var vsock *HVSockRegistryEntry
vsockNum, ok := m.MountVsocks[mount.Target]
if ok {
// Ignore errors here, we'll just try and recreate the
// vsock below.
testVsock, err := LoadHVSockRegistryEntry(vsockNum)
if err == nil {
vsock = testVsock
}
}
if vsock == nil {
testVsock, err := NewHVSockRegistryEntry(m.Name, Fileserver)
if err != nil {
return nil, err
}
defer func() {
if defErr != nil {
if err := testVsock.Remove(); err != nil {
logrus.Errorf("Removing vsock: %v", err)
}
}
}()
vsock = testVsock
}
logrus.Debugf("Going to share directory %s via 9p on vsock %d", mount.Source, vsock.Port)
toReturn[mount.Target] = vsock.Port
}
return toReturn, nil
}
func (m *HyperVMachine) removeShares() error {
var removalErr error
for _, mount := range m.Mounts {
vsockNum, ok := m.MountVsocks[mount.Target]
if !ok {
// Mount doesn't have a valid vsock, no need to tear down
continue
}
vsock, err := LoadHVSockRegistryEntry(vsockNum)
if err != nil {
logrus.Debugf("Vsock %d for mountpoint %s does not have a valid registry entry, skipping removal", vsockNum, mount.Target)
continue
}
if err := vsock.Remove(); err != nil {
if removalErr != nil {
logrus.Errorf("Error removing vsock: %w", removalErr)
}
removalErr = fmt.Errorf("removing vsock %d for mountpoint %s: %w", vsockNum, mount.Target, err)
}
}
return removalErr
}
func (m *HyperVMachine) startShares() error {
for mountpoint, sockNum := range m.MountVsocks {
args := []string{"-q", "--", "sudo", "podman"}
if logrus.IsLevelEnabled(logrus.DebugLevel) {
args = append(args, "--log-level=debug")
}
args = append(args, "machine", "client9p", fmt.Sprintf("%d", sockNum), mountpoint)
sshOpts := machine.SSHOptions{
Args: args,
}
if err := m.SSH(m.Name, sshOpts); err != nil {
return err
}
}
return nil
}