Add an initial stab at docstrings - enable docstring pylinter

This commit is contained in:
Pim van Pelt
2022-04-24 10:36:25 +00:00
parent 544f343da7
commit 5af27211f3
25 changed files with 304 additions and 17 deletions

View File

@ -14,7 +14,7 @@ ignore-patterns=test_.*
# W1203 = Use lazy % formatting in logging functions
# Disable the message(s) with the given id(s).
disable=R0201,W0212,W0613,C0301,R0914,C0111,W1203
disable=R0201,W0212,W0613,C0301,R0914,W1203
[FORMAT]
max-line-length=148

View File

@ -13,6 +13,7 @@
# limitations under the License.
#
# -*- coding: utf-8 -*-
""" A vppcfg configuration module that exposes its semantic/syntax validators """
from __future__ import (
absolute_import,
division,
@ -66,6 +67,15 @@ class IPInterfaceWithPrefixLength(Validator):
class Validator:
"""The Validator class takes a schema filename (which may be None, in which
case a built-in default is used), and a given YAML file represented as a string,
and holds it against syntax and semantic validators, returning a tuple of (bool,list)
where the boolean signals success/failure, and the list of strings are messages
that were added when validating the YAML config.
The purpose is to ensure that the YAML file is both syntactically correct,
which is ensured by Yamale, and semantically correct, which is ensured by a set
of built-in validators, and user-added validators (see the add_validator() method)."""
def __init__(self, schema):
self.logger = logging.getLogger("vppcfg.config")
self.logger.addHandler(logging.NullHandler())
@ -81,6 +91,8 @@ class Validator:
]
def validate(self, yaml):
"""Validate the semantics of all YAML maps, by calling self.validators in turn,
and then optionally calling validators that were added with add_validator()"""
ret_retval = True
ret_msgs = []
if not yaml:

View File

@ -11,6 +11,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
""" A vppcfg configuration module that handles addresses """
import ipaddress

View File

@ -11,6 +11,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
""" A vppcfg configuration module that handles bondethernets """
import logging
from config import interface
from config import mac
@ -162,6 +163,7 @@ def int_to_lb(loadbalance):
def validate_bondethernets(yaml):
"""Validate the semantics of all YAML 'bondethernets' entries"""
result = True
msgs = []
logger = logging.getLogger("vppcfg.config")

View File

@ -11,6 +11,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
""" A vppcfg configuration module that handles bridgedomains """
import logging
from config import interface
from config import loopback
@ -81,6 +82,8 @@ def bvi_unique(yaml, bviname):
def get_settings(yaml, ifname):
"""Return a dictionary of 'settings' including their VPP defaults, for the
bridgedomain identified by 'ifname' (bd10)"""
ifname, iface = get_by_name(yaml, ifname)
if not iface:
return None
@ -115,6 +118,7 @@ def get_settings(yaml, ifname):
def validate_bridgedomains(yaml):
"""Validate the semantics of all YAML 'bridgedomains' entries"""
result = True
msgs = []
logger = logging.getLogger("vppcfg.config")

View File

@ -11,6 +11,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
""" A vppcfg configuration module that validates interfaces """
import logging
from config import bondethernet
from config import bridgedomain
@ -415,6 +416,7 @@ def get_admin_state(yaml, ifname):
def validate_interfaces(yaml):
"""Validate the semantics of all YAML 'interfaces' entries"""
result = True
msgs = []
logger = logging.getLogger("vppcfg.config")

View File

@ -11,6 +11,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
""" A vppcfg configuration module that validates Linux Control Plane (lcp) elements """
def get_lcps(yaml, interfaces=True, loopbacks=True, bridgedomains=True):

View File

@ -11,6 +11,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
""" A vppcfg configuration module that validates loopbacks """
import logging
from config import lcp
from config import address
@ -53,6 +54,7 @@ def is_loopback(yaml, ifname):
def validate_loopbacks(yaml):
"""Validate the semantics of all YAML 'loopbacks' entries"""
result = True
msgs = []
logger = logging.getLogger("vppcfg.config")

View File

@ -11,6 +11,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
""" A vppcfg configuration module that validates MAC addresses """
import netaddr

View File

@ -11,6 +11,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
""" A vppcfg configuration module that validates taps """
import logging
from config import mac
@ -54,6 +55,7 @@ def is_host_name_unique(yaml, hostname):
def validate_taps(yaml):
"""Validate the semantics of all YAML 'taps' entries"""
result = True
msgs = []
logger = logging.getLogger("vppcfg.config")

View File

@ -1,3 +1,18 @@
#
# Copyright (c) 2022 Pim van Pelt
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at:
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# -*- coding: utf-8 -*-
""" Unit tests for bondethernet """
import unittest
import yaml
import config.bondethernet as bondethernet

View File

@ -1,3 +1,18 @@
#
# Copyright (c) 2022 Pim van Pelt
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at:
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# -*- coding: utf-8 -*-
""" Unit tests for bridgedomains """
import unittest
import yaml
import config.bridgedomain as bridgedomain

View File

@ -1,3 +1,18 @@
#
# Copyright (c) 2022 Pim van Pelt
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at:
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# -*- coding: utf-8 -*-
""" Unit tests for interfaces """
import unittest
import yaml
import config.interface as interface

View File

@ -1,3 +1,18 @@
#
# Copyright (c) 2022 Pim van Pelt
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at:
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# -*- coding: utf-8 -*-
""" Unit tests for LCPs """
import unittest
import yaml
import config.lcp as lcp

View File

@ -1,3 +1,18 @@
#
# Copyright (c) 2022 Pim van Pelt
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at:
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# -*- coding: utf-8 -*-
""" Unit tests for loopbacks """
import unittest
import yaml
import config.loopback as loopback

View File

@ -1,3 +1,18 @@
#
# Copyright (c) 2022 Pim van Pelt
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at:
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# -*- coding: utf-8 -*-
""" Unit tests for MAC addresses """
import unittest
import config.mac as mac

View File

@ -1,3 +1,18 @@
#
# Copyright (c) 2022 Pim van Pelt
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at:
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# -*- coding: utf-8 -*-
""" Unit tests for taps """
import unittest
import yaml
import config.tap as tap

View File

@ -1,3 +1,18 @@
#
# Copyright (c) 2022 Pim van Pelt
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at:
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# -*- coding: utf-8 -*-
""" Unit tests for vxlan_tunnels """
import unittest
import yaml
import config.vxlan_tunnel as vxlan_tunnel

View File

@ -11,6 +11,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
""" A vppcfg configuration module that validates vxlan_tunnels """
import logging
import ipaddress
@ -56,6 +57,7 @@ def get_vxlan_tunnels(yaml):
def validate_vxlan_tunnels(yaml):
"""Validate the semantics of all YAML 'vxlan_tunnels' entries"""
result = True
msgs = []
logger = logging.getLogger("vppcfg.config")

View File

@ -13,6 +13,7 @@
# limitations under the License.
#
# -*- coding: utf-8 -*-
""" This is a unit test suite for vppcfg """
import sys
import glob
@ -36,6 +37,8 @@ def example_validator(_yaml):
class YAMLTest(unittest.TestCase):
"""This test suite takes a YAML configuration file and holds it against the syntax
(Yamale) and semantic validators, returning errors in case of validation failures."""
def __init__(self, testName, yaml_filename, yaml_schema):
# calling the super class init varies for different python versions. This works for 2.7
super().__init__(testName)
@ -43,6 +46,7 @@ class YAMLTest(unittest.TestCase):
self.yaml_schema = yaml_schema
def test_yaml(self):
""" The test executor """
test = None
cfg = None
ncount = 0
@ -103,6 +107,7 @@ class YAMLTest(unittest.TestCase):
if __name__ == "__main__":
""" The main program """
parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument(
"-t",

View File

@ -1,3 +1,17 @@
#
# Copyright (c) 2022 Pim van Pelt
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at:
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# -*- coding: utf-8 -*-
"""
The functions in this file interact with the VPP API to modify certain
interface metadata.
@ -18,31 +32,45 @@ class Applier(VPPApi):
self.logger.info("VPP Applier: changing the dataplane is enabled")
def set_interface_ip_address(self, ifname, address, is_set=True):
"""Add (if_set=True) or remove (if_set=False) an IPv4 or IPv6 address including
prefixlen (ie 192.0.2.0/24 or 2001:db8::1/64) to an interface given by name
(ie GigabitEthernet3/0/0)"""
pass
def delete_loopback(self, ifname):
"""Delete a loopback identified by name (ie loop0)"""
pass
def delete_subinterface(self, ifname):
"""Delete a sub-int identified by name (ie GigabitEthernet3/0/0.100)"""
pass
def set_interface_l2_tag_rewrite(self, ifname, mode):
def set_interface_l2_tag_rewrite(self, ifname, vtr_op, vtr_push_dot1q, vtr_tag1, vtr_tag2):
"""Set l2 tag rewrite on an interface identified by name (ie GigabitEthernet3/0/0.100)
into a certain operational mode. TODO(pim) clarify the vtr_* arguments."""
## somewhere in interface.api see vtr_* fields
pass
def set_interface_l3(self, ifname):
"""Set an interface or sub-interface identified by name (ie GigabitEthernet3/0/0)
to L3 mode, removing it from bridges and l2xcs"""
pass
def delete_bridgedomain(self, instance):
def delete_bridgedomain(self, bd_id):
"""Delete a bridgedomain given by instance bd_id (ie 100). Cannot delete instance==0."""
pass
def delete_tap(self, ifname):
"""Delete a tap identified by name (ie tap100)"""
pass
def bond_remove_member(self, bondname, membername):
"""Remove a member interface given by name (ie GigabitEthernet3/0/0) from a bondethernet
interface given by name (ie BondEthernet0)"""
pass
def delete_bond(self, ifname):
"""Delete a bondethernet identified by name (ie BondEthernet0)"""
pass
def create_vxlan_tunnel(self, instance, config, is_create=True):
@ -50,15 +78,22 @@ class Applier(VPPApi):
pass
def set_interface_link_mtu(self, ifname, link_mtu):
"""Set the max frame size of an interface given by name to the link_mtu value (typically
1500, 9000, 9216"""
pass
def lcp_delete(self, lcpname):
"""Delete a linux control plane interface pair by name (ie 'xe0' or 'be10')"""
pass
def set_interface_packet_mtu(self, ifname, packet_mtu):
"""Set the L3 MTU of an interface given by name (ie GigabitEthernet3/0/0)"""
pass
def set_interface_state(self, ifname, state):
"""Set the admin link state (True is up, False is down) of an interface given
by name (ie GigabitEthernet3/0/0)"""
pass
def create_loopback_interface(self, instance, config):
@ -77,28 +112,43 @@ class Applier(VPPApi):
"""'config' is the YAML configuration for the taps: entry"""
pass
def create_bridgedomain(self, instance, config):
def create_bridgedomain(self, bd_id, config):
"""'config' is the YAML configuration for the bridgedomains: entry"""
pass
def lcp_create(self, ifname, host_if_name):
"""Create a linux control plane interface pair for an interface given by name
(ie GigabitEthernet3/0/0) under a Linux TAP device name host_if_name (ie e3-0-0)"""
pass
def set_interface_mac(self, ifname, mac):
"""Set the MAC address of interface given by name (ie GigabitEthernet3/0/0), the
MAC is of form aa:bb:cc:dd:ee:ff"""
pass
def bond_add_member(self, bondname, membername):
"""Add a member interface given by name (ie GigabitEthernet3/0/0) to a bondethernet
given by name (ie BondEthernet0)"""
pass
def sync_bridgedomain(self, instance, config):
def sync_bridgedomain(self, bd_id, config):
"""'config' is the YAML configuration for the bridgedomains: entry"""
pass
def set_interface_l2_bridge_bvi(self, instance, ifname):
def set_interface_l2_bridge_bvi(self, bd_id, ifname):
"""Set a loopback / BVI interface given by name (ie 'loop100') as a BVI of a bridge
domain identified by bd_id (ie 100)"""
pass
def set_interface_l2_bridge(self, instance, ifname):
def set_interface_l2_bridge(self, bd_id, ifname):
"""Set an interface given by name (ie 'GigabitEthernet3/0/0') into a bridge
domain identified by bd_id (ie 100)"""
pass
def set_interface_l2xc(self, rx_ifname, tx_ifname):
"""Cross connect the rx_ifname (ie GigabitEthernet3/0/0) to emit into the tx_ifname
(ie GigabitEthernet3/0/1). Note that this operation typically happens twice, once
for the a->b crossconnect, and again for the b->a crossconnect. Note that
crossconnecting sub-interfaces requires as well L2 rewriting (pop N for the amount
of tags on the source interface)"""
pass

View File

@ -1,3 +1,17 @@
#
# Copyright (c) 2022 Pim van Pelt
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at:
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# -*- coding: utf-8 -*-
"""
The functions in this file interact with the VPP API to retrieve certain
interface metadata and write it to a YAML file.
@ -10,10 +24,17 @@ from config import bondethernet
class Dumper(VPPApi):
"""The Dumper class first reads the configuration from a running VPP Dataplane
by using a set of (readonly) API getters, and then emits the configuration as
a YAML file with its write() method, either to stdout or to a filename.
Note that not all running VPP configs are "valid" in vppcfg's eyes. It is not
guaranteed that the output of the Dumper() will stand validation."""
def __init__(self, address="/run/vpp/api.sock", clientname="vppcfg"):
VPPApi.__init__(self, address, clientname)
def write(self, outfile):
""" Emit the configuration to either stdout (outfile=='-') or a filename """
if outfile and outfile == "-":
file = sys.stdout
outfile = "(stdout)"
@ -29,6 +50,8 @@ class Dumper(VPPApi):
self.logger.info(f"Wrote YAML config to {outfile}")
def cache_to_config(self):
""" Convert the VPP configuration cache (previously read by readconfig() into
a YAML representation."""
config = {
"loopbacks": {},
"bondethernets": {},

View File

@ -13,6 +13,10 @@
# limitations under the License.
#
# -*- coding: utf-8 -*-
"""
The functions in this file interact with the VPP API to retrieve certain
metadata, and plan configuration changes towards a given YAML target configuration.
"""
import sys
import logging
from config import loopback
@ -26,6 +30,14 @@ from vpp.vppapi import VPPApi
class Reconciler:
"""The Reconciler class first reads the running configuration of a VPP Dataplane,
and based on an intended target YAML configuration file, plans a path to make the
dataplane safely reflect the target config. It first prunes (removes) objects that
are not meant to be in the dataplane, or are in the dataplane but are not of the
correct create-time attributes; then it creates objects that are in the configuration
but not yet in the dataplane; and finally it syncs the configuration attributes of
objects that can be changed at runtime."""
def __init__(self, cfg):
self.logger = logging.getLogger("vppcfg.reconciler")
self.logger.addHandler(logging.NullHandler())
@ -68,12 +80,6 @@ class Reconciler:
ret = False
return ret
def vpp_readconfig(self):
if not self.vpp.readconfig():
self.logger.error("Could not (re)read config from VPP")
return False
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
@ -748,6 +754,7 @@ class Reconciler:
return ret
def create_loopbacks(self):
"""Create all loopbacks that occur in the config but not in VPP"""
for ifname in loopback.get_loopbacks(self.cfg):
if ifname in self.vpp.cache["interface_names"]:
continue
@ -760,6 +767,7 @@ class Reconciler:
return True
def create_bondethernets(self):
"""Create all bondethernets that occur in the config but not in VPP"""
for ifname in bondethernet.get_bondethernets(self.cfg):
if ifname in self.vpp.cache["interface_names"]:
continue
@ -776,6 +784,7 @@ class Reconciler:
return True
def create_vxlan_tunnels(self):
"""Create all vxlan_tunnels that occur in the config but not in VPP"""
for ifname in vxlan_tunnel.get_vxlan_tunnels(self.cfg):
if ifname in self.vpp.cache["interface_names"]:
continue
@ -789,6 +798,7 @@ class Reconciler:
return True
def create_sub_interfaces(self):
"""Create all sub-interfaces that occur in the config but not in VPP"""
## First create 1-tag (Dot1Q/Dot1AD), and then create 2-tag (Qin*) sub-interfaces
for do_qinx in [False, True]:
for ifname in interface.get_sub_interfaces(self.cfg):
@ -815,6 +825,7 @@ class Reconciler:
return True
def create_taps(self):
"""Create all taps that occur in the config but not in VPP"""
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"]:
@ -838,6 +849,7 @@ class Reconciler:
return True
def create_bridgedomains(self):
"""Create all bridgedomains that occur in the config but not in VPP"""
for ifname in bridgedomain.get_bridgedomains(self.cfg):
ifname, _iface = bridgedomain.get_by_name(self.cfg, ifname)
instance = int(ifname[2:])
@ -863,6 +875,7 @@ class Reconciler:
return True
def create_lcps(self):
"""Create all LCPs that occur in the config but not in VPP"""
lcpnames = [
self.vpp.cache["lcps"][x].host_if_name for x in self.vpp.cache["lcps"]
]
@ -900,6 +913,7 @@ class Reconciler:
return True
def sync(self):
"""Synchronize the VPP Dataplane configuration for all objects in the config"""
ret = True
if not self.sync_loopbacks():
self.logger.warning("Could not sync Loopbacks in VPP")
@ -928,6 +942,7 @@ class Reconciler:
return ret
def sync_loopbacks(self):
"""Synchronize the VPP Dataplane configuration for loopbacks"""
for ifname in loopback.get_loopbacks(self.cfg):
if not ifname in self.vpp.cache["interface_names"]:
## New loopback
@ -942,6 +957,7 @@ class Reconciler:
return True
def sync_phys(self):
"""Synchronize the VPP Dataplane configuration for PHYs"""
for ifname in interface.get_phys(self.cfg):
if not ifname in self.vpp.cache["interface_names"]:
## New interface
@ -956,6 +972,7 @@ class Reconciler:
return True
def sync_bondethernets(self):
"""Synchronize the VPP Dataplane configuration for bondethernets"""
for ifname in bondethernet.get_bondethernets(self.cfg):
if ifname in self.vpp.cache["interface_names"]:
vpp_iface = self.vpp.cache["interface_names"][ifname]
@ -1008,6 +1025,7 @@ class Reconciler:
return True
def sync_bridgedomains(self):
"""Synchronize the VPP Dataplane configuration for bridgedomains"""
for ifname in bridgedomain.get_bridgedomains(self.cfg):
instance = int(ifname[2:])
if instance in self.vpp.cache["bridgedomains"]:
@ -1097,6 +1115,7 @@ class Reconciler:
return True
def sync_l2xcs(self):
"""Synchronize the VPP Dataplane configuration for L2 cross connects"""
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(
@ -1134,6 +1153,9 @@ class Reconciler:
return True
def sync_mtu_direction(self, shrink=True):
"""Synchronize the VPP Dataplane packet MTU, where 'shrink' determines the
direction (if shrink is True, go from inner-most (QinQ) to outer-most (untagged),
and the other direction if shrink is False"""
if shrink:
tag_list = [2, 1, 0]
else:
@ -1176,6 +1198,9 @@ class Reconciler:
return True
def sync_link_mtu_direction(self, shrink=True):
"""Synchronize the VPP Dataplane max frame size (link MTU), where 'shrink' determines the
direction (if shrink is True, go from inner-most (QinQ) to outer-most (untagged),
and the other direction if shrink is False"""
for _idx, vpp_iface in self.vpp.cache["interfaces"].items():
if vpp_iface.sub_number_of_tags != 0:
continue
@ -1231,6 +1256,7 @@ class Reconciler:
return True
def sync_mtu(self):
"""Synchronize the VPP Dataplane configuration for interface MTU"""
ret = True
if not self.sync_link_mtu_direction(shrink=False):
self.logger.warning(
@ -1251,6 +1277,7 @@ class Reconciler:
return ret
def sync_addresses(self):
"""Synchronize the VPP Dataplane configuration for interface addresses"""
for ifname in interface.get_interfaces(self.cfg) + loopback.get_loopbacks(
self.cfg
):
@ -1279,6 +1306,7 @@ class Reconciler:
return True
def sync_admin_state(self):
"""Synchronize the VPP Dataplane configuration for interface admin state"""
for ifname in interface.get_interfaces(self.cfg) + loopback.get_loopbacks(
self.cfg
):

View File

@ -1,3 +1,17 @@
#
# Copyright (c) 2022 Pim van Pelt
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at:
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# -*- coding: utf-8 -*-
"""
The functions in this file interact with the VPP API to retrieve certain
interface metadata. Its base class will never change state. See the
@ -12,6 +26,7 @@ from vpp_papi import VPPApiClient
class VPPApi:
"""The VPPApi class is a base class that abstracts the vpp_papi."""
def __init__(self, address="/run/vpp/api.sock", clientname="vppcfg"):
self.logger = logging.getLogger("vppcfg.vppapi")
self.logger.addHandler(logging.NullHandler())
@ -25,6 +40,7 @@ class VPPApi:
self.lcp_enabled = False
def connect(self):
""" Connect to the VPP Dataplane, if we're not already connected """
if self.connected:
return True
@ -56,6 +72,7 @@ class VPPApi:
return True
def disconnect(self):
"""Disconnect from the VPP dataplane, if we are still connected."""
if not self.connected:
return True
self.vpp.disconnect()
@ -64,6 +81,7 @@ class VPPApi:
return True
def cache_clear(self):
""" Remove the cached VPP configuration elements and return an empty dictionary"""
self.cache_read = False
return {
"lcps": {},
@ -79,7 +97,7 @@ class VPPApi:
}
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 VPP config cache"""
for _idx, lcp in self.cache["lcps"].items():
if lcp.host_if_name == lcpname:
ifname = self.cache["interfaces"][lcp.host_sw_if_index].interface_name
@ -91,7 +109,7 @@ class VPPApi:
return False
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 VPP config cache"""
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}"
@ -106,6 +124,7 @@ class VPPApi:
return True
def cache_remove_l2xc(self, ifname):
"""Remvoes the l2xc from the VPP config cache"""
if not ifname in self.cache["interface_names"]:
self.logger.warning(
f"Trying to remove an L2XC which is not in the config: {ifname}"
@ -116,6 +135,7 @@ class VPPApi:
return True
def cache_remove_vxlan_tunnel(self, ifname):
"""Removes a vxlan_tunnel from the VPP config cache"""
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}"
@ -127,7 +147,7 @@ class VPPApi:
return True
def cache_remove_interface(self, ifname):
"""Removes the interface, identified by name, from the config."""
"""Removes the interface, identified by name, from the VPP config cache"""
if not ifname in self.cache["interface_names"]:
self.logger.warning(
f"Trying to remove an interface which is not in the config: {ifname}"
@ -154,6 +174,8 @@ class VPPApi:
return True
def readconfig(self):
"""Read the configuration out of a running VPP Dataplane and put it into a
VPP config cache"""
# pylint: disable=no-member
if not self.connected and not self.connect():
self.logger.error("Could not connect to VPP")
@ -257,6 +279,7 @@ class VPPApi:
return ret
def get_sub_interfaces(self):
"""Return all interfaces which have a sub-id and one or more tags"""
subints = [
self.cache["interfaces"][x].interface_name
for x in self.cache["interfaces"]
@ -266,6 +289,7 @@ class VPPApi:
return subints
def get_qinx_interfaces(self):
"""Return all interfaces which have a sub-id and a non-zero inner vlan tag"""
qinx_subints = [
self.cache["interfaces"][x].interface_name
for x in self.cache["interfaces"]
@ -275,6 +299,7 @@ class VPPApi:
return qinx_subints
def get_dot1x_interfaces(self):
"""Return all interfaces which have only an outer vlan tag (dot1q/dot1ad)"""
dot1x_subints = [
self.cache["interfaces"][x].interface_name
for x in self.cache["interfaces"]
@ -284,6 +309,7 @@ class VPPApi:
return dot1x_subints
def get_loopbacks(self):
"""Return all interfaces of VPP type 'Loopback'"""
loopbacks = [
self.cache["interfaces"][x].interface_name
for x in self.cache["interfaces"]
@ -292,6 +318,8 @@ class VPPApi:
return loopbacks
def get_phys(self):
"""Return all interfaces for which the super interface has the same sw_if_index
and aren't known to be virtual interfaces"""
phys = [
self.cache["interfaces"][x].interface_name
for x in self.cache["interfaces"]
@ -303,6 +331,7 @@ class VPPApi:
return phys
def get_bondethernets(self):
"""Return all bondethernet interfaces"""
bonds = [
self.cache["bondethernets"][x].interface_name
for x in self.cache["bondethernets"]
@ -310,6 +339,7 @@ class VPPApi:
return bonds
def get_vxlan_tunnels(self):
"""Return all vxlan_tunnel interfaces"""
vxlan_tunnels = [
self.cache["interfaces"][x].interface_name
for x in self.cache["interfaces"]
@ -318,6 +348,7 @@ class VPPApi:
return vxlan_tunnels
def get_lcp_by_interface(self, sw_if_index):
"""Return the LCP config cache for the interface given by sw_if_index"""
for _idx, lcp in self.cache["lcps"].items():
if lcp.phy_sw_if_index == sw_if_index:
return lcp

3
vppcfg
View File

@ -30,6 +30,7 @@ except ImportError:
def main():
""" The main vppcfg program """
parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument(
"-d",
@ -175,7 +176,7 @@ def main():
sys.exit(0)
reconciler = Reconciler(cfg)
if not reconciler.vpp_readconfig():
if not reconciler.vpp.readconfig():
sys.exit(-3)
if not reconciler.phys_exist_in_vpp():