Skip to content

Conversation

@sabinaaledort
Copy link
Contributor

@sabinaaledort sabinaaledort commented Jan 18, 2022

A service using an IP from a pool with a list of bgp-peers will not advertise the IP to peers outside that list.

In order to be able to select a specific set of peers for a given pool we need to:

  1. Add a name field to the Peer structure so it can be selected by an AddressPool.
  2. Add an optional list of bgp-peers, each one with its own (optional) node selector.

@sabinaaledort sabinaaledort changed the title Bgp peer selector Add BGP Peer Selector to AddressPool object Jan 18, 2022
@sabinaaledort sabinaaledort changed the title Add BGP Peer Selector to AddressPool object WIP: Add BGP Peer Selector to AddressPool object Jan 18, 2022
@fedepaol
Copy link
Member

I think it's worth waiting until #942 settles, it may have substantial impacts on this.

@sabinaaledort sabinaaledort force-pushed the bgp_peer_selector branch 2 times, most recently from 0be9247 to 2c53451 Compare January 18, 2022 12:46
@sabinaaledort sabinaaledort force-pushed the bgp_peer_selector branch 5 times, most recently from 335e246 to 17b13e6 Compare April 6, 2022 15:10
@sabinaaledort sabinaaledort force-pushed the bgp_peer_selector branch 6 times, most recently from d6499c0 to 5c04fda Compare April 7, 2022 14:53
address-family ipv6 unicast
neighbor {{$n.Addr}} activate
neighbor {{$n.Addr}} route-map {{$n.Addr}}-in in
neighbor {{$n.Addr}} route-map {{$n.Addr}}-out out
Copy link
Member

Choose a reason for hiding this comment

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

we have this below already

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's only added if the neighbor has advertisements. Not setting a route-map for a neighbor without advertisements advertises all ips to that neighbor, while setting it for the neighbor without actually creating it is doing the filtering.

err := validate(adv)
if err != nil {
return err
addAdv := true
Copy link
Member

Choose a reason for hiding this comment

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

Same as above

table.DescribeTable("A service IP will not be advertised to peers outside the BGPAdvertisement peers list", func(addressRange string, ipFamily ipfamily.Family) {
var testContainers []*frrcontainer.FRR
for _, c := range FRRContainers {
if !strings.Contains(c.Name, "multi") {
Copy link
Member

Choose a reason for hiding this comment

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

Why this? If you want to pick just two, be explicit and take the first two of the array (after the len check)

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 still unresolved I think?

}
}
},
table.Entry("IPV4", "192.168.10.0/24", ipfamily.IPv4),
Copy link
Member

Choose a reason for hiding this comment

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

I would add a test where you have the same service, two different advertisements advertised to two different peers (you can check the community for example)


if len(crdAd.Spec.Peers) > 0 {
ad.Peers = make([]string, 0, len(crdAd.Spec.Peers))
ad.Peers = append(ad.Peers, crdAd.Spec.Peers...)
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 extend the unit tests with this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

what do you mean?

Copy link
Member

Choose a reason for hiding this comment

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

I mean, this part is not covered by unit tests (I think).

@fedepaol
Copy link
Member

fedepaol commented Apr 8, 2022

Looks good overall, need to do another pass and focus on the frr config.

@sabinaaledort sabinaaledort force-pushed the bgp_peer_selector branch 4 times, most recently from cf16b56 to 2b98cc6 Compare April 10, 2022 13:28
@sabinaaledort sabinaaledort changed the title WIP: Add BGP Peer Selector to AddressPool object Add BGP Peer Selector to AddressPool object Apr 10, 2022
@sabinaaledort sabinaaledort force-pushed the bgp_peer_selector branch 2 times, most recently from 7cf80c7 to e61780e Compare April 11, 2022 14:56
route-map {{$n.Addr}}-out permit {{counter $n.Addr}}
match ip address prefix-list {{.LocalPreference}}-v4localpref-prefixes
set local-preference {{.LocalPreference}}
match ip address prefix-list {{$n.Addr}}-{{$pl.LocalPreference}}-v4localpref-prefixes
Copy link
Member

Choose a reason for hiding this comment

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

having a prefix per addr per local pref is gonna make the configuration explode. How about a custom function isPrefixValidForNeighbor that checks that (all the advertisements configured for the neigh) and then we add the route map or not? Same for community.

{{- end }}
{{- range $.PrefixesV4ForCommunity }}
{{- if eq ($a.IPFamily.String) "ipv4" }}
ip prefix-list {{$n.Addr}}-plv4 permit {{$a.Prefix}}
Copy link
Member

Choose a reason for hiding this comment

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

Here we create multiple records of the shape of:

route-map 10.2.2.255-out permit 2
  match ip address prefix-list 10.2.2.255-plv4
  on-match next

route-map 10.2.2.255-out permit 5
  match ip address prefix-list 10.2.2.255-plv4
  on-match next

I think building one prefix-list with all the addresses and doing the route map once will be more readable, and will add probably less churn on frr (because all the route maps are different, so I don't think it's gonna squash them).

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 suggest building such list in go and adding to the structure, so you can just leverage it.

address-family ipv4 unicast
neighbor {{$n.Addr}} activate
neighbor {{$n.Addr}} route-map {{$n.Addr}}-in in
neighbor {{$n.Addr}} route-map {{$n.Addr}}-out out
Copy link
Member

Choose a reason for hiding this comment

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

do we need this if we don't have any advertisement?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, if the neighbor doesn't have a route-map all ips are advertised to that neighbor, while setting it for the neighbor without actually creating it, is doing the filtering.

found := false
for _, peer := range adv.Peers {
if peer == s.name {
found = 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: you can break here

found := false
for _, peer := range adv.Peers {
if peer == s.name {
found = true
Copy link
Member

Choose a reason for hiding this comment

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

same

err = ConfigUpdater.Update(resources)
framework.ExpectNoError(err)

for _, c := range FRRContainers {
Copy link
Member

Choose a reason for hiding this comment

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

See my suggestoin above to pick named peers.
For the notAdvertised, I would add a function that filters FRRContainers out of a container and iterate over those.

}

ginkgo.By("checking same service advertised through two different advertisements to two different peers")
for i := 0; i < 2; i++ {
Copy link
Member

Choose a reason for hiding this comment

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

Same considerations as above, following by index is hard. Be explicit on naming.
One pool, PeerForAdv1 , PeerForAdv2, etc

Also, this should be a different test and not a by in the same. It's already long.

}
}

for i := 0; i < 2; i++ {
Copy link
Member

Choose a reason for hiding this comment

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

Same as above, avoid indexes, give peers a name

}
}

func validateServiceInRoutesForCommunity(c *frrcontainer.FRR, community string, family ipfamily.Family, svc *corev1.Service) error {
Copy link
Member

Choose a reason for hiding this comment

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

Put the eventually inside here, it will make the test body lighter

Copy link
Contributor Author

Choose a reason for hiding this comment

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

i use this function twice, once the eventually expects no error, the other time it expects an error.

Copy link
Member

Choose a reason for hiding this comment

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

You can wrap it with two functinos. Also, you may want to check that the error is what we expect with MatchError(ContainSubstring("not in routes")

ingressIP := e2eservice.GetIngressPoint(&ip)

Eventually(func() bool {
frrRoutesV4, frrRoutesV6, err := frr.Routes(c)
Copy link
Member

Choose a reason for hiding this comment

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

you can use frr.RoutesForFamily here (it's new)

@sabinaaledort sabinaaledort force-pushed the bgp_peer_selector branch 5 times, most recently from 8347a48 to 1c64bfe Compare April 13, 2022 15:09
{{- range $a := .Advertisements }}
{{- range $pl := prefixesForLocalPref $a $n.IPFamily.String }}
{{- range $p := $pl.Prefixes }}
{{ipCmd $n.IPFamily.String}} prefix-list {{$n.Addr}}-{{$pl.LocalPreference}}-{{ipCmd $n.IPFamily.String}}-localpref-prefixes permit {{$p}}
Copy link
Member

Choose a reason for hiding this comment

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

ipCmd can take IPFamily instead of string

also, how about renaming ipCmd to frrIPFamily?

route-map {{$n.Addr}}-out permit {{counter $n.Addr}}
match ipv6 address prefix-list {{.LocalPreference}}-v6localpref-prefixes
set local-preference {{.LocalPreference}}
match {{ipCmd $n.IPFamily.String}} address prefix-list {{$n.Addr}}-{{$pl.LocalPreference}}-{{ipCmd $n.IPFamily.String}}-localpref-prefixes
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if we should hide names like
{{$n.Addr}}-{{$pl.LocalPreference}}-{{ipCmd $n.IPFamily.String}}-localpref-prefixes behind a function that takes the neighbour and the local pref.
Something like localPrefPrefixList (neighbour, localpref)

Same for the others: communityPrefixListName() , allowedPrefixListName.
Wdyt?

}
return "ip"
},
"prefixesForLocalPref": func(advConfig advertisementConfig, ipFamily string) []localPrefPrefixes {
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 more "localPrefPrefixesForAdv". Also, can't you use the ipfamily in advertisementConfig?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

i need the neighbor ip family to avoid mixing ipv4 prefixes with neighbor ipv6 addresses

Copy link
Member

Choose a reason for hiding this comment

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

which is something we do not support today right? So that can be solved by filtering when we build the adv list for neighbour?

}
return advConfig.PrefixesV6ForLocalPref
},
"prefixesForCommunity": func(advConfig advertisementConfig, ipFamily string) []communityPrefixes {
Copy link
Member

Choose a reason for hiding this comment

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

same

addPrefixForLocalPref(prefixesV4ForLocalPref, prefixesV6ForLocalPref, adv.Prefix.String(), family, adv.LocalPref)
}

// The maps must be converted in sorted slice to make the rendering stable
Copy link
Member

Choose a reason for hiding this comment

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

this can be embedded in the advertisementConfig literal init above (same for prefixesV4ForLocalPref, moving the addPrefixForLocalPref before the init)

@sabinaaledort sabinaaledort changed the title Add BGP Peer Selector to AddressPool object Add peer selector to BGPAdvertisement object Apr 13, 2022

ginkgo.By(fmt.Sprintf("setting peer selector for addresspool number 1 to peer %s", peerForAdv1.Name))
peer := ""
for _, p := range resources.Peers {
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 repeating and distracting. How about initializing peer1 / peer2 in beforeEach (and peers: metallb.PeersForContainers(FRRContainers, ipFamily))?

I'd rename peerForAdv1 to something else too, because we must distinguish between peer - the container and peer - the BGPPeer we give to metallb

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 need the ipfamily for PeersForContainers which we don't have in before each

Copy link
Member

Choose a reason for hiding this comment

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

oh right. Wrapping it in a function?

ginkgo.By(fmt.Sprintf("checking service in routes of peer %s for community 1", c.Name))
Eventually(func() error {
return validateServiceInRoutesForCommunity(c, community1, ipFamily, svc)
}, 4*time.Minute, 1*time.Second).Should(BeNil())
Copy link
Member

Choose a reason for hiding this comment

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

With errors we use Should(HaveOccurred)

@sabinaaledort sabinaaledort force-pushed the bgp_peer_selector branch 4 times, most recently from 1a0f766 to 19d6c2c Compare April 14, 2022 09:30
A service using an IP from a pool with a list of bgp-peers
will not advertise the IP to peers outside that list.
A service IP from a pool with a list of bgp-peers will
not be advertised to peers outside that list.
Need to fix the test config files due to
the change in the frr config to support
the new BGP Peer Selector.
@fedepaol
Copy link
Member

LGTM
@cgoncalves would you mind giving a look at the config part?

@cgoncalves
Copy link
Contributor

LGTM @cgoncalves would you mind giving a look at the config part?

LGTM. Just a few nits I spotted, I wouldn't take issue merging this PR without addressing them.

@fedepaol
Copy link
Member

LGTM @cgoncalves would you mind giving a look at the config part?

LGTM. Just a few nits I spotted, I wouldn't take issue merging this PR without addressing them.

Ack, let's discuss them offline so we can follow up

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