base.go 25 KB

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