Shared filesystem management project for OpenStack.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

977 lines
43 KiB

  1. # Copyright (c) 2014 Red Hat, Inc.
  2. # All Rights Reserved.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  5. # not use this file except in compliance with the License. You may obtain
  6. # a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. # License for the specific language governing permissions and limitations
  14. # under the License.
  15. """ GlusterFS volume mapped share layout testcases.
  16. """
  17. import re
  18. import shutil
  19. import tempfile
  20. from unittest import mock
  21. import ddt
  22. from oslo_config import cfg
  23. from manila.common import constants
  24. from manila import context
  25. from manila import exception
  26. from manila.share import configuration as config
  27. from manila.share.drivers.glusterfs import common
  28. from manila.share.drivers.glusterfs import layout_volume
  29. from manila import test
  30. from manila.tests import fake_utils
  31. CONF = cfg.CONF
  32. def new_share(**kwargs):
  33. share = {
  34. 'id': 'fakeid',
  35. 'name': 'fakename',
  36. 'size': 1,
  37. 'share_proto': 'glusterfs',
  38. }
  39. share.update(kwargs)
  40. return share
  41. def glusterXMLOut(**kwargs):
  42. template = """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
  43. <cliOutput>
  44. <opRet>%(ret)d</opRet>
  45. <opErrno>%(errno)d</opErrno>
  46. <opErrstr>fake error</opErrstr>
  47. </cliOutput>"""
  48. return template % kwargs, ''
  49. FAKE_UUID1 = '11111111-1111-1111-1111-111111111111'
  50. FAKE_UUID2 = '22222222-2222-2222-2222-222222222222'
  51. @ddt.ddt
  52. class GlusterfsVolumeMappedLayoutTestCase(test.TestCase):
  53. """Tests GlusterfsVolumeMappedLayout."""
  54. def setUp(self):
  55. super(GlusterfsVolumeMappedLayoutTestCase, self).setUp()
  56. fake_utils.stub_out_utils_execute(self)
  57. self._execute = fake_utils.fake_execute
  58. self._context = context.get_admin_context()
  59. self.glusterfs_target1 = 'root@host1:/gv1'
  60. self.glusterfs_target2 = 'root@host2:/gv2'
  61. self.glusterfs_server1 = 'root@host1'
  62. self.glusterfs_server2 = 'root@host2'
  63. self.glusterfs_server1_volumes = 'manila-share-1-1G\nshare1'
  64. self.glusterfs_server2_volumes = 'manila-share-2-2G\nshare2'
  65. self.share1 = new_share(
  66. export_location=self.glusterfs_target1,
  67. status=constants.STATUS_AVAILABLE)
  68. self.share2 = new_share(
  69. export_location=self.glusterfs_target2,
  70. status=constants.STATUS_AVAILABLE)
  71. gmgr = common.GlusterManager
  72. self.gmgr1 = gmgr(self.glusterfs_server1, self._execute, None, None,
  73. requires={'volume': False})
  74. self.gmgr2 = gmgr(self.glusterfs_server2, self._execute, None, None,
  75. requires={'volume': False})
  76. self.glusterfs_volumes_dict = (
  77. {'root@host1:/manila-share-1-1G': {'size': 1},
  78. 'root@host2:/manila-share-2-2G': {'size': 2}})
  79. self.glusterfs_used_vols = set([
  80. 'root@host1:/manila-share-1-1G',
  81. 'root@host2:/manila-share-2-2G'])
  82. CONF.set_default('glusterfs_servers',
  83. [self.glusterfs_server1, self.glusterfs_server2])
  84. CONF.set_default('glusterfs_server_password',
  85. 'fake_password')
  86. CONF.set_default('glusterfs_path_to_private_key',
  87. '/fakepath/to/privatekey')
  88. CONF.set_default('glusterfs_volume_pattern',
  89. r'manila-share-\d+-#{size}G$')
  90. CONF.set_default('driver_handles_share_servers', False)
  91. self.fake_driver = mock.Mock()
  92. self.mock_object(self.fake_driver, '_execute',
  93. self._execute)
  94. self.fake_driver.GLUSTERFS_VERSION_MIN = (3, 6)
  95. self.fake_conf = config.Configuration(None)
  96. self.mock_object(tempfile, 'mkdtemp',
  97. mock.Mock(return_value='/tmp/tmpKGHKJ'))
  98. self.mock_object(common.GlusterManager, 'make_gluster_call')
  99. self.fake_private_storage = mock.Mock()
  100. with mock.patch.object(layout_volume.GlusterfsVolumeMappedLayout,
  101. '_glustermanager',
  102. side_effect=[self.gmgr1, self.gmgr2]):
  103. self._layout = layout_volume.GlusterfsVolumeMappedLayout(
  104. self.fake_driver, configuration=self.fake_conf,
  105. private_storage=self.fake_private_storage)
  106. self._layout.glusterfs_versions = {self.glusterfs_server1: ('3', '6'),
  107. self.glusterfs_server2: ('3', '7')}
  108. self.addCleanup(fake_utils.fake_execute_set_repliers, [])
  109. self.addCleanup(fake_utils.fake_execute_clear_log)
  110. @ddt.data({"test_kwargs": {}, "requires": {"volume": True}},
  111. {"test_kwargs": {'req_volume': False},
  112. "requires": {"volume": False}})
  113. @ddt.unpack
  114. def test_glustermanager(self, test_kwargs, requires):
  115. fake_obj = mock.Mock()
  116. self.mock_object(common, 'GlusterManager',
  117. mock.Mock(return_value=fake_obj))
  118. ret = self._layout._glustermanager(self.glusterfs_target1,
  119. **test_kwargs)
  120. common.GlusterManager.assert_called_once_with(
  121. self.glusterfs_target1, self._execute,
  122. self._layout.configuration.glusterfs_path_to_private_key,
  123. self._layout.configuration.glusterfs_server_password,
  124. requires=requires)
  125. self.assertEqual(fake_obj, ret)
  126. def test_compile_volume_pattern(self):
  127. volume_pattern = r'manila-share-\d+-(?P<size>\d+)G$'
  128. ret = self._layout._compile_volume_pattern()
  129. self.assertEqual(re.compile(volume_pattern), ret)
  130. @ddt.data({'root@host1:/manila-share-1-1G': 'NONE',
  131. 'root@host2:/manila-share-2-2G': None},
  132. {'root@host1:/manila-share-1-1G': FAKE_UUID1,
  133. 'root@host2:/manila-share-2-2G': None},
  134. {'root@host1:/manila-share-1-1G': 'foobarbaz',
  135. 'root@host2:/manila-share-2-2G': FAKE_UUID2},
  136. {'root@host1:/manila-share-1-1G': FAKE_UUID1,
  137. 'root@host2:/manila-share-2-2G': FAKE_UUID2})
  138. def test_fetch_gluster_volumes(self, sharemark):
  139. vol1_qualified = 'root@host1:/manila-share-1-1G'
  140. gmgr_vol1 = common.GlusterManager(vol1_qualified)
  141. gmgr_vol1.get_vol_option = mock.Mock(
  142. return_value=sharemark[vol1_qualified])
  143. vol2_qualified = 'root@host2:/manila-share-2-2G'
  144. gmgr_vol2 = common.GlusterManager(vol2_qualified)
  145. gmgr_vol2.get_vol_option = mock.Mock(
  146. return_value=sharemark[vol2_qualified])
  147. self.mock_object(
  148. self.gmgr1, 'gluster_call',
  149. mock.Mock(return_value=(self.glusterfs_server1_volumes, '')))
  150. self.mock_object(
  151. self.gmgr2, 'gluster_call',
  152. mock.Mock(return_value=(self.glusterfs_server2_volumes, '')))
  153. _glustermanager_calls = (self.gmgr1, gmgr_vol1, self.gmgr2, gmgr_vol2)
  154. self.mock_object(self._layout, '_glustermanager',
  155. mock.Mock(side_effect=_glustermanager_calls))
  156. expected_output = {}
  157. for q, d in self.glusterfs_volumes_dict.items():
  158. if sharemark[q] not in (FAKE_UUID1, FAKE_UUID2):
  159. expected_output[q] = d
  160. ret = self._layout._fetch_gluster_volumes()
  161. test_args = ('volume', 'list')
  162. self.gmgr1.gluster_call.assert_called_once_with(*test_args,
  163. log=mock.ANY)
  164. self.gmgr2.gluster_call.assert_called_once_with(*test_args,
  165. log=mock.ANY)
  166. gmgr_vol1.get_vol_option.assert_called_once_with(
  167. 'user.manila-share')
  168. gmgr_vol2.get_vol_option.assert_called_once_with(
  169. 'user.manila-share')
  170. self.assertEqual(expected_output, ret)
  171. def test_fetch_gluster_volumes_no_filter_used(self):
  172. vol1_qualified = 'root@host1:/manila-share-1-1G'
  173. gmgr_vol1 = common.GlusterManager(vol1_qualified)
  174. gmgr_vol1.get_vol_option = mock.Mock()
  175. vol2_qualified = 'root@host2:/manila-share-2-2G'
  176. gmgr_vol2 = common.GlusterManager(vol2_qualified)
  177. gmgr_vol2.get_vol_option = mock.Mock()
  178. self.mock_object(
  179. self.gmgr1, 'gluster_call',
  180. mock.Mock(return_value=(self.glusterfs_server1_volumes, '')))
  181. self.mock_object(
  182. self.gmgr2, 'gluster_call',
  183. mock.Mock(return_value=(self.glusterfs_server2_volumes, '')))
  184. _glustermanager_calls = (self.gmgr1, gmgr_vol1, self.gmgr2, gmgr_vol2)
  185. self.mock_object(self._layout, '_glustermanager',
  186. mock.Mock(side_effect=_glustermanager_calls))
  187. expected_output = self.glusterfs_volumes_dict
  188. ret = self._layout._fetch_gluster_volumes(filter_used=False)
  189. test_args = ('volume', 'list')
  190. self.gmgr1.gluster_call.assert_called_once_with(*test_args,
  191. log=mock.ANY)
  192. self.gmgr2.gluster_call.assert_called_once_with(*test_args,
  193. log=mock.ANY)
  194. self.assertFalse(gmgr_vol1.get_vol_option.called)
  195. self.assertFalse(gmgr_vol2.get_vol_option.called)
  196. self.assertEqual(expected_output, ret)
  197. def test_fetch_gluster_volumes_no_keymatch(self):
  198. vol1_qualified = 'root@host1:/manila-share-1'
  199. gmgr_vol1 = common.GlusterManager(vol1_qualified)
  200. gmgr_vol1.get_vol_option = mock.Mock(return_value=None)
  201. self._layout.configuration.glusterfs_servers = [self.glusterfs_server1]
  202. self.mock_object(
  203. self.gmgr1, 'gluster_call',
  204. mock.Mock(return_value=('manila-share-1', '')))
  205. _glustermanager_calls = (self.gmgr1, gmgr_vol1)
  206. self.mock_object(self._layout, '_glustermanager',
  207. mock.Mock(side_effect=_glustermanager_calls))
  208. self.mock_object(self._layout, 'volume_pattern',
  209. re.compile(r'manila-share-\d+(-(?P<size>\d+)G)?$'))
  210. expected_output = {'root@host1:/manila-share-1': {'size': None}}
  211. ret = self._layout._fetch_gluster_volumes()
  212. test_args = ('volume', 'list')
  213. self.gmgr1.gluster_call.assert_called_once_with(*test_args,
  214. log=mock.ANY)
  215. self.assertEqual(expected_output, ret)
  216. def test_fetch_gluster_volumes_error(self):
  217. test_args = ('volume', 'list')
  218. def raise_exception(*args, **kwargs):
  219. if(args == test_args):
  220. raise exception.GlusterfsException()
  221. self._layout.configuration.glusterfs_servers = [self.glusterfs_server1]
  222. self.mock_object(self.gmgr1, 'gluster_call',
  223. mock.Mock(side_effect=raise_exception))
  224. self.mock_object(self._layout, '_glustermanager',
  225. mock.Mock(return_value=self.gmgr1))
  226. self.mock_object(layout_volume.LOG, 'error')
  227. self.assertRaises(exception.GlusterfsException,
  228. self._layout._fetch_gluster_volumes)
  229. self.gmgr1.gluster_call.assert_called_once_with(*test_args,
  230. log=mock.ANY)
  231. def test_do_setup(self):
  232. self._layout.configuration.glusterfs_servers = [self.glusterfs_server1]
  233. self.mock_object(self.gmgr1, 'get_gluster_version',
  234. mock.Mock(return_value=('3', '6')))
  235. self.mock_object(self._layout, '_glustermanager',
  236. mock.Mock(return_value=self.gmgr1))
  237. self.mock_object(self._layout, '_fetch_gluster_volumes',
  238. mock.Mock(return_value=self.glusterfs_volumes_dict))
  239. self.mock_object(self._layout, '_check_mount_glusterfs')
  240. self._layout.gluster_used_vols = self.glusterfs_used_vols
  241. self.mock_object(layout_volume.LOG, 'warning')
  242. self._layout.do_setup(self._context)
  243. self._layout._fetch_gluster_volumes.assert_called_once_with(
  244. filter_used=False)
  245. self._layout._check_mount_glusterfs.assert_called_once_with()
  246. self.gmgr1.get_gluster_version.assert_called_once_with()
  247. def test_do_setup_unsupported_glusterfs_version(self):
  248. self._layout.configuration.glusterfs_servers = [self.glusterfs_server1]
  249. self.mock_object(self.gmgr1, 'get_gluster_version',
  250. mock.Mock(return_value=('3', '5')))
  251. self.mock_object(self._layout, '_glustermanager',
  252. mock.Mock(return_value=self.gmgr1))
  253. self.assertRaises(exception.GlusterfsException,
  254. self._layout.do_setup, self._context)
  255. self.gmgr1.get_gluster_version.assert_called_once_with()
  256. @ddt.data(exception.GlusterfsException, RuntimeError)
  257. def test_do_setup_get_gluster_version_fails(self, exc):
  258. def raise_exception(*args, **kwargs):
  259. raise exc
  260. self._layout.configuration.glusterfs_servers = [self.glusterfs_server1]
  261. self.mock_object(self.gmgr1, 'get_gluster_version',
  262. mock.Mock(side_effect=raise_exception))
  263. self.mock_object(self._layout, '_glustermanager',
  264. mock.Mock(return_value=self.gmgr1))
  265. self.assertRaises(exc, self._layout.do_setup, self._context)
  266. self.gmgr1.get_gluster_version.assert_called_once_with()
  267. def test_do_setup_glusterfs_no_volumes_provided_by_backend(self):
  268. self._layout.configuration.glusterfs_servers = [self.glusterfs_server1]
  269. self.mock_object(self.gmgr1, 'get_gluster_version',
  270. mock.Mock(return_value=('3', '6')))
  271. self.mock_object(self._layout, '_glustermanager',
  272. mock.Mock(return_value=self.gmgr1))
  273. self.mock_object(self._layout, '_fetch_gluster_volumes',
  274. mock.Mock(return_value={}))
  275. self.assertRaises(exception.GlusterfsException,
  276. self._layout.do_setup, self._context)
  277. self._layout._fetch_gluster_volumes.assert_called_once_with(
  278. filter_used=False)
  279. def test_share_manager(self):
  280. self.mock_object(self._layout, '_glustermanager',
  281. mock.Mock(return_value=self.gmgr1))
  282. self.mock_object(self._layout.private_storage,
  283. 'get', mock.Mock(return_value='host1:/gv1'))
  284. ret = self._layout._share_manager(self.share1)
  285. self._layout.private_storage.get.assert_called_once_with(
  286. self.share1['id'], 'volume')
  287. self._layout._glustermanager.assert_called_once_with('host1:/gv1')
  288. self.assertEqual(self.gmgr1, ret)
  289. def test_share_manager_no_privdata(self):
  290. self.mock_object(self._layout.private_storage,
  291. 'get', mock.Mock(return_value=None))
  292. ret = self._layout._share_manager(self.share1)
  293. self._layout.private_storage.get.assert_called_once_with(
  294. self.share1['id'], 'volume')
  295. self.assertIsNone(ret)
  296. def test_ensure_share(self):
  297. share = self.share1
  298. gmgr1 = common.GlusterManager(self.glusterfs_target1, self._execute,
  299. None, None)
  300. gmgr1.set_vol_option = mock.Mock()
  301. self.mock_object(self._layout, '_share_manager',
  302. mock.Mock(return_value=gmgr1))
  303. self._layout.ensure_share(self._context, share)
  304. self._layout._share_manager.assert_called_once_with(share)
  305. self.assertIn(self.glusterfs_target1, self._layout.gluster_used_vols)
  306. gmgr1.set_vol_option.assert_called_once_with(
  307. 'user.manila-share', share['id'])
  308. @ddt.data({"voldict": {"host:/share2G": {"size": 2}}, "used_vols": set(),
  309. "size": 1, "expected": "host:/share2G"},
  310. {"voldict": {"host:/share2G": {"size": 2}}, "used_vols": set(),
  311. "size": 2, "expected": "host:/share2G"},
  312. {"voldict": {"host:/share2G": {"size": 2}}, "used_vols": set(),
  313. "size": None, "expected": "host:/share2G"},
  314. {"voldict": {"host:/share2G": {"size": 2},
  315. "host:/share": {"size": None}},
  316. "used_vols": set(["host:/share2G"]), "size": 1,
  317. "expected": "host:/share"},
  318. {"voldict": {"host:/share2G": {"size": 2},
  319. "host:/share": {"size": None}},
  320. "used_vols": set(["host:/share2G"]), "size": 2,
  321. "expected": "host:/share"},
  322. {"voldict": {"host:/share2G": {"size": 2},
  323. "host:/share": {"size": None}},
  324. "used_vols": set(["host:/share2G"]), "size": 3,
  325. "expected": "host:/share"},
  326. {"voldict": {"host:/share2G": {"size": 2},
  327. "host:/share": {"size": None}},
  328. "used_vols": set(["host:/share2G"]), "size": None,
  329. "expected": "host:/share"},
  330. {"voldict": {"host:/share": {}}, "used_vols": set(), "size": 1,
  331. "expected": "host:/share"},
  332. {"voldict": {"host:/share": {}}, "used_vols": set(),
  333. "size": None, "expected": "host:/share"})
  334. @ddt.unpack
  335. def test_pop_gluster_vol(self, voldict, used_vols, size, expected):
  336. gmgr = common.GlusterManager
  337. gmgr1 = gmgr(expected, self._execute, None, None)
  338. self._layout._fetch_gluster_volumes = mock.Mock(return_value=voldict)
  339. self._layout.gluster_used_vols = used_vols
  340. self._layout._glustermanager = mock.Mock(return_value=gmgr1)
  341. self._layout.volume_pattern_keys = list(voldict.values())[0].keys()
  342. result = self._layout._pop_gluster_vol(size=size)
  343. self.assertEqual(expected, result)
  344. self.assertIn(result, used_vols)
  345. self._layout._fetch_gluster_volumes.assert_called_once_with()
  346. self._layout._glustermanager.assert_called_once_with(result)
  347. @ddt.data({"voldict": {"share2G": {"size": 2}},
  348. "used_vols": set(), "size": 3},
  349. {"voldict": {"share2G": {"size": 2}},
  350. "used_vols": set(["share2G"]), "size": None})
  351. @ddt.unpack
  352. def test_pop_gluster_vol_excp(self, voldict, used_vols, size):
  353. self._layout._fetch_gluster_volumes = mock.Mock(return_value=voldict)
  354. self._layout.gluster_used_vols = used_vols
  355. self._layout.volume_pattern_keys = list(voldict.values())[0].keys()
  356. self.assertRaises(exception.GlusterfsException,
  357. self._layout._pop_gluster_vol, size=size)
  358. self._layout._fetch_gluster_volumes.assert_called_once_with()
  359. self.assertFalse(
  360. self.fake_driver._setup_via_manager.called)
  361. def test_push_gluster_vol(self):
  362. self._layout.gluster_used_vols = set([
  363. self.glusterfs_target1, self.glusterfs_target2])
  364. self._layout._push_gluster_vol(self.glusterfs_target2)
  365. self.assertEqual(1, len(self._layout.gluster_used_vols))
  366. self.assertFalse(
  367. self.glusterfs_target2 in self._layout.gluster_used_vols)
  368. def test_push_gluster_vol_excp(self):
  369. self._layout.gluster_used_vols = set([self.glusterfs_target1])
  370. self._layout.gluster_unused_vols_dict = {}
  371. self.assertRaises(exception.GlusterfsException,
  372. self._layout._push_gluster_vol,
  373. self.glusterfs_target2)
  374. @ddt.data({'vers_minor': '6',
  375. 'cmd': ['find', '/tmp/tmpKGHKJ', '-mindepth', '1',
  376. '-delete']},
  377. {'vers_minor': '7',
  378. 'cmd': ['find', '/tmp/tmpKGHKJ', '-mindepth', '1', '!',
  379. '-path', '/tmp/tmpKGHKJ/.trashcan', '!', '-path',
  380. '/tmp/tmpKGHKJ/.trashcan/internal_op', '-delete']})
  381. @ddt.unpack
  382. def test_wipe_gluster_vol(self, vers_minor, cmd):
  383. tmpdir = '/tmp/tmpKGHKJ'
  384. gmgr = common.GlusterManager
  385. gmgr1 = gmgr(self.glusterfs_target1, self._execute, None, None)
  386. self._layout.glusterfs_versions = {
  387. self.glusterfs_server1: ('3', vers_minor)}
  388. self.mock_object(tempfile, 'mkdtemp',
  389. mock.Mock(return_value=tmpdir))
  390. self.mock_object(self.fake_driver, '_execute', mock.Mock())
  391. self.mock_object(common, '_mount_gluster_vol', mock.Mock())
  392. self.mock_object(common, '_umount_gluster_vol', mock.Mock())
  393. self.mock_object(shutil, 'rmtree', mock.Mock())
  394. self._layout._wipe_gluster_vol(gmgr1)
  395. tempfile.mkdtemp.assert_called_once_with()
  396. common._mount_gluster_vol.assert_called_once_with(
  397. self.fake_driver._execute, gmgr1.export,
  398. tmpdir)
  399. kwargs = {'run_as_root': True}
  400. self.fake_driver._execute.assert_called_once_with(
  401. *cmd, **kwargs)
  402. common._umount_gluster_vol.assert_called_once_with(
  403. self.fake_driver._execute, tmpdir)
  404. kwargs = {'ignore_errors': True}
  405. shutil.rmtree.assert_called_once_with(tmpdir,
  406. **kwargs)
  407. def test_wipe_gluster_vol_mount_fail(self):
  408. tmpdir = '/tmp/tmpKGHKJ'
  409. gmgr = common.GlusterManager
  410. gmgr1 = gmgr(self.glusterfs_target1, self._execute, None, None)
  411. self._layout.glusterfs_versions = {
  412. self.glusterfs_server1: ('3', '6')}
  413. self.mock_object(tempfile, 'mkdtemp',
  414. mock.Mock(return_value=tmpdir))
  415. self.mock_object(self.fake_driver, '_execute', mock.Mock())
  416. self.mock_object(common, '_mount_gluster_vol',
  417. mock.Mock(side_effect=exception.GlusterfsException))
  418. self.mock_object(common, '_umount_gluster_vol', mock.Mock())
  419. self.mock_object(shutil, 'rmtree', mock.Mock())
  420. self.assertRaises(exception.GlusterfsException,
  421. self._layout._wipe_gluster_vol,
  422. gmgr1)
  423. tempfile.mkdtemp.assert_called_once_with()
  424. common._mount_gluster_vol.assert_called_once_with(
  425. self.fake_driver._execute, gmgr1.export,
  426. tmpdir)
  427. self.assertFalse(self.fake_driver._execute.called)
  428. self.assertFalse(common._umount_gluster_vol.called)
  429. kwargs = {'ignore_errors': True}
  430. shutil.rmtree.assert_called_once_with(tmpdir,
  431. **kwargs)
  432. def test_wipe_gluster_vol_error_wiping_gluster_vol(self):
  433. tmpdir = '/tmp/tmpKGHKJ'
  434. gmgr = common.GlusterManager
  435. gmgr1 = gmgr(self.glusterfs_target1, self._execute, None, None)
  436. self._layout.glusterfs_versions = {
  437. self.glusterfs_server1: ('3', '6')}
  438. cmd = ['find', '/tmp/tmpKGHKJ', '-mindepth', '1', '-delete']
  439. self.mock_object(tempfile, 'mkdtemp',
  440. mock.Mock(return_value=tmpdir))
  441. self.mock_object(
  442. self.fake_driver, '_execute',
  443. mock.Mock(side_effect=exception.ProcessExecutionError))
  444. self.mock_object(common, '_mount_gluster_vol', mock.Mock())
  445. self.mock_object(common, '_umount_gluster_vol', mock.Mock())
  446. self.mock_object(shutil, 'rmtree', mock.Mock())
  447. self.assertRaises(exception.GlusterfsException,
  448. self._layout._wipe_gluster_vol,
  449. gmgr1)
  450. tempfile.mkdtemp.assert_called_once_with()
  451. common._mount_gluster_vol.assert_called_once_with(
  452. self.fake_driver._execute, gmgr1.export,
  453. tmpdir)
  454. kwargs = {'run_as_root': True}
  455. self.fake_driver._execute.assert_called_once_with(
  456. *cmd, **kwargs)
  457. common._umount_gluster_vol.assert_called_once_with(
  458. self.fake_driver._execute, tmpdir)
  459. kwargs = {'ignore_errors': True}
  460. shutil.rmtree.assert_called_once_with(tmpdir,
  461. **kwargs)
  462. def test_create_share(self):
  463. self._layout._pop_gluster_vol = mock.Mock(
  464. return_value=self.glusterfs_target1)
  465. gmgr1 = common.GlusterManager(self.glusterfs_target1)
  466. gmgr1.set_vol_option = mock.Mock()
  467. self.mock_object(self._layout, '_glustermanager',
  468. mock.Mock(return_value=gmgr1))
  469. self.mock_object(self.fake_driver, '_setup_via_manager',
  470. mock.Mock(return_value='host1:/gv1'))
  471. share = new_share()
  472. exp_locn = self._layout.create_share(self._context, share)
  473. self._layout._pop_gluster_vol.assert_called_once_with(share['size'])
  474. self.fake_driver._setup_via_manager.assert_called_once_with(
  475. {'manager': gmgr1, 'share': share})
  476. self._layout.private_storage.update.assert_called_once_with(
  477. share['id'], {'volume': self.glusterfs_target1})
  478. gmgr1.set_vol_option.assert_called_once_with(
  479. 'user.manila-share', share['id'])
  480. self.assertEqual('host1:/gv1', exp_locn)
  481. def test_create_share_error(self):
  482. self._layout._pop_gluster_vol = mock.Mock(
  483. side_effect=exception.GlusterfsException)
  484. share = new_share()
  485. self.assertRaises(exception.GlusterfsException,
  486. self._layout.create_share, self._context, share)
  487. self._layout._pop_gluster_vol.assert_called_once_with(
  488. share['size'])
  489. @ddt.data(None, '', 'Eeyore')
  490. def test_delete_share(self, clone_of):
  491. self._layout._push_gluster_vol = mock.Mock()
  492. self._layout._wipe_gluster_vol = mock.Mock()
  493. gmgr = common.GlusterManager
  494. gmgr1 = gmgr(self.glusterfs_target1, self._execute, None, None)
  495. gmgr1.set_vol_option = mock.Mock()
  496. gmgr1.get_vol_option = mock.Mock(return_value=clone_of)
  497. self.mock_object(self._layout, '_glustermanager',
  498. mock.Mock(return_value=gmgr1))
  499. self._layout.gluster_used_vols = set([self.glusterfs_target1])
  500. self._layout.delete_share(self._context, self.share1)
  501. gmgr1.get_vol_option.assert_called_once_with(
  502. 'user.manila-cloned-from')
  503. self._layout._wipe_gluster_vol.assert_called_once_with(gmgr1)
  504. self._layout._push_gluster_vol.assert_called_once_with(
  505. self.glusterfs_target1)
  506. self._layout.private_storage.delete.assert_called_once_with(
  507. self.share1['id'])
  508. gmgr1.set_vol_option.assert_has_calls([
  509. mock.call('user.manila-share', 'NONE'),
  510. mock.call('nfs.disable', 'on')
  511. ])
  512. def test_delete_share_clone(self):
  513. self._layout._push_gluster_vol = mock.Mock()
  514. self._layout._wipe_gluster_vol = mock.Mock()
  515. gmgr = common.GlusterManager
  516. gmgr1 = gmgr(self.glusterfs_target1, self._execute, None, None)
  517. gmgr1.gluster_call = mock.Mock()
  518. gmgr1.get_vol_option = mock.Mock(return_value=FAKE_UUID1)
  519. self.mock_object(self._layout, '_glustermanager',
  520. mock.Mock(return_value=gmgr1))
  521. self._layout.gluster_used_vols = set([self.glusterfs_target1])
  522. self._layout.delete_share(self._context, self.share1)
  523. gmgr1.get_vol_option.assert_called_once_with(
  524. 'user.manila-cloned-from')
  525. self.assertFalse(self._layout._wipe_gluster_vol.called)
  526. self._layout._push_gluster_vol.assert_called_once_with(
  527. self.glusterfs_target1)
  528. self._layout.private_storage.delete.assert_called_once_with(
  529. self.share1['id'])
  530. gmgr1.gluster_call.assert_called_once_with(
  531. 'volume', 'delete', 'gv1')
  532. def test_delete_share_error(self):
  533. self._layout._wipe_gluster_vol = mock.Mock()
  534. self._layout._wipe_gluster_vol.side_effect = (
  535. exception.GlusterfsException)
  536. self._layout._push_gluster_vol = mock.Mock()
  537. gmgr = common.GlusterManager
  538. gmgr1 = gmgr(self.glusterfs_target1, self._execute, None, None)
  539. gmgr1.get_vol_option = mock.Mock(return_value=None)
  540. self.mock_object(self._layout, '_glustermanager',
  541. mock.Mock(return_value=gmgr1))
  542. self._layout.gluster_used_vols = set([self.glusterfs_target1])
  543. self.assertRaises(exception.GlusterfsException,
  544. self._layout.delete_share, self._context,
  545. self.share1)
  546. self._layout._wipe_gluster_vol.assert_called_once_with(gmgr1)
  547. self.assertFalse(self._layout._push_gluster_vol.called)
  548. def test_delete_share_missing_record(self):
  549. self.mock_object(self._layout, '_share_manager',
  550. mock.Mock(return_value=None))
  551. self._layout.delete_share(self._context, self.share1)
  552. self._layout._share_manager.assert_called_once_with(self.share1)
  553. def test_create_snapshot(self):
  554. self._layout.gluster_nosnap_vols_dict = {}
  555. self._layout.glusterfs_versions = {self.glusterfs_server1: ('3', '6')}
  556. gmgr = common.GlusterManager
  557. gmgr1 = gmgr(self.glusterfs_target1, self._execute, None, None)
  558. self._layout.gluster_used_vols = set([self.glusterfs_target1])
  559. self.mock_object(gmgr1, 'gluster_call',
  560. mock.Mock(
  561. side_effect=(glusterXMLOut(ret=0, errno=0),)))
  562. self.mock_object(self._layout, '_glustermanager',
  563. mock.Mock(return_value=gmgr1))
  564. snapshot = {
  565. 'id': 'fake_snap_id',
  566. 'share_id': self.share1['id'],
  567. 'share': self.share1
  568. }
  569. ret = self._layout.create_snapshot(self._context, snapshot)
  570. self.assertIsNone(ret)
  571. args = ('--xml', 'snapshot', 'create', 'manila-fake_snap_id',
  572. gmgr1.volume)
  573. gmgr1.gluster_call.assert_called_once_with(*args, log=mock.ANY)
  574. @ddt.data({'side_effect': (glusterXMLOut(ret=-1, errno=2),),
  575. '_exception': exception.GlusterfsException},
  576. {'side_effect': (('', ''),),
  577. '_exception': exception.GlusterfsException})
  578. @ddt.unpack
  579. def test_create_snapshot_error(self, side_effect, _exception):
  580. self._layout.gluster_nosnap_vols_dict = {}
  581. self._layout.glusterfs_versions = {self.glusterfs_server1: ('3', '6')}
  582. gmgr = common.GlusterManager
  583. gmgr1 = gmgr(self.glusterfs_target1, self._execute, None, None)
  584. self._layout.gluster_used_vols = set([self.glusterfs_target1])
  585. self.mock_object(gmgr1, 'gluster_call',
  586. mock.Mock(side_effect=side_effect))
  587. self.mock_object(self._layout, '_glustermanager',
  588. mock.Mock(return_value=gmgr1))
  589. snapshot = {
  590. 'id': 'fake_snap_id',
  591. 'share_id': self.share1['id'],
  592. 'share': self.share1
  593. }
  594. self.assertRaises(_exception, self._layout.create_snapshot,
  595. self._context, snapshot)
  596. args = ('--xml', 'snapshot', 'create', 'manila-fake_snap_id',
  597. gmgr1.volume)
  598. gmgr1.gluster_call.assert_called_once_with(*args, log=mock.ANY)
  599. @ddt.data({"vers_minor": '6', "exctype": exception.GlusterfsException},
  600. {"vers_minor": '7',
  601. "exctype": exception.ShareSnapshotNotSupported})
  602. @ddt.unpack
  603. def test_create_snapshot_no_snap(self, vers_minor, exctype):
  604. self._layout.gluster_nosnap_vols_dict = {}
  605. self._layout.glusterfs_versions = {
  606. self.glusterfs_server1: ('3', vers_minor)}
  607. gmgr = common.GlusterManager
  608. gmgr1 = gmgr(self.glusterfs_target1, self._execute, None, None)
  609. self._layout.gluster_used_vols = set([self.glusterfs_target1])
  610. self.mock_object(gmgr1, 'gluster_call',
  611. mock.Mock(
  612. side_effect=(glusterXMLOut(ret=-1, errno=0),)))
  613. self.mock_object(self._layout, '_glustermanager',
  614. mock.Mock(return_value=gmgr1))
  615. snapshot = {
  616. 'id': 'fake_snap_id',
  617. 'share_id': self.share1['id'],
  618. 'share': self.share1
  619. }
  620. self.assertRaises(exctype, self._layout.create_snapshot, self._context,
  621. snapshot)
  622. args = ('--xml', 'snapshot', 'create', 'manila-fake_snap_id',
  623. gmgr1.volume)
  624. gmgr1.gluster_call.assert_called_once_with(*args, log=mock.ANY)
  625. @ddt.data({"vers_minor": '6', "exctype": exception.GlusterfsException},
  626. {"vers_minor": '7',
  627. "exctype": exception.ShareSnapshotNotSupported})
  628. @ddt.unpack
  629. def test_create_snapshot_no_snap_cached(self, vers_minor, exctype):
  630. self._layout.gluster_nosnap_vols_dict = {
  631. self.glusterfs_target1: 'fake error'}
  632. self._layout.glusterfs_versions = {
  633. self.glusterfs_server1: ('3', vers_minor)}
  634. self._layout.gluster_used_vols = set([self.glusterfs_target1])
  635. gmgr = common.GlusterManager
  636. gmgr1 = gmgr(self.glusterfs_target1, self._execute, None, None)
  637. self.mock_object(self._layout, '_share_manager',
  638. mock.Mock(return_value=gmgr1))
  639. snapshot = {
  640. 'id': 'fake_snap_id',
  641. 'share_id': self.share1['id'],
  642. 'share': self.share1
  643. }
  644. self.assertRaises(exctype, self._layout.create_snapshot, self._context,
  645. snapshot)
  646. def test_find_actual_backend_snapshot_name(self):
  647. gmgr = common.GlusterManager
  648. gmgr1 = gmgr(self.share1['export_location'], self._execute, None, None)
  649. self.mock_object(gmgr1, 'gluster_call',
  650. mock.Mock(return_value=('fake_snap_id_xyz', '')))
  651. snapshot = {
  652. 'id': 'fake_snap_id',
  653. 'share_id': self.share1['id'],
  654. 'share': self.share1
  655. }
  656. ret = self._layout._find_actual_backend_snapshot_name(gmgr1, snapshot)
  657. args = ('snapshot', 'list', gmgr1.volume, '--mode=script')
  658. gmgr1.gluster_call.assert_called_once_with(*args, log=mock.ANY)
  659. self.assertEqual('fake_snap_id_xyz', ret)
  660. @ddt.data('this is too bad', 'fake_snap_id_xyx\nfake_snap_id_pqr')
  661. def test_find_actual_backend_snapshot_name_bad_snap_list(self, snaplist):
  662. gmgr = common.GlusterManager
  663. gmgr1 = gmgr(self.share1['export_location'], self._execute, None, None)
  664. self.mock_object(gmgr1, 'gluster_call',
  665. mock.Mock(return_value=(snaplist, '')))
  666. snapshot = {
  667. 'id': 'fake_snap_id',
  668. 'share_id': self.share1['id'],
  669. 'share': self.share1
  670. }
  671. self.assertRaises(exception.GlusterfsException,
  672. self._layout._find_actual_backend_snapshot_name,
  673. gmgr1, snapshot)
  674. args = ('snapshot', 'list', gmgr1.volume, '--mode=script')
  675. gmgr1.gluster_call.assert_called_once_with(*args, log=mock.ANY)
  676. @ddt.data({'glusterfs_target': 'root@host1:/gv1',
  677. 'glusterfs_server': 'root@host1'},
  678. {'glusterfs_target': 'host1:/gv1',
  679. 'glusterfs_server': 'host1'})
  680. @ddt.unpack
  681. def test_create_share_from_snapshot(self, glusterfs_target,
  682. glusterfs_server):
  683. share = new_share()
  684. snapshot = {
  685. 'id': 'fake_snap_id',
  686. 'share_instance': new_share(export_location=glusterfs_target),
  687. 'share_id': 'fake_share_id',
  688. }
  689. volume = ''.join(['manila-', share['id']])
  690. new_vol_addr = ':/'.join([glusterfs_server, volume])
  691. gmgr = common.GlusterManager
  692. old_gmgr = gmgr(glusterfs_target, self._execute, None, None)
  693. new_gmgr = gmgr(new_vol_addr, self._execute, None, None)
  694. self._layout.gluster_used_vols = set([glusterfs_target])
  695. self._layout.glusterfs_versions = {glusterfs_server: ('3', '7')}
  696. self.mock_object(old_gmgr, 'gluster_call',
  697. mock.Mock(side_effect=[('', ''), ('', '')]))
  698. self.mock_object(new_gmgr, 'gluster_call',
  699. mock.Mock(side_effect=[('', ''), ('', ''), ('', '')]))
  700. self.mock_object(new_gmgr, 'get_vol_option',
  701. mock.Mock())
  702. new_gmgr.get_vol_option.return_value = (
  703. 'glusterfs-server-1,client')
  704. self.mock_object(self._layout, '_find_actual_backend_snapshot_name',
  705. mock.Mock(return_value='fake_snap_id_xyz'))
  706. self.mock_object(self._layout, '_share_manager',
  707. mock.Mock(return_value=old_gmgr))
  708. self.mock_object(self._layout, '_glustermanager',
  709. mock.Mock(return_value=new_gmgr))
  710. self.mock_object(self.fake_driver, '_setup_via_manager',
  711. mock.Mock(return_value='host1:/gv1'))
  712. ret = self._layout.create_share_from_snapshot(
  713. self._context, share, snapshot, None)
  714. (self._layout._find_actual_backend_snapshot_name.
  715. assert_called_once_with(old_gmgr, snapshot))
  716. args = (('snapshot', 'activate', 'fake_snap_id_xyz',
  717. 'force', '--mode=script'),
  718. ('snapshot', 'clone', volume, 'fake_snap_id_xyz'))
  719. old_gmgr.gluster_call.assert_has_calls(
  720. [mock.call(*a, log=mock.ANY) for a in args])
  721. args = (('volume', 'start', volume),
  722. ('volume', 'set', volume, 'user.manila-share', share['id']),
  723. ('volume', 'set', volume, 'user.manila-cloned-from',
  724. snapshot['share_id']))
  725. new_gmgr.gluster_call.assert_has_calls(
  726. [mock.call(*a, log=mock.ANY) for a in args], any_order=True)
  727. self._layout._share_manager.assert_called_once_with(
  728. snapshot['share_instance'])
  729. self._layout._glustermanager.assert_called_once_with(
  730. gmgr.parse(new_vol_addr))
  731. self._layout.driver._setup_via_manager.assert_called_once_with(
  732. {'manager': new_gmgr, 'share': share},
  733. {'manager': old_gmgr, 'share': snapshot['share_instance']})
  734. self._layout.private_storage.update.assert_called_once_with(
  735. share['id'], {'volume': new_vol_addr})
  736. self.assertIn(
  737. new_vol_addr,
  738. self._layout.gluster_used_vols)
  739. self.assertEqual('host1:/gv1', ret)
  740. def test_create_share_from_snapshot_error_unsupported_gluster_version(
  741. self):
  742. glusterfs_target = 'root@host1:/gv1'
  743. glusterfs_server = 'root@host1'
  744. share = new_share()
  745. volume = ''.join(['manila-', share['id']])
  746. new_vol_addr = ':/'.join([glusterfs_server, volume])
  747. gmgr = common.GlusterManager
  748. old_gmgr = gmgr(glusterfs_target, self._execute, None, None)
  749. new_gmgr = gmgr(new_vol_addr, self._execute, None, None)
  750. self._layout.gluster_used_vols_dict = {glusterfs_target: old_gmgr}
  751. self._layout.glusterfs_versions = {glusterfs_server: ('3', '6')}
  752. self.mock_object(
  753. old_gmgr, 'gluster_call',
  754. mock.Mock(side_effect=[('', ''), ('', '')]))
  755. self.mock_object(new_gmgr, 'get_vol_option',
  756. mock.Mock())
  757. new_gmgr.get_vol_option.return_value = (
  758. 'glusterfs-server-1,client')
  759. self.mock_object(self._layout, '_find_actual_backend_snapshot_name',
  760. mock.Mock(return_value='fake_snap_id_xyz'))
  761. self.mock_object(self._layout, '_share_manager',
  762. mock.Mock(return_value=old_gmgr))
  763. self.mock_object(self._layout, '_glustermanager',
  764. mock.Mock(return_value=new_gmgr))
  765. snapshot = {
  766. 'id': 'fake_snap_id',
  767. 'share_instance': new_share(export_location=glusterfs_target)
  768. }
  769. self.assertRaises(exception.GlusterfsException,
  770. self._layout.create_share_from_snapshot,
  771. self._context, share, snapshot)
  772. self.assertFalse(
  773. self._layout._find_actual_backend_snapshot_name.called)
  774. self.assertFalse(old_gmgr.gluster_call.called)
  775. self._layout._share_manager.assert_called_once_with(
  776. snapshot['share_instance'])
  777. self.assertFalse(self._layout._glustermanager.called)
  778. self.assertFalse(new_gmgr.get_vol_option.called)
  779. self.assertFalse(new_gmgr.gluster_call.called)
  780. self.assertNotIn(new_vol_addr,
  781. self._layout.glusterfs_versions.keys())
  782. def test_delete_snapshot(self):
  783. self._layout.gluster_nosnap_vols_dict = {}
  784. gmgr = common.GlusterManager
  785. gmgr1 = gmgr(self.share1['export_location'], self._execute, None, None)
  786. self._layout.gluster_used_vols = set([self.glusterfs_target1])
  787. self.mock_object(self._layout, '_find_actual_backend_snapshot_name',
  788. mock.Mock(return_value='fake_snap_id_xyz'))
  789. self.mock_object(
  790. gmgr1, 'gluster_call',
  791. mock.Mock(return_value=glusterXMLOut(ret=0, errno=0)))
  792. self.mock_object(self._layout, '_glustermanager',
  793. mock.Mock(return_value=gmgr1))
  794. snapshot = {
  795. 'id': 'fake_snap_id',
  796. 'share_id': self.share1['id'],
  797. 'share': self.share1
  798. }
  799. ret = self._layout.delete_snapshot(self._context, snapshot)
  800. self.assertIsNone(ret)
  801. args = ('--xml', 'snapshot', 'delete', 'fake_snap_id_xyz',
  802. '--mode=script')
  803. gmgr1.gluster_call.assert_called_once_with(*args, log=mock.ANY)
  804. (self._layout._find_actual_backend_snapshot_name.
  805. assert_called_once_with(gmgr1, snapshot))
  806. @ddt.data({'side_effect': (glusterXMLOut(ret=-1, errno=0),),
  807. '_exception': exception.GlusterfsException},
  808. {'side_effect': (('', ''),),
  809. '_exception': exception.GlusterfsException})
  810. @ddt.unpack
  811. def test_delete_snapshot_error(self, side_effect, _exception):
  812. self._layout.gluster_nosnap_vols_dict = {}
  813. gmgr = common.GlusterManager
  814. gmgr1 = gmgr(self.share1['export_location'], self._execute, None, None)
  815. self._layout.gluster_used_vols = set([self.glusterfs_target1])
  816. self.mock_object(self._layout, '_find_actual_backend_snapshot_name',
  817. mock.Mock(return_value='fake_snap_id_xyz'))
  818. args = ('--xml', 'snapshot', 'delete', 'fake_snap_id_xyz',
  819. '--mode=script')
  820. self.mock_object(
  821. gmgr1, 'gluster_call',
  822. mock.Mock(side_effect=side_effect))
  823. self.mock_object(self._layout, '_glustermanager',
  824. mock.Mock(return_value=gmgr1))
  825. snapshot = {
  826. 'id': 'fake_snap_id',
  827. 'share_id': self.share1['id'],
  828. 'share': self.share1
  829. }
  830. self.assertRaises(_exception, self._layout.delete_snapshot,
  831. self._context, snapshot)
  832. gmgr1.gluster_call.assert_called_once_with(*args, log=mock.ANY)
  833. (self._layout._find_actual_backend_snapshot_name.
  834. assert_called_once_with(gmgr1, snapshot))
  835. @ddt.data(
  836. ('manage_existing', ('share', 'driver_options'), {}),
  837. ('unmanage', ('share',), {}),
  838. ('extend_share', ('share', 'new_size'), {'share_server': None}),
  839. ('shrink_share', ('share', 'new_size'), {'share_server': None}))
  840. def test_nonimplemented_methods(self, method_invocation):
  841. method, args, kwargs = method_invocation
  842. self.assertRaises(NotImplementedError, getattr(self._layout, method),
  843. *args, **kwargs)