Skip to content

Commit

Permalink
Add support for LB adaptive_routing, location_strategy, random_steeri…
Browse files Browse the repository at this point in the history
…ng, and zero_downtime_failover
  • Loading branch information
tc80 committed Sep 30, 2022
1 parent 84ea595 commit 0dfdeec
Show file tree
Hide file tree
Showing 6 changed files with 472 additions and 3 deletions.
3 changes: 3 additions & 0 deletions .changelog/1941.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
resource/cloudflare_load_balancer: Add support for adaptive_routing, location_strategy, random_steering, and zero_downtime_failover
```
21 changes: 21 additions & 0 deletions docs/resources/load_balancer.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ The following arguments are supported:
- `session_affinity` - (Optional) Associates all requests coming from an end-user with a single origin. Cloudflare will set a cookie on the initial response to the client, such that consequent requests with the cookie in the request will go to the same origin, so long as it is available. Valid values are: `""`, `"none"`, `"cookie"`, and `"ip_cookie"`. Default is `""`.
- `session_affinity_ttl` - (Optional) Time, in seconds, until this load balancers session affinity cookie expires after being created. This parameter is ignored unless a supported session affinity policy is set. The current default of 23 hours will be used unless `session_affinity_ttl` is explicitly set. Once the expiry time has been reached, subsequent requests may get sent to a different origin server. Valid values are between 1800 and 604800.
- `session_affinity_attributes` - (Optional) Configure cookie attributes for session affinity cookie. See the field documentation below.
- `adaptive_routing` - (Optional) Controls features that modify the routing of requests to pools and origins in response to dynamic conditions, such as during the interval between active health monitoring requests. See the field documentation below.
- `location_strategy` - (Optional) Controls location-based steering for non-proxied requests. See the field documentation below.
- `random_steering` - (Optional) Configures pool weights for random steering. When the `steering_policy="random"`, a random pool is selected with probability proportional to these pool weights. See the field documentation below.
- `rules` - (Optional) A list of conditions and overrides for each load balancer operation. See the field documentation below.

**region_pools** requires the following:
Expand All @@ -101,6 +104,21 @@ The following arguments are supported:
- `samesite` - (Optional) Configures the SameSite attribute on session affinity cookie. Value "Auto" will be translated to "Lax" or "None" depending if Always Use HTTPS is enabled. Note: when using value "None", the secure attribute can not be set to "Never". Valid values: `"Auto"`, `"Lax"`, `"None"` or `"Strict"`.
- `secure` - (Optional) Configures the Secure attribute on session affinity cookie. Value "Always" indicates the Secure attribute will be set in the Set-Cookie header, "Never" indicates the Secure attribute will not be set, and "Auto" will set the Secure attribute depending if Always Use HTTPS is enabled. Valid values: `"Auto"`, `"Always"` or `"Never"`.
- `drain_duration` - (Optional) Configures the drain duration in seconds. This field is only used when session affinity is enabled on the load balancer.
- `zero_downtime_failover` - (Optional) Configures the zero-downtime failover between origins within a pool when session affinity is enabled. Value "none" means no failover takes place for sessions pinned to the origin. Value "temporary" means traffic will be sent to another other healthy origin until the originally pinned origin is available; note that this can potentially result in heavy origin flapping. Value "sticky" means the session affinity cookie is updated and subsequent requests are sent to the new origin. This feature is currently incompatible with Argo, Tiered Cache, and Bandwidth Alliance. Valid values: `"none"`, `"temporary"` or `"sticky"`. Default is `"none"`.

**adaptive_routing** optionally as the following:

- `failover_across_pools` - (Optional) Extends zero-downtime failover of requests to healthy origins from alternate pools, when no healthy alternate exists in the same pool, according to the failover order defined by traffic and origin steering. When set `false` (the default) zero-downtime failover will only occur between origins within the same pool.

**location_strategy** optionally as the following:

- `prefer_ecs` - (Optional) Whether the EDNS Client Subnet (ECS) GeoIP should be preferred as the authoritative location. Value "always" will always prefer ECS, "never" will never prefer ECS, "proximity" will prefer ECS only when `steering_policy="proximity"`, and "geo" will prefer ECS only when `steering_policy="geo"`. Valid values: `"always"`, `"never"`, `"proximity"`, `"geo"` or `""` for the default (`"proximity"`).
- `mode` - (Optional) Determines the authoritative location when ECS is not preferred, does not exist in the request, or its GeoIP lookup is unsuccessful. Value "pop" will use the Cloudflare PoP location. Value "resolver_ip" will use the DNS resolver GeoIP location. If the GeoIP lookup is unsuccessful, it will use the Cloudflare PoP location. Valid values: `"pop"`, `"resolver_ip"`, or `""` for the default (`"pop"`).

**random_steering** optionally as the following:

- `pool_weights` - (Optional) A mapping of pool IDs to custom weights. The weight is relative to other pools in the load balancer.
- `default_weight` - (Optional) The default weight for pools in the load balancer that are not specified in the `pool_weights` map.

**rules** optionally as the following:

Expand All @@ -117,6 +135,9 @@ The following arguments are supported:
- `session_affinity` - (Optional) See field above.
- `session_affinity_ttl` - (Optional) See field above.
- `session_affinity_attributes` - (Optional) See field above.
- `adaptive_routing` - (Optional) See field above.
- `location_strategy` - (Optional) See field above.
- `random_steering` - (Optional) See field above.
- `ttl` - (Optional) See field above.
- `steering_policy` - (Optional) See field above.
- `fallback_pool` - (Optional) See fallback_pool_id above.
Expand Down
248 changes: 245 additions & 3 deletions internal/provider/resource_cloudflare_load_balancer.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,24 @@ var rulesElem = &schema.Resource{
},
},

"adaptive_routing": {
Type: schema.TypeSet,
Optional: true,
Elem: adaptiveRoutingElem,
},

"location_strategy": {
Type: schema.TypeSet,
Optional: true,
Elem: locationStrategyElem,
},

"random_steering": {
Type: schema.TypeSet,
Optional: true,
Elem: randomSteeringElem,
},

"ttl": {
Type: schema.TypeInt,
Optional: true,
Expand Down Expand Up @@ -175,6 +193,50 @@ var rulesElem = &schema.Resource{
},
}

var adaptiveRoutingElem = &schema.Resource{
Schema: map[string]*schema.Schema{
"failover_across_pools": {
Type: schema.TypeBool,
Optional: true,
},
},
}

var locationStrategyElem = &schema.Resource{
Schema: map[string]*schema.Schema{
"prefer_ecs": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringInSlice([]string{"", "always", "never", "proximity", "geo"}, false),
},

"mode": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringInSlice([]string{"", "pop", "resolver_ip"}, false),
},
},
}

var randomSteeringElem = &schema.Resource{
Schema: map[string]*schema.Schema{
"pool_weights": {
Type: schema.TypeMap,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeFloat,
ValidateFunc: validation.FloatBetween(0, 1),
},
},

"default_weight": {
Type: schema.TypeFloat,
Optional: true,
ValidateFunc: validation.FloatBetween(0, 1),
},
},
}

var popPoolElem = &schema.Resource{
Schema: map[string]*schema.Schema{
"pop": {
Expand Down Expand Up @@ -299,6 +361,18 @@ func resourceCloudflareLoadBalancerCreate(ctx context.Context, d *schema.Resourc
newLoadBalancer.SessionAffinityAttributes = sessionAffinityAttributes
}

if adaptiveRouting, ok := d.GetOk("adaptive_routing"); ok {
newLoadBalancer.AdaptiveRouting = expandAdaptiveRouting(adaptiveRouting)
}

if locationStrategy, ok := d.GetOk("location_strategy"); ok {
newLoadBalancer.LocationStrategy = expandLocationStrategy(locationStrategy)
}

if randomSteering, ok := d.GetOk("random_steering"); ok {
newLoadBalancer.RandomSteering = expandRandomSteering(randomSteering)
}

if rules, ok := d.GetOk("rules"); ok {
v, err := expandRules(rules)
if err != nil {
Expand Down Expand Up @@ -382,6 +456,18 @@ func resourceCloudflareLoadBalancerUpdate(ctx context.Context, d *schema.Resourc
loadBalancer.SessionAffinityAttributes = sessionAffinityAttributes
}

if adaptiveRouting, ok := d.GetOk("adaptive_routing"); ok {
loadBalancer.AdaptiveRouting = expandAdaptiveRouting(adaptiveRouting)
}

if locationStrategy, ok := d.GetOk("location_strategy"); ok {
loadBalancer.LocationStrategy = expandLocationStrategy(locationStrategy)
}

if randomSteering, ok := d.GetOk("random_steering"); ok {
loadBalancer.RandomSteering = expandRandomSteering(randomSteering)
}

if rules, ok := d.GetOk("rules"); ok {
v, err := expandRules(rules)
if err != nil {
Expand Down Expand Up @@ -451,6 +537,24 @@ func resourceCloudflareLoadBalancerRead(ctx context.Context, d *schema.ResourceD
}
}

if _, adaptiveRoutingOk := d.GetOk("adaptive_routing"); adaptiveRoutingOk {
if err := d.Set("adaptive_routing", flattenAdaptiveRouting(loadBalancer.AdaptiveRouting)); err != nil {
return diag.FromErr(fmt.Errorf("failed to set adaptive_routing: %w", err))
}
}

if _, locationStrategyOk := d.GetOk("location_strategy"); locationStrategyOk {
if err := d.Set("location_strategy", flattenLocationStrategy(loadBalancer.LocationStrategy)); err != nil {
return diag.FromErr(fmt.Errorf("failed to set location_strategy: %w", err))
}
}

if _, randomSteeringOk := d.GetOk("random_steering"); randomSteeringOk {
if err := d.Set("random_steering", flattenRandomSteering(loadBalancer.RandomSteering)); err != nil {
return diag.FromErr(fmt.Errorf("failed to set random_steering: %w", err))
}
}

if len(loadBalancer.Rules) > 0 {
fr, err := flattenRules(d, loadBalancer.Rules)
if err != nil {
Expand Down Expand Up @@ -498,9 +602,30 @@ func flattenGeoPools(pools map[string][]string, geoType string) *schema.Set {

func flattenSessionAffinityAttrs(attrs *cloudflare.SessionAffinityAttributes) map[string]interface{} {
return map[string]interface{}{
"drain_duration": strconv.Itoa(attrs.DrainDuration),
"samesite": attrs.SameSite,
"secure": attrs.Secure,
"drain_duration": strconv.Itoa(attrs.DrainDuration),
"samesite": attrs.SameSite,
"secure": attrs.Secure,
"zero_downtime_failover": attrs.ZeroDowntimeFailover,
}
}

func flattenAdaptiveRouting(properties *cloudflare.AdaptiveRouting) map[string]interface{} {
return map[string]interface{}{
"failover_across_pools": properties.FailoverAcrossPools,
}
}

func flattenLocationStrategy(properties *cloudflare.LocationStrategy) map[string]interface{} {
return map[string]interface{}{
"prefer_ecs": properties.PreferECS,
"mode": properties.Mode,
}
}

func flattenRandomSteering(properties *cloudflare.RandomSteering) map[string]interface{} {
return map[string]interface{}{
"pool_weights": properties.PoolWeights,
"default_weight": properties.DefaultWeight,
}
}

Expand Down Expand Up @@ -629,6 +754,39 @@ func flattenRules(d *schema.ResourceData, rules []*cloudflare.LoadBalancerRule)
if _, ok := d.GetOkExists(fmt.Sprintf("rules.%d.overrides.0.session_affinity_attributes.secure", idx)); ok {
saa["secure"] = o.SessionAffinityAttrs.Secure
}
if _, ok := d.GetOkExists(fmt.Sprintf("rules.%d.overrides.0.session_affinity_attributes.zero_downtime_failover", idx)); ok {
saa["zero_downtime_failover"] = o.SessionAffinityAttrs.ZeroDowntimeFailover
}
}
if _, ok := d.GetOkExists(fmt.Sprintf("rules.%d.overrides.0.adaptive_routing", idx)); o.AdaptiveRouting != nil && ok {
ar := map[string]interface{}{}
om["adaptive_routing"] = ar
m["overrides"] = []interface{}{om}
if _, ok := d.GetOkExists(fmt.Sprintf("rules.%d.overrides.0.adaptive_routing.failover_across_pools", idx)); ok {
ar["failover_across_pools"] = o.AdaptiveRouting.FailoverAcrossPools
}
}
if _, ok := d.GetOkExists(fmt.Sprintf("rules.%d.overrides.0.location_strategy", idx)); o.LocationStrategy != nil && ok {
ls := map[string]interface{}{}
om["location_strategy"] = ls
m["overrides"] = []interface{}{om}
if _, ok := d.GetOkExists(fmt.Sprintf("rules.%d.overrides.0.location_strategy.prefer_ecs", idx)); ok {
ls["prefer_ecs"] = o.LocationStrategy.PreferECS
}
if _, ok := d.GetOkExists(fmt.Sprintf("rules.%d.overrides.0.location_strategy.mode", idx)); ok {
ls["mode"] = o.LocationStrategy.Mode
}
}
if _, ok := d.GetOkExists(fmt.Sprintf("rules.%d.overrides.0.random_steering", idx)); o.RandomSteering != nil && ok {
rs := map[string]interface{}{}
om["random_steering"] = rs
m["overrides"] = []interface{}{om}
if _, ok := d.GetOkExists(fmt.Sprintf("rules.%d.overrides.0.random_steering.pool_weights", idx)); ok {
rs["pool_weights"] = o.RandomSteering.PoolWeights
}
if _, ok := d.GetOkExists(fmt.Sprintf("rules.%d.overrides.0.random_steering.default_weight", idx)); ok {
rs["default_weight"] = o.RandomSteering.DefaultWeight
}
}
}

Expand Down Expand Up @@ -685,6 +843,45 @@ func expandRules(rdata interface{}) ([]*cloudflare.LoadBalancerRule, error) {
v.Secure = sec.(string)
lbr.Overrides.SessionAffinityAttrs = v
}
if zdf, ok := attr["zero_downtime_failover"]; ok {
v.ZeroDowntimeFailover = zdf.(string)
lbr.Overrides.SessionAffinityAttrs = v
}
}

if ar, ok := ov["adaptive_routing"]; ok {
properties := ar.(map[string]interface{})
v := &cloudflare.AdaptiveRouting{}
if f, ok := properties["failover_across_pools"]; ok {
v.FailoverAcrossPools = cloudflare.BoolPtr(f.(bool))
lbr.Overrides.AdaptiveRouting = v
}
}

if ls, ok := ov["location_strategy"]; ok {
properties := ls.(map[string]interface{})
v := &cloudflare.LocationStrategy{}
if p, ok := properties["prefer_ecs"]; ok {
v.PreferECS = p.(string)
lbr.Overrides.LocationStrategy = v
}
if m, ok := properties["mode"]; ok {
v.Mode = m.(string)
lbr.Overrides.LocationStrategy = v
}
}

if rs, ok := ov["random_steering"]; ok {
properties := rs.(map[string]interface{})
v := &cloudflare.RandomSteering{}
if pw, ok := properties["pool_weights"]; ok {
v.PoolWeights = pw.(map[string]float64)
lbr.Overrides.RandomSteering = v
}
if dw, ok := properties["default_weight"]; ok {
v.DefaultWeight = dw.(float64)
lbr.Overrides.RandomSteering = v
}
}

if ttl, ok := ov["ttl"]; ok {
Expand Down Expand Up @@ -771,8 +968,53 @@ func expandSessionAffinityAttrs(attrs interface{}) (*cloudflare.SessionAffinityA
if cfSessionAffinityAttrs.DrainDuration, err = strconv.Atoi(v.(string)); err != nil {
return nil, err
}
case "zero_downtime_failover":
cfSessionAffinityAttrs.ZeroDowntimeFailover = v.(string)
}
}

return &cfSessionAffinityAttrs, nil
}

func expandAdaptiveRouting(properties interface{}) *cloudflare.AdaptiveRouting {
var cfAdaptiveRouting cloudflare.AdaptiveRouting

for k, v := range properties.(map[string]interface{}) {
switch k {
case "failover_across_pools":
cfAdaptiveRouting.FailoverAcrossPools = cloudflare.BoolPtr(v.(bool))
}
}

return &cfAdaptiveRouting
}

func expandLocationStrategy(properties interface{}) *cloudflare.LocationStrategy {
var cfLocationStrategy cloudflare.LocationStrategy

for k, v := range properties.(map[string]interface{}) {
switch k {
case "prefer_ecs":
cfLocationStrategy.PreferECS = v.(string)
case "mode":
cfLocationStrategy.Mode = v.(string)
}
}

return &cfLocationStrategy
}

func expandRandomSteering(properties interface{}) *cloudflare.RandomSteering {
var cfRandomSteering cloudflare.RandomSteering

for k, v := range properties.(map[string]interface{}) {
switch k {
case "pool_weights":
cfRandomSteering.PoolWeights = v.(map[string]float64)
case "default_weight":
cfRandomSteering.DefaultWeight = v.(float64)
}
}

return &cfRandomSteering
}
Loading

0 comments on commit 0dfdeec

Please sign in to comment.