diff --git a/api-ref/source/parameters.yaml b/api-ref/source/parameters.yaml index d77c7e75ef..a8a523ce0f 100644 --- a/api-ref/source/parameters.yaml +++ b/api-ref/source/parameters.yaml @@ -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 diff --git a/api-ref/source/servers.inc b/api-ref/source/servers.inc index 5afb373564..9c634bf974 100644 --- a/api-ref/source/servers.inc +++ b/api-ref/source/servers.inc @@ -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 -------- diff --git a/doc/api_samples/servers/v2.32/server-create-req.json b/doc/api_samples/servers/v2.32/server-create-req.json new file mode 100644 index 0000000000..e4f79a4393 --- /dev/null +++ b/doc/api_samples/servers/v2.32/server-create-req.json @@ -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" + }] + } +} diff --git a/doc/api_samples/servers/v2.32/server-create-resp.json b/doc/api_samples/servers/v2.32/server-create-resp.json new file mode 100644 index 0000000000..25877f96a8 --- /dev/null +++ b/doc/api_samples/servers/v2.32/server-create-resp.json @@ -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" + } + ] + } +} diff --git a/doc/api_samples/versions/v21-version-get-resp.json b/doc/api_samples/versions/v21-version-get-resp.json index 616ce04d7a..c6ddd9acaa 100644 --- a/doc/api_samples/versions/v21-version-get-resp.json +++ b/doc/api_samples/versions/v21-version-get-resp.json @@ -19,7 +19,7 @@ } ], "status": "CURRENT", - "version": "2.31", + "version": "2.32", "min_version": "2.1", "updated": "2013-07-23T11:33:21Z" } diff --git a/doc/api_samples/versions/versions-get-resp.json b/doc/api_samples/versions/versions-get-resp.json index bc03195a92..3ec0c7f2a7 100644 --- a/doc/api_samples/versions/versions-get-resp.json +++ b/doc/api_samples/versions/versions-get-resp.json @@ -22,7 +22,7 @@ } ], "status": "CURRENT", - "version": "2.31", + "version": "2.32", "min_version": "2.1", "updated": "2013-07-23T11:33:21Z" } diff --git a/nova/api/openstack/api_version_request.py b/nova/api/openstack/api_version_request.py index 38d2aab298..86d1027eda 100644 --- a/nova/api/openstack/api_version_request.py +++ b/nova/api/openstack/api_version_request.py @@ -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 diff --git a/nova/api/openstack/compute/block_device_mapping.py b/nova/api/openstack/compute/block_device_mapping.py index a449abe061..71fae8645c 100644 --- a/nova/api/openstack/compute/block_device_mapping.py +++ b/nova/api/openstack/compute/block_device_mapping.py @@ -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 diff --git a/nova/api/openstack/compute/schemas/block_device_mapping.py b/nova/api/openstack/compute/schemas/block_device_mapping.py index 6c32199180..81097f936f 100644 --- a/nova/api/openstack/compute/schemas/block_device_mapping.py +++ b/nova/api/openstack/compute/schemas/block_device_mapping.py @@ -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] + } +} diff --git a/nova/api/openstack/compute/schemas/servers.py b/nova/api/openstack/compute/schemas/servers.py index 7e912f5026..924363f592 100644 --- a/nova/api/openstack/compute/schemas/servers.py +++ b/nova/api/openstack/compute/schemas/servers.py @@ -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': { diff --git a/nova/api/openstack/compute/servers.py b/nova/api/openstack/compute/servers.py index 4d6105fe7f..8352c405f0 100644 --- a/nova/api/openstack/compute/servers.py +++ b/nova/api/openstack/compute/servers.py @@ -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', diff --git a/nova/api/openstack/rest_api_version_history.rst b/nova/api/openstack/rest_api_version_history.rst index e9674f997e..44616be553 100644 --- a/nova/api/openstack/rest_api_version_history.rst +++ b/nova/api/openstack/rest_api_version_history.rst @@ -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'). diff --git a/nova/cmd/api.py b/nova/cmd/api.py index 77955c3b1f..d8c76ca5a7 100644 --- a/nova/cmd/api.py +++ b/nova/cmd/api.py @@ -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) diff --git a/nova/cmd/api_os_compute.py b/nova/cmd/api_os_compute.py index fc9de57e49..7465382f6a 100644 --- a/nova/cmd/api_os_compute.py +++ b/nova/cmd/api_os_compute.py @@ -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) diff --git a/nova/service.py b/nova/service.py index 22ddd218d8..8d85b502eb 100644 --- a/nova/service.py +++ b/nova/service.py @@ -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. diff --git a/nova/tests/functional/api_sample_tests/api_samples/servers/v2.32/server-create-req.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/servers/v2.32/server-create-req.json.tpl new file mode 100644 index 0000000000..87a6f49f04 --- /dev/null +++ b/nova/tests/functional/api_sample_tests/api_samples/servers/v2.32/server-create-req.json.tpl @@ -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" + }] + } +} diff --git a/nova/tests/functional/api_sample_tests/api_samples/servers/v2.32/server-create-resp.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/servers/v2.32/server-create-resp.json.tpl new file mode 100644 index 0000000000..4b30e0cfbd --- /dev/null +++ b/nova/tests/functional/api_sample_tests/api_samples/servers/v2.32/server-create-resp.json.tpl @@ -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" + } + ] + } +} diff --git a/nova/tests/functional/api_sample_tests/test_servers.py b/nova/tests/functional/api_sample_tests/test_servers.py index c8dd330bb8..a2686436a1 100644 --- a/nova/tests/functional/api_sample_tests/test_servers.py +++ b/nova/tests/functional/api_sample_tests/test_servers.py @@ -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): diff --git a/nova/tests/unit/api/openstack/compute/test_serversV21.py b/nova/tests/unit/api/openstack/compute/test_serversV21.py index ed3186cd38..8c3354c472 100644 --- a/nova/tests/unit/api/openstack/compute/test_serversV21.py +++ b/nova/tests/unit/api/openstack/compute/test_serversV21.py @@ -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 {} diff --git a/nova/tests/unit/cmd/test_nova_api.py b/nova/tests/unit/cmd/test_nova_api.py index d9477afbb8..c613c1c0e8 100644 --- a/nova/tests/unit/cmd/test_nova_api.py +++ b/nova/tests/unit/cmd/test_nova_api.py @@ -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) diff --git a/releasenotes/notes/virtual-device-role-tagging-ec0c36226a3f2e4d.yaml b/releasenotes/notes/virtual-device-role-tagging-ec0c36226a3f2e4d.yaml new file mode 100644 index 0000000000..77d94ccec9 --- /dev/null +++ b/releasenotes/notes/virtual-device-role-tagging-ec0c36226a3f2e4d.yaml @@ -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.