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
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ require (
github.com/onsi/ginkgo/v2 v2.27.2
github.com/onsi/gomega v1.38.2
github.com/openshift-eng/openshift-tests-extension v0.0.0-20251105193959-75a0be5d9bd7
github.com/openshift/api v0.0.0-20251106190826-ebe535b08719
github.com/openshift/api v0.0.0-20251111193948-50e2ece149d7
github.com/openshift/client-go v0.0.0-20251015124057-db0dee36e235
github.com/openshift/cluster-api-actuator-pkg/testutils v0.0.0-20250910145856-21d03d30056d
github.com/openshift/cluster-control-plane-machine-set-operator v0.0.0-20251029084908-344babe6a957
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -449,8 +449,8 @@ github.com/opencontainers/selinux v1.11.1 h1:nHFvthhM0qY8/m+vfhJylliSshm8G1jJ2jD
github.com/opencontainers/selinux v1.11.1/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec=
github.com/openshift-eng/openshift-tests-extension v0.0.0-20251105193959-75a0be5d9bd7 h1:Z1swlS6b3Adm6RPhjqefs3DWnNFLDxRX+WC8GMXhja4=
github.com/openshift-eng/openshift-tests-extension v0.0.0-20251105193959-75a0be5d9bd7/go.mod h1:6gkP5f2HL0meusT0Aim8icAspcD1cG055xxBZ9yC68M=
github.com/openshift/api v0.0.0-20251106190826-ebe535b08719 h1:KEwYyKaJniwhoyLB75tAMmJn9pMlk0PUlRfrsXYOhwM=
github.com/openshift/api v0.0.0-20251106190826-ebe535b08719/go.mod h1:d5uzF0YN2nQQFA0jIEWzzOZ+edmo6wzlGLvx5Fhz4uY=
github.com/openshift/api v0.0.0-20251111193948-50e2ece149d7 h1:MemawsK6SpxEaE5y0NqO5sIX3yTLIIyP89w6DGKukAk=
github.com/openshift/api v0.0.0-20251111193948-50e2ece149d7/go.mod h1:d5uzF0YN2nQQFA0jIEWzzOZ+edmo6wzlGLvx5Fhz4uY=
github.com/openshift/client-go v0.0.0-20251015124057-db0dee36e235 h1:9JBeIXmnHlpXTQPi7LPmu1jdxznBhAE7bb1K+3D8gxY=
github.com/openshift/client-go v0.0.0-20251015124057-db0dee36e235/go.mod h1:L49W6pfrZkfOE5iC1PqEkuLkXG4W0BX4w8b+L2Bv7fM=
github.com/openshift/cluster-api-actuator-pkg/testutils v0.0.0-20250910145856-21d03d30056d h1:+sqUThLi/lmgT5/scmmjnS6+RZFtbdxRAscNfCPyLPI=
Expand Down
1 change: 1 addition & 0 deletions pkg/operator/operator.go
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,7 @@ func (optr *Operator) maoConfigFromInfrastructure() (*OperatorConfig, error) {
// flags, we selectively populate the map (and therefore passed
// as args)
features := map[string]bool{
string(apifeatures.FeatureGateAWSDedicatedHosts): featureGates.Enabled(apifeatures.FeatureGateAWSDedicatedHosts),
string(apifeatures.FeatureGateMachineAPIMigration): featureGates.Enabled(apifeatures.FeatureGateMachineAPIMigration),
string(apifeatures.FeatureGateAzureWorkloadIdentity): featureGates.Enabled(apifeatures.FeatureGateAzureWorkloadIdentity),
string(apifeatures.FeatureGateVSphereMultiDisk): featureGates.Enabled(apifeatures.FeatureGateVSphereMultiDisk),
Expand Down
2 changes: 2 additions & 0 deletions pkg/operator/operator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,15 @@ var (
{Name: apifeatures.FeatureGateAzureWorkloadIdentity},
{Name: apifeatures.FeatureGateVSphereMultiDisk},
{Name: apifeatures.FeatureGateVSphereHostVMGroupZonal},
{Name: apifeatures.FeatureGateAWSDedicatedHosts},
}

enabledFeatureMap = map[string]bool{
"MachineAPIMigration": true,
"AzureWorkloadIdentity": true,
"VSphereMultiDisk": true,
"VSphereHostVMGroupZonal": true,
"AWSDedicatedHosts": true,
}
)

Expand Down
37 changes: 37 additions & 0 deletions pkg/webhooks/machine_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ type systemSpecifications struct {
type machineArch string

var (
// AWS Variables / Defaults

// awsDedicatedHostNamePattern is used to validate the id of a dedicated host
awsDedicatedHostNamePattern = regexp.MustCompile(`^h-[0-9a-f]{17}$`)

// Azure Defaults
defaultAzureVnet = func(clusterID string) string {
return fmt.Sprintf("%s-vnet", clusterID)
Expand Down Expand Up @@ -897,6 +902,38 @@ func validateAWS(m *machinev1beta1.Machine, config *admissionConfig) (bool, []st
}
}

// Dedicated host support.
// Check if host placement is configured. If so, then we need to determine placement affinity and validate configs.
if providerSpec.HostPlacement != nil {
klog.V(4).Infof("Validating AWS Host Placement")
placement := *providerSpec.HostPlacement
if placement.Affinity == nil {
errs = append(errs, field.Required(field.NewPath("spec.hostPlacement.affinity"), "affinity is required and must be set to either AnyAvailable or DedicatedHost"))
} else {
switch *placement.Affinity {
case machinev1beta1.HostAffinityAnyAvailable:
// Cannot have DedicatedHost set
if placement.DedicatedHost != nil {
errs = append(errs, field.Forbidden(field.NewPath("spec.hostPlacement.dedicatedHost"), "dedicatedHost is required when affinity is DedicatedHost, and forbidden otherwise"))
}
case machinev1beta1.HostAffinityDedicatedHost:
// We need to make sure DedicatedHost is set with a HostID
if placement.DedicatedHost == nil {
errs = append(errs, field.Required(field.NewPath("spec.hostPlacement.dedicatedHost"), "dedicatedHost is required when affinity is DedicatedHost, and forbidden otherwise"))
} else {
// If not set, return required error. If it does not match pattern, return pattern failure message.
if placement.DedicatedHost.ID == "" {
errs = append(errs, field.Required(field.NewPath("spec.hostPlacement.dedicatedHost.id"), "id is required and must start with 'h-' followed by 17 lowercase hexadecimal characters (0-9 and a-f)"))
} else if awsDedicatedHostNamePattern.FindStringSubmatch(placement.DedicatedHost.ID) == nil {
errs = append(errs, field.Invalid(field.NewPath("spec.hostPlacement.dedicatedHost.id"), placement.DedicatedHost.ID, "id must start with 'h-' followed by 17 lowercase hexadecimal characters (0-9 and a-f)"))
}
}
default:
errs = append(errs, field.Invalid(field.NewPath("spec.hostPlacement.affinity"), placement.Affinity, "affinity must be either AnyAvailable or DedicatedHost"))
}
}
}

if len(errs) > 0 {
return false, warnings, errs
}
Expand Down
135 changes: 135 additions & 0 deletions pkg/webhooks/machine_webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,141 @@ func TestMachineCreation(t *testing.T) {
},
expectedError: "",
},
{
name: "configure host placement with AnyAvailable affinity",
platformType: osconfigv1.AWSPlatformType,
clusterID: "aws-cluster",
providerSpecValue: &kruntime.RawExtension{
Object: &machinev1beta1.AWSMachineProviderConfig{
AMI: machinev1beta1.AWSResourceReference{
ID: ptr.To[string]("ami"),
},
InstanceType: "test",
HostPlacement: &machinev1beta1.HostPlacement{
Affinity: ptr.To(machinev1beta1.HostAffinityAnyAvailable),
},
},
},
expectedError: "",
},
{
name: "configure host placement with invalid affinity",
platformType: osconfigv1.AWSPlatformType,
clusterID: "aws-cluster",
providerSpecValue: &kruntime.RawExtension{
Object: &machinev1beta1.AWSMachineProviderConfig{
AMI: machinev1beta1.AWSResourceReference{
ID: ptr.To[string]("ami"),
},
InstanceType: "test",
HostPlacement: &machinev1beta1.HostPlacement{
Affinity: ptr.To(machinev1beta1.HostAffinity("invalid")),
},
},
},
expectedError: "admission webhook \"validation.machine.machine.openshift.io\" denied the request: spec.hostPlacement.affinity: Invalid value: \"invalid\": affinity must be either AnyAvailable or DedicatedHost",
},
{
name: "configure host placement dedicatedHost without dedicatedHost",
platformType: osconfigv1.AWSPlatformType,
clusterID: "aws-cluster",
providerSpecValue: &kruntime.RawExtension{
Object: &machinev1beta1.AWSMachineProviderConfig{
AMI: machinev1beta1.AWSResourceReference{
ID: ptr.To[string]("ami"),
},
InstanceType: "test",
HostPlacement: &machinev1beta1.HostPlacement{
Affinity: ptr.To(machinev1beta1.HostAffinityDedicatedHost),
},
},
},
expectedError: "admission webhook \"validation.machine.machine.openshift.io\" denied the request: spec.hostPlacement.dedicatedHost: Required value: dedicatedHost is required when affinity is DedicatedHost",
},
{
name: "configure host placement dedicatedHost with valid ID",
platformType: osconfigv1.AWSPlatformType,
clusterID: "aws-cluster",
providerSpecValue: &kruntime.RawExtension{
Object: &machinev1beta1.AWSMachineProviderConfig{
AMI: machinev1beta1.AWSResourceReference{
ID: ptr.To[string]("ami"),
},
InstanceType: "test",
HostPlacement: &machinev1beta1.HostPlacement{
Affinity: ptr.To(machinev1beta1.HostAffinityDedicatedHost),
DedicatedHost: &machinev1beta1.DedicatedHost{ID: "h-1234567890abcdef0"},
},
},
},
expectedError: "",
},
{
name: "configure host placement AnyAvailable forbids dedicatedHost",
platformType: osconfigv1.AWSPlatformType,
clusterID: "aws-cluster",
providerSpecValue: &kruntime.RawExtension{
Object: &machinev1beta1.AWSMachineProviderConfig{
AMI: machinev1beta1.AWSResourceReference{ID: ptr.To[string]("ami")},
InstanceType: "test",
HostPlacement: &machinev1beta1.HostPlacement{
Affinity: ptr.To(machinev1beta1.HostAffinityAnyAvailable),
DedicatedHost: &machinev1beta1.DedicatedHost{ID: "h-09dcf61cb388b0149"},
},
},
},
expectedError: "admission webhook \"validation.machine.machine.openshift.io\" denied the request: spec.hostPlacement.dedicatedHost: Forbidden: dedicatedHost is required when affinity is DedicatedHost, and forbidden otherwise",
},
{
name: "configure host placement dedicatedHost with empty ID",
platformType: osconfigv1.AWSPlatformType,
clusterID: "aws-cluster",
providerSpecValue: &kruntime.RawExtension{
Object: &machinev1beta1.AWSMachineProviderConfig{
AMI: machinev1beta1.AWSResourceReference{ID: ptr.To[string]("ami")},
InstanceType: "test",
HostPlacement: &machinev1beta1.HostPlacement{
Affinity: ptr.To(machinev1beta1.HostAffinityDedicatedHost),
DedicatedHost: &machinev1beta1.DedicatedHost{ID: ""},
},
},
},
expectedError: "admission webhook \"validation.machine.machine.openshift.io\" denied the request: spec.hostPlacement.dedicatedHost.id: Required value: id is required and must start with 'h-' followed by 17 lowercase hexadecimal characters (0-9 and a-f)",
},
{
name: "configure host placement dedicatedHost with ID not set",
platformType: osconfigv1.AWSPlatformType,
clusterID: "aws-cluster",
providerSpecValue: &kruntime.RawExtension{
Object: &machinev1beta1.AWSMachineProviderConfig{
AMI: machinev1beta1.AWSResourceReference{ID: ptr.To[string]("ami")},
InstanceType: "test",
HostPlacement: &machinev1beta1.HostPlacement{
Affinity: ptr.To(machinev1beta1.HostAffinityDedicatedHost),
DedicatedHost: &machinev1beta1.DedicatedHost{},
},
},
},
expectedError: "admission webhook \"validation.machine.machine.openshift.io\" denied the request: spec.hostPlacement.dedicatedHost.id: Required value: id is required and must start with 'h-' followed by 17 lowercase hexadecimal characters (0-9 and a-f)",
},
{
name: "configure host placement dedicatedHost with invalid ID",
platformType: osconfigv1.AWSPlatformType,
clusterID: "aws-cluster",
providerSpecValue: &kruntime.RawExtension{
Object: &machinev1beta1.AWSMachineProviderConfig{
AMI: machinev1beta1.AWSResourceReference{ID: ptr.To[string]("ami")},
InstanceType: "test",
HostPlacement: &machinev1beta1.HostPlacement{
Affinity: ptr.To(machinev1beta1.HostAffinityDedicatedHost),
DedicatedHost: &machinev1beta1.DedicatedHost{
ID: "invalid",
},
},
},
},
expectedError: "admission webhook \"validation.machine.machine.openshift.io\" denied the request: spec.hostPlacement.dedicatedHost.id: Invalid value: \"invalid\": id must start with 'h-' followed by 17 lowercase hexadecimal characters (0-9 and a-f)",
},
{
name: "with Azure and a nil provider spec value",
platformType: osconfigv1.AzurePlatformType,
Expand Down
4 changes: 2 additions & 2 deletions vendor/github.com/openshift/api/features/features.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading