github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/vsphere/internal/vsphereclient/client.go (about) 1 // Copyright 2015-2017 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package vsphereclient 5 6 import ( 7 "context" 8 "net/url" 9 "path" 10 "strings" 11 12 "github.com/juju/errors" 13 "github.com/juju/loggo" 14 "github.com/vmware/govmomi" 15 "github.com/vmware/govmomi/find" 16 "github.com/vmware/govmomi/list" 17 "github.com/vmware/govmomi/object" 18 "github.com/vmware/govmomi/property" 19 "github.com/vmware/govmomi/vim25/methods" 20 "github.com/vmware/govmomi/vim25/mo" 21 "github.com/vmware/govmomi/vim25/soap" 22 "github.com/vmware/govmomi/vim25/types" 23 ) 24 25 // Client encapsulates a vSphere client, exposing the subset of 26 // functionality that we require in the Juju provider. 27 type Client struct { 28 client *govmomi.Client 29 datacenter string 30 logger loggo.Logger 31 } 32 33 // Dial dials a new vSphere client connection using the given URL, 34 // scoped to the specified dataceter. The resulting Client's Close 35 // method must be called in order to release resources allocated by 36 // Dial. 37 func Dial( 38 ctx context.Context, 39 u *url.URL, 40 datacenter string, 41 logger loggo.Logger, 42 ) (*Client, error) { 43 client, err := govmomi.NewClient(ctx, u, true) 44 if err != nil { 45 return nil, errors.Trace(err) 46 } 47 return &Client{client, datacenter, logger}, nil 48 } 49 50 // Close logs out and closes the client connection. 51 func (c *Client) Close(ctx context.Context) error { 52 return c.client.Logout(ctx) 53 } 54 55 func (c *Client) lister(ref types.ManagedObjectReference) *list.Lister { 56 return &list.Lister{ 57 Collector: property.DefaultCollector(c.client.Client), 58 Reference: ref, 59 All: true, 60 } 61 } 62 63 func (c *Client) finder(ctx context.Context) (*find.Finder, *object.Datacenter, error) { 64 finder := find.NewFinder(c.client.Client, true) 65 datacenter, err := finder.Datacenter(ctx, c.datacenter) 66 if err != nil { 67 return nil, nil, errors.Trace(err) 68 } 69 finder.SetDatacenter(datacenter) 70 return finder, datacenter, nil 71 } 72 73 // RemoveVirtualMachines removes VMs matching the given path from the 74 // system. The path may include wildcards, to match multiple VMs. 75 func (c *Client) RemoveVirtualMachines(ctx context.Context, path string) error { 76 finder, _, err := c.finder(ctx) 77 if err != nil { 78 return errors.Trace(err) 79 } 80 81 vms, err := finder.VirtualMachineList(ctx, path) 82 if err != nil { 83 if _, ok := err.(*find.NotFoundError); ok { 84 c.logger.Debugf("no VMs matching path %q", path) 85 return nil 86 } 87 return errors.Annotatef(err, "listing VMs at %q", path) 88 } 89 90 // Retrieve VM details so we know which ones to power off. 91 refs := make([]types.ManagedObjectReference, len(vms)) 92 for i, vm := range vms { 93 refs[i] = vm.Reference() 94 } 95 var mos []mo.VirtualMachine 96 if err := c.client.Retrieve(ctx, refs, nil, &mos); err != nil { 97 return errors.Annotate(err, "retrieving VM details") 98 } 99 100 // We run all tasks in parallel, and wait for them below. 101 var lastError error 102 tasks := make([]*object.Task, 0, len(vms)*2) 103 for i, vm := range vms { 104 if mos[i].Runtime.PowerState == types.VirtualMachinePowerStatePoweredOn { 105 c.logger.Debugf("powering off %q", vm.Name()) 106 task, err := vm.PowerOff(ctx) 107 if err != nil { 108 lastError = errors.Annotatef(err, "powering off %q", vm.Name()) 109 c.logger.Errorf(err.Error()) 110 continue 111 } 112 tasks = append(tasks, task) 113 } 114 c.logger.Debugf("destroying %q", vm.Name()) 115 task, err := vm.Destroy(ctx) 116 if err != nil { 117 lastError = errors.Annotatef(err, "destroying %q", vm.Name()) 118 c.logger.Errorf(err.Error()) 119 continue 120 } 121 tasks = append(tasks, task) 122 } 123 124 for _, task := range tasks { 125 _, err := task.WaitForResult(ctx, nil) 126 if err != nil && !isManagedObjectNotFound(err) { 127 lastError = err 128 c.logger.Errorf(err.Error()) 129 } 130 } 131 return errors.Annotate(lastError, "failed to remove instances") 132 } 133 134 // VirtualMachines return list of all VMs in the system matching the given path. 135 func (c *Client) VirtualMachines(ctx context.Context, path string) ([]*mo.VirtualMachine, error) { 136 finder, _, err := c.finder(ctx) 137 if err != nil { 138 return nil, errors.Trace(err) 139 } 140 items, err := finder.VirtualMachineList(ctx, path) 141 if err != nil { 142 if _, ok := err.(*find.NotFoundError); ok { 143 return nil, nil 144 } 145 return nil, errors.Annotate(err, "listing VMs") 146 } 147 148 vms := make([]*mo.VirtualMachine, len(items)) 149 for i, item := range items { 150 var vm mo.VirtualMachine 151 err := c.client.RetrieveOne(ctx, item.Reference(), nil, &vm) 152 if err != nil { 153 return nil, errors.Trace(err) 154 } 155 vms[i] = &vm 156 } 157 return vms, nil 158 } 159 160 // ComputeResources returns list of all root compute resources in the system. 161 func (c *Client) ComputeResources(ctx context.Context) ([]*mo.ComputeResource, error) { 162 _, datacenter, err := c.finder(ctx) 163 if err != nil { 164 return nil, errors.Trace(err) 165 } 166 folders, err := datacenter.Folders(ctx) 167 if err != nil { 168 return nil, errors.Trace(err) 169 } 170 171 es, err := c.lister(folders.HostFolder.Reference()).List(ctx) 172 if err != nil { 173 return nil, errors.Trace(err) 174 } 175 176 var cprs []*mo.ComputeResource 177 for _, e := range es { 178 switch o := e.Object.(type) { 179 case mo.ClusterComputeResource: 180 cprs = append(cprs, &o.ComputeResource) 181 case mo.ComputeResource: 182 cprs = append(cprs, &o) 183 } 184 } 185 return cprs, nil 186 } 187 188 // Datastores returns list of all datastores in the system. 189 func (c *Client) Datastores(ctx context.Context) ([]*mo.Datastore, error) { 190 _, datacenter, err := c.finder(ctx) 191 if err != nil { 192 return nil, errors.Trace(err) 193 } 194 folders, err := datacenter.Folders(ctx) 195 if err != nil { 196 return nil, errors.Trace(err) 197 } 198 199 es, err := c.lister(folders.DatastoreFolder.Reference()).List(ctx) 200 if err != nil { 201 return nil, errors.Trace(err) 202 } 203 204 var datastores []*mo.Datastore 205 for _, e := range es { 206 switch o := e.Object.(type) { 207 case mo.Datastore: 208 datastores = append(datastores, &o) 209 } 210 } 211 return datastores, nil 212 } 213 214 // EnsureVMFolder creates the a VM folder with the given path if it doesn't 215 // already exist. 216 func (c *Client) EnsureVMFolder(ctx context.Context, folderPath string) (*object.Folder, error) { 217 finder, datacenter, err := c.finder(ctx) 218 if err != nil { 219 return nil, errors.Trace(err) 220 } 221 folders, err := datacenter.Folders(ctx) 222 if err != nil { 223 return nil, errors.Trace(err) 224 } 225 226 createFolder := func(parent *object.Folder, name string) (*object.Folder, error) { 227 folder, err := parent.CreateFolder(ctx, name) 228 if err != nil && soap.IsSoapFault(err) { 229 switch soap.ToSoapFault(err).VimFault().(type) { 230 case types.DuplicateName: 231 return finder.Folder(ctx, parent.InventoryPath+"/"+name) 232 } 233 } 234 return folder, err 235 } 236 237 parentFolder := folders.VmFolder 238 for _, name := range strings.Split(folderPath, "/") { 239 folder, err := createFolder(parentFolder, name) 240 if err != nil { 241 return nil, errors.Annotatef( 242 err, "creating folder %q in %q", 243 name, parentFolder.InventoryPath, 244 ) 245 } 246 parentFolder = folder 247 } 248 return parentFolder, nil 249 } 250 251 // DestroyVMFolder destroys a folder rooted at the datacenter's base VM folder. 252 func (c *Client) DestroyVMFolder(ctx context.Context, folderPath string) error { 253 finder, datacenter, err := c.finder(ctx) 254 if err != nil { 255 return errors.Trace(err) 256 } 257 folders, err := datacenter.Folders(ctx) 258 if err != nil { 259 return errors.Trace(err) 260 } 261 folderPath = path.Join(folders.VmFolder.InventoryPath, folderPath) 262 folder, err := finder.Folder(ctx, folderPath) 263 if err != nil { 264 if _, ok := err.(*find.NotFoundError); ok { 265 return nil 266 } 267 return errors.Trace(err) 268 } 269 270 task, err := folder.Destroy(ctx) 271 if err != nil { 272 return errors.Trace(err) 273 } 274 _, err = task.WaitForResult(ctx, nil) 275 if err != nil && !isManagedObjectNotFound(err) { 276 return errors.Trace(err) 277 } 278 return nil 279 } 280 281 // MoveVMFolderInto moves one VM folder into another. 282 func (c *Client) MoveVMFolderInto(ctx context.Context, parentPath, childPath string) error { 283 finder, datacenter, err := c.finder(ctx) 284 if err != nil { 285 return errors.Trace(err) 286 } 287 folders, err := datacenter.Folders(ctx) 288 if err != nil { 289 return errors.Trace(err) 290 } 291 292 parentPath = path.Join(folders.VmFolder.InventoryPath, parentPath) 293 childPath = path.Join(folders.VmFolder.InventoryPath, childPath) 294 parent, err := finder.Folder(ctx, parentPath) 295 if err != nil { 296 return errors.Trace(err) 297 } 298 child, err := finder.Folder(ctx, childPath) 299 if err != nil { 300 return errors.Trace(err) 301 } 302 303 task, err := parent.MoveInto(ctx, []types.ManagedObjectReference{child.Reference()}) 304 if err != nil { 305 return errors.Trace(err) 306 } 307 if _, err := task.WaitForResult(ctx, nil); err != nil { 308 return errors.Trace(err) 309 } 310 return nil 311 } 312 313 // MoveVMsInto moves a set of VMs into a folder. 314 func (c *Client) MoveVMsInto( 315 ctx context.Context, 316 folderPath string, 317 vms ...types.ManagedObjectReference, 318 ) error { 319 finder, datacenter, err := c.finder(ctx) 320 if err != nil { 321 return errors.Trace(err) 322 } 323 folders, err := datacenter.Folders(ctx) 324 if err != nil { 325 return errors.Trace(err) 326 } 327 folderPath = path.Join(folders.VmFolder.InventoryPath, folderPath) 328 folder, err := finder.Folder(ctx, folderPath) 329 if err != nil { 330 return errors.Trace(err) 331 } 332 333 task, err := folder.MoveInto(ctx, vms) 334 if err != nil { 335 return errors.Trace(err) 336 } 337 if _, err := task.WaitForResult(ctx, nil); err != nil { 338 return errors.Trace(err) 339 } 340 return nil 341 } 342 343 // UpdateVirtualMachineExtraConfig updates the "ExtraConfig" attributes 344 // of the specified virtual machine. Keys with empty values will be 345 // removed from the config; existing keys that are unspecified in the 346 // map will be untouched. 347 func (c *Client) UpdateVirtualMachineExtraConfig( 348 ctx context.Context, 349 vmInfo *mo.VirtualMachine, 350 metadata map[string]string, 351 ) error { 352 var spec types.VirtualMachineConfigSpec 353 for k, v := range metadata { 354 opt := &types.OptionValue{Key: k, Value: v} 355 spec.ExtraConfig = append(spec.ExtraConfig, opt) 356 } 357 vm := object.NewVirtualMachine(c.client.Client, vmInfo.Reference()) 358 task, err := vm.Reconfigure(ctx, spec) 359 if err != nil { 360 return errors.Annotate(err, "reconfiguring VM") 361 } 362 if _, err := task.WaitForResult(ctx, nil); err != nil { 363 return errors.Annotate(err, "reconfiguring VM") 364 } 365 return nil 366 } 367 368 // DeleteDatastoreFile deletes a file or directory in the datastore. 369 func (c *Client) DeleteDatastoreFile(ctx context.Context, datastorePath string) error { 370 _, datacenter, err := c.finder(ctx) 371 if err != nil { 372 return errors.Trace(err) 373 } 374 fileManager := object.NewFileManager(c.client.Client) 375 deleteTask, err := fileManager.DeleteDatastoreFile(ctx, datastorePath, datacenter) 376 if err != nil { 377 return errors.Trace(err) 378 } 379 if _, err := deleteTask.WaitForResult(ctx, nil); err != nil { 380 if types.IsFileNotFound(err) { 381 return nil 382 } 383 return errors.Trace(err) 384 } 385 return nil 386 } 387 388 func (c *Client) destroyVM( 389 ctx context.Context, 390 vm *object.VirtualMachine, 391 taskWaiter *taskWaiter, 392 ) error { 393 task, err := vm.Destroy(ctx) 394 if err != nil { 395 return errors.Trace(err) 396 } 397 _, err = taskWaiter.waitTask(ctx, task, "destroying VM") 398 return errors.Trace(err) 399 } 400 401 func (c *Client) cloneVM( 402 ctx context.Context, 403 srcVM *object.VirtualMachine, 404 dstName string, 405 vmFolder *object.Folder, 406 taskWaiter *taskWaiter, 407 ) (*object.VirtualMachine, error) { 408 task, err := srcVM.Clone(ctx, vmFolder, dstName, types.VirtualMachineCloneSpec{ 409 Config: &types.VirtualMachineConfigSpec{}, 410 Location: types.VirtualMachineRelocateSpec{}, 411 }) 412 if err != nil { 413 return nil, errors.Trace(err) 414 } 415 info, err := taskWaiter.waitTask(ctx, task, "cloning VM") 416 if err != nil { 417 return nil, err 418 } 419 return object.NewVirtualMachine(c.client.Client, info.Result.(types.ManagedObjectReference)), nil 420 } 421 422 func (c *Client) extendDisk( 423 ctx context.Context, 424 datacenter *object.Datacenter, 425 datastorePath string, 426 capacityKB int64, 427 taskWaiter *taskWaiter, 428 ) error { 429 // NOTE(axw) there's no ExtendVirtualDisk on the disk manager type, 430 // hence why we're dealing with request types directly. Send a patch 431 // to govmomi to add this to VirtualDiskManager. 432 433 diskManager := object.NewVirtualDiskManager(c.client.Client) 434 dcref := datacenter.Reference() 435 req := types.ExtendVirtualDisk_Task{ 436 This: diskManager.Reference(), 437 Name: datastorePath, 438 Datacenter: &dcref, 439 NewCapacityKb: capacityKB, 440 } 441 442 res, err := methods.ExtendVirtualDisk_Task(ctx, c.client.Client, &req) 443 if err != nil { 444 return errors.Trace(err) 445 } 446 task := object.NewTask(c.client.Client, res.Returnval) 447 _, err = taskWaiter.waitTask(ctx, task, "extending disk") 448 return errors.Trace(err) 449 } 450 451 func (c *Client) detachDisk( 452 ctx context.Context, 453 vm *object.VirtualMachine, 454 taskWaiter *taskWaiter, 455 ) (string, error) { 456 457 var mo mo.VirtualMachine 458 if err := c.client.RetrieveOne(ctx, vm.Reference(), []string{"config.hardware"}, &mo); err != nil { 459 return "", errors.Trace(err) 460 } 461 462 var spec types.VirtualMachineConfigSpec 463 var vmdkDatastorePath string 464 for _, dev := range mo.Config.Hardware.Device { 465 dev, ok := dev.(*types.VirtualDisk) 466 if !ok { 467 continue 468 } 469 backing, ok := dev.Backing.(types.BaseVirtualDeviceFileBackingInfo) 470 if !ok { 471 continue 472 } 473 vmdkDatastorePath = backing.GetVirtualDeviceFileBackingInfo().FileName 474 spec.DeviceChange = []types.BaseVirtualDeviceConfigSpec{ 475 &types.VirtualDeviceConfigSpec{ 476 Operation: types.VirtualDeviceConfigSpecOperationRemove, 477 Device: dev, 478 }, 479 } 480 break 481 } 482 if len(spec.DeviceChange) != 1 { 483 return "", errors.New("disk device not found") 484 } 485 486 task, err := vm.Reconfigure(ctx, spec) 487 if err != nil { 488 return "", errors.Trace(err) 489 } 490 if _, err := taskWaiter.waitTask(ctx, task, "detaching disk"); err != nil { 491 return "", errors.Trace(err) 492 } 493 return vmdkDatastorePath, nil 494 } 495 496 func isManagedObjectNotFound(err error) bool { 497 if f, ok := err.(types.HasFault); ok { 498 switch f.Fault().(type) { 499 case *types.ManagedObjectNotFound: 500 return true 501 } 502 } 503 return false 504 }