github.com/vmware/govmomi@v0.43.0/property/collector_test.go (about) 1 /* 2 Copyright (c) 2024-2024 VMware, Inc. All Rights Reserved. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package property_test 18 19 import ( 20 "context" 21 "testing" 22 "time" 23 24 "github.com/vmware/govmomi/find" 25 "github.com/vmware/govmomi/object" 26 "github.com/vmware/govmomi/property" 27 "github.com/vmware/govmomi/simulator" 28 "github.com/vmware/govmomi/vim25" 29 "github.com/vmware/govmomi/vim25/types" 30 ) 31 32 func TestWaitForUpdatesEx(t *testing.T) { 33 model := simulator.VPX() 34 model.Datacenter = 1 35 model.Cluster = 0 36 model.Pool = 0 37 model.Machine = 1 38 model.Autostart = false 39 40 simulator.Test(func(ctx context.Context, c *vim25.Client) { 41 // Set up the finder and get a VM. 42 finder := find.NewFinder(c, true) 43 datacenter, err := finder.DefaultDatacenter(ctx) 44 if err != nil { 45 t.Fatalf("default datacenter not found: %s", err) 46 } 47 finder.SetDatacenter(datacenter) 48 vmList, err := finder.VirtualMachineList(ctx, "*") 49 if len(vmList) == 0 { 50 t.Fatal("vmList == 0") 51 } 52 vm := vmList[0] 53 54 pc, err := property.DefaultCollector(c).Create(ctx) 55 if err != nil { 56 t.Fatalf("failed to create new property collector: %s", err) 57 } 58 59 // Start a goroutine to wait for power state changes to the VM. 60 chanResult := make(chan any) 61 cancelCtx, cancel := context.WithCancel(ctx) 62 defer cancel() 63 go func() { 64 defer close(chanResult) 65 if err := property.WaitForUpdatesEx( 66 cancelCtx, 67 pc, 68 &property.WaitFilter{ 69 CreateFilter: types.CreateFilter{ 70 Spec: getDatacenterToVMFolderFilter(datacenter), 71 }, 72 WaitOptions: property.WaitOptions{ 73 Options: &types.WaitOptions{ 74 MaxWaitSeconds: addrOf(int32(3)), 75 }, 76 }, 77 }, 78 func(updates []types.ObjectUpdate) bool { 79 return waitForPowerStateChanges( 80 cancelCtx, 81 vm, 82 chanResult, 83 updates, 84 types.VirtualMachinePowerStatePoweredOn) 85 }, 86 ); err != nil { 87 chanResult <- err 88 return 89 } 90 }() 91 92 // Power on the VM to cause a property change. 93 if _, err := vm.PowerOn(ctx); err != nil { 94 t.Fatalf("error while powering on vm: %s", err) 95 } 96 97 select { 98 case <-time.After(3 * time.Second): 99 t.Fatalf("timed out while waiting for property update") 100 case result := <-chanResult: 101 switch tResult := result.(type) { 102 case types.VirtualMachinePowerState: 103 if tResult != types.VirtualMachinePowerStatePoweredOn { 104 t.Fatalf("unexpected power state: %s", tResult) 105 } 106 case error: 107 t.Fatalf("error while waiting for updates: %s", tResult) 108 } 109 } 110 }, model) 111 } 112 113 func TestRetrievePropertiesOneAtATime(t *testing.T) { 114 model := simulator.VPX() 115 model.Datacenter = 1 116 model.Cluster = 0 117 model.Pool = 0 118 model.Machine = 3 119 model.Autostart = false 120 121 simulator.Test(func(ctx context.Context, c *vim25.Client) { 122 finder := find.NewFinder(c, true) 123 datacenter, err := finder.DefaultDatacenter(ctx) 124 if err != nil { 125 t.Fatalf("default datacenter not found: %s", err) 126 } 127 finder.SetDatacenter(datacenter) 128 pc := property.DefaultCollector(c) 129 130 resp, err := pc.RetrieveProperties(ctx, types.RetrieveProperties{ 131 SpecSet: []types.PropertyFilterSpec{ 132 getDatacenterToVMFolderFilter(datacenter), 133 }, 134 }, 1) 135 if err != nil { 136 t.Fatalf("failed to retrieve properties one object at a time: %s", err) 137 } 138 139 vmRefs := map[types.ManagedObjectReference]struct{}{} 140 for i := range resp.Returnval { 141 oc := resp.Returnval[i] 142 vmRefs[oc.Obj] = struct{}{} 143 } 144 145 if a, e := len(vmRefs), 3; a != 3 { 146 t.Fatalf("unexpected number of vms: a=%d, e=%d", a, e) 147 } 148 149 }, model) 150 } 151 152 func waitForPowerStateChanges( 153 ctx context.Context, 154 vm *object.VirtualMachine, 155 chanResult chan any, 156 updates []types.ObjectUpdate, 157 expectedPowerState types.VirtualMachinePowerState) bool { 158 159 for _, u := range updates { 160 if ctx.Err() != nil { 161 return false 162 } 163 if u.Obj != vm.Reference() { 164 continue 165 } 166 for _, cs := range u.ChangeSet { 167 if cs.Name == "runtime.powerState" { 168 if cs.Val == expectedPowerState { 169 select { 170 case <-ctx.Done(): 171 // No-op 172 default: 173 chanResult <- cs.Val 174 } 175 return true 176 } 177 } 178 } 179 } 180 return false 181 } 182 183 func getDatacenterToVMFolderFilter(dc *object.Datacenter) types.PropertyFilterSpec { 184 // Define a wait filter that looks for updates to VM power 185 // states for VMs under the specified datacenter. 186 return types.PropertyFilterSpec{ 187 ObjectSet: []types.ObjectSpec{ 188 { 189 Obj: dc.Reference(), 190 Skip: addrOf(true), 191 SelectSet: []types.BaseSelectionSpec{ 192 // Datacenter --> VM folder 193 &types.TraversalSpec{ 194 SelectionSpec: types.SelectionSpec{ 195 Name: "dcToVMFolder", 196 }, 197 Type: "Datacenter", 198 Path: "vmFolder", 199 SelectSet: []types.BaseSelectionSpec{ 200 &types.SelectionSpec{ 201 Name: "visitFolders", 202 }, 203 }, 204 }, 205 // Folder --> children (folder / VM) 206 &types.TraversalSpec{ 207 SelectionSpec: types.SelectionSpec{ 208 Name: "visitFolders", 209 }, 210 Type: "Folder", 211 // Folder --> children (folder / VM) 212 Path: "childEntity", 213 SelectSet: []types.BaseSelectionSpec{ 214 // Folder --> child folder 215 &types.SelectionSpec{ 216 Name: "visitFolders", 217 }, 218 }, 219 }, 220 }, 221 }, 222 }, 223 PropSet: []types.PropertySpec{ 224 { 225 Type: "VirtualMachine", 226 PathSet: []string{"runtime.powerState"}, 227 }, 228 }, 229 } 230 }