diff options
author | whoami-rajat <rajatdhasmana@gmail.com> | 2021-04-15 09:17:59 -0400 |
---|---|---|
committer | Rajat Dhasmana <rajatdhasmana@gmail.com> | 2021-08-12 09:20:20 -0400 |
commit | ecda7f640fe4e807ba07981abfc1a637ddb496cd (patch) | |
tree | 3cbb1f16a6a36cbcbfa2e6975490d39f7dc8f564 /glance_store/tests | |
parent | 85c7a06687291eba30510d63d3ee8b9e9cb33c5f (diff) | |
download | glance_store-ecda7f640fe4e807ba07981abfc1a637ddb496cd.tar.gz |
Add volume multiattach handling
We implemented cinder's new attachment API support with patch[1].
It was needed to add multiattach volume handling added with this
patch.
There is no special configuration change or user interference needed.
If a volume is of a multiattach type, then it will be handled as
a multiattach volume.
[1] https://review.opendev.org/c/openstack/glance_store/+/782200
Implements: blueprint attachment-api-and-multiattach-support
Closes-Bug: #1904546
Change-Id: Iffb825492a20fd877476acad05f817b399072f01
Diffstat (limited to 'glance_store/tests')
-rw-r--r-- | glance_store/tests/unit/common/test_attachment_state_manager.py | 108 | ||||
-rw-r--r-- | glance_store/tests/unit/common/test_cinder_utils.py | 21 | ||||
-rw-r--r-- | glance_store/tests/unit/test_cinder_store.py | 28 | ||||
-rw-r--r-- | glance_store/tests/unit/test_multistore_cinder.py | 28 |
4 files changed, 173 insertions, 12 deletions
diff --git a/glance_store/tests/unit/common/test_attachment_state_manager.py b/glance_store/tests/unit/common/test_attachment_state_manager.py new file mode 100644 index 0000000..f019c9b --- /dev/null +++ b/glance_store/tests/unit/common/test_attachment_state_manager.py @@ -0,0 +1,108 @@ +# Copyright 2021 RedHat Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from unittest import mock + +from oslo_config import cfg +from oslotest import base + +from glance_store.common import attachment_state_manager as attach_manager +from glance_store.common import cinder_utils +from glance_store import exceptions + +CONF = cfg.CONF + + +class AttachmentStateManagerTestCase(base.BaseTestCase): + + class FakeAttachmentState: + def __init__(self): + self.attachments = {mock.sentinel.attachments} + + def setUp(self): + super(AttachmentStateManagerTestCase, self).setUp() + self.__manager__ = attach_manager.__manager__ + + def get_state(self): + with self.__manager__.get_state() as state: + return state + + def test_get_state_host_not_initialized(self): + self.__manager__.state = None + self.assertRaises(exceptions.HostNotInitialized, + self.get_state) + + def test_get_state(self): + self.__manager__.state = self.FakeAttachmentState() + state = self.get_state() + self.assertEqual({mock.sentinel.attachments}, state.attachments) + + +class AttachmentStateTestCase(base.BaseTestCase): + + def setUp(self): + super(AttachmentStateTestCase, self).setUp() + self.attachments = set() + self.m = attach_manager._AttachmentState() + self.attach_call_1 = [mock.sentinel.client, mock.sentinel.volume_id] + self.attach_call_2 = {'mode': mock.sentinel.mode} + self.disconnect_vol_call = [mock.sentinel.connection_info, + mock.sentinel.device] + self.detach_call = [mock.sentinel.client, mock.sentinel.attachment_id] + self.attachment_dict = {'id': mock.sentinel.attachment_id} + + def _sentinel_attach(self): + attachment_id = self.m.attach( + mock.sentinel.client, mock.sentinel.volume_id, + mock.sentinel.host, mode=mock.sentinel.mode) + return attachment_id + + def _sentinel_detach(self, conn): + self.m.detach(mock.sentinel.client, mock.sentinel.attachment_id, + mock.sentinel.volume_id, mock.sentinel.host, + conn, mock.sentinel.connection_info, + mock.sentinel.device) + + @mock.patch.object(cinder_utils.API, 'attachment_create') + def test_attach(self, mock_attach_create): + mock_attach_create.return_value = self.attachment_dict + attachment = self._sentinel_attach() + mock_attach_create.assert_called_once_with( + *self.attach_call_1, **self.attach_call_2) + self.assertEqual(mock.sentinel.attachment_id, attachment['id']) + + @mock.patch.object(cinder_utils.API, 'attachment_delete') + def test_detach_without_attach(self, mock_attach_delete): + ex = exceptions.BackendException + conn = mock.MagicMock() + mock_attach_delete.side_effect = ex() + self.assertRaises(ex, self._sentinel_detach, conn) + conn.disconnect_volume.assert_called_once_with( + *self.disconnect_vol_call) + + @mock.patch.object(cinder_utils.API, 'attachment_create') + @mock.patch.object(cinder_utils.API, 'attachment_delete') + def test_detach_with_attach(self, mock_attach_delete, mock_attach_create): + conn = mock.MagicMock() + mock_attach_create.return_value = self.attachment_dict + attachment = self._sentinel_attach() + self._sentinel_detach(conn) + mock_attach_create.assert_called_once_with( + *self.attach_call_1, **self.attach_call_2) + self.assertEqual(mock.sentinel.attachment_id, attachment['id']) + conn.disconnect_volume.assert_called_once_with( + *self.disconnect_vol_call) + mock_attach_delete.assert_called_once_with( + *self.detach_call) diff --git a/glance_store/tests/unit/common/test_cinder_utils.py b/glance_store/tests/unit/common/test_cinder_utils.py index 2acfaac..2f68afb 100644 --- a/glance_store/tests/unit/common/test_cinder_utils.py +++ b/glance_store/tests/unit/common/test_cinder_utils.py @@ -74,6 +74,27 @@ class CinderUtilsTestCase(base.BaseTestCase): self.volume_api.attachment_create, self.fake_client, self.fake_vol_id) + def test_attachment_create_retries(self): + + fake_attach_id = 'fake-attach-id' + # Make create fail two times and succeed on the third attempt. + self.fake_client.attachments.create.side_effect = [ + cinder_exception.BadRequest(400), + cinder_exception.BadRequest(400), + fake_attach_id] + + # Make sure we get a clean result. + fake_attachment_id = self.volume_api.attachment_create( + self.fake_client, self.fake_vol_id) + + self.assertEqual(fake_attach_id, fake_attachment_id) + # Assert that we called attachment create three times due to the retry + # decorator. + self.fake_client.attachments.create.assert_has_calls([ + mock.call(self.fake_vol_id, None, mode=None), + mock.call(self.fake_vol_id, None, mode=None), + mock.call(self.fake_vol_id, None, mode=None)]) + def test_attachment_get(self): self.volume_api.attachment_get(self.fake_client, self.fake_attach_id) self.fake_client.attachments.show.assert_called_once_with( diff --git a/glance_store/tests/unit/test_cinder_store.py b/glance_store/tests/unit/test_cinder_store.py index ad25486..437cfa0 100644 --- a/glance_store/tests/unit/test_cinder_store.py +++ b/glance_store/tests/unit/test_cinder_store.py @@ -31,6 +31,7 @@ from oslo_concurrency import processutils from oslo_utils.secretutils import md5 from oslo_utils import units +from glance_store.common import attachment_state_manager from glance_store.common import cinder_utils from glance_store import exceptions from glance_store import location @@ -152,9 +153,11 @@ class TestCinderStore(base.StoreBaseTest, def _test_open_cinder_volume(self, open_mode, attach_mode, error, multipath_supported=False, enforce_multipath=False, - encrypted_nfs=False, qcow2_vol=False): + encrypted_nfs=False, qcow2_vol=False, + multiattach=False): self.config(cinder_mount_point_base=None) - fake_volume = mock.MagicMock(id=str(uuid.uuid4()), status='available') + fake_volume = mock.MagicMock(id=str(uuid.uuid4()), status='available', + multiattach=multiattach) fake_volume.manager.get.return_value = fake_volume fake_volumes = FakeObject(get=lambda id: fake_volume) fake_attachment_id = str(uuid.uuid4()) @@ -178,10 +181,20 @@ class TestCinderStore(base.StoreBaseTest, yield def do_open(): - with self.store._open_cinder_volume( - fake_client, fake_volume, open_mode): - if error: - raise error + if multiattach: + with mock.patch.object( + attachment_state_manager._AttachmentStateManager, + 'get_state') as mock_get_state: + mock_get_state.return_value.__enter__.return_value = ( + attachment_state_manager._AttachmentState()) + with self.store._open_cinder_volume( + fake_client, fake_volume, open_mode): + pass + else: + with self.store._open_cinder_volume( + fake_client, fake_volume, open_mode): + if error: + raise error def fake_factory(protocol, root_helper, **kwargs): return fake_connector @@ -298,6 +311,9 @@ class TestCinderStore(base.StoreBaseTest, def test_open_cinder_volume_nfs_qcow2_volume(self): self._test_open_cinder_volume('rb', 'ro', None, qcow2_vol=True) + def test_open_cinder_volume_multiattach_volume(self): + self._test_open_cinder_volume('rb', 'ro', None, multiattach=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 ebe69ee..167b379 100644 --- a/glance_store/tests/unit/test_multistore_cinder.py +++ b/glance_store/tests/unit/test_multistore_cinder.py @@ -33,6 +33,7 @@ from oslo_utils.secretutils import md5 from oslo_utils import units import glance_store as store +from glance_store.common import attachment_state_manager from glance_store.common import cinder_utils from glance_store import exceptions from glance_store import location @@ -184,9 +185,11 @@ class TestMultiCinderStore(base.MultiStoreBaseTest, def _test_open_cinder_volume(self, open_mode, attach_mode, error, multipath_supported=False, enforce_multipath=False, - encrypted_nfs=False, qcow2_vol=False): + encrypted_nfs=False, qcow2_vol=False, + multiattach=False): self.config(cinder_mount_point_base=None, group='cinder1') - fake_volume = mock.MagicMock(id=str(uuid.uuid4()), status='available') + fake_volume = mock.MagicMock(id=str(uuid.uuid4()), status='available', + multiattach=multiattach) fake_volume.manager.get.return_value = fake_volume fake_attachment_id = str(uuid.uuid4()) fake_attachment_create = {'id': fake_attachment_id} @@ -210,10 +213,20 @@ class TestMultiCinderStore(base.MultiStoreBaseTest, yield def do_open(): - with self.store._open_cinder_volume( - fake_client, fake_volume, open_mode): - if error: - raise error + if multiattach: + with mock.patch.object( + attachment_state_manager._AttachmentStateManager, + 'get_state') as mock_get_state: + mock_get_state.return_value.__enter__.return_value = ( + attachment_state_manager._AttachmentState()) + with self.store._open_cinder_volume( + fake_client, fake_volume, open_mode): + pass + else: + with self.store._open_cinder_volume( + fake_client, fake_volume, open_mode): + if error: + raise error def fake_factory(protocol, root_helper, **kwargs): return fake_connector @@ -332,6 +345,9 @@ class TestMultiCinderStore(base.MultiStoreBaseTest, def test_open_cinder_volume_nfs_qcow2_volume(self): self._test_open_cinder_volume('rb', 'ro', None, qcow2_vol=True) + def test_open_cinder_volume_multiattach_volume(self): + self._test_open_cinder_volume('rb', 'ro', None, multiattach=True) + def test_cinder_check_context(self): self.assertRaises(exceptions.BadStoreConfiguration, self.store._check_context, None) |