From 69ce33641d05169b7974c39ecf876d7489fc53e9 Mon Sep 17 00:00:00 2001 From: Dana Hoffman Date: Fri, 18 Nov 2016 09:08:26 -0700 Subject: [PATCH] First set of changes to enable internal load balancing using beta apis --- compute_operation.go | 110 +++++ config.go | 9 + provider.go | 2 + resource_compute_backend_service.go | 37 +- resource_compute_backend_service_test.go | 55 ++- resource_compute_forwarding_rule.go | 63 ++- resource_compute_forwarding_rule_test.go | 48 ++ resource_compute_health_check.go | 310 ++++++++++++ resource_compute_health_check_test.go | 156 ++++++ resource_compute_region_backend_service.go | 443 ++++++++++++++++++ ...rce_compute_region_backend_service_test.go | 346 ++++++++++++++ 11 files changed, 1552 insertions(+), 27 deletions(-) create mode 100644 resource_compute_health_check.go create mode 100644 resource_compute_health_check_test.go create mode 100644 resource_compute_region_backend_service.go create mode 100644 resource_compute_region_backend_service_test.go diff --git a/compute_operation.go b/compute_operation.go index 188deefd..c6c6c59d 100644 --- a/compute_operation.go +++ b/compute_operation.go @@ -7,6 +7,7 @@ import ( "time" "github.com/hashicorp/terraform/helper/resource" + computeBeta "google.golang.org/api/compute/v0.beta" "google.golang.org/api/compute/v1" ) @@ -30,6 +31,15 @@ type ComputeOperationWaiter struct { Zone string } +type ComputeOperationWaiterBeta struct { + Service *computeBeta.Service + Op *computeBeta.Operation + Project string + Region string + Type ComputeOperationWaitType + Zone string +} + func (w *ComputeOperationWaiter) RefreshFunc() resource.StateRefreshFunc { return func() (interface{}, string, error) { var op *compute.Operation @@ -60,6 +70,36 @@ func (w *ComputeOperationWaiter) RefreshFunc() resource.StateRefreshFunc { } } +func (w *ComputeOperationWaiterBeta) RefreshFunc() resource.StateRefreshFunc { + return func() (interface{}, string, error) { + var op *computeBeta.Operation + var err error + + switch w.Type { + case ComputeOperationWaitGlobal: + op, err = w.Service.GlobalOperations.Get( + w.Project, w.Op.Name).Do() + case ComputeOperationWaitRegion: + op, err = w.Service.RegionOperations.Get( + w.Project, w.Region, w.Op.Name).Do() + case ComputeOperationWaitZone: + op, err = w.Service.ZoneOperations.Get( + w.Project, w.Zone, w.Op.Name).Do() + default: + return nil, "bad-type", fmt.Errorf( + "Invalid wait type: %#v", w.Type) + } + + if err != nil { + return nil, "", err + } + + log.Printf("[DEBUG] Got %q when asking for operation %q", op.Status, w.Op.Name) + + return op, op.Status, nil + } +} + func (w *ComputeOperationWaiter) Conf() *resource.StateChangeConf { return &resource.StateChangeConf{ Pending: []string{"PENDING", "RUNNING"}, @@ -68,9 +108,18 @@ func (w *ComputeOperationWaiter) Conf() *resource.StateChangeConf { } } +func (w *ComputeOperationWaiterBeta) Conf() *resource.StateChangeConf { + return &resource.StateChangeConf{ + Pending: []string{"PENDING", "RUNNING"}, + Target: []string{"DONE"}, + Refresh: w.RefreshFunc(), + } +} + // ComputeOperationError wraps compute.OperationError and implements the // error interface so it can be returned. type ComputeOperationError compute.OperationError +type ComputeOperationErrorBeta computeBeta.OperationError func (e ComputeOperationError) Error() string { var buf bytes.Buffer @@ -82,6 +131,16 @@ func (e ComputeOperationError) Error() string { return buf.String() } +func (e ComputeOperationErrorBeta) Error() string { + var buf bytes.Buffer + + for _, err := range e.Errors { + buf.WriteString(err.Message + "\n") + } + + return buf.String() +} + func computeOperationWaitGlobal(config *Config, op *compute.Operation, project string, activity string) error { return computeOperationWaitGlobalTime(config, op, project, activity, 4) } @@ -111,6 +170,31 @@ func computeOperationWaitGlobalTime(config *Config, op *compute.Operation, proje return nil } +func computeOperationWaitGlobalBeta(config *Config, op *computeBeta.Operation, project string, activity string) error { + w := &ComputeOperationWaiterBeta{ + Service: config.clientComputeBeta, + Op: op, + Project: project, + Type: ComputeOperationWaitGlobal, + } + + state := w.Conf() + state.Delay = 10 * time.Second + state.Timeout = 4 * time.Minute + state.MinTimeout = 2 * time.Second + opRaw, err := state.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for %s: %s", activity, err) + } + + op = opRaw.(*computeBeta.Operation) + if op.Error != nil { + return ComputeOperationErrorBeta(*op.Error) + } + + return nil +} + func computeOperationWaitRegion(config *Config, op *compute.Operation, project string, region, activity string) error { w := &ComputeOperationWaiter{ Service: config.clientCompute, @@ -137,6 +221,32 @@ func computeOperationWaitRegion(config *Config, op *compute.Operation, project s return nil } +func computeOperationWaitRegionBeta(config *Config, op *computeBeta.Operation, project string, region, activity string) error { + w := &ComputeOperationWaiterBeta{ + Service: config.clientComputeBeta, + Op: op, + Project: project, + Type: ComputeOperationWaitRegion, + Region: region, + } + + state := w.Conf() + state.Delay = 10 * time.Second + state.Timeout = 4 * time.Minute + state.MinTimeout = 2 * time.Second + opRaw, err := state.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for %s: %s", activity, err) + } + + op = opRaw.(*computeBeta.Operation) + if op.Error != nil { + return ComputeOperationErrorBeta(*op.Error) + } + + return nil +} + func computeOperationWaitZone(config *Config, op *compute.Operation, project string, zone, activity string) error { return computeOperationWaitZoneTime(config, op, project, zone, 4, activity) } diff --git a/config.go b/config.go index 09cd750b..4c4d2187 100644 --- a/config.go +++ b/config.go @@ -14,6 +14,7 @@ import ( "golang.org/x/oauth2/google" "golang.org/x/oauth2/jwt" "google.golang.org/api/cloudresourcemanager/v1" + computeBeta "google.golang.org/api/compute/v0.beta" "google.golang.org/api/compute/v1" "google.golang.org/api/container/v1" "google.golang.org/api/dns/v1" @@ -31,6 +32,7 @@ type Config struct { Region string clientCompute *compute.Service + clientComputeBeta *computeBeta.Service clientContainer *container.Service clientDns *dns.Service clientPubsub *pubsub.Service @@ -102,6 +104,13 @@ func (c *Config) loadAndValidate() error { } c.clientCompute.UserAgent = userAgent + log.Printf("[INFO] Instantiating GCE beta client...") + c.clientComputeBeta, err = computeBeta.New(client) + if err != nil { + return err + } + c.clientComputeBeta.UserAgent = userAgent + log.Printf("[INFO] Instantiating GKE client...") c.clientContainer, err = container.New(client) if err != nil { diff --git a/provider.go b/provider.go index e126d756..ce8ef552 100644 --- a/provider.go +++ b/provider.go @@ -69,6 +69,7 @@ func Provider() terraform.ResourceProvider { "google_compute_forwarding_rule": resourceComputeForwardingRule(), "google_compute_global_address": resourceComputeGlobalAddress(), "google_compute_global_forwarding_rule": resourceComputeGlobalForwardingRule(), + "google_compute_health_check": resourceComputeHealthCheck(), "google_compute_http_health_check": resourceComputeHttpHealthCheck(), "google_compute_https_health_check": resourceComputeHttpsHealthCheck(), "google_compute_image": resourceComputeImage(), @@ -78,6 +79,7 @@ func Provider() terraform.ResourceProvider { "google_compute_instance_template": resourceComputeInstanceTemplate(), "google_compute_network": resourceComputeNetwork(), "google_compute_project_metadata": resourceComputeProjectMetadata(), + "google_compute_region_backend_service": resourceComputeRegionBackendService(), "google_compute_route": resourceComputeRoute(), "google_compute_ssl_certificate": resourceComputeSslCertificate(), "google_compute_subnetwork": resourceComputeSubnetwork(), diff --git a/resource_compute_backend_service.go b/resource_compute_backend_service.go index e860a225..2b35f97c 100644 --- a/resource_compute_backend_service.go +++ b/resource_compute_backend_service.go @@ -4,11 +4,12 @@ import ( "bytes" "fmt" "log" + "os" "regexp" "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/schema" - "google.golang.org/api/compute/v1" + "google.golang.org/api/compute/v0.beta" "google.golang.org/api/googleapi" ) @@ -99,6 +100,12 @@ func resourceComputeBackendService() *schema.Resource { Computed: true, }, + "load_balancing_scheme": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "port_name": &schema.Schema{ Type: schema.TypeString, Optional: true, @@ -185,13 +192,22 @@ func resourceComputeBackendServiceCreate(d *schema.ResourceData, meta interface{ service.EnableCDN = v.(bool) } + if v, ok := d.GetOk("load_balancing_scheme"); ok { + service.LoadBalancingScheme = v.(string) + } + + if v, ok := d.GetOk("region"); ok { + service.Region = v.(string) + } + project, err := getProject(d, config) if err != nil { return err } + fmt.Fprintf(os.Stderr, "[DEBUG] Creating new Backend Service: %#v", service) log.Printf("[DEBUG] Creating new Backend Service: %#v", service) - op, err := config.clientCompute.BackendServices.Insert( + op, err := config.clientComputeBeta.BackendServices.Insert( project, &service).Do() if err != nil { return fmt.Errorf("Error creating backend service: %s", err) @@ -201,7 +217,7 @@ func resourceComputeBackendServiceCreate(d *schema.ResourceData, meta interface{ d.SetId(service.Name) - err = computeOperationWaitGlobal(config, op, project, "Creating Backend Service") + err = computeOperationWaitGlobalBeta(config, op, project, "Creating Backend Service") if err != nil { return err } @@ -217,7 +233,7 @@ func resourceComputeBackendServiceRead(d *schema.ResourceData, meta interface{}) return err } - service, err := config.clientCompute.BackendServices.Get( + service, err := config.clientComputeBeta.BackendServices.Get( project, d.Id()).Do() if err != nil { if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 { @@ -238,6 +254,7 @@ func resourceComputeBackendServiceRead(d *schema.ResourceData, meta interface{}) d.Set("session_affinity", service.SessionAffinity) d.Set("timeout_sec", service.TimeoutSec) d.Set("fingerprint", service.Fingerprint) + d.Set("load_balancing_scheme", service.LoadBalancingScheme) d.Set("self_link", service.SelfLink) d.Set("backend", flattenBackends(service.Backends)) @@ -287,12 +304,16 @@ func resourceComputeBackendServiceUpdate(d *schema.ResourceData, meta interface{ service.SessionAffinity = d.Get("session_affinity").(string) } + if v, ok := d.GetOk("load_balancing_scheme"); ok { + service.LoadBalancingScheme = v.(string) + } + if d.HasChange("enable_cdn") { service.EnableCDN = d.Get("enable_cdn").(bool) } log.Printf("[DEBUG] Updating existing Backend Service %q: %#v", d.Id(), service) - op, err := config.clientCompute.BackendServices.Update( + op, err := config.clientComputeBeta.BackendServices.Update( project, d.Id(), &service).Do() if err != nil { return fmt.Errorf("Error updating backend service: %s", err) @@ -300,7 +321,7 @@ func resourceComputeBackendServiceUpdate(d *schema.ResourceData, meta interface{ d.SetId(service.Name) - err = computeOperationWaitGlobal(config, op, project, "Updating Backend Service") + err = computeOperationWaitGlobalBeta(config, op, project, "Updating Backend Service") if err != nil { return err } @@ -317,13 +338,13 @@ func resourceComputeBackendServiceDelete(d *schema.ResourceData, meta interface{ } log.Printf("[DEBUG] Deleting backend service %s", d.Id()) - op, err := config.clientCompute.BackendServices.Delete( + op, err := config.clientComputeBeta.BackendServices.Delete( project, d.Id()).Do() if err != nil { return fmt.Errorf("Error deleting backend service: %s", err) } - err = computeOperationWaitGlobal(config, op, project, "Deleting Backend Service") + err = computeOperationWaitGlobalBeta(config, op, project, "Deleting Backend Service") if err != nil { return err } diff --git a/resource_compute_backend_service_test.go b/resource_compute_backend_service_test.go index 133b91d8..7d2c5074 100644 --- a/resource_compute_backend_service_test.go +++ b/resource_compute_backend_service_test.go @@ -7,7 +7,7 @@ import ( "github.com/hashicorp/terraform/helper/acctest" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" - "google.golang.org/api/compute/v1" + "google.golang.org/api/compute/v0.beta" ) func TestAccComputeBackendService_basic(t *testing.T) { @@ -122,7 +122,7 @@ func testAccCheckComputeBackendServiceDestroy(s *terraform.State) error { continue } - _, err := config.clientCompute.BackendServices.Get( + _, err := config.clientComputeBeta.BackendServices.Get( config.Project, rs.Primary.ID).Do() if err == nil { return fmt.Errorf("Backend service still exists") @@ -145,7 +145,7 @@ func testAccCheckComputeBackendServiceExists(n string, svc *compute.BackendServi config := testAccProvider.Meta().(*Config) - found, err := config.clientCompute.BackendServices.Get( + found, err := config.clientComputeBeta.BackendServices.Get( config.Project, rs.Primary.ID).Do() if err != nil { return err @@ -221,11 +221,39 @@ func TestAccComputeBackendService_withSessionAffinity(t *testing.T) { } } +func TestAccComputeBackendService_withInternalLoadBalancing(t *testing.T) { + serviceName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + checkName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + var svc compute.BackendService + + // config := testAccProvider.Meta().(*Config) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeBackendServiceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeBackendService_withInternalLoadBalancing( + serviceName, checkName, "us-central1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeBackendServiceExists( + "google_compute_backend_service.foobar", &svc), + ), + }, + }, + }) + + if svc.LoadBalancingScheme != "INTERNAL" { + t.Errorf("Expected LoadBalancingScheme == INTERNAL, got %q", svc.EnableCDN) + } +} + func testAccComputeBackendService_basic(serviceName, checkName string) string { return fmt.Sprintf(` resource "google_compute_backend_service" "foobar" { name = "%s" - health_checks = ["${google_compute_http_health_check.zero.self_link}"] + health_checks = ["${google_compute_http_health_check.zero.name}"] } resource "google_compute_http_health_check" "zero" { @@ -254,6 +282,25 @@ resource "google_compute_http_health_check" "zero" { `, serviceName, checkName) } +func testAccComputeBackendService_withInternalLoadBalancing(serviceName, checkName, region string) string { + + return fmt.Sprintf(` +resource "google_compute_backend_service" "foobar" { + name = "%s" + health_checks = ["${google_compute_http_health_check.zero.self_link}"] + load_balancing_scheme = "INTERNAL" + region = "%s" +} + +resource "google_compute_http_health_check" "zero" { + name = "%s" + request_path = "/" + check_interval_sec = 1 + timeout_sec = 1 +} +`, serviceName, region, checkName) +} + func testAccComputeBackendService_basicModified(serviceName, checkOne, checkTwo string) string { return fmt.Sprintf(` resource "google_compute_backend_service" "foobar" { diff --git a/resource_compute_forwarding_rule.go b/resource_compute_forwarding_rule.go index 194845aa..9b67887a 100644 --- a/resource_compute_forwarding_rule.go +++ b/resource_compute_forwarding_rule.go @@ -5,7 +5,7 @@ import ( "log" "github.com/hashicorp/terraform/helper/schema" - "google.golang.org/api/compute/v1" + "google.golang.org/api/compute/v0.beta" "google.golang.org/api/googleapi" ) @@ -28,10 +28,16 @@ func resourceComputeForwardingRule() *schema.Resource { "target": &schema.Schema{ Type: schema.TypeString, - Required: true, + Optional: true, ForceNew: false, }, + "backend_service": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "description": &schema.Schema{ Type: schema.TypeString, Optional: true, @@ -52,6 +58,19 @@ func resourceComputeForwardingRule() *schema.Resource { Computed: true, }, + "load_balancing_scheme": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: "EXTERNAL", + }, + + "network": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "port_range": &schema.Schema{ Type: schema.TypeString, Optional: true, @@ -76,6 +95,12 @@ func resourceComputeForwardingRule() *schema.Resource { Type: schema.TypeString, Computed: true, }, + + "subnetwork": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, }, } } @@ -94,16 +119,20 @@ func resourceComputeForwardingRuleCreate(d *schema.ResourceData, meta interface{ } frule := &compute.ForwardingRule{ - IPAddress: d.Get("ip_address").(string), - IPProtocol: d.Get("ip_protocol").(string), - Description: d.Get("description").(string), - Name: d.Get("name").(string), - PortRange: d.Get("port_range").(string), - Target: d.Get("target").(string), + BackendService: d.Get("backend_service").(string), + IPAddress: d.Get("ip_address").(string), + IPProtocol: d.Get("ip_protocol").(string), + Description: d.Get("description").(string), + LoadBalancingScheme: d.Get("load_balancing_scheme").(string), + Name: d.Get("name").(string), + Network: d.Get("network").(string), + PortRange: d.Get("port_range").(string), + Subnetwork: d.Get("subnetwork").(string), + Target: d.Get("target").(string), } log.Printf("[DEBUG] ForwardingRule insert request: %#v", frule) - op, err := config.clientCompute.ForwardingRules.Insert( + op, err := config.clientComputeBeta.ForwardingRules.Insert( project, region, frule).Do() if err != nil { return fmt.Errorf("Error creating ForwardingRule: %s", err) @@ -112,7 +141,7 @@ func resourceComputeForwardingRuleCreate(d *schema.ResourceData, meta interface{ // It probably maybe worked, so store the ID now d.SetId(frule.Name) - err = computeOperationWaitRegion(config, op, project, region, "Creating Fowarding Rule") + err = computeOperationWaitRegionBeta(config, op, project, region, "Creating Fowarding Rule") if err != nil { return err } @@ -138,13 +167,13 @@ func resourceComputeForwardingRuleUpdate(d *schema.ResourceData, meta interface{ if d.HasChange("target") { target_name := d.Get("target").(string) target_ref := &compute.TargetReference{Target: target_name} - op, err := config.clientCompute.ForwardingRules.SetTarget( + op, err := config.clientComputeBeta.ForwardingRules.SetTarget( project, region, d.Id(), target_ref).Do() if err != nil { return fmt.Errorf("Error updating target: %s", err) } - err = computeOperationWaitRegion(config, op, project, region, "Updating Forwarding Rule") + err = computeOperationWaitRegionBeta(config, op, project, region, "Updating Forwarding Rule") if err != nil { return err } @@ -170,7 +199,7 @@ func resourceComputeForwardingRuleRead(d *schema.ResourceData, meta interface{}) return err } - frule, err := config.clientCompute.ForwardingRules.Get( + frule, err := config.clientComputeBeta.ForwardingRules.Get( project, region, d.Id()).Do() if err != nil { if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 { @@ -186,10 +215,14 @@ func resourceComputeForwardingRuleRead(d *schema.ResourceData, meta interface{}) d.Set("name", frule.Name) d.Set("target", frule.Target) + d.Set("backend_service", frule.BackendService) d.Set("description", frule.Description) + d.Set("load_balancing_scheme", frule.LoadBalancingScheme) + d.Set("network", frule.Network) d.Set("port_range", frule.PortRange) d.Set("project", project) d.Set("region", region) + d.Set("subnetwork", frule.Subnetwork) d.Set("ip_address", frule.IPAddress) d.Set("ip_protocol", frule.IPProtocol) d.Set("self_link", frule.SelfLink) @@ -211,13 +244,13 @@ func resourceComputeForwardingRuleDelete(d *schema.ResourceData, meta interface{ // Delete the ForwardingRule log.Printf("[DEBUG] ForwardingRule delete request") - op, err := config.clientCompute.ForwardingRules.Delete( + op, err := config.clientComputeBeta.ForwardingRules.Delete( project, region, d.Id()).Do() if err != nil { return fmt.Errorf("Error deleting ForwardingRule: %s", err) } - err = computeOperationWaitRegion(config, op, project, region, "Deleting Forwarding Rule") + err = computeOperationWaitRegionBeta(config, op, project, region, "Deleting Forwarding Rule") if err != nil { return err } diff --git a/resource_compute_forwarding_rule_test.go b/resource_compute_forwarding_rule_test.go index 08e9fa51..3e69c62f 100644 --- a/resource_compute_forwarding_rule_test.go +++ b/resource_compute_forwarding_rule_test.go @@ -50,6 +50,27 @@ func TestAccComputeForwardingRule_ip(t *testing.T) { }) } +func TestAccComputeForwardingRule_internalLoadBalancing(t *testing.T) { + serviceName := fmt.Sprintf("tf-%s", acctest.RandString(10)) + checkName := fmt.Sprintf("tf-%s", acctest.RandString(10)) + ruleName := fmt.Sprintf("tf-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeForwardingRuleDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeForwardingRule_internalLoadBalancing(serviceName, checkName, ruleName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeForwardingRuleExists( + "google_compute_forwarding_rule.foobar"), + ), + }, + }, + }) +} + func testAccCheckComputeForwardingRuleDestroy(s *terraform.State) error { config := testAccProvider.Meta().(*Config) @@ -132,3 +153,30 @@ resource "google_compute_forwarding_rule" "foobar" { } `, addrName, poolName, ruleName) } + +func testAccComputeForwardingRule_internalLoadBalancing(serviceName, checkName, ruleName string) string { + return fmt.Sprintf(` +resource "google_compute_region_backend_service" "foobar-bs" { + name = "%s" + description = "Resource created for Terraform acceptance testing" + health_checks = ["${google_compute_health_check.zero.self_link}"] + load_balancing_scheme = "INTERNAL" +} +resource "google_compute_health_check" "zero" { + name = "%s" + description = "Resource created for Terraform acceptance testing" + check_interval_sec = 1 + timeout_sec = 1 + + tcp_health_check { + port = "80" + } +} +resource "google_compute_forwarding_rule" "foobar" { + description = "Resource created for Terraform acceptance testing" + name = "%s" + load_balancing_scheme = "INTERNAL" + backend_service = "${google_compute_region_backend_service.foobar-bs.self_link}" +} +`, serviceName, checkName, ruleName) +} diff --git a/resource_compute_health_check.go b/resource_compute_health_check.go new file mode 100644 index 00000000..d3c288eb --- /dev/null +++ b/resource_compute_health_check.go @@ -0,0 +1,310 @@ +package google + +import ( + "fmt" + "log" + + "github.com/hashicorp/terraform/helper/schema" + "google.golang.org/api/compute/v1" + "google.golang.org/api/googleapi" +) + +func resourceComputeHealthCheck() *schema.Resource { + return &schema.Resource{ + Create: resourceComputeHealthCheckCreate, + Read: resourceComputeHealthCheckRead, + Delete: resourceComputeHealthCheckDelete, + Update: resourceComputeHealthCheckUpdate, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "check_interval_sec": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Default: 5, + }, + + "description": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + + "healthy_threshold": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Default: 2, + }, + + "type": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "TCP", + }, + + "tcp_health_check": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "port": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Default: 80, + }, + "port_name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "proxy_header": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "NONE", + }, + "request": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "response": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "project": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + }, + + "self_link": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + + "timeout_sec": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Default: 5, + }, + + "unhealthy_threshold": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Default: 2, + }, + }, + } +} + +func resourceComputeHealthCheckCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + project, err := getProject(d, config) + if err != nil { + return err + } + + // Build the parameter + hchk := &compute.HealthCheck{ + Name: d.Get("name").(string), + } + // Optional things + if v, ok := d.GetOk("description"); ok { + hchk.Description = v.(string) + } + if v, ok := d.GetOk("check_interval_sec"); ok { + hchk.CheckIntervalSec = int64(v.(int)) + } + if v, ok := d.GetOk("healthy_threshold"); ok { + hchk.HealthyThreshold = int64(v.(int)) + } + if v, ok := d.GetOk("timeout_sec"); ok { + hchk.TimeoutSec = int64(v.(int)) + } + if v, ok := d.GetOk("unhealthy_threshold"); ok { + hchk.UnhealthyThreshold = int64(v.(int)) + } + if v, ok := d.GetOk("type"); ok { + hchk.Type = v.(string) + } + if v, ok := d.GetOk("tcp_health_check"); ok { + // check that type is tcp? + tcpcheck := v.([]interface{})[0].(map[string]interface{}) + tcpHealthCheck := &compute.TCPHealthCheck{} + if val, ok := tcpcheck["port"]; ok { + tcpHealthCheck.Port = int64(val.(int)) + } + if val, ok := tcpcheck["port_name"]; ok { + tcpHealthCheck.PortName = val.(string) + } + if val, ok := tcpcheck["proxy_header"]; ok { + tcpHealthCheck.ProxyHeader = val.(string) + } + if val, ok := tcpcheck["request"]; ok { + tcpHealthCheck.Request = val.(string) + } + if val, ok := tcpcheck["response"]; ok { + tcpHealthCheck.Response = val.(string) + } + hchk.TcpHealthCheck = tcpHealthCheck + } + + log.Printf("[DEBUG] HealthCheck insert request: %#v", hchk) + op, err := config.clientCompute.HealthChecks.Insert( + project, hchk).Do() + if err != nil { + return fmt.Errorf("Error creating HealthCheck: %s", err) + } + + // It probably maybe worked, so store the ID now + d.SetId(hchk.Name) + + err = computeOperationWaitGlobal(config, op, project, "Creating Health Check") + if err != nil { + return err + } + + return resourceComputeHealthCheckRead(d, meta) +} + +func resourceComputeHealthCheckUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + project, err := getProject(d, config) + if err != nil { + return err + } + + // Build the parameter + hchk := &compute.HealthCheck{ + Name: d.Get("name").(string), + } + // Optional things + if v, ok := d.GetOk("description"); ok { + hchk.Description = v.(string) + } + if v, ok := d.GetOk("check_interval_sec"); ok { + hchk.CheckIntervalSec = int64(v.(int)) + } + if v, ok := d.GetOk("healthy_threshold"); ok { + hchk.HealthyThreshold = int64(v.(int)) + } + if v, ok := d.GetOk("timeout_sec"); ok { + hchk.TimeoutSec = int64(v.(int)) + } + if v, ok := d.GetOk("unhealthy_threshold"); ok { + hchk.UnhealthyThreshold = int64(v.(int)) + } + if v, ok := d.GetOk("type"); ok { + hchk.Type = v.(string) + } + if v, ok := d.GetOk("tcp_health_check"); ok { + // check that type is tcp? + tcpcheck := v.([]interface{})[0].(map[string]interface{}) + var tcpHealthCheck *compute.TCPHealthCheck + if val, ok := tcpcheck["port"]; ok { + tcpHealthCheck.Port = int64(val.(int)) + } + if val, ok := tcpcheck["port_name"]; ok { + tcpHealthCheck.PortName = val.(string) + } + if val, ok := tcpcheck["proxy_header"]; ok { + tcpHealthCheck.ProxyHeader = val.(string) + } + if val, ok := tcpcheck["request"]; ok { + tcpHealthCheck.Request = val.(string) + } + if val, ok := tcpcheck["response"]; ok { + tcpHealthCheck.Response = val.(string) + } + hchk.TcpHealthCheck = tcpHealthCheck + } + + log.Printf("[DEBUG] HealthCheck patch request: %#v", hchk) + op, err := config.clientCompute.HealthChecks.Patch( + project, hchk.Name, hchk).Do() + if err != nil { + return fmt.Errorf("Error patching HealthCheck: %s", err) + } + + // It probably maybe worked, so store the ID now + d.SetId(hchk.Name) + + err = computeOperationWaitGlobal(config, op, project, "Updating Health Check") + if err != nil { + return err + } + + return resourceComputeHealthCheckRead(d, meta) +} + +func resourceComputeHealthCheckRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + project, err := getProject(d, config) + if err != nil { + return err + } + + hchk, err := config.clientCompute.HealthChecks.Get( + project, d.Id()).Do() + if err != nil { + if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 { + // The resource doesn't exist anymore + log.Printf("[WARN] Removing Health Check %q because it's gone", d.Get("name").(string)) + d.SetId("") + + return nil + } + + return fmt.Errorf("Error reading HealthCheck: %s", err) + } + + d.Set("check_interval_sec", hchk.CheckIntervalSec) + d.Set("healthy_threshold", hchk.HealthyThreshold) + d.Set("timeout_sec", hchk.TimeoutSec) + d.Set("unhealthy_threshold", hchk.UnhealthyThreshold) + d.Set("type", hchk.Type) + d.Set("tcp_health_check", hchk.TcpHealthCheck) + d.Set("self_link", hchk.SelfLink) + d.Set("name", hchk.Name) + d.Set("description", hchk.Description) + d.Set("project", project) + + return nil +} + +func resourceComputeHealthCheckDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + project, err := getProject(d, config) + if err != nil { + return err + } + + // Delete the HealthCheck + op, err := config.clientCompute.HealthChecks.Delete( + project, d.Id()).Do() + if err != nil { + return fmt.Errorf("Error deleting HealthCheck: %s", err) + } + + err = computeOperationWaitGlobal(config, op, project, "Deleting Health Check") + if err != nil { + return err + } + + d.SetId("") + return nil +} diff --git a/resource_compute_health_check_test.go b/resource_compute_health_check_test.go new file mode 100644 index 00000000..493b7936 --- /dev/null +++ b/resource_compute_health_check_test.go @@ -0,0 +1,156 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "google.golang.org/api/compute/v1" +) + +func TestAccComputeHealthCheck_basic(t *testing.T) { + var healthCheck compute.HealthCheck + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeHealthCheckDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeHealthCheck_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeHealthCheckExists( + "google_compute_health_check.foobar", &healthCheck), + testAccCheckComputeHealthCheckThresholds( + 3, 3, &healthCheck), + ), + }, + }, + }) +} + +func TestAccComputeHealthCheck_update(t *testing.T) { + var healthCheck compute.HealthCheck + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeHealthCheckDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeHealthCheck_update1, + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeHealthCheckExists( + "google_compute_health_check.foobar", &healthCheck), + testAccCheckComputeHealthCheckThresholds( + 2, 2, &healthCheck), + ), + }, + resource.TestStep{ + Config: testAccComputeHealthCheck_update2, + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeHealthCheckExists( + "google_compute_health_check.foobar", &healthCheck), + testAccCheckComputeHealthCheckThresholds( + 10, 10, &healthCheck), + ), + }, + }, + }) +} + +func testAccCheckComputeHealthCheckDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_compute_health_check" { + continue + } + + _, err := config.clientCompute.HealthChecks.Get( + config.Project, rs.Primary.ID).Do() + if err == nil { + return fmt.Errorf("HealthCheck still exists") + } + } + + return nil +} + +func testAccCheckComputeHealthCheckExists(n string, healthCheck *compute.HealthCheck) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + config := testAccProvider.Meta().(*Config) + + found, err := config.clientCompute.HealthChecks.Get( + config.Project, rs.Primary.ID).Do() + if err != nil { + return err + } + + if found.Name != rs.Primary.ID { + return fmt.Errorf("HealthCheck not found") + } + + *healthCheck = *found + + return nil + } +} + +func testAccCheckComputeHealthCheckThresholds(healthy, unhealthy int64, healthCheck *compute.HealthCheck) resource.TestCheckFunc { + return func(s *terraform.State) error { + if healthCheck.HealthyThreshold != healthy { + return fmt.Errorf("HealthyThreshold doesn't match: expected %d, got %d", healthy, healthCheck.HealthyThreshold) + } + + if healthCheck.UnhealthyThreshold != unhealthy { + return fmt.Errorf("UnhealthyThreshold doesn't match: expected %d, got %d", unhealthy, healthCheck.UnhealthyThreshold) + } + + return nil + } +} + +var testAccComputeHealthCheck_basic = fmt.Sprintf(` +resource "google_compute_health_check" "foobar" { + check_interval_sec = 3 + description = "Resource created for Terraform acceptance testing" + healthy_threshold = 3 + name = "health-test-%s" + timeout_sec = 2 + unhealthy_threshold = 3 + tcp_health_check { + port = "80" + } +} +`, acctest.RandString(10)) + +var testAccComputeHealthCheck_update1 = fmt.Sprintf(` +resource "google_compute_health_check" "foobar" { + name = "Health-test-%s" + description = "Resource created for Terraform acceptance testing" + request_path = "/not_default" +} +`, acctest.RandString(10)) + +/* Change description, restore request_path to default, and change +* thresholds from defaults */ +var testAccComputeHealthCheck_update2 = fmt.Sprintf(` +resource "google_compute_health_check" "foobar" { + name = "Health-test-%s" + description = "Resource updated for Terraform acceptance testing" + healthy_threshold = 10 + unhealthy_threshold = 10 +} +`, acctest.RandString(10)) diff --git a/resource_compute_region_backend_service.go b/resource_compute_region_backend_service.go new file mode 100644 index 00000000..dd8aa1ce --- /dev/null +++ b/resource_compute_region_backend_service.go @@ -0,0 +1,443 @@ +package google + +import ( + "bytes" + "fmt" + "log" + "os" + "regexp" + + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/schema" + "google.golang.org/api/compute/v0.beta" + "google.golang.org/api/googleapi" +) + +func resourceComputeRegionBackendService() *schema.Resource { + return &schema.Resource{ + Create: resourceComputeRegionBackendServiceCreate, + Read: resourceComputeRegionBackendServiceRead, + Update: resourceComputeRegionBackendServiceUpdate, + Delete: resourceComputeRegionBackendServiceDelete, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + re := `^(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?)$` + if !regexp.MustCompile(re).MatchString(value) { + errors = append(errors, fmt.Errorf( + "%q (%q) doesn't match regexp %q", k, value, re)) + } + return + }, + }, + + "health_checks": &schema.Schema{ + Type: schema.TypeSet, + Elem: &schema.Schema{Type: schema.TypeString}, + Required: true, + Set: schema.HashString, + }, + + "backend": &schema.Schema{ + Type: schema.TypeSet, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "group": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "balancing_mode": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "UTILIZATION", + }, + "capacity_scaler": &schema.Schema{ + Type: schema.TypeFloat, + Optional: true, + Default: 1, + }, + "description": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "max_rate": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + }, + "max_rate_per_instance": &schema.Schema{ + Type: schema.TypeFloat, + Optional: true, + }, + "max_utilization": &schema.Schema{ + Type: schema.TypeFloat, + Optional: true, + Default: 0.8, + }, + }, + }, + Optional: true, + Set: resourceGoogleComputeRegionBackendServiceBackendHash, + }, + + "description": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + + "enable_cdn": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + + "fingerprint": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + + "load_balancing_scheme": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "port_name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "project": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "protocol": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "region": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "self_link": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + + "timeout_sec": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + }, + } +} + +func resourceComputeRegionBackendServiceCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + hc := d.Get("health_checks").(*schema.Set).List() + healthChecks := make([]string, 0, len(hc)) + for _, v := range hc { + healthChecks = append(healthChecks, v.(string)) + } + + service := compute.BackendService{ + Name: d.Get("name").(string), + HealthChecks: healthChecks, + } + + if v, ok := d.GetOk("backend"); ok { + service.Backends = expandBackends(v.(*schema.Set).List()) + } + + if v, ok := d.GetOk("description"); ok { + service.Description = v.(string) + } + + if v, ok := d.GetOk("port_name"); ok { + service.PortName = v.(string) + } + + if v, ok := d.GetOk("protocol"); ok { + service.Protocol = v.(string) + } + + if v, ok := d.GetOk("timeout_sec"); ok { + service.TimeoutSec = int64(v.(int)) + } + + if v, ok := d.GetOk("enable_cdn"); ok { + service.EnableCDN = v.(bool) + } + + if v, ok := d.GetOk("load_balancing_scheme"); ok { + service.LoadBalancingScheme = v.(string) + } + + project, err := getProject(d, config) + if err != nil { + return err + } + + region, err := getRegion(d, config) + if err != nil { + return err + } + + fmt.Fprintf(os.Stderr, "[DEBUG] Creating new Region Backend Service: %#v", service) // DO NOT SUBMIT + log.Printf("[DEBUG] Creating new Region Backend Service: %#v", service) + + op, err := config.clientComputeBeta.RegionBackendServices.Insert( + project, region, &service).Do() + if err != nil { + return fmt.Errorf("Error creating backend service: %s", err) + } + + log.Printf("[DEBUG] Waiting for new backend service, operation: %#v", op) + + d.SetId(service.Name) + + err = computeOperationWaitGlobalBeta(config, op, project, "Creating Backend Service") + if err != nil { + return err + } + + return resourceComputeRegionBackendServiceRead(d, meta) +} + +func resourceComputeRegionBackendServiceRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + project, err := getProject(d, config) + if err != nil { + return err + } + + region, err := getRegion(d, config) + if err != nil { + return err + } + + service, err := config.clientComputeBeta.RegionBackendServices.Get( + project, region, d.Id()).Do() + if err != nil { + if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 { + // The resource doesn't exist anymore + log.Printf("[WARN] Removing Backend Service %q because it's gone", d.Get("name").(string)) + d.SetId("") + + return nil + } + + return fmt.Errorf("Error reading service: %s", err) + } + + d.Set("description", service.Description) + d.Set("enable_cdn", service.EnableCDN) + d.Set("port_name", service.PortName) + d.Set("protocol", service.Protocol) + d.Set("timeout_sec", service.TimeoutSec) + d.Set("fingerprint", service.Fingerprint) + d.Set("load_balancing_scheme", service.LoadBalancingScheme) + d.Set("self_link", service.SelfLink) + + d.Set("backend", flattenBackends(service.Backends)) + d.Set("health_checks", service.HealthChecks) + + return nil +} + +func resourceComputeRegionBackendServiceUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + project, err := getProject(d, config) + if err != nil { + return err + } + + region, err := getRegion(d, config) + if err != nil { + return err + } + + hc := d.Get("health_checks").(*schema.Set).List() + healthChecks := make([]string, 0, len(hc)) + for _, v := range hc { + healthChecks = append(healthChecks, v.(string)) + } + + service := compute.BackendService{ + Name: d.Get("name").(string), + Fingerprint: d.Get("fingerprint").(string), + HealthChecks: healthChecks, + } + + // Optional things + if v, ok := d.GetOk("backend"); ok { + service.Backends = expandBackends(v.(*schema.Set).List()) + } + if v, ok := d.GetOk("description"); ok { + service.Description = v.(string) + } + if v, ok := d.GetOk("port_name"); ok { + service.PortName = v.(string) + } + if v, ok := d.GetOk("protocol"); ok { + service.Protocol = v.(string) + } + if v, ok := d.GetOk("timeout_sec"); ok { + service.TimeoutSec = int64(v.(int)) + } + + if v, ok := d.GetOk("load_balancing_scheme"); ok { + service.LoadBalancingScheme = v.(string) + } + + if d.HasChange("enable_cdn") { + service.EnableCDN = d.Get("enable_cdn").(bool) + } + + log.Printf("[DEBUG] Updating existing Backend Service %q: %#v", d.Id(), service) + op, err := config.clientComputeBeta.RegionBackendServices.Update( + project, region, d.Id(), &service).Do() + if err != nil { + return fmt.Errorf("Error updating backend service: %s", err) + } + + d.SetId(service.Name) + + err = computeOperationWaitGlobalBeta(config, op, project, "Updating Backend Service") + if err != nil { + return err + } + + return resourceComputeRegionBackendServiceRead(d, meta) +} + +func resourceComputeRegionBackendServiceDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + project, err := getProject(d, config) + if err != nil { + return err + } + + region, err := getRegion(d, config) + if err != nil { + return err + } + + log.Printf("[DEBUG] Deleting backend service %s", d.Id()) + op, err := config.clientComputeBeta.RegionBackendServices.Delete( + project, region, d.Id()).Do() + if err != nil { + return fmt.Errorf("Error deleting backend service: %s", err) + } + + err = computeOperationWaitGlobalBeta(config, op, project, "Deleting Backend Service") + if err != nil { + return err + } + + d.SetId("") + return nil +} + +// func expandBackends(configured []interface{}) []*compute.Backend { +// backends := make([]*compute.Backend, 0, len(configured)) + +// for _, raw := range configured { +// data := raw.(map[string]interface{}) + +// b := compute.Backend{ +// Group: data["group"].(string), +// } + +// if v, ok := data["balancing_mode"]; ok { +// b.BalancingMode = v.(string) +// } +// if v, ok := data["capacity_scaler"]; ok { +// b.CapacityScaler = v.(float64) +// } +// if v, ok := data["description"]; ok { +// b.Description = v.(string) +// } +// if v, ok := data["max_rate"]; ok { +// b.MaxRate = int64(v.(int)) +// } +// if v, ok := data["max_rate_per_instance"]; ok { +// b.MaxRatePerInstance = v.(float64) +// } +// if v, ok := data["max_utilization"]; ok { +// b.MaxUtilization = v.(float64) +// } + +// backends = append(backends, &b) +// } + +// return backends +// } + +// func flattenBackends(backends []*compute.Backend) []map[string]interface{} { +// result := make([]map[string]interface{}, 0, len(backends)) + +// for _, b := range backends { +// data := make(map[string]interface{}) + +// data["balancing_mode"] = b.BalancingMode +// data["capacity_scaler"] = b.CapacityScaler +// data["description"] = b.Description +// data["group"] = b.Group +// data["max_rate"] = b.MaxRate +// data["max_rate_per_instance"] = b.MaxRatePerInstance +// data["max_utilization"] = b.MaxUtilization + +// result = append(result, data) +// } + +// return result +// } + +func resourceGoogleComputeRegionBackendServiceBackendHash(v interface{}) int { + if v == nil { + return 0 + } + + var buf bytes.Buffer + m := v.(map[string]interface{}) + + buf.WriteString(fmt.Sprintf("%s-", m["group"].(string))) + + if v, ok := m["balancing_mode"]; ok { + buf.WriteString(fmt.Sprintf("%s-", v.(string))) + } + if v, ok := m["capacity_scaler"]; ok { + buf.WriteString(fmt.Sprintf("%f-", v.(float64))) + } + if v, ok := m["description"]; ok { + buf.WriteString(fmt.Sprintf("%s-", v.(string))) + } + if v, ok := m["max_rate"]; ok { + buf.WriteString(fmt.Sprintf("%d-", int64(v.(int)))) + } + if v, ok := m["max_rate_per_instance"]; ok { + buf.WriteString(fmt.Sprintf("%f-", v.(float64))) + } + if v, ok := m["max_rate_per_instance"]; ok { + buf.WriteString(fmt.Sprintf("%f-", v.(float64))) + } + + return hashcode.String(buf.String()) +} diff --git a/resource_compute_region_backend_service_test.go b/resource_compute_region_backend_service_test.go new file mode 100644 index 00000000..e60ebdc0 --- /dev/null +++ b/resource_compute_region_backend_service_test.go @@ -0,0 +1,346 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "google.golang.org/api/compute/v0.beta" +) + +func TestAccComputeRegionBackendService_basic(t *testing.T) { + serviceName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + checkName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + extraCheckName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + var svc compute.BackendService + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeRegionBackendServiceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeRegionBackendService_basic(serviceName, checkName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeRegionBackendServiceExists( + "google_compute_region_backend_service.foobar", &svc), + ), + }, + resource.TestStep{ + Config: testAccComputeRegionBackendService_basicModified( + serviceName, checkName, extraCheckName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeRegionBackendServiceExists( + "google_compute_region_backend_service.foobar", &svc), + ), + }, + }, + }) +} + +func TestAccComputeRegionBackendService_withBackend(t *testing.T) { + serviceName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + igName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + itName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + checkName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + var svc compute.BackendService + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeRegionBackendServiceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeRegionBackendService_withBackend( + serviceName, igName, itName, checkName, 10), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeRegionBackendServiceExists( + "google_compute_region_backend_service.lipsum", &svc), + ), + }, + }, + }) + + if svc.TimeoutSec != 10 { + t.Errorf("Expected TimeoutSec == 10, got %d", svc.TimeoutSec) + } + if svc.Protocol != "HTTP" { + t.Errorf("Expected Protocol to be HTTP, got %q", svc.Protocol) + } + if len(svc.Backends) != 1 { + t.Errorf("Expected 1 backend, got %d", len(svc.Backends)) + } +} + +func TestAccComputeRegionBackendService_withBackendAndUpdate(t *testing.T) { + serviceName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + igName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + itName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + checkName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + var svc compute.BackendService + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeRegionBackendServiceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeRegionBackendService_withBackend( + serviceName, igName, itName, checkName, 10), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeRegionBackendServiceExists( + "google_compute_region_backend_service.lipsum", &svc), + ), + }, + resource.TestStep{ + Config: testAccComputeRegionBackendService_withBackend( + serviceName, igName, itName, checkName, 20), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeRegionBackendServiceExists( + "google_compute_region_backend_service.lipsum", &svc), + ), + }, + }, + }) + + if svc.TimeoutSec != 20 { + t.Errorf("Expected TimeoutSec == 20, got %d", svc.TimeoutSec) + } + if svc.Protocol != "HTTP" { + t.Errorf("Expected Protocol to be HTTP, got %q", svc.Protocol) + } + if len(svc.Backends) != 1 { + t.Errorf("Expected 1 backend, got %d", len(svc.Backends)) + } +} + +func testAccCheckComputeRegionBackendServiceDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_compute_region_backend_service" { + continue + } + + _, err := config.clientComputeBeta.RegionBackendServices.Get( + config.Project, config.Region, rs.Primary.ID).Do() + if err == nil { + return fmt.Errorf("Backend service still exists") + } + } + + return nil +} + +func testAccCheckComputeRegionBackendServiceExists(n string, svc *compute.BackendService) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + config := testAccProvider.Meta().(*Config) + + found, err := config.clientComputeBeta.RegionBackendServices.Get( + config.Project, config.Region, rs.Primary.ID).Do() + if err != nil { + return err + } + + if found.Name != rs.Primary.ID { + return fmt.Errorf("Backend service not found") + } + + *svc = *found + + return nil + } +} + +func TestAccComputeRegionBackendService_withCDNEnabled(t *testing.T) { + serviceName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + checkName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + var svc compute.BackendService + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeRegionBackendServiceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeRegionBackendService_withCDNEnabled( + serviceName, checkName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeRegionBackendServiceExists( + "google_compute_region_backend_service.foobar", &svc), + ), + }, + }, + }) + + if svc.EnableCDN != true { + t.Errorf("Expected EnableCDN == true, got %t", svc.EnableCDN) + } +} + +func TestAccComputeRegionBackendService_withInternalLoadBalancing(t *testing.T) { + serviceName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + checkName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + var svc compute.BackendService + + // config := testAccProvider.Meta().(*Config) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeRegionBackendServiceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeRegionBackendService_withInternalLoadBalancing( + serviceName, checkName, "us-central1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeRegionBackendServiceExists( + "google_compute_region_backend_service.foobar", &svc), + ), + }, + }, + }) + + if svc.LoadBalancingScheme != "INTERNAL" { + t.Errorf("Expected LoadBalancingScheme == INTERNAL, got %q", svc.EnableCDN) + } +} + +func testAccComputeRegionBackendService_basic(serviceName, checkName string) string { + return fmt.Sprintf(` +resource "google_compute_region_backend_service" "foobar" { + name = "%s" + health_checks = ["${google_compute_health_check.zero.self_link}"] + load_balancing_scheme = "INTERNAL" +} + +resource "google_compute_health_check" "zero" { + name = "%s" + check_interval_sec = 1 + timeout_sec = 1 + + tcp_health_check { + port = "80" + } +} +`, serviceName, checkName) +} + +func testAccComputeRegionBackendService_withCDNEnabled(serviceName, checkName string) string { + return fmt.Sprintf(` +resource "google_compute_region_backend_service" "foobar" { + name = "%s" + health_checks = ["${google_compute_http_health_check.zero.self_link}"] + enable_cdn = true +} + +resource "google_compute_http_health_check" "zero" { + name = "%s" + request_path = "/" + check_interval_sec = 1 + timeout_sec = 1 +} +`, serviceName, checkName) +} + +func testAccComputeRegionBackendService_withInternalLoadBalancing(serviceName, checkName, region string) string { + + return fmt.Sprintf(` +resource "google_compute_region_backend_service" "foobar" { + name = "%s" + health_checks = ["${google_compute_health_check.zero.self_link}"] + load_balancing_scheme = "INTERNAL" +} + +resource "google_compute_health_check" "zero" { + name = "%s" + check_interval_sec = 1 + timeout_sec = 1 + + tcp_health_check { + port = "80" + } +} +`, serviceName, region, checkName) +} + +func testAccComputeRegionBackendService_basicModified(serviceName, checkOne, checkTwo string) string { + return fmt.Sprintf(` +resource "google_compute_region_backend_service" "foobar" { + name = "%s" + health_checks = ["${google_compute_http_health_check.one.self_link}"] +} + +resource "google_compute_http_health_check" "zero" { + name = "%s" + request_path = "/" + check_interval_sec = 1 + timeout_sec = 1 +} + +resource "google_compute_http_health_check" "one" { + name = "%s" + request_path = "/one" + check_interval_sec = 30 + timeout_sec = 30 +} +`, serviceName, checkOne, checkTwo) +} + +func testAccComputeRegionBackendService_withBackend( + serviceName, igName, itName, checkName string, timeout int64) string { + return fmt.Sprintf(` +resource "google_compute_region_backend_service" "lipsum" { + name = "%s" + description = "Hello World 1234" + port_name = "http" + protocol = "HTTP" + timeout_sec = %v + + backend { + group = "${google_compute_instance_group_manager.foobar.instance_group}" + } + + health_checks = ["${google_compute_http_health_check.default.self_link}"] +} + +resource "google_compute_instance_group_manager" "foobar" { + name = "%s" + instance_template = "${google_compute_instance_template.foobar.self_link}" + base_instance_name = "foobar" + zone = "us-central1-f" + target_size = 1 +} + +resource "google_compute_instance_template" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + + network_interface { + network = "default" + } + + disk { + source_image = "debian-8-jessie-v20160803" + auto_delete = true + boot = true + } +} + +resource "google_compute_http_health_check" "default" { + name = "%s" + request_path = "/" + check_interval_sec = 1 + timeout_sec = 1 +} +`, serviceName, timeout, igName, itName, checkName) +}