-
Notifications
You must be signed in to change notification settings - Fork 387
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use target port of Service in NodePortLocal to configure Pod reachability #2222
Conversation
Codecov Report
@@ Coverage Diff @@
## main #2222 +/- ##
=======================================
Coverage 62.05% 62.05%
=======================================
Files 277 277
Lines 21588 21615 +27
=======================================
+ Hits 13396 13414 +18
- Misses 6792 6800 +8
- Partials 1400 1401 +1
Flags with carried forward coverage won't be shown. Click here to find out more.
|
99a684e
to
e644083
Compare
e644083
to
db74963
Compare
@@ -282,11 +289,13 @@ func (c *NPLController) isNPLEnabledForServiceOfPod(obj interface{}) bool { | |||
if isSvc && svc.Spec.Type != corev1.ServiceTypeNodePort { | |||
if pod.Namespace == svc.Namespace && | |||
matchSvcSelectorPodLabels(svc.Spec.Selector, pod.GetLabels()) { | |||
return true | |||
for _, port := range svc.Spec.Ports { | |||
targetPorts.Insert(int(port.TargetPort.IntVal)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TargetPort
can be a string (named port in the target Pod's container). If we don't want to support this case yet, it's fine, but we should check for that case and log an error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but it seems that we should be able to support named ports here, by returning both a list of numerical ports and a list of named ports (strings), and letting handleAddUpdatePod
do the resolution from port name to port number.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added support for named target port
} | ||
if int(cport.HostPort) > 0 { | ||
klog.V(4).Infof("Host Port is defined for Container %s in Pod %s, thus extra NPL port is not allocated", container.Name, key) | ||
hostPorts[int(cport.ContainerPort)] = int(cport.HostPort) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it feels like using ContainerPort
as the key is not sufficient, we need ContainerPort
+ Protocol
?
it could be interesting to have a test case with 2 container ports, one for UDP and one for TCP but using the same container port number, and have them both exposed as host ports
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added protocol type in the key too.
I didn't understand the test case with two container ports.
I have added a few for test cases. Let me know if those covers that case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant a test with a Pod like this one:
testPod.Spec.Containers[0].Ports = append(
testPod.Spec.Containers[0].Ports,
corev1.ContainerPort{ContainerPort: int32(defaultPort), HostPort: int32(hostPort), Protocol: corev1.ProtocolTCP},
)
testPod.Spec.Containers[0].Ports = append(
testPod.Spec.Containers[0].Ports,
corev1.ContainerPort{ContainerPort: int32(defaultPort), HostPort: int32(hostPort), Protocol: corev1.ProtocolUDP},
)
with a Service using both of these as target ports
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@monotosh-avi ping on this, what do you think of adding this test?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@antoninbas I had missed this. Added a test case now.
// TestPodAddMultiPort creates a Pod with multiple ports and verifies that the Pod's NPL annotation | ||
// and the local port table are updated correctly. | ||
// and the local port table are updated with only one port used as target port of the Service. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that kind of defeats the original purpose of the test, now that we install forwarding rules for a single port...
maybe we should have a different test case to validate that only ports used as Service target ports are exposed with NPL
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. The original test case has been modified.
Now we are validating that only ports used as Service target ports are exposed with NPL. For annotation, this was already being done. Added couple of more checks to ensure no NPL rules are added for pod container ports.
test/e2e/nodeportlocal_test.go
Outdated
_, _, err := data.runCommandFromPod(antreaNamespace, antreaPod, agentContainerName, cmd) | ||
if err != nil { | ||
t.Logf("Error while deleting rule from iptables: %v", err) | ||
// Retry, as sometimes error can occur due to concurrent operations on iptables. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need this, I added -w 10
to the iptables delete command already to accommodate for this issue
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed
db74963
to
7a4b5de
Compare
if len(targetPortsInt) == 0 { | ||
if err := c.deleteAllPortRulesIfAny(podIP); err != nil { | ||
return err | ||
} | ||
if _, exists := pod.Annotations[NPLAnnotationKey]; exists { | ||
return c.cleanupNPLAnnotationForPod(pod) | ||
} | ||
return nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be better to keep this part early in the function and use if len(targetPortsInt) == 0 && len(targetsPortStr)
as the if check
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had thought about this, but in that case we would miss the condition where targetsPortStr is not empty but no matching port name is found in the container ports.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sounds good, but maybe a comment to that effect would avoid confusion in the future
do we have a unit test for that edge case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure. Added a comment.
In the test case TestTargetPortWithName, I have added a check to make sure updating Service with wrong target port ensures deletion of rules and annotations from the Pod.
portProtoStr := cport.Name + delim + string(cport.Protocol) | ||
portProtoInt := fmt.Sprint(cport.ContainerPort) + delim + string(cport.Protocol) | ||
if int(cport.HostPort) > 0 { | ||
klog.V(4).Infof("Host Port is defined for Container %s in Pod %s, thus extra NPL port is not allocated", container.Name, key) | ||
hostPorts[portProtoInt] = int(cport.HostPort) | ||
} | ||
if targetPortsStr.Has(portProtoStr) { | ||
targetPortsInt.Insert(portProtoInt) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can't we re-organize the code like this, I feel like it would make more sense since the port name is not always set:
portProtoInt := fmt.Sprint(cport.ContainerPort) + delim + string(cport.Protocol)
if int(cport.HostPort) > 0 {
klog.V(4).Infof("Host Port is defined for Container %s in Pod %s, thus extra NPL port is not allocated", container.Name, key)
hostPorts[portProtoInt] = int(cport.HostPort)
}
if cport.Name == "" {
continue
}
portProtoStr := cport.Name + delim + string(cport.Protocol)
if targetPortsStr.Has(portProtoStr) {
targetPortsInt.Insert(portProtoInt)
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
portProto := strings.Split(targetPort, delim) | ||
port, err := strconv.Atoi(portProto[0]) | ||
if err != nil { | ||
return fmt.Errorf("failed to parse port number from %s for pod %s: %v", portProto[0], key, err) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we have a helper function for building the port:proto string and one for parsing it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added.
newPort1 := 91 | ||
newPort2 := 92 | ||
testPod.Spec.Containers[0].Ports = append( | ||
testPod.Spec.Containers[0].Ports, | ||
corev1.ContainerPort{ContainerPort: int32(newPort)}, | ||
corev1.ContainerPort{ContainerPort: int32(newPort1)}, | ||
) | ||
testPod.Spec.Containers[0].Ports = append( | ||
testPod.Spec.Containers[0].Ports, | ||
corev1.ContainerPort{ContainerPort: int32(newPort2)}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm still trying to understand why we need to add 2 new ports here (91 and 91). Neither of these is a target port, so neither of these is used for NPL. We only use the default port (90), yet the test is called multi-port. It feels like 90 and 91 should both be NPL ports, but not 92.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually default port is 80. Updated the pod container ports to 80 and 81 and then the test ensures that we are adding rule for only 80.
@@ -453,6 +524,22 @@ func TestMultiplePods(t *testing.T) { | |||
assert.NotEqual(t, nplData1[0].NodePort, nplData2[0].NodePort) | |||
} | |||
|
|||
func TestMultipleService(t *testing.T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/TestMultipleService/TestMultipleServices
or maybe TestMultipleServicesSameBackendPod for clarity
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed
test/e2e/framework.go
Outdated
// createNginxPodWithPort creates an nginx Pod in the test namespace with configurable container port. | ||
func (data *TestData) createNginxPodWithPort(name, nodeName string, port int32) error { | ||
return data.createPodOnNode(name, nodeName, nginxImage, []string{}, nil, nil, []corev1.ContainerPort{ | ||
{ | ||
Name: "http", | ||
ContainerPort: port, | ||
Protocol: corev1.ProtocolTCP, | ||
}, | ||
}, false, nil) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this function should be moved to nodeportlocal_test.go
IMO. But I am not sure why we even need it. You always use the same port number (90) and I see no reference to the port name ("http").
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved to nodeportlocal_test.go. We needed this function to have option to provide port number for the container port of nginx pod.
return name + delim + prototcol | ||
} | ||
|
||
func parsePortProto(targetPort string) (int, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't this return both port and protocol?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The caller function is only using port for now. Later if required we can return both.
if len(targetPortsInt) == 0 { | ||
if err := c.deleteAllPortRulesIfAny(podIP); err != nil { | ||
return err | ||
} | ||
if _, exists := pod.Annotations[NPLAnnotationKey]; exists { | ||
return c.cleanupNPLAnnotationForPod(pod) | ||
} | ||
return nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sounds good, but maybe a comment to that effect would avoid confusion in the future
do we have a unit test for that edge case?
@@ -389,24 +412,29 @@ func TestPodDelete(t *testing.T) { | |||
} | |||
|
|||
// TestPodAddMultiPort creates a Pod with multiple ports and verifies that the Pod's NPL annotation | |||
// and the local port table are updated correctly. | |||
// and the local port table are updated with only one port used as target port of the Service. | |||
func TestPodAddMultiPort(t *testing.T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we're back to my original comment. Why are we modifying this test like this, instead of having 2 separate tests
- TestServiceMultipleTargetPorts: Service has 2 target ports pointing to 2 different container ports, both are exposed with NPL
- TestPodContainerPortWithoutService: a Pod has 2 container ports, only one of them exposed with NPL (this is your current test, but I feel like the name is confusing)
Or do we already have a test for the first case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it. I have added a separate test case.
be14851
to
be9ec9f
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
some small comments, otherwise LGTM
|
||
// targetPortsInt contains list of all ports that needs to be exposed for the Pod, including container ports | ||
// for named ports present in targetPortsStr. If it is empty, then all existing rules and annotations for the | ||
// Pod have to be cleaned up. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should add: "If a Service uses a named target port that doesn't match any named container port for the current Pod, no corresponding entry will be added to the targetPortsInt set by the code above."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated
testSvc := getTestSvc() | ||
testPod := getTestPod() | ||
// TestPodAddMultiPort creates a Pod with multiple ports and a Service with same ports as target ports. | ||
// It is verified that the Pod's NPL annotationthe local port table are updated correctly. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// It is verified that the Pod's NPL annotationthe local port table are updated correctly. | |
// It verifies that the Pod's NPL annotation and the local port table are updated correctly. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
testPod.Spec.Containers[0].Ports = append( | ||
testPod.Spec.Containers[0].Ports, | ||
corev1.ContainerPort{ContainerPort: defaultPort}, | ||
) | ||
testPod.Spec.Containers[0].Ports = append( | ||
testPod.Spec.Containers[0].Ports, | ||
corev1.ContainerPort{ContainerPort: int32(newPort)}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
adding Pod container ports is not required any more (now that we use the Service's target ports), unless we are dealing with a named port. Maybe for this test case we should omit the container ports.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@monotosh-avi ping on this, any thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
missed this, updated now
// It is verified that the Pod's NPL annotation and the local port table are updated | ||
// with only one port used as target port of the Service. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It verifies that the Pod's NPL annotation and the local port table are updated correctly, with only one port corresponding to the Service's single target port.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
// TestPodAddHostPort creates a Pod with multiple host ports having same value but different protocol. | ||
// It is verified that the Pod's NPL annotation is updated with host port, without allocating extra port | ||
// from NPL pool. No NPL rule is required for this case. | ||
func TestPodAddMultiProtocol(t *testing.T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test case name should include HostPort
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated
@@ -427,6 +496,76 @@ func TestPodAddHostPort(t *testing.T) { | |||
assert.False(t, testData.portTable.RuleExists(defaultPodIP, defaultPort)) | |||
} | |||
|
|||
// TestPodAddHostPort creates a Pod with multiple host ports having same value but different protocol. | |||
// It is verified that the Pod's NPL annotation is updated with host port, without allocating extra port |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/It is verified/It verifies
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
test/e2e/nodeportlocal_test.go
Outdated
} | ||
|
||
// createNginxPodWithPort creates an nginx Pod in the test namespace with configurable container port. | ||
func createNginxPodWithPort(name, nodeName string, port int32) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should stick with a method on the test data object for consistency:
func (data *testData) createNginxPodWithPort(...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
869430a
to
86174a7
Compare
test/e2e/nodeportlocal_test.go
Outdated
// createNginxPodWithPort creates an nginx Pod in the test namespace with configurable container port. | ||
func (data *TestData) createNginxPodWithPort(name, nodeName string, port int32) error { | ||
return data.createPodOnNode(name, nodeName, nginxImage, []string{}, nil, nil, []corev1.ContainerPort{ | ||
{ | ||
Name: "http", | ||
ContainerPort: port, | ||
Protocol: corev1.ProtocolTCP, | ||
}, | ||
}, false, nil) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am still unsure about this function and it's use: since NPL doesn't require the ContainerPort to be set anymore, what's the point?
Also it doesn't seem that there is any value in setting it to 90 in tests. I feel like it is actually misleading since we never use 90 as the Service targetPort. Am I missing something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought that adding a different port number in the pod explicitly would show that NPL doesn't depend on that port. But yeah, we don't need that. Removed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
/test-all |
/test-networkpolicy |
/test-e2e |
/test-all |
/test-e2e |
0577357
to
6d62cc1
Compare
/test-e2e |
@monotosh-avi I see that the e2e tests were consistently failing before your last push. Were you able to identify the root cause? |
@antoninbas it looked like there was some problem with the test setup. I do see the similar issue with other tests too., e.g. https://jenkins.antrea-ci.rocks/job/antrea-e2e-for-pull-request/2747/console. I didn't face this issue when I ran the tests in my local set up. |
@monotosh-avi could you rebase main. Some fixes went in to address known stability issues with the test jobs. I want to make sure that the test failures were because of them and not something else. It may be a good opportunity to squash your commits too (the squashed commit must be signed). Thanks! |
…lity Currently we use container ports of a Pod to program iptables rules to make the Pod reachable through a port in the Node. But container ports are not mandatory and multiple services can use different target ports for the same pod. Hence adding a change in NodePortLocal implementation, where target ports of all services would be used to program iptables rules and annotate pods. To implement this, target ports are being obtained from all the Services selecting a Pod, in the function handleAddUpdatePod(). Necessary changes in the tests have been added. Fixes antrea-io#1912 Signed-off-by: Monotosh Das <[email protected]>
6d62cc1
to
686b2d9
Compare
@antoninbas rebased and squashed commits. |
/test-all |
Another |
Currently we use container ports of a Pod to program iptables rules to make the Pod
reachable through a port in the Node. But container ports are not mandatory and multiple
services can use different target ports for the same pod. Hence adding a change in
NodePortLocal implementation, where target ports of all services would be used to
program iptables rules and annotate pods.
To implement this, target ports are being obtained from all the Services selecting a Pod,
in the function handleAddUpdatePod().
Necessary changes in the tests have been added.
Fixes #1912