Merge "Device tagging API support"

This commit is contained in:
Jenkins 2016-06-30 15:00:38 +00:00 committed by Gerrit Code Review
commit 5b31a0dac8
21 changed files with 330 additions and 14 deletions

View File

@ -1006,7 +1006,14 @@ block_device_mapping_v2:
"source_type": "image",
"volume_size": "25",
"destination_type": "volume",
"delete_on_termination": true }
"delete_on_termination": true,
"tag": "disk1" }
Starting in microversion 2.32, the tag is an optional, arbitrary attribute
that can be used to assign a tag to the block device. This tag is then
exposed to the guest in the metadata API and the config drive and is
associated to hardware metadata for that block device, such as bus (ex:
SCSI), bus address (ex: 1:0:2:0), and serial.
in: body
required: false
type: object
@ -1280,6 +1287,13 @@ device_resp:
in: body
required: true
type: string
device_tag:
description: |
An arbitrary tag.
in: body
required: false
type: string
min_version: 2.32
disabled_reason_body:
description: |
The reason for disabling a service.
@ -2627,6 +2641,13 @@ networks:
a network, specify the UUID of the network in the ``uuid`` attribute in a ``networks``
object. To provision the server instance with a NIC for an already existing port,
specify the port-id in the ``port`` attribute in a ``networks`` object.
Starting in microversion 2.32, it's possible to optionally assign an
arbitrary tag to a virtual network interface, specify the tag attribute in
the ``network`` object. An interface's tag is exposed to the guest in the
metadata API and the config drive and is associated to hardware metadata
for that network interface, such as bus (ex: PCI), bus address (ex:
0000:00:02.0), and MAC address.
in: body
required: false
type: object

View File

@ -293,6 +293,7 @@ Request
- networks.uuid: network_uuid
- networks.port: port
- networks.fixed_ip: fixed_ip
- networks.tag: device_tag
- personality: personality
- block_device_mapping_v2: block_device_mapping_v2
- block_device_mapping_v2.device_name: device_name
@ -301,6 +302,7 @@ Request
- block_device_mapping_v2.delete_on_termination: delete_on_termination
- block_device_mapping_v2.guest_format: guest_format
- block_device_mapping_v2.boot_index: boot_index
- block_device_mapping_v2.tag: device_tag
- config_drive: config_drive
- key_name: key_name
- os:scheduler_hints: os:scheduler_hints
@ -312,6 +314,11 @@ Request
.. literalinclude:: ../../doc/api_samples/servers/server-create-req.json
:language: javascript
**Example Create Server (v2.32)**
.. literalinclude:: ../../doc/api_samples/servers/v2.32/server-create-req.json
:language: javascript
Response
--------

View File

@ -0,0 +1,18 @@
{
"server" : {
"name" : "device-tagging-server",
"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"
}]
}
}

View File

@ -0,0 +1,22 @@
{
"server": {
"adminPass": "rojsEujtu7GB",
"OS-DCF:diskConfig": "AUTO",
"id": "05ec6bde-40bf-47e8-ac07-89c12b2eee03",
"links": [
{
"href": "http://openstack.example.com/v2.1/6f70656e737461636b20342065766572/servers/05ec6bde-40bf-47e8-ac07-89c12b2eee03",
"rel": "self"
},
{
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/servers/05ec6bde-40bf-47e8-ac07-89c12b2eee03",
"rel": "bookmark"
}
],
"security_groups": [
{
"name": "default"
}
]
}
}

View File

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

View File

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

View File

@ -80,6 +80,8 @@ REST_API_VERSION_HISTORY = """REST API Version History:
* 2.30 - Add a force flag in live-migrate request body and change the
behaviour for the host flag by calling the scheduler.
* 2.31 - Fix os-console-auth-tokens to work for all console types.
* 2.32 - Add tag to networks and block_device_mapping_v2 in server boot
request body.
"""
# The minimum and maximum versions of the API supported
@ -88,7 +90,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.31"
_MAX_API_VERSION = "2.32"
DEFAULT_API_VERSION = _MIN_API_VERSION

View File

@ -74,4 +74,7 @@ class BlockDeviceMapping(extensions.V21APIExtensionBase):
create_kwargs['legacy_bdm'] = False
def get_server_create_schema(self, version):
return schema_block_device_mapping.server_create
if version == '2.32':
return schema_block_device_mapping.server_create_v232
else:
return schema_block_device_mapping.server_create

View File

@ -15,6 +15,7 @@
import copy
from nova.api.openstack.compute.schemas import block_device_mapping_v1
from nova.api.openstack.compute.schemas import server_tags
from nova.api.validation import parameter_types
from nova.objects import fields
@ -71,3 +72,18 @@ server_create = {
'items': [block_device_mapping]
}
}
block_device_mapping_v232_new_item = {
'tag': server_tags.tag
}
block_device_mapping_v232 = copy.deepcopy(block_device_mapping)
block_device_mapping_v232['properties'].update(
block_device_mapping_v232_new_item)
server_create_v232 = {
'block_device_mapping_v2': {
'type': 'array',
'items': [block_device_mapping_v232]
}
}

View File

@ -14,6 +14,7 @@
import copy
from nova.api.openstack.compute.schemas import server_tags
from nova.api.validation import parameter_types
@ -67,6 +68,12 @@ base_create_v219['properties']['server'][
'properties']['description'] = parameter_types.description
base_create_v232 = copy.deepcopy(base_create_v219)
base_create_v232['properties']['server'][
'properties']['networks']['items'][
'properties']['tag'] = server_tags.tag
base_update = {
'type': 'object',
'properties': {

View File

@ -39,6 +39,7 @@ from nova import compute
from nova.compute import flavors
from nova.compute import utils as compute_utils
import nova.conf
from nova import context as nova_context
from nova import exception
from nova.i18n import _
from nova.i18n import _LW
@ -49,6 +50,7 @@ from nova import utils
ALIAS = 'servers'
TAG_SEARCH_FILTERS = ('tags', 'tags-any', 'not-tags', 'not-tags-any')
DEVICE_TAGGING_MIN_COMPUTE_VERSION = 14
CONF = nova.conf.CONF
@ -75,6 +77,8 @@ class ServersController(wsgi.Controller):
schema_server_update_v219 = schema_servers.base_update_v219
schema_server_rebuild_v219 = schema_servers.base_rebuild_v219
schema_server_create_v232 = schema_servers.base_create_v232
@staticmethod
def _add_location(robj):
# Just in case...
@ -166,6 +170,9 @@ class ServersController(wsgi.Controller):
invoke_kwds={"extension_info": self.extension_info},
propagate_map_exceptions=True)
if list(self.create_schema_manager):
self.create_schema_manager.map(self._create_extension_schema,
self.schema_server_create_v232,
'2.32')
self.create_schema_manager.map(self._create_extension_schema,
self.schema_server_create_v219,
'2.19')
@ -386,7 +393,8 @@ class ServersController(wsgi.Controller):
expl = _("Duplicate networks (%s) are not allowed") % net_id
raise exc.HTTPBadRequest(explanation=expl)
def _get_requested_networks(self, requested_networks):
def _get_requested_networks(self, requested_networks,
supports_device_tagging=False):
"""Create a list of requested networks from the networks attribute."""
networks = []
network_uuids = []
@ -399,6 +407,11 @@ class ServersController(wsgi.Controller):
request.address = network.get('fixed_ip', None)
request.port_id = network.get('port', None)
request.tag = network.get('tag', None)
if request.tag and not supports_device_tagging:
msg = _('Network interface tags are not yet supported.')
raise exc.HTTPBadRequest(explanation=msg)
if request.port_id:
request.network_id = None
if not utils.is_neutron():
@ -456,7 +469,8 @@ class ServersController(wsgi.Controller):
@extensions.expected_errors((400, 403, 409))
@validation.schema(schema_server_create_v20, '2.0', '2.0')
@validation.schema(schema_server_create, '2.1', '2.18')
@validation.schema(schema_server_create_v219, '2.19')
@validation.schema(schema_server_create_v219, '2.19', '2.31')
@validation.schema(schema_server_create_v232, '2.32')
def create(self, req, body):
"""Creates a new server for a given user."""
@ -511,12 +525,21 @@ class ServersController(wsgi.Controller):
if host or node:
context.can(server_policies.SERVERS % 'create:forced_host', {})
min_compute_version = objects.Service.get_minimum_version(
nova_context.get_admin_context(), 'nova-compute')
supports_device_tagging = (min_compute_version >=
DEVICE_TAGGING_MIN_COMPUTE_VERSION)
block_device_mapping = create_kwargs.get("block_device_mapping")
# TODO(Shao He, Feng) move this policy check to os-block-device-mapping
# extension after refactor it.
if block_device_mapping:
context.can(server_policies.SERVERS % 'create:attach_volume',
target)
for bdm in block_device_mapping:
if bdm.get('tag', None) and not supports_device_tagging:
msg = _('Block device tags are not yet supported.')
raise exc.HTTPBadRequest(explanation=msg)
image_uuid = self._image_from_req_data(server_dict, create_kwargs)
@ -537,7 +560,7 @@ class ServersController(wsgi.Controller):
if requested_networks is not None:
requested_networks = self._get_requested_networks(
requested_networks)
requested_networks, supports_device_tagging)
if requested_networks and len(requested_networks):
context.can(server_policies.SERVERS % 'create:attach_network',

View File

@ -324,3 +324,14 @@ user documentation.
Fix os-console-auth-tokens to return connection info for all types of tokens,
not just RDP.
2.32
----
Adds an optional, arbitrary 'tag' item to the 'networks' item in the server
boot request body. In addition, every item in the block_device_mapping_v2
array can also have an optional, arbitrary 'tag' item. These tags are used to
identify virtual device metadata, as exposed in the metadata API and on the
config drive. For example, a network interface on the virtual PCI bus tagged
with 'nic1' will appear in the metadata along with its bus (PCI), bus address
(ex: 0000:00:02.0), MAC address, and tag ('nic1').

View File

@ -43,6 +43,13 @@ def main():
logging.setup(CONF, "nova")
utils.monkey_patch()
objects.register_all()
if 'osapi_compute' in CONF.enabled_apis:
# NOTE(mriedem): This is needed for caching the nova-compute service
# version which is looked up when a server create request is made with
# network id of 'auto' or 'none'.
# TODO(mriedem): Remove this in Ocata when all computes should be
# at least Newton.
objects.Service.enable_min_version_cache()
log = logging.getLogger(__name__)
gmr.TextGuruMeditation.setup_autorun(version)

View File

@ -37,6 +37,11 @@ def main():
logging.setup(CONF, "nova")
utils.monkey_patch()
objects.register_all()
# NOTE(mriedem): This is needed for caching the nova-compute service
# version which is looked up when a server create request is made with
# network id of 'auto' or 'none'.
# TODO(mriedem): Remove this in Ocata when all computes should be Newton.
objects.Service.enable_min_version_cache()
gmr.TextGuruMeditation.setup_autorun(version)

View File

@ -314,12 +314,13 @@ class WSGIService(service.Service):
self.backdoor_port = None
def reset(self):
"""Reset server greenpool size to default.
"""Reset server greenpool size to default and service version cache.
:returns: None
"""
self.server.reset()
service_obj.Service.clear_min_version_cache()
def _get_manager(self):
"""Initialize a Manager object appropriate for this service.

View File

@ -0,0 +1,18 @@
{
"server" : {
"name" : "device-tagging-server",
"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"
}]
}
}

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

@ -148,6 +148,15 @@ class ServersSampleJson219Test(ServersSampleJsonTest):
self._verify_response('server-put-resp', subs, response, 200)
class ServersSampleJson232Test(ServersSampleBase):
microversion = '2.32'
sample_dir = 'servers'
scenarios = [('v2_32', {'api_major_version': 'v2.1'})]
def test_servers_post(self):
self._post_server(use_common_server_api_samples=False)
class ServersUpdateSampleJsonTest(ServersSampleBase):
def test_update_server(self):

View File

@ -3440,6 +3440,100 @@ class ServersControllerCreateTestV219(ServersControllerCreateTest):
self.req, body=self.body)
class ServersControllerCreateTestV232(test.NoDBTestCase):
def setUp(self):
super(ServersControllerCreateTestV232, self).setUp()
self.flags(use_neutron=True)
ext_info = extension_info.LoadedExtensionInfo()
self.controller = servers.ServersController(extension_info=ext_info)
self.body = {
'server': {
'name': 'device-tagging-server',
'imageRef': '6b0edabb-8cde-4684-a3f4-978960a51378',
'flavorRef': '2',
'networks': [{
'uuid': 'ff608d40-75e9-48cb-b745-77bb55b5eaf2'
}],
'block_device_mapping_v2': [{
'uuid': '70a599e0-31e7-49b7-b260-868f441e862b',
'source_type': 'image',
'destination_type': 'volume',
'boot_index': 0,
'volume_size': '1'
}]
}
}
self.req = fakes.HTTPRequestV21.blank('/fake/servers', version='2.32')
self.req.method = 'POST'
self.req.headers['content-type'] = 'application/json'
def _create_server(self):
self.req.body = jsonutils.dump_as_bytes(self.body)
self.controller.create(self.req, body=self.body)
def test_create_server_no_tags_old_compute(self):
with test.nested(
mock.patch.object(objects.Service, 'get_minimum_version',
return_value=13),
mock.patch.object(nova.compute.flavors, 'get_flavor_by_flavor_id',
return_value=objects.Flavor()),
mock.patch.object(
compute_api.API, 'create',
return_value=(
[{'uuid': 'f60012d9-5ba4-4547-ab48-f94ff7e62d4e'}],
1)),
):
self._create_server()
@mock.patch.object(objects.Service, 'get_minimum_version',
return_value=13)
def test_create_server_tagged_nic_old_compute_fails(self, get_min_ver):
self.body['server']['networks'][0]['tag'] = 'foo'
self.assertRaises(webob.exc.HTTPBadRequest, self._create_server)
@mock.patch.object(objects.Service, 'get_minimum_version',
return_value=13)
def test_create_server_tagged_bdm_old_compute_fails(self, get_min_ver):
self.body['server']['block_device_mapping_v2'][0]['tag'] = 'foo'
self.assertRaises(webob.exc.HTTPBadRequest, self._create_server)
def test_create_server_tagged_nic_new_compute(self):
with test.nested(
mock.patch.object(objects.Service, 'get_minimum_version',
return_value=14),
mock.patch.object(nova.compute.flavors, 'get_flavor_by_flavor_id',
return_value=objects.Flavor()),
mock.patch.object(
compute_api.API, 'create',
return_value=(
[{'uuid': 'f60012d9-5ba4-4547-ab48-f94ff7e62d4e'}],
1)),
):
self.body['server']['networks'][0]['tag'] = 'foo'
self._create_server()
def test_create_server_tagged_bdm_new_compute(self):
with test.nested(
mock.patch.object(objects.Service, 'get_minimum_version',
return_value=14),
mock.patch.object(nova.compute.flavors, 'get_flavor_by_flavor_id',
return_value=objects.Flavor()),
mock.patch.object(
compute_api.API, 'create',
return_value=(
[{'uuid': 'f60012d9-5ba4-4547-ab48-f94ff7e62d4e'}],
1)),
mock.patch.object(self.req, 'cache_db_instances'),
mock.patch.object(self.controller, '_add_location',
return_value=None)
):
self.body['server']['block_device_mapping_v2'][0]['tag'] = 'foo'
self._create_server()
class ServersControllerCreateTestWithMock(test.TestCase):
image_uuid = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6'
flavor_ref = 'http://localhost/123/flavors/3'
@ -4129,7 +4223,7 @@ class FakeExt(extensions.V21APIExtensionBase):
pass
def fake_schema_extension_point(self, version):
if version == '2.1' or version == '2.19':
if version in ('2.1', '2.19', '2.32'):
return self.fake_schema
elif version == '2.0':
return {}

View File

@ -22,10 +22,12 @@ from nova import test
# required because otherwise oslo early parse_args dies
@mock.patch.object(config, 'parse_args', new=lambda *args, **kwargs: None)
# required so we don't set the global service version cache
@mock.patch('nova.objects.Service.enable_min_version_cache')
class TestNovaAPI(test.NoDBTestCase):
@mock.patch('nova.service.process_launcher')
def test_with_ec2(self, launcher):
def test_with_ec2(self, launcher, version_cache):
"""Ensure that we don't explode if enabled_apis is wrong.
If the end user hasn't updated their config, an ec2 entry
@ -40,8 +42,9 @@ class TestNovaAPI(test.NoDBTestCase):
# collide on ports.
self.flags(osapi_compute_listen_port=0)
api.main()
version_cache.assert_called_once_with()
def test_continues_on_failure(self):
def test_continues_on_failure(self, version_cache):
count = [1, 2]
fake_server = mock.MagicMock()
@ -65,9 +68,10 @@ class TestNovaAPI(test.NoDBTestCase):
launcher = mock_service.process_launcher.return_value
launcher.launch_service.assert_called_once_with(
fake_server, workers=123)
self.assertFalse(version_cache.called)
@mock.patch('sys.exit')
def test_fails_if_none_started(self, mock_exit):
def test_fails_if_none_started(self, mock_exit, version_cache):
mock_exit.side_effect = test.TestingException
self.flags(enabled_apis=[])
with mock.patch.object(api, 'service') as mock_service:
@ -75,9 +79,10 @@ class TestNovaAPI(test.NoDBTestCase):
mock_exit.assert_called_once_with(1)
launcher = mock_service.process_launcher.return_value
self.assertFalse(launcher.wait.called)
self.assertFalse(version_cache.called)
@mock.patch('sys.exit')
def test_fails_if_all_failed(self, mock_exit):
def test_fails_if_all_failed(self, mock_exit, version_cache):
mock_exit.side_effect = test.TestingException
self.flags(enabled_apis=['foo', 'bar'])
with mock.patch.object(api, 'service') as mock_service:
@ -87,3 +92,4 @@ class TestNovaAPI(test.NoDBTestCase):
mock_exit.assert_called_once_with(1)
launcher = mock_service.process_launcher.return_value
self.assertFalse(launcher.wait.called)
self.assertFalse(version_cache.called)

View File

@ -0,0 +1,24 @@
---
features:
- |
The 2.32 microverison adds support for virtual device
role tagging. Device role tagging is an answer to the
question 'Which device is which, inside the guest?' When
booting an instance, an optional arbitrary 'tag'
parameter can be set on virtual network interfaces
and/or block device mappings. This tag is exposed to the
instance through the metadata API and on the config
drive. Each tagged virtual network interface is listed
along with information about the virtual hardware, such
as bus type (ex: PCI), bus address (ex: 0000:00:02.0),
and MAC address. For tagged block devices, the exposed
hardware metadata includes the bus (ex: SCSI), bus
address (ex: 1:0:2:0) and serial number.
issues:
- When using virtual device role tagging, the metadata on
the config drive lags behind the metadata obtained from
the metadata API. For example, if a tagged virtual
network interface is detached from the instance, its tag
remains in the metadata on the config drive. This is due
to the nature of the config drive, which, once written,
cannot be easily updated by Nova.