Skip to content

Commit

Permalink
Fix big CIDR create Pod slow
Browse files Browse the repository at this point in the history
Signed-off-by: lou-lan <[email protected]>
(cherry picked from commit ba95bce)
  • Loading branch information
lou-lan committed Jun 24, 2024
1 parent 3b51a13 commit c60cebb
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 21 deletions.
99 changes: 85 additions & 14 deletions pkg/ip/ip.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,32 +84,103 @@ func IsIP(version types.IPVersion, s string) error {
// [172.18.40.2 172.18.40.3] is [172.18.40.1].
//
// If sorted is true, the result set of IP addresses will be sorted.
func IPsDiffSet(ips1, ips2 []net.IP, sorted bool) []net.IP {
var ips []net.IP
marks := make(map[string]bool)
for _, ip := range ips1 {
func IPsDiffSet(ipSourceList, ipExcludeList []net.IP, sorted bool) []net.IP {
return getIPDiffSet(ipSourceList, ipExcludeList, sorted, -1)
}

func IsDiffIPSet(ipSourceList, ipExcludeList []net.IP) bool {
diff := getIPDiffSet(ipSourceList, ipExcludeList, false, 1)
return len(diff) > 0

Check warning on line 93 in pkg/ip/ip.go

View check run for this annotation

Codecov / codecov/patch

pkg/ip/ip.go#L91-L93

Added lines #L91 - L93 were not covered by tests
}

// getIPDiffSet returns a list of IPs from ipSourceList that are not in ipExcludeList. Parameters:
// - ipSourceList: a slice of net.IP that represents the source list of IPs.
// - ipExcludeList: a slice of net.IP that represents the list of IPs to be excluded.
// - sorted: a boolean indicating whether the resulting list should be sorted.
// - expectCount: an integer specifying the maximum number of IPs to return. If expectCount <= 0, all IPs will be returned.
func getIPDiffSet(ipSourceList, ipExcludeList []net.IP, sorted bool, expectCount int) []net.IP {
ips2Map := make(map[[16]byte]struct{}, len(ipExcludeList))
for _, ip := range ipExcludeList {
if ip != nil {
marks[ip.String()] = true
ips2Map[[16]byte(ip.To16())] = struct{}{}
}
}

for _, ip := range ips2 {
var result []net.IP
for _, ip := range ipSourceList {
if ip != nil {
delete(marks, ip.String())
if _, ok := ips2Map[[16]byte(ip.To16())]; !ok {
result = append(result, ip)
if expectCount > 0 && len(result) >= expectCount {
break

Check warning on line 115 in pkg/ip/ip.go

View check run for this annotation

Codecov / codecov/patch

pkg/ip/ip.go#L115

Added line #L115 was not covered by tests
}
}
}
}

for k := range marks {
ips = append(ips, net.ParseIP(k))
if sorted && len(result) > 1 {
sort.Slice(result, func(i, j int) bool {
return bytes.Compare(result[i].To16(), result[j].To16()) < 0
})

Check warning on line 124 in pkg/ip/ip.go

View check run for this annotation

Codecov / codecov/patch

pkg/ip/ip.go#L122-L124

Added lines #L122 - L124 were not covered by tests
}

if sorted {
sort.Slice(ips, func(i, j int) bool {
return bytes.Compare(ips[i].To16(), ips[j].To16()) < 0
})
return result
}

// FindAvailableIPs find available ip list in range
func FindAvailableIPs(ipRanges []string, ipList []net.IP, count int) []net.IP {
ipMap := make(map[[16]byte]struct{}, len(ipList))
for _, ip := range ipList {
if ip != nil {
ipMap[[16]byte(ip.To16())] = struct{}{}
}
}

return ips
var availableIPs []net.IP

for _, ipRange := range ipRanges {
if count == 0 {
break

Check warning on line 143 in pkg/ip/ip.go

View check run for this annotation

Codecov / codecov/patch

pkg/ip/ip.go#L143

Added line #L143 was not covered by tests
}

ips := strings.Split(ipRange, "-")
startIP := net.ParseIP(ips[0])
var endIP net.IP
if len(ips) == 2 {
endIP = net.ParseIP(ips[1])
} else {
endIP = startIP
}
if startIP == nil || endIP == nil {
continue

Check warning on line 155 in pkg/ip/ip.go

View check run for this annotation

Codecov / codecov/patch

pkg/ip/ip.go#L155

Added line #L155 was not covered by tests
}
if bytes.Compare(startIP, endIP) == 1 {
continue

Check warning on line 158 in pkg/ip/ip.go

View check run for this annotation

Codecov / codecov/patch

pkg/ip/ip.go#L158

Added line #L158 was not covered by tests
}

stop := nextIP(endIP)
for ip := startIP; !ip.Equal(stop) && count > 0; ip = nextIP(ip) {
if _, exists := ipMap[[16]byte(ip.To16())]; !exists {
availableIPs = append(availableIPs, ip)
count--
}
}
}

return availableIPs
}

func nextIP(ip net.IP) net.IP {
next := make(net.IP, len(ip))
copy(next, ip)

for i := len(next) - 1; i >= 0; i-- {
next[i]++
if next[i] != 0 {
break
}
}
return next
}

// IPsUnionSet calculates the union set of two IP address slices.
Expand Down
70 changes: 70 additions & 0 deletions pkg/ip/ip_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package ip_test

import (
"net"
"testing"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
Expand Down Expand Up @@ -383,3 +384,72 @@ var _ = Describe("IP", Label("ip_test"), func() {
})
})
})

func TestFindAvailableIPs(t *testing.T) {
tests := []struct {
name string
ipRanges []string
ipList []net.IP
count int
expected []net.IP
}{
{
name: "IPv4",
ipRanges: []string{"172.18.40.40"},
ipList: []net.IP{},
count: 1,
expected: []net.IP{net.ParseIP("172.18.40.40")},
},
{
name: "IPv4 range with some used IPs",
ipRanges: []string{"192.168.1.1-192.168.1.5"},
ipList: []net.IP{net.ParseIP("192.168.1.2"), net.ParseIP("192.168.1.4")},
count: 3,
expected: []net.IP{net.ParseIP("192.168.1.1"), net.ParseIP("192.168.1.3"), net.ParseIP("192.168.1.5")},
},
{
name: "IPv4 range with all IPs available",
ipRanges: []string{"10.0.0.1-10.0.0.3"},
ipList: []net.IP{},
count: 2,
expected: []net.IP{net.ParseIP("10.0.0.1"), net.ParseIP("10.0.0.2")},
},
{
name: "IPv6 range with some used IPs",
ipRanges: []string{"2001:db8::1-2001:db8::5"},
ipList: []net.IP{net.ParseIP("2001:db8::2"), net.ParseIP("2001:db8::4")},
count: 3,
expected: []net.IP{net.ParseIP("2001:db8::1"), net.ParseIP("2001:db8::3"), net.ParseIP("2001:db8::5")},
},
{
name: "IPv6 range with all IPs available",
ipRanges: []string{"2001:db8::1-2001:db8::3"},
ipList: []net.IP{},
count: 2,
expected: []net.IP{net.ParseIP("2001:db8::1"), net.ParseIP("2001:db8::2")},
},
{
name: "Mixed IPv4 and IPv6 ranges",
ipRanges: []string{"192.168.1.1-192.168.1.3", "2001:db8::1-2001:db8::3"},
ipList: []net.IP{net.ParseIP("192.168.1.2"), net.ParseIP("2001:db8::2")},
count: 4,
expected: []net.IP{net.ParseIP("192.168.1.1"), net.ParseIP("192.168.1.3"), net.ParseIP("2001:db8::1"), net.ParseIP("2001:db8::3")},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := spiderpoolip.FindAvailableIPs(tt.ipRanges, tt.ipList, tt.count)
if len(got) != len(tt.expected) {
t.Errorf("expected %v, got %v", tt.expected, got)
return
}
for i := range got {
if !got[i].Equal(tt.expected[i]) {
t.Errorf("expected %v, got %v", tt.expected, got)
break
}
}
})
}
}
4 changes: 2 additions & 2 deletions pkg/ippoolmanager/ippool_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,12 +173,12 @@ func (im *ipPoolManager) genRandomIP(ctx context.Context, ipPool *spiderpoolv2be
return nil, err
}

totalIPs, err := spiderpoolip.AssembleTotalIPs(*ipPool.Spec.IPVersion, ipPool.Spec.IPs, ipPool.Spec.ExcludeIPs)
unAvailableIPs, err := spiderpoolip.ParseIPRanges(*ipPool.Spec.IPVersion, ipPool.Spec.ExcludeIPs)
if err != nil {
return nil, err
}

availableIPs := spiderpoolip.IPsDiffSet(totalIPs, append(reservedIPs, usedIPs...), false)
availableIPs := spiderpoolip.FindAvailableIPs(ipPool.Spec.IPs, append(unAvailableIPs, append(reservedIPs, usedIPs...)...), 1)
if len(availableIPs) == 0 {
// traverse the usedIPs to find the previous allocated IPs if there be
// reference issue: https://github.com/spidernet-io/spiderpool/issues/2517
Expand Down
2 changes: 1 addition & 1 deletion pkg/subnetmanager/subnet_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ func (sm *subnetManager) preAllocateIPsFromSubnet(ctx context.Context, subnet *s
// TODO(Icarus9913): optimize it
// If we have difference sets, which means the subnet updated its status successfully in the last shrink operation but the next ippool update operation failed.
// In the situation, the ippool may allocate or release one of ips that subnet updated. So, we should correct the subnet status.
if len(spiderpoolip.IPsDiffSet(poolAllocatedIPs, subnetPoolIPs, false)) != 0 {
if spiderpoolip.IsDiffIPSet(poolAllocatedIPs, subnetPoolIPs) {

Check warning on line 262 in pkg/subnetmanager/subnet_manager.go

View check run for this annotation

Codecov / codecov/patch

pkg/subnetmanager/subnet_manager.go#L262

Added line #L262 was not covered by tests
log.Sugar().Warnf("the last whole auto-created pool scale operation interrupted, try to correct SpiderSubnet %s status %s IP allocations", subnet.Name, pool.Name)
poolTotalIPs, err := spiderpoolip.ParseIPRanges(ipVersion, pool.Spec.IPs)
if nil != err {
Expand Down
3 changes: 1 addition & 2 deletions pkg/subnetmanager/subnet_validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,8 +247,7 @@ func (sw *SubnetWebhook) validateOrphanIPPool(ctx context.Context, subnet *spide
if nil != err {
return field.InternalError(ipsField, fmt.Errorf("failed to assemble the total IP addresses of the Subnet '%s', error: %v", subnet.Name, err))
}
diffSet := spiderpoolip.IPsDiffSet(poolIPs, subnetIPs, false)
if len(diffSet) != 0 {
if spiderpoolip.IsDiffIPSet(poolIPs, subnetIPs) {
return field.Invalid(ipsField, subnet.Spec.IPs, fmt.Sprintf("SpiderIPPool '%s' owns some IP addresses that SpiderSubnet '%s' can't control", tmpPool.Name, subnet.Name))
}
}
Expand Down
3 changes: 1 addition & 2 deletions test/e2e/common/spidersubnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -378,8 +378,7 @@ LOOP:
return err
}

diffIps := ip.IPsDiffSet(ips1, ips2, false)
if diffIps != nil {
if ip.IsDiffIPSet(ips1, ips2) {
GinkgoWriter.Printf("inconsistent ip records in subnet %v/%v and pool %v/%v \n", subnetName, ips2, pool.Name, ips1)
continue LOOP
}
Expand Down

0 comments on commit c60cebb

Please sign in to comment.