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 }