From 4cf0d112439af65a00f48830c375ba9c78152831 Mon Sep 17 00:00:00 2001 From: traghavendra Date: Thu, 19 Mar 2020 02:10:50 -0700 Subject: [PATCH] HPE 3PAR: Support duplicated FQDN in network The 3PAR driver uses the FQDN of the node that is doing the attach as an unique identifier to map the volume. The problem is that the FQDN is not always unique, there are environments where the same FQDN can be found in different systems, and in those cases if both try to attach volumes the second system will fail. One example of this happening would be on a QA environment where you are creating VMs and they all have names like controller-0.localdomain and compute-0.localdomain. This patch adds a configuration option to the 3PAR driver called `unique_fqdn_network` to support these kind of environments. Conflicts: cinder/opts.py cinder/tests/unit/volume/drivers/hpe/test_hpe3par.py cinder/tests/unit/volume/drivers/test_kaminario.py cinder/volume/drivers/hpe/hpe_3par_common.py cinder/volume/drivers/kaminario/kaminario_common.py Change-Id: I781144cbe68d6908333234a614e021365b0a6d4a Closes-Bug: #1834695 (cherry picked from commit f1cb8e5d8fcb09e656ba433cf764135603ab5ce1) --- cinder/opts.py | 1 + .../unit/volume/drivers/hpe/test_hpe3par.py | 54 +++++++++++++++++-- .../unit/volume/drivers/test_kaminario.py | 18 +++++-- cinder/volume/driver.py | 12 +++++ cinder/volume/drivers/hpe/hpe_3par_common.py | 36 ++++++++----- cinder/volume/drivers/hpe/hpe_3par_fc.py | 4 +- cinder/volume/drivers/hpe/hpe_3par_iscsi.py | 4 +- .../drivers/kaminario/kaminario_common.py | 16 +++--- .../block-storage/drivers/hpe-3par-driver.rst | 27 ++++++++++ ...port-duplicated-fqdn-751ad1dbcd137fbb.yaml | 7 +++ 10 files changed, 147 insertions(+), 32 deletions(-) create mode 100644 releasenotes/notes/hpe-3par-support-duplicated-fqdn-751ad1dbcd137fbb.yaml diff --git a/cinder/opts.py b/cinder/opts.py index 3230ed06f64..96f7d027a99 100644 --- a/cinder/opts.py +++ b/cinder/opts.py @@ -295,6 +295,7 @@ def list_opts(): cinder_volume_driver.nvmet_opts, cinder_volume_driver.scst_opts, cinder_volume_driver.image_opts, + cinder_volume_driver.fqdn_opts, cinder_volume_drivers_datera_dateraiscsi.d_opts, cinder_volume_drivers_dell_emc_powermax_common.powermax_opts, cinder_volume_drivers_dell_emc_ps.eqlx_opts, diff --git a/cinder/tests/unit/volume/drivers/hpe/test_hpe3par.py b/cinder/tests/unit/volume/drivers/hpe/test_hpe3par.py index 4d0680f1650..171c6c7c45f 100644 --- a/cinder/tests/unit/volume/drivers/hpe/test_hpe3par.py +++ b/cinder/tests/unit/volume/drivers/hpe/test_hpe3par.py @@ -19,6 +19,7 @@ import copy import mock import ddt +from oslo_config import cfg from oslo_utils import units from oslo_utils import uuidutils @@ -29,6 +30,7 @@ from cinder import test from cinder.tests.unit import fake_volume from cinder.tests.unit.volume.drivers.hpe \ import fake_hpe_3par_client as hpe3parclient +from cinder.volume import configuration as cvol_cfg from cinder.volume.drivers.hpe import hpe_3par_base as hpedriverbase from cinder.volume.drivers.hpe import hpe_3par_common as hpecommon from cinder.volume.drivers.hpe import hpe_3par_fc as hpefcdriver @@ -39,6 +41,8 @@ from cinder.volume import volume_utils hpeexceptions = hpe3parclient.hpeexceptions +CONF = cfg.CONF + HPE3PAR_CPG = 'OpenStackCPG' HPE3PAR_CPG2 = 'fakepool' @@ -5070,9 +5074,53 @@ class TestHPE3PARDriverBase(HPE3PARBaseDriver): def test__safe_hostname(self): long_hostname = "abc123abc123abc123abc123abc123abc123" fixed_hostname = "abc123abc123abc123abc123abc123a" - common = hpecommon.HPE3PARCommon(None) - safe_host = common._safe_hostname(long_hostname) - self.assertEqual(fixed_hostname, safe_host) + mock_client = self.setup_driver() + with mock.patch.object(hpecommon.HPE3PARCommon, + '_create_client') as mock_create_client: + mock_create_client.return_value = mock_client + common = self.driver._login() + safe_host = common._safe_hostname(long_hostname) + self.assertEqual(fixed_hostname, safe_host) + + def test__safe_hostname_unique(self): + long_hostname = "abc123abc123abc123abc123abc123abc123" + mock_client = self.setup_driver() + with mock.patch.object(hpecommon.HPE3PARCommon, + '_create_client') as mock_create_client: + mock_create_client.return_value = mock_client + common = self.driver._login() + + self.addCleanup(CONF.clear_override, + 'unique_fqdn_network', + group=cvol_cfg.SHARED_CONF_GROUP) + CONF.set_override('unique_fqdn_network', + False, + group=cvol_cfg.SHARED_CONF_GROUP) + my_connector = self.connector.copy() + my_connector['initiator'] = 'iqn.1993-08.org.debian:01:222:12345' + ret_name = '54321-222-10-naibed.gro.80-3991' + safe_host = common._safe_hostname(long_hostname, my_connector) + self.assertEqual(ret_name, safe_host) + + def test__safe_hostname_unique_without_initiator(self): + long_hostname = "abc123abc123abc123abc123abc123abc123" + fixed_hostname = "abc123abc123abc123abc123abc123a" + mock_client = self.setup_driver() + with mock.patch.object(hpecommon.HPE3PARCommon, + '_create_client') as mock_create_client: + mock_create_client.return_value = mock_client + common = self.driver._login() + + self.addCleanup(CONF.clear_override, + 'unique_fqdn_network', + group=cvol_cfg.SHARED_CONF_GROUP) + CONF.set_override('unique_fqdn_network', + False, + group=cvol_cfg.SHARED_CONF_GROUP) + my_connector = self.connector.copy() + del(my_connector['initiator']) + safe_host = common._safe_hostname(long_hostname, my_connector) + self.assertEqual(fixed_hostname, safe_host) @mock.patch('cinder.volume.drivers.hpe.hpe_3par_common.HPE3PARCommon.' 'is_volume_group_snap_type') diff --git a/cinder/tests/unit/volume/drivers/test_kaminario.py b/cinder/tests/unit/volume/drivers/test_kaminario.py index 4b770f69ee6..6366b61e39c 100644 --- a/cinder/tests/unit/volume/drivers/test_kaminario.py +++ b/cinder/tests/unit/volume/drivers/test_kaminario.py @@ -17,6 +17,7 @@ import re import ddt import mock +from oslo_config import cfg from oslo_utils import units import time @@ -34,6 +35,8 @@ from cinder.volume.drivers.kaminario import kaminario_fc from cinder.volume.drivers.kaminario import kaminario_iscsi from cinder.volume import volume_utils +CONF = cfg.CONF + CONNECTOR = {'initiator': 'iqn.1993-08.org.debian:01:12aa12aa12aa', 'ip': '192.168.2.5', 'platform': 'x86_64', 'host': 'test-k2', 'wwpns': ['12341a2a00001234', '12341a2a00001235'], @@ -141,7 +144,6 @@ class TestKaminarioCommon(test.TestCase): self.conf = mock.Mock(spec=configuration.Configuration) self.conf.kaminario_dedup_type_name = "dedup" self.conf.volume_dd_blocksize = 2 - self.conf.unique_fqdn_network = True self.conf.disable_discovery = False def _setup_driver(self): @@ -524,7 +526,12 @@ class TestKaminarioCommon(test.TestCase): self.assertEqual(CONNECTOR['host'], result) def test_get_initiator_host_name_unique(self): - self.driver.configuration.unique_fqdn_network = False + self.addCleanup(CONF.clear_override, + 'unique_fqdn_network', + group=configuration.SHARED_CONF_GROUP) + CONF.set_override('unique_fqdn_network', + False, + group=configuration.SHARED_CONF_GROUP) result = self.driver.get_initiator_host_name(CONNECTOR) expected = re.sub('[:.]', '_', CONNECTOR['initiator'][::-1][:32]) self.assertEqual(expected, result) @@ -627,7 +634,12 @@ class TestKaminarioFC(TestKaminarioCommon): def test_get_initiator_host_name_unique(self): connector = CONNECTOR.copy() del connector['initiator'] - self.driver.configuration.unique_fqdn_network = False + self.addCleanup(CONF.clear_override, + 'unique_fqdn_network', + group=configuration.SHARED_CONF_GROUP) + CONF.set_override('unique_fqdn_network', + False, + group=configuration.SHARED_CONF_GROUP) result = self.driver.get_initiator_host_name(connector) expected = re.sub('[:.]', '_', connector['wwnns'][0][::-1][:32]) self.assertEqual(expected, result) diff --git a/cinder/volume/driver.py b/cinder/volume/driver.py index 6c9c474897f..37fdf84936a 100644 --- a/cinder/volume/driver.py +++ b/cinder/volume/driver.py @@ -319,6 +319,16 @@ image_opts = [ 'image transfer will be aborted when multipathd is not ' 'running. Otherwise, it will fallback to single path.'), ] +fqdn_opts = [ + cfg.BoolOpt('unique_fqdn_network', + default=True, + help="Whether or not our private network has unique FQDN on " + "each initiator or not. For example networks with QA " + "systems usually have multiple servers/VMs with the same " + "FQDN. When true this will create host entries on 3PAR " + "using the FQDN, when false it will use the reversed " + "IQN/WWNN."), +] CONF = cfg.CONF @@ -333,6 +343,7 @@ CONF.register_opts(nvmet_opts) CONF.register_opts(scst_opts) CONF.register_opts(backup_opts) CONF.register_opts(image_opts) +CONF.register_opts(fqdn_opts, group=configuration.SHARED_CONF_GROUP) CONF.import_opt('backup_use_same_host', 'cinder.backup.api') @@ -394,6 +405,7 @@ class BaseVD(object): self.configuration.append_config_values(scst_opts) self.configuration.append_config_values(backup_opts) self.configuration.append_config_values(image_opts) + self.configuration.append_config_values(fqdn_opts) utils.setup_tracing(self.configuration.safe_get('trace_flags')) # NOTE(geguileo): Don't allow to start if we are enabling diff --git a/cinder/volume/drivers/hpe/hpe_3par_common.py b/cinder/volume/drivers/hpe/hpe_3par_common.py index 30139ef1e82..0d668018a19 100644 --- a/cinder/volume/drivers/hpe/hpe_3par_common.py +++ b/cinder/volume/drivers/hpe/hpe_3par_common.py @@ -57,6 +57,7 @@ from cinder.i18n import _ from cinder.objects import fields from cinder import utils from cinder.volume import configuration +from cinder.volume import driver from cinder.volume import qos_specs from cinder.volume import volume_types from cinder.volume import volume_utils @@ -296,11 +297,12 @@ class HPE3PARCommon(object): 4.0.13 - Fixed detaching issue for volume with type multiattach enabled. bug #1834660 4.0.14 - Added Peer Persistence feature + 4.0.15 - Support duplicated FQDN in network. Bug #1834695 """ - VERSION = "4.0.14" + VERSION = "4.0.15" stats = {} @@ -371,7 +373,7 @@ class HPE3PARCommon(object): @staticmethod def get_driver_options(): - return hpe3par_opts + return hpe3par_opts + driver.fqdn_opts def check_flags(self, options, required_flags): for flag in required_flags: @@ -1447,19 +1449,29 @@ class HPE3PARCommon(object): raise exception.VolumeBackendAPIException( data=e.get_description()) - def _safe_hostname(self, hostname): + def _safe_hostname(self, hostname, connector=None): """We have to use a safe hostname length for 3PAR host names.""" - try: - index = hostname.index('.') - except ValueError: - # couldn't find it - index = len(hostname) + SHARED_CONF_GROUP = 'backend_defaults' + shared_backend_conf = CONF._get(SHARED_CONF_GROUP) + unique_fqdn_network = shared_backend_conf.unique_fqdn_network + LOG.debug("unique_fqdn_network: %(fqdn)s", + {'fqdn': unique_fqdn_network}) + if(not unique_fqdn_network and connector.get('initiator')): + iqn = connector.get('initiator') + iqn = iqn.replace(":", "-") + return iqn[::-1][:31] + else: + try: + index = hostname.index('.') + except ValueError: + # couldn't find it + index = len(hostname) - # we'll just chop this off for now. - if index > 31: - index = 31 + # we'll just chop this off for now. + if index > 31: + index = 31 - return hostname[:index] + return hostname[:index] def _get_3par_host(self, hostname): return self.client.getHost(hostname) diff --git a/cinder/volume/drivers/hpe/hpe_3par_fc.py b/cinder/volume/drivers/hpe/hpe_3par_fc.py index b80ca974491..2054efab812 100644 --- a/cinder/volume/drivers/hpe/hpe_3par_fc.py +++ b/cinder/volume/drivers/hpe/hpe_3par_fc.py @@ -339,7 +339,7 @@ class HPE3PARFCDriver(hpebasedriver.HPE3PARDriverBase): # for now, do not remove zones zone_remove = False else: - hostname = common._safe_hostname(connector['host']) + hostname = common._safe_hostname(connector['host'], connector) common.terminate_connection(volume, hostname, wwn=connector['wwpns'], remote_client=remote_client) @@ -528,7 +528,7 @@ class HPE3PARFCDriver(hpebasedriver.HPE3PARDriverBase): """Creates or modifies existing 3PAR host.""" host = None domain = None - hostname = common._safe_hostname(connector['host']) + hostname = common._safe_hostname(connector['host'], connector) if remote_target: cpg = common._get_cpg_from_cpg_map( remote_target['cpg_map'], src_cpg) diff --git a/cinder/volume/drivers/hpe/hpe_3par_iscsi.py b/cinder/volume/drivers/hpe/hpe_3par_iscsi.py index acf61de4623..e929941e2b6 100644 --- a/cinder/volume/drivers/hpe/hpe_3par_iscsi.py +++ b/cinder/volume/drivers/hpe/hpe_3par_iscsi.py @@ -495,7 +495,7 @@ class HPE3PARISCSIDriver(hpebasedriver.HPE3PARDriverBase): if is_force_detach: common.terminate_connection(volume, None, None) else: - hostname = common._safe_hostname(connector['host']) + hostname = common._safe_hostname(connector['host'], connector) common.terminate_connection( volume, hostname, @@ -601,7 +601,7 @@ class HPE3PARISCSIDriver(hpebasedriver.HPE3PARDriverBase): domain = None username = None password = None - hostname = common._safe_hostname(connector['host']) + hostname = common._safe_hostname(connector['host'], connector) if remote_target: cpg = common._get_cpg_from_cpg_map( diff --git a/cinder/volume/drivers/kaminario/kaminario_common.py b/cinder/volume/drivers/kaminario/kaminario_common.py index 9e704df1f0d..adc16c2a1de 100644 --- a/cinder/volume/drivers/kaminario/kaminario_common.py +++ b/cinder/volume/drivers/kaminario/kaminario_common.py @@ -35,6 +35,7 @@ from cinder import objects from cinder.objects import fields from cinder import utils from cinder.volume import configuration +from cinder.volume import driver from cinder.volume.drivers.san import san from cinder.volume import volume_utils @@ -51,14 +52,6 @@ kaminario_opts = [ default=False, help="K2 driver will calculate max_oversubscription_ratio " "on setting this option as True."), - cfg.BoolOpt('unique_fqdn_network', - default=True, - help="Whether or not our private network has unique FQDN on " - "each initiator or not. For example networks with QA " - "systems usually have multiple servers/VMs with the same " - "FQDN. When true this will create host entries on K2 " - "using the FQDN, when false it will use the reversed " - "IQN/WWNN."), cfg.BoolOpt('disable_discovery', default=False, help="Disabling iSCSI discovery (sendtargets) for multipath " @@ -137,7 +130,7 @@ class KaminarioCinderDriver(cinder.volume.driver.ISCSIDriver): @staticmethod def get_driver_options(): - return kaminario_opts + return kaminario_opts + driver.fqdn_opts @utils.trace def check_for_setup_error(self): @@ -844,7 +837,10 @@ class KaminarioCinderDriver(cinder.volume.driver.ISCSIDriver): """ name = connector.get('initiator', connector.get('wwnns', [''])[0])[::-1] - if self.configuration.unique_fqdn_network: + SHARED_CONF_GROUP = 'backend_defaults' + shared_backend_conf = CONF._get(SHARED_CONF_GROUP) + unique_fqdn_network = shared_backend_conf.unique_fqdn_network + if unique_fqdn_network: name = connector.get('host', name) return re.sub('[^0-9a-zA-Z-_]', '_', name[:32]) diff --git a/doc/source/configuration/block-storage/drivers/hpe-3par-driver.rst b/doc/source/configuration/block-storage/drivers/hpe-3par-driver.rst index 969669902ff..cc7dc30f7ec 100644 --- a/doc/source/configuration/block-storage/drivers/hpe-3par-driver.rst +++ b/doc/source/configuration/block-storage/drivers/hpe-3par-driver.rst @@ -539,3 +539,30 @@ Specify ip address of quorum witness server in ``/etc/cinder/cinder.conf`` ... +Support duplicated FQDN in network +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The 3PAR driver uses the FQDN of the node that is doing the +attach as an unique identifier to map the volume. + +The problem is that the FQDN is not always unique, there are +environments where the same FQDN can be found in different systems, and +in those cases if both try to attach volumes the second system will +fail. + +One example of this happening would be on a QA environment where you are +creating VMs and they all have names like controller-0.localdomain and +compute-0.localdomain. + +To support these kind of environments, the user can specify below flag +in backend_defaults section of cinder.conf as follows: + +``unique_fqdn_network = False`` + +When this flag is used, then during attach volume to instance, +iscsi initiator name is used instead of FQDN. + +If above mentioned flag is not specified in cinder.conf, +then its value is considered as True (by default) and +FQDN is used (existing behavior). + diff --git a/releasenotes/notes/hpe-3par-support-duplicated-fqdn-751ad1dbcd137fbb.yaml b/releasenotes/notes/hpe-3par-support-duplicated-fqdn-751ad1dbcd137fbb.yaml new file mode 100644 index 00000000000..9ca3ac7eec2 --- /dev/null +++ b/releasenotes/notes/hpe-3par-support-duplicated-fqdn-751ad1dbcd137fbb.yaml @@ -0,0 +1,7 @@ +--- +issues: + - | + HPE 3PAR driver now supports networks with duplicated FQDNs via configuration + option `unique_fqdn_network` so attaching in these networks will work + (bug #1834695). +