From 6209be5b5c0bd5d81078fdc82eb4001f202f90e7 Mon Sep 17 00:00:00 2001 From: Doug MacEachern Date: Fri, 15 Oct 2021 08:35:40 -0700 Subject: [PATCH] Support finding Portgroups by ID in Finder.Network With standard vSphere networking, Portgroups cannot have the same name within the same network folder. With NSX, Portgroups can have the same name, even within the same Switch. In this case, using an inventory path results in a MultipleFoundError. A MOID, switch UUID or segment ID can be used instead, as both are unique. - Add ContainerView.FindAny method - Add nsx backing and SegmentId defaults to the simulator for testing - Add simulator support for renaming a Portgroup, only allowing nsx to have duplicate names --- find/finder.go | 53 ++++++++++++++++++++++++++++++++++++++++++ govc/test/network.bats | 23 ++++++++++++++++++ property/filter.go | 26 ++++++++++++++++++++- simulator/dvs.go | 14 ++++++++++- simulator/entity.go | 5 ++-- simulator/model.go | 6 ++--- simulator/portgroup.go | 6 +++++ view/container_view.go | 17 ++++++++++++++ 8 files changed, 143 insertions(+), 7 deletions(-) diff --git a/find/finder.go b/find/finder.go index cd71d183b..61ac780c4 100644 --- a/find/finder.go +++ b/find/finder.go @@ -26,6 +26,7 @@ import ( "github.com/vmware/govmomi/list" "github.com/vmware/govmomi/object" "github.com/vmware/govmomi/property" + "github.com/vmware/govmomi/view" "github.com/vmware/govmomi/vim25" "github.com/vmware/govmomi/vim25/mo" "github.com/vmware/govmomi/vim25/types" @@ -789,9 +790,26 @@ func (f *Finder) NetworkList(ctx context.Context, path string) ([]object.Network return ns, nil } +// Network finds a NetworkReference using a Name, Inventory Path, ManagedObject ID, Logical Switch UUID or Segment ID. +// With standard vSphere networking, Portgroups cannot have the same name within the same network folder. +// With NSX, Portgroups can have the same name, even within the same Switch. In this case, using an inventory path +// results in a MultipleFoundError. A MOID, switch UUID or segment ID can be used instead, as both are unique. +// See also: https://kb.vmware.com/s/article/79872#Duplicate_names +// Examples: +// - Name: "dvpg-1" +// - Inventory Path: "vds-1/dvpg-1" +// - ManagedObject ID: "DistributedVirtualPortgroup:dvportgroup-53" +// - Logical Switch UUID: "da2a59b8-2450-4cb2-b5cc-79c4c1d2144c" +// - Segment ID: "/infra/segments/vnet_ce50e69b-1784-4a14-9206-ffd7f1f146f7" func (f *Finder) Network(ctx context.Context, path string) (object.NetworkReference, error) { networks, err := f.NetworkList(ctx, path) if err != nil { + if _, ok := err.(*NotFoundError); ok { + net, nerr := f.networkByID(ctx, path) + if nerr == nil { + return net, nil + } + } return nil, err } @@ -802,6 +820,41 @@ func (f *Finder) Network(ctx context.Context, path string) (object.NetworkRefere return networks[0], nil } +func (f *Finder) networkByID(ctx context.Context, path string) (object.NetworkReference, error) { + if ref := object.ReferenceFromString(path); ref != nil { + // This is a MOID + return object.NewReference(f.client, *ref).(object.NetworkReference), nil + } + + kind := []string{"DistributedVirtualPortgroup"} + + m := view.NewManager(f.client) + v, err := m.CreateContainerView(ctx, f.client.ServiceContent.RootFolder, kind, true) + if err != nil { + return nil, err + } + defer v.Destroy(ctx) + + filter := property.Filter{ + "config.logicalSwitchUuid": path, + "config.segmentId": path, + } + + refs, err := v.FindAny(ctx, kind, filter) + if err != nil { + return nil, err + } + + if len(refs) == 0 { + return nil, &NotFoundError{"network", path} + } + if len(refs) > 1 { + return nil, &MultipleFoundError{"network", path} + } + + return object.NewReference(f.client, refs[0]).(object.NetworkReference), nil +} + func (f *Finder) DefaultNetwork(ctx context.Context) (object.NetworkReference, error) { network, err := f.Network(ctx, "*") if err != nil { diff --git a/govc/test/network.bats b/govc/test/network.bats index 5508821dc..3008124d3 100755 --- a/govc/test/network.bats +++ b/govc/test/network.bats @@ -46,6 +46,10 @@ load test_helper run govc dvs.portgroup.add -dvs DVS0 -type ephemeral NSX-dvpg assert_success + uuid=$(govc object.collect -s network/NSX-dvpg config.logicalSwitchUuid) + sid=$(govc object.collect -s network/NSX-dvpg config.segmentId) + moid=$(govc ls -i network/NSX-dvpg) + run govc dvs.portgroup.add -dvs DVS1 -type ephemeral NSX-dvpg assert_success @@ -54,6 +58,25 @@ load test_helper run govc vm.network.add -vm $vm -net DVS0/NSX-dvpg assert_success # switch_name/portgroup_name is unique + + # Add a 2nd PG to the same switch, with the same name + run govc dvs.portgroup.add -dvs DVS0 -type ephemeral NSX-dvpg + assert_success + + run govc vm.network.add -vm $vm -net NSX-dvpg + assert_failure # resolves to multiple networks + + run govc vm.network.add -vm $vm -net DVS0/NSX-dvpg + assert_failure # switch_name/portgroup_name not is unique + + run govc vm.network.add -vm $vm -net "$uuid" + assert_success # switch uuid is unique + + run govc vm.network.add -vm $vm -net "$sid" + assert_success # segment id is unique + + run govc vm.network.add -vm $vm -net "$moid" + assert_success # moid is unique } @test "network change backing" { diff --git a/property/filter.go b/property/filter.go index b88d197cb..2287bbfd9 100644 --- a/property/filter.go +++ b/property/filter.go @@ -130,7 +130,7 @@ func (f Filter) MatchPropertyList(props []types.DynamicProperty) bool { return len(f) == len(props) // false if a property such as VM "guest" is unset } -// MatchObjectContent returns a list of ObjectContent.Obj where the ObjectContent.PropSet matches the Filter. +// MatchObjectContent returns a list of ObjectContent.Obj where the ObjectContent.PropSet matches all properties the Filter. func (f Filter) MatchObjectContent(objects []types.ObjectContent) []types.ManagedObjectReference { var refs []types.ManagedObjectReference @@ -142,3 +142,27 @@ func (f Filter) MatchObjectContent(objects []types.ObjectContent) []types.Manage return refs } + +// MatchAnyPropertyList returns true if any given props match the Filter. +func (f Filter) MatchAnyPropertyList(props []types.DynamicProperty) bool { + for _, p := range props { + if f.MatchProperty(p) { + return true + } + } + + return false +} + +// MatchAnyObjectContent returns a list of ObjectContent.Obj where the ObjectContent.PropSet matches any property in the Filter. +func (f Filter) MatchAnyObjectContent(objects []types.ObjectContent) []types.ManagedObjectReference { + var refs []types.ManagedObjectReference + + for _, o := range objects { + if f.MatchAnyPropertyList(o.PropSet) { + refs = append(refs, o.Obj) + } + } + + return refs +} diff --git a/simulator/dvs.go b/simulator/dvs.go index 9e32752d1..8894b854a 100644 --- a/simulator/dvs.go +++ b/simulator/dvs.go @@ -17,9 +17,12 @@ limitations under the License. package simulator import ( + "fmt" "strconv" "strings" + "github.com/google/uuid" + "github.com/vmware/govmomi/vim25/methods" "github.com/vmware/govmomi/vim25/mo" "github.com/vmware/govmomi/vim25/soap" @@ -46,7 +49,15 @@ func (s *DistributedVirtualSwitch) AddDVPortgroupTask(ctx *Context, c *types.Add // Standard AddDVPortgroupTask() doesn't allow duplicate names, but NSX 3.0 does create some DVPGs with the same name. // Allow duplicate names using this prefix so we can reproduce and test this condition. - if !strings.HasPrefix(pg.Name, "NSX-") { + if strings.HasPrefix(pg.Name, "NSX-") || spec.BackingType == string(types.DistributedVirtualPortgroupBackingTypeNsx) { + if spec.LogicalSwitchUuid == "" { + spec.LogicalSwitchUuid = uuid.New().String() + } + if spec.SegmentId == "" { + spec.SegmentId = fmt.Sprintf("/infra/segments/vnet_%s", uuid.New().String()) + } + + } else { if obj := Map.FindByName(pg.Name, f.ChildEntity); obj != nil { return nil, &types.DuplicateName{ Name: pg.Name, @@ -74,6 +85,7 @@ func (s *DistributedVirtualSwitch) AddDVPortgroupTask(ctx *Context, c *types.Add AutoExpand: spec.AutoExpand, VmVnicNetworkResourcePoolKey: spec.VmVnicNetworkResourcePoolKey, LogicalSwitchUuid: spec.LogicalSwitchUuid, + SegmentId: spec.SegmentId, BackingType: spec.BackingType, } diff --git a/simulator/entity.go b/simulator/entity.go index 7144298f2..5178da5a6 100644 --- a/simulator/entity.go +++ b/simulator/entity.go @@ -23,11 +23,12 @@ import ( "github.com/vmware/govmomi/vim25/types" ) -func RenameTask(ctx *Context, e mo.Entity, r *types.Rename_Task) soap.HasFault { +func RenameTask(ctx *Context, e mo.Entity, r *types.Rename_Task, dup ...bool) soap.HasFault { task := CreateTask(e, "rename", func(t *Task) (types.AnyType, types.BaseMethodFault) { obj := Map.Get(r.This).(mo.Entity).Entity() - if parent, ok := asFolderMO(Map.Get(*obj.Parent)); ok { + canDup := len(dup) == 1 && dup[0] + if parent, ok := asFolderMO(Map.Get(*obj.Parent)); ok && !canDup { if Map.FindByName(r.NewName, parent.ChildEntity) != nil { return nil, &types.InvalidArgument{InvalidProperty: "name"} } diff --git a/simulator/model.go b/simulator/model.go index dc1a8298d..8c3883d3f 100644 --- a/simulator/model.go +++ b/simulator/model.go @@ -669,9 +669,9 @@ func (m *Model) Create() error { for npg := 0; npg < m.PortgroupNSX; npg++ { name := m.fmtName(dcName+"_NSXPG", npg) spec := types.DVPortgroupConfigSpec{ - Name: name, - LogicalSwitchUuid: uuid.New().String(), - Type: string(types.DistributedVirtualPortgroupPortgroupTypeEarlyBinding), + Name: name, + Type: string(types.DistributedVirtualPortgroupPortgroupTypeEarlyBinding), + BackingType: string(types.DistributedVirtualPortgroupBackingTypeNsx), } task, err := dvs.AddPortgroup(ctx, []types.DVPortgroupConfigSpec{spec}) diff --git a/simulator/portgroup.go b/simulator/portgroup.go index 9f9705396..a59bd68f9 100644 --- a/simulator/portgroup.go +++ b/simulator/portgroup.go @@ -27,6 +27,12 @@ type DistributedVirtualPortgroup struct { mo.DistributedVirtualPortgroup } +func (s *DistributedVirtualPortgroup) RenameTask(ctx *Context, req *types.Rename_Task) soap.HasFault { + canDup := s.DistributedVirtualPortgroup.Config.BackingType == string(types.DistributedVirtualPortgroupBackingTypeNsx) + + return RenameTask(ctx, s, req, canDup) +} + func (s *DistributedVirtualPortgroup) ReconfigureDVPortgroupTask(ctx *Context, req *types.ReconfigureDVPortgroup_Task) soap.HasFault { task := CreateTask(s, "reconfigureDvPortgroup", func(t *Task) (types.AnyType, types.BaseMethodFault) { s.Config.DefaultPortConfig = req.Spec.DefaultPortConfig diff --git a/view/container_view.go b/view/container_view.go index c420acf9c..39041c41f 100644 --- a/view/container_view.go +++ b/view/container_view.go @@ -126,3 +126,20 @@ func (v ContainerView) Find(ctx context.Context, kind []string, filter property. return filter.MatchObjectContent(content), nil } + +// FindAny returns object references for entities of type kind, matching any property the given filter. +func (v ContainerView) FindAny(ctx context.Context, kind []string, filter property.Filter) ([]types.ManagedObjectReference, error) { + if len(filter) == 0 { + // Ensure we have at least 1 filter to avoid retrieving all properties. + filter = property.Filter{"name": "*"} + } + + var content []types.ObjectContent + + err := v.Retrieve(ctx, kind, filter.Keys(), &content) + if err != nil { + return nil, err + } + + return filter.MatchAnyObjectContent(content), nil +}