From e4e2e55e9489b0505183a9c5ba459e671d0f4ab7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Fischer?= Date: Mon, 27 Oct 2025 18:43:56 +0100 Subject: [PATCH] feat(loadbalancer): support for idle timeout (TCP and UDP) (#1039) --- docs/data-sources/loadbalancer.md | 18 +++ docs/resources/loadbalancer.md | 21 +++ .../stackit_loadbalancer/resource.tf | 3 + .../loadbalancer/loadbalancer/datasource.go | 24 +++ .../loadbalancer/loadbalancer/resource.go | 139 ++++++++++++++++++ .../loadbalancer/resource_test.go | 28 ++++ .../loadbalancer/loadbalancer_acc_test.go | 127 ++++++++++------ .../loadbalancer/testfiles/resource-max.tf | 86 +++++++---- 8 files changed, 371 insertions(+), 75 deletions(-) diff --git a/docs/data-sources/loadbalancer.md b/docs/data-sources/loadbalancer.md index 553f1d7e..e6b9f411 100644 --- a/docs/data-sources/loadbalancer.md +++ b/docs/data-sources/loadbalancer.md @@ -57,6 +57,8 @@ Read-Only: - `port` (Number) Port number where we listen for traffic. - `protocol` (String) Protocol is the highest network protocol we understand to load balance. - `target_pool` (String) Reference target pool by target pool name. +- `tcp` (Attributes) Options that are specific to the TCP protocol. (see [below for nested schema](#nestedatt--listeners--tcp)) +- `udp` (Attributes) Options that are specific to the UDP protocol. (see [below for nested schema](#nestedatt--listeners--udp)) ### Nested Schema for `listeners.server_name_indicators` @@ -66,6 +68,22 @@ Optional: - `name` (String) A domain name to match in order to pass TLS traffic to the target pool in the current listener + +### Nested Schema for `listeners.tcp` + +Read-Only: + +- `idle_timeout` (String) Time after which an idle connection is closed. The default value is set to 5 minutes, and the maximum value is one hour. + + + +### Nested Schema for `listeners.udp` + +Read-Only: + +- `idle_timeout` (String) Time after which an idle session is closed. The default value is set to 1 minute, and the maximum value is 2 minutes. + + ### Nested Schema for `networks` diff --git a/docs/resources/loadbalancer.md b/docs/resources/loadbalancer.md index 87b33562..a92b9675 100644 --- a/docs/resources/loadbalancer.md +++ b/docs/resources/loadbalancer.md @@ -96,6 +96,9 @@ resource "stackit_loadbalancer" "example" { port = 80 protocol = "PROTOCOL_TCP" target_pool = "example-target-pool" + tcp = { + idle_timeout = "90s" + } } ] networks = [ @@ -258,6 +261,8 @@ Optional: - `display_name` (String) - `server_name_indicators` (Attributes List) A list of domain names to match in order to pass TLS traffic to the target pool in the current listener (see [below for nested schema](#nestedatt--listeners--server_name_indicators)) +- `tcp` (Attributes) Options that are specific to the TCP protocol. (see [below for nested schema](#nestedatt--listeners--tcp)) +- `udp` (Attributes) Options that are specific to the UDP protocol. (see [below for nested schema](#nestedatt--listeners--udp)) ### Nested Schema for `listeners.server_name_indicators` @@ -267,6 +272,22 @@ Optional: - `name` (String) A domain name to match in order to pass TLS traffic to the target pool in the current listener + +### Nested Schema for `listeners.tcp` + +Optional: + +- `idle_timeout` (String) Time after which an idle connection is closed. The default value is set to 300 seconds, and the maximum value is 3600 seconds. The format is a duration and the unit must be seconds. Example: 30s + + + +### Nested Schema for `listeners.udp` + +Optional: + +- `idle_timeout` (String) Time after which an idle session is closed. The default value is set to 1 minute, and the maximum value is 2 minutes. The format is a duration and the unit must be seconds. Example: 30s + + ### Nested Schema for `networks` diff --git a/examples/resources/stackit_loadbalancer/resource.tf b/examples/resources/stackit_loadbalancer/resource.tf index f476e29c..ac118c79 100644 --- a/examples/resources/stackit_loadbalancer/resource.tf +++ b/examples/resources/stackit_loadbalancer/resource.tf @@ -77,6 +77,9 @@ resource "stackit_loadbalancer" "example" { port = 80 protocol = "PROTOCOL_TCP" target_pool = "example-target-pool" + tcp = { + idle_timeout = "90s" + } } ] networks = [ diff --git a/stackit/internal/services/loadbalancer/loadbalancer/datasource.go b/stackit/internal/services/loadbalancer/loadbalancer/datasource.go index e51cc1e0..393ba022 100644 --- a/stackit/internal/services/loadbalancer/loadbalancer/datasource.go +++ b/stackit/internal/services/loadbalancer/loadbalancer/datasource.go @@ -107,6 +107,10 @@ func (r *loadBalancerDataSource) Schema(_ context.Context, _ datasource.SchemaRe "targets.display_name": "Target display name", "ip": "Target IP", "region": "The resource region. If not defined, the provider region is used.", + "tcp_options": "Options that are specific to the TCP protocol.", + "tcp_options_idle_timeout": "Time after which an idle connection is closed. The default value is set to 5 minutes, and the maximum value is one hour.", + "udp_options": "Options that are specific to the UDP protocol.", + "udp_options_idle_timeout": "Time after which an idle session is closed. The default value is set to 1 minute, and the maximum value is 2 minutes.", } resp.Schema = schema.Schema{ @@ -171,6 +175,26 @@ func (r *loadBalancerDataSource) Schema(_ context.Context, _ datasource.SchemaRe Description: descriptions["target_pool"], Computed: true, }, + "tcp": schema.SingleNestedAttribute{ + Description: descriptions["tcp_options"], + Computed: true, + Attributes: map[string]schema.Attribute{ + "idle_timeout": schema.StringAttribute{ + Description: descriptions["tcp_options_idle_timeout"], + Computed: true, + }, + }, + }, + "udp": schema.SingleNestedAttribute{ + Description: descriptions["udp_options"], + Computed: true, + Attributes: map[string]schema.Attribute{ + "idle_timeout": schema.StringAttribute{ + Description: descriptions["udp_options_idle_timeout"], + Computed: true, + }, + }, + }, }, }, }, diff --git a/stackit/internal/services/loadbalancer/loadbalancer/resource.go b/stackit/internal/services/loadbalancer/loadbalancer/resource.go index 69828670..e6aa031f 100644 --- a/stackit/internal/services/loadbalancer/loadbalancer/resource.go +++ b/stackit/internal/services/loadbalancer/loadbalancer/resource.go @@ -72,6 +72,8 @@ type listener struct { Protocol types.String `tfsdk:"protocol"` ServerNameIndicators types.List `tfsdk:"server_name_indicators"` TargetPool types.String `tfsdk:"target_pool"` + TCP types.Object `tfsdk:"tcp"` + UDP types.Object `tfsdk:"udp"` } // Types corresponding to listener @@ -81,6 +83,8 @@ var listenerTypes = map[string]attr.Type{ "protocol": types.StringType, "server_name_indicators": types.ListType{ElemType: types.ObjectType{AttrTypes: serverNameIndicatorTypes}}, "target_pool": types.StringType, + "tcp": types.ObjectType{AttrTypes: tcpTypes}, + "udp": types.ObjectType{AttrTypes: udpTypes}, } // Struct corresponding to listener.ServerNameIndicators[i] @@ -93,6 +97,22 @@ var serverNameIndicatorTypes = map[string]attr.Type{ "name": types.StringType, } +type tcp struct { + IdleTimeout types.String `tfsdk:"idle_timeout"` +} + +var tcpTypes = map[string]attr.Type{ + "idle_timeout": types.StringType, +} + +type udp struct { + IdleTimeout types.String `tfsdk:"idle_timeout"` +} + +var udpTypes = map[string]attr.Type{ + "idle_timeout": types.StringType, +} + // Struct corresponding to Model.Networks[i] type network struct { NetworkId types.String `tfsdk:"network_id"` @@ -345,6 +365,10 @@ func (r *loadBalancerResource) Schema(_ context.Context, _ resource.SchemaReques "ip": "Target IP", "region": "The resource region. If not defined, the provider region is used.", "security_group_id": "The ID of the egress security group assigned to the Load Balancer's internal machines. This ID is essential for allowing traffic from the Load Balancer to targets in different networks or STACKIT network areas (SNA). To enable this, create a security group rule for your target VMs and set the `remote_security_group_id` of that rule to this value. This is typically used when `disable_security_group_assignment` is set to `true`.", + "tcp_options": "Options that are specific to the TCP protocol.", + "tcp_options_idle_timeout": "Time after which an idle connection is closed. The default value is set to 300 seconds, and the maximum value is 3600 seconds. The format is a duration and the unit must be seconds. Example: 30s", + "udp_options": "Options that are specific to the UDP protocol.", + "udp_options_idle_timeout": "Time after which an idle session is closed. The default value is set to 1 minute, and the maximum value is 2 minutes. The format is a duration and the unit must be seconds. Example: 30s", } resp.Schema = schema.Schema{ @@ -456,6 +480,27 @@ The example below creates the supporting infrastructure using the STACKIT Terraf stringplanmodifier.UseStateForUnknown(), }, }, + "tcp": schema.SingleNestedAttribute{ + Description: descriptions["tcp_options"], + Optional: true, + Attributes: map[string]schema.Attribute{ + "idle_timeout": schema.StringAttribute{ + Description: descriptions["tcp_options_idle_timeout"], + Optional: true, + }, + }, + }, + "udp": schema.SingleNestedAttribute{ + Description: descriptions["udp_options"], + Optional: true, + Computed: false, + Attributes: map[string]schema.Attribute{ + "idle_timeout": schema.StringAttribute{ + Description: descriptions["udp_options_idle_timeout"], + Optional: true, + }, + }, + }, }, }, }, @@ -907,6 +952,7 @@ func (r *loadBalancerResource) ImportState(ctx context.Context, req resource.Imp tflog.Info(ctx, "Load balancer state imported") } +// toCreatePayload and all other toX functions in this file turn a Terraform load balancer model into a createLoadBalancerPayload to be used with the load balancer API. func toCreatePayload(ctx context.Context, model *Model) (*loadbalancer.CreateLoadBalancerPayload, error) { if model == nil { return nil, fmt.Errorf("nil model") @@ -963,12 +1009,22 @@ func toListenersPayload(ctx context.Context, model *Model) (*[]loadbalancer.List if err != nil { return nil, fmt.Errorf("converting index %d: converting server_name_indicator: %w", i, err) } + tcp, err := toTCP(ctx, &listenerModel) + if err != nil { + return nil, fmt.Errorf("converting index %d: converting tcp: %w", i, err) + } + udp, err := toUDP(ctx, &listenerModel) + if err != nil { + return nil, fmt.Errorf("converting index %d: converting udp: %w", i, err) + } payload = append(payload, loadbalancer.Listener{ DisplayName: conversion.StringValueToPointer(listenerModel.DisplayName), Port: conversion.Int64ValueToPointer(listenerModel.Port), Protocol: loadbalancer.ListenerGetProtocolAttributeType(conversion.StringValueToPointer(listenerModel.Protocol)), ServerNameIndicators: serverNameIndicatorsPayload, TargetPool: conversion.StringValueToPointer(listenerModel.TargetPool), + Tcp: tcp, + Udp: udp, }) } @@ -997,6 +1053,44 @@ func toServerNameIndicatorsPayload(ctx context.Context, l *listener) (*[]loadbal return &payload, nil } +func toTCP(ctx context.Context, listener *listener) (*loadbalancer.OptionsTCP, error) { + if listener.TCP.IsNull() || listener.TCP.IsUnknown() { + return nil, nil + } + + tcp := tcp{} + diags := listener.TCP.As(ctx, &tcp, basetypes.ObjectAsOptions{}) + if diags.HasError() { + return nil, core.DiagsToError(diags) + } + if tcp.IdleTimeout.IsNull() || tcp.IdleTimeout.IsUnknown() { + return nil, nil + } + + return &loadbalancer.OptionsTCP{ + IdleTimeout: tcp.IdleTimeout.ValueStringPointer(), + }, nil +} + +func toUDP(ctx context.Context, listener *listener) (*loadbalancer.OptionsUDP, error) { + if listener.UDP.IsNull() || listener.UDP.IsUnknown() { + return nil, nil + } + + udp := udp{} + diags := listener.UDP.As(ctx, &udp, basetypes.ObjectAsOptions{}) + if diags.HasError() { + return nil, core.DiagsToError(diags) + } + if udp.IdleTimeout.IsNull() || udp.IdleTimeout.IsUnknown() { + return nil, nil + } + + return &loadbalancer.OptionsUDP{ + IdleTimeout: udp.IdleTimeout.ValueStringPointer(), + }, nil +} + func toNetworksPayload(ctx context.Context, model *Model) (*[]loadbalancer.Network, error) { if model.Networks.IsNull() || model.Networks.IsUnknown() { return nil, nil @@ -1222,6 +1316,7 @@ func toTargetsPayload(ctx context.Context, tp *targetPool) (*[]loadbalancer.Targ return &payload, nil } +// mapFields and all other map functions in this file translate an API resource into a Terraform model. func mapFields(ctx context.Context, lb *loadbalancer.LoadBalancer, m *Model, region string) error { if lb == nil { return fmt.Errorf("response input is nil") @@ -1292,6 +1387,16 @@ func mapListeners(loadBalancerResp *loadbalancer.LoadBalancer, m *Model) error { return fmt.Errorf("mapping index %d, field serverNameIndicators: %w", i, err) } + err = mapTCP(listenerResp.Tcp, listenerMap) + if err != nil { + return fmt.Errorf("mapping index %d, field tcp: %w", i, err) + } + + err = mapUDP(listenerResp.Udp, listenerMap) + if err != nil { + return fmt.Errorf("mapping index %d, field udp: %w", i, err) + } + listenerTF, diags := types.ObjectValue(listenerTypes, listenerMap) if diags.HasError() { return fmt.Errorf("mapping index %d: %w", i, core.DiagsToError(diags)) @@ -1344,6 +1449,40 @@ func mapServerNameIndicators(serverNameIndicatorsResp *[]loadbalancer.ServerName return nil } +func mapTCP(tcp *loadbalancer.OptionsTCP, listener map[string]attr.Value) error { + if tcp == nil || tcp.IdleTimeout == nil || *tcp.IdleTimeout == "" { + listener["tcp"] = types.ObjectNull(tcpTypes) + return nil + } + + tcpAttr, diags := types.ObjectValue(tcpTypes, map[string]attr.Value{ + "idle_timeout": types.StringValue(*tcp.IdleTimeout), + }) + if diags.HasError() { + return core.DiagsToError(diags) + } + + listener["tcp"] = tcpAttr + return nil +} + +func mapUDP(udp *loadbalancer.OptionsUDP, listener map[string]attr.Value) error { + if udp == nil || udp.IdleTimeout == nil || *udp.IdleTimeout == "" { + listener["udp"] = types.ObjectNull(udpTypes) + return nil + } + + udpAttr, diags := types.ObjectValue(udpTypes, map[string]attr.Value{ + "idle_timeout": types.StringValue(*udp.IdleTimeout), + }) + if diags.HasError() { + return core.DiagsToError(diags) + } + + listener["udp"] = udpAttr + return nil +} + func mapNetworks(loadBalancerResp *loadbalancer.LoadBalancer, m *Model) error { if loadBalancerResp.Networks == nil { m.Networks = types.ListNull(types.ObjectType{AttrTypes: networkTypes}) diff --git a/stackit/internal/services/loadbalancer/loadbalancer/resource_test.go b/stackit/internal/services/loadbalancer/loadbalancer/resource_test.go index 810e9c39..831ae1f4 100644 --- a/stackit/internal/services/loadbalancer/loadbalancer/resource_test.go +++ b/stackit/internal/services/loadbalancer/loadbalancer/resource_test.go @@ -62,6 +62,12 @@ func TestToCreatePayload(t *testing.T) { }, ), "target_pool": types.StringValue("target_pool"), + "tcp": types.ObjectValueMust(tcpTypes, map[string]attr.Value{ + "idle_timeout": types.StringValue("50s"), + }), + "udp": types.ObjectValueMust(udpTypes, map[string]attr.Value{ + "idle_timeout": types.StringValue("50s"), + }), }), }), Name: types.StringValue("name"), @@ -130,6 +136,12 @@ func TestToCreatePayload(t *testing.T) { }, }, TargetPool: utils.Ptr("target_pool"), + Tcp: &loadbalancer.OptionsTCP{ + IdleTimeout: utils.Ptr("50s"), + }, + Udp: &loadbalancer.OptionsUDP{ + IdleTimeout: utils.Ptr("50s"), + }, }, }, Name: utils.Ptr("name"), @@ -204,6 +216,8 @@ func TestToCreatePayload(t *testing.T) { }, ), "target_pool": types.StringValue("target_pool"), + "tcp": types.ObjectNull(tcpTypes), + "udp": types.ObjectNull(udpTypes), }), }), Name: types.StringValue("name"), @@ -511,6 +525,12 @@ func TestMapFields(t *testing.T) { }, }, TargetPool: utils.Ptr("target_pool"), + Tcp: &loadbalancer.OptionsTCP{ + IdleTimeout: utils.Ptr("50s"), + }, + Udp: &loadbalancer.OptionsUDP{ + IdleTimeout: utils.Ptr("50s"), + }, }, }), Name: utils.Ptr("name"), @@ -586,6 +606,12 @@ func TestMapFields(t *testing.T) { }, ), "target_pool": types.StringValue("target_pool"), + "tcp": types.ObjectValueMust(tcpTypes, map[string]attr.Value{ + "idle_timeout": types.StringValue("50s"), + }), + "udp": types.ObjectValueMust(udpTypes, map[string]attr.Value{ + "idle_timeout": types.StringValue("50s"), + }), }), }), Name: types.StringValue("name"), @@ -730,6 +756,8 @@ func TestMapFields(t *testing.T) { }, ), "target_pool": types.StringValue("target_pool"), + "tcp": types.ObjectNull(tcpTypes), + "udp": types.ObjectNull(udpTypes), }), }), Name: types.StringValue("name"), diff --git a/stackit/internal/services/loadbalancer/loadbalancer_acc_test.go b/stackit/internal/services/loadbalancer/loadbalancer_acc_test.go index fe96460c..90905338 100644 --- a/stackit/internal/services/loadbalancer/loadbalancer_acc_test.go +++ b/stackit/internal/services/loadbalancer/loadbalancer_acc_test.go @@ -52,25 +52,35 @@ var testConfigVarsMax = config.Variables{ "plan_id": config.StringVariable("p10"), "disable_security_group_assignment": config.BoolVariable(true), "network_name": config.StringVariable(fmt.Sprintf("tf-acc-n%s", acctest.RandStringFromCharSet(7, acctest.CharSetAlphaNum))), + "network_role": config.StringVariable("ROLE_LISTENERS_AND_TARGETS"), "server_name": config.StringVariable(fmt.Sprintf("tf-acc-s%s", acctest.RandStringFromCharSet(7, acctest.CharSetAlphaNum))), "loadbalancer_name": config.StringVariable(fmt.Sprintf("tf-acc-l%s", acctest.RandStringFromCharSet(7, acctest.CharSetAlphaNum))), - "target_pool_name": config.StringVariable("example-target-pool"), - "target_port": config.StringVariable("5432"), - "target_display_name": config.StringVariable("example-target"), - "listener_port": config.StringVariable("5432"), - "listener_protocol": config.StringVariable("PROTOCOL_TLS_PASSTHROUGH"), - "network_role": config.StringVariable("ROLE_LISTENERS_AND_TARGETS"), - "listener_display_name": config.StringVariable("example-listener"), - "listener_server_name_indicators": config.StringVariable("acc-test.runs.onstackit.cloud"), - "healthy_threshold": config.StringVariable("3"), - "health_interval": config.StringVariable("10s"), - "health_interval_jitter": config.StringVariable("5s"), - "health_timeout": config.StringVariable("10s"), - "unhealthy_threshold": config.StringVariable("3"), - "use_source_ip_address": config.StringVariable("true"), - "private_network_only": config.StringVariable("false"), - "acl": config.StringVariable("192.168.0.0/24"), + "target_display_name": config.StringVariable("example-target"), + + "sni_target_pool_name": config.StringVariable("example-target-pool"), + "sni_target_port": config.StringVariable("5432"), + "sni_listener_port": config.StringVariable("5432"), + "sni_listener_protocol": config.StringVariable("PROTOCOL_TLS_PASSTHROUGH"), + "sni_idle_timeout": config.StringVariable("42s"), + "sni_listener_display_name": config.StringVariable("example-listener"), + "sni_listener_server_name_indicators": config.StringVariable("acc-test.runs.onstackit.cloud"), + "sni_healthy_threshold": config.StringVariable("3"), + "sni_health_interval": config.StringVariable("10s"), + "sni_health_interval_jitter": config.StringVariable("5s"), + "sni_health_timeout": config.StringVariable("10s"), + "sni_unhealthy_threshold": config.StringVariable("3"), + "sni_use_source_ip_address": config.StringVariable("true"), + + "udp_target_pool_name": config.StringVariable("udp-target-pool"), + "udp_target_port": config.StringVariable("53"), + "udp_listener_port": config.StringVariable("53"), + "udp_listener_protocol": config.StringVariable("PROTOCOL_UDP"), + "udp_idle_timeout": config.StringVariable("43s"), + "udp_listener_display_name": config.StringVariable("udp-listener"), + + "private_network_only": config.StringVariable("false"), + "acl": config.StringVariable("192.168.0.0/24"), "observability_logs_push_url": config.StringVariable("https://logs.observability.dummy.stackit.cloud"), "observability_metrics_push_url": config.StringVariable("https://metrics.observability.dummy.stackit.cloud"), @@ -92,7 +102,7 @@ func configVarsMinUpdated() config.Variables { func configVarsMaxUpdated() config.Variables { tempConfig := make(config.Variables, len(testConfigVarsMax)) maps.Copy(tempConfig, testConfigVarsMax) - tempConfig["target_port"] = config.StringVariable("5431") + tempConfig["sni_target_port"] = config.StringVariable("5431") return tempConfig } @@ -235,27 +245,39 @@ func TestAccLoadBalancerResourceMax(t *testing.T) { resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "project_id", testutil.ConvertConfigVariable(testConfigVarsMax["project_id"])), resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "name", testutil.ConvertConfigVariable(testConfigVarsMax["loadbalancer_name"])), resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "plan_id", testutil.ConvertConfigVariable(testConfigVarsMax["plan_id"])), - resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "target_pools.0.name", testutil.ConvertConfigVariable(testConfigVarsMax["target_pool_name"])), - resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "target_pools.0.target_port", testutil.ConvertConfigVariable(testConfigVarsMax["target_port"])), - resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "target_pools.0.targets.0.display_name", testutil.ConvertConfigVariable(testConfigVarsMax["target_display_name"])), - resource.TestCheckResourceAttrSet("stackit_loadbalancer.loadbalancer", "target_pools.0.targets.0.ip"), - resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "listeners.0.display_name", testutil.ConvertConfigVariable(testConfigVarsMax["listener_display_name"])), - resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "listeners.0.port", testutil.ConvertConfigVariable(testConfigVarsMax["listener_port"])), - resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "listeners.0.protocol", testutil.ConvertConfigVariable(testConfigVarsMax["listener_protocol"])), - resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "listeners.0.target_pool", testutil.ConvertConfigVariable(testConfigVarsMax["target_pool_name"])), - resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "listeners.0.server_name_indicators.0.name", testutil.ConvertConfigVariable(testConfigVarsMax["listener_server_name_indicators"])), resource.TestCheckResourceAttrSet("stackit_loadbalancer.loadbalancer", "networks.0.network_id"), resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "networks.0.role", testutil.ConvertConfigVariable(testConfigVarsMax["network_role"])), resource.TestCheckResourceAttrSet("stackit_loadbalancer.loadbalancer", "external_address"), resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "disable_security_group_assignment", testutil.ConvertConfigVariable(testConfigVarsMax["disable_security_group_assignment"])), resource.TestCheckResourceAttrSet("stackit_loadbalancer.loadbalancer", "security_group_id"), - resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "target_pools.0.active_health_check.healthy_threshold", testutil.ConvertConfigVariable(testConfigVarsMax["healthy_threshold"])), - resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "target_pools.0.active_health_check.interval", testutil.ConvertConfigVariable(testConfigVarsMax["health_interval"])), - resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "target_pools.0.active_health_check.interval_jitter", testutil.ConvertConfigVariable(testConfigVarsMax["health_interval_jitter"])), - resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "target_pools.0.active_health_check.timeout", testutil.ConvertConfigVariable(testConfigVarsMax["health_timeout"])), - resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "target_pools.0.active_health_check.unhealthy_threshold", testutil.ConvertConfigVariable(testConfigVarsMax["unhealthy_threshold"])), - resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "target_pools.0.session_persistence.use_source_ip_address", testutil.ConvertConfigVariable(testConfigVarsMax["use_source_ip_address"])), + resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "listeners.0.display_name", testutil.ConvertConfigVariable(testConfigVarsMax["sni_listener_display_name"])), + resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "listeners.0.port", testutil.ConvertConfigVariable(testConfigVarsMax["sni_listener_port"])), + resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "listeners.0.protocol", testutil.ConvertConfigVariable(testConfigVarsMax["sni_listener_protocol"])), + resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "listeners.0.target_pool", testutil.ConvertConfigVariable(testConfigVarsMax["sni_target_pool_name"])), + resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "listeners.0.server_name_indicators.0.name", testutil.ConvertConfigVariable(testConfigVarsMax["sni_listener_server_name_indicators"])), + resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "listeners.0.tcp.idle_timeout", testutil.ConvertConfigVariable(testConfigVarsMax["sni_idle_timeout"])), + resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "target_pools.0.name", testutil.ConvertConfigVariable(testConfigVarsMax["sni_target_pool_name"])), + resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "target_pools.0.target_port", testutil.ConvertConfigVariable(testConfigVarsMax["sni_target_port"])), + resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "target_pools.0.targets.0.display_name", testutil.ConvertConfigVariable(testConfigVarsMax["target_display_name"])), + resource.TestCheckResourceAttrSet("stackit_loadbalancer.loadbalancer", "target_pools.0.targets.0.ip"), + resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "target_pools.0.active_health_check.healthy_threshold", testutil.ConvertConfigVariable(testConfigVarsMax["sni_healthy_threshold"])), + resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "target_pools.0.active_health_check.interval", testutil.ConvertConfigVariable(testConfigVarsMax["sni_health_interval"])), + resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "target_pools.0.active_health_check.interval_jitter", testutil.ConvertConfigVariable(testConfigVarsMax["sni_health_interval_jitter"])), + resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "target_pools.0.active_health_check.timeout", testutil.ConvertConfigVariable(testConfigVarsMax["sni_health_timeout"])), + resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "target_pools.0.active_health_check.unhealthy_threshold", testutil.ConvertConfigVariable(testConfigVarsMax["sni_unhealthy_threshold"])), + + resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "listeners.1.display_name", testutil.ConvertConfigVariable(testConfigVarsMax["udp_listener_display_name"])), + resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "listeners.1.port", testutil.ConvertConfigVariable(testConfigVarsMax["udp_listener_port"])), + resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "listeners.1.protocol", testutil.ConvertConfigVariable(testConfigVarsMax["udp_listener_protocol"])), + resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "listeners.1.target_pool", testutil.ConvertConfigVariable(testConfigVarsMax["udp_target_pool_name"])), + resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "listeners.1.udp.idle_timeout", testutil.ConvertConfigVariable(testConfigVarsMax["udp_idle_timeout"])), + resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "target_pools.1.name", testutil.ConvertConfigVariable(testConfigVarsMax["udp_target_pool_name"])), + resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "target_pools.1.target_port", testutil.ConvertConfigVariable(testConfigVarsMax["udp_target_port"])), + resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "target_pools.1.targets.0.display_name", testutil.ConvertConfigVariable(testConfigVarsMax["target_display_name"])), + resource.TestCheckResourceAttrSet("stackit_loadbalancer.loadbalancer", "target_pools.1.targets.0.ip"), + + resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "target_pools.0.session_persistence.use_source_ip_address", testutil.ConvertConfigVariable(testConfigVarsMax["sni_use_source_ip_address"])), resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "options.private_network_only", testutil.ConvertConfigVariable(testConfigVarsMax["private_network_only"])), resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "options.acl.0", testutil.ConvertConfigVariable(testConfigVarsMax["acl"])), @@ -306,26 +328,35 @@ func TestAccLoadBalancerResourceMax(t *testing.T) { "stackit_loadbalancer.loadbalancer", "name", ), // Load balancer instance - resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "target_pools.0.name", testutil.ConvertConfigVariable(testConfigVarsMax["target_pool_name"])), - resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "target_pools.0.target_port", testutil.ConvertConfigVariable(testConfigVarsMax["target_port"])), - resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "target_pools.0.targets.0.display_name", testutil.ConvertConfigVariable(testConfigVarsMax["target_display_name"])), - resource.TestCheckResourceAttrSet("data.stackit_loadbalancer.loadbalancer", "target_pools.0.targets.0.ip"), - resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "listeners.0.display_name", testutil.ConvertConfigVariable(testConfigVarsMax["listener_display_name"])), - resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "listeners.0.port", testutil.ConvertConfigVariable(testConfigVarsMax["listener_port"])), - resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "listeners.0.protocol", testutil.ConvertConfigVariable(testConfigVarsMax["listener_protocol"])), - resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "listeners.0.target_pool", testutil.ConvertConfigVariable(testConfigVarsMax["target_pool_name"])), - resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "listeners.0.server_name_indicators.0.name", testutil.ConvertConfigVariable(testConfigVarsMax["listener_server_name_indicators"])), resource.TestCheckResourceAttrSet("data.stackit_loadbalancer.loadbalancer", "networks.0.network_id"), resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "networks.0.role", testutil.ConvertConfigVariable(testConfigVarsMax["network_role"])), resource.TestCheckResourceAttrSet("data.stackit_loadbalancer.loadbalancer", "external_address"), resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "disable_security_group_assignment", testutil.ConvertConfigVariable(testConfigVarsMax["disable_security_group_assignment"])), resource.TestCheckResourceAttrSet("stackit_loadbalancer.loadbalancer", "security_group_id"), - resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "target_pools.0.active_health_check.healthy_threshold", testutil.ConvertConfigVariable(testConfigVarsMax["healthy_threshold"])), - resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "target_pools.0.active_health_check.interval", testutil.ConvertConfigVariable(testConfigVarsMax["health_interval"])), - resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "target_pools.0.active_health_check.interval_jitter", testutil.ConvertConfigVariable(testConfigVarsMax["health_interval_jitter"])), - resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "target_pools.0.active_health_check.timeout", testutil.ConvertConfigVariable(testConfigVarsMax["health_timeout"])), - resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "target_pools.0.active_health_check.unhealthy_threshold", testutil.ConvertConfigVariable(testConfigVarsMax["unhealthy_threshold"])), - resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "target_pools.0.session_persistence.use_source_ip_address", testutil.ConvertConfigVariable(testConfigVarsMax["use_source_ip_address"])), + + resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "target_pools.0.name", testutil.ConvertConfigVariable(testConfigVarsMax["sni_target_pool_name"])), + resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "target_pools.0.target_port", testutil.ConvertConfigVariable(testConfigVarsMax["sni_target_port"])), + resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "target_pools.0.targets.0.display_name", testutil.ConvertConfigVariable(testConfigVarsMax["target_display_name"])), + resource.TestCheckResourceAttrSet("data.stackit_loadbalancer.loadbalancer", "target_pools.0.targets.0.ip"), + resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "listeners.0.display_name", testutil.ConvertConfigVariable(testConfigVarsMax["sni_listener_display_name"])), + resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "listeners.0.port", testutil.ConvertConfigVariable(testConfigVarsMax["sni_listener_port"])), + resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "listeners.0.protocol", testutil.ConvertConfigVariable(testConfigVarsMax["sni_listener_protocol"])), + resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "listeners.0.target_pool", testutil.ConvertConfigVariable(testConfigVarsMax["sni_target_pool_name"])), + resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "listeners.0.server_name_indicators.0.name", testutil.ConvertConfigVariable(testConfigVarsMax["sni_listener_server_name_indicators"])), + resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "listeners.0.tcp.idle_timeout", testutil.ConvertConfigVariable(testConfigVarsMax["sni_idle_timeout"])), + resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "target_pools.0.active_health_check.healthy_threshold", testutil.ConvertConfigVariable(testConfigVarsMax["sni_healthy_threshold"])), + resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "target_pools.0.active_health_check.interval", testutil.ConvertConfigVariable(testConfigVarsMax["sni_health_interval"])), + resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "target_pools.0.active_health_check.interval_jitter", testutil.ConvertConfigVariable(testConfigVarsMax["sni_health_interval_jitter"])), + resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "target_pools.0.active_health_check.timeout", testutil.ConvertConfigVariable(testConfigVarsMax["sni_health_timeout"])), + resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "target_pools.0.active_health_check.unhealthy_threshold", testutil.ConvertConfigVariable(testConfigVarsMax["sni_unhealthy_threshold"])), + resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "target_pools.0.session_persistence.use_source_ip_address", testutil.ConvertConfigVariable(testConfigVarsMax["sni_use_source_ip_address"])), + + resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "listeners.1.display_name", testutil.ConvertConfigVariable(testConfigVarsMax["udp_listener_display_name"])), + resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "listeners.1.port", testutil.ConvertConfigVariable(testConfigVarsMax["udp_listener_port"])), + resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "listeners.1.protocol", testutil.ConvertConfigVariable(testConfigVarsMax["udp_listener_protocol"])), + resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "listeners.1.target_pool", testutil.ConvertConfigVariable(testConfigVarsMax["udp_target_pool_name"])), + resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "listeners.1.udp.idle_timeout", testutil.ConvertConfigVariable(testConfigVarsMax["udp_idle_timeout"])), + resource.TestCheckResourceAttr("data.stackit_loadbalancer.loadbalancer", "options.acl.0", testutil.ConvertConfigVariable(testConfigVarsMax["acl"])), resource.TestCheckResourceAttrSet("data.stackit_loadbalancer.loadbalancer", "options.observability.logs.credentials_ref"), resource.TestCheckResourceAttrPair("stackit_loadbalancer_observability_credential.logs", "credentials_ref", "data.stackit_loadbalancer.loadbalancer", "options.observability.logs.credentials_ref"), @@ -368,7 +399,7 @@ func TestAccLoadBalancerResourceMax(t *testing.T) { Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "project_id", testutil.ConvertConfigVariable(testConfigVarsMax["project_id"])), resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "name", testutil.ConvertConfigVariable(testConfigVarsMax["loadbalancer_name"])), - resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "target_pools.0.target_port", testutil.ConvertConfigVariable(configVarsMaxUpdated()["target_port"])), + resource.TestCheckResourceAttr("stackit_loadbalancer.loadbalancer", "target_pools.0.target_port", testutil.ConvertConfigVariable(configVarsMaxUpdated()["sni_target_port"])), ), }, // Deletion is done by the framework implicitly diff --git a/stackit/internal/services/loadbalancer/testfiles/resource-max.tf b/stackit/internal/services/loadbalancer/testfiles/resource-max.tf index 11691e8e..d870cb62 100644 --- a/stackit/internal/services/loadbalancer/testfiles/resource-max.tf +++ b/stackit/internal/services/loadbalancer/testfiles/resource-max.tf @@ -1,26 +1,36 @@ variable "project_id" {} variable "network_name" {} +variable "network_role" {} variable "server_name" {} variable "loadbalancer_name" {} variable "plan_id" {} -variable "target_pool_name" {} -variable "target_port" {} -variable "target_display_name" {} -variable "listener_port" {} -variable "listener_protocol" {} -variable "network_role" {} variable "disable_security_group_assignment" {} -variable "listener_display_name" {} -variable "listener_server_name_indicators" {} -variable "healthy_threshold" {} -variable "health_interval" {} -variable "health_interval_jitter" {} -variable "health_timeout" {} -variable "unhealthy_threshold" {} -variable "use_source_ip_address" {} +variable "target_display_name" {} + +variable "sni_target_pool_name" {} +variable "sni_target_port" {} +variable "sni_listener_port" {} +variable "sni_listener_protocol" {} +variable "sni_idle_timeout" {} +variable "sni_listener_display_name" {} +variable "sni_listener_server_name_indicators" {} +variable "sni_healthy_threshold" {} +variable "sni_health_interval" {} +variable "sni_health_interval_jitter" {} +variable "sni_health_timeout" {} +variable "sni_unhealthy_threshold" {} +variable "sni_use_source_ip_address" {} + +variable "udp_target_pool_name" {} +variable "udp_target_port" {} +variable "udp_listener_port" {} +variable "udp_listener_protocol" {} +variable "udp_idle_timeout" {} +variable "udp_listener_display_name" {} + variable "private_network_only" {} variable "acl" {} @@ -84,8 +94,8 @@ resource "stackit_loadbalancer" "loadbalancer" { disable_security_group_assignment = var.disable_security_group_assignment target_pools = [ { - name = var.target_pool_name - target_port = var.target_port + name = var.sni_target_pool_name + target_port = var.sni_target_port targets = [ { display_name = var.target_display_name @@ -93,28 +103,50 @@ resource "stackit_loadbalancer" "loadbalancer" { } ] active_health_check = { - healthy_threshold = var.healthy_threshold - interval = var.health_interval - interval_jitter = var.health_interval_jitter - timeout = var.health_timeout - unhealthy_threshold = var.unhealthy_threshold + healthy_threshold = var.sni_healthy_threshold + interval = var.sni_health_interval + interval_jitter = var.sni_health_interval_jitter + timeout = var.sni_health_timeout + unhealthy_threshold = var.sni_unhealthy_threshold } session_persistence = { - use_source_ip_address = var.use_source_ip_address + use_source_ip_address = var.sni_use_source_ip_address } + }, + { + name = var.udp_target_pool_name + target_port = var.udp_target_port + targets = [ + { + display_name = var.target_display_name + ip = stackit_network_interface.network_interface.ipv4 + } + ] } ] listeners = [ { - display_name = var.listener_display_name - port = var.listener_port - protocol = var.listener_protocol - target_pool = var.target_pool_name + display_name = var.sni_listener_display_name + port = var.sni_listener_port + protocol = var.sni_listener_protocol + target_pool = var.sni_target_pool_name server_name_indicators = [ { - name = var.listener_server_name_indicators + name = var.sni_listener_server_name_indicators } ] + tcp = { + idle_timeout = var.sni_idle_timeout + } + }, + { + display_name = var.udp_listener_display_name + port = var.udp_listener_port + protocol = var.udp_listener_protocol + target_pool = var.udp_target_pool_name + udp = { + idle_timeout = var.udp_idle_timeout + } } ] networks = [