-
Notifications
You must be signed in to change notification settings - Fork 1k
Support L2 mode specify interfaces for announcing LB IP #1536
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
Conversation
7931e36 to
ed4dfaa
Compare
speaker/layer2_controller.go
Outdated
| } | ||
|
|
||
| func getInterfaces(localNode string, l2Advertisements []*config.L2Advertisement) []string { | ||
| ifs := sets.NewString() |
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 won't handle the case where you have one advertisement with no interface (and thus, collecting all of them) and one other advertisement with a selector.
The desired result is to advertise all of them, but we'll advertise to a subset only.
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, but the problem is, if you have two l2 advertisements, one with some interfaces and another one with no interfaces, the semantic there is : advertise to all (because you have one l2 advertisement with no interfaces) but your if won't trigger because len(interfaces) is not 0
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, I'm ill-considered.
speaker/layer2_controller.go
Outdated
|
|
||
| func (c *layer2Controller) SetConfig(log.Logger, *config.Config) error { | ||
| func (c *layer2Controller) SetConfig(l log.Logger, cfg *config.Config) error { | ||
| interfacesMap := make(map[string][]string) |
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.
You are translating a set to []string from inside and then translating back when you use it.
Can't interfacesMap just be a map[string]set ?
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.
ok
speaker/layer2_controller_test.go
Outdated
| } | ||
| } | ||
|
|
||
| func equalStringSlice(a []string, b []string) bool { |
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.
put this on the bottom, as it's a util function
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.
ok
internal/layer2/announcer.go
Outdated
|
|
||
| type spam struct { | ||
| ip net.IP | ||
| poolName string |
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 not sure I like the idea of poolName leaking down here.
I think a better design would be getting the list of interfaces from outside as an option of spam
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.
If the struct is (ip, interfaces), when L2Advertisement changed,I am afraid the announced LB IP can't update its related interfaces.
By the way, I am sorry that I found I hasn't modify func (a *Announce) shouldAnnounce(ip net.IP).
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.
If the struct is (ip, interfaces), when L2Advertisement changed,I am afraid the announced LB IP can't update its related interfaces.
When a configuration changes, MetalLB will reprocess all the services, which in turn will call SetBalancer again, so this won't be an issue.
See
Line 434 in 9fce695
| level.Debug(l).Log("event", "startUpdate", "msg", "start of config update") |
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.
Regarding the namespace, MetalLB will filter objects not belonging to its namespace, so it's safe to assume namespace is not part of the key
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.
ok, I get it.
internal/layer2/announcer.go
Outdated
| func (a *Announce) SetBalancer(name string, ip net.IP, poolName string) { | ||
| // Call doSpam at the end of the function without holding the lock | ||
| defer a.doSpam(ip) | ||
| defer a.doSpam(ip, poolName) |
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.
here I would pass the list of interfaces (instead of poolName)
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 has been modified.
speaker/layer2_controller.go
Outdated
| func (c *layer2Controller) SetBalancer(l log.Logger, name string, lbIPs []net.IP, pool *config.Pool) error { | ||
| for _, lbIP := range lbIPs { | ||
| c.announcer.SetBalancer(name, lbIP) | ||
| c.announcer.SetBalancer(name, lbIP, pool.Name) |
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.
Assuming you maintain the mapping between pool name and interface outside, here you can just pass the list of interfaces.
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.
ok
internal/layer2/announcer.go
Outdated
| func (a *Announce) spamLoop() { | ||
| // Map IP to spam stop time. | ||
| m := map[string]time.Time{} | ||
| type spamInfo struct { |
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'd call this timedSpam or deferredSpam.
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.
ok
|
Overall looks good. I need to sleep a bit over the select loop and look at it carefully. |
|
Couple of extra notes: I'd change the commit message (we are trying to adhere to https://cbea.ms/git-commit/ as guidelines) |
fbd97f3 to
baf5e9b
Compare
internal/layer2/announcer.go
Outdated
| // Map IP to spam stop time. | ||
| m := map[string]time.Time{} | ||
| type timedSpam struct { | ||
| t time.Time |
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'd call this "until"
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
internal/layer2/announcer.go
Outdated
| } | ||
|
|
||
| func (a *Announce) gratuitous(ip net.IP) { | ||
| func (a *Announce) gratuitous(ipA ipAdvertisement) { |
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'd call ipA -> adv
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
internal/layer2/announcer.go
Outdated
| } | ||
|
|
||
| // updateIPs update announce.ips depend on service info. | ||
| func (a *Announce) updateIPs(name string, ip net.IP, interfaces sets.String) (hasUpdate bool) { |
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 is used by SetBalancer, please put it below it, reasonably after the exported functions
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 file's all export functions are in the last. Maybe the function's position can not change for consistency?
internal/layer2/announcer.go
Outdated
| } | ||
|
|
||
| // updateIPs update announce.ips depend on service info. | ||
| func (a *Announce) updateIPs(name string, ip net.IP, interfaces sets.String) (hasUpdate bool) { |
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'd avoid using named returns, just return the value
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
internal/layer2/announcer.go
Outdated
| return | ||
| } | ||
| ips[i].interfaces = interfaces | ||
| hasFound = true |
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.
we can just return here I think? If so we can remove hasFound
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
internal/layer2/announcer.go
Outdated
| return dropReasonAnnounceIP | ||
| } | ||
|
|
||
| // updateIPs update announce.ips depend on service info. |
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.
"and returns true if the announcement has changed"
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
internal/layer2/announcer.go
Outdated
| } | ||
|
|
||
| // updateIPs update announce.ips depend on service info. | ||
| func (a *Announce) updateIPs(name string, ip net.IP, interfaces sets.String) (hasUpdate bool) { |
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.
nit: updateBalancerIPs
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
internal/layer2/announcer.go
Outdated
| } | ||
|
|
||
| func (a *Announce) shouldAnnounce(ip net.IP) dropReason { | ||
| func isMatchIntf(intf string, ifs sets.String) bool { |
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.
nit: advertiseOnInterface
speaker/layer2_controller.go
Outdated
| if matchNode, ok := l2.Nodes[localNode]; !ok || !matchNode { | ||
| continue | ||
| } | ||
| if len(l2.Interfaces) == 0 { |
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 you add a comment here explaining the semantic? I am also tempted to wrap the set under a
struct {
allInterfaces bool
interfaces Set
}
WDYT? I am not sure I like propagating the len==0 -> all interfaces down there. A flag might be more explicit and understandable.
Ideally, that flag could be even a property of the "internal" l2advertisement, so the only place where we consider len==0 -> all interfaces is the CRD.
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.
ok
speaker/layer2_controller.go
Outdated
| return "notOwner" | ||
| } | ||
|
|
||
| func getInterfaces(localNode string, l2Advertisements []*config.L2Advertisement) sets.String { |
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.
Same, put below where it's used. See https://kentcdodds.com/blog/newspaper-code-structure for reference
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
|
Another round, mostly nits. It's converging :-) |
2da5636 to
7ae71f0
Compare
8512cb8 to
90db169
Compare
|
@fedepaol I have modified the func SetBalancer in announcer.go,review again? |
Add two commit for release notes and doc in this PR? |
| framework.ExpectNoError(err) | ||
|
|
||
| ginkgo.By("checking connectivity to its external VIP") | ||
| gomega.Eventually(func() 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.
Use Consistently here, with a lower timeout (like 1 min)
Yes exactly, so when we cut a new release we won't have to add a new pr for that. |
Sorry for the delay, I was off. Reviewed again! |
internal/layer2/announcer.go
Outdated
| if ip.To4() != nil { | ||
| for _, client := range a.arps { | ||
| if !adv.matchInterface(client.intf) { | ||
| level.Debug(a.logger).Log("op", "announcer", "info", "skip interfaces", client.intf) |
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.
change to:
level.Debug(a.logger).Log("op", "gratuitousAnnounce", "skipping interface", client.intf)
(also it will avoid having an odd number of elements which results in something like {"caller":"announcer.go:201","eth0":"(MISSING)","info":"skip interfaces","level":"debug","op":"announcer","ts":"2022-08-31T10:24:05Z"})
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.
ok
internal/layer2/announcer.go
Outdated
| } else { | ||
| for _, client := range a.ndps { | ||
| if !adv.matchInterface(client.intf) { | ||
| level.Debug(a.logger).Log("op", "announcer", "info", "skip interfaces", client.intf) |
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.
same: level.Debug(a.logger).Log("op", "gratuitousAnnounce", "skipping interface", client.intf)
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.
ok
internal/layer2/announcer.go
Outdated
| func (a *Announce) updateBalancerIPs(name string, adv IPAdvertisement) (*IPAdvertisement, bool) { | ||
| var old *IPAdvertisement | ||
| if ips, ok := a.ips[name]; ok { | ||
| for i, _ := range ips { |
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.
nit: _ not required (also why not use _, i ?)
internal/layer2/announcer.go
Outdated
| // updateBalancerIPs updates announce.ips depend on service info, and returns old config, a bool indicating if the announcement has changed or not. | ||
| func (a *Announce) updateBalancerIPs(name string, adv IPAdvertisement) (*IPAdvertisement, bool) { | ||
| var old *IPAdvertisement | ||
| if ips, ok := a.ips[name]; ok { |
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.
re the other comment, can you change ips -> ipAdvertisements here as well?
internal/layer2/announcer.go
Outdated
| } | ||
|
|
||
| a.ips[name] = append(a.ips[name], adv) | ||
| return old, true |
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.
nit: can we return nil, true here?
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 is gonna be removed I think.
As a second thought, let's leave them out for now. We'll add documentation and declare the feature when we have enough coverage to assure us the feature is working (I am assuming we'll add new tests in another PR) |
90db169 to
77511eb
Compare
|
A couple of nits around the code, which is getting to look really good in my opinion, plus a comment about the e2e tests which I think needs to be sorted out. |
399b413 to
0fae20a
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.
apart from the few unaddressed comments, lgtm
thanks a lot!
Add a new field "Interfaces" to "L2Advertisement". By setting this field, LB IP can be announced only from these specific interfaces.
L2 DeleteBalancer: continue instead of return if one of the service's IP has been used by another service
0fae20a to
4ce7e75
Compare
|
Thanks for taking the time to iterate over this! The result is really neat. |
|
@qingwusunny it'd be great if you can extend the e2e tests in a separate PR as we discussed in the past. |
This PR implement the feature that MetalLB announce IP from specified interfaces for layer2. Related design is #1359.
I has add two basic e2etest about interface selector.