...

Source file src/k8s.io/kubernetes/pkg/kubelet/server/stats/volume_stat_calculator_test.go

Documentation: k8s.io/kubernetes/pkg/kubelet/server/stats

     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  	"errors"
    21  	"fmt"
    22  	"testing"
    23  	"time"
    24  
    25  	"github.com/golang/mock/gomock"
    26  	"github.com/stretchr/testify/assert"
    27  
    28  	csipbv1 "github.com/container-storage-interface/spec/lib/go/csi"
    29  	k8sv1 "k8s.io/api/core/v1"
    30  	"k8s.io/apimachinery/pkg/api/resource"
    31  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    32  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    33  	"k8s.io/client-go/tools/record"
    34  	featuregatetesting "k8s.io/component-base/featuregate/testing"
    35  	kubestats "k8s.io/kubelet/pkg/apis/stats/v1alpha1"
    36  	"k8s.io/kubernetes/pkg/features"
    37  	statstest "k8s.io/kubernetes/pkg/kubelet/server/stats/testing"
    38  	"k8s.io/kubernetes/pkg/volume"
    39  )
    40  
    41  const (
    42  	namespace0  = "test0"
    43  	pName0      = "pod0"
    44  	capacity    = int64(10000000)
    45  	available   = int64(5000000)
    46  	inodesTotal = int64(2000)
    47  	inodesFree  = int64(1000)
    48  
    49  	vol0          = "vol0"
    50  	vol1          = "vol1"
    51  	vol2          = "vol2"
    52  	vol3          = "vol3"
    53  	pvcClaimName0 = "pvc-fake0"
    54  	pvcClaimName1 = "pvc-fake1"
    55  )
    56  
    57  var (
    58  	ErrorWatchTimeout = errors.New("watch event timeout")
    59  	// Create pod spec to test against
    60  	podVolumes = []k8sv1.Volume{
    61  		{
    62  			Name: vol0,
    63  			VolumeSource: k8sv1.VolumeSource{
    64  				GCEPersistentDisk: &k8sv1.GCEPersistentDiskVolumeSource{
    65  					PDName: "fake-device1",
    66  				},
    67  			},
    68  		},
    69  		{
    70  			Name: vol1,
    71  			VolumeSource: k8sv1.VolumeSource{
    72  				PersistentVolumeClaim: &k8sv1.PersistentVolumeClaimVolumeSource{
    73  					ClaimName: pvcClaimName0,
    74  				},
    75  			},
    76  		},
    77  		{
    78  			Name: vol2,
    79  			VolumeSource: k8sv1.VolumeSource{
    80  				PersistentVolumeClaim: &k8sv1.PersistentVolumeClaimVolumeSource{
    81  					ClaimName: pvcClaimName1,
    82  				},
    83  			},
    84  		},
    85  		{
    86  			Name: vol3,
    87  			VolumeSource: k8sv1.VolumeSource{
    88  				Ephemeral: &k8sv1.EphemeralVolumeSource{},
    89  			},
    90  		},
    91  	}
    92  
    93  	fakePod = &k8sv1.Pod{
    94  		ObjectMeta: metav1.ObjectMeta{
    95  			Name:      pName0,
    96  			Namespace: namespace0,
    97  			UID:       "UID" + pName0,
    98  		},
    99  		Spec: k8sv1.PodSpec{
   100  			Volumes: podVolumes,
   101  		},
   102  	}
   103  
   104  	volumeCondition = &csipbv1.VolumeCondition{}
   105  )
   106  
   107  func TestPVCRef(t *testing.T) {
   108  	mockCtrl := gomock.NewController(t)
   109  	defer mockCtrl.Finish()
   110  
   111  	// Setup mock stats provider
   112  	mockStats := statstest.NewMockProvider(mockCtrl)
   113  	volumes := map[string]volume.Volume{vol0: &fakeVolume{}, vol1: &fakeVolume{}, vol3: &fakeVolume{}}
   114  	mockStats.EXPECT().ListVolumesForPod(fakePod.UID).Return(volumes, true)
   115  	blockVolumes := map[string]volume.BlockVolume{vol2: &fakeBlockVolume{}}
   116  	mockStats.EXPECT().ListBlockVolumesForPod(fakePod.UID).Return(blockVolumes, true)
   117  
   118  	eventStore := make(chan string, 1)
   119  	fakeEventRecorder := record.FakeRecorder{
   120  		Events: eventStore,
   121  	}
   122  
   123  	// Calculate stats for pod
   124  	statsCalculator := newVolumeStatCalculator(mockStats, time.Minute, fakePod, &fakeEventRecorder)
   125  	statsCalculator.calcAndStoreStats()
   126  	vs, _ := statsCalculator.GetLatest()
   127  
   128  	assert.Len(t, append(vs.EphemeralVolumes, vs.PersistentVolumes...), 4)
   129  	// Verify 'vol0' doesn't have a PVC reference
   130  	assert.Contains(t, append(vs.EphemeralVolumes, vs.PersistentVolumes...), kubestats.VolumeStats{
   131  		Name:              vol0,
   132  		FsStats:           expectedFSStats(),
   133  		VolumeHealthStats: expectedVolumeHealthStats(),
   134  	})
   135  	// Verify 'vol1' has a PVC reference
   136  	assert.Contains(t, append(vs.EphemeralVolumes, vs.PersistentVolumes...), kubestats.VolumeStats{
   137  		Name: vol1,
   138  		PVCRef: &kubestats.PVCReference{
   139  			Name:      pvcClaimName0,
   140  			Namespace: namespace0,
   141  		},
   142  		FsStats:           expectedFSStats(),
   143  		VolumeHealthStats: expectedVolumeHealthStats(),
   144  	})
   145  	// // Verify 'vol2' has a PVC reference
   146  	assert.Contains(t, append(vs.EphemeralVolumes, vs.PersistentVolumes...), kubestats.VolumeStats{
   147  		Name: vol2,
   148  		PVCRef: &kubestats.PVCReference{
   149  			Name:      pvcClaimName1,
   150  			Namespace: namespace0,
   151  		},
   152  		FsStats:           expectedBlockStats(),
   153  		VolumeHealthStats: expectedVolumeHealthStats(),
   154  	})
   155  	// Verify 'vol3' has a PVC reference
   156  	assert.Contains(t, append(vs.EphemeralVolumes, vs.PersistentVolumes...), kubestats.VolumeStats{
   157  		Name: vol3,
   158  		PVCRef: &kubestats.PVCReference{
   159  			Name:      pName0 + "-" + vol3,
   160  			Namespace: namespace0,
   161  		},
   162  		FsStats:           expectedFSStats(),
   163  		VolumeHealthStats: expectedVolumeHealthStats(),
   164  	})
   165  }
   166  
   167  func TestNormalVolumeEvent(t *testing.T) {
   168  	mockCtrl := gomock.NewController(t)
   169  	defer mockCtrl.Finish()
   170  	mockStats := statstest.NewMockProvider(mockCtrl)
   171  
   172  	volumes := map[string]volume.Volume{vol0: &fakeVolume{}, vol1: &fakeVolume{}}
   173  	mockStats.EXPECT().ListVolumesForPod(fakePod.UID).Return(volumes, true)
   174  	blockVolumes := map[string]volume.BlockVolume{vol2: &fakeBlockVolume{}}
   175  	mockStats.EXPECT().ListBlockVolumesForPod(fakePod.UID).Return(blockVolumes, true)
   176  
   177  	eventStore := make(chan string, 2)
   178  	fakeEventRecorder := record.FakeRecorder{
   179  		Events: eventStore,
   180  	}
   181  
   182  	// Calculate stats for pod
   183  	statsCalculator := newVolumeStatCalculator(mockStats, time.Minute, fakePod, &fakeEventRecorder)
   184  	statsCalculator.calcAndStoreStats()
   185  
   186  	event, err := WatchEvent(eventStore)
   187  	assert.NotNil(t, err)
   188  	assert.Equal(t, "", event)
   189  }
   190  
   191  func TestAbnormalVolumeEvent(t *testing.T) {
   192  	defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIVolumeHealth, true)()
   193  	mockCtrl := gomock.NewController(t)
   194  	defer mockCtrl.Finish()
   195  
   196  	// Setup mock stats provider
   197  	mockStats := statstest.NewMockProvider(mockCtrl)
   198  	volumes := map[string]volume.Volume{vol0: &fakeVolume{}}
   199  	mockStats.EXPECT().ListVolumesForPod(fakePod.UID).Return(volumes, true)
   200  	blockVolumes := map[string]volume.BlockVolume{vol1: &fakeBlockVolume{}}
   201  	mockStats.EXPECT().ListBlockVolumesForPod(fakePod.UID).Return(blockVolumes, true)
   202  
   203  	eventStore := make(chan string, 2)
   204  	fakeEventRecorder := record.FakeRecorder{
   205  		Events: eventStore,
   206  	}
   207  
   208  	// Calculate stats for pod
   209  	if volumeCondition != nil {
   210  		volumeCondition.Message = "The target path of the volume doesn't exist"
   211  		volumeCondition.Abnormal = true
   212  	}
   213  	statsCalculator := newVolumeStatCalculator(mockStats, time.Minute, fakePod, &fakeEventRecorder)
   214  	statsCalculator.calcAndStoreStats()
   215  
   216  	event, err := WatchEvent(eventStore)
   217  	assert.Nil(t, err)
   218  	assert.Equal(t, fmt.Sprintf("Warning VolumeConditionAbnormal Volume %s: The target path of the volume doesn't exist", "vol0"), event)
   219  }
   220  
   221  func WatchEvent(eventChan <-chan string) (string, error) {
   222  	select {
   223  	case event := <-eventChan:
   224  		return event, nil
   225  	case <-time.After(5 * time.Second):
   226  		return "", ErrorWatchTimeout
   227  	}
   228  }
   229  
   230  // Fake volume/metrics provider
   231  var _ volume.Volume = &fakeVolume{}
   232  
   233  type fakeVolume struct{}
   234  
   235  func (v *fakeVolume) GetPath() string { return "" }
   236  
   237  func (v *fakeVolume) GetMetrics() (*volume.Metrics, error) {
   238  	return expectedMetrics(), nil
   239  }
   240  
   241  func expectedMetrics() *volume.Metrics {
   242  	vMetrics := &volume.Metrics{
   243  		Available:  resource.NewQuantity(available, resource.BinarySI),
   244  		Capacity:   resource.NewQuantity(capacity, resource.BinarySI),
   245  		Used:       resource.NewQuantity(available-capacity, resource.BinarySI),
   246  		Inodes:     resource.NewQuantity(inodesTotal, resource.BinarySI),
   247  		InodesFree: resource.NewQuantity(inodesFree, resource.BinarySI),
   248  		InodesUsed: resource.NewQuantity(inodesTotal-inodesFree, resource.BinarySI),
   249  	}
   250  
   251  	if volumeCondition != nil {
   252  		vMetrics.Message = &volumeCondition.Message
   253  		vMetrics.Abnormal = &volumeCondition.Abnormal
   254  	}
   255  
   256  	return vMetrics
   257  }
   258  
   259  func expectedFSStats() kubestats.FsStats {
   260  	metric := expectedMetrics()
   261  	available := uint64(metric.Available.Value())
   262  	capacity := uint64(metric.Capacity.Value())
   263  	used := uint64(metric.Used.Value())
   264  	inodes := uint64(metric.Inodes.Value())
   265  	inodesFree := uint64(metric.InodesFree.Value())
   266  	inodesUsed := uint64(metric.InodesUsed.Value())
   267  	return kubestats.FsStats{
   268  		AvailableBytes: &available,
   269  		CapacityBytes:  &capacity,
   270  		UsedBytes:      &used,
   271  		Inodes:         &inodes,
   272  		InodesFree:     &inodesFree,
   273  		InodesUsed:     &inodesUsed,
   274  	}
   275  }
   276  
   277  func expectedVolumeHealthStats() *kubestats.VolumeHealthStats {
   278  	metric := expectedMetrics()
   279  	hs := &kubestats.VolumeHealthStats{}
   280  
   281  	if metric != nil && metric.Abnormal != nil {
   282  		hs.Abnormal = *metric.Abnormal
   283  	}
   284  
   285  	return hs
   286  }
   287  
   288  // Fake block-volume/metrics provider, block-devices have no inodes
   289  var _ volume.BlockVolume = &fakeBlockVolume{}
   290  
   291  type fakeBlockVolume struct{}
   292  
   293  func (v *fakeBlockVolume) GetGlobalMapPath(*volume.Spec) (string, error) { return "", nil }
   294  
   295  func (v *fakeBlockVolume) GetPodDeviceMapPath() (string, string) { return "", "" }
   296  
   297  func (v *fakeBlockVolume) SupportsMetrics() bool { return true }
   298  
   299  func (v *fakeBlockVolume) GetMetrics() (*volume.Metrics, error) {
   300  	return expectedBlockMetrics(), nil
   301  }
   302  
   303  func expectedBlockMetrics() *volume.Metrics {
   304  	vMetrics := &volume.Metrics{
   305  		Available: resource.NewQuantity(available, resource.BinarySI),
   306  		Capacity:  resource.NewQuantity(capacity, resource.BinarySI),
   307  		Used:      resource.NewQuantity(available-capacity, resource.BinarySI),
   308  	}
   309  
   310  	if volumeCondition != nil {
   311  		vMetrics.Abnormal = &volumeCondition.Abnormal
   312  	}
   313  
   314  	return vMetrics
   315  }
   316  
   317  func expectedBlockStats() kubestats.FsStats {
   318  	metric := expectedBlockMetrics()
   319  	available := uint64(metric.Available.Value())
   320  	capacity := uint64(metric.Capacity.Value())
   321  	used := uint64(metric.Used.Value())
   322  	null := uint64(0)
   323  	return kubestats.FsStats{
   324  		AvailableBytes: &available,
   325  		CapacityBytes:  &capacity,
   326  		UsedBytes:      &used,
   327  		Inodes:         &null,
   328  		InodesFree:     &null,
   329  		InodesUsed:     &null,
   330  	}
   331  }
   332  

View as plain text