k8s.io/kubernetes@v1.29.3/pkg/kubelet/stats/provider_test.go (about) 1 /* 2 Copyright 2017 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 "context" 21 "fmt" 22 "testing" 23 "time" 24 25 "github.com/golang/mock/gomock" 26 cadvisorapiv1 "github.com/google/cadvisor/info/v1" 27 cadvisorapiv2 "github.com/google/cadvisor/info/v2" 28 fuzz "github.com/google/gofuzz" 29 "github.com/stretchr/testify/assert" 30 "github.com/stretchr/testify/require" 31 32 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 33 "k8s.io/apimachinery/pkg/types" 34 statsapi "k8s.io/kubelet/pkg/apis/stats/v1alpha1" 35 cadvisortest "k8s.io/kubernetes/pkg/kubelet/cadvisor/testing" 36 kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" 37 kubecontainertest "k8s.io/kubernetes/pkg/kubelet/container/testing" 38 kubepodtest "k8s.io/kubernetes/pkg/kubelet/pod/testing" 39 serverstats "k8s.io/kubernetes/pkg/kubelet/server/stats" 40 kubetypes "k8s.io/kubernetes/pkg/kubelet/types" 41 "k8s.io/kubernetes/pkg/volume" 42 ) 43 44 const ( 45 // Offsets from seed value in generated container stats. 46 offsetCPUUsageCores = iota 47 offsetCPUUsageCoreSeconds 48 offsetMemPageFaults 49 offsetMemMajorPageFaults 50 offsetMemUsageBytes 51 offsetMemRSSBytes 52 offsetMemWorkingSetBytes 53 offsetNetRxBytes 54 offsetNetRxErrors 55 offsetNetTxBytes 56 offsetNetTxErrors 57 offsetFsCapacity 58 offsetFsAvailable 59 offsetFsUsage 60 offsetFsInodes 61 offsetFsInodesFree 62 offsetFsTotalUsageBytes 63 offsetFsBaseUsageBytes 64 offsetFsInodeUsage 65 offsetAcceleratorDutyCycle 66 offsetMemSwapUsageBytes 67 ) 68 69 var ( 70 timestamp = time.Now() 71 creationTime = timestamp.Add(-5 * time.Minute) 72 ) 73 74 func TestGetCgroupStats(t *testing.T) { 75 const ( 76 cgroupName = "test-cgroup-name" 77 containerInfoSeed = 1000 78 updateStats = false 79 ) 80 81 mockCtrl := gomock.NewController(t) 82 defer mockCtrl.Finish() 83 84 var ( 85 mockCadvisor = cadvisortest.NewMockInterface(mockCtrl) 86 mockPodManager = new(kubepodtest.MockManager) 87 mockRuntimeCache = new(kubecontainertest.MockRuntimeCache) 88 89 assert = assert.New(t) 90 options = cadvisorapiv2.RequestOptions{IdType: cadvisorapiv2.TypeName, Count: 2, Recursive: false} 91 92 containerInfo = getTestContainerInfo(containerInfoSeed, "test-pod", "test-ns", "test-container") 93 containerInfoMap = map[string]cadvisorapiv2.ContainerInfo{cgroupName: containerInfo} 94 ) 95 96 mockCadvisor.EXPECT().ContainerInfoV2(cgroupName, options).Return(containerInfoMap, nil) 97 98 provider := newStatsProvider(mockCadvisor, mockPodManager, mockRuntimeCache, fakeContainerStatsProvider{}) 99 cs, ns, err := provider.GetCgroupStats(cgroupName, updateStats) 100 assert.NoError(err) 101 102 checkCPUStats(t, "", containerInfoSeed, cs.CPU) 103 checkMemoryStats(t, "", containerInfoSeed, containerInfo, cs.Memory) 104 checkNetworkStats(t, "", containerInfoSeed, ns) 105 checkSwapStats(t, "", containerInfoSeed, containerInfo, cs.Swap) 106 107 assert.Equal(cgroupName, cs.Name) 108 assert.Equal(metav1.NewTime(containerInfo.Spec.CreationTime), cs.StartTime) 109 } 110 111 func TestGetCgroupCPUAndMemoryStats(t *testing.T) { 112 const ( 113 cgroupName = "test-cgroup-name" 114 containerInfoSeed = 1000 115 updateStats = false 116 ) 117 118 mockCtrl := gomock.NewController(t) 119 defer mockCtrl.Finish() 120 121 var ( 122 mockCadvisor = cadvisortest.NewMockInterface(mockCtrl) 123 mockPodManager = new(kubepodtest.MockManager) 124 mockRuntimeCache = new(kubecontainertest.MockRuntimeCache) 125 126 assert = assert.New(t) 127 options = cadvisorapiv2.RequestOptions{IdType: cadvisorapiv2.TypeName, Count: 2, Recursive: false} 128 129 containerInfo = getTestContainerInfo(containerInfoSeed, "test-pod", "test-ns", "test-container") 130 containerInfoMap = map[string]cadvisorapiv2.ContainerInfo{cgroupName: containerInfo} 131 ) 132 133 mockCadvisor.EXPECT().ContainerInfoV2(cgroupName, options).Return(containerInfoMap, nil) 134 135 provider := newStatsProvider(mockCadvisor, mockPodManager, mockRuntimeCache, fakeContainerStatsProvider{}) 136 cs, err := provider.GetCgroupCPUAndMemoryStats(cgroupName, updateStats) 137 assert.NoError(err) 138 139 checkCPUStats(t, "", containerInfoSeed, cs.CPU) 140 checkMemoryStats(t, "", containerInfoSeed, containerInfo, cs.Memory) 141 142 assert.Equal(cgroupName, cs.Name) 143 assert.Equal(metav1.NewTime(containerInfo.Spec.CreationTime), cs.StartTime) 144 } 145 146 func TestRootFsStats(t *testing.T) { 147 const ( 148 rootFsInfoSeed = 1000 149 containerInfoSeed = 2000 150 ) 151 152 mockCtrl := gomock.NewController(t) 153 defer mockCtrl.Finish() 154 155 var ( 156 mockCadvisor = cadvisortest.NewMockInterface(mockCtrl) 157 mockPodManager = new(kubepodtest.MockManager) 158 mockRuntimeCache = new(kubecontainertest.MockRuntimeCache) 159 160 assert = assert.New(t) 161 options = cadvisorapiv2.RequestOptions{IdType: cadvisorapiv2.TypeName, Count: 2, Recursive: false} 162 163 rootFsInfo = getTestFsInfo(rootFsInfoSeed) 164 containerInfo = getTestContainerInfo(containerInfoSeed, "test-pod", "test-ns", "test-container") 165 containerInfoMap = map[string]cadvisorapiv2.ContainerInfo{"/": containerInfo} 166 ) 167 168 mockCadvisor.EXPECT().RootFsInfo().Return(rootFsInfo, nil) 169 mockCadvisor.EXPECT().ContainerInfoV2("/", options).Return(containerInfoMap, nil) 170 171 provider := newStatsProvider(mockCadvisor, mockPodManager, mockRuntimeCache, fakeContainerStatsProvider{}) 172 stats, err := provider.RootFsStats() 173 assert.NoError(err) 174 175 checkFsStats(t, "", rootFsInfoSeed, stats) 176 177 assert.Equal(metav1.NewTime(containerInfo.Stats[0].Timestamp), stats.Time) 178 assert.Equal(rootFsInfo.Usage, *stats.UsedBytes) 179 assert.Equal(*rootFsInfo.Inodes-*rootFsInfo.InodesFree, *stats.InodesUsed) 180 } 181 182 func TestGetContainerInfo(t *testing.T) { 183 ctx := context.Background() 184 cadvisorAPIFailure := fmt.Errorf("cAdvisor failure") 185 runtimeError := fmt.Errorf("List containers error") 186 tests := []struct { 187 name string 188 containerID string 189 containerPath string 190 cadvisorContainerInfo cadvisorapiv1.ContainerInfo 191 runtimeError error 192 podList []*kubecontainer.Pod 193 requestedPodFullName string 194 requestedPodUID types.UID 195 requestedContainerName string 196 expectDockerContainerCall bool 197 mockError error 198 expectedError error 199 expectStats bool 200 }{ 201 { 202 name: "get container info", 203 containerID: "ab2cdf", 204 containerPath: "/docker/ab2cdf", 205 cadvisorContainerInfo: cadvisorapiv1.ContainerInfo{ 206 ContainerReference: cadvisorapiv1.ContainerReference{ 207 Name: "/docker/ab2cdf", 208 }, 209 }, 210 runtimeError: nil, 211 podList: []*kubecontainer.Pod{ 212 { 213 ID: "12345678", 214 Name: "qux", 215 Namespace: "ns", 216 Containers: []*kubecontainer.Container{ 217 { 218 Name: "foo", 219 ID: kubecontainer.ContainerID{Type: "test", ID: "ab2cdf"}, 220 }, 221 }, 222 }, 223 }, 224 requestedPodFullName: "qux_ns", 225 requestedPodUID: "", 226 requestedContainerName: "foo", 227 expectDockerContainerCall: true, 228 mockError: nil, 229 expectedError: nil, 230 expectStats: true, 231 }, 232 { 233 name: "get container info when cadvisor failed", 234 containerID: "ab2cdf", 235 containerPath: "/docker/ab2cdf", 236 cadvisorContainerInfo: cadvisorapiv1.ContainerInfo{}, 237 runtimeError: nil, 238 podList: []*kubecontainer.Pod{ 239 { 240 ID: "uuid", 241 Name: "qux", 242 Namespace: "ns", 243 Containers: []*kubecontainer.Container{ 244 { 245 Name: "foo", 246 ID: kubecontainer.ContainerID{Type: "test", ID: "ab2cdf"}, 247 }, 248 }, 249 }, 250 }, 251 requestedPodFullName: "qux_ns", 252 requestedPodUID: "uuid", 253 requestedContainerName: "foo", 254 expectDockerContainerCall: true, 255 mockError: cadvisorAPIFailure, 256 expectedError: cadvisorAPIFailure, 257 expectStats: false, 258 }, 259 { 260 name: "get container info on non-existent container", 261 containerID: "", 262 containerPath: "", 263 cadvisorContainerInfo: cadvisorapiv1.ContainerInfo{}, 264 runtimeError: nil, 265 podList: []*kubecontainer.Pod{}, 266 requestedPodFullName: "qux", 267 requestedPodUID: "", 268 requestedContainerName: "foo", 269 expectDockerContainerCall: false, 270 mockError: nil, 271 expectedError: kubecontainer.ErrContainerNotFound, 272 expectStats: false, 273 }, 274 { 275 name: "get container info when container runtime failed", 276 containerID: "", 277 containerPath: "", 278 cadvisorContainerInfo: cadvisorapiv1.ContainerInfo{}, 279 runtimeError: runtimeError, 280 podList: []*kubecontainer.Pod{}, 281 requestedPodFullName: "qux", 282 requestedPodUID: "", 283 requestedContainerName: "foo", 284 mockError: nil, 285 expectedError: runtimeError, 286 expectStats: false, 287 }, 288 { 289 name: "get container info with no containers", 290 containerID: "", 291 containerPath: "", 292 cadvisorContainerInfo: cadvisorapiv1.ContainerInfo{}, 293 runtimeError: nil, 294 podList: []*kubecontainer.Pod{}, 295 requestedPodFullName: "qux_ns", 296 requestedPodUID: "", 297 requestedContainerName: "foo", 298 mockError: nil, 299 expectedError: kubecontainer.ErrContainerNotFound, 300 expectStats: false, 301 }, 302 { 303 name: "get container info with no matching containers", 304 containerID: "", 305 containerPath: "", 306 cadvisorContainerInfo: cadvisorapiv1.ContainerInfo{}, 307 runtimeError: nil, 308 podList: []*kubecontainer.Pod{ 309 { 310 ID: "12345678", 311 Name: "qux", 312 Namespace: "ns", 313 Containers: []*kubecontainer.Container{ 314 { 315 Name: "bar", 316 ID: kubecontainer.ContainerID{Type: "test", ID: "fakeID"}, 317 }, 318 }, 319 }, 320 }, 321 requestedPodFullName: "qux_ns", 322 requestedPodUID: "", 323 requestedContainerName: "foo", 324 mockError: nil, 325 expectedError: kubecontainer.ErrContainerNotFound, 326 expectStats: false, 327 }, 328 } 329 330 mockCtrl := gomock.NewController(t) 331 defer mockCtrl.Finish() 332 333 for _, tc := range tests { 334 var ( 335 mockCadvisor = cadvisortest.NewMockInterface(mockCtrl) 336 mockPodManager = kubepodtest.NewMockManager(mockCtrl) 337 mockRuntimeCache = kubecontainertest.NewMockRuntimeCache(mockCtrl) 338 339 cadvisorReq = &cadvisorapiv1.ContainerInfoRequest{} 340 ) 341 342 mockPodManager.EXPECT().TranslatePodUID(tc.requestedPodUID).Return(kubetypes.ResolvedPodUID(tc.requestedPodUID)) 343 mockRuntimeCache.EXPECT().GetPods(ctx).Return(tc.podList, tc.runtimeError) 344 if tc.expectDockerContainerCall { 345 mockCadvisor.EXPECT().DockerContainer(tc.containerID, cadvisorReq).Return(tc.cadvisorContainerInfo, tc.mockError) 346 } 347 348 provider := newStatsProvider(mockCadvisor, mockPodManager, mockRuntimeCache, fakeContainerStatsProvider{}) 349 stats, err := provider.GetContainerInfo(ctx, tc.requestedPodFullName, tc.requestedPodUID, tc.requestedContainerName, cadvisorReq) 350 assert.Equal(t, tc.expectedError, err) 351 352 if tc.expectStats { 353 require.NotNil(t, stats) 354 } 355 } 356 } 357 358 func TestGetRawContainerInfoRoot(t *testing.T) { 359 mockCtrl := gomock.NewController(t) 360 defer mockCtrl.Finish() 361 362 var ( 363 mockCadvisor = cadvisortest.NewMockInterface(mockCtrl) 364 mockPodManager = new(kubepodtest.MockManager) 365 mockRuntimeCache = new(kubecontainertest.MockRuntimeCache) 366 367 cadvisorReq = &cadvisorapiv1.ContainerInfoRequest{} 368 containerPath = "/" 369 containerInfo = &cadvisorapiv1.ContainerInfo{ 370 ContainerReference: cadvisorapiv1.ContainerReference{ 371 Name: containerPath, 372 }, 373 } 374 ) 375 376 mockCadvisor.EXPECT().ContainerInfo(containerPath, cadvisorReq).Return(containerInfo, nil) 377 378 provider := newStatsProvider(mockCadvisor, mockPodManager, mockRuntimeCache, fakeContainerStatsProvider{}) 379 _, err := provider.GetRawContainerInfo(containerPath, cadvisorReq, false) 380 assert.NoError(t, err) 381 } 382 383 func TestGetRawContainerInfoSubcontainers(t *testing.T) { 384 mockCtrl := gomock.NewController(t) 385 defer mockCtrl.Finish() 386 387 var ( 388 mockCadvisor = cadvisortest.NewMockInterface(mockCtrl) 389 mockPodManager = new(kubepodtest.MockManager) 390 mockRuntimeCache = new(kubecontainertest.MockRuntimeCache) 391 392 cadvisorReq = &cadvisorapiv1.ContainerInfoRequest{} 393 containerPath = "/kubelet" 394 containerInfo = map[string]*cadvisorapiv1.ContainerInfo{ 395 containerPath: { 396 ContainerReference: cadvisorapiv1.ContainerReference{ 397 Name: containerPath, 398 }, 399 }, 400 "/kubelet/sub": { 401 ContainerReference: cadvisorapiv1.ContainerReference{ 402 Name: "/kubelet/sub", 403 }, 404 }, 405 } 406 ) 407 408 mockCadvisor.EXPECT().SubcontainerInfo(containerPath, cadvisorReq).Return(containerInfo, nil) 409 410 provider := newStatsProvider(mockCadvisor, mockPodManager, mockRuntimeCache, fakeContainerStatsProvider{}) 411 result, err := provider.GetRawContainerInfo(containerPath, cadvisorReq, true) 412 assert.NoError(t, err) 413 assert.Len(t, result, 2) 414 } 415 416 func TestHasDedicatedImageFs(t *testing.T) { 417 ctx := context.Background() 418 mockCtrl := gomock.NewController(t) 419 defer mockCtrl.Finish() 420 imageStatsExpected := &statsapi.FsStats{AvailableBytes: uint64Ptr(1)} 421 422 for desc, test := range map[string]struct { 423 rootfsDevice string 424 imagefsDevice string 425 dedicated bool 426 imageFsStats *statsapi.FsStats 427 containerFsStats *statsapi.FsStats 428 }{ 429 "dedicated device for image filesystem": { 430 rootfsDevice: "root/device", 431 imagefsDevice: "image/device", 432 dedicated: true, 433 imageFsStats: imageStatsExpected, 434 }, 435 "shared device for image filesystem": { 436 rootfsDevice: "share/device", 437 imagefsDevice: "share/device", 438 dedicated: false, 439 imageFsStats: imageStatsExpected, 440 containerFsStats: imageStatsExpected, 441 }, 442 "split filesystem for images": { 443 rootfsDevice: "root/device", 444 imagefsDevice: "root/device", 445 dedicated: true, 446 imageFsStats: &statsapi.FsStats{AvailableBytes: uint64Ptr(1)}, 447 containerFsStats: &statsapi.FsStats{AvailableBytes: uint64Ptr(2)}, 448 }, 449 } { 450 t.Logf("TestCase %q", desc) 451 var ( 452 mockCadvisor = cadvisortest.NewMockInterface(mockCtrl) 453 mockPodManager = new(kubepodtest.MockManager) 454 mockRuntimeCache = new(kubecontainertest.MockRuntimeCache) 455 ) 456 mockCadvisor.EXPECT().RootFsInfo().Return(cadvisorapiv2.FsInfo{Device: test.rootfsDevice}, nil) 457 provider := newStatsProvider(mockCadvisor, mockPodManager, mockRuntimeCache, fakeContainerStatsProvider{ 458 device: test.imagefsDevice, 459 imageFs: test.imageFsStats, 460 containerFs: test.containerFsStats, 461 }) 462 463 dedicated, err := provider.HasDedicatedImageFs(ctx) 464 assert.NoError(t, err) 465 assert.Equal(t, test.dedicated, dedicated) 466 } 467 } 468 469 func getTerminatedContainerInfo(seed int, podName string, podNamespace string, containerName string) cadvisorapiv2.ContainerInfo { 470 cinfo := getTestContainerInfo(seed, podName, podNamespace, containerName) 471 cinfo.Stats[0].Memory.RSS = 0 472 cinfo.Stats[0].CpuInst.Usage.Total = 0 473 cinfo.Stats[0].Network = &cadvisorapiv2.NetworkStats{ 474 Interfaces: []cadvisorapiv1.InterfaceStats{{ 475 Name: "eth0", 476 RxBytes: 0, 477 RxErrors: 0, 478 TxBytes: 0, 479 TxErrors: 0, 480 }, { 481 Name: "cbr0", 482 RxBytes: 0, 483 RxErrors: 0, 484 TxBytes: 0, 485 TxErrors: 0, 486 }}, 487 } 488 return cinfo 489 } 490 491 func getContainerInfoWithZeroCpuMem(seed int, podName string, podNamespace string, containerName string) cadvisorapiv2.ContainerInfo { 492 cinfo := getTestContainerInfo(seed, podName, podNamespace, containerName) 493 cinfo.Stats[0].Memory.RSS = 0 494 cinfo.Stats[0].CpuInst.Usage.Total = 0 495 return cinfo 496 } 497 498 func getTestContainerInfo(seed int, podName string, podNamespace string, containerName string) cadvisorapiv2.ContainerInfo { 499 labels := map[string]string{} 500 if podName != "" { 501 labels = map[string]string{ 502 "io.kubernetes.pod.name": podName, 503 "io.kubernetes.pod.uid": "UID" + podName, 504 "io.kubernetes.pod.namespace": podNamespace, 505 "io.kubernetes.container.name": containerName, 506 } 507 } 508 // by default, kernel will set memory.limit_in_bytes to 1 << 63 if not bounded 509 unlimitedMemory := uint64(1 << 63) 510 spec := cadvisorapiv2.ContainerSpec{ 511 CreationTime: testTime(creationTime, seed), 512 HasCpu: true, 513 HasMemory: true, 514 HasNetwork: true, 515 Labels: labels, 516 Memory: cadvisorapiv2.MemorySpec{ 517 Limit: unlimitedMemory, 518 SwapLimit: unlimitedMemory, 519 }, 520 CustomMetrics: generateCustomMetricSpec(), 521 } 522 523 totalUsageBytes := uint64(seed + offsetFsTotalUsageBytes) 524 baseUsageBytes := uint64(seed + offsetFsBaseUsageBytes) 525 inodeUsage := uint64(seed + offsetFsInodeUsage) 526 527 stats := cadvisorapiv2.ContainerStats{ 528 Timestamp: testTime(timestamp, seed), 529 Cpu: &cadvisorapiv1.CpuStats{}, 530 CpuInst: &cadvisorapiv2.CpuInstStats{}, 531 Memory: &cadvisorapiv1.MemoryStats{ 532 Usage: uint64(seed + offsetMemUsageBytes), 533 WorkingSet: uint64(seed + offsetMemWorkingSetBytes), 534 RSS: uint64(seed + offsetMemRSSBytes), 535 ContainerData: cadvisorapiv1.MemoryStatsMemoryData{ 536 Pgfault: uint64(seed + offsetMemPageFaults), 537 Pgmajfault: uint64(seed + offsetMemMajorPageFaults), 538 }, 539 Swap: uint64(seed + offsetMemSwapUsageBytes), 540 }, 541 Network: &cadvisorapiv2.NetworkStats{ 542 Interfaces: []cadvisorapiv1.InterfaceStats{{ 543 Name: "eth0", 544 RxBytes: uint64(seed + offsetNetRxBytes), 545 RxErrors: uint64(seed + offsetNetRxErrors), 546 TxBytes: uint64(seed + offsetNetTxBytes), 547 TxErrors: uint64(seed + offsetNetTxErrors), 548 }, { 549 Name: "cbr0", 550 RxBytes: 100, 551 RxErrors: 100, 552 TxBytes: 100, 553 TxErrors: 100, 554 }}, 555 }, 556 CustomMetrics: generateCustomMetrics(spec.CustomMetrics), 557 Filesystem: &cadvisorapiv2.FilesystemStats{ 558 TotalUsageBytes: &totalUsageBytes, 559 BaseUsageBytes: &baseUsageBytes, 560 InodeUsage: &inodeUsage, 561 }, 562 Accelerators: []cadvisorapiv1.AcceleratorStats{ 563 { 564 Make: "nvidia", 565 Model: "Tesla K80", 566 ID: "foobar", 567 MemoryTotal: uint64(seed + offsetMemUsageBytes), 568 MemoryUsed: uint64(seed + offsetMemUsageBytes), 569 DutyCycle: uint64(seed + offsetAcceleratorDutyCycle), 570 }, 571 }, 572 } 573 stats.Cpu.Usage.Total = uint64(seed + offsetCPUUsageCoreSeconds) 574 stats.CpuInst.Usage.Total = uint64(seed + offsetCPUUsageCores) 575 return cadvisorapiv2.ContainerInfo{ 576 Spec: spec, 577 Stats: []*cadvisorapiv2.ContainerStats{&stats}, 578 } 579 } 580 581 func getTestFsInfo(seed int) cadvisorapiv2.FsInfo { 582 var ( 583 inodes = uint64(seed + offsetFsInodes) 584 inodesFree = uint64(seed + offsetFsInodesFree) 585 ) 586 return cadvisorapiv2.FsInfo{ 587 Timestamp: time.Now(), 588 Device: "test-device", 589 Mountpoint: "test-mount-point", 590 Capacity: uint64(seed + offsetFsCapacity), 591 Available: uint64(seed + offsetFsAvailable), 592 Usage: uint64(seed + offsetFsUsage), 593 Inodes: &inodes, 594 InodesFree: &inodesFree, 595 } 596 } 597 598 func getPodVolumeStats(seed int, volumeName string) statsapi.VolumeStats { 599 availableBytes := uint64(seed + offsetFsAvailable) 600 capacityBytes := uint64(seed + offsetFsCapacity) 601 usedBytes := uint64(seed + offsetFsUsage) 602 inodes := uint64(seed + offsetFsInodes) 603 inodesFree := uint64(seed + offsetFsInodesFree) 604 inodesUsed := uint64(seed + offsetFsInodeUsage) 605 fsStats := statsapi.FsStats{ 606 Time: metav1.NewTime(time.Now()), 607 AvailableBytes: &availableBytes, 608 CapacityBytes: &capacityBytes, 609 UsedBytes: &usedBytes, 610 Inodes: &inodes, 611 InodesFree: &inodesFree, 612 InodesUsed: &inodesUsed, 613 } 614 return statsapi.VolumeStats{ 615 FsStats: fsStats, 616 Name: volumeName, 617 } 618 } 619 620 func generateCustomMetricSpec() []cadvisorapiv1.MetricSpec { 621 f := fuzz.New().NilChance(0).Funcs( 622 func(e *cadvisorapiv1.MetricSpec, c fuzz.Continue) { 623 c.Fuzz(&e.Name) 624 switch c.Intn(3) { 625 case 0: 626 e.Type = cadvisorapiv1.MetricGauge 627 case 1: 628 e.Type = cadvisorapiv1.MetricCumulative 629 case 2: 630 e.Type = cadvisorapiv1.MetricType("delta") 631 } 632 switch c.Intn(2) { 633 case 0: 634 e.Format = cadvisorapiv1.IntType 635 case 1: 636 e.Format = cadvisorapiv1.FloatType 637 } 638 c.Fuzz(&e.Units) 639 }) 640 var ret []cadvisorapiv1.MetricSpec 641 f.Fuzz(&ret) 642 return ret 643 } 644 645 func generateCustomMetrics(spec []cadvisorapiv1.MetricSpec) map[string][]cadvisorapiv1.MetricVal { 646 ret := map[string][]cadvisorapiv1.MetricVal{} 647 for _, metricSpec := range spec { 648 f := fuzz.New().NilChance(0).Funcs( 649 func(e *cadvisorapiv1.MetricVal, c fuzz.Continue) { 650 switch metricSpec.Format { 651 case cadvisorapiv1.IntType: 652 c.Fuzz(&e.IntValue) 653 case cadvisorapiv1.FloatType: 654 c.Fuzz(&e.FloatValue) 655 } 656 }) 657 658 var metrics []cadvisorapiv1.MetricVal 659 f.Fuzz(&metrics) 660 ret[metricSpec.Name] = metrics 661 } 662 return ret 663 } 664 665 func testTime(base time.Time, seed int) time.Time { 666 return base.Add(time.Duration(seed) * time.Second) 667 } 668 669 func checkNetworkStats(t *testing.T, label string, seed int, stats *statsapi.NetworkStats) { 670 assert.NotNil(t, stats) 671 assert.EqualValues(t, testTime(timestamp, seed).Unix(), stats.Time.Time.Unix(), label+".Net.Time") 672 assert.EqualValues(t, "eth0", stats.Name, "default interface name is not eth0") 673 assert.EqualValues(t, seed+offsetNetRxBytes, *stats.RxBytes, label+".Net.RxBytes") 674 assert.EqualValues(t, seed+offsetNetRxErrors, *stats.RxErrors, label+".Net.RxErrors") 675 assert.EqualValues(t, seed+offsetNetTxBytes, *stats.TxBytes, label+".Net.TxBytes") 676 assert.EqualValues(t, seed+offsetNetTxErrors, *stats.TxErrors, label+".Net.TxErrors") 677 678 assert.EqualValues(t, 2, len(stats.Interfaces), "network interfaces should contain 2 elements") 679 680 assert.EqualValues(t, "eth0", stats.Interfaces[0].Name, "default interface name is not eth0") 681 assert.EqualValues(t, seed+offsetNetRxBytes, *stats.Interfaces[0].RxBytes, label+".Net.TxErrors") 682 assert.EqualValues(t, seed+offsetNetRxErrors, *stats.Interfaces[0].RxErrors, label+".Net.TxErrors") 683 assert.EqualValues(t, seed+offsetNetTxBytes, *stats.Interfaces[0].TxBytes, label+".Net.TxErrors") 684 assert.EqualValues(t, seed+offsetNetTxErrors, *stats.Interfaces[0].TxErrors, label+".Net.TxErrors") 685 686 assert.EqualValues(t, "cbr0", stats.Interfaces[1].Name, "cbr0 interface name is not cbr0") 687 assert.EqualValues(t, 100, *stats.Interfaces[1].RxBytes, label+".Net.TxErrors") 688 assert.EqualValues(t, 100, *stats.Interfaces[1].RxErrors, label+".Net.TxErrors") 689 assert.EqualValues(t, 100, *stats.Interfaces[1].TxBytes, label+".Net.TxErrors") 690 assert.EqualValues(t, 100, *stats.Interfaces[1].TxErrors, label+".Net.TxErrors") 691 692 } 693 694 func checkCPUStats(t *testing.T, label string, seed int, stats *statsapi.CPUStats) { 695 require.NotNil(t, stats.Time, label+".CPU.Time") 696 require.NotNil(t, stats.UsageNanoCores, label+".CPU.UsageNanoCores") 697 require.NotNil(t, stats.UsageNanoCores, label+".CPU.UsageCoreSeconds") 698 assert.EqualValues(t, testTime(timestamp, seed).Unix(), stats.Time.Time.Unix(), label+".CPU.Time") 699 assert.EqualValues(t, seed+offsetCPUUsageCores, *stats.UsageNanoCores, label+".CPU.UsageCores") 700 assert.EqualValues(t, seed+offsetCPUUsageCoreSeconds, *stats.UsageCoreNanoSeconds, label+".CPU.UsageCoreSeconds") 701 } 702 703 func checkMemoryStats(t *testing.T, label string, seed int, info cadvisorapiv2.ContainerInfo, stats *statsapi.MemoryStats) { 704 assert.EqualValues(t, testTime(timestamp, seed).Unix(), stats.Time.Time.Unix(), label+".Mem.Time") 705 assert.EqualValues(t, seed+offsetMemUsageBytes, *stats.UsageBytes, label+".Mem.UsageBytes") 706 assert.EqualValues(t, seed+offsetMemWorkingSetBytes, *stats.WorkingSetBytes, label+".Mem.WorkingSetBytes") 707 assert.EqualValues(t, seed+offsetMemRSSBytes, *stats.RSSBytes, label+".Mem.RSSBytes") 708 assert.EqualValues(t, seed+offsetMemPageFaults, *stats.PageFaults, label+".Mem.PageFaults") 709 assert.EqualValues(t, seed+offsetMemMajorPageFaults, *stats.MajorPageFaults, label+".Mem.MajorPageFaults") 710 if !info.Spec.HasMemory || isMemoryUnlimited(info.Spec.Memory.Limit) { 711 assert.Nil(t, stats.AvailableBytes, label+".Mem.AvailableBytes") 712 } else { 713 expected := info.Spec.Memory.Limit - *stats.WorkingSetBytes 714 assert.EqualValues(t, expected, *stats.AvailableBytes, label+".Mem.AvailableBytes") 715 } 716 } 717 718 func checkSwapStats(t *testing.T, label string, seed int, info cadvisorapiv2.ContainerInfo, stats *statsapi.SwapStats) { 719 label += ".Swap" 720 721 assert.EqualValues(t, testTime(timestamp, seed).Unix(), stats.Time.Time.Unix(), label+".Time") 722 assert.EqualValues(t, seed+offsetMemSwapUsageBytes, *stats.SwapUsageBytes, label+".SwapUsageBytes") 723 724 if !info.Spec.HasMemory || isMemoryUnlimited(info.Spec.Memory.SwapLimit) { 725 assert.Nil(t, stats.SwapAvailableBytes, label+".SwapAvailableBytes") 726 } else { 727 expected := info.Spec.Memory.Limit - *stats.SwapUsageBytes 728 assert.EqualValues(t, expected, *stats.SwapAvailableBytes, label+".AvailableBytes") 729 } 730 } 731 732 func checkFsStats(t *testing.T, label string, seed int, stats *statsapi.FsStats) { 733 assert.EqualValues(t, seed+offsetFsCapacity, *stats.CapacityBytes, label+".CapacityBytes") 734 assert.EqualValues(t, seed+offsetFsAvailable, *stats.AvailableBytes, label+".AvailableBytes") 735 assert.EqualValues(t, seed+offsetFsInodes, *stats.Inodes, label+".Inodes") 736 assert.EqualValues(t, seed+offsetFsInodesFree, *stats.InodesFree, label+".InodesFree") 737 } 738 739 func checkEphemeralStats(t *testing.T, label string, containerSeeds []int, volumeSeeds []int, containerLogStats []*volume.Metrics, stats *statsapi.FsStats) { 740 var usedBytes, inodeUsage int 741 for _, cseed := range containerSeeds { 742 usedBytes += cseed + offsetFsBaseUsageBytes 743 inodeUsage += cseed + offsetFsInodeUsage 744 // If containerLogStats is nil, then the log stats calculated from cAdvisor 745 // information is used. Since it's Total - Base, and these values are 746 // set to the offset, we can use the calculated difference in the offset 747 // to account for this. 748 if containerLogStats == nil { 749 usedBytes += offsetFsTotalUsageBytes - offsetFsBaseUsageBytes 750 } 751 } 752 for _, vseed := range volumeSeeds { 753 usedBytes += vseed + offsetFsUsage 754 inodeUsage += vseed + offsetFsInodeUsage 755 } 756 for _, logStats := range containerLogStats { 757 usedBytes += int(logStats.Used.Value()) 758 inodeUsage += int(logStats.InodesUsed.Value()) 759 } 760 assert.EqualValues(t, usedBytes, int(*stats.UsedBytes), label+".UsedBytes") 761 assert.EqualValues(t, inodeUsage, int(*stats.InodesUsed), label+".InodesUsed") 762 } 763 764 type fakeResourceAnalyzer struct { 765 podVolumeStats serverstats.PodVolumeStats 766 } 767 768 func (o *fakeResourceAnalyzer) Start() {} 769 func (o *fakeResourceAnalyzer) Get(context.Context, bool) (*statsapi.Summary, error) { return nil, nil } 770 func (o *fakeResourceAnalyzer) GetCPUAndMemoryStats(context.Context) (*statsapi.Summary, error) { 771 return nil, nil 772 } 773 func (o *fakeResourceAnalyzer) GetPodVolumeStats(uid types.UID) (serverstats.PodVolumeStats, bool) { 774 return o.podVolumeStats, true 775 } 776 777 type fakeContainerStatsProvider struct { 778 device string 779 imageFs *statsapi.FsStats 780 containerFs *statsapi.FsStats 781 } 782 783 func (p fakeContainerStatsProvider) ListPodStats(context.Context) ([]statsapi.PodStats, error) { 784 return nil, fmt.Errorf("not implemented") 785 } 786 787 func (p fakeContainerStatsProvider) ListPodStatsAndUpdateCPUNanoCoreUsage(context.Context) ([]statsapi.PodStats, error) { 788 return nil, fmt.Errorf("not implemented") 789 } 790 791 func (p fakeContainerStatsProvider) ListPodCPUAndMemoryStats(context.Context) ([]statsapi.PodStats, error) { 792 return nil, fmt.Errorf("not implemented") 793 } 794 795 func (p fakeContainerStatsProvider) ImageFsStats(context.Context) (*statsapi.FsStats, *statsapi.FsStats, error) { 796 return p.imageFs, p.containerFs, nil 797 } 798 799 func (p fakeContainerStatsProvider) ImageFsDevice(context.Context) (string, error) { 800 return p.device, nil 801 }