Skip to content
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

Update Pod annotations for Service type change #1936

Merged
merged 6 commits into from
Apr 7, 2021

Conversation

chauhanshubham
Copy link
Contributor

includes a UT for the same.
Fixes #1910

@codecov-io
Copy link

codecov-io commented Mar 3, 2021

Codecov Report

Merging #1936 (347cfa1) into main (5752f96) will increase coverage by 0.48%.
The diff coverage is 0.00%.

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #1936      +/-   ##
==========================================
+ Coverage   61.57%   62.06%   +0.48%     
==========================================
  Files         265      265              
  Lines       19770    19774       +4     
==========================================
+ Hits        12174    12272      +98     
+ Misses       6333     6233     -100     
- Partials     1263     1269       +6     
Flag Coverage Δ
e2e-tests 22.57% <0.00%> (?)
kind-e2e-tests 52.82% <0.00%> (-0.05%) ⬇️
unit-tests 41.68% <0.00%> (-0.03%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
pkg/agent/nodeportlocal/k8s/npl_controller.go 60.15% <0.00%> (-0.98%) ⬇️
pkg/agent/stats/collector.go 94.61% <0.00%> (-3.85%) ⬇️
pkg/apiserver/storage/ram/watch.go 90.38% <0.00%> (-3.85%) ⬇️
pkg/agent/openflow/client.go 60.37% <0.00%> (+0.53%) ⬆️
...ntroller/networkpolicy/networkpolicy_controller.go 71.10% <0.00%> (+0.97%) ⬆️
pkg/agent/agent.go 48.92% <0.00%> (+1.19%) ⬆️
pkg/ovs/openflow/ofctrl_builder.go 56.46% <0.00%> (+1.26%) ⬆️
pkg/ovs/ovsconfig/ovs_client.go 51.73% <0.00%> (+2.24%) ⬆️
pkg/agent/openflow/pipeline.go 73.22% <0.00%> (+3.10%) ⬆️
pkg/agent/openflow/cookie/allocator.go 67.74% <0.00%> (+3.22%) ⬆️
... and 1 more

Copy link
Contributor

@antoninbas antoninbas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

out-of-curiosity, do you think it would make sense to log a warning in the Agent's NPL controller when processing a NodePort Service which has the NPL enabled annotation?

podKeys = oldPodSet.Difference(newPodSet).Union(newPodSet.Difference(oldPodSet))
oldPodSet = sets.NewString(c.getPodsFromService(oldSvc)...)
newPodSet = sets.NewString(c.getPodsFromService(newSvc)...)
podKeys = podKeys.Union(oldPodSet.Difference(newPodSet).Union(newPodSet.Difference(oldPodSet)))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we write our own SymmetricDifference method instead of doing oldPodSet.Difference(newPodSet).Union(newPodSet.Difference(oldPodSet))? It will be more readable and it may also be more efficient since we would not be building intermediate sets.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1, maybe the difference is minor here, but I found using Union when calculating the union of many sets affected the performance a lot (in particular, the TestInitXLargeScaleWithOneNamespace test) as it always makes a new set and copies all items when merging each set. I added a function to improve it in #1938. Maybe we could have a separate PR to add SymmetricDifference in same place and replace all such usage.

Comment on lines 215 to 225
var oldPodSet, newPodSet sets.String
if oldSvc.Spec.Type != newSvc.Spec.Type {
newPodSet = sets.NewString(c.getPodsFromService(newSvc)...)
podKeys = podKeys.Union(newPodSet)
}

if !reflect.DeepEqual(oldSvc.Spec.Selector, newSvc.Spec.Selector) {
// Disjunctive union of Pods from both Service sets.
oldPodSet := sets.NewString(c.getPodsFromService(oldSvc)...)
newPodSet := sets.NewString(c.getPodsFromService(newSvc)...)
podKeys = oldPodSet.Difference(newPodSet).Union(newPodSet.Difference(oldPodSet))
oldPodSet = sets.NewString(c.getPodsFromService(oldSvc)...)
newPodSet = sets.NewString(c.getPodsFromService(newSvc)...)
podKeys = podKeys.Union(oldPodSet.Difference(newPodSet).Union(newPodSet.Difference(oldPodSet)))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that this code does not really need to execute in case of an annotation change (oldSvcAnnotation != newSvcAnnotation), should it be in an else block?

If I look at this edge case:

  • oldSvcAnnotation is true
  • newSvcAnnotation is false
  • oldSvc.Spec.Type != newSvc.Spec.Type
  • oldSvc.Spec.Selector != newSvc.Spec.Selector
    You actually end up enqueuing both old Pods and new Pods, when you only need to enqueue old Pods.


var oldPodSet, newPodSet sets.String
if oldSvc.Spec.Type != newSvc.Spec.Type {
newPodSet = sets.NewString(c.getPodsFromService(newSvc)...)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What Service types we care about? In all the Service type change case, we always use the newPodSet?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If nplAnnotation changes from false to true, the newPortSet is already counted in, and we can avoid getting the set twice?

if oldSvc.Spec.Type != newSvc.Spec.Type {
newPodSet = sets.NewString(c.getPodsFromService(newSvc)...)
if newPodSet != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, what Service types we care about? In all the Service type change case, we should always process the newPodSet?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We essentially care about transitions from non-nodeport -> nodeport and vice-versa.
In cases of Service Type updates, I anticipate no change in the set of Pods (considering selectors to be identical at this moment), so we can just check the newPodSet for this, since old one must be the same.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In cases of Service Type updates, I anticipate no change in the set of Pods (considering selectors to be identical at this moment), so we can just check the newPodSet for this, since old one must be the same.

Can you clarify this? I don't think there is any guarantee that the Service Type update will be the only change in the Service spec. The selector may change as well. Even if the user first updated the Service Type, then updated the Selector separately, these 2 updates may be combined and the update handler may be called a single time.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that is correct, the selector may change as well in the same update, in which case it should come in the following conditional, where it compares the selectors in the spec.

Copy link
Contributor

@antoninbas antoninbas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the logic is still not correct. With the latest code it seems that if only the annotation changes and nothing else, we do not process any Pod, which doesn't seem right.

I suggest the following logic, which I find easier to read and hopefully should be correct:

if newSvcAnnotation == "true" && newSvc.Spec.Type == corev1.ServiceTypeNodePort {
  klog.Warningf("Service %s is of type NodePort and cannot be used for NodePortLocal, the '%s' annotation will have no effect", newSvc.Name, NPLEnabledAnnotationKey)
}

var oldPodSet, newPodSet sets.String
sameSelector := reflect.DeepEqual(oldSvc.Spec.Selector, newSvc.Spec.Selector)
oldPodSet = sets.NewString(c.getPodsFromService(oldSvc)...)
if sameSelector {
    newPodSet = oldPodSet
} else {
    newPodSet = sets.NewString(c.getPodsFromService(newSvc)...)
}

podKeys := sets.String{}
oldNPLEnabled = oldSvcAnnotation == "true" && oldSvc.Spec.Type != corev1.ServiceTypeNodePort
newNPLEnabled = newSvcAnnotation == "true" && newSvc.Spec.Type != corev1.ServiceTypeNodePort
switch {
  case oldNPLEnabled && newNPLEnabled && !sameSelector:
      podKeys = utilsets.SymmetricDifference(oldPodSet, newPodSet)
  case oldNPLEnabled && !newNPLEnabled:
      podKeys = oldPodSet
   case newNPLEnabled && !oldNPLEnabled:
      podKeys = newPodSet
}

What do you think? It should take care of Jianjun's comment as well.

Also please rebase the PR and resolve conflicts.

@jianjuns
Copy link
Contributor

@antoninbas : but could we avoid calling getPodsFromService() when not needed, as it is expensive?

@antoninbas
Copy link
Contributor

@antoninbas : but could we avoid calling getPodsFromService() when not needed, as it is expensive?

sure, that can be moved to the individual case statements as needed

@chauhanshubham chauhanshubham force-pushed the svctype-npl branch 2 times, most recently from 71d7068 to f894784 Compare March 15, 2021 07:12
@chauhanshubham
Copy link
Contributor Author

I believe the logic is still not correct. With the latest code it seems that if only the annotation changes and nothing else, we do not process any Pod, which doesn't seem right.

Missed the podKeys initialization while checking for annotation changes, the latest commit takes care of that. We can look at refactoring the function, if it looks unreadable right now, but I feel it has a logical sequencing to it that separates out various Service key/value comparisons.

} else if !reflect.DeepEqual(oldSvc.Spec.Selector, newSvc.Spec.Selector) {
}

if oldSvc.Spec.Type != newSvc.Spec.Type {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we check one of old or new type is NodePort here, if we just care about changing to NodePort or changing from NodePort?

@chauhanshubham
Copy link
Contributor Author

I think in cases where Service annotation or Service type changes, the Pod set is not affected, so maybe we can just rely on the Pods from the new Service. The Pod sets will only differ when the selector changes.
I tried simplifying the code using this. What do you guys think?
Also a few additions to the Unit tests. The tests now cover changes in Service annotations, type, selectors (invalid to valid and vice-versa).

@chauhanshubham
Copy link
Contributor Author

chauhanshubham commented Mar 15, 2021

Also maybe this

if !reflect.DeepEqual(oldSvc.Spec.Selector, newSvc.Spec.Selector) {
	// Disjunctive union of Pods from both Service sets.
	oldPodSet := sets.NewString(c.getPodsFromService(oldSvc)...)
	newPodSet := sets.NewString(c.getPodsFromService(newSvc)...)
	podKeys = podKeys.Union(utilsets.SymmetricDifference(oldPodSet, newPodSet))
}

can be simplified further to

if !reflect.DeepEqual(oldSvc.Spec.Selector, newSvc.Spec.Selector) {
	// Disjunctive union of Pods from both Service sets.
	oldPodSet := sets.NewString(c.getPodsFromService(oldSvc)...)
	if podKeys == nil {
		podKeys = sets.NewString(c.getPodsFromService(newSvc)...)
	}
	podKeys = utilsets.SymmetricDifference(oldPodSet, newPodSet)
}

Which essentially means that
podkeys = podkeys union (oldPodSet symmetric-difference newPodSet)
is similar to
podkeys = podkeys symmetric-difference oldPodSet
since, prior to the selector comparison we are ensuring that podkeys can be either nil or equal to newPodSet.

This is not part of the PR yet, I can add it in the next commit, if this sounds right to you all.

EDIT: I think symmetricDifference is still required

@jianjuns
Copy link
Contributor

The proposal looks good to me. I am fine not to optimize Service type change further, as it is rather uncommon.

if podKeys == nil {
podKeys = sets.NewString(c.getPodsFromService(newSvc)...)
}
podKeys = utilsets.SymmetricDifference(oldPodSet, podKeys)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If annotation/Type is updated as well as Selector, don't it need to process the pods in the intersection?
For example: update a service's annotation from true to false and its selector from app=foo to tier=frontend, shouldn't all pods matching app=foo be processed?

@antoninbas antoninbas added this to the Antrea v1.0 release milestone Mar 30, 2021
@antoninbas
Copy link
Contributor

@chauhanshubham could we try to merge this for the v1.0 release (Wednesday)?

Copy link
Contributor

@jianjuns jianjuns left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks for addressing the comments!

Copy link
Contributor

@antoninbas antoninbas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@antoninbas
Copy link
Contributor

/test-all

@tnqn tnqn merged commit b73ab99 into antrea-io:main Apr 7, 2021
antoninbas pushed a commit to antoninbas/antrea that referenced this pull request Apr 30, 2021
antoninbas pushed a commit to antoninbas/antrea that referenced this pull request Apr 30, 2021
antoninbas pushed a commit that referenced this pull request Apr 30, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
6 participants