Skip to content
Open
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
5 changes: 5 additions & 0 deletions admission-policies/vsphere/kustomization.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
- unsupported-vsphere-spec-fields.yaml
213 changes: 213 additions & 0 deletions admission-policies/vsphere/unsupported-vsphere-spec-fields.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
# ValidatingAdmissionPolicy for blocking unsupported vSphere fields
#
# API Version: v1beta1
# This policy targets cluster-api-provider-vsphere v1beta1 API.
# When upgrading to v1beta2, add these additional fields to block:
# - nestedHV
# - ftEncryptionMode
# - migrateEncryption
# - cryptoKeyID
# - cryptoProfile
#
# Fields Currently Allowed (conversion supports them):
# - template, cloneMode, snapshot
# - numCPUs, numCoresPerSocket, memoryMiB, diskGiB
# - server, datacenter, folder, datastore, resourcePool
# - tagIDs, dataDisks
# - network.devices[*].networkName
# - network.devices[*].gateway4 (supports IPv6 addresses too)
# - network.devices[*].ipAddrs (supports IPv4 and IPv6)
# - network.devices[*].nameservers (supports IPv4 and IPv6)
# - network.devices[*].addressesFromPools (IPAM)
#
# Fields Warned for Future Enhancement (when MAPI support is added):
# - storagePolicyName (medium priority)
# - hardwareVersion (low priority)
# - pciDevices (low priority)
# - network.devices[*].macAddr (low priority - CAPV supports it but not in MAPI yet)
#
# See docs/vsphere-field-conversion-support.md for complete field documentation.
#
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: "openshift-cluster-api-unsupported-vsphere-spec-fields"
spec:
policyName: "openshift-cluster-api-unsupported-vsphere-spec-fields"
validationActions: [Deny]
matchResources:
namespaceSelector:
matchExpressions:
- key: kubernetes.io/metadata.name
operator: In
values:
- openshift-cluster-api
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: "openshift-cluster-api-unsupported-vsphere-spec-fields-warning"
spec:
policyName: "openshift-cluster-api-unsupported-vsphere-spec-fields-warning"
validationActions: [Warn]
matchResources:
namespaceSelector:
matchExpressions:
- key: kubernetes.io/metadata.name
operator: In
values:
- openshift-cluster-api
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: "openshift-cluster-api-unsupported-vsphere-spec-fields"
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups: ["infrastructure.cluster.x-k8s.io"]
apiVersions: ["v1beta1"]
operations: ["CREATE", "UPDATE"]
resources: ["vspheremachines", "vspheremachinetemplates"]
variables:
- name: machineSpec
expression: "object.kind == 'VSphereMachine' ? object.spec : object.spec.template.spec"
- name: specPath
expression: "object.kind == 'VSphereMachine' ? 'spec' : 'spec.template.spec'"
validations:
# Cluster-level configuration
- expression: "!has(variables.machineSpec.thumbprint) || variables.machineSpec.thumbprint == ''"
messageExpression: "variables.specPath + '.thumbprint is not supported - TLS validation is handled at cluster level'"

# CAPI-only runtime configuration
- expression: "!has(variables.machineSpec.customVMXKeys) || variables.machineSpec.customVMXKeys.size() == 0"
Comment thread
AnnaZivkovic marked this conversation as resolved.
messageExpression: "variables.specPath + '.customVMXKeys is not supported in OpenShift'"

# Resource management (not implemented in CAPV)
- expression: "!has(variables.machineSpec.resources)"
messageExpression: "variables.specPath + '.resources is not supported'"

# Lifecycle management (not in MAPI)
- expression: "!has(variables.machineSpec.guestSoftPowerOffTimeout)"
messageExpression: "variables.specPath + '.guestSoftPowerOffTimeout is not supported'"

- expression: "!has(variables.machineSpec.namingStrategy)"
messageExpression: "variables.specPath + '.namingStrategy is not supported'"

# Power off mode - only allow hard or unset
- expression: "!has(variables.machineSpec.powerOffMode) || variables.machineSpec.powerOffMode == '' || variables.machineSpec.powerOffMode == 'hard'"
messageExpression: "variables.specPath + '.powerOffMode must be \"hard\" or unset - soft power-off is not supported'"

# Operating system type (not used by CAPV)
- expression: "!has(variables.machineSpec.os) || variables.machineSpec.os == ''"
messageExpression: "variables.specPath + '.os is not supported'"

# Network-level fields
- expression: "!has(variables.machineSpec.network.routes) || variables.machineSpec.network.routes.size() == 0"
messageExpression: "variables.specPath + '.network.routes is not supported'"

- expression: "!has(variables.machineSpec.network.preferredAPIServerCIDR) || variables.machineSpec.network.preferredAPIServerCIDR == ''"
messageExpression: "variables.specPath + '.network.preferredAPIServerCIDR is not supported'"

# Network device fields - iterate over all devices and check unsupported fields
- expression: >-
!has(variables.machineSpec.network.devices) ||
variables.machineSpec.network.devices.all(device,
!has(device.deviceName) || device.deviceName == ''
)
messageExpression: "variables.specPath + '.network.devices[*].deviceName is not supported'"

- expression: >-
!has(variables.machineSpec.network.devices) ||
variables.machineSpec.network.devices.all(device,
!has(device.dhcp6)
)
messageExpression: "variables.specPath + '.network.devices[*].dhcp6 is not supported'"

- expression: >-
!has(variables.machineSpec.network.devices) ||
variables.machineSpec.network.devices.all(device,
!has(device.gateway6) || device.gateway6 == ''
)
messageExpression: "variables.specPath + '.network.devices[*].gateway6 is not supported'"

- expression: >-
!has(variables.machineSpec.network.devices) ||
variables.machineSpec.network.devices.all(device,
!has(device.mtu)
)
messageExpression: "variables.specPath + '.network.devices[*].mtu is not supported'"

- expression: >-
!has(variables.machineSpec.network.devices) ||
variables.machineSpec.network.devices.all(device,
!has(device.routes) || device.routes.size() == 0
)
messageExpression: "variables.specPath + '.network.devices[*].routes is not supported'"

- expression: >-
!has(variables.machineSpec.network.devices) ||
variables.machineSpec.network.devices.all(device,
!has(device.searchDomains) || device.searchDomains.size() == 0
)
messageExpression: "variables.specPath + '.network.devices[*].searchDomains is not supported'"

- expression: >-
!has(variables.machineSpec.network.devices) ||
variables.machineSpec.network.devices.all(device,
!has(device.dhcp4Overrides)
)
messageExpression: "variables.specPath + '.network.devices[*].dhcp4Overrides is not supported'"

- expression: >-
!has(variables.machineSpec.network.devices) ||
variables.machineSpec.network.devices.all(device,
!has(device.dhcp6Overrides)
)
messageExpression: "variables.specPath + '.network.devices[*].dhcp6Overrides is not supported'"

- expression: >-
!has(variables.machineSpec.network.devices) ||
variables.machineSpec.network.devices.all(device,
!has(device.skipIPAllocation)
)
messageExpression: "variables.specPath + '.network.devices[*].skipIPAllocation is not supported'"
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: "openshift-cluster-api-unsupported-vsphere-spec-fields-warning"
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups: ["infrastructure.cluster.x-k8s.io"]
apiVersions: ["v1beta1"]
operations: ["CREATE", "UPDATE"]
resources: ["vspheremachines", "vspheremachinetemplates"]
variables:
- name: machineSpec
expression: "object.kind == 'VSphereMachine' ? object.spec : object.spec.template.spec"
- name: specPath
expression: "object.kind == 'VSphereMachine' ? 'spec' : 'spec.template.spec'"
validations:
# Storage policy (not yet supported - future enhancement)
- expression: "!has(variables.machineSpec.storagePolicyName) || variables.machineSpec.storagePolicyName == ''"
messageExpression: "variables.specPath + '.storagePolicyName is not yet supported but planned for future release'"

# PCI devices (not yet supported - future enhancement)
- expression: "!has(variables.machineSpec.pciDevices) || variables.machineSpec.pciDevices.size() == 0"
messageExpression: "variables.specPath + '.pciDevices is not yet supported but planned for future release'"

# Hardware version (not yet supported - future enhancement)
- expression: "!has(variables.machineSpec.hardwareVersion) || variables.machineSpec.hardwareVersion == ''"
messageExpression: "variables.specPath + '.hardwareVersion is not yet supported but planned for future release'"

# MAC address (not yet supported - future enhancement)
- expression: >-
!has(variables.machineSpec.network.devices) ||
variables.machineSpec.network.devices.all(device,
!has(device.macAddr) || device.macAddr == ''
)
messageExpression: "variables.specPath + '.network.devices[*].macAddr is not yet supported but planned for future release'"
36 changes: 27 additions & 9 deletions cmd/machine-api-migration/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"k8s.io/utils/clock"
awsv1 "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2"
openstackv1 "sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1"
vspherev1 "sigs.k8s.io/cluster-api-provider-vsphere/apis/v1beta1"
clusterv1 "sigs.k8s.io/cluster-api/api/core/v1beta2"

"github.com/openshift/api/features"
Expand Down Expand Up @@ -66,6 +67,7 @@ func initScheme(scheme *runtime.Scheme) {
utilruntime.Must(configv1.Install(scheme))
utilruntime.Must(awsv1.AddToScheme(scheme))
utilruntime.Must(openstackv1.AddToScheme(scheme))
utilruntime.Must(vspherev1.AddToScheme(scheme))
utilruntime.Must(clusterv1.AddToScheme(scheme))
}

Expand All @@ -91,7 +93,8 @@ func main() {
os.Exit(1)
}

if err := checkFeatureGates(ctx, log, mgr); err != nil {
currentFeatureGates, err := setupFeatureGates(ctx, log, mgr)
if err != nil {
log.Error(err, "unable to check feature gates")
os.Exit(1)
}
Expand All @@ -113,7 +116,7 @@ func main() {
os.Exit(1)
}

checkPlatformSupported(ctx, log, platform)
checkPlatformSupported(ctx, log, platform, currentFeatureGates)

for name, controller := range getControllers(operatorConfig, platform, infra, infraTypes) {
if err := controller.SetupWithManager(mgr); err != nil {
Expand All @@ -130,30 +133,45 @@ func main() {
}
}

func checkFeatureGates(ctx context.Context, log logr.Logger, mgr ctrl.Manager) error {
featureGateAccessor, err := getFeatureGates(ctx, mgr)
func setupFeatureGates(ctx context.Context, log logr.Logger, mgr ctrl.Manager) (featuregates.FeatureGate, error) {
featureGateAccessor, err := getFeatureGatesAccessor(ctx, mgr)
if err != nil {
return fmt.Errorf("unable to get feature gates: %w", err)
return nil, fmt.Errorf("unable to get feature gates: %w", err)
}

currentFeatureGates, err := featureGateAccessor.CurrentFeatureGates()
if err != nil {
return fmt.Errorf("unable to get current feature gates: %w", err)
return nil, fmt.Errorf("unable to get current feature gates: %w", err)
}

if !currentFeatureGates.Enabled(features.FeatureGateMachineAPIMigration) {
log.Info("MachineAPIMigration feature gate is not enabled, nothing to do. Waiting for termination signal.")
exitAfterTerminationSignal(ctx)
}

return nil
checkMachineAPIMigrationEnabled(ctx, log, currentFeatureGates)

return currentFeatureGates, nil
}

func checkMachineAPIMigrationEnabled(ctx context.Context, log logr.Logger, currentFeatureGates featuregates.FeatureGate) {
if !currentFeatureGates.Enabled(features.FeatureGateMachineAPIMigration) {
log.Info("MachineAPIMigration feature gate is not enabled, nothing to do. Waiting for termination signal.")
exitAfterTerminationSignal(ctx)
}
}

func checkPlatformSupported(ctx context.Context, log logr.Logger, platform configv1.PlatformType) {
func checkPlatformSupported(ctx context.Context, log logr.Logger, platform configv1.PlatformType, currentFeatureGates featuregates.FeatureGate) {
switch platform {
case configv1.AWSPlatformType, configv1.OpenStackPlatformType:
log.Info("starting controllers", "platform", platform)
case configv1.VSpherePlatformType:
if !currentFeatureGates.Enabled(features.FeatureGateMachineAPIMigrationVSphere) {
log.Info("MachineAPIMigrationVSphere feature gate is not enabled for vSphere platform. Waiting for termination signal.")
exitAfterTerminationSignal(ctx)
}

log.Info("MachineAPIMigration: starting %s controllers", platform)
default:
log.Info("MachineAPIMigration not implemented for platform, nothing to do. Waiting for termination signal.", "platform", platform)
exitAfterTerminationSignal(ctx)
Expand Down Expand Up @@ -213,7 +231,7 @@ func exitAfterTerminationSignal(ctx context.Context) {

// getFeatureGates is used to fetch the current feature gates from the cluster.
// We use this to check if the machine api migration is actually enabled or not.
func getFeatureGates(ctx context.Context, mgr ctrl.Manager) (featuregates.FeatureGateAccess, error) {
func getFeatureGatesAccessor(ctx context.Context, mgr ctrl.Manager) (featuregates.FeatureGateAccess, error) {
desiredVersion := util.GetReleaseVersion()
missingVersion := "0.0.1-snapshot"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ func (r *MachineMigrationReconciler) isOldAuthoritativeResourcePaused(ctx contex
return false, fmt.Errorf("failed to get Cluster API infra machine: %w", err)
}

infraMachinePausedConditionStatus, err := util.GetConditionStatus(infraMachine, clusterv1.PausedCondition)
infraMachinePausedConditionStatus, err := util.GetConditionStatusFromInfraObject(infraMachine, clusterv1.PausedCondition)
if err != nil {
return false, fmt.Errorf("unable to get paused condition for %s/%s: %w", infraMachine.GetNamespace(), infraMachine.GetName(), err)
}
Expand Down
26 changes: 26 additions & 0 deletions pkg/controllers/machinesetsync/machineset_sync_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import (
awsv1 "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2"
ibmpowervsv1 "sigs.k8s.io/cluster-api-provider-ibmcloud/api/v1beta2"
openstackv1 "sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1"
vspherev1 "sigs.k8s.io/cluster-api-provider-vsphere/apis/v1beta1"
clusterv1 "sigs.k8s.io/cluster-api/api/core/v1beta2"
"sigs.k8s.io/cluster-api/util/annotations"
ctrl "sigs.k8s.io/controller-runtime"
Expand Down Expand Up @@ -391,6 +392,12 @@ func filterOutdatedInfraMachineTemplates(infraMachineTemplateList client.ObjectL
outdatedTemplates = append(outdatedTemplates, &template)
}
}
case *vspherev1.VSphereMachineTemplateList:
for _, template := range list.Items {
if template.GetName() != newInfraMachineTemplateName {
outdatedTemplates = append(outdatedTemplates, &template)
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
default:
return nil, fmt.Errorf("%w: got unknown type %T", errUnexpectedInfraMachineTemplateListType, list)
}
Expand Down Expand Up @@ -705,6 +712,20 @@ func (r *MachineSetSyncReconciler) convertCAPIToMAPIMachineSet(capiMachineSet *c
return capi2mapi.FromMachineSetAndPowerVSMachineTemplateAndPowerVSCluster( //nolint: wrapcheck
capiMachineSet, machineTemplate, cluster,
).ToMachineSet()
case configv1.VSpherePlatformType:
machineTemplate, ok := infraMachineTemplate.(*vspherev1.VSphereMachineTemplate)
if !ok {
return nil, nil, fmt.Errorf("%w, expected VSphereMachineTemplate, got %T", errUnexpectedInfraMachineTemplateType, infraMachineTemplate)
}

cluster, ok := infraCluster.(*vspherev1.VSphereCluster)
if !ok {
return nil, nil, fmt.Errorf("%w, expected VSphereCluster, got %T", errUnexpectedInfraClusterType, infraCluster)
}

return capi2mapi.FromMachineSetAndVSphereMachineTemplateAndVSphereCluster( //nolint: wrapcheck
capiMachineSet, machineTemplate, cluster,
).ToMachineSet()
default:
return nil, nil, fmt.Errorf("%w: %s", errPlatformNotSupported, r.Platform)
}
Expand All @@ -719,6 +740,8 @@ func (r *MachineSetSyncReconciler) convertMAPIToCAPIMachineSet(mapiMachineSet *m
return mapi2capi.FromOpenStackMachineSetAndInfra(mapiMachineSet, r.Infra).ToMachineSetAndMachineTemplate() //nolint:wrapcheck
case configv1.PowerVSPlatformType:
return mapi2capi.FromPowerVSMachineSetAndInfra(mapiMachineSet, r.Infra).ToMachineSetAndMachineTemplate() //nolint:wrapcheck
case configv1.VSpherePlatformType:
return mapi2capi.FromVSphereMachineSetAndInfra(mapiMachineSet, r.Infra).ToMachineSetAndMachineTemplate() //nolint:wrapcheck
default:
return nil, nil, nil, fmt.Errorf("%w: %s", errPlatformNotSupported, r.Platform)
}
Expand Down Expand Up @@ -1321,6 +1344,8 @@ func initInfraMachineTemplateListAndInfraClusterListFromProvider(platform config
return &openstackv1.OpenStackMachineTemplateList{}, &openstackv1.OpenStackClusterList{}, nil
case configv1.PowerVSPlatformType:
return &ibmpowervsv1.IBMPowerVSMachineTemplateList{}, &ibmpowervsv1.IBMPowerVSClusterList{}, nil
case configv1.VSpherePlatformType:
return &vspherev1.VSphereMachineTemplateList{}, &vspherev1.VSphereClusterList{}, nil
default:
return nil, nil, fmt.Errorf("%w: %s", errPlatformNotSupported, platform)
}
Expand All @@ -1334,6 +1359,7 @@ func compareCAPIInfraMachineTemplates(platform configv1.PlatformType, infraMachi
case configv1.AWSPlatformType:
case configv1.OpenStackPlatformType:
case configv1.PowerVSPlatformType:
case configv1.VSpherePlatformType:
default:
return nil, fmt.Errorf("%w: %s", errPlatformNotSupported, platform)
}
Expand Down
Loading