summaryrefslogtreecommitdiffstats
path: root/scripts/automation/trex_control_plane/stl/console/trex_console.py
diff options
context:
space:
mode:
authorimarom <imarom@cisco.com>2016-02-08 10:55:20 -0500
committerimarom <imarom@cisco.com>2016-02-08 10:55:20 -0500
commit6107c1ca4aa485c5971ff3326513b8f4934f7ac1 (patch)
treea109d80501bd087e3219f68c186fb55bc17e090a /scripts/automation/trex_control_plane/stl/console/trex_console.py
parentf5a5e50bfe046148a20f6ce578d6082119dec2c0 (diff)
huge refactor - again
Diffstat (limited to 'scripts/automation/trex_control_plane/stl/console/trex_console.py')
-rwxr-xr-xscripts/automation/trex_control_plane/stl/console/trex_console.py784
1 files changed, 784 insertions, 0 deletions
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()
+