Skip to content

Commit 6209be5

Browse files
committed
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
1 parent 086bb56 commit 6209be5

File tree

8 files changed

+143
-7
lines changed

8 files changed

+143
-7
lines changed

find/finder.go

+53
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"github.com/vmware/govmomi/list"
2727
"github.com/vmware/govmomi/object"
2828
"github.com/vmware/govmomi/property"
29+
"github.com/vmware/govmomi/view"
2930
"github.com/vmware/govmomi/vim25"
3031
"github.com/vmware/govmomi/vim25/mo"
3132
"github.com/vmware/govmomi/vim25/types"
@@ -789,9 +790,26 @@ func (f *Finder) NetworkList(ctx context.Context, path string) ([]object.Network
789790
return ns, nil
790791
}
791792

793+
// Network finds a NetworkReference using a Name, Inventory Path, ManagedObject ID, Logical Switch UUID or Segment ID.
794+
// With standard vSphere networking, Portgroups cannot have the same name within the same network folder.
795+
// With NSX, Portgroups can have the same name, even within the same Switch. In this case, using an inventory path
796+
// results in a MultipleFoundError. A MOID, switch UUID or segment ID can be used instead, as both are unique.
797+
// See also: https://kb.vmware.com/s/article/79872#Duplicate_names
798+
// Examples:
799+
// - Name: "dvpg-1"
800+
// - Inventory Path: "vds-1/dvpg-1"
801+
// - ManagedObject ID: "DistributedVirtualPortgroup:dvportgroup-53"
802+
// - Logical Switch UUID: "da2a59b8-2450-4cb2-b5cc-79c4c1d2144c"
803+
// - Segment ID: "/infra/segments/vnet_ce50e69b-1784-4a14-9206-ffd7f1f146f7"
792804
func (f *Finder) Network(ctx context.Context, path string) (object.NetworkReference, error) {
793805
networks, err := f.NetworkList(ctx, path)
794806
if err != nil {
807+
if _, ok := err.(*NotFoundError); ok {
808+
net, nerr := f.networkByID(ctx, path)
809+
if nerr == nil {
810+
return net, nil
811+
}
812+
}
795813
return nil, err
796814
}
797815

@@ -802,6 +820,41 @@ func (f *Finder) Network(ctx context.Context, path string) (object.NetworkRefere
802820
return networks[0], nil
803821
}
804822

823+
func (f *Finder) networkByID(ctx context.Context, path string) (object.NetworkReference, error) {
824+
if ref := object.ReferenceFromString(path); ref != nil {
825+
// This is a MOID
826+
return object.NewReference(f.client, *ref).(object.NetworkReference), nil
827+
}
828+
829+
kind := []string{"DistributedVirtualPortgroup"}
830+
831+
m := view.NewManager(f.client)
832+
v, err := m.CreateContainerView(ctx, f.client.ServiceContent.RootFolder, kind, true)
833+
if err != nil {
834+
return nil, err
835+
}
836+
defer v.Destroy(ctx)
837+
838+
filter := property.Filter{
839+
"config.logicalSwitchUuid": path,
840+
"config.segmentId": path,
841+
}
842+
843+
refs, err := v.FindAny(ctx, kind, filter)
844+
if err != nil {
845+
return nil, err
846+
}
847+
848+
if len(refs) == 0 {
849+
return nil, &NotFoundError{"network", path}
850+
}
851+
if len(refs) > 1 {
852+
return nil, &MultipleFoundError{"network", path}
853+
}
854+
855+
return object.NewReference(f.client, refs[0]).(object.NetworkReference), nil
856+
}
857+
805858
func (f *Finder) DefaultNetwork(ctx context.Context) (object.NetworkReference, error) {
806859
network, err := f.Network(ctx, "*")
807860
if err != nil {

govc/test/network.bats

+23
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ load test_helper
4646
run govc dvs.portgroup.add -dvs DVS0 -type ephemeral NSX-dvpg
4747
assert_success
4848

49+
uuid=$(govc object.collect -s network/NSX-dvpg config.logicalSwitchUuid)
50+
sid=$(govc object.collect -s network/NSX-dvpg config.segmentId)
51+
moid=$(govc ls -i network/NSX-dvpg)
52+
4953
run govc dvs.portgroup.add -dvs DVS1 -type ephemeral NSX-dvpg
5054
assert_success
5155

@@ -54,6 +58,25 @@ load test_helper
5458

5559
run govc vm.network.add -vm $vm -net DVS0/NSX-dvpg
5660
assert_success # switch_name/portgroup_name is unique
61+
62+
# Add a 2nd PG to the same switch, with the same name
63+
run govc dvs.portgroup.add -dvs DVS0 -type ephemeral NSX-dvpg
64+
assert_success
65+
66+
run govc vm.network.add -vm $vm -net NSX-dvpg
67+
assert_failure # resolves to multiple networks
68+
69+
run govc vm.network.add -vm $vm -net DVS0/NSX-dvpg
70+
assert_failure # switch_name/portgroup_name not is unique
71+
72+
run govc vm.network.add -vm $vm -net "$uuid"
73+
assert_success # switch uuid is unique
74+
75+
run govc vm.network.add -vm $vm -net "$sid"
76+
assert_success # segment id is unique
77+
78+
run govc vm.network.add -vm $vm -net "$moid"
79+
assert_success # moid is unique
5780
}
5881

5982
@test "network change backing" {

property/filter.go

+25-1
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ func (f Filter) MatchPropertyList(props []types.DynamicProperty) bool {
130130
return len(f) == len(props) // false if a property such as VM "guest" is unset
131131
}
132132

133-
// MatchObjectContent returns a list of ObjectContent.Obj where the ObjectContent.PropSet matches the Filter.
133+
// MatchObjectContent returns a list of ObjectContent.Obj where the ObjectContent.PropSet matches all properties the Filter.
134134
func (f Filter) MatchObjectContent(objects []types.ObjectContent) []types.ManagedObjectReference {
135135
var refs []types.ManagedObjectReference
136136

@@ -142,3 +142,27 @@ func (f Filter) MatchObjectContent(objects []types.ObjectContent) []types.Manage
142142

143143
return refs
144144
}
145+
146+
// MatchAnyPropertyList returns true if any given props match the Filter.
147+
func (f Filter) MatchAnyPropertyList(props []types.DynamicProperty) bool {
148+
for _, p := range props {
149+
if f.MatchProperty(p) {
150+
return true
151+
}
152+
}
153+
154+
return false
155+
}
156+
157+
// MatchAnyObjectContent returns a list of ObjectContent.Obj where the ObjectContent.PropSet matches any property in the Filter.
158+
func (f Filter) MatchAnyObjectContent(objects []types.ObjectContent) []types.ManagedObjectReference {
159+
var refs []types.ManagedObjectReference
160+
161+
for _, o := range objects {
162+
if f.MatchAnyPropertyList(o.PropSet) {
163+
refs = append(refs, o.Obj)
164+
}
165+
}
166+
167+
return refs
168+
}

simulator/dvs.go

+13-1
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,12 @@ limitations under the License.
1717
package simulator
1818

1919
import (
20+
"fmt"
2021
"strconv"
2122
"strings"
2223

24+
"github.com/google/uuid"
25+
2326
"github.com/vmware/govmomi/vim25/methods"
2427
"github.com/vmware/govmomi/vim25/mo"
2528
"github.com/vmware/govmomi/vim25/soap"
@@ -46,7 +49,15 @@ func (s *DistributedVirtualSwitch) AddDVPortgroupTask(ctx *Context, c *types.Add
4649

4750
// Standard AddDVPortgroupTask() doesn't allow duplicate names, but NSX 3.0 does create some DVPGs with the same name.
4851
// Allow duplicate names using this prefix so we can reproduce and test this condition.
49-
if !strings.HasPrefix(pg.Name, "NSX-") {
52+
if strings.HasPrefix(pg.Name, "NSX-") || spec.BackingType == string(types.DistributedVirtualPortgroupBackingTypeNsx) {
53+
if spec.LogicalSwitchUuid == "" {
54+
spec.LogicalSwitchUuid = uuid.New().String()
55+
}
56+
if spec.SegmentId == "" {
57+
spec.SegmentId = fmt.Sprintf("/infra/segments/vnet_%s", uuid.New().String())
58+
}
59+
60+
} else {
5061
if obj := Map.FindByName(pg.Name, f.ChildEntity); obj != nil {
5162
return nil, &types.DuplicateName{
5263
Name: pg.Name,
@@ -74,6 +85,7 @@ func (s *DistributedVirtualSwitch) AddDVPortgroupTask(ctx *Context, c *types.Add
7485
AutoExpand: spec.AutoExpand,
7586
VmVnicNetworkResourcePoolKey: spec.VmVnicNetworkResourcePoolKey,
7687
LogicalSwitchUuid: spec.LogicalSwitchUuid,
88+
SegmentId: spec.SegmentId,
7789
BackingType: spec.BackingType,
7890
}
7991

simulator/entity.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,12 @@ import (
2323
"github.com/vmware/govmomi/vim25/types"
2424
)
2525

26-
func RenameTask(ctx *Context, e mo.Entity, r *types.Rename_Task) soap.HasFault {
26+
func RenameTask(ctx *Context, e mo.Entity, r *types.Rename_Task, dup ...bool) soap.HasFault {
2727
task := CreateTask(e, "rename", func(t *Task) (types.AnyType, types.BaseMethodFault) {
2828
obj := Map.Get(r.This).(mo.Entity).Entity()
2929

30-
if parent, ok := asFolderMO(Map.Get(*obj.Parent)); ok {
30+
canDup := len(dup) == 1 && dup[0]
31+
if parent, ok := asFolderMO(Map.Get(*obj.Parent)); ok && !canDup {
3132
if Map.FindByName(r.NewName, parent.ChildEntity) != nil {
3233
return nil, &types.InvalidArgument{InvalidProperty: "name"}
3334
}

simulator/model.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -669,9 +669,9 @@ func (m *Model) Create() error {
669669
for npg := 0; npg < m.PortgroupNSX; npg++ {
670670
name := m.fmtName(dcName+"_NSXPG", npg)
671671
spec := types.DVPortgroupConfigSpec{
672-
Name: name,
673-
LogicalSwitchUuid: uuid.New().String(),
674-
Type: string(types.DistributedVirtualPortgroupPortgroupTypeEarlyBinding),
672+
Name: name,
673+
Type: string(types.DistributedVirtualPortgroupPortgroupTypeEarlyBinding),
674+
BackingType: string(types.DistributedVirtualPortgroupBackingTypeNsx),
675675
}
676676

677677
task, err := dvs.AddPortgroup(ctx, []types.DVPortgroupConfigSpec{spec})

simulator/portgroup.go

+6
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ type DistributedVirtualPortgroup struct {
2727
mo.DistributedVirtualPortgroup
2828
}
2929

30+
func (s *DistributedVirtualPortgroup) RenameTask(ctx *Context, req *types.Rename_Task) soap.HasFault {
31+
canDup := s.DistributedVirtualPortgroup.Config.BackingType == string(types.DistributedVirtualPortgroupBackingTypeNsx)
32+
33+
return RenameTask(ctx, s, req, canDup)
34+
}
35+
3036
func (s *DistributedVirtualPortgroup) ReconfigureDVPortgroupTask(ctx *Context, req *types.ReconfigureDVPortgroup_Task) soap.HasFault {
3137
task := CreateTask(s, "reconfigureDvPortgroup", func(t *Task) (types.AnyType, types.BaseMethodFault) {
3238
s.Config.DefaultPortConfig = req.Spec.DefaultPortConfig

view/container_view.go

+17
Original file line numberDiff line numberDiff line change
@@ -126,3 +126,20 @@ func (v ContainerView) Find(ctx context.Context, kind []string, filter property.
126126

127127
return filter.MatchObjectContent(content), nil
128128
}
129+
130+
// FindAny returns object references for entities of type kind, matching any property the given filter.
131+
func (v ContainerView) FindAny(ctx context.Context, kind []string, filter property.Filter) ([]types.ManagedObjectReference, error) {
132+
if len(filter) == 0 {
133+
// Ensure we have at least 1 filter to avoid retrieving all properties.
134+
filter = property.Filter{"name": "*"}
135+
}
136+
137+
var content []types.ObjectContent
138+
139+
err := v.Retrieve(ctx, kind, filter.Keys(), &content)
140+
if err != nil {
141+
return nil, err
142+
}
143+
144+
return filter.MatchAnyObjectContent(content), nil
145+
}

0 commit comments

Comments
 (0)