Replace cinderclient usage with openstacksdk

Change-Id: Ib4a533584da85281d425fdbffa12a52d4838e185
Closes-Bug: #2042494
This commit is contained in:
Steve Baker 2024-03-25 10:20:59 +13:00
parent d68aea2ebe
commit 11ece80ad8
5 changed files with 323 additions and 402 deletions

View File

@ -15,8 +15,8 @@
import datetime import datetime
import json import json
from cinderclient import exceptions as cinder_exceptions import openstack
from cinderclient.v3 import client from openstack.connection import exceptions as openstack_exc
from oslo_log import log from oslo_log import log
from ironic.common import context as ironic_context from ironic.common import context as ironic_context
@ -40,8 +40,8 @@ def _get_cinder_session():
return _CINDER_SESSION return _CINDER_SESSION
def get_client(context, auth_from_config=False): def get_client(context=None, auth_from_config=False):
"""Get a cinder client connection. """Retrieve a cinder client connection.
:param context: request context, :param context: request context,
instance of ironic.common.context.RequestContext instance of ironic.common.context.RequestContext
@ -49,6 +49,7 @@ def get_client(context, auth_from_config=False):
conf parameters conf parameters
:returns: A cinder client. :returns: A cinder client.
""" """
service_auth = keystone.get_auth('cinder') service_auth = keystone.get_auth('cinder')
session = _get_cinder_session() session = _get_cinder_session()
# Used the service cached session to get the endpoint # Used the service cached session to get the endpoint
@ -93,16 +94,12 @@ def get_client(context, auth_from_config=False):
endpoint = keystone.get_endpoint('cinder', session=sess, endpoint = keystone.get_endpoint('cinder', session=sess,
auth=user_auth) auth=user_auth)
# NOTE(pas-ha) cinderclient has both 'connect_retries' (passed to conn = openstack.connection.Connection(
# ksa.Adapter) and 'retries' (used in its subclass of ksa.Adapter) options. session=sess,
# The first governs retries on establishing the HTTP connection, block_storage_endpoint_override=endpoint,
# the second governs retries on OverLimit exceptions from API. block_storage_api_version='3')
# The description of [cinder]/retries fits the first,
# so this is what we pass. return conn.global_request(context.global_id).block_storage
return client.Client(session=sess, auth=user_auth,
endpoint_override=endpoint,
connect_retries=CONF.cinder.retries,
global_request_id=context.global_id)
def is_volume_available(volume): def is_volume_available(volume):
@ -114,7 +111,7 @@ def is_volume_available(volume):
""" """
return (volume.status == AVAILABLE return (volume.status == AVAILABLE
or (volume.status == IN_USE or (volume.status == IN_USE
and volume.multiattach)) and volume.is_multiattach))
def is_volume_attached(node, volume): def is_volume_attached(node, volume):
@ -172,28 +169,6 @@ def _create_metadata_dictionary(node, action):
return {label: json.dumps(data)} return {label: json.dumps(data)}
def _init_client(task, auth_from_config=False):
"""Obtain cinder client and return it for use.
:param task: TaskManager instance representing the operation.
:param auth_from_config: If we should source our authentication parameters
from the configured service as opposed to request
context.
:returns: A cinder client.
:raises: StorageError If an exception is encountered creating the client.
"""
node = task.node
try:
return get_client(task.context,
auth_from_config=auth_from_config)
except Exception as e:
msg = (_('Failed to initialize cinder client for operations on node '
'%(uuid)s: %(err)s') % {'uuid': node.uuid, 'err': e})
LOG.error(msg)
raise exception.StorageError(msg)
def attach_volumes(task, volume_list, connector): def attach_volumes(task, volume_list, connector):
"""Attach volumes to a node. """Attach volumes to a node.
@ -282,12 +257,17 @@ def attach_volumes(task, volume_list, connector):
node = task.node node = task.node
LOG.debug('Initializing volume attach for node %(node)s.', LOG.debug('Initializing volume attach for node %(node)s.',
{'node': node.uuid}) {'node': node.uuid})
client = _init_client(task) try:
block_storage = get_client(context=task.context)
except openstack_exc.SDKException as e:
msg = _('Failed to connect to block storage service '
': %(err)s') % {'err': e}
raise exception.StorageError(msg)
connected = [] connected = []
for volume_id in volume_list: for volume_id in volume_list:
try: try:
volume = client.volumes.get(volume_id) volume = block_storage.get_volume(volume_id)
except cinder_exceptions.ClientException as e: except openstack_exc.SDKException as e:
msg = (_('Failed to get volume %(vol_id)s from cinder for node ' msg = (_('Failed to get volume %(vol_id)s from cinder for node '
'%(uuid)s: %(err)s') % '%(uuid)s: %(err)s') %
{'vol_id': volume_id, 'uuid': node.uuid, 'err': e}) {'vol_id': volume_id, 'uuid': node.uuid, 'err': e})
@ -308,8 +288,8 @@ def attach_volumes(task, volume_list, connector):
continue continue
try: try:
client.volumes.reserve(volume_id) block_storage.reserve_volume(volume)
except cinder_exceptions.ClientException as e: except openstack_exc.SDKException as e:
msg = (_('Failed to reserve volume %(vol_id)s for node %(node)s: ' msg = (_('Failed to reserve volume %(vol_id)s for node %(node)s: '
'%(err)s)') % '%(err)s)') %
{'vol_id': volume_id, 'node': node.uuid, 'err': e}) {'vol_id': volume_id, 'node': node.uuid, 'err': e})
@ -318,9 +298,9 @@ def attach_volumes(task, volume_list, connector):
try: try:
# Provide connector information to cinder # Provide connector information to cinder
connection = client.volumes.initialize_connection(volume_id, connection = block_storage.init_volume_attachment(
connector) volume, connector)
except cinder_exceptions.ClientException as e: except openstack_exc.SDKException as e:
msg = (_('Failed to initialize connection for volume ' msg = (_('Failed to initialize connection for volume '
'%(vol_id)s to node %(node)s: %(err)s') % '%(vol_id)s to node %(node)s: %(err)s') %
{'vol_id': volume_id, 'node': node.uuid, 'err': e}) {'vol_id': volume_id, 'node': node.uuid, 'err': e})
@ -344,11 +324,11 @@ def attach_volumes(task, volume_list, connector):
# been completed, which moves the volume to the # been completed, which moves the volume to the
# 'attached' state. This action also sets a mountpoint # 'attached' state. This action also sets a mountpoint
# for the volume, as cinder requires a mointpoint to # for the volume, as cinder requires a mointpoint to
# attach the volume, thus we send 'mount_volume'. # attach the volume, thus we send 'ironic_mountpoint'.
client.volumes.attach(volume_id, instance_uuid, block_storage.attach_volume(
'ironic_mountpoint') volume, 'ironic_mountpoint', instance=instance_uuid)
except cinder_exceptions.ClientException as e: except openstack_exc.SDKException as e:
msg = (_('Failed to inform cinder that the attachment for volume ' msg = (_('Failed to inform cinder that the attachment for volume '
'%(vol_id)s for node %(node)s has been completed: ' '%(vol_id)s for node %(node)s has been completed: '
'%(err)s') % '%(err)s') %
@ -358,11 +338,10 @@ def attach_volumes(task, volume_list, connector):
try: try:
# Set metadata to assist a user in volume identification # Set metadata to assist a user in volume identification
client.volumes.set_metadata( block_storage.set_volume_metadata(
volume_id, volume, **_create_metadata_dictionary(node, 'attached'))
_create_metadata_dictionary(node, 'attached'))
except cinder_exceptions.ClientException as e: except openstack_exc.SDKException as e:
LOG.warning('Failed to update volume metadata for volume ' LOG.warning('Failed to update volume metadata for volume '
'%(vol_id)s for node %(node)s: %(err)s', '%(vol_id)s for node %(node)s: %(err)s',
{'vol_id': volume_id, 'node': node.uuid, 'err': e}) {'vol_id': volume_id, 'node': node.uuid, 'err': e})
@ -410,15 +389,20 @@ def detach_volumes(task, volume_list, connector, allow_errors=False):
LOG.error(msg) LOG.error(msg)
raise exception.StorageError(msg) raise exception.StorageError(msg)
client = _init_client(task, auth_from_config=False) try:
block_storage = get_client(context=task.context)
except openstack_exc.SDKException as e:
msg = _('Failed to connect to block storage service '
': %(err)s') % {'err': e}
raise exception.StorageError(msg)
node = task.node node = task.node
LOG.debug('Initializing volume detach for node %(node)s.', LOG.debug('Initializing volume detach for node %(node)s.',
{'node': node.uuid}) {'node': node.uuid})
for volume_id in volume_list: for volume_id in volume_list:
try: try:
volume = client.volumes.get(volume_id) volume = block_storage.get_volume(volume_id)
except cinder_exceptions.ClientException as e: except openstack_exc.SDKException as e:
_handle_errors(_('Failed to get volume %(vol_id)s from cinder for ' _handle_errors(_('Failed to get volume %(vol_id)s from cinder for '
'node %(node)s: %(err)s') % 'node %(node)s: %(err)s') %
{'vol_id': volume_id, 'node': node.uuid, 'err': e}) {'vol_id': volume_id, 'node': node.uuid, 'err': e})
@ -434,8 +418,8 @@ def detach_volumes(task, volume_list, connector, allow_errors=False):
continue continue
try: try:
client.volumes.begin_detaching(volume_id) block_storage.begin_volume_detaching(volume)
except cinder_exceptions.ClientException as e: except openstack_exc.SDKException as e:
_handle_errors(_('Failed to request detach for volume %(vol_id)s ' _handle_errors(_('Failed to request detach for volume %(vol_id)s '
'from cinder for node %(node)s: %(err)s') % 'from cinder for node %(node)s: %(err)s') %
{'vol_id': volume_id, 'node': node.uuid, 'err': e} {'vol_id': volume_id, 'node': node.uuid, 'err': e}
@ -445,8 +429,8 @@ def detach_volumes(task, volume_list, connector, allow_errors=False):
# is set to True. # is set to True.
try: try:
# Remove the attachment # Remove the attachment
client.volumes.terminate_connection(volume_id, connector) block_storage.terminate_volume_attachment(volume, connector)
except cinder_exceptions.ClientException as e: except openstack_exc.SDKException as e:
_handle_errors(_('Failed to detach volume %(vol_id)s from node ' _handle_errors(_('Failed to detach volume %(vol_id)s from node '
'%(node)s: %(err)s') % '%(node)s: %(err)s') %
{'vol_id': volume_id, 'node': node.uuid, 'err': e}) {'vol_id': volume_id, 'node': node.uuid, 'err': e})
@ -462,8 +446,8 @@ def detach_volumes(task, volume_list, connector, allow_errors=False):
attachment_id = _get_attachment_id(node, volume) attachment_id = _get_attachment_id(node, volume)
try: try:
# Update the API attachment record # Update the API attachment record
client.volumes.detach(volume_id, attachment_id) block_storage.detach_volume(volume, attachment_id)
except cinder_exceptions.ClientException as e: except openstack_exc.SDKException as e:
_handle_errors(_('Failed to inform cinder that the detachment for ' _handle_errors(_('Failed to inform cinder that the detachment for '
'volume %(vol_id)s from node %(node)s has been ' 'volume %(vol_id)s from node %(node)s has been '
'completed: %(err)s') % 'completed: %(err)s') %
@ -473,10 +457,10 @@ def detach_volumes(task, volume_list, connector, allow_errors=False):
# is set to True. # is set to True.
try: try:
# Set metadata to assist in volume identification. # Set metadata to assist in volume identification.
client.volumes.set_metadata( block_storage.set_volume_metadata(
volume_id, volume,
_create_metadata_dictionary(node, 'detached')) **_create_metadata_dictionary(node, 'detached'))
except cinder_exceptions.ClientException as e: except openstack_exc.SDKException as e:
LOG.warning('Failed to update volume %(vol_id)s metadata for node ' LOG.warning('Failed to update volume %(vol_id)s metadata for node '
'%(node)s: %(err)s', '%(node)s: %(err)s',
{'vol_id': volume_id, 'node': node.uuid, 'err': e}) {'vol_id': volume_id, 'node': node.uuid, 'err': e})

View File

@ -19,8 +19,11 @@ from ironic.conf import auth
opts = [ opts = [
cfg.IntOpt('retries', cfg.IntOpt('retries',
default=3, default=3,
help=_('Client retries in the case of a failed request ' deprecated_for_removal=True,
'connection.')), deprecated_reason=_('Replaced by status_code_retries and '
'status_code_retry_delay.'),
help=_('DEPRECATED: Client retries in the case of a failed '
'request.')),
cfg.IntOpt('action_retries', cfg.IntOpt('action_retries',
default=3, default=3,
help=_('Number of retries in the case of a failed ' help=_('Number of retries in the case of a failed '
@ -33,8 +36,6 @@ opts = [
] ]
# NOTE(pas-ha) cinder V3 which ironic requires is registered as volumev3
# service type ATM
def register_opts(conf): def register_opts(conf):
conf.register_opts(opts, group='cinder') conf.register_opts(opts, group='cinder')
auth.register_auth_opts(conf, 'cinder', service_type='volumev3') auth.register_auth_opts(conf, 'cinder', service_type='volumev3')

View File

@ -12,122 +12,83 @@
# under the License. # under the License.
import datetime import datetime
from http import client as http_client
import json import json
from unittest import mock from unittest import mock
from cinderclient import exceptions as cinder_exceptions from keystoneauth1 import loading as ks_loading
import cinderclient.v3 as cinderclient import openstack
from openstack.connection import exceptions as openstack_exc
from oslo_utils import uuidutils from oslo_utils import uuidutils
from ironic.common import cinder from ironic.common import cinder
from ironic.common import context from ironic.common import context
from ironic.common import exception from ironic.common import exception
from ironic.common import keystone
from ironic.conductor import task_manager from ironic.conductor import task_manager
from ironic.tests import base from ironic.tests import base
from ironic.tests.unit.db import base as db_base from ironic.tests.unit.db import base as db_base
from ironic.tests.unit.objects import utils as object_utils from ironic.tests.unit.objects import utils as object_utils
@mock.patch.object(keystone, 'get_auth', autospec=True)
@mock.patch.object(keystone, 'get_session', autospec=True)
class TestCinderSession(base.TestCase):
def setUp(self):
super(TestCinderSession, self).setUp()
self.config(timeout=1,
retries=2,
group='cinder')
cinder._CINDER_SESSION = None
def test__get_cinder_session(self, mock_keystone_session, mock_auth):
"""Check establishing new session when no session exists."""
mock_keystone_session.return_value = 'session1'
self.assertEqual('session1', cinder._get_cinder_session())
mock_keystone_session.assert_called_once_with('cinder')
"""Check if existing session is used."""
mock_keystone_session.reset_mock()
mock_keystone_session.return_value = 'session2'
self.assertEqual('session1', cinder._get_cinder_session())
self.assertFalse(mock_keystone_session.called)
self.assertFalse(mock_auth.called)
@mock.patch('ironic.common.keystone.get_adapter', autospec=True)
@mock.patch('ironic.common.keystone.get_service_auth', autospec=True, @mock.patch('ironic.common.keystone.get_service_auth', autospec=True,
return_value=mock.sentinel.sauth) return_value=mock.sentinel.sauth)
@mock.patch('ironic.common.keystone.get_auth', autospec=True) @mock.patch('ironic.common.keystone.get_auth', autospec=True,
return_value=mock.sentinel.auth)
@mock.patch('ironic.common.keystone.get_adapter', autospec=True)
@mock.patch('ironic.common.keystone.get_session', autospec=True, @mock.patch('ironic.common.keystone.get_session', autospec=True,
return_value=mock.sentinel.session) return_value=mock.sentinel.session)
@mock.patch.object(cinderclient.Client, '__init__', autospec=True, @mock.patch.object(openstack.connection, "Connection", autospec=True)
return_value=None)
class TestCinderClient(base.TestCase): class TestCinderClient(base.TestCase):
def setUp(self): def setUp(self):
super(TestCinderClient, self).setUp() super(TestCinderClient, self).setUp()
self.config(timeout=1, # NOTE(pas-ha) register keystoneauth dynamic options manually
retries=2, plugin = ks_loading.get_plugin_loader('password')
opts = ks_loading.get_auth_plugin_conf_options(plugin)
self.cfg_fixture.register_opts(opts, group='cinder')
self.config(retries=2,
group='cinder') group='cinder')
self.config(username='test-admin-user',
project_name='test-admin-tenant',
password='test-admin-password',
auth_url='test-auth-uri',
auth_type='password',
interface='internal',
service_type='block_storage',
timeout=10,
group='cinder')
# force-reset the global session object
cinder._CINDER_SESSION = None cinder._CINDER_SESSION = None
self.context = context.RequestContext(global_request_id='global') self.context = context.RequestContext(global_request_id='global')
def _assert_client_call(self, init_mock, url, auth=mock.sentinel.auth, def test_get_cinder_client_with_context(self, mock_client_init,
auth_from_config=None): mock_session, mock_adapter,
if not auth_from_config: mock_auth, mock_sauth):
self.context.auth_token = 'meow' self.context = context.RequestContext(global_request_id='global',
cinder.get_client(self.context, auth_from_config=auth_from_config) auth_token='test-token-123')
init_mock.assert_called_once_with( cinder.get_client(context=self.context)
mock.ANY, mock_client_init.assert_called_once_with(
session=mock.sentinel.session, session=mock.sentinel.session,
auth=auth, block_storage_endpoint_override=mock.ANY,
endpoint_override=url, block_storage_api_version='3')
connect_retries=2, # testing handling of default url_timeout
global_request_id='global')
def test_get_client(self, mock_client_init, mock_session, mock_auth,
mock_sauth, mock_adapter):
mock_auth.return_value = mock_auth_obj = mock.Mock()
mock_auth_obj.get_project_id.return_value = '1111'
mock_adapter.return_value = mock_adapter_obj = mock.Mock()
mock_adapter_obj.get_endpoint.side_effect = iter([
'cinder_url/1111',
'cinder_url'])
self._assert_client_call(mock_client_init, 'cinder_url',
auth=mock.sentinel.sauth)
mock_session.assert_has_calls([ mock_session.assert_has_calls([
mock.call('cinder'), mock.call('cinder'),
mock.call('cinder', timeout=1, auth=mock.sentinel.sauth)]) mock.call('cinder', auth=mock.sentinel.sauth, timeout=10)
mock_auth.assert_called_once_with('cinder') ])
mock_adapter.assert_has_calls([
mock.call('cinder', session=mock.sentinel.session,
auth=mock_auth_obj),
mock.call('cinder', session=mock.sentinel.session, def test__get_cinder_session(self, mock_client_init,
auth=mock.sentinel.sauth)]) mock_session, mock_adapter,
self.assertTrue(mock_sauth.called) mock_auth, mock_sauth):
"""Check establishing new session when no session exists."""
mock_session.return_value = 'session1'
self.assertEqual('session1', cinder._get_cinder_session())
mock_session.assert_called_once_with('cinder')
def test_get_client_service_token(self, mock_client_init, mock_session, """Check if existing session is used."""
mock_auth, mock_sauth, mock_adapter): mock_session.reset_mock()
mock_auth.return_value = mock_auth_obj = mock.Mock() mock_session.return_value = 'session2'
mock_auth_obj.get_project_id.return_value = '1111' self.assertEqual('session1', cinder._get_cinder_session())
mock_adapter.return_value = mock_adapter_obj = mock.Mock() self.assertFalse(mock_session.called)
mock_adapter_obj.get_endpoint.side_effect = iter([
'cinder_url/1111',
'cinder_url'])
self._assert_client_call(
mock_client_init,
'cinder_url',
auth=None,
auth_from_config=True)
mock_session.assert_has_calls([
mock.call('cinder'),
mock.call('cinder', timeout=1, auth=mock_auth_obj)])
mock_auth.assert_called_once_with('cinder')
mock_adapter.assert_called_once_with(
'cinder', session=mock.sentinel.session, auth=mock_auth_obj)
self.assertFalse(mock_sauth.called)
class TestCinderUtils(db_base.DbTestCase): class TestCinderUtils(db_base.DbTestCase):
@ -140,11 +101,11 @@ class TestCinderUtils(db_base.DbTestCase):
def test_is_volume_available(self): def test_is_volume_available(self):
available_volumes = [ available_volumes = [
mock.Mock(status=cinder.AVAILABLE, multiattach=False), mock.Mock(status=cinder.AVAILABLE, is_multiattach=False),
mock.Mock(status=cinder.IN_USE, multiattach=True)] mock.Mock(status=cinder.IN_USE, is_multiattach=True)]
unavailable_volumes = [ unavailable_volumes = [
mock.Mock(status=cinder.IN_USE, multiattach=False), mock.Mock(status=cinder.IN_USE, is_multiattach=False),
mock.Mock(status='fake-non-status', multiattach=True)] mock.Mock(status='fake-non-status', is_multiattach=True)]
for vol in available_volumes: for vol in available_volumes:
result = cinder.is_volume_available(vol) result = cinder.is_volume_available(vol)
@ -201,10 +162,7 @@ class TestCinderUtils(db_base.DbTestCase):
self.assertEqual(expected_data, data) self.assertEqual(expected_data, data)
@mock.patch.object(cinder, '_get_cinder_session', autospec=True) @mock.patch.object(cinder, 'get_client', autospec=True)
@mock.patch.object(cinderclient.volumes.VolumeManager, 'set_metadata',
autospec=True)
@mock.patch.object(cinderclient.volumes.VolumeManager, 'get', autospec=True)
class TestCinderActions(db_base.DbTestCase): class TestCinderActions(db_base.DbTestCase):
def setUp(self): def setUp(self):
@ -214,17 +172,10 @@ class TestCinderActions(db_base.DbTestCase):
instance_uuid=uuidutils.generate_uuid()) instance_uuid=uuidutils.generate_uuid())
self.mount_point = 'ironic_mountpoint' self.mount_point = 'ironic_mountpoint'
@mock.patch.object(cinderclient.volumes.VolumeManager, 'attach',
autospec=True)
@mock.patch.object(cinderclient.volumes.VolumeManager,
'initialize_connection', autospec=True)
@mock.patch.object(cinderclient.volumes.VolumeManager, 'reserve',
autospec=True)
@mock.patch.object(cinder, 'is_volume_attached', autospec=True) @mock.patch.object(cinder, 'is_volume_attached', autospec=True)
@mock.patch.object(cinder, '_create_metadata_dictionary', autospec=True) @mock.patch.object(cinder, '_create_metadata_dictionary', autospec=True)
def test_attach_volumes(self, mock_create_meta, mock_is_attached, def test_attach_volumes(self, mock_create_meta, mock_is_attached,
mock_reserve, mock_init, mock_attach, mock_get, mock_client):
mock_set_meta, mock_session):
"""Iterate once on a single volume with success.""" """Iterate once on a single volume with success."""
volume_id = '111111111-0000-0000-0000-000000000003' volume_id = '111111111-0000-0000-0000-000000000003'
@ -241,7 +192,16 @@ class TestCinderActions(db_base.DbTestCase):
connector = {'foo': 'bar'} connector = {'foo': 'bar'}
mock_create_meta.return_value = {'bar': 'baz'} mock_create_meta.return_value = {'bar': 'baz'}
mock_is_attached.return_value = False mock_is_attached.return_value = False
mock_get.return_value = mock.Mock(attachments=[], id='000-001')
mock_bs = mock_client.return_value
mock_get = mock_bs.get_volume
mock_init = mock_bs.init_volume_attachment
mock_reserve = mock_bs.reserve_volume
mock_attach = mock_bs.attach_volume
mock_set_meta = mock_bs.set_volume_metadata
volume = mock.Mock(attachments=[], id='000-001')
mock_get.return_value = volume
mock_init.return_value = { mock_init.return_value = {
'driver_volume_type': 'iscsi', 'driver_volume_type': 'iscsi',
@ -254,25 +214,17 @@ class TestCinderActions(db_base.DbTestCase):
attachments = cinder.attach_volumes(task, volumes, connector) attachments = cinder.attach_volumes(task, volumes, connector)
self.assertEqual(expected, attachments) self.assertEqual(expected, attachments)
mock_reserve.assert_called_once_with(mock.ANY, volume_id) mock_reserve.assert_called_once_with(volume)
mock_init.assert_called_once_with(mock.ANY, volume_id, connector) mock_init.assert_called_once_with(volume, connector)
mock_attach.assert_called_once_with(mock.ANY, volume_id, mock_attach.assert_called_once_with(volume,
self.node.instance_uuid, self.mount_point,
self.mount_point) instance=self.node.instance_uuid)
mock_set_meta.assert_called_once_with(mock.ANY, volume_id, mock_set_meta.assert_called_once_with(volume, bar='baz')
{'bar': 'baz'}) mock_get.assert_called_once_with(volume_id)
mock_get.assert_called_once_with(mock.ANY, volume_id)
@mock.patch.object(cinderclient.volumes.VolumeManager, 'attach',
autospec=True)
@mock.patch.object(cinderclient.volumes.VolumeManager,
'initialize_connection', autospec=True)
@mock.patch.object(cinderclient.volumes.VolumeManager, 'reserve',
autospec=True)
@mock.patch.object(cinder, '_create_metadata_dictionary', autospec=True) @mock.patch.object(cinder, '_create_metadata_dictionary', autospec=True)
def test_attach_volumes_one_attached( def test_attach_volumes_one_attached(
self, mock_create_meta, mock_reserve, mock_init, mock_attach, self, mock_create_meta, mock_client):
mock_get, mock_set_meta, mock_session):
"""Iterate with two volumes, one already attached.""" """Iterate with two volumes, one already attached."""
volume_id = '111111111-0000-0000-0000-000000000003' volume_id = '111111111-0000-0000-0000-000000000003'
@ -292,8 +244,17 @@ class TestCinderActions(db_base.DbTestCase):
volumes = [volume_id, 'already_attached'] volumes = [volume_id, 'already_attached']
connector = {'foo': 'bar'} connector = {'foo': 'bar'}
mock_create_meta.return_value = {'bar': 'baz'} mock_create_meta.return_value = {'bar': 'baz'}
mock_bs = mock_client.return_value
mock_get = mock_bs.get_volume
mock_init = mock_bs.init_volume_attachment
mock_reserve = mock_bs.reserve_volume
mock_attach = mock_bs.attach_volume
mock_set_meta = mock_bs.set_volume_metadata
volume = mock.Mock(attachments=[], id='000-000')
mock_get.side_effect = [ mock_get.side_effect = [
mock.Mock(attachments=[], id='000-000'), volume,
mock.Mock(attachments=[{'server_id': self.node.uuid}], mock.Mock(attachments=[{'server_id': self.node.uuid}],
id='000-001') id='000-001')
] ]
@ -309,21 +270,18 @@ class TestCinderActions(db_base.DbTestCase):
attachments = cinder.attach_volumes(task, volumes, connector) attachments = cinder.attach_volumes(task, volumes, connector)
self.assertEqual(expected, attachments) self.assertEqual(expected, attachments)
mock_reserve.assert_called_once_with(mock.ANY, volume_id) mock_reserve.assert_called_once_with(volume)
mock_init.assert_called_once_with(mock.ANY, volume_id, connector) mock_init.assert_called_once_with(volume, connector)
mock_attach.assert_called_once_with(mock.ANY, volume_id, mock_attach.assert_called_once_with(volume,
self.node.instance_uuid, self.mount_point,
self.mount_point) instance=self.node.instance_uuid)
mock_set_meta.assert_called_once_with(mock.ANY, volume_id, mock_set_meta.assert_called_once_with(volume, bar='baz')
{'bar': 'baz'})
@mock.patch.object(cinderclient.Client, '__init__', autospec=True) def test_attach_volumes_conn_init_failure(
def test_attach_volumes_client_init_failure( self, mock_client):
self, mock_client, mock_get, mock_set_meta, mock_session):
connector = {'foo': 'bar'} connector = {'foo': 'bar'}
volumes = ['111111111-0000-0000-0000-000000000003'] volumes = ['111111111-0000-0000-0000-000000000003']
mock_client.side_effect = cinder_exceptions.BadRequest( mock_client.side_effect = openstack_exc.EndpointNotFound()
http_client.BAD_REQUEST)
with task_manager.acquire(self.context, self.node.uuid) as task: with task_manager.acquire(self.context, self.node.uuid) as task:
self.assertRaises(exception.StorageError, self.assertRaises(exception.StorageError,
@ -332,29 +290,31 @@ class TestCinderActions(db_base.DbTestCase):
volumes, volumes,
connector) connector)
@mock.patch.object(cinderclient.volumes.VolumeManager, 'attach',
autospec=True)
@mock.patch.object(cinderclient.volumes.VolumeManager,
'initialize_connection', autospec=True)
@mock.patch.object(cinderclient.volumes.VolumeManager, 'reserve',
autospec=True)
@mock.patch.object(cinder, '_create_metadata_dictionary', autospec=True) @mock.patch.object(cinder, '_create_metadata_dictionary', autospec=True)
def test_attach_volumes_vol_not_found( def test_attach_volumes_vol_not_found(
self, mock_create_meta, mock_reserve, mock_init, mock_attach, self, mock_create_meta, mock_client):
mock_get, mock_set_meta, mock_session):
"""Raise an error if the volume lookup fails""" """Raise an error if the volume lookup fails"""
def __mock_get_side_effect(client, volume_id): volume = mock.Mock(attachments=[], uuid='000-000')
if volume_id == 'not_found':
raise cinder_exceptions.NotFound( def __mock_get_side_effect(vol):
http_client.NOT_FOUND, message='error') if vol == 'not_found':
raise openstack_exc.ResourceNotFound()
else: else:
return mock.Mock(attachments=[], uuid='000-000') return volume
volumes = ['111111111-0000-0000-0000-000000000003', volumes = ['111111111-0000-0000-0000-000000000003',
'not_found', 'not_found',
'not_reached'] 'not_reached']
connector = {'foo': 'bar'} connector = {'foo': 'bar'}
mock_bs = mock_client.return_value
mock_get = mock_bs.get_volume
mock_init = mock_bs.init_volume_attachment
mock_reserve = mock_bs.reserve_volume
mock_attach = mock_bs.attach_volume
mock_set_meta = mock_bs.set_volume_metadata
mock_get.side_effect = __mock_get_side_effect mock_get.side_effect = __mock_get_side_effect
mock_create_meta.return_value = {'bar': 'baz'} mock_create_meta.return_value = {'bar': 'baz'}
@ -364,33 +324,27 @@ class TestCinderActions(db_base.DbTestCase):
task, task,
volumes, volumes,
connector) connector)
mock_get.assert_any_call(mock.ANY, mock_get.assert_any_call('111111111-0000-0000-0000-000000000003')
'111111111-0000-0000-0000-000000000003') mock_get.assert_any_call('not_found')
mock_get.assert_any_call(mock.ANY, 'not_found')
self.assertEqual(2, mock_get.call_count) self.assertEqual(2, mock_get.call_count)
mock_reserve.assert_called_once_with( mock_reserve.assert_called_once_with(volume)
mock.ANY, '111111111-0000-0000-0000-000000000003') mock_init.assert_called_once_with(volume, connector)
mock_init.assert_called_once_with(
mock.ANY, '111111111-0000-0000-0000-000000000003', connector)
mock_attach.assert_called_once_with( mock_attach.assert_called_once_with(
mock.ANY, '111111111-0000-0000-0000-000000000003', volume, self.mount_point, instance=self.node.instance_uuid)
self.node.instance_uuid, self.mount_point) mock_set_meta.assert_called_once_with(volume, bar='baz')
mock_set_meta.assert_called_once_with(
mock.ANY, '111111111-0000-0000-0000-000000000003', {'bar': 'baz'})
@mock.patch.object(cinderclient.volumes.VolumeManager, 'reserve',
autospec=True)
@mock.patch.object(cinder, 'is_volume_attached', autospec=True) @mock.patch.object(cinder, 'is_volume_attached', autospec=True)
def test_attach_volumes_reserve_failure(self, mock_is_attached, def test_attach_volumes_reserve_failure(self, mock_is_attached,
mock_reserve, mock_get, mock_client):
mock_set_meta, mock_session):
volumes = ['111111111-0000-0000-0000-000000000003'] volumes = ['111111111-0000-0000-0000-000000000003']
connector = {'foo': 'bar'} connector = {'foo': 'bar'}
volume = mock.Mock(attachments=[]) volume = mock.Mock(attachments=[])
mock_bs = mock_client.return_value
mock_get = mock_bs.get_volume
mock_reserve = mock_bs.reserve_volume
mock_get.return_value = volume mock_get.return_value = volume
mock_is_attached.return_value = False mock_is_attached.return_value = False
mock_reserve.side_effect = cinder_exceptions.NotAcceptable( mock_reserve.side_effect = openstack_exc.HttpException()
http_client.NOT_ACCEPTABLE)
with task_manager.acquire(self.context, self.node.uuid) as task: with task_manager.acquire(self.context, self.node.uuid) as task:
self.assertRaises(exception.StorageError, self.assertRaises(exception.StorageError,
@ -400,15 +354,11 @@ class TestCinderActions(db_base.DbTestCase):
connector) connector)
mock_is_attached.assert_called_once_with(mock.ANY, volume) mock_is_attached.assert_called_once_with(mock.ANY, volume)
@mock.patch.object(cinderclient.volumes.VolumeManager,
'initialize_connection', autospec=True)
@mock.patch.object(cinderclient.volumes.VolumeManager, 'reserve',
autospec=True)
@mock.patch.object(cinder, 'is_volume_attached', autospec=True) @mock.patch.object(cinder, 'is_volume_attached', autospec=True)
@mock.patch.object(cinder, '_create_metadata_dictionary', autospec=True) @mock.patch.object(cinder, '_create_metadata_dictionary', autospec=True)
def test_attach_volumes_initialize_connection_failure( def test_attach_volumes_initialize_connection_failure(
self, mock_create_meta, mock_is_attached, mock_reserve, mock_init, self, mock_create_meta, mock_is_attached,
mock_get, mock_set_meta, mock_session): mock_client):
"""Fail attachment upon an initialization failure.""" """Fail attachment upon an initialization failure."""
volume_id = '111111111-0000-0000-0000-000000000003' volume_id = '111111111-0000-0000-0000-000000000003'
@ -416,9 +366,15 @@ class TestCinderActions(db_base.DbTestCase):
connector = {'foo': 'bar'} connector = {'foo': 'bar'}
mock_create_meta.return_value = {'bar': 'baz'} mock_create_meta.return_value = {'bar': 'baz'}
mock_is_attached.return_value = False mock_is_attached.return_value = False
mock_get.return_value = mock.Mock(attachments=[])
mock_init.side_effect = cinder_exceptions.NotAcceptable( mock_bs = mock_client.return_value
http_client.NOT_ACCEPTABLE) mock_get = mock_bs.get_volume
mock_init = mock_bs.init_volume_attachment
mock_reserve = mock_bs.reserve_volume
volume = mock.Mock(attachments=[])
mock_get.return_value = volume
mock_init.side_effect = openstack_exc.HttpException("not acceptable")
with task_manager.acquire(self.context, self.node.uuid) as task: with task_manager.acquire(self.context, self.node.uuid) as task:
self.assertRaises(exception.StorageError, self.assertRaises(exception.StorageError,
@ -427,63 +383,55 @@ class TestCinderActions(db_base.DbTestCase):
volumes, volumes,
connector) connector)
mock_get.assert_called_once_with(mock.ANY, volume_id) mock_get.assert_called_once_with(volume_id)
mock_reserve.assert_called_once_with(mock.ANY, volume_id) mock_reserve.assert_called_once_with(volume)
mock_init.assert_called_once_with(mock.ANY, volume_id, connector) mock_init.assert_called_once_with(volume, connector)
@mock.patch.object(cinderclient.volumes.VolumeManager, 'attach',
autospec=True)
@mock.patch.object(cinderclient.volumes.VolumeManager,
'initialize_connection', autospec=True)
@mock.patch.object(cinderclient.volumes.VolumeManager, 'reserve',
autospec=True)
@mock.patch.object(cinder, 'is_volume_attached', autospec=True) @mock.patch.object(cinder, 'is_volume_attached', autospec=True)
@mock.patch.object(cinder, '_create_metadata_dictionary', autospec=True) @mock.patch.object(cinder, '_create_metadata_dictionary', autospec=True)
def test_attach_volumes_attach_record_failure( def test_attach_volumes_attach_record_failure(
self, mock_create_meta, mock_is_attached, mock_reserve, self, mock_create_meta, mock_is_attached, mock_client):
mock_init, mock_attach, mock_get, mock_set_meta, mock_session):
"""Attach a volume and fail if final record failure occurs""" """Attach a volume and fail if final record failure occurs"""
volume_id = '111111111-0000-0000-0000-000000000003' volume_id = '111111111-0000-0000-0000-000000000003'
volumes = [volume_id] volumes = [volume_id]
connector = {'foo': 'bar'} connector = {'foo': 'bar'}
mock_create_meta.return_value = {'bar': 'baz'} mock_create_meta.return_value = {'bar': 'baz'}
mock_is_attached.return_value = False mock_is_attached.return_value = False
mock_get.return_value = mock.Mock(attachments=[], id='000-003')
mock_bs = mock_client.return_value
mock_get = mock_bs.get_volume
mock_init = mock_bs.init_volume_attachment
mock_reserve = mock_bs.reserve_volume
mock_attach = mock_bs.attach_volume
volume = mock.Mock(attachments=[], id='000-003')
mock_get.return_value = volume
mock_init.return_value = { mock_init.return_value = {
'driver_volume_type': 'iscsi', 'driver_volume_type': 'iscsi',
'data': { 'data': {
'target_iqn': 'iqn.2010-10.org.openstack:volume-00000002', 'target_iqn': 'iqn.2010-10.org.openstack:volume-00000002',
'target_portal': '127.0.0.0.1:3260', 'target_portal': '127.0.0.0.1:3260',
'target_lun': 2}} 'target_lun': 2}}
mock_attach.side_effect = cinder_exceptions.ClientException( mock_attach.side_effect = openstack_exc.HttpException("not acceptable")
http_client.NOT_ACCEPTABLE, 'error')
with task_manager.acquire(self.context, self.node.uuid) as task: with task_manager.acquire(self.context, self.node.uuid) as task:
self.assertRaises(exception.StorageError, cinder.attach_volumes, self.assertRaises(exception.StorageError, cinder.attach_volumes,
task, volumes, connector) task, volumes, connector)
mock_reserve.assert_called_once_with(mock.ANY, volume_id) mock_reserve.assert_called_once_with(volume)
mock_init.assert_called_once_with(mock.ANY, volume_id, connector) mock_init.assert_called_once_with(volume, connector)
mock_attach.assert_called_once_with(mock.ANY, volume_id, mock_attach.assert_called_once_with(volume,
self.node.instance_uuid, self.mount_point,
self.mount_point) instance=self.node.instance_uuid)
mock_get.assert_called_once_with(mock.ANY, volume_id) mock_get.assert_called_once_with(volume_id)
mock_is_attached.assert_called_once_with(mock.ANY, mock_is_attached.assert_called_once_with(mock.ANY, volume)
mock_get.return_value)
@mock.patch.object(cinderclient.volumes.VolumeManager, 'attach',
autospec=True)
@mock.patch.object(cinderclient.volumes.VolumeManager,
'initialize_connection', autospec=True)
@mock.patch.object(cinderclient.volumes.VolumeManager, 'reserve',
autospec=True)
@mock.patch.object(cinder, 'is_volume_attached', autospec=True) @mock.patch.object(cinder, 'is_volume_attached', autospec=True)
@mock.patch.object(cinder, '_create_metadata_dictionary', autospec=True) @mock.patch.object(cinder, '_create_metadata_dictionary', autospec=True)
@mock.patch.object(cinder, 'LOG', autospec=True) @mock.patch.object(cinder, 'LOG', autospec=True)
def test_attach_volumes_attach_set_meta_failure( def test_attach_volumes_attach_set_meta_failure(
self, mock_log, mock_create_meta, mock_is_attached, self, mock_log, mock_create_meta, mock_is_attached,
mock_reserve, mock_init, mock_attach, mock_get, mock_set_meta, mock_client):
mock_session):
"""Attach a volume and tolerate set_metadata failure.""" """Attach a volume and tolerate set_metadata failure."""
expected = [{ expected = [{
@ -499,43 +447,42 @@ class TestCinderActions(db_base.DbTestCase):
connector = {'foo': 'bar'} connector = {'foo': 'bar'}
mock_create_meta.return_value = {'bar': 'baz'} mock_create_meta.return_value = {'bar': 'baz'}
mock_is_attached.return_value = False mock_is_attached.return_value = False
mock_get.return_value = mock.Mock(attachments=[], id='000-000')
mock_bs = mock_client.return_value
mock_get = mock_bs.get_volume
mock_init = mock_bs.init_volume_attachment
mock_reserve = mock_bs.reserve_volume
mock_attach = mock_bs.attach_volume
mock_set_meta = mock_bs.set_volume_metadata
volume = mock.Mock(attachments=[], id='000-000')
mock_get.return_value = volume
mock_init.return_value = { mock_init.return_value = {
'driver_volume_type': 'iscsi', 'driver_volume_type': 'iscsi',
'data': { 'data': {
'target_iqn': 'iqn.2010-10.org.openstack:volume-00000002', 'target_iqn': 'iqn.2010-10.org.openstack:volume-00000002',
'target_portal': '127.0.0.0.1:3260', 'target_portal': '127.0.0.0.1:3260',
'target_lun': 2}} 'target_lun': 2}}
mock_set_meta.side_effect = cinder_exceptions.NotAcceptable( mock_set_meta.side_effect = openstack_exc.HttpException()
http_client.NOT_ACCEPTABLE)
with task_manager.acquire(self.context, self.node.uuid) as task: with task_manager.acquire(self.context, self.node.uuid) as task:
attachments = cinder.attach_volumes(task, volumes, connector) attachments = cinder.attach_volumes(task, volumes, connector)
self.assertEqual(expected, attachments) self.assertEqual(expected, attachments)
mock_reserve.assert_called_once_with(mock.ANY, volume_id) mock_reserve.assert_called_once_with(volume)
mock_init.assert_called_once_with(mock.ANY, volume_id, connector) mock_init.assert_called_once_with(volume, connector)
mock_attach.assert_called_once_with(mock.ANY, volume_id, mock_attach.assert_called_once_with(volume,
self.node.instance_uuid, self.mount_point,
self.mount_point) instance=self.node.instance_uuid)
mock_set_meta.assert_called_once_with(mock.ANY, volume_id, mock_set_meta.assert_called_once_with(volume, bar='baz')
{'bar': 'baz'}) mock_get.assert_called_once_with(volume_id)
mock_get.assert_called_once_with(mock.ANY, volume_id) mock_is_attached.assert_called_once_with(mock.ANY, volume)
mock_is_attached.assert_called_once_with(mock.ANY,
mock_get.return_value)
self.assertTrue(mock_log.warning.called) self.assertTrue(mock_log.warning.called)
@mock.patch.object(cinderclient.volumes.VolumeManager, 'detach',
autospec=True)
@mock.patch.object(cinderclient.volumes.VolumeManager,
'terminate_connection', autospec=True)
@mock.patch.object(cinderclient.volumes.VolumeManager, 'begin_detaching',
autospec=True)
@mock.patch.object(cinder, 'is_volume_attached', autospec=True) @mock.patch.object(cinder, 'is_volume_attached', autospec=True)
@mock.patch.object(cinder, '_create_metadata_dictionary', autospec=True) @mock.patch.object(cinder, '_create_metadata_dictionary', autospec=True)
def test_detach_volumes( def test_detach_volumes(
self, mock_create_meta, mock_is_attached, mock_begin, mock_term, self, mock_create_meta, mock_is_attached, mock_client):
mock_detach, mock_get, mock_set_meta, mock_session):
"""Iterate once and detach a volume without issues.""" """Iterate once and detach a volume without issues."""
volume_id = '111111111-0000-0000-0000-000000000003' volume_id = '111111111-0000-0000-0000-000000000003'
volumes = [volume_id] volumes = [volume_id]
@ -543,28 +490,29 @@ class TestCinderActions(db_base.DbTestCase):
connector = {'foo': 'bar'} connector = {'foo': 'bar'}
mock_create_meta.return_value = {'bar': 'baz'} mock_create_meta.return_value = {'bar': 'baz'}
mock_is_attached.return_value = True mock_is_attached.return_value = True
mock_get.return_value = mock.Mock(attachments=[
mock_bs = mock_client.return_value
mock_get = mock_bs.get_volume
mock_begin = mock_bs.begin_volume_detaching
mock_term = mock_bs.terminate_volume_attachment
mock_detach = mock_bs.detach_volume
mock_set_meta = mock_bs.set_volume_metadata
volume = mock.Mock(attachments=[
{'server_id': self.node.uuid, 'attachment_id': 'qux'}]) {'server_id': self.node.uuid, 'attachment_id': 'qux'}])
mock_get.return_value = volume
with task_manager.acquire(self.context, self.node.uuid) as task: with task_manager.acquire(self.context, self.node.uuid) as task:
cinder.detach_volumes(task, volumes, connector, allow_errors=False) cinder.detach_volumes(task, volumes, connector, allow_errors=False)
mock_begin.assert_called_once_with(mock.ANY, volume_id) mock_begin.assert_called_once_with(volume)
mock_term.assert_called_once_with(mock.ANY, volume_id, {'foo': 'bar'}) mock_term.assert_called_once_with(volume, {'foo': 'bar'})
mock_detach.assert_called_once_with(mock.ANY, volume_id, 'qux') mock_detach.assert_called_once_with(volume, 'qux')
mock_set_meta.assert_called_once_with(mock.ANY, volume_id, mock_set_meta.assert_called_once_with(volume, bar='baz')
{'bar': 'baz'})
@mock.patch.object(cinderclient.volumes.VolumeManager, 'detach',
autospec=True)
@mock.patch.object(cinderclient.volumes.VolumeManager,
'terminate_connection', autospec=True)
@mock.patch.object(cinderclient.volumes.VolumeManager, 'begin_detaching',
autospec=True)
@mock.patch.object(cinder, '_create_metadata_dictionary', autospec=True) @mock.patch.object(cinder, '_create_metadata_dictionary', autospec=True)
def test_detach_volumes_one_detached( def test_detach_volumes_one_detached(
self, mock_create_meta, mock_begin, mock_term, mock_detach, self, mock_create_meta, mock_client):
mock_get, mock_set_meta, mock_session):
"""Iterate with two volumes, one already detached.""" """Iterate with two volumes, one already detached."""
volume_id = '111111111-0000-0000-0000-000000000003' volume_id = '111111111-0000-0000-0000-000000000003'
volumes = [volume_id, 'detached'] volumes = [volume_id, 'detached']
@ -572,56 +520,49 @@ class TestCinderActions(db_base.DbTestCase):
connector = {'foo': 'bar'} connector = {'foo': 'bar'}
mock_create_meta.return_value = {'bar': 'baz'} mock_create_meta.return_value = {'bar': 'baz'}
mock_bs = mock_client.return_value
mock_get = mock_bs.get_volume
mock_begin = mock_bs.begin_volume_detaching
mock_term = mock_bs.terminate_volume_attachment
mock_detach = mock_bs.detach_volume
mock_set_meta = mock_bs.set_volume_metadata
volume = mock.Mock(attachments=[
{'server_id': self.node.uuid, 'attachment_id': 'qux'}])
mock_get.side_effect = [ mock_get.side_effect = [
mock.Mock(attachments=[ volume, mock.Mock(attachments=[])
{'server_id': self.node.uuid, 'attachment_id': 'qux'}]),
mock.Mock(attachments=[])
] ]
with task_manager.acquire(self.context, self.node.uuid) as task: with task_manager.acquire(self.context, self.node.uuid) as task:
cinder.detach_volumes(task, volumes, connector, allow_errors=False) cinder.detach_volumes(task, volumes, connector, allow_errors=False)
mock_begin.assert_called_once_with(mock.ANY, volume_id) mock_begin.assert_called_once_with(volume)
mock_term.assert_called_once_with(mock.ANY, volume_id, {'foo': 'bar'}) mock_term.assert_called_once_with(volume, {'foo': 'bar'})
mock_detach.assert_called_once_with(mock.ANY, volume_id, 'qux') mock_detach.assert_called_once_with(volume, 'qux')
mock_set_meta.assert_called_once_with(mock.ANY, volume_id, mock_set_meta.assert_called_once_with(volume, bar='baz')
{'bar': 'baz'})
@mock.patch.object(cinderclient.Client, '__init__', autospec=True) def test_detach_volumes_conn_init_failure_bad_request(
def test_detach_volumes_client_init_failure_bad_request( self, mock_client):
self, mock_client, mock_get, mock_set_meta, mock_session):
connector = {'foo': 'bar'} connector = {'foo': 'bar'}
volumes = ['111111111-0000-0000-0000-000000000003'] volumes = ['111111111-0000-0000-0000-000000000003']
with task_manager.acquire(self.context, self.node.uuid) as task: with task_manager.acquire(self.context, self.node.uuid) as task:
mock_client.side_effect = cinder_exceptions.BadRequest( mock_client.side_effect = openstack_exc.BadRequestException()
http_client.BAD_REQUEST)
self.assertRaises(exception.StorageError, self.assertRaises(exception.StorageError,
cinder.detach_volumes, cinder.detach_volumes,
task, task,
volumes, volumes,
connector) connector)
@mock.patch.object(cinderclient.Client, '__init__', autospec=True) def test_detach_volumes_vol_not_found(self, mock_client):
def test_detach_volumes_client_init_failure_invalid_parameter_value(
self, mock_client, mock_get, mock_set_meta, mock_session):
connector = {'foo': 'bar'}
volumes = ['111111111-0000-0000-0000-000000000003']
with task_manager.acquire(self.context, self.node.uuid) as task:
# While we would be permitting failures, this is an exception that
# must be raised since the client cannot be initialized.
mock_client.side_effect = exception.InvalidParameterValue('error')
self.assertRaises(exception.StorageError,
cinder.detach_volumes, task, volumes,
connector, allow_errors=True)
def test_detach_volumes_vol_not_found(self, mock_get, mock_set_meta,
mock_session):
"""Raise an error if the volume lookup fails""" """Raise an error if the volume lookup fails"""
volumes = ['vol1'] volumes = ['vol1']
connector = {'foo': 'bar'} connector = {'foo': 'bar'}
mock_get.side_effect = cinder_exceptions.NotFound( mock_bs = mock_client.return_value
http_client.NOT_FOUND, message='error') mock_get = mock_bs.get_volume
mock_set_meta = mock_bs.set_volume_metadata
mock_get.side_effect = openstack_exc.NotFoundException()
with task_manager.acquire(self.context, self.node.uuid) as task: with task_manager.acquire(self.context, self.node.uuid) as task:
self.assertRaises(exception.StorageError, self.assertRaises(exception.StorageError,
@ -635,27 +576,26 @@ class TestCinderActions(db_base.DbTestCase):
cinder.detach_volumes(task, volumes, connector, allow_errors=True) cinder.detach_volumes(task, volumes, connector, allow_errors=True)
self.assertFalse(mock_set_meta.called) self.assertFalse(mock_set_meta.called)
@mock.patch.object(cinderclient.volumes.VolumeManager, 'detach',
autospec=True)
@mock.patch.object(cinderclient.volumes.VolumeManager,
'terminate_connection', autospec=True)
@mock.patch.object(cinderclient.volumes.VolumeManager, 'begin_detaching',
autospec=True)
@mock.patch.object(cinder, 'is_volume_attached', autospec=True) @mock.patch.object(cinder, 'is_volume_attached', autospec=True)
@mock.patch.object(cinder, '_create_metadata_dictionary', autospec=True) @mock.patch.object(cinder, '_create_metadata_dictionary', autospec=True)
def test_detach_volumes_begin_detaching_failure( def test_detach_volumes_begin_detaching_failure(
self, mock_create_meta, mock_is_attached, mock_begin, mock_term, self, mock_create_meta, mock_is_attached, mock_client):
mock_detach, mock_get, mock_set_meta, mock_session):
volume_id = '111111111-0000-0000-0000-000000000003' volume_id = '111111111-0000-0000-0000-000000000003'
volumes = [volume_id] volumes = [volume_id]
connector = {'foo': 'bar'} connector = {'foo': 'bar'}
mock_bs = mock_client.return_value
mock_get = mock_bs.get_volume
mock_begin = mock_bs.begin_volume_detaching
mock_term = mock_bs.terminate_volume_attachment
mock_detach = mock_bs.detach_volume
mock_set_meta = mock_bs.set_volume_metadata
volume = mock.Mock(attachments=[]) volume = mock.Mock(attachments=[])
mock_get.return_value = volume mock_get.return_value = volume
mock_create_meta.return_value = {'bar': 'baz'} mock_create_meta.return_value = {'bar': 'baz'}
mock_is_attached.return_value = True mock_is_attached.return_value = True
mock_begin.side_effect = cinder_exceptions.NotAcceptable( mock_begin.side_effect = openstack_exc.HttpException("not acceptable")
http_client.NOT_ACCEPTABLE)
with task_manager.acquire(self.context, self.node.uuid) as task: with task_manager.acquire(self.context, self.node.uuid) as task:
self.assertRaises(exception.StorageError, self.assertRaises(exception.StorageError,
@ -665,30 +605,30 @@ class TestCinderActions(db_base.DbTestCase):
connector) connector)
mock_is_attached.assert_called_once_with(mock.ANY, volume) mock_is_attached.assert_called_once_with(mock.ANY, volume)
cinder.detach_volumes(task, volumes, connector, allow_errors=True) cinder.detach_volumes(task, volumes, connector, allow_errors=True)
mock_term.assert_called_once_with(mock.ANY, volume_id, mock_term.assert_called_once_with(volume,
{'foo': 'bar'}) {'foo': 'bar'})
mock_detach.assert_called_once_with(mock.ANY, volume_id, None) mock_detach.assert_called_once_with(volume, None)
mock_set_meta.assert_called_once_with(mock.ANY, volume_id, mock_set_meta.assert_called_once_with(volume, bar='baz')
{'bar': 'baz'})
@mock.patch.object(cinderclient.volumes.VolumeManager,
'terminate_connection', autospec=True)
@mock.patch.object(cinderclient.volumes.VolumeManager, 'begin_detaching',
autospec=True)
@mock.patch.object(cinder, 'is_volume_attached', autospec=True) @mock.patch.object(cinder, 'is_volume_attached', autospec=True)
@mock.patch.object(cinder, '_create_metadata_dictionary', autospec=True) @mock.patch.object(cinder, '_create_metadata_dictionary', autospec=True)
def test_detach_volumes_term_failure( def test_detach_volumes_term_failure(
self, mock_create_meta, mock_is_attached, mock_begin, mock_term, self, mock_create_meta, mock_is_attached, mock_client):
mock_get, mock_set_meta, mock_session):
volume_id = '111111111-0000-0000-0000-000000000003' volume_id = '111111111-0000-0000-0000-000000000003'
volumes = [volume_id] volumes = [volume_id]
connector = {'foo': 'bar'} connector = {'foo': 'bar'}
mock_create_meta.return_value = {'bar': 'baz'} mock_create_meta.return_value = {'bar': 'baz'}
mock_is_attached.return_value = True mock_is_attached.return_value = True
mock_get.return_value = {'id': volume_id, 'attachments': []} mock_bs = mock_client.return_value
mock_term.side_effect = cinder_exceptions.NotAcceptable( mock_get = mock_bs.get_volume
http_client.NOT_ACCEPTABLE) mock_begin = mock_bs.begin_volume_detaching
mock_term = mock_bs.terminate_volume_attachment
mock_set_meta = mock_bs.set_volume_metadata
volume = mock.Mock(id=volume_id, attachments=[])
mock_get.return_value = volume
mock_term.side_effect = openstack_exc.HttpException("not acceptable")
with task_manager.acquire(self.context, self.node.uuid) as task: with task_manager.acquire(self.context, self.node.uuid) as task:
self.assertRaises(exception.StorageError, self.assertRaises(exception.StorageError,
@ -696,32 +636,30 @@ class TestCinderActions(db_base.DbTestCase):
task, task,
volumes, volumes,
connector) connector)
mock_begin.assert_called_once_with(mock.ANY, volume_id) mock_begin.assert_called_once_with(volume)
mock_term.assert_called_once_with(mock.ANY, volume_id, connector) mock_term.assert_called_once_with(volume, connector)
cinder.detach_volumes(task, volumes, connector, allow_errors=True) cinder.detach_volumes(task, volumes, connector, allow_errors=True)
self.assertFalse(mock_set_meta.called) self.assertFalse(mock_set_meta.called)
@mock.patch.object(cinderclient.volumes.VolumeManager, 'detach',
autospec=True)
@mock.patch.object(cinderclient.volumes.VolumeManager,
'terminate_connection', autospec=True)
@mock.patch.object(cinderclient.volumes.VolumeManager, 'begin_detaching',
autospec=True)
@mock.patch.object(cinder, 'is_volume_attached', autospec=True) @mock.patch.object(cinder, 'is_volume_attached', autospec=True)
@mock.patch.object(cinder, '_create_metadata_dictionary', autospec=True) @mock.patch.object(cinder, '_create_metadata_dictionary', autospec=True)
def test_detach_volumes_detach_failure_errors_not_allowed( def test_detach_volumes_detach_failure_errors_not_allowed(
self, mock_create_meta, mock_is_attached, mock_begin, mock_term, self, mock_create_meta, mock_is_attached, mock_client):
mock_detach, mock_get, mock_set_meta, mock_session):
volume_id = '111111111-0000-0000-0000-000000000003' volume_id = '111111111-0000-0000-0000-000000000003'
volumes = [volume_id] volumes = [volume_id]
connector = {'foo': 'bar'} connector = {'foo': 'bar'}
mock_create_meta.return_value = {'bar': 'baz'} mock_create_meta.return_value = {'bar': 'baz'}
mock_is_attached.return_value = True mock_is_attached.return_value = True
mock_get.return_value = mock.Mock(attachments=[ mock_bs = mock_client.return_value
mock_get = mock_bs.get_volume
mock_detach = mock_bs.detach_volume
mock_set_meta = mock_bs.set_volume_metadata
volume = mock.Mock(attachments=[
{'server_id': self.node.uuid, 'attachment_id': 'qux'}]) {'server_id': self.node.uuid, 'attachment_id': 'qux'}])
mock_detach.side_effect = cinder_exceptions.NotAcceptable( mock_get.return_value = volume
http_client.NOT_ACCEPTABLE) mock_detach.side_effect = openstack_exc.HttpException("not acceptable")
with task_manager.acquire(self.context, self.node.uuid) as task: with task_manager.acquire(self.context, self.node.uuid) as task:
self.assertRaises(exception.StorageError, self.assertRaises(exception.StorageError,
@ -730,61 +668,55 @@ class TestCinderActions(db_base.DbTestCase):
volumes, volumes,
connector, connector,
allow_errors=False) allow_errors=False)
mock_detach.assert_called_once_with(mock.ANY, volume_id, 'qux') mock_detach.assert_called_once_with(volume, 'qux')
self.assertFalse(mock_set_meta.called) self.assertFalse(mock_set_meta.called)
@mock.patch.object(cinderclient.volumes.VolumeManager, 'detach',
autospec=True)
@mock.patch.object(cinderclient.volumes.VolumeManager,
'terminate_connection', autospec=True)
@mock.patch.object(cinderclient.volumes.VolumeManager, 'begin_detaching',
autospec=True)
@mock.patch.object(cinder, 'is_volume_attached', autospec=True) @mock.patch.object(cinder, 'is_volume_attached', autospec=True)
@mock.patch.object(cinder, '_create_metadata_dictionary', autospec=True) @mock.patch.object(cinder, '_create_metadata_dictionary', autospec=True)
def test_detach_volumes_detach_failure_errors_allowed( def test_detach_volumes_detach_failure_errors_allowed(
self, mock_create_meta, mock_is_attached, mock_begin, mock_term, self, mock_create_meta, mock_is_attached, mock_client):
mock_detach, mock_get, mock_set_meta, mock_session):
volume_id = '111111111-0000-0000-0000-000000000003' volume_id = '111111111-0000-0000-0000-000000000003'
volumes = [volume_id] volumes = [volume_id]
connector = {'foo': 'bar'} connector = {'foo': 'bar'}
mock_create_meta.return_value = {'bar': 'baz'} mock_create_meta.return_value = {'bar': 'baz'}
mock_is_attached.return_value = True mock_is_attached.return_value = True
mock_get.return_value = mock.Mock(attachments=[ mock_bs = mock_client.return_value
mock_get = mock_bs.get_volume
mock_detach = mock_bs.detach_volume
mock_set_meta = mock_bs.set_volume_metadata
volume = mock.Mock(attachments=[
{'server_id': self.node.uuid, 'attachment_id': 'qux'}]) {'server_id': self.node.uuid, 'attachment_id': 'qux'}])
mock_set_meta.side_effect = cinder_exceptions.NotAcceptable( mock_get.return_value = volume
http_client.NOT_ACCEPTABLE) mock_set_meta.side_effect = openstack_exc.HttpException()
with task_manager.acquire(self.context, self.node.uuid) as task: with task_manager.acquire(self.context, self.node.uuid) as task:
cinder.detach_volumes(task, volumes, connector, allow_errors=True) cinder.detach_volumes(task, volumes, connector, allow_errors=True)
mock_detach.assert_called_once_with(mock.ANY, volume_id, 'qux') mock_detach.assert_called_once_with(volume, 'qux')
mock_set_meta.assert_called_once_with(mock.ANY, volume_id, mock_set_meta.assert_called_once_with(volume, bar='baz')
{'bar': 'baz'})
@mock.patch.object(cinderclient.volumes.VolumeManager, 'detach',
autospec=True)
@mock.patch.object(cinderclient.volumes.VolumeManager,
'terminate_connection', autospec=True)
@mock.patch.object(cinderclient.volumes.VolumeManager, 'begin_detaching',
autospec=True)
@mock.patch.object(cinder, 'is_volume_attached', autospec=True) @mock.patch.object(cinder, 'is_volume_attached', autospec=True)
@mock.patch.object(cinder, '_create_metadata_dictionary', autospec=True) @mock.patch.object(cinder, '_create_metadata_dictionary', autospec=True)
def test_detach_volumes_detach_meta_failure_errors_not_allowed( def test_detach_volumes_detach_meta_failure_errors_not_allowed(
self, mock_create_meta, mock_is_attached, mock_begin, mock_term, self, mock_create_meta, mock_is_attached, mock_client):
mock_detach, mock_get, mock_set_meta, mock_session):
volume_id = '111111111-0000-0000-0000-000000000003' volume_id = '111111111-0000-0000-0000-000000000003'
volumes = [volume_id] volumes = [volume_id]
connector = {'foo': 'bar'} connector = {'foo': 'bar'}
mock_create_meta.return_value = {'bar': 'baz'} mock_create_meta.return_value = {'bar': 'baz'}
mock_is_attached.return_value = True mock_is_attached.return_value = True
mock_get.return_value = mock.Mock(attachments=[ mock_bs = mock_client.return_value
mock_get = mock_bs.get_volume
mock_detach = mock_bs.detach_volume
mock_set_meta = mock_bs.set_volume_metadata
volume = mock.Mock(attachments=[
{'server_id': self.node.uuid, 'attachment_id': 'qux'}]) {'server_id': self.node.uuid, 'attachment_id': 'qux'}])
mock_set_meta.side_effect = cinder_exceptions.NotAcceptable( mock_get.return_value = volume
http_client.NOT_ACCEPTABLE) mock_set_meta.side_effect = openstack_exc.HttpException()
with task_manager.acquire(self.context, self.node.uuid) as task: with task_manager.acquire(self.context, self.node.uuid) as task:
cinder.detach_volumes(task, volumes, connector, allow_errors=False) cinder.detach_volumes(task, volumes, connector, allow_errors=False)
mock_detach.assert_called_once_with(mock.ANY, volume_id, 'qux') mock_detach.assert_called_once_with(volume, 'qux')
mock_set_meta.assert_called_once_with(mock.ANY, volume_id, mock_set_meta.assert_called_once_with(volume, bar='baz')
{'bar': 'baz'})

View File

@ -0,0 +1,5 @@
---
upgrade:
- |
`python-cinderclient` is no longer a dependency, all OpenStack Cinder
operations are now done using `openstacksdk`.

View File

@ -11,7 +11,6 @@ alembic>=1.4.2 # MIT
automaton>=1.9.0 # Apache-2.0 automaton>=1.9.0 # Apache-2.0
eventlet>=0.30.1 # MIT eventlet>=0.30.1 # MIT
WebOb>=1.7.1 # MIT WebOb>=1.7.1 # MIT
python-cinderclient!=4.0.0,>=3.3.0 # Apache-2.0
python-glanceclient>=2.8.0 # Apache-2.0 python-glanceclient>=2.8.0 # Apache-2.0
keystoneauth1>=4.2.0 # Apache-2.0 keystoneauth1>=4.2.0 # Apache-2.0
ironic-lib>=6.0.0 # Apache-2.0 ironic-lib>=6.0.0 # Apache-2.0
@ -45,7 +44,7 @@ jsonschema>=4.0.0 # MIT
psutil>=3.2.2 # BSD psutil>=3.2.2 # BSD
futurist>=1.2.0 # Apache-2.0 futurist>=1.2.0 # Apache-2.0
tooz>=2.7.0 # Apache-2.0 tooz>=2.7.0 # Apache-2.0
openstacksdk>=0.48.0 # Apache-2.0 openstacksdk>=0.99.0 # Apache-2.0
sushy>=4.8.0 sushy>=4.8.0
construct>=2.9.39 # MIT construct>=2.9.39 # MIT
netaddr>=0.9.0 # BSD netaddr>=0.9.0 # BSD