[Pure Storage] Add replication support for NVMe driver

Update driver with replication support prior to initial
release of this driver in Zed.

Change-Id: I75be679ec3cabe0d93534ec3f0115875295db630
This commit is contained in:
Simon Dodsley 2022-08-30 11:00:09 -04:00
parent 2aa4922bdd
commit bf3e51e5b2
4 changed files with 211 additions and 48 deletions

View File

@ -87,6 +87,10 @@ FC_PORT_NAMES = ["ct0.fc2", "ct0.fc3", "ct1.fc2", "ct1.fc3"]
NVME_IPS = ["10.0.0." + str(i + 1) for i in range(len(NVME_PORT_NAMES))] NVME_IPS = ["10.0.0." + str(i + 1) for i in range(len(NVME_PORT_NAMES))]
NVME_IPS += ["[2001:db8::" + str(i + 1) + "]" NVME_IPS += ["[2001:db8::" + str(i + 1) + "]"
for i in range(len(NVME_PORT_NAMES))] for i in range(len(NVME_PORT_NAMES))]
AC_NVME_IPS = ["10.0.0." + str(i + 1 + len(NVME_PORT_NAMES))
for i in range(len(NVME_PORT_NAMES))]
AC_NVME_IPS += ["[2001:db8::1:" + str(i + 1) + "]"
for i in range(len(NVME_PORT_NAMES))]
NVME_CIDR = "0.0.0.0/0" NVME_CIDR = "0.0.0.0/0"
NVME_CIDR_V6 = "::/0" NVME_CIDR_V6 = "::/0"
NVME_PORT = 4420 NVME_PORT = 4420
@ -131,6 +135,7 @@ NVME_CONNECTOR = {"nqn": INITIATOR_NQN, "host": HOSTNAME}
ISCSI_CONNECTOR = {"initiator": INITIATOR_IQN, "host": HOSTNAME} ISCSI_CONNECTOR = {"initiator": INITIATOR_IQN, "host": HOSTNAME}
FC_CONNECTOR = {"wwpns": {INITIATOR_WWN}, "host": HOSTNAME} FC_CONNECTOR = {"wwpns": {INITIATOR_WWN}, "host": HOSTNAME}
TARGET_NQN = "nqn.2010-06.com.purestorage:flasharray.12345abc" TARGET_NQN = "nqn.2010-06.com.purestorage:flasharray.12345abc"
AC_TARGET_NQN = "nqn.2010-06.com.purestorage:flasharray.67890def"
TARGET_IQN = "iqn.2010-06.com.purestorage:flasharray.12345abc" TARGET_IQN = "iqn.2010-06.com.purestorage:flasharray.12345abc"
AC_TARGET_IQN = "iqn.2018-06.com.purestorage:flasharray.67890def" AC_TARGET_IQN = "iqn.2018-06.com.purestorage:flasharray.67890def"
TARGET_WWN = "21000024ff59fe94" TARGET_WWN = "21000024ff59fe94"
@ -166,6 +171,12 @@ NVME_PORTS = [{"name": name,
"portal": ip + ":" + TARGET_ROCE_PORT, "portal": ip + ":" + TARGET_ROCE_PORT,
"wwn": None, "wwn": None,
} for name, ip in zip(NVME_PORT_NAMES * 2, NVME_IPS)] } for name, ip in zip(NVME_PORT_NAMES * 2, NVME_IPS)]
AC_NVME_PORTS = [{"name": name,
"nqn": AC_TARGET_NQN,
"iqn": None,
"portal": ip + ":" + TARGET_ROCE_PORT,
"wwn": None,
} for name, ip in zip(NVME_PORT_NAMES * 2, AC_NVME_IPS)]
ISCSI_PORTS = [{"name": name, ISCSI_PORTS = [{"name": name,
"iqn": TARGET_IQN, "iqn": TARGET_IQN,
"portal": ip + ":" + TARGET_PORT, "portal": ip + ":" + TARGET_PORT,
@ -340,7 +351,55 @@ NVME_CONNECTION_INFO_V6 = {
"volume_nguid": "0009714b5cb916324a9374c470002b2c8", "volume_nguid": "0009714b5cb916324a9374c470002b2c8",
}, },
} }
NVME_CONNECTION_INFO_AC = {
"driver_volume_type": "nvmeof",
"data": {
"target_nqn": TARGET_NQN,
"discard": True,
"portals": [
(NVME_IPS[0], NVME_PORT, "rdma"),
(NVME_IPS[1], NVME_PORT, "rdma"),
(NVME_IPS[2], NVME_PORT, "rdma"),
(NVME_IPS[3], NVME_PORT, "rdma"),
(AC_NVME_IPS[0], NVME_PORT, "rdma"),
(AC_NVME_IPS[1], NVME_PORT, "rdma"),
(AC_NVME_IPS[2], NVME_PORT, "rdma"),
(AC_NVME_IPS[3], NVME_PORT, "rdma")],
"volume_nguid": "0009714b5cb916324a9374c470002b2c8",
},
}
NVME_CONNECTION_INFO_AC_FILTERED = {
"driver_volume_type": "nvmeof",
"data": {
"target_nqn": TARGET_NQN,
"discard": True,
# Final entry filtered by NVME_CIDR_FILTERED
"portals": [
(NVME_IPS[0], NVME_PORT, "rdma"),
(NVME_IPS[1], NVME_PORT, "rdma"),
(NVME_IPS[2], NVME_PORT, "rdma"),
(NVME_IPS[3], NVME_PORT, "rdma"),
(AC_NVME_IPS[0], NVME_PORT, "rdma"),
(AC_NVME_IPS[1], NVME_PORT, "rdma"),
(AC_NVME_IPS[2], NVME_PORT, "rdma")],
"volume_nguid": "0009714b5cb916324a9374c470002b2c8",
},
}
NVME_CONNECTION_INFO_AC_FILTERED_LIST = {
"driver_volume_type": "nvmeof",
"data": {
"target_nqn": TARGET_NQN,
"discard": True,
# Final entry filtered by NVME_CIDR_FILTERED
"portals": [
(NVME_IPS[1], NVME_PORT, "rdma"),
(NVME_IPS[2], NVME_PORT, "rdma"),
(AC_NVME_IPS[5].strip("[]"), NVME_PORT, "rdma"), # IPv6
(AC_NVME_IPS[6].strip("[]"), NVME_PORT, "rdma"), # IPv6
],
"volume_nguid": "0009714b5cb916324a9374c470002b2c8",
},
}
FC_CONNECTION_INFO = { FC_CONNECTION_INFO = {
"driver_volume_type": "fibre_channel", "driver_volume_type": "fibre_channel",
"data": { "data": {
@ -978,37 +1037,6 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase):
mock.call(self.array, [mock_sync_target], 'cinder-pod') mock.call(self.array, [mock_sync_target], 'cinder-pod')
]) ])
@mock.patch(BASE_DRIVER_OBJ + '._setup_replicated_pods')
@mock.patch(BASE_DRIVER_OBJ + '._generate_replication_retention')
@mock.patch(BASE_DRIVER_OBJ + '._setup_replicated_pgroups')
def test_do_setup_replicated_sync_rep_bad_driver(
self,
mock_setup_repl_pgroups,
mock_generate_replication_retention,
mock_setup_pods):
retention = mock.MagicMock()
mock_generate_replication_retention.return_value = retention
self._setup_mocks_for_replication()
self.mock_config.safe_get.return_value = [
{
"backend_id": "foo",
"managed_backend_name": None,
"san_ip": "1.2.3.4",
"api_token": "abc123",
"type": "sync",
}
]
mock_sync_target = mock.MagicMock()
mock_sync_target.get.return_value = GET_ARRAY_SECONDARY
self.array.get.return_value = GET_ARRAY_PRIMARY
self.driver._storage_protocol = 'NVMe-RoCE'
self.purestorage_module.FlashArray.side_effect = [self.array,
mock_sync_target]
self.assertRaises(pure.PureDriverException,
self.driver.do_setup,
None)
def test_update_provider_info_update_all(self): def test_update_provider_info_update_all(self):
test_vols = [ test_vols = [
self.new_fake_vol(spec={'id': fake.VOLUME_ID}, self.new_fake_vol(spec={'id': fake.VOLUME_ID},
@ -4520,6 +4548,155 @@ class PureNVMEDriverTestCase(PureBaseSharedDriverTestCase):
NVME_CONNECTOR, NVME_CONNECTOR,
) )
@mock.patch(NVME_DRIVER_OBJ + "._get_nguid")
@mock.patch(NVME_DRIVER_OBJ + "._get_wwn")
@mock.patch(NVME_DRIVER_OBJ + "._connect")
@mock.patch(NVME_DRIVER_OBJ + "._get_target_nvme_ports")
def test_initialize_connection_uniform_ac(
self, mock_get_nvme_ports, mock_connection, mock_get_wwn,
mock_get_nguid
):
repl_extra_specs = {
"replication_type": "<in> sync",
"replication_enabled": "<is> true",
}
vol, vol_name = self.new_fake_vol(type_extra_specs=repl_extra_specs)
mock_get_nvme_ports.side_effect = [NVME_PORTS, AC_NVME_PORTS]
mock_get_wwn.return_value = "3624a93709714b5cb91634c470002b2c8"
mock_get_nguid.return_value = "0009714b5cb916324a9374c470002b2c8"
mock_connection.side_effect = [
{
"vol": vol_name,
"lun": 1,
},
{
"vol": vol_name,
"lun": 5,
},
]
result = deepcopy(NVME_CONNECTION_INFO_AC)
self.driver._is_active_cluster_enabled = True
mock_secondary = mock.MagicMock()
self.driver._uniform_active_cluster_target_arrays = [mock_secondary]
real_result = self.driver.initialize_connection(vol, NVME_CONNECTOR)
self.assertDictEqual(result, real_result)
mock_get_nvme_ports.assert_has_calls(
[
mock.call(self.array),
mock.call(mock_secondary),
]
)
mock_connection.assert_has_calls(
[
mock.call(self.array, vol_name, NVME_CONNECTOR),
mock.call(
mock_secondary, vol_name, NVME_CONNECTOR),
]
)
@mock.patch(NVME_DRIVER_OBJ + "._get_nguid")
@mock.patch(NVME_DRIVER_OBJ + "._get_wwn")
@mock.patch(NVME_DRIVER_OBJ + "._connect")
@mock.patch(NVME_DRIVER_OBJ + "._get_target_nvme_ports")
def test_initialize_connection_uniform_ac_cidr(
self, mock_get_nvme_ports, mock_connection, mock_get_wwn,
mock_get_nguid
):
repl_extra_specs = {
"replication_type": "<in> sync",
"replication_enabled": "<is> true",
}
vol, vol_name = self.new_fake_vol(type_extra_specs=repl_extra_specs)
mock_get_nvme_ports.side_effect = [NVME_PORTS, AC_NVME_PORTS]
mock_get_wwn.return_value = "3624a93709714b5cb91634c470002b2c8"
mock_get_nguid.return_value = "0009714b5cb916324a9374c470002b2c8"
mock_connection.side_effect = [
{
"vol": vol_name,
"lun": 1,
},
{
"vol": vol_name,
"lun": 5,
},
]
result = deepcopy(NVME_CONNECTION_INFO_AC_FILTERED)
self.driver._is_active_cluster_enabled = True
# Set up some CIDRs to block: this will block only one of the
# get four+three results back
self.driver.configuration.pure_nvme_cidr = NVME_CIDR_FILTERED
mock_secondary = mock.MagicMock()
self.driver._uniform_active_cluster_target_arrays = [mock_secondary]
real_result = self.driver.initialize_connection(vol, NVME_CONNECTOR)
self.assertDictEqual(result, real_result)
mock_get_nvme_ports.assert_has_calls(
[
mock.call(self.array),
mock.call(mock_secondary),
]
)
mock_connection.assert_has_calls(
[
mock.call(self.array, vol_name, NVME_CONNECTOR),
mock.call(mock_secondary, vol_name, NVME_CONNECTOR),
]
)
@mock.patch(NVME_DRIVER_OBJ + "._get_nguid")
@mock.patch(NVME_DRIVER_OBJ + "._get_wwn")
@mock.patch(NVME_DRIVER_OBJ + "._connect")
@mock.patch(NVME_DRIVER_OBJ + "._get_target_nvme_ports")
def test_initialize_connection_uniform_ac_cidrs(
self, mock_get_nvme_ports, mock_connection, mock_get_wwn,
mock_get_nguid
):
repl_extra_specs = {
"replication_type": "<in> sync",
"replication_enabled": "<is> true",
}
vol, vol_name = self.new_fake_vol(type_extra_specs=repl_extra_specs)
mock_get_nvme_ports.side_effect = [NVME_PORTS, AC_NVME_PORTS]
mock_get_wwn.return_value = "3624a93709714b5cb91634c470002b2c8"
mock_get_nguid.return_value = "0009714b5cb916324a9374c470002b2c8"
mock_connection.side_effect = [
{
"vol": vol_name,
"lun": 1,
},
{
"vol": vol_name,
"lun": 5,
},
]
result = deepcopy(NVME_CONNECTION_INFO_AC_FILTERED_LIST)
self.driver._is_active_cluster_enabled = True
# Set up some CIDRs to block: this will allow only 2 addresses from
# each host of the ActiveCluster, so we should check that we only
# get two+two results back
self.driver.configuration.pure_nvme = NVME_CIDR
self.driver.configuration.pure_nvme_cidr_list = NVME_CIDRS_FILTERED
mock_secondary = mock.MagicMock()
self.driver._uniform_active_cluster_target_arrays = [mock_secondary]
real_result = self.driver.initialize_connection(vol, NVME_CONNECTOR)
self.assertDictEqual(result, real_result)
mock_get_nvme_ports.assert_has_calls(
[
mock.call(self.array),
mock.call(mock_secondary),
]
)
mock_connection.assert_has_calls(
[
mock.call(self.array, vol_name, NVME_CONNECTOR),
mock.call(mock_secondary, vol_name, NVME_CONNECTOR),
]
)
@mock.patch(NVME_DRIVER_OBJ + "._get_nguid") @mock.patch(NVME_DRIVER_OBJ + "._get_nguid")
@mock.patch(NVME_DRIVER_OBJ + "._get_wwn") @mock.patch(NVME_DRIVER_OBJ + "._get_wwn")
@mock.patch(NVME_DRIVER_OBJ + "._connect") @mock.patch(NVME_DRIVER_OBJ + "._connect")

View File

@ -282,13 +282,6 @@ class PureBaseVolumeDriver(san.SanDriver):
ssl_cert_path = replication_device.get("ssl_cert_path", None) ssl_cert_path = replication_device.get("ssl_cert_path", None)
repl_type = replication_device.get("type", repl_type = replication_device.get("type",
REPLICATION_TYPE_ASYNC) REPLICATION_TYPE_ASYNC)
if (
repl_type == REPLICATION_TYPE_SYNC
and "NVMe" in self._storage_protocol
):
msg = _('NVMe driver does not support synchronous '
'replication')
raise PureDriverException(reason=msg)
uniform = strutils.bool_from_string( uniform = strutils.bool_from_string(
replication_device.get("uniform", False)) replication_device.get("uniform", False))

View File

@ -26,8 +26,6 @@ means you do not have the high-availability and non-disruptive upgrade
benefits provided by FlashArray. Multipathing must be used to take advantage benefits provided by FlashArray. Multipathing must be used to take advantage
of these benefits. of these benefits.
The NVMe driver does not support synchronous replication using ActiveCluster.
Supported operations Supported operations
~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~
@ -51,8 +49,7 @@ Supported operations
* Create a thin provisioned volume. * Create a thin provisioned volume.
* Replicate volumes to remote Pure Storage array(s) - synchronous replication * Replicate volumes to remote Pure Storage array(s)
is not supported with the NVMe driver.
QoS support for the Pure Storage drivers include the ability to set the QoS support for the Pure Storage drivers include the ability to set the
following capabilities in the OpenStack Block Storage API following capabilities in the OpenStack Block Storage API
@ -267,10 +264,6 @@ of the remote array.
The ``REPLICATION_TYPE`` value for the ``type`` key can be either ``sync`` or The ``REPLICATION_TYPE`` value for the ``type`` key can be either ``sync`` or
``async`` ``async``
.. note::
Synchronous replication is not supported by the NVMe driver.
If the ``type`` is ``sync`` volumes will be created in a stretched Pod. This If the ``type`` is ``sync`` volumes will be created in a stretched Pod. This
requires two arrays pre-configured with Active Cluster enabled. You can requires two arrays pre-configured with Active Cluster enabled. You can
optionally specify ``uniform`` as ``true`` or ``false``, this will instruct optionally specify ``uniform`` as ``true`` or ``false``, this will instruct

View File

@ -4,4 +4,4 @@ features:
Pure Storage adds a new driver to support NVMe-RoCE for the Pure Storage adds a new driver to support NVMe-RoCE for the
FlashArray. FlashArray.
All features of the iSCSI and FC drivers are fully supported by this new All features of the iSCSI and FC drivers are fully supported by this new
driver with the exception of synchronous replication. driver.