Skip to content

Commit 71925a5

Browse files
committed
🐛 Launchtemplate needs to be updated if spot options are changed
1 parent f509a70 commit 71925a5

File tree

2 files changed

+241
-6
lines changed

2 files changed

+241
-6
lines changed

pkg/cloud/services/ec2/launchtemplate.go

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -826,6 +826,24 @@ func (s *Service) deleteLaunchTemplateVersion(id string, version *int64) error {
826826
return nil
827827
}
828828

829+
// SDKToSpotMarketOptions converts EC2 instance market options to SpotMarketOptions.
830+
func SDKToSpotMarketOptions(instanceMarketOptions *ec2.LaunchTemplateInstanceMarketOptions) *infrav1.SpotMarketOptions {
831+
if instanceMarketOptions == nil || aws.StringValue(instanceMarketOptions.MarketType) != ec2.MarketTypeSpot {
832+
return nil
833+
}
834+
835+
if instanceMarketOptions.SpotOptions == nil {
836+
return &infrav1.SpotMarketOptions{}
837+
}
838+
839+
result := &infrav1.SpotMarketOptions{}
840+
if instanceMarketOptions.SpotOptions.MaxPrice != nil {
841+
result.MaxPrice = instanceMarketOptions.SpotOptions.MaxPrice
842+
}
843+
844+
return result
845+
}
846+
829847
// SDKToLaunchTemplate converts an AWS EC2 SDK instance to the CAPA instance type.
830848
func (s *Service) SDKToLaunchTemplate(d *ec2.LaunchTemplateVersion) (*expinfrav1.AWSLaunchTemplate, string, *apimachinerytypes.NamespacedName, *string, error) {
831849
v := d.LaunchTemplateData
@@ -834,9 +852,10 @@ func (s *Service) SDKToLaunchTemplate(d *ec2.LaunchTemplateVersion) (*expinfrav1
834852
AMI: infrav1.AMIReference{
835853
ID: v.ImageId,
836854
},
837-
InstanceType: aws.StringValue(v.InstanceType),
838-
SSHKeyName: v.KeyName,
839-
VersionNumber: d.VersionNumber,
855+
InstanceType: aws.StringValue(v.InstanceType),
856+
SSHKeyName: v.KeyName,
857+
SpotMarketOptions: SDKToSpotMarketOptions(v.InstanceMarketOptions),
858+
VersionNumber: d.VersionNumber,
840859
}
841860

842861
if v.MetadataOptions != nil {
@@ -928,6 +947,9 @@ func (s *Service) LaunchTemplateNeedsUpdate(scope scope.LaunchTemplateScope, inc
928947
if !cmp.Equal(incoming.InstanceMetadataOptions, existing.InstanceMetadataOptions) {
929948
return true, nil
930949
}
950+
if !cmp.Equal(incoming.SpotMarketOptions, existing.SpotMarketOptions) {
951+
return true, nil
952+
}
931953

932954
incomingIDs, err := s.GetAdditionalSecurityGroupsIDs(incoming.AdditionalSecurityGroups)
933955
if err != nil {

pkg/cloud/services/ec2/launchtemplate_test.go

Lines changed: 216 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,139 @@ func TestServiceSDKToLaunchTemplate(t *testing.T) {
364364
wantDataSecretKey: nil, // respective tag is not given
365365
wantBootstrapDataHash: nil, // respective tag is not given
366366
},
367+
{
368+
name: "spot market options",
369+
input: &ec2.LaunchTemplateVersion{
370+
LaunchTemplateId: aws.String("lt-12345"),
371+
LaunchTemplateName: aws.String("foo"),
372+
LaunchTemplateData: &ec2.ResponseLaunchTemplateData{
373+
ImageId: aws.String("foo-image"),
374+
IamInstanceProfile: &ec2.LaunchTemplateIamInstanceProfileSpecification{
375+
Arn: aws.String("instance-profile/foo-profile"),
376+
},
377+
KeyName: aws.String("foo-keyname"),
378+
InstanceMarketOptions: &ec2.LaunchTemplateInstanceMarketOptions{
379+
MarketType: aws.String(ec2.MarketTypeSpot),
380+
SpotOptions: &ec2.LaunchTemplateSpotMarketOptions{
381+
MaxPrice: aws.String("0.05"),
382+
},
383+
},
384+
UserData: aws.String(base64.StdEncoding.EncodeToString([]byte(testUserData))),
385+
},
386+
VersionNumber: aws.Int64(1),
387+
},
388+
wantLT: &expinfrav1.AWSLaunchTemplate{
389+
Name: "foo",
390+
AMI: infrav1.AMIReference{
391+
ID: aws.String("foo-image"),
392+
},
393+
IamInstanceProfile: "foo-profile",
394+
SSHKeyName: aws.String("foo-keyname"),
395+
VersionNumber: aws.Int64(1),
396+
SpotMarketOptions: &infrav1.SpotMarketOptions{
397+
MaxPrice: aws.String("0.05"),
398+
},
399+
},
400+
wantHash: testUserDataHash,
401+
wantDataSecretKey: nil,
402+
},
403+
{
404+
name: "spot market options with no max price",
405+
input: &ec2.LaunchTemplateVersion{
406+
LaunchTemplateId: aws.String("lt-12345"),
407+
LaunchTemplateName: aws.String("foo"),
408+
LaunchTemplateData: &ec2.ResponseLaunchTemplateData{
409+
ImageId: aws.String("foo-image"),
410+
IamInstanceProfile: &ec2.LaunchTemplateIamInstanceProfileSpecification{
411+
Arn: aws.String("instance-profile/foo-profile"),
412+
},
413+
KeyName: aws.String("foo-keyname"),
414+
InstanceMarketOptions: &ec2.LaunchTemplateInstanceMarketOptions{
415+
MarketType: aws.String(ec2.MarketTypeSpot),
416+
SpotOptions: &ec2.LaunchTemplateSpotMarketOptions{},
417+
},
418+
UserData: aws.String(base64.StdEncoding.EncodeToString([]byte(testUserData))),
419+
},
420+
VersionNumber: aws.Int64(1),
421+
},
422+
wantLT: &expinfrav1.AWSLaunchTemplate{
423+
Name: "foo",
424+
AMI: infrav1.AMIReference{
425+
ID: aws.String("foo-image"),
426+
},
427+
IamInstanceProfile: "foo-profile",
428+
SSHKeyName: aws.String("foo-keyname"),
429+
VersionNumber: aws.Int64(1),
430+
SpotMarketOptions: &infrav1.SpotMarketOptions{},
431+
},
432+
wantHash: testUserDataHash,
433+
wantDataSecretKey: nil,
434+
},
435+
{
436+
name: "spot market options without SpotOptions",
437+
input: &ec2.LaunchTemplateVersion{
438+
LaunchTemplateId: aws.String("lt-12345"),
439+
LaunchTemplateName: aws.String("foo"),
440+
LaunchTemplateData: &ec2.ResponseLaunchTemplateData{
441+
ImageId: aws.String("foo-image"),
442+
IamInstanceProfile: &ec2.LaunchTemplateIamInstanceProfileSpecification{
443+
Arn: aws.String("instance-profile/foo-profile"),
444+
},
445+
KeyName: aws.String("foo-keyname"),
446+
InstanceMarketOptions: &ec2.LaunchTemplateInstanceMarketOptions{
447+
MarketType: aws.String(ec2.MarketTypeSpot),
448+
},
449+
UserData: aws.String(base64.StdEncoding.EncodeToString([]byte(testUserData))),
450+
},
451+
VersionNumber: aws.Int64(1),
452+
},
453+
wantLT: &expinfrav1.AWSLaunchTemplate{
454+
Name: "foo",
455+
AMI: infrav1.AMIReference{
456+
ID: aws.String("foo-image"),
457+
},
458+
IamInstanceProfile: "foo-profile",
459+
SSHKeyName: aws.String("foo-keyname"),
460+
VersionNumber: aws.Int64(1),
461+
SpotMarketOptions: &infrav1.SpotMarketOptions{},
462+
},
463+
wantHash: testUserDataHash,
464+
wantDataSecretKey: nil,
465+
},
466+
{
467+
name: "non-spot market type",
468+
input: &ec2.LaunchTemplateVersion{
469+
LaunchTemplateId: aws.String("lt-12345"),
470+
LaunchTemplateName: aws.String("foo"),
471+
LaunchTemplateData: &ec2.ResponseLaunchTemplateData{
472+
ImageId: aws.String("foo-image"),
473+
IamInstanceProfile: &ec2.LaunchTemplateIamInstanceProfileSpecification{
474+
Arn: aws.String("instance-profile/foo-profile"),
475+
},
476+
KeyName: aws.String("foo-keyname"),
477+
InstanceMarketOptions: &ec2.LaunchTemplateInstanceMarketOptions{
478+
MarketType: aws.String("different-market-type"),
479+
SpotOptions: &ec2.LaunchTemplateSpotMarketOptions{
480+
MaxPrice: aws.String("0.05"),
481+
},
482+
},
483+
UserData: aws.String(base64.StdEncoding.EncodeToString([]byte(testUserData))),
484+
},
485+
VersionNumber: aws.Int64(1),
486+
},
487+
wantLT: &expinfrav1.AWSLaunchTemplate{
488+
Name: "foo",
489+
AMI: infrav1.AMIReference{
490+
ID: aws.String("foo-image"),
491+
},
492+
IamInstanceProfile: "foo-profile",
493+
SSHKeyName: aws.String("foo-keyname"),
494+
VersionNumber: aws.Int64(1),
495+
SpotMarketOptions: nil, // Should be nil since market type is not "spot"
496+
},
497+
wantHash: testUserDataHash,
498+
wantDataSecretKey: nil,
499+
},
367500
{
368501
name: "tag of bootstrap secret",
369502
input: &ec2.LaunchTemplateVersion{
@@ -458,6 +591,20 @@ func TestServiceLaunchTemplateNeedsUpdate(t *testing.T) {
458591
want bool
459592
wantErr bool
460593
}{
594+
{
595+
name: "only core security groups, order shouldn't matter",
596+
incoming: &expinfrav1.AWSLaunchTemplate{
597+
AdditionalSecurityGroups: []infrav1.AWSResourceReference{},
598+
},
599+
existing: &expinfrav1.AWSLaunchTemplate{
600+
AdditionalSecurityGroups: []infrav1.AWSResourceReference{
601+
{ID: aws.String("sg-222")},
602+
{ID: aws.String("sg-111")},
603+
},
604+
},
605+
want: false,
606+
wantErr: false,
607+
},
461608
{
462609
name: "the same security groups",
463610
incoming: &expinfrav1.AWSLaunchTemplate{
@@ -515,6 +662,10 @@ func TestServiceLaunchTemplateNeedsUpdate(t *testing.T) {
515662
IamInstanceProfile: DefaultAmiNameFormat,
516663
},
517664
existing: &expinfrav1.AWSLaunchTemplate{
665+
AdditionalSecurityGroups: []infrav1.AWSResourceReference{
666+
{ID: aws.String("sg-111")},
667+
{ID: aws.String("sg-222")},
668+
},
518669
IamInstanceProfile: "some-other-profile",
519670
},
520671
want: true,
@@ -525,6 +676,10 @@ func TestServiceLaunchTemplateNeedsUpdate(t *testing.T) {
525676
InstanceType: "t3.micro",
526677
},
527678
existing: &expinfrav1.AWSLaunchTemplate{
679+
AdditionalSecurityGroups: []infrav1.AWSResourceReference{
680+
{ID: aws.String("sg-111")},
681+
{ID: aws.String("sg-222")},
682+
},
528683
InstanceType: "t3.large",
529684
},
530685
want: true,
@@ -558,9 +713,14 @@ func TestServiceLaunchTemplateNeedsUpdate(t *testing.T) {
558713
HTTPTokens: infrav1.HTTPTokensStateRequired,
559714
},
560715
},
561-
existing: &expinfrav1.AWSLaunchTemplate{},
562-
want: true,
563-
wantErr: false,
716+
existing: &expinfrav1.AWSLaunchTemplate{
717+
AdditionalSecurityGroups: []infrav1.AWSResourceReference{
718+
{ID: aws.String("sg-111")},
719+
{ID: aws.String("sg-222")},
720+
},
721+
},
722+
want: true,
723+
wantErr: false,
564724
},
565725
{
566726
name: "new launch template instance metadata options, removing IMDSv2 requirement",
@@ -574,6 +734,59 @@ func TestServiceLaunchTemplateNeedsUpdate(t *testing.T) {
574734
want: true,
575735
wantErr: false,
576736
},
737+
{
738+
name: "Should return true if incoming SpotMarketOptions is different from existing SpotMarketOptions",
739+
incoming: &expinfrav1.AWSLaunchTemplate{
740+
SpotMarketOptions: &infrav1.SpotMarketOptions{
741+
MaxPrice: aws.String("0.10"),
742+
},
743+
},
744+
existing: &expinfrav1.AWSLaunchTemplate{
745+
AdditionalSecurityGroups: []infrav1.AWSResourceReference{
746+
{ID: aws.String("sg-111")},
747+
{ID: aws.String("sg-222")},
748+
},
749+
SpotMarketOptions: &infrav1.SpotMarketOptions{
750+
MaxPrice: aws.String("0.05"),
751+
},
752+
},
753+
want: true,
754+
wantErr: false,
755+
},
756+
{
757+
name: "Should return true if incoming adds SpotMarketOptions and existing has none",
758+
incoming: &expinfrav1.AWSLaunchTemplate{
759+
SpotMarketOptions: &infrav1.SpotMarketOptions{
760+
MaxPrice: aws.String("0.10"),
761+
},
762+
},
763+
existing: &expinfrav1.AWSLaunchTemplate{
764+
AdditionalSecurityGroups: []infrav1.AWSResourceReference{
765+
{ID: aws.String("sg-111")},
766+
{ID: aws.String("sg-222")},
767+
},
768+
SpotMarketOptions: nil,
769+
},
770+
want: true,
771+
wantErr: false,
772+
},
773+
{
774+
name: "Should return true if incoming removes SpotMarketOptions and existing has some",
775+
incoming: &expinfrav1.AWSLaunchTemplate{
776+
SpotMarketOptions: nil,
777+
},
778+
existing: &expinfrav1.AWSLaunchTemplate{
779+
AdditionalSecurityGroups: []infrav1.AWSResourceReference{
780+
{ID: aws.String("sg-111")},
781+
{ID: aws.String("sg-222")},
782+
},
783+
SpotMarketOptions: &infrav1.SpotMarketOptions{
784+
MaxPrice: aws.String("0.05"),
785+
},
786+
},
787+
want: true,
788+
wantErr: false,
789+
},
577790
}
578791
for _, tt := range tests {
579792
t.Run(tt.name, func(t *testing.T) {

0 commit comments

Comments
 (0)