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"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
|
@ -36,36 +37,38 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Driver struct {
|
type Driver struct {
|
||||||
Id string
|
Id string
|
||||||
AccessKey string
|
AccessKey string
|
||||||
SecretKey string
|
SecretKey string
|
||||||
SessionToken string
|
SessionToken string
|
||||||
Region string
|
Region string
|
||||||
AMI string
|
AMI string
|
||||||
SSHKeyID int
|
SSHKeyID int
|
||||||
SSHUser string
|
SSHUser string
|
||||||
SSHPort int
|
SSHPort int
|
||||||
KeyName string
|
KeyName string
|
||||||
InstanceId string
|
InstanceId string
|
||||||
InstanceType string
|
InstanceType string
|
||||||
IPAddress string
|
IPAddress string
|
||||||
PrivateIPAddress string
|
PrivateIPAddress string
|
||||||
MachineName string
|
MachineName string
|
||||||
SecurityGroupId string
|
SecurityGroupId string
|
||||||
SecurityGroupName string
|
SecurityGroupName string
|
||||||
ReservationId string
|
ReservationId string
|
||||||
RootSize int64
|
RootSize int64
|
||||||
IamInstanceProfile string
|
IamInstanceProfile string
|
||||||
VpcId string
|
VpcId string
|
||||||
SubnetId string
|
SubnetId string
|
||||||
Zone string
|
Zone string
|
||||||
CaCertPath string
|
CaCertPath string
|
||||||
PrivateKeyPath string
|
PrivateKeyPath string
|
||||||
SwarmMaster bool
|
SwarmMaster bool
|
||||||
SwarmHost string
|
SwarmHost string
|
||||||
SwarmDiscovery string
|
SwarmDiscovery string
|
||||||
storePath string
|
storePath string
|
||||||
keyPath string
|
keyPath string
|
||||||
|
RequestSpotInstance bool
|
||||||
|
SpotPrice string
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -152,6 +155,15 @@ func GetCreateFlags() []cli.Flag {
|
||||||
Value: "ubuntu",
|
Value: "ubuntu",
|
||||||
EnvVar: "AWS_SSH_USER",
|
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.SessionToken = flags.String("amazonec2-session-token")
|
||||||
d.Region = region
|
d.Region = region
|
||||||
d.AMI = image
|
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.InstanceType = flags.String("amazonec2-instance-type")
|
||||||
d.VpcId = flags.String("amazonec2-vpc-id")
|
d.VpcId = flags.String("amazonec2-vpc-id")
|
||||||
d.SubnetId = flags.String("amazonec2-subnet-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)
|
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)
|
var instance amz.EC2Instance
|
||||||
|
if d.RequestSpotInstance {
|
||||||
if err != nil {
|
spotInstanceRequestId, err := d.getClient().RequestSpotInstances(d.AMI, d.InstanceType, d.Zone, 1, d.SecurityGroupId, d.KeyName, d.SubnetId, bdm, d.IamInstanceProfile, d.SpotPrice)
|
||||||
return fmt.Errorf("Error launching instance: %s", err)
|
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
|
d.InstanceId = instance.InstanceId
|
||||||
|
@ -371,7 +409,7 @@ func (d *Driver) Create() error {
|
||||||
"Name": d.MachineName,
|
"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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -59,25 +59,27 @@ func getTestStorePath() (string, error) {
|
||||||
func getDefaultTestDriverFlags() *DriverOptionsMock {
|
func getDefaultTestDriverFlags() *DriverOptionsMock {
|
||||||
return &DriverOptionsMock{
|
return &DriverOptionsMock{
|
||||||
Data: map[string]interface{}{
|
Data: map[string]interface{}{
|
||||||
"name": "test",
|
"name": "test",
|
||||||
"url": "unix:///var/run/docker.sock",
|
"url": "unix:///var/run/docker.sock",
|
||||||
"swarm": false,
|
"swarm": false,
|
||||||
"swarm-host": "",
|
"swarm-host": "",
|
||||||
"swarm-master": false,
|
"swarm-master": false,
|
||||||
"swarm-discovery": "",
|
"swarm-discovery": "",
|
||||||
"amazonec2-ami": "ami-12345",
|
"amazonec2-ami": "ami-12345",
|
||||||
"amazonec2-access-key": "abcdefg",
|
"amazonec2-access-key": "abcdefg",
|
||||||
"amazonec2-secret-key": "12345",
|
"amazonec2-secret-key": "12345",
|
||||||
"amazonec2-session-token": "",
|
"amazonec2-session-token": "",
|
||||||
"amazonec2-instance-type": "t1.micro",
|
"amazonec2-instance-type": "t1.micro",
|
||||||
"amazonec2-vpc-id": "vpc-12345",
|
"amazonec2-vpc-id": "vpc-12345",
|
||||||
"amazonec2-subnet-id": "subnet-12345",
|
"amazonec2-subnet-id": "subnet-12345",
|
||||||
"amazonec2-security-group": "docker-machine-test",
|
"amazonec2-security-group": "docker-machine-test",
|
||||||
"amazonec2-region": "us-east-1",
|
"amazonec2-region": "us-east-1",
|
||||||
"amazonec2-zone": "e",
|
"amazonec2-zone": "e",
|
||||||
"amazonec2-root-size": 10,
|
"amazonec2-root-size": 10,
|
||||||
"amazonec2-iam-instance-profile": "",
|
"amazonec2-iam-instance-profile": "",
|
||||||
"amazonec2-ssh-user": "ubuntu",
|
"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"`
|
OwnerId string `xml:"ownerId"`
|
||||||
Instances []EC2Instance `xml:"instancesSet>item"`
|
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 {
|
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
|
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 {
|
func (e *EC2) DeleteKeyPair(name string) error {
|
||||||
v := url.Values{}
|
v := url.Values{}
|
||||||
v.Set("Action", "DeleteKeyPair")
|
v.Set("Action", "DeleteKeyPair")
|
||||||
|
|
Loading…
Reference in New Issue