Pass multiattach flag to reserve_block_device_name
This is similar to how we use reserve_block_device_name for tagged attach in that we can fail fast in the API if trying to attach a multiattach volume to an instance on a compute host that can't support it, either because the compute service is too old or the virt driver doesn't support the multiattach capability yet. A later change will build on this to pass the multiattach flag from the API when a multiattach volume is requested in a new microversion, similar to how 2.49 works. Part of blueprint multi-attach-volume Change-Id: Ieec2bbd8a23c861c89bf9922517fa6a5562f1937
This commit is contained in:
parent
ecd19ce45b
commit
1b53028c9c
|
@ -337,6 +337,7 @@ class VolumeAttachmentController(wsgi.Controller):
|
|||
raise exc.HTTPNotFound(explanation=e.format_message())
|
||||
except (exception.InstanceIsLocked,
|
||||
exception.DevicePathInUse) as e:
|
||||
# TODO(mriedem): Need to handle MultiattachNotSupportedByVirtDriver
|
||||
raise exc.HTTPConflict(explanation=e.format_message())
|
||||
except exception.InstanceInvalidState as state_error:
|
||||
common.raise_http_conflict_for_instance_invalid_state(state_error,
|
||||
|
|
|
@ -3654,6 +3654,7 @@ class API(base.Base):
|
|||
# the same time. When db access is removed from
|
||||
# compute, the bdm will be created here and we will
|
||||
# have to make sure that they are assigned atomically.
|
||||
# TODO(mriedem): Handle multiattach here.
|
||||
volume_bdm = self.compute_rpcapi.reserve_block_device_name(
|
||||
context, instance, device, volume_id, disk_bus=disk_bus,
|
||||
device_type=device_type, tag=tag)
|
||||
|
|
|
@ -485,7 +485,7 @@ class ComputeVirtAPI(virtapi.VirtAPI):
|
|||
class ComputeManager(manager.Manager):
|
||||
"""Manages the running instances from creation to destruction."""
|
||||
|
||||
target = messaging.Target(version='4.19')
|
||||
target = messaging.Target(version='4.20')
|
||||
|
||||
# How long to wait in seconds before re-issuing a shutdown
|
||||
# signal to an instance during power off. The overall
|
||||
|
@ -5177,12 +5177,18 @@ class ComputeManager(manager.Manager):
|
|||
@reverts_task_state
|
||||
@wrap_instance_fault
|
||||
def reserve_block_device_name(self, context, instance, device,
|
||||
volume_id, disk_bus, device_type, tag=None):
|
||||
volume_id, disk_bus, device_type, tag=None,
|
||||
multiattach=False):
|
||||
if (tag and not
|
||||
self.driver.capabilities.get('supports_tagged_attach_volume',
|
||||
False)):
|
||||
raise exception.VolumeTaggedAttachNotSupported()
|
||||
|
||||
if (multiattach and not
|
||||
self.driver.capabilities.get('supports_multiattach', False)):
|
||||
raise exception.MultiattachNotSupportedByVirtDriver(
|
||||
volume_id=volume_id)
|
||||
|
||||
@utils.synchronized(instance.uuid)
|
||||
def do_reserve():
|
||||
bdms = (
|
||||
|
|
|
@ -337,6 +337,7 @@ class ComputeAPI(object):
|
|||
* 4.19 - build_and_run_instance() now gets a 'host_list' parameter
|
||||
representing potential alternate hosts for retries within a
|
||||
cell.
|
||||
* 4.20 - Add multiattach argument to reserve_block_device_name().
|
||||
'''
|
||||
|
||||
VERSION_ALIASES = {
|
||||
|
@ -965,13 +966,24 @@ class ComputeAPI(object):
|
|||
return cctxt.call(ctxt, 'get_host_uptime')
|
||||
|
||||
def reserve_block_device_name(self, ctxt, instance, device, volume_id,
|
||||
disk_bus=None, device_type=None, tag=None):
|
||||
disk_bus=None, device_type=None, tag=None,
|
||||
multiattach=False):
|
||||
kw = {'instance': instance, 'device': device,
|
||||
'volume_id': volume_id, 'disk_bus': disk_bus,
|
||||
'device_type': device_type, 'tag': tag}
|
||||
version = '4.15'
|
||||
'device_type': device_type, 'tag': tag,
|
||||
'multiattach': multiattach}
|
||||
version = '4.20'
|
||||
|
||||
client = self.router.client(ctxt)
|
||||
if not client.can_send_version(version):
|
||||
if multiattach:
|
||||
# NOTE(mriedem): Reserve attempted with a multiattach volume,
|
||||
# but the compute isn't new enough to handle that value so we
|
||||
# need to fail since the compute is too old to support it.
|
||||
raise exception.MultiattachSupportNotYetAvailable()
|
||||
version = '4.15'
|
||||
kw.pop('multiattach')
|
||||
|
||||
if not client.can_send_version(version):
|
||||
if tag:
|
||||
# NOTE(artom) Reserve attempted with a device role tag, but
|
||||
|
|
|
@ -260,6 +260,14 @@ class MultiattachNotSupportedByVirtDriver(NovaException):
|
|||
code = 409
|
||||
|
||||
|
||||
class MultiattachSupportNotYetAvailable(NovaException):
|
||||
# This exception indicates the deployment is not yet new enough to support
|
||||
# multiattach volumes, so a 409 HTTPConflict response is generally used
|
||||
# for handling this in the API.
|
||||
msg_fmt = _("Multiattach volume support is not yet available.")
|
||||
code = 409
|
||||
|
||||
|
||||
class VolumeNotCreated(NovaException):
|
||||
msg_fmt = _("Volume %(volume_id)s did not finish being created"
|
||||
" even after we waited %(seconds)s seconds or %(attempts)s"
|
||||
|
|
|
@ -31,7 +31,7 @@ LOG = logging.getLogger(__name__)
|
|||
|
||||
|
||||
# NOTE(danms): This is the global service version counter
|
||||
SERVICE_VERSION = 26
|
||||
SERVICE_VERSION = 27
|
||||
|
||||
|
||||
# NOTE(danms): This is our SERVICE_VERSION history. The idea is that any
|
||||
|
@ -122,6 +122,9 @@ SERVICE_VERSION_HISTORY = (
|
|||
{'compute_rpc': '4.18'},
|
||||
# Version 26: Adds a 'host_list' parameter to build_and_run_instance()
|
||||
{'compute_rpc': '4.19'},
|
||||
# Version 27: Compute RPC version 4.20; adds multiattach argument to
|
||||
# reserve_block_device_name().
|
||||
{'compute_rpc': '4.20'},
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -410,6 +410,34 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase):
|
|||
'fake_device', 'fake_volume_id', 'fake_disk_bus',
|
||||
'fake_device_type', tag='foo')
|
||||
|
||||
@mock.patch.object(objects.BlockDeviceMapping, 'create')
|
||||
@mock.patch.object(objects.BlockDeviceMappingList, 'get_by_instance_uuid',
|
||||
return_value=objects.BlockDeviceMappingList())
|
||||
def test_reserve_block_device_name_multiattach(self, mock_get,
|
||||
mock_create):
|
||||
"""Tests the case that multiattach=True and the driver supports it."""
|
||||
instance = fake_instance.fake_instance_obj(self.context)
|
||||
with test.nested(
|
||||
mock.patch.object(self.compute,
|
||||
'_get_device_name_for_instance',
|
||||
return_value='/dev/vda'),
|
||||
mock.patch.dict(self.compute.driver.capabilities,
|
||||
supports_multiattach=True)):
|
||||
self.compute.reserve_block_device_name(
|
||||
self.context, instance, device=None, volume_id=uuids.volume_id,
|
||||
disk_bus=None, device_type=None, multiattach=True)
|
||||
|
||||
@mock.patch.object(compute_utils, 'add_instance_fault_from_exc')
|
||||
def test_reserve_block_device_name_multiattach_raises(self, _):
|
||||
with mock.patch.dict(self.compute.driver.capabilities,
|
||||
supports_multiattach=False):
|
||||
self.assertRaises(exception.MultiattachNotSupportedByVirtDriver,
|
||||
self.compute.reserve_block_device_name,
|
||||
self.context,
|
||||
fake_instance.fake_instance_obj(self.context),
|
||||
'fake_device', 'fake_volume_id', 'fake_disk_bus',
|
||||
'fake_device_type', multiattach=True)
|
||||
|
||||
@mock.patch.object(objects.Instance, 'save')
|
||||
@mock.patch.object(time, 'sleep')
|
||||
def test_allocate_network_succeeds_after_retries(
|
||||
|
|
|
@ -535,10 +535,66 @@ class ComputeRpcAPITestCase(test.NoDBTestCase):
|
|||
self._test_compute_api('reserve_block_device_name', 'call',
|
||||
instance=self.fake_instance_obj, device='device',
|
||||
volume_id='id', disk_bus='ide', device_type='cdrom',
|
||||
tag='foo', version='4.15',
|
||||
tag='foo', multiattach=True, version='4.20',
|
||||
_return_value=objects_block_dev.BlockDeviceMapping())
|
||||
|
||||
def test_reserve_block_device_name_raises(self):
|
||||
ctxt = context.RequestContext('fake_user', 'fake_project')
|
||||
instance = self.fake_instance_obj
|
||||
rpcapi = compute_rpcapi.ComputeAPI()
|
||||
cctxt_mock = mock.Mock()
|
||||
mock_client = mock.Mock()
|
||||
rpcapi.router.client = mock.Mock()
|
||||
rpcapi.router.client.return_value = mock_client
|
||||
with test.nested(
|
||||
mock.patch.object(mock_client, 'can_send_version',
|
||||
side_effect=[False, False]),
|
||||
mock.patch.object(mock_client, 'prepare',
|
||||
return_value=cctxt_mock)
|
||||
) as (
|
||||
can_send_mock, prepare_mock
|
||||
):
|
||||
self.assertRaises(exception.TaggedAttachmentNotSupported,
|
||||
rpcapi.reserve_block_device_name, ctxt, instance,
|
||||
'fake_device', 'fake_volume_id', tag='foo')
|
||||
can_send_calls = [mock.call('4.20'), mock.call('4.15')]
|
||||
can_send_mock.assert_has_calls(can_send_calls)
|
||||
|
||||
def test_reserve_block_device_name_downgrades_version(self):
|
||||
ctxt = context.RequestContext('fake_user', 'fake_project')
|
||||
instance = self.fake_instance_obj
|
||||
rpcapi = compute_rpcapi.ComputeAPI()
|
||||
call_mock = mock.Mock()
|
||||
cctxt_mock = mock.Mock(call=call_mock)
|
||||
mock_client = mock.Mock()
|
||||
rpcapi.router.client = mock.Mock()
|
||||
rpcapi.router.client.return_value = mock_client
|
||||
with test.nested(
|
||||
mock.patch.object(mock_client, 'can_send_version',
|
||||
side_effect=[False, False]),
|
||||
mock.patch.object(mock_client, 'prepare',
|
||||
return_value=cctxt_mock)
|
||||
) as (
|
||||
can_send_mock, prepare_mock
|
||||
):
|
||||
rpcapi.reserve_block_device_name(ctxt, instance, 'fake_device',
|
||||
'fake_volume_id')
|
||||
|
||||
can_send_calls = [mock.call('4.20'), mock.call('4.15')]
|
||||
can_send_mock.assert_has_calls(can_send_calls)
|
||||
prepare_mock.assert_called_once_with(server=instance['host'],
|
||||
version='4.0')
|
||||
call_mock.assert_called_once_with(ctxt, 'reserve_block_device_name',
|
||||
instance=instance,
|
||||
device='fake_device',
|
||||
volume_id='fake_volume_id',
|
||||
disk_bus=None, device_type=None)
|
||||
|
||||
def test_reserve_block_device_name_raises_no_multiattach(self):
|
||||
"""Tests that if multiattach=True but the compute service is too
|
||||
old for the multiattach argument, an error is raised from the RPC
|
||||
client.
|
||||
"""
|
||||
ctxt = context.RequestContext('fake_user', 'fake_project')
|
||||
instance = self.fake_instance_obj
|
||||
rpcapi = compute_rpcapi.ComputeAPI()
|
||||
|
@ -554,12 +610,16 @@ class ComputeRpcAPITestCase(test.NoDBTestCase):
|
|||
) as (
|
||||
can_send_mock, prepare_mock
|
||||
):
|
||||
self.assertRaises(exception.TaggedAttachmentNotSupported,
|
||||
self.assertRaises(exception.MultiattachSupportNotYetAvailable,
|
||||
rpcapi.reserve_block_device_name, ctxt, instance,
|
||||
'fake_device', 'fake_volume_id', tag='foo')
|
||||
can_send_mock.assert_called_once_with('4.15')
|
||||
'fake_device', 'fake_volume_id',
|
||||
multiattach=True)
|
||||
can_send_mock.assert_called_once_with('4.20')
|
||||
|
||||
def test_reserve_block_device_name_downgrades_version(self):
|
||||
def test_reserve_block_device_name_downgrades_version_multiattach(self):
|
||||
"""Tests that if multiattach=False and the compute service is too
|
||||
old for the multiattach argument, it's removed from the RPC call.
|
||||
"""
|
||||
ctxt = context.RequestContext('fake_user', 'fake_project')
|
||||
instance = self.fake_instance_obj
|
||||
rpcapi = compute_rpcapi.ComputeAPI()
|
||||
|
@ -570,23 +630,23 @@ class ComputeRpcAPITestCase(test.NoDBTestCase):
|
|||
rpcapi.router.client.return_value = mock_client
|
||||
with test.nested(
|
||||
mock.patch.object(mock_client, 'can_send_version',
|
||||
return_value=False),
|
||||
side_effect=[False, True]),
|
||||
mock.patch.object(mock_client, 'prepare',
|
||||
return_value=cctxt_mock)
|
||||
) as (
|
||||
can_send_mock, prepare_mock
|
||||
):
|
||||
rpcapi.reserve_block_device_name(ctxt, instance, 'fake_device',
|
||||
'fake_volume_id')
|
||||
rpcapi.reserve_block_device_name(
|
||||
ctxt, instance, 'fake_device', 'fake_volume_id', tag='foo')
|
||||
|
||||
can_send_mock.assert_called_once_with('4.15')
|
||||
can_send_calls = [mock.call('4.20'), mock.call('4.15')]
|
||||
can_send_mock.assert_has_calls(can_send_calls)
|
||||
prepare_mock.assert_called_once_with(server=instance['host'],
|
||||
version='4.0')
|
||||
call_mock.assert_called_once_with(ctxt, 'reserve_block_device_name',
|
||||
instance=instance,
|
||||
device='fake_device',
|
||||
volume_id='fake_volume_id',
|
||||
disk_bus=None, device_type=None)
|
||||
version='4.15')
|
||||
call_mock.assert_called_once_with(
|
||||
ctxt, 'reserve_block_device_name', instance=instance,
|
||||
device='fake_device', volume_id='fake_volume_id', disk_bus=None,
|
||||
device_type=None, tag='foo')
|
||||
|
||||
def test_refresh_instance_security_rules(self):
|
||||
expected_args = {'instance': self.fake_instance_obj}
|
||||
|
|
Loading…
Reference in New Issue