Skip to content

Commit a58c1d0

Browse files
rvanderp3jimzim
andcommitted
add support for dynamic dedicated host allocation
Co-authored-by: Jim Zimmerman <[email protected]>
1 parent 8ab8731 commit a58c1d0

25 files changed

+1412
-11
lines changed

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: AllocatedHostID 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: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,19 @@ const (
4141
DefaultMachinePoolIgnitionStorageType = IgnitionStorageTypeOptionUnencryptedUserData
4242
)
4343

44+
// DynamicHostReleaseStrategy defines the strategy for releasing a dynamically allocated dedicated host.
45+
// It determines when the dedicated host associated with an AWSMachine should be released, such as
46+
// automatically upon machine deletion or only when manually triggered.
47+
type DynamicHostReleaseStrategy string
48+
49+
const (
50+
// DedicatedHostReleaseStrategyOnMachineDeletion means the dedicated host will be released when the machine is deleted.
51+
DedicatedHostReleaseStrategyOnMachineDeletion = DynamicHostReleaseStrategy("on-machine-deletion")
52+
53+
// DedicatedHostReleaseStrategyManual means the dedicated host will be released manually by the user.
54+
DedicatedHostReleaseStrategyManual = DynamicHostReleaseStrategy("manual")
55+
)
56+
4457
// SecretBackend defines variants for backend secret storage.
4558
type SecretBackend string
4659

@@ -235,17 +248,25 @@ type AWSMachineSpec struct {
235248
MarketType MarketType `json:"marketType,omitempty"`
236249

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

241256
// 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.
257+
// When HostAffinity is set to host, an instance started onto a specific host always restarts on the same host if stopped.
258+
// When HostAffinity is set to default, and you stop and restart the instance, it can be restarted on any available host.
244259
// When HostAffinity is defined, HostID is required.
245260
// +optional
246261
// +kubebuilder:validation:Enum:=default;host
262+
// +kubebuilder:default=default
247263
HostAffinity *string `json:"hostAffinity,omitempty"`
248264

265+
// DynamicHostAllocation enables automatic allocation of a single dedicated host.
266+
// This field is mutually exclusive with HostID and always allocates exactly one host.
267+
// +optional
268+
DynamicHostAllocation *DynamicHostAllocationSpec `json:"dynamicHostAllocation,omitempty"`
269+
249270
// CapacityReservationPreference specifies the preference for use of Capacity Reservations by the instance. Valid values include:
250271
// "Open": The instance may make use of open Capacity Reservations that match its AZ and InstanceType
251272
// "None": The instance may not make use of any Capacity Reservations. This is to conserve open reservations for desired workloads
@@ -255,6 +276,21 @@ type AWSMachineSpec struct {
255276
CapacityReservationPreference CapacityReservationPreference `json:"capacityReservationPreference,omitempty"`
256277
}
257278

279+
// DynamicHostAllocationSpec defines the configuration for dynamic dedicated host allocation.
280+
// This specification always allocates exactly one dedicated host per machine.
281+
type DynamicHostAllocationSpec struct {
282+
// AutoRelease determines whether to automatically release the dedicated host
283+
// when the machine is deleted.
284+
// +kubebuilder:default=on-machine-deletion
285+
// +kubebuilder:validation:Enum:=on-machine-deletion;manual
286+
// +optional
287+
Release DynamicHostReleaseStrategy `json:"release,omitempty"`
288+
289+
// Tags to apply to the allocated dedicated host.
290+
// +optional
291+
Tags map[string]string `json:"tags,omitempty"`
292+
}
293+
258294
// CloudInit defines options related to the bootstrapping systems where
259295
// CloudInit is used.
260296
type CloudInit struct {
@@ -432,6 +468,26 @@ type AWSMachineStatus struct {
432468
// Conditions defines current service state of the AWSMachine.
433469
// +optional
434470
Conditions clusterv1.Conditions `json:"conditions,omitempty"`
471+
472+
// AllocatedHostID tracks the dynamically allocated dedicated host ID.
473+
// This field is populated when DynamicHostAllocation is used.
474+
// +optional
475+
AllocatedHostID *string `json:"allocatedHostID,omitempty"`
476+
477+
// HostReleaseAttempts tracks the number of attempts to release the dedicated host.
478+
// This field is used for implementing retry logic when host release fails.
479+
// +optional
480+
HostReleaseAttempts *int32 `json:"hostReleaseAttempts,omitempty"`
481+
482+
// LastHostReleaseAttempt tracks the timestamp of the last attempt to release the dedicated host.
483+
// This field is used for implementing retry logic with backoff.
484+
// +optional
485+
LastHostReleaseAttempt *metav1.Time `json:"lastHostReleaseAttempt,omitempty"`
486+
487+
// HostReleaseFailedReason tracks the reason for the last host release failure.
488+
// This field is used for debugging and implementing retry logic.
489+
// +optional
490+
HostReleaseFailedReason *string `json:"hostReleaseFailedReason,omitempty"`
435491
}
436492

437493
// +kubebuilder:object:root=true

api/v1beta2/awsmachine_webhook.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ func (*awsMachineWebhook) ValidateCreate(_ context.Context, obj runtime.Object)
8080
allErrs = append(allErrs, r.validateNetworkElasticIPPool()...)
8181
allErrs = append(allErrs, r.validateInstanceMarketType()...)
8282
allErrs = append(allErrs, r.validateCapacityReservation()...)
83+
allErrs = append(allErrs, r.validateHostAllocation()...)
8384

8485
return nil, aggregateObjErrors(r.GroupVersionKind().GroupKind(), r.Name, allErrs)
8586
}
@@ -110,6 +111,7 @@ func (*awsMachineWebhook) ValidateUpdate(ctx context.Context, oldObj, newObj run
110111
allErrs = append(allErrs, r.validateAdditionalSecurityGroups()...)
111112
allErrs = append(allErrs, r.Spec.AdditionalTags.Validate()...)
112113
allErrs = append(allErrs, r.validateHostAffinity()...)
114+
allErrs = append(allErrs, r.validateHostAllocation()...)
113115

114116
newAWSMachineSpec := newAWSMachine["spec"].(map[string]interface{})
115117
oldAWSMachineSpec := oldAWSMachine["spec"].(map[string]interface{})
@@ -477,6 +479,20 @@ func (r *AWSMachine) validateHostAffinity() field.ErrorList {
477479
return allErrs
478480
}
479481

482+
func (r *AWSMachine) validateHostAllocation() field.ErrorList {
483+
var allErrs field.ErrorList
484+
485+
// Check if both hostID and dynamicHostAllocation are specified
486+
hasHostID := r.Spec.HostID != nil && len(*r.Spec.HostID) > 0
487+
hasDynamicHostAllocation := r.Spec.DynamicHostAllocation != nil
488+
489+
if hasHostID && hasDynamicHostAllocation {
490+
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"))
491+
}
492+
493+
return allErrs
494+
}
495+
480496
func (r *AWSMachine) validateSSHKeyName() field.ErrorList {
481497
return validateSSHKeyName(r.Spec.SSHKeyName)
482498
}

api/v1beta2/awsmachine_webhook_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,47 @@ func TestAWSMachineCreate(t *testing.T) {
543543
},
544544
wantErr: true,
545545
},
546+
{
547+
name: "hostID and dynamicHostAllocation are mutually exclusive",
548+
machine: &AWSMachine{
549+
Spec: AWSMachineSpec{
550+
InstanceType: "test",
551+
HostID: aws.String("h-1234567890abcdef0"),
552+
DynamicHostAllocation: &DynamicHostAllocationSpec{
553+
Release: DedicatedHostReleaseStrategyOnMachineDeletion,
554+
Tags: map[string]string{
555+
"Environment": "test",
556+
},
557+
},
558+
},
559+
},
560+
wantErr: true,
561+
},
562+
{
563+
name: "hostID alone is valid",
564+
machine: &AWSMachine{
565+
Spec: AWSMachineSpec{
566+
InstanceType: "test",
567+
HostID: aws.String("h-1234567890abcdef0"),
568+
},
569+
},
570+
wantErr: false,
571+
},
572+
{
573+
name: "dynamicHostAllocation alone is valid",
574+
machine: &AWSMachine{
575+
Spec: AWSMachineSpec{
576+
InstanceType: "test",
577+
DynamicHostAllocation: &DynamicHostAllocationSpec{
578+
Release: DedicatedHostReleaseStrategyOnMachineDeletion,
579+
Tags: map[string]string{
580+
"Environment": "test",
581+
},
582+
},
583+
},
584+
},
585+
wantErr: false,
586+
},
546587
}
547588
for _, tt := range tests {
548589
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: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,27 @@ 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+
Release: DedicatedHostReleaseStrategyOnMachineDeletion,
94+
Tags: map[string]string{
95+
"Environment": "test",
96+
},
97+
},
98+
},
99+
},
100+
},
101+
},
102+
wantError: true,
103+
},
83104
}
84105
for _, tt := range tests {
85106
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
)

api/v1beta2/types.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,11 @@ type Instance struct {
286286
// +optional
287287
HostID *string `json:"hostID,omitempty"`
288288

289+
// DynamicHostAllocation enables automatic allocation of dedicated hosts.
290+
// This field is mutually exclusive with HostID.
291+
// +optional
292+
DynamicHostAllocation *DynamicHostAllocationSpec `json:"dynamicHostAllocation,omitempty"`
293+
289294
// CapacityReservationPreference specifies the preference for use of Capacity Reservations by the instance. Valid values include:
290295
// "Open": The instance may make use of open Capacity Reservations that match its AZ and InstanceType
291296
// "None": The instance may not make use of any Capacity Reservations. This is to conserve open reservations for desired workloads
@@ -311,6 +316,33 @@ const (
311316
CapacityReservationPreferenceOpen CapacityReservationPreference = "Open"
312317
)
313318

319+
// DedicatedHostInfo contains information about a dedicated host.
320+
type DedicatedHostInfo struct {
321+
// HostID is the ID of the dedicated host.
322+
HostID string `json:"hostID"`
323+
324+
// InstanceFamily is the instance family supported by the host.
325+
InstanceFamily string `json:"instanceFamily"`
326+
327+
// InstanceType is the instance type supported by the host.
328+
InstanceType string `json:"instanceType"`
329+
330+
// AvailabilityZone is the AZ where the host is located.
331+
AvailabilityZone string `json:"availabilityZone"`
332+
333+
// State is the current state of the dedicated host.
334+
State string `json:"state"`
335+
336+
// TotalCapacity is the total number of instances that can be launched on the host.
337+
TotalCapacity int32 `json:"totalCapacity"`
338+
339+
// AvailableCapacity is the number of instances that can still be launched on the host.
340+
AvailableCapacity int32 `json:"availableCapacity"`
341+
342+
// Tags associated with the dedicated host.
343+
Tags map[string]string `json:"tags,omitempty"`
344+
}
345+
314346
// MarketType describes the market type of an Instance
315347
// +kubebuilder:validation:Enum:=OnDemand;Spot;CapacityBlock
316348
type MarketType string

0 commit comments

Comments
 (0)