From 2828fc9aab33b742c59a499dbf06ea2239ec6220 Mon Sep 17 00:00:00 2001 From: imarom Date: Tue, 26 Jan 2016 08:57:43 -0500 Subject: API simplification and example --- .../trex_control_plane/client/trex_port.py | 60 +++----- .../client/trex_stateless_client.py | 77 +++------- .../trex_control_plane/common/trex_stats.py | 3 +- .../common/trex_stl_exceptions.py | 53 +++++++ .../trex_control_plane/common/trex_streams.py | 162 +++++++++++++++++++++ .../trex_control_plane/console/trex_console.py | 5 +- 6 files changed, 260 insertions(+), 100 deletions(-) create mode 100644 scripts/automation/trex_control_plane/common/trex_stl_exceptions.py (limited to 'scripts') diff --git a/scripts/automation/trex_control_plane/client/trex_port.py b/scripts/automation/trex_control_plane/client/trex_port.py index 438ff4be..c8147faf 100644 --- a/scripts/automation/trex_control_plane/client/trex_port.py +++ b/scripts/automation/trex_control_plane/client/trex_port.py @@ -49,7 +49,6 @@ class Port(object): self.streams = {} self.profile = None self.session_id = session_id - self.loaded_stream_pack = None self.port_stats = trex_stats.CPortStats(self) @@ -139,63 +138,44 @@ class Port(object): # operations on port can be done on state idle or state streams return ((self.state == self.STATE_IDLE) or (self.state == self.STATE_STREAMS)) - # add stream to the port - def add_stream (self, stream_id, stream_obj): - if not self.is_port_writable(): - return self.err("Please stop port before attempting to add streams") - - - params = {"handler": self.handler, - "port_id": self.port_id, - "stream_id": stream_id, - "stream": stream_obj} + # add streams + def add_streams (self, streams_list): - rc = self.transmit("add_stream", params) - if rc.bad(): - return self.err(rc.err()) - - # add the stream - self.streams[stream_id] = StreamOnPort(stream_obj, Port._generate_stream_metadata(stream_id, stream_obj)) - - # the only valid state now - self.state = self.STATE_STREAMS + if not self.is_acquired(): + return self.err("port is not owned") - return self.ok() + if not self.is_port_writable(): + return self.err("Please stop port before attempting to add streams") - # add multiple streams - def add_streams (self, LoadedStreamList_obj): batch = [] + for stream in (streams_list if isinstance(streams_list, list) else [streams_list]): - self.loaded_stream_pack = LoadedStreamList_obj - compiled_stream_list = LoadedStreamList_obj.compiled - - for stream_pack in compiled_stream_list: params = {"handler": self.handler, "port_id": self.port_id, - "stream_id": stream_pack.stream_id, - "stream": stream_pack.stream} + "stream_id": stream.get_id(), + "stream": stream.to_json()} cmd = RpcCmdData('add_stream', params) batch.append(cmd) + # meta data for show streams + self.streams[stream.get_id()] = StreamOnPort(stream.to_json(), + Port._generate_stream_metadata(stream.get_id(), stream.to_json())) + rc = self.transmit_batch(batch) - if rc.bad(): + if not rc: return self.err(rc.err()) - # validate that every action succeeded - - # add the stream - for stream_pack in compiled_stream_list: - self.streams[stream_pack.stream_id] = StreamOnPort(stream_pack.stream, - Port._generate_stream_metadata(stream_pack.stream_id, - stream_pack.stream)) + # the only valid state now self.state = self.STATE_STREAMS return self.ok() + + # remove stream from port def remove_stream (self, stream_id): @@ -461,10 +441,6 @@ class Port(object): def generate_loaded_streams_sum(self, stream_id_list): if self.state == self.STATE_DOWN or self.state == self.STATE_STREAMS: return {} - elif self.loaded_stream_pack is None: - # avoid crashing when sync with remote server isn't operational - # TODO: MAKE SURE TO HANDLE THIS CASE FOR BETTER UX - return {} streams_data = {} if not stream_id_list: @@ -477,7 +453,7 @@ class Port(object): if stream_id in self.streams} - return {"referring_file" : self.loaded_stream_pack.name, + return {"referring_file" : "", "streams" : streams_data} @staticmethod diff --git a/scripts/automation/trex_control_plane/client/trex_stateless_client.py b/scripts/automation/trex_control_plane/client/trex_stateless_client.py index 6b8e42a6..886edb61 100755 --- a/scripts/automation/trex_control_plane/client/trex_stateless_client.py +++ b/scripts/automation/trex_control_plane/client/trex_stateless_client.py @@ -23,56 +23,9 @@ import re import random from trex_port import Port from common.trex_types import * +from common.trex_stl_exceptions import * from trex_async_client import CTRexAsyncClient -# basic error for API -class STLError(Exception): - def __init__ (self, msg): - self.msg = str(msg) - - def __str__ (self): - exc_type, exc_obj, exc_tb = sys.exc_info() - fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1] - - - s = "\n******\n" - s += "Error at {0}:{1}\n\n".format(format_text(fname, 'bold'), format_text(exc_tb.tb_lineno), 'bold') - s += "specific error:\n\n{0}\n".format(format_text(self.msg, 'bold')) - - return s - - def brief (self): - return self.msg - - -# raised when the client state is invalid for operation -class STLStateError(STLError): - def __init__ (self, op, state): - self.msg = "Operation '{0}' is not valid while '{1}'".format(op, state) - - -# port state error -class STLPortStateError(STLError): - def __init__ (self, port, op, state): - self.msg = "Operation '{0}' on port(s) '{1}' is not valid while port(s) '{2}'".format(op, port, state) - - -# raised when argument is not valid for operation -class STLArgumentError(STLError): - def __init__ (self, name, got, valid_values = None, extended = None): - self.msg = "Argument: '{0}' invalid value: '{1}'".format(name, got) - if valid_values: - self.msg += " - valid values are '{0}'".format(valid_values) - - if extended: - self.msg += "\n{0}".format(extended) - -# raised when timeout occurs -class STLTimeoutError(STLError): - def __init__ (self, timeout): - self.msg = "Timeout: operation took more than '{0}' seconds".format(timeout) - - ############################ logger ############################# ############################ ############################# @@ -541,14 +494,14 @@ class STLClient(object): return rc - def __add_stream(self, stream_id, stream_obj, port_id_list = None): + def __add_streams(self, stream_list, port_id_list = None): port_id_list = self.__ports(port_id_list) rc = RC() for port_id in port_id_list: - rc.add(self.ports[port_id].add_stream(stream_id, stream_obj)) + rc.add(self.ports[port_id].add_streams(stream_list)) return rc @@ -1209,7 +1162,8 @@ class STLClient(object): :parameters: ports : list ports to execute the command - + streams: list + streams to attach :raises: + :exc:`STLError` @@ -1226,8 +1180,16 @@ class STLClient(object): if not rc: raise STLArgumentError('ports', ports, valid_values = self.get_all_ports()) - self.logger.pre_cmd("Attaching {0} streams to port(s) {1}:".format(len(streams.compiled), ports)) - rc = self.__add_stream_pack(streams, ports) + # transform single stream + if not isinstance(streams, list): + streams = [streams] + + # check streams + if not all([isinstance(stream, STLStream) for stream in streams]): + raise STLArgumentError('streams', streams) + + self.logger.pre_cmd("Attaching {0} streams to port(s) {1}:".format(len(streams), ports)) + rc = self.__add_streams(streams, ports) self.logger.post_cmd(rc) if not rc: @@ -1266,11 +1228,16 @@ class STLClient(object): # load the YAML try: - streams = self.streams_db.load_yaml_file(filename) + streams_pack = self.streams_db.load_yaml_file(filename) except Exception as e: raise STLError(str(e)) - # attach + # HACK - convert the stream pack to simple streams + streams = [] + for stream in streams_pack.compiled: + s = HACKSTLStream(stream) + streams.append(s) + self.add_streams(streams, ports) diff --git a/scripts/automation/trex_control_plane/common/trex_stats.py b/scripts/automation/trex_control_plane/common/trex_stats.py index 52c0c0a1..464ee56a 100755 --- a/scripts/automation/trex_control_plane/common/trex_stats.py +++ b/scripts/automation/trex_control_plane/common/trex_stats.py @@ -223,7 +223,8 @@ class CTRexInfoGenerator(object): info_table = text_tables.TRexTextTable() info_table.set_cols_align(["c"] + ["l"] + ["r"] + ["c"] + ["r"] + ["c"]) - info_table.set_cols_width([4] + [20] + [8] + [16] + [10] + [12]) + info_table.set_cols_width([10] + [20] + [8] + [16] + [10] + [12]) + info_table.set_cols_dtype(["t"] + ["t"] + ["t"] + ["t"] + ["t"] + ["t"]) info_table.add_rows([v.values() for k, v in return_streams_data['streams'].iteritems()], diff --git a/scripts/automation/trex_control_plane/common/trex_stl_exceptions.py b/scripts/automation/trex_control_plane/common/trex_stl_exceptions.py new file mode 100644 index 00000000..9be20db9 --- /dev/null +++ b/scripts/automation/trex_control_plane/common/trex_stl_exceptions.py @@ -0,0 +1,53 @@ +import os +import sys +from common.text_opts import * + +# basic error for API +class STLError(Exception): + def __init__ (self, msg): + self.msg = str(msg) + + def __str__ (self): + exc_type, exc_obj, exc_tb = sys.exc_info() + fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1] + + + s = "\n******\n" + s += "Error at {0}:{1}\n\n".format(format_text(fname, 'bold'), format_text(exc_tb.tb_lineno), 'bold') + s += "specific error:\n\n{0}\n".format(format_text(self.msg, 'bold')) + + return s + + def brief (self): + return self.msg + + +# raised when the client state is invalid for operation +class STLStateError(STLError): + def __init__ (self, op, state): + self.msg = "Operation '{0}' is not valid while '{1}'".format(op, state) + + +# port state error +class STLPortStateError(STLError): + def __init__ (self, port, op, state): + self.msg = "Operation '{0}' on port(s) '{1}' is not valid while port(s) '{2}'".format(op, port, state) + + +# raised when argument is not valid for operation +class STLArgumentError(STLError): + def __init__ (self, name, got, valid_values = None, extended = None): + self.msg = "Argument: '{0}' invalid value: '{1}'".format(name, got) + if valid_values: + self.msg += " - valid values are '{0}'".format(valid_values) + + if extended: + self.msg += "\n{0}".format(extended) + +# raised when timeout occurs +class STLTimeoutError(STLError): + def __init__ (self, timeout): + self.msg = "Timeout: operation took more than '{0}' seconds".format(timeout) + + + diff --git a/scripts/automation/trex_control_plane/common/trex_streams.py b/scripts/automation/trex_control_plane/common/trex_streams.py index ea3d71d1..90cb812d 100755 --- a/scripts/automation/trex_control_plane/common/trex_streams.py +++ b/scripts/automation/trex_control_plane/common/trex_streams.py @@ -4,10 +4,12 @@ import external_packages from client_utils.packet_builder import CTRexPktBuilder from collections import OrderedDict, namedtuple from client_utils.yaml_utils import * +import trex_stl_exceptions import dpkt import struct import copy import os +import random StreamPack = namedtuple('StreamPack', ['stream_id', 'stream']) LoadedStreamList = namedtuple('LoadedStreamList', ['name', 'loaded', 'compiled']) @@ -323,3 +325,163 @@ class CStreamsDB(object): else: return self.stream_packs.get(name) + +########################### Simple Streams ########################### +from trex_stl_exceptions import * + +class STLStream(object): + + def __init__ (self, + packet, + pps = 1, + enabled = True, + self_start = True, + isg = 0.0, + rx_stats = None, + next_stream_id = -1): + + # type checking + if not isinstance(pps, (int, float)): + raise STLArgumentError('pps', pps) + + if not isinstance(packet, CTRexPktBuilder): + raise STLArgumentError('packet', packet) + + if not isinstance(enabled, bool): + raise STLArgumentError('enabled', enabled) + + if not isinstance(self_start, bool): + raise STLArgumentError('self_start', self_start) + + if not isinstance(isg, (int, float)): + raise STLArgumentError('isg', isg) + + # use a random 31 bit for ID + self.stream_id = random.getrandbits(31) + + self.fields = {} + + # basic fields + self.fields['enabled'] = enabled + self.fields['self_start'] = self_start + self.fields['isg'] = isg + + self.fields['next_stream_id'] = next_stream_id + + # mode + self.fields['mode'] = {} + self.fields['mode']['pps'] = pps + + # packet and VM + self.fields['packet'] = packet.dump_pkt() + self.fields['vm'] = packet.get_vm_data() + + self.fields['rx_stats'] = {} + if not rx_stats: + self.fields['rx_stats']['enabled'] = False + + + def __str__ (self): + return json.dumps(self.fields, indent = 4, separators=(',', ': '), sort_keys = True) + + def to_json (self): + return self.fields + + def get_id (self): + return self.stream_id + + +# continuous stream +class STLContStream(STLStream): + def __init__ (self, + packet, + pps = 1, + enabled = True, + self_start = True, + isg = 0.0, + rx_stats = None): + + super(STLContStream, self).__init__(packet, + pps, + enabled, + self_start, + isg, + rx_stats, + next_stream_id = -1) + + # type + self.fields['mode']['type'] = "continuous" + + + +# single burst +class STLSingleBurstStream(STLStream): + def __init__ (self, + packet, + total_pkts, + pps = 1, + enabled = True, + self_start = True, + isg = 0.0, + rx_stats = None, + next_stream_id = -1): + + + if not isinstance(total_pkts, int): + raise STLArgumentError('total_pkts', total_pkts) + + super(STLSingleBurstStream, self).__init__(packet, + pps, + enabled, + self_start, + isg, + rx_stats, + next_stream_id) + + self.fields['mode']['type'] = "single_burst" + self.fields['mode']['total_pkts'] = total_pkts + + +# multi burst stream +class STLMultiBurstStream(STLStream): + def __init__ (self, + packet, + pkts_per_burst = 1, + pps = 1, + ibg = 0.0, + count = 1, + enabled = True, + self_start = True, + isg = 0.0, + rx_stats = None, + next_stream_id = -1): + + + if not isinstance(pkts_per_burst, int): + raise STLArgumentError('pkts_per_burst', pkts_per_burst) + + if not isinstance(count, int): + raise STLArgumentError('count', count) + + if not isinstance(ibg, (int, float)): + raise STLArgumentError('ibg', ibg) + + super(STLMultiBurstStream, self).__init__(packet, enabled, self_start, isg, rx_stats) + + self.fields['mode']['type'] = "single_burst" + self.fields['mode']['pkts_per_burst'] = pkts_per_burst + self.fields['mode']['ibg'] = ibg + self.fields['mode']['count'] = count + + +# REMOVE ME when can - convert from stream pack to a simple stream +class HACKSTLStream(STLStream): + def __init__ (self, stream_pack): + if not isinstance(stream_pack, StreamPack): + raise Exception("internal error") + + packet = CTRexPktBuilder() + packet.load_from_stream_obj(stream_pack.stream) + super(HACKSTLStream, self).__init__(packet) + + self.fields = stream_pack.stream diff --git a/scripts/automation/trex_control_plane/console/trex_console.py b/scripts/automation/trex_control_plane/console/trex_console.py index 62b68861..1defc6b2 100755 --- a/scripts/automation/trex_control_plane/console/trex_console.py +++ b/scripts/automation/trex_control_plane/console/trex_console.py @@ -29,11 +29,12 @@ import sys import tty, termios import trex_root_path from common.trex_streams import * -from client.trex_stateless_client import STLClient, LoggerApi, STLError +from client.trex_stateless_client import STLClient, LoggerApi from common.text_opts import * from client_utils.general_utils import user_input, get_current_user from client_utils import parsing_opts import trex_tui +from common.trex_stl_exceptions import * from functools import wraps __version__ = "1.1" @@ -752,7 +753,7 @@ def main(): finally: with stateless_client.logger.supress(): - stateless_client.disconnect() + stateless_client.disconnect(stop_traffic = False) if __name__ == '__main__': -- cgit 1.2.3-korg