Skip to content

Commit

Permalink
cnf network: add frrk8 metallb test cases
Browse files Browse the repository at this point in the history
  • Loading branch information
Gregory Kopels committed Sep 22, 2024
1 parent e4ddb12 commit 4424d8b
Show file tree
Hide file tree
Showing 8 changed files with 995 additions and 12 deletions.
194 changes: 194 additions & 0 deletions tests/cnf/core/network/metallb/internal/frr/frr.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,50 @@ type (
Bestpath bool `json:"bestpath,omitempty"`
} `json:"routes"`
}

advertisedRoute struct {
AddrPrefix string `json:"addrPrefix"`
PrefixLen int `json:"prefixLen"`
Network string `json:"network"`
NextHop string `json:"nextHop"`
Metric int `json:"metric"`
LocPrf int `json:"locPrf"`
Weight int `json:"weight"`
Path string `json:"path"`
BGPOriginCode string `json:"bgpOriginCode"`
}
// BgpAdvertisedRoutes creates struct from json output.
BgpAdvertisedRoutes struct {
BGPTableVersion int `json:"bgpTableVersion"`
BGPLocalRouterID string `json:"bgpLocalRouterId"`
DefaultLocPrf int `json:"defaultLocPrf"`
LocalAS int `json:"localAS"`
AdvertisedRoutes map[string]advertisedRoute `json:"advertisedRoutes"`
}
// BgpReceivedRoutes struct includes Routes map.
BgpReceivedRoutes struct {
Routes map[string][]RouteInfo `json:"routes"`
}
// RouteInfo struct includes route info.
RouteInfo struct {
Prefix string `json:"prefix"`
PrefixLen int `json:"prefixLen"`
Protocol string `json:"protocol"`
Metric int `json:"metric"`
Uptime string `json:"uptime"`
Nexthops []Nexthop `json:"nexthops"`
}
// Nexthop struct includes nexthop route info.
Nexthop struct {
Flags int `json:"flags"`
Fib bool `json:"fib"`
IP string `json:"ip"`
Afi string `json:"afi"`
InterfaceIndex int `json:"interfaceIndex"`
InterfaceName string `json:"interfaceName"`
Active bool `json:"active"`
Weight int `json:"weight"`
}
)

// DefineBaseConfig defines minimal required FRR configuration.
Expand Down Expand Up @@ -91,6 +135,51 @@ func DefineBGPConfig(localBGPASN, remoteBGPASN int, neighborsIPAddresses []strin
return bgpConfig
}

// DefineBGPConfigWitgStaticRoutes returns string which represents BGP config file peering to all given IP addresses.
func DefineBGPConfigWitgStaticRoutes(localBGPASN, remoteBGPASN int, neighborsIPAddresses []string,
multiHop, bfd bool) string {
bgpConfig := tsparams.FRRBaseConfig + fmt.Sprintf("ip route %s/32 172.16.0.10\n", neighborsIPAddresses[1]) +
fmt.Sprintf("ip route %s/32 172.16.0.11\n!\n", neighborsIPAddresses[0]) +
fmt.Sprintf("router bgp %d\n", localBGPASN) +
tsparams.FRRDefaultBGPPreConfig

for _, ipAddress := range neighborsIPAddresses {
bgpConfig += fmt.Sprintf(" neighbor %s remote-as %d\n neighbor %s password %s\n",
ipAddress, remoteBGPASN, ipAddress, tsparams.BGPPassword)

if bfd {
bgpConfig += fmt.Sprintf(" neighbor %s bfd\n", ipAddress)
}

if multiHop {
bgpConfig += fmt.Sprintf(" neighbor %s ebgp-multihop 2\n", ipAddress)
}
}

bgpConfig += "!\naddress-family ipv4 unicast\n"
for _, ipAddress := range neighborsIPAddresses {
bgpConfig += fmt.Sprintf(" neighbor %s activate\n", ipAddress)
}

bgpConfig += " network 192.168.100.0/24\n"
bgpConfig += " network 192.168.200.0/24\n"
bgpConfig += "exit-address-family\n"

// Add network commands only once for IPv6
bgpConfig += "!\naddress-family ipv6 unicast\n"
for _, ipAddress := range neighborsIPAddresses {
bgpConfig += fmt.Sprintf(" neighbor %s activate\n", ipAddress)
}

bgpConfig += " network 2001:100::0/64\n"
bgpConfig += " network 2001:200::0/64\n"
bgpConfig += "exit-address-family\n"

bgpConfig += "!\nline vty\n!\nend\n"

return bgpConfig
}

// BGPNeighborshipHasState verifies that BGP session on a pod has given state.
func BGPNeighborshipHasState(frrPod *pod.Builder, neighborIPAddress string, state string) (bool, error) {
var result map[string]bgpDescription
Expand Down Expand Up @@ -230,3 +319,108 @@ func runningConfig(frrPod *pod.Builder) (string, error) {

return bgpStateOut.String(), nil
}

// GetBGPAdvertisedRoutes retrieves the routes advertised from the external frr pod to the frr nodes.
func GetBGPAdvertisedRoutes(frrPod *pod.Builder, nodeIPs []string) (string, error) {
var allRoutes strings.Builder

// Loop through each nodeIP and execute the command
for _, nodeIP := range nodeIPs {
// Execute the BGP command for each nodeIP
routes, err := frrPod.ExecCommand(append(netparam.VtySh,
fmt.Sprintf("sh ip bgp neighbors %s advertised-routes json", nodeIP)))
if err != nil {
return "", fmt.Errorf("error collecting BGP advertised routes from pod %s for nodeIP %s: %w",
frrPod.Definition.Name, nodeIP, err)
}

// Parse the BGP advertised routes for each nodeIP
bgpAdvertised, err := parseBGPAdvertisedRoutes(routes.String())
if err != nil {
return "", fmt.Errorf("error parsing BGP advertised routes for nodeIP %s: %w", nodeIP, err)
}

// Store the result for the current nodeIP
allRoutes.WriteString(fmt.Sprintf("Node IP: %s\n", nodeIP))
allRoutes.WriteString(bgpAdvertised)
allRoutes.WriteString("\n")
}

// Return all results combined
return allRoutes.String(), nil
}

func parseBGPAdvertisedRoutes(jsonData string) (string, error) {
var bgpRoutes BgpAdvertisedRoutes

// Parse the JSON data into the struct
err := json.Unmarshal([]byte(jsonData), &bgpRoutes)
if err != nil {
return "", fmt.Errorf("error parsing BGP advertised routes: %w", err)
}

// Format only the network values as a string
var result strings.Builder
for _, route := range bgpRoutes.AdvertisedRoutes {
result.WriteString(fmt.Sprintf("%s\n", route.Network))
}

return result.String(), nil
}

// VerifyBGPReceivedRoutes verifies routes were received via BGP.
func VerifyBGPReceivedRoutes(frrk8sPods []*pod.Builder) (string, error) {
var result strings.Builder

for _, frrk8sPod := range frrk8sPods {
// Run the "sh ip route bgp json" command on each pod
output, err := frrk8sPod.ExecCommand(append(netparam.VtySh, "sh ip route bgp json"), "frr")
if err != nil {
return "", fmt.Errorf("error collecting BGP received routes from pod %s: %w",
frrk8sPod.Definition.Name, err)
}

// Parse the JSON output to get the BGP routes
bgpRoutes, err := parseBGPReceivedRoutes(output.String())

if err != nil {
return "", fmt.Errorf("error parsing BGP JSON from pod %s: %w", frrk8sPod.Definition.Name, err)
}

// Write the pod name to the result
result.WriteString(fmt.Sprintf("Pod: %s\n", frrk8sPod.Definition.Name))

// Extract and write the prefixes (keys of the Routes map) and corresponding route info
for prefix, routeInfos := range bgpRoutes.Routes {
result.WriteString(fmt.Sprintf(" Prefix: %s\n", prefix))

for _, routeInfo := range routeInfos {
result.WriteString(fmt.Sprintf(" Route Info: Prefix: %s", routeInfo.Prefix))
}
}

result.WriteString("\n") // Add an empty line between pods
}

return result.String(), nil
}

func parseBGPReceivedRoutes(jsonData string) (*BgpReceivedRoutes, error) {
var bgpRoutes map[string][]RouteInfo // This matches the structure of your JSON

// Parse the JSON data into the struct
err := json.Unmarshal([]byte(jsonData), &bgpRoutes)
if err != nil {
return nil, fmt.Errorf("error parsing BGP received routes: %w", err)
}

// Create a new BgpReceivedRoutes struct and populate it with the parsed data
parsedRoutes := &BgpReceivedRoutes{
Routes: bgpRoutes, // The map directly holds prefixes as keys
}

// Print the parsed routes for debugging
fmt.Printf("Parsed Routes: %+v\n", bgpRoutes)

return parsedRoutes, nil
}
5 changes: 5 additions & 0 deletions tests/cnf/core/network/metallb/internal/tsparams/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,13 @@ const (
LabelBGPTestCases = "bgp"
// LabelLayer2TestCases represents layer2 label that can be used for test cases selection.
LabelLayer2TestCases = "layer2"
// LabelFRRTestCases represents frrk8 label that can be used for test cases selection.
LabelFRRTestCases = "frrk8"
// BGPPassword var is used to set password for BGP session between FRR speakers.
BGPPassword = "bgp-test"
// MlbAddressListError an error message when the ECO_CNF_CORE_NET_MLB_ADDR_LIST is incorrect.
MlbAddressListError = "An unexpected error occurred while " +
"determining the IP addresses from the ECO_CNF_CORE_NET_MLB_ADDR_LIST environment variable."
// DaemonsFile represents FRR default daemon configuration template.
DaemonsFile = `
# This file tells the frr package which daemons to start.
Expand Down
2 changes: 2 additions & 0 deletions tests/cnf/core/network/metallb/internal/tsparams/mlbvars.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ var (
FRRK8sDefaultLabel = "component=frr-k8s"
// ExternalMacVlanNADName represents default external NetworkAttachmentDefinition name.
ExternalMacVlanNADName = "external"
// HubMacVlanNADName represents default external NetworkAttachmentDefinition name.
HubMacVlanNADName = "nad-hub"
// SleepCMD represents shel sleep command.
SleepCMD = []string{"/bin/bash", "-c", "sleep INF"}
// FRRContainerName represents default FRR's container name.
Expand Down
15 changes: 9 additions & 6 deletions tests/cnf/core/network/metallb/tests/bfd-test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@ var _ = Describe("BFD", Ordered, Label(tsparams.LabelBFDTestCases), ContinueOnFa
var err error
By("Getting MetalLb load balancer ip addresses")
ipv4metalLbIPList, ipv6metalLbIPList, err = metallbenv.GetMetalLbIPByIPStack()
Expect(err).ToNot(HaveOccurred(), "An unexpected error occurred while "+
"determining the IP addresses from the ECO_CNF_CORE_NET_MLB_ADDR_LIST environment variable.")
Expect(err).ToNot(HaveOccurred(), tsparams.MlbAddressListError)

if len(ipv4metalLbIPList) < 2 {
Skip("MetalLb BFD tests require 2 ip addresses. Please check ECO_CNF_CORE_NET_MLB_ADDR_LIST env var")
Expand All @@ -60,7 +59,7 @@ var _ = Describe("BFD", Ordered, Label(tsparams.LabelBFDTestCases), ContinueOnFa

err = metallbenv.IsEnvVarMetalLbIPinNodeExtNetRange(ipv4NodeAddrList, ipv4metalLbIPList, nil)
Expect(err).ToNot(HaveOccurred(), "Failed to validate metalLb exported ip address")
createExternalNad()
createExternalNad(tsparams.ExternalMacVlanNADName)
})

Context("single hop", Label("singlehop"), func() {
Expand Down Expand Up @@ -211,7 +210,7 @@ var _ = Describe("BFD", Ordered, Label(tsparams.LabelBFDTestCases), ContinueOnFa

DescribeTable("should provide fast link failure detection", reportxml.ID("47186"),
func(bgpProtocol, ipStack string, externalTrafficPolicy corev1.ServiceExternalTrafficPolicyType) {
createExternalNad()
createExternalNad(tsparams.ExternalMacVlanNADName)

By("Verifying that speaker route map is not empty")
Expect(speakerRoutesMap).ToNot(BeNil(), "Speaker route map is empty")
Expand Down Expand Up @@ -526,8 +525,12 @@ func buildRoutesMap(podList []*pod.Builder, nextHopList []string) (map[string]st

routesMap := make(map[string]string)

for num, pod := range podList {
routesMap[pod.Definition.Spec.NodeName] = nextHopList[num]
for _, pod := range podList {
if pod.Definition.Spec.NodeName == "worker-0" {
routesMap[pod.Definition.Spec.NodeName] = nextHopList[1]
} else {
routesMap[pod.Definition.Spec.NodeName] = nextHopList[0]
}
}

return routesMap, nil
Expand Down
5 changes: 2 additions & 3 deletions tests/cnf/core/network/metallb/tests/bgp-tests.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@ var _ = Describe("BGP", Ordered, Label(tsparams.LabelBGPTestCases), ContinueOnFa
var err error
By("Getting MetalLb load balancer ip addresses")
ipv4metalLbIPList, ipv6metalLbIPList, err = metallbenv.GetMetalLbIPByIPStack()
Expect(err).ToNot(HaveOccurred(), "An unexpected error occurred while "+
"determining the IP addresses from the ECO_CNF_CORE_NET_MLB_ADDR_LIST environment variable.")
Expect(err).ToNot(HaveOccurred(), tsparams.MlbAddressListError)

By("Getting external nodes ip addresses")
cnfWorkerNodeList, err = nodes.List(APIClient,
Expand Down Expand Up @@ -64,7 +63,7 @@ var _ = Describe("BGP", Ordered, Label(tsparams.LabelBGPTestCases), ContinueOnFa

BeforeEach(func() {
By("Creating External NAD")
createExternalNad()
createExternalNad(tsparams.ExternalMacVlanNADName)

By("Listing metalLb speakers pod")
var err error
Expand Down
51 changes: 49 additions & 2 deletions tests/cnf/core/network/metallb/tests/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,22 @@ func createConfigMap(
return masterConfigMap
}

func createExternalNad() {
func createHubConfigMap(name string) *configmap.Builder {
frrBFDConfig := frr.DefineBGPConfig(
64500, tsparams.LocalBGPASN, []string{"10.10.0.10"}, false, false)
configMapData := frr.DefineBaseConfig(tsparams.DaemonsFile, frrBFDConfig, "")
hubConfigMap, err := configmap.NewBuilder(APIClient, name, tsparams.TestNamespaceName).WithData(configMapData).Create()
Expect(err).ToNot(HaveOccurred(), "Failed to create hub config map")

return hubConfigMap
}

func createExternalNad(name string) {
By("Creating external BR-EX NetworkAttachmentDefinition")

macVlanPlugin, err := define.MasterNadPlugin(coreparams.OvnExternalBridge, "bridge", nad.IPAMStatic())
Expect(err).ToNot(HaveOccurred(), "Failed to define master nad plugin")
externalNad, err = nad.NewBuilder(APIClient, tsparams.ExternalMacVlanNADName, tsparams.TestNamespaceName).
externalNad, err = nad.NewBuilder(APIClient, name, tsparams.TestNamespaceName).
WithMasterPlugin(macVlanPlugin).Create()
Expect(err).ToNot(HaveOccurred(), "Failed to create external NetworkAttachmentDefinition")
Expect(externalNad.Exists()).To(BeTrue(), "Failed to detect external NetworkAttachmentDefinition")
Expand Down Expand Up @@ -206,6 +216,43 @@ func createFrrPod(
return frrPod
}

func createFrrHubPod(
name string,
nodeName string,
configmapName string,
defaultCMD []string,
secondaryNetConfig []*types.NetworkSelectionElement,
podName ...string) *pod.Builder {
if len(podName) > 0 {
name = podName[0]
}

frrPod := pod.NewBuilder(APIClient, name, tsparams.TestNamespaceName, NetConfig.FrrImage).
DefineOnNode(nodeName).
WithTolerationToMaster().
WithSecondaryNetwork(secondaryNetConfig).
RedefineDefaultCMD(defaultCMD)

By("Creating FRR container")

if configmapName != "" {
frrContainer := pod.NewContainerBuilder(
tsparams.FRRSecondContainerName, NetConfig.CnfNetTestContainer, tsparams.SleepCMD).
WithSecurityCapabilities([]string{"NET_ADMIN", "NET_RAW", "SYS_ADMIN"}, true)

frrCtr, err := frrContainer.GetContainerCfg()
Expect(err).ToNot(HaveOccurred(), "Failed to get container configuration")
frrPod.WithAdditionalContainer(frrCtr).WithLocalVolume(configmapName, "/etc/frr")
}

By("Creating FRR pod in the test namespace")

frrPod, err := frrPod.WithPrivilegedFlag().CreateAndWaitUntilRunning(5 * time.Minute)
Expect(err).ToNot(HaveOccurred(), "Failed to create FRR test pod")

return frrPod
}

func setupMetalLbService(
ipStack string,
ipAddressPool *metallb.IPAddressPoolBuilder,
Expand Down
Loading

0 comments on commit 4424d8b

Please sign in to comment.