diff --git a/.changes/v2.20.0/528-features.md b/.changes/v2.20.0/528-features.md new file mode 100644 index 000000000..588909410 --- /dev/null +++ b/.changes/v2.20.0/528-features.md @@ -0,0 +1 @@ +* Added method `VM.GetEnvironment` to retrieve OVF Environment [GH-528] \ No newline at end of file diff --git a/govcd/vm.go b/govcd/vm.go index e9c525de1..6c7752cf7 100644 --- a/govcd/vm.go +++ b/govcd/vm.go @@ -832,6 +832,20 @@ func (vm *VM) GetProductSectionList() (*types.ProductSectionList, error) { return getProductSectionList(vm.client, vm.VM.HREF) } +// GetEnvironment returns the OVF Environment. It's only available for poweredOn VM +func (vm *VM) GetEnvironment() (*types.OvfEnvironment, error) { + vmStatus, err := vm.GetStatus() + if err != nil { + return nil, fmt.Errorf("unable to get OVF environment: %s", err) + } + + if vmStatus != "POWERED_ON" { + return nil, fmt.Errorf("OVF environment is only available when VM is powered on") + } + + return vm.VM.Environment, nil +} + // GetGuestCustomizationSection retrieves guest customization section for a VM. It allows to read VM guest customization properties. func (vm *VM) GetGuestCustomizationSection() (*types.GuestCustomizationSection, error) { if vm == nil || vm.VM.HREF == "" { diff --git a/govcd/vm_test.go b/govcd/vm_test.go index e395f6811..8da8cc47f 100644 --- a/govcd/vm_test.go +++ b/govcd/vm_test.go @@ -2281,3 +2281,70 @@ func createNsxtVAppAndVm(vcd *TestVCD, check *C) (*VApp, *VM) { return vapp, vm } + +func (vcd *TestVCD) Test_GetOvfEnvironment(check *C) { + if vcd.skipVappTests { + check.Skip("Skipping test because vapp was not successfully created at setup") + } + vapp := vcd.findFirstVapp() + existingVm, vmName := vcd.findFirstVm(vapp) + if vmName == "" { + check.Skip("skipping test because no VM is found") + } + vm, err := vcd.client.Client.GetVMByHref(existingVm.HREF) + check.Assert(err, IsNil) + + vmStatus, err := vm.GetStatus() + check.Assert(err, IsNil) + if vmStatus != "POWERED_ON" { + task, err := vm.PowerOn() + check.Assert(err, IsNil) + err = task.WaitTaskCompletion() + check.Assert(err, IsNil) + check.Assert(task.Task.Status, Equals, "success") + } + + // Read ovfenv when VM is started + ovfenv, err := vm.GetEnvironment() + check.Assert(err, IsNil) + check.Assert(ovfenv, NotNil) + + // Provides information from the virtualization platform like VM moref + check.Assert(strings.Contains(ovfenv.VCenterId, "vm-"), Equals, true) + + // Check virtualization platform Vendor + check.Assert(ovfenv.PlatformSection, NotNil) + check.Assert(ovfenv.PlatformSection.Vendor, Equals, "VMware, Inc.") + + // Check guest operating system level configuration for hostname + check.Assert(ovfenv.PropertySection, NotNil) + for _, p := range ovfenv.PropertySection.Properties { + if p.Key == "vCloud_computerName" { + check.Assert(p.Value, Not(Equals), "") + } + } + check.Assert(ovfenv.EthernetAdapterSection, NotNil) + for _, p := range ovfenv.EthernetAdapterSection.Adapters { + check.Assert(p.Mac, Not(Equals), "") + } + + // PowerOff + task, err := vm.PowerOff() + check.Assert(err, IsNil) + err = task.WaitTaskCompletion() + check.Assert(err, IsNil) + check.Assert(task.Task.Status, Equals, "success") + + ovfenv, err = vm.GetEnvironment() + check.Assert(strings.Contains(err.Error(), "OVF environment is only available when VM is powered on"), Equals, true) + check.Assert(ovfenv, IsNil) + + // Leave things as they were + if vmStatus != "POWERED_OFF" { + task, err := vm.PowerOn() + check.Assert(err, IsNil) + err = task.WaitTaskCompletion() + check.Assert(err, IsNil) + check.Assert(task.Task.Status, Equals, "success") + } +} diff --git a/types/v56/vm_types.go b/types/v56/vm_types.go index 16482be97..9c33047f8 100644 --- a/types/v56/vm_types.go +++ b/types/v56/vm_types.go @@ -51,8 +51,8 @@ type Vm struct { Snapshots *SnapshotSection `xml:"SnapshotSection,omitempty"` - // TODO: OVF Sections to be implemented - // Environment OVF_Environment `xml:"Environment,omitempty" + // The OVF environment defines how the guest software and the virtualization platform interact. + Environment *OvfEnvironment `xml:"Environment,omitempty"` VmSpecSection *VmSpecSection `xml:"VmSpecSection,omitempty"` @@ -161,3 +161,50 @@ type SourcedVmTemplateParams struct { VmTemplateInstantiationParams *InstantiationParams `xml:"VmTemplateInstantiationParams,omitempty"` // Same as InstantiationParams used for VMs within a vApp StorageProfile *Reference `xml:"StorageProfile,omitempty"` // A reference to a storage profile to be used for the VM. The specified storage profile must exist in the organization vDC that contains the composed vApp. If not specified, the default storage profile for the vDC is used. } + +// The OVF environment enables the guest software to access information about the virtualization platform, such as +// the user-specified values for the properties defined in the OVF descriptor. +type OvfEnvironment struct { + XMLName xml.Name `xml:"Environment"` + Ve string `xml:"ve,attr,omitempty"` // Xml namespace + Id string `xml:"id,attr,omitempty"` // Identification of VM from OVF Descriptor. Describes this virtual system. + VCenterId string `xml:"vCenterId,attr,omitempty"` // VM moref in the vCenter + PlatformSection *PlatformSection `xml:"PlatformSection,omitempty"` // Describes the virtualization platform + PropertySection *PropertySection `xml:"PropertySection,omitempty"` // Property elements with key/value pairs + EthernetAdapterSection *EthernetAdapterSection `xml:"EthernetAdapterSection,omitempty"` // Contains adapters info and virtual networks attached +} + +// Provides information from the virtualization platform +type PlatformSection struct { + XMLName xml.Name `xml:"PlatformSection"` + Kind string `xml:"Kind,omitempty"` // Hypervisor kind is typically VMware ESXi + Version string `xml:"Version,omitempty"` // Hypervisor version + Vendor string `xml:"Vendor,omitempty"` // VMware, Inc. + Locale string `xml:"Locale,omitempty"` // Hypervisor locale +} + +// Contains a list of key/value pairs corresponding to properties defined in the OVF descriptor +// Operating system level configuration, such as host names, IP address, subnets, gateways, etc. +// Application-level configuration such as DNS name of active directory server, databases and +// other external services. +type PropertySection struct { + XMLName xml.Name `xml:"PropertySection"` + Properties []*OvfProperty `xml:"Property,omitempty"` +} + +type OvfProperty struct { + Key string `xml:"key,attr"` + Value string `xml:"value,attr"` +} + +// Contains adapters info and virtual networks attached +type EthernetAdapterSection struct { + XMLName xml.Name `xml:"EthernetAdapterSection"` + Adapters []*Adapter `xml:"Adapter,omitempty"` +} + +type Adapter struct { + Mac string `xml:"mac,attr"` + Network string `xml:"network,attr"` + UnitNumber string `xml:"unitNumber,attr"` +}