base.go 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628
  1. /*
  2. Copyright 2018 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package testsuites
  14. import (
  15. "context"
  16. "flag"
  17. "fmt"
  18. "regexp"
  19. "strings"
  20. "time"
  21. "github.com/onsi/ginkgo"
  22. v1 "k8s.io/api/core/v1"
  23. storagev1 "k8s.io/api/storage/v1"
  24. apierrs "k8s.io/apimachinery/pkg/api/errors"
  25. "k8s.io/apimachinery/pkg/api/resource"
  26. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  27. "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
  28. utilerrors "k8s.io/apimachinery/pkg/util/errors"
  29. "k8s.io/apimachinery/pkg/util/sets"
  30. clientset "k8s.io/client-go/kubernetes"
  31. csilib "k8s.io/csi-translation-lib"
  32. "k8s.io/kubernetes/test/e2e/framework"
  33. e2elog "k8s.io/kubernetes/test/e2e/framework/log"
  34. "k8s.io/kubernetes/test/e2e/framework/metrics"
  35. "k8s.io/kubernetes/test/e2e/framework/podlogs"
  36. "k8s.io/kubernetes/test/e2e/framework/volume"
  37. "k8s.io/kubernetes/test/e2e/storage/testpatterns"
  38. )
  39. var (
  40. migratedPlugins *string
  41. )
  42. func init() {
  43. migratedPlugins = flag.String("storage.migratedPlugins", "", "comma separated list of in-tree plugin names of form 'kubernetes.io/{pluginName}' migrated to CSI")
  44. }
  45. type opCounts map[string]int64
  46. // TestSuite represents an interface for a set of tests which works with TestDriver
  47. type TestSuite interface {
  48. // getTestSuiteInfo returns the TestSuiteInfo for this TestSuite
  49. getTestSuiteInfo() TestSuiteInfo
  50. // defineTest defines tests of the testpattern for the driver.
  51. // Called inside a Ginkgo context that reflects the current driver and test pattern,
  52. // so the test suite can define tests directly with ginkgo.It.
  53. defineTests(TestDriver, testpatterns.TestPattern)
  54. }
  55. // TestSuiteInfo represents a set of parameters for TestSuite
  56. type TestSuiteInfo struct {
  57. name string // name of the TestSuite
  58. featureTag string // featureTag for the TestSuite
  59. testPatterns []testpatterns.TestPattern // Slice of TestPattern for the TestSuite
  60. }
  61. // TestResource represents an interface for resources that is used by TestSuite
  62. type TestResource interface {
  63. // cleanupResource cleans up the test resources created when setting up the resource
  64. cleanupResource()
  65. }
  66. func getTestNameStr(suite TestSuite, pattern testpatterns.TestPattern) string {
  67. tsInfo := suite.getTestSuiteInfo()
  68. return fmt.Sprintf("[Testpattern: %s]%s %s%s", pattern.Name, pattern.FeatureTag, tsInfo.name, tsInfo.featureTag)
  69. }
  70. // DefineTestSuite defines tests for all testpatterns and all testSuites for a driver
  71. func DefineTestSuite(driver TestDriver, tsInits []func() TestSuite) {
  72. for _, testSuiteInit := range tsInits {
  73. suite := testSuiteInit()
  74. for _, pattern := range suite.getTestSuiteInfo().testPatterns {
  75. p := pattern
  76. ginkgo.Context(getTestNameStr(suite, p), func() {
  77. ginkgo.BeforeEach(func() {
  78. // Skip unsupported tests to avoid unnecessary resource initialization
  79. skipUnsupportedTest(driver, p)
  80. })
  81. suite.defineTests(driver, p)
  82. })
  83. }
  84. }
  85. }
  86. // skipUnsupportedTest will skip tests if the combination of driver, and testpattern
  87. // is not suitable to be tested.
  88. // Whether it needs to be skipped is checked by following steps:
  89. // 1. Check if Whether SnapshotType is supported by driver from its interface
  90. // 2. Check if Whether volType is supported by driver from its interface
  91. // 3. Check if fsType is supported
  92. // 4. Check with driver specific logic
  93. //
  94. // Test suites can also skip tests inside their own defineTests function or in
  95. // individual tests.
  96. func skipUnsupportedTest(driver TestDriver, pattern testpatterns.TestPattern) {
  97. dInfo := driver.GetDriverInfo()
  98. var isSupported bool
  99. // 1. Check if Whether SnapshotType is supported by driver from its interface
  100. // if isSupported, we still execute the driver and suite tests
  101. if len(pattern.SnapshotType) > 0 {
  102. switch pattern.SnapshotType {
  103. case testpatterns.DynamicCreatedSnapshot:
  104. _, isSupported = driver.(SnapshottableTestDriver)
  105. default:
  106. isSupported = false
  107. }
  108. if !isSupported {
  109. framework.Skipf("Driver %s doesn't support snapshot type %v -- skipping", dInfo.Name, pattern.SnapshotType)
  110. }
  111. } else {
  112. // 2. Check if Whether volType is supported by driver from its interface
  113. switch pattern.VolType {
  114. case testpatterns.InlineVolume:
  115. _, isSupported = driver.(InlineVolumeTestDriver)
  116. case testpatterns.PreprovisionedPV:
  117. _, isSupported = driver.(PreprovisionedPVTestDriver)
  118. case testpatterns.DynamicPV:
  119. _, isSupported = driver.(DynamicPVTestDriver)
  120. default:
  121. isSupported = false
  122. }
  123. if !isSupported {
  124. framework.Skipf("Driver %s doesn't support %v -- skipping", dInfo.Name, pattern.VolType)
  125. }
  126. // 3. Check if fsType is supported
  127. if !dInfo.SupportedFsType.Has(pattern.FsType) {
  128. framework.Skipf("Driver %s doesn't support %v -- skipping", dInfo.Name, pattern.FsType)
  129. }
  130. if pattern.FsType == "xfs" && framework.NodeOSDistroIs("gci", "cos", "windows") {
  131. framework.Skipf("Distro doesn't support xfs -- skipping")
  132. }
  133. if pattern.FsType == "ntfs" && !framework.NodeOSDistroIs("windows") {
  134. framework.Skipf("Distro %s doesn't support ntfs -- skipping", framework.TestContext.NodeOSDistro)
  135. }
  136. }
  137. // 4. Check with driver specific logic
  138. driver.SkipUnsupportedTest(pattern)
  139. }
  140. // genericVolumeTestResource is a generic implementation of TestResource that wil be able to
  141. // be used in most of TestSuites.
  142. // See volume_io.go or volumes.go in test/e2e/storage/testsuites/ for how to use this resource.
  143. // Also, see subpath.go in the same directory for how to extend and use it.
  144. type genericVolumeTestResource struct {
  145. driver TestDriver
  146. config *PerTestConfig
  147. pattern testpatterns.TestPattern
  148. volType string
  149. volSource *v1.VolumeSource
  150. pvc *v1.PersistentVolumeClaim
  151. pv *v1.PersistentVolume
  152. sc *storagev1.StorageClass
  153. volume TestVolume
  154. }
  155. var _ TestResource = &genericVolumeTestResource{}
  156. func createGenericVolumeTestResource(driver TestDriver, config *PerTestConfig, pattern testpatterns.TestPattern) *genericVolumeTestResource {
  157. r := genericVolumeTestResource{
  158. driver: driver,
  159. config: config,
  160. pattern: pattern,
  161. }
  162. dInfo := driver.GetDriverInfo()
  163. f := config.Framework
  164. cs := f.ClientSet
  165. fsType := pattern.FsType
  166. volType := pattern.VolType
  167. // Create volume for pre-provisioned volume tests
  168. r.volume = CreateVolume(driver, config, volType)
  169. switch volType {
  170. case testpatterns.InlineVolume:
  171. e2elog.Logf("Creating resource for inline volume")
  172. if iDriver, ok := driver.(InlineVolumeTestDriver); ok {
  173. r.volSource = iDriver.GetVolumeSource(false, fsType, r.volume)
  174. r.volType = dInfo.Name
  175. }
  176. case testpatterns.PreprovisionedPV:
  177. e2elog.Logf("Creating resource for pre-provisioned PV")
  178. if pDriver, ok := driver.(PreprovisionedPVTestDriver); ok {
  179. pvSource, volumeNodeAffinity := pDriver.GetPersistentVolumeSource(false, fsType, r.volume)
  180. if pvSource != nil {
  181. r.volSource, r.pv, r.pvc = createVolumeSourceWithPVCPV(f, dInfo.Name, pvSource, volumeNodeAffinity, false, pattern.VolMode)
  182. }
  183. r.volType = fmt.Sprintf("%s-preprovisionedPV", dInfo.Name)
  184. }
  185. case testpatterns.DynamicPV:
  186. e2elog.Logf("Creating resource for dynamic PV")
  187. if dDriver, ok := driver.(DynamicPVTestDriver); ok {
  188. claimSize := dDriver.GetClaimSize()
  189. r.sc = dDriver.GetDynamicProvisionStorageClass(r.config, fsType)
  190. ginkgo.By("creating a StorageClass " + r.sc.Name)
  191. var err error
  192. r.sc, err = cs.StorageV1().StorageClasses().Create(r.sc)
  193. framework.ExpectNoError(err)
  194. if r.sc != nil {
  195. r.volSource, r.pv, r.pvc = createVolumeSourceWithPVCPVFromDynamicProvisionSC(
  196. f, dInfo.Name, claimSize, r.sc, false, pattern.VolMode)
  197. }
  198. r.volType = fmt.Sprintf("%s-dynamicPV", dInfo.Name)
  199. }
  200. default:
  201. framework.Failf("genericVolumeTestResource doesn't support: %s", volType)
  202. }
  203. if r.volSource == nil {
  204. framework.Skipf("Driver %s doesn't support %v -- skipping", dInfo.Name, volType)
  205. }
  206. return &r
  207. }
  208. // cleanupResource cleans up genericVolumeTestResource
  209. func (r *genericVolumeTestResource) cleanupResource() {
  210. f := r.config.Framework
  211. volType := r.pattern.VolType
  212. if r.pvc != nil || r.pv != nil {
  213. switch volType {
  214. case testpatterns.PreprovisionedPV:
  215. ginkgo.By("Deleting pv and pvc")
  216. if errs := framework.PVPVCCleanup(f.ClientSet, f.Namespace.Name, r.pv, r.pvc); len(errs) != 0 {
  217. framework.Failf("Failed to delete PVC or PV: %v", utilerrors.NewAggregate(errs))
  218. }
  219. case testpatterns.DynamicPV:
  220. ginkgo.By("Deleting pvc")
  221. // We only delete the PVC so that PV (and disk) can be cleaned up by dynamic provisioner
  222. if r.pv != nil && r.pv.Spec.PersistentVolumeReclaimPolicy != v1.PersistentVolumeReclaimDelete {
  223. framework.Failf("Test framework does not currently support Dynamically Provisioned Persistent Volume %v specified with reclaim policy that isnt %v",
  224. r.pv.Name, v1.PersistentVolumeReclaimDelete)
  225. }
  226. if r.pvc != nil {
  227. err := framework.DeletePersistentVolumeClaim(f.ClientSet, r.pvc.Name, f.Namespace.Name)
  228. framework.ExpectNoError(err, "Failed to delete PVC %v", r.pvc.Name)
  229. if r.pv != nil {
  230. err = framework.WaitForPersistentVolumeDeleted(f.ClientSet, r.pv.Name, 5*time.Second, 5*time.Minute)
  231. framework.ExpectNoError(err, "Persistent Volume %v not deleted by dynamic provisioner", r.pv.Name)
  232. }
  233. }
  234. default:
  235. framework.Failf("Found PVC (%v) or PV (%v) but not running Preprovisioned or Dynamic test pattern", r.pvc, r.pv)
  236. }
  237. }
  238. if r.sc != nil {
  239. ginkgo.By("Deleting sc")
  240. deleteStorageClass(f.ClientSet, r.sc.Name)
  241. }
  242. // Cleanup volume for pre-provisioned volume tests
  243. if r.volume != nil {
  244. r.volume.DeleteVolume()
  245. }
  246. }
  247. func createVolumeSourceWithPVCPV(
  248. f *framework.Framework,
  249. name string,
  250. pvSource *v1.PersistentVolumeSource,
  251. volumeNodeAffinity *v1.VolumeNodeAffinity,
  252. readOnly bool,
  253. volMode v1.PersistentVolumeMode,
  254. ) (*v1.VolumeSource, *v1.PersistentVolume, *v1.PersistentVolumeClaim) {
  255. pvConfig := framework.PersistentVolumeConfig{
  256. NamePrefix: fmt.Sprintf("%s-", name),
  257. StorageClassName: f.Namespace.Name,
  258. PVSource: *pvSource,
  259. NodeAffinity: volumeNodeAffinity,
  260. }
  261. pvcConfig := framework.PersistentVolumeClaimConfig{
  262. StorageClassName: &f.Namespace.Name,
  263. }
  264. if volMode != "" {
  265. pvConfig.VolumeMode = &volMode
  266. pvcConfig.VolumeMode = &volMode
  267. }
  268. e2elog.Logf("Creating PVC and PV")
  269. pv, pvc, err := framework.CreatePVCPV(f.ClientSet, pvConfig, pvcConfig, f.Namespace.Name, false)
  270. framework.ExpectNoError(err, "PVC, PV creation failed")
  271. err = framework.WaitOnPVandPVC(f.ClientSet, f.Namespace.Name, pv, pvc)
  272. framework.ExpectNoError(err, "PVC, PV failed to bind")
  273. volSource := &v1.VolumeSource{
  274. PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
  275. ClaimName: pvc.Name,
  276. ReadOnly: readOnly,
  277. },
  278. }
  279. return volSource, pv, pvc
  280. }
  281. func createVolumeSourceWithPVCPVFromDynamicProvisionSC(
  282. f *framework.Framework,
  283. name string,
  284. claimSize string,
  285. sc *storagev1.StorageClass,
  286. readOnly bool,
  287. volMode v1.PersistentVolumeMode,
  288. ) (*v1.VolumeSource, *v1.PersistentVolume, *v1.PersistentVolumeClaim) {
  289. cs := f.ClientSet
  290. ns := f.Namespace.Name
  291. ginkgo.By("creating a claim")
  292. pvc := getClaim(claimSize, ns)
  293. pvc.Spec.StorageClassName = &sc.Name
  294. if volMode != "" {
  295. pvc.Spec.VolumeMode = &volMode
  296. }
  297. var err error
  298. pvc, err = cs.CoreV1().PersistentVolumeClaims(ns).Create(pvc)
  299. framework.ExpectNoError(err)
  300. if !isDelayedBinding(sc) {
  301. err = framework.WaitForPersistentVolumeClaimPhase(v1.ClaimBound, cs, pvc.Namespace, pvc.Name, framework.Poll, framework.ClaimProvisionTimeout)
  302. framework.ExpectNoError(err)
  303. }
  304. pvc, err = cs.CoreV1().PersistentVolumeClaims(pvc.Namespace).Get(pvc.Name, metav1.GetOptions{})
  305. framework.ExpectNoError(err)
  306. var pv *v1.PersistentVolume
  307. if !isDelayedBinding(sc) {
  308. pv, err = cs.CoreV1().PersistentVolumes().Get(pvc.Spec.VolumeName, metav1.GetOptions{})
  309. framework.ExpectNoError(err)
  310. }
  311. volSource := &v1.VolumeSource{
  312. PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
  313. ClaimName: pvc.Name,
  314. ReadOnly: readOnly,
  315. },
  316. }
  317. return volSource, pv, pvc
  318. }
  319. func isDelayedBinding(sc *storagev1.StorageClass) bool {
  320. if sc.VolumeBindingMode != nil {
  321. return *sc.VolumeBindingMode == storagev1.VolumeBindingWaitForFirstConsumer
  322. }
  323. return false
  324. }
  325. func getClaim(claimSize string, ns string) *v1.PersistentVolumeClaim {
  326. claim := v1.PersistentVolumeClaim{
  327. ObjectMeta: metav1.ObjectMeta{
  328. GenerateName: "pvc-",
  329. Namespace: ns,
  330. },
  331. Spec: v1.PersistentVolumeClaimSpec{
  332. AccessModes: []v1.PersistentVolumeAccessMode{
  333. v1.ReadWriteOnce,
  334. },
  335. Resources: v1.ResourceRequirements{
  336. Requests: v1.ResourceList{
  337. v1.ResourceName(v1.ResourceStorage): resource.MustParse(claimSize),
  338. },
  339. },
  340. },
  341. }
  342. return &claim
  343. }
  344. // deleteStorageClass deletes the passed in StorageClass and catches errors other than "Not Found"
  345. func deleteStorageClass(cs clientset.Interface, className string) {
  346. err := cs.StorageV1().StorageClasses().Delete(className, nil)
  347. if err != nil && !apierrs.IsNotFound(err) {
  348. framework.ExpectNoError(err)
  349. }
  350. }
  351. // convertTestConfig returns a framework test config with the
  352. // parameters specified for the testsuite or (if available) the
  353. // dynamically created config for the volume server.
  354. //
  355. // This is done because TestConfig is the public API for
  356. // the testsuites package whereas volume.TestConfig is merely
  357. // an implementation detail. It contains fields that have no effect,
  358. // which makes it unsuitable for use in the testsuits public API.
  359. func convertTestConfig(in *PerTestConfig) volume.TestConfig {
  360. if in.ServerConfig != nil {
  361. return *in.ServerConfig
  362. }
  363. return volume.TestConfig{
  364. Namespace: in.Framework.Namespace.Name,
  365. Prefix: in.Prefix,
  366. ClientNodeName: in.ClientNodeName,
  367. NodeSelector: in.ClientNodeSelector,
  368. }
  369. }
  370. func getSnapshot(claimName string, ns, snapshotClassName string) *unstructured.Unstructured {
  371. snapshot := &unstructured.Unstructured{
  372. Object: map[string]interface{}{
  373. "kind": "VolumeSnapshot",
  374. "apiVersion": snapshotAPIVersion,
  375. "metadata": map[string]interface{}{
  376. "generateName": "snapshot-",
  377. "namespace": ns,
  378. },
  379. "spec": map[string]interface{}{
  380. "snapshotClassName": snapshotClassName,
  381. "source": map[string]interface{}{
  382. "name": claimName,
  383. "kind": "PersistentVolumeClaim",
  384. },
  385. },
  386. },
  387. }
  388. return snapshot
  389. }
  390. // StartPodLogs begins capturing log output and events from current
  391. // and future pods running in the namespace of the framework. That
  392. // ends when the returned cleanup function is called.
  393. //
  394. // The output goes to log files (when using --report-dir, as in the
  395. // CI) or the output stream (otherwise).
  396. func StartPodLogs(f *framework.Framework) func() {
  397. ctx, cancel := context.WithCancel(context.Background())
  398. cs := f.ClientSet
  399. ns := f.Namespace
  400. to := podlogs.LogOutput{
  401. StatusWriter: ginkgo.GinkgoWriter,
  402. }
  403. if framework.TestContext.ReportDir == "" {
  404. to.LogWriter = ginkgo.GinkgoWriter
  405. } else {
  406. test := ginkgo.CurrentGinkgoTestDescription()
  407. reg := regexp.MustCompile("[^a-zA-Z0-9_-]+")
  408. // We end the prefix with a slash to ensure that all logs
  409. // end up in a directory named after the current test.
  410. //
  411. // TODO: use a deeper directory hierarchy once gubernator
  412. // supports that (https://github.com/kubernetes/test-infra/issues/10289).
  413. to.LogPathPrefix = framework.TestContext.ReportDir + "/" +
  414. reg.ReplaceAllString(test.FullTestText, "_") + "/"
  415. }
  416. podlogs.CopyAllLogs(ctx, cs, ns.Name, to)
  417. // pod events are something that the framework already collects itself
  418. // after a failed test. Logging them live is only useful for interactive
  419. // debugging, not when we collect reports.
  420. if framework.TestContext.ReportDir == "" {
  421. podlogs.WatchPods(ctx, cs, ns.Name, ginkgo.GinkgoWriter)
  422. }
  423. return cancel
  424. }
  425. func getVolumeOpsFromMetricsForPlugin(ms metrics.Metrics, pluginName string) opCounts {
  426. totOps := opCounts{}
  427. for method, samples := range ms {
  428. switch method {
  429. case "storage_operation_status_count":
  430. for _, sample := range samples {
  431. plugin := string(sample.Metric["volume_plugin"])
  432. if pluginName != plugin {
  433. continue
  434. }
  435. opName := string(sample.Metric["operation_name"])
  436. if opName == "verify_controller_attached_volume" {
  437. // We ignore verify_controller_attached_volume because it does not call into
  438. // the plugin. It only watches Node API and updates Actual State of World cache
  439. continue
  440. }
  441. totOps[opName] = totOps[opName] + int64(sample.Value)
  442. }
  443. }
  444. }
  445. return totOps
  446. }
  447. func getVolumeOpCounts(c clientset.Interface, pluginName string) opCounts {
  448. if !framework.ProviderIs("gce", "gke", "aws") {
  449. return opCounts{}
  450. }
  451. nodeLimit := 25
  452. metricsGrabber, err := metrics.NewMetricsGrabber(c, nil, true, false, true, false, false)
  453. if err != nil {
  454. framework.ExpectNoError(err, "Error creating metrics grabber: %v", err)
  455. }
  456. if !metricsGrabber.HasRegisteredMaster() {
  457. e2elog.Logf("Warning: Environment does not support getting controller-manager metrics")
  458. return opCounts{}
  459. }
  460. controllerMetrics, err := metricsGrabber.GrabFromControllerManager()
  461. framework.ExpectNoError(err, "Error getting c-m metrics : %v", err)
  462. totOps := getVolumeOpsFromMetricsForPlugin(metrics.Metrics(controllerMetrics), pluginName)
  463. e2elog.Logf("Node name not specified for getVolumeOpCounts, falling back to listing nodes from API Server")
  464. nodes, err := c.CoreV1().Nodes().List(metav1.ListOptions{})
  465. framework.ExpectNoError(err, "Error listing nodes: %v", err)
  466. if len(nodes.Items) <= nodeLimit {
  467. // For large clusters with > nodeLimit nodes it is too time consuming to
  468. // gather metrics from all nodes. We just ignore the node metrics
  469. // for those clusters
  470. for _, node := range nodes.Items {
  471. nodeMetrics, err := metricsGrabber.GrabFromKubelet(node.GetName())
  472. framework.ExpectNoError(err, "Error getting Kubelet %v metrics: %v", node.GetName(), err)
  473. totOps = addOpCounts(totOps, getVolumeOpsFromMetricsForPlugin(metrics.Metrics(nodeMetrics), pluginName))
  474. }
  475. } else {
  476. e2elog.Logf("Skipping operation metrics gathering from nodes in getVolumeOpCounts, greater than %v nodes", nodeLimit)
  477. }
  478. return totOps
  479. }
  480. func addOpCounts(o1 opCounts, o2 opCounts) opCounts {
  481. totOps := opCounts{}
  482. seen := sets.NewString()
  483. for op, count := range o1 {
  484. seen.Insert(op)
  485. totOps[op] = totOps[op] + count + o2[op]
  486. }
  487. for op, count := range o2 {
  488. if !seen.Has(op) {
  489. totOps[op] = totOps[op] + count
  490. }
  491. }
  492. return totOps
  493. }
  494. func getMigrationVolumeOpCounts(cs clientset.Interface, pluginName string) (opCounts, opCounts) {
  495. if len(pluginName) > 0 {
  496. var migratedOps opCounts
  497. csiName, err := csilib.GetCSINameFromInTreeName(pluginName)
  498. if err != nil {
  499. e2elog.Logf("Could not find CSI Name for in-tree plugin %v", pluginName)
  500. migratedOps = opCounts{}
  501. } else {
  502. csiName = "kubernetes.io/csi:" + csiName
  503. migratedOps = getVolumeOpCounts(cs, csiName)
  504. }
  505. return getVolumeOpCounts(cs, pluginName), migratedOps
  506. } else {
  507. // Not an in-tree driver
  508. e2elog.Logf("Test running for native CSI Driver, not checking metrics")
  509. return opCounts{}, opCounts{}
  510. }
  511. }
  512. func getTotOps(ops opCounts) int64 {
  513. var tot int64 = 0
  514. for _, count := range ops {
  515. tot += count
  516. }
  517. return tot
  518. }
  519. func validateMigrationVolumeOpCounts(cs clientset.Interface, pluginName string, oldInTreeOps, oldMigratedOps opCounts) {
  520. if len(pluginName) == 0 {
  521. // This is a native CSI Driver and we don't check ops
  522. return
  523. }
  524. if sets.NewString(strings.Split(*migratedPlugins, ",")...).Has(pluginName) {
  525. // If this plugin is migrated based on the test flag storage.migratedPlugins
  526. newInTreeOps, _ := getMigrationVolumeOpCounts(cs, pluginName)
  527. for op, count := range newInTreeOps {
  528. if count != oldInTreeOps[op] {
  529. framework.Failf("In-tree plugin %v migrated to CSI Driver, however found %v %v metrics for in-tree plugin", pluginName, count-oldInTreeOps[op], op)
  530. }
  531. }
  532. // We don't check for migrated metrics because some negative test cases
  533. // may not do any volume operations and therefore not emit any metrics
  534. } else {
  535. // In-tree plugin is not migrated
  536. e2elog.Logf("In-tree plugin %v is not migrated, not validating any metrics", pluginName)
  537. // We don't check in-tree plugin metrics because some negative test
  538. // cases may not do any volume operations and therefore not emit any
  539. // metrics
  540. // We don't check counts for the Migrated version of the driver because
  541. // if tests are running in parallel a test could be using the CSI Driver
  542. // natively and increase the metrics count
  543. // TODO(dyzz): Add a dimension to OperationGenerator metrics for
  544. // "migrated"->true/false so that we can disambiguate migrated metrics
  545. // and native CSI Driver metrics. This way we can check the counts for
  546. // migrated version of the driver for stronger negative test case
  547. // guarantees (as well as more informative metrics).
  548. }
  549. }