X-Git-Url: https://gerrit.fd.io/r/gitweb?p=csit.git;a=blobdiff_plain;f=resources%2Flibraries%2Fpython%2Fhoneycomb%2FHoneycombSetup.py;h=858aa2134402e4b72a83f995ceb22dbccbde645f;hp=e9c129526028c08a0a4b27369589e51836f22df6;hb=bd680a762d945b7970f7e62b7ee47776f0301f8c;hpb=c37f394a29165f839c3032e7f9485e35fb3307f2 diff --git a/resources/libraries/python/honeycomb/HoneycombSetup.py b/resources/libraries/python/honeycomb/HoneycombSetup.py index e9c1295260..858aa21344 100644 --- a/resources/libraries/python/honeycomb/HoneycombSetup.py +++ b/resources/libraries/python/honeycomb/HoneycombSetup.py @@ -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, \ @@ -53,9 +55,12 @@ class HoneycombSetup(object): :type nodes: list :raises HoneycombError: If Honeycomb fails to start. """ - logger.console("Starting Honeycomb service ...") - cmd = "{0}/start".format(Const.REMOTE_HC_DIR) + HoneycombSetup.print_environment(nodes) + + logger.console("\nStarting Honeycomb service ...") + + cmd = "sudo service honeycomb start" for node in nodes: if node['type'] == NodeType.DUT: @@ -81,9 +86,9 @@ class HoneycombSetup(object): :type nodes: list :raises HoneycombError: If Honeycomb failed to stop. """ - logger.console("Shutting down Honeycomb service ...") + logger.console("\nShutting down Honeycomb service ...") - cmd = "{0}/stop".format(Const.REMOTE_HC_DIR) + cmd = "sudo service honeycomb stop" errors = [] for node in nodes: @@ -100,6 +105,36 @@ class HoneycombSetup(object): raise HoneycombError('Node(s) {0} failed to stop Honeycomb.'. format(errors)) + @staticmethod + def restart_honeycomb_and_vpp_on_duts(*nodes): + """Restart the Honeycomb service on specified DUT nodes. + + Use the keyword "Check Honeycomb Startup State" to check when Honeycomb + is fully restarted. + :param nodes: List of nodes to restart Honeycomb on. + :type nodes: list + :raises HoneycombError: If Honeycomb failed to restart. + """ + logger.console("\nRestarting Honeycomb service ...") + + cmd = "sudo service honeycomb restart && sudo service vpp restart" + errors = [] + + for node in nodes: + if node['type'] == NodeType.DUT: + ssh = SSH() + ssh.connect(node) + (ret_code, _, _) = ssh.exec_command_sudo(cmd) + if int(ret_code) != 0: + errors.append(node['host']) + else: + logger.info("Restart of Honeycomb and VPP on node {0} is " + "in progress ...".format(node['host'])) + if errors: + raise HoneycombError('Node(s) {0} failed to restart Honeycomb' + ' and/or VPP.'. + format(errors)) + @staticmethod def check_honeycomb_startup_state(*nodes): """Check state of Honeycomb service during startup on specified nodes. @@ -123,6 +158,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, enable_logging=False) if status_code == HTTPCodes.OK: @@ -139,6 +175,13 @@ class HoneycombSetup(object): else: raise HoneycombError('Unexpected return code: {0}.'. format(status_code)) + + status_code, _ = HcUtil.get_honeycomb_data( + node, "config_vpp_interfaces") + if status_code != HTTPCodes.OK: + raise HoneycombError('Honeycomb on node {0} running but ' + 'not yet ready.'.format(node['host']), + enable_logging=False) return True @staticmethod @@ -153,7 +196,7 @@ class HoneycombSetup(object): :return: True if all GETs fail to connect. :rtype bool """ - cmd = "ps -ef | grep -v grep | grep karaf" + cmd = "ps -ef | grep -v grep | grep honeycomb" for node in nodes: if node['type'] == NodeType.DUT: try: @@ -185,3 +228,289 @@ class HoneycombSetup(object): logger.info("Honeycomb on node {0} has stopped". format(node['host'])) return True + + @staticmethod + def configure_restconf_binding_address(node): + """Configure Honeycomb to accept restconf requests from all IP + addresses. IP version is determined by node data. + + :param node: Information about a DUT node. + :type node: dict + :raises HoneycombError: If the configuration could not be changed. + """ + + find = "restconf-https-binding-address" + try: + IPv6Address(unicode(node["host"])) + # if management IP of the node is in IPv6 format + replace = '\\"restconf-https-binding-address\\": \\"0::0\\",' + except (AttributeError, AddressValueError): + replace = '\\"restconf-https-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() + 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): + """Print information about the nodes to log. The information is defined + by commands in cmds tuple at the beginning of this method. + + :param nodes: List of DUT nodes to get information about. + :type nodes: list + """ + + # TODO: When everything is set and running in VIRL env, transform this + # method to a keyword checking the environment. + + cmds = ("uname -a", + "df -lh", + "echo $JAVA_HOME", + "echo $PATH", + "which java", + "java -version", + "dpkg --list | grep openjdk", + "ls -la /opt/honeycomb") + + for node in nodes: + if node['type'] == NodeType.DUT: + logger.info("Checking node {} ...".format(node['host'])) + for cmd in cmds: + logger.info("Command: {}".format(cmd)) + ssh = SSH() + ssh.connect(node) + ssh.exec_command_sudo(cmd) + + @staticmethod + def print_ports(node): + """Uses "sudo netstat -anp | grep java" to print port where a java + application listens. + + :param node: Honeycomb node where we want to print the ports. + :type node: dict + """ + + cmds = ("netstat -anp | grep java", + "ps -ef | grep [h]oneycomb") + + logger.info("Checking node {} ...".format(node['host'])) + for cmd in cmds: + logger.info("Command: {}".format(cmd)) + ssh = SSH() + ssh.connect(node) + ssh.exec_command_sudo(cmd) + + @staticmethod + def configure_log_level(node, level): + """Set Honeycomb logging to the specified level. + + :param node: Honeycomb node. + :param level: Log level (INFO, DEBUG, TRACE). + :type node: dict + :type level: str + """ + + find = 'logger name=\\"io.fd\\"' + replace = ''.format(level) + + argument = '"/{0}/c\\ {1}"'.format(find, replace) + path = "{0}/config/logback.xml".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 enable_module_features(node, *features): + """Configure Honeycomb to use VPP modules that are disabled by default. + + ..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. + :type node: dict + :type features: string + :raises HoneycombError: If the configuration could not be changed. + """ + + disabled_features = { + "NSH": "io.fd.hc2vpp.vppnsh.impl.VppNshModule" + } + + 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)) + + @staticmethod + def copy_java_libraries(node): + """Copy Java libraries installed by vpp-api-java package to honeycomb + lib folder. + + This is a (temporary?) workaround for jvpp version mismatches. + + :param node: Honeycomb node + :type node: dict + """ + + ssh = SSH() + ssh.connect(node) + (_, stdout, _) = ssh.exec_command_sudo( + "ls /usr/share/java | grep ^jvpp-*") + + files = stdout.split("\n")[:-1] + for item in files: + # example filenames: + # jvpp-registry-17.04.jar + # jvpp-core-17.04.jar + + parts = item.split("-") + version = "{0}-SNAPSHOT".format(parts[2][:5]) + artifact_id = "{0}-{1}".format(parts[0], parts[1]) + + 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/{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 find_odl_client(node): + """Check if there is a karaf directory in home. + + :param node: Honeycomb node. + :type node: dict + :returns: True if ODL client is present on node, else False. + :rtype: bool + """ + + ssh = SSH() + ssh.connect(node) + (ret_code, stdout, _) = ssh.exec_command_sudo( + "ls ~ | grep karaf") + + logger.debug(stdout) + return not bool(ret_code) + + @staticmethod + def start_odl_client(node): + """Start ODL client on the specified node. + + karaf should be located in home directory, and VPP and Honeycomb should + already be running, otherwise the start will fail. + :param node: Nodes to start ODL client on. + :type node: dict + :raises HoneycombError: If Honeycomb fails to start. + """ + + logger.console("\nStarting ODL client ...") + + cmd = "~/*karaf*/bin/start" + + ssh = SSH() + ssh.connect(node) + (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 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.warn("ODL mount point was already configured.") + else: + raise HoneycombError('Mount point configuration not successful')