Skip to content

Commit 369f28a

Browse files
committed
Rebalance auto-assigned egress IPs when necessary
1 parent 0a40be0 commit 369f28a

File tree

2 files changed

+94
-11
lines changed

2 files changed

+94
-11
lines changed

pkg/network/common/egressip.go

Lines changed: 60 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -490,10 +490,7 @@ func (eit *EgressIPTracker) findEgressIPAllocation(ip net.IP, allocation map[str
490490
if node.offline {
491491
continue
492492
}
493-
egressIPs, exists := allocation[node.nodeName]
494-
if !exists {
495-
continue
496-
}
493+
egressIPs := allocation[node.nodeName]
497494
for _, parsed := range node.parsedCIDRs {
498495
if parsed.Contains(ip) {
499496
if bestNode != "" {
@@ -511,13 +508,13 @@ func (eit *EgressIPTracker) findEgressIPAllocation(ip net.IP, allocation map[str
511508
return bestNode, otherNodes
512509
}
513510

514-
// ReallocateEgressIPs returns a map from Node name to array-of-Egress-IP for all auto-allocated egress IPs
515-
func (eit *EgressIPTracker) ReallocateEgressIPs() map[string][]string {
516-
eit.Lock()
517-
defer eit.Unlock()
511+
func (eit *EgressIPTracker) makeEmptyAllocation() (map[string][]string, map[string]bool) {
512+
return make(map[string][]string), make(map[string]bool)
513+
}
514+
515+
func (eit *EgressIPTracker) allocateExistingEgressIPs(allocation map[string][]string, alreadyAllocated map[string]bool) bool {
516+
removedEgressIPs := false
518517

519-
allocation := make(map[string][]string)
520-
alreadyAllocated := make(map[string]bool)
521518
for _, node := range eit.nodes {
522519
if len(node.parsedCIDRs) > 0 {
523520
allocation[node.nodeName] = make([]string, 0, node.requestedIPs.Len())
@@ -526,7 +523,7 @@ func (eit *EgressIPTracker) ReallocateEgressIPs() map[string][]string {
526523
// For each active egress IP, if it still fits within some egress CIDR on its node,
527524
// add it to that node's allocation.
528525
for egressIP, eip := range eit.egressIPs {
529-
if eip.assignedNodeIP == "" {
526+
if eip.assignedNodeIP == "" || alreadyAllocated[egressIP] {
530527
continue
531528
}
532529
node := eip.nodes[0]
@@ -539,13 +536,19 @@ func (eit *EgressIPTracker) ReallocateEgressIPs() map[string][]string {
539536
}
540537
if found && !node.offline {
541538
allocation[node.nodeName] = append(allocation[node.nodeName], egressIP)
539+
} else {
540+
removedEgressIPs = true
542541
}
543542
// (We set alreadyAllocated even if the egressIP will be removed from
544543
// its current node; we can't assign it to a new node until the next
545544
// reallocation.)
546545
alreadyAllocated[egressIP] = true
547546
}
548547

548+
return removedEgressIPs
549+
}
550+
551+
func (eit *EgressIPTracker) allocateNewEgressIPs(allocation map[string][]string, alreadyAllocated map[string]bool) {
549552
// Allocate pending egress IPs that can only go to a single node
550553
for egressIP, eip := range eit.egressIPs {
551554
if alreadyAllocated[egressIP] {
@@ -567,6 +570,52 @@ func (eit *EgressIPTracker) ReallocateEgressIPs() map[string][]string {
567570
allocation[nodeName] = append(allocation[nodeName], egressIP)
568571
}
569572
}
573+
}
574+
575+
// ReallocateEgressIPs returns a map from Node name to array-of-Egress-IP for all auto-allocated egress IPs
576+
func (eit *EgressIPTracker) ReallocateEgressIPs() map[string][]string {
577+
eit.Lock()
578+
defer eit.Unlock()
579+
580+
allocation, alreadyAllocated := eit.makeEmptyAllocation()
581+
removedEgressIPs := eit.allocateExistingEgressIPs(allocation, alreadyAllocated)
582+
eit.allocateNewEgressIPs(allocation, alreadyAllocated)
583+
if removedEgressIPs {
584+
// Process the removals now; we'll get called again afterward and can
585+
// check for balance then.
586+
return allocation
587+
}
588+
589+
// Compare the allocation to what we would have gotten if we started from scratch,
590+
// to see if things have gotten too unbalanced. (In particular, if a node goes
591+
// offline, gets emptied, and then comes back online, we want to move a bunch of
592+
// egress IPs back onto that node.)
593+
fullReallocation, alreadyAllocated := eit.makeEmptyAllocation()
594+
eit.allocateNewEgressIPs(fullReallocation, alreadyAllocated)
595+
596+
emptyNodes := []string{}
597+
for nodeName, fullEgressIPs := range fullReallocation {
598+
incrementalEgressIPs := allocation[nodeName]
599+
if len(incrementalEgressIPs) < len(fullEgressIPs)/2 {
600+
emptyNodes = append(emptyNodes, nodeName)
601+
}
602+
}
603+
604+
if len(emptyNodes) > 0 {
605+
// Make a new incremental allocation, but skipping all of the egress IPs
606+
// that got assigned to the "empty" nodes in the full reallocation; this
607+
// will cause them to be dropped from their current nodes and then later
608+
// reassigned (to one of the "empty" nodes, for balance).
609+
allocation, alreadyAllocated = eit.makeEmptyAllocation()
610+
for _, nodeName := range emptyNodes {
611+
for _, egressIP := range fullReallocation[nodeName] {
612+
alreadyAllocated[egressIP] = true
613+
}
614+
}
615+
eit.allocateExistingEgressIPs(allocation, alreadyAllocated)
616+
eit.allocateNewEgressIPs(allocation, alreadyAllocated)
617+
eit.updateEgressCIDRs = true
618+
}
570619

571620
return allocation
572621
}

pkg/network/common/egressip_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1106,4 +1106,38 @@ func TestEgressCIDRAllocationOffline(t *testing.T) {
11061106
t.Fatalf("Bad IP allocation: %#v", allocation)
11071107
}
11081108
updateAllocations(eit, allocation)
1109+
1110+
// Bring node-3 back
1111+
eit.SetNodeOffline("172.17.0.3", false)
1112+
err = w.assertUpdateEgressCIDRsNotification()
1113+
if err != nil {
1114+
t.Fatalf("%v", err)
1115+
}
1116+
1117+
// First reallocation should remove some IPs from node-4 and node-5 but not add
1118+
// them to node-3. As above, the "balanced" allocation we're aiming for may not
1119+
// be perfect, but it has to be planning to assign at least 2 IPs to node-3.
1120+
allocation = eit.ReallocateEgressIPs()
1121+
node3ips = allocation["node-3"]
1122+
node4ips = allocation["node-4"]
1123+
node5ips = allocation["node-5"]
1124+
if len(node3ips) != 0 || len(node4ips)+len(node5ips) > 4 {
1125+
t.Fatalf("Bad IP allocation: %#v", allocation)
1126+
}
1127+
updateAllocations(eit, allocation)
1128+
1129+
err = w.assertUpdateEgressCIDRsNotification()
1130+
if err != nil {
1131+
t.Fatalf("%v", err)
1132+
}
1133+
1134+
// Next reallocation should reassign egress IPs to node-3
1135+
allocation = eit.ReallocateEgressIPs()
1136+
node3ips = allocation["node-3"]
1137+
node4ips = allocation["node-4"]
1138+
node5ips = allocation["node-5"]
1139+
if len(node3ips) < 2 || len(node4ips) == 0 || len(node5ips) == 0 {
1140+
t.Fatalf("Bad IP allocation: %#v", allocation)
1141+
}
1142+
updateAllocations(eit, allocation)
11091143
}

0 commit comments

Comments
 (0)