Skip to content

Commit

Permalink
Support finding Portgroups by ID in Finder.Network
Browse files Browse the repository at this point in the history
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
  • Loading branch information
dougm committed Oct 20, 2021
1 parent 086bb56 commit 6209be5
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 7 deletions.
53 changes: 53 additions & 0 deletions find/finder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
}

Expand All @@ -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 {
Expand Down
23 changes: 23 additions & 0 deletions govc/test/network.bats
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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" {
Expand Down
26 changes: 25 additions & 1 deletion property/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
}
14 changes: 13 additions & 1 deletion simulator/dvs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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,
Expand Down Expand Up @@ -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,
}

Expand Down
5 changes: 3 additions & 2 deletions simulator/entity.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"}
}
Expand Down
6 changes: 3 additions & 3 deletions simulator/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -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})
Expand Down
6 changes: 6 additions & 0 deletions simulator/portgroup.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 17 additions & 0 deletions view/container_view.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

0 comments on commit 6209be5

Please sign in to comment.