Merge "[VNX]Add force detach
support"
This commit is contained in:
commit
1e3461182b
@ -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
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
@ -1517,6 +1517,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
|
||||||
@ -1626,3 +1661,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()
|
||||||
|
@ -1010,19 +1010,32 @@ 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
|
||||||
self.remove_host_access(volume, host, sg)
|
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
|
host = None if is_force_detach else self.build_host(connector)
|
||||||
# terminate_connection_cleanup. The storage group may be deleted in
|
sg_list = (self.client.filter_sg(volume.vnx_lun_id) if is_force_detach
|
||||||
# the terminate_connection_cleanup which is needed during getting
|
else [self.client.get_storage_group(host.name)])
|
||||||
# return data
|
|
||||||
self.update_storage_group_if_required(sg)
|
|
||||||
re = self.build_terminate_connection_return_data(host, sg)
|
|
||||||
self.terminate_connection_cleanup(host, sg)
|
|
||||||
|
|
||||||
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):
|
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:
|
||||||
LOG.warning("Storage Group %s is not found. "
|
# `host` is None when force-detach
|
||||||
"Nothing can be done in terminate_connection().",
|
if host is not None:
|
||||||
hostname)
|
# 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:
|
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.",
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~
|
||||||
|
@ -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