Skip to content

Commit

Permalink
E2E: add test for the unnumbered BGP peering
Browse files Browse the repository at this point in the history
The test requires point-to-point connection so we cannot use any
existing infra peer. Therefore a new peer (docker container)
runs, and point-to-point connections are created using `sudo ip`
commands.

There are two tests:
1. one for the native frrconfiguration
2. one for the raw frrconfig, which is used to check the new infra setup
   part

Signed-off-by: karampok <karampok@gmail.com>
  • Loading branch information
karampok committed Nov 11, 2024
1 parent a9c016c commit ecdded6
Show file tree
Hide file tree
Showing 7 changed files with 289 additions and 92 deletions.
1 change: 1 addition & 0 deletions e2etests/e2etest_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ func handleFlags() {
runOnHost = true
}
dump.ReportPath = reportPath
tests.FRRImage = frrImage
}

func TestMain(m *testing.M) {
Expand Down
2 changes: 1 addition & 1 deletion e2etests/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ toolchain go1.22.4

replace (
github.com/metallb/frr-k8s => ../
go.universe.tf/e2etest => github.com/metallb/metallb/e2etest v0.0.0-20240715121012-af3c10d65f18
go.universe.tf/e2etest => github.com/karampok/metallb/e2etest v0.0.0-20241111094309-f118a127c354
go.universe.tf/metallb => github.com/metallb/metallb v0.14.5
)

Expand Down
4 changes: 2 additions & 2 deletions e2etests/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,8 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/karampok/metallb/e2etest v0.0.0-20241022120120-7d1415714a0c h1:5QnQB5bQ0XNtwOl4dvJ+2oS/it0QZG27Pv+mgCCuq3E=
github.com/karampok/metallb/e2etest v0.0.0-20241022120120-7d1415714a0c/go.mod h1:5XhF1cXaT3NLEK4AKhxsO27Op7OxFfQhKpwdNf4GrrA=
github.com/karampok/metallb/e2etest v0.0.0-20241111094309-f118a127c354 h1:qIV6r0NoA6cm6qmr6yQHKgdryF8pvagMfCkQvsM0+7o=
github.com/karampok/metallb/e2etest v0.0.0-20241111094309-f118a127c354/go.mod h1:5XhF1cXaT3NLEK4AKhxsO27Op7OxFfQhKpwdNf4GrrA=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
Expand Down
136 changes: 47 additions & 89 deletions e2etests/go.work.sum

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions e2etests/pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,12 @@ func (u *Updater) Clean() error {

return nil
}

func (u *Updater) CleanFRRConfiguration(cr frrk8sv1beta1.FRRConfiguration) error {
err := u.client.Delete(context.Background(), &cr)
if err != nil && !errors.IsNotFound(err) {
return err
}

return nil
}
199 changes: 199 additions & 0 deletions e2etests/tests/unnumbered.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
// SPDX-License-Identifier:Apache-2.0

package tests

import (
"fmt"
"net"
"os"
"time"

frrk8sv1beta1 "github.com/metallb/frr-k8s/api/v1beta1"
"github.com/metallb/frrk8stests/pkg/config"
"github.com/metallb/frrk8stests/pkg/dump"
"github.com/metallb/frrk8stests/pkg/infra"
"github.com/metallb/frrk8stests/pkg/k8s"
"github.com/metallb/frrk8stests/pkg/k8sclient"
"github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"go.universe.tf/e2etest/pkg/frr"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

frrcontainer "go.universe.tf/e2etest/pkg/frr/container"
)

var FRRImage string

// The Unnumbered test can on IPv4, IPv6 or Dual it only needs the IPv6 LLA
// addresses in the interface.
var _ = ginkgo.Describe("Unnumbered configure BGP peering", func() {
var (
node corev1.Node
updater *config.Updater
peer *frrcontainer.FRR
)

ginkgo.BeforeEach(func() {
if _, found := os.LookupEnv("RUN_FRR_CONTAINER_ON_HOST_NETWORK"); found {
ginkgo.Skip("Skipping this test because RUN_FRR_CONTAINER_ON_HOST_NETWORK is set to true")
}
var err error
updater, err = config.NewUpdater()
Expect(err).NotTo(HaveOccurred())

nodes, err := k8s.Nodes(k8sclient.New())
Expect(err).NotTo(HaveOccurred())
node = nodes[0]

peer, err = frrcontainer.SetupP2PPeer(FRRImage, node)
Expect(err).NotTo(HaveOccurred())
ginkgo.DeferCleanup(func() {
err := frrcontainer.Delete([]*frrcontainer.FRR{peer})
Expect(err).NotTo(HaveOccurred())
})

err = updater.Clean()
Expect(err).NotTo(HaveOccurred())

})

ginkgo.AfterEach(func() {
if ginkgo.CurrentSpecReport().Failed() {
reporter := dump.NewK8sReporter(k8s.FRRK8sNamespace)
testName := ginkgo.CurrentSpecReport().FullText()
dump.K8sInfo(testName, reporter)
dump.BGPInfo(testName, []*frrcontainer.FRR{peer}, k8sclient.New())
}
})

ginkgo.Context("with native neighbor config", func() {
ginkgo.It("session should be established and routes to be validated", func() {
cr := frrk8sv1beta1.FRRConfiguration{
ObjectMeta: metav1.ObjectMeta{
Name: "native-for-" + peer.Name,
Namespace: k8s.FRRK8sNamespace,
},
Spec: frrk8sv1beta1.FRRConfigurationSpec{
BGP: frrk8sv1beta1.BGPConfig{
Routers: []frrk8sv1beta1.Router{
{
ASN: infra.FRRK8sASN,
Neighbors: []frrk8sv1beta1.Neighbor{
{
DynamicASN: frrk8sv1beta1.ExternalASNMode,
Interface: "net0",
ToAdvertise: frrk8sv1beta1.Advertise{
Allowed: frrk8sv1beta1.AllowedOutPrefixes{
Mode: frrk8sv1beta1.AllowAll,
},
},
},
},
Prefixes: []string{"5.5.5.5/32", "fc00:f888:ccd:e793::1/128"},
},
},
},
NodeSelector: metav1.LabelSelector{
MatchLabels: map[string]string{
"kubernetes.io/hostname": node.GetLabels()["kubernetes.io/hostname"],
},
},
},
}
ginkgo.DeferCleanup(func() {
err := updater.CleanFRRConfiguration(cr)
Expect(err).NotTo(HaveOccurred())
})

err := updater.Update([]corev1.Secret{}, cr)
Expect(err).NotTo(HaveOccurred())

validate(peer, []string{"5.5.5.5", "fc00:f888:ccd:e793::1"})
})
})

// This spec is added to verify that the point-to-point setup works
ginkgo.Context("with raw config", func() {
ginkgo.It("session should be established and routes to be validated", func() {
raw := `
frr defaults traditional
no ipv6 forwarding
router bgp 65000
no bgp ebgp-requires-policy
no bgp network import-check
neighbor net0 interface remote-as external
address-family ipv6 unicast
network fc00:f888:ccd:e793::1/128
neighbor net0 activate
exit-address-family
address-family ipv4 unicast
network 5.5.5.5/32
neighbor net0 activate
exit-address-family
exit
end
`

cr := frrk8sv1beta1.FRRConfiguration{
ObjectMeta: metav1.ObjectMeta{
Name: "raw-for-" + peer.Name,
Namespace: k8s.FRRK8sNamespace,
},
Spec: frrk8sv1beta1.FRRConfigurationSpec{
NodeSelector: metav1.LabelSelector{
MatchLabels: map[string]string{
"kubernetes.io/hostname": node.GetLabels()["kubernetes.io/hostname"],
},
},
Raw: frrk8sv1beta1.RawConfig{
Config: raw,
Priority: 5,
},
},
}
ginkgo.DeferCleanup(func() {
err := updater.CleanFRRConfiguration(cr)
Expect(err).NotTo(HaveOccurred())
})

err := updater.Update([]corev1.Secret{}, cr)
Expect(err).NotTo(HaveOccurred())

validate(peer, []string{"5.5.5.5", "fc00:f888:ccd:e793::1"})
})
})
})

func validate(peer *frrcontainer.FRR, prefixes []string) {
Eventually(func() error {
neighbors, err := frr.NeighborsInfo(peer)
Expect(err).NotTo(HaveOccurred())
for _, n := range neighbors {
if !n.Connected {
return fmt.Errorf("node %v BGP session not established", n)
}
}
return nil
}, 2*time.Minute, 10*time.Second).ShouldNot(HaveOccurred(),
"timed out waiting to validate nodes peered with the frr instance")

// NOTE: we define the MAC address of net0, and therefore we can define the LLA
nextHops := []net.IP{net.ParseIP("fe80::dcad:beff:feff:1160")} // net.ParseIP("fe80::dcad:beff:feff:1161"),

Eventually(func() error {
v4, v6, err := frr.Routes(peer)
if err != nil {
return err
}
allRoutes := frr.RoutesJoin(v4, v6)
for _, p := range prefixes {
v, exist := allRoutes[p]
if !exist {
return fmt.Errorf("%s is missing", p)
}
Expect(v.NextHops).To(ConsistOf(nextHops))
}
return nil
}, 2*time.Minute, 1*time.Second).ShouldNot(HaveOccurred(), fmt.Sprintf("peer should have the routes %s", prefixes))
}
30 changes: 30 additions & 0 deletions e2etests/tests/webhooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,36 @@ var _ = ginkgo.Describe("Webhooks", func() {
},
"has both ASN and DynamicASN specified",
),
ginkgo.Entry("no address and no interface is specified",
func(cfg *frrk8sv1beta1.FRRConfiguration) {
cfg.Spec.BGP.Routers = []frrk8sv1beta1.Router{
{
Neighbors: []frrk8sv1beta1.Neighbor{
{
ASN: 100,
},
},
},
}
},
"has no Address or Interface specified",
),
ginkgo.Entry("both address and interface specified",
func(cfg *frrk8sv1beta1.FRRConfiguration) {
cfg.Spec.BGP.Routers = []frrk8sv1beta1.Router{
{
Neighbors: []frrk8sv1beta1.Neighbor{
{
ASN: 100,
Address: "1.2.3.4",
Interface: "eth0",
},
},
},
}
},
"has both Address and Interface specified",
),
)

ginkgo.It("Should reject create/update when there is a conflict with an existing config", func() {
Expand Down

0 comments on commit ecdded6

Please sign in to comment.