[SEBA-450] (part 1)

Refactoring, python3 compat, and tox tests on:

- xosconfig
- xosgenx
- xosutil

Eliminate use of yaml.load() which is unsafe, switch to yaml.safe_load()

More diagnostics during database migration

Change-Id: I0fae5782fca401603a7c4e4ec2b9269ad24bda97
diff --git a/lib/xos-config/.gitignore b/lib/xos-config/.gitignore
index 10432e2..a04aaf4 100644
--- a/lib/xos-config/.gitignore
+++ b/lib/xos-config/.gitignore
@@ -1,7 +1,2 @@
-.noseids
-build
-XosConfig.egg-info
-dist
-.coverage
-coverage.xml
-cover
\ No newline at end of file
+# setup.py copies this, don't commit it
+xosconfig/VERSION
diff --git a/lib/xos-config/MANIFEST.in b/lib/xos-config/MANIFEST.in
index 2887117..0bb6996 100644
--- a/lib/xos-config/MANIFEST.in
+++ b/lib/xos-config/MANIFEST.in
@@ -1,2 +1,4 @@
+include requirements.txt
+include xosconfig/VERSION
+include xosconfig/synchronizer-config-schema.yaml
 include xosconfig/xos-config-schema.yaml
-include xosconfig/synchronizer-config-schema.yaml
\ No newline at end of file
diff --git a/lib/xos-config/requirements.txt b/lib/xos-config/requirements.txt
new file mode 100644
index 0000000..5528415
--- /dev/null
+++ b/lib/xos-config/requirements.txt
@@ -0,0 +1,2 @@
+PyYAML~=3.12
+pykwalify~=1.6.0
diff --git a/lib/xos-config/setup.py b/lib/xos-config/setup.py
index 1e683f1..57f2939 100644
--- a/lib/xos-config/setup.py
+++ b/lib/xos-config/setup.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python
-
 # Copyright 2017-present Open Networking Foundation
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,23 +12,40 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-try:
-    from xosutil.autoversion_setup import setup_with_auto_version as setup
-except ImportError:
-    # xosutil is not installed. Expect this to happen when we build an egg, in which case xosgenx.version will
-    # automatically have the right version.
-    from setuptools import setup
+from __future__ import absolute_import
 
-from xosconfig.version import __version__
+import os
+from shutil import copyfile
+
+from setuptools import setup
+
+
+def version():
+    # Copy VERSION file of parent to module directory if not found
+    if not os.path.exists("xosconfig/VERSION"):
+        copyfile("../../VERSION", "xosconfig/VERSION")
+    with open("xosconfig/VERSION") as f:
+        return f.read().strip()
+
+
+def parse_requirements(filename):
+    # parse a requirements.txt file, allowing for blank lines and comments
+    requirements = []
+    for line in open(filename):
+        if line and line.startswith("#"):
+            requirements.append(line)
+    return requirements
+
 
 setup(
-    name="XosConfig",
-    version=__version__,
+    name="xosconfig",
+    version=version(),
     description="XOS Config Library",
     author="Matteo Scandolo",
-    author_email="teo@onlab.us",
+    author_email="teo@opennetworking.org",
+    classifiers=["License :: OSI Approved :: Apache Software License"],
+    license="Apache v2",
     packages=["xosconfig"],
+    install_requires=parse_requirements("requirements.txt"),
     include_package_data=True,
-    # TODO add all deps to the install_requires section
-    install_requires=["pykwalify>=1.6.0"],
 )
diff --git a/lib/xos-config/tests/test_config.py b/lib/xos-config/tests/test_config.py
index 5eb86af..edec0b5 100644
--- a/lib/xos-config/tests/test_config.py
+++ b/lib/xos-config/tests/test_config.py
@@ -12,9 +12,11 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from __future__ import absolute_import
 
-import unittest
 import os
+import unittest
+
 from xosconfig import Config
 from xosconfig import Config as Config2
 
@@ -79,7 +81,7 @@
         with self.assertRaises(Exception) as e:
             Config.init(sample_conf)
             Config2.init(sample_conf)
-        self.assertEqual(e.exception.message, "[XOS-Config] Module already initialized")
+        self.assertEqual(str(e.exception), "[XOS-Config] Module already initialized")
 
     def test_config_not_initialized(self):
         """
@@ -88,7 +90,7 @@
         with self.assertRaises(Exception) as e:
             Config.get("database")
         self.assertEqual(
-            e.exception.message, "[XOS-Config] Module has not been initialized"
+            str(e.exception), "[XOS-Config] Module has not been initialized"
         )
 
     def test_missing_file_exception(self):
@@ -98,7 +100,7 @@
         with self.assertRaises(Exception) as e:
             Config.init("missing_conf")
         self.assertEqual(
-            e.exception.message, "[XOS-Config] Config file not found at: missing_conf"
+            str(e.exception), "[XOS-Config] Config file not found at: missing_conf"
         )
 
     def test_yaml_not_valid(self):
@@ -108,7 +110,7 @@
         with self.assertRaises(Exception) as e:
             Config.init(yaml_not_valid)
         self.assertTrue(
-            e.exception.message.startswith("[XOS-Config] The config format is wrong:")
+            str(e.exception).startswith("[XOS-Config] The config format is wrong:")
         )
 
     def test_invalid_format(self):
@@ -118,7 +120,7 @@
         with self.assertRaises(Exception) as e:
             Config.init(invalid_format)
         self.assertEqual(
-            e.exception.message,
+            str(e.exception),
             (
                 "[XOS-Config] The config format is wrong: Schema validation failed:\n"
                 " - Value '['I am', 'a yaml', 'but the', 'format is not', 'correct']' is not a dict. Value path: ''."
@@ -133,7 +135,7 @@
         with self.assertRaises(Exception) as e:
             Config.init("missing_conf")
         self.assertEqual(
-            e.exception.message, "[XOS-Config] Config file not found at: env.yaml"
+            str(e.exception), "[XOS-Config] Config file not found at: env.yaml"
         )
         del os.environ["XOS_CONFIG_FILE"]
 
@@ -145,10 +147,10 @@
         with self.assertRaises(Exception) as e:
             Config.init(basic_conf)
         self.assertRegexpMatches(
-            e.exception.message,
+            str(e.exception),
             r"\[XOS\-Config\] Config schema not found at: (.+)env-schema\.yaml",
         )
-        # self.assertEqual(e.exception.message, "[XOS-Config] Config schema not found at: env-schema.yaml")
+        # self.assertEqual(str(e.exception), "[XOS-Config] Config schema not found at: env-schema.yaml")
         del os.environ["XOS_CONFIG_SCHEMA"]
 
     def test_schema_override_usage(self):
@@ -159,7 +161,7 @@
         with self.assertRaises(Exception) as e:
             Config.init(basic_conf)
         self.assertEqual(
-            e.exception.message,
+            str(e.exception),
             (
                 "[XOS-Config] The config format is wrong: Schema validation failed:\n"
                 " - Key 'database' was not defined. Path: ''."
diff --git a/lib/xos-config/tox.ini b/lib/xos-config/tox.ini
new file mode 100644
index 0000000..55981ac
--- /dev/null
+++ b/lib/xos-config/tox.ini
@@ -0,0 +1,43 @@
+; Copyright 2019-present Open Networking Foundation
+;
+; 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.
+
+[tox]
+envlist = py27,py35,py36,py37
+skip_missing_interpreters = true
+
+[testenv]
+deps =
+  -r requirements.txt
+  nose2
+  flake8
+
+commands =
+  nose2 -c tox.ini --verbose --junit-xml
+  flake8
+
+[flake8]
+max-line-length = 119
+
+[unittest]
+plugins=nose2.plugins.junitxml
+
+[junit-xml]
+path=nose2-results.xml
+
+[coverage]
+always-on = True
+coverage = xosconfig
+coverage-report =
+  xml
+  term
diff --git a/lib/xos-config/xosconfig/__init__.py b/lib/xos-config/xosconfig/__init__.py
index 9a0b30c..0ae9199 100644
--- a/lib/xos-config/xosconfig/__init__.py
+++ b/lib/xos-config/xosconfig/__init__.py
@@ -12,6 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from __future__ import absolute_import
 from .config import Config
 
 __all__ = ["Config"]
diff --git a/lib/xos-config/xosconfig/config.py b/lib/xos-config/xosconfig/config.py
index aac6ffb..1fe6707 100644
--- a/lib/xos-config/xosconfig/config.py
+++ b/lib/xos-config/xosconfig/config.py
@@ -13,12 +13,16 @@
 # limitations under the License.
 
 
+from __future__ import absolute_import
+
 import os
 import sys
-import yaml
-import default
-from pykwalify.core import Core as PyKwalify
+
 import pykwalify
+import yaml
+from pykwalify.core import Core as PyKwalify
+
+from . import default
 
 pykwalify.init_logging(1)