diff --git a/Makefile b/Makefile index d8d31ce..7d8deac 100644 --- a/Makefile +++ b/Makefile @@ -25,6 +25,10 @@ install: pkg-deb: dpkg-buildpackage -uc -us -b +.PHONY: check-style +check-style: + PYTHONPATH=./$(VPPCFG) pylint ./$(VPPCFG) + .PHONY: uninstall uninstall: sudo $(PIP) uninstall $(VPPCFG) diff --git a/debian/rules b/debian/rules index 22d2abd..c2e1c4b 100755 --- a/debian/rules +++ b/debian/rules @@ -3,4 +3,5 @@ %: dh $@ --with python3 --buildsystem=pybuild --with systemd +# TODO: fix test.py to that unit tests can be automagically called. override_dh_auto_test: diff --git a/docs/config-guide.md b/docs/config-guide.md index bc214f5..f4838b9 100644 --- a/docs/config-guide.md +++ b/docs/config-guide.md @@ -13,7 +13,7 @@ types of validation: to a running VPP. *Note*: Some semantic checks are stricter than VPP, because applying them may leave the dataplane in a non-recoverable state. -For the curious, the Yamale syntax validation lives in [this schema](../schema.yaml). +For the curious, the Yamale syntax validation lives in [this schema](../vppcfg/schema.yaml). If you want to get started quickly and don't mind cargo-culting, take a look at [this example](../example.yaml). ### Basic structure diff --git a/setup.py b/setup.py index 20a8a36..533e9df 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,5 @@ -from setuptools import setup, find_packages +"""vppcfg setuptools setup.py for pip and deb pkg installations""" +from setuptools import setup setup( name="vppcfg", @@ -17,5 +18,5 @@ setup( "vppcfg = vppcfg.vppcfg:main", ] }, - include_package_data=True, + package_data={"vppcfg": ["*.yaml"]}, ) diff --git a/vppcfg/__init__.py b/vppcfg/__init__.py index 93bc317..81d730c 100644 --- a/vppcfg/__init__.py +++ b/vppcfg/__init__.py @@ -1,9 +1,16 @@ -import os, sys - -ROOT_DIR = os.path.dirname(__file__) - -# fix the module load path -sys.path.insert(0, ROOT_DIR) - -# fix the yaml search path -os.chdir(ROOT_DIR) +#!/usr/bin/env python +# +# Copyright (c) 2022 Ray Kinsella +# +# 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 -*- +""" __init__.py added to suppress pylint error """ diff --git a/vppcfg/config/__init__.py b/vppcfg/config/__init__.py index 63115d5..74fb926 100644 --- a/vppcfg/config/__init__.py +++ b/vppcfg/config/__init__.py @@ -32,12 +32,12 @@ except ImportError: sys.exit(-2) from yamale.validators import DefaultValidators, Validator -from config.loopback import validate_loopbacks -from config.bondethernet import validate_bondethernets -from config.interface import validate_interfaces -from config.bridgedomain import validate_bridgedomains -from config.vxlan_tunnel import validate_vxlan_tunnels -from config.tap import validate_taps +from .loopback import validate_loopbacks +from .bondethernet import validate_bondethernets +from .interface import validate_interfaces +from .bridgedomain import validate_bridgedomains +from .vxlan_tunnel import validate_vxlan_tunnels +from .tap import validate_taps class IPInterfaceWithPrefixLength(Validator): @@ -104,13 +104,12 @@ class Validator: if self.schema: fname = self.schema self.logger.debug(f"Validating against --schema {fname}") - elif hasattr(sys, "_MEIPASS"): - ## See vppcfg.spec data_files that includes schema.yaml into the bundle - self.logger.debug("Validating against built-in schema") - fname = os.path.join(sys._MEIPASS, "schema.yaml") else: - fname = "./schema.yaml" - self.logger.debug(f"Validating against fallthrough default schema {fname}") + ## See setup.py data files that includes schema.yaml into the bundle + self.logger.debug("Validating against built-in schema") + fname = os.path.abspath( + os.path.join(os.path.dirname(__file__), "..", "schema.yaml") + ) if not os.path.isfile(fname): self.logger.error(f"Cannot file schema file: {fname}") diff --git a/vppcfg/config/bondethernet.py b/vppcfg/config/bondethernet.py index 6ef17df..f23d55f 100644 --- a/vppcfg/config/bondethernet.py +++ b/vppcfg/config/bondethernet.py @@ -13,8 +13,8 @@ # """ A vppcfg configuration module that handles bondethernets """ import logging -from config import interface -from config import mac +from . import interface +from . import mac def get_bondethernets(yaml): diff --git a/vppcfg/config/bridgedomain.py b/vppcfg/config/bridgedomain.py index fd0b634..74f5e61 100644 --- a/vppcfg/config/bridgedomain.py +++ b/vppcfg/config/bridgedomain.py @@ -13,8 +13,8 @@ # """ A vppcfg configuration module that handles bridgedomains """ import logging -from config import interface -from config import loopback +from . import interface +from . import loopback def get_bridgedomains(yaml): diff --git a/vppcfg/config/interface.py b/vppcfg/config/interface.py index 8f8e9ef..be7d3b8 100644 --- a/vppcfg/config/interface.py +++ b/vppcfg/config/interface.py @@ -13,14 +13,14 @@ # """ A vppcfg configuration module that validates interfaces """ import logging -from config import bondethernet -from config import bridgedomain -from config import loopback -from config import vxlan_tunnel -from config import lcp -from config import address -from config import mac -from config import tap +from . import bondethernet +from . import bridgedomain +from . import loopback +from . import vxlan_tunnel +from . import lcp +from . import address +from . import mac +from . import tap def get_qinx_parent_by_name(yaml, ifname): diff --git a/vppcfg/config/loopback.py b/vppcfg/config/loopback.py index 3bb1d33..5db307f 100644 --- a/vppcfg/config/loopback.py +++ b/vppcfg/config/loopback.py @@ -13,9 +13,9 @@ # """ A vppcfg configuration module that validates loopbacks """ import logging -from config import lcp -from config import address -from config import mac +from . import lcp +from . import address +from . import mac def get_loopbacks(yaml): diff --git a/vppcfg/config/tap.py b/vppcfg/config/tap.py index ee89dcf..3c482a6 100644 --- a/vppcfg/config/tap.py +++ b/vppcfg/config/tap.py @@ -13,7 +13,7 @@ # """ A vppcfg configuration module that validates taps """ import logging -from config import mac +from . import mac def get_taps(yaml): diff --git a/vppcfg/config/test_bondethernet.py b/vppcfg/config/test_bondethernet.py index cbc5e0b..8871ddf 100644 --- a/vppcfg/config/test_bondethernet.py +++ b/vppcfg/config/test_bondethernet.py @@ -15,12 +15,13 @@ """ Unit tests for bondethernet """ import unittest import yaml -import config.bondethernet as bondethernet +from . import bondethernet +from .unittestyaml import UnitTestYaml class TestBondEthernetMethods(unittest.TestCase): def setUp(self): - with open("unittest/test_bondethernet.yaml", "r") as f: + with UnitTestYaml("test_bondethernet.yaml") as f: self.cfg = yaml.load(f, Loader=yaml.FullLoader) def test_get_by_name(self): diff --git a/vppcfg/config/test_bridgedomain.py b/vppcfg/config/test_bridgedomain.py index e415490..5af3040 100644 --- a/vppcfg/config/test_bridgedomain.py +++ b/vppcfg/config/test_bridgedomain.py @@ -15,12 +15,13 @@ """ Unit tests for bridgedomains """ import unittest import yaml -import config.bridgedomain as bridgedomain +from . import bridgedomain +from .unittestyaml import UnitTestYaml class TestBridgeDomainMethods(unittest.TestCase): def setUp(self): - with open("unittest/test_bridgedomain.yaml", "r") as f: + with UnitTestYaml("test_bridgedomain.yaml") as f: self.cfg = yaml.load(f, Loader=yaml.FullLoader) def test_get_by_name(self): diff --git a/vppcfg/config/test_interface.py b/vppcfg/config/test_interface.py index 210043b..c677874 100644 --- a/vppcfg/config/test_interface.py +++ b/vppcfg/config/test_interface.py @@ -15,12 +15,13 @@ """ Unit tests for interfaces """ import unittest import yaml -import config.interface as interface +from . import interface +from .unittestyaml import UnitTestYaml class TestInterfaceMethods(unittest.TestCase): def setUp(self): - with open("unittest/test_interface.yaml", "r") as f: + with UnitTestYaml("test_interface.yaml") as f: self.cfg = yaml.load(f, Loader=yaml.FullLoader) def test_enumerators(self): diff --git a/vppcfg/config/test_lcp.py b/vppcfg/config/test_lcp.py index 3f3a1c4..1badeea 100644 --- a/vppcfg/config/test_lcp.py +++ b/vppcfg/config/test_lcp.py @@ -15,13 +15,14 @@ """ Unit tests for LCPs """ import unittest import yaml -import config.lcp as lcp -import config.interface as interface +from . import lcp +from . import interface +from .unittestyaml import UnitTestYaml class TestLCPMethods(unittest.TestCase): def setUp(self): - with open("unittest/test_lcp.yaml", "r") as f: + with UnitTestYaml("test_lcp.yaml") as f: self.cfg = yaml.load(f, Loader=yaml.FullLoader) def test_enumerators(self): diff --git a/vppcfg/config/test_loopback.py b/vppcfg/config/test_loopback.py index 591a08c..9c148c5 100644 --- a/vppcfg/config/test_loopback.py +++ b/vppcfg/config/test_loopback.py @@ -15,12 +15,13 @@ """ Unit tests for loopbacks """ import unittest import yaml -import config.loopback as loopback +from . import loopback +from .unittestyaml import UnitTestYaml class TestLoopbackMethods(unittest.TestCase): def setUp(self): - with open("unittest/test_loopback.yaml", "r") as f: + with UnitTestYaml("test_loopback.yaml") as f: self.cfg = yaml.load(f, Loader=yaml.FullLoader) def test_get_by_lcp_name(self): diff --git a/vppcfg/config/test_mac.py b/vppcfg/config/test_mac.py index 2a079be..545481c 100644 --- a/vppcfg/config/test_mac.py +++ b/vppcfg/config/test_mac.py @@ -14,7 +14,7 @@ # -*- coding: utf-8 -*- """ Unit tests for MAC addresses """ import unittest -import config.mac as mac +from . import mac class TestMACMethods(unittest.TestCase): diff --git a/vppcfg/config/test_tap.py b/vppcfg/config/test_tap.py index cc804e5..65775f8 100644 --- a/vppcfg/config/test_tap.py +++ b/vppcfg/config/test_tap.py @@ -15,12 +15,13 @@ """ Unit tests for taps """ import unittest import yaml -import config.tap as tap +from . import tap +from .unittestyaml import UnitTestYaml class TestTAPMethods(unittest.TestCase): def setUp(self): - with open("unittest/test_tap.yaml", "r") as f: + with UnitTestYaml("test_tap.yaml") as f: self.cfg = yaml.load(f, Loader=yaml.FullLoader) def test_get_by_name(self): diff --git a/vppcfg/config/test_vxlan_tunnel.py b/vppcfg/config/test_vxlan_tunnel.py index 9f5c4aa..cdcf081 100644 --- a/vppcfg/config/test_vxlan_tunnel.py +++ b/vppcfg/config/test_vxlan_tunnel.py @@ -15,12 +15,13 @@ """ Unit tests for vxlan_tunnels """ import unittest import yaml -import config.vxlan_tunnel as vxlan_tunnel +from . import vxlan_tunnel +from .unittestyaml import UnitTestYaml class TestVXLANMethods(unittest.TestCase): def setUp(self): - with open("unittest/test_vxlan_tunnel.yaml", "r") as f: + with UnitTestYaml("test_vxlan_tunnel.yaml") as f: self.cfg = yaml.load(f, Loader=yaml.FullLoader) def test_get_by_name(self): diff --git a/vppcfg/config/unittestyaml.py b/vppcfg/config/unittestyaml.py new file mode 100644 index 0000000..4615246 --- /dev/null +++ b/vppcfg/config/unittestyaml.py @@ -0,0 +1,41 @@ +""" module to help locate unittest resources """ +#!/usr/bin/env python +# +# Copyright (c) 2022 Ray Kinsella +# +# 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 -*- + +import os + + +class UnitTestYaml: + """Helper classes to find and load unit test yaml resources""" + + def __init__(self, fpath): + self.file = None + self.filename = fpath + self.resdir = os.path.abspath( + os.path.join(os.path.dirname(__file__), "../unittest") + ) + + def __enter__(self): + self.file = open( + os.path.join(self.resdir, self.filename), "r", encoding="utf-8" + ) + return self.file + + def __exit__(self, exc_type, exc_val, exc_tb): + self.file.close() + + def __str__(self): + return self.file diff --git a/example.yaml b/vppcfg/example.yaml similarity index 100% rename from example.yaml rename to vppcfg/example.yaml diff --git a/vppcfg/tests.py b/vppcfg/tests.py index 31e3ccb..7958148 100755 --- a/vppcfg/tests.py +++ b/vppcfg/tests.py @@ -14,13 +14,19 @@ # # -*- coding: utf-8 -*- """ This is a unit test suite for vppcfg """ - +# pylint: disable=duplicate-code +import os import sys import glob import re import unittest import yaml -from config import Validator + +try: + from vppcfg.config import Validator +except ModuleNotFoundError: + sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) + from vppcfg.config import Validator try: import argparse diff --git a/vppcfg/vpp/applier.py b/vppcfg/vpp/applier.py index c2d36f8..cac633d 100644 --- a/vppcfg/vpp/applier.py +++ b/vppcfg/vpp/applier.py @@ -17,7 +17,7 @@ The functions in this file interact with the VPP API to modify certain interface metadata. """ -from vpp.vppapi import VPPApi +from .vppapi import VPPApi class Applier(VPPApi): diff --git a/vppcfg/vpp/dumper.py b/vppcfg/vpp/dumper.py index d00ccc7..adfb5b6 100644 --- a/vppcfg/vpp/dumper.py +++ b/vppcfg/vpp/dumper.py @@ -19,8 +19,8 @@ interface metadata and write it to a YAML file. import sys import yaml -from vpp.vppapi import VPPApi -from config import bondethernet +from vppcfg.config import bondethernet +from .vppapi import VPPApi class Dumper(VPPApi): diff --git a/vppcfg/vpp/reconciler.py b/vppcfg/vpp/reconciler.py index 00e7d94..73ff406 100644 --- a/vppcfg/vpp/reconciler.py +++ b/vppcfg/vpp/reconciler.py @@ -19,14 +19,14 @@ metadata, and plan configuration changes towards a given YAML target configurati """ import sys import logging -from config import loopback -from config import interface -from config import bondethernet -from config import bridgedomain -from config import vxlan_tunnel -from config import lcp -from config import tap -from vpp.vppapi import VPPApi +from vppcfg.config import loopback +from vppcfg.config import interface +from vppcfg.config import bondethernet +from vppcfg.config import bridgedomain +from vppcfg.config import vxlan_tunnel +from vppcfg.config import lcp +from vppcfg.config import tap +from .vppapi import VPPApi class Reconciler: diff --git a/vppcfg/vppcfg.py b/vppcfg/vppcfg.py index f2cf35e..e2fe67a 100755 --- a/vppcfg/vppcfg.py +++ b/vppcfg/vppcfg.py @@ -15,12 +15,20 @@ # -*- coding: utf-8 -*- """vppcfg is a utility to configure a running VPP Dataplane using YAML config files. See http://github.com/pimvanpelt/vppcfg/README.md for details. """ +# pylint: disable=duplicate-code +import os import sys import logging import yaml -from config import Validator -from vpp.reconciler import Reconciler -from vpp.dumper import Dumper + +# Ensure the paths are correct when we execute from the source tree +try: + from vppcfg.config import Validator +except ModuleNotFoundError: + sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) + from vppcfg.config import Validator +from vppcfg.vpp.reconciler import Reconciler +from vppcfg.vpp.dumper import Dumper try: import argparse