Skip to content

Commit 8a071c7

Browse files
committed
feat(controller): reconcile group CIDRs into ImpNetwork status
1 parent a4fc2a0 commit 8a071c7

3 files changed

Lines changed: 110 additions & 0 deletions

File tree

internal/controller/events.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const (
1818
EventReasonIPAllocated = "IPAllocated"
1919
EventReasonNATRulesApplied = "NATRulesApplied"
2020
EventReasonCiliumConfigMissing = "CiliumConfigMissing"
21+
EventReasonGroupCIDRError = "GroupCIDRError"
2122
)
2223

2324
// ImpVM condition type constants.

internal/controller/impnetwork_controller.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package controller
33
import (
44
"context"
55
"fmt"
6+
"reflect"
67
"strings"
78
"time"
89

@@ -183,6 +184,11 @@ func (r *ImpNetworkReconciler) sync(ctx context.Context, net *impdevv1alpha1.Imp
183184
return ctrl.Result{}, err
184185
}
185186

187+
// Allocate and record group subnets.
188+
if err := r.reconcileGroupCIDRs(ctx, net); err != nil {
189+
return ctrl.Result{}, err
190+
}
191+
186192
// Enroll Running VMs as Cilium external workloads (no-op if Cilium absent).
187193
if err := r.reconcileCiliumEnrollment(ctx, net); err != nil {
188194
return ctrl.Result{}, err
@@ -284,6 +290,27 @@ func (r *ImpNetworkReconciler) ciliumPresent() bool {
284290
return err == nil
285291
}
286292

293+
// reconcileGroupCIDRs derives subnet CIDRs for each network group in spec.groups
294+
// and stores the result in status.groupCIDRs. Idempotent: skips the status patch
295+
// when the computed CIDRs match what is already recorded.
296+
func (r *ImpNetworkReconciler) reconcileGroupCIDRs(ctx context.Context, net *impdevv1alpha1.ImpNetwork) error {
297+
desired, err := carveGroupCIDRs(net.Spec.Subnet, net.Spec.Groups)
298+
if err != nil {
299+
logf.FromContext(ctx).Error(err, "group CIDR carving failed", "network", net.Name)
300+
r.Recorder.Eventf(net, corev1.EventTypeWarning, EventReasonGroupCIDRError,
301+
"Group CIDR carving failed: %v", err)
302+
return nil // don't block reconcile for this — operator continues without group CIDRs
303+
}
304+
305+
if reflect.DeepEqual(net.Status.GroupCIDRs, desired) {
306+
return nil
307+
}
308+
309+
base := net.DeepCopy()
310+
net.Status.GroupCIDRs = desired
311+
return r.Status().Patch(ctx, net, client.MergeFrom(base))
312+
}
313+
287314
// reconcileCiliumEnrollment creates CiliumExternalWorkload objects for Running VMs
288315
// attached to this network, and GCs CEWs for VMs that are no longer Running.
289316
// It is a no-op when Cilium is not the active CNI or its CRDs are not present.

internal/controller/impnetwork_controller_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,3 +412,85 @@ var _ = Describe("ImpNetwork Controller: CiliumConfigMissing", func() {
412412
}, 200*time.Millisecond, 50*time.Millisecond).ShouldNot(ContainSubstring(EventReasonCiliumConfigMissing))
413413
})
414414
})
415+
416+
var _ = Describe("ImpNetwork Controller: group CIDR allocation", func() {
417+
ctx := context.Background()
418+
419+
It("populates status.groupCIDRs from spec.groups on reconcile", func() {
420+
net := &impdevv1alpha1.ImpNetwork{
421+
ObjectMeta: metav1.ObjectMeta{Name: "group-net-1", Namespace: "default"},
422+
Spec: impdevv1alpha1.ImpNetworkSpec{
423+
Subnet: "10.55.0.0/24",
424+
Groups: []impdevv1alpha1.NetworkGroupSpec{
425+
{Name: "workers", ExpectedSize: 14},
426+
{Name: "controllers", ExpectedSize: 4},
427+
},
428+
},
429+
}
430+
Expect(k8sClient.Create(ctx, net)).To(Succeed())
431+
DeferCleanup(func() { k8sClient.Delete(ctx, net) }) //nolint:errcheck
432+
433+
r, _ := newNetworkReconciler(unknownStore())
434+
// First reconcile: adds finalizer.
435+
_, err := r.Reconcile(ctx, reconcile.Request{
436+
NamespacedName: types.NamespacedName{Name: "group-net-1", Namespace: "default"},
437+
})
438+
Expect(err).NotTo(HaveOccurred())
439+
// Second reconcile: runs sync, populates groupCIDRs.
440+
_, err = r.Reconcile(ctx, reconcile.Request{
441+
NamespacedName: types.NamespacedName{Name: "group-net-1", Namespace: "default"},
442+
})
443+
Expect(err).NotTo(HaveOccurred())
444+
445+
updated := &impdevv1alpha1.ImpNetwork{}
446+
Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "group-net-1", Namespace: "default"}, updated)).To(Succeed())
447+
Expect(updated.Status.GroupCIDRs).To(HaveLen(2))
448+
449+
byName := make(map[string]string)
450+
for _, gc := range updated.Status.GroupCIDRs {
451+
byName[gc.Name] = gc.CIDR
452+
}
453+
// "controllers" sorted first: /29 at 10.55.0.0
454+
Expect(byName["controllers"]).To(Equal("10.55.0.0/29"))
455+
// "workers": /28 aligned after /29 block → 10.55.0.16
456+
Expect(byName["workers"]).To(Equal("10.55.0.16/28"))
457+
})
458+
459+
It("clears status.groupCIDRs when spec.groups is removed", func() {
460+
net := &impdevv1alpha1.ImpNetwork{
461+
ObjectMeta: metav1.ObjectMeta{Name: "group-net-2", Namespace: "default"},
462+
Spec: impdevv1alpha1.ImpNetworkSpec{
463+
Subnet: "10.56.0.0/24",
464+
Groups: []impdevv1alpha1.NetworkGroupSpec{
465+
{Name: "a", ExpectedSize: 4},
466+
},
467+
},
468+
}
469+
Expect(k8sClient.Create(ctx, net)).To(Succeed())
470+
DeferCleanup(func() { k8sClient.Delete(ctx, net) }) //nolint:errcheck
471+
472+
r, _ := newNetworkReconciler(unknownStore())
473+
for i := 0; i < 2; i++ {
474+
_, err := r.Reconcile(ctx, reconcile.Request{
475+
NamespacedName: types.NamespacedName{Name: "group-net-2", Namespace: "default"},
476+
})
477+
Expect(err).NotTo(HaveOccurred())
478+
}
479+
480+
// Remove groups from spec.
481+
updated := &impdevv1alpha1.ImpNetwork{}
482+
Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "group-net-2", Namespace: "default"}, updated)).To(Succeed())
483+
updated.Spec.Groups = nil
484+
Expect(k8sClient.Update(ctx, updated)).To(Succeed())
485+
486+
// Reconcile again.
487+
_, err := r.Reconcile(ctx, reconcile.Request{
488+
NamespacedName: types.NamespacedName{Name: "group-net-2", Namespace: "default"},
489+
})
490+
Expect(err).NotTo(HaveOccurred())
491+
492+
final := &impdevv1alpha1.ImpNetwork{}
493+
Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "group-net-2", Namespace: "default"}, final)).To(Succeed())
494+
Expect(final.Status.GroupCIDRs).To(BeEmpty())
495+
})
496+
})

0 commit comments

Comments
 (0)