Operation generator migration metric fixes and test metrics retrieval code

This commit is contained in:
David Zhu 2019-04-25 16:20:03 -07:00
parent dc071b8e81
commit 213cc99ead
3 changed files with 224 additions and 5 deletions

View File

@ -295,16 +295,18 @@ func (og *operationGenerator) GenerateBulkVolumeVerifyFunc(
func (og *operationGenerator) GenerateAttachVolumeFunc( func (og *operationGenerator) GenerateAttachVolumeFunc(
volumeToAttach VolumeToAttach, volumeToAttach VolumeToAttach,
actualStateOfWorld ActualStateOfWorldAttacherUpdater) volumetypes.GeneratedOperations { actualStateOfWorld ActualStateOfWorldAttacherUpdater) volumetypes.GeneratedOperations {
originalSpec := volumeToAttach.VolumeSpec
attachVolumeFunc := func() (error, error) { attachVolumeFunc := func() (error, error) {
var attachableVolumePlugin volume.AttachableVolumePlugin var attachableVolumePlugin volume.AttachableVolumePlugin
originalSpec := volumeToAttach.VolumeSpec
nu, err := nodeUsingCSIPlugin(og, volumeToAttach.VolumeSpec, volumeToAttach.NodeName) nu, err := nodeUsingCSIPlugin(og, volumeToAttach.VolumeSpec, volumeToAttach.NodeName)
if err != nil { if err != nil {
return volumeToAttach.GenerateError("AttachVolume.NodeUsingCSIPlugin failed", err) return volumeToAttach.GenerateError("AttachVolume.NodeUsingCSIPlugin failed", err)
} }
// useCSIPlugin will check both CSIMigration and the plugin specific feature gate // useCSIPlugin will check both CSIMigration and the plugin specific feature gates
if useCSIPlugin(og.volumePluginMgr, volumeToAttach.VolumeSpec) && nu { ucp := useCSIPlugin(og.volumePluginMgr, volumeToAttach.VolumeSpec)
if ucp && nu {
// The volume represented by this spec is CSI and thus should be migrated // The volume represented by this spec is CSI and thus should be migrated
attachableVolumePlugin, err = og.volumePluginMgr.FindAttachablePluginByName(csi.CSIPluginName) attachableVolumePlugin, err = og.volumePluginMgr.FindAttachablePluginByName(csi.CSIPluginName)
if err != nil || attachableVolumePlugin == nil { if err != nil || attachableVolumePlugin == nil {
@ -382,8 +384,29 @@ func (og *operationGenerator) GenerateAttachVolumeFunc(
} }
} }
// Get attacher plugin
attachableVolumePluginName := unknownAttachableVolumePlugin attachableVolumePluginName := unknownAttachableVolumePlugin
// TODO(dyzz) Ignoring this error means that if the plugin is migrated and
// any transient error is encountered (API unavailable, driver not installed)
// the operation will have it's metric registered with the in-tree plugin instead
// of the CSI Driver we migrated to. Fixing this requires a larger refactor that
// involves determining the plugin_name for the metric generating "CompleteFunc"
// during the actual "OperationFunc" and not during this generation function
nu, _ := nodeUsingCSIPlugin(og, volumeToAttach.VolumeSpec, volumeToAttach.NodeName)
ucp := useCSIPlugin(og.volumePluginMgr, volumeToAttach.VolumeSpec)
// Need to translate the spec here if the plugin is migrated so that the metrics
// emitted show the correct (migrated) plugin
if ucp && nu {
csiSpec, err := translateSpec(volumeToAttach.VolumeSpec)
if err == nil {
volumeToAttach.VolumeSpec = csiSpec
}
// If we have an error here we ignore it, the metric emitted will then be for the
// in-tree plugin. This error case(skipped one) will also trigger an error
// while the generated function is executed. And those errors will be handled during the execution of the generated
// function with a back off policy.
}
// Get attacher plugin
attachableVolumePlugin, err := attachableVolumePlugin, err :=
og.volumePluginMgr.FindAttachablePluginBySpec(volumeToAttach.VolumeSpec) og.volumePluginMgr.FindAttachablePluginBySpec(volumeToAttach.VolumeSpec)
// It's ok to ignore the error, returning error is not expected from this function. // It's ok to ignore the error, returning error is not expected from this function.
@ -528,7 +551,20 @@ func (og *operationGenerator) GenerateMountVolumeFunc(
actualStateOfWorld ActualStateOfWorldMounterUpdater, actualStateOfWorld ActualStateOfWorldMounterUpdater,
isRemount bool) volumetypes.GeneratedOperations { isRemount bool) volumetypes.GeneratedOperations {
// Get mounter plugin // Get mounter plugin
originalSpec := volumeToMount.VolumeSpec
volumePluginName := unknownVolumePlugin volumePluginName := unknownVolumePlugin
// Need to translate the spec here if the plugin is migrated so that the metrics
// emitted show the correct (migrated) plugin
if useCSIPlugin(og.volumePluginMgr, volumeToMount.VolumeSpec) {
csiSpec, err := translateSpec(volumeToMount.VolumeSpec)
if err == nil {
volumeToMount.VolumeSpec = csiSpec
}
// If we have an error here we ignore it, the metric emitted will then be for the
// in-tree plugin. This error case(skipped one) will also trigger an error
// while the generated function is executed. And those errors will be handled during the execution of the generated
// function with a back off policy.
}
volumePlugin, err := volumePlugin, err :=
og.volumePluginMgr.FindPluginBySpec(volumeToMount.VolumeSpec) og.volumePluginMgr.FindPluginBySpec(volumeToMount.VolumeSpec)
if err == nil && volumePlugin != nil { if err == nil && volumePlugin != nil {
@ -536,7 +572,7 @@ func (og *operationGenerator) GenerateMountVolumeFunc(
} }
mountVolumeFunc := func() (error, error) { mountVolumeFunc := func() (error, error) {
originalSpec := volumeToMount.VolumeSpec
// Get mounter plugin // Get mounter plugin
if useCSIPlugin(og.volumePluginMgr, volumeToMount.VolumeSpec) { if useCSIPlugin(og.volumePluginMgr, volumeToMount.VolumeSpec) {
csiSpec, err := translateSpec(volumeToMount.VolumeSpec) csiSpec, err := translateSpec(volumeToMount.VolumeSpec)

View File

@ -88,6 +88,7 @@ func InitNFSDriver() testsuites.TestDriver {
return &nfsDriver{ return &nfsDriver{
driverInfo: testsuites.DriverInfo{ driverInfo: testsuites.DriverInfo{
Name: "nfs", Name: "nfs",
PluginName: "kubernetes.io/nfs",
MaxFileSize: testpatterns.FileSizeLarge, MaxFileSize: testpatterns.FileSizeLarge,
SupportedFsType: sets.NewString( SupportedFsType: sets.NewString(
"", // Default fsType "", // Default fsType
@ -229,6 +230,7 @@ func InitGlusterFSDriver() testsuites.TestDriver {
return &glusterFSDriver{ return &glusterFSDriver{
driverInfo: testsuites.DriverInfo{ driverInfo: testsuites.DriverInfo{
Name: "gluster", Name: "gluster",
PluginName: "kubernetes.io/glusterfs",
MaxFileSize: testpatterns.FileSizeMedium, MaxFileSize: testpatterns.FileSizeMedium,
SupportedFsType: sets.NewString( SupportedFsType: sets.NewString(
"", // Default fsType "", // Default fsType
@ -346,6 +348,7 @@ func InitISCSIDriver() testsuites.TestDriver {
return &iSCSIDriver{ return &iSCSIDriver{
driverInfo: testsuites.DriverInfo{ driverInfo: testsuites.DriverInfo{
Name: "iscsi", Name: "iscsi",
PluginName: "kubernetes.io/iscsi",
FeatureTag: "[Feature:Volumes]", FeatureTag: "[Feature:Volumes]",
MaxFileSize: testpatterns.FileSizeMedium, MaxFileSize: testpatterns.FileSizeMedium,
SupportedFsType: sets.NewString( SupportedFsType: sets.NewString(
@ -458,6 +461,7 @@ func InitRbdDriver() testsuites.TestDriver {
return &rbdDriver{ return &rbdDriver{
driverInfo: testsuites.DriverInfo{ driverInfo: testsuites.DriverInfo{
Name: "rbd", Name: "rbd",
PluginName: "kubernetes.io/rbd",
FeatureTag: "[Feature:Volumes]", FeatureTag: "[Feature:Volumes]",
MaxFileSize: testpatterns.FileSizeMedium, MaxFileSize: testpatterns.FileSizeMedium,
SupportedFsType: sets.NewString( SupportedFsType: sets.NewString(
@ -585,6 +589,7 @@ func InitCephFSDriver() testsuites.TestDriver {
return &cephFSDriver{ return &cephFSDriver{
driverInfo: testsuites.DriverInfo{ driverInfo: testsuites.DriverInfo{
Name: "ceph", Name: "ceph",
PluginName: "kubernetes.io/cephfs",
FeatureTag: "[Feature:Volumes]", FeatureTag: "[Feature:Volumes]",
MaxFileSize: testpatterns.FileSizeMedium, MaxFileSize: testpatterns.FileSizeMedium,
SupportedFsType: sets.NewString( SupportedFsType: sets.NewString(
@ -684,6 +689,7 @@ func InitHostPathDriver() testsuites.TestDriver {
return &hostPathDriver{ return &hostPathDriver{
driverInfo: testsuites.DriverInfo{ driverInfo: testsuites.DriverInfo{
Name: "hostPath", Name: "hostPath",
PluginName: "kubernetes.io/host-path",
MaxFileSize: testpatterns.FileSizeMedium, MaxFileSize: testpatterns.FileSizeMedium,
SupportedFsType: sets.NewString( SupportedFsType: sets.NewString(
"", // Default fsType "", // Default fsType
@ -756,6 +762,7 @@ func InitHostPathSymlinkDriver() testsuites.TestDriver {
return &hostPathSymlinkDriver{ return &hostPathSymlinkDriver{
driverInfo: testsuites.DriverInfo{ driverInfo: testsuites.DriverInfo{
Name: "hostPathSymlink", Name: "hostPathSymlink",
PluginName: "kubernetes.io/host-path",
MaxFileSize: testpatterns.FileSizeMedium, MaxFileSize: testpatterns.FileSizeMedium,
SupportedFsType: sets.NewString( SupportedFsType: sets.NewString(
"", // Default fsType "", // Default fsType
@ -896,6 +903,7 @@ func InitEmptydirDriver() testsuites.TestDriver {
return &emptydirDriver{ return &emptydirDriver{
driverInfo: testsuites.DriverInfo{ driverInfo: testsuites.DriverInfo{
Name: "emptydir", Name: "emptydir",
PluginName: "kubernetes.io/empty-dir",
MaxFileSize: testpatterns.FileSizeMedium, MaxFileSize: testpatterns.FileSizeMedium,
SupportedFsType: sets.NewString( SupportedFsType: sets.NewString(
"", // Default fsType "", // Default fsType
@ -961,6 +969,7 @@ func InitCinderDriver() testsuites.TestDriver {
return &cinderDriver{ return &cinderDriver{
driverInfo: testsuites.DriverInfo{ driverInfo: testsuites.DriverInfo{
Name: "cinder", Name: "cinder",
PluginName: "kubernetes.io/cinder",
MaxFileSize: testpatterns.FileSizeMedium, MaxFileSize: testpatterns.FileSizeMedium,
SupportedFsType: sets.NewString( SupportedFsType: sets.NewString(
"", // Default fsType "", // Default fsType
@ -1129,6 +1138,7 @@ func InitGcePdDriver() testsuites.TestDriver {
return &gcePdDriver{ return &gcePdDriver{
driverInfo: testsuites.DriverInfo{ driverInfo: testsuites.DriverInfo{
Name: "gcepd", Name: "gcepd",
PluginName: "kubernetes.io/gce-pd",
MaxFileSize: testpatterns.FileSizeMedium, MaxFileSize: testpatterns.FileSizeMedium,
SupportedFsType: supportedTypes, SupportedFsType: supportedTypes,
SupportedMountOption: sets.NewString("debug", "nouid32"), SupportedMountOption: sets.NewString("debug", "nouid32"),
@ -1256,6 +1266,7 @@ func InitVSphereDriver() testsuites.TestDriver {
return &vSphereDriver{ return &vSphereDriver{
driverInfo: testsuites.DriverInfo{ driverInfo: testsuites.DriverInfo{
Name: "vSphere", Name: "vSphere",
PluginName: "kubernetes.io/vsphere-volume",
MaxFileSize: testpatterns.FileSizeMedium, MaxFileSize: testpatterns.FileSizeMedium,
SupportedFsType: sets.NewString( SupportedFsType: sets.NewString(
"", // Default fsType "", // Default fsType
@ -1377,6 +1388,7 @@ func InitAzureDriver() testsuites.TestDriver {
return &azureDriver{ return &azureDriver{
driverInfo: testsuites.DriverInfo{ driverInfo: testsuites.DriverInfo{
Name: "azure", Name: "azure",
PluginName: "kubernetes.io/azure-file",
MaxFileSize: testpatterns.FileSizeMedium, MaxFileSize: testpatterns.FileSizeMedium,
SupportedFsType: sets.NewString( SupportedFsType: sets.NewString(
"", // Default fsType "", // Default fsType
@ -1495,6 +1507,7 @@ func InitAwsDriver() testsuites.TestDriver {
return &awsDriver{ return &awsDriver{
driverInfo: testsuites.DriverInfo{ driverInfo: testsuites.DriverInfo{
Name: "aws", Name: "aws",
PluginName: "kubernetes.io/aws-ebs",
MaxFileSize: testpatterns.FileSizeMedium, MaxFileSize: testpatterns.FileSizeMedium,
SupportedFsType: sets.NewString( SupportedFsType: sets.NewString(
"", // Default fsType "", // Default fsType
@ -1661,6 +1674,7 @@ func InitLocalDriverWithVolumeType(volumeType utils.LocalVolumeType) func() test
return &localDriver{ return &localDriver{
driverInfo: testsuites.DriverInfo{ driverInfo: testsuites.DriverInfo{
Name: "local", Name: "local",
PluginName: "kubernetes.io/local-volume",
FeatureTag: featureTag, FeatureTag: featureTag,
MaxFileSize: maxFileSize, MaxFileSize: maxFileSize,
SupportedFsType: supportedFsTypes, SupportedFsType: supportedFsTypes,

View File

@ -18,8 +18,10 @@ package testsuites
import ( import (
"context" "context"
"flag"
"fmt" "fmt"
"regexp" "regexp"
"strings"
"time" "time"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
@ -31,13 +33,26 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
utilerrors "k8s.io/apimachinery/pkg/util/errors" utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/sets"
clientset "k8s.io/client-go/kubernetes" clientset "k8s.io/client-go/kubernetes"
csilib "k8s.io/csi-translation-lib"
"k8s.io/kubernetes/test/e2e/framework" "k8s.io/kubernetes/test/e2e/framework"
"k8s.io/kubernetes/test/e2e/framework/metrics"
"k8s.io/kubernetes/test/e2e/framework/podlogs" "k8s.io/kubernetes/test/e2e/framework/podlogs"
"k8s.io/kubernetes/test/e2e/framework/volume" "k8s.io/kubernetes/test/e2e/framework/volume"
"k8s.io/kubernetes/test/e2e/storage/testpatterns" "k8s.io/kubernetes/test/e2e/storage/testpatterns"
) )
var (
migratedPlugins *string
)
func init() {
migratedPlugins = flag.String("storage.migratedPlugins", "", "comma seperated list of in-tree plugin names of form 'kubernetes.io/{pluginName}' migrated to CSI")
}
type opCounts map[string]int64
// TestSuite represents an interface for a set of tests which works with TestDriver // TestSuite represents an interface for a set of tests which works with TestDriver
type TestSuite interface { type TestSuite interface {
// getTestSuiteInfo returns the TestSuiteInfo for this TestSuite // getTestSuiteInfo returns the TestSuiteInfo for this TestSuite
@ -465,3 +480,157 @@ func StartPodLogs(f *framework.Framework) func() {
return cancel return cancel
} }
func getVolumeOpsFromMetricsForPlugin(ms metrics.ControllerManagerMetrics, pluginName string) opCounts {
totOps := opCounts{}
for method, samples := range ms {
switch method {
case "storage_operation_status_count":
for _, sample := range samples {
plugin := string(sample.Metric["volume_plugin"])
if pluginName != plugin {
continue
}
opName := string(sample.Metric["operation_name"])
if opName == "verify_controller_attached_volume" {
// We ignore verify_controller_attached_volume because it does not call into
// the plugin. It only watches Node API and updates Actual State of World cache
continue
}
totOps[opName] = totOps[opName] + int64(sample.Value)
}
}
}
return totOps
}
func getVolumeOpsFromKubeletMetricsForPlugin(ms metrics.KubeletMetrics, pluginName string) opCounts {
totOps := opCounts{}
for method, samples := range ms {
switch method {
case "storage_operation_status_count":
for _, sample := range samples {
plugin := string(sample.Metric["volume_plugin"])
if pluginName != plugin {
continue
}
opName := string(sample.Metric["operation_name"])
if opName == "verify_controller_attached_volume" {
// We ignore verify_controller_attached_volume because it does not call into
// the plugin. It only watches Node API and updates Actual State of World cache
continue
}
totOps[opName] = totOps[opName] + int64(sample.Value)
}
}
}
return totOps
}
func getVolumeOpCounts(c clientset.Interface, pluginName string) opCounts {
metricsGrabber, err := metrics.NewMetricsGrabber(c, nil, true, false, true, false, false)
if err != nil {
framework.Failf("Error creating metrics grabber : %v", err)
}
if !metricsGrabber.HasRegisteredMaster() {
framework.Skipf("Environment does not support getting controller-manager metrics - skipping")
}
controllerMetrics, err := metricsGrabber.GrabFromControllerManager()
framework.ExpectNoError(err, "Error getting c-m metrics : %v", err)
totOps := getVolumeOpsFromMetricsForPlugin(controllerMetrics, pluginName)
nodes, err := c.CoreV1().Nodes().List(metav1.ListOptions{})
framework.ExpectNoError(err, "Error listing nodes: %v", err)
for _, node := range nodes.Items {
nodeMetrics, err := metricsGrabber.GrabFromKubelet(node.GetName())
framework.ExpectNoError(err, "Error getting Kubelet %v metrics: %v", node.GetName(), err)
totOps = addOpCounts(totOps, getVolumeOpsFromKubeletMetricsForPlugin(nodeMetrics, pluginName))
}
return totOps
}
func addOpCounts(o1 opCounts, o2 opCounts) opCounts {
totOps := opCounts{}
seen := sets.NewString()
for op, count := range o1 {
seen.Insert(op)
totOps[op] = totOps[op] + count
totOps[op] = totOps[op] + o2[op]
}
for op, count := range o2 {
if !seen.Has(op) {
totOps[op] = totOps[op] + count
}
}
return totOps
}
func getMigrationVolumeOpCounts(cs clientset.Interface, pluginName string) (opCounts, opCounts) {
if len(pluginName) > 0 {
var migratedOps opCounts
csiName, err := csilib.GetCSINameFromInTreeName(pluginName)
if err != nil {
framework.Logf("Could not find CSI Name for in-tree plugin %v", pluginName)
migratedOps = opCounts{}
} else {
csiName = "kubernetes.io/csi:" + csiName
migratedOps = getVolumeOpCounts(cs, csiName)
}
return getVolumeOpCounts(cs, pluginName), migratedOps
} else {
// Not an in-tree driver
framework.Logf("Test running for native CSI Driver, not checking metrics")
return opCounts{}, opCounts{}
}
}
func getTotOps(ops opCounts) int64 {
var tot int64 = 0
for _, count := range ops {
tot += count
}
return tot
}
func validateMigrationVolumeOpCounts(cs clientset.Interface, pluginName string, oldInTreeOps, oldMigratedOps opCounts) {
if len(pluginName) == 0 {
// This is a native CSI Driver and we don't check ops
return
}
newInTreeOps, newMigratedOps := getMigrationVolumeOpCounts(cs, pluginName)
if sets.NewString(strings.Split(*migratedPlugins, ",")...).Has(pluginName) {
// If this plugin is migrated based on the test flag storage.migratedPlugins
for op, count := range newInTreeOps {
if count != oldInTreeOps[op] {
framework.Failf("In-tree plugin %v migrated to CSI Driver, however found %v %v metrics for in-tree plugin", pluginName, count-oldInTreeOps[op], op)
}
}
totMigrated := getTotOps(newMigratedOps)
oldTotMigrated := getTotOps(oldMigratedOps)
if totMigrated-oldTotMigrated <= 0 {
framework.Failf("In-tree plugin %v migrated to CSI Driver, however found %v metrics for migrated plugin", pluginName, totMigrated-oldTotMigrated)
}
} else {
// In-tree plugin is not migrated
totInTree := getTotOps(newInTreeOps)
oldTotInTree := getTotOps(oldInTreeOps)
if totInTree == oldTotInTree {
framework.Failf("In-tree plugin %v NOT migrated to CSI Driver, however found did not find any operation metrics for in-tree plugin", pluginName)
}
// We don't check counts for the Migrated version of the driver because if tests are running in parallel a test could be using
// the CSI Driver natively and increase the metrics count
// TODO(dyzz): Add a dimension to OperationGenerator metrics for "migrated"->true/false so that we can disambiguate migrated metrics
// and native CSI Driver metrics. This way we can check the counts for migrated version of the driver for stronger negative test case
// guarantees (as well as more informative metrics).
}
}