Skip to content

Commit 9a6ef80

Browse files
committed
Support UDNs for EIP route advertisements
Add support to advertise EIPs for UDNs in cluster manager RouteAdvertisements controller. Selected Egress IPs are those that - are served on the same namespaces as where the selected networks are serving, and - are assigned to a selected node - are on the default network subnet for that node Egress IPs, just as with Pod IPs, will be advertised on routers on the target VRF on the selected nodes. `auto` is not supported as target VRF for Egress IPs. Better support for Egress IPs on subnets other that the default network node subnet, including any support for VRF-Lite interface subnets, is left for a future exercise. We would need cluster manager to be able to: - map non VRF-Lite interface subnets to the proper BGP sessions - tell apart VRF-Lite interface subnets from other secondary interface subnets Signed-off-by: Jaime Caamaño Ruiz <[email protected]>
1 parent 8f6e3ee commit 9a6ef80

File tree

3 files changed

+254
-115
lines changed

3 files changed

+254
-115
lines changed

go-controller/pkg/clustermanager/routeadvertisements/controller.go

Lines changed: 137 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,14 @@ var (
5757

5858
// Controller reconciles RouteAdvertisements
5959
type Controller struct {
60-
eipLister egressiplisters.EgressIPLister
61-
frrLister frrlisters.FRRConfigurationLister
62-
nadLister nadlisters.NetworkAttachmentDefinitionLister
63-
nodeLister corelisters.NodeLister
64-
raLister ralisters.RouteAdvertisementsLister
60+
wf *factory.WatchFactory
61+
62+
eipLister egressiplisters.EgressIPLister
63+
frrLister frrlisters.FRRConfigurationLister
64+
nadLister nadlisters.NetworkAttachmentDefinitionLister
65+
nodeLister corelisters.NodeLister
66+
raLister ralisters.RouteAdvertisementsLister
67+
namespaceLister corelisters.NamespaceLister
6568

6669
frrClient frrclientset.Interface
6770
nadClient nadclientset.Interface
@@ -72,6 +75,7 @@ type Controller struct {
7275
nadController controllerutil.Controller
7376
nodeController controllerutil.Controller
7477
raController controllerutil.Controller
78+
nsController controllerutil.Controller
7579

7680
nm networkmanager.Interface
7781
}
@@ -83,15 +87,17 @@ func NewController(
8387
ovnClient *util.OVNClusterManagerClientset,
8488
) *Controller {
8589
c := &Controller{
86-
eipLister: wf.EgressIPInformer().Lister(),
87-
frrLister: wf.FRRConfigurationsInformer().Lister(),
88-
nadLister: wf.NADInformer().Lister(),
89-
nodeLister: wf.NodeCoreInformer().Lister(),
90-
raLister: wf.RouteAdvertisementsInformer().Lister(),
91-
frrClient: ovnClient.FRRClient,
92-
nadClient: ovnClient.NetworkAttchDefClient,
93-
raClient: ovnClient.RouteAdvertisementsClient,
94-
nm: nm,
90+
wf: wf,
91+
eipLister: wf.EgressIPInformer().Lister(),
92+
frrLister: wf.FRRConfigurationsInformer().Lister(),
93+
nadLister: wf.NADInformer().Lister(),
94+
nodeLister: wf.NodeCoreInformer().Lister(),
95+
raLister: wf.RouteAdvertisementsInformer().Lister(),
96+
namespaceLister: wf.NamespaceInformer().Lister(),
97+
frrClient: ovnClient.FRRClient,
98+
nadClient: ovnClient.NetworkAttchDefClient,
99+
raClient: ovnClient.RouteAdvertisementsClient,
100+
nm: nm,
95101
}
96102

97103
handleError := func(key string, errorstatus error) error {
@@ -153,14 +159,24 @@ func NewController(
153159

154160
eipConfig := &controllerutil.ControllerConfig[eiptypes.EgressIP]{
155161
RateLimiter: workqueue.DefaultTypedControllerRateLimiter[string](),
156-
Reconcile: c.reconcileEgressIP,
162+
Reconcile: c.reconcileEgressIPs,
157163
Threadiness: 1,
158164
Informer: wf.EgressIPInformer().Informer(),
159165
Lister: wf.EgressIPInformer().Lister().List,
160166
ObjNeedsUpdate: egressIPNeedsUpdate,
161167
}
162168
c.eipController = controllerutil.NewController("clustermanager routeadvertisements egressip controller", eipConfig)
163169

170+
nsConfig := &controllerutil.ControllerConfig[corev1.Namespace]{
171+
RateLimiter: workqueue.DefaultTypedControllerRateLimiter[string](),
172+
Reconcile: c.reconcileEgressIPs,
173+
Threadiness: 1,
174+
Informer: wf.NamespaceInformer().Informer(),
175+
Lister: wf.NamespaceInformer().Lister().List,
176+
ObjNeedsUpdate: nsNeedsUpdate,
177+
}
178+
c.nsController = controllerutil.NewController("clustermanager routeadvertisements namespace controller", nsConfig)
179+
164180
return c
165181
}
166182

@@ -171,6 +187,7 @@ func (c *Controller) Start() error {
171187
c.frrController,
172188
c.nadController,
173189
c.nodeController,
190+
c.nsController,
174191
c.raController,
175192
)
176193
}
@@ -181,20 +198,33 @@ func (c *Controller) Stop() {
181198
c.frrController,
182199
c.nadController,
183200
c.nodeController,
201+
c.nsController,
184202
c.raController,
185203
)
186-
klog.Infof("Cluster manager routeadvertisements stoppedu")
204+
klog.Infof("Cluster manager routeadvertisements stopped")
187205
}
188206

189207
func (c *Controller) ReconcileNetwork(_ string, old, new util.NetInfo) {
190-
// This controller already listens on NAD events however we skip NADs
191-
// pointing to networks that network manager is still not aware of; so we
192-
// only need to signal the reconciliation of new networks. Reconcile one of
193-
// the NADs of the network to do s
194-
if new == nil || old != nil {
195-
return
196-
}
197-
c.nadController.Reconcile(new.GetNADs()[0])
208+
// This controller already listens on NAD events but there is two additional
209+
// scenarios we need to cover for:
210+
// - for newly created networks, we need to wait until network manager is
211+
// aware of them.
212+
// - if the namespaces served by a network change.
213+
oldNamespaces, newNamespaces := sets.New[string](), sets.New[string]()
214+
if old != nil {
215+
oldNamespaces.Insert(old.GetNADNamespaces()...)
216+
}
217+
if new != nil {
218+
newNamespaces.Insert(new.GetNADNamespaces()...)
219+
}
220+
if new != nil && !newNamespaces.Equal(oldNamespaces) {
221+
// we use one of the NADs of the network to reconcile it
222+
c.nadController.Reconcile(new.GetNADs()[0])
223+
// if the namespaces served by a network changed, it is possible that
224+
// those namespaces are served or no longer served by the default
225+
// network, so reconcile it as well
226+
c.nadController.Reconcile(config.Kubernetes.OVNConfigNamespace + "/" + types.DefaultNetworkName)
227+
}
198228
}
199229

200230
// Reconcile RouteAdvertisements. For each selected FRRConfiguration and node,
@@ -206,7 +236,8 @@ func (c *Controller) ReconcileNetwork(_ string, old, new util.NetInfo) {
206236
//
207237
// - If EgressIP advertisements are enabled, the generated FRRConfiguration will
208238
// announce from the node the EgressIPs allocated to it on the matching target
209-
// VRFs.
239+
// VRFs. Selected EgressIP are those that serve the same namespaces as the
240+
// selected networks. Target VRF `auto` is not supported for EgressIPs.
210241
//
211242
// - If pod network advertisements are enabled, the generated FRRConfiguration
212243
// will import the target VRFs on the selected networks as required.
@@ -222,7 +253,7 @@ func (c *Controller) ReconcileNetwork(_ string, old, new util.NetInfo) {
222253
// Finally, it will update the status of the RouteAdvertisements.
223254
//
224255
// The controller processes selected events of RouteAdvertisements,
225-
// FRRConfigurations, Nodes, EgressIPs and NADs.
256+
// FRRConfigurations, Nodes, EgressIPs, NADs and namespaces.
226257
func (c *Controller) reconcile(name string) error {
227258
startTime := time.Now()
228259
klog.V(5).Infof("Syncing routeadvertisements %q", name)
@@ -294,6 +325,11 @@ func (c *Controller) generateFRRConfigurations(ra *ratypes.RouteAdvertisements)
294325
return nil, nil, nil
295326
}
296327

328+
advertisements := sets.New(ra.Spec.Advertisements...)
329+
if advertisements.Has(ratypes.EgressIP) && ra.Spec.TargetVRF == "auto" {
330+
return nil, nil, fmt.Errorf("%w: advertising EgressIP not supported with TargetVRF set to 'auto'", errConfig)
331+
}
332+
297333
// if we are matching on the well known default network label, create an
298334
// internal nad for it if it doesn't exist
299335
if matchesDefaultNetworkLabel(ra.Spec.NetworkSelector) {
@@ -369,7 +405,6 @@ func (c *Controller) generateFRRConfigurations(ra *ratypes.RouteAdvertisements)
369405
if err != nil {
370406
return nil, nil, err
371407
}
372-
advertisements := sets.New(ra.Spec.Advertisements...)
373408
if !nodeSelector.Empty() && advertisements.Has(ratypes.PodNetwork) {
374409
return nil, nil, fmt.Errorf("%w: node selector cannot be specified if pod network is advertised", errConfig)
375410
}
@@ -444,15 +479,15 @@ func (c *Controller) generateFRRConfigurations(ra *ratypes.RouteAdvertisements)
444479

445480
// helper to gather egress ips and cache during reconcile
446481
// TODO perhaps cache across reconciles as well
447-
var nodeEgressIPs map[string][]string
448-
getEgressIPs := func(nodeName string) ([]string, error) {
449-
if nodeEgressIPs == nil {
450-
nodeEgressIPs, err = c.getEgressIPsByNode()
482+
var eipsByNodesByNetworks map[string]map[string]sets.Set[string]
483+
getEgressIPsByNode := func(nodeName string) (map[string]sets.Set[string], error) {
484+
if eipsByNodesByNetworks == nil {
485+
eipsByNodesByNetworks, err = c.getEgressIPsByNodesByNetworks(networkSet)
451486
if err != nil {
452487
return nil, err
453488
}
454489
}
455-
return nodeEgressIPs[nodeName], nil
490+
return eipsByNodesByNetworks[nodeName], nil
456491
}
457492

458493
// helper to gather host subnets and egress ips as prefixes
@@ -469,13 +504,11 @@ func (c *Controller) generateFRRConfigurations(ra *ratypes.RouteAdvertisements)
469504
// gather EgressIPs
470505
var eips []string
471506
if advertisements.Has(ratypes.EgressIP) {
472-
if network != types.DefaultNetworkName {
473-
return nil, fmt.Errorf("%w: can't advertise EgressIP in selected non default network %q: %w", errConfig, network, err)
474-
}
475-
eips, err = getEgressIPs(nodeName)
507+
eipsByNode, err := getEgressIPsByNode(nodeName)
476508
if err != nil {
477509
return nil, err
478510
}
511+
eips = eipsByNode[network].UnsortedList()
479512
}
480513

481514
prefixes := make([]string, 0, len(subnets)+len(eips))
@@ -500,8 +533,13 @@ func (c *Controller) generateFRRConfigurations(ra *ratypes.RouteAdvertisements)
500533
// ordered
501534
slices.Sort(selectedNetworks.hostNetworkSubnets[network])
502535
}
503-
// ordered
504-
slices.Sort(selectedNetworks.hostSubnets)
536+
// order, dedup
537+
selectedNetworks.hostSubnets = sets.List(sets.New(selectedNetworks.hostSubnets...))
538+
539+
// if there is no prefixes to advertise for this node, skip it
540+
if len(selectedNetworks.hostSubnets) == 0 {
541+
continue
542+
}
505543

506544
matchedNetworks := sets.New[string]()
507545
for _, frrConfig := range frrConfigs {
@@ -985,26 +1023,69 @@ func (c *Controller) getOrCreateDefaultNetworkNAD() (*nadtypes.NetworkAttachment
9851023
)
9861024
}
9871025

988-
// getEgressIPsByNode iterates all existing egress IPs and returns them indexed
989-
// by node
990-
func (c *Controller) getEgressIPsByNode() (map[string][]string, error) {
1026+
// getEgressIPsByNodesByNetworks iterates all existing egress IPs that apply to
1027+
// any of the provided networks and returns a "node -> network -> eips"
1028+
// map.
1029+
func (c *Controller) getEgressIPsByNodesByNetworks(networks sets.Set[string]) (map[string]map[string]sets.Set[string], error) {
1030+
eipsByNodesByNetworks := map[string]map[string]sets.Set[string]{}
1031+
addEgressIPsByNodesByNetwork := func(eipsByNodes map[string]string, network string) {
1032+
for node, eip := range eipsByNodes {
1033+
if eipsByNodesByNetworks[node] == nil {
1034+
eipsByNodesByNetworks[node] = map[string]sets.Set[string]{}
1035+
}
1036+
if eipsByNodesByNetworks[node][network] == nil {
1037+
eipsByNodesByNetworks[node][network] = sets.New[string]()
1038+
}
1039+
eipsByNodesByNetworks[node][network].Insert(eip)
1040+
}
1041+
}
1042+
1043+
addEgressIPsByNodesByNetworkSelector := func(eipsByNodes map[string]string, namespaceSelector *metav1.LabelSelector) error {
1044+
nsSelector, err := metav1.LabelSelectorAsSelector(namespaceSelector)
1045+
if err != nil {
1046+
return err
1047+
}
1048+
selected, err := c.namespaceLister.List(nsSelector)
1049+
if err != nil {
1050+
return err
1051+
}
1052+
for _, namespace := range selected {
1053+
namespaceNetwork := c.nm.GetActiveNetworkForNamespaceFast(namespace.Name)
1054+
networkName := namespaceNetwork.GetNetworkName()
1055+
if networks.Has(networkName) {
1056+
addEgressIPsByNodesByNetwork(eipsByNodes, networkName)
1057+
}
1058+
}
1059+
return nil
1060+
}
1061+
9911062
eips, err := c.eipLister.List(labels.Everything())
9921063
if err != nil {
9931064
return nil, err
9941065
}
9951066

996-
eipsByNode := map[string][]string{}
9971067
for _, eip := range eips {
1068+
eipsByNodes := make(map[string]string, len(eip.Status.Items))
9981069
for _, item := range eip.Status.Items {
999-
if item.EgressIP == "" {
1070+
// skip unassigned EIPs
1071+
if item.EgressIP == "" || item.Node == "" {
10001072
continue
10011073
}
1074+
10021075
ip := item.EgressIP + util.GetIPFullMaskString(item.EgressIP)
1003-
eipsByNode[item.Node] = append(eipsByNode[item.Node], ip)
1076+
eipsByNodes[item.Node] = ip
1077+
}
1078+
if len(eipsByNodes) == 0 {
1079+
continue
1080+
}
1081+
1082+
err = addEgressIPsByNodesByNetworkSelector(eipsByNodes, &eip.Spec.NamespaceSelector)
1083+
if err != nil {
1084+
return nil, err
10041085
}
10051086
}
10061087

1007-
return eipsByNode, nil
1088+
return eipsByNodesByNetworks, nil
10081089
}
10091090

10101091
// isOwnUpdate checks if an object was updated by us last, as indicated by its
@@ -1055,12 +1136,13 @@ func nadNeedsUpdate(oldObj, newObj *nadtypes.NetworkAttachmentDefinition) bool {
10551136
func nodeNeedsUpdate(oldObj, newObj *corev1.Node) bool {
10561137
return oldObj == nil || newObj == nil ||
10571138
!reflect.DeepEqual(oldObj.Labels, newObj.Labels) ||
1058-
util.NodeSubnetAnnotationChanged(oldObj, newObj)
1139+
util.NodeSubnetAnnotationChanged(oldObj, newObj) ||
1140+
oldObj.Annotations[util.OvnNodeIfAddr] != newObj.Annotations[util.OvnNodeIfAddr]
10591141
}
10601142

10611143
func egressIPNeedsUpdate(oldObj, newObj *eiptypes.EgressIP) bool {
1062-
if oldObj != nil && newObj != nil && reflect.DeepEqual(oldObj.Status, newObj.Status) {
1063-
return false
1144+
if oldObj != nil && newObj != nil {
1145+
return !reflect.DeepEqual(oldObj.Status, newObj.Status) || !reflect.DeepEqual(oldObj.Spec.NamespaceSelector, newObj.Spec.NamespaceSelector)
10641146
}
10651147
if oldObj != nil && len(oldObj.Status.Items) > 0 {
10661148
return true
@@ -1071,6 +1153,12 @@ func egressIPNeedsUpdate(oldObj, newObj *eiptypes.EgressIP) bool {
10711153
return false
10721154
}
10731155

1156+
func nsNeedsUpdate(oldObj, newObj *corev1.Namespace) bool {
1157+
// we only care about label changes, added/deleted namespaces served by a
1158+
// UDN will already be reflected in a network update
1159+
return oldObj != nil && newObj != nil && !reflect.DeepEqual(oldObj.Labels, newObj.Labels)
1160+
}
1161+
10741162
func (c *Controller) reconcileFRRConfiguration(key string) error {
10751163
namespace, name, err := cache.SplitMetaNamespaceKey(key)
10761164
if err != nil {
@@ -1133,7 +1221,7 @@ func (c *Controller) reconcileNAD(key string) error {
11331221
return nil
11341222
}
11351223

1136-
func (c *Controller) reconcileEgressIP(_ string) error {
1224+
func (c *Controller) reconcileEgressIPs(string) error {
11371225
// reconcile RAs that advertise EIPs
11381226
ras, err := c.raLister.List(labels.Everything())
11391227
if err != nil {

0 commit comments

Comments
 (0)