Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vcsim: RetrievePropertiesEx & ContinueRetrievePropertiesEx #2

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion property/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,11 +143,23 @@ func (p *Collector) CancelWaitForUpdates(ctx context.Context) error {
}

// RetrieveProperties wraps RetrievePropertiesEx and ContinueRetrievePropertiesEx to collect properties in batches.
func (p *Collector) RetrieveProperties(ctx context.Context, req types.RetrieveProperties) (*types.RetrievePropertiesResponse, error) {
func (p *Collector) RetrieveProperties(
ctx context.Context,
req types.RetrieveProperties,
maxObjectsArgs ...int32) (*types.RetrievePropertiesResponse, error) {

var opts types.RetrieveOptions
if l := len(maxObjectsArgs); l > 1 {
return nil, fmt.Errorf("maxObjectsArgs accepts a single value")
} else if l == 1 {
opts.MaxObjects = maxObjectsArgs[0]
}

req.This = p.Reference()
rx, err := methods.RetrievePropertiesEx(ctx, p.roundTripper, &types.RetrievePropertiesEx{
This: req.This,
SpecSet: req.SpecSet,
Options: opts,
})
if err != nil {
return nil, err
Expand Down
111 changes: 75 additions & 36 deletions property/collector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ func TestWaitForUpdatesEx(t *testing.T) {
cancelCtx,
pc,
&property.WaitFilter{
CreateFilter: getDatacenterToVMFolderFilter(datacenter),
CreateFilter: types.CreateFilter{
Spec: getDatacenterToVMFolderFilter(datacenter),
},
WaitOptions: property.WaitOptions{
Options: &types.WaitOptions{
MaxWaitSeconds: addrOf(int32(3)),
Expand Down Expand Up @@ -108,6 +110,45 @@ func TestWaitForUpdatesEx(t *testing.T) {
}, model)
}

func TestRetrievePropertiesOneAtATime(t *testing.T) {
model := simulator.VPX()
model.Datacenter = 1
model.Cluster = 0
model.Pool = 0
model.Machine = 3
model.Autostart = false

simulator.Test(func(ctx context.Context, c *vim25.Client) {
finder := find.NewFinder(c, true)
datacenter, err := finder.DefaultDatacenter(ctx)
if err != nil {
t.Fatalf("default datacenter not found: %s", err)
}
finder.SetDatacenter(datacenter)
pc := property.DefaultCollector(c)

resp, err := pc.RetrieveProperties(ctx, types.RetrieveProperties{
SpecSet: []types.PropertyFilterSpec{
getDatacenterToVMFolderFilter(datacenter),
},
}, 1)
if err != nil {
t.Fatalf("failed to retrieve properties one object at a time: %s", err)
}

vmRefs := map[types.ManagedObjectReference]struct{}{}
for i := range resp.Returnval {
oc := resp.Returnval[i]
vmRefs[oc.Obj] = struct{}{}
}

if a, e := len(vmRefs), 3; a != 3 {
t.Fatalf("unexpected number of vms: a=%d, e=%d", a, e)
}

}, model)
}

func waitForPowerStateChanges(
ctx context.Context,
vm *object.VirtualMachine,
Expand Down Expand Up @@ -139,52 +180,50 @@ func waitForPowerStateChanges(
return false
}

func getDatacenterToVMFolderFilter(dc *object.Datacenter) types.CreateFilter {
func getDatacenterToVMFolderFilter(dc *object.Datacenter) types.PropertyFilterSpec {
// Define a wait filter that looks for updates to VM power
// states for VMs under the specified datacenter.
return types.CreateFilter{
Spec: types.PropertyFilterSpec{
ObjectSet: []types.ObjectSpec{
{
Obj: dc.Reference(),
Skip: addrOf(true),
SelectSet: []types.BaseSelectionSpec{
// Datacenter --> VM folder
&types.TraversalSpec{
SelectionSpec: types.SelectionSpec{
Name: "dcToVMFolder",
},
Type: "Datacenter",
Path: "vmFolder",
SelectSet: []types.BaseSelectionSpec{
&types.SelectionSpec{
Name: "visitFolders",
},
return types.PropertyFilterSpec{
ObjectSet: []types.ObjectSpec{
{
Obj: dc.Reference(),
Skip: addrOf(true),
SelectSet: []types.BaseSelectionSpec{
// Datacenter --> VM folder
&types.TraversalSpec{
SelectionSpec: types.SelectionSpec{
Name: "dcToVMFolder",
},
Type: "Datacenter",
Path: "vmFolder",
SelectSet: []types.BaseSelectionSpec{
&types.SelectionSpec{
Name: "visitFolders",
},
},
},
// Folder --> children (folder / VM)
&types.TraversalSpec{
SelectionSpec: types.SelectionSpec{
Name: "visitFolders",
},
Type: "Folder",
// Folder --> children (folder / VM)
&types.TraversalSpec{
SelectionSpec: types.SelectionSpec{
Path: "childEntity",
SelectSet: []types.BaseSelectionSpec{
// Folder --> child folder
&types.SelectionSpec{
Name: "visitFolders",
},
Type: "Folder",
// Folder --> children (folder / VM)
Path: "childEntity",
SelectSet: []types.BaseSelectionSpec{
// Folder --> child folder
&types.SelectionSpec{
Name: "visitFolders",
},
},
},
},
},
},
PropSet: []types.PropertySpec{
{
Type: "VirtualMachine",
PathSet: []string{"runtime.powerState"},
},
},
PropSet: []types.PropertySpec{
{
Type: "VirtualMachine",
PathSet: []string{"runtime.powerState"},
},
},
}
Expand Down
5 changes: 4 additions & 1 deletion property/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,10 @@ func ExampleCollector_WaitForUpdatesEx_addingRemovingPropertyFilters() {
}

// Now create a property filter that will catch the update.
pf, err := pc.CreateFilter(ctx, getDatacenterToVMFolderFilter(datacenter))
pf, err := pc.CreateFilter(
ctx,
types.CreateFilter{Spec: getDatacenterToVMFolderFilter(datacenter)},
)
if err != nil {
return fmt.Errorf("failed to create dc2vm property filter: %w", err)
}
Expand Down
81 changes: 80 additions & 1 deletion simulator/property_collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import (
"sync"
"time"

"github.com/google/uuid"

"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/simulator/internal"
"github.com/vmware/govmomi/vim25"
Expand Down Expand Up @@ -523,6 +525,62 @@ func (pc *PropertyCollector) DestroyPropertyCollector(ctx *Context, c *types.Des
return body
}

var retrievePropertiesExBook sync.Map

type retrievePropertiesExPage struct {
MaxObjects int32
Objects []types.ObjectContent
}

func (pc *PropertyCollector) ContinueRetrievePropertiesEx(ctx *Context, r *types.ContinueRetrievePropertiesEx) soap.HasFault {
body := &methods.ContinueRetrievePropertiesExBody{}

if r.Token == "" {
body.Fault_ = Fault("", &types.InvalidPropertyFault{Name: "token"})
return body
}

obj, ok := retrievePropertiesExBook.LoadAndDelete(r.Token)
if !ok {
body.Fault_ = Fault("", &types.InvalidPropertyFault{Name: "token"})
return body
}

page := obj.(retrievePropertiesExPage)

var (
objsToStore []types.ObjectContent
objsToReturn []types.ObjectContent
)
for i := range page.Objects {
if page.MaxObjects <= 0 || i < int(page.MaxObjects) {
objsToReturn = append(objsToReturn, page.Objects[i])
} else {
objsToStore = append(objsToStore, page.Objects[i])
}
}

if len(objsToStore) > 0 {
body.Res = &types.ContinueRetrievePropertiesExResponse{}
body.Res.Returnval.Token = uuid.NewString()
retrievePropertiesExBook.Store(
body.Res.Returnval.Token,
retrievePropertiesExPage{
MaxObjects: page.MaxObjects,
Objects: objsToStore,
})
}

if len(objsToReturn) > 0 {
if body.Res == nil {
body.Res = &types.ContinueRetrievePropertiesExResponse{}
}
body.Res.Returnval.Objects = objsToReturn
}

return body
}

func (pc *PropertyCollector) RetrievePropertiesEx(ctx *Context, r *types.RetrievePropertiesEx) soap.HasFault {
body := &methods.RetrievePropertiesExBody{}

Expand All @@ -537,7 +595,28 @@ func (pc *PropertyCollector) RetrievePropertiesEx(ctx *Context, r *types.Retriev
}
} else {
objects := res.Objects[:0]
for _, o := range res.Objects {

var (
objsToStore []types.ObjectContent
objsToReturn []types.ObjectContent
)
for i := range res.Objects {
if r.Options.MaxObjects <= 0 || i < int(r.Options.MaxObjects) {
objsToReturn = append(objsToReturn, res.Objects[i])
} else {
objsToStore = append(objsToStore, res.Objects[i])
}
}

if len(objsToStore) > 0 {
res.Token = uuid.NewString()
retrievePropertiesExBook.Store(res.Token, retrievePropertiesExPage{
MaxObjects: r.Options.MaxObjects,
Objects: objsToStore,
})
}

for _, o := range objsToReturn {
propSet := o.PropSet[:0]
for _, p := range o.PropSet {
if p.Val != nil {
Expand Down
23 changes: 19 additions & 4 deletions simulator/virtual_machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -2184,10 +2184,6 @@ func (vm *VirtualMachine) CloneVMTask(ctx *Context, req *types.CloneVM_Task) soa
VmPathName: vmx.String(),
},
}
if req.Spec.Config != nil {
config.ExtraConfig = req.Spec.Config.ExtraConfig
config.InstanceUuid = req.Spec.Config.InstanceUuid
}

// Copying hardware properties
config.NumCPUs = vm.Config.Hardware.NumCPU
Expand Down Expand Up @@ -2224,6 +2220,14 @@ func (vm *VirtualMachine) CloneVMTask(ctx *Context, req *types.CloneVM_Task) soa
})
}

if dst, src := config, req.Spec.Config; src != nil {
dst.ExtraConfig = src.ExtraConfig
copyNonEmptyValue(&dst.Uuid, &src.Uuid)
copyNonEmptyValue(&dst.InstanceUuid, &src.InstanceUuid)
copyNonEmptyValue(&dst.NumCPUs, &src.NumCPUs)
copyNonEmptyValue(&dst.MemoryMB, &src.MemoryMB)
}

res := ctx.Map.Get(req.Folder).(vmFolder).CreateVMTask(ctx, &types.CreateVM_Task{
This: folder.Self,
Config: config,
Expand Down Expand Up @@ -2264,6 +2268,17 @@ func (vm *VirtualMachine) CloneVMTask(ctx *Context, req *types.CloneVM_Task) soa
}
}

func copyNonEmptyValue[T comparable](dst, src *T) {
if dst == nil || src == nil {
return
}
var t T
if *src == t {
return
}
*dst = *src
}

func (vm *VirtualMachine) RelocateVMTask(ctx *Context, req *types.RelocateVM_Task) soap.HasFault {
task := CreateTask(vm, "relocateVm", func(t *Task) (types.AnyType, types.BaseMethodFault) {
var changes []types.PropertyChange
Expand Down