mirror of https://github.com/docker/docs.git
Merge pull request #1000 from ehazlett/spot-instance
EC2 spot instance support
This commit is contained in:
commit
65f4a24916
|
@ -10,6 +10,7 @@ import (
|
|||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/codegangsta/cli"
|
||||
|
@ -36,36 +37,38 @@ var (
|
|||
)
|
||||
|
||||
type Driver struct {
|
||||
Id string
|
||||
AccessKey string
|
||||
SecretKey string
|
||||
SessionToken string
|
||||
Region string
|
||||
AMI string
|
||||
SSHKeyID int
|
||||
SSHUser string
|
||||
SSHPort int
|
||||
KeyName string
|
||||
InstanceId string
|
||||
InstanceType string
|
||||
IPAddress string
|
||||
PrivateIPAddress string
|
||||
MachineName string
|
||||
SecurityGroupId string
|
||||
SecurityGroupName string
|
||||
ReservationId string
|
||||
RootSize int64
|
||||
IamInstanceProfile string
|
||||
VpcId string
|
||||
SubnetId string
|
||||
Zone string
|
||||
CaCertPath string
|
||||
PrivateKeyPath string
|
||||
SwarmMaster bool
|
||||
SwarmHost string
|
||||
SwarmDiscovery string
|
||||
storePath string
|
||||
keyPath string
|
||||
Id string
|
||||
AccessKey string
|
||||
SecretKey string
|
||||
SessionToken string
|
||||
Region string
|
||||
AMI string
|
||||
SSHKeyID int
|
||||
SSHUser string
|
||||
SSHPort int
|
||||
KeyName string
|
||||
InstanceId string
|
||||
InstanceType string
|
||||
IPAddress string
|
||||
PrivateIPAddress string
|
||||
MachineName string
|
||||
SecurityGroupId string
|
||||
SecurityGroupName string
|
||||
ReservationId string
|
||||
RootSize int64
|
||||
IamInstanceProfile string
|
||||
VpcId string
|
||||
SubnetId string
|
||||
Zone string
|
||||
CaCertPath string
|
||||
PrivateKeyPath string
|
||||
SwarmMaster bool
|
||||
SwarmHost string
|
||||
SwarmDiscovery string
|
||||
storePath string
|
||||
keyPath string
|
||||
RequestSpotInstance bool
|
||||
SpotPrice string
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -152,6 +155,15 @@ func GetCreateFlags() []cli.Flag {
|
|||
Value: "ubuntu",
|
||||
EnvVar: "AWS_SSH_USER",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "amazonec2-request-spot-instance",
|
||||
Usage: "Set this flag to request spot instance",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "amazonec2-spot-price",
|
||||
Usage: "AWS spot instance bid price (in dollar)",
|
||||
Value: "0.50",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -194,6 +206,8 @@ func (d *Driver) SetConfigFromFlags(flags drivers.DriverOptions) error {
|
|||
d.SessionToken = flags.String("amazonec2-session-token")
|
||||
d.Region = region
|
||||
d.AMI = image
|
||||
d.RequestSpotInstance = flags.Bool("amazonec2-request-spot-instance")
|
||||
d.SpotPrice = flags.String("amazonec2-spot-price")
|
||||
d.InstanceType = flags.String("amazonec2-instance-type")
|
||||
d.VpcId = flags.String("amazonec2-vpc-id")
|
||||
d.SubnetId = flags.String("amazonec2-subnet-id")
|
||||
|
@ -335,10 +349,34 @@ func (d *Driver) Create() error {
|
|||
}
|
||||
|
||||
log.Debugf("launching instance in subnet %s", d.SubnetId)
|
||||
instance, err := d.getClient().RunInstance(d.AMI, d.InstanceType, d.Zone, 1, 1, d.SecurityGroupId, d.KeyName, d.SubnetId, bdm, d.IamInstanceProfile)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error launching instance: %s", err)
|
||||
var instance amz.EC2Instance
|
||||
if d.RequestSpotInstance {
|
||||
spotInstanceRequestId, err := d.getClient().RequestSpotInstances(d.AMI, d.InstanceType, d.Zone, 1, d.SecurityGroupId, d.KeyName, d.SubnetId, bdm, d.IamInstanceProfile, d.SpotPrice)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error request spot instance: %s", err)
|
||||
}
|
||||
var instanceId string
|
||||
var spotInstanceRequestStatus string
|
||||
log.Info("Waiting for spot instance...")
|
||||
// check until fulfilled
|
||||
for instanceId == "" {
|
||||
time.Sleep(time.Second * 5)
|
||||
spotInstanceRequestStatus, instanceId, err = d.getClient().DescribeSpotInstanceRequests(spotInstanceRequestId)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error describe spot instance request: %s", err)
|
||||
}
|
||||
log.Debugf("spot instance request status: %s", spotInstanceRequestStatus)
|
||||
}
|
||||
instance, err = d.getClient().GetInstance(instanceId)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error get instance: %s", err)
|
||||
}
|
||||
} else {
|
||||
inst, err := d.getClient().RunInstance(d.AMI, d.InstanceType, d.Zone, 1, 1, d.SecurityGroupId, d.KeyName, d.SubnetId, bdm, d.IamInstanceProfile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error launching instance: %s", err)
|
||||
}
|
||||
instance = inst
|
||||
}
|
||||
|
||||
d.InstanceId = instance.InstanceId
|
||||
|
@ -371,7 +409,7 @@ func (d *Driver) Create() error {
|
|||
"Name": d.MachineName,
|
||||
}
|
||||
|
||||
if err = d.getClient().CreateTags(d.InstanceId, tags); err != nil {
|
||||
if err := d.getClient().CreateTags(d.InstanceId, tags); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -59,25 +59,27 @@ func getTestStorePath() (string, error) {
|
|||
func getDefaultTestDriverFlags() *DriverOptionsMock {
|
||||
return &DriverOptionsMock{
|
||||
Data: map[string]interface{}{
|
||||
"name": "test",
|
||||
"url": "unix:///var/run/docker.sock",
|
||||
"swarm": false,
|
||||
"swarm-host": "",
|
||||
"swarm-master": false,
|
||||
"swarm-discovery": "",
|
||||
"amazonec2-ami": "ami-12345",
|
||||
"amazonec2-access-key": "abcdefg",
|
||||
"amazonec2-secret-key": "12345",
|
||||
"amazonec2-session-token": "",
|
||||
"amazonec2-instance-type": "t1.micro",
|
||||
"amazonec2-vpc-id": "vpc-12345",
|
||||
"amazonec2-subnet-id": "subnet-12345",
|
||||
"amazonec2-security-group": "docker-machine-test",
|
||||
"amazonec2-region": "us-east-1",
|
||||
"amazonec2-zone": "e",
|
||||
"amazonec2-root-size": 10,
|
||||
"amazonec2-iam-instance-profile": "",
|
||||
"amazonec2-ssh-user": "ubuntu",
|
||||
"name": "test",
|
||||
"url": "unix:///var/run/docker.sock",
|
||||
"swarm": false,
|
||||
"swarm-host": "",
|
||||
"swarm-master": false,
|
||||
"swarm-discovery": "",
|
||||
"amazonec2-ami": "ami-12345",
|
||||
"amazonec2-access-key": "abcdefg",
|
||||
"amazonec2-secret-key": "12345",
|
||||
"amazonec2-session-token": "",
|
||||
"amazonec2-instance-type": "t1.micro",
|
||||
"amazonec2-vpc-id": "vpc-12345",
|
||||
"amazonec2-subnet-id": "subnet-12345",
|
||||
"amazonec2-security-group": "docker-machine-test",
|
||||
"amazonec2-region": "us-east-1",
|
||||
"amazonec2-zone": "e",
|
||||
"amazonec2-root-size": 10,
|
||||
"amazonec2-iam-instance-profile": "",
|
||||
"amazonec2-ssh-user": "ubuntu",
|
||||
"amazonec2-request-spot-instance": false,
|
||||
"amazonec2-spot-price": "",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
package amz
|
||||
|
||||
type DescribeSpotInstanceRequestsResponse struct {
|
||||
RequestId string `xml:"requestId"`
|
||||
SpotInstanceRequestSet []struct {
|
||||
Status struct {
|
||||
Code string `xml:"code"`
|
||||
} `xml:"status"`
|
||||
InstanceId string `xml:"instanceId"`
|
||||
} `xml:"spotInstanceRequestSet>item"`
|
||||
}
|
|
@ -103,6 +103,14 @@ type (
|
|||
OwnerId string `xml:"ownerId"`
|
||||
Instances []EC2Instance `xml:"instancesSet>item"`
|
||||
}
|
||||
|
||||
RequestSpotInstancesResponse struct {
|
||||
RequestId string `xml:"requestId"`
|
||||
SpotInstanceRequestSet []struct {
|
||||
SpotInstanceRequestId string `xml:"spotInstanceRequestId"`
|
||||
State string `xml:"state"`
|
||||
} `xml:"spotInstanceRequestSet>item"`
|
||||
}
|
||||
)
|
||||
|
||||
func newAwsApiResponseError(r http.Response) error {
|
||||
|
@ -217,6 +225,81 @@ func (e *EC2) RunInstance(amiId string, instanceType string, zone string, minCou
|
|||
return instance.info, nil
|
||||
}
|
||||
|
||||
func (e *EC2) RequestSpotInstances(amiId string, instanceType string, zone string, instanceCount int, securityGroup string, keyName string, subnetId string, bdm *BlockDeviceMapping, role string, spotPrice string) (string, error) {
|
||||
v := url.Values{}
|
||||
v.Set("Action", "RequestSpotInstances")
|
||||
v.Set("LaunchSpecification.ImageId", amiId)
|
||||
v.Set("LaunchSpecification.Placement.AvailabilityZone", e.Region+zone)
|
||||
v.Set("InstanceCount", strconv.Itoa(instanceCount))
|
||||
v.Set("SpotPrice", spotPrice)
|
||||
v.Set("LaunchSpecification.KeyName", keyName)
|
||||
v.Set("LaunchSpecification.InstanceType", instanceType)
|
||||
v.Set("LaunchSpecification.NetworkInterface.0.DeviceIndex", "0")
|
||||
v.Set("LaunchSpecification.NetworkInterface.0.SecurityGroupId.0", securityGroup)
|
||||
v.Set("LaunchSpecification.NetworkInterface.0.SubnetId", subnetId)
|
||||
v.Set("LaunchSpecification.NetworkInterface.0.AssociatePublicIpAddress", "1")
|
||||
|
||||
if len(role) > 0 {
|
||||
v.Set("LaunchSpecification.IamInstanceProfile.Name", role)
|
||||
}
|
||||
|
||||
if bdm != nil {
|
||||
v.Set("LaunchSpecification.BlockDeviceMapping.0.DeviceName", bdm.DeviceName)
|
||||
v.Set("LaunchSpecification.BlockDeviceMapping.0.VirtualName", bdm.VirtualName)
|
||||
v.Set("LaunchSpecification.BlockDeviceMapping.0.Ebs.VolumeSize", strconv.FormatInt(bdm.VolumeSize, 10))
|
||||
v.Set("LaunchSpecification.BlockDeviceMapping.0.Ebs.VolumeType", bdm.VolumeType)
|
||||
deleteOnTerm := 0
|
||||
if bdm.DeleteOnTermination {
|
||||
deleteOnTerm = 1
|
||||
}
|
||||
v.Set("LaunchSpecification.BlockDeviceMapping.0.Ebs.DeleteOnTermination", strconv.Itoa(deleteOnTerm))
|
||||
}
|
||||
|
||||
resp, err := e.awsApiCall(v)
|
||||
|
||||
if err != nil {
|
||||
return "", newAwsApiCallError(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
contents, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Error reading AWS response body")
|
||||
}
|
||||
unmarshalledResponse := RequestSpotInstancesResponse{}
|
||||
err = xml.Unmarshal(contents, &unmarshalledResponse)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Error unmarshalling AWS response XML: %s", err)
|
||||
}
|
||||
return unmarshalledResponse.SpotInstanceRequestSet[0].SpotInstanceRequestId, nil
|
||||
}
|
||||
|
||||
func (e *EC2) DescribeSpotInstanceRequests(spotInstanceRequestId string) (string, string, error) {
|
||||
v := url.Values{}
|
||||
v.Set("Action", "DescribeSpotInstanceRequests")
|
||||
v.Set("SpotInstanceRequestId.0", spotInstanceRequestId)
|
||||
|
||||
resp, err := e.awsApiCall(v)
|
||||
|
||||
if err != nil {
|
||||
return "", "", newAwsApiCallError(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
contents, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("Error reading AWS response body")
|
||||
}
|
||||
unmarshalledResponse := DescribeSpotInstanceRequestsResponse{}
|
||||
err = xml.Unmarshal(contents, &unmarshalledResponse)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("Error unmarshalling AWS response XML: %s", err)
|
||||
}
|
||||
if code := unmarshalledResponse.SpotInstanceRequestSet[0].Status.Code; code != "fulfilled" {
|
||||
return code, "", nil
|
||||
}
|
||||
return "fulfilled", unmarshalledResponse.SpotInstanceRequestSet[0].InstanceId, nil
|
||||
}
|
||||
|
||||
func (e *EC2) DeleteKeyPair(name string) error {
|
||||
v := url.Values{}
|
||||
v.Set("Action", "DeleteKeyPair")
|
||||
|
|
Loading…
Reference in New Issue