Skip to content

Commit

Permalink
Vm extra configuration (#666)
Browse files Browse the repository at this point in the history
* Add VM methods GetExtraConfig, UpdateExtraConfig, DeleteExtraConfig
* Add Changelog entry

---------
Signed-off-by: Giuseppe Maxia <giuseppe.maxia@broadcom.com>
Co-authored-by: Dainius Serplis <dserplis@vmware.com>
  • Loading branch information
dataclouder committed May 14, 2024
1 parent 2aed4dc commit 85e22f8
Show file tree
Hide file tree
Showing 6 changed files with 372 additions and 0 deletions.
1 change: 1 addition & 0 deletions .changes/v2.25.0/666-features.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* Added `VM` methods `GetExtraConfig`, `UpdateExtraConfig`, `DeleteExtraConfig` to manage VM extra-configuration [GH-666]
111 changes: 111 additions & 0 deletions govcd/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -2105,3 +2105,114 @@ func (vm *VM) ConsolidateDisks() error {
}
return task.WaitTaskCompletion()
}

// GetExtraConfig retrieves the extra configuration items from a VM
func (vm *VM) GetExtraConfig() ([]*types.ExtraConfigMarshal, error) {
if vm.VM.HREF == "" {
return nil, fmt.Errorf("cannot update VM spec section, VM HREF is unset")
}

virtualHardwareSection := &types.ResponseVirtualHardwareSection{}
_, err := vm.client.ExecuteRequest(vm.VM.HREF+"/virtualHardwareSection/", http.MethodGet, types.MimeVirtualHardwareSection, "error retrieving virtual hardware: %s", nil, virtualHardwareSection)
if err != nil {
return nil, err
}

convertedExtraConfig := convertExtraConfig(virtualHardwareSection.ExtraConfigs)

return convertedExtraConfig, nil
}

// UpdateExtraConfig adds or changes items in the VM Extra Configuration set
// Returns the modified set
// Note: an item with an empty `Value` will be deleted.
func (vm *VM) UpdateExtraConfig(update []*types.ExtraConfigMarshal) ([]*types.ExtraConfigMarshal, error) {
return vm.updateExtraConfig(update, false)
}

// DeleteExtraConfig removes items from the VM Extra Configuration set
// Returns the modified set
func (vm *VM) DeleteExtraConfig(deleteItems []*types.ExtraConfigMarshal) ([]*types.ExtraConfigMarshal, error) {
return vm.updateExtraConfig(deleteItems, true)
}

// updateExtraConfig adds, changes, or delete items in the VM Extra Configuration set
func (vm *VM) updateExtraConfig(update []*types.ExtraConfigMarshal, wantDelete bool) ([]*types.ExtraConfigMarshal, error) {
if vm.VM.HREF == "" {
return nil, fmt.Errorf("cannot update VM spec section, VM HREF is unset")
}

virtualHardwareSection := &types.ResponseVirtualHardwareSection{}
_, err := vm.client.ExecuteRequest(vm.VM.HREF+"/virtualHardwareSection/", http.MethodGet, types.MimeVirtualHardwareSection, "error retrieving virtual hardware: %s", nil, virtualHardwareSection)
if err != nil {
return nil, err
}

var newExtraConfig []*types.ExtraConfigMarshal

var invalidKeys []string

if wantDelete {
for _, ec := range update {
newExtraConfig = append(newExtraConfig, &types.ExtraConfigMarshal{Key: ec.Key, Value: ""})
}

} else {
for _, ec := range update {
if strings.Contains(ec.Key, " ") {
invalidKeys = append(invalidKeys, ec.Key)
continue
}
newExtraConfig = append(newExtraConfig, ec)
}
if len(invalidKeys) > 0 {
return nil, fmt.Errorf("[vm.UpdateExtraConfig] invalid keys provided: [%s]", strings.Join(invalidKeys, ","))
}
}

requestVirtualHardwareSection := &types.RequestVirtualHardwareSection{
Info: "Virtual hardware requirements",
Ovf: types.XMLNamespaceOVF,
Rasd: types.XMLNamespaceRASD,
Vssd: types.XMLNamespaceVSSD,
Ns4: types.XMLNamespaceVCloud,
Vmw: types.XMLNamespaceVMW,

Type: virtualHardwareSection.Type,
System: virtualHardwareSection.System,
Item: virtualHardwareSection.Item,

ExtraConfigs: newExtraConfig,
}

task, err := vm.client.ExecuteTaskRequest(vm.VM.HREF+"/virtualHardwareSection/", http.MethodPut,
types.MimeVirtualHardwareSection, "error updating VM spec section: %s", requestVirtualHardwareSection)
if err != nil {
return nil, err
}

err = task.WaitTaskCompletion()
if err != nil {
return nil, fmt.Errorf("error waiting task: %s", err)
}

xtraCfg, err := vm.GetExtraConfig()
if err != nil {
return nil, fmt.Errorf("got error while retrieving extra config: %s", err)
}

return xtraCfg, nil
}

func convertExtraConfig(source []*types.ExtraConfig) []*types.ExtraConfigMarshal {
resp := make([]*types.ExtraConfigMarshal, len(source))
for index, field := range source {
resp[index] = &types.ExtraConfigMarshal{
Key: field.Key,
Value: field.Value,
Required: field.Required,
}
}

return resp
}
125 changes: 125 additions & 0 deletions govcd/vm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ package govcd

import (
"fmt"
"slices"
"strings"
"time"

Expand Down Expand Up @@ -2225,3 +2226,127 @@ func (vcd *TestVCD) Test_VmConsolidateDisks(check *C) {
err = task.WaitTaskCompletion()
check.Assert(err, IsNil)
}

func (vcd *TestVCD) Test_VmExtraConfig(check *C) {

fmt.Printf("Running: %s\n", check.TestName())
if vcd.skipVappTests {
check.Skip("Skipping test because vApp wasn't properly created")
}

vapp := vcd.findFirstVapp()
if vapp.VApp.Name == "" {
check.Skip("Disabled: No suitable vApp found in vDC")
}
vm, _ := vcd.findFirstVm(vapp)
if vm.Name == "" {
check.Skip("Disabled: No suitable VM found in vDC")
}

poweredOffVm, err := vcd.client.Client.GetVMByHref(vm.HREF)
check.Assert(err, IsNil)

newVapp, poweredOnVm := createNsxtVAppAndVm(vcd, check)

testVmExtraConfig(vcd, "powered OFF VM", poweredOffVm, check, false, false)
testVmExtraConfig(vcd, "formerly powered OFF VM, now powered ON", poweredOffVm, check, true, false)
testVmExtraConfig(vcd, "powered ON VM", poweredOnVm, check, true, false)
testVmExtraConfig(vcd, "formerly powered ON VM, now powered OFF", poweredOnVm, check, false, true)

// poweredOffVm should be brought back to its original state
task, err := poweredOffVm.PowerOff()
check.Assert(err, IsNil)
err = task.WaitTaskCompletion()
check.Assert(err, IsNil)
// Removing the newly created VM and its vApp
task, err = newVapp.Delete()
check.Assert(err, IsNil)
err = task.WaitTaskCompletion()
check.Assert(err, IsNil)
}

func testVmExtraConfig(vcd *TestVCD, label string, vm *VM, check *C, wantPowerOn, wantPowerOff bool) {

fmt.Println(label)
if wantPowerOn {
task, err := vm.PowerOn()
check.Assert(err, IsNil)
err = task.WaitTaskCompletion()
check.Assert(err, IsNil)
}
if wantPowerOff && !wantPowerOn {
task, err := vm.PowerOff()
check.Assert(err, IsNil)
err = task.WaitTaskCompletion()
check.Assert(err, IsNil)
}
printVerbose("vm extra config %# v\n", pretty.Formatter(vm.VM.VirtualHardwareSection.ExtraConfig))

configSimilar := types.ExtraConfigMarshal{
Key: "hpet1.present",
Value: "TRUE",
}
configWithValidKey := types.ExtraConfigMarshal{
Key: "Norwegian.wood",
Value: "With a little help from my friends",
}
configWithInvalidKey := types.ExtraConfigMarshal{
Key: "Eleanor Rigby", // invalid key: contains a space
Value: "The long and winding road",
}

xtraConfig, err := vm.GetExtraConfig()
check.Assert(err, IsNil)
printVerbose("initial values %# v\n", pretty.Formatter(xtraConfig))

// Checks that keys containing spaces trigger an error.
invalidUpdatedCfg, err := vm.UpdateExtraConfig([]*types.ExtraConfigMarshal{&configWithInvalidKey})
check.Assert(err, NotNil)
check.Assert(invalidUpdatedCfg, IsNil)
check.Assert(strings.Contains(err.Error(), "invalid keys"), Equals, true)

containsKey := func(items []*types.ExtraConfigMarshal, key string) bool {
return slices.ContainsFunc(items, func(marshal *types.ExtraConfigMarshal) bool {
return marshal.Key == key
})
}
containsKeyValue := func(items []*types.ExtraConfigMarshal, key, value string) bool {
return slices.ContainsFunc(items, func(marshal *types.ExtraConfigMarshal) bool {
return marshal.Key == key && marshal.Value == value
})
}

// Adds two items
updatedCfg, err := vm.UpdateExtraConfig([]*types.ExtraConfigMarshal{&configSimilar, &configWithValidKey})
check.Assert(err, IsNil)
check.Assert(updatedCfg, NotNil)

updatedXtraConfig, err := vm.GetExtraConfig()
check.Assert(err, IsNil)
check.Assert(updatedXtraConfig, NotNil)
printVerbose(" after update %# v\n", pretty.Formatter(updatedXtraConfig))

check.Assert(containsKey(updatedXtraConfig, configWithValidKey.Key), Equals, true)
check.Assert(containsKey(updatedXtraConfig, configSimilar.Key), Equals, true)

// Change the value of an existing key
modifiedValue := "modified value"
configSimilar.Value = modifiedValue
configWithValidKey.Value = modifiedValue
modifiedExtraCfg, err := vm.UpdateExtraConfig([]*types.ExtraConfigMarshal{&configSimilar, &configWithValidKey})
check.Assert(err, IsNil)
check.Assert(modifiedExtraCfg, NotNil)
printVerbose(" after modification %# v\n", pretty.Formatter(modifiedExtraCfg))
check.Assert(containsKeyValue(modifiedExtraCfg, configSimilar.Key, modifiedValue), Equals, true)
check.Assert(containsKeyValue(modifiedExtraCfg, configWithValidKey.Key, modifiedValue), Equals, true)

// Delete the recently inserted items
afterDeleteXtraConfig, err := vm.DeleteExtraConfig([]*types.ExtraConfigMarshal{&configSimilar, &configWithValidKey})
check.Assert(err, IsNil)
check.Assert(afterDeleteXtraConfig, NotNil)

printVerbose("after delete %# v\n", pretty.Formatter(afterDeleteXtraConfig))

check.Assert(containsKey(afterDeleteXtraConfig, configWithValidKey.Key), Equals, false)
check.Assert(containsKey(afterDeleteXtraConfig, configSimilar.Key), Equals, false)
}
9 changes: 9 additions & 0 deletions types/v56/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -1741,6 +1741,15 @@ type VirtualHardwareSection struct {
HREF string `xml:"href,attr,omitempty"`
Type string `xml:"type,attr,omitempty"`
Item []*VirtualHardwareItem `xml:"Item,omitempty"`

ExtraConfig []*VmVirtualHardwareSectionExtraConfig `xml:"ExtraConfig,omitempty"`
Link []*Link `xml:"Link,omitempty"`
}

type VmVirtualHardwareSectionExtraConfig struct {
Key string `xml:"key,attr"`
Value string `xml:"value,attr"`
Required bool `xml:"required,attr"`
}

// Each ovf:Item parsed from the ovf:VirtualHardwareSection
Expand Down
47 changes: 47 additions & 0 deletions types/v56/vm_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,3 +221,50 @@ type Adapter struct {
Network string `xml:"network,attr"`
UnitNumber string `xml:"unitNumber,attr"`
}

// RequestVirtualHardwareSection is used to start a request in VM Extra Configuration set
type RequestVirtualHardwareSection struct {
// Extends OVF Section_Type
XMLName xml.Name `xml:"ovf:VirtualHardwareSection"`
Ovf string `xml:"xmlns:ovf,attr"`
Vssd string `xml:"xmlns:vssd,attr"`
Rasd string `xml:"xmlns:rasd,attr"`
Ns4 string `xml:"xmlns:ns4,attr"`
Vmw string `xml:"xmlns:vmw,attr"`

Info string `xml:"ovf:Info"`
HREF string `xml:"href,attr,omitempty"`
Type string `xml:"type,attr,omitempty"`
System []InnerXML `xml:"ovf:System,omitempty"`
Item []InnerXML `xml:"ovf:Item,omitempty"`

ExtraConfigs []*ExtraConfigMarshal `xml:"vmw:ExtraConfig,omitempty"`
}

// ResponseVirtualHardwareSection is used to get a response
type ResponseVirtualHardwareSection struct {
// Extends OVF Section_Type
XMLName xml.Name `xml:"VirtualHardwareSection"`
Xmlns string `xml:"vcloud,attr,omitempty"`
Ovf string `xml:"xmlns:ovf,attr"`
Ns4 string `xml:"xmlns:ns4,attr"`
Vssd string `xml:"xmlns:vssd,attr"`
Rasd string `xml:"xmlns:rasd,attr"`
Vmw string `xml:"xmlns:vmw,attr"`

Info string `xml:"Info"`
HREF string `xml:"href,attr,omitempty"`
Type string `xml:"type,attr,omitempty"`

System []InnerXML `xml:"System,omitempty"`
Item []InnerXML `xml:"Item,omitempty"`

ExtraConfigs []*ExtraConfig `xml:"ExtraConfig,omitempty"`
}

// ExtraConfig describes an Extra Configuration item
type ExtraConfig struct {
Key string `xml:"key,attr"`
Value string `xml:"value,attr"`
Required bool `xml:"required,attr"`
}
79 changes: 79 additions & 0 deletions types/v56/vmmarshal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package types

import (
"encoding/xml"
)

type ExtraConfigVirtualHardwareSectionMarshal struct {
NS10 string `xml:"xmlns:ns10,attr,omitempty"`

Info string `xml:"ovf:Info"`
Items []*VirtualHardwareItemMarshal `xml:"ovf:Item,omitempty"`
ExtraConfigs []*ExtraConfigMarshal `xml:"vmw:ExtraConfig,omitempty"`
}
type ExtraConfigMarshal struct {
Key string `xml:"vmw:key,attr"`
Value string `xml:"vmw:value,attr"`
Required bool `xml:"ovf:required,attr"`
}

type VirtualHardwareItemMarshal struct {
XMLName xml.Name `xml:"ovf:Item"`
Type string `xml:"ns10:type,attr,omitempty"`
Href string `xml:"ns10:href,attr,omitempty"`

Address *NillableElementMarshal `xml:"rasd:Address"`
AddressOnParent *NillableElementMarshal `xml:"rasd:AddressOnParent"`
AllocationUnits *NillableElementMarshal `xml:"rasd:AllocationUnits"`
AutomaticAllocation *NillableElementMarshal `xml:"rasd:AutomaticAllocation"`
AutomaticDeallocation *NillableElementMarshal `xml:"rasd:AutomaticDeallocation"`
ConfigurationName *NillableElementMarshal `xml:"rasd:ConfigurationName"`
Connection []*VirtualHardwareConnectionMarshal `xml:"rasd:Connection,omitempty"`
ConsumerVisibility *NillableElementMarshal `xml:"rasd:ConsumerVisibility"`
Description *NillableElementMarshal `xml:"rasd:Description"`
ElementName *NillableElementMarshal `xml:"rasd:ElementName,omitempty"`
Generation *NillableElementMarshal `xml:"rasd:Generation"`
HostResource []*VirtualHardwareHostResourceMarshal `xml:"rasd:HostResource,omitempty"`
InstanceID int `xml:"rasd:InstanceID"`
Limit *NillableElementMarshal `xml:"rasd:Limit"`
MappingBehavior *NillableElementMarshal `xml:"rasd:MappingBehavior"`
OtherResourceType *NillableElementMarshal `xml:"rasd:OtherResourceType"`
Parent *NillableElementMarshal `xml:"rasd:Parent"`
PoolID *NillableElementMarshal `xml:"rasd:PoolID"`
Reservation *NillableElementMarshal `xml:"rasd:Reservation"`
ResourceSubType *NillableElementMarshal `xml:"rasd:ResourceSubType"`
ResourceType *NillableElementMarshal `xml:"rasd:ResourceType"`
VirtualQuantity *NillableElementMarshal `xml:"rasd:VirtualQuantity"`
VirtualQuantityUnits *NillableElementMarshal `xml:"rasd:VirtualQuantityUnits"`
Weight *NillableElementMarshal `xml:"rasd:Weight"`

CoresPerSocket *CoresPerSocketMarshal `xml:"vmw:CoresPerSocket,omitempty"`
Link []*Link `xml:"Link,omitempty"`
}

type NillableElementMarshal struct {
XmlnsXsi string `xml:"xmlns:xsi,attr,omitempty"`
XsiNil string `xml:"xsi:nil,attr,omitempty"`
Value string `xml:",chardata"`
}

type CoresPerSocketMarshal struct {
OvfRequired string `xml:"ovf:required,attr,omitempty"`
Value string `xml:",chardata"`
}

type VirtualHardwareConnectionMarshal struct {
IpAddressingMode string `xml:"ns10:ipAddressingMode,attr,omitempty"`
IPAddress string `xml:"ns10:ipAddress,attr,omitempty"`
PrimaryConnection bool `xml:"ns10:primaryNetworkConnection,attr,omitempty"`
Value string `xml:",chardata"`
}

type VirtualHardwareHostResourceMarshal struct {
StorageProfile string `xml:"ns10:storageProfileHref,attr,omitempty"`
BusType int `xml:"ns10:busType,attr,omitempty"`
BusSubType string `xml:"ns10:busSubType,attr,omitempty"`
Capacity int `xml:"ns10:capacity,attr,omitempty"`
Iops string `xml:"ns10:iops,attr,omitempty"`
OverrideVmDefault string `xml:"ns10:storageProfileOverrideVmDefault,attr,omitempty"`
}

0 comments on commit 85e22f8

Please sign in to comment.