Extracting Ns._create_task() and creating unit test 72/11372/2
authorsousaedu <eduardo.sousa@canonical.com>
Fri, 19 Nov 2021 01:01:32 +0000 (01:01 +0000)
committersousaedu <eduardo.sousa@canonical.com>
Fri, 19 Nov 2021 01:10:54 +0000 (01:10 +0000)
Extracted the _create_task() to ease the testability of that code.
Introduced a unit test to cover that function.
Re-enabled unit tests and black now is mandatory.

Change-Id: I6b304b14b60b182dee6b274cf95228a81ddc2771
Signed-off-by: sousaedu <eduardo.sousa@canonical.com>
NG-RO/osm_ng_ro/ns.py
NG-RO/osm_ng_ro/tests/test_ns.py [new file with mode: 0644]
RO-VIM-azure/osm_rovim_azure/vimconn_azure.py
RO-VIM-gcp/osm_rovim_gcp/vimconn_gcp.py
integration-tests/test_vimconn_gcp.py
releasenotes/notes/extracting_create_task-e76ed58bfe5a6751.yaml [new file with mode: 0644]
tox.ini

index ee0463a..35502c2 100644 (file)
@@ -18,6 +18,7 @@
 
 # import yaml
 import logging
+from typing import Any, Dict
 from traceback import format_exc as traceback_format_exc
 from osm_ng_ro.ns_thread import NsWorker, NsWorkerException, deep_get
 from osm_ng_ro.validation import validate_input, deploy_schema
@@ -385,6 +386,49 @@ class Ns(object):
 
         return db_content
 
+    @staticmethod
+    def _create_task(
+        deployment_info: Dict[str, Any],
+        target_id: str,
+        item: str,
+        action: str,
+        target_record: str,
+        target_record_id: str,
+        extra_dict: Dict[str, Any] = None,
+    ) -> Dict[str, Any]:
+        """Function to create task dict from deployment information.
+
+        Args:
+            deployment_info (Dict[str, Any]): [description]
+            target_id (str): [description]
+            item (str): [description]
+            action (str): [description]
+            target_record (str): [description]
+            target_record_id (str): [description]
+            extra_dict (Dict[str, Any], optional): [description]. Defaults to None.
+
+        Returns:
+            Dict[str, Any]: [description]
+        """
+        task = {
+            "target_id": target_id,  # it will be removed before pushing at database
+            "action_id": deployment_info.get("action_id"),
+            "nsr_id": deployment_info.get("nsr_id"),
+            "task_id": f"{deployment_info.get('action_id')}:{deployment_info.get('task_index')}",
+            "status": "SCHEDULED",
+            "action": action,
+            "item": item,
+            "target_record": target_record,
+            "target_record_id": target_record_id,
+        }
+
+        if extra_dict:
+            task.update(extra_dict)  # params, find_params, depends_on
+
+        deployment_info["task_index"] = deployment_info.get("task_index", 0) + 1
+
+        return task
+
     def deploy(self, session, indata, version, nsr_id, *args, **kwargs):
         self.logger.debug("ns.deploy nsr_id={} indata={}".format(nsr_id, indata))
         validate_input(indata, deploy_schema)
@@ -441,37 +485,6 @@ class Ns(object):
 
                     index += 1
 
-            def _create_task(
-                target_id,
-                item,
-                action,
-                target_record,
-                target_record_id,
-                extra_dict=None,
-            ):
-                nonlocal task_index
-                nonlocal action_id
-                nonlocal nsr_id
-
-                task = {
-                    "target_id": target_id,  # it will be removed before pushing at database
-                    "action_id": action_id,
-                    "nsr_id": nsr_id,
-                    "task_id": "{}:{}".format(action_id, task_index),
-                    "status": "SCHEDULED",
-                    "action": action,
-                    "item": item,
-                    "target_record": target_record,
-                    "target_record_id": target_record_id,
-                }
-
-                if extra_dict:
-                    task.update(extra_dict)  # params, find_params, depends_on
-
-                task_index += 1
-
-                return task
-
             def _create_ro_task(target_id, task):
                 nonlocal action_id
                 nonlocal task_index
@@ -917,6 +930,8 @@ class Ns(object):
             ):
                 nonlocal db_new_tasks
                 nonlocal tasks_by_target_record_id
+                nonlocal action_id
+                nonlocal nsr_id
                 nonlocal task_index
 
                 # ensure all the target_list elements has an "id". If not assign the index as id
@@ -956,15 +971,23 @@ class Ns(object):
                                 item_ = "sdn_net"
                                 target_record_id += ".sdn"
 
-                            task = _create_task(
-                                target_vim,
-                                item_,
-                                "DELETE",
-                                target_record="{}.{}.vim_info.{}".format(
-                                    db_record, item_index, target_vim
-                                ),
+                            deployment_info = {
+                                "action_id": action_id,
+                                "nsr_id": nsr_id,
+                                "task_index": task_index,
+                            }
+
+                            task = Ns._create_task(
+                                deployment_info=deployment_info,
+                                target_id=target_vim,
+                                item=item_,
+                                action="DELETE",
+                                target_record=f"{db_record}.{item_index}.vim_info.{target_vim}",
                                 target_record_id=target_record_id,
                             )
+
+                            task_index = deployment_info.get("task_index")
+
                             tasks_by_target_record_id[target_record_id] = task
                             db_new_tasks.append(task)
                             # TODO delete
@@ -1010,16 +1033,25 @@ class Ns(object):
                             target_item, target_viminfo, target_record_id
                         )
                         self._assign_vim(target_vim)
-                        task = _create_task(
-                            target_vim,
-                            item_,
-                            "CREATE",
-                            target_record="{}.{}.vim_info.{}".format(
-                                db_record, item_index, target_vim
-                            ),
+
+                        deployment_info = {
+                            "action_id": action_id,
+                            "nsr_id": nsr_id,
+                            "task_index": task_index,
+                        }
+
+                        task = Ns._create_task(
+                            deployment_info=deployment_info,
+                            target_id=target_vim,
+                            item=item_,
+                            action="CREATE",
+                            target_record=f"{db_record}.{item_index}.vim_info.{target_vim}",
                             target_record_id=target_record_id,
                             extra_dict=extra_dict,
                         )
+
+                        task_index = deployment_info.get("task_index")
+
                         tasks_by_target_record_id[target_record_id] = task
                         db_new_tasks.append(task)
 
@@ -1030,6 +1062,8 @@ class Ns(object):
 
             def _process_action(indata):
                 nonlocal db_new_tasks
+                nonlocal action_id
+                nonlocal nsr_id
                 nonlocal task_index
                 nonlocal db_vnfrs
                 nonlocal db_ro_nsr
@@ -1085,14 +1119,25 @@ class Ns(object):
                                     ],
                                 },
                             }
-                            task = _create_task(
-                                target_vim,
-                                "vdu",
-                                "EXEC",
+
+                            deployment_info = {
+                                "action_id": action_id,
+                                "nsr_id": nsr_id,
+                                "task_index": task_index,
+                            }
+
+                            task = Ns._create_task(
+                                deployment_info=deployment_info,
+                                target_id=target_vim,
+                                item="vdu",
+                                action="EXEC",
                                 target_record=target_record,
                                 target_record_id=None,
                                 extra_dict=extra_dict,
                             )
+
+                            task_index = deployment_info.get("task_index")
+
                             db_new_tasks.append(task)
 
             with self.write_lock:
diff --git a/NG-RO/osm_ng_ro/tests/test_ns.py b/NG-RO/osm_ng_ro/tests/test_ns.py
new file mode 100644 (file)
index 0000000..fc56329
--- /dev/null
@@ -0,0 +1,68 @@
+#######################################################################################
+# Copyright ETSI Contributors and Others.
+#
+# 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.
+#######################################################################################
+
+import unittest
+
+from osm_ng_ro.ns import Ns
+
+
+__author__ = "Eduardo Sousa"
+__date__ = "$19-NOV-2021 00:00:00$"
+
+
+class TestNs(unittest.TestCase):
+    def setUp(self):
+        pass
+
+    def test__create_task(self):
+        expected_result = {
+            "target_id": "vim_openstack_1",
+            "action_id": "123456",
+            "nsr_id": "654321",
+            "task_id": "123456:1",
+            "status": "SCHEDULED",
+            "action": "CREATE",
+            "item": "test_item",
+            "target_record": "test_target_record",
+            "target_record_id": "test_target_record_id",
+            # values coming from extra_dict
+            "params": "test_params",
+            "find_params": "test_find_params",
+            "depends_on": "test_depends_on",
+        }
+        deployment_info = {
+            "action_id": "123456",
+            "nsr_id": "654321",
+            "task_index": 1,
+        }
+
+        task = Ns._create_task(
+            deployment_info=deployment_info,
+            target_id="vim_openstack_1",
+            item="test_item",
+            action="CREATE",
+            target_record="test_target_record",
+            target_record_id="test_target_record_id",
+            extra_dict={
+                "params": "test_params",
+                "find_params": "test_find_params",
+                "depends_on": "test_depends_on",
+            },
+        )
+
+        self.assertEqual(deployment_info.get("task_index"), 2)
+        self.assertDictEqual(task, expected_result)
index a1d3aa8..68f7711 100755 (executable)
@@ -548,7 +548,9 @@ class vimconnector(vimconn.VimConnector):
         self._reload_connection()
 
         subnet_id = net["net_id"]
-        location = self.region or self._get_location_from_resource_group(self.resource_group)
+        location = self.region or self._get_location_from_resource_group(
+            self.resource_group
+        )
         try:
             net_ifz = {"location": location}
             net_ip_config = {
index ecfdb06..4a3258c 100644 (file)
@@ -157,9 +157,7 @@ class vimconnector(vimconn.VimConnector):
         self.logger.debug("Config: %s", config)
         scopes = ["https://www.googleapis.com/auth/cloud-platform"]
         self.credentials = None
-        if (
-            "credentials" in config
-        ):
+        if "credentials" in config:
             self.logger.debug("Setting credentials")
             # Settings Google Cloud credentials dict
             credentials_body = config["credentials"]
@@ -491,9 +489,7 @@ class vimconnector(vimconn.VimConnector):
         if not network_list:
             return []
         else:
-            self.logger.debug(
-                "get_network Return: network_list[0] %s", network_list[0]
-            )
+            self.logger.debug("get_network Return: network_list[0] %s", network_list[0])
             return network_list[0]
 
     def delete_network(self, net_id, created_items=None):
@@ -679,9 +675,7 @@ class vimconnector(vimconn.VimConnector):
         return vm_name_aux.lower()
 
     def get_flavor_id_from_data(self, flavor_dict):
-        self.logger.debug(
-            "get_flavor_id_from_data begin: flavor_dict %s", flavor_dict
-        )
+        self.logger.debug("get_flavor_id_from_data begin: flavor_dict %s", flavor_dict)
         filter_dict = flavor_dict or {}
 
         try:
@@ -695,7 +689,8 @@ class vimconnector(vimconn.VimConnector):
 
             cpus = filter_dict.get("vcpus") or 0
             memMB = filter_dict.get("ram") or 0
-            numberInterfaces = len(filter_dict.get("interfaces", [])) or 4 # Workaround (it should be 0)
+            # Workaround (it should be 0)
+            numberInterfaces = len(filter_dict.get("interfaces", [])) or 4
 
             # Filter
             filtered_machines = []
@@ -862,7 +857,9 @@ class vimconnector(vimconn.VimConnector):
                     self.logger.debug("New random name: %s", random_name)
                     break
                 else:
-                    self.logger.error("Exception generating random name (%s) for the instance", name)
+                    self.logger.error(
+                        "Exception generating random name (%s) for the instance", name
+                    )
                     self._format_vimconn_exception(e)
 
         return random_name
@@ -928,7 +925,9 @@ class vimconnector(vimconn.VimConnector):
                     net_iface["subnetwork"] = net.get("net_id")
                 # In order to get an external IP address, the key "accessConfigs" must be used
                 # in the interace. It has to be of type "ONE_TO_ONE_NAT" and name "External NAT"
-                if net.get("floating_ip", False) or (net["use"] == "mgmt" and self.config.get("use_floating_ip")):
+                if net.get("floating_ip", False) or (
+                    net["use"] == "mgmt" and self.config.get("use_floating_ip")
+                ):
                     net_iface["accessConfigs"] = [
                         {"type": "ONE_TO_ONE_NAT", "name": "External NAT"}
                     ]
@@ -1007,10 +1006,11 @@ class vimconnector(vimconn.VimConnector):
                     self.logger.error("new_vminstance rollback fail {}".format(e2))
 
             else:
-                self.logger.debug("Exception creating new vminstance: %s", e, exc_info=True)
+                self.logger.debug(
+                    "Exception creating new vminstance: %s", e, exc_info=True
+                )
                 self._format_vimconn_exception(e)
 
-
     def _build_metadata(self, vm_name, cloud_config):
 
         # initial metadata
@@ -1022,9 +1022,7 @@ class vimconnector(vimconn.VimConnector):
         if cloud_config:
             self.logger.debug("cloud config: %s", cloud_config)
             _, userdata = self._create_user_data(cloud_config)
-            metadata["items"].append(
-                {"key": "user-data", "value": userdata}
-            )
+            metadata["items"].append({"key": "user-data", "value": userdata})
 
         # either password of ssh-keys are required
         # we will always use ssh-keys, in case it is not available we will generate it
@@ -1057,7 +1055,6 @@ class vimconnector(vimconn.VimConnector):
 
         return metadata
 
-
     def _generate_keys(self):
         """Method used to generate a pair of private/public keys.
         This method is used because to create a vm in Azure we always need a key or a password
@@ -1083,7 +1080,6 @@ class vimconnector(vimconn.VimConnector):
 
         return private_key, public_key
 
-
     def _get_unused_vm_name(self, vm_name):
         """
         Checks the vm name and in case it is used adds a suffix to the name to allow creation
@@ -1514,4 +1510,3 @@ class vimconnector(vimconn.VimConnector):
                 )
             )
             self._format_vimconn_exception(e)
-
index aa4e240..f973538 100644 (file)
@@ -35,10 +35,10 @@ __author__ = "Sergio G.R."
 __date__ = "$05-nov-2021 12:00:00$"
 
 
-class TestGCPOperations():
+class TestGCPOperations:
 
     gcp_conn = None
-    time_id = datetime.today().strftime('%Y%m%d%H%M%S')
+    time_id = datetime.today().strftime("%Y%m%d%H%M%S")
     vim_id = "gcp-test-" + time_id
     vim_name = vim_id
     vm_name = "gcp-test-vm-" + time_id
@@ -57,7 +57,7 @@ class TestGCPOperations():
         credentials_file,
         image_id,
         image_connector_id,
-        flavor_id
+        flavor_id,
     ):
         self.config["project_name"] = project_name
         self.config["region_name"] = region_name
@@ -66,7 +66,8 @@ class TestGCPOperations():
                 self.config["credentials"] = json.load(file)
         except ValueError:
             raise Exception(
-                "Not possible to read credentials JSON file %s", self.config["credentials"]
+                "Not possible to read credentials JSON file %s",
+                self.config["credentials"],
             )
         self.image_id = image_id
         self.image_connector_id = image_connector_id
@@ -87,8 +88,12 @@ class TestGCPOperations():
         )
 
     def test_networks(self):
-        net_id_1 = self.gcp_conn.new_network(self.net_name, None, {"subnet_address": "10.0.0.0/25"})
-        net_id_2 = self.gcp_conn.new_network(self.net_name, None, {"subnet_address": "10.9.0.0/25"})
+        net_id_1 = self.gcp_conn.new_network(
+            self.net_name, None, {"subnet_address": "10.0.0.0/25"}
+        )
+        net_id_2 = self.gcp_conn.new_network(
+            self.net_name, None, {"subnet_address": "10.9.0.0/25"}
+        )
         _ = self.gcp_conn.delete_network(net_id_1[0])
         _ = self.gcp_conn.delete_network(net_id_2[0])
 
@@ -105,8 +110,12 @@ class TestGCPOperations():
         _ = self.gcp_conn.delete_vminstance(vm_id_1[0])
 
     def test_vminstances_2_nets(self):
-        net_id_1 = self.gcp_conn.new_network(self.net_name, None, {"subnet_address": "10.0.0.0/25"})
-        net_id_2 = self.gcp_conn.new_network(self.net_name, None, {"subnet_address": "10.9.0.0/25"})
+        net_id_1 = self.gcp_conn.new_network(
+            self.net_name, None, {"subnet_address": "10.0.0.0/25"}
+        )
+        net_id_2 = self.gcp_conn.new_network(
+            self.net_name, None, {"subnet_address": "10.9.0.0/25"}
+        )
 
         vm_id_1 = self.gcp_conn.new_vminstance(
             name=self.vm_name,
@@ -114,7 +123,10 @@ class TestGCPOperations():
             start=True,
             image_id=self.image_id,
             flavor_id=self.flavor_id,
-            net_list=[{"net_id": net_id_1[0], "use": "mgmt"}, {"net_id": net_id_2[0], "use": "internal"}],
+            net_list=[
+                {"net_id": net_id_1[0], "use": "mgmt"},
+                {"net_id": net_id_2[0], "use": "internal"},
+            ],
             cloud_config=self.cloud_config,
         )
         _ = self.gcp_conn.delete_vminstance(vm_id_1[0])
@@ -122,7 +134,6 @@ class TestGCPOperations():
         _ = self.gcp_conn.delete_network(net_id_1[0])
         _ = self.gcp_conn.delete_network(net_id_2[0])
 
-
     def test_vminstances_image_connector_id(self):
         image_id = self.gcp_conn.get_image_list({"name": self.image_connector_id})
         vm_id_1 = self.gcp_conn.new_vminstance(
@@ -138,7 +149,12 @@ class TestGCPOperations():
 
     def test_vminstances_flavor(self):
         machine_type = self.gcp_conn.get_flavor_id_from_data(
-            {'disk': 10, 'ram': 2048, 'vcpus': 1, 'extended': {'mempage-size': 'LARGE', 'numas': [{'threads': 1}]}}
+            {
+                "disk": 10,
+                "ram": 2048,
+                "vcpus": 1,
+                "extended": {"mempage-size": "LARGE", "numas": [{"threads": 1}]},
+            }
         )
         vm_id_1 = self.gcp_conn.new_vminstance(
             name=self.vm_name,
@@ -168,7 +184,7 @@ if __name__ == "__main__":
     try:
         with open(gcp_env_file) as f:
             for line in f:
-                var, value = line.replace('\n', '').split("=")
+                var, value = line.replace("\n", "").split("=")
                 if var == "GCP_PROJECT":
                     project_name = value
                 elif var == "GCP_REGION":
@@ -182,9 +198,7 @@ if __name__ == "__main__":
                 elif var == "GCP_FLAVOR":
                     flavor_id = value
     except ValueError:
-        raise Exception(
-            "Wrong format of GCP test environment file"
-        )
+        raise Exception("Wrong format of GCP test environment file")
 
     if (
         project_name is None
@@ -206,11 +220,10 @@ if __name__ == "__main__":
         credentials_file,
         image_id,
         image_connector_id,
-        flavor_id
+        flavor_id,
     )
     test_gcp.test_networks()
     test_gcp.test_vminstances_default()
     test_gcp.test_vminstances_2_nets()
     test_gcp.test_vminstances_connector_id()
     test_gcp.test_vminstances_flavor()
-
diff --git a/releasenotes/notes/extracting_create_task-e76ed58bfe5a6751.yaml b/releasenotes/notes/extracting_create_task-e76ed58bfe5a6751.yaml
new file mode 100644 (file)
index 0000000..9726d36
--- /dev/null
@@ -0,0 +1,23 @@
+#######################################################################################
+# Copyright ETSI Contributors and Others.
+#
+# 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.
+#######################################################################################
+---
+other:
+  - |
+    Extraction of _create_task() from being a nested function inside Ns.deploy().
+    The _create_task() function is now a static method inside the Ns class. This
+    eases the testability of _create_task().
+    With this extraction a unit test was introduced to cover the extracted function.
diff --git a/tox.ini b/tox.ini
index 316575f..34e22ae 100644 (file)
--- a/tox.ini
+++ b/tox.ini
@@ -16,7 +16,7 @@
 #######################################################################################
 
 [tox]
-envlist = black, flake8, pylint, safety
+envlist = black, cover, flake8, pylint, safety
 
 [tox:jenkins]
 toxworkdir = /tmp/.tox
@@ -43,25 +43,26 @@ commands =
 deps = black
 skip_install = true
 commands =
-        - black --check --diff NG-RO
-        - black --check --diff RO-plugin
-        - black --check --diff RO-SDN-arista_cloudvision
-        - black --check --diff RO-SDN-dpb
-        - black --check --diff RO-SDN-dynpac
-        - black --check --diff RO-SDN-floodlight_openflow
-        - black --check --diff RO-SDN-ietfl2vpn
-        - black --check --diff RO-SDN-juniper_contrail
-        - black --check --diff RO-SDN-odl_openflow
-        - black --check --diff RO-SDN-onos_openflow
-        - black --check --diff RO-SDN-onos_vpls
-        - black --check --diff RO-VIM-aws
-        - black --check --diff RO-VIM-azure
-        - black --check --diff RO-VIM-fos
-        - black --check --diff RO-VIM-opennebula
-        - black --check --diff RO-VIM-openstack
-        - black --check --diff RO-VIM-openvim
-        - black --check --diff RO-VIM-vmware
-        - black --check --diff RO-VIM-gcp
+        black --check --diff NG-RO
+        black --check --diff RO-plugin
+        black --check --diff RO-SDN-arista_cloudvision
+        black --check --diff RO-SDN-dpb
+        black --check --diff RO-SDN-dynpac
+        black --check --diff RO-SDN-floodlight_openflow
+        black --check --diff RO-SDN-ietfl2vpn
+        black --check --diff RO-SDN-juniper_contrail
+        black --check --diff RO-SDN-odl_openflow
+        black --check --diff RO-SDN-onos_openflow
+        black --check --diff RO-SDN-onos_vpls
+        black --check --diff RO-VIM-aws
+        black --check --diff RO-VIM-azure
+        black --check --diff RO-VIM-fos
+        black --check --diff RO-VIM-opennebula
+        black --check --diff RO-VIM-openstack
+        black --check --diff RO-VIM-openvim
+        black --check --diff RO-VIM-vmware
+        black --check --diff RO-VIM-gcp
+        black --check --diff integration-tests
 
 
 #######################################################################################
@@ -70,72 +71,72 @@ deps =  {[testenv]deps}
         -r{toxinidir}/requirements-dev.txt
         -r{toxinidir}/requirements-test.txt
 skip_install = true
+whitelist_externals = sh
 commands =
         sh -c 'rm -f nosetests.xml'
         coverage erase
         # NG-RO
-        - nose2 -C --coverage NG-RO/osm_ng_ro
+        nose2 -C --coverage NG-RO/osm_ng_ro -s NG-RO/osm_ng_ro
         sh -c 'mv .coverage .coverage_ng_ro'
         # RO-plugin
-        - nose2 -C --coverage RO-plugin/osm_ro_plugin
+        nose2 -C --coverage RO-plugin/osm_ro_plugin -s RO-plugin/osm_ro_plugin
         sh -c 'mv .coverage .coverage_ro_plugin'
         # RO-SDN-arista_cloudvision
-        - nose2 -C --coverage RO-SDN-arista_cloudvision/osm_rosdn_arista_cloudvision
+        nose2 -C --coverage RO-SDN-arista_cloudvision/osm_rosdn_arista_cloudvision -s RO-SDN-arista_cloudvision/osm_rosdn_arista_cloudvision
         sh -c 'mv .coverage .coverage_rosdn_arista_cloudvision'
         # RO-SDN-dpb
-        - nose2 -C --coverage RO-SDN-dpb/osm_rosdn_dpb
+        nose2 -C --coverage RO-SDN-dpb/osm_rosdn_dpb -s RO-SDN-dpb/osm_rosdn_dpb
         sh -c 'mv .coverage .coverage_rosdn_dpb'
         # RO-SDN-dynpac
-        - nose2 -C --coverage RO-SDN-dynpac/osm_rosdn_dynpac
+        nose2 -C --coverage RO-SDN-dynpac/osm_rosdn_dynpac -s RO-SDN-dynpac/osm_rosdn_dynpac
         sh -c 'mv .coverage .coverage_rosdn_dynpac'
         # RO-SDN-floodlight_openflow
-        - nose2 -C --coverage RO-SDN-floodlight_openflow/osm_rosdn_floodlightof
+        nose2 -C --coverage RO-SDN-floodlight_openflow/osm_rosdn_floodlightof -s RO-SDN-floodlight_openflow/osm_rosdn_floodlightof
         sh -c 'mv .coverage .coverage_rosdn_floodlightof'
         # RO-SDN-ietfl2vpn
-        - nose2 -C --coverage RO-SDN-ietfl2vpn/osm_rosdn_ietfl2vpn
+        nose2 -C --coverage RO-SDN-ietfl2vpn/osm_rosdn_ietfl2vpn -s RO-SDN-ietfl2vpn/osm_rosdn_ietfl2vpn
         sh -c 'mv .coverage .coverage_rosdn_ietfl2vpn'
         # RO-SDN-juniper_contrail
-        - nose2 -C --coverage RO-SDN-juniper_contrail/osm_rosdn_juniper_contrail
+        nose2 -C --coverage RO-SDN-juniper_contrail/osm_rosdn_juniper_contrail -s RO-SDN-juniper_contrail/osm_rosdn_juniper_contrail
         sh -c 'mv .coverage .coverage_rosdn_juniper_contrail'
         # RO-SDN-odl_openflow
-        - nose2 -C --coverage RO-SDN-odl_openflow/osm_rosdn_odlof
+        nose2 -C --coverage RO-SDN-odl_openflow/osm_rosdn_odlof -s RO-SDN-odl_openflow/osm_rosdn_odlof
         sh -c 'mv .coverage .coverage_rosdn_odlof'
         # RO-SDN-onos_openflow
-        - nose2 -C --coverage RO-SDN-onos_openflow/osm_rosdn_onosof
+        nose2 -C --coverage RO-SDN-onos_openflow/osm_rosdn_onosof -s RO-SDN-onos_openflow/osm_rosdn_onosof
         sh -c 'mv .coverage .coverage_rosdn_onosof'
         # RO-SDN-onos_vpls
-        - nose2 -C --coverage RO-SDN-onos_vpls/osm_rosdn_onos_vpls
+        nose2 -C --coverage RO-SDN-onos_vpls/osm_rosdn_onos_vpls -s RO-SDN-onos_vpls/osm_rosdn_onos_vpls
         sh -c 'mv .coverage .coverage_rosdn_onos_vpls'
         # RO-VIM-aws
-        - nose2 -C --coverage RO-VIM-aws/osm_rovim_aws
+        nose2 -C --coverage RO-VIM-aws/osm_rovim_aws -s RO-VIM-aws/osm_rovim_aws
         sh -c 'mv .coverage .coverage_rovim_aws'
         # RO-VIM-azure
-        - nose2 -C --coverage RO-VIM-azure/osm_rovim_azure
+        nose2 -C --coverage RO-VIM-azure/osm_rovim_azure -s RO-VIM-azure/osm_rovim_azure
         sh -c 'mv .coverage .coverage_rovim_azure'
         # RO-VIM-fos
-        - nose2 -C --coverage RO-VIM-fos/osm_rovim_fos
+        nose2 -C --coverage RO-VIM-fos/osm_rovim_fos -s RO-VIM-fos/osm_rovim_fos
         sh -c 'mv .coverage .coverage_rovim_fos'
         # RO-VIM-opennebula
-        - nose2 -C --coverage RO-VIM-opennebula/osm_rovim_opennebula
+        nose2 -C --coverage RO-VIM-opennebula/osm_rovim_opennebula -s RO-VIM-opennebula/osm_rovim_opennebula
         sh -c 'mv .coverage .coverage_rovim_opennebula'
         # RO-VIM-openstack
-        - nose2 -C --coverage RO-VIM-openstack/osm_rovim_openstack -s RO-VIM-openstack/osm_rovim_openstack
-        sh -c 'mv .coverage .coverage_rovim_openstack'
+        # nose2 -C --coverage RO-VIM-openstack/osm_rovim_openstack -s RO-VIM-openstack/osm_rovim_openstack
+        sh -c 'mv .coverage .coverage_rovim_openstack'
         # RO-VIM-openvim
-        - nose2 -C --coverage RO-VIM-openvim/osm_rovim_openvim
+        nose2 -C --coverage RO-VIM-openvim/osm_rovim_openvim -s RO-VIM-openvim/osm_rovim_openvim
         sh -c 'mv .coverage .coverage_rovim_openvim'
         # RO-VIM-vmware
-        - nose2 -C --coverage RO-VIM-vmware/osm_rovim_vmware -s RO-VIM-vmware/osm_rovim_vmware
-        sh -c 'mv .coverage .coverage_rovim_vmware'
+        # nose2 -C --coverage RO-VIM-vmware/osm_rovim_vmware -s RO-VIM-vmware/osm_rovim_vmware
+        sh -c 'mv .coverage .coverage_rovim_vmware'
         # RO-VIM-gcp
-        - nose2 -C --coverage RO-VIM-gcp/osm_rovim_gcp
+        nose2 -C --coverage RO-VIM-gcp/osm_rovim_gcp -s RO-VIM-gcp/osm_rovim_gcp
         sh -c 'mv .coverage .coverage_rovim_gcp'
         # Combine results and generate reports
-        coverage combine .coverage_ng_ro .coverage_ro_plugin .coverage_rosdn_arista_cloudvision .coverage_rosdn_dpb .coverage_rosdn_dynpac .coverage_rosdn_floodlightof .coverage_rosdn_ietfl2vpn .coverage_rosdn_juniper_contrail .coverage_rosdn_odlof .coverage_rosdn_onos_vpls .coverage_rosdn_onosof .coverage_rovim_aws .coverage_rovim_azure .coverage_rovim_fos .coverage_rovim_opennebula .coverage_rovim_openstack .coverage_rovim_openvim .coverage_rovim_vmware .coverage_rovim_gcp
+        coverage combine .coverage_ng_ro .coverage_ro_plugin .coverage_rosdn_arista_cloudvision .coverage_rosdn_dpb .coverage_rosdn_dynpac .coverage_rosdn_floodlightof .coverage_rosdn_ietfl2vpn .coverage_rosdn_juniper_contrail .coverage_rosdn_odlof .coverage_rosdn_onos_vpls .coverage_rosdn_onosof .coverage_rovim_aws .coverage_rovim_azure .coverage_rovim_fos .coverage_rovim_opennebula .coverage_rovim_openvim .coverage_rovim_gcp # .coverage_rovim_openstack .coverage_rovim_vmware 
         coverage report --omit='*tests*'
         coverage html -d ./cover --omit='*tests*'
         coverage xml -o coverage.xml --omit='*tests*'
-whitelist_externals = sh
 
 
 #######################################################################################
@@ -210,12 +211,12 @@ commands =
 [testenv:pip-compile]
 deps =  pip-tools==5.5.0
 skip_install = true
+whitelist_externals = sh
 commands =
         - sh -c "for file in requirements*.in ; do pip-compile -rU --no-header $file ;\
         out=`echo $file | sed 's/.in/.txt/'` ; \
         sed -i -e '1 e head -16 tox.ini' $out ;\
         done"
-whitelist_externals = sh
 
 
 #######################################################################################
@@ -223,229 +224,247 @@ whitelist_externals = sh
 deps =  {[testenv]deps}
         -r{toxinidir}/requirements-dist.txt
 skip_install = true
+whitelist_externals = sh
 changedir = {toxinidir}/NG-RO
 commands =
         sh -c 'rm -rf osm_ng_ro/requirements.txt deb_dist dist osm_ng_ro.egg-info osm_ng_ro*.tar.gz'
         sh -c 'cp ../requirements.txt osm_ng_ro/requirements.txt'
         python3 setup.py --command-packages=stdeb.command sdist_dsc
         sh -c 'cd deb_dist/osm-ng-ro*/ && dpkg-buildpackage -rfakeroot -uc -us'
-whitelist_externals = sh
+
 
 #######################################################################################
 [testenv:dist_ro_plugin]
 deps =  {[testenv]deps}
         -r{toxinidir}/requirements-dist.txt
 skip_install = true
+whitelist_externals = sh
 changedir = {toxinidir}/RO-plugin
 commands =
         sh -c 'rm -rf deb_dist dist osm_ro_plugin.egg-info osm_ro_plugin*.tar.gz'
         python3 setup.py --command-packages=stdeb.command sdist_dsc
         sh -c 'cd deb_dist/osm-ro-plugin*/ && dpkg-buildpackage -rfakeroot -uc -us'
-whitelist_externals = sh
+
 
 #######################################################################################
 [testenv:dist_ro_sdn_arista_cloudvision]
 deps =  {[testenv]deps}
         -r{toxinidir}/requirements-dist.txt
 skip_install = true
+whitelist_externals = sh
 changedir = {toxinidir}/RO-SDN-arista_cloudvision
 commands =
         sh -c 'rm -rf deb_dist dist osm_rosdn_arista_cloudvision.egg-info osm_rosdn_arista_cloudvision*.tar.gz'
         python3 setup.py --command-packages=stdeb.command sdist_dsc
         sh -c 'cd deb_dist/osm-rosdn-arista-cloudvision*/ && dpkg-buildpackage -rfakeroot -uc -us'
-whitelist_externals = sh
+
 
 #######################################################################################
 [testenv:dist_ro_sdn_dpb]
 deps =  {[testenv]deps}
         -r{toxinidir}/requirements-dist.txt
 skip_install = true
+whitelist_externals = sh
 changedir = {toxinidir}/RO-SDN-dpb
 commands =
         sh -c 'rm -rf deb_dist dist osm_rosdn_dpb.egg-info osm_rosdn_dpb*.tar.gz'
         python3 setup.py --command-packages=stdeb.command sdist_dsc
         sh -c 'cd deb_dist/osm-rosdn-dpb*/ && dpkg-buildpackage -rfakeroot -uc -us'
-whitelist_externals = sh
+
 
 #######################################################################################
 [testenv:dist_ro_sdn_dynpac]
 deps =  {[testenv]deps}
         -r{toxinidir}/requirements-dist.txt
 skip_install = true
+whitelist_externals = sh
 changedir = {toxinidir}/RO-SDN-dynpac
 commands =
         sh -c 'rm -rf deb_dist dist osm_rosdn_dynpac.egg-info osm_rosdn_dynpac*.tar.gz'
         python3 setup.py --command-packages=stdeb.command sdist_dsc
         sh -c 'cd deb_dist/osm-rosdn-dynpac*/ && dpkg-buildpackage -rfakeroot -uc -us'
-whitelist_externals = sh
+
 
 #######################################################################################
 [testenv:dist_ro_sdn_floodlight_of]
 deps =  {[testenv]deps}
         -r{toxinidir}/requirements-dist.txt
 skip_install = true
+whitelist_externals = sh
 changedir = {toxinidir}/RO-SDN-floodlight_openflow
 commands =
         sh -c 'rm -rf deb_dist dist osm_rosdn_floodlightof.egg-info osm_rosdn_floodlightof*.tar.gz'
         python3 setup.py --command-packages=stdeb.command sdist_dsc
         sh -c 'cd deb_dist/osm-rosdn-floodlightof*/ && dpkg-buildpackage -rfakeroot -uc -us'
-whitelist_externals = sh
+
 
 #######################################################################################
 [testenv:dist_ro_sdn_ietfl2vpn]
 deps =  {[testenv]deps}
         -r{toxinidir}/requirements-dist.txt
 skip_install = true
+whitelist_externals = sh
 changedir = {toxinidir}/RO-SDN-ietfl2vpn
 commands =
         sh -c 'rm -rf deb_dist dist osm_rosdn_ietfl2vpn.egg-info osm_rosdn_ietfl2vpn*.tar.gz'
         python3 setup.py --command-packages=stdeb.command sdist_dsc
         sh -c 'cd deb_dist/osm-rosdn-ietfl2vpn*/ && dpkg-buildpackage -rfakeroot -uc -us'
-whitelist_externals = sh
+
 
 #######################################################################################
 [testenv:dist_ro_sdn_juniper_contrail]
 deps =  {[testenv]deps}
         -r{toxinidir}/requirements-dist.txt
 skip_install = true
+whitelist_externals = sh
 changedir = {toxinidir}/RO-SDN-juniper_contrail
 commands =
         sh -c 'rm -rf deb_dist dist osm_rosdn_juniper_contrail.egg-info osm_rosdn_juniper_contrail*.tar.gz'
         python3 setup.py --command-packages=stdeb.command sdist_dsc
         sh -c 'cd deb_dist/osm-rosdn-juniper-contrail*/ && dpkg-buildpackage -rfakeroot -uc -us'
-whitelist_externals = sh
+
 
 #######################################################################################
 [testenv:dist_ro_sdn_odl_of]
 deps =  {[testenv]deps}
         -r{toxinidir}/requirements-dist.txt
 skip_install = true
+whitelist_externals = sh
 changedir = {toxinidir}/RO-SDN-odl_openflow
 commands =
         sh -c 'rm -rf deb_dist dist osm_rosdn_odlof.egg-info osm_rosdn_odlof*.tar.gz'
         python3 setup.py --command-packages=stdeb.command sdist_dsc
         sh -c 'cd deb_dist/osm-rosdn-odlof*/ && dpkg-buildpackage -rfakeroot -uc -us'
-whitelist_externals = sh
+
 
 #######################################################################################
 [testenv:dist_ro_sdn_onos_of]
 deps =  {[testenv]deps}
         -r{toxinidir}/requirements-dist.txt
 skip_install = true
+whitelist_externals = sh
 changedir = {toxinidir}/RO-SDN-onos_openflow
 commands =
         sh -c 'rm -rf deb_dist dist osm_rosdn_onosof.egg-info osm_rosdn_onosof*.tar.gz'
         python3 setup.py --command-packages=stdeb.command sdist_dsc
         sh -c 'cd deb_dist/osm-rosdn-onosof*/ && dpkg-buildpackage -rfakeroot -uc -us'
-whitelist_externals = sh
+
 
 #######################################################################################
 [testenv:dist_ro_sdn_onos_vpls]
 deps =  {[testenv]deps}
         -r{toxinidir}/requirements-dist.txt
 skip_install = true
+whitelist_externals = sh
 changedir = {toxinidir}/RO-SDN-onos_vpls
 commands =
         sh -c 'rm -rf deb_dist dist osm_rosdn_onos_vpls.egg-info osm_rosdn_onos_vpls*.tar.gz'
         python3 setup.py --command-packages=stdeb.command sdist_dsc
         sh -c 'cd deb_dist/osm-rosdn-onos-vpls*/ && dpkg-buildpackage -rfakeroot -uc -us'
-whitelist_externals = sh
+
 
 #######################################################################################
 [testenv:dist_ro_vim_aws]
 deps =  {[testenv]deps}
         -r{toxinidir}/requirements-dist.txt
 skip_install = true
+whitelist_externals = sh
 changedir = {toxinidir}/RO-VIM-aws
 commands =
         sh -c 'rm -rf deb_dist dist osm_rovim_aws.egg-info osm_rovim_aws*.tar.gz'
         python3 setup.py --command-packages=stdeb.command sdist_dsc
         sh -c 'cd deb_dist/osm-rovim-aws*/ && dpkg-buildpackage -rfakeroot -uc -us'
-whitelist_externals = sh
+
 
 #######################################################################################
 [testenv:dist_ro_vim_azure]
 deps =  {[testenv]deps}
         -r{toxinidir}/requirements-dist.txt
 skip_install = true
+whitelist_externals = sh
 changedir = {toxinidir}/RO-VIM-azure
 commands =
         sh -c 'rm -rf deb_dist dist osm_rovim_azure.egg-info osm_rovim_azure*.tar.gz'
         python3 setup.py --command-packages=stdeb.command sdist_dsc
         sh -c 'cd deb_dist/osm-rovim-azure*/ && dpkg-buildpackage -rfakeroot -uc -us'
-whitelist_externals = sh
+
 
 #######################################################################################
 [testenv:dist_ro_vim_fos]
 deps =  {[testenv]deps}
         -r{toxinidir}/requirements-dist.txt
 skip_install = true
+whitelist_externals = sh
 changedir = {toxinidir}/RO-VIM-fos
 commands =
         sh -c 'rm -rf deb_dist dist osm_rovim_fos.egg-info osm_rovim_fos*.tar.gz'
         python3 setup.py --command-packages=stdeb.command sdist_dsc
         sh -c 'cd deb_dist/osm-rovim-fos*/ && dpkg-buildpackage -rfakeroot -uc -us'
-whitelist_externals = sh
+
 
 #######################################################################################
 [testenv:dist_ro_vim_opennebula]
 deps =  {[testenv]deps}
         -r{toxinidir}/requirements-dist.txt
 skip_install = true
+whitelist_externals = sh
 changedir = {toxinidir}/RO-VIM-opennebula
 commands =
         sh -c 'rm -rf deb_dist dist osm_rovim_opennebula.egg-info osm_rovim_opennebula*.tar.gz'
         python3 setup.py --command-packages=stdeb.command sdist_dsc
         sh -c 'cd deb_dist/osm-rovim-opennebula*/ && dpkg-buildpackage -rfakeroot -uc -us'
-whitelist_externals = sh
+
 
 #######################################################################################
 [testenv:dist_ro_vim_openstack]
 deps =  {[testenv]deps}
         -r{toxinidir}/requirements-dist.txt
 skip_install = true
+whitelist_externals = sh
 changedir = {toxinidir}/RO-VIM-openstack
 commands =
         sh -c 'rm -rf deb_dist dist osm_rovim_openstack.egg-info osm_rovim_openstack*.tar.gz'
         python3 setup.py --command-packages=stdeb.command sdist_dsc
         sh -c 'cd deb_dist/osm-rovim-openstack*/ && dpkg-buildpackage -rfakeroot -uc -us'
-whitelist_externals = sh
+
 
 #######################################################################################
 [testenv:dist_ro_vim_openvim]
 deps =  {[testenv]deps}
         -r{toxinidir}/requirements-dist.txt
 skip_install = true
+whitelist_externals = sh
 changedir = {toxinidir}/RO-VIM-openvim
 commands =
         sh -c 'rm -rf deb_dist dist osm_rovim_openvim.egg-info osm_rovim_openvim*.tar.gz'
         python3 setup.py --command-packages=stdeb.command sdist_dsc
         sh -c 'cd deb_dist/osm-rovim-openvim*/ && dpkg-buildpackage -rfakeroot -uc -us'
-whitelist_externals = sh
+
 
 #######################################################################################
 [testenv:dist_ro_vim_vmware]
 deps =  {[testenv]deps}
         -r{toxinidir}/requirements-dist.txt
 skip_install = true
+whitelist_externals = sh
 changedir = {toxinidir}/RO-VIM-vmware
 commands =
         sh -c 'rm -rf deb_dist dist osm_rovim_vmware.egg-info osm_rovim_vmware*.tar.gz'
         python3 setup.py --command-packages=stdeb.command sdist_dsc
         sh -c 'cd deb_dist/osm-rovim-vmware*/ && dpkg-buildpackage -rfakeroot -uc -us'
-whitelist_externals = sh
+
 
 #######################################################################################
 [testenv:dist_ro_vim_gcp]
 deps =  {[testenv]deps}
         -r{toxinidir}/requirements-dist.txt
 skip_install = true
+whitelist_externals = sh
 changedir = {toxinidir}/RO-VIM-gcp
 commands =
         sh -c 'rm -rf deb_dist dist osm_rovim_gcp.egg-info osm_rovim_gcp*.tar.gz'
         python3 setup.py --command-packages=stdeb.command sdist_dsc
         sh -c 'cd deb_dist/osm-rovim-gcp*/ && dpkg-buildpackage -rfakeroot -uc -us'
-whitelist_externals = sh
 
 
 #######################################################################################