summaryrefslogtreecommitdiff
path: root/glance_store/tests
diff options
context:
space:
mode:
authorwhoami-rajat <rajatdhasmana@gmail.com>2021-04-15 09:17:59 -0400
committerRajat Dhasmana <rajatdhasmana@gmail.com>2021-08-12 09:20:20 -0400
commitecda7f640fe4e807ba07981abfc1a637ddb496cd (patch)
tree3cbb1f16a6a36cbcbfa2e6975490d39f7dc8f564 /glance_store/tests
parent85c7a06687291eba30510d63d3ee8b9e9cb33c5f (diff)
downloadglance_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.py108
-rw-r--r--glance_store/tests/unit/common/test_cinder_utils.py21
-rw-r--r--glance_store/tests/unit/test_cinder_store.py28
-rw-r--r--glance_store/tests/unit/test_multistore_cinder.py28
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)