github.com/vmware/go-vcloud-director/v2@v2.24.0/govcd/vapp.go (about) 1 /* 2 * Copyright 2023 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. 3 */ 4 5 package govcd 6 7 import ( 8 "encoding/xml" 9 "errors" 10 "fmt" 11 "net/http" 12 "strconv" 13 "time" 14 15 "github.com/vmware/go-vcloud-director/v2/types/v56" 16 "github.com/vmware/go-vcloud-director/v2/util" 17 ) 18 19 type VApp struct { 20 VApp *types.VApp 21 client *Client 22 } 23 24 func NewVApp(cli *Client) *VApp { 25 return &VApp{ 26 VApp: new(types.VApp), 27 client: cli, 28 } 29 } 30 31 func (vcdClient *VCDClient) NewVApp(client *Client) VApp { 32 newvapp := NewVApp(client) 33 return *newvapp 34 } 35 36 // struct type used to pass information for vApp network creation 37 type VappNetworkSettings struct { 38 ID string 39 Name string 40 Description string 41 Gateway string 42 NetMask string 43 SubnetPrefixLength string 44 DNS1 string 45 DNS2 string 46 DNSSuffix string 47 GuestVLANAllowed *bool 48 StaticIPRanges []*types.IPRange 49 DhcpSettings *DhcpSettings 50 RetainIpMacEnabled *bool 51 VappFenceEnabled *bool 52 } 53 54 // struct type used to pass information for vApp network DHCP 55 type DhcpSettings struct { 56 IsEnabled bool 57 MaxLeaseTime int 58 DefaultLeaseTime int 59 IPRange *types.IPRange 60 } 61 62 // Returns the vdc where the vapp resides in. 63 func (vapp *VApp) GetParentVDC() (Vdc, error) { 64 for _, link := range vapp.VApp.Link { 65 if (link.Type == types.MimeVDC || link.Type == types.MimeAdminVDC) && link.Rel == "up" { 66 67 vdc := NewVdc(vapp.client) 68 69 _, err := vapp.client.ExecuteRequest(link.HREF, http.MethodGet, 70 "", "error retrieving parent vdc: %s", nil, vdc.Vdc) 71 if err != nil { 72 return Vdc{}, err 73 } 74 75 parent, err := vdc.getParentOrg() 76 if err != nil { 77 return Vdc{}, err 78 } 79 vdc.parent = parent 80 return *vdc, nil 81 } 82 } 83 return Vdc{}, fmt.Errorf("could not find a parent Vdc") 84 } 85 86 func (vapp *VApp) Refresh() error { 87 88 if vapp.VApp.HREF == "" { 89 return fmt.Errorf("cannot refresh, Object is empty") 90 } 91 92 url := vapp.VApp.HREF 93 // Empty struct before a new unmarshal, otherwise we end up with duplicate 94 // elements in slices. 95 vapp.VApp = &types.VApp{} 96 97 _, err := vapp.client.ExecuteRequest(url, http.MethodGet, 98 "", "error refreshing vApp: %s", nil, vapp.VApp) 99 100 // The request was successful 101 return err 102 } 103 104 // AddVM create vm in vApp using vApp template 105 // orgVdcNetworks - adds org VDC networks to be available for vApp. Can be empty. 106 // vappNetworkName - adds vApp network to be available for vApp. Can be empty. 107 // vappTemplate - vApp Template which will be used for VM creation. 108 // name - name for VM. 109 // acceptAllEulas - setting allows to automatically accept or not Eulas. 110 // 111 // Deprecated: Use vapp.AddNewVM instead for more sophisticated network handling 112 func (vapp *VApp) AddVM(orgVdcNetworks []*types.OrgVDCNetwork, vappNetworkName string, vappTemplate VAppTemplate, name string, acceptAllEulas bool) (Task, error) { 113 util.Logger.Printf("[INFO] vapp.AddVM() is deprecated in favor of vapp.AddNewVM()") 114 if vappTemplate == (VAppTemplate{}) || vappTemplate.VAppTemplate == nil { 115 return Task{}, fmt.Errorf("vApp Template can not be empty") 116 } 117 118 // primaryNetworkConnectionIndex will be inherited from template or defaulted to 0 119 // if the template does not have any NICs assigned. 120 primaryNetworkConnectionIndex := 0 121 if vappTemplate.VAppTemplate.Children != nil && len(vappTemplate.VAppTemplate.Children.VM) > 0 && 122 vappTemplate.VAppTemplate.Children.VM[0].NetworkConnectionSection != nil { 123 primaryNetworkConnectionIndex = vappTemplate.VAppTemplate.Children.VM[0].NetworkConnectionSection.PrimaryNetworkConnectionIndex 124 } 125 126 networkConnectionSection := types.NetworkConnectionSection{ 127 Info: "Network config for sourced item", 128 PrimaryNetworkConnectionIndex: primaryNetworkConnectionIndex, 129 } 130 131 for index, orgVdcNetwork := range orgVdcNetworks { 132 networkConnectionSection.NetworkConnection = append(networkConnectionSection.NetworkConnection, 133 &types.NetworkConnection{ 134 Network: orgVdcNetwork.Name, 135 NetworkConnectionIndex: index, 136 IsConnected: true, 137 IPAddressAllocationMode: types.IPAllocationModePool, 138 }, 139 ) 140 } 141 142 if vappNetworkName != "" { 143 networkConnectionSection.NetworkConnection = append(networkConnectionSection.NetworkConnection, 144 &types.NetworkConnection{ 145 Network: vappNetworkName, 146 NetworkConnectionIndex: len(orgVdcNetworks), 147 IsConnected: true, 148 IPAddressAllocationMode: types.IPAllocationModePool, 149 }, 150 ) 151 } 152 153 return vapp.AddNewVM(name, vappTemplate, &networkConnectionSection, acceptAllEulas) 154 } 155 156 // AddRawVM accepts raw types.ReComposeVAppParams which contains all information for VM creation 157 func (vapp *VApp) AddRawVM(vAppComposition *types.ReComposeVAppParams) (*VM, error) { 158 apiEndpoint := urlParseRequestURI(vapp.VApp.HREF) 159 apiEndpoint.Path += "/action/recomposeVApp" 160 161 // Return the task 162 task, err := vapp.client.ExecuteTaskRequestWithApiVersion(apiEndpoint.String(), http.MethodPost, 163 types.MimeRecomposeVappParams, "error instantiating a new VM: %s", 164 vAppComposition, vapp.client.GetSpecificApiVersionOnCondition(">=37.1", "37.1")) 165 if err != nil { 166 return nil, err 167 } 168 169 err = task.WaitTaskCompletion() 170 if err != nil { 171 return nil, fmt.Errorf("VM creation task failed: %s", err) 172 } 173 174 // VM task does not return any reference to VM therefore it must be looked up by name after 175 // creation 176 177 var vmName string 178 if vAppComposition.SourcedItem != nil && vAppComposition.SourcedItem.Source != nil { 179 vmName = vAppComposition.SourcedItem.Source.Name 180 } 181 182 vm, err := vapp.GetVMByName(vmName, true) 183 if err != nil { 184 return nil, fmt.Errorf("error finding VM %s in vApp %s after creation: %s", vAppComposition.Name, vapp.VApp.Name, err) 185 } 186 187 return vm, nil 188 189 } 190 191 // AddNewVM adds VM from vApp template with custom NetworkConnectionSection 192 func (vapp *VApp) AddNewVM(name string, vappTemplate VAppTemplate, network *types.NetworkConnectionSection, acceptAllEulas bool) (Task, error) { 193 return vapp.AddNewVMWithStorageProfile(name, vappTemplate, network, nil, acceptAllEulas) 194 } 195 196 // AddNewVMWithStorageProfile adds VM from vApp template with custom NetworkConnectionSection and optional storage profile 197 func (vapp *VApp) AddNewVMWithStorageProfile(name string, vappTemplate VAppTemplate, 198 network *types.NetworkConnectionSection, 199 storageProfileRef *types.Reference, acceptAllEulas bool) (Task, error) { 200 return addNewVMW(vapp, name, vappTemplate, network, storageProfileRef, nil, acceptAllEulas) 201 } 202 203 // AddNewVMWithComputePolicy adds VM from vApp template with custom NetworkConnectionSection and optional storage profile 204 // and compute policy 205 func (vapp *VApp) AddNewVMWithComputePolicy(name string, vappTemplate VAppTemplate, 206 network *types.NetworkConnectionSection, 207 storageProfileRef *types.Reference, computePolicy *types.VdcComputePolicy, acceptAllEulas bool) (Task, error) { 208 return addNewVMW(vapp, name, vappTemplate, network, storageProfileRef, computePolicy, acceptAllEulas) 209 } 210 211 // addNewVMW adds VM from vApp template with custom NetworkConnectionSection and optional storage profile 212 // and optional compute policy 213 func addNewVMW(vapp *VApp, name string, vappTemplate VAppTemplate, 214 network *types.NetworkConnectionSection, 215 storageProfileRef *types.Reference, computePolicy *types.VdcComputePolicy, acceptAllEulas bool) (Task, error) { 216 217 if vappTemplate == (VAppTemplate{}) || vappTemplate.VAppTemplate == nil { 218 return Task{}, fmt.Errorf("vApp Template can not be empty") 219 } 220 221 templateHref := vappTemplate.VAppTemplate.HREF 222 if vappTemplate.VAppTemplate.Children != nil && len(vappTemplate.VAppTemplate.Children.VM) != 0 { 223 templateHref = vappTemplate.VAppTemplate.Children.VM[0].HREF 224 } 225 226 // Status 8 means The object is resolved and powered off. 227 // https://vdc-repo.vmware.com/vmwb-repository/dcr-public/94b8bd8d-74ff-4fe3-b7a4-41ae31516ed7/1b42f3b5-8b31-4279-8b3f-547f6c7c5aa8/doc/GUID-843BE3AD-5EF6-4442-B864-BCAE44A51867.html 228 if vappTemplate.VAppTemplate.Status != 8 { 229 return Task{}, fmt.Errorf("vApp Template shape is not ok (status: %d)", vappTemplate.VAppTemplate.Status) 230 } 231 232 // Validate network config only if it was supplied 233 if network != nil && network.NetworkConnection != nil { 234 for _, nic := range network.NetworkConnection { 235 if nic.Network == "" { 236 return Task{}, fmt.Errorf("missing mandatory attribute Network: %s", nic.Network) 237 } 238 if nic.IPAddressAllocationMode == "" { 239 return Task{}, fmt.Errorf("missing mandatory attribute IPAddressAllocationMode: %s", nic.IPAddressAllocationMode) 240 } 241 } 242 } 243 244 vAppComposition := &types.ReComposeVAppParams{ 245 Ovf: types.XMLNamespaceOVF, 246 Xsi: types.XMLNamespaceXSI, 247 Xmlns: types.XMLNamespaceVCloud, 248 Deploy: false, 249 Name: vapp.VApp.Name, 250 PowerOn: false, 251 Description: vapp.VApp.Description, 252 SourcedItem: &types.SourcedCompositionItemParam{ 253 Source: &types.Reference{ 254 HREF: templateHref, 255 Name: name, 256 }, 257 InstantiationParams: &types.InstantiationParams{}, // network config is injected below 258 }, 259 AllEULAsAccepted: acceptAllEulas, 260 } 261 262 // Add storage profile 263 if storageProfileRef != nil && storageProfileRef.HREF != "" { 264 vAppComposition.SourcedItem.StorageProfile = storageProfileRef 265 } 266 267 // Add compute policy 268 if computePolicy != nil && computePolicy.ID != "" { 269 vdcComputePolicyHref, err := vapp.client.OpenApiBuildEndpoint(types.OpenApiPathVersion1_0_0, types.OpenApiEndpointVdcComputePolicies, computePolicy.ID) 270 if err != nil { 271 return Task{}, fmt.Errorf("error constructing HREF for compute policy") 272 } 273 vAppComposition.SourcedItem.ComputePolicy = &types.ComputePolicy{VmSizingPolicy: &types.Reference{HREF: vdcComputePolicyHref.String()}} 274 } 275 276 // Inject network config 277 vAppComposition.SourcedItem.InstantiationParams.NetworkConnectionSection = network 278 279 apiEndpoint := urlParseRequestURI(vapp.VApp.HREF) 280 apiEndpoint.Path += "/action/recomposeVApp" 281 282 // Return the task 283 return vapp.client.ExecuteTaskRequest(apiEndpoint.String(), http.MethodPost, 284 types.MimeRecomposeVappParams, "error instantiating a new VM: %s", vAppComposition) 285 286 } 287 288 // ========================= issue#252 ================================== 289 // TODO: To be refactored, handling networks better. See issue#252 for details 290 // https://github.com/vmware/go-vcloud-director/issues/252 291 // ====================================================================== 292 func (vapp *VApp) RemoveVM(vm VM) error { 293 err := vapp.Refresh() 294 if err != nil { 295 return fmt.Errorf("error refreshing vApp before removing VM: %s", err) 296 } 297 task := NewTask(vapp.client) 298 if vapp.VApp.Tasks != nil { 299 for _, taskItem := range vapp.VApp.Tasks.Task { 300 task.Task = taskItem 301 // Leftover tasks may have unhandled errors that can be dismissed at this stage 302 // we complete any incomplete tasks at this stage, to finish the refresh. 303 if task.Task.Status != "error" && task.Task.Status != "success" { 304 err := task.WaitTaskCompletion() 305 if err != nil { 306 return fmt.Errorf("error performing task: %s", err) 307 } 308 } 309 } 310 } 311 312 vcomp := &types.ReComposeVAppParams{ 313 Ovf: types.XMLNamespaceOVF, 314 Xsi: types.XMLNamespaceXSI, 315 Xmlns: types.XMLNamespaceVCloud, 316 DeleteItem: &types.DeleteItem{ 317 HREF: vm.VM.HREF, 318 }, 319 } 320 321 apiEndpoint := urlParseRequestURI(vapp.VApp.HREF) 322 apiEndpoint.Path += "/action/recomposeVApp" 323 324 deleteTask, err := vapp.client.ExecuteTaskRequest(apiEndpoint.String(), http.MethodPost, 325 types.MimeRecomposeVappParams, "error removing VM: %s", vcomp) 326 if err != nil { 327 return err 328 } 329 330 err = deleteTask.WaitTaskCompletion() 331 if err != nil { 332 return fmt.Errorf("error performing removing VM task: %s", err) 333 } 334 335 return nil 336 } 337 338 func (vapp *VApp) PowerOn() (Task, error) { 339 340 err := vapp.BlockWhileStatus("UNRESOLVED", vapp.client.MaxRetryTimeout) 341 if err != nil { 342 return Task{}, fmt.Errorf("error powering on vApp: %s", err) 343 } 344 345 apiEndpoint := urlParseRequestURI(vapp.VApp.HREF) 346 apiEndpoint.Path += "/power/action/powerOn" 347 348 // Return the task 349 return vapp.client.ExecuteTaskRequest(apiEndpoint.String(), http.MethodPost, 350 "", "error powering on vApp: %s", nil) 351 } 352 353 func (vapp *VApp) PowerOff() (Task, error) { 354 355 apiEndpoint := urlParseRequestURI(vapp.VApp.HREF) 356 apiEndpoint.Path += "/power/action/powerOff" 357 358 // Return the task 359 return vapp.client.ExecuteTaskRequest(apiEndpoint.String(), http.MethodPost, 360 "", "error powering off vApp: %s", nil) 361 362 } 363 364 func (vapp *VApp) Reboot() (Task, error) { 365 366 apiEndpoint := urlParseRequestURI(vapp.VApp.HREF) 367 apiEndpoint.Path += "/power/action/reboot" 368 369 // Return the task 370 return vapp.client.ExecuteTaskRequest(apiEndpoint.String(), http.MethodPost, 371 "", "error rebooting vApp: %s", nil) 372 } 373 374 func (vapp *VApp) Reset() (Task, error) { 375 376 apiEndpoint := urlParseRequestURI(vapp.VApp.HREF) 377 apiEndpoint.Path += "/power/action/reset" 378 379 // Return the task 380 return vapp.client.ExecuteTaskRequest(apiEndpoint.String(), http.MethodPost, 381 "", "error resetting vApp: %s", nil) 382 } 383 384 // Suspend suspends a vApp 385 func (vapp *VApp) Suspend() (Task, error) { 386 387 apiEndpoint := urlParseRequestURI(vapp.VApp.HREF) 388 apiEndpoint.Path += "/power/action/suspend" 389 390 // Return the task 391 return vapp.client.ExecuteTaskRequest(apiEndpoint.String(), http.MethodPost, 392 "", "error suspending vApp: %s", nil) 393 } 394 395 // DiscardSuspendedState takes back a vApp from suspension 396 func (vapp *VApp) DiscardSuspendedState() error { 397 // Status 3 means that the vApp is suspended 398 if vapp.VApp.Status != 3 { 399 return nil 400 } 401 apiEndpoint := urlParseRequestURI(vapp.VApp.HREF) 402 apiEndpoint.Path += "/action/discardSuspendedState" 403 404 // Return the task 405 task, err := vapp.client.ExecuteTaskRequest(apiEndpoint.String(), http.MethodPost, 406 "", "error discarding suspended state for vApp: %s", nil) 407 if err != nil { 408 return err 409 } 410 return task.WaitTaskCompletion() 411 } 412 413 func (vapp *VApp) Shutdown() (Task, error) { 414 415 apiEndpoint := urlParseRequestURI(vapp.VApp.HREF) 416 apiEndpoint.Path += "/power/action/shutdown" 417 418 // Return the task 419 return vapp.client.ExecuteTaskRequest(apiEndpoint.String(), http.MethodPost, 420 "", "error shutting down vApp: %s", nil) 421 } 422 423 func (vapp *VApp) Undeploy() (Task, error) { 424 425 vu := &types.UndeployVAppParams{ 426 Xmlns: types.XMLNamespaceVCloud, 427 UndeployPowerAction: "powerOff", 428 } 429 430 apiEndpoint := urlParseRequestURI(vapp.VApp.HREF) 431 apiEndpoint.Path += "/action/undeploy" 432 433 // Return the task 434 return vapp.client.ExecuteTaskRequest(apiEndpoint.String(), http.MethodPost, 435 types.MimeUndeployVappParams, "error undeploy vApp: %s", vu) 436 } 437 438 func (vapp *VApp) Deploy() (Task, error) { 439 440 vu := &types.DeployVAppParams{ 441 Xmlns: types.XMLNamespaceVCloud, 442 PowerOn: false, 443 } 444 445 apiEndpoint := urlParseRequestURI(vapp.VApp.HREF) 446 apiEndpoint.Path += "/action/deploy" 447 448 // Return the task 449 return vapp.client.ExecuteTaskRequest(apiEndpoint.String(), http.MethodPost, 450 types.MimeDeployVappParams, "error deploy vApp: %s", vu) 451 } 452 453 func (vapp *VApp) Delete() (Task, error) { 454 455 // Return the task 456 return vapp.client.ExecuteTaskRequest(vapp.VApp.HREF, http.MethodDelete, 457 "", "error deleting vApp: %s", nil) 458 } 459 460 func (vapp *VApp) RunCustomizationScript(computername, script string) (Task, error) { 461 return vapp.Customize(computername, script, false) 462 } 463 464 // Customize applies customization to first child VM 465 // 466 // Deprecated: Use vm.SetGuestCustomizationSection() 467 func (vapp *VApp) Customize(computername, script string, changeSid bool) (Task, error) { 468 err := vapp.Refresh() 469 if err != nil { 470 return Task{}, fmt.Errorf("error refreshing vApp before running customization: %s", err) 471 } 472 473 // Check if VApp Children is populated 474 if vapp.VApp.Children == nil { 475 return Task{}, fmt.Errorf("vApp doesn't contain any children, interrupting customization") 476 } 477 478 vu := &types.GuestCustomizationSection{ 479 Ovf: types.XMLNamespaceOVF, 480 Xsi: types.XMLNamespaceXSI, 481 Xmlns: types.XMLNamespaceVCloud, 482 483 HREF: vapp.VApp.Children.VM[0].HREF, 484 Type: types.MimeGuestCustomizationSection, 485 Info: "Specifies Guest OS Customization Settings", 486 Enabled: addrOf(true), 487 ComputerName: computername, 488 CustomizationScript: script, 489 ChangeSid: &changeSid, 490 } 491 492 apiEndpoint := urlParseRequestURI(vapp.VApp.Children.VM[0].HREF) 493 apiEndpoint.Path += "/guestCustomizationSection/" 494 495 // Return the task 496 return vapp.client.ExecuteTaskRequest(apiEndpoint.String(), http.MethodPut, 497 types.MimeGuestCustomizationSection, "error customizing VM: %s", vu) 498 } 499 500 func (vapp *VApp) GetStatus() (string, error) { 501 err := vapp.Refresh() 502 if err != nil { 503 return "", fmt.Errorf("error refreshing vApp: %s", err) 504 } 505 // Trying to make this function future-proof: 506 // If a new status is added to a future vCD API and the status map in types.go 507 // is not updated, we may get a panic. 508 // Using the ", ok" construct we take control of the data lookup and are able to fail 509 // gracefully. 510 statusText, ok := types.VAppStatuses[vapp.VApp.Status] 511 if ok { 512 return statusText, nil 513 } 514 return "", fmt.Errorf("status %d does not have a description in types.VappStatuses", vapp.VApp.Status) 515 } 516 517 // BlockWhileStatus blocks until the status of vApp exits unwantedStatus. 518 // It sleeps 200 milliseconds between iterations and times out after timeOutAfterSeconds 519 // of seconds. 520 func (vapp *VApp) BlockWhileStatus(unwantedStatus string, timeOutAfterSeconds int) error { 521 timeoutAfter := time.After(time.Duration(timeOutAfterSeconds) * time.Second) 522 tick := time.NewTicker(200 * time.Millisecond) 523 524 for { 525 select { 526 case <-timeoutAfter: 527 return fmt.Errorf("timed out waiting for vApp to exit state %s after %d seconds", 528 unwantedStatus, timeOutAfterSeconds) 529 case <-tick.C: 530 currentStatus, err := vapp.GetStatus() 531 532 if err != nil { 533 return fmt.Errorf("could not get vApp status %s", err) 534 } 535 if currentStatus != unwantedStatus { 536 return nil 537 } 538 } 539 } 540 } 541 542 func (vapp *VApp) GetNetworkConnectionSection() (*types.NetworkConnectionSection, error) { 543 544 networkConnectionSection := &types.NetworkConnectionSection{} 545 546 if vapp.VApp.Children.VM[0].HREF == "" { 547 return networkConnectionSection, fmt.Errorf("cannot refresh, Object is empty") 548 } 549 550 _, err := vapp.client.ExecuteRequest(vapp.VApp.Children.VM[0].HREF+"/networkConnectionSection/", http.MethodGet, 551 types.MimeNetworkConnectionSection, "error retrieving network connection: %s", nil, networkConnectionSection) 552 553 // The request was successful 554 return networkConnectionSection, err 555 } 556 557 // Sets number of available virtual logical processors 558 // (i.e. CPUs x cores per socket) 559 // https://communities.vmware.com/thread/576209 560 // Deprecated: Use vm.ChangeCPUcount() 561 func (vapp *VApp) ChangeCPUCount(virtualCpuCount int) (Task, error) { 562 return vapp.ChangeCPUCountWithCore(virtualCpuCount, nil) 563 } 564 565 // Sets number of available virtual logical processors 566 // (i.e. CPUs x cores per socket) and cores per socket. 567 // Socket count is a result of: virtual logical processors/cores per socket 568 // https://communities.vmware.com/thread/576209 569 // Deprecated: Use vm.ChangeCPUCountWithCore() 570 func (vapp *VApp) ChangeCPUCountWithCore(virtualCpuCount int, coresPerSocket *int) (Task, error) { 571 572 err := vapp.Refresh() 573 if err != nil { 574 return Task{}, fmt.Errorf("error refreshing vApp before running customization: %s", err) 575 } 576 577 // Check if VApp Children is populated 578 if vapp.VApp.Children == nil { 579 return Task{}, fmt.Errorf("vApp doesn't contain any children, interrupting customization") 580 } 581 582 newcpu := &types.OVFItem{ 583 XmlnsRasd: types.XMLNamespaceRASD, 584 XmlnsVCloud: types.XMLNamespaceVCloud, 585 XmlnsXsi: types.XMLNamespaceXSI, 586 XmlnsVmw: types.XMLNamespaceVMW, 587 VCloudHREF: vapp.VApp.Children.VM[0].HREF + "/virtualHardwareSection/cpu", 588 VCloudType: types.MimeRasdItem, 589 AllocationUnits: "hertz * 10^6", 590 Description: "Number of Virtual CPUs", 591 ElementName: strconv.Itoa(virtualCpuCount) + " virtual CPU(s)", 592 InstanceID: 4, 593 Reservation: 0, 594 ResourceType: types.ResourceTypeProcessor, 595 VirtualQuantity: int64(virtualCpuCount), 596 Weight: 0, 597 CoresPerSocket: coresPerSocket, 598 Link: &types.Link{ 599 HREF: vapp.VApp.Children.VM[0].HREF + "/virtualHardwareSection/cpu", 600 Rel: "edit", 601 Type: types.MimeRasdItem, 602 }, 603 } 604 605 apiEndpoint := urlParseRequestURI(vapp.VApp.Children.VM[0].HREF) 606 apiEndpoint.Path += "/virtualHardwareSection/cpu" 607 608 // Return the task 609 return vapp.client.ExecuteTaskRequest(apiEndpoint.String(), http.MethodPut, 610 types.MimeRasdItem, "error changing CPU count: %s", newcpu) 611 } 612 613 func (vapp *VApp) ChangeStorageProfile(name string) (Task, error) { 614 err := vapp.Refresh() 615 if err != nil { 616 return Task{}, fmt.Errorf("error refreshing vApp before running customization: %s", err) 617 } 618 619 if vapp.VApp.Children == nil || len(vapp.VApp.Children.VM) == 0 { 620 return Task{}, fmt.Errorf("vApp doesn't contain any children, interrupting customization") 621 } 622 623 vdc, err := vapp.GetParentVDC() 624 if err != nil { 625 return Task{}, fmt.Errorf("error retrieving parent VDC for vApp %s", vapp.VApp.Name) 626 } 627 storageProfileRef, err := vdc.FindStorageProfileReference(name) 628 if err != nil { 629 return Task{}, fmt.Errorf("error retrieving storage profile %s for vApp %s", name, vapp.VApp.Name) 630 } 631 632 newProfile := &types.Vm{ 633 Name: vapp.VApp.Children.VM[0].Name, 634 StorageProfile: &storageProfileRef, 635 Xmlns: types.XMLNamespaceVCloud, 636 } 637 638 // Return the task 639 return vapp.client.ExecuteTaskRequest(vapp.VApp.Children.VM[0].HREF, http.MethodPut, 640 types.MimeVM, "error changing CPU count: %s", newProfile) 641 } 642 643 // Deprecated as it changes only first VM's name 644 func (vapp *VApp) ChangeVMName(name string) (Task, error) { 645 err := vapp.Refresh() 646 if err != nil { 647 return Task{}, fmt.Errorf("error refreshing vApp before running customization: %s", err) 648 } 649 650 if vapp.VApp.Children == nil { 651 return Task{}, fmt.Errorf("vApp doesn't contain any children, interrupting customization") 652 } 653 654 newName := &types.Vm{ 655 Name: name, 656 Xmlns: types.XMLNamespaceVCloud, 657 } 658 659 // Return the task 660 return vapp.client.ExecuteTaskRequest(vapp.VApp.Children.VM[0].HREF, http.MethodPut, 661 types.MimeVM, "error changing VM name: %s", newName) 662 } 663 664 // SetOvf sets guest properties for the first child VM in vApp 665 // 666 // Deprecated: Use vm.SetProductSectionList() 667 func (vapp *VApp) SetOvf(parameters map[string]string) (Task, error) { 668 err := vapp.Refresh() 669 if err != nil { 670 return Task{}, fmt.Errorf("error refreshing vApp before running customization: %s", err) 671 } 672 673 if vapp.VApp.Children == nil { 674 return Task{}, fmt.Errorf("vApp doesn't contain any children, interrupting customization") 675 } 676 677 if vapp.VApp.Children.VM[0].ProductSection == nil { 678 return Task{}, fmt.Errorf("vApp doesn't contain any children with ProductSection, interrupting customization") 679 } 680 681 for key, value := range parameters { 682 for _, ovf_value := range vapp.VApp.Children.VM[0].ProductSection.Property { 683 if ovf_value.Key == key { 684 ovf_value.Value = &types.Value{Value: value} 685 break 686 } 687 } 688 } 689 690 ovf := &types.ProductSectionList{ 691 Xmlns: types.XMLNamespaceVCloud, 692 Ovf: types.XMLNamespaceOVF, 693 ProductSection: vapp.VApp.Children.VM[0].ProductSection, 694 } 695 696 apiEndpoint := urlParseRequestURI(vapp.VApp.Children.VM[0].HREF) 697 apiEndpoint.Path += "/productSections" 698 699 // Return the task 700 return vapp.client.ExecuteTaskRequest(apiEndpoint.String(), http.MethodPut, 701 types.MimeProductSection, "error setting ovf: %s", ovf) 702 } 703 704 func (vapp *VApp) ChangeNetworkConfig(networks []map[string]interface{}, ip string) (Task, error) { 705 err := vapp.Refresh() 706 if err != nil { 707 return Task{}, fmt.Errorf("error refreshing VM before running customization: %s", err) 708 } 709 710 if vapp.VApp.Children == nil { 711 return Task{}, fmt.Errorf("vApp doesn't contain any children, interrupting customization") 712 } 713 714 networksection, err := vapp.GetNetworkConnectionSection() 715 if err != nil { 716 return Task{}, err 717 } 718 719 for index, network := range networks { 720 // Determine what type of address is requested for the vApp 721 ipAllocationMode := types.IPAllocationModeNone 722 ipAddress := "Any" 723 724 // TODO: Review current behaviour of using DHCP when left blank 725 if ip == "" || ip == "dhcp" || network["ip"] == "dhcp" { 726 ipAllocationMode = types.IPAllocationModeDHCP 727 } else if ip == "allocated" || network["ip"] == "allocated" { 728 ipAllocationMode = types.IPAllocationModePool 729 } else if ip == "none" || network["ip"] == "none" { 730 ipAllocationMode = types.IPAllocationModeNone 731 } else if ip != "" || network["ip"] != "" { 732 ipAllocationMode = types.IPAllocationModeManual 733 // TODO: Check a valid IP has been given 734 ipAddress = ip 735 } 736 737 util.Logger.Printf("[DEBUG] Function ChangeNetworkConfig() for %s invoked", network["orgnetwork"]) 738 739 networksection.Xmlns = types.XMLNamespaceVCloud 740 networksection.Ovf = types.XMLNamespaceOVF 741 networksection.Info = "Specifies the available VM network connections" 742 743 networksection.NetworkConnection[index].NeedsCustomization = true 744 networksection.NetworkConnection[index].IPAddress = ipAddress 745 networksection.NetworkConnection[index].IPAddressAllocationMode = ipAllocationMode 746 networksection.NetworkConnection[index].MACAddress = "" 747 748 if network["is_primary"] == true { 749 networksection.PrimaryNetworkConnectionIndex = index 750 } 751 752 } 753 754 apiEndpoint := urlParseRequestURI(vapp.VApp.Children.VM[0].HREF) 755 apiEndpoint.Path += "/networkConnectionSection/" 756 757 // Return the task 758 return vapp.client.ExecuteTaskRequest(apiEndpoint.String(), http.MethodPut, 759 types.MimeNetworkConnectionSection, "error changing network config: %s", networksection) 760 } 761 762 // Deprecated as it changes only first VM's memory 763 func (vapp *VApp) ChangeMemorySize(size int) (Task, error) { 764 765 err := vapp.Refresh() 766 if err != nil { 767 return Task{}, fmt.Errorf("error refreshing vApp before running customization: %s", err) 768 } 769 770 // Check if VApp Children is populated 771 if vapp.VApp.Children == nil { 772 return Task{}, fmt.Errorf("vApp doesn't contain any children, interrupting customization") 773 } 774 775 newMem := &types.OVFItem{ 776 XmlnsRasd: types.XMLNamespaceRASD, 777 XmlnsVCloud: types.XMLNamespaceVCloud, 778 XmlnsXsi: types.XMLNamespaceXSI, 779 VCloudHREF: vapp.VApp.Children.VM[0].HREF + "/virtualHardwareSection/memory", 780 VCloudType: types.MimeRasdItem, 781 AllocationUnits: "byte * 2^20", 782 Description: "Memory Size", 783 ElementName: strconv.Itoa(size) + " MB of memory", 784 InstanceID: 5, 785 Reservation: 0, 786 ResourceType: types.ResourceTypeMemory, 787 VirtualQuantity: int64(size), 788 Weight: 0, 789 Link: &types.Link{ 790 HREF: vapp.VApp.Children.VM[0].HREF + "/virtualHardwareSection/memory", 791 Rel: "edit", 792 Type: types.MimeRasdItem, 793 }, 794 } 795 796 apiEndpoint := urlParseRequestURI(vapp.VApp.Children.VM[0].HREF) 797 apiEndpoint.Path += "/virtualHardwareSection/memory" 798 799 // Return the task 800 return vapp.client.ExecuteTaskRequest(apiEndpoint.String(), http.MethodPut, 801 types.MimeRasdItem, "error changing memory size: %s", newMem) 802 } 803 804 func (vapp *VApp) GetNetworkConfig() (*types.NetworkConfigSection, error) { 805 806 networkConfig := &types.NetworkConfigSection{} 807 808 if vapp.VApp.HREF == "" { 809 return networkConfig, fmt.Errorf("cannot refresh, Object is empty") 810 } 811 812 _, err := vapp.client.ExecuteRequest(vapp.VApp.HREF+"/networkConfigSection/", http.MethodGet, 813 types.MimeNetworkConfigSection, "error retrieving network config: %s", nil, networkConfig) 814 815 // The request was successful 816 return networkConfig, err 817 } 818 819 // AddRAWNetworkConfig adds existing VDC network to vApp 820 // Deprecated: in favor of vapp.AddOrgNetwork 821 func (vapp *VApp) AddRAWNetworkConfig(orgvdcnetworks []*types.OrgVDCNetwork) (Task, error) { 822 823 vAppNetworkConfig, err := vapp.GetNetworkConfig() 824 if err != nil { 825 return Task{}, fmt.Errorf("error getting vApp networks: %s", err) 826 } 827 networkConfigurations := vAppNetworkConfig.NetworkConfig 828 829 for _, network := range orgvdcnetworks { 830 networkConfigurations = append(networkConfigurations, 831 types.VAppNetworkConfiguration{ 832 NetworkName: network.Name, 833 Configuration: &types.NetworkConfiguration{ 834 ParentNetwork: &types.Reference{ 835 HREF: network.HREF, 836 }, 837 FenceMode: types.FenceModeBridged, 838 }, 839 }, 840 ) 841 } 842 843 return updateNetworkConfigurations(vapp, networkConfigurations) 844 } 845 846 // Function allows to create isolated network for vApp. This is equivalent to vCD UI function - vApp network creation. 847 // Deprecated: in favor of vapp.CreateVappNetwork 848 func (vapp *VApp) AddIsolatedNetwork(newIsolatedNetworkSettings *VappNetworkSettings) (Task, error) { 849 850 err := validateNetworkConfigSettings(newIsolatedNetworkSettings) 851 if err != nil { 852 return Task{}, err 853 } 854 855 // for case when range is one ip address 856 if newIsolatedNetworkSettings.DhcpSettings != nil && newIsolatedNetworkSettings.DhcpSettings.IPRange != nil && newIsolatedNetworkSettings.DhcpSettings.IPRange.EndAddress == "" { 857 newIsolatedNetworkSettings.DhcpSettings.IPRange.EndAddress = newIsolatedNetworkSettings.DhcpSettings.IPRange.StartAddress 858 } 859 860 // only add values if available. Won't be send to API if not provided 861 var networkFeatures *types.NetworkFeatures 862 if newIsolatedNetworkSettings.DhcpSettings != nil { 863 networkFeatures = &types.NetworkFeatures{DhcpService: &types.DhcpService{ 864 IsEnabled: newIsolatedNetworkSettings.DhcpSettings.IsEnabled, 865 DefaultLeaseTime: newIsolatedNetworkSettings.DhcpSettings.DefaultLeaseTime, 866 MaxLeaseTime: newIsolatedNetworkSettings.DhcpSettings.MaxLeaseTime, 867 IPRange: newIsolatedNetworkSettings.DhcpSettings.IPRange}} 868 } 869 870 networkConfigurations := vapp.VApp.NetworkConfigSection.NetworkConfig 871 networkConfigurations = append(networkConfigurations, 872 types.VAppNetworkConfiguration{ 873 NetworkName: newIsolatedNetworkSettings.Name, 874 Description: newIsolatedNetworkSettings.Description, 875 Configuration: &types.NetworkConfiguration{ 876 FenceMode: types.FenceModeIsolated, 877 GuestVlanAllowed: newIsolatedNetworkSettings.GuestVLANAllowed, 878 Features: networkFeatures, 879 IPScopes: &types.IPScopes{IPScope: []*types.IPScope{&types.IPScope{IsInherited: false, Gateway: newIsolatedNetworkSettings.Gateway, 880 Netmask: newIsolatedNetworkSettings.NetMask, DNS1: newIsolatedNetworkSettings.DNS1, 881 DNS2: newIsolatedNetworkSettings.DNS2, DNSSuffix: newIsolatedNetworkSettings.DNSSuffix, IsEnabled: true, 882 IPRanges: &types.IPRanges{IPRange: newIsolatedNetworkSettings.StaticIPRanges}}}}, 883 }, 884 IsDeployed: false, 885 }) 886 887 return updateNetworkConfigurations(vapp, networkConfigurations) 888 889 } 890 891 // CreateVappNetwork creates isolated or nat routed(connected to Org VDC network) network for vApp. 892 // Returns pointer to types.NetworkConfigSection or error 893 // If orgNetwork is nil, then isolated network created. 894 func (vapp *VApp) CreateVappNetwork(newNetworkSettings *VappNetworkSettings, orgNetwork *types.OrgVDCNetwork) (*types.NetworkConfigSection, error) { 895 task, err := vapp.CreateVappNetworkAsync(newNetworkSettings, orgNetwork) 896 if err != nil { 897 return nil, err 898 } 899 err = task.WaitTaskCompletion() 900 if err != nil { 901 return nil, fmt.Errorf("%s", combinedTaskErrorMessage(task.Task, err)) 902 } 903 904 vAppNetworkConfig, err := vapp.GetNetworkConfig() 905 if err != nil { 906 return nil, fmt.Errorf("error getting vApp networks: %#v", err) 907 } 908 909 return vAppNetworkConfig, nil 910 } 911 912 // CreateVappNetworkAsync creates asynchronously isolated or nat routed network for vApp. Returns Task or error 913 // If orgNetwork is nil, then isolated network created. 914 func (vapp *VApp) CreateVappNetworkAsync(newNetworkSettings *VappNetworkSettings, orgNetwork *types.OrgVDCNetwork) (Task, error) { 915 916 err := validateNetworkConfigSettings(newNetworkSettings) 917 if err != nil { 918 return Task{}, err 919 } 920 921 // for case when range is one ip address 922 if newNetworkSettings.DhcpSettings != nil && newNetworkSettings.DhcpSettings.IPRange != nil && newNetworkSettings.DhcpSettings.IPRange.EndAddress == "" { 923 newNetworkSettings.DhcpSettings.IPRange.EndAddress = newNetworkSettings.DhcpSettings.IPRange.StartAddress 924 } 925 926 // only add values if available. Won't be send to API if not provided 927 var networkFeatures *types.NetworkFeatures 928 if newNetworkSettings.DhcpSettings != nil { 929 networkFeatures = &types.NetworkFeatures{DhcpService: &types.DhcpService{ 930 IsEnabled: newNetworkSettings.DhcpSettings.IsEnabled, 931 DefaultLeaseTime: newNetworkSettings.DhcpSettings.DefaultLeaseTime, 932 MaxLeaseTime: newNetworkSettings.DhcpSettings.MaxLeaseTime, 933 IPRange: newNetworkSettings.DhcpSettings.IPRange}, 934 } 935 } 936 937 networkConfigurations := vapp.VApp.NetworkConfigSection.NetworkConfig 938 vappConfiguration := types.VAppNetworkConfiguration{ 939 NetworkName: newNetworkSettings.Name, 940 Description: newNetworkSettings.Description, 941 Configuration: &types.NetworkConfiguration{ 942 FenceMode: types.FenceModeIsolated, 943 GuestVlanAllowed: newNetworkSettings.GuestVLANAllowed, 944 Features: networkFeatures, 945 IPScopes: &types.IPScopes{ 946 IPScope: []*types.IPScope{{ 947 IsInherited: false, 948 Gateway: newNetworkSettings.Gateway, 949 Netmask: newNetworkSettings.NetMask, 950 SubnetPrefixLength: newNetworkSettings.SubnetPrefixLength, 951 DNS1: newNetworkSettings.DNS1, 952 DNS2: newNetworkSettings.DNS2, 953 DNSSuffix: newNetworkSettings.DNSSuffix, 954 IsEnabled: true, 955 IPRanges: &types.IPRanges{IPRange: newNetworkSettings.StaticIPRanges}}}}, 956 RetainNetInfoAcrossDeployments: newNetworkSettings.RetainIpMacEnabled, 957 }, 958 IsDeployed: false, 959 } 960 961 if orgNetwork != nil { 962 vappConfiguration.Configuration.ParentNetwork = &types.Reference{ 963 HREF: orgNetwork.HREF, 964 } 965 vappConfiguration.Configuration.FenceMode = types.FenceModeNAT 966 } 967 968 networkConfigurations = append(networkConfigurations, 969 vappConfiguration) 970 971 return updateNetworkConfigurations(vapp, networkConfigurations) 972 } 973 974 // AddOrgNetwork adds Org VDC network as vApp network. 975 // Returns pointer to types.NetworkConfigSection or error 976 func (vapp *VApp) AddOrgNetwork(newNetworkSettings *VappNetworkSettings, orgNetwork *types.OrgVDCNetwork, isFenced bool) (*types.NetworkConfigSection, error) { 977 task, err := vapp.AddOrgNetworkAsync(newNetworkSettings, orgNetwork, isFenced) 978 if err != nil { 979 return nil, err 980 } 981 err = task.WaitTaskCompletion() 982 if err != nil { 983 return nil, fmt.Errorf("%s", combinedTaskErrorMessage(task.Task, err)) 984 } 985 986 vAppNetworkConfig, err := vapp.GetNetworkConfig() 987 if err != nil { 988 return nil, fmt.Errorf("error getting vApp networks: %#v", err) 989 } 990 991 return vAppNetworkConfig, nil 992 } 993 994 // AddOrgNetworkAsync adds asynchronously Org VDC network as vApp network. Returns Task or error 995 func (vapp *VApp) AddOrgNetworkAsync(newNetworkSettings *VappNetworkSettings, orgNetwork *types.OrgVDCNetwork, isFenced bool) (Task, error) { 996 997 if orgNetwork == nil { 998 return Task{}, errors.New("org VDC network is missing") 999 } 1000 1001 fenceMode := types.FenceModeBridged 1002 if isFenced { 1003 fenceMode = types.FenceModeNAT 1004 } 1005 1006 networkConfigurations := vapp.VApp.NetworkConfigSection.NetworkConfig 1007 vappConfiguration := types.VAppNetworkConfiguration{ 1008 NetworkName: orgNetwork.Name, 1009 Configuration: &types.NetworkConfiguration{ 1010 FenceMode: fenceMode, 1011 ParentNetwork: &types.Reference{ 1012 HREF: orgNetwork.HREF, 1013 }, 1014 RetainNetInfoAcrossDeployments: newNetworkSettings.RetainIpMacEnabled, 1015 }, 1016 IsDeployed: false, 1017 } 1018 networkConfigurations = append(networkConfigurations, 1019 vappConfiguration) 1020 1021 return updateNetworkConfigurations(vapp, networkConfigurations) 1022 1023 } 1024 1025 // UpdateNetwork updates vApp networks (isolated or connected to Org VDC network) 1026 // Returns pointer to types.NetworkConfigSection or error 1027 func (vapp *VApp) UpdateNetwork(newNetworkSettings *VappNetworkSettings, orgNetwork *types.OrgVDCNetwork) (*types.NetworkConfigSection, error) { 1028 task, err := vapp.UpdateNetworkAsync(newNetworkSettings, orgNetwork) 1029 if err != nil { 1030 return nil, err 1031 } 1032 err = task.WaitTaskCompletion() 1033 if err != nil { 1034 return nil, fmt.Errorf("%s", combinedTaskErrorMessage(task.Task, err)) 1035 } 1036 1037 vAppNetworkConfig, err := vapp.GetNetworkConfig() 1038 if err != nil { 1039 return nil, fmt.Errorf("error getting vApp networks: %#v", err) 1040 } 1041 1042 return vAppNetworkConfig, nil 1043 } 1044 1045 // UpdateNetworkAsync asynchronously updates vApp networks (isolated or connected to Org VDC network). 1046 // Returns task or error 1047 func (vapp *VApp) UpdateNetworkAsync(networkSettingsToUpdate *VappNetworkSettings, orgNetwork *types.OrgVDCNetwork) (Task, error) { 1048 util.Logger.Printf("[TRACE] UpdateNetworkAsync with values: %#v and connect to org network: %#v", networkSettingsToUpdate, orgNetwork) 1049 currentNetworkConfiguration, err := vapp.GetNetworkConfig() 1050 if err != nil { 1051 return Task{}, err 1052 } 1053 var networkToUpdate types.VAppNetworkConfiguration 1054 var networkToUpdateIndex int 1055 for index, networkConfig := range currentNetworkConfiguration.NetworkConfig { 1056 if networkConfig.Link != nil { 1057 uuid, err := GetUuidFromHref(networkConfig.Link.HREF, false) 1058 if err != nil { 1059 return Task{}, err 1060 } 1061 if uuid == extractUuid(networkSettingsToUpdate.ID) { 1062 networkToUpdate = networkConfig 1063 networkToUpdateIndex = index 1064 break 1065 } 1066 } 1067 } 1068 1069 if networkToUpdate == (types.VAppNetworkConfiguration{}) { 1070 return Task{}, fmt.Errorf("not found network to update with Id %s", networkSettingsToUpdate.ID) 1071 } 1072 if networkToUpdate.Configuration == nil { 1073 networkToUpdate.Configuration = &types.NetworkConfiguration{} 1074 } 1075 networkToUpdate.Configuration.RetainNetInfoAcrossDeployments = networkSettingsToUpdate.RetainIpMacEnabled 1076 // new network to connect 1077 if networkToUpdate.Configuration.ParentNetwork == nil && orgNetwork != nil { 1078 networkToUpdate.Configuration.FenceMode = types.FenceModeNAT 1079 networkToUpdate.Configuration.ParentNetwork = &types.Reference{HREF: orgNetwork.HREF} 1080 } 1081 // change network to connect 1082 if networkToUpdate.Configuration.ParentNetwork != nil && orgNetwork != nil && networkToUpdate.Configuration.ParentNetwork.HREF != orgNetwork.HREF { 1083 networkToUpdate.Configuration.ParentNetwork = &types.Reference{HREF: orgNetwork.HREF} 1084 } 1085 // remove network to connect 1086 if orgNetwork == nil { 1087 networkToUpdate.Configuration.FenceMode = types.FenceModeIsolated 1088 networkToUpdate.Configuration.ParentNetwork = nil 1089 } 1090 networkToUpdate.Description = networkSettingsToUpdate.Description 1091 networkToUpdate.NetworkName = networkSettingsToUpdate.Name 1092 networkToUpdate.Configuration.GuestVlanAllowed = networkSettingsToUpdate.GuestVLANAllowed 1093 networkToUpdate.Configuration.IPScopes.IPScope[0].Gateway = networkSettingsToUpdate.Gateway 1094 networkToUpdate.Configuration.IPScopes.IPScope[0].Netmask = networkSettingsToUpdate.NetMask 1095 networkToUpdate.Configuration.IPScopes.IPScope[0].DNS1 = networkSettingsToUpdate.DNS1 1096 networkToUpdate.Configuration.IPScopes.IPScope[0].DNS2 = networkSettingsToUpdate.DNS2 1097 networkToUpdate.Configuration.IPScopes.IPScope[0].DNSSuffix = networkSettingsToUpdate.DNSSuffix 1098 networkToUpdate.Configuration.IPScopes.IPScope[0].IPRanges = &types.IPRanges{IPRange: networkSettingsToUpdate.StaticIPRanges} 1099 1100 // for case when range is one ip address 1101 if networkSettingsToUpdate.DhcpSettings != nil && networkSettingsToUpdate.DhcpSettings.IPRange != nil && networkSettingsToUpdate.DhcpSettings.IPRange.EndAddress == "" { 1102 networkSettingsToUpdate.DhcpSettings.IPRange.EndAddress = networkSettingsToUpdate.DhcpSettings.IPRange.StartAddress 1103 } 1104 1105 if networkToUpdate.Configuration.Features == nil { 1106 networkToUpdate.Configuration.Features = &types.NetworkFeatures{} 1107 } 1108 1109 // remove DHCP config 1110 if networkSettingsToUpdate.DhcpSettings == nil { 1111 networkToUpdate.Configuration.Features.DhcpService = nil 1112 } 1113 1114 // create DHCP config 1115 if networkSettingsToUpdate.DhcpSettings != nil && networkToUpdate.Configuration.Features.DhcpService == nil { 1116 networkToUpdate.Configuration.Features.DhcpService = &types.DhcpService{ 1117 IsEnabled: networkSettingsToUpdate.DhcpSettings.IsEnabled, 1118 DefaultLeaseTime: networkSettingsToUpdate.DhcpSettings.DefaultLeaseTime, 1119 MaxLeaseTime: networkSettingsToUpdate.DhcpSettings.MaxLeaseTime, 1120 IPRange: networkSettingsToUpdate.DhcpSettings.IPRange} 1121 } 1122 1123 // update DHCP config 1124 if networkSettingsToUpdate.DhcpSettings != nil && networkToUpdate.Configuration.Features.DhcpService != nil { 1125 networkToUpdate.Configuration.Features.DhcpService.IsEnabled = networkSettingsToUpdate.DhcpSettings.IsEnabled 1126 networkToUpdate.Configuration.Features.DhcpService.DefaultLeaseTime = networkSettingsToUpdate.DhcpSettings.DefaultLeaseTime 1127 networkToUpdate.Configuration.Features.DhcpService.MaxLeaseTime = networkSettingsToUpdate.DhcpSettings.MaxLeaseTime 1128 networkToUpdate.Configuration.Features.DhcpService.IPRange = networkSettingsToUpdate.DhcpSettings.IPRange 1129 } 1130 1131 currentNetworkConfiguration.NetworkConfig[networkToUpdateIndex] = networkToUpdate 1132 1133 return updateNetworkConfigurations(vapp, currentNetworkConfiguration.NetworkConfig) 1134 } 1135 1136 // UpdateOrgNetwork updates Org VDC network which is part of a vApp 1137 // Returns pointer to types.NetworkConfigSection or error 1138 func (vapp *VApp) UpdateOrgNetwork(newNetworkSettings *VappNetworkSettings, isFenced bool) (*types.NetworkConfigSection, error) { 1139 task, err := vapp.UpdateOrgNetworkAsync(newNetworkSettings, isFenced) 1140 if err != nil { 1141 return nil, err 1142 } 1143 err = task.WaitTaskCompletion() 1144 if err != nil { 1145 return nil, fmt.Errorf("%s", combinedTaskErrorMessage(task.Task, err)) 1146 } 1147 1148 vAppNetworkConfig, err := vapp.GetNetworkConfig() 1149 if err != nil { 1150 return nil, fmt.Errorf("error getting vApp networks: %#v", err) 1151 } 1152 1153 return vAppNetworkConfig, nil 1154 } 1155 1156 // UpdateOrgNetworkAsync asynchronously updates Org VDC network which is part of a vApp 1157 // Returns task or error 1158 func (vapp *VApp) UpdateOrgNetworkAsync(networkSettingsToUpdate *VappNetworkSettings, isFenced bool) (Task, error) { 1159 util.Logger.Printf("[TRACE] UpdateOrgNetworkAsync with values: %#v ", networkSettingsToUpdate) 1160 currentNetworkConfiguration, err := vapp.GetNetworkConfig() 1161 if err != nil { 1162 return Task{}, err 1163 } 1164 var networkToUpdate types.VAppNetworkConfiguration 1165 var networkToUpdateIndex int 1166 1167 for index, networkConfig := range currentNetworkConfiguration.NetworkConfig { 1168 if networkConfig.Link != nil { 1169 uuid, err := GetUuidFromHref(networkConfig.Link.HREF, false) 1170 if err != nil { 1171 return Task{}, err 1172 } 1173 1174 if uuid == extractUuid(networkSettingsToUpdate.ID) { 1175 networkToUpdate = networkConfig 1176 networkToUpdateIndex = index 1177 break 1178 } 1179 } 1180 } 1181 1182 if networkToUpdate == (types.VAppNetworkConfiguration{}) { 1183 return Task{}, fmt.Errorf("not found network to update with Id %s", networkSettingsToUpdate.ID) 1184 } 1185 1186 fenceMode := types.FenceModeBridged 1187 if isFenced { 1188 fenceMode = types.FenceModeNAT 1189 } 1190 1191 if networkToUpdate.Configuration == nil { 1192 networkToUpdate.Configuration = &types.NetworkConfiguration{} 1193 } 1194 networkToUpdate.Configuration.RetainNetInfoAcrossDeployments = networkSettingsToUpdate.RetainIpMacEnabled 1195 networkToUpdate.Configuration.FenceMode = fenceMode 1196 1197 currentNetworkConfiguration.NetworkConfig[networkToUpdateIndex] = networkToUpdate 1198 1199 return updateNetworkConfigurations(vapp, currentNetworkConfiguration.NetworkConfig) 1200 } 1201 1202 func validateNetworkConfigSettings(networkSettings *VappNetworkSettings) error { 1203 if networkSettings.Name == "" { 1204 return errors.New("network name is missing") 1205 } 1206 1207 if networkSettings.Gateway == "" { 1208 return errors.New("network gateway IP is missing") 1209 } 1210 1211 if networkSettings.NetMask == "" && networkSettings.SubnetPrefixLength == "" { 1212 return errors.New("network mask and subnet prefix length config is missing, exactly one is required") 1213 } 1214 1215 if networkSettings.NetMask != "" && networkSettings.SubnetPrefixLength != "" { 1216 return errors.New("exactly one of netmask and prefix length can be supplied") 1217 } 1218 1219 if networkSettings.DhcpSettings != nil && networkSettings.DhcpSettings.IPRange == nil { 1220 return errors.New("network DHCP ip range config is missing") 1221 } 1222 1223 if networkSettings.DhcpSettings != nil && networkSettings.DhcpSettings.IPRange.StartAddress == "" { 1224 return errors.New("network DHCP ip range start address is missing") 1225 } 1226 1227 return nil 1228 } 1229 1230 // RemoveNetwork removes any network (be it isolated or connected to an Org Network) from vApp 1231 // Returns pointer to types.NetworkConfigSection or error 1232 func (vapp *VApp) RemoveNetwork(identifier string) (*types.NetworkConfigSection, error) { 1233 task, err := vapp.RemoveNetworkAsync(identifier) 1234 if err != nil { 1235 return nil, err 1236 } 1237 err = task.WaitTaskCompletion() 1238 if err != nil { 1239 return nil, fmt.Errorf("%s", combinedTaskErrorMessage(task.Task, err)) 1240 } 1241 1242 vAppNetworkConfig, err := vapp.GetNetworkConfig() 1243 if err != nil { 1244 return nil, fmt.Errorf("error getting vApp networks: %#v", err) 1245 } 1246 1247 return vAppNetworkConfig, nil 1248 } 1249 1250 // RemoveNetworkAsync asynchronously removes any network (be it isolated or connected to an Org Network) from vApp 1251 // Accepts network ID or name 1252 func (vapp *VApp) RemoveNetworkAsync(identifier string) (Task, error) { 1253 1254 if identifier == "" { 1255 return Task{}, fmt.Errorf("network ID/name can't be empty") 1256 } 1257 1258 networkConfigurations := vapp.VApp.NetworkConfigSection.NetworkConfig 1259 for _, networkConfig := range networkConfigurations { 1260 networkId, err := GetUuidFromHref(networkConfig.Link.HREF, false) 1261 if err != nil { 1262 return Task{}, fmt.Errorf("unable to get network ID from HREF: %s", err) 1263 } 1264 if networkId == extractUuid(identifier) || networkConfig.NetworkName == identifier { 1265 deleteUrl := vapp.client.VCDHREF.String() + "/network/" + networkId 1266 errMessage := fmt.Sprintf("detaching vApp network %s (id '%s'): %%s", networkConfig.NetworkName, networkId) 1267 task, err := vapp.client.ExecuteTaskRequest(deleteUrl, http.MethodDelete, types.AnyXMLMime, errMessage, nil) 1268 if err != nil { 1269 return Task{}, err 1270 } 1271 1272 return task, nil 1273 } 1274 } 1275 1276 return Task{}, fmt.Errorf("network to remove %s, wasn't found", identifier) 1277 1278 } 1279 1280 // Removes vApp isolated network 1281 // Deprecated: in favor vapp.RemoveNetwork 1282 func (vapp *VApp) RemoveIsolatedNetwork(networkName string) (Task, error) { 1283 1284 if networkName == "" { 1285 return Task{}, fmt.Errorf("network name can't be empty") 1286 } 1287 1288 networkConfigurations := vapp.VApp.NetworkConfigSection.NetworkConfig 1289 isNetworkFound := false 1290 for index, networkConfig := range networkConfigurations { 1291 if networkConfig.NetworkName == networkName { 1292 isNetworkFound = true 1293 networkConfigurations = append(networkConfigurations[:index], networkConfigurations[index+1:]...) 1294 } 1295 } 1296 1297 if !isNetworkFound { 1298 return Task{}, fmt.Errorf("network to remove %s, wasn't found", networkName) 1299 } 1300 1301 return updateNetworkConfigurations(vapp, networkConfigurations) 1302 } 1303 1304 // Function allows to update vApp network configuration. This works for updating, deleting and adding. 1305 // Network configuration has to be full with new, changed elements and unchanged. 1306 // https://opengrok.eng.vmware.com/source/xref/cloud-sp-main.perforce-shark.1700/sp-main/dev-integration/system-tests/SystemTests/src/main/java/com/vmware/cloud/systemtests/util/VAppNetworkUtils.java#createVAppNetwork 1307 // http://pubs.vmware.com/vcloud-api-1-5/wwhelp/wwhimpl/js/html/wwhelp.htm#href=api_prog/GUID-92622A15-E588-4FA1-92DA-A22A4757F2A0.html#1_14_12_10_1 1308 func updateNetworkConfigurations(vapp *VApp, networkConfigurations []types.VAppNetworkConfiguration) (Task, error) { 1309 util.Logger.Printf("[TRACE] updateNetworkConfigurations for vAPP: %#v and network config: %#v", vapp, networkConfigurations) 1310 networkConfig := &types.NetworkConfigSection{ 1311 Info: "Configuration parameters for logical networks", 1312 Ovf: types.XMLNamespaceOVF, 1313 Type: types.MimeNetworkConfigSection, 1314 Xmlns: types.XMLNamespaceVCloud, 1315 NetworkConfig: networkConfigurations, 1316 } 1317 1318 apiEndpoint := urlParseRequestURI(vapp.VApp.HREF) 1319 apiEndpoint.Path += "/networkConfigSection/" 1320 1321 // Return the task 1322 return vapp.client.ExecuteTaskRequest(apiEndpoint.String(), http.MethodPut, 1323 types.MimeNetworkConfigSection, "error updating vApp Network: %s", networkConfig) 1324 } 1325 1326 // RemoveAllNetworks detaches all networks from vApp 1327 func (vapp *VApp) RemoveAllNetworks() (Task, error) { 1328 return updateNetworkConfigurations(vapp, []types.VAppNetworkConfiguration{}) 1329 } 1330 1331 // SetProductSectionList sets product section for a vApp. It allows to change vApp guest properties. 1332 // 1333 // The slice of properties "ProductSectionList.ProductSection.Property" is not necessarily ordered 1334 // or returned as set before 1335 func (vapp *VApp) SetProductSectionList(productSection *types.ProductSectionList) (*types.ProductSectionList, error) { 1336 err := setProductSectionList(vapp.client, vapp.VApp.HREF, productSection) 1337 if err != nil { 1338 return nil, fmt.Errorf("unable to set vApp product section: %s", err) 1339 } 1340 1341 return vapp.GetProductSectionList() 1342 } 1343 1344 // GetProductSectionList retrieves product section for a vApp. It allows to read vApp guest properties. 1345 // 1346 // The slice of properties "ProductSectionList.ProductSection.Property" is not necessarily ordered 1347 // or returned as set before 1348 func (vapp *VApp) GetProductSectionList() (*types.ProductSectionList, error) { 1349 return getProductSectionList(vapp.client, vapp.VApp.HREF) 1350 } 1351 1352 // GetVMByName returns a VM reference if the VM name matches an existing one. 1353 // If no valid VM is found, it returns a nil VM reference and an error 1354 func (vapp *VApp) GetVMByName(vmName string, refresh bool) (*VM, error) { 1355 if refresh { 1356 err := vapp.Refresh() 1357 if err != nil { 1358 return nil, fmt.Errorf("error refreshing vApp: %s", err) 1359 } 1360 } 1361 1362 //vApp Might Not Have Any VMs 1363 if vapp.VApp.Children == nil { 1364 return nil, ErrorEntityNotFound 1365 } 1366 1367 util.Logger.Printf("[TRACE] Looking for VM: %s", vmName) 1368 for _, child := range vapp.VApp.Children.VM { 1369 1370 util.Logger.Printf("[TRACE] Looking at: %s", child.Name) 1371 if child.Name == vmName { 1372 return vapp.client.GetVMByHref(child.HREF) 1373 } 1374 1375 } 1376 util.Logger.Printf("[TRACE] Couldn't find VM: %s", vmName) 1377 return nil, ErrorEntityNotFound 1378 } 1379 1380 // GetVMById returns a VM reference if the VM ID matches an existing one. 1381 // If no valid VM is found, it returns a nil VM reference and an error 1382 func (vapp *VApp) GetVMById(id string, refresh bool) (*VM, error) { 1383 if refresh { 1384 err := vapp.Refresh() 1385 if err != nil { 1386 return nil, fmt.Errorf("error refreshing vApp: %s", err) 1387 } 1388 } 1389 1390 //vApp Might Not Have Any VMs 1391 if vapp.VApp.Children == nil { 1392 return nil, ErrorEntityNotFound 1393 } 1394 1395 util.Logger.Printf("[TRACE] Looking for VM: %s", id) 1396 for _, child := range vapp.VApp.Children.VM { 1397 1398 util.Logger.Printf("[TRACE] Looking at: %s", child.Name) 1399 if equalIds(id, child.ID, child.HREF) { 1400 return vapp.client.GetVMByHref(child.HREF) 1401 } 1402 } 1403 util.Logger.Printf("[TRACE] Couldn't find VM: %s", id) 1404 return nil, ErrorEntityNotFound 1405 } 1406 1407 // GetVMByNameOrId returns a VM reference if either the VM name or ID matches an existing one. 1408 // If no valid VM is found, it returns a nil VM reference and an error 1409 func (vapp *VApp) GetVMByNameOrId(identifier string, refresh bool) (*VM, error) { 1410 getByName := func(name string, refresh bool) (interface{}, error) { return vapp.GetVMByName(name, refresh) } 1411 getById := func(id string, refresh bool) (interface{}, error) { return vapp.GetVMById(id, refresh) } 1412 entity, err := getEntityByNameOrId(getByName, getById, identifier, false) 1413 if entity == nil { 1414 return nil, err 1415 } 1416 return entity.(*VM), err 1417 } 1418 1419 // QueryVappList returns a list of all vApps in all the organizations available to the caller 1420 func (client *Client) QueryVappList() ([]*types.QueryResultVAppRecordType, error) { 1421 var vappList []*types.QueryResultVAppRecordType 1422 queryType := client.GetQueryType(types.QtVapp) 1423 params := map[string]string{ 1424 "type": queryType, 1425 "filterEncoded": "true", 1426 } 1427 vappResult, err := client.cumulativeQuery(queryType, nil, params) 1428 if err != nil { 1429 return nil, fmt.Errorf("error getting vApp list : %s", err) 1430 } 1431 vappList = vappResult.Results.VAppRecord 1432 if client.IsSysAdmin { 1433 vappList = vappResult.Results.AdminVAppRecord 1434 } 1435 return vappList, nil 1436 } 1437 1438 // getOrgInfo finds the organization to which the vApp belongs (through the VDC), and returns its name and ID 1439 func (vapp *VApp) getOrgInfo() (*TenantContext, error) { 1440 previous, exists := orgInfoCache[vapp.VApp.ID] 1441 if exists { 1442 return previous, nil 1443 } 1444 var err error 1445 vdc, err := vapp.GetParentVDC() 1446 if err != nil { 1447 return nil, err 1448 } 1449 return vdc.getTenantContext() 1450 } 1451 1452 // UpdateNameDescription can change the name and the description of a vApp 1453 // If name is empty, it is left unchanged. 1454 func (vapp *VApp) UpdateNameDescription(newName, newDescription string) error { 1455 if vapp == nil || vapp.VApp.HREF == "" { 1456 return fmt.Errorf("vApp or href cannot be empty") 1457 } 1458 1459 // Skip update if we are using the original values 1460 if (newName == vapp.VApp.Name || newName == "") && (newDescription == vapp.VApp.Description) { 1461 return nil 1462 } 1463 1464 opType := types.MimeRecomposeVappParams 1465 1466 href := "" 1467 for _, link := range vapp.VApp.Link { 1468 if link.Type == opType && link.Rel == "recompose" { 1469 href = link.HREF 1470 break 1471 } 1472 } 1473 1474 if href == "" { 1475 return fmt.Errorf("no appropriate link for update found for vApp %s", vapp.VApp.Name) 1476 } 1477 1478 if newName == "" { 1479 newName = vapp.VApp.Name 1480 } 1481 1482 recomposeParams := &types.SmallRecomposeVappParams{ 1483 XMLName: xml.Name{}, 1484 Ovf: types.XMLNamespaceOVF, 1485 Xsi: types.XMLNamespaceXSI, 1486 Xmlns: types.XMLNamespaceVCloud, 1487 Name: newName, 1488 Description: newDescription, 1489 Deploy: vapp.VApp.Deployed, 1490 } 1491 1492 task, err := vapp.client.ExecuteTaskRequest(href, http.MethodPost, 1493 opType, "error updating vapp: %s", recomposeParams) 1494 1495 if err != nil { 1496 return fmt.Errorf("unable to update vApp: %s", err) 1497 } 1498 1499 err = task.WaitTaskCompletion() 1500 if err != nil { 1501 return fmt.Errorf("task for updating vApp failed: %s", err) 1502 } 1503 return vapp.Refresh() 1504 } 1505 1506 // UpdateDescription changes the description of a vApp 1507 func (vapp *VApp) UpdateDescription(newDescription string) error { 1508 return vapp.UpdateNameDescription("", newDescription) 1509 } 1510 1511 // Rename changes the name of a vApp 1512 func (vapp *VApp) Rename(newName string) error { 1513 return vapp.UpdateNameDescription(newName, vapp.VApp.Description) 1514 } 1515 1516 func (vapp *VApp) getTenantContext() (*TenantContext, error) { 1517 parentVdc, err := vapp.GetParentVDC() 1518 if err != nil { 1519 return nil, err 1520 } 1521 return parentVdc.getTenantContext() 1522 } 1523 1524 // RenewLease updates the lease terms for the vApp 1525 func (vapp *VApp) RenewLease(deploymentLeaseInSeconds, storageLeaseInSeconds int) error { 1526 1527 href := "" 1528 if vapp.VApp.LeaseSettingsSection != nil { 1529 if vapp.VApp.LeaseSettingsSection.DeploymentLeaseInSeconds == deploymentLeaseInSeconds && 1530 vapp.VApp.LeaseSettingsSection.StorageLeaseInSeconds == storageLeaseInSeconds { 1531 // Requested parameters are the same as existing parameters: exit without updating 1532 return nil 1533 } 1534 href = vapp.VApp.LeaseSettingsSection.HREF 1535 } 1536 if href == "" { 1537 for _, link := range vapp.VApp.Link { 1538 if link.Rel == "edit" && link.Type == types.MimeLeaseSettingSection { 1539 href = link.HREF 1540 break 1541 } 1542 } 1543 } 1544 if href == "" { 1545 return fmt.Errorf("link to update lease settings not found for vApp %s", vapp.VApp.Name) 1546 } 1547 1548 var leaseSettings = types.UpdateLeaseSettingsSection{ 1549 HREF: href, 1550 XmlnsOvf: types.XMLNamespaceOVF, 1551 Xmlns: types.XMLNamespaceVCloud, 1552 OVFInfo: "Lease section settings", 1553 Type: types.MimeLeaseSettingSection, 1554 DeploymentLeaseInSeconds: &deploymentLeaseInSeconds, 1555 StorageLeaseInSeconds: &storageLeaseInSeconds, 1556 } 1557 1558 task, err := vapp.client.ExecuteTaskRequest(href, http.MethodPut, 1559 types.MimeLeaseSettingSection, "error updating vapp lease : %s", &leaseSettings) 1560 1561 if err != nil { 1562 return fmt.Errorf("unable to update vApp lease: %s", err) 1563 } 1564 1565 err = task.WaitTaskCompletion() 1566 if err != nil { 1567 return fmt.Errorf("task for updating vApp lease failed: %s", err) 1568 } 1569 return vapp.Refresh() 1570 } 1571 1572 // GetLease retrieves the lease terms for a vApp 1573 func (vapp *VApp) GetLease() (*types.LeaseSettingsSection, error) { 1574 1575 href := "" 1576 if vapp.VApp.LeaseSettingsSection != nil { 1577 href = vapp.VApp.LeaseSettingsSection.HREF 1578 } 1579 if href == "" { 1580 for _, link := range vapp.VApp.Link { 1581 if link.Type == types.MimeLeaseSettingSection { 1582 href = link.HREF 1583 break 1584 } 1585 } 1586 } 1587 if href == "" { 1588 return nil, fmt.Errorf("link to retrieve lease settings not found for vApp %s", vapp.VApp.Name) 1589 } 1590 var leaseSettings types.LeaseSettingsSection 1591 1592 _, err := vapp.client.ExecuteRequest(href, http.MethodGet, "", "error getting vApp lease info: %s", nil, &leaseSettings) 1593 1594 if err != nil { 1595 return nil, err 1596 } 1597 return &leaseSettings, nil 1598 }