diff --git a/builtin/providers/google/config.go b/builtin/providers/google/config.go index 65c5b26bc..31d5ff468 100644 --- a/builtin/providers/google/config.go +++ b/builtin/providers/google/config.go @@ -7,6 +7,7 @@ import ( "net/http" "os" + "code.google.com/p/google-api-go-client/autoscaler/v1beta2" "code.google.com/p/google-api-go-client/compute/v1" "code.google.com/p/google-api-go-client/replicapool/v1beta2" @@ -24,6 +25,7 @@ type Config struct { clientCompute *compute.Service clientReplicaPool *replicapool.Service + clientAutoscaler *autoscaler.Service } func (c *Config) loadAndValidate() error { @@ -95,6 +97,11 @@ func (c *Config) loadAndValidate() error { return err } + c.clientAutoscaler, err = autoscaler.New(client) + if err != nil { + return err + } + return nil } diff --git a/builtin/providers/google/operation.go b/builtin/providers/google/operation.go index 141a7684c..286804c9c 100644 --- a/builtin/providers/google/operation.go +++ b/builtin/providers/google/operation.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" + "code.google.com/p/google-api-go-client/autoscaler/v1beta2" "code.google.com/p/google-api-go-client/compute/v1" "code.google.com/p/google-api-go-client/replicapool/v1beta2" "github.com/hashicorp/terraform/helper/resource" @@ -125,3 +126,49 @@ func (e ReplicaPoolOperationError) Error() string { return buf.String() } + +// Autoscaler Operations +type AutoscalerOperationWaiter struct { + Service *autoscaler.Service + Op *autoscaler.Operation + Project string + Zone string +} + +func (w *AutoscalerOperationWaiter) RefreshFunc() resource.StateRefreshFunc { + return func() (interface{}, string, error) { + var op *autoscaler.Operation + var err error + + op, err = w.Service.ZoneOperations.Get( + w.Project, w.Zone, w.Op.Name).Do() + + if err != nil { + return nil, "", err + } + + return op, op.Status, nil + } +} + +func (w *AutoscalerOperationWaiter) Conf() *resource.StateChangeConf { + return &resource.StateChangeConf{ + Pending: []string{"PENDING", "RUNNING"}, + Target: "DONE", + Refresh: w.RefreshFunc(), + } +} + +// AutoscalerOperationError wraps autoscaler.OperationError and implements the +// error interface so it can be returned. +type AutoscalerOperationError autoscaler.OperationError + +func (e AutoscalerOperationError) Error() string { + var buf bytes.Buffer + + for _, err := range e.Errors { + buf.WriteString(err.Message + "\n") + } + + return buf.String() +} diff --git a/builtin/providers/google/provider.go b/builtin/providers/google/provider.go index cf4093aa7..be39f4e8f 100644 --- a/builtin/providers/google/provider.go +++ b/builtin/providers/google/provider.go @@ -29,6 +29,7 @@ func Provider() terraform.ResourceProvider { }, ResourcesMap: map[string]*schema.Resource{ + "google_autoscaler": resourceAutoscaler(), "google_compute_address": resourceComputeAddress(), "google_compute_disk": resourceComputeDisk(), "google_compute_firewall": resourceComputeFirewall(), diff --git a/builtin/providers/google/resource_autoscaler.go b/builtin/providers/google/resource_autoscaler.go new file mode 100644 index 000000000..ee9222577 --- /dev/null +++ b/builtin/providers/google/resource_autoscaler.go @@ -0,0 +1,366 @@ +package google + +import ( + "fmt" + "log" + "time" + + "code.google.com/p/google-api-go-client/autoscaler/v1beta2" + "code.google.com/p/google-api-go-client/googleapi" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAutoscaler() *schema.Resource { + return &schema.Resource{ + Create: resourceAutoscalerCreate, + Read: resourceAutoscalerRead, + Update: resourceAutoscalerUpdate, + Delete: resourceAutoscalerDelete, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + ForceNew: true, + Required: true, + }, + + "description": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + + "target": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "autoscaling_policy": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "min_replicas": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + }, + + "max_replicas": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + }, + + "cooldown_period": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + }, + + "cpu_utilization": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "target": &schema.Schema{ + Type: schema.TypeFloat, + Required: true, + }, + }, + }, + }, + + "metric": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "target": &schema.Schema{ + Type: schema.TypeFloat, + Required: true, + }, + + "type": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + + "load_balancing_utilization": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "target": &schema.Schema{ + Type: schema.TypeFloat, + Required: true, + }, + }, + }, + }, + }, + }, + }, + + "zone": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "self_link": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceAutoscalerCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + // Get the zone + log.Printf("[DEBUG] Loading zone: %s", d.Get("zone").(string)) + zone, err := config.clientCompute.Zones.Get( + config.Project, d.Get("zone").(string)).Do() + if err != nil { + return fmt.Errorf( + "Error loading zone '%s': %s", d.Get("zone").(string), err) + } + + // Build the parameter + scaler := &autoscaler.Autoscaler{ + Name: d.Get("name").(string), + Target: d.Get("target").(string), + } + + // Optional fields + if v, ok := d.GetOk("description"); ok { + scaler.Description = v.(string) + } + + prefix := "autoscaling_policy.0." + + scaler.AutoscalingPolicy = &autoscaler.AutoscalingPolicy{ + MaxNumReplicas: int64(d.Get(prefix + "max_replicas").(int)), + MinNumReplicas: int64(d.Get(prefix + "min_replicas").(int)), + CoolDownPeriodSec: int64(d.Get(prefix + "cooldown_period").(int)), + } + + // Check that only one autoscaling policy is defined + + policyCounter := 0 + if _, ok := d.GetOk(prefix + "cpu_utilization"); ok { + policyCounter++ + scaler.AutoscalingPolicy.CpuUtilization = &autoscaler.AutoscalingPolicyCpuUtilization{ + UtilizationTarget: d.Get(prefix + "cpu_utilization.0.target").(float64), + } + } + if _, ok := d.GetOk("autoscaling_policy.0.metric"); ok { + policyCounter++ + scaler.AutoscalingPolicy.CustomMetricUtilizations = []*autoscaler.AutoscalingPolicyCustomMetricUtilization{ + { + Metric: d.Get(prefix + "metric.0.name").(string), + UtilizationTarget: d.Get(prefix + "metric.0.target").(float64), + UtilizationTargetType: d.Get(prefix + "metric.0.type").(string), + }, + } + + } + if _, ok := d.GetOk("autoscaling_policy.0.load_balancing_utilization"); ok { + policyCounter++ + scaler.AutoscalingPolicy.LoadBalancingUtilization = &autoscaler.AutoscalingPolicyLoadBalancingUtilization{ + UtilizationTarget: d.Get(prefix + "load_balancing_utilization.0.target").(float64), + } + } + + if policyCounter != 1 { + return fmt.Errorf("One policy must be defined for an autoscaler.") + } + + op, err := config.clientAutoscaler.Autoscalers.Insert( + config.Project, zone.Name, scaler).Do() + if err != nil { + return fmt.Errorf("Error creating Autoscaler: %s", err) + } + + // It probably maybe worked, so store the ID now + d.SetId(scaler.Name) + + // Wait for the operation to complete + w := &AutoscalerOperationWaiter{ + Service: config.clientAutoscaler, + Op: op, + Project: config.Project, + Zone: zone.Name, + } + state := w.Conf() + state.Timeout = 2 * time.Minute + state.MinTimeout = 1 * time.Second + opRaw, err := state.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for Autoscaler to create: %s", err) + } + op = opRaw.(*autoscaler.Operation) + if op.Error != nil { + // The resource didn't actually create + d.SetId("") + + // Return the error + return AutoscalerOperationError(*op.Error) + } + + return resourceAutoscalerRead(d, meta) +} + +func resourceAutoscalerRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + zone := d.Get("zone").(string) + scaler, err := config.clientAutoscaler.Autoscalers.Get( + config.Project, zone, d.Id()).Do() + if err != nil { + if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 { + // The resource doesn't exist anymore + d.SetId("") + + return nil + } + + return fmt.Errorf("Error reading Autoscaler: %s", err) + } + + d.Set("self_link", scaler.SelfLink) + + return nil +} + +func resourceAutoscalerUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + zone := d.Get("zone").(string) + + // Build the parameter + scaler := &autoscaler.Autoscaler{ + Name: d.Get("name").(string), + Target: d.Get("target").(string), + } + + // Optional fields + if v, ok := d.GetOk("description"); ok { + scaler.Description = v.(string) + } + + prefix := "autoscaling_policy.0." + + scaler.AutoscalingPolicy = &autoscaler.AutoscalingPolicy{ + MaxNumReplicas: int64(d.Get(prefix + "max_replicas").(int)), + MinNumReplicas: int64(d.Get(prefix + "min_replicas").(int)), + CoolDownPeriodSec: int64(d.Get(prefix + "cooldown_period").(int)), + } + + // Check that only one autoscaling policy is defined + + policyCounter := 0 + if _, ok := d.GetOk(prefix + "cpu_utilization"); ok { + if d.Get(prefix+"cpu_utilization.0.target").(float64) != 0 { + policyCounter++ + scaler.AutoscalingPolicy.CpuUtilization = &autoscaler.AutoscalingPolicyCpuUtilization{ + UtilizationTarget: d.Get(prefix + "cpu_utilization.0.target").(float64), + } + } + } + if _, ok := d.GetOk("autoscaling_policy.0.metric"); ok { + if d.Get(prefix+"metric.0.name") != "" { + policyCounter++ + scaler.AutoscalingPolicy.CustomMetricUtilizations = []*autoscaler.AutoscalingPolicyCustomMetricUtilization{ + { + Metric: d.Get(prefix + "metric.0.name").(string), + UtilizationTarget: d.Get(prefix + "metric.0.target").(float64), + UtilizationTargetType: d.Get(prefix + "metric.0.type").(string), + }, + } + } + + } + if _, ok := d.GetOk("autoscaling_policy.0.load_balancing_utilization"); ok { + if d.Get(prefix+"load_balancing_utilization.0.target").(float64) != 0 { + policyCounter++ + scaler.AutoscalingPolicy.LoadBalancingUtilization = &autoscaler.AutoscalingPolicyLoadBalancingUtilization{ + UtilizationTarget: d.Get(prefix + "load_balancing_utilization.0.target").(float64), + } + } + } + + if policyCounter != 1 { + return fmt.Errorf("One policy must be defined for an autoscaler.") + } + + op, err := config.clientAutoscaler.Autoscalers.Patch( + config.Project, zone, d.Id(), scaler).Do() + if err != nil { + return fmt.Errorf("Error updating Autoscaler: %s", err) + } + + // It probably maybe worked, so store the ID now + d.SetId(scaler.Name) + + // Wait for the operation to complete + w := &AutoscalerOperationWaiter{ + Service: config.clientAutoscaler, + Op: op, + Project: config.Project, + Zone: zone, + } + state := w.Conf() + state.Timeout = 2 * time.Minute + state.MinTimeout = 1 * time.Second + opRaw, err := state.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for Autoscaler to update: %s", err) + } + op = opRaw.(*autoscaler.Operation) + if op.Error != nil { + // Return the error + return AutoscalerOperationError(*op.Error) + } + + return resourceAutoscalerRead(d, meta) +} + +func resourceAutoscalerDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + zone := d.Get("zone").(string) + op, err := config.clientAutoscaler.Autoscalers.Delete( + config.Project, zone, d.Id()).Do() + if err != nil { + return fmt.Errorf("Error deleting autoscaler: %s", err) + } + + // Wait for the operation to complete + w := &AutoscalerOperationWaiter{ + Service: config.clientAutoscaler, + Op: op, + Project: config.Project, + Zone: zone, + } + state := w.Conf() + state.Timeout = 2 * time.Minute + state.MinTimeout = 1 * time.Second + opRaw, err := state.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for Autoscaler to delete: %s", err) + } + op = opRaw.(*autoscaler.Operation) + if op.Error != nil { + // Return the error + return AutoscalerOperationError(*op.Error) + } + + d.SetId("") + return nil +}