Repository: vmware/govmomi
Pull request #1799 (2020-01-16 05:28:24)

vcsim: add support for NSX backed networks


Patch:
diff --git a/object/opaque_network.go b/object/opaque_network.go
index 8a3be010..6797d325 100644
--- a/object/opaque_network.go
+++ b/object/opaque_network.go
@@ -41,17 +41,11 @@ func (n OpaqueNetwork) GetInventoryPath() string {
 
 // EthernetCardBackingInfo returns the VirtualDeviceBackingInfo for this Network
 func (n OpaqueNetwork) EthernetCardBackingInfo(ctx context.Context) (types.BaseVirtualDeviceBackingInfo, error) {
-	var net mo.OpaqueNetwork
-
-	if err := n.Properties(ctx, n.Reference(), []string{"summary"}, &net); err != nil {
+	summary, err := n.Summary(ctx)
+	if err != nil {
 		return nil, err
 	}
 
-	summary, ok := net.Summary.(*types.OpaqueNetworkSummary)
-	if !ok {
-		return nil, fmt.Errorf("%s unsupported network type: %T", n, net.Summary)
-	}
-
 	backing := &types.VirtualEthernetCardOpaqueNetworkBackingInfo{
 		OpaqueNetworkId:   summary.OpaqueNetworkId,
 		OpaqueNetworkType: summary.OpaqueNetworkType,
@@ -59,3 +53,20 @@ func (n OpaqueNetwork) EthernetCardBackingInfo(ctx context.Context) (types.BaseV
 
 	return backing, nil
 }
+
+// Summary returns the mo.OpaqueNetwork.Summary property
+func (n OpaqueNetwork) Summary(ctx context.Context) (*types.OpaqueNetworkSummary, error) {
+	var props mo.OpaqueNetwork
+
+	err := n.Properties(ctx, n.Reference(), []string{"summary"}, &props)
+	if err != nil {
+		return nil, err
+	}
+
+	summary, ok := props.Summary.(*types.OpaqueNetworkSummary)
+	if !ok {
+		return nil, fmt.Errorf("%s unsupported network summary type: %T", n, props.Summary)
+	}
+
+	return summary, nil
+}
diff --git a/simulator/dvs.go b/simulator/dvs.go
index 30139014..c6a4e589 100644
--- a/simulator/dvs.go
+++ b/simulator/dvs.go
@@ -66,6 +66,14 @@ func (s *DistributedVirtualSwitch) AddDVPortgroupTask(c *types.AddDVPortgroup_Ta
 				ConfigVersion:                spec.ConfigVersion,
 				AutoExpand:                   spec.AutoExpand,
 				VmVnicNetworkResourcePoolKey: spec.VmVnicNetworkResourcePoolKey,
+				LogicalSwitchUuid:            spec.LogicalSwitchUuid,
+				BackingType:                  spec.BackingType,
+			}
+
+			if pg.Config.LogicalSwitchUuid != "" {
+				if pg.Config.BackingType == "" {
+					pg.Config.BackingType = "nsx"
+				}
 			}
 
 			if pg.Config.DefaultPortConfig == nil {
diff --git a/simulator/folder.go b/simulator/folder.go
index a15b4397..0f1dc2e1 100644
--- a/simulator/folder.go
+++ b/simulator/folder.go
@@ -17,11 +17,13 @@ limitations under the License.
 package simulator
 
 import (
+	"errors"
 	"fmt"
 	"math/rand"
 	"path"
 	"strings"
 
+	"github.com/google/uuid"
 	"github.com/vmware/govmomi/object"
 	"github.com/vmware/govmomi/vim25/methods"
 	"github.com/vmware/govmomi/vim25/mo"
@@ -112,6 +114,38 @@ func (f *Folder) typeNotSupported() *soap.Fault {
 	return Fault(fmt.Sprintf("%s supports types: %#v", f.Self, f.ChildType), &types.NotSupported{})
 }
 
+// AddOpaqueNetwork adds an OpaqueNetwork type to the inventory, with default backing to that of an nsx.LogicalSwitch.
+// The vSphere API does not have a method to add this directly, so it must either be called directly or via Model.OpaqueNetwork setting.
+func (f *Folder) AddOpaqueNetwork(summary types.OpaqueNetworkSummary) error {
+	if !f.hasChildType("Network") {
+		return errors.New("not a network folder")
+	}
+
+	if summary.OpaqueNetworkId == "" {
+		summary.OpaqueNetworkId = uuid.New().String()
+	}
+	if summary.OpaqueNetworkType == "" {
+		summary.OpaqueNetworkType = "nsx.LogicalSwitch"
+	}
+	if summary.Name == "" {
+		summary.Name = summary.OpaqueNetworkType + "-" + summary.OpaqueNetworkId
+	}
+
+	net := new(mo.OpaqueNetwork)
+	if summary.Network == nil {
+		summary.Network = &net.Self
+	} else {
+		net.Self = *summary.Network
+	}
+	summary.Accessible = true
+	net.Network.Name = summary.Name
+	net.Summary = &summary
+
+	f.putChild(net)
+
+	return nil
+}
+
 type addStandaloneHost struct {
 	*Folder
 
diff --git a/simulator/model.go b/simulator/model.go
index 67295e09..ee8e926f 100644
--- a/simulator/model.go
+++ b/simulator/model.go
@@ -27,6 +27,7 @@ import (
 	"path/filepath"
 	"reflect"
 
+	"github.com/google/uuid"
 	"github.com/vmware/govmomi"
 	"github.com/vmware/govmomi/object"
 	"github.com/vmware/govmomi/simulator/esx"
@@ -71,6 +72,13 @@ type Model struct {
 	// Portgroup specifies the number of DistributedVirtualPortgroup entities to create per Datacenter
 	Portgroup int
 
+	// PortgroupNSX specifies the number NSX backed DistributedVirtualPortgroup entities to create per Datacenter
+	PortgroupNSX int
+
+	// OpaqueNetwork specifies the number of OpaqueNetwork entities to create per Datacenter,
+	// with Summary.OpaqueNetworkType set to nsx.LogicalSwitch and Summary.OpaqueNetworkId to a random uuid.
+	OpaqueNetwork int
+
 	// Host specifies the number of standalone HostSystems entities to create per Datacenter
 	Host int `json:",omitempty"`
 
@@ -180,6 +188,8 @@ func (m *Model) Count() Model {
 			count.Folder++
 		case "StoragePod":
 			count.Pod++
+		case "OpaqueNetwork":
+			count.OpaqueNetwork++
 		}
 	}
 
@@ -525,7 +535,7 @@ func (m *Model) Create() error {
 			nfolder++
 		}
 
-		if m.Portgroup > 0 {
+		if m.Portgroup > 0 || m.PortgroupNSX > 0 {
 			var spec types.DVSCreateSpec
 			spec.ConfigSpec = &types.VMwareDVSConfigSpec{}
 			spec.ConfigSpec.GetDVSConfigSpec().Name = m.fmtName("DVS", 0)
@@ -541,28 +551,54 @@ func (m *Model) Create() error {
 			}
 
 			dvs = object.NewDistributedVirtualSwitch(client, info.Result.(types.ManagedObjectReference))
+		}
 
-			for npg := 0; npg < m.Portgroup; npg++ {
-				name := m.fmtName(dcName+"_DVPG", npg)
+		for npg := 0; npg < m.Portgroup; npg++ {
+			name := m.fmtName(dcName+"_DVPG", npg)
 
-				task, err = dvs.AddPortgroup(ctx, []types.DVPortgroupConfigSpec{{Name: name}})
-				if err != nil {
-					return err
-				}
+			task, err := dvs.AddPortgroup(ctx, []types.DVPortgroupConfigSpec{{Name: name}})
+			if err != nil {
+				return err
+			}
+			if err = task.Wait(ctx); err != nil {
+				return err
+			}
 
-				err = task.Wait(ctx)
-				if err != nil {
-					return err
-				}
+			// Use the 1st DVPG for the VMs eth0 backing
+			if npg == 0 {
+				// AddPortgroup_Task does not return the moid, so we look it up by name
+				net := Map.Get(folders.NetworkFolder.Reference()).(*Folder)
+				pg := Map.FindByName(name, net.ChildEntity)
 
-				// Use the 1st DVPG for the VMs eth0 backing
-				if npg == 0 {
-					// AddPortgroup_Task does not return the moid, so we look it up by name
-					net := Map.Get(folders.NetworkFolder.Reference()).(*Folder)
-					pg := Map.FindByName(name, net.ChildEntity)
+				vmnet, _ = object.NewDistributedVirtualPortgroup(client, pg.Reference()).EthernetCardBackingInfo(ctx)
+			}
+		}
 
-					vmnet, _ = object.NewDistributedVirtualPortgroup(client, pg.Reference()).EthernetCardBackingInfo(ctx)
-				}
+		for npg := 0; npg < m.PortgroupNSX; npg++ {
+			name := m.fmtName(dcName+"_NSXPG", npg)
+			spec := types.DVPortgroupConfigSpec{
+				Name:              name,
+				LogicalSwitchUuid: uuid.New().String(),
+			}
+
+			task, err := dvs.AddPortgroup(ctx, []types.DVPortgroupConfigSpec{spec})
+			if err != nil {
+				return err
+			}
+			if err = task.Wait(ctx); err != nil {
+				return err
+			}
+		}
+
+		// Must use simulator methods directly for OpaqueNetwork
+		networkFolder := Map.Get(folders.NetworkFolder.Reference()).(*Folder)
+
+		for i := 0; i < m.OpaqueNetwork; i++ {
+			var summary types.OpaqueNetworkSummary
+			summary.Name = m.fmtName(dcName+"_NSX", i)
+			err := networkFolder.AddOpaqueNetwork(summary)
+			if err != nil {
+				return err
 			}
 		}
 
diff --git a/simulator/portgroup.go b/simulator/portgroup.go
index af93a500..7abe1970 100644
--- a/simulator/portgroup.go
+++ b/simulator/portgroup.go
@@ -39,6 +39,8 @@ func (s *DistributedVirtualPortgroup) ReconfigureDVPortgroupTask(req *types.Reco
 		s.Config.Policy = req.Spec.Policy
 		s.Config.PortNameFormat = req.Spec.PortNameFormat
 		s.Config.VmVnicNetworkResourcePoolKey = req.Spec.VmVnicNetworkResourcePoolKey
+		s.Config.LogicalSwitchUuid = req.Spec.LogicalSwitchUuid
+		s.Config.BackingType = req.Spec.BackingType
 
 		return nil, nil
 	})
diff --git a/vcsim/main.go b/vcsim/main.go
index 9cc73d5a..dd644ab5 100644
--- a/vcsim/main.go
+++ b/vcsim/main.go
@@ -59,6 +59,8 @@ func main() {
 	flag.IntVar(&model.App, "app", model.App, "Number of virtual apps per compute resource")
 	flag.IntVar(&model.Pod, "pod", model.Pod, "Number of storage pods per datacenter")
 	flag.IntVar(&model.Portgroup, "pg", model.Portgroup, "Number of port groups")
+	flag.IntVar(&model.PortgroupNSX, "pg-nsx", model.PortgroupNSX, "Number of NSX backed port groups")
+	flag.IntVar(&model.OpaqueNetwork, "nsx", model.OpaqueNetwork, "Number of NSX backed opaque networks")
 	flag.IntVar(&model.Folder, "folder", model.Folder, "Number of folders")
 	flag.BoolVar(&model.Autostart, "autostart", model.Autostart, "Autostart model created VMs")
 	v := &model.ServiceContent.About.ApiVersion
diff --git a/vim25/types/types.go b/vim25/types/types.go
index 6f00415e..988f1c97 100644
--- a/vim25/types/types.go
+++ b/vim25/types/types.go
@@ -11866,6 +11866,11 @@ type DVPortgroupConfigInfo struct {
 	AutoExpand                   *bool                                     `xml:"autoExpand"`
 	VmVnicNetworkResourcePoolKey string                                    `xml:"vmVnicNetworkResourcePoolKey,omitempty"`
 	Uplink                       *bool                                     `xml:"uplink"`
+	LogicalSwitchUuid            string                                    `xml:"logicalSwitchUuid,omitempty"`
+	SegmentId                    string                                    `xml:"segmentId,omitempty"`
+	TransportZoneName            string                                    `xml:"transportZoneName,omitempty"`
+	TransportZoneUuid            string                                    `xml:"transportZoneUuid,omitempty"`
+	BackingType                  string                                    `xml:"backingType,omitempty"`
 }
 
 func init() {
@@ -11887,6 +11892,11 @@ type DVPortgroupConfigSpec struct {
 	VendorSpecificConfig         []DistributedVirtualSwitchKeyedOpaqueBlob `xml:"vendorSpecificConfig,omitempty"`
 	AutoExpand                   *bool                                     `xml:"autoExpand"`
 	VmVnicNetworkResourcePoolKey string                                    `xml:"vmVnicNetworkResourcePoolKey,omitempty"`
+	LogicalSwitchUuid            string                                    `xml:"logicalSwitchUuid,omitempty"`
+	SegmentId                    string                                    `xml:"segmentId,omitempty"`
+	TransportZoneName            string                                    `xml:"transportZoneName,omitempty"`
+	TransportZoneUuid            string                                    `xml:"transportZoneUuid,omitempty"`
+	BackingType                  string                                    `xml:"backingType,omitempty"`
 }
 
 func init() {


Test patch:
diff --git a/object/opaque_network_test.go b/object/opaque_network_test.go
new file mode 100644
index 00000000..206ece64
--- /dev/null
+++ b/object/opaque_network_test.go
@@ -0,0 +1,63 @@
+/*
+Copyright (c) 2020 VMware, Inc. All Rights Reserved.
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+    http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package object_test
+
+import (
+	"context"
+	"testing"
+
+	"github.com/google/uuid"
+	"github.com/vmware/govmomi/find"
+	"github.com/vmware/govmomi/object"
+	"github.com/vmware/govmomi/simulator"
+	"github.com/vmware/govmomi/vim25"
+)
+
+func TestOpaqueNetworkEthernetCardBackingInfo(t *testing.T) {
+	model := simulator.VPX()
+	model.OpaqueNetwork = 1 // Create 1 NSX backed OpaqueNetwork per DC
+
+	simulator.Test(func(ctx context.Context, c *vim25.Client) {
+		finder := find.NewFinder(c)
+
+		net, err := finder.Network(ctx, "DC0_NSX0")
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		nsx, ok := net.(*object.OpaqueNetwork)
+		if !ok {
+			t.Fatalf("network type=%T", net)
+		}
+
+		summary, err := nsx.Summary(ctx)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		if net.Reference() != *summary.Network {
+			t.Fatal()
+		}
+
+		_, err = uuid.Parse(summary.OpaqueNetworkId)
+		if err != nil {
+			t.Errorf("parsing %q: %s", summary.OpaqueNetworkId, err)
+		}
+
+		_, err = net.EthernetCardBackingInfo(ctx)
+		if err != nil {
+			t.Fatal(err)
+		}
+	}, model)
+}
diff --git a/simulator/example_test.go b/simulator/example_test.go
index 3c6150bc..97a341d0 100644
--- a/simulator/example_test.go
+++ b/simulator/example_test.go
@@ -22,9 +22,13 @@ import (
 	"log"
 
 	"github.com/vmware/govmomi"
+	"github.com/vmware/govmomi/find"
+	"github.com/vmware/govmomi/object"
 	"github.com/vmware/govmomi/session"
 	"github.com/vmware/govmomi/simulator"
 	"github.com/vmware/govmomi/vim25"
+	"github.com/vmware/govmomi/vim25/mo"
+	"github.com/vmware/govmomi/vim25/types"
 )
 
 // Example boilerplate for starting a simulator initialized with an ESX model.
@@ -124,3 +128,94 @@ func ExampleTest() {
 	})
 	// Output: user
 }
+
+// Folder.AddOpaqueNetwork can be used to create an NSX backed OpaqueNetwork.
+func ExampleFolder_AddOpaqueNetwork() {
+	simulator.Run(func(ctx context.Context, c *vim25.Client) error {
+		finder := find.NewFinder(c)
+
+		// Find the network folder via vSphere API
+		obj, err := finder.Folder(ctx, "network")
+		if err != nil {
+			return err
+		}
+
+		// Get vcsim's network Folder object
+		folder := simulator.Map.Get(obj.Reference()).(*simulator.Folder)
+
+		spec := types.OpaqueNetworkSummary{
+			NetworkSummary: types.NetworkSummary{
+				Name: "my-nsx-network",
+			},
+			OpaqueNetworkId:   "my-nsx-id",
+			OpaqueNetworkType: "nsx.LogicalSwitch",
+		}
+
+		// Add NSX backed OpaqueNetwork, calling the simulator.Folder method directly
+		err = folder.AddOpaqueNetwork(spec)
+		if err != nil {
+			return err
+		}
+
+		// Find the OpaqueNetwork via vSphere API
+		net, err := finder.Network(ctx, spec.Name)
+		if err != nil {
+			return err
+		}
+
+		nsx := net.(*object.OpaqueNetwork)
+		summary, err := nsx.Summary(ctx)
+		if err != nil {
+			return err
+		}
+
+		// The summary fields should match those of the spec used to create it
+		fmt.Printf("%s: %s", nsx.Name(), summary.OpaqueNetworkId)
+		return nil
+	})
+	// Output: my-nsx-network: my-nsx-id
+}
+
+// AddDVPortgroup against vcsim can create both standard and nsx backed DistributedVirtualPortgroup networks
+func ExampleDistributedVirtualSwitch_AddDVPortgroupTask() {
+	simulator.Run(func(ctx context.Context, c *vim25.Client) error {
+		finder := find.NewFinder(c)
+
+		dvs0, err := finder.Network(ctx, "DVS0")
+		if err != nil {
+			return err
+		}
+
+		spec := types.DVPortgroupConfigSpec{
+			Name:              "my-nsx-dvpg",
+			LogicalSwitchUuid: "my-nsx-id",
+		}
+
+		dvs := dvs0.(*object.DistributedVirtualSwitch)
+		task, err := dvs.AddPortgroup(ctx, []types.DVPortgroupConfigSpec{spec})
+		if err != nil {
+			return err
+		}
+		if err = task.Wait(ctx); err != nil {
+			return err
+		}
+
+		pg0, err := finder.Network(ctx, spec.Name)
+		if err != nil {
+			return err
+		}
+
+		pg := pg0.(*object.DistributedVirtualPortgroup)
+
+		var props mo.DistributedVirtualPortgroup
+		err = pg.Properties(ctx, pg.Reference(), []string{"config"}, &props)
+		if err != nil {
+			return err
+		}
+
+		fmt.Printf("%s: %s %s", pg.Name(), props.Config.BackingType, props.Config.LogicalSwitchUuid)
+
+		return nil
+	})
+	// Output: my-nsx-dvpg: nsx my-nsx-id
+}
diff --git a/simulator/model_test.go b/simulator/model_test.go
index 69c3775a..1503049a 100644
--- a/simulator/model_test.go
+++ b/simulator/model_test.go
@@ -31,7 +31,7 @@ func compareModel(t *testing.T, m *Model) {
 	pools := (m.Pool * m.Cluster * m.Datacenter) + (m.Host+m.Cluster)*m.Datacenter
 	// root folder + Datacenter folders {host,vm,datastore,network} + top-level folders
 	folders := 1 + (4 * m.Datacenter) + (5 * m.Folder)
-	pgs := m.Portgroup
+	pgs := m.Portgroup + m.PortgroupNSX
 	if pgs > 0 {
 		pgs++ // uplinks
 	}
@@ -44,6 +44,7 @@ func compareModel(t *testing.T, m *Model) {
 		{m.Datacenter, count.Datacenter, "Datacenter"},
 		{m.Cluster * m.Datacenter, count.Cluster, "Cluster"},
 		{pgs * m.Datacenter, count.Portgroup, "Portgroup"},
+		{m.OpaqueNetwork * m.Datacenter, count.OpaqueNetwork, "OpaqueNetwork"},
 		{m.Datastore * m.Datacenter, count.Datastore, "Datastore"},
 		{hosts, count.Host, "Host"},
 		{vms, count.Machine, "VirtualMachine"},
@@ -106,6 +107,22 @@ func TestModelNoSwitchVPX(t *testing.T) {
 	compareModel(t, m)
 }
 
+func TestModelNSX(t *testing.T) {
+	m := VPX()
+	m.Portgroup = 0
+	m.PortgroupNSX = 1
+	m.OpaqueNetwork = 1
+
+	defer m.Remove()
+
+	err := m.Create()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	compareModel(t, m)
+}
+
 func TestModelCustomVPX(t *testing.T) {
 	m := &Model{
 		ServiceContent: vpx.ServiceContent,
diff --git a/simulator/portgroup_test.go b/simulator/portgroup_test.go
index 1ccfdf65..de784cf4 100644
--- a/simulator/portgroup_test.go
+++ b/simulator/portgroup_test.go
@@ -18,9 +18,12 @@ package simulator
 
 import (
 	"context"
+	"strings"
 	"testing"
 
+	"github.com/google/uuid"
 	"github.com/vmware/govmomi/object"
+	"github.com/vmware/govmomi/vim25"
 	"github.com/vmware/govmomi/vim25/types"
 )
 
@@ -124,3 +127,33 @@ func TestPortgroupBacking(t *testing.T) {
 		t.Error("expected error")
 	}
 }
+
+func TestPortgroupBackingWithNSX(t *testing.T) {
+	model := VPX()
+	model.Portgroup = 0
+	model.PortgroupNSX = 1
+
+	Test(func(context.Context, *vim25.Client) {
+		pgs := Map.All("DistributedVirtualPortgroup")
+		n := len(pgs) - 1
+		if model.PortgroupNSX != n {
+			t.Errorf("%d pgs", n)
+		}
+
+		for _, obj := range pgs {
+			pg := obj.(*DistributedVirtualPortgroup)
+			if strings.Contains(pg.Name, "DVUplinks") {
+				continue
+			}
+
+			if pg.Config.BackingType != "nsx" {
+				t.Errorf("backing=%q", pg.Config.BackingType)
+			}
+
+			_, err := uuid.Parse(pg.Config.LogicalSwitchUuid)
+			if err != nil {
+				t.Errorf("parsing %q: %s", pg.Config.LogicalSwitchUuid, err)
+			}
+		}
+	}, model)
+}