Skip to content

Commit fb9abd3

Browse files
committed
update
1 parent 8ab8731 commit fb9abd3

27 files changed

+1508
-40
lines changed

api/v1beta1/awscluster_conversion.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ func (src *AWSCluster) ConvertTo(dstRaw conversion.Hub) error {
6666
dst.Status.Bastion.HostAffinity = restored.Status.Bastion.HostAffinity
6767
dst.Status.Bastion.HostID = restored.Status.Bastion.HostID
6868
dst.Status.Bastion.CapacityReservationPreference = restored.Status.Bastion.CapacityReservationPreference
69+
if restored.Status.Bastion.DynamicHostAllocation != nil {
70+
dst.Status.Bastion.DynamicHostAllocation = restored.Status.Bastion.DynamicHostAllocation
71+
}
6972
}
7073
dst.Spec.Partition = restored.Spec.Partition
7174

api/v1beta1/awsmachine_conversion.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ func (src *AWSMachine) ConvertTo(dstRaw conversion.Hub) error {
4848
dst.Spec.HostAffinity = restored.Spec.HostAffinity
4949
dst.Spec.CapacityReservationPreference = restored.Spec.CapacityReservationPreference
5050
dst.Spec.NetworkInterfaceType = restored.Spec.NetworkInterfaceType
51+
if restored.Spec.DynamicHostAllocation != nil {
52+
dst.Spec.DynamicHostAllocation = restored.Spec.DynamicHostAllocation
53+
}
5154
if restored.Spec.ElasticIPPool != nil {
5255
if dst.Spec.ElasticIPPool == nil {
5356
dst.Spec.ElasticIPPool = &infrav1.ElasticIPPool{}
@@ -60,6 +63,11 @@ func (src *AWSMachine) ConvertTo(dstRaw conversion.Hub) error {
6063
}
6164
}
6265

66+
dst.Status.DedicatedHostID = restored.Status.DedicatedHostID
67+
dst.Status.HostReleaseAttempts = restored.Status.HostReleaseAttempts
68+
dst.Status.LastHostReleaseAttempt = restored.Status.LastHostReleaseAttempt
69+
dst.Status.HostReleaseFailedReason = restored.Status.HostReleaseFailedReason
70+
6371
return nil
6472
}
6573

@@ -115,6 +123,9 @@ func (r *AWSMachineTemplate) ConvertTo(dstRaw conversion.Hub) error {
115123
dst.Spec.Template.Spec.HostAffinity = restored.Spec.Template.Spec.HostAffinity
116124
dst.Spec.Template.Spec.CapacityReservationPreference = restored.Spec.Template.Spec.CapacityReservationPreference
117125
dst.Spec.Template.Spec.NetworkInterfaceType = restored.Spec.Template.Spec.NetworkInterfaceType
126+
if restored.Spec.Template.Spec.DynamicHostAllocation != nil {
127+
dst.Spec.Template.Spec.DynamicHostAllocation = restored.Spec.Template.Spec.DynamicHostAllocation
128+
}
118129
if restored.Spec.Template.Spec.ElasticIPPool != nil {
119130
if dst.Spec.Template.Spec.ElasticIPPool == nil {
120131
dst.Spec.Template.Spec.ElasticIPPool = &infrav1.ElasticIPPool{}

api/v1beta1/conversion.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,3 +103,8 @@ func Convert_v1beta2_S3Bucket_To_v1beta1_S3Bucket(in *v1beta2.S3Bucket, out *S3B
103103
func Convert_v1beta2_Ignition_To_v1beta1_Ignition(in *v1beta2.Ignition, out *Ignition, s conversion.Scope) error {
104104
return autoConvert_v1beta2_Ignition_To_v1beta1_Ignition(in, out, s)
105105
}
106+
107+
func Convert_v1beta2_AWSMachineStatus_To_v1beta1_AWSMachineStatus(in *v1beta2.AWSMachineStatus, out *AWSMachineStatus, s conversion.Scope) error {
108+
// Note: DedicatedHostID is not present in v1beta1, so it will be dropped during conversion
109+
return autoConvert_v1beta2_AWSMachineStatus_To_v1beta1_AWSMachineStatus(in, out, s)
110+
}

api/v1beta1/zz_generated.conversion.go

Lines changed: 6 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/v1beta2/awsmachine_types.go

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ const (
4141
DefaultMachinePoolIgnitionStorageType = IgnitionStorageTypeOptionUnencryptedUserData
4242
)
4343

44+
4445
// SecretBackend defines variants for backend secret storage.
4546
type SecretBackend string
4647

@@ -213,6 +214,10 @@ type AWSMachineSpec struct {
213214
PlacementGroupPartition int64 `json:"placementGroupPartition,omitempty"`
214215

215216
// Tenancy indicates if instance should run on shared or single-tenant hardware.
217+
// When Tenancy=host, AWS will attempt to find a suitable host from:
218+
// - Preexisting allocated hosts that have auto-placement enabled
219+
// - A specific host ID, if configured
220+
// - Allocating a new dedicated host if DynamicHostAllocation is configured
216221
// +optional
217222
// +kubebuilder:validation:Enum:=default;dedicated;host
218223
Tenancy string `json:"tenancy,omitempty"`
@@ -235,17 +240,27 @@ type AWSMachineSpec struct {
235240
MarketType MarketType `json:"marketType,omitempty"`
236241

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

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

257+
// DynamicHostAllocation enables automatic allocation of a single dedicated host.
258+
// This field is mutually exclusive with HostID and always allocates exactly one host.
259+
// Cost effectiveness of allocating a single instance on a dedicated host may vary
260+
// depending on the instance type and the region.
261+
// +optional
262+
DynamicHostAllocation *DynamicHostAllocationSpec `json:"dynamicHostAllocation,omitempty"`
263+
249264
// CapacityReservationPreference specifies the preference for use of Capacity Reservations by the instance. Valid values include:
250265
// "Open": The instance may make use of open Capacity Reservations that match its AZ and InstanceType
251266
// "None": The instance may not make use of any Capacity Reservations. This is to conserve open reservations for desired workloads
@@ -255,6 +270,14 @@ type AWSMachineSpec struct {
255270
CapacityReservationPreference CapacityReservationPreference `json:"capacityReservationPreference,omitempty"`
256271
}
257272

273+
// DynamicHostAllocationSpec defines the configuration for dynamic dedicated host allocation.
274+
// This specification always allocates exactly one dedicated host per machine.
275+
type DynamicHostAllocationSpec struct {
276+
// Tags to apply to the allocated dedicated host.
277+
// +optional
278+
Tags map[string]string `json:"tags,omitempty"`
279+
}
280+
258281
// CloudInit defines options related to the bootstrapping systems where
259282
// CloudInit is used.
260283
type CloudInit struct {
@@ -432,6 +455,23 @@ type AWSMachineStatus struct {
432455
// Conditions defines current service state of the AWSMachine.
433456
// +optional
434457
Conditions clusterv1.Conditions `json:"conditions,omitempty"`
458+
459+
// DedicatedHostID tracks the dynamically allocated dedicated host ID.
460+
// This field is populated when DynamicHostAllocation is used.
461+
// +optional
462+
DedicatedHostID *string `json:"dedicatedHostID,omitempty"`
463+
464+
// HostReleaseAttempts tracks the number of attempts to release the dedicated host.
465+
// +optional
466+
HostReleaseAttempts *int32 `json:"hostReleaseAttempts,omitempty"`
467+
468+
// LastHostReleaseAttempt tracks the timestamp of the last attempt to release the dedicated host.
469+
// +optional
470+
LastHostReleaseAttempt *metav1.Time `json:"lastHostReleaseAttempt,omitempty"`
471+
472+
// HostReleaseFailedReason tracks the reason for the last host release failure.
473+
// +optional
474+
HostReleaseFailedReason *string `json:"hostReleaseFailedReason,omitempty"`
435475
}
436476

437477
// +kubebuilder:object:root=true

api/v1beta2/awsmachine_webhook.go

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,11 @@ func (*awsMachineWebhook) ValidateCreate(_ context.Context, obj runtime.Object)
7575
allErrs = append(allErrs, r.validateNonRootVolumes()...)
7676
allErrs = append(allErrs, r.validateSSHKeyName()...)
7777
allErrs = append(allErrs, r.validateAdditionalSecurityGroups()...)
78-
allErrs = append(allErrs, r.validateHostAffinity()...)
7978
allErrs = append(allErrs, r.Spec.AdditionalTags.Validate()...)
8079
allErrs = append(allErrs, r.validateNetworkElasticIPPool()...)
8180
allErrs = append(allErrs, r.validateInstanceMarketType()...)
8281
allErrs = append(allErrs, r.validateCapacityReservation()...)
82+
allErrs = append(allErrs, r.validateHostAllocation()...)
8383

8484
return nil, aggregateObjErrors(r.GroupVersionKind().GroupKind(), r.Name, allErrs)
8585
}
@@ -109,7 +109,7 @@ func (*awsMachineWebhook) ValidateUpdate(ctx context.Context, oldObj, newObj run
109109
allErrs = append(allErrs, r.validateCloudInitSecret()...)
110110
allErrs = append(allErrs, r.validateAdditionalSecurityGroups()...)
111111
allErrs = append(allErrs, r.Spec.AdditionalTags.Validate()...)
112-
allErrs = append(allErrs, r.validateHostAffinity()...)
112+
allErrs = append(allErrs, r.validateHostAllocation()...)
113113

114114
newAWSMachineSpec := newAWSMachine["spec"].(map[string]interface{})
115115
oldAWSMachineSpec := oldAWSMachine["spec"].(map[string]interface{})
@@ -466,14 +466,17 @@ func (r *AWSMachine) validateAdditionalSecurityGroups() field.ErrorList {
466466
return allErrs
467467
}
468468

469-
func (r *AWSMachine) validateHostAffinity() field.ErrorList {
469+
func (r *AWSMachine) validateHostAllocation() field.ErrorList {
470470
var allErrs field.ErrorList
471471

472-
if r.Spec.HostAffinity != nil {
473-
if r.Spec.HostID == nil || len(*r.Spec.HostID) == 0 {
474-
allErrs = append(allErrs, field.Required(field.NewPath("spec.hostID"), "hostID must be set when hostAffinity is configured"))
475-
}
472+
// Check if both hostID and dynamicHostAllocation are specified
473+
hasHostID := r.Spec.HostID != nil && len(*r.Spec.HostID) > 0
474+
hasDynamicHostAllocation := r.Spec.DynamicHostAllocation != nil
475+
476+
if hasHostID && hasDynamicHostAllocation {
477+
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec.hostID"), "hostID and dynamicHostAllocation are mutually exclusive"), field.Forbidden(field.NewPath("spec.dynamicHostAllocation"), "hostID and dynamicHostAllocation are mutually exclusive"))
476478
}
479+
477480
return allErrs
478481
}
479482

api/v1beta2/awsmachine_webhook_test.go

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -465,16 +465,6 @@ func TestAWSMachineCreate(t *testing.T) {
465465
},
466466
wantErr: true,
467467
},
468-
{
469-
name: "configure host affinity without Host ID",
470-
machine: &AWSMachine{
471-
Spec: AWSMachineSpec{
472-
InstanceType: "test",
473-
HostAffinity: ptr.To("default"),
474-
},
475-
},
476-
wantErr: true,
477-
},
478468
{
479469
name: "create with valid BYOIPv4",
480470
machine: &AWSMachine{
@@ -543,6 +533,45 @@ func TestAWSMachineCreate(t *testing.T) {
543533
},
544534
wantErr: true,
545535
},
536+
{
537+
name: "hostID and dynamicHostAllocation are mutually exclusive",
538+
machine: &AWSMachine{
539+
Spec: AWSMachineSpec{
540+
InstanceType: "test",
541+
HostID: aws.String("h-1234567890abcdef0"),
542+
DynamicHostAllocation: &DynamicHostAllocationSpec{
543+
Tags: map[string]string{
544+
"Environment": "test",
545+
},
546+
},
547+
},
548+
},
549+
wantErr: true,
550+
},
551+
{
552+
name: "hostID alone is valid",
553+
machine: &AWSMachine{
554+
Spec: AWSMachineSpec{
555+
InstanceType: "test",
556+
HostID: aws.String("h-1234567890abcdef0"),
557+
},
558+
},
559+
wantErr: false,
560+
},
561+
{
562+
name: "dynamicHostAllocation alone is valid",
563+
machine: &AWSMachine{
564+
Spec: AWSMachineSpec{
565+
InstanceType: "test",
566+
DynamicHostAllocation: &DynamicHostAllocationSpec{
567+
Tags: map[string]string{
568+
"Environment": "test",
569+
},
570+
},
571+
},
572+
},
573+
wantErr: false,
574+
},
546575
}
547576
for _, tt := range tests {
548577
t.Run(tt.name, func(t *testing.T) {

api/v1beta2/awsmachinetemplate_webhook.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,22 @@ func (r *AWSMachineTemplate) validateIgnitionAndCloudInit() field.ErrorList {
172172

173173
return allErrs
174174
}
175+
func (r *AWSMachineTemplate) validateHostAllocation() field.ErrorList {
176+
var allErrs field.ErrorList
177+
178+
spec := r.Spec.Template.Spec
179+
180+
// Check if both hostID and dynamicHostAllocation are specified
181+
hasHostID := spec.HostID != nil && len(*spec.HostID) > 0
182+
hasDynamicHostAllocation := spec.DynamicHostAllocation != nil
183+
184+
if hasHostID && hasDynamicHostAllocation {
185+
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec.template.spec.hostID"), "hostID and dynamicHostAllocation are mutually exclusive"), field.Forbidden(field.NewPath("spec.template.spec.dynamicHostAllocation"), "hostID and dynamicHostAllocation are mutually exclusive"))
186+
}
187+
188+
return allErrs
189+
}
190+
175191
func (r *AWSMachineTemplate) validateSSHKeyName() field.ErrorList {
176192
return validateSSHKeyName(r.Spec.Template.Spec.SSHKeyName)
177193
}
@@ -205,6 +221,7 @@ func (r *AWSMachineTemplateWebhook) ValidateCreate(_ context.Context, raw runtim
205221
allErrs = append(allErrs, obj.validateSSHKeyName()...)
206222
allErrs = append(allErrs, obj.validateAdditionalSecurityGroups()...)
207223
allErrs = append(allErrs, obj.Spec.Template.Spec.AdditionalTags.Validate()...)
224+
allErrs = append(allErrs, obj.validateHostAllocation()...)
208225

209226
return nil, aggregateObjErrors(obj.GroupVersionKind().GroupKind(), obj.Name, allErrs)
210227
}

api/v1beta2/awsmachinetemplate_webhook_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,26 @@ func TestAWSMachineTemplateValidateCreate(t *testing.T) {
8080
},
8181
wantError: false,
8282
},
83+
{
84+
name: "hostID and dynamicHostAllocation are mutually exclusive",
85+
inputTemplate: &AWSMachineTemplate{
86+
ObjectMeta: metav1.ObjectMeta{},
87+
Spec: AWSMachineTemplateSpec{
88+
Template: AWSMachineTemplateResource{
89+
Spec: AWSMachineSpec{
90+
InstanceType: "test",
91+
HostID: aws.String("h-1234567890abcdef0"),
92+
DynamicHostAllocation: &DynamicHostAllocationSpec{
93+
Tags: map[string]string{
94+
"Environment": "test",
95+
},
96+
},
97+
},
98+
},
99+
},
100+
},
101+
wantError: true,
102+
},
83103
}
84104
for _, tt := range tests {
85105
t.Run(tt.name, func(t *testing.T) {

api/v1beta2/conditions_consts.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,10 @@ const (
146146
// InstanceReadyCondition reports on current status of the EC2 instance. Ready indicates the instance is in a Running state.
147147
InstanceReadyCondition clusterv1.ConditionType = "InstanceReady"
148148

149+
// DedicatedHostReleaseCondition reports on the status of dedicated host release operations.
150+
// This condition tracks whether the dedicated host has been successfully released or if there are failures.
151+
DedicatedHostReleaseCondition clusterv1.ConditionType = "DedicatedHostRelease"
152+
149153
// InstanceNotFoundReason used when the instance couldn't be retrieved.
150154
InstanceNotFoundReason = "InstanceNotFound"
151155
// InstanceTerminatedReason instance is in a terminated state.
@@ -191,4 +195,13 @@ const (
191195

192196
// S3BucketFailedReason is used when any errors occur during reconciliation of an S3 bucket.
193197
S3BucketFailedReason = "S3BucketCreationFailed"
198+
199+
// DedicatedHostReleaseSucceededReason used when the dedicated host is successfully released.
200+
DedicatedHostReleaseSucceededReason = "DedicatedHostReleaseSucceeded"
201+
202+
// DedicatedHostReleaseFailedReason used when the dedicated host release fails.
203+
DedicatedHostReleaseFailedReason = "DedicatedHostReleaseFailed"
204+
205+
// DedicatedHostReleaseRetryingReason used when the dedicated host release is being retried.
206+
DedicatedHostReleaseRetryingReason = "DedicatedHostReleaseRetrying"
194207
)

0 commit comments

Comments
 (0)