diff options
author | whoami-rajat <rajatdhasmana@gmail.com> | 2022-03-10 22:28:29 +0530 |
---|---|---|
committer | whoami-rajat <rajatdhasmana@gmail.com> | 2022-03-11 14:26:40 +0530 |
commit | bb790de1dca91bd143d493ba94f5b9c48b74302e (patch) | |
tree | 5e118262ec78dc30dece641b4a819020b3dc04f0 /glance_store/tests | |
parent | 7b5b50fd9f49aab8c145d4fa2681f865cc07cbf6 (diff) | |
download | glance_store-bb790de1dca91bd143d493ba94f5b9c48b74302e.tar.gz |
Refactor cinder store tests[1/2]
This patch aims at a refactoring effort that would remove
duplicate tests (current and future) by moving them into a
common base class which is called via both single and multi store
test modules with their specific configurations.
This has a lot of benefits:
1) Removes duplicate code
2) Makes addition of new tests easier and cleaner
3) Ensuring a new method/code path added is tested in both
single and multi store configurations
4) Fixing issues detected while refactoring methods
(Eg: fake_chown accepted an optional backend parameter which
does not match the signature of original method temporary_chown)
Change-Id: Iaacb3e117cac2a661750bdb21bd0a7496078ea26
Diffstat (limited to 'glance_store/tests')
-rw-r--r-- | glance_store/tests/unit/test_cinder_base.py | 371 | ||||
-rw-r--r-- | glance_store/tests/unit/test_cinder_store.py | 338 | ||||
-rw-r--r-- | glance_store/tests/unit/test_multistore_cinder.py | 344 |
3 files changed, 414 insertions, 639 deletions
diff --git a/glance_store/tests/unit/test_cinder_base.py b/glance_store/tests/unit/test_cinder_base.py new file mode 100644 index 0000000..45d786a --- /dev/null +++ b/glance_store/tests/unit/test_cinder_base.py @@ -0,0 +1,371 @@ +# Copyright 2022 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. + +import contextlib +import os +from unittest import mock + +import six +import socket +import sys +import tempfile +import time +import uuid + +from os_brick.initiator import connector +from oslo_concurrency import processutils +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 + +sys.modules['glance_store.common.fs_mount'] = mock.Mock() +from glance_store._drivers import cinder # noqa + + +class TestCinderStoreBase(object): + + def test_get_cinderclient(self): + cc = self.store.get_cinderclient(self.context) + self.assertEqual('fake_token', cc.client.auth.token) + self.assertEqual('http://foo/public_url', cc.client.auth.endpoint) + + def test_temporary_chown(self): + fake_stat = mock.MagicMock(st_uid=1) + + with mock.patch.object(os, 'stat', return_value=fake_stat), \ + mock.patch.object(os, 'getuid', return_value=2), \ + mock.patch.object(processutils, 'execute') as mock_execute, \ + mock.patch.object(cinder.Store, 'get_root_helper', + return_value='sudo'): + with self.store.temporary_chown('test'): + pass + expected_calls = [mock.call('chown', 2, 'test', run_as_root=True, + root_helper='sudo'), + mock.call('chown', 1, 'test', run_as_root=True, + root_helper='sudo')] + self.assertEqual(expected_calls, mock_execute.call_args_list) + + @mock.patch.object(time, 'sleep') + def test_wait_volume_status(self, mock_sleep): + fake_manager = mock.MagicMock(get=mock.Mock()) + volume_available = mock.MagicMock(manager=fake_manager, + id='fake-id', + status='available') + volume_in_use = mock.MagicMock(manager=fake_manager, + id='fake-id', + status='in-use') + fake_manager.get.side_effect = [volume_available, volume_in_use] + self.assertEqual(volume_in_use, + self.store._wait_volume_status( + volume_available, 'available', 'in-use')) + fake_manager.get.assert_called_with('fake-id') + mock_sleep.assert_called_once_with(0.5) + + @mock.patch.object(time, 'sleep') + def test_wait_volume_status_unexpected(self, mock_sleep): + fake_manager = mock.MagicMock(get=mock.Mock()) + volume_available = mock.MagicMock(manager=fake_manager, + id='fake-id', + status='error') + fake_manager.get.return_value = volume_available + self.assertRaises(exceptions.BackendException, + self.store._wait_volume_status, + volume_available, 'available', 'in-use') + fake_manager.get.assert_called_with('fake-id') + + @mock.patch.object(time, 'sleep') + def test_wait_volume_status_timeout(self, mock_sleep): + fake_manager = mock.MagicMock(get=mock.Mock()) + volume_available = mock.MagicMock(manager=fake_manager, + id='fake-id', + status='available') + fake_manager.get.return_value = volume_available + self.assertRaises(exceptions.BackendException, + self.store._wait_volume_status, + volume_available, 'available', 'in-use') + fake_manager.get.assert_called_with('fake-id') + + def _test_open_cinder_volume(self, open_mode, attach_mode, error, + multipath_supported=False, + enforce_multipath=False, + encrypted_nfs=False, qcow2_vol=False, + multiattach=False): + 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} + if encrypted_nfs or qcow2_vol: + fake_attachment_update = mock.MagicMock( + id=fake_attachment_id, + connection_info={'driver_volume_type': 'nfs'}) + else: + fake_attachment_update = mock.MagicMock(id=fake_attachment_id) + fake_conn_info = mock.MagicMock(connector={}) + fake_volumes = mock.MagicMock(get=lambda id: fake_volume) + fake_client = mock.MagicMock(volumes=fake_volumes) + _, fake_dev_path = tempfile.mkstemp(dir=self.test_dir) + fake_devinfo = {'path': fake_dev_path} + fake_connector = mock.MagicMock( + connect_volume=mock.Mock(return_value=fake_devinfo), + disconnect_volume=mock.Mock()) + + @contextlib.contextmanager + def fake_chown(path): + yield + + def do_open(): + 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 + + root_helper = "sudo glance-rootwrap /etc/glance/rootwrap.conf" + with mock.patch.object(cinder.Store, + '_wait_volume_status', + return_value=fake_volume), \ + mock.patch.object(cinder.Store, 'temporary_chown', + side_effect=fake_chown), \ + mock.patch.object(cinder.Store, 'get_root_helper', + return_value=root_helper), \ + mock.patch.object(connector.InitiatorConnector, 'factory', + side_effect=fake_factory + ) as fake_conn_obj, \ + mock.patch.object(cinder_utils.API, + 'attachment_create', + return_value=fake_attachment_create + ) as attach_create, \ + mock.patch.object(cinder_utils.API, + 'attachment_update', + return_value=fake_attachment_update + ) as attach_update, \ + mock.patch.object(cinder_utils.API, + 'attachment_delete') as attach_delete, \ + mock.patch.object(cinder_utils.API, + 'attachment_get') as attach_get, \ + mock.patch.object(cinder_utils.API, + 'attachment_complete') as attach_complete, \ + mock.patch.object(socket, + 'gethostname') as mock_get_host, \ + mock.patch.object(socket, + 'getaddrinfo') as mock_get_host_ip: + + fake_host = 'fake_host' + fake_addr_info = [[0, 1, 2, 3, ['127.0.0.1']]] + fake_ip = fake_addr_info[0][4][0] + mock_get_host.return_value = fake_host + mock_get_host_ip.return_value = fake_addr_info + + with mock.patch.object(connector, + 'get_connector_properties', + return_value=fake_conn_info) as mock_conn: + if error: + self.assertRaises(error, do_open) + elif encrypted_nfs or qcow2_vol: + fake_volume.encrypted = False + if encrypted_nfs: + fake_volume.encrypted = True + elif qcow2_vol: + attach_get.return_value = mock.MagicMock( + connection_info={'format': 'qcow2'}) + try: + with self.store._open_cinder_volume( + fake_client, fake_volume, open_mode): + pass + except exceptions.BackendException: + attach_delete.assert_called_once_with( + fake_client, fake_attachment_id) + else: + do_open() + if not (encrypted_nfs or qcow2_vol): + mock_conn.assert_called_once_with( + root_helper, fake_ip, + multipath_supported, enforce_multipath, + host=fake_host) + fake_connector.connect_volume.assert_called_once_with( + mock.ANY) + fake_connector.disconnect_volume.assert_called_once_with( + mock.ANY, fake_devinfo) + fake_conn_obj.assert_called_once_with( + mock.ANY, root_helper, conn=mock.ANY, + use_multipath=multipath_supported) + attach_create.assert_called_once_with( + fake_client, fake_volume.id, mode=attach_mode) + attach_update.assert_called_once_with( + fake_client, fake_attachment_id, + fake_conn_info, mountpoint='glance_store') + attach_complete.assert_called_once_with( + fake_client, fake_attachment_id) + attach_delete.assert_called_once_with(fake_client, + fake_attachment_id) + else: + mock_conn.assert_called_once_with( + root_helper, fake_ip, + multipath_supported, enforce_multipath, + host=fake_host) + fake_connector.connect_volume.assert_not_called() + fake_connector.disconnect_volume.assert_not_called() + fake_conn_obj.assert_called_once_with( + mock.ANY, root_helper, conn=mock.ANY, + use_multipath=multipath_supported) + attach_create.assert_called_once_with( + fake_client, fake_volume.id, mode=attach_mode) + attach_update.assert_called_once_with( + fake_client, fake_attachment_id, + fake_conn_info, mountpoint='glance_store') + attach_delete.assert_called_once_with( + fake_client, fake_attachment_id) + + def test_open_cinder_volume_rw(self): + self._test_open_cinder_volume('wb', 'rw', None) + + def test_open_cinder_volume_ro(self): + self._test_open_cinder_volume('rb', 'ro', None) + + def test_open_cinder_volume_error(self): + self._test_open_cinder_volume('wb', 'rw', IOError) + + def test_open_cinder_volume_nfs_encrypted(self): + self._test_open_cinder_volume('rb', 'ro', None, encrypted_nfs=True) + + 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 _fake_volume_type_check(self, name): + if name != 'some_type': + raise cinder.cinder_exception.NotFound(code=404) + + def _test_configure_add_valid_type(self): + + with mock.patch.object(self.store, 'get_cinderclient') as mocked_cc: + mocked_cc.return_value = mock.MagicMock( + volume_types=mock.MagicMock( + find=self._fake_volume_type_check)) + # If volume type exists, no exception is raised + self.store.configure_add() + + def _test_configure_add_invalid_type(self): + + with mock.patch.object(self.store, 'get_cinderclient') as mocked_cc: + mocked_cc.return_value = mock.MagicMock( + volume_types=mock.MagicMock( + find=self._fake_volume_type_check)) + with mock.patch.object(cinder, 'LOG') as mock_log: + self.store.configure_add() + mock_log.warning.assert_called_with( + "Invalid `cinder_volume_type some_random_type`") + + def _get_uri_loc(self, fake_volume_uuid, is_multi_store=False): + if is_multi_store: + uri = "cinder://cinder1/%s" % fake_volume_uuid + loc = location.get_location_from_uri_and_backend( + uri, "cinder1", conf=self.conf) + else: + uri = "cinder://%s" % fake_volume_uuid + loc = location.get_location_from_uri(uri, conf=self.conf) + + return loc + + def _test_cinder_get(self, is_multi_store=False): + expected_size = 5 * units.Ki + expected_file_contents = b"*" * expected_size + volume_file = six.BytesIO(expected_file_contents) + fake_client = mock.MagicMock(auth_token=None, management_url=None) + fake_volume_uuid = str(uuid.uuid4()) + fake_volume = mock.MagicMock(id=fake_volume_uuid, + metadata={'image_size': expected_size}, + status='available') + fake_volume.manager.get.return_value = fake_volume + fake_volumes = mock.MagicMock(get=lambda id: fake_volume) + + @contextlib.contextmanager + def fake_open(client, volume, mode): + self.assertEqual('rb', mode) + yield volume_file + + with mock.patch.object(cinder.Store, 'get_cinderclient') as mock_cc, \ + mock.patch.object(self.store, '_open_cinder_volume', + side_effect=fake_open): + mock_cc.return_value = mock.MagicMock(client=fake_client, + volumes=fake_volumes) + + loc = self._get_uri_loc(fake_volume_uuid, + is_multi_store=is_multi_store) + + (image_file, image_size) = self.store.get(loc, + context=self.context) + + expected_num_chunks = 2 + data = b"" + num_chunks = 0 + + for chunk in image_file: + num_chunks += 1 + data += chunk + self.assertEqual(expected_num_chunks, num_chunks) + self.assertEqual(expected_file_contents, data) + + def _test_cinder_get_size(self, is_multi_store=False): + fake_client = mock.MagicMock(auth_token=None, management_url=None) + fake_volume_uuid = str(uuid.uuid4()) + fake_volume = mock.MagicMock(size=5, metadata={}) + fake_volumes = {fake_volume_uuid: fake_volume} + + with mock.patch.object(cinder.Store, 'get_cinderclient') as mocked_cc: + mocked_cc.return_value = mock.MagicMock(client=fake_client, + volumes=fake_volumes) + + loc = self._get_uri_loc(fake_volume_uuid, + is_multi_store=is_multi_store) + + image_size = self.store.get_size(loc, context=self.context) + self.assertEqual(fake_volume.size * units.Gi, image_size) + + def _test_cinder_get_size_with_metadata(self, is_multi_store=False): + fake_client = mock.MagicMock(auth_token=None, management_url=None) + fake_volume_uuid = str(uuid.uuid4()) + expected_image_size = 4500 * units.Mi + fake_volume = mock.MagicMock( + size=5, metadata={'image_size': expected_image_size}) + fake_volumes = {fake_volume_uuid: fake_volume} + + with mock.patch.object(cinder.Store, 'get_cinderclient') as mocked_cc: + mocked_cc.return_value = mock.MagicMock(client=fake_client, + volumes=fake_volumes) + + loc = self._get_uri_loc(fake_volume_uuid, + is_multi_store=is_multi_store) + + image_size = self.store.get_size(loc, context=self.context) + self.assertEqual(expected_image_size, image_size) diff --git a/glance_store/tests/unit/test_cinder_store.py b/glance_store/tests/unit/test_cinder_store.py index 05eb7d0..ab4f502 100644 --- a/glance_store/tests/unit/test_cinder_store.py +++ b/glance_store/tests/unit/test_cinder_store.py @@ -18,26 +18,20 @@ import errno import hashlib import io import math -import os from unittest import mock import six -import socket import sys -import tempfile import time import uuid -from os_brick.initiator import connector -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 from glance_store.tests import base +from glance_store.tests.unit import test_cinder_base from glance_store.tests.unit import test_store_capabilities sys.modules['glance_store.common.fs_mount'] = mock.Mock() @@ -45,7 +39,8 @@ from glance_store._drivers import cinder # noqa class TestCinderStore(base.StoreBaseTest, - test_store_capabilities.TestStoreCapabilitiesChecking): + test_store_capabilities.TestStoreCapabilitiesChecking, + test_cinder_base.TestCinderStoreBase): def setUp(self): super(TestCinderStore, self).setUp() @@ -65,11 +60,7 @@ class TestCinderStore(base.StoreBaseTest, project_id='fake_project') self.hash_algo = 'sha256' cinder._reset_cinder_session() - - def test_get_cinderclient(self): - cc = self.store.get_cinderclient(self.context) - self.assertEqual('fake_token', cc.client.auth.token) - self.assertEqual('http://foo/public_url', cc.client.auth.endpoint) + self.config(cinder_mount_point_base=None) def _test_get_cinderclient_with_user_overriden(self): self.config(cinder_store_user_name='test_user') @@ -89,221 +80,16 @@ class TestCinderStore(base.StoreBaseTest, cc = self._test_get_cinderclient_with_user_overriden() self.assertEqual('test_region', cc.client.region_name) - def test_temporary_chown(self): - class fake_stat(object): - st_uid = 1 - - with mock.patch.object(os, 'stat', return_value=fake_stat()), \ - mock.patch.object(os, 'getuid', return_value=2), \ - mock.patch.object(processutils, 'execute') as mock_execute, \ - mock.patch.object(cinder.Store, 'get_root_helper', - return_value='sudo'): - with self.store.temporary_chown('test'): - pass - expected_calls = [mock.call('chown', 2, 'test', run_as_root=True, - root_helper='sudo'), - mock.call('chown', 1, 'test', run_as_root=True, - root_helper='sudo')] - self.assertEqual(expected_calls, mock_execute.call_args_list) - - @mock.patch.object(time, 'sleep') - def test_wait_volume_status(self, mock_sleep): - fake_manager = mock.MagicMock(get=mock.Mock()) - volume_available = mock.MagicMock(manager=fake_manager, - id='fake-id', - status='available') - volume_in_use = mock.MagicMock(manager=fake_manager, - id='fake-id', - status='in-use') - fake_manager.get.side_effect = [volume_available, volume_in_use] - self.assertEqual(volume_in_use, - self.store._wait_volume_status( - volume_available, 'available', 'in-use')) - fake_manager.get.assert_called_with('fake-id') - mock_sleep.assert_called_once_with(0.5) - - @mock.patch.object(time, 'sleep') - def test_wait_volume_status_unexpected(self, mock_sleep): - fake_manager = mock.MagicMock(get=mock.Mock()) - volume_available = mock.MagicMock(manager=fake_manager, - id='fake-id', - status='error') - fake_manager.get.return_value = volume_available - self.assertRaises(exceptions.BackendException, - self.store._wait_volume_status, - volume_available, 'available', 'in-use') - fake_manager.get.assert_called_with('fake-id') - - @mock.patch.object(time, 'sleep') - def test_wait_volume_status_timeout(self, mock_sleep): - fake_manager = mock.MagicMock(get=mock.Mock()) - volume_available = mock.MagicMock(manager=fake_manager, - id='fake-id', - status='available') - fake_manager.get.return_value = volume_available - self.assertRaises(exceptions.BackendException, - self.store._wait_volume_status, - volume_available, 'available', 'in-use') - fake_manager.get.assert_called_with('fake-id') - - def _test_open_cinder_volume(self, open_mode, attach_mode, error, - multipath_supported=False, - enforce_multipath=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', - multiattach=multiattach) - fake_volume.manager.get.return_value = fake_volume - fake_volumes = mock.MagicMock(get=lambda id: fake_volume) - fake_attachment_id = str(uuid.uuid4()) - fake_attachment_create = {'id': fake_attachment_id} - if encrypted_nfs or qcow2_vol: - fake_attachment_update = mock.MagicMock( - id=fake_attachment_id, - connection_info={'driver_volume_type': 'nfs'}) - else: - fake_attachment_update = mock.MagicMock(id=fake_attachment_id) - fake_conn_info = mock.MagicMock(connector={}) - fake_client = mock.MagicMock(volumes=fake_volumes) - _, fake_dev_path = tempfile.mkstemp(dir=self.test_dir) - fake_devinfo = {'path': fake_dev_path} - fake_connector = mock.MagicMock( - connect_volume=mock.Mock(return_value=fake_devinfo), - disconnect_volume=mock.Mock()) - - @contextlib.contextmanager - def fake_chown(path, backend=None): - yield - - def do_open(): - 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 - - root_helper = "sudo glance-rootwrap /etc/glance/rootwrap.conf" - with mock.patch.object(cinder.Store, - '_wait_volume_status', - return_value=fake_volume), \ - mock.patch.object(cinder.Store, 'temporary_chown', - side_effect=fake_chown), \ - mock.patch.object(cinder.Store, 'get_root_helper', - return_value=root_helper), \ - mock.patch.object(connector.InitiatorConnector, 'factory', - side_effect=fake_factory - ) as fake_conn_obj, \ - mock.patch.object(cinder_utils.API, 'attachment_create', - return_value=fake_attachment_create - ) as attach_create, \ - mock.patch.object(cinder_utils.API, 'attachment_update', - return_value=fake_attachment_update - ) as attach_update, \ - mock.patch.object(cinder_utils.API, - 'attachment_delete') as attach_delete, \ - mock.patch.object(cinder_utils.API, - 'attachment_get') as attach_get, \ - mock.patch.object(cinder_utils.API, - 'attachment_complete') as attach_complete, \ - mock.patch.object(socket, - 'gethostname') as mock_get_host, \ - mock.patch.object(socket, - 'getaddrinfo') as mock_get_host_ip: - - fake_host = 'fake_host' - fake_addr_info = [[0, 1, 2, 3, ['127.0.0.1']]] - fake_ip = fake_addr_info[0][4][0] - mock_get_host.return_value = fake_host - mock_get_host_ip.return_value = fake_addr_info - - with mock.patch.object(connector, - 'get_connector_properties', - return_value=fake_conn_info) as mock_conn: - if error: - self.assertRaises(error, do_open) - elif encrypted_nfs or qcow2_vol: - fake_volume.encrypted = False - if encrypted_nfs: - fake_volume.encrypted = True - elif qcow2_vol: - attach_get.return_value = mock.MagicMock( - connection_info={'format': 'qcow2'}) - try: - with self.store._open_cinder_volume( - fake_client, fake_volume, open_mode): - pass - except exceptions.BackendException: - attach_delete.assert_called_once_with( - fake_client, fake_attachment_id) - else: - do_open() - - if not (encrypted_nfs or qcow2_vol): - mock_conn.assert_called_once_with( - root_helper, fake_ip, - multipath_supported, enforce_multipath, - host=fake_host) - fake_connector.connect_volume.assert_called_once_with( - mock.ANY) - fake_connector.disconnect_volume.assert_called_once_with( - mock.ANY, fake_devinfo) - fake_conn_obj.assert_called_once_with( - mock.ANY, root_helper, conn=mock.ANY, - use_multipath=multipath_supported) - attach_create.assert_called_once_with( - fake_client, fake_volume.id, mode=attach_mode) - attach_update.assert_called_once_with( - fake_client, fake_attachment_id, - fake_conn_info, mountpoint='glance_store') - attach_complete.assert_called_once_with( - fake_client, fake_attachment_id) - attach_delete.assert_called_once_with( - fake_client, fake_attachment_id) - else: - mock_conn.assert_called_once_with( - root_helper, fake_ip, - multipath_supported, enforce_multipath, - host=fake_host) - fake_connector.connect_volume.assert_not_called() - fake_connector.disconnect_volume.assert_not_called() - fake_conn_obj.assert_called_once_with( - mock.ANY, root_helper, conn=mock.ANY, - use_multipath=multipath_supported) - attach_create.assert_called_once_with( - fake_client, fake_volume.id, mode=attach_mode) - attach_update.assert_called_once_with( - fake_client, fake_attachment_id, - fake_conn_info, mountpoint='glance_store') - attach_delete.assert_called_once_with( - fake_client, fake_attachment_id) - - def test_open_cinder_volume_rw(self): - self._test_open_cinder_volume('wb', 'rw', None) - - def test_open_cinder_volume_ro(self): - self._test_open_cinder_volume('rb', 'ro', None) - - def test_open_cinder_volume_error(self): - self._test_open_cinder_volume('wb', 'rw', IOError) - - def test_open_cinder_volume_multipath_supported(self): + def test_open_cinder_volume_multipath_enabled(self): self.config(cinder_use_multipath=True) self._test_open_cinder_volume('wb', 'rw', None, multipath_supported=True) + def test_open_cinder_volume_multipath_disabled(self): + self.config(cinder_use_multipath=False) + 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) self.config(cinder_enforce_multipath=True) @@ -311,15 +97,6 @@ class TestCinderStore(base.StoreBaseTest, multipath_supported=True, enforce_multipath=True) - def test_open_cinder_volume_nfs_encrypted(self): - self._test_open_cinder_volume('rb', 'ro', None, encrypted_nfs=True) - - 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) @@ -331,73 +108,13 @@ class TestCinderStore(base.StoreBaseTest, self.store._check_context(mock.MagicMock(service_catalog='fake')) def test_cinder_get(self): - expected_size = 5 * units.Ki - expected_file_contents = b"*" * expected_size - volume_file = six.BytesIO(expected_file_contents) - fake_client = mock.MagicMock(auth_token=None, management_url=None) - fake_volume_uuid = str(uuid.uuid4()) - fake_volume = mock.MagicMock(id=fake_volume_uuid, - metadata={'image_size': expected_size}, - status='available') - fake_volume.manager.get.return_value = fake_volume - fake_volumes = mock.MagicMock(get=lambda id: fake_volume) - - @contextlib.contextmanager - def fake_open(client, volume, mode): - self.assertEqual('rb', mode) - yield volume_file - - with mock.patch.object(cinder.Store, 'get_cinderclient') as mock_cc, \ - mock.patch.object(self.store, '_open_cinder_volume', - side_effect=fake_open): - mock_cc.return_value = mock.MagicMock(client=fake_client, - volumes=fake_volumes) - uri = "cinder://%s" % fake_volume_uuid - loc = location.get_location_from_uri(uri, conf=self.conf) - (image_file, image_size) = self.store.get(loc, - context=self.context) - - expected_num_chunks = 2 - data = b"" - num_chunks = 0 - - for chunk in image_file: - num_chunks += 1 - data += chunk - self.assertEqual(expected_num_chunks, num_chunks) - self.assertEqual(expected_file_contents, data) + self._test_cinder_get() def test_cinder_get_size(self): - fake_client = mock.MagicMock(auth_token=None, management_url=None) - fake_volume_uuid = str(uuid.uuid4()) - fake_volume = mock.MagicMock(size=5, metadata={}) - fake_volumes = {fake_volume_uuid: fake_volume} - - with mock.patch.object(cinder.Store, 'get_cinderclient') as mocked_cc: - mocked_cc.return_value = mock.MagicMock(client=fake_client, - volumes=fake_volumes) - - uri = 'cinder://%s' % fake_volume_uuid - loc = location.get_location_from_uri(uri, conf=self.conf) - image_size = self.store.get_size(loc, context=self.context) - self.assertEqual(fake_volume.size * units.Gi, image_size) + self._test_cinder_get_size() def test_cinder_get_size_with_metadata(self): - fake_client = mock.MagicMock(auth_token=None, management_url=None) - fake_volume_uuid = str(uuid.uuid4()) - expected_image_size = 4500 * units.Mi - fake_volume = mock.MagicMock( - size=5, metadata={'image_size': expected_image_size}) - fake_volumes = {fake_volume_uuid: fake_volume} - - with mock.patch.object(cinder.Store, 'get_cinderclient') as mocked_cc: - mocked_cc.return_value = mock.MagicMock(client=fake_client, - volumes=fake_volumes) - - uri = 'cinder://%s' % fake_volume_uuid - loc = location.get_location_from_uri(uri, conf=self.conf) - image_size = self.store.get_size(loc, context=self.context) - self.assertEqual(expected_image_size, image_size) + self._test_cinder_get_size_with_metadata() def _test_cinder_add(self, fake_volume, volume_file, size_kb=5, verifier=None, fail_resize=False): @@ -489,26 +206,15 @@ class TestCinderStore(base.StoreBaseTest, def test_set_url_prefix(self): self.assertEqual('cinder://', self.store._url_prefix) - def test_configure_add(self): - - def fake_volume_type(name): - if name != 'some_type': - raise cinder.cinder_exception.NotFound(code=404) - - with mock.patch.object(self.store, 'get_cinderclient') as mocked_cc: - mocked_cc.return_value = mock.MagicMock( - volume_types=mock.MagicMock( - find=fake_volume_type)) - self.config(cinder_volume_type='some_type') - # If volume type exists, no exception is raised - self.store.configure_add() - # setting cinder_volume_type to non-existent value will log a - # warning - self.config(cinder_volume_type='some_random_type') - with mock.patch.object(cinder, 'LOG') as mock_log: - self.store.configure_add() - mock_log.warning.assert_called_with( - "Invalid `cinder_volume_type some_random_type`") + def test_configure_add_valid_type(self): + self.config(cinder_volume_type='some_type') + self._test_configure_add_valid_type() + + def test_configure_add_invalid_type(self): + # setting cinder_volume_type to non-existent value will log a + # warning + self.config(cinder_volume_type='some_random_type') + self._test_configure_add_invalid_type() def test__get_device_size(self): fake_data = b"fake binary data" diff --git a/glance_store/tests/unit/test_multistore_cinder.py b/glance_store/tests/unit/test_multistore_cinder.py index 0c7bbfa..82bf7a2 100644 --- a/glance_store/tests/unit/test_multistore_cinder.py +++ b/glance_store/tests/unit/test_multistore_cinder.py @@ -17,29 +17,23 @@ import contextlib import errno import io import math -import os from unittest import mock import six -import socket import sys -import tempfile import time import uuid import fixtures -from os_brick.initiator import connector -from oslo_concurrency import processutils from oslo_config import cfg 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 from glance_store.tests import base +from glance_store.tests.unit import test_cinder_base from glance_store.tests.unit import test_store_capabilities as test_cap sys.modules['glance_store.common.fs_mount'] = mock.Mock() @@ -47,7 +41,8 @@ from glance_store._drivers import cinder # noqa class TestMultiCinderStore(base.MultiStoreBaseTest, - test_cap.TestStoreCapabilitiesChecking): + test_cap.TestStoreCapabilitiesChecking, + test_cinder_base.TestCinderStoreBase): # NOTE(flaper87): temporary until we # can move to a fully-local lib. @@ -97,15 +92,11 @@ class TestMultiCinderStore(base.MultiStoreBaseTest, auth_token='admin_token', project_id='admin_project') cinder._reset_cinder_session() + self.config(cinder_mount_point_base=None, group='cinder1') def test_location_url_prefix_is_set(self): self.assertEqual("cinder://cinder1", self.store.url_prefix) - def test_get_cinderclient(self): - cc = self.store.get_cinderclient(self.context) - self.assertEqual('fake_token', cc.client.auth.token) - self.assertEqual('http://foo/public_url', cc.client.auth.endpoint) - def test_get_cinderclient_with_user_overriden(self): self.config(cinder_store_user_name='test_user', group="cinder1") self.config(cinder_store_password='test_password', group="cinder1") @@ -121,216 +112,10 @@ class TestMultiCinderStore(base.MultiStoreBaseTest, self.assertEqual('admin_token', cc.client.auth.token) self.assertEqual('http://foo/public_url', cc.client.auth.endpoint) - def test_temporary_chown(self): - class fake_stat(object): - st_uid = 1 - - with mock.patch.object(os, 'stat', return_value=fake_stat()), \ - mock.patch.object(os, 'getuid', return_value=2), \ - mock.patch.object(processutils, 'execute') as mock_execute, \ - mock.patch.object(cinder.Store, 'get_root_helper', - return_value='sudo'): - with self.store.temporary_chown('test'): - pass - expected_calls = [mock.call('chown', 2, 'test', run_as_root=True, - root_helper='sudo'), - mock.call('chown', 1, 'test', run_as_root=True, - root_helper='sudo')] - self.assertEqual(expected_calls, mock_execute.call_args_list) - - @mock.patch.object(time, 'sleep') - def test_wait_volume_status(self, mock_sleep): - fake_manager = mock.MagicMock(get=mock.Mock()) - volume_available = mock.MagicMock(manager=fake_manager, - id='fake-id', - status='available') - volume_in_use = mock.MagicMock(manager=fake_manager, - id='fake-id', - status='in-use') - fake_manager.get.side_effect = [volume_available, volume_in_use] - self.assertEqual(volume_in_use, - self.store._wait_volume_status( - volume_available, 'available', 'in-use')) - fake_manager.get.assert_called_with('fake-id') - mock_sleep.assert_called_once_with(0.5) - - @mock.patch.object(time, 'sleep') - def test_wait_volume_status_unexpected(self, mock_sleep): - fake_manager = mock.MagicMock(get=mock.Mock()) - volume_available = mock.MagicMock(manager=fake_manager, - id='fake-id', - status='error') - fake_manager.get.return_value = volume_available - self.assertRaises(exceptions.BackendException, - self.store._wait_volume_status, - volume_available, 'available', 'in-use') - fake_manager.get.assert_called_with('fake-id') - - @mock.patch.object(time, 'sleep') - def test_wait_volume_status_timeout(self, mock_sleep): - fake_manager = mock.MagicMock(get=mock.Mock()) - volume_available = mock.MagicMock(manager=fake_manager, - id='fake-id', - status='available') - fake_manager.get.return_value = volume_available - self.assertRaises(exceptions.BackendException, - self.store._wait_volume_status, - volume_available, 'available', 'in-use') - fake_manager.get.assert_called_with('fake-id') - - def _test_open_cinder_volume(self, open_mode, attach_mode, error, - multipath_supported=False, - enforce_multipath=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', - multiattach=multiattach) - fake_volume.manager.get.return_value = fake_volume - fake_attachment_id = str(uuid.uuid4()) - fake_attachment_create = {'id': fake_attachment_id} - if encrypted_nfs or qcow2_vol: - fake_attachment_update = mock.MagicMock( - id=fake_attachment_id, - connection_info={'driver_volume_type': 'nfs'}) - else: - fake_attachment_update = mock.MagicMock(id=fake_attachment_id) - fake_conn_info = mock.MagicMock(connector={}) - fake_volumes = mock.MagicMock(get=lambda id: fake_volume) - fake_client = mock.MagicMock(volumes=fake_volumes) - _, fake_dev_path = tempfile.mkstemp(dir=self.test_dir) - fake_devinfo = {'path': fake_dev_path} - fake_connector = mock.MagicMock( - connect_volume=mock.Mock(return_value=fake_devinfo), - disconnect_volume=mock.Mock()) - - @contextlib.contextmanager - def fake_chown(path, backend=None): - yield - - def do_open(): - 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 - - root_helper = "sudo glance-rootwrap /etc/glance/rootwrap.conf" - with mock.patch.object(cinder.Store, - '_wait_volume_status', - return_value=fake_volume), \ - mock.patch.object(cinder.Store, 'temporary_chown', - side_effect=fake_chown), \ - mock.patch.object(cinder.Store, 'get_root_helper', - return_value=root_helper), \ - mock.patch.object(connector.InitiatorConnector, 'factory', - side_effect=fake_factory - ) as fake_conn_obj, \ - mock.patch.object(cinder_utils.API, - 'attachment_create', - return_value=fake_attachment_create - ) as attach_create, \ - mock.patch.object(cinder_utils.API, - 'attachment_update', - return_value=fake_attachment_update - ) as attach_update, \ - mock.patch.object(cinder_utils.API, - 'attachment_delete') as attach_delete, \ - mock.patch.object(cinder_utils.API, - 'attachment_get') as attach_get, \ - mock.patch.object(cinder_utils.API, - 'attachment_complete') as attach_complete, \ - mock.patch.object(socket, - 'gethostname') as mock_get_host, \ - mock.patch.object(socket, - 'getaddrinfo') as mock_get_host_ip: - - fake_host = 'fake_host' - fake_addr_info = [[0, 1, 2, 3, ['127.0.0.1']]] - fake_ip = fake_addr_info[0][4][0] - mock_get_host.return_value = fake_host - mock_get_host_ip.return_value = fake_addr_info - - with mock.patch.object(connector, - 'get_connector_properties', - return_value=fake_conn_info) as mock_conn: - if error: - self.assertRaises(error, do_open) - elif encrypted_nfs or qcow2_vol: - fake_volume.encrypted = False - if encrypted_nfs: - fake_volume.encrypted = True - elif qcow2_vol: - attach_get.return_value = mock.MagicMock( - connection_info={'format': 'qcow2'}) - try: - with self.store._open_cinder_volume( - fake_client, fake_volume, open_mode): - pass - except exceptions.BackendException: - attach_delete.assert_called_once_with( - fake_client, fake_attachment_id) - else: - do_open() - if not (encrypted_nfs or qcow2_vol): - mock_conn.assert_called_once_with( - root_helper, fake_ip, - multipath_supported, enforce_multipath, - host=fake_host) - fake_connector.connect_volume.assert_called_once_with( - mock.ANY) - fake_connector.disconnect_volume.assert_called_once_with( - mock.ANY, fake_devinfo) - fake_conn_obj.assert_called_once_with( - mock.ANY, root_helper, conn=mock.ANY, - use_multipath=multipath_supported) - attach_create.assert_called_once_with( - fake_client, fake_volume.id, mode=attach_mode) - attach_update.assert_called_once_with( - fake_client, fake_attachment_id, - fake_conn_info, mountpoint='glance_store') - attach_complete.assert_called_once_with( - fake_client, fake_attachment_id) - attach_delete.assert_called_once_with(fake_client, - fake_attachment_id) - else: - mock_conn.assert_called_once_with( - root_helper, fake_ip, - multipath_supported, enforce_multipath, - host=fake_host) - fake_connector.connect_volume.assert_not_called() - fake_connector.disconnect_volume.assert_not_called() - fake_conn_obj.assert_called_once_with( - mock.ANY, root_helper, conn=mock.ANY, - use_multipath=multipath_supported) - attach_create.assert_called_once_with( - fake_client, fake_volume.id, mode=attach_mode) - attach_update.assert_called_once_with( - fake_client, fake_attachment_id, - fake_conn_info, mountpoint='glance_store') - attach_delete.assert_called_once_with( - fake_client, fake_attachment_id) - - def test_open_cinder_volume_rw(self): - self._test_open_cinder_volume('wb', 'rw', None) - - def test_open_cinder_volume_ro(self): - self._test_open_cinder_volume('rb', 'ro', None) - - def test_open_cinder_volume_error(self): - self._test_open_cinder_volume('wb', 'rw', IOError) + def test_open_cinder_volume_multipath_enabled(self): + self.config(cinder_use_multipath=True, group='cinder1') + self._test_open_cinder_volume('wb', 'rw', None, + multipath_supported=True) def test_open_cinder_volume_multipath_disabled(self): self.config(cinder_use_multipath=False, group='cinder1') @@ -338,22 +123,12 @@ class TestMultiCinderStore(base.MultiStoreBaseTest, 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_open_cinder_volume_nfs_encrypted(self): - self._test_open_cinder_volume('rb', 'ro', None, encrypted_nfs=True) - - 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) @@ -364,28 +139,17 @@ class TestMultiCinderStore(base.MultiStoreBaseTest, self.store._check_context(mock.MagicMock(service_catalog='fake')) - def test_configure_add(self): - - def fake_volume_type_check(name): - if name != 'some_type': - raise cinder.cinder_exception.NotFound(code=404) + def test_configure_add_valid_type(self): + self.config(cinder_volume_type='some_type', + group=self.store.backend_group) + self._test_configure_add_valid_type() - with mock.patch.object(self.store, 'get_cinderclient') as mocked_cc: - mocked_cc.return_value = mock.MagicMock( - volume_types=mock.MagicMock( - find=fake_volume_type_check)) - self.config(cinder_volume_type='some_type', - group=self.store.backend_group) - # If volume type exists, no exception is raised - self.store.configure_add() - # setting cinder_volume_type to non-existent value will log a - # warning - self.config(cinder_volume_type='some_random_type', - group=self.store.backend_group) - with mock.patch.object(cinder, 'LOG') as mock_log: - self.store.configure_add() - mock_log.warning.assert_called_with( - "Invalid `cinder_volume_type some_random_type`") + def test_configure_add_invalid_type(self): + # setting cinder_volume_type to non-existent value will log a + # warning + self.config(cinder_volume_type='some_random_type', + group=self.store.backend_group) + self._test_configure_add_invalid_type() def test_configure_add_cinder_service_down(self): @@ -454,79 +218,13 @@ class TestMultiCinderStore(base.MultiStoreBaseTest, self.assertFalse(type_match) def test_cinder_get(self): - expected_size = 5 * units.Ki - expected_file_contents = b"*" * expected_size - volume_file = six.BytesIO(expected_file_contents) - fake_client = mock.MagicMock(auth_token=None, management_url=None) - fake_volume_uuid = str(uuid.uuid4()) - fake_volume = mock.MagicMock(id=fake_volume_uuid, - metadata={'image_size': expected_size}, - status='available') - fake_volume.manager.get.return_value = fake_volume - fake_volumes = mock.MagicMock(get=lambda id: fake_volume) - - @contextlib.contextmanager - def fake_open(client, volume, mode): - self.assertEqual('rb', mode) - yield volume_file - - with mock.patch.object(cinder.Store, 'get_cinderclient') as mock_cc, \ - mock.patch.object(self.store, '_open_cinder_volume', - side_effect=fake_open): - mock_cc.return_value = mock.MagicMock(client=fake_client, - volumes=fake_volumes) - uri = "cinder://cinder1/%s" % fake_volume_uuid - loc = location.get_location_from_uri_and_backend(uri, - "cinder1", - conf=self.conf) - (image_file, image_size) = self.store.get(loc, - context=self.context) - - expected_num_chunks = 2 - data = b"" - num_chunks = 0 - - for chunk in image_file: - num_chunks += 1 - data += chunk - self.assertEqual(expected_num_chunks, num_chunks) - self.assertEqual(expected_file_contents, data) + self._test_cinder_get(is_multi_store=True) def test_cinder_get_size(self): - fake_client = mock.MagicMock(auth_token=None, management_url=None) - fake_volume_uuid = str(uuid.uuid4()) - fake_volume = mock.MagicMock(size=5, metadata={}) - fake_volumes = {fake_volume_uuid: fake_volume} - - with mock.patch.object(cinder.Store, 'get_cinderclient') as mocked_cc: - mocked_cc.return_value = mock.MagicMock(client=fake_client, - volumes=fake_volumes) - - uri = 'cinder://cinder1/%s' % fake_volume_uuid - loc = location.get_location_from_uri_and_backend(uri, - "cinder1", - conf=self.conf) - image_size = self.store.get_size(loc, context=self.context) - self.assertEqual(fake_volume.size * units.Gi, image_size) + self._test_cinder_get_size(is_multi_store=True) def test_cinder_get_size_with_metadata(self): - fake_client = mock.MagicMock(auth_token=None, management_url=None) - fake_volume_uuid = str(uuid.uuid4()) - expected_image_size = 4500 * units.Mi - fake_volume = mock.MagicMock( - size=5, metadata={'image_size': expected_image_size}) - fake_volumes = {fake_volume_uuid: fake_volume} - - with mock.patch.object(cinder.Store, 'get_cinderclient') as mocked_cc: - mocked_cc.return_value = mock.MagicMock(client=fake_client, - volumes=fake_volumes) - - uri = 'cinder://cinder1/%s' % fake_volume_uuid - loc = location.get_location_from_uri_and_backend(uri, - "cinder1", - conf=self.conf) - image_size = self.store.get_size(loc, context=self.context) - self.assertEqual(expected_image_size, image_size) + self._test_cinder_get_size_with_metadata(is_multi_store=True) def _test_cinder_add(self, fake_volume, volume_file, size_kb=5, verifier=None, backend="cinder1", fail_resize=False): |