From baaaaa67b556d586bcbfe6a17b46a99cd3d7198f Mon Sep 17 00:00:00 2001 From: Pim van Pelt Date: Fri, 22 Apr 2022 13:05:55 +0000 Subject: [PATCH] Initial formatting run with Black. Integration tests and unit tests pass before and after this change. --- config/__init__.py | 53 +- config/address.py | 74 +-- config/bondethernet.py | 148 ++--- config/bridgedomain.py | 124 +++-- config/interface.py | 471 +++++++++------- config/lcp.py | 40 +- config/loopback.py | 59 +- config/mac.py | 14 +- config/tap.py | 73 ++- config/test_bondethernet.py | 23 +- config/test_bridgedomain.py | 49 +- config/test_interface.py | 112 ++-- config/test_lcp.py | 31 +- config/test_loopback.py | 5 +- config/test_mac.py | 1 + config/test_tap.py | 3 +- config/test_vxlan_tunnel.py | 3 +- config/vxlan_tunnel.py | 43 +- tests.py | 95 +++- vpp/dumper.py | 240 +++++---- vpp/reconciler.py | 1019 ++++++++++++++++++++--------------- vpp/vppapi.py | 255 +++++---- 22 files changed, 1757 insertions(+), 1178 deletions(-) diff --git a/config/__init__.py b/config/__init__.py index 797030a..9ad5725 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -23,6 +23,7 @@ import logging import ipaddress import os.path import sys + try: import yamale except ImportError: @@ -37,13 +38,15 @@ from config.tap import validate_taps from yamale.validators import DefaultValidators, Validator + class IPInterfaceWithPrefixLength(Validator): - """ Custom IPAddress config - takes IP/prefixlen as input: - 192.0.2.1/29 or 2001:db8::1/64 are correct. The PrefixLength - is required, and must be a number (0-32 for IPv4 and 0-128 for - IPv6). + """Custom IPAddress config - takes IP/prefixlen as input: + 192.0.2.1/29 or 2001:db8::1/64 are correct. The PrefixLength + is required, and must be a number (0-32 for IPv4 and 0-128 for + IPv6). """ - tag = 'ip_interface' + + tag = "ip_interface" def _is_valid(self, value): try: @@ -52,9 +55,9 @@ class IPInterfaceWithPrefixLength(Validator): return False if not isinstance(value, str): return False - if not '/' in value: + if not "/" in value: return False - e = value.split('/') + e = value.split("/") if not len(e) == 2: return False if not e[1].isnumeric(): @@ -62,20 +65,20 @@ class IPInterfaceWithPrefixLength(Validator): return True - class Validator(object): def __init__(self, schema): - self.logger = logging.getLogger('vppcfg.config') + self.logger = logging.getLogger("vppcfg.config") self.logger.addHandler(logging.NullHandler()) self.schema = schema self.validators = [ - validate_bondethernets, - validate_interfaces, - validate_loopbacks, - validate_bridgedomains, - validate_vxlan_tunnels, - validate_taps ] + validate_bondethernets, + validate_interfaces, + validate_loopbacks, + validate_bridgedomains, + validate_vxlan_tunnels, + validate_taps, + ] def validate(self, yaml): ret_rv = True @@ -109,7 +112,7 @@ class Validator(object): ret_rv = False for result in e.results: for error in result.errors: - ret_msgs.extend([f'yamale: {error}']) + ret_msgs.extend([f"yamale: {error}"]) return ret_rv, ret_msgs self.logger.debug("Validating Semantics...") @@ -126,7 +129,7 @@ class Validator(object): return ret_rv, ret_msgs def valid_config(self, yaml): - """ Validate the given YAML configuration in 'yaml' against syntax + """Validate the given YAML configuration in 'yaml' against syntax validation given in the yamale 'schema', and all semantic configs. Returns True if the configuration is valid, False otherwise. @@ -142,13 +145,13 @@ class Validator(object): return True def add_validator(self, func): - """ Add a validator function, which strictly takes the prototype - rv, msgs = func(yaml) - returning a Boolean success value in rv and a List of strings - in msgs. The function will be passed the configuration YAML and - gets to opine if it's valid or not. + """Add a validator function, which strictly takes the prototype + rv, msgs = func(yaml) + returning a Boolean success value in rv and a List of strings + in msgs. The function will be passed the configuration YAML and + gets to opine if it's valid or not. - Note: will only be called iff Yamale syntax-check succeeded, - and it will be called after all built-in validators. - """ + Note: will only be called iff Yamale syntax-check succeeded, + and it will be called after all built-in validators. + """ self.validators.append(func) diff --git a/config/address.py b/config/address.py index 27223e6..73cea84 100644 --- a/config/address.py +++ b/config/address.py @@ -15,70 +15,72 @@ import logging import config.interface as interface import ipaddress + def get_all_addresses_except_ifname(yaml, except_ifname): - """ Return a list of all ipaddress.ip_interface() instances in the entire config, - except for those that belong to 'ifname'. + """Return a list of all ipaddress.ip_interface() instances in the entire config, + except for those that belong to 'ifname'. """ ret = [] - if 'interfaces' in yaml: - for ifname, iface in yaml['interfaces'].items(): + if "interfaces" in yaml: + for ifname, iface in yaml["interfaces"].items(): if ifname == except_ifname: continue - if 'addresses' in iface: - for a in iface['addresses']: + if "addresses" in iface: + for a in iface["addresses"]: ret.append(ipaddress.ip_interface(a)) - if 'sub-interfaces' in iface: - for subid, sub_iface in iface['sub-interfaces'].items(): + if "sub-interfaces" in iface: + for subid, sub_iface in iface["sub-interfaces"].items(): sub_ifname = f"{ifname}.{int(subid)}" if sub_ifname == except_ifname: continue - if 'addresses' in sub_iface: - for a in sub_iface['addresses']: + if "addresses" in sub_iface: + for a in sub_iface["addresses"]: ret.append(ipaddress.ip_interface(a)) - if 'loopbacks' in yaml: - for ifname, iface in yaml['loopbacks'].items(): + if "loopbacks" in yaml: + for ifname, iface in yaml["loopbacks"].items(): if ifname == except_ifname: continue - if 'addresses' in iface: - for a in iface['addresses']: + if "addresses" in iface: + for a in iface["addresses"]: ret.append(ipaddress.ip_interface(a)) - if 'bridgedomains' in yaml: - for ifname, iface in yaml['bridgedomains'].items(): + if "bridgedomains" in yaml: + for ifname, iface in yaml["bridgedomains"].items(): if ifname == except_ifname: continue - if 'addresses' in iface: - for a in iface['addresses']: + if "addresses" in iface: + for a in iface["addresses"]: ret.append(ipaddress.ip_interface(a)) return ret + def is_allowed(yaml, ifname, iface_addresses, ip_interface): - """ Returns True if there is at most one occurence of the ip_interface (an IPv4/IPv6 prefix+len) - in the entire config. That said, we need the 'iface_addresses' because VPP is a bit fickle in - this regard. + """Returns True if there is at most one occurence of the ip_interface (an IPv4/IPv6 prefix+len) + in the entire config. That said, we need the 'iface_addresses' because VPP is a bit fickle in + this regard. - IP addresses from the same prefix/len can be added to a given interface (ie 192.0.2.1/24 and - 192.0.2.2/24), but other than that, any prefix can not occur as a more-specific or less-specific - of any other interface. + IP addresses from the same prefix/len can be added to a given interface (ie 192.0.2.1/24 and + 192.0.2.2/24), but other than that, any prefix can not occur as a more-specific or less-specific + of any other interface. - So, we will allow: - - any ip_interface that is of equal network/len of existing one(s) _on the same interface_ + So, we will allow: + - any ip_interface that is of equal network/len of existing one(s) _on the same interface_ - And, we will reject - - any ip_interface that is a more specific of any existing one - - any ip_interface that is a less specific of any existing one + And, we will reject + - any ip_interface that is a more specific of any existing one + - any ip_interface that is a less specific of any existing one - Examples: - vpp# set interface ip address loop0 192.0.2.1/24 - vpp# set interface ip address loop0 192.0.2.2/24 - vpp# set interface ip address loop0 192.0.2.1/29 - set interface ip address: failed to add 192.0.2.1/29 on loop0 which conflicts with 192.0.2.1/24 for interface loop0 - vpp# set interface ip address loop0 192.0.2.3/23 - set interface ip address: failed to add 192.0.2.3/23 on loop0 which conflicts with 192.0.2.1/24 for interface loop0 + Examples: + vpp# set interface ip address loop0 192.0.2.1/24 + vpp# set interface ip address loop0 192.0.2.2/24 + vpp# set interface ip address loop0 192.0.2.1/29 + set interface ip address: failed to add 192.0.2.1/29 on loop0 which conflicts with 192.0.2.1/24 for interface loop0 + vpp# set interface ip address loop0 192.0.2.3/23 + set interface ip address: failed to add 192.0.2.3/23 on loop0 which conflicts with 192.0.2.1/24 for interface loop0 """ all_other_addresses = get_all_addresses_except_ifname(yaml, ifname) diff --git a/config/bondethernet.py b/config/bondethernet.py index efcf520..49829ce 100644 --- a/config/bondethernet.py +++ b/config/bondethernet.py @@ -15,66 +15,67 @@ import logging import config.interface as interface import config.mac as mac + def get_bondethernets(yaml): - """ Return a list of all bondethernets. """ + """Return a list of all bondethernets.""" ret = [] - if 'bondethernets' in yaml: - for ifname, iface in yaml['bondethernets'].items(): + if "bondethernets" in yaml: + for ifname, iface in yaml["bondethernets"].items(): ret.append(ifname) return ret def get_by_name(yaml, ifname): - """ Return the BondEthernet by name, if it exists. Return None,None otherwise. """ + """Return the BondEthernet by name, if it exists. Return None,None otherwise.""" try: - if ifname in yaml['bondethernets']: - return ifname, yaml['bondethernets'][ifname] + if ifname in yaml["bondethernets"]: + return ifname, yaml["bondethernets"][ifname] except: pass return None, None def is_bondethernet(yaml, ifname): - """ Returns True if the interface name is an existing BondEthernet. """ + """Returns True if the interface name is an existing BondEthernet.""" ifname, iface = get_by_name(yaml, ifname) return not iface == None def is_bond_member(yaml, ifname): - """ Returns True if this interface is a member of a BondEthernet. """ - if not 'bondethernets' in yaml: + """Returns True if this interface is a member of a BondEthernet.""" + if not "bondethernets" in yaml: return False - for bond, iface in yaml['bondethernets'].items(): - if not 'interfaces' in iface: + for bond, iface in yaml["bondethernets"].items(): + if not "interfaces" in iface: continue - if ifname in iface['interfaces']: + if ifname in iface["interfaces"]: return True return False def get_mode(yaml, ifname): - """ Return the mode of the BondEthernet as a string, defaulting to 'lacp' - if no mode is given. Return None if the bond interface doesn't exist. + """Return the mode of the BondEthernet as a string, defaulting to 'lacp' + if no mode is given. Return None if the bond interface doesn't exist. - Return values: 'round-robin','active-backup','broadcast','lacp','xor' - """ + Return values: 'round-robin','active-backup','broadcast','lacp','xor' + """ ifname, iface = get_by_name(yaml, ifname) if not iface: return None - if not 'mode' in iface: - return 'lacp' - return iface['mode'] + if not "mode" in iface: + return "lacp" + return iface["mode"] def mode_to_int(mode): - """ Returns the integer representation in VPP of a given bondethernet mode, - or -1 if 'mode' is not a valid string. + """Returns the integer representation in VPP of a given bondethernet mode, + or -1 if 'mode' is not a valid string. - See src/vnet/bonding/bond.api and schema.yaml for valid pairs. """ + See src/vnet/bonding/bond.api and schema.yaml for valid pairs.""" - ret = { 'round-robin': 1, 'active-backup': 2, 'xor': 3, 'broadcast': 4, 'lacp': 5 } + ret = {"round-robin": 1, "active-backup": 2, "xor": 3, "broadcast": 4, "lacp": 5} try: return ret[mode] except: @@ -83,12 +84,12 @@ def mode_to_int(mode): def int_to_mode(mode): - """ Returns the string representation in VPP of a given bondethernet mode, - or "" if 'mode' is not a valid id. + """Returns the string representation in VPP of a given bondethernet mode, + or "" if 'mode' is not a valid id. - See src/vnet/bonding/bond.api and schema.yaml for valid pairs. """ + See src/vnet/bonding/bond.api and schema.yaml for valid pairs.""" - ret = { 1: 'round-robin', 2: 'active-backup', 3: 'xor', 4: 'broadcast', 5: 'lacp' } + ret = {1: "round-robin", 2: "active-backup", 3: "xor", 4: "broadcast", 5: "lacp"} try: return ret[mode] except: @@ -97,33 +98,40 @@ def int_to_mode(mode): def get_lb(yaml, ifname): - """ Return the loadbalance strategy of the BondEthernet as a string. Only - 'xor' and 'lacp' modes have loadbalance strategies, so return None if - those modes are not used. + """Return the loadbalance strategy of the BondEthernet as a string. Only + 'xor' and 'lacp' modes have loadbalance strategies, so return None if + those modes are not used. - Return values: 'l2', 'l23', 'l34', with 'l34' being the default if - the bond is in xor/lacp mode without a load-balance strategy set - explicitly.""" + Return values: 'l2', 'l23', 'l34', with 'l34' being the default if + the bond is in xor/lacp mode without a load-balance strategy set + explicitly.""" ifname, iface = get_by_name(yaml, ifname) if not iface: return None mode = get_mode(yaml, ifname) - if not mode in ['xor','lacp']: + if not mode in ["xor", "lacp"]: return None - if not 'load-balance' in iface: - return 'l34' - return iface['load-balance'] + if not "load-balance" in iface: + return "l34" + return iface["load-balance"] def lb_to_int(lb): - """ Returns the integer representation in VPP of a given load-balance strategy, - or -1 if 'lb' is not a valid string. + """Returns the integer representation in VPP of a given load-balance strategy, + or -1 if 'lb' is not a valid string. - See src/vnet/bonding/bond.api and schema.yaml for valid pairs, although - bond.api defined more than we use in vppcfg. """ + See src/vnet/bonding/bond.api and schema.yaml for valid pairs, although + bond.api defined more than we use in vppcfg.""" - ret = { 'l2': 0, 'l34': 1, 'l23': 2, 'round-robin': 3, 'broadcast': 4, 'active-backup': 5 } + ret = { + "l2": 0, + "l34": 1, + "l23": 2, + "round-robin": 3, + "broadcast": 4, + "active-backup": 5, + } try: return ret[lb] except: @@ -132,13 +140,20 @@ def lb_to_int(lb): def int_to_lb(lb): - """ Returns the string representation in VPP of a given load-balance strategy, - or "" if 'lb' is not a valid int. + """Returns the string representation in VPP of a given load-balance strategy, + or "" if 'lb' is not a valid int. - See src/vnet/bonding/bond.api and schema.yaml for valid pairs, although - bond.api defined more than we use in vppcfg. """ + See src/vnet/bonding/bond.api and schema.yaml for valid pairs, although + bond.api defined more than we use in vppcfg.""" - ret = { 0: 'l2', 1: 'l34', 2: 'l23', 3: 'round-robin', 4: 'broadcast', 5: 'active-backup' } + ret = { + 0: "l2", + 1: "l34", + 2: "l23", + 3: "round-robin", + 4: "broadcast", + 5: "active-backup", + } try: return ret[lb] except: @@ -149,13 +164,13 @@ def int_to_lb(lb): def validate_bondethernets(yaml): result = True msgs = [] - logger = logging.getLogger('vppcfg.config') + logger = logging.getLogger("vppcfg.config") logger.addHandler(logging.NullHandler()) - if not 'bondethernets' in yaml: + if not "bondethernets" in yaml: return result, msgs - for ifname, iface in yaml['bondethernets'].items(): + for ifname, iface in yaml["bondethernets"].items(): logger.debug(f"bondethernet {ifname}: {iface}") bond_ifname, bond_iface = interface.get_by_name(yaml, ifname) bond_mtu = 1500 @@ -166,26 +181,37 @@ def validate_bondethernets(yaml): bond_mtu = interface.get_mtu(yaml, bond_ifname) instance = int(ifname[12:]) if instance > 4294967294: - msgs.append(f"bondethernet {ifname} has instance {int(instance)} which is too large") + msgs.append( + f"bondethernet {ifname} has instance {int(instance)} which is too large" + ) result = False - if not get_mode(yaml, bond_ifname) in ['xor','lacp'] and 'load-balance' in iface: - msgs.append(f"bondethernet {ifname} can only have load-balance if in mode XOR or LACP") + if ( + not get_mode(yaml, bond_ifname) in ["xor", "lacp"] + and "load-balance" in iface + ): + msgs.append( + f"bondethernet {ifname} can only have load-balance if in mode XOR or LACP" + ) result = False - if 'mac' in iface and mac.is_multicast(iface['mac']): - msgs.append(f"bondethernet {ifname} MAC address {iface['mac']} cannot be multicast") + if "mac" in iface and mac.is_multicast(iface["mac"]): + msgs.append( + f"bondethernet {ifname} MAC address {iface['mac']} cannot be multicast" + ) result = False - if not 'interfaces' in iface: + if not "interfaces" in iface: continue - for member in iface['interfaces']: + for member in iface["interfaces"]: if (None, None) == interface.get_by_name(yaml, member): msgs.append(f"bondethernet {ifname} member {member} does not exist") result = False continue if interface.has_sub(yaml, member): - msgs.append(f"bondethernet {ifname} member {member} has sub-interface(s)") + msgs.append( + f"bondethernet {ifname} member {member} has sub-interface(s)" + ) result = False if interface.has_lcp(yaml, member): msgs.append(f"bondethernet {ifname} member {member} has an LCP") @@ -194,7 +220,9 @@ def validate_bondethernets(yaml): msgs.append(f"bondethernet {ifname} member {member} has an address") result = False member_mtu = interface.get_mtu(yaml, member) - if member_mtu != bond_mtu: - msgs.append(f"bondethernet {ifname} member {member} MTU {int(member_mtu)} does not match BondEthernet MTU {int(bond_mtu)}") + if member_mtu != bond_mtu: + msgs.append( + f"bondethernet {ifname} member {member} MTU {int(member_mtu)} does not match BondEthernet MTU {int(bond_mtu)}" + ) result = False return result, msgs diff --git a/config/bridgedomain.py b/config/bridgedomain.py index a511be5..e93d9ed 100644 --- a/config/bridgedomain.py +++ b/config/bridgedomain.py @@ -19,66 +19,67 @@ import config.address as address def get_bridgedomains(yaml): - """ Return a list of all bridgedomains. """ + """Return a list of all bridgedomains.""" ret = [] - if not 'bridgedomains' in yaml: + if not "bridgedomains" in yaml: return ret - for ifname, iface in yaml['bridgedomains'].items(): + for ifname, iface in yaml["bridgedomains"].items(): ret.append(ifname) return ret def get_by_name(yaml, ifname): - """ Return the BridgeDomain by name (bd*), if it exists. Return None,None otherwise. """ + """Return the BridgeDomain by name (bd*), if it exists. Return None,None otherwise.""" try: - if ifname in yaml['bridgedomains']: - return ifname, yaml['bridgedomains'][ifname] + if ifname in yaml["bridgedomains"]: + return ifname, yaml["bridgedomains"][ifname] except: pass return None, None def is_bridgedomain(yaml, ifname): - """ Returns True if the name (bd*) is an existing bridgedomain. """ + """Returns True if the name (bd*) is an existing bridgedomain.""" ifname, iface = get_by_name(yaml, ifname) return not iface == None def get_bridge_interfaces(yaml): - """ Returns a list of all interfaces that are bridgedomain members """ + """Returns a list of all interfaces that are bridgedomain members""" ret = [] - if not 'bridgedomains' in yaml: + if not "bridgedomains" in yaml: return ret - for ifname, iface in yaml['bridgedomains'].items(): - if 'interfaces' in iface: - ret.extend(iface['interfaces']) + for ifname, iface in yaml["bridgedomains"].items(): + if "interfaces" in iface: + ret.extend(iface["interfaces"]) return ret + def is_bridge_interface_unique(yaml, ifname): - """ Returns True if this interface is referenced in bridgedomains zero or one times """ + """Returns True if this interface is referenced in bridgedomains zero or one times""" ifs = get_bridge_interfaces(yaml) return ifs.count(ifname) < 2 def is_bridge_interface(yaml, ifname): - """ Returns True if this interface is a member of a BridgeDomain """ + """Returns True if this interface is a member of a BridgeDomain""" return ifname in get_bridge_interfaces(yaml) def bvi_unique(yaml, bviname): - """ Returns True if the BVI identified by bviname is unique among all BridgeDomains. """ - if not 'bridgedomains' in yaml: + """Returns True if the BVI identified by bviname is unique among all BridgeDomains.""" + if not "bridgedomains" in yaml: return True n = 0 - for ifname, iface in yaml['bridgedomains'].items(): - if 'bvi' in iface and iface['bvi'] == bviname: + for ifname, iface in yaml["bridgedomains"].items(): + if "bvi" in iface and iface["bvi"] == bviname: n += 1 - return n<2 + return n < 2 def get_settings(yaml, ifname): @@ -87,57 +88,61 @@ def get_settings(yaml, ifname): return None settings = { - 'learn': True, - 'unicast-flood': True, - 'unknown-unicast-flood': True, - 'unicast-forward': True, - 'arp-termination': False, - 'arp-unicast-forward': False, - 'mac-age-minutes': 0, ## 0 means disabled - } - if 'settings' in iface: - if 'learn' in iface['settings']: - settings['learn'] = iface['settings']['learn'] - if 'unicast-flood' in iface['settings']: - settings['unicast-flood'] = iface['settings']['unicast-flood'] - if 'unknown-unicast-flood' in iface['settings']: - settings['unknown-unicast-flood'] = iface['settings']['unknown-unicast-flood'] - if 'unicast-forward' in iface['settings']: - settings['unicast-forward'] = iface['settings']['unicast-forward'] - if 'arp-termination' in iface['settings']: - settings['arp-termination'] = iface['settings']['arp-termination'] - if 'arp-unicast-forward' in iface['settings']: - settings['arp-unicast-forward'] = iface['settings']['arp-unicast-forward'] - if 'mac-age-minutes' in iface['settings']: - settings['mac-age-minutes'] = int(iface['settings']['mac-age-minutes']) + "learn": True, + "unicast-flood": True, + "unknown-unicast-flood": True, + "unicast-forward": True, + "arp-termination": False, + "arp-unicast-forward": False, + "mac-age-minutes": 0, ## 0 means disabled + } + if "settings" in iface: + if "learn" in iface["settings"]: + settings["learn"] = iface["settings"]["learn"] + if "unicast-flood" in iface["settings"]: + settings["unicast-flood"] = iface["settings"]["unicast-flood"] + if "unknown-unicast-flood" in iface["settings"]: + settings["unknown-unicast-flood"] = iface["settings"][ + "unknown-unicast-flood" + ] + if "unicast-forward" in iface["settings"]: + settings["unicast-forward"] = iface["settings"]["unicast-forward"] + if "arp-termination" in iface["settings"]: + settings["arp-termination"] = iface["settings"]["arp-termination"] + if "arp-unicast-forward" in iface["settings"]: + settings["arp-unicast-forward"] = iface["settings"]["arp-unicast-forward"] + if "mac-age-minutes" in iface["settings"]: + settings["mac-age-minutes"] = int(iface["settings"]["mac-age-minutes"]) return settings def validate_bridgedomains(yaml): result = True msgs = [] - logger = logging.getLogger('vppcfg.config') + logger = logging.getLogger("vppcfg.config") logger.addHandler(logging.NullHandler()) - if not 'bridgedomains' in yaml: + if not "bridgedomains" in yaml: return result, msgs - for ifname, iface in yaml['bridgedomains'].items(): + for ifname, iface in yaml["bridgedomains"].items(): logger.debug(f"bridgedomain {iface}") bd_mtu = 1500 - if 'mtu' in iface: - bd_mtu = iface['mtu'] + if "mtu" in iface: + bd_mtu = iface["mtu"] instance = int(ifname[2:]) if instance == 0: msgs.append(f"bridgedomain {ifname} is reserved") result = False elif instance > 16777215: - msgs.append(f"bridgedomain {ifname} has instance {int(instance)} which is too large") + msgs.append( + f"bridgedomain {ifname} has instance {int(instance)} which is too large" + ) result = False - if 'bvi' in iface: - bviname = iface['bvi'] - bvi_ifname, bvi_iface = loopback.get_by_name(yaml,iface['bvi']) + if "bvi" in iface: + bviname = iface["bvi"] + bvi_ifname, bvi_iface = loopback.get_by_name(yaml, iface["bvi"]) if not bvi_unique(yaml, bvi_ifname): msgs.append(f"bridgedomain {ifname} BVI {bvi_ifname} is not unique") result = False @@ -147,14 +152,16 @@ def validate_bridgedomains(yaml): continue bvi_mtu = 1500 - if 'mtu' in bvi_iface: - bvi_mtu = bvi_iface['mtu'] + if "mtu" in bvi_iface: + bvi_mtu = bvi_iface["mtu"] if bvi_mtu != bd_mtu: - msgs.append(f"bridgedomain {ifname} BVI {bvi_ifname} has MTU {int(bvi_mtu)}, while bridge has {int(bd_mtu)}") + msgs.append( + f"bridgedomain {ifname} BVI {bvi_ifname} has MTU {int(bvi_mtu)}, while bridge has {int(bd_mtu)}" + ) result = False - if 'interfaces' in iface: - for member in iface['interfaces']: + if "interfaces" in iface: + for member in iface["interfaces"]: if (None, None) == interface.get_by_name(yaml, member): msgs.append(f"bridgedomain {ifname} member {member} does not exist") result = False @@ -171,8 +178,9 @@ def validate_bridgedomains(yaml): result = False member_mtu = interface.get_mtu(yaml, member) if member_mtu != bd_mtu: - msgs.append(f"bridgedomain {ifname} member {member} has MTU {int(member_mtu)}, while bridge has {int(bd_mtu)}") + msgs.append( + f"bridgedomain {ifname} member {member} has MTU {int(member_mtu)}, while bridge has {int(bd_mtu)}" + ) result = False - return result, msgs diff --git a/config/interface.py b/config/interface.py index e9ea6c6..f8cb9ce 100644 --- a/config/interface.py +++ b/config/interface.py @@ -21,77 +21,78 @@ import config.address as address import config.mac as mac import config.tap as tap + def get_qinx_parent_by_name(yaml, ifname): - """ Returns the sub-interface which matches a QinAD or QinQ outer tag, or None,None - if that sub-interface doesn't exist. """ + """Returns the sub-interface which matches a QinAD or QinQ outer tag, or None,None + if that sub-interface doesn't exist.""" if not is_qinx(yaml, ifname): return None, None qinx_ifname, qinx_iface = get_by_name(yaml, ifname) if not qinx_iface: - return None,None + return None, None qinx_encap = get_encapsulation(yaml, ifname) if not qinx_encap: - return None,None + return None, None parent_ifname, parent_iface = get_parent_by_name(yaml, ifname) if not parent_iface: - return None,None + return None, None - for subid, sub_iface in parent_iface['sub-interfaces'].items(): + for subid, sub_iface in parent_iface["sub-interfaces"].items(): sub_ifname = f"{parent_ifname}.{int(subid)}" sub_encap = get_encapsulation(yaml, sub_ifname) if not sub_encap: continue - if qinx_encap['dot1q'] > 0 and sub_encap['dot1q'] == qinx_encap['dot1q']: + if qinx_encap["dot1q"] > 0 and sub_encap["dot1q"] == qinx_encap["dot1q"]: return sub_ifname, sub_iface - if qinx_encap['dot1ad'] > 0 and sub_encap['dot1ad'] == qinx_encap['dot1ad']: + if qinx_encap["dot1ad"] > 0 and sub_encap["dot1ad"] == qinx_encap["dot1ad"]: return sub_ifname, sub_iface - return None,None + return None, None def get_parent_by_name(yaml, ifname): - """ Returns the sub-interface's parent, or None,None if the sub-int doesn't exist. """ + """Returns the sub-interface's parent, or None,None if the sub-int doesn't exist.""" try: - parent_ifname, subid = ifname.split('.') + parent_ifname, subid = ifname.split(".") subid = int(subid) - iface = yaml['interfaces'][parent_ifname] + iface = yaml["interfaces"][parent_ifname] return parent_ifname, iface except: pass - return None,None + return None, None def get_by_lcp_name(yaml, lcpname): - """ Returns the interface or sub-interface by a given lcp name, or None,None if it does not exist """ - if not 'interfaces' in yaml: - return None,None - for ifname, iface in yaml['interfaces'].items(): - if 'lcp' in iface and iface['lcp'] == lcpname: + """Returns the interface or sub-interface by a given lcp name, or None,None if it does not exist""" + if not "interfaces" in yaml: + return None, None + for ifname, iface in yaml["interfaces"].items(): + if "lcp" in iface and iface["lcp"] == lcpname: return ifname, iface - if not 'sub-interfaces' in iface: + if not "sub-interfaces" in iface: continue - for subid, sub_iface in yaml['interfaces'][ifname]['sub-interfaces'].items(): + for subid, sub_iface in yaml["interfaces"][ifname]["sub-interfaces"].items(): sub_ifname = f"{ifname}.{int(subid)}" - if 'lcp' in sub_iface and sub_iface['lcp'] == lcpname: + if "lcp" in sub_iface and sub_iface["lcp"] == lcpname: return sub_ifname, sub_iface - return None,None + return None, None def get_by_name(yaml, ifname): - """ Returns the interface or sub-interface by a given name, or None,None if it does not exist """ - if '.' in ifname: + """Returns the interface or sub-interface by a given name, or None,None if it does not exist""" + if "." in ifname: try: - phy_ifname, subid = ifname.split('.') + phy_ifname, subid = ifname.split(".") subid = int(subid) - iface = yaml['interfaces'][phy_ifname]['sub-interfaces'][subid] + iface = yaml["interfaces"][phy_ifname]["sub-interfaces"][subid] return ifname, iface except: return None, None try: - iface = yaml['interfaces'][ifname] + iface = yaml["interfaces"][ifname] return ifname, iface except: pass @@ -99,121 +100,125 @@ def get_by_name(yaml, ifname): def is_sub(yaml, ifname): - """ Returns True if this interface is a sub-interface """ + """Returns True if this interface is a sub-interface""" parent_ifname, parent_iface = get_parent_by_name(yaml, ifname) return isinstance(parent_iface, dict) def has_sub(yaml, ifname): - """ Returns True if this interface has sub-interfaces """ - if not 'interfaces' in yaml: + """Returns True if this interface has sub-interfaces""" + if not "interfaces" in yaml: return False - if ifname in yaml['interfaces']: - iface = yaml['interfaces'][ifname] - if 'sub-interfaces' in iface and len(iface['sub-interfaces']) > 0: + if ifname in yaml["interfaces"]: + iface = yaml["interfaces"][ifname] + if "sub-interfaces" in iface and len(iface["sub-interfaces"]) > 0: return True return False def has_address(yaml, ifname): - """ Returns True if this interface or sub-interface has one or more addresses""" + """Returns True if this interface or sub-interface has one or more addresses""" ifname, iface = get_by_name(yaml, ifname) if not iface: return False - return 'addresses' in iface + return "addresses" in iface def get_l2xc_interfaces(yaml): - """ Returns a list of all interfaces that have an L2 CrossConnect """ + """Returns a list of all interfaces that have an L2 CrossConnect""" ret = [] - if not 'interfaces' in yaml: + if not "interfaces" in yaml: return ret - for ifname, iface in yaml['interfaces'].items(): - if 'l2xc' in iface: + for ifname, iface in yaml["interfaces"].items(): + if "l2xc" in iface: ret.append(ifname) - if 'sub-interfaces' in iface: - for subid, sub_iface in iface['sub-interfaces'].items(): + if "sub-interfaces" in iface: + for subid, sub_iface in iface["sub-interfaces"].items(): sub_ifname = f"{ifname}.{int(subid)}" - if 'l2xc' in sub_iface: + if "l2xc" in sub_iface: ret.append(sub_ifname) return ret def is_l2xc_interface(yaml, ifname): - """ Returns True if this interface has an L2 CrossConnect """ + """Returns True if this interface has an L2 CrossConnect""" return ifname in get_l2xc_interfaces(yaml) def get_l2xc_target_interfaces(yaml): - """ Returns a list of all interfaces that are the target of an L2 CrossConnect """ + """Returns a list of all interfaces that are the target of an L2 CrossConnect""" ret = [] - if 'interfaces' in yaml: - for ifname, iface in yaml['interfaces'].items(): - if 'l2xc' in iface: - ret.append(iface['l2xc']) - if 'sub-interfaces' in iface: - for subid, sub_iface in iface['sub-interfaces'].items(): - if 'l2xc' in sub_iface: - ret.append(sub_iface['l2xc']) + if "interfaces" in yaml: + for ifname, iface in yaml["interfaces"].items(): + if "l2xc" in iface: + ret.append(iface["l2xc"]) + if "sub-interfaces" in iface: + for subid, sub_iface in iface["sub-interfaces"].items(): + if "l2xc" in sub_iface: + ret.append(sub_iface["l2xc"]) return ret def is_l2xc_target_interface(yaml, ifname): - """ Returns True if this interface is the target of an L2 CrossConnect """ + """Returns True if this interface is the target of an L2 CrossConnect""" return ifname in get_l2xc_target_interfaces(yaml) def is_l2xc_target_interface_unique(yaml, ifname): - """ Returns True if this interface is referenced as an l2xc target zero or one times """ + """Returns True if this interface is referenced as an l2xc target zero or one times""" ifs = get_l2xc_target_interfaces(yaml) return ifs.count(ifname) < 2 def has_lcp(yaml, ifname): - """ Returns True if this interface or sub-interface has an LCP """ + """Returns True if this interface or sub-interface has an LCP""" ifname, iface = get_by_name(yaml, ifname) if not iface: return False - return 'lcp' in iface + return "lcp" in iface def valid_encapsulation(yaml, ifname): - """ Returns True if the sub interface has a valid encapsulation, or - none at all """ + """Returns True if the sub interface has a valid encapsulation, or + none at all""" ifname, iface = get_by_name(yaml, ifname) if not iface: return True - if not 'encapsulation' in iface: + if not "encapsulation" in iface: return True - encap = iface['encapsulation'] - if 'dot1ad' in encap and 'dot1q' in encap: + encap = iface["encapsulation"] + if "dot1ad" in encap and "dot1q" in encap: return False - if 'inner-dot1q' in encap and not ('dot1ad' in encap or 'dot1q' in encap): + if "inner-dot1q" in encap and not ("dot1ad" in encap or "dot1q" in encap): return False - if 'exact-match' in encap and encap['exact-match'] == False and has_lcp(yaml, ifname): + if ( + "exact-match" in encap + and encap["exact-match"] == False + and has_lcp(yaml, ifname) + ): return False return True def get_encapsulation(yaml, ifname): - """ Returns the encapsulation of an interface name as a fully formed dictionary: + """Returns the encapsulation of an interface name as a fully formed dictionary: - dot1q: int (default 0) - dot1ad: int (default 0) - inner-dot1q: int (default 0) - exact-match: bool (default False) + dot1q: int (default 0) + dot1ad: int (default 0) + inner-dot1q: int (default 0) + exact-match: bool (default False) - If the interface is not a sub-int with valid encapsulation, None is returned. + If the interface is not a sub-int with valid encapsulation, None is returned. """ if not valid_encapsulation(yaml, ifname): return None @@ -225,49 +230,49 @@ def get_encapsulation(yaml, ifname): parent_ifname, parent_iface = get_parent_by_name(yaml, ifname) if not iface or not parent_iface: return None - parent_ifname, subid = ifname.split('.') + parent_ifname, subid = ifname.split(".") dot1q = 0 dot1ad = 0 inner_dot1q = 0 exact_match = False - if not 'encapsulation' in iface: + if not "encapsulation" in iface: dot1q = int(subid) exact_match = True else: - if 'dot1q' in iface['encapsulation']: - dot1q = iface['encapsulation']['dot1q'] - elif 'dot1ad' in iface['encapsulation']: - dot1ad = iface['encapsulation']['dot1ad'] - if 'inner-dot1q' in iface['encapsulation']: - inner_dot1q = iface['encapsulation']['inner-dot1q'] - if 'exact-match' in iface['encapsulation']: - exact_match = iface['encapsulation']['exact-match'] + if "dot1q" in iface["encapsulation"]: + dot1q = iface["encapsulation"]["dot1q"] + elif "dot1ad" in iface["encapsulation"]: + dot1ad = iface["encapsulation"]["dot1ad"] + if "inner-dot1q" in iface["encapsulation"]: + inner_dot1q = iface["encapsulation"]["inner-dot1q"] + if "exact-match" in iface["encapsulation"]: + exact_match = iface["encapsulation"]["exact-match"] return { - "dot1q": int(dot1q), - "dot1ad": int(dot1ad), - "inner-dot1q": int(inner_dot1q), - "exact-match": bool(exact_match) - } + "dot1q": int(dot1q), + "dot1ad": int(dot1ad), + "inner-dot1q": int(inner_dot1q), + "exact-match": bool(exact_match), + } def get_phys(yaml): - """ Return a list of all toplevel (ie. non-sub) interfaces which are + """Return a list of all toplevel (ie. non-sub) interfaces which are assumed to be physical network cards, eg TenGigabitEthernet1/0/0. Note that derived/created interfaces such as Tunnels, BondEthernets and - Loopbacks are not returned """ + Loopbacks are not returned""" ret = [] - if not 'interfaces' in yaml: + if not "interfaces" in yaml: return ret - for ifname, iface in yaml['interfaces'].items(): + for ifname, iface in yaml["interfaces"].items(): if is_phy(yaml, ifname): ret.append(ifname) return ret def is_phy(yaml, ifname): - """ Returns True if the ifname is the name of a physical network interface. """ + """Returns True if the ifname is the name of a physical network interface.""" ifname, iface = get_by_name(yaml, ifname) if iface == None: @@ -287,33 +292,34 @@ def is_phy(yaml, ifname): def get_interfaces(yaml): - """ Return a list of all interface and sub-interface names """ + """Return a list of all interface and sub-interface names""" ret = [] - if not 'interfaces' in yaml: + if not "interfaces" in yaml: return ret - for ifname, iface in yaml['interfaces'].items(): + for ifname, iface in yaml["interfaces"].items(): ret.append(ifname) - if not 'sub-interfaces' in iface: + if not "sub-interfaces" in iface: continue - for subid, sub_iface in iface['sub-interfaces'].items(): + for subid, sub_iface in iface["sub-interfaces"].items(): ret.append(f"{ifname}.{int(subid)}") return ret def get_sub_interfaces(yaml): - """ Return all interfaces which are a subinterface. """ + """Return all interfaces which are a subinterface.""" ret = [] for ifname in get_interfaces(yaml): if is_sub(yaml, ifname): ret.append(ifname) return ret -def get_qinx_interfaces(yaml): - """ Return all interfaces which are double-tagged, either QinAD or QinQ. - These interfaces will always have a valid encapsulation with 'inner-dot1q' - set to non-zero. - Note: this is always a strict subset of get_sub_interfaces() +def get_qinx_interfaces(yaml): + """Return all interfaces which are double-tagged, either QinAD or QinQ. + These interfaces will always have a valid encapsulation with 'inner-dot1q' + set to non-zero. + + Note: this is always a strict subset of get_sub_interfaces() """ ret = [] for ifname in get_interfaces(yaml): @@ -322,19 +328,19 @@ def get_qinx_interfaces(yaml): encap = get_encapsulation(yaml, ifname) if not encap: continue - if encap['inner-dot1q'] > 0: + if encap["inner-dot1q"] > 0: ret.append(ifname) return ret def is_qinx(yaml, ifname): - """ Returns True if the interface is a double-tagged (QinQ or QinAD) interface """ + """Returns True if the interface is a double-tagged (QinQ or QinAD) interface""" return ifname in get_qinx_interfaces(yaml) def unique_encapsulation(yaml, sub_ifname): - """ Ensures that for the sub_ifname specified, there exist no other sub-ints on the - parent with the same encapsulation. """ + """Ensures that for the sub_ifname specified, there exist no other sub-ints on the + parent with the same encapsulation.""" new_ifname, iface = get_by_name(yaml, sub_ifname) parent_ifname, parent_iface = get_parent_by_name(yaml, new_ifname) if not iface or not parent_iface: @@ -345,7 +351,7 @@ def unique_encapsulation(yaml, sub_ifname): return False ncount = 0 - for subid, sibling_iface in parent_iface['sub-interfaces'].items(): + for subid, sibling_iface in parent_iface["sub-interfaces"].items(): sibling_ifname = f"{parent_ifname}.{int(subid)}" sibling_encap = get_encapsulation(yaml, sibling_ifname) if sub_encap == sibling_encap and new_ifname != sibling_ifname: @@ -355,7 +361,7 @@ def unique_encapsulation(yaml, sub_ifname): def is_l2(yaml, ifname): - """ Returns True if the interface is an L2XC source, L2XC target or a member of a bridgedomain """ + """Returns True if the interface is an L2XC source, L2XC target or a member of a bridgedomain""" if bridgedomain.is_bridge_interface(yaml, ifname): return True if is_l2xc_interface(yaml, ifname): @@ -366,22 +372,23 @@ def is_l2(yaml, ifname): def is_l3(yaml, ifname): - """ Returns True if the interface exists and is neither l2xc target nor bridgedomain """ + """Returns True if the interface exists and is neither l2xc target nor bridgedomain""" return not is_l2(yaml, ifname) def get_lcp(yaml, ifname): - """ Returns the LCP of the interface. If the interface is a sub-interface with L3 + """Returns the LCP of the interface. If the interface is a sub-interface with L3 enabled, synthesize it based on its parent, using smart QinQ syntax. - Return None if no LCP can be found. """ + Return None if no LCP can be found.""" ifname, iface = get_by_name(yaml, ifname) - if iface and 'lcp' in iface: - return iface['lcp'] + if iface and "lcp" in iface: + return iface["lcp"] return None + def get_mtu(yaml, ifname): - """ Returns MTU of the interface. If it's not set, return the parent's MTU, and + """Returns MTU of the interface. If it's not set, return the parent's MTU, and return 1500 if no MTU was set on the sub-int or the parent.""" ifname, iface = get_by_name(yaml, ifname) if not iface: @@ -390,61 +397,70 @@ def get_mtu(yaml, ifname): parent_ifname, parent_iface = get_parent_by_name(yaml, ifname) try: - return iface['mtu'] - return parent_iface['mtu'] + return iface["mtu"] + return parent_iface["mtu"] except: pass return 1500 def get_admin_state(yaml, ifname): - """ Return True if the interface admin state should be 'up'. Return False - if it does not exist, or if it's set to 'down'. """ + """Return True if the interface admin state should be 'up'. Return False + if it does not exist, or if it's set to 'down'.""" ifname, iface = get_by_name(yaml, ifname) if not iface: return False - if not 'state' in iface: + if not "state" in iface: return True - return iface['state'] == 'up' + return iface["state"] == "up" def validate_interfaces(yaml): result = True msgs = [] - logger = logging.getLogger('vppcfg.config') + logger = logging.getLogger("vppcfg.config") logger.addHandler(logging.NullHandler()) - if not 'interfaces' in yaml: + if not "interfaces" in yaml: return result, msgs - for ifname, iface in yaml['interfaces'].items(): + for ifname, iface in yaml["interfaces"].items(): logger.debug(f"interface {iface}") - if ifname.startswith("BondEthernet") and (None,None) == bondethernet.get_by_name(yaml, ifname): + if ifname.startswith("BondEthernet") and ( + None, + None, + ) == bondethernet.get_by_name(yaml, ifname): msgs.append(f"interface {ifname} does not exist in bondethernets") result = False - if ifname.startswith("BondEthernet") and 'mac' in iface: - msgs.append(f"interface {ifname} is a member of bondethernet, cannot set MAC") + if ifname.startswith("BondEthernet") and "mac" in iface: + msgs.append( + f"interface {ifname} is a member of bondethernet, cannot set MAC" + ) result = False - if not 'state' in iface: - iface['state'] = 'up' + if not "state" in iface: + iface["state"] = "up" - if 'mac' in iface and mac.is_multicast(iface['mac']): - msgs.append(f"interface {ifname} MAC address {iface['mac']} cannot be multicast") + if "mac" in iface and mac.is_multicast(iface["mac"]): + msgs.append( + f"interface {ifname} MAC address {iface['mac']} cannot be multicast" + ) result = False iface_mtu = get_mtu(yaml, ifname) iface_lcp = get_lcp(yaml, ifname) iface_address = has_address(yaml, ifname) - if ifname.startswith('tap'): + if ifname.startswith("tap"): tap_ifname, tap_iface = tap.get_by_name(yaml, ifname) if not tap_iface: msgs.append(f"interface {ifname} is a TAP but does not exist in taps") result = False - elif 'mtu' in tap_iface['host']: - host_mtu = tap_iface['host']['mtu'] + elif "mtu" in tap_iface["host"]: + host_mtu = tap_iface["host"]["mtu"] if host_mtu != iface_mtu: - msgs.append(f"interface {ifname} is a TAP so its MTU {int(iface_mtu)} must match host MTU {int(host_mtu)}") + msgs.append( + f"interface {ifname} is a TAP so its MTU {int(iface_mtu)} must match host MTU {int(host_mtu)}" + ) result = False if iface_address: msgs.append(f"interface {ifname} is a TAP so it cannot have an address") @@ -453,28 +469,38 @@ def validate_interfaces(yaml): msgs.append(f"interface {ifname} is a TAP so it cannot have an LCP") result = False if has_sub(yaml, ifname): - msgs.append(f"interface {ifname} is a TAP so it cannot have sub-interfaces") + msgs.append( + f"interface {ifname} is a TAP so it cannot have sub-interfaces" + ) result = False if is_l2(yaml, ifname) and iface_lcp: - msgs.append(f"interface {ifname} is in L2 mode but has LCP name {iface_lcp}") + msgs.append( + f"interface {ifname} is in L2 mode but has LCP name {iface_lcp}" + ) result = False if is_l2(yaml, ifname) and iface_address: msgs.append(f"interface {ifname} is in L2 mode but has an address") result = False if iface_lcp and not lcp.is_unique(yaml, iface_lcp): - msgs.append(f"interface {ifname} does not have a unique LCP name {iface_lcp}") + msgs.append( + f"interface {ifname} does not have a unique LCP name {iface_lcp}" + ) result = False - if 'addresses' in iface: - for a in iface['addresses']: - if not address.is_allowed(yaml, ifname, iface['addresses'], a): - msgs.append(f"interface {ifname} IP address {a} conflicts with another") + if "addresses" in iface: + for a in iface["addresses"]: + if not address.is_allowed(yaml, ifname, iface["addresses"], a): + msgs.append( + f"interface {ifname} IP address {a} conflicts with another" + ) result = False - if 'l2xc' in iface: + if "l2xc" in iface: if has_sub(yaml, ifname): - msgs.append(f"interface {ifname} has l2xc so it cannot have sub-interfaces") + msgs.append( + f"interface {ifname} has l2xc so it cannot have sub-interfaces" + ) result = False if iface_lcp: msgs.append(f"interface {ifname} has l2xc so it cannot have an LCP") @@ -482,31 +508,45 @@ def validate_interfaces(yaml): if iface_address: msgs.append(f"interface {ifname} has l2xc so it cannot have an address") result = False - if (None,None) == get_by_name(yaml, iface['l2xc']): - msgs.append(f"interface {ifname} l2xc target {iface['l2xc']} does not exist") + if (None, None) == get_by_name(yaml, iface["l2xc"]): + msgs.append( + f"interface {ifname} l2xc target {iface['l2xc']} does not exist" + ) result = False - if iface['l2xc'] == ifname: + if iface["l2xc"] == ifname: msgs.append(f"interface {ifname} l2xc target cannot be itself") result = False - target_mtu = get_mtu(yaml, iface['l2xc']) + target_mtu = get_mtu(yaml, iface["l2xc"]) if target_mtu != iface_mtu: - msgs.append(f"interface {ifname} l2xc target MTU {int(target_mtu)} does not match source MTU {int(iface_mtu)}") + msgs.append( + f"interface {ifname} l2xc target MTU {int(target_mtu)} does not match source MTU {int(iface_mtu)}" + ) result = False - if not is_l2xc_target_interface_unique(yaml, iface['l2xc']): - msgs.append(f"interface {ifname} l2xc target {iface['l2xc']} is not unique") + if not is_l2xc_target_interface_unique(yaml, iface["l2xc"]): + msgs.append( + f"interface {ifname} l2xc target {iface['l2xc']} is not unique" + ) result = False - if bridgedomain.is_bridge_interface(yaml, iface['l2xc']): - msgs.append(f"interface {ifname} l2xc target {iface['l2xc']} is in a bridgedomain") + if bridgedomain.is_bridge_interface(yaml, iface["l2xc"]): + msgs.append( + f"interface {ifname} l2xc target {iface['l2xc']} is in a bridgedomain" + ) result = False - if has_lcp(yaml, iface['l2xc']): - msgs.append(f"interface {ifname} l2xc target {iface['l2xc']} cannot have an LCP") + if has_lcp(yaml, iface["l2xc"]): + msgs.append( + f"interface {ifname} l2xc target {iface['l2xc']} cannot have an LCP" + ) result = False - if has_address(yaml, iface['l2xc']): - msgs.append(f"interface {ifname} l2xc target {iface['l2xc']} cannot have an address") + if has_address(yaml, iface["l2xc"]): + msgs.append( + f"interface {ifname} l2xc target {iface['l2xc']} cannot have an address" + ) result = False if has_sub(yaml, ifname): - for sub_id, sub_iface in yaml['interfaces'][ifname]['sub-interfaces'].items(): + for sub_id, sub_iface in yaml["interfaces"][ifname][ + "sub-interfaces" + ].items(): logger.debug(f"sub-interface {sub_iface}") sub_ifname = f"{ifname}.{int(sub_id)}" if not sub_iface: @@ -514,93 +554,138 @@ def validate_interfaces(yaml): result = False continue - if not 'state' in sub_iface: - sub_iface['state'] = 'up' - if sub_iface['state'] == 'up' and iface['state'] == 'down': - msgs.append(f"sub-interface {sub_ifname} cannot be up if parent {ifname} is down") + if not "state" in sub_iface: + sub_iface["state"] = "up" + if sub_iface["state"] == "up" and iface["state"] == "down": + msgs.append( + f"sub-interface {sub_ifname} cannot be up if parent {ifname} is down" + ) result = False sub_mtu = get_mtu(yaml, sub_ifname) if sub_mtu > iface_mtu: - msgs.append(f"sub-interface {sub_ifname} has MTU {int(sub_iface['mtu'])} higher than parent {ifname} MTU {int(iface_mtu)}") + msgs.append( + f"sub-interface {sub_ifname} has MTU {int(sub_iface['mtu'])} higher than parent {ifname} MTU {int(iface_mtu)}" + ) result = False if is_qinx(yaml, sub_ifname): mid_ifname, mid_iface = get_qinx_parent_by_name(yaml, sub_ifname) mid_mtu = get_mtu(yaml, mid_ifname) if sub_mtu > mid_mtu: - msgs.append(f"sub-interface {sub_ifname} has MTU {int(sub_iface['mtu'])} higher than parent {mid_ifname} MTU {int(mid_mtu)}") + msgs.append( + f"sub-interface {sub_ifname} has MTU {int(sub_iface['mtu'])} higher than parent {mid_ifname} MTU {int(mid_mtu)}" + ) result = False sub_lcp = get_lcp(yaml, sub_ifname) if is_l2(yaml, sub_ifname) and sub_lcp: - msgs.append(f"sub-interface {sub_ifname} is in L2 mode but has LCP name {sub_lcp}") + msgs.append( + f"sub-interface {sub_ifname} is in L2 mode but has LCP name {sub_lcp}" + ) result = False if sub_lcp and not lcp.is_unique(yaml, sub_lcp): - msgs.append(f"sub-interface {sub_ifname} does not have a unique LCP name {sub_lcp}") + msgs.append( + f"sub-interface {sub_ifname} does not have a unique LCP name {sub_lcp}" + ) result = False if sub_lcp and not iface_lcp: - msgs.append(f"sub-interface {sub_ifname} has LCP name {sub_lcp} but {ifname} does not have an LCP") + msgs.append( + f"sub-interface {sub_ifname} has LCP name {sub_lcp} but {ifname} does not have an LCP" + ) result = False if sub_lcp and is_qinx(yaml, sub_ifname): mid_ifname, mid_iface = get_qinx_parent_by_name(yaml, sub_ifname) if not mid_iface: - msgs.append(f"sub-interface {sub_ifname} is QinX and has LCP name {sub_lcp} which requires a parent") + msgs.append( + f"sub-interface {sub_ifname} is QinX and has LCP name {sub_lcp} which requires a parent" + ) result = False elif not get_lcp(yaml, mid_ifname): - msgs.append(f"sub-interface {sub_ifname} is QinX and has LCP name {sub_lcp} but {mid_ifname} does not have an LCP") + msgs.append( + f"sub-interface {sub_ifname} is QinX and has LCP name {sub_lcp} but {mid_ifname} does not have an LCP" + ) result = False encap = get_encapsulation(yaml, sub_ifname) - if sub_lcp and (not encap or not encap['exact-match']): - msgs.append(f"sub-interface {sub_ifname} has LCP name {sub_lcp} but its encapsulation is not exact-match") + if sub_lcp and (not encap or not encap["exact-match"]): + msgs.append( + f"sub-interface {sub_ifname} has LCP name {sub_lcp} but its encapsulation is not exact-match" + ) result = False if has_address(yaml, sub_ifname): - if not encap or not encap['exact-match']: - msgs.append(f"sub-interface {sub_ifname} has an address but its encapsulation is not exact-match") + if not encap or not encap["exact-match"]: + msgs.append( + f"sub-interface {sub_ifname} has an address but its encapsulation is not exact-match" + ) result = False if is_l2(yaml, sub_ifname): - msgs.append(f"sub-interface {sub_ifname} is in L2 mode but has an address") + msgs.append( + f"sub-interface {sub_ifname} is in L2 mode but has an address" + ) result = False - for a in sub_iface['addresses']: - if not address.is_allowed(yaml, sub_ifname, sub_iface['addresses'], a): - msgs.append(f"sub-interface {sub_ifname} IP address {a} conflicts with another") + for a in sub_iface["addresses"]: + if not address.is_allowed( + yaml, sub_ifname, sub_iface["addresses"], a + ): + msgs.append( + f"sub-interface {sub_ifname} IP address {a} conflicts with another" + ) result = False if not valid_encapsulation(yaml, sub_ifname): msgs.append(f"sub-interface {sub_ifname} has invalid encapsulation") result = False elif not unique_encapsulation(yaml, sub_ifname): - msgs.append(f"sub-interface {sub_ifname} does not have unique encapsulation") + msgs.append( + f"sub-interface {sub_ifname} does not have unique encapsulation" + ) result = False - if 'l2xc' in sub_iface: + if "l2xc" in sub_iface: if has_lcp(yaml, sub_ifname): - msgs.append(f"sub-interface {sub_ifname} has l2xc so it cannot have an LCP") + msgs.append( + f"sub-interface {sub_ifname} has l2xc so it cannot have an LCP" + ) result = False if has_address(yaml, sub_ifname): - msgs.append(f"sub-interface {sub_ifname} has l2xc so it cannot have an address") + msgs.append( + f"sub-interface {sub_ifname} has l2xc so it cannot have an address" + ) result = False - if (None, None) == get_by_name(yaml, sub_iface['l2xc']): - msgs.append(f"sub-interface {sub_ifname} l2xc target {sub_iface['l2xc']} does not exist") + if (None, None) == get_by_name(yaml, sub_iface["l2xc"]): + msgs.append( + f"sub-interface {sub_ifname} l2xc target {sub_iface['l2xc']} does not exist" + ) result = False - if sub_iface['l2xc'] == sub_ifname: - msgs.append(f"sub-interface {sub_ifname} l2xc target cannot be itself") + if sub_iface["l2xc"] == sub_ifname: + msgs.append( + f"sub-interface {sub_ifname} l2xc target cannot be itself" + ) result = False - target_mtu = get_mtu(yaml, sub_iface['l2xc']) + target_mtu = get_mtu(yaml, sub_iface["l2xc"]) if target_mtu != sub_mtu: - msgs.append(f"sub-interface {ifname} l2xc target MTU {int(target_mtu)} does not match source MTU {int(sub_mtu)}") + msgs.append( + f"sub-interface {ifname} l2xc target MTU {int(target_mtu)} does not match source MTU {int(sub_mtu)}" + ) result = False - if not is_l2xc_target_interface_unique(yaml, sub_iface['l2xc']): - msgs.append(f"sub-interface {sub_ifname} l2xc target {sub_iface['l2xc']} is not unique") + if not is_l2xc_target_interface_unique(yaml, sub_iface["l2xc"]): + msgs.append( + f"sub-interface {sub_ifname} l2xc target {sub_iface['l2xc']} is not unique" + ) result = False - if bridgedomain.is_bridge_interface(yaml, sub_iface['l2xc']): - msgs.append(f"sub-interface {sub_ifname} l2xc target {sub_iface['l2xc']} is in a bridgedomain") + if bridgedomain.is_bridge_interface(yaml, sub_iface["l2xc"]): + msgs.append( + f"sub-interface {sub_ifname} l2xc target {sub_iface['l2xc']} is in a bridgedomain" + ) result = False - if has_lcp(yaml, sub_iface['l2xc']): - msgs.append(f"sub-interface {sub_ifname} l2xc target {sub_iface['l2xc']} cannot have an LCP") + if has_lcp(yaml, sub_iface["l2xc"]): + msgs.append( + f"sub-interface {sub_ifname} l2xc target {sub_iface['l2xc']} cannot have an LCP" + ) result = False - if has_address(yaml, sub_iface['l2xc']): - msgs.append(f"sub-interface {sub_ifname} l2xc target {sub_iface['l2xc']} cannot have an address") + if has_address(yaml, sub_iface["l2xc"]): + msgs.append( + f"sub-interface {sub_ifname} l2xc target {sub_iface['l2xc']} cannot have an address" + ) result = False - return result, msgs diff --git a/config/lcp.py b/config/lcp.py index 2006eeb..a903505 100644 --- a/config/lcp.py +++ b/config/lcp.py @@ -13,33 +13,35 @@ # import logging + def get_lcps(yaml, interfaces=True, loopbacks=True, bridgedomains=True): - """ Returns a list of LCPs configured in the system. Optionally (de)select the different - types of LCP. Return an empty list if there are none of the given type(s). """ + """Returns a list of LCPs configured in the system. Optionally (de)select the different + types of LCP. Return an empty list if there are none of the given type(s).""" ret = [] - if interfaces and 'interfaces' in yaml: - for ifname, iface in yaml['interfaces'].items(): - if 'lcp' in iface: - ret.append(iface['lcp']) - if 'sub-interfaces' in iface: - for subid, sub_iface in iface['sub-interfaces'].items(): - if 'lcp' in sub_iface: - ret.append(sub_iface['lcp']) + if interfaces and "interfaces" in yaml: + for ifname, iface in yaml["interfaces"].items(): + if "lcp" in iface: + ret.append(iface["lcp"]) + if "sub-interfaces" in iface: + for subid, sub_iface in iface["sub-interfaces"].items(): + if "lcp" in sub_iface: + ret.append(sub_iface["lcp"]) - if loopbacks and 'loopbacks' in yaml: - for ifname, iface in yaml['loopbacks'].items(): - if 'lcp' in iface: - ret.append(iface['lcp']) - if bridgedomains and 'bridgedomains' in yaml: - for ifname, iface in yaml['bridgedomains'].items(): - if 'lcp' in iface: - ret.append(iface['lcp']) + if loopbacks and "loopbacks" in yaml: + for ifname, iface in yaml["loopbacks"].items(): + if "lcp" in iface: + ret.append(iface["lcp"]) + if bridgedomains and "bridgedomains" in yaml: + for ifname, iface in yaml["bridgedomains"].items(): + if "lcp" in iface: + ret.append(iface["lcp"]) return ret + def is_unique(yaml, lcpname): - """ Returns True if there is at most one occurence of the LCP name in the entire config.""" + """Returns True if there is at most one occurence of the LCP name in the entire config.""" lcps = get_lcps(yaml) return lcps.count(lcpname) < 2 diff --git a/config/loopback.py b/config/loopback.py index ec9bc1b..44f759c 100644 --- a/config/loopback.py +++ b/config/loopback.py @@ -16,37 +16,38 @@ import config.lcp as lcp import config.address as address import config.mac as mac + def get_loopbacks(yaml): - """ Return a list of all loopbacks. """ + """Return a list of all loopbacks.""" ret = [] - if 'loopbacks' in yaml: - for ifname, iface in yaml['loopbacks'].items(): + if "loopbacks" in yaml: + for ifname, iface in yaml["loopbacks"].items(): ret.append(ifname) return ret def get_by_lcp_name(yaml, lcpname): - """ Returns the loopback by a given lcp name, or None,None if it does not exist """ - if not 'loopbacks' in yaml: - return None,None - for ifname, iface in yaml['loopbacks'].items(): - if 'lcp' in iface and iface['lcp'] == lcpname: + """Returns the loopback by a given lcp name, or None,None if it does not exist""" + if not "loopbacks" in yaml: + return None, None + for ifname, iface in yaml["loopbacks"].items(): + if "lcp" in iface and iface["lcp"] == lcpname: return ifname, iface - return None,None + return None, None def get_by_name(yaml, ifname): - """ Return the loopback by name, if it exists. Return None otherwise. """ + """Return the loopback by name, if it exists. Return None otherwise.""" try: - if ifname in yaml['loopbacks']: - return ifname, yaml['loopbacks'][ifname] + if ifname in yaml["loopbacks"]: + return ifname, yaml["loopbacks"][ifname] except: pass return None, None def is_loopback(yaml, ifname): - """ Returns True if the interface name is an existing loopback. """ + """Returns True if the interface name is an existing loopback.""" ifname, iface = get_by_name(yaml, ifname) return not iface == None @@ -54,28 +55,36 @@ def is_loopback(yaml, ifname): def validate_loopbacks(yaml): result = True msgs = [] - logger = logging.getLogger('vppcfg.config') + logger = logging.getLogger("vppcfg.config") logger.addHandler(logging.NullHandler()) - if not 'loopbacks' in yaml: + if not "loopbacks" in yaml: return result, msgs - for ifname, iface in yaml['loopbacks'].items(): + for ifname, iface in yaml["loopbacks"].items(): logger.debug(f"loopback {iface}") instance = int(ifname[4:]) if instance > 4095: - msgs.append(f"loopback {ifname} has instance {int(instance)} which is too large") + msgs.append( + f"loopback {ifname} has instance {int(instance)} which is too large" + ) result = False - if 'lcp' in iface and not lcp.is_unique(yaml, iface['lcp']): - msgs.append(f"loopback {ifname} does not have a unique LCP name {iface['lcp']}") + if "lcp" in iface and not lcp.is_unique(yaml, iface["lcp"]): + msgs.append( + f"loopback {ifname} does not have a unique LCP name {iface['lcp']}" + ) result = False - if 'addresses' in iface: - for a in iface['addresses']: - if not address.is_allowed(yaml, ifname, iface['addresses'], a): - msgs.append(f"loopback {ifname} IP address {a} conflicts with another") + if "addresses" in iface: + for a in iface["addresses"]: + if not address.is_allowed(yaml, ifname, iface["addresses"], a): + msgs.append( + f"loopback {ifname} IP address {a} conflicts with another" + ) result = False - if 'mac' in iface and mac.is_multicast(iface['mac']): - msgs.append(f"loopback {ifname} MAC address {iface['mac']} cannot be multicast") + if "mac" in iface and mac.is_multicast(iface["mac"]): + msgs.append( + f"loopback {ifname} MAC address {iface['mac']} cannot be multicast" + ) result = False return result, msgs diff --git a/config/mac.py b/config/mac.py index ba7a05b..79316d8 100644 --- a/config/mac.py +++ b/config/mac.py @@ -14,33 +14,37 @@ import logging import netaddr + def is_valid(mac): - """ Return True if the string given in `mac` is a valid (6-byte) MAC address, - as defined by netaddr.EUI """ + """Return True if the string given in `mac` is a valid (6-byte) MAC address, + as defined by netaddr.EUI""" try: addr = netaddr.EUI(mac) except: return False return True + def is_local(mac): - """ Return True if a MAC address is a valid locally administered one. """ + """Return True if a MAC address is a valid locally administered one.""" try: addr = netaddr.EUI(mac) except: return False return bool(addr.words[0] & 0b10) + def is_multicast(mac): - """ Return True if a MAC address is a valid multicast one. """ + """Return True if a MAC address is a valid multicast one.""" try: addr = netaddr.EUI(mac) except: return False return bool(addr.words[0] & 0b01) + def is_unicast(mac): - """ Return True if a MAC address is a valid unicast one. """ + """Return True if a MAC address is a valid unicast one.""" try: addr = netaddr.EUI(mac) except: diff --git a/config/tap.py b/config/tap.py index c04289c..96775b9 100644 --- a/config/tap.py +++ b/config/tap.py @@ -14,54 +14,55 @@ import logging import config.mac as mac + def get_taps(yaml): - """ Return a list of all taps. """ + """Return a list of all taps.""" ret = [] - if 'taps' in yaml: - for ifname, iface in yaml['taps'].items(): + if "taps" in yaml: + for ifname, iface in yaml["taps"].items(): ret.append(ifname) return ret def get_by_name(yaml, ifname): - """ Return the tap by name, if it exists. Return None otherwise. """ + """Return the tap by name, if it exists. Return None otherwise.""" try: - if ifname in yaml['taps']: - return ifname, yaml['taps'][ifname] + if ifname in yaml["taps"]: + return ifname, yaml["taps"][ifname] except: pass return None, None def is_tap(yaml, ifname): - """ Returns True if the interface name is an existing tap in the config. - The TAP has to be explicitly named in the configuration, and notably - a TAP belonging to a Linux Control Plane (LCP) will return False. + """Returns True if the interface name is an existing tap in the config. + The TAP has to be explicitly named in the configuration, and notably + a TAP belonging to a Linux Control Plane (LCP) will return False. """ ifname, iface = get_by_name(yaml, ifname) return not iface == None def is_host_name_unique(yaml, hostname): - """ Returns True if there is at most one occurence of the given ifname amonst all host-names of TAPs. """ - if not 'taps' in yaml: + """Returns True if there is at most one occurence of the given ifname amonst all host-names of TAPs.""" + if not "taps" in yaml: return True host_names = [] - for tap_ifname, tap_iface in yaml['taps'].items(): - host_names.append(tap_iface['host']['name']) + for tap_ifname, tap_iface in yaml["taps"].items(): + host_names.append(tap_iface["host"]["name"]) return host_names.count(hostname) < 2 def validate_taps(yaml): result = True msgs = [] - logger = logging.getLogger('vppcfg.config') + logger = logging.getLogger("vppcfg.config") logger.addHandler(logging.NullHandler()) - if not 'taps' in yaml: + if not "taps" in yaml: return result, msgs - for ifname, iface in yaml['taps'].items(): + for ifname, iface in yaml["taps"].items(): logger.debug(f"tap {iface}") instance = int(ifname[3:]) @@ -70,32 +71,46 @@ def validate_taps(yaml): msgs.append(f"tap {ifname} has instance {int(instance)} which is too large") result = False - if not is_host_name_unique(yaml, iface['host']['name']): - msgs.append(f"tap {ifname} does not have a unique host name {iface['host']['name']}") + if not is_host_name_unique(yaml, iface["host"]["name"]): + msgs.append( + f"tap {ifname} does not have a unique host name {iface['host']['name']}" + ) result = False - if 'rx-ring-size' in iface: - n = iface['rx-ring-size'] - if n & (n-1) != 0: + if "rx-ring-size" in iface: + n = iface["rx-ring-size"] + if n & (n - 1) != 0: msgs.append(f"tap {ifname} rx-ring-size must be a power of two") result = False - if 'tx-ring-size' in iface: - n = iface['tx-ring-size'] - if n & (n-1) != 0: + if "tx-ring-size" in iface: + n = iface["tx-ring-size"] + if n & (n - 1) != 0: msgs.append(f"tap {ifname} tx-ring-size must be a power of two") result = False - if 'namespace-create' in iface['host'] and iface['host']['namespace-create'] and not 'namespace' in iface['host']: - msgs.append(f"tap {ifname} namespace-create can only be set if namespace is set") + if ( + "namespace-create" in iface["host"] + and iface["host"]["namespace-create"] + and not "namespace" in iface["host"] + ): + msgs.append( + f"tap {ifname} namespace-create can only be set if namespace is set" + ) result = False - if 'bridge-create' in iface['host'] and iface['host']['bridge-create'] and not 'bridge' in iface['host']: + if ( + "bridge-create" in iface["host"] + and iface["host"]["bridge-create"] + and not "bridge" in iface["host"] + ): msgs.append(f"tap {ifname} bridge-create can only be set if bridge is set") result = False - if 'mac' in iface['host'] and mac.is_multicast(iface['host']['mac']): - msgs.append(f"tap {ifname} host MAC address {iface['host']['mac']} cannot be multicast") + if "mac" in iface["host"] and mac.is_multicast(iface["host"]["mac"]): + msgs.append( + f"tap {ifname} host MAC address {iface['host']['mac']} cannot be multicast" + ) result = False return result, msgs diff --git a/config/test_bondethernet.py b/config/test_bondethernet.py index c2754d7..5faebea 100644 --- a/config/test_bondethernet.py +++ b/config/test_bondethernet.py @@ -2,17 +2,18 @@ import unittest import yaml import config.bondethernet as bondethernet + class TestBondEthernetMethods(unittest.TestCase): def setUp(self): with open("unittest/test_bondethernet.yaml", "r") as f: - self.cfg = yaml.load(f, Loader = yaml.FullLoader) + self.cfg = yaml.load(f, Loader=yaml.FullLoader) def test_get_by_name(self): ifname, iface = bondethernet.get_by_name(self.cfg, "BondEthernet0") self.assertIsNotNone(iface) self.assertEqual("BondEthernet0", ifname) - self.assertIn("GigabitEthernet1/0/0", iface['interfaces']) - self.assertNotIn("GigabitEthernet2/0/0", iface['interfaces']) + self.assertIn("GigabitEthernet1/0/0", iface["interfaces"]) + self.assertNotIn("GigabitEthernet2/0/0", iface["interfaces"]) ifname, iface = bondethernet.get_by_name(self.cfg, "BondEthernet-notexist") self.assertIsNone(iface) @@ -22,11 +23,15 @@ class TestBondEthernetMethods(unittest.TestCase): self.assertTrue(bondethernet.is_bond_member(self.cfg, "GigabitEthernet1/0/0")) self.assertTrue(bondethernet.is_bond_member(self.cfg, "GigabitEthernet1/0/1")) self.assertFalse(bondethernet.is_bond_member(self.cfg, "GigabitEthernet2/0/0")) - self.assertFalse(bondethernet.is_bond_member(self.cfg, "GigabitEthernet2/0/0.100")) + self.assertFalse( + bondethernet.is_bond_member(self.cfg, "GigabitEthernet2/0/0.100") + ) def test_is_bondethernet(self): self.assertTrue(bondethernet.is_bondethernet(self.cfg, "BondEthernet0")) - self.assertFalse(bondethernet.is_bondethernet(self.cfg, "BondEthernet-notexist")) + self.assertFalse( + bondethernet.is_bondethernet(self.cfg, "BondEthernet-notexist") + ) self.assertFalse(bondethernet.is_bondethernet(self.cfg, "GigabitEthernet1/0/0")) def test_enumerators(self): @@ -38,8 +43,8 @@ class TestBondEthernetMethods(unittest.TestCase): self.assertNotIn("BondEthernet-noexist", ifs) def test_get_mode(self): - self.assertEqual('lacp', bondethernet.get_mode(self.cfg, "BondEthernet0")) - self.assertEqual('xor', bondethernet.get_mode(self.cfg, "BondEthernet1")) + self.assertEqual("lacp", bondethernet.get_mode(self.cfg, "BondEthernet0")) + self.assertEqual("xor", bondethernet.get_mode(self.cfg, "BondEthernet1")) def test_mode_to_int(self): self.assertEqual(1, bondethernet.mode_to_int("round-robin")) @@ -59,8 +64,8 @@ class TestBondEthernetMethods(unittest.TestCase): self.assertEqual("", bondethernet.int_to_mode(6)) def test_get_lb(self): - self.assertEqual('l34', bondethernet.get_lb(self.cfg, "BondEthernet0")) - self.assertEqual('l2', bondethernet.get_lb(self.cfg, "BondEthernet1")) + self.assertEqual("l34", bondethernet.get_lb(self.cfg, "BondEthernet0")) + self.assertEqual("l2", bondethernet.get_lb(self.cfg, "BondEthernet1")) self.assertIsNone(bondethernet.get_lb(self.cfg, "BondEthernet2")) def test_lb_to_int(self): diff --git a/config/test_bridgedomain.py b/config/test_bridgedomain.py index f699d3e..f9be7ad 100644 --- a/config/test_bridgedomain.py +++ b/config/test_bridgedomain.py @@ -2,17 +2,18 @@ import unittest import yaml import config.bridgedomain as bridgedomain + class TestBridgeDomainMethods(unittest.TestCase): def setUp(self): with open("unittest/test_bridgedomain.yaml", "r") as f: - self.cfg = yaml.load(f, Loader = yaml.FullLoader) + self.cfg = yaml.load(f, Loader=yaml.FullLoader) def test_get_by_name(self): ifname, iface = bridgedomain.get_by_name(self.cfg, "bd10") self.assertIsNotNone(iface) self.assertEqual("bd10", ifname) - self.assertEqual(iface['mtu'], 3000) - self.assertIn("BondEthernet0", iface['interfaces']) + self.assertEqual(iface["mtu"], 3000) + self.assertIn("BondEthernet0", iface["interfaces"]) ifname, iface = bridgedomain.get_by_name(self.cfg, "bd-notexist") self.assertIsNone(iface) @@ -25,14 +26,28 @@ class TestBridgeDomainMethods(unittest.TestCase): self.assertFalse(bridgedomain.is_bridgedomain(self.cfg, "GigabitEthernet1/0/0")) def test_members(self): - self.assertTrue(bridgedomain.is_bridge_interface(self.cfg, "GigabitEthernet1/0/0")) - self.assertTrue(bridgedomain.is_bridge_interface(self.cfg, "GigabitEthernet2/0/0.100")) - self.assertFalse(bridgedomain.is_bridge_interface(self.cfg, "GigabitEthernet3/0/0")) - self.assertFalse(bridgedomain.is_bridge_interface(self.cfg, "GigabitEthernet3/0/0.100")) + self.assertTrue( + bridgedomain.is_bridge_interface(self.cfg, "GigabitEthernet1/0/0") + ) + self.assertTrue( + bridgedomain.is_bridge_interface(self.cfg, "GigabitEthernet2/0/0.100") + ) + self.assertFalse( + bridgedomain.is_bridge_interface(self.cfg, "GigabitEthernet3/0/0") + ) + self.assertFalse( + bridgedomain.is_bridge_interface(self.cfg, "GigabitEthernet3/0/0.100") + ) def test_unique(self): - self.assertFalse(bridgedomain.is_bridge_interface_unique(self.cfg, "GigabitEthernet1/0/0")) - self.assertTrue(bridgedomain.is_bridge_interface_unique(self.cfg, "GigabitEthernet2/0/0.100")) + self.assertFalse( + bridgedomain.is_bridge_interface_unique(self.cfg, "GigabitEthernet1/0/0") + ) + self.assertTrue( + bridgedomain.is_bridge_interface_unique( + self.cfg, "GigabitEthernet2/0/0.100" + ) + ) def test_enumerators(self): ifs = bridgedomain.get_bridge_interfaces(self.cfg) @@ -55,13 +70,13 @@ class TestBridgeDomainMethods(unittest.TestCase): self.assertIsNone(settings) settings = bridgedomain.get_settings(self.cfg, "bd10") - self.assertTrue(settings['learn']) - self.assertTrue(settings['unknown-unicast-flood']) - self.assertTrue(settings['unicast-flood']) - self.assertEqual(settings['mac-age-minutes'], 0) + self.assertTrue(settings["learn"]) + self.assertTrue(settings["unknown-unicast-flood"]) + self.assertTrue(settings["unicast-flood"]) + self.assertEqual(settings["mac-age-minutes"], 0) settings = bridgedomain.get_settings(self.cfg, "bd11") - self.assertTrue(settings['learn']) - self.assertFalse(settings['unknown-unicast-flood']) - self.assertFalse(settings['unicast-flood']) - self.assertEqual(settings['mac-age-minutes'], 10) + self.assertTrue(settings["learn"]) + self.assertFalse(settings["unknown-unicast-flood"]) + self.assertFalse(settings["unicast-flood"]) + self.assertEqual(settings["mac-age-minutes"], 10) diff --git a/config/test_interface.py b/config/test_interface.py index 2371b74..acc4bdf 100644 --- a/config/test_interface.py +++ b/config/test_interface.py @@ -2,10 +2,11 @@ import unittest import yaml import config.interface as interface + class TestInterfaceMethods(unittest.TestCase): def setUp(self): with open("unittest/test_interface.yaml", "r") as f: - self.cfg = yaml.load(f, Loader = yaml.FullLoader) + self.cfg = yaml.load(f, Loader=yaml.FullLoader) def test_enumerators(self): ifs = interface.get_interfaces(self.cfg) @@ -52,22 +53,42 @@ class TestInterfaceMethods(unittest.TestCase): self.assertEqual(interface.get_mtu(self.cfg, "GigabitEthernet1/0/1.201"), 1500) def test_encapsulation(self): - self.assertTrue(interface.valid_encapsulation(self.cfg, "GigabitEthernet1/0/1.200")) - self.assertTrue(interface.unique_encapsulation(self.cfg, "GigabitEthernet1/0/1.200")) - self.assertEqual(interface.get_encapsulation(self.cfg, "GigabitEthernet1/0/1.200"), - { 'dot1q': 1000, 'dot1ad': 0, 'inner-dot1q': 0, 'exact-match': False }) - self.assertEqual(interface.get_encapsulation(self.cfg, "GigabitEthernet1/0/1.201"), - { 'dot1q': 1000, 'dot1ad': 0, 'inner-dot1q': 1234, 'exact-match': False }) - self.assertEqual(interface.get_encapsulation(self.cfg, "GigabitEthernet1/0/1.202"), - { 'dot1q': 0, 'dot1ad': 1000, 'inner-dot1q': 0, 'exact-match': False }) - self.assertEqual(interface.get_encapsulation(self.cfg, "GigabitEthernet1/0/1.203"), - { 'dot1q': 0, 'dot1ad': 1000, 'inner-dot1q': 1000, 'exact-match': True }) + self.assertTrue( + interface.valid_encapsulation(self.cfg, "GigabitEthernet1/0/1.200") + ) + self.assertTrue( + interface.unique_encapsulation(self.cfg, "GigabitEthernet1/0/1.200") + ) + self.assertEqual( + interface.get_encapsulation(self.cfg, "GigabitEthernet1/0/1.200"), + {"dot1q": 1000, "dot1ad": 0, "inner-dot1q": 0, "exact-match": False}, + ) + self.assertEqual( + interface.get_encapsulation(self.cfg, "GigabitEthernet1/0/1.201"), + {"dot1q": 1000, "dot1ad": 0, "inner-dot1q": 1234, "exact-match": False}, + ) + self.assertEqual( + interface.get_encapsulation(self.cfg, "GigabitEthernet1/0/1.202"), + {"dot1q": 0, "dot1ad": 1000, "inner-dot1q": 0, "exact-match": False}, + ) + self.assertEqual( + interface.get_encapsulation(self.cfg, "GigabitEthernet1/0/1.203"), + {"dot1q": 0, "dot1ad": 1000, "inner-dot1q": 1000, "exact-match": True}, + ) - self.assertFalse(interface.valid_encapsulation(self.cfg, "GigabitEthernet1/0/0.100")) - self.assertFalse(interface.valid_encapsulation(self.cfg, "GigabitEthernet1/0/0.101")) + self.assertFalse( + interface.valid_encapsulation(self.cfg, "GigabitEthernet1/0/0.100") + ) + self.assertFalse( + interface.valid_encapsulation(self.cfg, "GigabitEthernet1/0/0.101") + ) - self.assertFalse(interface.unique_encapsulation(self.cfg, "GigabitEthernet1/0/0.102")) - self.assertFalse(interface.unique_encapsulation(self.cfg, "GigabitEthernet1/0/0.103")) + self.assertFalse( + interface.unique_encapsulation(self.cfg, "GigabitEthernet1/0/0.102") + ) + self.assertFalse( + interface.unique_encapsulation(self.cfg, "GigabitEthernet1/0/0.103") + ) def test_has_sub(self): self.assertTrue(interface.has_sub(self.cfg, "GigabitEthernet1/0/1")) @@ -97,8 +118,12 @@ class TestInterfaceMethods(unittest.TestCase): self.assertEqual(interface.get_lcp(self.cfg, "GigabitEthernet1/0/1"), "e1") self.assertEqual(interface.get_lcp(self.cfg, "GigabitEthernet1/0/1.100"), "foo") - self.assertEqual(interface.get_lcp(self.cfg, "GigabitEthernet1/0/1.101"), "e1.100") - self.assertEqual(interface.get_lcp(self.cfg, "GigabitEthernet1/0/1.102"), "e1.100.100") + self.assertEqual( + interface.get_lcp(self.cfg, "GigabitEthernet1/0/1.101"), "e1.100" + ) + self.assertEqual( + interface.get_lcp(self.cfg, "GigabitEthernet1/0/1.102"), "e1.100.100" + ) self.assertIsNone(interface.get_lcp(self.cfg, "GigabitEthernet1/0/1.200")) self.assertIsNone(interface.get_lcp(self.cfg, "GigabitEthernet1/0/1.201")) @@ -119,12 +144,16 @@ class TestInterfaceMethods(unittest.TestCase): self.assertIn("GigabitEthernet3/0/0", l2xc_ifs) self.assertIn("GigabitEthernet3/0/0", l2xc_target_ifs) self.assertTrue(interface.is_l2xc_interface(self.cfg, "GigabitEthernet3/0/0")) - self.assertTrue(interface.is_l2xc_target_interface(self.cfg, "GigabitEthernet3/0/0")) + self.assertTrue( + interface.is_l2xc_target_interface(self.cfg, "GigabitEthernet3/0/0") + ) self.assertNotIn("GigabitEthernet2/0/0", l2xc_ifs) self.assertNotIn("GigabitEthernet2/0/0", l2xc_target_ifs) self.assertFalse(interface.is_l2xc_interface(self.cfg, "GigabitEthernet2/0/0")) - self.assertFalse(interface.is_l2xc_target_interface(self.cfg, "GigabitEthernet2/0/0")) + self.assertFalse( + interface.is_l2xc_target_interface(self.cfg, "GigabitEthernet2/0/0") + ) def test_l2(self): self.assertTrue(interface.is_l2(self.cfg, "GigabitEthernet3/0/0")) @@ -151,22 +180,29 @@ class TestInterfaceMethods(unittest.TestCase): self.assertEqual(ifname, "GigabitEthernet1/0/1.201") self.assertIsNotNone(iface) encap = interface.get_encapsulation(self.cfg, ifname) - self.assertEqual(encap, {'dot1q': 1000, 'dot1ad': 0, 'inner-dot1q': 1234, 'exact-match': False}) + self.assertEqual( + encap, + {"dot1q": 1000, "dot1ad": 0, "inner-dot1q": 1234, "exact-match": False}, + ) ifname, iface = interface.get_by_name(self.cfg, "GigabitEthernet1/0/1.1") self.assertIsNone(ifname) self.assertIsNone(iface) def test_get_parent_by_name(self): - ifname, iface = interface.get_parent_by_name(self.cfg, "GigabitEthernet1/0/1.201") + ifname, iface = interface.get_parent_by_name( + self.cfg, "GigabitEthernet1/0/1.201" + ) self.assertEqual(ifname, "GigabitEthernet1/0/1") self.assertIsNotNone(iface) - self.assertNotIn('encapsulation', iface) + self.assertNotIn("encapsulation", iface) - ifname, iface = interface.get_parent_by_name(self.cfg, "GigabitEthernet1/0/1.200") + ifname, iface = interface.get_parent_by_name( + self.cfg, "GigabitEthernet1/0/1.200" + ) self.assertEqual(ifname, "GigabitEthernet1/0/1") self.assertIsNotNone(iface) - self.assertNotIn('encapsulation', iface) + self.assertNotIn("encapsulation", iface) ifname, iface = interface.get_parent_by_name(self.cfg, "GigabitEthernet1/0/1") self.assertIsNone(ifname) @@ -177,22 +213,34 @@ class TestInterfaceMethods(unittest.TestCase): self.assertIsNone(iface) def test_get_qinx_parent_by_name(self): - self.assertIsNotNone(interface.get_qinx_parent_by_name(self.cfg, "GigabitEthernet1/0/1.202")) - self.assertIsNotNone(interface.get_qinx_parent_by_name(self.cfg, "GigabitEthernet1/0/1.203")) + self.assertIsNotNone( + interface.get_qinx_parent_by_name(self.cfg, "GigabitEthernet1/0/1.202") + ) + self.assertIsNotNone( + interface.get_qinx_parent_by_name(self.cfg, "GigabitEthernet1/0/1.203") + ) - ifname, iface = interface.get_qinx_parent_by_name(self.cfg, "GigabitEthernet1/0/1") + ifname, iface = interface.get_qinx_parent_by_name( + self.cfg, "GigabitEthernet1/0/1" + ) self.assertIsNone(iface) self.assertIsNone(ifname) - ifname, iface = interface.get_qinx_parent_by_name(self.cfg, "GigabitEthernet1/0/1.100") + ifname, iface = interface.get_qinx_parent_by_name( + self.cfg, "GigabitEthernet1/0/1.100" + ) self.assertIsNone(iface) self.assertIsNone(ifname) - ifname, iface = interface.get_qinx_parent_by_name(self.cfg, "GigabitEthernet1/0/1.200") + ifname, iface = interface.get_qinx_parent_by_name( + self.cfg, "GigabitEthernet1/0/1.200" + ) self.assertIsNone(iface) self.assertIsNone(ifname) - ifname, iface = interface.get_qinx_parent_by_name(self.cfg, "GigabitEthernet1/0/1.201") + ifname, iface = interface.get_qinx_parent_by_name( + self.cfg, "GigabitEthernet1/0/1.201" + ) self.assertEqual(ifname, "GigabitEthernet1/0/1.200") def test_get_phys(self): @@ -211,4 +259,6 @@ class TestInterfaceMethods(unittest.TestCase): self.assertFalse(interface.get_admin_state(self.cfg, "GigabitEthernet2/0/0")) self.assertTrue(interface.get_admin_state(self.cfg, "GigabitEthernet1/0/0")) self.assertTrue(interface.get_admin_state(self.cfg, "GigabitEthernet1/0/0.101")) - self.assertFalse(interface.get_admin_state(self.cfg, "GigabitEthernet1/0/0.102")) + self.assertFalse( + interface.get_admin_state(self.cfg, "GigabitEthernet1/0/0.102") + ) diff --git a/config/test_lcp.py b/config/test_lcp.py index ea1d126..6721ed2 100644 --- a/config/test_lcp.py +++ b/config/test_lcp.py @@ -3,10 +3,11 @@ import yaml import config.lcp as lcp import config.interface as interface + class TestLCPMethods(unittest.TestCase): def setUp(self): with open("unittest/test_lcp.yaml", "r") as f: - self.cfg = yaml.load(f, Loader = yaml.FullLoader) + self.cfg = yaml.load(f, Loader=yaml.FullLoader) def test_enumerators(self): lcps = lcp.get_lcps(self.cfg) @@ -26,22 +27,36 @@ class TestLCPMethods(unittest.TestCase): self.assertFalse(lcp.is_unique(self.cfg, "thrice")) def test_qinx(self): - qinx_ifname, qinx_iface = interface.get_by_name(self.cfg, "GigabitEthernet1/0/1.201") - mid_ifname, mid_iface = interface.get_qinx_parent_by_name(self.cfg, "GigabitEthernet1/0/1.201") - parent_ifname, parent_iface = interface.get_parent_by_name(self.cfg, "GigabitEthernet1/0/1.201") + qinx_ifname, qinx_iface = interface.get_by_name( + self.cfg, "GigabitEthernet1/0/1.201" + ) + mid_ifname, mid_iface = interface.get_qinx_parent_by_name( + self.cfg, "GigabitEthernet1/0/1.201" + ) + parent_ifname, parent_iface = interface.get_parent_by_name( + self.cfg, "GigabitEthernet1/0/1.201" + ) self.assertEqual(qinx_ifname, "GigabitEthernet1/0/1.201") self.assertEqual(mid_ifname, "GigabitEthernet1/0/1.200") self.assertEqual(parent_ifname, "GigabitEthernet1/0/1") - qinx_ifname, qinx_iface = interface.get_by_name(self.cfg, "GigabitEthernet1/0/1.201") - mid_ifname, mid_iface = interface.get_qinx_parent_by_name(self.cfg, "GigabitEthernet1/0/1.201") - parent_ifname, parent_iface = interface.get_parent_by_name(self.cfg, "GigabitEthernet1/0/1.201") + qinx_ifname, qinx_iface = interface.get_by_name( + self.cfg, "GigabitEthernet1/0/1.201" + ) + mid_ifname, mid_iface = interface.get_qinx_parent_by_name( + self.cfg, "GigabitEthernet1/0/1.201" + ) + parent_ifname, parent_iface = interface.get_parent_by_name( + self.cfg, "GigabitEthernet1/0/1.201" + ) self.assertEqual(qinx_ifname, "GigabitEthernet1/0/1.201") self.assertEqual(mid_ifname, "GigabitEthernet1/0/1.200") self.assertEqual(parent_ifname, "GigabitEthernet1/0/1") - ifname, iface = interface.get_qinx_parent_by_name(self.cfg, "GigabitEthernet1/0/1.100") + ifname, iface = interface.get_qinx_parent_by_name( + self.cfg, "GigabitEthernet1/0/1.100" + ) self.assertIsNone(ifname) self.assertIsNone(iface) diff --git a/config/test_loopback.py b/config/test_loopback.py index dc8d610..8d27d00 100644 --- a/config/test_loopback.py +++ b/config/test_loopback.py @@ -2,10 +2,11 @@ import unittest import yaml import config.loopback as loopback + class TestLoopbackMethods(unittest.TestCase): def setUp(self): with open("unittest/test_loopback.yaml", "r") as f: - self.cfg = yaml.load(f, Loader = yaml.FullLoader) + self.cfg = yaml.load(f, Loader=yaml.FullLoader) def test_get_by_lcp_name(self): ifname, iface = loopback.get_by_lcp_name(self.cfg, "loop56789012345") @@ -20,7 +21,7 @@ class TestLoopbackMethods(unittest.TestCase): ifname, iface = loopback.get_by_name(self.cfg, "loop1") self.assertIsNotNone(iface) self.assertEqual("loop1", ifname) - self.assertEqual(iface['mtu'], 2000) + self.assertEqual(iface["mtu"], 2000) ifname, iface = loopback.get_by_name(self.cfg, "loop-noexist") self.assertIsNone(ifname) diff --git a/config/test_mac.py b/config/test_mac.py index f9b194a..89773dd 100644 --- a/config/test_mac.py +++ b/config/test_mac.py @@ -1,6 +1,7 @@ import unittest import config.mac as mac + class TestMACMethods(unittest.TestCase): def test_is_valid(self): self.assertTrue(mac.is_valid("00:01:02:03:04:05")) diff --git a/config/test_tap.py b/config/test_tap.py index df3015d..5af3e4f 100644 --- a/config/test_tap.py +++ b/config/test_tap.py @@ -2,10 +2,11 @@ import unittest import yaml import config.tap as tap + class TestTAPMethods(unittest.TestCase): def setUp(self): with open("unittest/test_tap.yaml", "r") as f: - self.cfg = yaml.load(f, Loader = yaml.FullLoader) + self.cfg = yaml.load(f, Loader=yaml.FullLoader) def test_get_by_name(self): ifname, iface = tap.get_by_name(self.cfg, "tap0") diff --git a/config/test_vxlan_tunnel.py b/config/test_vxlan_tunnel.py index 835f0c1..2a1e7de 100644 --- a/config/test_vxlan_tunnel.py +++ b/config/test_vxlan_tunnel.py @@ -2,10 +2,11 @@ import unittest import yaml import config.vxlan_tunnel as vxlan_tunnel + class TestVXLANMethods(unittest.TestCase): def setUp(self): with open("unittest/test_vxlan_tunnel.yaml", "r") as f: - self.cfg = yaml.load(f, Loader = yaml.FullLoader) + self.cfg = yaml.load(f, Loader=yaml.FullLoader) def test_get_by_name(self): ifname, iface = vxlan_tunnel.get_by_name(self.cfg, "vxlan_tunnel0") diff --git a/config/vxlan_tunnel.py b/config/vxlan_tunnel.py index 5d50325..3e7e1bd 100644 --- a/config/vxlan_tunnel.py +++ b/config/vxlan_tunnel.py @@ -15,42 +15,43 @@ import logging import config.interface as interface import ipaddress + def get_by_name(yaml, ifname): - """ Return the VXLAN by name, if it exists. Return None otherwise. """ + """Return the VXLAN by name, if it exists. Return None otherwise.""" try: - if ifname in yaml['vxlan_tunnels']: - return ifname, yaml['vxlan_tunnels'][ifname] + if ifname in yaml["vxlan_tunnels"]: + return ifname, yaml["vxlan_tunnels"][ifname] except: pass return None, None def is_vxlan_tunnel(yaml, ifname): - """ Returns True if the interface name is an existing VXLAN Tunnel. """ + """Returns True if the interface name is an existing VXLAN Tunnel.""" ifname, iface = get_by_name(yaml, ifname) return not iface == None def vni_unique(yaml, vni): - """ Return True if the VNI is unique amongst all VXLANs """ - if not 'vxlan_tunnels' in yaml: + """Return True if the VNI is unique amongst all VXLANs""" + if not "vxlan_tunnels" in yaml: return True ncount = 0 - for ifname, iface in yaml['vxlan_tunnels'].items(): - if iface['vni'] == vni: + for ifname, iface in yaml["vxlan_tunnels"].items(): + if iface["vni"] == vni: ncount = ncount + 1 return ncount < 2 def get_vxlan_tunnels(yaml): - """ Returns a list of all VXLAN tunnel interface names. """ + """Returns a list of all VXLAN tunnel interface names.""" ret = [] - if not 'vxlan_tunnels' in yaml: + if not "vxlan_tunnels" in yaml: return ret - for ifname, iface in yaml['vxlan_tunnels'].items(): + for ifname, iface in yaml["vxlan_tunnels"].items(): ret.append(ifname) return ret @@ -58,27 +59,31 @@ def get_vxlan_tunnels(yaml): def validate_vxlan_tunnels(yaml): result = True msgs = [] - logger = logging.getLogger('vppcfg.config') + logger = logging.getLogger("vppcfg.config") logger.addHandler(logging.NullHandler()) - if not 'vxlan_tunnels' in yaml: + if not "vxlan_tunnels" in yaml: return result, msgs - for ifname, iface in yaml['vxlan_tunnels'].items(): + for ifname, iface in yaml["vxlan_tunnels"].items(): logger.debug(f"vxlan_tunnel {ifname}: {iface}") instance = int(ifname[12:]) if instance > 2147483647: - msgs.append(f"vxlan_tunnel {ifname} has instance {int(instance)} which is too large") + msgs.append( + f"vxlan_tunnel {ifname} has instance {int(instance)} which is too large" + ) result = False - vni = iface['vni'] + vni = iface["vni"] if not vni_unique(yaml, vni): msgs.append(f"vxlan_tunnel {ifname} VNI {int(vni)} is not unique") result = False - local = ipaddress.ip_address(iface['local']) - remote = ipaddress.ip_address(iface['remote']) + local = ipaddress.ip_address(iface["local"]) + remote = ipaddress.ip_address(iface["remote"]) if local.version != remote.version: - msgs.append(f"vxlan_tunnel {ifname} local and remote are not the same address family") + msgs.append( + f"vxlan_tunnel {ifname} local and remote are not the same address family" + ) result = False return result, msgs diff --git a/tests.py b/tests.py index 6771e3f..4195a55 100755 --- a/tests.py +++ b/tests.py @@ -27,15 +27,17 @@ except ImportError: print("ERROR: install argparse manually: sudo pip install argparse") sys.exit(-2) + def example_validator(yaml): - """ A simple example validator that takes the YAML configuration file as an input, + """A simple example validator that takes the YAML configuration file as an input, and returns a tuple of rv (return value, True is success), and a list of string - messages to the validation framework. """ + messages to the validation framework.""" rv = True msgs = [] return rv, msgs + class YAMLTest(unittest.TestCase): def __init__(self, testName, yaml_filename, yaml_schema): # calling the super class init varies for different python versions. This works for 2.7 @@ -46,19 +48,19 @@ class YAMLTest(unittest.TestCase): def test_yaml(self): unittest = None cfg = None - n=0 + n = 0 try: with open(self.yaml_filename, "r") as f: for data in yaml.load_all(f, Loader=yaml.Loader): - if n==0: + if n == 0: unittest = data n = n + 1 - elif n==1: + elif n == 1: cfg = data n = n + 1 except: pass - self.assertEqual(n,2) + self.assertEqual(n, 2) self.assertIsNotNone(unittest) if not cfg: return @@ -68,8 +70,12 @@ class YAMLTest(unittest.TestCase): msgs_unexpected = 0 msgs_expected = [] - if 'test' in unittest and 'errors' in unittest['test'] and 'expected' in unittest['test']['errors']: - msgs_expected = unittest['test']['errors']['expected'] + if ( + "test" in unittest + and "errors" in unittest["test"] + and "expected" in unittest["test"]["errors"] + ): + msgs_expected = unittest["test"]["errors"]["expected"] fail = False for m in msgs: @@ -83,11 +89,18 @@ class YAMLTest(unittest.TestCase): fail = True count = 0 - if 'test' in unittest and 'errors' in unittest['test'] and 'count' in unittest['test']['errors']: - count = unittest['test']['errors']['count'] + if ( + "test" in unittest + and "errors" in unittest["test"] + and "count" in unittest["test"]["errors"] + ): + count = unittest["test"]["errors"]["count"] if len(msgs) != count: - print(f"{self.yaml_filename}: Unexpected error count {len(msgs)} (expecting {int(count)})", file=sys.stderr) + print( + f"{self.yaml_filename}: Unexpected error count {len(msgs)} (expecting {int(count)})", + file=sys.stderr, + ) self.assertEqual(len(msgs), count) self.assertFalse(fail) @@ -96,27 +109,63 @@ class YAMLTest(unittest.TestCase): if __name__ == "__main__": parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter) - parser.add_argument('-t', '--test', dest='test', type=str, nargs='+', default=['unittest/yaml/*.yaml'], help="""YAML test file(s)""") - parser.add_argument('-s', '--schema', dest='schema', type=str, default='./schema.yaml', help="""YAML schema validation file""") - parser.add_argument('-d', '--debug', dest='debug', action='store_true', help="""Enable debug, default False""") - parser.add_argument('-q', '--quiet', dest='quiet', action='store_true', help="""Be quiet (only log warnings/errors), default False""") - + parser.add_argument( + "-t", + "--test", + dest="test", + type=str, + nargs="+", + default=["unittest/yaml/*.yaml"], + help="""YAML test file(s)""", + ) + parser.add_argument( + "-s", + "--schema", + dest="schema", + type=str, + default="./schema.yaml", + help="""YAML schema validation file""", + ) + parser.add_argument( + "-d", + "--debug", + dest="debug", + action="store_true", + help="""Enable debug, default False""", + ) + parser.add_argument( + "-q", + "--quiet", + dest="quiet", + action="store_true", + help="""Be quiet (only log warnings/errors), default False""", + ) args = parser.parse_args() if args.debug: - verbosity=2 + verbosity = 2 elif args.quiet: - verbosity=0 + verbosity = 0 else: - verbosity=1 + verbosity = 1 yaml_suite = unittest.TestSuite() for pattern in args.test: for fn in glob.glob(pattern): - yaml_suite.addTest(YAMLTest('test_yaml', yaml_filename=fn, yaml_schema=args.schema)) - yaml_ok = unittest.TextTestRunner(verbosity=verbosity, buffer=True).run(yaml_suite).wasSuccessful() + yaml_suite.addTest( + YAMLTest("test_yaml", yaml_filename=fn, yaml_schema=args.schema) + ) + yaml_ok = ( + unittest.TextTestRunner(verbosity=verbosity, buffer=True) + .run(yaml_suite) + .wasSuccessful() + ) - tests = unittest.TestLoader().discover(start_dir=".", pattern='test_*.py') - unit_ok = unittest.TextTestRunner(verbosity=verbosity, buffer=True).run(tests).wasSuccessful() + tests = unittest.TestLoader().discover(start_dir=".", pattern="test_*.py") + unit_ok = ( + unittest.TextTestRunner(verbosity=verbosity, buffer=True) + .run(tests) + .wasSuccessful() + ) retval = 0 if not yaml_ok: diff --git a/vpp/dumper.py b/vpp/dumper.py index 2a5bf55..6dbca4b 100644 --- a/vpp/dumper.py +++ b/vpp/dumper.py @@ -1,23 +1,24 @@ -''' +""" The functions in this file interact with the VPP API to retrieve certain interface metadata and write it to a YAML file. -''' +""" from vpp.vppapi import VPPApi import sys import yaml import config.bondethernet as bondethernet + class Dumper(VPPApi): - def __init__(self, address='/run/vpp/api.sock', clientname='vppcfg'): + def __init__(self, address="/run/vpp/api.sock", clientname="vppcfg"): VPPApi.__init__(self, address, clientname) def write(self, outfile): - if outfile and outfile == '-': + if outfile and outfile == "-": fh = sys.stdout outfile = "(stdout)" else: - fh = open(outfile, 'w') + fh = open(outfile, "w") config = self.cache_to_config() @@ -28,140 +29,187 @@ class Dumper(VPPApi): self.logger.info(f"Wrote YAML config to {outfile}") def cache_to_config(self): - config = {"loopbacks": {}, "bondethernets": {}, "interfaces": {}, "bridgedomains": {}, "vxlan_tunnels": {}, "taps": {} } - for idx, bond_iface in self.cache['bondethernets'].items(): + config = { + "loopbacks": {}, + "bondethernets": {}, + "interfaces": {}, + "bridgedomains": {}, + "vxlan_tunnels": {}, + "taps": {}, + } + for idx, bond_iface in self.cache["bondethernets"].items(): bond = {"description": ""} - if bond_iface.sw_if_index in self.cache['bondethernet_members']: - members = [self.cache['interfaces'][x].interface_name for x in self.cache['bondethernet_members'][bond_iface.sw_if_index]] + if bond_iface.sw_if_index in self.cache["bondethernet_members"]: + members = [ + self.cache["interfaces"][x].interface_name + for x in self.cache["bondethernet_members"][bond_iface.sw_if_index] + ] if len(members) > 0: - bond['interfaces'] = members + bond["interfaces"] = members mode = bondethernet.int_to_mode(bond_iface.mode) - bond['mode'] = mode - if mode in ['xor', 'lacp']: - bond['load-balance'] = bondethernet.int_to_lb(bond_iface.lb) - iface = self.cache['interfaces'][bond_iface.sw_if_index] - bond['mac'] = str(iface.l2_address) - config['bondethernets'][iface.interface_name] = bond + bond["mode"] = mode + if mode in ["xor", "lacp"]: + bond["load-balance"] = bondethernet.int_to_lb(bond_iface.lb) + iface = self.cache["interfaces"][bond_iface.sw_if_index] + bond["mac"] = str(iface.l2_address) + config["bondethernets"][iface.interface_name] = bond - for numtags in [ 0, 1, 2 ]: - for idx, iface in self.cache['interfaces'].items(): + for numtags in [0, 1, 2]: + for idx, iface in self.cache["interfaces"].items(): if iface.sub_number_of_tags != numtags: continue - if iface.interface_dev_type=='Loopback': + if iface.interface_dev_type == "Loopback": if iface.sub_id > 0: - self.logger.warning(f"Refusing to export sub-interfaces of loopback devices ({iface.interface_name})") + self.logger.warning( + f"Refusing to export sub-interfaces of loopback devices ({iface.interface_name})" + ) continue loop = {"description": ""} - loop['mtu'] = iface.mtu[0] - loop['mac'] = str(iface.l2_address) - if iface.sw_if_index in self.cache['lcps']: - loop['lcp'] = self.cache['lcps'][iface.sw_if_index].host_if_name - if iface.sw_if_index in self.cache['interface_addresses']: - if len(self.cache['interface_addresses'][iface.sw_if_index]) > 0: - loop['addresses'] = self.cache['interface_addresses'][iface.sw_if_index] - config['loopbacks'][iface.interface_name] = loop - elif iface.interface_dev_type in ['bond', 'VXLAN', 'dpdk', 'virtio']: - i = {"description": "" } - if iface.sw_if_index in self.cache['lcps']: - i['lcp'] = self.cache['lcps'][iface.sw_if_index].host_if_name - if iface.sw_if_index in self.cache['interface_addresses']: - if len(self.cache['interface_addresses'][iface.sw_if_index]) > 0: - i['addresses'] = self.cache['interface_addresses'][iface.sw_if_index] - if iface.sw_if_index in self.cache['l2xcs']: - l2xc = self.cache['l2xcs'][iface.sw_if_index] - i['l2xc'] = self.cache['interfaces'][l2xc.tx_sw_if_index].interface_name - if not self.cache['interfaces'][idx].flags & 1: # IF_STATUS_API_FLAG_ADMIN_UP - i['state'] = 'down' + loop["mtu"] = iface.mtu[0] + loop["mac"] = str(iface.l2_address) + if iface.sw_if_index in self.cache["lcps"]: + loop["lcp"] = self.cache["lcps"][iface.sw_if_index].host_if_name + if iface.sw_if_index in self.cache["interface_addresses"]: + if ( + len(self.cache["interface_addresses"][iface.sw_if_index]) + > 0 + ): + loop["addresses"] = self.cache["interface_addresses"][ + iface.sw_if_index + ] + config["loopbacks"][iface.interface_name] = loop + elif iface.interface_dev_type in ["bond", "VXLAN", "dpdk", "virtio"]: + i = {"description": ""} + if iface.sw_if_index in self.cache["lcps"]: + i["lcp"] = self.cache["lcps"][iface.sw_if_index].host_if_name + if iface.sw_if_index in self.cache["interface_addresses"]: + if ( + len(self.cache["interface_addresses"][iface.sw_if_index]) + > 0 + ): + i["addresses"] = self.cache["interface_addresses"][ + iface.sw_if_index + ] + if iface.sw_if_index in self.cache["l2xcs"]: + l2xc = self.cache["l2xcs"][iface.sw_if_index] + i["l2xc"] = self.cache["interfaces"][ + l2xc.tx_sw_if_index + ].interface_name + if ( + not self.cache["interfaces"][idx].flags & 1 + ): # IF_STATUS_API_FLAG_ADMIN_UP + i["state"] = "down" - if iface.interface_dev_type == 'dpdk' and iface.sub_number_of_tags == 0: - i['mac'] = str(iface.l2_address) + if ( + iface.interface_dev_type == "dpdk" + and iface.sub_number_of_tags == 0 + ): + i["mac"] = str(iface.l2_address) if self.tap_is_lcp(iface.interface_name): continue - i['mtu'] = iface.mtu[0] + i["mtu"] = iface.mtu[0] if iface.sub_number_of_tags == 0: - config['interfaces'][iface.interface_name] = i + config["interfaces"][iface.interface_name] = i continue - + encap = {} - if iface.sub_if_flags&8: - encap['dot1ad'] = iface.sub_outer_vlan_id + if iface.sub_if_flags & 8: + encap["dot1ad"] = iface.sub_outer_vlan_id else: - encap['dot1q'] = iface.sub_outer_vlan_id + encap["dot1q"] = iface.sub_outer_vlan_id if iface.sub_inner_vlan_id > 0: - encap['inner-dot1q'] = iface.sub_inner_vlan_id - encap['exact-match'] = bool(iface.sub_if_flags&16) - i['encapsulation'] = encap + encap["inner-dot1q"] = iface.sub_inner_vlan_id + encap["exact-match"] = bool(iface.sub_if_flags & 16) + i["encapsulation"] = encap - sup_iface = self.cache['interfaces'][iface.sup_sw_if_index] + sup_iface = self.cache["interfaces"][iface.sup_sw_if_index] if iface.mtu[0] > 0: - i['mtu'] = iface.mtu[0] + i["mtu"] = iface.mtu[0] else: - i['mtu'] = sup_iface.mtu[0] - if not 'sub-interfaces' in config['interfaces'][sup_iface.interface_name]: - config['interfaces'][sup_iface.interface_name]['sub-interfaces'] = {} - config['interfaces'][sup_iface.interface_name]['sub-interfaces'][iface.sub_id] = i + i["mtu"] = sup_iface.mtu[0] + if ( + not "sub-interfaces" + in config["interfaces"][sup_iface.interface_name] + ): + config["interfaces"][sup_iface.interface_name][ + "sub-interfaces" + ] = {} + config["interfaces"][sup_iface.interface_name]["sub-interfaces"][ + iface.sub_id + ] = i - for idx, iface in self.cache['vxlan_tunnels'].items(): - vpp_iface = self.cache['interfaces'][iface.sw_if_index] - vxlan = { "description": "", - "vni": int(iface.vni), - "local": str(iface.src_address), - "remote": str(iface.dst_address) } - config['vxlan_tunnels'][vpp_iface.interface_name] = vxlan + for idx, iface in self.cache["vxlan_tunnels"].items(): + vpp_iface = self.cache["interfaces"][iface.sw_if_index] + vxlan = { + "description": "", + "vni": int(iface.vni), + "local": str(iface.src_address), + "remote": str(iface.dst_address), + } + config["vxlan_tunnels"][vpp_iface.interface_name] = vxlan - for idx, iface in self.cache['taps'].items(): - vpp_tap = self.cache['taps'][iface.sw_if_index] - vpp_iface = self.cache['interfaces'][vpp_tap.sw_if_index] + for idx, iface in self.cache["taps"].items(): + vpp_tap = self.cache["taps"][iface.sw_if_index] + vpp_iface = self.cache["interfaces"][vpp_tap.sw_if_index] if self.tap_is_lcp(vpp_iface.interface_name): continue - tap = { "description": "", - "tx-ring-size": vpp_tap.tx_ring_sz, - "rx-ring-size": vpp_tap.rx_ring_sz, - "host": { - "mac": str(vpp_tap.host_mac_addr), - "name": vpp_tap.host_if_name, - } } + tap = { + "description": "", + "tx-ring-size": vpp_tap.tx_ring_sz, + "rx-ring-size": vpp_tap.rx_ring_sz, + "host": { + "mac": str(vpp_tap.host_mac_addr), + "name": vpp_tap.host_if_name, + }, + } if vpp_tap.host_mtu_size > 0: - tap['host']['mtu'] = vpp_tap.host_mtu_size + tap["host"]["mtu"] = vpp_tap.host_mtu_size if vpp_tap.host_namespace: - tap['host']['namespace'] = vpp_tap.host_namespace + tap["host"]["namespace"] = vpp_tap.host_namespace if vpp_tap.host_bridge: - tap['host']['bridge'] = vpp_tap.host_bridge - config['taps'][vpp_iface.interface_name] = tap + tap["host"]["bridge"] = vpp_tap.host_bridge + config["taps"][vpp_iface.interface_name] = tap - for idx, iface in self.cache['bridgedomains'].items(): + for idx, iface in self.cache["bridgedomains"].items(): bridge_name = f"bd{int(idx)}" mtu = 1500 bridge = {"description": ""} settings = {} - settings['learn'] = iface.learn - settings['unicast-flood'] = iface.flood - settings['unknown-unicast-flood'] = iface.uu_flood - settings['unicast-forward'] = iface.forward - settings['arp-termination'] = iface.arp_term - settings['arp-unicast-forward'] = iface.arp_ufwd - settings['mac-age-minutes'] = int(iface.mac_age) - bridge['settings'] = settings + settings["learn"] = iface.learn + settings["unicast-flood"] = iface.flood + settings["unknown-unicast-flood"] = iface.uu_flood + settings["unicast-forward"] = iface.forward + settings["arp-termination"] = iface.arp_term + settings["arp-unicast-forward"] = iface.arp_ufwd + settings["mac-age-minutes"] = int(iface.mac_age) + bridge["settings"] = settings bvi = None - if iface.bvi_sw_if_index != 2**32-1: - bvi = self.cache['interfaces'][iface.bvi_sw_if_index] + if iface.bvi_sw_if_index != 2**32 - 1: + bvi = self.cache["interfaces"][iface.bvi_sw_if_index] mtu = bvi.mtu[0] - bridge['bvi'] = bvi.interface_name + bridge["bvi"] = bvi.interface_name members = [] for member in iface.sw_if_details: - if bvi and bvi.interface_name == self.cache['interfaces'][member.sw_if_index].interface_name == bvi.interface_name: + if ( + bvi + and bvi.interface_name + == self.cache["interfaces"][member.sw_if_index].interface_name + == bvi.interface_name + ): continue - members.append(self.cache['interfaces'][member.sw_if_index].interface_name) - mtu = self.cache['interfaces'][member.sw_if_index].mtu[0] + members.append( + self.cache["interfaces"][member.sw_if_index].interface_name + ) + mtu = self.cache["interfaces"][member.sw_if_index].mtu[0] if len(members) > 0: - bridge['interfaces'] = members - bridge['mtu'] = mtu - config['bridgedomains'][bridge_name] = bridge + bridge["interfaces"] = members + bridge["mtu"] = mtu + config["bridgedomains"][bridge_name] = bridge return config diff --git a/vpp/reconciler.py b/vpp/reconciler.py index 9d65dbe..60460db 100644 --- a/vpp/reconciler.py +++ b/vpp/reconciler.py @@ -24,40 +24,41 @@ import config.lcp as lcp import config.tap as tap from vpp.vppapi import VPPApi -class Reconciler(): + +class Reconciler: def __init__(self, cfg): - self.logger = logging.getLogger('vppcfg.reconciler') + self.logger = logging.getLogger("vppcfg.reconciler") self.logger.addHandler(logging.NullHandler()) self.vpp = VPPApi() self.cfg = cfg ## List of CLI calls emitted during the prune, create and sync phases. - self.cli={ "prune": [], "create": [], "sync": [] } + self.cli = {"prune": [], "create": [], "sync": []} def __del__(self): self.vpp.disconnect() def lcps_exist_with_lcp_enabled(self): - """ Returns False if there are LCPs defined in the configuration, but LinuxCP - functionality is not enabled in VPP. """ + """Returns False if there are LCPs defined in the configuration, but LinuxCP + functionality is not enabled in VPP.""" if not lcp.get_lcps(self.cfg): return True return self.vpp.lcp_enabled def phys_exist_in_vpp(self): - """ Return True if all PHYs in the config exist as physical interface names + """Return True if all PHYs in the config exist as physical interface names in VPP. Return False otherwise.""" ret = True for ifname in interface.get_phys(self.cfg): - if not ifname in self.vpp.cache['interface_names']: + if not ifname in self.vpp.cache["interface_names"]: self.logger.warning(f"Interface {ifname} does not exist in VPP") ret = False return ret def phys_exist_in_config(self): - """ Return True if all interfaces in VPP exist as physical interface names + """Return True if all interfaces in VPP exist as physical interface names in the config. Return False otherwise.""" ret = True @@ -74,9 +75,9 @@ class Reconciler(): return True def prune(self): - """ Remove all objects from VPP that do not occur in the config. For an indepth explanation - of how and why this particular pruning order is chosen, see README.md section on - Reconciling. """ + """Remove all objects from VPP that do not occur in the config. For an indepth explanation + of how and why this particular pruning order is chosen, see README.md section on + Reconciling.""" ret = True if not self.prune_admin_state(): self.logger.warning("Could not set interfaces down in VPP") @@ -111,46 +112,48 @@ class Reconciler(): return ret def prune_addresses(self, ifname, address_list): - """ Remove all addresses from interface ifname, except those in address_list, - which may be an empty list, in which case all addresses are removed. + """Remove all addresses from interface ifname, except those in address_list, + which may be an empty list, in which case all addresses are removed. """ - idx = self.vpp.cache['interface_names'][ifname].sw_if_index + idx = self.vpp.cache["interface_names"][ifname].sw_if_index removed_addresses = [] - for a in self.vpp.cache['interface_addresses'][idx]: + for a in self.vpp.cache["interface_addresses"][idx]: if not a in address_list: - cli=f"set interface ip address del {ifname} {a}" - self.cli['prune'].append(cli); + cli = f"set interface ip address del {ifname} {a}" + self.cli["prune"].append(cli) removed_addresses.append(a) else: self.logger.debug(f"Address OK: {ifname} {a}") for a in removed_addresses: - self.vpp.cache['interface_addresses'][idx].remove(a) + self.vpp.cache["interface_addresses"][idx].remove(a) def prune_loopbacks(self): - """ Remove loopbacks from VPP, if they do not occur in the config. """ - removed_interfaces=[] - for numtags in [ 2, 1, 0 ]: - for idx, vpp_iface in self.vpp.cache['interfaces'].items(): - if vpp_iface.interface_dev_type!='Loopback': + """Remove loopbacks from VPP, if they do not occur in the config.""" + removed_interfaces = [] + for numtags in [2, 1, 0]: + for idx, vpp_iface in self.vpp.cache["interfaces"].items(): + if vpp_iface.interface_dev_type != "Loopback": continue if vpp_iface.sub_number_of_tags != numtags: continue - config_ifname, config_iface = loopback.get_by_name(self.cfg, vpp_iface.interface_name) + config_ifname, config_iface = loopback.get_by_name( + self.cfg, vpp_iface.interface_name + ) if not config_iface: self.prune_addresses(vpp_iface.interface_name, []) if numtags == 0: - cli=f"delete loopback interface intfc {vpp_iface.interface_name}" - self.cli['prune'].append(cli); + cli = f"delete loopback interface intfc {vpp_iface.interface_name}" + self.cli["prune"].append(cli) removed_interfaces.append(vpp_iface.interface_name) else: - cli=f"delete sub {vpp_iface.interface_name}" - self.cli['prune'].append(cli); + cli = f"delete sub {vpp_iface.interface_name}" + self.cli["prune"].append(cli) removed_interfaces.append(vpp_iface.interface_name) continue self.logger.debug(f"Loopback OK: {vpp_iface.interface_name}") addresses = [] - if 'addresses' in config_iface: - addresses = config_iface['addresses'] + if "addresses" in config_iface: + addresses = config_iface["addresses"] self.prune_addresses(vpp_iface.interface_name, addresses) for ifname in removed_interfaces: @@ -158,11 +161,10 @@ class Reconciler(): return True - def prune_bridgedomains(self): - """ Remove bridge-domains from VPP, if they do not occur in the config. If any interfaces are - found in to-be removed bridge-domains, they are returned to L3 mode, and tag-rewrites removed. """ - for idx, bridge in self.vpp.cache['bridgedomains'].items(): + """Remove bridge-domains from VPP, if they do not occur in the config. If any interfaces are + found in to-be removed bridge-domains, they are returned to L3 mode, and tag-rewrites removed.""" + for idx, bridge in self.vpp.cache["bridgedomains"].items(): bridgename = f"bd{int(idx)}" config_ifname, config_iface = bridgedomain.get_by_name(self.cfg, bridgename) members = [] @@ -170,69 +172,89 @@ class Reconciler(): for member in bridge.sw_if_details: if member.sw_if_index == bridge.bvi_sw_if_index: continue - member_iface = self.vpp.cache['interfaces'][member.sw_if_index] + member_iface = self.vpp.cache["interfaces"][member.sw_if_index] member_ifname = member_iface.interface_name if member_iface.sub_id > 0: - cli=f"set interface l2 tag-rewrite {member_ifname} disable" - self.cli['prune'].append(cli); - cli=f"set interface l3 {member_ifname}" - self.cli['prune'].append(cli); - if bridge.bvi_sw_if_index in self.vpp.cache['interfaces']: - bviname = self.vpp.cache['interfaces'][bridge.bvi_sw_if_index].interface_name - cli=f"set interface l3 {bviname}" - self.cli['prune'].append(cli); - cli=f"create bridge-domain {int(idx)} del" - self.cli['prune'].append(cli); + cli = f"set interface l2 tag-rewrite {member_ifname} disable" + self.cli["prune"].append(cli) + cli = f"set interface l3 {member_ifname}" + self.cli["prune"].append(cli) + if bridge.bvi_sw_if_index in self.vpp.cache["interfaces"]: + bviname = self.vpp.cache["interfaces"][ + bridge.bvi_sw_if_index + ].interface_name + cli = f"set interface l3 {bviname}" + self.cli["prune"].append(cli) + cli = f"create bridge-domain {int(idx)} del" + self.cli["prune"].append(cli) else: self.logger.debug(f"BridgeDomain OK: {bridgename}") for member in bridge.sw_if_details: - member_ifname = self.vpp.cache['interfaces'][member.sw_if_index].interface_name - if 'members' in config_iface and member_ifname in config_iface['members']: + member_ifname = self.vpp.cache["interfaces"][ + member.sw_if_index + ].interface_name + if ( + "members" in config_iface + and member_ifname in config_iface["members"] + ): if interface.is_sub(self.cfg, member_ifname): - cli=f"set interface l2 tag-rewrite {member_ifname} disable" - self.cli['prune'].append(cli); - cli=f"set interface l3 {member_ifname}" - self.cli['prune'].append(cli); - if 'bvi' in config_iface and bridge.bvi_sw_if_index in self.vpp.cache['interfaces']: - bviname = self.vpp.cache['interfaces'][bridge.bvi_sw_if_index].interface_name - if bviname != config_iface['bvi']: - cli=f"set interface l3 {bviname}" - self.cli['prune'].append(cli); + cli = ( + f"set interface l2 tag-rewrite {member_ifname} disable" + ) + self.cli["prune"].append(cli) + cli = f"set interface l3 {member_ifname}" + self.cli["prune"].append(cli) + if ( + "bvi" in config_iface + and bridge.bvi_sw_if_index in self.vpp.cache["interfaces"] + ): + bviname = self.vpp.cache["interfaces"][ + bridge.bvi_sw_if_index + ].interface_name + if bviname != config_iface["bvi"]: + cli = f"set interface l3 {bviname}" + self.cli["prune"].append(cli) return True def prune_l2xcs(self): - """ Remove all L2XC source interfaces from VPP, if they do not occur in the config. If they occur, - but are crossconnected to a different interface name, also remove them. Interfaces are put - back into L3 mode, and their tag-rewrites removed. """ - removed_l2xcs=[] - for idx, l2xc in self.vpp.cache['l2xcs'].items(): - vpp_rx_ifname = self.vpp.cache['interfaces'][l2xc.rx_sw_if_index].interface_name - config_rx_ifname, config_rx_iface = interface.get_by_name(self.cfg, vpp_rx_ifname) + """Remove all L2XC source interfaces from VPP, if they do not occur in the config. If they occur, + but are crossconnected to a different interface name, also remove them. Interfaces are put + back into L3 mode, and their tag-rewrites removed.""" + removed_l2xcs = [] + for idx, l2xc in self.vpp.cache["l2xcs"].items(): + vpp_rx_ifname = self.vpp.cache["interfaces"][ + l2xc.rx_sw_if_index + ].interface_name + config_rx_ifname, config_rx_iface = interface.get_by_name( + self.cfg, vpp_rx_ifname + ) if not config_rx_ifname: - if self.vpp.cache['interfaces'][l2xc.rx_sw_if_index].sub_id > 0: - cli=f"set interface l2 tag-rewrite {vpp_rx_ifname} disable" - self.cli['prune'].append(cli); - cli=f"set interface l3 {vpp_rx_ifname}" - self.cli['prune'].append(cli); + if self.vpp.cache["interfaces"][l2xc.rx_sw_if_index].sub_id > 0: + cli = f"set interface l2 tag-rewrite {vpp_rx_ifname} disable" + self.cli["prune"].append(cli) + cli = f"set interface l3 {vpp_rx_ifname}" + self.cli["prune"].append(cli) removed_l2xcs.append(vpp_rx_ifname) continue if not interface.is_l2xc_interface(self.cfg, config_rx_ifname): if interface.is_sub(self.cfg, config_rx_ifname): - cli=f"set interface l2 tag-rewrite {vpp_rx_ifname} disable" - self.cli['prune'].append(cli); - cli=f"set interface l3 {vpp_rx_ifname}" - self.cli['prune'].append(cli); + cli = f"set interface l2 tag-rewrite {vpp_rx_ifname} disable" + self.cli["prune"].append(cli) + cli = f"set interface l3 {vpp_rx_ifname}" + self.cli["prune"].append(cli) removed_l2xcs.append(vpp_rx_ifname) continue - vpp_tx_ifname = self.vpp.cache['interfaces'][l2xc.tx_sw_if_index].interface_name - if vpp_tx_ifname != config_rx_iface['l2xc']: + vpp_tx_ifname = self.vpp.cache["interfaces"][ + l2xc.tx_sw_if_index + ].interface_name + if vpp_tx_ifname != config_rx_iface["l2xc"]: if interface.is_sub(self.cfg, config_rx_ifname): - cli=f"set interface l2 tag-rewrite {vpp_rx_ifname} disable" - self.cli['prune'].append(cli); - cli=f"set interface l3 {vpp_rx_ifname}" - self.cli['prune'].append(cli); + cli = f"set interface l2 tag-rewrite {vpp_rx_ifname} disable" + self.cli["prune"].append(cli) + cli = f"set interface l3 {vpp_rx_ifname}" + self.cli["prune"].append(cli) removed_l2xcs.append(vpp_rx_ifname) continue self.logger.debug(f"L2XC OK: {vpp_rx_ifname} -> {vpp_tx_ifname}") @@ -241,41 +263,41 @@ class Reconciler(): return True def __vxlan_tunnel_has_diff(self, ifname): - """ Returns True if the given ifname (vxlan_tunnel0) has different attributes between VPP - and the given configuration, or if either does not exist. + """Returns True if the given ifname (vxlan_tunnel0) has different attributes between VPP + and the given configuration, or if either does not exist. - Returns False if they are identical.""" + Returns False if they are identical.""" - if not ifname in self.vpp.cache['interface_names']: + if not ifname in self.vpp.cache["interface_names"]: return True - vpp_iface = self.vpp.cache['interface_names'][ifname] + vpp_iface = self.vpp.cache["interface_names"][ifname] - if vpp_iface.sw_if_index not in self.vpp.cache['vxlan_tunnels']: + if vpp_iface.sw_if_index not in self.vpp.cache["vxlan_tunnels"]: return True - vpp_vxlan = self.vpp.cache['vxlan_tunnels'][vpp_iface.sw_if_index] + vpp_vxlan = self.vpp.cache["vxlan_tunnels"][vpp_iface.sw_if_index] config_ifname, config_iface = vxlan_tunnel.get_by_name(self.cfg, ifname) if not config_iface: return True - if config_iface['local'] != str(vpp_vxlan.src_address): + if config_iface["local"] != str(vpp_vxlan.src_address): return True - if config_iface['remote'] != str(vpp_vxlan.dst_address): + if config_iface["remote"] != str(vpp_vxlan.dst_address): return True - if config_iface['vni'] != vpp_vxlan.vni: + if config_iface["vni"] != vpp_vxlan.vni: return True return False def __tap_has_diff(self, ifname): - """ Returns True if the given ifname (tap0) has different attributes between VPP - and the given configuration, or if either does not exist. + """Returns True if the given ifname (tap0) has different attributes between VPP + and the given configuration, or if either does not exist. - Returns False if the TAP is a Linux Control Plane LIP. - Returns False if they are identical.""" - if not ifname in self.vpp.cache['interface_names']: + Returns False if the TAP is a Linux Control Plane LIP. + Returns False if they are identical.""" + if not ifname in self.vpp.cache["interface_names"]: return True - vpp_iface = self.vpp.cache['interface_names'][ifname] - vpp_tap = self.vpp.cache['taps'][vpp_iface.sw_if_index] + vpp_iface = self.vpp.cache["interface_names"][ifname] + vpp_tap = self.vpp.cache["taps"][vpp_iface.sw_if_index] config_ifname, config_iface = tap.get_by_name(self.cfg, ifname) if not config_iface: @@ -284,37 +306,51 @@ class Reconciler(): if self.vpp.tap_is_lcp(ifname): return False - if 'name' in config_iface['host'] and config_iface['host']['name'] != vpp_tap.host_if_name: + if ( + "name" in config_iface["host"] + and config_iface["host"]["name"] != vpp_tap.host_if_name + ): return True - if 'mtu' in config_iface['host'] and config_iface['host']['mtu'] != vpp_tap.host_mtu_size: + if ( + "mtu" in config_iface["host"] + and config_iface["host"]["mtu"] != vpp_tap.host_mtu_size + ): return True - if 'mac' in config_iface['host'] and config_iface['host']['mac'] != str(vpp_tap.host_mac_addr): + if "mac" in config_iface["host"] and config_iface["host"]["mac"] != str( + vpp_tap.host_mac_addr + ): return True - if 'bridge' in config_iface['host'] and config_iface['host']['bridge'] != vpp_tap.host_bridge: + if ( + "bridge" in config_iface["host"] + and config_iface["host"]["bridge"] != vpp_tap.host_bridge + ): return True - if 'namespace' in config_iface['host'] and config_iface['host']['namespace'] != vpp_tap.host_namespace: + if ( + "namespace" in config_iface["host"] + and config_iface["host"]["namespace"] != vpp_tap.host_namespace + ): return True return False def __bond_has_diff(self, ifname): - """ Returns True if the given ifname (BondEthernet0) have different attributes, - or if either does not exist. + """Returns True if the given ifname (BondEthernet0) have different attributes, + or if either does not exist. - Returns False if they are identical. - """ - if not ifname in self.vpp.cache['interface_names']: + Returns False if they are identical. + """ + if not ifname in self.vpp.cache["interface_names"]: return True - vpp_iface = self.vpp.cache['interface_names'][ifname] - if not vpp_iface.sw_if_index in self.vpp.cache['bondethernets']: + vpp_iface = self.vpp.cache["interface_names"][ifname] + if not vpp_iface.sw_if_index in self.vpp.cache["bondethernets"]: return True config_ifname, config_iface = bondethernet.get_by_name(self.cfg, ifname) if not config_iface: return True - vpp_bond = self.vpp.cache['bondethernets'][vpp_iface.sw_if_index] + vpp_bond = self.vpp.cache["bondethernets"][vpp_iface.sw_if_index] mode = bondethernet.mode_to_int(bondethernet.get_mode(self.cfg, config_ifname)) if mode != -1 and mode != vpp_bond.mode: return True @@ -325,12 +361,12 @@ class Reconciler(): return False def prune_taps(self): - """ Remove all TAPs from VPP, if they are not in the config. As an exception, - TAPs which are a part of Linux Control Plane, are left alone, to be handled - by prune_lcps() later. """ + """Remove all TAPs from VPP, if they are not in the config. As an exception, + TAPs which are a part of Linux Control Plane, are left alone, to be handled + by prune_lcps() later.""" removed_taps = [] - for idx, vpp_tap in self.vpp.cache['taps'].items(): - vpp_iface = self.vpp.cache['interfaces'][vpp_tap.sw_if_index] + for idx, vpp_tap in self.vpp.cache["taps"].items(): + vpp_iface = self.vpp.cache["interfaces"][vpp_tap.sw_if_index] vpp_ifname = vpp_iface.interface_name if self.vpp.tap_is_lcp(vpp_ifname): continue @@ -340,41 +376,44 @@ class Reconciler(): self.logger.debug(f"TAP OK: {vpp_ifname}") for ifname in removed_taps: - cli=f"delete tap {ifname}" - self.cli['prune'].append(cli) + cli = f"delete tap {ifname}" + self.cli["prune"].append(cli) self.vpp.cache_remove_interface(ifname) return True def prune_bondethernets(self): - """ Remove all BondEthernets from VPP, if they are not in the config. If the bond has members, - remove those from the bond before removing the bond. """ - removed_interfaces=[] - removed_bondethernet_members=[] - for idx, bond in self.vpp.cache['bondethernets'].items(): + """Remove all BondEthernets from VPP, if they are not in the config. If the bond has members, + remove those from the bond before removing the bond.""" + removed_interfaces = [] + removed_bondethernet_members = [] + for idx, bond in self.vpp.cache["bondethernets"].items(): vpp_ifname = bond.interface_name config_ifname, config_iface = bondethernet.get_by_name(self.cfg, vpp_ifname) if self.__bond_has_diff(vpp_ifname): self.prune_addresses(vpp_ifname, []) - for member in self.vpp.cache['bondethernet_members'][idx]: - member_ifname = self.vpp.cache['interfaces'][member].interface_name - cli=f"bond del {member_ifname}" - self.cli['prune'].append(cli); + for member in self.vpp.cache["bondethernet_members"][idx]: + member_ifname = self.vpp.cache["interfaces"][member].interface_name + cli = f"bond del {member_ifname}" + self.cli["prune"].append(cli) removed_bondethernet_members.append(member_ifname) - cli=f"delete bond {vpp_ifname}" - self.cli['prune'].append(cli); + cli = f"delete bond {vpp_ifname}" + self.cli["prune"].append(cli) removed_interfaces.append(vpp_ifname) continue - for member in self.vpp.cache['bondethernet_members'][idx]: - member_ifname = self.vpp.cache['interfaces'][member].interface_name - if 'interfaces' in config_iface and not member_ifname in config_iface['interfaces']: - cli=f"bond del {member_ifname}" - self.cli['prune'].append(cli); + for member in self.vpp.cache["bondethernet_members"][idx]: + member_ifname = self.vpp.cache["interfaces"][member].interface_name + if ( + "interfaces" in config_iface + and not member_ifname in config_iface["interfaces"] + ): + cli = f"bond del {member_ifname}" + self.cli["prune"].append(cli) removed_bondethernet_members.append(member_ifname) addresses = [] - if 'addresses' in config_iface: - addresses = config_iface['addresses'] + if "addresses" in config_iface: + addresses = config_iface["addresses"] self.prune_addresses(vpp_ifname, addresses) self.logger.debug(f"BondEthernet OK: {vpp_ifname}") @@ -387,30 +426,34 @@ class Reconciler(): return True def prune_vxlan_tunnels(self): - """ Remove all VXLAN Tunnels from VPP, if they are not in the config. If they are in the config - but with differing attributes, remove them also. """ - removed_interfaces=[] - for idx, vpp_vxlan in self.vpp.cache['vxlan_tunnels'].items(): - vpp_ifname = self.vpp.cache['interfaces'][idx].interface_name + """Remove all VXLAN Tunnels from VPP, if they are not in the config. If they are in the config + but with differing attributes, remove them also.""" + removed_interfaces = [] + for idx, vpp_vxlan in self.vpp.cache["vxlan_tunnels"].items(): + vpp_ifname = self.vpp.cache["interfaces"][idx].interface_name config_ifname, config_iface = vxlan_tunnel.get_by_name(self.cfg, vpp_ifname) if not config_iface: self.prune_addresses(vpp_ifname, []) - cli=(f"create vxlan tunnel instance {vpp_vxlan.instance} " - f"src {vpp_vxlan.src_address} dst {vpp_vxlan.dst_address} vni {vpp_vxlan.vni} del") - self.cli['prune'].append(cli); + cli = ( + f"create vxlan tunnel instance {vpp_vxlan.instance} " + f"src {vpp_vxlan.src_address} dst {vpp_vxlan.dst_address} vni {vpp_vxlan.vni} del" + ) + self.cli["prune"].append(cli) removed_interfaces.append(vpp_ifname) continue if self.__vxlan_tunnel_has_diff(config_ifname): - cli=(f"create vxlan tunnel instance {vpp_vxlan.instance} " - f"src {vpp_vxlan.src_address} dst {vpp_vxlan.dst_address} vni {vpp_vxlan.vni} del") - self.cli['prune'].append(cli); + cli = ( + f"create vxlan tunnel instance {vpp_vxlan.instance} " + f"src {vpp_vxlan.src_address} dst {vpp_vxlan.dst_address} vni {vpp_vxlan.vni} del" + ) + self.cli["prune"].append(cli) removed_interfaces.append(vpp_ifname) continue config_ifname, config_iface = interface.get_by_name(self.cfg, vpp_ifname) if config_iface: addresses = [] - if 'addresses' in config_iface: - addresses = config_iface['addresses'] + if "addresses" in config_iface: + addresses = config_iface["addresses"] self.prune_addresses(vpp_ifname, addresses) self.logger.debug(f"VXLAN Tunnel OK: {vpp_ifname}") @@ -421,25 +464,33 @@ class Reconciler(): return True def prune_sub_interfaces(self): - """ Remove interfaces from VPP if they are not in the config, if their encapsulation is different, - or if the BondEthernet they reside on is different. - Start with inner-most (QinQ/QinAD), then Dot1Q/Dot1AD.""" - removed_interfaces=[] - for numtags in [ 2, 1 ]: + """Remove interfaces from VPP if they are not in the config, if their encapsulation is different, + or if the BondEthernet they reside on is different. + Start with inner-most (QinQ/QinAD), then Dot1Q/Dot1AD.""" + removed_interfaces = [] + for numtags in [2, 1]: for vpp_ifname in self.vpp.get_sub_interfaces(): - vpp_iface = self.vpp.cache['interface_names'][vpp_ifname] + vpp_iface = self.vpp.cache["interface_names"][vpp_ifname] if vpp_iface.sub_number_of_tags != numtags: continue if self.vpp.tap_is_lcp(vpp_ifname): continue - prune=False - config_ifname, config_iface = interface.get_by_name(self.cfg, vpp_ifname) + prune = False + config_ifname, config_iface = interface.get_by_name( + self.cfg, vpp_ifname + ) if not config_iface: prune = True - elif vpp_iface.interface_dev_type=='bond' and vpp_iface.sub_number_of_tags > 0: - config_parent_ifname, config_parent_iface = interface.get_parent_by_name(self.cfg, vpp_ifname) + elif ( + vpp_iface.interface_dev_type == "bond" + and vpp_iface.sub_number_of_tags > 0 + ): + ( + config_parent_ifname, + config_parent_iface, + ) = interface.get_parent_by_name(self.cfg, vpp_ifname) if self.__bond_has_diff(config_parent_ifname): prune = True @@ -450,14 +501,14 @@ class Reconciler(): if prune: self.prune_addresses(vpp_ifname, []) - cli=f"delete sub {vpp_ifname}" - self.cli['prune'].append(cli); + cli = f"delete sub {vpp_ifname}" + self.cli["prune"].append(cli) removed_interfaces.append(vpp_ifname) continue addresses = [] - if 'addresses' in config_iface: - addresses = config_iface['addresses'] + if "addresses" in config_iface: + addresses = config_iface["addresses"] self.prune_addresses(vpp_ifname, addresses) self.logger.debug(f"Sub Interface OK: {vpp_ifname}") @@ -467,105 +518,124 @@ class Reconciler(): return True def prune_phys(self): - """ Set default MTU and remove IPs for PHYs that are not in the config. """ + """Set default MTU and remove IPs for PHYs that are not in the config.""" for vpp_ifname in self.vpp.get_phys(): - vpp_iface = self.vpp.cache['interface_names'][vpp_ifname] + vpp_iface = self.vpp.cache["interface_names"][vpp_ifname] config_ifname, config_iface = interface.get_by_name(self.cfg, vpp_ifname) if not config_iface: ## Interfaces were sent DOWN in the prune_admin_state() step previously self.prune_addresses(vpp_ifname, []) if vpp_iface.link_mtu != 9000: - cli=f"set interface mtu 9000 {vpp_ifname}" - self.cli['prune'].append(cli); + cli = f"set interface mtu 9000 {vpp_ifname}" + self.cli["prune"].append(cli) continue addresses = [] - if 'addresses' in config_iface: - addresses = config_iface['addresses'] + if "addresses" in config_iface: + addresses = config_iface["addresses"] self.prune_addresses(vpp_ifname, addresses) self.logger.debug(f"Interface OK: {vpp_ifname}") return True def __parent_iface_by_encap(self, sup_sw_if_index, outer, dot1ad=True): - """ Returns the sw_if_index of an interface on a given super_sw_if_index with given dot1q/dot1ad outer and inner-dot1q=0, - in other words the intermediary Dot1Q/Dot1AD belonging to a QinX interface. If the interface doesn't exist, None is - returned. """ - for idx, iface in self.vpp.cache['interfaces'].items(): + """Returns the sw_if_index of an interface on a given super_sw_if_index with given dot1q/dot1ad outer and inner-dot1q=0, + in other words the intermediary Dot1Q/Dot1AD belonging to a QinX interface. If the interface doesn't exist, None is + returned.""" + for idx, iface in self.vpp.cache["interfaces"].items(): if iface.sup_sw_if_index != sup_sw_if_index: continue if iface.sub_inner_vlan_id > 0: continue - if dot1ad and (iface.sub_if_flags&8) and iface.sub_outer_vlan_id == outer: + if dot1ad and (iface.sub_if_flags & 8) and iface.sub_outer_vlan_id == outer: self.logger.debug(f"match: {iface.interface_name} (dot1ad)") return idx - if not dot1ad and not (iface.sub_if_flags&8) and iface.sub_outer_vlan_id == outer: + if ( + not dot1ad + and not (iface.sub_if_flags & 8) + and iface.sub_outer_vlan_id == outer + ): self.logger.debug(f"match: {iface.interface_name} (dot1q)") return idx return None def __get_encapsulation(self, iface): - """ Return a dictionary-based encapsulation of the sub-interface, which helps comparing them to the same object - returned by config.interface.get_encapsulation(). """ - if iface.sub_if_flags&8: + """Return a dictionary-based encapsulation of the sub-interface, which helps comparing them to the same object + returned by config.interface.get_encapsulation().""" + if iface.sub_if_flags & 8: dot1ad = iface.sub_outer_vlan_id dot1q = 0 else: dot1q = iface.sub_outer_vlan_id dot1ad = 0 inner_dot1q = iface.sub_inner_vlan_id - exact_match = iface.sub_if_flags&16 - return { "dot1q": int(dot1q), - "dot1ad": int(dot1ad), - "inner-dot1q": int(inner_dot1q), - "exact-match": bool(exact_match) } + exact_match = iface.sub_if_flags & 16 + return { + "dot1q": int(dot1q), + "dot1ad": int(dot1ad), + "inner-dot1q": int(inner_dot1q), + "exact-match": bool(exact_match), + } def prune_lcps(self): - """ Remove LCPs which are not in the configuration, starting with QinQ/QinAD interfaces, then Dot1Q/Dot1AD, - and finally PHYs/BondEthernets/Tunnels/Loopbacks. For QinX, special care is taken to ensure that - their intermediary interface exists, and has the correct encalsulation. If the intermediary interface - changed, the QinX LCP is removed. The same is true for Dot1Q/Dot1AD interfaces: if their encapsulation - has changed, we will have to re-create the underlying sub-interface, so the LCP has to be removed. + """Remove LCPs which are not in the configuration, starting with QinQ/QinAD interfaces, then Dot1Q/Dot1AD, + and finally PHYs/BondEthernets/Tunnels/Loopbacks. For QinX, special care is taken to ensure that + their intermediary interface exists, and has the correct encalsulation. If the intermediary interface + changed, the QinX LCP is removed. The same is true for Dot1Q/Dot1AD interfaces: if their encapsulation + has changed, we will have to re-create the underlying sub-interface, so the LCP has to be removed. - Order is important: destroying an LCP of a PHY will invalidate its Dot1Q/Dot1AD as well as their - downstream children in Linux. + Order is important: destroying an LCP of a PHY will invalidate its Dot1Q/Dot1AD as well as their + downstream children in Linux. """ - lcps = self.vpp.cache['lcps'] + lcps = self.vpp.cache["lcps"] removed_lcps = [] - for numtags in [ 2, 1, 0 ]: + for numtags in [2, 1, 0]: for idx, lcp in lcps.items(): - vpp_iface = self.vpp.cache['interfaces'][lcp.phy_sw_if_index] + vpp_iface = self.vpp.cache["interfaces"][lcp.phy_sw_if_index] if vpp_iface.sub_number_of_tags != numtags: continue - if vpp_iface.interface_dev_type=='Loopback': - config_ifname, config_iface = loopback.get_by_lcp_name(self.cfg, lcp.host_if_name) + if vpp_iface.interface_dev_type == "Loopback": + config_ifname, config_iface = loopback.get_by_lcp_name( + self.cfg, lcp.host_if_name + ) else: - config_ifname, config_iface = interface.get_by_lcp_name(self.cfg, lcp.host_if_name) + config_ifname, config_iface = interface.get_by_lcp_name( + self.cfg, lcp.host_if_name + ) if not config_iface: ## Interface doesn't exist in the config removed_lcps.append(lcp) continue - if not 'lcp' in config_iface: + if not "lcp" in config_iface: ## Interface doesn't have an LCP removed_lcps.append(lcp) continue if vpp_iface.sub_number_of_tags == 2: - vpp_parent_idx = self.__parent_iface_by_encap(vpp_iface.sup_sw_if_index, vpp_iface.sub_outer_vlan_id, vpp_iface.sub_if_flags&8) - vpp_parent_iface = self.vpp.cache['interfaces'][vpp_parent_idx] + vpp_parent_idx = self.__parent_iface_by_encap( + vpp_iface.sup_sw_if_index, + vpp_iface.sub_outer_vlan_id, + vpp_iface.sub_if_flags & 8, + ) + vpp_parent_iface = self.vpp.cache["interfaces"][vpp_parent_idx] parent_lcp = lcps[vpp_parent_iface.sw_if_index] - config_parent_ifname, config_parent_iface = interface.get_by_lcp_name(self.cfg, parent_lcp.host_if_name) + ( + config_parent_ifname, + config_parent_iface, + ) = interface.get_by_lcp_name(self.cfg, parent_lcp.host_if_name) if not config_parent_iface: ## QinX's parent doesn't exist in the config removed_lcps.append(lcp) continue - if not 'lcp' in config_parent_iface: + if not "lcp" in config_parent_iface: ## QinX's parent doesn't have an LCP removed_lcps.append(lcp) continue - if parent_lcp.host_if_name != config_parent_iface['lcp']: + if parent_lcp.host_if_name != config_parent_iface["lcp"]: ## QinX's parent LCP name mismatch removed_lcps.append(lcp) continue - config_parent_encap = interface.get_encapsulation(self.cfg, config_parent_ifname) + config_parent_encap = interface.get_encapsulation( + self.cfg, config_parent_ifname + ) vpp_parent_encap = self.__get_encapsulation(vpp_parent_iface) if config_parent_encap != vpp_parent_encap: ## QinX's parent encapsulation mismatch @@ -580,59 +650,74 @@ class Reconciler(): removed_lcps.append(lcp) continue - if vpp_iface.interface_dev_type=='Loopback': + if vpp_iface.interface_dev_type == "Loopback": ## Loopbacks will not have a PHY to check. continue - if vpp_iface.interface_dev_type=='bond': - bond_iface = self.vpp.cache['interfaces'][vpp_iface.sup_sw_if_index] + if vpp_iface.interface_dev_type == "bond": + bond_iface = self.vpp.cache["interfaces"][vpp_iface.sup_sw_if_index] if self.__bond_has_diff(bond_iface.interface_name): ## If BondEthernet changed, it has to be re-created, so all LCPs must be removed. removed_lcps.append(lcp) continue phy_lcp = lcps[vpp_iface.sup_sw_if_index] - config_phy_ifname, config_phy_iface = interface.get_by_lcp_name(self.cfg, phy_lcp.host_if_name) + config_phy_ifname, config_phy_iface = interface.get_by_lcp_name( + self.cfg, phy_lcp.host_if_name + ) if not config_phy_iface: ## Phy doesn't exist in the config removed_lcps.append(lcp) continue - if not 'lcp' in config_phy_iface: + if not "lcp" in config_phy_iface: ## Phy doesn't have an LCP removed_lcps.append(lcp) continue - if phy_lcp.host_if_name != config_phy_iface['lcp']: + if phy_lcp.host_if_name != config_phy_iface["lcp"]: ## Phy LCP name mismatch removed_lcps.append(lcp) continue - self.logger.debug(f"LCP OK: {lcp.host_if_name} -> (vpp={vpp_iface.interface_name}, config={config_ifname})") + self.logger.debug( + f"LCP OK: {lcp.host_if_name} -> (vpp={vpp_iface.interface_name}, config={config_ifname})" + ) for lcp in removed_lcps: - vpp_ifname = self.vpp.cache['interfaces'][lcp.phy_sw_if_index].interface_name - cli=f"lcp delete {vpp_ifname}" - self.cli['prune'].append(cli); + vpp_ifname = self.vpp.cache["interfaces"][ + lcp.phy_sw_if_index + ].interface_name + cli = f"lcp delete {vpp_ifname}" + self.cli["prune"].append(cli) self.vpp.cache_remove_lcp(lcp.host_if_name) return True def prune_admin_state(self): - """ Set admin-state down for all interfaces that are not in the config. """ - for ifname in self.vpp.get_qinx_interfaces() + self.vpp.get_dot1x_interfaces() + self.vpp.get_bondethernets() + self.vpp.get_phys() + self.vpp.get_vxlan_tunnels() + self.vpp.get_loopbacks(): - if not ifname in interface.get_interfaces(self.cfg) + loopback.get_loopbacks(self.cfg): - vpp_iface = self.vpp.cache['interface_names'][ifname] + """Set admin-state down for all interfaces that are not in the config.""" + for ifname in ( + self.vpp.get_qinx_interfaces() + + self.vpp.get_dot1x_interfaces() + + self.vpp.get_bondethernets() + + self.vpp.get_phys() + + self.vpp.get_vxlan_tunnels() + + self.vpp.get_loopbacks() + ): + if not ifname in interface.get_interfaces( + self.cfg + ) + loopback.get_loopbacks(self.cfg): + vpp_iface = self.vpp.cache["interface_names"][ifname] if self.vpp.tap_is_lcp(ifname): continue - if vpp_iface.flags & 1: # IF_STATUS_API_FLAG_ADMIN_UP - cli=f"set interface state {ifname} down" - self.cli['prune'].append(cli); + if vpp_iface.flags & 1: # IF_STATUS_API_FLAG_ADMIN_UP + cli = f"set interface state {ifname} down" + self.cli["prune"].append(cli) return True def create(self): - """ Create all objects in VPP that occur in the config but not in VPP. For an indepth - explanation of how and why this particular pruning order is chosen, see README.md - section on Reconciling. """ + """Create all objects in VPP that occur in the config but not in VPP. For an indepth + explanation of how and why this particular pruning order is chosen, see README.md + section on Reconciling.""" ret = True if not self.create_loopbacks(): self.logger.warning("Could not create Loopbacks in VPP") @@ -659,41 +744,43 @@ class Reconciler(): def create_loopbacks(self): for ifname in loopback.get_loopbacks(self.cfg): - if ifname in self.vpp.cache['interface_names']: + if ifname in self.vpp.cache["interface_names"]: continue instance = int(ifname[4:]) - cli=f"create loopback interface instance {int(instance)}" + cli = f"create loopback interface instance {int(instance)}" ifname, iface = loopback.get_by_name(self.cfg, ifname) - if 'mac' in iface: + if "mac" in iface: cli += f" mac {iface['mac']}" - self.cli['create'].append(cli); + self.cli["create"].append(cli) return True def create_bondethernets(self): for ifname in bondethernet.get_bondethernets(self.cfg): - if ifname in self.vpp.cache['interface_names']: + if ifname in self.vpp.cache["interface_names"]: continue ifname, iface = bondethernet.get_by_name(self.cfg, ifname) instance = int(ifname[12:]) mode = bondethernet.get_mode(self.cfg, ifname) - cli=f"create bond id {int(instance)} mode {mode}" + cli = f"create bond id {int(instance)} mode {mode}" lb = bondethernet.get_lb(self.cfg, ifname) if lb: cli += f" load-balance {lb}" - if 'mac' in iface: + if "mac" in iface: cli += f" hw-addr {iface['mac']}" - self.cli['create'].append(cli); + self.cli["create"].append(cli) return True def create_vxlan_tunnels(self): for ifname in vxlan_tunnel.get_vxlan_tunnels(self.cfg): - if ifname in self.vpp.cache['interface_names']: + if ifname in self.vpp.cache["interface_names"]: continue ifname, iface = vxlan_tunnel.get_by_name(self.cfg, ifname) instance = int(ifname[12:]) - cli=(f"create vxlan tunnel src {iface['local']} dst {iface['remote']} " - f"instance {instance} vni {iface['vni']} decap-next l2") - self.cli['create'].append(cli); + cli = ( + f"create vxlan tunnel src {iface['local']} dst {iface['remote']} " + f"instance {instance} vni {iface['vni']} decap-next l2" + ) + self.cli["create"].append(cli) return True def create_sub_interfaces(self): @@ -704,44 +791,44 @@ class Reconciler(): continue ifname, iface = interface.get_by_name(self.cfg, ifname) - if ifname in self.vpp.cache['interface_names']: + if ifname in self.vpp.cache["interface_names"]: continue ## Assemble the encapsulation string encap = interface.get_encapsulation(self.cfg, ifname) - if encap['dot1ad'] > 0: + if encap["dot1ad"] > 0: encapstr = f"dot1ad {int(encap['dot1ad'])}" else: encapstr = f"dot1q {int(encap['dot1q'])}" if do_qinx: encapstr += f" inner-dot1q {int(encap['inner-dot1q'])}" - if encap['exact-match'] == True: + if encap["exact-match"] == True: encapstr += " exact-match" - parent, subid = ifname.split('.') - cli=f"create sub {parent} {int(int(subid))} {encapstr}" - self.cli['create'].append(cli); + parent, subid = ifname.split(".") + cli = f"create sub {parent} {int(int(subid))} {encapstr}" + self.cli["create"].append(cli) return True def create_taps(self): for ifname in tap.get_taps(self.cfg): ifname, iface = tap.get_by_name(self.cfg, ifname) - if ifname in self.vpp.cache['interface_names']: + if ifname in self.vpp.cache["interface_names"]: continue - instance=int(ifname[3:]) - cli=f"create tap id {int(instance)} host-if-name {iface['host']['name']}" - if 'mac' in iface['host']: - cli+=f" host-mac-addr {iface['host']['mac']}" - if 'namespace' in iface['host']: - cli+=f" host-ns {int(iface['host']['namespace'])}" - if 'bridge' in iface['host']: - cli+=f" host-bridge {iface['host']['bridge']}" - if 'mtu' in iface['host']: - cli+=f" host-mtu-size {int(iface['host']['mtu'])}" - if 'rx-ring-size' in iface: - cli+=f" rx-ring-size {int(iface['rx-ring-size'])}" - if 'tx-ring-size' in iface: - cli+=f" tx-ring-size {int(iface['tx-ring-size'])}" - self.cli['create'].append(cli) + instance = int(ifname[3:]) + cli = f"create tap id {int(instance)} host-if-name {iface['host']['name']}" + if "mac" in iface["host"]: + cli += f" host-mac-addr {iface['host']['mac']}" + if "namespace" in iface["host"]: + cli += f" host-ns {int(iface['host']['namespace'])}" + if "bridge" in iface["host"]: + cli += f" host-bridge {iface['host']['bridge']}" + if "mtu" in iface["host"]: + cli += f" host-mtu-size {int(iface['host']['mtu'])}" + if "rx-ring-size" in iface: + cli += f" rx-ring-size {int(iface['rx-ring-size'])}" + if "tx-ring-size" in iface: + cli += f" tx-ring-size {int(iface['tx-ring-size'])}" + self.cli["create"].append(cli) return True @@ -750,44 +837,48 @@ class Reconciler(): ifname, iface = bridgedomain.get_by_name(self.cfg, ifname) instance = int(ifname[2:]) settings = bridgedomain.get_settings(self.cfg, ifname) - if instance in self.vpp.cache['bridgedomains']: + if instance in self.vpp.cache["bridgedomains"]: continue - cli=f"create bridge-domain {instance}" - if not settings['learn']: + cli = f"create bridge-domain {instance}" + if not settings["learn"]: cli += " learn 0" - if not settings['unicast-flood']: + if not settings["unicast-flood"]: cli += " flood 0" - if not settings['unknown-unicast-flood']: + if not settings["unknown-unicast-flood"]: cli += " uu-flood 0" - if not settings['unicast-forward']: + if not settings["unicast-forward"]: cli += " forward 0" - if settings['arp-termination']: + if settings["arp-termination"]: cli += " arp-term 1" - if settings['arp-unicast-forward']: + if settings["arp-unicast-forward"]: cli += " arp-ufwd 1" - if settings['mac-age-minutes'] > 0: + if settings["mac-age-minutes"] > 0: cli += f" mac-age {int(settings['mac-age-minutes'])}" - self.cli['create'].append(cli); + self.cli["create"].append(cli) return True def create_lcps(self): - lcpnames = [self.vpp.cache['lcps'][x].host_if_name for x in self.vpp.cache['lcps']] + lcpnames = [ + self.vpp.cache["lcps"][x].host_if_name for x in self.vpp.cache["lcps"] + ] - ## First create untagged ... - for ifname in interface.get_interfaces(self.cfg) + loopback.get_loopbacks(self.cfg): + ## First create untagged ... + for ifname in interface.get_interfaces(self.cfg) + loopback.get_loopbacks( + self.cfg + ): if interface.is_sub(self.cfg, ifname): continue - if ifname.startswith('loop'): + if ifname.startswith("loop"): ifname, iface = loopback.get_by_name(self.cfg, ifname) else: ifname, iface = interface.get_by_name(self.cfg, ifname) - if not 'lcp' in iface: + if not "lcp" in iface: continue - if iface['lcp'] in lcpnames: + if iface["lcp"] in lcpnames: continue - cli=f"lcp create {ifname} host-if {iface['lcp']}" - self.cli['create'].append(cli); + cli = f"lcp create {ifname} host-if {iface['lcp']}" + self.cli["create"].append(cli) ## ... then 1-tag (Dot1Q/Dot1AD), and then create 2-tag (Qin*) LCPs for do_qinx in [False, True]: @@ -795,12 +886,12 @@ class Reconciler(): if not do_qinx == interface.is_qinx(self.cfg, ifname): continue ifname, iface = interface.get_by_name(self.cfg, ifname) - if not 'lcp' in iface: + if not "lcp" in iface: continue - if iface['lcp'] in lcpnames: + if iface["lcp"] in lcpnames: continue - cli=f"lcp create {ifname} host-if {iface['lcp']}" - self.cli['create'].append(cli); + cli = f"lcp create {ifname} host-if {iface['lcp']}" + self.cli["create"].append(cli) return True def sync(self): @@ -833,180 +924,220 @@ class Reconciler(): def sync_loopbacks(self): for ifname in loopback.get_loopbacks(self.cfg): - if not ifname in self.vpp.cache['interface_names']: + if not ifname in self.vpp.cache["interface_names"]: ## New loopback continue - vpp_iface = self.vpp.cache['interface_names'][ifname] + vpp_iface = self.vpp.cache["interface_names"][ifname] config_ifname, config_iface = loopback.get_by_name(self.cfg, ifname) - if 'mac' in config_iface and config_iface['mac'] != str(vpp_iface.l2_address): - cli=f"set interface mac address {config_ifname} {config_iface['mac']}" - self.cli['sync'].append(cli) + if "mac" in config_iface and config_iface["mac"] != str( + vpp_iface.l2_address + ): + cli = f"set interface mac address {config_ifname} {config_iface['mac']}" + self.cli["sync"].append(cli) return True def sync_phys(self): for ifname in interface.get_phys(self.cfg): - if not ifname in self.vpp.cache['interface_names']: + if not ifname in self.vpp.cache["interface_names"]: ## New interface continue - vpp_iface = self.vpp.cache['interface_names'][ifname] + vpp_iface = self.vpp.cache["interface_names"][ifname] config_ifname, config_iface = interface.get_by_name(self.cfg, ifname) - if 'mac' in config_iface and config_iface['mac'] != str(vpp_iface.l2_address): - cli=f"set interface mac address {config_ifname} {config_iface['mac']}" - self.cli['sync'].append(cli) + if "mac" in config_iface and config_iface["mac"] != str( + vpp_iface.l2_address + ): + cli = f"set interface mac address {config_ifname} {config_iface['mac']}" + self.cli["sync"].append(cli) return True def sync_bondethernets(self): for ifname in bondethernet.get_bondethernets(self.cfg): - if ifname in self.vpp.cache['interface_names']: - vpp_iface = self.vpp.cache['interface_names'][ifname] - vpp_members = [self.vpp.cache['interfaces'][x].interface_name for x in self.vpp.cache['bondethernet_members'][vpp_iface.sw_if_index]] + if ifname in self.vpp.cache["interface_names"]: + vpp_iface = self.vpp.cache["interface_names"][ifname] + vpp_members = [ + self.vpp.cache["interfaces"][x].interface_name + for x in self.vpp.cache["bondethernet_members"][ + vpp_iface.sw_if_index + ] + ] else: ## New BondEthernet vpp_iface = None vpp_members = [] - config_bond_ifname, config_bond_iface = bondethernet.get_by_name(self.cfg, ifname) - if not 'interfaces' in config_bond_iface: + config_bond_ifname, config_bond_iface = bondethernet.get_by_name( + self.cfg, ifname + ) + if not "interfaces" in config_bond_iface: continue config_ifname, config_iface = interface.get_by_name(self.cfg, ifname) bondmac = None - for member_ifname in sorted(config_bond_iface['interfaces']): - member_ifname, member_iface = interface.get_by_name(self.cfg, member_ifname) - member_iface = self.vpp.cache['interface_names'][member_ifname] + for member_ifname in sorted(config_bond_iface["interfaces"]): + member_ifname, member_iface = interface.get_by_name( + self.cfg, member_ifname + ) + member_iface = self.vpp.cache["interface_names"][member_ifname] if not member_ifname in vpp_members: if len(vpp_members) == 0: bondmac = member_iface.l2_address - cli=f"bond add {config_bond_ifname} {member_iface.interface_name}" - self.cli['sync'].append(cli); - if vpp_iface and 'mac' in config_iface and str(vpp_iface.l2_address) != config_iface['mac']: - cli=f"set interface mac address {config_ifname} {config_iface['mac']}" - self.cli['sync'].append(cli); - elif bondmac and 'lcp' in config_iface: + cli = f"bond add {config_bond_ifname} {member_iface.interface_name}" + self.cli["sync"].append(cli) + if ( + vpp_iface + and "mac" in config_iface + and str(vpp_iface.l2_address) != config_iface["mac"] + ): + cli = f"set interface mac address {config_ifname} {config_iface['mac']}" + self.cli["sync"].append(cli) + elif bondmac and "lcp" in config_iface: ## TODO(pim) - Ensure LCP has the same MAC as the BondEthernet ## VPP, when creating a BondEthernet, will give it an ephemeral MAC. Then, when the ## first member is enslaved, the MAC address changes to that of the first member. ## However, LinuxCP does not propagate this change to the Linux side (because there ## is no API callback for MAC address changes). To ensure consistency, every time we ## sync members, we ought to ensure the Linux device has the same MAC as its BondEthernet. - cli=f"comment {{ ip link set {config_iface['lcp']} address {bondmac} }}" - self.cli['sync'].append(cli); + cli = ( + f"comment {{ ip link set {config_iface['lcp']} address {bondmac} }}" + ) + self.cli["sync"].append(cli) return True def sync_bridgedomains(self): for ifname in bridgedomain.get_bridgedomains(self.cfg): instance = int(ifname[2:]) - if instance in self.vpp.cache['bridgedomains']: - vpp_bridge = self.vpp.cache['bridgedomains'][instance] + if instance in self.vpp.cache["bridgedomains"]: + vpp_bridge = self.vpp.cache["bridgedomains"][instance] bvi_sw_if_index = vpp_bridge.bvi_sw_if_index - bridge_sw_if_index_list = [x.sw_if_index for x in vpp_bridge.sw_if_details] - bridge_members = [self.vpp.cache['interfaces'][x].interface_name for x in bridge_sw_if_index_list if x in self.vpp.cache['interfaces']] + bridge_sw_if_index_list = [ + x.sw_if_index for x in vpp_bridge.sw_if_details + ] + bridge_members = [ + self.vpp.cache["interfaces"][x].interface_name + for x in bridge_sw_if_index_list + if x in self.vpp.cache["interfaces"] + ] else: ## New BridgeDomain vpp_bridge = None bvi_sw_if_index = -1 bridge_members = [] - config_bridge_ifname, config_bridge_iface = bridgedomain.get_by_name(self.cfg, f"bd{int(instance)}") + config_bridge_ifname, config_bridge_iface = bridgedomain.get_by_name( + self.cfg, f"bd{int(instance)}" + ) if vpp_bridge: # Sync settings on existing bridge. create_bridgedomain() will have set them for new bridges. settings = bridgedomain.get_settings(self.cfg, config_bridge_ifname) - if settings['learn'] != vpp_bridge.learn: - cli=f"set bridge-domain learn {int(instance)}" - if not settings['learn']: + if settings["learn"] != vpp_bridge.learn: + cli = f"set bridge-domain learn {int(instance)}" + if not settings["learn"]: cli += " disable" - self.cli['sync'].append(cli); - if settings['unicast-forward'] != vpp_bridge.forward: - cli=f"set bridge-domain forward {int(instance)}" - if not settings['unicast-forward']: + self.cli["sync"].append(cli) + if settings["unicast-forward"] != vpp_bridge.forward: + cli = f"set bridge-domain forward {int(instance)}" + if not settings["unicast-forward"]: cli += " disable" - self.cli['sync'].append(cli); - if settings['unicast-flood'] != vpp_bridge.flood: - cli=f"set bridge-domain flood {int(instance)}" - if not settings['unicast-flood']: + self.cli["sync"].append(cli) + if settings["unicast-flood"] != vpp_bridge.flood: + cli = f"set bridge-domain flood {int(instance)}" + if not settings["unicast-flood"]: cli += " disable" - self.cli['sync'].append(cli); - if settings['unknown-unicast-flood'] != vpp_bridge.uu_flood: - cli=f"set bridge-domain uu-flood {int(instance)}" - if not settings['unknown-unicast-flood']: + self.cli["sync"].append(cli) + if settings["unknown-unicast-flood"] != vpp_bridge.uu_flood: + cli = f"set bridge-domain uu-flood {int(instance)}" + if not settings["unknown-unicast-flood"]: cli += " disable" - self.cli['sync'].append(cli); - if settings['arp-termination'] != vpp_bridge.arp_term: - cli=f"set bridge-domain arp term {int(instance)}" - if not settings['arp-termination']: + self.cli["sync"].append(cli) + if settings["arp-termination"] != vpp_bridge.arp_term: + cli = f"set bridge-domain arp term {int(instance)}" + if not settings["arp-termination"]: cli += " disable" - self.cli['sync'].append(cli); - if settings['arp-unicast-forward'] != vpp_bridge.arp_ufwd: - cli=f"set bridge-domain arp-ufwd {int(instance)}" - if not settings['arp-unicast-forward']: + self.cli["sync"].append(cli) + if settings["arp-unicast-forward"] != vpp_bridge.arp_ufwd: + cli = f"set bridge-domain arp-ufwd {int(instance)}" + if not settings["arp-unicast-forward"]: cli += " disable" - self.cli['sync'].append(cli); - if settings['mac-age-minutes'] != vpp_bridge.mac_age: - cli=f"set bridge-domain mac-age {int(instance)} {int(settings['mac-age-minutes'])}" - self.cli['sync'].append(cli); + self.cli["sync"].append(cli) + if settings["mac-age-minutes"] != vpp_bridge.mac_age: + cli = f"set bridge-domain mac-age {int(instance)} {int(settings['mac-age-minutes'])}" + self.cli["sync"].append(cli) - if 'bvi' in config_bridge_iface: - bviname = config_bridge_iface['bvi'] - if bviname in self.vpp.cache['interface_names'] and self.vpp.cache['interface_names'][bviname].sw_if_index == bvi_sw_if_index: + if "bvi" in config_bridge_iface: + bviname = config_bridge_iface["bvi"] + if ( + bviname in self.vpp.cache["interface_names"] + and self.vpp.cache["interface_names"][bviname].sw_if_index + == bvi_sw_if_index + ): continue - cli=f"set interface l2 bridge {bviname} {int(instance)} bvi" - self.cli['sync'].append(cli); + cli = f"set interface l2 bridge {bviname} {int(instance)} bvi" + self.cli["sync"].append(cli) - if not 'interfaces' in config_bridge_iface: + if not "interfaces" in config_bridge_iface: continue - for member_ifname in config_bridge_iface['interfaces']: - member_ifname, member_iface = interface.get_by_name(self.cfg, member_ifname) + for member_ifname in config_bridge_iface["interfaces"]: + member_ifname, member_iface = interface.get_by_name( + self.cfg, member_ifname + ) if not member_ifname in bridge_members: - cli=f"set interface l2 bridge {member_ifname} {int(instance)}" - self.cli['sync'].append(cli); - operation="disable" + cli = f"set interface l2 bridge {member_ifname} {int(instance)}" + self.cli["sync"].append(cli) + operation = "disable" if interface.is_qinx(self.cfg, member_ifname): - operation="pop 2" + operation = "pop 2" elif interface.is_sub(self.cfg, member_ifname): - operation="pop 1" - cli=f"set interface l2 tag-rewrite {member_ifname} {operation}" - self.cli['sync'].append(cli); + operation = "pop 1" + cli = f"set interface l2 tag-rewrite {member_ifname} {operation}" + self.cli["sync"].append(cli) return True def sync_l2xcs(self): for ifname in interface.get_l2xc_interfaces(self.cfg): config_rx_ifname, config_rx_iface = interface.get_by_name(self.cfg, ifname) - config_tx_ifname, config_tx_iface = interface.get_by_name(self.cfg, config_rx_iface['l2xc']) + config_tx_ifname, config_tx_iface = interface.get_by_name( + self.cfg, config_rx_iface["l2xc"] + ) vpp_rx_iface = None vpp_tx_iface = None - if config_rx_ifname in self.vpp.cache['interface_names']: - vpp_rx_iface = self.vpp.cache['interface_names'][config_rx_ifname] - if config_tx_ifname in self.vpp.cache['interface_names']: - vpp_tx_iface = self.vpp.cache['interface_names'][config_tx_ifname] + if config_rx_ifname in self.vpp.cache["interface_names"]: + vpp_rx_iface = self.vpp.cache["interface_names"][config_rx_ifname] + if config_tx_ifname in self.vpp.cache["interface_names"]: + vpp_tx_iface = self.vpp.cache["interface_names"][config_tx_ifname] l2xc_changed = False if not vpp_rx_iface or not vpp_tx_iface: l2xc_changed = True - elif not vpp_rx_iface.sw_if_index in self.vpp.cache['l2xcs']: + elif not vpp_rx_iface.sw_if_index in self.vpp.cache["l2xcs"]: l2xc_changed = True - elif not vpp_tx_iface.sw_if_index == self.vpp.cache['l2xcs'][vpp_rx_iface.sw_if_index].tx_sw_if_index: + elif ( + not vpp_tx_iface.sw_if_index + == self.vpp.cache["l2xcs"][vpp_rx_iface.sw_if_index].tx_sw_if_index + ): l2xc_changed = True if l2xc_changed: - cli=f"set interface l2 xconnect {config_rx_ifname} {config_tx_ifname}" - self.cli['sync'].append(cli); + cli = f"set interface l2 xconnect {config_rx_ifname} {config_tx_ifname}" + self.cli["sync"].append(cli) - operation="disable" + operation = "disable" if interface.is_qinx(self.cfg, config_rx_ifname): - operation="pop 2" + operation = "pop 2" elif interface.is_sub(self.cfg, config_rx_ifname): - operation="pop 1" - cli=f"set interface l2 tag-rewrite {config_rx_ifname} {operation}" - self.cli['sync'].append(cli); + operation = "pop 1" + cli = f"set interface l2 tag-rewrite {config_rx_ifname} {operation}" + self.cli["sync"].append(cli) return True def sync_mtu_direction(self, shrink=True): if shrink: - tag_list = [ 2, 1, 0 ] + tag_list = [2, 1, 0] else: - tag_list = [ 0, 1, 2 ] + tag_list = [0, 1, 2] for numtags in tag_list: - for ifname in interface.get_interfaces(self.cfg) + loopback.get_loopbacks(self.cfg): + for ifname in interface.get_interfaces(self.cfg) + loopback.get_loopbacks( + self.cfg + ): if numtags == 0 and interface.is_sub(self.cfg, ifname): continue if numtags == 1 and not interface.is_sub(self.cfg, ifname): @@ -1018,80 +1149,93 @@ class Reconciler(): config_mtu = 1500 vpp_mtu = 9000 if ifname.startswith("loop"): - if ifname in self.vpp.cache['interface_names']: - vpp_mtu = self.vpp.cache['interface_names'][ifname].mtu[0] + if ifname in self.vpp.cache["interface_names"]: + vpp_mtu = self.vpp.cache["interface_names"][ifname].mtu[0] vpp_ifname, config_iface = loopback.get_by_name(self.cfg, ifname) - if 'mtu' in config_iface: - config_mtu = config_iface['mtu'] + if "mtu" in config_iface: + config_mtu = config_iface["mtu"] else: if numtags > 0: vpp_mtu = 0 - if ifname in self.vpp.cache['interface_names']: - vpp_mtu = self.vpp.cache['interface_names'][ifname].mtu[0] + if ifname in self.vpp.cache["interface_names"]: + vpp_mtu = self.vpp.cache["interface_names"][ifname].mtu[0] vpp_ifname, config_iface = interface.get_by_name(self.cfg, ifname) config_mtu = interface.get_mtu(self.cfg, ifname) if shrink and config_mtu < vpp_mtu: - cli=f"set interface mtu packet {int(config_mtu)} {vpp_ifname}" - self.cli['sync'].append(cli); + cli = f"set interface mtu packet {int(config_mtu)} {vpp_ifname}" + self.cli["sync"].append(cli) elif not shrink and config_mtu > vpp_mtu: - cli=f"set interface mtu packet {int(config_mtu)} {vpp_ifname}" - self.cli['sync'].append(cli); + cli = f"set interface mtu packet {int(config_mtu)} {vpp_ifname}" + self.cli["sync"].append(cli) return True def sync_link_mtu_direction(self, shrink=True): - for idx, vpp_iface in self.vpp.cache['interfaces'].items(): + for idx, vpp_iface in self.vpp.cache["interfaces"].items(): if vpp_iface.sub_number_of_tags != 0: continue - if vpp_iface.interface_dev_type in ['local', 'Loopback', 'VXLAN', 'virtio']: + if vpp_iface.interface_dev_type in ["local", "Loopback", "VXLAN", "virtio"]: continue - config_ifname, config_iface = interface.get_by_name(self.cfg, vpp_iface.interface_name) + config_ifname, config_iface = interface.get_by_name( + self.cfg, vpp_iface.interface_name + ) if not config_iface: - self.logger.warning(f"Interface {vpp_iface.interface_name} exists in VPP but not in config, this is dangerous") + self.logger.warning( + f"Interface {vpp_iface.interface_name} exists in VPP but not in config, this is dangerous" + ) continue if not interface.is_phy(self.cfg, vpp_iface.interface_name): continue config_mtu = interface.get_mtu(self.cfg, vpp_iface.interface_name) - if vpp_iface.interface_dev_type=='bond' and vpp_iface.link_mtu < config_mtu: - self.logger.warning(f"{vpp_iface.interface_name} has a Max Frame Size ({vpp_iface.link_mtu}) " - "lower than desired MTU ({config_mtu}), this is unsupported") + if ( + vpp_iface.interface_dev_type == "bond" + and vpp_iface.link_mtu < config_mtu + ): + self.logger.warning( + f"{vpp_iface.interface_name} has a Max Frame Size ({vpp_iface.link_mtu}) " + "lower than desired MTU ({config_mtu}), this is unsupported" + ) continue if shrink and config_mtu < vpp_iface.link_mtu: ## If the interface is up, temporarily down it in order to change the Max Frame Size - if vpp_iface.flags & 1: # IF_STATUS_API_FLAG_ADMIN_UP - cli=f"set interface state {vpp_iface.interface_name} down" - self.cli['sync'].append(cli); + if vpp_iface.flags & 1: # IF_STATUS_API_FLAG_ADMIN_UP + cli = f"set interface state {vpp_iface.interface_name} down" + self.cli["sync"].append(cli) - cli=f"set interface mtu {int(config_mtu)} {vpp_iface.interface_name}" - self.cli['sync'].append(cli); + cli = f"set interface mtu {int(config_mtu)} {vpp_iface.interface_name}" + self.cli["sync"].append(cli) - if vpp_iface.flags & 1: # IF_STATUS_API_FLAG_ADMIN_UP - cli=f"set interface state {vpp_iface.interface_name} up" - self.cli['sync'].append(cli); + if vpp_iface.flags & 1: # IF_STATUS_API_FLAG_ADMIN_UP + cli = f"set interface state {vpp_iface.interface_name} up" + self.cli["sync"].append(cli) elif not shrink and config_mtu > vpp_iface.link_mtu: ## If the interface is up, temporarily down it in order to change the Max Frame Size - if vpp_iface.flags & 1: # IF_STATUS_API_FLAG_ADMIN_UP - cli=f"set interface state {vpp_iface.interface_name} down" - self.cli['sync'].append(cli); + if vpp_iface.flags & 1: # IF_STATUS_API_FLAG_ADMIN_UP + cli = f"set interface state {vpp_iface.interface_name} down" + self.cli["sync"].append(cli) - cli=f"set interface mtu {int(config_mtu)} {vpp_iface.interface_name}" - self.cli['sync'].append(cli); + cli = f"set interface mtu {int(config_mtu)} {vpp_iface.interface_name}" + self.cli["sync"].append(cli) - if vpp_iface.flags & 1: # IF_STATUS_API_FLAG_ADMIN_UP - cli=f"set interface state {vpp_iface.interface_name} up" - self.cli['sync'].append(cli); + if vpp_iface.flags & 1: # IF_STATUS_API_FLAG_ADMIN_UP + cli = f"set interface state {vpp_iface.interface_name} up" + self.cli["sync"].append(cli) return True def sync_mtu(self): ret = True if not self.sync_link_mtu_direction(shrink=False): - self.logger.warning("Could not sync growing interface Max Frame Size in VPP") + self.logger.warning( + "Could not sync growing interface Max Frame Size in VPP" + ) ret = False if not self.sync_link_mtu_direction(shrink=True): - self.logger.warning("Could not sync shrinking interface Max Frame Size in VPP") + self.logger.warning( + "Could not sync shrinking interface Max Frame Size in VPP" + ) ret = False if not self.sync_mtu_direction(shrink=True): self.logger.warning("Could not sync shrinking interface MTU in VPP") @@ -1102,30 +1246,37 @@ class Reconciler(): return ret def sync_addresses(self): - for ifname in interface.get_interfaces(self.cfg) + loopback.get_loopbacks(self.cfg): - config_addresses=[] - vpp_addresses=[] + for ifname in interface.get_interfaces(self.cfg) + loopback.get_loopbacks( + self.cfg + ): + config_addresses = [] + vpp_addresses = [] if ifname.startswith("loop"): vpp_ifname, config_iface = loopback.get_by_name(self.cfg, ifname) - if 'addresses' in config_iface: - config_addresses = config_iface['addresses'] + if "addresses" in config_iface: + config_addresses = config_iface["addresses"] else: vpp_ifname, config_iface = interface.get_by_name(self.cfg, ifname) - if 'addresses' in config_iface: - config_addresses = config_iface['addresses'] - if vpp_ifname in self.vpp.cache['interface_names']: - sw_if_index = self.vpp.cache['interface_names'][vpp_ifname].sw_if_index - if sw_if_index in self.vpp.cache['interface_addresses']: - vpp_addresses = [str(x) for x in self.vpp.cache['interface_addresses'][sw_if_index]] + if "addresses" in config_iface: + config_addresses = config_iface["addresses"] + if vpp_ifname in self.vpp.cache["interface_names"]: + sw_if_index = self.vpp.cache["interface_names"][vpp_ifname].sw_if_index + if sw_if_index in self.vpp.cache["interface_addresses"]: + vpp_addresses = [ + str(x) + for x in self.vpp.cache["interface_addresses"][sw_if_index] + ] for a in config_addresses: if a in vpp_addresses: continue - cli=f"set interface ip address {vpp_ifname} {a}" - self.cli['sync'].append(cli); + cli = f"set interface ip address {vpp_ifname} {a}" + self.cli["sync"].append(cli) return True def sync_admin_state(self): - for ifname in interface.get_interfaces(self.cfg) + loopback.get_loopbacks(self.cfg): + for ifname in interface.get_interfaces(self.cfg) + loopback.get_loopbacks( + self.cfg + ): if ifname.startswith("loop"): vpp_ifname, config_iface = loopback.get_by_name(self.cfg, ifname) config_admin_state = 1 @@ -1134,43 +1285,51 @@ class Reconciler(): config_admin_state = interface.get_admin_state(self.cfg, ifname) vpp_admin_state = 0 - if vpp_ifname in self.vpp.cache['interface_names']: - vpp_admin_state = self.vpp.cache['interface_names'][vpp_ifname].flags & 1 # IF_STATUS_API_FLAG_ADMIN_UP + if vpp_ifname in self.vpp.cache["interface_names"]: + vpp_admin_state = ( + self.vpp.cache["interface_names"][vpp_ifname].flags & 1 + ) # IF_STATUS_API_FLAG_ADMIN_UP if config_admin_state == vpp_admin_state: continue - state="up" + state = "up" if config_admin_state == 0: - state="down" - cli=f"set interface state {vpp_ifname} {state}" - self.cli['sync'].append(cli); + state = "down" + cli = f"set interface state {vpp_ifname} {state}" + self.cli["sync"].append(cli) return True def write(self, outfile, ok=False): - """ Emit the CLI contents to stdout (if outfile=='-') or a named file otherwise. - If the 'ok' flag is False, emit a warning at the top and bottom of the file. + """Emit the CLI contents to stdout (if outfile=='-') or a named file otherwise. + If the 'ok' flag is False, emit a warning at the top and bottom of the file. """ # Assemble the intended output into a list output = [] if not ok: - output.append("comment { vppcfg: Planning failed, be careful with this output! }") + output.append( + "comment { vppcfg: Planning failed, be careful with this output! }" + ) - for phase in [ "prune", "create", "sync" ]: + for phase in ["prune", "create", "sync"]: n = len(self.cli[phase]) if n > 0: - output.append(f"comment {{ vppcfg {phase}: {n} CLI statement(s) follow }}") + output.append( + f"comment {{ vppcfg {phase}: {n} CLI statement(s) follow }}" + ) output.extend(self.cli[phase]) if not ok: - output.append("comment { vppcfg: Planning failed, be careful with this output! }") + output.append( + "comment { vppcfg: Planning failed, be careful with this output! }" + ) # Emit the output list to stdout or a file - if outfile and outfile == '-': + if outfile and outfile == "-": fh = sys.stdout outfile = "(stdout)" else: - fh = open(outfile, 'w') + fh = open(outfile, "w") if len(output) > 0: - print('\n'.join(output), file=fh) + print("\n".join(output), file=fh) if fh is not sys.stdout: fh.close() diff --git a/vpp/vppapi.py b/vpp/vppapi.py index f448233..04afadd 100644 --- a/vpp/vppapi.py +++ b/vpp/vppapi.py @@ -1,17 +1,18 @@ -''' +""" The functions in this file interact with the VPP API to retrieve certain interface metadata. Its base class will never change state. See the derived classes VPPApiDumper() and VPPApiApplier() -''' +""" from vpp_papi import VPPApiClient import os import fnmatch import logging -class VPPApi(): - def __init__(self, address='/run/vpp/api.sock', clientname='vppcfg'): - self.logger = logging.getLogger('vppcfg.vppapi') + +class VPPApi: + def __init__(self, address="/run/vpp/api.sock", clientname="vppcfg"): + self.logger = logging.getLogger("vppcfg.vppapi") self.logger.addHandler(logging.NullHandler()) self.address = address @@ -26,116 +27,135 @@ class VPPApi(): if self.connected: return True - vpp_json_dir = '/usr/share/vpp/api/' + vpp_json_dir = "/usr/share/vpp/api/" ## vpp_json_dir = "/home/pim/src/vpp/build-root/build-vpp_debug-native/vpp/CMakeFiles/" # construct a list of all the json api files jsonfiles = [] for root, dirnames, filenames in os.walk(vpp_json_dir): - for filename in fnmatch.filter(filenames, '*.api.json'): + for filename in fnmatch.filter(filenames, "*.api.json"): jsonfiles.append(os.path.join(root, filename)) if not jsonfiles: - self.logger.error('no json api files found') + self.logger.error("no json api files found") return False - self.vpp = VPPApiClient(apifiles=jsonfiles, - server_address=self.address) + self.vpp = VPPApiClient(apifiles=jsonfiles, server_address=self.address) try: - self.logger.debug('Connecting to VPP') + self.logger.debug("Connecting to VPP") self.vpp.connect(self.clientname) except: return False v = self.vpp.api.show_version() - self.logger.info(f'VPP version is {v.version}') + self.logger.info(f"VPP version is {v.version}") self.connected = True return True - def disconnect(self): if not self.connected: return True self.vpp.disconnect() - self.logger.debug('Disconnected from VPP') + self.logger.debug("Disconnected from VPP") self.connected = False return True def cache_clear(self): self.cache_read = False - return {"lcps": {}, "interface_names": {}, "interfaces": {}, "interface_addresses": {}, - "bondethernets": {}, "bondethernet_members": {}, - "bridgedomains": {}, "vxlan_tunnels": {}, "l2xcs": {}, "taps": {}} + return { + "lcps": {}, + "interface_names": {}, + "interfaces": {}, + "interface_addresses": {}, + "bondethernets": {}, + "bondethernet_members": {}, + "bridgedomains": {}, + "vxlan_tunnels": {}, + "l2xcs": {}, + "taps": {}, + } def cache_remove_lcp(self, lcpname): - """ Removes the LCP and TAP interface, identified by lcpname, from the config. """ - found=False - for idx, lcp in self.cache['lcps'].items(): + """Removes the LCP and TAP interface, identified by lcpname, from the config.""" + found = False + for idx, lcp in self.cache["lcps"].items(): if lcp.host_if_name == lcpname: found = True break if not found: - self.logger.warning(f"Trying to remove an LCP which is not in the config: {lcpname}") + self.logger.warning( + f"Trying to remove an LCP which is not in the config: {lcpname}" + ) return False - ifname = self.cache['interfaces'][lcp.host_sw_if_index].interface_name - del self.cache['lcps'][lcp.phy_sw_if_index] + ifname = self.cache["interfaces"][lcp.host_sw_if_index].interface_name + del self.cache["lcps"][lcp.phy_sw_if_index] # Remove the TAP interface and its dependencies return self.cache_remove_interface(ifname) def cache_remove_bondethernet_member(self, ifname): - """ Removes the bonderthernet member interface, identified by name, from the config. """ - if not ifname in self.cache['interface_names']: - self.logger.warning(f"Trying to remove a bondethernet member interface which is not in the config: {ifname}") + """Removes the bonderthernet member interface, identified by name, from the config.""" + if not ifname in self.cache["interface_names"]: + self.logger.warning( + f"Trying to remove a bondethernet member interface which is not in the config: {ifname}" + ) return False - iface = self.cache['interface_names'][ifname] - for bond_idx, members in self.cache['bondethernet_members'].items(): + iface = self.cache["interface_names"][ifname] + for bond_idx, members in self.cache["bondethernet_members"].items(): if iface.sw_if_index in members: - self.cache['bondethernet_members'][bond_idx].remove(iface.sw_if_index) + self.cache["bondethernet_members"][bond_idx].remove(iface.sw_if_index) return True def cache_remove_l2xc(self, ifname): - if not ifname in self.cache['interface_names']: - self.logger.warning(f"Trying to remove an L2XC which is not in the config: {ifname}") + if not ifname in self.cache["interface_names"]: + self.logger.warning( + f"Trying to remove an L2XC which is not in the config: {ifname}" + ) return False - iface = self.cache['interface_names'][ifname] - self.cache['l2xcs'].pop(iface.sw_if_index, None) + iface = self.cache["interface_names"][ifname] + self.cache["l2xcs"].pop(iface.sw_if_index, None) return True def cache_remove_vxlan_tunnel(self, ifname): - if not ifname in self.cache['interface_names']: - self.logger.warning(f"Trying to remove a VXLAN Tunnel which is not in the config: {ifname}") + if not ifname in self.cache["interface_names"]: + self.logger.warning( + f"Trying to remove a VXLAN Tunnel which is not in the config: {ifname}" + ) return False - iface = self.cache['interface_names'][ifname] - self.cache['vxlan_tunnels'].pop(iface.sw_if_index, None) + iface = self.cache["interface_names"][ifname] + self.cache["vxlan_tunnels"].pop(iface.sw_if_index, None) return True def cache_remove_interface(self, ifname): - """ Removes the interface, identified by name, from the config. """ - if not ifname in self.cache['interface_names']: - self.logger.warning(f"Trying to remove an interface which is not in the config: {ifname}") + """Removes the interface, identified by name, from the config.""" + if not ifname in self.cache["interface_names"]: + self.logger.warning( + f"Trying to remove an interface which is not in the config: {ifname}" + ) return False - iface = self.cache['interface_names'][ifname] - del self.cache['interfaces'][iface.sw_if_index] - if len(self.cache['interface_addresses'][iface.sw_if_index]) > 0: + iface = self.cache["interface_names"][ifname] + del self.cache["interfaces"][iface.sw_if_index] + if len(self.cache["interface_addresses"][iface.sw_if_index]) > 0: self.logger.warning(f"Not all addresses were removed on {ifname}") - del self.cache['interface_addresses'][iface.sw_if_index] - del self.cache['interface_names'][ifname] + del self.cache["interface_addresses"][iface.sw_if_index] + del self.cache["interface_names"][ifname] ## Use my_dict.pop('key', None), as it allows 'key' to be absent - if iface.sw_if_index in self.cache['bondethernet_members']: - if len(self.cache['bondethernet_members'][iface.sw_if_index]) != 0: - self.logger.warning(f"When removing BondEthernet {ifname}, its members are not empty: {self.cache['bondethernet_members'][iface.sw_if_index]}") + if iface.sw_if_index in self.cache["bondethernet_members"]: + if len(self.cache["bondethernet_members"][iface.sw_if_index]) != 0: + self.logger.warning( + f"When removing BondEthernet {ifname}, its members are not empty: {self.cache['bondethernet_members'][iface.sw_if_index]}" + ) else: - del self.cache['bondethernet_members'][iface.sw_if_index] - self.cache['bondethernets'].pop(iface.sw_if_index, None) - self.cache['taps'].pop(iface.sw_if_index, None) + del self.cache["bondethernet_members"][iface.sw_if_index] + self.cache["bondethernets"].pop(iface.sw_if_index, None) + self.cache["taps"].pop(iface.sw_if_index, None) return True def readconfig(self): @@ -155,116 +175,169 @@ class VPPApi(): if lcp.phy_sw_if_index > 65535 or lcp.host_sw_if_index > 65535: ## Work around endianness bug: https://gerrit.fd.io/r/c/vpp/+/35479 ## TODO(pim) - remove this when 22.06 ships - lcp = lcp._replace(phy_sw_if_index=socket.ntohl(lcp.phy_sw_if_index)) - lcp = lcp._replace(host_sw_if_index=socket.ntohl(lcp.host_sw_if_index)) + lcp = lcp._replace( + phy_sw_if_index=socket.ntohl(lcp.phy_sw_if_index) + ) + lcp = lcp._replace( + host_sw_if_index=socket.ntohl(lcp.host_sw_if_index) + ) lcp = lcp._replace(vif_index=socket.ntohl(lcp.vif_index)) - self.logger.warning(f"LCP workaround for endianness issue on {lcp.host_if_name}") - self.cache['lcps'][lcp.phy_sw_if_index] = lcp + self.logger.warning( + f"LCP workaround for endianness issue on {lcp.host_if_name}" + ) + self.cache["lcps"][lcp.phy_sw_if_index] = lcp self.lcp_enabled = True except: - self.logger.warning("linux-cp not found, will not reconcile Linux Control Plane") + self.logger.warning( + "linux-cp not found, will not reconcile Linux Control Plane" + ) self.logger.debug("Retrieving interfaces") r = self.vpp.api.sw_interface_dump() for iface in r: - self.cache['interfaces'][iface.sw_if_index] = iface - self.cache['interface_names'][iface.interface_name] = iface - self.cache['interface_addresses'][iface.sw_if_index] = [] + self.cache["interfaces"][iface.sw_if_index] = iface + self.cache["interface_names"][iface.interface_name] = iface + self.cache["interface_addresses"][iface.sw_if_index] = [] self.logger.debug(f"Retrieving IPv4 addresses for {iface.interface_name}") - ipr = self.vpp.api.ip_address_dump(sw_if_index=iface.sw_if_index, is_ipv6=False) + ipr = self.vpp.api.ip_address_dump( + sw_if_index=iface.sw_if_index, is_ipv6=False + ) for ip in ipr: - self.cache['interface_addresses'][iface.sw_if_index].append(str(ip.prefix)) + self.cache["interface_addresses"][iface.sw_if_index].append( + str(ip.prefix) + ) self.logger.debug(f"Retrieving IPv6 addresses for {iface.interface_name}") - ipr = self.vpp.api.ip_address_dump(sw_if_index=iface.sw_if_index, is_ipv6=True) + ipr = self.vpp.api.ip_address_dump( + sw_if_index=iface.sw_if_index, is_ipv6=True + ) for ip in ipr: - self.cache['interface_addresses'][iface.sw_if_index].append(str(ip.prefix)) - + self.cache["interface_addresses"][iface.sw_if_index].append( + str(ip.prefix) + ) + self.logger.debug("Retrieving bondethernets") r = self.vpp.api.sw_bond_interface_dump() for iface in r: - self.cache['bondethernets'][iface.sw_if_index] = iface - self.cache['bondethernet_members'][iface.sw_if_index] = [] - for member in self.vpp.api.sw_member_interface_dump(sw_if_index=iface.sw_if_index): - self.cache['bondethernet_members'][iface.sw_if_index].append(member.sw_if_index) - + self.cache["bondethernets"][iface.sw_if_index] = iface + self.cache["bondethernet_members"][iface.sw_if_index] = [] + for member in self.vpp.api.sw_member_interface_dump( + sw_if_index=iface.sw_if_index + ): + self.cache["bondethernet_members"][iface.sw_if_index].append( + member.sw_if_index + ) + self.logger.debug("Retrieving bridgedomains") r = self.vpp.api.bridge_domain_dump() for bridge in r: - self.cache['bridgedomains'][bridge.bd_id] = bridge - + self.cache["bridgedomains"][bridge.bd_id] = bridge + self.logger.debug("Retrieving vxlan_tunnels") r = self.vpp.api.vxlan_tunnel_v2_dump() for vxlan in r: - self.cache['vxlan_tunnels'][vxlan.sw_if_index] = vxlan - + self.cache["vxlan_tunnels"][vxlan.sw_if_index] = vxlan + self.logger.debug("Retrieving L2 Cross Connects") r = self.vpp.api.l2_xconnect_dump() for l2xc in r: - self.cache['l2xcs'][l2xc.rx_sw_if_index] = l2xc + self.cache["l2xcs"][l2xc.rx_sw_if_index] = l2xc self.logger.debug("Retrieving TAPs") r = self.vpp.api.sw_interface_tap_v2_dump() for tap in r: - self.cache['taps'][tap.sw_if_index] = tap + self.cache["taps"][tap.sw_if_index] = tap self.cache_read = True return self.cache_read def phys_exist(self, ifname_list): - """ Return True if all interfaces in the `ifname_list` exist as physical interface names + """Return True if all interfaces in the `ifname_list` exist as physical interface names in VPP. Return False otherwise.""" ret = True for ifname in ifname_list: - if not ifname in self.cache['interface_names']: + if not ifname in self.cache["interface_names"]: self.logger.warning(f"Interface {ifname} does not exist in VPP") ret = False return ret def get_sub_interfaces(self): - subints = [self.cache['interfaces'][x].interface_name for x in self.cache['interfaces'] if self.cache['interfaces'][x].sub_id>0 and self.cache['interfaces'][x].sub_number_of_tags > 0] + subints = [ + self.cache["interfaces"][x].interface_name + for x in self.cache["interfaces"] + if self.cache["interfaces"][x].sub_id > 0 + and self.cache["interfaces"][x].sub_number_of_tags > 0 + ] return subints def get_qinx_interfaces(self): - qinx_subints = [self.cache['interfaces'][x].interface_name for x in self.cache['interfaces'] if self.cache['interfaces'][x].sub_id>0 and self.cache['interfaces'][x].sub_inner_vlan_id>0] + qinx_subints = [ + self.cache["interfaces"][x].interface_name + for x in self.cache["interfaces"] + if self.cache["interfaces"][x].sub_id > 0 + and self.cache["interfaces"][x].sub_inner_vlan_id > 0 + ] return qinx_subints def get_dot1x_interfaces(self): - dot1x_subints = [self.cache['interfaces'][x].interface_name for x in self.cache['interfaces'] if self.cache['interfaces'][x].sub_id>0 and self.cache['interfaces'][x].sub_inner_vlan_id==0] + dot1x_subints = [ + self.cache["interfaces"][x].interface_name + for x in self.cache["interfaces"] + if self.cache["interfaces"][x].sub_id > 0 + and self.cache["interfaces"][x].sub_inner_vlan_id == 0 + ] return dot1x_subints def get_loopbacks(self): - loopbacks = [self.cache['interfaces'][x].interface_name for x in self.cache['interfaces'] if self.cache['interfaces'][x].interface_dev_type=='Loopback'] + loopbacks = [ + self.cache["interfaces"][x].interface_name + for x in self.cache["interfaces"] + if self.cache["interfaces"][x].interface_dev_type == "Loopback" + ] return loopbacks def get_phys(self): - phys = [self.cache['interfaces'][x].interface_name for x in self.cache['interfaces'] if self.cache['interfaces'][x].sw_if_index == self.cache['interfaces'][x].sup_sw_if_index and self.cache['interfaces'][x].interface_dev_type not in ['virtio', 'BVI', 'Loopback', 'VXLAN', 'local', 'bond']] + phys = [ + self.cache["interfaces"][x].interface_name + for x in self.cache["interfaces"] + if self.cache["interfaces"][x].sw_if_index + == self.cache["interfaces"][x].sup_sw_if_index + and self.cache["interfaces"][x].interface_dev_type + not in ["virtio", "BVI", "Loopback", "VXLAN", "local", "bond"] + ] return phys def get_bondethernets(self): - bonds = [self.cache['bondethernets'][x].interface_name for x in self.cache['bondethernets']] + bonds = [ + self.cache["bondethernets"][x].interface_name + for x in self.cache["bondethernets"] + ] return bonds def get_vxlan_tunnels(self): - vxlan_tunnels = [self.cache['interfaces'][x].interface_name for x in self.cache['interfaces'] if self.cache['interfaces'][x].interface_dev_type in ['VXLAN']] + vxlan_tunnels = [ + self.cache["interfaces"][x].interface_name + for x in self.cache["interfaces"] + if self.cache["interfaces"][x].interface_dev_type in ["VXLAN"] + ] return vxlan_tunnels def get_lcp_by_interface(self, sw_if_index): - for idx, lcp in self.cache['lcps'].items(): + for idx, lcp in self.cache["lcps"].items(): if lcp.phy_sw_if_index == sw_if_index: return lcp return None def tap_is_lcp(self, tap_ifname): - """ Returns True if the given tap_ifname is a TAP interface belonging to an LCP, - or False otherwise.""" - if not tap_ifname in self.cache['interface_names']: + """Returns True if the given tap_ifname is a TAP interface belonging to an LCP, + or False otherwise.""" + if not tap_ifname in self.cache["interface_names"]: return False - vpp_iface = self.cache['interface_names'][tap_ifname] - if not vpp_iface.interface_dev_type=="virtio": + vpp_iface = self.cache["interface_names"][tap_ifname] + if not vpp_iface.interface_dev_type == "virtio": return False - for idx, lcp in self.cache['lcps'].items(): + for idx, lcp in self.cache["lcps"].items(): if vpp_iface.sw_if_index == lcp.host_sw_if_index: return True return False