Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions api/v1beta1/awscluster_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,14 @@ func (src *AWSCluster) ConvertTo(dstRaw conversion.Hub) error {
dst.Status.Bastion.NetworkInterfaceType = restored.Status.Bastion.NetworkInterfaceType
dst.Status.Bastion.CapacityReservationID = restored.Status.Bastion.CapacityReservationID
dst.Status.Bastion.MarketType = restored.Status.Bastion.MarketType
dst.Status.Bastion.HostAffinity = restored.Status.Bastion.HostAffinity
dst.Status.Bastion.HostID = restored.Status.Bastion.HostID
dst.Status.Bastion.HostResourceGroupArn = restored.Status.Bastion.HostResourceGroupArn
dst.Status.Bastion.CapacityReservationPreference = restored.Status.Bastion.CapacityReservationPreference
dst.Status.Bastion.CPUOptions = restored.Status.Bastion.CPUOptions
if restored.Status.Bastion.DynamicHostAllocation != nil {
dst.Status.Bastion.DynamicHostAllocation = restored.Status.Bastion.DynamicHostAllocation
}
}
dst.Spec.Partition = restored.Spec.Partition

Expand Down
10 changes: 10 additions & 0 deletions api/v1beta1/awsmachine_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ func (src *AWSMachine) ConvertTo(dstRaw conversion.Hub) error {
dst.Spec.SecurityGroupOverrides = restored.Spec.SecurityGroupOverrides
dst.Spec.CapacityReservationID = restored.Spec.CapacityReservationID
dst.Spec.MarketType = restored.Spec.MarketType
dst.Spec.HostID = restored.Spec.HostID
dst.Spec.HostResourceGroupArn = restored.Spec.HostResourceGroupArn
dst.Spec.LicenseConfigurationArns = restored.Spec.LicenseConfigurationArns
dst.Spec.HostAffinity = restored.Spec.HostAffinity
dst.Spec.CapacityReservationPreference = restored.Spec.CapacityReservationPreference
dst.Spec.NetworkInterfaceType = restored.Spec.NetworkInterfaceType
if restored.Spec.ElasticIPPool != nil {
if dst.Spec.ElasticIPPool == nil {
Expand Down Expand Up @@ -107,6 +112,11 @@ func (r *AWSMachineTemplate) ConvertTo(dstRaw conversion.Hub) error {
dst.Spec.Template.Spec.SecurityGroupOverrides = restored.Spec.Template.Spec.SecurityGroupOverrides
dst.Spec.Template.Spec.CapacityReservationID = restored.Spec.Template.Spec.CapacityReservationID
dst.Spec.Template.Spec.MarketType = restored.Spec.Template.Spec.MarketType
dst.Spec.Template.Spec.HostID = restored.Spec.Template.Spec.HostID
dst.Spec.Template.Spec.HostResourceGroupArn = restored.Spec.Template.Spec.HostResourceGroupArn
dst.Spec.Template.Spec.LicenseConfigurationArns = restored.Spec.Template.Spec.LicenseConfigurationArns
dst.Spec.Template.Spec.HostAffinity = restored.Spec.Template.Spec.HostAffinity
dst.Spec.Template.Spec.CapacityReservationPreference = restored.Spec.Template.Spec.CapacityReservationPreference
dst.Spec.Template.Spec.NetworkInterfaceType = restored.Spec.Template.Spec.NetworkInterfaceType
if restored.Spec.Template.Spec.ElasticIPPool != nil {
if dst.Spec.Template.Spec.ElasticIPPool == nil {
Expand Down
14 changes: 14 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.

53 changes: 53 additions & 0 deletions api/v1beta2/awsmachine_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,59 @@ type AWSMachineSpec struct {
// If marketType is not specified and spotMarketOptions is provided, the marketType defaults to "Spot".
// +optional
MarketType MarketType `json:"marketType,omitempty"`

// HostID specifies the Dedicated Host on which the instance must be started.
// This field is mutually exclusive with DynamicHostAllocation.
// +kubebuilder:validation:Pattern=`^h-[0-9a-f]{17}$`
// +kubebuilder:validation:MaxLength=19
// +optional
HostID *string `json:"hostID,omitempty"`

// HostResourceGroupArn specifies the Dedicated Host Resource Group ARN on which the instance must be started.
// This field is mutually exclusive with DynamicHostAllocation and HostID.
// Note: The instance's AMI licenses must match the licenses associated with the host resource group.
// If the host resource group has no associated licenses, ensure the AMI also has no special licensing requirements.
// +kubebuilder:validation:Pattern=`^arn:aws[a-z\-]*:resource-groups:[a-z0-9\-]+:[0-9]{12}:group/[a-zA-Z0-9\-_]+$`
// +optional
HostResourceGroupArn *string `json:"hostResourceGroupArn,omitempty"`

// LicenseConfigurationArns specifies the License Configuration ARNs to associate with the instance.
// This field is required when HostResourceGroupArn is specified to ensure proper license compliance.
// +kubebuilder:validation:MaxItems=10
// +optional
LicenseConfigurationArns []string `json:"licenseConfigurationArns,omitempty"`

// HostAffinity specifies the dedicated host affinity setting for the instance.
// When HostAffinity is set to host, an instance started onto a specific host always restarts on the same host if stopped.
// When HostAffinity is set to default, and you stop and restart the instance, it can be restarted on any available host.
// When HostAffinity is defined, HostID is required.
// +optional
// +kubebuilder:validation:Enum:=default;host
// +kubebuilder:default=host
HostAffinity *string `json:"hostAffinity,omitempty"`

// DynamicHostAllocation enables automatic allocation of a single dedicated host.
// This field is mutually exclusive with HostID and always allocates exactly one host.
// Cost effectiveness of allocating a single instance on a dedicated host may vary
// depending on the instance type and the region.
// +optional
DynamicHostAllocation *DynamicHostAllocationSpec `json:"dynamicHostAllocation,omitempty"`

// CapacityReservationPreference specifies the preference for use of Capacity Reservations by the instance. Valid values include:
// "Open": The instance may make use of open Capacity Reservations that match its AZ and InstanceType
// "None": The instance may not make use of any Capacity Reservations. This is to conserve open reservations for desired workloads
// "CapacityReservationsOnly": The instance will only run if matched or targeted to a Capacity Reservation. Note that this is incompatible with a MarketType of `Spot`
// +kubebuilder:validation:Enum="";None;CapacityReservationsOnly;Open
// +optional
CapacityReservationPreference CapacityReservationPreference `json:"capacityReservationPreference,omitempty"`
}

// DynamicHostAllocationSpec defines the configuration for dynamic dedicated host allocation.
// This specification always allocates exactly one dedicated host per machine.
type DynamicHostAllocationSpec struct {
// Tags to apply to the allocated dedicated host.
// +optional
Tags map[string]string `json:"tags,omitempty"`
}

// CloudInit defines options related to the bootstrapping systems where
Expand Down
33 changes: 33 additions & 0 deletions api/v1beta2/awsmachine_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,39 @@ func (r *AWSMachine) validateAdditionalSecurityGroups() field.ErrorList {
return allErrs
}

func (r *AWSMachine) validateHostAllocation() field.ErrorList {
var allErrs field.ErrorList

// Check if multiple host allocation options are specified
hasHostID := r.Spec.HostID != nil && len(*r.Spec.HostID) > 0
hasHostResourceGroupArn := r.Spec.HostResourceGroupArn != nil && len(*r.Spec.HostResourceGroupArn) > 0
hasDynamicHostAllocation := r.Spec.DynamicHostAllocation != nil

count := 0
if hasHostID {
count++
}
if hasHostResourceGroupArn {
count++
}
if hasDynamicHostAllocation {
count++
}

if count > 1 {
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec"), "hostID, hostResourceGroupArn, and dynamicHostAllocation are mutually exclusive"))
}

// Validate licenseConfigurationArns is required when hostResourceGroupArn is specified
if hasHostResourceGroupArn {
if len(r.Spec.LicenseConfigurationArns) == 0 {
allErrs = append(allErrs, field.Required(field.NewPath("spec", "licenseConfigurationArns"), "licenseConfigurationArns is required when hostResourceGroupArn is specified"))
}
}

return allErrs
}

func (r *AWSMachine) validateSSHKeyName() field.ErrorList {
return validateSSHKeyName(r.Spec.SSHKeyName)
}
112 changes: 112 additions & 0 deletions api/v1beta2/awsmachine_webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,118 @@ func TestAWSMachineCreate(t *testing.T) {
},
wantErr: true,
},
{
name: "hostID and dynamicHostAllocation are mutually exclusive",
machine: &AWSMachine{
Spec: AWSMachineSpec{
InstanceType: "test",
HostID: aws.String("h-1234567890abcdef0"),
DynamicHostAllocation: &DynamicHostAllocationSpec{
Tags: map[string]string{
"Environment": "test",
},
},
},
},
wantErr: true,
},
{
name: "hostID alone is valid",
machine: &AWSMachine{
Spec: AWSMachineSpec{
InstanceType: "test",
HostID: aws.String("h-1234567890abcdef0"),
},
},
wantErr: false,
},
{
name: "dynamicHostAllocation alone is valid",
machine: &AWSMachine{
Spec: AWSMachineSpec{
InstanceType: "test",
DynamicHostAllocation: &DynamicHostAllocationSpec{
Tags: map[string]string{
"Environment": "test",
},
},
},
},
wantErr: false,
},
{
name: "hostResourceGroupArn alone is valid",
machine: &AWSMachine{
Spec: AWSMachineSpec{
InstanceType: "test",
HostResourceGroupArn: aws.String("arn:aws:resource-groups:us-west-2:123456789012:group/test-group"),
},
},
wantErr: false,
},
{
name: "hostID and hostResourceGroupArn are mutually exclusive",
machine: &AWSMachine{
Spec: AWSMachineSpec{
InstanceType: "test",
HostID: aws.String("h-1234567890abcdef0"),
HostResourceGroupArn: aws.String("arn:aws:resource-groups:us-west-2:123456789012:group/test-group"),
},
},
wantErr: true,
},
{
name: "hostResourceGroupArn and dynamicHostAllocation are mutually exclusive",
machine: &AWSMachine{
Spec: AWSMachineSpec{
InstanceType: "test",
HostResourceGroupArn: aws.String("arn:aws:resource-groups:us-west-2:123456789012:group/test-group"),
DynamicHostAllocation: &DynamicHostAllocationSpec{
Tags: map[string]string{
"Environment": "test",
},
},
},
},
wantErr: true,
},
{
name: "all three host allocation options are mutually exclusive",
machine: &AWSMachine{
Spec: AWSMachineSpec{
InstanceType: "test",
HostID: aws.String("h-1234567890abcdef0"),
HostResourceGroupArn: aws.String("arn:aws:resource-groups:us-west-2:123456789012:group/test-group"),
DynamicHostAllocation: &DynamicHostAllocationSpec{
Tags: map[string]string{
"Environment": "test",
},
},
},
},
wantErr: true,
},
{
name: "hostResourceGroupArn without licenseConfigurationArns should fail",
machine: &AWSMachine{
Spec: AWSMachineSpec{
InstanceType: "test",
HostResourceGroupArn: aws.String("arn:aws:resource-groups:us-west-2:123456789012:group/test-group"),
},
},
wantErr: true,
},
{
name: "hostResourceGroupArn with licenseConfigurationArns should succeed",
machine: &AWSMachine{
Spec: AWSMachineSpec{
InstanceType: "test",
HostResourceGroupArn: aws.String("arn:aws:resource-groups:us-west-2:123456789012:group/test-group"),
LicenseConfigurationArns: []string{"arn:aws:license-manager:us-west-2:259732043995:license-configuration:lic-4acd3f7c117b9e314cce36e46084d071"},
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
36 changes: 36 additions & 0 deletions api/v1beta2/awsmachinetemplate_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,42 @@ func (r *AWSMachineTemplate) validateIgnitionAndCloudInit() field.ErrorList {

return allErrs
}

func (r *AWSMachineTemplate) validateHostAllocation() field.ErrorList {
var allErrs field.ErrorList

spec := r.Spec.Template.Spec

// Check if multiple host allocation options are specified
hasHostID := spec.HostID != nil && len(*spec.HostID) > 0
hasHostResourceGroupArn := spec.HostResourceGroupArn != nil && len(*spec.HostResourceGroupArn) > 0
hasDynamicHostAllocation := spec.DynamicHostAllocation != nil

count := 0
if hasHostID {
count++
}
if hasHostResourceGroupArn {
count++
}
if hasDynamicHostAllocation {
count++
}

if count > 1 {
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec.template.spec"), "hostID, hostResourceGroupArn, and dynamicHostAllocation are mutually exclusive"))
}

// Validate licenseConfigurationArns is required when hostResourceGroupArn is specified
if hasHostResourceGroupArn {
if len(spec.LicenseConfigurationArns) == 0 {
allErrs = append(allErrs, field.Required(field.NewPath("spec", "template", "spec", "licenseConfigurationArns"), "licenseConfigurationArns is required when hostResourceGroupArn is specified"))
}
}

return allErrs
}

func (r *AWSMachineTemplate) validateSSHKeyName() field.ErrorList {
return validateSSHKeyName(r.Spec.Template.Spec.SSHKeyName)
}
Expand Down
Loading
Loading