Study and respond to PyLint
Add a reasonably tolerant .pylintrc and fix most pylint errors and warnings. ------------------------------------------------------------------ Your code has been rated at 9.78/10
This commit is contained in:
20
.pylintrc
Normal file
20
.pylintrc
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
[MASTER]
|
||||||
|
init-hook="from pylint.config import find_pylintrc; import os, sys; sys.path.append(os.path.dirname(find_pylintrc()))"
|
||||||
|
ignore-patterns=test_.*
|
||||||
|
|
||||||
|
[MESSAGES CONTROL]
|
||||||
|
|
||||||
|
# Pointless whinging
|
||||||
|
# R0201 = Method could be a function
|
||||||
|
# W0212 = Accessing protected attribute of client class
|
||||||
|
# W0613 = Unused argument
|
||||||
|
# C0301 = Line too long
|
||||||
|
# R0914 = Too many local variables
|
||||||
|
# C0111 = Missing docstring
|
||||||
|
# W1203 = Use lazy % formatting in logging functions
|
||||||
|
|
||||||
|
# Disable the message(s) with the given id(s).
|
||||||
|
disable=R0201,W0212,W0613,C0301,R0914,C0111,W1203
|
||||||
|
|
||||||
|
[FORMAT]
|
||||||
|
max-line-length=148
|
@ -20,6 +20,7 @@ sudo pip3 install netaddr
|
|||||||
sudo pip3 install ipaddress
|
sudo pip3 install ipaddress
|
||||||
sudo pip3 install pyinstaller
|
sudo pip3 install pyinstaller
|
||||||
sudo pip3 install black
|
sudo pip3 install black
|
||||||
|
sudo pip3 install pylint
|
||||||
|
|
||||||
## Ensure all unittests pass.
|
## Ensure all unittests pass.
|
||||||
./tests.py -d -t unittest/yaml/*.yaml
|
./tests.py -d -t unittest/yaml/*.yaml
|
||||||
|
@ -29,6 +29,8 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
print("ERROR: install yamale manually: sudo pip install yamale")
|
print("ERROR: install yamale manually: sudo pip install yamale")
|
||||||
sys.exit(-2)
|
sys.exit(-2)
|
||||||
|
from yamale.validators import DefaultValidators, Validator
|
||||||
|
|
||||||
from config.loopback import validate_loopbacks
|
from config.loopback import validate_loopbacks
|
||||||
from config.bondethernet import validate_bondethernets
|
from config.bondethernet import validate_bondethernets
|
||||||
from config.interface import validate_interfaces
|
from config.interface import validate_interfaces
|
||||||
@ -36,8 +38,6 @@ from config.bridgedomain import validate_bridgedomains
|
|||||||
from config.vxlan_tunnel import validate_vxlan_tunnels
|
from config.vxlan_tunnel import validate_vxlan_tunnels
|
||||||
from config.tap import validate_taps
|
from config.tap import validate_taps
|
||||||
|
|
||||||
from yamale.validators import DefaultValidators, Validator
|
|
||||||
|
|
||||||
|
|
||||||
class IPInterfaceWithPrefixLength(Validator):
|
class IPInterfaceWithPrefixLength(Validator):
|
||||||
"""Custom IPAddress config - takes IP/prefixlen as input:
|
"""Custom IPAddress config - takes IP/prefixlen as input:
|
||||||
@ -50,22 +50,22 @@ class IPInterfaceWithPrefixLength(Validator):
|
|||||||
|
|
||||||
def _is_valid(self, value):
|
def _is_valid(self, value):
|
||||||
try:
|
try:
|
||||||
network = ipaddress.ip_interface(value)
|
_network = ipaddress.ip_interface(value)
|
||||||
except:
|
except:
|
||||||
return False
|
return False
|
||||||
if not isinstance(value, str):
|
if not isinstance(value, str):
|
||||||
return False
|
return False
|
||||||
if not "/" in value:
|
if not "/" in value:
|
||||||
return False
|
return False
|
||||||
e = value.split("/")
|
elems = value.split("/")
|
||||||
if not len(e) == 2:
|
if not len(elems) == 2:
|
||||||
return False
|
return False
|
||||||
if not e[1].isnumeric():
|
if not elems[1].isnumeric():
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
class Validator(object):
|
class Validator:
|
||||||
def __init__(self, schema):
|
def __init__(self, schema):
|
||||||
self.logger = logging.getLogger("vppcfg.config")
|
self.logger = logging.getLogger("vppcfg.config")
|
||||||
self.logger.addHandler(logging.NullHandler())
|
self.logger.addHandler(logging.NullHandler())
|
||||||
@ -81,52 +81,52 @@ class Validator(object):
|
|||||||
]
|
]
|
||||||
|
|
||||||
def validate(self, yaml):
|
def validate(self, yaml):
|
||||||
ret_rv = True
|
ret_retval = True
|
||||||
ret_msgs = []
|
ret_msgs = []
|
||||||
if not yaml:
|
if not yaml:
|
||||||
return ret_rv, ret_msgs
|
return ret_retval, ret_msgs
|
||||||
|
|
||||||
validators = DefaultValidators.copy()
|
validators = DefaultValidators.copy()
|
||||||
validators[IPInterfaceWithPrefixLength.tag] = IPInterfaceWithPrefixLength
|
validators[IPInterfaceWithPrefixLength.tag] = IPInterfaceWithPrefixLength
|
||||||
if self.schema:
|
if self.schema:
|
||||||
fn = self.schema
|
fname = self.schema
|
||||||
self.logger.debug(f"Validating against --schema {fn}")
|
self.logger.debug(f"Validating against --schema {fname}")
|
||||||
elif hasattr(sys, "_MEIPASS"):
|
elif hasattr(sys, "_MEIPASS"):
|
||||||
## See vppcfg.spec data_files that includes schema.yaml into the bundle
|
## See vppcfg.spec data_files that includes schema.yaml into the bundle
|
||||||
self.logger.debug("Validating against built-in schema")
|
self.logger.debug("Validating against built-in schema")
|
||||||
fn = os.path.join(sys._MEIPASS, "schema.yaml")
|
fname = os.path.join(sys._MEIPASS, "schema.yaml")
|
||||||
else:
|
else:
|
||||||
fn = "./schema.yaml"
|
fname = "./schema.yaml"
|
||||||
self.logger.debug(f"Validating against fallthrough default schema {fn}")
|
self.logger.debug(f"Validating against fallthrough default schema {fname}")
|
||||||
|
|
||||||
if not os.path.isfile(fn):
|
if not os.path.isfile(fname):
|
||||||
self.logger.error(f"Cannot file schema file: {fn}")
|
self.logger.error(f"Cannot file schema file: {fname}")
|
||||||
return False, ret_msgs
|
return False, ret_msgs
|
||||||
|
|
||||||
try:
|
try:
|
||||||
schema = yamale.make_schema(fn, validators=validators)
|
schema = yamale.make_schema(fname, validators=validators)
|
||||||
data = yamale.make_data(content=str(yaml))
|
data = yamale.make_data(content=str(yaml))
|
||||||
yamale.validate(schema, data)
|
yamale.validate(schema, data)
|
||||||
self.logger.debug("Schema correctly validated by yamale")
|
self.logger.debug("Schema correctly validated by yamale")
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
ret_rv = False
|
ret_retval = False
|
||||||
for result in e.results:
|
for result in e.results:
|
||||||
for error in result.errors:
|
for error in result.errors:
|
||||||
ret_msgs.extend([f"yamale: {error}"])
|
ret_msgs.extend([f"yamale: {error}"])
|
||||||
return ret_rv, ret_msgs
|
return ret_retval, ret_msgs
|
||||||
|
|
||||||
self.logger.debug("Validating Semantics...")
|
self.logger.debug("Validating Semantics...")
|
||||||
|
|
||||||
for v in self.validators:
|
for validator in self.validators:
|
||||||
rv, msgs = v(yaml)
|
retval, msgs = validator(yaml)
|
||||||
if msgs:
|
if msgs:
|
||||||
ret_msgs.extend(msgs)
|
ret_msgs.extend(msgs)
|
||||||
if not rv:
|
if not retval:
|
||||||
ret_rv = False
|
ret_retval = False
|
||||||
|
|
||||||
if ret_rv:
|
if ret_retval:
|
||||||
self.logger.debug("Semantics correctly validated")
|
self.logger.debug("Semantics correctly validated")
|
||||||
return ret_rv, ret_msgs
|
return ret_retval, ret_msgs
|
||||||
|
|
||||||
def valid_config(self, yaml):
|
def valid_config(self, yaml):
|
||||||
"""Validate the given YAML configuration in 'yaml' against syntax
|
"""Validate the given YAML configuration in 'yaml' against syntax
|
||||||
@ -135,10 +135,10 @@ class Validator(object):
|
|||||||
Returns True if the configuration is valid, False otherwise.
|
Returns True if the configuration is valid, False otherwise.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
rv, msgs = self.validate(yaml)
|
retval, msgs = self.validate(yaml)
|
||||||
if not rv:
|
if not retval:
|
||||||
for m in msgs:
|
for msg in msgs:
|
||||||
self.logger.error(m)
|
self.logger.error(msg)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.logger.info("Configuration validated successfully")
|
self.logger.info("Configuration validated successfully")
|
||||||
|
@ -11,8 +11,6 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
import logging
|
|
||||||
import config.interface as interface
|
|
||||||
import ipaddress
|
import ipaddress
|
||||||
|
|
||||||
|
|
||||||
@ -27,8 +25,8 @@ def get_all_addresses_except_ifname(yaml, except_ifname):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
if "addresses" in iface:
|
if "addresses" in iface:
|
||||||
for a in iface["addresses"]:
|
for addr in iface["addresses"]:
|
||||||
ret.append(ipaddress.ip_interface(a))
|
ret.append(ipaddress.ip_interface(addr))
|
||||||
if "sub-interfaces" in iface:
|
if "sub-interfaces" in iface:
|
||||||
for subid, sub_iface in iface["sub-interfaces"].items():
|
for subid, sub_iface in iface["sub-interfaces"].items():
|
||||||
sub_ifname = f"{ifname}.{int(subid)}"
|
sub_ifname = f"{ifname}.{int(subid)}"
|
||||||
@ -36,24 +34,24 @@ def get_all_addresses_except_ifname(yaml, except_ifname):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
if "addresses" in sub_iface:
|
if "addresses" in sub_iface:
|
||||||
for a in sub_iface["addresses"]:
|
for addr in sub_iface["addresses"]:
|
||||||
ret.append(ipaddress.ip_interface(a))
|
ret.append(ipaddress.ip_interface(addr))
|
||||||
if "loopbacks" in yaml:
|
if "loopbacks" in yaml:
|
||||||
for ifname, iface in yaml["loopbacks"].items():
|
for ifname, iface in yaml["loopbacks"].items():
|
||||||
if ifname == except_ifname:
|
if ifname == except_ifname:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if "addresses" in iface:
|
if "addresses" in iface:
|
||||||
for a in iface["addresses"]:
|
for addr in iface["addresses"]:
|
||||||
ret.append(ipaddress.ip_interface(a))
|
ret.append(ipaddress.ip_interface(addr))
|
||||||
if "bridgedomains" in yaml:
|
if "bridgedomains" in yaml:
|
||||||
for ifname, iface in yaml["bridgedomains"].items():
|
for ifname, iface in yaml["bridgedomains"].items():
|
||||||
if ifname == except_ifname:
|
if ifname == except_ifname:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if "addresses" in iface:
|
if "addresses" in iface:
|
||||||
for a in iface["addresses"]:
|
for addr in iface["addresses"]:
|
||||||
ret.append(ipaddress.ip_interface(a))
|
ret.append(ipaddress.ip_interface(addr))
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
@ -99,8 +97,8 @@ def is_allowed(yaml, ifname, iface_addresses, ip_interface):
|
|||||||
if my_ip_network.subnet_of(ipaddress.ip_network(ipi, strict=False)):
|
if my_ip_network.subnet_of(ipaddress.ip_network(ipi, strict=False)):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
for ip in iface_addresses:
|
for addr in iface_addresses:
|
||||||
ipi = ipaddress.ip_interface(ip)
|
ipi = ipaddress.ip_interface(addr)
|
||||||
if ipi.version != my_ip_network.version:
|
if ipi.version != my_ip_network.version:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -12,15 +12,15 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
import logging
|
import logging
|
||||||
import config.interface as interface
|
from config import interface
|
||||||
import config.mac as mac
|
from config import mac
|
||||||
|
|
||||||
|
|
||||||
def get_bondethernets(yaml):
|
def get_bondethernets(yaml):
|
||||||
"""Return a list of all bondethernets."""
|
"""Return a list of all bondethernets."""
|
||||||
ret = []
|
ret = []
|
||||||
if "bondethernets" in yaml:
|
if "bondethernets" in yaml:
|
||||||
for ifname, iface in yaml["bondethernets"].items():
|
for ifname, _iface in yaml["bondethernets"].items():
|
||||||
ret.append(ifname)
|
ret.append(ifname)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ def get_by_name(yaml, ifname):
|
|||||||
try:
|
try:
|
||||||
if ifname in yaml["bondethernets"]:
|
if ifname in yaml["bondethernets"]:
|
||||||
return ifname, yaml["bondethernets"][ifname]
|
return ifname, yaml["bondethernets"][ifname]
|
||||||
except:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
@ -38,7 +38,7 @@ def get_by_name(yaml, ifname):
|
|||||||
def is_bondethernet(yaml, ifname):
|
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)
|
ifname, iface = get_by_name(yaml, ifname)
|
||||||
return not iface == None
|
return iface is not None
|
||||||
|
|
||||||
|
|
||||||
def is_bond_member(yaml, ifname):
|
def is_bond_member(yaml, ifname):
|
||||||
@ -46,7 +46,7 @@ def is_bond_member(yaml, ifname):
|
|||||||
if not "bondethernets" in yaml:
|
if not "bondethernets" in yaml:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
for bond, iface in yaml["bondethernets"].items():
|
for _bond, iface in yaml["bondethernets"].items():
|
||||||
if not "interfaces" in iface:
|
if not "interfaces" in iface:
|
||||||
continue
|
continue
|
||||||
if ifname in iface["interfaces"]:
|
if ifname in iface["interfaces"]:
|
||||||
@ -78,7 +78,7 @@ def mode_to_int(mode):
|
|||||||
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:
|
try:
|
||||||
return ret[mode]
|
return ret[mode]
|
||||||
except:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
return -1
|
return -1
|
||||||
|
|
||||||
@ -92,7 +92,7 @@ def int_to_mode(mode):
|
|||||||
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:
|
try:
|
||||||
return ret[mode]
|
return ret[mode]
|
||||||
except:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
@ -109,7 +109,7 @@ def get_lb(yaml, ifname):
|
|||||||
if not iface:
|
if not iface:
|
||||||
return None
|
return None
|
||||||
mode = get_mode(yaml, ifname)
|
mode = get_mode(yaml, ifname)
|
||||||
if not mode in ["xor", "lacp"]:
|
if mode not in ["xor", "lacp"]:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if not "load-balance" in iface:
|
if not "load-balance" in iface:
|
||||||
@ -117,7 +117,7 @@ def get_lb(yaml, ifname):
|
|||||||
return iface["load-balance"]
|
return iface["load-balance"]
|
||||||
|
|
||||||
|
|
||||||
def lb_to_int(lb):
|
def lb_to_int(loadbalance):
|
||||||
"""Returns the integer representation in VPP of a given load-balance strategy,
|
"""Returns the integer representation in VPP of a given load-balance strategy,
|
||||||
or -1 if 'lb' is not a valid string.
|
or -1 if 'lb' is not a valid string.
|
||||||
|
|
||||||
@ -133,13 +133,13 @@ def lb_to_int(lb):
|
|||||||
"active-backup": 5,
|
"active-backup": 5,
|
||||||
}
|
}
|
||||||
try:
|
try:
|
||||||
return ret[lb]
|
return ret[loadbalance]
|
||||||
except:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
return -1
|
return -1
|
||||||
|
|
||||||
|
|
||||||
def int_to_lb(lb):
|
def int_to_lb(loadbalance):
|
||||||
"""Returns the string representation in VPP of a given load-balance strategy,
|
"""Returns the string representation in VPP of a given load-balance strategy,
|
||||||
or "" if 'lb' is not a valid int.
|
or "" if 'lb' is not a valid int.
|
||||||
|
|
||||||
@ -155,8 +155,8 @@ def int_to_lb(lb):
|
|||||||
5: "active-backup",
|
5: "active-backup",
|
||||||
}
|
}
|
||||||
try:
|
try:
|
||||||
return ret[lb]
|
return ret[loadbalance]
|
||||||
except:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
@ -186,7 +186,7 @@ def validate_bondethernets(yaml):
|
|||||||
)
|
)
|
||||||
result = False
|
result = False
|
||||||
if (
|
if (
|
||||||
not get_mode(yaml, bond_ifname) in ["xor", "lacp"]
|
get_mode(yaml, bond_ifname) not in ["xor", "lacp"]
|
||||||
and "load-balance" in iface
|
and "load-balance" in iface
|
||||||
):
|
):
|
||||||
msgs.append(
|
msgs.append(
|
||||||
|
@ -12,10 +12,8 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
import logging
|
import logging
|
||||||
import config.interface as interface
|
from config import interface
|
||||||
import config.loopback as loopback
|
from config import loopback
|
||||||
import config.lcp as lcp
|
|
||||||
import config.address as address
|
|
||||||
|
|
||||||
|
|
||||||
def get_bridgedomains(yaml):
|
def get_bridgedomains(yaml):
|
||||||
@ -23,7 +21,7 @@ def get_bridgedomains(yaml):
|
|||||||
ret = []
|
ret = []
|
||||||
if not "bridgedomains" in yaml:
|
if not "bridgedomains" in yaml:
|
||||||
return ret
|
return ret
|
||||||
for ifname, iface in yaml["bridgedomains"].items():
|
for ifname, _iface in yaml["bridgedomains"].items():
|
||||||
ret.append(ifname)
|
ret.append(ifname)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
@ -33,7 +31,7 @@ def get_by_name(yaml, ifname):
|
|||||||
try:
|
try:
|
||||||
if ifname in yaml["bridgedomains"]:
|
if ifname in yaml["bridgedomains"]:
|
||||||
return ifname, yaml["bridgedomains"][ifname]
|
return ifname, yaml["bridgedomains"][ifname]
|
||||||
except:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
@ -41,7 +39,7 @@ def get_by_name(yaml, ifname):
|
|||||||
def is_bridgedomain(yaml, ifname):
|
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)
|
ifname, iface = get_by_name(yaml, ifname)
|
||||||
return not iface == None
|
return iface is not None
|
||||||
|
|
||||||
|
|
||||||
def get_bridge_interfaces(yaml):
|
def get_bridge_interfaces(yaml):
|
||||||
@ -51,7 +49,7 @@ def get_bridge_interfaces(yaml):
|
|||||||
if not "bridgedomains" in yaml:
|
if not "bridgedomains" in yaml:
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
for ifname, iface in yaml["bridgedomains"].items():
|
for _ifname, iface in yaml["bridgedomains"].items():
|
||||||
if "interfaces" in iface:
|
if "interfaces" in iface:
|
||||||
ret.extend(iface["interfaces"])
|
ret.extend(iface["interfaces"])
|
||||||
|
|
||||||
@ -75,11 +73,11 @@ def bvi_unique(yaml, bviname):
|
|||||||
"""Returns True if the BVI identified by bviname is unique among all BridgeDomains."""
|
"""Returns True if the BVI identified by bviname is unique among all BridgeDomains."""
|
||||||
if not "bridgedomains" in yaml:
|
if not "bridgedomains" in yaml:
|
||||||
return True
|
return True
|
||||||
n = 0
|
ncount = 0
|
||||||
for ifname, iface in yaml["bridgedomains"].items():
|
for _ifname, iface in yaml["bridgedomains"].items():
|
||||||
if "bvi" in iface and iface["bvi"] == bviname:
|
if "bvi" in iface and iface["bvi"] == bviname:
|
||||||
n += 1
|
ncount += 1
|
||||||
return n < 2
|
return ncount < 2
|
||||||
|
|
||||||
|
|
||||||
def get_settings(yaml, ifname):
|
def get_settings(yaml, ifname):
|
||||||
@ -141,7 +139,6 @@ def validate_bridgedomains(yaml):
|
|||||||
result = False
|
result = False
|
||||||
|
|
||||||
if "bvi" in iface:
|
if "bvi" in iface:
|
||||||
bviname = iface["bvi"]
|
|
||||||
bvi_ifname, bvi_iface = loopback.get_by_name(yaml, iface["bvi"])
|
bvi_ifname, bvi_iface = loopback.get_by_name(yaml, iface["bvi"])
|
||||||
if not bvi_unique(yaml, bvi_ifname):
|
if not bvi_unique(yaml, bvi_ifname):
|
||||||
msgs.append(f"bridgedomain {ifname} BVI {bvi_ifname} is not unique")
|
msgs.append(f"bridgedomain {ifname} BVI {bvi_ifname} is not unique")
|
||||||
|
@ -12,14 +12,14 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
import logging
|
import logging
|
||||||
import config.bondethernet as bondethernet
|
from config import bondethernet
|
||||||
import config.bridgedomain as bridgedomain
|
from config import bridgedomain
|
||||||
import config.loopback as loopback
|
from config import loopback
|
||||||
import config.vxlan_tunnel as vxlan_tunnel
|
from config import vxlan_tunnel
|
||||||
import config.lcp as lcp
|
from config import lcp
|
||||||
import config.address as address
|
from config import address
|
||||||
import config.mac as mac
|
from config import mac
|
||||||
import config.tap as tap
|
from config import tap
|
||||||
|
|
||||||
|
|
||||||
def get_qinx_parent_by_name(yaml, ifname):
|
def get_qinx_parent_by_name(yaml, ifname):
|
||||||
@ -28,7 +28,7 @@ def get_qinx_parent_by_name(yaml, ifname):
|
|||||||
|
|
||||||
if not is_qinx(yaml, ifname):
|
if not is_qinx(yaml, ifname):
|
||||||
return None, None
|
return None, None
|
||||||
qinx_ifname, qinx_iface = get_by_name(yaml, ifname)
|
_qinx_ifname, qinx_iface = get_by_name(yaml, ifname)
|
||||||
if not qinx_iface:
|
if not qinx_iface:
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
@ -54,12 +54,17 @@ def get_qinx_parent_by_name(yaml, ifname):
|
|||||||
|
|
||||||
def get_parent_by_name(yaml, ifname):
|
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."""
|
||||||
|
if not ifname:
|
||||||
|
return None, None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
parent_ifname, subid = ifname.split(".")
|
parent_ifname, subid = ifname.split(".")
|
||||||
subid = int(subid)
|
subid = int(subid)
|
||||||
iface = yaml["interfaces"][parent_ifname]
|
iface = yaml["interfaces"][parent_ifname]
|
||||||
return parent_ifname, iface
|
return parent_ifname, iface
|
||||||
except:
|
except KeyError:
|
||||||
|
pass
|
||||||
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
@ -88,20 +93,22 @@ def get_by_name(yaml, ifname):
|
|||||||
subid = int(subid)
|
subid = int(subid)
|
||||||
iface = yaml["interfaces"][phy_ifname]["sub-interfaces"][subid]
|
iface = yaml["interfaces"][phy_ifname]["sub-interfaces"][subid]
|
||||||
return ifname, iface
|
return ifname, iface
|
||||||
except:
|
except ValueError:
|
||||||
|
return None, None
|
||||||
|
except KeyError:
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
iface = yaml["interfaces"][ifname]
|
iface = yaml["interfaces"][ifname]
|
||||||
return ifname, iface
|
return ifname, iface
|
||||||
except:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
|
|
||||||
def is_sub(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)
|
_parent_ifname, parent_iface = get_parent_by_name(yaml, ifname)
|
||||||
return isinstance(parent_iface, dict)
|
return isinstance(parent_iface, dict)
|
||||||
|
|
||||||
|
|
||||||
@ -153,11 +160,11 @@ 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 = []
|
ret = []
|
||||||
if "interfaces" in yaml:
|
if "interfaces" in yaml:
|
||||||
for ifname, iface in yaml["interfaces"].items():
|
for _ifname, iface in yaml["interfaces"].items():
|
||||||
if "l2xc" in iface:
|
if "l2xc" in iface:
|
||||||
ret.append(iface["l2xc"])
|
ret.append(iface["l2xc"])
|
||||||
if "sub-interfaces" in iface:
|
if "sub-interfaces" in iface:
|
||||||
for subid, sub_iface in iface["sub-interfaces"].items():
|
for _subid, sub_iface in iface["sub-interfaces"].items():
|
||||||
if "l2xc" in sub_iface:
|
if "l2xc" in sub_iface:
|
||||||
ret.append(sub_iface["l2xc"])
|
ret.append(sub_iface["l2xc"])
|
||||||
|
|
||||||
@ -200,11 +207,7 @@ def valid_encapsulation(yaml, ifname):
|
|||||||
return False
|
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
|
return False
|
||||||
if (
|
if "exact-match" in encap and not encap["exact-match"] and has_lcp(yaml, ifname):
|
||||||
"exact-match" in encap
|
|
||||||
and encap["exact-match"] == False
|
|
||||||
and has_lcp(yaml, ifname)
|
|
||||||
):
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
@ -227,10 +230,10 @@ def get_encapsulation(yaml, ifname):
|
|||||||
if not iface:
|
if not iface:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
parent_ifname, parent_iface = get_parent_by_name(yaml, ifname)
|
_parent_ifname, parent_iface = get_parent_by_name(yaml, ifname)
|
||||||
if not iface or not parent_iface:
|
if not iface or not parent_iface:
|
||||||
return None
|
return None
|
||||||
parent_ifname, subid = ifname.split(".")
|
_parent_ifname, subid = ifname.split(".")
|
||||||
|
|
||||||
dot1q = 0
|
dot1q = 0
|
||||||
dot1ad = 0
|
dot1ad = 0
|
||||||
@ -265,7 +268,7 @@ def get_phys(yaml):
|
|||||||
ret = []
|
ret = []
|
||||||
if not "interfaces" in yaml:
|
if not "interfaces" in yaml:
|
||||||
return ret
|
return ret
|
||||||
for ifname, iface in yaml["interfaces"].items():
|
for ifname, _iface in yaml["interfaces"].items():
|
||||||
if is_phy(yaml, ifname):
|
if is_phy(yaml, ifname):
|
||||||
ret.append(ifname)
|
ret.append(ifname)
|
||||||
return ret
|
return ret
|
||||||
@ -275,7 +278,7 @@ 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)
|
ifname, iface = get_by_name(yaml, ifname)
|
||||||
if iface == None:
|
if iface is None:
|
||||||
return False
|
return False
|
||||||
if is_sub(yaml, ifname):
|
if is_sub(yaml, ifname):
|
||||||
return False
|
return False
|
||||||
@ -300,7 +303,7 @@ def get_interfaces(yaml):
|
|||||||
ret.append(ifname)
|
ret.append(ifname)
|
||||||
if not "sub-interfaces" in iface:
|
if not "sub-interfaces" in iface:
|
||||||
continue
|
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)}")
|
ret.append(f"{ifname}.{int(subid)}")
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
@ -351,7 +354,7 @@ def unique_encapsulation(yaml, sub_ifname):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
ncount = 0
|
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_ifname = f"{parent_ifname}.{int(subid)}"
|
||||||
sibling_encap = get_encapsulation(yaml, sibling_ifname)
|
sibling_encap = get_encapsulation(yaml, sibling_ifname)
|
||||||
if sub_encap == sibling_encap and new_ifname != sibling_ifname:
|
if sub_encap == sibling_encap and new_ifname != sibling_ifname:
|
||||||
@ -391,16 +394,12 @@ 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."""
|
return 1500 if no MTU was set on the sub-int or the parent."""
|
||||||
ifname, iface = get_by_name(yaml, ifname)
|
ifname, iface = get_by_name(yaml, ifname)
|
||||||
if not iface:
|
if iface and "mtu" in iface:
|
||||||
return 1500
|
|
||||||
|
|
||||||
parent_ifname, parent_iface = get_parent_by_name(yaml, ifname)
|
|
||||||
|
|
||||||
try:
|
|
||||||
return iface["mtu"]
|
return iface["mtu"]
|
||||||
|
|
||||||
|
_parent_ifname, parent_iface = get_parent_by_name(yaml, ifname)
|
||||||
|
if parent_iface and "mtu" in parent_iface:
|
||||||
return parent_iface["mtu"]
|
return parent_iface["mtu"]
|
||||||
except:
|
|
||||||
pass
|
|
||||||
return 1500
|
return 1500
|
||||||
|
|
||||||
|
|
||||||
@ -451,7 +450,7 @@ def validate_interfaces(yaml):
|
|||||||
iface_address = has_address(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)
|
_tap_ifname, tap_iface = tap.get_by_name(yaml, ifname)
|
||||||
if not tap_iface:
|
if not tap_iface:
|
||||||
msgs.append(f"interface {ifname} is a TAP but does not exist in taps")
|
msgs.append(f"interface {ifname} is a TAP but does not exist in taps")
|
||||||
result = False
|
result = False
|
||||||
@ -489,10 +488,10 @@ def validate_interfaces(yaml):
|
|||||||
result = False
|
result = False
|
||||||
|
|
||||||
if "addresses" in iface:
|
if "addresses" in iface:
|
||||||
for a in iface["addresses"]:
|
for addr in iface["addresses"]:
|
||||||
if not address.is_allowed(yaml, ifname, iface["addresses"], a):
|
if not address.is_allowed(yaml, ifname, iface["addresses"], addr):
|
||||||
msgs.append(
|
msgs.append(
|
||||||
f"interface {ifname} IP address {a} conflicts with another"
|
f"interface {ifname} IP address {addr} conflicts with another"
|
||||||
)
|
)
|
||||||
result = False
|
result = False
|
||||||
|
|
||||||
@ -624,12 +623,12 @@ def validate_interfaces(yaml):
|
|||||||
f"sub-interface {sub_ifname} is in L2 mode but has an address"
|
f"sub-interface {sub_ifname} is in L2 mode but has an address"
|
||||||
)
|
)
|
||||||
result = False
|
result = False
|
||||||
for a in sub_iface["addresses"]:
|
for addr in sub_iface["addresses"]:
|
||||||
if not address.is_allowed(
|
if not address.is_allowed(
|
||||||
yaml, sub_ifname, sub_iface["addresses"], a
|
yaml, sub_ifname, sub_iface["addresses"], addr
|
||||||
):
|
):
|
||||||
msgs.append(
|
msgs.append(
|
||||||
f"sub-interface {sub_ifname} IP address {a} conflicts with another"
|
f"sub-interface {sub_ifname} IP address {addr} conflicts with another"
|
||||||
)
|
)
|
||||||
result = False
|
result = False
|
||||||
if not valid_encapsulation(yaml, sub_ifname):
|
if not valid_encapsulation(yaml, sub_ifname):
|
||||||
|
@ -11,7 +11,6 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
import logging
|
|
||||||
|
|
||||||
|
|
||||||
def get_lcps(yaml, interfaces=True, loopbacks=True, bridgedomains=True):
|
def get_lcps(yaml, interfaces=True, loopbacks=True, bridgedomains=True):
|
||||||
@ -20,20 +19,20 @@ def get_lcps(yaml, interfaces=True, loopbacks=True, bridgedomains=True):
|
|||||||
|
|
||||||
ret = []
|
ret = []
|
||||||
if interfaces and "interfaces" in yaml:
|
if interfaces and "interfaces" in yaml:
|
||||||
for ifname, iface in yaml["interfaces"].items():
|
for _ifname, iface in yaml["interfaces"].items():
|
||||||
if "lcp" in iface:
|
if "lcp" in iface:
|
||||||
ret.append(iface["lcp"])
|
ret.append(iface["lcp"])
|
||||||
if "sub-interfaces" in iface:
|
if "sub-interfaces" in iface:
|
||||||
for subid, sub_iface in iface["sub-interfaces"].items():
|
for _subid, sub_iface in iface["sub-interfaces"].items():
|
||||||
if "lcp" in sub_iface:
|
if "lcp" in sub_iface:
|
||||||
ret.append(sub_iface["lcp"])
|
ret.append(sub_iface["lcp"])
|
||||||
|
|
||||||
if loopbacks and "loopbacks" in yaml:
|
if loopbacks and "loopbacks" in yaml:
|
||||||
for ifname, iface in yaml["loopbacks"].items():
|
for _ifname, iface in yaml["loopbacks"].items():
|
||||||
if "lcp" in iface:
|
if "lcp" in iface:
|
||||||
ret.append(iface["lcp"])
|
ret.append(iface["lcp"])
|
||||||
if bridgedomains and "bridgedomains" in yaml:
|
if bridgedomains and "bridgedomains" in yaml:
|
||||||
for ifname, iface in yaml["bridgedomains"].items():
|
for _ifname, iface in yaml["bridgedomains"].items():
|
||||||
if "lcp" in iface:
|
if "lcp" in iface:
|
||||||
ret.append(iface["lcp"])
|
ret.append(iface["lcp"])
|
||||||
|
|
||||||
|
@ -12,16 +12,16 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
import logging
|
import logging
|
||||||
import config.lcp as lcp
|
from config import lcp
|
||||||
import config.address as address
|
from config import address
|
||||||
import config.mac as mac
|
from config import mac
|
||||||
|
|
||||||
|
|
||||||
def get_loopbacks(yaml):
|
def get_loopbacks(yaml):
|
||||||
"""Return a list of all loopbacks."""
|
"""Return a list of all loopbacks."""
|
||||||
ret = []
|
ret = []
|
||||||
if "loopbacks" in yaml:
|
if "loopbacks" in yaml:
|
||||||
for ifname, iface in yaml["loopbacks"].items():
|
for ifname, _iface in yaml["loopbacks"].items():
|
||||||
ret.append(ifname)
|
ret.append(ifname)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
@ -41,7 +41,7 @@ def get_by_name(yaml, ifname):
|
|||||||
try:
|
try:
|
||||||
if ifname in yaml["loopbacks"]:
|
if ifname in yaml["loopbacks"]:
|
||||||
return ifname, yaml["loopbacks"][ifname]
|
return ifname, yaml["loopbacks"][ifname]
|
||||||
except:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
@ -49,7 +49,7 @@ def get_by_name(yaml, ifname):
|
|||||||
def is_loopback(yaml, ifname):
|
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)
|
ifname, iface = get_by_name(yaml, ifname)
|
||||||
return not iface == None
|
return iface is not None
|
||||||
|
|
||||||
|
|
||||||
def validate_loopbacks(yaml):
|
def validate_loopbacks(yaml):
|
||||||
@ -75,10 +75,10 @@ def validate_loopbacks(yaml):
|
|||||||
)
|
)
|
||||||
result = False
|
result = False
|
||||||
if "addresses" in iface:
|
if "addresses" in iface:
|
||||||
for a in iface["addresses"]:
|
for addr in iface["addresses"]:
|
||||||
if not address.is_allowed(yaml, ifname, iface["addresses"], a):
|
if not address.is_allowed(yaml, ifname, iface["addresses"], addr):
|
||||||
msgs.append(
|
msgs.append(
|
||||||
f"loopback {ifname} IP address {a} conflicts with another"
|
f"loopback {ifname} IP address {addr} conflicts with another"
|
||||||
)
|
)
|
||||||
result = False
|
result = False
|
||||||
if "mac" in iface and mac.is_multicast(iface["mac"]):
|
if "mac" in iface and mac.is_multicast(iface["mac"]):
|
||||||
|
@ -11,7 +11,6 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
import logging
|
|
||||||
import netaddr
|
import netaddr
|
||||||
|
|
||||||
|
|
||||||
@ -19,7 +18,7 @@ def is_valid(mac):
|
|||||||
"""Return True if the string given in `mac` is a valid (6-byte) MAC address,
|
"""Return True if the string given in `mac` is a valid (6-byte) MAC address,
|
||||||
as defined by netaddr.EUI"""
|
as defined by netaddr.EUI"""
|
||||||
try:
|
try:
|
||||||
addr = netaddr.EUI(mac)
|
_addr = netaddr.EUI(mac)
|
||||||
except:
|
except:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
@ -12,14 +12,14 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
import logging
|
import logging
|
||||||
import config.mac as mac
|
from config import mac
|
||||||
|
|
||||||
|
|
||||||
def get_taps(yaml):
|
def get_taps(yaml):
|
||||||
"""Return a list of all taps."""
|
"""Return a list of all taps."""
|
||||||
ret = []
|
ret = []
|
||||||
if "taps" in yaml:
|
if "taps" in yaml:
|
||||||
for ifname, iface in yaml["taps"].items():
|
for ifname, _iface in yaml["taps"].items():
|
||||||
ret.append(ifname)
|
ret.append(ifname)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
@ -29,7 +29,7 @@ def get_by_name(yaml, ifname):
|
|||||||
try:
|
try:
|
||||||
if ifname in yaml["taps"]:
|
if ifname in yaml["taps"]:
|
||||||
return ifname, yaml["taps"][ifname]
|
return ifname, yaml["taps"][ifname]
|
||||||
except:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
@ -40,7 +40,7 @@ def is_tap(yaml, ifname):
|
|||||||
a TAP belonging to a Linux Control Plane (LCP) will return False.
|
a TAP belonging to a Linux Control Plane (LCP) will return False.
|
||||||
"""
|
"""
|
||||||
ifname, iface = get_by_name(yaml, ifname)
|
ifname, iface = get_by_name(yaml, ifname)
|
||||||
return not iface == None
|
return iface is not None
|
||||||
|
|
||||||
|
|
||||||
def is_host_name_unique(yaml, hostname):
|
def is_host_name_unique(yaml, hostname):
|
||||||
@ -48,7 +48,7 @@ def is_host_name_unique(yaml, hostname):
|
|||||||
if not "taps" in yaml:
|
if not "taps" in yaml:
|
||||||
return True
|
return True
|
||||||
host_names = []
|
host_names = []
|
||||||
for tap_ifname, tap_iface in yaml["taps"].items():
|
for _tap_ifname, tap_iface in yaml["taps"].items():
|
||||||
host_names.append(tap_iface["host"]["name"])
|
host_names.append(tap_iface["host"]["name"])
|
||||||
return host_names.count(hostname) < 2
|
return host_names.count(hostname) < 2
|
||||||
|
|
||||||
@ -78,14 +78,14 @@ def validate_taps(yaml):
|
|||||||
result = False
|
result = False
|
||||||
|
|
||||||
if "rx-ring-size" in iface:
|
if "rx-ring-size" in iface:
|
||||||
n = iface["rx-ring-size"]
|
ncount = iface["rx-ring-size"]
|
||||||
if n & (n - 1) != 0:
|
if ncount & (ncount - 1) != 0:
|
||||||
msgs.append(f"tap {ifname} rx-ring-size must be a power of two")
|
msgs.append(f"tap {ifname} rx-ring-size must be a power of two")
|
||||||
result = False
|
result = False
|
||||||
|
|
||||||
if "tx-ring-size" in iface:
|
if "tx-ring-size" in iface:
|
||||||
n = iface["tx-ring-size"]
|
ncount = iface["tx-ring-size"]
|
||||||
if n & (n - 1) != 0:
|
if ncount & (ncount - 1) != 0:
|
||||||
msgs.append(f"tap {ifname} tx-ring-size must be a power of two")
|
msgs.append(f"tap {ifname} tx-ring-size must be a power of two")
|
||||||
result = False
|
result = False
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ class TestInterfaceMethods(unittest.TestCase):
|
|||||||
def test_mtu(self):
|
def test_mtu(self):
|
||||||
self.assertEqual(interface.get_mtu(self.cfg, "GigabitEthernet1/0/1"), 9216)
|
self.assertEqual(interface.get_mtu(self.cfg, "GigabitEthernet1/0/1"), 9216)
|
||||||
self.assertEqual(interface.get_mtu(self.cfg, "GigabitEthernet1/0/1.200"), 9000)
|
self.assertEqual(interface.get_mtu(self.cfg, "GigabitEthernet1/0/1.200"), 9000)
|
||||||
self.assertEqual(interface.get_mtu(self.cfg, "GigabitEthernet1/0/1.201"), 1500)
|
self.assertEqual(interface.get_mtu(self.cfg, "GigabitEthernet1/0/1.201"), 9216)
|
||||||
|
|
||||||
def test_encapsulation(self):
|
def test_encapsulation(self):
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
import logging
|
import logging
|
||||||
import config.interface as interface
|
|
||||||
import ipaddress
|
import ipaddress
|
||||||
|
|
||||||
|
|
||||||
@ -21,7 +20,7 @@ def get_by_name(yaml, ifname):
|
|||||||
try:
|
try:
|
||||||
if ifname in yaml["vxlan_tunnels"]:
|
if ifname in yaml["vxlan_tunnels"]:
|
||||||
return ifname, yaml["vxlan_tunnels"][ifname]
|
return ifname, yaml["vxlan_tunnels"][ifname]
|
||||||
except:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
@ -29,7 +28,7 @@ def get_by_name(yaml, ifname):
|
|||||||
def is_vxlan_tunnel(yaml, ifname):
|
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)
|
ifname, iface = get_by_name(yaml, ifname)
|
||||||
return not iface == None
|
return iface is not None
|
||||||
|
|
||||||
|
|
||||||
def vni_unique(yaml, vni):
|
def vni_unique(yaml, vni):
|
||||||
@ -38,7 +37,7 @@ def vni_unique(yaml, vni):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
ncount = 0
|
ncount = 0
|
||||||
for ifname, iface in yaml["vxlan_tunnels"].items():
|
for _ifname, iface in yaml["vxlan_tunnels"].items():
|
||||||
if iface["vni"] == vni:
|
if iface["vni"] == vni:
|
||||||
ncount = ncount + 1
|
ncount = ncount + 1
|
||||||
|
|
||||||
@ -51,7 +50,7 @@ def get_vxlan_tunnels(yaml):
|
|||||||
if not "vxlan_tunnels" in yaml:
|
if not "vxlan_tunnels" in yaml:
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
for ifname, iface in yaml["vxlan_tunnels"].items():
|
for ifname, _iface in yaml["vxlan_tunnels"].items():
|
||||||
ret.append(ifname)
|
ret.append(ifname)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ interfaces:
|
|||||||
mtu: 2000
|
mtu: 2000
|
||||||
l2xc: vxlan_tunnel0
|
l2xc: vxlan_tunnel0
|
||||||
101:
|
101:
|
||||||
|
mtu: 1500
|
||||||
l2xc: vxlan_tunnel1
|
l2xc: vxlan_tunnel1
|
||||||
GigabitEthernet3/0/1:
|
GigabitEthernet3/0/1:
|
||||||
lcp: "e3-0-1"
|
lcp: "e3-0-1"
|
||||||
|
82
tests.py
82
tests.py
@ -15,11 +15,11 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import yaml
|
|
||||||
from config import Validator
|
|
||||||
import glob
|
import glob
|
||||||
import re
|
import re
|
||||||
import unittest
|
import unittest
|
||||||
|
import yaml
|
||||||
|
from config import Validator
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import argparse
|
import argparse
|
||||||
@ -28,73 +28,71 @@ except ImportError:
|
|||||||
sys.exit(-2)
|
sys.exit(-2)
|
||||||
|
|
||||||
|
|
||||||
def example_validator(yaml):
|
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
|
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
|
return True, []
|
||||||
msgs = []
|
|
||||||
|
|
||||||
return rv, msgs
|
|
||||||
|
|
||||||
|
|
||||||
class YAMLTest(unittest.TestCase):
|
class YAMLTest(unittest.TestCase):
|
||||||
def __init__(self, testName, yaml_filename, yaml_schema):
|
def __init__(self, testName, yaml_filename, yaml_schema):
|
||||||
# calling the super class init varies for different python versions. This works for 2.7
|
# calling the super class init varies for different python versions. This works for 2.7
|
||||||
super(YAMLTest, self).__init__(testName)
|
super().__init__(testName)
|
||||||
self.yaml_filename = yaml_filename
|
self.yaml_filename = yaml_filename
|
||||||
self.yaml_schema = yaml_schema
|
self.yaml_schema = yaml_schema
|
||||||
|
|
||||||
def test_yaml(self):
|
def test_yaml(self):
|
||||||
unittest = None
|
test = None
|
||||||
cfg = None
|
cfg = None
|
||||||
n = 0
|
ncount = 0
|
||||||
try:
|
try:
|
||||||
with open(self.yaml_filename, "r") as f:
|
with open(self.yaml_filename, "r", encoding="utf-8") as file:
|
||||||
for data in yaml.load_all(f, Loader=yaml.Loader):
|
for data in yaml.load_all(file, Loader=yaml.Loader):
|
||||||
if n == 0:
|
if ncount == 0:
|
||||||
unittest = data
|
test = data
|
||||||
n = n + 1
|
ncount += 1
|
||||||
elif n == 1:
|
elif ncount == 1:
|
||||||
cfg = data
|
cfg = data
|
||||||
n = n + 1
|
ncount += 1
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
self.assertEqual(n, 2)
|
self.assertEqual(ncount, 2)
|
||||||
self.assertIsNotNone(unittest)
|
self.assertIsNotNone(test)
|
||||||
if not cfg:
|
if not cfg:
|
||||||
return
|
return
|
||||||
|
|
||||||
v = Validator(schema=self.yaml_schema)
|
validator = Validator(schema=self.yaml_schema)
|
||||||
rv, msgs = v.validate(cfg)
|
_rv, msgs = validator.validate(cfg)
|
||||||
|
|
||||||
msgs_unexpected = 0
|
|
||||||
msgs_expected = []
|
msgs_expected = []
|
||||||
if (
|
if (
|
||||||
"test" in unittest
|
"test" in test
|
||||||
and "errors" in unittest["test"]
|
and "errors" in test["test"]
|
||||||
and "expected" in unittest["test"]["errors"]
|
and "expected" in test["test"]["errors"]
|
||||||
):
|
):
|
||||||
msgs_expected = unittest["test"]["errors"]["expected"]
|
msgs_expected = test["test"]["errors"]["expected"]
|
||||||
|
|
||||||
fail = False
|
fail = False
|
||||||
for m in msgs:
|
for msg in msgs:
|
||||||
this_msg_expected = False
|
this_msg_expected = False
|
||||||
for expected in msgs_expected:
|
for expected in msgs_expected:
|
||||||
if re.match(expected, m):
|
if re.match(expected, msg):
|
||||||
this_msg_expected = True
|
this_msg_expected = True
|
||||||
break
|
break
|
||||||
if not this_msg_expected:
|
if not this_msg_expected:
|
||||||
print(f"{self.yaml_filename}: Unexpected message: {m}", file=sys.stderr)
|
print(
|
||||||
|
f"{self.yaml_filename}: Unexpected message: {msg}", file=sys.stderr
|
||||||
|
)
|
||||||
fail = True
|
fail = True
|
||||||
|
|
||||||
count = 0
|
count = 0
|
||||||
if (
|
if (
|
||||||
"test" in unittest
|
"test" in test
|
||||||
and "errors" in unittest["test"]
|
and "errors" in test["test"]
|
||||||
and "count" in unittest["test"]["errors"]
|
and "count" in test["test"]["errors"]
|
||||||
):
|
):
|
||||||
count = unittest["test"]["errors"]["count"]
|
count = test["test"]["errors"]["count"]
|
||||||
|
|
||||||
if len(msgs) != count:
|
if len(msgs) != count:
|
||||||
print(
|
print(
|
||||||
@ -143,11 +141,11 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
if args.debug:
|
if args.debug:
|
||||||
verbosity = 2
|
VERBOSITY = 2
|
||||||
elif args.quiet:
|
elif args.quiet:
|
||||||
verbosity = 0
|
VERBOSITY = 0
|
||||||
else:
|
else:
|
||||||
verbosity = 1
|
VERBOSITY = 1
|
||||||
yaml_suite = unittest.TestSuite()
|
yaml_suite = unittest.TestSuite()
|
||||||
for pattern in args.test:
|
for pattern in args.test:
|
||||||
for fn in glob.glob(pattern):
|
for fn in glob.glob(pattern):
|
||||||
@ -155,21 +153,21 @@ if __name__ == "__main__":
|
|||||||
YAMLTest("test_yaml", yaml_filename=fn, yaml_schema=args.schema)
|
YAMLTest("test_yaml", yaml_filename=fn, yaml_schema=args.schema)
|
||||||
)
|
)
|
||||||
yaml_ok = (
|
yaml_ok = (
|
||||||
unittest.TextTestRunner(verbosity=verbosity, buffer=True)
|
unittest.TextTestRunner(verbosity=VERBOSITY, buffer=True)
|
||||||
.run(yaml_suite)
|
.run(yaml_suite)
|
||||||
.wasSuccessful()
|
.wasSuccessful()
|
||||||
)
|
)
|
||||||
|
|
||||||
tests = unittest.TestLoader().discover(start_dir=".", pattern="test_*.py")
|
tests = unittest.TestLoader().discover(start_dir=".", pattern="test_*.py")
|
||||||
unit_ok = (
|
unit_ok = (
|
||||||
unittest.TextTestRunner(verbosity=verbosity, buffer=True)
|
unittest.TextTestRunner(verbosity=VERBOSITY, buffer=True)
|
||||||
.run(tests)
|
.run(tests)
|
||||||
.wasSuccessful()
|
.wasSuccessful()
|
||||||
)
|
)
|
||||||
|
|
||||||
retval = 0
|
RETVAL = 0
|
||||||
if not yaml_ok:
|
if not yaml_ok:
|
||||||
retval = retval - 1
|
RETVAL -= 1
|
||||||
if not unit_ok:
|
if not unit_ok:
|
||||||
retval = retval - 2
|
RETVAL -= 2
|
||||||
sys.exit(retval)
|
sys.exit(RETVAL)
|
||||||
|
@ -15,6 +15,7 @@ interfaces:
|
|||||||
mtu: 3000
|
mtu: 3000
|
||||||
sub-interfaces:
|
sub-interfaces:
|
||||||
1234:
|
1234:
|
||||||
|
mtu: 1500
|
||||||
description: "BD11 and BD12"
|
description: "BD11 and BD12"
|
||||||
|
|
||||||
bridgedomains:
|
bridgedomains:
|
||||||
|
@ -11,6 +11,8 @@ class Applier(VPPApi):
|
|||||||
and will ensure that the local cache is consistent after creations and
|
and will ensure that the local cache is consistent after creations and
|
||||||
modifications."""
|
modifications."""
|
||||||
|
|
||||||
|
# pylint: disable=unnecessary-pass
|
||||||
|
|
||||||
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)
|
VPPApi.__init__(self, address, clientname)
|
||||||
self.logger.info("VPP Applier: changing the dataplane is enabled")
|
self.logger.info("VPP Applier: changing the dataplane is enabled")
|
||||||
@ -47,9 +49,6 @@ class Applier(VPPApi):
|
|||||||
"""'config' is the YAML configuration for the vxlan_tunnels: entry"""
|
"""'config' is the YAML configuration for the vxlan_tunnels: entry"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def delete_subinterface(self, ifname):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def set_interface_link_mtu(self, ifname, link_mtu):
|
def set_interface_link_mtu(self, ifname, link_mtu):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -3,10 +3,10 @@ The functions in this file interact with the VPP API to retrieve certain
|
|||||||
interface metadata and write it to a YAML file.
|
interface metadata and write it to a YAML file.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from vpp.vppapi import VPPApi
|
|
||||||
import sys
|
import sys
|
||||||
import yaml
|
import yaml
|
||||||
import config.bondethernet as bondethernet
|
from vpp.vppapi import VPPApi
|
||||||
|
from config import bondethernet
|
||||||
|
|
||||||
|
|
||||||
class Dumper(VPPApi):
|
class Dumper(VPPApi):
|
||||||
@ -15,17 +15,17 @@ class Dumper(VPPApi):
|
|||||||
|
|
||||||
def write(self, outfile):
|
def write(self, outfile):
|
||||||
if outfile and outfile == "-":
|
if outfile and outfile == "-":
|
||||||
fh = sys.stdout
|
file = sys.stdout
|
||||||
outfile = "(stdout)"
|
outfile = "(stdout)"
|
||||||
else:
|
else:
|
||||||
fh = open(outfile, "w")
|
file = open(outfile, "w", encoding="utf-8")
|
||||||
|
|
||||||
config = self.cache_to_config()
|
config = self.cache_to_config()
|
||||||
|
|
||||||
print(yaml.dump(config), file=fh)
|
print(yaml.dump(config), file=file)
|
||||||
|
|
||||||
if fh is not sys.stdout:
|
if file is not sys.stdout:
|
||||||
fh.close()
|
file.close()
|
||||||
self.logger.info(f"Wrote YAML config to {outfile}")
|
self.logger.info(f"Wrote YAML config to {outfile}")
|
||||||
|
|
||||||
def cache_to_config(self):
|
def cache_to_config(self):
|
||||||
|
@ -15,13 +15,13 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import sys
|
import sys
|
||||||
import logging
|
import logging
|
||||||
import config.loopback as loopback
|
from config import loopback
|
||||||
import config.interface as interface
|
from config import interface
|
||||||
import config.bondethernet as bondethernet
|
from config import bondethernet
|
||||||
import config.bridgedomain as bridgedomain
|
from config import bridgedomain
|
||||||
import config.vxlan_tunnel as vxlan_tunnel
|
from config import vxlan_tunnel
|
||||||
import config.lcp as lcp
|
from config import lcp
|
||||||
import config.tap as tap
|
from config import tap
|
||||||
from vpp.vppapi import VPPApi
|
from vpp.vppapi import VPPApi
|
||||||
|
|
||||||
|
|
||||||
@ -117,26 +117,26 @@ class Reconciler:
|
|||||||
"""
|
"""
|
||||||
idx = self.vpp.cache["interface_names"][ifname].sw_if_index
|
idx = self.vpp.cache["interface_names"][ifname].sw_if_index
|
||||||
removed_addresses = []
|
removed_addresses = []
|
||||||
for a in self.vpp.cache["interface_addresses"][idx]:
|
for addr in self.vpp.cache["interface_addresses"][idx]:
|
||||||
if not a in address_list:
|
if not addr in address_list:
|
||||||
cli = f"set interface ip address del {ifname} {a}"
|
cli = f"set interface ip address del {ifname} {addr}"
|
||||||
self.cli["prune"].append(cli)
|
self.cli["prune"].append(cli)
|
||||||
removed_addresses.append(a)
|
removed_addresses.append(addr)
|
||||||
else:
|
else:
|
||||||
self.logger.debug(f"Address OK: {ifname} {a}")
|
self.logger.debug(f"Address OK: {ifname} {addr}")
|
||||||
for a in removed_addresses:
|
for addr in removed_addresses:
|
||||||
self.vpp.cache["interface_addresses"][idx].remove(a)
|
self.vpp.cache["interface_addresses"][idx].remove(addr)
|
||||||
|
|
||||||
def prune_loopbacks(self):
|
def prune_loopbacks(self):
|
||||||
"""Remove loopbacks from VPP, if they do not occur in the config."""
|
"""Remove loopbacks from VPP, if they do not occur in the config."""
|
||||||
removed_interfaces = []
|
removed_interfaces = []
|
||||||
for numtags in [2, 1, 0]:
|
for numtags in [2, 1, 0]:
|
||||||
for idx, vpp_iface in self.vpp.cache["interfaces"].items():
|
for _idx, vpp_iface in self.vpp.cache["interfaces"].items():
|
||||||
if vpp_iface.interface_dev_type != "Loopback":
|
if vpp_iface.interface_dev_type != "Loopback":
|
||||||
continue
|
continue
|
||||||
if vpp_iface.sub_number_of_tags != numtags:
|
if vpp_iface.sub_number_of_tags != numtags:
|
||||||
continue
|
continue
|
||||||
config_ifname, config_iface = loopback.get_by_name(
|
_config_ifname, config_iface = loopback.get_by_name(
|
||||||
self.cfg, vpp_iface.interface_name
|
self.cfg, vpp_iface.interface_name
|
||||||
)
|
)
|
||||||
if not config_iface:
|
if not config_iface:
|
||||||
@ -166,8 +166,9 @@ class Reconciler:
|
|||||||
found in to-be removed bridge-domains, they are returned to L3 mode, and tag-rewrites removed."""
|
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():
|
for idx, bridge in self.vpp.cache["bridgedomains"].items():
|
||||||
bridgename = f"bd{int(idx)}"
|
bridgename = f"bd{int(idx)}"
|
||||||
config_ifname, config_iface = bridgedomain.get_by_name(self.cfg, bridgename)
|
_config_ifname, config_iface = bridgedomain.get_by_name(
|
||||||
members = []
|
self.cfg, bridgename
|
||||||
|
)
|
||||||
if not config_iface:
|
if not config_iface:
|
||||||
for member in bridge.sw_if_details:
|
for member in bridge.sw_if_details:
|
||||||
if member.sw_if_index == bridge.bvi_sw_if_index:
|
if member.sw_if_index == bridge.bvi_sw_if_index:
|
||||||
@ -222,7 +223,7 @@ class Reconciler:
|
|||||||
but are crossconnected to a different interface name, also remove them. Interfaces are put
|
but are crossconnected to a different interface name, also remove them. Interfaces are put
|
||||||
back into L3 mode, and their tag-rewrites removed."""
|
back into L3 mode, and their tag-rewrites removed."""
|
||||||
removed_l2xcs = []
|
removed_l2xcs = []
|
||||||
for idx, l2xc in self.vpp.cache["l2xcs"].items():
|
for _idx, l2xc in self.vpp.cache["l2xcs"].items():
|
||||||
vpp_rx_ifname = self.vpp.cache["interfaces"][
|
vpp_rx_ifname = self.vpp.cache["interfaces"][
|
||||||
l2xc.rx_sw_if_index
|
l2xc.rx_sw_if_index
|
||||||
].interface_name
|
].interface_name
|
||||||
@ -276,7 +277,7 @@ class Reconciler:
|
|||||||
return True
|
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)
|
_config_ifname, config_iface = vxlan_tunnel.get_by_name(self.cfg, ifname)
|
||||||
if not config_iface:
|
if not config_iface:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -299,7 +300,7 @@ class Reconciler:
|
|||||||
vpp_iface = self.vpp.cache["interface_names"][ifname]
|
vpp_iface = self.vpp.cache["interface_names"][ifname]
|
||||||
vpp_tap = self.vpp.cache["taps"][vpp_iface.sw_if_index]
|
vpp_tap = self.vpp.cache["taps"][vpp_iface.sw_if_index]
|
||||||
|
|
||||||
config_ifname, config_iface = tap.get_by_name(self.cfg, ifname)
|
_config_ifname, config_iface = tap.get_by_name(self.cfg, ifname)
|
||||||
if not config_iface:
|
if not config_iface:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -352,10 +353,12 @@ class Reconciler:
|
|||||||
|
|
||||||
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))
|
mode = bondethernet.mode_to_int(bondethernet.get_mode(self.cfg, config_ifname))
|
||||||
if mode != -1 and mode != vpp_bond.mode:
|
if mode not in (-1, vpp_bond.mode):
|
||||||
return True
|
return True
|
||||||
lb = bondethernet.lb_to_int(bondethernet.get_lb(self.cfg, config_ifname))
|
loadbalance = bondethernet.lb_to_int(
|
||||||
if lb != -1 and lb != vpp_bond.lb:
|
bondethernet.get_lb(self.cfg, config_ifname)
|
||||||
|
)
|
||||||
|
if loadbalance not in (-1, vpp_bond.lb):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
@ -365,7 +368,7 @@ class Reconciler:
|
|||||||
TAPs which are a part of Linux Control Plane, are left alone, to be handled
|
TAPs which are a part of Linux Control Plane, are left alone, to be handled
|
||||||
by prune_lcps() later."""
|
by prune_lcps() later."""
|
||||||
removed_taps = []
|
removed_taps = []
|
||||||
for idx, vpp_tap in self.vpp.cache["taps"].items():
|
for _idx, vpp_tap in self.vpp.cache["taps"].items():
|
||||||
vpp_iface = self.vpp.cache["interfaces"][vpp_tap.sw_if_index]
|
vpp_iface = self.vpp.cache["interfaces"][vpp_tap.sw_if_index]
|
||||||
vpp_ifname = vpp_iface.interface_name
|
vpp_ifname = vpp_iface.interface_name
|
||||||
if self.vpp.tap_is_lcp(vpp_ifname):
|
if self.vpp.tap_is_lcp(vpp_ifname):
|
||||||
@ -388,7 +391,9 @@ class Reconciler:
|
|||||||
removed_bondethernet_members = []
|
removed_bondethernet_members = []
|
||||||
for idx, bond in self.vpp.cache["bondethernets"].items():
|
for idx, bond in self.vpp.cache["bondethernets"].items():
|
||||||
vpp_ifname = bond.interface_name
|
vpp_ifname = bond.interface_name
|
||||||
config_ifname, config_iface = bondethernet.get_by_name(self.cfg, vpp_ifname)
|
_config_ifname, config_iface = bondethernet.get_by_name(
|
||||||
|
self.cfg, vpp_ifname
|
||||||
|
)
|
||||||
|
|
||||||
if self.__bond_has_diff(vpp_ifname):
|
if self.__bond_has_diff(vpp_ifname):
|
||||||
self.prune_addresses(vpp_ifname, [])
|
self.prune_addresses(vpp_ifname, [])
|
||||||
@ -478,7 +483,7 @@ class Reconciler:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
prune = False
|
prune = False
|
||||||
config_ifname, config_iface = interface.get_by_name(
|
_config_ifname, config_iface = interface.get_by_name(
|
||||||
self.cfg, vpp_ifname
|
self.cfg, vpp_ifname
|
||||||
)
|
)
|
||||||
if not config_iface:
|
if not config_iface:
|
||||||
@ -489,7 +494,7 @@ class Reconciler:
|
|||||||
):
|
):
|
||||||
(
|
(
|
||||||
config_parent_ifname,
|
config_parent_ifname,
|
||||||
config_parent_iface,
|
_config_parent_iface,
|
||||||
) = interface.get_parent_by_name(self.cfg, vpp_ifname)
|
) = interface.get_parent_by_name(self.cfg, vpp_ifname)
|
||||||
if self.__bond_has_diff(config_parent_ifname):
|
if self.__bond_has_diff(config_parent_ifname):
|
||||||
prune = True
|
prune = True
|
||||||
@ -521,7 +526,7 @@ class Reconciler:
|
|||||||
"""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():
|
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)
|
_config_ifname, config_iface = interface.get_by_name(self.cfg, vpp_ifname)
|
||||||
if not config_iface:
|
if not config_iface:
|
||||||
## Interfaces were sent DOWN in the prune_admin_state() step previously
|
## Interfaces were sent DOWN in the prune_admin_state() step previously
|
||||||
self.prune_addresses(vpp_ifname, [])
|
self.prune_addresses(vpp_ifname, [])
|
||||||
@ -589,25 +594,25 @@ class Reconciler:
|
|||||||
|
|
||||||
removed_lcps = []
|
removed_lcps = []
|
||||||
for numtags in [2, 1, 0]:
|
for numtags in [2, 1, 0]:
|
||||||
for idx, lcp in lcps.items():
|
for _idx, lcp_iface in lcps.items():
|
||||||
vpp_iface = self.vpp.cache["interfaces"][lcp.phy_sw_if_index]
|
vpp_iface = self.vpp.cache["interfaces"][lcp_iface.phy_sw_if_index]
|
||||||
if vpp_iface.sub_number_of_tags != numtags:
|
if vpp_iface.sub_number_of_tags != numtags:
|
||||||
continue
|
continue
|
||||||
if vpp_iface.interface_dev_type == "Loopback":
|
if vpp_iface.interface_dev_type == "Loopback":
|
||||||
config_ifname, config_iface = loopback.get_by_lcp_name(
|
config_ifname, config_iface = loopback.get_by_lcp_name(
|
||||||
self.cfg, lcp.host_if_name
|
self.cfg, lcp_iface.host_if_name
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
config_ifname, config_iface = interface.get_by_lcp_name(
|
config_ifname, config_iface = interface.get_by_lcp_name(
|
||||||
self.cfg, lcp.host_if_name
|
self.cfg, lcp_iface.host_if_name
|
||||||
)
|
)
|
||||||
if not config_iface:
|
if not config_iface:
|
||||||
## Interface doesn't exist in the config
|
## Interface doesn't exist in the config
|
||||||
removed_lcps.append(lcp)
|
removed_lcps.append(lcp_iface)
|
||||||
continue
|
continue
|
||||||
if not "lcp" in config_iface:
|
if not "lcp" in config_iface:
|
||||||
## Interface doesn't have an LCP
|
## Interface doesn't have an LCP
|
||||||
removed_lcps.append(lcp)
|
removed_lcps.append(lcp_iface)
|
||||||
continue
|
continue
|
||||||
if vpp_iface.sub_number_of_tags == 2:
|
if vpp_iface.sub_number_of_tags == 2:
|
||||||
vpp_parent_idx = self.__parent_iface_by_encap(
|
vpp_parent_idx = self.__parent_iface_by_encap(
|
||||||
@ -623,15 +628,15 @@ class Reconciler:
|
|||||||
) = interface.get_by_lcp_name(self.cfg, parent_lcp.host_if_name)
|
) = interface.get_by_lcp_name(self.cfg, parent_lcp.host_if_name)
|
||||||
if not config_parent_iface:
|
if not config_parent_iface:
|
||||||
## QinX's parent doesn't exist in the config
|
## QinX's parent doesn't exist in the config
|
||||||
removed_lcps.append(lcp)
|
removed_lcps.append(lcp_iface)
|
||||||
continue
|
continue
|
||||||
if not "lcp" in config_parent_iface:
|
if not "lcp" in config_parent_iface:
|
||||||
## QinX's parent doesn't have an LCP
|
## QinX's parent doesn't have an LCP
|
||||||
removed_lcps.append(lcp)
|
removed_lcps.append(lcp_iface)
|
||||||
continue
|
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
|
## QinX's parent LCP name mismatch
|
||||||
removed_lcps.append(lcp)
|
removed_lcps.append(lcp_iface)
|
||||||
continue
|
continue
|
||||||
config_parent_encap = interface.get_encapsulation(
|
config_parent_encap = interface.get_encapsulation(
|
||||||
self.cfg, config_parent_ifname
|
self.cfg, config_parent_ifname
|
||||||
@ -639,7 +644,7 @@ class Reconciler:
|
|||||||
vpp_parent_encap = self.__get_encapsulation(vpp_parent_iface)
|
vpp_parent_encap = self.__get_encapsulation(vpp_parent_iface)
|
||||||
if config_parent_encap != vpp_parent_encap:
|
if config_parent_encap != vpp_parent_encap:
|
||||||
## QinX's parent encapsulation mismatch
|
## QinX's parent encapsulation mismatch
|
||||||
removed_lcps.append(lcp)
|
removed_lcps.append(lcp_iface)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if vpp_iface.sub_number_of_tags > 0:
|
if vpp_iface.sub_number_of_tags > 0:
|
||||||
@ -647,7 +652,7 @@ class Reconciler:
|
|||||||
vpp_encap = self.__get_encapsulation(vpp_iface)
|
vpp_encap = self.__get_encapsulation(vpp_iface)
|
||||||
if config_encap != vpp_encap:
|
if config_encap != vpp_encap:
|
||||||
## Encapsulation mismatch
|
## Encapsulation mismatch
|
||||||
removed_lcps.append(lcp)
|
removed_lcps.append(lcp_iface)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if vpp_iface.interface_dev_type == "Loopback":
|
if vpp_iface.interface_dev_type == "Loopback":
|
||||||
@ -657,37 +662,37 @@ class Reconciler:
|
|||||||
bond_iface = self.vpp.cache["interfaces"][vpp_iface.sup_sw_if_index]
|
bond_iface = self.vpp.cache["interfaces"][vpp_iface.sup_sw_if_index]
|
||||||
if self.__bond_has_diff(bond_iface.interface_name):
|
if self.__bond_has_diff(bond_iface.interface_name):
|
||||||
## If BondEthernet changed, it has to be re-created, so all LCPs must be removed.
|
## If BondEthernet changed, it has to be re-created, so all LCPs must be removed.
|
||||||
removed_lcps.append(lcp)
|
removed_lcps.append(lcp_iface)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
phy_lcp = lcps[vpp_iface.sup_sw_if_index]
|
phy_lcp = lcps[vpp_iface.sup_sw_if_index]
|
||||||
config_phy_ifname, config_phy_iface = interface.get_by_lcp_name(
|
_config_phy_ifname, config_phy_iface = interface.get_by_lcp_name(
|
||||||
self.cfg, phy_lcp.host_if_name
|
self.cfg, phy_lcp.host_if_name
|
||||||
)
|
)
|
||||||
if not config_phy_iface:
|
if not config_phy_iface:
|
||||||
## Phy doesn't exist in the config
|
## Phy doesn't exist in the config
|
||||||
removed_lcps.append(lcp)
|
removed_lcps.append(lcp_iface)
|
||||||
continue
|
continue
|
||||||
if not "lcp" in config_phy_iface:
|
if not "lcp" in config_phy_iface:
|
||||||
## Phy doesn't have an LCP
|
## Phy doesn't have an LCP
|
||||||
removed_lcps.append(lcp)
|
removed_lcps.append(lcp_iface)
|
||||||
continue
|
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
|
## Phy LCP name mismatch
|
||||||
removed_lcps.append(lcp)
|
removed_lcps.append(lcp_iface)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
self.logger.debug(
|
self.logger.debug(
|
||||||
f"LCP OK: {lcp.host_if_name} -> (vpp={vpp_iface.interface_name}, config={config_ifname})"
|
f"LCP OK: {lcp_iface.host_if_name} -> (vpp={vpp_iface.interface_name}, config={config_ifname})"
|
||||||
)
|
)
|
||||||
|
|
||||||
for lcp in removed_lcps:
|
for lcp_iface in removed_lcps:
|
||||||
vpp_ifname = self.vpp.cache["interfaces"][
|
vpp_ifname = self.vpp.cache["interfaces"][
|
||||||
lcp.phy_sw_if_index
|
lcp_iface.phy_sw_if_index
|
||||||
].interface_name
|
].interface_name
|
||||||
cli = f"lcp delete {vpp_ifname}"
|
cli = f"lcp delete {vpp_ifname}"
|
||||||
self.cli["prune"].append(cli)
|
self.cli["prune"].append(cli)
|
||||||
self.vpp.cache_remove_lcp(lcp.host_if_name)
|
self.vpp.cache_remove_lcp(lcp_iface.host_if_name)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def prune_admin_state(self):
|
def prune_admin_state(self):
|
||||||
@ -762,9 +767,9 @@ class Reconciler:
|
|||||||
instance = int(ifname[12:])
|
instance = int(ifname[12:])
|
||||||
mode = bondethernet.get_mode(self.cfg, ifname)
|
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)
|
loadbalance = bondethernet.get_lb(self.cfg, ifname)
|
||||||
if lb:
|
if loadbalance:
|
||||||
cli += f" load-balance {lb}"
|
cli += f" load-balance {loadbalance}"
|
||||||
if "mac" in iface:
|
if "mac" in iface:
|
||||||
cli += f" hw-addr {iface['mac']}"
|
cli += f" hw-addr {iface['mac']}"
|
||||||
self.cli["create"].append(cli)
|
self.cli["create"].append(cli)
|
||||||
@ -790,7 +795,7 @@ class Reconciler:
|
|||||||
if not do_qinx == interface.is_qinx(self.cfg, ifname):
|
if not do_qinx == interface.is_qinx(self.cfg, ifname):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
ifname, iface = interface.get_by_name(self.cfg, ifname)
|
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
|
continue
|
||||||
|
|
||||||
@ -802,7 +807,7 @@ class Reconciler:
|
|||||||
encapstr = f"dot1q {int(encap['dot1q'])}"
|
encapstr = f"dot1q {int(encap['dot1q'])}"
|
||||||
if do_qinx:
|
if do_qinx:
|
||||||
encapstr += f" inner-dot1q {int(encap['inner-dot1q'])}"
|
encapstr += f" inner-dot1q {int(encap['inner-dot1q'])}"
|
||||||
if encap["exact-match"] == True:
|
if encap["exact-match"]:
|
||||||
encapstr += " exact-match"
|
encapstr += " exact-match"
|
||||||
parent, subid = ifname.split(".")
|
parent, subid = ifname.split(".")
|
||||||
cli = f"create sub {parent} {int(int(subid))} {encapstr}"
|
cli = f"create sub {parent} {int(int(subid))} {encapstr}"
|
||||||
@ -834,7 +839,7 @@ class Reconciler:
|
|||||||
|
|
||||||
def create_bridgedomains(self):
|
def create_bridgedomains(self):
|
||||||
for ifname in bridgedomain.get_bridgedomains(self.cfg):
|
for ifname in bridgedomain.get_bridgedomains(self.cfg):
|
||||||
ifname, iface = bridgedomain.get_by_name(self.cfg, ifname)
|
ifname, _iface = bridgedomain.get_by_name(self.cfg, ifname)
|
||||||
instance = int(ifname[2:])
|
instance = int(ifname[2:])
|
||||||
settings = bridgedomain.get_settings(self.cfg, ifname)
|
settings = bridgedomain.get_settings(self.cfg, ifname)
|
||||||
if instance in self.vpp.cache["bridgedomains"]:
|
if instance in self.vpp.cache["bridgedomains"]:
|
||||||
@ -1076,7 +1081,7 @@ class Reconciler:
|
|||||||
if not "interfaces" in config_bridge_iface:
|
if not "interfaces" in config_bridge_iface:
|
||||||
continue
|
continue
|
||||||
for member_ifname in config_bridge_iface["interfaces"]:
|
for member_ifname in config_bridge_iface["interfaces"]:
|
||||||
member_ifname, member_iface = interface.get_by_name(
|
member_ifname, _member_iface = interface.get_by_name(
|
||||||
self.cfg, member_ifname
|
self.cfg, member_ifname
|
||||||
)
|
)
|
||||||
if not member_ifname in bridge_members:
|
if not member_ifname in bridge_members:
|
||||||
@ -1094,7 +1099,7 @@ class Reconciler:
|
|||||||
def sync_l2xcs(self):
|
def sync_l2xcs(self):
|
||||||
for ifname in interface.get_l2xc_interfaces(self.cfg):
|
for ifname in interface.get_l2xc_interfaces(self.cfg):
|
||||||
config_rx_ifname, config_rx_iface = interface.get_by_name(self.cfg, ifname)
|
config_rx_ifname, config_rx_iface = interface.get_by_name(self.cfg, ifname)
|
||||||
config_tx_ifname, config_tx_iface = interface.get_by_name(
|
config_tx_ifname, _config_tx_iface = interface.get_by_name(
|
||||||
self.cfg, config_rx_iface["l2xc"]
|
self.cfg, config_rx_iface["l2xc"]
|
||||||
)
|
)
|
||||||
vpp_rx_iface = None
|
vpp_rx_iface = None
|
||||||
@ -1171,13 +1176,13 @@ class Reconciler:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def sync_link_mtu_direction(self, shrink=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:
|
if vpp_iface.sub_number_of_tags != 0:
|
||||||
continue
|
continue
|
||||||
if vpp_iface.interface_dev_type in ["local", "Loopback", "VXLAN", "virtio"]:
|
if vpp_iface.interface_dev_type in ["local", "Loopback", "VXLAN", "virtio"]:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
config_ifname, config_iface = interface.get_by_name(
|
_config_ifname, config_iface = interface.get_by_name(
|
||||||
self.cfg, vpp_iface.interface_name
|
self.cfg, vpp_iface.interface_name
|
||||||
)
|
)
|
||||||
if not config_iface:
|
if not config_iface:
|
||||||
@ -1266,10 +1271,10 @@ class Reconciler:
|
|||||||
str(x)
|
str(x)
|
||||||
for x in self.vpp.cache["interface_addresses"][sw_if_index]
|
for x in self.vpp.cache["interface_addresses"][sw_if_index]
|
||||||
]
|
]
|
||||||
for a in config_addresses:
|
for addr in config_addresses:
|
||||||
if a in vpp_addresses:
|
if addr in vpp_addresses:
|
||||||
continue
|
continue
|
||||||
cli = f"set interface ip address {vpp_ifname} {a}"
|
cli = f"set interface ip address {vpp_ifname} {addr}"
|
||||||
self.cli["sync"].append(cli)
|
self.cli["sync"].append(cli)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -1278,10 +1283,10 @@ class Reconciler:
|
|||||||
self.cfg
|
self.cfg
|
||||||
):
|
):
|
||||||
if ifname.startswith("loop"):
|
if ifname.startswith("loop"):
|
||||||
vpp_ifname, config_iface = loopback.get_by_name(self.cfg, ifname)
|
vpp_ifname, _config_iface = loopback.get_by_name(self.cfg, ifname)
|
||||||
config_admin_state = 1
|
config_admin_state = 1
|
||||||
else:
|
else:
|
||||||
vpp_ifname, config_iface = interface.get_by_name(self.cfg, ifname)
|
vpp_ifname, _config_iface = interface.get_by_name(self.cfg, ifname)
|
||||||
config_admin_state = interface.get_admin_state(self.cfg, ifname)
|
config_admin_state = interface.get_admin_state(self.cfg, ifname)
|
||||||
|
|
||||||
vpp_admin_state = 0
|
vpp_admin_state = 0
|
||||||
@ -1298,39 +1303,39 @@ class Reconciler:
|
|||||||
self.cli["sync"].append(cli)
|
self.cli["sync"].append(cli)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def write(self, outfile, ok=False):
|
def write(self, outfile, emit_ok=False):
|
||||||
"""Emit the CLI contents to stdout (if outfile=='-') or a named file otherwise.
|
"""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.
|
If the 'emit_ok' flag is False, emit a warning at the top and bottom of the file.
|
||||||
"""
|
"""
|
||||||
# Assemble the intended output into a list
|
# Assemble the intended output into a list
|
||||||
output = []
|
output = []
|
||||||
if not ok:
|
if not emit_ok:
|
||||||
output.append(
|
output.append(
|
||||||
"comment { vppcfg: Planning failed, be careful with this output! }"
|
"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])
|
ncount = len(self.cli[phase])
|
||||||
if n > 0:
|
if ncount > 0:
|
||||||
output.append(
|
output.append(
|
||||||
f"comment {{ vppcfg {phase}: {n} CLI statement(s) follow }}"
|
f"comment {{ vppcfg {phase}: {ncount} CLI statement(s) follow }}"
|
||||||
)
|
)
|
||||||
output.extend(self.cli[phase])
|
output.extend(self.cli[phase])
|
||||||
|
|
||||||
if not ok:
|
if not emit_ok:
|
||||||
output.append(
|
output.append(
|
||||||
"comment { vppcfg: Planning failed, be careful with this output! }"
|
"comment { vppcfg: Planning failed, be careful with this output! }"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Emit the output list to stdout or a file
|
# Emit the output list to stdout or a file
|
||||||
if outfile and outfile == "-":
|
if outfile and outfile == "-":
|
||||||
fh = sys.stdout
|
file = sys.stdout
|
||||||
outfile = "(stdout)"
|
outfile = "(stdout)"
|
||||||
else:
|
else:
|
||||||
fh = open(outfile, "w")
|
file = open(outfile, "w", encoding="utf-8")
|
||||||
if len(output) > 0:
|
if len(output) > 0:
|
||||||
print("\n".join(output), file=fh)
|
print("\n".join(output), file=file)
|
||||||
if fh is not sys.stdout:
|
if file is not sys.stdout:
|
||||||
fh.close()
|
file.close()
|
||||||
|
|
||||||
self.logger.info(f"Wrote {len(output)} lines to {outfile}")
|
self.logger.info(f"Wrote {len(output)} lines to {outfile}")
|
||||||
|
@ -4,10 +4,11 @@ interface metadata. Its base class will never change state. See the
|
|||||||
derived classes VPPApiDumper() and VPPApiApplier()
|
derived classes VPPApiDumper() and VPPApiApplier()
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from vpp_papi import VPPApiClient
|
|
||||||
import os
|
import os
|
||||||
import fnmatch
|
import fnmatch
|
||||||
import logging
|
import logging
|
||||||
|
import socket
|
||||||
|
from vpp_papi import VPPApiClient
|
||||||
|
|
||||||
|
|
||||||
class VPPApi:
|
class VPPApi:
|
||||||
@ -32,7 +33,7 @@ class VPPApi:
|
|||||||
|
|
||||||
# construct a list of all the json api files
|
# construct a list of all the json api files
|
||||||
jsonfiles = []
|
jsonfiles = []
|
||||||
for root, dirnames, filenames in os.walk(vpp_json_dir):
|
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))
|
jsonfiles.append(os.path.join(root, filename))
|
||||||
|
|
||||||
@ -47,8 +48,9 @@ class VPPApi:
|
|||||||
except:
|
except:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
v = self.vpp.api.show_version()
|
# pylint: disable=no-member
|
||||||
self.logger.info(f"VPP version is {v.version}")
|
api_response = self.vpp.api.show_version()
|
||||||
|
self.logger.info(f"VPP version is {api_response.version}")
|
||||||
|
|
||||||
self.connected = True
|
self.connected = True
|
||||||
return True
|
return True
|
||||||
@ -78,22 +80,15 @@ class VPPApi:
|
|||||||
|
|
||||||
def cache_remove_lcp(self, lcpname):
|
def cache_remove_lcp(self, lcpname):
|
||||||
"""Removes the LCP and TAP interface, identified by lcpname, from the config."""
|
"""Removes the LCP and TAP interface, identified by lcpname, from the config."""
|
||||||
found = False
|
for _idx, lcp in self.cache["lcps"].items():
|
||||||
for idx, lcp in self.cache["lcps"].items():
|
|
||||||
if lcp.host_if_name == lcpname:
|
if lcp.host_if_name == lcpname:
|
||||||
found = True
|
ifname = self.cache["interfaces"][lcp.host_sw_if_index].interface_name
|
||||||
break
|
del self.cache["lcps"][lcp.phy_sw_if_index]
|
||||||
if not found:
|
return self.cache_remove_interface(ifname)
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
f"Trying to remove an LCP which is not in the config: {lcpname}"
|
f"Trying to remove an LCP which is not in the config: {lcpname}"
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
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):
|
def cache_remove_bondethernet_member(self, ifname):
|
||||||
"""Removes the bonderthernet member interface, identified by name, from the config."""
|
"""Removes the bonderthernet member interface, identified by name, from the config."""
|
||||||
@ -159,6 +154,7 @@ class VPPApi:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def readconfig(self):
|
def readconfig(self):
|
||||||
|
# pylint: disable=no-member
|
||||||
if not self.connected and not self.connect():
|
if not self.connected and not self.connect():
|
||||||
self.logger.error("Could not connect to VPP")
|
self.logger.error("Could not connect to VPP")
|
||||||
return False
|
return False
|
||||||
@ -169,9 +165,9 @@ class VPPApi:
|
|||||||
self.lcp_enabled = False
|
self.lcp_enabled = False
|
||||||
try:
|
try:
|
||||||
self.logger.debug("Retrieving LCPs")
|
self.logger.debug("Retrieving LCPs")
|
||||||
r = self.vpp.api.lcp_itf_pair_get()
|
api_response = self.vpp.api.lcp_itf_pair_get()
|
||||||
if isinstance(r, tuple) and r[0].retval == 0:
|
if isinstance(api_response, tuple) and api_response[0].retval == 0:
|
||||||
for lcp in r[1]:
|
for lcp in api_response[1]:
|
||||||
if lcp.phy_sw_if_index > 65535 or lcp.host_sw_if_index > 65535:
|
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
|
## Work around endianness bug: https://gerrit.fd.io/r/c/vpp/+/35479
|
||||||
## TODO(pim) - remove this when 22.06 ships
|
## TODO(pim) - remove this when 22.06 ships
|
||||||
@ -193,8 +189,8 @@ class VPPApi:
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.logger.debug("Retrieving interfaces")
|
self.logger.debug("Retrieving interfaces")
|
||||||
r = self.vpp.api.sw_interface_dump()
|
api_response = self.vpp.api.sw_interface_dump()
|
||||||
for iface in r:
|
for iface in api_response:
|
||||||
self.cache["interfaces"][iface.sw_if_index] = iface
|
self.cache["interfaces"][iface.sw_if_index] = iface
|
||||||
self.cache["interface_names"][iface.interface_name] = iface
|
self.cache["interface_names"][iface.interface_name] = iface
|
||||||
self.cache["interface_addresses"][iface.sw_if_index] = []
|
self.cache["interface_addresses"][iface.sw_if_index] = []
|
||||||
@ -202,22 +198,22 @@ class VPPApi:
|
|||||||
ipr = self.vpp.api.ip_address_dump(
|
ipr = self.vpp.api.ip_address_dump(
|
||||||
sw_if_index=iface.sw_if_index, is_ipv6=False
|
sw_if_index=iface.sw_if_index, is_ipv6=False
|
||||||
)
|
)
|
||||||
for ip in ipr:
|
for addr in ipr:
|
||||||
self.cache["interface_addresses"][iface.sw_if_index].append(
|
self.cache["interface_addresses"][iface.sw_if_index].append(
|
||||||
str(ip.prefix)
|
str(addr.prefix)
|
||||||
)
|
)
|
||||||
self.logger.debug(f"Retrieving IPv6 addresses for {iface.interface_name}")
|
self.logger.debug(f"Retrieving IPv6 addresses for {iface.interface_name}")
|
||||||
ipr = self.vpp.api.ip_address_dump(
|
ipr = self.vpp.api.ip_address_dump(
|
||||||
sw_if_index=iface.sw_if_index, is_ipv6=True
|
sw_if_index=iface.sw_if_index, is_ipv6=True
|
||||||
)
|
)
|
||||||
for ip in ipr:
|
for addr in ipr:
|
||||||
self.cache["interface_addresses"][iface.sw_if_index].append(
|
self.cache["interface_addresses"][iface.sw_if_index].append(
|
||||||
str(ip.prefix)
|
str(addr.prefix)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.logger.debug("Retrieving bondethernets")
|
self.logger.debug("Retrieving bondethernets")
|
||||||
r = self.vpp.api.sw_bond_interface_dump()
|
api_response = self.vpp.api.sw_bond_interface_dump()
|
||||||
for iface in r:
|
for iface in api_response:
|
||||||
self.cache["bondethernets"][iface.sw_if_index] = iface
|
self.cache["bondethernets"][iface.sw_if_index] = iface
|
||||||
self.cache["bondethernet_members"][iface.sw_if_index] = []
|
self.cache["bondethernet_members"][iface.sw_if_index] = []
|
||||||
for member in self.vpp.api.sw_member_interface_dump(
|
for member in self.vpp.api.sw_member_interface_dump(
|
||||||
@ -228,23 +224,23 @@ class VPPApi:
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.logger.debug("Retrieving bridgedomains")
|
self.logger.debug("Retrieving bridgedomains")
|
||||||
r = self.vpp.api.bridge_domain_dump()
|
api_response = self.vpp.api.bridge_domain_dump()
|
||||||
for bridge in r:
|
for bridge in api_response:
|
||||||
self.cache["bridgedomains"][bridge.bd_id] = bridge
|
self.cache["bridgedomains"][bridge.bd_id] = bridge
|
||||||
|
|
||||||
self.logger.debug("Retrieving vxlan_tunnels")
|
self.logger.debug("Retrieving vxlan_tunnels")
|
||||||
r = self.vpp.api.vxlan_tunnel_v2_dump()
|
api_response = self.vpp.api.vxlan_tunnel_v2_dump()
|
||||||
for vxlan in r:
|
for vxlan in api_response:
|
||||||
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")
|
self.logger.debug("Retrieving L2 Cross Connects")
|
||||||
r = self.vpp.api.l2_xconnect_dump()
|
api_response = self.vpp.api.l2_xconnect_dump()
|
||||||
for l2xc in r:
|
for l2xc in api_response:
|
||||||
self.cache["l2xcs"][l2xc.rx_sw_if_index] = l2xc
|
self.cache["l2xcs"][l2xc.rx_sw_if_index] = l2xc
|
||||||
|
|
||||||
self.logger.debug("Retrieving TAPs")
|
self.logger.debug("Retrieving TAPs")
|
||||||
r = self.vpp.api.sw_interface_tap_v2_dump()
|
api_response = self.vpp.api.sw_interface_tap_v2_dump()
|
||||||
for tap in r:
|
for tap in api_response:
|
||||||
self.cache["taps"][tap.sw_if_index] = tap
|
self.cache["taps"][tap.sw_if_index] = tap
|
||||||
|
|
||||||
self.cache_read = True
|
self.cache_read = True
|
||||||
@ -322,7 +318,7 @@ class VPPApi:
|
|||||||
return vxlan_tunnels
|
return vxlan_tunnels
|
||||||
|
|
||||||
def get_lcp_by_interface(self, sw_if_index):
|
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:
|
if lcp.phy_sw_if_index == sw_if_index:
|
||||||
return lcp
|
return lcp
|
||||||
return None
|
return None
|
||||||
@ -337,7 +333,7 @@ class VPPApi:
|
|||||||
if not vpp_iface.interface_dev_type == "virtio":
|
if not vpp_iface.interface_dev_type == "virtio":
|
||||||
return False
|
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:
|
if vpp_iface.sw_if_index == lcp.host_sw_if_index:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
41
vppcfg
41
vppcfg
@ -13,10 +13,11 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
"""vppcfg is a utility to configure a running VPP Dataplane using YAML
|
||||||
|
config files. See http://github.com/pimvanpelt/vppcfg/README.md for details. """
|
||||||
import sys
|
import sys
|
||||||
import yaml
|
|
||||||
import logging
|
import logging
|
||||||
|
import yaml
|
||||||
from config import Validator
|
from config import Validator
|
||||||
from vpp.reconciler import Reconciler
|
from vpp.reconciler import Reconciler
|
||||||
from vpp.dumper import Dumper
|
from vpp.dumper import Dumper
|
||||||
@ -149,64 +150,64 @@ def main():
|
|||||||
)
|
)
|
||||||
|
|
||||||
if args.command == "dump":
|
if args.command == "dump":
|
||||||
d = Dumper()
|
dumper = Dumper()
|
||||||
if not d.readconfig():
|
if not dumper.readconfig():
|
||||||
logging.error("Could not retrieve config from VPP")
|
logging.error("Could not retrieve config from VPP")
|
||||||
sys.exit(-7)
|
sys.exit(-7)
|
||||||
d.write(args.outfile)
|
dumper.write(args.outfile)
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(args.config, "r") as f:
|
with open(args.config, "r", encoding="utf-8") as file:
|
||||||
logging.info(f"Loading configfile {args.config}")
|
logging.info(f"Loading configfile {args.config}")
|
||||||
cfg = yaml.load(f, Loader=yaml.FullLoader)
|
cfg = yaml.load(file, Loader=yaml.FullLoader)
|
||||||
logging.debug(f"Config: {cfg}")
|
logging.debug(f"Config: {cfg}")
|
||||||
except:
|
except OSError as err:
|
||||||
logging.error(f"Couldn't read config from {args.config}")
|
logging.error(f"Couldn't read config from {args.config}: {err}")
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
v = Validator(schema=args.schema)
|
validator = Validator(schema=args.schema)
|
||||||
if not v.valid_config(cfg):
|
if not validator.valid_config(cfg):
|
||||||
logging.error("Configuration is not valid, bailing")
|
logging.error("Configuration is not valid, bailing")
|
||||||
sys.exit(-2)
|
sys.exit(-2)
|
||||||
logging.info("Configuration is valid")
|
logging.info("Configuration is valid")
|
||||||
if args.command == "check":
|
if args.command == "check":
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
r = Reconciler(cfg)
|
reconciler = Reconciler(cfg)
|
||||||
if not r.vpp_readconfig():
|
if not reconciler.vpp_readconfig():
|
||||||
sys.exit(-3)
|
sys.exit(-3)
|
||||||
|
|
||||||
if not r.phys_exist_in_vpp():
|
if not reconciler.phys_exist_in_vpp():
|
||||||
logging.error("Not all PHYs in the config exist in VPP")
|
logging.error("Not all PHYs in the config exist in VPP")
|
||||||
sys.exit(-4)
|
sys.exit(-4)
|
||||||
|
|
||||||
if not r.phys_exist_in_config():
|
if not reconciler.phys_exist_in_config():
|
||||||
logging.error("Not all PHYs in VPP exist in the config")
|
logging.error("Not all PHYs in VPP exist in the config")
|
||||||
sys.exit(-5)
|
sys.exit(-5)
|
||||||
|
|
||||||
if not r.lcps_exist_with_lcp_enabled():
|
if not reconciler.lcps_exist_with_lcp_enabled():
|
||||||
logging.error(
|
logging.error(
|
||||||
"Linux Control Plane is needed, but linux-cp API is not available"
|
"Linux Control Plane is needed, but linux-cp API is not available"
|
||||||
)
|
)
|
||||||
sys.exit(-6)
|
sys.exit(-6)
|
||||||
|
|
||||||
failed = False
|
failed = False
|
||||||
if not r.prune():
|
if not reconciler.prune():
|
||||||
if not args.force:
|
if not args.force:
|
||||||
logging.error("Planning prune failure")
|
logging.error("Planning prune failure")
|
||||||
sys.exit(-10)
|
sys.exit(-10)
|
||||||
failed = True
|
failed = True
|
||||||
logging.warning("Planning prune failure, continuing due to --force")
|
logging.warning("Planning prune failure, continuing due to --force")
|
||||||
|
|
||||||
if not r.create():
|
if not reconciler.create():
|
||||||
if not args.force:
|
if not args.force:
|
||||||
logging.error("Planning create failure")
|
logging.error("Planning create failure")
|
||||||
sys.exit(-20)
|
sys.exit(-20)
|
||||||
failed = True
|
failed = True
|
||||||
logging.warning("Planning create failure, continuing due to --force")
|
logging.warning("Planning create failure, continuing due to --force")
|
||||||
|
|
||||||
if not r.sync():
|
if not reconciler.sync():
|
||||||
if not args.force:
|
if not args.force:
|
||||||
logging.error("Planning sync failure")
|
logging.error("Planning sync failure")
|
||||||
sys.exit(-30)
|
sys.exit(-30)
|
||||||
@ -214,7 +215,7 @@ def main():
|
|||||||
logging.warning("Planning sync failure, continuing due to --force")
|
logging.warning("Planning sync failure, continuing due to --force")
|
||||||
|
|
||||||
if args.command == "plan":
|
if args.command == "plan":
|
||||||
r.write(args.outfile, ok=not failed)
|
reconciler.write(args.outfile, emit_ok=not failed)
|
||||||
|
|
||||||
if failed:
|
if failed:
|
||||||
logging.error("Planning failed")
|
logging.error("Planning failed")
|
||||||
|
Reference in New Issue
Block a user