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

Add guest properties support for vm and vapp #235

Merged
merged 25 commits into from
Aug 29, 2019
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
* Added methods `AdminOrg.GetVDCByName` and related `GetVDCById`, `GetVDCByNameOrId`
* Added methods `AdminOrg.GetAdminVDCByName` and related `GetAdminVDCById`, `GetAdminVDCByNameOrId`
* Added methods `Catalog.Refresh` and `AdminCatalog.Refresh`
* Added methods `vm.SetProductSectionList` and `vm.GetProductSectionList` allowing to manipulate VM
guest properties [#235](https://github.com/vmware/go-vcloud-director/pull/235)
* Added methods `vapp.SetProductSectionList` and `vapp.GetProductSectionList` allowing to manipulate
vApp guest properties [#235](https://github.com/vmware/go-vcloud-director/pull/235)

IMPROVEMENTS:

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module github.com/vmware/go-vcloud-director/v2

require (
github.com/hashicorp/go-version v1.1.0
github.com/kr/pretty v0.1.0
github.com/kr/pretty v0.1.0 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127
gopkg.in/yaml.v2 v2.2.2
)
1 change: 0 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/vmware/go-vcloud-director v2.0.0+incompatible h1:3B121XZVdEOxRhv5ARswKVxXt4KznAbun8GoXNbbZWs=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
2 changes: 1 addition & 1 deletion govcd/lb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ func checkLb(queryUrl string, expectedResponses []string, maxRetryTimeout int) e
for {
select {
case <-timeoutAfter:
return fmt.Errorf("timed out waiting for all nodes to be up: %s", err)
return fmt.Errorf("timed out waiting for all nodes to be up")
case <-tick.C:
var resp *http.Response
resp, err = httpClient.Get(queryUrl)
Expand Down
53 changes: 53 additions & 0 deletions govcd/productsection.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright 2019 VMware, Inc. All rights reserved. Licensed under the Apache v2 License.
*/

package govcd

import (
"fmt"
"net/http"

"github.com/vmware/go-vcloud-director/v2/types/v56"
)

// setProductSectionList is a shared function for both vApp and VM
func setProductSectionList(client *Client, href string, productSection *types.ProductSectionList) error {
if href == "" {
return fmt.Errorf("href cannot be empty to set product section")
}

productSection.Xmlns = types.XMLNamespaceVCloud
productSection.Ovf = types.XMLNamespaceOVF

task, err := client.ExecuteTaskRequest(href+"/productSections", http.MethodPut,
types.MimeProductSection, "error setting product section: %s", productSection)

if err != nil {
return fmt.Errorf("unable to set product section: %s", err)
}

err = task.WaitTaskCompletion()
if err != nil {
return fmt.Errorf("task for setting product section failed: %s", err)
}

return nil
}

// getProductSectionList is a shared function for both vApp and VM
func getProductSectionList(client *Client, href string) (*types.ProductSectionList, error) {
if href == "" {
return nil, fmt.Errorf("href cannot be empty to get product section")
}
productSection := &types.ProductSectionList{}

_, err := client.ExecuteRequest(href+"/productSections", http.MethodGet,
types.MimeProductSection, "error retrieving product section : %s", nil, productSection)

if err != nil {
return nil, fmt.Errorf("unable to retrieve product section: %s", err)
}

return productSection, nil
}
26 changes: 25 additions & 1 deletion govcd/vapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,9 @@ func (vapp *VApp) ChangeVMName(name string) (Task, error) {
types.MimeVM, "error changing VM name: %s", newName)
}

// SetOvf sets guest properties for the first child VM in vApp
//
// Deprecated: Use vm.SetProductSectionList()
func (vapp *VApp) SetOvf(parameters map[string]string) (Task, error) {
err := vapp.Refresh()
if err != nil {
Expand Down Expand Up @@ -830,7 +833,28 @@ func updateNetworkConfigurations(vapp *VApp, networkConfigurations []types.VAppN
types.MimeNetworkConfigSection, "error updating vApp Network: %s", networkConfig)
}

// Function RemoveAllNetworks unattach all networks from VAPP
// RemoveAllNetworks detaches all networks from vApp
func (vapp *VApp) RemoveAllNetworks() (Task, error) {
return updateNetworkConfigurations(vapp, []types.VAppNetworkConfiguration{})
}

// SetProductSectionList sets product section for a vApp. It allows to change vApp guest properties.
//
// The slice of properties "ProductSectionList.ProductSection.Property" is not necessarily ordered
// or returned as set before
func (vapp *VApp) SetProductSectionList(productSection *types.ProductSectionList) (*types.ProductSectionList, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add a sentence - a hint - that this product section represents guest properties in all comments of the four public functions.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in "maniapulate", sounds funny though :D

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

err := setProductSectionList(vapp.client, vapp.VApp.HREF, productSection)
if err != nil {
return nil, fmt.Errorf("unable to set vApp product section: %s", err)
}

return vapp.GetProductSectionList()
}

// GetProductSectionList retrieves product section for a vApp. It allows to read vApp guest properties.
//
// The slice of properties "ProductSectionList.ProductSection.Property" is not necessarily ordered
// or returned as set before
func (vapp *VApp) GetProductSectionList() (*types.ProductSectionList, error) {
return getProductSectionList(vapp.client, vapp.VApp.HREF)
}
10 changes: 10 additions & 0 deletions govcd/vapp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -680,3 +680,13 @@ func (vcd *TestVCD) Test_RemoveAllNetworks(check *C) {
}
check.Assert(hasNetworks, Equals, false)
}

// Test_VappSetProductSectionList sets vApp product section, retrieves it and deeply matches if
// properties were properly set using a propertyTester helper.
func (vcd *TestVCD) Test_VappSetProductSectionList(check *C) {
if vcd.skipVappTests {
check.Skip("Skipping test because vapp was not successfully created at setup")
}
vapp := vcd.findFirstVapp()
propertyTester(vcd, check, &vapp)
}
94 changes: 94 additions & 0 deletions govcd/vapp_vm_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// +build vapp vm functional ALL

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

package govcd

import (
"github.com/vmware/go-vcloud-director/v2/types/v56"
. "gopkg.in/check.v1"
)

// guestPropertyGetSetter interface is used for covering tests in both VM and vApp guest property
type productSectionListGetSetter interface {
GetProductSectionList() (*types.ProductSectionList, error)
SetProductSectionList(productSection *types.ProductSectionList) (*types.ProductSectionList, error)
}

// propertyTester is a guest property setter accepting guestPropertyGetSetter interface for trying
// out settings on all objects implementing such interface
func propertyTester(vcd *TestVCD, check *C, object productSectionListGetSetter) {
productSection := &types.ProductSectionList{
ProductSection: &types.ProductSection{
Info: "Custom properties",
Property: []*types.Property{
&types.Property{
UserConfigurable: false,
Key: "sys_owner",
Label: "sys_owner_label",
Type: "string",
DefaultValue: "sys_owner_default",
Value: &types.Value{Value: "test"},
},
&types.Property{
UserConfigurable: true,
Key: "asset_tag",
Label: "asset_tag_label",
Type: "string",
DefaultValue: "asset_tag_default",
Value: &types.Value{Value: "xxxyyy"},
},
&types.Property{
UserConfigurable: true,
Key: "guestinfo.config.bootstrap.ip",
Label: "guestinfo.config.bootstrap.ip_label",
Type: "string",
DefaultValue: "default_ip",
Value: &types.Value{Value: "192.168.12.180"},
},
},
},
}

productSection.SortByPropertyKeyName()

gotproductSection, err := object.SetProductSectionList(productSection)
check.Assert(err, IsNil)
gotproductSection.SortByPropertyKeyName()

getproductSection, err := object.GetProductSectionList()
check.Assert(err, IsNil)
getproductSection.SortByPropertyKeyName()

// Check that values were set in API
vbauzys marked this conversation as resolved.
Show resolved Hide resolved
check.Assert(getproductSection, NotNil)
check.Assert(getproductSection.ProductSection, NotNil)
check.Assert(len(getproductSection.ProductSection.Property), Equals, 3)

check.Assert(getproductSection.ProductSection.Property[0].Key, Equals, "asset_tag")
check.Assert(getproductSection.ProductSection.Property[0].Label, Equals, "asset_tag_label")
check.Assert(getproductSection.ProductSection.Property[0].Type, Equals, "string")
check.Assert(getproductSection.ProductSection.Property[0].Value.Value, Equals, "xxxyyy")
check.Assert(getproductSection.ProductSection.Property[0].DefaultValue, Equals, "asset_tag_default")
check.Assert(getproductSection.ProductSection.Property[0].UserConfigurable, Equals, true)

check.Assert(getproductSection.ProductSection.Property[1].Key, Equals, "guestinfo.config.bootstrap.ip")
check.Assert(getproductSection.ProductSection.Property[1].Label, Equals, "guestinfo.config.bootstrap.ip_label")
check.Assert(getproductSection.ProductSection.Property[1].Type, Equals, "string")
check.Assert(getproductSection.ProductSection.Property[1].Value.Value, Equals, "192.168.12.180")
check.Assert(getproductSection.ProductSection.Property[1].DefaultValue, Equals, "default_ip")
check.Assert(getproductSection.ProductSection.Property[1].UserConfigurable, Equals, true)

check.Assert(getproductSection.ProductSection.Property[2].Key, Equals, "sys_owner")
check.Assert(getproductSection.ProductSection.Property[2].Label, Equals, "sys_owner_label")
check.Assert(getproductSection.ProductSection.Property[2].Type, Equals, "string")
check.Assert(getproductSection.ProductSection.Property[2].Value.Value, Equals, "test")
check.Assert(getproductSection.ProductSection.Property[2].DefaultValue, Equals, "sys_owner_default")
check.Assert(getproductSection.ProductSection.Property[2].UserConfigurable, Equals, false)

// Ensure the object are deeply equal
check.Assert(gotproductSection.ProductSection.Property, DeepEquals, productSection.ProductSection.Property)
check.Assert(getproductSection, DeepEquals, gotproductSection)
}
21 changes: 21 additions & 0 deletions govcd/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -723,3 +723,24 @@ func (vm *VM) ToggleHardwareVirtualization(isEnabled bool) (Task, error) {
return vm.client.ExecuteTaskRequest(apiEndpoint.String(), http.MethodPost,
"", errMessage, nil)
}

// SetProductSectionList sets product section for a VM. It allows to change VM guest properties.
//
// The slice of properties "ProductSectionList.ProductSection.Property" is not necessarily ordered
// or returned as set before
func (vm *VM) SetProductSectionList(productSection *types.ProductSectionList) (*types.ProductSectionList, error) {
err := setProductSectionList(vm.client, vm.VM.HREF, productSection)
if err != nil {
return nil, fmt.Errorf("unable to set VM product section: %s", err)
}

return vm.GetProductSectionList()
}

// GetProductSectionList retrieves product section for a VM. It allows to read VM guest properties.
//
// The slice of properties "ProductSectionList.ProductSection.Property" is not necessarily ordered
// or returned as set before
func (vm *VM) GetProductSectionList() (*types.ProductSectionList, error) {
return getProductSectionList(vm.client, vm.VM.HREF)
}
16 changes: 16 additions & 0 deletions govcd/vm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -981,3 +981,19 @@ func (vcd *TestVCD) Test_BlockWhileGuestCustomizationStatus(check *C) {
err = vm.BlockWhileGuestCustomizationStatus("invalid_GC_STATUS", 5)
check.Assert(err, IsNil)
}

// Test_VMSetProductSectionList sets product section, retrieves it and deeply matches if properties
// were properly set using a propertyTester helper.
func (vcd *TestVCD) Test_VMSetProductSectionList(check *C) {
if vcd.skipVappTests {
check.Skip("Skipping test because vapp was not successfully created at setup")
}
vapp := vcd.findFirstVapp()
vmType, vmName := vcd.findFirstVm(vapp)
if vmName == "" {
check.Skip("skipping test because no VM is found")
}
vm, err := vcd.client.Client.FindVMByHREF(vmType.HREF)
check.Assert(err, IsNil)
propertyTester(vcd, check, &vm)
}
102 changes: 102 additions & 0 deletions govcd/vm_unit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
package govcd

import (
"reflect"
"testing"

"github.com/vmware/go-vcloud-director/v2/types/v56"
Expand Down Expand Up @@ -270,3 +271,104 @@ func Test_VMupdateNicParameters_singleNIC(t *testing.T) {

}
}

// TestProductSectionList_SortByPropertyKeyName validates that a
// SortByPropertyKeyName() works on ProductSectionList and can handle empty properties as well as
// sort correctly
func TestProductSectionList_SortByPropertyKeyName(t *testing.T) {
sliceProductSection := &types.ProductSectionList{
ProductSection: &types.ProductSection{},
}

emptyProductSection := &types.ProductSectionList{
ProductSection: &types.ProductSection{
Info: "Custom properties",
},
}

// unordered list for test
sortOrder := &types.ProductSectionList{
ProductSection: &types.ProductSection{
Info: "Custom properties",
Property: []*types.Property{
&types.Property{
UserConfigurable: false,
Key: "sys_owner",
Label: "sys_owner_label",
Type: "string",
DefaultValue: "sys_owner_default",
Value: &types.Value{Value: "test"},
},
&types.Property{
UserConfigurable: true,
Key: "asset_tag",
Label: "asset_tag_label",
Type: "string",
DefaultValue: "asset_tag_default",
Value: &types.Value{Value: "xxxyyy"},
},
&types.Property{
UserConfigurable: true,
Key: "guestinfo.config.bootstrap.ip",
Label: "guestinfo.config.bootstrap.ip_label",
Type: "string",
DefaultValue: "default_ip",
Value: &types.Value{Value: "192.168.12.180"},
},
},
},
}
// correct state after ordering
expectedSortedOrder := &types.ProductSectionList{
ProductSection: &types.ProductSection{
Info: "Custom properties",
Property: []*types.Property{
&types.Property{
UserConfigurable: true,
Key: "asset_tag",
Label: "asset_tag_label",
Type: "string",
DefaultValue: "asset_tag_default",
Value: &types.Value{Value: "xxxyyy"},
},
&types.Property{
UserConfigurable: true,
Key: "guestinfo.config.bootstrap.ip",
Label: "guestinfo.config.bootstrap.ip_label",
Type: "string",
DefaultValue: "default_ip",
Value: &types.Value{Value: "192.168.12.180"},
},
&types.Property{
UserConfigurable: false,
Key: "sys_owner",
Label: "sys_owner_label",
Type: "string",
DefaultValue: "sys_owner_default",
Value: &types.Value{Value: "test"},
},
},
},
}

tests := []struct {
name string
setValue *types.ProductSectionList
expectedValue *types.ProductSectionList
}{
{name: "Empty", setValue: emptyProductSection, expectedValue: emptyProductSection},
{name: "Slice", setValue: sliceProductSection, expectedValue: sliceProductSection},
{name: "SortOrder", setValue: sortOrder, expectedValue: expectedSortedOrder},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := tt.setValue
p.SortByPropertyKeyName()

if !reflect.DeepEqual(p, tt.expectedValue) {
t.Errorf("Objects were not deeply equal: \n%#+v\n, got:\n %#+v\n", tt.expectedValue, p)
}

})
}
}
Loading