From 9f2ef0e56ab3b5cb1be2e30856447ec6d6cee1d1 Mon Sep 17 00:00:00 2001 From: Ray Kinsella Date: Tue, 12 Jul 2022 15:32:23 +0100 Subject: [PATCH] build: fix python load paths Fixed python load paths so that vppcfg will work installed as python library and standalone from the source directory, fixing load pathes for resources such as yaml files along the way. Added a make target for pylint called 'make check-style', fixed a number of minor pylint issues along the way. Signed-off-by: Ray Kinsella --- Makefile | 4 +++ debian/rules | 1 + docs/config-guide.md | 2 +- setup.py | 5 ++-- vppcfg/__init__.py | 25 +++++++++++------- vppcfg/config/__init__.py | 23 ++++++++-------- vppcfg/config/bondethernet.py | 4 +-- vppcfg/config/bridgedomain.py | 4 +-- vppcfg/config/interface.py | 16 +++++------ vppcfg/config/loopback.py | 6 ++--- vppcfg/config/tap.py | 2 +- vppcfg/config/test_bondethernet.py | 5 ++-- vppcfg/config/test_bridgedomain.py | 5 ++-- vppcfg/config/test_interface.py | 5 ++-- vppcfg/config/test_lcp.py | 7 ++--- vppcfg/config/test_loopback.py | 5 ++-- vppcfg/config/test_mac.py | 2 +- vppcfg/config/test_tap.py | 5 ++-- vppcfg/config/test_vxlan_tunnel.py | 5 ++-- vppcfg/config/unittestyaml.py | 41 +++++++++++++++++++++++++++++ example.yaml => vppcfg/example.yaml | 0 vppcfg/tests.py | 10 +++++-- vppcfg/vpp/applier.py | 2 +- vppcfg/vpp/dumper.py | 4 +-- vppcfg/vpp/reconciler.py | 16 +++++------ vppcfg/vppcfg.py | 14 +++++++--- 26 files changed, 146 insertions(+), 72 deletions(-) create mode 100644 vppcfg/config/unittestyaml.py rename example.yaml => vppcfg/example.yaml (100%) 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