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

Add support for DVPG backed imported NSX-T Org VDC networks #564

Merged
merged 10 commits into from
Apr 13, 2023
Merged
4 changes: 4 additions & 0 deletions .changes/v2.20.0/654-features.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
* Add support for importable Distributed Virtual Port Group (DVPG) read via types
`VcenterImportableDvpg` and `types.VcenterImportableDvpg` and methods
`VCDClient.GetVcenterImportableDvpgByName`, `VCDClient.GetAllVcenterImportableDvpgs`,
`Vdc.GetVcenterImportableDvpgByName`, `Vdc.GetAllVcenterImportableDvpgs` [GH-654]
1 change: 1 addition & 0 deletions govcd/api_vcd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ type TestConfig struct {
Manager string `yaml:"manager"`
Tier0router string `yaml:"tier0router"`
Tier0routerVrf string `yaml:"tier0routerVrf"`
Dvpg string `yaml:"dvpg"`
Vdc string `yaml:"vdc"`
ExternalNetwork string `yaml:"externalNetwork"`
EdgeGateway string `yaml:"edgeGateway"`
Expand Down
38 changes: 38 additions & 0 deletions govcd/generic_functions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package govcd

import "fmt"

// oneOrError is used to cover up a common pattern in this codebase which is usually used in
// GetXByName functions.
// API endpoint returns N elements for an object we are looking (most commonly because API does not
// support filtering) and final filtering by Name must be done in code.
// After filtering returned entities one must be sure that exactly one was found and handle 3 cases:
// * If 0 entities are found - an error containing ErrorEntityNotFound must be returned
// * If >1 entities are found - an error containing the number of entities must be returned
// * If 1 entity was found - return it
//
// An example of code that was previously handled in non generic way - we had a lot of these
// occurrences throughout the code:
//
// if len(nsxtEdgeClusters) == 0 {
// // ErrorEntityNotFound is injected here for the ability to validate problem using ContainsNotFound()
// return nil, fmt.Errorf("%s: no NSX-T Tier-0 Edge Cluster with name '%s' for Org VDC with id '%s' found",
// ErrorEntityNotFound, name, vdc.Vdc.ID)
// }

// if len(nsxtEdgeClusters) > 1 {
// return nil, fmt.Errorf("more than one (%d) NSX-T Edge Cluster with name '%s' for Org VDC with id '%s' found",
// len(nsxtEdgeClusters), name, vdc.Vdc.ID)
// }
func oneOrError[T any](key, name string, entitySlice []*T) (*T, error) {
if len(entitySlice) > 1 {
return nil, fmt.Errorf("got more than one entity by %s '%s' %d", key, name, len(entitySlice))
}

if len(entitySlice) == 0 {
// No entity found - returning ErrorEntityNotFound as it must be wrapped in the returned error
return nil, fmt.Errorf("%s: got zero entities by %s '%s'", ErrorEntityNotFound, key, name)
}

return entitySlice[0], nil
}
122 changes: 122 additions & 0 deletions govcd/importable_dvpg.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
* Copyright 2023 VMware, Inc. All rights reserved. Licensed under the Apache v2 License.
*/

package govcd

import (
"fmt"
"net/url"

"github.com/vmware/go-vcloud-director/v2/types/v56"
)

// VcenterImportableDvpg is a read only structure that allows to get information about a Distributed
// Virtual Port Group (DVPG) network backing that is available for import.
//
// Note. API returns only unused DVPGs. If the DVPG is already consumed - it will not be returned.
type VcenterImportableDvpg struct {
VcenterImportableDvpg *types.VcenterImportableDvpg
client *Client
}

// GetVcenterImportableDvpgByName retrieves a DVPG by name
//
// Note. API returns only unused DVPGs. If the DVPG is already consumed - it will not be returned.
func (vcdClient *VCDClient) GetVcenterImportableDvpgByName(name string) (*VcenterImportableDvpg, error) {
if name == "" {
return nil, fmt.Errorf("empty importable Distributed Virtual Port Group Name specified")
}

vcImportableDvpgs, err := vcdClient.GetAllVcenterImportableDvpgs(nil)
if err != nil {
return nil, fmt.Errorf("could not find Distributed Virtual Port Group with Name '%s' for vCenter with ID '%s': %s",
name, "", err)
}

filteredVcImportableDvpgs := filterVcImportableDvpgsByName(name, vcImportableDvpgs)

return oneOrError("name", name, filteredVcImportableDvpgs)
}

// GetAllVcenterImportableDvpgs retrieves all DVPGs that are available for import.
//
// Note. API returns only unused DVPGs. If the DVPG is already consumed - it will not be returned.
func (vcdClient *VCDClient) GetAllVcenterImportableDvpgs(queryParameters url.Values) ([]*VcenterImportableDvpg, error) {
return getAllVcenterImportableDvpgs(&vcdClient.Client, queryParameters)
}

// GetVcenterImportableDvpgByName retrieves a DVPG that is available for import within the Org VDC.
func (vdc *Vdc) GetVcenterImportableDvpgByName(name string) (*VcenterImportableDvpg, error) {
if name == "" {
return nil, fmt.Errorf("empty importable Distributed Virtual Port Group Name specified")
}

vcImportableDvpgs, err := vdc.GetAllVcenterImportableDvpgs(nil)
if err != nil {
return nil, fmt.Errorf("could not find Distributed Virtual Port Group with name '%s': %s", name, err)
}

filteredVcImportableDvpgs := filterVcImportableDvpgsByName(name, vcImportableDvpgs)

return oneOrError("name", name, filteredVcImportableDvpgs)
}

// GetAllVcenterImportableDvpgs retrieves all DVPGs that are available for import within the Org VDC.
//
// Note. API returns only unused DVPGs. If the DVPG is already consumed - it will not be returned.
func (vdc *Vdc) GetAllVcenterImportableDvpgs(queryParameters url.Values) ([]*VcenterImportableDvpg, error) {
if vdc == nil || vdc.Vdc == nil || vdc.Vdc.ID == "" {
return nil, fmt.Errorf("cannot get Importable DVPGs without VDC ID")
}

queryParams := copyOrNewUrlValues(queryParameters)
queryParams = queryParameterFilterAnd("orgVdcId=="+vdc.Vdc.ID, queryParams)

return getAllVcenterImportableDvpgs(vdc.client, queryParams)

}

func getAllVcenterImportableDvpgs(client *Client, queryParameters url.Values) ([]*VcenterImportableDvpg, error) {
endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointImportableDvpgs
apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint)
if err != nil {
return nil, err
}

urlRef, err := client.OpenApiBuildEndpoint(endpoint)
if err != nil {
return nil, err
}

queryParams := copyOrNewUrlValues(queryParameters)

typeResponses := []*types.VcenterImportableDvpg{{}}
err = client.OpenApiGetAllItems(apiVersion, urlRef, queryParams, &typeResponses, nil)
if err != nil {
return nil, err
}

returnObjects := make([]*VcenterImportableDvpg, len(typeResponses))
for sliceIndex := range typeResponses {
returnObjects[sliceIndex] = &VcenterImportableDvpg{
VcenterImportableDvpg: typeResponses[sliceIndex],
client: client,
}
}

return returnObjects, nil
}

// filterVcImportableDvpgsByName is created as a fix for local filtering instead of using
// FIQL filter (because it does not support it).
func filterVcImportableDvpgsByName(name string, allNVcImportableDvpgs []*VcenterImportableDvpg) []*VcenterImportableDvpg {
filteredVcImportableDvpgs := make([]*VcenterImportableDvpg, 0)
for index, VcImportableDvpg := range allNVcImportableDvpgs {
if allNVcImportableDvpgs[index].VcenterImportableDvpg.BackingRef.Name == name {
filteredVcImportableDvpgs = append(filteredVcImportableDvpgs, VcImportableDvpg)
}
}

return filteredVcImportableDvpgs
}
50 changes: 50 additions & 0 deletions govcd/importable_dvpg_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//go:build network || nsxt || functional || openapi || ALL

/*
* Copyright 2023 VMware, Inc. All rights reserved. Licensed under the Apache v2 License.
*/

package govcd

import (
"fmt"

. "gopkg.in/check.v1"
)

func (vcd *TestVCD) Test_VcenterImportableDvpg(check *C) {
if vcd.skipAdminTests {
check.Skip(fmt.Sprintf(TestRequiresSysAdminPrivileges, check.TestName()))
}
skipNoNsxtConfiguration(vcd, check)

if vcd.config.VCD.Nsxt.Dvpg == "" {
check.Skip("No NSX-T Dvpg provided")
}

// Get all DVPGs
dvpgs, err := vcd.client.GetAllVcenterImportableDvpgs(nil)
check.Assert(err, IsNil)
check.Assert(len(dvpgs) > 0, Equals, true)

// Get DVPG by name
dvpgByName, err := vcd.client.GetVcenterImportableDvpgByName(vcd.config.VCD.Nsxt.Dvpg)
check.Assert(err, IsNil)
check.Assert(dvpgByName, NotNil)
check.Assert(dvpgByName.VcenterImportableDvpg.BackingRef.Name, Equals, vcd.config.VCD.Nsxt.Dvpg)

// Get all DVPGs withing NSX-T VDC
nsxtVdc, err := vcd.org.GetVDCByName(vcd.config.VCD.Nsxt.Vdc, false)
check.Assert(err, IsNil)
check.Assert(nsxtVdc, NotNil)

allDvpgsWithingVdc, err := nsxtVdc.GetAllVcenterImportableDvpgs(nil)
check.Assert(err, IsNil)
check.Assert(len(allDvpgsWithingVdc) > 0, Equals, true)

// Get DVPG by name within NSX-T VDC
dvpgByNameWithinVdc, err := nsxtVdc.GetVcenterImportableDvpgByName(vcd.config.VCD.Nsxt.Dvpg)
check.Assert(err, IsNil)
check.Assert(dvpgByNameWithinVdc, NotNil)
check.Assert(dvpgByNameWithinVdc.VcenterImportableDvpg.BackingRef.Name, Equals, vcd.config.VCD.Nsxt.Dvpg)
}
1 change: 1 addition & 0 deletions govcd/openapi_endpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ var endpointMinApiVersions = map[string]string{
types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointRoles + types.OpenApiEndpointRights: "31.0",
types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointAuditTrail: "33.0",
types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointImportableTier0Routers: "32.0",
types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointImportableDvpgs: "36.0",
types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointTestConnection: "34.0",
// OpenApiEndpointExternalNetworks endpoint support was introduced with version 32.0 however it was still not stable
// enough to be used. (i.e. it did not support update "PUT")
Expand Down
55 changes: 54 additions & 1 deletion govcd/openapi_org_network_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ func (vcd *TestVCD) Test_NsxtOrgVdcNetworkRouted(check *C) {
runOpenApiOrgVdcNetworkWithVdcGroupTest(check, vcd, orgVdcNetworkConfig, types.OrgVdcNetworkTypeRouted, []dhcpConfigFunc{nsxtRoutedDhcpConfigEdgeMode, nsxtDhcpConfigNetworkMode})
}

func (vcd *TestVCD) Test_NsxtOrgVdcNetworkImported(check *C) {
func (vcd *TestVCD) Test_NsxtOrgVdcNetworkImportedNsxtLogicalSwitch(check *C) {
if vcd.skipAdminTests {
check.Skip(fmt.Sprintf(TestRequiresSysAdminPrivileges, check.TestName()))
}
Expand Down Expand Up @@ -165,6 +165,59 @@ func (vcd *TestVCD) Test_NsxtOrgVdcNetworkImported(check *C) {
runOpenApiOrgVdcNetworkWithVdcGroupTest(check, vcd, orgVdcNetworkConfig, types.OrgVdcNetworkTypeOpaque, nil)
}

func (vcd *TestVCD) Test_NsxtOrgVdcNetworkImportedDistributedVirtualPortGroup(check *C) {
if vcd.skipAdminTests {
check.Skip(fmt.Sprintf(TestRequiresSysAdminPrivileges, check.TestName()))
}
skipOpenApiEndpointTest(vcd, check, types.OpenApiPathVersion1_0_0+types.OpenApiEndpointOrgVdcNetworks)
skipNoNsxtConfiguration(vcd, check)

if vcd.config.VCD.Nsxt.Dvpg == "" {
check.Skip("Distributed Virtual Port Group was not provided")
}

dvpg, err := vcd.nsxtVdc.GetVcenterImportableDvpgByName(vcd.config.VCD.Nsxt.Dvpg)
check.Assert(err, IsNil)

orgVdcNetworkConfig := &types.OpenApiOrgVdcNetwork{
Name: check.TestName(),
Description: check.TestName() + "-description",

OwnerRef: &types.OpenApiReference{ID: vcd.nsxtVdc.Vdc.ID},

NetworkType: types.OrgVdcNetworkTypeOpaque,
// BackingNetworkId contains Distributed Virtual Port Group ID for Imported networks
BackingNetworkId: dvpg.VcenterImportableDvpg.BackingRef.ID,
BackingNetworkType: types.OrgVdcNetworkBackingTypeDvPortgroup,

Subnets: types.OrgVdcNetworkSubnets{
Values: []types.OrgVdcNetworkSubnetValues{
{
Gateway: "2.1.1.1",
PrefixLength: 24,
DNSServer1: "8.8.8.8",
DNSServer2: "8.8.4.4",
DNSSuffix: "foo.bar",
IPRanges: types.OrgVdcNetworkSubnetIPRanges{
Values: []types.OrgVdcNetworkSubnetIPRangeValues{
{
StartAddress: "2.1.1.20",
EndAddress: "2.1.1.30",
},
{
StartAddress: "2.1.1.40",
EndAddress: "2.1.1.50",
},
}},
},
},
},
}

// Org VDC network backed by Distributed Virtual Port Group can only be created in VDC (not VDC Group)
runOpenApiOrgVdcNetworkTest(check, vcd, vcd.nsxtVdc, orgVdcNetworkConfig, types.OrgVdcNetworkTypeOpaque, nil)
}

func (vcd *TestVCD) Test_NsxvOrgVdcNetworkIsolated(check *C) {
skipOpenApiEndpointTest(vcd, check, types.OpenApiPathVersion1_0_0+types.OpenApiEndpointOrgVdcNetworks)

Expand Down
2 changes: 2 additions & 0 deletions govcd/sample_govcd_test_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ vcd:
externalNetwork: tier0-backed-external-network
# Existing NSX-T based VDC
vdc: nsxt-vdc-name
# Distributed Virtual Port Group in vSphere that is available for NSX-T cluster
dvpg: test-nsxt-dvpg-no-uplink
# Existing NSX-T edge gateway
edgeGateway: nsxt-gw-name
# Existing NSX-T segment to test NSX-T Imported Org Vdc network
Expand Down
14 changes: 12 additions & 2 deletions types/v56/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,7 @@ const (
OpenApiEndpointAuditTrail = "auditTrail/"
OpenApiEndpointImportableTier0Routers = "nsxTResources/importableTier0Routers"
OpenApiEndpointImportableSwitches = "/network/orgvdcnetworks/importableswitches"
OpenApiEndpointImportableDvpgs = "virtualCenters/resources/importableDvpgs"
OpenApiEndpointEdgeClusters = "nsxTResources/edgeClusters"
OpenApiEndpointExternalNetworks = "externalNetworks/"
OpenApiEndpointVdcComputePolicies = "vdcComputePolicies/"
Expand Down Expand Up @@ -442,10 +443,19 @@ const (
OrgVdcNetworkTypeRouted = "NAT_ROUTED"
// OrgVdcNetworkTypeIsolated can be used to create NSX-T or NSX-V isolated Org Vdc network
OrgVdcNetworkTypeIsolated = "ISOLATED"
// OrgVdcNetworkTypeOpaque type is used to create NSX-T imported Org Vdc network
OrgVdcNetworkTypeOpaque = "OPAQUE"
// OrgVdcNetworkTypeDirect can be used to create NSX-V direct Org Vdc network
OrgVdcNetworkTypeDirect = "DIRECT"
// OrgVdcNetworkTypeOpaque type is used to create NSX-T imported Org Vdc network
OrgVdcNetworkTypeOpaque = "OPAQUE"
)

const (
// OrgVdcNetworkBackingTypeVirtualWire matches Org VDC network backing type for NSX-V
OrgVdcNetworkBackingTypeVirtualWire = "VIRTUAL_WIRE"
// OrgVdcNetworkBackingTypeNsxtFlexibleSegment matches Org VDC network backing type for NSX-T networks
OrgVdcNetworkBackingTypeNsxtFlexibleSegment = "NSXT_FLEXIBLE_SEGMENT"
// OrgVdcNetworkBackingTypeDvPortgroup matches Org VDC network backing type for NSX-T Imported network backed by DV Portgroup
OrgVdcNetworkBackingTypeDvPortgroup = "DV_PORTGROUP"
)

const (
Expand Down
20 changes: 18 additions & 2 deletions types/v56/nsxt_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,10 @@ type OpenApiOrgVdcNetwork struct {

// backingNetworkId contains the NSX ID of the backing network.
BackingNetworkId string `json:"backingNetworkId,omitempty"`
// backingNetworkType contains object type of the backing network. ('VIRTUAL_WIRE' for NSX-V, 'NSXT_FLEXIBLE_SEGMENT'
// for NSX-T)
// backingNetworkType contains object type of the backing network.
// * 'VIRTUAL_WIRE' for NSX-V'
// * 'NSXT_FLEXIBLE_SEGMENT' for NSX-T networks
// * 'DV_PORTGROUP' for NSX-T Imported network backed by DV Portgroup
BackingNetworkType string `json:"backingNetworkType,omitempty"`

// ParentNetwork should have external network ID specified when creating NSX-V direct network
Expand Down Expand Up @@ -1648,3 +1650,17 @@ type VdcNetworkProfileServicesEdgeCluster struct {
BackingID string `json:"backingId"`
EdgeClusterRef *OpenApiReference `json:"edgeClusterRef,omitempty"`
}

// VcenterImportableDvpg defines a Distributed Port Group that can be imported into VCD
// from a vCenter Server.
//
// Note. This is a read-only structure.
type VcenterImportableDvpg struct {
BackingRef *OpenApiReference `json:"backingRef"`
DvSwitch struct {
BackingRef *OpenApiReference `json:"backingRef"`
VirtualCenter *OpenApiReference `json:"virtualCenter"`
} `json:"dvSwitch"`
VirtualCenter *OpenApiReference `json:"virtualCenter"`
Vlan string `json:"vlan"`
}