diff --git a/.changes/v2.17.0/413-improvements.md b/.changes/v2.17.0/413-improvements.md new file mode 100644 index 000000000..d8977837f --- /dev/null +++ b/.changes/v2.17.0/413-improvements.md @@ -0,0 +1 @@ +* Added method `VM.Shutdown` to shut down guest OS [GH-413], [GH-496] diff --git a/govcd/vm.go b/govcd/vm.go index 99f554c67..94006c052 100644 --- a/govcd/vm.go +++ b/govcd/vm.go @@ -492,6 +492,25 @@ func (vm *VM) Undeploy() (Task, error) { types.MimeUndeployVappParams, "error undeploy VM: %s", vu) } +// Shutdown triggers a VM undeploy and shutdown action. "Shut Down Guest OS" action in UI behaves +// this way. +// +// Note. Success of this operation depends on the VM having Guest Tools installed. +func (vm *VM) Shutdown() (Task, error) { + + vu := &types.UndeployVAppParams{ + Xmlns: types.XMLNamespaceVCloud, + UndeployPowerAction: "shutdown", + } + + apiEndpoint := urlParseRequestURI(vm.VM.HREF) + apiEndpoint.Path += "/action/undeploy" + + // Return the task + return vm.client.ExecuteTaskRequest(apiEndpoint.String(), http.MethodPost, + types.MimeUndeployVappParams, "error undeploy VM: %s", vu) +} + // Attach or detach an independent disk // Use the disk/action/attach or disk/action/detach links in a VM to attach or detach an independent disk. // Reference: vCloud API Programming Guide for Service Providers vCloud API 30.0 PDF Page 164 - 165, diff --git a/govcd/vm_test.go b/govcd/vm_test.go index 63781ba95..b89e713d9 100644 --- a/govcd/vm_test.go +++ b/govcd/vm_test.go @@ -710,6 +710,70 @@ func (vcd *TestVCD) Test_VMPowerOnPowerOff(check *C) { check.Assert(vmStatus, Equals, "POWERED_OFF") } +func (vcd *TestVCD) Test_VmShutdown(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) + + vdc, err := vm.GetParentVdc() + check.Assert(err, IsNil) + + // Ensure VM is not powered on + vmStatus, err := vm.GetStatus() + check.Assert(err, IsNil) + fmt.Println("VM status: ", vmStatus) + + 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") + } + + // Wait until Guest Tools gets to `REBOOT_PENDING` or `GC_COMPLETE` as there is no real way to + // check if VM has Guest Tools operating + for { + err = vm.Refresh() + check.Assert(err, IsNil) + + vmQuery, err := vdc.QueryVM(vapp.VApp.Name, vm.VM.Name) + check.Assert(err, IsNil) + + printVerbose("VM Tools Status: %s\n", vmQuery.VM.GcStatus) + if vmQuery.VM.GcStatus == "GC_COMPLETE" || vmQuery.VM.GcStatus == "REBOOT_PENDING" { + break + } + + time.Sleep(3 * time.Second) + } + printVerbose("Shuting down VM:\n") + + task, err := vm.Shutdown() + check.Assert(err, IsNil) + err = task.WaitTaskCompletion() + check.Assert(err, IsNil) + check.Assert(task.Task.Status, Equals, "success") + + newStatus, err := vm.GetStatus() + check.Assert(err, IsNil) + printVerbose("New VM status: %s\n", newStatus) + check.Assert(newStatus, Equals, "POWERED_OFF") + + // End of test - power on the VM to leave it running + task, err = vm.PowerOn() + check.Assert(err, IsNil) + err = task.WaitTaskCompletion() + check.Assert(err, IsNil) +} + func (vcd *TestVCD) Test_GetNetworkConnectionSection(check *C) { if vcd.skipVappTests { check.Skip("Skipping test because vapp was not successfully created at setup") diff --git a/types/v56/vm_types.go b/types/v56/vm_types.go index e19af9b19..e06ba13b9 100644 --- a/types/v56/vm_types.go +++ b/types/v56/vm_types.go @@ -42,6 +42,8 @@ type Vm struct { // Section ovf:VirtualHardwareSection VirtualHardwareSection *VirtualHardwareSection `xml:"VirtualHardwareSection,omitempty"` + RuntimeInfoSection *RuntimeInfoSection `xml:"RuntimeInfoSection,omitempty"` + // FIXME: Upstream bug? Missing NetworkConnectionSection NetworkConnectionSection *NetworkConnectionSection `xml:"NetworkConnectionSection,omitempty"` @@ -65,6 +67,16 @@ type Vm struct { Media *Reference `xml:"Media,omitempty"` // Reference to the media object to insert in a new VM. } +type RuntimeInfoSection struct { + Ns10 string `xml:"ns10,attr"` + Type string `xml:"type,attr"` + Href string `xml:"href,attr"` + Info string `xml:"Info"` + VMWareTools struct { + Version string `xml:"version,attr"` + } `xml:"VMWareTools"` +} + // VmSpecSection from Vm struct type VmSpecSection struct { Modified *bool `xml:"Modified,attr,omitempty"`