Image upload fails if cinder multipath is enabled

Image upload is failing with NoFibreChannelVolumeDeviceFound error after
configuring cinder backend (HP3Par FC storage) for the glance service. The
issue here is that usually cinder calls os_brick with multipath information
but in this case cinder driver of glance is doing the call (without passing
any multipath information). with hard-coded' False to let os_brick library
know that multipath is not configured.

In order to fix this, added two new boolean config option for cinder
driver default to False;

'cinder_use_multipath'
'cinder_enforce_multipath'

Change-Id: I07064f3cb1a33ac4ac7e4b572f8e1b5c688bd9a3
Closes-Bug: #1863983
This commit is contained in:
Abhishek Kekane 2020-02-20 07:21:14 +00:00
parent d0736144a1
commit ae73287c7b
4 changed files with 104 additions and 28 deletions

View File

@ -304,6 +304,34 @@ Possible values:
Related options:
* None
"""),
cfg.BoolOpt('cinder_enforce_multipath',
default=False,
help="""
If this is set to True, attachment of volumes for image transfer will
be aborted when multipathd is not running. Otherwise, it will fallback
to single path.
Possible values:
* True or False
Related options:
* cinder_use_multipath
"""),
cfg.BoolOpt('cinder_use_multipath',
default=False,
help="""
Flag to identify mutipath is supported or not in the deployment.
Set it to False if multipath is not supported.
Possible values:
* True or False
Related options:
* cinder_enforce_multipath
"""),
]
@ -498,8 +526,17 @@ class Store(glance_store.driver.Store):
root_helper = get_root_helper(backend=self.backend_group)
priv_context.init(root_helper=shlex.split(root_helper))
host = socket.gethostname()
properties = connector.get_connector_properties(root_helper, host,
False, False)
if self.backend_group:
use_multipath = getattr(
self.conf, self.backend_group).cinder_use_multipath
enforce_multipath = getattr(
self.conf, self.backend_group).cinder_enforce_multipath
else:
use_multipath = self.conf.glance_store.cinder_use_multipath
enforce_multipath = self.conf.glance_store.cinder_enforce_multipath
properties = connector.get_connector_properties(
root_helper, host, use_multipath, enforce_multipath)
try:
volume.reserve(volume)

View File

@ -145,7 +145,9 @@ class TestCinderStore(base.StoreBaseTest,
volume_available, 'available', 'in-use')
fake_manager.get.assert_called_with('fake-id')
def _test_open_cinder_volume(self, open_mode, attach_mode, error):
def _test_open_cinder_volume(self, open_mode, attach_mode, error,
multipath_supported=False,
enforce_multipath=False):
fake_volume = mock.MagicMock(id=str(uuid.uuid4()), status='available')
fake_volumes = FakeObject(get=lambda id: fake_volume,
detach=mock.Mock())
@ -179,22 +181,26 @@ class TestCinderStore(base.StoreBaseTest,
side_effect=fake_chown), \
mock.patch.object(cinder, 'get_root_helper',
return_value=root_helper), \
mock.patch.object(connector, 'get_connector_properties'), \
mock.patch.object(connector.InitiatorConnector, 'factory',
side_effect=fake_factory):
if error:
self.assertRaises(error, do_open)
else:
do_open()
with mock.patch.object(connector,
'get_connector_properties') as mock_conn:
if error:
self.assertRaises(error, do_open)
else:
do_open()
fake_connector.connect_volume.assert_called_once_with(mock.ANY)
fake_connector.disconnect_volume.assert_called_once_with(
mock.ANY, fake_devinfo)
fake_volume.attach.assert_called_once_with(
None, 'glance_store', attach_mode,
host_name=socket.gethostname())
fake_volumes.detach.assert_called_once_with(fake_volume)
mock_conn.assert_called_once_with(
root_helper, socket.gethostname(), multipath_supported,
enforce_multipath)
fake_connector.connect_volume.assert_called_once_with(mock.ANY)
fake_connector.disconnect_volume.assert_called_once_with(
mock.ANY, fake_devinfo)
fake_volume.attach.assert_called_once_with(
None, 'glance_store', attach_mode,
host_name=socket.gethostname())
fake_volumes.detach.assert_called_once_with(fake_volume)
def test_open_cinder_volume_rw(self):
self._test_open_cinder_volume('wb', 'rw', None)
@ -205,6 +211,18 @@ class TestCinderStore(base.StoreBaseTest,
def test_open_cinder_volume_error(self):
self._test_open_cinder_volume('wb', 'rw', IOError)
def test_open_cinder_volume_multipath_supported(self):
self.config(cinder_use_multipath=True)
self._test_open_cinder_volume('wb', 'rw', None,
multipath_supported=True)
def test_open_cinder_volume_enforce_multipath(self):
self.config(cinder_use_multipath=True)
self.config(cinder_enforce_multipath=True)
self._test_open_cinder_volume('wb', 'rw', None,
multipath_supported=True,
enforce_multipath=True)
def test_cinder_configure_add(self):
self.assertRaises(exceptions.BadStoreConfiguration,
self.store._check_context, None)

View File

@ -165,7 +165,9 @@ class TestMultiCinderStore(base.MultiStoreBaseTest,
volume_available, 'available', 'in-use')
fake_manager.get.assert_called_with('fake-id')
def _test_open_cinder_volume(self, open_mode, attach_mode, error):
def _test_open_cinder_volume(self, open_mode, attach_mode, error,
multipath_supported=False,
enforce_multipath=False):
fake_volume = mock.MagicMock(id=str(uuid.uuid4()), status='available')
fake_volumes = FakeObject(get=lambda id: fake_volume,
detach=mock.Mock())
@ -199,22 +201,26 @@ class TestMultiCinderStore(base.MultiStoreBaseTest,
side_effect=fake_chown), \
mock.patch.object(cinder, 'get_root_helper',
return_value=root_helper), \
mock.patch.object(connector, 'get_connector_properties'), \
mock.patch.object(connector.InitiatorConnector, 'factory',
side_effect=fake_factory):
if error:
self.assertRaises(error, do_open)
else:
do_open()
with mock.patch.object(connector,
'get_connector_properties') as mock_conn:
if error:
self.assertRaises(error, do_open)
else:
do_open()
fake_connector.connect_volume.assert_called_once_with(mock.ANY)
fake_connector.disconnect_volume.assert_called_once_with(
mock.ANY, fake_devinfo)
fake_volume.attach.assert_called_once_with(
None, 'glance_store', attach_mode,
host_name=socket.gethostname())
fake_volumes.detach.assert_called_once_with(fake_volume)
mock_conn.assert_called_once_with(
root_helper, socket.gethostname(), multipath_supported,
enforce_multipath)
fake_connector.connect_volume.assert_called_once_with(mock.ANY)
fake_connector.disconnect_volume.assert_called_once_with(
mock.ANY, fake_devinfo)
fake_volume.attach.assert_called_once_with(
None, 'glance_store', attach_mode,
host_name=socket.gethostname())
fake_volumes.detach.assert_called_once_with(fake_volume)
def test_open_cinder_volume_rw(self):
self._test_open_cinder_volume('wb', 'rw', None)
@ -225,6 +231,19 @@ class TestMultiCinderStore(base.MultiStoreBaseTest,
def test_open_cinder_volume_error(self):
self._test_open_cinder_volume('wb', 'rw', IOError)
def test_open_cinder_volume_multipath_disabled(self):
self.config(cinder_use_multipath=False, group='cinder1')
self._test_open_cinder_volume('wb', 'rw', None,
multipath_supported=False)
def test_open_cinder_volume_enforce_multipath(self):
self.config(cinder_use_multipath=True, group='cinder1')
self.config(cinder_enforce_multipath=True, group='cinder1')
self._test_open_cinder_volume('wb', 'rw', None,
multipath_supported=True,
enforce_multipath=True)
def test_cinder_configure_add(self):
self.assertRaises(exceptions.BadStoreConfiguration,
self.store._check_context, None)

View File

@ -82,6 +82,8 @@ class OptsTestCase(base.StoreBaseTest):
'cinder_store_password',
'cinder_store_project_name',
'cinder_volume_type',
'cinder_use_multipath',
'cinder_enforce_multipath',
'default_swift_reference',
'https_insecure',
'filesystem_store_chunk_size',