HC Test: improve functional suite setup and teardown
[csit.git] / resources / libraries / python / honeycomb / HoneycombSetup.py
index 89e6207..53130f4 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (c) 2016 Cisco and/or its affiliates.
+# Copyright (c) 2017 Cisco and/or its affiliates.
 # 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:
@@ -13,6 +13,8 @@
 
 """Implementation of keywords for Honeycomb setup."""
 
+from ipaddress import IPv6Address, AddressValueError
+
 from robot.api import logger
 
 from resources.libraries.python.HTTPRequest import HTTPRequest, HTTPCodes, \
@@ -23,6 +25,7 @@ from resources.libraries.python.honeycomb.HoneycombUtil \
     import HoneycombUtil as HcUtil
 from resources.libraries.python.ssh import SSH
 from resources.libraries.python.topology import NodeType
+from resources.libraries.python.DUTSetup import DUTSetup
 
 
 class HoneycombSetup(object):
@@ -56,7 +59,7 @@ class HoneycombSetup(object):
 
         HoneycombSetup.print_environment(nodes)
 
-        logger.console("\nStarting Honeycomb service ...")
+        logger.console("\n(re)Starting Honeycomb service ...")
 
         cmd = "sudo service honeycomb start"
 
@@ -127,7 +130,7 @@ class HoneycombSetup(object):
         for node in nodes:
             if node['type'] == NodeType.DUT:
                 HoneycombSetup.print_ports(node)
-                status_code, _ = HTTPRequest.get(node, path, timeout=10,
+                status_code, _ = HTTPRequest.get(node, path,
                                                  enable_logging=False)
                 if status_code == HTTPCodes.OK:
                     logger.info("Honeycomb on node {0} is up and running".
@@ -169,7 +172,6 @@ class HoneycombSetup(object):
             if node['type'] == NodeType.DUT:
                 try:
                     status_code, _ = HTTPRequest.get(node, '/index.html',
-                                                     timeout=5,
                                                      enable_logging=False)
                     if status_code == HTTPCodes.OK:
                         raise HoneycombError('Honeycomb on node {0} is still '
@@ -198,31 +200,58 @@ class HoneycombSetup(object):
         return True
 
     @staticmethod
-    def configure_unsecured_access(*nodes):
-        """Configure Honeycomb to allow restconf requests through insecure HTTP
-        used by tests. By default this is only allowed for localhost.
+    def configure_restconf_binding_address(node):
+        """Configure Honeycomb to accept restconf requests from all IP
+        addresses. IP version is determined by node data.
 
-         :param nodes: All nodes in test topology.
-         :type nodes: dict
+         :param node: Information about a DUT node.
+         :type node: dict
          :raises HoneycombError: If the configuration could not be changed.
          """
-        # TODO: Modify tests to use HTTPS instead.
 
         find = "restconf-binding-address"
-        replace = '\\"restconf-binding-address\\": \\"0.0.0.0\\",'
+        try:
+            IPv6Address(unicode(node["host"]))
+            # if management IP of the node is in IPv6 format
+            replace = '\\"restconf-binding-address\\": \\"0::0\\",'
+        except (AttributeError, AddressValueError):
+            replace = '\\"restconf-binding-address\\": \\"0.0.0.0\\",'
 
         argument = '"/{0}/c\\ {1}"'.format(find, replace)
         path = "{0}/config/honeycomb.json".format(Const.REMOTE_HC_DIR)
         command = "sed -i {0} {1}".format(argument, path)
 
         ssh = SSH()
-        for node in nodes:
-            if node['type'] == NodeType.DUT:
-                ssh.connect(node)
-                (ret_code, _, stderr) = ssh.exec_command_sudo(command)
-                if ret_code != 0:
-                    raise HoneycombError("Failed to modify configuration on "
-                                         "node {0}, {1}".format(node, stderr))
+        ssh.connect(node)
+        (ret_code, _, stderr) = ssh.exec_command_sudo(command)
+        if ret_code != 0:
+            raise HoneycombError("Failed to modify configuration on "
+                                 "node {0}, {1}".format(node, stderr))
+
+    @staticmethod
+    def configure_jvpp_timeout(node, timeout=10):
+        """Configure timeout value for Java API commands Honeycomb sends to VPP.
+
+         :param node: Information about a DUT node.
+         :param timeout: Timeout value in seconds.
+         :type node: dict
+         :type timeout: int
+         :raises HoneycombError: If the configuration could not be changed.
+         """
+
+        find = "jvpp-request-timeout"
+        replace = '\\"jvpp-request-timeout\\": {0}'.format(timeout)
+
+        argument = '"/{0}/c\\ {1}"'.format(find, replace)
+        path = "{0}/config/jvpp.json".format(Const.REMOTE_HC_DIR)
+        command = "sed -i {0} {1}".format(argument, path)
+
+        ssh = SSH()
+        ssh.connect(node)
+        (ret_code, _, stderr) = ssh.exec_command_sudo(command)
+        if ret_code != 0:
+            raise HoneycombError("Failed to modify configuration on "
+                                 "node {0}, {1}".format(node, stderr))
 
     @staticmethod
     def print_environment(nodes):
@@ -298,16 +327,19 @@ class HoneycombSetup(object):
                                  "node {0}, {1}".format(node, stderr))
 
     @staticmethod
-    def enable_module_features(node, *features):
-        """Configure Honeycomb to use VPP modules that are disabled by default.
+    def manage_honeycomb_features(node, feature, disable=False):
+        """Configure Honeycomb to use features that are disabled by default, or
+        disable previously enabled features.
 
         ..Note:: If the module is not enabled in VPP, Honeycomb will
         be unable to establish VPP connection.
 
         :param node: Honeycomb node.
-        :param features: Features to enable.
+        :param feature: Feature to enable.
+        :param disable: Disable the specified feature instead of enabling it.
         :type node: dict
-        :type features: string
+        :type feature: string
+        :type disable: bool
         :raises HoneycombError: If the configuration could not be changed.
          """
 
@@ -318,23 +350,24 @@ class HoneycombSetup(object):
         ssh = SSH()
         ssh.connect(node)
 
-        for feature in features:
-            if feature in disabled_features.keys():
-                # uncomment by replacing the entire line
-                find = replace = "{0}".format(disabled_features[feature])
-
-                argument = '"/{0}/c\\ {1}"'.format(find, replace)
-                path = "{0}/modules/*module-config"\
-                    .format(Const.REMOTE_HC_DIR)
-                command = "sed -i {0} {1}".format(argument, path)
-
-                (ret_code, _, stderr) = ssh.exec_command_sudo(command)
-                if ret_code != 0:
-                    raise HoneycombError("Failed to modify configuration on "
-                                         "node {0}, {1}".format(node, stderr))
-            else:
-                raise HoneycombError(
-                    "Unrecognized feature {0}.".format(feature))
+        if feature in disabled_features.keys():
+            # uncomment by replacing the entire line
+            find = replace = "{0}".format(disabled_features[feature])
+            if disable:
+                replace = "// {0}".format(find)
+
+            argument = '"/{0}/c\\ {1}"'.format(find, replace)
+            path = "{0}/modules/*module-config"\
+                .format(Const.REMOTE_HC_DIR)
+            command = "sed -i {0} {1}".format(argument, path)
+
+            (ret_code, _, stderr) = ssh.exec_command_sudo(command)
+            if ret_code != 0:
+                raise HoneycombError("Failed to modify configuration on "
+                                     "node {0}, {1}".format(node, stderr))
+        else:
+            raise HoneycombError(
+                "Unrecognized feature {0}.".format(feature))
 
     @staticmethod
     def copy_java_libraries(node):
@@ -365,10 +398,199 @@ class HoneycombSetup(object):
             directory = "{0}/lib/io/fd/vpp/{1}/{2}".format(
                 Const.REMOTE_HC_DIR, artifact_id, version)
             cmd = "sudo mkdir -p {0}; " \
-                  "sudo cp /usr/share/java/{0} {1}/{2}-{3}.jar".format(
-                    item, directory, artifact_id, version)
+                  "sudo cp /usr/share/java/{1} {0}/{2}-{3}.jar".format(
+                      directory, item, artifact_id, version)
 
             (ret_code, _, stderr) = ssh.exec_command(cmd)
             if ret_code != 0:
                 raise HoneycombError("Failed to copy JVPP libraries on "
                                      "node {0}, {1}".format(node, stderr))
+
+    @staticmethod
+    def copy_odl_client(node, odl_name, src_path, dst_path):
+        """Copy ODL Client from source path to destination path.
+
+        :param node: Honeycomb node.
+        :param odl_name: Name of ODL client version to use.
+        :param src_path: Source Path where to find ODl client.
+        :param dst_path: Destination path.
+        :type node: dict
+        :type odl_name: str
+        :type src_path: str
+        :type dst_path: str
+        :raises HoneycombError: If the operation fails.
+        """
+
+        ssh = SSH()
+        ssh.connect(node)
+
+        cmd = "cp -r {src}/*karaf_{odl_name}* {dst}".format(
+            src=src_path, odl_name=odl_name, dst=dst_path)
+
+        ret_code, _, _ = ssh.exec_command(cmd, timeout=30)
+        if int(ret_code) != 0:
+            raise HoneycombError(
+                "Failed to copy ODL client on node {0}".format(node["host"]))
+
+    @staticmethod
+    def setup_odl_client(node, path):
+        """Start ODL client on the specified node.
+
+        Karaf should be located in the provided path, and VPP and Honeycomb
+        should already be running, otherwise the start will fail.
+        :param node: Node to start ODL client on.
+        :param path: Path to ODL client on node.
+        :type node: dict
+        :type path: str
+        :raises HoneycombError: If Honeycomb fails to start.
+        """
+
+        logger.console("\nStarting ODL client ...")
+        ssh = SSH()
+        ssh.connect(node)
+
+        cmd = "{path}/*karaf*/bin/start clean".format(path=path)
+        ret_code, _, _ = ssh.exec_command_sudo(cmd)
+
+        if int(ret_code) != 0:
+            raise HoneycombError('Node {0} failed to start ODL.'.
+                                 format(node['host']))
+        else:
+            logger.info("Starting the ODL client on node {0} is "
+                        "in progress ...".format(node['host']))
+
+    @staticmethod
+    def install_odl_features(node, path, *features):
+        """Install required features on a running ODL client.
+
+        :param node: Honeycomb node.
+        :param path: Path to ODL client on node.
+        :param features: Optional, list of additional features to install.
+        :type node: dict
+        :type path: str
+        :type features: list
+        """
+
+        ssh = SSH()
+        ssh.connect(node)
+
+        cmd = "{path}/*karaf*/bin/client -u karaf feature:install " \
+              "odl-restconf-all odl-netconf-connector-all".format(path=path)
+        for feature in features:
+            cmd += " {0}".format(feature)
+
+        ret_code, _, stderr = ssh.exec_command_sudo(cmd, timeout=120)
+
+        if int(ret_code) != 0:
+            raise HoneycombError("Feature install did not succeed.")
+
+    @staticmethod
+    def check_odl_startup_state(node):
+        """Check the status of ODL client startup.
+
+        :param node: Honeycomb node.
+        :param node: dict
+        :returns: True when ODL is started.
+        :rtype: bool
+        :raises HoneycombError: When the response is not code 200: OK.
+        """
+
+        path = HcUtil.read_path_from_url_file(
+            "odl_client/odl_netconf_connector")
+        expected_status_codes = (HTTPCodes.UNAUTHORIZED,
+                                 HTTPCodes.FORBIDDEN,
+                                 HTTPCodes.NOT_FOUND,
+                                 HTTPCodes.SERVICE_UNAVAILABLE,
+                                 HTTPCodes.INTERNAL_SERVER_ERROR)
+
+        status_code, _ = HTTPRequest.get(node, path, timeout=10,
+                                         enable_logging=False)
+        if status_code == HTTPCodes.OK:
+            logger.info("ODL client on node {0} is up and running".
+                        format(node['host']))
+        elif status_code in expected_status_codes:
+            if status_code == HTTPCodes.UNAUTHORIZED:
+                logger.info('Unauthorized. If this triggers keyword '
+                            'timeout, verify username and password.')
+            raise HoneycombError('ODL client on node {0} running but '
+                                 'not yet ready.'.format(node['host']),
+                                 enable_logging=False)
+        else:
+            raise HoneycombError('Unexpected return code: {0}.'.
+                                 format(status_code))
+        return True
+
+    @staticmethod
+    def mount_honeycomb_on_odl(node):
+        """Tell ODL client to mount Honeycomb instance over netconf.
+
+        :param node: Honeycomb node.
+        :type node: dict
+        :raises HoneycombError: When the response is not code 200: OK.
+        """
+
+        path = HcUtil.read_path_from_url_file(
+            "odl_client/odl_netconf_connector")
+
+        url_file = "{0}/{1}".format(Const.RESOURCES_TPL_HC,
+                                    "odl_client/mount_honeycomb.xml")
+
+        with open(url_file) as template:
+            data = template.read()
+
+        status_code, _ = HTTPRequest.post(
+            node, path, headers={"Content-Type": "application/xml"},
+            payload=data, timeout=10, enable_logging=False)
+
+        if status_code == HTTPCodes.OK:
+            logger.info("ODL mount point configured successfully.")
+        elif status_code == HTTPCodes.CONFLICT:
+            logger.info("ODL mount point was already configured.")
+        else:
+            raise HoneycombError('Mount point configuration not successful')
+
+    @staticmethod
+    def stop_odl_client(node, path):
+        """Stop ODL client service on the specified node.
+
+        :param node: Node to start ODL client on.
+        :param path: Path to ODL client.
+        :type node: dict
+        :type path: str
+        :raises HoneycombError: If ODL client fails to stop.
+        """
+
+        ssh = SSH()
+        ssh.connect(node)
+
+        cmd = "{0}/*karaf*/bin/stop".format(path)
+
+        ssh = SSH()
+        ssh.connect(node)
+        ret_code, _, _ = ssh.exec_command_sudo(cmd)
+        if int(ret_code) != 0:
+            logger.warn("ODL Client refused to shut down.")
+            cmd = "pkill -f 'karaf'"
+            (ret_code, _, _) = ssh.exec_command_sudo(cmd)
+            if int(ret_code) != 0:
+                raise HoneycombError('Node {0} failed to stop ODL.'.
+                                     format(node['host']))
+
+        logger.info("ODL client service stopped.")
+
+    @staticmethod
+    def stop_vpp_service(node):
+        """Stop VPP service on the specified node.
+
+        :param node: VPP node.
+        :type node: dict
+        :raises RuntimeError: If VPP fails to stop.
+        """
+
+        ssh = SSH()
+        ssh.connect(node)
+        cmd = "service vpp stop"
+        ret_code, _, _ = ssh.exec_command_sudo(cmd)
+        if int(ret_code) != 0:
+            raise RuntimeError("Could not stop VPP service on node {0}".format(
+                node['host']))