github.com/solo-io/unik@v0.0.0-20190717152701-a58d3e8e33b7/pkg/providers/virtualbox/virtualboxclient/client.go (about) 1 package virtualboxclient 2 3 import ( 4 "fmt" 5 "github.com/sirupsen/logrus" 6 "github.com/emc-advanced-dev/pkg/errors" 7 "github.com/solo-io/unik/pkg/config" 8 "github.com/solo-io/unik/pkg/types" 9 "os/exec" 10 "path" 11 "path/filepath" 12 "regexp" 13 "strings" 14 ) 15 16 type VboxVm struct { 17 Name string 18 UUID string 19 MACAddr string 20 Devices []*VboxDevice 21 Running bool 22 } 23 24 type VboxDevice struct { 25 DiskFile string 26 ControllerKey string 27 } 28 29 func (vm *VboxVm) String() string { 30 if vm == nil { 31 return "<nil>" 32 } 33 return fmt.Sprintf("%-v", *vm) 34 } 35 36 func vboxManageQuiet(args ...string) ([]byte, error) { 37 cmd := exec.Command("VBoxManage", args...) 38 out, err := cmd.CombinedOutput() 39 if err != nil { 40 return nil, fmt.Errorf("%s", string(out)) 41 } 42 return out, nil 43 } 44 45 func vboxManage(args ...string) ([]byte, error) { 46 cmd := exec.Command("VBoxManage", args...) 47 logrus.WithField("command", cmd.Args).Debugf("running VBoxManage command") 48 out, err := cmd.CombinedOutput() 49 if err != nil { 50 return nil, fmt.Errorf("%s", string(out)) 51 } 52 if len(out) > 0 { 53 logrus.WithField("result", string(out)).Debugf("VBoxManage result") 54 } 55 return out, nil 56 } 57 58 func parseVmInfo(vmInfo string) (*VboxVm, error) { 59 var name, uuid, macAddr string 60 var running bool 61 devices := []*VboxDevice{} 62 lines := strings.Split(vmInfo, "\n") 63 for _, line := range lines { 64 if strings.HasPrefix(line, "NAME:") { 65 rLineBegin, err := regexp.Compile("NAME:\\ +") 66 if err != nil { 67 return nil, errors.New("compiling regex", err) 68 } 69 name = string(rLineBegin.ReplaceAll([]byte(line), []byte(""))) 70 } 71 if strings.HasPrefix(line, "UUID:") { 72 rLineBegin, err := regexp.Compile("UUID:\\ +") 73 if err != nil { 74 return nil, errors.New("compiling regex", err) 75 } 76 uuid = string(rLineBegin.ReplaceAll([]byte(line), []byte(""))) 77 } 78 if strings.Contains(line, "NIC 1:") { //first network adapter must be the IP we use 79 rLineBegin, err := regexp.Compile("NIC 1:.*MAC. ") 80 if err != nil { 81 return nil, errors.New("compiling regex", err) 82 } 83 rLineEnd, err := regexp.Compile(",.*") 84 if err != nil { 85 return nil, errors.New("compiling regex", err) 86 } 87 macAddr = formatMac(string(rLineBegin.ReplaceAll(rLineEnd.ReplaceAll([]byte(line), []byte("")), []byte("")))) 88 //logrus.Debugf("mac address found for vm: %s", macAddr) 89 } 90 if strings.Contains(line, "SCSI (") { 91 device, err := parseDevice(line) 92 if err == nil { 93 devices = append(devices, device) 94 } 95 } 96 if strings.Contains(line, "State") && strings.Contains(line, "running") { 97 running = true 98 } 99 } 100 if macAddr == "" { 101 return nil, errors.New("mac address not found in vm info: "+string(vmInfo), nil) 102 } 103 if uuid == "" { 104 return nil, errors.New("uuid address not found in vm info: "+string(vmInfo), nil) 105 } 106 return &VboxVm{Name: name, MACAddr: macAddr, Running: running, Devices: devices, UUID: uuid}, nil 107 } 108 109 func parseDevice(deviceLine string) (*VboxDevice, error) { 110 rLineBegin, err := regexp.Compile("SCSI \\([0-9], [0-15]\\): ") 111 if err != nil { 112 return nil, errors.New("compiling regex", err) 113 } 114 rLineEnd, err := regexp.Compile("\\(UUID: .*") 115 if err != nil { 116 return nil, errors.New("compiling regex", err) 117 } 118 diskFile := rLineBegin.ReplaceAll(rLineEnd.ReplaceAll([]byte(deviceLine), []byte("")), []byte("")) 119 rLineBegin, err = regexp.Compile(".*\\([0-15], ") 120 if err != nil { 121 return nil, errors.New("compiling regex", err) 122 } 123 rLineEnd, err = regexp.Compile("\\):.*") 124 if err != nil { 125 return nil, errors.New("compiling regex", err) 126 } 127 controllerKey := rLineBegin.ReplaceAll(rLineEnd.ReplaceAll([]byte(deviceLine), []byte("")), []byte("")) 128 return &VboxDevice{DiskFile: string(diskFile), ControllerKey: string(controllerKey)}, nil 129 } 130 131 func Vms() ([]*VboxVm, error) { 132 out, err := vboxManageQuiet("list", "vms") 133 if err != nil { 134 return nil, errors.New("getting vm list from virtualbox", err) 135 } 136 vmNames := []string{} 137 lines := strings.Split(string(out), "\n") 138 r, err := regexp.Compile("\"(.*)\"") 139 if err != nil { 140 return nil, errors.New("compiling regex", err) 141 } 142 for _, line := range lines { 143 vmName := r.FindStringSubmatch(line) 144 if len(vmName) > 0 { 145 vmNames = append(vmNames, vmName[1]) 146 } 147 } 148 vms := []*VboxVm{} 149 for _, vmName := range vmNames { 150 if strings.Contains(vmName, "inaccessible") { 151 continue 152 } 153 logrus.Debugf("found vm: " + vmName) 154 vmInfo, err := vboxManage("showvminfo", vmName) 155 if err != nil { 156 return nil, errors.New("getting vm info for "+vmName, err) 157 } 158 vm, err := parseVmInfo(string(vmInfo)) 159 if err != nil { 160 return nil, errors.New("parsing vm info string", err) 161 } 162 vm.Name = vmName 163 vms = append(vms, vm) 164 } 165 166 return vms, nil 167 } 168 169 func GetVm(vmNameOrId string) (*VboxVm, error) { 170 vmInfo, err := vboxManageQuiet("showvminfo", vmNameOrId) 171 if err != nil { 172 return nil, errors.New("getting vm info for "+vmNameOrId, err) 173 } 174 vm, err := parseVmInfo(string(vmInfo)) 175 if err != nil { 176 return nil, errors.New("parsing vm info string", err) 177 } 178 return vm, nil 179 } 180 181 func CreateVm(vmName, baseFolder string, memoryMb int, adapterName string, adapterType config.VirtualboxAdapterType, storageDriver types.StorageDriver) error { 182 var nicArgs []string 183 switch adapterType { 184 case config.BridgedAdapter: 185 nicArgs = []string{"modifyvm", vmName, "--nic1", "bridged", "--bridgeadapter1", adapterName, "--nictype1", "virtio"} 186 case config.HostOnlyAdapter: 187 nicArgs = []string{"modifyvm", vmName, "--nic1", "hostonly", "--hostonlyadapter1", adapterName, "--nictype1", "virtio"} 188 default: 189 return errors.New(string(adapterType)+" not a valid adapter type, must specify either "+string(config.BridgedAdapter)+" or "+string(config.HostOnlyAdapter)+" network config", nil) 190 } 191 if _, err := vboxManage("createvm", "--name", vmName, "--basefolder", baseFolder, "-ostype", "Linux26_64"); err != nil { 192 return errors.New("creating vm", err) 193 } 194 if _, err := vboxManage("registervm", filepath.Join(baseFolder, vmName, fmt.Sprintf("%s.vbox", vmName))); err != nil { 195 return errors.New("registering vm", err) 196 } 197 switch storageDriver { 198 case types.StorageDriver_SCSI: 199 if _, err := vboxManage("storagectl", vmName, "--name", "SCSI", "--add", "scsi", "--controller", "LsiLogic"); err != nil { 200 return errors.New("adding scsi storage controller", err) 201 } 202 case types.StorageDriver_SATA: 203 if _, err := vboxManage("storagectl", vmName, "--name", "SATA Controller", "--add", "sata", "--controller", "IntelAHCI"); err != nil { 204 return errors.New("adding sata storage controller", err) 205 } 206 case types.StorageDriver_IDE: 207 if _, err := vboxManage("storagectl", vmName, "--name", "IDE Controller", "--add", "ide", "--bootable", "on"); err != nil { 208 return errors.New("adding ide storage controller", err) 209 } 210 } 211 //NIC ORDER MATTERS 212 if _, err := vboxManage(nicArgs...); err != nil { 213 return errors.New("setting "+string(adapterType)+" networking on vm", err) 214 } 215 if _, err := vboxManage("modifyvm", vmName, "--nic2", "nat", "--nictype2", "virtio"); err != nil { 216 return errors.New("setting nat networking on vm", err) 217 } 218 if _, err := vboxManage("modifyvm", vmName, "--memory", fmt.Sprintf("%v", memoryMb)); err != nil { 219 return errors.New("setting nat networking on vm", err) 220 } 221 if _, err := vboxManage("modifyvm", vmName, "--uart1", "0x3F8", "4", "--uartmode1", "file", path.Join(baseFolder, vmName, "Logs", "serial.log")); err != nil { 222 return errors.New("setting serial", err) 223 } 224 return nil 225 } 226 227 func CreateVmNatless(vmName, baseFolder, adapterName string, adapterType config.VirtualboxAdapterType, storageDriver types.StorageDriver) error { 228 var nicArgs []string 229 switch adapterType { 230 case config.BridgedAdapter: 231 nicArgs = []string{"modifyvm", vmName, "--nic1", "bridged", "--bridgeadapter1", adapterName, "--nictype1", "virtio"} 232 case config.HostOnlyAdapter: 233 nicArgs = []string{"modifyvm", vmName, "--nic1", "hostonly", "--hostonlyadapter1", adapterName, "--nictype1", "virtio"} 234 default: 235 return errors.New(string(adapterType)+" not a valid adapter type, must specify either "+string(config.BridgedAdapter)+" or "+string(config.HostOnlyAdapter)+" network config", nil) 236 } 237 if _, err := vboxManage("createvm", "--name", vmName, "--basefolder", baseFolder, "-ostype", "Linux26_64"); err != nil { 238 return errors.New("creating vm", err) 239 } 240 if _, err := vboxManage("registervm", filepath.Join(baseFolder, vmName, fmt.Sprintf("%s.vbox", vmName))); err != nil { 241 return errors.New("registering vm", err) 242 } 243 switch storageDriver { 244 case types.StorageDriver_SCSI: 245 if _, err := vboxManage("storagectl", vmName, "--name", "SCSI", "--add", "scsi", "--controller", "LsiLogic"); err != nil { 246 return errors.New("adding scsi storage controller", err) 247 } 248 case types.StorageDriver_SATA: 249 if _, err := vboxManage("storagectl", vmName, "--name", "SATA Controller", "--add", "sata", "--controller", "IntelAHCI"); err != nil { 250 return errors.New("adding sata storage controller", err) 251 } 252 } 253 if _, err := vboxManage(nicArgs...); err != nil { 254 return errors.New("setting "+string(adapterType)+" networking on vm", err) 255 } 256 return nil 257 } 258 259 func ConfigureVmNetwork(vmName, adapterName string, adapterType config.VirtualboxAdapterType) error { 260 var nicArgs []string 261 switch adapterType { 262 case config.BridgedAdapter: 263 nicArgs = []string{"modifyvm", vmName, "--nic1", "bridged", "--bridgeadapter1", adapterName, "--nictype1", "virtio"} 264 case config.HostOnlyAdapter: 265 nicArgs = []string{"modifyvm", vmName, "--nic1", "hostonly", "--hostonlyadapter1", adapterName, "--nictype1", "virtio"} 266 default: 267 return errors.New(string(adapterType)+" not a valid adapter type, must specify either "+string(config.BridgedAdapter)+" or "+string(config.HostOnlyAdapter)+" network config", nil) 268 } 269 if _, err := vboxManage(nicArgs...); err != nil { 270 return errors.New("setting "+string(adapterType)+" networking on vm", err) 271 } 272 return nil 273 } 274 275 func DestroyVm(vmNameOrId string) error { 276 if _, err := vboxManage("unregistervm", vmNameOrId, "--delete"); err != nil { 277 return errors.New("unregistering and deleting vm", err) 278 } 279 return nil 280 } 281 282 func PowerOnVm(vmNameOrId string) error { 283 _, err := vboxManage("startvm", vmNameOrId, "--type", "headless") 284 return err 285 } 286 287 func PowerOffVm(vmNameOrId string) error { 288 _, err := vboxManage("controlvm", vmNameOrId, "poweroff") 289 return err 290 } 291 292 func RefreshDiskUUID(diskPath string) error { 293 _, err := vboxManage("internalcommands", "sethduuid", diskPath) 294 return err 295 } 296 297 func AttachDisk(vmNameOrId, vmdkPath string, controllerPort int, storageDriver types.StorageDriver) error { 298 switch storageDriver { 299 case types.StorageDriver_SCSI: 300 return attachDiskSCSI(vmNameOrId, vmdkPath, controllerPort) 301 case types.StorageDriver_SATA: 302 return attachDiskSATA(vmNameOrId, vmdkPath, controllerPort) 303 case types.StorageDriver_IDE: 304 return attachDiskIDE(vmNameOrId, vmdkPath, controllerPort) 305 } 306 return errors.New("unknown storage driver "+string(storageDriver), nil) 307 } 308 309 func DetachDisk(vmNameOrId string, controllerPort int, storageDriver types.StorageDriver) error { 310 switch storageDriver { 311 case types.StorageDriver_SCSI: 312 return detachDiskSCSI(vmNameOrId, controllerPort) 313 case types.StorageDriver_SATA: 314 return detachDiskSATA(vmNameOrId, controllerPort) 315 } 316 return errors.New("unknown storage driver "+string(storageDriver), nil) 317 } 318 319 func attachDiskSCSI(vmNameOrId, vmdkPath string, controllerPort int) error { 320 if _, err := vboxManage("storageattach", vmNameOrId, "--storagectl", "SCSI", "--port", fmt.Sprintf("%v", controllerPort), "--type", "hdd", "--medium", vmdkPath); err != nil { 321 return errors.New("attaching storage", err) 322 } 323 return nil 324 } 325 326 func attachDiskIDE(vmNameOrId, vmdkPath string, controllerPort int) error { 327 if _, err := vboxManage("storageattach", vmNameOrId, "--storagectl", "IDE Controller", "--port", fmt.Sprintf("%v", controllerPort), "--device", "0", "--type", "hdd", "--medium", vmdkPath); err != nil { 328 return errors.New("attaching storage", err) 329 } 330 return nil 331 } 332 333 func detachDiskSCSI(vmNameOrId string, controllerPort int) error { 334 if _, err := vboxManage("storageattach", vmNameOrId, "--storagectl", "SCSI", "--port", fmt.Sprintf("%v", controllerPort), "--type", "hdd", "--medium", "none"); err != nil { 335 return errors.New("attaching storage", err) 336 } 337 return nil 338 } 339 340 func attachDiskSATA(vmNameOrId, vmdkPath string, controllerPort int) error { 341 if _, err := vboxManage("storageattach", vmNameOrId, "--storagectl", "SATA Controller", "--port", fmt.Sprintf("%v", controllerPort), "--type", "hdd", "--medium", vmdkPath); err != nil { 342 return errors.New("attaching storage", err) 343 } 344 return nil 345 } 346 347 func detachDiskSATA(vmNameOrId string, controllerPort int) error { 348 if _, err := vboxManage("storageattach", vmNameOrId, "--storagectl", "SATA Controller", "--port", fmt.Sprintf("%v", controllerPort), "--type", "hdd", "--medium", "none"); err != nil { 349 return errors.New("attaching storage", err) 350 } 351 return nil 352 } 353 354 func formatMac(rawMac string) string { 355 return strings.ToLower(rawMac[0:2] + ":" + rawMac[2:4] + ":" + rawMac[4:6] + ":" + rawMac[6:8] + ":" + rawMac[8:10] + ":" + rawMac[10:12]) 356 }