610 lines
20 KiB
Python
610 lines
20 KiB
Python
# Copyright 2021 Red Hat, Inc.
|
|
#
|
|
# 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 errno
|
|
import ipaddress
|
|
import os
|
|
import socket
|
|
|
|
import netaddr
|
|
|
|
from oslo_concurrency import processutils
|
|
from oslo_log import log as logging
|
|
import pyroute2
|
|
from pyroute2 import iproute
|
|
from pyroute2 import netlink as pyroute_netlink
|
|
from pyroute2.netlink import exceptions as netlink_exceptions
|
|
from pyroute2.netlink import rtnl
|
|
from pyroute2.netlink.rtnl import ndmsg
|
|
import tenacity
|
|
|
|
import ovn_bgp_agent
|
|
from ovn_bgp_agent import constants
|
|
from ovn_bgp_agent import exceptions as agent_exc
|
|
from ovn_bgp_agent.utils import linux_net as l_net
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
_IP_VERSION_FAMILY_MAP = {4: socket.AF_INET, 6: socket.AF_INET6}
|
|
NUD_STATES = {state[1]: state[0] for state in ndmsg.states.items()}
|
|
|
|
|
|
def get_scope_name(scope):
|
|
"""Return the name of the scope or the scope number if the name is unknown.
|
|
|
|
For backward compatibility (with "ip" tool) "global" scope is converted to
|
|
"universe" before converting to number
|
|
"""
|
|
scope = 'universe' if scope == 'global' else scope
|
|
return rtnl.rt_scope.get(scope, scope)
|
|
|
|
|
|
def set_device_state(device, state):
|
|
set_link_attribute(device, state=state)
|
|
|
|
|
|
@ovn_bgp_agent.privileged.default.entrypoint
|
|
def ensure_vrf(vrf_name, vrf_table):
|
|
try:
|
|
set_device_state(vrf_name, constants.LINK_UP)
|
|
except agent_exc.NetworkInterfaceNotFound:
|
|
create_interface(vrf_name, 'vrf', vrf_table=vrf_table,
|
|
state=constants.LINK_UP)
|
|
|
|
|
|
@ovn_bgp_agent.privileged.default.entrypoint
|
|
def ensure_bridge(bridge_name):
|
|
try:
|
|
set_device_state(bridge_name, constants.LINK_UP)
|
|
except agent_exc.NetworkInterfaceNotFound:
|
|
create_interface(bridge_name, 'bridge', br_stp_state=0,
|
|
state=constants.LINK_UP)
|
|
|
|
|
|
@ovn_bgp_agent.privileged.default.entrypoint
|
|
def ensure_vxlan(vxlan_name, vni, local_ip, dstport):
|
|
try:
|
|
set_device_state(vxlan_name, constants.LINK_UP)
|
|
except agent_exc.NetworkInterfaceNotFound:
|
|
# FIXME: Perhaps we need to set neigh_suppress on
|
|
create_interface(vxlan_name, 'vxlan',
|
|
vxlan_id=vni,
|
|
vxlan_port=dstport,
|
|
vxlan_local=local_ip,
|
|
vxlan_learning=False,
|
|
state=constants.LINK_UP)
|
|
|
|
|
|
@ovn_bgp_agent.privileged.default.entrypoint
|
|
def ensure_veth(veth_name, veth_peer):
|
|
try:
|
|
set_device_state(veth_name, constants.LINK_UP)
|
|
except agent_exc.NetworkInterfaceNotFound:
|
|
create_interface(veth_name, 'veth', peer=veth_peer,
|
|
state=constants.LINK_UP)
|
|
set_device_state(veth_peer, constants.LINK_UP)
|
|
|
|
|
|
@ovn_bgp_agent.privileged.default.entrypoint
|
|
def ensure_dummy_device(device):
|
|
try:
|
|
set_device_state(device, constants.LINK_UP)
|
|
except agent_exc.NetworkInterfaceNotFound:
|
|
create_interface(device, 'dummy', state=constants.LINK_UP)
|
|
|
|
|
|
@ovn_bgp_agent.privileged.default.entrypoint
|
|
def ensure_vlan_device_for_network(bridge, vlan_tag):
|
|
vlan_device_name = '{}.{}'.format(bridge, vlan_tag)
|
|
try:
|
|
set_device_state(vlan_device_name, constants.LINK_UP)
|
|
except agent_exc.NetworkInterfaceNotFound:
|
|
create_interface(vlan_device_name, 'vlan',
|
|
physical_interface=bridge,
|
|
vlan_id=vlan_tag,
|
|
state=constants.LINK_UP)
|
|
|
|
|
|
@tenacity.retry(
|
|
retry=tenacity.retry_if_exception_type(
|
|
netlink_exceptions.NetlinkDumpInterrupted),
|
|
wait=tenacity.wait_exponential(multiplier=0.02, max=1),
|
|
stop=tenacity.stop_after_delay(8),
|
|
reraise=True)
|
|
@ovn_bgp_agent.privileged.default.entrypoint
|
|
def set_master_for_device(device, master):
|
|
try:
|
|
with pyroute2.IPRoute() as ipr:
|
|
dev_index = ipr.link_lookup(ifname=device)[0]
|
|
master_index = ipr.link_lookup(ifname=master)[0]
|
|
# Check if already associated to the master,
|
|
# and associate it if not
|
|
iface = ipr.link('get', index=dev_index)[0]
|
|
if iface.get_attr('IFLA_MASTER') != master_index:
|
|
ipr.link('set', index=dev_index, master=master_index)
|
|
except IndexError:
|
|
LOG.debug("No need to set %s on VRF %s, as one of them is deleted",
|
|
device, master)
|
|
|
|
|
|
@ovn_bgp_agent.privileged.default.entrypoint
|
|
def delete_device(device):
|
|
try:
|
|
delete_interface(device)
|
|
except agent_exc.NetworkInterfaceNotFound:
|
|
LOG.debug("Interfaces %s already deleted.", device)
|
|
|
|
|
|
@ovn_bgp_agent.privileged.default.entrypoint
|
|
def route_create(route):
|
|
scope = route.pop('scope', 'link')
|
|
route['scope'] = get_scope_name(scope)
|
|
if 'family' not in route:
|
|
route['family'] = socket.AF_INET
|
|
_run_iproute_route('replace', **route)
|
|
|
|
|
|
@ovn_bgp_agent.privileged.default.entrypoint
|
|
def route_delete(route):
|
|
scope = route.pop('scope', 'link')
|
|
route['scope'] = get_scope_name(scope)
|
|
if 'family' not in route:
|
|
route['family'] = socket.AF_INET
|
|
_run_iproute_route('del', **route)
|
|
|
|
|
|
@ovn_bgp_agent.privileged.default.entrypoint
|
|
def set_kernel_flag(flag, value):
|
|
command = ["sysctl", "-w", "{}={}".format(flag, value)]
|
|
try:
|
|
return processutils.execute(*command)
|
|
except Exception as e:
|
|
LOG.error("Unable to execute %s. Exception: %s", command, e)
|
|
raise
|
|
|
|
|
|
@ovn_bgp_agent.privileged.default.entrypoint
|
|
def delete_exposed_ips(ips, nic):
|
|
for ip_address in ips:
|
|
delete_ip_address(ip_address, nic)
|
|
|
|
|
|
@ovn_bgp_agent.privileged.default.entrypoint
|
|
def rule_create(rule):
|
|
_run_iproute_rule('add', **rule)
|
|
|
|
|
|
@ovn_bgp_agent.privileged.default.entrypoint
|
|
def rule_delete(rule):
|
|
_run_iproute_rule('del', **rule)
|
|
|
|
|
|
@ovn_bgp_agent.privileged.default.entrypoint
|
|
def delete_ip_rules(ip_rules):
|
|
for rule_ip, rule_info in ip_rules.items():
|
|
rule = {'dst': rule_ip.split("/")[0],
|
|
'dst_len': int(rule_ip.split("/")[1]),
|
|
'table': int(rule_info['table']),
|
|
'family': rule_info['family']}
|
|
_run_iproute_rule('del', **rule)
|
|
|
|
|
|
@ovn_bgp_agent.privileged.default.entrypoint
|
|
def add_ndp_proxy(ip, dev, vlan=None):
|
|
# FIXME(ltomasbo): This should use pyroute instead but I didn't find
|
|
# out how
|
|
net_ip = str(ipaddress.IPv6Network(ip, strict=False).network_address)
|
|
dev_name = dev
|
|
if vlan:
|
|
dev_name = "{}.{}".format(dev, vlan)
|
|
command = ["ip", "-6", "nei", "add", "proxy", net_ip, "dev", dev_name]
|
|
try:
|
|
return processutils.execute(*command)
|
|
except Exception as e:
|
|
LOG.error("Unable to execute %s. Exception: %s", command, e)
|
|
raise
|
|
|
|
|
|
@ovn_bgp_agent.privileged.default.entrypoint
|
|
def del_ndp_proxy(ip, dev, vlan=None):
|
|
# FIXME(ltomasbo): This should use pyroute instead but I didn't find
|
|
# out how
|
|
net_ip = str(ipaddress.IPv6Network(ip, strict=False).network_address)
|
|
dev_name = dev
|
|
if vlan:
|
|
dev_name = "{}.{}".format(dev, vlan)
|
|
command = ["ip", "-6", "nei", "del", "proxy", net_ip, "dev", dev_name]
|
|
env = dict(os.environ)
|
|
env['LC_ALL'] = 'C'
|
|
try:
|
|
return processutils.execute(*command, env_variables=env)
|
|
except Exception as e:
|
|
if "No such file or directory" in e.stderr:
|
|
# Already deleted
|
|
return
|
|
LOG.error("Unable to execute %s. Exception: %s", command, e)
|
|
raise
|
|
|
|
|
|
@ovn_bgp_agent.privileged.default.entrypoint
|
|
def add_ip_to_dev(ip, nic):
|
|
add_ip_address(ip, nic)
|
|
|
|
|
|
@ovn_bgp_agent.privileged.default.entrypoint
|
|
def del_ip_from_dev(ip, nic):
|
|
delete_ip_address(ip, nic)
|
|
|
|
|
|
@ovn_bgp_agent.privileged.default.entrypoint
|
|
def add_ip_nei(ip, lladdr, dev):
|
|
ip_version = l_net.get_ip_version(ip)
|
|
family = _IP_VERSION_FAMILY_MAP[ip_version]
|
|
_run_iproute_neigh('replace',
|
|
dev,
|
|
dst=ip,
|
|
lladdr=lladdr,
|
|
family=family,
|
|
state=ndmsg.states['permanent'])
|
|
|
|
|
|
@ovn_bgp_agent.privileged.default.entrypoint
|
|
def del_ip_nei(ip, lladdr, dev):
|
|
ip_version = l_net.get_ip_version(ip)
|
|
family = _IP_VERSION_FAMILY_MAP[ip_version]
|
|
_run_iproute_neigh('del',
|
|
dev,
|
|
dst=ip.split("/")[0],
|
|
lladdr=lladdr,
|
|
family=family,
|
|
state=ndmsg.states['permanent'])
|
|
|
|
|
|
@tenacity.retry(
|
|
retry=tenacity.retry_if_exception_type(
|
|
netlink_exceptions.NetlinkDumpInterrupted),
|
|
wait=tenacity.wait_exponential(multiplier=0.02, max=1),
|
|
stop=tenacity.stop_after_delay(8),
|
|
reraise=True)
|
|
def get_neigh_entries(device, ip_version, **kwargs):
|
|
"""Dump all neighbour entries.
|
|
|
|
:param ip_version: IP version of entries to show (4 or 6)
|
|
:param device: Device name to use in dumping entries
|
|
:param kwargs: Callers add any filters they use as kwargs
|
|
:return: a list of dictionaries, each representing a neighbour.
|
|
The dictionary format is: {'dst': ip_address,
|
|
'lladdr': mac_address,
|
|
'device': device_name}
|
|
"""
|
|
family = _IP_VERSION_FAMILY_MAP[ip_version]
|
|
dump = _run_iproute_neigh('dump',
|
|
device,
|
|
family=family,
|
|
**kwargs)
|
|
entries = []
|
|
for entry in dump:
|
|
attrs = dict(entry['attrs'])
|
|
entries.append({'dst': attrs['NDA_DST'],
|
|
'lladdr': attrs.get('NDA_LLADDR'),
|
|
'device': device,
|
|
'state': NUD_STATES[entry['state']]})
|
|
return entries
|
|
|
|
|
|
def add_unreachable_route(vrf_name):
|
|
# NOTE(ltomasbo): This method is to set the default route for the table
|
|
# (and hence default route for the VRF)
|
|
# ip route add table 10 unreachable default metric 4278198272
|
|
# Find vrf table.
|
|
device = get_link_device(vrf_name)
|
|
ifla_linkinfo = get_attr(device, 'IFLA_LINKINFO')
|
|
ifla_data = get_attr(ifla_linkinfo, 'IFLA_INFO_DATA')
|
|
vrf_table = get_attr(ifla_data, 'IFLA_VRF_TABLE')
|
|
for ip_version in (socket.AF_INET, socket.AF_INET6):
|
|
kwargs = {'dst': 'default',
|
|
'family': ip_version,
|
|
'table': vrf_table,
|
|
'type': 'unreachable',
|
|
'scope': None,
|
|
'proto': 'boot',
|
|
'priority': 4278198272}
|
|
route_create(kwargs)
|
|
|
|
|
|
@ovn_bgp_agent.privileged.default.entrypoint
|
|
def create_routing_table_for_bridge(table_number, bridge):
|
|
with open('/etc/iproute2/rt_tables', 'a') as rt_tables:
|
|
rt_tables.write('{} {}\n'.format(table_number, bridge))
|
|
|
|
|
|
def _translate_ip_device_exception(e, device):
|
|
if e.code == errno.ENODEV:
|
|
raise agent_exc.NetworkInterfaceNotFound(device=device)
|
|
if e.code == errno.EOPNOTSUPP:
|
|
raise agent_exc.InterfaceOperationNotSupported(device=device)
|
|
if e.code == errno.EINVAL:
|
|
raise agent_exc.InvalidArgument(device=device)
|
|
if e.code == errno.EEXIST:
|
|
raise agent_exc.InterfaceAlreadyExists(device=device)
|
|
raise e
|
|
|
|
|
|
def _translate_ip_addr_exception(e, ip, device):
|
|
if e.code == errno.EEXIST:
|
|
raise agent_exc.IpAddressAlreadyExists(ip=ip, device=device)
|
|
if e.code == errno.EADDRNOTAVAIL:
|
|
LOG.debug('No need to delete IP address %s on dev %s as it does '
|
|
'not exist', ip, device)
|
|
return
|
|
raise e
|
|
|
|
|
|
def _translate_ip_route_exception(e, kwargs):
|
|
if e.code == errno.EEXIST: # Already exists
|
|
LOG.debug("Route %s already exists.", kwargs)
|
|
return
|
|
if e.code == errno.ENOENT or e.code == errno.ESRCH: # Not found
|
|
LOG.debug("Route already deleted: %s", kwargs)
|
|
return
|
|
raise e
|
|
|
|
|
|
def _translate_ip_rule_exception(e, kwargs):
|
|
if e.code == errno.EEXIST: # Already exists
|
|
LOG.debug("Rule %s already exists.", kwargs)
|
|
return
|
|
if e.code == errno.ENOENT: # Not found
|
|
LOG.debug("Rule already deleted: %s", kwargs)
|
|
return
|
|
raise e
|
|
|
|
|
|
def get_attr(pyroute2_obj, attr_name):
|
|
"""Get an attribute in a pyroute object
|
|
|
|
pyroute2 object attributes are stored under a key called 'attrs'. This key
|
|
contains a tuple of tuples. E.g.:
|
|
pyroute2_obj = {'attrs': (('TCA_KIND': 'htb'),
|
|
('TCA_OPTIONS': {...}))}
|
|
|
|
:param pyroute2_obj: (dict) pyroute2 object
|
|
:param attr_name: (string) first value of the tuple we are looking for
|
|
:return: (object) second value of the tuple, None if the tuple doesn't
|
|
exist
|
|
"""
|
|
rule_attrs = pyroute2_obj.get('attrs', [])
|
|
for attr in (attr for attr in rule_attrs if attr[0] == attr_name):
|
|
return attr[1]
|
|
|
|
|
|
def make_serializable(value):
|
|
"""Make a pyroute2 object serializable
|
|
|
|
This function converts 'netlink.nla_slot' object (key, value) in a list
|
|
of two elements.
|
|
"""
|
|
def _ensure_string(value):
|
|
return value.decode() if isinstance(value, bytes) else value
|
|
|
|
if isinstance(value, list):
|
|
return [make_serializable(item) for item in value]
|
|
elif isinstance(value, pyroute_netlink.nla_slot):
|
|
return [_ensure_string(value[0]), make_serializable(value[1])]
|
|
elif isinstance(value, pyroute_netlink.nla_base):
|
|
return make_serializable(value.dump())
|
|
elif isinstance(value, dict):
|
|
return {_ensure_string(key): make_serializable(data)
|
|
for key, data in value.items()}
|
|
elif isinstance(value, tuple):
|
|
return tuple(make_serializable(item) for item in value)
|
|
return _ensure_string(value)
|
|
|
|
|
|
def _get_link_id(ifname, raise_exception=True):
|
|
with iproute.IPRoute() as ip:
|
|
link_id = ip.link_lookup(ifname=ifname)
|
|
if not link_id or len(link_id) < 1:
|
|
if raise_exception:
|
|
raise agent_exc.NetworkInterfaceNotFound(device=ifname)
|
|
LOG.debug('Interface %(dev)s not found', {'dev': ifname})
|
|
return
|
|
return link_id[0]
|
|
|
|
|
|
@ovn_bgp_agent.privileged.default.entrypoint
|
|
def get_link_id(device):
|
|
return _get_link_id(device, raise_exception=False)
|
|
|
|
|
|
def get_link_state(device_name):
|
|
device = get_link_device(device_name)
|
|
return device['state'] if device else None
|
|
|
|
|
|
def get_link_device(device_name):
|
|
for device in get_link_devices():
|
|
if get_attr(device, 'IFLA_IFNAME') == device_name:
|
|
return device
|
|
|
|
|
|
@ovn_bgp_agent.privileged.default.entrypoint
|
|
def get_bridge_vlans(device_name):
|
|
index = _get_link_id(device_name, raise_exception=False)
|
|
if not index:
|
|
LOG.debug("OVS Bridge %s deleted, no need to get information about "
|
|
"associated vlan devices", device_name)
|
|
|
|
vlan_devices = get_link_devices(link=index)
|
|
vlans = []
|
|
for vlan_device in vlan_devices:
|
|
ifla_linkinfo = get_attr(vlan_device, 'IFLA_LINKINFO')
|
|
if ifla_linkinfo:
|
|
ifla_data = get_attr(ifla_linkinfo, 'IFLA_INFO_DATA')
|
|
if ifla_data:
|
|
vlans.append(get_attr(ifla_data, 'IFLA_VLAN_ID'))
|
|
return vlans
|
|
|
|
|
|
@tenacity.retry(
|
|
retry=tenacity.retry_if_exception_type(
|
|
netlink_exceptions.NetlinkDumpInterrupted),
|
|
wait=tenacity.wait_exponential(multiplier=0.02, max=1),
|
|
stop=tenacity.stop_after_delay(8),
|
|
reraise=True)
|
|
@ovn_bgp_agent.privileged.default.entrypoint
|
|
def get_link_devices(**kwargs):
|
|
"""List interfaces in a namespace
|
|
|
|
:return: (list) interfaces in a namespace
|
|
"""
|
|
index = kwargs.pop('index') if 'index' in kwargs else 'all'
|
|
try:
|
|
with iproute.IPRoute() as ip:
|
|
return make_serializable(ip.get_links(index, **kwargs))
|
|
except OSError:
|
|
raise
|
|
|
|
|
|
def _run_iproute_link(command, ifname, **kwargs):
|
|
try:
|
|
with iproute.IPRoute() as ip:
|
|
idx = _get_link_id(ifname)
|
|
return ip.link(command, index=idx, **kwargs)
|
|
except netlink_exceptions.NetlinkError as e:
|
|
_translate_ip_device_exception(e, ifname)
|
|
|
|
|
|
def _run_iproute_addr(command, device, **kwargs):
|
|
try:
|
|
with iproute.IPRoute() as ip:
|
|
idx = _get_link_id(device)
|
|
return ip.addr(command, index=idx, **kwargs)
|
|
except netlink_exceptions.NetlinkError as e:
|
|
_translate_ip_addr_exception(e, ip=kwargs['address'], device=device)
|
|
|
|
|
|
def _run_iproute_route(command, **kwargs):
|
|
try:
|
|
with iproute.IPRoute() as ip:
|
|
ip.route(command, **kwargs)
|
|
except netlink_exceptions.NetlinkError as e:
|
|
_translate_ip_route_exception(e, kwargs)
|
|
|
|
|
|
def _run_iproute_rule(command, **kwargs):
|
|
try:
|
|
with iproute.IPRoute() as ip:
|
|
ip.rule(command, **kwargs)
|
|
except netlink_exceptions.NetlinkError as e:
|
|
_translate_ip_rule_exception(e, kwargs)
|
|
|
|
|
|
def _run_iproute_neigh(command, device, **kwargs):
|
|
try:
|
|
with iproute.IPRoute() as ip:
|
|
idx = _get_link_id(device)
|
|
return ip.neigh(command, ifindex=idx, **kwargs)
|
|
except agent_exc.NetworkInterfaceNotFound:
|
|
LOG.debug("No need to %s nei for dev %s as it does not exists",
|
|
command, device)
|
|
|
|
|
|
@ovn_bgp_agent.privileged.default.entrypoint
|
|
def create_interface(ifname, kind, **kwargs):
|
|
ifname = ifname[:15]
|
|
try:
|
|
with iproute.IPRoute() as ip:
|
|
physical_interface = kwargs.pop('physical_interface', None)
|
|
if physical_interface:
|
|
link_key = 'vxlan_link' if kind == 'vxlan' else 'link'
|
|
kwargs[link_key] = _get_link_id(physical_interface)
|
|
ip.link("add", ifname=ifname, kind=kind, **kwargs)
|
|
except netlink_exceptions.NetlinkError as e:
|
|
_translate_ip_device_exception(e, ifname)
|
|
|
|
|
|
@ovn_bgp_agent.privileged.default.entrypoint
|
|
def delete_interface(ifname, **kwargs):
|
|
_run_iproute_link('del', ifname, **kwargs)
|
|
|
|
|
|
@ovn_bgp_agent.privileged.default.entrypoint
|
|
def set_link_attribute(ifname, **kwargs):
|
|
_run_iproute_link("set", ifname, **kwargs)
|
|
|
|
|
|
@ovn_bgp_agent.privileged.default.entrypoint
|
|
def add_ip_address(ip_address, ifname):
|
|
net = netaddr.IPNetwork(ip_address)
|
|
ip_version = l_net.get_ip_version(ip_address)
|
|
address = str(net.ip)
|
|
prefixlen = 32 if ip_version == 4 else 128
|
|
family = _IP_VERSION_FAMILY_MAP[ip_version]
|
|
_run_iproute_addr('add',
|
|
ifname,
|
|
address=address,
|
|
mask=prefixlen,
|
|
family=family)
|
|
|
|
|
|
@ovn_bgp_agent.privileged.default.entrypoint
|
|
def delete_ip_address(ip_address, ifname):
|
|
net = netaddr.IPNetwork(ip_address)
|
|
ip_version = l_net.get_ip_version(ip_address)
|
|
address = str(net.ip)
|
|
prefixlen = 32 if ip_version == 4 else 128
|
|
family = _IP_VERSION_FAMILY_MAP[ip_version]
|
|
_run_iproute_addr("delete",
|
|
ifname,
|
|
address=address,
|
|
mask=prefixlen,
|
|
family=family)
|
|
|
|
|
|
@ovn_bgp_agent.privileged.default.entrypoint
|
|
def get_ip_addresses(**kwargs):
|
|
"""List of IP addresses in a namespace
|
|
|
|
:return: (tuple) IP addresses in a namespace
|
|
"""
|
|
with iproute.IPRoute() as ip:
|
|
return make_serializable(ip.get_addr(**kwargs))
|
|
|
|
|
|
@tenacity.retry(
|
|
retry=tenacity.retry_if_exception_type(
|
|
netlink_exceptions.NetlinkDumpInterrupted),
|
|
wait=tenacity.wait_exponential(multiplier=0.02, max=1),
|
|
stop=tenacity.stop_after_delay(8),
|
|
reraise=True)
|
|
@ovn_bgp_agent.privileged.default.entrypoint
|
|
def list_ip_routes(ip_version, device=None, table=None, **kwargs):
|
|
"""List IP routes"""
|
|
kwargs['family'] = _IP_VERSION_FAMILY_MAP[ip_version]
|
|
if device:
|
|
kwargs['oif'] = _get_link_id(device)
|
|
if table:
|
|
kwargs['table'] = int(table)
|
|
with iproute.IPRoute() as ip:
|
|
return make_serializable(ip.route('show', **kwargs))
|
|
|
|
|
|
@ovn_bgp_agent.privileged.default.entrypoint
|
|
def list_ip_rules(ip_version, **kwargs):
|
|
"""List all IP rules"""
|
|
with iproute.IPRoute() as ip:
|
|
return make_serializable(ip.get_rules(
|
|
family=_IP_VERSION_FAMILY_MAP[ip_version], **kwargs))
|