@@ -20,6 +20,7 @@ import (
2020 "context"
2121 "os"
2222 "path/filepath"
23+ "strings"
2324
2425 . "github.com/onsi/ginkgo/v2"
2526 . "github.com/onsi/gomega"
@@ -30,6 +31,55 @@ import (
3031 "sigs.k8s.io/cri-tools/pkg/framework"
3132)
3233
34+ // expectedMetricDescriptorNames contains all expected metric descriptor names
35+ // based on metrics returned by kubelet with CRI-O and cadvisor on the legacy cadvisor stats provider
36+ // on kubernetes 1.35.
37+ var expectedMetricDescriptorNames = []string {
38+ "container_cpu_load_average_10s" ,
39+ "container_cpu_load_d_average_10s" ,
40+ "container_cpu_system_seconds_total" ,
41+ "container_cpu_usage_seconds_total" ,
42+ "container_cpu_user_seconds_total" ,
43+ "container_file_descriptors" ,
44+ "container_fs_inodes_free" ,
45+ "container_fs_inodes_total" ,
46+ "container_fs_io_current" ,
47+ "container_fs_io_time_seconds_total" ,
48+ "container_fs_io_time_weighted_seconds_total" ,
49+ "container_fs_limit_bytes" ,
50+ "container_fs_read_seconds_total" ,
51+ "container_fs_reads_merged_total" ,
52+ "container_fs_reads_total" ,
53+ "container_fs_sector_reads_total" ,
54+ "container_fs_sector_writes_total" ,
55+ "container_fs_usage_bytes" ,
56+ "container_fs_write_seconds_total" ,
57+ "container_fs_writes_merged_total" ,
58+ "container_fs_writes_total" ,
59+ "container_last_seen" ,
60+ "container_memory_cache" ,
61+ "container_memory_failcnt" ,
62+ "container_memory_failures_total" ,
63+ "container_memory_kernel_usage" ,
64+ "container_memory_mapped_file" ,
65+ "container_memory_max_usage_bytes" ,
66+ "container_memory_rss" ,
67+ "container_memory_swap" ,
68+ "container_memory_total_active_file_bytes" ,
69+ "container_memory_total_inactive_file_bytes" ,
70+ "container_memory_usage_bytes" ,
71+ "container_memory_working_set_bytes" ,
72+ "container_network_receive_bytes_total" ,
73+ "container_network_receive_errors_total" ,
74+ "container_network_receive_packets_dropped_total" ,
75+ "container_network_receive_packets_total" ,
76+ "container_network_transmit_bytes_total" ,
77+ "container_network_transmit_errors_total" ,
78+ "container_network_transmit_packets_dropped_total" ,
79+ "container_network_transmit_packets_total" ,
80+ "container_oom_events_total" ,
81+ }
82+
3383var _ = framework .KubeDescribe ("PodSandbox" , func () {
3484 f := framework .NewDefaultCRIFramework ()
3585
@@ -80,6 +130,45 @@ var _ = framework.KubeDescribe("PodSandbox", func() {
80130 podID = "" // no need to cleanup pod
81131 })
82132 })
133+ Context ("runtime should support metrics operations" , func () {
134+ var podID string
135+ var podConfig * runtimeapi.PodSandboxConfig
136+
137+ AfterEach (func () {
138+ if podID != "" {
139+ By ("stop PodSandbox" )
140+ Expect (rc .StopPodSandbox (context .TODO (), podID )).NotTo (HaveOccurred ())
141+ By ("delete PodSandbox" )
142+ Expect (rc .RemovePodSandbox (context .TODO (), podID )).NotTo (HaveOccurred ())
143+ }
144+ })
145+
146+ It ("runtime should support returning metrics descriptors [Conformance]" , func () {
147+ By ("list metric descriptors" )
148+ descs := listMetricDescriptors (rc )
149+
150+ By ("verify expected metric descriptors are present" )
151+ testMetricDescriptors (descs )
152+ })
153+
154+ It ("runtime should support listing pod sandbox metrics [Conformance]" , func () {
155+ By ("create pod sandbox" )
156+ podID , podConfig = framework .CreatePodSandboxForContainer (rc )
157+
158+ By ("create container in pod" )
159+ ic := f .CRIClient .CRIImageClient
160+ containerID := framework .CreatePauseContainer (rc , ic , podID , podConfig , "container-for-metrics-" )
161+
162+ By ("start container" )
163+ startContainer (rc , containerID )
164+
165+ By ("list pod sandbox metrics" )
166+ metrics := listPodSandboxMetrics (rc )
167+
168+ By ("verify pod metrics are present" )
169+ testPodSandboxMetrics (metrics , podID )
170+ })
171+ })
83172})
84173
85174// podSandboxFound returns whether PodSandbox is found.
@@ -166,6 +255,17 @@ func listPodSandbox(c internalapi.RuntimeService, filter *runtimeapi.PodSandboxF
166255 return pods
167256}
168257
258+ // listMetricDescriptors lists MetricDescriptors.
259+ func listMetricDescriptors (c internalapi.RuntimeService ) []* runtimeapi.MetricDescriptor {
260+ By ("List MetricDescriptors." )
261+
262+ descs , err := c .ListMetricDescriptors (context .TODO ())
263+ framework .ExpectNoError (err , "failed to list MetricDescriptors status: %v" , err )
264+ framework .Logf ("List MetricDescriptors succeed" )
265+
266+ return descs
267+ }
268+
169269// createLogTempDir creates the log temp directory for podSandbox.
170270func createLogTempDir (podSandboxName string ) (hostPath , podLogPath string ) {
171271 hostPath , err := os .MkdirTemp ("" , "podLogTest" )
@@ -196,3 +296,76 @@ func createPodSandboxWithLogDirectory(c internalapi.RuntimeService) (sandboxID s
196296
197297 return framework .RunPodSandbox (c , podConfig ), podConfig , hostPath
198298}
299+
300+ // testMetricDescriptors verifies that all expected metric descriptors are present.
301+ func testMetricDescriptors (descs []* runtimeapi.MetricDescriptor ) {
302+ returnedDescriptors := make (map [string ]* runtimeapi.MetricDescriptor )
303+ for _ , desc := range descs {
304+ returnedDescriptors [desc .GetName ()] = desc
305+ Expect (desc .GetHelp ()).NotTo (BeEmpty (), "Metric descriptor %q should have help text" , desc .GetName ())
306+ Expect (desc .GetLabelKeys ()).NotTo (BeEmpty (), "Metric descriptor %q should have label keys" , desc .GetName ())
307+ }
308+
309+ missingMetrics := []string {}
310+
311+ for _ , expectedName := range expectedMetricDescriptorNames {
312+ _ , found := returnedDescriptors [expectedName ]
313+ if ! found {
314+ missingMetrics = append (missingMetrics , expectedName )
315+ }
316+ }
317+
318+ Expect (missingMetrics ).To (BeEmpty (), "Expected %s metrics to be present and they were not" , strings .Join (missingMetrics , " " ))
319+ }
320+
321+ // listPodSandboxMetrics lists PodSandboxMetrics.
322+ func listPodSandboxMetrics (c internalapi.RuntimeService ) []* runtimeapi.PodSandboxMetrics {
323+ By ("List PodSandboxMetrics." )
324+
325+ metrics , err := c .ListPodSandboxMetrics (context .TODO ())
326+ framework .ExpectNoError (err , "failed to list PodSandboxMetrics: %v" , err )
327+ framework .Logf ("List PodSandboxMetrics succeed" )
328+
329+ return metrics
330+ }
331+
332+ // testPodSandboxMetrics verifies that metrics are present for the specified pod.
333+ func testPodSandboxMetrics (allMetrics []* runtimeapi.PodSandboxMetrics , podID string ) {
334+ var podMetrics * runtimeapi.PodSandboxMetrics
335+
336+ for _ , m := range allMetrics {
337+ if m .GetPodSandboxId () == podID {
338+ podMetrics = m
339+
340+ break
341+ }
342+ }
343+
344+ Expect (podMetrics ).NotTo (BeNil (), "Metrics for pod %q should be present" , podID )
345+
346+ metricNamesFound := make (map [string ]bool )
347+
348+ for _ , metric := range podMetrics .GetMetrics () {
349+ if ! metricNamesFound [metric .GetName ()] {
350+ metricNamesFound [metric .GetName ()] = true
351+ }
352+ }
353+
354+ for _ , containerMetric := range podMetrics .GetContainerMetrics () {
355+ for _ , metric := range containerMetric .GetMetrics () {
356+ if ! metricNamesFound [metric .GetName ()] {
357+ metricNamesFound [metric .GetName ()] = true
358+ }
359+ }
360+ }
361+
362+ missingMetrics := []string {}
363+
364+ for _ , expectedName := range expectedMetricDescriptorNames {
365+ if ! metricNamesFound [expectedName ] {
366+ missingMetrics = append (missingMetrics , expectedName )
367+ }
368+ }
369+
370+ Expect (missingMetrics ).To (BeEmpty (), "Expected %s metrics to be present and they were not" , strings .Join (missingMetrics , " " ))
371+ }
0 commit comments