Skip to content

Commit

Permalink
Added the suport for capacity blocks
Browse files Browse the repository at this point in the history
  • Loading branch information
athiruma committed Nov 8, 2024
1 parent 85759ce commit d34e59b
Show file tree
Hide file tree
Showing 21 changed files with 666 additions and 47 deletions.
1 change: 1 addition & 0 deletions api/v1beta1/awscluster_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ func (src *AWSCluster) ConvertTo(dstRaw conversion.Hub) error {
dst.Status.Bastion.PrivateDNSName = restored.Status.Bastion.PrivateDNSName
dst.Status.Bastion.PublicIPOnLaunch = restored.Status.Bastion.PublicIPOnLaunch
dst.Status.Bastion.CapacityReservationID = restored.Status.Bastion.CapacityReservationID
dst.Status.Bastion.CapacityBlock = restored.Status.Bastion.CapacityBlock
}
dst.Spec.Partition = restored.Spec.Partition

Expand Down
2 changes: 2 additions & 0 deletions api/v1beta1/awsmachine_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ func (src *AWSMachine) ConvertTo(dstRaw conversion.Hub) error {
dst.Spec.PrivateDNSName = restored.Spec.PrivateDNSName
dst.Spec.SecurityGroupOverrides = restored.Spec.SecurityGroupOverrides
dst.Spec.CapacityReservationID = restored.Spec.CapacityReservationID
dst.Spec.CapacityBlock = restored.Spec.CapacityBlock
if restored.Spec.ElasticIPPool != nil {
if dst.Spec.ElasticIPPool == nil {
dst.Spec.ElasticIPPool = &infrav1.ElasticIPPool{}
Expand Down Expand Up @@ -104,6 +105,7 @@ func (r *AWSMachineTemplate) ConvertTo(dstRaw conversion.Hub) error {
dst.Spec.Template.Spec.PrivateDNSName = restored.Spec.Template.Spec.PrivateDNSName
dst.Spec.Template.Spec.SecurityGroupOverrides = restored.Spec.Template.Spec.SecurityGroupOverrides
dst.Spec.Template.Spec.CapacityReservationID = restored.Spec.Template.Spec.CapacityReservationID
dst.Spec.Template.Spec.CapacityBlock = restored.Spec.Template.Spec.CapacityBlock
if restored.Spec.Template.Spec.ElasticIPPool != nil {
if dst.Spec.Template.Spec.ElasticIPPool == nil {
dst.Spec.Template.Spec.ElasticIPPool = &infrav1.ElasticIPPool{}
Expand Down
2 changes: 2 additions & 0 deletions api/v1beta1/zz_generated.conversion.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions api/v1beta2/awsmachine_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,11 @@ type AWSMachineSpec struct {
// CapacityReservationID specifies the target Capacity Reservation into which the instance should be launched.
// +optional
CapacityReservationID *string `json:"capacityReservationId,omitempty"`

// enables usage of pre-purchased compute capacity (capacity blocks) with AWS Capacity Reservations.
// If enabled, CapacityReservationID must be specified to identify the target reservation.
// +optional
CapacityBlock *bool `json:"capacityBlock,omitempty"`
}

// CloudInit defines options related to the bootstrapping systems where
Expand Down
5 changes: 5 additions & 0 deletions api/v1beta2/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,11 @@ type Instance struct {
// CapacityReservationID specifies the target Capacity Reservation into which the instance should be launched.
// +optional
CapacityReservationID *string `json:"capacityReservationId,omitempty"`

// enables usage of pre-purchased compute capacity (capacity blocks) with AWS Capacity Reservations.
// If enabled, CapacityReservationID must be specified to identify the target reservation.
// +optional
CapacityBlock *bool `json:"capacityBlock,omitempty"`
}

// InstanceMetadataState describes the state of InstanceMetadataOptions.HttpEndpoint and InstanceMetadataOptions.InstanceMetadataTags
Expand Down
10 changes: 10 additions & 0 deletions api/v1beta2/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -1138,6 +1138,11 @@ spec:
availabilityZone:
description: Availability zone of instance
type: string
capacityBlock:
description: |-
enables usage of pre-purchased compute capacity (capacity blocks) with AWS Capacity Reservations.
If enabled, CapacityReservationID must be specified to identify the target reservation.
type: boolean
capacityReservationId:
description: CapacityReservationID specifies the target Capacity
Reservation into which the instance should be launched.
Expand Down Expand Up @@ -3199,6 +3204,11 @@ spec:
availabilityZone:
description: Availability zone of instance
type: string
capacityBlock:
description: |-
enables usage of pre-purchased compute capacity (capacity blocks) with AWS Capacity Reservations.
If enabled, CapacityReservationID must be specified to identify the target reservation.
type: boolean
capacityReservationId:
description: CapacityReservationID specifies the target Capacity
Reservation into which the instance should be launched.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2113,6 +2113,11 @@ spec:
availabilityZone:
description: Availability zone of instance
type: string
capacityBlock:
description: |-
enables usage of pre-purchased compute capacity (capacity blocks) with AWS Capacity Reservations.
If enabled, CapacityReservationID must be specified to identify the target reservation.
type: boolean
capacityReservationId:
description: CapacityReservationID specifies the target Capacity
Reservation into which the instance should be launched.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,15 @@ spec:
description: ID of resource
type: string
type: object
capacityBlock:
description: |-
enables usage of pre-purchased compute capacity (capacity blocks) with AWS Capacity Reservations.
If enabled, CapacityReservationID must be specified to identify the target reservation.
type: boolean
capacityReservationId:
description: CapacityReservationID specifies the target Capacity
Reservation into which the instance should be launched.
type: string
iamInstanceProfile:
description: |-
The name or the Amazon Resource Name (ARN) of the instance profile associated
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,11 @@ spec:
description: ID of resource
type: string
type: object
capacityBlock:
description: |-
enables usage of pre-purchased compute capacity (capacity blocks) with AWS Capacity Reservations.
If enabled, CapacityReservationID must be specified to identify the target reservation.
type: boolean
capacityReservationId:
description: CapacityReservationID specifies the target Capacity Reservation
into which the instance should be launched.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,11 @@ spec:
description: ID of resource
type: string
type: object
capacityBlock:
description: |-
enables usage of pre-purchased compute capacity (capacity blocks) with AWS Capacity Reservations.
If enabled, CapacityReservationID must be specified to identify the target reservation.
type: boolean
capacityReservationId:
description: CapacityReservationID specifies the target Capacity
Reservation into which the instance should be launched.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,15 @@ spec:
description: ID of resource
type: string
type: object
capacityBlock:
description: |-
enables usage of pre-purchased compute capacity (capacity blocks) with AWS Capacity Reservations.
If enabled, CapacityReservationID must be specified to identify the target reservation.
type: boolean
capacityReservationId:
description: CapacityReservationID specifies the target Capacity
Reservation into which the instance should be launched.
type: string
iamInstanceProfile:
description: |-
The name or the Amazon Resource Name (ARN) of the instance profile associated
Expand Down
18 changes: 17 additions & 1 deletion exp/api/v1beta1/conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,16 @@ func (src *AWSMachinePool) ConvertTo(dstRaw conversion.Hub) error {
dst.Spec.AWSLaunchTemplate.PrivateDNSName = restored.Spec.AWSLaunchTemplate.PrivateDNSName
}

if restored.Spec.AWSLaunchTemplate.CapacityReservationID != nil {
dst.Spec.AWSLaunchTemplate.CapacityReservationID = restored.Spec.AWSLaunchTemplate.CapacityReservationID
}

if restored.Spec.AWSLaunchTemplate.CapacityBlock != nil {
dst.Spec.AWSLaunchTemplate.CapacityBlock = restored.Spec.AWSLaunchTemplate.CapacityBlock
}

dst.Spec.DefaultInstanceWarmup = restored.Spec.DefaultInstanceWarmup
dst.Spec.AWSLaunchTemplate.NonRootVolumes = restored.Spec.AWSLaunchTemplate.NonRootVolumes

return nil
}

Expand Down Expand Up @@ -109,6 +116,15 @@ func (src *AWSManagedMachinePool) ConvertTo(dstRaw conversion.Hub) error {
if restored.Spec.AWSLaunchTemplate.PrivateDNSName != nil {
dst.Spec.AWSLaunchTemplate.PrivateDNSName = restored.Spec.AWSLaunchTemplate.PrivateDNSName
}

if restored.Spec.AWSLaunchTemplate.CapacityReservationID != nil {
dst.Spec.AWSLaunchTemplate.CapacityReservationID = restored.Spec.AWSLaunchTemplate.CapacityReservationID
}

if restored.Spec.AWSLaunchTemplate.CapacityBlock != nil {
dst.Spec.AWSLaunchTemplate.CapacityBlock = restored.Spec.AWSLaunchTemplate.CapacityBlock
}

}
if restored.Spec.AvailabilityZoneSubnetType != nil {
dst.Spec.AvailabilityZoneSubnetType = restored.Spec.AvailabilityZoneSubnetType
Expand Down
2 changes: 2 additions & 0 deletions exp/api/v1beta1/zz_generated.conversion.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions exp/api/v1beta2/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,15 @@ type AWSLaunchTemplate struct {
// PrivateDNSName is the options for the instance hostname.
// +optional
PrivateDNSName *infrav1.PrivateDNSName `json:"privateDnsName,omitempty"`

// CapacityReservationID specifies the target Capacity Reservation into which the instance should be launched.
// +optional
CapacityReservationID *string `json:"capacityReservationId,omitempty"`

// enables usage of pre-purchased compute capacity (capacity blocks) with AWS Capacity Reservations.
// If enabled, CapacityReservationID must be specified to identify the target reservation.
// +optional
CapacityBlock *bool `json:"capacityBlock,omitempty"`
}

// Overrides are used to override the instance type specified by the launch template with multiple
Expand Down
10 changes: 10 additions & 0 deletions exp/api/v1beta2/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

39 changes: 39 additions & 0 deletions pkg/cloud/services/ec2/helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,45 @@ func setupMachinePoolScope(cl client.Client, ec2Scope scope.EC2Scope) (*scope.Ma
})
}

func setupCapacityBlocksMachinePoolScope(cl client.Client, ec2Scope scope.EC2Scope) (*scope.MachinePoolScope, error) {
return scope.NewMachinePoolScope(scope.MachinePoolScopeParams{
Client: cl,
InfraCluster: ec2Scope,
Cluster: newCluster(),
MachinePool: newMachinePool(),
AWSMachinePool: newAWSCapacityMachinePool(),
})
}

func newAWSCapacityMachinePool() *expinfrav1.AWSMachinePool {
return &expinfrav1.AWSMachinePool{
TypeMeta: metav1.TypeMeta{
Kind: "AWSMachinePool",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "aws-mp-name",
Namespace: "aws-mp-ns",
},
Spec: expinfrav1.AWSMachinePoolSpec{
AvailabilityZones: []string{"us-east-1"},
AdditionalTags: infrav1.Tags{},
AWSLaunchTemplate: expinfrav1.AWSLaunchTemplate{
Name: "aws-launch-template",
IamInstanceProfile: "instance-profile",
AMI: infrav1.AMIReference{},
InstanceType: "t3.large",
SSHKeyName: aws.String("default"),
CapacityBlock: aws.Bool(true),
CapacityReservationID: aws.String("cr-12345678901234567"),
},
},
Status: expinfrav1.AWSMachinePoolStatus{
LaunchTemplateID: "launch-template-id",
},
}
}

func defaultEC2Tags(name, clusterName string) []*ec2.Tag {
return []*ec2.Tag{
{
Expand Down
71 changes: 46 additions & 25 deletions pkg/cloud/services/ec2/instances.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,8 @@ func (s *Service) CreateInstance(scope *scope.MachineScope, userData []byte, use

input.CapacityReservationID = scope.AWSMachine.Spec.CapacityReservationID

input.CapacityBlock = scope.AWSMachine.Spec.CapacityBlock

s.scope.Debug("Running instance", "machine-role", scope.Role())
s.scope.Debug("Running instance with instance metadata options", "metadata options", input.InstanceMetadataOptions)
out, err := s.runInstance(scope.Role(), input)
Expand Down Expand Up @@ -637,8 +639,13 @@ func (s *Service) runInstance(role string, i *infrav1.Instance) (*infrav1.Instan
input.TagSpecifications = append(input.TagSpecifications, spec)
}
}

input.InstanceMarketOptions = getInstanceMarketOptionsRequest(i.SpotMarketOptions)
marketOptions, err := getInstanceMarketOptionsRequest(i)
if err != nil {
return nil, err
}
if marketOptions != nil {
input.InstanceMarketOptions = marketOptions
}
input.MetadataOptions = getInstanceMetadataOptionsRequest(i.InstanceMetadataOptions)
input.PrivateDnsNameOptions = getPrivateDNSNameOptionsRequest(i.PrivateDNSName)
input.CapacityReservationSpecification = getCapacityReservationSpecification(i.CapacityReservationID)
Expand Down Expand Up @@ -1138,34 +1145,48 @@ func getCapacityReservationSpecification(capacityReservationID *string) *ec2.Cap
}
}

func getInstanceMarketOptionsRequest(spotMarketOptions *infrav1.SpotMarketOptions) *ec2.InstanceMarketOptionsRequest {
if spotMarketOptions == nil {
// Instance is not a Spot instance
return nil
func getInstanceMarketOptionsRequest(i *infrav1.Instance) (*ec2.InstanceMarketOptionsRequest, error) {
if i.CapacityBlock != nil && i.SpotMarketOptions != nil {
return nil, errors.New("can't create spot capacity-blocks, remove spot market request")
}

// Set required values for Spot instances
spotOptions := &ec2.SpotMarketOptions{}

// The following two options ensure that:
// - If an instance is interrupted, it is terminated rather than hibernating or stopping
// - No replacement instance will be created if the instance is interrupted
// - If the spot request cannot immediately be fulfilled, it will not be created
// This behaviour should satisfy the 1:1 mapping of Machines to Instances as
// assumed by the Cluster API.
spotOptions.SetInstanceInterruptionBehavior(ec2.InstanceInterruptionBehaviorTerminate)
spotOptions.SetSpotInstanceType(ec2.SpotInstanceTypeOneTime)

maxPrice := spotMarketOptions.MaxPrice
if maxPrice != nil && *maxPrice != "" {
spotOptions.SetMaxPrice(*maxPrice)
// Handle Capacity Block case.
if ptr.Deref(i.CapacityBlock, false) {
if i.CapacityReservationID == nil {
return nil, errors.Errorf("capacityReservationID is required when CapacityBlock is enabled")
}
return &ec2.InstanceMarketOptionsRequest{
MarketType: aws.String(ec2.MarketTypeCapacityBlock),
}, nil
}

instanceMarketOptionsRequest := &ec2.InstanceMarketOptionsRequest{}
instanceMarketOptionsRequest.SetMarketType(ec2.MarketTypeSpot)
instanceMarketOptionsRequest.SetSpotOptions(spotOptions)
// Handle Spot instance case.
if i.SpotMarketOptions == nil {
// Instance is not a Spot instance
return nil, nil
}

return instanceMarketOptionsRequest
// Set required values for Spot instances
spotOpts := &ec2.SpotMarketOptions{
// The following two options ensure that:
// - If an instance is interrupted, it is terminated rather than hibernating or stopping
// - No replacement instance will be created if the instance is interrupted
// - If the spot request cannot immediately be fulfilled, it will not be created
// This behaviour should satisfy the 1:1 mapping of Machines to Instances as
// assumed by the Cluster API.
InstanceInterruptionBehavior: aws.String(ec2.InstanceInterruptionBehaviorTerminate),
SpotInstanceType: aws.String(ec2.SpotInstanceTypeOneTime),
}

maxPrice := ptr.Deref(i.SpotMarketOptions.MaxPrice, "")
if maxPrice != "" {
spotOpts.MaxPrice = aws.String(maxPrice)
}

return &ec2.InstanceMarketOptionsRequest{
MarketType: aws.String(ec2.MarketTypeSpot),
SpotOptions: spotOpts,
}, nil
}

func getInstanceMetadataOptionsRequest(metadataOptions *infrav1.InstanceMetadataOptions) *ec2.InstanceMetadataOptionsRequest {
Expand Down
Loading

0 comments on commit d34e59b

Please sign in to comment.