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

Support unnumbered BGP peering #212

Merged
merged 3 commits into from
Dec 17, 2024
Merged

Support unnumbered BGP peering #212

merged 3 commits into from
Dec 17, 2024

Conversation

karampok
Copy link
Contributor

@karampok karampok commented Oct 22, 2024

/kind feature

What this PR does / why we need it:
As the result of this design proposal
https://github.com/metallb/metallb/blob/main/design/unnumbered-bgp.md

Special notes for your reviewer:
Currently depends on this PR that adds the test functions needed
metallb/metallb#2595,

Release note:

Support unnumbered BGP peering

@karampok karampok force-pushed the unnumbered branch 7 times, most recently from 104a2fc to d1a45ba Compare October 29, 2024 09:33
@karampok karampok force-pushed the unnumbered branch 7 times, most recently from ecdded6 to 4d465b0 Compare November 12, 2024 11:26
@karampok karampok marked this pull request as ready for review November 12, 2024 12:24
@@ -117,7 +117,15 @@ type Neighbor struct {
SourceAddress string `json:"sourceaddress,omitempty"`

// Address is the IP address to establish the session with.
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 you add the // +optional tag as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

Comment on lines 173 to 185
if n.Address == "" && n.Interface == "" {
return nil, fmt.Errorf("neighbor %s has no Address or Interface specified", neighborName(n))
}

if n.Address != "" && n.Interface != "" {
return nil, fmt.Errorf("neighbor %s has both Address and Interface specified", neighborName(n))
}

Copy link
Member

Choose a reason for hiding this comment

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

let's put these first

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

Comment on lines 149 to 164
neighborFamily := ipfamily.Unknown
if n.Address != "" && n.Interface == "" {
neigh, err := ipfamily.ForAddresses(n.Address)
if err != nil {
return nil, fmt.Errorf("failed to find ipfamily for %s, %w", n.Address, err)
}
neighborFamily = neigh
}
Copy link
Member

Choose a reason for hiding this comment

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

when this is below the validations, this can be if n.Address != ""

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

Comment on lines 196 to 200
if n.Address == "" && n.Interface != "" {
res.Unnumbered = true
res.Addr = n.Interface
res.IPFamily = ipfamily.DualStack
}
Copy link
Member

Choose a reason for hiding this comment

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

let's put this above the res, and change the if to n.Interface != "" (or something like defaulting these with vars and only have the n.Address != "" condition, overwriting them)

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 think done, but I am not sure I understood exactly what you suggested.

return nil, fmt.Errorf("failed to find ipfamily for %s, %w", n.Address, err)
neighborFamily := ipfamily.Unknown
if n.Address != "" && n.Interface == "" {
neigh, err := ipfamily.ForAddresses(n.Address)
Copy link
Member

Choose a reason for hiding this comment

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

nit: neigh -> f

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

Comment on lines 104 to 181
ginkgo.DeferCleanup(func() {
err := updater.CleanFRRConfiguration(cr)
Expect(err).NotTo(HaveOccurred())
})
Copy link
Member

Choose a reason for hiding this comment

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

nit: move this below the create

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

})
})

func validate(peer *frrcontainer.FRR, prefixes []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, more descriptive name

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

Comment on lines 171 to 173
Expect(err).NotTo(HaveOccurred())
for _, n := range neighbors {
Copy link
Member

Choose a reason for hiding this comment

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

I think we should also verify that there are actual neighbors (len > 0 or something)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good catch, true, done

Routers: []frrk8sv1beta1.Router{
{
ASN: infra.FRRK8sASN,
Neighbors: []frrk8sv1beta1.Neighbor{
Copy link
Member

Choose a reason for hiding this comment

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

can we also add a simple bfd here just to be sure it works?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sounds good idea, not done, todo

@@ -68,6 +68,7 @@ type NeighborConfig struct {
ASN string
SrcAddr string
Addr string
Copy link
Member

Choose a reason for hiding this comment

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

not sure I like that Addr can now be both interface or address, but for now can you add a comment about it please?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

comment added, but we can sure discuss what implementation you would like. Any suggestions?

Copy link
Contributor Author

@karampok karampok left a comment

Choose a reason for hiding this comment

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

Thanks for the feedback, all done except the addition of BFD

// addresses in the interface.
var _ = ginkgo.Describe("Unnumbered configure BGP peering", func() {
var (
node corev1.Node
Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

var (
node corev1.Node
updater *config.Updater
peer *frrcontainer.FRR
Copy link
Contributor Author

Choose a reason for hiding this comment

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

done (but if you have better name suggestion let me know)

@@ -80,3 +80,12 @@ func (u *Updater) Clean() error {

return nil
}

func (u *Updater) CleanFRRConfiguration(cr frrk8sv1beta1.FRRConfiguration) error {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

updater, err = config.NewUpdater()
Expect(err).NotTo(HaveOccurred())

nodes, err := k8s.Nodes(k8sclient.New())
Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

Routers: []frrk8sv1beta1.Router{
{
ASN: infra.FRRK8sASN,
Neighbors: []frrk8sv1beta1.Neighbor{
Copy link
Contributor Author

Choose a reason for hiding this comment

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

sounds good idea, not done, todo

Comment on lines 173 to 185
if n.Address == "" && n.Interface == "" {
return nil, fmt.Errorf("neighbor %s has no Address or Interface specified", neighborName(n))
}

if n.Address != "" && n.Interface != "" {
return nil, fmt.Errorf("neighbor %s has both Address and Interface specified", neighborName(n))
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

Comment on lines 149 to 164
neighborFamily := ipfamily.Unknown
if n.Address != "" && n.Interface == "" {
neigh, err := ipfamily.ForAddresses(n.Address)
if err != nil {
return nil, fmt.Errorf("failed to find ipfamily for %s, %w", n.Address, err)
}
neighborFamily = neigh
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

Comment on lines 196 to 200
if n.Address == "" && n.Interface != "" {
res.Unnumbered = true
res.Addr = n.Interface
res.IPFamily = ipfamily.DualStack
}
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 think done, but I am not sure I understood exactly what you suggested.

err: nil,
},
{
name: "Neighbor with without Interface and without Address",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@@ -68,6 +68,7 @@ type NeighborConfig struct {
ASN string
SrcAddr string
Addr string
Copy link
Contributor Author

Choose a reason for hiding this comment

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

comment added, but we can sure discuss what implementation you would like. Any suggestions?

@karampok
Copy link
Contributor Author

karampok commented Nov 18, 2024

Added one commit that I will squash, now I am testing with BFD the session.

@karampok karampok force-pushed the unnumbered branch 3 times, most recently from 500f5f7 to de8afe2 Compare November 18, 2024 15:18

externalP2PContainer, err := frrcontainer.CreateP2PPeerFor(nodeWithP2PConnection.Name, iface, FRRImage)
Expect(err).NotTo(HaveOccurred())
ginkgo.By(fmt.Sprintf("create p2p %s:%s -- %s:%s)", nodeWithP2PConnection.Name, iface, iface, externalP2PContainer.Name))
Copy link
Member

Choose a reason for hiding this comment

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

create -> creating + move this above the CreateP2P

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done ( need to skip the real name though)

Expect(err).NotTo(HaveOccurred())
})

ginkgo.By(fmt.Sprintf("load frrconfig to %s", externalP2PContainer.Name))
Copy link
Member

Choose a reason for hiding this comment

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

nit: -> updating frr config for container %s

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

err = externalP2PContainer.UpdateBGPConfigFile(c)
Expect(err).NotTo(HaveOccurred())

ginkgo.By(fmt.Sprintf("apply frrconfiguration %s", "for-"+externalP2PContainer.Name))
Copy link
Member

Choose a reason for hiding this comment

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

-> peering node %s with its p2p container %s

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

ginkgo.DescribeTable("BGP session is established and routes are verified", func(rc frrconfig.RouterConfigUnnumbered) {
iface := rc.Interface
remoteASN := rc.ASNLocal
prefixSend := []string{"5.5.5.0/24", "5555::/64"}
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 closer to where it's needed

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 added on top, I think it fits better

Expect(err).NotTo(HaveOccurred())
peerLLA, err := container.GetIPv6Link(externalP2PContainer.Name, iface)
Expect(err).NotTo(HaveOccurred())
ginkgo.By(fmt.Sprintf("%s/%s/%s --- %s/%s/%s",
Copy link
Member

Choose a reason for hiding this comment

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

By("validating the node and p2p container peered")

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

Comment on lines 155 to 158
// We can also use existing functions if fix the frr.Ipv4 to be LLA
pod, err := k8s.FRRK8sPodOnNode(cs, nodeWithP2PConnection.Name)
Expect(err).NotTo(HaveOccurred())
ValidateNodesHaveRoutes([]*corev1.Pod{pod}, *externalP2PContainer, rc.ToAdvertiseV4, rc.ToAdvertiseV6)
Copy link
Member

Choose a reason for hiding this comment

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

what does this add?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

nothing I let it there to let you know that the usual function can work also in case you wanted to use that. Removed

})
})

func validateBGPPeering(peer *frrcontainer.FRR, lla string) {
Copy link
Member

Choose a reason for hiding this comment

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

nit: validateBGPPeering -> validateUnnumberedBGPPeering ? also lla -> nodeLLA ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

return nil
}
}
return fmt.Errorf("no connected neighbor was found")
Copy link
Member

Choose a reason for hiding this comment

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

modify this to include the node's lla

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

}

func validatePrefixViaFor(peer executor.Executor, dev, nextHop string, prefixes ...string) {
ginkgo.By(fmt.Sprintf("validate prefix %s via %s dev %s", prefixes, nextHop, dev))
Copy link
Member

Choose a reason for hiding this comment

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

validate -> validating

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

})

func validateBGPPeering(peer *frrcontainer.FRR, lla string) {
ginkgo.By(fmt.Sprintf("validate BGP peering to %s", peer.Name))
Copy link
Member

Choose a reason for hiding this comment

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

validate -> validating, from %s to %s

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

Copy link
Contributor Author

@karampok karampok left a comment

Choose a reason for hiding this comment

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

Hope I did not miss something, thanks for the feedback @oribon

Comment on lines 147 to 149
// we cannot use
// ValidatePrefixesForNeighbor(*externalP2PContainer, []corev1.Node{nodeWithP2PConnection}, prefixSend...)
// because deep down the LLA ip cannot be found as part of the node
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 think done

Comment on lines 155 to 158
// We can also use existing functions if fix the frr.Ipv4 to be LLA
pod, err := k8s.FRRK8sPodOnNode(cs, nodeWithP2PConnection.Name)
Expect(err).NotTo(HaveOccurred())
ValidateNodesHaveRoutes([]*corev1.Pod{pod}, *externalP2PContainer, rc.ToAdvertiseV4, rc.ToAdvertiseV6)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

nothing I let it there to let you know that the usual function can work also in case you wanted to use that. Removed

}

func validatePrefixViaFor(peer executor.Executor, dev, nextHop string, prefixes ...string) {
ginkgo.By(fmt.Sprintf("validate prefix %s via %s dev %s", prefixes, nextHop, dev))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

ginkgo.DescribeTable("BGP session is established and routes are verified", func(rc frrconfig.RouterConfigUnnumbered) {
iface := rc.Interface
remoteASN := rc.ASNLocal
prefixSend := []string{"5.5.5.0/24", "5555::/64"}
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 added on top, I think it fits better

Expect(err).NotTo(HaveOccurred())
})

ginkgo.By(fmt.Sprintf("load frrconfig to %s", externalP2PContainer.Name))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

done


validateBGPPeering(externalP2PContainer, nodeLLA)

ginkgo.By(fmt.Sprintf("validate routing table on %s (prefixSend)", externalP2PContainer.Name))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

// ValidatePrefixesForNeighbor(*externalP2PContainer, []corev1.Node{nodeWithP2PConnection}, prefixSend...)
// because deep down the LLA ip cannot be found as part of the node

ginkgo.By(fmt.Sprintf("validate routing table on %s (prefixReceive)", nodeWithP2PConnection.Name))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

})
})

func validateBGPPeering(peer *frrcontainer.FRR, lla string) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

})

func validateBGPPeering(peer *frrcontainer.FRR, lla string) {
ginkgo.By(fmt.Sprintf("validate BGP peering to %s", peer.Name))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

return nil
}
}
return fmt.Errorf("no connected neighbor was found")
Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@karampok karampok force-pushed the unnumbered branch 13 times, most recently from c6d870b to 62264b7 Compare December 12, 2024 11:40
if err != nil {
return nil, fmt.Errorf("failed to find ipfamily for %s, %w", n.Address, err)
if n.Address == "" && n.Interface == "" {
return nil, fmt.Errorf("there is a neighbor with no interface and address")
Copy link
Member

Choose a reason for hiding this comment

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

nit: let's say which neighbor

Copy link
Contributor Author

Choose a reason for hiding this comment

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

how? both interface and address is empty, which field should I print ASN?

Copy link
Member

Choose a reason for hiding this comment

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

yep, that for example

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 can do that . Fyi @oribon if I remember that error message was your feedback

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@@ -530,6 +531,215 @@ func TestNeighbours(t *testing.T) {
}
}

const unnumberedNeihbours = `
Copy link
Member

Choose a reason for hiding this comment

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

typo in neighbors

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

res := &frr.NeighborConfig{
Name: neighborName(n),
ASN: asnFor(n),
SrcAddr: n.SourceAddress,
Addr: n.Address,
Addr: address,
Copy link
Member

Choose a reason for hiding this comment

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

I don't like the fact that we hide the interface under Addr here. Can we have an explicit field?

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 agree that name Addr and the value looks not ideal.

Given that FRR itself is using the word PEER to indicate either Addr or Interface name neighbor PEER interface remote-as <internal|external|auto|ASN

What do you think about renaming .Addr to .Peer? Or you prefer to have .Interface field?

I am not in favor of two field because it means everytime we are using .Addr (e.g. error) we should add logic if .Addr empty use the .Interface (but is not that we currently reference that a lot, is more about the future, so not strong argument)

(https://docs.frrouting.org/en/latest/bgp.html#clicmd-neighbor-PEER-interface-remote-as-internal-external-auto-ASN)

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've agreed if we hadn't to make logic on the fact that it is unnumbered or not, but we are doing it:

neighbor {{.neighbor.Addr}}{{ if .neighbor.Unnumbered }} interface{{ end }} remote-as {{.neighbor.ASN}}

if that's the case I'd prefer having a clear api where fields named explicitly and if we need to call out errors, have a function that returns the proper string (as we do elsewhere).
Names and fields are (almost) free, and we should strive for clarity first.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

not done, TODO

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

)

ginkgo.BeforeEach(func() {
if _, found := os.LookupEnv("RUN_FRR_CONTAINER_ON_HOST_NETWORK"); found {
Copy link
Member

Choose a reason for hiding this comment

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

let's leave this to the caller. I'd rather have the runner willingly opt out instead of having discovery logic in the tests themselves

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure I can remove

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

err = updater.Update([]corev1.Secret{}, cr)
Expect(err).NotTo(HaveOccurred())
ginkgo.DeferCleanup(func() {
err := updater.DeleteFRRConfiguration(cr)
Copy link
Member

Choose a reason for hiding this comment

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

why do we need this and the clean we are doing in beforeEach is not enough?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

the clean in beforeEach is generic clean all so is enough and currently required because most tests do not cleanup their CRs. Nevertheless from gikngko perspective it is recommended each test to cleanup the resources, for example if we want to move towards parallel test that is a small contribution.

Given you comment I will remove it also.

Copy link
Member

Choose a reason for hiding this comment

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

What's bugging me is that all the tests clean the configuration to start with a clean slate, then I land on this and have to spend extra energy to understand why it's different, just to find out that it's unnecessary.
So yes, let's keep the structure consistent with the other tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

removed for consistency

(but for the record is bugging me the opposite, why tests are not take care of the cleanup)

)

// This spec is added to verify that the point-to-point setup works
ginkgo.When("raw config", func() {
Copy link
Member

Choose a reason for hiding this comment

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

mind explaining why using When instead of "context" as per all the other tests?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No real reason, I suppose to avoid having the "when" in the description. Not sure if there is any guidance when When should be used instead of Context.

I will correct it so test looks similar

Copy link
Member

Choose a reason for hiding this comment

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

There is no guidance, and I even did not know about when. I'd rather keep consistency if there is no real need.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done (test was removed)

}),
)

// This spec is added to verify that the point-to-point setup works
Copy link
Member

Choose a reason for hiding this comment

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

Not sure I understood. What's the difference with the other test (apart from using the raw config?)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This test is actually checking that the create container, the p2p wire function , and the unnumber peer config works, without depending on actual code.

E.g. if that test is failing we should be looking whether test code change, external peer config, docker version, frr version introduced something.

Do you prefer that I remove it or update the comment?

Copy link
Member

Choose a reason for hiding this comment

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

Let's remove it. I understand what you mean, but the point of having a CI is to run it incrementally every change and have vision of what's breaking and when. So ideally, if changing a test or frr or docker version is breaking, we'll find out when we apply that change.

Under this logic we should duplicate all our tests to use raw config too.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

Under this logic we should duplicate all our tests to use raw config too.

No other test is setting up neighbor in a unique way so I do not agree fully with that but no strong opinion to keep it also.

res := &frr.NeighborConfig{
Name: neighborName(n),
ASN: asnFor(n),
SrcAddr: n.SourceAddress,
Addr: n.Address,
Addr: address,
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've agreed if we hadn't to make logic on the fact that it is unnumbered or not, but we are doing it:

neighbor {{.neighbor.Addr}}{{ if .neighbor.Unnumbered }} interface{{ end }} remote-as {{.neighbor.ASN}}

if that's the case I'd prefer having a clear api where fields named explicitly and if we need to call out errors, have a function that returns the proper string (as we do elsewhere).
Names and fields are (almost) free, and we should strive for clarity first.

The API changes
```
type Neighbor struct {
  Address string `json:address`
  Interface string `json:interface`
}
```
For metrics when we have unnumbered peering the `vtysh show neigh`
return a key that is not a valid IP (is `net0`), code was modified
accordingly

```
frrk8s_bgp_announced_prefixes_total{peer="net1",vrf="default"} 0
frrk8s_bgp_keepalives_received{peer="net1",vrf="default"} 0
frrk8s_bgp_keepalives_sent{peer="net1",vrf="default"} 0
frrk8s_bgp_notifications_sent{peer="net1",vrf="default"} 0
frrk8s_bgp_opens_received{peer="net1",vrf="default"} 0
```

Signed-off-by: karampok <karampok@gmail.com>
With the new version we need to change the .IP of the neighbor to .ID.
The struct in metallb/e2etest looks as follows:

```
type Neighbor struct {
	// ID is the key that vtysh returns for the neighbor,
	// it can be either IP or interface name if unnumbered.
	ID                      string
	VRF                     string
```

Signed-off-by: karampok <karampok@gmail.com>
The test requires point-to-point connection so we cannot use any
existing infra peer. Therefore a new peer (docker container)
runs with no network, and point-to-point connections are created using
`sudo ip` commands.

There are three specs
```
Unnumbered BGP session is established and routes are verified iBGP
Unnumbered BGP session is established and routes are verified eBGP
```

Signed-off-by: karampok <karampok@gmail.com>
@karampok
Copy link
Contributor Author

@oribon @fedepaol I think I have covered all you points. Thanks

@fedepaol
Copy link
Member

LGTM, sending to merge queue

@fedepaol fedepaol added this pull request to the merge queue Dec 17, 2024
Merged via the queue into metallb:main with commit 80d4b7a Dec 17, 2024
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants