Skip to content

Commit 8baff5a

Browse files
committed
pkg ipamd: add unit test for secondary ENI exclusion
1 parent 9204ac6 commit 8baff5a

File tree

1 file changed

+183
-0
lines changed

1 file changed

+183
-0
lines changed
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"). You may
4+
// not use this file except in compliance with the License. A copy of the
5+
// License is located at
6+
//
7+
// http://aws.amazon.com/apache2.0/
8+
//
9+
// or in the "license" file accompanying this file. This file is distributed
10+
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
// express or implied. See the License for the specific language governing
12+
// permissions and limitations under the License.
13+
14+
package datastore
15+
16+
import (
17+
"net"
18+
"strings"
19+
"testing"
20+
"time"
21+
22+
"github.com/stretchr/testify/assert"
23+
)
24+
25+
// TestSecondaryENIExclusionFocused tests the exact scenarios mentioned:
26+
// 1. When secondary ENI is excluded, new pods should avoid it
27+
// 2. When all non-excluded ENIs are full, allocation should fail appropriately
28+
func TestSecondaryENIExclusionFocused(t *testing.T) {
29+
ds := NewDataStore(Testlog, NullCheckpoint{}, false, defaultNetworkCard)
30+
31+
// Setup: Primary ENI with one IP
32+
err := ds.AddENI("eni-primary", 0, true, false, false)
33+
assert.NoError(t, err)
34+
35+
primaryIP := net.IPNet{IP: net.ParseIP("10.0.1.1"), Mask: net.IPv4Mask(255, 255, 255, 255)}
36+
err = ds.AddIPv4CidrToStore("eni-primary", primaryIP, false)
37+
assert.NoError(t, err)
38+
39+
// Setup: Secondary ENI with one IP
40+
err = ds.AddENI("eni-secondary", 1, false, false, false)
41+
assert.NoError(t, err)
42+
43+
secondaryIP := net.IPNet{IP: net.ParseIP("10.0.2.1"), Mask: net.IPv4Mask(255, 255, 255, 255)}
44+
err = ds.AddIPv4CidrToStore("eni-secondary", secondaryIP, false)
45+
assert.NoError(t, err)
46+
47+
// Test 1: Before exclusion - verify we have 2 allocatable ENIs
48+
allocatableENIs := ds.GetAllocatableENIs(10, false) // maxIPperENI=10, skipPrimary=false
49+
assert.Equal(t, 2, len(allocatableENIs), "Should have 2 allocatable ENIs before exclusion")
50+
51+
// Test 2: Exclude secondary ENI
52+
err = ds.SetENIExcludedForPodIPs("eni-secondary", true)
53+
assert.NoError(t, err)
54+
assert.True(t, ds.eniPool["eni-secondary"].IsExcludedForPodIPs)
55+
56+
// Test 3: After exclusion - verify only 1 allocatable ENI remains
57+
allocatableENIs = ds.GetAllocatableENIs(10, false)
58+
assert.Equal(t, 1, len(allocatableENIs), "Should have only 1 allocatable ENI after exclusion")
59+
assert.Equal(t, "eni-primary", allocatableENIs[0].ID)
60+
61+
// Test 4: Assign first pod - should work and go to primary ENI
62+
key1 := IPAMKey{"net0", "pod-1", "eth0"}
63+
ip1, _, err := ds.AssignPodIPv4Address(key1, IPAMMetadata{K8SPodNamespace: "default", K8SPodName: "pod-1"})
64+
assert.NoError(t, err)
65+
assert.True(t, strings.HasPrefix(ip1, "10.0.1."), "First pod should go to primary ENI, got: %s", ip1)
66+
67+
// Test 5: Primary ENI should now be full
68+
assert.Equal(t, 1, ds.eniPool["eni-primary"].AssignedIPv4Addresses())
69+
assert.Equal(t, 0, ds.eniPool["eni-secondary"].AssignedIPv4Addresses())
70+
71+
// Test 6: Try to assign second pod - should fail since primary is full and secondary is excluded
72+
key2 := IPAMKey{"net0", "pod-2", "eth0"}
73+
_, _, err = ds.AssignPodIPv4Address(key2, IPAMMetadata{K8SPodNamespace: "default", K8SPodName: "pod-2"})
74+
assert.Error(t, err, "Should fail when only non-excluded ENI is full")
75+
assert.Contains(t, err.Error(), "no available IP/Prefix addresses")
76+
77+
// Test 7: Verify excluded secondary ENI should be deletable (it has no pods)
78+
ds.eniPool["eni-secondary"].createTime = time.Time{} // Make it old enough
79+
80+
// Make sure IPs are out of cooldown
81+
for _, cidr := range ds.eniPool["eni-secondary"].AvailableIPv4Cidrs {
82+
for _, addr := range cidr.IPAddresses {
83+
addr.UnassignedTime = time.Time{}
84+
}
85+
}
86+
87+
removableENI := ds.RemoveUnusedENIFromStore(0, 0, 0)
88+
assert.Equal(t, "eni-secondary", removableENI, "Excluded secondary ENI with no pods should be deletable")
89+
90+
// Test 8: Verify secondary ENI was removed from pool
91+
_, exists := ds.eniPool["eni-secondary"]
92+
assert.False(t, exists, "Secondary ENI should be removed from pool")
93+
94+
// Test 9: Primary ENI should remain unaffected
95+
_, exists = ds.eniPool["eni-primary"]
96+
assert.True(t, exists, "Primary ENI should remain in pool")
97+
assert.Equal(t, 1, ds.eniPool["eni-primary"].AssignedIPv4Addresses())
98+
99+
// Test 10: Verify allocatable ENIs after secondary removal
100+
allocatableENIs = ds.GetAllocatableENIs(10, false)
101+
assert.Equal(t, 1, len(allocatableENIs), "Should still have 1 allocatable ENI")
102+
assert.Equal(t, "eni-primary", allocatableENIs[0].ID)
103+
}
104+
105+
// TestSecondaryENIExclusionWithPods tests exclusion when secondary ENI has existing pods
106+
func TestSecondaryENIExclusionWithPods(t *testing.T) {
107+
ds := NewDataStore(Testlog, NullCheckpoint{}, false, defaultNetworkCard)
108+
109+
// Setup ENIs
110+
err := ds.AddENI("eni-primary", 0, true, false, false)
111+
assert.NoError(t, err)
112+
113+
err = ds.AddENI("eni-secondary", 1, false, false, false)
114+
assert.NoError(t, err)
115+
116+
// Add IPs
117+
primaryIP := net.IPNet{IP: net.ParseIP("10.0.1.1"), Mask: net.IPv4Mask(255, 255, 255, 255)}
118+
err = ds.AddIPv4CidrToStore("eni-primary", primaryIP, false)
119+
assert.NoError(t, err)
120+
121+
secondaryIP := net.IPNet{IP: net.ParseIP("10.0.2.1"), Mask: net.IPv4Mask(255, 255, 255, 255)}
122+
err = ds.AddIPv4CidrToStore("eni-secondary", secondaryIP, false)
123+
assert.NoError(t, err)
124+
125+
// Assign pods to both ENIs naturally through regular allocation
126+
key1 := IPAMKey{"net0", "pod-1", "eth0"}
127+
ip1, _, err := ds.AssignPodIPv4Address(key1, IPAMMetadata{K8SPodNamespace: "default", K8SPodName: "pod-1"})
128+
assert.NoError(t, err)
129+
130+
key2 := IPAMKey{"net0", "pod-2", "eth0"}
131+
ip2, _, err := ds.AssignPodIPv4Address(key2, IPAMMetadata{K8SPodNamespace: "default", K8SPodName: "pod-2"})
132+
assert.NoError(t, err)
133+
134+
// Find which key corresponds to secondary ENI assignment
135+
var keyOnSecondary IPAMKey
136+
if strings.HasPrefix(ip1, "10.0.2.") {
137+
keyOnSecondary = key1
138+
} else if strings.HasPrefix(ip2, "10.0.2.") {
139+
keyOnSecondary = key2
140+
} else {
141+
t.Fatal("No pod was assigned to secondary ENI")
142+
}
143+
144+
// Verify we have assignments on both ENIs
145+
totalPods := ds.eniPool["eni-primary"].AssignedIPv4Addresses() + ds.eniPool["eni-secondary"].AssignedIPv4Addresses()
146+
assert.Equal(t, 2, totalPods)
147+
assert.Greater(t, ds.eniPool["eni-secondary"].AssignedIPv4Addresses(), 0)
148+
149+
// Now exclude secondary ENI
150+
err = ds.SetENIExcludedForPodIPs("eni-secondary", true)
151+
assert.NoError(t, err)
152+
153+
// Test: Excluded ENI with pods should NOT be deletable
154+
ds.eniPool["eni-secondary"].createTime = time.Time{} // Make old enough
155+
removableENI := ds.RemoveUnusedENIFromStore(0, 0, 0)
156+
assert.Equal(t, "", removableENI, "Excluded ENI with pods should not be deletable")
157+
158+
// Test: New allocations should skip excluded ENI (if primary has space)
159+
// We'll add another IP to primary to allow new allocation
160+
primaryIP2 := net.IPNet{IP: net.ParseIP("10.0.1.2"), Mask: net.IPv4Mask(255, 255, 255, 255)}
161+
err = ds.AddIPv4CidrToStore("eni-primary", primaryIP2, false)
162+
assert.NoError(t, err)
163+
164+
key3 := IPAMKey{"net0", "new-pod", "eth0"}
165+
ip3, _, err := ds.AssignPodIPv4Address(key3, IPAMMetadata{K8SPodNamespace: "default", K8SPodName: "new-pod"})
166+
assert.NoError(t, err)
167+
assert.True(t, strings.HasPrefix(ip3, "10.0.1."), "New pod should avoid excluded secondary ENI, got: %s", ip3)
168+
169+
// Test: After removing pod from excluded ENI, it should become deletable
170+
_, _, _, _, err = ds.UnassignPodIPAddress(keyOnSecondary)
171+
assert.NoError(t, err)
172+
173+
// Clear cooldown
174+
for _, cidr := range ds.eniPool["eni-secondary"].AvailableIPv4Cidrs {
175+
for _, addr := range cidr.IPAddresses {
176+
addr.UnassignedTime = time.Time{}
177+
}
178+
}
179+
180+
// Now it should be deletable
181+
removableENI = ds.RemoveUnusedENIFromStore(0, 0, 0)
182+
assert.Equal(t, "eni-secondary", removableENI, "Excluded ENI should be deletable after pod removal")
183+
}

0 commit comments

Comments
 (0)