Skip to content

Commit 9fed90c

Browse files
committed
e2e: Use ovnk allocator and reserve IPs
This change replace the customa llocator with the ovnk allocator and also reserver the cluster IPs so test can just ask for another IP without the problem of cluster ip collision. Signed-off-by: Enrique Llorente <[email protected]>
1 parent f1c76a6 commit 9fed90c

File tree

3 files changed

+65
-238
lines changed

3 files changed

+65
-238
lines changed

test/e2e/ipalloc/ipalloc.go

Lines changed: 0 additions & 47 deletions
This file was deleted.

test/e2e/ipalloc/primaryipalloc.go

Lines changed: 33 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,20 @@ package ipalloc
33
import (
44
"context"
55
"fmt"
6+
"net"
7+
"sync"
8+
9+
ipallocator "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/allocator/ip"
610
"github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util"
7-
corev1 "k8s.io/api/core/v1"
811
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
912
v1 "k8s.io/client-go/kubernetes/typed/core/v1"
10-
"net"
11-
"sync"
1213
)
1314

1415
// primaryIPAllocator attempts to allocate an IP in the same subnet as a nodes primary network
1516
type primaryIPAllocator struct {
1617
mu *sync.Mutex
17-
v4 *ipAllocator
18-
v6 *ipAllocator
18+
v4 *ipallocator.Range
19+
v6 *ipallocator.Range
1920
nodeClient v1.NodeInterface
2021
}
2122

@@ -47,91 +48,37 @@ func newPrimaryIPAllocator(nodeClient v1.NodeInterface) (*primaryIPAllocator, er
4748
if len(nodes.Items) == 0 {
4849
return ipa, fmt.Errorf("expected at least one node but found zero")
4950
}
50-
// FIXME: the approach taken here to find the first node IP+mask and then to increment the second last octet wont work in
51-
// all scenarios (node with /24). We should generate an EgressIP compatible with a Node providers primary network and then take care its unique globally.
5251

53-
// The approach here is to grab initial starting IP from first node found, increment the second last octet.
54-
// Approach taken here won't work for Nodes handed /24 subnets.
55-
nodePrimaryIPs, err := util.ParseNodePrimaryIfAddr(&nodes.Items[0])
56-
if err != nil {
57-
return ipa, fmt.Errorf("failed to parse node primary interface address from Node object: %v", err)
58-
}
59-
if nodePrimaryIPs.V4.IP != nil {
60-
// should be ok with /16 and /64 node primary provider subnets
61-
// TODO; fixme; what about /24 subnet Nodes like GCP
62-
nodePrimaryIPs.V4.IP[len(nodePrimaryIPs.V4.IP)-2]++
63-
ipa.v4 = newIPAllocator(&net.IPNet{IP: nodePrimaryIPs.V4.IP, Mask: nodePrimaryIPs.V4.Net.Mask})
64-
}
65-
if nodePrimaryIPs.V6.IP != nil {
66-
nodePrimaryIPs.V6.IP[len(nodePrimaryIPs.V6.IP)-2]++
67-
ipa.v6 = newIPAllocator(&net.IPNet{IP: nodePrimaryIPs.V6.IP, Mask: nodePrimaryIPs.V6.Net.Mask})
68-
}
69-
// verify the new starting base IP is within all Nodes subnets
70-
if nodePrimaryIPs.V4.IP != nil {
71-
ipNets, err := getNodePrimaryProviderIPs(nodes.Items, false)
72-
if err != nil {
73-
return ipa, err
74-
}
75-
nextIP, err := ipa.v4.AllocateNextIP()
76-
if err != nil {
77-
return ipa, err
78-
}
79-
if !isIPWithinAllSubnets(ipNets, nextIP) {
80-
return ipa, fmt.Errorf("IP %s is not within all Node subnets", nextIP)
81-
}
82-
}
83-
if nodePrimaryIPs.V6.IP != nil {
84-
ipNets, err := getNodePrimaryProviderIPs(nodes.Items, true)
85-
if err != nil {
86-
return ipa, err
87-
}
88-
nextIP, err := ipa.v6.AllocateNextIP()
89-
if err != nil {
90-
return ipa, err
91-
}
92-
if !isIPWithinAllSubnets(ipNets, nextIP) {
93-
return ipa, fmt.Errorf("IP %s is not within all Node subnets", nextIP)
94-
}
95-
}
96-
97-
return ipa, nil
98-
}
99-
100-
func getNodePrimaryProviderIPs(nodes []corev1.Node, isIPv6 bool) ([]*net.IPNet, error) {
101-
ipNets := make([]*net.IPNet, 0, len(nodes))
102-
for _, node := range nodes {
52+
for _, node := range nodes.Items {
10353
nodePrimaryIPs, err := util.ParseNodePrimaryIfAddr(&node)
10454
if err != nil {
105-
return nil, fmt.Errorf("failed to parse node primary interface address from Node %s object: %v", node.Name, err)
55+
return ipa, fmt.Errorf("failed to parse node primary interface address from Node %s object: %v", node.Name, err)
56+
}
57+
if nodePrimaryIPs.V4.IP != nil {
58+
if ipa.v4 == nil {
59+
ipa.v4, err = ipallocator.NewCIDRRange(nodePrimaryIPs.V4.Net)
60+
if err != nil {
61+
return ipa, fmt.Errorf("failed to create new CIDR range for IPv4: %v", err)
62+
}
63+
}
64+
if err := ipa.v4.Allocate(nodePrimaryIPs.V4.IP); err != nil {
65+
return ipa, fmt.Errorf("failed to allocate IPv4 %s: %v", nodePrimaryIPs.V4.IP, err)
66+
}
67+
}
68+
if nodePrimaryIPs.V6.IP != nil {
69+
if ipa.v6 == nil {
70+
ipa.v6, err = ipallocator.NewCIDRRange(nodePrimaryIPs.V6.Net)
71+
if err != nil {
72+
return ipa, fmt.Errorf("failed to create new CIDR range for IPv6: %v", err)
73+
}
74+
}
75+
if err := ipa.v6.Allocate(nodePrimaryIPs.V6.IP); err != nil {
76+
return ipa, fmt.Errorf("failed to allocate IPv6 %s: %v", nodePrimaryIPs.V6.IP, err)
77+
}
10678
}
107-
var mask net.IPMask
108-
var ip net.IP
10979

110-
if isIPv6 {
111-
ip = nodePrimaryIPs.V6.IP
112-
mask = nodePrimaryIPs.V6.Net.Mask
113-
} else {
114-
ip = nodePrimaryIPs.V4.IP
115-
mask = nodePrimaryIPs.V4.Net.Mask
116-
}
117-
if len(ip) == 0 || len(mask) == 0 {
118-
return nil, fmt.Errorf("failed to find Node %s primary Node IP and/or mask", node.Name)
119-
}
120-
ipNets = append(ipNets, &net.IPNet{IP: ip, Mask: mask})
12180
}
122-
return ipNets, nil
123-
}
124-
125-
func isIPWithinAllSubnets(ipNets []*net.IPNet, ip net.IP) bool {
126-
if len(ipNets) == 0 {
127-
return false
128-
}
129-
for _, ipNet := range ipNets {
130-
if !ipNet.Contains(ip) {
131-
return false
132-
}
133-
}
134-
return true
81+
return ipa, nil
13582
}
13683

13784
func (pia *primaryIPAllocator) IncrementAndGetNextV4(times int) (net.IP, error) {
@@ -148,12 +95,9 @@ func (pia *primaryIPAllocator) AllocateNextV4() (net.IP, error) {
14895
if pia.v4 == nil {
14996
return nil, fmt.Errorf("IPv4 is not enable ")
15097
}
151-
if pia.v4.net == nil {
152-
return nil, fmt.Errorf("IPv4 is not enabled but Allocation request was called")
153-
}
15498
pia.mu.Lock()
15599
defer pia.mu.Unlock()
156-
return allocateIP(pia.nodeClient, pia.v4.AllocateNextIP)
100+
return pia.v4.AllocateNext()
157101
}
158102

159103
func (pia *primaryIPAllocator) IncrementAndGetNextV6(times int) (net.IP, error) {
@@ -170,51 +114,7 @@ func (pia primaryIPAllocator) AllocateNextV6() (net.IP, error) {
170114
if pia.v6 == nil {
171115
return nil, fmt.Errorf("IPv6 is not enabled but Allocation request was called")
172116
}
173-
if pia.v6.net == nil {
174-
return nil, fmt.Errorf("ipv6 network is not set")
175-
}
176117
pia.mu.Lock()
177118
defer pia.mu.Unlock()
178-
return allocateIP(pia.nodeClient, pia.v6.AllocateNextIP)
179-
}
180-
181-
type allocNextFn func() (net.IP, error)
182-
183-
func allocateIP(nodeClient v1.NodeInterface, allocateFn allocNextFn) (net.IP, error) {
184-
nodeList, err := nodeClient.List(context.TODO(), metav1.ListOptions{})
185-
if err != nil {
186-
return nil, fmt.Errorf("failed to list nodes: %v", err)
187-
}
188-
for {
189-
nextIP, err := allocateFn()
190-
if err != nil {
191-
return nil, fmt.Errorf("failed to allocated next IP address: %v", err)
192-
}
193-
firstOctet := nextIP[len(nextIP)-1]
194-
// skip 0 and 1
195-
if firstOctet == 0 || firstOctet == 1 {
196-
continue
197-
}
198-
isConflict, err := isConflictWithExistingHostIPs(nodeList.Items, nextIP)
199-
if err != nil {
200-
return nil, fmt.Errorf("failed to determine if IP conflicts with existing IPs: %v", err)
201-
}
202-
if !isConflict {
203-
return nextIP, nil
204-
}
205-
}
206-
}
207-
208-
func isConflictWithExistingHostIPs(nodes []corev1.Node, ip net.IP) (bool, error) {
209-
ipStr := ip.String()
210-
for _, node := range nodes {
211-
nodeIPsSet, err := util.ParseNodeHostCIDRsDropNetMask(&node)
212-
if err != nil {
213-
return false, fmt.Errorf("failed to parse node %s primary annotation info: %v", node.Name, err)
214-
}
215-
if nodeIPsSet.Has(ipStr) {
216-
return true, nil
217-
}
218-
}
219-
return false, nil
119+
return pia.v6.AllocateNext()
220120
}

test/e2e/ipalloc/primaryipalloc_test.go

Lines changed: 32 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -15,48 +15,13 @@ import (
1515
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1616
"k8s.io/apimachinery/pkg/runtime"
1717
"k8s.io/client-go/kubernetes/fake"
18-
utilsnet "k8s.io/utils/net"
1918
)
2019

2120
func TestUtilSuite(t *testing.T) {
2221
gomega.RegisterFailHandler(ginkgo.Fail)
2322
ginkgo.RunSpecs(t, "node ip alloc suite")
2423
}
2524

26-
func TestAllocateNext(t *testing.T) {
27-
tests := []struct {
28-
desc string
29-
input *net.IPNet
30-
output []net.IP
31-
}{
32-
{
33-
desc: "increments IPv4 address",
34-
input: mustParseCIDRIncIP("192.168.1.5/16"), // mask /24 would fail
35-
output: []net.IP{net.ParseIP("192.168.1.6"), net.ParseIP("192.168.1.7"), net.ParseIP("192.168.1.8")},
36-
},
37-
{
38-
desc: "increments IPv6 address",
39-
input: mustParseCIDRIncIP("fc00:f853:ccd:e793::6/64"),
40-
output: []net.IP{net.ParseIP("fc00:f853:ccd:e793::7"), net.ParseIP("fc00:f853:ccd:e793::8"), net.ParseIP("fc00:f853:ccd:e793::9")},
41-
},
42-
}
43-
44-
for i, tc := range tests {
45-
t.Run(fmt.Sprintf("%d:%s", i, tc.desc), func(t *testing.T) {
46-
nodeIPAlloc := newIPAllocator(tc.input)
47-
for _, expectedIP := range tc.output {
48-
allocatedIP, err := nodeIPAlloc.AllocateNextIP()
49-
if err != nil {
50-
t.Errorf("failed to allocated next IP: %v", err)
51-
}
52-
if !allocatedIP.Equal(expectedIP) {
53-
t.Errorf("Expected IP %q, but got %q", expectedIP.String(), allocatedIP.String())
54-
}
55-
}
56-
})
57-
}
58-
}
59-
6025
// mustParseCIDRIncIP parses the IP and CIDR. It adds the IP to the returned IPNet.
6126
func mustParseCIDRIncIP(cidr string) *net.IPNet {
6227
ip, ipNet, err := net.ParseCIDR(cidr)
@@ -78,20 +43,19 @@ type node struct {
7843
}
7944

8045
func TestIPAlloc(t *testing.T) {
46+
g := gomega.NewWithT(t)
47+
8148
tests := []struct {
82-
desc string
83-
existingPrimaryNodeIPs []node
84-
expectedFromAllocateNext []string
49+
desc string
50+
existingPrimaryNodeIPs []node
8551
}{
8652
{
87-
desc: "IPv4",
88-
existingPrimaryNodeIPs: []node{{v4: network{ip: "192.168.1.1", mask: "16"}}, {v4: network{ip: "192.168.1.2", mask: "16"}}},
89-
expectedFromAllocateNext: []string{"192.168.2.3", "192.168.2.4"},
53+
desc: "IPv4",
54+
existingPrimaryNodeIPs: []node{{v4: network{ip: "192.168.1.1", mask: "16"}}, {v4: network{ip: "192.168.1.2", mask: "16"}}},
9055
},
9156
{
92-
desc: "IPv6",
93-
existingPrimaryNodeIPs: []node{{v4: network{ip: "fc00:f853:ccd:e793::5", mask: "64"}}, {v4: network{ip: "fc00:f853:ccd:e793::6", mask: "64"}}},
94-
expectedFromAllocateNext: []string{"fc00:f853:ccd:e793::8", "fc00:f853:ccd:e793::9"},
57+
desc: "IPv6",
58+
existingPrimaryNodeIPs: []node{{v6: network{ip: "fc00:f853:ccd:e793::5", mask: "64"}}, {v6: network{ip: "fc00:f853:ccd:e793::6", mask: "64"}}},
9559
},
9660
}
9761

@@ -103,23 +67,33 @@ func TestIPAlloc(t *testing.T) {
10367
t.Errorf(err.Error())
10468
return
10569
}
106-
for _, expectedIPStr := range tc.expectedFromAllocateNext {
107-
expectedIP := net.ParseIP(expectedIPStr)
108-
var nextIP net.IP
109-
var err error
110-
if utilsnet.IsIPv6(expectedIP) {
111-
nextIP, err = pipa.AllocateNextV6()
112-
} else {
113-
nextIP, err = pipa.AllocateNextV4()
114-
}
115-
if err != nil || nextIP == nil {
116-
t.Errorf("failed to allocated next IPv4 or IPv6 address. err %v", err)
117-
return
70+
existingIPv4IPs := []string{}
71+
existingIPv6IPs := []string{}
72+
allocatedIPv4IPs := []string{}
73+
allocatedIPv6IPs := []string{}
74+
for _, existingPrimaryNodeIP := range tc.existingPrimaryNodeIPs {
75+
if existingPrimaryNodeIP.v4.ip != "" {
76+
existingIPv4IPs = append(existingIPv4IPs, existingPrimaryNodeIP.v4.ip)
77+
nextIPv4, err := pipa.AllocateNextV4()
78+
g.Expect(err).ToNot(gomega.HaveOccurred(), "should succeed in allocating the next IPv4 address")
79+
g.Expect(nextIPv4).ToNot(gomega.BeNil(), "should allocate next IPv4 address")
80+
allocatedIPv4IPs = append(allocatedIPv4IPs, nextIPv4.String())
11881
}
119-
if !nextIP.Equal(expectedIP) {
120-
t.Errorf("expected IP %q, but found %q", expectedIP, nextIP)
82+
83+
if existingPrimaryNodeIP.v6.ip != "" {
84+
existingIPv6IPs = append(existingIPv6IPs, existingPrimaryNodeIP.v6.ip)
85+
nextIPv6, err := pipa.AllocateNextV6()
86+
g.Expect(err).ToNot(gomega.HaveOccurred(), "should succeed in allocating the next IPv6 address")
87+
g.Expect(nextIPv6).ToNot(gomega.BeNil(), "should allocate next IPv6 address")
88+
allocatedIPv6IPs = append(allocatedIPv6IPs, nextIPv6.String())
12189
}
12290
}
91+
if len(existingIPv4IPs) > 0 {
92+
g.Expect(allocatedIPv4IPs).NotTo(gomega.ContainElements(existingIPv4IPs))
93+
}
94+
if len(existingIPv6IPs) > 0 {
95+
g.Expect(allocatedIPv6IPs).NotTo(gomega.ContainElements(existingIPv6IPs))
96+
}
12397
})
12498
}
12599

0 commit comments

Comments
 (0)