OpenStack Block Storage (Cinder)
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.

test_remotefs.py 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576
  1. # Copyright 2014 Cloudbase Solutions Srl
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  4. # not use this file except in compliance with the License. You may obtain
  5. # a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  11. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  12. # License for the specific language governing permissions and limitations
  13. # under the License.
  14. import collections
  15. import copy
  16. import os
  17. import ddt
  18. import mock
  19. from cinder import context
  20. from cinder import exception
  21. from cinder.image import image_utils
  22. from cinder import test
  23. from cinder.tests.unit import fake_snapshot
  24. from cinder.tests.unit import fake_volume
  25. from cinder import utils
  26. from cinder.volume.drivers import remotefs
  27. @ddt.ddt
  28. class RemoteFsSnapDriverTestCase(test.TestCase):
  29. _FAKE_MNT_POINT = '/mnt/fake_hash'
  30. def setUp(self):
  31. super(RemoteFsSnapDriverTestCase, self).setUp()
  32. self._driver = remotefs.RemoteFSSnapDriver()
  33. self._driver._remotefsclient = mock.Mock()
  34. self._driver._execute = mock.Mock()
  35. self._driver._delete = mock.Mock()
  36. self.context = context.get_admin_context()
  37. self._fake_volume = fake_volume.fake_volume_obj(
  38. self.context, provider_location='fake_share')
  39. self._fake_volume_path = os.path.join(self._FAKE_MNT_POINT,
  40. self._fake_volume.name)
  41. self._fake_snapshot = fake_snapshot.fake_snapshot_obj(self.context)
  42. self._fake_snapshot_path = (self._fake_volume_path + '.' +
  43. self._fake_snapshot.id)
  44. self._fake_snapshot.volume = self._fake_volume
  45. def _test_delete_snapshot(self, volume_in_use=False,
  46. stale_snapshot=False,
  47. is_active_image=True):
  48. # If the snapshot is not the active image, it is guaranteed that
  49. # another snapshot exists having it as backing file.
  50. fake_snapshot_name = os.path.basename(self._fake_snapshot_path)
  51. fake_info = {'active': fake_snapshot_name,
  52. self._fake_snapshot.id: fake_snapshot_name}
  53. fake_snap_img_info = mock.Mock()
  54. fake_base_img_info = mock.Mock()
  55. if stale_snapshot:
  56. fake_snap_img_info.backing_file = None
  57. else:
  58. fake_snap_img_info.backing_file = self._fake_volume.name
  59. fake_snap_img_info.file_format = 'qcow2'
  60. fake_base_img_info.backing_file = None
  61. fake_base_img_info.file_format = 'raw'
  62. self._driver._local_path_volume_info = mock.Mock(
  63. return_value=mock.sentinel.fake_info_path)
  64. self._driver._qemu_img_info = mock.Mock(
  65. side_effect=[fake_snap_img_info, fake_base_img_info])
  66. self._driver._local_volume_dir = mock.Mock(
  67. return_value=self._FAKE_MNT_POINT)
  68. self._driver._read_info_file = mock.Mock()
  69. self._driver._write_info_file = mock.Mock()
  70. self._driver._img_commit = mock.Mock()
  71. self._driver._rebase_img = mock.Mock()
  72. self._driver._ensure_share_writable = mock.Mock()
  73. self._driver._delete_stale_snapshot = mock.Mock()
  74. self._driver._delete_snapshot_online = mock.Mock()
  75. expected_info = {
  76. 'active': fake_snapshot_name,
  77. self._fake_snapshot.id: fake_snapshot_name
  78. }
  79. if volume_in_use:
  80. self._fake_snapshot.volume.status = 'in-use'
  81. self._driver._read_info_file.return_value = fake_info
  82. self._driver._delete_snapshot(self._fake_snapshot)
  83. if stale_snapshot:
  84. self._driver._delete_stale_snapshot.assert_called_once_with(
  85. self._fake_snapshot)
  86. else:
  87. expected_online_delete_info = {
  88. 'active_file': fake_snapshot_name,
  89. 'snapshot_file': fake_snapshot_name,
  90. 'base_file': self._fake_volume.name,
  91. 'base_id': None,
  92. 'new_base_file': None
  93. }
  94. self._driver._delete_snapshot_online.assert_called_once_with(
  95. self.context, self._fake_snapshot,
  96. expected_online_delete_info)
  97. elif is_active_image:
  98. self._driver._read_info_file.return_value = fake_info
  99. self._driver._delete_snapshot(self._fake_snapshot)
  100. self._driver._img_commit.assert_called_once_with(
  101. self._fake_snapshot_path)
  102. self.assertNotIn(self._fake_snapshot.id, fake_info)
  103. self._driver._write_info_file.assert_called_once_with(
  104. mock.sentinel.fake_info_path, fake_info)
  105. else:
  106. fake_upper_snap_id = 'fake_upper_snap_id'
  107. fake_upper_snap_path = (
  108. self._fake_volume_path + '-snapshot' + fake_upper_snap_id)
  109. fake_upper_snap_name = os.path.basename(fake_upper_snap_path)
  110. fake_backing_chain = [
  111. {'filename': fake_upper_snap_name,
  112. 'backing-filename': fake_snapshot_name},
  113. {'filename': fake_snapshot_name,
  114. 'backing-filename': self._fake_volume.name},
  115. {'filename': self._fake_volume.name,
  116. 'backing-filename': None}]
  117. fake_info[fake_upper_snap_id] = fake_upper_snap_name
  118. fake_info[self._fake_snapshot.id] = fake_snapshot_name
  119. fake_info['active'] = fake_upper_snap_name
  120. expected_info = copy.deepcopy(fake_info)
  121. del expected_info[self._fake_snapshot.id]
  122. self._driver._read_info_file.return_value = fake_info
  123. self._driver._get_backing_chain_for_path = mock.Mock(
  124. return_value=fake_backing_chain)
  125. self._driver._delete_snapshot(self._fake_snapshot)
  126. self._driver._img_commit.assert_called_once_with(
  127. self._fake_snapshot_path)
  128. self._driver._rebase_img.assert_called_once_with(
  129. fake_upper_snap_path, self._fake_volume.name,
  130. fake_base_img_info.file_format)
  131. self._driver._write_info_file.assert_called_once_with(
  132. mock.sentinel.fake_info_path, expected_info)
  133. def test_delete_snapshot_when_active_file(self):
  134. self._test_delete_snapshot()
  135. def test_delete_snapshot_in_use(self):
  136. self._test_delete_snapshot(volume_in_use=True)
  137. def test_delete_snapshot_in_use_stale_snapshot(self):
  138. self._test_delete_snapshot(volume_in_use=True,
  139. stale_snapshot=True)
  140. def test_delete_snapshot_with_one_upper_file(self):
  141. self._test_delete_snapshot(is_active_image=False)
  142. def test_delete_stale_snapshot(self):
  143. fake_snapshot_name = os.path.basename(self._fake_snapshot_path)
  144. fake_snap_info = {
  145. 'active': self._fake_volume.name,
  146. self._fake_snapshot.id: fake_snapshot_name
  147. }
  148. expected_info = {'active': self._fake_volume.name}
  149. self._driver._local_path_volume_info = mock.Mock(
  150. return_value=mock.sentinel.fake_info_path)
  151. self._driver._read_info_file = mock.Mock(
  152. return_value=fake_snap_info)
  153. self._driver._local_volume_dir = mock.Mock(
  154. return_value=self._FAKE_MNT_POINT)
  155. self._driver._write_info_file = mock.Mock()
  156. self._driver._delete_stale_snapshot(self._fake_snapshot)
  157. self._driver._delete.assert_called_once_with(self._fake_snapshot_path)
  158. self._driver._write_info_file.assert_called_once_with(
  159. mock.sentinel.fake_info_path, expected_info)
  160. @mock.patch.object(remotefs.RemoteFSDriver,
  161. 'secure_file_operations_enabled',
  162. return_value=True)
  163. @mock.patch.object(os, 'stat')
  164. def test_do_create_snapshot(self, _mock_stat, _mock_sec_enabled):
  165. self._driver._local_volume_dir = mock.Mock(
  166. return_value=self._fake_volume_path)
  167. fake_backing_path = os.path.join(
  168. self._driver._local_volume_dir(),
  169. self._fake_volume.name)
  170. self._driver._execute = mock.Mock()
  171. self._driver._set_rw_permissions = mock.Mock()
  172. self._driver._qemu_img_info = mock.Mock(
  173. return_value=mock.Mock(file_format=mock.sentinel.backing_fmt))
  174. self._driver._do_create_snapshot(self._fake_snapshot,
  175. self._fake_volume.name,
  176. self._fake_snapshot_path)
  177. command1 = ['qemu-img', 'create', '-f', 'qcow2', '-o',
  178. 'backing_file=%s,backing_fmt=%s' %
  179. (fake_backing_path,
  180. mock.sentinel.backing_fmt),
  181. self._fake_snapshot_path,
  182. "%dG" % self._fake_volume.size]
  183. command2 = ['qemu-img', 'rebase', '-u',
  184. '-b', self._fake_volume.name,
  185. '-F', mock.sentinel.backing_fmt,
  186. self._fake_snapshot_path]
  187. command3 = ['chown', '--reference=%s' % fake_backing_path,
  188. self._fake_snapshot_path]
  189. calls = [mock.call(*command1, run_as_root=True),
  190. mock.call(*command2, run_as_root=True),
  191. mock.call(*command3, run_as_root=True)]
  192. self._driver._execute.assert_has_calls(calls)
  193. def _test_create_snapshot(self, volume_in_use=False):
  194. fake_snapshot_info = {}
  195. fake_snapshot_file_name = os.path.basename(self._fake_snapshot_path)
  196. self._driver._local_path_volume_info = mock.Mock(
  197. return_value=mock.sentinel.fake_info_path)
  198. self._driver._read_info_file = mock.Mock(
  199. return_value=fake_snapshot_info)
  200. self._driver._do_create_snapshot = mock.Mock()
  201. self._driver._create_snapshot_online = mock.Mock()
  202. self._driver._write_info_file = mock.Mock()
  203. self._driver.get_active_image_from_info = mock.Mock(
  204. return_value=self._fake_volume.name)
  205. self._driver._get_new_snap_path = mock.Mock(
  206. return_value=self._fake_snapshot_path)
  207. expected_snapshot_info = {
  208. 'active': fake_snapshot_file_name,
  209. self._fake_snapshot.id: fake_snapshot_file_name
  210. }
  211. if volume_in_use:
  212. self._fake_snapshot.volume.status = 'in-use'
  213. expected_method_called = '_create_snapshot_online'
  214. else:
  215. self._fake_snapshot.volume.status = 'available'
  216. expected_method_called = '_do_create_snapshot'
  217. self._driver._create_snapshot(self._fake_snapshot)
  218. fake_method = getattr(self._driver, expected_method_called)
  219. fake_method.assert_called_with(
  220. self._fake_snapshot, self._fake_volume.name,
  221. self._fake_snapshot_path)
  222. self._driver._write_info_file.assert_called_with(
  223. mock.sentinel.fake_info_path,
  224. expected_snapshot_info)
  225. def test_create_snapshot_volume_available(self):
  226. self._test_create_snapshot()
  227. def test_create_snapshot_volume_in_use(self):
  228. self._test_create_snapshot(volume_in_use=True)
  229. def test_create_snapshot_invalid_volume(self):
  230. self._fake_snapshot.volume.status = 'error'
  231. self.assertRaises(exception.InvalidVolume,
  232. self._driver._create_snapshot,
  233. self._fake_snapshot)
  234. @mock.patch('cinder.db.snapshot_get')
  235. @mock.patch('time.sleep')
  236. def test_create_snapshot_online_with_concurrent_delete(
  237. self, mock_sleep, mock_snapshot_get):
  238. self._driver._nova = mock.Mock()
  239. # Test what happens when progress is so slow that someone
  240. # decides to delete the snapshot while the last known status is
  241. # "creating".
  242. mock_snapshot_get.side_effect = [
  243. {'status': 'creating', 'progress': '42%'},
  244. {'status': 'creating', 'progress': '45%'},
  245. {'status': 'deleting'},
  246. ]
  247. with mock.patch.object(self._driver, '_do_create_snapshot') as \
  248. mock_do_create_snapshot:
  249. self.assertRaises(exception.RemoteFSConcurrentRequest,
  250. self._driver._create_snapshot_online,
  251. self._fake_snapshot,
  252. self._fake_volume.name,
  253. self._fake_snapshot_path)
  254. mock_do_create_snapshot.assert_called_once_with(
  255. self._fake_snapshot, self._fake_volume.name,
  256. self._fake_snapshot_path)
  257. self.assertEqual([mock.call(1), mock.call(1)],
  258. mock_sleep.call_args_list)
  259. self.assertEqual(3, mock_snapshot_get.call_count)
  260. mock_snapshot_get.assert_called_with(self._fake_snapshot._context,
  261. self._fake_snapshot.id)
  262. @mock.patch.object(utils, 'synchronized')
  263. def _locked_volume_operation_test_helper(self, mock_synchronized, func,
  264. expected_exception=False,
  265. *args, **kwargs):
  266. def mock_decorator(*args, **kwargs):
  267. def mock_inner(f):
  268. return f
  269. return mock_inner
  270. mock_synchronized.side_effect = mock_decorator
  271. expected_lock = '%s-%s' % (self._driver.driver_prefix,
  272. self._fake_volume.id)
  273. if expected_exception:
  274. self.assertRaises(expected_exception, func,
  275. self._driver,
  276. *args, **kwargs)
  277. else:
  278. ret_val = func(self._driver, *args, **kwargs)
  279. mock_synchronized.assert_called_with(expected_lock,
  280. external=False)
  281. self.assertEqual(mock.sentinel.ret_val, ret_val)
  282. def test_locked_volume_id_operation(self):
  283. mock_volume = mock.Mock()
  284. mock_volume.id = self._fake_volume.id
  285. @remotefs.locked_volume_id_operation
  286. def synchronized_func(inst, volume):
  287. return mock.sentinel.ret_val
  288. self._locked_volume_operation_test_helper(func=synchronized_func,
  289. volume=mock_volume)
  290. def test_locked_volume_id_snapshot_operation(self):
  291. mock_snapshot = mock.Mock()
  292. mock_snapshot.volume.id = self._fake_volume.id
  293. @remotefs.locked_volume_id_operation
  294. def synchronized_func(inst, snapshot):
  295. return mock.sentinel.ret_val
  296. self._locked_volume_operation_test_helper(func=synchronized_func,
  297. snapshot=mock_snapshot)
  298. def test_locked_volume_id_operation_exception(self):
  299. @remotefs.locked_volume_id_operation
  300. def synchronized_func(inst):
  301. return mock.sentinel.ret_val
  302. self._locked_volume_operation_test_helper(
  303. func=synchronized_func,
  304. expected_exception=exception.VolumeBackendAPIException)
  305. @mock.patch.object(image_utils, 'qemu_img_info')
  306. @mock.patch('os.path.basename')
  307. def _test_qemu_img_info(self, mock_basename,
  308. mock_qemu_img_info, backing_file, basedir,
  309. valid_backing_file=True):
  310. fake_vol_name = 'fake_vol_name'
  311. mock_info = mock_qemu_img_info.return_value
  312. mock_info.image = mock.sentinel.image_path
  313. mock_info.backing_file = backing_file
  314. self._driver._VALID_IMAGE_EXTENSIONS = ['vhd', 'vhdx', 'raw', 'qcow2']
  315. mock_basename.side_effect = [mock.sentinel.image_basename,
  316. mock.sentinel.backing_file_basename]
  317. if valid_backing_file:
  318. img_info = self._driver._qemu_img_info_base(
  319. mock.sentinel.image_path, fake_vol_name, basedir)
  320. self.assertEqual(mock_info, img_info)
  321. self.assertEqual(mock.sentinel.image_basename,
  322. mock_info.image)
  323. expected_basename_calls = [mock.call(mock.sentinel.image_path)]
  324. if backing_file:
  325. self.assertEqual(mock.sentinel.backing_file_basename,
  326. mock_info.backing_file)
  327. expected_basename_calls.append(mock.call(backing_file))
  328. mock_basename.assert_has_calls(expected_basename_calls)
  329. else:
  330. self.assertRaises(exception.RemoteFSException,
  331. self._driver._qemu_img_info_base,
  332. mock.sentinel.image_path,
  333. fake_vol_name, basedir)
  334. mock_qemu_img_info.assert_called_with(mock.sentinel.image_path,
  335. run_as_root=True)
  336. @ddt.data([None, '/fake_basedir'],
  337. ['/fake_basedir/cb2016/fake_vol_name', '/fake_basedir'],
  338. ['/fake_basedir/cb2016/fake_vol_name.vhd', '/fake_basedir'],
  339. ['/fake_basedir/cb2016/fake_vol_name.404f-404',
  340. '/fake_basedir'],
  341. ['/fake_basedir/cb2016/fake_vol_name.tmp-snap-404f-404',
  342. '/fake_basedir'])
  343. @ddt.unpack
  344. def test_qemu_img_info_valid_backing_file(self, backing_file, basedir):
  345. self._test_qemu_img_info(backing_file=backing_file,
  346. basedir=basedir)
  347. @ddt.data(['/other_random_path', '/fake_basedir'],
  348. ['/other_basedir/cb2016/fake_vol_name', '/fake_basedir'],
  349. ['/fake_basedir/invalid_hash/fake_vol_name', '/fake_basedir'],
  350. ['/fake_basedir/cb2016/invalid_vol_name', '/fake_basedir'],
  351. ['/fake_basedir/cb2016/fake_vol_name.info', '/fake_basedir'],
  352. ['/fake_basedir/cb2016/fake_vol_name-random-suffix',
  353. '/fake_basedir'],
  354. ['/fake_basedir/cb2016/fake_vol_name.invalidext',
  355. '/fake_basedir'])
  356. @ddt.unpack
  357. def test_qemu_img_info_invalid_backing_file(self, backing_file, basedir):
  358. self._test_qemu_img_info(backing_file=backing_file,
  359. basedir=basedir,
  360. valid_backing_file=False)
  361. def test_create_cloned_volume(self):
  362. drv = self._driver
  363. with mock.patch.object(drv, '_create_snapshot') as \
  364. mock_create_snapshot,\
  365. mock.patch.object(drv, '_delete_snapshot') as \
  366. mock_delete_snapshot,\
  367. mock.patch.object(drv, '_copy_volume_from_snapshot') as \
  368. mock_copy_volume_from_snapshot:
  369. volume = fake_volume.fake_volume_obj(self.context)
  370. src_vref_id = '375e32b2-804a-49f2-b282-85d1d5a5b9e1'
  371. src_vref = fake_volume.fake_volume_obj(
  372. self.context,
  373. id=src_vref_id,
  374. name='volume-%s' % src_vref_id)
  375. vol_attrs = ['provider_location', 'size', 'id', 'name', 'status',
  376. 'volume_type', 'metadata']
  377. Volume = collections.namedtuple('Volume', vol_attrs)
  378. snap_attrs = ['volume_name', 'volume_size', 'name',
  379. 'volume_id', 'id', 'volume']
  380. Snapshot = collections.namedtuple('Snapshot', snap_attrs)
  381. volume_ref = Volume(id=volume.id,
  382. name=volume.name,
  383. status=volume.status,
  384. provider_location=volume.provider_location,
  385. size=volume.size,
  386. volume_type=volume.volume_type,
  387. metadata=volume.metadata)
  388. snap_ref = Snapshot(volume_name=volume.name,
  389. name='clone-snap-%s' % src_vref.id,
  390. volume_size=src_vref.size,
  391. volume_id=src_vref.id,
  392. id='tmp-snap-%s' % src_vref.id,
  393. volume=src_vref)
  394. drv.create_cloned_volume(volume, src_vref)
  395. mock_create_snapshot.assert_called_once_with(snap_ref)
  396. mock_copy_volume_from_snapshot.assert_called_once_with(
  397. snap_ref, volume_ref, volume['size'])
  398. self.assertTrue(mock_delete_snapshot.called)
  399. def test_create_regular_file(self):
  400. self._driver._create_regular_file('/path', 1)
  401. self._driver._execute.assert_called_once_with('dd', 'if=/dev/zero',
  402. 'of=/path', 'bs=1M',
  403. 'count=1024',
  404. run_as_root=True)
  405. class RemoteFSPoolMixinTestCase(test.TestCase):
  406. def setUp(self):
  407. super(RemoteFSPoolMixinTestCase, self).setUp()
  408. # We'll instantiate this directly for now.
  409. self._driver = remotefs.RemoteFSPoolMixin()
  410. self.context = context.get_admin_context()
  411. @mock.patch.object(remotefs.RemoteFSPoolMixin,
  412. '_get_pool_name_from_volume')
  413. @mock.patch.object(remotefs.RemoteFSPoolMixin,
  414. '_get_share_from_pool_name')
  415. def test_find_share(self, mock_get_share_from_pool,
  416. mock_get_pool_from_volume):
  417. share = self._driver._find_share(mock.sentinel.volume)
  418. self.assertEqual(mock_get_share_from_pool.return_value, share)
  419. mock_get_pool_from_volume.assert_called_once_with(
  420. mock.sentinel.volume)
  421. mock_get_share_from_pool.assert_called_once_with(
  422. mock_get_pool_from_volume.return_value)
  423. def test_get_pool_name_from_volume(self):
  424. fake_pool = 'fake_pool'
  425. fake_host = 'fake_host@fake_backend#%s' % fake_pool
  426. fake_vol = fake_volume.fake_volume_obj(
  427. self.context, provider_location='fake_share',
  428. host=fake_host)
  429. pool_name = self._driver._get_pool_name_from_volume(fake_vol)
  430. self.assertEqual(fake_pool, pool_name)
  431. def test_update_volume_stats(self):
  432. share_total_gb = 3
  433. share_free_gb = 2
  434. share_used_gb = 4 # provisioned space
  435. expected_allocated_gb = share_total_gb - share_free_gb
  436. self._driver._mounted_shares = [mock.sentinel.share]
  437. self._driver.configuration = mock.Mock()
  438. self._driver.configuration.safe_get.return_value = (
  439. mock.sentinel.backend_name)
  440. self._driver.vendor_name = mock.sentinel.vendor_name
  441. self._driver.driver_volume_type = mock.sentinel.driver_volume_type
  442. self._driver._thin_provisioning_support = (
  443. mock.sentinel.thin_prov_support)
  444. self._driver.get_version = mock.Mock(
  445. return_value=mock.sentinel.driver_version)
  446. self._driver._ensure_shares_mounted = mock.Mock()
  447. self._driver._get_capacity_info = mock.Mock(
  448. return_value=(share_total_gb << 30,
  449. share_free_gb << 30,
  450. share_used_gb << 30))
  451. self._driver._get_pool_name_from_share = mock.Mock(
  452. return_value=mock.sentinel.pool_name)
  453. expected_pool = {
  454. 'pool_name': mock.sentinel.pool_name,
  455. 'total_capacity_gb': float(share_total_gb),
  456. 'free_capacity_gb': float(share_free_gb),
  457. 'provisioned_capacity_gb': float(share_used_gb),
  458. 'allocated_capacity_gb': float(expected_allocated_gb),
  459. 'reserved_percentage': (
  460. self._driver.configuration.reserved_percentage),
  461. 'max_over_subscription_ratio': (
  462. self._driver.configuration.max_over_subscription_ratio),
  463. 'thin_provisioning_support': (
  464. mock.sentinel.thin_prov_support),
  465. 'QoS_support': False,
  466. }
  467. expected_stats = {
  468. 'volume_backend_name': mock.sentinel.backend_name,
  469. 'vendor_name': mock.sentinel.vendor_name,
  470. 'driver_version': mock.sentinel.driver_version,
  471. 'storage_protocol': mock.sentinel.driver_volume_type,
  472. 'total_capacity_gb': 0,
  473. 'free_capacity_gb': 0,
  474. 'pools': [expected_pool],
  475. }
  476. self._driver._update_volume_stats()
  477. self.assertDictEqual(expected_stats, self._driver._stats)
  478. self._driver._get_capacity_info.assert_called_once_with(
  479. mock.sentinel.share)
  480. self._driver.configuration.safe_get.assert_called_once_with(
  481. 'volume_backend_name')