Merge "module_utils - network_data_v2"
This commit is contained in:
commit
da56e9d367
|
@ -0,0 +1,405 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2018 OpenStack Foundation
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import collections
|
||||||
|
import ipaddress
|
||||||
|
import jsonschema
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
DOMAIN_NAME_REGEX = (r'^(?=^.{1,255}$)(?!.*\.\..*)(.{1,63}\.)'
|
||||||
|
r'+(.{0,63}\.?)|(?!\.)(?!.*\.\..*)(^.{1,63}$)'
|
||||||
|
r'|(^\.$)$')
|
||||||
|
NET_DATA_V2_SCHEMA = '''
|
||||||
|
---
|
||||||
|
$schema: http://json-schema.org/draft-04/schema
|
||||||
|
|
||||||
|
definitions:
|
||||||
|
domain_name_string:
|
||||||
|
type: string
|
||||||
|
pattern: {domain_name_regex}
|
||||||
|
ipv4_allocation_pool:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
start:
|
||||||
|
type: string
|
||||||
|
ip_address_version: 4
|
||||||
|
end:
|
||||||
|
type: string
|
||||||
|
ip_address_version: 4
|
||||||
|
additionalProperties: False
|
||||||
|
uniqueItems: true
|
||||||
|
required:
|
||||||
|
- start
|
||||||
|
- end
|
||||||
|
ipv4_route:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
destination:
|
||||||
|
type: string
|
||||||
|
ip_subnet_version: 4
|
||||||
|
nexthop:
|
||||||
|
type: string
|
||||||
|
ip_address_version: 4
|
||||||
|
additionalProperties: False
|
||||||
|
uniqueItems: true
|
||||||
|
required:
|
||||||
|
- destination
|
||||||
|
- nexthop
|
||||||
|
ipv6_allocation_pool:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
start:
|
||||||
|
type: string
|
||||||
|
ip_address_version: 6
|
||||||
|
end:
|
||||||
|
type: string
|
||||||
|
ip_address_version: 6
|
||||||
|
additionalProperties: False
|
||||||
|
uniqueItems: true
|
||||||
|
required:
|
||||||
|
- start
|
||||||
|
- end
|
||||||
|
ipv6_route:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
destination:
|
||||||
|
type: string
|
||||||
|
ip_subnet_version: 6
|
||||||
|
nexthop:
|
||||||
|
type: string
|
||||||
|
ip_address_version: 6
|
||||||
|
additionalProperties: False
|
||||||
|
uniqueItems: true
|
||||||
|
required:
|
||||||
|
- destination
|
||||||
|
- nexthop
|
||||||
|
|
||||||
|
ipv4_subnet:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
ip_subnet:
|
||||||
|
type: string
|
||||||
|
ip_subnet_version: 4
|
||||||
|
gateway_ip:
|
||||||
|
type: string
|
||||||
|
ip_address_version: 4
|
||||||
|
allocation_pools:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/definitions/ipv4_allocation_pool"
|
||||||
|
enable_dhcp:
|
||||||
|
type: boolean
|
||||||
|
routes:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/definitions/ipv4_route"
|
||||||
|
vlan:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
maximum: 4096
|
||||||
|
physical_network:
|
||||||
|
type: string
|
||||||
|
network_type:
|
||||||
|
enum:
|
||||||
|
- flat
|
||||||
|
- vlan
|
||||||
|
segmentation_id:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
maximum: 4096
|
||||||
|
additionalProperties: False
|
||||||
|
required:
|
||||||
|
- ip_subnet
|
||||||
|
|
||||||
|
ipv6_subnet:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
ipv6_subnet:
|
||||||
|
type: string
|
||||||
|
ip_subnet_version: 6
|
||||||
|
gateway_ipv6:
|
||||||
|
type: string
|
||||||
|
ip_address_version: 6
|
||||||
|
ipv6_allocation_pools:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/definitions/ipv6_allocation_pool"
|
||||||
|
routes_ipv6:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/definitions/ipv6_route"
|
||||||
|
ipv6_address_mode:
|
||||||
|
enum:
|
||||||
|
- null
|
||||||
|
- dhcpv6-stateful
|
||||||
|
- dhcpv6-stateless
|
||||||
|
ipv6_ra_mode:
|
||||||
|
enum:
|
||||||
|
- null
|
||||||
|
- dhcpv6-stateful
|
||||||
|
- dhcpv6-stateless
|
||||||
|
enable_dhcp:
|
||||||
|
type: boolean
|
||||||
|
vlan:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
maximum: 4096
|
||||||
|
physical_network:
|
||||||
|
type: string
|
||||||
|
network_type:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- flat
|
||||||
|
- vlan
|
||||||
|
segmentation_id:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
maximum: 4096
|
||||||
|
additionalProperties: False
|
||||||
|
required:
|
||||||
|
- ipv6_subnet
|
||||||
|
|
||||||
|
dual_subnet:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
ip_subnet:
|
||||||
|
type: string
|
||||||
|
ip_subnet_version: 4
|
||||||
|
gateway_ip:
|
||||||
|
type: string
|
||||||
|
ip_address_version: 4
|
||||||
|
allocation_pools:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/definitions/ipv4_allocation_pool"
|
||||||
|
routes:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/definitions/ipv4_route"
|
||||||
|
ipv6_subnet:
|
||||||
|
type: string
|
||||||
|
ip_subnet_version: 6
|
||||||
|
gateway_ipv6:
|
||||||
|
type: string
|
||||||
|
ip_address_version: 6
|
||||||
|
ipv6_allocation_pools:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/definitions/ipv6_allocation_pool"
|
||||||
|
routes_ipv6:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/definitions/ipv6_route"
|
||||||
|
ipv6_address_mode:
|
||||||
|
enum:
|
||||||
|
- null
|
||||||
|
- dhcpv6-stateful
|
||||||
|
- dhcpv6-stateless
|
||||||
|
ipv6_ra_mode:
|
||||||
|
enum:
|
||||||
|
- null
|
||||||
|
- dhcpv6-stateful
|
||||||
|
- dhcpv6-stateless
|
||||||
|
enable_dhcp:
|
||||||
|
type: boolean
|
||||||
|
vlan:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
maximum: 4096
|
||||||
|
physical_network:
|
||||||
|
type: string
|
||||||
|
network_type:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- flat
|
||||||
|
- vlan
|
||||||
|
segmentation_id:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
maximum: 4096
|
||||||
|
additionalProperties: False
|
||||||
|
required:
|
||||||
|
- ip_subnet
|
||||||
|
- ipv6_subnet
|
||||||
|
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
name_lower:
|
||||||
|
type: string
|
||||||
|
admin_state_up:
|
||||||
|
type: boolean
|
||||||
|
dns_domain:
|
||||||
|
$ref: "#/definitions/domain_name_string"
|
||||||
|
mtu:
|
||||||
|
type: integer
|
||||||
|
minimum: 1000
|
||||||
|
maximum: 65536
|
||||||
|
shared:
|
||||||
|
type: boolean
|
||||||
|
service_net_map_replace:
|
||||||
|
type: string
|
||||||
|
ipv6:
|
||||||
|
type: boolean
|
||||||
|
vip:
|
||||||
|
type: boolean
|
||||||
|
subnets:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
oneOf:
|
||||||
|
- $ref: "#/definitions/ipv4_subnet"
|
||||||
|
- $ref: "#/definitions/ipv6_subnet"
|
||||||
|
- $ref: "#/definitions/dual_subnet"
|
||||||
|
additionalProperties: False
|
||||||
|
required:
|
||||||
|
- name
|
||||||
|
- subnets
|
||||||
|
'''.format(domain_name_regex=DOMAIN_NAME_REGEX)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_detailed_errors(error, depth, absolute_schema_path, absolute_schema,
|
||||||
|
filter_errors=True):
|
||||||
|
"""Returns a list of error messages from all subschema validations.
|
||||||
|
|
||||||
|
Recurses the error tree and adds one message per sub error. That list can
|
||||||
|
get long, because jsonschema also tests the hypothesis that the provided
|
||||||
|
network element type is wrong (e.g. "ovs_bridge" instead of "ovs_bond").
|
||||||
|
Setting `filter_errors=True` assumes the type, if specified, is correct and
|
||||||
|
therefore produces a much shorter list of more relevant results.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not error.context:
|
||||||
|
return []
|
||||||
|
|
||||||
|
sub_errors = error.context
|
||||||
|
if filter_errors:
|
||||||
|
if (absolute_schema_path[-1] in ['oneOf', 'anyOf']
|
||||||
|
and isinstance(error.instance, collections.Mapping)
|
||||||
|
and 'type' in error.instance):
|
||||||
|
found, index = _find_type_in_schema_list(
|
||||||
|
error.validator_value, error.instance['type'])
|
||||||
|
if found:
|
||||||
|
sub_errors = [i for i in sub_errors if (
|
||||||
|
i.schema_path[0] == index)]
|
||||||
|
|
||||||
|
details = []
|
||||||
|
sub_errors = sorted(sub_errors, key=lambda e: e.schema_path)
|
||||||
|
for sub_error in sub_errors:
|
||||||
|
schema_path = collections.deque(absolute_schema_path)
|
||||||
|
schema_path.extend(sub_error.schema_path)
|
||||||
|
details.append("{} {}: {}".format(
|
||||||
|
'-' * depth,
|
||||||
|
_pretty_print_schema_path(schema_path, absolute_schema),
|
||||||
|
sub_error.message)
|
||||||
|
)
|
||||||
|
details.extend(_get_detailed_errors(
|
||||||
|
sub_error, depth + 1, schema_path, absolute_schema,
|
||||||
|
filter_errors))
|
||||||
|
return details
|
||||||
|
|
||||||
|
|
||||||
|
def _find_type_in_schema_list(schemas, type):
|
||||||
|
"""Finds an object of a given type in an anyOf/oneOf array.
|
||||||
|
|
||||||
|
Returns a tuple (`found`, `index`), where `found` indicates whether
|
||||||
|
on object of type `type` was found in the `schemas` array.
|
||||||
|
If so, `index` contains the object's position in the array.
|
||||||
|
"""
|
||||||
|
for index, schema in enumerate(schemas):
|
||||||
|
if not isinstance(schema, collections.Mapping):
|
||||||
|
continue
|
||||||
|
if '$ref' in schema and schema['$ref'].split('/')[-1] == type:
|
||||||
|
return True, index
|
||||||
|
if ('properties' in schema and 'type' in schema['properties']
|
||||||
|
and schema['properties']['type'] == type):
|
||||||
|
return True, index
|
||||||
|
return False, 0
|
||||||
|
|
||||||
|
|
||||||
|
def _pretty_print_schema_path(absolute_schema_path, absolute_schema):
|
||||||
|
"""Returns a representation of the schema path that's easier to read.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
>>> _pretty_print_schema_path("items/oneOf/0/properties/use_dhcp/oneOf/2")
|
||||||
|
"items/oneOf/interface/use_dhcp/oneOf/param"
|
||||||
|
"""
|
||||||
|
|
||||||
|
pretty_path = []
|
||||||
|
current_path = []
|
||||||
|
current_schema = absolute_schema
|
||||||
|
for item in absolute_schema_path:
|
||||||
|
if item not in ["properties"]:
|
||||||
|
pretty_path.append(item)
|
||||||
|
current_path.append(item)
|
||||||
|
current_schema = current_schema[item]
|
||||||
|
if (isinstance(current_schema, collections.Mapping)
|
||||||
|
and '$ref' in current_schema):
|
||||||
|
if (isinstance(pretty_path[-1], int) and pretty_path[-2]
|
||||||
|
in ['oneOf', 'anyOf']):
|
||||||
|
pretty_path[-1] = current_schema['$ref'].split('/')[-1]
|
||||||
|
current_path = current_schema['$ref'].split('/')
|
||||||
|
current_schema = absolute_schema
|
||||||
|
for i in current_path[1:]:
|
||||||
|
current_schema = current_schema[i]
|
||||||
|
return '/'.join([str(x) for x in pretty_path])
|
||||||
|
|
||||||
|
|
||||||
|
def validate_json_schema(net_data):
|
||||||
|
|
||||||
|
def ip_subnet_version_validator(validator, ip_version, instance, schema):
|
||||||
|
msg = '{} does not appear to be an IPv{} subnet'.format(
|
||||||
|
instance, ip_version)
|
||||||
|
try:
|
||||||
|
if not ipaddress.ip_network(instance).version == ip_version:
|
||||||
|
yield jsonschema.ValidationError(msg)
|
||||||
|
except ValueError:
|
||||||
|
yield jsonschema.ValidationError(msg)
|
||||||
|
|
||||||
|
def ip_address_version_validator(validator, ip_version, instance, schema):
|
||||||
|
msg = '{} does not appear to be an IPv{} address'.format(
|
||||||
|
instance, ip_version)
|
||||||
|
try:
|
||||||
|
if not ipaddress.ip_address(instance).version == ip_version:
|
||||||
|
yield jsonschema.ValidationError(msg)
|
||||||
|
except ValueError:
|
||||||
|
yield jsonschema.ValidationError(msg)
|
||||||
|
|
||||||
|
schema = yaml.safe_load(NET_DATA_V2_SCHEMA)
|
||||||
|
net_data_validator = jsonschema.validators.extend(
|
||||||
|
jsonschema.Draft4Validator,
|
||||||
|
validators={'ip_subnet_version': ip_subnet_version_validator,
|
||||||
|
'ip_address_version': ip_address_version_validator})
|
||||||
|
validator = net_data_validator(schema)
|
||||||
|
errors = validator.iter_errors(instance=net_data)
|
||||||
|
|
||||||
|
error_messages = []
|
||||||
|
for error in errors:
|
||||||
|
details = _get_detailed_errors(error, 1, error.schema_path, schema)
|
||||||
|
|
||||||
|
config_path = '/'.join([str(x) for x in error.path])
|
||||||
|
if details:
|
||||||
|
error_messages.append(
|
||||||
|
"Failed schema validation at {}:\n {}\n"
|
||||||
|
" Sub-schemas tested and not matching:\n {}".format(
|
||||||
|
config_path, error.message, '\n '.join(details)))
|
||||||
|
else:
|
||||||
|
error_messages.append(
|
||||||
|
"Failed schema validation at {}:\n {}".format(
|
||||||
|
config_path, error.message))
|
||||||
|
|
||||||
|
return error_messages
|
|
@ -0,0 +1,411 @@
|
||||||
|
# Copyright 2019 Red Hat, Inc.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
import copy
|
||||||
|
|
||||||
|
from tripleo_ansible.tests import base as tests_base
|
||||||
|
|
||||||
|
from tripleo_ansible.ansible_plugins.module_utils import network_data_v2
|
||||||
|
|
||||||
|
|
||||||
|
NET_DATA = yaml.safe_load('''
|
||||||
|
---
|
||||||
|
name: Storage
|
||||||
|
name_lower: storage
|
||||||
|
admin_state_up: false
|
||||||
|
dns_domain: storage.localdomain.
|
||||||
|
mtu: 1442
|
||||||
|
shared: false
|
||||||
|
service_net_map_replace: storage
|
||||||
|
ipv6: true
|
||||||
|
vip: true
|
||||||
|
subnets:
|
||||||
|
subnet01:
|
||||||
|
ip_subnet: 172.18.1.0/24
|
||||||
|
gateway_ip: 172.18.1.254
|
||||||
|
allocation_pools:
|
||||||
|
- start: 172.18.1.1
|
||||||
|
end: 172.18.1.250
|
||||||
|
routes:
|
||||||
|
- destination: 172.18.0.0/24
|
||||||
|
nexthop: 172.18.1.254
|
||||||
|
ipv6_subnet: 2001:db8:a::/64
|
||||||
|
gateway_ipv6: 2001:db8:a::1
|
||||||
|
ipv6_allocation_pools:
|
||||||
|
- start: 2001:db8:a::0010
|
||||||
|
end: 2001:db8:a::fff9
|
||||||
|
routes_ipv6:
|
||||||
|
- destination: 2001:db8:b::/64
|
||||||
|
nexthop: 2001:db8:a::1
|
||||||
|
ipv6_address_mode: null
|
||||||
|
ipv6_ra_mode: null
|
||||||
|
enable_dhcp: false
|
||||||
|
physical_network: storage_subnet01
|
||||||
|
network_type: flat
|
||||||
|
segmentation_id: 21
|
||||||
|
vlan: 21
|
||||||
|
subnet02:
|
||||||
|
ip_subnet: 172.18.0.0/24
|
||||||
|
gateway_ip: 172.18.0.254
|
||||||
|
allocation_pools:
|
||||||
|
- start: 172.18.0.10
|
||||||
|
end: 172.18.0.250
|
||||||
|
routes:
|
||||||
|
- destination: 172.18.1.0/24
|
||||||
|
nexthop: 172.18.0.254
|
||||||
|
ipv6_subnet: 2001:db8:b::/64
|
||||||
|
gateway_ipv6: 2001:db8:b::1
|
||||||
|
ipv6_allocation_pools:
|
||||||
|
- start: 2001:db8:b::0010
|
||||||
|
end: 2001:db8:b::fff9
|
||||||
|
routes_ipv6:
|
||||||
|
- destination: 2001:db8:a::/64
|
||||||
|
nexthop: 2001:db8:b::1
|
||||||
|
ipv6_address_mode: null
|
||||||
|
ipv6_ra_mode: null
|
||||||
|
enable_dhcp: false
|
||||||
|
physical_network: storage_subnet01
|
||||||
|
network_type: flat
|
||||||
|
segmentation_id: 21
|
||||||
|
vlan: 20
|
||||||
|
''')
|
||||||
|
|
||||||
|
IPV4_SUBNET_KEYS = {'ip_subnet', 'allocation_pools', 'routes', 'gateway_ip'}
|
||||||
|
IPV6_SUBNET_KEYS = {'ipv6_subnet', 'ipv6_allocation_pools', 'routes_ipv6',
|
||||||
|
'gateway_ipv6', 'ipv6_address_mode', 'ipv6_ra_mode'}
|
||||||
|
|
||||||
|
|
||||||
|
class TestNetworkDataV2(tests_base.TestCase):
|
||||||
|
|
||||||
|
def test_validator_ok(self):
|
||||||
|
ipv4_only = copy.deepcopy(NET_DATA)
|
||||||
|
ipv6_only = copy.deepcopy(NET_DATA)
|
||||||
|
ipv4_only.pop('ipv6')
|
||||||
|
for name, subnet in ipv4_only['subnets'].items():
|
||||||
|
[subnet.pop(k) for k in IPV6_SUBNET_KEYS]
|
||||||
|
for name, subnet in ipv6_only['subnets'].items():
|
||||||
|
[subnet.pop(k) for k in IPV4_SUBNET_KEYS]
|
||||||
|
|
||||||
|
error_messages = network_data_v2.validate_json_schema(NET_DATA)
|
||||||
|
self.assertEqual([], error_messages)
|
||||||
|
error_messages = network_data_v2.validate_json_schema(ipv4_only)
|
||||||
|
self.assertEqual([], error_messages)
|
||||||
|
error_messages = network_data_v2.validate_json_schema(ipv6_only)
|
||||||
|
self.assertEqual([], error_messages)
|
||||||
|
|
||||||
|
def test_validator_fail(self):
|
||||||
|
dual = copy.deepcopy(NET_DATA)
|
||||||
|
dual.pop('name') # Required
|
||||||
|
dual['mtu'] = 400 # too low
|
||||||
|
dual['ipv6'] = 'not_bool'
|
||||||
|
dual['vip'] = 'not_bool'
|
||||||
|
dual['admin_state_up'] = 'not_bool'
|
||||||
|
dual['shared'] = 'not_bool'
|
||||||
|
dual['invalid_key'] = 'foo'
|
||||||
|
s02 = dual['subnets']['subnet02']
|
||||||
|
s02['ip_subnet'] = 'invalid'
|
||||||
|
s02['gateway_ip'] = '2001:db8:a::1' # Wrong ip version
|
||||||
|
s02['allocation_pools'][0]['foo'] = 'foo' # Invalid key
|
||||||
|
s02['allocation_pools'][0]['start'] = '2001:db8:a::1'
|
||||||
|
s02['routes'][0]['foo'] = 'foo' # Invalid key
|
||||||
|
s02['routes'][0]['nexthop'] = '172222.18.1.254'
|
||||||
|
s02['routes'][0]['destination'] = '172.18.0.0/99' # netmask error
|
||||||
|
s02['enable_dhcp'] = 'not_a_bool'
|
||||||
|
s02['physical_network'] = dict() # Invalid, should be string
|
||||||
|
s02['network_type'] = 'invalid'
|
||||||
|
s02['vlan'] = 'not_an_int'
|
||||||
|
s02['ipv6_subnet'] = 'invalid'
|
||||||
|
s02['gateway_ipv6'] = '172.18.1.254' # Wrong ip version
|
||||||
|
s02['ipv6_allocation_pools'][0]['v6_invalid_key'] = 'foo'
|
||||||
|
s02['ipv6_allocation_pools'][0]['end'] = '172.18.1.20'
|
||||||
|
s02['routes_ipv6'][0]['v6_invalid_key'] = 'foo'
|
||||||
|
s02['routes_ipv6'][0]['destination'] = '2001:XXX8:X::/64'
|
||||||
|
s02['ipv6_address_mode'] = 'invalid'
|
||||||
|
s02['ipv6_ra_mode'] = 'invalid'
|
||||||
|
|
||||||
|
ipv4_only = copy.deepcopy(dual)
|
||||||
|
ipv6_only = copy.deepcopy(dual)
|
||||||
|
for name, subnet in ipv4_only['subnets'].items():
|
||||||
|
[subnet.pop(k) for k in IPV6_SUBNET_KEYS]
|
||||||
|
for name, subnet in ipv6_only['subnets'].items():
|
||||||
|
[subnet.pop(k) for k in IPV4_SUBNET_KEYS]
|
||||||
|
ipv4_only['subnets']['subnet01'].pop('ip_subnet') # Required
|
||||||
|
ipv6_only['subnets']['subnet01'].pop('ipv6_subnet') # Required
|
||||||
|
|
||||||
|
error_messages_dual = network_data_v2.validate_json_schema(dual)
|
||||||
|
error_messages_dual = '\n'.join(error_messages_dual)
|
||||||
|
error_messages_ipv4 = network_data_v2.validate_json_schema(ipv4_only)
|
||||||
|
error_messages_ipv4 = '\n'.join(error_messages_ipv4)
|
||||||
|
error_messages_ipv6 = network_data_v2.validate_json_schema(ipv6_only)
|
||||||
|
error_messages_ipv6 = '\n'.join(error_messages_ipv6)
|
||||||
|
|
||||||
|
self.assertRegex(error_messages_dual,
|
||||||
|
(r"Failed schema validation at admin_state_up:\n"
|
||||||
|
r" 'not_bool' is not of type 'boolean'\n"))
|
||||||
|
self.assertRegex(error_messages_dual,
|
||||||
|
(r"Failed schema validation at mtu:\n"
|
||||||
|
r" 400 is less than the minimum of 1000\n"))
|
||||||
|
self.assertRegex(error_messages_dual,
|
||||||
|
(r"Failed schema validation at shared:\n"
|
||||||
|
r" 'not_bool' is not of type 'boolean'\n"))
|
||||||
|
self.assertRegex(error_messages_dual,
|
||||||
|
(r"Failed schema validation at ipv6:\n"
|
||||||
|
r" 'not_bool' is not of type 'boolean'\n"))
|
||||||
|
self.assertRegex(error_messages_dual,
|
||||||
|
(r"Failed schema validation at vip:\n"
|
||||||
|
r" 'not_bool' is not of type 'boolean'\n"))
|
||||||
|
self.assertRegex(error_messages_dual,
|
||||||
|
(r"Failed schema validation at :\n"
|
||||||
|
r".*Additional properties are not allowed "
|
||||||
|
r"\('invalid_key' was unexpected\)\n"))
|
||||||
|
self.assertRegex(error_messages_dual,
|
||||||
|
(r"Failed schema validation at :\n"
|
||||||
|
r".*'name' is a required property"))
|
||||||
|
self.assertRegex(error_messages_dual,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/dual_subnet"
|
||||||
|
r"/allocation_pools/items/additionalProperties: "
|
||||||
|
r"Additional properties are not allowed \('foo' was "
|
||||||
|
r"unexpected\)\n"))
|
||||||
|
self.assertRegex(error_messages_dual,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/dual_subnet"
|
||||||
|
r"/allocation_pools/items/start/ip_address_version: "
|
||||||
|
r"2001:db8:a::1 does not appear to be an IPv4 "
|
||||||
|
r"address\n"))
|
||||||
|
self.assertRegex(error_messages_dual,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/dual_subnet"
|
||||||
|
r"/enable_dhcp/type: 'not_a_bool' is not of type "
|
||||||
|
r"'boolean'\n"))
|
||||||
|
self.assertRegex(error_messages_dual,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/dual_subnet"
|
||||||
|
r"/gateway_ip/ip_address_version: 2001:db8:a::1 "
|
||||||
|
r"does not appear to be an IPv4 address\n"))
|
||||||
|
self.assertRegex(error_messages_dual,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/dual_subnet"
|
||||||
|
r"/gateway_ipv6/ip_address_version: 172.18.1.254 "
|
||||||
|
r"does not appear to be an IPv6 address\n"))
|
||||||
|
self.assertRegex(error_messages_dual,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/dual_subnet"
|
||||||
|
r"/ip_subnet/ip_subnet_version: invalid does not "
|
||||||
|
r"appear to be an IPv4 subnet\n"))
|
||||||
|
self.assertRegex(error_messages_dual,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/dual_subnet"
|
||||||
|
r"/ipv6_address_mode/enum: 'invalid' is not one of "
|
||||||
|
r"\[None, 'dhcpv6-stateful', 'dhcpv6-stateless'\]"
|
||||||
|
r"\n"))
|
||||||
|
self.assertRegex(error_messages_dual,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/dual_subnet"
|
||||||
|
r"/ipv6_allocation_pools/items"
|
||||||
|
r"/additionalProperties: Additional properties are "
|
||||||
|
r"not allowed \('v6_invalid_key' was unexpected\)"
|
||||||
|
r"\n"))
|
||||||
|
self.assertRegex(error_messages_dual,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/dual_subnet"
|
||||||
|
r"/ipv6_allocation_pools/items/end"
|
||||||
|
r"/ip_address_version: 172.18.1.20 does not appear "
|
||||||
|
r"to be an IPv6 address\n"))
|
||||||
|
self.assertRegex(error_messages_dual,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/dual_subnet"
|
||||||
|
r"/ipv6_ra_mode/enum: 'invalid' is not one of "
|
||||||
|
r"\[None, 'dhcpv6-stateful', 'dhcpv6-stateless'\]\n"))
|
||||||
|
self.assertRegex(error_messages_dual,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/dual_subnet"
|
||||||
|
r"/ipv6_subnet/ip_subnet_version: invalid does not "
|
||||||
|
r"appear to be an IPv6 subnet\n"))
|
||||||
|
self.assertRegex(error_messages_dual,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/dual_subnet"
|
||||||
|
r"/network_type/enum: 'invalid' is not one of "
|
||||||
|
r"\['flat', 'vlan'\]\n"))
|
||||||
|
self.assertRegex(error_messages_dual,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/dual_subnet"
|
||||||
|
r"/physical_network/type: {} is not of type 'string'"
|
||||||
|
r"\n"))
|
||||||
|
self.assertRegex(error_messages_dual,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/dual_subnet"
|
||||||
|
r"/routes/items/additionalProperties: Additional "
|
||||||
|
r"properties are not allowed \('foo' was unexpected\)"
|
||||||
|
r"\n"))
|
||||||
|
self.assertRegex(error_messages_dual,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/dual_subnet"
|
||||||
|
r"/routes/items/destination/ip_subnet_version: "
|
||||||
|
r"172.18.0.0/99 does not appear to be an IPv4 subnet"
|
||||||
|
r"\n"))
|
||||||
|
self.assertRegex(error_messages_dual,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/dual_subnet"
|
||||||
|
r"/routes/items/nexthop/ip_address_version: "
|
||||||
|
r"172222.18.1.254 does not appear to be an IPv4 "
|
||||||
|
r"address\n"))
|
||||||
|
self.assertRegex(error_messages_dual,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/dual_subnet"
|
||||||
|
r"/routes_ipv6/items/additionalProperties: "
|
||||||
|
r"Additional properties are not allowed \("
|
||||||
|
r"'v6_invalid_key' was unexpected\)\n"))
|
||||||
|
self.assertRegex(error_messages_dual,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/dual_subnet"
|
||||||
|
r"/routes_ipv6/items/destination/ip_subnet_version: "
|
||||||
|
r"2001:XXX8:X::/64 does not appear to be an IPv6 "
|
||||||
|
r"subnet\n"))
|
||||||
|
self.assertRegex(error_messages_dual,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/dual_subnet"
|
||||||
|
r"/vlan/type: 'not_an_int' is not of type 'integer'"
|
||||||
|
r"\n"))
|
||||||
|
|
||||||
|
self.assertRegex(error_messages_dual,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/ipv6_subnet"
|
||||||
|
r"/additionalProperties: Additional properties are "
|
||||||
|
r"not allowed \(.*'routes'.* were unexpected\)\n"))
|
||||||
|
self.assertRegex(error_messages_dual,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/ipv6_subnet"
|
||||||
|
r"/additionalProperties: Additional properties are "
|
||||||
|
r"not allowed \(.*'allocation_pools'.* were "
|
||||||
|
r"unexpected\)\n"))
|
||||||
|
self.assertRegex(error_messages_dual,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/ipv6_subnet"
|
||||||
|
r"/additionalProperties: Additional properties are "
|
||||||
|
r"not allowed \(.*'ip_subnet'.* were unexpected\)\n"))
|
||||||
|
self.assertRegex(error_messages_dual,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/ipv6_subnet"
|
||||||
|
r"/additionalProperties: Additional properties are "
|
||||||
|
r"not allowed \(.*'gateway_ip'.* were unexpected\)"
|
||||||
|
r"\n"))
|
||||||
|
|
||||||
|
self.assertRegex(error_messages_dual,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/ipv4_subnet"
|
||||||
|
r"/additionalProperties: Additional properties are "
|
||||||
|
r"not allowed \(.*'routes_ipv6'.* were unexpected\)"
|
||||||
|
r"\n"))
|
||||||
|
self.assertRegex(error_messages_dual,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/ipv4_subnet"
|
||||||
|
r"/additionalProperties: Additional properties are "
|
||||||
|
r"not allowed \(.*'ipv6_allocation_pools'.* were "
|
||||||
|
r"unexpected\)\n"))
|
||||||
|
self.assertRegex(error_messages_dual,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/ipv4_subnet"
|
||||||
|
r"/additionalProperties: Additional properties are "
|
||||||
|
r"not allowed \(.*'ipv6_subnet'.* were unexpected\)"
|
||||||
|
r"\n"))
|
||||||
|
self.assertRegex(error_messages_dual,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/ipv4_subnet"
|
||||||
|
r"/additionalProperties: Additional properties are "
|
||||||
|
r"not allowed \(.*'gateway_ipv6'.* were unexpected\)"
|
||||||
|
r"\n"))
|
||||||
|
|
||||||
|
self.assertRegex(error_messages_ipv4,
|
||||||
|
r"Failed schema validation at subnets/subnet01:\n")
|
||||||
|
self.assertRegex(error_messages_ipv4,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/ipv4_subnet"
|
||||||
|
r"/required: 'ip_subnet' is a required property\n"))
|
||||||
|
self.assertRegex(error_messages_ipv4,
|
||||||
|
r"Failed schema validation at subnets/subnet02:\n")
|
||||||
|
self.assertRegex(error_messages_ipv4,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/ipv4_subnet"
|
||||||
|
r"/allocation_pools/items/additionalProperties: "
|
||||||
|
r"Additional properties are not allowed \('foo' was "
|
||||||
|
r"unexpected\)\n"))
|
||||||
|
self.assertRegex(error_messages_ipv4,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/ipv4_subnet"
|
||||||
|
r"/allocation_pools/items/start/ip_address_version: "
|
||||||
|
r"2001:db8:a::1 does not appear to be an IPv4 "
|
||||||
|
r"address\n"))
|
||||||
|
self.assertRegex(error_messages_ipv4,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/ipv4_subnet/"
|
||||||
|
r"enable_dhcp/type: \'not_a_bool\' is not of type "
|
||||||
|
r"\'boolean\'\n"))
|
||||||
|
self.assertRegex(error_messages_ipv4,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/ipv4_subnet"
|
||||||
|
r"/gateway_ip/ip_address_version: 2001:db8:a::1 does "
|
||||||
|
r"not appear to be an IPv4 address\n"))
|
||||||
|
self.assertRegex(error_messages_ipv4,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/ipv4_subnet"
|
||||||
|
r"/ip_subnet/ip_subnet_version: invalid does not "
|
||||||
|
r"appear to be an IPv4 subnet\n"))
|
||||||
|
self.assertRegex(error_messages_ipv4,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/ipv4_subnet"
|
||||||
|
r"/network_type/enum: 'invalid' is not one of "
|
||||||
|
r"\['flat', 'vlan'\].*"))
|
||||||
|
self.assertRegex(error_messages_ipv4,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/ipv4_subnet"
|
||||||
|
r"/physical_network/type: {} is not of type 'string'"
|
||||||
|
r"\n"))
|
||||||
|
self.assertRegex(error_messages_ipv4,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/ipv4_subnet"
|
||||||
|
r"/routes/items/additionalProperties: Additional "
|
||||||
|
r"properties are not allowed \('foo' was "
|
||||||
|
r"unexpected\)\n"))
|
||||||
|
self.assertRegex(error_messages_ipv4,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/ipv4_subnet"
|
||||||
|
r"/routes/items/destination/ip_subnet_version: "
|
||||||
|
r"172.18.0.0/99 does not appear to be an IPv4 subnet"
|
||||||
|
r"\n"))
|
||||||
|
self.assertRegex(error_messages_ipv4,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/ipv4_subnet"
|
||||||
|
r"/routes/items/nexthop/ip_address_version: "
|
||||||
|
r"172222.18.1.254 does not appear to be an IPv4 "
|
||||||
|
r"address\n"))
|
||||||
|
self.assertRegex(error_messages_ipv4,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/ipv4_subnet"
|
||||||
|
r"/vlan/type: 'not_an_int' is not of type 'integer'"
|
||||||
|
r"\n"))
|
||||||
|
|
||||||
|
self.assertRegex(error_messages_ipv6,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/ipv6_subnet"
|
||||||
|
r"/enable_dhcp/type: 'not_a_bool' is not of type "
|
||||||
|
r"'boolean'\n"))
|
||||||
|
self.assertRegex(error_messages_ipv6,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/ipv6_subnet"
|
||||||
|
r"/gateway_ipv6/ip_address_version: 172.18.1.254 "
|
||||||
|
r"does not appear to be an IPv6 address\n"))
|
||||||
|
self.assertRegex(error_messages_ipv6,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/ipv6_subnet"
|
||||||
|
r"/ipv6_address_mode/enum: 'invalid' is not one of "
|
||||||
|
r"\[None, 'dhcpv6-stateful', 'dhcpv6-stateless'\]"
|
||||||
|
r"\n"))
|
||||||
|
self.assertRegex(error_messages_ipv6,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/ipv6_subnet"
|
||||||
|
r"/ipv6_allocation_pools/items"
|
||||||
|
r"/additionalProperties: Additional properties are "
|
||||||
|
r"not allowed \('v6_invalid_key' was unexpected\)"
|
||||||
|
r"\n"))
|
||||||
|
self.assertRegex(error_messages_ipv6,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/ipv6_subnet"
|
||||||
|
r"/ipv6_allocation_pools/items/end"
|
||||||
|
r"/ip_address_version: 172.18.1.20 does not appear "
|
||||||
|
r"to be an IPv6 address\n"))
|
||||||
|
self.assertRegex(error_messages_ipv6,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/ipv6_subnet"
|
||||||
|
r"/ipv6_subnet/ip_subnet_version: invalid does not "
|
||||||
|
r"appear to be an IPv6 subnet\n"))
|
||||||
|
self.assertRegex(error_messages_ipv6,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/ipv6_subnet"
|
||||||
|
r"/network_type/enum: 'invalid' is not one of "
|
||||||
|
r"\['flat', 'vlan'\]\n"))
|
||||||
|
self.assertRegex(error_messages_ipv6,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/ipv6_subnet"
|
||||||
|
r"/physical_network/type: {} is not of type 'string'"
|
||||||
|
r"\n"))
|
||||||
|
self.assertRegex(error_messages_ipv6,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/ipv6_subnet"
|
||||||
|
r"/routes_ipv6/items/additionalProperties: "
|
||||||
|
r"Additional properties are not allowed "
|
||||||
|
r"\('v6_invalid_key' was unexpected\)\n"))
|
||||||
|
self.assertRegex(error_messages_ipv6,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/ipv6_subnet"
|
||||||
|
r"/routes_ipv6/items/destination/ip_subnet_version: "
|
||||||
|
r"2001:XXX8:X::/64 does not appear to be an IPv6 "
|
||||||
|
r"subnet\n"))
|
||||||
|
self.assertRegex(error_messages_ipv6,
|
||||||
|
(r"- subnets/additionalProperties/oneOf/ipv6_subnet"
|
||||||
|
r"/vlan/type: 'not_an_int' is not of type 'integer'"
|
||||||
|
r"\n"))
|
Loading…
Reference in New Issue