Merge "[VNX]Add `force detach` support" into stable/pike

This commit is contained in:
Zuul 2018-04-04 06:49:15 +00:00 committed by Gerrit Code Review
commit 181f90f4cf
7 changed files with 153 additions and 19 deletions

View File

@ -82,7 +82,11 @@ group: &group_base
status: 'creating' status: 'creating'
replication_status: 'enabled' replication_status: 'enabled'
connector: &connector_base
_properties:
host: host_1
initiator: ['iqn.2012-07.org.fake:01']
ip: 192.168.1.111
########################################################### ###########################################################
# TestCommonAdapter, TestISCSIAdapter, TestFCAdapter # TestCommonAdapter, TestISCSIAdapter, TestFCAdapter
@ -331,6 +335,13 @@ test_auto_register_initiator_no_white_list:
test_auto_register_initiator_no_port_to_reg: test_auto_register_initiator_no_port_to_reg:
volume: *volume_base volume: *volume_base
test_terminate_connection:
volume: *volume_base
connector: *connector_base
test_terminate_connection_force_detach:
volume: *volume_base
test_remove_host_access: test_remove_host_access:
volume: *volume_base volume: *volume_base

View File

@ -1853,6 +1853,29 @@ test_build_provider_location:
_properties: _properties:
serial: 'vnx-serial' serial: 'vnx-serial'
test_terminate_connection:
sg: &sg_terminate_connection
_properties:
existed: True
vnx:
_methods:
get_sg: *sg_terminate_connection
test_terminate_connection_force_detach:
sg: &sg_terminate_connection_force_detach_1
_properties:
existed: True
sg: &sg_terminate_connection_force_detach_2
_properties:
existed: True
sgs: &sgs_terminate_connection_force_detach
_methods:
shadow_copy: [*sg_terminate_connection_force_detach_1,
*sg_terminate_connection_force_detach_2]
vnx:
_methods:
get_sg: *sgs_terminate_connection_force_detach
test_remove_host_access: test_remove_host_access:
sg: &sg_remove_host_access sg: &sg_remove_host_access
_properties: _properties:

View File

@ -1520,6 +1520,41 @@ class TestISCSIAdapter(test.TestCase):
self.assertRaises(exception.InvalidConfigurationValue, self.assertRaises(exception.InvalidConfigurationValue,
vnx_iscsi._normalize_config) vnx_iscsi._normalize_config)
@res_mock.mock_driver_input
@res_mock.patch_iscsi_adapter
def test_terminate_connection(self, adapter, mocked_res, mocked_input):
cinder_volume = mocked_input['volume']
connector = mocked_input['connector']
adapter.remove_host_access = mock.Mock()
adapter.update_storage_group_if_required = mock.Mock()
adapter.build_terminate_connection_return_data = mock.Mock()
adapter.terminate_connection_cleanup = mock.Mock()
adapter.terminate_connection(cinder_volume, connector)
adapter.remove_host_access.assert_called_once()
adapter.update_storage_group_if_required.assert_called_once()
adapter.build_terminate_connection_return_data \
.assert_called_once()
adapter.terminate_connection_cleanup.assert_called_once()
@res_mock.mock_driver_input
@res_mock.patch_iscsi_adapter
def test_terminate_connection_force_detach(self, adapter, mocked_res,
mocked_input):
cinder_volume = mocked_input['volume']
connector = None
adapter.remove_host_access = mock.Mock()
adapter.update_storage_group_if_required = mock.Mock()
adapter.build_terminate_connection_return_data = mock.Mock()
adapter.terminate_connection_cleanup = mock.Mock()
adapter.terminate_connection(cinder_volume, connector)
adapter.remove_host_access.assert_called()
adapter.update_storage_group_if_required.assert_called()
adapter.build_terminate_connection_return_data \
.assert_not_called()
adapter.terminate_connection_cleanup.assert_called()
class TestFCAdapter(test.TestCase): class TestFCAdapter(test.TestCase):
STORAGE_PROTOCOL = common.PROTOCOL_FC STORAGE_PROTOCOL = common.PROTOCOL_FC
@ -1629,3 +1664,38 @@ class TestFCAdapter(test.TestCase):
self.assertEqual({'wwn1': ['5006016636E01CB2']}, tgt_map) self.assertEqual({'wwn1': ['5006016636E01CB2']}, tgt_map)
get_mapping.assert_called_once_with( get_mapping.assert_called_once_with(
['wwn1', 'wwn2'], ['5006016636E01CB2']) ['wwn1', 'wwn2'], ['5006016636E01CB2'])
@res_mock.mock_driver_input
@res_mock.patch_iscsi_adapter
def test_terminate_connection(self, adapter, mocked_res, mocked_input):
cinder_volume = mocked_input['volume']
connector = mocked_input['connector']
adapter.remove_host_access = mock.Mock()
adapter.update_storage_group_if_required = mock.Mock()
adapter.build_terminate_connection_return_data = mock.Mock()
adapter.terminate_connection_cleanup = mock.Mock()
adapter.terminate_connection(cinder_volume, connector)
adapter.remove_host_access.assert_called_once()
adapter.update_storage_group_if_required.assert_called_once()
adapter.build_terminate_connection_return_data \
.assert_called_once()
adapter.terminate_connection_cleanup.assert_called_once()
@res_mock.mock_driver_input
@res_mock.patch_iscsi_adapter
def test_terminate_connection_force_detach(self, adapter, mocked_res,
mocked_input):
cinder_volume = mocked_input['volume']
connector = None
adapter.remove_host_access = mock.Mock()
adapter.update_storage_group_if_required = mock.Mock()
adapter.build_terminate_connection_return_data = mock.Mock()
adapter.terminate_connection_cleanup = mock.Mock()
adapter.terminate_connection(cinder_volume, connector)
adapter.remove_host_access.assert_called()
adapter.update_storage_group_if_required.assert_called()
adapter.build_terminate_connection_return_data \
.assert_not_called()
adapter.terminate_connection_cleanup.assert_called()

View File

@ -1010,8 +1010,18 @@ class CommonAdapter(replication.ReplicationAdapter):
:param volume: `common.Volume` object with volume information. :param volume: `common.Volume` object with volume information.
:param connector: connector information from Nova. :param connector: connector information from Nova.
""" """
host = self.build_host(connector) # None `connector` means force detach the volume from all hosts.
sg = self.client.get_storage_group(host.name) is_force_detach = False
if connector is None:
LOG.info('Force detaching volume %s from all hosts.', volume.name)
is_force_detach = True
host = None if is_force_detach else self.build_host(connector)
sg_list = (self.client.filter_sg(volume.vnx_lun_id) if is_force_detach
else [self.client.get_storage_group(host.name)])
return_data = None
for sg in sg_list:
self.remove_host_access(volume, host, sg) self.remove_host_access(volume, host, sg)
# build_terminate_connection return data should go before # build_terminate_connection return data should go before
@ -1019,10 +1029,13 @@ class CommonAdapter(replication.ReplicationAdapter):
# the terminate_connection_cleanup which is needed during getting # the terminate_connection_cleanup which is needed during getting
# return data # return data
self.update_storage_group_if_required(sg) self.update_storage_group_if_required(sg)
re = self.build_terminate_connection_return_data(host, sg) if not is_force_detach:
# force detach will return None
return_data = self.build_terminate_connection_return_data(
host, sg)
self.terminate_connection_cleanup(host, sg) self.terminate_connection_cleanup(host, sg)
return re return return_data
def update_storage_group_if_required(self, sg): def update_storage_group_if_required(self, sg):
if sg.existed and self.destroy_empty_sg: if sg.existed and self.destroy_empty_sg:
@ -1036,17 +1049,19 @@ class CommonAdapter(replication.ReplicationAdapter):
:param sg: object of `storops` storage group. :param sg: object of `storops` storage group.
""" """
lun = self.client.get_lun(lun_id=volume.vnx_lun_id) lun = self.client.get_lun(lun_id=volume.vnx_lun_id)
hostname = host.name
if not sg.existed: if not sg.existed:
# `host` is None when force-detach
if host is not None:
# Only print this warning message when normal detach
LOG.warning("Storage Group %s is not found. " LOG.warning("Storage Group %s is not found. "
"Nothing can be done in terminate_connection().", "Nothing can be done in terminate_connection().",
hostname) host.name)
else: else:
try: try:
sg.detach_alu(lun) sg.detach_alu(lun)
except storops_ex.VNXDetachAluNotFoundError: except storops_ex.VNXDetachAluNotFoundError:
LOG.warning("Volume %(vol)s is not in Storage Group %(sg)s.", LOG.warning("Volume %(vol)s is not in Storage Group %(sg)s.",
{'vol': volume.name, 'sg': hostname}) {'vol': volume.name, 'sg': sg.name})
def build_terminate_connection_return_data(self, host, sg): def build_terminate_connection_return_data(self, host, sg):
raise NotImplementedError() raise NotImplementedError()
@ -1064,7 +1079,8 @@ class CommonAdapter(replication.ReplicationAdapter):
LOG.info("Storage Group %s is empty.", sg.name) LOG.info("Storage Group %s is empty.", sg.name)
sg.disconnect_host(sg.name) sg.disconnect_host(sg.name)
sg.delete() sg.delete()
if self.itor_auto_dereg: if host is not None and self.itor_auto_dereg:
# `host` is None when force-detach
self._deregister_initiator(host) self._deregister_initiator(host)
except storops_ex.StoropsException: except storops_ex.StoropsException:
LOG.warning("Failed to destroy Storage Group %s.", LOG.warning("Failed to destroy Storage Group %s.",

View File

@ -726,3 +726,6 @@ class Client(object):
def add_lun_to_ioclass(self, ioclass_name, lun_id): def add_lun_to_ioclass(self, ioclass_name, lun_id):
ioclass = self.vnx.get_ioclass(name=ioclass_name) ioclass = self.vnx.get_ioclass(name=ioclass_name)
ioclass.add_lun(lun_id) ioclass.add_lun(lun_id)
def filter_sg(self, attached_lun_id):
return self.vnx.get_sg().shadow_copy(attached_lun=attached_lun_id)

View File

@ -16,7 +16,7 @@ System requirements
- VNX Operational Environment for Block version 5.32 or higher. - VNX Operational Environment for Block version 5.32 or higher.
- VNX Snapshot and Thin Provisioning license should be activated for VNX. - VNX Snapshot and Thin Provisioning license should be activated for VNX.
- Python library ``storops`` to interact with VNX. - Python library ``storops`` version 0.5.7 or higher to interact with VNX.
- Navisphere CLI v7.32 or higher is installed along with the driver. - Navisphere CLI v7.32 or higher is installed along with the driver.
Supported operations Supported operations
@ -599,6 +599,13 @@ Obsolete extra specs
- ``storagetype:provisioning`` - ``storagetype:provisioning``
- ``storagetype:pool`` - ``storagetype:pool``
Force detach
------------
The user could use `os-force_detach` action to detach a volume from all its attached hosts.
For more detail, please refer to
https://developer.openstack.org/api-ref/block-storage/v2/?expanded=force-detach-volume-detail#force-detach-volume
Advanced features Advanced features
~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~

View File

@ -0,0 +1,4 @@
---
features:
- |
Add support to force detach a volume from all hosts on VNX.