Skip to content

Commit 3c08f86

Browse files
fix: [WIN-NPM] don't apply policy to new pod with same IP as an old pod (#1569)
* don't apply policy to IP's old pod owner * fix podkey/ip check * test case for pod key check * clarify error message * fix windows lint and have complete UT coverage of isIPAffiliated * comment out flaky UT for now
1 parent 75d8bee commit 3c08f86

File tree

7 files changed

+180
-115
lines changed

7 files changed

+180
-115
lines changed

npm/pkg/dataplane/dataplane-test-cases_windows_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,40 @@ func getAllSerialTests() []*SerialTestCase {
370370
},
371371
},
372372
},
373+
{
374+
Description: "Pod B replaces Pod A with same IP",
375+
Actions: []*Action{
376+
CreateEndpoint(endpoint1, ip1),
377+
CreatePod("x", "a", ip1, thisNode, map[string]string{"k1": "v1"}),
378+
ApplyDP(),
379+
DeleteEndpoint(endpoint1),
380+
CreateEndpoint(endpoint2, ip1),
381+
// in case CreatePod("x", "b", ...) is somehow processed before DeletePod("x", "a", ...)
382+
CreatePod("x", "b", ip1, thisNode, map[string]string{"k2": "v2"}),
383+
// policy should not be applied to x/b since ipset manager should not consider pod x/b part of k1:v1 selector ipsets
384+
UpdatePolicy(policyXBaseOnK1V1()),
385+
},
386+
TestCaseMetadata: &TestCaseMetadata{
387+
Tags: []Tag{
388+
podCrudTag,
389+
netpolCrudTag,
390+
},
391+
DpCfg: defaultWindowsDPCfg,
392+
InitialEndpoints: nil,
393+
ExpectedSetPolicies: []*hcn.SetPolicySetting{
394+
dptestutils.SetPolicy(emptySet),
395+
dptestutils.SetPolicy(allNamespaces, emptySet.GetHashedName(), nsXSet.GetHashedName()),
396+
dptestutils.SetPolicy(nsXSet, ip1),
397+
dptestutils.SetPolicy(podK1Set, ip1),
398+
dptestutils.SetPolicy(podK1V1Set, ip1),
399+
dptestutils.SetPolicy(podK2Set, ip1),
400+
dptestutils.SetPolicy(podK2V2Set, ip1),
401+
},
402+
ExpectedEnpdointACLs: map[string][]*hnswrapper.FakeEndpointPolicy{
403+
endpoint2: {},
404+
},
405+
},
406+
},
373407
}
374408
}
375409

npm/pkg/dataplane/dataplane_windows.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ func (dp *DataPlane) updatePod(pod *updateNPMPod) error {
219219
}
220220

221221
selectorIPSets := dp.getSelectorIPSets(policy)
222-
ok, err := dp.ipsetMgr.DoesIPSatisfySelectorIPSets(pod.PodIP, selectorIPSets)
222+
ok, err := dp.ipsetMgr.DoesIPSatisfySelectorIPSets(pod.PodIP, pod.PodKey, selectorIPSets)
223223
if err != nil {
224224
return err
225225
}
@@ -263,12 +263,21 @@ func (dp *DataPlane) getEndpointsToApplyPolicy(policy *policies.NPMNetworkPolicy
263263
defer dp.endpointCache.Unlock()
264264

265265
endpointList := make(map[string]string)
266-
for ip := range netpolSelectorIPs {
266+
for ip, podKey := range netpolSelectorIPs {
267267
endpoint, ok := dp.endpointCache.cache[ip]
268268
if !ok {
269269
klog.Infof("[DataPlane] Ignoring endpoint with IP %s since it was not found in the endpoint cache. This IP might not be in the HNS network", ip)
270270
continue
271271
}
272+
273+
if endpoint.podKey != podKey {
274+
// in case the pod controller hasn't updated the dp yet that the IP's pod owner has changed
275+
klog.Infof(
276+
"[DataPlane] ignoring endpoint with IP %s since the pod keys are different. podKey: [%s], endpoint: [%+v], endpoint stale pod key: [%+v]",
277+
ip, podKey, endpoint, endpoint.stalePodKey)
278+
continue
279+
}
280+
272281
endpointList[ip] = endpoint.id
273282
endpoint.netPolReference[policy.PolicyKey] = struct{}{}
274283
}

npm/pkg/dataplane/ipsets/ipset.go

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -388,24 +388,6 @@ func (set *IPSet) hasMember(memberName string) bool {
388388
return isMember
389389
}
390390

391-
// isIPAffiliated determines whether an IP belongs to the set or its member sets in the case of a list set.
392-
// This method and GetSetContents are good examples of how the ipset struct may have been better designed
393-
// as an interface with hash and list implementations. Not worth it to redesign though.
394-
func (set *IPSet) isIPAffiliated(ip string) bool {
395-
if set.Kind == HashSet {
396-
if _, ok := set.IPPodKey[ip]; ok {
397-
return true
398-
}
399-
}
400-
for _, memberSet := range set.MemberIPSets {
401-
_, ok := memberSet.IPPodKey[ip]
402-
if ok {
403-
return true
404-
}
405-
}
406-
return false
407-
}
408-
409391
func (set *IPSet) canSetBeSelectorIPSet() bool {
410392
return (set.Type == KeyLabelOfPod ||
411393
set.Type == KeyValueLabelOfPod ||
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package ipsets
2+
3+
// isIPAffiliated determines whether an PodIP belongs to the set or its member sets in the case of a list set.
4+
// This method and GetSetContents are good examples of how the ipset struct may have been better designed
5+
// as an interface with hash and list implementations. Not worth it to redesign though.
6+
func (set *IPSet) isIPAffiliated(ip, podKey string) bool {
7+
if set.Kind == HashSet {
8+
if key, ok := set.IPPodKey[ip]; ok && key == podKey {
9+
return true
10+
}
11+
}
12+
for _, memberSet := range set.MemberIPSets {
13+
if key, ok := memberSet.IPPodKey[ip]; ok && key == podKey {
14+
return true
15+
}
16+
}
17+
return false
18+
}

npm/pkg/dataplane/ipsets/ipsetmanager_windows.go

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ type networkPolicyBuilder struct {
2828
toDeleteSets map[string]*hcn.SetPolicySetting
2929
}
3030

31-
func (iMgr *IPSetManager) DoesIPSatisfySelectorIPSets(ip string, setList map[string]struct{}) (bool, error) {
31+
func (iMgr *IPSetManager) DoesIPSatisfySelectorIPSets(ip, podKey string, setList map[string]struct{}) (bool, error) {
3232
if len(setList) == 0 {
3333
klog.Infof("[ipset manager] unexpectedly encountered empty selector list")
3434
return true, nil
@@ -42,18 +42,19 @@ func (iMgr *IPSetManager) DoesIPSatisfySelectorIPSets(ip string, setList map[str
4242

4343
for setName := range setList {
4444
set := iMgr.setMap[setName]
45-
if !set.isIPAffiliated(ip) {
45+
if !set.isIPAffiliated(ip, podKey) {
4646
return false, nil
4747
}
4848
}
4949

5050
return true, nil
5151
}
5252

53-
// GetIPsFromSelectorIPSets will take in a map of prefixedSetNames and return an intersection of IPs
54-
func (iMgr *IPSetManager) GetIPsFromSelectorIPSets(setList map[string]struct{}) (map[string]struct{}, error) {
53+
// GetIPsFromSelectorIPSets will take in a map of prefixedSetNames and return an intersection of IPs mapped to pod key
54+
func (iMgr *IPSetManager) GetIPsFromSelectorIPSets(setList map[string]struct{}) (map[string]string, error) {
55+
ips := make(map[string]string)
5556
if len(setList) == 0 {
56-
return map[string]struct{}{}, nil
57+
return ips, nil
5758
}
5859
iMgr.Lock()
5960
defer iMgr.Unlock()
@@ -67,30 +68,29 @@ func (iMgr *IPSetManager) GetIPsFromSelectorIPSets(setList map[string]struct{})
6768
// which is a hash set, and we favor hash sets for firstSet
6869
var firstSet *IPSet
6970
for setName := range setList {
70-
set := iMgr.setMap[setName]
71-
if set.Kind == HashSet || firstSet == nil {
71+
firstSet = iMgr.setMap[setName]
72+
if firstSet.Kind == HashSet {
7273
// firstSet can be any set, but ideally is a hash set for efficiency (compare the branch for hash sets to the one for lists below)
73-
firstSet = set
74+
break
7475
}
7576
}
76-
ips := make(map[string]struct{})
7777
if firstSet.Kind == HashSet {
7878
// include every IP in firstSet that is also affiliated with every other selector set
79-
for ip := range firstSet.IPPodKey {
79+
for ip, podKey := range firstSet.IPPodKey {
8080
isAffiliated := true
8181
for otherSetName := range setList {
8282
if otherSetName == firstSet.Name {
8383
continue
8484
}
8585
otherSet := iMgr.setMap[otherSetName]
86-
if !otherSet.isIPAffiliated(ip) {
86+
if !otherSet.isIPAffiliated(ip, podKey) {
8787
isAffiliated = false
8888
break
8989
}
9090
}
9191

9292
if isAffiliated {
93-
ips[ip] = struct{}{}
93+
ips[ip] = podKey
9494
}
9595
}
9696
} else {
@@ -100,19 +100,27 @@ func (iMgr *IPSetManager) GetIPsFromSelectorIPSets(setList map[string]struct{})
100100

101101
// only loop over the unique affiliated IPs
102102
for _, memberSet := range firstSet.MemberIPSets {
103-
for ip := range memberSet.IPPodKey {
104-
ips[ip] = struct{}{}
103+
for ip, podKey := range memberSet.IPPodKey {
104+
if oldKey, ok := ips[ip]; ok && oldKey != podKey {
105+
// this could lead to unintentionally considering this Pod (Pod B) to be part of the selector set if:
106+
// 1. Pod B has the same IP as a previous Pod A
107+
// 2. Pod B create is somehow processed before Pod A delete
108+
// 3. This method is called before Pod A delete
109+
// again, this
110+
klog.Warningf("[GetIPsFromSelectorIPSets] IP currently associated with two different pod keys. to ensure no issues occur with network policies, restart this ip: %s", ip)
111+
}
112+
ips[ip] = podKey
105113
}
106114
}
107-
for ip := range ips {
115+
for ip, podKey := range ips {
108116
// identical to the hash set case
109117
isAffiliated := true
110118
for otherSetName := range setList {
111119
if otherSetName == firstSet.Name {
112120
continue
113121
}
114122
otherSet := iMgr.setMap[otherSetName]
115-
if !otherSet.isIPAffiliated(ip) {
123+
if !otherSet.isIPAffiliated(ip, podKey) {
116124
isAffiliated = false
117125
break
118126
}

0 commit comments

Comments
 (0)