From 925c06af7ff8f38d8902af9fb8be53e37a8640d4 Mon Sep 17 00:00:00 2001 From: Dainius Date: Tue, 13 Oct 2020 16:45:30 +0300 Subject: [PATCH] Introduce 'vcd_external_network_v2' and dependencies with NSX-T support (#560) * Introduce 'vcd_external_network_v2' * vcd_vcenter * vcd_portgroup * vcd_nsxt_manager * vcd_nsxt_tier0_router --- .changes/v3.0.0/560-features.md | 6 + go.mod | 2 +- go.sum | 4 +- vcd/config_test.go | 15 + vcd/datasource_nsxt_manager_test.go | 56 +++ vcd/datasource_nsxt_tier0_router.go | 85 ++++ vcd/datasource_nsxt_tier0_router_test.go | 73 +++ vcd/datasource_test.go | 39 +- vcd/datasource_vcd_external_network_v2.go | 89 ++++ ...datasource_vcd_external_network_v2_test.go | 65 +++ vcd/datasource_vcd_nsxt_manager.go | 50 ++ vcd/datasource_vcd_portgroup.go | 67 +++ vcd/datasource_vcenter.go | 96 ++++ vcd/datasource_vcenter_test.go | 50 ++ vcd/provider.go | 64 +-- vcd/resource_vcd_external_network_v2.go | 467 ++++++++++++++++++ vcd/resource_vcd_external_network_v2_test.go | 463 +++++++++++++++++ vcd/resource_vcd_nsxv_firewall_rule_test.go | 35 -- vcd/resource_vcd_org.go | 13 +- vcd/resource_vcd_org_group_test.go | 10 +- vcd/resource_vcd_vm_affinity_rule.go | 2 +- vcd/sample_vcd_test_config.json | 5 + vcd/testcheck_funcs_test.go | 99 +++- .../vmware/go-vcloud-director/v2/govcd/api.go | 44 ++ .../v2/govcd/external_network_v2.go | 192 +++++++ .../v2/govcd/nsxt_tier0_router.go | 138 ++++++ .../go-vcloud-director/v2/govcd/openapi.go | 37 +- .../v2/govcd/openapi_endpoints.go | 7 +- .../v2/govcd/sample_govcd_test_config.yaml | 7 + .../go-vcloud-director/v2/govcd/system.go | 14 + .../v2/types/v56/constants.go | 14 + .../v2/types/v56/openapi.go | 85 ++++ .../go-vcloud-director/v2/types/v56/types.go | 14 +- .../go-vcloud-director/v2/util/logging.go | 6 +- vendor/modules.txt | 2 +- .../docs/d/external_network_v2.html.markdown | 40 ++ website/docs/d/nsxt_manager.html.markdown | 36 ++ .../docs/d/nsxt_tier0_router.html.markdown | 43 ++ website/docs/d/org_vdc.html.markdown | 2 + website/docs/d/portgroup.html.markdown | 43 ++ website/docs/d/vcenter.html.markdown | 36 ++ website/docs/r/external_network.html.markdown | 2 + .../docs/r/external_network_v2.html.markdown | 176 +++++++ website/docs/r/org_vdc.html.markdown | 40 ++ website/vcd.erb | 18 + 45 files changed, 2755 insertions(+), 96 deletions(-) create mode 100644 .changes/v3.0.0/560-features.md create mode 100644 vcd/datasource_nsxt_manager_test.go create mode 100644 vcd/datasource_nsxt_tier0_router.go create mode 100644 vcd/datasource_nsxt_tier0_router_test.go create mode 100644 vcd/datasource_vcd_external_network_v2.go create mode 100644 vcd/datasource_vcd_external_network_v2_test.go create mode 100644 vcd/datasource_vcd_nsxt_manager.go create mode 100644 vcd/datasource_vcd_portgroup.go create mode 100644 vcd/datasource_vcenter.go create mode 100644 vcd/datasource_vcenter_test.go create mode 100644 vcd/resource_vcd_external_network_v2.go create mode 100644 vcd/resource_vcd_external_network_v2_test.go create mode 100644 vendor/github.com/vmware/go-vcloud-director/v2/govcd/external_network_v2.go create mode 100644 vendor/github.com/vmware/go-vcloud-director/v2/govcd/nsxt_tier0_router.go create mode 100644 website/docs/d/external_network_v2.html.markdown create mode 100644 website/docs/d/nsxt_manager.html.markdown create mode 100644 website/docs/d/nsxt_tier0_router.html.markdown create mode 100644 website/docs/d/portgroup.html.markdown create mode 100644 website/docs/d/vcenter.html.markdown create mode 100644 website/docs/r/external_network_v2.html.markdown diff --git a/.changes/v3.0.0/560-features.md b/.changes/v3.0.0/560-features.md new file mode 100644 index 000000000..480652eed --- /dev/null +++ b/.changes/v3.0.0/560-features.md @@ -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] diff --git a/go.mod b/go.mod index 84c8d0a6e..b694e3abb 100644 --- a/go.mod +++ b/go.mod @@ -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 ) diff --git a/go.sum b/go.sum index e71f27371..23739dddd 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/vcd/config_test.go b/vcd/config_test.go index abff5a5ad..d8535f9ac 100644 --- a/vcd/config_test.go +++ b/vcd/config_test.go @@ -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"` @@ -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") + } } diff --git a/vcd/datasource_nsxt_manager_test.go b/vcd/datasource_nsxt_manager_test.go new file mode 100644 index 000000000..97d27841c --- /dev/null +++ b/vcd/datasource_nsxt_manager_test.go @@ -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}}" +} +` diff --git a/vcd/datasource_nsxt_tier0_router.go b/vcd/datasource_nsxt_tier0_router.go new file mode 100644 index 000000000..353366fac --- /dev/null +++ b/vcd/datasource_nsxt_tier0_router.go @@ -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) +} diff --git a/vcd/datasource_nsxt_tier0_router_test.go b/vcd/datasource_nsxt_tier0_router_test.go new file mode 100644 index 000000000..93b9f5b65 --- /dev/null +++ b/vcd/datasource_nsxt_tier0_router_test.go @@ -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 +} +` diff --git a/vcd/datasource_test.go b/vcd/datasource_test.go index 3da4ce2c4..b7f8c03eb 100644 --- a/vcd/datasource_test.go +++ b/vcd/datasource_test.go @@ -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, @@ -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 { @@ -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 @@ -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" diff --git a/vcd/datasource_vcd_external_network_v2.go b/vcd/datasource_vcd_external_network_v2.go new file mode 100644 index 000000000..fc1188f95 --- /dev/null +++ b/vcd/datasource_vcd_external_network_v2.go @@ -0,0 +1,89 @@ +package vcd + +import ( + "fmt" + "log" + + "github.com/vmware/go-vcloud-director/v2/govcd" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +func datasourceVcdExternalNetworkV2() *schema.Resource { + return &schema.Resource{ + Read: datasourceVcdExternalNetworkV2Read, + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "description": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "ip_scope": &schema.Schema{ + Type: schema.TypeSet, + Computed: true, + Description: "A set of IP scopes for the network", + Elem: networkV2IpScope, + }, + "vsphere_network": &schema.Schema{ + Type: schema.TypeSet, + Computed: true, + Description: "A list of port groups that back this network. Each referenced DV_PORTGROUP or NETWORK must exist on a vCenter server registered with the system.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "vcenter_id": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + Description: "The vCenter server ID", + }, + "portgroup_id": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + Description: "The portgroup ID", + }, + }, + }, + }, + "nsxt_network": &schema.Schema{ + Type: schema.TypeSet, + Computed: true, + MaxItems: 1, + ForceNew: true, + Description: "", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "nsxt_manager_id": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + Description: "ID of NSX-T manager", + }, + "nsxt_tier0_router_id": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + Description: "ID of NSX-T Tier-0 router", + }, + }, + }, + }, + }, + } +} + +func datasourceVcdExternalNetworkV2Read(d *schema.ResourceData, meta interface{}) error { + vcdClient := meta.(*VCDClient) + log.Printf("[TRACE] external network V2 data source read initiated") + + name := d.Get("name").(string) + + extNet, err := govcd.GetExternalNetworkV2ByName(vcdClient.VCDClient, name) + if err != nil { + return fmt.Errorf("could not find external network V2 by name '%s': %s", name, err) + } + + d.SetId(extNet.ExternalNetwork.ID) + + return setExternalNetworkV2Data(d, extNet.ExternalNetwork) +} diff --git a/vcd/datasource_vcd_external_network_v2_test.go b/vcd/datasource_vcd_external_network_v2_test.go new file mode 100644 index 000000000..4272c9b08 --- /dev/null +++ b/vcd/datasource_vcd_external_network_v2_test.go @@ -0,0 +1,65 @@ +// +build functional network extnetwork ALL + +package vcd + +import ( + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +func TestAccVcdExternalNetworkV2Datasource(t *testing.T) { + if !usingSysAdmin() { + t.Skip(t.Name() + " requires system admin privileges") + return + } + + vcdClient := createTemporaryVCDConnection() + if vcdClient.Client.APIVCDMaxVersionIs("< 33.0") { + t.Skip(t.Name() + " requires at least API v33.0 (vCD 10+)") + } + + var params = StringMap{ + "ExistingExternalNetwork": testConfig.Networking.ExternalNetwork, + "Tags": "network extnetwork", + } + + configText := templateFill(externalNetworkV2Datasource, params) + if vcdShortTest { + t.Skip(acceptanceTestsSkipped) + return + } + + debugPrintf("#[DEBUG] CONFIGURATION: %s", configText) + + datasourceName := "data.vcd_external_network_v2.ext-net-nsxv" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: configText, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestMatchResourceAttr(datasourceName, "id", regexp.MustCompile(`^urn:vcloud:network:.*`)), + resource.TestCheckResourceAttrSet(datasourceName, "vsphere_network.0.portgroup_id"), + resource.TestCheckResourceAttrSet(datasourceName, "vsphere_network.0.vcenter_id"), + resource.TestCheckResourceAttr(datasourceName, "nsxt_network.#", "0"), + resource.TestCheckResourceAttr(datasourceName, "vsphere_network.#", "1"), + // Cannot be too explicit because this test depends on existing external network and it may have + // wide configuration. + resourceFieldIntNotEqual(datasourceName, "ip_scope.#", 0), + resource.TestCheckResourceAttrSet(datasourceName, "vsphere_network.0.portgroup_id"), + resource.TestMatchResourceAttr(datasourceName, "vsphere_network.0.vcenter_id", regexp.MustCompile(`^urn:vcloud:vimserver:.*`)), + resource.TestCheckResourceAttr(datasourceName, "nsxt_network.#", "0"), + ), + }, + }, + }) +} + +const externalNetworkV2Datasource = ` +data "vcd_external_network_v2" "ext-net-nsxv" { + name = "{{.ExistingExternalNetwork}}" +} +` diff --git a/vcd/datasource_vcd_nsxt_manager.go b/vcd/datasource_vcd_nsxt_manager.go new file mode 100644 index 000000000..e6b386956 --- /dev/null +++ b/vcd/datasource_vcd_nsxt_manager.go @@ -0,0 +1,50 @@ +package vcd + +import ( + "fmt" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/vmware/go-vcloud-director/v2/govcd" +) + +func datasourceVcdNsxtManager() *schema.Resource { + return &schema.Resource{ + Read: datasourceNsxtManagerRead, + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + Description: "Name of NSX-T manager.", + }, + }, + } +} + +func datasourceNsxtManagerRead(d *schema.ResourceData, meta interface{}) error { + vcdClient := meta.(*VCDClient) + nsxtManagerName := d.Get("name").(string) + + nsxtManagers, err := vcdClient.QueryNsxtManagerByName(nsxtManagerName) + if err != nil { + return fmt.Errorf("could not find NSX-T manager by name '%s': %s", nsxtManagerName, err) + } + + if len(nsxtManagers) == 0 { + return fmt.Errorf("%s found %d NSX-T managers with name '%s'", + govcd.ErrorEntityNotFound, len(nsxtManagers), nsxtManagerName) + } + + if len(nsxtManagers) > 1 { + return fmt.Errorf("found %d NSX-T managers with name '%s'", len(nsxtManagers), nsxtManagerName) + } + + // We try to keep IDs clean + id := extractUuid(nsxtManagers[0].HREF) + urn, err := govcd.BuildUrnWithUuid("urn:vcloud:nsxtmanager:", id) + if err != nil { + return fmt.Errorf("could not construct URN from id '%s': %s", id, err) + } + d.SetId(urn) + + return nil +} diff --git a/vcd/datasource_vcd_portgroup.go b/vcd/datasource_vcd_portgroup.go new file mode 100644 index 000000000..82d0f8fdb --- /dev/null +++ b/vcd/datasource_vcd_portgroup.go @@ -0,0 +1,67 @@ +package vcd + +import ( + "fmt" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + "github.com/vmware/go-vcloud-director/v2/govcd" + "github.com/vmware/go-vcloud-director/v2/types/v56" +) + +func datasourceVcdPortgroup() *schema.Resource { + return &schema.Resource{ + Read: datasourcePortgroupRead, + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + Description: "Name of NSX-T Tier-0 router.", + }, + "type": &schema.Schema{ + Type: schema.TypeString, + Required: true, + Description: "Portgroup type. One of 'NETWORK', 'DV_PORTGROUP'", + ValidateFunc: validation.StringInSlice([]string{types.ExternalNetworkBackingTypeNetwork, types.ExternalNetworkBackingDvPortgroup}, false), + }, + }, + } +} + +func datasourcePortgroupRead(d *schema.ResourceData, meta interface{}) error { + vcdClient := meta.(*VCDClient) + + portGroupType := d.Get("type").(string) + portGroupName := d.Get("name").(string) + + var err error + var pgs []*types.PortGroupRecordType + + switch portGroupType { + // Standard vSwitch portgroup + case types.ExternalNetworkBackingTypeNetwork: + pgs, err = govcd.QueryNetworkPortGroup(vcdClient.VCDClient, portGroupName) + // Distributed switch portgroup + case types.ExternalNetworkBackingDvPortgroup: + pgs, err = govcd.QueryDistributedPortGroup(vcdClient.VCDClient, portGroupName) + default: + return fmt.Errorf("unrecognized portgroup_type: %s", portGroupType) + } + + if err != nil { + return fmt.Errorf("error querying for portgroups '%s' of type '%s': %s", portGroupName, portGroupType, err) + } + + if len(pgs) == 0 { + return fmt.Errorf("%s: expected to get exactly one portgroup with name '%s' of type '%s', got %d", + govcd.ErrorEntityNotFound, portGroupName, portGroupType, len(pgs)) + } + + if len(pgs) > 1 { + return fmt.Errorf("expected to get exactly one portgroup with name '%s' of type '%s', got %d", + portGroupName, portGroupType, len(pgs)) + } + + d.SetId(pgs[0].MoRef) + return nil +} diff --git a/vcd/datasource_vcenter.go b/vcd/datasource_vcenter.go new file mode 100644 index 000000000..62a8663e4 --- /dev/null +++ b/vcd/datasource_vcenter.go @@ -0,0 +1,96 @@ +package vcd + +import ( + "fmt" + "log" + "net/url" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/vmware/go-vcloud-director/v2/govcd" + "github.com/vmware/go-vcloud-director/v2/types/v56" +) + +func datasourceVcdVcenter() *schema.Resource { + return &schema.Resource{ + Read: datasourceVcenterRead, + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + Description: "Name of vCenter.", + }, + "vcenter_version": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + Description: "vCenter version", + }, + "vcenter_host": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + Description: "vCenter hostname", + }, + "status": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + Description: "vCenter status", + }, + "is_enabled": &schema.Schema{ + Type: schema.TypeBool, + Computed: true, + Description: "vCenter version", + }, + // In UI this field is called `connection`, but it is a reserved field in Terrraform + "connection_status": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + Description: "vCenter connection state", + }, + }, + } +} + +func datasourceVcenterRead(d *schema.ResourceData, meta interface{}) error { + vcdClient := meta.(*VCDClient) + + vCenterName := d.Get("name").(string) + + vcs, err := govcd.QueryVirtualCenters(vcdClient.VCDClient, "name=="+vCenterName) + if err != nil { + return fmt.Errorf("error occured while querying vCenters: %s", err) + } + + if len(vcs) == 0 { + return fmt.Errorf("%s: could not identify single vCenter. Got %d with name '%s'", + govcd.ErrorEntityNotFound, len(vcs), vCenterName) + } + + if len(vcs) > 1 { + return fmt.Errorf("could not identify single vCenter. Got %d with name '%s'", + len(vcs), vCenterName) + } + + uuid := extractUuid(vcs[0].HREF) + urn, err := govcd.BuildUrnWithUuid("urn:vcloud:vimserver:", uuid) + if err != nil { + return fmt.Errorf("could not build URN for ID '%s': %s", uuid, err) + } + + d.SetId(urn) + setVcenterData(d, vcs[0]) + + return nil +} + +func setVcenterData(d *schema.ResourceData, vc *types.QueryResultVirtualCenterRecordType) { + _ = d.Set("vcenter_version", vc.VcVersion) + // vc.Url is in format `https://XXXX.com/sdk` while UI shows hostname only so we extract it + // The error should not be a reason to fail datasource if it is invalid so it is just logged + host, err := url.Parse(vc.Url) + if err != nil { + log.Printf("[DEBUG] [vCenter read] - could not parse vCenter URL '%s': %s", vc.Url, err) + } + _ = d.Set("vcenter_host", host.Host) + _ = d.Set("status", vc.Status) + _ = d.Set("is_enabled", vc.IsEnabled) + _ = d.Set("connection_status", vc.ListenerState) +} diff --git a/vcd/datasource_vcenter_test.go b/vcd/datasource_vcenter_test.go new file mode 100644 index 000000000..eb202bc73 --- /dev/null +++ b/vcd/datasource_vcenter_test.go @@ -0,0 +1,50 @@ +// +build ALL functional + +package vcd + +import ( + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +func TestAccVcdVcenter(t *testing.T) { + if !usingSysAdmin() { + t.Skip(t.Name() + " requires system admin privileges") + } + + var params = StringMap{ + "Vcenter": testConfig.Networking.Vcenter, + } + configText := templateFill(datasourceTestVcenter, params) + if vcdShortTest { + t.Skip(acceptanceTestsSkipped) + return + } + debugPrintf("#[DEBUG] CONFIGURATION: %s", configText) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: configText, + Check: resource.ComposeTestCheckFunc( + resource.TestMatchResourceAttr("data.vcd_vcenter.vc", "id", regexp.MustCompile("^urn:vcloud:vimserver:.*")), + resource.TestCheckResourceAttrSet("data.vcd_vcenter.vc", "vcenter_version"), + resource.TestCheckResourceAttrSet("data.vcd_vcenter.vc", "vcenter_host"), + resource.TestCheckResourceAttrSet("data.vcd_vcenter.vc", "status"), + resource.TestCheckResourceAttrSet("data.vcd_vcenter.vc", "is_enabled"), + resource.TestCheckResourceAttrSet("data.vcd_vcenter.vc", "connection_status"), + ), + }, + }, + }) +} + +const datasourceTestVcenter = ` +data "vcd_vcenter" "vc" { + name = "{{.Vcenter}}" + } +` diff --git a/vcd/provider.go b/vcd/provider.go index 7b23af923..ca7f5add2 100644 --- a/vcd/provider.go +++ b/vcd/provider.go @@ -30,34 +30,39 @@ func Resources(nameRegexp string, includeDeprecated bool) (map[string]*schema.Re } var globalDataSourceMap = map[string]*schema.Resource{ - "vcd_org": datasourceVcdOrg(), // 2.5 - "vcd_org_user": datasourceVcdOrgUser(), // 3.0 - "vcd_org_vdc": datasourceVcdOrgVdc(), // 2.5 - "vcd_catalog": datasourceVcdCatalog(), // 2.5 - "vcd_catalog_media": datasourceVcdCatalogMedia(), // 2.5 - "vcd_catalog_item": datasourceVcdCatalogItem(), // 2.5 - "vcd_edgegateway": datasourceVcdEdgeGateway(), // 2.5 - "vcd_external_network": datasourceVcdExternalNetwork(), // 2.5 - "vcd_independent_disk": datasourceVcIndependentDisk(), // 2.5 - "vcd_network_routed": datasourceVcdNetworkRouted(), // 2.5 - "vcd_network_direct": datasourceVcdNetworkDirect(), // 2.5 - "vcd_network_isolated": datasourceVcdNetworkIsolated(), // 2.5 - "vcd_vapp": datasourceVcdVApp(), // 2.5 - "vcd_vapp_vm": datasourceVcdVAppVm(), // 2.6 - "vcd_lb_service_monitor": datasourceVcdLbServiceMonitor(), // 2.4 - "vcd_lb_server_pool": datasourceVcdLbServerPool(), // 2.4 - "vcd_lb_app_profile": datasourceVcdLBAppProfile(), // 2.4 - "vcd_lb_app_rule": datasourceVcdLBAppRule(), // 2.4 - "vcd_lb_virtual_server": datasourceVcdLbVirtualServer(), // 2.4 - "vcd_nsxv_dnat": datasourceVcdNsxvDnat(), // 2.5 - "vcd_nsxv_snat": datasourceVcdNsxvSnat(), // 2.5 - "vcd_nsxv_firewall_rule": datasourceVcdNsxvFirewallRule(), // 2.5 - "vcd_nsxv_dhcp_relay": datasourceVcdNsxvDhcpRelay(), // 2.6 - "vcd_nsxv_ip_set": datasourceVcdIpSet(), // 2.6 - "vcd_vapp_network": datasourceVcdVappNetwork(), // 2.7 - "vcd_vapp_org_network": datasourceVcdVappOrgNetwork(), // 2.7 - "vcd_vm_affinity_rule": datasourceVcdVmAffinityRule(), // 2.9 - "vcd_vm_sizing_policy": datasourceVcdVmSizingPolicy(), // 3.0 + "vcd_org": datasourceVcdOrg(), // 2.5 + "vcd_org_user": datasourceVcdOrgUser(), // 3.0 + "vcd_org_vdc": datasourceVcdOrgVdc(), // 2.5 + "vcd_catalog": datasourceVcdCatalog(), // 2.5 + "vcd_catalog_media": datasourceVcdCatalogMedia(), // 2.5 + "vcd_catalog_item": datasourceVcdCatalogItem(), // 2.5 + "vcd_edgegateway": datasourceVcdEdgeGateway(), // 2.5 + "vcd_external_network": datasourceVcdExternalNetwork(), // 2.5 + "vcd_external_network_v2": datasourceVcdExternalNetworkV2(), // 3.0 + "vcd_independent_disk": datasourceVcIndependentDisk(), // 2.5 + "vcd_network_routed": datasourceVcdNetworkRouted(), // 2.5 + "vcd_network_direct": datasourceVcdNetworkDirect(), // 2.5 + "vcd_network_isolated": datasourceVcdNetworkIsolated(), // 2.5 + "vcd_vapp": datasourceVcdVApp(), // 2.5 + "vcd_vapp_vm": datasourceVcdVAppVm(), // 2.6 + "vcd_lb_service_monitor": datasourceVcdLbServiceMonitor(), // 2.4 + "vcd_lb_server_pool": datasourceVcdLbServerPool(), // 2.4 + "vcd_lb_app_profile": datasourceVcdLBAppProfile(), // 2.4 + "vcd_lb_app_rule": datasourceVcdLBAppRule(), // 2.4 + "vcd_lb_virtual_server": datasourceVcdLbVirtualServer(), // 2.4 + "vcd_nsxv_dnat": datasourceVcdNsxvDnat(), // 2.5 + "vcd_nsxv_snat": datasourceVcdNsxvSnat(), // 2.5 + "vcd_nsxv_firewall_rule": datasourceVcdNsxvFirewallRule(), // 2.5 + "vcd_nsxv_dhcp_relay": datasourceVcdNsxvDhcpRelay(), // 2.6 + "vcd_nsxv_ip_set": datasourceVcdIpSet(), // 2.6 + "vcd_vapp_network": datasourceVcdVappNetwork(), // 2.7 + "vcd_vapp_org_network": datasourceVcdVappOrgNetwork(), // 2.7 + "vcd_vm_affinity_rule": datasourceVcdVmAffinityRule(), // 2.9 + "vcd_vm_sizing_policy": datasourceVcdVmSizingPolicy(), // 3.0 + "vcd_nsxt_manager": datasourceVcdNsxtManager(), // 3.0 + "vcd_nsxt_tier0_router": datasourceVcdNsxtTier0Router(), // 3.0 + "vcd_portgroup": datasourceVcdPortgroup(), // 3.0 + "vcd_vcenter": datasourceVcdVcenter(), // 3.0 } var globalResourceMap = map[string]*schema.Resource{ @@ -68,8 +73,8 @@ var globalResourceMap = map[string]*schema.Resource{ "vcd_vapp_network": resourceVcdVappNetwork(), // 2.1 "vcd_vapp": resourceVcdVApp(), // 1.0 "vcd_edgegateway": resourceVcdEdgeGateway(), // 2.4 - "vcd_edgegateway_settings": resourceVcdEdgeGatewaySettings(), // 3.0 "vcd_edgegateway_vpn": resourceVcdEdgeGatewayVpn(), // 1.0 + "vcd_edgegateway_settings": resourceVcdEdgeGatewaySettings(), // 3.0 "vcd_vapp_vm": resourceVcdVAppVm(), // 1.0 "vcd_org": resourceOrg(), // 2.0 "vcd_org_vdc": resourceVcdOrgVdc(), // 2.2 @@ -98,6 +103,7 @@ var globalResourceMap = map[string]*schema.Resource{ "vcd_vapp_static_routing": resourceVcdVappNetworkStaticRouting(), // 2.9 "vcd_vm_affinity_rule": resourceVcdVmAffinityRule(), // 2.9 "vcd_vapp_access_control": resourceVcdAccessControlVapp(), // 3.0 + "vcd_external_network_v2": resourceVcdExternalNetworkV2(), // 3.0 "vcd_vm_sizing_policy": resourceVcdVmSizingPolicy(), // 3.0 } diff --git a/vcd/resource_vcd_external_network_v2.go b/vcd/resource_vcd_external_network_v2.go new file mode 100644 index 000000000..b51bbedf1 --- /dev/null +++ b/vcd/resource_vcd_external_network_v2.go @@ -0,0 +1,467 @@ +package vcd + +import ( + "fmt" + "log" + + "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" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" +) + +var networkV2IpScope = &schema.Resource{ + Schema: map[string]*schema.Schema{ + "gateway": &schema.Schema{ + Type: schema.TypeString, + Required: true, + Description: "Gateway of the network", + ValidateFunc: validation.IsIPAddress, + }, + "prefix_length": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + Description: "Network mask", + ValidateFunc: validation.IntAtLeast(1), + }, + "enabled": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: true, + Description: "If subnet is enabled", + }, + "static_ip_pool": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Description: "IP ranges used for static pool allocation in the network", + Elem: networkV2IpRange, + }, + "dns1": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Description: "Primary DNS server", + ValidateFunc: validation.IsIPAddress, + // Only NSX-V allows configuring DNS + ConflictsWith: []string{"nsxt_network"}, + }, + "dns2": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Description: "Secondary DNS server", + ValidateFunc: validation.IsIPAddress, + // Only NSX-V allows configuring DNS + ConflictsWith: []string{"nsxt_network"}, + }, + "dns_suffix": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Description: "DNS suffix", + // Only NSX-V allows configuring DNS + ConflictsWith: []string{"nsxt_network"}, + }, + }, +} + +var networkV2IpRange = &schema.Resource{ + Schema: map[string]*schema.Schema{ + "start_address": &schema.Schema{ + Type: schema.TypeString, + Required: true, + Description: "Start address of the IP range", + ValidateFunc: validation.IsIPAddress, + }, + "end_address": &schema.Schema{ + Type: schema.TypeString, + Required: true, + Description: "End address of the IP range", + ValidateFunc: validation.IsIPAddress, + }, + }, +} + +var networkV2NsxtNetwork = &schema.Resource{ + Schema: map[string]*schema.Schema{ + "nsxt_manager_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "ID of NSX-T manager", + }, + "nsxt_tier0_router_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "ID of NSX-T Tier-0 router", + }, + }, +} + +var networkV2VsphereNetwork = &schema.Resource{ + Schema: map[string]*schema.Schema{ + "vcenter_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The vCenter server name", + }, + "portgroup_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The name of the port group", + }, + }, +} + +func resourceVcdExternalNetworkV2() *schema.Resource { + return &schema.Resource{ + Create: resourceVcdExternalNetworkV2Create, + Update: resourceVcdExternalNetworkV2Update, + Delete: resourceVcdExternalNetworkV2Delete, + Read: resourceVcdExternalNetworkV2Read, + Importer: &schema.ResourceImporter{ + State: resourceVcdExternalNetworkV2Import, + }, + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + Description: "Network name", + }, + "description": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Description: "Network description", + }, + "ip_scope": &schema.Schema{ + Type: schema.TypeSet, + Required: true, + Description: "A set of IP scopes for the network", + Elem: networkV2IpScope, + }, + "vsphere_network": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + ExactlyOneOf: []string{"vsphere_network", "nsxt_network"}, + + ForceNew: true, + Description: "A set of port groups that back this network. Each referenced DV_PORTGROUP or NETWORK must exist on a vCenter server registered with the system.", + Elem: networkV2VsphereNetwork, + }, + "nsxt_network": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + ExactlyOneOf: []string{"vsphere_network", "nsxt_network"}, + MaxItems: 1, + ForceNew: true, + Description: "Reference to NSX-T Tier-0 router and manager", + Elem: networkV2NsxtNetwork, + }, + }, + } +} + +func resourceVcdExternalNetworkV2Create(d *schema.ResourceData, meta interface{}) error { + vcdClient := meta.(*VCDClient) + log.Printf("[TRACE] external network V2 creation initiated") + + netType, err := getExternalNetworkV2Type(vcdClient, d) + if err != nil { + return fmt.Errorf("could not get network data: %s", err) + } + + extNet, err := govcd.CreateExternalNetworkV2(vcdClient.VCDClient, netType) + if err != nil { + return fmt.Errorf("error applying data: %s", err) + } + + // Only store ID and leave all the rest to "READ" + d.SetId(extNet.ExternalNetwork.ID) + + return resourceVcdExternalNetworkV2Read(d, meta) +} + +func resourceVcdExternalNetworkV2Update(d *schema.ResourceData, meta interface{}) error { + vcdClient := meta.(*VCDClient) + log.Printf("[TRACE] update network V2 creation initiated") + + extNet, err := govcd.GetExternalNetworkV2ById(vcdClient.VCDClient, d.Id()) + if err != nil { + return fmt.Errorf("could not find external network V2 by ID '%s': %s", d.Id(), err) + } + + netType, err := getExternalNetworkV2Type(vcdClient, d) + if err != nil { + return fmt.Errorf("could not get network data: %s", err) + } + + netType.ID = extNet.ExternalNetwork.ID + extNet.ExternalNetwork = netType + + _, err = extNet.Update() + if err != nil { + return fmt.Errorf("error updating external network V2: %s", err) + } + + return resourceVcdExternalNetworkV2Read(d, meta) +} + +func resourceVcdExternalNetworkV2Read(d *schema.ResourceData, meta interface{}) error { + vcdClient := meta.(*VCDClient) + log.Printf("[TRACE] external network V2 read initiated") + + extNet, err := govcd.GetExternalNetworkV2ById(vcdClient.VCDClient, d.Id()) + if err != nil { + if govcd.ContainsNotFound(err) { + d.SetId("") + return nil + } + return fmt.Errorf("could not find external network V2 by ID '%s': %s", d.Id(), err) + } + + return setExternalNetworkV2Data(d, extNet.ExternalNetwork) +} + +func resourceVcdExternalNetworkV2Delete(d *schema.ResourceData, meta interface{}) error { + vcdClient := meta.(*VCDClient) + log.Printf("[TRACE] external network V2 creation initiated") + + extNet, err := govcd.GetExternalNetworkV2ById(vcdClient.VCDClient, d.Id()) + if err != nil { + return fmt.Errorf("could not find external network V2 by ID '%s': %s", d.Id(), err) + } + + return extNet.Delete() +} + +// resourceVcdExternalNetworkV2Import is responsible for importing the resource. +// The d.ID() field as being passed from `terraform import _resource_name_ _the_id_string_ requires +// a name based dot-formatted path to the object to lookup the object and sets the id of object. +// `terraform import` automatically performs `refresh` operation which loads up all other fields. +// For this resource, the import path is just the external network name. +// +// Example import path (id): externalNetworkName +// Example import command: terraform import vcd_external_network_v2.externalNetworkResourceName externalNetworkName +func resourceVcdExternalNetworkV2Import(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + + vcdClient := meta.(*VCDClient) + + extNetRes, err := govcd.GetExternalNetworkV2ByName(vcdClient.VCDClient, d.Id()) + if err != nil { + return nil, fmt.Errorf("error fetching external network V2 details %s", err) + } + + d.SetId(extNetRes.ExternalNetwork.ID) + + setExternalNetworkV2Data(d, extNetRes.ExternalNetwork) + + return []*schema.ResourceData{d}, nil +} + +func getExternalNetworkV2Type(vcdClient *VCDClient, d *schema.ResourceData) (*types.ExternalNetworkV2, error) { + networkBackings, err := getExternalNetworkV2BackingType(vcdClient, d) + if err != nil { + return nil, fmt.Errorf("error getting network backing type: %s", err) + } + subnetSlice := getSubnetsType(d) + + newExtNet := &types.ExternalNetworkV2{ + Name: d.Get("name").(string), + Description: d.Get("description").(string), + Subnets: types.ExternalNetworkV2Subnets{Values: subnetSlice}, + NetworkBackings: networkBackings, + } + + return newExtNet, nil +} + +func getExternalNetworkV2BackingType(vcdClient *VCDClient, d *schema.ResourceData) (types.ExternalNetworkV2Backings, error) { + var backings types.ExternalNetworkV2Backings + // var backing types.ExternalNetworkV2Backing + // Network backings + nsxtNetwork := d.Get("nsxt_network").(*schema.Set) + nsxvNetwork := d.Get("vsphere_network").(*schema.Set) + + switch { + // NSX-T network defined. Can only be one. + case len(nsxtNetwork.List()) > 0: + nsxtNetworkSlice := nsxtNetwork.List() + nsxtNetworkStrings := convertToStringMap(nsxtNetworkSlice[0].(map[string]interface{})) + backing := types.ExternalNetworkV2Backing{ + BackingID: nsxtNetworkStrings["nsxt_tier0_router_id"], // Tier 0- router + BackingType: types.ExternalNetworkBackingTypeNsxtTier0Router, + NetworkProvider: types.NetworkProvider{ + ID: nsxtNetworkStrings["nsxt_manager_id"], // NSX-T manager + }, + } + backings.Values = append(backings.Values, backing) + // NSX-V network defined. Can be multiple blocks + case len(nsxvNetwork.List()) > 0: + nsxvNetworkSlice := nsxvNetwork.List() + + for nsxvNetworkIndex := range nsxvNetworkSlice { + + nsxvNetworkStrings := convertToStringMap(nsxvNetworkSlice[nsxvNetworkIndex].(map[string]interface{})) + + // Lookup portgroup type to avoid user passing it because it was already present in datasource + pgType, err := getPortGroupTypeById(vcdClient, nsxvNetworkStrings["portgroup_id"], nsxvNetworkStrings["vcenter_id"]) + if err != nil { + return types.ExternalNetworkV2Backings{}, fmt.Errorf("error validating portgroup type: %s", err) + } + + backing := types.ExternalNetworkV2Backing{ + BackingID: nsxvNetworkStrings["portgroup_id"], + BackingType: pgType, + NetworkProvider: types.NetworkProvider{ + ID: nsxvNetworkStrings["vcenter_id"], + }, + } + + backings.Values = append(backings.Values, backing) + } + } + return backings, nil +} + +func getPortGroupTypeById(vcdClient *VCDClient, portGroupId, vCenterId string) (string, error) { + var pgType string + + // Lookup portgroup_type + pgs, err := govcd.QueryPortGroups(vcdClient.VCDClient, "moref=="+portGroupId) + if err != nil { + return "", fmt.Errorf("error validating portgroup '%s' type: %s", portGroupId, err) + } + + for _, pg := range pgs { + if pg.MoRef == portGroupId && haveSameUuid(pg.Vc, vCenterId) { + pgType = pg.PortgroupType + } + } + if pgType == "" { + return "", fmt.Errorf("could not find portgroup type for '%s'", portGroupId) + } + + return pgType, nil +} + +func getSubnetsType(d *schema.ResourceData) []types.ExternalNetworkV2Subnet { + subnets := d.Get("ip_scope").(*schema.Set) + subnetSlice := make([]types.ExternalNetworkV2Subnet, len(subnets.List())) + for subnetIndex, subnet := range subnets.List() { + subnetMap := subnet.(map[string]interface{}) + + subnet := types.ExternalNetworkV2Subnet{ + Gateway: subnetMap["gateway"].(string), + DNSSuffix: subnetMap["dns_suffix"].(string), + DNSServer1: subnetMap["dns1"].(string), + DNSServer2: subnetMap["dns2"].(string), + PrefixLength: subnetMap["prefix_length"].(int), + Enabled: subnetMap["enabled"].(bool), + } + // Loop over IP ranges (static IP pools) + subnet.IPRanges = types.ExternalNetworkV2IPRanges{Values: processIpRanges(subnetMap)} + + subnetSlice[subnetIndex] = subnet + } + return subnetSlice +} + +func processIpRanges(subnetMap map[string]interface{}) []types.ExternalNetworkV2IPRange { + rrr := subnetMap["static_ip_pool"].(*schema.Set) + subnetRng := make([]types.ExternalNetworkV2IPRange, len(rrr.List())) + for rangeIndex, subnetRange := range rrr.List() { + subnetRangeStr := convertToStringMap(subnetRange.(map[string]interface{})) + oneRange := types.ExternalNetworkV2IPRange{ + StartAddress: subnetRangeStr["start_address"], + EndAddress: subnetRangeStr["end_address"], + } + subnetRng[rangeIndex] = oneRange + } + return subnetRng +} + +func setExternalNetworkV2Data(d *schema.ResourceData, net *types.ExternalNetworkV2) error { + _ = d.Set("name", net.Name) + _ = d.Set("description", net.Description) + + // Loop over all subnets (known as ip_scope in UI) + subnetSlice := make([]interface{}, len(net.Subnets.Values)) + for i, subnet := range net.Subnets.Values { + subnetMap := make(map[string]interface{}) + subnetMap["gateway"] = subnet.Gateway + subnetMap["prefix_length"] = subnet.PrefixLength + subnetMap["dns1"] = subnet.DNSServer1 + subnetMap["dns2"] = subnet.DNSServer2 + subnetMap["dns_suffix"] = subnet.DNSSuffix + subnetMap["enabled"] = subnet.Enabled + + // Gather all IP ranges (known as static_ip_pool in UI) + if len(subnet.IPRanges.Values) > 0 { + ipRangeSlice := make([]interface{}, len(subnet.IPRanges.Values)) + for ii, ipRange := range subnet.IPRanges.Values { + ipRangeMap := make(map[string]interface{}) + ipRangeMap["start_address"] = ipRange.StartAddress + ipRangeMap["end_address"] = ipRange.EndAddress + + ipRangeSlice[ii] = ipRangeMap + } + ipRangeSet := schema.NewSet(schema.HashResource(networkV2IpRange), ipRangeSlice) + subnetMap["static_ip_pool"] = ipRangeSet + } + subnetSlice[i] = subnetMap + } + + subnetSet := schema.NewSet(schema.HashResource(networkV2IpScope), subnetSlice) + err := d.Set("ip_scope", subnetSet) + if err != nil { + return fmt.Errorf("error setting 'ip_scope' block: %s", err) + } + + // Switch on first value of backing ID. If it is NSX-T - it can be only one block (limited by schema). + // NSX-V can have more than one + switch net.NetworkBackings.Values[0].BackingType { + // Some versions of VCD behave strangely in API. They do accept a parameter of types.ExternalNetworkBackingTypeNetwork + // as it was always the case, but in response they do return "PORTGROUP". + case types.ExternalNetworkBackingDvPortgroup, types.ExternalNetworkBackingTypeNetwork, "PORTGROUP": + backingInterface := make([]interface{}, len(net.NetworkBackings.Values)) + for backingIndex := range net.NetworkBackings.Values { + backing := net.NetworkBackings.Values[backingIndex] + backingMap := make(map[string]interface{}) + backingMap["vcenter_id"] = backing.NetworkProvider.ID + backingMap["portgroup_id"] = backing.BackingID + + backingInterface[backingIndex] = backingMap + + } + backingSet := schema.NewSet(schema.HashResource(networkV2VsphereNetwork), backingInterface) + err := d.Set("vsphere_network", backingSet) + if err != nil { + return fmt.Errorf("error setting 'vsphere_network' block: %s", err) + } + + // TODO API V34.0 !Important! + // When a VRF Tier-0 router is used - responded backingType is "UNKNOWN" until API v34.0 where + // field `BackingType` is deprecated in favor of `backingTypeValue` which supports explicit + // `NSXT_VRF_TIER0`. VRF Tier-0 routers are only officially supported in 10.2 release. + case types.ExternalNetworkBackingTypeNsxtTier0Router, "UNKNOWN": + backingInterface := make([]interface{}, 1) + backing := net.NetworkBackings.Values[0] + backingMap := make(map[string]interface{}) + backingMap["nsxt_manager_id"] = backing.NetworkProvider.ID + backingMap["nsxt_tier0_router_id"] = backing.BackingID + + backingInterface[0] = backingMap + backingSet := schema.NewSet(schema.HashResource(networkV2NsxtNetwork), backingInterface) + err := d.Set("nsxt_network", backingSet) + if err != nil { + return fmt.Errorf("error setting 'nsxt_network' block: %s", err) + } + + default: + return fmt.Errorf("unrecognized network backing type: %s", net.NetworkBackings.Values[0].BackingType) + } + + return nil +} diff --git a/vcd/resource_vcd_external_network_v2_test.go b/vcd/resource_vcd_external_network_v2_test.go new file mode 100644 index 000000000..69b2b154b --- /dev/null +++ b/vcd/resource_vcd_external_network_v2_test.go @@ -0,0 +1,463 @@ +// +build functional network extnetwork nsxt ALL + +package vcd + +import ( + "fmt" + "regexp" + "testing" + + "github.com/vmware/go-vcloud-director/v2/govcd" + + "github.com/hashicorp/terraform-plugin-sdk/terraform" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +func TestAccVcdExternalNetworkV2NsxtVrf(t *testing.T) { + testAccVcdExternalNetworkV2Nsxt(t, testConfig.Nsxt.Tier0routerVrf) +} + +func TestAccVcdExternalNetworkV2Nsxt(t *testing.T) { + testAccVcdExternalNetworkV2Nsxt(t, testConfig.Nsxt.Tier0router) +} + +func testAccVcdExternalNetworkV2Nsxt(t *testing.T, nsxtTier0Router string) { + + if !usingSysAdmin() { + t.Skip(t.Name() + " requires system admin privileges") + return + } + + skipNoNsxtConfiguration(t) + vcdClient := createTemporaryVCDConnection() + if vcdClient.Client.APIVCDMaxVersionIs("< 33.0") { + t.Skip(t.Name() + " requires at least API v33.0 (vCD 10+)") + } + + startAddress := "192.168.30.51" + endAddress := "192.168.30.62" + description := "Test External Network" + var params = StringMap{ + "NsxtManager": testConfig.Nsxt.Manager, + "NsxtTier0Router": nsxtTier0Router, + "ExternalNetworkName": t.Name(), + "Type": testConfig.Networking.ExternalNetworkPortGroupType, + "PortGroup": testConfig.Networking.ExternalNetworkPortGroup, + "Vcenter": testConfig.Networking.Vcenter, + "StartAddress": startAddress, + "EndAddress": endAddress, + "Description": description, + "Gateway": "192.168.30.49", + "Netmask": "24", + "Tags": "network extnetwork nsxt", + } + + params["FuncName"] = t.Name() + configText := templateFill(testAccCheckVcdExternalNetworkV2Nsxt, params) + debugPrintf("#[DEBUG] CONFIGURATION: %s", configText) + + params["FuncName"] = t.Name() + "step1" + configText1 := templateFill(testAccCheckVcdExternalNetworkV2NsxtStep1, params) + debugPrintf("#[DEBUG] CONFIGURATION: %s", configText1) + + if vcdShortTest { + t.Skip(acceptanceTestsSkipped) + return + } + resourceName := "vcd_external_network_v2.ext-net-nsxt" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckExternalNetworkDestroyV2(t.Name()), + Steps: []resource.TestStep{ + resource.TestStep{ + Config: configText, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", t.Name()), + resource.TestCheckResourceAttr(resourceName, "description", description), + resource.TestCheckResourceAttr(resourceName, "vsphere_network.#", "0"), + resource.TestCheckResourceAttr(resourceName, "nsxt_network.#", "1"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.#", "2"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.1420917927.dns1", ""), + resource.TestCheckResourceAttr(resourceName, "ip_scope.1420917927.dns2", ""), + resource.TestCheckResourceAttr(resourceName, "ip_scope.1420917927.dns_suffix", ""), + resource.TestCheckResourceAttr(resourceName, "ip_scope.1420917927.enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.1420917927.gateway", "192.168.30.49"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.1420917927.prefix_length", "24"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.1420917927.static_ip_pool.#", "1"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.1420917927.static_ip_pool.1203345861.end_address", "192.168.30.62"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.1420917927.static_ip_pool.1203345861.start_address", "192.168.30.51"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.3421983869.dns1", ""), + resource.TestCheckResourceAttr(resourceName, "ip_scope.3421983869.dns2", ""), + resource.TestCheckResourceAttr(resourceName, "ip_scope.3421983869.dns_suffix", ""), + resource.TestCheckResourceAttr(resourceName, "ip_scope.3421983869.enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.3421983869.gateway", "14.14.14.1"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.3421983869.prefix_length", "24"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.3421983869.static_ip_pool.#", "2"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.3421983869.static_ip_pool.2275320158.end_address", "14.14.14.25"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.3421983869.static_ip_pool.2275320158.start_address", "14.14.14.20"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.3421983869.static_ip_pool.550532203.end_address", "14.14.14.15"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.3421983869.static_ip_pool.550532203.start_address", "14.14.14.10"), + resource.TestCheckResourceAttr(resourceName, "nsxt_network.#", "1"), + testCheckMatchOutput("nsxt-manager", regexp.MustCompile("^urn:vcloud:nsxtmanager:.*")), + testCheckOutputNonEmpty("nsxt-tier0-router"), // Match any non empty string + ), + }, + resource.TestStep{ + ResourceName: resourceName + "-import", + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: importStateIdTopHierarchy(t.Name()), + }, + resource.TestStep{ + Config: configText1, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", t.Name()), + resource.TestCheckResourceAttr(resourceName, "description", description), + resource.TestCheckResourceAttr(resourceName, "vsphere_network.#", "0"), + resource.TestCheckResourceAttr(resourceName, "nsxt_network.#", "1"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.#", "1"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.1428757071.dns1", ""), + resource.TestCheckResourceAttr(resourceName, "ip_scope.1428757071.dns2", ""), + resource.TestCheckResourceAttr(resourceName, "ip_scope.1428757071.dns_suffix", ""), + resource.TestCheckResourceAttr(resourceName, "ip_scope.1428757071.enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.1428757071.gateway", "192.168.30.49"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.1428757071.prefix_length", "24"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.1428757071.static_ip_pool.#", "1"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.1428757071.static_ip_pool.1203345861.end_address", "192.168.30.62"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.1428757071.static_ip_pool.1203345861.start_address", "192.168.30.51"), + resource.TestCheckResourceAttr(resourceName, "nsxt_network.#", "1"), + testCheckMatchOutput("nsxt-manager", regexp.MustCompile("^urn:vcloud:nsxtmanager:.*")), + testCheckOutputNonEmpty("nsxt-tier0-router"), // Match any non empty string + + // Data source + resource.TestCheckResourceAttrPair(resourceName, "name", "data."+resourceName, "name"), + resource.TestCheckResourceAttrPair(resourceName, "description", "data."+resourceName, "description"), + resource.TestCheckResourceAttrPair(resourceName, "ip_scope.#", "data."+resourceName, "ip_scope.#"), + resource.TestCheckResourceAttrPair(resourceName, "ip_scope.1428757071.dns1", "data."+resourceName, "ip_scope.1428757071.dns1"), + resource.TestCheckResourceAttrPair(resourceName, "ip_scope.1428757071.dns2", "data."+resourceName, "ip_scope.1428757071.dns2"), + resource.TestCheckResourceAttrPair(resourceName, "ip_scope.1428757071.dns_suffix", "data."+resourceName, "ip_scope.1428757071.dns_suffix"), + resource.TestCheckResourceAttrPair(resourceName, "ip_scope.1428757071.enabled", "data."+resourceName, "ip_scope.1428757071.enabled"), + resource.TestCheckResourceAttrPair(resourceName, "ip_scope.1428757071.gateway", "data."+resourceName, "ip_scope.1428757071.gateway"), + resource.TestCheckResourceAttrPair(resourceName, "ip_scope.1428757071.prefix_length", "data."+resourceName, "ip_scope.1428757071.prefix_length"), + resource.TestCheckResourceAttrPair(resourceName, "ip_scope.1428757071.static_ip_pool.#", "data."+resourceName, "ip_scope.1428757071.static_ip_pool.#"), + resource.TestCheckResourceAttrPair(resourceName, "ip_scope.1428757071.static_ip_pool.1203345861.end_address", "data."+resourceName, "ip_scope.1428757071.static_ip_pool.1203345861.end_address"), + resource.TestCheckResourceAttrPair(resourceName, "ip_scope.1428757071.static_ip_pool.1203345861.start_address", "data."+resourceName, "ip_scope.1428757071.static_ip_pool.1203345861.start_address"), + resource.TestCheckResourceAttrPair(resourceName, "vsphere_network.#", "data."+resourceName, "vsphere_network.#"), + resource.TestCheckResourceAttrPair(resourceName, "nsxt_network.#", "data."+resourceName, "nsxt_network.#"), + resource.TestMatchResourceAttr("data."+resourceName, "nsxt_network.0.nsxt_manager_id", regexp.MustCompile("^urn:vcloud:nsxtmanager:.*")), + resource.TestCheckResourceAttrSet("data."+resourceName, "nsxt_network.0.nsxt_tier0_router_id"), + ), + }, + }, + }) +} + +const testAccCheckVcdExternalNetworkV2NsxtDS = ` +data "vcd_nsxt_manager" "main" { + name = "{{.NsxtManager}}" +} + +data "vcd_nsxt_tier0_router" "router" { + name = "{{.NsxtTier0Router}}" + nsxt_manager_id = data.vcd_nsxt_manager.main.id +} + +` + +const testAccCheckVcdExternalNetworkV2Nsxt = testAccCheckVcdExternalNetworkV2NsxtDS + ` +resource "vcd_external_network_v2" "ext-net-nsxt" { + name = "{{.ExternalNetworkName}}" + description = "{{.Description}}" + + nsxt_network { + nsxt_manager_id = data.vcd_nsxt_manager.main.id + nsxt_tier0_router_id = data.vcd_nsxt_tier0_router.router.id + } + + ip_scope { + enabled = false + gateway = "{{.Gateway}}" + prefix_length = "{{.Netmask}}" + + static_ip_pool { + start_address = "{{.StartAddress}}" + end_address = "{{.EndAddress}}" + } + } + + ip_scope { + gateway = "14.14.14.1" + prefix_length = "24" + + static_ip_pool { + start_address = "14.14.14.10" + end_address = "14.14.14.15" + } + + static_ip_pool { + start_address = "14.14.14.20" + end_address = "14.14.14.25" + } + } +} + +output "nsxt-manager" { + value = tolist(vcd_external_network_v2.ext-net-nsxt.nsxt_network)[0].nsxt_manager_id +} + +output "nsxt-tier0-router" { + value = tolist(vcd_external_network_v2.ext-net-nsxt.nsxt_network)[0].nsxt_tier0_router_id +} +` +const testAccCheckVcdExternalNetworkV2NsxtStep1 = testAccCheckVcdExternalNetworkV2NsxtDS + ` +# skip-binary-test: only for updates +resource "vcd_external_network_v2" "ext-net-nsxt" { + name = "{{.ExternalNetworkName}}" + description = "{{.Description}}" + + nsxt_network { + nsxt_manager_id = data.vcd_nsxt_manager.main.id + nsxt_tier0_router_id = data.vcd_nsxt_tier0_router.router.id + } + + ip_scope { + enabled = true + gateway = "{{.Gateway}}" + prefix_length = "{{.Netmask}}" + + static_ip_pool { + start_address = "{{.StartAddress}}" + end_address = "{{.EndAddress}}" + } + } +} + +data "vcd_external_network_v2" "ext-net-nsxt" { + name = vcd_external_network_v2.ext-net-nsxt.name +} + +output "nsxt-manager" { + value = tolist(vcd_external_network_v2.ext-net-nsxt.nsxt_network)[0].nsxt_manager_id +} + +output "nsxt-tier0-router" { + value = tolist(vcd_external_network_v2.ext-net-nsxt.nsxt_network)[0].nsxt_tier0_router_id +} +` + +func TestAccVcdExternalNetworkV2Nsxv(t *testing.T) { + if !usingSysAdmin() { + t.Skip(t.Name() + " requires system admin privileges") + return + } + + vcdClient := createTemporaryVCDConnection() + if vcdClient.Client.APIVCDMaxVersionIs("< 33.0") { + t.Skip(t.Name() + " requires at least API v33.0 (vCD 10+)") + } + + description := "Test External Network" + var params = StringMap{ + "ExternalNetworkName": t.Name(), + "Type": testConfig.Networking.ExternalNetworkPortGroupType, + "PortGroup": testConfig.Networking.ExternalNetworkPortGroup, + "Vcenter": testConfig.Networking.Vcenter, + "StartAddress": "192.168.30.51", + "EndAddress": "192.168.30.62", + "Description": description, + "Gateway": "192.168.30.49", + "Netmask": "24", + "Dns1": "192.168.0.164", + "Dns2": "192.168.0.196", + "Tags": "network extnetwork nsxt", + } + + configText := templateFill(testAccCheckVcdExternalNetworkV2Nsxv, params) + params["FuncName"] = t.Name() + "step1" + configText1 := templateFill(testAccCheckVcdExternalNetworkV2NsxvUpdate, params) + if vcdShortTest { + t.Skip(acceptanceTestsSkipped) + return + } + debugPrintf("#[DEBUG] CONFIGURATION: %s", configText) + debugPrintf("#[DEBUG] CONFIGURATION: %s", configText1) + + resourceName := "vcd_external_network_v2.ext-net-nsxv" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckExternalNetworkDestroyV2(t.Name()), + Steps: []resource.TestStep{ + resource.TestStep{ + Config: configText, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", t.Name()), + resource.TestCheckResourceAttr(resourceName, "description", description), + resource.TestCheckResourceAttr(resourceName, "ip_scope.#", "1"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.2118535427.dns1", "192.168.0.164"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.2118535427.dns2", "192.168.0.196"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.2118535427.dns_suffix", "company.biz"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.2118535427.enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.2118535427.gateway", "192.168.30.49"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.2118535427.prefix_length", "24"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.2118535427.static_ip_pool.#", "1"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.2118535427.static_ip_pool.1203345861.end_address", "192.168.30.62"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.2118535427.static_ip_pool.1203345861.start_address", "192.168.30.51"), + resource.TestCheckResourceAttr(resourceName, "nsxt_network.#", "0"), + resource.TestCheckResourceAttr(resourceName, "vsphere_network.#", "1"), + testCheckOutputNonEmpty("vcenter-id"), // Match any non empty string + testCheckOutputNonEmpty("portgroup-id"), // Match any non empty string + ), + }, + resource.TestStep{ + Config: configText1, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", t.Name()), + resource.TestCheckResourceAttr(resourceName, "description", description), + resource.TestCheckResourceAttr(resourceName, "ip_scope.#", "2"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.2145267691.dns1", "192.168.0.164"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.2145267691.dns2", "192.168.0.196"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.2145267691.dns_suffix", "company.biz"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.2145267691.enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.2145267691.gateway", "192.168.30.49"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.2145267691.prefix_length", "24"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.2145267691.static_ip_pool.#", "1"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.2145267691.static_ip_pool.1203345861.end_address", "192.168.30.62"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.2145267691.static_ip_pool.1203345861.start_address", "192.168.30.51"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.801323554.dns1", "8.8.8.8"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.801323554.dns2", "8.8.4.4"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.801323554.dns_suffix", "asd.biz"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.801323554.enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.801323554.gateway", "88.88.88.1"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.801323554.prefix_length", "24"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.801323554.static_ip_pool.#", "1"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.801323554.static_ip_pool.2396875145.end_address", "88.88.88.100"), + resource.TestCheckResourceAttr(resourceName, "ip_scope.801323554.static_ip_pool.2396875145.start_address", "88.88.88.10"), + resource.TestCheckResourceAttr(resourceName, "nsxt_network.#", "0"), + resource.TestCheckResourceAttr(resourceName, "vsphere_network.#", "1"), + testCheckMatchOutput("vcenter-id", regexp.MustCompile("^urn:vcloud:vimserver:.*")), + testCheckOutputNonEmpty("portgroup-id"), // Match any non empty string because IDs may differ + ), + }, + resource.TestStep{ + ResourceName: resourceName + "-import", + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: importStateIdTopHierarchy(t.Name()), + }, + }, + }) +} + +const testAccCheckVcdExternalNetworkV2NsxvDs = ` +data "vcd_vcenter" "vc" { + name = "{{.Vcenter}}" +} + +data "vcd_portgroup" "sw" { + name = "{{.PortGroup}}" + type = "{{.Type}}" +} + +` + +const testAccCheckVcdExternalNetworkV2Nsxv = testAccCheckVcdExternalNetworkV2NsxvDs + ` +resource "vcd_external_network_v2" "ext-net-nsxv" { + name = "{{.ExternalNetworkName}}" + description = "{{.Description}}" + + vsphere_network { + vcenter_id = data.vcd_vcenter.vc.id + portgroup_id = data.vcd_portgroup.sw.id + } + + ip_scope { + gateway = "{{.Gateway}}" + prefix_length = "{{.Netmask}}" + dns1 = "{{.Dns1}}" + dns2 = "{{.Dns2}}" + dns_suffix = "company.biz" + + static_ip_pool { + start_address = "{{.StartAddress}}" + end_address = "{{.EndAddress}}" + } + } +} + +output "vcenter-id" { + value = tolist(vcd_external_network_v2.ext-net-nsxv.vsphere_network)[0].vcenter_id +} + +output "portgroup-id" { + value = tolist(vcd_external_network_v2.ext-net-nsxv.vsphere_network)[0].portgroup_id +} +` + +const testAccCheckVcdExternalNetworkV2NsxvUpdate = testAccCheckVcdExternalNetworkV2NsxvDs + ` +# skip-binary-test: only for updates +resource "vcd_external_network_v2" "ext-net-nsxv" { + name = "{{.ExternalNetworkName}}" + description = "{{.Description}}" + + vsphere_network { + vcenter_id = data.vcd_vcenter.vc.id + portgroup_id = data.vcd_portgroup.sw.id + } + + ip_scope { + enabled = false + gateway = "{{.Gateway}}" + prefix_length = "{{.Netmask}}" + dns1 = "{{.Dns1}}" + dns2 = "{{.Dns2}}" + dns_suffix = "company.biz" + + static_ip_pool { + start_address = "{{.StartAddress}}" + end_address = "{{.EndAddress}}" + } + } + + ip_scope { + gateway = "88.88.88.1" + prefix_length = "24" + dns1 = "8.8.8.8" + dns2 = "8.8.4.4" + dns_suffix = "asd.biz" + + static_ip_pool { + start_address = "88.88.88.10" + end_address = "88.88.88.100" + } + } +} + +output "vcenter-id" { + value = tolist(vcd_external_network_v2.ext-net-nsxv.vsphere_network)[0].vcenter_id +} + +output "portgroup-id" { + value = tolist(vcd_external_network_v2.ext-net-nsxv.vsphere_network)[0].portgroup_id +} +` + +func testAccCheckExternalNetworkDestroyV2(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "vcd_external_network_v2" && rs.Primary.Attributes["name"] != name { + continue + } + + conn := testAccProvider.Meta().(*VCDClient) + _, err := govcd.GetExternalNetworkV2ByName(conn.VCDClient, rs.Primary.ID) + if err == nil { + return fmt.Errorf("external network v2 %s still exists", rs.Primary.ID) + } + } + + return nil + } +} diff --git a/vcd/resource_vcd_nsxv_firewall_rule_test.go b/vcd/resource_vcd_nsxv_firewall_rule_test.go index c9684905d..38ac487df 100644 --- a/vcd/resource_vcd_nsxv_firewall_rule_test.go +++ b/vcd/resource_vcd_nsxv_firewall_rule_test.go @@ -4,7 +4,6 @@ package vcd import ( "fmt" - "reflect" "regexp" "testing" @@ -451,40 +450,6 @@ func TestAccVcdNsxvEdgeFirewallRule(t *testing.T) { }) } -// resourceFieldsEqual checks if secondObject has all the fields and their values set as the -// firstObject except `[]excludeFields`. This is very useful to check if data sources have all -// the same values as resources -func resourceFieldsEqual(firstObject, secondObject string, excludeFields []string) resource.TestCheckFunc { - return func(s *terraform.State) error { - resource1, ok := s.RootModule().Resources[firstObject] - if !ok { - return fmt.Errorf("unable to find %s", firstObject) - } - - resource2, ok := s.RootModule().Resources[secondObject] - if !ok { - return fmt.Errorf("unable to find %s", secondObject) - } - - for fieldName := range resource1.Primary.Attributes { - // Do not validate the fields marked for exclusion - if stringInSlice(fieldName, excludeFields) { - continue - } - - if vcdTestVerbose { - fmt.Printf("field %s %s (value %s) and %s (value %s))\n", fieldName, firstObject, - resource1.Primary.Attributes[fieldName], secondObject, resource2.Primary.Attributes[fieldName]) - } - if !reflect.DeepEqual(resource1.Primary.Attributes[fieldName], resource2.Primary.Attributes[fieldName]) { - return fmt.Errorf("field %s differs in resources %s (value %s) and %s (value %s)", - fieldName, firstObject, resource1.Primary.Attributes[fieldName], secondObject, resource2.Primary.Attributes[fieldName]) - } - } - return nil - } -} - // importStateFirewallUiNumberByResourceName constructs an import path (ID in Terraform import terms) in the format of: // organization.vdc.edge-gateway-name.ui-no:X // It uses terraform.State to find existing object's UI ID by 'resource.resource-name' diff --git a/vcd/resource_vcd_org.go b/vcd/resource_vcd_org.go index 586dcb20d..b096f651c 100644 --- a/vcd/resource_vcd_org.go +++ b/vcd/resource_vcd_org.go @@ -9,7 +9,6 @@ package vcd import ( "fmt" "log" - "regexp" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/helper/validation" @@ -283,7 +282,7 @@ func resourceOrgDelete(d *schema.ResourceData, m interface{}) error { // organizations created by previous versions, where the ID // was not reliable adminOrg, err := vcdClient.VCDClient.GetAdminOrgByNameOrId(identifier) - if govcd.ContainsNotFound(err) && isUuid(identifier) { + if govcd.ContainsNotFound(err) && govcd.IsUuid(identifier) { adminOrg, err = vcdClient.VCDClient.GetAdminOrgByNameOrId(orgName) } @@ -321,7 +320,7 @@ func resourceOrgUpdate(d *schema.ResourceData, m interface{}) error { // organizations created by previous versions, where the ID // was not reliable adminOrg, err := vcdClient.VCDClient.GetAdminOrgByNameOrId(identifier) - if govcd.ContainsNotFound(err) && isUuid(identifier) { + if govcd.ContainsNotFound(err) && govcd.IsUuid(identifier) { adminOrg, err = vcdClient.VCDClient.GetAdminOrgByNameOrId(orgName) } @@ -433,7 +432,7 @@ func resourceOrgRead(d *schema.ResourceData, m interface{}) error { // The double attempt is a workaround when dealing with // organizations created by previous versions, where the ID // was not reliable - if govcd.ContainsNotFound(err) && isUuid(identifier) { + if govcd.ContainsNotFound(err) && govcd.IsUuid(identifier) { // Identifier was created by previous version and it is not a valid ID // If the Org is not found by ID, , the ID is invalid, and we have the name in the resource data, // we try to access it using the name. @@ -493,9 +492,3 @@ func getOrgNames(d *schema.ResourceData) (orgName string, fullName string, err e } return orgName, fullName, nil } - -// Returns true if the identifier is a bare UUID -func isUuid(identifier string) bool { - reUuid := regexp.MustCompile(`^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$`) - return reUuid.MatchString(identifier) -} diff --git a/vcd/resource_vcd_org_group_test.go b/vcd/resource_vcd_org_group_test.go index 576ff4661..1f9cddff4 100644 --- a/vcd/resource_vcd_org_group_test.go +++ b/vcd/resource_vcd_org_group_test.go @@ -95,10 +95,12 @@ func TestAccVcdOrgGroup(t *testing.T) { // Remove LDAP settings at the end of test defer func() { - fmt.Printf("# Removing LDAP settings for Org '%s'\n", ldapConfig.org.AdminOrg.Name) - err := ldapConfig.org.LdapDisable() - if err != nil { - ldapConfig.t.Errorf("error removing LDAP settings for Org '%s': %s", ldapConfig.org.AdminOrg.Name, err) + if ldapConfig.org != nil && ldapConfig.org.AdminOrg != nil { + fmt.Printf("# Removing LDAP settings for Org '%s'\n", ldapConfig.org.AdminOrg.Name) + err := ldapConfig.org.LdapDisable() + if err != nil { + ldapConfig.t.Errorf("error removing LDAP settings for Org '%s': %s", ldapConfig.org.AdminOrg.Name, err) + } } }() diff --git a/vcd/resource_vcd_vm_affinity_rule.go b/vcd/resource_vcd_vm_affinity_rule.go index 037cd3dd9..87de839bd 100644 --- a/vcd/resource_vcd_vm_affinity_rule.go +++ b/vcd/resource_vcd_vm_affinity_rule.go @@ -336,7 +336,7 @@ func resourceVcdVmAffinityRuleImport(d *schema.ResourceData, meta interface{}) ( return nil, fmt.Errorf("[VM affinity rule import] empty VM affinity rule identifier provided") } - lookingForId := isUuid(affinityRuleIdentifier) + lookingForId := govcd.IsUuid(affinityRuleIdentifier) vcdClient := meta.(*VCDClient) adminOrg, err := vcdClient.GetAdminOrgByName(orgName) if err != nil { diff --git a/vcd/sample_vcd_test_config.json b/vcd/sample_vcd_test_config.json index 3fda89f2d..6b6c22bb0 100644 --- a/vcd/sample_vcd_test_config.json +++ b/vcd/sample_vcd_test_config.json @@ -91,6 +91,11 @@ "externalNetworkPortGroup": "ForTestingPG", "externalNetworkPortGroupType": "DV_PORTGROUP" }, + "nsxt": { + "manager": "nsxManager1", + "tier0router": "tier-0-2", + "tier0routervrf": "tier-0-2" + }, "logging" : { "//": "Enables logging from go-vcloud-director in vendor", "enabled": false, diff --git a/vcd/testcheck_funcs_test.go b/vcd/testcheck_funcs_test.go index 3e33b226c..cac0bb95c 100644 --- a/vcd/testcheck_funcs_test.go +++ b/vcd/testcheck_funcs_test.go @@ -1,9 +1,12 @@ -// +build vapp vm user ALL functional +// +build vapp vm user nsxt extnetwork network gateway ALL functional package vcd import ( "fmt" + "reflect" + "regexp" + "strconv" "github.com/hashicorp/terraform-plugin-sdk/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/terraform" @@ -61,3 +64,97 @@ func (c *testCachedFieldValue) testCheckCachedResourceFieldValue(resource, field return nil } } + +// testCheckMatchOutput allows to match output field with regexp +func testCheckMatchOutput(name string, r *regexp.Regexp) resource.TestCheckFunc { + return func(s *terraform.State) error { + ms := s.RootModule() + rs, ok := ms.Outputs[name] + if !ok { + return fmt.Errorf("Not found: %s", name) + } + + if !r.MatchString(rs.Value.(string)) { + return fmt.Errorf( + "Output '%s': expected %#v, got %#v", name, rs.Value, rs) + } + + return nil + } +} + +// testCheckOutputNonEmpty checks that output field is not empty +func testCheckOutputNonEmpty(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + ms := s.RootModule() + rs, ok := ms.Outputs[name] + if !ok { + return fmt.Errorf("Not found: %s", name) + } + + if rs.Value.(string) == "" { + return fmt.Errorf("Output '%s': expected '', got %#v", name, rs) + } + + return nil + } +} + +// resourceFieldsEqual checks if secondObject has all the fields and their values set as the +// firstObject except `[]excludeFields`. This is very useful to check if data sources have all +// the same values as resources +func resourceFieldsEqual(firstObject, secondObject string, excludeFields []string) resource.TestCheckFunc { + return func(s *terraform.State) error { + resource1, ok := s.RootModule().Resources[firstObject] + if !ok { + return fmt.Errorf("unable to find %s", firstObject) + } + + resource2, ok := s.RootModule().Resources[secondObject] + if !ok { + return fmt.Errorf("unable to find %s", secondObject) + } + + for fieldName := range resource1.Primary.Attributes { + // Do not validate the fields marked for exclusion + if stringInSlice(fieldName, excludeFields) { + continue + } + + if vcdTestVerbose { + fmt.Printf("field %s %s (value %s) and %s (value %s))\n", fieldName, firstObject, + resource1.Primary.Attributes[fieldName], secondObject, resource2.Primary.Attributes[fieldName]) + } + if !reflect.DeepEqual(resource1.Primary.Attributes[fieldName], resource2.Primary.Attributes[fieldName]) { + return fmt.Errorf("field %s differs in resources %s (value %s) and %s (value %s)", + fieldName, firstObject, resource1.Primary.Attributes[fieldName], secondObject, resource2.Primary.Attributes[fieldName]) + } + } + return nil + } +} + +func resourceFieldIntNotEqual(object, field string, notEqualTo int) resource.TestCheckFunc { + return func(s *terraform.State) error { + resource, ok := s.RootModule().Resources[object] + if !ok { + return fmt.Errorf("unable to find %s", object) + } + + fieldValue, fieldSet := resource.Primary.Attributes[field] + if !fieldSet { + return fmt.Errorf("expected field '%s' to be set in resource '%s'", field, object) + } + + intFieldValue, err := strconv.Atoi(fieldValue) + if err != nil { + return fmt.Errorf("could not convert field '%s' to int: %s", fieldValue, err) + } + + if intFieldValue == notEqualTo { + return fmt.Errorf("expected field '%s' value to be '%d'!='%d'", object, intFieldValue, notEqualTo) + } + + return nil + } +} diff --git a/vendor/github.com/vmware/go-vcloud-director/v2/govcd/api.go b/vendor/github.com/vmware/go-vcloud-director/v2/govcd/api.go index b6ca432ea..3bde69dad 100644 --- a/vendor/github.com/vmware/go-vcloud-director/v2/govcd/api.go +++ b/vendor/github.com/vmware/go-vcloud-director/v2/govcd/api.go @@ -15,6 +15,7 @@ import ( "net/http" "net/url" "os" + "regexp" "strings" "time" @@ -658,6 +659,49 @@ func takeStringPointer(x string) *string { return &x } +// IsUuid returns true if the identifier is a bare UUID +func IsUuid(identifier string) bool { + reUuid := regexp.MustCompile(`^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$`) + return reUuid.MatchString(identifier) +} + +// isUrn validates if supplied identifier is of URN format (e.g. urn:vcloud:nsxtmanager:09722307-aee0-4623-af95-7f8e577c9ebc) +// it checks for the following criteria: +// 1. idenfifier is not empty +// 2. identifier has 4 elements separated by ':' +// 3. element 1 is 'urn' and element 4 is valid UUID +func isUrn(identifier string) bool { + if identifier == "" { + return false + } + + ss := strings.Split(identifier, ":") + if len(ss) != 4 { + return false + } + + if ss[0] != "urn" && !IsUuid(ss[3]) { + return false + } + + return true +} + +// BuildUrnWithUuid helps to build valid URNs where APIs require URN format, but other API responds with UUID (or +// extracted from HREF) +func BuildUrnWithUuid(urnPrefix, uuid string) (string, error) { + if !IsUuid(uuid) { + return "", fmt.Errorf("supplied uuid '%s' is not valid UUID", uuid) + } + + urn := urnPrefix + uuid + if !isUrn(urn) { + return "", fmt.Errorf("failed building valid URN '%s'", urn) + } + + return urn, nil +} + // takeFloatAddress is a helper that returns the address of an `float64` func takeFloatAddress(x float64) *float64 { return &x diff --git a/vendor/github.com/vmware/go-vcloud-director/v2/govcd/external_network_v2.go b/vendor/github.com/vmware/go-vcloud-director/v2/govcd/external_network_v2.go new file mode 100644 index 000000000..b5d58bb4b --- /dev/null +++ b/vendor/github.com/vmware/go-vcloud-director/v2/govcd/external_network_v2.go @@ -0,0 +1,192 @@ +/* + * Copyright 2020 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" +) + +// ExternalNetworkV2 is a type for version 2 of external network which uses OpenAPI endpoint to +// manage external networks of both types (NSX-V and NSX-T) +type ExternalNetworkV2 struct { + ExternalNetwork *types.ExternalNetworkV2 + client *Client +} + +// CreateExternalNetworkV2 creates a new external network using OpenAPI endpoint. It can create +// NSX-V and NSX-T backed networks based on what ExternalNetworkV2.NetworkBackings is +// provided. types.ExternalNetworkV2 has documented fields. +func CreateExternalNetworkV2(vcdClient *VCDClient, newExtNet *types.ExternalNetworkV2) (*ExternalNetworkV2, error) { + endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointExternalNetworks + minimumApiVersion, err := vcdClient.Client.checkOpenApiEndpointCompatibility(endpoint) + if err != nil { + return nil, err + } + + urlRef, err := vcdClient.Client.OpenApiBuildEndpoint(endpoint) + if err != nil { + return nil, err + } + + returnExtNet := &ExternalNetworkV2{ + ExternalNetwork: &types.ExternalNetworkV2{}, + client: &vcdClient.Client, + } + + err = vcdClient.Client.OpenApiPostItem(minimumApiVersion, urlRef, nil, newExtNet, returnExtNet.ExternalNetwork) + if err != nil { + return nil, fmt.Errorf("error creating external network: %s", err) + } + + return returnExtNet, nil +} + +// GetExternalNetworkV2ById retrieves external network by given ID using OpenAPI endpoint +func GetExternalNetworkV2ById(vcdClient *VCDClient, id string) (*ExternalNetworkV2, error) { + endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointExternalNetworks + minimumApiVersion, err := vcdClient.Client.checkOpenApiEndpointCompatibility(endpoint) + if err != nil { + return nil, err + } + + if id == "" { + return nil, fmt.Errorf("empty external network id") + } + + urlRef, err := vcdClient.Client.OpenApiBuildEndpoint(endpoint, id) + if err != nil { + return nil, err + } + + extNet := &ExternalNetworkV2{ + ExternalNetwork: &types.ExternalNetworkV2{}, + client: &vcdClient.Client, + } + + err = vcdClient.Client.OpenApiGetItem(minimumApiVersion, urlRef, nil, extNet.ExternalNetwork) + if err != nil { + return nil, err + } + + return extNet, nil +} + +// GetExternalNetworkV2ByName retrieves external network by given name using OpenAPI endpoint. +// Returns an error if not exactly one network is found. +func GetExternalNetworkV2ByName(vcdClient *VCDClient, name string) (*ExternalNetworkV2, error) { + + if name == "" { + return nil, fmt.Errorf("name cannot be empty") + } + + queryParams := url.Values{} + queryParams.Add("filter", "name=="+name) + + res, err := GetAllExternalNetworksV2(vcdClient, queryParams) + if err != nil { + return nil, fmt.Errorf("could not find external network by name: %s", err) + } + + if len(res) == 0 { + return nil, fmt.Errorf("%s: expected exactly one external network with name '%s'. Got %d", ErrorEntityNotFound, name, len(res)) + } + + if len(res) > 1 { + return nil, fmt.Errorf("expected exactly one external network with name '%s'. Got %d", name, len(res)) + } + + return res[0], nil +} + +// GetAllExternalNetworksV2 retrieves all external networks using OpenAPI endpoint. Query parameters can be supplied to +// perform additional filtering +func GetAllExternalNetworksV2(vcdClient *VCDClient, queryParameters url.Values) ([]*ExternalNetworkV2, error) { + endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointExternalNetworks + minimumApiVersion, err := vcdClient.Client.checkOpenApiEndpointCompatibility(endpoint) + if err != nil { + return nil, err + } + + urlRef, err := vcdClient.Client.OpenApiBuildEndpoint(endpoint) + if err != nil { + return nil, err + } + + typeResponses := []*types.ExternalNetworkV2{{}} + err = vcdClient.Client.OpenApiGetAllItems(minimumApiVersion, urlRef, queryParameters, &typeResponses) + if err != nil { + return nil, err + } + + // Wrap all typeResponses into external network types with client + returnExtNetworks := make([]*ExternalNetworkV2, len(typeResponses)) + for sliceIndex := range typeResponses { + returnExtNetworks[sliceIndex] = &ExternalNetworkV2{ + ExternalNetwork: typeResponses[sliceIndex], + client: &vcdClient.Client, + } + } + + return returnExtNetworks, nil +} + +// Update updates existing external network using OpenAPI endpoint +func (extNet *ExternalNetworkV2) Update() (*ExternalNetworkV2, error) { + endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointExternalNetworks + minimumApiVersion, err := extNet.client.checkOpenApiEndpointCompatibility(endpoint) + if err != nil { + return nil, err + } + + if extNet.ExternalNetwork.ID == "" { + return nil, fmt.Errorf("cannot update external network without id") + } + + urlRef, err := extNet.client.OpenApiBuildEndpoint(endpoint, extNet.ExternalNetwork.ID) + if err != nil { + return nil, err + } + + returnExtNet := &ExternalNetworkV2{ + ExternalNetwork: &types.ExternalNetworkV2{}, + client: extNet.client, + } + + err = extNet.client.OpenApiPutItem(minimumApiVersion, urlRef, nil, extNet.ExternalNetwork, returnExtNet.ExternalNetwork) + if err != nil { + return nil, fmt.Errorf("error updating external network: %s", err) + } + + return returnExtNet, nil +} + +// Delete deletes external network using OpenAPI endpoint +func (extNet *ExternalNetworkV2) Delete() error { + endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointExternalNetworks + minimumApiVersion, err := extNet.client.checkOpenApiEndpointCompatibility(endpoint) + if err != nil { + return err + } + + if extNet.ExternalNetwork.ID == "" { + return fmt.Errorf("cannot delete external network without id") + } + + urlRef, err := extNet.client.OpenApiBuildEndpoint(endpoint, extNet.ExternalNetwork.ID) + if err != nil { + return err + } + + err = extNet.client.OpenApiDeleteItem(minimumApiVersion, urlRef, nil) + + if err != nil { + return fmt.Errorf("error deleting extNet: %s", err) + } + + return nil +} diff --git a/vendor/github.com/vmware/go-vcloud-director/v2/govcd/nsxt_tier0_router.go b/vendor/github.com/vmware/go-vcloud-director/v2/govcd/nsxt_tier0_router.go new file mode 100644 index 000000000..8c6719b17 --- /dev/null +++ b/vendor/github.com/vmware/go-vcloud-director/v2/govcd/nsxt_tier0_router.go @@ -0,0 +1,138 @@ +/* + * Copyright 2020 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" +) + +// NsxtTier0Router +type NsxtTier0Router struct { + NsxtTier0Router *types.NsxtTier0Router + client *Client +} + +// GetImportableNsxtTier0RouterByName retrieves NSX-T tier 0 router by given parent NSX-T manager ID and Tier 0 router +// name +// +// Warning. The API returns only unused Tier-0 routers (the ones that are not used in external networks yet) +// +// Note. NSX-T manager ID is mandatory and must be in URN format (e.g. +// urn:vcloud:nsxtmanager:09722307-aee0-4623-af95-7f8e577c9ebc) + +func (vcdCli *VCDClient) GetImportableNsxtTier0RouterByName(name, nsxtManagerId string) (*NsxtTier0Router, error) { + if nsxtManagerId == "" { + return nil, fmt.Errorf("no NSX-T manager ID specified") + } + + if !isUrn(nsxtManagerId) { + return nil, fmt.Errorf("NSX-T manager ID is not URN (e.g. 'urn:vcloud:nsxtmanager:09722307-aee0-4623-af95-7f8e577c9ebc)', got: %s", nsxtManagerId) + } + + if name == "" { + return nil, fmt.Errorf("empty Tier 0 router name specified") + } + + // Ideally FIQL filter could be used to filter on server side and get only desired result, but filtering on + // 'displayName' is not yet supported. The only supported field for filtering is + // _context==urn:vcloud:nsxtmanager:09722307-aee0-4623-af95-7f8e577c9ebc to specify parent NSX-T manager (This + // automatically happens in GetAllImportableNsxtTier0Routers()). The below filter injection is left as documentation. + /* + queryParameters := copyOrNewUrlValues(nil) + queryParameters.Add("filter", "displayName=="+name) + */ + + nsxtTier0Routers, err := vcdCli.GetAllImportableNsxtTier0Routers(nsxtManagerId, nil) + if err != nil { + return nil, fmt.Errorf("could not find NSX-T Tier-0 router with name '%s' for NSX-T manager with id '%s': %s", + name, nsxtManagerId, err) + } + + // TODO remove this when FIQL supports filtering on 'displayName' + nsxtTier0Routers = filterNsxtTier0RoutersInExternalNetworks(name, nsxtTier0Routers) + // EOF TODO remove this when FIQL supports filtering on 'displayName' + + if len(nsxtTier0Routers) == 0 { + // ErrorEntityNotFound is injected here for the ability to validate problem using ContainsNotFound() + return nil, fmt.Errorf("%s: no NSX-T Tier-0 router with name '%s' for NSX-T manager with id '%s' found", + ErrorEntityNotFound, name, nsxtManagerId) + } + + if len(nsxtTier0Routers) > 1 { + return nil, fmt.Errorf("more than one (%d) NSX-T Tier-0 router with name '%s' for NSX-T manager with id '%s' found", + len(nsxtTier0Routers), name, nsxtManagerId) + } + + return nsxtTier0Routers[0], nil +} + +// filterNsxtTier0RoutersInExternalNetworks is created as a fix for local filtering instead of using +// FIQL filter (because it does not support it). +func filterNsxtTier0RoutersInExternalNetworks(name string, allNnsxtTier0Routers []*NsxtTier0Router) []*NsxtTier0Router { + filteredNsxtTier0Routers := make([]*NsxtTier0Router, 0) + for index, nsxtTier0Router := range allNnsxtTier0Routers { + if allNnsxtTier0Routers[index].NsxtTier0Router.DisplayName == name { + filteredNsxtTier0Routers = append(filteredNsxtTier0Routers, nsxtTier0Router) + } + } + + return filteredNsxtTier0Routers + +} + +// GetAllImportableNsxtTier0Routers retrieves all NSX-T Tier-0 routers using OpenAPI endpoint. Query parameters can be +// supplied to perform additional filtering. By default it injects FIQL filter _context==nsxtManagerId (e.g. +// _context==urn:vcloud:nsxtmanager:09722307-aee0-4623-af95-7f8e577c9ebc) because it is mandatory to list child Tier-0 +// routers. +// +// Warning. The API returns only unused Tier-0 routers (the ones that are not used in external networks yet) +// +// Note. IDs of Tier-0 routers do not have a standard and may look as strings when they are created using UI or as UUIDs +// when they are created using API +func (vcdCli *VCDClient) GetAllImportableNsxtTier0Routers(nsxtManagerId string, queryParameters url.Values) ([]*NsxtTier0Router, error) { + if !isUrn(nsxtManagerId) { + return nil, fmt.Errorf("NSX-T manager ID is not URN (e.g. 'urn:vcloud:nsxtmanager:09722307-aee0-4623-af95-7f8e577c9ebc)', got: %s", nsxtManagerId) + } + + endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointImportableTier0Routers + minimumApiVersion, err := vcdCli.Client.checkOpenApiEndpointCompatibility(endpoint) + if err != nil { + return nil, err + } + + urlRef, err := vcdCli.Client.OpenApiBuildEndpoint(endpoint) + if err != nil { + return nil, err + } + + // Get all Tier-0 routers that are accessible to an organization VDC. Routers that are already associated with an + // External Network are filtered out. The “_context” filter key must be set with the id of the NSX-T manager for which + // we want to get the Tier-0 routers for. + // + // _context==urn:vcloud:nsxtmanager:09722307-aee0-4623-af95-7f8e577c9ebc + + // Create a copy of queryParameters so that original queryParameters are not mutated (because a map is always a + // reference) + queryParams := queryParameterFilterAnd("_context=="+nsxtManagerId, queryParameters) + + typeResponses := []*types.NsxtTier0Router{{}} + err = vcdCli.Client.OpenApiGetAllItems(minimumApiVersion, urlRef, queryParams, &typeResponses) + if err != nil { + return nil, err + } + + returnObjects := make([]*NsxtTier0Router, len(typeResponses)) + for sliceIndex := range typeResponses { + returnObjects[sliceIndex] = &NsxtTier0Router{ + NsxtTier0Router: typeResponses[sliceIndex], + client: &vcdCli.Client, + } + } + + return returnObjects, nil +} diff --git a/vendor/github.com/vmware/go-vcloud-director/v2/govcd/openapi.go b/vendor/github.com/vmware/go-vcloud-director/v2/govcd/openapi.go index 860c1f237..23e6c7a19 100644 --- a/vendor/github.com/vmware/go-vcloud-director/v2/govcd/openapi.go +++ b/vendor/github.com/vmware/go-vcloud-director/v2/govcd/openapi.go @@ -283,7 +283,7 @@ func (client *Client) OpenApiPostItem(apiVersion string, urlRef *url.URL, params // Task Owner ID is the ID of created object. ID must be used (although HREF exists in task) because HREF points to // old XML API and here we need to pull data from OpenAPI. - newObjectUrl, _ := url.ParseRequestURI(urlRefCopy.String() + "/" + task.Task.Owner.ID) + newObjectUrl, _ := url.ParseRequestURI(urlRefCopy.String() + task.Task.Owner.ID) err = client.OpenApiGetItem(apiVersion, newObjectUrl, nil, outType) if err != nil { return fmt.Errorf("error retrieving item after creation: %s", err) @@ -665,6 +665,41 @@ func jsonRawMessagesToStrings(messages []json.RawMessage) []string { return resultString } +// copyOrNewUrlValues either creates a copy of parameters or instantiates a new url.Values if nil parameters are +// supplied. It helps to avoid mutating supplied parameter when additional values must be injected internally. +func copyOrNewUrlValues(parameters url.Values) url.Values { + parameterCopy := make(map[string][]string) + + // if supplied parameters are nil - we just return new initialized + if parameters == nil { + return parameterCopy + } + + // Copy URL values + for key, value := range parameters { + parameterCopy[key] = value + } + + return parameterCopy +} + +// queryParameterFilterAnd is a helper to append "AND" clause to FIQL filter by using ';' (semicolon) if any values are +// already set in 'filter' value of parameters. If none existed before then 'filter' value will be set. +// +// Note. It does a copy of supplied 'parameters' value and does not mutate supplied original parameters. +func queryParameterFilterAnd(filter string, parameters url.Values) url.Values { + newParameters := copyOrNewUrlValues(parameters) + + existingFilter := newParameters.Get("filter") + if existingFilter == "" { + newParameters.Set("filter", filter) + return newParameters + } + + newParameters.Set("filter", existingFilter+";"+filter) + return newParameters +} + // defaultPageSize allows to set 'pageSize' query parameter to defaultPageSize if one is not already specified in // url.Values while preserving all other supplied url.Values func defaultPageSize(queryParams url.Values, defaultPageSize string) url.Values { diff --git a/vendor/github.com/vmware/go-vcloud-director/v2/govcd/openapi_endpoints.go b/vendor/github.com/vmware/go-vcloud-director/v2/govcd/openapi_endpoints.go index 814cf6fa8..20e54b9ab 100644 --- a/vendor/github.com/vmware/go-vcloud-director/v2/govcd/openapi_endpoints.go +++ b/vendor/github.com/vmware/go-vcloud-director/v2/govcd/openapi_endpoints.go @@ -12,7 +12,12 @@ import ( // endpointMinApiVersions holds mapping of OpenAPI endpoints and API versions they were introduced in. var endpointMinApiVersions = map[string]string{ - types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointRoles: "31.0", + types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointRoles: "31.0", + types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointAuditTrail: "33.0", + types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointImportableTier0Routers: "32.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") + types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointExternalNetworks: "33.0", types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointVdcComputePolicies: "32.0", types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointVdcAssignedComputePolicies: "33.0", } diff --git a/vendor/github.com/vmware/go-vcloud-director/v2/govcd/sample_govcd_test_config.yaml b/vendor/github.com/vmware/go-vcloud-director/v2/govcd/sample_govcd_test_config.yaml index 269012d35..deb297691 100644 --- a/vendor/github.com/vmware/go-vcloud-director/v2/govcd/sample_govcd_test_config.yaml +++ b/vendor/github.com/vmware/go-vcloud-director/v2/govcd/sample_govcd_test_config.yaml @@ -58,6 +58,13 @@ vcd: name: nsxTPvdc1 storage_profile: "*" network_pool: "NSX-T Overlay 1" + nsxt: + # NSX-T manager to be used + manager: nsxManager1 + # NSX-T tier-0 router used for external network + tier0router: tier-0-router + # NSX-T tier-0 VRF router used for external network + tier0routerVrf: tier-0-router-vrf # An Org catalog, possibly containing at least one item catalog: name: mycat diff --git a/vendor/github.com/vmware/go-vcloud-director/v2/govcd/system.go b/vendor/github.com/vmware/go-vcloud-director/v2/govcd/system.go index fa2649cdf..8952d8ac8 100644 --- a/vendor/github.com/vmware/go-vcloud-director/v2/govcd/system.go +++ b/vendor/github.com/vmware/go-vcloud-director/v2/govcd/system.go @@ -799,6 +799,20 @@ func QueryOrgVdcNetworkByName(vcdCli *VCDClient, name string) ([]*types.QueryRes return results.Results.OrgVdcNetworkRecord, nil } +// QueryNsxtManagerByName searches for NSX-T managers available in VCD +func (vcdCli *VCDClient) QueryNsxtManagerByName(name string) ([]*types.QueryResultNsxtManagerRecordType, error) { + results, err := vcdCli.QueryWithNotEncodedParams(nil, map[string]string{ + "type": "nsxTManager", + "filter": fmt.Sprintf("name==%s", url.QueryEscape(name)), + "filterEncoded": "true", + }) + if err != nil { + return nil, err + } + + return results.Results.NsxtManagerRecord, nil +} + // GetOrgByName finds an Organization by name // On success, returns a pointer to the Org structure and a nil error // On failure, returns a nil pointer and an error diff --git a/vendor/github.com/vmware/go-vcloud-director/v2/types/v56/constants.go b/vendor/github.com/vmware/go-vcloud-director/v2/types/v56/constants.go index ebd581b18..1e012eca4 100644 --- a/vendor/github.com/vmware/go-vcloud-director/v2/types/v56/constants.go +++ b/vendor/github.com/vmware/go-vcloud-director/v2/types/v56/constants.go @@ -325,6 +325,9 @@ const ( const ( OpenApiPathVersion1_0_0 = "1.0.0/" OpenApiEndpointRoles = "roles/" + OpenApiEndpointAuditTrail = "auditTrail/" + OpenApiEndpointImportableTier0Routers = "nsxTResources/importableTier0Routers" + OpenApiEndpointExternalNetworks = "externalNetworks/" OpenApiEndpointVdcComputePolicies = "vdcComputePolicies/" OpenApiEndpointVdcAssignedComputePolicies = "vdcs/%s/computePolicies" ) @@ -336,3 +339,14 @@ const ( // HeaderAuthContext requires the Org name of the tenant HeaderAuthContext = "X-VMWARE-VCLOUD-AUTH-CONTEXT" ) + +const ( + // ExternalNetworkBackingTypeNsxtTier0Router defines backing type of NSX-T Tier-0 router + ExternalNetworkBackingTypeNsxtTier0Router = "NSXT_TIER0" + // ExternalNetworkBackingTypeNsxtVrfTier0Router defines backing type of NSX-T Tier-0 VRF router + ExternalNetworkBackingTypeNsxtVrfTier0Router = "NSXT_VRF_TIER0" + // ExternalNetworkBackingTypeNetwork defines vSwitch portgroup + ExternalNetworkBackingTypeNetwork = "NETWORK" + // ExternalNetworkBackingDvPortgroup refers distributed switch portgroup + ExternalNetworkBackingDvPortgroup = "DV_PORTGROUP" +) diff --git a/vendor/github.com/vmware/go-vcloud-director/v2/types/v56/openapi.go b/vendor/github.com/vmware/go-vcloud-director/v2/types/v56/openapi.go index 887dbbfba..5dce9e5e1 100644 --- a/vendor/github.com/vmware/go-vcloud-director/v2/types/v56/openapi.go +++ b/vendor/github.com/vmware/go-vcloud-director/v2/types/v56/openapi.go @@ -51,6 +51,91 @@ type Role struct { ReadOnly bool `json:"readOnly"` } +// NsxtTier0Router defines NSX-T Tier 0 router +type NsxtTier0Router struct { + ID string `json:"id,omitempty"` + Description string `json:"description"` + DisplayName string `json:"displayName"` +} + +// ExternalNetworkV2 defines a struct for OpenAPI endpoint which is capable of creating NSX-V or +// NSX-T external network based on provided NetworkBackings. +type ExternalNetworkV2 struct { + // ID is unique for the network. This field is read-only. + ID string `json:"id,omitempty"` + // Name of the network. + Name string `json:"name"` + // Description of the network + Description string `json:"description"` + // Subnets define one or more subnets and IP allocation pools in edge gateway + Subnets ExternalNetworkV2Subnets `json:"subnets"` + // NetworkBackings for this external network. Describes if this external network is backed by + // port groups, vCenter standard switch or an NSX-T Tier-0 router. + NetworkBackings ExternalNetworkV2Backings `json:"networkBackings"` +} + +// ExternalNetworkV2IPRange defines allocated IP pools for a subnet in external network +type ExternalNetworkV2IPRange struct { + // StartAddress holds starting IP address in the range + StartAddress string `json:"startAddress"` + // EndAddress holds ending IP address in the range + EndAddress string `json:"endAddress"` +} + +// ExternalNetworkV2IPRanges contains slice of ExternalNetworkV2IPRange +type ExternalNetworkV2IPRanges struct { + Values []ExternalNetworkV2IPRange `json:"values"` +} + +// ExternalNetworkV2Subnets contains slice of ExternalNetworkV2Subnet +type ExternalNetworkV2Subnets struct { + Values []ExternalNetworkV2Subnet `json:"values"` +} + +// ExternalNetworkV2Subnet defines one subnet for external network with assigned static IP ranges +type ExternalNetworkV2Subnet struct { + // Gateway for the subnet + Gateway string `json:"gateway"` + // PrefixLength holds prefix length of the subnet + PrefixLength int `json:"prefixLength"` + // DNSSuffix is the DNS suffix that VMs attached to this network will use (NSX-V only) + DNSSuffix string `json:"dnsSuffix"` + // DNSServer1 - first DNS server that VMs attached to this network will use (NSX-V only) + DNSServer1 string `json:"dnsServer1"` + // DNSServer2 - second DNS server that VMs attached to this network will use (NSX-V only) + DNSServer2 string `json:"dnsServer2"` + // Enabled indicates whether the external network subnet is currently enabled + Enabled bool `json:"enabled"` + // UsedIPCount shows number of IP addresses defined by the static IP ranges + UsedIPCount int `json:"usedIpCount,omitempty"` + // TotalIPCount shows number of IP address used from the static IP ranges + TotalIPCount int `json:"totalIpCount,omitempty"` + // IPRanges define allocated static IP pools allocated from a defined subnet + IPRanges ExternalNetworkV2IPRanges `json:"ipRanges"` +} + +type ExternalNetworkV2Backings struct { + Values []ExternalNetworkV2Backing `json:"values"` +} + +// ExternalNetworkV2Backing defines which networking subsystem is used for external network (NSX-T or NSX-V) +type ExternalNetworkV2Backing struct { + // BackingID must contain either Tier-0 router ID for NSX-T or PortGroup ID for NSX-V + BackingID string `json:"backingId"` + Name string `json:"name,omitempty"` + // BackingType can be either ExternalNetworkBackingTypeNsxtTier0Router in case of NSX-T or one + // of ExternalNetworkBackingTypeNetwork or ExternalNetworkBackingDvPortgroup in case of NSX-V + BackingType string `json:"backingType"` + // NetworkProvider defines backing network manager + NetworkProvider NetworkProvider `json:"networkProvider"` +} + +// NetworkProvider can be NSX-T manager or vCenter. ID is sufficient for creation purpose. +type NetworkProvider struct { + Name string `json:"name,omitempty"` + ID string `json:"id"` +} + // VdcComputePolicy is represented as VM sizing policy in UI type VdcComputePolicy struct { ID string `json:"id,omitempty"` diff --git a/vendor/github.com/vmware/go-vcloud-director/v2/types/v56/types.go b/vendor/github.com/vmware/go-vcloud-director/v2/types/v56/types.go index 11512ee88..825675653 100644 --- a/vendor/github.com/vmware/go-vcloud-director/v2/types/v56/types.go +++ b/vendor/github.com/vmware/go-vcloud-director/v2/types/v56/types.go @@ -2102,6 +2102,7 @@ type QueryResultRecordsType struct { AdminCatalogItemRecord []*QueryResultCatalogItemType `xml:"AdminCatalogItemRecord"` // A record representing an admin catalog item VappTemplateRecord []*QueryResultVappTemplateType `xml:"VAppTemplateRecord"` // A record representing a vApp template AdminVappTemplateRecord []*QueryResultVappTemplateType `xml:"AdminVAppTemplateRecord"` // A record representing an admin vApp template + NsxtManagerRecord []*QueryResultNsxtManagerRecordType `xml:"NsxTManagerRecord"` // A record representing NSX-T manager } // QueryResultCatalogItemType represents a catalog item as query result @@ -2330,7 +2331,7 @@ type QueryResultVirtualCenterRecordType struct { IsEnabled bool `xml:"isEnabled,attr,omitempty"` IsSupported bool `xml:"isSupported,attr,omitempty"` ListenerState string `xml:"listenerState,attr,omitempty"` - Status string `xml:"stats,attr,omitempty"` + Status string `xml:"status,attr,omitempty"` Url string `xml:"url,attr,omitempty"` UserName string `xml:"userName,attr,omitempty"` VcVersion string `xml:"vcVersion,attr,omitempty"` @@ -2644,6 +2645,17 @@ type QueryResultOrgVdcNetworkRecordType struct { Metadata *Metadata `xml:"Metadata,omitempty"` } +type QueryResultNsxtManagerRecordType struct { + Xmlns string `xml:"xmlns,attr,omitempty"` + Name string `xml:"name,attr"` + URL string `xml:"url,attr"` + HREF string `xml:"href,attr"` + Site string `xml:"site,attr"` + LocationId string `xml:"locationId,attr"` + SiteName string `xml:"siteName,attr"` + Link []*Link `xml:"Link,omitempty"` +} + // Represents org VDC Network // Reference: vCloud API 27.0 - Network Pool // https://code.vmware.com/apis/72/vcloud-director#/doc/doc/types/VMWNetworkPoolType.html diff --git a/vendor/github.com/vmware/go-vcloud-director/v2/util/logging.go b/vendor/github.com/vmware/go-vcloud-director/v2/util/logging.go index b64f839da..ad8b386e4 100644 --- a/vendor/github.com/vmware/go-vcloud-director/v2/util/logging.go +++ b/vendor/github.com/vmware/go-vcloud-director/v2/util/logging.go @@ -297,7 +297,7 @@ func ProcessRequestOutput(caller, operation, url, payload string, req *http.Requ payload = "[binary data]" } if dataSize > 0 { - Logger.Printf("Request data: [%d] %s\n", dataSize, hidePasswords(payload, false)) + Logger.Printf("Request data: [%d]\n%s\n", dataSize, hidePasswords(payload, false)) } Logger.Printf("Req header:\n") logSanitizedHeader(req.Header) @@ -345,9 +345,9 @@ func ProcessResponseOutput(caller string, resp *http.Response, result string) { dataSize := len(result) outTextSize := len(outText) if outTextSize != dataSize { - Logger.Printf("Response text: [%d -> %d] %s\n", dataSize, outTextSize, hideTokens(outText, false)) + Logger.Printf("Response text: [%d -> %d]\n%s\n", dataSize, outTextSize, hideTokens(outText, false)) } else { - Logger.Printf("Response text: [%d] %s\n", dataSize, hideTokens(outText, false)) + Logger.Printf("Response text: [%d]\n%s\n", dataSize, hideTokens(outText, false)) } } diff --git a/vendor/modules.txt b/vendor/modules.txt index 44825ccbc..2966606e5 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -234,7 +234,7 @@ github.com/ulikunitz/xz/lzma # github.com/vmihailenco/msgpack v4.0.1+incompatible github.com/vmihailenco/msgpack github.com/vmihailenco/msgpack/codes -# github.com/vmware/go-vcloud-director/v2 v2.9.0-beta.2 +# github.com/vmware/go-vcloud-director/v2 v2.9.0-rc.1 github.com/vmware/go-vcloud-director/v2/govcd github.com/vmware/go-vcloud-director/v2/types/v56 github.com/vmware/go-vcloud-director/v2/util diff --git a/website/docs/d/external_network_v2.html.markdown b/website/docs/d/external_network_v2.html.markdown new file mode 100644 index 000000000..3880b869c --- /dev/null +++ b/website/docs/d/external_network_v2.html.markdown @@ -0,0 +1,40 @@ +--- +layout: "vcd" +page_title: "VMware Cloud Director: vcd_external_network_v2" +sidebar_current: "docs-vcd-data-source-external-network-v2" +description: |- + Provides a VMware Cloud Director External Network data source (version 2). New version of this data source + uses new VCD API and is capable of creating NSX-T backed external networks as well as port group + backed ones. +--- + +# vcd\_external\_network\_v2 + +Provides a VMware Cloud Director External Network data source (version 2). New version of this data source uses new VCD +API and is capable of handling NSX-T backed external networks as well as port group backed ones. + +-> **Note:** This resource uses new VMware Cloud Director +[OpenAPI](https://code.vmware.com/docs/11982/getting-started-with-vmware-cloud-director-openapi) and +requires at least VCD *10.0+*. It supports both NSX-T and NSX-V backed networks (NSX-T *3.0+* requires VCD *10.1.1+*) + +Supported in provider *v3.0+*. + +## Example Usage + +```hcl +data "vcd_external_network_v2" "ext_net" { + name = "my-nsxt-net" +} + +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) external network name + +## Attribute Reference + +All properties defined in [vcd_external_network_v2](/docs/providers/vcd/r/external_network_v2.html) +resource are available. diff --git a/website/docs/d/nsxt_manager.html.markdown b/website/docs/d/nsxt_manager.html.markdown new file mode 100644 index 000000000..a6ad527b1 --- /dev/null +++ b/website/docs/d/nsxt_manager.html.markdown @@ -0,0 +1,36 @@ +--- +layout: "vcd" +page_title: "VMware Cloud Director: vcd_nsxt_manager" +sidebar_current: "docs-vcd-data-source-nsxt-manager" +description: |- + Provides a data source for available NSX-T manager. +--- + +# vcd\_nsxt\_manager + +Provides a data source for NSX-T manager. + +Supported in provider *v3.0+* + +~> **Note:** This resource uses new VMware Cloud Director +[OpenAPI](https://code.vmware.com/docs/11982/getting-started-with-vmware-cloud-director-openapi) and +requires at least VCD *10.1.1+* and NSX-T *3.0+*. + +## Example Usage + +```hcl +data "vcd_nsxt_manager" "main" { + name = "nsxt-manager-one" +} +``` + + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) NSX-T manager name + +## Attribute reference + +Only ID is set to be able and reference in other resources or data sources. diff --git a/website/docs/d/nsxt_tier0_router.html.markdown b/website/docs/d/nsxt_tier0_router.html.markdown new file mode 100644 index 000000000..24962fb10 --- /dev/null +++ b/website/docs/d/nsxt_tier0_router.html.markdown @@ -0,0 +1,43 @@ +--- +layout: "vcd" +page_title: "VMware Cloud Director: vcd_nsxt_tier0_router" +sidebar_current: "docs-vcd-data-source-nsxt-tier0-router" +description: |- + Provides a data source for available NSX-T Tier-0 routers. +--- + +# vcd\_nsxt\_tier0\_router + +Provides a data source for available NSX-T Tier-0 routers. + +Supported in provider *v3.0+* + +~> **Note:** This resource uses new VMware Cloud Director +[OpenAPI](https://code.vmware.com/docs/11982/getting-started-with-vmware-cloud-director-openapi) and +requires at least VCD *10.1.1+* and NSX-T *3.0+*. + +## Example Usage + +```hcl +data "vcd_nsxt_manager" "main" { + name = "nsxt-manager-one" +} + +data "vcd_nsxt_tier0_router" "router" { + name = "nsxt-tier0-router" + nsxt_manager_id = data.vcd_nsxt_manager.main.id +} +``` + + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) NSX-T Tier-0 router name. **Note**. Tier-0 router name must be unique inside NSX-T manager because +API does not allow to filter by other fields. +* `nsxt_manager_id` - (Required) NSX-T manager should be referenced. + +## Attribute reference + +* `is_assigned` - Boolean value reflecting if Tier-0 router is already consumed by external network. \ No newline at end of file diff --git a/website/docs/d/org_vdc.html.markdown b/website/docs/d/org_vdc.html.markdown index d0ccdbf12..5757919fb 100644 --- a/website/docs/d/org_vdc.html.markdown +++ b/website/docs/d/org_vdc.html.markdown @@ -11,6 +11,8 @@ description: |- Provides a vCloud Director Organization VDC data source. An Organization VDC can be used to reference a VCD and use its data within other resources or data sources. +-> **Note:** This resource supports NSX-T and NSX-V based Org VDCs + Supported in provider *v2.5+* ## Example Usage diff --git a/website/docs/d/portgroup.html.markdown b/website/docs/d/portgroup.html.markdown new file mode 100644 index 000000000..082eeb9f2 --- /dev/null +++ b/website/docs/d/portgroup.html.markdown @@ -0,0 +1,43 @@ +--- +layout: "vcd" +page_title: "VMware Cloud Director: vcd_portgroup" +sidebar_current: "docs-vcd-data-source-portgroup" +description: |- + Provides a data source for available vCenter Port Groups. +--- + +# vcd\_portgroup + +Provides a data source for available vCenter Port Groups. + +Supported in provider *v3.0+* + + +## Example Usage for vSwitch Port Group + +```hcl +data "vcd_portgroup" "first-pg-vswitch" { + name = "pg-name" + type = "NETWORK" +} +``` + +## Example Usage for Distributed Port Group + +```hcl +data "vcd_portgroup" "first-pg-dvswitch" { + name = "pg-name" + type = "DV_PORTGROUP" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) Organization VDC name +* `type` - (Required) `NETWORK` for vSwitch port group or `DV_PORTGROUP` for distributed port group. + +## Attribute reference + +Only ID is set to be able and reference in other resources or data sources. diff --git a/website/docs/d/vcenter.html.markdown b/website/docs/d/vcenter.html.markdown new file mode 100644 index 000000000..ebfda7b93 --- /dev/null +++ b/website/docs/d/vcenter.html.markdown @@ -0,0 +1,36 @@ +--- +layout: "vcd" +page_title: "VMware Cloud Director: vcd_vcenter" +sidebar_current: "docs-vcd-data-source-vcenter" +description: |- + Provides a data source for vCenter server attached to VCD. +--- + +# vcd\_vcenter + +Provides a data source for vCenter server attached to VCD. + +Supported in provider *v3.0+* + + +## Example Usage + +```hcl +data "vcd_vcenter" "vc" { + name = "vcenter-one" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) vCenter name + +## Attribute reference + +* `connection_status` - vCenter connection status (e.g. `CONNECTED`). +* `is_enabled` - Boolean value if vCenter is enabled. +* `status` - vCenter status (e.g. `READY`). +* `vcenter_host` - Hostname of configured vCenter. +* `vcenter_version` - vCenter version (e.g. `6.7.0`) diff --git a/website/docs/r/external_network.html.markdown b/website/docs/r/external_network.html.markdown index 81ff0a771..30e6dd039 100644 --- a/website/docs/r/external_network.html.markdown +++ b/website/docs/r/external_network.html.markdown @@ -13,6 +13,8 @@ Requires system administrator privileges. Supported in provider *v2.2+* +~> **Note:** For NSX-T suported external network please use [vcd_external_network_v2](/docs/providers/vcd/r/external_network_v2.html) + ## Example Usage ```hcl diff --git a/website/docs/r/external_network_v2.html.markdown b/website/docs/r/external_network_v2.html.markdown new file mode 100644 index 000000000..3311b4919 --- /dev/null +++ b/website/docs/r/external_network_v2.html.markdown @@ -0,0 +1,176 @@ +--- +layout: "vcd" +page_title: "VMware Cloud Director: vcd_external_network_v2" +sidebar_current: "docs-vcd-resource-external-network-v2" +description: |- + Provides a VMware Cloud Director External Network resource (version 2). New version of this resource + uses new VCD API and is capable of creating NSX-T backed external networks as well as port group + backed ones. +--- + +# vcd\_external\_network\_v2 + +Provides a VMware Cloud Director External Network resource (version 2). New version of this resource +uses new VCD API and is capable of creating NSX-T backed external networks as well as port group +backed ones. + +-> **Note:** This resource uses new VMware Cloud Director +[OpenAPI](https://code.vmware.com/docs/11982/getting-started-with-vmware-cloud-director-openapi) and +requires at least VCD *10.0+*. It supports both NSX-T and NSX-V backed networks (NSX-T *3.0+* requires VCD *10.1.1+*) + +Supported in provider *v3.0+*. + +## Example Usage (NSX-T backed external network) + +```hcl +data "vcd_nsxt_manager" "main" { + name = "nsxManager" +} + +data "vcd_nsxt_tier0_router" "router" { + name = "first-router" + nsxt_manager_id = data.vcd_nsxt_manager.main.id +} + +resource "vcd_external_network_v2" "ext-net-nsxt" { + name = "nsxt-external-network" + description = "First NSX-T backed network" + + nsxt_network { + nsxt_manager_id = data.vcd_nsxt_manager.main.id + nsxt_tier0_router_id = data.vcd_nsxt_tier0_router.router.id + } + + ip_scope { + enabled = false + gateway = "88.88.88.1" + prefix_length = "24" + + static_ip_pool { + start_address = "88.88.88.88" + end_address = "88.88.88.100" + } + } + + ip_scope { + # enabled = true # by default + gateway = "14.14.14.1" + prefix_length = "24" + + static_ip_pool { + start_address = "14.14.14.10" + end_address = "14.14.14.15" + } + + static_ip_pool { + start_address = "14.14.14.20" + end_address = "14.14.14.25" + } + } +} +``` + +## Example Usage (NSX-V backed external network) +```hcl +data "vcd_vcenter" "vc" { + name = "vc1" +} + +data "vcd_portgroup" "sw" { + name = "TestbedPG" + type = "DV_PORTGROUP" +} + +resource "vcd_external_network_v2" "ext-net-nsxv" { + name = "nsxv-external-network" + description = "NSX-V based external network" + + vsphere_network { + vcenter_id = data.vcd_vcenter.vc.id + portgroup_id = data.vcd_portgroup.sw.id + } + + ip_scope { + gateway = "192.168.30.49" + prefix_length = "24" + dns1 = "192.168.0.164" + dns2 = "192.168.0.196" + dns_suffix = "company.biz" + + static_ip_pool { + start_address = "192.168.30.51" + end_address = "192.168.30.62" + } + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) A unique name for the network +* `description` - (Optional) Network friendly description +* `ip_scope` - (Required) One or more IP scopes for the network. See [IP Scope](#ipscope) below for details. +* `vsphere_network` - (Optional) One or more blocks of [vSphere Network](#vspherenetwork).. +* `nsxt_network` - (Optional) NSX-T network definition. See [NSX-T Network](#nsxtnetwork) below for details. + + +## IP Scope + +* `gateway` - (Required) Gateway of the network. +* `prefix_length` - (Required) Network prefix. +* `static_ip_pool` - (Required) IP ranges used for static pool allocation in the network. See [IP Pool](#ip-pool) below for details. +* `dns1` - (Optional) Primary DNS server. **Only valid** for NSX-V networks. +* `dns2` - (Optional) Secondary DNS server. **Only valid** for NSX-V networks. +* `dns_suffix` (Optional) A FQDN for the virtual machines on this network. **Only valid** for NSX-V networks. +* `enabled` - (Optional) Default is `true`. + + +## IP Pool + +* `start_address` - (Required) Start address of the IP range +* `end_address` - (Required) End address of the IP range + + +## vSphere Network + +* `vcenter_id` - (Required) vCenter ID. Can be looked up using [`vcd_vcenter`](/docs/providers/vcd/d/vcenter.html) data source. +* `portgroup_id` - (Required) vSphere portgroup ID. Can be looked up using [`vcd_portgroup`](/docs/providers/vcd/d/portgroup.html) data source. + + +## NSX-T Network + +* `nsxt_manager_id` - (Required) NSX-T manager ID. Can be looked up using [`vcd_nsxt_manager`](/docs/providers/vcd/d/nsxt_manager.html) data source. +* `nsxt_tier0_router_id` - (Required) NSX-T Tier-0 router ID. Can be looked up using + [`vcd_nsxt_tier0_router`](/docs/providers/vcd/d/nsxt_tier0_router.html) data source. + +## Importing + +~> **Note:** The current implementation of Terraform import can only import resources into the state. It does not generate +configuration. [More information.][docs-import] + +An existing external network can be [imported][docs-import] into this resource via supplying the path for an external network. Since the external network is +at the top of the vCD hierarchy, the path corresponds to the external network name. +For example, using this structure, representing an existing external network that was **not** created using Terraform: + +```hcl +resource "vcd_external_network_v2" "tf-external-network" { + name = "my-ext-net" +} +``` + +You can import such external network into terraform state using this command + +``` +terraform import vcd_external_network_v2.tf-external-network my-ext-net +``` + +[docs-import]:https://www.terraform.io/docs/import/ + +NOTE: the default separator (.) can be changed using Provider.import_separator or variable VCD_IMPORT_SEPARATOR + +While the above structure is the minimum needed to get an import, it is not sufficient to run `terraform plan`, +as it lacks several mandatory fields. To use the imported resource, you will need to add the missing properties +using the data in `terraform.tfstate` as a reference. If the resource does not need modifications, consider using +an [external network data source](/docs/providers/vcd/d/external_network_v2.html) instead. diff --git a/website/docs/r/org_vdc.html.markdown b/website/docs/r/org_vdc.html.markdown index 05e828228..e5faefb3a 100644 --- a/website/docs/r/org_vdc.html.markdown +++ b/website/docs/r/org_vdc.html.markdown @@ -11,6 +11,9 @@ description: |- Provides a vCloud Director Organization VDC resource. This can be used to create and delete an Organization VDC. Requires system administrator privileges. +-> **Note:** This resource supports NSX-T and NSX-V based Org Vdcs by providing relevant +`network_pool_name` and `provider_vdc_name` + Supported in provider *v2.2+* ## Example Usage @@ -62,6 +65,43 @@ resource "vcd_org_vdc" "my-vdc" { } ``` +## Example Usage (NSX-T) +```hcl +resource "vcd_org_vdc" "nsxt-vdc" { + name = "NSXT-VDC" + org = "main-org" + + allocation_model = "ReservationPool" + network_pool_name = "NSX-T Overlay 1" + provider_vdc_name = "nsxTPvdc1" + + compute_capacity { + cpu { + allocated = "1024" + limit = "1024" + } + + memory { + allocated = "1024" + limit = "1024" + } + } + + storage_profile { + name = "*" + enabled = true + limit = 10240 + default = true + } + + enabled = true + enable_thin_provisioning = true + enable_fast_provisioning = true + delete_force = true + delete_recursive = true +} +``` + ## Example Usage (With VM sizing policies) ```hcl diff --git a/website/vcd.erb b/website/vcd.erb index 3bff90a69..dafa15783 100644 --- a/website/vcd.erb +++ b/website/vcd.erb @@ -43,6 +43,9 @@ > vcd_external_network + > + vcd_external_network_v2 + > vcd_edgegateway @@ -106,6 +109,18 @@ > vcd_nsxv_dhcp_relay + > + vcd_vcenter + + > + vcd_portgroup + + > + vcd_nsxt_manager + + > + vcd_nsxt_tier0_router + @@ -136,6 +151,9 @@ > vcd_external_network + > + vcd_external_network_v2 + > vcd_network_routed