Skip to content

Commit

Permalink
Standalone VM (#356)
Browse files Browse the repository at this point in the history
* Add types to support standalone VM
* Add functions to create and retrieve standalone VM
* Add functions to delete standalone VM
* Add tests for standalone VM
* Standardize 'VM' name in comments
* Add HREF to VdcComputePolicy struct
* Rename 'types.Vm' to 'types.VM'
* Add standaloneVm to cleanup function
  • Loading branch information
dataclouder authored Feb 23, 2021
1 parent d64d2b4 commit 6afe647
Show file tree
Hide file tree
Showing 17 changed files with 556 additions and 78 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ for all networks types for NSX-V and NSX-T backed VDCs [#354](https://github.com
* Added `NsxtImportableSwitch` structure with `GetNsxtImportableSwitchByName` and `GetAllNsxtImportableSwitches` to
lookup NSX-T segments for use in NSX-T Imported networks [#354](https://github.com/vmware/go-vcloud-director/pull/354)
* Added `vdc.IsNsxt` and `vdc.IsNsxv` methods to verify if VDC is backed by NSX-T or NSX-V [#354](https://github.com/vmware/go-vcloud-director/pull/354)
* Added types `types.CreateVmParams` and `types.InstantiateVmTemplateParams`
* Added Vdc methods `CreateStandaloneVMFromTemplate`, `CreateStandaloneVMFromTemplateAsync` `CreateStandaloneVm`, `CreateStandaloneVmAsync`
* Added Vdc methods `QueryVmByName`, `QueryVmById`, `QueryVmList`
* Added VM methods `Delete`, `DeleteAsync`

BREAKING CHANGES:
* Renamed `types.VM` to `types.Vm` to facilitate implementation of standalone VM

BUGS FIXED:
* Made IPAddress field for IPAddresses struct to array [#350](https://github.com/vmware/go-vcloud-director/pull/350)
Expand Down
2 changes: 1 addition & 1 deletion govcd/api.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2019 VMware, Inc. All rights reserved. Licensed under the Apache v2 License.
* Copyright 2021 VMware, Inc. All rights reserved. Licensed under the Apache v2 License.
*/

// Package govcd provides a simple binding for vCloud Director REST APIs.
Expand Down
24 changes: 21 additions & 3 deletions govcd/api_vcd_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// +build api openapi functional catalog vapp gateway network org query extnetwork task vm vdc system disk lb lbAppRule lbAppProfile lbServerPool lbServiceMonitor lbVirtualServer user search nsxv nsxt auth affinity ALL

/*
* Copyright 2019 VMware, Inc. All rights reserved. Licensed under the Apache v2 License.
* Copyright 2021 VMware, Inc. All rights reserved. Licensed under the Apache v2 License.
*/

package govcd
Expand Down Expand Up @@ -1033,6 +1033,24 @@ func (vcd *TestVCD) removeLeftoverEntities(entity CleanupEntity) {
vcd.infoCleanup(notDeletedMsg, entity.EntityType, entity.Name, err)
}
return
case "standaloneVm":
vm, err := vcd.vdc.QueryVmById(entity.Name) // The VM ID must be passed as Name
if IsNotFound(err) {
vcd.infoCleanup(notFoundMsg, entity.EntityType, entity.Name)
return
}
if err != nil {
vcd.infoCleanup("removeLeftoverEntries: [ERROR] retrieving standalone VM '%s'. %s\n",
entity.Name, err)
return
}
err = vm.Delete()
if err != nil {
vcd.infoCleanup("removeLeftoverEntries: [ERROR] deleting VM '%s' : %s\n",
entity.Name, err)
return
}
vcd.infoCleanup(removedMsg, entity.EntityType, entity.Name, entity.CreatedBy)
case "vm":
vapp, err := vcd.vdc.GetVAppByName(entity.Parent, true)
if err != nil {
Expand Down Expand Up @@ -1635,13 +1653,13 @@ func Test_splitParent(t *testing.T) {
}
}

func (vcd *TestVCD) findFirstVm(vapp VApp) (types.VM, string) {
func (vcd *TestVCD) findFirstVm(vapp VApp) (types.Vm, string) {
for _, vm := range vapp.VApp.Children.VM {
if vm.Name != "" {
return *vm, vm.Name
}
}
return types.VM{}, ""
return types.Vm{}, ""
}

func (vcd *TestVCD) findFirstVapp() VApp {
Expand Down
9 changes: 5 additions & 4 deletions govcd/entity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ package govcd
import (
"fmt"
"reflect"
"strings"

. "gopkg.in/check.v1"
)
Expand Down Expand Up @@ -148,7 +149,7 @@ func (vcd *TestVCD) testFinderGetGenericEntity(def getterTestDefinition, check *
fmt.Printf("# Detected entity type %s\n", reflect.TypeOf(entity1))
}

check.Assert(reflect.TypeOf(entity1).String(), Equals, wantedType)
check.Assert(strings.ToLower(reflect.TypeOf(entity1).String()), Equals, strings.ToLower(wantedType))

check.Assert(entity1, NotNil)
check.Assert(entity1.name(), Equals, entityName)
Expand All @@ -165,7 +166,7 @@ func (vcd *TestVCD) testFinderGetGenericEntity(def getterTestDefinition, check *
check.Assert(entity2, NotNil)
check.Assert(entity2.name(), Equals, entityName)
check.Assert(entity2.id(), Equals, entityId)
check.Assert(reflect.TypeOf(entity2).String(), Equals, wantedType)
check.Assert(strings.ToLower(reflect.TypeOf(entity2).String()), Equals, strings.ToLower(wantedType))

// 3. Get the entity by Name or ID, using a known ID
if testVerbose {
Expand All @@ -178,7 +179,7 @@ func (vcd *TestVCD) testFinderGetGenericEntity(def getterTestDefinition, check *
check.Assert(entity3, NotNil)
check.Assert(entity3.name(), Equals, entityName)
check.Assert(entity3.id(), Equals, entityId)
check.Assert(reflect.TypeOf(entity3).String(), Equals, wantedType)
check.Assert(strings.ToLower(reflect.TypeOf(entity3).String()), Equals, strings.ToLower(wantedType))

// 4. Get the entity by Name or ID, using the entity name
if testVerbose {
Expand All @@ -191,7 +192,7 @@ func (vcd *TestVCD) testFinderGetGenericEntity(def getterTestDefinition, check *
check.Assert(entity4, NotNil)
check.Assert(entity4.name(), Equals, entityName)
check.Assert(entity4.id(), Equals, entityId)
check.Assert(reflect.TypeOf(entity4).String(), Equals, wantedType)
check.Assert(strings.ToLower(reflect.TypeOf(entity4).String()), Equals, strings.ToLower(wantedType))

// 5. Attempting a search by name with an invalid name
if testVerbose {
Expand Down
2 changes: 1 addition & 1 deletion govcd/filter_interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ func (vapp QueryVapp) GetMetadataValue(key string) string {
// --------------------------------------------------------------
func (vm QueryVm) GetHref() string { return vm.HREF }
func (vm QueryVm) GetName() string { return vm.Name }
func (vm QueryVm) GetType() string { return "VM" }
func (vm QueryVm) GetType() string { return "Vm" }
func (vm QueryVm) GetIp() string { return vm.IpAddress }
func (vm QueryVm) GetDate() string { return vm.DateCreated }
func (vm QueryVm) GetParentName() string { return vm.ContainerName }
Expand Down
19 changes: 18 additions & 1 deletion govcd/monitor.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2019 VMware, Inc. All rights reserved. Licensed under the Apache v2 License.
* Copyright 2021 VMware, Inc. All rights reserved. Licensed under the Apache v2 License.
*/

// Contains auxiliary functions to show library entities structure.
Expand Down Expand Up @@ -30,6 +30,7 @@ import (
// network
// externalNetwork
// vapp
// vm
// task
// Edge Gateway service configuration

Expand All @@ -53,6 +54,15 @@ func prettyVapp(vapp types.VApp) string {
return ""
}

// Returns a VM structure as JSON
func prettyVm(vm types.Vm) string {
byteBuf, err := json.MarshalIndent(vm, " ", " ")
if err == nil {
return fmt.Sprintf("%s\n", string(byteBuf))
}
return ""
}

// Returns an OrgUser structure as JSON
func prettyUser(user types.User) string {
byteBuf, err := json.MarshalIndent(user, " ", " ")
Expand Down Expand Up @@ -187,6 +197,13 @@ func ShowVapp(vapp types.VApp) {
out("screen", prettyVapp(vapp))
}

func LogVm(vm types.Vm) {
out("log", prettyVm(vm))
}

func ShowVm(vm types.Vm) {
out("screen", prettyVm(vm))
}
func ShowOrg(org types.Org) {
out("screen", prettyOrg(org))
}
Expand Down
5 changes: 2 additions & 3 deletions govcd/vapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,6 @@ func addNewVMW(vapp *VApp, name string, vappTemplate VAppTemplate,
// https://github.com/vmware/go-vcloud-director/issues/252
// ======================================================================
func (vapp *VApp) RemoveVM(vm VM) error {

err := vapp.Refresh()
if err != nil {
return fmt.Errorf("error refreshing vApp before removing VM: %s", err)
Expand Down Expand Up @@ -574,7 +573,7 @@ func (vapp *VApp) ChangeStorageProfile(name string) (Task, error) {
return Task{}, fmt.Errorf("error retrieving storage profile %s for vApp %s", name, vapp.VApp.Name)
}

newProfile := &types.VM{
newProfile := &types.Vm{
Name: vapp.VApp.Children.VM[0].Name,
StorageProfile: &storageProfileRef,
Xmlns: types.XMLNamespaceVCloud,
Expand All @@ -596,7 +595,7 @@ func (vapp *VApp) ChangeVMName(name string) (Task, error) {
return Task{}, fmt.Errorf("vApp doesn't contain any children, interrupting customization")
}

newName := &types.VM{
newName := &types.Vm{
Name: name,
Xmlns: types.XMLNamespaceVCloud,
}
Expand Down
2 changes: 1 addition & 1 deletion govcd/vapp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -737,7 +737,7 @@ func (vcd *TestVCD) Test_GetVM(check *C) {
var def = getterTestDefinition{
parentType: "VApp",
parentName: vapp.VApp.Name,
entityType: "VM",
entityType: "Vm",
entityName: vmName,
getByName: getByName,
getById: getById,
Expand Down
165 changes: 164 additions & 1 deletion govcd/vdc.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2019 VMware, Inc. All rights reserved. Licensed under the Apache v2 License.
* Copyright 2021 VMware, Inc. All rights reserved. Licensed under the Apache v2 License.
*/

package govcd
Expand Down Expand Up @@ -901,6 +901,169 @@ func (vdc *Vdc) GetVappList() []*types.ResourceReference {
return list
}

// CreateStandaloneVmAsync starts a standalone VM creation without a template, returning a task
func (vdc *Vdc) CreateStandaloneVmAsync(params *types.CreateVmParams) (Task, error) {
util.Logger.Printf("[TRACE] Vdc.CreateStandaloneVmAsync - Creating VM ")

if vdc.Vdc.HREF == "" {
return Task{}, fmt.Errorf("cannot create VM, Object VDC is empty")
}

href := ""
for _, link := range vdc.Vdc.Link {
if link.Type == types.MimeCreateVmParams && link.Rel == "add" {
href = link.HREF
break
}
}
if href == "" {
return Task{}, fmt.Errorf("error retrieving VM creation link from VDC %s", vdc.Vdc.Name)
}
if params == nil {
return Task{}, fmt.Errorf("empty parameters passed to standalone VM creation")
}
params.XmlnsOvf = types.XMLNamespaceOVF

return vdc.client.ExecuteTaskRequest(href, http.MethodPost, types.MimeCreateVmParams, "error creating standalone VM: %s", params)
}

// getVmFromTask finds a VM from a running standalone VM creation task
// It retrieves the VM owner (the hidden vApp), and from that one finds the new VM
func (vdc *Vdc) getVmFromTask(task Task, name string) (*VM, error) {
owner := task.Task.Owner.HREF
if owner == "" {
return nil, fmt.Errorf("task owner is null for VM %s", name)
}
vapp, err := vdc.GetVAppByHref(owner)
if err != nil {
return nil, err
}
if vapp.VApp.Children == nil {
return nil, ErrorEntityNotFound
}
if len(vapp.VApp.Children.VM) == 0 {
return nil, fmt.Errorf("vApp %s contains no VMs", vapp.VApp.Name)
}
if len(vapp.VApp.Children.VM) > 1 {
return nil, fmt.Errorf("vApp %s contains more than one VM", vapp.VApp.Name)
}
for _, child := range vapp.VApp.Children.VM {
util.Logger.Printf("[TRACE] Looking at: %s", child.Name)
return vapp.client.GetVMByHref(child.HREF)
}
return nil, ErrorEntityNotFound
}

// CreateStandaloneVm creates a standalone VM without a template
func (vdc *Vdc) CreateStandaloneVm(params *types.CreateVmParams) (*VM, error) {

task, err := vdc.CreateStandaloneVmAsync(params)
if err != nil {
return nil, err
}
err = task.WaitTaskCompletion()
if err != nil {
return nil, err
}
return vdc.getVmFromTask(task, params.Name)
}

// QueryVmByName finds a standalone VM by name
// The search fails either if there are more VMs with the wanted name, or if there are none
// It can also retrieve a standard VM (created from vApp)
func (vdc *Vdc) QueryVmByName(name string) (*VM, error) {
vmList, err := vdc.QueryVmList(types.VmQueryFilterOnlyDeployed)
if err != nil {
return nil, err
}
var foundVM []*types.QueryResultVMRecordType
for _, vm := range vmList {
if vm.Name == name {
foundVM = append(foundVM, vm)
}
}
if len(foundVM) == 0 {
return nil, ErrorEntityNotFound
}
if len(foundVM) > 1 {
return nil, fmt.Errorf("more than one VM found with name %s", name)
}
return vdc.client.GetVMByHref(foundVM[0].HREF)
}

// QueryVmById retrieves a standalone VM by ID
// It can also retrieve a standard VM (created from vApp)
func (vdc *Vdc) QueryVmById(id string) (*VM, error) {
vmList, err := vdc.QueryVmList(types.VmQueryFilterOnlyDeployed)
if err != nil {
return nil, err
}
var foundVM []*types.QueryResultVMRecordType
for _, vm := range vmList {
if equalIds(id, vm.ID, vm.HREF) {
foundVM = append(foundVM, vm)
}
}
if len(foundVM) == 0 {
return nil, ErrorEntityNotFound
}
if len(foundVM) > 1 {
return nil, fmt.Errorf("more than one VM found with ID %s", id)
}
return vdc.client.GetVMByHref(foundVM[0].HREF)
}

// CreateStandaloneVMFromTemplateAsync starts a standalone VM creation using a template
func (vdc *Vdc) CreateStandaloneVMFromTemplateAsync(params *types.InstantiateVmTemplateParams) (Task, error) {

util.Logger.Printf("[TRACE] Vdc.CreateStandaloneVMFromTemplateAsync - Creating VM")

if vdc.Vdc.HREF == "" {
return Task{}, fmt.Errorf("cannot create VM, provided VDC is empty")
}

href := ""
for _, link := range vdc.Vdc.Link {
if link.Type == types.MimeInstantiateVmTemplateParams && link.Rel == "add" {
href = link.HREF
break
}
}
if href == "" {
return Task{}, fmt.Errorf("error retrieving VM instantiate from template link from VDC %s", vdc.Vdc.Name)
}

if params.Name == "" {
return Task{}, fmt.Errorf("[CreateStandaloneVMFromTemplateAsync] missing VM name")
}
if params.SourcedVmTemplateItem == nil {
return Task{}, fmt.Errorf("[CreateStandaloneVMFromTemplateAsync] missing SourcedVmTemplateItem")
}
if params.SourcedVmTemplateItem.Source == nil {
return Task{}, fmt.Errorf("[CreateStandaloneVMFromTemplateAsync] missing vApp template Source")
}
if params.SourcedVmTemplateItem.Source.HREF == "" {
return Task{}, fmt.Errorf("[CreateStandaloneVMFromTemplateAsync] empty HREF in vApp template Source")
}
params.XmlnsOvf = types.XMLNamespaceOVF

return vdc.client.ExecuteTaskRequest(href, http.MethodPost, types.MimeInstantiateVmTemplateParams, "error creating standalone VM from template: %s", params)
}

// CreateStandaloneVMFromTemplate creates a standalone VM from a template
func (vdc *Vdc) CreateStandaloneVMFromTemplate(params *types.InstantiateVmTemplateParams) (*VM, error) {

task, err := vdc.CreateStandaloneVMFromTemplateAsync(params)
if err != nil {
return nil, err
}
err = task.WaitTaskCompletion()
if err != nil {
return nil, err
}
return vdc.getVmFromTask(task, params.Name)
}

// GetCapabilities allows to retrieve a list of VDC capabilities. It has a list of values. Some particularly useful are:
// * networkProvider - overlay stack responsible for providing network functionality. (NSX_V or NSX_T)
// * crossVdc - supports cross vDC network creation
Expand Down
2 changes: 2 additions & 0 deletions govcd/vdccomputepolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
// In UI called VM sizing policy. In API VDC compute policy
type VdcComputePolicy struct {
VdcComputePolicy *types.VdcComputePolicy
Href string
client *Client
}

Expand Down Expand Up @@ -48,6 +49,7 @@ func getVdcComputePolicyById(client *Client, id string) (*VdcComputePolicy, erro

vdcComputePolicy := &VdcComputePolicy{
VdcComputePolicy: &types.VdcComputePolicy{},
Href: urlRef.String(),
client: client,
}

Expand Down
Loading

0 comments on commit 6afe647

Please sign in to comment.