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 f1cb8e5d8f)
This commit is contained in:
traghavendra 2020-03-19 02:10:50 -07:00 committed by raghavendrat
parent 062104e1aa
commit 4cf0d11243
10 changed files with 147 additions and 32 deletions

View File

@ -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,

View File

@ -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')

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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(

View File

@ -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])

View File

@ -539,3 +539,30 @@ Specify ip address of quorum witness server in ``/etc/cinder/cinder.conf``
<other parameters>
...
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).

View File

@ -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).