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
|
# W1203 = Use lazy % formatting in logging functions
|
||||||
|
|
||||||
# Disable the message(s) with the given id(s).
|
# 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]
|
[FORMAT]
|
||||||
max-line-length=148
|
max-line-length=148
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
""" A vppcfg configuration module that exposes its semantic/syntax validators """
|
||||||
from __future__ import (
|
from __future__ import (
|
||||||
absolute_import,
|
absolute_import,
|
||||||
division,
|
division,
|
||||||
@ -66,6 +67,15 @@ class IPInterfaceWithPrefixLength(Validator):
|
|||||||
|
|
||||||
|
|
||||||
class 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):
|
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,6 +91,8 @@ class Validator:
|
|||||||
]
|
]
|
||||||
|
|
||||||
def validate(self, yaml):
|
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_retval = True
|
||||||
ret_msgs = []
|
ret_msgs = []
|
||||||
if not yaml:
|
if not yaml:
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
# 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.
|
||||||
#
|
#
|
||||||
|
""" A vppcfg configuration module that handles addresses """
|
||||||
import ipaddress
|
import ipaddress
|
||||||
|
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
# 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.
|
||||||
#
|
#
|
||||||
|
""" A vppcfg configuration module that handles bondethernets """
|
||||||
import logging
|
import logging
|
||||||
from config import interface
|
from config import interface
|
||||||
from config import mac
|
from config import mac
|
||||||
@ -162,6 +163,7 @@ def int_to_lb(loadbalance):
|
|||||||
|
|
||||||
|
|
||||||
def validate_bondethernets(yaml):
|
def validate_bondethernets(yaml):
|
||||||
|
"""Validate the semantics of all YAML 'bondethernets' entries"""
|
||||||
result = True
|
result = True
|
||||||
msgs = []
|
msgs = []
|
||||||
logger = logging.getLogger("vppcfg.config")
|
logger = logging.getLogger("vppcfg.config")
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
# 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.
|
||||||
#
|
#
|
||||||
|
""" A vppcfg configuration module that handles bridgedomains """
|
||||||
import logging
|
import logging
|
||||||
from config import interface
|
from config import interface
|
||||||
from config import loopback
|
from config import loopback
|
||||||
@ -81,6 +82,8 @@ def bvi_unique(yaml, bviname):
|
|||||||
|
|
||||||
|
|
||||||
def get_settings(yaml, ifname):
|
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)
|
ifname, iface = get_by_name(yaml, ifname)
|
||||||
if not iface:
|
if not iface:
|
||||||
return None
|
return None
|
||||||
@ -115,6 +118,7 @@ def get_settings(yaml, ifname):
|
|||||||
|
|
||||||
|
|
||||||
def validate_bridgedomains(yaml):
|
def validate_bridgedomains(yaml):
|
||||||
|
"""Validate the semantics of all YAML 'bridgedomains' entries"""
|
||||||
result = True
|
result = True
|
||||||
msgs = []
|
msgs = []
|
||||||
logger = logging.getLogger("vppcfg.config")
|
logger = logging.getLogger("vppcfg.config")
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
# 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.
|
||||||
#
|
#
|
||||||
|
""" A vppcfg configuration module that validates interfaces """
|
||||||
import logging
|
import logging
|
||||||
from config import bondethernet
|
from config import bondethernet
|
||||||
from config import bridgedomain
|
from config import bridgedomain
|
||||||
@ -415,6 +416,7 @@ def get_admin_state(yaml, ifname):
|
|||||||
|
|
||||||
|
|
||||||
def validate_interfaces(yaml):
|
def validate_interfaces(yaml):
|
||||||
|
"""Validate the semantics of all YAML 'interfaces' entries"""
|
||||||
result = True
|
result = True
|
||||||
msgs = []
|
msgs = []
|
||||||
logger = logging.getLogger("vppcfg.config")
|
logger = logging.getLogger("vppcfg.config")
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
# 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.
|
||||||
#
|
#
|
||||||
|
""" A vppcfg configuration module that validates Linux Control Plane (lcp) elements """
|
||||||
|
|
||||||
|
|
||||||
def get_lcps(yaml, interfaces=True, loopbacks=True, bridgedomains=True):
|
def get_lcps(yaml, interfaces=True, loopbacks=True, bridgedomains=True):
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
# 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.
|
||||||
#
|
#
|
||||||
|
""" A vppcfg configuration module that validates loopbacks """
|
||||||
import logging
|
import logging
|
||||||
from config import lcp
|
from config import lcp
|
||||||
from config import address
|
from config import address
|
||||||
@ -53,6 +54,7 @@ def is_loopback(yaml, ifname):
|
|||||||
|
|
||||||
|
|
||||||
def validate_loopbacks(yaml):
|
def validate_loopbacks(yaml):
|
||||||
|
"""Validate the semantics of all YAML 'loopbacks' entries"""
|
||||||
result = True
|
result = True
|
||||||
msgs = []
|
msgs = []
|
||||||
logger = logging.getLogger("vppcfg.config")
|
logger = logging.getLogger("vppcfg.config")
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
# 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.
|
||||||
#
|
#
|
||||||
|
""" A vppcfg configuration module that validates MAC addresses """
|
||||||
import netaddr
|
import netaddr
|
||||||
|
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
# 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.
|
||||||
#
|
#
|
||||||
|
""" A vppcfg configuration module that validates taps """
|
||||||
import logging
|
import logging
|
||||||
from config import mac
|
from config import mac
|
||||||
|
|
||||||
@ -54,6 +55,7 @@ def is_host_name_unique(yaml, hostname):
|
|||||||
|
|
||||||
|
|
||||||
def validate_taps(yaml):
|
def validate_taps(yaml):
|
||||||
|
"""Validate the semantics of all YAML 'taps' entries"""
|
||||||
result = True
|
result = True
|
||||||
msgs = []
|
msgs = []
|
||||||
logger = logging.getLogger("vppcfg.config")
|
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 unittest
|
||||||
import yaml
|
import yaml
|
||||||
import config.bondethernet as bondethernet
|
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 unittest
|
||||||
import yaml
|
import yaml
|
||||||
import config.bridgedomain as bridgedomain
|
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 unittest
|
||||||
import yaml
|
import yaml
|
||||||
import config.interface as interface
|
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 unittest
|
||||||
import yaml
|
import yaml
|
||||||
import config.lcp as lcp
|
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 unittest
|
||||||
import yaml
|
import yaml
|
||||||
import config.loopback as loopback
|
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 unittest
|
||||||
import config.mac as mac
|
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 unittest
|
||||||
import yaml
|
import yaml
|
||||||
import config.tap as tap
|
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 unittest
|
||||||
import yaml
|
import yaml
|
||||||
import config.vxlan_tunnel as vxlan_tunnel
|
import config.vxlan_tunnel as vxlan_tunnel
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
# 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.
|
||||||
#
|
#
|
||||||
|
""" A vppcfg configuration module that validates vxlan_tunnels """
|
||||||
import logging
|
import logging
|
||||||
import ipaddress
|
import ipaddress
|
||||||
|
|
||||||
@ -56,6 +57,7 @@ def get_vxlan_tunnels(yaml):
|
|||||||
|
|
||||||
|
|
||||||
def validate_vxlan_tunnels(yaml):
|
def validate_vxlan_tunnels(yaml):
|
||||||
|
"""Validate the semantics of all YAML 'vxlan_tunnels' entries"""
|
||||||
result = True
|
result = True
|
||||||
msgs = []
|
msgs = []
|
||||||
logger = logging.getLogger("vppcfg.config")
|
logger = logging.getLogger("vppcfg.config")
|
||||||
|
5
tests.py
5
tests.py
@ -13,6 +13,7 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
""" This is a unit test suite for vppcfg """
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import glob
|
import glob
|
||||||
@ -36,6 +37,8 @@ def example_validator(_yaml):
|
|||||||
|
|
||||||
|
|
||||||
class YAMLTest(unittest.TestCase):
|
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):
|
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().__init__(testName)
|
super().__init__(testName)
|
||||||
@ -43,6 +46,7 @@ class YAMLTest(unittest.TestCase):
|
|||||||
self.yaml_schema = yaml_schema
|
self.yaml_schema = yaml_schema
|
||||||
|
|
||||||
def test_yaml(self):
|
def test_yaml(self):
|
||||||
|
""" The test executor """
|
||||||
test = None
|
test = None
|
||||||
cfg = None
|
cfg = None
|
||||||
ncount = 0
|
ncount = 0
|
||||||
@ -103,6 +107,7 @@ class YAMLTest(unittest.TestCase):
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
""" The main program """
|
||||||
parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter)
|
parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-t",
|
"-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
|
The functions in this file interact with the VPP API to modify certain
|
||||||
interface metadata.
|
interface metadata.
|
||||||
@ -18,31 +32,45 @@ class Applier(VPPApi):
|
|||||||
self.logger.info("VPP Applier: changing the dataplane is enabled")
|
self.logger.info("VPP Applier: changing the dataplane is enabled")
|
||||||
|
|
||||||
def set_interface_ip_address(self, ifname, address, is_set=True):
|
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
|
pass
|
||||||
|
|
||||||
def delete_loopback(self, ifname):
|
def delete_loopback(self, ifname):
|
||||||
|
"""Delete a loopback identified by name (ie loop0)"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def delete_subinterface(self, ifname):
|
def delete_subinterface(self, ifname):
|
||||||
|
"""Delete a sub-int identified by name (ie GigabitEthernet3/0/0.100)"""
|
||||||
pass
|
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
|
## somewhere in interface.api see vtr_* fields
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def set_interface_l3(self, ifname):
|
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
|
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
|
pass
|
||||||
|
|
||||||
def delete_tap(self, ifname):
|
def delete_tap(self, ifname):
|
||||||
|
"""Delete a tap identified by name (ie tap100)"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def bond_remove_member(self, bondname, membername):
|
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
|
pass
|
||||||
|
|
||||||
def delete_bond(self, ifname):
|
def delete_bond(self, ifname):
|
||||||
|
"""Delete a bondethernet identified by name (ie BondEthernet0)"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def create_vxlan_tunnel(self, instance, config, is_create=True):
|
def create_vxlan_tunnel(self, instance, config, is_create=True):
|
||||||
@ -50,15 +78,22 @@ class Applier(VPPApi):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def set_interface_link_mtu(self, ifname, link_mtu):
|
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
|
pass
|
||||||
|
|
||||||
def lcp_delete(self, lcpname):
|
def lcp_delete(self, lcpname):
|
||||||
|
"""Delete a linux control plane interface pair by name (ie 'xe0' or 'be10')"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def set_interface_packet_mtu(self, ifname, packet_mtu):
|
def set_interface_packet_mtu(self, ifname, packet_mtu):
|
||||||
|
"""Set the L3 MTU of an interface given by name (ie GigabitEthernet3/0/0)"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def set_interface_state(self, ifname, state):
|
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
|
pass
|
||||||
|
|
||||||
def create_loopback_interface(self, instance, config):
|
def create_loopback_interface(self, instance, config):
|
||||||
@ -77,28 +112,43 @@ class Applier(VPPApi):
|
|||||||
"""'config' is the YAML configuration for the taps: entry"""
|
"""'config' is the YAML configuration for the taps: entry"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def create_bridgedomain(self, instance, config):
|
def create_bridgedomain(self, bd_id, config):
|
||||||
"""'config' is the YAML configuration for the bridgedomains: entry"""
|
"""'config' is the YAML configuration for the bridgedomains: entry"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def lcp_create(self, ifname, host_if_name):
|
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
|
pass
|
||||||
|
|
||||||
def set_interface_mac(self, ifname, mac):
|
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
|
pass
|
||||||
|
|
||||||
def bond_add_member(self, bondname, membername):
|
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
|
pass
|
||||||
|
|
||||||
def sync_bridgedomain(self, instance, config):
|
def sync_bridgedomain(self, bd_id, config):
|
||||||
"""'config' is the YAML configuration for the bridgedomains: entry"""
|
"""'config' is the YAML configuration for the bridgedomains: entry"""
|
||||||
pass
|
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
|
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
|
pass
|
||||||
|
|
||||||
def set_interface_l2xc(self, rx_ifname, tx_ifname):
|
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
|
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
|
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.
|
||||||
@ -10,10 +24,17 @@ from config import bondethernet
|
|||||||
|
|
||||||
|
|
||||||
class Dumper(VPPApi):
|
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"):
|
def __init__(self, address="/run/vpp/api.sock", clientname="vppcfg"):
|
||||||
VPPApi.__init__(self, address, clientname)
|
VPPApi.__init__(self, address, clientname)
|
||||||
|
|
||||||
def write(self, outfile):
|
def write(self, outfile):
|
||||||
|
""" Emit the configuration to either stdout (outfile=='-') or a filename """
|
||||||
if outfile and outfile == "-":
|
if outfile and outfile == "-":
|
||||||
file = sys.stdout
|
file = sys.stdout
|
||||||
outfile = "(stdout)"
|
outfile = "(stdout)"
|
||||||
@ -29,6 +50,8 @@ class Dumper(VPPApi):
|
|||||||
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):
|
||||||
|
""" Convert the VPP configuration cache (previously read by readconfig() into
|
||||||
|
a YAML representation."""
|
||||||
config = {
|
config = {
|
||||||
"loopbacks": {},
|
"loopbacks": {},
|
||||||
"bondethernets": {},
|
"bondethernets": {},
|
||||||
|
@ -13,6 +13,10 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
# -*- coding: utf-8 -*-
|
# -*- 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 sys
|
||||||
import logging
|
import logging
|
||||||
from config import loopback
|
from config import loopback
|
||||||
@ -26,6 +30,14 @@ from vpp.vppapi import VPPApi
|
|||||||
|
|
||||||
|
|
||||||
class Reconciler:
|
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):
|
def __init__(self, cfg):
|
||||||
self.logger = logging.getLogger("vppcfg.reconciler")
|
self.logger = logging.getLogger("vppcfg.reconciler")
|
||||||
self.logger.addHandler(logging.NullHandler())
|
self.logger.addHandler(logging.NullHandler())
|
||||||
@ -68,12 +80,6 @@ class Reconciler:
|
|||||||
ret = False
|
ret = False
|
||||||
return ret
|
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):
|
def prune(self):
|
||||||
"""Remove all objects from VPP that do not occur in the config. For an indepth explanation
|
"""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
|
of how and why this particular pruning order is chosen, see README.md section on
|
||||||
@ -748,6 +754,7 @@ class Reconciler:
|
|||||||
return ret
|
return ret
|
||||||
|
|
||||||
def create_loopbacks(self):
|
def create_loopbacks(self):
|
||||||
|
"""Create all loopbacks that occur in the config but not in VPP"""
|
||||||
for ifname in loopback.get_loopbacks(self.cfg):
|
for ifname in loopback.get_loopbacks(self.cfg):
|
||||||
if ifname in self.vpp.cache["interface_names"]:
|
if ifname in self.vpp.cache["interface_names"]:
|
||||||
continue
|
continue
|
||||||
@ -760,6 +767,7 @@ class Reconciler:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def create_bondethernets(self):
|
def create_bondethernets(self):
|
||||||
|
"""Create all bondethernets that occur in the config but not in VPP"""
|
||||||
for ifname in bondethernet.get_bondethernets(self.cfg):
|
for ifname in bondethernet.get_bondethernets(self.cfg):
|
||||||
if ifname in self.vpp.cache["interface_names"]:
|
if ifname in self.vpp.cache["interface_names"]:
|
||||||
continue
|
continue
|
||||||
@ -776,6 +784,7 @@ class Reconciler:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def create_vxlan_tunnels(self):
|
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):
|
for ifname in vxlan_tunnel.get_vxlan_tunnels(self.cfg):
|
||||||
if ifname in self.vpp.cache["interface_names"]:
|
if ifname in self.vpp.cache["interface_names"]:
|
||||||
continue
|
continue
|
||||||
@ -789,6 +798,7 @@ class Reconciler:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def create_sub_interfaces(self):
|
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
|
## First create 1-tag (Dot1Q/Dot1AD), and then create 2-tag (Qin*) sub-interfaces
|
||||||
for do_qinx in [False, True]:
|
for do_qinx in [False, True]:
|
||||||
for ifname in interface.get_sub_interfaces(self.cfg):
|
for ifname in interface.get_sub_interfaces(self.cfg):
|
||||||
@ -815,6 +825,7 @@ class Reconciler:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def create_taps(self):
|
def create_taps(self):
|
||||||
|
"""Create all taps that occur in the config but not in VPP"""
|
||||||
for ifname in tap.get_taps(self.cfg):
|
for ifname in tap.get_taps(self.cfg):
|
||||||
ifname, iface = tap.get_by_name(self.cfg, ifname)
|
ifname, iface = tap.get_by_name(self.cfg, ifname)
|
||||||
if ifname in self.vpp.cache["interface_names"]:
|
if ifname in self.vpp.cache["interface_names"]:
|
||||||
@ -838,6 +849,7 @@ class Reconciler:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def create_bridgedomains(self):
|
def create_bridgedomains(self):
|
||||||
|
"""Create all bridgedomains that occur in the config but not in VPP"""
|
||||||
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:])
|
||||||
@ -863,6 +875,7 @@ class Reconciler:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def create_lcps(self):
|
def create_lcps(self):
|
||||||
|
"""Create all LCPs that occur in the config but not in VPP"""
|
||||||
lcpnames = [
|
lcpnames = [
|
||||||
self.vpp.cache["lcps"][x].host_if_name for x in self.vpp.cache["lcps"]
|
self.vpp.cache["lcps"][x].host_if_name for x in self.vpp.cache["lcps"]
|
||||||
]
|
]
|
||||||
@ -900,6 +913,7 @@ class Reconciler:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def sync(self):
|
def sync(self):
|
||||||
|
"""Synchronize the VPP Dataplane configuration for all objects in the config"""
|
||||||
ret = True
|
ret = True
|
||||||
if not self.sync_loopbacks():
|
if not self.sync_loopbacks():
|
||||||
self.logger.warning("Could not sync Loopbacks in VPP")
|
self.logger.warning("Could not sync Loopbacks in VPP")
|
||||||
@ -928,6 +942,7 @@ class Reconciler:
|
|||||||
return ret
|
return ret
|
||||||
|
|
||||||
def sync_loopbacks(self):
|
def sync_loopbacks(self):
|
||||||
|
"""Synchronize the VPP Dataplane configuration for loopbacks"""
|
||||||
for ifname in loopback.get_loopbacks(self.cfg):
|
for ifname in loopback.get_loopbacks(self.cfg):
|
||||||
if not ifname in self.vpp.cache["interface_names"]:
|
if not ifname in self.vpp.cache["interface_names"]:
|
||||||
## New loopback
|
## New loopback
|
||||||
@ -942,6 +957,7 @@ class Reconciler:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def sync_phys(self):
|
def sync_phys(self):
|
||||||
|
"""Synchronize the VPP Dataplane configuration for PHYs"""
|
||||||
for ifname in interface.get_phys(self.cfg):
|
for ifname in interface.get_phys(self.cfg):
|
||||||
if not ifname in self.vpp.cache["interface_names"]:
|
if not ifname in self.vpp.cache["interface_names"]:
|
||||||
## New interface
|
## New interface
|
||||||
@ -956,6 +972,7 @@ class Reconciler:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def sync_bondethernets(self):
|
def sync_bondethernets(self):
|
||||||
|
"""Synchronize the VPP Dataplane configuration for bondethernets"""
|
||||||
for ifname in bondethernet.get_bondethernets(self.cfg):
|
for ifname in bondethernet.get_bondethernets(self.cfg):
|
||||||
if ifname in self.vpp.cache["interface_names"]:
|
if ifname in self.vpp.cache["interface_names"]:
|
||||||
vpp_iface = self.vpp.cache["interface_names"][ifname]
|
vpp_iface = self.vpp.cache["interface_names"][ifname]
|
||||||
@ -1008,6 +1025,7 @@ class Reconciler:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def sync_bridgedomains(self):
|
def sync_bridgedomains(self):
|
||||||
|
"""Synchronize the VPP Dataplane configuration for bridgedomains"""
|
||||||
for ifname in bridgedomain.get_bridgedomains(self.cfg):
|
for ifname in bridgedomain.get_bridgedomains(self.cfg):
|
||||||
instance = int(ifname[2:])
|
instance = int(ifname[2:])
|
||||||
if instance in self.vpp.cache["bridgedomains"]:
|
if instance in self.vpp.cache["bridgedomains"]:
|
||||||
@ -1097,6 +1115,7 @@ class Reconciler:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def sync_l2xcs(self):
|
def sync_l2xcs(self):
|
||||||
|
"""Synchronize the VPP Dataplane configuration for L2 cross connects"""
|
||||||
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(
|
||||||
@ -1134,6 +1153,9 @@ class Reconciler:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def sync_mtu_direction(self, shrink=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:
|
if shrink:
|
||||||
tag_list = [2, 1, 0]
|
tag_list = [2, 1, 0]
|
||||||
else:
|
else:
|
||||||
@ -1176,6 +1198,9 @@ class Reconciler:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def sync_link_mtu_direction(self, shrink=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():
|
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
|
||||||
@ -1231,6 +1256,7 @@ class Reconciler:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def sync_mtu(self):
|
def sync_mtu(self):
|
||||||
|
"""Synchronize the VPP Dataplane configuration for interface MTU"""
|
||||||
ret = True
|
ret = True
|
||||||
if not self.sync_link_mtu_direction(shrink=False):
|
if not self.sync_link_mtu_direction(shrink=False):
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
@ -1251,6 +1277,7 @@ class Reconciler:
|
|||||||
return ret
|
return ret
|
||||||
|
|
||||||
def sync_addresses(self):
|
def sync_addresses(self):
|
||||||
|
"""Synchronize the VPP Dataplane configuration for interface addresses"""
|
||||||
for ifname in interface.get_interfaces(self.cfg) + loopback.get_loopbacks(
|
for ifname in interface.get_interfaces(self.cfg) + loopback.get_loopbacks(
|
||||||
self.cfg
|
self.cfg
|
||||||
):
|
):
|
||||||
@ -1279,6 +1306,7 @@ class Reconciler:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def sync_admin_state(self):
|
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(
|
for ifname in interface.get_interfaces(self.cfg) + loopback.get_loopbacks(
|
||||||
self.cfg
|
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
|
The functions in this file interact with the VPP API to retrieve certain
|
||||||
interface metadata. Its base class will never change state. See the
|
interface metadata. Its base class will never change state. See the
|
||||||
@ -12,6 +26,7 @@ from vpp_papi import VPPApiClient
|
|||||||
|
|
||||||
|
|
||||||
class VPPApi:
|
class VPPApi:
|
||||||
|
"""The VPPApi class is a base class that abstracts the vpp_papi."""
|
||||||
def __init__(self, address="/run/vpp/api.sock", clientname="vppcfg"):
|
def __init__(self, address="/run/vpp/api.sock", clientname="vppcfg"):
|
||||||
self.logger = logging.getLogger("vppcfg.vppapi")
|
self.logger = logging.getLogger("vppcfg.vppapi")
|
||||||
self.logger.addHandler(logging.NullHandler())
|
self.logger.addHandler(logging.NullHandler())
|
||||||
@ -25,6 +40,7 @@ class VPPApi:
|
|||||||
self.lcp_enabled = False
|
self.lcp_enabled = False
|
||||||
|
|
||||||
def connect(self):
|
def connect(self):
|
||||||
|
""" Connect to the VPP Dataplane, if we're not already connected """
|
||||||
if self.connected:
|
if self.connected:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -56,6 +72,7 @@ class VPPApi:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def disconnect(self):
|
def disconnect(self):
|
||||||
|
"""Disconnect from the VPP dataplane, if we are still connected."""
|
||||||
if not self.connected:
|
if not self.connected:
|
||||||
return True
|
return True
|
||||||
self.vpp.disconnect()
|
self.vpp.disconnect()
|
||||||
@ -64,6 +81,7 @@ class VPPApi:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def cache_clear(self):
|
def cache_clear(self):
|
||||||
|
""" Remove the cached VPP configuration elements and return an empty dictionary"""
|
||||||
self.cache_read = False
|
self.cache_read = False
|
||||||
return {
|
return {
|
||||||
"lcps": {},
|
"lcps": {},
|
||||||
@ -79,7 +97,7 @@ 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 VPP config cache"""
|
||||||
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:
|
||||||
ifname = self.cache["interfaces"][lcp.host_sw_if_index].interface_name
|
ifname = self.cache["interfaces"][lcp.host_sw_if_index].interface_name
|
||||||
@ -91,7 +109,7 @@ class VPPApi:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
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 VPP config cache"""
|
||||||
if not ifname in self.cache["interface_names"]:
|
if not ifname in self.cache["interface_names"]:
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
f"Trying to remove a bondethernet member interface which is not in the config: {ifname}"
|
f"Trying to remove a bondethernet member interface which is not in the config: {ifname}"
|
||||||
@ -106,6 +124,7 @@ class VPPApi:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def cache_remove_l2xc(self, ifname):
|
def cache_remove_l2xc(self, ifname):
|
||||||
|
"""Remvoes the l2xc from the VPP config cache"""
|
||||||
if not ifname in self.cache["interface_names"]:
|
if not ifname in self.cache["interface_names"]:
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
f"Trying to remove an L2XC which is not in the config: {ifname}"
|
f"Trying to remove an L2XC which is not in the config: {ifname}"
|
||||||
@ -116,6 +135,7 @@ class VPPApi:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def cache_remove_vxlan_tunnel(self, ifname):
|
def cache_remove_vxlan_tunnel(self, ifname):
|
||||||
|
"""Removes a vxlan_tunnel from the VPP config cache"""
|
||||||
if not ifname in self.cache["interface_names"]:
|
if not ifname in self.cache["interface_names"]:
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
f"Trying to remove a VXLAN Tunnel which is not in the config: {ifname}"
|
f"Trying to remove a VXLAN Tunnel which is not in the config: {ifname}"
|
||||||
@ -127,7 +147,7 @@ class VPPApi:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def cache_remove_interface(self, ifname):
|
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"]:
|
if not ifname in self.cache["interface_names"]:
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
f"Trying to remove an interface which is not in the config: {ifname}"
|
f"Trying to remove an interface which is not in the config: {ifname}"
|
||||||
@ -154,6 +174,8 @@ class VPPApi:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def readconfig(self):
|
def readconfig(self):
|
||||||
|
"""Read the configuration out of a running VPP Dataplane and put it into a
|
||||||
|
VPP config cache"""
|
||||||
# pylint: disable=no-member
|
# 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")
|
||||||
@ -257,6 +279,7 @@ class VPPApi:
|
|||||||
return ret
|
return ret
|
||||||
|
|
||||||
def get_sub_interfaces(self):
|
def get_sub_interfaces(self):
|
||||||
|
"""Return all interfaces which have a sub-id and one or more tags"""
|
||||||
subints = [
|
subints = [
|
||||||
self.cache["interfaces"][x].interface_name
|
self.cache["interfaces"][x].interface_name
|
||||||
for x in self.cache["interfaces"]
|
for x in self.cache["interfaces"]
|
||||||
@ -266,6 +289,7 @@ class VPPApi:
|
|||||||
return subints
|
return subints
|
||||||
|
|
||||||
def get_qinx_interfaces(self):
|
def get_qinx_interfaces(self):
|
||||||
|
"""Return all interfaces which have a sub-id and a non-zero inner vlan tag"""
|
||||||
qinx_subints = [
|
qinx_subints = [
|
||||||
self.cache["interfaces"][x].interface_name
|
self.cache["interfaces"][x].interface_name
|
||||||
for x in self.cache["interfaces"]
|
for x in self.cache["interfaces"]
|
||||||
@ -275,6 +299,7 @@ class VPPApi:
|
|||||||
return qinx_subints
|
return qinx_subints
|
||||||
|
|
||||||
def get_dot1x_interfaces(self):
|
def get_dot1x_interfaces(self):
|
||||||
|
"""Return all interfaces which have only an outer vlan tag (dot1q/dot1ad)"""
|
||||||
dot1x_subints = [
|
dot1x_subints = [
|
||||||
self.cache["interfaces"][x].interface_name
|
self.cache["interfaces"][x].interface_name
|
||||||
for x in self.cache["interfaces"]
|
for x in self.cache["interfaces"]
|
||||||
@ -284,6 +309,7 @@ class VPPApi:
|
|||||||
return dot1x_subints
|
return dot1x_subints
|
||||||
|
|
||||||
def get_loopbacks(self):
|
def get_loopbacks(self):
|
||||||
|
"""Return all interfaces of VPP type 'Loopback'"""
|
||||||
loopbacks = [
|
loopbacks = [
|
||||||
self.cache["interfaces"][x].interface_name
|
self.cache["interfaces"][x].interface_name
|
||||||
for x in self.cache["interfaces"]
|
for x in self.cache["interfaces"]
|
||||||
@ -292,6 +318,8 @@ class VPPApi:
|
|||||||
return loopbacks
|
return loopbacks
|
||||||
|
|
||||||
def get_phys(self):
|
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 = [
|
phys = [
|
||||||
self.cache["interfaces"][x].interface_name
|
self.cache["interfaces"][x].interface_name
|
||||||
for x in self.cache["interfaces"]
|
for x in self.cache["interfaces"]
|
||||||
@ -303,6 +331,7 @@ class VPPApi:
|
|||||||
return phys
|
return phys
|
||||||
|
|
||||||
def get_bondethernets(self):
|
def get_bondethernets(self):
|
||||||
|
"""Return all bondethernet interfaces"""
|
||||||
bonds = [
|
bonds = [
|
||||||
self.cache["bondethernets"][x].interface_name
|
self.cache["bondethernets"][x].interface_name
|
||||||
for x in self.cache["bondethernets"]
|
for x in self.cache["bondethernets"]
|
||||||
@ -310,6 +339,7 @@ class VPPApi:
|
|||||||
return bonds
|
return bonds
|
||||||
|
|
||||||
def get_vxlan_tunnels(self):
|
def get_vxlan_tunnels(self):
|
||||||
|
"""Return all vxlan_tunnel interfaces"""
|
||||||
vxlan_tunnels = [
|
vxlan_tunnels = [
|
||||||
self.cache["interfaces"][x].interface_name
|
self.cache["interfaces"][x].interface_name
|
||||||
for x in self.cache["interfaces"]
|
for x in self.cache["interfaces"]
|
||||||
@ -318,6 +348,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):
|
||||||
|
"""Return the LCP config cache for the interface given by 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
|
||||||
|
3
vppcfg
3
vppcfg
@ -30,6 +30,7 @@ except ImportError:
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
""" The main vppcfg program """
|
||||||
parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter)
|
parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-d",
|
"-d",
|
||||||
@ -175,7 +176,7 @@ def main():
|
|||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
reconciler = Reconciler(cfg)
|
reconciler = Reconciler(cfg)
|
||||||
if not reconciler.vpp_readconfig():
|
if not reconciler.vpp.readconfig():
|
||||||
sys.exit(-3)
|
sys.exit(-3)
|
||||||
|
|
||||||
if not reconciler.phys_exist_in_vpp():
|
if not reconciler.phys_exist_in_vpp():
|
||||||
|
Reference in New Issue
Block a user