Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support load balancer origin weights and session affinity #57

Conversation

lawrencejones
Copy link

Updating the cloudflare-go library brings support for the weight
parameter of load balancer origins. This allows configuration of traffic
priority to each of the backend origins, which is critical in more
complex load balancing setups.

I don't have a full-featured Cloudflare account, so I can't run all the tests. The fully specified load balancer test is within reach of my pauper setup, so I can provide test output for that!

λ (lab) ~> make testacc TEST=./cloudflare TESTARGS='-run=TestAccCloudFlareLoadBalancerPool_FullySpecified.*'
==> Checking that code complies with gofmt requirements...
TF_ACC=1 go test ./cloudflare -v -run=TestAccCloudFlareLoadBalancerPool_FullySpecified.* -timeout 120m
=== RUN   TestAccCloudFlareLoadBalancerPool_FullySpecified
=== PAUSE TestAccCloudFlareLoadBalancerPool_FullySpecified
=== CONT  TestAccCloudFlareLoadBalancerPool_FullySpecified
--- PASS: TestAccCloudFlareLoadBalancerPool_FullySpecified (3.70s)
PASS
ok      github.com/terraform-providers/terraform-provider-cloudflare/cloudflare 3.728s

@lawrencejones lawrencejones changed the title Support load balancer origin weights Support load balancer origin weights and session affinity May 1, 2018
@lawrencejones
Copy link
Author

lawrencejones commented May 1, 2018

I've added session affinity here, as that comes in the same dependency update that the origin weights are included in.

λ (prd) ~> make testacc TEST=./cloudflare TESTARGS='-run=TestAccCloudFlareLoadBalancer_SessionAffinity.*'
==> Checking that code complies with gofmt requirements...
TF_ACC=1 go test ./cloudflare -v -run=TestAccCloudFlareLoadBalancer_SessionAffinity.* -timeout 120m
=== RUN   TestAccCloudFlareLoadBalancer_SessionAffinity
=== PAUSE TestAccCloudFlareLoadBalancer_SessionAffinity
=== CONT  TestAccCloudFlareLoadBalancer_SessionAffinity
--- PASS: TestAccCloudFlareLoadBalancer_SessionAffinity (8.16s)
PASS
ok      github.com/terraform-providers/terraform-provider-cloudflare/cloudflare 8.178s

There is a question about whether we'd use the key session_affinity or persistence, given that cloudflare-go uses the latter. I'd personally prefer to match the keys used in the API (session_affinity), and that also happens to be the term I find most clearly explains the feature.

I defer to the maintainers on this decision though.

@lawrencejones lawrencejones force-pushed the lawrence-support-load-balancer-weights branch from 6e84fab to 6dc7d3e Compare May 1, 2018 17:14
@lawrencejones
Copy link
Author

Not sure exactly what's failing in the CI environment. If anyone more familiar with the setup could help, that would be appreciated.

@elithrar
Copy link

elithrar commented May 4, 2018

@prdonahue - can you re-start this build? Looks like a network error at the time.

@lawrencejones
Copy link
Author

I'm going to re-touch the commit to get the build going again.

@lawrencejones lawrencejones force-pushed the lawrence-support-load-balancer-weights branch from 6dc7d3e to 40aef1e Compare May 4, 2018 15:32
@lawrencejones
Copy link
Author

Yeah was a flaky build. @prdonahue should be all good to review :)

@elithrar
Copy link

elithrar commented May 4, 2018

There we go! Thanks @lawrencejones. I'm just running a full test (make testacc) against my test account locally, but should be good to go.

@elithrar
Copy link

elithrar commented May 4, 2018

Seeing a few test failures for tests that create/mutate a Load Balancer object ->

~/.go/src/github.com/terraform-providers/terraform-provider-cloudflare (pr-57/lb-weights) ✔  make testacc TESTARGS='-run=LoadBalancer'

==> Checking that code complies with gofmt requirements...
TF_ACC=1 go test $(go list ./... |grep -v 'vendor') -v -run=LoadBalancer -timeout 120m
?       github.com/terraform-providers/terraform-provider-cloudflare    [no test files]
=== RUN   TestAccCloudFlareLoadBalancerMonitor_Import
=== PAUSE TestAccCloudFlareLoadBalancerMonitor_Import
=== RUN   TestAccCloudFlareLoadBalancerPool_Import
=== PAUSE TestAccCloudFlareLoadBalancerPool_Import
=== RUN   TestAccCloudFlareLoadBalancer_Import
=== PAUSE TestAccCloudFlareLoadBalancer_Import
=== RUN   TestAccCloudFlareLoadBalancerMonitor_Basic
=== PAUSE TestAccCloudFlareLoadBalancerMonitor_Basic
=== RUN   TestAccCloudFlareLoadBalancerMonitor_FullySpecified
=== PAUSE TestAccCloudFlareLoadBalancerMonitor_FullySpecified
=== RUN   TestAccCloudFlareLoadBalancerMonitor_Update
=== PAUSE TestAccCloudFlareLoadBalancerMonitor_Update
=== RUN   TestAccCloudFlareLoadBalancerMonitor_CreateAfterManualDestroy
=== PAUSE TestAccCloudFlareLoadBalancerMonitor_CreateAfterManualDestroy
=== RUN   TestAccCloudFlareLoadBalancerPool_Basic
=== PAUSE TestAccCloudFlareLoadBalancerPool_Basic
=== RUN   TestAccCloudFlareLoadBalancerPool_FullySpecified
=== PAUSE TestAccCloudFlareLoadBalancerPool_FullySpecified
=== RUN   TestAccCloudFlareLoadBalancerPool_ForceNew
=== PAUSE TestAccCloudFlareLoadBalancerPool_ForceNew
=== RUN   TestAccCloudFlareLoadBalancerPool_CreateAfterManualDestroy
=== PAUSE TestAccCloudFlareLoadBalancerPool_CreateAfterManualDestroy
=== RUN   TestAccCloudFlareLoadBalancer_Basic
=== PAUSE TestAccCloudFlareLoadBalancer_Basic
=== RUN   TestAccCloudFlareLoadBalancer_SessionAffinity
=== PAUSE TestAccCloudFlareLoadBalancer_SessionAffinity
=== RUN   TestAccCloudFlareLoadBalancer_GeoBalanced
=== PAUSE TestAccCloudFlareLoadBalancer_GeoBalanced
=== RUN   TestAccCloudFlareLoadBalancer_DuplicatePool
=== PAUSE TestAccCloudFlareLoadBalancer_DuplicatePool
=== RUN   TestAccCloudFlareLoadBalancer_Update
=== PAUSE TestAccCloudFlareLoadBalancer_Update
=== RUN   TestAccCloudFlareLoadBalancer_CreateAfterManualDestroy
=== PAUSE TestAccCloudFlareLoadBalancer_CreateAfterManualDestroy
=== CONT  TestAccCloudFlareLoadBalancerMonitor_Import
=== CONT  TestAccCloudFlareLoadBalancerPool_ForceNew
=== CONT  TestAccCloudFlareLoadBalancerMonitor_Update
=== CONT  TestAccCloudFlareLoadBalancer_DuplicatePool
--- PASS: TestAccCloudFlareLoadBalancer_DuplicatePool (1.46s)
=== CONT  TestAccCloudFlareLoadBalancer_CreateAfterManualDestroy
--- PASS: TestAccCloudFlareLoadBalancerMonitor_Import (1.89s)
=== CONT  TestAccCloudFlareLoadBalancer_Update
--- FAIL: TestAccCloudFlareLoadBalancer_CreateAfterManualDestroy (2.27s)
        testing.go:434: Step 0 error: Error applying: 1 error(s) occurred:

                * cloudflare_load_balancer.39xznlen2c: 1 error(s) occurred:

                * cloudflare_load_balancer.39xznlen2c: error creating load balancer for zone: error from makeRequest: HTTP status 400: content "{\n  \"result\": null,\n  \"success\": false,\n  \"errors\": [\n    {\n      \"code\": 6007,\n      \"message\": \"Malformed request body.\"\n    }\n  ],\n  \"messages\": []\n}\n"
=== CONT  TestAccCloudFlareLoadBalancerPool_Basic
--- FAIL: TestAccCloudFlareLoadBalancer_Update (1.92s)
        testing.go:434: Step 0 error: Error applying: 1 error(s) occurred:

                * cloudflare_load_balancer.nnjm0rx9s9: 1 error(s) occurred:

                * cloudflare_load_balancer.nnjm0rx9s9: error creating load balancer for zone: error from makeRequest: HTTP status 400: content "{\n  \"result\": null,\n  \"success\": false,\n  \"errors\": [\n    {\n      \"code\": 6007,\n      \"message\": \"Malformed request body.\"\n    }\n  ],\n  \"messages\": []\n}\n"
=== CONT  TestAccCloudFlareLoadBalancerPool_FullySpecified
--- PASS: TestAccCloudFlareLoadBalancerMonitor_Update (3.91s)
=== CONT  TestAccCloudFlareLoadBalancer_SessionAffinity
--- PASS: TestAccCloudFlareLoadBalancerPool_Basic (1.76s)
=== CONT  TestAccCloudFlareLoadBalancer_GeoBalanced
--- PASS: TestAccCloudFlareLoadBalancerPool_ForceNew (6.23s)
=== CONT  TestAccCloudFlareLoadBalancer_Basic
--- PASS: TestAccCloudFlareLoadBalancerPool_FullySpecified (2.62s)
=== CONT  TestAccCloudFlareLoadBalancerMonitor_Basic
--- PASS: TestAccCloudFlareLoadBalancer_SessionAffinity (4.48s)
=== CONT  TestAccCloudFlareLoadBalancerMonitor_FullySpecified
--- FAIL: TestAccCloudFlareLoadBalancer_GeoBalanced (3.02s)
        testing.go:434: Step 0 error: Error applying: 1 error(s) occurred:

                * cloudflare_load_balancer.3217kq931c: 1 error(s) occurred:

                * cloudflare_load_balancer.3217kq931c: error creating load balancer for zone: error from makeRequest: HTTP status 400: content "{\n  \"result\": null,\n  \"success\": false,\n  \"errors\": [\n    {\n      \"code\": 6007,\n      \"message\": \"Malformed request body.\"\n    }\n  ],\n  \"messages\": []\n}\n"
=== CONT  TestAccCloudFlareLoadBalancerMonitor_CreateAfterManualDestroy
--- PASS: TestAccCloudFlareLoadBalancerMonitor_Basic (2.21s)
=== CONT  TestAccCloudFlareLoadBalancer_Import
--- FAIL: TestAccCloudFlareLoadBalancer_Basic (2.55s)
        testing.go:434: Step 0 error: Error applying: 1 error(s) occurred:

                * cloudflare_load_balancer.xeeiqnioru: 1 error(s) occurred:

                * cloudflare_load_balancer.xeeiqnioru: error creating load balancer for zone: error from makeRequest: HTTP status 400: content "{\n  \"result\": null,\n  \"success\": false,\n  \"errors\": [\n    {\n      \"code\": 6007,\n      \"message\": \"Malformed request body.\"\n    }\n  ],\n  \"messages\": []\n}\n"
=== CONT  TestAccCloudFlareLoadBalancerPool_CreateAfterManualDestroy
--- PASS: TestAccCloudFlareLoadBalancerMonitor_FullySpecified (1.87s)
=== CONT  TestAccCloudFlareLoadBalancerPool_Import
--- FAIL: TestAccCloudFlareLoadBalancer_Import (2.12s)
        testing.go:434: Step 0 error: Error applying: 1 error(s) occurred:

                * cloudflare_load_balancer.is8n37ej0f: 1 error(s) occurred:

                * cloudflare_load_balancer.is8n37ej0f: error creating load balancer for zone: error from makeRequest: HTTP status 400: content "{\n  \"result\": null,\n  \"success\": false,\n  \"errors\": [\n    {\n      \"code\": 6007,\n      \"message\": \"Malformed request body.\"\n    }\n  ],\n  \"messages\": []\n}\n"
--- PASS: TestAccCloudFlareLoadBalancerMonitor_CreateAfterManualDestroy (3.27s)
--- PASS: TestAccCloudFlareLoadBalancerPool_Import (1.83s)
--- PASS: TestAccCloudFlareLoadBalancerPool_CreateAfterManualDestroy (4.31s)
FAIL
FAIL    github.com/terraform-providers/terraform-provider-cloudflare/cloudflare 13.110s
make: *** [testacc] Error 1

Looking into the details.

@lawrencejones
Copy link
Author

Ahh, these are all on tests I can't run (no multi-geo support from my cheap account). Any help with this would be appreciated!

@elithrar
Copy link

elithrar commented May 4, 2018 via email

@elithrar
Copy link

elithrar commented May 4, 2018

OK, figured it out ->

When adding session_affinity, the empty value is the string "none", rather than null. By default, Terraform is sending null, and the API is getting back the error ->

e.g.

// Correct
' --data-binary '{"description":"","created_on":"2018-05-04T16:15:53.433059Z","modified_on":"2018-05-04T16:15:53.433059Z","id":"122e4868dcbfade9a3cd5bd594bea2e3","proxied":true,"enabled":true,"name":"repeater.questionable.services","session_affinity":"none","fallback_pool":"bfa5f38006d89f6abf0ffc311f71709b","default_pools":["bfa5f38006d89f6abf0ffc311f71709b"],"pop_pools":{},"region_pools":{}}' --compressed
// Incorrect
' --data-binary '{"description":"","created_on":"2018-05-04T16:15:53.433059Z","modified_on":"2018-05-04T16:15:53.433059Z","id":"122e4868dcbfade9a3cd5bd594bea2e3","proxied":true,"enabled":true,"name":"repeater.questionable.services","session_affinity":null,"fallback_pool":"bfa5f38006d89f6abf0ffc311f71709b","default_pools":["bfa5f38006d89f6abf0ffc311f71709b"],"pop_pools":{},"region_pools":{}}' --compressed

Thus (and these aren't mutually exclusive) ->

  • The Load Balancing API should accept null as a valid value
  • cloudflare-go should default to "none" for that field
  • cloudflare-go should just remove the field if it is not populated, which is interpreted by the API as "none" and set accordingly in the response.

@lawrencejones
Copy link
Author

That's great, thanks for that! Not going to get time today but will definitely fix this over the weekend- your comment should make it an easy fix.

@elithrar
Copy link

elithrar commented May 4, 2018

Addressing in cloudflare/cloudflare-go#179 -> waiting on build & a quick review from a colleague, then I can tag a new cloudflare-go release.

@elithrar
Copy link

elithrar commented May 4, 2018

Addressed in v0.8.5 of cloudflare-go - https://github.com/cloudflare/cloudflare-go/releases/tag/v0.8.5

If you update the pinned version of cloudflare-go to 1f9007fbecae20711133c60519338c41cef1ffb4 this should "just work".

@elithrar
Copy link

elithrar commented May 4, 2018

Apply this patch to the repo, or just run govendor fetch github.com/cloudflare/cloudflare-go :)

From 0e723c3c25e96cc81b6be0a2d89a1e91820f7d60 Mon Sep 17 00:00:00 2001
From: Lawrence Jones <[email protected]>
Date: Mon, 30 Apr 2018 10:55:01 +0100
Subject: [PATCH 1/3] vendoring: update cloudflare-go

---
 .../github.com/cloudflare/cloudflare-go/load_balancing.go | 8 +++++---
 vendor/vendor.json                                        | 6 +++---
 2 files changed, 8 insertions(+), 6 deletions(-)

diff --git a/vendor/github.com/cloudflare/cloudflare-go/load_balancing.go b/vendor/github.com/cloudflare/cloudflare-go/load_balancing.go
index 9b398c8..79da42a 100644
--- a/vendor/github.com/cloudflare/cloudflare-go/load_balancing.go
+++ b/vendor/github.com/cloudflare/cloudflare-go/load_balancing.go
@@ -26,9 +26,10 @@ type LoadBalancerPool struct {
 }
 
 type LoadBalancerOrigin struct {
-	Name    string `json:"name"`
-	Address string `json:"address"`
-	Enabled bool   `json:"enabled"`
+	Name    string  `json:"name"`
+	Address string  `json:"address"`
+	Enabled bool    `json:"enabled"`
+	Weight  float64 `json:"weight"`
 }
 
 // LoadBalancerMonitor represents a load balancer monitor's properties.
@@ -61,6 +62,7 @@ type LoadBalancer struct {
 	RegionPools  map[string][]string `json:"region_pools"`
 	PopPools     map[string][]string `json:"pop_pools"`
 	Proxied      bool                `json:"proxied"`
+	Persistence  string              `json:"session_affinity"`
 }
 
 // loadBalancerPoolResponse represents the response from the load balancer pool endpoints.
diff --git a/vendor/vendor.json b/vendor/vendor.json
index 5908593..6b13d50 100644
--- a/vendor/vendor.json
+++ b/vendor/vendor.json
@@ -227,10 +227,10 @@
 			"revisionTime": "2017-07-27T06:48:18Z"
 		},
 		{
-			"checksumSHA1": "mDCKeVKLfjNsKNhPhBnxDPGPeVE=",
+			"checksumSHA1": "hxieX0BFuuQN6b/UAw0B0YeYYpc=",
 			"path": "github.com/cloudflare/cloudflare-go",
-			"revision": "e1f3c4226ea9280f7177f0bf4a8bb2f750466b12",
-			"revisionTime": "2018-03-28T15:27:59Z"
+			"revision": "667a72333f4e362f45844ea90caef0e49ff8f97c",
+			"revisionTime": "2018-04-05T19:09:46Z"
 		},
 		{
 			"checksumSHA1": "dvabztWVQX8f6oMLRyv4dLH+TGY=",
-- 
2.17.0


From 4c200b36f64c379e0ca8c28f1f3922e15480442b Mon Sep 17 00:00:00 2001
From: Lawrence Jones <[email protected]>
Date: Mon, 30 Apr 2018 11:07:48 +0100
Subject: [PATCH 2/3] Support weight parameter for load balancer origins

Updating the cloudflare-go library brings support for the weight
parameter of load balancer origins. This allows configuration of traffic
priority to each of the backend origins, which is critical in more
complex load balancing setups.
---
 .../resource_cloudflare_load_balancer_pool.go | 27 +++++++++++++++++++
 ...urce_cloudflare_load_balancer_pool_test.go |  2 ++
 2 files changed, 29 insertions(+)

diff --git a/cloudflare/resource_cloudflare_load_balancer_pool.go b/cloudflare/resource_cloudflare_load_balancer_pool.go
index 94b1921..3f4b99f 100644
--- a/cloudflare/resource_cloudflare_load_balancer_pool.go
+++ b/cloudflare/resource_cloudflare_load_balancer_pool.go
@@ -113,6 +113,13 @@ var originsElem = &schema.Resource{
 			},
 		},
 
+		"weight": {
+			Type:         schema.TypeFloat,
+			Optional:     true,
+			Default:      1.0,
+			ValidateFunc: floatBetween(0.0, 1.0),
+		},
+
 		"enabled": {
 			Type:     schema.TypeBool,
 			Optional: true,
@@ -172,6 +179,7 @@ func expandLoadBalancerOrigins(originSet *schema.Set) (origins []cloudflare.Load
 			Name:    o["name"].(string),
 			Address: o["address"].(string),
 			Enabled: o["enabled"].(bool),
+			Weight:  o["weight"].(float64),
 		}
 		origins = append(origins, origin)
 	}
@@ -221,6 +229,7 @@ func flattenLoadBalancerOrigins(origins []cloudflare.LoadBalancerOrigin) *schema
 			"name":    o.Name,
 			"address": o.Address,
 			"enabled": o.Enabled,
+			"weight":  o.Weight,
 		}
 		flattened = append(flattened, cfg)
 	}
@@ -239,3 +248,21 @@ func resourceCloudFlareLoadBalancerPoolDelete(d *schema.ResourceData, meta inter
 
 	return nil
 }
+
+// floatBetween returns a validate function that can be used in schema definitions.
+func floatBetween(min, max float64) schema.SchemaValidateFunc {
+	return func(i interface{}, k string) (s []string, es []error) {
+		v, ok := i.(float64)
+		if !ok {
+			es = append(es, fmt.Errorf("expected type of %s to be float64", k))
+			return
+		}
+
+		if v < min || v > max {
+			es = append(es, fmt.Errorf("expected %s to be within %v and %v", k, min, max))
+			return
+		}
+
+		return
+	}
+}
diff --git a/cloudflare/resource_cloudflare_load_balancer_pool_test.go b/cloudflare/resource_cloudflare_load_balancer_pool_test.go
index 7aedb83..933af27 100644
--- a/cloudflare/resource_cloudflare_load_balancer_pool_test.go
+++ b/cloudflare/resource_cloudflare_load_balancer_pool_test.go
@@ -246,10 +246,12 @@ resource "cloudflare_load_balancer_pool" "%[1]s" {
     name = "example-1"
     address = "192.0.2.1"
     enabled = false
+    weight = 1.0
   }
   origins {
     name = "example-2"
     address = "192.0.2.2"
+    weight = 0.5
   }
   check_regions = ["WEU"]
   description = "tfacc-fully-specified"
-- 
2.17.0


From 40aef1ec5c6390d7ff6f7afa401346689cac9fb6 Mon Sep 17 00:00:00 2001
From: Lawrence Jones <[email protected]>
Date: Tue, 1 May 2018 18:10:07 +0100
Subject: [PATCH 3/3] Support session_affinity for load balancers

The newer cloudflare-go library supports configuring session affinity
values for Cloudflare load balancers. This commit updates the terraform
resource to support this field.
---
 .../resource_cloudflare_load_balancer.go      |  6 +++
 .../resource_cloudflare_load_balancer_test.go | 43 ++++++++++++++++++-
 2 files changed, 48 insertions(+), 1 deletion(-)

diff --git a/cloudflare/resource_cloudflare_load_balancer.go b/cloudflare/resource_cloudflare_load_balancer.go
index 8046f4e..7d53ea9 100644
--- a/cloudflare/resource_cloudflare_load_balancer.go
+++ b/cloudflare/resource_cloudflare_load_balancer.go
@@ -57,6 +57,11 @@ func resourceCloudFlareLoadBalancer() *schema.Resource {
 				},
 			},
 
+			"session_affinity": {
+				Type:     schema.TypeString,
+				Optional: true,
+			},
+
 			"proxied": {
 				Type:          schema.TypeBool,
 				Optional:      true,
@@ -157,6 +162,7 @@ func resourceCloudFlareLoadBalancerCreate(d *schema.ResourceData, meta interface
 		DefaultPools: expandInterfaceToStringList(d.Get("default_pool_ids")),
 		Proxied:      d.Get("proxied").(bool),
 		TTL:          d.Get("ttl").(int),
+		Persistence:  d.Get("session_affinity").(string),
 	}
 
 	if description, ok := d.GetOk("description"); ok {
diff --git a/cloudflare/resource_cloudflare_load_balancer_test.go b/cloudflare/resource_cloudflare_load_balancer_test.go
index 1b85099..ed32593 100644
--- a/cloudflare/resource_cloudflare_load_balancer_test.go
+++ b/cloudflare/resource_cloudflare_load_balancer_test.go
@@ -9,11 +9,12 @@ import (
 
 	"os"
 
+	"regexp"
+
 	"github.com/cloudflare/cloudflare-go"
 	"github.com/hashicorp/terraform/helper/acctest"
 	"github.com/hashicorp/terraform/helper/resource"
 	"github.com/hashicorp/terraform/terraform"
-	"regexp"
 )
 
 func TestAccCloudFlareLoadBalancer_Basic(t *testing.T) {
@@ -49,6 +50,35 @@ func TestAccCloudFlareLoadBalancer_Basic(t *testing.T) {
 	})
 }
 
+func TestAccCloudFlareLoadBalancer_SessionAffinity(t *testing.T) {
+	t.Parallel()
+	var loadBalancer cloudflare.LoadBalancer
+	zone := os.Getenv("CLOUDFLARE_DOMAIN")
+	rnd := acctest.RandString(10)
+	name := "cloudflare_load_balancer." + rnd
+
+	resource.Test(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckCloudFlareLoadBalancerDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccCheckCloudFlareLoadBalancerConfigSessionAffinity(zone, rnd),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckCloudFlareLoadBalancerExists(name, &loadBalancer),
+					testAccCheckCloudFlareLoadBalancerIDIsValid(name, zone),
+					// explicitly verify that our session_affinity has been set
+					resource.TestCheckResourceAttr(name, "session_affinity", "cookie"),
+					// dont check that other specified values are set, this will be evident by lack
+					// of plan diff some values will get empty values
+					resource.TestCheckResourceAttr(name, "pop_pools.#", "0"),
+					resource.TestCheckResourceAttr(name, "region_pools.#", "0"),
+				),
+			},
+		},
+	})
+}
+
 func TestAccCloudFlareLoadBalancer_GeoBalanced(t *testing.T) {
 	t.Parallel()
 	var loadBalancer cloudflare.LoadBalancer
@@ -299,6 +329,17 @@ resource "cloudflare_load_balancer" "%[2]s" {
 }`, zone, id)
 }
 
+func testAccCheckCloudFlareLoadBalancerConfigSessionAffinity(zone, id string) string {
+	return testAccCheckCloudFlareLoadBalancerPoolConfigBasic(id) + fmt.Sprintf(`
+resource "cloudflare_load_balancer" "%[2]s" {
+  zone = "%[1]s"
+  name = "tf-testacc-lb-session-affinity-%[2]s"
+  fallback_pool_id = "${cloudflare_load_balancer_pool.%[2]s.id}"
+  default_pool_ids = ["${cloudflare_load_balancer_pool.%[2]s.id}"]
+	session_affinity = "cookie"
+}`, zone, id)
+}
+
 func testAccCheckCloudFlareLoadBalancerConfigGeoBalanced(zone, id string) string {
 	return testAccCheckCloudFlareLoadBalancerPoolConfigBasic(id) + fmt.Sprintf(`
 resource "cloudflare_load_balancer" "%[2]s" {
-- 
2.17.0

Updating the cloudflare-go library brings support for the weight
parameter of load balancer origins. This allows configuration of traffic
priority to each of the backend origins, which is critical in more
complex load balancing setups.
The newer cloudflare-go library supports configuring session affinity
values for Cloudflare load balancers. This commit updates the terraform
resource to support this field.
@lawrencejones lawrencejones force-pushed the lawrence-support-load-balancer-weights branch from 40aef1e to f0d0c43 Compare May 4, 2018 18:56
@lawrencejones
Copy link
Author

Rebased with the latest version, ran the tests and other than some rate limits things seemed to work! Will wait for a full test run on your end to confirm though.

@elithrar
Copy link

elithrar commented May 4, 2018

Tests pass in full -

➜  ~/.go/src/github.com/terraform-providers/terraform-provider-cloudflare (pr-57/lb-weights) ✔ make testacc TESTARGS='--run=LoadBalancer'

==> Checking that code complies with gofmt requirements...
TF_ACC=1 go test $(go list ./... |grep -v 'vendor') -v --run=LoadBalancer -timeout 120m
?       github.com/terraform-providers/terraform-provider-cloudflare    [no test files]
=== RUN   TestAccCloudFlareLoadBalancerMonitor_Import
=== PAUSE TestAccCloudFlareLoadBalancerMonitor_Import
=== RUN   TestAccCloudFlareLoadBalancerPool_Import
=== PAUSE TestAccCloudFlareLoadBalancerPool_Import
=== RUN   TestAccCloudFlareLoadBalancer_Import
=== PAUSE TestAccCloudFlareLoadBalancer_Import
=== RUN   TestAccCloudFlareLoadBalancerMonitor_Basic
=== PAUSE TestAccCloudFlareLoadBalancerMonitor_Basic
=== RUN   TestAccCloudFlareLoadBalancerMonitor_FullySpecified
=== PAUSE TestAccCloudFlareLoadBalancerMonitor_FullySpecified
=== RUN   TestAccCloudFlareLoadBalancerMonitor_Update
=== PAUSE TestAccCloudFlareLoadBalancerMonitor_Update
=== RUN   TestAccCloudFlareLoadBalancerMonitor_CreateAfterManualDestroy
=== PAUSE TestAccCloudFlareLoadBalancerMonitor_CreateAfterManualDestroy
=== RUN   TestAccCloudFlareLoadBalancerPool_Basic
=== PAUSE TestAccCloudFlareLoadBalancerPool_Basic
=== RUN   TestAccCloudFlareLoadBalancerPool_FullySpecified
=== PAUSE TestAccCloudFlareLoadBalancerPool_FullySpecified
=== RUN   TestAccCloudFlareLoadBalancerPool_ForceNew
=== PAUSE TestAccCloudFlareLoadBalancerPool_ForceNew
=== RUN   TestAccCloudFlareLoadBalancerPool_CreateAfterManualDestroy
=== PAUSE TestAccCloudFlareLoadBalancerPool_CreateAfterManualDestroy
=== RUN   TestAccCloudFlareLoadBalancer_Basic
=== PAUSE TestAccCloudFlareLoadBalancer_Basic
=== RUN   TestAccCloudFlareLoadBalancer_SessionAffinity
=== PAUSE TestAccCloudFlareLoadBalancer_SessionAffinity
=== RUN   TestAccCloudFlareLoadBalancer_GeoBalanced
=== PAUSE TestAccCloudFlareLoadBalancer_GeoBalanced
=== RUN   TestAccCloudFlareLoadBalancer_DuplicatePool
=== PAUSE TestAccCloudFlareLoadBalancer_DuplicatePool
=== RUN   TestAccCloudFlareLoadBalancer_Update
=== PAUSE TestAccCloudFlareLoadBalancer_Update
=== RUN   TestAccCloudFlareLoadBalancer_CreateAfterManualDestroy
=== PAUSE TestAccCloudFlareLoadBalancer_CreateAfterManualDestroy
=== CONT  TestAccCloudFlareLoadBalancerMonitor_Import
=== CONT  TestAccCloudFlareLoadBalancerPool_ForceNew
=== CONT  TestAccCloudFlareLoadBalancer_CreateAfterManualDestroy
=== CONT  TestAccCloudFlareLoadBalancer_GeoBalanced
--- PASS: TestAccCloudFlareLoadBalancerMonitor_Import (1.57s)
=== CONT  TestAccCloudFlareLoadBalancer_SessionAffinity
--- PASS: TestAccCloudFlareLoadBalancerPool_ForceNew (6.02s)
=== CONT  TestAccCloudFlareLoadBalancer_Basic
--- PASS: TestAccCloudFlareLoadBalancer_GeoBalanced (6.16s)
=== CONT  TestAccCloudFlareLoadBalancerPool_CreateAfterManualDestroy
--- PASS: TestAccCloudFlareLoadBalancer_SessionAffinity (6.61s)
=== CONT  TestAccCloudFlareLoadBalancer_Update
--- PASS: TestAccCloudFlareLoadBalancer_CreateAfterManualDestroy (9.12s)
=== CONT  TestAccCloudFlareLoadBalancerMonitor_Update
--- PASS: TestAccCloudFlareLoadBalancer_Basic (4.71s)
=== CONT  TestAccCloudFlareLoadBalancerPool_FullySpecified
--- PASS: TestAccCloudFlareLoadBalancerPool_CreateAfterManualDestroy (5.28s)
=== CONT  TestAccCloudFlareLoadBalancerPool_Basic
--- PASS: TestAccCloudFlareLoadBalancerMonitor_Update (2.77s)
=== CONT  TestAccCloudFlareLoadBalancerMonitor_CreateAfterManualDestroy
--- PASS: TestAccCloudFlareLoadBalancerPool_FullySpecified (2.49s)
=== CONT  TestAccCloudFlareLoadBalancerMonitor_Basic
--- PASS: TestAccCloudFlareLoadBalancerPool_Basic (2.48s)
=== CONT  TestAccCloudFlareLoadBalancerMonitor_FullySpecified
--- PASS: TestAccCloudFlareLoadBalancerMonitor_CreateAfterManualDestroy (2.99s)
=== CONT  TestAccCloudFlareLoadBalancer_Import
--- PASS: TestAccCloudFlareLoadBalancerMonitor_Basic (2.01s)
=== CONT  TestAccCloudFlareLoadBalancerPool_Import
--- PASS: TestAccCloudFlareLoadBalancer_Update (7.43s)
=== CONT  TestAccCloudFlareLoadBalancer_DuplicatePool
--- PASS: TestAccCloudFlareLoadBalancerMonitor_FullySpecified (1.75s)
--- PASS: TestAccCloudFlareLoadBalancer_DuplicatePool (1.07s)
--- PASS: TestAccCloudFlareLoadBalancerPool_Import (2.46s)
--- PASS: TestAccCloudFlareLoadBalancer_Import (4.54s)
PASS
ok      github.com/terraform-providers/terraform-provider-cloudflare/cloudflare 19.438s

@prdonahue to merge as I don't have write perms on this repo yet

@prdonahue
Copy link

@catsby could we get this reviewed/merged please?

@lawrencejones
Copy link
Author

Hey @prdonahue, just a quick ping on this. We're quite keen to use this in production, and it feels like we don't have anything blocking a merge?

@prdonahue
Copy link

@lawrencejones sorry, waiting on a HashiCorp review. @catsby mind merging this?

Copy link
Contributor

@catsby catsby left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello! There doesn't appear to be any documentation to match these new options, we'll need those added before we can merge.

Additionally, I see weight is being added to originsElem, which is used by at least the origins attribute. Because origins is a TypeSet and we're adding a new field to it, I beleive the hash ID for each preexisting origins block will change. As a result, anyone who has existing infrastructure and upgrades to this new version, will see a change as the IDs are now different. Because origins are ForceNew and Required, I believe this will result Terraform attempting to destroy and recreate any existing load balancer pools, which is probably not what we want to happen.

Can someone verify this is or is not the behavior you get when you upgrade? First create a load balancer pool with the current version of this Provider, then install a version with this new weight feature compiled in, and verify the results with terraform plan.

If this is the case, we'll need to include a State migration.

Example:

Let me know!

@catsby
Copy link
Contributor

catsby commented May 10, 2018

@prdonahue I reviewed, it needs documentation added, and I have a question/suspicion about the upgrade path

@nouvellonsteph
Copy link

Hey @catsby

So I did the test and the behaviour isn't the one you're referring to but still bad, tho. When I create an LB configuration with the actual stable version (so without weights for the origins) everything goes fine.

When I try to add the weights, I'm getting an error, which is good for me as we don't support this feature in the actual version:
Error: cloudflare_load_balancer_pool.foo: origins.0: invalid or unknown key: weight
Error: cloudflare_load_balancer_pool.foo: origins.1: invalid or unknown key: weight

I did fetch locally the new branch and build it locally, then a terraform init where I'm correctly getting the local version of the plugin.

The issue is that when I'm adding weightto my Origins, nothing is anormal in the output of terraform plan

  ~ cloudflare_load_balancer_pool.foo
      origins.1712833627.address: "" => "192.0.2.1"
      origins.1712833627.enabled: "" => "false"
      origins.1712833627.name:    "" => "example-1"
      origins.1712833627.weight:  "" => "0.2"
      origins.1836039525.address: "" => "192.0.2.2"
      origins.1836039525.enabled: "" => "true"
      origins.1836039525.name:    "" => "example-2"
      origins.1836039525.weight:  "" => "0.8"
      origins.4022274604.address: "192.0.2.1" => ""
      origins.4022274604.enabled: "false" => "false"
      origins.4022274604.name:    "example-1" => ""
      origins.4022274604.weight:  "1" => "0"
      origins.4120949060.address: "192.0.2.2" => ""
      origins.4120949060.enabled: "true" => "false"
      origins.4120949060.name:    "example-2" => ""
      origins.4120949060.weight:  "1" => "0"


Plan: 0 to add, 1 to change, 0 to destroy.

So as you can see, the ID isn't rotating and a simple change is planned for the pool. The issue comes with the terraform apply where it says that the pool doesn't support update:

  origins.1712833627.address: "" => "192.0.2.1"
  origins.1712833627.enabled: "" => "false"
  origins.1712833627.name:    "" => "example-1"
  origins.1712833627.weight:  "" => "0.2"
  origins.1836039525.address: "" => "192.0.2.2"
  origins.1836039525.enabled: "" => "true"
  origins.1836039525.name:    "" => "example-2"
  origins.1836039525.weight:  "" => "0.8"
  origins.4022274604.address: "192.0.2.1" => ""
  origins.4022274604.enabled: "false" => "false"
  origins.4022274604.name:    "example-1" => ""
  origins.4022274604.weight:  "1" => "0"
  origins.4120949060.address: "192.0.2.2" => ""
  origins.4120949060.enabled: "true" => "false"
  origins.4120949060.name:    "example-2" => ""
  origins.4120949060.weight:  "1" => "0"

Error: Error applying plan:

1 error(s) occurred:

* cloudflare_load_balancer_pool.foo: 1 error(s) occurred:

* cloudflare_load_balancer_pool.foo: doesn't support update

Here is my TF configuration file:

variable "cloudflare_email" {
  type = "string"
}

variable "cloudflare_token" {
  type = "string"
}

provider "cloudflare" {
  email = "${var.cloudflare_email}"
  token = "${var.cloudflare_token}"
}

resource "cloudflare_load_balancer_pool" "foo" {
  name = "example-pool1"

  origins {
    name    = "example-1"
    address = "192.0.2.1"
    enabled = false
    weight  = "0.2"
  }

  origins {
    name    = "example-2"
    address = "192.0.2.2"
    weight  = "0.8"
  }

  description        = "example load balancer pool"
  enabled            = true
  minimum_origins    = 1
  notification_email = "[email protected]"
}

resource "cloudflare_load_balancer" "bar" {
  zone             = "tete.com"
  name             = "bar"
  fallback_pool_id = "${cloudflare_load_balancer_pool.foo.id}"
  default_pool_ids = ["${cloudflare_load_balancer_pool.foo.id}"]
  description      = "example load balancer using geo-balancing"
  proxied          = true
}

Spotted a glitch in our cloudflare-go plugin as well, the plugin is creating the pool at the user level even if the user belongs to an Organisation (/user) https://api.cloudflare.com/#load-balancer-pools-create-pool, and this is problematic since LBs are created globally. As a result, when I'm using Terraform with a user associated to an ORG, the pool is created and the LB is generating an error because the pool isn't found. I'll try to PR something to detect if the user is part of an ORG and then rewrite the backend API call accordingly.

Let me know if you want me to do some extended tests.
cc @prdonahue

@lawrencejones
Copy link
Author

Hey all! Really busy at work right now, so not going to have time to address this imminently, but if anyone wants to take over the PR in the meantime then let me know and I'll add you to the repo so you can push to this branch.

Would hate to hold up anyone who was free to work on this!

@grega
Copy link

grega commented Aug 31, 2018

Would love to see this merged as we're relying on the weight param in our existing pools and are really keen to move the pool config into Terraform. Happy to help out (have time towards the end of Sept).

@lawrencejones
Copy link
Author

Hey @grega, I’ve given you push access to this fork so you can add commits if you need this. We’re not doing any work on this just right now, so I don’t expect to get time to work on it soon. Hopefully this means we can both make changes whenever we might get the chance!

@ghost ghost added the size/M label Oct 26, 2018
@patryk
Copy link
Contributor

patryk commented Nov 14, 2018

Merged in #153. Thank you @lawrencejones for initial PR.

@patryk patryk closed this Nov 14, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants