Enable new routing rules on the fly without restarting interfaces
This change enables os-net-config to add and delete routing rules
without restarting interfaces. The iproute2 binary is called for
each rule that is added or deleted, and the updates happen without
the interface being restarted. The config files will be updated so
that on the next interface restart the changes will be permanent.
This change fixes bug 1865123.
Change-Id: I83f8b26000148a9b311ddf43cd423475373be404
Closes-bug: 1865123
(cherry picked from commit a007ca238b
)
This commit is contained in:
parent
74d32121f6
commit
049314b09b
|
@ -170,6 +170,14 @@ class IfcfgNetConfig(os_net_config.NetConfig):
|
|||
routes.append(line)
|
||||
return routes
|
||||
|
||||
def parse_ifcfg_rules(self, ifcfg_data):
|
||||
"""Break out the individual rules from an ifcfg rule file."""
|
||||
rules = []
|
||||
for line in ifcfg_data.split("\n"):
|
||||
if not line.startswith("#"):
|
||||
rules.append(line)
|
||||
return rules
|
||||
|
||||
def enumerate_ifcfg_changes(self, ifcfg_data_old, ifcfg_data_new):
|
||||
"""Determine which values are added/modified/removed
|
||||
|
||||
|
@ -208,6 +216,24 @@ class IfcfgNetConfig(os_net_config.NetConfig):
|
|||
route_changes.append((route, 'added'))
|
||||
return route_changes
|
||||
|
||||
def enumerate_ifcfg_rule_changes(self, old_rules, new_rules):
|
||||
"""Determine which routes are added or removed.
|
||||
|
||||
:param file_values: contents of existing interface route rule file
|
||||
:param data_values: contents of replacement interface route rule file
|
||||
:return: list of tuples representing changes (rule, state), where
|
||||
state is one of added or removed
|
||||
"""
|
||||
|
||||
rule_changes = []
|
||||
for rule in old_rules:
|
||||
if rule not in new_rules:
|
||||
rule_changes.append((rule, 'removed'))
|
||||
for rule in new_rules:
|
||||
if rule not in old_rules:
|
||||
rule_changes.append((rule, 'added'))
|
||||
return rule_changes
|
||||
|
||||
def ifcfg_requires_restart(self, filename, new_data):
|
||||
"""Determine if changes to the ifcfg file require a restart to apply.
|
||||
|
||||
|
@ -327,6 +353,31 @@ class IfcfgNetConfig(os_net_config.NetConfig):
|
|||
commands.append('route add ' + route[0])
|
||||
return commands
|
||||
|
||||
def iproute2_rule_commands(self, filename, data):
|
||||
"""Return a list of commands for 'ip route' to modify routing rules.
|
||||
|
||||
The list of commands is generated by comparing the old and new
|
||||
configs, and calculating which rules need to be added and which
|
||||
need to be removed.
|
||||
|
||||
:param filename: path to the original interface route rule file
|
||||
:param data: data that is to be written to new route rule file
|
||||
:return: list of commands to feed to 'ip' to reconfigure route rules
|
||||
"""
|
||||
|
||||
file_values = self.parse_ifcfg_rules(utils.get_file_data(filename))
|
||||
data_values = self.parse_ifcfg_rules(data)
|
||||
rule_changes = self.enumerate_ifcfg_rule_changes(file_values,
|
||||
data_values)
|
||||
commands = []
|
||||
|
||||
for rule in rule_changes:
|
||||
if rule[1] == 'removed':
|
||||
commands.append('rule del ' + rule[0])
|
||||
elif rule[1] == 'added':
|
||||
commands.append('rule add ' + rule[0])
|
||||
return commands
|
||||
|
||||
def child_members(self, name):
|
||||
children = set()
|
||||
try:
|
||||
|
@ -1160,6 +1211,7 @@ class IfcfgNetConfig(os_net_config.NetConfig):
|
|||
apply_interfaces = []
|
||||
apply_bridges = []
|
||||
apply_routes = []
|
||||
apply_rules = []
|
||||
update_files = {}
|
||||
all_file_names = []
|
||||
ivs_uplinks = [] # ivs physical uplinks
|
||||
|
@ -1214,6 +1266,8 @@ class IfcfgNetConfig(os_net_config.NetConfig):
|
|||
apply_routes.append((interface_name, route6_data))
|
||||
if utils.diff(rule_path, rule_data):
|
||||
update_files[rule_path] = rule_data
|
||||
if interface_name not in restart_interfaces:
|
||||
apply_rules.append((interface_name, rule_data))
|
||||
|
||||
for interface_name, iface_data in self.ivsinterface_data.items():
|
||||
route_data = self.route_data.get(interface_name, '')
|
||||
|
@ -1248,6 +1302,8 @@ class IfcfgNetConfig(os_net_config.NetConfig):
|
|||
apply_routes.append((interface_name, route6_data))
|
||||
if utils.diff(rule_path, rule_data):
|
||||
update_files[rule_path] = rule_data
|
||||
if interface_name not in restart_interfaces:
|
||||
apply_rules.append((interface_name, rule_data))
|
||||
|
||||
for iface_name, iface_data in self.nfvswitch_intiface_data.items():
|
||||
route_data = self.route_data.get(iface_name, '')
|
||||
|
@ -1282,6 +1338,8 @@ class IfcfgNetConfig(os_net_config.NetConfig):
|
|||
apply_routes.append((iface_name, route6_data))
|
||||
if utils.diff(rule_path, rule_data):
|
||||
update_files[rule_path] = rule_data
|
||||
if iface_name not in restart_interfaces:
|
||||
apply_rules.append((iface_name, rule_data))
|
||||
|
||||
for bridge_name, bridge_data in self.bridge_data.items():
|
||||
route_data = self.route_data.get(bridge_name, '')
|
||||
|
@ -1319,6 +1377,8 @@ class IfcfgNetConfig(os_net_config.NetConfig):
|
|||
apply_routes.append((bridge_name, route6_data))
|
||||
if utils.diff(br_rule_path, rule_data):
|
||||
update_files[br_rule_path] = rule_data
|
||||
if bridge_name not in restart_interfaces:
|
||||
apply_rules.append((bridge_name, rule_data))
|
||||
|
||||
for bridge_name, bridge_data in self.linuxbridge_data.items():
|
||||
route_data = self.route_data.get(bridge_name, '')
|
||||
|
@ -1356,6 +1416,8 @@ class IfcfgNetConfig(os_net_config.NetConfig):
|
|||
apply_routes.append((bridge_name, route6_data))
|
||||
if utils.diff(br_rule_path, rule_data):
|
||||
update_files[br_rule_path] = rule_data
|
||||
if bridge_name not in restart_bridges:
|
||||
apply_rules.append((bridge_name, rule_data))
|
||||
|
||||
for team_name, team_data in self.linuxteam_data.items():
|
||||
route_data = self.route_data.get(team_name, '')
|
||||
|
@ -1469,6 +1531,8 @@ class IfcfgNetConfig(os_net_config.NetConfig):
|
|||
apply_routes.append((interface_name, route6_data))
|
||||
if utils.diff(rule_path, rule_data):
|
||||
update_files[rule_path] = rule_data
|
||||
if interface_name not in restart_interfaces:
|
||||
apply_rules.append((interface_name, rule_data))
|
||||
|
||||
# NOTE(hjensas): Process the VLAN's last so that we know if the vlan's
|
||||
# parent interface is being restarted.
|
||||
|
@ -1513,6 +1577,8 @@ class IfcfgNetConfig(os_net_config.NetConfig):
|
|||
apply_routes.append((vlan_name, route6_data))
|
||||
if utils.diff(vlan_rule_path, rule_data):
|
||||
update_files[vlan_rule_path] = rule_data
|
||||
if vlan_name not in restart_vlans:
|
||||
apply_rules.append((vlan_name, rule_data))
|
||||
|
||||
if self.vpp_interface_data or self.vpp_bond_data:
|
||||
vpp_path = self.root_dir + vpp_config_path()
|
||||
|
@ -1589,6 +1655,24 @@ class IfcfgNetConfig(os_net_config.NetConfig):
|
|||
self.child_members(interface[0]))
|
||||
break
|
||||
|
||||
for interface in apply_rules:
|
||||
logger.debug('Applying rules for interface %s' % interface[0])
|
||||
filename = self.root_dir + route_rule_config_path(interface[0])
|
||||
commands = self.iproute2_rule_commands(filename, interface[1])
|
||||
for command in commands:
|
||||
args = command.split()
|
||||
try:
|
||||
if len(args) > 0:
|
||||
self.execute('Running ip %s' % command, ipcmd,
|
||||
*args)
|
||||
except Exception as e:
|
||||
logger.warning("Error in 'ip %s', restarting %s:\n%s" %
|
||||
(command, interface[0], str(e)))
|
||||
restart_interfaces.append(interface[0])
|
||||
restart_interfaces.extend(
|
||||
self.child_members(interface[0]))
|
||||
break
|
||||
|
||||
for vlan in restart_vlans:
|
||||
self.ifdown(vlan)
|
||||
|
||||
|
|
|
@ -142,6 +142,14 @@ _IFCFG_ROUTES2 = """default via 192.0.1.1 dev eth0
|
|||
192.0.1.1/24 via 192.0.3.1 dev eth1
|
||||
"""
|
||||
|
||||
_IFCFG_RULES1 = """to 192.168.2.0/24 table main priority 500
|
||||
from 192.168.2.0/24 table 200 priority 501
|
||||
"""
|
||||
|
||||
_IFCFG_RULES2 = """to 192.168.1.0/24 table main priority 500
|
||||
from 192.168.1.0/24 table 200 priority 501
|
||||
"""
|
||||
|
||||
_V4_IFCFG_MAPPED = _V4_IFCFG.replace('em1', 'nic1') + "HWADDR=a1:b2:c3:d4:e5\n"
|
||||
|
||||
|
||||
|
@ -2183,6 +2191,24 @@ class TestIfcfgNetConfigApply(base.TestCase):
|
|||
_IFCFG_ROUTES2)
|
||||
self.assertTrue(commands == command_list1)
|
||||
|
||||
def test_ifcfg_rule_commands(self):
|
||||
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
interface = "eth0"
|
||||
rule_filename = tmpdir + '/rule-' + interface
|
||||
file = open(rule_filename, 'w')
|
||||
file.write(_IFCFG_RULES1)
|
||||
file.close()
|
||||
|
||||
# Changing only the rules should delete and add rules
|
||||
command_list1 = ['rule del to 192.168.2.0/24 table main priority 500',
|
||||
'rule del from 192.168.2.0/24 table 200 priority 501',
|
||||
'rule add to 192.168.1.0/24 table main priority 500',
|
||||
'rule add from 192.168.1.0/24 table 200 priority 501']
|
||||
commands = self.provider.iproute2_rule_commands(rule_filename,
|
||||
_IFCFG_RULES2)
|
||||
self.assertTrue(commands == command_list1)
|
||||
|
||||
def test_ifcfg_ipmtu_commands(self):
|
||||
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
|
|
Loading…
Reference in New Issue