Skip to content

Commit e58d9fb

Browse files
rvanderp3jimzim
andcommitted
WIP: Allocate dedicated host
Co-authored-by: Jim Zimmerman <[email protected]>
1 parent 8ab8731 commit e58d9fb

22 files changed

+1301
-9
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: 3 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: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,11 @@ type AWSMachineSpec struct {
246246
// +kubebuilder:validation:Enum:=default;host
247247
HostAffinity *string `json:"hostAffinity,omitempty"`
248248

249+
// DynamicHostAllocation enables automatic allocation of dedicated hosts.
250+
// This field is mutually exclusive with HostID.
251+
// +optional
252+
DynamicHostAllocation *DynamicHostAllocationSpec `json:"dynamicHostAllocation,omitempty"`
253+
249254
// CapacityReservationPreference specifies the preference for use of Capacity Reservations by the instance. Valid values include:
250255
// "Open": The instance may make use of open Capacity Reservations that match its AZ and InstanceType
251256
// "None": The instance may not make use of any Capacity Reservations. This is to conserve open reservations for desired workloads
@@ -255,6 +260,40 @@ type AWSMachineSpec struct {
255260
CapacityReservationPreference CapacityReservationPreference `json:"capacityReservationPreference,omitempty"`
256261
}
257262

263+
// DynamicHostAllocationSpec defines the configuration for dynamic dedicated host allocation.
264+
type DynamicHostAllocationSpec struct {
265+
// InstanceFamily specifies the EC2 instance family (e.g., "m5", "c5", "r5").
266+
// +kubebuilder:validation:Required
267+
InstanceFamily string `json:"instanceFamily"`
268+
269+
// AvailabilityZone specifies the target availability zone for allocation.
270+
// If not specified, uses the same AZ as the instance.
271+
// +optional
272+
AvailabilityZone *string `json:"availabilityZone,omitempty"`
273+
274+
// InstanceType specifies the specific instance type for the dedicated host.
275+
// If not specified, derives from InstanceFamily.
276+
// +optional
277+
InstanceType *string `json:"instanceType,omitempty"`
278+
279+
// Quantity specifies the number of dedicated hosts to allocate.
280+
// +kubebuilder:validation:Minimum=1
281+
// +kubebuilder:validation:Maximum=10
282+
// +kubebuilder:default=1
283+
// +optional
284+
Quantity *int32 `json:"quantity,omitempty"`
285+
286+
// AutoRelease determines whether to automatically release the dedicated host
287+
// when the machine is deleted.
288+
// +kubebuilder:default=true
289+
// +optional
290+
AutoRelease *bool `json:"autoRelease,omitempty"`
291+
292+
// Tags to apply to the allocated dedicated host.
293+
// +optional
294+
Tags map[string]string `json:"tags,omitempty"`
295+
}
296+
258297
// CloudInit defines options related to the bootstrapping systems where
259298
// CloudInit is used.
260299
type CloudInit struct {
@@ -432,6 +471,11 @@ type AWSMachineStatus struct {
432471
// Conditions defines current service state of the AWSMachine.
433472
// +optional
434473
Conditions clusterv1.Conditions `json:"conditions,omitempty"`
474+
475+
// AllocatedHostID tracks the dynamically allocated dedicated host ID.
476+
// This field is populated when DynamicHostAllocation is used.
477+
// +optional
478+
AllocatedHostID *string `json:"allocatedHostID,omitempty"`
435479
}
436480

437481
// +kubebuilder:object:root=true

api/v1beta2/awsmachinetemplate_webhook.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package v1beta2
1919
import (
2020
"context"
2121
"fmt"
22+
"strings"
2223

2324
"github.com/google/go-cmp/cmp"
2425
apierrors "k8s.io/apimachinery/pkg/api/errors"
@@ -176,6 +177,38 @@ func (r *AWSMachineTemplate) validateSSHKeyName() field.ErrorList {
176177
return validateSSHKeyName(r.Spec.Template.Spec.SSHKeyName)
177178
}
178179

180+
func (r *AWSMachineTemplate) validateInstanceTypeMatchesDedicatedHost() field.ErrorList {
181+
dynamicHostAllocation := r.Spec.Template.Spec.DynamicHostAllocation
182+
if r.Spec.Template.Spec.DynamicHostAllocation == nil {
183+
return nil
184+
}
185+
186+
desiredInstanceType := dynamicHostAllocation.InstanceType
187+
if desiredInstanceType != nil {
188+
if *desiredInstanceType != r.Spec.Template.Spec.InstanceType {
189+
return field.ErrorList{field.Invalid(field.NewPath("spec", "template", "spec", "dynamicHostAllocation", "instanceType"), *desiredInstanceType, "instance type does not match dedicated host")}
190+
}
191+
}
192+
193+
desiredInstanceFamily := dynamicHostAllocation.InstanceFamily
194+
195+
if len(desiredInstanceFamily) == 0 {
196+
return field.ErrorList{field.Invalid(field.NewPath("spec", "template", "spec", "dynamicHostAllocation", "instanceFamily"), desiredInstanceFamily, "instance family is required")}
197+
}
198+
199+
instanceTypeParts := strings.Split(r.Spec.Template.Spec.InstanceType, ".")
200+
if len(instanceTypeParts) < 2 {
201+
return field.ErrorList{field.Invalid(field.NewPath("spec", "template", "spec", "instanceType"), r.Spec.Template.Spec.InstanceType, "instance type must be in the format of <family>.<instance>")}
202+
}
203+
204+
familyFromInstanceType := instanceTypeParts[0]
205+
if familyFromInstanceType != desiredInstanceFamily {
206+
return field.ErrorList{field.Invalid(field.NewPath("spec", "template", "spec", "dynamicHostAllocation", "instanceFamily"), desiredInstanceFamily, "instance family does not match instance type")}
207+
}
208+
209+
return nil
210+
}
211+
179212
// ValidateCreate implements webhook.Validator so a webhook will be registered for the type.
180213
func (r *AWSMachineTemplateWebhook) ValidateCreate(_ context.Context, raw runtime.Object) (admission.Warnings, error) {
181214
var allErrs field.ErrorList
@@ -204,6 +237,7 @@ func (r *AWSMachineTemplateWebhook) ValidateCreate(_ context.Context, raw runtim
204237
allErrs = append(allErrs, obj.validateNonRootVolumes()...)
205238
allErrs = append(allErrs, obj.validateSSHKeyName()...)
206239
allErrs = append(allErrs, obj.validateAdditionalSecurityGroups()...)
240+
allErrs = append(allErrs, obj.validateInstanceTypeMatchesDedicatedHost()...)
207241
allErrs = append(allErrs, obj.Spec.Template.Spec.AdditionalTags.Validate()...)
208242

209243
return nil, aggregateObjErrors(obj.GroupVersionKind().GroupKind(), obj.Name, allErrs)

api/v1beta2/awsmachinetemplate_webhook_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,40 @@ func TestAWSMachineTemplateValidateCreate(t *testing.T) {
8080
},
8181
wantError: false,
8282
},
83+
{
84+
name: "ensure instance type family mismatch is detected",
85+
inputTemplate: &AWSMachineTemplate{
86+
ObjectMeta: metav1.ObjectMeta{},
87+
Spec: AWSMachineTemplateSpec{
88+
Template: AWSMachineTemplateResource{
89+
Spec: AWSMachineSpec{
90+
InstanceType: "m6i.xlarge",
91+
DynamicHostAllocation: &DynamicHostAllocationSpec{
92+
InstanceFamily: "m6g",
93+
},
94+
},
95+
},
96+
},
97+
},
98+
wantError: true,
99+
},
100+
{
101+
name: "ensure instance type family matches is allowed",
102+
inputTemplate: &AWSMachineTemplate{
103+
ObjectMeta: metav1.ObjectMeta{},
104+
Spec: AWSMachineTemplateSpec{
105+
Template: AWSMachineTemplateResource{
106+
Spec: AWSMachineSpec{
107+
InstanceType: "m6i.xlarge",
108+
DynamicHostAllocation: &DynamicHostAllocationSpec{
109+
InstanceFamily: "m6i",
110+
},
111+
},
112+
},
113+
},
114+
},
115+
wantError: false,
116+
},
83117
}
84118
for _, tt := range tests {
85119
t.Run(tt.name, func(t *testing.T) {

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

api/v1beta2/zz_generated.deepcopy.go

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

0 commit comments

Comments
 (0)