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  }