diff --git a/glance_store/_drivers/cinder.py b/glance_store/_drivers/cinder.py index b6bd2e17..e8572b6b 100644 --- a/glance_store/_drivers/cinder.py +++ b/glance_store/_drivers/cinder.py @@ -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) diff --git a/glance_store/tests/unit/test_cinder_store.py b/glance_store/tests/unit/test_cinder_store.py index 49f86a7a..8850cef7 100644 --- a/glance_store/tests/unit/test_cinder_store.py +++ b/glance_store/tests/unit/test_cinder_store.py @@ -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) diff --git a/glance_store/tests/unit/test_multistore_cinder.py b/glance_store/tests/unit/test_multistore_cinder.py index acb9a29b..12ae7133 100644 --- a/glance_store/tests/unit/test_multistore_cinder.py +++ b/glance_store/tests/unit/test_multistore_cinder.py @@ -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) diff --git a/glance_store/tests/unit/test_opts.py b/glance_store/tests/unit/test_opts.py index 5c66ae5d..e45570a3 100644 --- a/glance_store/tests/unit/test_opts.py +++ b/glance_store/tests/unit/test_opts.py @@ -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',