diff options
Diffstat (limited to 'scripts/automation/trex_control_plane/stl')
5 files changed, 250 insertions, 156 deletions
diff --git a/scripts/automation/trex_control_plane/stl/console/trex_capture.py b/scripts/automation/trex_control_plane/stl/console/trex_capture.py index 922497db..e5708e9b 100644 --- a/scripts/automation/trex_control_plane/stl/console/trex_capture.py +++ b/scripts/automation/trex_control_plane/stl/console/trex_capture.py @@ -1,7 +1,7 @@ from trex_stl_lib.api import * from trex_stl_lib.utils import parsing_opts, text_tables import threading - +import tempfile class CaptureMonitorWriter(object): def init (self): @@ -27,7 +27,7 @@ class CaptureMonitorWriterStdout(CaptureMonitorWriter): def init (self): self.logger.log(format_text("\nStarting capture monitor on selected ports", 'bold')) self.logger.log(format_text("*** any captured packet will be displayed on screen ***\n")) - self.logger.log(format_text("('capture monitor --stop' to abort capturing...)\n", 'bold')) + self.logger.log(format_text("('capture monitor stop' to abort capturing...)\n", 'bold')) def deinit (self): @@ -81,18 +81,42 @@ class CaptureMonitorWriterPipe(CaptureMonitorWriter): self.logger = logger def init (self): - self.fifo_name = '/tmp/out.fif' - if os.path.exists(self.fifo_name): - os.unlink(self.fifo_name) + self.fifo_name = tempfile.mktemp() + + try: + os.mkfifo(self.fifo_name) + + self.logger.log(format_text("\nPlease run 'wireshark -k -i {0}'".format(self.fifo_name), 'bold')) + self.logger.log('\nWaiting for Wireshark connection...') + + self.fifo = os.open(self.fifo_name, os.O_WRONLY) + self.logger.log('Successfuly connected to Wireshark...') + self.logger.log(format_text('\n*** Capture monitoring started ***\n', 'bold')) - os.mkfifo(self.fifo_name) - self.fifo = os.open(self.fifo_name, os.O_WRONLY) + self.writer = RawPcapWriter(self.fifo_name, linktype = 1, sync = True) + self.writer._write_header(None) + + except KeyboardInterrupt as e: + os.unlink(self.fifo_name) + raise STLError("*** pipe monitor aborted...cleaning up") + + except OSError as e: + os.unlink(self.fifo_name) + raise STLError("failed to create pipe {0}\n{1}".format(self.fifo_name, str(e))) + + + def deinit (self): + os.unlink(self.fifo_name) + - self.writer = RawPcapWriter(self.fifo_name, linktype = 1, sync = True) - self.writer._write_header(None) - def handle_pkts (self, pkts): - pass + for pkt in pkts: + pkt_bin = base64.b64decode(pkt['binary']) + try: + self.writer._write_packet(pkt_bin, sec = 0, usec = 0) + except IOError: + klgjdf + class CaptureMonitor(object): @@ -127,14 +151,14 @@ class CaptureMonitor(object): raise STLError('unknown writer type') - self.writer.init() - with self.logger.supress(): self.capture_id = self.client.start_capture(tx_port_list, rx_port_list, limit = rate_pps) self.tx_port_list = tx_port_list self.rx_port_list = rx_port_list + self.writer.init() + self.t = threading.Thread(target = self.__thread_cb) self.t.setDaemon(True) @@ -154,6 +178,7 @@ class CaptureMonitor(object): self.client.stop_capture(self.capture_id, None) self.capture_id = -1 self.writer.deinit() + def get_mon_row (self): if not self.is_active(): @@ -215,7 +240,7 @@ class CaptureMonitor(object): pkts = rc.data()['pkts'] if not pkts: continue - + self.writer.handle_pkts(pkts) @@ -232,33 +257,50 @@ class CaptureManager(object): # install parsers self.parser = parsing_opts.gen_parser(self, "capture", self.parse_line_internal.__doc__) - subparsers = self.parser.add_subparsers(title = "commands", dest="commands") + self.subparsers = self.parser.add_subparsers(title = "commands", dest="commands") + + self.install_record_parser() + self.install_monitor_parser() + + # show + self.show_parser = self.subparsers.add_parser('show', help = "show all active captures") + + # reset + self.clear_parser = self.subparsers.add_parser('clear', help = "remove all active captures") + + # register handlers + self.cmds = {'record': self.parse_record, 'monitor' : self.parse_monitor, 'clear': self.parse_clear, 'show' : self.parse_show} + + + def install_record_parser (self): + # recording + self.record_parser = self.subparsers.add_parser('record', help = "PCAP recording") + record_sub = self.record_parser.add_subparsers(title = 'commands', dest = 'record_cmd') + self.record_start_parser = record_sub.add_parser('start', help = "starts a new buffered capture") + self.record_stop_parser = record_sub.add_parser('stop', help = "stops an active buffered capture") # start - self.start_parser = subparsers.add_parser('start', help = "starts a new buffered capture") - self.start_parser.add_arg_list(parsing_opts.TX_PORT_LIST, - parsing_opts.RX_PORT_LIST, - parsing_opts.LIMIT) + self.record_start_parser.add_arg_list(parsing_opts.TX_PORT_LIST, + parsing_opts.RX_PORT_LIST, + parsing_opts.LIMIT) # stop - self.stop_parser = subparsers.add_parser('stop', help = "stops an active capture") - self.stop_parser.add_arg_list(parsing_opts.CAPTURE_ID, - parsing_opts.OUTPUT_FILENAME) - - # show - self.show_parser = subparsers.add_parser('show', help = "show all active captures") + self.record_stop_parser.add_arg_list(parsing_opts.CAPTURE_ID, + parsing_opts.OUTPUT_FILENAME) + + + def install_monitor_parser (self): # monitor - self.monitor_parser = subparsers.add_parser('monitor', help = "attach a constant monitor to port(s)") - self.monitor_parser.add_arg_list(parsing_opts.TX_PORT_LIST, - parsing_opts.RX_PORT_LIST, - parsing_opts.MONITOR_TYPE) + self.monitor_parser = self.subparsers.add_parser('monitor', help = 'live monitoring') + monitor_sub = self.monitor_parser.add_subparsers(title = 'commands', dest = 'mon_cmd') + self.monitor_start_parser = monitor_sub.add_parser('start', help = 'starts a monitor') + self.monitor_stop_parser = monitor_sub.add_parser('stop', help = 'stops an active monitor') + + self.monitor_start_parser.add_arg_list(parsing_opts.TX_PORT_LIST, + parsing_opts.RX_PORT_LIST, + parsing_opts.MONITOR_TYPE) - # reset - self.clear_parser = subparsers.add_parser('clear', help = "remove all active captures") - - # register handlers - self.cmds = {'start': self.parse_start, 'stop' : self.parse_stop, 'clear': self.parse_clear, 'monitor': self.parse_monitor, 'show' : self.parse_show} def stop (self): @@ -289,22 +331,48 @@ class CaptureManager(object): self.cmds[opts.commands](opts) - def parse_start (self, opts): + # record methods + def parse_record (self, opts): + if opts.record_cmd == 'start': + self.parse_record_start(opts) + elif opts.record_cmd == 'stop': + self.parse_record_stop(opts) + else: + assert(0) + + def parse_record_start (self, opts): if not opts.tx_port_list and not opts.rx_port_list: - self.start_parser.formatted_error('please provide either --tx or --rx') + self.record_start_parser.formatted_error('please provide either --tx or --rx') return self.c.start_capture(opts.tx_port_list, opts.rx_port_list, opts.limit) - def parse_stop (self, opts): - if self.monitor.is_active() and self.monitor.get_capture_id() == opts.capture_id: - self.monitor.stop() - else: - self.c.stop_capture(opts.capture_id, opts.output_filename) + def parse_record_stop (self, opts): + captures = self.c.get_capture_status() + ids = [c['id'] for c in captures] + + if opts.capture_id == self.monitor.get_capture_id(): + self.record_stop_parser.formatted_error("'{0}' is a monitor, please use 'capture monitor stop'".format(opts.capture_id)) + return + + if opts.capture_id not in ids: + self.record_stop_parser.formatted_error("'{0}' is not an active capture ID".format(opts.capture_id)) + return + + self.c.stop_capture(opts.capture_id, opts.output_filename) + # monitor methods def parse_monitor (self, opts): + if opts.mon_cmd == 'start': + self.parse_monitor_start(opts) + elif opts.mon_cmd == 'stop': + self.parse_monitor_stop(opts) + else: + assert(0) + + def parse_monitor_start (self, opts): mon_type = 'compact' if opts.verbose: @@ -312,10 +380,15 @@ class CaptureManager(object): elif opts.pipe: mon_type = 'pipe' - + if not opts.tx_port_list and not opts.rx_port_list: + self.monitor_start_parser.formatted_error('please provide either --tx or --rx') + return + self.monitor.stop() - self.monitor.start(opts.tx_port_list, opts.rx_port_list, 10, mon_type) + self.monitor.start(opts.tx_port_list, opts.rx_port_list, 100, mon_type) + def parse_monitor_stop (self, opts): + self.monitor.stop() def parse_clear (self, opts): self.monitor.stop() @@ -357,9 +430,9 @@ class CaptureManager(object): mon_table.header(['ID', 'Packets Seen', 'Bytes Seen', 'TX Ports', 'RX Ports']) if cap_table._rows: - text_tables.print_table_with_header(cap_table, "Buffers") + text_tables.print_table_with_header(cap_table, '\nActive Recorders') if mon_table._rows: - text_tables.print_table_with_header(mon_table, "Monitors") + text_tables.print_table_with_header(mon_table, '\nActive Monitor') diff --git a/scripts/automation/trex_control_plane/stl/console/trex_console.py b/scripts/automation/trex_control_plane/stl/console/trex_console.py index b0ab70e0..bf543045 100755 --- a/scripts/automation/trex_control_plane/stl/console/trex_console.py +++ b/scripts/automation/trex_control_plane/stl/console/trex_console.py @@ -29,6 +29,8 @@ import string import os import sys import tty, termios +from threading import Lock +import threading try: import stl_path @@ -39,6 +41,7 @@ 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 +from .trex_capture import CaptureManager try: import trex_tui @@ -172,6 +175,8 @@ class TRexConsole(TRexGeneralCmd): def __init__(self, stateless_client, verbose = False): + self.cmd_lock = Lock() + self.stateless_client = stateless_client TRexGeneralCmd.__init__(self) @@ -184,8 +189,11 @@ class TRexConsole(TRexGeneralCmd): self.intro = "\n-=TRex Console v{ver}=-\n".format(ver=__version__) self.intro += "\nType 'help' or '?' for supported actions\n" + self.cap_mngr = CaptureManager(stateless_client, self.cmd_lock) + self.postcmd(False, "") + ################### internal section ######################## @@ -231,6 +239,7 @@ class TRexConsole(TRexGeneralCmd): lines = line.split(';') try: + self.cmd_lock.acquire() for line in lines: stop = self.onecmd(line) stop = self.postcmd(stop, line) @@ -238,10 +247,15 @@ class TRexConsole(TRexGeneralCmd): return "quit" return "" + except STLError as e: print(e) return '' + finally: + self.cmd_lock.release() + + def postcmd(self, stop, line): self.prompt = self.stateless_client.generate_prompt(prefix = 'trex') @@ -349,7 +363,7 @@ class TRexConsole(TRexGeneralCmd): @verify_connected def do_capture (self, line): '''Manage PCAP captures''' - self.stateless_client.capture_line(line) + self.cap_mngr.parse_line(line) def help_capture (self): self.do_capture("-h") @@ -443,7 +457,9 @@ class TRexConsole(TRexGeneralCmd): def do_disconnect (self, line): '''Disconnect from the server\n''' - + + # stop any monitors before disconnecting + self.cap_mngr.stop() self.stateless_client.disconnect_line(line) @@ -688,6 +704,7 @@ class TRexConsole(TRexGeneralCmd): l=help.splitlines() print("{:<30} {:<30}".format(cmd + " - ",l[0] )) + # a custorm cmdloop wrapper def start(self): while True: @@ -702,6 +719,9 @@ class TRexConsole(TRexGeneralCmd): self.intro = None continue + finally: + self.cap_mngr.stop() + if self.terminal: self.terminal.kill() @@ -934,6 +954,8 @@ def main(): with stateless_client.logger.supress(): stateless_client.disconnect(stop_traffic = False) + + if __name__ == '__main__': main() diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py index d75c554e..c632ad7c 100755 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_client.py @@ -17,10 +17,12 @@ from .utils.text_opts import * from functools import wraps from texttable import ansi_len + from collections import namedtuple from yaml import YAMLError import time import datetime +import threading import re import random import json @@ -601,7 +603,7 @@ class STLClient(object): self.util_stats, self.xstats, self.async_client.monitor) - + ############# private functions - used by the class itself ########### @@ -2981,7 +2983,7 @@ class STLClient(object): self.logger.post_cmd(rc) if verbose: - for x in filter(bool, rc.data()): + for x in filter(bool, listify(rc.data())): self.logger.log(format_text("{0}".format(x), 'bold')) if not rc: @@ -2999,6 +3001,10 @@ class STLClient(object): tx_ports - on which ports to capture TX rx_ports - on which ports to capture RX limit - limit how many packets will be written + + :returns: + the new capture_id + :raises: + :exe:'STLError' @@ -3018,7 +3024,7 @@ class STLClient(object): non_service_ports = list_difference(set(tx_ports + rx_ports), self.get_service_enabled_ports()) if non_service_ports: - raise STLError("Port(s) {0} are not under service mode. PCAP capturing requires all ports to be in service mode") + raise STLError("Port(s) {0} are not under service mode. PCAP capturing requires all ports to be in service mode".format(non_service_ports)) self.logger.pre_cmd("Starting PCAP capturing up to {0} packets".format(limit)) @@ -3030,8 +3036,39 @@ class STLClient(object): if not rc: raise STLError(rc) + return rc.data()['capture_id'] + + + def __fetch_capture_packets (self, capture_id, output_filename, pkt_count): + self.logger.pre_cmd("Writing {0} packets to '{1}'".format(pkt_count, output_filename)) + + # create a PCAP file + writer = RawPcapWriter(output_filename, linktype = 1) + writer._write_header(None) + + # fetch + pending = pkt_count + rc = RC_OK() + while pending > 0: + rc = self._transmit("capture", params = {'command': 'fetch', 'capture_id': capture_id, 'pkt_limit': 50}) + if not rc: + self.logger.post_cmd(rc) + raise STLError(rc) + + pkts = rc.data()['pkts'] + for pkt in pkts: + ts = pkt['ts'] + pkt_bin = base64.b64decode(pkt['binary']) + writer._write_packet(pkt_bin, sec = 0, usec = 0) + + pending = rc.data()['pending'] + + + self.logger.post_cmd(rc) + + @__api_check(True) def stop_capture (self, capture_id, output_filename): """ @@ -3045,8 +3082,6 @@ class STLClient(object): + :exe:'STLError' """ - - # stopping a capture requires: # 1. stopping @@ -3063,36 +3098,19 @@ class STLClient(object): # pkt count pkt_count = rc.data()['pkt_count'] - - if not output_filename or pkt_count == 0: - return - self.logger.pre_cmd("Writing {0} packets to '{1}'".format(pkt_count, output_filename)) - - # create a PCAP file - writer = RawPcapWriter(output_filename, linktype = 1) - writer._write_header(None) - - # fetch - while True: - rc = self._transmit("capture", params = {'command': 'fetch', 'capture_id': capture_id, 'pkt_limit': 50}) - if not rc: - self.logger.post_cmd(rc) - raise STLError(rc) - - pkts = rc.data()['pkts'] - for pkt in pkts: - ts = pkt['ts'] - pkt_bin = base64.b64decode(pkt['binary']) - writer._write_packet(pkt_bin, sec = 0, usec = 0) - - if rc.data()['pending'] == 0: - break + # fetch packets + if output_filename: + self.__fetch_capture_packets(capture_id, output_filename, pkt_count) + # remove + self.logger.pre_cmd("Removing PCAP capture {0} from server".format(capture_id)) + rc = self._transmit("capture", params = {'command': 'remove', 'capture_id': capture_id}) self.logger.post_cmd(rc) + if not rc: + raise STLError(rc) - # get capture status @__api_check(True) def get_capture_status (self): """ @@ -3109,7 +3127,25 @@ class STLClient(object): return rc.data() - + @__api_check(True) + def remove_all_captures (self): + """ + Removes any existing captures + """ + captures = self.get_capture_status() + + self.logger.pre_cmd("Removing all PCAP captures from server") + + for c in captures: + # remove + rc = self._transmit("capture", params = {'command': 'remove', 'capture_id': c['id']}) + if not rc: + raise STLError(rc) + + self.logger.post_cmd(RC_OK()) + + + @__api_check(True) def set_rx_queue (self, ports = None, size = 1000): """ @@ -3230,6 +3266,7 @@ class STLClient(object): return wrap + @__console def ping_line (self, line): '''pings the server / specific IP''' @@ -3820,77 +3857,9 @@ class STLClient(object): opts.link, opts.led, opts.flow_ctrl) + + - - - - @__console - def capture_line (self, line): - '''Manage PCAP recorders''' - - # default - if not line: - line = "show" - - parser = parsing_opts.gen_parser(self, "capture", self.capture_line.__doc__) - subparsers = parser.add_subparsers(title = "commands", dest="commands") - - # start - start_parser = subparsers.add_parser('start', help = "starts a new capture") - start_parser.add_arg_list(parsing_opts.TX_PORT_LIST, - parsing_opts.RX_PORT_LIST, - parsing_opts.LIMIT) - - # stop - stop_parser = subparsers.add_parser('stop', help = "stops an active capture") - stop_parser.add_arg_list(parsing_opts.CAPTURE_ID, - parsing_opts.OUTPUT_FILENAME) - - # show - show_parser = subparsers.add_parser('show', help = "show all active captures") - - opts = parser.parse_args(line.split()) - - if not opts: - return opts - - # start - if opts.commands == 'start': - if not opts.tx_port_list and not opts.rx_port_list: - start_parser.formatted_error('please provide either --tx or --rx') - return - - self.start_capture(opts.tx_port_list, opts.rx_port_list, opts.limit) - - # stop - elif opts.commands == 'stop': - self.stop_capture(opts.capture_id, opts.output_filename) - - # show - else: - data = self.get_capture_status() - - stats_table = text_tables.TRexTextTable() - stats_table.set_cols_align(["c"] * 6) - stats_table.set_cols_width([15] * 6) - - for elem in data: - row = [elem['id'], - elem['state'], - '[{0}/{1}]'.format(elem['count'], elem['limit']), - format_num(elem['bytes'], suffix = 'B'), - bitfield_to_str(elem['filter']['tx']), - bitfield_to_str(elem['filter']['rx'])] - - stats_table.add_rows([row], header=False) - - stats_table.header(['ID', 'Status', 'Count', 'Bytes', 'TX Ports', 'RX Ports']) - text_tables.print_table_with_header(stats_table, "Captures") - - - return RC_OK() - - @__console def resolve_line (self, line): @@ -4089,3 +4058,4 @@ class STLClient(object): self.set_service_mode(ports = opts.ports, enabled = opts.enabled) + 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 index cb594ef4..8d3aedbe 100755 --- 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 @@ -85,6 +85,10 @@ STREAMS_MASK CORE_MASK_GROUP CAPTURE_PORTS_GROUP +MONITOR_TYPE_VERBOSE +MONITOR_TYPE_PIPE +MONITOR_TYPE + # ALL_STREAMS # STREAM_LIST_WITH_ALL @@ -606,6 +610,7 @@ OPTIONS_DB = {MULTIPLIER: ArgumentPack(['-m', '--multiplier'], 'help': 'A list of ports to capture on the TX side', 'default': []}), + RX_PORT_LIST: ArgumentPack(['--rx'], {'nargs': '+', 'dest':'rx_port_list', @@ -614,7 +619,21 @@ OPTIONS_DB = {MULTIPLIER: ArgumentPack(['-m', '--multiplier'], 'type': int, 'help': 'A list of ports to capture on the RX side', 'default': []}), - + + + MONITOR_TYPE_VERBOSE: ArgumentPack(['-v', '--verbose'], + {'action': 'store_true', + 'dest': 'verbose', + 'default': False, + 'help': 'output to screen as verbose'}), + + MONITOR_TYPE_PIPE: ArgumentPack(['-p', '--pipe'], + {'action': 'store_true', + 'dest': 'pipe', + 'default': False, + 'help': 'forward packets to a pipe'}), + + CAPTURE_ID: ArgumentPack(['-i', '--id'], {'help': "capture ID to remove", 'dest': "capture_id", @@ -646,6 +665,12 @@ OPTIONS_DB = {MULTIPLIER: ArgumentPack(['-m', '--multiplier'], {'required': False}), CAPTURE_PORTS_GROUP: ArgumentGroup(NON_MUTEX, [TX_PORT_LIST, RX_PORT_LIST], {}), + + + MONITOR_TYPE: ArgumentGroup(MUTEX, [MONITOR_TYPE_VERBOSE, + MONITOR_TYPE_PIPE], + {'required': False}), + } class _MergeAction(argparse._AppendAction): @@ -760,7 +785,7 @@ class CCmdArgParser(argparse.ArgumentParser): def formatted_error (self, msg): self.print_usage() - self.stateless_client.logger.log(msg) + self._print_message(('%s: error: %s\n') % (self.prog, msg)) def get_flags (opt): 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 index 63b05bf4..3ffd07e2 100644 --- 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 @@ -133,12 +133,16 @@ def underline(text): # apply attribute on each non-empty line def text_attribute(text, attribute): - return '\n'.join(['{start}{txt}{end}'.format( - start = TEXT_CODES[attribute]['start'], - txt = line, - end = TEXT_CODES[attribute]['end']) - if line else '' for line in ('%s' % text).split('\n')]) - + 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, |