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.

test_driver.py 100KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491
  1. # Copyright (c) 2016 Mirantis, 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. import ddt
  16. import mock
  17. from oslo_config import cfg
  18. from manila import context
  19. from manila import exception
  20. from manila.share.drivers.ganesha import utils as ganesha_utils
  21. from manila.share.drivers.zfsonlinux import driver as zfs_driver
  22. from manila import test
  23. CONF = cfg.CONF
  24. class FakeConfig(object):
  25. def __init__(self, *args, **kwargs):
  26. self.driver_handles_share_servers = False
  27. self.share_driver = 'fake_share_driver_name'
  28. self.share_backend_name = 'FAKE_BACKEND_NAME'
  29. self.zfs_share_export_ip = kwargs.get(
  30. "zfs_share_export_ip", "1.1.1.1")
  31. self.zfs_service_ip = kwargs.get("zfs_service_ip", "2.2.2.2")
  32. self.zfs_zpool_list = kwargs.get(
  33. "zfs_zpool_list", ["foo", "bar/subbar", "quuz"])
  34. self.zfs_use_ssh = kwargs.get("zfs_use_ssh", False)
  35. self.zfs_share_export_ip = kwargs.get(
  36. "zfs_share_export_ip", "240.241.242.243")
  37. self.zfs_service_ip = kwargs.get("zfs_service_ip", "240.241.242.244")
  38. self.ssh_conn_timeout = kwargs.get("ssh_conn_timeout", 123)
  39. self.zfs_ssh_username = kwargs.get(
  40. "zfs_ssh_username", 'fake_username')
  41. self.zfs_ssh_user_password = kwargs.get(
  42. "zfs_ssh_user_password", 'fake_pass')
  43. self.zfs_ssh_private_key_path = kwargs.get(
  44. "zfs_ssh_private_key_path", '/fake/path')
  45. self.zfs_replica_snapshot_prefix = kwargs.get(
  46. "zfs_replica_snapshot_prefix", "tmp_snapshot_for_replication_")
  47. self.zfs_migration_snapshot_prefix = kwargs.get(
  48. "zfs_migration_snapshot_prefix", "tmp_snapshot_for_migration_")
  49. self.zfs_dataset_creation_options = kwargs.get(
  50. "zfs_dataset_creation_options", ["fook=foov", "bark=barv"])
  51. self.network_config_group = kwargs.get(
  52. "network_config_group", "fake_network_config_group")
  53. self.admin_network_config_group = kwargs.get(
  54. "admin_network_config_group", "fake_admin_network_config_group")
  55. self.config_group = kwargs.get("config_group", "fake_config_group")
  56. self.reserved_share_percentage = kwargs.get(
  57. "reserved_share_percentage", 0)
  58. self.max_over_subscription_ratio = kwargs.get(
  59. "max_over_subscription_ratio", 15.0)
  60. self.filter_function = kwargs.get("filter_function", None)
  61. self.goodness_function = kwargs.get("goodness_function", None)
  62. def safe_get(self, key):
  63. return getattr(self, key)
  64. def append_config_values(self, *args, **kwargs):
  65. pass
  66. class FakeDriverPrivateStorage(object):
  67. def __init__(self):
  68. self.storage = {}
  69. def update(self, entity_id, data):
  70. if entity_id not in self.storage:
  71. self.storage[entity_id] = {}
  72. self.storage[entity_id].update(data)
  73. def get(self, entity_id, key):
  74. return self.storage.get(entity_id, {}).get(key)
  75. def delete(self, entity_id):
  76. self.storage.pop(entity_id, None)
  77. class FakeTempDir(object):
  78. def __enter__(self, *args, **kwargs):
  79. return '/foo/path'
  80. def __exit__(*args, **kwargs):
  81. pass
  82. class GetBackendConfigurationTestCase(test.TestCase):
  83. def test_get_backend_configuration_success(self):
  84. backend_name = 'fake_backend_name'
  85. self.mock_object(
  86. zfs_driver.CONF, 'list_all_sections',
  87. mock.Mock(return_value=['fake1', backend_name, 'fake2']))
  88. mock_config = self.mock_object(
  89. zfs_driver.configuration, 'Configuration')
  90. result = zfs_driver.get_backend_configuration(backend_name)
  91. self.assertEqual(mock_config.return_value, result)
  92. mock_config.assert_called_once_with(
  93. zfs_driver.driver.share_opts, config_group=backend_name)
  94. mock_config.return_value.append_config_values.assert_has_calls([
  95. mock.call(zfs_driver.zfsonlinux_opts),
  96. mock.call(zfs_driver.share_manager_opts),
  97. mock.call(zfs_driver.driver.ssh_opts),
  98. ])
  99. def test_get_backend_configuration_error(self):
  100. backend_name = 'fake_backend_name'
  101. self.mock_object(
  102. zfs_driver.CONF, 'list_all_sections',
  103. mock.Mock(return_value=['fake1', 'fake2']))
  104. mock_config = self.mock_object(
  105. zfs_driver.configuration, 'Configuration')
  106. self.assertRaises(
  107. exception.BadConfigurationException,
  108. zfs_driver.get_backend_configuration,
  109. backend_name,
  110. )
  111. self.assertFalse(mock_config.called)
  112. self.assertFalse(mock_config.return_value.append_config_values.called)
  113. @ddt.ddt
  114. class ZFSonLinuxShareDriverTestCase(test.TestCase):
  115. def setUp(self):
  116. self.mock_object(zfs_driver.CONF, '_check_required_opts')
  117. super(self.__class__, self).setUp()
  118. self._context = context.get_admin_context()
  119. self.ssh_executor = self.mock_object(ganesha_utils, 'SSHExecutor')
  120. self.configuration = FakeConfig()
  121. self.private_storage = FakeDriverPrivateStorage()
  122. self.driver = zfs_driver.ZFSonLinuxShareDriver(
  123. configuration=self.configuration,
  124. private_storage=self.private_storage)
  125. def test_init(self):
  126. self.assertTrue(hasattr(self.driver, 'replica_snapshot_prefix'))
  127. self.assertEqual(
  128. self.driver.replica_snapshot_prefix,
  129. self.configuration.zfs_replica_snapshot_prefix)
  130. self.assertEqual(
  131. self.driver.backend_name,
  132. self.configuration.share_backend_name)
  133. self.assertEqual(
  134. self.driver.zpool_list, ['foo', 'bar', 'quuz'])
  135. self.assertEqual(
  136. self.driver.dataset_creation_options,
  137. self.configuration.zfs_dataset_creation_options)
  138. self.assertEqual(
  139. self.driver.share_export_ip,
  140. self.configuration.zfs_share_export_ip)
  141. self.assertEqual(
  142. self.driver.service_ip,
  143. self.configuration.zfs_service_ip)
  144. self.assertEqual(
  145. self.driver.private_storage,
  146. self.private_storage)
  147. self.assertTrue(hasattr(self.driver, '_helpers'))
  148. self.assertEqual(self.driver._helpers, {})
  149. for attr_name in ('execute', 'execute_with_retry', 'parse_zfs_answer',
  150. 'get_zpool_option', 'get_zfs_option', 'zfs'):
  151. self.assertTrue(hasattr(self.driver, attr_name))
  152. def test_init_error_with_duplicated_zpools(self):
  153. configuration = FakeConfig(
  154. zfs_zpool_list=['foo', 'bar', 'foo/quuz'])
  155. self.assertRaises(
  156. exception.BadConfigurationException,
  157. zfs_driver.ZFSonLinuxShareDriver,
  158. configuration=configuration,
  159. private_storage=self.private_storage
  160. )
  161. def test__setup_helpers(self):
  162. mock_import_class = self.mock_object(
  163. zfs_driver.importutils, 'import_class')
  164. self.configuration.zfs_share_helpers = ['FOO=foo.module.WithHelper']
  165. result = self.driver._setup_helpers()
  166. self.assertIsNone(result)
  167. mock_import_class.assert_called_once_with('foo.module.WithHelper')
  168. mock_import_class.return_value.assert_called_once_with(
  169. self.configuration)
  170. self.assertEqual(
  171. self.driver._helpers,
  172. {'FOO': mock_import_class.return_value.return_value})
  173. def test__setup_helpers_error(self):
  174. self.configuration.zfs_share_helpers = []
  175. self.assertRaises(
  176. exception.BadConfigurationException, self.driver._setup_helpers)
  177. def test__get_share_helper(self):
  178. self.driver._helpers = {'FOO': 'BAR'}
  179. result = self.driver._get_share_helper('FOO')
  180. self.assertEqual('BAR', result)
  181. @ddt.data({}, {'foo': 'bar'})
  182. def test__get_share_helper_error(self, share_proto):
  183. self.assertRaises(
  184. exception.InvalidShare, self.driver._get_share_helper, 'NFS')
  185. @ddt.data(True, False)
  186. def test_do_setup(self, use_ssh):
  187. self.mock_object(self.driver, '_setup_helpers')
  188. self.mock_object(self.driver, 'ssh_executor')
  189. self.configuration.zfs_use_ssh = use_ssh
  190. self.driver.do_setup('fake_context')
  191. self.driver._setup_helpers.assert_called_once_with()
  192. if use_ssh:
  193. self.assertEqual(4, self.driver.ssh_executor.call_count)
  194. else:
  195. self.assertEqual(3, self.driver.ssh_executor.call_count)
  196. @ddt.data(
  197. ('foo', '127.0.0.1'),
  198. ('127.0.0.1', 'foo'),
  199. ('256.0.0.1', '127.0.0.1'),
  200. ('::1/128', '127.0.0.1'),
  201. ('127.0.0.1', '::1/128'),
  202. )
  203. @ddt.unpack
  204. def test_do_setup_error_on_ip_addresses_configuration(
  205. self, share_export_ip, service_ip):
  206. self.mock_object(self.driver, '_setup_helpers')
  207. self.driver.share_export_ip = share_export_ip
  208. self.driver.service_ip = service_ip
  209. self.assertRaises(
  210. exception.BadConfigurationException,
  211. self.driver.do_setup, 'fake_context')
  212. self.driver._setup_helpers.assert_called_once_with()
  213. @ddt.data([], '', None)
  214. def test_do_setup_no_zpools_configured(self, zpool_list):
  215. self.mock_object(self.driver, '_setup_helpers')
  216. self.driver.zpool_list = zpool_list
  217. self.assertRaises(
  218. exception.BadConfigurationException,
  219. self.driver.do_setup, 'fake_context')
  220. self.driver._setup_helpers.assert_called_once_with()
  221. @ddt.data(None, '', 'foo_replication_domain')
  222. def test__get_pools_info(self, replication_domain):
  223. self.mock_object(
  224. self.driver, 'get_zpool_option',
  225. mock.Mock(side_effect=['2G', '3G', '5G', '4G']))
  226. self.configuration.replication_domain = replication_domain
  227. self.driver.zpool_list = ['foo', 'bar']
  228. expected = [
  229. {'pool_name': 'foo', 'total_capacity_gb': 3.0,
  230. 'free_capacity_gb': 2.0, 'reserved_percentage': 0,
  231. 'compression': [True, False],
  232. 'dedupe': [True, False],
  233. 'thin_provisioning': [True],
  234. 'max_over_subscription_ratio': (
  235. self.driver.configuration.max_over_subscription_ratio),
  236. 'qos': [False]},
  237. {'pool_name': 'bar', 'total_capacity_gb': 4.0,
  238. 'free_capacity_gb': 5.0, 'reserved_percentage': 0,
  239. 'compression': [True, False],
  240. 'dedupe': [True, False],
  241. 'thin_provisioning': [True],
  242. 'max_over_subscription_ratio': (
  243. self.driver.configuration.max_over_subscription_ratio),
  244. 'qos': [False]},
  245. ]
  246. if replication_domain:
  247. for pool in expected:
  248. pool['replication_type'] = 'readable'
  249. result = self.driver._get_pools_info()
  250. self.assertEqual(expected, result)
  251. self.driver.get_zpool_option.assert_has_calls([
  252. mock.call('foo', 'free'),
  253. mock.call('foo', 'size'),
  254. mock.call('bar', 'free'),
  255. mock.call('bar', 'size'),
  256. ])
  257. @ddt.data(
  258. ([], {'compression': [True, False], 'dedupe': [True, False]}),
  259. (['dedup=off'], {'compression': [True, False], 'dedupe': [False]}),
  260. (['dedup=on'], {'compression': [True, False], 'dedupe': [True]}),
  261. (['compression=on'], {'compression': [True], 'dedupe': [True, False]}),
  262. (['compression=off'],
  263. {'compression': [False], 'dedupe': [True, False]}),
  264. (['compression=fake'],
  265. {'compression': [True], 'dedupe': [True, False]}),
  266. (['compression=fake', 'dedup=off'],
  267. {'compression': [True], 'dedupe': [False]}),
  268. (['compression=off', 'dedup=on'],
  269. {'compression': [False], 'dedupe': [True]}),
  270. )
  271. @ddt.unpack
  272. def test__init_common_capabilities(
  273. self, dataset_creation_options, expected_part):
  274. self.driver.dataset_creation_options = (
  275. dataset_creation_options)
  276. expected = {
  277. 'thin_provisioning': [True],
  278. 'qos': [False],
  279. 'max_over_subscription_ratio': (
  280. self.driver.configuration.max_over_subscription_ratio),
  281. }
  282. expected.update(expected_part)
  283. self.driver._init_common_capabilities()
  284. self.assertEqual(expected, self.driver.common_capabilities)
  285. @ddt.data(None, '', 'foo_replication_domain')
  286. def test__update_share_stats(self, replication_domain):
  287. self.configuration.replication_domain = replication_domain
  288. self.mock_object(self.driver, '_get_pools_info')
  289. self.assertEqual({}, self.driver._stats)
  290. expected = {
  291. 'driver_handles_share_servers': False,
  292. 'driver_name': 'ZFS',
  293. 'driver_version': '1.0',
  294. 'free_capacity_gb': 'unknown',
  295. 'pools': self.driver._get_pools_info.return_value,
  296. 'qos': False,
  297. 'replication_domain': replication_domain,
  298. 'reserved_percentage': 0,
  299. 'share_backend_name': self.driver.backend_name,
  300. 'share_group_stats': {'consistent_snapshot_support': None},
  301. 'snapshot_support': True,
  302. 'create_share_from_snapshot_support': True,
  303. 'revert_to_snapshot_support': False,
  304. 'mount_snapshot_support': False,
  305. 'storage_protocol': 'NFS',
  306. 'total_capacity_gb': 'unknown',
  307. 'vendor_name': 'Open Source',
  308. 'filter_function': None,
  309. 'goodness_function': None,
  310. 'ipv4_support': True,
  311. 'ipv6_support': False,
  312. }
  313. if replication_domain:
  314. expected['replication_type'] = 'readable'
  315. self.driver._update_share_stats()
  316. self.assertEqual(expected, self.driver._stats)
  317. self.driver._get_pools_info.assert_called_once_with()
  318. @ddt.data('', 'foo', 'foo-bar', 'foo_bar', 'foo-bar_quuz')
  319. def test__get_share_name(self, share_id):
  320. prefix = 'fake_prefix_'
  321. self.configuration.zfs_dataset_name_prefix = prefix
  322. self.configuration.zfs_dataset_snapshot_name_prefix = 'quuz'
  323. expected = prefix + share_id.replace('-', '_')
  324. result = self.driver._get_share_name(share_id)
  325. self.assertEqual(expected, result)
  326. @ddt.data('', 'foo', 'foo-bar', 'foo_bar', 'foo-bar_quuz')
  327. def test__get_snapshot_name(self, snapshot_id):
  328. prefix = 'fake_prefix_'
  329. self.configuration.zfs_dataset_name_prefix = 'quuz'
  330. self.configuration.zfs_dataset_snapshot_name_prefix = prefix
  331. expected = prefix + snapshot_id.replace('-', '_')
  332. result = self.driver._get_snapshot_name(snapshot_id)
  333. self.assertEqual(expected, result)
  334. def test__get_dataset_creation_options_not_set(self):
  335. self.driver.dataset_creation_options = []
  336. mock_get_extra_specs_from_share = self.mock_object(
  337. zfs_driver.share_types,
  338. 'get_extra_specs_from_share',
  339. mock.Mock(return_value={}))
  340. share = {'size': '5'}
  341. result = self.driver._get_dataset_creation_options(share=share)
  342. self.assertIsInstance(result, list)
  343. self.assertEqual(2, len(result))
  344. for v in ('quota=5G', 'readonly=off'):
  345. self.assertIn(v, result)
  346. mock_get_extra_specs_from_share.assert_called_once_with(share)
  347. @ddt.data(True, False)
  348. def test__get_dataset_creation_options(self, is_readonly):
  349. mock_get_extra_specs_from_share = self.mock_object(
  350. zfs_driver.share_types,
  351. 'get_extra_specs_from_share',
  352. mock.Mock(return_value={}))
  353. self.driver.dataset_creation_options = [
  354. 'readonly=quuz', 'sharenfs=foo', 'sharesmb=bar', 'k=v', 'q=w',
  355. ]
  356. share = {'size': 5}
  357. readonly = 'readonly=%s' % ('on' if is_readonly else 'off')
  358. expected = [readonly, 'k=v', 'q=w', 'quota=5G']
  359. result = self.driver._get_dataset_creation_options(
  360. share=share, is_readonly=is_readonly)
  361. self.assertEqual(sorted(expected), sorted(result))
  362. mock_get_extra_specs_from_share.assert_called_once_with(share)
  363. @ddt.data(
  364. ('<is> True', [True, False], ['dedup=off'], 'dedup=on'),
  365. ('True', [True, False], ['dedup=off'], 'dedup=on'),
  366. ('on', [True, False], ['dedup=off'], 'dedup=on'),
  367. ('yes', [True, False], ['dedup=off'], 'dedup=on'),
  368. ('1', [True, False], ['dedup=off'], 'dedup=on'),
  369. ('True', [True], [], 'dedup=on'),
  370. ('<is> False', [True, False], [], 'dedup=off'),
  371. ('False', [True, False], [], 'dedup=off'),
  372. ('False', [False], ['dedup=on'], 'dedup=off'),
  373. ('off', [False], ['dedup=on'], 'dedup=off'),
  374. ('no', [False], ['dedup=on'], 'dedup=off'),
  375. ('0', [False], ['dedup=on'], 'dedup=off'),
  376. )
  377. @ddt.unpack
  378. def test__get_dataset_creation_options_with_updated_dedupe(
  379. self, dedupe_extra_spec, dedupe_capability, driver_options,
  380. expected):
  381. mock_get_extra_specs_from_share = self.mock_object(
  382. zfs_driver.share_types,
  383. 'get_extra_specs_from_share',
  384. mock.Mock(return_value={'dedupe': dedupe_extra_spec}))
  385. self.driver.dataset_creation_options = driver_options
  386. self.driver.common_capabilities['dedupe'] = dedupe_capability
  387. share = {'size': 5}
  388. expected_options = ['quota=5G', 'readonly=off']
  389. expected_options.append(expected)
  390. result = self.driver._get_dataset_creation_options(share=share)
  391. self.assertEqual(sorted(expected_options), sorted(result))
  392. mock_get_extra_specs_from_share.assert_called_once_with(share)
  393. @ddt.data(
  394. ('on', [True, False], ['compression=off'], 'compression=on'),
  395. ('on', [True], [], 'compression=on'),
  396. ('off', [False], ['compression=on'], 'compression=off'),
  397. ('off', [True, False], [], 'compression=off'),
  398. ('foo', [True, False], [], 'compression=foo'),
  399. ('bar', [True], [], 'compression=bar'),
  400. )
  401. @ddt.unpack
  402. def test__get_dataset_creation_options_with_updated_compression(
  403. self, extra_spec, capability, driver_options, expected_option):
  404. mock_get_extra_specs_from_share = self.mock_object(
  405. zfs_driver.share_types,
  406. 'get_extra_specs_from_share',
  407. mock.Mock(return_value={'zfsonlinux:compression': extra_spec}))
  408. self.driver.dataset_creation_options = driver_options
  409. self.driver.common_capabilities['compression'] = capability
  410. share = {'size': 5}
  411. expected_options = ['quota=5G', 'readonly=off']
  412. expected_options.append(expected_option)
  413. result = self.driver._get_dataset_creation_options(share=share)
  414. self.assertEqual(sorted(expected_options), sorted(result))
  415. mock_get_extra_specs_from_share.assert_called_once_with(share)
  416. @ddt.data(
  417. ({'dedupe': 'fake'}, {'dedupe': [True, False]}),
  418. ({'dedupe': 'on'}, {'dedupe': [False]}),
  419. ({'dedupe': 'off'}, {'dedupe': [True]}),
  420. ({'zfsonlinux:compression': 'fake'}, {'compression': [False]}),
  421. ({'zfsonlinux:compression': 'on'}, {'compression': [False]}),
  422. ({'zfsonlinux:compression': 'off'}, {'compression': [True]}),
  423. )
  424. @ddt.unpack
  425. def test__get_dataset_creation_options_error(
  426. self, extra_specs, common_capabilities):
  427. mock_get_extra_specs_from_share = self.mock_object(
  428. zfs_driver.share_types,
  429. 'get_extra_specs_from_share',
  430. mock.Mock(return_value=extra_specs))
  431. share = {'size': 5}
  432. self.driver.common_capabilities.update(common_capabilities)
  433. self.assertRaises(
  434. exception.ZFSonLinuxException,
  435. self.driver._get_dataset_creation_options,
  436. share=share
  437. )
  438. mock_get_extra_specs_from_share.assert_called_once_with(share)
  439. @ddt.data('bar/quuz', 'bar/quuz/', 'bar')
  440. def test__get_dataset_name(self, second_zpool):
  441. self.configuration.zfs_zpool_list = ['foo', second_zpool]
  442. prefix = 'fake_prefix_'
  443. self.configuration.zfs_dataset_name_prefix = prefix
  444. share = {'id': 'abc-def_ghi', 'host': 'hostname@backend_name#bar'}
  445. result = self.driver._get_dataset_name(share)
  446. if second_zpool[-1] == '/':
  447. second_zpool = second_zpool[0:-1]
  448. expected = '%s/%sabc_def_ghi' % (second_zpool, prefix)
  449. self.assertEqual(expected, result)
  450. def test_create_share(self):
  451. mock_get_helper = self.mock_object(self.driver, '_get_share_helper')
  452. self.mock_object(self.driver, 'zfs')
  453. mock_get_extra_specs_from_share = self.mock_object(
  454. zfs_driver.share_types,
  455. 'get_extra_specs_from_share',
  456. mock.Mock(return_value={}))
  457. context = 'fake_context'
  458. share = {
  459. 'id': 'fake_share_id',
  460. 'host': 'hostname@backend_name#bar',
  461. 'share_proto': 'NFS',
  462. 'size': 4,
  463. }
  464. self.configuration.zfs_dataset_name_prefix = 'some_prefix_'
  465. self.configuration.zfs_ssh_username = 'someuser'
  466. self.driver.share_export_ip = '1.1.1.1'
  467. self.driver.service_ip = '2.2.2.2'
  468. dataset_name = 'bar/subbar/some_prefix_fake_share_id'
  469. result = self.driver.create_share(context, share, share_server=None)
  470. self.assertEqual(
  471. mock_get_helper.return_value.create_exports.return_value,
  472. result,
  473. )
  474. self.assertEqual(
  475. 'share',
  476. self.driver.private_storage.get(share['id'], 'entity_type'))
  477. self.assertEqual(
  478. dataset_name,
  479. self.driver.private_storage.get(share['id'], 'dataset_name'))
  480. self.assertEqual(
  481. 'someuser@2.2.2.2',
  482. self.driver.private_storage.get(share['id'], 'ssh_cmd'))
  483. self.assertEqual(
  484. 'bar',
  485. self.driver.private_storage.get(share['id'], 'pool_name'))
  486. self.driver.zfs.assert_called_once_with(
  487. 'create', '-o', 'quota=4G', '-o', 'fook=foov', '-o', 'bark=barv',
  488. '-o', 'readonly=off', 'bar/subbar/some_prefix_fake_share_id')
  489. mock_get_helper.assert_has_calls([
  490. mock.call('NFS'), mock.call().create_exports(dataset_name)
  491. ])
  492. mock_get_extra_specs_from_share.assert_called_once_with(share)
  493. def test_create_share_with_share_server(self):
  494. self.assertRaises(
  495. exception.InvalidInput,
  496. self.driver.create_share,
  497. 'fake_context', 'fake_share', share_server={'id': 'fake_server'},
  498. )
  499. def test_delete_share(self):
  500. dataset_name = 'bar/subbar/some_prefix_fake_share_id'
  501. mock_delete = self.mock_object(
  502. self.driver, '_delete_dataset_or_snapshot_with_retry')
  503. self.mock_object(self.driver, '_get_share_helper')
  504. self.mock_object(zfs_driver.LOG, 'warning')
  505. self.mock_object(
  506. self.driver, 'zfs', mock.Mock(return_value=('a', 'b')))
  507. snap_name = '%s@%s' % (
  508. dataset_name, self.driver.replica_snapshot_prefix)
  509. self.mock_object(
  510. self.driver, 'parse_zfs_answer',
  511. mock.Mock(
  512. side_effect=[
  513. [{'NAME': 'fake_dataset_name'}, {'NAME': dataset_name}],
  514. [{'NAME': 'snap_name'},
  515. {'NAME': '%s@foo' % dataset_name},
  516. {'NAME': snap_name}],
  517. ]))
  518. context = 'fake_context'
  519. share = {
  520. 'id': 'fake_share_id',
  521. 'host': 'hostname@backend_name#bar',
  522. 'share_proto': 'NFS',
  523. 'size': 4,
  524. }
  525. self.configuration.zfs_dataset_name_prefix = 'some_prefix_'
  526. self.configuration.zfs_ssh_username = 'someuser'
  527. self.driver.share_export_ip = '1.1.1.1'
  528. self.driver.service_ip = '2.2.2.2'
  529. self.driver.private_storage.update(
  530. share['id'],
  531. {'pool_name': 'bar', 'dataset_name': dataset_name}
  532. )
  533. self.driver.delete_share(context, share, share_server=None)
  534. self.driver.zfs.assert_has_calls([
  535. mock.call('list', '-r', 'bar'),
  536. mock.call('list', '-r', '-t', 'snapshot', 'bar'),
  537. ])
  538. self.driver._get_share_helper.assert_has_calls([
  539. mock.call('NFS'), mock.call().remove_exports(dataset_name)])
  540. self.driver.parse_zfs_answer.assert_has_calls([
  541. mock.call('a'), mock.call('a')])
  542. mock_delete.assert_has_calls([
  543. mock.call(snap_name),
  544. mock.call(dataset_name),
  545. ])
  546. self.assertEqual(0, zfs_driver.LOG.warning.call_count)
  547. def test_delete_share_absent(self):
  548. dataset_name = 'bar/subbar/some_prefix_fake_share_id'
  549. mock_delete = self.mock_object(
  550. self.driver, '_delete_dataset_or_snapshot_with_retry')
  551. self.mock_object(self.driver, '_get_share_helper')
  552. self.mock_object(zfs_driver.LOG, 'warning')
  553. self.mock_object(
  554. self.driver, 'zfs', mock.Mock(return_value=('a', 'b')))
  555. snap_name = '%s@%s' % (
  556. dataset_name, self.driver.replica_snapshot_prefix)
  557. self.mock_object(
  558. self.driver, 'parse_zfs_answer',
  559. mock.Mock(side_effect=[[], [{'NAME': snap_name}]]))
  560. context = 'fake_context'
  561. share = {
  562. 'id': 'fake_share_id',
  563. 'host': 'hostname@backend_name#bar',
  564. 'size': 4,
  565. }
  566. self.configuration.zfs_dataset_name_prefix = 'some_prefix_'
  567. self.configuration.zfs_ssh_username = 'someuser'
  568. self.driver.share_export_ip = '1.1.1.1'
  569. self.driver.service_ip = '2.2.2.2'
  570. self.driver.private_storage.update(share['id'], {'pool_name': 'bar'})
  571. self.driver.delete_share(context, share, share_server=None)
  572. self.assertEqual(0, self.driver._get_share_helper.call_count)
  573. self.assertEqual(0, mock_delete.call_count)
  574. self.driver.zfs.assert_called_once_with('list', '-r', 'bar')
  575. self.driver.parse_zfs_answer.assert_called_once_with('a')
  576. zfs_driver.LOG.warning.assert_called_once_with(
  577. mock.ANY, {'id': share['id'], 'name': dataset_name})
  578. def test_delete_share_with_share_server(self):
  579. self.assertRaises(
  580. exception.InvalidInput,
  581. self.driver.delete_share,
  582. 'fake_context', 'fake_share', share_server={'id': 'fake_server'},
  583. )
  584. def test_create_snapshot(self):
  585. self.configuration.zfs_dataset_snapshot_name_prefix = 'prefx_'
  586. self.mock_object(self.driver, 'zfs')
  587. snapshot = {
  588. 'id': 'fake_snapshot_instance_id',
  589. 'snapshot_id': 'fake_snapshot_id',
  590. 'host': 'hostname@backend_name#bar',
  591. 'size': 4,
  592. 'share_instance_id': 'fake_share_id'
  593. }
  594. snapshot_name = 'foo_data_set_name@prefx_%s' % snapshot['id']
  595. self.driver.private_storage.update(
  596. snapshot['share_instance_id'],
  597. {'dataset_name': 'foo_data_set_name'})
  598. result = self.driver.create_snapshot('fake_context', snapshot)
  599. self.driver.zfs.assert_called_once_with(
  600. 'snapshot', snapshot_name)
  601. self.assertEqual(
  602. snapshot_name.split('@')[-1],
  603. self.driver.private_storage.get(
  604. snapshot['snapshot_id'], 'snapshot_tag'))
  605. self.assertEqual({"provider_location": snapshot_name}, result)
  606. def test_delete_snapshot(self):
  607. snapshot = {
  608. 'id': 'fake_snapshot_instance_id',
  609. 'snapshot_id': 'fake_snapshot_id',
  610. 'host': 'hostname@backend_name#bar',
  611. 'size': 4,
  612. 'share_instance_id': 'fake_share_id',
  613. }
  614. dataset_name = 'foo_zpool/bar_dataset_name'
  615. snap_tag = 'prefix_%s' % snapshot['id']
  616. snap_name = '%(dataset)s@%(tag)s' % {
  617. 'dataset': dataset_name, 'tag': snap_tag}
  618. mock_delete = self.mock_object(
  619. self.driver, '_delete_dataset_or_snapshot_with_retry')
  620. self.mock_object(zfs_driver.LOG, 'warning')
  621. self.mock_object(
  622. self.driver, 'zfs', mock.Mock(return_value=('a', 'b')))
  623. self.mock_object(
  624. self.driver, 'parse_zfs_answer',
  625. mock.Mock(side_effect=[
  626. [{'NAME': 'some_other_dataset@snapshot_name'},
  627. {'NAME': snap_name}],
  628. []]))
  629. context = 'fake_context'
  630. self.driver.private_storage.update(
  631. snapshot['id'], {'snapshot_name': snap_name})
  632. self.driver.private_storage.update(
  633. snapshot['snapshot_id'], {'snapshot_tag': snap_tag})
  634. self.driver.private_storage.update(
  635. snapshot['share_instance_id'], {'dataset_name': dataset_name})
  636. self.assertEqual(
  637. snap_tag,
  638. self.driver.private_storage.get(
  639. snapshot['snapshot_id'], 'snapshot_tag'))
  640. self.driver.delete_snapshot(context, snapshot, share_server=None)
  641. self.assertIsNone(
  642. self.driver.private_storage.get(
  643. snapshot['snapshot_id'], 'snapshot_tag'))
  644. self.assertEqual(0, zfs_driver.LOG.warning.call_count)
  645. self.driver.zfs.assert_called_once_with(
  646. 'list', '-r', '-t', 'snapshot', snap_name)
  647. self.driver.parse_zfs_answer.assert_called_once_with('a')
  648. mock_delete.assert_called_once_with(snap_name)
  649. def test_delete_snapshot_absent(self):
  650. snapshot = {
  651. 'id': 'fake_snapshot_instance_id',
  652. 'snapshot_id': 'fake_snapshot_id',
  653. 'host': 'hostname@backend_name#bar',
  654. 'size': 4,
  655. 'share_instance_id': 'fake_share_id',
  656. }
  657. dataset_name = 'foo_zpool/bar_dataset_name'
  658. snap_tag = 'prefix_%s' % snapshot['id']
  659. snap_name = '%(dataset)s@%(tag)s' % {
  660. 'dataset': dataset_name, 'tag': snap_tag}
  661. mock_delete = self.mock_object(
  662. self.driver, '_delete_dataset_or_snapshot_with_retry')
  663. self.mock_object(zfs_driver.LOG, 'warning')
  664. self.mock_object(
  665. self.driver, 'zfs', mock.Mock(return_value=('a', 'b')))
  666. self.mock_object(
  667. self.driver, 'parse_zfs_answer',
  668. mock.Mock(side_effect=[[], [{'NAME': snap_name}]]))
  669. context = 'fake_context'
  670. self.driver.private_storage.update(
  671. snapshot['id'], {'snapshot_name': snap_name})
  672. self.driver.private_storage.update(
  673. snapshot['snapshot_id'], {'snapshot_tag': snap_tag})
  674. self.driver.private_storage.update(
  675. snapshot['share_instance_id'], {'dataset_name': dataset_name})
  676. self.driver.delete_snapshot(context, snapshot, share_server=None)
  677. self.assertEqual(0, mock_delete.call_count)
  678. self.driver.zfs.assert_called_once_with(
  679. 'list', '-r', '-t', 'snapshot', snap_name)
  680. self.driver.parse_zfs_answer.assert_called_once_with('a')
  681. zfs_driver.LOG.warning.assert_called_once_with(
  682. mock.ANY, {'id': snapshot['id'], 'name': snap_name})
  683. def test_delete_snapshot_with_share_server(self):
  684. self.assertRaises(
  685. exception.InvalidInput,
  686. self.driver.delete_snapshot,
  687. 'fake_context', 'fake_snapshot',
  688. share_server={'id': 'fake_server'},
  689. )
  690. def test_create_share_from_snapshot(self):
  691. mock_get_helper = self.mock_object(self.driver, '_get_share_helper')
  692. self.mock_object(self.driver, 'zfs')
  693. self.mock_object(self.driver, 'execute')
  694. mock_get_extra_specs_from_share = self.mock_object(
  695. zfs_driver.share_types,
  696. 'get_extra_specs_from_share',
  697. mock.Mock(return_value={}))
  698. context = 'fake_context'
  699. share = {
  700. 'id': 'fake_share_id',
  701. 'host': 'hostname@backend_name#bar',
  702. 'share_proto': 'NFS',
  703. 'size': 4,
  704. }
  705. snapshot = {
  706. 'id': 'fake_snapshot_instance_id',
  707. 'snapshot_id': 'fake_snapshot_id',
  708. 'host': 'hostname@backend_name#bar',
  709. 'size': 4,
  710. 'share_instance_id': share['id'],
  711. }
  712. dataset_name = 'bar/subbar/some_prefix_%s' % share['id']
  713. snap_tag = 'prefix_%s' % snapshot['id']
  714. snap_name = '%(dataset)s@%(tag)s' % {
  715. 'dataset': dataset_name, 'tag': snap_tag}
  716. self.configuration.zfs_dataset_name_prefix = 'some_prefix_'
  717. self.configuration.zfs_ssh_username = 'someuser'
  718. self.driver.share_export_ip = '1.1.1.1'
  719. self.driver.service_ip = '2.2.2.2'
  720. self.driver.private_storage.update(
  721. snapshot['id'], {'snapshot_name': snap_name})
  722. self.driver.private_storage.update(
  723. snapshot['snapshot_id'], {'snapshot_tag': snap_tag})
  724. self.driver.private_storage.update(
  725. snapshot['share_instance_id'], {'dataset_name': dataset_name})
  726. result = self.driver.create_share_from_snapshot(
  727. context, share, snapshot, share_server=None)
  728. self.assertEqual(
  729. mock_get_helper.return_value.create_exports.return_value,
  730. result,
  731. )
  732. self.assertEqual(
  733. 'share',
  734. self.driver.private_storage.get(share['id'], 'entity_type'))
  735. self.assertEqual(
  736. dataset_name,
  737. self.driver.private_storage.get(share['id'], 'dataset_name'))
  738. self.assertEqual(
  739. 'someuser@2.2.2.2',
  740. self.driver.private_storage.get(share['id'], 'ssh_cmd'))
  741. self.assertEqual(
  742. 'bar',
  743. self.driver.private_storage.get(share['id'], 'pool_name'))
  744. self.driver.execute.assert_has_calls([
  745. mock.call(
  746. 'ssh', 'someuser@2.2.2.2',
  747. 'sudo', 'zfs', 'send', '-vD', snap_name, '|',
  748. 'sudo', 'zfs', 'receive', '-v',
  749. 'bar/subbar/some_prefix_fake_share_id'),
  750. mock.call(
  751. 'sudo', 'zfs', 'destroy',
  752. 'bar/subbar/some_prefix_fake_share_id@%s' % snap_tag),
  753. ])
  754. self.driver.zfs.assert_has_calls([
  755. mock.call('set', opt, 'bar/subbar/some_prefix_fake_share_id')
  756. for opt in ('quota=4G', 'bark=barv', 'readonly=off', 'fook=foov')
  757. ], any_order=True)
  758. mock_get_helper.assert_has_calls([
  759. mock.call('NFS'), mock.call().create_exports(dataset_name)
  760. ])
  761. mock_get_extra_specs_from_share.assert_called_once_with(share)
  762. def test_create_share_from_snapshot_with_share_server(self):
  763. self.assertRaises(
  764. exception.InvalidInput,
  765. self.driver.create_share_from_snapshot,
  766. 'fake_context', 'fake_share', 'fake_snapshot',
  767. share_server={'id': 'fake_server'},
  768. )
  769. def test_get_pool(self):
  770. share = {'host': 'hostname@backend_name#bar'}
  771. result = self.driver.get_pool(share)
  772. self.assertEqual('bar', result)
  773. @ddt.data('on', 'off', 'rw=1.1.1.1')
  774. def test_ensure_share(self, get_zfs_option_answer):
  775. share = {
  776. 'id': 'fake_share_id',
  777. 'host': 'hostname@backend_name#bar',
  778. 'share_proto': 'NFS',
  779. }
  780. dataset_name = 'foo_zpool/foo_fs'
  781. self.mock_object(
  782. self.driver, '_get_dataset_name',
  783. mock.Mock(return_value=dataset_name))
  784. self.mock_object(
  785. self.driver, 'get_zfs_option',
  786. mock.Mock(return_value=get_zfs_option_answer))
  787. mock_helper = self.mock_object(self.driver, '_get_share_helper')
  788. self.mock_object(
  789. self.driver, 'zfs', mock.Mock(return_value=('a', 'b')))
  790. self.mock_object(
  791. self.driver, 'parse_zfs_answer',
  792. mock.Mock(side_effect=[[{'NAME': 'fake1'},
  793. {'NAME': dataset_name},
  794. {'NAME': 'fake2'}]] * 2))
  795. for s in ('1', '2'):
  796. self.driver.zfs.reset_mock()
  797. self.driver.get_zfs_option.reset_mock()
  798. mock_helper.reset_mock()
  799. self.driver.parse_zfs_answer.reset_mock()
  800. self.driver._get_dataset_name.reset_mock()
  801. self.driver.share_export_ip = '1.1.1.%s' % s
  802. self.driver.service_ip = '2.2.2.%s' % s
  803. self.configuration.zfs_ssh_username = 'user%s' % s
  804. result = self.driver.ensure_share('fake_context', share)
  805. self.assertEqual(
  806. 'user%(s)s@2.2.2.%(s)s' % {'s': s},
  807. self.driver.private_storage.get(share['id'], 'ssh_cmd'))
  808. self.driver.get_zfs_option.assert_called_once_with(
  809. dataset_name, 'sharenfs')
  810. mock_helper.assert_called_once_with(
  811. share['share_proto'])
  812. mock_helper.return_value.get_exports.assert_called_once_with(
  813. dataset_name)
  814. expected_calls = [mock.call('list', '-r', 'bar')]
  815. if get_zfs_option_answer != 'off':
  816. expected_calls.append(mock.call('share', dataset_name))
  817. self.driver.zfs.assert_has_calls(expected_calls)
  818. self.driver.parse_zfs_answer.assert_called_once_with('a')
  819. self.driver._get_dataset_name.assert_called_once_with(share)
  820. self.assertEqual(
  821. mock_helper.return_value.get_exports.return_value,
  822. result,
  823. )
  824. def test_ensure_share_absent(self):
  825. share = {'id': 'fake_share_id', 'host': 'hostname@backend_name#bar'}
  826. dataset_name = 'foo_zpool/foo_fs'
  827. self.driver.private_storage.update(
  828. share['id'], {'dataset_name': dataset_name})
  829. self.mock_object(self.driver, 'get_zfs_option')
  830. self.mock_object(self.driver, '_get_share_helper')
  831. self.mock_object(
  832. self.driver, 'zfs', mock.Mock(return_value=('a', 'b')))
  833. self.mock_object(
  834. self.driver, 'parse_zfs_answer',
  835. mock.Mock(side_effect=[[], [{'NAME': dataset_name}]]))
  836. self.assertRaises(
  837. exception.ShareResourceNotFound,
  838. self.driver.ensure_share,
  839. 'fake_context', share,
  840. )
  841. self.assertEqual(0, self.driver.get_zfs_option.call_count)
  842. self.assertEqual(0, self.driver._get_share_helper.call_count)
  843. self.driver.zfs.assert_called_once_with('list', '-r', 'bar')
  844. self.driver.parse_zfs_answer.assert_called_once_with('a')
  845. def test_ensure_share_with_share_server(self):
  846. self.assertRaises(
  847. exception.InvalidInput,
  848. self.driver.ensure_share,
  849. 'fake_context', 'fake_share', share_server={'id': 'fake_server'},
  850. )
  851. def test_get_network_allocations_number(self):
  852. self.assertEqual(0, self.driver.get_network_allocations_number())
  853. def test_extend_share(self):
  854. dataset_name = 'foo_zpool/foo_fs'
  855. self.mock_object(
  856. self.driver, '_get_dataset_name',
  857. mock.Mock(return_value=dataset_name))
  858. self.mock_object(self.driver, 'zfs')
  859. self.driver.extend_share('fake_share', 5)
  860. self.driver._get_dataset_name.assert_called_once_with('fake_share')
  861. self.driver.zfs.assert_called_once_with(
  862. 'set', 'quota=5G', dataset_name)
  863. def test_extend_share_with_share_server(self):
  864. self.assertRaises(
  865. exception.InvalidInput,
  866. self.driver.extend_share,
  867. 'fake_context', 'fake_share', 5,
  868. share_server={'id': 'fake_server'},
  869. )
  870. def test_shrink_share(self):
  871. dataset_name = 'foo_zpool/foo_fs'
  872. self.mock_object(
  873. self.driver, '_get_dataset_name',
  874. mock.Mock(return_value=dataset_name))
  875. self.mock_object(self.driver, 'zfs')
  876. self.mock_object(
  877. self.driver, 'get_zfs_option', mock.Mock(return_value='4G'))
  878. share = {'id': 'fake_share_id'}
  879. self.driver.shrink_share(share, 5)
  880. self.driver._get_dataset_name.assert_called_once_with(share)
  881. self.driver.get_zfs_option.assert_called_once_with(
  882. dataset_name, 'used')
  883. self.driver.zfs.assert_called_once_with(
  884. 'set', 'quota=5G', dataset_name)
  885. def test_shrink_share_data_loss(self):
  886. dataset_name = 'foo_zpool/foo_fs'
  887. self.mock_object(
  888. self.driver, '_get_dataset_name',
  889. mock.Mock(return_value=dataset_name))
  890. self.mock_object(self.driver, 'zfs')
  891. self.mock_object(
  892. self.driver, 'get_zfs_option', mock.Mock(return_value='6G'))
  893. share = {'id': 'fake_share_id'}
  894. self.assertRaises(
  895. exception.ShareShrinkingPossibleDataLoss,
  896. self.driver.shrink_share, share, 5)
  897. self.driver._get_dataset_name.assert_called_once_with(share)
  898. self.driver.get_zfs_option.assert_called_once_with(
  899. dataset_name, 'used')
  900. self.assertEqual(0, self.driver.zfs.call_count)
  901. def test_shrink_share_with_share_server(self):
  902. self.assertRaises(
  903. exception.InvalidInput,
  904. self.driver.shrink_share,
  905. 'fake_context', 'fake_share', 5,
  906. share_server={'id': 'fake_server'},
  907. )
  908. def test__get_replication_snapshot_prefix(self):
  909. replica = {'id': 'foo-_bar-_id'}
  910. self.driver.replica_snapshot_prefix = 'PrEfIx'
  911. result = self.driver._get_replication_snapshot_prefix(replica)
  912. self.assertEqual('PrEfIx_foo__bar__id', result)
  913. def test__get_replication_snapshot_tag(self):
  914. replica = {'id': 'foo-_bar-_id'}
  915. self.driver.replica_snapshot_prefix = 'PrEfIx'
  916. mock_utcnow = self.mock_object(zfs_driver.timeutils, 'utcnow')
  917. result = self.driver._get_replication_snapshot_tag(replica)
  918. self.assertEqual(
  919. ('PrEfIx_foo__bar__id_time_'
  920. '%s' % mock_utcnow.return_value.isoformat.return_value),
  921. result)
  922. mock_utcnow.assert_called_once_with()
  923. mock_utcnow.return_value.isoformat.assert_called_once_with()
  924. def test__get_active_replica(self):
  925. replica_list = [
  926. {'replica_state': zfs_driver.constants.REPLICA_STATE_IN_SYNC,
  927. 'id': '1'},
  928. {'replica_state': zfs_driver.constants.REPLICA_STATE_ACTIVE,
  929. 'id': '2'},
  930. {'replica_state': zfs_driver.constants.REPLICA_STATE_OUT_OF_SYNC,
  931. 'id': '3'},
  932. ]
  933. result = self.driver._get_active_replica(replica_list)
  934. self.assertEqual(replica_list[1], result)
  935. def test__get_active_replica_not_found(self):
  936. replica_list = [
  937. {'replica_state': zfs_driver.constants.REPLICA_STATE_IN_SYNC,
  938. 'id': '1'},
  939. {'replica_state': zfs_driver.constants.REPLICA_STATE_OUT_OF_SYNC,
  940. 'id': '3'},
  941. ]
  942. self.assertRaises(
  943. exception.ReplicationException,
  944. self.driver._get_active_replica,
  945. replica_list,
  946. )
  947. def test_update_access(self):
  948. self.mock_object(self.driver, '_get_dataset_name')
  949. mock_helper = self.mock_object(self.driver, '_get_share_helper')
  950. mock_shell_executor = self.mock_object(
  951. self.driver, '_get_shell_executor_by_host')
  952. share = {
  953. 'share_proto': 'NFS',
  954. 'host': 'foo_host@bar_backend@quuz_pool',
  955. }
  956. result = self.driver.update_access(
  957. 'fake_context', share, [1], [2], [3])
  958. self.driver._get_dataset_name.assert_called_once_with(share)
  959. mock_shell_executor.assert_called_once_with(share['host'])
  960. self.assertEqual(
  961. mock_helper.return_value.update_access.return_value,
  962. result,
  963. )
  964. def test_update_access_with_share_server(self):
  965. self.assertRaises(
  966. exception.InvalidInput,
  967. self.driver.update_access,
  968. 'fake_context', 'fake_share', [], [], [],
  969. share_server={'id': 'fake_server'},
  970. )
  971. @ddt.data(
  972. ({}, True),
  973. ({"size": 5}, True),
  974. ({"size": 5, "foo": "bar"}, False),
  975. ({"size": "5", "foo": "bar"}, True),
  976. )
  977. @ddt.unpack
  978. def test_manage_share_success_expected(self, driver_options, mount_exists):
  979. old_dataset_name = "foopool/path/to/old/dataset/name"
  980. new_dataset_name = "foopool/path/to/new/dataset/name"
  981. share = {
  982. "id": "fake_share_instance_id",
  983. "share_id": "fake_share_id",
  984. "export_locations": [{"path": "1.1.1.1:/%s" % old_dataset_name}],
  985. "host": "foobackend@foohost#foopool",
  986. "share_proto": "NFS",
  987. }
  988. mock_get_extra_specs_from_share = self.mock_object(
  989. zfs_driver.share_types,
  990. 'get_extra_specs_from_share',
  991. mock.Mock(return_value={}))
  992. mock_sleep = self.mock_object(zfs_driver.time, "sleep")
  993. mock__get_dataset_name = self.mock_object(
  994. self.driver, "_get_dataset_name",
  995. mock.Mock(return_value=new_dataset_name))
  996. mock_helper = self.mock_object(self.driver, "_get_share_helper")
  997. mock_zfs = self.mock_object(
  998. self.driver, "zfs",
  999. mock.Mock(return_value=("fake_out", "fake_error")))
  1000. mock_zfs_with_retry = self.mock_object(self.driver, "zfs_with_retry")
  1001. mock_execute = self.mock_object(self.driver, "execute")
  1002. if mount_exists:
  1003. mock_execute.return_value = "%s " % old_dataset_name, "fake_err"
  1004. else:
  1005. mock_execute.return_value = ("foo", "bar")
  1006. mock_parse_zfs_answer = self.mock_object(
  1007. self.driver,
  1008. "parse_zfs_answer",
  1009. mock.Mock(return_value=[
  1010. {"NAME": "some_other_dataset_1"},
  1011. {"NAME": old_dataset_name},
  1012. {"NAME": "some_other_dataset_2"},
  1013. ]))
  1014. mock_get_zfs_option = self.mock_object(
  1015. self.driver, 'get_zfs_option', mock.Mock(return_value="4G"))
  1016. result = self.driver.manage_existing(share, driver_options)
  1017. self.assertTrue(mock_helper.return_value.get_exports.called)
  1018. self.assertTrue(mock_zfs_with_retry.called)
  1019. self.assertEqual(2, len(result))
  1020. self.assertIn("size", result)
  1021. self.assertIn("export_locations", result)
  1022. self.assertEqual(5, result["size"])
  1023. self.assertEqual(
  1024. mock_helper.return_value.get_exports.return_value,
  1025. result["export_locations"])
  1026. if mount_exists:
  1027. mock_sleep.assert_called_once_with(1)
  1028. mock_execute.assert_called_once_with("sudo", "mount")
  1029. mock_parse_zfs_answer.assert_called_once_with(mock_zfs.return_value[0])
  1030. if driver_options.get("size"):
  1031. self.assertFalse(mock_get_zfs_option.called)
  1032. else:
  1033. mock_get_zfs_option.assert_called_once_with(
  1034. old_dataset_name, "used")
  1035. mock__get_dataset_name.assert_called_once_with(share)
  1036. mock_get_extra_specs_from_share.assert_called_once_with(share)
  1037. def test_manage_share_wrong_pool(self):
  1038. old_dataset_name = "foopool/path/to/old/dataset/name"
  1039. new_dataset_name = "foopool/path/to/new/dataset/name"
  1040. share = {
  1041. "id": "fake_share_instance_id",
  1042. "share_id": "fake_share_id",
  1043. "export_locations": [{"path": "1.1.1.1:/%s" % old_dataset_name}],
  1044. "host": "foobackend@foohost#barpool",
  1045. "share_proto": "NFS",
  1046. }
  1047. mock_get_extra_specs_from_share = self.mock_object(
  1048. zfs_driver.share_types,
  1049. 'get_extra_specs_from_share',
  1050. mock.Mock(return_value={}))
  1051. mock__get_dataset_name = self.mock_object(
  1052. self.driver, "_get_dataset_name",
  1053. mock.Mock(return_value=new_dataset_name))
  1054. mock_get_zfs_option = self.mock_object(
  1055. self.driver, 'get_zfs_option', mock.Mock(return_value="4G"))
  1056. self.assertRaises(
  1057. exception.ZFSonLinuxException,
  1058. self.driver.manage_existing,
  1059. share, {}
  1060. )
  1061. mock__get_dataset_name.assert_called_once_with(share)
  1062. mock_get_zfs_option.assert_called_once_with(old_dataset_name, "used")
  1063. mock_get_extra_specs_from_share.assert_called_once_with(share)
  1064. def test_manage_share_dataset_not_found(self):
  1065. old_dataset_name = "foopool/path/to/old/dataset/name"
  1066. new_dataset_name = "foopool/path/to/new/dataset/name"
  1067. share = {
  1068. "id": "fake_share_instance_id",
  1069. "share_id": "fake_share_id",
  1070. "export_locations": [{"path": "1.1.1.1:/%s" % old_dataset_name}],
  1071. "host": "foobackend@foohost#foopool",
  1072. "share_proto": "NFS",
  1073. }
  1074. mock_get_extra_specs_from_share = self.mock_object(
  1075. zfs_driver.share_types,
  1076. 'get_extra_specs_from_share',
  1077. mock.Mock(return_value={}))
  1078. mock__get_dataset_name = self.mock_object(
  1079. self.driver, "_get_dataset_name",
  1080. mock.Mock(return_value=new_dataset_name))
  1081. mock_get_zfs_option = self.mock_object(
  1082. self.driver, 'get_zfs_option', mock.Mock(return_value="4G"))
  1083. mock_zfs = self.mock_object(
  1084. self.driver, "zfs",
  1085. mock.Mock(return_value=("fake_out", "fake_error")))
  1086. mock_parse_zfs_answer = self.mock_object(
  1087. self.driver,
  1088. "parse_zfs_answer",
  1089. mock.Mock(return_value=[{"NAME": "some_other_dataset_1"}]))
  1090. self.assertRaises(
  1091. exception.ZFSonLinuxException,
  1092. self.driver.manage_existing,
  1093. share, {}
  1094. )
  1095. mock__get_dataset_name.assert_called_once_with(share)
  1096. mock_get_zfs_option.assert_called_once_with(old_dataset_name, "used")
  1097. mock_zfs.assert_called_once_with(
  1098. "list", "-r", old_dataset_name.split("/")[0])
  1099. mock_parse_zfs_answer.assert_called_once_with(mock_zfs.return_value[0])
  1100. mock_get_extra_specs_from_share.assert_called_once_with(share)
  1101. def test_unmanage(self):
  1102. share = {'id': 'fake_share_id'}
  1103. self.mock_object(self.driver.private_storage, 'delete')
  1104. self.driver.unmanage(share)
  1105. self.driver.private_storage.delete.assert_called_once_with(share['id'])
  1106. @ddt.data(
  1107. {},
  1108. {"size": 5},
  1109. {"size": "5"},
  1110. )
  1111. def test_manage_existing_snapshot(self, driver_options):
  1112. dataset_name = "path/to/dataset"
  1113. old_provider_location = dataset_name + "@original_snapshot_tag"
  1114. snapshot_instance = {
  1115. "id": "fake_snapshot_instance_id",
  1116. "share_instance_id": "fake_share_instance_id",
  1117. "snapshot_id": "fake_snapshot_id",
  1118. "provider_location": old_provider_location,
  1119. }
  1120. new_snapshot_tag = "fake_new_snapshot_tag"
  1121. new_provider_location = (
  1122. old_provider_location.split("@")[0] + "@" + new_snapshot_tag)
  1123. self.mock_object(self.driver, "zfs")
  1124. self.mock_object(
  1125. self.driver, "get_zfs_option", mock.Mock(return_value="5G"))
  1126. self.mock_object(
  1127. self.driver,
  1128. '_get_snapshot_name',
  1129. mock.Mock(return_value=new_snapshot_tag))
  1130. self.driver.private_storage.update(
  1131. snapshot_instance["share_instance_id"],
  1132. {"dataset_name": dataset_name})
  1133. result = self.driver.manage_existing_snapshot(
  1134. snapshot_instance, driver_options)
  1135. expected_result = {
  1136. "size": 5,
  1137. "provider_location": new_provider_location,
  1138. }
  1139. self.assertEqual(expected_result, result)
  1140. self.driver._get_snapshot_name.assert_called_once_with(
  1141. snapshot_instance["id"])
  1142. self.driver.zfs.assert_has_calls([
  1143. mock.call("list", "-r", "-t", "snapshot", old_provider_location),
  1144. mock.call("rename", old_provider_location, new_provider_location),
  1145. ])
  1146. def test_manage_existing_snapshot_not_found(self):
  1147. dataset_name = "path/to/dataset"
  1148. old_provider_location = dataset_name + "@original_snapshot_tag"
  1149. new_snapshot_tag = "fake_new_snapshot_tag"
  1150. snapshot_instance = {
  1151. "id": "fake_snapshot_instance_id",
  1152. "snapshot_id": "fake_snapshot_id",
  1153. "provider_location": old_provider_location,
  1154. }
  1155. self.mock_object(
  1156. self.driver, "_get_snapshot_name",
  1157. mock.Mock(return_value=new_snapshot_tag))
  1158. self.mock_object(
  1159. self.driver, "zfs",
  1160. mock.Mock(side_effect=exception.ProcessExecutionError("FAKE")))
  1161. self.assertRaises(
  1162. exception.ManageInvalidShareSnapshot,
  1163. self.driver.manage_existing_snapshot,
  1164. snapshot_instance, {},
  1165. )
  1166. self.driver.zfs.assert_called_once_with(
  1167. "list", "-r", "-t", "snapshot", old_provider_location)
  1168. self.driver._get_snapshot_name.assert_called_once_with(
  1169. snapshot_instance["id"])
  1170. def test_unmanage_snapshot(self):
  1171. snapshot_instance = {
  1172. "id": "fake_snapshot_instance_id",
  1173. "snapshot_id": "fake_snapshot_id",
  1174. }
  1175. self.mock_object(self.driver.private_storage, "delete")
  1176. self.driver.unmanage_snapshot(snapshot_instance)
  1177. self.driver.private_storage.delete.assert_called_once_with(
  1178. snapshot_instance["snapshot_id"])
  1179. def test__delete_dataset_or_snapshot_with_retry_snapshot(self):
  1180. self.mock_object(self.driver, 'get_zfs_option')
  1181. self.mock_object(self.driver, 'zfs')
  1182. self.driver._delete_dataset_or_snapshot_with_retry('foo@bar')
  1183. self.driver.get_zfs_option.assert_called_once_with(
  1184. 'foo@bar', 'mountpoint')
  1185. self.driver.zfs.assert_called_once_with(
  1186. 'destroy', '-f', 'foo@bar')
  1187. def test__delete_dataset_or_snapshot_with_retry_of(self):
  1188. self.mock_object(self.driver, 'get_zfs_option')
  1189. self.mock_object(
  1190. self.driver, 'execute', mock.Mock(return_value=('a', 'b')))
  1191. self.mock_object(zfs_driver.time, 'sleep')
  1192. self.mock_object(zfs_driver.LOG, 'debug')
  1193. self.mock_object(
  1194. zfs_driver.time, 'time', mock.Mock(side_effect=range(1, 70, 2)))
  1195. dataset_name = 'fake/dataset/name'
  1196. self.assertRaises(
  1197. exception.ZFSonLinuxException,
  1198. self.driver._delete_dataset_or_snapshot_with_retry,
  1199. dataset_name,
  1200. )
  1201. self.driver.get_zfs_option.assert_called_once_with(
  1202. dataset_name, 'mountpoint')
  1203. self.assertEqual(29, zfs_driver.LOG.debug.call_count)
  1204. def test__delete_dataset_or_snapshot_with_retry_temp_of(self):
  1205. self.mock_object(self.driver, 'get_zfs_option')
  1206. self.mock_object(self.driver, 'zfs')
  1207. self.mock_object(
  1208. self.driver, 'execute', mock.Mock(side_effect=[
  1209. ('a', 'b'),
  1210. exception.ProcessExecutionError(
  1211. 'FAKE lsof returns not found')]))
  1212. self.mock_object(zfs_driver.time, 'sleep')
  1213. self.mock_object(zfs_driver.LOG, 'debug')
  1214. self.mock_object(
  1215. zfs_driver.time, 'time', mock.Mock(side_effect=range(1, 70, 2)))
  1216. dataset_name = 'fake/dataset/name'
  1217. self.driver._delete_dataset_or_snapshot_with_retry(dataset_name)
  1218. self.driver.get_zfs_option.assert_called_once_with(
  1219. dataset_name, 'mountpoint')
  1220. self.assertEqual(2, self.driver.execute.call_count)
  1221. self.assertEqual(1, zfs_driver.LOG.debug.call_count)
  1222. zfs_driver.LOG.debug.assert_called_once_with(
  1223. mock.ANY, {'name': dataset_name, 'out': 'a'})
  1224. zfs_driver.time.sleep.assert_called_once_with(2)
  1225. self.driver.zfs.assert_called_once_with('destroy', '-f', dataset_name)
  1226. def test__delete_dataset_or_snapshot_with_retry_busy(self):
  1227. self.mock_object(self.driver, 'get_zfs_option')
  1228. self.mock_object(
  1229. self.driver, 'execute', mock.Mock(
  1230. side_effect=exception.ProcessExecutionError(
  1231. 'FAKE lsof returns not found')))
  1232. self.mock_object(
  1233. self.driver, 'zfs', mock.Mock(side_effect=[
  1234. exception.ProcessExecutionError(
  1235. 'cannot destroy FAKE: dataset is busy\n'),
  1236. None, None]))
  1237. self.mock_object(zfs_driver.time, 'sleep')
  1238. self.mock_object(zfs_driver.LOG, 'info')
  1239. dataset_name = 'fake/dataset/name'
  1240. self.driver._delete_dataset_or_snapshot_with_retry(dataset_name)
  1241. self.driver.get_zfs_option.assert_called_once_with(
  1242. dataset_name, 'mountpoint')
  1243. self.assertEqual(2, zfs_driver.time.sleep.call_count)
  1244. self.assertEqual(2, self.driver.execute.call_count)
  1245. self.assertEqual(1, zfs_driver.LOG.info.call_count)
  1246. self.assertEqual(2, self.driver.zfs.call_count)
  1247. def test_create_replica(self):
  1248. active_replica = {
  1249. 'id': 'fake_active_replica_id',
  1250. 'host': 'hostname1@backend_name1#foo',
  1251. 'size': 5,
  1252. 'replica_state': zfs_driver.constants.REPLICA_STATE_ACTIVE,
  1253. }
  1254. replica_list = [active_replica]
  1255. new_replica = {
  1256. 'id': 'fake_new_replica_id',
  1257. 'host': 'hostname2@backend_name2#bar',
  1258. 'share_proto': 'NFS',
  1259. 'replica_state': None,
  1260. }
  1261. dst_dataset_name = (
  1262. 'bar/subbar/fake_dataset_name_prefix%s' % new_replica['id'])
  1263. access_rules = ['foo_rule', 'bar_rule']
  1264. self.driver.private_storage.update(
  1265. active_replica['id'],
  1266. {'dataset_name': 'fake/active/dataset/name',
  1267. 'ssh_cmd': 'fake_ssh_cmd'}
  1268. )
  1269. self.mock_object(
  1270. self.driver, 'execute',
  1271. mock.Mock(side_effect=[('a', 'b'), ('c', 'd')]))
  1272. self.mock_object(self.driver, 'zfs')
  1273. mock_helper = self.mock_object(self.driver, '_get_share_helper')
  1274. self.configuration.zfs_dataset_name_prefix = 'fake_dataset_name_prefix'
  1275. mock_utcnow = self.mock_object(zfs_driver.timeutils, 'utcnow')
  1276. mock_utcnow.return_value.isoformat.return_value = 'some_time'
  1277. result = self.driver.create_replica(
  1278. 'fake_context', replica_list, new_replica, access_rules, [])
  1279. expected = {
  1280. 'export_locations': (
  1281. mock_helper.return_value.create_exports.return_value),
  1282. 'replica_state': zfs_driver.constants.REPLICA_STATE_IN_SYNC,
  1283. 'access_rules_status': zfs_driver.constants.STATUS_ACTIVE,
  1284. }
  1285. self.assertEqual(expected, result)
  1286. mock_helper.assert_has_calls([
  1287. mock.call('NFS'),
  1288. mock.call().update_access(
  1289. dst_dataset_name, access_rules, add_rules=[],
  1290. delete_rules=[], make_all_ro=True),
  1291. mock.call('NFS'),
  1292. mock.call().create_exports(dst_dataset_name),
  1293. ])
  1294. self.driver.zfs.assert_has_calls([
  1295. mock.call('set', 'readonly=on', dst_dataset_name),
  1296. mock.call('set', 'quota=%sG' % active_replica['size'],
  1297. dst_dataset_name),
  1298. ])
  1299. src_snapshot_name = (
  1300. 'fake/active/dataset/name@'
  1301. 'tmp_snapshot_for_replication__fake_new_replica_id_time_some_time')
  1302. self.driver.execute.assert_has_calls([
  1303. mock.call('ssh', 'fake_ssh_cmd', 'sudo', 'zfs', 'snapshot',
  1304. src_snapshot_name),
  1305. mock.call(
  1306. 'ssh', 'fake_ssh_cmd',
  1307. 'sudo', 'zfs', 'send', '-vDR', src_snapshot_name, '|',
  1308. 'ssh', 'fake_username@240.241.242.244',
  1309. 'sudo', 'zfs', 'receive', '-v', dst_dataset_name
  1310. ),
  1311. ])
  1312. mock_utcnow.assert_called_once_with()
  1313. mock_utcnow.return_value.isoformat.assert_called_once_with()
  1314. def test_delete_replica_not_found(self):
  1315. dataset_name = 'foo/dataset/name'
  1316. pool_name = 'foo_pool'
  1317. replica = {'id': 'fake_replica_id'}
  1318. replica_list = [replica]
  1319. replica_snapshots = []
  1320. self.mock_object(
  1321. self.driver, '_get_dataset_name',
  1322. mock.Mock(return_value=dataset_name))
  1323. self.mock_object(
  1324. self.driver, 'zfs',
  1325. mock.Mock(side_effect=[('a', 'b'), ('c', 'd')]))
  1326. self.mock_object(
  1327. self.driver, 'parse_zfs_answer', mock.Mock(side_effect=[[], []]))
  1328. self.mock_object(self.driver, '_delete_dataset_or_snapshot_with_retry')
  1329. self.mock_object(zfs_driver.LOG, 'warning')
  1330. self.mock_object(self.driver, '_get_share_helper')
  1331. self.driver.private_storage.update(
  1332. replica['id'], {'pool_name': pool_name})
  1333. self.driver.delete_replica('fake_context', replica_list,
  1334. replica_snapshots, replica)
  1335. zfs_driver.LOG.warning.assert_called_once_with(
  1336. mock.ANY, {'id': replica['id'], 'name': dataset_name})
  1337. self.assertEqual(0, self.driver._get_share_helper.call_count)
  1338. self.assertEqual(
  1339. 0, self.driver._delete_dataset_or_snapshot_with_retry.call_count)
  1340. self.driver._get_dataset_name.assert_called_once_with(replica)
  1341. self.driver.zfs.assert_has_calls([
  1342. mock.call('list', '-r', '-t', 'snapshot', pool_name),
  1343. mock.call('list', '-r', pool_name),
  1344. ])
  1345. self.driver.parse_zfs_answer.assert_has_calls([
  1346. mock.call('a'), mock.call('c'),
  1347. ])
  1348. def test_delete_replica(self):
  1349. dataset_name = 'foo/dataset/name'
  1350. pool_name = 'foo_pool'
  1351. replica = {'id': 'fake_replica_id', 'share_proto': 'NFS'}
  1352. replica_list = [replica]
  1353. self.mock_object(
  1354. self.driver, '_get_dataset_name',
  1355. mock.Mock(return_value=dataset_name))
  1356. self.mock_object(
  1357. self.driver, 'zfs',
  1358. mock.Mock(side_effect=[('a', 'b'), ('c', 'd')]))
  1359. self.mock_object(
  1360. self.driver, 'parse_zfs_answer', mock.Mock(side_effect=[
  1361. [{'NAME': 'some_other_dataset@snapshot'},
  1362. {'NAME': dataset_name + '@foo_snap'}],
  1363. [{'NAME': 'some_other_dataset'},
  1364. {'NAME': dataset_name}],
  1365. ]))
  1366. mock_helper = self.mock_object(self.driver, '_get_share_helper')
  1367. self.mock_object(self.driver, '_delete_dataset_or_snapshot_with_retry')
  1368. self.mock_object(zfs_driver.LOG, 'warning')
  1369. self.driver.private_storage.update(
  1370. replica['id'],
  1371. {'pool_name': pool_name, 'dataset_name': dataset_name})
  1372. self.driver.delete_replica('fake_context', replica_list, [], replica)
  1373. self.assertEqual(0, zfs_driver.LOG.warning.call_count)
  1374. self.assertEqual(0, self.driver._get_dataset_name.call_count)
  1375. self.driver._delete_dataset_or_snapshot_with_retry.assert_has_calls([
  1376. mock.call(dataset_name + '@foo_snap'),
  1377. mock.call(dataset_name),
  1378. ])
  1379. self.driver.zfs.assert_has_calls([
  1380. mock.call('list', '-r', '-t', 'snapshot', pool_name),
  1381. mock.call('list', '-r', pool_name),
  1382. ])
  1383. self.driver.parse_zfs_answer.assert_has_calls([
  1384. mock.call('a'), mock.call('c'),
  1385. ])
  1386. mock_helper.assert_called_once_with(replica['share_proto'])
  1387. mock_helper.return_value.remove_exports.assert_called_once_with(
  1388. dataset_name)
  1389. def test_update_replica(self):
  1390. active_replica = {
  1391. 'id': 'fake_active_replica_id',
  1392. 'host': 'hostname1@backend_name1#foo',
  1393. 'size': 5,
  1394. 'replica_state': zfs_driver.constants.REPLICA_STATE_ACTIVE,
  1395. }
  1396. replica = {
  1397. 'id': 'fake_new_replica_id',
  1398. 'host': 'hostname2@backend_name2#bar',
  1399. 'share_proto': 'NFS',
  1400. 'replica_state': None,
  1401. }
  1402. replica_list = [replica, active_replica]
  1403. replica_snapshots = []
  1404. dst_dataset_name = (
  1405. 'bar/subbar/fake_dataset_name_prefix%s' % replica['id'])
  1406. src_dataset_name = (
  1407. 'bar/subbar/fake_dataset_name_prefix%s' % active_replica['id'])
  1408. access_rules = ['foo_rule', 'bar_rule']
  1409. old_repl_snapshot_tag = (
  1410. self.driver._get_replication_snapshot_prefix(
  1411. active_replica) + 'foo')
  1412. snap_tag_prefix = self.driver._get_replication_snapshot_prefix(
  1413. replica)
  1414. self.driver.private_storage.update(
  1415. active_replica['id'],
  1416. {'dataset_name': src_dataset_name,
  1417. 'ssh_cmd': 'fake_src_ssh_cmd',
  1418. 'repl_snapshot_tag': old_repl_snapshot_tag}
  1419. )
  1420. self.driver.private_storage.update(
  1421. replica['id'],
  1422. {'dataset_name': dst_dataset_name,
  1423. 'ssh_cmd': 'fake_dst_ssh_cmd',
  1424. 'repl_snapshot_tag': old_repl_snapshot_tag}
  1425. )
  1426. self.mock_object(
  1427. self.driver, 'execute',
  1428. mock.Mock(side_effect=[('a', 'b'), ('c', 'd'), ('e', 'f')]))
  1429. self.mock_object(self.driver, 'execute_with_retry',
  1430. mock.Mock(side_effect=[('g', 'h')]))
  1431. self.mock_object(self.driver, 'zfs',
  1432. mock.Mock(side_effect=[('j', 'k'), ('l', 'm')]))
  1433. self.mock_object(
  1434. self.driver, 'parse_zfs_answer',
  1435. mock.Mock(side_effect=[
  1436. ({'NAME': dst_dataset_name + '@' + old_repl_snapshot_tag},
  1437. {'NAME': dst_dataset_name + '@%s_time_some_time' %
  1438. snap_tag_prefix},
  1439. {'NAME': 'other/dataset/name1@' + old_repl_snapshot_tag}),
  1440. ({'NAME': src_dataset_name + '@' + old_repl_snapshot_tag},
  1441. {'NAME': src_dataset_name + '@' + snap_tag_prefix + 'quuz'},
  1442. {'NAME': 'other/dataset/name2@' + old_repl_snapshot_tag}),
  1443. ])
  1444. )
  1445. mock_helper = self.mock_object(self.driver, '_get_share_helper')
  1446. self.configuration.zfs_dataset_name_prefix = 'fake_dataset_name_prefix'
  1447. mock_utcnow = self.mock_object(zfs_driver.timeutils, 'utcnow')
  1448. mock_utcnow.return_value.isoformat.return_value = 'some_time'
  1449. mock_delete_snapshot = self.mock_object(
  1450. self.driver, '_delete_dataset_or_snapshot_with_retry')
  1451. result = self.driver.update_replica_state(
  1452. 'fake_context', replica_list, replica, access_rules,
  1453. replica_snapshots)
  1454. self.assertEqual(zfs_driver.constants.REPLICA_STATE_IN_SYNC, result)
  1455. mock_helper.assert_called_once_with('NFS')
  1456. mock_helper.return_value.update_access.assert_called_once_with(
  1457. dst_dataset_name, access_rules, add_rules=[], delete_rules=[],
  1458. make_all_ro=True)
  1459. self.driver.execute_with_retry.assert_called_once_with(
  1460. 'ssh', 'fake_src_ssh_cmd', 'sudo', 'zfs', 'destroy', '-f',
  1461. src_dataset_name + '@' + snap_tag_prefix + 'quuz')
  1462. self.driver.execute.assert_has_calls([
  1463. mock.call(
  1464. 'ssh', 'fake_src_ssh_cmd', 'sudo', 'zfs', 'snapshot',
  1465. src_dataset_name + '@' +
  1466. self.driver._get_replication_snapshot_tag(replica)),
  1467. mock.call(
  1468. 'ssh', 'fake_src_ssh_cmd', 'sudo', 'zfs', 'send',
  1469. '-vDRI', old_repl_snapshot_tag,
  1470. src_dataset_name + '@%s' % snap_tag_prefix + '_time_some_time',
  1471. '|', 'ssh', 'fake_dst_ssh_cmd',
  1472. 'sudo', 'zfs', 'receive', '-vF', dst_dataset_name),
  1473. mock.call(
  1474. 'ssh', 'fake_src_ssh_cmd',
  1475. 'sudo', 'zfs', 'list', '-r', '-t', 'snapshot', 'bar'),
  1476. ])
  1477. mock_delete_snapshot.assert_called_once_with(
  1478. dst_dataset_name + '@' + old_repl_snapshot_tag)
  1479. self.driver.parse_zfs_answer.assert_has_calls(
  1480. [mock.call('l'), mock.call('e')])
  1481. def test_promote_replica_active_available(self):
  1482. active_replica = {
  1483. 'id': 'fake_active_replica_id',
  1484. 'host': 'hostname1@backend_name1#foo',
  1485. 'size': 5,
  1486. 'replica_state': zfs_driver.constants.REPLICA_STATE_ACTIVE,
  1487. }
  1488. replica = {
  1489. 'id': 'fake_first_replica_id',
  1490. 'host': 'hostname2@backend_name2#bar',
  1491. 'share_proto': 'NFS',
  1492. 'replica_state': zfs_driver.constants.REPLICA_STATE_IN_SYNC,
  1493. }
  1494. second_replica = {
  1495. 'id': 'fake_second_replica_id',
  1496. 'host': 'hostname3@backend_name3#quuz',
  1497. 'share_proto': 'NFS',
  1498. 'replica_state': zfs_driver.constants.REPLICA_STATE_IN_SYNC,
  1499. }
  1500. replica_list = [replica, active_replica, second_replica]
  1501. dst_dataset_name = (
  1502. 'bar/subbar/fake_dataset_name_prefix%s' % replica['id'])
  1503. src_dataset_name = (
  1504. 'bar/subbar/fake_dataset_name_prefix%s' % active_replica['id'])
  1505. access_rules = ['foo_rule', 'bar_rule']
  1506. old_repl_snapshot_tag = (
  1507. self.driver._get_replication_snapshot_prefix(
  1508. active_replica) + 'foo')
  1509. snap_tag_prefix = self.driver._get_replication_snapshot_prefix(
  1510. active_replica) + '_time_some_time'
  1511. self.driver.private_storage.update(
  1512. active_replica['id'],
  1513. {'dataset_name': src_dataset_name,
  1514. 'ssh_cmd': 'fake_src_ssh_cmd',
  1515. 'repl_snapshot_tag': old_repl_snapshot_tag}
  1516. )
  1517. for repl in (replica, second_replica):
  1518. self.driver.private_storage.update(
  1519. repl['id'],
  1520. {'dataset_name': (
  1521. 'bar/subbar/fake_dataset_name_prefix%s' % repl['id']),
  1522. 'ssh_cmd': 'fake_dst_ssh_cmd',
  1523. 'repl_snapshot_tag': old_repl_snapshot_tag}
  1524. )
  1525. self.mock_object(
  1526. self.driver, 'execute',
  1527. mock.Mock(side_effect=[
  1528. ('a', 'b'),
  1529. ('c', 'd'),
  1530. ('e', 'f'),
  1531. exception.ProcessExecutionError('Second replica sync failure'),
  1532. ]))
  1533. self.mock_object(self.driver, 'zfs',
  1534. mock.Mock(side_effect=[('g', 'h')]))
  1535. mock_helper = self.mock_object(self.driver, '_get_share_helper')
  1536. self.configuration.zfs_dataset_name_prefix = 'fake_dataset_name_prefix'
  1537. mock_utcnow = self.mock_object(zfs_driver.timeutils, 'utcnow')
  1538. mock_utcnow.return_value.isoformat.return_value = 'some_time'
  1539. mock_delete_snapshot = self.mock_object(
  1540. self.driver, '_delete_dataset_or_snapshot_with_retry')
  1541. result = self.driver.promote_replica(
  1542. 'fake_context', replica_list, replica, access_rules)
  1543. expected = [
  1544. {'access_rules_status':
  1545. zfs_driver.constants.SHARE_INSTANCE_RULES_SYNCING,
  1546. 'id': 'fake_active_replica_id',
  1547. 'replica_state': zfs_driver.constants.REPLICA_STATE_IN_SYNC},
  1548. {'access_rules_status': zfs_driver.constants.STATUS_ACTIVE,
  1549. 'id': 'fake_first_replica_id',
  1550. 'replica_state': zfs_driver.constants.REPLICA_STATE_ACTIVE},
  1551. {'access_rules_status':
  1552. zfs_driver.constants.SHARE_INSTANCE_RULES_SYNCING,
  1553. 'id': 'fake_second_replica_id',
  1554. 'replica_state': zfs_driver.constants.REPLICA_STATE_OUT_OF_SYNC},
  1555. ]
  1556. for repl in expected:
  1557. self.assertIn(repl, result)
  1558. self.assertEqual(3, len(result))
  1559. mock_helper.assert_called_once_with('NFS')
  1560. mock_helper.return_value.update_access.assert_called_once_with(
  1561. dst_dataset_name, access_rules, add_rules=[], delete_rules=[])
  1562. self.driver.zfs.assert_called_once_with(
  1563. 'set', 'readonly=off', dst_dataset_name)
  1564. self.assertEqual(0, mock_delete_snapshot.call_count)
  1565. for repl in (active_replica, replica):
  1566. self.assertEqual(
  1567. snap_tag_prefix,
  1568. self.driver.private_storage.get(
  1569. repl['id'], 'repl_snapshot_tag'))
  1570. self.assertEqual(
  1571. old_repl_snapshot_tag,
  1572. self.driver.private_storage.get(
  1573. second_replica['id'], 'repl_snapshot_tag'))
  1574. def test_promote_replica_active_not_available(self):
  1575. active_replica = {
  1576. 'id': 'fake_active_replica_id',
  1577. 'host': 'hostname1@backend_name1#foo',
  1578. 'size': 5,
  1579. 'replica_state': zfs_driver.constants.REPLICA_STATE_ACTIVE,
  1580. }
  1581. replica = {
  1582. 'id': 'fake_first_replica_id',
  1583. 'host': 'hostname2@backend_name2#bar',
  1584. 'share_proto': 'NFS',
  1585. 'replica_state': zfs_driver.constants.REPLICA_STATE_IN_SYNC,
  1586. }
  1587. second_replica = {
  1588. 'id': 'fake_second_replica_id',
  1589. 'host': 'hostname3@backend_name3#quuz',
  1590. 'share_proto': 'NFS',
  1591. 'replica_state': zfs_driver.constants.REPLICA_STATE_IN_SYNC,
  1592. }
  1593. third_replica = {
  1594. 'id': 'fake_third_replica_id',
  1595. 'host': 'hostname4@backend_name4#fff',
  1596. 'share_proto': 'NFS',
  1597. 'replica_state': zfs_driver.constants.REPLICA_STATE_IN_SYNC,
  1598. }
  1599. replica_list = [replica, active_replica, second_replica, third_replica]
  1600. dst_dataset_name = (
  1601. 'bar/subbar/fake_dataset_name_prefix%s' % replica['id'])
  1602. src_dataset_name = (
  1603. 'bar/subbar/fake_dataset_name_prefix%s' % active_replica['id'])
  1604. access_rules = ['foo_rule', 'bar_rule']
  1605. old_repl_snapshot_tag = (
  1606. self.driver._get_replication_snapshot_prefix(
  1607. active_replica) + 'foo')
  1608. snap_tag_prefix = self.driver._get_replication_snapshot_prefix(
  1609. replica) + '_time_some_time'
  1610. self.driver.private_storage.update(
  1611. active_replica['id'],
  1612. {'dataset_name': src_dataset_name,
  1613. 'ssh_cmd': 'fake_src_ssh_cmd',
  1614. 'repl_snapshot_tag': old_repl_snapshot_tag}
  1615. )
  1616. for repl in (replica, second_replica, third_replica):
  1617. self.driver.private_storage.update(
  1618. repl['id'],
  1619. {'dataset_name': (
  1620. 'bar/subbar/fake_dataset_name_prefix%s' % repl['id']),
  1621. 'ssh_cmd': 'fake_dst_ssh_cmd',
  1622. 'repl_snapshot_tag': old_repl_snapshot_tag}
  1623. )
  1624. self.mock_object(
  1625. self.driver, 'execute',
  1626. mock.Mock(side_effect=[
  1627. exception.ProcessExecutionError('Active replica failure'),
  1628. ('a', 'b'),
  1629. exception.ProcessExecutionError('Second replica sync failure'),
  1630. ('c', 'd'),
  1631. ]))
  1632. self.mock_object(self.driver, 'zfs',
  1633. mock.Mock(side_effect=[('g', 'h'), ('i', 'j')]))
  1634. mock_helper = self.mock_object(self.driver, '_get_share_helper')
  1635. self.configuration.zfs_dataset_name_prefix = 'fake_dataset_name_prefix'
  1636. mock_utcnow = self.mock_object(zfs_driver.timeutils, 'utcnow')
  1637. mock_utcnow.return_value.isoformat.return_value = 'some_time'
  1638. mock_delete_snapshot = self.mock_object(
  1639. self.driver, '_delete_dataset_or_snapshot_with_retry')
  1640. result = self.driver.promote_replica(
  1641. 'fake_context', replica_list, replica, access_rules)
  1642. expected = [
  1643. {'access_rules_status':
  1644. zfs_driver.constants.SHARE_INSTANCE_RULES_SYNCING,
  1645. 'id': 'fake_active_replica_id',
  1646. 'replica_state': zfs_driver.constants.REPLICA_STATE_OUT_OF_SYNC},
  1647. {'access_rules_status': zfs_driver.constants.STATUS_ACTIVE,
  1648. 'id': 'fake_first_replica_id',
  1649. 'replica_state': zfs_driver.constants.REPLICA_STATE_ACTIVE},
  1650. {'access_rules_status':
  1651. zfs_driver.constants.SHARE_INSTANCE_RULES_SYNCING,
  1652. 'id': 'fake_second_replica_id'},
  1653. {'access_rules_status':
  1654. zfs_driver.constants.SHARE_INSTANCE_RULES_SYNCING,
  1655. 'id': 'fake_third_replica_id',
  1656. 'replica_state': zfs_driver.constants.REPLICA_STATE_OUT_OF_SYNC},
  1657. ]
  1658. for repl in expected:
  1659. self.assertIn(repl, result)
  1660. self.assertEqual(4, len(result))
  1661. mock_helper.assert_called_once_with('NFS')
  1662. mock_helper.return_value.update_access.assert_called_once_with(
  1663. dst_dataset_name, access_rules, add_rules=[], delete_rules=[])
  1664. self.driver.zfs.assert_has_calls([
  1665. mock.call('snapshot', dst_dataset_name + '@' + snap_tag_prefix),
  1666. mock.call('set', 'readonly=off', dst_dataset_name),
  1667. ])
  1668. self.assertEqual(0, mock_delete_snapshot.call_count)
  1669. for repl in (second_replica, replica):
  1670. self.assertEqual(
  1671. snap_tag_prefix,
  1672. self.driver.private_storage.get(
  1673. repl['id'], 'repl_snapshot_tag'))
  1674. for repl in (active_replica, third_replica):
  1675. self.assertEqual(
  1676. old_repl_snapshot_tag,
  1677. self.driver.private_storage.get(
  1678. repl['id'], 'repl_snapshot_tag'))
  1679. def test_create_replicated_snapshot(self):
  1680. active_replica = {
  1681. 'id': 'fake_active_replica_id',
  1682. 'replica_state': zfs_driver.constants.REPLICA_STATE_ACTIVE,
  1683. }
  1684. replica = {
  1685. 'id': 'fake_first_replica_id',
  1686. 'replica_state': zfs_driver.constants.REPLICA_STATE_IN_SYNC,
  1687. }
  1688. second_replica = {
  1689. 'id': 'fake_second_replica_id',
  1690. 'replica_state': zfs_driver.constants.REPLICA_STATE_IN_SYNC,
  1691. }
  1692. replica_list = [replica, active_replica, second_replica]
  1693. snapshot_instances = [
  1694. {'id': 'si_%s' % r['id'], 'share_instance_id': r['id'],
  1695. 'snapshot_id': 'some_snapshot_id'}
  1696. for r in replica_list
  1697. ]
  1698. src_dataset_name = (
  1699. 'bar/subbar/fake_dataset_name_prefix%s' % active_replica['id'])
  1700. old_repl_snapshot_tag = (
  1701. self.driver._get_replication_snapshot_prefix(
  1702. active_replica) + 'foo')
  1703. self.driver.private_storage.update(
  1704. active_replica['id'],
  1705. {'dataset_name': src_dataset_name,
  1706. 'ssh_cmd': 'fake_src_ssh_cmd',
  1707. 'repl_snapshot_tag': old_repl_snapshot_tag}
  1708. )
  1709. for repl in (replica, second_replica):
  1710. self.driver.private_storage.update(
  1711. repl['id'],
  1712. {'dataset_name': (
  1713. 'bar/subbar/fake_dataset_name_prefix%s' % repl['id']),
  1714. 'ssh_cmd': 'fake_dst_ssh_cmd',
  1715. 'repl_snapshot_tag': old_repl_snapshot_tag}
  1716. )
  1717. self.mock_object(
  1718. self.driver, 'execute', mock.Mock(side_effect=[
  1719. ('a', 'b'),
  1720. ('c', 'd'),
  1721. ('e', 'f'),
  1722. exception.ProcessExecutionError('Second replica sync failure'),
  1723. ]))
  1724. self.configuration.zfs_dataset_name_prefix = 'fake_dataset_name_prefix'
  1725. self.configuration.zfs_dataset_snapshot_name_prefix = (
  1726. 'fake_dataset_snapshot_name_prefix')
  1727. snap_tag_prefix = (
  1728. self.configuration.zfs_dataset_snapshot_name_prefix +
  1729. 'si_%s' % active_replica['id'])
  1730. repl_snap_tag = 'fake_repl_tag'
  1731. self.mock_object(
  1732. self.driver, '_get_replication_snapshot_tag',
  1733. mock.Mock(return_value=repl_snap_tag))
  1734. result = self.driver.create_replicated_snapshot(
  1735. 'fake_context', replica_list, snapshot_instances)
  1736. expected = [
  1737. {'id': 'si_fake_active_replica_id',
  1738. 'status': zfs_driver.constants.STATUS_AVAILABLE},
  1739. {'id': 'si_fake_first_replica_id',
  1740. 'status': zfs_driver.constants.STATUS_AVAILABLE},
  1741. {'id': 'si_fake_second_replica_id',
  1742. 'status': zfs_driver.constants.STATUS_ERROR},
  1743. ]
  1744. for repl in expected:
  1745. self.assertIn(repl, result)
  1746. self.assertEqual(3, len(result))
  1747. for repl in (active_replica, replica):
  1748. self.assertEqual(
  1749. repl_snap_tag,
  1750. self.driver.private_storage.get(
  1751. repl['id'], 'repl_snapshot_tag'))
  1752. self.assertEqual(
  1753. old_repl_snapshot_tag,
  1754. self.driver.private_storage.get(
  1755. second_replica['id'], 'repl_snapshot_tag'))
  1756. self.assertEqual(
  1757. snap_tag_prefix,
  1758. self.driver.private_storage.get(
  1759. snapshot_instances[0]['snapshot_id'], 'snapshot_tag'))
  1760. self.driver._get_replication_snapshot_tag.assert_called_once_with(
  1761. active_replica)
  1762. def test_delete_replicated_snapshot(self):
  1763. active_replica = {
  1764. 'id': 'fake_active_replica_id',
  1765. 'replica_state': zfs_driver.constants.REPLICA_STATE_ACTIVE,
  1766. }
  1767. replica = {
  1768. 'id': 'fake_first_replica_id',
  1769. 'replica_state': zfs_driver.constants.REPLICA_STATE_IN_SYNC,
  1770. }
  1771. second_replica = {
  1772. 'id': 'fake_second_replica_id',
  1773. 'replica_state': zfs_driver.constants.REPLICA_STATE_IN_SYNC,
  1774. }
  1775. replica_list = [replica, active_replica, second_replica]
  1776. active_snapshot_instance = {
  1777. 'id': 'si_%s' % active_replica['id'],
  1778. 'share_instance_id': active_replica['id'],
  1779. 'snapshot_id': 'some_snapshot_id',
  1780. 'share_id': 'some_share_id',
  1781. }
  1782. snapshot_instances = [
  1783. {'id': 'si_%s' % r['id'], 'share_instance_id': r['id'],
  1784. 'snapshot_id': active_snapshot_instance['snapshot_id'],
  1785. 'share_id': active_snapshot_instance['share_id']}
  1786. for r in (replica, second_replica)
  1787. ]
  1788. snapshot_instances.append(active_snapshot_instance)
  1789. for si in snapshot_instances:
  1790. self.driver.private_storage.update(
  1791. si['id'], {'snapshot_name': 'fake_snap_name_%s' % si['id']})
  1792. src_dataset_name = (
  1793. 'bar/subbar/fake_dataset_name_prefix%s' % active_replica['id'])
  1794. old_repl_snapshot_tag = (
  1795. self.driver._get_replication_snapshot_prefix(
  1796. active_replica) + 'foo')
  1797. new_repl_snapshot_tag = 'foo_snapshot_tag'
  1798. dataset_name = 'some_dataset_name'
  1799. self.driver.private_storage.update(
  1800. active_replica['id'],
  1801. {'dataset_name': src_dataset_name,
  1802. 'ssh_cmd': 'fake_src_ssh_cmd',
  1803. 'repl_snapshot_tag': old_repl_snapshot_tag}
  1804. )
  1805. for replica in (replica, second_replica):
  1806. self.driver.private_storage.update(
  1807. replica['id'],
  1808. {'dataset_name': dataset_name,
  1809. 'ssh_cmd': 'fake_ssh_cmd'}
  1810. )
  1811. self.driver.private_storage.update(
  1812. snapshot_instances[0]['snapshot_id'],
  1813. {'snapshot_tag': new_repl_snapshot_tag}
  1814. )
  1815. snap_name = 'fake_snap_name'
  1816. self.mock_object(
  1817. self.driver, 'zfs', mock.Mock(return_value=['out', 'err']))
  1818. self.mock_object(
  1819. self.driver, 'execute', mock.Mock(side_effect=[
  1820. ('a', 'b'),
  1821. ('c', 'd'),
  1822. exception.ProcessExecutionError('Second replica sync failure'),
  1823. ]))
  1824. self.mock_object(
  1825. self.driver, 'parse_zfs_answer', mock.Mock(side_effect=[
  1826. ({'NAME': 'foo'}, {'NAME': snap_name}),
  1827. ({'NAME': 'bar'}, {'NAME': snap_name}),
  1828. [],
  1829. ]))
  1830. expected = sorted([
  1831. {'id': si['id'], 'status': 'deleted'} for si in snapshot_instances
  1832. ], key=lambda item: item['id'])
  1833. self.assertEqual(
  1834. new_repl_snapshot_tag,
  1835. self.driver.private_storage.get(
  1836. snapshot_instances[0]['snapshot_id'], 'snapshot_tag'))
  1837. result = self.driver.delete_replicated_snapshot(
  1838. 'fake_context', replica_list, snapshot_instances)
  1839. self.assertIsNone(
  1840. self.driver.private_storage.get(
  1841. snapshot_instances[0]['snapshot_id'], 'snapshot_tag'))
  1842. self.driver.execute.assert_has_calls([
  1843. mock.call('ssh', 'fake_ssh_cmd', 'sudo', 'zfs', 'list', '-r', '-t',
  1844. 'snapshot', dataset_name + '@' + new_repl_snapshot_tag)
  1845. for i in (0, 1)
  1846. ])
  1847. self.assertIsInstance(result, list)
  1848. self.assertEqual(3, len(result))
  1849. self.assertEqual(expected, sorted(result, key=lambda item: item['id']))
  1850. self.driver.parse_zfs_answer.assert_has_calls([
  1851. mock.call('out'),
  1852. ])
  1853. @ddt.data(
  1854. ({'NAME': 'fake'}, zfs_driver.constants.STATUS_ERROR),
  1855. ({'NAME': 'fake_snap_name'}, zfs_driver.constants.STATUS_AVAILABLE),
  1856. )
  1857. @ddt.unpack
  1858. def test_update_replicated_snapshot(self, parse_answer, expected_status):
  1859. snap_name = 'fake_snap_name'
  1860. self.mock_object(self.driver, '_update_replica_state')
  1861. self.mock_object(
  1862. self.driver, '_get_saved_snapshot_name',
  1863. mock.Mock(return_value=snap_name))
  1864. self.mock_object(
  1865. self.driver, 'zfs', mock.Mock(side_effect=[('a', 'b')]))
  1866. self.mock_object(
  1867. self.driver, 'parse_zfs_answer', mock.Mock(side_effect=[
  1868. [parse_answer]
  1869. ]))
  1870. fake_context = 'fake_context'
  1871. replica_list = ['foo', 'bar']
  1872. share_replica = 'quuz'
  1873. snapshot_instance = {'id': 'fake_snapshot_instance_id'}
  1874. snapshot_instances = ['q', 'w', 'e', 'r', 't', 'y']
  1875. result = self.driver.update_replicated_snapshot(
  1876. fake_context, replica_list, share_replica, snapshot_instances,
  1877. snapshot_instance)
  1878. self.driver._update_replica_state.assert_called_once_with(
  1879. fake_context, replica_list, share_replica)
  1880. self.driver._get_saved_snapshot_name.assert_called_once_with(
  1881. snapshot_instance)
  1882. self.driver.zfs.assert_called_once_with(
  1883. 'list', '-r', '-t', 'snapshot', snap_name)
  1884. self.driver.parse_zfs_answer.assert_called_once_with('a')
  1885. self.assertIsInstance(result, dict)
  1886. self.assertEqual(2, len(result))
  1887. self.assertIn('status', result)
  1888. self.assertIn('id', result)
  1889. self.assertEqual(expected_status, result['status'])
  1890. self.assertEqual(snapshot_instance['id'], result['id'])
  1891. def test__get_shell_executor_by_host_local(self):
  1892. backend_name = 'foobackend'
  1893. host = 'foohost@%s#foopool' % backend_name
  1894. CONF.set_default(
  1895. 'enabled_share_backends', 'fake1,%s,fake2,fake3' % backend_name)
  1896. self.assertIsNone(self.driver._shell_executors.get(backend_name))
  1897. result = self.driver._get_shell_executor_by_host(host)
  1898. self.assertEqual(self.driver.execute, result)
  1899. def test__get_shell_executor_by_host_remote(self):
  1900. backend_name = 'foobackend'
  1901. host = 'foohost@%s#foopool' % backend_name
  1902. CONF.set_default('enabled_share_backends', 'fake1,fake2,fake3')
  1903. mock_get_remote_shell_executor = self.mock_object(
  1904. zfs_driver.zfs_utils, 'get_remote_shell_executor')
  1905. mock_config = self.mock_object(zfs_driver, 'get_backend_configuration')
  1906. self.assertIsNone(self.driver._shell_executors.get(backend_name))
  1907. for i in (1, 2):
  1908. result = self.driver._get_shell_executor_by_host(host)
  1909. self.assertEqual(
  1910. mock_get_remote_shell_executor.return_value, result)
  1911. mock_get_remote_shell_executor.assert_called_once_with(
  1912. ip=mock_config.return_value.zfs_service_ip,
  1913. port=22,
  1914. conn_timeout=mock_config.return_value.ssh_conn_timeout,
  1915. login=mock_config.return_value.zfs_ssh_username,
  1916. password=mock_config.return_value.zfs_ssh_user_password,
  1917. privatekey=mock_config.return_value.zfs_ssh_private_key_path,
  1918. max_size=10,
  1919. )
  1920. zfs_driver.get_backend_configuration.assert_called_once_with(
  1921. backend_name)
  1922. def test__get_migration_snapshot_tag(self):
  1923. share_instance = {'id': 'fake-share_instance_id'}
  1924. current_time = 'fake_current_time'
  1925. mock_utcnow = self.mock_object(zfs_driver.timeutils, 'utcnow')
  1926. mock_utcnow.return_value.isoformat.return_value = current_time
  1927. expected_value = (
  1928. self.driver.migration_snapshot_prefix +
  1929. '_fake_share_instance_id_time_' + current_time)
  1930. result = self.driver._get_migration_snapshot_tag(share_instance)
  1931. self.assertEqual(expected_value, result)
  1932. def test_migration_check_compatibility(self):
  1933. src_share = {'host': 'foohost@foobackend#foopool'}
  1934. dst_backend_name = 'barbackend'
  1935. dst_share = {'host': 'barhost@%s#barpool' % dst_backend_name}
  1936. expected = {
  1937. 'compatible': True,
  1938. 'writable': False,
  1939. 'preserve_metadata': True,
  1940. 'nondisruptive': True,
  1941. }
  1942. self.mock_object(
  1943. zfs_driver,
  1944. 'get_backend_configuration',
  1945. mock.Mock(return_value=type(
  1946. 'FakeConfig', (object,), {
  1947. 'share_driver': self.driver.configuration.share_driver})))
  1948. actual = self.driver.migration_check_compatibility(
  1949. 'fake_context', src_share, dst_share)
  1950. self.assertEqual(expected, actual)
  1951. zfs_driver.get_backend_configuration.assert_called_once_with(
  1952. dst_backend_name)
  1953. def test_migration_start(self):
  1954. username = self.driver.configuration.zfs_ssh_username
  1955. hostname = self.driver.configuration.zfs_service_ip
  1956. dst_username = username + '_dst'
  1957. dst_hostname = hostname + '_dst'
  1958. src_share = {
  1959. 'id': 'fake_src_share_id',
  1960. 'host': 'foohost@foobackend#foopool',
  1961. }
  1962. src_dataset_name = 'foo_dataset_name'
  1963. dst_share = {
  1964. 'id': 'fake_dst_share_id',
  1965. 'host': 'barhost@barbackend#barpool',
  1966. }
  1967. dst_dataset_name = 'bar_dataset_name'
  1968. snapshot_tag = 'fake_migration_snapshot_tag'
  1969. self.mock_object(
  1970. self.driver,
  1971. '_get_dataset_name',
  1972. mock.Mock(return_value=dst_dataset_name))
  1973. self.mock_object(
  1974. self.driver,
  1975. '_get_migration_snapshot_tag',
  1976. mock.Mock(return_value=snapshot_tag))
  1977. self.mock_object(
  1978. zfs_driver,
  1979. 'get_backend_configuration',
  1980. mock.Mock(return_value=type(
  1981. 'FakeConfig', (object,), {
  1982. 'zfs_ssh_username': dst_username,
  1983. 'zfs_service_ip': dst_hostname,
  1984. })))
  1985. self.mock_object(self.driver, 'execute')
  1986. self.mock_object(
  1987. zfs_driver.utils, 'tempdir',
  1988. mock.MagicMock(side_effect=FakeTempDir))
  1989. self.driver.private_storage.update(
  1990. src_share['id'],
  1991. {'dataset_name': src_dataset_name,
  1992. 'ssh_cmd': username + '@' + hostname})
  1993. src_snapshot_name = (
  1994. '%(dataset_name)s@%(snapshot_tag)s' % {
  1995. 'snapshot_tag': snapshot_tag,
  1996. 'dataset_name': src_dataset_name,
  1997. }
  1998. )
  1999. with mock.patch("six.moves.builtins.open",
  2000. mock.mock_open(read_data="data")) as mock_file:
  2001. self.driver.migration_start(
  2002. self._context, src_share, dst_share, None, None)
  2003. expected_file_content = (
  2004. 'ssh %(ssh_cmd)s sudo zfs send -vDR %(snap)s | '
  2005. 'ssh %(dst_ssh_cmd)s sudo zfs receive -v %(dst_dataset)s'
  2006. ) % {
  2007. 'ssh_cmd': self.driver.private_storage.get(
  2008. src_share['id'], 'ssh_cmd'),
  2009. 'dst_ssh_cmd': self.driver.private_storage.get(
  2010. dst_share['id'], 'ssh_cmd'),
  2011. 'snap': src_snapshot_name,
  2012. 'dst_dataset': dst_dataset_name,
  2013. }
  2014. mock_file.assert_called_with("/foo/path/bar_dataset_name.sh", "w")
  2015. mock_file.return_value.write.assert_called_once_with(
  2016. expected_file_content)
  2017. self.driver.execute.assert_has_calls([
  2018. mock.call('sudo', 'zfs', 'snapshot', src_snapshot_name),
  2019. mock.call('sudo', 'chmod', '755', mock.ANY),
  2020. mock.call('nohup', mock.ANY, '&'),
  2021. ])
  2022. self.driver._get_migration_snapshot_tag.assert_called_once_with(
  2023. dst_share)
  2024. self.driver._get_dataset_name.assert_called_once_with(
  2025. dst_share)
  2026. for k, v in (('dataset_name', dst_dataset_name),
  2027. ('migr_snapshot_tag', snapshot_tag),
  2028. ('pool_name', 'barpool'),
  2029. ('ssh_cmd', dst_username + '@' + dst_hostname)):
  2030. self.assertEqual(
  2031. v, self.driver.private_storage.get(dst_share['id'], k))
  2032. def test_migration_continue_success(self):
  2033. dst_share = {
  2034. 'id': 'fake_dst_share_id',
  2035. 'host': 'barhost@barbackend#barpool',
  2036. }
  2037. dst_dataset_name = 'bar_dataset_name'
  2038. snapshot_tag = 'fake_migration_snapshot_tag'
  2039. self.driver.private_storage.update(
  2040. dst_share['id'], {
  2041. 'migr_snapshot_tag': snapshot_tag,
  2042. 'dataset_name': dst_dataset_name,
  2043. })
  2044. mock_executor = self.mock_object(
  2045. self.driver, '_get_shell_executor_by_host')
  2046. self.mock_object(
  2047. self.driver, 'execute',
  2048. mock.Mock(return_value=('fake_out', 'fake_err')))
  2049. result = self.driver.migration_continue(
  2050. self._context, 'fake_src_share', dst_share, None, None)
  2051. self.assertTrue(result)
  2052. mock_executor.assert_called_once_with(dst_share['host'])
  2053. self.driver.execute.assert_has_calls([
  2054. mock.call('ps', 'aux'),
  2055. mock.call('sudo', 'zfs', 'get', 'quota', dst_dataset_name,
  2056. executor=mock_executor.return_value),
  2057. ])
  2058. def test_migration_continue_pending(self):
  2059. dst_share = {
  2060. 'id': 'fake_dst_share_id',
  2061. 'host': 'barhost@barbackend#barpool',
  2062. }
  2063. dst_dataset_name = 'bar_dataset_name'
  2064. snapshot_tag = 'fake_migration_snapshot_tag'
  2065. self.driver.private_storage.update(
  2066. dst_share['id'], {
  2067. 'migr_snapshot_tag': snapshot_tag,
  2068. 'dataset_name': dst_dataset_name,
  2069. })
  2070. mock_executor = self.mock_object(
  2071. self.driver, '_get_shell_executor_by_host')
  2072. self.mock_object(
  2073. self.driver, 'execute',
  2074. mock.Mock(return_value=('foo@%s' % snapshot_tag, 'fake_err')))
  2075. result = self.driver.migration_continue(
  2076. self._context, 'fake_src_share', dst_share, None, None)
  2077. self.assertIsNone(result)
  2078. self.assertFalse(mock_executor.called)
  2079. self.driver.execute.assert_called_once_with('ps', 'aux')
  2080. def test_migration_continue_exception(self):
  2081. dst_share = {
  2082. 'id': 'fake_dst_share_id',
  2083. 'host': 'barhost@barbackend#barpool',
  2084. }
  2085. dst_dataset_name = 'bar_dataset_name'
  2086. snapshot_tag = 'fake_migration_snapshot_tag'
  2087. self.driver.private_storage.update(
  2088. dst_share['id'], {
  2089. 'migr_snapshot_tag': snapshot_tag,
  2090. 'dataset_name': dst_dataset_name,
  2091. })
  2092. mock_executor = self.mock_object(
  2093. self.driver, '_get_shell_executor_by_host')
  2094. self.mock_object(
  2095. self.driver, 'execute',
  2096. mock.Mock(side_effect=[
  2097. ('fake_out', 'fake_err'),
  2098. exception.ProcessExecutionError('fake'),
  2099. ]))
  2100. self.assertRaises(
  2101. exception.ZFSonLinuxException,
  2102. self.driver.migration_continue,
  2103. self._context, 'fake_src_share', dst_share, None, None
  2104. )
  2105. mock_executor.assert_called_once_with(dst_share['host'])
  2106. self.driver.execute.assert_has_calls([
  2107. mock.call('ps', 'aux'),
  2108. mock.call('sudo', 'zfs', 'get', 'quota', dst_dataset_name,
  2109. executor=mock_executor.return_value),
  2110. ])
  2111. def test_migration_complete(self):
  2112. src_share = {'id': 'fake_src_share_id'}
  2113. dst_share = {
  2114. 'id': 'fake_dst_share_id',
  2115. 'host': 'barhost@barbackend#barpool',
  2116. 'share_proto': 'fake_share_proto',
  2117. }
  2118. dst_dataset_name = 'bar_dataset_name'
  2119. snapshot_tag = 'fake_migration_snapshot_tag'
  2120. self.driver.private_storage.update(
  2121. dst_share['id'], {
  2122. 'migr_snapshot_tag': snapshot_tag,
  2123. 'dataset_name': dst_dataset_name,
  2124. })
  2125. dst_snapshot_name = (
  2126. '%(dataset_name)s@%(snapshot_tag)s' % {
  2127. 'snapshot_tag': snapshot_tag,
  2128. 'dataset_name': dst_dataset_name,
  2129. }
  2130. )
  2131. mock_helper = self.mock_object(self.driver, '_get_share_helper')
  2132. mock_executor = self.mock_object(
  2133. self.driver, '_get_shell_executor_by_host')
  2134. self.mock_object(
  2135. self.driver, 'execute',
  2136. mock.Mock(return_value=('fake_out', 'fake_err')))
  2137. self.mock_object(self.driver, 'delete_share')
  2138. result = self.driver.migration_complete(
  2139. self._context, src_share, dst_share, None, None)
  2140. expected_result = {
  2141. 'export_locations': (mock_helper.return_value.
  2142. create_exports.return_value)
  2143. }
  2144. self.assertEqual(expected_result, result)
  2145. mock_executor.assert_called_once_with(dst_share['host'])
  2146. self.driver.execute.assert_called_once_with(
  2147. 'sudo', 'zfs', 'destroy', dst_snapshot_name,
  2148. executor=mock_executor.return_value,
  2149. )
  2150. self.driver.delete_share.assert_called_once_with(
  2151. self._context, src_share)
  2152. mock_helper.assert_called_once_with(dst_share['share_proto'])
  2153. mock_helper.return_value.create_exports.assert_called_once_with(
  2154. dst_dataset_name,
  2155. executor=self.driver._get_shell_executor_by_host.return_value)
  2156. def test_migration_cancel_success(self):
  2157. src_dataset_name = 'fake_src_dataset_name'
  2158. src_share = {
  2159. 'id': 'fake_src_share_id',
  2160. 'dataset_name': src_dataset_name,
  2161. }
  2162. dst_share = {
  2163. 'id': 'fake_dst_share_id',
  2164. 'host': 'barhost@barbackend#barpool',
  2165. 'share_proto': 'fake_share_proto',
  2166. }
  2167. dst_dataset_name = 'fake_dst_dataset_name'
  2168. snapshot_tag = 'fake_migration_snapshot_tag'
  2169. dst_ssh_cmd = 'fake_dst_ssh_cmd'
  2170. self.driver.private_storage.update(
  2171. src_share['id'], {'dataset_name': src_dataset_name})
  2172. self.driver.private_storage.update(
  2173. dst_share['id'], {
  2174. 'migr_snapshot_tag': snapshot_tag,
  2175. 'dataset_name': dst_dataset_name,
  2176. 'ssh_cmd': dst_ssh_cmd,
  2177. })
  2178. self.mock_object(zfs_driver.time, 'sleep')
  2179. mock_delete_dataset = self.mock_object(
  2180. self.driver, '_delete_dataset_or_snapshot_with_retry')
  2181. ps_output = (
  2182. "fake_line1\nfoo_user 12345 foo_dataset_name@%s\n"
  2183. "fake_line2") % snapshot_tag
  2184. self.mock_object(
  2185. self.driver, 'execute',
  2186. mock.Mock(return_value=(ps_output, 'fake_err'))
  2187. )
  2188. self.driver.migration_cancel(
  2189. self._context, src_share, dst_share, [], {})
  2190. self.driver.execute.assert_has_calls([
  2191. mock.call('ps', 'aux'),
  2192. mock.call('sudo', 'kill', '-9', '12345'),
  2193. mock.call('ssh', dst_ssh_cmd, 'sudo', 'zfs', 'destroy', '-r',
  2194. dst_dataset_name),
  2195. ])
  2196. zfs_driver.time.sleep.assert_called_once_with(2)
  2197. mock_delete_dataset.assert_called_once_with(
  2198. src_dataset_name + '@' + snapshot_tag)
  2199. def test_migration_cancel_error(self):
  2200. src_dataset_name = 'fake_src_dataset_name'
  2201. src_share = {
  2202. 'id': 'fake_src_share_id',
  2203. 'dataset_name': src_dataset_name,
  2204. }
  2205. dst_share = {
  2206. 'id': 'fake_dst_share_id',
  2207. 'host': 'barhost@barbackend#barpool',
  2208. 'share_proto': 'fake_share_proto',
  2209. }
  2210. dst_dataset_name = 'fake_dst_dataset_name'
  2211. snapshot_tag = 'fake_migration_snapshot_tag'
  2212. dst_ssh_cmd = 'fake_dst_ssh_cmd'
  2213. self.driver.private_storage.update(
  2214. src_share['id'], {'dataset_name': src_dataset_name})
  2215. self.driver.private_storage.update(
  2216. dst_share['id'], {
  2217. 'migr_snapshot_tag': snapshot_tag,
  2218. 'dataset_name': dst_dataset_name,
  2219. 'ssh_cmd': dst_ssh_cmd,
  2220. })
  2221. self.mock_object(zfs_driver.time, 'sleep')
  2222. mock_delete_dataset = self.mock_object(
  2223. self.driver, '_delete_dataset_or_snapshot_with_retry')
  2224. self.mock_object(
  2225. self.driver, 'execute',
  2226. mock.Mock(side_effect=exception.ProcessExecutionError),
  2227. )
  2228. self.driver.migration_cancel(
  2229. self._context, src_share, dst_share, [], {})
  2230. self.driver.execute.assert_has_calls([
  2231. mock.call('ps', 'aux'),
  2232. mock.call('ssh', dst_ssh_cmd, 'sudo', 'zfs', 'destroy', '-r',
  2233. dst_dataset_name),
  2234. ])
  2235. zfs_driver.time.sleep.assert_called_once_with(2)
  2236. mock_delete_dataset.assert_called_once_with(
  2237. src_dataset_name + '@' + snapshot_tag)