From 40027c1ecab6eb998d3ffd5294683c2f384422ec Mon Sep 17 00:00:00 2001 From: Peter Mikus Date: Mon, 3 Jul 2017 17:11:53 +0000 Subject: [PATCH] FIX: TLDK bootstrap to reflect latest changes on VIRL Change-Id: I73017e17543bda145c470cdc474bce4eda547742 Signed-off-by: Peter Mikus --- resources/tools/virl/bin/start-testcase-TLDK | 303 +++++++++++++++++++++------ 1 file changed, 236 insertions(+), 67 deletions(-) diff --git a/resources/tools/virl/bin/start-testcase-TLDK b/resources/tools/virl/bin/start-testcase-TLDK index 263e8beb90..f6021dacbd 100755 --- a/resources/tools/virl/bin/start-testcase-TLDK +++ b/resources/tools/virl/bin/start-testcase-TLDK @@ -1,6 +1,6 @@ #!/usr/bin/python -# 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,33 +13,118 @@ # See the License for the specific language governing permissions and # limitations under the License. -__author__ = 'fangyinx.hu@intel.com' +"""This script is handling starting of VIRL simulations.""" -import sys -import requests -import re -import os import argparse -import tempfile +import netifaces +import os +import paramiko +import random +import re import shutil +import sys +import tempfile import time -import paramiko -import netifaces -# -# Helper function to indent a text string -# +import requests + +IPS_PER_SIMULATION = 5 + def indent(lines, amount, fillchar=' '): + """Indent the string by amount of fill chars. + + :param lines: String to indent. + :param amount: Number of fill chars. + :param fillchar: Filling character. + :type lines: str + :type amount: int + :type fillchar: str + :returns: Indented string. + :rtype: str + """ padding = amount * fillchar return padding + ('\n'+padding).join(lines.split('\n')) +def print_to_stderr(msg, end='\n'): + """Writes any text to stderr. + + :param msg: Message to print. + :param end: By default print new line at the end. + :type msg: str + :type end: str + """ + try: + sys.stderr.write(str(msg) + end) + except ValueError: + pass + +def get_assigned_interfaces(args, network="flat"): + """Retrieve assigned interfaces in openstack network. + + :param args: Command line params. + :param network: Openstack network. + :type args: ArgumentParser + :type network: str + :returns: Assigned interfaces. + :rtype: list + :raises RuntimeError: If response is not 200. + """ + req = requests.get('http://{}/openstack/rest/ports/{}' + .format(args.virl_ip, network), + auth=(args.username, args.password)) + if req.status_code == 200: + return req.json() + else: + raise RuntimeError("ERROR: Retrieving ports in use - " + "Status other than 200 HTTP OK:\n{}" + .format(req.content)) + +def get_assigned_interfaces_count(args, network="flat"): + """Count assigned interfaces in openstack network. + + :param args: Command line params. + :param network: Openstack network. + :type args: ArgumentParser + :type network: str + :returns: Assigned interfaces count. + :rtype: int + """ + return len(get_assigned_interfaces(args, network=network)) + +def check_ip_addresses(args): + """Check IP address availability. + + :param args: Command line params. + :type args: ArgumentParser + :raises RuntimeError: If not enough free addresses available. + """ + for i in range(args.wait_count): + if args.quota - \ + get_assigned_interfaces_count(args) >= IPS_PER_SIMULATION: + break + if args.verbosity >= 2: + print_to_stderr("DEBUG: - Attempt {} out of {}, waiting for free " + "IP addresses".format(i, args.wait_count)) + # Wait random amount of time within range 1-3 minutes + time.sleep(random.randint(60, 180)) + else: + raise RuntimeError("ERROR: Not enough IP addresses to run simulation") + +def check_virl_resources(args): + """Check virl resources availability. + + :param args: Command line params. + :type args: ArgumentParser + """ + check_ip_addresses(args) + # -# Main function. # FIXME: Right now, this is really coded like a shell script, as one big # function executed in sequence. This should be broken down into multiple # functions. # def main(): + """ Main function.""" # # Get our default interface IP address. This will become the default # value for the "NFS Server IP" option. @@ -82,10 +167,14 @@ def main(): default="127.0.0.1:19399") parser.add_argument("-u", "--username", help="VIRL username", default="tb4-virl") + parser.add_argument("-au", "--admin-username", help="VIRL admin username", + default="uwmadmin") parser.add_argument("-p", "--password", help="VIRL password", default="Cisco1234") parser.add_argument("-su", "--ssh-user", help="SSH username", default="cisco") + parser.add_argument("-e", "--expiry", help="Simulation expiry", + default="120") parser.add_argument("-spr", "--ssh-privkey", help="SSH private keyfile", default="/home/jenkins-in/.ssh/id_rsa_virl") parser.add_argument("-spu", "--ssh-pubkey", help="SSH public keyfile", @@ -95,6 +184,9 @@ def main(): default="csit-ubuntu-14.04.4_2016-05-25_1.0") parser.add_argument("--topology-directory", help="Topology directory", default="/home/jenkins-in/testcase-infra/topologies") + parser.add_argument("-q", "--quota", + help="VIRL quota for max number of allowed IPs", + type=int, default=74) args = parser.parse_args() @@ -102,7 +194,8 @@ def main(): # Check if topology and template exist # if args.verbosity >= 2: - print "DEBUG: Running with topology {}".format(args.topology) + print_to_stderr("DEBUG: Running with topology {}" + .format(args.topology)) topology_virl_filename = os.path.join(args.topology_directory, args.topology + ".virl") @@ -110,12 +203,12 @@ def main(): args.topology + ".yaml") if not os.path.isfile(topology_virl_filename): - print "ERROR: Topology VIRL file {} does not exist".\ - format(topology_virl_filename) + print_to_stderr("ERROR: Topology VIRL file {} does not exist" + .format(topology_virl_filename)) sys.exit(1) if not os.path.isfile(topology_yaml_filename): - print "ERROR: Topology YAML file {} does not exist".\ - format(topology_yaml_filename) + print_to_stderr("ERROR: Topology YAML file {} does not exist" + .format(topology_yaml_filename)) sys.exit(1) # @@ -123,9 +216,11 @@ def main(): # for package in args.packages: if args.verbosity >= 2: - print "DEBUG: Checking if file {} exists".format(package) + print_to_stderr("DEBUG: Checking if file {} exists" + .format(package)) if not os.path.isfile(package): - print "ERROR: TLDK package {} does not exist.".format(package) + print_to_stderr("ERROR: TLDK package {} does not exist." + .format(package)) sys.exit(1) # @@ -149,33 +244,68 @@ def main(): os.close(temp_handle) try: - new_file = open(temp_topology, 'rb') - headers = {'Content-Type': 'text/xml'} + data = open(temp_topology, 'rb') + check_virl_resources(args) req = requests.post('http://' + args.virl_ip + '/simengine/rest/launch', - headers=headers, - auth=(args.username, args.password), data=new_file) + auth=(args.username, args.password), + data=data) if args.verbosity >= 2: - print "DEBUG: - Response Code {}".format(req.status_code) + print_to_stderr("DEBUG: - Request URL {}" + .format(req.url)) + print_to_stderr("{}" + .format(req.text)) + print_to_stderr("DEBUG: - Response Code {}" + .format(req.status_code)) new_file.close() - - except: - print "ERROR: Launching VIRL simulation - received invalid response" - print req - os.remove(temp_topology) - sys.exit(1) - - if req.status_code != 200: - print "ERROR: Launching VIRL simulation - received status other " + \ - "than 200 HTTP OK" - print "Status was: {} \n".format(req.status_code) - print "Response content was: " - print req.content + if req.status_code != 200: + raise RuntimeError("ERROR: Launching VIRL simulation - " + "Status other than 200 HTTP OK:\n{}" + .format(req.content)) + except (requests.exceptions.RequestException, + RuntimeError) as ex_error: + print_to_stderr(ex_error) os.remove(temp_topology) sys.exit(1) # If we got here, we had a good response. The response content is the # session ID. session_id = req.content + if args.verbosity >= 1: + print_to_stderr("DEBUG: VIRL simulation session-id: {}" + .format(session_id)) + + # Set session expiry to autokill sessions if not done from jenkins + if not args.keep: + if args.verbosity >= 1: + print_to_stderr("DEBUG: Setting expire for session-id: {}" + .format(session_id)) + try: + req = requests.put('http://' + args.virl_ip + + '/simengine/rest/admin-update/' + session_id + + '/expiry', + auth=(args.admin_username, args.password), + params={'user': args.username, + 'expires': args.expiry}) + if args.verbosity >= 2: + print_to_stderr("DEBUG: - Request URL {}" + .format(req.url)) + print_to_stderr("{}" + .format(req.text)) + print_to_stderr("DEBUG: - Response Code {}" + .format(req.status_code)) + if req.status_code != 200: + raise RuntimeError("ERROR: Setting expiry to simulation - " + "Status other than 200 HTTP OK:\n{}" + .format(req.content)) + except (requests.exceptions.RequestException, + RuntimeError) as ex_error: + print_to_stderr(ex_error) + req = requests.get('http://' + args.virl_ip + + '/simengine/rest/stop/' + session_id, + auth=(args.username, args.password)) + os.remove(temp_topology) + print "{}".format(session_id) + sys.exit(1) # # Create simulation scratch directory. Move topology file into that @@ -198,7 +328,7 @@ def main(): # Wait for simulation to become active # if args.verbosity >= 1: - print "DEBUG: Waiting for simulation to become active" + print_to_stderr("DEBUG: Waiting for simulation to become active") sim_is_started = False nodelist = [] @@ -227,32 +357,64 @@ def main(): if data[session_id][key]['state'] == "ACTIVE": active += 1 if args.verbosity >= 2: - print "DEBUG: - Attempt {} out of {}, total {} hosts, {} active".\ - format(args.wait_count-count, args.wait_count, total, active) + print_to_stderr("DEBUG: - Attempt {} out of {}, total {} hosts, " + "{} active".format(args.wait_count-count, + args.wait_count, total, active)) if active == total: sim_is_started = True if not sim_is_started: - print "ERROR: Simulation started OK but devices never changed to " + \ - "ACTIVE state" - print "Last VIRL response:" - print data + print_to_stderr("ERROR: Simulation nodes never changed to ACTIVE state") + print_to_stderr("Last VIRL response:") + print_to_stderr(data) if not args.keep: - shutil.rmtree(scratch_directory) req = requests.get('http://' + args.virl_ip + '/simengine/rest/stop/' + session_id, auth=(args.username, args.password)) + try: + shutil.rmtree(scratch_directory) + except shutil.Error: + print_to_stderr("ERROR: Removing scratch directory") + print "{}".format(session_id) + sys.exit(1) if args.verbosity >= 2: - print "DEBUG: Nodes: " + ", ".join(nodelist) + print_to_stderr("DEBUG: Nodes: {}" + .format(", ".join(nodelist))) # # Fetch simulation's IPs and create files # (ansible hosts file, topology YAML file) # - req = requests.get('http://' + args.virl_ip + - '/simengine/rest/interfaces/' + session_id + - '?fetch-state=1', auth=(args.username, args.password)) + try: + req = requests.get('http://' + args.virl_ip + + '/simengine/rest/interfaces/' + session_id, + auth=(args.username, args.password), + params={'fetch-state': '1'}) + if args.verbosity >= 2: + print_to_stderr("DEBUG: - Request URL {}" + .format(req.url)) + print_to_stderr("DEBUG: - Request Text") + print_to_stderr("{}".format(req.text)) + print_to_stderr("DEBUG: - Response Code {}" + .format(req.status_code)) + if req.status_code != 200: + raise RuntimeError("ERROR:Fetching IP's of simulation - " + "Status other than 200 HTTP OK:\n{}" + .format(req.content)) + except (requests.exceptions.RequestException, + RuntimeError) as ex_error: + print_to_stderr(ex_error) + if not args.keep: + req = requests.get('http://' + args.virl_ip + + '/simengine/rest/stop/' + session_id, + auth=(args.username, args.password)) + try: + shutil.rmtree(scratch_directory) + except shutil.Error: + print_to_stderr("ERROR: Removing scratch directory") + print "{}".format(session_id) + sys.exit(1) data = req.json() # Populate node addresses @@ -265,8 +427,8 @@ def main(): nodeaddrs[nodetype][key] = re.split('\\/', \ data[session_id][key]['management']['ip-address'])[0] if args.verbosity >= 2: - print "DEBUG: Node {} is of type {} and has management IP {}".\ - format(key, nodetype, nodeaddrs[nodetype][key]) + print_to_stderr("DEBUG: Node {} is of type {} and has mgmt IP {}" + .format(key, nodetype, nodeaddrs[nodetype][key])) topology[key] = {} for key2 in data[session_id][key]: @@ -298,7 +460,7 @@ def main(): # Wait for hosts to become reachable over SSH # if args.verbosity >= 1: - print "DEBUG: Waiting for hosts to become reachable using SSH" + print_to_stderr("DEBUG: Waiting for hosts to become reachable over SSH") missing = -1 count = args.wait_count @@ -311,30 +473,37 @@ def main(): if not os.path.exists(os.path.join(scratch_directory, key)): missing += 1 if args.verbosity >= 2: - print "DEBUG: - Attempt {} out of {}, waiting for {} hosts".\ - format(args.wait_count-count, args.wait_count, missing) + print_to_stderr("DEBUG: Attempt {} out of {}, waiting for {} hosts" + .format(args.wait_count-count, args.wait_count, + missing)) if missing != 0: - print "ERROR: Simulation started OK but {} hosts ".format(missing) + \ - "never mounted their NFS directory" + print_to_stderr("ERROR: Simulation started OK but {} hosts never " + "mounted their NFS directory".format(missing)) if not args.keep: - shutil.rmtree(scratch_directory) req = requests.get('http://' + args.virl_ip + '/simengine/rest/stop/' + session_id, auth=(args.username, args.password)) + try: + shutil.rmtree(scratch_directory) + except shutil.Error: + print_to_stderr("ERROR: Removing scratch directory") + print "{}".format(session_id) + sys.exit(1) # # just decompress the TLDK tar packages # if args.verbosity >= 1: - print "DEBUG: Uprading TLDK" + print_to_stderr("DEBUG: Uprading TLDK") for key1 in nodeaddrs: if not key1 == 'tg': for key2 in nodeaddrs[key1]: ipaddr = nodeaddrs[key1][key2] if args.verbosity >= 2: - print "DEBUG: Upgrading TLDK on node {}".format(ipaddr) + print_to_stderr("DEBUG: Upgrading TLDK on node {}" + .format(ipaddr)) paramiko.util.log_to_file(os.path.join(scratch_directory, "ssh.log")) client = paramiko.SSHClient() @@ -343,15 +512,15 @@ def main(): client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) client.connect(ipaddr, username=args.ssh_user, key_filename=args.ssh_privkey) - stdin, stdout, stderr = \ + _, stdout, stderr = \ client.exec_command('cd /scratch/tldktest/ && sudo tar zxf tldk_depends.tar.gz') c_stdout = stdout.read() c_stderr = stderr.read() if args.verbosity >= 2: - print "DEBUG: Command output was:" - print c_stdout - print "DEBUG: Command stderr was:" - print c_stderr + print_to_stderr("DEBUG: Command output was:") + print_to_stderr(c_stdout) + print_to_stderr("DEBUG: Command stderr was:") + print_to_stderr(c_stderr) # # Write a file with timestamp to scratch directory. We can use this to track @@ -365,9 +534,9 @@ def main(): # Declare victory # if args.verbosity >= 1: - print "SESSION ID: {}".format(session_id) + print_to_stderr("SESSION ID: {}".format(session_id)) print "{}".format(session_id) if __name__ == "__main__": - sys.exit(main()) + sys.exit(main()) \ No newline at end of file -- 2.16.6