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