Skip to content

Conversation

@qingwusunny
Copy link

@qingwusunny qingwusunny commented Jul 26, 2022

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.

@qingwusunny qingwusunny force-pushed the update_L2Advertisement branch 5 times, most recently from 7931e36 to ed4dfaa Compare July 26, 2022 11:43
}

func getInterfaces(localNode string, l2Advertisements []*config.L2Advertisement) []string {
ifs := sets.NewString()
Copy link
Member

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.

Copy link
Member

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

Copy link
Author

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.


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)
Copy link
Member

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 ?

Copy link
Author

Choose a reason for hiding this comment

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

ok

}
}

func equalStringSlice(a []string, b []string) bool {
Copy link
Member

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

Copy link
Author

Choose a reason for hiding this comment

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

ok


type spam struct {
ip net.IP
poolName string
Copy link
Member

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

Copy link
Author

@qingwusunny qingwusunny Jul 27, 2022

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).

Copy link
Member

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

level.Debug(l).Log("event", "startUpdate", "msg", "start of config update")

Copy link
Member

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

Copy link
Author

Choose a reason for hiding this comment

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

ok, I get it.

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)
Copy link
Member

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)

Copy link
Author

Choose a reason for hiding this comment

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

It has been modified.

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)
Copy link
Member

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.

Copy link
Author

Choose a reason for hiding this comment

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

ok

func (a *Announce) spamLoop() {
// Map IP to spam stop time.
m := map[string]time.Time{}
type spamInfo struct {
Copy link
Member

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.

Copy link
Author

Choose a reason for hiding this comment

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

ok

@fedepaol
Copy link
Member

Overall looks good. I need to sleep a bit over the select loop and look at it carefully.
Also, I'd be more comfortable in having at least one or two basic e2e tests as part of this PR. This will give us confidence that we are merging something that works.

@fedepaol
Copy link
Member

Couple of extra notes: I'd change the commit message (we are trying to adhere to https://cbea.ms/git-commit/ as guidelines)
For later, when we converge on the review:
I'd add also a commit changing the release notes (0.13.5 is not there yet, you can add it) to mention this feature.
I'd add a commit expanding the "advanced l2 configuration" section of the doc with this change

@qingwusunny qingwusunny changed the title Support L2 mode specify interfaces for announcing LB IP [WIP] Support L2 mode specify interfaces for announcing LB IP Jul 27, 2022
@qingwusunny qingwusunny force-pushed the update_L2Advertisement branch 3 times, most recently from fbd97f3 to baf5e9b Compare July 28, 2022 11:11
// Map IP to spam stop time.
m := map[string]time.Time{}
type timedSpam struct {
t time.Time
Copy link
Member

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"

Copy link
Author

Choose a reason for hiding this comment

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

done

}

func (a *Announce) gratuitous(ip net.IP) {
func (a *Announce) gratuitous(ipA ipAdvertisement) {
Copy link
Member

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

Copy link
Author

Choose a reason for hiding this comment

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

done

}

// updateIPs update announce.ips depend on service info.
func (a *Announce) updateIPs(name string, ip net.IP, interfaces sets.String) (hasUpdate bool) {
Copy link
Member

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

Copy link
Author

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?

}

// updateIPs update announce.ips depend on service info.
func (a *Announce) updateIPs(name string, ip net.IP, interfaces sets.String) (hasUpdate bool) {
Copy link
Member

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

Copy link
Author

Choose a reason for hiding this comment

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

done

return
}
ips[i].interfaces = interfaces
hasFound = true
Copy link
Member

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

Copy link
Author

Choose a reason for hiding this comment

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

done

return dropReasonAnnounceIP
}

// updateIPs update announce.ips depend on service info.
Copy link
Member

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"

Copy link
Author

Choose a reason for hiding this comment

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

Done

}

// updateIPs update announce.ips depend on service info.
func (a *Announce) updateIPs(name string, ip net.IP, interfaces sets.String) (hasUpdate bool) {
Copy link
Member

Choose a reason for hiding this comment

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

nit: updateBalancerIPs

Copy link
Author

Choose a reason for hiding this comment

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

Done

}

func (a *Announce) shouldAnnounce(ip net.IP) dropReason {
func isMatchIntf(intf string, ifs sets.String) bool {
Copy link
Member

Choose a reason for hiding this comment

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

nit: advertiseOnInterface

if matchNode, ok := l2.Nodes[localNode]; !ok || !matchNode {
continue
}
if len(l2.Interfaces) == 0 {
Copy link
Member

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.

Copy link
Author

Choose a reason for hiding this comment

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

ok

return "notOwner"
}

func getInterfaces(localNode string, l2Advertisements []*config.L2Advertisement) sets.String {
Copy link
Member

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

Copy link
Author

Choose a reason for hiding this comment

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

done

@fedepaol
Copy link
Member

Another round, mostly nits. It's converging :-)

@qingwusunny qingwusunny force-pushed the update_L2Advertisement branch 10 times, most recently from 2da5636 to 7ae71f0 Compare August 3, 2022 09:45
@qingwusunny qingwusunny force-pushed the update_L2Advertisement branch 2 times, most recently from 8512cb8 to 90db169 Compare August 12, 2022 09:22
@qingwusunny
Copy link
Author

@fedepaol I have modified the func SetBalancer in announcer.go,review again?

@qingwusunny
Copy link
Author

Couple of extra notes: I'd change the commit message (we are trying to adhere to https://cbea.ms/git-commit/ as guidelines) For later, when we converge on the review: I'd add also a commit changing the release notes (0.13.5 is not there yet, you can add it) to mention this feature. I'd add a commit expanding the "advanced l2 configuration" section of the doc with this change

Add two commit for release notes and doc in this PR?

@qingwusunny
Copy link
Author

@fedepaol I have modified the func SetBalancer in announcer.go,review again?

And @oribon

framework.ExpectNoError(err)

ginkgo.By("checking connectivity to its external VIP")
gomega.Eventually(func() error {
Copy link
Member

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)

@fedepaol
Copy link
Member

Couple of extra notes: I'd change the commit message (we are trying to adhere to https://cbea.ms/git-commit/ as guidelines) For later, when we converge on the review: I'd add also a commit changing the release notes (0.13.5 is not there yet, you can add it) to mention this feature. I'd add a commit expanding the "advanced l2 configuration" section of the doc with this change

Add two commit for release notes and doc in this PR?

Yes exactly, so when we cut a new release we won't have to add a new pr for that.

@fedepaol
Copy link
Member

@fedepaol I have modified the func SetBalancer in announcer.go,review again?

Sorry for the delay, I was off. Reviewed again!

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)
Copy link
Member

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"})

Copy link
Author

Choose a reason for hiding this comment

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

ok

} else {
for _, client := range a.ndps {
if !adv.matchInterface(client.intf) {
level.Debug(a.logger).Log("op", "announcer", "info", "skip interfaces", client.intf)
Copy link
Member

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)

Copy link
Author

Choose a reason for hiding this comment

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

ok

func (a *Announce) updateBalancerIPs(name string, adv IPAdvertisement) (*IPAdvertisement, bool) {
var old *IPAdvertisement
if ips, ok := a.ips[name]; ok {
for i, _ := range ips {
Copy link
Member

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 ?)

// 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 {
Copy link
Member

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?

}

a.ips[name] = append(a.ips[name], adv)
return old, true
Copy link
Member

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?

Copy link
Member

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.

@fedepaol
Copy link
Member

Couple of extra notes: I'd change the commit message (we are trying to adhere to https://cbea.ms/git-commit/ as guidelines) For later, when we converge on the review: I'd add also a commit changing the release notes (0.13.5 is not there yet, you can add it) to mention this feature. I'd add a commit expanding the "advanced l2 configuration" section of the doc with this change

Add two commit for release notes and doc in this PR?

Yes exactly, so when we cut a new release we won't have to add a new pr for that.

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)

@qingwusunny qingwusunny force-pushed the update_L2Advertisement branch from 90db169 to 77511eb Compare September 2, 2022 07:38
@fedepaol
Copy link
Member

fedepaol commented Sep 2, 2022

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.

@qingwusunny qingwusunny force-pushed the update_L2Advertisement branch 2 times, most recently from 399b413 to 0fae20a Compare September 7, 2022 03:34
Copy link
Member

@oribon oribon left a 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!

zhoujiao added 3 commits September 8, 2022 19:08
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
@qingwusunny qingwusunny force-pushed the update_L2Advertisement branch from 0fae20a to 4ce7e75 Compare September 8, 2022 11:12
@fedepaol
Copy link
Member

fedepaol commented Sep 8, 2022

Thanks for taking the time to iterate over this! The result is really neat.
LGTM

@fedepaol fedepaol merged commit fb96318 into metallb:main Sep 8, 2022
@fedepaol
Copy link
Member

fedepaol commented Sep 8, 2022

@qingwusunny it'd be great if you can extend the e2e tests in a separate PR as we discussed in the past.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants