Skip to content

Commit 6b01038

Browse files
committed
introduce tests to check whether workqueue metrics exist
Signed-off-by: chaosi-zju <[email protected]>
1 parent ba1e68d commit 6b01038

File tree

3 files changed

+218
-1
lines changed

3 files changed

+218
-1
lines changed

test/e2e/metrics_test.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
Copyright 2023 The Karmada Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package e2e
18+
19+
import (
20+
"context"
21+
22+
"github.com/onsi/ginkgo/v2"
23+
"github.com/onsi/gomega"
24+
"github.com/prometheus/common/model"
25+
"k8s.io/component-base/metrics/testutil"
26+
27+
testhelper "github.com/karmada-io/karmada/test/helper"
28+
)
29+
30+
var _ = ginkgo.Describe("metrics testing", func() {
31+
var grabber *testhelper.Grabber
32+
33+
const workqueueQueueDuration = "workqueue_queue_duration_seconds_sum"
34+
35+
ginkgo.BeforeEach(func() {
36+
var err error
37+
grabber, err = testhelper.NewMetricsGrabber(context.TODO(), hostKubeClient)
38+
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
39+
})
40+
41+
ginkgo.Context("workqueue metrics testing", func() {
42+
43+
ginkgo.It("verify whether workqueue metrics exist", func() {
44+
var metrics *testutil.Metrics
45+
var workqueueQueueDurationMetrics model.Samples
46+
47+
ginkgo.By("1. grab metrics from karmada controller", func() {
48+
// the output format of `metrics` is like:
49+
// [{
50+
// "metric": {
51+
// "__name__": "workqueue_queue_duration_seconds_sum",
52+
// "controller": "work-status-controller",
53+
// "name": "work-status-controller"
54+
// },
55+
// "value": [0, "0.12403110800000001"]
56+
//}]
57+
var err error
58+
metrics, err = grabber.GrabMetricsFromKarmadaControllerManager(context.TODO())
59+
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
60+
})
61+
62+
ginkgo.By("2. verify whether workqueue metrics exist", func() {
63+
workqueueQueueDurationMetrics = (*metrics)[workqueueQueueDuration]
64+
gomega.Expect(workqueueQueueDurationMetrics.Len()).Should(gomega.BeNumerically(">", 0))
65+
})
66+
67+
ginkgo.By("3. verify the value of work-status-controller metric greater than 0", func() {
68+
workStatusMetric := testhelper.GetMetricByName(workqueueQueueDurationMetrics, "work-status-controller")
69+
gomega.Expect(workStatusMetric).ShouldNot(gomega.BeNil())
70+
gomega.Expect(workStatusMetric.Value).Should(gomega.BeNumerically(">", 0))
71+
})
72+
})
73+
})
74+
})

test/e2e/suite_test.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,11 +103,13 @@ var (
103103
)
104104

105105
var (
106+
hostContext string
106107
karmadaContext string
107108
kubeconfig string
108109
karmadactlPath string
109110
restConfig *rest.Config
110111
karmadaHost string
112+
hostKubeClient kubernetes.Interface
111113
kubeClient kubernetes.Interface
112114
karmadaClient karmada.Interface
113115
dynamicClient dynamic.Interface
@@ -125,7 +127,8 @@ func init() {
125127
// eg. ginkgo -v --race --trace --fail-fast -p --randomize-all ./test/e2e/ -- --poll-interval=5s --poll-timeout=5m
126128
flag.DurationVar(&pollInterval, "poll-interval", 5*time.Second, "poll-interval defines the interval time for a poll operation")
127129
flag.DurationVar(&pollTimeout, "poll-timeout", 300*time.Second, "poll-timeout defines the time which the poll operation times out")
128-
flag.StringVar(&karmadaContext, "karmada-context", karmadaContext, "Name of the cluster context in control plane kubeconfig file.")
130+
flag.StringVar(&hostContext, "host-context", "karmada-host", "Name of the host cluster context in control plane kubeconfig file.")
131+
flag.StringVar(&karmadaContext, "karmada-context", "karmada-apiserver", "Name of the karmada cluster context in control plane kubeconfig file.")
129132
}
130133

131134
func TestE2E(t *testing.T) {
@@ -148,6 +151,13 @@ var _ = ginkgo.SynchronizedBeforeSuite(func() []byte {
148151
gomega.Expect(karmadactlPath).ShouldNot(gomega.BeEmpty())
149152

150153
clusterProvider = cluster.NewProvider()
154+
155+
restConfig, err = framework.LoadRESTClientConfig(kubeconfig, hostContext)
156+
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
157+
158+
hostKubeClient, err = kubernetes.NewForConfig(restConfig)
159+
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
160+
151161
restConfig, err = framework.LoadRESTClientConfig(kubeconfig, karmadaContext)
152162
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
153163

test/helper/metrics.go

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/*
2+
Copyright 2023 The Karmada Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package helper
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"regexp"
23+
"time"
24+
25+
"github.com/prometheus/common/model"
26+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27+
"k8s.io/apimachinery/pkg/util/wait"
28+
clientset "k8s.io/client-go/kubernetes"
29+
"k8s.io/component-base/metrics/testutil"
30+
"k8s.io/klog/v2"
31+
)
32+
33+
const (
34+
karmadaNamespace = "karmada-system"
35+
karmadaControllerManagerDeploy = "karmada-controller-manager"
36+
karmadaControllerManagerPort = 8080
37+
karmadaControllerManagerLeaderMetric = "leader_election_master_status"
38+
)
39+
40+
type Grabber struct {
41+
hostKubeClient clientset.Interface
42+
controllerManagerPods []string
43+
}
44+
45+
func NewMetricsGrabber(ctx context.Context, c clientset.Interface) (*Grabber, error) {
46+
grabber := Grabber{hostKubeClient: c}
47+
regKarmadaControllerManager := regexp.MustCompile(karmadaControllerManagerDeploy + "-.*")
48+
49+
podList, err := c.CoreV1().Pods(karmadaNamespace).List(ctx, metav1.ListOptions{})
50+
if err != nil {
51+
return nil, err
52+
}
53+
if len(podList.Items) < 1 {
54+
klog.Warningf("Can't find any pods in namespace %s to grab metrics from", karmadaNamespace)
55+
}
56+
for _, pod := range podList.Items {
57+
if regKarmadaControllerManager.MatchString(pod.Name) {
58+
grabber.controllerManagerPods = append(grabber.controllerManagerPods, pod.Name)
59+
}
60+
}
61+
return &grabber, nil
62+
}
63+
64+
// GrabMetricsFromKarmadaControllerManager fetch metrics from the leader of karmada controller manager
65+
func (g *Grabber) GrabMetricsFromKarmadaControllerManager(ctx context.Context) (*testutil.Metrics, error) {
66+
var output string
67+
var lastMetricsFetchErr error
68+
69+
var result *testutil.Metrics
70+
// judge which pod is the leader of karmada controller manager
71+
for _, podName := range g.controllerManagerPods {
72+
if metricsWaitErr := wait.PollUntilContextTimeout(ctx, time.Second, 10*time.Second, true, func(ctx context.Context) (bool, error) {
73+
output, lastMetricsFetchErr = GetMetricsFromPod(ctx, g.hostKubeClient, podName, karmadaNamespace, karmadaControllerManagerPort)
74+
return lastMetricsFetchErr == nil, nil
75+
}); metricsWaitErr != nil {
76+
klog.Errorf("error waiting for %s to expose metrics: %v; %v", podName, metricsWaitErr, lastMetricsFetchErr)
77+
continue
78+
}
79+
80+
podMetrics := testutil.Metrics{}
81+
metricsParseErr := testutil.ParseMetrics(output, &podMetrics)
82+
if metricsParseErr != nil {
83+
klog.Errorf("failed to parse metrics for %s: %v", podName, metricsParseErr)
84+
continue
85+
}
86+
87+
if !isKarmadaControllerManagerLeader(podMetrics[karmadaControllerManagerLeaderMetric]) {
88+
klog.Infof("skip fetch %s since it is not the leader pod", podName)
89+
continue
90+
}
91+
92+
result = &podMetrics
93+
break
94+
}
95+
96+
if result == nil {
97+
return nil, fmt.Errorf("failed to fetch metrics from the leader of karmada controller manager")
98+
}
99+
return result, nil
100+
}
101+
102+
// GetMetricsFromPod retrieves metrics data.
103+
func GetMetricsFromPod(ctx context.Context, client clientset.Interface, podName string, namespace string, port int) (string, error) {
104+
rawOutput, err := client.CoreV1().RESTClient().Get().
105+
Namespace(namespace).
106+
Resource("pods").
107+
SubResource("proxy").
108+
Name(fmt.Sprintf("%s:%d", podName, port)).
109+
Suffix("metrics").
110+
Do(ctx).Raw()
111+
if err != nil {
112+
return "", err
113+
}
114+
return string(rawOutput), nil
115+
}
116+
117+
func isKarmadaControllerManagerLeader(samples model.Samples) bool {
118+
for _, sample := range samples {
119+
if sample.Metric["name"] == karmadaControllerManagerDeploy && sample.Value > 0 {
120+
return true
121+
}
122+
}
123+
return false
124+
}
125+
126+
func GetMetricByName(samples model.Samples, name string) *model.Sample {
127+
for _, sample := range samples {
128+
if sample.Metric["name"] == model.LabelValue(name) {
129+
return sample
130+
}
131+
}
132+
return nil
133+
}

0 commit comments

Comments
 (0)