diff --git a/os_net_config/objects.py b/os_net_config/objects.py index e8f3c62c..5e070f28 100644 --- a/os_net_config/objects.py +++ b/os_net_config/objects.py @@ -224,8 +224,23 @@ class Route(object): @staticmethod def from_json(json): - next_hop = _get_required_field(json, 'next_hop', 'Route') - ip_netmask = json.get('ip_netmask', "") + if json.get('next_hop') and json.get('nexthop'): + msg = ('Invalid Route JSON object with both next_hop and nexthop ' + 'configured. Use either next_hop or nexthop.') + raise InvalidConfigException(msg) + + if json.get('ip_netmask') and json.get('destination'): + msg = ('Invalid Route JSON object with both ip_netmask and ' + 'destination configured. Use either ip_netmask or ' + 'destination.') + raise InvalidConfigException(msg) + + next_hop = json.get('next_hop', json.get('nexthop')) + if next_hop is None: + msg = ('Route JSON objects require next_hop or nexthop to be ' + 'configured.') + raise InvalidConfigException(msg) + ip_netmask = json.get('ip_netmask', json.get('destination', "")) route_options = json.get('route_options', "") default = strutils.bool_from_string(str(json.get('default', False))) return Route(next_hop, ip_netmask, default, route_options) diff --git a/os_net_config/schema.yaml b/os_net_config/schema.yaml index 4104027e..ac61a302 100644 --- a/os_net_config/schema.yaml +++ b/os_net_config/schema.yaml @@ -119,7 +119,8 @@ definitions: route: type: object - properties: + oneOf: + - properties: next_hop: $ref: "#/definitions/ip_address_string_or_param" ip_netmask: @@ -128,9 +129,21 @@ definitions: $ref: "#/definitions/bool_or_param" route_options: $ref: "#/definitions/string_or_param" - required: - - next_hop - additionalProperties: False + requires: + - next_hop + additionalProperties: False + - properties: + nexthop: + $ref: "#/definitions/ip_address_string_or_param" + destination: + $ref: "#/definitions/ip_cidr_string_or_param" + default: + $ref: "#/definitions/bool_or_param" + route_options: + $ref: "#/definitions/string_or_param" + requires: + - nexthop + additionalProperties: False list_of_route: type: array items: diff --git a/os_net_config/tests/test_objects.py b/os_net_config/tests/test_objects.py index 04b07272..3616951c 100644 --- a/os_net_config/tests/test_objects.py +++ b/os_net_config/tests/test_objects.py @@ -49,6 +49,24 @@ class TestRoute(base.TestCase): self.assertTrue(route.default) self.assertEqual("metric 10", route.route_options) + def test_from_json_neutron_schema(self): + data = '{"nexthop": "172.19.0.254", "destination": "192.168.1.0/26"}' + route = objects.Route.from_json(json.loads(data)) + self.assertEqual("172.19.0.254", route.next_hop) + self.assertEqual("192.168.1.0/26", route.ip_netmask) + + data = {'nexthop': '172.19.0.254', + 'next_hop': '172.19.0.1', + 'destination': '192.168.1.0/26'} + self.assertRaises(objects.InvalidConfigException, + objects.Route.from_json, data) + + data = {'nexthop': '172.19.0.254', + 'destination': '192.168.1.0/26', + 'ip_netmask': '172.19.0.0/24'} + self.assertRaises(objects.InvalidConfigException, + objects.Route.from_json, data) + class TestAddress(base.TestCase): diff --git a/os_net_config/tests/test_validator.py b/os_net_config/tests/test_validator.py index 2a7cb749..9db87509 100644 --- a/os_net_config/tests/test_validator.py +++ b/os_net_config/tests/test_validator.py @@ -176,6 +176,24 @@ class TestDerivedTypes(base.TestCase): data = {"next_hop": "172.19.0.1", "ip_netmask": "172.19.0.0/24", "default": True, "route_options": "metric 10"} self.assertTrue(v.is_valid(data)) + data = {"nexthop": "172.19.0.1", "destination": "172.19.0.0/24", + "default": True, "route_options": "metric 10"} + self.assertTrue(v.is_valid(data)) + + # Validation fails unless only os-net-config or neutron schema. + # os-net-config :: ip_netmask + next_hop + # neutron :: destination + nexthop + data = {"next_hop": "172.19.0.1", "destination": "172.19.0.0/24"} + self.assertFalse(v.is_valid(data)) + data = {"nexthop": "172.19.0.1", "ip_netmask": "172.19.0.0/24"} + self.assertFalse(v.is_valid(data)) + data = {"nexthop": "172.19.0.1", "destination": "172.19.0.0/24", + "ip_netmask": "172.19.0.0/24"} + self.assertFalse(v.is_valid(data)) + data = {"next_hop": "172.19.0.1", "nexthop": "172.19.0.1", + "destination": "172.19.0.0/24"} + self.assertFalse(v.is_valid(data)) + data["unkown_property"] = "value" self.assertFalse(v.is_valid(data)) self.assertFalse(v.is_valid({})) diff --git a/releasenotes/notes/add-neutron-route-schema-support-e8e20a8c3b79d14d.yaml b/releasenotes/notes/add-neutron-route-schema-support-e8e20a8c3b79d14d.yaml new file mode 100644 index 00000000..035af147 --- /dev/null +++ b/releasenotes/notes/add-neutron-route-schema-support-e8e20a8c3b79d14d.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Adds support to use ``destination`` and ``nexthop`` as keys in the + ``Route`` objects. ``destination`` maps to ``ip_netmask`` and ``nexthop`` + maps to ``next_hop``. Neutron Route objects use ``destination`` and + ``nexthop``, supporting the same schema allow passing a neutron route + directly to os-net-config.