From e92e254aff1109607c4fc8e50304625aea63c396 Mon Sep 17 00:00:00 2001 From: Daniel Gonzalez Nothnagel Date: Fri, 10 Jul 2026 14:21:45 +0200 Subject: [PATCH 1/2] Propagate topology labels from machine to ephemeral volumes Copy zone and region labels from `MachinePoolSelector` to `VolumePoolSelector` when creating ephemeral volumes, without overwriting labels already set on the volume. This ensures ephemeral volumes are scheduled in the same zone/region as the machine they belong to, except if the user has specifically defined different topology labels. Signed-off-by: Daniel Gonzalez Nothnagel --- .../machine_ephemeralvolume_controller.go | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/internal/controllers/compute/machine_ephemeralvolume_controller.go b/internal/controllers/compute/machine_ephemeralvolume_controller.go index 7ae2f78ab..8c84b534e 100644 --- a/internal/controllers/compute/machine_ephemeralvolume_controller.go +++ b/internal/controllers/compute/machine_ephemeralvolume_controller.go @@ -33,6 +33,11 @@ const ( MachineEphemeralVolumeManager = "machine-ephemeral-volume" ) +var poolSelectorTopologyLabels = []string{ + string(commonv1alpha1.TopologyLabelZone), + string(commonv1alpha1.TopologyLabelRegion), +} + type MachineEphemeralVolumeReconciler struct { k8sevents.EventRecorder client.Client @@ -60,6 +65,22 @@ func (r *MachineEphemeralVolumeReconciler) reconcileExists(ctx context.Context, return r.reconcile(ctx, log, machine) } +func (r *MachineEphemeralVolumeReconciler) copyTopologyLabels(machine *computev1alpha1.Machine, volume *storagev1alpha1.Volume) { + for _, key := range poolSelectorTopologyLabels { + if _, ok := volume.Spec.VolumePoolSelector[key]; ok { + // Do not overwrite existing topology labels + continue + } + if val, ok := machine.Spec.MachinePoolSelector[key]; ok { + if volume.Spec.VolumePoolSelector == nil { + volume.Spec.VolumePoolSelector = make(map[string]string) + } + + volume.Spec.VolumePoolSelector[key] = val + } + } +} + func (r *MachineEphemeralVolumeReconciler) ephemeralMachineVolumeByName(machine *computev1alpha1.Machine) map[string]*storagev1alpha1.Volume { res := make(map[string]*storagev1alpha1.Volume) for _, machineVolume := range machine.Spec.Volumes { @@ -78,8 +99,10 @@ func (r *MachineEphemeralVolumeReconciler) ephemeralMachineVolumeByName(machine }, Spec: ephemeral.VolumeTemplate.Spec, } + volume.Spec.VolumePoolSelector = maps.Clone(ephemeral.VolumeTemplate.Spec.VolumePoolSelector) annotations.SetDefaultEphemeralManagedBy(volume) _ = ctrl.SetControllerReference(machine, volume, r.Scheme()) + r.copyTopologyLabels(machine, volume) res[volumeName] = volume } return res From 88fc20691e08b97ecd859f2bffc9b02e73954671 Mon Sep 17 00:00:00 2001 From: Daniel Gonzalez Nothnagel Date: Fri, 10 Jul 2026 14:24:12 +0200 Subject: [PATCH 2/2] Add tests for topology label propagation to ephemeral volumes Signed-off-by: Daniel Gonzalez Nothnagel --- ...machine_ephemeralvolume_controller_test.go | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/internal/controllers/compute/machine_ephemeralvolume_controller_test.go b/internal/controllers/compute/machine_ephemeralvolume_controller_test.go index 26b5270bc..ba2592af2 100644 --- a/internal/controllers/compute/machine_ephemeralvolume_controller_test.go +++ b/internal/controllers/compute/machine_ephemeralvolume_controller_test.go @@ -146,6 +146,103 @@ var _ = Describe("MachineEphemeralVolumeController", func() { )) }) + It("should propagate topology labels from machine pool selector to ephemeral volume pool selector", func(ctx SpecContext) { + By("creating a machine with zone topology label in pool selector") + machine := &computev1alpha1.Machine{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns.Name, + GenerateName: "machine-topology-", + }, + Spec: computev1alpha1.MachineSpec{ + MachineClassRef: corev1.LocalObjectReference{Name: machineClass.Name}, + MachinePoolSelector: map[string]string{ + "topology.ironcore.dev/zone": "zone-a", + "topology.ironcore.dev/region": "region-1", + }, + Volumes: []computev1alpha1.Volume{ + { + Name: "ephem-volume", + VolumeSource: computev1alpha1.VolumeSource{ + Ephemeral: &computev1alpha1.EphemeralVolumeSource{ + VolumeTemplate: &storagev1alpha1.VolumeTemplateSpec{ + Spec: storagev1alpha1.VolumeSpec{ + VolumeClassRef: &corev1.LocalObjectReference{Name: volumeClass.Name}, + Resources: corev1alpha1.ResourceList{ + corev1alpha1.ResourceStorage: resource.MustParse("500Mi"), + }, + }, + }, + }, + }, + }, + }, + }, + } + Expect(k8sClient.Create(ctx, machine)).To(Succeed()) + + By("waiting for the ephemeral volume to exist with propagated topology labels") + ephemVolume := &storagev1alpha1.Volume{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns.Name, + Name: computev1alpha1.MachineEphemeralVolumeName(machine.Name, "ephem-volume"), + }, + } + Eventually(Object(ephemVolume)).Should(SatisfyAll( + HaveField("Spec.VolumePoolSelector", HaveKeyWithValue("topology.ironcore.dev/zone", "zone-a")), + HaveField("Spec.VolumePoolSelector", HaveKeyWithValue("topology.ironcore.dev/region", "region-1")), + )) + }) + + It("should not overwrite user-specified topology labels in ephemeral volume pool selector", func(ctx SpecContext) { + By("creating a machine with topology labels in both machine pool selector and volume template") + machine := &computev1alpha1.Machine{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns.Name, + GenerateName: "machine-override-topology-", + }, + Spec: computev1alpha1.MachineSpec{ + MachineClassRef: corev1.LocalObjectReference{Name: machineClass.Name}, + MachinePoolSelector: map[string]string{ + "topology.ironcore.dev/zone": "zone-a", + "topology.ironcore.dev/region": "region-1", + }, + Volumes: []computev1alpha1.Volume{ + { + Name: "ephem-volume", + VolumeSource: computev1alpha1.VolumeSource{ + Ephemeral: &computev1alpha1.EphemeralVolumeSource{ + VolumeTemplate: &storagev1alpha1.VolumeTemplateSpec{ + Spec: storagev1alpha1.VolumeSpec{ + VolumeClassRef: &corev1.LocalObjectReference{Name: volumeClass.Name}, + Resources: corev1alpha1.ResourceList{ + corev1alpha1.ResourceStorage: resource.MustParse("500Mi"), + }, + VolumePoolSelector: map[string]string{ + "topology.ironcore.dev/zone": "zone-b", + }, + }, + }, + }, + }, + }, + }, + }, + } + Expect(k8sClient.Create(ctx, machine)).To(Succeed()) + + By("waiting for the ephemeral volume with user-specified zone taking precedence and region propagated") + ephemVolume := &storagev1alpha1.Volume{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns.Name, + Name: computev1alpha1.MachineEphemeralVolumeName(machine.Name, "ephem-volume"), + }, + } + Eventually(Object(ephemVolume)).Should(SatisfyAll( + HaveField("Spec.VolumePoolSelector", HaveKeyWithValue("topology.ironcore.dev/zone", "zone-b")), + HaveField("Spec.VolumePoolSelector", HaveKeyWithValue("topology.ironcore.dev/region", "region-1")), + )) + }) + It("should not delete externally managed volumes for a machine", func(ctx SpecContext) { By("creating a machine") machine := &computev1alpha1.Machine{