mirror of
https://github.com/linux-system-roles/network.git
synced 2026-01-23 02:15:17 +00:00
Support routing tables in static routes
The users want to use the policy routing (e.g. source routing), so that they can forward the packet based on the other criteria except for the destination address in the packet. In such scenario, the routing tables have to be supported beforehand in static routes, so that the users can define policy routing rules later to instruct the system which table to use to determine the correct route. Signed-off-by: Wen Liang <liangwen12year@gmail.com>
This commit is contained in:
parent
20667b0860
commit
5eb03fa992
8 changed files with 620 additions and 6 deletions
|
|
@ -473,8 +473,11 @@ The IP configuration supports the following options:
|
|||
|
||||
Static route configuration can be specified via a list of routes given in the
|
||||
`route` option. The default value is an empty list. Each route is a dictionary with
|
||||
the following entries: `network`, `prefix`, `gateway` and `metric`. `network` and
|
||||
`prefix` specify the destination network.
|
||||
the following entries: `network`, `prefix`, `gateway`, `metric` and `table`.
|
||||
`network` and `prefix` specify the destination network. `table` supports both the
|
||||
numeric table and named table. In order to specify the named table, the users have
|
||||
to ensure the named table is properly defined in `/etc/iproute2/rt_tables` or
|
||||
`/etc/iproute2/rt_tables.d/*.conf`.
|
||||
Note that Classless inter-domain routing (CIDR) notation or network mask notation
|
||||
are not supported yet.
|
||||
|
||||
|
|
|
|||
36
examples/route_table_support.yml
Normal file
36
examples/route_table_support.yml
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- hosts: all
|
||||
tasks:
|
||||
- name: Add a new routing table
|
||||
lineinfile:
|
||||
path: /etc/iproute2/rt_tables.d/table.conf
|
||||
line: "200 custom"
|
||||
mode: "0644"
|
||||
create: yes
|
||||
|
||||
- name: Configure connection profile and specify the table in static routes
|
||||
import_role:
|
||||
name: linux-system-roles.network
|
||||
vars:
|
||||
network_connections:
|
||||
- name: eth0
|
||||
type: ethernet
|
||||
state: up
|
||||
autoconnect: yes
|
||||
ip:
|
||||
dhcp4: no
|
||||
address:
|
||||
- 198.51.100.3/26
|
||||
route:
|
||||
- network: 198.51.100.128
|
||||
prefix: 26
|
||||
gateway: 198.51.100.1
|
||||
metric: 2
|
||||
table: 30400
|
||||
- network: 198.51.100.64
|
||||
prefix: 26
|
||||
gateway: 198.51.100.6
|
||||
metric: 4
|
||||
table: custom
|
||||
...
|
||||
|
|
@ -1133,6 +1133,11 @@ class NMUtil:
|
|||
rr = NM.IPRoute.new(
|
||||
r["family"], r["network"], r["prefix"], r["gateway"], r["metric"]
|
||||
)
|
||||
if r["table"]:
|
||||
NM.IPRoute.set_attribute(
|
||||
rr, "table", Util.GLib().Variant.new_uint32(r["table"])
|
||||
)
|
||||
|
||||
if r["family"] == socket.AF_INET:
|
||||
s_ip4.add_route(rr)
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ from __future__ import absolute_import, division, print_function
|
|||
|
||||
__metaclass__ = type
|
||||
|
||||
import os
|
||||
import posixpath
|
||||
import socket
|
||||
import re
|
||||
|
|
@ -246,6 +247,62 @@ class ArgValidatorStr(ArgValidator):
|
|||
return True
|
||||
|
||||
|
||||
class ArgValidatorRouteTable(ArgValidator):
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
required=False,
|
||||
default_value=None,
|
||||
):
|
||||
ArgValidator.__init__(self, name, required, default_value)
|
||||
|
||||
def _validate_impl(self, value, name):
|
||||
table = None
|
||||
try:
|
||||
if isinstance(value, bool):
|
||||
# bool can (probably) be converted to integer type,
|
||||
# but here we don't want to accept a boolean value.
|
||||
pass
|
||||
elif isinstance(value, int):
|
||||
table = int(value)
|
||||
elif isinstance(value, Util.STRING_TYPE):
|
||||
try:
|
||||
table = int(value)
|
||||
except Exception:
|
||||
table = value
|
||||
except Exception:
|
||||
pass
|
||||
if table is None:
|
||||
raise ValidationError(
|
||||
name,
|
||||
"route table must be the named or numeric tables but is {0}".format(
|
||||
value
|
||||
),
|
||||
)
|
||||
if isinstance(table, int):
|
||||
if table < 1:
|
||||
raise ValidationError(
|
||||
name,
|
||||
"route table value is {0} but cannot be less than 1".format(value),
|
||||
)
|
||||
elif table > 0xFFFFFFFF:
|
||||
raise ValidationError(
|
||||
name,
|
||||
"route table value is {0} but cannot be greater than 4294967295".format(
|
||||
value
|
||||
),
|
||||
)
|
||||
if isinstance(table, Util.STRING_TYPE):
|
||||
if table == "":
|
||||
raise ValidationError(name, "route table name cannot be empty string")
|
||||
if not IPRouteUtils.ROUTE_TABLE_ALIAS_RE.match(table):
|
||||
raise ValidationError(
|
||||
name, "route table name contains invalid characters"
|
||||
)
|
||||
|
||||
return table
|
||||
|
||||
|
||||
class ArgValidatorNum(ArgValidator):
|
||||
def __init__( # pylint: disable=too-many-arguments
|
||||
self,
|
||||
|
|
@ -543,6 +600,7 @@ class ArgValidatorIPRoute(ArgValidatorDict):
|
|||
ArgValidatorNum(
|
||||
"metric", default_value=-1, val_min=-1, val_max=0xFFFFFFFF
|
||||
),
|
||||
ArgValidatorRouteTable("table"),
|
||||
],
|
||||
default_value=None,
|
||||
)
|
||||
|
|
@ -1858,6 +1916,19 @@ class ArgValidator_ListConnections(ArgValidatorList):
|
|||
VALIDATE_ONE_MODE_NM = "nm"
|
||||
VALIDATE_ONE_MODE_INITSCRIPTS = "initscripts"
|
||||
|
||||
def validate_route_tables(self, connection, idx):
|
||||
for r in connection["ip"]["route"]:
|
||||
if isinstance(r["table"], Util.STRING_TYPE):
|
||||
mapping = IPRouteUtils.get_route_tables_mapping()
|
||||
if r["table"] in mapping:
|
||||
r["table"] = mapping[r["table"]]
|
||||
else:
|
||||
raise ValidationError.from_connection(
|
||||
idx,
|
||||
"cannot find route table {0} in `/etc/iproute2/rt_tables` or "
|
||||
"`/etc/iproute2/rt_tables.d/`".format(r["table"]),
|
||||
)
|
||||
|
||||
def validate_connection_one(self, mode, connections, idx):
|
||||
def _ipv4_enabled(connection):
|
||||
has_addrs4 = any(
|
||||
|
|
@ -2012,3 +2083,99 @@ class ArgValidator_ListConnections(ArgValidatorList):
|
|||
"match.path is not supported by the running version of "
|
||||
"NetworkManger.",
|
||||
)
|
||||
self.validate_route_tables(connection, idx)
|
||||
|
||||
|
||||
class IPRouteUtils(object):
|
||||
|
||||
ROUTE_TABLE_ALIAS_RE = re.compile("^[a-zA-Z0-9_.-]+$")
|
||||
|
||||
@classmethod
|
||||
def _parse_route_tables_mapping(cls, file_content, mapping):
|
||||
for line in file_content.split(b"\n"):
|
||||
|
||||
# iproute2 only skips over leading ' ' and '\t'.
|
||||
line = line.lstrip()
|
||||
|
||||
# skip empty lines or comments
|
||||
if not line:
|
||||
continue
|
||||
|
||||
# In Python 2.x, there is no new type called `bytes`. And Python 2.x
|
||||
# `bytes` is just an alias to the str type
|
||||
if Util.PY3:
|
||||
if line[0] == ord(b"#"):
|
||||
continue
|
||||
else:
|
||||
if line[0] == "#":
|
||||
continue
|
||||
|
||||
# iproute2 splits at the first space.
|
||||
ll = line.split(b" ", 1)
|
||||
if len(ll) != 2:
|
||||
continue
|
||||
line1 = ll[0]
|
||||
line2 = ll[1]
|
||||
|
||||
comment_space = line2.find(b" ")
|
||||
if comment_space >= 0:
|
||||
# when commenting the route table entry in the same line,
|
||||
# iproute2 only accepts one ' ', followed by '#'
|
||||
if not re.match(b"^[a-zA-Z0-9_.-]+ #", line2):
|
||||
continue
|
||||
line2 = line2[:comment_space]
|
||||
|
||||
# convert to UTF-8 and only accept benign characters.
|
||||
try:
|
||||
line2 = line2.decode("utf-8")
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
if not cls.ROUTE_TABLE_ALIAS_RE.match(line2):
|
||||
continue
|
||||
|
||||
tableid = None
|
||||
try:
|
||||
tableid = int(line1)
|
||||
except Exception:
|
||||
if line.startswith(b"0x"):
|
||||
try:
|
||||
tableid = int(line1[2:], 16)
|
||||
except Exception:
|
||||
pass
|
||||
if tableid is None or tableid < 0 or tableid > 0xFFFFFFFF:
|
||||
continue
|
||||
|
||||
mapping[line2] = tableid
|
||||
|
||||
@classmethod
|
||||
def _parse_route_tables_mapping_from_file(cls, filename, mapping):
|
||||
try:
|
||||
with open(filename, "rb") as f:
|
||||
file_content = f.read()
|
||||
except Exception:
|
||||
return
|
||||
cls._parse_route_tables_mapping(file_content, mapping)
|
||||
|
||||
@classmethod
|
||||
def get_route_tables_mapping(cls):
|
||||
if not hasattr(cls, "_cached_rt_tables"):
|
||||
mapping = {}
|
||||
cls._parse_route_tables_mapping_from_file(
|
||||
"/etc/iproute2/rt_tables", mapping
|
||||
)
|
||||
# In iproute2, the directory `/etc/iproute2/rt_tables/rt_tables.d`
|
||||
# is also iterated when get the mapping between the route table name
|
||||
# and route table id,
|
||||
# https://git.kernel.org/pub/scm/network/iproute2/iproute2.git/tree/lib/rt_names.c?id=ade99e208c1843ed3b6eb9d138aa15a6a5eb5219#n391
|
||||
try:
|
||||
fnames = os.listdir("/etc/iproute2/rt_tables.d")
|
||||
except Exception:
|
||||
fnames = []
|
||||
for f in fnames:
|
||||
if f.endswith(".conf") and f[0] != ".":
|
||||
cls._parse_route_tables_mapping_from_file(
|
||||
"/etc/iproute2/rt_tables.d/" + f, mapping
|
||||
)
|
||||
cls._cached_rt_tables = mapping
|
||||
return cls._cached_rt_tables
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ ibution_major_version | int < 9",
|
|||
"comment": "# NetworkManager 1.26.0 added support for match.path setting",
|
||||
},
|
||||
"playbooks/tests_reapply.yml": {},
|
||||
"playbooks/tests_route_table.yml": {},
|
||||
# team interface is not supported on Fedora
|
||||
"playbooks/tests_team.yml": {
|
||||
EXTRA_RUN_CONDITION: "ansible_distribution != 'Fedora'",
|
||||
|
|
|
|||
151
tests/playbooks/tests_route_table.yml
Normal file
151
tests/playbooks/tests_route_table.yml
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
---
|
||||
- hosts: all
|
||||
|
||||
- name: Test configuring ethernet devices
|
||||
hosts: all
|
||||
vars:
|
||||
type: veth
|
||||
interface: ethtest0
|
||||
|
||||
|
||||
tasks:
|
||||
- name: "set type={{ type }} and interface={{ interface }}"
|
||||
set_fact:
|
||||
type: "{{ type }}"
|
||||
interface: "{{ interface }}"
|
||||
- include_tasks: tasks/show_interfaces.yml
|
||||
- include_tasks: tasks/manage_test_interface.yml
|
||||
vars:
|
||||
state: present
|
||||
- include_tasks: tasks/assert_device_present.yml
|
||||
|
||||
- name: Configure connection profile and specify the numeric table in
|
||||
static routes
|
||||
import_role:
|
||||
name: linux-system-roles.network
|
||||
vars:
|
||||
network_connections:
|
||||
- name: "{{ interface }}"
|
||||
interface_name: "{{ interface }}"
|
||||
state: up
|
||||
type: ethernet
|
||||
autoconnect: yes
|
||||
ip:
|
||||
dhcp4: no
|
||||
address:
|
||||
- 198.51.100.3/26
|
||||
route:
|
||||
- network: 198.51.100.128
|
||||
prefix: 26
|
||||
gateway: 198.51.100.1
|
||||
metric: 2
|
||||
table: 30400
|
||||
- network: 198.51.100.64
|
||||
prefix: 26
|
||||
gateway: 198.51.100.6
|
||||
metric: 4
|
||||
table: 30200
|
||||
|
||||
- name: Get the routes from the route table 30200
|
||||
command: ip route show table 30200
|
||||
register: route_table_30200
|
||||
ignore_errors: yes
|
||||
changed_when: false
|
||||
|
||||
- name: Get the routes from the route table 30400
|
||||
command: ip route show table 30400
|
||||
register: route_table_30400
|
||||
ignore_errors: yes
|
||||
changed_when: false
|
||||
|
||||
- name: Assert that the route table 30200 contains the specified route
|
||||
assert:
|
||||
that:
|
||||
- route_table_30200.stdout is search("198.51.100.64/26 via
|
||||
198.51.100.6 dev ethtest0 proto static metric 4")
|
||||
msg: "the route table 30200 does not exist or does not contain the
|
||||
specified route"
|
||||
|
||||
|
||||
- name: Assert that the route table 30400 contains the specified route
|
||||
assert:
|
||||
that:
|
||||
- route_table_30400.stdout is search("198.51.100.128/26 via
|
||||
198.51.100.1 dev ethtest0 proto static metric 2")
|
||||
msg: "the route table 30400 does not exist or does not contain the
|
||||
specified route"
|
||||
|
||||
- name: Create a dedicated test file in `/etc/iproute2/rt_tables.d/` and
|
||||
add a new routing table
|
||||
lineinfile:
|
||||
path: /etc/iproute2/rt_tables.d/table.conf
|
||||
line: "200 custom"
|
||||
mode: "0644"
|
||||
create: yes
|
||||
|
||||
- name: Reconfigure connection profile and specify the named table in
|
||||
static routes
|
||||
import_role:
|
||||
name: linux-system-roles.network
|
||||
vars:
|
||||
network_connections:
|
||||
- name: "{{ interface }}"
|
||||
interface_name: "{{ interface }}"
|
||||
state: up
|
||||
type: ethernet
|
||||
autoconnect: yes
|
||||
ip:
|
||||
dhcp4: no
|
||||
address:
|
||||
- 198.51.100.3/26
|
||||
route:
|
||||
- network: 198.51.100.128
|
||||
prefix: 26
|
||||
gateway: 198.51.100.1
|
||||
metric: 2
|
||||
table: custom
|
||||
- network: 198.51.100.64
|
||||
prefix: 26
|
||||
gateway: 198.51.100.6
|
||||
metric: 4
|
||||
table: custom
|
||||
|
||||
- name: Get the routes from the named route table 'custom'
|
||||
command: ip route show table custom
|
||||
register: route_table_custom
|
||||
ignore_errors: yes
|
||||
changed_when: false
|
||||
|
||||
- name: Assert that the named route table 'custom' contains the
|
||||
specified route
|
||||
assert:
|
||||
that:
|
||||
- route_table_custom.stdout is search("198.51.100.128/26 via
|
||||
198.51.100.1 dev ethtest0 proto static metric 2")
|
||||
- route_table_custom.stdout is search("198.51.100.64/26 via
|
||||
198.51.100.6 dev ethtest0 proto static metric 4")
|
||||
msg: "the named route table 'custom' does not exist or does not contain
|
||||
the specified route"
|
||||
|
||||
- name: Remove the dedicated test file in `/etc/iproute2/rt_tables.d/`
|
||||
file:
|
||||
state: absent
|
||||
path: /etc/iproute2/rt_tables.d/table.conf
|
||||
|
||||
- import_playbook: down_profile.yml
|
||||
vars:
|
||||
profile: "{{ interface }}"
|
||||
# FIXME: assert profile/device down
|
||||
- import_playbook: remove_profile.yml
|
||||
vars:
|
||||
profile: "{{ interface }}"
|
||||
# FIXME: assert profile away
|
||||
- name: Remove interfaces
|
||||
hosts: all
|
||||
tasks:
|
||||
- include_tasks: tasks/manage_test_interface.yml
|
||||
vars:
|
||||
state: absent
|
||||
- include_tasks: tasks/assert_device_absent.yml
|
||||
...
|
||||
20
tests/tests_route_table_nm.yml
Normal file
20
tests/tests_route_table_nm.yml
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
# This file was generated by ensure_provider_tests.py
|
||||
---
|
||||
# set network provider and gather facts
|
||||
- hosts: all
|
||||
name: Run playbook 'playbooks/tests_route_table.yml' with nm as provider
|
||||
tasks:
|
||||
- include_tasks: tasks/el_repo_setup.yml
|
||||
- name: Set network provider to 'nm'
|
||||
set_fact:
|
||||
network_provider: nm
|
||||
tags:
|
||||
- always
|
||||
|
||||
|
||||
# The test requires or should run with NetworkManager, therefore it cannot run
|
||||
# on RHEL/CentOS 6
|
||||
- import_playbook: playbooks/tests_route_table.yml
|
||||
when:
|
||||
- ansible_distribution_major_version != '6'
|
||||
|
|
@ -230,6 +230,7 @@ class TestValidator(Python26CompatTestCase):
|
|||
"prefix": int(r.get_prefix()),
|
||||
"gateway": r.get_next_hop(),
|
||||
"metric": int(r.get_metric()),
|
||||
"table": r.get_attribute("table"),
|
||||
}
|
||||
for r in route_list_new
|
||||
]
|
||||
|
|
@ -267,17 +268,21 @@ class TestValidator(Python26CompatTestCase):
|
|||
s6.clear_routes()
|
||||
for r in kwargs["nm_route_list_current"][idx]:
|
||||
r = parser.validate(r)
|
||||
r = NM.IPRoute.new(
|
||||
rr = NM.IPRoute.new(
|
||||
r["family"],
|
||||
r["network"],
|
||||
r["prefix"],
|
||||
r["gateway"],
|
||||
r["metric"],
|
||||
)
|
||||
if r.get_family() == socket.AF_INET:
|
||||
s4.add_route(r)
|
||||
if r["table"]:
|
||||
NM.IPRoute.set_attribute(
|
||||
rr, "table", Util.GLib().Variant.new_uint32(r["table"])
|
||||
)
|
||||
if r["family"] == socket.AF_INET:
|
||||
s4.add_route(rr)
|
||||
else:
|
||||
s6.add_route(r)
|
||||
s6.add_route(rr)
|
||||
con_new = nmutil.connection_create(
|
||||
connections, idx, connection_current=con_new
|
||||
)
|
||||
|
|
@ -1038,6 +1043,7 @@ class TestValidator(Python26CompatTestCase):
|
|||
"prefix": 24,
|
||||
"gateway": None,
|
||||
"metric": -1,
|
||||
"table": None,
|
||||
}
|
||||
],
|
||||
},
|
||||
|
|
@ -1357,6 +1363,7 @@ class TestValidator(Python26CompatTestCase):
|
|||
"prefix": 24,
|
||||
"gateway": None,
|
||||
"metric": -1,
|
||||
"table": None,
|
||||
}
|
||||
],
|
||||
},
|
||||
|
|
@ -1495,6 +1502,7 @@ class TestValidator(Python26CompatTestCase):
|
|||
"prefix": 24,
|
||||
"gateway": None,
|
||||
"metric": -1,
|
||||
"table": None,
|
||||
}
|
||||
],
|
||||
},
|
||||
|
|
@ -1551,6 +1559,7 @@ class TestValidator(Python26CompatTestCase):
|
|||
"prefix": 24,
|
||||
"gateway": None,
|
||||
"metric": -1,
|
||||
"table": None,
|
||||
}
|
||||
],
|
||||
},
|
||||
|
|
@ -2248,6 +2257,7 @@ class TestValidator(Python26CompatTestCase):
|
|||
"prefix": 24,
|
||||
"gateway": None,
|
||||
"metric": 545,
|
||||
"table": None,
|
||||
},
|
||||
{
|
||||
"family": socket.AF_INET,
|
||||
|
|
@ -2255,6 +2265,7 @@ class TestValidator(Python26CompatTestCase):
|
|||
"prefix": 30,
|
||||
"gateway": None,
|
||||
"metric": -1,
|
||||
"table": None,
|
||||
},
|
||||
],
|
||||
"dns": [],
|
||||
|
|
@ -2345,6 +2356,7 @@ class TestValidator(Python26CompatTestCase):
|
|||
"prefix": 24,
|
||||
"gateway": None,
|
||||
"metric": 545,
|
||||
"table": None,
|
||||
},
|
||||
{
|
||||
"family": socket.AF_INET,
|
||||
|
|
@ -2352,6 +2364,7 @@ class TestValidator(Python26CompatTestCase):
|
|||
"prefix": 30,
|
||||
"gateway": None,
|
||||
"metric": -1,
|
||||
"table": None,
|
||||
},
|
||||
{
|
||||
"family": socket.AF_INET6,
|
||||
|
|
@ -2359,6 +2372,7 @@ class TestValidator(Python26CompatTestCase):
|
|||
"prefix": 64,
|
||||
"gateway": None,
|
||||
"metric": -1,
|
||||
"table": None,
|
||||
},
|
||||
],
|
||||
"dns": [],
|
||||
|
|
@ -4081,6 +4095,223 @@ class TestUtils(unittest.TestCase):
|
|||
self.assertEqual(result, test_case[1])
|
||||
|
||||
|
||||
class TestValidatorRouteTable(Python26CompatTestCase):
|
||||
def setUp(self):
|
||||
self.test_connections = [
|
||||
{
|
||||
"name": "eth0",
|
||||
"type": "ethernet",
|
||||
"ip": {
|
||||
"dhcp4": False,
|
||||
"address": ["198.51.100.3/26"],
|
||||
"route": [
|
||||
{
|
||||
"network": "198.51.100.128",
|
||||
"prefix": 26,
|
||||
"gateway": "198.51.100.1",
|
||||
"metric": 2,
|
||||
"table": 30400,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
]
|
||||
self.validator = network_lsr.argument_validator.ArgValidator_ListConnections()
|
||||
self.rt_parsing = network_lsr.argument_validator.IPRouteUtils()
|
||||
self.old_getter = (
|
||||
network_lsr.argument_validator.IPRouteUtils.get_route_tables_mapping
|
||||
)
|
||||
network_lsr.argument_validator.IPRouteUtils.get_route_tables_mapping = (
|
||||
classmethod(
|
||||
lambda cls: {
|
||||
"custom": 200,
|
||||
"eth": 30400,
|
||||
}
|
||||
)
|
||||
)
|
||||
# the connection index is 0 because there is only one connection profile
|
||||
# defined here
|
||||
self.connection_index = 0
|
||||
|
||||
def tearDown(self):
|
||||
network_lsr.argument_validator.IPRouteUtils.get_route_tables_mapping = (
|
||||
self.old_getter
|
||||
)
|
||||
|
||||
def test_valid_numeric_route_tables(self):
|
||||
"""
|
||||
Test that the value between 1 and 4294967295 are the valid value for numeric
|
||||
route tables and the value will not be normalized
|
||||
"""
|
||||
|
||||
self.validator.validate_route_tables(
|
||||
self.validator.validate(self.test_connections)[0],
|
||||
self.connection_index,
|
||||
)
|
||||
self.assertEqual(
|
||||
self.test_connections[0]["ip"]["route"][0]["table"],
|
||||
30400,
|
||||
)
|
||||
|
||||
self.test_connections[0]["ip"]["route"][0]["table"] = 200
|
||||
self.validator.validate_route_tables(
|
||||
self.validator.validate(self.test_connections)[0],
|
||||
self.connection_index,
|
||||
)
|
||||
self.assertEqual(
|
||||
self.test_connections[0]["ip"]["route"][0]["table"],
|
||||
200,
|
||||
)
|
||||
|
||||
def test_invalid_numeric_route_tables(self):
|
||||
"""
|
||||
Test that the value less than 1 or greater than 4294967295 are the invalid
|
||||
value for numeric route tables
|
||||
"""
|
||||
|
||||
self.test_connections[0]["ip"]["route"][0]["table"] = 0
|
||||
val_min = 1
|
||||
val_max = 0xFFFFFFFF
|
||||
self.assertRaisesRegex(
|
||||
ValidationError,
|
||||
"route table value is {0} but cannot be less than {1}".format(
|
||||
self.test_connections[0]["ip"]["route"][0]["table"],
|
||||
val_min,
|
||||
),
|
||||
self.validator.validate,
|
||||
self.test_connections,
|
||||
)
|
||||
|
||||
self.test_connections[0]["ip"]["route"][0]["table"] = 4294967296
|
||||
self.assertRaisesRegex(
|
||||
ValidationError,
|
||||
"route table value is {0} but cannot be greater than {1}".format(
|
||||
self.test_connections[0]["ip"]["route"][0]["table"],
|
||||
val_max,
|
||||
),
|
||||
self.validator.validate,
|
||||
self.test_connections,
|
||||
)
|
||||
|
||||
def test_empty_route_table_name(self):
|
||||
"""
|
||||
Test that empty string is invalid value for route table name
|
||||
"""
|
||||
|
||||
self.test_connections[0]["ip"]["route"][0]["table"] = ""
|
||||
self.assertRaisesRegex(
|
||||
ValidationError,
|
||||
"route table name cannot be empty string",
|
||||
self.validator.validate,
|
||||
self.test_connections,
|
||||
)
|
||||
|
||||
def test_invalid_value_types_for_route_tables(self):
|
||||
"""
|
||||
Test that the value types apart from string type and integer type are all
|
||||
invalid value types for route tables
|
||||
"""
|
||||
|
||||
self.test_connections[0]["ip"]["route"][0]["table"] = False
|
||||
self.assertRaisesRegex(
|
||||
ValidationError,
|
||||
"route table must be the named or numeric tables but is {0}".format(
|
||||
self.test_connections[0]["ip"]["route"][0]["table"]
|
||||
),
|
||||
self.validator.validate,
|
||||
self.test_connections,
|
||||
)
|
||||
|
||||
self.test_connections[0]["ip"]["route"][0]["table"] = 2.5
|
||||
self.assertRaisesRegex(
|
||||
ValidationError,
|
||||
"route table must be the named or numeric tables but is {0}".format(
|
||||
self.test_connections[0]["ip"]["route"][0]["table"]
|
||||
),
|
||||
self.validator.validate,
|
||||
self.test_connections,
|
||||
)
|
||||
|
||||
def test_invalid_route_table_names(self):
|
||||
"""
|
||||
Test that the route table names should not be composed from the characters
|
||||
which are not contained within the benign set r"^[a-zA-Z0-9_.-]+$"
|
||||
"""
|
||||
|
||||
self.test_connections[0]["ip"]["route"][0]["table"] = "test*"
|
||||
self.assertRaisesRegex(
|
||||
ValidationError,
|
||||
"route table name contains invalid characters",
|
||||
self.validator.validate,
|
||||
self.test_connections,
|
||||
)
|
||||
|
||||
self.test_connections[0]["ip"]["route"][0]["table"] = "!!!"
|
||||
self.assertRaisesRegex(
|
||||
ValidationError,
|
||||
"route table name contains invalid characters",
|
||||
self.validator.validate,
|
||||
self.test_connections,
|
||||
)
|
||||
|
||||
def test_parse_rt_tables(self):
|
||||
"""
|
||||
Test that the `IPRouteUtils._parse_route_tables_mapping()` will create the
|
||||
route tables mapping dictionary when feeding the proper route table file
|
||||
content
|
||||
"""
|
||||
|
||||
def parse(file_content):
|
||||
mapping = {}
|
||||
network_lsr.argument_validator.IPRouteUtils._parse_route_tables_mapping(
|
||||
file_content, mapping
|
||||
)
|
||||
return mapping
|
||||
|
||||
self.assertEqual(parse(b""), {})
|
||||
self.assertEqual(parse(b"5 x"), {"x": 5})
|
||||
self.assertEqual(parse(b" 7 y "), {})
|
||||
self.assertEqual(parse(b"5 x\n0x4 y"), {"x": 5, "y": 4})
|
||||
self.assertEqual(parse(b"5 x #df\n0x4 y"), {"x": 5, "y": 4})
|
||||
self.assertEqual(parse(b"5 x #df\n0x4 y\n7\ty"), {"x": 5, "y": 4})
|
||||
self.assertEqual(parse(b"-1 x #df\n0x4 y\n5 x"), {"x": 5, "y": 4})
|
||||
|
||||
def test_table_found_when_validate_route_tables(self):
|
||||
"""
|
||||
Test that the `validate_route_tables()` will find the table id mapping from
|
||||
`IPRouteUtils.get_route_tables_mapping()`.
|
||||
"""
|
||||
|
||||
self.test_connections[0]["ip"]["route"][0]["table"] = "custom"
|
||||
|
||||
self.validator.validate_route_tables(
|
||||
self.test_connections[0],
|
||||
self.connection_index,
|
||||
)
|
||||
self.assertEqual(
|
||||
self.test_connections[0]["ip"]["route"][0]["table"],
|
||||
200,
|
||||
)
|
||||
|
||||
def test_table_not_found_when_validate_route_tables(self):
|
||||
"""
|
||||
Test that the validation error is raised when the `validate_route_tables()` cannot
|
||||
find the table id mapping from `IPRouteUtils.get_route_tables_mapping()`.
|
||||
"""
|
||||
|
||||
self.test_connections[0]["ip"]["route"][0]["table"] = "test"
|
||||
self.assertRaisesRegex(
|
||||
ValidationError,
|
||||
"cannot find route table {0} in `/etc/iproute2/rt_tables` or "
|
||||
"`/etc/iproute2/rt_tables.d/`".format(
|
||||
self.test_connections[0]["ip"]["route"][0]["table"]
|
||||
),
|
||||
self.validator.validate_route_tables,
|
||||
self.test_connections[0],
|
||||
self.connection_index,
|
||||
)
|
||||
|
||||
|
||||
class TestSysUtils(unittest.TestCase):
|
||||
def test_link_read_permaddress(self):
|
||||
self.assertEqual(SysUtil._link_read_permaddress("lo"), "00:00:00:00:00:00")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue