diff --git a/docs/resources/vpn_gateway_v5.md b/docs/resources/vpn_gateway_v5.md new file mode 100644 index 000000000..8de7b7680 --- /dev/null +++ b/docs/resources/vpn_gateway_v5.md @@ -0,0 +1,252 @@ +--- +subcategory: "Virtual Private Network (VPN)" +layout: "opentelekomcloud" +page_title: "OpenTelekomCloud: opentelekomcloud_enterprise_vpn_gateway_v5" +sidebar_current: "docs-opentelekomcloud-resource-enterprise-vpn-gateway-v5" +description: |- +Manages a Enterprise VPN Gateway Service resource within OpenTelekomCloud. +--- + +# opentelekomcloud_enterprise_vpn_gateway_v5 + +Manages a VPN gateway resource within OpenTelekomCloud. + +## Example Usage + +### Basic Usage + +```hcl +variable "name" {} + +resource "opentelekomcloud_enterprise_vpn_gateway_v5" "gw_1" { + name = var.name + vpc_id = opentelekomcloud_vpc_v1.vpc.id + local_subnets = [opentelekomcloud_vpc_subnet_v1.subnet.cidr] + connect_subnet = opentelekomcloud_vpc_subnet_v1.subnet.id + + availability_zones = [ + "eu-de-01", + "eu-de-02" + ] + + eip1 { + id = opentelekomcloud_vpc_eip_v1.eip_1.id + } + + eip2 { + id = opentelekomcloud_vpc_eip_v1.eip_2.id + } + + tags = { + key = "val" + foo = "bar" + } +} +``` + +### Creating a VPN gateway with creating new EIPs + +```hcl +variable "name" {} + +resource "opentelekomcloud_enterprise_vpn_gateway_v5" "gw_1" { + name = var.name + ha_mode = "active-standby" + vpc_id = opentelekomcloud_vpc_v1.vpc.id + local_subnets = [opentelekomcloud_vpc_subnet_v1.subnet.cidr] + connect_subnet = opentelekomcloud_vpc_subnet_v1.subnet.id + + availability_zones = [ + "eu-de-01", + "eu-de-02" + ] + + eip1 { + bandwidth_name = "evpn-gw-bw-1" + type = "5_bgp" + bandwidth_size = 5 + charge_mode = "traffic" + } + + eip2 { + bandwidth_name = "evpn-gw-bw-2" + type = "5_bgp" + bandwidth_size = 5 + charge_mode = "traffic" + } +} + +``` + +### Creating a private VPN gateway with Enterprise Router + +```hcl +variable "name" {} +variable "er_id" {} + +resource "opentelekomcloud_enterprise_vpn_gateway_v5" "gw_1" { + name = var.name + network_type = "private" + attachment_type = "er" + er_id = var.er_id + + availability_zones = [ + "eu-de-01", + "eu-de-02" + ] + + access_vpc_id = opentelekomcloud_vpc_v1.vpc_er.id + access_subnet_id = opentelekomcloud_vpc_subnet_v1.subnet_er.id + + access_private_ip_1 = "172.16.0.99" + access_private_ip_2 = "172.16.0.100" +} +``` + +## Argument Reference + +The following arguments are supported: +* `name` - (Required, String) The name of the VPN gateway. + The valid length is limited from `1` to `64`, only letters, digits, hyphens (-) and underscores (_) are allowed. + +* `availability_zones` - (Required, List, ForceNew) The list of availability zone IDs. + Changing this parameter will create a new resource. + +* `flavor` - (Optional, String, ForceNew) The flavor of the VPN gateway. + The value can be `Basic`, `Professional1`, `Professional2`. Defaults to `Professional1`. + Changing this parameter will create a new resource. + +* `attachment_type` - (Optional, String, ForceNew) The attachment type. The value can be `vpc` and `er`. + Defaults to `vpc`. + Changing this parameter will create a new resource. + +* `network_type` - (Optional, String, ForceNew) The network type. The value can be `public` and `private`. + Defaults to `public`. + Changing this parameter will create a new resource. + +* `vpc_id` - (Optional, String, ForceNew) The ID of the VPC to which the VPN gateway is connected. + This parameter is mandatory when `attachment_type` is `vpc`. + Changing this parameter will create a new resource. + +* `local_subnets` - (Optional, List) The list of local subnets. + This parameter is mandatory when `attachment_type` is `vpc`. + +* `connect_subnet` - (Optional, String, ForceNew) The Network ID of the VPC subnet used by the VPN gateway. + This parameter is mandatory when `attachment_type` is `vpc`. + Changing this parameter will create a new resource. + +* `er_id` - (Optional, String, ForceNew) The enterprise router ID to attach with to VPN gateway. + This parameter is mandatory when `attachment_type` is `er`. + Changing this parameter will create a new resource. + +* `ha_mode` - (Optional, String, ForceNew) The HA mode of VPN gateway. Valid values are `active-active` and + `active-standby`. The default value is `active-active`. + Changing this parameter will create a new resource. + +* `eip1` - (Optional, List) The master 1 IP in active-active VPN gateway or the master IP + in active-standby VPN gateway. This parameter is mandatory when `network_type` is `public` or left empty. + The [object](#GwCreateRequestEip) structure is documented below. + +* `eip2` - (Optional, List, ForceNew) The master 2 IP in active-active VPN gateway or the slave IP + in active-standby VPN gateway. This parameter is mandatory when `network_type` is **public** or left empty. + The [object](#GwCreateRequestEip) structure is documented below. + +* `access_vpc_id` - (Optional, String, ForceNew) The access VPC ID. + The default value is the value of `vpc_id`. + Changing this parameter will create a new resource. + +* `access_subnet_id` - (Optional, String, ForceNew) The access subnet ID. + The default value is the value of `connect_subnet`. + Changing this parameter will create a new resource. + +* `access_private_ip_1` - (Optional, String, ForceNew) The private IP 1 in private network type VPN gateway. + It is the master IP 1 in `active-active` HA mode, and the master IP in `active-standby` HA mode. + Must declare the `access_private_ip_2` at the same time, and can not use the same IP value. + Changing this parameter will create a new resource. + +* `access_private_ip_2` - (Optional, String, ForceNew) The private IP 2 in private network type VPN gateway. + It is the master IP 2 in `active-active` HA mode, and the slave IP in `active-standby` HA mode. + Must declare the `access_private_ip_1` at the same time, and can not use the same IP value. + Changing this parameter will create a new resource. + +* `asn` - (Optional, Int, ForceNew) The ASN number of BGP. The value ranges from `1` to `4,294,967,295`. + Defaults to `64,512`. + Changing this parameter will create a new resource. + + +The `eip1` or `eip2` block supports: + +* `id` - (Optional, String, ForceNew) The public IP ID. + Changing this parameter will create a new resource. + +* `type` - (Optional, String, ForceNew) The EIP type. + Changing this parameter will create a new resource. + +* `bandwidth_name` - (Optional, String, ForceNew) The bandwidth name. + The valid length is limited from `1` to `64`, only letters, digits, hyphens (-) and underscores (_) are allowed. + Changing this parameter will create a new resource. + +* `bandwidth_size` - (Optional, Int, ForceNew) Bandwidth size in Mbit/s. When the `flavor` is `Basic`, the value + cannot be greater than `100`. When the `flavor` is `Professional1`, the value cannot be greater than `300`. + When the `flavor` is `Professional2`, the value cannot be greater than `1,000`. + Changing this parameter will create a new resource. + +* `charge_mode` - (Optional, String, ForceNew) The charge mode of the bandwidth. The value can be `bandwidth` and `traffic`. + Changing this parameter will create a new resource. + + ~> You can use `id` to specify an existing EIP or use `type`, `bandwidth_name`, `bandwidth_size` and `charge_mode` to + create a new EIP. + +* `tags` - (Optional, Map) Specifies the tags of the VPN gateway. + + +## Attribute Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The ID of the VPN gateway + +* `status` - The status of VPN gateway. + +* `created_at` - The create time. + +* `updated_at` - The update time. + +* `used_connection_group` - The number of used connection groups. + +* `used_connection_number` - The number of used connections. + +* `er_attachment_id` - The ER attachment ID. + +* `region` - Specifies the region in which to create the resource. + +* `eip1` - The master 1 IP in active-active VPN gateway or the master IP in active-standby VPN gateway. + The [object](#GatewayGetResponseEip) structure is documented below. + +* `eip2` - The master 2 IP in active-active VPN gateway or the slave IP in active-standby VPN gateway. + The [object](#GatewayGetResponseEip) structure is documented below. + + +The `eip1` or `eip2` block supports: + +* `bandwidth_id` - The bandwidth ID. + +* `ip_address` - The public IP address. + +* `ip_version` - Specifies the EIP version. + +## Timeouts + +This resource provides the following timeouts configuration options: + +* `create` - Default is 10 minutes. +* `update` - Default is 10 minutes. +* `delete` - Default is 10 minutes. + +## Import + +The gateway can be imported using the `id`, e.g. + +```bash +$ terraform import opentelekomcloud_enterprise_vpn_gateway_v5.test +``` diff --git a/go.mod b/go.mod index aba0714da..e6f9c59d2 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/jmespath/go-jmespath v0.4.0 github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4 github.com/mitchellh/go-homedir v1.1.0 - github.com/opentelekomcloud/gophertelekomcloud v0.9.4-0.20241001094048-fbd948f2ab7e + github.com/opentelekomcloud/gophertelekomcloud v0.9.4-0.20241004091110-c63bcb5025ba github.com/unknwon/com v1.0.1 golang.org/x/crypto v0.21.0 golang.org/x/sync v0.1.0 diff --git a/go.sum b/go.sum index cec509e5b..ed28558d2 100644 --- a/go.sum +++ b/go.sum @@ -156,8 +156,8 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce h1:RPclfga2SEJmgMmz2k+Mg7cowZ8yv4Trqw9UsJby758= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= -github.com/opentelekomcloud/gophertelekomcloud v0.9.4-0.20241001094048-fbd948f2ab7e h1:JT2bfxV3X4Pb+1H3rVts1olmWCqtiXb3PI+9z334WBU= -github.com/opentelekomcloud/gophertelekomcloud v0.9.4-0.20241001094048-fbd948f2ab7e/go.mod h1:M1F6OfSRZRzAmAFKQqSLClX952at5hx5rHe4UTEykgg= +github.com/opentelekomcloud/gophertelekomcloud v0.9.4-0.20241004091110-c63bcb5025ba h1:zq7iB7GwrjXqRdh/2WDXcaG82e34iq1X5j0giiFqGNw= +github.com/opentelekomcloud/gophertelekomcloud v0.9.4-0.20241004091110-c63bcb5025ba/go.mod h1:M1F6OfSRZRzAmAFKQqSLClX952at5hx5rHe4UTEykgg= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= diff --git a/opentelekomcloud/acceptance/vpn/resource_opentelekomcloud_enterprise_vpn_gateway_v5_test.go b/opentelekomcloud/acceptance/vpn/resource_opentelekomcloud_enterprise_vpn_gateway_v5_test.go new file mode 100644 index 000000000..c9c372785 --- /dev/null +++ b/opentelekomcloud/acceptance/vpn/resource_opentelekomcloud_enterprise_vpn_gateway_v5_test.go @@ -0,0 +1,337 @@ +package acceptance + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/opentelekomcloud/gophertelekomcloud/openstack/evpn/v5/gateway" + "github.com/opentelekomcloud/terraform-provider-opentelekomcloud/opentelekomcloud/acceptance/common" + "github.com/opentelekomcloud/terraform-provider-opentelekomcloud/opentelekomcloud/acceptance/env" + "github.com/opentelekomcloud/terraform-provider-opentelekomcloud/opentelekomcloud/common/cfg" +) + +const ( + resourceEvpnGatewayName = "opentelekomcloud_enterprise_vpn_gateway_v5.gw_1" + resourceEvpnGatewayEip1Name = "opentelekomcloud_vpc_eip_v1.eip_1" + resourceEvpnGatewayEip2Name = "opentelekomcloud_vpc_eip_v1.eip_2" +) + +func getGatewayResourceFunc(conf *cfg.Config, state *terraform.ResourceState) (interface{}, error) { + client, err := conf.EvpnV5Client(env.OS_REGION_NAME) + if err != nil { + return nil, fmt.Errorf("error creating OpenTelekomCloud EVPN v5 client: %s", err) + } + return gateway.Get(client, state.Primary.ID) +} + +func TestAccGateway_basic(t *testing.T) { + var gw gateway.Gateway + name := fmt.Sprintf("evpn_acc_gw_%s", acctest.RandString(5)) + updateName := fmt.Sprintf("evpn_acc_gw_up_%s", acctest.RandString(5)) + + rc := common.InitResourceCheck( + resourceEvpnGatewayName, + &gw, + getGatewayResourceFunc, + ) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { common.TestAccPreCheck(t) }, + ProviderFactories: common.TestAccProviderFactories, + CheckDestroy: rc.CheckResourceDestroy(), + Steps: []resource.TestStep{ + { + Config: testEvpnGateway_basic(name), + Check: resource.ComposeTestCheckFunc( + rc.CheckResourceExists(), + resource.TestCheckResourceAttr(resourceEvpnGatewayName, "name", name), + resource.TestCheckResourceAttr(resourceEvpnGatewayName, "ha_mode", "active-active"), + resource.TestCheckResourceAttr(resourceEvpnGatewayName, "status", "ACTIVE"), + resource.TestCheckResourceAttrPair(resourceEvpnGatewayName, "eip1.0.id", resourceEvpnGatewayEip1Name, "id"), + resource.TestCheckResourceAttrPair(resourceEvpnGatewayName, "eip2.0.id", resourceEvpnGatewayEip2Name, "id"), + resource.TestCheckResourceAttr(resourceEvpnGatewayName, "tags.key", "val"), + resource.TestCheckResourceAttr(resourceEvpnGatewayName, "tags.foo", "bar"), + ), + }, + { + Config: testEvpnGateway_update(updateName), + Check: resource.ComposeTestCheckFunc( + rc.CheckResourceExists(), + resource.TestCheckResourceAttr(resourceEvpnGatewayName, "name", updateName), + resource.TestCheckResourceAttr(resourceEvpnGatewayName, "local_subnets.1", "192.168.2.0/24"), + resource.TestCheckResourceAttr(resourceEvpnGatewayName, "tags.key", "val"), + resource.TestCheckResourceAttr(resourceEvpnGatewayName, "tags.foo", "bar-update"), + ), + }, + { + ResourceName: resourceEvpnGatewayName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccGateway_activeStandbyHAMode(t *testing.T) { + var gw gateway.Gateway + name := fmt.Sprintf("evpn_acc_gw_%s", acctest.RandString(5)) + + rc := common.InitResourceCheck( + resourceEvpnGatewayName, + &gw, + getGatewayResourceFunc, + ) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { common.TestAccPreCheck(t) }, + ProviderFactories: common.TestAccProviderFactories, + CheckDestroy: rc.CheckResourceDestroy(), + Steps: []resource.TestStep{ + { + Config: testEvpnGateway_activeStandbyHAMode(name), + Check: resource.ComposeTestCheckFunc( + rc.CheckResourceExists(), + resource.TestCheckResourceAttr(resourceEvpnGatewayName, "name", name), + resource.TestCheckResourceAttr(resourceEvpnGatewayName, "ha_mode", "active-standby"), + resource.TestCheckResourceAttr(resourceEvpnGatewayName, "status", "ACTIVE"), + resource.TestCheckResourceAttr(resourceEvpnGatewayName, "eip1.0.bandwidth_name", "evpn-gw-bw-1"), + resource.TestCheckResourceAttr(resourceEvpnGatewayName, "eip2.0.bandwidth_name", "evpn-gw-bw-2"), + ), + }, + { + ResourceName: resourceEvpnGatewayName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccGateway_withER(t *testing.T) { + var gw gateway.Gateway + name := fmt.Sprintf("evpn_acc_gw_%s", acctest.RandString(5)) + + rc := common.InitResourceCheck( + resourceEvpnGatewayName, + &gw, + getGatewayResourceFunc, + ) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { common.TestAccPreCheck(t) }, + ProviderFactories: common.TestAccProviderFactories, + CheckDestroy: rc.CheckResourceDestroy(), + Steps: []resource.TestStep{ + { + Config: testEvpnGateway_withER(name), + Check: resource.ComposeTestCheckFunc( + rc.CheckResourceExists(), + resource.TestCheckResourceAttr(resourceEvpnGatewayName, "name", name), + resource.TestCheckResourceAttr(resourceEvpnGatewayName, "network_type", "private"), + resource.TestCheckResourceAttr(resourceEvpnGatewayName, "attachment_type", "er"), + resource.TestCheckResourceAttr(resourceEvpnGatewayName, "status", "ACTIVE"), + resource.TestCheckResourceAttr(resourceEvpnGatewayName, "access_private_ip_1", "172.16.0.99"), + resource.TestCheckResourceAttr(resourceEvpnGatewayName, "access_private_ip_2", "172.16.0.100"), + ), + }, + { + ResourceName: resourceEvpnGatewayName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testEvpnGateway_base(name string) string { + return fmt.Sprintf(` +resource "opentelekomcloud_vpc_v1" "vpc" { + name = "%[1]s" + cidr = "192.168.0.0/16" +} + +resource "opentelekomcloud_vpc_subnet_v1" "subnet" { + name = "%[1]s" + vpc_id = opentelekomcloud_vpc_v1.vpc.id + cidr = "192.168.0.0/24" + gateway_ip = "192.168.0.1" +} + +resource "opentelekomcloud_vpc_eip_v1" "eip_1" { + publicip { + type = "5_bgp" + } + bandwidth { + name = "%[1]s-1" + size = 8 + share_type = "PER" + charge_mode = "traffic" + } +} + +resource "opentelekomcloud_vpc_eip_v1" "eip_2" { + publicip { + type = "5_bgp" + } + bandwidth { + name = "%[1]s-2" + size = 9 + share_type = "PER" + charge_mode = "traffic" + } +} +`, name) +} + +func testEvpnGateway_basic(name string) string { + return fmt.Sprintf(` +%s + +resource "opentelekomcloud_enterprise_vpn_gateway_v5" "gw_1" { + name = "%s" + vpc_id = opentelekomcloud_vpc_v1.vpc.id + local_subnets = [opentelekomcloud_vpc_subnet_v1.subnet.cidr] + connect_subnet = opentelekomcloud_vpc_subnet_v1.subnet.id + + availability_zones = [ + "eu-de-01", + "eu-de-02" + ] + + eip1 { + id = opentelekomcloud_vpc_eip_v1.eip_1.id + } + + eip2 { + id = opentelekomcloud_vpc_eip_v1.eip_2.id + } + + tags = { + key = "val" + foo = "bar" + } +} +`, testEvpnGateway_base(name), name) +} + +func testEvpnGateway_update(name string) string { + return fmt.Sprintf(` +%s + +resource "opentelekomcloud_enterprise_vpn_gateway_v5" "gw_1" { + name = "%s" + vpc_id = opentelekomcloud_vpc_v1.vpc.id + local_subnets = [ + opentelekomcloud_vpc_subnet_v1.subnet.cidr, + "192.168.2.0/24" + ] + connect_subnet = opentelekomcloud_vpc_subnet_v1.subnet.id + + availability_zones = [ + "eu-de-01", + "eu-de-02" + ] + + eip1 { + id = opentelekomcloud_vpc_eip_v1.eip_1.id + } + + eip2 { + id = opentelekomcloud_vpc_eip_v1.eip_2.id + } + + tags = { + key = "val" + foo = "bar-update" + } +} +`, testEvpnGateway_base(name), name) +} + +func testEvpnGateway_activeStandbyHAMode(name string) string { + return fmt.Sprintf(` +resource "opentelekomcloud_vpc_v1" "vpc" { + name = "%[1]s" + cidr = "192.168.0.0/16" +} + +resource "opentelekomcloud_vpc_subnet_v1" "subnet" { + name = "%[1]s" + vpc_id = opentelekomcloud_vpc_v1.vpc.id + cidr = "192.168.0.0/24" + gateway_ip = "192.168.0.1" +} + +resource "opentelekomcloud_enterprise_vpn_gateway_v5" "gw_1" { + name = "%[1]s" + ha_mode = "active-standby" + vpc_id = opentelekomcloud_vpc_v1.vpc.id + local_subnets = [opentelekomcloud_vpc_subnet_v1.subnet.cidr] + connect_subnet = opentelekomcloud_vpc_subnet_v1.subnet.id + + availability_zones = [ + "eu-de-01", + "eu-de-02" + ] + + eip1 { + bandwidth_name = "evpn-gw-bw-1" + type = "5_bgp" + bandwidth_size = 5 + charge_mode = "traffic" + } + + eip2 { + bandwidth_name = "evpn-gw-bw-2" + type = "5_bgp" + bandwidth_size = 5 + charge_mode = "traffic" + } +} +`, name) +} + +func testEvpnGateway_withER(name string) string { + return fmt.Sprintf(` +resource "opentelekomcloud_vpc_v1" "vpc_er" { + name = "%[1]s" + cidr = "172.16.0.0/16" +} + +resource "opentelekomcloud_vpc_subnet_v1" "subnet_er" { + name = "%[1]s" + vpc_id = opentelekomcloud_vpc_v1.vpc_er.id + cidr = "172.16.0.0/24" + gateway_ip = "172.16.0.1" +} + +resource "opentelekomcloud_er_instance_v3" "er_1" { + availability_zones = ["eu-de-01", "eu-de-02"] + + name = "%[1]s" + asn = "65000" + description = "evpn test" +} + +resource "opentelekomcloud_enterprise_vpn_gateway_v5" "gw_1" { + name = "%[1]s" + network_type = "private" + attachment_type = "er" + er_id = opentelekomcloud_er_instance_v3.er_1.id + + availability_zones = [ + "eu-de-01", + "eu-de-02" + ] + + access_vpc_id = opentelekomcloud_vpc_v1.vpc_er.id + access_subnet_id = opentelekomcloud_vpc_subnet_v1.subnet_er.id + + access_private_ip_1 = "172.16.0.99" + access_private_ip_2 = "172.16.0.100" +} +`, name) +} diff --git a/opentelekomcloud/common/cfg/config.go b/opentelekomcloud/common/cfg/config.go index f87bb8094..eefe69512 100644 --- a/opentelekomcloud/common/cfg/config.go +++ b/opentelekomcloud/common/cfg/config.go @@ -1175,6 +1175,13 @@ func (c *Config) TmsV1Client() (*golangsdk.ServiceClient, error) { return service, nil } +func (c *Config) EvpnV5Client(region string) (*golangsdk.ServiceClient, error) { + return openstack.NewEVPNServiceV3(c.HwClient, golangsdk.EndpointOpts{ + Region: region, + Availability: c.getEndpointType(), + }) +} + func reconfigProjectName(src Config, projectName ProjectName) (*Config, error) { config := &Config{} if err := copier.Copy(config, &src); err != nil { diff --git a/opentelekomcloud/provider.go b/opentelekomcloud/provider.go index d478da3c9..6c0a30342 100644 --- a/opentelekomcloud/provider.go +++ b/opentelekomcloud/provider.go @@ -560,6 +560,7 @@ func Provider() *schema.Provider { "opentelekomcloud_vpnaas_ike_policy_v2": vpn.ResourceVpnIKEPolicyV2(), "opentelekomcloud_vpnaas_endpoint_group_v2": vpn.ResourceVpnEndpointGroupV2(), "opentelekomcloud_vpnaas_site_connection_v2": vpn.ResourceVpnSiteConnectionV2(), + "opentelekomcloud_enterprise_vpn_gateway_v5": vpn.ResourceEnterpriseVpnGateway(), "opentelekomcloud_waf_alarm_notification_v1": waf.ResourceWafAlarmNotificationV1(), "opentelekomcloud_waf_certificate_v1": waf.ResourceWafCertificateV1(), "opentelekomcloud_waf_domain_v1": waf.ResourceWafDomainV1(), diff --git a/opentelekomcloud/services/vpn/common.go b/opentelekomcloud/services/vpn/common.go index 4ddec2b24..16aa94ee7 100644 --- a/opentelekomcloud/services/vpn/common.go +++ b/opentelekomcloud/services/vpn/common.go @@ -2,5 +2,7 @@ package vpn const ( errCreationV2Client = "error creating OpenTelekomCloud NetworkingV2 client: %w" + errCreationV5Client = "error creating OpenTelekomCloud EvpnV5 client: %w" keyClientV2 = "vpc-v2-client" + keyClientV5 = "vpc-v5-client" ) diff --git a/opentelekomcloud/services/vpn/resource_opentelekomcloud_enterprise_vpn_gateway_v5.go b/opentelekomcloud/services/vpn/resource_opentelekomcloud_enterprise_vpn_gateway_v5.go new file mode 100644 index 000000000..718c563ff --- /dev/null +++ b/opentelekomcloud/services/vpn/resource_opentelekomcloud_enterprise_vpn_gateway_v5.go @@ -0,0 +1,526 @@ +package vpn + +import ( + "context" + "fmt" + "log" + "time" + + "github.com/hashicorp/go-multierror" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + golangsdk "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/openstack/common/pointerto" + "github.com/opentelekomcloud/gophertelekomcloud/openstack/common/tags" + "github.com/opentelekomcloud/gophertelekomcloud/openstack/evpn/v5/gateway" + vpntags "github.com/opentelekomcloud/gophertelekomcloud/openstack/evpn/v5/tags" + "github.com/opentelekomcloud/terraform-provider-opentelekomcloud/opentelekomcloud/common" + "github.com/opentelekomcloud/terraform-provider-opentelekomcloud/opentelekomcloud/common/cfg" + "github.com/opentelekomcloud/terraform-provider-opentelekomcloud/opentelekomcloud/common/fmterr" +) + +func ResourceEnterpriseVpnGateway() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceEvpnGatewayCreate, + UpdateContext: resourceEvpnGatewayUpdate, + ReadContext: resourceEvpnGatewayRead, + DeleteContext: resourceEvpnGatewayDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Update: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "availability_zones": { + Type: schema.TypeList, + Elem: &schema.Schema{Type: schema.TypeString}, + Required: true, + ForceNew: true, + }, + "flavor": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + }, + "attachment_type": { + Type: schema.TypeString, + Optional: true, + Default: "vpc", + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{ + "vpc", "er", + }, false), + }, + "network_type": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + }, + "vpc_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + "local_subnets": { + Type: schema.TypeList, + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, + Computed: true, + }, + "connect_subnet": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + "er_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + }, + "ha_mode": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + ValidateFunc: validation.StringInSlice([]string{"active-active", "active-standby"}, false), + }, + "eip1": { + Type: schema.TypeList, + MaxItems: 1, + Elem: GatewayEipSchema(), + Optional: true, + Computed: true, + RequiredWith: []string{"eip2"}, + }, + "eip2": { + Type: schema.TypeList, + MaxItems: 1, + Elem: GatewayEipSchema(), + Optional: true, + Computed: true, + RequiredWith: []string{"eip1"}, + }, + "access_vpc_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + }, + "access_subnet_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + }, + "asn": { + Type: schema.TypeInt, + Optional: true, + Default: 64512, + ForceNew: true, + }, + "access_private_ip_1": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + RequiredWith: []string{"access_private_ip_2"}, + }, + "access_private_ip_2": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + RequiredWith: []string{"access_private_ip_1"}, + }, + "tags": common.TagsSchema(), + "status": { + Type: schema.TypeString, + Computed: true, + }, + "created_at": { + Type: schema.TypeString, + Computed: true, + }, + "updated_at": { + Type: schema.TypeString, + Computed: true, + }, + "er_attachment_id": { + Type: schema.TypeString, + Computed: true, + }, + "used_connection_group": { + Type: schema.TypeInt, + Computed: true, + }, + "used_connection_number": { + Type: schema.TypeInt, + Computed: true, + }, + "region": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func GatewayEipSchema() *schema.Resource { + sc := schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + "type": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + "bandwidth_name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + "bandwidth_size": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + ForceNew: true, + }, + "charge_mode": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{ + "bandwidth", "traffic", + }, false), + }, + + "bandwidth_id": { + Type: schema.TypeString, + Computed: true, + }, + "ip_address": { + Type: schema.TypeString, + Computed: true, + }, + "ip_version": { + Type: schema.TypeInt, + Computed: true, + }, + }, + } + return &sc +} + +func resourceEvpnGatewayCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + config := meta.(*cfg.Config) + client, err := common.ClientFromCtx(ctx, keyClientV5, func() (*golangsdk.ServiceClient, error) { + return config.EvpnV5Client(config.GetRegion(d)) + }) + if err != nil { + return fmterr.Errorf(errCreationV5Client, err) + } + + var zones []string + azRaw := d.Get("availability_zones").([]interface{}) + for _, az := range azRaw { + zones = append(zones, az.(string)) + } + gatewayTags := d.Get("tags").(map[string]interface{}) + var tagSlice []tags.ResourceTag + for k, v := range gatewayTags { + tagSlice = append(tagSlice, tags.ResourceTag{Key: k, Value: v.(string)}) + } + createOpts := gateway.CreateOpts{ + Name: d.Get("name").(string), + NetworkType: d.Get("network_type").(string), + AttachmentType: d.Get("attachment_type").(string), + ErId: d.Get("er_id").(string), + VpcId: d.Get("vpc_id").(string), + LocalSubnets: buildLocalSubnets(d), + ConnectSubnet: d.Get("connect_subnet").(string), + BgpAsn: pointerto.Int(d.Get("asn").(int)), + Flavor: d.Get("flavor").(string), + AvailabilityZoneIds: zones, + Eip1: buildCreateEvpnGatewayEIP(d, "eip1"), + Eip2: buildCreateEvpnGatewayEIP(d, "eip2"), + AccessVpcId: d.Get("access_vpc_id").(string), + AccessSubnetId: d.Get("access_subnet_id").(string), + HaMode: d.Get("ha_mode").(string), + AccessPrivateIp1: d.Get("access_private_ip_1").(string), + AccessPrivateIp2: d.Get("access_private_ip_2").(string), + Tags: tagSlice, + } + + n, err := gateway.Create(client, createOpts) + if err != nil { + return fmterr.Errorf("error creating OpenTelekomCloud EVPN gateway: %w", err) + } + + d.SetId(n.ID) + stateConf := &resource.StateChangeConf{ + Pending: []string{"CREATING"}, + Target: []string{"ACTIVE"}, + Refresh: waitForGatewayActive(client, n.ID), + Timeout: d.Timeout(schema.TimeoutCreate), + Delay: 10 * time.Second, + PollInterval: 5 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForStateContext(ctx) + if err != nil { + return fmterr.Errorf("error waiting for OpenTelekomCloud EVPN gateway (%s) to become ACTIVE: %w", n.ID, err) + } + clientCtx := common.CtxWithClient(ctx, client, keyClientV5) + return resourceEvpnGatewayRead(clientCtx, d, meta) +} + +func buildLocalSubnets(d *schema.ResourceData) []string { + var localSubnets []string + subRaw := d.Get("local_subnets").([]interface{}) + for _, s := range subRaw { + localSubnets = append(localSubnets, s.(string)) + } + return localSubnets +} + +func buildCreateEvpnGatewayEIP(d *schema.ResourceData, param string) *gateway.Eip { + if rawArray, ok := d.Get(param).([]interface{}); ok { + if len(rawArray) == 0 { + return nil + } + + raw, ok := rawArray[0].(map[string]interface{}) + if !ok { + return nil + } + + eip := &gateway.Eip{ + ID: raw["id"].(string), + Type: raw["type"].(string), + ChargeMode: raw["charge_mode"].(string), + BandwidthSize: raw["bandwidth_size"].(int), + BandwidthName: raw["bandwidth_name"].(string), + } + return eip + } + return nil +} + +func resourceEvpnGatewayRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + config := meta.(*cfg.Config) + client, err := common.ClientFromCtx(ctx, keyClientV5, func() (*golangsdk.ServiceClient, error) { + return config.EvpnV5Client(config.GetRegion(d)) + }) + if err != nil { + return fmterr.Errorf(errCreationV5Client, err) + } + gw, err := gateway.Get(client, d.Id()) + if err != nil { + return diag.Errorf("error retrieving OpenTelekomCloud EVPN gateway (%s): %s", d.Id(), err) + } + + tagsMap := make(map[string]string) + for _, tag := range gw.Tags { + tagsMap[tag.Key] = tag.Value + } + + mErr := multierror.Append( + nil, + d.Set("region", config.GetRegion(d)), + d.Set("attachment_type", gw.AttachmentType), + d.Set("availability_zones", gw.AvailabilityZoneIds), + d.Set("asn", gw.BgpAsn), + d.Set("connect_subnet", gw.ConnectSubnet), + d.Set("created_at", gw.CreatedAt), + d.Set("flavor", gw.Flavor), + d.Set("local_subnets", gw.LocalSubnets), + d.Set("ha_mode", gw.HaMode), + d.Set("eip1", flattenEvpGatewayResponseEip(gw.Eip1)), + d.Set("name", gw.Name), + d.Set("eip2", flattenEvpGatewayResponseEip(gw.Eip2)), + d.Set("status", gw.Status), + d.Set("updated_at", gw.UpdatedAt), + d.Set("used_connection_group", gw.UsedConnectionGroup), + d.Set("used_connection_number", gw.UsedConnectionNumber), + d.Set("vpc_id", gw.VpcId), + d.Set("access_vpc_id", gw.AccessVpcId), + d.Set("access_subnet_id", gw.AccessSubnetId), + d.Set("er_id", gw.ErId), + d.Set("network_type", gw.NetworkType), + d.Set("access_private_ip_1", gw.AccessPrivateIp1), + d.Set("access_private_ip_2", gw.AccessPrivateIp2), + d.Set("tags", tagsMap), + ) + + return diag.FromErr(mErr.ErrorOrNil()) +} + +func flattenEvpGatewayResponseEip(resp gateway.EipResp) []interface{} { + rst := []interface{}{ + map[string]interface{}{ + "bandwidth_id": resp.BandwidthId, + "bandwidth_name": resp.BandwidthName, + "bandwidth_size": resp.BandwidthSize, + "charge_mode": resp.ChargeMode, + "id": resp.ID, + "ip_address": resp.IpAddress, + "ip_version": resp.IpVersion, + "type": resp.Type, + }, + } + return rst +} + +func resourceEvpnGatewayUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + config := meta.(*cfg.Config) + client, err := common.ClientFromCtx(ctx, keyClientV5, func() (*golangsdk.ServiceClient, error) { + return config.EvpnV5Client(config.GetRegion(d)) + }) + if err != nil { + return fmterr.Errorf(errCreationV5Client, err) + } + + updateChanges := []string{ + "name", + "local_subnets", + "eip1", + "eip2", + } + + if d.HasChanges(updateChanges...) { + opts := gateway.UpdateOpts{ + GatewayID: d.Id(), + Name: d.Get("name").(string), + LocalSubnets: buildLocalSubnets(d), + Eip1: buildCreateEvpnGatewayEIP(d, "eip1"), + Eip2: buildCreateEvpnGatewayEIP(d, "eip2"), + } + _, err = gateway.Update(client, opts) + if err != nil { + return diag.Errorf("error updating OpenTelekomCloud EVPN gateway: %s", err) + } + } + + // update tags + if d.HasChange("tags") { + if err = updateTags(client, d, "vpn-gateway", d.Id()); err != nil { + return diag.Errorf("error updating tags of OpenTelekomCloud EVPN gateway (%s): %s", d.Id(), err) + } + } + clientCtx := common.CtxWithClient(ctx, client, keyClientV5) + return resourceEvpnGatewayRead(clientCtx, d, meta) +} + +func updateTags(client *golangsdk.ServiceClient, d *schema.ResourceData, resourceType, id string) error { + if d.HasChange("tags") { + oldMapRaw, newMapRaw := d.GetChange("tags") + oldMap := oldMapRaw.(map[string]interface{}) + newMap := newMapRaw.(map[string]interface{}) + + // remove old tags + if len(oldMap) > 0 { + tagList := common.ExpandResourceTags(oldMap) + err := vpntags.Delete(client, resourceType, id, vpntags.TagsOpts{Tags: tagList}) + if err != nil { + return err + } + } + + // set new tags + if len(newMap) > 0 { + tagList := common.ExpandResourceTags(newMap) + err := vpntags.Create(client, resourceType, id, vpntags.TagsOpts{Tags: tagList}) + if err != nil { + return err + } + } + } + + return nil +} + +func resourceEvpnGatewayDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + config := meta.(*cfg.Config) + client, err := common.ClientFromCtx(ctx, keyClientV5, func() (*golangsdk.ServiceClient, error) { + return config.EvpnV5Client(config.GetRegion(d)) + }) + if err != nil { + return fmterr.Errorf(errCreationV5Client, err) + } + + err = gateway.Delete(client, d.Id()) + if err != nil { + return common.CheckDeletedDiag(d, err, "error deleting OpenTelekomCloud EVPN gateway") + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{"DELETING"}, + Target: []string{"DELETED"}, + Refresh: waitForGatewayDeletion(client, d.Id()), + Timeout: d.Timeout(schema.TimeoutCreate), + Delay: 10 * time.Second, + } + _, err = stateConf.WaitForStateContext(ctx) + if err != nil { + return diag.Errorf("error waiting for deleting OpenTelekomCloud EVPN gateway (%s) to complete: %s", d.Id(), err) + } + return nil +} + +func waitForGatewayActive(client *golangsdk.ServiceClient, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + n, err := gateway.Get(client, id) + if err != nil { + return nil, "", err + } + if n.Status == "ACTIVE" { + return n, "ACTIVE", nil + } + return n, "CREATING", nil + } +} + +func waitForGatewayDeletion(client *golangsdk.ServiceClient, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + r, err := gateway.Get(client, id) + if err != nil { + if _, ok := err.(golangsdk.ErrDefault404); ok { + log.Printf("[DEBUG] The OpenTelekomCloud EVPN gateway has been deleted (ID:%s).", id) + return r, "DELETED", nil + } + return nil, "ERROR", err + } + switch r.Status { + case "ACTIVE", "PENDING_DELETE": + return r, "DELETING", nil + default: + err = fmt.Errorf("error deleting OpenTelekomCloud EVPN gateway[%s]. "+ + "Unexpected status: %v", r.ID, r.Status) + return r, "ERROR", err + } + } +} diff --git a/releasenotes/notes/evpn-gateway-f67a7b2d43de7294.yaml b/releasenotes/notes/evpn-gateway-f67a7b2d43de7294.yaml new file mode 100644 index 000000000..670afb873 --- /dev/null +++ b/releasenotes/notes/evpn-gateway-f67a7b2d43de7294.yaml @@ -0,0 +1,4 @@ +--- +features: + | + **[EVPN]** Add new resource ``resource/opentelekomcloud_enterprise_vpn_gateway_v5`` (`#2671 `_)