Migrating to Python v3

 - support for Python v3.8.x
 - support for Python v3.5.x
 - new tag, 2019.2.8
 - updates class generation and iterators
 - unittests updated with coverage >75%
 - new coverage routines
 - unittests profiling
 - full fake data for unittests
 - unittest testrun is ~1.5 seconds long

Bugfixes
 - 34834, proper use of 'sudo' option
 - multiple proper iterator use
 - 37919, show warning when installed and candidate versions
   are newer comparing to release version

Change-Id: Idd6b889f7ce94ae0c832e2f0a0346e4fdc3264a3
Related-PROD: PROD-34834 PROD-34664 PROD-34919
diff --git a/cfg_checker/modules/network/mapper.py b/cfg_checker/modules/network/mapper.py
index 482bdfa..59f3781 100644
--- a/cfg_checker/modules/network/mapper.py
+++ b/cfg_checker/modules/network/mapper.py
@@ -124,7 +124,7 @@
                 continue
 
             # build map based on IPs and save info too
-            for if_name, _dat in _pillar.iteritems():
+            for if_name, _dat in _pillar.items():
                 # get proper IF name
                 _if_name = if_name if 'name' not in _dat else _dat['name']
                 # place it
@@ -195,11 +195,11 @@
 
         logger_cli.info("-> mapping IPs")
         # match interfaces by IP subnets
-        for host, node_data in salt_master.nodes.iteritems():
+        for host, node_data in salt_master.nodes.items():
             if not salt_master.is_node_available(host):
                 continue
 
-            for net_name, net_data in node_data['networks'].iteritems():
+            for net_name, net_data in node_data['networks'].items():
                 # cut net name
                 _i = net_name.find('@')
                 _name = net_name if _i < 0 else net_name[:_i]
@@ -321,7 +321,7 @@
 
                 # debug, print built tree
                 # logger_cli.debug("# '{}'".format(_ifname))
-                lvls = _tree.keys()
+                lvls = list(_tree.keys())
                 lvls.sort()
                 n = len(lvls)
                 m = max([len(_tree[k].keys()) for k in _tree.keys()])
@@ -330,11 +330,14 @@
                 while True:
                     _lv = lvls.pop(0)
                     # get all interfaces on this level
-                    nets = _tree[_lv].keys()
+                    nets = iter(_tree[_lv].keys())
                     while True:
                         y = 0
                         # get next interface
-                        _net = nets.pop(0)
+                        try:
+                            _net = next(nets)
+                        except StopIteration:
+                            break
                         # all nets
                         _a = [_net]
                         # put current interface if this is only one left
diff --git a/cfg_checker/modules/network/network_errors.py b/cfg_checker/modules/network/network_errors.py
index 6c41021..7159a36 100644
--- a/cfg_checker/modules/network/network_errors.py
+++ b/cfg_checker/modules/network/network_errors.py
@@ -23,9 +23,9 @@
     NET_PING_ERROR = next(_c)
     NET_PING_NOT_RESOLVED = next(_c)
 
-    def __init__(self):
-        super(NetworkErrors, self).__init__("NET")
+    _initialized = False
 
+    def _add_types(self):
         self.add_error_type(
             self.NET_MTU_MISMATCH,
             "MTU mismatch on runtime interface and in reclass"
@@ -82,6 +82,21 @@
             self.NET_PING_NOT_RESOLVED,
             "Host not resolved while conducting Ping"
         )
+        self._initialized = True
+
+    def __init__(self, folder=None):
+        super(NetworkErrors, self).__init__("NET", folder=folder)
+
+        if not self._initialized:
+            self._add_types()
+            self._initialized = True
+
+    def __call__(self):
+        if not self._initialized:
+            self._add_types()
+            self._initialized = True
+
+        return self
 
 
 del _c
diff --git a/cfg_checker/modules/network/pinger.py b/cfg_checker/modules/network/pinger.py
index 266727b..0500284 100644
--- a/cfg_checker/modules/network/pinger.py
+++ b/cfg_checker/modules/network/pinger.py
@@ -44,7 +44,7 @@
     def ping_nodes(self, network_cidr_str):
         # Conduct actual ping using network CIDR
         logger_cli.info("# Collecting node pairs")
-        _fake_if = ipaddress.IPv4Interface(unicode(network_cidr_str))
+        _fake_if = ipaddress.IPv4Interface(str(network_cidr_str))
         _net = _fake_if.network
         # collect nodes and ips from reclass
         nodes = self._collect_node_addresses(_net)
@@ -69,7 +69,7 @@
                 "targets": {}
             }
 
-            for tgt_host, tgt_data in nodes.iteritems():
+            for tgt_host, tgt_data in nodes.items():
                 _t = _packets[src_host]["targets"]
                 for tgt_if in tgt_data:
                     tgt_if_name = tgt_if['name']
@@ -110,7 +110,7 @@
         _progress = Progress(_count)
         _progress_index = 0
         _node_index = 0
-        for src, src_data in _packets.iteritems():
+        for src, src_data in _packets.items():
             _targets = src_data["targets"]
             _node_index += 1
             # create 'targets.json' on source host
@@ -154,7 +154,7 @@
                 )
                 continue
             # Handle return codes
-            for tgt_node, _tgt_ips in _result.iteritems():
+            for tgt_node, _tgt_ips in _result.items():
                 for _params in _tgt_ips:
                     _body = "{}({}) --{}--> {}({}@{})\n".format(
                             src,