@@ -20,16 +20,67 @@ import (
2020 "context"
2121 "os"
2222 "path/filepath"
23+ "strings"
2324
2425 . "github.com/onsi/ginkgo/v2"
2526 . "github.com/onsi/gomega"
27+ "google.golang.org/grpc/codes"
28+ "google.golang.org/grpc/status"
2629 internalapi "k8s.io/cri-api/pkg/apis"
2730 runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"
2831
2932 "sigs.k8s.io/cri-tools/pkg/common"
3033 "sigs.k8s.io/cri-tools/pkg/framework"
3134)
3235
36+ // expectedMetricDescriptorNames contains all expected metric descriptor names
37+ // based on metrics returned by kubelet with CRI-O and cadvisor on the legacy cadvisor stats provider
38+ // on kubernetes 1.35.
39+ var expectedMetricDescriptorNames = []string {
40+ "container_blkio_device_usage_total" ,
41+ "container_cpu_load_average_10s" ,
42+ "container_cpu_system_seconds_total" ,
43+ "container_cpu_usage_seconds_total" ,
44+ "container_cpu_user_seconds_total" ,
45+ "container_file_descriptors" ,
46+ "container_fs_reads_bytes_total" ,
47+ "container_fs_reads_total" ,
48+ "container_fs_usage_bytes" ,
49+ "container_fs_writes_bytes_total" ,
50+ "container_fs_writes_total" ,
51+ "container_last_seen" ,
52+ "container_memory_cache" ,
53+ "container_memory_failcnt" ,
54+ "container_memory_failures_total" ,
55+ "container_memory_mapped_file" ,
56+ "container_memory_max_usage_bytes" ,
57+ "container_memory_rss" ,
58+ "container_memory_swap" ,
59+ "container_memory_usage_bytes" ,
60+ "container_memory_working_set_bytes" ,
61+ "container_network_receive_bytes_total" ,
62+ "container_network_receive_errors_total" ,
63+ "container_network_receive_packets_dropped_total" ,
64+ "container_network_receive_packets_total" ,
65+ "container_network_transmit_bytes_total" ,
66+ "container_network_transmit_errors_total" ,
67+ "container_network_transmit_packets_dropped_total" ,
68+ "container_network_transmit_packets_total" ,
69+ "container_oom_events_total" ,
70+ "container_processes" ,
71+ "container_sockets" ,
72+ "container_spec_cpu_period" ,
73+ "container_spec_cpu_shares" ,
74+ "container_spec_memory_limit_bytes" ,
75+ "container_spec_memory_reservation_limit_bytes" ,
76+ "container_spec_memory_swap_limit_bytes" ,
77+ "container_start_time_seconds" ,
78+ "container_tasks_state" ,
79+ "container_threads" ,
80+ "container_threads_max" ,
81+ "container_ulimits_soft" ,
82+ }
83+
3384var _ = framework .KubeDescribe ("PodSandbox" , func () {
3485 f := framework .NewDefaultCRIFramework ()
3586
@@ -80,6 +131,55 @@ var _ = framework.KubeDescribe("PodSandbox", func() {
80131 podID = "" // no need to cleanup pod
81132 })
82133 })
134+ Context ("runtime should support metrics operations" , func () {
135+ var podID string
136+ var podConfig * runtimeapi.PodSandboxConfig
137+ BeforeEach (func () {
138+ _ , err := rc .ListMetricDescriptors (context .TODO ())
139+ if err != nil {
140+ s , ok := status .FromError (err )
141+ Expect (ok && s .Code () == codes .Unimplemented ).To (BeTrue (), "Expected CRI metric descriptors call to either be not supported, or not error" )
142+ if s .Code () == codes .Unimplemented {
143+ Skip ("CRI Metrics endpoints not supported by this runtime version" )
144+ }
145+ }
146+ })
147+
148+ AfterEach (func () {
149+ if podID != "" {
150+ By ("stop PodSandbox" )
151+ Expect (rc .StopPodSandbox (context .TODO (), podID )).NotTo (HaveOccurred ())
152+ By ("delete PodSandbox" )
153+ Expect (rc .RemovePodSandbox (context .TODO (), podID )).NotTo (HaveOccurred ())
154+ }
155+ })
156+
157+ It ("runtime should support returning metrics descriptors [Conformance]" , func () {
158+ By ("list metric descriptors" )
159+ descs := listMetricDescriptors (rc )
160+
161+ By ("verify expected metric descriptors are present" )
162+ testMetricDescriptors (descs )
163+ })
164+
165+ It ("runtime should support listing pod sandbox metrics [Conformance]" , func () {
166+ By ("create pod sandbox" )
167+ podID , podConfig = framework .CreatePodSandboxForContainer (rc )
168+
169+ By ("create container in pod" )
170+ ic := f .CRIClient .CRIImageClient
171+ containerID := framework .CreatePauseContainer (rc , ic , podID , podConfig , "container-for-metrics-" )
172+
173+ By ("start container" )
174+ startContainer (rc , containerID )
175+
176+ By ("list pod sandbox metrics" )
177+ metrics := listPodSandboxMetrics (rc )
178+
179+ By ("verify pod metrics are present" )
180+ testPodSandboxMetrics (metrics , podID )
181+ })
182+ })
83183})
84184
85185// podSandboxFound returns whether PodSandbox is found.
@@ -166,6 +266,17 @@ func listPodSandbox(c internalapi.RuntimeService, filter *runtimeapi.PodSandboxF
166266 return pods
167267}
168268
269+ // listMetricDescriptors lists MetricDescriptors.
270+ func listMetricDescriptors (c internalapi.RuntimeService ) []* runtimeapi.MetricDescriptor {
271+ By ("List MetricDescriptors." )
272+
273+ descs , err := c .ListMetricDescriptors (context .TODO ())
274+ framework .ExpectNoError (err , "failed to list MetricDescriptors status: %v" , err )
275+ framework .Logf ("List MetricDescriptors succeed" )
276+
277+ return descs
278+ }
279+
169280// createLogTempDir creates the log temp directory for podSandbox.
170281func createLogTempDir (podSandboxName string ) (hostPath , podLogPath string ) {
171282 hostPath , err := os .MkdirTemp ("" , "podLogTest" )
@@ -196,3 +307,76 @@ func createPodSandboxWithLogDirectory(c internalapi.RuntimeService) (sandboxID s
196307
197308 return framework .RunPodSandbox (c , podConfig ), podConfig , hostPath
198309}
310+
311+ // testMetricDescriptors verifies that all expected metric descriptors are present.
312+ func testMetricDescriptors (descs []* runtimeapi.MetricDescriptor ) {
313+ returnedDescriptors := make (map [string ]* runtimeapi.MetricDescriptor )
314+ for _ , desc := range descs {
315+ returnedDescriptors [desc .GetName ()] = desc
316+ Expect (desc .GetHelp ()).NotTo (BeEmpty (), "Metric descriptor %q should have help text" , desc .GetName ())
317+ Expect (desc .GetLabelKeys ()).NotTo (BeEmpty (), "Metric descriptor %q should have label keys" , desc .GetName ())
318+ }
319+
320+ missingMetrics := []string {}
321+
322+ for _ , expectedName := range expectedMetricDescriptorNames {
323+ _ , found := returnedDescriptors [expectedName ]
324+ if ! found {
325+ missingMetrics = append (missingMetrics , expectedName )
326+ }
327+ }
328+
329+ Expect (missingMetrics ).To (BeEmpty (), "Expected %s metrics to be present and they were not" , strings .Join (missingMetrics , " " ))
330+ }
331+
332+ // listPodSandboxMetrics lists PodSandboxMetrics.
333+ func listPodSandboxMetrics (c internalapi.RuntimeService ) []* runtimeapi.PodSandboxMetrics {
334+ By ("List PodSandboxMetrics." )
335+
336+ metrics , err := c .ListPodSandboxMetrics (context .TODO ())
337+ framework .ExpectNoError (err , "failed to list PodSandboxMetrics: %v" , err )
338+ framework .Logf ("List PodSandboxMetrics succeed" )
339+
340+ return metrics
341+ }
342+
343+ // testPodSandboxMetrics verifies that metrics are present for the specified pod.
344+ func testPodSandboxMetrics (allMetrics []* runtimeapi.PodSandboxMetrics , podID string ) {
345+ var podMetrics * runtimeapi.PodSandboxMetrics
346+
347+ for _ , m := range allMetrics {
348+ if m .GetPodSandboxId () == podID {
349+ podMetrics = m
350+
351+ break
352+ }
353+ }
354+
355+ Expect (podMetrics ).NotTo (BeNil (), "Metrics for pod %q should be present" , podID )
356+
357+ metricNamesFound := make (map [string ]bool )
358+
359+ for _ , metric := range podMetrics .GetMetrics () {
360+ if ! metricNamesFound [metric .GetName ()] {
361+ metricNamesFound [metric .GetName ()] = true
362+ }
363+ }
364+
365+ for _ , containerMetric := range podMetrics .GetContainerMetrics () {
366+ for _ , metric := range containerMetric .GetMetrics () {
367+ if ! metricNamesFound [metric .GetName ()] {
368+ metricNamesFound [metric .GetName ()] = true
369+ }
370+ }
371+ }
372+
373+ missingMetrics := []string {}
374+
375+ for _ , expectedName := range expectedMetricDescriptorNames {
376+ if ! metricNamesFound [expectedName ] {
377+ missingMetrics = append (missingMetrics , expectedName )
378+ }
379+ }
380+
381+ Expect (missingMetrics ).To (BeEmpty (), "Expected %s metrics to be present and they were not" , strings .Join (missingMetrics , " " ))
382+ }
0 commit comments