Skip to content

Commit

Permalink
Support launch templates with network configuration (LeanerCloud#352)
Browse files Browse the repository at this point in the history
* Support launch templates with network configuration

The RunInstances input needs to be cleaned from any attributes
conflicting with Network interface definition coming
from the group's LaunchTemplate

This hopefully fixes LeanerCloud#345.

* Improve logging of DescribeLaunchTemplateVersions falures

* Add ec2:DescribeLaunchTemplateVersions to IAM permissions

* Fix code formatting
  • Loading branch information
cristim authored Jul 1, 2019
1 parent 79f41c3 commit 373662a
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 31 deletions.
3 changes: 2 additions & 1 deletion cloudformation/stacks/AutoSpotting/template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@
"}"
]
]
- !Ref 'AWS::NoValue'
- !Ref 'AWS::NoValue'
Arn: !If
- "StackSetsIsRegional"
- Fn::GetAtt:
Expand Down Expand Up @@ -447,6 +447,7 @@
- "ec2:DeleteTags"
- "ec2:DescribeInstanceAttribute"
- "ec2:DescribeInstances"
- "ec2:DescribeLaunchTemplateVersions"
- "ec2:DescribeRegions"
- "ec2:DescribeSpotPriceHistory"
- "ec2:RunInstances"
Expand Down
33 changes: 31 additions & 2 deletions core/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,28 @@ func (i *instance) convertSecurityGroups() []*string {
return groupIDs
}

func (i *instance) launchTemplateHasNetworkInterfaces(ver, id *string) bool {
res, err := i.region.services.ec2.DescribeLaunchTemplateVersions(
&ec2.DescribeLaunchTemplateVersionsInput{
Versions: []*string{ver},
LaunchTemplateId: id,
},
)

if err != nil {
logger.Println("Failed to describe launch template", *id, "version", *ver,
"encountered error:", err.Error())
}

if err == nil && len(res.LaunchTemplateVersions) == 1 {
lt := res.LaunchTemplateVersions[0]
if len(lt.LaunchTemplateData.NetworkInterfaces) > 0 {
return true
}
}
return false
}

func (i *instance) createRunInstancesInput(instanceType string, price float64) *ec2.RunInstancesInput {
var retval ec2.RunInstancesInput

Expand Down Expand Up @@ -537,9 +559,16 @@ func (i *instance) createRunInstancesInput(instanceType string, price float64) *
}

if i.asg.LaunchTemplate != nil {
ver := i.asg.LaunchTemplate.Version
id := i.asg.LaunchTemplate.LaunchTemplateId

retval.LaunchTemplate = &ec2.LaunchTemplateSpecification{
LaunchTemplateId: i.asg.LaunchTemplate.LaunchTemplateId,
Version: i.asg.LaunchTemplate.Version,
LaunchTemplateId: id,
Version: ver,
}

if i.launchTemplateHasNetworkInterfaces(id, ver) {
retval.SubnetId, retval.SecurityGroupIds = nil, nil
}
}

Expand Down
160 changes: 132 additions & 28 deletions core/instance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1537,8 +1537,22 @@ func Test_instance_createRunInstancesInput(t *testing.T) {
want *ec2.RunInstancesInput
}{
{
name: "create run instances input without launch-configuration",
name: "create run instances input with basic launch template",
inst: instance{
region: &region{
services: connections{
ec2: mockEC2{
dltverr: nil,
dltvo: &ec2.DescribeLaunchTemplateVersionsOutput{
LaunchTemplateVersions: []*ec2.LaunchTemplateVersion{
{
LaunchTemplateData: &ec2.ResponseLaunchTemplateData{},
},
},
},
},
},
},
asg: &autoScalingGroup{
name: "mygroup",
Group: &autoscaling.Group{
Expand Down Expand Up @@ -1641,8 +1655,28 @@ func Test_instance_createRunInstancesInput(t *testing.T) {
},
},
{
name: "create run instances input with simple LC",
name: "create run instances input with launch template containing advanced network configuration",
inst: instance{
region: &region{
services: connections{
ec2: mockEC2{
dltverr: nil,
dltvo: &ec2.DescribeLaunchTemplateVersionsOutput{
LaunchTemplateVersions: []*ec2.LaunchTemplateVersion{
{
LaunchTemplateData: &ec2.ResponseLaunchTemplateData{
NetworkInterfaces: []*ec2.LaunchTemplateInstanceNetworkInterfaceSpecification{
{
Description: aws.String("dummy network interface definition"),
},
},
},
},
},
},
},
},
},
asg: &autoScalingGroup{
name: "mygroup",
Group: &autoscaling.Group{
Expand All @@ -1652,6 +1686,98 @@ func Test_instance_createRunInstancesInput(t *testing.T) {
Version: aws.String("v1"),
},
},
},
Instance: &ec2.Instance{
EbsOptimized: aws.Bool(true),

IamInstanceProfile: &ec2.IamInstanceProfile{
Arn: aws.String("profile-arn"),
},

ImageId: aws.String("ami-123"),
InstanceType: aws.String("t2.medium"),
KeyName: aws.String("mykey"),

Placement: &ec2.Placement{
Affinity: aws.String("foo"),
},

SecurityGroups: []*ec2.GroupIdentifier{
{
GroupName: aws.String("foo"),
GroupId: aws.String("sg-123"),
},
{
GroupName: aws.String("bar"),
GroupId: aws.String("sg-456"),
},
},

SubnetId: aws.String("subnet-123"),
},
}, args: args{
instanceType: "t2.small",
price: 1.5,
},
want: &ec2.RunInstancesInput{

EbsOptimized: aws.Bool(true),
ImageId: aws.String("ami-123"),

InstanceMarketOptions: &ec2.InstanceMarketOptionsRequest{
MarketType: aws.String("spot"),
SpotOptions: &ec2.SpotMarketOptions{
MaxPrice: aws.String("1.5"),
},
},

InstanceType: aws.String("t2.small"),
KeyName: aws.String("mykey"),

LaunchTemplate: &ec2.LaunchTemplateSpecification{
LaunchTemplateId: aws.String("lt-id"),
Version: aws.String("v1"),
},

MaxCount: aws.Int64(1),
MinCount: aws.Int64(1),

Placement: &ec2.Placement{
Affinity: aws.String("foo"),
},

TagSpecifications: []*ec2.TagSpecification{{
ResourceType: aws.String("instance"),
Tags: []*ec2.Tag{
{
Key: aws.String("LaunchTemplateID"),
Value: aws.String("lt-id"),
},
{
Key: aws.String("LaunchTemplateVersion"),
Value: aws.String("v1"),
},
{
Key: aws.String("launched-by-autospotting"),
Value: aws.String("true"),
},
{
Key: aws.String("launched-for-asg"),
Value: aws.String("mygroup"),
},
},
},
},
},
},
{
name: "create run instances input with simple LC",
inst: instance{
asg: &autoScalingGroup{
name: "mygroup",
Group: &autoscaling.Group{
LaunchConfigurationName: aws.String("myLC"),
},
launchConfiguration: &launchConfiguration{
LaunchConfiguration: &autoscaling.LaunchConfiguration{
BlockDeviceMappings: nil,
Expand Down Expand Up @@ -1713,11 +1839,6 @@ func Test_instance_createRunInstancesInput(t *testing.T) {
InstanceType: aws.String("t2.small"),
KeyName: aws.String("mykey"),

LaunchTemplate: &ec2.LaunchTemplateSpecification{
LaunchTemplateId: aws.String("lt-id"),
Version: aws.String("v1"),
},

MaxCount: aws.Int64(1),
MinCount: aws.Int64(1),

Expand All @@ -1736,12 +1857,8 @@ func Test_instance_createRunInstancesInput(t *testing.T) {
ResourceType: aws.String("instance"),
Tags: []*ec2.Tag{
{
Key: aws.String("LaunchTemplateID"),
Value: aws.String("lt-id"),
},
{
Key: aws.String("LaunchTemplateVersion"),
Value: aws.String("v1"),
Key: aws.String("LaunchConfigurationName"),
Value: aws.String("myLC"),
},
{
Key: aws.String("launched-by-autospotting"),
Expand All @@ -1765,10 +1882,6 @@ func Test_instance_createRunInstancesInput(t *testing.T) {
name: "mygroup",
Group: &autoscaling.Group{
LaunchConfigurationName: aws.String("myLC"),
LaunchTemplate: &autoscaling.LaunchTemplateSpecification{
LaunchTemplateId: aws.String("lt-id"),
Version: aws.String("v1"),
},
},
launchConfiguration: &launchConfiguration{
LaunchConfiguration: &autoscaling.LaunchConfiguration{
Expand Down Expand Up @@ -1843,11 +1956,6 @@ func Test_instance_createRunInstancesInput(t *testing.T) {
InstanceType: aws.String("t2.small"),
KeyName: aws.String("mykey"),

LaunchTemplate: &ec2.LaunchTemplateSpecification{
LaunchTemplateId: aws.String("lt-id"),
Version: aws.String("v1"),
},

MaxCount: aws.Int64(1),
MinCount: aws.Int64(1),

Expand Down Expand Up @@ -1875,12 +1983,8 @@ func Test_instance_createRunInstancesInput(t *testing.T) {
ResourceType: aws.String("instance"),
Tags: []*ec2.Tag{
{
Key: aws.String("LaunchTemplateID"),
Value: aws.String("lt-id"),
},
{
Key: aws.String("LaunchTemplateVersion"),
Value: aws.String("v1"),
Key: aws.String("LaunchConfigurationName"),
Value: aws.String("myLC"),
},
{
Key: aws.String("launched-by-autospotting"),
Expand Down
8 changes: 8 additions & 0 deletions core/mock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ type mockEC2 struct {
// Delete Tags
dto *ec2.DeleteTagsOutput
dterr error

// DescribeLaunchTemplateVersionsOutput
dltvo *ec2.DescribeLaunchTemplateVersionsOutput
dltverr error
}

func (m mockEC2) DescribeSpotPriceHistory(in *ec2.DescribeSpotPriceHistoryInput) (*ec2.DescribeSpotPriceHistoryOutput, error) {
Expand Down Expand Up @@ -78,6 +82,10 @@ func (m mockEC2) DeleteTags(*ec2.DeleteTagsInput) (*ec2.DeleteTagsOutput, error)
return m.dto, m.dterr
}

func (m mockEC2) DescribeLaunchTemplateVersions(*ec2.DescribeLaunchTemplateVersionsInput) (*ec2.DescribeLaunchTemplateVersionsOutput, error) {
return m.dltvo, m.dltverr
}

// For testing we "convert" the SecurityGroupIDs/SecurityGroupNames by
// prefixing the original name/id with "sg-" if not present already. We
// also fill up the rest of the string to the length of a typical ID with
Expand Down

0 comments on commit 373662a

Please sign in to comment.