Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reconfigure VM implementation #1

Open
wants to merge 1 commit into
base: sample
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
301 changes: 279 additions & 22 deletions examples/datastores/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ be used to navigate a vSphere inventory structure using govmomi.
package main

import (
"errors"
"context"
"flag"
"fmt"
Expand All @@ -31,13 +32,37 @@ import (
"text/tabwriter"

"github.com/vmware/govmomi"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/find"
"github.com/vmware/govmomi/property"
"github.com/vmware/govmomi/units"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
)

const (
ProviderName = "vsphere"
ActivePowerState = "poweredOn"
SCSIControllerType = "scsi"
LSILogicControllerType = "lsiLogic"
BusLogicControllerType = "busLogic"
PVSCSIControllerType = "pvscsi"
LSILogicSASControllerType = "lsiLogic-sas"
SCSIControllerLimit = 4
SCSIControllerDeviceLimit = 15
SCSIDeviceSlots = 16
SCSIReservedSlot = 7
ThinDiskType = "thin"
PreallocatedDiskType = "preallocated"
EagerZeroedThickDiskType = "eagerZeroedThick"
ZeroedThickDiskType = "zeroedThick"
VolDir = "kubevols"
RoundTripperDefaultCount = 3
)

var ErrNoDiskUUIDFound = errors.New("No disk UUID found")
var ErrNoDiskIDFound = errors.New("No vSphere disk ID found")
var ErrNoDevicesFound = errors.New("No devices found")
var ErrNonSupportedControllerType = errors.New("Disk is attached to non-supported controller type")
var ErrFileAlreadyExist = errors.New("File requested already exist")

// GetEnvString returns string from environment variable.
func GetEnvString(v string, def string) string {
r := os.Getenv(v)
Expand Down Expand Up @@ -113,6 +138,115 @@ func exit(err error) {
os.Exit(1)
}


func getSCSIController(vmDevices object.VirtualDeviceList, scsiType string) *types.VirtualController {
// get virtual scsi controller of passed argument type
for _, device := range vmDevices {
devType := vmDevices.Type(device)
if devType == scsiType {
if c, ok := device.(types.BaseVirtualController); ok {
return c.GetVirtualController()
}
}
}
return nil
}

func getSCSIControllers(vmDevices object.VirtualDeviceList) []*types.VirtualController {
// get all virtual scsi controllers
var scsiControllers []*types.VirtualController
for _, device := range vmDevices {
devType := vmDevices.Type(device)
switch devType {
case SCSIControllerType, strings.ToLower(LSILogicControllerType), strings.ToLower(BusLogicControllerType), PVSCSIControllerType, strings.ToLower(LSILogicSASControllerType):
if c, ok := device.(types.BaseVirtualController); ok {
scsiControllers = append(scsiControllers, c.GetVirtualController())
}
}
}
return scsiControllers
}

func getSCSIControllersOfType(vmDevices object.VirtualDeviceList, scsiType string) []*types.VirtualController {
// get virtual scsi controllers of passed argument type
var scsiControllers []*types.VirtualController
for _, device := range vmDevices {
devType := vmDevices.Type(device)
if devType == scsiType {
if c, ok := device.(types.BaseVirtualController); ok {
scsiControllers = append(scsiControllers, c.GetVirtualController())
}
}
}
return scsiControllers
}

func getAvailableSCSIController(scsiControllers []*types.VirtualController) *types.VirtualController {
// get SCSI controller which has space for adding more devices
for _, controller := range scsiControllers {
if len(controller.Device) < SCSIControllerDeviceLimit {
return controller
}
}
return nil
}

// Removes SCSI controller which is latest attached to VM.
func cleanUpController(newSCSIController types.BaseVirtualDevice, vmDevices object.VirtualDeviceList, vm *object.VirtualMachine, ctx context.Context) error {
if newSCSIController == nil || vmDevices == nil || vm == nil {
return nil
}
ctls := vmDevices.SelectByType(newSCSIController)
if len(ctls) < 1 {
return ErrNoDevicesFound
}
newScsi := ctls[len(ctls)-1]
err := vm.RemoveDevice(ctx, true, newScsi)
if err != nil {
return err
}
return nil
}

func getNextUnitNumber(devices object.VirtualDeviceList, c types.BaseVirtualController) (int32, error) {
// get next available SCSI controller unit number
var takenUnitNumbers [SCSIDeviceSlots]bool
takenUnitNumbers[SCSIReservedSlot] = true
key := c.GetVirtualController().Key

for _, device := range devices {
d := device.GetVirtualDevice()
if d.ControllerKey == key {
if d.UnitNumber != nil {
takenUnitNumbers[*d.UnitNumber] = true
}
}
}
for unitNumber, takenUnitNumber := range takenUnitNumbers {
if !takenUnitNumber {
return int32(unitNumber), nil
}
}
return -1, fmt.Errorf("SCSI Controller with key=%d does not have any available slots (LUN).", key)
}

func formatVirtualDiskUUID(uuid string) string {
uuidwithNoSpace := strings.Replace(uuid, " ", "", -1)
uuidWithNoHypens := strings.Replace(uuidwithNoSpace, "-", "", -1)
return strings.ToLower(uuidWithNoHypens)
}

// Returns formatted UUID for a virtual disk device.
func getVirtualDiskUUID(newDevice types.BaseVirtualDevice) (string, error) {
vd := newDevice.GetVirtualDevice()

if b, ok := vd.Backing.(*types.VirtualDiskFlatVer2BackingInfo); ok {
uuid := formatVirtualDiskUUID(b.Uuid)
return uuid, nil
}
return "", ErrNoDiskUUIDFound
}

func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
Expand Down Expand Up @@ -145,36 +279,159 @@ func main() {
// Make future calls local to this datacenter
f.SetDatacenter(dc)

// Find datastores in datacenter
dss, err := f.DatastoreList(ctx, "*")
vmDiskPath := "[vsanDatastore] kubevols/redis-master.vmdk"
datastorePathObj := new(object.DatastorePath)
isSuccess := datastorePathObj.FromString(vmDiskPath)
if !isSuccess {
exit(errors.New("Failed to parse vmDiskPath"))
}

ds, err := f.Datastore(ctx, datastorePathObj.Datastore)

vmRegex := "k8s-mine/node1"

vm, err := f.VirtualMachine(ctx, vmRegex)
if err != nil {
exit(err)
}

vmDevices, err := vm.Device(ctx)
if err != nil {
exit(err)
}

var diskControllerType = "pvscsi"
// find SCSI controller of particular type from VM devices
allSCSIControllers := getSCSIControllers(vmDevices)
scsiControllersOfRequiredType := getSCSIControllersOfType(vmDevices, diskControllerType)
scsiController := getAvailableSCSIController(scsiControllersOfRequiredType)

var newSCSICreated = false
var newSCSIController types.BaseVirtualDevice

// creating a scsi controller as there is none found of controller type defined
if scsiController == nil {
if len(allSCSIControllers) >= 4 {
fmt.Fprintf(os.Stderr, "Error: we reached the maximum number of controllers we can attach\n")
os.Exit(1)
}
newSCSIController, err := vmDevices.CreateSCSIController(diskControllerType)
if err != nil {
exit(err)
}
configNewSCSIController := newSCSIController.(types.BaseVirtualSCSIController).GetVirtualSCSIController()
hotAndRemove := true
configNewSCSIController.HotAddRemove = &hotAndRemove
configNewSCSIController.SharedBus = types.VirtualSCSISharing(types.VirtualSCSISharingNoSharing)

// add the scsi controller to virtual machine
err = vm.AddDevice(context.TODO(), newSCSIController)
if err != nil {
// attempt clean up of scsi controller
if vmDevices, err := vm.Device(ctx); err == nil {
cleanUpController(newSCSIController, vmDevices, vm, ctx)
}
exit(err)
}

pc := property.DefaultCollector(c.Client)
// verify scsi controller in virtual machine
vmDevices, err = vm.Device(ctx)
if err != nil {
// cannot cleanup if there is no device list
exit(err)
}

// Convert datastores into list of references
var refs []types.ManagedObjectReference
for _, ds := range dss {
refs = append(refs, ds.Reference())
scsiController = getSCSIController(vmDevices, "pvscsi")
if scsiController == nil {
// attempt clean up of scsi controller
cleanUpController(newSCSIController, vmDevices, vm, ctx)
fmt.Fprintf(os.Stderr, "Error: no scsi controller\n")
os.Exit(1)
}
newSCSICreated = true
}

// Retrieve summary property for all datastores
var dst []mo.Datastore
err = pc.Retrieve(ctx, refs, []string{"summary"}, &dst)
disk := vmDevices.CreateDisk(scsiController, ds.Reference(), vmDiskPath)
unitNumber, err := getNextUnitNumber(vmDevices, scsiController)
if err != nil {
exit(err)
}
*disk.UnitNumber = unitNumber

backing := disk.Backing.(*types.VirtualDiskFlatVer2BackingInfo)
backing.DiskMode = string(types.VirtualDiskModeIndependent_persistent)

// Reconfigure VM
virtualMachineConfigSpec := types.VirtualMachineConfigSpec{}
deviceConfigSpec := &types.VirtualDeviceConfigSpec{
Device: disk,
Operation: types.VirtualDeviceConfigSpecOperationAdd,
//Profile: []types.VirtualMachineProfileSpec {
// &types.VirtualMachineDefinedProfileSpec {
// ProfileId : "f4e5bade-15a2-4805-bf8e-52318c4ce443",
// },
//},
}

storageProfileSpec := &types.VirtualMachineDefinedProfileSpec {
ProfileId : "f4e5bade-15a2-4805-bf8e-52318c4ce443",
}

deviceConfigSpec.Profile = append(deviceConfigSpec.Profile, storageProfileSpec)
virtualMachineConfigSpec.DeviceChange = append(virtualMachineConfigSpec.DeviceChange, deviceConfigSpec)

task, err := vm.Reconfigure(ctx, virtualMachineConfigSpec)
if err != nil {
exit(err)
}

// Print summary per datastore
tw := tabwriter.NewWriter(os.Stdout, 2, 0, 2, ' ', 0)
fmt.Fprintf(tw, "Name:\tType:\tCapacity:\tFree:\n")
for _, ds := range dst {
fmt.Fprintf(tw, "%s\t", ds.Summary.Name)
fmt.Fprintf(tw, "%s\t", ds.Summary.Type)
fmt.Fprintf(tw, "%s\t", units.ByteSize(ds.Summary.Capacity))
fmt.Fprintf(tw, "%s\t", units.ByteSize(ds.Summary.FreeSpace))
fmt.Fprintf(tw, "\n")
err = task.Wait(ctx)
if err != nil {
exit(err)
}

// Attach disk to the VM
//err = vm.AddDevice(ctx, disk)
//if err != nil {
// if newSCSICreated {
// cleanUpController(newSCSIController, vmDevices, vm, ctx)
// }
// exit(err)
//}

vmDevices, err = vm.Device(ctx)
if err != nil {
if newSCSICreated {
cleanUpController(newSCSIController, vmDevices, vm, ctx)
}
exit(err)
}

devices := vmDevices.SelectByType(disk)
if len(devices) < 1 {
if newSCSICreated {
cleanUpController(newSCSIController, vmDevices, vm, ctx)
}
exit(errors.New("No devices found"))
}

// get new disk id
newDevice := devices[len(devices)-1]
deviceName := devices.Name(newDevice)

// get device uuid
diskUUID, err := getVirtualDiskUUID(newDevice)
if err != nil {
if newSCSICreated {
cleanUpController(newSCSIController, vmDevices, vm, ctx)
}
exit(err)
}

tw := tabwriter.NewWriter(os.Stdout, 2, 0, 2, ' ', 0)
fmt.Fprintf(tw, "DeviceName:\tDiskUUID:\n")
fmt.Fprintf(tw, "%s\t", deviceName)
fmt.Fprintf(tw, "%s\t", diskUUID)
fmt.Fprintf(tw, "\n")
tw.Flush()
}