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  }