k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/kubelet/stats/cri_stats_provider_windows_test.go (about) 1 /* 2 Copyright 2021 The Kubernetes 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 stats 18 19 import ( 20 "reflect" 21 "testing" 22 "time" 23 24 "github.com/Microsoft/hcsshim" 25 cadvisorapiv2 "github.com/google/cadvisor/info/v2" 26 "github.com/stretchr/testify/assert" 27 "k8s.io/apimachinery/pkg/api/resource" 28 v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "k8s.io/apimachinery/pkg/types" 30 runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" 31 statsapi "k8s.io/kubelet/pkg/apis/stats/v1alpha1" 32 kubecontainertest "k8s.io/kubernetes/pkg/kubelet/container/testing" 33 "k8s.io/kubernetes/pkg/kubelet/kuberuntime" 34 "k8s.io/kubernetes/pkg/volume" 35 testingclock "k8s.io/utils/clock/testing" 36 ) 37 38 type fakeNetworkStatsProvider struct { 39 containers []containerStats 40 } 41 42 type containerStats struct { 43 container hcsshim.ContainerProperties 44 hcsStats []hcsshim.NetworkStats 45 } 46 47 func (s fakeNetworkStatsProvider) GetHNSEndpointStats(endpointName string) (*hcsshim.HNSEndpointStats, error) { 48 eps := hcsshim.HNSEndpointStats{} 49 for _, c := range s.containers { 50 for _, stat := range c.hcsStats { 51 if endpointName == stat.InstanceId { 52 eps = hcsshim.HNSEndpointStats{ 53 EndpointID: stat.EndpointId, 54 BytesSent: stat.BytesSent, 55 BytesReceived: stat.BytesReceived, 56 PacketsReceived: stat.PacketsReceived, 57 PacketsSent: stat.PacketsSent, 58 } 59 } 60 } 61 } 62 63 return &eps, nil 64 } 65 66 func (s fakeNetworkStatsProvider) HNSListEndpointRequest() ([]hcsshim.HNSEndpoint, error) { 67 uniqueEndpoints := map[string]*hcsshim.HNSEndpoint{} 68 69 for _, c := range s.containers { 70 for _, stat := range c.hcsStats { 71 e, found := uniqueEndpoints[stat.EndpointId] 72 if found { 73 // add the container 74 e.SharedContainers = append(e.SharedContainers, c.container.ID) 75 continue 76 } 77 78 uniqueEndpoints[stat.EndpointId] = &hcsshim.HNSEndpoint{ 79 Name: stat.EndpointId, 80 Id: stat.EndpointId, 81 SharedContainers: []string{c.container.ID}, 82 } 83 } 84 } 85 86 eps := []hcsshim.HNSEndpoint{} 87 for _, ep := range uniqueEndpoints { 88 eps = append(eps, *ep) 89 } 90 91 return eps, nil 92 } 93 94 func Test_criStatsProvider_listContainerNetworkStats(t *testing.T) { 95 fakeClock := testingclock.NewFakeClock(time.Time{}) 96 tests := []struct { 97 name string 98 fields fakeNetworkStatsProvider 99 want map[string]*statsapi.NetworkStats 100 wantErr bool 101 skipped bool 102 }{ 103 { 104 name: "basic example", 105 fields: fakeNetworkStatsProvider{ 106 containers: []containerStats{ 107 { 108 container: hcsshim.ContainerProperties{ 109 ID: "c1", 110 }, hcsStats: []hcsshim.NetworkStats{ 111 { 112 BytesReceived: 1, 113 BytesSent: 10, 114 EndpointId: "test", 115 InstanceId: "test", 116 }, 117 }, 118 }, 119 { 120 container: hcsshim.ContainerProperties{ 121 ID: "c2", 122 }, hcsStats: []hcsshim.NetworkStats{ 123 { 124 BytesReceived: 2, 125 BytesSent: 20, 126 EndpointId: "test2", 127 InstanceId: "test2", 128 }, 129 }, 130 }, 131 }, 132 }, 133 want: map[string]*statsapi.NetworkStats{ 134 "c1": { 135 Time: v1.NewTime(fakeClock.Now()), 136 InterfaceStats: statsapi.InterfaceStats{ 137 Name: "test", 138 RxBytes: toP(1), 139 TxBytes: toP(10), 140 }, 141 Interfaces: []statsapi.InterfaceStats{ 142 { 143 Name: "test", 144 RxBytes: toP(1), 145 146 TxBytes: toP(10), 147 }, 148 }, 149 }, 150 "c2": { 151 Time: v1.Time{}, 152 InterfaceStats: statsapi.InterfaceStats{ 153 Name: "test2", 154 RxBytes: toP(2), 155 TxBytes: toP(20), 156 }, 157 Interfaces: []statsapi.InterfaceStats{ 158 { 159 Name: "test2", 160 RxBytes: toP(2), 161 TxBytes: toP(20), 162 }, 163 }, 164 }, 165 }, 166 wantErr: false, 167 }, 168 { 169 name: "multiple containers same endpoint", 170 fields: fakeNetworkStatsProvider{ 171 containers: []containerStats{ 172 { 173 container: hcsshim.ContainerProperties{ 174 ID: "c1", 175 }, hcsStats: []hcsshim.NetworkStats{ 176 { 177 BytesReceived: 1, 178 BytesSent: 10, 179 EndpointId: "test", 180 InstanceId: "test", 181 }, 182 }, 183 }, 184 { 185 container: hcsshim.ContainerProperties{ 186 ID: "c2", 187 }, hcsStats: []hcsshim.NetworkStats{ 188 { 189 BytesReceived: 2, 190 BytesSent: 20, 191 EndpointId: "test2", 192 InstanceId: "test2", 193 }, 194 }, 195 }, 196 { 197 container: hcsshim.ContainerProperties{ 198 ID: "c3", 199 }, hcsStats: []hcsshim.NetworkStats{ 200 { 201 BytesReceived: 3, 202 BytesSent: 30, 203 EndpointId: "test2", 204 InstanceId: "test3", 205 }, 206 }, 207 }, 208 }, 209 }, 210 want: map[string]*statsapi.NetworkStats{ 211 "c1": { 212 Time: v1.NewTime(fakeClock.Now()), 213 InterfaceStats: statsapi.InterfaceStats{ 214 Name: "test", 215 RxBytes: toP(1), 216 TxBytes: toP(10), 217 }, 218 Interfaces: []statsapi.InterfaceStats{ 219 { 220 Name: "test", 221 RxBytes: toP(1), 222 223 TxBytes: toP(10), 224 }, 225 }, 226 }, 227 "c2": { 228 Time: v1.Time{}, 229 InterfaceStats: statsapi.InterfaceStats{ 230 Name: "test2", 231 RxBytes: toP(2), 232 TxBytes: toP(20), 233 }, 234 Interfaces: []statsapi.InterfaceStats{ 235 { 236 Name: "test2", 237 RxBytes: toP(2), 238 TxBytes: toP(20), 239 }, 240 }, 241 }, 242 "c3": { 243 Time: v1.Time{}, 244 InterfaceStats: statsapi.InterfaceStats{ 245 Name: "test2", 246 RxBytes: toP(2), 247 TxBytes: toP(20), 248 }, 249 Interfaces: []statsapi.InterfaceStats{ 250 { 251 Name: "test2", 252 RxBytes: toP(2), 253 TxBytes: toP(20), 254 }, 255 }, 256 }, 257 }, 258 wantErr: false, 259 }, 260 { 261 name: "multiple stats instances of same interface only picks up first", 262 fields: fakeNetworkStatsProvider{ 263 containers: []containerStats{ 264 { 265 container: hcsshim.ContainerProperties{ 266 ID: "c1", 267 }, hcsStats: []hcsshim.NetworkStats{ 268 { 269 BytesReceived: 1, 270 BytesSent: 10, 271 EndpointId: "test", 272 InstanceId: "test", 273 }, 274 { 275 BytesReceived: 3, 276 BytesSent: 30, 277 EndpointId: "test", 278 InstanceId: "test3", 279 }, 280 }, 281 }, 282 { 283 container: hcsshim.ContainerProperties{ 284 ID: "c2", 285 }, hcsStats: []hcsshim.NetworkStats{ 286 { 287 BytesReceived: 2, 288 BytesSent: 20, 289 EndpointId: "test2", 290 InstanceId: "test2", 291 }, 292 }, 293 }, 294 }, 295 }, 296 want: map[string]*statsapi.NetworkStats{ 297 "c1": { 298 Time: v1.NewTime(fakeClock.Now()), 299 InterfaceStats: statsapi.InterfaceStats{ 300 Name: "test", 301 RxBytes: toP(1), 302 TxBytes: toP(10), 303 }, 304 Interfaces: []statsapi.InterfaceStats{ 305 { 306 Name: "test", 307 RxBytes: toP(1), 308 309 TxBytes: toP(10), 310 }, 311 }, 312 }, 313 "c2": { 314 Time: v1.Time{}, 315 InterfaceStats: statsapi.InterfaceStats{ 316 Name: "test2", 317 RxBytes: toP(2), 318 TxBytes: toP(20), 319 }, 320 Interfaces: []statsapi.InterfaceStats{ 321 { 322 Name: "test2", 323 RxBytes: toP(2), 324 TxBytes: toP(20), 325 }, 326 }, 327 }, 328 }, 329 wantErr: false, 330 }, 331 { 332 name: "multiple endpoints per container", 333 fields: fakeNetworkStatsProvider{ 334 containers: []containerStats{ 335 { 336 container: hcsshim.ContainerProperties{ 337 ID: "c1", 338 }, hcsStats: []hcsshim.NetworkStats{ 339 { 340 BytesReceived: 1, 341 BytesSent: 10, 342 EndpointId: "test", 343 InstanceId: "test", 344 }, 345 { 346 BytesReceived: 3, 347 BytesSent: 30, 348 EndpointId: "test3", 349 InstanceId: "test3", 350 }, 351 }, 352 }, 353 { 354 container: hcsshim.ContainerProperties{ 355 ID: "c2", 356 }, hcsStats: []hcsshim.NetworkStats{ 357 { 358 BytesReceived: 2, 359 BytesSent: 20, 360 EndpointId: "test2", 361 InstanceId: "test2", 362 }, 363 }, 364 }, 365 }, 366 }, 367 want: map[string]*statsapi.NetworkStats{ 368 "c1": { 369 Time: v1.NewTime(fakeClock.Now()), 370 InterfaceStats: statsapi.InterfaceStats{ 371 Name: "test", 372 RxBytes: toP(1), 373 TxBytes: toP(10), 374 }, 375 Interfaces: []statsapi.InterfaceStats{ 376 { 377 Name: "test", 378 RxBytes: toP(1), 379 380 TxBytes: toP(10), 381 }, 382 { 383 Name: "test3", 384 RxBytes: toP(3), 385 386 TxBytes: toP(30), 387 }, 388 }, 389 }, 390 "c2": { 391 Time: v1.Time{}, 392 InterfaceStats: statsapi.InterfaceStats{ 393 Name: "test2", 394 RxBytes: toP(2), 395 TxBytes: toP(20), 396 }, 397 Interfaces: []statsapi.InterfaceStats{ 398 { 399 Name: "test2", 400 RxBytes: toP(2), 401 TxBytes: toP(20), 402 }, 403 }, 404 }, 405 }, 406 wantErr: false, 407 skipped: true, 408 }, 409 } 410 for _, tt := range tests { 411 t.Run(tt.name, func(t *testing.T) { 412 // TODO: Remove skip once https://github.com/kubernetes/kubernetes/issues/116692 is fixed. 413 if tt.skipped { 414 t.Skip("Test temporarily skipped.") 415 } 416 p := &criStatsProvider{ 417 windowsNetworkStatsProvider: fakeNetworkStatsProvider{ 418 containers: tt.fields.containers, 419 }, 420 clock: fakeClock, 421 } 422 got, err := p.listContainerNetworkStats() 423 if (err != nil) != tt.wantErr { 424 t.Errorf("listContainerNetworkStats() error = %v, wantErr %v", err, tt.wantErr) 425 return 426 } 427 if !reflect.DeepEqual(got, tt.want) { 428 t.Errorf("listContainerNetworkStats() got = %v, want %v", got, tt.want) 429 } 430 }) 431 } 432 } 433 434 func toP(i uint64) *uint64 { 435 return &i 436 } 437 438 func Test_criStatsProvider_makeWinContainerStats(t *testing.T) { 439 fakeClock := testingclock.NewFakeClock(time.Time{}) 440 containerStartTime := fakeClock.Now() 441 442 cpuUsageTimestamp := int64(555555) 443 cpuUsageNanoSeconds := uint64(0x123456) 444 cpuUsageNanoCores := uint64(0x4000) 445 memoryUsageTimestamp := int64(666666) 446 memoryUsageWorkingSetBytes := uint64(0x11223344) 447 memoryUsageAvailableBytes := uint64(0x55667788) 448 memoryUsagePageFaults := uint64(200) 449 logStatsUsed := uint64(5000) 450 logStatsInodesUsed := uint64(5050) 451 452 // getPodContainerLogStats is called during makeWindowsContainerStats to populate ContainerStats.Logs 453 c0LogStats := &volume.Metrics{ 454 Used: resource.NewQuantity(int64(logStatsUsed), resource.BinarySI), 455 InodesUsed: resource.NewQuantity(int64(logStatsInodesUsed), resource.BinarySI), 456 } 457 fakeStats := map[string]*volume.Metrics{ 458 kuberuntime.BuildContainerLogsDirectory(testPodLogDirectory, "sb0-ns", "sb0-name", types.UID("sb0-uid"), "c0"): c0LogStats, 459 } 460 fakeOS := &kubecontainertest.FakeOS{} 461 fakeHostStatsProvider := NewFakeHostStatsProviderWithData(fakeStats, fakeOS) 462 463 p := &criStatsProvider{ 464 clock: fakeClock, 465 hostStatsProvider: fakeHostStatsProvider, 466 } 467 468 inputStats := &runtimeapi.WindowsContainerStats{ 469 Attributes: &runtimeapi.ContainerAttributes{ 470 Metadata: &runtimeapi.ContainerMetadata{ 471 Name: "c0", 472 }, 473 }, 474 Cpu: &runtimeapi.WindowsCpuUsage{ 475 Timestamp: cpuUsageTimestamp, 476 UsageCoreNanoSeconds: &runtimeapi.UInt64Value{ 477 Value: cpuUsageNanoSeconds, 478 }, 479 UsageNanoCores: &runtimeapi.UInt64Value{ 480 Value: cpuUsageNanoCores, 481 }, 482 }, 483 Memory: &runtimeapi.WindowsMemoryUsage{ 484 Timestamp: memoryUsageTimestamp, 485 AvailableBytes: &runtimeapi.UInt64Value{ 486 Value: memoryUsageAvailableBytes, 487 }, 488 WorkingSetBytes: &runtimeapi.UInt64Value{ 489 Value: memoryUsageWorkingSetBytes, 490 }, 491 PageFaults: &runtimeapi.UInt64Value{ 492 Value: memoryUsagePageFaults, 493 }, 494 }, 495 } 496 497 inputContainer := &runtimeapi.Container{ 498 CreatedAt: containerStartTime.Unix(), 499 Metadata: &runtimeapi.ContainerMetadata{ 500 Name: "c0", 501 }, 502 } 503 504 inputRootFsInfo := &cadvisorapiv2.FsInfo{} 505 506 // Used by the getPodContainerLogStats() call in makeWinContainerStats() 507 inputPodSandboxMetadata := &runtimeapi.PodSandboxMetadata{ 508 Namespace: "sb0-ns", 509 Name: "sb0-name", 510 Uid: "sb0-uid", 511 } 512 513 got, err := p.makeWinContainerStats(inputStats, inputContainer, inputRootFsInfo, make(map[runtimeapi.FilesystemIdentifier]*cadvisorapiv2.FsInfo), inputPodSandboxMetadata) 514 515 expected := &statsapi.ContainerStats{ 516 Name: "c0", 517 StartTime: v1.NewTime(time.Unix(0, containerStartTime.Unix())), 518 CPU: &statsapi.CPUStats{ 519 Time: v1.NewTime(time.Unix(0, cpuUsageTimestamp)), 520 UsageNanoCores: toP(cpuUsageNanoCores), 521 UsageCoreNanoSeconds: toP(cpuUsageNanoSeconds), 522 }, 523 Memory: &statsapi.MemoryStats{ 524 Time: v1.NewTime(time.Unix(0, memoryUsageTimestamp)), 525 AvailableBytes: toP(memoryUsageAvailableBytes), 526 WorkingSetBytes: toP(memoryUsageWorkingSetBytes), 527 PageFaults: toP(memoryUsagePageFaults), 528 }, 529 Rootfs: &statsapi.FsStats{}, 530 Logs: &statsapi.FsStats{ 531 Time: c0LogStats.Time, 532 UsedBytes: toP(logStatsUsed), 533 InodesUsed: toP(logStatsInodesUsed), 534 }, 535 } 536 537 if err != nil { 538 t.Fatalf("makeWinContainerStats() error = %v, expected no error", err) 539 } 540 541 if !reflect.DeepEqual(got.CPU, expected.CPU) { 542 t.Errorf("makeWinContainerStats() CPU = %v, expected %v", got.CPU, expected.CPU) 543 } 544 545 if !reflect.DeepEqual(got.Memory, expected.Memory) { 546 t.Errorf("makeWinContainerStats() Memory = %v, want %v", got.Memory, expected.Memory) 547 } 548 549 if !reflect.DeepEqual(got.Rootfs, expected.Rootfs) { 550 t.Errorf("makeWinContainerStats() Rootfs = %v, want %v", got.Rootfs, expected.Rootfs) 551 } 552 553 // Log stats contain pointers to calculated resource values so we cannot use DeepEqual here 554 assert.Equal(t, *got.Logs.UsedBytes, logStatsUsed, "Logs.UsedBytes does not match expected value") 555 assert.Equal(t, *got.Logs.InodesUsed, logStatsInodesUsed, "Logs.InodesUsed does not match expected value") 556 }