Enable lint, flake8 and unit tests

Cleans up non pep compliant code.
Adds a simple unit test.
Formats according to black.

Tox automatically runs lint, flake8 and unit test suite
with coverage.  To run each individually, execute:

tox -e pylint
tox -e black
tox -e flake8
tox -e cover

Note that these are all run for each patch via Jenkins.  The full
tox suite should be run locally before any commit to ensure it
will not fail in Jenkins.

Change-Id: I2f87abe3d5086d6d65ac33a27780c498fc7b1cd3
Signed-off-by: beierlm <mark.beierl@canonical.com>
diff --git a/n2vc/provisioner.py b/n2vc/provisioner.py
index 33c13f1..5107242 100644
--- a/n2vc/provisioner.py
+++ b/n2vc/provisioner.py
@@ -15,15 +15,15 @@
 import os
 import re
 import shlex
+from subprocess import CalledProcessError
 import tempfile
 import time
 import uuid
-from subprocess import CalledProcessError
-
-import paramiko
-import n2vc.exceptions
 
 from juju.client import client
+import n2vc.exceptions
+import paramiko
+
 
 arches = [
     [re.compile(r"amd64|x86_64"), "amd64"],
@@ -32,7 +32,6 @@
     [re.compile(r"aarch64"), "arm64"],
     [re.compile(r"ppc64|ppc64el|ppc64le"), "ppc64el"],
     [re.compile(r"s390x?"), "s390x"],
-
 ]
 
 
@@ -79,6 +78,7 @@
 netfilter-persistent save
 """
 
+
 class SSHProvisioner:
     """Provision a manually created machine via SSH."""
 
@@ -121,7 +121,7 @@
 
         # Read the private key into a paramiko.RSAKey
         if os.path.exists(private_key_path):
-            with open(private_key_path, 'r') as f:
+            with open(private_key_path, "r") as f:
                 pkey = paramiko.RSAKey.from_private_key(f)
 
         #######################################################################
@@ -155,7 +155,7 @@
                 )
                 break
             except paramiko.ssh_exception.SSHException as e:
-                if 'Error reading SSH protocol banner' == str(e):
+                if "Error reading SSH protocol banner" == str(e):
                     # Once more, with feeling
                     ssh.connect(host, port=22, username=user, pkey=pkey)
                 else:
@@ -163,8 +163,10 @@
                     self.log.debug("Unhandled exception caught: {}".format(e))
                     raise e
             except Exception as e:
-                if 'Unable to connect to port' in str(e):
-                    self.log.debug("Waiting for VM to boot, sleeping {} seconds".format(delay))
+                if "Unable to connect to port" in str(e):
+                    self.log.debug(
+                        "Waiting for VM to boot, sleeping {} seconds".format(delay)
+                    )
                     if attempts > retry:
                         raise e
                     else:
@@ -194,17 +196,16 @@
         if type(cmd) is not list:
             cmd = [cmd]
 
-        cmds = ' '.join(cmd)
-        stdin, stdout, stderr = ssh.exec_command(cmds, get_pty=pty)
+        cmds = " ".join(cmd)
+        _, stdout, stderr = ssh.exec_command(cmds, get_pty=pty)
         retcode = stdout.channel.recv_exit_status()
 
         if retcode > 0:
             output = stderr.read().strip()
-            raise CalledProcessError(returncode=retcode, cmd=cmd,
-                                     output=output)
+            raise CalledProcessError(returncode=retcode, cmd=cmd, output=output)
         return (
-            stdout.read().decode('utf-8').strip(),
-            stderr.read().decode('utf-8').strip()
+            stdout.read().decode("utf-8").strip(),
+            stderr.read().decode("utf-8").strip(),
         )
 
     def _init_ubuntu_user(self):
@@ -218,7 +219,7 @@
         try:
             # Run w/o allocating a pty, so we fail if sudo prompts for a passwd
             ssh = self._get_ssh_client()
-            stdout, stderr = self._run_command(ssh, "sudo -n true", pty=False)
+            self._run_command(ssh, "sudo -n true", pty=False)
         except paramiko.ssh_exception.AuthenticationException:
             raise n2vc.exceptions.AuthenticationFailed(self.user)
         except paramiko.ssh_exception.NoValidConnectionsError:
@@ -228,7 +229,6 @@
                 ssh.close()
 
         # Infer the public key
-        public_key = None
         public_key_path = "{}.pub".format(self.private_key_path)
 
         if not os.path.exists(public_key_path):
@@ -245,9 +245,7 @@
             ssh = self._get_ssh_client()
 
             self._run_command(
-                ssh,
-                ["sudo", "/bin/bash -c " + shlex.quote(script)],
-                pty=True
+                ssh, ["sudo", "/bin/bash -c " + shlex.quote(script)], pty=True
             )
         except paramiko.ssh_exception.AuthenticationException as e:
             raise e
@@ -264,32 +262,30 @@
         """
 
         info = {
-            'series': '',
-            'arch': '',
-            'cpu-cores': '',
-            'mem': '',
+            "series": "",
+            "arch": "",
+            "cpu-cores": "",
+            "mem": "",
         }
 
-        stdout, stderr = self._run_command(
-            ssh,
-            ["sudo", "/bin/bash -c " + shlex.quote(DETECTION_SCRIPT)],
-            pty=True,
+        stdout, _ = self._run_command(
+            ssh, ["sudo", "/bin/bash -c " + shlex.quote(DETECTION_SCRIPT)], pty=True,
         )
 
         lines = stdout.split("\n")
 
         # Remove extraneous line if DNS resolution of hostname famils
         # i.e. sudo: unable to resolve host test-1-mgmtvm-1: Connection timed out
-        if 'unable to resolve host' in lines[0]:
+        if "unable to resolve host" in lines[0]:
             lines = lines[1:]
 
-        info['series'] = lines[0].strip()
-        info['arch'] = normalize_arch(lines[1].strip())
+        info["series"] = lines[0].strip()
+        info["arch"] = normalize_arch(lines[1].strip())
 
-        memKb = re.split(r'\s+', lines[2])[1]
+        memKb = re.split(r"\s+", lines[2])[1]
 
         # Convert megabytes -> kilobytes
-        info['mem'] = round(int(memKb) / 1024)
+        info["mem"] = round(int(memKb) / 1024)
 
         # Detect available CPUs
         recorded = {}
@@ -302,7 +298,7 @@
                 cores = line.split(":")[1].strip()
 
                 if physical_id not in recorded.keys():
-                    info['cpu-cores'] += cores
+                    info["cpu-cores"] += cores
                     recorded[physical_id] = True
 
         return info
@@ -321,22 +317,19 @@
                 ssh = self._get_ssh_client()
 
                 hw = self._detect_hardware_and_os(ssh)
-                params.series = hw['series']
+                params.series = hw["series"]
                 params.instance_id = "manual:{}".format(self.host)
                 params.nonce = "manual:{}:{}".format(
-                    self.host,
-                    str(uuid.uuid4()),  # a nop for Juju w/manual machines
+                    self.host, str(uuid.uuid4()),  # a nop for Juju w/manual machines
                 )
                 params.hardware_characteristics = {
-                    'arch': hw['arch'],
-                    'mem': int(hw['mem']),
-                    'cpu-cores': int(hw['cpu-cores']),
+                    "arch": hw["arch"],
+                    "mem": int(hw["mem"]),
+                    "cpu-cores": int(hw["cpu-cores"]),
                 }
-                params.addresses = [{
-                    'value': self.host,
-                    'type': 'ipv4',
-                    'scope': 'public',
-                }]
+                params.addresses = [
+                    {"value": self.host, "type": "ipv4", "scope": "public"}
+                ]
 
             except paramiko.ssh_exception.AuthenticationException as e:
                 raise e
@@ -378,7 +371,7 @@
             - 127.0.0.1:17070
             - '[::1]:17070'
         """
-        m = re.search('apiaddresses:\n- (\d+\.\d+\.\d+\.\d+):17070', results.script)
+        m = re.search(r"apiaddresses:\n- (\d+\.\d+\.\d+\.\d+):17070", results.script)
         apiaddress = m.group(1)
 
         """Add IP Table rule
@@ -405,20 +398,18 @@
                 self._run_configure_script(script)
                 break
             except Exception as e:
-                    self.log.debug("Waiting for dpkg, sleeping {} seconds".format(delay))
-                    if attempts > retry:
-                        raise e
-                    else:
-                        time.sleep(delay)
-                        # Slowly back off the retry
-                        delay += 15
+                self.log.debug("Waiting for dpkg, sleeping {} seconds".format(delay))
+                if attempts > retry:
+                    raise e
+                else:
+                    time.sleep(delay)
+                    # Slowly back off the retry
+                    delay += 15
 
         # self.log.debug("Running configure script")
         self._run_configure_script(results.script)
         # self.log.debug("Configure script finished")
 
-
-
     def _run_configure_script(self, script: str):
         """Run the script to install the Juju agent on the target machine.
 
@@ -427,25 +418,21 @@
             if the upload fails
         """
         _, tmpFile = tempfile.mkstemp()
-        with open(tmpFile, 'w') as f:
+        with open(tmpFile, "w") as f:
             f.write(script)
         try:
             # get ssh client
-            ssh = self._get_ssh_client(
-                user="ubuntu",
-            )
+            ssh = self._get_ssh_client(user="ubuntu",)
 
             # copy the local copy of the script to the remote machine
             sftp = paramiko.SFTPClient.from_transport(ssh.get_transport())
             sftp.put(
-                tmpFile,
-                tmpFile,
+                tmpFile, tmpFile,
             )
 
             # run the provisioning script
-            stdout, stderr = self._run_command(
-                ssh,
-                "sudo /bin/bash {}".format(tmpFile),
+            self._run_command(
+                ssh, "sudo /bin/bash {}".format(tmpFile),
             )
 
         except paramiko.ssh_exception.AuthenticationException as e: