Merge "Add microversion 2.67 to support volume_type"

This commit is contained in:
Zuul 2018-10-13 18:46:09 +00:00 committed by Gerrit Code Review
commit 396156eb13
19 changed files with 302 additions and 5 deletions

View File

@ -2269,6 +2269,23 @@ device_type:
in: body
required: false
type: string
device_volume_type:
description: |
The device ``volume_type``. This can be used to specify the type of volume
which the compute service will create and attach to the server.
If not specified, the block storage service will provide a default volume
type. See the `block storage volume types API <https://developer.openstack.org/api-ref/block-storage/v3/#volume-types-types>`_
for more details.
There are some restrictions on ``volume_type``:
- It can be a volume type ID or name.
- It is only supported with ``source_type`` of ``blank``, ``image`` or
``snapshot``.
- It is only supported with ``destination_type`` of ``volume``.
in: body
required: false
type: string
min_version: 2.67
# Optional input parameter in the body for PUT /os-services/{service_id} added
# in microversion 2.53.
disabled_reason_2_53_in:

View File

@ -363,6 +363,7 @@ Request
- block_device_mapping_v2.uuid: block_device_uuid
- block_device_mapping_v2.volume_size: volume_size
- block_device_mapping_v2.tag: device_tag_bdm
- block_device_mapping_v2.volume_type: device_volume_type
- config_drive: config_drive
- imageRef: imageRef
- key_name: key_name

View File

@ -0,0 +1,19 @@
{
"server" : {
"name" : "bfv-server-with-volume-type",
"flavorRef" : "http://openstack.example.com/flavors/1",
"networks" : [{
"uuid" : "ff608d40-75e9-48cb-b745-77bb55b5eaf2",
"tag": "nic1"
}],
"block_device_mapping_v2": [{
"uuid": "70a599e0-31e7-49b7-b260-868f441e862b",
"source_type": "image",
"destination_type": "volume",
"boot_index": 0,
"volume_size": "1",
"tag": "disk1",
"volume_type": "lvm-1"
}]
}
}

View File

@ -0,0 +1,22 @@
{
"server": {
"OS-DCF:diskConfig": "AUTO",
"adminPass": "S5wqy9sPYUvU",
"id": "97108291-2fd7-4dc2-a909-eaae0306a6a9",
"links": [
{
"href": "http://openstack.example.com/v2.1/6f70656e737461636b20342065766572/servers/97108291-2fd7-4dc2-a909-eaae0306a6a9",
"rel": "self"
},
{
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/servers/97108291-2fd7-4dc2-a909-eaae0306a6a9",
"rel": "bookmark"
}
],
"security_groups": [
{
"name": "default"
}
]
}
}

View File

@ -19,7 +19,7 @@
}
],
"status": "CURRENT",
"version": "2.66",
"version": "2.67",
"min_version": "2.1",
"updated": "2013-07-23T11:33:21Z"
}

View File

@ -22,7 +22,7 @@
}
],
"status": "CURRENT",
"version": "2.66",
"version": "2.67",
"min_version": "2.1",
"updated": "2013-07-23T11:33:21Z"
}

View File

@ -161,6 +161,14 @@ fields (in addition to the ones that were already there):
usage is to set it to 0 for the boot device and leave it as None for any
other devices.
* volume_type - Added in microversion 2.67 to the servers create API to
support specifying volume type when booting instances. When we snapshot a
volume-backed server, the block_device_mapping_v2 image metadata will
include the volume_type from the BDM record so if the user then creates
another server from that snapshot, the volume that nova creates from that
snapshot will use the same volume_type. If a user wishes to change that
volume type in the image metadata, they can do so via the image API.
Valid source / dest combinations
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -162,6 +162,8 @@ REST_API_VERSION_HISTORY = """REST API Version History:
``updated_at`` time to filter nova resources, the resources
include the servers API, os-instance-action API and
os-migrations API.
* 2.67 - Adds the optional ``volume_type`` field to the
``block_device_mapping_v2`` parameter when creating a server.
"""
# The minimum and maximum versions of the API supported
@ -170,7 +172,7 @@ REST_API_VERSION_HISTORY = """REST API Version History:
# Note(cyeoh): This only applies for the v2.1 API once microversions
# support is fully merged. It does not affect the V2 API.
_MIN_API_VERSION = "2.1"
_MAX_API_VERSION = "2.66"
_MAX_API_VERSION = "2.67"
DEFAULT_API_VERSION = _MIN_API_VERSION
# Almost all proxy APIs which are related to network, images and baremetal

View File

@ -349,6 +349,13 @@ base_create_v263['properties']['server']['properties'][
'trusted_image_certificates'] = parameter_types.trusted_certs
# Add volume type in block_device_mapping_v2.
base_create_v267 = copy.deepcopy(base_create_v263)
base_create_v267['properties']['server']['properties'][
'block_device_mapping_v2']['items'][
'properties']['volume_type'] = parameter_types.volume_type
base_update = {
'type': 'object',
'properties': {

View File

@ -417,7 +417,8 @@ class ServersController(wsgi.Controller):
@validation.schema(schema_servers.base_create_v242, '2.42', '2.51')
@validation.schema(schema_servers.base_create_v252, '2.52', '2.56')
@validation.schema(schema_servers.base_create_v257, '2.57', '2.62')
@validation.schema(schema_servers.base_create_v263, '2.63')
@validation.schema(schema_servers.base_create_v263, '2.63', '2.66')
@validation.schema(schema_servers.base_create_v267, '2.67')
def create(self, req, body):
"""Creates a new server for a given user."""
context = req.environ['nova.context']
@ -646,6 +647,7 @@ class ServersController(wsgi.Controller):
exception.InvalidBDMEphemeralSize,
exception.InvalidBDMFormat,
exception.InvalidBDMSwapSize,
exception.VolumeTypeNotFound,
exception.AutoDiskConfigDisabledByImage,
exception.ImageCPUPinningForbidden,
exception.ImageCPUThreadPolicyForbidden,
@ -673,6 +675,7 @@ class ServersController(wsgi.Controller):
exception.NetworkAmbiguous,
exception.NoUniqueMatch,
exception.MultiattachSupportNotYetAvailable,
exception.VolumeTypeSupportNotYetAvailable,
exception.CertificateValidationNotYetAvailable) as error:
raise exc.HTTPConflict(explanation=error.format_message())

View File

@ -374,6 +374,11 @@ volume_id = {
}
volume_type = {
'type': ['string', 'null'], 'minLength': 0, 'maxLength': 255
}
network_id = {
'type': 'string', 'format': 'uuid'
}

View File

@ -173,6 +173,7 @@ class BlockDeviceDict(dict):
source_type = api_dict.get('source_type')
device_uuid = api_dict.get('uuid')
destination_type = api_dict.get('destination_type')
volume_type = api_dict.get('volume_type')
if source_type == 'blank' and device_uuid:
raise exception.InvalidBDMFormat(
@ -191,12 +192,24 @@ class BlockDeviceDict(dict):
boot_index = -1
boot_index = int(boot_index)
# if this bdm is generated from --image ,then
# if this bdm is generated from --image, then
# source_type = image and destination_type = local is allowed
if not (image_uuid_specified and boot_index == 0):
raise exception.InvalidBDMFormat(
details=_("Mapping image to local is not supported."))
if destination_type == 'local' and volume_type:
raise exception.InvalidBDMFormat(
details=_("Specifying a volume_type with destination_type="
"local is not supported."))
# Specifying a volume_type with a pre-existing source volume is
# not supported.
if source_type == 'volume' and volume_type:
raise exception.InvalidBDMFormat(
details=_("Specifying volume type to existing volume is "
"not supported."))
api_dict.pop('uuid', None)
return cls(api_dict)

View File

@ -1722,6 +1722,13 @@ class CinderFixtureNewAttachFlow(fixtures.Fixture):
'target_lun': '1'}}}
return attachment_ref
def fake_get_all_volume_types(*args, **kwargs):
return [{
# This is used in the 2.67 API sample test.
'id': '5f9204ec-3e94-4f27-9beb-fe7bb73b6eb9',
'name': 'lvm-1'
}]
self.test.stub_out('nova.volume.cinder.API.attachment_create',
fake_attachment_create)
self.test.stub_out('nova.volume.cinder.API.attachment_delete',
@ -1745,6 +1752,8 @@ class CinderFixtureNewAttachFlow(fixtures.Fixture):
lambda *args, **kwargs: None)
self.test.stub_out('nova.volume.cinder.API.check_attached',
lambda *args, **kwargs: None)
self.test.stub_out('nova.volume.cinder.API.get_all_volume_types',
fake_get_all_volume_types)
class PlacementApiClient(object):

View File

@ -0,0 +1,19 @@
{
"server" : {
"name" : "bfv-server-with-volume-type",
"flavorRef" : "%(host)s/flavors/1",
"networks" : [{
"uuid" : "ff608d40-75e9-48cb-b745-77bb55b5eaf2",
"tag": "nic1"
}],
"block_device_mapping_v2": [{
"uuid": "%(image_id)s",
"source_type": "image",
"destination_type": "volume",
"boot_index": 0,
"volume_size": "1",
"tag": "disk1",
"volume_type": "lvm-1"
}]
}
}

View File

@ -0,0 +1,22 @@
{
"server": {
"OS-DCF:diskConfig": "AUTO",
"adminPass": "%(password)s",
"id": "%(id)s",
"links": [
{
"href": "%(versioned_compute_endpoint)s/servers/%(uuid)s",
"rel": "self"
},
{
"href": "%(compute_endpoint)s/servers/%(uuid)s",
"rel": "bookmark"
}
],
"security_groups": [
{
"name": "default"
}
]
}
}

View File

@ -21,6 +21,7 @@ import six
from nova.api.openstack import api_version_request as avr
import nova.conf
from nova.tests import fixtures as nova_fixtures
from nova.tests.functional.api_sample_tests import api_sample_base
from nova.tests.unit.api.openstack import fakes
from nova.tests.unit.image import fake
@ -353,6 +354,18 @@ class ServersSampleJson266Test(ServersSampleBase):
'servers-details-with-changes-before', subs, response, 200)
class ServersSampleJson267Test(ServersSampleBase):
microversion = '2.67'
scenarios = [('v2_67', {'api_major_version': 'v2.1'})]
def setUp(self):
super(ServersSampleJson267Test, self).setUp()
self.useFixture(nova_fixtures.CinderFixtureNewAttachFlow(self))
def test_servers_post(self):
return self._post_server(use_common_server_api_samples=False)
class ServersUpdateSampleJsonTest(ServersSampleBase):
def test_update_server(self):

View File

@ -6508,6 +6508,92 @@ class ServersControllerCreateTestV263(ServersControllerCreateTest):
six.text_type(ex))
class ServersControllerCreateTestV267(ServersControllerCreateTest):
def setUp(self):
super(ServersControllerCreateTestV267, self).setUp()
self.block_device_mapping_v2 = [
{'uuid': '70a599e0-31e7-49b7-b260-868f441e862b',
'source_type': 'image',
'destination_type': 'volume',
'boot_index': 0,
'volume_size': '1',
'volume_type': 'fake-lvm-1'
}]
def _test_create_extra(self, *args, **kwargs):
self.req.api_version_request = \
api_version_request.APIVersionRequest('2.67')
return super(ServersControllerCreateTestV267, self)._test_create_extra(
*args, **kwargs)
@mock.patch('nova.objects.Service.get_minimum_version',
return_value=compute_api.MIN_COMPUTE_VOLUME_TYPE)
def test_create_server_with_trusted_volume_type_pre_2_67_fails(self,
get_min_ver):
"""Make sure we can't use volume_type before 2.67"""
self.body['server'].update(
{'block_device_mapping_v2': self.block_device_mapping_v2})
self.req.body = jsonutils.dump_as_bytes(self.block_device_mapping_v2)
self.req.api_version_request = \
api_version_request.APIVersionRequest('2.66')
ex = self.assertRaises(
exception.ValidationError, self.controller.create, self.req,
body=self.body)
self.assertIn("'volume_type' was unexpected", six.text_type(ex))
@mock.patch.object(compute_api.API, 'create',
side_effect=exception.VolumeTypeNotFound(
id_or_name='fake-lvm-1'))
def test_create_instance_with_volume_type_not_found(self, mock_create):
"""Trying to boot from volume with a volume type that does not exist
will result in a 400 error.
"""
params = {'block_device_mapping_v2': self.block_device_mapping_v2}
ex = self.assertRaises(webob.exc.HTTPBadRequest,
self._test_create_extra, params)
self.assertIn('Volume type fake-lvm-1 could not be found',
six.text_type(ex))
@mock.patch('nova.objects.service.get_minimum_version_all_cells',
return_value=compute_api.MIN_COMPUTE_VOLUME_TYPE - 1)
def test_check_volume_type_new_inst_old_compute(self, get_min_version):
"""Trying to boot from volume with a volume_type but not all computes
are upgraded will result in a 409 error.
"""
params = {'block_device_mapping_v2': self.block_device_mapping_v2}
ex = self.assertRaises(webob.exc.HTTPConflict,
self._test_create_extra, params)
self.assertIn('Volume type support is not yet available',
six.text_type(ex))
def test_create_instance_with_volume_type_empty_string(self):
"""Test passing volume_type='' which is accepted but not used."""
self.block_device_mapping_v2[0]['volume_type'] = ''
params = {'block_device_mapping_v2': self.block_device_mapping_v2}
self._test_create_extra(params)
def test_create_instance_with_none_volume_type(self):
"""Test passing volume_type=None which is accepted but not used."""
self.block_device_mapping_v2[0]['volume_type'] = None
params = {'block_device_mapping_v2': self.block_device_mapping_v2}
self._test_create_extra(params)
def test_create_instance_without_volume_type(self):
"""Test passing without volume_type which is accepted but not used."""
self.block_device_mapping_v2[0].pop('volume_type')
params = {'block_device_mapping_v2': self.block_device_mapping_v2}
self._test_create_extra(params)
def test_create_instance_with_volume_type_too_long(self):
"""Tests the maxLength schema validation on volume_type."""
self.block_device_mapping_v2[0]['volume_type'] = 'X' * 256
params = {'block_device_mapping_v2': self.block_device_mapping_v2}
ex = self.assertRaises(exception.ValidationError,
self._test_create_extra, params)
self.assertIn('is too long', six.text_type(ex))
class ServersControllerCreateTestWithMock(test.TestCase):
image_uuid = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6'
flavor_ref = 'http://localhost/123/flavors/3'

View File

@ -17,6 +17,7 @@
Tests for Block Device utility functions.
"""
from oslo_utils.fixture import uuidsentinel as uuids
import six
from nova import block_device
from nova import exception
@ -599,6 +600,45 @@ class TestBlockDeviceDict(test.NoDBTestCase):
self.assertEqual(retexp,
block_device.BlockDeviceDict.from_api(api_dict, True))
def test_from_api_invalid_oneof_image_id_or_destination_local_mapping(
self):
api_dict = {'id': 1,
'source_type': 'image',
'destination_type': 'local',
'uuid': 'fake-volume-id-1',
'volume_type': 'fake-lvm-1',
'boot_index': 1}
ex = self.assertRaises(exception.InvalidBDMFormat,
block_device.BlockDeviceDict.from_api,
api_dict, False)
self.assertIn('Mapping image to local is not supported',
six.text_type(ex))
def test_from_api_invalid_volume_type_to_destination_local_mapping(self):
api_dict = {'id': 1,
'source_type': 'volume',
'destination_type': 'local',
'uuid': 'fake-volume-id-1',
'volume_type': 'fake-lvm-1'}
ex = self.assertRaises(exception.InvalidBDMFormat,
block_device.BlockDeviceDict.from_api,
api_dict, False)
self.assertIn('Specifying a volume_type with destination_type=local '
'is not supported', six.text_type(ex))
def test_from_api_invalid_specify_volume_type_with_source_volume_mapping(
self):
api_dict = {'id': 1,
'source_type': 'volume',
'destination_type': 'volume',
'uuid': 'fake-volume-id-1',
'volume_type': 'fake-lvm-1'}
ex = self.assertRaises(exception.InvalidBDMFormat,
block_device.BlockDeviceDict.from_api,
api_dict, False)
self.assertIn('Specifying volume type to existing volume is '
'not supported', six.text_type(ex))
def test_legacy(self):
for legacy, new in zip(self.legacy_mapping, self.new_mapping):
self.assertThat(

View File

@ -0,0 +1,11 @@
---
features:
- Microversion 2.67 adds the optional parameter ``volume_type`` to
block_device_mapping_v2, which can be used to specify ``volume_type``
when creating a server.
This would only apply to BDMs with ``source_type`` of `blank`, `image` and
`snapshot` and ``destination_type`` of `volume`.
The compute API will reject server create requests with a specified
``volume_type`` until all nova-compute services are upgraded to Stein.