diff --git a/docs/data-sources/apigw_gateway_features_v2.md b/docs/data-sources/apigw_gateway_features_v2.md
new file mode 100644
index 000000000..44c0e3581
--- /dev/null
+++ b/docs/data-sources/apigw_gateway_features_v2.md
@@ -0,0 +1,57 @@
+---
+subcategory: "APIGW"
+layout: "opentelekomcloud"
+page_title: "OpenTelekomCloud: opentelekomcloud_apigw_gateway_features_v2"
+sidebar_current: "docs-opentelekomcloud-datasource-apigw-gateway-features-v2"
+description: |-
+ Get the all APIGW gateway features from OpenTelekomCloud
+---
+
+Up-to-date reference of API arguments for API Gateway environment variable service you can get at
+[documentation portal](https://docs.otc.t-systems.com/api-gateway/api-ref/dedicated_gateway_apis_v2/gateway_feature_management/querying_gateway_features.html)
+
+# opentelekomcloud_apigw_gateway_features_v2
+
+Use this data source to get the list of the features under the APIGW gateway within OpenTelekomCloud.
+
+## Example Usage
+
+```hcl
+variable gateway_id {}
+
+data "opentelekomcloud_apigw_gateway_features_v2" "ft" {
+ gateway_id = var.gateway_id
+}
+```
+
+## Argument Reference
+
+The following arguments are supported:
+
+* `gateway_id` - (Required, String) Specified the ID of the dedicated gateway to which the features belong.
+
+* `name` - (Optional, String) Specified the name of the feature.
+
+## Attribute Reference
+
+In addition to all arguments above, the following attributes are exported:
+
+* `id` - The data source ID.
+
+* `region` - The region in which to query the data source.
+
+* `features` - All instance features that match the filter parameters.
+ The [features](#instance_features) structure is documented below.
+
+
+The `features` block supports:
+
+* `id` - The ID of the feature.
+
+* `name` - The name of the feature.
+
+* `enabled` - Whether the feature is enabled.
+
+* `config` - The detailed configuration of the instance feature.
+
+* `updated_at` - The latest update time of the feature, in RFC3339 format.
diff --git a/docs/resources/apigw_gateway_feature_v2.md b/docs/resources/apigw_gateway_feature_v2.md
new file mode 100644
index 000000000..f14c35db0
--- /dev/null
+++ b/docs/resources/apigw_gateway_feature_v2.md
@@ -0,0 +1,70 @@
+---
+subcategory: "APIGW"
+layout: "opentelekomcloud"
+page_title: "OpenTelekomCloud: opentelekomcloud_apigw_gateway_feature_v2"
+sidebar_current: "docs-opentelekomcloud-resource-apigw-gateway-feature-v2"
+description: |-
+ Manages a APIGW gateway feature resource within OpenTelekomCloud.
+---
+
+Up-to-date reference of API arguments for API Gateway environment variable service you can get at
+[documentation portal](https://docs.otc.t-systems.com/api-gateway/api-ref/dedicated_gateway_apis_v2/gateway_feature_management/configuring_a_feature_for_a_gateway.html)
+
+# opentelekomcloud_apigw_gateway_feature_v2
+
+Manages an APIGW gateway feature resource within OpenTelekomCloud.
+
+-> For various types of feature parameter configurations, please refer to the
+ [documentation](https://docs.otc.t-systems.com/api-gateway/api-ref/appendix/supported_features.html#apig-api-20200402).
+
+## Example Usage
+
+```hcl
+variable "gateway_id" {}
+
+resource "opentelekomcloud_apigw_gateway_feature_v2" "feat" {
+ gateway_id = var.gateway_id
+ name = "ratelimit"
+ enabled = true
+
+ config = jsonencode({
+ api_limits = 300
+ })
+}
+```
+
+## Argument Reference
+
+The following arguments are supported:
+
+* `gateway_id` - (Required, String, ForceNew) Specified the ID of the dedicated gateway to which the feature belongs.
+ Changing this creates a new resource.
+
+* `name` - (Required, String, ForceNew) Specified the name of the feature.
+ Changing this creates a new resource.
+
+* `enabled` - (Optional, Bool) Specified whether to enable the feature. Default value is `false`.
+
+* `config` - (Optional, String) Specified the detailed configuration of the feature.
+
+## Attribute Reference
+
+In addition to all arguments above, the following attributes are exported:
+
+* `id` - The resource ID. The value is the feature name.
+
+* `region` - The region in which to create the resource.
+
+## Timeouts
+
+This resource provides the following timeouts configuration options:
+
+* `create` - Default is 5 minutes.
+
+## Import
+
+The resource can be imported using `gateway_id` and `name`, separated by a slash (/), e.g.
+
+```bash
+$ terraform import opentelekomcloud_apigw_gateway_feature_v2.feat /
+```
diff --git a/docs/resources/apigw_gateway_routes_v2.md b/docs/resources/apigw_gateway_routes_v2.md
new file mode 100644
index 000000000..1e6a2772c
--- /dev/null
+++ b/docs/resources/apigw_gateway_routes_v2.md
@@ -0,0 +1,53 @@
+---
+subcategory: "APIGW"
+layout: "opentelekomcloud"
+page_title: "OpenTelekomCloud: opentelekomcloud_apigw_gateway_routes_v2"
+sidebar_current: "docs-opentelekomcloud-resource-apigw-gateway-routes-v2"
+description: |-
+ Manages a APIGW gateway routes resource within OpenTelekomCloud.
+---
+
+Up-to-date reference of API arguments for API Gateway environment variable service you can get at
+[documentation portal](https://docs.otc.t-systems.com/api-gateway/api-ref/dedicated_gateway_apis_v2/gateway_feature_management/configuring_a_feature_for_a_gateway.html)
+
+# opentelekomcloud_apigw_gateway_routes_v2
+
+Manages a APIGW gateway routes resource within OpenTelekomCloud.
+
+## Example Usage
+
+```hcl
+variable "gateway_id" {}
+
+resource "opentelekomcloud_apigw_gateway_routes_v2" "rt" {
+ gateway_id = var.gateway_id
+ nexthops = ["172.16.3.0/24", "172.16.7.0/24"]
+}
+```
+
+## Argument Reference
+
+The following arguments are supported:
+
+* `gateway_id` - (Required, String, ForceNew) Specifies the ID of the dedicated gateway to which the routes belong.
+ Changing this will create a new resource.
+
+* `nexthops` - (Required, List) Specifies the configuration of the next-hop routes.
+
+-> The network segment of the next hop cannot overlap with the network segment of the APIGW gateway.
+
+## Attribute Reference
+
+In addition to all arguments above, the following attributes are exported:
+
+* `id` - The resource ID (gateway ID).
+
+* `region` - The region where the dedicated gateway and routes are located.
+
+## Import
+
+Routes can be imported using their related dedicated instance ID (`gateway_id`), e.g.
+
+```bash
+$ terraform import opentelekomcloud_apigw_gateway_routes_v2.rt 628001b3c5eg6d3e91a8da530f46427y
+```
diff --git a/opentelekomcloud/acceptance/apigw/data_source_opentelekomcloud_apigw_gateway_features_v2_test.go b/opentelekomcloud/acceptance/apigw/data_source_opentelekomcloud_apigw_gateway_features_v2_test.go
new file mode 100644
index 000000000..0ba3a531a
--- /dev/null
+++ b/opentelekomcloud/acceptance/apigw/data_source_opentelekomcloud_apigw_gateway_features_v2_test.go
@@ -0,0 +1,99 @@
+package acceptance
+
+import (
+ "fmt"
+ "regexp"
+ "testing"
+
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
+ "github.com/opentelekomcloud/terraform-provider-opentelekomcloud/opentelekomcloud/acceptance/common"
+ "github.com/opentelekomcloud/terraform-provider-opentelekomcloud/opentelekomcloud/acceptance/env"
+)
+
+func TestAccDataSourceInstanceFeatures_basic(t *testing.T) {
+ var (
+ rName = "data.opentelekomcloud_apigw_gateway_features_v2.test"
+ dc = common.InitDataSourceCheck(rName)
+
+ byName = "data.opentelekomcloud_apigw_gateway_features_v2.filter_by_name"
+ dcByName = common.InitDataSourceCheck(byName)
+
+ byNotFoundName = "data.opentelekomcloud_apigw_gateway_features_v2.filter_by_not_found_name"
+ dcByNotFoundName = common.InitDataSourceCheck(byNotFoundName)
+ )
+
+ resource.ParallelTest(t, resource.TestCase{
+ PreCheck: func() {
+ common.TestAccPreCheck(t)
+ common.TestAccPreCheckApigw(t)
+ },
+ ProviderFactories: common.TestAccProviderFactories,
+ Steps: []resource.TestStep{
+ {
+ Config: testAccDataSourceInstanceFeatures_basic(),
+ Check: resource.ComposeTestCheckFunc(
+ dc.CheckResourceExists(),
+ resource.TestMatchResourceAttr(rName, "features.#", regexp.MustCompile(`^[1-9]([0-9]*)?$`)),
+ dcByName.CheckResourceExists(),
+ resource.TestMatchResourceAttr(byName, "features.0.updated_at",
+ regexp.MustCompile(`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}?(Z|([+-]\d{2}:\d{2}))$`)),
+ resource.TestCheckOutput("is_name_filter_useful", "true"),
+ dcByNotFoundName.CheckResourceExists(),
+ resource.TestCheckOutput("is_name_not_found_filter_useful", "true"),
+ ),
+ },
+ },
+ })
+}
+
+func testAccDataSourceInstanceFeatures_basic() string {
+ return fmt.Sprintf(`
+locals {
+ gateway_id = "%[1]s"
+}
+
+data "opentelekomcloud_apigw_gateway_features_v2" "test" {
+ gateway_id = local.gateway_id
+}
+
+# Filter by name
+locals {
+ feature_name = data.opentelekomcloud_apigw_gateway_features_v2.test.features[0].name
+}
+
+data "opentelekomcloud_apigw_gateway_features_v2" "filter_by_name" {
+ gateway_id = local.gateway_id
+ name = local.feature_name
+}
+
+locals {
+ name_filter_result = [
+ for v in data.opentelekomcloud_apigw_gateway_features_v2.filter_by_name.features[*].name : v == local.feature_name
+ ]
+}
+
+output "is_name_filter_useful" {
+ value = length(local.name_filter_result) > 0 && alltrue(local.name_filter_result)
+}
+
+# Filter by name (not found)
+locals {
+ not_found_name = "not_found"
+}
+
+data "opentelekomcloud_apigw_gateway_features_v2" "filter_by_not_found_name" {
+ gateway_id = local.gateway_id
+ name = local.not_found_name
+}
+
+locals {
+ not_found_name_filter_result = [
+ for v in data.opentelekomcloud_apigw_gateway_features_v2.filter_by_not_found_name.features[*].name : strcontains(v, local.not_found_name)
+ ]
+}
+
+output "is_name_not_found_filter_useful" {
+ value = length(local.not_found_name_filter_result) == 0
+}
+`, env.OS_APIGW_GATEWAY_ID)
+}
diff --git a/opentelekomcloud/acceptance/apigw/resource_opentelekomcloud_apigw_gateway_feature_v2_test.go b/opentelekomcloud/acceptance/apigw/resource_opentelekomcloud_apigw_gateway_feature_v2_test.go
new file mode 100644
index 000000000..32d39cc6f
--- /dev/null
+++ b/opentelekomcloud/acceptance/apigw/resource_opentelekomcloud_apigw_gateway_feature_v2_test.go
@@ -0,0 +1,130 @@
+package acceptance
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
+ "github.com/opentelekomcloud/gophertelekomcloud/openstack/apigw/v2/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"
+)
+
+func getInstanceFeatureFunc(cfg *cfg.Config, state *terraform.ResourceState) (interface{}, error) {
+ client, err := cfg.APIGWV2Client(env.OS_REGION_NAME)
+ if err != nil {
+ return nil, fmt.Errorf("error creating APIG v2 client: %s", err)
+ }
+ gatewayId := state.Primary.Attributes["gateway_id"]
+ features, err := gateway.ListGatewayFeatures(client, gateway.ListFeaturesOpts{
+ GatewayID: gatewayId,
+ Limit: 500,
+ })
+ if err != nil {
+ return nil, err
+ }
+ if len(features) < 1 {
+ return nil, err
+ }
+ var f gateway.FeatureResp
+ for _, feature := range features {
+ if feature.Name == state.Primary.ID {
+ f = feature
+ }
+ }
+ return f, err
+}
+
+func TestAccInstanceFeature_basic(t *testing.T) {
+ var (
+ feature gateway.FeatureResp
+ rName = "opentelekomcloud_apigw_gateway_feature_v2.feat"
+ )
+
+ rc := common.InitResourceCheck(
+ rName,
+ &feature,
+ getInstanceFeatureFunc,
+ )
+
+ // Avoid CheckDestroy because this resource already exists and does not need to be deleted.
+ // lintignore:AT001
+ resource.ParallelTest(t, resource.TestCase{
+ PreCheck: func() {
+ common.TestAccPreCheck(t)
+ common.TestAccPreCheckApigw(t)
+ },
+ ProviderFactories: common.TestAccProviderFactories,
+ Steps: []resource.TestStep{
+ {
+ Config: testAccInstanceFeature_basic(),
+ Check: resource.ComposeTestCheckFunc(
+ rc.CheckResourceExists(),
+ resource.TestCheckResourceAttr(rName, "name", "ratelimit"),
+ resource.TestCheckResourceAttr(rName, "enabled", "true"),
+ resource.TestCheckResourceAttr(rName, "config", "{\"api_limits\":200}"),
+ ),
+ },
+ {
+ Config: testAccInstanceFeature_basicUpdate(),
+ Check: resource.ComposeTestCheckFunc(
+ rc.CheckResourceExists(),
+ resource.TestCheckResourceAttr(rName, "name", "ratelimit"),
+ resource.TestCheckResourceAttr(rName, "config", "{\"api_limits\":300}"),
+ ),
+ },
+ {
+ ResourceName: rName,
+ ImportState: true,
+ ImportStateVerify: true,
+ ImportStateIdFunc: testAccInstanceFeatureResourceImportStateFunc(rName),
+ },
+ },
+ })
+}
+
+func testAccInstanceFeatureResourceImportStateFunc(rName string) resource.ImportStateIdFunc {
+ return func(s *terraform.State) (string, error) {
+ rs, ok := s.RootModule().Resources[rName]
+ if !ok {
+ return "", fmt.Errorf("resource (%s) not found: %s", rName, rs)
+ }
+ gatewayId := rs.Primary.Attributes["gateway_id"]
+ featureName := rs.Primary.ID
+ if gatewayId == "" || featureName == "" {
+ return "", fmt.Errorf("missing some attributes, want '/', but '%s/%s'",
+ gatewayId, featureName)
+ }
+ return fmt.Sprintf("%s/%s", gatewayId, featureName), nil
+ }
+}
+
+func testAccInstanceFeature_basic() string {
+ return fmt.Sprintf(`
+resource "opentelekomcloud_apigw_gateway_feature_v2" "feat" {
+ gateway_id = "%[1]s"
+ name = "ratelimit"
+ enabled = true
+
+ config = jsonencode({
+ api_limits = 200
+ })
+}
+`, env.OS_APIGW_GATEWAY_ID)
+}
+
+func testAccInstanceFeature_basicUpdate() string {
+ return fmt.Sprintf(`
+resource "opentelekomcloud_apigw_gateway_feature_v2" "feat" {
+ gateway_id = "%[1]s"
+ name = "ratelimit"
+ enabled = true
+
+ config = jsonencode({
+ api_limits = 300
+ })
+}
+`, env.OS_APIGW_GATEWAY_ID)
+}
diff --git a/opentelekomcloud/acceptance/apigw/resource_opentelekomcloud_apigw_gateway_routes_v2_test.go b/opentelekomcloud/acceptance/apigw/resource_opentelekomcloud_apigw_gateway_routes_v2_test.go
new file mode 100644
index 000000000..a28b2b904
--- /dev/null
+++ b/opentelekomcloud/acceptance/apigw/resource_opentelekomcloud_apigw_gateway_routes_v2_test.go
@@ -0,0 +1,96 @@
+package acceptance
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
+ "github.com/opentelekomcloud/gophertelekomcloud/openstack/apigw/v2/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"
+)
+
+func getInstanceRoutesFunc(cfg *cfg.Config, state *terraform.ResourceState) (interface{}, error) {
+ client, err := cfg.APIGWV2Client(env.OS_REGION_NAME)
+ if err != nil {
+ return nil, fmt.Errorf("error creating APIG v2 client: %s", err)
+ }
+ opts := gateway.ListFeaturesOpts{
+ GatewayID: state.Primary.ID,
+ Limit: 500,
+ }
+ resp, err := gateway.ListGatewayFeatures(client, opts)
+ if err != nil {
+ return nil, fmt.Errorf("error querying feature list: %s", err)
+ }
+
+ for _, val := range resp {
+ if val.Name == "route" {
+ return val, nil
+ }
+ }
+ return nil, fmt.Errorf("error querying feature: route")
+}
+
+func TestAccInstanceRoutes_basic(t *testing.T) {
+ var (
+ feature gateway.FeatureResp
+ rName = "opentelekomcloud_apigw_gateway_routes_v2.rt"
+ )
+
+ rc := common.InitResourceCheck(
+ rName,
+ &feature,
+ getInstanceRoutesFunc,
+ )
+
+ resource.ParallelTest(t, resource.TestCase{
+ PreCheck: func() {
+ common.TestAccPreCheck(t)
+ common.TestAccPreCheckApigw(t)
+ },
+ ProviderFactories: common.TestAccProviderFactories,
+ Steps: []resource.TestStep{
+ {
+ Config: testAccInstanceRoutes_basic(),
+ Check: resource.ComposeTestCheckFunc(
+ rc.CheckResourceExists(),
+ resource.TestCheckResourceAttrPair(rName, "gateway_id", "opentelekomcloud_apigw_gateway_routes_v2.rt", "id"),
+ resource.TestCheckResourceAttr(rName, "nexthops.#", "2"),
+ ),
+ },
+ {
+ Config: testAccInstanceRoutes_basicUpdate(),
+ Check: resource.ComposeTestCheckFunc(
+ rc.CheckResourceExists(),
+ resource.TestCheckResourceAttr(rName, "nexthops.#", "2"),
+ ),
+ },
+ {
+ ResourceName: rName,
+ ImportState: true,
+ ImportStateVerify: true,
+ },
+ },
+ })
+}
+
+func testAccInstanceRoutes_basic() string {
+ return fmt.Sprintf(`
+resource "opentelekomcloud_apigw_gateway_routes_v2" "rt" {
+ gateway_id = "%s"
+ nexthops = ["172.16.128.0/20", "172.16.0.0/20"]
+}
+`, env.OS_APIGW_GATEWAY_ID)
+}
+
+func testAccInstanceRoutes_basicUpdate() string {
+ return fmt.Sprintf(`
+resource "opentelekomcloud_apigw_gateway_routes_v2" "rt" {
+ gateway_id = "%s"
+ nexthops = ["172.16.64.0/20", "172.16.192.0/20"]
+}
+`, env.OS_APIGW_GATEWAY_ID)
+}
diff --git a/opentelekomcloud/acceptance/common/common.go b/opentelekomcloud/acceptance/common/common.go
index b8473b9df..d89c43145 100644
--- a/opentelekomcloud/acceptance/common/common.go
+++ b/opentelekomcloud/acceptance/common/common.go
@@ -132,6 +132,12 @@ func TestAccVBSBackupShareCheck(t *testing.T) {
}
}
+func TestAccPreCheckApigw(t *testing.T) {
+ if env.OS_APIGW_GATEWAY_ID == "" {
+ t.Skip("Before running APIGW acceptance tests, please ensure the env 'OS_APIGW_GATEWAY_ID' has been configured")
+ }
+}
+
func TestAccPreCheckServiceAvailability(t *testing.T, service string, regions []string) diag.Diagnostics {
t.Logf("Service: %s, Region %s", service, env.OS_REGION_NAME)
config := TestAccProvider.Meta().(*cfg.Config)
diff --git a/opentelekomcloud/acceptance/env/vars.go b/opentelekomcloud/acceptance/env/vars.go
index d81a2a563..92492db0d 100644
--- a/opentelekomcloud/acceptance/env/vars.go
+++ b/opentelekomcloud/acceptance/env/vars.go
@@ -27,6 +27,7 @@ var (
OS_DC_HOSTING_ID = os.Getenv("OS_DC_HOSTING_ID")
OS_DDM_ID = os.Getenv("OS_DDM_ID")
OS_RDS_ID = os.Getenv("OS_RDS_ID")
+ OS_APIGW_GATEWAY_ID = os.Getenv("OS_APIGW_GATEWAY_ID")
)
func flavorID() string {
diff --git a/opentelekomcloud/common/times.go b/opentelekomcloud/common/times.go
index 9192a9745..de16c30ef 100644
--- a/opentelekomcloud/common/times.go
+++ b/opentelekomcloud/common/times.go
@@ -1,6 +1,9 @@
package common
-import "time"
+import (
+ "log"
+ "time"
+)
func FormatTimeStampRFC3339(timestamp int64, isUTC bool, customFormat ...string) string {
if timestamp == 0 {
@@ -16,3 +19,40 @@ func FormatTimeStampRFC3339(timestamp int64, isUTC bool, customFormat ...string)
}
return createTime.Format(time.RFC3339)
}
+
+// ConvertTimeStrToNanoTimestamp is a method that used to convert the time string into the corresponding timestamp (in
+// nanosecond), e.g.
+// The supported time formats are as follows:
+// - RFC3339 format:
+// 2006-01-02T15:04:05Z (default time format, if you are missing customFormat input)
+// 2006-01-02T15:04:05.000000Z
+// 2006-01-02T15:04:05Z08:00
+// - Other time formats:
+// 2006-01-02 15:04:05
+// 2006-01-02 15:04:05+08:00
+// 2006-01-02T15:04:05
+// ...
+//
+// Two common uses are shown below:
+// - ConvertTimeStrToNanoTimestamp("2024-01-01T00:00:00Z")
+// - ConvertTimeStrToNanoTimestamp("2024-01-01T00:00:00+08:00", "2006-01-02T15:04:05Z08:00")
+func ConvertTimeStrToNanoTimestamp(timeStr string, customFormat ...string) int64 {
+ // The default time format is RFC3339.
+ timeFormat := time.RFC3339
+ if len(customFormat) > 0 {
+ timeFormat = customFormat[0]
+ }
+ t, err := time.Parse(timeFormat, timeStr)
+ if err != nil {
+ log.Printf("error parsing the input time (%s), the time string does not match time format (%s): %s",
+ timeStr, timeFormat, err)
+ return 0
+ }
+
+ timestamp := t.UnixNano() / int64(time.Millisecond)
+ // If the time is less than 1970-01-01T00:00:00Z, the timestamp is negative, such as: "0001-01-01T00:00:00Z"
+ if timestamp < 0 {
+ return 0
+ }
+ return timestamp
+}
diff --git a/opentelekomcloud/provider.go b/opentelekomcloud/provider.go
index 78241f8e1..64038b346 100644
--- a/opentelekomcloud/provider.go
+++ b/opentelekomcloud/provider.go
@@ -261,6 +261,7 @@ func Provider() *schema.Provider {
DataSourcesMap: map[string]*schema.Resource{
"opentelekomcloud_antiddos_v1": antiddos.DataSourceAntiDdosV1(),
"opentelekomcloud_apigw_api_history_v2": apigw.DataSourceApigwApiHistory(),
+ "opentelekomcloud_apigw_gateway_features_v2": apigw.DataSourceGatewayFeaturesV2(),
"opentelekomcloud_cbr_backup_v3": cbr.DataSourceCBRBackupsV3(),
"opentelekomcloud_cbr_backup_ids_v3": cbr.DataSourceCBRBackupsIdsV3(),
"opentelekomcloud_cce_cluster_v3": cce.DataSourceCCEClusterV3(),
@@ -385,6 +386,8 @@ func Provider() *schema.Provider {
"opentelekomcloud_apigw_environment_v2": apigw.ResourceAPIEnvironmentv2(),
"opentelekomcloud_apigw_environment_variable_v2": apigw.ResourceAPIEnvironment2Variable(),
"opentelekomcloud_apigw_gateway_v2": apigw.ResourceAPIGWv2(),
+ "opentelekomcloud_apigw_gateway_routes_v2": apigw.ResourceGatewayRoutesV2(),
+ "opentelekomcloud_apigw_gateway_feature_v2": apigw.ResourceGatewayFeatureV2(),
"opentelekomcloud_apigw_group_v2": apigw.ResourceAPIGroupV2(),
"opentelekomcloud_apigw_response_v2": apigw.ResourceAPIResponseV2(),
"opentelekomcloud_apigw_signature_v2": apigw.ResourceAPISignatureV2(),
diff --git a/opentelekomcloud/services/apigw/common.go b/opentelekomcloud/services/apigw/common.go
index 888e33e8a..0826b8489 100644
--- a/opentelekomcloud/services/apigw/common.go
+++ b/opentelekomcloud/services/apigw/common.go
@@ -22,6 +22,9 @@ type (
NetworkType string
SecretAction string
+ RouteConfig struct {
+ UserRoutes []interface{} `json:"user_routes"`
+ }
)
const (
diff --git a/opentelekomcloud/services/apigw/data_source_opentelekomcloud_apigw_gateway_features_v2.go b/opentelekomcloud/services/apigw/data_source_opentelekomcloud_apigw_gateway_features_v2.go
new file mode 100644
index 000000000..46d322cac
--- /dev/null
+++ b/opentelekomcloud/services/apigw/data_source_opentelekomcloud_apigw_gateway_features_v2.go
@@ -0,0 +1,137 @@
+package apigw
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/hashicorp/go-multierror"
+ "github.com/hashicorp/go-uuid"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+ golangsdk "github.com/opentelekomcloud/gophertelekomcloud"
+ "github.com/opentelekomcloud/gophertelekomcloud/openstack/apigw/v2/gateway"
+ "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 DataSourceGatewayFeaturesV2() *schema.Resource {
+ return &schema.Resource{
+ ReadContext: dataSourceInstanceFeaturesV2Read,
+
+ Schema: map[string]*schema.Schema{
+ "gateway_id": {
+ Type: schema.TypeString,
+ Required: true,
+ },
+ "name": {
+ Type: schema.TypeString,
+ Optional: true,
+ },
+ "features": {
+ Type: schema.TypeList,
+ Computed: true,
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "id": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "name": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "enabled": {
+ Type: schema.TypeBool,
+ Computed: true,
+ },
+ "config": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "updated_at": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ },
+ },
+ },
+ "region": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ },
+ }
+}
+
+func dataSourceInstanceFeaturesV2Read(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+ config := meta.(*cfg.Config)
+ client, err := common.ClientFromCtx(ctx, keyClientV2, func() (*golangsdk.ServiceClient, error) {
+ return config.APIGWV2Client(config.GetRegion(d))
+ })
+ if err != nil {
+ return fmterr.Errorf(errCreationV2Client, err)
+ }
+
+ opts := gateway.ListFeaturesOpts{
+ GatewayID: d.Get("gateway_id").(string),
+ // Default value of parameter 'limit' is 20, parameter 'offset' is an invalid parameter.
+ // If we omit it, we can only obtain 20 features, other features will be lost.
+ Limit: 500,
+ }
+ features, err := gateway.ListGatewayFeatures(client, opts)
+ if err != nil {
+ return diag.Errorf("error querying OpenTelekomCloud APIGW v2 gateway feature list: %s", err)
+ }
+
+ dataSourceId, err := uuid.GenerateUUID()
+ if err != nil {
+ return diag.Errorf("unable to generate ID: %s", err)
+ }
+ d.SetId(dataSourceId)
+
+ mErr := multierror.Append(nil,
+ d.Set("region", config.GetRegion(d)),
+ d.Set("features", filterInstanceFeatures(flattenInstanceFeatures(features), d)),
+ )
+ return diag.FromErr(mErr.ErrorOrNil())
+}
+
+func filterInstanceFeatures(all []interface{}, d *schema.ResourceData) []interface{} {
+ rst := make([]interface{}, 0, len(all))
+ for _, v := range all {
+ vMap, ok := v.(map[string]interface{})
+ if !ok {
+ continue
+ }
+ if param, ok := d.GetOk("name"); ok {
+ name, ok := vMap["name"].(string)
+ if !ok || fmt.Sprint(param) != name {
+ continue
+ }
+ }
+ rst = append(rst, vMap)
+ }
+ return rst
+}
+
+func flattenInstanceFeatures(features []gateway.FeatureResp) []interface{} {
+ if len(features) < 1 {
+ return nil
+ }
+
+ result := make([]interface{}, 0, len(features))
+ for _, feature := range features {
+ updateTime := common.ConvertTimeStrToNanoTimestamp(feature.UpdatedAt)
+ result = append(result, map[string]interface{}{
+ "id": feature.ID,
+ "name": feature.Name,
+ "enabled": feature.Enabled,
+ "config": feature.Config,
+ // If this feature has not been configured, the time format is "0001-01-01T00:00:00Z",
+ // the corresponding timestamp is a negative, and this format is uniformly processed as an empty string.
+ "updated_at": common.FormatTimeStampRFC3339(updateTime/1000, false),
+ })
+ }
+ return result
+}
diff --git a/opentelekomcloud/services/apigw/resource_opentelekomcloud_apigw_gateway_feature_v2.go b/opentelekomcloud/services/apigw/resource_opentelekomcloud_apigw_gateway_feature_v2.go
new file mode 100644
index 000000000..33151e55a
--- /dev/null
+++ b/opentelekomcloud/services/apigw/resource_opentelekomcloud_apigw_gateway_feature_v2.go
@@ -0,0 +1,206 @@
+package apigw
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "strings"
+ "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/jmespath/go-jmespath"
+ golangsdk "github.com/opentelekomcloud/gophertelekomcloud"
+ "github.com/opentelekomcloud/gophertelekomcloud/openstack/apigw/v2/gateway"
+ "github.com/opentelekomcloud/gophertelekomcloud/openstack/common/pointerto"
+ "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 ResourceGatewayFeatureV2() *schema.Resource {
+ return &schema.Resource{
+ CreateContext: resourceInstanceFeatureV2Create,
+ ReadContext: resourceInstanceFeatureV2Read,
+ UpdateContext: resourceInstanceFeatureV2Update,
+ DeleteContext: resourceInstanceFeatureV2Delete,
+
+ Importer: &schema.ResourceImporter{
+ StateContext: resourceInstanceFeatureImportState,
+ },
+
+ Timeouts: &schema.ResourceTimeout{
+ Create: schema.DefaultTimeout(5 * time.Minute),
+ },
+
+ Schema: map[string]*schema.Schema{
+ "gateway_id": {
+ Type: schema.TypeString,
+ Required: true,
+ ForceNew: true,
+ },
+ "name": {
+ Type: schema.TypeString,
+ Required: true,
+ ForceNew: true,
+ },
+ "enabled": {
+ Type: schema.TypeBool,
+ Optional: true,
+ },
+ "config": {
+ Type: schema.TypeString,
+ Optional: true,
+ },
+ "region": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ },
+ }
+}
+
+func updateFeatureConfiguration(ctx context.Context, client *golangsdk.ServiceClient, d *schema.ResourceData, gatewayId, name string) (*gateway.FeatureResp, error) {
+ var resp *gateway.FeatureResp
+ var reqErr error
+ err := resource.RetryContext(ctx, d.Timeout(schema.TimeoutCreate), func() *resource.RetryError {
+ resp, reqErr = gateway.ConfigureFeature(client, gateway.FeatureOpts{
+ GatewayID: gatewayId,
+ Name: name,
+ Enable: pointerto.Bool(d.Get("enabled").(bool)),
+ Config: d.Get("config").(string),
+ })
+ isRetry, err := handleOperationError409(reqErr)
+ if isRetry {
+ // lintignore:R018
+ time.Sleep(30 * time.Second)
+ return resource.RetryableError(err)
+ }
+ if err != nil {
+ return resource.NonRetryableError(err)
+ }
+ return nil
+ })
+ return resp, err
+}
+
+func resourceInstanceFeatureV2Create(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+ config := meta.(*cfg.Config)
+ client, err := common.ClientFromCtx(ctx, keyClientV2, func() (*golangsdk.ServiceClient, error) {
+ return config.APIGWV2Client(config.GetRegion(d))
+ })
+ if err != nil {
+ return fmterr.Errorf(errCreationV2Client, err)
+ }
+ gatewayId := d.Get("gateway_id").(string)
+ name := d.Get("name").(string)
+ feature, err := updateFeatureConfiguration(ctx, client, d, gatewayId, name)
+ if err != nil {
+ return diag.Errorf("error creating OpenTelekomCloud APIGW v2 gateway feature: %s", err)
+ }
+
+ d.SetId(feature.Name)
+ clientCtx := common.CtxWithClient(ctx, client, keyClientV2)
+ return resourceInstanceFeatureV2Read(clientCtx, d, meta)
+}
+
+func handleOperationError409(err error) (bool, error) {
+ if err == nil {
+ return false, nil
+ }
+ if errCode, ok := err.(golangsdk.ErrUnexpectedResponseCode); ok && errCode.Actual == 409 {
+ var apiError interface{}
+ if jsonErr := json.Unmarshal(errCode.Body, &apiError); jsonErr != nil {
+ return false, jsonErr
+ }
+
+ errCode, searchErr := jmespath.Search("error_code", apiError)
+ if searchErr != nil {
+ return false, err
+ }
+
+ // APIG.3711: A configuration parameter can be modified only once per minute.
+ if errCode == "APIG.3711" {
+ return true, err
+ }
+ }
+ return false, err
+}
+
+func resourceInstanceFeatureV2Read(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+ config := meta.(*cfg.Config)
+ client, err := common.ClientFromCtx(ctx, keyClientV2, func() (*golangsdk.ServiceClient, error) {
+ return config.APIGWV2Client(config.GetRegion(d))
+ })
+ if err != nil {
+ return fmterr.Errorf(errCreationV2Client, err)
+ }
+
+ gatewayId := d.Get("gateway_id").(string)
+ features, err := gateway.ListGatewayFeatures(client, gateway.ListFeaturesOpts{
+ GatewayID: gatewayId,
+ Limit: 500,
+ })
+ if err != nil {
+ // When instance ID not exist, status code is 404, error code id APIG.3030
+ return common.CheckDeletedDiag(d, err, "Instance feature configuration")
+ }
+ if len(features) < 1 {
+ return diag.Errorf("error getting OpenTelekomCloud APIGW v2 gateway features: %s", err)
+ }
+ var f gateway.FeatureResp
+ for _, feature := range features {
+ if feature.Name == d.Id() {
+ f = feature
+ }
+ }
+ mErr := multierror.Append(nil,
+ d.Set("name", f.Name),
+ d.Set("enabled", f.Enabled),
+ d.Set("config", f.Config),
+ )
+
+ return diag.FromErr(mErr.ErrorOrNil())
+}
+
+func resourceInstanceFeatureV2Update(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+ config := meta.(*cfg.Config)
+ client, err := common.ClientFromCtx(ctx, keyClientV2, func() (*golangsdk.ServiceClient, error) {
+ return config.APIGWV2Client(config.GetRegion(d))
+ })
+ if err != nil {
+ return fmterr.Errorf(errCreationV2Client, err)
+ }
+
+ gatewayId := d.Get("gateway_id").(string)
+ _, err = updateFeatureConfiguration(ctx, client, d, gatewayId, d.Id())
+ if err != nil {
+ return diag.Errorf("error creating OpenTelekomCloud APIGW v2 gateway feature: %s", err)
+ }
+ if err != nil {
+ return diag.Errorf("error updating feature (%s) under specified instance (%s): %s", d.Id(), gatewayId, err)
+ }
+ clientCtx := common.CtxWithClient(ctx, client, keyClientV2)
+ return resourceInstanceFeatureV2Read(clientCtx, d, meta)
+}
+
+func resourceInstanceFeatureV2Delete(_ context.Context, _ *schema.ResourceData, _ interface{}) diag.Diagnostics {
+ return nil
+}
+
+func resourceInstanceFeatureImportState(_ context.Context, d *schema.ResourceData, _ interface{}) ([]*schema.ResourceData, error) {
+ importedId := d.Id()
+ parts := strings.Split(importedId, "/")
+ if len(parts) != 2 {
+ return nil, fmt.Errorf("invalid format specified for import ID, want /, but got '%s'", importedId)
+ }
+
+ mErr := multierror.Append(
+ d.Set("gateway_id", parts[0]),
+ )
+ d.SetId(parts[1])
+
+ return []*schema.ResourceData{d}, mErr.ErrorOrNil()
+}
diff --git a/opentelekomcloud/services/apigw/resource_opentelekomcloud_apigw_gateway_routes_v2.go b/opentelekomcloud/services/apigw/resource_opentelekomcloud_apigw_gateway_routes_v2.go
new file mode 100644
index 000000000..525026dfb
--- /dev/null
+++ b/opentelekomcloud/services/apigw/resource_opentelekomcloud_apigw_gateway_routes_v2.go
@@ -0,0 +1,178 @@
+package apigw
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "log"
+
+ "github.com/hashicorp/go-multierror"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+ golangsdk "github.com/opentelekomcloud/gophertelekomcloud"
+ "github.com/opentelekomcloud/gophertelekomcloud/openstack/apigw/v2/gateway"
+ "github.com/opentelekomcloud/gophertelekomcloud/openstack/common/pointerto"
+ "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 ResourceGatewayRoutesV2() *schema.Resource {
+ return &schema.Resource{
+ CreateContext: resourceInstanceRoutesV2Create,
+ ReadContext: resourceInstanceRoutesV2Read,
+ UpdateContext: resourceInstanceRoutesV2Update,
+ DeleteContext: resourceInstanceRoutesV2Delete,
+
+ Importer: &schema.ResourceImporter{
+ StateContext: resourceInstanceRoutesImportState,
+ },
+
+ Schema: map[string]*schema.Schema{
+ "gateway_id": {
+ Type: schema.TypeString,
+ Required: true,
+ ForceNew: true,
+ },
+ "nexthops": {
+ Type: schema.TypeSet,
+ Required: true,
+ Elem: &schema.Schema{Type: schema.TypeString},
+ },
+ "region": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ },
+ }
+}
+
+func modifyInstanceRoutes(client *golangsdk.ServiceClient, gatewayId string, routes []interface{}) error {
+ routeConfig := map[string]interface{}{
+ "user_routes": routes,
+ }
+ routeBytes, err := json.Marshal(routeConfig)
+ if err != nil {
+ return fmt.Errorf("error parsing routes configuration: %s", err)
+ }
+ opts := gateway.FeatureOpts{
+ GatewayID: gatewayId,
+ Name: "route",
+ Enable: pointerto.Bool(true),
+ Config: string(routeBytes),
+ }
+ log.Printf("[DEBUG] The modify options of the gateway routes is: %#v", opts)
+ _, err = gateway.ConfigureFeature(client, opts)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func resourceInstanceRoutesV2Create(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+ config := meta.(*cfg.Config)
+ client, err := common.ClientFromCtx(ctx, keyClientV2, func() (*golangsdk.ServiceClient, error) {
+ return config.APIGWV2Client(config.GetRegion(d))
+ })
+ if err != nil {
+ return fmterr.Errorf(errCreationV2Client, err)
+ }
+
+ var (
+ gatewayId = d.Get("gateway_id").(string)
+ routes = d.Get("nexthops").(*schema.Set)
+ )
+ if err := modifyInstanceRoutes(client, gatewayId, routes.List()); err != nil {
+ return diag.Errorf("error creating OpenTelekomCloud APIGW v2 gateway routes: %v", err)
+ }
+ d.SetId(gatewayId)
+
+ clientCtx := common.CtxWithClient(ctx, client, keyClientV2)
+ return resourceInstanceRoutesV2Read(clientCtx, d, meta)
+}
+
+func resourceInstanceRoutesV2Read(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+ config := meta.(*cfg.Config)
+ client, err := common.ClientFromCtx(ctx, keyClientV2, func() (*golangsdk.ServiceClient, error) {
+ return config.APIGWV2Client(config.GetRegion(d))
+ })
+ if err != nil {
+ return fmterr.Errorf(errCreationV2Client, err)
+ }
+
+ gatewayId := d.Get("gateway_id").(string)
+
+ opts := gateway.ListFeaturesOpts{
+ GatewayID: gatewayId,
+ // Default value of parameter 'limit' is 20, parameter 'offset' is an invalid parameter.
+ // If we omit it, we can only obtain 20 features, other features will be lost.
+ Limit: 500,
+ }
+ resp, err := gateway.ListGatewayFeatures(client, opts)
+ if err != nil {
+ return diag.Errorf("error querying OpenTelekomCloud APIGW v2 gateway feature list: %s", err)
+ }
+ log.Printf("[DEBUG] The feature list is: %v", resp)
+
+ var routeConfig string
+ for _, val := range resp {
+ if val.Name == "route" {
+ routeConfig = val.Config
+ break
+ }
+ }
+ var result RouteConfig
+ err = json.Unmarshal([]byte(routeConfig), &result)
+ if err != nil {
+ return diag.Errorf("error analyzing routes configuration: %s", err)
+ }
+ if len(result.UserRoutes) < 1 {
+ return common.CheckDeletedDiag(d, golangsdk.ErrDefault404{}, "Instance routes")
+ }
+
+ return diag.FromErr(d.Set("nexthops", result.UserRoutes))
+}
+
+func resourceInstanceRoutesV2Update(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+ config := meta.(*cfg.Config)
+ client, err := common.ClientFromCtx(ctx, keyClientV2, func() (*golangsdk.ServiceClient, error) {
+ return config.APIGWV2Client(config.GetRegion(d))
+ })
+ if err != nil {
+ return fmterr.Errorf(errCreationV2Client, err)
+ }
+
+ var (
+ gatewayId = d.Get("gateway_id").(string)
+ routes = d.Get("nexthops").(*schema.Set)
+ )
+ if err := modifyInstanceRoutes(client, gatewayId, routes.List()); err != nil {
+ return diag.Errorf("error updating OpenTelekomCloud APIGW v2 gateway routes: %v", err)
+ }
+
+ clientCtx := common.CtxWithClient(ctx, client, keyClientV2)
+ return resourceInstanceRoutesV2Read(clientCtx, d, meta)
+}
+
+func resourceInstanceRoutesV2Delete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+ config := meta.(*cfg.Config)
+ client, err := common.ClientFromCtx(ctx, keyClientV2, func() (*golangsdk.ServiceClient, error) {
+ return config.APIGWV2Client(config.GetRegion(d))
+ })
+ if err != nil {
+ return fmterr.Errorf(errCreationV2Client, err)
+ }
+
+ gatewayId := d.Get("gateway_id").(string)
+ // The expression "{\"user_routes\":null}" has the same result as the expression"{\"user_routes\":[]}".
+ if err := modifyInstanceRoutes(client, gatewayId, nil); err != nil {
+ return diag.Errorf("error deleting OpenTelekomCloud APIGW v2 gateway routes: %v", err)
+ }
+
+ return nil
+}
+
+func resourceInstanceRoutesImportState(_ context.Context, d *schema.ResourceData, _ interface{}) ([]*schema.ResourceData, error) {
+ mErr := multierror.Append(nil, d.Set("gateway_id", d.Id()))
+ return []*schema.ResourceData{d}, mErr.ErrorOrNil()
+}
diff --git a/releasenotes/notes/apigw-features-56240ebbc955562c.yaml b/releasenotes/notes/apigw-features-56240ebbc955562c.yaml
new file mode 100644
index 000000000..5c1725131
--- /dev/null
+++ b/releasenotes/notes/apigw-features-56240ebbc955562c.yaml
@@ -0,0 +1,8 @@
+---
+features:
+ |
+ **[APIGW]** Add new ``resource/opentelekomcloud_apigw_gateway_routes_v2`` (`#2755 `_)
+ |
+ **[APIGW]** Add new ``resource/opentelekomcloud_apigw_gateway_feature_v2`` (`#2755 `_)
+ |
+ **[APIGW]** Add new ``data-source/opentelekomcloud_apigw_gateway_features_v2`` (`#2755 `_)