Add JSON schema and test for network_data.json

Added JSON schema defining `network_data.json` contents and
beefed up the MetadataTest functional test cases to use a
real instance instead of a database shell. This way the
tests see real data in the metadata service like a real
network_data.json.

Besides internal Nova consumption, this schema might be
helpful to other tools (such as ironic or Glean) to
validate human-generated `network_data.json` prior to
using it.

Co-Authored-By: Balazs Gibizer <balazs.gibizer@est.tech>
Change-Id: Ie5a5a1fc81c7c2d3f61b72d19de464cfc9dab5ec
This commit is contained in:
Ilya Etingof 2020-01-14 18:19:40 +01:00
parent 67d9b5114e
commit 69ee625a66
4 changed files with 656 additions and 30 deletions

View File

@ -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"
]
}
]
}

View File

@ -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"
}
]
}
}
}
}

View File

@ -337,6 +337,8 @@ example:
}
::download:`Download</../../doc/api_schemas/network_data.json>` network_data.json JSON schema.
.. _metadata-ec2-format:
EC2-compatible metadata

View File

@ -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)