Skip to content

Commit

Permalink
Introduce 'vcd_external_network_v2' and dependencies with NSX-T suppo…
Browse files Browse the repository at this point in the history
…rt (#560)

* Introduce 'vcd_external_network_v2' 
* vcd_vcenter
* vcd_portgroup
* vcd_nsxt_manager
* vcd_nsxt_tier0_router
  • Loading branch information
Didainius authored Oct 13, 2020
1 parent bfeecd8 commit 925c06a
Show file tree
Hide file tree
Showing 45 changed files with 2,755 additions and 96 deletions.
6 changes: 6 additions & 0 deletions .changes/v3.0.0/560-features.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
* **New Resource**: `vcd_external_network_v2` with NSX-T support [GH-560]
* **New Data Source**: `vcd_external_network_v2` with NSX-T support [GH-560]
* **New Data Source**: `vcd_vcenter` [GH-560]
* **New Data Source**: `vcd_portgroup` [GH-560]
* **New Data Source**: `vcd_nsxt_manager` [GH-560]
* **New Data Source**: `vcd_nsxt_tier0_router` [GH-560]
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ require (
github.com/hashicorp/hcl/v2 v2.3.0 // indirect
github.com/hashicorp/terraform-config-inspect v0.0.0-20191212124732-c6ae6269b9d7 // indirect
github.com/hashicorp/terraform-plugin-sdk v1.8.0
github.com/vmware/go-vcloud-director/v2 v2.9.0-beta.2
github.com/vmware/go-vcloud-director/v2 v2.9.0-rc.1
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 // indirect
)
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -213,8 +213,8 @@ github.com/vmihailenco/msgpack v3.3.3+incompatible h1:wapg9xDUZDzGCNFlwc5SqI1rvc
github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
github.com/vmihailenco/msgpack v4.0.1+incompatible h1:RMF1enSPeKTlXrXdOcqjFUElywVZjjC6pqse21bKbEU=
github.com/vmihailenco/msgpack v4.0.1+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
github.com/vmware/go-vcloud-director/v2 v2.9.0-beta.2 h1:4cU0YFLkdW7u2rDn9abOJe9wWinyI6t2szVciOAURZk=
github.com/vmware/go-vcloud-director/v2 v2.9.0-beta.2/go.mod h1:czvTQZlB4/WsOsL7rMVCb+SYAPJhx/dYoS/Sk7rc/O0=
github.com/vmware/go-vcloud-director/v2 v2.9.0-rc.1 h1:I7ySxE+ahHJbH1GXfoZfBop1YaHwdoSPWef5vx30l/w=
github.com/vmware/go-vcloud-director/v2 v2.9.0-rc.1/go.mod h1:czvTQZlB4/WsOsL7rMVCb+SYAPJhx/dYoS/Sk7rc/O0=
github.com/zclconf/go-cty v1.0.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s=
github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s=
github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8=
Expand Down
15 changes: 15 additions & 0 deletions vcd/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,11 @@ type TestConfig struct {
PeerSubnetGateway string `json:"peerSubnetGw"`
} `json:"peer"`
} `json:"networking"`
Nsxt struct {
Manager string `json:"manager"`
Tier0router string `json:"tier0router"`
Tier0routerVrf string `json:"tier0routervrf"`
} `json:"nsxt"`
Logging struct {
Enabled bool `json:"enabled,omitempty"`
LogFileName string `json:"logFileName,omitempty"`
Expand Down Expand Up @@ -1117,4 +1122,14 @@ func skipNoNsxtConfiguration(t *testing.T) {
if testConfig.VCD.NsxtProviderVdc.StorageProfile == "" {
t.Skip(generalMessage + "No storage profile specified")
}

if testConfig.Nsxt.Manager == "" {
t.Skip(generalMessage + "No NSX-T manager specified")
}
if testConfig.Nsxt.Tier0router == "" {
t.Skip(generalMessage + "No NSX-T Tier-0 specified")
}
if testConfig.Nsxt.Tier0routerVrf == "" {
t.Skip(generalMessage + "No VRF NSX-T Tier-0 specified")
}
}
56 changes: 56 additions & 0 deletions vcd/datasource_nsxt_manager_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// +build ALL nsxt functional

package vcd

import (
"regexp"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
)

func TestAccVcdDatasourceNsxtManager(t *testing.T) {

if !usingSysAdmin() {
t.Skip(t.Name() + " requires system admin privileges")
return
}

skipNoNsxtConfiguration(t)

var params = StringMap{
"FuncName": t.Name(),
"NsxtManager": testConfig.Nsxt.Manager,
"Tags": "nsxt",
}

configText := templateFill(testAccCheckVcdNsxtManager, params)

if vcdShortTest {
t.Skip(acceptanceTestsSkipped)
return
}
debugPrintf("#[DEBUG] CONFIGURATION: %s", configText)

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
resource.TestStep{
Config: configText,
Check: resource.ComposeTestCheckFunc(
// ID must match URN 'urn:vcloud:nsxtmanager:09722307-aee0-4623-af95-7f8e577c9ebc'
resource.TestMatchResourceAttr("data.vcd_nsxt_manager.nsxt", "id",
regexp.MustCompile(`urn:vcloud:nsxtmanager:[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$`)),
resource.TestCheckResourceAttr("data.vcd_nsxt_manager.nsxt", "name", params["NsxtManager"].(string)),
),
},
},
})
}

const testAccCheckVcdNsxtManager = `
data "vcd_nsxt_manager" "nsxt" {
name = "{{.NsxtManager}}"
}
`
85 changes: 85 additions & 0 deletions vcd/datasource_nsxt_tier0_router.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package vcd

import (
"fmt"

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

"github.com/vmware/go-vcloud-director/v2/govcd"

"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
)

func datasourceVcdNsxtTier0Router() *schema.Resource {
return &schema.Resource{
Read: datasourceNsxtTier0RouterRead,
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
Description: "Name of NSX-T Tier-0 router.",
},
"nsxt_manager_id": &schema.Schema{
Type: schema.TypeString,
Required: true,
Description: "ID of NSX-T manager.",
},
"is_assigned": &schema.Schema{
Type: schema.TypeBool,
Computed: true,
Description: "Defines if Tier-0 router is already assigned to external network.",
},
},
}
}

// datasourceNsxtTier0RouterRead has special behavior. By default `GetImportableNsxtTier0RouterByName` which uses API
// endpoint `1.0.0/nsxTResources/importableTier0Routers` does not return Tier-0 routers when they are used in external
// networks. This causes a problem in regular Terraform flow - when user uses this datasource to reference Tier-0 router
// for external network creation - next "apply" would fail with "Tier 0 router not found error". If original endpoint
// does not find Tier-0 router - then this datasource queries all defined external networks and looks for Tier-0 router
// backing by name.
func datasourceNsxtTier0RouterRead(d *schema.ResourceData, meta interface{}) error {
vcdClient := meta.(*VCDClient)
nsxtManagerId := d.Get("nsxt_manager_id").(string)
tier0RouterName := d.Get("name").(string)

tier0Router, err := vcdClient.GetImportableNsxtTier0RouterByName(tier0RouterName, nsxtManagerId)
if err != nil && !govcd.ContainsNotFound(err) {
return fmt.Errorf("could not find NSX-T Tier-0 router by name '%s' in NSX-T manager %s: %s",
tier0RouterName, nsxtManagerId, err)
}

// If unused Tier-0 router is found - set the ID and return
if err == nil {
d.Set("is_assigned", false)
d.SetId(tier0Router.NsxtTier0Router.ID)
return nil
}

// API endpoint for Tier-0 routers does not return Tier-0 routers which are already used in external networks
// therefore we are searching for used Tier-0 router name in external networks. This should not cause any risks as
// required permissions should be of the same level.
if govcd.ContainsNotFound(err) {
// Filtering by network backing is unsupported therefore queryParameters are nil
extNets, err := govcd.GetAllExternalNetworksV2(vcdClient.VCDClient, nil)
if err != nil {
return fmt.Errorf("could not find external networks: %s", err)
}

for _, extNetwork := range extNets {
for _, v := range extNetwork.ExternalNetwork.NetworkBackings.Values {
// Very odd but when VRF Tier-0 router is used - BackingType can be UNKNOWN
if v.Name == tier0RouterName &&
(v.BackingType == types.ExternalNetworkBackingTypeNsxtTier0Router || v.BackingType == "UNKNOWN") {
d.Set("is_assigned", true)
d.SetId(v.BackingID)
return nil
}
}
}
}

return fmt.Errorf("%s: could not find NSX-T Tier-0 router by name '%s' in NSX-T manager %s",
govcd.ErrorEntityNotFound, tier0RouterName, nsxtManagerId)
}
73 changes: 73 additions & 0 deletions vcd/datasource_nsxt_tier0_router_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// +build ALL nsxt functional

package vcd

import (
"regexp"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
)

// TestAccVcdDatasourceNsxtTier0Router checks if datasource can find existing regular Tier-0 router
// provided it is specified in configuration
func TestAccVcdDatasourceNsxtTier0Router(t *testing.T) {
testAccVcdDatasourceNsxtTier0Router(t, testConfig.Nsxt.Tier0router)
}

// TestAccVcdDatasourceNsxtTier0Router checks if datasource can find existing VRF Tier-0 router
// provided it is specified in configuration
func TestAccVcdDatasourceNsxtTier0RouterVrf(t *testing.T) {
testAccVcdDatasourceNsxtTier0Router(t, testConfig.Nsxt.Tier0routerVrf)
}

func testAccVcdDatasourceNsxtTier0Router(t *testing.T, tier0RouterName string) {

if !usingSysAdmin() {
t.Skip(t.Name() + " requires system admin privileges")
return
}

skipNoNsxtConfiguration(t)

var params = StringMap{
"FuncName": t.Name(),
"NsxtManager": testConfig.Nsxt.Manager,
"NsxtTier0Router": tier0RouterName,
"Tags": "nsxt",
}

configText := templateFill(testAccCheckVcdNsxtTier0Router, params)

if vcdShortTest {
t.Skip(acceptanceTestsSkipped)
return
}
debugPrintf("#[DEBUG] CONFIGURATION: %s", configText)

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
resource.TestStep{
Config: configText,
Check: resource.ComposeTestCheckFunc(
// ID must match URN 'urn:vcloud:nsxtmanager:09722307-aee0-4623-af95-7f8e577c9ebc'
resource.TestMatchResourceAttr("data.vcd_nsxt_manager.nsxt", "id",
regexp.MustCompile(`urn:vcloud:nsxtmanager:[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$`)),
resource.TestCheckResourceAttr("data.vcd_nsxt_tier0_router.router", "name", params["NsxtTier0Router"].(string)),
),
},
},
})
}

const testAccCheckVcdNsxtTier0Router = `
data "vcd_nsxt_manager" "nsxt" {
name = "{{.NsxtManager}}"
}
data "vcd_nsxt_tier0_router" "router" {
name = "{{.NsxtTier0Router}}"
nsxt_manager_id = data.vcd_nsxt_manager.nsxt.id
}
`
39 changes: 33 additions & 6 deletions vcd/datasource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,35 @@ func TestAccDataSourceNotFound(t *testing.T) {
return
}

// Setup temporary client to evaluate versions and conditionally skip tests
vcdClient := createTemporaryVCDConnection()

// Run a sub-test for each of data source defined in provider
for _, dataSource := range Provider().DataSources() {
t.Run(dataSource.Name, testSpecificDataSourceNotFound(t, dataSource.Name))
t.Run(dataSource.Name, testSpecificDataSourceNotFound(t, dataSource.Name, vcdClient))
}
}

func testSpecificDataSourceNotFound(t *testing.T, dataSourceName string) func(*testing.T) {
func testSpecificDataSourceNotFound(t *testing.T, dataSourceName string, vcdClient *VCDClient) func(*testing.T) {
return func(t *testing.T) {

// Skip sub-test if conditions are not met
if dataSourceName == "vcd_external_network" && !usingSysAdmin() {
switch {
case dataSourceName == "vcd_external_network" && !usingSysAdmin():
t.Skip(`Works only with system admin privileges`)
case dataSourceName == "vcd_external_network_v2" && vcdClient.Client.APIVCDMaxVersionIs("< 33") &&
!usingSysAdmin():
t.Skip("External network V2 requires at least API version 33 (VCD 10.0+)")
case (dataSourceName == "vcd_nsxt_tier0_router" || dataSourceName == "vcd_external_network_v2" || dataSourceName == "vcd_nsxt_manager") &&
(testConfig.Nsxt.Manager == "" || testConfig.Nsxt.Tier0router == "") || !usingSysAdmin():
t.Skip(`No NSX-T configuration detected`)
}

// Get list of mandatory fields in schema for a particular data source
mandatoryFields := getMandatoryDataSourceSchemaFields(dataSourceName)
mandatoryRuntimeFields := getMandatoryDataSourceRuntimeFields(dataSourceName)
mandatoryFields = append(mandatoryFields, mandatoryRuntimeFields...)
addedParams := addMandatoryParams(dataSourceName, mandatoryFields, t)
addedParams := addMandatoryParams(dataSourceName, mandatoryFields, t, vcdClient)

var params = StringMap{
"DataSourceName": dataSourceName,
Expand Down Expand Up @@ -93,7 +103,7 @@ func getMandatoryDataSourceRuntimeFields(dataSourceName string) []string {
return []string{}
}

func addMandatoryParams(dataSourceName string, mandatoryFields []string, t *testing.T) string {
func addMandatoryParams(dataSourceName string, mandatoryFields []string, t *testing.T, vcdClient *VCDClient) string {
var templateFields string
for fieldIndex := range mandatoryFields {

Expand All @@ -103,6 +113,12 @@ func addMandatoryParams(dataSourceName string, mandatoryFields []string, t *test
return templateFields
}

// vcd_portgroup requires portgroup type
if dataSourceName == "vcd_portgroup" && mandatoryFields[fieldIndex] == "type" {
templateFields = templateFields + `type = "` + testConfig.Networking.ExternalNetworkPortGroupType + `"` + "\n"
return templateFields
}

switch mandatoryFields[fieldIndex] {
// Fields, which must be valid to satisfy a data source
case "org": // Some data sources require org - fill it from testConfig
Expand All @@ -118,7 +134,18 @@ func addMandatoryParams(dataSourceName string, mandatoryFields []string, t *test
return ""
}
templateFields = templateFields + `vapp_name = "` + vapp.VApp.Name + `"` + "\n"

case "nsxt_manager_id":
// This test needs a valid nsxt_manager_id
nsxtManager, err := vcdClient.QueryNsxtManagerByName(testConfig.Nsxt.Manager)
if err != nil {
t.Skipf("No suitable NSX-T manager found for this test: %s", err)
return ""
}
nsxtManagerUrn, err := govcd.BuildUrnWithUuid("urn:vcloud:nsxtmanager:", extractUuid(nsxtManager[0].HREF))
if err != nil {
t.Errorf("error building URN for NSX-T manager")
}
templateFields = templateFields + `nsxt_manager_id = "` + nsxtManagerUrn + `"` + "\n"
// Invalid fields which are required for some resources for search (usually they are used instead of `name`)
case "rule_id":
templateFields = templateFields + `rule_id = "347928347234"` + "\n"
Expand Down
Loading

0 comments on commit 925c06a

Please sign in to comment.