예제 #1
0
 def setUp(self, mock_ghn, mock_grnam, mock_pwnam):
     """Set up UUT and all the flags required for later fake_executes."""
     super(HGSTTestCase, self).setUp()
     self.stubs.Set(processutils, 'execute', self._fake_execute)
     self._fail_vgc_cluster = False
     self._fail_ip = False
     self._fail_network_list = False
     self._fail_domain_list = False
     self._empty_domain_list = False
     self._fail_host_storage = False
     self._fail_space_list = False
     self._fail_space_delete = False
     self._fail_set_apphosts = False
     self._fail_extend = False
     self._request_cancel = False
     self._return_blocked = 0
     self.configuration = mock.Mock(spec=conf.Configuration)
     self.configuration.safe_get = self._fake_safe_get
     self._reset_configuration()
     self.driver = HGSTDriver(configuration=self.configuration,
                              execute=self._fake_execute)
예제 #2
0
 def setUp(self, mock_ghn, mock_grnam, mock_pwnam):
     """Set up UUT and all the flags required for later fake_executes."""
     super(HGSTTestCase, self).setUp()
     self.stubs.Set(processutils, "execute", self._fake_execute)
     self._fail_vgc_cluster = False
     self._fail_ip = False
     self._fail_network_list = False
     self._fail_domain_list = False
     self._empty_domain_list = False
     self._fail_host_storage = False
     self._fail_space_list = False
     self._fail_space_delete = False
     self._fail_set_apphosts = False
     self._fail_extend = False
     self._request_cancel = False
     self._return_blocked = 0
     self.configuration = mock.Mock(spec=conf.Configuration)
     self.configuration.safe_get = self._fake_safe_get
     self._reset_configuration()
     self.driver = HGSTDriver(configuration=self.configuration, execute=self._fake_execute)
예제 #3
0
class HGSTTestCase(test.TestCase):

    # Need to mock these since we use them on driver creation
    @mock.patch('pwd.getpwnam', return_value=1)
    @mock.patch('grp.getgrnam', return_value=1)
    @mock.patch('socket.gethostbyname', return_value='123.123.123.123')
    def setUp(self, mock_ghn, mock_grnam, mock_pwnam):
        """Set up UUT and all the flags required for later fake_executes."""
        super(HGSTTestCase, self).setUp()
        self.stubs.Set(processutils, 'execute', self._fake_execute)
        self._fail_vgc_cluster = False
        self._fail_ip = False
        self._fail_network_list = False
        self._fail_domain_list = False
        self._empty_domain_list = False
        self._fail_host_storage = False
        self._fail_space_list = False
        self._fail_space_delete = False
        self._fail_set_apphosts = False
        self._fail_extend = False
        self._request_cancel = False
        self._return_blocked = 0
        self.configuration = mock.Mock(spec=conf.Configuration)
        self.configuration.safe_get = self._fake_safe_get
        self._reset_configuration()
        self.driver = HGSTDriver(configuration=self.configuration,
                                 execute=self._fake_execute)

    def _fake_safe_get(self, value):
        """Don't throw exception on missing parameters, return None."""
        try:
            val = getattr(self.configuration, value)
        except AttributeError:
            val = None
        return val

    def _reset_configuration(self):
        """Set safe and sane values for config params."""
        self.configuration.num_volume_device_scan_tries = 1
        self.configuration.volume_dd_blocksize = '1M'
        self.configuration.volume_backend_name = 'hgst-1'
        self.configuration.hgst_storage_servers = 'stor1:gbd0,stor2:gbd0'
        self.configuration.hgst_net = 'net1'
        self.configuration.hgst_redundancy = '0'
        self.configuration.hgst_space_user = '******'
        self.configuration.hgst_space_group = 'xanadu'
        self.configuration.hgst_space_mode = '0777'

    def _parse_space_create(self, *cmd):
        """Eats a vgc-cluster space-create command line to a dict."""
        self.created = {'storageserver': ''}
        cmd = list(*cmd)
        while cmd:
            param = cmd.pop(0)
            if param == "-n":
                self.created['name'] = cmd.pop(0)
            elif param == "-N":
                self.created['net'] = cmd.pop(0)
            elif param == "-s":
                self.created['size'] = cmd.pop(0)
            elif param == "--redundancy":
                self.created['redundancy'] = cmd.pop(0)
            elif param == "--user":
                self.created['user'] = cmd.pop(0)
            elif param == "--user":
                self.created['user'] = cmd.pop(0)
            elif param == "--group":
                self.created['group'] = cmd.pop(0)
            elif param == "--mode":
                self.created['mode'] = cmd.pop(0)
            elif param == "-S":
                self.created['storageserver'] += cmd.pop(0) + ","
            else:
                pass

    def _parse_space_extend(self, *cmd):
        """Eats a vgc-cluster space-extend commandline to a dict."""
        self.extended = {'storageserver': ''}
        cmd = list(*cmd)
        while cmd:
            param = cmd.pop(0)
            if param == "-n":
                self.extended['name'] = cmd.pop(0)
            elif param == "-s":
                self.extended['size'] = cmd.pop(0)
            elif param == "-S":
                self.extended['storageserver'] += cmd.pop(0) + ","
            else:
                pass
        if self._fail_extend:
            raise processutils.ProcessExecutionError(exit_code=1)
        else:
            return '', ''

    def _parse_space_delete(self, *cmd):
        """Eats a vgc-cluster space-delete commandline to a dict."""
        self.deleted = {}
        cmd = list(*cmd)
        while cmd:
            param = cmd.pop(0)
            if param == "-n":
                self.deleted['name'] = cmd.pop(0)
            else:
                pass
        if self._fail_space_delete:
            raise processutils.ProcessExecutionError(exit_code=1)
        else:
            return '', ''

    def _parse_space_list(self, *cmd):
        """Eats a vgc-cluster space-list commandline to a dict."""
        json = False
        nameOnly = False
        cmd = list(*cmd)
        while cmd:
            param = cmd.pop(0)
            if param == "--json":
                json = True
            elif param == "--name-only":
                nameOnly = True
            elif param == "-n":
                pass  # Don't use the name here...
            else:
                pass
        if self._fail_space_list:
            raise processutils.ProcessExecutionError(exit_code=1)
        elif nameOnly:
            return "space1\nspace2\nvolume1\n", ''
        elif json:
            return HGST_SPACE_JSON, ''
        else:
            return '', ''

    def _parse_network_list(self, *cmd):
        """Eat a network-list command and return error or results."""
        if self._fail_network_list:
            raise processutils.ProcessExecutionError(exit_code=1)
        else:
            return NETWORK_LIST, ''

    def _parse_domain_list(self, *cmd):
        """Eat a domain-list command and return error, empty, or results."""
        if self._fail_domain_list:
            raise processutils.ProcessExecutionError(exit_code=1)
        elif self._empty_domain_list:
            return '', ''
        else:
            return "thisserver\nthatserver\nanotherserver\n", ''

    def _fake_execute(self, *cmd, **kwargs):
        """Sudo hook to catch commands to allow running on all hosts."""
        cmdlist = list(cmd)
        exe = cmdlist.pop(0)
        if exe == 'vgc-cluster':
            exe = cmdlist.pop(0)
            if exe == "request-cancel":
                self._request_cancel = True
                if self._return_blocked > 0:
                    return 'Request cancelled', ''
                else:
                    raise processutils.ProcessExecutionError(exit_code=1)
            elif self._fail_vgc_cluster:
                raise processutils.ProcessExecutionError(exit_code=1)
            elif exe == "--version":
                return "HGST Solutions V2.5.0.0.x.x.x.x.x", ''
            elif exe == "space-list":
                return self._parse_space_list(cmdlist)
            elif exe == "space-create":
                self._parse_space_create(cmdlist)
                if self._return_blocked > 0:
                    self._return_blocked = self._return_blocked - 1
                    out = "VGC_CREATE_000002\nBLOCKED\n"
                    raise processutils.ProcessExecutionError(stdout=out,
                                                             exit_code=1)
                return '', ''
            elif exe == "space-delete":
                return self._parse_space_delete(cmdlist)
            elif exe == "space-extend":
                return self._parse_space_extend(cmdlist)
            elif exe == "host-storage":
                if self._fail_host_storage:
                    raise processutils.ProcessExecutionError(exit_code=1)
                return HGST_HOST_STORAGE, ''
            elif exe == "domain-list":
                return self._parse_domain_list()
            elif exe == "network-list":
                return self._parse_network_list()
            elif exe == "space-set-apphosts":
                if self._fail_set_apphosts:
                    raise processutils.ProcessExecutionError(exit_code=1)
                return '', ''
            else:
                raise NotImplementedError
        elif exe == 'ip':
            if self._fail_ip:
                raise processutils.ProcessExecutionError(exit_code=1)
            else:
                return IP_OUTPUT, ''
        elif exe == 'dd':
            self.dd_count = -1
            for p in cmdlist:
                if 'count=' in p:
                    self.dd_count = int(p[6:])
            return DD_OUTPUT, ''
        else:
            return '', ''

    @mock.patch('pwd.getpwnam', return_value=1)
    @mock.patch('grp.getgrnam', return_value=1)
    @mock.patch('socket.gethostbyname', return_value='123.123.123.123')
    def test_vgc_cluster_not_present(self, mock_ghn, mock_grnam, mock_pwnam):
        """Test exception when vgc-cluster returns an error."""
        # Should pass
        self._fail_vgc_cluster = False
        self.driver.check_for_setup_error()
        # Should throw exception
        self._fail_vgc_cluster = True
        self.assertRaises(exception.VolumeDriverException,
                          self.driver.check_for_setup_error)

    @mock.patch('pwd.getpwnam', return_value=1)
    @mock.patch('grp.getgrnam', return_value=1)
    @mock.patch('socket.gethostbyname', return_value='123.123.123.123')
    def test_parameter_redundancy_invalid(self, mock_ghn, mock_grnam,
                                          mock_pwnam):
        """Test when hgst_redundancy config parameter not 0 or 1."""
        # Should pass
        self.driver.check_for_setup_error()
        # Should throw exceptions
        self.configuration.hgst_redundancy = ''
        self.assertRaises(exception.VolumeDriverException,
                          self.driver.check_for_setup_error)
        self.configuration.hgst_redundancy = 'Fred'
        self.assertRaises(exception.VolumeDriverException,
                          self.driver.check_for_setup_error)

    @mock.patch('pwd.getpwnam', return_value=1)
    @mock.patch('grp.getgrnam', return_value=1)
    @mock.patch('socket.gethostbyname', return_value='123.123.123.123')
    def test_parameter_user_invalid(self, mock_ghn, mock_grnam, mock_pwnam):
        """Test exception when hgst_space_user doesn't map to UNIX user."""
        # Should pass
        self.driver.check_for_setup_error()
        # Should throw exceptions
        mock_pwnam.side_effect = KeyError()
        self.configuration.hgst_space_user = ''
        self.assertRaises(exception.VolumeDriverException,
                          self.driver.check_for_setup_error)
        self.configuration.hgst_space_user = '******'
        self.assertRaises(exception.VolumeDriverException,
                          self.driver.check_for_setup_error)

    @mock.patch('pwd.getpwnam', return_value=1)
    @mock.patch('grp.getgrnam', return_value=1)
    @mock.patch('socket.gethostbyname', return_value='123.123.123.123')
    def test_parameter_group_invalid(self, mock_ghn, mock_grnam, mock_pwnam):
        """Test exception when hgst_space_group doesn't map to UNIX group."""
        # Should pass
        self.driver.check_for_setup_error()
        # Should throw exceptions
        mock_grnam.side_effect = KeyError()
        self.configuration.hgst_space_group = ''
        self.assertRaises(exception.VolumeDriverException,
                          self.driver.check_for_setup_error)
        self.configuration.hgst_space_group = 'Fred!`'
        self.assertRaises(exception.VolumeDriverException,
                          self.driver.check_for_setup_error)

    @mock.patch('pwd.getpwnam', return_value=1)
    @mock.patch('grp.getgrnam', return_value=1)
    @mock.patch('socket.gethostbyname', return_value='123.123.123.123')
    def test_parameter_mode_invalid(self, mock_ghn, mock_grnam, mock_pwnam):
        """Test exception when mode for created spaces isn't proper format."""
        # Should pass
        self.driver.check_for_setup_error()
        # Should throw exceptions
        self.configuration.hgst_space_mode = ''
        self.assertRaises(exception.VolumeDriverException,
                          self.driver.check_for_setup_error)
        self.configuration.hgst_space_mode = 'Fred'
        self.assertRaises(exception.VolumeDriverException,
                          self.driver.check_for_setup_error)

    @mock.patch('pwd.getpwnam', return_value=1)
    @mock.patch('grp.getgrnam', return_value=1)
    @mock.patch('socket.gethostbyname', return_value='123.123.123.123')
    def test_parameter_net_invalid(self, mock_ghn, mock_grnam, mock_pwnam):
        """Test exception when hgst_net not in the domain."""
        # Should pass
        self.driver.check_for_setup_error()
        # Should throw exceptions
        self._fail_network_list = True
        self.configuration.hgst_net = 'Fred'
        self.assertRaises(exception.VolumeDriverException,
                          self.driver.check_for_setup_error)
        self._fail_network_list = False

    @mock.patch('pwd.getpwnam', return_value=1)
    @mock.patch('grp.getgrnam', return_value=1)
    @mock.patch('socket.gethostbyname', return_value='123.123.123.123')
    def test_ip_addr_fails(self, mock_ghn, mock_grnam, mock_pwnam):
        """Test exception when IP ADDR command fails."""
        # Should pass
        self.driver.check_for_setup_error()
        # Throw exception, need to clear internal cached host in driver
        self._fail_ip = True
        self.driver._vgc_host = None
        self.assertRaises(exception.VolumeDriverException,
                          self.driver.check_for_setup_error)

    @mock.patch('pwd.getpwnam', return_value=1)
    @mock.patch('grp.getgrnam', return_value=1)
    @mock.patch('socket.gethostbyname', return_value='123.123.123.123')
    def test_domain_list_fails(self, mock_ghn, mock_grnam, mock_pwnam):
        """Test exception when domain-list fails for the domain."""
        # Should pass
        self.driver.check_for_setup_error()
        # Throw exception, need to clear internal cached host in driver
        self._fail_domain_list = True
        self.driver._vgc_host = None
        self.assertRaises(exception.VolumeDriverException,
                          self.driver.check_for_setup_error)

    @mock.patch('pwd.getpwnam', return_value=1)
    @mock.patch('grp.getgrnam', return_value=1)
    @mock.patch('socket.gethostbyname', return_value='123.123.123.123')
    def test_not_in_domain(self, mock_ghn, mock_grnam, mock_pwnam):
        """Test exception when Cinder host not domain member."""
        # Should pass
        self.driver.check_for_setup_error()
        # Throw exception, need to clear internal cached host in driver
        self._empty_domain_list = True
        self.driver._vgc_host = None
        self.assertRaises(exception.VolumeDriverException,
                          self.driver.check_for_setup_error)

    @mock.patch('pwd.getpwnam', return_value=1)
    @mock.patch('grp.getgrnam', return_value=1)
    @mock.patch('socket.gethostbyname', return_value='123.123.123.123')
    def test_parameter_storageservers_invalid(self, mock_ghn, mock_grnam,
                                              mock_pwnam):
        """Test exception when the storage servers are invalid/missing."""
        # Should pass
        self.driver.check_for_setup_error()
        # Storage_hosts missing
        self.configuration.hgst_storage_servers = ''
        self.assertRaises(exception.VolumeDriverException,
                          self.driver.check_for_setup_error)
        # missing a : between host and devnode
        self.configuration.hgst_storage_servers = 'stor1,stor2'
        self.assertRaises(exception.VolumeDriverException,
                          self.driver.check_for_setup_error)
        # missing a : between host and devnode
        self.configuration.hgst_storage_servers = 'stor1:gbd0,stor2'
        self.assertRaises(exception.VolumeDriverException,
                          self.driver.check_for_setup_error)
        # Host not in cluster
        self.configuration.hgst_storage_servers = 'stor1:gbd0'
        self._fail_host_storage = True
        self.assertRaises(exception.VolumeDriverException,
                          self.driver.check_for_setup_error)

    def test_update_volume_stats(self):
        """Get cluster space available, should pass."""
        actual = self.driver.get_volume_stats(True)
        self.assertEqual('HGST', actual['vendor_name'])
        self.assertEqual('hgst', actual['storage_protocol'])
        self.assertEqual(90, actual['total_capacity_gb'])
        self.assertEqual(87, actual['free_capacity_gb'])
        self.assertEqual(0, actual['reserved_percentage'])

    def test_update_volume_stats_redundancy(self):
        """Get cluster space available, half-sized - 1 for mirrors."""
        self.configuration.hgst_redundancy = '1'
        actual = self.driver.get_volume_stats(True)
        self.assertEqual('HGST', actual['vendor_name'])
        self.assertEqual('hgst', actual['storage_protocol'])
        self.assertEqual(44, actual['total_capacity_gb'])
        self.assertEqual(43, actual['free_capacity_gb'])
        self.assertEqual(0, actual['reserved_percentage'])

    def test_update_volume_stats_cached(self):
        """Get cached cluster space, should not call executable."""
        self._fail_host_storage = True
        actual = self.driver.get_volume_stats(False)
        self.assertEqual('HGST', actual['vendor_name'])
        self.assertEqual('hgst', actual['storage_protocol'])
        self.assertEqual(90, actual['total_capacity_gb'])
        self.assertEqual(87, actual['free_capacity_gb'])
        self.assertEqual(0, actual['reserved_percentage'])

    def test_update_volume_stats_error(self):
        """Test that when host-storage gives an error, return unknown."""
        self._fail_host_storage = True
        actual = self.driver.get_volume_stats(True)
        self.assertEqual('HGST', actual['vendor_name'])
        self.assertEqual('hgst', actual['storage_protocol'])
        self.assertEqual('unknown', actual['total_capacity_gb'])
        self.assertEqual('unknown', actual['free_capacity_gb'])
        self.assertEqual(0, actual['reserved_percentage'])

    @mock.patch('socket.gethostbyname', return_value='123.123.123.123')
    def test_create_volume(self, mock_ghn):
        """Test volume creation, ensure appropriate size expansion/name."""
        ctxt = context.get_admin_context()
        extra_specs = {}
        type_ref = volume_types.create(ctxt, 'hgst-1', extra_specs)
        volume = {
            'id': '1',
            'name': 'volume1',
            'display_name': '',
            'volume_type_id': type_ref['id'],
            'size': 10
        }
        ret = self.driver.create_volume(volume)
        expected = {
            'redundancy': '0',
            'group': 'xanadu',
            'name': 'volume10',
            'mode': '0777',
            'user': '******',
            'net': 'net1',
            'storageserver': 'stor1:gbd0,stor2:gbd0,',
            'size': '12'
        }
        self.assertDictMatch(expected, self.created)
        # Check the returned provider, note the the provider_id is hashed
        expected_pid = {'provider_id': 'volume10'}
        self.assertDictMatch(expected_pid, ret)

    @mock.patch('socket.gethostbyname', return_value='123.123.123.123')
    def test_create_volume_name_creation_fail(self, mock_ghn):
        """Test volume creation exception when can't make a hashed name."""
        ctxt = context.get_admin_context()
        extra_specs = {}
        type_ref = volume_types.create(ctxt, 'hgst-1', extra_specs)
        volume = {
            'id': '1',
            'name': 'volume1',
            'display_name': '',
            'volume_type_id': type_ref['id'],
            'size': 10
        }
        self._fail_space_list = True
        self.assertRaises(exception.VolumeDriverException,
                          self.driver.create_volume, volume)

    @mock.patch('socket.gethostbyname', return_value='123.123.123.123')
    def test_create_snapshot(self, mock_ghn):
        """Test creating a snapshot, ensure full data of original copied."""
        # Now snapshot the volume and check commands
        snapshot = {
            'volume_name': 'volume10',
            'volume_size': 10,
            'volume_id': 'xxx',
            'display_name': 'snap10',
            'name': '123abc',
            'volume_size': 10,
            'id': '123abc',
            'volume': {
                'provider_id': 'space10'
            }
        }
        ret = self.driver.create_snapshot(snapshot)
        # We must copy entier underlying storage, ~12GB, not just 10GB
        self.assertEqual(11444, self.dd_count)
        # Check space-create command
        expected = {
            'redundancy': '0',
            'group': 'xanadu',
            'name': snapshot['display_name'],
            'mode': '0777',
            'user': '******',
            'net': 'net1',
            'storageserver': 'stor1:gbd0,stor2:gbd0,',
            'size': '12'
        }
        self.assertDictMatch(expected, self.created)
        # Check the returned provider
        expected_pid = {'provider_id': 'snap10'}
        self.assertDictMatch(expected_pid, ret)

    @mock.patch('socket.gethostbyname', return_value='123.123.123.123')
    def test_create_cloned_volume(self, mock_ghn):
        """Test creating a clone, ensure full size is copied from original."""
        ctxt = context.get_admin_context()
        extra_specs = {}
        type_ref = volume_types.create(ctxt, 'hgst-1', extra_specs)
        orig = {
            'id': '1',
            'name': 'volume1',
            'display_name': '',
            'volume_type_id': type_ref['id'],
            'size': 10,
            'provider_id': 'space_orig'
        }
        clone = {
            'id': '2',
            'name': 'clone1',
            'display_name': '',
            'volume_type_id': type_ref['id'],
            'size': 10
        }
        pid = self.driver.create_cloned_volume(clone, orig)
        # We must copy entier underlying storage, ~12GB, not just 10GB
        self.assertEqual(11444, self.dd_count)
        # Check space-create command
        expected = {
            'redundancy': '0',
            'group': 'xanadu',
            'name': 'clone1',
            'mode': '0777',
            'user': '******',
            'net': 'net1',
            'storageserver': 'stor1:gbd0,stor2:gbd0,',
            'size': '12'
        }
        self.assertDictMatch(expected, self.created)
        # Check the returned provider
        expected_pid = {'provider_id': 'clone1'}
        self.assertDictMatch(expected_pid, pid)

    @mock.patch('socket.gethostbyname', return_value='123.123.123.123')
    def test_add_cinder_apphosts_fails(self, mock_ghn):
        """Test exception when set-apphost can't connect volume to host."""
        ctxt = context.get_admin_context()
        extra_specs = {}
        type_ref = volume_types.create(ctxt, 'hgst-1', extra_specs)
        orig = {
            'id': '1',
            'name': 'volume1',
            'display_name': '',
            'volume_type_id': type_ref['id'],
            'size': 10,
            'provider_id': 'space_orig'
        }
        clone = {
            'id': '2',
            'name': 'clone1',
            'display_name': '',
            'volume_type_id': type_ref['id'],
            'size': 10
        }
        self._fail_set_apphosts = True
        self.assertRaises(exception.VolumeDriverException,
                          self.driver.create_cloned_volume, clone, orig)

    @mock.patch('socket.gethostbyname', return_value='123.123.123.123')
    def test_create_volume_from_snapshot(self, mock_ghn):
        """Test creating volume from snapshot, ensure full space copy."""
        ctxt = context.get_admin_context()
        extra_specs = {}
        type_ref = volume_types.create(ctxt, 'hgst-1', extra_specs)
        snap = {
            'id': '1',
            'name': 'volume1',
            'display_name': '',
            'volume_type_id': type_ref['id'],
            'size': 10,
            'provider_id': 'space_orig'
        }
        volume = {
            'id': '2',
            'name': 'volume2',
            'display_name': '',
            'volume_type_id': type_ref['id'],
            'size': 10
        }
        pid = self.driver.create_volume_from_snapshot(volume, snap)
        # We must copy entier underlying storage, ~12GB, not just 10GB
        self.assertEqual(11444, self.dd_count)
        # Check space-create command
        expected = {
            'redundancy': '0',
            'group': 'xanadu',
            'name': 'volume2',
            'mode': '0777',
            'user': '******',
            'net': 'net1',
            'storageserver': 'stor1:gbd0,stor2:gbd0,',
            'size': '12'
        }
        self.assertDictMatch(expected, self.created)
        # Check the returned provider
        expected_pid = {'provider_id': 'volume2'}
        self.assertDictMatch(expected_pid, pid)

    @mock.patch('socket.gethostbyname', return_value='123.123.123.123')
    def test_create_volume_blocked(self, mock_ghn):
        """Test volume creation where only initial space-create is blocked.

        This should actually pass because we are blocked byt return an error
        in request-cancel, meaning that it got unblocked before we could kill
        the space request.
        """
        ctxt = context.get_admin_context()
        extra_specs = {}
        type_ref = volume_types.create(ctxt, 'hgst-1', extra_specs)
        volume = {
            'id': '1',
            'name': 'volume1',
            'display_name': '',
            'volume_type_id': type_ref['id'],
            'size': 10
        }
        self._return_blocked = 1  # Block & fail cancel => create succeeded
        ret = self.driver.create_volume(volume)
        expected = {
            'redundancy': '0',
            'group': 'xanadu',
            'name': 'volume10',
            'mode': '0777',
            'user': '******',
            'net': 'net1',
            'storageserver': 'stor1:gbd0,stor2:gbd0,',
            'size': '12'
        }
        self.assertDictMatch(expected, self.created)
        # Check the returned provider
        expected_pid = {'provider_id': 'volume10'}
        self.assertDictMatch(expected_pid, ret)
        self.assertEqual(True, self._request_cancel)

    @mock.patch('socket.gethostbyname', return_value='123.123.123.123')
    def test_create_volume_blocked_and_fail(self, mock_ghn):
        """Test volume creation where space-create blocked permanently.

        This should fail because the initial create was blocked and the
        request-cancel succeeded, meaning the create operation never
        completed.
        """
        ctxt = context.get_admin_context()
        extra_specs = {}
        type_ref = volume_types.create(ctxt, 'hgst-1', extra_specs)
        volume = {
            'id': '1',
            'name': 'volume1',
            'display_name': '',
            'volume_type_id': type_ref['id'],
            'size': 10
        }
        self._return_blocked = 2  # Block & pass cancel => create failed. :(
        self.assertRaises(exception.VolumeDriverException,
                          self.driver.create_volume, volume)
        self.assertEqual(True, self._request_cancel)

    def test_delete_volume(self):
        """Test deleting existing volume, ensure proper name used."""
        ctxt = context.get_admin_context()
        extra_specs = {}
        type_ref = volume_types.create(ctxt, 'hgst-1', extra_specs)
        volume = {
            'id': '1',
            'name': 'volume1',
            'display_name': '',
            'volume_type_id': type_ref['id'],
            'size': 10,
            'provider_id': 'volume10'
        }
        self.driver.delete_volume(volume)
        expected = {'name': 'volume10'}
        self.assertDictMatch(expected, self.deleted)

    def test_delete_volume_failure_modes(self):
        """Test cases where space-delete fails, but OS delete is still OK."""
        ctxt = context.get_admin_context()
        extra_specs = {}
        type_ref = volume_types.create(ctxt, 'hgst-1', extra_specs)
        volume = {
            'id': '1',
            'name': 'volume1',
            'display_name': '',
            'volume_type_id': type_ref['id'],
            'size': 10,
            'provider_id': 'volume10'
        }
        self._fail_space_delete = True
        # This should not throw an exception, space-delete failure not problem
        self.driver.delete_volume(volume)
        self._fail_space_delete = False
        volume['provider_id'] = None
        # This should also not throw an exception
        self.driver.delete_volume(volume)

    def test_delete_snapshot(self):
        """Test deleting a snapshot, ensure proper name is removed."""
        ctxt = context.get_admin_context()
        extra_specs = {}
        type_ref = volume_types.create(ctxt, 'hgst-1', extra_specs)
        snapshot = {
            'id': '1',
            'name': 'volume1',
            'display_name': '',
            'volume_type_id': type_ref['id'],
            'size': 10,
            'provider_id': 'snap10'
        }
        self.driver.delete_snapshot(snapshot)
        expected = {'name': 'snap10'}
        self.assertDictMatch(expected, self.deleted)

    def test_extend_volume(self):
        """Test extending a volume, check the size in GB vs. GiB."""
        ctxt = context.get_admin_context()
        extra_specs = {}
        type_ref = volume_types.create(ctxt, 'hgst-1', extra_specs)
        volume = {
            'id': '1',
            'name': 'volume1',
            'display_name': '',
            'volume_type_id': type_ref['id'],
            'size': 10,
            'provider_id': 'volume10'
        }
        self.extended = {'name': '', 'size': '0', 'storageserver': ''}
        self.driver.extend_volume(volume, 12)
        expected = {
            'name': 'volume10',
            'size': '2',
            'storageserver': 'stor1:gbd0,stor2:gbd0,'
        }
        self.assertDictMatch(expected, self.extended)

    def test_extend_volume_noextend(self):
        """Test extending a volume where Space does not need to be enlarged.

        Because Spaces are generated somewhat larger than the requested size
        from OpenStack due to the base10(HGST)/base2(OS) mismatch, they can
        sometimes be larger than requested from OS.  In that case a
        volume_extend may actually be a noop since the volume is already large
        enough to satisfy OS's request.
        """
        ctxt = context.get_admin_context()
        extra_specs = {}
        type_ref = volume_types.create(ctxt, 'hgst-1', extra_specs)
        volume = {
            'id': '1',
            'name': 'volume1',
            'display_name': '',
            'volume_type_id': type_ref['id'],
            'size': 10,
            'provider_id': 'volume10'
        }
        self.extended = {'name': '', 'size': '0', 'storageserver': ''}
        self.driver.extend_volume(volume, 10)
        expected = {'name': '', 'size': '0', 'storageserver': ''}
        self.assertDictMatch(expected, self.extended)

    def test_space_list_fails(self):
        """Test exception is thrown when we can't call space-list."""
        ctxt = context.get_admin_context()
        extra_specs = {}
        type_ref = volume_types.create(ctxt, 'hgst-1', extra_specs)
        volume = {
            'id': '1',
            'name': 'volume1',
            'display_name': '',
            'volume_type_id': type_ref['id'],
            'size': 10,
            'provider_id': 'volume10'
        }
        self.extended = {'name': '', 'size': '0', 'storageserver': ''}
        self._fail_space_list = True
        self.assertRaises(exception.VolumeDriverException,
                          self.driver.extend_volume, volume, 12)

    def test_cli_error_not_blocked(self):
        """Test the _blocked handler's handlinf of a non-blocked error.

        The _handle_blocked handler is called on any process errors in the
        code.  If the error was not caused by a blocked command condition
        (syntax error, out of space, etc.) then it should just throw the
        exception and not try and retry the command.
        """
        ctxt = context.get_admin_context()
        extra_specs = {}
        type_ref = volume_types.create(ctxt, 'hgst-1', extra_specs)
        volume = {
            'id': '1',
            'name': 'volume1',
            'display_name': '',
            'volume_type_id': type_ref['id'],
            'size': 10,
            'provider_id': 'volume10'
        }
        self.extended = {'name': '', 'size': '0', 'storageserver': ''}
        self._fail_extend = True
        self.assertRaises(exception.VolumeDriverException,
                          self.driver.extend_volume, volume, 12)
        self.assertEqual(False, self._request_cancel)

    @mock.patch('socket.gethostbyname', return_value='123.123.123.123')
    def test_initialize_connection(self, moch_ghn):
        """Test that the connection_info for Nova makes sense."""
        volume = {'name': '123', 'provider_id': 'spacey'}
        conn = self.driver.initialize_connection(volume, None)
        expected = {'name': 'spacey', 'noremovehost': 'thisserver'}
        self.assertDictMatch(expected, conn['data'])
예제 #4
0
class HGSTTestCase(test.TestCase):

    # Need to mock these since we use them on driver creation
    @mock.patch("pwd.getpwnam", return_value=1)
    @mock.patch("grp.getgrnam", return_value=1)
    @mock.patch("socket.gethostbyname", return_value="123.123.123.123")
    def setUp(self, mock_ghn, mock_grnam, mock_pwnam):
        """Set up UUT and all the flags required for later fake_executes."""
        super(HGSTTestCase, self).setUp()
        self.stubs.Set(processutils, "execute", self._fake_execute)
        self._fail_vgc_cluster = False
        self._fail_ip = False
        self._fail_network_list = False
        self._fail_domain_list = False
        self._empty_domain_list = False
        self._fail_host_storage = False
        self._fail_space_list = False
        self._fail_space_delete = False
        self._fail_set_apphosts = False
        self._fail_extend = False
        self._request_cancel = False
        self._return_blocked = 0
        self.configuration = mock.Mock(spec=conf.Configuration)
        self.configuration.safe_get = self._fake_safe_get
        self._reset_configuration()
        self.driver = HGSTDriver(configuration=self.configuration, execute=self._fake_execute)

    def _fake_safe_get(self, value):
        """Don't throw exception on missing parameters, return None."""
        try:
            val = getattr(self.configuration, value)
        except AttributeError:
            val = None
        return val

    def _reset_configuration(self):
        """Set safe and sane values for config params."""
        self.configuration.num_volume_device_scan_tries = 1
        self.configuration.volume_dd_blocksize = "1M"
        self.configuration.volume_backend_name = "hgst-1"
        self.configuration.hgst_storage_servers = "stor1:gbd0,stor2:gbd0"
        self.configuration.hgst_net = "net1"
        self.configuration.hgst_redundancy = "0"
        self.configuration.hgst_space_user = "******"
        self.configuration.hgst_space_group = "xanadu"
        self.configuration.hgst_space_mode = "0777"

    def _parse_space_create(self, *cmd):
        """Eats a vgc-cluster space-create command line to a dict."""
        self.created = {"storageserver": ""}
        cmd = list(*cmd)
        while cmd:
            param = cmd.pop(0)
            if param == "-n":
                self.created["name"] = cmd.pop(0)
            elif param == "-N":
                self.created["net"] = cmd.pop(0)
            elif param == "-s":
                self.created["size"] = cmd.pop(0)
            elif param == "--redundancy":
                self.created["redundancy"] = cmd.pop(0)
            elif param == "--user":
                self.created["user"] = cmd.pop(0)
            elif param == "--user":
                self.created["user"] = cmd.pop(0)
            elif param == "--group":
                self.created["group"] = cmd.pop(0)
            elif param == "--mode":
                self.created["mode"] = cmd.pop(0)
            elif param == "-S":
                self.created["storageserver"] += cmd.pop(0) + ","
            else:
                pass

    def _parse_space_extend(self, *cmd):
        """Eats a vgc-cluster space-extend commandline to a dict."""
        self.extended = {"storageserver": ""}
        cmd = list(*cmd)
        while cmd:
            param = cmd.pop(0)
            if param == "-n":
                self.extended["name"] = cmd.pop(0)
            elif param == "-s":
                self.extended["size"] = cmd.pop(0)
            elif param == "-S":
                self.extended["storageserver"] += cmd.pop(0) + ","
            else:
                pass
        if self._fail_extend:
            raise processutils.ProcessExecutionError(exit_code=1)
        else:
            return "", ""

    def _parse_space_delete(self, *cmd):
        """Eats a vgc-cluster space-delete commandline to a dict."""
        self.deleted = {}
        cmd = list(*cmd)
        while cmd:
            param = cmd.pop(0)
            if param == "-n":
                self.deleted["name"] = cmd.pop(0)
            else:
                pass
        if self._fail_space_delete:
            raise processutils.ProcessExecutionError(exit_code=1)
        else:
            return "", ""

    def _parse_space_list(self, *cmd):
        """Eats a vgc-cluster space-list commandline to a dict."""
        json = False
        nameOnly = False
        cmd = list(*cmd)
        while cmd:
            param = cmd.pop(0)
            if param == "--json":
                json = True
            elif param == "--name-only":
                nameOnly = True
            elif param == "-n":
                pass  # Don't use the name here...
            else:
                pass
        if self._fail_space_list:
            raise processutils.ProcessExecutionError(exit_code=1)
        elif nameOnly:
            return "space1\nspace2\nvolume1\n", ""
        elif json:
            return HGST_SPACE_JSON, ""
        else:
            return "", ""

    def _parse_network_list(self, *cmd):
        """Eat a network-list command and return error or results."""
        if self._fail_network_list:
            raise processutils.ProcessExecutionError(exit_code=1)
        else:
            return NETWORK_LIST, ""

    def _parse_domain_list(self, *cmd):
        """Eat a domain-list command and return error, empty, or results."""
        if self._fail_domain_list:
            raise processutils.ProcessExecutionError(exit_code=1)
        elif self._empty_domain_list:
            return "", ""
        else:
            return "thisserver\nthatserver\nanotherserver\n", ""

    def _fake_execute(self, *cmd, **kwargs):
        """Sudo hook to catch commands to allow running on all hosts."""
        cmdlist = list(cmd)
        exe = cmdlist.pop(0)
        if exe == "vgc-cluster":
            exe = cmdlist.pop(0)
            if exe == "request-cancel":
                self._request_cancel = True
                if self._return_blocked > 0:
                    return "Request cancelled", ""
                else:
                    raise processutils.ProcessExecutionError(exit_code=1)
            elif self._fail_vgc_cluster:
                raise processutils.ProcessExecutionError(exit_code=1)
            elif exe == "--version":
                return "HGST Solutions V2.5.0.0.x.x.x.x.x", ""
            elif exe == "space-list":
                return self._parse_space_list(cmdlist)
            elif exe == "space-create":
                self._parse_space_create(cmdlist)
                if self._return_blocked > 0:
                    self._return_blocked = self._return_blocked - 1
                    out = "VGC_CREATE_000002\nBLOCKED\n"
                    raise processutils.ProcessExecutionError(stdout=out, exit_code=1)
                return "", ""
            elif exe == "space-delete":
                return self._parse_space_delete(cmdlist)
            elif exe == "space-extend":
                return self._parse_space_extend(cmdlist)
            elif exe == "host-storage":
                if self._fail_host_storage:
                    raise processutils.ProcessExecutionError(exit_code=1)
                return HGST_HOST_STORAGE, ""
            elif exe == "domain-list":
                return self._parse_domain_list()
            elif exe == "network-list":
                return self._parse_network_list()
            elif exe == "space-set-apphosts":
                if self._fail_set_apphosts:
                    raise processutils.ProcessExecutionError(exit_code=1)
                return "", ""
            else:
                raise NotImplementedError
        elif exe == "ip":
            if self._fail_ip:
                raise processutils.ProcessExecutionError(exit_code=1)
            else:
                return IP_OUTPUT, ""
        elif exe == "dd":
            self.dd_count = -1
            for p in cmdlist:
                if "count=" in p:
                    self.dd_count = int(p[6:])
            return DD_OUTPUT, ""
        else:
            return "", ""

    @mock.patch("pwd.getpwnam", return_value=1)
    @mock.patch("grp.getgrnam", return_value=1)
    @mock.patch("socket.gethostbyname", return_value="123.123.123.123")
    def test_vgc_cluster_not_present(self, mock_ghn, mock_grnam, mock_pwnam):
        """Test exception when vgc-cluster returns an error."""
        # Should pass
        self._fail_vgc_cluster = False
        self.driver.check_for_setup_error()
        # Should throw exception
        self._fail_vgc_cluster = True
        self.assertRaises(exception.VolumeDriverException, self.driver.check_for_setup_error)

    @mock.patch("pwd.getpwnam", return_value=1)
    @mock.patch("grp.getgrnam", return_value=1)
    @mock.patch("socket.gethostbyname", return_value="123.123.123.123")
    def test_parameter_redundancy_invalid(self, mock_ghn, mock_grnam, mock_pwnam):
        """Test when hgst_redundancy config parameter not 0 or 1."""
        # Should pass
        self.driver.check_for_setup_error()
        # Should throw exceptions
        self.configuration.hgst_redundancy = ""
        self.assertRaises(exception.VolumeDriverException, self.driver.check_for_setup_error)
        self.configuration.hgst_redundancy = "Fred"
        self.assertRaises(exception.VolumeDriverException, self.driver.check_for_setup_error)

    @mock.patch("pwd.getpwnam", return_value=1)
    @mock.patch("grp.getgrnam", return_value=1)
    @mock.patch("socket.gethostbyname", return_value="123.123.123.123")
    def test_parameter_user_invalid(self, mock_ghn, mock_grnam, mock_pwnam):
        """Test exception when hgst_space_user doesn't map to UNIX user."""
        # Should pass
        self.driver.check_for_setup_error()
        # Should throw exceptions
        mock_pwnam.side_effect = KeyError()
        self.configuration.hgst_space_user = ""
        self.assertRaises(exception.VolumeDriverException, self.driver.check_for_setup_error)
        self.configuration.hgst_space_user = "******"
        self.assertRaises(exception.VolumeDriverException, self.driver.check_for_setup_error)

    @mock.patch("pwd.getpwnam", return_value=1)
    @mock.patch("grp.getgrnam", return_value=1)
    @mock.patch("socket.gethostbyname", return_value="123.123.123.123")
    def test_parameter_group_invalid(self, mock_ghn, mock_grnam, mock_pwnam):
        """Test exception when hgst_space_group doesn't map to UNIX group."""
        # Should pass
        self.driver.check_for_setup_error()
        # Should throw exceptions
        mock_grnam.side_effect = KeyError()
        self.configuration.hgst_space_group = ""
        self.assertRaises(exception.VolumeDriverException, self.driver.check_for_setup_error)
        self.configuration.hgst_space_group = "Fred!`"
        self.assertRaises(exception.VolumeDriverException, self.driver.check_for_setup_error)

    @mock.patch("pwd.getpwnam", return_value=1)
    @mock.patch("grp.getgrnam", return_value=1)
    @mock.patch("socket.gethostbyname", return_value="123.123.123.123")
    def test_parameter_mode_invalid(self, mock_ghn, mock_grnam, mock_pwnam):
        """Test exception when mode for created spaces isn't proper format."""
        # Should pass
        self.driver.check_for_setup_error()
        # Should throw exceptions
        self.configuration.hgst_space_mode = ""
        self.assertRaises(exception.VolumeDriverException, self.driver.check_for_setup_error)
        self.configuration.hgst_space_mode = "Fred"
        self.assertRaises(exception.VolumeDriverException, self.driver.check_for_setup_error)

    @mock.patch("pwd.getpwnam", return_value=1)
    @mock.patch("grp.getgrnam", return_value=1)
    @mock.patch("socket.gethostbyname", return_value="123.123.123.123")
    def test_parameter_net_invalid(self, mock_ghn, mock_grnam, mock_pwnam):
        """Test exception when hgst_net not in the domain."""
        # Should pass
        self.driver.check_for_setup_error()
        # Should throw exceptions
        self._fail_network_list = True
        self.configuration.hgst_net = "Fred"
        self.assertRaises(exception.VolumeDriverException, self.driver.check_for_setup_error)
        self._fail_network_list = False

    @mock.patch("pwd.getpwnam", return_value=1)
    @mock.patch("grp.getgrnam", return_value=1)
    @mock.patch("socket.gethostbyname", return_value="123.123.123.123")
    def test_ip_addr_fails(self, mock_ghn, mock_grnam, mock_pwnam):
        """Test exception when IP ADDR command fails."""
        # Should pass
        self.driver.check_for_setup_error()
        # Throw exception, need to clear internal cached host in driver
        self._fail_ip = True
        self.driver._vgc_host = None
        self.assertRaises(exception.VolumeDriverException, self.driver.check_for_setup_error)

    @mock.patch("pwd.getpwnam", return_value=1)
    @mock.patch("grp.getgrnam", return_value=1)
    @mock.patch("socket.gethostbyname", return_value="123.123.123.123")
    def test_domain_list_fails(self, mock_ghn, mock_grnam, mock_pwnam):
        """Test exception when domain-list fails for the domain."""
        # Should pass
        self.driver.check_for_setup_error()
        # Throw exception, need to clear internal cached host in driver
        self._fail_domain_list = True
        self.driver._vgc_host = None
        self.assertRaises(exception.VolumeDriverException, self.driver.check_for_setup_error)

    @mock.patch("pwd.getpwnam", return_value=1)
    @mock.patch("grp.getgrnam", return_value=1)
    @mock.patch("socket.gethostbyname", return_value="123.123.123.123")
    def test_not_in_domain(self, mock_ghn, mock_grnam, mock_pwnam):
        """Test exception when Cinder host not domain member."""
        # Should pass
        self.driver.check_for_setup_error()
        # Throw exception, need to clear internal cached host in driver
        self._empty_domain_list = True
        self.driver._vgc_host = None
        self.assertRaises(exception.VolumeDriverException, self.driver.check_for_setup_error)

    @mock.patch("pwd.getpwnam", return_value=1)
    @mock.patch("grp.getgrnam", return_value=1)
    @mock.patch("socket.gethostbyname", return_value="123.123.123.123")
    def test_parameter_storageservers_invalid(self, mock_ghn, mock_grnam, mock_pwnam):
        """Test exception when the storage servers are invalid/missing."""
        # Should pass
        self.driver.check_for_setup_error()
        # Storage_hosts missing
        self.configuration.hgst_storage_servers = ""
        self.assertRaises(exception.VolumeDriverException, self.driver.check_for_setup_error)
        # missing a : between host and devnode
        self.configuration.hgst_storage_servers = "stor1,stor2"
        self.assertRaises(exception.VolumeDriverException, self.driver.check_for_setup_error)
        # missing a : between host and devnode
        self.configuration.hgst_storage_servers = "stor1:gbd0,stor2"
        self.assertRaises(exception.VolumeDriverException, self.driver.check_for_setup_error)
        # Host not in cluster
        self.configuration.hgst_storage_servers = "stor1:gbd0"
        self._fail_host_storage = True
        self.assertRaises(exception.VolumeDriverException, self.driver.check_for_setup_error)

    def test_update_volume_stats(self):
        """Get cluster space available, should pass."""
        actual = self.driver.get_volume_stats(True)
        self.assertEqual("HGST", actual["vendor_name"])
        self.assertEqual("hgst", actual["storage_protocol"])
        self.assertEqual(90, actual["total_capacity_gb"])
        self.assertEqual(87, actual["free_capacity_gb"])
        self.assertEqual(0, actual["reserved_percentage"])

    def test_update_volume_stats_redundancy(self):
        """Get cluster space available, half-sized - 1 for mirrors."""
        self.configuration.hgst_redundancy = "1"
        actual = self.driver.get_volume_stats(True)
        self.assertEqual("HGST", actual["vendor_name"])
        self.assertEqual("hgst", actual["storage_protocol"])
        self.assertEqual(44, actual["total_capacity_gb"])
        self.assertEqual(43, actual["free_capacity_gb"])
        self.assertEqual(0, actual["reserved_percentage"])

    def test_update_volume_stats_cached(self):
        """Get cached cluster space, should not call executable."""
        self._fail_host_storage = True
        actual = self.driver.get_volume_stats(False)
        self.assertEqual("HGST", actual["vendor_name"])
        self.assertEqual("hgst", actual["storage_protocol"])
        self.assertEqual(90, actual["total_capacity_gb"])
        self.assertEqual(87, actual["free_capacity_gb"])
        self.assertEqual(0, actual["reserved_percentage"])

    def test_update_volume_stats_error(self):
        """Test that when host-storage gives an error, return unknown."""
        self._fail_host_storage = True
        actual = self.driver.get_volume_stats(True)
        self.assertEqual("HGST", actual["vendor_name"])
        self.assertEqual("hgst", actual["storage_protocol"])
        self.assertEqual("unknown", actual["total_capacity_gb"])
        self.assertEqual("unknown", actual["free_capacity_gb"])
        self.assertEqual(0, actual["reserved_percentage"])

    @mock.patch("socket.gethostbyname", return_value="123.123.123.123")
    def test_create_volume(self, mock_ghn):
        """Test volume creation, ensure appropriate size expansion/name."""
        ctxt = context.get_admin_context()
        extra_specs = {}
        type_ref = volume_types.create(ctxt, "hgst-1", extra_specs)
        volume = {"id": "1", "name": "volume1", "display_name": "", "volume_type_id": type_ref["id"], "size": 10}
        ret = self.driver.create_volume(volume)
        expected = {
            "redundancy": "0",
            "group": "xanadu",
            "name": "volume10",
            "mode": "0777",
            "user": "******",
            "net": "net1",
            "storageserver": "stor1:gbd0,stor2:gbd0,",
            "size": "12",
        }
        self.assertDictMatch(expected, self.created)
        # Check the returned provider, note the the provider_id is hashed
        expected_pid = {"provider_id": "volume10"}
        self.assertDictMatch(expected_pid, ret)

    @mock.patch("socket.gethostbyname", return_value="123.123.123.123")
    def test_create_volume_name_creation_fail(self, mock_ghn):
        """Test volume creation exception when can't make a hashed name."""
        ctxt = context.get_admin_context()
        extra_specs = {}
        type_ref = volume_types.create(ctxt, "hgst-1", extra_specs)
        volume = {"id": "1", "name": "volume1", "display_name": "", "volume_type_id": type_ref["id"], "size": 10}
        self._fail_space_list = True
        self.assertRaises(exception.VolumeDriverException, self.driver.create_volume, volume)

    @mock.patch("socket.gethostbyname", return_value="123.123.123.123")
    def test_create_snapshot(self, mock_ghn):
        """Test creating a snapshot, ensure full data of original copied."""
        # Now snapshot the volume and check commands
        snapshot = {
            "volume_name": "volume10",
            "volume_id": "xxx",
            "display_name": "snap10",
            "name": "123abc",
            "volume_size": 10,
            "id": "123abc",
            "volume": {"provider_id": "space10"},
        }
        ret = self.driver.create_snapshot(snapshot)
        # We must copy entier underlying storage, ~12GB, not just 10GB
        self.assertEqual(11444, self.dd_count)
        # Check space-create command
        expected = {
            "redundancy": "0",
            "group": "xanadu",
            "name": snapshot["display_name"],
            "mode": "0777",
            "user": "******",
            "net": "net1",
            "storageserver": "stor1:gbd0,stor2:gbd0,",
            "size": "12",
        }
        self.assertDictMatch(expected, self.created)
        # Check the returned provider
        expected_pid = {"provider_id": "snap10"}
        self.assertDictMatch(expected_pid, ret)

    @mock.patch("socket.gethostbyname", return_value="123.123.123.123")
    def test_create_cloned_volume(self, mock_ghn):
        """Test creating a clone, ensure full size is copied from original."""
        ctxt = context.get_admin_context()
        extra_specs = {}
        type_ref = volume_types.create(ctxt, "hgst-1", extra_specs)
        orig = {
            "id": "1",
            "name": "volume1",
            "display_name": "",
            "volume_type_id": type_ref["id"],
            "size": 10,
            "provider_id": "space_orig",
        }
        clone = {"id": "2", "name": "clone1", "display_name": "", "volume_type_id": type_ref["id"], "size": 10}
        pid = self.driver.create_cloned_volume(clone, orig)
        # We must copy entier underlying storage, ~12GB, not just 10GB
        self.assertEqual(11444, self.dd_count)
        # Check space-create command
        expected = {
            "redundancy": "0",
            "group": "xanadu",
            "name": "clone1",
            "mode": "0777",
            "user": "******",
            "net": "net1",
            "storageserver": "stor1:gbd0,stor2:gbd0,",
            "size": "12",
        }
        self.assertDictMatch(expected, self.created)
        # Check the returned provider
        expected_pid = {"provider_id": "clone1"}
        self.assertDictMatch(expected_pid, pid)

    @mock.patch("socket.gethostbyname", return_value="123.123.123.123")
    def test_add_cinder_apphosts_fails(self, mock_ghn):
        """Test exception when set-apphost can't connect volume to host."""
        ctxt = context.get_admin_context()
        extra_specs = {}
        type_ref = volume_types.create(ctxt, "hgst-1", extra_specs)
        orig = {
            "id": "1",
            "name": "volume1",
            "display_name": "",
            "volume_type_id": type_ref["id"],
            "size": 10,
            "provider_id": "space_orig",
        }
        clone = {"id": "2", "name": "clone1", "display_name": "", "volume_type_id": type_ref["id"], "size": 10}
        self._fail_set_apphosts = True
        self.assertRaises(exception.VolumeDriverException, self.driver.create_cloned_volume, clone, orig)

    @mock.patch("socket.gethostbyname", return_value="123.123.123.123")
    def test_create_volume_from_snapshot(self, mock_ghn):
        """Test creating volume from snapshot, ensure full space copy."""
        ctxt = context.get_admin_context()
        extra_specs = {}
        type_ref = volume_types.create(ctxt, "hgst-1", extra_specs)
        snap = {
            "id": "1",
            "name": "volume1",
            "display_name": "",
            "volume_type_id": type_ref["id"],
            "size": 10,
            "provider_id": "space_orig",
        }
        volume = {"id": "2", "name": "volume2", "display_name": "", "volume_type_id": type_ref["id"], "size": 10}
        pid = self.driver.create_volume_from_snapshot(volume, snap)
        # We must copy entier underlying storage, ~12GB, not just 10GB
        self.assertEqual(11444, self.dd_count)
        # Check space-create command
        expected = {
            "redundancy": "0",
            "group": "xanadu",
            "name": "volume2",
            "mode": "0777",
            "user": "******",
            "net": "net1",
            "storageserver": "stor1:gbd0,stor2:gbd0,",
            "size": "12",
        }
        self.assertDictMatch(expected, self.created)
        # Check the returned provider
        expected_pid = {"provider_id": "volume2"}
        self.assertDictMatch(expected_pid, pid)

    @mock.patch("socket.gethostbyname", return_value="123.123.123.123")
    def test_create_volume_blocked(self, mock_ghn):
        """Test volume creation where only initial space-create is blocked.

        This should actually pass because we are blocked byt return an error
        in request-cancel, meaning that it got unblocked before we could kill
        the space request.
        """
        ctxt = context.get_admin_context()
        extra_specs = {}
        type_ref = volume_types.create(ctxt, "hgst-1", extra_specs)
        volume = {"id": "1", "name": "volume1", "display_name": "", "volume_type_id": type_ref["id"], "size": 10}
        self._return_blocked = 1  # Block & fail cancel => create succeeded
        ret = self.driver.create_volume(volume)
        expected = {
            "redundancy": "0",
            "group": "xanadu",
            "name": "volume10",
            "mode": "0777",
            "user": "******",
            "net": "net1",
            "storageserver": "stor1:gbd0,stor2:gbd0,",
            "size": "12",
        }
        self.assertDictMatch(expected, self.created)
        # Check the returned provider
        expected_pid = {"provider_id": "volume10"}
        self.assertDictMatch(expected_pid, ret)
        self.assertEqual(True, self._request_cancel)

    @mock.patch("socket.gethostbyname", return_value="123.123.123.123")
    def test_create_volume_blocked_and_fail(self, mock_ghn):
        """Test volume creation where space-create blocked permanently.

        This should fail because the initial create was blocked and the
        request-cancel succeeded, meaning the create operation never
        completed.
        """
        ctxt = context.get_admin_context()
        extra_specs = {}
        type_ref = volume_types.create(ctxt, "hgst-1", extra_specs)
        volume = {"id": "1", "name": "volume1", "display_name": "", "volume_type_id": type_ref["id"], "size": 10}
        self._return_blocked = 2  # Block & pass cancel => create failed. :(
        self.assertRaises(exception.VolumeDriverException, self.driver.create_volume, volume)
        self.assertEqual(True, self._request_cancel)

    def test_delete_volume(self):
        """Test deleting existing volume, ensure proper name used."""
        ctxt = context.get_admin_context()
        extra_specs = {}
        type_ref = volume_types.create(ctxt, "hgst-1", extra_specs)
        volume = {
            "id": "1",
            "name": "volume1",
            "display_name": "",
            "volume_type_id": type_ref["id"],
            "size": 10,
            "provider_id": "volume10",
        }
        self.driver.delete_volume(volume)
        expected = {"name": "volume10"}
        self.assertDictMatch(expected, self.deleted)

    def test_delete_volume_failure_modes(self):
        """Test cases where space-delete fails, but OS delete is still OK."""
        ctxt = context.get_admin_context()
        extra_specs = {}
        type_ref = volume_types.create(ctxt, "hgst-1", extra_specs)
        volume = {
            "id": "1",
            "name": "volume1",
            "display_name": "",
            "volume_type_id": type_ref["id"],
            "size": 10,
            "provider_id": "volume10",
        }
        self._fail_space_delete = True
        # This should not throw an exception, space-delete failure not problem
        self.driver.delete_volume(volume)
        self._fail_space_delete = False
        volume["provider_id"] = None
        # This should also not throw an exception
        self.driver.delete_volume(volume)

    def test_delete_snapshot(self):
        """Test deleting a snapshot, ensure proper name is removed."""
        ctxt = context.get_admin_context()
        extra_specs = {}
        type_ref = volume_types.create(ctxt, "hgst-1", extra_specs)
        snapshot = {
            "id": "1",
            "name": "volume1",
            "display_name": "",
            "volume_type_id": type_ref["id"],
            "size": 10,
            "provider_id": "snap10",
        }
        self.driver.delete_snapshot(snapshot)
        expected = {"name": "snap10"}
        self.assertDictMatch(expected, self.deleted)

    def test_extend_volume(self):
        """Test extending a volume, check the size in GB vs. GiB."""
        ctxt = context.get_admin_context()
        extra_specs = {}
        type_ref = volume_types.create(ctxt, "hgst-1", extra_specs)
        volume = {
            "id": "1",
            "name": "volume1",
            "display_name": "",
            "volume_type_id": type_ref["id"],
            "size": 10,
            "provider_id": "volume10",
        }
        self.extended = {"name": "", "size": "0", "storageserver": ""}
        self.driver.extend_volume(volume, 12)
        expected = {"name": "volume10", "size": "2", "storageserver": "stor1:gbd0,stor2:gbd0,"}
        self.assertDictMatch(expected, self.extended)

    def test_extend_volume_noextend(self):
        """Test extending a volume where Space does not need to be enlarged.

        Because Spaces are generated somewhat larger than the requested size
        from OpenStack due to the base10(HGST)/base2(OS) mismatch, they can
        sometimes be larger than requested from OS.  In that case a
        volume_extend may actually be a noop since the volume is already large
        enough to satisfy OS's request.
        """
        ctxt = context.get_admin_context()
        extra_specs = {}
        type_ref = volume_types.create(ctxt, "hgst-1", extra_specs)
        volume = {
            "id": "1",
            "name": "volume1",
            "display_name": "",
            "volume_type_id": type_ref["id"],
            "size": 10,
            "provider_id": "volume10",
        }
        self.extended = {"name": "", "size": "0", "storageserver": ""}
        self.driver.extend_volume(volume, 10)
        expected = {"name": "", "size": "0", "storageserver": ""}
        self.assertDictMatch(expected, self.extended)

    def test_space_list_fails(self):
        """Test exception is thrown when we can't call space-list."""
        ctxt = context.get_admin_context()
        extra_specs = {}
        type_ref = volume_types.create(ctxt, "hgst-1", extra_specs)
        volume = {
            "id": "1",
            "name": "volume1",
            "display_name": "",
            "volume_type_id": type_ref["id"],
            "size": 10,
            "provider_id": "volume10",
        }
        self.extended = {"name": "", "size": "0", "storageserver": ""}
        self._fail_space_list = True
        self.assertRaises(exception.VolumeDriverException, self.driver.extend_volume, volume, 12)

    def test_cli_error_not_blocked(self):
        """Test the _blocked handler's handlinf of a non-blocked error.

        The _handle_blocked handler is called on any process errors in the
        code.  If the error was not caused by a blocked command condition
        (syntax error, out of space, etc.) then it should just throw the
        exception and not try and retry the command.
        """
        ctxt = context.get_admin_context()
        extra_specs = {}
        type_ref = volume_types.create(ctxt, "hgst-1", extra_specs)
        volume = {
            "id": "1",
            "name": "volume1",
            "display_name": "",
            "volume_type_id": type_ref["id"],
            "size": 10,
            "provider_id": "volume10",
        }
        self.extended = {"name": "", "size": "0", "storageserver": ""}
        self._fail_extend = True
        self.assertRaises(exception.VolumeDriverException, self.driver.extend_volume, volume, 12)
        self.assertEqual(False, self._request_cancel)

    @mock.patch("socket.gethostbyname", return_value="123.123.123.123")
    def test_initialize_connection(self, moch_ghn):
        """Test that the connection_info for Nova makes sense."""
        volume = {"name": "123", "provider_id": "spacey"}
        conn = self.driver.initialize_connection(volume, None)
        expected = {"name": "spacey", "noremovehost": "thisserver"}
        self.assertDictMatch(expected, conn["data"])