github.com/jsoriano/terraform@v0.6.7-0.20151026070445-8b70867fdd95/builtin/providers/aws/resource_aws_spot_instance_request.go (about)

     1  package aws
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"time"
     7  
     8  	"github.com/aws/aws-sdk-go/aws"
     9  	"github.com/aws/aws-sdk-go/aws/awserr"
    10  	"github.com/aws/aws-sdk-go/service/ec2"
    11  	"github.com/hashicorp/terraform/helper/resource"
    12  	"github.com/hashicorp/terraform/helper/schema"
    13  )
    14  
    15  func resourceAwsSpotInstanceRequest() *schema.Resource {
    16  	return &schema.Resource{
    17  		Create: resourceAwsSpotInstanceRequestCreate,
    18  		Read:   resourceAwsSpotInstanceRequestRead,
    19  		Delete: resourceAwsSpotInstanceRequestDelete,
    20  		Update: resourceAwsSpotInstanceRequestUpdate,
    21  
    22  		Schema: func() map[string]*schema.Schema {
    23  			// The Spot Instance Request Schema is based on the AWS Instance schema.
    24  			s := resourceAwsInstance().Schema
    25  
    26  			// Everything on a spot instance is ForceNew except tags
    27  			for k, v := range s {
    28  				if k == "tags" {
    29  					continue
    30  				}
    31  				v.ForceNew = true
    32  			}
    33  
    34  			s["spot_price"] = &schema.Schema{
    35  				Type:     schema.TypeString,
    36  				Required: true,
    37  				ForceNew: true,
    38  			}
    39  			s["spot_type"] = &schema.Schema{
    40  				Type:     schema.TypeString,
    41  				Optional: true,
    42  				Default:  "persistent",
    43  			}
    44  			s["wait_for_fulfillment"] = &schema.Schema{
    45  				Type:     schema.TypeBool,
    46  				Optional: true,
    47  				Default:  false,
    48  			}
    49  			s["spot_bid_status"] = &schema.Schema{
    50  				Type:     schema.TypeString,
    51  				Computed: true,
    52  			}
    53  			s["spot_request_state"] = &schema.Schema{
    54  				Type:     schema.TypeString,
    55  				Computed: true,
    56  			}
    57  			s["spot_instance_id"] = &schema.Schema{
    58  				Type:     schema.TypeString,
    59  				Computed: true,
    60  			}
    61  
    62  			return s
    63  		}(),
    64  	}
    65  }
    66  
    67  func resourceAwsSpotInstanceRequestCreate(d *schema.ResourceData, meta interface{}) error {
    68  	conn := meta.(*AWSClient).ec2conn
    69  
    70  	instanceOpts, err := buildAwsInstanceOpts(d, meta)
    71  	if err != nil {
    72  		return err
    73  	}
    74  
    75  	spotOpts := &ec2.RequestSpotInstancesInput{
    76  		SpotPrice: aws.String(d.Get("spot_price").(string)),
    77  		Type:      aws.String(d.Get("spot_type").(string)),
    78  
    79  		// Though the AWS API supports creating spot instance requests for multiple
    80  		// instances, for TF purposes we fix this to one instance per request.
    81  		// Users can get equivalent behavior out of TF's "count" meta-parameter.
    82  		InstanceCount: aws.Int64(1),
    83  
    84  		LaunchSpecification: &ec2.RequestSpotLaunchSpecification{
    85  			BlockDeviceMappings: instanceOpts.BlockDeviceMappings,
    86  			EbsOptimized:        instanceOpts.EBSOptimized,
    87  			Monitoring:          instanceOpts.Monitoring,
    88  			IamInstanceProfile:  instanceOpts.IAMInstanceProfile,
    89  			ImageId:             instanceOpts.ImageID,
    90  			InstanceType:        instanceOpts.InstanceType,
    91  			KeyName:             instanceOpts.KeyName,
    92  			Placement:           instanceOpts.SpotPlacement,
    93  			SecurityGroupIds:    instanceOpts.SecurityGroupIDs,
    94  			SecurityGroups:      instanceOpts.SecurityGroups,
    95  			SubnetId:            instanceOpts.SubnetID,
    96  			UserData:            instanceOpts.UserData64,
    97  		},
    98  	}
    99  
   100  	// Make the spot instance request
   101  	log.Printf("[DEBUG] Requesting spot bid opts: %s", spotOpts)
   102  	resp, err := conn.RequestSpotInstances(spotOpts)
   103  	if err != nil {
   104  		return fmt.Errorf("Error requesting spot instances: %s", err)
   105  	}
   106  	if len(resp.SpotInstanceRequests) != 1 {
   107  		return fmt.Errorf(
   108  			"Expected response with length 1, got: %s", resp)
   109  	}
   110  
   111  	sir := *resp.SpotInstanceRequests[0]
   112  	d.SetId(*sir.SpotInstanceRequestId)
   113  
   114  	if d.Get("wait_for_fulfillment").(bool) {
   115  		spotStateConf := &resource.StateChangeConf{
   116  			// http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/spot-bid-status.html
   117  			Pending:    []string{"start", "pending-evaluation", "pending-fulfillment"},
   118  			Target:     "fulfilled",
   119  			Refresh:    SpotInstanceStateRefreshFunc(conn, sir),
   120  			Timeout:    10 * time.Minute,
   121  			Delay:      10 * time.Second,
   122  			MinTimeout: 3 * time.Second,
   123  		}
   124  
   125  		log.Printf("[DEBUG] waiting for spot bid to resolve... this may take several minutes.")
   126  		_, err = spotStateConf.WaitForState()
   127  
   128  		if err != nil {
   129  			return fmt.Errorf("Error while waiting for spot request (%s) to resolve: %s", sir, err)
   130  		}
   131  	}
   132  
   133  	return resourceAwsSpotInstanceRequestUpdate(d, meta)
   134  }
   135  
   136  // Update spot state, etc
   137  func resourceAwsSpotInstanceRequestRead(d *schema.ResourceData, meta interface{}) error {
   138  	conn := meta.(*AWSClient).ec2conn
   139  
   140  	req := &ec2.DescribeSpotInstanceRequestsInput{
   141  		SpotInstanceRequestIds: []*string{aws.String(d.Id())},
   142  	}
   143  	resp, err := conn.DescribeSpotInstanceRequests(req)
   144  
   145  	if err != nil {
   146  		// If the spot request was not found, return nil so that we can show
   147  		// that it is gone.
   148  		if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidSpotInstanceRequestID.NotFound" {
   149  			d.SetId("")
   150  			return nil
   151  		}
   152  
   153  		// Some other error, report it
   154  		return err
   155  	}
   156  
   157  	// If nothing was found, then return no state
   158  	if len(resp.SpotInstanceRequests) == 0 {
   159  		d.SetId("")
   160  		return nil
   161  	}
   162  
   163  	request := resp.SpotInstanceRequests[0]
   164  
   165  	// if the request is cancelled, then it is gone
   166  	if *request.State == "cancelled" {
   167  		d.SetId("")
   168  		return nil
   169  	}
   170  
   171  	d.Set("spot_bid_status", *request.Status.Code)
   172  	// Instance ID is not set if the request is still pending
   173  	if request.InstanceId != nil {
   174  		d.Set("spot_instance_id", *request.InstanceId)
   175  	}
   176  	d.Set("spot_request_state", *request.State)
   177  	d.Set("tags", tagsToMap(request.Tags))
   178  
   179  	return nil
   180  }
   181  
   182  func resourceAwsSpotInstanceRequestUpdate(d *schema.ResourceData, meta interface{}) error {
   183  	conn := meta.(*AWSClient).ec2conn
   184  
   185  	d.Partial(true)
   186  	if err := setTags(conn, d); err != nil {
   187  		return err
   188  	} else {
   189  		d.SetPartial("tags")
   190  	}
   191  
   192  	d.Partial(false)
   193  
   194  	return resourceAwsSpotInstanceRequestRead(d, meta)
   195  }
   196  
   197  func resourceAwsSpotInstanceRequestDelete(d *schema.ResourceData, meta interface{}) error {
   198  	conn := meta.(*AWSClient).ec2conn
   199  
   200  	log.Printf("[INFO] Cancelling spot request: %s", d.Id())
   201  	_, err := conn.CancelSpotInstanceRequests(&ec2.CancelSpotInstanceRequestsInput{
   202  		SpotInstanceRequestIds: []*string{aws.String(d.Id())},
   203  	})
   204  
   205  	if err != nil {
   206  		return fmt.Errorf("Error cancelling spot request (%s): %s", d.Id(), err)
   207  	}
   208  
   209  	if instanceId := d.Get("spot_instance_id").(string); instanceId != "" {
   210  		log.Printf("[INFO] Terminating instance: %s", instanceId)
   211  		if err := awsTerminateInstance(conn, instanceId); err != nil {
   212  			return fmt.Errorf("Error terminating spot instance: %s", err)
   213  		}
   214  	}
   215  
   216  	return nil
   217  }
   218  
   219  // SpotInstanceStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
   220  // an EC2 spot instance request
   221  func SpotInstanceStateRefreshFunc(
   222  	conn *ec2.EC2, sir ec2.SpotInstanceRequest) resource.StateRefreshFunc {
   223  
   224  	return func() (interface{}, string, error) {
   225  		resp, err := conn.DescribeSpotInstanceRequests(&ec2.DescribeSpotInstanceRequestsInput{
   226  			SpotInstanceRequestIds: []*string{sir.SpotInstanceRequestId},
   227  		})
   228  
   229  		if err != nil {
   230  			if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidSpotInstanceRequestID.NotFound" {
   231  				// Set this to nil as if we didn't find anything.
   232  				resp = nil
   233  			} else {
   234  				log.Printf("Error on StateRefresh: %s", err)
   235  				return nil, "", err
   236  			}
   237  		}
   238  
   239  		if resp == nil || len(resp.SpotInstanceRequests) == 0 {
   240  			// Sometimes AWS just has consistency issues and doesn't see
   241  			// our request yet. Return an empty state.
   242  			return nil, "", nil
   243  		}
   244  
   245  		req := resp.SpotInstanceRequests[0]
   246  		return req, *req.Status.Code, nil
   247  	}
   248  }