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

Implement receiving / filtering prefixes from outside #21

Merged
merged 3 commits into from
Jul 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion e2etests/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ go 1.19
replace (
github.com/metallb/frrk8s => ../
github.com/onsi/ginkgo/v2 => github.com/onsi/ginkgo/v2 v2.11.0
go.universe.tf/e2etest => github.com/metallb/metallb/e2etest v0.0.0-20230704151713-ca4eed32d1f6
go.universe.tf/e2etest => github.com/metallb/metallb/e2etest v0.0.0-20230719075116-22ffd63a2942
k8s.io/api => k8s.io/api v0.26.0
k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.26.0
k8s.io/apimachinery => k8s.io/apimachinery v0.26.0
Expand Down
4 changes: 2 additions & 2 deletions e2etests/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -226,8 +226,8 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/metallb/metallb/e2etest v0.0.0-20230704151713-ca4eed32d1f6 h1:PlT2uIiyi0jsbXax1wapWg0MjGoODcx5BODqdoXP40A=
github.com/metallb/metallb/e2etest v0.0.0-20230704151713-ca4eed32d1f6/go.mod h1:pvpDmNSSuGzhAF32B9Rsf8cl+9XiAMq0fRMaWbJsWAo=
github.com/metallb/metallb/e2etest v0.0.0-20230719075116-22ffd63a2942 h1:KHKjIaQSPXxqSRMf7KwRGSigM5OdrmGsTZUjBCCYEmI=
github.com/metallb/metallb/e2etest v0.0.0-20230719075116-22ffd63a2942/go.mod h1:pvpDmNSSuGzhAF32B9Rsf8cl+9XiAMq0fRMaWbJsWAo=
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws=
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc=
github.com/mitchellh/mapstructure v1.4.2 h1:6h7AQ0yhTcIsmFmnAwQls75jp2Gzs4iB8W7pjMO+rqo=
Expand Down
4 changes: 2 additions & 2 deletions e2etests/go.work.sum
Original file line number Diff line number Diff line change
Expand Up @@ -263,8 +263,8 @@ github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118/go.mod h1:ZFUnHI
github.com/mdlayher/ndp v0.0.0-20200602162440-17ab9e3e5567/go.mod h1:32w/5dDZWVSEOxyniAgKK4d7dHTuO6TCxWmUznQe3f8=
github.com/mdlayher/packet v1.0.0/go.mod h1:eE7/ctqDhoiRhQ44ko5JZU2zxB88g+JH/6jmnjzPjOU=
github.com/mdlayher/socket v0.2.1/go.mod h1:QLlNPkFR88mRUNQIzRBMfXxwKal8H7u1h3bL1CV+f0E=
github.com/metallb/metallb/e2etest v0.0.0-20230704151713-ca4eed32d1f6 h1:PlT2uIiyi0jsbXax1wapWg0MjGoODcx5BODqdoXP40A=
github.com/metallb/metallb/e2etest v0.0.0-20230704151713-ca4eed32d1f6/go.mod h1:pvpDmNSSuGzhAF32B9Rsf8cl+9XiAMq0fRMaWbJsWAo=
github.com/metallb/metallb/e2etest v0.0.0-20230719075116-22ffd63a2942 h1:KHKjIaQSPXxqSRMf7KwRGSigM5OdrmGsTZUjBCCYEmI=
github.com/metallb/metallb/e2etest v0.0.0-20230719075116-22ffd63a2942/go.mod h1:pvpDmNSSuGzhAF32B9Rsf8cl+9XiAMq0fRMaWbJsWAo=
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
github.com/mindprince/gonvml v0.0.0-20190828220739-9ebdce4bb989/go.mod h1:2eu9pRWp8mo84xCg6KswZ+USQHjwgRhNp06sozOdsTY=
github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4=
Expand Down
40 changes: 40 additions & 0 deletions e2etests/pkg/routes/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,24 @@ import (
"go.universe.tf/e2etest/pkg/frr"
frrcontainer "go.universe.tf/e2etest/pkg/frr/container"
"go.universe.tf/e2etest/pkg/ipfamily"
"go.universe.tf/metallb/e2etest/pkg/executor"
v1 "k8s.io/api/core/v1"
"k8s.io/kubernetes/test/e2e/framework"
)

// PodHasPrefixFromContainer tells if the given frr-k8s pod has recevied a route for
// the given prefix from the given container.
func PodHasPrefixFromContainer(pod *v1.Pod, frr frrcontainer.FRR, prefix string) bool {
_, cidr, _ := net.ParseCIDR(prefix)
ipFamily := ipfamily.ForCIDR(cidr)
nextHop := frr.Ipv4
if ipFamily == ipfamily.IPv6 {
nextHop = frr.Ipv6
}
vrf := frr.RouterConfig.VRF
return hasPrefix(pod, ipFamily, cidr, nextHop, vrf)
}

// CheckNeighborHasPrefix tells if the given frr container has a route toward the given prefix
// via the set of node passed to this function.
func CheckNeighborHasPrefix(neighbor frrcontainer.FRR, prefix string, nodes []v1.Node) (bool, error) {
Expand Down Expand Up @@ -67,3 +82,28 @@ func routeForCIDR(cidr *net.IPNet, routesV4 map[string]frr.Route, routesV6 map[s
}
return frr.Route{}, RouteNotFoundError(fmt.Sprintf("route %s not found", cidr))
}

func hasPrefix(pod *v1.Pod, pairingFamily ipfamily.Family, prefix *net.IPNet, nextHop, vrf string) bool {
found := false
podExec := executor.ForPod(pod.Namespace, pod.Name, "frr")
routes, frrRoutesV6, err := frr.RoutesForVRF(vrf, podExec)
framework.ExpectNoError(err)

if pairingFamily == ipfamily.IPv6 {
routes = frrRoutesV6
}

out:
for _, route := range routes {
if !cidrsAreEqual(route.Destination, prefix) {
continue
}
for _, nh := range route.NextHops {
if nh.String() == nextHop {
found = true
break out
}
}
}
return found
}
298 changes: 298 additions & 0 deletions e2etests/tests/receiving.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,298 @@
// SPDX-License-Identifier:Apache-2.0

package tests

import (
"github.com/onsi/ginkgo/v2"
"go.universe.tf/e2etest/pkg/frr/container"

frrk8sv1beta1 "github.com/metallb/frrk8s/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"
frrconfig "go.universe.tf/e2etest/pkg/frr/config"
"go.universe.tf/e2etest/pkg/ipfamily"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/kubernetes/test/e2e/framework"
admissionapi "k8s.io/pod-security-admission/api"
)

var _ = ginkgo.Describe("Receiving routes", func() {
var cs clientset.Interface
var f *framework.Framework

defer ginkgo.GinkgoRecover()
clientconfig, err := framework.LoadConfig()
framework.ExpectNoError(err)
updater, err := config.NewUpdater(clientconfig)
framework.ExpectNoError(err)
reporter := dump.NewK8sReporter(framework.TestContext.KubeConfig, k8s.FRRK8sNamespace)

f = framework.NewDefaultFramework("bgpfrr")
f.NamespacePodSecurityEnforceLevel = admissionapi.LevelPrivileged

ginkgo.AfterEach(func() {
if ginkgo.CurrentSpecReport().Failed() {
testName := ginkgo.CurrentSpecReport().LeafNodeText
dump.K8sInfo(testName, reporter)
dump.BGPInfo(testName, infra.FRRContainers, f.ClientSet, f)
}
})

ginkgo.BeforeEach(func() {
ginkgo.By("Clearing any previous configuration")

for _, c := range infra.FRRContainers {
err := c.UpdateBGPConfigFile(frrconfig.Empty)
framework.ExpectNoError(err)
}
err := updater.Clean()
framework.ExpectNoError(err)

cs = f.ClientSet
})

ginkgo.Context("Receiving IPs", func() {
type params struct {
vrf string
ipFamily ipfamily.Family
myAsn uint32
toAdvertiseV4 []string
toAdvertiseV6 []string
modifyPeers func([]config.Peer, []config.Peer)
validate func([]config.Peer, []config.Peer, []*v1.Pod)
}

ginkgo.DescribeTable("Works with external frrs", func(p params) {
frrs := config.ContainersForVRF(infra.FRRContainers, p.vrf)
peersV4, peersV6 := config.PeersForContainers(frrs, p.ipFamily)
p.modifyPeers(peersV4, peersV6)
neighbors := config.NeighborsFromPeers(peersV4, peersV6)

config := frrk8sv1beta1.FRRConfiguration{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "default",
},
Spec: frrk8sv1beta1.FRRConfigurationSpec{
BGP: frrk8sv1beta1.BGPConfig{
Routers: []frrk8sv1beta1.Router{
{
ASN: p.myAsn,
VRF: p.vrf,
Neighbors: neighbors,
},
},
},
},
}

ginkgo.By("pairing with nodes")
for _, c := range frrs {
err := container.PairWithNodes(cs, c, p.ipFamily, func(frr *container.FRR) {
frr.NeighborConfig.ToAdvertiseV4 = p.toAdvertiseV4
frr.NeighborConfig.ToAdvertiseV6 = p.toAdvertiseV6
})
framework.ExpectNoError(err)
}
err := updater.Update(config)
framework.ExpectNoError(err)

nodes, err := k8s.Nodes(cs)
framework.ExpectNoError(err)

for _, c := range frrs {
ValidateFRRPeeredWithNodes(nodes, c, p.ipFamily)
}

pods, err := k8s.FRRK8sPods(cs)
framework.ExpectNoError(err)

ginkgo.By("validating")
p.validate(peersV4, peersV6, pods)
},
ginkgo.Entry("IPV4 - receive ips from all", params{
ipFamily: ipfamily.IPv4,
vrf: "",
myAsn: infra.FRRK8sASN,
toAdvertiseV4: []string{"192.168.2.0/24", "192.169.2.0/24"},
modifyPeers: func(ppV4 []config.Peer, ppV6 []config.Peer) {
for i := range ppV4 {
ppV4[i].Neigh.ToReceive.Allowed.Mode = frrk8sv1beta1.AllowAll
}
},
validate: func(ppV4 []config.Peer, ppV6 []config.Peer, pods []*v1.Pod) {
for _, p := range ppV4 {
ValidateNodesHaveRoutes(pods, p.FRR, []string{"192.168.2.0/24", "192.169.2.0/24"}...)
}
},
}),
ginkgo.Entry("IPV4 - receive ips from some, all mode", params{
ipFamily: ipfamily.IPv4,
vrf: "",
myAsn: infra.FRRK8sASN,
toAdvertiseV4: []string{"192.168.2.0/24", "192.169.2.0/24"},
modifyPeers: func(ppV4 []config.Peer, ppV6 []config.Peer) {
ppV4[0].Neigh.ToReceive.Allowed.Mode = frrk8sv1beta1.AllowAll
},
validate: func(ppV4 []config.Peer, ppV6 []config.Peer, pods []*v1.Pod) {
ValidateNodesHaveRoutes(pods, ppV4[0].FRR, []string{"192.168.2.0/24", "192.169.2.0/24"}...)
for _, p := range ppV4[1:] {
ValidateNodesDoNotHaveRoutes(pods, p.FRR, []string{"192.168.2.0/24", "192.169.2.0/24"}...)
}
},
}),
ginkgo.Entry("IPV4 - receive ips from some, explicit mode", params{
ipFamily: ipfamily.IPv4,
vrf: "",
myAsn: infra.FRRK8sASN,
toAdvertiseV4: []string{"192.168.2.0/24", "192.169.2.0/24", "192.170.2.0/24"},
modifyPeers: func(ppV4 []config.Peer, ppV6 []config.Peer) {
ppV4[0].Neigh.ToReceive.Allowed.Prefixes = []string{"192.168.2.0/24", "192.169.2.0/24"}
},
validate: func(ppV4 []config.Peer, ppV6 []config.Peer, pods []*v1.Pod) {
ValidateNodesHaveRoutes(pods, ppV4[0].FRR, []string{"192.168.2.0/24", "192.169.2.0/24"}...)
ValidateNodesDoNotHaveRoutes(pods, ppV4[0].FRR, []string{"192.170.2.0/24"}...)
for _, p := range ppV4[1:] {
ValidateNodesDoNotHaveRoutes(pods, p.FRR, []string{"192.168.2.0/24", "192.169.2.0/24", "192.170.2.0/24"}...)
}
},
}),
ginkgo.Entry("IPV6 - receive ips from all", params{
ipFamily: ipfamily.IPv6,
vrf: "",
myAsn: infra.FRRK8sASN,
toAdvertiseV6: []string{"fc00:f853:ccd:e799::/64", "fc00:f853:ccd:e800::/64"},
modifyPeers: func(ppV4 []config.Peer, ppV6 []config.Peer) {
for i := range ppV4 {
ppV4[i].Neigh.ToReceive.Allowed.Mode = frrk8sv1beta1.AllowAll
}
for i := range ppV6 {
ppV6[i].Neigh.ToReceive.Allowed.Mode = frrk8sv1beta1.AllowAll
}
},
validate: func(ppV4 []config.Peer, ppV6 []config.Peer, pods []*v1.Pod) {
for _, p := range ppV6 {
ValidateNodesHaveRoutes(pods, p.FRR, []string{"fc00:f853:ccd:e799::/64", "fc00:f853:ccd:e800::/64"}...)
}
},
}),
ginkgo.Entry("IPV6 - receive ips from some, explicit mode", params{
ipFamily: ipfamily.IPv6,
vrf: "",
myAsn: infra.FRRK8sASN,
toAdvertiseV6: []string{"fc00:f853:ccd:e799::/64", "fc00:f853:ccd:e800::/64"},
modifyPeers: func(ppV4 []config.Peer, ppV6 []config.Peer) {
ppV6[0].Neigh.ToReceive.Allowed.Prefixes = []string{"fc00:f853:ccd:e799::/64"}
},
validate: func(ppV4 []config.Peer, ppV6 []config.Peer, pods []*v1.Pod) {
ValidateNodesHaveRoutes(pods, ppV6[0].FRR, []string{"fc00:f853:ccd:e799::/64"}...)
ValidateNodesDoNotHaveRoutes(pods, ppV6[0].FRR, []string{"fc00:f853:ccd:e800::/64"}...)
for _, p := range ppV6[1:] {
ValidateNodesDoNotHaveRoutes(pods, p.FRR, []string{"fc00:f853:ccd:e799::/64", "fc00:f853:ccd:e800::/64"}...)
}
},
}),
ginkgo.Entry("IPV4 - VRF - receive ips from some, all mode", params{
ipFamily: ipfamily.IPv4,
vrf: infra.VRFName,
myAsn: infra.FRRK8sASNVRF,
toAdvertiseV4: []string{"192.168.2.0/24", "192.169.2.0/24"},
modifyPeers: func(ppV4 []config.Peer, ppV6 []config.Peer) {
ppV4[0].Neigh.ToReceive.Allowed.Mode = frrk8sv1beta1.AllowAll
},
validate: func(ppV4 []config.Peer, ppV6 []config.Peer, pods []*v1.Pod) {
ValidateNodesHaveRoutes(pods, ppV4[0].FRR, []string{"192.168.2.0/24", "192.169.2.0/24"}...)
for _, p := range ppV4[1:] {
ValidateNodesDoNotHaveRoutes(pods, p.FRR, []string{"192.168.2.0/24", "192.169.2.0/24"}...)
}
},
}),
ginkgo.Entry("IPV4 - VRF - receive ips from some, explicit mode", params{
ipFamily: ipfamily.IPv4,
vrf: infra.VRFName,
myAsn: infra.FRRK8sASNVRF,
toAdvertiseV4: []string{"192.168.2.0/24", "192.169.2.0/24", "192.170.2.0/24"},
modifyPeers: func(ppV4 []config.Peer, ppV6 []config.Peer) {
ppV4[0].Neigh.ToReceive.Allowed.Prefixes = []string{"192.168.2.0/24", "192.169.2.0/24"}
},
validate: func(ppV4 []config.Peer, ppV6 []config.Peer, pods []*v1.Pod) {
ValidateNodesHaveRoutes(pods, ppV4[0].FRR, []string{"192.168.2.0/24", "192.169.2.0/24"}...)
ValidateNodesDoNotHaveRoutes(pods, ppV4[0].FRR, []string{"192.170.2.0/24"}...)
for _, p := range ppV4[1:] {
ValidateNodesDoNotHaveRoutes(pods, p.FRR, []string{"192.168.2.0/24", "192.169.2.0/24", "192.170.2.0/24"}...)
}
},
}),
ginkgo.Entry("IPV6 - VRF - receive ips from all", params{
ipFamily: ipfamily.IPv6,
vrf: infra.VRFName,
myAsn: infra.FRRK8sASNVRF,
toAdvertiseV6: []string{"fc00:f853:ccd:e799::/64", "fc00:f853:ccd:e800::/64"},
modifyPeers: func(ppV4 []config.Peer, ppV6 []config.Peer) {
for i := range ppV4 {
ppV4[i].Neigh.ToReceive.Allowed.Mode = frrk8sv1beta1.AllowAll
}
for i := range ppV6 {
ppV6[i].Neigh.ToReceive.Allowed.Mode = frrk8sv1beta1.AllowAll
}
},
validate: func(ppV4 []config.Peer, ppV6 []config.Peer, pods []*v1.Pod) {
for _, p := range ppV6 {
ValidateNodesHaveRoutes(pods, p.FRR, []string{"fc00:f853:ccd:e799::/64", "fc00:f853:ccd:e800::/64"}...)
}
},
}),
ginkgo.Entry("DUALSTACK - receive ips from all", params{
ipFamily: ipfamily.DualStack,
vrf: "",
myAsn: infra.FRRK8sASN,
toAdvertiseV4: []string{"192.168.2.0/24", "192.169.2.0/24"},
toAdvertiseV6: []string{"fc00:f853:ccd:e799::/64", "fc00:f853:ccd:e800::/64"},
modifyPeers: func(ppV4 []config.Peer, ppV6 []config.Peer) {
for i := range ppV4 {
ppV4[i].Neigh.ToReceive.Allowed.Mode = frrk8sv1beta1.AllowAll
}
for i := range ppV6 {
ppV6[i].Neigh.ToReceive.Allowed.Mode = frrk8sv1beta1.AllowAll
}
},

validate: func(ppV4 []config.Peer, ppV6 []config.Peer, pods []*v1.Pod) {
for _, p := range ppV4 {
ValidateNodesHaveRoutes(pods, p.FRR, []string{"192.168.2.0/24", "192.169.2.0/24"}...)
}
for _, p := range ppV6 {
ValidateNodesHaveRoutes(pods, p.FRR, []string{"fc00:f853:ccd:e799::/64", "fc00:f853:ccd:e800::/64"}...)
}
},
}),
ginkgo.Entry("DUALSTACK - receive ips from some, explicit mode", params{
ipFamily: ipfamily.DualStack,
vrf: "",
myAsn: infra.FRRK8sASN,
toAdvertiseV4: []string{"192.168.2.0/24", "192.169.2.0/24"},
toAdvertiseV6: []string{"fc00:f853:ccd:e799::/64", "fc00:f853:ccd:e800::/64"},
modifyPeers: func(ppV4 []config.Peer, ppV6 []config.Peer) {
ppV4[0].Neigh.ToReceive.Allowed.Prefixes = []string{"192.169.2.0/24"}
ppV6[0].Neigh.ToReceive.Allowed.Prefixes = []string{"fc00:f853:ccd:e799::/64"}
},
validate: func(ppV4 []config.Peer, ppV6 []config.Peer, pods []*v1.Pod) {
ValidateNodesHaveRoutes(pods, ppV4[0].FRR, []string{"192.169.2.0/24"}...)
ValidateNodesDoNotHaveRoutes(pods, ppV4[0].FRR, []string{"192.168.2.0/24"}...)
for _, p := range ppV4[1:] {
ValidateNodesDoNotHaveRoutes(pods, p.FRR, []string{"192.168.2.0/24", "192.169.2.0/24"}...)
}
ValidateNodesHaveRoutes(pods, ppV6[0].FRR, []string{"fc00:f853:ccd:e799::/64"}...)
ValidateNodesDoNotHaveRoutes(pods, ppV6[0].FRR, []string{"fc00:f853:ccd:e800::/64"}...)
for _, p := range ppV6[1:] {
ValidateNodesDoNotHaveRoutes(pods, p.FRR, []string{"fc00:f853:ccd:e799::/64", "fc00:f853:ccd:e800::/64"}...)
}
},
}),
)
})
})
Loading