external.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  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. "context"
  16. "flag"
  17. "io/ioutil"
  18. "github.com/pkg/errors"
  19. storagev1 "k8s.io/api/storage/v1"
  20. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  21. "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
  22. "k8s.io/apimachinery/pkg/runtime"
  23. "k8s.io/apimachinery/pkg/runtime/schema"
  24. "k8s.io/apimachinery/pkg/util/sets"
  25. "k8s.io/client-go/kubernetes/scheme"
  26. "k8s.io/kubernetes/test/e2e/framework"
  27. "k8s.io/kubernetes/test/e2e/framework/config"
  28. e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
  29. e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
  30. "k8s.io/kubernetes/test/e2e/framework/volume"
  31. "k8s.io/kubernetes/test/e2e/storage/testpatterns"
  32. "k8s.io/kubernetes/test/e2e/storage/testsuites"
  33. "k8s.io/kubernetes/test/e2e/storage/utils"
  34. "github.com/onsi/ginkgo"
  35. )
  36. // DriverDefinition needs to be filled in via a .yaml or .json
  37. // file. Its methods then implement the TestDriver interface, using
  38. // nothing but the information in this struct.
  39. type driverDefinition struct {
  40. // DriverInfo is the static information that the storage testsuite
  41. // expects from a test driver. See test/e2e/storage/testsuites/testdriver.go
  42. // for details. The only field with a non-zero default is the list of
  43. // supported file systems (SupportedFsType): it is set so that tests using
  44. // the default file system are enabled.
  45. DriverInfo testsuites.DriverInfo
  46. // StorageClass must be set to enable dynamic provisioning tests.
  47. // The default is to not run those tests.
  48. StorageClass struct {
  49. // FromName set to true enables the usage of a storage
  50. // class with DriverInfo.Name as provisioner and no
  51. // parameters.
  52. FromName bool
  53. // FromFile is used only when FromName is false. It
  54. // loads a storage class from the given .yaml or .json
  55. // file. File names are resolved by the
  56. // framework.testfiles package, which typically means
  57. // that they can be absolute or relative to the test
  58. // suite's --repo-root parameter.
  59. //
  60. // This can be used when the storage class is meant to have
  61. // additional parameters.
  62. FromFile string
  63. // FromExistingClassName specifies the name of a pre-installed
  64. // StorageClass that will be copied and used for the tests.
  65. FromExistingClassName string
  66. }
  67. // SnapshotClass must be set to enable snapshotting tests.
  68. // The default is to not run those tests.
  69. SnapshotClass struct {
  70. // FromName set to true enables the usage of a
  71. // snapshotter class with DriverInfo.Name as provisioner.
  72. FromName bool
  73. // TODO (?): load from file
  74. }
  75. // InlineVolumes defines one or more volumes for use as inline
  76. // ephemeral volumes. At least one such volume has to be
  77. // defined to enable testing of inline ephemeral volumes. If
  78. // a test needs more volumes than defined, some of the defined
  79. // volumes will be used multiple times.
  80. //
  81. // DriverInfo.Name is used as name of the driver in the inline volume.
  82. InlineVolumes []struct {
  83. // Attributes are passed as NodePublishVolumeReq.volume_context.
  84. // Can be empty.
  85. Attributes map[string]string
  86. // Shared defines whether the resulting volume is
  87. // shared between different pods (i.e. changes made
  88. // in one pod are visible in another)
  89. Shared bool
  90. // ReadOnly must be set to true if the driver does not
  91. // support mounting as read/write.
  92. ReadOnly bool
  93. }
  94. // SupportedSizeRange defines the desired size of dynamically
  95. // provisioned volumes.
  96. SupportedSizeRange volume.SizeRange
  97. // ClientNodeName selects a specific node for scheduling test pods.
  98. // Can be left empty. Most drivers should not need this and instead
  99. // use topology to ensure that pods land on the right node(s).
  100. ClientNodeName string
  101. }
  102. // List of testSuites to be executed for each external driver.
  103. var csiTestSuites = []func() testsuites.TestSuite{
  104. testsuites.InitEphemeralTestSuite,
  105. testsuites.InitMultiVolumeTestSuite,
  106. testsuites.InitProvisioningTestSuite,
  107. testsuites.InitSnapshottableTestSuite,
  108. testsuites.InitSubPathTestSuite,
  109. testsuites.InitVolumeIOTestSuite,
  110. testsuites.InitVolumeModeTestSuite,
  111. testsuites.InitVolumesTestSuite,
  112. testsuites.InitVolumeExpandTestSuite,
  113. testsuites.InitDisruptiveTestSuite,
  114. testsuites.InitVolumeLimitsTestSuite,
  115. }
  116. func init() {
  117. config.Flags.Var(testDriverParameter{}, "storage.testdriver", "name of a .yaml or .json file that defines a driver for storage testing, can be used more than once")
  118. }
  119. // testDriverParameter is used to hook loading of the driver
  120. // definition file and test instantiation into argument parsing: for
  121. // each of potentially many parameters, Set is called and then does
  122. // both immediately. There is no other code location between argument
  123. // parsing and starting of the test suite where those test could be
  124. // defined.
  125. type testDriverParameter struct {
  126. }
  127. var _ flag.Value = testDriverParameter{}
  128. func (t testDriverParameter) String() string {
  129. return "<.yaml or .json file>"
  130. }
  131. func (t testDriverParameter) Set(filename string) error {
  132. return AddDriverDefinition(filename)
  133. }
  134. // AddDriverDefinition defines ginkgo tests for CSI driver definition file.
  135. // Either --storage.testdriver cmdline argument or AddDriverDefinition can be used
  136. // to define the tests.
  137. func AddDriverDefinition(filename string) error {
  138. driver, err := loadDriverDefinition(filename)
  139. if err != nil {
  140. return err
  141. }
  142. if driver.DriverInfo.Name == "" {
  143. return errors.Errorf("%q: DriverInfo.Name not set", filename)
  144. }
  145. description := "External Storage " + testsuites.GetDriverNameWithFeatureTags(driver)
  146. ginkgo.Describe(description, func() {
  147. testsuites.DefineTestSuite(driver, csiTestSuites)
  148. })
  149. return nil
  150. }
  151. func loadDriverDefinition(filename string) (*driverDefinition, error) {
  152. if filename == "" {
  153. return nil, errors.New("missing file name")
  154. }
  155. data, err := ioutil.ReadFile(filename)
  156. if err != nil {
  157. return nil, err
  158. }
  159. // Some reasonable defaults follow.
  160. driver := &driverDefinition{
  161. DriverInfo: testsuites.DriverInfo{
  162. SupportedFsType: sets.NewString(
  163. "", // Default fsType
  164. ),
  165. },
  166. SupportedSizeRange: volume.SizeRange{
  167. Min: "5Gi",
  168. },
  169. }
  170. // TODO: strict checking of the file content once https://github.com/kubernetes/kubernetes/pull/71589
  171. // or something similar is merged.
  172. if err := runtime.DecodeInto(scheme.Codecs.UniversalDecoder(), data, driver); err != nil {
  173. return nil, errors.Wrap(err, filename)
  174. }
  175. return driver, nil
  176. }
  177. var _ testsuites.TestDriver = &driverDefinition{}
  178. // We have to implement the interface because dynamic PV may or may
  179. // not be supported. driverDefinition.SkipUnsupportedTest checks that
  180. // based on the actual driver definition.
  181. var _ testsuites.DynamicPVTestDriver = &driverDefinition{}
  182. // Same for snapshotting.
  183. var _ testsuites.SnapshottableTestDriver = &driverDefinition{}
  184. // And for ephemeral volumes.
  185. var _ testsuites.EphemeralTestDriver = &driverDefinition{}
  186. // runtime.DecodeInto needs a runtime.Object but doesn't do any
  187. // deserialization of it and therefore none of the methods below need
  188. // an implementation.
  189. var _ runtime.Object = &driverDefinition{}
  190. func (d *driverDefinition) DeepCopyObject() runtime.Object {
  191. return nil
  192. }
  193. func (d *driverDefinition) GetObjectKind() schema.ObjectKind {
  194. return nil
  195. }
  196. func (d *driverDefinition) GetDriverInfo() *testsuites.DriverInfo {
  197. return &d.DriverInfo
  198. }
  199. func (d *driverDefinition) SkipUnsupportedTest(pattern testpatterns.TestPattern) {
  200. supported := false
  201. // TODO (?): add support for more volume types
  202. switch pattern.VolType {
  203. case "":
  204. supported = true
  205. case testpatterns.DynamicPV:
  206. if d.StorageClass.FromName || d.StorageClass.FromFile != "" || d.StorageClass.FromExistingClassName != "" {
  207. supported = true
  208. }
  209. case testpatterns.CSIInlineVolume:
  210. supported = len(d.InlineVolumes) != 0
  211. }
  212. if !supported {
  213. e2eskipper.Skipf("Driver %q does not support volume type %q - skipping", d.DriverInfo.Name, pattern.VolType)
  214. }
  215. supported = false
  216. switch pattern.SnapshotType {
  217. case "":
  218. supported = true
  219. case testpatterns.DynamicCreatedSnapshot:
  220. if d.SnapshotClass.FromName {
  221. supported = true
  222. }
  223. }
  224. if !supported {
  225. e2eskipper.Skipf("Driver %q does not support snapshot type %q - skipping", d.DriverInfo.Name, pattern.SnapshotType)
  226. }
  227. }
  228. func (d *driverDefinition) GetDynamicProvisionStorageClass(config *testsuites.PerTestConfig, fsType string) *storagev1.StorageClass {
  229. var (
  230. sc *storagev1.StorageClass
  231. err error
  232. )
  233. f := config.Framework
  234. switch {
  235. case d.StorageClass.FromName:
  236. sc = &storagev1.StorageClass{Provisioner: d.DriverInfo.Name}
  237. case d.StorageClass.FromExistingClassName != "":
  238. sc, err = f.ClientSet.StorageV1().StorageClasses().Get(context.TODO(), d.StorageClass.FromExistingClassName, metav1.GetOptions{})
  239. framework.ExpectNoError(err, "getting storage class %s", d.StorageClass.FromExistingClassName)
  240. case d.StorageClass.FromFile != "":
  241. var ok bool
  242. items, err := utils.LoadFromManifests(d.StorageClass.FromFile)
  243. framework.ExpectNoError(err, "load storage class from %s", d.StorageClass.FromFile)
  244. framework.ExpectEqual(len(items), 1, "exactly one item from %s", d.StorageClass.FromFile)
  245. err = utils.PatchItems(f, items...)
  246. framework.ExpectNoError(err, "patch items")
  247. sc, ok = items[0].(*storagev1.StorageClass)
  248. framework.ExpectEqual(ok, true, "storage class from %s", d.StorageClass.FromFile)
  249. }
  250. framework.ExpectNotEqual(sc, nil, "storage class is unexpectantly nil")
  251. if fsType != "" {
  252. if sc.Parameters == nil {
  253. sc.Parameters = map[string]string{}
  254. }
  255. // This limits the external storage test suite to only CSI drivers, which may need to be
  256. // reconsidered if we eventually need to move in-tree storage tests out.
  257. sc.Parameters["csi.storage.k8s.io/fstype"] = fsType
  258. }
  259. return testsuites.GetStorageClass(sc.Provisioner, sc.Parameters, sc.VolumeBindingMode, f.Namespace.Name, "e2e-sc")
  260. }
  261. func (d *driverDefinition) GetSnapshotClass(config *testsuites.PerTestConfig) *unstructured.Unstructured {
  262. if !d.SnapshotClass.FromName {
  263. e2eskipper.Skipf("Driver %q does not support snapshotting - skipping", d.DriverInfo.Name)
  264. }
  265. snapshotter := d.DriverInfo.Name
  266. parameters := map[string]string{}
  267. ns := config.Framework.Namespace.Name
  268. suffix := snapshotter + "-vsc"
  269. return testsuites.GetSnapshotClass(snapshotter, parameters, ns, suffix)
  270. }
  271. func (d *driverDefinition) GetVolume(config *testsuites.PerTestConfig, volumeNumber int) (map[string]string, bool, bool) {
  272. if len(d.InlineVolumes) == 0 {
  273. e2eskipper.Skipf("%s does not have any InlineVolumeAttributes defined", d.DriverInfo.Name)
  274. }
  275. volume := d.InlineVolumes[volumeNumber%len(d.InlineVolumes)]
  276. return volume.Attributes, volume.Shared, volume.ReadOnly
  277. }
  278. func (d *driverDefinition) GetCSIDriverName(config *testsuites.PerTestConfig) string {
  279. return d.DriverInfo.Name
  280. }
  281. func (d *driverDefinition) PrepareTest(f *framework.Framework) (*testsuites.PerTestConfig, func()) {
  282. config := &testsuites.PerTestConfig{
  283. Driver: d,
  284. Prefix: "external",
  285. Framework: f,
  286. ClientNodeSelection: e2epod.NodeSelection{Name: d.ClientNodeName},
  287. }
  288. return config, func() {}
  289. }