@@ -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+ grpcstatus "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,36 @@ 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+ BeforeEach (func () {
137+ _ , err := rc .ListMetricDescriptors (context .TODO ())
138+ if err != nil {
139+ s , ok := grpcstatus .FromError (err )
140+ Expect (ok && s .Code () == codes .Unimplemented ).To (BeTrue (), "Expected CRI metric descriptors call to either be not supported, or not error" )
141+ if s .Code () == codes .Unimplemented {
142+ Skip ("CRI Metrics endpoints not supported by this runtime version" )
143+ }
144+ }
145+ })
146+
147+ AfterEach (func () {
148+ if podID != "" {
149+ By ("stop PodSandbox" )
150+ Expect (rc .StopPodSandbox (context .TODO (), podID )).NotTo (HaveOccurred ())
151+ By ("delete PodSandbox" )
152+ Expect (rc .RemovePodSandbox (context .TODO (), podID )).NotTo (HaveOccurred ())
153+ }
154+ })
155+
156+ It ("runtime should support returning metrics descriptors [Conformance]" , func () {
157+ By ("list metric descriptors" )
158+ descs := listMetricDescriptors (rc )
159+
160+ By ("verify expected metric descriptors are present" )
161+ testMetricDescriptors (descs )
162+ })
163+ })
83164})
84165
85166// podSandboxFound returns whether PodSandbox is found.
@@ -166,6 +247,17 @@ func listPodSandbox(c internalapi.RuntimeService, filter *runtimeapi.PodSandboxF
166247 return pods
167248}
168249
250+ // listMetricDescriptors lists MetricDescriptors.
251+ func listMetricDescriptors (c internalapi.RuntimeService ) []* runtimeapi.MetricDescriptor {
252+ By ("List MetricDescriptors." )
253+
254+ descs , err := c .ListMetricDescriptors (context .TODO ())
255+ framework .ExpectNoError (err , "failed to list MetricDescriptors status: %v" , err )
256+ framework .Logf ("List MetricDescriptors succeed" )
257+
258+ return descs
259+ }
260+
169261// createLogTempDir creates the log temp directory for podSandbox.
170262func createLogTempDir (podSandboxName string ) (hostPath , podLogPath string ) {
171263 hostPath , err := os .MkdirTemp ("" , "podLogTest" )
@@ -196,3 +288,24 @@ func createPodSandboxWithLogDirectory(c internalapi.RuntimeService) (sandboxID s
196288
197289 return framework .RunPodSandbox (c , podConfig ), podConfig , hostPath
198290}
291+
292+ // testMetricDescriptors verifies that all expected metric descriptors are present.
293+ func testMetricDescriptors (descs []* runtimeapi.MetricDescriptor ) {
294+ returnedDescriptors := make (map [string ]* runtimeapi.MetricDescriptor )
295+ for _ , desc := range descs {
296+ returnedDescriptors [desc .GetName ()] = desc
297+ Expect (desc .GetHelp ()).NotTo (BeEmpty (), "Metric descriptor %q should have help text" , desc .GetName ())
298+ Expect (desc .GetLabelKeys ()).NotTo (BeEmpty (), "Metric descriptor %q should have label keys" , desc .GetName ())
299+ }
300+
301+ missingMetrics := []string {}
302+
303+ for _ , expectedName := range expectedMetricDescriptorNames {
304+ _ , found := returnedDescriptors [expectedName ]
305+ if ! found {
306+ missingMetrics = append (missingMetrics , expectedName )
307+ }
308+ }
309+
310+ Expect (missingMetrics ).To (BeEmpty (), "Expected %s metrics to be present and they were not" , strings .Join (missingMetrics , " " ))
311+ }
0 commit comments