Bug 666: Fix window_size overflow with Paramiko
[osm/N2VC.git] / tests / base.py
index 1db4843..a0a2b78 100644 (file)
@@ -25,8 +25,7 @@ here = os.path.dirname(os.path.realpath(__file__))
 def is_bootstrapped():
     result = subprocess.run(['juju', 'switch'], stdout=subprocess.PIPE)
     return (
-        result.returncode == 0 and
-        len(result.stdout.decode().strip()) > 0)
+        result.returncode == 0 and len(result.stdout.decode().strip()) > 0)
 
 
 bootstrapped = pytest.mark.skipif(
@@ -65,9 +64,9 @@ def debug(msg):
     logging.debug(
         "[{}] {}".format(now.strftime('%Y-%m-%dT%H:%M:%S'), msg)
     )
-    print(
-        "[{}] {}".format(now.strftime('%Y-%m-%dT%H:%M:%S'), msg)
-    )
+    print(
+        "[{}] {}".format(now.strftime('%Y-%m-%dT%H:%M:%S'), msg)
+    )
 
 
 def get_charm_path():
@@ -163,7 +162,7 @@ def create_lxd_container(public_key=None, name="test_name"):
         name,
     )
 
-    private_key_path, public_key_path = find_juju_ssh_keys()
+    private_key_path, public_key_path = find_n2vc_ssh_keys()
 
     try:
         # create profile w/cloud-init and juju ssh key
@@ -231,6 +230,25 @@ def create_lxd_container(public_key=None, name="test_name"):
             )
         )
 
+    try:
+        waitcount = 0
+        while waitcount <= 5:
+            if is_sshd_running(container):
+                break
+            waitcount += 1
+            time.sleep(1)
+        if waitcount >= 5:
+            debug("couldn't detect sshd running")
+            raise Exception("Unable to verify container sshd")
+
+    except Exception as ex:
+        debug(
+            "Error checking sshd status on {}: {}".format(
+                test_machine,
+                ex,
+            )
+        )
+
     # HACK: We need to give sshd a chance to bind to the interface,
     # and pylxd's container.execute seems to be broken and fails and/or
     # hangs trying to properly check if the service is up.
@@ -247,6 +265,28 @@ def create_lxd_container(public_key=None, name="test_name"):
     return container
 
 
+def is_sshd_running(container):
+    """Check if sshd is running in the container.
+
+    Check to see if the sshd process is running and listening on port 22.
+
+    :param container: The container to check
+    :return boolean: True if sshd is running.
+    """
+    debug("Container: {}".format(container))
+    try:
+        (rc, stdout, stderr) = container.execute(
+            ["service", "ssh", "status"]
+        )
+        # If the status is a) found and b) running, the exit code will be 0
+        if rc == 0:
+            return True
+    except Exception as ex:
+        debug("Failed to check sshd service status: {}".format(ex))
+
+    return False
+
+
 def destroy_lxd_container(container):
     """Stop and delete a LXD container.
 
@@ -330,6 +370,21 @@ def find_lxd_config():
     return (None, None)
 
 
+def find_n2vc_ssh_keys():
+    """Find the N2VC ssh keys."""
+
+    paths = []
+    paths.append(os.path.expanduser("~/.ssh/"))
+
+    for path in paths:
+        if os.path.exists(path):
+            private = os.path.expanduser("{}/id_n2vc_rsa".format(path))
+            public = os.path.expanduser("{}/id_n2vc_rsa.pub".format(path))
+            if os.path.exists(private) and os.path.exists(public):
+                return (private, public)
+    return (None, None)
+
+
 def find_juju_ssh_keys():
     """Find the Juju ssh keys."""
 
@@ -417,9 +472,6 @@ class TestN2VC(object):
         self.ns_name = self.nsd['name']
         self.vnf_name = self.vnfd['name']
 
-        # Hard-coded to default for now, but this may change in the future.
-        self.model = "default"
-
         self.charms = {}
         self.parse_vnf_descriptor()
         assert self.charms is not {}
@@ -444,6 +496,7 @@ class TestN2VC(object):
         """
         debug("Running teardown_class...")
         try:
+
             debug("Destroying LXD containers...")
             for application in self.state:
                 if self.state[application]['container']:
@@ -452,9 +505,10 @@ class TestN2VC(object):
 
             # Logout of N2VC
             if self.n2vc:
-                debug("Logging out of N2VC...")
+                debug("teardown_class(): Logging out of N2VC...")
                 yield from self.n2vc.logout()
-                debug("Logging out of N2VC...done.")
+                debug("teardown_class(): Logging out of N2VC...done.")
+
             debug("Running teardown_class...done.")
         except Exception as ex:
             debug("Exception in teardown_class: {}".format(ex))
@@ -536,7 +590,7 @@ class TestN2VC(object):
         # Make sure the charm snap is installed
         try:
             subprocess.check_call(['which', 'charm'])
-        except subprocess.CalledProcessError as e:
+        except subprocess.CalledProcessError:
             raise Exception("charm snap not installed.")
 
         if charm not in self.artifacts:
@@ -548,19 +602,24 @@ class TestN2VC(object):
                 builds = get_charm_path()
 
                 if not os.path.exists("{}/builds/{}".format(builds, charm)):
-                    cmd = "charm build {}/{} -o {}/".format(
+                    cmd = "charm build --no-local-layers {}/{} -o {}/".format(
                         get_layer_path(),
                         charm,
                         builds,
                     )
                     subprocess.check_call(shlex.split(cmd))
 
-                self.artifacts[charm] = {
-                    'tmpdir': builds,
-                    'charm': "{}/builds/{}".format(builds, charm),
-                }
             except subprocess.CalledProcessError as e:
-                raise Exception("charm build failed: {}.".format(e))
+                # charm build will return error code 100 if the charm fails
+                # the auto-run of charm proof, which we can safely ignore for
+                # our CI charms.
+                if e.returncode != 100:
+                    raise Exception("charm build failed: {}.".format(e))
+
+            self.artifacts[charm] = {
+                'tmpdir': builds,
+                'charm': "{}/builds/{}".format(builds, charm),
+            }
 
         return self.artifacts[charm]['charm']
 
@@ -573,20 +632,53 @@ class TestN2VC(object):
         if not self.n2vc:
             self.n2vc = get_n2vc(loop=loop)
 
-        vnf_name = self.n2vc.FormatApplicationName(
+        debug("Creating model for Network Service {}".format(self.ns_name))
+        await self.n2vc.CreateNetworkService(self.ns_name)
+
+        application = self.n2vc.FormatApplicationName(
             self.ns_name,
             self.vnf_name,
             str(vnf_index),
         )
+
+        # Initialize the state of the application
+        self.state[application] = {
+            'status': None,     # Juju status
+            'container': None,  # lxd container, for proxy charms
+            'actions': {},      # Actions we've executed
+            'done': False,      # Are we done testing this charm?
+            'phase': "deploy",  # What phase is this application in?
+        }
+
         debug("Deploying charm at {}".format(self.artifacts[charm]))
 
+        # If this is a native charm, we need to provision the underlying
+        # machine ala an LXC container.
+        machine_spec = {}
+
+        if not self.isproxy(application):
+            debug("Creating container for native charm")
+            # args = ("default", application, None, None)
+            self.state[application]['container'] = create_lxd_container(
+                name=os.path.basename(__file__)
+            )
+
+            hostname = self.get_container_ip(
+                self.state[application]['container'],
+            )
+
+            machine_spec = {
+                'host': hostname,
+                'user': 'ubuntu',
+            }
+
         await self.n2vc.DeployCharms(
             self.ns_name,
-            vnf_name,
+            application,
             self.vnfd,
             self.get_charm(charm),
             params,
-            {},
+            machine_spec,
             self.n2vc_callback,
         )
 
@@ -722,6 +814,7 @@ class TestN2VC(object):
 
     @classmethod
     async def configure_proxy_charm(self, *args):
+        """Configure a container for use via ssh."""
         (model, application, _, _) = args
 
         try:
@@ -841,9 +934,31 @@ class TestN2VC(object):
             self._running = False
             self._stopping = True
 
+            # Destroy the network service
+            try:
+                await self.n2vc.DestroyNetworkService(self.ns_name)
+            except Exception as e:
+                debug(
+                    "Error Destroying Network Service \"{}\": {}".format(
+                        self.ns_name,
+                        e,
+                    )
+                )
+
+            # Wait for the applications to be removed and delete the containers
             for application in self.charms:
                 try:
-                    await self.n2vc.RemoveCharms(self.model, application)
+
+                    while True:
+                        # Wait for the application to be removed
+                        await asyncio.sleep(10)
+                        if not await self.n2vc.HasApplication(
+                            self.ns_name,
+                            application,
+                        ):
+                            break
+
+                    # Need to wait for the charm to finish, because native charms
                     if self.state[application]['container']:
                         debug("Deleting LXD container...")
                         destroy_lxd_container(
@@ -858,10 +973,10 @@ class TestN2VC(object):
 
             # Logout of N2VC
             try:
-                debug("Logging out of N2VC...")
+                debug("stop(): Logging out of N2VC...")
                 await self.n2vc.logout()
                 self.n2vc = None
-                debug("Logging out of N2VC...Done.")
+                debug("stop(): Logging out of N2VC...Done.")
             except Exception as ex:
                 debug(ex)
 
@@ -903,7 +1018,7 @@ class TestN2VC(object):
         )
 
         await self.n2vc.ExecutePrimitive(
-            self.model,
+            self.ns_name,
             application,
             "config",
             None,
@@ -928,7 +1043,7 @@ class TestN2VC(object):
             Re-run those actions so we can inspect the status.
             """
             uuids = await self.n2vc.ExecuteInitialPrimitives(
-                self.model,
+                self.ns_name,
                 application,
                 init_config,
             )
@@ -960,7 +1075,7 @@ class TestN2VC(object):
             debug("Collecting metrics for {}".format(application))
 
             metrics = await self.n2vc.GetMetrics(
-                self.model,
+                self.ns_name,
                 application,
             )
 
@@ -1010,7 +1125,7 @@ class TestN2VC(object):
 
                     debug("Getting status of {} ({})...".format(uid, status))
                     status = await self.n2vc.GetPrimitiveStatus(
-                        self.model,
+                        self.ns_name,
                         uid,
                     )
                     debug("...state of {} is {}".format(uid, status))
@@ -1053,6 +1168,14 @@ class TestN2VC(object):
             debug("{} is done".format(application))
             return
 
+        if status in ['error']:
+            # To test broken charms, if a charm enters an error state we should
+            # end the test
+            debug("{} is in an error state, stop the test.".format(application))
+            # asyncio.ensure_future(self.stop())
+            self.state[application]['done'] = True
+            assert False
+
         if status in ["blocked"] and self.isproxy(application):
             if self.state[application]['phase'] == "deploy":
                 debug("Configuring proxy charm for {}".format(application))