Add an initial stab at docstrings - enable docstring pylinter
This commit is contained in:
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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")
|
||||
|
@ -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")
|
||||
|
@ -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")
|
||||
|
@ -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):
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
|
5
tests.py
5
tests.py
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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": {},
|
||||
|
@ -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
|
||||
):
|
||||
|
@ -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
3
vppcfg
@ -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():
|
||||
|
Reference in New Issue
Block a user