SEBA-497 delayering, Makefile, and tox for fabric-synchronizer

Change-Id: I916f4b632019dddceae5a6f024a0b1c64d8188d8
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..b8f06bb
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,2 @@
+.tox
+venv-service
diff --git a/.gitignore b/.gitignore
index 0f03cd1..0d57996 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,8 +1,7 @@
 .idea
-*.pyc
-
+.tox
 .coverage
-htmlcov
 coverage.xml
-nose2-junit.xml
-
+nose2-results.xml
+venv-service
+*.pyc
diff --git a/Dockerfile.synchronizer b/Dockerfile.synchronizer
index 9eef086..d3ec340 100644
--- a/Dockerfile.synchronizer
+++ b/Dockerfile.synchronizer
@@ -14,9 +14,14 @@
 
 # docker build -t xosproject/fabric-synchronizer:candidate -f Dockerfile.synchronizer .
 
-# xosproject/fabric-synchronizer
-FROM xosproject/xos-synchronizer-base:2.2.18
+FROM xosproject/alpine-grpc-base:0.9.0
 
+# Install pip packages
+COPY requirements.txt /tmp/requirements.txt
+RUN pip install -r /tmp/requirements.txt \
+ && pip freeze > /var/xos/pip_freeze_fabric_service_`date -u +%Y%m%dT%H%M%S`
+ 
+# Copy files
 COPY xos/synchronizer /opt/xos/synchronizers/fabric
 COPY VERSION /opt/xos/synchronizers/fabric/
 
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..ada7bb8
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,80 @@
+# 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.
+
+# Configure shell
+SHELL = bash -e -o pipefail
+
+# Variables
+VERSION                  ?= $(shell cat ./VERSION)
+SERVICE_NAME             ?= $(notdir $(abspath .))
+SYNCHRONIZER_NAME        ?= fabric-synchronizer
+
+## Docker related
+DOCKER_REGISTRY          ?=
+DOCKER_REPOSITORY        ?=
+DOCKER_BUILD_ARGS        ?=
+DOCKER_TAG               ?= ${VERSION}
+DOCKER_IMAGENAME         := ${DOCKER_REGISTRY}${DOCKER_REPOSITORY}${SYNCHRONIZER_NAME}:${DOCKER_TAG}
+
+## Docker labels. Only set ref and commit date if committed
+DOCKER_LABEL_VCS_URL     ?= $(shell git remote get-url $(shell git remote))
+DOCKER_LABEL_VCS_REF     ?= $(shell git diff-index --quiet HEAD -- && git rev-parse HEAD || echo "unknown")
+DOCKER_LABEL_COMMIT_DATE ?= $(shell git diff-index --quiet HEAD -- && git show -s --format=%cd --date=iso-strict HEAD || echo "unknown" )
+DOCKER_LABEL_BUILD_DATE  ?= $(shell date -u "+%Y-%m-%dT%H:%M:%SZ")
+
+## Migration related - paths are relative to the xos subdirectory within this repo
+XOS_DIR                  ?= "../../../xos"
+SERVICES_DIR             ?= "../.."
+
+all: test
+
+docker-build:
+	docker build $(DOCKER_BUILD_ARGS) \
+    -t ${DOCKER_IMAGENAME} \
+    --build-arg org_label_schema_version="${VERSION}" \
+    --build-arg org_label_schema_vcs_url="${DOCKER_LABEL_VCS_URL}" \
+    --build-arg org_label_schema_vcs_ref="${DOCKER_LABEL_VCS_REF}" \
+    --build-arg org_label_schema_build_date="${DOCKER_LABEL_BUILD_DATE}" \
+    --build-arg org_opencord_vcs_commit_date="${DOCKER_LABEL_COMMIT_DATE}" \
+    -f Dockerfile.synchronizer .
+
+docker-push:
+	docker push ${DOCKER_IMAGENAME}
+
+test: test-unit test-migration
+
+test-unit:
+	tox
+
+venv-service:
+	virtualenv $@;\
+    source ./$@/bin/activate ; set -u ;\
+    pip install -r requirements.txt xosmigrate~=3.0.1
+
+create-migration: venv-service
+	source ./venv-service/bin/activate; set -u;\
+    cd xos; xos-migrate --xos-dir ${XOS_DIR} --services-dir ${SERVICES_DIR} -s ${SERVICE_NAME}
+
+test-migration: venv-service
+	source ./venv-service/bin/activate; set -u;\
+    cd xos; xos-migrate --xos-dir ${XOS_DIR} --services-dir ${SERVICES_DIR} -s ${SERVICE_NAME} --check
+
+clean:
+	find . -name '*.pyc' | xargs rm -f
+	rm -rf \
+    .tox \
+    venv-service \
+    xos/.coverage \
+    xos/coverage.xml \
+    xos/nose2-results.xml
diff --git a/VERSION b/VERSION
index 39b2a23..ccbccc3 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2.1.13-dev
+2.2.0
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..a1a09fc
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,4 @@
+xossynchronizer~=3.0.1
+xosapi~=3.0.1
+xoskafka~=3.0.1
+ipaddress~=1.0.22
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..3982559
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,46 @@
+; 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
+;future envlist = py27,py35,py36,py37
+skip_missing_interpreters = true
+skipsdist = True
+
+[testenv]
+deps =
+  -r requirements.txt
+  requests_mock
+  nose2
+;  flake8
+
+changedir = xos
+commands =
+  nose2 -c ../tox.ini --verbose --junit-xml
+; future  flake8
+
+[flake8]
+max-line-length = 119
+
+[unittest]
+plugins = nose2.plugins.junitxml
+
+[junit-xml]
+path = nose2-results.xml
+
+[coverage]
+always-on = True
+coverage-report =
+  term
+  xml
diff --git a/xos/synchronizer/__init__.py b/xos/synchronizer/__init__.py
new file mode 100644
index 0000000..19d1424
--- /dev/null
+++ b/xos/synchronizer/__init__.py
@@ -0,0 +1,13 @@
+# 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.
diff --git a/xos/synchronizer/event_steps/__init__.py b/xos/synchronizer/event_steps/__init__.py
new file mode 100644
index 0000000..19d1424
--- /dev/null
+++ b/xos/synchronizer/event_steps/__init__.py
@@ -0,0 +1,13 @@
+# 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.
diff --git a/xos/synchronizer/event_steps/kubernetes_event.py b/xos/synchronizer/event_steps/kubernetes_event.py
index 507da0d..9f02171 100644
--- a/xos/synchronizer/event_steps/kubernetes_event.py
+++ b/xos/synchronizer/event_steps/kubernetes_event.py
@@ -13,10 +13,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from __future__ import absolute_import
 
 import json
-import os
-import sys
 from xossynchronizer.event_steps.eventstep import EventStep
 from xossynchronizer.modelaccessor import model_accessor
 from xossynchronizer.modelaccessor import FabricService, Switch, Service
@@ -25,6 +24,7 @@
 
 log = create_logger(Config().get('logging'))
 
+
 class KubernetesPodDetailsEventStep(EventStep):
     topics = ["xos.kubernetes.pod-details"]
     technology = "kafka"
@@ -63,12 +63,17 @@
 
             for switch in Switch.objects.all():
                 log.info("Dirtying Switch", switch=switch)
-                switch.backend_code=0
-                switch.backend_status="resynchronize due to kubernetes event"
+                switch.backend_code = 0
+                switch.backend_status = "resynchronize due to kubernetes event"
                 switch.save(update_fields=["updated", "backend_code", "backend_status"], always_update_timestamp=True)
 
                 for port in switch.ports.all():
                     log.info("Dirtying SwitchPort", port=port)
-                    port.backend_code=0
-                    port.backend_status="resynchronize due to kubernetes event"
-                    port.save(update_fields=["updated", "backend_code", "backend_status"], always_update_timestamp=True)
+                    port.backend_code = 0
+                    port.backend_status = "resynchronize due to kubernetes event"
+                    port.save(
+                        update_fields=[
+                            "updated",
+                            "backend_code",
+                            "backend_status"],
+                        always_update_timestamp=True)
diff --git a/xos/synchronizer/event_steps/onos_event.py b/xos/synchronizer/event_steps/onos_event.py
index 8458516..27199f2 100644
--- a/xos/synchronizer/event_steps/onos_event.py
+++ b/xos/synchronizer/event_steps/onos_event.py
@@ -13,6 +13,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from __future__ import absolute_import
 
 import json
 from xossynchronizer.event_steps.eventstep import EventStep
diff --git a/xos/synchronizer/event_steps/test_kubernetes_event.py b/xos/synchronizer/event_steps/test_kubernetes_event.py
index ae2915d..a69c608 100644
--- a/xos/synchronizer/event_steps/test_kubernetes_event.py
+++ b/xos/synchronizer/event_steps/test_kubernetes_event.py
@@ -12,15 +12,18 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+#from __future__ import absolute_import
+
+import imp
 import unittest
 import json
-import functools
-from mock import patch, call, Mock, PropertyMock
-import requests_mock
+from mock import patch, call, Mock
+import os
+import sys
 
-import os, sys
 
-test_path=os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
+test_path = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
+
 
 class TestKubernetesEvent(unittest.TestCase):
 
@@ -42,8 +45,8 @@
 
         import xossynchronizer.modelaccessor
         import mock_modelaccessor
-        reload(mock_modelaccessor) # in case nose2 loaded it in a previous test
-        reload(xossynchronizer.modelaccessor)      # in case nose2 loaded it in a previous test
+        imp.reload(mock_modelaccessor)  # in case nose2 loaded it in a previous test
+        imp.reload(xossynchronizer.modelaccessor)  # in case nose2 loaded it in a previous test
 
         from xossynchronizer.modelaccessor import model_accessor
         self.model_accessor = model_accessor
@@ -59,18 +62,18 @@
 
         self.onos = ONOSService(name="myonos",
                                 id=1111,
-                                rest_hostname = "onos-url",
-                                rest_port = "8181",
-                                rest_username = "karaf",
-                                rest_password = "karaf",
+                                rest_hostname="onos-url",
+                                rest_port="8181",
+                                rest_username="karaf",
+                                rest_password="karaf",
                                 backend_code=1,
                                 backend_status="succeeded")
 
         self.fabric_service = FabricService(name="fabric",
-                                                   id=1112,
-                                                   backend_code=1,
-                                                   backend_status="succeeded",
-                                                   provider_services=[self.onos])
+                                            id=1112,
+                                            backend_code=1,
+                                            backend_status="succeeded",
+                                            provider_services=[self.onos])
 
         self.switch = Switch(name="switch1",
                              backend_code=1,
@@ -115,7 +118,7 @@
             self.assertEqual(self.switch.backend_status, "resynchronize due to kubernetes event")
 
             switch_save.assert_called_with(self.switch, update_fields=["updated", "backend_code", "backend_status"],
-                                            always_update_timestamp=True)
+                                           always_update_timestamp=True)
 
             self.assertEqual(self.port1.backend_code, 0)
             self.assertEqual(self.port1.backend_status, "resynchronize due to kubernetes event")
@@ -123,10 +126,22 @@
             self.assertEqual(self.port2.backend_code, 0)
             self.assertEqual(self.port2.backend_status, "resynchronize due to kubernetes event")
 
-            switchport_save.assert_has_calls([call(self.port1, update_fields=["updated", "backend_code", "backend_status"],
-                                            always_update_timestamp=True),
-                                       call(self.port2, update_fields=["updated", "backend_code", "backend_status"],
-                                            always_update_timestamp=True)])
+            switchport_save.assert_has_calls(
+                [
+                    call(
+                        self.port1,
+                        update_fields=[
+                            "updated",
+                            "backend_code",
+                            "backend_status"],
+                        always_update_timestamp=True),
+                    call(
+                        self.port2,
+                        update_fields=[
+                            "updated",
+                            "backend_code",
+                            "backend_status"],
+                        always_update_timestamp=True)])
 
     def test_process_event_unknownstatus(self):
         with patch.object(FabricService.objects, "get_items") as fabric_service_objects, \
@@ -220,8 +235,6 @@
 
             switchport_save.assert_not_called()
 
+
 if __name__ == '__main__':
     unittest.main()
-
-
-
diff --git a/xos/synchronizer/event_steps/test_onos_event.py b/xos/synchronizer/event_steps/test_onos_event.py
index 2174577..61b0185 100644
--- a/xos/synchronizer/event_steps/test_onos_event.py
+++ b/xos/synchronizer/event_steps/test_onos_event.py
@@ -12,6 +12,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+#from __future__ import absolute_import
+
+import imp
 import unittest
 import json
 from mock import patch, Mock
@@ -42,8 +45,8 @@
 
         import xossynchronizer.modelaccessor
         import mock_modelaccessor
-        reload(mock_modelaccessor)  # in case nose2 loaded it in a previous test
-        reload(xossynchronizer.modelaccessor)  # in case nose2 loaded it in a previous test
+        imp.reload(mock_modelaccessor)  # in case nose2 loaded it in a previous test
+        imp.reload(xossynchronizer.modelaccessor)  # in case nose2 loaded it in a previous test
 
         from xossynchronizer.modelaccessor import model_accessor
         self.model_accessor = model_accessor
@@ -92,7 +95,7 @@
 
     def test_process_event_enable(self):
         with patch.object(Switch.objects, "get_items") as switch_objects, \
-             patch.object(SwitchPort.objects, "get_items") as switchport_objects:
+                patch.object(SwitchPort.objects, "get_items") as switchport_objects:
             switch_objects.return_value = [self.switch]
             switchport_objects.return_value = [self.port1, self.port2]
 
@@ -109,7 +112,7 @@
 
     def test_process_event_disable(self):
         with patch.object(Switch.objects, "get_items") as switch_objects, \
-             patch.object(SwitchPort.objects, "get_items") as switchport_objects:
+                patch.object(SwitchPort.objects, "get_items") as switchport_objects:
             switch_objects.return_value = [self.switch]
             switchport_objects.return_value = [self.port1, self.port2]
 
@@ -126,7 +129,7 @@
 
     def test_process_event_no_switch(self):
         with patch.object(Switch.objects, "get_items") as switch_objects, \
-             patch.object(SwitchPort.objects, "get_items") as switchport_objects:
+                patch.object(SwitchPort.objects, "get_items") as switchport_objects:
             switch_objects.return_value = [self.switch]
             switchport_objects.return_value = [self.port1, self.port2]
 
@@ -145,7 +148,7 @@
 
     def test_process_event_no_port(self):
         with patch.object(Switch.objects, "get_items") as switch_objects, \
-             patch.object(SwitchPort.objects, "get_items") as switchport_objects:
+                patch.object(SwitchPort.objects, "get_items") as switchport_objects:
             switch_objects.return_value = [self.switch]
             switchport_objects.return_value = [self.port1, self.port2]
 
diff --git a/xos/synchronizer/fabric-synchronizer.py b/xos/synchronizer/fabric-synchronizer.py
index 205bd4a..1c98ecd 100755
--- a/xos/synchronizer/fabric-synchronizer.py
+++ b/xos/synchronizer/fabric-synchronizer.py
@@ -1,3 +1,4 @@
+#!/usr/bin/env python
 
 # Copyright 2017-present Open Networking Foundation
 #
@@ -13,10 +14,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-
-#!/usr/bin/env python
-
-# This imports and runs ../../xos-observer.py
+from __future__ import absolute_import
 
 import os
 from xossynchronizer import Synchronizer
diff --git a/xos/synchronizer/model_policies/__init__.py b/xos/synchronizer/model_policies/__init__.py
new file mode 100644
index 0000000..19d1424
--- /dev/null
+++ b/xos/synchronizer/model_policies/__init__.py
@@ -0,0 +1,13 @@
+# 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.
diff --git a/xos/synchronizer/model_policies/model_policy_compute_nodes.py b/xos/synchronizer/model_policies/model_policy_compute_nodes.py
index a253a12..14044af 100644
--- a/xos/synchronizer/model_policies/model_policy_compute_nodes.py
+++ b/xos/synchronizer/model_policies/model_policy_compute_nodes.py
@@ -13,6 +13,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from __future__ import absolute_import
+
 import ipaddress
 import random
 from xossynchronizer.modelaccessor import NodeToSwitchPort, PortInterface, model_accessor
@@ -21,10 +23,9 @@
 from xosconfig import Config
 from multistructlog import create_logger
 
-from helpers import Helpers
-
 log = create_logger(Config().get('logging'))
 
+
 class ComputeNodePolicy(Policy):
     model_name = "NodeToSwitchPort"
 
@@ -42,7 +43,7 @@
 
     @staticmethod
     def generateVlan(used_vlans):
-        availabel_tags = range(16, 4093)
+        availabel_tags = list(range(16, 4093))
         valid_tags = list(set(availabel_tags) - set(used_vlans))
         if len(valid_tags) == 0:
             raise Exception("No VLANs left")
@@ -65,7 +66,12 @@
         return self.handle_update(node_to_port)
 
     def handle_update(self, node_to_port):
-        log.info("MODEL_POLICY: NodeToSwitchPort %s handle update" % node_to_port.id, node=node_to_port.node, port=node_to_port.port, switch=node_to_port.port.switch)
+        log.info(
+            "MODEL_POLICY: NodeToSwitchPort %s handle update" %
+            node_to_port.id,
+            node=node_to_port.node,
+            port=node_to_port.port,
+            switch=node_to_port.port.switch)
 
         compute_node = node_to_port.node
 
@@ -92,9 +98,8 @@
             )
 
             interface.save()
-        
-        # TODO if the model is updated I need to remove the old interface, how?
 
+        # TODO if the model is updated I need to remove the old interface, how?
 
     def handle_delete(self, node_to_port):
         pass
diff --git a/xos/synchronizer/model_policies/test_model_policy_compute_node.py b/xos/synchronizer/model_policies/test_model_policy_compute_node.py
index 9b17d8d..4c8b41e 100644
--- a/xos/synchronizer/model_policies/test_model_policy_compute_node.py
+++ b/xos/synchronizer/model_policies/test_model_policy_compute_node.py
@@ -13,14 +13,18 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+#from __future__ import absolute_import
 
+import imp
 import unittest
 import ipaddress
-from mock import patch, call, Mock, PropertyMock
+from mock import patch, Mock
 
-import os, sys
+import os
+import sys
 
-test_path=os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
+test_path = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
+
 
 class TestComputeNodePolicy(unittest.TestCase):
     def setUp(self):
@@ -38,8 +42,8 @@
 
         import xossynchronizer.modelaccessor
         import mock_modelaccessor
-        reload(mock_modelaccessor) # in case nose2 loaded it in a previous test
-        reload(xossynchronizer.modelaccessor)      # in case nose2 loaded it in a previous test
+        imp.reload(mock_modelaccessor)  # in case nose2 loaded it in a previous test
+        imp.reload(xossynchronizer.modelaccessor)      # in case nose2 loaded it in a previous test
 
         from model_policy_compute_nodes import ComputeNodePolicy, model_accessor
 
@@ -51,8 +55,8 @@
         for (k, v) in model_accessor.all_model_classes.items():
             globals()[k] = v
 
-        # Some of the functions we call have side-effects. For example, creating a VSGServiceInstance may lead to creation of
-        # tags. Ideally, this wouldn't happen, but it does. So make sure we reset the world.
+        # Some of the functions we call have side-effects. For example, creating a VSGServiceInstance may lead to
+        # creation of tags. Ideally, this wouldn't happen, but it does. So make sure we reset the world.
         model_accessor.reset_all_object_stores()
 
         self.policy = ComputeNodePolicy
@@ -63,7 +67,7 @@
 
     def test_getLastAddress(self):
 
-        dataPlaneIp = unicode("10.6.1.2/24", "utf-8")
+        dataPlaneIp = u"10.6.1.2/24"
         interface = ipaddress.ip_interface(dataPlaneIp)
         subnet = ipaddress.ip_network(interface.network)
         last_ip = self.policy.getLastAddress(subnet)
@@ -71,7 +75,7 @@
 
     def test_generateVlan(self):
 
-        used_vlans = range(16, 4093)
+        used_vlans = list(range(16, 4093))
         used_vlans.remove(1000)
 
         vlan = self.policy.generateVlan(used_vlans)
@@ -80,7 +84,7 @@
 
     def test_generateVlanFail(self):
 
-        used_vlans = range(16, 4093)
+        used_vlans = list(range(16, 4093))
 
         with self.assertRaises(Exception) as e:
             self.policy.generateVlan(used_vlans)
@@ -89,13 +93,13 @@
 
     def test_getVlanByCidr_same_subnet(self):
 
-        mock_pi_ip = unicode("10.6.1.2/24", "utf-8")
-        
+        mock_pi_ip = u"10.6.1.2/24"
+
         mock_pi = Mock()
         mock_pi.vlanUntagged = 1234
         mock_pi.ips = str(self.policy.getPortCidrByIp(mock_pi_ip))
 
-        test_ip = unicode("10.6.1.1/24", "utf-8")
+        test_ip = u"10.6.1.1/24"
         test_subnet = self.policy.getPortCidrByIp(test_ip)
 
         with patch.object(PortInterface.objects, "get_items") as get_pi:
@@ -106,12 +110,12 @@
 
     def test_getVlanByCidr_different_subnet(self):
 
-        mock_pi_ip = unicode("10.6.1.2/24", "utf-8")
+        mock_pi_ip = u"10.6.1.2/24"
         mock_pi = Mock()
         mock_pi.vlanUntagged = 1234
         mock_pi.ips = str(self.policy.getPortCidrByIp(mock_pi_ip))
 
-        test_ip = unicode("192.168.1.1/24", "utf-8")
+        test_ip = u"192.168.1.1/24"
         test_subnet = self.policy.getPortCidrByIp(test_ip)
 
         with patch.object(PortInterface.objects, "get_items") as get_pi:
@@ -130,7 +134,7 @@
 
     def test_handle_update_do_nothing(self):
 
-        mock_pi_ip = unicode("10.6.1.2/24", "utf-8")
+        mock_pi_ip = u"10.6.1.2/24"
         mock_pi = Mock()
         mock_pi.port_id = 1
         mock_pi.name = "test_interface"
@@ -142,8 +146,8 @@
         self.model.node.dataPlaneIntf = "test_interface"
 
         with patch.object(PortInterface.objects, "get_items") as get_pi, \
-            patch.object(self.policy, "getPortCidrByIp") as get_subnet, \
-            patch.object(PortInterface, 'save') as mock_save:
+                patch.object(self.policy, "getPortCidrByIp") as get_subnet, \
+                patch.object(PortInterface, 'save') as mock_save:
 
             get_pi.return_value = [mock_pi]
             get_subnet.return_value = mock_pi.ips
@@ -158,11 +162,11 @@
 
         self.model.port.id = 1
         self.model.node.dataPlaneIntf = "test_interface"
-        self.model.node.dataPlaneIp = unicode("10.6.1.2/24", "utf-8")
+        self.model.node.dataPlaneIp = u"10.6.1.2/24"
 
         with patch.object(PortInterface.objects, "get_items") as get_pi, \
-            patch.object(self.policy, "getVlanByCidr") as get_vlan, \
-            patch.object(PortInterface, "save", autospec=True) as mock_save:
+                patch.object(self.policy, "getVlanByCidr") as get_vlan, \
+                patch.object(PortInterface, "save", autospec=True) as mock_save:
 
             get_pi.return_value = []
             get_vlan.return_value = "1234"
@@ -181,4 +185,3 @@
 if __name__ == '__main__':
     sys.path.append("../steps")  # so we can import helpers from steps directory
     unittest.main()
-
diff --git a/xos/synchronizer/steps/__init__.py b/xos/synchronizer/steps/__init__.py
new file mode 100644
index 0000000..19d1424
--- /dev/null
+++ b/xos/synchronizer/steps/__init__.py
@@ -0,0 +1,13 @@
+# 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.
diff --git a/xos/synchronizer/steps/helpers.py b/xos/synchronizer/steps/helpers.py
index 2203db1..3ac04fe 100644
--- a/xos/synchronizer/steps/helpers.py
+++ b/xos/synchronizer/steps/helpers.py
@@ -12,6 +12,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from __future__ import absolute_import
+
 class Helpers():
     @staticmethod
     def format_url(url):
@@ -19,6 +21,7 @@
             return url
         else:
             return 'http://%s' % url
+
     @staticmethod
     def get_onos_fabric_service(model_accessor):
         # FIXME do not select by name but follow ServiceDependency
diff --git a/xos/synchronizer/steps/sync_fabric_port.py b/xos/synchronizer/steps/sync_fabric_port.py
index 1ab0f52..4f0c914 100644
--- a/xos/synchronizer/steps/sync_fabric_port.py
+++ b/xos/synchronizer/steps/sync_fabric_port.py
@@ -13,6 +13,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+#from __future__ import absolute_import
+
 import requests
 import urllib
 from requests.auth import HTTPBasicAuth
@@ -117,15 +119,21 @@
     def delete_record(self, model):
         if model.leaf_model_name == "PortInterface":
             log.info("Received update for PortInterface", port=model.port.portId, interface=model.name)
-            log.info("Removing port interface %s from port %s/%s in onos-fabric" % (model.name, model.port.switch.ofId, model.port.portId))
+            log.info("Removing port interface %s from port %s/%s in onos-fabric" %
+                     (model.name, model.port.switch.ofId, model.port.portId))
 
             # resync the existing interfaces
             return self.sync_record(model.port)
 
         if model.leaf_model_name == "FabricIpAddress":
             # TODO add unit tests
-            log.info("Received update for FabricIpAddress", port=model.interface.port.portId, interface=model.interface.name, ip=model.ip)
-            log.info("Removing IP %s from interface %s, on port %s/%s in onos-fabric" % (model.ip, model.interface.name, model.interface.port.switch.ofId, model.interface.port.portId))
+            log.info(
+                "Received update for FabricIpAddress",
+                port=model.interface.port.portId,
+                interface=model.interface.name,
+                ip=model.ip)
+            log.info("Removing IP %s from interface %s, on port %s/%s in onos-fabric" %
+                     (model.ip, model.interface.name, model.interface.port.switch.ofId, model.interface.port.portId))
 
             # resync the existing interfaces
             return self.sync_record(model.interface.port)
diff --git a/xos/synchronizer/steps/sync_fabric_switch.py b/xos/synchronizer/steps/sync_fabric_switch.py
index 31c5575..cac7ac5 100644
--- a/xos/synchronizer/steps/sync_fabric_switch.py
+++ b/xos/synchronizer/steps/sync_fabric_switch.py
@@ -13,6 +13,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+#from __future__ import absolute_import
+
 import requests
 from requests.auth import HTTPBasicAuth
 from xossynchronizer.steps.syncstep import SyncStep
@@ -25,6 +27,7 @@
 
 log = create_logger(Config().get('logging'))
 
+
 class SyncFabricSwitch(SyncStep):
     provides = [Switch]
     observes = Switch
@@ -33,22 +36,22 @@
         log.info("Adding switch %s to onos-fabric" % model.name)
         # Send device info to onos-fabric netcfg
         data = {
-          "devices": {
-            model.ofId: {
-              "basic": {
-                "name": model.name,
-                "driver": model.driver
-              },
-              "segmentrouting" : {
-                "name" : model.name,
-                "ipv4NodeSid" : model.ipv4NodeSid,
-                "ipv4Loopback" : model.ipv4Loopback,
-                "routerMac" : model.routerMac,
-                "isEdgeRouter" : model.isEdgeRouter,
-                "adjacencySids" : []
-              }
+            "devices": {
+                model.ofId: {
+                    "basic": {
+                        "name": model.name,
+                        "driver": model.driver
+                    },
+                    "segmentrouting": {
+                        "name": model.name,
+                        "ipv4NodeSid": model.ipv4NodeSid,
+                        "ipv4Loopback": model.ipv4Loopback,
+                        "routerMac": model.routerMac,
+                        "isEdgeRouter": model.isEdgeRouter,
+                        "adjacencySids": []
+                    }
+                }
             }
-          }
         }
 
         onos = Helpers.get_onos_fabric_service(model_accessor=self.model_accessor)
@@ -61,15 +64,15 @@
             raise Exception("Failed to add device %s into ONOS" % model.name)
         else:
             try:
-                print r.json()
+                log.info("result", json=r.json())
             except Exception:
-                print r.text
+                log.info("result", text=r.text)
 
     def delete_record(self, model):
         log.info("Removing switch %s from onos-fabric" % model.name)
         onos = Helpers.get_onos_fabric_service(model_accessor=self.model_accessor)
         url = 'http://%s:%s/onos/v1/network/configuration/devices/%s' % (
-        onos.rest_hostname, onos.rest_port, model.ofId)
+            onos.rest_hostname, onos.rest_port, model.ofId)
 
         r = requests.delete(url, auth=HTTPBasicAuth(onos.rest_username, onos.rest_password))
 
diff --git a/xos/synchronizer/steps/test_sync_fabric_port.py b/xos/synchronizer/steps/test_sync_fabric_port.py
index 234a048..af370cb 100644
--- a/xos/synchronizer/steps/test_sync_fabric_port.py
+++ b/xos/synchronizer/steps/test_sync_fabric_port.py
@@ -12,6 +12,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+#from __future__ import absolute_import
+
+import imp
 import unittest
 import urllib
 import functools
@@ -30,6 +33,7 @@
         return False
     return True
 
+
 class TestSyncFabricPort(unittest.TestCase):
 
     def setUp(self):
@@ -49,8 +53,8 @@
 
         import xossynchronizer.modelaccessor
         import mock_modelaccessor
-        reload(mock_modelaccessor)  # in case nose2 loaded it in a previous test
-        reload(xossynchronizer.modelaccessor)  # in case nose2 loaded it in a previous test
+        imp.reload(mock_modelaccessor)  # in case nose2 loaded it in a previous test
+        imp.reload(xossynchronizer.modelaccessor)  # in case nose2 loaded it in a previous test
 
         from xossynchronizer.modelaccessor import model_accessor
         self.model_accessor = model_accessor
diff --git a/xos/synchronizer/steps/test_sync_fabric_switch.py b/xos/synchronizer/steps/test_sync_fabric_switch.py
index 2184a6d..b749b94 100644
--- a/xos/synchronizer/steps/test_sync_fabric_switch.py
+++ b/xos/synchronizer/steps/test_sync_fabric_switch.py
@@ -12,24 +12,27 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+#from __future__ import absolute_import
+
+import imp
 import unittest
-
 import functools
-from mock import patch, call, Mock, PropertyMock
+from mock import patch, Mock
 import requests_mock
-import multistructlog
-from multistructlog import create_logger
 
-import os, sys
+import os
+import sys
 
-test_path=os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
+test_path = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
+
 
 def match_json(desired, req):
-    if desired!=req.json():
+    if desired != req.json():
         raise Exception("Got request %s, but body is not matching" % req.url)
         return False
     return True
 
+
 class TestSyncFabricSwitch(unittest.TestCase):
 
     def setUp(self):
@@ -49,8 +52,8 @@
 
         import xossynchronizer.modelaccessor
         import mock_modelaccessor
-        reload(mock_modelaccessor) # in case nose2 loaded it in a previous test
-        reload(xossynchronizer.modelaccessor)      # in case nose2 loaded it in a previous test
+        imp.reload(mock_modelaccessor)  # in case nose2 loaded it in a previous test
+        imp.reload(xossynchronizer.modelaccessor)      # in case nose2 loaded it in a previous test
 
         from xossynchronizer.modelaccessor import model_accessor
         self.model_accessor = model_accessor
@@ -61,11 +64,9 @@
         for (k, v) in model_accessor.all_model_classes.items():
             globals()[k] = v
 
-
         self.sync_step = SyncFabricSwitch
         self.sync_step.log = Mock()
 
-
         # mock onos-fabric
         onos_fabric = Mock()
         onos_fabric.name = "onos-fabric"
@@ -105,17 +106,17 @@
             "devices": {
                 self.o.ofId: {
                     "basic": {
-                    "name": self.o.name,
-                    "driver": self.o.driver
-                },
-                "segmentrouting" : {
-                    "name" : self.o.name,
-                    "ipv4NodeSid" : self.o.ipv4NodeSid,
-                    "ipv4Loopback" : self.o.ipv4Loopback,
-                    "routerMac" : self.o.routerMac,
-                    "isEdgeRouter" : self.o.isEdgeRouter,
-                    "adjacencySids" : []
-              }
+                        "name": self.o.name,
+                        "driver": self.o.driver
+                    },
+                    "segmentrouting": {
+                        "name": self.o.name,
+                        "ipv4NodeSid": self.o.ipv4NodeSid,
+                        "ipv4Loopback": self.o.ipv4Loopback,
+                        "routerMac": self.o.routerMac,
+                        "isEdgeRouter": self.o.isEdgeRouter,
+                        "adjacencySids": []
+                    }
                 }
             }
         }
@@ -134,7 +135,7 @@
     @requests_mock.Mocker()
     def test_delete_switch(self, m):
         m.delete("http://onos-fabric:8181/onos/v1/network/configuration/devices/of:1234",
-            status_code=204)
+                 status_code=204)
 
         self.o.ofId = "of:1234"
 
@@ -145,5 +146,6 @@
 
             self.assertTrue(m.called)
 
+
 if __name__ == '__main__':
-    unittest.main()
\ No newline at end of file
+    unittest.main()
diff --git a/xos/unittest.cfg b/xos/unittest.cfg
deleted file mode 100644
index f28ec79..0000000
--- a/xos/unittest.cfg
+++ /dev/null
@@ -1,12 +0,0 @@
-[unittest]
-plugins=nose2.plugins.junitxml
-code-directories=synchronizer
-                 steps
-                 model_policies
-                 event_steps
-[coverage]
-always-on = True
-coverage = synchronizer
-coverage-report = term
-coverage-report = html
-coverage-report = xml