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 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)
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'])
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"])