@@ -18,10 +18,15 @@ import (
1818)
1919
2020type nodeEgress struct {
21- nodeIP string
22- sdnIP string
23- requestedIPs sets.String
24- offline bool
21+ nodeName string
22+ nodeIP string
23+ sdnIP string
24+
25+ requestedIPs sets.String
26+ requestedCIDRs sets.String
27+ parsedCIDRs map [string ]* net.IPNet
28+
29+ offline bool
2530}
2631
2732type namespaceEgress struct {
@@ -32,7 +37,8 @@ type namespaceEgress struct {
3237}
3338
3439type egressIPInfo struct {
35- ip string
40+ ip string
41+ parsed net.IP
3642
3743 nodes []* nodeEgress
3844 namespaces []* namespaceEgress
@@ -48,6 +54,8 @@ type EgressIPWatcher interface {
4854 SetNamespaceEgressNormal (vnid uint32 )
4955 SetNamespaceEgressDropped (vnid uint32 )
5056 SetNamespaceEgressViaEgressIP (vnid uint32 , egressIP , nodeIP string )
57+
58+ UpdateEgressCIDRs ()
5159}
5260
5361type EgressIPTracker struct {
@@ -58,9 +66,11 @@ type EgressIPTracker struct {
5866 nodesByNodeIP map [string ]* nodeEgress
5967 namespacesByVNID map [uint32 ]* namespaceEgress
6068 egressIPs map [string ]* egressIPInfo
69+ nodesWithCIDRs int
6170
6271 changedEgressIPs map [* egressIPInfo ]bool
6372 changedNamespaces map [* namespaceEgress ]bool
73+ updateEgressCIDRs bool
6474}
6575
6676func NewEgressIPTracker (watcher EgressIPWatcher ) * EgressIPTracker {
@@ -84,7 +94,7 @@ func (eit *EgressIPTracker) Start(hostSubnetInformer networkinformers.HostSubnet
8494func (eit * EgressIPTracker ) ensureEgressIPInfo (egressIP string ) * egressIPInfo {
8595 eg := eit .egressIPs [egressIP ]
8696 if eg == nil {
87- eg = & egressIPInfo {ip : egressIP }
97+ eg = & egressIPInfo {ip : egressIP , parsed : net . ParseIP ( egressIP ) }
8898 eit .egressIPs [egressIP ] = eg
8999 }
90100 return eg
@@ -177,22 +187,40 @@ func (eit *EgressIPTracker) UpdateHostSubnetEgress(hs *networkapi.HostSubnet) {
177187
178188 node := eit .nodesByNodeIP [hs .HostIP ]
179189 if node == nil {
180- if len (hs .EgressIPs ) == 0 {
190+ if len (hs .EgressIPs ) == 0 && len ( hs . EgressCIDRs ) == 0 {
181191 return
182192 }
183193 node = & nodeEgress {
194+ nodeName : hs .Host ,
184195 nodeIP : hs .HostIP ,
185196 sdnIP : sdnIP ,
186197 requestedIPs : sets .NewString (),
187198 }
188199 eit .nodesByNodeIP [hs .HostIP ] = node
189- } else if len (hs .EgressIPs ) == 0 {
200+ } else if len (hs .EgressIPs ) == 0 && len ( hs . EgressCIDRs ) == 0 {
190201 delete (eit .nodesByNodeIP , hs .HostIP )
191202 }
192- oldRequestedIPs := node .requestedIPs
193- node .requestedIPs = sets .NewString (hs .EgressIPs ... )
203+
204+ // Process EgressCIDRs
205+ newRequestedCIDRs := sets .NewString (hs .EgressCIDRs ... )
206+ if ! node .requestedCIDRs .Equal (newRequestedCIDRs ) {
207+ if len (hs .EgressCIDRs ) == 0 {
208+ eit .nodesWithCIDRs --
209+ } else if node .requestedCIDRs .Len () == 0 {
210+ eit .nodesWithCIDRs ++
211+ }
212+ node .requestedCIDRs = newRequestedCIDRs
213+ node .parsedCIDRs = make (map [string ]* net.IPNet )
214+ for _ , cidr := range hs .EgressCIDRs {
215+ _ , parsed , _ := net .ParseCIDR (cidr )
216+ node .parsedCIDRs [cidr ] = parsed
217+ }
218+ eit .updateEgressCIDRs = true
219+ }
194220
195221 // Process new and removed EgressIPs
222+ oldRequestedIPs := node .requestedIPs
223+ node .requestedIPs = sets .NewString (hs .EgressIPs ... )
196224 for _ , ip := range node .requestedIPs .Difference (oldRequestedIPs ).UnsortedList () {
197225 eit .addNodeEgressIP (node , ip )
198226 }
@@ -301,6 +329,13 @@ func (eit *EgressIPTracker) syncEgressIPs() {
301329 for ns := range changedNamespaces {
302330 eit .syncEgressNamespaceState (ns )
303331 }
332+
333+ if eit .updateEgressCIDRs {
334+ eit .updateEgressCIDRs = false
335+ if eit .nodesWithCIDRs > 0 {
336+ eit .watcher .UpdateEgressCIDRs ()
337+ }
338+ }
304339}
305340
306341func (eit * EgressIPTracker ) syncEgressNodeState (eg * egressIPInfo , active bool ) {
@@ -313,6 +348,10 @@ func (eit *EgressIPTracker) syncEgressNodeState(eg *egressIPInfo, active bool) {
313348 eit .watcher .ReleaseEgressIP (eg .ip , eg .assignedNodeIP )
314349 eg .assignedNodeIP = ""
315350 }
351+
352+ if eg .assignedNodeIP == "" {
353+ eit .updateEgressCIDRs = true
354+ }
316355}
317356
318357func (eit * EgressIPTracker ) syncEgressNamespaceState (ns * namespaceEgress ) {
@@ -402,3 +441,103 @@ func (eit *EgressIPTracker) Ping(ip string, timeout time.Duration) bool {
402441 return true
403442 }
404443}
444+
445+ // Finds the best node to allocate the egress IP to, given the existing allocation. The
446+ // boolean return value indicates whether multiple nodes could host the IP.
447+ func (eit * EgressIPTracker ) findEgressIPAllocation (ip net.IP , allocation map [string ][]string ) (string , bool ) {
448+ bestNode := ""
449+ otherNodes := false
450+
451+ for _ , node := range eit .nodesByNodeIP {
452+ egressIPs , exists := allocation [node .nodeName ]
453+ if ! exists {
454+ continue
455+ }
456+ for _ , parsed := range node .parsedCIDRs {
457+ if parsed .Contains (ip ) {
458+ if bestNode != "" {
459+ otherNodes = true
460+ if len (allocation [bestNode ]) < len (egressIPs ) {
461+ break
462+ }
463+ }
464+ bestNode = node .nodeName
465+ break
466+ }
467+ }
468+ }
469+
470+ return bestNode , otherNodes
471+ }
472+
473+ // ReallocateEgressIPs returns a map from Node name to array-of-Egress-IP. Unchanged nodes are not included.
474+ func (eit * EgressIPTracker ) ReallocateEgressIPs () map [string ][]string {
475+ eit .Lock ()
476+ defer eit .Unlock ()
477+
478+ allocation := make (map [string ][]string )
479+ changed := make (map [string ]bool )
480+ alreadyAllocated := make (map [string ]bool )
481+ for _ , node := range eit .nodesByNodeIP {
482+ if len (node .parsedCIDRs ) > 0 {
483+ allocation [node .nodeName ] = make ([]string , 0 , node .requestedIPs .Len ())
484+ }
485+ }
486+ // For each active egress IP, if it still fits within some egress CIDR on its node,
487+ // add it to that node's allocation. (Otherwise add the node to the "changed" map,
488+ // since we'll be removing this egress IP from it.)
489+ for egressIP , eip := range eit .egressIPs {
490+ if eip .assignedNodeIP == "" {
491+ continue
492+ }
493+ node := eip .nodes [0 ]
494+ found := false
495+ for _ , parsed := range node .parsedCIDRs {
496+ if parsed .Contains (eip .parsed ) {
497+ found = true
498+ break
499+ }
500+ }
501+ if found {
502+ allocation [node .nodeName ] = append (allocation [node .nodeName ], egressIP )
503+ } else {
504+ changed [node .nodeName ] = true
505+ }
506+ // (We set alreadyAllocated even if the egressIP will be removed from
507+ // its current node; we can't assign it to a new node until the next
508+ // reallocation.)
509+ alreadyAllocated [egressIP ] = true
510+ }
511+
512+ // Allocate pending egress IPs that can only go to a single node
513+ for egressIP , eip := range eit .egressIPs {
514+ if alreadyAllocated [egressIP ] {
515+ continue
516+ }
517+ nodeName , otherNodes := eit .findEgressIPAllocation (eip .parsed , allocation )
518+ if nodeName != "" && ! otherNodes {
519+ allocation [nodeName ] = append (allocation [nodeName ], egressIP )
520+ changed [nodeName ] = true
521+ alreadyAllocated [egressIP ] = true
522+ }
523+ }
524+ // Allocate any other pending egress IPs that we can
525+ for egressIP , eip := range eit .egressIPs {
526+ if alreadyAllocated [egressIP ] {
527+ continue
528+ }
529+ nodeName , _ := eit .findEgressIPAllocation (eip .parsed , allocation )
530+ if nodeName != "" {
531+ allocation [nodeName ] = append (allocation [nodeName ], egressIP )
532+ changed [nodeName ] = true
533+ }
534+ }
535+
536+ // Remove unchanged nodes from the return value
537+ for _ , node := range eit .nodesByNodeIP {
538+ if ! changed [node .nodeName ] {
539+ delete (allocation , node .nodeName )
540+ }
541+ }
542+ return allocation
543+ }
0 commit comments