diff --git a/doc/api_schemas/config_drive.json b/doc/api_schemas/config_drive.json new file mode 100644 index 000000000000..d4ba5e7d2676 --- /dev/null +++ b/doc/api_schemas/config_drive.json @@ -0,0 +1,30 @@ +{ + "anyOf": [ + { + "type": "object", + "properties": { + "meta_data": { + "type": "object" + }, + "network_data": { + "type": "object" + }, + "user_data": { + "type": [ + "object", + "array", + "string", + "null" + ] + } + }, + "additionalProperties": false + }, + { + "type": [ + "string", + "null" + ] + } + ] +} diff --git a/doc/api_schemas/network_data.json b/doc/api_schemas/network_data.json new file mode 100644 index 000000000000..7162daf3431b --- /dev/null +++ b/doc/api_schemas/network_data.json @@ -0,0 +1,580 @@ +{ + "$schema": "http://openstack.org/nova/network_data.json#", + "id": "http://openstack.org/nova/network_data.json", + "type": "object", + "title": "OpenStack Nova network metadata schema", + "description": "Schema of Nova instance network configuration information", + "required": [ + "links", + "networks", + "services" + ], + "properties": { + "links": { + "$id": "#/properties/links", + "type": "array", + "title": "L2 interfaces settings", + "items": { + "$id": "#/properties/links/items", + "oneOf": [ + { + "$ref": "#/definitions/l2_link" + }, + { + "$ref": "#/definitions/l2_bond" + }, + { + "$ref": "#/definitions/l2_vlan" + } + ] + } + }, + "networks": { + "$id": "#/properties/networks", + "type": "array", + "title": "L3 networks", + "items": { + "$id": "#/properties/networks/items", + "oneOf": [ + { + "$ref": "#/definitions/l3_ipv4_network" + }, + { + "$ref": "#/definitions/l3_ipv6_network" + } + ] + } + }, + "services": { + "$ref": "#/definitions/services" + } + }, + "definitions": { + "l2_address": { + "$id": "#/definitions/l2_address", + "type": "string", + "pattern": "(?i)^([0-9A-F]{2}[:-]){5}([0-9A-F]{2})$", + "title": "L2 interface address", + "examples": [ + "fa:16:3e:9c:bf:3d" + ] + }, + "l2_id": { + "$id": "#/definitions/l2_id", + "type": "string", + "title": "L2 interface ID", + "examples": [ + "eth0" + ] + }, + "l2_mtu": { + "$id": "#/definitions/l2_mtu", + "title": "L2 interface MTU", + "anyOf": [ + { + "type": "number", + "minimum": 1, + "maximum": 65535 + }, + { + "type": "null" + } + ], + "examples": [ + 1500 + ] + }, + "l2_vif_id": { + "$id": "#/definitions/l2_vif_id", + "type": "string", + "title": "Virtual interface ID", + "examples": [ + "cd9f6d46-4a3a-43ab-a466-994af9db96fc" + ] + }, + "l2_link": { + "$id": "#/definitions/l2_link", + "type": "object", + "title": "L2 interface configuration settings", + "required": [ + "ethernet_mac_address", + "id", + "type" + ], + "properties": { + "id": { + "$ref": "#/definitions/l2_id" + }, + "ethernet_mac_address": { + "$ref": "#/definitions/l2_address" + }, + "mtu": { + "$ref": "#/definitions/l2_mtu" + }, + "type": { + "$id": "#/definitions/l2_link/properties/type", + "type": "string", + "enum": [ + "bridge", + "dvs", + "hw_veb", + "hyperv", + "ovs", + "tap", + "vhostuser", + "vif", + "phy" + ], + "title": "Interface type", + "examples": [ + "bridge" + ] + }, + "vif_id": { + "$ref": "#/definitions/l2_vif_id" + } + } + }, + "l2_bond": { + "$id": "#/definitions/l2_bond", + "type": "object", + "title": "L2 bonding interface configuration settings", + "required": [ + "ethernet_mac_address", + "id", + "type", + "bond_mode", + "bond_links" + ], + "properties": { + "id": { + "$ref": "#/definitions/l2_id" + }, + "ethernet_mac_address": { + "$ref": "#/definitions/l2_address" + }, + "mtu": { + "$ref": "#/definitions/l2_mtu" + }, + "type": { + "$id": "#/definitions/l2_bond/properties/type", + "type": "string", + "enum": [ + "bond" + ], + "title": "Interface type", + "examples": [ + "bond" + ] + }, + "vif_id": { + "$ref": "#/definitions/l2_vif_id" + }, + "bond_mode": { + "$id": "#/definitions/bond/properties/bond_mode", + "type": "string", + "title": "Port bonding type", + "enum": [ + "802.1ad", + "balance-rr", + "active-backup", + "balance-xor", + "broadcast", + "balance-tlb", + "balance-alb" + ], + "examples": [ + "802.1ad" + ] + }, + "bond_links": { + "$id": "#/definitions/bond/properties/bond_links", + "type": "array", + "title": "Port bonding links", + "items": { + "$id": "#/definitions/bond/properties/bond_links/items", + "type": "string" + } + } + } + }, + "l2_vlan": { + "$id": "#/definitions/l2_vlan", + "type": "object", + "title": "L2 VLAN interface configuration settings", + "required": [ + "vlan_mac_address", + "id", + "type", + "vlan_link", + "vlan_id" + ], + "properties": { + "id": { + "$ref": "#/definitions/l2_id" + }, + "vlan_mac_address": { + "$ref": "#/definitions/l2_address" + }, + "mtu": { + "$ref": "#/definitions/l2_mtu" + }, + "type": { + "$id": "#/definitions/l2_vlan/properties/type", + "type": "string", + "enum": [ + "vlan" + ], + "title": "VLAN interface type", + "examples": [ + "vlan" + ] + }, + "vif_id": { + "$ref": "#/definitions/l2_vif_id" + }, + "vlan_id": { + "$id": "#/definitions/l2_vlan/properties/vlan_id", + "type": "integer", + "title": "VLAN ID" + }, + "vlan_link": { + "$id": "#/definitions/l2_vlan/properties/vlan_link", + "type": "string", + "title": "VLAN link name" + } + } + }, + "l3_id": { + "$id": "#/definitions/l3_id", + "type": "string", + "title": "Network name", + "examples": [ + "network0" + ] + }, + "l3_link": { + "$id": "#/definitions/l3_link", + "type": "string", + "title": "L2 network link to use for L3 interface", + "examples": [ + "99e88329-f20d-4741-9593-25bf07847b16" + ] + }, + "l3_network_id": { + "$id": "#/definitions/l3_network_id", + "type": "string", + "title": "Network ID", + "examples": [ + "99e88329-f20d-4741-9593-25bf07847b16" + ] + }, + "l3_ipv4_type": { + "$id": "#/definitions/l3_ipv4_type", + "type": "string", + "enum": [ + "ipv4", + "ipv4_dhcp" + ], + "title": "L3 IPv4 network type", + "examples": [ + "ipv4_dhcp" + ] + }, + "l3_ipv6_type": { + "$id": "#/definitions/l3_ipv6_type", + "type": "string", + "enum": [ + "ipv6", + "ipv6_dhcp", + "ipv6_slaac" + ], + "title": "L3 IPv6 network type", + "examples": [ + "ipv6_dhcp" + ] + }, + "l3_ipv4_host": { + "$id": "#/definitions/l3_ipv4_host", + "type": "string", + "pattern": "^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$", + "title": "L3 IPv4 host address", + "examples": [ + "192.168.81.99" + ] + }, + "l3_ipv6_host": { + "$id": "#/definitions/l3_ipv6_host", + "type": "string", + "pattern": "^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))(/[0-9]{1,2})?$", + "title": "L3 IPv6 host address", + "examples": [ + "2001:db8:3:4::192.168.81.99" + ] + }, + "l3_ipv4_netmask": { + "$id": "#/definitions/l3_ipv4_netmask", + "type": "string", + "pattern": "^(254|252|248|240|224|192|128|0)\\.0\\.0\\.0|255\\.(254|252|248|240|224|192|128|0)\\.0\\.0|255\\.255\\.(254|252|248|240|224|192|128|0)\\.0|255\\.255\\.255\\.(254|252|248|240|224|192|128|0)$", + "title": "L3 IPv4 network mask", + "examples": [ + "255.255.252.0" + ] + }, + "l3_ipv6_netmask": { + "$id": "#/definitions/l3_ipv6_netmask", + "type": "string", + "pattern": "^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7})|(::))$", + "title": "L3 IPv6 network mask", + "examples": [ + "ffff:ffff:ffff:ffff::" + ] + }, + "l3_ipv4_nw": { + "$id": "#/definitions/l3_ipv4_nw", + "type": "string", + "pattern": "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$", + "title": "L3 IPv4 network address", + "examples": [ + "0.0.0.0" + ] + }, + "l3_ipv6_nw": { + "$id": "#/definitions/l3_ipv6_nw", + "type": "string", + "pattern": "^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7})|(::))$", + "title": "L3 IPv6 network address", + "examples": [ + "8000::" + ] + }, + "l3_ipv4_gateway": { + "$id": "#/definitions/l3_ipv4_gateway", + "type": "string", + "pattern": "^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$", + "title": "L3 IPv4 gateway address", + "examples": [ + "192.168.200.1" + ] + }, + "l3_ipv6_gateway": { + "$id": "#/definitions/l3_ipv6_gateway", + "type": "string", + "pattern": "^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$", + "title": "L3 IPv6 gateway address", + "examples": [ + "2001:db8:3:4::192.168.81.99" + ] + }, + "l3_ipv4_network_route": { + "$id": "#/definitions/l3_ipv4_network_route", + "type": "object", + "title": "L3 IPv4 routing configuration item", + "required": [ + "gateway", + "netmask", + "network" + ], + "properties": { + "network": { + "$ref": "#/definitions/l3_ipv4_nw" + }, + "netmask": { + "$ref": "#/definitions/l3_ipv4_netmask" + }, + "gateway": { + "$ref": "#/definitions/l3_ipv4_gateway" + }, + "services": { + "$ref": "#/definitions/ipv4_services" + } + } + }, + "l3_ipv6_network_route": { + "$id": "#/definitions/l3_ipv6_network_route", + "type": "object", + "title": "L3 IPv6 routing configuration item", + "required": [ + "gateway", + "netmask", + "network" + ], + "properties": { + "network": { + "$ref": "#/definitions/l3_ipv6_nw" + }, + "netmask": { + "$ref": "#/definitions/l3_ipv6_netmask" + }, + "gateway": { + "$ref": "#/definitions/l3_ipv6_gateway" + }, + "services": { + "$ref": "#/definitions/ipv6_services" + } + } + }, + "l3_ipv4_network": { + "$id": "#/definitions/l3_ipv4_network", + "type": "object", + "title": "L3 IPv4 network configuration", + "required": [ + "id", + "link", + "network_id", + "type" + ], + "properties": { + "id": { + "$ref": "#/definitions/l3_id" + }, + "link": { + "$ref": "#/definitions/l3_link" + }, + "network_id": { + "$ref": "#/definitions/l3_network_id" + }, + "type": { + "$ref": "#/definitions/l3_ipv4_type" + }, + "ip_address": { + "$ref": "#/definitions/l3_ipv4_host" + }, + "netmask": { + "$ref": "#/definitions/l3_ipv4_netmask" + }, + "routes": { + "$id": "#/definitions/l3_ipv4_network/routes", + "type": "array", + "title": "L3 IPv4 network routes", + "items": { + "$ref": "#/definitions/l3_ipv4_network_route" + } + } + } + }, + "l3_ipv6_network": { + "$id": "#/definitions/l3_ipv6_network", + "type": "object", + "title": "L3 IPv6 network configuration", + "required": [ + "id", + "link", + "network_id", + "type" + ], + "properties": { + "id": { + "$ref": "#/definitions/l3_id" + }, + "link": { + "$ref": "#/definitions/l3_link" + }, + "network_id": { + "$ref": "#/definitions/l3_network_id" + }, + "type": { + "$ref": "#/definitions/l3_ipv6_type" + }, + "ip_address": { + "$ref": "#/definitions/l3_ipv6_host" + }, + "netmask": { + "$ref": "#/definitions/l3_ipv6_netmask" + }, + "routes": { + "$id": "#/definitions/properties/l3_ipv6_network/routes", + "type": "array", + "title": "L3 IPv6 network routes", + "items": { + "$ref": "#/definitions/l3_ipv6_network_route" + } + } + } + }, + "ipv4_service": { + "$id": "#/definitions/ipv4_service", + "type": "object", + "title": "Service on a IPv4 network", + "required": [ + "address", + "type" + ], + "properties": { + "address": { + "$ref": "#/definitions/l3_ipv4_host" + }, + "type": { + "$id": "#/definitions/ipv4_service/properties/type", + "type": "string", + "enum": [ + "dns" + ], + "title": "Service type", + "examples": [ + "dns" + ] + } + } + }, + "ipv6_service": { + "$id": "#/definitions/ipv6_service", + "type": "object", + "title": "Service on a IPv6 network", + "required": [ + "address", + "type" + ], + "properties": { + "address": { + "$ref": "#/definitions/l3_ipv6_host" + }, + "type": { + "$id": "#/definitions/ipv4_service/properties/type", + "type": "string", + "enum": [ + "dns" + ], + "title": "Service type", + "examples": [ + "dns" + ] + } + } + }, + "ipv4_services": { + "$id": "#/definitions/ipv4_services", + "type": "array", + "title": "Network services on IPv4 network", + "items": { + "$id": "#/definitions/ipv4_services/items", + "$ref": "#/definitions/ipv4_service" + } + }, + "ipv6_services": { + "$id": "#/definitions/ipv6_services", + "type": "array", + "title": "Network services on IPv6 network", + "items": { + "$id": "#/definitions/ipv6_services/items", + "$ref": "#/definitions/ipv6_service" + } + }, + "services": { + "$id": "#/definitions/services", + "type": "array", + "title": "Network services", + "items": { + "$id": "#/definitions/services/items", + "anyOf": [ + { + "$ref": "#/definitions/ipv4_service" + }, + { + "$ref": "#/definitions/ipv6_service" + } + ] + } + } + } +} diff --git a/doc/source/user/metadata.rst b/doc/source/user/metadata.rst index 7a3fe8b6a595..eac34c8480c7 100644 --- a/doc/source/user/metadata.rst +++ b/doc/source/user/metadata.rst @@ -337,6 +337,8 @@ example: } +::download:`Download` network_data.json JSON schema. + .. _metadata-ec2-format: EC2-compatible metadata diff --git a/nova/tests/functional/test_metadata.py b/nova/tests/functional/test_metadata.py index 879db850af15..f89e872b5f40 100644 --- a/nova/tests/functional/test_metadata.py +++ b/nova/tests/functional/test_metadata.py @@ -15,15 +15,18 @@ from __future__ import absolute_import import fixtures +import jsonschema +import os import requests from oslo_serialization import jsonutils from oslo_utils import uuidutils -from nova import context -from nova import objects from nova import test from nova.tests import fixtures as nova_fixtures +from nova.tests.functional import fixtures as func_fixtures +from nova.tests.functional import integrated_helpers +from nova.tests.unit.image import fake as fake_image class fake_result(object): @@ -45,45 +48,38 @@ def fake_request(obj, url, method, **kwargs): return real_request(method, url, **kwargs) -class MetadataTest(test.TestCase): +class MetadataTest(test.TestCase, integrated_helpers.InstanceHelperMixin): def setUp(self): super(MetadataTest, self).setUp() + + fake_image.stub_out_image_service(self) + self.addCleanup(fake_image.FakeImageService_reset) + self.useFixture(nova_fixtures.NeutronFixture(self)) + self.useFixture(func_fixtures.PlacementFixture()) + self.start_service('conductor') + self.start_service('scheduler') + self.api = self.useFixture( + nova_fixtures.OSAPIFixture(api_version='v2.1')).api + self.start_service('compute') + + # create a server for the tests + server = self._build_server(name='test') + server = self.api.post_server({'server': server}) + self.server = self._wait_for_state_change(server, 'ACTIVE') + self.api_fixture = self.useFixture(nova_fixtures.OSMetadataServer()) self.md_url = self.api_fixture.md_url - ctxt = context.RequestContext('fake', 'fake') - flavor = objects.Flavor( - id=1, name='flavor1', memory_mb=256, vcpus=1, root_gb=1, - ephemeral_gb=1, flavorid='1', swap=0, rxtx_factor=1.0, - vcpu_weight=1, disabled=False, is_public=True, extra_specs={}, - projects=[]) - instance = objects.Instance(ctxt, flavor=flavor, vcpus=1, - memory_mb=256, root_gb=0, ephemeral_gb=0, - project_id='fake', hostname='test') - instance.create() - - # The NeutronFixture is needed to provide the fixed IP for the metadata - # service - self.useFixture(nova_fixtures.NeutronFixture(self)) - + # make sure that the metadata service returns information about the + # server we created above def fake_get_fixed_ip_by_address(self, ctxt, address): - return {'instance_uuid': instance.uuid} + return {'instance_uuid': server['id']} self.useFixture( fixtures.MonkeyPatch( 'nova.network.neutronv2.api.API.get_fixed_ip_by_address', fake_get_fixed_ip_by_address)) - def fake_get_ec2_ip_info(nw_info): - return {'fixed_ips': ['127.0.0.2'], - 'fixed_ip6s': [], - 'floating_ips': []} - - self.useFixture( - fixtures.MonkeyPatch( - 'nova.virt.netutils.get_ec2_ip_info', - fake_get_ec2_ip_info)) - def test_lookup_metadata_root_url(self): res = requests.request('GET', self.md_url, timeout=5) self.assertEqual(200, res.status_code) @@ -174,7 +170,25 @@ class MetadataTest(test.TestCase): self.assertIn('instance-id', j['testing']) self.assertTrue(uuidutils.is_uuid_like(j['testing']['instance-id'])) self.assertIn('hostname', j['testing']) - self.assertEqual('fake', j['testing']['project-id']) + self.assertEqual(self.server['tenant_id'], j['testing']['project-id']) self.assertIn('metadata', j['testing']) self.assertIn('image-id', j['testing']) self.assertIn('user-data', j['testing']) + + def test_network_data_matches_schema(self): + self.useFixture(fixtures.MonkeyPatch( + 'keystoneauth1.session.Session.request', fake_request)) + + url = '%sopenstack/latest/network_data.json' % self.md_url + + res = requests.request('GET', url, timeout=5) + self.assertEqual(200, res.status_code) + + # load the jsonschema for network_data + schema_file = os.path.normpath(os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "../../../doc/api_schemas/network_data.json")) + with open(schema_file, 'rb') as f: + schema = jsonutils.load(f) + + jsonschema.validate(res.json(), schema)