Skip to content

Commit 491c2f4

Browse files
committed
tests/reporter: Add gauge scrape
Print the gauge scrape, and also print the VMIs that are attached to it, for easy debugging. Signed-off-by: Ram Lavi <ralavi@redhat.com>
1 parent 84e6a06 commit 491c2f4

File tree

1 file changed

+134
-0
lines changed

1 file changed

+134
-0
lines changed

tests/tests_suite_test.go

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"fmt"
88
"io"
99
"os"
10+
"regexp"
1011
"strings"
1112
"testing"
1213

@@ -18,6 +19,7 @@ import (
1819
kubevirtv1 "kubevirt.io/api/core/v1"
1920

2021
"github.com/k8snetworkplumbingwg/kubemacpool/pkg/names"
22+
"github.com/k8snetworkplumbingwg/kubemacpool/tests/kubectl"
2123
"github.com/k8snetworkplumbingwg/kubemacpool/tests/reporter"
2224
)
2325

@@ -144,6 +146,11 @@ func dumpKubemacpoolLogs(failureCount int) {
144146
if err := logNodes(failureCount); err != nil {
145147
fmt.Println(err)
146148
}
149+
150+
// Dump collision gauge values to help debug alert flakiness / lingering collisions.
151+
if err := logMACCollisionGauge(failureCount); err != nil {
152+
fmt.Println(err)
153+
}
147154
}
148155

149156
func logPodContainersLogs(podName string, containers []corev1.Container, failureCount int) error {
@@ -588,3 +595,130 @@ func logNodes(failureCount int) error {
588595

589596
return nil
590597
}
598+
599+
func logMACCollisionGauge(failureCount int) error {
600+
report, err := macCollisionGaugeReport()
601+
if strings.TrimSpace(report) == "" {
602+
report = "(no kmp_mac_collisions metrics found)"
603+
}
604+
605+
if err != nil {
606+
report = fmt.Sprintf("failed to build kmp mac collision gauge report: %v\n\n%s", err, report)
607+
}
608+
609+
if logErr := reporter.LogToFile("kmp_mac_collisions_gauge", report, artifactDir, failureCount); logErr != nil {
610+
return fmt.Errorf("failed to log mac collision gauge report: %w", logErr)
611+
}
612+
613+
return err
614+
}
615+
616+
func macCollisionGaugeReport() (string, error) {
617+
var b strings.Builder
618+
619+
token, stderr, err := getPrometheusToken()
620+
if err != nil {
621+
return "", fmt.Errorf("failed to get prometheus token: %s: %w", stderr, err)
622+
}
623+
624+
metrics, err := getMetrics(token)
625+
if err != nil {
626+
return "", fmt.Errorf("failed to scrape /metrics: %w", err)
627+
}
628+
629+
vmiByMAC, err := getVMIByMAC()
630+
if err != nil {
631+
// Keep whatever collision lines we can so the output is still useful.
632+
appendMACCollisionLines(&b, metrics, nil)
633+
return strings.TrimSpace(b.String()), err
634+
}
635+
636+
appendMACCollisionLines(&b, metrics, vmiByMAC)
637+
return strings.TrimSpace(b.String()), nil
638+
}
639+
640+
func getVMIByMAC() (map[string][]string, error) {
641+
const vmiJSONPath = `{range .items[*]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{.status.interfaces[*].mac}{"\n"}{end}`
642+
allVMIOutput, vmiStderr, vmiErr := kubectl.Kubectl(
643+
"get", "vmi", "-A",
644+
"-o", "jsonpath="+vmiJSONPath,
645+
)
646+
if vmiErr != nil {
647+
return nil, fmt.Errorf("failed to run kubectl get vmi: %s: %w", vmiStderr, vmiErr)
648+
}
649+
650+
vmiByMAC := map[string][]string{}
651+
for _, line := range strings.Split(allVMIOutput, "\n") {
652+
line = strings.TrimSpace(line)
653+
if line == "" {
654+
continue
655+
}
656+
657+
parts := strings.Split(line, "\t")
658+
// Expected: <namespace>\t<name>\t<macs...>
659+
if len(parts) < 3 {
660+
continue
661+
}
662+
663+
namespace := strings.TrimSpace(parts[0])
664+
name := strings.TrimSpace(parts[1])
665+
macsField := strings.TrimSpace(strings.Join(parts[2:], "\t"))
666+
if namespace == "" || name == "" || macsField == "" {
667+
continue
668+
}
669+
670+
vmiID := namespace + "/" + name
671+
for _, mac := range strings.Fields(macsField) {
672+
mac = strings.ToLower(strings.TrimSpace(mac))
673+
if mac == "" {
674+
continue
675+
}
676+
vmiByMAC[mac] = append(vmiByMAC[mac], vmiID)
677+
}
678+
}
679+
680+
return vmiByMAC, nil
681+
}
682+
683+
func appendMACCollisionLines(b *strings.Builder, metrics string, vmiByMAC map[string][]string) {
684+
for _, line := range strings.Split(metrics, "\n") {
685+
if !strings.HasPrefix(line, macCollisionMetric+"{") {
686+
continue
687+
}
688+
689+
b.WriteString(line)
690+
b.WriteString("\n")
691+
692+
mac, ok := parseMACLabelValue(line)
693+
if !ok {
694+
continue
695+
}
696+
697+
mac = strings.ToLower(mac)
698+
b.WriteString(fmt.Sprintf("MAC %s VMI matches:\n", mac))
699+
700+
if vmiByMAC == nil {
701+
b.WriteString("(skipped: failed listing VMIs)\n")
702+
continue
703+
}
704+
705+
matches := vmiByMAC[mac]
706+
if len(matches) == 0 {
707+
b.WriteString("(no matching VMIs found)\n")
708+
continue
709+
}
710+
711+
b.WriteString(strings.Join(matches, "\n"))
712+
b.WriteString("\n")
713+
}
714+
}
715+
716+
func parseMACLabelValue(line string) (string, bool) {
717+
var macLabelValueRE = regexp.MustCompile(`\bmac="([^"]+)"`)
718+
719+
m := macLabelValueRE.FindStringSubmatch(line)
720+
if len(m) != 2 {
721+
return "", false
722+
}
723+
return m[1], true
724+
}

0 commit comments

Comments
 (0)