"Fossies" - the Fresh Open Source Software Archive

Member "manila-11.0.1/manila/tests/share/drivers/zfsonlinux/test_driver.py" (1 Feb 2021, 107232 Bytes) of package /linux/misc/openstack/manila-11.0.1.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. See also the latest Fossies "Diffs" side-by-side code changes report for "test_driver.py": 11.0.0_vs_11.0.1.

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