Merge "[VNX]Add force detach
support"
This commit is contained in:
commit
1e3461182b
cinder
tests/unit/volume/drivers/dell_emc/vnx
volume/drivers/dell_emc/vnx
doc/source/configuration/block-storage/drivers
releasenotes/notes
@ -82,7 +82,11 @@ group: &group_base
|
||||
status: 'creating'
|
||||
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
|
||||
@ -331,6 +335,13 @@ test_auto_register_initiator_no_white_list:
|
||||
test_auto_register_initiator_no_port_to_reg:
|
||||
volume: *volume_base
|
||||
|
||||
test_terminate_connection:
|
||||
volume: *volume_base
|
||||
connector: *connector_base
|
||||
|
||||
test_terminate_connection_force_detach:
|
||||
volume: *volume_base
|
||||
|
||||
test_remove_host_access:
|
||||
volume: *volume_base
|
||||
|
||||
|
@ -1853,6 +1853,29 @@ test_build_provider_location:
|
||||
_properties:
|
||||
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:
|
||||
sg: &sg_remove_host_access
|
||||
_properties:
|
||||
|
@ -1517,6 +1517,41 @@ class TestISCSIAdapter(test.TestCase):
|
||||
self.assertRaises(exception.InvalidConfigurationValue,
|
||||
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):
|
||||
STORAGE_PROTOCOL = common.PROTOCOL_FC
|
||||
@ -1626,3 +1661,38 @@ class TestFCAdapter(test.TestCase):
|
||||
self.assertEqual({'wwn1': ['5006016636E01CB2']}, tgt_map)
|
||||
get_mapping.assert_called_once_with(
|
||||
['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()
|
||||
|
@ -1010,19 +1010,32 @@ class CommonAdapter(replication.ReplicationAdapter):
|
||||
:param volume: `common.Volume` object with volume information.
|
||||
:param connector: connector information from Nova.
|
||||
"""
|
||||
host = self.build_host(connector)
|
||||
sg = self.client.get_storage_group(host.name)
|
||||
self.remove_host_access(volume, host, sg)
|
||||
# None `connector` means force detach the volume from all hosts.
|
||||
is_force_detach = False
|
||||
if connector is None:
|
||||
LOG.info('Force detaching volume %s from all hosts.', volume.name)
|
||||
is_force_detach = True
|
||||
|
||||
# build_terminate_connection return data should go before
|
||||
# terminate_connection_cleanup. The storage group may be deleted in
|
||||
# the terminate_connection_cleanup which is needed during getting
|
||||
# return data
|
||||
self.update_storage_group_if_required(sg)
|
||||
re = self.build_terminate_connection_return_data(host, sg)
|
||||
self.terminate_connection_cleanup(host, sg)
|
||||
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 re
|
||||
return_data = None
|
||||
for sg in sg_list:
|
||||
self.remove_host_access(volume, host, sg)
|
||||
|
||||
# build_terminate_connection return data should go before
|
||||
# terminate_connection_cleanup. The storage group may be deleted in
|
||||
# the terminate_connection_cleanup which is needed during getting
|
||||
# return data
|
||||
self.update_storage_group_if_required(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)
|
||||
|
||||
return return_data
|
||||
|
||||
def update_storage_group_if_required(self, sg):
|
||||
if sg.existed and self.destroy_empty_sg:
|
||||
@ -1036,17 +1049,19 @@ class CommonAdapter(replication.ReplicationAdapter):
|
||||
:param sg: object of `storops` storage group.
|
||||
"""
|
||||
lun = self.client.get_lun(lun_id=volume.vnx_lun_id)
|
||||
hostname = host.name
|
||||
if not sg.existed:
|
||||
LOG.warning("Storage Group %s is not found. "
|
||||
"Nothing can be done in terminate_connection().",
|
||||
hostname)
|
||||
# `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. "
|
||||
"Nothing can be done in terminate_connection().",
|
||||
host.name)
|
||||
else:
|
||||
try:
|
||||
sg.detach_alu(lun)
|
||||
except storops_ex.VNXDetachAluNotFoundError:
|
||||
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):
|
||||
raise NotImplementedError()
|
||||
@ -1064,7 +1079,8 @@ class CommonAdapter(replication.ReplicationAdapter):
|
||||
LOG.info("Storage Group %s is empty.", sg.name)
|
||||
sg.disconnect_host(sg.name)
|
||||
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)
|
||||
except storops_ex.StoropsException:
|
||||
LOG.warning("Failed to destroy Storage Group %s.",
|
||||
|
@ -726,3 +726,6 @@ class Client(object):
|
||||
def add_lun_to_ioclass(self, ioclass_name, lun_id):
|
||||
ioclass = self.vnx.get_ioclass(name=ioclass_name)
|
||||
ioclass.add_lun(lun_id)
|
||||
|
||||
def filter_sg(self, attached_lun_id):
|
||||
return self.vnx.get_sg().shadow_copy(attached_lun=attached_lun_id)
|
||||
|
@ -16,7 +16,7 @@ System requirements
|
||||
|
||||
- VNX Operational Environment for Block version 5.32 or higher.
|
||||
- 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.
|
||||
|
||||
Supported operations
|
||||
@ -599,6 +599,13 @@ Obsolete extra specs
|
||||
- ``storagetype:provisioning``
|
||||
- ``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
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Add support to force detach a volume from all hosts on VNX.
|
Loading…
x
Reference in New Issue
Block a user