Skip to content

Commit

Permalink
cnf network: add bgp connect time test cases
Browse files Browse the repository at this point in the history
  • Loading branch information
Gregory Kopels committed Sep 23, 2024
1 parent f976a46 commit d7b2926
Show file tree
Hide file tree
Showing 3 changed files with 372 additions and 0 deletions.
94 changes: 94 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,54 @@ 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"`
}
// BGPConnectionInfo struct includes the connectRetryTimer.
BGPConnectionInfo struct {
ConnectRetryTimer int `json:"connectRetryTimer"`
}
)

// DefineBaseConfig defines minimal required FRR configuration.
Expand Down Expand Up @@ -230,3 +278,49 @@ func runningConfig(frrPod *pod.Builder) (string, error) {

return bgpStateOut.String(), nil
}

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

for _, frrk8sPod := range frrk8sPods {
// Run the "show bgp neighbor <bgpPeerIP> json" command on each pod
output, err := frrk8sPod.ExecCommand(append(netparam.VtySh,
fmt.Sprintf("show bgp neighbor %s json", bgpPeerIP)), "frr")
if err != nil {
return "", fmt.Errorf("error collecting BGP neighbor info from pod %s: %w",
frrk8sPod.Definition.Name, err)
}

// Parsing JSON
var bgpData map[string]BGPConnectionInfo
err = json.Unmarshal(output.Bytes(), &bgpData)

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

// Extracting ConnectRetryTimer from the parsed JSON
for _, bgpInfo := range bgpData {
// Check the ConnectRetryTimer and log the output
result.WriteString(fmt.Sprintf("ConnectRetryTimer: %d\n", bgpInfo.ConnectRetryTimer))

// Optional: Compare the ConnectRetryTimer with the expected bgpConnectTimer
if bgpInfo.ConnectRetryTimer != bgpConnectTimer {
result.WriteString(fmt.Sprintf("Mismatch in pod %s: expected %d, got %d\n",
frrk8sPod.Definition.Name, bgpConnectTimer, bgpInfo.ConnectRetryTimer))
}
}
}

return result.String(), nil
}

// ResetBGPConnection restarts the TCP connection.
func ResetBGPConnection(frrPod *pod.Builder) error {
glog.V(90).Infof("Resetting BGP session to all neighbors: %s", frrPod.Definition.Name)

_, err := frrPod.ExecCommand(append(netparam.VtySh, "clear ip bgp *"))

return err
}
252 changes: 252 additions & 0 deletions tests/cnf/core/network/metallb/tests/bgp-connect-time-tests.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
package tests

import (
"fmt"
"time"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/openshift-kni/eco-goinfra/pkg/configmap"
"github.com/openshift-kni/eco-goinfra/pkg/metallb"
"github.com/openshift-kni/eco-goinfra/pkg/nad"
"github.com/openshift-kni/eco-goinfra/pkg/namespace"
"github.com/openshift-kni/eco-goinfra/pkg/nodes"
"github.com/openshift-kni/eco-goinfra/pkg/pod"
"github.com/openshift-kni/eco-goinfra/pkg/reportxml"
"github.com/openshift-kni/eco-goinfra/pkg/service"
. "github.com/openshift-kni/eco-gotests/tests/cnf/core/network/internal/netinittools"
"github.com/openshift-kni/eco-gotests/tests/cnf/core/network/metallb/internal/frr"
"github.com/openshift-kni/eco-gotests/tests/cnf/core/network/metallb/internal/metallbenv"
"github.com/openshift-kni/eco-gotests/tests/cnf/core/network/metallb/internal/tsparams"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
)

var _ = Describe("FRR", Ordered, Label(tsparams.LabelBGPTestCases), ContinueOnFailure, func() {

BeforeAll(func() {
var (
err error
)

By("Getting MetalLb load balancer ip addresses")
ipv4metalLbIPList, ipv6metalLbIPList, err = metallbenv.GetMetalLbIPByIPStack()
Expect(err).ToNot(HaveOccurred(), "An error occurred while "+
"determining the IP addresses from ECO_CNF_CORE_NET_MLB_ADDR_LIST environment variable.")

By("Getting external nodes ip addresses")
cnfWorkerNodeList, err = nodes.List(APIClient,
metav1.ListOptions{LabelSelector: labels.Set(NetConfig.WorkerLabelMap).String()})
Expect(err).ToNot(HaveOccurred(), "Failed to discover worker nodes")

By("Selecting worker node for BGP tests")
workerLabelMap, workerNodeList = setWorkerNodeListAndLabelForBfdTests(cnfWorkerNodeList, metalLbTestsLabel)
ipv4NodeAddrList, err = nodes.ListExternalIPv4Networks(
APIClient, metav1.ListOptions{LabelSelector: labels.Set(workerLabelMap).String()})
Expect(err).ToNot(HaveOccurred(), "Failed to collect external nodes ip addresses")

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

By("Listing master nodes")
masterNodeList, err = nodes.List(APIClient,
metav1.ListOptions{LabelSelector: labels.Set(NetConfig.ControlPlaneLabelMap).String()})
Expect(err).ToNot(HaveOccurred(), "Fail to list master nodes")
Expect(len(masterNodeList)).To(BeNumerically(">", 0),
"Failed to detect master nodes")
})

BeforeEach(func() {
By("Creating a new instance of MetalLB Speakers on workers")
err := metallbenv.CreateNewMetalLbDaemonSetAndWaitUntilItsRunning(tsparams.DefaultTimeout, workerLabelMap)
Expect(err).ToNot(HaveOccurred(), "Failed to recreate metalLb daemonset")

_, err = pod.List(APIClient, NetConfig.MlbOperatorNamespace, metav1.ListOptions{
LabelSelector: tsparams.FRRK8sDefaultLabel,
})
Expect(err).ToNot(HaveOccurred(), "Fail to list speaker pods")

})

AfterEach(func() {
By("Cleaning MetalLb operator namespace")
metalLbNs, err := namespace.Pull(APIClient, NetConfig.MlbOperatorNamespace)
Expect(err).ToNot(HaveOccurred(), "Failed to pull metalLb operator namespace")
err = metalLbNs.CleanObjects(
tsparams.DefaultTimeout,
metallb.GetBGPPeerGVR(),
metallb.GetBFDProfileGVR(),
metallb.GetBGPPeerGVR(),
metallb.GetBGPAdvertisementGVR(),
metallb.GetIPAddressPoolGVR(),
metallb.GetMetalLbIoGVR())
Expect(err).ToNot(HaveOccurred(), "Failed to remove object's from operator namespace")

By("Cleaning test namespace")
err = namespace.NewBuilder(APIClient, tsparams.TestNamespaceName).CleanObjects(
tsparams.DefaultTimeout,
pod.GetGVR(),
service.GetServiceGVR(),
configmap.GetGVR(),
nad.GetGVR())
Expect(err).ToNot(HaveOccurred(), "Failed to clean test namespace")
})

It("Verify configuration of a FRR node router peer with the connectTime less than the default of 120 seconds",
reportxml.ID("74414"), func() {

By("Collecting information before test")
frrk8sPods, err := pod.List(APIClient, NetConfig.MlbOperatorNamespace, metav1.ListOptions{
LabelSelector: tsparams.FRRK8sDefaultLabel,
})
Expect(err).ToNot(HaveOccurred(), "Failed to list speaker pods")

By("Creating BGP Peers with 10 second retry connect timer")
createBGPPeerWithConnectTimeAndVerifyIfItsReady(metav1.Duration{Duration: 10 * time.Second},
ipv4metalLbIPList[0], "", 64500, false, frrk8sPods)

By("Validate BGP Peers with 10 second retry connect timer")
frrk8sPods, err = pod.List(APIClient, NetConfig.MlbOperatorNamespace, metav1.ListOptions{
LabelSelector: "app=frr-k8s",
})
Expect(err).ToNot(HaveOccurred(), "Fail to find Frrk8 pod list")

By("Verify BGP connect timer for bgppeer")
Eventually(func() string {
// Get the routes
connectTime, err := frr.VerifyBGPConnectTime(frrk8sPods, "10.46.81.131", 10)
Expect(err).ToNot(HaveOccurred(), "Failed to verify BGP connect time")

// Return the routes to be checked
return connectTime
}, 60*time.Second, 5*time.Second).Should(ContainSubstring("ConnectRetryTimer: 10"),
"Fail to find received BGP connect time")
})

It("Verify the retry timers connect on an established Metallb neighbor with a timer connect less "+
"then the default of 120",
reportxml.ID("74416"), func() {

By("Collecting information before test")
frrk8sPods, err := pod.List(APIClient, NetConfig.MlbOperatorNamespace, metav1.ListOptions{
LabelSelector: tsparams.FRRK8sDefaultLabel,
})
Expect(err).ToNot(HaveOccurred(), "Failed to list speaker pods")

By("Create an external FRR Pod")
frrPod := createAndDeployFRRPod()

By("Creating BGP Peers with 10 second retry connect timer")
createBGPPeerWithConnectTimeAndVerifyIfItsReady(metav1.Duration{Duration: 10 * time.Second},
ipv4metalLbIPList[0], "", 64500, false, frrk8sPods)

By("Validate BGP Peers with 10 second retry connect timer")
frrk8sPods, err = pod.List(APIClient, NetConfig.MlbOperatorNamespace, metav1.ListOptions{
LabelSelector: "app=frr-k8s",
})
Expect(err).ToNot(HaveOccurred(), "Fail to find Frrk8 pod list")

By("Verify BGP connect timer for bgppeer")
Eventually(func() string {
// Get the routes
connectTime, err := frr.VerifyBGPConnectTime(frrk8sPods, "10.46.81.131", 10)
Expect(err).ToNot(HaveOccurred(), "Failed to verify BGP connect time")

// Return the routes to be checked
return connectTime
}, 60*time.Second, 5*time.Second).Should(ContainSubstring("ConnectRetryTimer: 10"),
"Fail to find received BGP connect time")

By("Reset the BGP session ")
err = frr.ResetBGPConnection(frrPod)
Expect(err).ToNot(HaveOccurred(), "Failed to reset BGP connection")

By("Verify that BGP session is established and up in less then 10 seconds")
verifyMetalLbBGPConnectTime(frrPod, removePrefixFromIPList(ipv4NodeAddrList))
})

It("Update the timer to less then the default on an existing BGP connection",
reportxml.ID("74417"), func() {

By("Collecting information before test")
frrk8sPods, err := pod.List(APIClient, NetConfig.MlbOperatorNamespace, metav1.ListOptions{
LabelSelector: tsparams.FRRK8sDefaultLabel,
})
Expect(err).ToNot(HaveOccurred(), "Failed to list speaker pods")

By("Creating BGP Peers")
createBGPPeerAndVerifyIfItsReady(ipv4metalLbIPList[0], "", 64500, false, frrk8sPods)

By("Validate BGP Peers with the default retry connect timer")
frrk8sPods, err = pod.List(APIClient, NetConfig.MlbOperatorNamespace, metav1.ListOptions{
LabelSelector: "app=frr-k8s",
})
Expect(err).ToNot(HaveOccurred(), "Fail to find Frrk8 pod list")

Eventually(func() string {
// Get the routes
connectTime, err := frr.VerifyBGPConnectTime(frrk8sPods, "10.46.81.131", 120)
Expect(err).ToNot(HaveOccurred(), "Failed to verify BGP connect time")

// Return the routes to be checked
return connectTime
}, 60*time.Second, 5*time.Second).Should(ContainSubstring("ConnectRetryTimer: 120"),
"Fail to find received BGP connect time")

By("Update the BGP Peers connect timer to 10 seconds")
bgpPeer, err := metallb.PullBGPPeer(APIClient, "testpeer", NetConfig.MlbOperatorNamespace)
Expect(err).ToNot(HaveOccurred(), "Failed to find bgp peer")

_, err = bgpPeer.WithConnectTime(metav1.Duration{Duration: 10 * time.Second}).Update(true)
Expect(err).ToNot(HaveOccurred(), "Failed to update the bgp peer with a 10s connect timer")

By("Validate BGP Peers with the default retry connect timer")
Eventually(func() string {
// Get the routes
connectTime, err := frr.VerifyBGPConnectTime(frrk8sPods, "10.46.81.131", 10)
Expect(err).ToNot(HaveOccurred(), "Failed to verify BGP connect time")

// Return the routes to be checked
return connectTime
}, 60*time.Second, 5*time.Second).Should(ContainSubstring("ConnectRetryTimer: 10"),
"Fail to find received BGP connect time")
})
})

func createAndDeployFRRPod() *pod.Builder {
var err error

By("Creating External NAD")
createExternalNad()

By("Creating static ip annotation")

staticIPAnnotation := pod.StaticIPAnnotation(
externalNad.Definition.Name, []string{fmt.Sprintf("%s/%s", ipv4metalLbIPList[0], "24")})

By("Creating MetalLb configMap")

masterConfigMap := createConfigMap(tsparams.LocalBGPASN, ipv4NodeAddrList, false, false)

By("Creating FRR Pod")

frrPod := createFrrPod(
masterNodeList[0].Object.Name, masterConfigMap.Definition.Name, []string{}, staticIPAnnotation)

time.Sleep(20 * time.Second)
Eventually(func() error {
return err
}, time.Minute, tsparams.DefaultRetryInterval).ShouldNot(HaveOccurred(),
"Failed to collect metrics from speaker pods")

return frrPod
}

func verifyMetalLbBGPConnectTime(frrPod *pod.Builder, peerAddrList []string) {
for _, peerAddress := range removePrefixFromIPList(peerAddrList) {
Eventually(frr.BGPNeighborshipHasState,
time.Second*10, time.Second).
WithArguments(frrPod, peerAddress, "Established").Should(
BeTrue(), "Failed to receive BGP status UP")
}
}
26 changes: 26 additions & 0 deletions tests/cnf/core/network/metallb/tests/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/openshift-kni/eco-gotests/tests/cnf/core/network/metallb/internal/tsparams"
"gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/types"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// test cases variables that are accessible across entire package.
Expand Down Expand Up @@ -284,3 +285,28 @@ func metalLbDaemonSetShouldMatchConditionAndBeInReadyState(
}, tsparams.DefaultTimeout, tsparams.DefaultRetryInterval).Should(expectedCondition, errorMessage)
Expect(metalLbDs.IsReady(120*time.Second)).To(BeTrue(), "MetalLb daemonSet is not Ready")
}

func createBGPPeerWithConnectTimeAndVerifyIfItsReady(connectTime metav1.Duration,
peerIP, bfdProfileName string, remoteAsn uint32, eBgpMultiHop bool, frrk8sPods []*pod.Builder) {
By("Creating BGP Peer")

bgpPeer := metallb.NewBPGPeerBuilder(APIClient, "testpeer", NetConfig.MlbOperatorNamespace,
peerIP, tsparams.LocalBGPASN, remoteAsn).WithPassword(tsparams.BGPPassword).WithEBGPMultiHop(eBgpMultiHop)

bgpPeer.WithConnectTime(connectTime)

if bfdProfileName != "" {
bgpPeer.WithBFDProfile(bfdProfileName)
}

_, err := bgpPeer.Create()
Expect(err).ToNot(HaveOccurred(), "Failed to create BGP peer")

By("Verifying if BGP protocol configured")

for _, frrk8sPod := range frrk8sPods {
Eventually(frr.IsProtocolConfigured,
time.Minute, tsparams.DefaultRetryInterval).WithArguments(frrk8sPod, "router bgp").
Should(BeTrue(), "BGP is not configured on the Speakers")
}
}

0 comments on commit d7b2926

Please sign in to comment.