Skip to content

Commit 5b06b69

Browse files
committed
select instance 'MarketType' option
1 parent 398422f commit 5b06b69

File tree

16 files changed

+1462
-41
lines changed

16 files changed

+1462
-41
lines changed

Diff for: pkg/actuators/machine/instances.go

+51-23
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,12 @@ func launchInstance(machine *machinev1beta1.Machine, machineProviderConfig *mach
447447
return nil, err
448448
}
449449

450+
instanceMarketOptions, err := getInstanceMarketOptionsRequest(machineProviderConfig)
451+
452+
if err != nil {
453+
return nil, err
454+
}
455+
450456
inputConfig := ec2.RunInstancesInput{
451457
ImageId: amiID,
452458
InstanceType: aws.String(machineProviderConfig.InstanceType),
@@ -460,7 +466,7 @@ func launchInstance(machine *machinev1beta1.Machine, machineProviderConfig *mach
460466
UserData: &userDataEnc,
461467
Placement: placement,
462468
MetadataOptions: getInstanceMetadataOptionsRequest(machineProviderConfig),
463-
InstanceMarketOptions: getInstanceMarketOptionsRequest(machineProviderConfig),
469+
InstanceMarketOptions: instanceMarketOptions,
464470
CapacityReservationSpecification: capacityReservationSpecification,
465471
}
466472

@@ -566,34 +572,56 @@ func sortInstances(instances []*ec2.Instance) {
566572
sort.Sort(instanceList(instances))
567573
}
568574

569-
func getInstanceMarketOptionsRequest(providerConfig *machinev1beta1.AWSMachineProviderConfig) *ec2.InstanceMarketOptionsRequest {
570-
if providerConfig.SpotMarketOptions == nil {
571-
// Instance is not a Spot instance
572-
return nil
575+
func getInstanceMarketOptionsRequest(providerConfig *machinev1beta1.AWSMachineProviderConfig) (*ec2.InstanceMarketOptionsRequest, error) {
576+
if providerConfig.MarketType != "" && providerConfig.MarketType == machinev1beta1.MarketTypeCapacityBlock && providerConfig.SpotMarketOptions != nil {
577+
return nil, errors.New("can't create spot capacity-blocks, remove spot market request")
573578
}
574579

575-
// Set required values for Spot instances
576-
spotOptions := &ec2.SpotMarketOptions{}
577-
// The following two options ensure that:
578-
// - If an instance is interrupted, it is terminated rather than hibernating or stopping
579-
// - No replacement instance will be created if the instance is interrupted
580-
// - If the spot request cannot immediately be fulfilled, it will not be created
581-
// This behaviour should satisfy the 1:1 mapping of Machines to Instances as
582-
// assumed by the machine API.
583-
spotOptions.SetInstanceInterruptionBehavior(ec2.InstanceInterruptionBehaviorTerminate)
584-
spotOptions.SetSpotInstanceType(ec2.SpotInstanceTypeOneTime)
580+
// Infer MarketType if not explicitly set
581+
if providerConfig.SpotMarketOptions != nil && providerConfig.MarketType == "" {
582+
providerConfig.MarketType = machinev1beta1.MarketTypeSpot
583+
}
585584

586-
// Set the MaxPrice if specified by the providerConfig
587-
maxPrice := providerConfig.SpotMarketOptions.MaxPrice
588-
if maxPrice != nil && *maxPrice != "" {
589-
spotOptions.SetMaxPrice(*maxPrice)
585+
if providerConfig.MarketType == "" {
586+
providerConfig.MarketType = machinev1beta1.MarketTypeOnDemand
590587
}
591588

592-
instanceMarketOptionsRequest := &ec2.InstanceMarketOptionsRequest{}
593-
instanceMarketOptionsRequest.SetMarketType(ec2.MarketTypeSpot)
594-
instanceMarketOptionsRequest.SetSpotOptions(spotOptions)
589+
switch providerConfig.MarketType {
590+
case machinev1beta1.MarketTypeCapacityBlock:
591+
if providerConfig.CapacityReservationID == "" {
592+
return nil, errors.New("capacityReservationID is required when CapacityBlock is enabled")
593+
}
594+
return &ec2.InstanceMarketOptionsRequest{
595+
MarketType: aws.String(ec2.MarketTypeCapacityBlock),
596+
}, nil
597+
case machinev1beta1.MarketTypeSpot:
598+
// Set required values for Spot instances
599+
spotOpts := &ec2.SpotMarketOptions{
600+
// The following two options ensure that:
601+
// - If an instance is interrupted, it is terminated rather than hibernating or stopping
602+
// - No replacement instance will be created if the instance is interrupted
603+
// - If the spot request cannot immediately be fulfilled, it will not be created
604+
// This behaviour should satisfy the 1:1 mapping of Machines to Instances as
605+
// assumed by the Cluster API.
606+
InstanceInterruptionBehavior: aws.String(ec2.InstanceInterruptionBehaviorTerminate),
607+
SpotInstanceType: aws.String(ec2.SpotInstanceTypeOneTime),
608+
}
609+
610+
if maxPrice := aws.StringValue(providerConfig.SpotMarketOptions.MaxPrice); maxPrice != "" {
611+
spotOpts.MaxPrice = aws.String(maxPrice)
612+
}
595613

596-
return instanceMarketOptionsRequest
614+
return &ec2.InstanceMarketOptionsRequest{
615+
MarketType: aws.String(ec2.MarketTypeSpot),
616+
SpotOptions: spotOpts,
617+
}, nil
618+
case machinev1beta1.MarketTypeOnDemand:
619+
// Instance is on-demand or empty
620+
return nil, nil
621+
default:
622+
// Invalid MarketType provided
623+
return nil, fmt.Errorf("invalid MarketType %q", providerConfig.MarketType)
624+
}
597625
}
598626

599627
// constructInstancePlacement configures the placement options for the RunInstances request

Diff for: pkg/actuators/machine/instances_test.go

+67-18
Original file line numberDiff line numberDiff line change
@@ -1227,31 +1227,39 @@ func TestSortInstances(t *testing.T) {
12271227
}
12281228

12291229
func TestGetInstanceMarketOptionsRequest(t *testing.T) {
1230+
mockCapacityReservationID := "cr-123"
12301231
testCases := []struct {
1231-
name string
1232-
spotMarketOptions *machinev1beta1.SpotMarketOptions
1233-
expectedRequest *ec2.InstanceMarketOptionsRequest
1232+
name string
1233+
providerConfig *machinev1beta1.AWSMachineProviderConfig
1234+
expectedRequest *ec2.InstanceMarketOptionsRequest
1235+
wantErr bool
12341236
}{
12351237
{
1236-
name: "with no Spot options specified",
1237-
spotMarketOptions: nil,
1238-
expectedRequest: nil,
1238+
name: "with no Spot options specified",
1239+
providerConfig: &machinev1beta1.AWSMachineProviderConfig{},
1240+
expectedRequest: nil,
1241+
wantErr: false,
12391242
},
12401243
{
1241-
name: "with an empty Spot options specified",
1242-
spotMarketOptions: &machinev1beta1.SpotMarketOptions{},
1244+
name: "with an empty Spot options specified",
1245+
providerConfig: &machinev1beta1.AWSMachineProviderConfig{
1246+
SpotMarketOptions: &machinev1beta1.SpotMarketOptions{},
1247+
},
12431248
expectedRequest: &ec2.InstanceMarketOptionsRequest{
12441249
MarketType: aws.String(ec2.MarketTypeSpot),
12451250
SpotOptions: &ec2.SpotMarketOptions{
12461251
InstanceInterruptionBehavior: aws.String(ec2.InstanceInterruptionBehaviorTerminate),
12471252
SpotInstanceType: aws.String(ec2.SpotInstanceTypeOneTime),
12481253
},
12491254
},
1255+
wantErr: false,
12501256
},
12511257
{
12521258
name: "with an empty MaxPrice specified",
1253-
spotMarketOptions: &machinev1beta1.SpotMarketOptions{
1254-
MaxPrice: aws.String(""),
1259+
providerConfig: &machinev1beta1.AWSMachineProviderConfig{
1260+
SpotMarketOptions: &machinev1beta1.SpotMarketOptions{
1261+
MaxPrice: aws.String(""),
1262+
},
12551263
},
12561264
expectedRequest: &ec2.InstanceMarketOptionsRequest{
12571265
MarketType: aws.String(ec2.MarketTypeSpot),
@@ -1260,11 +1268,14 @@ func TestGetInstanceMarketOptionsRequest(t *testing.T) {
12601268
SpotInstanceType: aws.String(ec2.SpotInstanceTypeOneTime),
12611269
},
12621270
},
1271+
wantErr: false,
12631272
},
12641273
{
12651274
name: "with a valid MaxPrice specified",
1266-
spotMarketOptions: &machinev1beta1.SpotMarketOptions{
1267-
MaxPrice: aws.String("0.01"),
1275+
providerConfig: &machinev1beta1.AWSMachineProviderConfig{
1276+
SpotMarketOptions: &machinev1beta1.SpotMarketOptions{
1277+
MaxPrice: aws.String("0.01"),
1278+
},
12681279
},
12691280
expectedRequest: &ec2.InstanceMarketOptionsRequest{
12701281
MarketType: aws.String(ec2.MarketTypeSpot),
@@ -1274,18 +1285,56 @@ func TestGetInstanceMarketOptionsRequest(t *testing.T) {
12741285
MaxPrice: aws.String("0.01"),
12751286
},
12761287
},
1288+
wantErr: false,
1289+
},
1290+
{
1291+
name: "invalid MarketType specified",
1292+
expectedRequest: nil,
1293+
providerConfig: &machinev1beta1.AWSMachineProviderConfig{
1294+
MarketType: machinev1beta1.MarketType("invalid"),
1295+
},
1296+
wantErr: true,
1297+
},
1298+
{
1299+
name: "with a MarketType to MarketTypeCapacityBlock specified with capacityReservationID set to nil",
1300+
providerConfig: &machinev1beta1.AWSMachineProviderConfig{
1301+
MarketType: machinev1beta1.MarketTypeCapacityBlock,
1302+
CapacityReservationID: "",
1303+
},
1304+
expectedRequest: nil,
1305+
wantErr: true,
1306+
},
1307+
{
1308+
name: "with a MarketType to MarketTypeCapacityBlock with capacityReservationID set to nil",
1309+
providerConfig: &machinev1beta1.AWSMachineProviderConfig{
1310+
MarketType: machinev1beta1.MarketTypeCapacityBlock,
1311+
CapacityReservationID: mockCapacityReservationID,
1312+
},
1313+
expectedRequest: &ec2.InstanceMarketOptionsRequest{
1314+
MarketType: aws.String(ec2.MarketTypeCapacityBlock),
1315+
},
1316+
wantErr: false,
1317+
},
1318+
{
1319+
name: "with a MarketType to MarketTypeCapacityBlock set with capacityReservationID set and empty Spot options specified",
1320+
providerConfig: &machinev1beta1.AWSMachineProviderConfig{
1321+
MarketType: machinev1beta1.MarketTypeCapacityBlock,
1322+
CapacityReservationID: mockCapacityReservationID,
1323+
SpotMarketOptions: &machinev1beta1.SpotMarketOptions{},
1324+
},
1325+
wantErr: true,
12771326
},
12781327
}
12791328

12801329
for _, tc := range testCases {
12811330
t.Run(tc.name, func(t *testing.T) {
1282-
providerConfig := &machinev1beta1.AWSMachineProviderConfig{
1283-
SpotMarketOptions: tc.spotMarketOptions,
1284-
}
1331+
g := gmg.NewWithT(t)
12851332

1286-
request := getInstanceMarketOptionsRequest(providerConfig)
1287-
if !reflect.DeepEqual(request, tc.expectedRequest) {
1288-
t.Errorf("Case: %s. Got: %v, expected: %v", tc.name, request, tc.expectedRequest)
1333+
request, err := getInstanceMarketOptionsRequest(tc.providerConfig)
1334+
if err == nil {
1335+
g.Expect(request).To(gmg.BeEquivalentTo(tc.expectedRequest))
1336+
} else {
1337+
g.Expect(err).To(gmg.HaveOccurred())
12891338
}
12901339
})
12911340
}

Diff for: vendor/github.com/spf13/afero/.editorconfig

+12
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: vendor/github.com/spf13/afero/.golangci.yaml

+18
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: vendor/github.com/spf13/pflag/.editorconfig

+12
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: vendor/github.com/spf13/pflag/.golangci.yaml

+4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: vendor/golang.org/x/net/internal/httpcommon/ascii.go

+53
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)