Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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