github.com/cloud-foundations/dominator@v0.0.0-20221004181915-6e4fee580046/cmd/vm-control/exportVirshVm.go (about)

     1  package main
     2  
     3  import (
     4  	"encoding/xml"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  	"os/exec"
     9  	"path/filepath"
    10  
    11  	"github.com/Cloud-Foundations/Dominator/lib/errors"
    12  	"github.com/Cloud-Foundations/Dominator/lib/fsutil"
    13  	"github.com/Cloud-Foundations/Dominator/lib/log"
    14  	proto "github.com/Cloud-Foundations/Dominator/proto/hypervisor"
    15  )
    16  
    17  type vmExporterVirsh struct{}
    18  
    19  func exportVirshVmSubcommand(args []string, logger log.DebugLogger) error {
    20  	if err := exportVirshVm(args[0], logger); err != nil {
    21  		return fmt.Errorf("Error exporting VM: %s", err)
    22  	}
    23  	return nil
    24  }
    25  
    26  func exportVirshVm(vmHostname string, logger log.DebugLogger) error {
    27  	return vmExport(vmHostname, vmExporterVirsh{}, logger)
    28  }
    29  
    30  func (exporter vmExporterVirsh) createVm(hostname string,
    31  	vmInfo proto.ExportLocalVmInfo) error {
    32  	virshInfo := virshInfoType{
    33  		Cpu: cpuType{Mode: "host-passthrough"},
    34  		Devices: devicesInfo{
    35  			Interfaces: []interfaceType{
    36  				addressToInterface(vmInfo.Address, vmInfo.Bridges[0])},
    37  			SerialPorts: []serialType{{
    38  				Type:   "file",
    39  				Source: serialSourceType{Path: "/dev/null"},
    40  			}},
    41  		},
    42  		Memory: memoryInfo{Value: vmInfo.MemoryInMiB, Unit: "MiB"},
    43  		Name:   hostname,
    44  		Os: osInfo{
    45  			Type: osTypeInfo{Arch: "x86_64", Machine: "pc", Value: "hvm"},
    46  		},
    47  		Type: "kvm",
    48  		VCpu: vCpuInfo{
    49  			Num:       (vmInfo.MilliCPUs + 500) / 1000,
    50  			Placement: "static"},
    51  	}
    52  	for index, address := range vmInfo.SecondaryAddresses {
    53  		virshInfo.Devices.Interfaces = append(virshInfo.Devices.Interfaces,
    54  			addressToInterface(address, vmInfo.Bridges[index+1]))
    55  	}
    56  	for index, volume := range vmInfo.Volumes {
    57  		volume, err := makeVolume(volume, index,
    58  			vmInfo.VolumeLocations[index].Filename)
    59  		if err != nil {
    60  			return err
    61  		}
    62  		virshInfo.Devices.Volumes = append(virshInfo.Devices.Volumes, volume)
    63  	}
    64  	if virshInfo.VCpu.Num < 1 {
    65  		virshInfo.VCpu.Num = 1
    66  	}
    67  	xmlData, err := xml.MarshalIndent(virshInfo, "", "    ")
    68  	if err != nil {
    69  		return err
    70  	}
    71  	xmlData = append(xmlData, '\n')
    72  	os.Stdout.Write(xmlData)
    73  	response, err := askForInputChoice(
    74  		fmt.Sprintf("Have you added %s/%s to your DHCP Server",
    75  			vmInfo.Address.MacAddress, vmInfo.Address.IpAddress),
    76  		[]string{"yes", "no"})
    77  	if err != nil {
    78  		return err
    79  	}
    80  	switch response {
    81  	case "no":
    82  		return errors.New("DHCP not configured for VM")
    83  	case "yes":
    84  	default:
    85  		return fmt.Errorf("invalid response: %s", response)
    86  	}
    87  	tmpdir, err := ioutil.TempDir("", "export-vm")
    88  	if err != nil {
    89  		return err
    90  	}
    91  	defer os.RemoveAll(tmpdir)
    92  	tmpfile := filepath.Join(tmpdir, hostname+".xml")
    93  	if file, err := os.Create(tmpfile); err != nil {
    94  		return err
    95  	} else {
    96  		defer file.Close()
    97  		if _, err := file.Write(xmlData); err != nil {
    98  			return err
    99  		}
   100  		if err := file.Close(); err != nil {
   101  			return err
   102  		}
   103  	}
   104  	cmd := exec.Command("virsh", "define", tmpfile)
   105  	cmd.Stdout = os.Stdout
   106  	cmd.Stderr = os.Stderr
   107  	if err := cmd.Run(); err != nil {
   108  		return fmt.Errorf("error defining virsh VM: %s", err)
   109  	}
   110  	cmd = exec.Command("virsh", "start", hostname)
   111  	cmd.Stdout = os.Stdout
   112  	cmd.Stderr = os.Stderr
   113  	if err := cmd.Run(); err != nil {
   114  		return fmt.Errorf("error starting virsh VM: %s", err)
   115  	}
   116  	return nil
   117  }
   118  
   119  func (exporter vmExporterVirsh) destroyVm(hostname string) error {
   120  	cmd := exec.Command("virsh", "destroy", hostname)
   121  	cmd.Stdout = os.Stdout
   122  	cmd.Stderr = os.Stderr
   123  	if err := cmd.Run(); err != nil {
   124  		return fmt.Errorf("error destroying new VM: %s", err)
   125  	}
   126  	cmd = exec.Command("virsh",
   127  		[]string{"undefine", "--managed-save", "--snapshots-metadata",
   128  			"--remove-all-storage", hostname}...)
   129  	cmd.Stdout = os.Stdout
   130  	cmd.Stderr = os.Stderr
   131  	if err := cmd.Run(); err != nil {
   132  		return fmt.Errorf("error undefining new VM: %s", err)
   133  	}
   134  	return nil
   135  }
   136  
   137  func addressToInterface(address proto.Address, bridge string) interfaceType {
   138  	return interfaceType{
   139  		Mac:    macType{Address: address.MacAddress},
   140  		Model:  modelType{Type: "virtio"},
   141  		Source: bridgeType{Bridge: bridge},
   142  		Type:   "bridge",
   143  	}
   144  }
   145  
   146  func makeVolume(volume proto.Volume, index int,
   147  	filename string) (volumeType, error) {
   148  	dirname := filepath.Dir(filename)
   149  	dirname = filepath.Join(filepath.Dir(dirname), "export",
   150  		filepath.Base(dirname))
   151  	if err := os.MkdirAll(dirname, fsutil.DirPerms); err != nil {
   152  		return volumeType{}, err
   153  	}
   154  	exportFilename := filepath.Join(dirname, filepath.Base(filename))
   155  	os.Remove(exportFilename)
   156  	if err := os.Link(filename, exportFilename); err != nil {
   157  		return volumeType{}, err
   158  	}
   159  	return volumeType{
   160  		Device: "disk",
   161  		Driver: driverType{
   162  			Name:  "qemu",
   163  			Type:  volume.Format.String(),
   164  			Cache: "none",
   165  			Io:    "native",
   166  		},
   167  		Source: sourceType{File: exportFilename},
   168  		Target: targetType{Device: "vd" + string('a'+index), Bus: "virtio"},
   169  		Type:   "file",
   170  	}, nil
   171  }