From 64c082765df0c8a05020e75ed48d87d4206739d9 Mon Sep 17 00:00:00 2001 From: adezxc Date: Tue, 21 Mar 2023 08:53:25 +0100 Subject: [PATCH] Fix ipv6 vApp network creation (#1007) Add prefix length support while creating vapp networks. --------- Signed-off-by: Adam Jasinski --- .changes/v3.9.0/1007-bug-fixes.md | 2 + .changes/v3.9.0/1007-deprecations.md | 1 + vcd/config_test.go | 6 +- vcd/datasource_vcd_vapp_network.go | 6 + vcd/resource_vcd_vapp_network.go | 24 +- vcd/resource_vcd_vapp_network_test.go | 297 ++++++++++++++++++++-- vcd/resource_vcd_vapp_org_network_test.go | 8 +- vcd/resource_vcd_vapp_vm_test.go | 2 +- website/docs/r/vapp_network.html.markdown | 49 +++- 9 files changed, 358 insertions(+), 37 deletions(-) create mode 100644 .changes/v3.9.0/1007-bug-fixes.md create mode 100644 .changes/v3.9.0/1007-deprecations.md diff --git a/.changes/v3.9.0/1007-bug-fixes.md b/.changes/v3.9.0/1007-bug-fixes.md new file mode 100644 index 000000000..5fd85ac63 --- /dev/null +++ b/.changes/v3.9.0/1007-bug-fixes.md @@ -0,0 +1,2 @@ +* Add `prefix_length` field to `vcd_vapp_network` as creating IPv6 vApp networks was not supported due to the lack of a suitable subnet representation (Issue #999) [GH-1007] +* Remove incorrect default value from `vcd_vapp_network` `netmask` field, as it prevents using IPV6 networks. Users of already defined resources need to add a `netmask = "255.255.255.0"` when using Ipv4 [GH-1007] diff --git a/.changes/v3.9.0/1007-deprecations.md b/.changes/v3.9.0/1007-deprecations.md new file mode 100644 index 000000000..427c23259 --- /dev/null +++ b/.changes/v3.9.0/1007-deprecations.md @@ -0,0 +1 @@ +* Deprecate `netmask` in `vcd_vapp_network` [GH-1007] \ No newline at end of file diff --git a/vcd/config_test.go b/vcd/config_test.go index 5c0bb40ae..9b997d0b5 100644 --- a/vcd/config_test.go +++ b/vcd/config_test.go @@ -982,14 +982,14 @@ func importStateIdOrgCatalogObject(objectName string) resource.ImportStateIdFunc } // Used by all entities that depend on Org + VDC + vApp (such as VM, vapp networks) -func importStateIdVappObject(vappName, objectName string) resource.ImportStateIdFunc { +func importStateIdVappObject(vappName, objectName, vdc string) resource.ImportStateIdFunc { return func(*terraform.State) (string, error) { - if testConfig.VCD.Org == "" || testConfig.VCD.Vdc == "" || vappName == "" || objectName == "" { + if testConfig.VCD.Org == "" || vappName == "" || objectName == "" { return "", fmt.Errorf("missing information to generate import path") } return testConfig.VCD.Org + ImportSeparator + - testConfig.VCD.Vdc + + vdc + ImportSeparator + vappName + ImportSeparator + diff --git a/vcd/datasource_vcd_vapp_network.go b/vcd/datasource_vcd_vapp_network.go index bc0f7b7bb..5ec99d884 100644 --- a/vcd/datasource_vcd_vapp_network.go +++ b/vcd/datasource_vcd_vapp_network.go @@ -2,6 +2,7 @@ package vcd import ( "context" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) @@ -41,6 +42,11 @@ func datasourceVcdVappNetwork() *schema.Resource { Computed: true, Description: "Netmask address for a subnet", }, + "prefix_length": { + Type: schema.TypeString, + Computed: true, + Description: "Subnet prefix length", + }, "gateway": { Type: schema.TypeString, Computed: true, diff --git a/vcd/resource_vcd_vapp_network.go b/vcd/resource_vcd_vapp_network.go index 695d82c61..1564812c6 100644 --- a/vcd/resource_vcd_vapp_network.go +++ b/vcd/resource_vcd_vapp_network.go @@ -58,11 +58,22 @@ func resourceVcdVappNetwork() *schema.Resource { Description: "Optional description for the network", }, "netmask": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Default: "255.255.255.0", - Description: "Netmask address for a subnet. Default is 255.255.255.0", + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + Deprecated: "Use prefix_length instead which supports both IPv4 and IPv6", + Description: "Netmask address for a subnet.", + ExactlyOneOf: []string{"prefix_length", "netmask"}, + }, + "prefix_length": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + Description: "Prefix length for a subnet", + ExactlyOneOf: []string{"netmask", "prefix_length"}, + ValidateFunc: IsIntAndAtLeast(0), }, "gateway": { Type: schema.TypeString, @@ -194,6 +205,7 @@ func resourceVappNetworkCreate(ctx context.Context, d *schema.ResourceData, meta Description: d.Get("description").(string), Gateway: d.Get("gateway").(string), NetMask: d.Get("netmask").(string), + SubnetPrefixLength: d.Get("prefix_length").(string), DNS1: d.Get("dns1").(string), DNS2: d.Get("dns2").(string), DNSSuffix: d.Get("dns_suffix").(string), @@ -318,6 +330,7 @@ func genericVappNetworkRead(d *schema.ResourceData, meta interface{}, origin str if config.IPScopes != nil { dSet(d, "gateway", config.IPScopes.IPScope[0].Gateway) dSet(d, "netmask", config.IPScopes.IPScope[0].Netmask) + dSet(d, "prefix_length", config.IPScopes.IPScope[0].SubnetPrefixLength) dSet(d, "dns1", config.IPScopes.IPScope[0].DNS1) dSet(d, "dns2", config.IPScopes.IPScope[0].DNS2) dSet(d, "dns_suffix", config.IPScopes.IPScope[0].DNSSuffix) @@ -401,6 +414,7 @@ func resourceVappNetworkUpdate(ctx context.Context, d *schema.ResourceData, meta Description: d.Get("description").(string), Gateway: d.Get("gateway").(string), NetMask: d.Get("netmask").(string), + SubnetPrefixLength: d.Get("prefix_length").(string), DNS1: d.Get("dns1").(string), DNS2: d.Get("dns2").(string), DNSSuffix: d.Get("dns_suffix").(string), diff --git a/vcd/resource_vcd_vapp_network_test.go b/vcd/resource_vcd_vapp_network_test.go index 6c11fdd0f..865b873a5 100644 --- a/vcd/resource_vcd_vapp_network_test.go +++ b/vcd/resource_vcd_vapp_network_test.go @@ -74,7 +74,51 @@ func TestAccVcdVappNetwork_Isolated(t *testing.T) { } testParamsNotEmpty(t, params) - runVappNetworkTest(t, params) + runVappNetworkTestNetmask(t, params) + postTestChecks(t) +} + +func TestAccVcdVappNetwork_Isolated_ipv6(t *testing.T) { + preTestChecks(t) + + var params = StringMap{ + "Org": testConfig.VCD.Org, + "Vdc": testConfig.Nsxt.Vdc, + "resourceName": t.Name(), + // we can't change network name as this results in ID (HREF) change + "vappNetworkName": t.Name(), + "description": "network description", + "descriptionForUpdate": "update", + "gateway": "fe80:0:0:0:0:0:0:aaaa", + "prefix_length": "100", + "dns1": "ab:ab:ab:ab:ab:ab:ab:ab", + "dns1ForUpdate": "ab:ab:ab:ab:ab:ab:ab:ac", + "dns2": "bb:bb:bb:bb:bb:bb:bb:bb", + "dns2ForUpdate": "bb:bb:bb:bb:bb:bb:bb:bc", + "dnsSuffix": dnsSuffix, + "dnsSuffixForUpdate": "updated", + "guestVlanAllowed": guestVlanAllowed, + "guestVlanAllowedForUpdate": "false", + "startAddress": "fe80:0:0:0:0:0:0:aa", + "startAddressForUpdate": "fe80:0:0:0:0:0:0:bb", + "endAddress": "fe80:0:0:0:0:0:0:ab", + "endAddressForUpdate": "fe80:0:0:0:0:0:0:bc", + "vappName": vappNameForNetworkTest, + "vappVmName": t.Name(), + "NetworkName": "TestAccVcdVAppNet", + // adding space to allow pass validation in testParamsNotEmpty which skips the test if param value is empty + // to avoid running test when test data is missing + "OrgNetworkKey": " ", + "equalsChar": " ", + "quotationChar": " ", + "orgNetwork": " ", + "orgNetworkForUpdate": " ", + "retainIpMacEnabled": "false", + "retainIpMacEnabledForUpdate": "false", + } + testParamsNotEmpty(t, params) + + runVappNetworkTestPrefixLength(t, params) postTestChecks(t) } @@ -129,11 +173,12 @@ func TestAccVcdVappNetwork_Nat(t *testing.T) { } testParamsNotEmpty(t, params) - runVappNetworkTest(t, params) + runVappNetworkTestNetmask(t, params) postTestChecks(t) } -func runVappNetworkTest(t *testing.T, params StringMap) { +// TODO leave only one test, runVappNetworkTestPrefixLength after Netmask is fully deprecated +func runVappNetworkTestNetmask(t *testing.T, params StringMap) { configText := templateFill(testAccCheckVappNetwork_basic, params) debugPrintf("#[DEBUG] CONFIGURATION: %s", configText) params["FuncName"] = t.Name() + "-Update" @@ -148,12 +193,12 @@ func runVappNetworkTest(t *testing.T, params StringMap) { resourceName := "vcd_vapp_network." + params["resourceName"].(string) resource.Test(t, resource.TestCase{ ProviderFactories: testAccProviders, - CheckDestroy: testAccCheckVappNetworkDestroy, + CheckDestroy: testAccCheckVappNetworkDestroyNsxv, Steps: []resource.TestStep{ { Config: configText, Check: resource.ComposeTestCheckFunc( - testAccCheckVappNetworkExists(resourceName), + testAccCheckVappNetworkExists(resourceName, testConfig.VCD.Vdc), resource.TestCheckResourceAttr( resourceName, "name", params["vappNetworkName"].(string)), resource.TestCheckResourceAttr( @@ -190,7 +235,7 @@ func runVappNetworkTest(t *testing.T, params StringMap) { { Config: updateConfigText, Check: resource.ComposeTestCheckFunc( - testAccCheckVappNetworkExists(resourceName), + testAccCheckVappNetworkExists(resourceName, testConfig.VCD.Vdc), resource.TestCheckResourceAttr( resourceName, "name", params["vappNetworkName"].(string)), resource.TestCheckResourceAttr( @@ -228,7 +273,7 @@ func runVappNetworkTest(t *testing.T, params StringMap) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateIdFunc: importStateIdVappObject(params["vappName"].(string), params["vappNetworkName"].(string)), + ImportStateIdFunc: importStateIdVappObject(params["vappName"].(string), params["vappNetworkName"].(string), testConfig.VCD.Vdc), // These fields can't be retrieved from user data. ImportStateVerifyIgnore: []string{"org", "vdc", "reboot_vapp_on_removal"}, }, @@ -236,7 +281,96 @@ func runVappNetworkTest(t *testing.T, params StringMap) { }) } -func testAccCheckVappNetworkExists(n string) resource.TestCheckFunc { +func runVappNetworkTestPrefixLength(t *testing.T, params StringMap) { + configText := templateFill(testAccCheckVappNetwork_basic_ipv6, params) + debugPrintf("#[DEBUG] CONFIGURATION: %s", configText) + params["FuncName"] = t.Name() + "-Update" + updateConfigText := templateFill(testAccCheckVappNetwork_update_ipv6, params) + debugPrintf("#[DEBUG] CONFIGURATION: %s", updateConfigText) + + if vcdShortTest { + t.Skip(acceptanceTestsSkipped) + return + } + + resourceName := "vcd_vapp_network." + params["resourceName"].(string) + resource.Test(t, resource.TestCase{ + ProviderFactories: testAccProviders, + CheckDestroy: testAccCheckVappNetworkDestroyNsxt, + Steps: []resource.TestStep{ + { + Config: configText, + Check: resource.ComposeTestCheckFunc( + testAccCheckVappNetworkExists(resourceName, testConfig.Nsxt.Vdc), + resource.TestCheckResourceAttr( + resourceName, "name", params["vappNetworkName"].(string)), + resource.TestCheckResourceAttr( + resourceName, "description", params["description"].(string)), + resource.TestCheckResourceAttr( + resourceName, "gateway", params["gateway"].(string)), + resource.TestCheckResourceAttr( + resourceName, "prefix_length", params["prefix_length"].(string)), + resource.TestCheckResourceAttr( + resourceName, "dns1", params["dns1"].(string)), + resource.TestCheckResourceAttr( + resourceName, "dns2", params["dns2"].(string)), + resource.TestCheckResourceAttr( + resourceName, "dns_suffix", dnsSuffix), + resource.TestCheckResourceAttr( + resourceName, "guest_vlan_allowed", guestVlanAllowed), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "static_ip_pool.*", map[string]string{ + "start_address": params["startAddress"].(string), + "end_address": params["endAddress"].(string), + }), + resource.TestCheckResourceAttr( + resourceName, "org_network_name", strings.TrimSpace(params["orgNetwork"].(string))), + resource.TestCheckResourceAttr( + resourceName, "retain_ip_mac_enabled", params["retainIpMacEnabled"].(string)), + ), + }, + { + Config: updateConfigText, + Check: resource.ComposeTestCheckFunc( + testAccCheckVappNetworkExists(resourceName, testConfig.Nsxt.Vdc), + resource.TestCheckResourceAttr( + resourceName, "name", params["vappNetworkName"].(string)), + resource.TestCheckResourceAttr( + resourceName, "description", params["descriptionForUpdate"].(string)), + resource.TestCheckResourceAttr( + resourceName, "gateway", params["gateway"].(string)), + resource.TestCheckResourceAttr( + resourceName, "prefix_length", params["prefix_length"].(string)), + resource.TestCheckResourceAttr( + resourceName, "dns1", params["dns1ForUpdate"].(string)), + resource.TestCheckResourceAttr( + resourceName, "dns2", params["dns2ForUpdate"].(string)), + resource.TestCheckResourceAttr( + resourceName, "dns_suffix", params["dnsSuffixForUpdate"].(string)), + resource.TestCheckResourceAttr( + resourceName, "guest_vlan_allowed", params["guestVlanAllowedForUpdate"].(string)), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "static_ip_pool.*", map[string]string{ + "start_address": params["startAddressForUpdate"].(string), + "end_address": params["endAddressForUpdate"].(string), + }), + resource.TestCheckResourceAttr( + resourceName, "org_network_name", strings.TrimSpace(params["orgNetworkForUpdate"].(string))), + resource.TestCheckResourceAttr( + resourceName, "retain_ip_mac_enabled", params["retainIpMacEnabledForUpdate"].(string)), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: importStateIdVappObject(params["vappName"].(string), params["vappNetworkName"].(string), testConfig.Nsxt.Vdc), + // These fields can't be retrieved from user data. + ImportStateVerifyIgnore: []string{"org", "vdc", "reboot_vapp_on_removal"}, + }, + }, + }) +} + +func testAccCheckVappNetworkExists(n, vdc string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -247,9 +381,7 @@ func testAccCheckVappNetworkExists(n string) resource.TestCheckFunc { return fmt.Errorf("no vapp network ID is set") } - conn := testAccProvider.Meta().(*VCDClient) - - found, err := isVappNetworkFound(conn, rs, "exist") + found, err := doesVappNetworkExist(rs, vdc) if err != nil { return err } @@ -264,14 +396,28 @@ func testAccCheckVappNetworkExists(n string) resource.TestCheckFunc { // TODO: In future this can be improved to check if network delete only, // when test suite will create vApp which can be reused -func testAccCheckVappNetworkDestroy(s *terraform.State) error { - conn := testAccProvider.Meta().(*VCDClient) +func testAccCheckVappNetworkDestroyNsxv(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "vcd_vapp" { + continue + } + + _, err := doesVappNetworkExist(rs, testConfig.VCD.Vdc) + if err == nil { + return fmt.Errorf("vapp %s still exists", vappNameForNetworkTest) + } + } + + return nil +} + +func testAccCheckVappNetworkDestroyNsxt(s *terraform.State) error { for _, rs := range s.RootModule().Resources { if rs.Type != "vcd_vapp" { continue } - _, err := isVappNetworkFound(conn, rs, "destroy") + _, err := doesVappNetworkExist(rs, testConfig.Nsxt.Vdc) if err == nil { return fmt.Errorf("vapp %s still exists", vappNameForNetworkTest) } @@ -280,8 +426,9 @@ func testAccCheckVappNetworkDestroy(s *terraform.State) error { return nil } -func isVappNetworkFound(conn *VCDClient, rs *terraform.ResourceState, origin string) (bool, error) { - _, vdc, err := conn.GetOrgAndVdc(testConfig.VCD.Org, testConfig.VCD.Vdc) +func doesVappNetworkExist(rs *terraform.ResourceState, testVdc string) (bool, error) { + conn := testAccProvider.Meta().(*VCDClient) + _, vdc, err := conn.GetOrgAndVdc(testConfig.VCD.Org, testVdc) if err != nil { return false, fmt.Errorf(errorRetrievingOrgAndVdc, err) } @@ -291,10 +438,6 @@ func isVappNetworkFound(conn *VCDClient, rs *terraform.ResourceState, origin str return false, fmt.Errorf("error retrieving vApp: %s, %#v", rs.Primary.ID, err) } - // Avoid looking for network when the purpose is only finding whether the vApp exists - if origin == "destroy" { - return true, nil - } networkConfig, err := vapp.GetNetworkConfig() if err != nil { return false, fmt.Errorf("error retrieving network config from vApp: %#v", err) @@ -368,6 +511,62 @@ resource "vcd_vapp_network" "{{.resourceName}}" { } ` +const testAccCheckVappNetwork_basic_ipv6 = ` +resource "vcd_vapp" "{{.vappName}}" { + name = "{{.vappName}}" + org = "{{.Org}}" + vdc = "{{.Vdc}}" +} + +resource "vcd_vapp_vm" "{{.vappVmName}}" { + org = "{{.Org}}" + vdc = "{{.Vdc}}" + + vapp_name = "{{.vappName}}" + name = "{{.vappVmName}}" + computer_name = "emptyVM" + memory = 2048 + cpus = 2 + cpu_cores = 1 + + network { + type = "vapp" + name = vcd_vapp_network.{{.resourceName}}.name + ip_allocation_mode = "POOL" + } + + os_type = "sles10_64Guest" + hardware_version = "vmx-14" + + depends_on = [vcd_vapp.{{.vappName}}, vcd_vapp_network.{{.resourceName}}] +} + +resource "vcd_vapp_network" "{{.resourceName}}" { + org = "{{.Org}}" + vdc = "{{.Vdc}}" + name = "{{.vappNetworkName}}" + description = "{{.description}}" + vapp_name = "{{.vappName}}" + gateway = "{{.gateway}}" + prefix_length = "{{.prefix_length}}" + dns1 = "{{.dns1}}" + dns2 = "{{.dns2}}" + dns_suffix = "{{.dnsSuffix}}" + guest_vlan_allowed = {{.guestVlanAllowed}} + + static_ip_pool { + start_address = "{{.startAddress}}" + end_address = "{{.endAddress}}" + } + + {{.OrgNetworkKey}} {{.equalsChar}} {{.quotationChar}}{{.orgNetwork}}{{.quotationChar}} + + retain_ip_mac_enabled = "{{.retainIpMacEnabled}}" + + depends_on = ["vcd_vapp.{{.vappName}}"] +} +` + const testAccCheckVappNetwork_update = ` resource "vcd_vapp" "{{.vappName}}" { name = "{{.vappName}}" @@ -434,6 +633,62 @@ resource "vcd_vapp_network" "{{.resourceName}}" { } ` +const testAccCheckVappNetwork_update_ipv6 = ` +resource "vcd_vapp" "{{.vappName}}" { + name = "{{.vappName}}" + org = "{{.Org}}" + vdc = "{{.Vdc}}" +} + +resource "vcd_vapp_vm" "{{.vappVmName}}" { + org = "{{.Org}}" + vdc = "{{.Vdc}}" + + vapp_name = "{{.vappName}}" + name = "{{.vappVmName}}" + computer_name = "emptyVM" + memory = 2048 + cpus = 2 + cpu_cores = 1 + + network { + type = "vapp" + name = vcd_vapp_network.{{.resourceName}}.name + ip_allocation_mode = "POOL" + } + + os_type = "sles10_64Guest" + hardware_version = "vmx-14" + + depends_on = ["vcd_vapp.{{.vappName}}", "vcd_vapp_network.{{.resourceName}}"] +} + +resource "vcd_vapp_network" "{{.resourceName}}" { + org = "{{.Org}}" + vdc = "{{.Vdc}}" + name = "{{.vappNetworkName}}" + description = "{{.descriptionForUpdate}}" + vapp_name = "{{.vappName}}" + gateway = "{{.gateway}}" + prefix_length = "{{.prefix_length}}" + dns1 = "{{.dns1ForUpdate}}" + dns2 = "{{.dns2ForUpdate}}" + dns_suffix = "{{.dnsSuffixForUpdate}}" + guest_vlan_allowed = {{.guestVlanAllowedForUpdate}} + static_ip_pool { + start_address = "{{.startAddressForUpdate}}" + end_address = "{{.endAddressForUpdate}}" + } + + {{.OrgNetworkKey}} {{.equalsChar}} {{.quotationChar}}{{.orgNetworkForUpdate}}{{.quotationChar}} + + retain_ip_mac_enabled = "{{.retainIpMacEnabledForUpdate}}" + reboot_vapp_on_removal = true + + depends_on = ["vcd_vapp.{{.vappName}}"] +} +` + // TestAccVcdNsxtVappNetworks checks that NSX-T Org networks can be attached to vApp, given that // NSX-T Edge Cluster is specified in NSX-T VDC func TestAccVcdNsxtVappNetworks(t *testing.T) { @@ -907,7 +1162,7 @@ func TestAccVcdNsxtVappNetworkRemovalFails(t *testing.T) { resource.Test(t, resource.TestCase{ ProviderFactories: testAccProviders, CheckDestroy: resource.ComposeAggregateTestCheckFunc( - testAccCheckVappNetworkDestroy, + testAccCheckVappNetworkDestroyNsxv, ), Steps: []resource.TestStep{ { // Create setup diff --git a/vcd/resource_vcd_vapp_org_network_test.go b/vcd/resource_vcd_vapp_org_network_test.go index 387e89cb8..7df3e085b 100644 --- a/vcd/resource_vcd_vapp_org_network_test.go +++ b/vcd/resource_vcd_vapp_org_network_test.go @@ -71,12 +71,12 @@ func runVappOrgNetworkTest(t *testing.T, params StringMap) { resourceName := "vcd_vapp_org_network." + params["resourceName"].(string) resource.Test(t, resource.TestCase{ ProviderFactories: testAccProviders, - CheckDestroy: testAccCheckVappNetworkDestroy, + CheckDestroy: testAccCheckVappNetworkDestroyNsxv, Steps: []resource.TestStep{ { Config: configText, Check: resource.ComposeTestCheckFunc( - testAccCheckVappNetworkExists(resourceName), + testAccCheckVappNetworkExists(resourceName, testConfig.VCD.Vdc), resource.TestCheckResourceAttr( resourceName, "vapp_name", params["vappName"].(string)), resource.TestCheckResourceAttr( @@ -90,7 +90,7 @@ func runVappOrgNetworkTest(t *testing.T, params StringMap) { { Config: updateConfigText, Check: resource.ComposeTestCheckFunc( - testAccCheckVappNetworkExists(resourceName), + testAccCheckVappNetworkExists(resourceName, testConfig.VCD.Vdc), resource.TestCheckResourceAttr( resourceName, "vapp_name", params["vappName"].(string)), resource.TestCheckResourceAttr( @@ -105,7 +105,7 @@ func runVappOrgNetworkTest(t *testing.T, params StringMap) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateIdFunc: importStateIdVappObject(params["vappName"].(string), params["orgNetwork"].(string)), + ImportStateIdFunc: importStateIdVappObject(params["vappName"].(string), params["orgNetwork"].(string), testConfig.VCD.Vdc), // These fields can't be retrieved from user data. ImportStateVerifyIgnore: []string{"org", "vdc", "reboot_vapp_on_removal"}, }, diff --git a/vcd/resource_vcd_vapp_vm_test.go b/vcd/resource_vcd_vapp_vm_test.go index 4ca77e8fc..83512af6b 100644 --- a/vcd/resource_vcd_vapp_vm_test.go +++ b/vcd/resource_vcd_vapp_vm_test.go @@ -81,7 +81,7 @@ func TestAccVcdVAppVm_Basic(t *testing.T) { ResourceName: "vcd_vapp_vm." + vmName, ImportState: true, ImportStateVerify: true, - ImportStateIdFunc: importStateIdVappObject(vappName2, vmName), + ImportStateIdFunc: importStateIdVappObject(vappName2, vmName, testConfig.VCD.Vdc), // These fields can't be retrieved from user data ImportStateVerifyIgnore: []string{"template_name", "catalog_name", "accept_all_eulas", "power_on", "computer_name", "prevent_update_power_off"}, diff --git a/website/docs/r/vapp_network.html.markdown b/website/docs/r/vapp_network.html.markdown index 5dcafd8d7..fff46be85 100644 --- a/website/docs/r/vapp_network.html.markdown +++ b/website/docs/r/vapp_network.html.markdown @@ -12,7 +12,7 @@ description: |- Supported in provider *v2.1+* -## Example Usage +## Example Usage (IPv4) ```hcl resource "vcd_vapp_network" "vappNet" { @@ -22,7 +22,7 @@ resource "vcd_vapp_network" "vappNet" { name = "my-net" vapp_name = "my-vapp" gateway = "192.168.2.1" - netmask = "255.255.255.0" + prefix_length = "24" dns1 = "192.168.2.1" dns2 = "192.168.2.2" dns_suffix = "mybiz.biz" @@ -47,6 +47,41 @@ resource "vcd_vapp_network" "vappNet" { } ``` +## Example Usage (IPv6) + +```hcl +resource "vcd_vapp_network" "vappNet_ipv6" { + org = "my-org" # Optional + vdc = "my-vdc" # Optional + + name = "my-net-ipv6" + vapp_name = "my-vapp" + gateway = "fe80:0:0:0:0:0:0:aaaa" + prefix_length = "24" + dns1 = "2001:4860:4860:0:0:0:0:8888" + dns2 = "2001:4860:4860:0:0:0:0:8844" + dns_suffix = "mybiz.biz" + guest_vlan_allowed = true + + # VCD 10.4.1+ API does not allow to remove vApp network from + # a powered on vApp. Setting reboot_vapp_on_removal to true + # will allow to power off parent vApp for network removal. + # Note. It will power on the vApp if it was not powered off + # before the operation. + # reboot_vapp_on_removal = true + + static_ip_pool { + start_address = "fe80:0:0:0:0:0:0:aacc" + end_address = "fe80:0:0:0:0:0:0:aadd" + } + + dhcp_pool { + start_address = "fe80:0:0:0:0:0:0:aaaa" + end_address = "fe80:0:0:0:0:0:0:aabb" + } +} +``` + ## Argument Reference The following arguments are supported: @@ -57,8 +92,16 @@ The following arguments are supported: * `name` - (Required) A unique name for the network. * `description` - (Optional; *v2.7+*, *vCD 9.5+*) Description of vApp network * `vapp_name` - (Required) The vApp this network belongs to. -* `netmask` - (Optional) The netmask for the new network. Default is `255.255.255.0`. +* `netmask` - (Deprecated) Use `prefix_length` instead. The netmask for the new network. + +~> **Warning:** In `v3.9.0`, field `netmask` no longer has a `default` value of `255.255.255.0` so that IPv6 can be supported using the new `prefix_length` field. +This change makes `terraform validate|plan` fail if the user didn't provide a value earlier and relied on default `255.255.255.0`. +In case that happens, a user needs to add `"netmask" = "255.255.255.0"` to existing vApp networks. +* `prefix_length` - (Optional) The subnet prefix length for the network. * `gateway` - (Required) The gateway for this network. + +~> **Note:** VCD returns IPv6 addresses in extended-shortened format e.g `fe80:0:a:ab:0:abc:abcd:aaaa`, it is up to the user +to match it, otherwise Terraform will return an inconsistent plan. * `dns1` - (Optional) First DNS server to use. * `dns2` - (Optional) Second DNS server to use. * `dns_suffix` - (Optional) A FQDN for the virtual machines on this network.