Skip to content

Commit fc58bcd

Browse files
Merge pull request #587 from lmiccini/bgp_420
OCP 4.20 frrconfiguration migration between namespaces
2 parents 9b54405 + 763393a commit fc58bcd

5 files changed

Lines changed: 251 additions & 18 deletions

File tree

config/rbac/role.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ rules:
2424
- ""
2525
resources:
2626
- endpoints
27+
- namespaces
2728
verbs:
2829
- get
2930
- list

internal/bgp/funcs.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,14 @@
22
package bgp
33

44
import (
5+
"context"
56
"net"
67

8+
corev1 "k8s.io/api/core/v1"
9+
k8s_errors "k8s.io/apimachinery/pkg/api/errors"
10+
"k8s.io/apimachinery/pkg/types"
11+
"sigs.k8s.io/controller-runtime/pkg/client"
12+
713
k8s_networkv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1"
814
frrk8sv1 "github.com/metallb/frr-k8s/api/v1beta1"
915
"github.com/openstack-k8s-operators/lib-common/modules/common/util"
@@ -65,3 +71,21 @@ func GetNodesRunningPods(podNetworkDetailList []PodDetail) []string {
6571

6672
return nodes
6773
}
74+
75+
// OpenShiftFRRNamespace is the FRR namespace used in OpenShift 4.20+
76+
const OpenShiftFRRNamespace = "openshift-frr-k8s"
77+
78+
// GetFRRConfigurationNamespace checks whether migrationNamespace exists and returns it,
79+
// otherwise falls back to defaultNamespace.
80+
func GetFRRConfigurationNamespace(ctx context.Context, c client.Client, migrationNamespace, defaultNamespace string) (string, error) {
81+
ns := &corev1.Namespace{}
82+
err := c.Get(ctx, types.NamespacedName{Name: migrationNamespace}, ns)
83+
if err != nil {
84+
if k8s_errors.IsNotFound(err) {
85+
return defaultNamespace, nil
86+
}
87+
return "", err
88+
}
89+
90+
return migrationNamespace, nil
91+
}

internal/controller/network/bgpconfiguration_controller.go

Lines changed: 66 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -56,19 +56,28 @@ import (
5656
// BGPConfigurationReconciler reconciles a BGPConfiguration object
5757
type BGPConfigurationReconciler struct {
5858
client.Client
59-
Kclient kubernetes.Interface
60-
Scheme *runtime.Scheme
59+
Kclient kubernetes.Interface
60+
Scheme *runtime.Scheme
61+
FRRMigrationNamespace string
6162
}
6263

6364
// GetLogger returns a logger object with a prefix of "controller.name" and additional controller context fields
6465
func (r *BGPConfigurationReconciler) GetLogger(ctx context.Context) logr.Logger {
6566
return log.FromContext(ctx).WithName("Controllers").WithName("BGPConfiguration")
6667
}
6768

69+
func (r *BGPConfigurationReconciler) getFRRMigrationNamespace() string {
70+
if r.FRRMigrationNamespace != "" {
71+
return r.FRRMigrationNamespace
72+
}
73+
return bgp.OpenShiftFRRNamespace
74+
}
75+
6876
//+kubebuilder:rbac:groups=network.openstack.org,resources=bgpconfigurations,verbs=get;list;watch;create;update;patch;delete
6977
//+kubebuilder:rbac:groups=network.openstack.org,resources=bgpconfigurations/status,verbs=get;update;patch
7078
//+kubebuilder:rbac:groups=network.openstack.org,resources=bgpconfigurations/finalizers,verbs=update
7179
//+kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch;update;patch
80+
//+kubebuilder:rbac:groups=core,resources=namespaces,verbs=get;list;watch
7281
//+kubebuilder:rbac:groups=frrk8s.metallb.io,resources=frrconfigurations,verbs=get;list;watch;create;update;patch;delete;deletecollection
7382
//+kubebuilder:rbac:groups=k8s.cni.cncf.io,resources=network-attachment-definitions,verbs=get;list;watch
7483

@@ -353,12 +362,17 @@ func (r *BGPConfigurationReconciler) reconcileDelete(ctx context.Context, instan
353362
Log := r.GetLogger(ctx)
354363
Log.Info("Reconciling Service delete")
355364

356-
// Delete all FRRConfiguration in the Spec.FRRConfigurationNamespace namespace,
365+
frrNamespace, err := bgp.GetFRRConfigurationNamespace(ctx, r.Client, r.getFRRMigrationNamespace(), instance.Spec.FRRConfigurationNamespace)
366+
if err != nil {
367+
return ctrl.Result{}, fmt.Errorf("unable to determine FRR namespace: %w", err)
368+
}
369+
370+
// Delete all FRRConfiguration in the determined namespace,
357371
// which have the correct owner and ownernamespace label
358-
err := r.DeleteAllOf(
372+
err = r.DeleteAllOf(
359373
ctx,
360374
&frrk8sv1.FRRConfiguration{},
361-
client.InNamespace(instance.Spec.FRRConfigurationNamespace),
375+
client.InNamespace(frrNamespace),
362376
client.MatchingLabels{
363377
labels.GetOwnerNameLabelSelector(labels.GetGroupLabel("bgpconfiguration")): instance.Name,
364378
labels.GetOwnerNameSpaceLabelSelector(labels.GetGroupLabel("bgpconfiguration")): instance.Namespace,
@@ -368,6 +382,12 @@ func (r *BGPConfigurationReconciler) reconcileDelete(ctx context.Context, instan
368382
return ctrl.Result{}, fmt.Errorf("error DeleteAllOf FRRConfiguration: %w", err)
369383
}
370384

385+
if frrNamespace != instance.Spec.FRRConfigurationNamespace {
386+
if err := r.cleanupOldNamespaceFRRConfigurations(ctx, instance, instance.Spec.FRRConfigurationNamespace); err != nil {
387+
Log.Error(err, "Failed to cleanup FRRConfigurations from old namespace", "namespace", instance.Spec.FRRConfigurationNamespace)
388+
}
389+
}
390+
371391
// Service is deleted so remove the finalizer.
372392
controllerutil.RemoveFinalizer(instance, helper.GetFinalizer())
373393
Log.Info("Reconciled Service delete successfully")
@@ -379,6 +399,12 @@ func (r *BGPConfigurationReconciler) reconcileNormal(ctx context.Context, instan
379399
Log := r.GetLogger(ctx)
380400
Log.Info("Reconciling Service")
381401

402+
frrNamespace, err := bgp.GetFRRConfigurationNamespace(ctx, r.Client, r.getFRRMigrationNamespace(), instance.Spec.FRRConfigurationNamespace)
403+
if err != nil {
404+
return ctrl.Result{}, fmt.Errorf("unable to determine FRR namespace: %w", err)
405+
}
406+
Log.Info("Using FRR namespace", "namespace", frrNamespace)
407+
382408
// Get a list of pods which are in the same namespace as the ctlplane
383409
// to verify if a FRRConfiguration needs to be created for.
384410
podList := &corev1.PodList{}
@@ -398,7 +424,7 @@ func (r *BGPConfigurationReconciler) reconcileNormal(ctx context.Context, instan
398424
// get all frrconfigs
399425
frrConfigList := &frrk8sv1.FRRConfigurationList{}
400426
listOpts = []client.ListOption{
401-
client.InNamespace(instance.Spec.FRRConfigurationNamespace), // defaults to metallb-system
427+
client.InNamespace(frrNamespace),
402428
}
403429
if err := r.List(ctx, frrConfigList, listOpts...); err != nil {
404430
return ctrl.Result{}, fmt.Errorf("unable to retrieve FRRConfigurationList %w", err)
@@ -462,6 +488,7 @@ func (r *BGPConfigurationReconciler) reconcileNormal(ctx context.Context, instan
462488
map[string]string{
463489
groupLabel + "/pod-name": podNetworkDetail.Name,
464490
}),
491+
frrNamespace,
465492
)
466493
if err != nil {
467494
return ctrl.Result{}, err
@@ -474,6 +501,13 @@ func (r *BGPConfigurationReconciler) reconcileNormal(ctx context.Context, instan
474501
return ctrl.Result{}, err
475502
}
476503

504+
// Clean up old FRRConfigurations from the old namespace if we've migrated to a new one
505+
if frrNamespace != instance.Spec.FRRConfigurationNamespace {
506+
if err := r.cleanupOldNamespaceFRRConfigurations(ctx, instance, instance.Spec.FRRConfigurationNamespace); err != nil {
507+
Log.Error(err, "Failed to cleanup old FRRConfigurations from namespace", "namespace", instance.Spec.FRRConfigurationNamespace)
508+
}
509+
}
510+
477511
Log.Info("Reconciled Service successfully")
478512
return ctrl.Result{}, nil
479513
}
@@ -500,6 +534,30 @@ func (r *BGPConfigurationReconciler) deleteStaleFRRConfigurations(ctx context.Co
500534
return nil
501535
}
502536

537+
func (r *BGPConfigurationReconciler) cleanupOldNamespaceFRRConfigurations(
538+
ctx context.Context,
539+
instance *networkv1.BGPConfiguration,
540+
oldNamespace string,
541+
) error {
542+
Log := r.GetLogger(ctx)
543+
Log.Info("Cleaning up old FRRConfigurations", "namespace", oldNamespace)
544+
545+
err := r.DeleteAllOf(
546+
ctx,
547+
&frrk8sv1.FRRConfiguration{},
548+
client.InNamespace(oldNamespace),
549+
client.MatchingLabels{
550+
labels.GetOwnerNameLabelSelector(labels.GetGroupLabel("bgpconfiguration")): instance.Name,
551+
labels.GetOwnerNameSpaceLabelSelector(labels.GetGroupLabel("bgpconfiguration")): instance.Namespace,
552+
},
553+
)
554+
if err != nil && !k8s_errors.IsNotFound(err) {
555+
return fmt.Errorf("error DeleteAllOf FRRConfiguration in old namespace %s: %w", oldNamespace, err)
556+
}
557+
558+
return nil
559+
}
560+
503561
// getPodNetworkDetails - returns the podDetails for a list of pods in status.phase: Running
504562
// where the pod has the multus k8s_networkv1.NetworkAttachmentAnnot annotation
505563
// and its value is not '[]' OR has a predictableip label
@@ -650,6 +708,7 @@ func (r *BGPConfigurationReconciler) createOrPatchFRRConfiguration(
650708
podDtl *bgp.PodDetail,
651709
nodeFRRCfgs map[string]frrk8sv1.FRRConfiguration,
652710
frrLabels map[string]string,
711+
frrNamespace string,
653712
) error {
654713
Log := r.GetLogger(ctx)
655714
Log.Info("Reconciling createOrUpdateFRRConfiguration")
@@ -662,7 +721,7 @@ func (r *BGPConfigurationReconciler) createOrPatchFRRConfiguration(
662721
frrConfig := &frrk8sv1.FRRConfiguration{
663722
ObjectMeta: metav1.ObjectMeta{
664723
Name: instance.Namespace + "-" + podDtl.Name,
665-
Namespace: instance.Spec.FRRConfigurationNamespace,
724+
Namespace: frrNamespace,
666725
},
667726
}
668727

test/functional/bgpconfiguration_controller_test.go

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
frrk8sv1 "github.com/metallb/frr-k8s/api/v1beta1"
2727
networkv1 "github.com/openstack-k8s-operators/infra-operator/apis/network/v1beta1"
2828
corev1 "k8s.io/api/core/v1"
29+
k8s_errors "k8s.io/apimachinery/pkg/api/errors"
2930
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3031
)
3132

@@ -573,4 +574,150 @@ var _ = Describe("BGPConfiguration controller", func() {
573574
})
574575
})
575576
})
577+
578+
When("FRR migration namespace exists (OpenShift 4.20+ migration)", func() {
579+
var podFrrName types.NamespacedName
580+
var podName types.NamespacedName
581+
var metallbNS *corev1.Namespace
582+
var migrationNS *corev1.Namespace
583+
584+
BeforeEach(func() {
585+
metallbNS = th.CreateNamespace(frrCfgNamespace + "-" + namespace)
586+
587+
// Use a unique namespace name to avoid envtest namespace leak
588+
migrationNSName := "frr-migration-" + namespace
589+
migrationNS = th.CreateNamespace(migrationNSName)
590+
bgpReconciler.FRRMigrationNamespace = migrationNSName
591+
DeferCleanup(func() {
592+
bgpReconciler.FRRMigrationNamespace = ""
593+
})
594+
595+
migrationFRRCfgName := types.NamespacedName{Namespace: migrationNS.Name, Name: "worker-0"}
596+
migrationFRRCfg := CreateFRRConfiguration(migrationFRRCfgName, GetMetalLBFRRConfigurationSpec("worker-0"))
597+
Expect(migrationFRRCfg).To(Not(BeNil()))
598+
599+
nad := th.CreateNAD(types.NamespacedName{Namespace: namespace, Name: "internalapi"}, GetNADSpec())
600+
601+
bgpcfg := CreateBGPConfiguration(namespace, GetBGPConfigurationSpec(metallbNS.Name))
602+
bgpcfgName.Name = bgpcfg.GetName()
603+
bgpcfgName.Namespace = bgpcfg.GetNamespace()
604+
605+
podName = types.NamespacedName{Namespace: namespace, Name: uuid.New().String()}
606+
th.CreatePod(podName, GetPodAnnotation(namespace), GetPodSpec("worker-0"))
607+
th.SimulatePodPhaseRunning(podName)
608+
609+
podFrrName.Name = podName.Namespace + "-" + podName.Name
610+
podFrrName.Namespace = migrationNS.Name
611+
612+
DeferCleanup(th.DeleteInstance, bgpcfg)
613+
DeferCleanup(th.DeleteInstance, nad)
614+
DeferCleanup(th.DeleteInstance, migrationFRRCfg)
615+
})
616+
617+
It("should create FRRConfiguration in the migration namespace instead of metallb-system", func() {
618+
pod := th.GetPod(podName)
619+
Expect(pod).To(Not(BeNil()))
620+
621+
Eventually(func(g Gomega) {
622+
frr := GetFRRConfiguration(types.NamespacedName{Namespace: migrationNS.Name, Name: podFrrName.Name})
623+
g.Expect(frr).To(Not(BeNil()))
624+
g.Expect(frr.Namespace).To(Equal(migrationNS.Name))
625+
g.Expect(frr.Spec.BGP.Routers[0].Prefixes[0]).To(Equal("172.17.0.40/32"))
626+
}, timeout, interval).Should(Succeed())
627+
628+
frr := &frrk8sv1.FRRConfiguration{}
629+
Consistently(func(g Gomega) {
630+
err := k8sClient.Get(ctx, types.NamespacedName{Namespace: metallbNS.Name, Name: podFrrName.Name}, frr)
631+
g.Expect(err).To(HaveOccurred())
632+
g.Expect(k8s_errors.IsNotFound(err)).To(BeTrue())
633+
}, timeout, interval).Should(Succeed())
634+
})
635+
})
636+
637+
When("existing FRRConfigurations in old namespace get migrated to new namespace", func() {
638+
var podFrrName types.NamespacedName
639+
var podName types.NamespacedName
640+
var metallbNS *corev1.Namespace
641+
642+
BeforeEach(func() {
643+
metallbNS = th.CreateNamespace(frrCfgNamespace + "-" + namespace)
644+
645+
meallbFRRCfgName := types.NamespacedName{Namespace: metallbNS.Name, Name: "worker-0"}
646+
meallbFRRCfg := CreateFRRConfiguration(meallbFRRCfgName, GetMetalLBFRRConfigurationSpec("worker-0"))
647+
Expect(meallbFRRCfg).To(Not(BeNil()))
648+
649+
nad := th.CreateNAD(types.NamespacedName{Namespace: namespace, Name: "internalapi"}, GetNADSpec())
650+
651+
bgpcfg := CreateBGPConfiguration(namespace, GetBGPConfigurationSpec(metallbNS.Name))
652+
bgpcfgName.Name = bgpcfg.GetName()
653+
bgpcfgName.Namespace = bgpcfg.GetNamespace()
654+
655+
podName = types.NamespacedName{Namespace: namespace, Name: uuid.New().String()}
656+
th.CreatePod(podName, GetPodAnnotation(namespace), GetPodSpec("worker-0"))
657+
th.SimulatePodPhaseRunning(podName)
658+
659+
podFrrName.Name = podName.Namespace + "-" + podName.Name
660+
podFrrName.Namespace = metallbNS.Name
661+
662+
DeferCleanup(th.DeleteInstance, bgpcfg)
663+
DeferCleanup(th.DeleteInstance, nad)
664+
DeferCleanup(th.DeleteInstance, meallbFRRCfg)
665+
})
666+
667+
It("should migrate FRRConfigurations from old namespace and clean up", func() {
668+
// First verify the FRRConfiguration exists in the old namespace
669+
Eventually(func(g Gomega) {
670+
frr := GetFRRConfiguration(types.NamespacedName{Namespace: metallbNS.Name, Name: podFrrName.Name})
671+
g.Expect(frr).To(Not(BeNil()))
672+
g.Expect(frr.Spec.BGP.Routers[0].Prefixes[0]).To(Equal("172.17.0.40/32"))
673+
}, timeout, interval).Should(Succeed())
674+
675+
// Now simulate OCP 4.20 upgrade: create the migration namespace and
676+
// add the reference FRRConfiguration there
677+
migrationNSName := "frr-migration-" + namespace
678+
migrationNS := th.CreateNamespace(migrationNSName)
679+
migrationFRRCfg := CreateFRRConfiguration(
680+
types.NamespacedName{Namespace: migrationNS.Name, Name: "worker-0"},
681+
GetMetalLBFRRConfigurationSpec("worker-0"),
682+
)
683+
Expect(migrationFRRCfg).To(Not(BeNil()))
684+
685+
bgpReconciler.FRRMigrationNamespace = migrationNSName
686+
DeferCleanup(func() {
687+
bgpReconciler.FRRMigrationNamespace = ""
688+
})
689+
DeferCleanup(th.DeleteInstance, migrationFRRCfg)
690+
691+
// Trigger a reconcile by updating the pod
692+
pod := th.GetPod(podName)
693+
if pod.Annotations == nil {
694+
pod.Annotations = map[string]string{}
695+
}
696+
pod.Annotations["trigger-reconcile"] = "true"
697+
Eventually(func(g Gomega) {
698+
g.Expect(k8sClient.Update(ctx, pod)).Should(Succeed())
699+
}, timeout, interval).Should(Succeed())
700+
701+
// Verify FRRConfiguration is created in the new namespace
702+
Eventually(func(g Gomega) {
703+
frr := GetFRRConfiguration(types.NamespacedName{
704+
Namespace: migrationNS.Name,
705+
Name: podFrrName.Name,
706+
})
707+
g.Expect(frr).To(Not(BeNil()))
708+
g.Expect(frr.Spec.BGP.Routers[0].Prefixes[0]).To(Equal("172.17.0.40/32"))
709+
}, timeout, interval).Should(Succeed())
710+
711+
// Verify old FRRConfiguration in metallb-system is cleaned up
712+
Eventually(func(g Gomega) {
713+
frr := &frrk8sv1.FRRConfiguration{}
714+
err := k8sClient.Get(ctx, types.NamespacedName{
715+
Namespace: metallbNS.Name,
716+
Name: podFrrName.Name,
717+
}, frr)
718+
g.Expect(err).To(HaveOccurred())
719+
g.Expect(k8s_errors.IsNotFound(err)).To(BeTrue())
720+
}, timeout, interval).Should(Succeed())
721+
})
722+
})
576723
})

0 commit comments

Comments
 (0)