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  }