diff options
author | 2016-02-08 10:55:20 -0500 | |
---|---|---|
committer | 2016-02-08 10:55:20 -0500 | |
commit | 6107c1ca4aa485c5971ff3326513b8f4934f7ac1 (patch) | |
tree | a109d80501bd087e3219f68c186fb55bc17e090a /scripts/automation/trex_control_plane/stl | |
parent | f5a5e50bfe046148a20f6ce578d6082119dec2c0 (diff) |
huge refactor - again
Diffstat (limited to 'scripts/automation/trex_control_plane/stl')
29 files changed, 2519 insertions, 21 deletions
diff --git a/scripts/automation/trex_control_plane/stl/__init__.py b/scripts/automation/trex_control_plane/stl/console/__init__.py index e69de29b..e69de29b 100644 --- a/scripts/automation/trex_control_plane/stl/__init__.py +++ b/scripts/automation/trex_control_plane/stl/console/__init__.py diff --git a/scripts/automation/trex_control_plane/stl/console/trex_console.py b/scripts/automation/trex_control_plane/stl/console/trex_console.py new file mode 100755 index 00000000..789ad4ab --- /dev/null +++ b/scripts/automation/trex_control_plane/stl/console/trex_console.py @@ -0,0 +1,784 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Dan Klein, Itay Marom +Cisco Systems, Inc. + +Copyright (c) 2015-2015 Cisco Systems, Inc. +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 + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" +import subprocess +import cmd +import json +import ast +import argparse +import random +import readline +import string +import os +import sys +import tty, termios + +from trex_stl_lib.api import * + +from trex_stl_lib.utils.text_opts import * +from trex_stl_lib.utils.common import user_input, get_current_user +from trex_stl_lib.utils import parsing_opts + + +import trex_tui + +from functools import wraps + +__version__ = "1.1" + +# console custom logger +class ConsoleLogger(LoggerApi): + def __init__ (self): + self.prompt_redraw = None + + def write (self, msg, newline = True): + if newline: + print msg + else: + print msg, + + def flush (self): + sys.stdout.flush() + + # override this for the prompt fix + def async_log (self, msg, level = LoggerApi.VERBOSE_REGULAR, newline = True): + self.log(msg, level, newline) + if ( (self.level >= LoggerApi.VERBOSE_REGULAR) and self.prompt_redraw ): + self.prompt_redraw() + self.flush() + + +def set_window_always_on_top (title): + # we need the GDK module, if not available - ignroe this command + try: + import gtk.gdk + except ImportError: + return + + # search the window and set it as above + root = gtk.gdk.get_default_root_window() + + for id in root.property_get('_NET_CLIENT_LIST')[2]: + w = gtk.gdk.window_foreign_new(id) + if w: + name = w.property_get('WM_NAME')[2] + if name == title: + w.set_keep_above(True) + gtk.gdk.window_process_all_updates() + break + + +class TRexGeneralCmd(cmd.Cmd): + def __init__(self): + cmd.Cmd.__init__(self) + # configure history behaviour + self._history_file_dir = "/tmp/trex/console/" + self._history_file = self.get_history_file_full_path() + readline.set_history_length(100) + # load history, if any + self.load_console_history() + + + def get_console_identifier(self): + return self.__class__.__name__ + + def get_history_file_full_path(self): + return "{dir}{filename}.hist".format(dir=self._history_file_dir, + filename=self.get_console_identifier()) + + def load_console_history(self): + if os.path.exists(self._history_file): + readline.read_history_file(self._history_file) + return + + def save_console_history(self): + if not os.path.exists(self._history_file_dir): + # make the directory available for every user + try: + original_umask = os.umask(0) + os.makedirs(self._history_file_dir, mode = 0777) + finally: + os.umask(original_umask) + + + # os.mknod(self._history_file) + readline.write_history_file(self._history_file) + return + + def print_history (self): + + length = readline.get_current_history_length() + + for i in xrange(1, length + 1): + cmd = readline.get_history_item(i) + print "{:<5} {:}".format(i, cmd) + + def get_history_item (self, index): + length = readline.get_current_history_length() + if index > length: + print format_text("please select an index between {0} and {1}".format(0, length)) + return None + + return readline.get_history_item(index) + + + def emptyline(self): + """Called when an empty line is entered in response to the prompt. + + This overriding is such that when empty line is passed, **nothing happens**. + """ + return + + def completenames(self, text, *ignored): + """ + This overriding is such that a space is added to name completion. + """ + dotext = 'do_'+text + return [a[3:]+' ' for a in self.get_names() if a.startswith(dotext)] + + +# +# main console object +class TRexConsole(TRexGeneralCmd): + """Trex Console""" + + def __init__(self, stateless_client, verbose = False): + + self.stateless_client = stateless_client + + TRexGeneralCmd.__init__(self) + + self.tui = trex_tui.TrexTUI(stateless_client) + self.terminal = None + + self.verbose = verbose + + self.intro = "\n-=TRex Console v{ver}=-\n".format(ver=__version__) + self.intro += "\nType 'help' or '?' for supported actions\n" + + self.postcmd(False, "") + + + ################### internal section ######################## + + def prompt_redraw (self): + self.postcmd(False, "") + sys.stdout.write("\n" + self.prompt + readline.get_line_buffer()) + sys.stdout.flush() + + + def verify_connected(f): + @wraps(f) + def wrap(*args): + inst = args[0] + func_name = f.__name__ + if func_name.startswith("do_"): + func_name = func_name[3:] + + if not inst.stateless_client.is_connected(): + print format_text("\n'{0}' cannot be executed on offline mode\n".format(func_name), 'bold') + return + + ret = f(*args) + return ret + + return wrap + + # TODO: remove this ugly duplication + def verify_connected_and_rw (f): + @wraps(f) + def wrap(*args): + inst = args[0] + func_name = f.__name__ + if func_name.startswith("do_"): + func_name = func_name[3:] + + if not inst.stateless_client.is_connected(): + print format_text("\n'{0}' cannot be executed on offline mode\n".format(func_name), 'bold') + return + + if inst.stateless_client.is_all_ports_acquired(): + print format_text("\n'{0}' cannot be executed on read only mode\n".format(func_name), 'bold') + return + + rc = f(*args) + return rc + + return wrap + + + def get_console_identifier(self): + return "{context}_{server}".format(context=get_current_user(), + server=self.stateless_client.get_connection_info()['server']) + + def register_main_console_methods(self): + main_names = set(self.trex_console.get_names()).difference(set(dir(self.__class__))) + for name in main_names: + for prefix in 'do_', 'help_', 'complete_': + if name.startswith(prefix): + self.__dict__[name] = getattr(self.trex_console, name) + + def precmd(self, line): + # before doing anything, save history snapshot of the console + # this is done before executing the command in case of ungraceful application exit + self.save_console_history() + + lines = line.split(';') + + for line in lines: + stop = self.onecmd(line) + stop = self.postcmd(stop, line) + if stop: + return "quit" + + return "" + + + def postcmd(self, stop, line): + + if not self.stateless_client.is_connected(): + self.prompt = "TRex (offline) > " + self.supported_rpc = None + return stop + + if self.stateless_client.is_all_ports_acquired(): + self.prompt = "TRex (read only) > " + return stop + + + self.prompt = "TRex > " + + return stop + + def default(self, line): + print "'{0}' is an unrecognized command. type 'help' or '?' for a list\n".format(line) + + @staticmethod + def tree_autocomplete(text): + dir = os.path.dirname(text) + if dir: + path = dir + else: + path = "." + + + start_string = os.path.basename(text) + + targets = [] + + for x in os.listdir(path): + if x.startswith(start_string): + y = os.path.join(path, x) + if os.path.isfile(y): + targets.append(x + ' ') + elif os.path.isdir(y): + targets.append(x + '/') + + return targets + + + ####################### shell commands ####################### + @verify_connected + def do_ping (self, line): + '''Ping the server\n''' + self.stateless_client.ping() + + + # set verbose on / off + def do_verbose(self, line): + '''Shows or set verbose mode\n''' + if line == "": + print "\nverbose is " + ("on\n" if self.verbose else "off\n") + + elif line == "on": + self.verbose = True + self.stateless_client.set_verbose("high") + print format_text("\nverbose set to on\n", 'green', 'bold') + + elif line == "off": + self.verbose = False + self.stateless_client.set_verbose("normal") + print format_text("\nverbose set to off\n", 'green', 'bold') + + else: + print format_text("\nplease specify 'on' or 'off'\n", 'bold') + + # show history + def help_history (self): + self.do_history("-h") + + def do_shell (self, line): + return self.do_history(line) + + + def do_history (self, line): + '''Manage the command history\n''' + + item = parsing_opts.ArgumentPack(['item'], + {"nargs": '?', + 'metavar': 'item', + 'type': parsing_opts.check_negative, + 'help': "an history item index", + 'default': 0}) + + parser = parsing_opts.gen_parser(self, + "history", + self.do_history.__doc__, + item) + + opts = parser.parse_args(line.split()) + if opts is None: + return + + if opts.item == 0: + self.print_history() + else: + cmd = self.get_history_item(opts.item) + if cmd == None: + return + + print "Executing '{0}'".format(cmd) + + return self.onecmd(cmd) + + + + ############### connect + def do_connect (self, line): + '''Connects to the server\n''' + + self.stateless_client.connect_line(line) + + + def do_disconnect (self, line): + '''Disconnect from the server\n''' + + self.stateless_client.disconnect_line(line) + + + ############### start + + def complete_start(self, text, line, begidx, endidx): + s = line.split() + l = len(s) + + file_flags = parsing_opts.get_flags(parsing_opts.FILE_PATH) + + if (l > 1) and (s[l - 1] in file_flags): + return TRexConsole.tree_autocomplete("") + + if (l > 2) and (s[l - 2] in file_flags): + return TRexConsole.tree_autocomplete(s[l - 1]) + + @verify_connected_and_rw + def do_start(self, line): + '''Start selected traffic in specified port(s) on TRex\n''' + + self.stateless_client.start_line(line) + + + + + def help_start(self): + self.do_start("-h") + + ############# stop + @verify_connected_and_rw + def do_stop(self, line): + '''stops port(s) transmitting traffic\n''' + + self.stateless_client.stop_line(line) + + def help_stop(self): + self.do_stop("-h") + + ############# update + @verify_connected_and_rw + def do_update(self, line): + '''update speed of port(s)currently transmitting traffic\n''' + + self.stateless_client.update_line(line) + + def help_update (self): + self.do_update("-h") + + ############# pause + @verify_connected_and_rw + def do_pause(self, line): + '''pause port(s) transmitting traffic\n''' + + self.stateless_client.pause_line(line) + + ############# resume + @verify_connected_and_rw + def do_resume(self, line): + '''resume port(s) transmitting traffic\n''' + + self.stateless_client.resume_line(line) + + + + ########## reset + @verify_connected_and_rw + def do_reset (self, line): + '''force stop all ports\n''' + self.stateless_client.reset_line(line) + + + ######### validate + @verify_connected + def do_validate (self, line): + '''validates port(s) stream configuration\n''' + + self.stateless_client.validate_line(line) + + + @verify_connected + def do_stats(self, line): + '''Fetch statistics from TRex server by port\n''' + self.stateless_client.show_stats_line(line) + + + def help_stats(self): + self.do_stats("-h") + + @verify_connected + def do_streams(self, line): + '''Fetch statistics from TRex server by port\n''' + self.stateless_client.show_streams_line(line) + + + def help_streams(self): + self.do_streams("-h") + + @verify_connected + def do_clear(self, line): + '''Clear cached local statistics\n''' + self.stateless_client.clear_stats_line(line) + + + def help_clear(self): + self.do_clear("-h") + + + def help_events (self): + self.do_events("-h") + + def do_events (self, line): + '''shows events recieved from server\n''' + + x = parsing_opts.ArgumentPack(['-c','--clear'], + {'action' : "store_true", + 'default': False, + 'help': "clear the events log"}) + + parser = parsing_opts.gen_parser(self, + "events", + self.do_events.__doc__, + x) + + opts = parser.parse_args(line.split()) + if opts is None: + return + + events = self.stateless_client.get_events() + for ev in events: + print ev + + if opts.clear: + self.stateless_client.clear_events() + print format_text("\n\nEvent log was cleared\n\n") + + + # tui + @verify_connected + def do_tui (self, line): + '''Shows a graphical console\n''' + + parser = parsing_opts.gen_parser(self, + "tui", + self.do_tui.__doc__, + parsing_opts.XTERM) + + opts = parser.parse_args(line.split()) + if opts is None: + return + + if opts.xterm: + + info = self.stateless_client.get_connection_info() + + exe = './trex-console --top -t -q -s {0} -p {1} --async_port {2}'.format(info['server'], info['sync_port'], info['async_port']) + cmd = ['xterm', '-geometry', '111x42', '-sl', '0', '-title', 'trex_tui', '-e', exe] + self.terminal = subprocess.Popen(cmd) + + return + + + with self.stateless_client.logger.supress(): + self.tui.show() + + + def help_tui (self): + do_tui("-h") + + + # quit function + def do_quit(self, line): + '''Exit the client\n''' + return True + + + def do_help (self, line): + '''Shows This Help Screen\n''' + if line: + try: + func = getattr(self, 'help_' + line) + except AttributeError: + try: + doc = getattr(self, 'do_' + line).__doc__ + if doc: + self.stdout.write("%s\n"%str(doc)) + return + except AttributeError: + pass + self.stdout.write("%s\n"%str(self.nohelp % (line,))) + return + func() + return + + print "\nSupported Console Commands:" + print "----------------------------\n" + + cmds = [x[3:] for x in self.get_names() if x.startswith("do_")] + for cmd in cmds: + if ( (cmd == "EOF") or (cmd == "q") or (cmd == "exit") or (cmd == "h")): + continue + + try: + doc = getattr(self, 'do_' + cmd).__doc__ + if doc: + help = str(doc) + else: + help = "*** Undocumented Function ***\n" + except AttributeError: + help = "*** Undocumented Function ***\n" + + print "{:<30} {:<30}".format(cmd + " - ", help) + + # a custorm cmdloop wrapper + def start(self): + while True: + try: + self.cmdloop() + break + except KeyboardInterrupt as e: + if not readline.get_line_buffer(): + raise KeyboardInterrupt + else: + print "" + self.intro = None + continue + + if self.terminal: + self.terminal.kill() + + # aliases + do_exit = do_EOF = do_q = do_quit + do_h = do_history + + +# run a script of commands +def run_script_file (self, filename, stateless_client): + + self.logger.log(format_text("\nRunning script file '{0}'...".format(filename), 'bold')) + + with open(filename) as f: + script_lines = f.readlines() + + cmd_table = {} + + # register all the commands + cmd_table['start'] = stateless_client.start_line + cmd_table['stop'] = stateless_client.stop_line + cmd_table['reset'] = stateless_client.reset_line + + for index, line in enumerate(script_lines, start = 1): + line = line.strip() + if line == "": + continue + if line.startswith("#"): + continue + + sp = line.split(' ', 1) + cmd = sp[0] + if len(sp) == 2: + args = sp[1] + else: + args = "" + + stateless_client.logger.log(format_text("Executing line {0} : '{1}'\n".format(index, line))) + + if not cmd in cmd_table: + print "\n*** Error at line {0} : '{1}'\n".format(index, line) + stateless_client.logger.log(format_text("unknown command '{0}'\n".format(cmd), 'bold')) + return False + + cmd_table[cmd](args) + + stateless_client.logger.log(format_text("\n[Done]", 'bold')) + + return True + + +# +def is_valid_file(filename): + if not os.path.isfile(filename): + raise argparse.ArgumentTypeError("The file '%s' does not exist" % filename) + + return filename + + + +def setParserOptions(): + parser = argparse.ArgumentParser(prog="trex_console.py") + + parser.add_argument("-s", "--server", help = "TRex Server [default is localhost]", + default = "localhost", + type = str) + + parser.add_argument("-p", "--port", help = "TRex Server Port [default is 4501]\n", + default = 4501, + type = int) + + parser.add_argument("--async_port", help = "TRex ASync Publisher Port [default is 4500]\n", + default = 4500, + dest='pub', + type = int) + + parser.add_argument("-u", "--user", help = "User Name [default is currently logged in user]\n", + default = get_current_user(), + type = str) + + parser.add_argument("-v", "--verbose", dest="verbose", + action="store_true", help="Switch ON verbose option. Default is: OFF.", + default = False) + + + parser.add_argument("--no_acquire", dest="acquire", + action="store_false", help="Acquire all ports on connect. Default is: ON.", + default = True) + + parser.add_argument("--batch", dest="batch", + nargs = 1, + type = is_valid_file, + help = "Run the console in a batch mode with file", + default = None) + + parser.add_argument("-t", "--tui", dest="tui", + action="store_true", help="Starts with TUI mode", + default = False) + + parser.add_argument("-x", "--xtui", dest="xtui", + action="store_true", help="Starts with XTERM TUI mode", + default = False) + + parser.add_argument("--top", dest="top", + action="store_true", help="Set the window as always on top", + default = False) + + parser.add_argument("-q", "--quiet", dest="quiet", + action="store_true", help="Starts with all outputs suppressed", + default = False) + + return parser + + +def main(): + parser = setParserOptions() + options = parser.parse_args() + + if options.xtui: + options.tui = True + + # always on top + if options.top: + set_window_always_on_top('trex_tui') + + + # Stateless client connection + if options.quiet: + verbose_level = LoggerApi.VERBOSE_QUIET + elif options.verbose: + verbose_level = LoggerApi.VERBOSE_HIGH + else: + verbose_level = LoggerApi.VERBOSE_REGULAR + + # Stateless client connection + logger = ConsoleLogger() + stateless_client = STLClient(username = options.user, + server = options.server, + sync_port = options.port, + async_port = options.pub, + verbose_level = verbose_level, + logger = logger) + + # TUI or no acquire will give us READ ONLY mode + try: + stateless_client.connect() + except STLError as e: + logger.log("Log:\n" + format_text(e.brief() + "\n", 'bold')) + return + + if not options.tui and options.acquire: + try: + # acquire all ports + stateless_client.acquire() + except STLError as e: + logger.log("Log:\n" + format_text(e.brief() + "\n", 'bold')) + logger.log(format_text("\nSwitching to read only mode - only few commands will be available", 'bold')) + + + # a script mode + if options.batch: + cont = run_script_file(options.batch[0], stateless_client) + if not cont: + return + + # console + try: + console = TRexConsole(stateless_client, options.verbose) + logger.prompt_redraw = console.prompt_redraw + + # TUI + if options.tui: + console.do_tui("-x" if options.xtui else "") + else: + console.start() + + except KeyboardInterrupt as e: + print "\n\n*** Caught Ctrl + C... Exiting...\n\n" + + finally: + with stateless_client.logger.supress(): + stateless_client.disconnect(stop_traffic = False) + +if __name__ == '__main__': + + main() + diff --git a/scripts/automation/trex_control_plane/stl/console/trex_root_path.py b/scripts/automation/trex_control_plane/stl/console/trex_root_path.py new file mode 100755 index 00000000..de4ec03b --- /dev/null +++ b/scripts/automation/trex_control_plane/stl/console/trex_root_path.py @@ -0,0 +1,15 @@ +#!/router/bin/python + +import os +import sys + +def add_root_to_path (): + """adds trex_control_plane root dir to script path, up to `depth` parent dirs""" + root_dirname = 'trex_control_plane' + file_path = os.path.dirname(os.path.realpath(__file__)) + + components = file_path.split(os.sep) + sys.path.append( str.join(os.sep, components[:components.index(root_dirname)+1]) ) + return + +add_root_to_path() diff --git a/scripts/automation/trex_control_plane/stl/console/trex_tui.py b/scripts/automation/trex_control_plane/stl/console/trex_tui.py new file mode 100644 index 00000000..f972b905 --- /dev/null +++ b/scripts/automation/trex_control_plane/stl/console/trex_tui.py @@ -0,0 +1,464 @@ +import termios +import sys +import os +import time +from collections import OrderedDict +import datetime +from cStringIO import StringIO + +from trex_stl_lib.utils.text_opts import * +from trex_stl_lib.utils import text_tables + +# for STL exceptions +from trex_stl_lib.api import * + +class SimpleBar(object): + def __init__ (self, desc, pattern): + self.desc = desc + self.pattern = pattern + self.pattern_len = len(pattern) + self.index = 0 + + def show (self): + if self.desc: + print format_text("{0} {1}".format(self.desc, self.pattern[self.index]), 'bold') + else: + print format_text("{0}".format(self.pattern[self.index]), 'bold') + + self.index = (self.index + 1) % self.pattern_len + + +# base type of a panel +class TrexTUIPanel(object): + def __init__ (self, mng, name): + + self.mng = mng + self.name = name + self.stateless_client = mng.stateless_client + + def show (self): + raise NotImplementedError("must implement this") + + def get_key_actions (self): + raise NotImplementedError("must implement this") + + def get_name (self): + return self.name + + +# dashboard panel +class TrexTUIDashBoard(TrexTUIPanel): + def __init__ (self, mng): + super(TrexTUIDashBoard, self).__init__(mng, "dashboard") + + self.key_actions = OrderedDict() + + self.key_actions['c'] = {'action': self.action_clear, 'legend': 'clear', 'show': True} + self.key_actions['p'] = {'action': self.action_pause, 'legend': 'pause', 'show': True} + self.key_actions['r'] = {'action': self.action_resume, 'legend': 'resume', 'show': True} + self.key_actions['+'] = {'action': self.action_raise, 'legend': 'up 5%', 'show': True} + self.key_actions['-'] = {'action': self.action_lower, 'legend': 'low 5%', 'show': True} + + self.ports = self.stateless_client.get_all_ports() + + + def show (self): + stats = self.stateless_client._get_formatted_stats(self.ports) + # print stats to screen + for stat_type, stat_data in stats.iteritems(): + text_tables.print_table_with_header(stat_data.text_table, stat_type) + + + def get_key_actions (self): + allowed = {} + + allowed['c'] = self.key_actions['c'] + + if self.stateless_client.is_all_ports_acquired(): + return allowed + + if len(self.stateless_client.get_transmitting_ports()) > 0: + allowed['p'] = self.key_actions['p'] + allowed['+'] = self.key_actions['+'] + allowed['-'] = self.key_actions['-'] + + + if len(self.stateless_client.get_paused_ports()) > 0: + allowed['r'] = self.key_actions['r'] + + return allowed + + + ######### actions + def action_pause (self): + try: + rc = self.stateless_client.pause(ports = self.mng.ports) + except STLError: + pass + + return "" + + + + def action_resume (self): + try: + self.stateless_client.resume(ports = self.mng.ports) + except STLError: + pass + + return "" + + + def action_raise (self): + try: + self.stateless_client.update(mult = "5%+", ports = self.mng.ports) + except STLError: + pass + + return "" + + + def action_lower (self): + try: + self.stateless_client.update(mult = "5%-", ports = self.mng.ports) + except STLError: + pass + + return "" + + + def action_clear (self): + self.stateless_client.clear_stats(self.mng.ports) + return "cleared all stats" + + +# port panel +class TrexTUIPort(TrexTUIPanel): + def __init__ (self, mng, port_id): + super(TrexTUIPort, self).__init__(mng, "port {0}".format(port_id)) + + self.port_id = port_id + self.port = self.mng.stateless_client.get_port(port_id) + + self.key_actions = OrderedDict() + + self.key_actions['c'] = {'action': self.action_clear, 'legend': 'clear', 'show': True} + self.key_actions['p'] = {'action': self.action_pause, 'legend': 'pause', 'show': True} + self.key_actions['r'] = {'action': self.action_resume, 'legend': 'resume', 'show': True} + self.key_actions['+'] = {'action': self.action_raise, 'legend': 'up 5%', 'show': True} + self.key_actions['-'] = {'action': self.action_lower, 'legend': 'low 5%', 'show': True} + + + def show (self): + stats = self.stateless_client._get_formatted_stats([self.port_id]) + # print stats to screen + for stat_type, stat_data in stats.iteritems(): + text_tables.print_table_with_header(stat_data.text_table, stat_type) + + def get_key_actions (self): + + allowed = {} + + allowed['c'] = self.key_actions['c'] + + if self.stateless_client.is_all_ports_acquired(): + return allowed + + if self.port.state == self.port.STATE_TX: + allowed['p'] = self.key_actions['p'] + allowed['+'] = self.key_actions['+'] + allowed['-'] = self.key_actions['-'] + + elif self.port.state == self.port.STATE_PAUSE: + allowed['r'] = self.key_actions['r'] + + + return allowed + + # actions + def action_pause (self): + try: + self.stateless_client.pause(ports = [self.port_id]) + except STLError: + pass + + return "" + + def action_resume (self): + try: + self.stateless_client.resume(ports = [self.port_id]) + except STLError: + pass + + return "" + + + def action_raise (self): + mult = {'type': 'percentage', 'value': 5, 'op': 'add'} + + try: + self.stateless_client.update(mult = mult, ports = [self.port_id]) + except STLError: + pass + + return "" + + def action_lower (self): + mult = {'type': 'percentage', 'value': 5, 'op': 'sub'} + + try: + self.stateless_client.update(mult = mult, ports = [self.port_id]) + except STLError: + pass + + return "" + + def action_clear (self): + self.stateless_client.clear_stats([self.port_id]) + return "port {0}: cleared stats".format(self.port_id) + +# log +class TrexTUILog(): + def __init__ (self): + self.log = [] + + def add_event (self, msg): + self.log.append("[{0}] {1}".format(str(datetime.datetime.now().time()), msg)) + + def show (self, max_lines = 4): + + cut = len(self.log) - max_lines + if cut < 0: + cut = 0 + + print format_text("\nLog:", 'bold', 'underline') + + for msg in self.log[cut:]: + print msg + + +# Panels manager (contains server panels) +class TrexTUIPanelManager(): + def __init__ (self, tui): + self.tui = tui + self.stateless_client = tui.stateless_client + self.ports = self.stateless_client.get_all_ports() + + + self.panels = {} + self.panels['dashboard'] = TrexTUIDashBoard(self) + + self.key_actions = OrderedDict() + self.key_actions['q'] = {'action': self.action_quit, 'legend': 'quit', 'show': True} + self.key_actions['g'] = {'action': self.action_show_dash, 'legend': 'dashboard', 'show': True} + + for port_id in self.ports: + self.key_actions[str(port_id)] = {'action': self.action_show_port(port_id), 'legend': 'port {0}'.format(port_id), 'show': False} + self.panels['port {0}'.format(port_id)] = TrexTUIPort(self, port_id) + + # start with dashboard + self.main_panel = self.panels['dashboard'] + + # log object + self.log = TrexTUILog() + + self.generate_legend() + + self.conn_bar = SimpleBar('status: ', ['|','/','-','\\']) + self.dis_bar = SimpleBar('status: ', ['X', ' ']) + self.show_log = False + + + def generate_legend (self): + self.legend = "\n{:<12}".format("browse:") + + for k, v in self.key_actions.iteritems(): + if v['show']: + x = "'{0}' - {1}, ".format(k, v['legend']) + self.legend += "{:}".format(x) + + self.legend += "'0-{0}' - port display".format(len(self.ports) - 1) + + + self.legend += "\n{:<12}".format(self.main_panel.get_name() + ":") + for k, v in self.main_panel.get_key_actions().iteritems(): + if v['show']: + x = "'{0}' - {1}, ".format(k, v['legend']) + self.legend += "{:}".format(x) + + + def print_connection_status (self): + if self.tui.get_state() == self.tui.STATE_ACTIVE: + self.conn_bar.show() + else: + self.dis_bar.show() + + def print_legend (self): + print format_text(self.legend, 'bold') + + + # on window switch or turn on / off of the TUI we call this + def init (self, show_log = False): + self.show_log = show_log + self.generate_legend() + + def show (self): + self.main_panel.show() + self.print_connection_status() + self.print_legend() + + if self.show_log: + self.log.show() + + + def handle_key (self, ch): + # check for the manager registered actions + if ch in self.key_actions: + msg = self.key_actions[ch]['action']() + + # check for main panel actions + elif ch in self.main_panel.get_key_actions(): + msg = self.main_panel.get_key_actions()[ch]['action']() + + else: + msg = "" + + self.generate_legend() + + if msg == None: + return False + else: + if msg: + self.log.add_event(msg) + return True + + + # actions + + def action_quit (self): + return None + + def action_show_dash (self): + self.main_panel = self.panels['dashboard'] + self.init(self.show_log) + return "" + + def action_show_port (self, port_id): + def action_show_port_x (): + self.main_panel = self.panels['port {0}'.format(port_id)] + self.init() + return "" + + return action_show_port_x + + + +# shows a textual top style window +class TrexTUI(): + + STATE_ACTIVE = 0 + STATE_LOST_CONT = 1 + STATE_RECONNECT = 2 + + def __init__ (self, stateless_client): + self.stateless_client = stateless_client + + self.pm = TrexTUIPanelManager(self) + + + + def handle_key_input (self): + # try to read a single key + ch = os.read(sys.stdin.fileno(), 1) + if ch != None and len(ch) > 0: + return (self.pm.handle_key(ch), True) + + else: + return (True, False) + + + def clear_screen (self): + #os.system('clear') + # maybe this is faster ? + sys.stdout.write("\x1b[2J\x1b[H") + + + + def show (self, show_log = False): + # init termios + old_settings = termios.tcgetattr(sys.stdin) + new_settings = termios.tcgetattr(sys.stdin) + new_settings[3] = new_settings[3] & ~(termios.ECHO | termios.ICANON) # lflags + new_settings[6][termios.VMIN] = 0 # cc + new_settings[6][termios.VTIME] = 0 # cc + termios.tcsetattr(sys.stdin, termios.TCSADRAIN, new_settings) + + self.pm.init(show_log) + + self.state = self.STATE_ACTIVE + self.draw_policer = 0 + + try: + while True: + # draw and handle user input + cont, force_draw = self.handle_key_input() + self.draw_screen(force_draw) + if not cont: + break + time.sleep(0.1) + + # regular state + if self.state == self.STATE_ACTIVE: + # if no connectivity - move to lost connecitivty + if not self.stateless_client.async_client.is_alive(): + self.stateless_client._invalidate_stats(self.pm.ports) + self.state = self.STATE_LOST_CONT + + + # lost connectivity + elif self.state == self.STATE_LOST_CONT: + # got it back + if self.stateless_client.async_client.is_alive(): + # move to state reconnect + self.state = self.STATE_RECONNECT + + + # restored connectivity - try to reconnect + elif self.state == self.STATE_RECONNECT: + + try: + self.stateless_client.connect("RO") + self.state = self.STATE_ACTIVE + except STLError: + self.state = self.STATE_LOST_CONT + + + finally: + # restore + termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings) + + print "" + + + # draw once + def draw_screen (self, force_draw = False): + + if (self.draw_policer >= 5) or (force_draw): + + # capture stdout to a string + old_stdout = sys.stdout + sys.stdout = mystdout = StringIO() + self.pm.show() + sys.stdout = old_stdout + + self.clear_screen() + print mystdout.getvalue() + sys.stdout.flush() + + self.draw_policer = 0 + else: + self.draw_policer += 1 + + def get_state (self): + return self.state + diff --git a/scripts/automation/trex_control_plane/stl/examples/stl_bi_dir_flows.py b/scripts/automation/trex_control_plane/stl/examples/stl_bi_dir_flows.py new file mode 100644 index 00000000..46b84c6e --- /dev/null +++ b/scripts/automation/trex_control_plane/stl/examples/stl_bi_dir_flows.py @@ -0,0 +1,114 @@ +import stl_path +from trex_control_plane.stl.api import * + +import time +import json + +# simple packet creation +def create_pkt (size, direction): + + ip_range = {'src': {'start': "10.0.0.1", 'end': "10.0.0.254"}, + 'dst': {'start': "8.0.0.1", 'end': "8.0.0.254"}} + + if (direction == 0): + src = ip_range['src'] + dst = ip_range['dst'] + else: + src = ip_range['dst'] + dst = ip_range['src'] + + vm = [ + # src + STLVmFlowVar(name="src",min_value=src['start'],max_value=src['end'],size=4,op="inc"), + STLVmWriteFlowVar(fv_name="src",pkt_offset= "IP.src"), + + # dst + STLVmFlowVar(name="dst",min_value=dst['start'],max_value=dst['end'],size=4,op="inc"), + STLVmWriteFlowVar(fv_name="dst",pkt_offset= "IP.dst"), + + # checksum + STLVmFixIpv4(offset = "IP") + ] + + + base = Ether()/IP()/UDP() + pad = max(0, len(base)) * 'x' + + return STLPktBuilder(pkt = base/pad, + vm = vm) + + +def simple_burst (): + + + # create client + c = STLClient() + passed = True + + try: + # turn this on for some information + #c.set_verbose("high") + + # create two streams + s1 = STLStream(packet = create_pkt(200, 0), + mode = STLTXCont(pps = 100)) + + # second stream with a phase of 1ms (inter stream gap) + s2 = STLStream(packet = create_pkt(200, 1), + isg = 1000, + mode = STLTXCont(pps = 100)) + + + # connect to server + c.connect() + + # prepare our ports (my machine has 0 <--> 1 with static route) + c.reset(ports = [0, 1]) + + # add both streams to ports + c.add_streams(s1, ports = [0]) + c.add_streams(s2, ports = [1]) + + # clear the stats before injecting + c.clear_stats() + + # choose rate and start traffic for 10 seconds on 5 mpps + print "Running 5 Mpps on ports 0, 1 for 10 seconds..." + c.start(ports = [0, 1], mult = "5mpps", duration = 10) + + # block until done + c.wait_on_traffic(ports = [0, 1]) + + # read the stats after the test + stats = c.get_stats() + + print json.dumps(stats[0], indent = 4, separators=(',', ': '), sort_keys = True) + print json.dumps(stats[1], indent = 4, separators=(',', ': '), sort_keys = True) + + lost_a = stats[0]["opackets"] - stats[1]["ipackets"] + lost_b = stats[1]["opackets"] - stats[0]["ipackets"] + + print "\npackets lost from 0 --> 1: {0} pkts".format(lost_a) + print "packets lost from 1 --> 0: {0} pkts".format(lost_b) + + if (lost_a == 0) and (lost_b == 0): + passed = True + else: + passed = False + + except STLError as e: + passed = False + print e + + finally: + c.disconnect() + + if passed: + print "\nTest has passed :-)\n" + else: + print "\nTest has failed :-(\n" + + +# run the tests +simple_burst() + diff --git a/scripts/automation/trex_control_plane/stl/examples/stl_imix.py b/scripts/automation/trex_control_plane/stl/examples/stl_imix.py new file mode 100644 index 00000000..c083a207 --- /dev/null +++ b/scripts/automation/trex_control_plane/stl/examples/stl_imix.py @@ -0,0 +1,101 @@ +import stl_path +from trex_control_plane.stl.api import * + +import time +import json +from pprint import pprint + +# IMIX test +# it maps the ports to sides +# then it load a predefind profile 'IMIX' +# and attach it to both sides and inject +# at a certain rate for some time +# finally it checks that all packets arrived +def imix_test (): + + + # create client + c = STLClient() + passed = True + + + try: + + # connect to server + c.connect() + + # take all the ports + c.reset() + + # map ports - identify the routes + table = stl_map_ports(c) + + print "Mapped ports to sides {0} <--> {1}".format(table['dir'][0], table['dir'][1]) + dir_0 = table['dir'][0] + dir_1 = table['dir'][1] + + # load IMIX profile + streams = c.load_profile('../../../stl/profiles/imix.py') + + # add both streams to ports + c.add_streams(streams, ports = dir_0) + c.add_streams(streams, ports = dir_1) + + # clear the stats before injecting + c.clear_stats() + + # choose rate and start traffic for 10 seconds on 5 mpps + duration = 10 + mult = "5mpps" + print "Injecting {0} <--> {1} on total rate of '{2}' for {3} seconds".format(dir_0, dir_1, mult, duration) + + c.start(ports = (dir_0 + dir_1), mult = mult, duration = duration, total = True) + + # block until done + c.wait_on_traffic(ports = (dir_0 + dir_1)) + + # read the stats after the test + stats = c.get_stats() + + # use this for debug info on all the stats + #pprint(stats) + + # sum dir 0 + dir_0_opackets = sum([stats[i]["opackets"] for i in dir_0]) + dir_0_ipackets = sum([stats[i]["ipackets"] for i in dir_0]) + + # sum dir 1 + dir_1_opackets = sum([stats[i]["opackets"] for i in dir_1]) + dir_1_ipackets = sum([stats[i]["ipackets"] for i in dir_1]) + + + lost_0 = dir_0_opackets - dir_1_ipackets + lost_1 = dir_1_opackets - dir_0_ipackets + + print "\nPackets injected from {0}: {1:,}".format(dir_0, dir_0_opackets) + print "Packets injected from {0}: {1:,}".format(dir_1, dir_1_opackets) + + print "\npackets lost from {0} --> {1}: {2:,} pkts".format(dir_0, dir_0, lost_0) + print "packets lost from {0} --> {1}: {2:,} pkts".format(dir_0, dir_0, lost_0) + + if (lost_0 == 0) and (lost_0 == 0): + passed = True + else: + passed = False + + except STLError as e: + passed = False + print e + + finally: + c.disconnect() + + if passed: + print "\nTest has passed :-)\n" + else: + print "\nTest has failed :-(\n" + + +# run the tests +imix_test() + diff --git a/scripts/automation/trex_control_plane/stl/examples/stl_path.py b/scripts/automation/trex_control_plane/stl/examples/stl_path.py new file mode 100644 index 00000000..a5b8102b --- /dev/null +++ b/scripts/automation/trex_control_plane/stl/examples/stl_path.py @@ -0,0 +1,4 @@ +import sys + +# FIXME to the write path for trex_control_plane +sys.path.insert(0, "../../../automation") diff --git a/scripts/automation/trex_control_plane/stl/examples/stl_run_udp_simple.py b/scripts/automation/trex_control_plane/stl/examples/stl_run_udp_simple.py new file mode 100644 index 00000000..47db1b5a --- /dev/null +++ b/scripts/automation/trex_control_plane/stl/examples/stl_run_udp_simple.py @@ -0,0 +1,219 @@ +#!/usr/bin/python +import sys, getopt +import argparse; +""" +Sample API application, +Connect to TRex +Send UDP packet in specific length +Each direction has its own IP range +Compare Rx-pkts to TX-pkts assuming ports are loopback + +""" + +import stl_path +from trex_control_plane.stl.api import * + +H_VER = "trex-x v0.1 " + +class t_global(object): + args=None; + + +import dpkt +import time +import json +import string + +def generate_payload(length): + word = '' + alphabet_size = len(string.letters) + for i in range(length): + word += string.letters[(i % alphabet_size)] + return word + +# simple packet creation +def create_pkt (frame_size = 9000, direction=0): + + ip_range = {'src': {'start': "10.0.0.1", 'end': "10.0.0.254"}, + 'dst': {'start': "8.0.0.1", 'end': "8.0.0.254"}} + + if (direction == 0): + src = ip_range['src'] + dst = ip_range['dst'] + else: + src = ip_range['dst'] + dst = ip_range['src'] + + vm = [ + # src + STLVmFlowVar(name="src",min_value=src['start'],max_value=src['end'],size=4,op="inc"), + STLVmWriteFlowVar(fv_name="src",pkt_offset= "IP.src"), + + # dst + STLVmFlowVar(name="dst",min_value=dst['start'],max_value=dst['end'],size=4,op="inc"), + STLVmWriteFlowVar(fv_name="dst",pkt_offset= "IP.dst"), + + # checksum + STLVmFixIpv4(offset = "IP") + ] + + pkt_base = Ether(src="00:00:00:00:00:01",dst="00:00:00:00:00:02")/IP()/UDP(dport=12,sport=1025) + pyld_size = frame_size - len(pkt_base); + pkt_pyld = generate_payload(pyld_size) + + return STLPktBuilder(pkt = pkt_base/pkt_pyld, + vm = vm) + + +def simple_burst (duration = 10, frame_size = 9000, speed = '1gbps'): + + if (frame_size < 60): + frame_size = 60 + + pkt_dir_0 = create_pkt (frame_size, 0) + + pkt_dir_1 = create_pkt (frame_size, 1) + + # create client + c = STLClient(server = t_global.args.ip) + + passed = True + + try: + # turn this on for some information + #c.set_verbose("high") + + # create two streams + s1 = STLStream(packet = pkt_dir_0, + mode = STLTXCont(pps = 100)) + + # second stream with a phase of 1ms (inter stream gap) + s2 = STLStream(packet = pkt_dir_1, + isg = 1000, + mode = STLTXCont(pps = 100)) + + if t_global.args.debug: + STLStream.dump_to_yaml ("example.yaml", [s1,s2]) # export to YAML so you can run it on simulator ./stl-sim -f example.yaml -o o.pcap + + # connect to server + c.connect() + + # prepare our ports (my machine has 0 <--> 1 with static route) + c.reset(ports = [0, 1]) + + # add both streams to ports + c.add_streams(s1, ports = [0]) + c.add_streams(s2, ports = [1]) + + # clear the stats before injecting + c.clear_stats() + + # choose rate and start traffic for 10 seconds on 5 mpps + print "Running {0} on ports 0, 1 for 10 seconds, UDP {1}...".format(speed,frame_size+4) + c.start(ports = [0, 1], mult = speed, duration = duration) + + # block until done + c.wait_on_traffic(ports = [0, 1]) + + # read the stats after the test + stats = c.get_stats() + + #print stats + print json.dumps(stats[0], indent = 4, separators=(',', ': '), sort_keys = True) + print json.dumps(stats[1], indent = 4, separators=(',', ': '), sort_keys = True) + + lost_a = stats[0]["opackets"] - stats[1]["ipackets"] + lost_b = stats[1]["opackets"] - stats[0]["ipackets"] + + print "\npackets lost from 0 --> 1: {0} pkts".format(lost_a) + print "packets lost from 1 --> 0: {0} pkts".format(lost_b) + + if (lost_a == 0) and (lost_b == 0): + passed = True + else: + passed = False + + except STLError as e: + passed = False + print e + + finally: + c.disconnect() + + if passed: + print "\nPASSED\n" + else: + print "\nFAILED\n" + +def process_options (): + parser = argparse.ArgumentParser(usage=""" + connect to TRex and send burst of packets + + examples + + stl_run_udp_simple.py -s 9001 + + stl_run_udp_simple.py -s 9000 -d 2 + + stl_run_udp_simple.py -s 3000 -d 3 -m 10mbps + + stl_run_udp_simple.py -s 3000 -d 3 -m 10mbps --debug + + then run the simulator on the output + ./stl-sim -f example.yaml -o a.pcap ==> a.pcap include the packet + + """, + description="example for TRex api", + epilog=" written by hhaim"); + + parser.add_argument("-s", "--frame-size", + dest="frame_size", + help='L2 frame size in bytes without FCS', + default=60, + type = int, + ) + + parser.add_argument("--ip", + dest="ip", + help='remote trex ip default local', + default="127.0.0.1", + type = str + ) + + + parser.add_argument('-d','--duration', + dest='duration', + help='duration in second ', + default=10, + type = int, + ) + + + parser.add_argument('-m','--multiplier', + dest='mul', + help='speed in gbps/pps for example 1gbps, 1mbps, 1mpps ', + default="1mbps" + ) + + parser.add_argument('--debug', + action='store_true', + help='see debug into ') + + parser.add_argument('--version', action='version', + version=H_VER ) + + t_global.args = parser.parse_args(); + print t_global.args + + + +def main(): + process_options () + simple_burst(duration = t_global.args.duration, + frame_size = t_global.args.frame_size, + speed = t_global.args.mul + ) + +if __name__ == "__main__": + main() + diff --git a/scripts/automation/trex_control_plane/stl/examples/stl_simple_burst.py b/scripts/automation/trex_control_plane/stl/examples/stl_simple_burst.py new file mode 100644 index 00000000..1e3e7695 --- /dev/null +++ b/scripts/automation/trex_control_plane/stl/examples/stl_simple_burst.py @@ -0,0 +1,65 @@ +import stl_path +from trex_control_plane.stl.api import * + +import time + +def simple_burst (): + + + # create client + c = STLClient() + passed = True + + try: + pkt = STLPktBuilder(pkt = Ether()/IP(src="16.0.0.1",dst="48.0.0.1")/UDP(dport=12,sport=1025)/IP()/'a_payload_example') + + # create two bursts and link them + s1 = STLStream(name = 'A', + packet = pkt, + mode = STLTXSingleBurst(total_pkts = 5000), + next = 'B') + + s2 = STLStream(name = 'B', + self_start = False, + packet = pkt, + mode = STLTXSingleBurst(total_pkts = 3000)) + + # connect to server + c.connect() + + # prepare our ports + c.reset(ports = [0, 3]) + + # add both streams to ports + stream_ids = c.add_streams([s1, s2], ports = [0, 3]) + + # run 5 times + for i in xrange(1, 6): + c.clear_stats() + c.start(ports = [0, 3], mult = "1gbps") + c.wait_on_traffic(ports = [0, 3]) + + stats = c.get_stats() + ipackets = stats['total']['ipackets'] + + print "Test iteration {0} - Packets Received: {1} ".format(i, ipackets) + # (5000 + 3000) * 2 ports = 16,000 + if (ipackets != (16000)): + passed = False + + except STLError as e: + passed = False + print e + + finally: + c.disconnect() + + if passed: + print "\nTest has passed :-)\n" + else: + print "\nTest has failed :-(\n" + + +# run the tests +simple_burst() + diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/__init__.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/__init__.py diff --git a/scripts/automation/trex_control_plane/stl/api.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/api.py index c12628b5..4c0c10fa 100644 --- a/scripts/automation/trex_control_plane/stl/api.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/api.py @@ -1,6 +1,6 @@ # get external libs -import trex_control_plane.client_utils.external_packages +import trex_stl_ext # client and exceptions from trex_stl_exceptions import * diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_async_client.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_async_client.py index 9b3b9577..410482b9 100644 --- a/scripts/automation/trex_control_plane/stl/trex_stl_async_client.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_async_client.py @@ -10,7 +10,7 @@ import random from trex_stl_jsonrpc_client import JsonRpcClient, BatchMessage -from common.text_opts import * +from utils.text_opts import * from trex_stl_stats import * from trex_stl_types import * diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_client.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py index 08f640b5..ed11791b 100644 --- a/scripts/automation/trex_control_plane/stl/trex_stl_client.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py @@ -1,9 +1,8 @@ #!/router/bin/python # for API usage the path name must be full -from trex_control_plane.stl.trex_stl_exceptions import * -#from trex_control_plane.stl.trex_stl_streams import * -from trex_stl_streams import * +from trex_stl_lib.trex_stl_exceptions import * +from trex_stl_lib.trex_stl_streams import * from trex_stl_jsonrpc_client import JsonRpcClient, BatchMessage import trex_stl_stats @@ -12,8 +11,8 @@ from trex_stl_port import Port from trex_stl_types import * from trex_stl_async_client import CTRexAsyncClient -from trex_control_plane.client_utils import parsing_opts, text_tables, general_utils -from trex_control_plane.common.text_opts import * +from utils import parsing_opts, text_tables, common +from utils.text_opts import * from collections import namedtuple @@ -23,6 +22,7 @@ import datetime import re import random import json +import traceback ############################ logger ############################# ############################ ############################# @@ -376,7 +376,7 @@ class STLClient(object): """docstring for STLClient""" def __init__(self, - username = general_utils.get_current_user(), + username = common.get_current_user(), server = "localhost", sync_port = 4501, async_port = 4500, diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_exceptions.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_exceptions.py index d5b3885d..45acc72e 100644 --- a/scripts/automation/trex_control_plane/stl/trex_stl_exceptions.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_exceptions.py @@ -1,7 +1,7 @@ import os import sys -from trex_control_plane.common.text_opts import * +from utils.text_opts import * # basic error for API class STLError(Exception): diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_ext.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_ext.py new file mode 100644 index 00000000..1092679a --- /dev/null +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_ext.py @@ -0,0 +1,74 @@ +import sys +import os +import warnings + +# if not set - set it to default +if not 'TREX_STL_EXT_PATH' in globals(): + CURRENT_PATH = os.path.dirname(os.path.realpath(__file__)) + # ../../../../external_libs + TREX_STL_EXT_PATH = os.path.abspath(os.path.join(CURRENT_PATH, os.pardir, os.pardir, os.pardir, os.pardir, 'external_libs')) + +# check path exists +if not os.path.exists(TREX_STL_EXT_PATH): + print "Unable to find external packages path: '{0}'".format(TREX_STL_EXT_PATH) + print "Please provide the correct path using TREX_STL_EXT_PATH variable" + exit(0) + +# the modules required +CLIENT_UTILS_MODULES = ['dpkt-1.8.6', + 'yaml-3.11', + 'texttable-0.8.4', + 'scapy-2.3.1' + ] + +def import_client_utils_modules(): + import_module_list(CLIENT_UTILS_MODULES) + + +def import_module_list(modules_list): + assert(isinstance(modules_list, list)) + for p in modules_list: + full_path = os.path.join(TREX_STL_EXT_PATH, p) + fix_path = os.path.normcase(full_path) + sys.path.insert(1, full_path) + + + import_platform_dirs() + + + +def import_platform_dirs (): + # handle platform dirs + + # try fedora 18 first and then cel5.9 + # we are using the ZMQ module to determine the right platform + + full_path = os.path.join(TREX_STL_EXT_PATH, 'platform/fedora18') + fix_path = os.path.normcase(full_path) + sys.path.insert(0, full_path) + try: + # try to import and delete it from the namespace + import zmq + del zmq + return + except: + sys.path.pop(0) + pass + + full_path = os.path.join(TREX_STL_EXT_PATH, 'platform/cel59') + fix_path = os.path.normcase(full_path) + sys.path.insert(0, full_path) + try: + # try to import and delete it from the namespace + import zmq + del zmq + return + + except: + sys.path.pop(0) + sys.modules['zmq'] = None + warnings.warn("unable to determine platform type for ZMQ import") + + +import_client_utils_modules() + diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_jsonrpc_client.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_jsonrpc_client.py index 887681a7..ab3c7282 100644 --- a/scripts/automation/trex_control_plane/stl/trex_stl_jsonrpc_client.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_jsonrpc_client.py @@ -2,11 +2,11 @@ import zmq import json -import client_utils.general_utils import re from time import sleep from collections import namedtuple from trex_stl_types import * +from utils.common import random_id_gen class bcolors: BLUE = '\033[94m' @@ -48,7 +48,8 @@ class JsonRpcClient(object): # default values self.port = default_port self.server = default_server - self.id_gen = client_utils.general_utils.random_id_gen() + + self.id_gen = random_id_gen() def get_connection_details (self): diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_packet_builder_interface.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_packet_builder_interface.py index b6e7c026..b6e7c026 100644 --- a/scripts/automation/trex_control_plane/stl/trex_stl_packet_builder_interface.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_packet_builder_interface.py diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_packet_builder_scapy.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_packet_builder_scapy.py index 74946c12..0811209a 100644 --- a/scripts/automation/trex_control_plane/stl/trex_stl_packet_builder_scapy.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_packet_builder_scapy.py @@ -585,7 +585,7 @@ class CTRexVmDescTupleGen(CTRexVmDescBase): ################################################################################################ -lass CScapyTRexPktBuilder(CTrexPktBuilderInterface): +class CScapyTRexPktBuilder(CTrexPktBuilderInterface): """ This class defines the TRex API of building a packet using dpkt package. @@ -794,11 +794,11 @@ lass CScapyTRexPktBuilder(CTrexPktBuilderInterface): return p_utl.get_field_offet_by_str(field_name) def _get_pkt_as_str(self): - if self.pkt : - str(self.pkt) + if self.pkt: + return str(self.pkt) if self.pkt_raw: return self.pkt_raw - raise CTRexPacketBuildException(-11,('empty packet') % (var_name) ); + raise CTRexPacketBuildException(-11, 'empty packet'); def _add_tuple_gen(self,tuple_gen): diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_port.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_port.py index b2cf1c90..b2cf1c90 100644 --- a/scripts/automation/trex_control_plane/stl/trex_stl_port.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_port.py diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_sim.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_sim.py index d61e04bf..1252b752 100644 --- a/scripts/automation/trex_control_plane/stl/trex_stl_sim.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_sim.py @@ -16,10 +16,13 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ +# simulator can be run as a standalone +import trex_stl_ext + from trex_stl_exceptions import * from yaml import YAMLError from trex_stl_streams import * -from client_utils import parsing_opts +from utils import parsing_opts from trex_stl_client import STLClient import re @@ -142,6 +145,29 @@ class STLSim(object): # load streams cmds_json = [] + + id = 1 + + lookup = {} + # allocate IDs + for stream in stream_list: + if stream.get_id() == None: + stream.set_id(id) + id += 1 + + lookup[stream.get_name()] = stream.get_id() + + # resolve names + for stream in stream_list: + next_id = -1 + next = stream.get_next() + if next: + if not next in lookup: + raise STLError("stream dependency error - unable to find '{0}'".format(next)) + next_id = lookup[next] + + stream.fields['next_stream_id'] = next_id + for stream in stream_list: cmd = {"id":1, "jsonrpc": "2.0", diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_stats.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py index f880a914..3f09e47c 100644 --- a/scripts/automation/trex_control_plane/stl/trex_stl_stats.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py @@ -1,7 +1,7 @@ #!/router/bin/python -from client_utils import text_tables -from common.text_opts import format_text, format_threshold, format_num +from utils import text_tables +from utils.text_opts import format_text, format_threshold, format_num from trex_stl_async_client import CTRexAsyncStats diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_std.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_std.py index 72a5ea52..72a5ea52 100644 --- a/scripts/automation/trex_control_plane/stl/trex_stl_std.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_std.py diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_streams.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_streams.py index 798b2f67..d8e86fef 100644 --- a/scripts/automation/trex_control_plane/stl/trex_stl_streams.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_streams.py @@ -5,8 +5,6 @@ from trex_stl_packet_builder_interface import CTrexPktBuilderInterface from trex_stl_packet_builder_scapy import CScapyTRexPktBuilder, Ether, IP from collections import OrderedDict, namedtuple -from trex_control_plane.client_utils.yaml_utils import * - from dpkt import pcap import random import yaml @@ -143,6 +141,7 @@ class STLStream(object): # packet builder packet.compile() + # packet and VM self.fields['packet'] = packet.dump_pkt() self.fields['vm'] = packet.get_vm_data() diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_types.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_types.py index a7ddacea..1164076b 100644 --- a/scripts/automation/trex_control_plane/stl/trex_stl_types.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_types.py @@ -1,6 +1,6 @@ from collections import namedtuple -from common.text_opts import * +from utils.text_opts import * RpcCmdData = namedtuple('RpcCmdData', ['method', 'params']) diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/__init__.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/__init__.py diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/common.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/common.py new file mode 100644 index 00000000..117017c3 --- /dev/null +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/common.py @@ -0,0 +1,47 @@ +import os +import sys +import string +import random + +try: + import pwd +except ImportError: + import getpass + pwd = None + +using_python_3 = True if sys.version_info.major == 3 else False + +def get_current_user(): + if pwd: + return pwd.getpwuid(os.geteuid()).pw_name + else: + return getpass.getuser() + + +def user_input(): + if using_python_3: + return input() + else: + # using python version 2 + return raw_input() + + +def random_id_gen(length=8): + """ + A generator for creating a random chars id of specific length + + :parameters: + length : int + the desired length of the generated id + + default: 8 + + :return: + a random id with each next() request. + """ + id_chars = string.ascii_lowercase + string.digits + while True: + return_id = '' + for i in range(length): + return_id += random.choice(id_chars) + yield return_id diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py new file mode 100755 index 00000000..968bbb7e --- /dev/null +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/parsing_opts.py @@ -0,0 +1,362 @@ +import argparse +from collections import namedtuple +import sys +import re +import os + +ArgumentPack = namedtuple('ArgumentPack', ['name_or_flags', 'options']) +ArgumentGroup = namedtuple('ArgumentGroup', ['type', 'args', 'options']) + + +# list of available parsing options +MULTIPLIER = 1 +MULTIPLIER_STRICT = 2 +PORT_LIST = 3 +ALL_PORTS = 4 +PORT_LIST_WITH_ALL = 5 +FILE_PATH = 6 +FILE_FROM_DB = 7 +SERVER_IP = 8 +STREAM_FROM_PATH_OR_FILE = 9 +DURATION = 10 +FORCE = 11 +DRY_RUN = 12 +XTERM = 13 +TOTAL = 14 +FULL_OUTPUT = 15 + +GLOBAL_STATS = 50 +PORT_STATS = 51 +PORT_STATUS = 52 +STATS_MASK = 53 + +STREAMS_MASK = 60 +# ALL_STREAMS = 61 +# STREAM_LIST_WITH_ALL = 62 + + + +# list of ArgumentGroup types +MUTEX = 1 + +def check_negative(value): + ivalue = int(value) + if ivalue < 0: + raise argparse.ArgumentTypeError("non positive value provided: '{0}'".format(value)) + return ivalue + +def match_time_unit(val): + '''match some val against time shortcut inputs ''' + match = re.match("^(\d+(\.\d+)?)([m|h]?)$", val) + if match: + digit = float(match.group(1)) + unit = match.group(3) + if not unit: + return digit + elif unit == 'm': + return digit*60 + else: + return digit*60*60 + else: + raise argparse.ArgumentTypeError("Duration should be passed in the following format: \n" + "-d 100 : in sec \n" + "-d 10m : in min \n" + "-d 1h : in hours") + +match_multiplier_help = """Multiplier should be passed in the following format: + [number][<empty> | bps | kbps | mbps | gbps | pps | kpps | mpps | %% ]. + no suffix will provide an absoulute factor and percentage + will provide a percentage of the line rate. examples + : '-m 10', '-m 10kbps', '-m 10mpps', '-m 23%%' """ + + +# decodes multiplier +# if allow_update - no +/- is allowed +# divide states between how many entities the +# value should be divided +def decode_multiplier(val, allow_update = False, divide_count = 1): + + # must be string + if not isinstance(val, str): + return None + + # do we allow updates ? +/- + if not allow_update: + match = re.match("^(\d+(\.\d+)?)(bps|kbps|mbps|gbps|pps|kpps|mpps|%?)$", val) + op = None + else: + match = re.match("^(\d+(\.\d+)?)(bps|kbps|mbps|gbps|pps|kpps|mpps|%?)([\+\-])?$", val) + if match: + op = match.group(4) + else: + op = None + + result = {} + + if match: + + value = float(match.group(1)) + unit = match.group(3) + + + + # raw type (factor) + if not unit: + result['type'] = 'raw' + result['value'] = value + + elif unit == 'bps': + result['type'] = 'bps' + result['value'] = value + + elif unit == 'kbps': + result['type'] = 'bps' + result['value'] = value * 1000 + + elif unit == 'mbps': + result['type'] = 'bps' + result['value'] = value * 1000 * 1000 + + elif unit == 'gbps': + result['type'] = 'bps' + result['value'] = value * 1000 * 1000 * 1000 + + elif unit == 'pps': + result['type'] = 'pps' + result['value'] = value + + elif unit == "kpps": + result['type'] = 'pps' + result['value'] = value * 1000 + + elif unit == "mpps": + result['type'] = 'pps' + result['value'] = value * 1000 * 1000 + + elif unit == "%": + result['type'] = 'percentage' + result['value'] = value + + + if op == "+": + result['op'] = "add" + elif op == "-": + result['op'] = "sub" + else: + result['op'] = "abs" + + if result['op'] != 'percentage': + result['value'] = result['value'] / divide_count + + return result + + else: + return None + + +def match_multiplier(val): + '''match some val against multiplier shortcut inputs ''' + result = decode_multiplier(val, allow_update = True) + if not result: + raise argparse.ArgumentTypeError(match_multiplier_help) + + return val + + +def match_multiplier_strict(val): + '''match some val against multiplier shortcut inputs ''' + result = decode_multiplier(val, allow_update = False) + if not result: + raise argparse.ArgumentTypeError(match_multiplier_help) + + return val + + +def is_valid_file(filename): + if not os.path.isfile(filename): + raise argparse.ArgumentTypeError("The file '%s' does not exist" % filename) + + return filename + + +OPTIONS_DB = {MULTIPLIER: ArgumentPack(['-m', '--multiplier'], + {'help': match_multiplier_help, + 'dest': "mult", + 'default': "1", + 'type': match_multiplier}), + + MULTIPLIER_STRICT: ArgumentPack(['-m', '--multiplier'], + {'help': match_multiplier_help, + 'dest': "mult", + 'default': "1", + 'type': match_multiplier_strict}), + + TOTAL: ArgumentPack(['-t', '--total'], + {'help': "traffic will be divided between all ports specified", + 'dest': "total", + 'default': False, + 'action': "store_true"}), + + PORT_LIST: ArgumentPack(['--port'], + {"nargs": '+', + 'dest':'ports', + 'metavar': 'PORTS', + 'type': int, + 'help': "A list of ports on which to apply the command", + 'default': []}), + + ALL_PORTS: ArgumentPack(['-a'], + {"action": "store_true", + "dest": "all_ports", + 'help': "Set this flag to apply the command on all available ports", + 'default': False},), + + DURATION: ArgumentPack(['-d'], + {'action': "store", + 'metavar': 'TIME', + 'dest': 'duration', + 'type': match_time_unit, + 'default': -1.0, + 'help': "Set duration time for TRex."}), + + FORCE: ArgumentPack(['--force'], + {"action": "store_true", + 'default': False, + 'help': "Set if you want to stop active ports before applying new TRex run on them."}), + + FILE_PATH: ArgumentPack(['-f'], + {'metavar': 'FILE', + 'dest': 'file', + 'nargs': 1, + 'type': is_valid_file, + 'help': "File path to YAML file that describes a stream pack. "}), + + FILE_FROM_DB: ArgumentPack(['--db'], + {'metavar': 'LOADED_STREAM_PACK', + 'help': "A stream pack which already loaded into console cache."}), + + SERVER_IP: ArgumentPack(['--server'], + {'metavar': 'SERVER', + 'help': "server IP"}), + + DRY_RUN: ArgumentPack(['-n', '--dry'], + {'action': 'store_true', + 'dest': 'dry', + 'default': False, + 'help': "Dry run - no traffic will be injected"}), + + + XTERM: ArgumentPack(['-x', '--xterm'], + {'action': 'store_true', + 'dest': 'xterm', + 'default': False, + 'help': "Starts TUI in xterm window"}), + + + FULL_OUTPUT: ArgumentPack(['--full'], + {'action': 'store_true', + 'help': "Prompt full info in a JSON format"}), + + GLOBAL_STATS: ArgumentPack(['-g'], + {'action': 'store_true', + 'help': "Fetch only global statistics"}), + + PORT_STATS: ArgumentPack(['-p'], + {'action': 'store_true', + 'help': "Fetch only port statistics"}), + + PORT_STATUS: ArgumentPack(['--ps'], + {'action': 'store_true', + 'help': "Fetch only port status data"}), + + STREAMS_MASK: ArgumentPack(['--streams'], + {"nargs": '+', + 'dest':'streams', + 'metavar': 'STREAMS', + 'type': int, + 'help': "A list of stream IDs to query about. Default: analyze all streams", + 'default': []}), + + + # advanced options + PORT_LIST_WITH_ALL: ArgumentGroup(MUTEX, [PORT_LIST, + ALL_PORTS], + {'required': False}), + + STREAM_FROM_PATH_OR_FILE: ArgumentGroup(MUTEX, [FILE_PATH, + FILE_FROM_DB], + {'required': True}), + STATS_MASK: ArgumentGroup(MUTEX, [GLOBAL_STATS, + PORT_STATS, + PORT_STATUS], + {}) + } + + +class CCmdArgParser(argparse.ArgumentParser): + + def __init__(self, stateless_client, *args, **kwargs): + super(CCmdArgParser, self).__init__(*args, **kwargs) + self.stateless_client = stateless_client + + def parse_args(self, args=None, namespace=None): + try: + opts = super(CCmdArgParser, self).parse_args(args, namespace) + if opts is None: + return None + + # if all ports are marked or + if (getattr(opts, "all_ports", None) == True) or (getattr(opts, "ports", None) == []): + opts.ports = self.stateless_client.get_all_ports() + + # so maybe we have ports configured + elif getattr(opts, "ports", None): + for port in opts.ports: + if not self.stateless_client._validate_port_list([port]): + self.error("port id '{0}' is not a valid port id\n".format(port)) + + return opts + + except SystemExit: + # recover from system exit scenarios, such as "help", or bad arguments. + return None + + +def get_flags (opt): + return OPTIONS_DB[opt].name_or_flags + +def gen_parser(stateless_client, op_name, description, *args): + parser = CCmdArgParser(stateless_client, prog=op_name, conflict_handler='resolve', + description=description) + for param in args: + try: + + if isinstance(param, int): + argument = OPTIONS_DB[param] + else: + argument = param + + if isinstance(argument, ArgumentGroup): + if argument.type == MUTEX: + # handle as mutually exclusive group + group = parser.add_mutually_exclusive_group(**argument.options) + for sub_argument in argument.args: + group.add_argument(*OPTIONS_DB[sub_argument].name_or_flags, + **OPTIONS_DB[sub_argument].options) + else: + # ignore invalid objects + continue + elif isinstance(argument, ArgumentPack): + parser.add_argument(*argument.name_or_flags, + **argument.options) + else: + # ignore invalid objects + continue + except KeyError as e: + cause = e.args[0] + raise KeyError("The attribute '{0}' is missing as a field of the {1} option.\n".format(cause, param)) + return parser + + +if __name__ == "__main__": + pass
\ No newline at end of file diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/text_opts.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/text_opts.py new file mode 100644 index 00000000..78a0ab1f --- /dev/null +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/text_opts.py @@ -0,0 +1,192 @@ +import json +import re + +TEXT_CODES = {'bold': {'start': '\x1b[1m', + 'end': '\x1b[22m'}, + 'cyan': {'start': '\x1b[36m', + 'end': '\x1b[39m'}, + 'blue': {'start': '\x1b[34m', + 'end': '\x1b[39m'}, + 'red': {'start': '\x1b[31m', + 'end': '\x1b[39m'}, + 'magenta': {'start': '\x1b[35m', + 'end': '\x1b[39m'}, + 'green': {'start': '\x1b[32m', + 'end': '\x1b[39m'}, + 'yellow': {'start': '\x1b[33m', + 'end': '\x1b[39m'}, + 'underline': {'start': '\x1b[4m', + 'end': '\x1b[24m'}} + +class TextCodesStripper: + keys = [re.escape(v['start']) for k,v in TEXT_CODES.iteritems()] + keys += [re.escape(v['end']) for k,v in TEXT_CODES.iteritems()] + pattern = re.compile("|".join(keys)) + + @staticmethod + def strip (s): + return re.sub(TextCodesStripper.pattern, '', s) + +def format_num (size, suffix = "", compact = True, opts = ()): + txt = "NaN" + + if type(size) == str: + return "N/A" + + u = '' + + if compact: + for unit in ['','K','M','G','T','P']: + if abs(size) < 1000.0: + u = unit + break + size /= 1000.0 + + if isinstance(size, float): + txt = "%3.2f" % (size) + else: + txt = "{:,}".format(size) + + if u or suffix: + txt += " {:}{:}".format(u, suffix) + + if isinstance(opts, tuple): + return format_text(txt, *opts) + else: + return format_text(txt, (opts)) + + + +def format_time (t_sec): + if t_sec < 0: + return "infinite" + + if t_sec < 1: + # low numbers + for unit in ['ms', 'usec', 'ns']: + t_sec *= 1000.0 + if t_sec >= 1.0: + return '{:,.2f} [{:}]'.format(t_sec, unit) + + return "NaN" + + else: + # seconds + if t_sec < 60.0: + return '{:,.2f} [{:}]'.format(t_sec, 'sec') + + # minutes + t_sec /= 60.0 + if t_sec < 60.0: + return '{:,.2f} [{:}]'.format(t_sec, 'minutes') + + # hours + t_sec /= 60.0 + if t_sec < 24.0: + return '{:,.2f} [{:}]'.format(t_sec, 'hours') + + # days + t_sec /= 24.0 + return '{:,.2f} [{:}]'.format(t_sec, 'days') + + +def format_percentage (size): + return "%0.2f %%" % (size) + +def bold(text): + return text_attribute(text, 'bold') + + +def cyan(text): + return text_attribute(text, 'cyan') + + +def blue(text): + return text_attribute(text, 'blue') + + +def red(text): + return text_attribute(text, 'red') + + +def magenta(text): + return text_attribute(text, 'magenta') + + +def green(text): + return text_attribute(text, 'green') + +def yellow(text): + return text_attribute(text, 'yellow') + +def underline(text): + return text_attribute(text, 'underline') + + +def text_attribute(text, attribute): + if isinstance(text, str): + return "{start}{txt}{stop}".format(start=TEXT_CODES[attribute]['start'], + txt=text, + stop=TEXT_CODES[attribute]['end']) + elif isinstance(text, unicode): + return u"{start}{txt}{stop}".format(start=TEXT_CODES[attribute]['start'], + txt=text, + stop=TEXT_CODES[attribute]['end']) + else: + raise Exception("not a string") + + +FUNC_DICT = {'blue': blue, + 'bold': bold, + 'green': green, + 'yellow': yellow, + 'cyan': cyan, + 'magenta': magenta, + 'underline': underline, + 'red': red} + + +def format_text(text, *args): + return_string = text + for i in args: + func = FUNC_DICT.get(i) + if func: + return_string = func(return_string) + + return return_string + + +def format_threshold (value, red_zone, green_zone): + if value >= red_zone[0] and value <= red_zone[1]: + return format_text("{0}".format(value), 'red') + + if value >= green_zone[0] and value <= green_zone[1]: + return format_text("{0}".format(value), 'green') + + return "{0}".format(value) + +# pretty print for JSON +def pretty_json (json_str, use_colors = True): + pretty_str = json.dumps(json.loads(json_str), indent = 4, separators=(',', ': '), sort_keys = True) + + if not use_colors: + return pretty_str + + try: + # int numbers + pretty_str = re.sub(r'([ ]*:[ ]+)(\-?[1-9][0-9]*[^.])',r'\1{0}'.format(blue(r'\2')), pretty_str) + # float + pretty_str = re.sub(r'([ ]*:[ ]+)(\-?[1-9][0-9]*\.[0-9]+)',r'\1{0}'.format(magenta(r'\2')), pretty_str) + # # strings + # + pretty_str = re.sub(r'([ ]*:[ ]+)("[^"]*")',r'\1{0}'.format(red(r'\2')), pretty_str) + pretty_str = re.sub(r"('[^']*')", r'{0}\1{1}'.format(TEXT_CODES['magenta']['start'], + TEXT_CODES['red']['start']), pretty_str) + except : + pass + + return pretty_str + + +if __name__ == "__main__": + pass diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/text_tables.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/text_tables.py new file mode 100644 index 00000000..07753fda --- /dev/null +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/text_tables.py @@ -0,0 +1,31 @@ +from texttable import Texttable +from text_opts import format_text + +class TRexTextTable(Texttable): + + def __init__(self): + Texttable.__init__(self) + # set class attributes so that it'll be more like TRex standard output + self.set_chars(['-', '|', '-', '-']) + self.set_deco(Texttable.HEADER | Texttable.VLINES) + +class TRexTextInfo(Texttable): + + def __init__(self): + Texttable.__init__(self) + # set class attributes so that it'll be more like TRex standard output + self.set_chars(['-', ':', '-', '-']) + self.set_deco(Texttable.VLINES) + +def generate_trex_stats_table(): + pass + +def print_table_with_header(texttable_obj, header="", untouched_header=""): + header = header.replace("_", " ").title() + untouched_header + print format_text(header, 'cyan', 'underline') + "\n" + + print (texttable_obj.draw() + "\n").encode('utf-8') + +if __name__ == "__main__": + pass + |