# # analyzer.py: # # This file implements the following: # 1. Analysis of the collected info # 2. Report any problems # 3. Report what is correct # import pprint import re import argparse import subprocess import json import os from itertools import combinations from common import settings, debug, get_router from common import load_json, get_subnet, is_network_public import yaml tick = '✔' cross = '✘' def get_vm_qrouters(info, vm): vms = info['vms'] if not vms.has_key(vm): return 'unknown' # Get IP of any of the VM's interfaces for ip in vms[vm]['interfaces'].keys(): break routers = [] subnet = get_subnet(ip) namespaces = info['namespaces'] for nms in namespaces.keys(): if re.search('^qrouter-', nms): if not namespaces[nms].has_key('interfaces'): continue for intf in namespaces[nms]['interfaces'].keys(): ip = namespaces[nms]['interfaces'][intf] if re.search(subnet, ip): routers.append(nms) return routers # Even if there is one qrouter namespace via which all ping tests passed, we # consider the ping test to be a success. def did_ping_test_pass(cmds): qrouter_result = True for qrouter in sorted(cmds.keys()): debug('Checking ping status in qrouter %s' % qrouter) qrouter_result = True for key in cmds[qrouter].keys(): (src_vm, dst_vm) = key for key2 in sorted(cmds[qrouter][key].keys()): (src_ip, dst_ip) = key2 result = cmds[qrouter][key][key2]['pass'] if not result: qrouter_result = False break # check the next namsepace, this one failed # if all ping passed via this qrouter, return true if qrouter_result: return qrouter_result # There was no qrouter via which all pings passed! return qrouter_result def run_ping_command(cmd, comment=''): debug('Running ' + comment + ': ' + cmd) return subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT, universal_newlines=True).replace('\t', ' ') def report_file_open(report_file): f = open(report_file, 'w') f.write('<html>\n') f.write('<head>\n') f.write( '<script type="text/javascript" src="{{ STATIC_URL }}/don/CollapsibleLists.js"></script>\n') f.write( '<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}/don/don.css">\n') f.write('<title>DON: Analysis Results</title>\n') f.write('</head>\n') f.write('<body onload=CollapsibleLists.apply()>\n') return f def report_file_close(file_handle): file_handle.write('</body>\n') file_handle.write('</html>\n') def print_ping_result(cmds, overall_result, info, comment=None): lines = [] lines.append('<h2>Ping Test Results</h2>\n') if comment: lines.append('<h3>%s</h3>\n' % comment) lines.append('<ul class="collapsibleList">\n') for qrouter in sorted(cmds.keys()): router = get_router(qrouter, info) lines.append(' <li>%s (Namespace [%s])\n' % (router, qrouter)) lines.append(' <ul class="collapsibleList">\n') for key in sorted(cmds[qrouter].keys()): (src_vm, dst_vm) = key lines.append(' <li>%s → %s\n' % (src_vm, dst_vm)) lines.append(' <ul class="collapsibleList">\n') for key2 in sorted(cmds[qrouter][key].keys()): (src_ip, dst_ip) = key2 result = cmds[qrouter][key][key2]['pass'] result_str = '<font class="fail">%s</font>' % cross if result: result_str = '<font class="pass">%s</font>' % tick lines.append(' <li>%15s → %15s %s\n' % (src_ip, dst_ip, result_str)) lines.append(' <ul class="collapsibleList">\n') if result: lines.append(' <pre class="pass">\n') else: lines.append(' <pre class="fail">\n') for line in cmds[qrouter][key][key2]['output'].split('\n'): lines.append(line + '\n') lines.append(' </pre>\n') lines.append(' </ul>\n') lines.append(' </li>\n') lines.append(' </ul>\n') lines.append(' </li>\n') lines.append(' </ul>\n') lines.append(' </li>\n') lines.append('</ul>\n') overall_str = '<font class="fail">%s</font>' % cross if overall_result: overall_str = '<font class="pass">%s</font>' % tick lines.append('OVERALL RESULT: %s\n' % overall_str) return lines def get_vm_credentials(config_file='credentials.yaml'): try: with open(config_file, 'r') as s: return yaml.safe_load(s) except IOError, e: print '%s :%s' % (e.args[1], config_file) raise def test_ping(info): debug('Running ping test') vms = info['vms'] vm_credentials = get_vm_credentials() for vm in sorted(vms.keys()): vms[vm]['qrouter'] = get_vm_qrouters(info, vm) vm_pairs = list(combinations(sorted(vms.keys()), 2)) pprint.pprint(vm_pairs) cmds = {} for (src_vm, dst_vm) in vm_pairs: for qrouter in vms[src_vm]['qrouter']: debug(qrouter) if not cmds.has_key(qrouter): cmds[qrouter] = {} cmds[qrouter][(src_vm, dst_vm)] = {} for src_ip in vms[src_vm]['interfaces'].keys(): if is_network_public(src_ip, src_vm, info): continue for dst_ip in vms[dst_vm]['interfaces'].keys(): if is_network_public(dst_ip, dst_vm, info): continue username = vm_credentials.get( src_vm, vm_credentials['default'])['username'] passwd = vm_credentials.get(src_vm, vm_credentials[ 'default'])['password'] cmd = 'sudo ip netns exec ' + qrouter cmd += ' python ping.py --src_ip %s --dst_ip %s --username "%s" --passwd "%s" --count %d --timeout %d' % \ (src_ip, dst_ip, username, passwd, 1, 2) comment = 'Ping [%s (%s) => %s (%s)]' % ( src_vm, src_ip, dst_vm, dst_ip) output = run_ping_command(cmd, comment=comment) a = json.loads(output) success = a['pass'] cmds[qrouter][(src_vm, dst_vm)][(src_ip, dst_ip)] = { 'cmd': cmd, 'output': output, 'pass': success } debug(pprint.pformat(cmds)) ping_test_passed = did_ping_test_pass(cmds) return (ping_test_passed, cmds) def run_ovs_command(cmd, comment=''): debug('Running ' + comment + ': ' + cmd) return subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT, universal_newlines=True).replace('\t', ' ') def process_ovs_output(output): for line in output: if re.search('PASS', line): return True return False def print_ovs_result(cmds, overall_result, info, comment=None): lines = [] lines.append('<h2>OVS Test Results</h2>\n') if comment: lines.append('<h3>%s</h3>\n' % comment) lines.append('<ul class="collapsibleList">\n') lines.append(' <li>OVS bridge br-int\n') lines.append(' <ul class="collapsibleList">\n') for tag in sorted(cmds.keys()): lines.append(' <li>tag %s\n' % (tag)) lines.append(' <ul class="collapsibleList">\n') for key in sorted(cmds[tag].keys()): (src_port, dst_port) = key result = cmds[tag][key]['pass'] result_str = '<font class="fail">%s</font>' % cross if result: result_str = '<font class="pass">%s</font>' % tick lines.append(' <li>%3s → %3s %s\n' % (src_port, dst_port, result_str)) lines.append(' <ul class="collapsibleList">\n') if result: lines.append(' <pre class="pass">\n') else: lines.append(' <pre class="fail">\n') for line in cmds[tag][key]['output'].split('\n'): lines.append(line + '\n') lines.append(' </ul>\n') lines.append(' </li>\n') lines.append(' </ul>\n') lines.append(' </li>\n') lines.append(' </ul>\n') lines.append(' </li>\n') lines.append('</ul>\n') overall_str = '<font class="fail">%s</font>' % cross if overall_result: overall_str = '<font class="pass">%s</font>' % tick lines.append('OVERALL RESULT: %s\n' % overall_str) return lines def test_ovs(info): debug('Running OVS test') ovs_test_passed = True cmds = {} # first construct a dictionary of tag to br-int ports br_int_ports = info['bridges']['br-int']['ports'] tag_to_port = {} for port in br_int_ports.keys(): if not re.search('^qvo', port): continue tag = br_int_ports[port]['tag'] port_id = br_int_ports[port]['id'] if not tag_to_port.has_key(tag): tag_to_port[tag] = [] tag_to_port[tag].append((port_id, port)) debug(pprint.pformat(tag_to_port)) for tag in sorted(tag_to_port.keys(), key=lambda x: int(x)): cmds[tag] = {} port_count = len(tag_to_port[tag]) if port_count < 2: debug('tag %s is used by single port %s. Skipping test!' % (tag, tag_to_port[tag][0])) continue port_list = list(map(lambda (x, y): x, tag_to_port[tag])) sorted_port_list = sorted(port_list, key=lambda x: int(x)) port_pairs = list(combinations(sorted_port_list, 2)) for (src_port, dst_port) in port_pairs: cmds[tag][(src_port, dst_port)] = {} cmd = '' cmd += 'python ovs.py --src_port_id %s --dst_port_id %s --tag %s --ovs_bridge br-int' % \ (src_port, dst_port, tag) comment = 'ovs [tag: %s port %s => %s' % (tag, src_port, dst_port) output = run_ovs_command(cmd, comment=comment) success = process_ovs_output(output) if not success: ovs_test_passed = False cmds[tag][(src_port, dst_port)] = { 'cmd': cmd, 'output': output, 'pass': success } return (ovs_test_passed, cmds) # Dictionary of tests test_suite = { 'ping': { 'help': 'Ping test between all pairs of VMs', 'func': test_ping, 'result': 'not run', 'formatter': print_ping_result, 'html': None, }, 'ovs': { 'help': 'OVS test between all pairs of ports using the same tag in br-int', 'func': test_ovs, 'result': 'not run', 'formatter': print_ovs_result, 'html': None, }, } def analyze(json_filename, params): settings['debug'] = True BASE_DIR = os.path.dirname(os.path.dirname(__file__)) CUR_DIR = os.getcwd() os.chdir(BASE_DIR + '/ovs') # NEW_DIR = os.getcwd() # return BASE_DIR + ':' + CUR_DIR + ':' + NEW_DIR debug('This is what I am going to analyze') my_info = load_json(json_filename) for test in test_suite.keys(): flag = 'test:' + test if params[flag] or params['test:all']: (result, cmds) = test_suite[test]['func'](my_info) if result: test_suite[test]['result'] = 'PASS' else: test_suite[test]['result'] = 'FAIL' lines = test_suite[test]['formatter'](cmds, result, my_info, test_suite[test]['help']) test_suite[test]['html'] = lines debug(params['test:report_file']) f = report_file_open(params['test:report_file']) for test in test_suite.keys(): if test_suite[test]['html']: for line in test_suite[test]['html']: f.write(line) report_file_close(f) os.chdir(CUR_DIR) def check_args(): parser = argparse.ArgumentParser(description='Static analysis of output of commands', formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument('--debug', dest='debug', help='Enable debugging', default=True, action='store_true') parser.add_argument('--info_file', dest='info_file', help='Info is read in JSON format in this file', default="don.json", type=str) parser.add_argument('--ping', dest='ping', help='ping test between all VMs', default=False, action='store_true') parser.add_argument('--ping_count', dest='ping_count', help='how many ping packets to send', default=2, type=int) parser.add_argument('--ping_timeout', dest='ping_timeout', help='ping timeout period in seconds', default=2, type=int) parser.add_argument('--ovs', dest='ovs', help='ovs test between ports using same tag in br-int', default=False, action='store_true') parser.add_argument('--test_all', dest='test_all', help='Perform all tests in test suite', default=False, action='store_true') parser.add_argument('--error_file', dest='error_file', help='All errors will be reported to this file', type=str, default='don.error.txt') parser.add_argument('--report_file', dest='report_file', help='Report will be written in this file in HTML format', type=str, default='don.report.html') args = parser.parse_args() settings['debug'] = args.debug settings['info_file'] = args.info_file settings['error_file'] = args.error_file settings['test:all'] = args.test_all settings['test:ping'] = args.test_all or args.ping settings['test:ping_count'] = args.ping_count settings['test:ping_timeout'] = args.ping_timeout settings['test:ovs'] = args.test_all or args.ovs settings['test:report_file'] = args.report_file def main(): check_args() analyze(settings['info_file'], settings) pprint.pprint(test_suite) if __name__ == "__main__": main()