csi_block_test.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508
  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 csi
  14. import (
  15. "fmt"
  16. "os"
  17. "path/filepath"
  18. "testing"
  19. api "k8s.io/api/core/v1"
  20. "k8s.io/api/storage/v1beta1"
  21. meta "k8s.io/apimachinery/pkg/apis/meta/v1"
  22. utilfeature "k8s.io/apiserver/pkg/util/feature"
  23. fakeclient "k8s.io/client-go/kubernetes/fake"
  24. featuregatetesting "k8s.io/component-base/featuregate/testing"
  25. "k8s.io/kubernetes/pkg/features"
  26. "k8s.io/kubernetes/pkg/volume"
  27. volumetest "k8s.io/kubernetes/pkg/volume/testing"
  28. )
  29. func prepareBlockMapperTest(plug *csiPlugin, specVolumeName string, t *testing.T) (*csiBlockMapper, *volume.Spec, *api.PersistentVolume, error) {
  30. registerFakePlugin(testDriver, "endpoint", []string{"1.0.0"}, t)
  31. pv := makeTestPV(specVolumeName, 10, testDriver, testVol)
  32. spec := volume.NewSpecFromPersistentVolume(pv, pv.Spec.PersistentVolumeSource.CSI.ReadOnly)
  33. mapper, err := plug.NewBlockVolumeMapper(
  34. spec,
  35. &api.Pod{ObjectMeta: meta.ObjectMeta{UID: testPodUID, Namespace: testns}},
  36. volume.VolumeOptions{},
  37. )
  38. if err != nil {
  39. return nil, nil, nil, fmt.Errorf("Failed to make a new Mapper: %v", err)
  40. }
  41. csiMapper := mapper.(*csiBlockMapper)
  42. return csiMapper, spec, pv, nil
  43. }
  44. func TestBlockMapperGetGlobalMapPath(t *testing.T) {
  45. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIBlockVolume, true)()
  46. plug, tmpDir := newTestPlugin(t, nil)
  47. defer os.RemoveAll(tmpDir)
  48. // TODO (vladimirvivien) specName with slashes will not work
  49. testCases := []struct {
  50. name string
  51. specVolumeName string
  52. path string
  53. }{
  54. {
  55. name: "simple specName",
  56. specVolumeName: "spec-0",
  57. path: filepath.Join(tmpDir, fmt.Sprintf("plugins/kubernetes.io/csi/volumeDevices/%s/%s", "spec-0", "dev")),
  58. },
  59. {
  60. name: "specName with dots",
  61. specVolumeName: "test.spec.1",
  62. path: filepath.Join(tmpDir, fmt.Sprintf("plugins/kubernetes.io/csi/volumeDevices/%s/%s", "test.spec.1", "dev")),
  63. },
  64. }
  65. for _, tc := range testCases {
  66. t.Logf("test case: %s", tc.name)
  67. csiMapper, spec, _, err := prepareBlockMapperTest(plug, tc.specVolumeName, t)
  68. if err != nil {
  69. t.Fatalf("Failed to make a new Mapper: %v", err)
  70. }
  71. path, err := csiMapper.GetGlobalMapPath(spec)
  72. if err != nil {
  73. t.Errorf("mapper GetGlobalMapPath failed: %v", err)
  74. }
  75. if tc.path != path {
  76. t.Errorf("expecting path %s, got %s", tc.path, path)
  77. }
  78. }
  79. }
  80. func TestBlockMapperGetStagingPath(t *testing.T) {
  81. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIBlockVolume, true)()
  82. plug, tmpDir := newTestPlugin(t, nil)
  83. defer os.RemoveAll(tmpDir)
  84. testCases := []struct {
  85. name string
  86. specVolumeName string
  87. path string
  88. }{
  89. {
  90. name: "simple specName",
  91. specVolumeName: "spec-0",
  92. path: filepath.Join(tmpDir, fmt.Sprintf("plugins/kubernetes.io/csi/volumeDevices/staging/%s", "spec-0")),
  93. },
  94. {
  95. name: "specName with dots",
  96. specVolumeName: "test.spec.1",
  97. path: filepath.Join(tmpDir, fmt.Sprintf("plugins/kubernetes.io/csi/volumeDevices/staging/%s", "test.spec.1")),
  98. },
  99. }
  100. for _, tc := range testCases {
  101. t.Logf("test case: %s", tc.name)
  102. csiMapper, _, _, err := prepareBlockMapperTest(plug, tc.specVolumeName, t)
  103. if err != nil {
  104. t.Fatalf("Failed to make a new Mapper: %v", err)
  105. }
  106. path := csiMapper.getStagingPath()
  107. if tc.path != path {
  108. t.Errorf("expecting path %s, got %s", tc.path, path)
  109. }
  110. }
  111. }
  112. func TestBlockMapperGetPublishPath(t *testing.T) {
  113. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIBlockVolume, true)()
  114. plug, tmpDir := newTestPlugin(t, nil)
  115. defer os.RemoveAll(tmpDir)
  116. testCases := []struct {
  117. name string
  118. specVolumeName string
  119. path string
  120. }{
  121. {
  122. name: "simple specName",
  123. specVolumeName: "spec-0",
  124. path: filepath.Join(tmpDir, fmt.Sprintf("plugins/kubernetes.io/csi/volumeDevices/publish/%s", "spec-0")),
  125. },
  126. {
  127. name: "specName with dots",
  128. specVolumeName: "test.spec.1",
  129. path: filepath.Join(tmpDir, fmt.Sprintf("plugins/kubernetes.io/csi/volumeDevices/publish/%s", "test.spec.1")),
  130. },
  131. }
  132. for _, tc := range testCases {
  133. t.Logf("test case: %s", tc.name)
  134. csiMapper, _, _, err := prepareBlockMapperTest(plug, tc.specVolumeName, t)
  135. if err != nil {
  136. t.Fatalf("Failed to make a new Mapper: %v", err)
  137. }
  138. path := csiMapper.getPublishPath()
  139. if tc.path != path {
  140. t.Errorf("expecting path %s, got %s", tc.path, path)
  141. }
  142. }
  143. }
  144. func TestBlockMapperGetDeviceMapPath(t *testing.T) {
  145. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIBlockVolume, true)()
  146. plug, tmpDir := newTestPlugin(t, nil)
  147. defer os.RemoveAll(tmpDir)
  148. testCases := []struct {
  149. name string
  150. specVolumeName string
  151. path string
  152. }{
  153. {
  154. name: "simple specName",
  155. specVolumeName: "spec-0",
  156. path: filepath.Join(tmpDir, fmt.Sprintf("pods/%s/volumeDevices/kubernetes.io~csi", testPodUID)),
  157. },
  158. {
  159. name: "specName with dots",
  160. specVolumeName: "test.spec.1",
  161. path: filepath.Join(tmpDir, fmt.Sprintf("pods/%s/volumeDevices/kubernetes.io~csi", testPodUID)),
  162. },
  163. }
  164. for _, tc := range testCases {
  165. t.Logf("test case: %s", tc.name)
  166. csiMapper, _, _, err := prepareBlockMapperTest(plug, tc.specVolumeName, t)
  167. if err != nil {
  168. t.Fatalf("Failed to make a new Mapper: %v", err)
  169. }
  170. path, volName := csiMapper.GetPodDeviceMapPath()
  171. if tc.path != path {
  172. t.Errorf("expecting path %s, got %s", tc.path, path)
  173. }
  174. if tc.specVolumeName != volName {
  175. t.Errorf("expecting volName %s, got %s", tc.specVolumeName, volName)
  176. }
  177. }
  178. }
  179. func TestBlockMapperSetupDevice(t *testing.T) {
  180. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIBlockVolume, true)()
  181. plug, tmpDir := newTestPlugin(t, nil)
  182. defer os.RemoveAll(tmpDir)
  183. fakeClient := fakeclient.NewSimpleClientset()
  184. host := volumetest.NewFakeVolumeHostWithCSINodeName(
  185. tmpDir,
  186. fakeClient,
  187. nil,
  188. "fakeNode",
  189. nil,
  190. )
  191. plug.host = host
  192. csiMapper, _, pv, err := prepareBlockMapperTest(plug, "test-pv", t)
  193. if err != nil {
  194. t.Fatalf("Failed to make a new Mapper: %v", err)
  195. }
  196. pvName := pv.GetName()
  197. nodeName := string(plug.host.GetNodeName())
  198. csiMapper.csiClient = setupClient(t, true)
  199. attachID := getAttachmentName(csiMapper.volumeID, string(csiMapper.driverName), string(nodeName))
  200. attachment := makeTestAttachment(attachID, nodeName, pvName)
  201. attachment.Status.Attached = true
  202. _, err = csiMapper.k8s.StorageV1().VolumeAttachments().Create(attachment)
  203. if err != nil {
  204. t.Fatalf("failed to setup VolumeAttachment: %v", err)
  205. }
  206. t.Log("created attachement ", attachID)
  207. devicePath, err := csiMapper.SetUpDevice()
  208. if err != nil {
  209. t.Fatalf("mapper failed to SetupDevice: %v", err)
  210. }
  211. // Check if SetUpDevice returns the right path
  212. publishPath := csiMapper.getPublishPath()
  213. if devicePath != publishPath {
  214. t.Fatalf("mapper.SetupDevice returned unexpected path %s instead of %v", devicePath, publishPath)
  215. }
  216. // Check if NodeStageVolume staged to the right path
  217. stagingPath := csiMapper.getStagingPath()
  218. svols := csiMapper.csiClient.(*fakeCsiDriverClient).nodeClient.GetNodeStagedVolumes()
  219. svol, ok := svols[csiMapper.volumeID]
  220. if !ok {
  221. t.Error("csi server may not have received NodeStageVolume call")
  222. }
  223. if svol.Path != stagingPath {
  224. t.Errorf("csi server expected device path %s, got %s", stagingPath, svol.Path)
  225. }
  226. // Check if NodePublishVolume published to the right path
  227. pvols := csiMapper.csiClient.(*fakeCsiDriverClient).nodeClient.GetNodePublishedVolumes()
  228. pvol, ok := pvols[csiMapper.volumeID]
  229. if !ok {
  230. t.Error("csi server may not have received NodePublishVolume call")
  231. }
  232. if pvol.Path != publishPath {
  233. t.Errorf("csi server expected path %s, got %s", publishPath, pvol.Path)
  234. }
  235. }
  236. func TestBlockMapperMapDevice(t *testing.T) {
  237. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIBlockVolume, true)()
  238. plug, tmpDir := newTestPlugin(t, nil)
  239. defer os.RemoveAll(tmpDir)
  240. fakeClient := fakeclient.NewSimpleClientset()
  241. host := volumetest.NewFakeVolumeHostWithCSINodeName(
  242. tmpDir,
  243. fakeClient,
  244. nil,
  245. "fakeNode",
  246. nil,
  247. )
  248. plug.host = host
  249. csiMapper, _, pv, err := prepareBlockMapperTest(plug, "test-pv", t)
  250. if err != nil {
  251. t.Fatalf("Failed to make a new Mapper: %v", err)
  252. }
  253. pvName := pv.GetName()
  254. nodeName := string(plug.host.GetNodeName())
  255. csiMapper.csiClient = setupClient(t, true)
  256. attachID := getAttachmentName(csiMapper.volumeID, string(csiMapper.driverName), string(nodeName))
  257. attachment := makeTestAttachment(attachID, nodeName, pvName)
  258. attachment.Status.Attached = true
  259. _, err = csiMapper.k8s.StorageV1().VolumeAttachments().Create(attachment)
  260. if err != nil {
  261. t.Fatalf("failed to setup VolumeAttachment: %v", err)
  262. }
  263. t.Log("created attachement ", attachID)
  264. devicePath, err := csiMapper.SetUpDevice()
  265. if err != nil {
  266. t.Fatalf("mapper failed to SetupDevice: %v", err)
  267. }
  268. globalMapPath, err := csiMapper.GetGlobalMapPath(csiMapper.spec)
  269. if err != nil {
  270. t.Fatalf("mapper failed to GetGlobalMapPath: %v", err)
  271. }
  272. // Actual SetupDevice should create a symlink to or a bind mout of device in devicePath.
  273. // Create dummy file there before calling MapDevice to test it properly.
  274. fd, err := os.Create(devicePath)
  275. if err != nil {
  276. t.Fatalf("mapper failed to create dummy file in devicePath: %v", err)
  277. }
  278. if err := fd.Close(); err != nil {
  279. t.Fatalf("mapper failed to close dummy file in devicePath: %v", err)
  280. }
  281. // Map device to global and pod device map path
  282. volumeMapPath, volName := csiMapper.GetPodDeviceMapPath()
  283. err = csiMapper.MapDevice(devicePath, globalMapPath, volumeMapPath, volName, csiMapper.podUID)
  284. if err != nil {
  285. t.Fatalf("mapper failed to GetGlobalMapPath: %v", err)
  286. }
  287. // Check if symlink {globalMapPath}/{podUID} exists
  288. globalMapFilePath := filepath.Join(globalMapPath, string(csiMapper.podUID))
  289. if _, err := os.Stat(globalMapFilePath); err != nil {
  290. if os.IsNotExist(err) {
  291. t.Errorf("mapper.MapDevice failed, symlink in globalMapPath not created: %v", err)
  292. t.Errorf("mapper.MapDevice devicePath:%v, globalMapPath: %v, globalMapFilePath: %v",
  293. devicePath, globalMapPath, globalMapFilePath)
  294. } else {
  295. t.Errorf("mapper.MapDevice failed: %v", err)
  296. }
  297. }
  298. // Check if symlink {volumeMapPath}/{volName} exists
  299. volumeMapFilePath := filepath.Join(volumeMapPath, volName)
  300. if _, err := os.Stat(volumeMapFilePath); err != nil {
  301. if os.IsNotExist(err) {
  302. t.Errorf("mapper.MapDevice failed, symlink in volumeMapPath not created: %v", err)
  303. } else {
  304. t.Errorf("mapper.MapDevice failed: %v", err)
  305. }
  306. }
  307. }
  308. func TestBlockMapperMapDeviceNotSupportAttach(t *testing.T) {
  309. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIBlockVolume, true)()
  310. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIDriverRegistry, true)()
  311. plug, tmpDir := newTestPlugin(t, nil)
  312. defer os.RemoveAll(tmpDir)
  313. fakeClient := fakeclient.NewSimpleClientset()
  314. attachRequired := false
  315. fakeDriver := &v1beta1.CSIDriver{
  316. ObjectMeta: meta.ObjectMeta{
  317. Name: testDriver,
  318. },
  319. Spec: v1beta1.CSIDriverSpec{
  320. AttachRequired: &attachRequired,
  321. },
  322. }
  323. _, err := plug.host.GetKubeClient().StorageV1beta1().CSIDrivers().Create(fakeDriver)
  324. if err != nil {
  325. t.Fatalf("Failed to create a fakeDriver: %v", err)
  326. }
  327. host := volumetest.NewFakeVolumeHostWithCSINodeName(
  328. tmpDir,
  329. fakeClient,
  330. nil,
  331. "fakeNode",
  332. nil,
  333. )
  334. plug.host = host
  335. csiMapper, _, _, err := prepareBlockMapperTest(plug, "test-pv", t)
  336. if err != nil {
  337. t.Fatalf("Failed to make a new Mapper: %v", err)
  338. }
  339. csiMapper.csiClient = setupClient(t, true)
  340. devicePath, err := csiMapper.SetUpDevice()
  341. if err != nil {
  342. t.Fatalf("mapper failed to SetupDevice: %v", err)
  343. }
  344. globalMapPath, err := csiMapper.GetGlobalMapPath(csiMapper.spec)
  345. if err != nil {
  346. t.Fatalf("mapper failed to GetGlobalMapPath: %v", err)
  347. }
  348. // Actual SetupDevice should create a symlink to or a bind mout of device in devicePath.
  349. // Create dummy file there before calling MapDevice to test it properly.
  350. fd, err := os.Create(devicePath)
  351. if err != nil {
  352. t.Fatalf("mapper failed to create dummy file in devicePath: %v", err)
  353. }
  354. if err := fd.Close(); err != nil {
  355. t.Fatalf("mapper failed to close dummy file in devicePath: %v", err)
  356. }
  357. // Map device to global and pod device map path
  358. volumeMapPath, volName := csiMapper.GetPodDeviceMapPath()
  359. err = csiMapper.MapDevice(devicePath, globalMapPath, volumeMapPath, volName, csiMapper.podUID)
  360. if err != nil {
  361. t.Fatalf("mapper failed to GetGlobalMapPath: %v", err)
  362. }
  363. // Check if symlink {globalMapPath}/{podUID} exists
  364. globalMapFilePath := filepath.Join(globalMapPath, string(csiMapper.podUID))
  365. if _, err := os.Stat(globalMapFilePath); err != nil {
  366. if os.IsNotExist(err) {
  367. t.Errorf("mapper.MapDevice failed, symlink in globalMapPath not created: %v", err)
  368. t.Errorf("mapper.MapDevice devicePath:%v, globalMapPath: %v, globalMapFilePath: %v",
  369. devicePath, globalMapPath, globalMapFilePath)
  370. } else {
  371. t.Errorf("mapper.MapDevice failed: %v", err)
  372. }
  373. }
  374. // Check if symlink {volumeMapPath}/{volName} exists
  375. volumeMapFilePath := filepath.Join(volumeMapPath, volName)
  376. if _, err := os.Stat(volumeMapFilePath); err != nil {
  377. if os.IsNotExist(err) {
  378. t.Errorf("mapper.MapDevice failed, symlink in volumeMapPath not created: %v", err)
  379. } else {
  380. t.Errorf("mapper.MapDevice failed: %v", err)
  381. }
  382. }
  383. }
  384. func TestBlockMapperTearDownDevice(t *testing.T) {
  385. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIBlockVolume, true)()
  386. plug, tmpDir := newTestPlugin(t, nil)
  387. defer os.RemoveAll(tmpDir)
  388. fakeClient := fakeclient.NewSimpleClientset()
  389. host := volumetest.NewFakeVolumeHostWithCSINodeName(
  390. tmpDir,
  391. fakeClient,
  392. nil,
  393. "fakeNode",
  394. nil,
  395. )
  396. plug.host = host
  397. _, spec, pv, err := prepareBlockMapperTest(plug, "test-pv", t)
  398. if err != nil {
  399. t.Fatalf("Failed to make a new Mapper: %v", err)
  400. }
  401. // save volume data
  402. dir := getVolumeDeviceDataDir(pv.ObjectMeta.Name, plug.host)
  403. if err := os.MkdirAll(dir, 0755); err != nil && !os.IsNotExist(err) {
  404. t.Errorf("failed to create dir [%s]: %v", dir, err)
  405. }
  406. if err := saveVolumeData(
  407. dir,
  408. volDataFileName,
  409. map[string]string{
  410. volDataKey.specVolID: pv.ObjectMeta.Name,
  411. volDataKey.driverName: testDriver,
  412. volDataKey.volHandle: testVol,
  413. },
  414. ); err != nil {
  415. t.Fatalf("failed to save volume data: %v", err)
  416. }
  417. unmapper, err := plug.NewBlockVolumeUnmapper(pv.ObjectMeta.Name, testPodUID)
  418. if err != nil {
  419. t.Fatalf("failed to make a new Unmapper: %v", err)
  420. }
  421. csiUnmapper := unmapper.(*csiBlockMapper)
  422. csiUnmapper.csiClient = setupClient(t, true)
  423. globalMapPath, err := csiUnmapper.GetGlobalMapPath(spec)
  424. if err != nil {
  425. t.Fatalf("unmapper failed to GetGlobalMapPath: %v", err)
  426. }
  427. err = csiUnmapper.TearDownDevice(globalMapPath, "/dev/test")
  428. if err != nil {
  429. t.Fatal(err)
  430. }
  431. // ensure csi client call and node unpblished
  432. pubs := csiUnmapper.csiClient.(*fakeCsiDriverClient).nodeClient.GetNodePublishedVolumes()
  433. if _, ok := pubs[csiUnmapper.volumeID]; ok {
  434. t.Error("csi server may not have received NodeUnpublishVolume call")
  435. }
  436. // ensure csi client call and node unstaged
  437. vols := csiUnmapper.csiClient.(*fakeCsiDriverClient).nodeClient.GetNodeStagedVolumes()
  438. if _, ok := vols[csiUnmapper.volumeID]; ok {
  439. t.Error("csi server may not have received NodeUnstageVolume call")
  440. }
  441. }