Add SupportsMetrics() for Block-mode volumes

Volumes that are provisioned with `VolumeMode: Block` often have a
MetrucsProvider interface declared in their type. However, the
MetricsProvider should implement a GetMetrics() function. In the cases
where the storage drivers do not implement GetMetrics(), a panic can
occur.

Usual type-assertions are not sufficient in this case. All assertions
assume the interface is present. There is no straight forward way to
verify that a valid GetMetrics() function is provided.

By adding SupportsMetrics(), storage driver implementations require
careful reviewing for metrics support.
This commit is contained in:
Niels de Vos 2021-05-20 15:18:26 +02:00
parent fd3bbf6f9e
commit b997e0e4d6
15 changed files with 88 additions and 1 deletions

View File

@ -567,6 +567,10 @@ func (f *stubBlockVolume) UnmapPodDevice() error {
return nil
}
func (f *stubBlockVolume) SupportsMetrics() bool {
return false
}
func (f *stubBlockVolume) GetMetrics() (*volume.Metrics, error) {
return nil, nil
}

View File

@ -112,7 +112,12 @@ func (s *volumeStatCalculator) calcAndStoreStats() {
for name, v := range blockVolumes {
// Only add the blockVolume if it implements the MetricsProvider interface
if _, ok := v.(volume.MetricsProvider); ok {
metricVolumes[name] = v
// Some drivers inherit the MetricsProvider interface from Filesystem
// mode volumes, but do not implement it for Block mode. Checking
// SupportsMetrics() will prevent panics in that case.
if v.SupportsMetrics() {
metricVolumes[name] = v
}
}
}
}

View File

@ -246,6 +246,8 @@ func (v *fakeBlockVolume) GetGlobalMapPath(*volume.Spec) (string, error) { retur
func (v *fakeBlockVolume) GetPodDeviceMapPath() (string, string) { return "", "" }
func (v *fakeBlockVolume) SupportsMetrics() bool { return true }
func (v *fakeBlockVolume) GetMetrics() (*volume.Metrics, error) {
return expectedBlockMetrics(), nil
}

View File

@ -165,3 +165,9 @@ func (ebs *awsElasticBlockStore) GetPodDeviceMapPath() (string, string) {
name := awsElasticBlockStorePluginName
return ebs.plugin.host.GetPodVolumeDeviceDir(ebs.podUID, utilstrings.EscapeQualifiedName(name)), ebs.volName
}
// SupportsMetrics returns true for awsElasticBlockStore as it initializes the
// MetricsProvider.
func (ebs *awsElasticBlockStore) SupportsMetrics() bool {
return true
}

View File

@ -129,6 +129,7 @@ func (plugin *azureDataDiskPlugin) newUnmapperInternal(volName string, podUID ty
type azureDataDiskUnmapper struct {
*dataDisk
volume.MetricsNil
}
var _ volume.BlockVolumeUnmapper = &azureDataDiskUnmapper{}
@ -157,3 +158,9 @@ func (disk *dataDisk) GetPodDeviceMapPath() (string, string) {
name := azureDataDiskPluginName
return disk.plugin.host.GetPodVolumeDeviceDir(disk.podUID, utilstrings.EscapeQualifiedName(name)), disk.volumeName
}
// SupportsMetrics returns true for azureDataDiskMapper as it initializes the
// MetricsProvider.
func (addm *azureDataDiskMapper) SupportsMetrics() bool {
return true
}

View File

@ -140,6 +140,7 @@ func (plugin *cinderPlugin) newUnmapperInternal(volName string, podUID types.UID
type cinderPluginUnmapper struct {
*cinderVolume
volume.MetricsNil
}
var _ volume.BlockVolumeUnmapper = &cinderPluginUnmapper{}
@ -168,3 +169,9 @@ func (cd *cinderVolume) GetPodDeviceMapPath() (string, string) {
name := cinderVolumePluginName
return cd.plugin.host.GetPodVolumeDeviceDir(cd.podUID, utilstrings.EscapeQualifiedName(name)), cd.volName
}
// SupportsMetrics returns true for cinderVolumeMapper as it initializes the
// MetricsProvider.
func (cvm *cinderVolumeMapper) SupportsMetrics() bool {
return true
}

View File

@ -115,6 +115,12 @@ func (m *csiBlockMapper) GetStagingPath() string {
return filepath.Join(m.plugin.host.GetVolumeDevicePluginDir(CSIPluginName), "staging", m.specName)
}
// SupportsMetrics returns true for csiBlockMapper as it initializes the
// MetricsProvider.
func (m *csiBlockMapper) SupportsMetrics() bool {
return true
}
// getPublishDir returns path to a directory, where the volume is published to each pod.
// Example: plugins/kubernetes.io/csi/volumeDevices/publish/{specName}
func (m *csiBlockMapper) getPublishDir() string {

View File

@ -174,3 +174,9 @@ func (pd *gcePersistentDisk) GetPodDeviceMapPath() (string, string) {
name := gcePersistentDiskPluginName
return pd.plugin.host.GetPodVolumeDeviceDir(pd.podUID, utilstrings.EscapeQualifiedName(name)), pd.volName
}
// SupportsMetrics returns true for gcePersistentDisk as it initializes the
// MetricsProvider.
func (pd *gcePersistentDisk) SupportsMetrics() bool {
return true
}

View File

@ -393,6 +393,13 @@ type iscsiDiskUnmapper struct {
*iscsiDisk
exec utilexec.Interface
deviceUtil ioutil.DeviceUtil
volume.MetricsNil
}
// SupportsMetrics returns true for SupportsMetrics as it initializes the
// MetricsProvider.
func (idm *iscsiDiskMapper) SupportsMetrics() bool {
return true
}
var _ volume.BlockVolumeUnmapper = &iscsiDiskUnmapper{}

View File

@ -633,9 +633,16 @@ func (m *localVolumeMapper) GetStagingPath() string {
return ""
}
// SupportsMetrics returns true for SupportsMetrics as it initializes the
// MetricsProvider.
func (m *localVolumeMapper) SupportsMetrics() bool {
return true
}
// localVolumeUnmapper implements the BlockVolumeUnmapper interface for local volumes.
type localVolumeUnmapper struct {
*localVolume
volume.MetricsNil
}
var _ volume.BlockVolumeUnmapper = &localVolumeUnmapper{}

View File

@ -23,6 +23,11 @@ var _ MetricsProvider = &MetricsNil{}
// metrics.
type MetricsNil struct{}
// SupportsMetrics returns false for the MetricsNil type.
func (*MetricsNil) SupportsMetrics() bool {
return false
}
// GetMetrics returns an empty Metrics and an error.
// See MetricsProvider.GetMetrics
func (*MetricsNil) GetMetrics() (*Metrics, error) {

View File

@ -20,6 +20,14 @@ import (
"testing"
)
func TestMetricsNilSupportsMetrics(t *testing.T) {
metrics := &MetricsNil{}
supported := metrics.SupportsMetrics()
if supported {
t.Error("Expected no support for metrics")
}
}
func TestMetricsNilGetCapacity(t *testing.T) {
metrics := &MetricsNil{}
actual, err := metrics.GetMetrics()

View File

@ -938,6 +938,12 @@ func (rbd *rbd) rbdPodDeviceMapPath() (string, string) {
return rbd.plugin.host.GetPodVolumeDeviceDir(rbd.podUID, utilstrings.EscapeQualifiedName(name)), rbd.volName
}
// SupportsMetrics returns true for rbdDiskMapper as it initializes the
// MetricsProvider.
func (rdm *rbdDiskMapper) SupportsMetrics() bool {
return true
}
type rbdDiskUnmapper struct {
*rbdDiskMapper
}

View File

@ -49,6 +49,10 @@ type BlockVolume interface {
// ex. pods/{podUid}/{DefaultKubeletVolumeDevicesDirName}/{escapeQualifiedPluginName}/, {volumeName}
GetPodDeviceMapPath() (string, string)
// SupportsMetrics should return true if the MetricsProvider is
// initialized
SupportsMetrics() bool
// MetricsProvider embeds methods for exposing metrics (e.g.
// used, available space).
MetricsProvider

View File

@ -144,6 +144,7 @@ var _ volume.BlockVolumeUnmapper = &vsphereBlockVolumeUnmapper{}
type vsphereBlockVolumeUnmapper struct {
*vsphereVolume
volume.MetricsNil
}
// GetGlobalMapPath returns global map path and error
@ -159,3 +160,9 @@ func (v *vsphereVolume) GetGlobalMapPath(spec *volume.Spec) (string, error) {
func (v *vsphereVolume) GetPodDeviceMapPath() (string, string) {
return v.plugin.host.GetPodVolumeDeviceDir(v.podUID, utilstrings.EscapeQualifiedName(vsphereVolumePluginName)), v.volName
}
// SupportsMetrics returns true for vsphereBlockVolumeMapper as it initializes the
// MetricsProvider.
func (vbvm *vsphereBlockVolumeMapper) SupportsMetrics() bool {
return true
}