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