Skip to content

Commit c3dc1e8

Browse files
committed
e2e: add logLevel tests for MetalLB core components
- Add tests to verify logLevel default and debug - Update Webhook error message for bgpBackend type Signed-off-by: Anvesh J <[email protected]>
1 parent 8c24061 commit c3dc1e8

File tree

3 files changed

+219
-1
lines changed

3 files changed

+219
-1
lines changed

api/v1beta1/metallb_webhook.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ func (metallb *MetalLB) Validate() error {
114114
metallb.Spec.BGPBackend != FRRK8sMode &&
115115
metallb.Spec.BGPBackend != FRRK8sExternalMode &&
116116
metallb.Spec.BGPBackend != FRRMode {
117-
return errors.New("Invalid BGP Backend, must be one of native, frr, frr-k8s")
117+
return errors.New("Invalid BGP Backend, must be one of native, frr, frr-k8s, frr-k8s-external")
118118
}
119119

120120
if err := validateFRRK8sConfig(metallb.Spec); err != nil {

test/e2e/functional/tests/e2e.go

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package tests
33
import (
44
"context"
55
"fmt"
6+
"io"
67
"os"
78
"time"
89

@@ -803,6 +804,125 @@ var _ = Describe("metallb", func() {
803804
Expect(testclient.Client.Create(context.Background(), metallb)).Should(Succeed())
804805
})
805806
})
807+
808+
Context("MetalLB Core Components Log Level Feature", func() {
809+
BeforeEach(func() {
810+
metallbutils.DeleteDefaultMetalLB(OperatorNameSpace, UseMetallbResourcesFromFile)
811+
812+
})
813+
814+
AfterEach(func() {
815+
metallbutils.DeleteDefaultMetalLB(OperatorNameSpace, UseMetallbResourcesFromFile)
816+
})
817+
818+
It("Verify logLevel default and debug behavior in speaker and controller pods", func() {
819+
// Create MetalLB with default log level (info)
820+
metallb, err := metallbutils.Get(OperatorNameSpace, UseMetallbResourcesFromFile)
821+
Expect(err).ToNot(HaveOccurred())
822+
Expect(testclient.Client.Create(context.Background(), metallb)).Should(Succeed())
823+
824+
By("Waiting for MetalLB controller deployment to be ready")
825+
metallbutils.WaitForControllerDeploymentReady(metallb.Namespace, metallbutils.DeployTimeout)
826+
827+
By("Waiting for MetalLB speaker daemonset to be ready")
828+
metallbutils.WaitForSpeakerDaemonSetReady(metallb.Namespace, metallbutils.DeployTimeout)
829+
830+
By("Fetch controller pods from metallb-system namespace")
831+
controllerPods := metallbutils.GetMetalLBPods(OperatorNameSpace, "controller")
832+
833+
By("Verify default loglevel in controller pod logs - should contain info but not debug")
834+
for _, controllerPod := range controllerPods.Items {
835+
req := testclient.Client.Pods(OperatorNameSpace).GetLogs(controllerPod.Name, &corev1.PodLogOptions{
836+
Container: "controller",
837+
})
838+
logs, err := req.Stream(context.Background())
839+
Expect(err).ToNot(HaveOccurred(), "Failed to get controller pod logs")
840+
defer logs.Close()
841+
842+
logContent, err := io.ReadAll(logs)
843+
Expect(err).ToNot(HaveOccurred(), "Failed to read controller pod logs")
844+
logString := string(logContent)
845+
846+
Expect(logString).Should(ContainSubstring(`"level":"info"`), "Controller pod logs should contain info level logs by default")
847+
Expect(logString).ShouldNot(ContainSubstring(`"level":"debug"`), "Controller pod logs should not contain debug level logs when using default info log level")
848+
}
849+
850+
By("Fetch speaker pods from metallb-system namespace")
851+
speakerPods := metallbutils.GetMetalLBPods(OperatorNameSpace, "speaker")
852+
853+
By("Verify default loglevel in speaker pod logs - should contain info but not debug")
854+
for _, speakerPod := range speakerPods.Items {
855+
req := testclient.Client.Pods(OperatorNameSpace).GetLogs(speakerPod.Name, &corev1.PodLogOptions{
856+
Container: "speaker",
857+
})
858+
logs, err := req.Stream(context.Background())
859+
Expect(err).ToNot(HaveOccurred(), "Failed to get speaker pod logs")
860+
defer logs.Close()
861+
862+
logContent, err := io.ReadAll(logs)
863+
Expect(err).ToNot(HaveOccurred(), "Failed to read speaker pod logs")
864+
logString := string(logContent)
865+
866+
Expect(logString).Should(ContainSubstring(`"level":"info"`), "Speaker pod logs should contain info level logs by default")
867+
Expect(logString).ShouldNot(ContainSubstring(`"level":"debug"`), "Speaker pod logs should not contain debug level logs when using default info log level")
868+
}
869+
870+
// Update MetalLB CR to set LogLevel to debug
871+
By("Updating MetalLB CR to set LogLevel to debug")
872+
err = testclient.Client.Get(context.Background(), goclient.ObjectKey{Namespace: metallb.Namespace, Name: metallb.Name}, metallb)
873+
Expect(err).ToNot(HaveOccurred())
874+
metallb.Spec.LogLevel = metallbv1beta1.LogLevelDebug
875+
Expect(testclient.Client.Update(context.Background(), metallb)).Should(Succeed())
876+
877+
By("Waiting for Progressing condition to be True after update")
878+
metallbutils.WaitForProgressingConditionTrue(metallb, 30*time.Second)
879+
880+
By("Waiting for Progressing condition to be False and Available condition to be True")
881+
metallbutils.WaitForProgressingFalseAndAvailableTrue(metallb, metallbutils.DeployTimeout)
882+
883+
By("Fetch controller pods after update")
884+
controllerPods = metallbutils.GetMetalLBPods(OperatorNameSpace, "controller")
885+
886+
By("Verify debug loglevel in controller pod logs - should contain both debug and info")
887+
for _, controllerPod := range controllerPods.Items {
888+
req := testclient.Client.Pods(OperatorNameSpace).GetLogs(controllerPod.Name, &corev1.PodLogOptions{
889+
Container: "controller",
890+
})
891+
logs, err := req.Stream(context.Background())
892+
Expect(err).ToNot(HaveOccurred(), "Failed to get controller pod logs")
893+
defer logs.Close()
894+
895+
logContent, err := io.ReadAll(logs)
896+
Expect(err).ToNot(HaveOccurred(), "Failed to read controller pod logs")
897+
logString := string(logContent)
898+
899+
// Verify that debug log level is set - logs should contain JSON format "level":"debug"
900+
Expect(logString).Should(ContainSubstring(`"level":"debug"`), "Controller pod logs should contain debug level logs when LogLevel is set to debug")
901+
Expect(logString).Should(ContainSubstring(`"level":"info"`), "Controller pod logs should also contain info level logs when LogLevel is set to debug")
902+
}
903+
904+
By("Fetch speaker pods after update")
905+
speakerPods = metallbutils.GetMetalLBPods(OperatorNameSpace, "speaker")
906+
907+
By("Verify debug loglevel in speaker pod logs - should contain both debug and info")
908+
for _, speakerPod := range speakerPods.Items {
909+
req := testclient.Client.Pods(OperatorNameSpace).GetLogs(speakerPod.Name, &corev1.PodLogOptions{
910+
Container: "speaker",
911+
})
912+
logs, err := req.Stream(context.Background())
913+
Expect(err).ToNot(HaveOccurred(), "Failed to get speaker pod logs")
914+
defer logs.Close()
915+
916+
logContent, err := io.ReadAll(logs)
917+
Expect(err).ToNot(HaveOccurred(), "Failed to read speaker pod logs")
918+
logString := string(logContent)
919+
920+
// Verify that debug log level is set - logs should contain JSON format "level":"debug"
921+
Expect(logString).Should(ContainSubstring(`"level":"debug"`), "Speaker pod logs should contain debug level logs when LogLevel is set to debug")
922+
Expect(logString).Should(ContainSubstring(`"level":"info"`), "Speaker pod logs should also contain info level logs when LogLevel is set to debug")
923+
}
924+
})
925+
})
806926
})
807927

808928
// Gomega transformation functions for v1.Container

test/e2e/metallb/metallb.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/metallb/metallb-operator/pkg/status"
1414
"github.com/metallb/metallb-operator/test/consts"
1515
testclient "github.com/metallb/metallb-operator/test/e2e/client"
16+
v1 "k8s.io/api/core/v1"
1617
schv1 "k8s.io/api/scheduling/v1"
1718
"k8s.io/apimachinery/pkg/api/errors"
1819
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -142,3 +143,100 @@ func DeletePriorityClass(pc *schv1.PriorityClass) {
142143
}
143144
Expect(err).ToNot(HaveOccurred())
144145
}
146+
147+
// WaitForControllerDeploymentReady waits for the MetalLB controller deployment to be ready
148+
func WaitForControllerDeploymentReady(namespace string, timeout time.Duration) {
149+
Eventually(func() error {
150+
deploy, err := testclient.Client.Deployments(namespace).Get(context.Background(), consts.MetalLBDeploymentName, metav1.GetOptions{})
151+
if err != nil {
152+
return err
153+
}
154+
155+
if deploy.Status.ReadyReplicas != deploy.Status.Replicas {
156+
return fmt.Errorf("deployment %s is not ready, expected %d ready replicas got %d", consts.MetalLBDeploymentName, deploy.Status.Replicas, deploy.Status.ReadyReplicas)
157+
}
158+
159+
return nil
160+
}, timeout, Interval).ShouldNot(HaveOccurred())
161+
}
162+
163+
// WaitForSpeakerDaemonSetReady waits for the MetalLB speaker daemonset to be ready
164+
func WaitForSpeakerDaemonSetReady(namespace string, timeout time.Duration) {
165+
Eventually(func() error {
166+
daemonset, err := testclient.Client.DaemonSets(namespace).Get(context.Background(), consts.MetalLBDaemonsetName, metav1.GetOptions{})
167+
if err != nil {
168+
return err
169+
}
170+
171+
if daemonset.Status.DesiredNumberScheduled == 0 {
172+
return fmt.Errorf("daemonset %s has no desired pods scheduled", consts.MetalLBDaemonsetName)
173+
}
174+
if daemonset.Status.DesiredNumberScheduled != daemonset.Status.NumberReady {
175+
return fmt.Errorf("daemonset %s is not ready, expected %d ready pods got %d", consts.MetalLBDaemonsetName, daemonset.Status.DesiredNumberScheduled, daemonset.Status.NumberReady)
176+
}
177+
178+
return nil
179+
}, timeout, Interval).ShouldNot(HaveOccurred())
180+
}
181+
182+
// WaitForProgressingConditionTrue waits for the MetalLB Progressing condition to be True
183+
func WaitForProgressingConditionTrue(metallb *metallbv1beta1.MetalLB, timeout time.Duration) {
184+
Eventually(func() bool {
185+
config := &metallbv1beta1.MetalLB{}
186+
err := testclient.Client.Get(context.Background(), goclient.ObjectKey{Namespace: metallb.Namespace, Name: metallb.Name}, config)
187+
if err != nil {
188+
return false
189+
}
190+
if config.Status.Conditions == nil {
191+
return false
192+
}
193+
for _, condition := range config.Status.Conditions {
194+
if condition.Type == status.ConditionProgressing && condition.Status == metav1.ConditionTrue {
195+
return true
196+
}
197+
}
198+
return false
199+
}, timeout, Interval).Should(BeTrue(), "Progressing condition should be True after update")
200+
}
201+
202+
// WaitForProgressingFalseAndAvailableTrue waits for the MetalLB Progressing condition to be False and Available condition to be True
203+
func WaitForProgressingFalseAndAvailableTrue(metallb *metallbv1beta1.MetalLB, timeout time.Duration) {
204+
Eventually(func() bool {
205+
config := &metallbv1beta1.MetalLB{}
206+
err := testclient.Client.Get(context.Background(), goclient.ObjectKey{Namespace: metallb.Namespace, Name: metallb.Name}, config)
207+
if err != nil {
208+
return false
209+
}
210+
if config.Status.Conditions == nil {
211+
return false
212+
}
213+
progressingFalse := false
214+
availableTrue := false
215+
for _, condition := range config.Status.Conditions {
216+
if condition.Type == status.ConditionProgressing && condition.Status == metav1.ConditionFalse {
217+
progressingFalse = true
218+
}
219+
if condition.Type == status.ConditionAvailable && condition.Status == metav1.ConditionTrue {
220+
availableTrue = true
221+
}
222+
}
223+
return progressingFalse && availableTrue
224+
}, timeout, Interval).Should(BeTrue(), "Progressing should be False and Available should be True after update completes")
225+
}
226+
227+
func DeleteDefaultMetalLB(namespace string, useMetallbResourcesFromFile bool) {
228+
metallb, err := Get(namespace, useMetallbResourcesFromFile)
229+
Expect(err).ToNot(HaveOccurred(), "Failed to get MetalLB resource")
230+
DeleteAndCheck(metallb)
231+
}
232+
233+
// GetMetalLBPods fetches pods for a given MetalLB component (controller or speaker)
234+
// and validates that the list is not empty
235+
func GetMetalLBPods(namespace string, component string) *v1.PodList {
236+
pods, err := testclient.Client.Pods(namespace).List(context.Background(), metav1.ListOptions{
237+
LabelSelector: fmt.Sprintf("component=%s", component),
238+
})
239+
Expect(err).ToNot(HaveOccurred(), fmt.Sprintf("Failed to list %s pods", component))
240+
Expect(len(pods.Items)).Should(BeNumerically(">", 0), fmt.Sprintf("%s Pods List should not be empty", component))
241+
return pods
242+
}

0 commit comments

Comments
 (0)