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 103KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555
  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. 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_side_effects = [
  1002. ("%s " % old_dataset_name, "fake_err")
  1003. if mount_exists else ("foo", "bar")
  1004. ] * 3
  1005. if mount_exists:
  1006. # After three retries, assume the mount goes away
  1007. mock_execute_side_effects.append((("foo", "bar")))
  1008. mock_execute = self.mock_object(
  1009. self.driver, "execute",
  1010. mock.Mock(side_effect=iter(mock_execute_side_effects)))
  1011. mock_parse_zfs_answer = self.mock_object(
  1012. self.driver,
  1013. "parse_zfs_answer",
  1014. mock.Mock(return_value=[
  1015. {"NAME": "some_other_dataset_1"},
  1016. {"NAME": old_dataset_name},
  1017. {"NAME": "some_other_dataset_2"},
  1018. ]))
  1019. mock_get_zfs_option = self.mock_object(
  1020. self.driver, 'get_zfs_option', mock.Mock(return_value="4G"))
  1021. result = self.driver.manage_existing(share, driver_options)
  1022. self.assertTrue(mock_helper.return_value.get_exports.called)
  1023. self.assertTrue(mock_zfs_with_retry.called)
  1024. self.assertEqual(2, len(result))
  1025. self.assertIn("size", result)
  1026. self.assertIn("export_locations", result)
  1027. self.assertEqual(5, result["size"])
  1028. self.assertEqual(
  1029. mock_helper.return_value.get_exports.return_value,
  1030. result["export_locations"])
  1031. mock_execute.assert_called_with("sudo", "mount")
  1032. if mount_exists:
  1033. self.assertEqual(4, mock_execute.call_count)
  1034. else:
  1035. self.assertEqual(1, mock_execute.call_count)
  1036. mock_parse_zfs_answer.assert_called_once_with(mock_zfs.return_value[0])
  1037. if driver_options.get("size"):
  1038. self.assertFalse(mock_get_zfs_option.called)
  1039. else:
  1040. mock_get_zfs_option.assert_called_once_with(
  1041. old_dataset_name, "used")
  1042. mock__get_dataset_name.assert_called_once_with(share)
  1043. mock_get_extra_specs_from_share.assert_called_once_with(share)
  1044. def test_manage_share_wrong_pool(self):
  1045. old_dataset_name = "foopool/path/to/old/dataset/name"
  1046. new_dataset_name = "foopool/path/to/new/dataset/name"
  1047. share = {
  1048. "id": "fake_share_instance_id",
  1049. "share_id": "fake_share_id",
  1050. "export_locations": [{"path": "1.1.1.1:/%s" % old_dataset_name}],
  1051. "host": "foobackend@foohost#barpool",
  1052. "share_proto": "NFS",
  1053. }
  1054. mock_get_extra_specs_from_share = self.mock_object(
  1055. zfs_driver.share_types,
  1056. 'get_extra_specs_from_share',
  1057. mock.Mock(return_value={}))
  1058. mock__get_dataset_name = self.mock_object(
  1059. self.driver, "_get_dataset_name",
  1060. mock.Mock(return_value=new_dataset_name))
  1061. mock_get_zfs_option = self.mock_object(
  1062. self.driver, 'get_zfs_option', mock.Mock(return_value="4G"))
  1063. self.assertRaises(
  1064. exception.ZFSonLinuxException,
  1065. self.driver.manage_existing,
  1066. share, {}
  1067. )
  1068. mock__get_dataset_name.assert_called_once_with(share)
  1069. mock_get_zfs_option.assert_called_once_with(old_dataset_name, "used")
  1070. mock_get_extra_specs_from_share.assert_called_once_with(share)
  1071. def test_manage_share_dataset_not_found(self):
  1072. old_dataset_name = "foopool/path/to/old/dataset/name"
  1073. new_dataset_name = "foopool/path/to/new/dataset/name"
  1074. share = {
  1075. "id": "fake_share_instance_id",
  1076. "share_id": "fake_share_id",
  1077. "export_locations": [{"path": "1.1.1.1:/%s" % old_dataset_name}],
  1078. "host": "foobackend@foohost#foopool",
  1079. "share_proto": "NFS",
  1080. }
  1081. mock_get_extra_specs_from_share = self.mock_object(
  1082. zfs_driver.share_types,
  1083. 'get_extra_specs_from_share',
  1084. mock.Mock(return_value={}))
  1085. mock__get_dataset_name = self.mock_object(
  1086. self.driver, "_get_dataset_name",
  1087. mock.Mock(return_value=new_dataset_name))
  1088. mock_get_zfs_option = self.mock_object(
  1089. self.driver, 'get_zfs_option', mock.Mock(return_value="4G"))
  1090. mock_zfs = self.mock_object(
  1091. self.driver, "zfs",
  1092. mock.Mock(return_value=("fake_out", "fake_error")))
  1093. mock_parse_zfs_answer = self.mock_object(
  1094. self.driver,
  1095. "parse_zfs_answer",
  1096. mock.Mock(return_value=[{"NAME": "some_other_dataset_1"}]))
  1097. self.assertRaises(
  1098. exception.ZFSonLinuxException,
  1099. self.driver.manage_existing,
  1100. share, {}
  1101. )
  1102. mock__get_dataset_name.assert_called_once_with(share)
  1103. mock_get_zfs_option.assert_called_once_with(old_dataset_name, "used")
  1104. mock_zfs.assert_called_once_with(
  1105. "list", "-r", old_dataset_name.split("/")[0])
  1106. mock_parse_zfs_answer.assert_called_once_with(mock_zfs.return_value[0])
  1107. mock_get_extra_specs_from_share.assert_called_once_with(share)
  1108. def test_manage_unmount_exception(self):
  1109. old_ds_name = "foopool/path/to/old/dataset/name"
  1110. new_ds_name = "foopool/path/to/new/dataset/name"
  1111. share = {
  1112. "id": "fake_share_instance_id",
  1113. "share_id": "fake_share_id",
  1114. "export_locations": [{"path": "1.1.1.1:/%s" % old_ds_name}],
  1115. "host": "foobackend@foohost#foopool",
  1116. "share_proto": "NFS",
  1117. }
  1118. mock_get_extra_specs_from_share = self.mock_object(
  1119. zfs_driver.share_types,
  1120. 'get_extra_specs_from_share',
  1121. mock.Mock(return_value={}))
  1122. self.mock_object(zfs_driver.time, "sleep")
  1123. mock__get_dataset_name = self.mock_object(
  1124. self.driver, "_get_dataset_name",
  1125. mock.Mock(return_value=new_ds_name))
  1126. mock_helper = self.mock_object(self.driver, "_get_share_helper")
  1127. mock_zfs = self.mock_object(
  1128. self.driver, "zfs",
  1129. mock.Mock(return_value=("fake_out", "fake_error")))
  1130. mock_zfs_with_retry = self.mock_object(self.driver, "zfs_with_retry")
  1131. # 10 Retries, would mean 20 calls to check the mount still exists
  1132. mock_execute_side_effects = [("%s " % old_ds_name, "fake_err")] * 21
  1133. mock_execute = self.mock_object(
  1134. self.driver, "execute",
  1135. mock.Mock(side_effect=mock_execute_side_effects))
  1136. mock_parse_zfs_answer = self.mock_object(
  1137. self.driver,
  1138. "parse_zfs_answer",
  1139. mock.Mock(return_value=[
  1140. {"NAME": "some_other_dataset_1"},
  1141. {"NAME": old_ds_name},
  1142. {"NAME": "some_other_dataset_2"},
  1143. ]))
  1144. mock_get_zfs_option = self.mock_object(
  1145. self.driver, 'get_zfs_option', mock.Mock(return_value="4G"))
  1146. self.assertRaises(exception.ZFSonLinuxException,
  1147. self.driver.manage_existing,
  1148. share, {'size': 10})
  1149. self.assertFalse(mock_helper.return_value.get_exports.called)
  1150. mock_zfs_with_retry.assert_called_with("umount", "-f", old_ds_name)
  1151. mock_execute.assert_called_with("sudo", "mount")
  1152. self.assertEqual(10, mock_zfs_with_retry.call_count)
  1153. self.assertEqual(20, mock_execute.call_count)
  1154. mock_parse_zfs_answer.assert_called_once_with(mock_zfs.return_value[0])
  1155. self.assertFalse(mock_get_zfs_option.called)
  1156. mock__get_dataset_name.assert_called_once_with(share)
  1157. mock_get_extra_specs_from_share.assert_called_once_with(share)
  1158. def test_unmanage(self):
  1159. share = {'id': 'fake_share_id'}
  1160. self.mock_object(self.driver.private_storage, 'delete')
  1161. self.driver.unmanage(share)
  1162. self.driver.private_storage.delete.assert_called_once_with(share['id'])
  1163. @ddt.data(
  1164. {},
  1165. {"size": 5},
  1166. {"size": "5"},
  1167. )
  1168. def test_manage_existing_snapshot(self, driver_options):
  1169. dataset_name = "path/to/dataset"
  1170. old_provider_location = dataset_name + "@original_snapshot_tag"
  1171. snapshot_instance = {
  1172. "id": "fake_snapshot_instance_id",
  1173. "share_instance_id": "fake_share_instance_id",
  1174. "snapshot_id": "fake_snapshot_id",
  1175. "provider_location": old_provider_location,
  1176. }
  1177. new_snapshot_tag = "fake_new_snapshot_tag"
  1178. new_provider_location = (
  1179. old_provider_location.split("@")[0] + "@" + new_snapshot_tag)
  1180. self.mock_object(self.driver, "zfs")
  1181. self.mock_object(
  1182. self.driver, "get_zfs_option", mock.Mock(return_value="5G"))
  1183. self.mock_object(
  1184. self.driver,
  1185. '_get_snapshot_name',
  1186. mock.Mock(return_value=new_snapshot_tag))
  1187. self.driver.private_storage.update(
  1188. snapshot_instance["share_instance_id"],
  1189. {"dataset_name": dataset_name})
  1190. result = self.driver.manage_existing_snapshot(
  1191. snapshot_instance, driver_options)
  1192. expected_result = {
  1193. "size": 5,
  1194. "provider_location": new_provider_location,
  1195. }
  1196. self.assertEqual(expected_result, result)
  1197. self.driver._get_snapshot_name.assert_called_once_with(
  1198. snapshot_instance["id"])
  1199. self.driver.zfs.assert_has_calls([
  1200. mock.call("list", "-r", "-t", "snapshot", old_provider_location),
  1201. mock.call("rename", old_provider_location, new_provider_location),
  1202. ])
  1203. def test_manage_existing_snapshot_not_found(self):
  1204. dataset_name = "path/to/dataset"
  1205. old_provider_location = dataset_name + "@original_snapshot_tag"
  1206. new_snapshot_tag = "fake_new_snapshot_tag"
  1207. snapshot_instance = {
  1208. "id": "fake_snapshot_instance_id",
  1209. "snapshot_id": "fake_snapshot_id",
  1210. "provider_location": old_provider_location,
  1211. }
  1212. self.mock_object(
  1213. self.driver, "_get_snapshot_name",
  1214. mock.Mock(return_value=new_snapshot_tag))
  1215. self.mock_object(
  1216. self.driver, "zfs",
  1217. mock.Mock(side_effect=exception.ProcessExecutionError("FAKE")))
  1218. self.assertRaises(
  1219. exception.ManageInvalidShareSnapshot,
  1220. self.driver.manage_existing_snapshot,
  1221. snapshot_instance, {},
  1222. )
  1223. self.driver.zfs.assert_called_once_with(
  1224. "list", "-r", "-t", "snapshot", old_provider_location)
  1225. self.driver._get_snapshot_name.assert_called_once_with(
  1226. snapshot_instance["id"])
  1227. def test_unmanage_snapshot(self):
  1228. snapshot_instance = {
  1229. "id": "fake_snapshot_instance_id",
  1230. "snapshot_id": "fake_snapshot_id",
  1231. }
  1232. self.mock_object(self.driver.private_storage, "delete")
  1233. self.driver.unmanage_snapshot(snapshot_instance)
  1234. self.driver.private_storage.delete.assert_called_once_with(
  1235. snapshot_instance["snapshot_id"])
  1236. def test__delete_dataset_or_snapshot_with_retry_snapshot(self):
  1237. self.mock_object(self.driver, 'get_zfs_option')
  1238. self.mock_object(self.driver, 'zfs')
  1239. self.driver._delete_dataset_or_snapshot_with_retry('foo@bar')
  1240. self.driver.get_zfs_option.assert_called_once_with(
  1241. 'foo@bar', 'mountpoint')
  1242. self.driver.zfs.assert_called_once_with(
  1243. 'destroy', '-f', 'foo@bar')
  1244. def test__delete_dataset_or_snapshot_with_retry_of(self):
  1245. self.mock_object(self.driver, 'get_zfs_option')
  1246. self.mock_object(
  1247. self.driver, 'execute', mock.Mock(return_value=('a', 'b')))
  1248. self.mock_object(zfs_driver.time, 'sleep')
  1249. self.mock_object(zfs_driver.LOG, 'debug')
  1250. self.mock_object(
  1251. zfs_driver.time, 'time', mock.Mock(side_effect=range(1, 70, 2)))
  1252. dataset_name = 'fake/dataset/name'
  1253. self.assertRaises(
  1254. exception.ZFSonLinuxException,
  1255. self.driver._delete_dataset_or_snapshot_with_retry,
  1256. dataset_name,
  1257. )
  1258. self.driver.get_zfs_option.assert_called_once_with(
  1259. dataset_name, 'mountpoint')
  1260. self.assertEqual(29, zfs_driver.LOG.debug.call_count)
  1261. def test__delete_dataset_or_snapshot_with_retry_temp_of(self):
  1262. self.mock_object(self.driver, 'get_zfs_option')
  1263. self.mock_object(self.driver, 'zfs')
  1264. self.mock_object(
  1265. self.driver, 'execute', mock.Mock(side_effect=[
  1266. ('a', 'b'),
  1267. exception.ProcessExecutionError(
  1268. 'FAKE lsof returns not found')]))
  1269. self.mock_object(zfs_driver.time, 'sleep')
  1270. self.mock_object(zfs_driver.LOG, 'debug')
  1271. self.mock_object(
  1272. zfs_driver.time, 'time', mock.Mock(side_effect=range(1, 70, 2)))
  1273. dataset_name = 'fake/dataset/name'
  1274. self.driver._delete_dataset_or_snapshot_with_retry(dataset_name)
  1275. self.driver.get_zfs_option.assert_called_once_with(
  1276. dataset_name, 'mountpoint')
  1277. self.assertEqual(2, self.driver.execute.call_count)
  1278. self.assertEqual(1, zfs_driver.LOG.debug.call_count)
  1279. zfs_driver.LOG.debug.assert_called_once_with(
  1280. mock.ANY, {'name': dataset_name, 'out': 'a'})
  1281. zfs_driver.time.sleep.assert_called_once_with(2)
  1282. self.driver.zfs.assert_called_once_with('destroy', '-f', dataset_name)
  1283. def test__delete_dataset_or_snapshot_with_retry_busy(self):
  1284. self.mock_object(self.driver, 'get_zfs_option')
  1285. self.mock_object(
  1286. self.driver, 'execute', mock.Mock(
  1287. side_effect=exception.ProcessExecutionError(
  1288. 'FAKE lsof returns not found')))
  1289. self.mock_object(
  1290. self.driver, 'zfs', mock.Mock(side_effect=[
  1291. exception.ProcessExecutionError(
  1292. 'cannot destroy FAKE: dataset is busy\n'),
  1293. None, None]))
  1294. self.mock_object(zfs_driver.time, 'sleep')
  1295. self.mock_object(zfs_driver.LOG, 'info')
  1296. dataset_name = 'fake/dataset/name'
  1297. self.driver._delete_dataset_or_snapshot_with_retry(dataset_name)
  1298. self.driver.get_zfs_option.assert_called_once_with(
  1299. dataset_name, 'mountpoint')
  1300. self.assertEqual(2, zfs_driver.time.sleep.call_count)
  1301. self.assertEqual(2, self.driver.execute.call_count)
  1302. self.assertEqual(1, zfs_driver.LOG.info.call_count)
  1303. self.assertEqual(2, self.driver.zfs.call_count)
  1304. def test_create_replica(self):
  1305. active_replica = {
  1306. 'id': 'fake_active_replica_id',
  1307. 'host': 'hostname1@backend_name1#foo',
  1308. 'size': 5,
  1309. 'replica_state': zfs_driver.constants.REPLICA_STATE_ACTIVE,
  1310. }
  1311. replica_list = [active_replica]
  1312. new_replica = {
  1313. 'id': 'fake_new_replica_id',
  1314. 'host': 'hostname2@backend_name2#bar',
  1315. 'share_proto': 'NFS',
  1316. 'replica_state': None,
  1317. }
  1318. dst_dataset_name = (
  1319. 'bar/subbar/fake_dataset_name_prefix%s' % new_replica['id'])
  1320. access_rules = ['foo_rule', 'bar_rule']
  1321. self.driver.private_storage.update(
  1322. active_replica['id'],
  1323. {'dataset_name': 'fake/active/dataset/name',
  1324. 'ssh_cmd': 'fake_ssh_cmd'}
  1325. )
  1326. self.mock_object(
  1327. self.driver, 'execute',
  1328. mock.Mock(side_effect=[('a', 'b'), ('c', 'd')]))
  1329. self.mock_object(self.driver, 'zfs')
  1330. mock_helper = self.mock_object(self.driver, '_get_share_helper')
  1331. self.configuration.zfs_dataset_name_prefix = 'fake_dataset_name_prefix'
  1332. mock_utcnow = self.mock_object(zfs_driver.timeutils, 'utcnow')
  1333. mock_utcnow.return_value.isoformat.return_value = 'some_time'
  1334. result = self.driver.create_replica(
  1335. 'fake_context', replica_list, new_replica, access_rules, [])
  1336. expected = {
  1337. 'export_locations': (
  1338. mock_helper.return_value.create_exports.return_value),
  1339. 'replica_state': zfs_driver.constants.REPLICA_STATE_IN_SYNC,
  1340. 'access_rules_status': zfs_driver.constants.STATUS_ACTIVE,
  1341. }
  1342. self.assertEqual(expected, result)
  1343. mock_helper.assert_has_calls([
  1344. mock.call('NFS'),
  1345. mock.call().update_access(
  1346. dst_dataset_name, access_rules, add_rules=[],
  1347. delete_rules=[], make_all_ro=True),
  1348. mock.call('NFS'),
  1349. mock.call().create_exports(dst_dataset_name),
  1350. ])
  1351. self.driver.zfs.assert_has_calls([
  1352. mock.call('set', 'readonly=on', dst_dataset_name),
  1353. mock.call('set', 'quota=%sG' % active_replica['size'],
  1354. dst_dataset_name),
  1355. ])
  1356. src_snapshot_name = (
  1357. 'fake/active/dataset/name@'
  1358. 'tmp_snapshot_for_replication__fake_new_replica_id_time_some_time')
  1359. self.driver.execute.assert_has_calls([
  1360. mock.call('ssh', 'fake_ssh_cmd', 'sudo', 'zfs', 'snapshot',
  1361. src_snapshot_name),
  1362. mock.call(
  1363. 'ssh', 'fake_ssh_cmd',
  1364. 'sudo', 'zfs', 'send', '-vDR', src_snapshot_name, '|',
  1365. 'ssh', 'fake_username@240.241.242.244',
  1366. 'sudo', 'zfs', 'receive', '-v', dst_dataset_name
  1367. ),
  1368. ])
  1369. mock_utcnow.assert_called_once_with()
  1370. mock_utcnow.return_value.isoformat.assert_called_once_with()
  1371. def test_delete_replica_not_found(self):
  1372. dataset_name = 'foo/dataset/name'
  1373. pool_name = 'foo_pool'
  1374. replica = {'id': 'fake_replica_id'}
  1375. replica_list = [replica]
  1376. replica_snapshots = []
  1377. self.mock_object(
  1378. self.driver, '_get_dataset_name',
  1379. mock.Mock(return_value=dataset_name))
  1380. self.mock_object(
  1381. self.driver, 'zfs',
  1382. mock.Mock(side_effect=[('a', 'b'), ('c', 'd')]))
  1383. self.mock_object(
  1384. self.driver, 'parse_zfs_answer', mock.Mock(side_effect=[[], []]))
  1385. self.mock_object(self.driver, '_delete_dataset_or_snapshot_with_retry')
  1386. self.mock_object(zfs_driver.LOG, 'warning')
  1387. self.mock_object(self.driver, '_get_share_helper')
  1388. self.driver.private_storage.update(
  1389. replica['id'], {'pool_name': pool_name})
  1390. self.driver.delete_replica('fake_context', replica_list,
  1391. replica_snapshots, replica)
  1392. zfs_driver.LOG.warning.assert_called_once_with(
  1393. mock.ANY, {'id': replica['id'], 'name': dataset_name})
  1394. self.assertEqual(0, self.driver._get_share_helper.call_count)
  1395. self.assertEqual(
  1396. 0, self.driver._delete_dataset_or_snapshot_with_retry.call_count)
  1397. self.driver._get_dataset_name.assert_called_once_with(replica)
  1398. self.driver.zfs.assert_has_calls([
  1399. mock.call('list', '-r', '-t', 'snapshot', pool_name),
  1400. mock.call('list', '-r', pool_name),
  1401. ])
  1402. self.driver.parse_zfs_answer.assert_has_calls([
  1403. mock.call('a'), mock.call('c'),
  1404. ])
  1405. def test_delete_replica(self):
  1406. dataset_name = 'foo/dataset/name'
  1407. pool_name = 'foo_pool'
  1408. replica = {'id': 'fake_replica_id', 'share_proto': 'NFS'}
  1409. replica_list = [replica]
  1410. self.mock_object(
  1411. self.driver, '_get_dataset_name',
  1412. mock.Mock(return_value=dataset_name))
  1413. self.mock_object(
  1414. self.driver, 'zfs',
  1415. mock.Mock(side_effect=[('a', 'b'), ('c', 'd')]))
  1416. self.mock_object(
  1417. self.driver, 'parse_zfs_answer', mock.Mock(side_effect=[
  1418. [{'NAME': 'some_other_dataset@snapshot'},
  1419. {'NAME': dataset_name + '@foo_snap'}],
  1420. [{'NAME': 'some_other_dataset'},
  1421. {'NAME': dataset_name}],
  1422. ]))
  1423. mock_helper = self.mock_object(self.driver, '_get_share_helper')
  1424. self.mock_object(self.driver, '_delete_dataset_or_snapshot_with_retry')
  1425. self.mock_object(zfs_driver.LOG, 'warning')
  1426. self.driver.private_storage.update(
  1427. replica['id'],
  1428. {'pool_name': pool_name, 'dataset_name': dataset_name})
  1429. self.driver.delete_replica('fake_context', replica_list, [], replica)
  1430. self.assertEqual(0, zfs_driver.LOG.warning.call_count)
  1431. self.assertEqual(0, self.driver._get_dataset_name.call_count)
  1432. self.driver._delete_dataset_or_snapshot_with_retry.assert_has_calls([
  1433. mock.call(dataset_name + '@foo_snap'),
  1434. mock.call(dataset_name),
  1435. ])
  1436. self.driver.zfs.assert_has_calls([
  1437. mock.call('list', '-r', '-t', 'snapshot', pool_name),
  1438. mock.call('list', '-r', pool_name),
  1439. ])
  1440. self.driver.parse_zfs_answer.assert_has_calls([
  1441. mock.call('a'), mock.call('c'),
  1442. ])
  1443. mock_helper.assert_called_once_with(replica['share_proto'])
  1444. mock_helper.return_value.remove_exports.assert_called_once_with(
  1445. dataset_name)
  1446. def test_update_replica(self):
  1447. active_replica = {
  1448. 'id': 'fake_active_replica_id',
  1449. 'host': 'hostname1@backend_name1#foo',
  1450. 'size': 5,
  1451. 'replica_state': zfs_driver.constants.REPLICA_STATE_ACTIVE,
  1452. }
  1453. replica = {
  1454. 'id': 'fake_new_replica_id',
  1455. 'host': 'hostname2@backend_name2#bar',
  1456. 'share_proto': 'NFS',
  1457. 'replica_state': None,
  1458. }
  1459. replica_list = [replica, active_replica]
  1460. replica_snapshots = []
  1461. dst_dataset_name = (
  1462. 'bar/subbar/fake_dataset_name_prefix%s' % replica['id'])
  1463. src_dataset_name = (
  1464. 'bar/subbar/fake_dataset_name_prefix%s' % active_replica['id'])
  1465. access_rules = ['foo_rule', 'bar_rule']
  1466. old_repl_snapshot_tag = (
  1467. self.driver._get_replication_snapshot_prefix(
  1468. active_replica) + 'foo')
  1469. snap_tag_prefix = self.driver._get_replication_snapshot_prefix(
  1470. replica)
  1471. self.driver.private_storage.update(
  1472. active_replica['id'],
  1473. {'dataset_name': src_dataset_name,
  1474. 'ssh_cmd': 'fake_src_ssh_cmd',
  1475. 'repl_snapshot_tag': old_repl_snapshot_tag}
  1476. )
  1477. self.driver.private_storage.update(
  1478. replica['id'],
  1479. {'dataset_name': dst_dataset_name,
  1480. 'ssh_cmd': 'fake_dst_ssh_cmd',
  1481. 'repl_snapshot_tag': old_repl_snapshot_tag}
  1482. )
  1483. self.mock_object(
  1484. self.driver, 'execute',
  1485. mock.Mock(side_effect=[('a', 'b'), ('c', 'd'), ('e', 'f')]))
  1486. self.mock_object(self.driver, 'execute_with_retry',
  1487. mock.Mock(side_effect=[('g', 'h')]))
  1488. self.mock_object(self.driver, 'zfs',
  1489. mock.Mock(side_effect=[('j', 'k'), ('l', 'm')]))
  1490. self.mock_object(
  1491. self.driver, 'parse_zfs_answer',
  1492. mock.Mock(side_effect=[
  1493. ({'NAME': dst_dataset_name + '@' + old_repl_snapshot_tag},
  1494. {'NAME': dst_dataset_name + '@%s_time_some_time' %
  1495. snap_tag_prefix},
  1496. {'NAME': 'other/dataset/name1@' + old_repl_snapshot_tag}),
  1497. ({'NAME': src_dataset_name + '@' + old_repl_snapshot_tag},
  1498. {'NAME': src_dataset_name + '@' + snap_tag_prefix + 'quuz'},
  1499. {'NAME': 'other/dataset/name2@' + old_repl_snapshot_tag}),
  1500. ])
  1501. )
  1502. mock_helper = self.mock_object(self.driver, '_get_share_helper')
  1503. self.configuration.zfs_dataset_name_prefix = 'fake_dataset_name_prefix'
  1504. mock_utcnow = self.mock_object(zfs_driver.timeutils, 'utcnow')
  1505. mock_utcnow.return_value.isoformat.return_value = 'some_time'
  1506. mock_delete_snapshot = self.mock_object(
  1507. self.driver, '_delete_dataset_or_snapshot_with_retry')
  1508. result = self.driver.update_replica_state(
  1509. 'fake_context', replica_list, replica, access_rules,
  1510. replica_snapshots)
  1511. self.assertEqual(zfs_driver.constants.REPLICA_STATE_IN_SYNC, result)
  1512. mock_helper.assert_called_once_with('NFS')
  1513. mock_helper.return_value.update_access.assert_called_once_with(
  1514. dst_dataset_name, access_rules, add_rules=[], delete_rules=[],
  1515. make_all_ro=True)
  1516. self.driver.execute_with_retry.assert_called_once_with(
  1517. 'ssh', 'fake_src_ssh_cmd', 'sudo', 'zfs', 'destroy', '-f',
  1518. src_dataset_name + '@' + snap_tag_prefix + 'quuz')
  1519. self.driver.execute.assert_has_calls([
  1520. mock.call(
  1521. 'ssh', 'fake_src_ssh_cmd', 'sudo', 'zfs', 'snapshot',
  1522. src_dataset_name + '@' +
  1523. self.driver._get_replication_snapshot_tag(replica)),
  1524. mock.call(
  1525. 'ssh', 'fake_src_ssh_cmd', 'sudo', 'zfs', 'send',
  1526. '-vDRI', old_repl_snapshot_tag,
  1527. src_dataset_name + '@%s' % snap_tag_prefix + '_time_some_time',
  1528. '|', 'ssh', 'fake_dst_ssh_cmd',
  1529. 'sudo', 'zfs', 'receive', '-vF', dst_dataset_name),
  1530. mock.call(
  1531. 'ssh', 'fake_src_ssh_cmd',
  1532. 'sudo', 'zfs', 'list', '-r', '-t', 'snapshot', 'bar'),
  1533. ])
  1534. mock_delete_snapshot.assert_called_once_with(
  1535. dst_dataset_name + '@' + old_repl_snapshot_tag)
  1536. self.driver.parse_zfs_answer.assert_has_calls(
  1537. [mock.call('l'), mock.call('e')])
  1538. def test_promote_replica_active_available(self):
  1539. active_replica = {
  1540. 'id': 'fake_active_replica_id',
  1541. 'host': 'hostname1@backend_name1#foo',
  1542. 'size': 5,
  1543. 'replica_state': zfs_driver.constants.REPLICA_STATE_ACTIVE,
  1544. }
  1545. replica = {
  1546. 'id': 'fake_first_replica_id',
  1547. 'host': 'hostname2@backend_name2#bar',
  1548. 'share_proto': 'NFS',
  1549. 'replica_state': zfs_driver.constants.REPLICA_STATE_IN_SYNC,
  1550. }
  1551. second_replica = {
  1552. 'id': 'fake_second_replica_id',
  1553. 'host': 'hostname3@backend_name3#quuz',
  1554. 'share_proto': 'NFS',
  1555. 'replica_state': zfs_driver.constants.REPLICA_STATE_IN_SYNC,
  1556. }
  1557. replica_list = [replica, active_replica, second_replica]
  1558. dst_dataset_name = (
  1559. 'bar/subbar/fake_dataset_name_prefix%s' % replica['id'])
  1560. src_dataset_name = (
  1561. 'bar/subbar/fake_dataset_name_prefix%s' % active_replica['id'])
  1562. access_rules = ['foo_rule', 'bar_rule']
  1563. old_repl_snapshot_tag = (
  1564. self.driver._get_replication_snapshot_prefix(
  1565. active_replica) + 'foo')
  1566. snap_tag_prefix = self.driver._get_replication_snapshot_prefix(
  1567. active_replica) + '_time_some_time'
  1568. self.driver.private_storage.update(
  1569. active_replica['id'],
  1570. {'dataset_name': src_dataset_name,
  1571. 'ssh_cmd': 'fake_src_ssh_cmd',
  1572. 'repl_snapshot_tag': old_repl_snapshot_tag}
  1573. )
  1574. for repl in (replica, second_replica):
  1575. self.driver.private_storage.update(
  1576. repl['id'],
  1577. {'dataset_name': (
  1578. 'bar/subbar/fake_dataset_name_prefix%s' % repl['id']),
  1579. 'ssh_cmd': 'fake_dst_ssh_cmd',
  1580. 'repl_snapshot_tag': old_repl_snapshot_tag}
  1581. )
  1582. self.mock_object(
  1583. self.driver, 'execute',
  1584. mock.Mock(side_effect=[
  1585. ('a', 'b'),
  1586. ('c', 'd'),
  1587. ('e', 'f'),
  1588. exception.ProcessExecutionError('Second replica sync failure'),
  1589. ]))
  1590. self.mock_object(self.driver, 'zfs',
  1591. mock.Mock(side_effect=[('g', 'h')]))
  1592. mock_helper = self.mock_object(self.driver, '_get_share_helper')
  1593. self.configuration.zfs_dataset_name_prefix = 'fake_dataset_name_prefix'
  1594. mock_utcnow = self.mock_object(zfs_driver.timeutils, 'utcnow')
  1595. mock_utcnow.return_value.isoformat.return_value = 'some_time'
  1596. mock_delete_snapshot = self.mock_object(
  1597. self.driver, '_delete_dataset_or_snapshot_with_retry')
  1598. result = self.driver.promote_replica(
  1599. 'fake_context', replica_list, replica, access_rules)
  1600. expected = [
  1601. {'access_rules_status':
  1602. zfs_driver.constants.SHARE_INSTANCE_RULES_SYNCING,
  1603. 'id': 'fake_active_replica_id',
  1604. 'replica_state': zfs_driver.constants.REPLICA_STATE_IN_SYNC},
  1605. {'access_rules_status': zfs_driver.constants.STATUS_ACTIVE,
  1606. 'id': 'fake_first_replica_id',
  1607. 'replica_state': zfs_driver.constants.REPLICA_STATE_ACTIVE},
  1608. {'access_rules_status':
  1609. zfs_driver.constants.SHARE_INSTANCE_RULES_SYNCING,
  1610. 'id': 'fake_second_replica_id',
  1611. 'replica_state': zfs_driver.constants.REPLICA_STATE_OUT_OF_SYNC},
  1612. ]
  1613. for repl in expected:
  1614. self.assertIn(repl, result)
  1615. self.assertEqual(3, len(result))
  1616. mock_helper.assert_called_once_with('NFS')
  1617. mock_helper.return_value.update_access.assert_called_once_with(
  1618. dst_dataset_name, access_rules, add_rules=[], delete_rules=[])
  1619. self.driver.zfs.assert_called_once_with(
  1620. 'set', 'readonly=off', dst_dataset_name)
  1621. self.assertEqual(0, mock_delete_snapshot.call_count)
  1622. for repl in (active_replica, replica):
  1623. self.assertEqual(
  1624. snap_tag_prefix,
  1625. self.driver.private_storage.get(
  1626. repl['id'], 'repl_snapshot_tag'))
  1627. self.assertEqual(
  1628. old_repl_snapshot_tag,
  1629. self.driver.private_storage.get(
  1630. second_replica['id'], 'repl_snapshot_tag'))
  1631. def test_promote_replica_active_not_available(self):
  1632. active_replica = {
  1633. 'id': 'fake_active_replica_id',
  1634. 'host': 'hostname1@backend_name1#foo',
  1635. 'size': 5,
  1636. 'replica_state': zfs_driver.constants.REPLICA_STATE_ACTIVE,
  1637. }
  1638. replica = {
  1639. 'id': 'fake_first_replica_id',
  1640. 'host': 'hostname2@backend_name2#bar',
  1641. 'share_proto': 'NFS',
  1642. 'replica_state': zfs_driver.constants.REPLICA_STATE_IN_SYNC,
  1643. }
  1644. second_replica = {
  1645. 'id': 'fake_second_replica_id',
  1646. 'host': 'hostname3@backend_name3#quuz',
  1647. 'share_proto': 'NFS',
  1648. 'replica_state': zfs_driver.constants.REPLICA_STATE_IN_SYNC,
  1649. }
  1650. third_replica = {
  1651. 'id': 'fake_third_replica_id',
  1652. 'host': 'hostname4@backend_name4#fff',
  1653. 'share_proto': 'NFS',
  1654. 'replica_state': zfs_driver.constants.REPLICA_STATE_IN_SYNC,
  1655. }
  1656. replica_list = [replica, active_replica, second_replica, third_replica]
  1657. dst_dataset_name = (
  1658. 'bar/subbar/fake_dataset_name_prefix%s' % replica['id'])
  1659. src_dataset_name = (
  1660. 'bar/subbar/fake_dataset_name_prefix%s' % active_replica['id'])
  1661. access_rules = ['foo_rule', 'bar_rule']
  1662. old_repl_snapshot_tag = (
  1663. self.driver._get_replication_snapshot_prefix(
  1664. active_replica) + 'foo')
  1665. snap_tag_prefix = self.driver._get_replication_snapshot_prefix(
  1666. replica) + '_time_some_time'
  1667. self.driver.private_storage.update(
  1668. active_replica['id'],
  1669. {'dataset_name': src_dataset_name,
  1670. 'ssh_cmd': 'fake_src_ssh_cmd',
  1671. 'repl_snapshot_tag': old_repl_snapshot_tag}
  1672. )
  1673. for repl in (replica, second_replica, third_replica):
  1674. self.driver.private_storage.update(
  1675. repl['id'],
  1676. {'dataset_name': (
  1677. 'bar/subbar/fake_dataset_name_prefix%s' % repl['id']),
  1678. 'ssh_cmd': 'fake_dst_ssh_cmd',
  1679. 'repl_snapshot_tag': old_repl_snapshot_tag}
  1680. )
  1681. self.mock_object(
  1682. self.driver, 'execute',
  1683. mock.Mock(side_effect=[
  1684. exception.ProcessExecutionError('Active replica failure'),
  1685. ('a', 'b'),
  1686. exception.ProcessExecutionError('Second replica sync failure'),
  1687. ('c', 'd'),
  1688. ]))
  1689. self.mock_object(self.driver, 'zfs',
  1690. mock.Mock(side_effect=[('g', 'h'), ('i', 'j')]))
  1691. mock_helper = self.mock_object(self.driver, '_get_share_helper')
  1692. self.configuration.zfs_dataset_name_prefix = 'fake_dataset_name_prefix'
  1693. mock_utcnow = self.mock_object(zfs_driver.timeutils, 'utcnow')
  1694. mock_utcnow.return_value.isoformat.return_value = 'some_time'
  1695. mock_delete_snapshot = self.mock_object(
  1696. self.driver, '_delete_dataset_or_snapshot_with_retry')
  1697. result = self.driver.promote_replica(
  1698. 'fake_context', replica_list, replica, access_rules)
  1699. expected = [
  1700. {'access_rules_status':
  1701. zfs_driver.constants.SHARE_INSTANCE_RULES_SYNCING,
  1702. 'id': 'fake_active_replica_id',
  1703. 'replica_state': zfs_driver.constants.REPLICA_STATE_OUT_OF_SYNC},
  1704. {'access_rules_status': zfs_driver.constants.STATUS_ACTIVE,
  1705. 'id': 'fake_first_replica_id',
  1706. 'replica_state': zfs_driver.constants.REPLICA_STATE_ACTIVE},
  1707. {'access_rules_status':
  1708. zfs_driver.constants.SHARE_INSTANCE_RULES_SYNCING,
  1709. 'id': 'fake_second_replica_id'},
  1710. {'access_rules_status':
  1711. zfs_driver.constants.SHARE_INSTANCE_RULES_SYNCING,
  1712. 'id': 'fake_third_replica_id',
  1713. 'replica_state': zfs_driver.constants.REPLICA_STATE_OUT_OF_SYNC},
  1714. ]
  1715. for repl in expected:
  1716. self.assertIn(repl, result)
  1717. self.assertEqual(4, len(result))
  1718. mock_helper.assert_called_once_with('NFS')
  1719. mock_helper.return_value.update_access.assert_called_once_with(
  1720. dst_dataset_name, access_rules, add_rules=[], delete_rules=[])
  1721. self.driver.zfs.assert_has_calls([
  1722. mock.call('snapshot', dst_dataset_name + '@' + snap_tag_prefix),
  1723. mock.call('set', 'readonly=off', dst_dataset_name),
  1724. ])
  1725. self.assertEqual(0, mock_delete_snapshot.call_count)
  1726. for repl in (second_replica, replica):
  1727. self.assertEqual(
  1728. snap_tag_prefix,
  1729. self.driver.private_storage.get(
  1730. repl['id'], 'repl_snapshot_tag'))
  1731. for repl in (active_replica, third_replica):
  1732. self.assertEqual(
  1733. old_repl_snapshot_tag,
  1734. self.driver.private_storage.get(
  1735. repl['id'], 'repl_snapshot_tag'))
  1736. def test_create_replicated_snapshot(self):
  1737. active_replica = {
  1738. 'id': 'fake_active_replica_id',
  1739. 'replica_state': zfs_driver.constants.REPLICA_STATE_ACTIVE,
  1740. }
  1741. replica = {
  1742. 'id': 'fake_first_replica_id',
  1743. 'replica_state': zfs_driver.constants.REPLICA_STATE_IN_SYNC,
  1744. }
  1745. second_replica = {
  1746. 'id': 'fake_second_replica_id',
  1747. 'replica_state': zfs_driver.constants.REPLICA_STATE_IN_SYNC,
  1748. }
  1749. replica_list = [replica, active_replica, second_replica]
  1750. snapshot_instances = [
  1751. {'id': 'si_%s' % r['id'], 'share_instance_id': r['id'],
  1752. 'snapshot_id': 'some_snapshot_id'}
  1753. for r in replica_list
  1754. ]
  1755. src_dataset_name = (
  1756. 'bar/subbar/fake_dataset_name_prefix%s' % active_replica['id'])
  1757. old_repl_snapshot_tag = (
  1758. self.driver._get_replication_snapshot_prefix(
  1759. active_replica) + 'foo')
  1760. self.driver.private_storage.update(
  1761. active_replica['id'],
  1762. {'dataset_name': src_dataset_name,
  1763. 'ssh_cmd': 'fake_src_ssh_cmd',
  1764. 'repl_snapshot_tag': old_repl_snapshot_tag}
  1765. )
  1766. for repl in (replica, second_replica):
  1767. self.driver.private_storage.update(
  1768. repl['id'],
  1769. {'dataset_name': (
  1770. 'bar/subbar/fake_dataset_name_prefix%s' % repl['id']),
  1771. 'ssh_cmd': 'fake_dst_ssh_cmd',
  1772. 'repl_snapshot_tag': old_repl_snapshot_tag}
  1773. )
  1774. self.mock_object(
  1775. self.driver, 'execute', mock.Mock(side_effect=[
  1776. ('a', 'b'),
  1777. ('c', 'd'),
  1778. ('e', 'f'),
  1779. exception.ProcessExecutionError('Second replica sync failure'),
  1780. ]))
  1781. self.configuration.zfs_dataset_name_prefix = 'fake_dataset_name_prefix'
  1782. self.configuration.zfs_dataset_snapshot_name_prefix = (
  1783. 'fake_dataset_snapshot_name_prefix')
  1784. snap_tag_prefix = (
  1785. self.configuration.zfs_dataset_snapshot_name_prefix +
  1786. 'si_%s' % active_replica['id'])
  1787. repl_snap_tag = 'fake_repl_tag'
  1788. self.mock_object(
  1789. self.driver, '_get_replication_snapshot_tag',
  1790. mock.Mock(return_value=repl_snap_tag))
  1791. result = self.driver.create_replicated_snapshot(
  1792. 'fake_context', replica_list, snapshot_instances)
  1793. expected = [
  1794. {'id': 'si_fake_active_replica_id',
  1795. 'status': zfs_driver.constants.STATUS_AVAILABLE},
  1796. {'id': 'si_fake_first_replica_id',
  1797. 'status': zfs_driver.constants.STATUS_AVAILABLE},
  1798. {'id': 'si_fake_second_replica_id',
  1799. 'status': zfs_driver.constants.STATUS_ERROR},
  1800. ]
  1801. for repl in expected:
  1802. self.assertIn(repl, result)
  1803. self.assertEqual(3, len(result))
  1804. for repl in (active_replica, replica):
  1805. self.assertEqual(
  1806. repl_snap_tag,
  1807. self.driver.private_storage.get(
  1808. repl['id'], 'repl_snapshot_tag'))
  1809. self.assertEqual(
  1810. old_repl_snapshot_tag,
  1811. self.driver.private_storage.get(
  1812. second_replica['id'], 'repl_snapshot_tag'))
  1813. self.assertEqual(
  1814. snap_tag_prefix,
  1815. self.driver.private_storage.get(
  1816. snapshot_instances[0]['snapshot_id'], 'snapshot_tag'))
  1817. self.driver._get_replication_snapshot_tag.assert_called_once_with(
  1818. active_replica)
  1819. def test_delete_replicated_snapshot(self):
  1820. active_replica = {
  1821. 'id': 'fake_active_replica_id',
  1822. 'replica_state': zfs_driver.constants.REPLICA_STATE_ACTIVE,
  1823. }
  1824. replica = {
  1825. 'id': 'fake_first_replica_id',
  1826. 'replica_state': zfs_driver.constants.REPLICA_STATE_IN_SYNC,
  1827. }
  1828. second_replica = {
  1829. 'id': 'fake_second_replica_id',
  1830. 'replica_state': zfs_driver.constants.REPLICA_STATE_IN_SYNC,
  1831. }
  1832. replica_list = [replica, active_replica, second_replica]
  1833. active_snapshot_instance = {
  1834. 'id': 'si_%s' % active_replica['id'],
  1835. 'share_instance_id': active_replica['id'],
  1836. 'snapshot_id': 'some_snapshot_id',
  1837. 'share_id': 'some_share_id',
  1838. }
  1839. snapshot_instances = [
  1840. {'id': 'si_%s' % r['id'], 'share_instance_id': r['id'],
  1841. 'snapshot_id': active_snapshot_instance['snapshot_id'],
  1842. 'share_id': active_snapshot_instance['share_id']}
  1843. for r in (replica, second_replica)
  1844. ]
  1845. snapshot_instances.append(active_snapshot_instance)
  1846. for si in snapshot_instances:
  1847. self.driver.private_storage.update(
  1848. si['id'], {'snapshot_name': 'fake_snap_name_%s' % si['id']})
  1849. src_dataset_name = (
  1850. 'bar/subbar/fake_dataset_name_prefix%s' % active_replica['id'])
  1851. old_repl_snapshot_tag = (
  1852. self.driver._get_replication_snapshot_prefix(
  1853. active_replica) + 'foo')
  1854. new_repl_snapshot_tag = 'foo_snapshot_tag'
  1855. dataset_name = 'some_dataset_name'
  1856. self.driver.private_storage.update(
  1857. active_replica['id'],
  1858. {'dataset_name': src_dataset_name,
  1859. 'ssh_cmd': 'fake_src_ssh_cmd',
  1860. 'repl_snapshot_tag': old_repl_snapshot_tag}
  1861. )
  1862. for replica in (replica, second_replica):
  1863. self.driver.private_storage.update(
  1864. replica['id'],
  1865. {'dataset_name': dataset_name,
  1866. 'ssh_cmd': 'fake_ssh_cmd'}
  1867. )
  1868. self.driver.private_storage.update(
  1869. snapshot_instances[0]['snapshot_id'],
  1870. {'snapshot_tag': new_repl_snapshot_tag}
  1871. )
  1872. snap_name = 'fake_snap_name'
  1873. self.mock_object(
  1874. self.driver, 'zfs', mock.Mock(return_value=['out', 'err']))
  1875. self.mock_object(
  1876. self.driver, 'execute', mock.Mock(side_effect=[
  1877. ('a', 'b'),
  1878. ('c', 'd'),
  1879. exception.ProcessExecutionError('Second replica sync failure'),
  1880. ]))
  1881. self.mock_object(
  1882. self.driver, 'parse_zfs_answer', mock.Mock(side_effect=[
  1883. ({'NAME': 'foo'}, {'NAME': snap_name}),
  1884. ({'NAME': 'bar'}, {'NAME': snap_name}),
  1885. [],
  1886. ]))
  1887. expected = sorted([
  1888. {'id': si['id'], 'status': 'deleted'} for si in snapshot_instances
  1889. ], key=lambda item: item['id'])
  1890. self.assertEqual(
  1891. new_repl_snapshot_tag,
  1892. self.driver.private_storage.get(
  1893. snapshot_instances[0]['snapshot_id'], 'snapshot_tag'))
  1894. result = self.driver.delete_replicated_snapshot(
  1895. 'fake_context', replica_list, snapshot_instances)
  1896. self.assertIsNone(
  1897. self.driver.private_storage.get(
  1898. snapshot_instances[0]['snapshot_id'], 'snapshot_tag'))
  1899. self.driver.execute.assert_has_calls([
  1900. mock.call('ssh', 'fake_ssh_cmd', 'sudo', 'zfs', 'list', '-r', '-t',
  1901. 'snapshot', dataset_name + '@' + new_repl_snapshot_tag)
  1902. for i in (0, 1)
  1903. ])
  1904. self.assertIsInstance(result, list)
  1905. self.assertEqual(3, len(result))
  1906. self.assertEqual(expected, sorted(result, key=lambda item: item['id']))
  1907. self.driver.parse_zfs_answer.assert_has_calls([
  1908. mock.call('out'),
  1909. ])
  1910. @ddt.data(
  1911. ({'NAME': 'fake'}, zfs_driver.constants.STATUS_ERROR),
  1912. ({'NAME': 'fake_snap_name'}, zfs_driver.constants.STATUS_AVAILABLE),
  1913. )
  1914. @ddt.unpack
  1915. def test_update_replicated_snapshot(self, parse_answer, expected_status):
  1916. snap_name = 'fake_snap_name'
  1917. self.mock_object(self.driver, '_update_replica_state')
  1918. self.mock_object(
  1919. self.driver, '_get_saved_snapshot_name',
  1920. mock.Mock(return_value=snap_name))
  1921. self.mock_object(
  1922. self.driver, 'zfs', mock.Mock(side_effect=[('a', 'b')]))
  1923. self.mock_object(
  1924. self.driver, 'parse_zfs_answer', mock.Mock(side_effect=[
  1925. [parse_answer]
  1926. ]))
  1927. fake_context = 'fake_context'
  1928. replica_list = ['foo', 'bar']
  1929. share_replica = 'quuz'
  1930. snapshot_instance = {'id': 'fake_snapshot_instance_id'}
  1931. snapshot_instances = ['q', 'w', 'e', 'r', 't', 'y']
  1932. result = self.driver.update_replicated_snapshot(
  1933. fake_context, replica_list, share_replica, snapshot_instances,
  1934. snapshot_instance)
  1935. self.driver._update_replica_state.assert_called_once_with(
  1936. fake_context, replica_list, share_replica)
  1937. self.driver._get_saved_snapshot_name.assert_called_once_with(
  1938. snapshot_instance)
  1939. self.driver.zfs.assert_called_once_with(
  1940. 'list', '-r', '-t', 'snapshot', snap_name)
  1941. self.driver.parse_zfs_answer.assert_called_once_with('a')
  1942. self.assertIsInstance(result, dict)
  1943. self.assertEqual(2, len(result))
  1944. self.assertIn('status', result)
  1945. self.assertIn('id', result)
  1946. self.assertEqual(expected_status, result['status'])
  1947. self.assertEqual(snapshot_instance['id'], result['id'])
  1948. def test__get_shell_executor_by_host_local(self):
  1949. backend_name = 'foobackend'
  1950. host = 'foohost@%s#foopool' % backend_name
  1951. CONF.set_default(
  1952. 'enabled_share_backends', 'fake1,%s,fake2,fake3' % backend_name)
  1953. self.assertIsNone(self.driver._shell_executors.get(backend_name))
  1954. result = self.driver._get_shell_executor_by_host(host)
  1955. self.assertEqual(self.driver.execute, result)
  1956. def test__get_shell_executor_by_host_remote(self):
  1957. backend_name = 'foobackend'
  1958. host = 'foohost@%s#foopool' % backend_name
  1959. CONF.set_default('enabled_share_backends', 'fake1,fake2,fake3')
  1960. mock_get_remote_shell_executor = self.mock_object(
  1961. zfs_driver.zfs_utils, 'get_remote_shell_executor')
  1962. mock_config = self.mock_object(zfs_driver, 'get_backend_configuration')
  1963. self.assertIsNone(self.driver._shell_executors.get(backend_name))
  1964. for i in (1, 2):
  1965. result = self.driver._get_shell_executor_by_host(host)
  1966. self.assertEqual(
  1967. mock_get_remote_shell_executor.return_value, result)
  1968. mock_get_remote_shell_executor.assert_called_once_with(
  1969. ip=mock_config.return_value.zfs_service_ip,
  1970. port=22,
  1971. conn_timeout=mock_config.return_value.ssh_conn_timeout,
  1972. login=mock_config.return_value.zfs_ssh_username,
  1973. password=mock_config.return_value.zfs_ssh_user_password,
  1974. privatekey=mock_config.return_value.zfs_ssh_private_key_path,
  1975. max_size=10,
  1976. )
  1977. zfs_driver.get_backend_configuration.assert_called_once_with(
  1978. backend_name)
  1979. def test__get_migration_snapshot_tag(self):
  1980. share_instance = {'id': 'fake-share_instance_id'}
  1981. current_time = 'fake_current_time'
  1982. mock_utcnow = self.mock_object(zfs_driver.timeutils, 'utcnow')
  1983. mock_utcnow.return_value.isoformat.return_value = current_time
  1984. expected_value = (
  1985. self.driver.migration_snapshot_prefix +
  1986. '_fake_share_instance_id_time_' + current_time)
  1987. result = self.driver._get_migration_snapshot_tag(share_instance)
  1988. self.assertEqual(expected_value, result)
  1989. def test_migration_check_compatibility(self):
  1990. src_share = {'host': 'foohost@foobackend#foopool'}
  1991. dst_backend_name = 'barbackend'
  1992. dst_share = {'host': 'barhost@%s#barpool' % dst_backend_name}
  1993. expected = {
  1994. 'compatible': True,
  1995. 'writable': False,
  1996. 'preserve_metadata': True,
  1997. 'nondisruptive': True,
  1998. }
  1999. self.mock_object(
  2000. zfs_driver,
  2001. 'get_backend_configuration',
  2002. mock.Mock(return_value=type(
  2003. 'FakeConfig', (object,), {
  2004. 'share_driver': self.driver.configuration.share_driver})))
  2005. actual = self.driver.migration_check_compatibility(
  2006. 'fake_context', src_share, dst_share)
  2007. self.assertEqual(expected, actual)
  2008. zfs_driver.get_backend_configuration.assert_called_once_with(
  2009. dst_backend_name)
  2010. def test_migration_start(self):
  2011. username = self.driver.configuration.zfs_ssh_username
  2012. hostname = self.driver.configuration.zfs_service_ip
  2013. dst_username = username + '_dst'
  2014. dst_hostname = hostname + '_dst'
  2015. src_share = {
  2016. 'id': 'fake_src_share_id',
  2017. 'host': 'foohost@foobackend#foopool',
  2018. }
  2019. src_dataset_name = 'foo_dataset_name'
  2020. dst_share = {
  2021. 'id': 'fake_dst_share_id',
  2022. 'host': 'barhost@barbackend#barpool',
  2023. }
  2024. dst_dataset_name = 'bar_dataset_name'
  2025. snapshot_tag = 'fake_migration_snapshot_tag'
  2026. self.mock_object(
  2027. self.driver,
  2028. '_get_dataset_name',
  2029. mock.Mock(return_value=dst_dataset_name))
  2030. self.mock_object(
  2031. self.driver,
  2032. '_get_migration_snapshot_tag',
  2033. mock.Mock(return_value=snapshot_tag))
  2034. self.mock_object(
  2035. zfs_driver,
  2036. 'get_backend_configuration',
  2037. mock.Mock(return_value=type(
  2038. 'FakeConfig', (object,), {
  2039. 'zfs_ssh_username': dst_username,
  2040. 'zfs_service_ip': dst_hostname,
  2041. })))
  2042. self.mock_object(self.driver, 'execute')
  2043. self.mock_object(
  2044. zfs_driver.utils, 'tempdir',
  2045. mock.MagicMock(side_effect=FakeTempDir))
  2046. self.driver.private_storage.update(
  2047. src_share['id'],
  2048. {'dataset_name': src_dataset_name,
  2049. 'ssh_cmd': username + '@' + hostname})
  2050. src_snapshot_name = (
  2051. '%(dataset_name)s@%(snapshot_tag)s' % {
  2052. 'snapshot_tag': snapshot_tag,
  2053. 'dataset_name': src_dataset_name,
  2054. }
  2055. )
  2056. with mock.patch("six.moves.builtins.open",
  2057. mock.mock_open(read_data="data")) as mock_file:
  2058. self.driver.migration_start(
  2059. self._context, src_share, dst_share, None, None)
  2060. expected_file_content = (
  2061. 'ssh %(ssh_cmd)s sudo zfs send -vDR %(snap)s | '
  2062. 'ssh %(dst_ssh_cmd)s sudo zfs receive -v %(dst_dataset)s'
  2063. ) % {
  2064. 'ssh_cmd': self.driver.private_storage.get(
  2065. src_share['id'], 'ssh_cmd'),
  2066. 'dst_ssh_cmd': self.driver.private_storage.get(
  2067. dst_share['id'], 'ssh_cmd'),
  2068. 'snap': src_snapshot_name,
  2069. 'dst_dataset': dst_dataset_name,
  2070. }
  2071. mock_file.assert_called_with("/foo/path/bar_dataset_name.sh", "w")
  2072. mock_file.return_value.write.assert_called_once_with(
  2073. expected_file_content)
  2074. self.driver.execute.assert_has_calls([
  2075. mock.call('sudo', 'zfs', 'snapshot', src_snapshot_name),
  2076. mock.call('sudo', 'chmod', '755', mock.ANY),
  2077. mock.call('nohup', mock.ANY, '&'),
  2078. ])
  2079. self.driver._get_migration_snapshot_tag.assert_called_once_with(
  2080. dst_share)
  2081. self.driver._get_dataset_name.assert_called_once_with(
  2082. dst_share)
  2083. for k, v in (('dataset_name', dst_dataset_name),
  2084. ('migr_snapshot_tag', snapshot_tag),
  2085. ('pool_name', 'barpool'),
  2086. ('ssh_cmd', dst_username + '@' + dst_hostname)):
  2087. self.assertEqual(
  2088. v, self.driver.private_storage.get(dst_share['id'], k))
  2089. def test_migration_continue_success(self):
  2090. dst_share = {
  2091. 'id': 'fake_dst_share_id',
  2092. 'host': 'barhost@barbackend#barpool',
  2093. }
  2094. dst_dataset_name = 'bar_dataset_name'
  2095. snapshot_tag = 'fake_migration_snapshot_tag'
  2096. self.driver.private_storage.update(
  2097. dst_share['id'], {
  2098. 'migr_snapshot_tag': snapshot_tag,
  2099. 'dataset_name': dst_dataset_name,
  2100. })
  2101. mock_executor = self.mock_object(
  2102. self.driver, '_get_shell_executor_by_host')
  2103. self.mock_object(
  2104. self.driver, 'execute',
  2105. mock.Mock(return_value=('fake_out', 'fake_err')))
  2106. result = self.driver.migration_continue(
  2107. self._context, 'fake_src_share', dst_share, None, None)
  2108. self.assertTrue(result)
  2109. mock_executor.assert_called_once_with(dst_share['host'])
  2110. self.driver.execute.assert_has_calls([
  2111. mock.call('ps', 'aux'),
  2112. mock.call('sudo', 'zfs', 'get', 'quota', dst_dataset_name,
  2113. executor=mock_executor.return_value),
  2114. ])
  2115. def test_migration_continue_pending(self):
  2116. dst_share = {
  2117. 'id': 'fake_dst_share_id',
  2118. 'host': 'barhost@barbackend#barpool',
  2119. }
  2120. dst_dataset_name = 'bar_dataset_name'
  2121. snapshot_tag = 'fake_migration_snapshot_tag'
  2122. self.driver.private_storage.update(
  2123. dst_share['id'], {
  2124. 'migr_snapshot_tag': snapshot_tag,
  2125. 'dataset_name': dst_dataset_name,
  2126. })
  2127. mock_executor = self.mock_object(
  2128. self.driver, '_get_shell_executor_by_host')
  2129. self.mock_object(
  2130. self.driver, 'execute',
  2131. mock.Mock(return_value=('foo@%s' % snapshot_tag, 'fake_err')))
  2132. result = self.driver.migration_continue(
  2133. self._context, 'fake_src_share', dst_share, None, None)
  2134. self.assertIsNone(result)
  2135. self.assertFalse(mock_executor.called)
  2136. self.driver.execute.assert_called_once_with('ps', 'aux')
  2137. def test_migration_continue_exception(self):
  2138. dst_share = {
  2139. 'id': 'fake_dst_share_id',
  2140. 'host': 'barhost@barbackend#barpool',
  2141. }
  2142. dst_dataset_name = 'bar_dataset_name'
  2143. snapshot_tag = 'fake_migration_snapshot_tag'
  2144. self.driver.private_storage.update(
  2145. dst_share['id'], {
  2146. 'migr_snapshot_tag': snapshot_tag,
  2147. 'dataset_name': dst_dataset_name,
  2148. })
  2149. mock_executor = self.mock_object(
  2150. self.driver, '_get_shell_executor_by_host')
  2151. self.mock_object(
  2152. self.driver, 'execute',
  2153. mock.Mock(side_effect=[
  2154. ('fake_out', 'fake_err'),
  2155. exception.ProcessExecutionError('fake'),
  2156. ]))
  2157. self.assertRaises(
  2158. exception.ZFSonLinuxException,
  2159. self.driver.migration_continue,
  2160. self._context, 'fake_src_share', dst_share, None, None
  2161. )
  2162. mock_executor.assert_called_once_with(dst_share['host'])
  2163. self.driver.execute.assert_has_calls([
  2164. mock.call('ps', 'aux'),
  2165. mock.call('sudo', 'zfs', 'get', 'quota', dst_dataset_name,
  2166. executor=mock_executor.return_value),
  2167. ])
  2168. def test_migration_complete(self):
  2169. src_share = {'id': 'fake_src_share_id'}
  2170. dst_share = {
  2171. 'id': 'fake_dst_share_id',
  2172. 'host': 'barhost@barbackend#barpool',
  2173. 'share_proto': 'fake_share_proto',
  2174. }
  2175. dst_dataset_name = 'bar_dataset_name'
  2176. snapshot_tag = 'fake_migration_snapshot_tag'
  2177. self.driver.private_storage.update(
  2178. dst_share['id'], {
  2179. 'migr_snapshot_tag': snapshot_tag,
  2180. 'dataset_name': dst_dataset_name,
  2181. })
  2182. dst_snapshot_name = (
  2183. '%(dataset_name)s@%(snapshot_tag)s' % {
  2184. 'snapshot_tag': snapshot_tag,
  2185. 'dataset_name': dst_dataset_name,
  2186. }
  2187. )
  2188. mock_helper = self.mock_object(self.driver, '_get_share_helper')
  2189. mock_executor = self.mock_object(
  2190. self.driver, '_get_shell_executor_by_host')
  2191. self.mock_object(
  2192. self.driver, 'execute',
  2193. mock.Mock(return_value=('fake_out', 'fake_err')))
  2194. self.mock_object(self.driver, 'delete_share')
  2195. result = self.driver.migration_complete(
  2196. self._context, src_share, dst_share, None, None)
  2197. expected_result = {
  2198. 'export_locations': (mock_helper.return_value.
  2199. create_exports.return_value)
  2200. }
  2201. self.assertEqual(expected_result, result)
  2202. mock_executor.assert_called_once_with(dst_share['host'])
  2203. self.driver.execute.assert_called_once_with(
  2204. 'sudo', 'zfs', 'destroy', dst_snapshot_name,
  2205. executor=mock_executor.return_value,
  2206. )
  2207. self.driver.delete_share.assert_called_once_with(
  2208. self._context, src_share)
  2209. mock_helper.assert_called_once_with(dst_share['share_proto'])
  2210. mock_helper.return_value.create_exports.assert_called_once_with(
  2211. dst_dataset_name,
  2212. executor=self.driver._get_shell_executor_by_host.return_value)
  2213. def test_migration_cancel_success(self):
  2214. src_dataset_name = 'fake_src_dataset_name'
  2215. src_share = {
  2216. 'id': 'fake_src_share_id',
  2217. 'dataset_name': src_dataset_name,
  2218. }
  2219. dst_share = {
  2220. 'id': 'fake_dst_share_id',
  2221. 'host': 'barhost@barbackend#barpool',
  2222. 'share_proto': 'fake_share_proto',
  2223. }
  2224. dst_dataset_name = 'fake_dst_dataset_name'
  2225. snapshot_tag = 'fake_migration_snapshot_tag'
  2226. dst_ssh_cmd = 'fake_dst_ssh_cmd'
  2227. self.driver.private_storage.update(
  2228. src_share['id'], {'dataset_name': src_dataset_name})
  2229. self.driver.private_storage.update(
  2230. dst_share['id'], {
  2231. 'migr_snapshot_tag': snapshot_tag,
  2232. 'dataset_name': dst_dataset_name,
  2233. 'ssh_cmd': dst_ssh_cmd,
  2234. })
  2235. self.mock_object(zfs_driver.time, 'sleep')
  2236. mock_delete_dataset = self.mock_object(
  2237. self.driver, '_delete_dataset_or_snapshot_with_retry')
  2238. ps_output = (
  2239. "fake_line1\nfoo_user 12345 foo_dataset_name@%s\n"
  2240. "fake_line2") % snapshot_tag
  2241. self.mock_object(
  2242. self.driver, 'execute',
  2243. mock.Mock(return_value=(ps_output, 'fake_err'))
  2244. )
  2245. self.driver.migration_cancel(
  2246. self._context, src_share, dst_share, [], {})
  2247. self.driver.execute.assert_has_calls([
  2248. mock.call('ps', 'aux'),
  2249. mock.call('sudo', 'kill', '-9', '12345'),
  2250. mock.call('ssh', dst_ssh_cmd, 'sudo', 'zfs', 'destroy', '-r',
  2251. dst_dataset_name),
  2252. ])
  2253. zfs_driver.time.sleep.assert_called_once_with(2)
  2254. mock_delete_dataset.assert_called_once_with(
  2255. src_dataset_name + '@' + snapshot_tag)
  2256. def test_migration_cancel_error(self):
  2257. src_dataset_name = 'fake_src_dataset_name'
  2258. src_share = {
  2259. 'id': 'fake_src_share_id',
  2260. 'dataset_name': src_dataset_name,
  2261. }
  2262. dst_share = {
  2263. 'id': 'fake_dst_share_id',
  2264. 'host': 'barhost@barbackend#barpool',
  2265. 'share_proto': 'fake_share_proto',
  2266. }
  2267. dst_dataset_name = 'fake_dst_dataset_name'
  2268. snapshot_tag = 'fake_migration_snapshot_tag'
  2269. dst_ssh_cmd = 'fake_dst_ssh_cmd'
  2270. self.driver.private_storage.update(
  2271. src_share['id'], {'dataset_name': src_dataset_name})
  2272. self.driver.private_storage.update(
  2273. dst_share['id'], {
  2274. 'migr_snapshot_tag': snapshot_tag,
  2275. 'dataset_name': dst_dataset_name,
  2276. 'ssh_cmd': dst_ssh_cmd,
  2277. })
  2278. self.mock_object(zfs_driver.time, 'sleep')
  2279. mock_delete_dataset = self.mock_object(
  2280. self.driver, '_delete_dataset_or_snapshot_with_retry')
  2281. self.mock_object(
  2282. self.driver, 'execute',
  2283. mock.Mock(side_effect=exception.ProcessExecutionError),
  2284. )
  2285. self.driver.migration_cancel(
  2286. self._context, src_share, dst_share, [], {})
  2287. self.driver.execute.assert_has_calls([
  2288. mock.call('ps', 'aux'),
  2289. mock.call('ssh', dst_ssh_cmd, 'sudo', 'zfs', 'destroy', '-r',
  2290. dst_dataset_name),
  2291. ])
  2292. zfs_driver.time.sleep.assert_called_once_with(2)
  2293. mock_delete_dataset.assert_called_once_with(
  2294. src_dataset_name + '@' + snapshot_tag)