Skip to content
Draft
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: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -367,3 +367,5 @@ require (
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
)

replace github.com/openshift/api => github.com/vr4manta/api v0.0.0-20260121131545-9cd445624861
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -449,8 +449,6 @@ 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-20260114133223-6ab113cb7368 h1:kSr3DOlq0NCrHd65HB2o/pBsks7AfRm+fkpf9RLUPoc=
github.com/openshift/api v0.0.0-20260114133223-6ab113cb7368/go.mod h1:d5uzF0YN2nQQFA0jIEWzzOZ+edmo6wzlGLvx5Fhz4uY=
github.com/openshift/client-go v0.0.0-20251202151200-fb4471581cf8 h1:97rgISdT4IOmXlmEUV5Wr6d8BzzjPclzAjCARLbSlT0=
github.com/openshift/client-go v0.0.0-20251202151200-fb4471581cf8/go.mod h1:WVJnsrbSO1J8x8KceOmv1d5CpoN34Uzsaz1O4MIOKJI=
github.com/openshift/cluster-api-actuator-pkg/testutils v0.0.0-20250910145856-21d03d30056d h1:+sqUThLi/lmgT5/scmmjnS6+RZFtbdxRAscNfCPyLPI=
Expand Down Expand Up @@ -616,6 +614,8 @@ github.com/uudashr/iface v1.3.1 h1:bA51vmVx1UIhiIsQFSNq6GZ6VPTk3WNMZgRiCe9R29U=
github.com/uudashr/iface v1.3.1/go.mod h1:4QvspiRd3JLPAEXBQ9AiZpLbJlrWWgRChOKDJEuQTdg=
github.com/vmware/govmomi v0.52.0 h1:JyxQ1IQdllrY7PJbv2am9mRsv3p9xWlIQ66bv+XnyLw=
github.com/vmware/govmomi v0.52.0/go.mod h1:Yuc9xjznU3BH0rr6g7MNS1QGvxnJlE1vOvTJ7Lx7dqI=
github.com/vr4manta/api v0.0.0-20260121131545-9cd445624861 h1:CI4EPLfHEssv+uuL1y7feU5zT9Swy0f3PnCE7SbMZYA=
github.com/vr4manta/api v0.0.0-20260121131545-9cd445624861/go.mod h1:d5uzF0YN2nQQFA0jIEWzzOZ+edmo6wzlGLvx5Fhz4uY=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xen0n/gosmopolitan v1.2.2 h1:/p2KTnMzwRexIW8GlKawsTWOxn7UHA+jCMF/V8HHtvU=
Expand Down
59 changes: 51 additions & 8 deletions pkg/webhooks/machine_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -949,20 +949,15 @@ func processAWSPlacementTenancy(placement machinev1beta1.Placement) field.ErrorL
switch *placement.Host.Affinity {
case machinev1beta1.HostAffinityAnyAvailable:
// DedicatedHost is optional. If it is set, make sure it follows conventions
if placement.Host.DedicatedHost != nil && !awsDedicatedHostNamePattern.MatchString(placement.Host.DedicatedHost.ID) {
errs = append(errs, field.Invalid(field.NewPath("spec.placement.host.dedicatedHost.id"), placement.Host.DedicatedHost.ID, "id must start with 'h-' followed by 8 or 17 lowercase hexadecimal characters (0-9 and a-f)"))
if placement.Host.DedicatedHost != nil {
errs = append(errs, validateDedicatedHost(placement.Host.DedicatedHost)...)
}
case machinev1beta1.HostAffinityDedicatedHost:
// We need to make sure DedicatedHost is set with an ID
if placement.Host.DedicatedHost == nil {
errs = append(errs, field.Required(field.NewPath("spec.placement.host.dedicatedHost"), "dedicatedHost is required when hostAffinity is DedicatedHost, and optional otherwise"))
} else {
// If not set, return required error. If it does not match pattern, return pattern failure message.
if placement.Host.DedicatedHost.ID == "" {
errs = append(errs, field.Required(field.NewPath("spec.placement.host.dedicatedHost.id"), "id is required and must start with 'h-' followed by 8 or 17 lowercase hexadecimal characters (0-9 and a-f)"))
} else if !awsDedicatedHostNamePattern.MatchString(placement.Host.DedicatedHost.ID) {
errs = append(errs, field.Invalid(field.NewPath("spec.placement.host.dedicatedHost.id"), placement.Host.DedicatedHost.ID, "id must start with 'h-' followed by 8 or 17 lowercase hexadecimal characters (0-9 and a-f)"))
}
errs = append(errs, validateDedicatedHost(placement.Host.DedicatedHost)...)
}
default:
errs = append(errs, field.Invalid(field.NewPath("spec.placement.host.affinity"), placement.Host.Affinity, "hostAffinity must be either AnyAvailable or DedicatedHost"))
Expand All @@ -983,6 +978,54 @@ func processAWSPlacementTenancy(placement machinev1beta1.Placement) field.ErrorL
return errs
}

// validateDedicatedHost validates that all fields in the DedicatedHost are configured correctly.
func validateDedicatedHost(host *machinev1beta1.DedicatedHost) field.ErrorList {
var errs field.ErrorList

// If host is nil, then nothing to validate
if host == nil {
return errs
}

strategy := machinev1beta1.AllocationStrategyUserProvided
if host.AllocationStrategy != nil {
strategy = *host.AllocationStrategy
}

switch strategy {
// Empty string is for backward compatability in case an existing config exists with the allocation strategy not set.
// Default is User Provided.
case machinev1beta1.AllocationStrategyUserProvided, "":
// User Provided requires the ID being set of the host to use
if host.ID == "" {
errs = append(errs, field.Required(field.NewPath("spec.placement.host.dedicatedHost.id"), "id is required when allocationStrategy is UserProvided and must start with 'h-' followed by 8 or 17 lowercase hexadecimal characters (0-9 and a-f)"))
} else if !awsDedicatedHostNamePattern.MatchString(host.ID) {
errs = append(errs, field.Invalid(field.NewPath("spec.placement.host.dedicatedHost.id"), host.ID, "id must start with 'h-' followed by 8 or 17 lowercase hexadecimal characters (0-9 and a-f)"))
}

// DynamicHostAllocation is not allowed if user provided
if host.DynamicHostAllocation != nil {
errs = append(errs, field.Invalid(field.NewPath("spec.placement.host.dedicatedHost.dynamicHostAllocation"), host.ID, "dynamicHostAllocation is only allowed when allocationStrategy is Dynamic"))
}
case machinev1beta1.AllocationStrategyDynamic:
// ID must not be set
if host.ID != "" {
errs = append(errs, field.Forbidden(field.NewPath("spec.placement.host.dedicatedHost.id"), "id is only allowed when allocationStrategy is Provided"))
}
default:
errs = append(
errs,
field.Invalid(
field.NewPath("spec.placement.host.dedicatedHost.allocationStrategy"),
host.AllocationStrategy,
fmt.Sprintf("Invalid allocationStrategy, the only allowed options are: %s, %s", machinev1beta1.AllocationStrategyUserProvided, machinev1beta1.AllocationStrategyDynamic),
),
)
}

return errs
}

// getDuplicatedTags iterates through the AWS TagSpecifications
// to determine if any tag Name is duplicated within the list.
// A list of duplicated names will be returned.
Expand Down
208 changes: 204 additions & 4 deletions pkg/webhooks/machine_webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ func TestMachineCreation(t *testing.T) {
},
},
},
expectedError: "admission webhook \"validation.machine.machine.openshift.io\" denied the request: spec.placement.host.dedicatedHost.id: Required value: id is required and must start with 'h-' followed by 8 or 17 lowercase hexadecimal characters (0-9 and a-f)",
expectedError: "admission webhook \"validation.machine.machine.openshift.io\" denied the request: spec.placement.host.dedicatedHost.id: Required value: id is required when allocationStrategy is UserProvided and must start with 'h-' followed by 8 or 17 lowercase hexadecimal characters (0-9 and a-f)",
},
{
name: "configure host placement with AnyAvailable affinity and empty ID",
Expand All @@ -399,7 +399,7 @@ func TestMachineCreation(t *testing.T) {
},
},
},
expectedError: "admission webhook \"validation.machine.machine.openshift.io\" denied the request: spec.placement.host.dedicatedHost.id: Required value: id is required and must start with 'h-' followed by 8 or 17 lowercase hexadecimal characters (0-9 and a-f)",
expectedError: "admission webhook \"validation.machine.machine.openshift.io\" denied the request: spec.placement.host.dedicatedHost.id: Required value: id is required when allocationStrategy is UserProvided and must start with 'h-' followed by 8 or 17 lowercase hexadecimal characters (0-9 and a-f)",
},
{
name: "configure host placement with AnyAvailable affinity and invalid ID",
Expand Down Expand Up @@ -532,7 +532,7 @@ func TestMachineCreation(t *testing.T) {
},
},
},
expectedError: "admission webhook \"validation.machine.machine.openshift.io\" denied the request: spec.placement.host.dedicatedHost.id: Required value: id is required and must start with 'h-' followed by 8 or 17 lowercase hexadecimal characters (0-9 and a-f)",
expectedError: "admission webhook \"validation.machine.machine.openshift.io\" denied the request: spec.placement.host.dedicatedHost.id: Required value: id is required when allocationStrategy is UserProvided and must start with 'h-' followed by 8 or 17 lowercase hexadecimal characters (0-9 and a-f)",
},
{
name: "configure host placement with DedicatedHost affinity and ID not set",
Expand All @@ -551,7 +551,7 @@ func TestMachineCreation(t *testing.T) {
},
},
},
expectedError: "admission webhook \"validation.machine.machine.openshift.io\" denied the request: spec.placement.host.dedicatedHost.id: Required value: id is required and must start with 'h-' followed by 8 or 17 lowercase hexadecimal characters (0-9 and a-f)",
expectedError: "admission webhook \"validation.machine.machine.openshift.io\" denied the request: spec.placement.host.dedicatedHost.id: Required value: id is required when allocationStrategy is UserProvided and must start with 'h-' followed by 8 or 17 lowercase hexadecimal characters (0-9 and a-f)",
},
{
name: "configure host placement with DedicatedHost affinity and invalid ID",
Expand Down Expand Up @@ -679,6 +679,206 @@ func TestMachineCreation(t *testing.T) {
},
expectedError: "admission webhook \"validation.machine.machine.openshift.io\" denied the request: spec.placement.host: Forbidden: host may only be specified when tenancy is 'host'",
},
{
name: "configure AllocationStrategy Provided 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",
Placement: machinev1beta1.Placement{
Tenancy: machinev1beta1.HostTenancy,
Host: &machinev1beta1.HostPlacement{
Affinity: ptr.To(machinev1beta1.HostAffinityDedicatedHost),
DedicatedHost: &machinev1beta1.DedicatedHost{
AllocationStrategy: ptr.To(machinev1beta1.AllocationStrategyUserProvided),
ID: "h-1234567890abcdef0",
},
},
},
},
},
expectedError: "",
},
{
name: "configure AllocationStrategy Provided without ID",
platformType: osconfigv1.AWSPlatformType,
clusterID: "aws-cluster",
providerSpecValue: &kruntime.RawExtension{
Object: &machinev1beta1.AWSMachineProviderConfig{
AMI: machinev1beta1.AWSResourceReference{ID: ptr.To[string]("ami")},
InstanceType: "test",
Placement: machinev1beta1.Placement{
Tenancy: machinev1beta1.HostTenancy,
Host: &machinev1beta1.HostPlacement{
Affinity: ptr.To(machinev1beta1.HostAffinityDedicatedHost),
DedicatedHost: &machinev1beta1.DedicatedHost{
AllocationStrategy: ptr.To(machinev1beta1.AllocationStrategyUserProvided),
},
},
},
},
},
expectedError: "admission webhook \"validation.machine.machine.openshift.io\" denied the request: spec.placement.host.dedicatedHost.id: Required value: id is required when allocationStrategy is UserProvided and must start with 'h-' followed by 8 or 17 lowercase hexadecimal characters (0-9 and a-f)",
},
{
name: "configure AllocationStrategy UserProvided 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",
Placement: machinev1beta1.Placement{
Tenancy: machinev1beta1.HostTenancy,
Host: &machinev1beta1.HostPlacement{
Affinity: ptr.To(machinev1beta1.HostAffinityDedicatedHost),
DedicatedHost: &machinev1beta1.DedicatedHost{
AllocationStrategy: ptr.To(machinev1beta1.AllocationStrategyUserProvided),
ID: "",
},
},
},
},
},
expectedError: "admission webhook \"validation.machine.machine.openshift.io\" denied the request: spec.placement.host.dedicatedHost.id: Required value: id is required when allocationStrategy is UserProvided and must start with 'h-' followed by 8 or 17 lowercase hexadecimal characters (0-9 and a-f)",
},
{
name: "configure AllocationStrategy UserProvided with DynamicHostAllocation",
platformType: osconfigv1.AWSPlatformType,
clusterID: "aws-cluster",
providerSpecValue: &kruntime.RawExtension{
Object: &machinev1beta1.AWSMachineProviderConfig{
AMI: machinev1beta1.AWSResourceReference{ID: ptr.To[string]("ami")},
InstanceType: "test",
Placement: machinev1beta1.Placement{
Tenancy: machinev1beta1.HostTenancy,
Host: &machinev1beta1.HostPlacement{
Affinity: ptr.To(machinev1beta1.HostAffinityDedicatedHost),
DedicatedHost: &machinev1beta1.DedicatedHost{
AllocationStrategy: ptr.To(machinev1beta1.AllocationStrategyUserProvided),
ID: "h-1234567890abcdef0",
DynamicHostAllocation: &machinev1beta1.DynamicHostAllocationSpec{
Tags: map[string]string{"key": "value"},
},
},
},
},
},
},
expectedError: "admission webhook \"validation.machine.machine.openshift.io\" denied the request: spec.placement.host.dedicatedHost.dynamicHostAllocation: Invalid value: \"h-1234567890abcdef0\": dynamicHostAllocation is only allowed when allocationStrategy is Dynamic",
},
{
name: "configure AllocationStrategy Dynamic without ID",
platformType: osconfigv1.AWSPlatformType,
clusterID: "aws-cluster",
providerSpecValue: &kruntime.RawExtension{
Object: &machinev1beta1.AWSMachineProviderConfig{
AMI: machinev1beta1.AWSResourceReference{ID: ptr.To[string]("ami")},
InstanceType: "test",
Placement: machinev1beta1.Placement{
Tenancy: machinev1beta1.HostTenancy,
Host: &machinev1beta1.HostPlacement{
Affinity: ptr.To(machinev1beta1.HostAffinityDedicatedHost),
DedicatedHost: &machinev1beta1.DedicatedHost{
AllocationStrategy: ptr.To(machinev1beta1.AllocationStrategyDynamic),
},
},
},
},
},
expectedError: "",
},
{
name: "configure AllocationStrategy Dynamic with DynamicHostAllocation",
platformType: osconfigv1.AWSPlatformType,
clusterID: "aws-cluster",
providerSpecValue: &kruntime.RawExtension{
Object: &machinev1beta1.AWSMachineProviderConfig{
AMI: machinev1beta1.AWSResourceReference{ID: ptr.To[string]("ami")},
InstanceType: "test",
Placement: machinev1beta1.Placement{
Tenancy: machinev1beta1.HostTenancy,
Host: &machinev1beta1.HostPlacement{
Affinity: ptr.To(machinev1beta1.HostAffinityDedicatedHost),
DedicatedHost: &machinev1beta1.DedicatedHost{
AllocationStrategy: ptr.To(machinev1beta1.AllocationStrategyDynamic),
DynamicHostAllocation: &machinev1beta1.DynamicHostAllocationSpec{
Tags: map[string]string{"env": "test"},
},
},
},
},
},
},
expectedError: "",
},
{
name: "configure AllocationStrategy Dynamic with ID",
platformType: osconfigv1.AWSPlatformType,
clusterID: "aws-cluster",
providerSpecValue: &kruntime.RawExtension{
Object: &machinev1beta1.AWSMachineProviderConfig{
AMI: machinev1beta1.AWSResourceReference{ID: ptr.To[string]("ami")},
InstanceType: "test",
Placement: machinev1beta1.Placement{
Tenancy: machinev1beta1.HostTenancy,
Host: &machinev1beta1.HostPlacement{
Affinity: ptr.To(machinev1beta1.HostAffinityDedicatedHost),
DedicatedHost: &machinev1beta1.DedicatedHost{
AllocationStrategy: ptr.To(machinev1beta1.AllocationStrategyDynamic),
ID: "h-1234567890abcdef0",
},
},
},
},
},
expectedError: "admission webhook \"validation.machine.machine.openshift.io\" denied the request: spec.placement.host.dedicatedHost.id: Forbidden: id is only allowed when allocationStrategy is Provided",
},
{
name: "configure nil AllocationStrategy with valid ID (backward compatibility)",
platformType: osconfigv1.AWSPlatformType,
clusterID: "aws-cluster",
providerSpecValue: &kruntime.RawExtension{
Object: &machinev1beta1.AWSMachineProviderConfig{
AMI: machinev1beta1.AWSResourceReference{ID: ptr.To[string]("ami")},
InstanceType: "test",
Placement: machinev1beta1.Placement{
Tenancy: machinev1beta1.HostTenancy,
Host: &machinev1beta1.HostPlacement{
Affinity: ptr.To(machinev1beta1.HostAffinityDedicatedHost),
DedicatedHost: &machinev1beta1.DedicatedHost{
AllocationStrategy: nil,
ID: "h-1234567890abcdef0",
},
},
},
},
},
expectedError: "",
},
{
name: "configure nil AllocationStrategy without ID (backward compatibility)",
platformType: osconfigv1.AWSPlatformType,
clusterID: "aws-cluster",
providerSpecValue: &kruntime.RawExtension{
Object: &machinev1beta1.AWSMachineProviderConfig{
AMI: machinev1beta1.AWSResourceReference{ID: ptr.To[string]("ami")},
InstanceType: "test",
Placement: machinev1beta1.Placement{
Tenancy: machinev1beta1.HostTenancy,
Host: &machinev1beta1.HostPlacement{
Affinity: ptr.To(machinev1beta1.HostAffinityDedicatedHost),
DedicatedHost: &machinev1beta1.DedicatedHost{
AllocationStrategy: nil,
},
},
},
},
},
expectedError: "admission webhook \"validation.machine.machine.openshift.io\" denied the request: spec.placement.host.dedicatedHost.id: Required value: id is required when allocationStrategy is UserProvided and must start with 'h-' followed by 8 or 17 lowercase hexadecimal characters (0-9 and a-f)",
},
{
name: "with VolumeType set to gp3 and Throughput set under minium value",
platformType: osconfigv1.AWSPlatformType,
Expand Down
Loading