external.go 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. /*
  2. Copyright 2019 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 external
  14. import (
  15. "flag"
  16. "io/ioutil"
  17. "github.com/pkg/errors"
  18. storagev1 "k8s.io/api/storage/v1"
  19. "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
  20. "k8s.io/apimachinery/pkg/runtime"
  21. "k8s.io/apimachinery/pkg/runtime/schema"
  22. "k8s.io/apimachinery/pkg/util/sets"
  23. "k8s.io/apiserver/pkg/storage/names"
  24. "k8s.io/client-go/kubernetes/scheme"
  25. "k8s.io/kubernetes/test/e2e/framework"
  26. "k8s.io/kubernetes/test/e2e/storage/testpatterns"
  27. "k8s.io/kubernetes/test/e2e/storage/testsuites"
  28. "github.com/onsi/ginkgo"
  29. "github.com/onsi/gomega"
  30. )
  31. // List of testSuites to be executed for each external driver.
  32. var csiTestSuites = []func() testsuites.TestSuite{
  33. testsuites.InitMultiVolumeTestSuite,
  34. testsuites.InitProvisioningTestSuite,
  35. testsuites.InitSnapshottableTestSuite,
  36. testsuites.InitSubPathTestSuite,
  37. testsuites.InitVolumeIOTestSuite,
  38. testsuites.InitVolumeModeTestSuite,
  39. testsuites.InitVolumesTestSuite,
  40. }
  41. func init() {
  42. flag.Var(testDriverParameter{}, "storage.testdriver", "name of a .yaml or .json file that defines a driver for storage testing, can be used more than once")
  43. }
  44. // testDriverParameter is used to hook loading of the driver
  45. // definition file and test instantiation into argument parsing: for
  46. // each of potentially many parameters, Set is called and then does
  47. // both immediately. There is no other code location between argument
  48. // parsing and starting of the test suite where those test could be
  49. // defined.
  50. type testDriverParameter struct {
  51. }
  52. var _ flag.Value = testDriverParameter{}
  53. func (t testDriverParameter) String() string {
  54. return "<.yaml or .json file>"
  55. }
  56. func (t testDriverParameter) Set(filename string) error {
  57. driver, err := t.loadDriverDefinition(filename)
  58. if err != nil {
  59. return err
  60. }
  61. if driver.DriverInfo.Name == "" {
  62. return errors.Errorf("%q: DriverInfo.Name not set", filename)
  63. }
  64. description := "External Storage " + testsuites.GetDriverNameWithFeatureTags(driver)
  65. ginkgo.Describe(description, func() {
  66. testsuites.DefineTestSuite(driver, csiTestSuites)
  67. })
  68. return nil
  69. }
  70. func (t testDriverParameter) loadDriverDefinition(filename string) (*driverDefinition, error) {
  71. if filename == "" {
  72. return nil, errors.New("missing file name")
  73. }
  74. data, err := ioutil.ReadFile(filename)
  75. if err != nil {
  76. return nil, err
  77. }
  78. // Some reasonable defaults follow.
  79. driver := &driverDefinition{
  80. DriverInfo: testsuites.DriverInfo{
  81. SupportedFsType: sets.NewString(
  82. "", // Default fsType
  83. ),
  84. },
  85. ClaimSize: "5Gi",
  86. }
  87. // TODO: strict checking of the file content once https://github.com/kubernetes/kubernetes/pull/71589
  88. // or something similar is merged.
  89. if err := runtime.DecodeInto(scheme.Codecs.UniversalDecoder(), data, driver); err != nil {
  90. return nil, errors.Wrap(err, filename)
  91. }
  92. return driver, nil
  93. }
  94. var _ testsuites.TestDriver = &driverDefinition{}
  95. // We have to implement the interface because dynamic PV may or may
  96. // not be supported. driverDefinition.SkipUnsupportedTest checks that
  97. // based on the actual driver definition.
  98. var _ testsuites.DynamicPVTestDriver = &driverDefinition{}
  99. // Same for snapshotting.
  100. var _ testsuites.SnapshottableTestDriver = &driverDefinition{}
  101. // runtime.DecodeInto needs a runtime.Object but doesn't do any
  102. // deserialization of it and therefore none of the methods below need
  103. // an implementation.
  104. var _ runtime.Object = &driverDefinition{}
  105. // DriverDefinition needs to be filled in via a .yaml or .json
  106. // file. It's methods then implement the TestDriver interface, using
  107. // nothing but the information in this struct.
  108. type driverDefinition struct {
  109. // DriverInfo is the static information that the storage testsuite
  110. // expects from a test driver. See test/e2e/storage/testsuites/testdriver.go
  111. // for details. The only field with a non-zero default is the list of
  112. // supported file systems (SupportedFsType): it is set so that tests using
  113. // the default file system are enabled.
  114. DriverInfo testsuites.DriverInfo
  115. // ShortName is used to create unique names for test cases and test resources.
  116. ShortName string
  117. // StorageClass must be set to enable dynamic provisioning tests.
  118. // The default is to not run those tests.
  119. StorageClass struct {
  120. // FromName set to true enables the usage of a storage
  121. // class with DriverInfo.Name as provisioner and no
  122. // parameters.
  123. FromName bool
  124. // FromFile is used only when FromName is false. It
  125. // loads a storage class from the given .yaml or .json
  126. // file. File names are resolved by the
  127. // framework.testfiles package, which typically means
  128. // that they can be absolute or relative to the test
  129. // suite's --repo-root parameter.
  130. //
  131. // This can be used when the storage class is meant to have
  132. // additional parameters.
  133. FromFile string
  134. }
  135. // SnapshotClass must be set to enable snapshotting tests.
  136. // The default is to not run those tests.
  137. SnapshotClass struct {
  138. // FromName set to true enables the usage of a
  139. // snapshotter class with DriverInfo.Name as provisioner.
  140. FromName bool
  141. // TODO (?): load from file
  142. }
  143. // ClaimSize defines the desired size of dynamically
  144. // provisioned volumes. Default is "5GiB".
  145. ClaimSize string
  146. // ClientNodeName selects a specific node for scheduling test pods.
  147. // Can be left empty. Most drivers should not need this and instead
  148. // use topology to ensure that pods land on the right node(s).
  149. ClientNodeName string
  150. }
  151. func (d *driverDefinition) DeepCopyObject() runtime.Object {
  152. return nil
  153. }
  154. func (d *driverDefinition) GetObjectKind() schema.ObjectKind {
  155. return nil
  156. }
  157. func (d *driverDefinition) GetDriverInfo() *testsuites.DriverInfo {
  158. return &d.DriverInfo
  159. }
  160. func (d *driverDefinition) SkipUnsupportedTest(pattern testpatterns.TestPattern) {
  161. supported := false
  162. // TODO (?): add support for more volume types
  163. switch pattern.VolType {
  164. case "":
  165. supported = true
  166. case testpatterns.DynamicPV:
  167. if d.StorageClass.FromName || d.StorageClass.FromFile != "" {
  168. supported = true
  169. }
  170. }
  171. if !supported {
  172. framework.Skipf("Driver %q does not support volume type %q - skipping", d.DriverInfo.Name, pattern.VolType)
  173. }
  174. supported = false
  175. switch pattern.SnapshotType {
  176. case "":
  177. supported = true
  178. case testpatterns.DynamicCreatedSnapshot:
  179. if d.SnapshotClass.FromName {
  180. supported = true
  181. }
  182. }
  183. if !supported {
  184. framework.Skipf("Driver %q does not support snapshot type %q - skipping", d.DriverInfo.Name, pattern.SnapshotType)
  185. }
  186. }
  187. func (d *driverDefinition) GetDynamicProvisionStorageClass(config *testsuites.PerTestConfig, fsType string) *storagev1.StorageClass {
  188. f := config.Framework
  189. if d.StorageClass.FromName {
  190. provisioner := d.DriverInfo.Name
  191. parameters := map[string]string{}
  192. ns := f.Namespace.Name
  193. suffix := provisioner + "-sc"
  194. if fsType != "" {
  195. parameters["csi.storage.k8s.io/fstype"] = fsType
  196. }
  197. return testsuites.GetStorageClass(provisioner, parameters, nil, ns, suffix)
  198. }
  199. items, err := f.LoadFromManifests(d.StorageClass.FromFile)
  200. framework.ExpectNoError(err, "load storage class from %s", d.StorageClass.FromFile)
  201. gomega.Expect(len(items)).To(gomega.Equal(1), "exactly one item from %s", d.StorageClass.FromFile)
  202. err = f.PatchItems(items...)
  203. framework.ExpectNoError(err, "patch items")
  204. sc, ok := items[0].(*storagev1.StorageClass)
  205. gomega.Expect(ok).To(gomega.BeTrue(), "storage class from %s", d.StorageClass.FromFile)
  206. // Ensure that we can load more than once as required for
  207. // GetDynamicProvisionStorageClass by adding a random suffix.
  208. sc.Name = names.SimpleNameGenerator.GenerateName(sc.Name + "-")
  209. if fsType != "" {
  210. if sc.Parameters == nil {
  211. sc.Parameters = map[string]string{}
  212. }
  213. sc.Parameters["csi.storage.k8s.io/fstype"] = fsType
  214. }
  215. return sc
  216. }
  217. func (d *driverDefinition) GetSnapshotClass(config *testsuites.PerTestConfig) *unstructured.Unstructured {
  218. if !d.SnapshotClass.FromName {
  219. framework.Skipf("Driver %q does not support snapshotting - skipping", d.DriverInfo.Name)
  220. }
  221. snapshotter := d.DriverInfo.Name
  222. parameters := map[string]string{}
  223. ns := config.Framework.Namespace.Name
  224. suffix := snapshotter + "-vsc"
  225. return testsuites.GetSnapshotClass(snapshotter, parameters, ns, suffix)
  226. }
  227. func (d *driverDefinition) GetClaimSize() string {
  228. return d.ClaimSize
  229. }
  230. func (d *driverDefinition) PrepareTest(f *framework.Framework) (*testsuites.PerTestConfig, func()) {
  231. config := &testsuites.PerTestConfig{
  232. Driver: d,
  233. Prefix: "external",
  234. Framework: f,
  235. ClientNodeName: d.ClientNodeName,
  236. }
  237. return config, func() {}
  238. }