diff options
Diffstat (limited to 'scripts/automation/trex_control_plane')
21 files changed, 2540 insertions, 380 deletions
diff --git a/scripts/automation/trex_control_plane/stf/trex_stf_lib/trex_client.py b/scripts/automation/trex_control_plane/stf/trex_stf_lib/trex_client.py index e9d2b8a0..5d992c6e 100755 --- a/scripts/automation/trex_control_plane/stf/trex_stf_lib/trex_client.py +++ b/scripts/automation/trex_control_plane/stf/trex_stf_lib/trex_client.py @@ -150,10 +150,8 @@ class CTRexClient(object): user = user or self.__default_user try: d = int(d) - if d < 30 and not trex_development: # test duration should be at least 30 seconds, unless trex_development flag is specified. - raise ValueError except ValueError: - raise ValueError('d parameter must be integer, specifying how long TRex run, and must be larger than 30 secs.') + raise ValueError('d parameter must be integer, specifying how long TRex run.') trex_cmd_options.update( {'f' : f, 'd' : d} ) if not trex_cmd_options.get('l'): 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 b23b5f1f..627761ff 100755 --- a/scripts/automation/trex_control_plane/stl/console/trex_console.py +++ b/scripts/automation/trex_control_plane/stl/console/trex_console.py @@ -202,7 +202,7 @@ class TRexConsole(TRexGeneralCmd): 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 @@ -313,6 +313,7 @@ class TRexConsole(TRexGeneralCmd): def do_shell (self, line): self.do_history(line) + @verify_connected def do_push (self, line): '''Push a local PCAP file\n''' self.stateless_client.push_line(line) @@ -320,6 +321,7 @@ class TRexConsole(TRexGeneralCmd): def help_push (self): self.do_push("-h") + @verify_connected def do_portattr (self, line): '''Change/show port(s) attributes\n''' self.stateless_client.set_port_attr_line(line) @@ -328,6 +330,42 @@ class TRexConsole(TRexGeneralCmd): self.do_portattr("-h") @verify_connected + def do_l2 (self, line): + '''Configures a port in L2 mode''' + self.stateless_client.set_l2_mode_line(line) + + def help_l2 (self): + self.do_l2("-h") + + @verify_connected + def do_l3 (self, line): + '''Configures a port in L3 mode''' + self.stateless_client.set_l3_mode_line(line) + + def help_l3 (self): + self.do_l3("-h") + + + @verify_connected + def do_set_rx_sniffer (self, line): + '''Sets a port sniffer on RX channel as PCAP recorder''' + self.stateless_client.set_rx_sniffer_line(line) + + def help_sniffer (self): + self.do_set_rx_sniffer("-h") + + @verify_connected + def do_resolve (self, line): + '''Resolve ARP for ports''' + self.stateless_client.resolve_line(line) + + def help_resolve (self): + self.do_resolve("-h") + + do_arp = do_resolve + help_arp = help_resolve + + @verify_connected def do_map (self, line): '''Maps ports topology\n''' ports = self.stateless_client.get_acquired_ports() @@ -416,6 +454,7 @@ class TRexConsole(TRexGeneralCmd): '''Release ports\n''' self.stateless_client.release_line(line) + @verify_connected def do_reacquire (self, line): '''reacquire all the ports under your logged user name''' self.stateless_client.reacquire_line(line) @@ -469,7 +508,7 @@ class TRexConsole(TRexGeneralCmd): ############# update @verify_connected def do_update(self, line): - '''update speed of port(s)currently transmitting traffic\n''' + '''update speed of port(s) currently transmitting traffic\n''' self.stateless_client.update_line(line) @@ -530,6 +569,13 @@ class TRexConsole(TRexGeneralCmd): '''Clear cached local statistics\n''' self.stateless_client.clear_stats_line(line) + @verify_connected + def do_service (self, line): + '''Sets port(s) service mode state''' + self.stateless_client.service_line(line) + + def help_service (self, line): + self.do_service("-h") def help_clear(self): self.do_clear("-h") diff --git a/scripts/automation/trex_control_plane/stl/console/trex_tui.py b/scripts/automation/trex_control_plane/stl/console/trex_tui.py index d7db6d30..37ef8000 100644 --- a/scripts/automation/trex_control_plane/stl/console/trex_tui.py +++ b/scripts/automation/trex_control_plane/stl/console/trex_tui.py @@ -645,14 +645,14 @@ class TrexTUI(): # regular state if self.state == self.STATE_ACTIVE: # if no connectivity - move to lost connecitivty - if not self.stateless_client.async_client.is_alive(): + if not self.stateless_client.async_client.is_active(): 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 the async is alive (might be zomibe, but alive) try to reconnect if self.stateless_client.async_client.is_alive(): # move to state reconnect self.state = self.STATE_RECONNECT @@ -1153,7 +1153,7 @@ class AsyncKeysEngineConsole: # errors else: err_msgs = ascii_split(str(func_rc)) - self.last_status = format_text(err_msgs[0], 'red') + self.last_status = format_text(clear_formatting(err_msgs[0]), 'red') if len(err_msgs) > 1: self.last_status += " [{0} more errors messages]".format(len(err_msgs) - 1) color = 'red' diff --git a/scripts/automation/trex_control_plane/stl/examples/stl_imix_bidir_update.py b/scripts/automation/trex_control_plane/stl/examples/stl_imix_bidir_update.py new file mode 100644 index 00000000..22cceb8f --- /dev/null +++ b/scripts/automation/trex_control_plane/stl/examples/stl_imix_bidir_update.py @@ -0,0 +1,123 @@ +import stl_path +from trex_stl_lib.api import * + +import imp +import time +import json +from pprint import pprint +import argparse + +# 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 (server): + + + # create client + c = STLClient(server = server) + passed = True + + + try: + + # connect to server + c.connect() + + # take all the ports + c.reset() + + dir_0 = [0] + dir_1 = [1] + + print "Mapped ports to sides {0} <--> {1}".format(dir_0, dir_1) + + # load IMIX profile + profile_file = os.path.join(stl_path.STL_PROFILES_PATH, 'imix.py') + profile1 = STLProfile.load_py(profile_file, direction=0) + profile2 = STLProfile.load_py(profile_file, direction=1) + stream1 = profile1.get_streams() + stream2 = profile2.get_streams() + + # add both streams to ports + c.add_streams(stream1, ports = dir_0) + c.add_streams(stream2, ports = dir_1) + + + # clear the stats before injecting + c.clear_stats() + + c.start(ports = (dir_0 + dir_1), mult = "100kpps", total = True) + + while True: + for rate in range(200,3100,10): + + # choose rate and start traffic for 10 seconds on 5 mpps + #mult = "30%" + my_mult = ("%dkpps"%rate) + print "Injecting {0} <--> {1} on total rate of '{2}' ".format(dir_0, dir_1, my_mult) + c.clear_stats() + + + c.update(ports = (dir_0 + dir_1), mult = my_mult) + + #time.sleep(1); + + # 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_1, lost_0) + print "packets lost from {0} --> {1}: {2:,} pkts".format(dir_1, dir_0, lost_1) + + if (lost_0 <= 0) and (lost_1 <= 0): # less or equal because we might have incoming arps etc. + 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" + +parser = argparse.ArgumentParser(description="Example for TRex Stateless, sending IMIX traffic") +parser.add_argument('-s', '--server', + dest='server', + help='Remote trex address', + default='127.0.0.1', + type = str) +args = parser.parse_args() + +# run the tests +imix_test(args.server) + diff --git a/scripts/automation/trex_control_plane/stl/services/scapy_server/field_engine.json b/scripts/automation/trex_control_plane/stl/services/scapy_server/field_engine.json new file mode 100644 index 00000000..85d10e65 --- /dev/null +++ b/scripts/automation/trex_control_plane/stl/services/scapy_server/field_engine.json @@ -0,0 +1,269 @@ +{ + "instructions": [ + { + "id": "STLVmFlowVar", + "parameters": ["name", "init_value", "max_value","min_value","step", "size","op"] + }, + { + "id": "STLVmWrFlowVar", + "parameters": ["fv_name", "pkt_offset","offset_fixup","add_val","is_big"] + }, + { + "id": "STLVmWrMaskFlowVar", + "parameters": ["fv_name", "pkt_offset", "pkt_cast_size","mask", "shift","add_value","is_big"] + }, + { + "id": "STLVmFixIpv4", + "parameters": ["offset"] + }, + { + "id": "STLVmTrimPktSize", + "parameters": ["fv_name"] + }, + { + "id": "STLVmTupleGen", + "parameters": ["name", "ip_min", "ip_max", "port_min", "port_max", "limit_flows", "flags"] + }, + { + "id": "STLVmFlowVarRepetableRandom", + "parameters": ["name", "size", "limit", "seed", "min_value", "max_value"] + }, + { + "id": "STLVmFixChecksumHw", + "parameters": ["l3_offset","l4_offset","l4_type"] + } + ], + + "instruction_params_meta": [ + { + "id": "name", + "name": "Name", + "type": "ENUM", + "editable": true, + "required": true, + "defaultValue": "Not defined" + }, + { + "id": "init_value", + "name": "Initial value", + "type": "STRING", + "defaultValue": "0" + }, + { + "id": "max_value", + "name": "Maximum value", + "type": "STRING", + "required": true, + "defaultValue": "0" + }, + { + "id": "min_value", + "name": "Minimum value", + "type": "STRING", + "required": true, + "defaultValue": "0" + }, + { + "id": "step", + "name": "Step", + "type": "NUMBER", + "required": true, + "defaultValue": "1" + }, + { + "id": "op", + "name": "Operation", + "type": "ENUM", + "defaultValue": "inc", + "dict": { + "dec": "Decrement", + "inc": "Increment", + "random": "Random" + }, + "required": true + }, + { + "id": "size", + "name": "Size", + "type": "ENUM", + "defaultValue": "4", + "dict": { + "1": "1", + "2": "2", + "4": "4", + "8": "8" + } + }, + { + "id": "fv_name", + "name": "Variable name", + "type": "ENUM", + "required": true, + "editable": true + }, + { + "id": "pkt_offset", + "name": "Offset", + "type": "ENUM", + "required": true, + "editable": true, + "defaultValue": 0 + }, + { + "id": "pkt_cast_size", + "name": "Packet cast size", + "type": "ENUM", + "defaultValue": 1, + "dict":{ + "1":1, + "2":2, + "4":4 + } + }, + { + "id": "shift", + "name": "Shift", + "type": "NUMBER", + "defaultValue": 0 + }, + { + "id": "mask", + "name": "Mask", + "type": "STRING", + "defaultValue": "0xff" + }, + { + "id": "offset_fixup", + "name": "offset_fixup", + "type": "NUMBER", + "defaultValue": 0 + }, + { + "id": "add_val", + "name": "add_val", + "type": "NUMBER", + "defaultValue": 0 + }, + { + "id": "add_value", + "name": "add_value", + "type": "NUMBER", + "defaultValue": 0 + }, + { + "id": "is_big", + "name": "is_big", + "type": "ENUM", + "defaultValue": "true", + "dict": { + "true": "true", + "false": "false" + } + }, + { + "id": "offset", + "name": "Offset", + "type": "ENUM", + "required": true, + "editable": true, + "defaultValue": 0 + }, + { + "id": "l3_offset", + "name": "L3 offset", + "type": "STRING", + "required": true, + "autocomplete": true, + "defaultValue": "IP" + }, + { + "id": "l4_offset", + "name": "L4 offset", + "type": "STRING", + "required": true, + "defaultValue": "TCP" + }, + { + "id": "ip_min", + "name": "Min IP", + "type": "STRING", + "defaultValue": "0.0.0.1" + }, + { + "id": "ip_max", + "name": "Max IP", + "type": "STRING", + "defaultValue": "0.0.0.10" + }, + { + "id": "port_max", + "name": "Max Port number", + "type": "NUMBER", + "defaultValue": 65535 + }, + { + "id": "port_min", + "name": "Min Port number", + "type": "NUMBER", + "defaultValue": 1025 + }, + { + "id": "limit_flows", + "name": "FLows limit", + "type": "NUMBER", + "defaultValue": 100000 + }, + { + "id": "limit", + "name": "Limit", + "type": "NUMBER", + "defaultValue": 100 + }, + { + "id": "seed", + "name": "Seed", + "type": "String", + "defaultValue": "None" + }, + { + "id": "flags", + "name": "Flags", + "type": "NUMBER", + "defaultValue": 0 + }, + { + "id": "l4_type", + "name": "L4 type", + "type": "ENUM", + "required": true, + "editable": false, + "defaultValue": "13", + "dict": { + "11": "L4_TYPE_UDP", + "13": "L4_TYPE_TCP" + } + } + ], + "supported_protocols": ["IP","TCP","UDP"], + "templates":[ + { + "id": "simple_flow_var", + "name": "Simple variable", + "instructionIds": ["STLVmFlowVar", "STLVmWrFlowVar"] + }, + { + "id": "rep_rand_var", + "name": "Repeatable random", + "instructionIds": ["STLVmFlowVarRepetableRandom", "STLVmWrFlowVar"] + } + ], + "global_params_meta":[ + { + "id": "cache_size", + "name": "Cache size", + "type": "NUMBER", + "defaultValue": "1000" + } + ] +} + diff --git a/scripts/automation/trex_control_plane/stl/services/scapy_server/scapy_service.py b/scripts/automation/trex_control_plane/stl/services/scapy_server/scapy_service.py index 8d99fe92..e5f1b20c 100755 --- a/scripts/automation/trex_control_plane/stl/services/scapy_server/scapy_service.py +++ b/scripts/automation/trex_control_plane/stl/services/scapy_server/scapy_service.py @@ -1,17 +1,20 @@ import os import sys + stl_pathname = os.path.abspath(os.path.join(os.pardir, os.pardir)) sys.path.append(stl_pathname) from trex_stl_lib.api import * +import trex_stl_lib.trex_stl_packet_builder_scapy import tempfile import hashlib import base64 import numbers import random -import inspect +from inspect import getdoc import json +import re from pprint import pprint # add some layers as an example @@ -121,6 +124,45 @@ class Scapy_service_api(): """ pass + def build_pkt_ex(self, client_v_handler, pkt_model_descriptor, extra_options): + """ build_pkt_ex(self,client_v_handler,pkt_model_descriptor, extra_options) -> Dictionary (of Offsets,Show2 and Buffer) + Performs calculations on the given packet and returns results for that packet. + + Parameters + ---------- + pkt_descriptor - An array of dictionaries describing a network packet + extra_options - A dictionary of extra options required for building packet + + Returns + ------- + - The packets offsets: each field in every layer is mapped inside the Offsets Dictionary + - The Show2: A description of each field and its value in every layer of the packet + - The Buffer: The Hexdump of packet encoded in base64 + + Raises + ------ + will raise an exception when the Scapy string format is illegal, contains syntax error, contains non-supported + protocl, etc. + """ + pass + + def load_instruction_parameter_values(self, client_v_handler, pkt_model_descriptor, vm_instructions_model, parameter_id): + """ load_instruction_parameter_values(self,client_v_handler,pkt_model_descriptor, vm_instructions_model, parameter_id) -> Dictionary (of possible parameter values) + Returns possible valies for given pararameter id depends on current pkt structure and vm_instructions + model. + + Parameters + ---------- + pkt_descriptor - An array of dictionaries describing a network packet + vm_instructions_model - A dictionary of extra options required for building packet + parameter_id - A string of parameter id + + Returns + ------- + Possible parameter values map. + + """ + pass def get_tree(self,client_v_handler): """ get_tree(self) -> Dictionary describing an example of hierarchy in layers @@ -372,7 +414,15 @@ class Scapy_service(Scapy_service_api): self.version_minor = '01' self.server_v_hashed = self._generate_version_hash(self.version_major,self.version_minor) self.protocol_definitions = {} # protocolId -> prococol definition overrides data + self.field_engine_supported_protocols = {} + self.instruction_parameter_meta_definitions = [] + self.field_engine_parameter_meta_definitions = [] + self.field_engine_templates_definitions = [] + self.field_engine_instructions_meta = [] + self.field_engine_instruction_expressions = [] self._load_definitions_from_json() + self._load_field_engine_meta_from_json() + self._vm_instructions = dict([m for m in inspect.getmembers(trex_stl_lib.trex_stl_packet_builder_scapy, inspect.isclass) if m[1].__module__ == 'trex_stl_lib.trex_stl_packet_builder_scapy']) def _load_definitions_from_json(self): # load protocol definitions from a json file @@ -382,6 +432,27 @@ class Scapy_service(Scapy_service_api): for protocol in protocols: self.protocol_definitions[ protocol['id'] ] = protocol + def _load_field_engine_meta_from_json(self): + # load protocol definitions from a json file + self.instruction_parameter_meta_definitions = [] + self.field_engine_supported_protocols = {} + self.field_engine_parameter_meta_definitions = [] + self.field_engine_templates_definitions = [] + with open('field_engine.json', 'r') as f: + metas = json.load(f) + self.instruction_parameter_meta_definitions = metas["instruction_params_meta"] + self.field_engine_instructions_meta = metas["instructions"] + self._append_intructions_help() + self.field_engine_supported_protocols = metas["supported_protocols"] + self.field_engine_parameter_meta_definitions = metas["global_params_meta"] + self.field_engine_templates_definitions = metas["templates"] + + + def _append_intructions_help(self): + for instruction_meta in self.field_engine_instructions_meta: + clazz = eval(instruction_meta['id']) + instruction_meta['help'] = base64.b64encode(getdoc(clazz.__init__)).decode('ascii') + def _all_protocol_structs(self): old_stdout = sys.stdout sys.stdout = mystdout = StringIO() @@ -708,6 +779,134 @@ class Scapy_service(Scapy_service_api): pkt = self._packet_model_to_scapy_packet(pkt_model_descriptor) return self._pkt_data(pkt) + + def build_pkt_ex(self, client_v_handler, pkt_model_descriptor, extra_options): + res = self.build_pkt(client_v_handler, pkt_model_descriptor) + pkt = self._packet_model_to_scapy_packet(pkt_model_descriptor) + + field_engine = {} + field_engine['instructions'] = [] + field_engine['error'] = None + try: + field_engine['instructions'] = self._generate_vm_instructions(pkt, extra_options['field_engine']) + except AssertionError as e: + field_engine['error'] = e.message + except CTRexPacketBuildException as e: + field_engine['error'] = e.message + + field_engine['vm_instructions_expressions'] = self.field_engine_instruction_expressions + res['field_engine'] = field_engine + return res + + def load_instruction_parameter_values(self, client_v_handler, pkt_model_descriptor, vm_instructions_model, parameter_id): + + given_protocol_ids = [str(proto['id']) for proto in pkt_model_descriptor] + + values = {} + if parameter_id == "name": + values = self._curent_pkt_protocol_fields(given_protocol_ids, "_") + + if parameter_id == "fv_name": + values = self._existed_flow_var_names(vm_instructions_model['field_engine']['instructions']) + + if parameter_id == "pkt_offset": + values = self._curent_pkt_protocol_fields(given_protocol_ids, ".") + + if parameter_id == "offset": + for ip_idx in range(given_protocol_ids.count("IP")): + value = "IP:{0}".format(ip_idx) + values[value] = value + + return {"map": values} + + def _existed_flow_var_names(self, instructions): + return dict((instruction['parameters']['name'], instruction['parameters']['name']) for instruction in instructions if self._nameParamterExist(instruction)) + + def _nameParamterExist(self, instruction): + try: + instruction['parameters']['name'] + return True + except KeyError: + return False + + def _curent_pkt_protocol_fields(self, given_protocol_ids, delimiter): + given_protocol_classes = [c for c in Packet.__subclasses__() if c.__name__ in given_protocol_ids] + protocol_fields = {} + for protocol_class in given_protocol_classes: + protocol_name = protocol_class.__name__ + protocol_count = given_protocol_ids.count(protocol_name) + for field_desc in protocol_class.fields_desc: + if delimiter == '.' and protocol_count > 1: + for idx in range(protocol_count): + formatted_name = "{0}:{1}{2}{3}".format(protocol_name, idx, delimiter, field_desc.name) + protocol_fields[formatted_name] = formatted_name + else: + formatted_name = "{0}{1}{2}".format(protocol_name, delimiter, field_desc.name) + protocol_fields[formatted_name] = formatted_name + + return protocol_fields + + def _generate_vm_instructions(self, pkt, field_engine_model_descriptor): + self.field_engine_instruction_expressions = [] + instructions = [] + instructions_def = field_engine_model_descriptor['instructions'] + for instruction_def in instructions_def: + instruction_id = instruction_def['id'] + instruction_class = self._vm_instructions[instruction_id] + parameters = {k: self._sanitize_value(k, v) for (k, v) in instruction_def['parameters'].iteritems()} + instructions.append(instruction_class(**parameters)) + + fe_parameters = field_engine_model_descriptor['global_parameters'] + + cache_size = None + if "cache_size" in fe_parameters: + assert self._is_int(fe_parameters['cache_size']), 'Cache size must be a number' + cache_size = int(fe_parameters['cache_size']) + + + pkt_builder = STLPktBuilder(pkt=pkt, vm=STLScVmRaw(instructions, cache_size=cache_size)) + pkt_builder.compile() + return pkt_builder.get_vm_data() + + def _sanitize_value(self, param_id, val): + if param_id == "pkt_offset": + if self._is_int(val): + return int(val) + elif val == "Ether.src": + return 0 + elif val == "Ether.dst": + return 6 + elif val == "Ether.type": + return 12 + else: + if val == "None" or val == "none": + return None + if val == "true": + return True + elif val == "false": + return False + elif re.match("[0-9a-f]{2}([-:])[0-9a-f]{2}(\\1[0-9a-f]{2}){4}$", str(val.lower())): + return int(str(val).replace(":", ""), 16) + + if self._is_int(val): + return int(val) + + str_val = str(val) + return int(str_val, 16) if str_val.startswith("0x") else str_val + + def _get_instruction_parameter_meta(self, param_id): + for meta in self.instruction_parameter_meta_definitions: + if meta['id'] == param_id: + return meta + raise Scapy_Exception("Unable to get meta for {0}" % param_id) + + def _is_int(self, val): + try: + int(val) + return True + except ValueError: + return False + # @deprecated. to be removed def get_all(self,client_v_handler): if not (self._verify_version_handler(client_v_handler)): @@ -796,7 +995,11 @@ class Scapy_service(Scapy_service_api): "name": protoDef.get('name') or pkt_class.name, "fields": self._get_fields_definition(pkt_class, protoDef.get('fields') or []) }) - res = {"protocols": protocols} + res = {"protocols": protocols, + "feInstructionParameters": self.instruction_parameter_meta_definitions, + "feInstructions": self.field_engine_instructions_meta, + "feParameters": self.field_engine_parameter_meta_definitions, + "feTemplates": self.field_engine_templates_definitions} return res def get_payload_classes(self,client_v_handler, pkt_model_descriptor): @@ -889,8 +1092,6 @@ class Scapy_service(Scapy_service_api): pcap_bin = tmpPcap.read() return bytes_to_b64(pcap_bin) - - #--------------------------------------------------------------------------- diff --git a/scripts/automation/trex_control_plane/stl/services/scapy_server/unit_tests/basetest.py b/scripts/automation/trex_control_plane/stl/services/scapy_server/unit_tests/basetest.py index 1db2c62b..e48880e8 100644 --- a/scripts/automation/trex_control_plane/stl/services/scapy_server/unit_tests/basetest.py +++ b/scripts/automation/trex_control_plane/stl/services/scapy_server/unit_tests/basetest.py @@ -53,6 +53,9 @@ def get_version_handler(): def build_pkt(model_def): return pass_result(service.build_pkt(v_handler, model_def)) +def build_pkt_ex(model_def, instructions_def): + return pass_result(service.build_pkt_ex(v_handler, model_def, instructions_def)) + def build_pkt_get_scapy(model_def): return build_pkt_to_scapy(build_pkt(model_def)) diff --git a/scripts/automation/trex_control_plane/stl/services/scapy_server/unit_tests/test_scapy_service.py b/scripts/automation/trex_control_plane/stl/services/scapy_server/unit_tests/test_scapy_service.py index 91a457dc..e1094a79 100644 --- a/scripts/automation/trex_control_plane/stl/services/scapy_server/unit_tests/test_scapy_service.py +++ b/scripts/automation/trex_control_plane/stl/services/scapy_server/unit_tests/test_scapy_service.py @@ -113,15 +113,23 @@ def test_get_all(): def test_get_definitions_all(): get_definitions(None) - def_classnames = [pdef['id'] for pdef in get_definitions(None)['protocols']] + defs = get_definitions(None) + def_classnames = [pdef['id'] for pdef in defs['protocols']] assert("IP" in def_classnames) assert("Dot1Q" in def_classnames) assert("TCP" in def_classnames) + # All instructions should have a help description. + fe_instructions = defs['feInstructions'] + for instruction in fe_instructions: + print(instruction['help']) + assert("help" in instruction) + def test_get_definitions_ether(): res = get_definitions(["Ether"]) - assert(len(res) == 1) - assert(res['protocols'][0]['id'] == "Ether") + protocols = res['protocols'] + assert(len(protocols) == 1) + assert(protocols[0]['id'] == "Ether") def test_get_payload_classes(): eth_payloads = get_payload_classes([{"id":"Ether"}]) @@ -250,3 +258,34 @@ def test_ip_definitions(): assert(fields[9]['id'] == 'chksum') assert(fields[9]['auto'] == True) +def test_generate_vm_instructions(): + ip_pkt_model = [ + layer_def("Ether"), + layer_def("IP", src="16.0.0.1", dst="48.0.0.1") + ] + ip_instructions_model = {"field_engine": {"instructions": [{"id": "STLVmFlowVar", + "parameters": {"op": "inc", "min_value": "192.168.0.10", + "size": "1", "name": "ip_src", + "step": "1", + "max_value": "192.168.0.100"}}, + {"id": "STLVmWrFlowVar", + "parameters": {"pkt_offset": "IP.src", "is_big": "true", + "add_val": "0", "offset_fixup": "0", + "fv_name": "ip_src"}}, + {"id": "STLVmFlowVar", + "parameters": {"op": "dec", "min_value": "32", + "size": "1", "name": "ip_ttl", + "step": "4", "max_value": "64"}}, + {"id": "STLVmWrFlowVar", + "parameters": {"pkt_offset": "IP.ttl", "is_big": "true", + "add_val": "0", "offset_fixup": "0", + "fv_name": "ip_ttl"}}], + "global_parameters": {}}} + res = build_pkt_ex(ip_pkt_model, ip_instructions_model) + src_instruction = res['field_engine']['instructions']['instructions'][0] + assert(src_instruction['min_value'] == 3232235530) + assert(src_instruction['max_value'] == 3232235620) + + ttl_instruction = res['field_engine']['instructions']['instructions'][2] + assert(ttl_instruction['min_value'] == 32) + assert(ttl_instruction['max_value'] == 64) diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_async_client.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_async_client.py index 2c95844b..11e87592 100644 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_async_client.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_async_client.py @@ -137,6 +137,10 @@ class CTRexAsyncStatsManager(): class CTRexAsyncClient(): + THREAD_STATE_ACTIVE = 1 + THREAD_STATE_ZOMBIE = 2 + THREAD_STATE_DEAD = 3 + def __init__ (self, server, port, stateless_client): self.port = port @@ -159,7 +163,10 @@ class CTRexAsyncClient(): self.connected = False self.zipped = ZippedMsg() - + + self.t_state = self.THREAD_STATE_DEAD + + # connects the async channel def connect (self): @@ -173,8 +180,8 @@ class CTRexAsyncClient(): self.socket = self.context.socket(zmq.SUB) - # before running the thread - mark as active - self.active = True + # before running the thread - mark as active + self.t_state = self.THREAD_STATE_ACTIVE self.t = threading.Thread(target = self._run) # kill this thread on exit and don't add it to the join list @@ -198,26 +205,26 @@ class CTRexAsyncClient(): return RC_OK() - - # disconnect def disconnect (self): if not self.connected: return # mark for join - self.active = False - - # signal that the context was destroyed (exit the thread loop) + self.t_state = self.THREAD_STATE_DEAD self.context.term() - - # join self.t.join() + # done self.connected = False + # set the thread as a zombie (in case of server death) + def set_as_zombie (self): + self.last_data_recv_ts = None + self.t_state = self.THREAD_STATE_ZOMBIE + # thread function def _run (self): @@ -231,12 +238,19 @@ class CTRexAsyncClient(): self.monitor.reset() - while self.active: + while self.t_state != self.THREAD_STATE_DEAD: try: with self.monitor: line = self.socket.recv() + # last data recv. + self.last_data_recv_ts = time.time() + + # if thread was marked as zomibe - it does nothing besides fetching messages + if self.t_state == self.THREAD_STATE_ZOMBIE: + continue + self.monitor.on_recv_msg(line) # try to decomrpess @@ -246,7 +260,6 @@ class CTRexAsyncClient(): line = line.decode() - self.last_data_recv_ts = time.time() # signal once if not got_data: @@ -259,13 +272,14 @@ class CTRexAsyncClient(): # signal once if got_data: self.event_handler.on_async_dead() + self.set_as_zombie() got_data = False continue except zmq.ContextTerminated: # outside thread signaled us to exit - assert(not self.active) + assert(self.t_state != self.THREAD_STATE_ACTIVE) break msg = json.loads(line) @@ -283,16 +297,29 @@ class CTRexAsyncClient(): # closing of socket must be from the same thread self.socket.close(linger = 0) - def is_thread_alive (self): - return self.t.is_alive() - - # did we get info for the last 3 seconds ? + + # return True if the subscriber got data in the last 3 seconds + # even if zombie - will return true if got data def is_alive (self): + + # maybe the thread has exited with exception + if not self.t.is_alive(): + return False + + # simply no data if self.last_data_recv_ts == None: return False + # timeout of data return ( (time.time() - self.last_data_recv_ts) < 3 ) + + # more granular than active - it means that thread state is active we get info + # zomibes will return false + def is_active (self): + return self.is_alive() and self.t_state == self.THREAD_STATE_ACTIVE + + def get_stats (self): return self.stats 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 80a4c4dc..a42247e7 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 @@ -12,9 +12,10 @@ from .trex_stl_types import * from .trex_stl_async_client import CTRexAsyncClient from .utils import parsing_opts, text_tables, common -from .utils.common import list_intersect, list_difference, is_sub_list, PassiveTimer +from .utils.common import * from .utils.text_opts import * from functools import wraps +from texttable import ansi_len from collections import namedtuple from yaml import YAMLError @@ -24,6 +25,8 @@ import re import random import json import traceback +import os.path + ############################ logger ############################# ############################ ############################# @@ -32,9 +35,10 @@ import traceback # logger API for the client class LoggerApi(object): # verbose levels - VERBOSE_QUIET = 0 - VERBOSE_REGULAR = 1 - VERBOSE_HIGH = 2 + VERBOSE_QUIET = 0 + VERBOSE_REGULAR_SYNC = 1 + VERBOSE_REGULAR = 2 + VERBOSE_HIGH = 3 def __init__(self): self.level = LoggerApi.VERBOSE_REGULAR @@ -62,7 +66,7 @@ class LoggerApi(object): # simple log message with verbose - def log (self, msg, level = VERBOSE_REGULAR, newline = True): + def log (self, msg, level = VERBOSE_REGULAR_SYNC, newline = True): if not self.check_verbose(level): return @@ -90,19 +94,21 @@ class LoggerApi(object): # supress object getter - def supress (self): + def supress (self, level = VERBOSE_QUIET): class Supress(object): - def __init__ (self, logger): + def __init__ (self, logger, level): self.logger = logger + self.level = level def __enter__ (self): self.saved_level = self.logger.get_verbose() - self.logger.set_verbose(LoggerApi.VERBOSE_QUIET) + if self.level < self.saved_level: + self.logger.set_verbose(self.level) def __exit__ (self, type, value, traceback): self.logger.set_verbose(self.saved_level) - return Supress(self) + return Supress(self, level) @@ -175,8 +181,8 @@ class EventsHandler(object): def on_async_dead (self): if self.client.connected: msg = 'Lost connection to server' - self.__add_event_log('local', 'info', msg, True) self.client.connected = False + self.__add_event_log('local', 'info', msg, True) def on_async_alive (self): @@ -322,25 +328,30 @@ class EventsHandler(object): # port attr changed elif (event_type == 8): + port_id = int(data['port_id']) - if data['attr'] == self.client.ports[port_id].attr: - return # false alarm - old_info = self.client.ports[port_id].get_info() - self.__async_event_port_attr_changed(port_id, data['attr']) - new_info = self.client.ports[port_id].get_info() + + diff = self.__async_event_port_attr_changed(port_id, data['attr']) + if not diff: + return + + ev = "port {0} attributes changed".format(port_id) - for key, old_val in old_info.items(): - new_val = new_info[key] - if old_val != new_val: - ev += '\n {key}: {old} -> {new}'.format( - key = key, - old = old_val.lower() if type(old_val) is str else old_val, - new = new_val.lower() if type(new_val) is str else new_val) + for key, (old_val, new_val) in diff.items(): + ev += '\n {key}: {old} -> {new}'.format( + key = key, + old = old_val.lower() if type(old_val) is str else old_val, + new = new_val.lower() if type(new_val) is str else new_val) + show_event = True - + + + # server stopped elif (event_type == 100): ev = "Server has stopped" + # to avoid any new messages on async + self.client.async_client.set_as_zombie() self.__async_event_server_stopped() show_event = True @@ -392,7 +403,7 @@ class EventsHandler(object): def __async_event_port_attr_changed (self, port_id, attr): if port_id in self.client.ports: - self.client.ports[port_id].async_event_port_attr_changed(attr) + return self.client.ports[port_id].async_event_port_attr_changed(attr) # add event to log def __add_event_log (self, origin, ev_type, msg, show = False): @@ -650,7 +661,7 @@ class STLClient(object): return rc - + def __add_streams(self, stream_list, port_id_list = None): port_id_list = self.__ports(port_id_list) @@ -767,7 +778,7 @@ class STLClient(object): return rc - def __push_remote (self, pcap_filename, port_id_list, ipg_usec, speedup, count, duration, is_dual): + def __push_remote (self, pcap_filename, port_id_list, ipg_usec, speedup, count, duration, is_dual, min_ipg_usec): port_id_list = self.__ports(port_id_list) rc = RC() @@ -783,7 +794,8 @@ class STLClient(object): count, duration, is_dual, - slave_handler)) + slave_handler, + min_ipg_usec)) return rc @@ -799,17 +811,85 @@ class STLClient(object): return rc + def __resolve (self, port_id_list = None, retries = 0): + port_id_list = self.__ports(port_id_list) + + rc = RC() + + for port_id in port_id_list: + rc.add(self.ports[port_id].arp_resolve(retries)) + + return rc + + def __set_port_attr (self, port_id_list = None, attr_dict = None): port_id_list = self.__ports(port_id_list) rc = RC() + for port_id, port_attr_dict in zip(port_id_list, attr_dict): + rc.add(self.ports[port_id].set_attr(**port_attr_dict)) + + return rc + + + def __set_rx_sniffer (self, port_id_list, base_filename, limit): + port_id_list = self.__ports(port_id_list) + rc = RC() + + for port_id in port_id_list: + head, tail = os.path.splitext(base_filename) + filename = "{0}-{1}{2}".format(head, port_id, tail) + rc.add(self.ports[port_id].set_rx_sniffer(filename, limit)) + + return rc + + + def __remove_rx_sniffer (self, port_id_list): + port_id_list = self.__ports(port_id_list) + rc = RC() + for port_id in port_id_list: - rc.add(self.ports[port_id].set_attr(attr_dict)) + rc.add(self.ports[port_id].remove_rx_sniffer()) return rc + def __set_rx_queue (self, port_id_list, size): + port_id_list = self.__ports(port_id_list) + rc = RC() + for port_id in port_id_list: + rc.add(self.ports[port_id].set_rx_queue(size)) + + return rc + + def __remove_rx_queue (self, port_id_list): + port_id_list = self.__ports(port_id_list) + rc = RC() + + for port_id in port_id_list: + rc.add(self.ports[port_id].remove_rx_queue()) + + return rc + + def __get_rx_queue_pkts (self, port_id_list): + port_id_list = self.__ports(port_id_list) + rc = RC() + + for port_id in port_id_list: + rc.add(self.ports[port_id].get_rx_queue_pkts()) + + return rc + + def __set_service_mode (self, port_id_list, enabled): + port_id_list = self.__ports(port_id_list) + rc = RC() + + for port_id in port_id_list: + rc.add(self.ports[port_id].set_service_mode(enabled)) + + return rc + # connect to server def __connect(self): @@ -1008,7 +1088,7 @@ class STLClient(object): if not port_id in valid_ports: raise STLError("Port ID '{0}' is not a valid port ID - valid values: {1}".format(port_id, valid_ports)) - return port_id_list + return list_remove_dup(port_id_list) # transmit request on the RPC link @@ -1313,6 +1393,18 @@ class STLClient(object): if port_obj.is_active()] + def get_resolvable_ports (self): + return [port_id + for port_id, port_obj in self.ports.items() + if port_obj.is_acquired() and port_obj.get_dst_addr()['ipv4'] is not None] + + + def get_service_enabled_ports(self): + return [port_id + for port_id, port_obj in self.ports.items() + if port_obj.is_acquired() and port_obj.is_service_mode_on()] + + # get paused ports def get_paused_ports (self, owned = True): if owned: @@ -1338,6 +1430,7 @@ class STLClient(object): # get stats + @__api_check(True) def get_stats (self, ports = None, sync_now = True): """ Return dictionary containing statistics information gathered from the server. @@ -1585,7 +1678,7 @@ class STLClient(object): ports = ports if ports is not None else self.get_all_ports() ports = self._validate_port_list(ports) - return [self.ports[port_id].get_info() for port_id in ports] + return [self.ports[port_id].get_formatted_info() for port_id in ports] ############################ Commands ############################# @@ -1700,6 +1793,9 @@ class STLClient(object): self.__release(ports) raise STLError(rc) + for port_id in ports: + if not self.ports[port_id].is_resolved(): + self.logger.log(format_text('*** Warning - Port {0} destination is unresolved ***'.format(port_id), 'bold')) @__api_check(True) def release (self, ports = None): @@ -1725,30 +1821,144 @@ class STLClient(object): if not rc: raise STLError(rc) + + @__api_check(True) - def ping(self): + def ping_rpc_server(self): """ - Pings the server + Pings the RPC server :parameters: - None - + None :raises: + :exc:`STLError` """ - + self.logger.pre_cmd("Pinging the server on '{0}' port '{1}': ".format(self.connection_info['server'], self.connection_info['sync_port'])) rc = self._transmit("ping", api_class = None) + + self.logger.post_cmd(rc) + + if not rc: + raise STLError(rc) + + + @__api_check(True) + def set_l2_mode (self, port, dst_mac): + """ + Sets the port mode to L2 + + :parameters: + port - the port to set the source address + dst_mac - destination MAC + :raises: + + :exc:`STLError` + """ + validate_type('port', port, int) + if port not in self.get_all_ports(): + raise STLError("port {0} is not a valid port id".format(port)) + + if not is_valid_mac(dst_mac): + raise STLError("dest_mac is not a valid MAC address: '{0}'".format(dst_mac)) + + self.logger.pre_cmd("Setting port {0} in L2 mode: ".format(port)) + rc = self.ports[port].set_l2_mode(dst_mac) self.logger.post_cmd(rc) + + if not rc: + raise STLError(rc) + + + @__api_check(True) + def set_l3_mode (self, port, src_ipv4, dst_ipv4): + """ + Sets the port mode to L3 + :parameters: + port - the port to set the source address + src_ipv4 - IPv4 source address for the port + dst_ipv4 - IPv4 destination address + :raises: + + :exc:`STLError` + """ + + validate_type('port', port, int) + if port not in self.get_all_ports(): + raise STLError("port {0} is not a valid port id".format(port)) + + if not is_valid_ipv4(src_ipv4): + raise STLError("src_ipv4 is not a valid IPv4 address: '{0}'".format(src_ipv4)) + + if not is_valid_ipv4(dst_ipv4): + raise STLError("dst_ipv4 is not a valid IPv4 address: '{0}'".format(dst_ipv4)) + + self.logger.pre_cmd("Setting port {0} in L3 mode: ".format(port)) + rc = self.ports[port].set_l3_mode(src_ipv4, dst_ipv4) + self.logger.post_cmd(rc) + if not rc: raise STLError(rc) + + # try to resolve + with self.logger.supress(level = LoggerApi.VERBOSE_REGULAR_SYNC): + self.logger.pre_cmd("ARP resolving address '{0}': ".format(dst_ipv4)) + rc = self.ports[port].arp_resolve(0) + self.logger.post_cmd(rc) + if not rc: + raise STLError(rc) + @__api_check(True) + def ping_ip (self, src_port, dst_ipv4, pkt_size = 64, count = 5): + """ + Pings an IP address through a port + + :parameters: + src_port - on which port_id to send the ICMP PING request + dst_ipv4 - which IP to ping + pkt_size - packet size to use + count - how many times to ping + :raises: + + :exc:`STLError` + + """ + # validate src port + validate_type('src_port', src_port, int) + if src_port not in self.get_all_ports(): + raise STLError("src port is not a valid port id") + + if not is_valid_ipv4(dst_ipv4): + raise STLError("dst_ipv4 is not a valid IPv4 address: '{0}'".format(dst_ipv4)) + + if (pkt_size < 64) or (pkt_size > 9216): + raise STLError("pkt_size should be a value between 64 and 9216: '{0}'".format(pkt_size)) + + validate_type('count', count, int) + + self.logger.pre_cmd("Pinging {0} from port {1} with {2} bytes of data:".format(dst_ipv4, + src_port, + pkt_size)) + + # no async messages + with self.logger.supress(level = LoggerApi.VERBOSE_REGULAR_SYNC): + self.logger.log('') + for i in range(count): + rc = self.ports[src_port].ping(ping_ipv4 = dst_ipv4, pkt_size = pkt_size) + if not rc: + raise STLError(rc) + + self.logger.log(rc.data()) + + if i != (count - 1): + time.sleep(1) + + + + @__api_check(True) def server_shutdown (self, force = False): """ Sends the server a request for total shutdown @@ -1795,6 +2005,8 @@ class STLClient(object): if not rc: raise STLError(rc) + return rc.data() + @__api_check(True) def get_util_stats(self): """ @@ -1814,7 +2026,6 @@ class STLClient(object): @__api_check(True) def get_xstats(self, port_id): - print(port_id) """ Get extended stats of port: all the counters as dict. @@ -1833,7 +2044,7 @@ class STLClient(object): @__api_check(True) - def reset(self, ports = None): + def reset(self, ports = None, restart = False): """ Force acquire ports, stop the traffic, remove all streams and clear stats @@ -1841,7 +2052,9 @@ class STLClient(object): ports : list Ports on which to execute the command - + restart: bool + Restart the NICs (link down / up) + :raises: + :exc:`STLError` @@ -1851,12 +2064,34 @@ class STLClient(object): ports = ports if ports is not None else self.get_all_ports() ports = self._validate_port_list(ports) - # force take the port and ignore any streams on it - self.acquire(ports, force = True, sync_streams = False) - self.stop(ports, rx_delay_ms = 0) - self.remove_all_streams(ports) - self.clear_stats(ports) + + if restart: + self.logger.pre_cmd("Hard resetting ports {0}:".format(ports)) + else: + self.logger.pre_cmd("Resetting ports {0}:".format(ports)) + + + try: + with self.logger.supress(): + # force take the port and ignore any streams on it + self.acquire(ports, force = True, sync_streams = False) + self.stop(ports, rx_delay_ms = 0) + self.remove_all_streams(ports) + self.clear_stats(ports) + self.set_port_attr(ports, + promiscuous = False, + link_up = True if restart else None) + self.set_service_mode(ports, False) + self.remove_rx_sniffer(ports) + self.remove_rx_queue(ports) + + except STLError as e: + self.logger.post_cmd(False) + raise e + + self.logger.post_cmd(RC_OK()) + @__api_check(True) def remove_all_streams (self, ports = None): @@ -1995,7 +2230,28 @@ class STLClient(object): raise STLError(rc) - + # common checks for start API + def __pre_start_check (self, ports, force): + + # verify link status + ports_link_down = [port_id for port_id in ports if not self.ports[port_id].is_up()] + if ports_link_down and not force: + raise STLError("Port(s) %s - link DOWN - check the connection or specify 'force'" % ports_link_down) + + # verify ports are stopped or force stop them + active_ports = [port_id for port_id in ports if self.ports[port_id].is_active()] + if active_ports and not force: + raise STLError("Port(s) {0} are active - please stop them or specify 'force'".format(active_ports)) + + # warn if ports are not resolved + unresolved_ports = [port_id for port_id in ports if not self.ports[port_id].is_resolved()] + if unresolved_ports and not force: + raise STLError("Port(s) {0} have unresolved destination addresses - please resolve them or specify 'force'".format(unresolved_ports)) + + if self.get_service_enabled_ports() and not force: + raise STLError("Port(s) {0} are under service mode - please disable service mode or specify 'force'".format(self.get_service_enabled_ports())) + + @__api_check(True) def start (self, ports = None, @@ -2050,11 +2306,10 @@ class STLClient(object): validate_type('total', total, bool) validate_type('core_mask', core_mask, (int, list)) - # verify link status - ports_link_down = [port_id for port_id in ports if self.ports[port_id].attr.get('link',{}).get('up') == False] - if not force and ports_link_down: - raise STLError("Port(s) %s - link DOWN - check the connection or specify 'force'" % ports_link_down) - + + # some sanity checks before attempting start + self.__pre_start_check(ports, force) + ######################### # decode core mask argument decoded_mask = self.__decode_core_mask(ports, core_mask) @@ -2068,17 +2323,12 @@ class STLClient(object): raise STLArgumentError('mult', mult) - # verify ports are stopped or force stop them + # stop active ports if needed active_ports = list(set(self.get_active_ports()).intersection(ports)) - if active_ports: - if not force: - raise STLError("Port(s) {0} are active - please stop them or specify 'force'".format(active_ports)) - else: - rc = self.stop(active_ports) - if not rc: - raise STLError(rc) - + if active_ports and force: + self.stop(active_ports) + # start traffic self.logger.pre_cmd("Starting traffic on port(s) {0}:".format(ports)) rc = self.__start(mult_obj, duration, ports, force, decoded_mask) @@ -2115,7 +2365,7 @@ class STLClient(object): return ports = self._validate_port_list(ports) - + self.logger.pre_cmd("Stopping traffic on port(s) {0}:".format(ports)) rc = self.__stop(ports) self.logger.post_cmd(rc) @@ -2245,7 +2495,8 @@ class STLClient(object): speedup = 1.0, count = 1, duration = -1, - is_dual = False): + is_dual = False, + min_ipg_usec = None): """ Push a remote server-reachable PCAP file the path must be fullpath accessible to the server @@ -2258,7 +2509,8 @@ class STLClient(object): Ports on which to execute the command ipg_usec : float - Inter-packet gap in microseconds + Inter-packet gap in microseconds. + Exclusive with min_ipg_usec speedup : float A factor to adjust IPG. effectively IPG = IPG / speedup @@ -2275,6 +2527,10 @@ class STLClient(object): also requires that all the ports will be in master mode with their adjacent ports as slaves + min_ipg_usec : float + Minimum inter-packet gap in microseconds to guard from too small ipg. + Exclusive with ipg_usec + :raises: + :exc:`STLError` @@ -2288,6 +2544,7 @@ class STLClient(object): validate_type('count', count, int) validate_type('duration', duration, (float, int)) validate_type('is_dual', is_dual, bool) + validate_type('min_ipg_usec', min_ipg_usec, (float, int, type(None))) # for dual mode check that all are masters if is_dual: @@ -2306,7 +2563,7 @@ class STLClient(object): self.logger.pre_cmd("Pushing remote PCAP on port(s) {0}:".format(ports)) - rc = self.__push_remote(pcap_filename, ports, ipg_usec, speedup, count, duration, is_dual) + rc = self.__push_remote(pcap_filename, ports, ipg_usec, speedup, count, duration, is_dual, min_ipg_usec) self.logger.post_cmd(rc) if not rc: @@ -2324,7 +2581,8 @@ class STLClient(object): force = False, vm = None, packet_hook = None, - is_dual = False): + is_dual = False, + min_ipg_usec = None): """ Push a local PCAP to the server This is equivalent to loading a PCAP file to a profile @@ -2340,7 +2598,8 @@ class STLClient(object): Ports on which to execute the command ipg_usec : float - Inter-packet gap in microseconds + Inter-packet gap in microseconds. + Exclusive with min_ipg_usec speedup : float A factor to adjust IPG. effectively IPG = IPG / speedup @@ -2366,6 +2625,10 @@ class STLClient(object): also requires that all the ports will be in master mode with their adjacent ports as slaves + min_ipg_usec : float + Minimum inter-packet gap in microseconds to guard from too small ipg. + Exclusive with ipg_usec + :raises: + :exc:`STLError` @@ -2380,6 +2643,9 @@ class STLClient(object): validate_type('duration', duration, (float, int)) validate_type('vm', vm, (list, type(None))) validate_type('is_dual', is_dual, bool) + validate_type('min_ipg_usec', min_ipg_usec, (float, int, type(None))) + if all([ipg_usec, min_ipg_usec]): + raise STLError('Please specify either ipg or minimal ipg, not both.') # no support for > 1MB PCAP - use push remote @@ -2392,7 +2658,7 @@ class STLClient(object): slave = port ^ 0x1 if slave in ports: - raise STLError("dual mode: cannot provide adjacent ports ({0}, {1}) in a batch".format(master, slave)) + raise STLError("dual mode: please specify only one of adjacent ports ({0}, {1}) in a batch".format(master, slave)) if not slave in self.get_acquired_ports(): raise STLError("dual mode: adjacent port {0} must be owned during dual mode".format(slave)) @@ -2408,7 +2674,8 @@ class STLClient(object): speedup, count, vm = vm, - packet_hook = packet_hook) + packet_hook = packet_hook, + min_ipg_usec = min_ipg_usec) self.logger.post_cmd(RC_OK) except STLError as e: self.logger.post_cmd(RC_ERR(e)) @@ -2433,7 +2700,8 @@ class STLClient(object): count, vm = vm, packet_hook = packet_hook, - split_mode = split_mode) + split_mode = split_mode, + min_ipg_usec = min_ipg_usec) self.logger.post_cmd(RC_OK()) @@ -2441,7 +2709,7 @@ class STLClient(object): self.logger.post_cmd(RC_ERR(e)) raise - all_ports = ports + [p ^ 0x1 for p in ports] + all_ports = ports + [p ^ 0x1 for p in ports if profile_b] self.remove_all_streams(ports = all_ports) @@ -2450,7 +2718,8 @@ class STLClient(object): slave = port ^ 0x1 self.add_streams(profile_a.get_streams(), master) - self.add_streams(profile_b.get_streams(), slave) + if profile_b: + self.add_streams(profile_b.get_streams(), slave) return self.start(ports = all_ports, duration = duration) @@ -2612,7 +2881,7 @@ class STLClient(object): while set(self.get_active_ports()).intersection(ports): # make sure ASYNC thread is still alive - otherwise we will be stuck forever - if not self.async_client.is_thread_alive(): + if not self.async_client.is_active(): raise STLError("subscriber thread is dead") time.sleep(0.01) @@ -2626,16 +2895,22 @@ class STLClient(object): @__api_check(True) - def set_port_attr (self, ports = None, promiscuous = None, link_up = None, led_on = None, flow_ctrl = None): + def set_port_attr (self, + ports = None, + promiscuous = None, + link_up = None, + led_on = None, + flow_ctrl = None, + resolve = True): """ Set port attributes :parameters: - promiscuous - True or False - link_up - True or False - led_on - True or False - flow_ctrl - 0: disable all, 1: enable tx side, 2: enable rx side, 3: full enable - + promiscuous - True or False + link_up - True or False + led_on - True or False + flow_ctrl - 0: disable all, 1: enable tx side, 2: enable rx side, 3: full enable + resolve - if true, in case a destination address is configured as IPv4 try to resolve it :raises: + :exe:'STLError' @@ -2649,29 +2924,221 @@ class STLClient(object): validate_type('link_up', link_up, (bool, type(None))) validate_type('led_on', led_on, (bool, type(None))) validate_type('flow_ctrl', flow_ctrl, (int, type(None))) + + # common attributes for all ports + cmn_attr_dict = {} - # build attributes - attr_dict = {} - if promiscuous is not None: - attr_dict['promiscuous'] = {'enabled': promiscuous} - if link_up is not None: - attr_dict['link_status'] = {'up': link_up} - if led_on is not None: - attr_dict['led_status'] = {'on': led_on} - if flow_ctrl is not None: - attr_dict['flow_ctrl_mode'] = {'mode': flow_ctrl} + cmn_attr_dict['promiscuous'] = promiscuous + cmn_attr_dict['link_status'] = link_up + cmn_attr_dict['led_status'] = led_on + cmn_attr_dict['flow_ctrl_mode'] = flow_ctrl - # no attributes to set - if not attr_dict: - return - + # each port starts with a set of the common attributes + attr_dict = [dict(cmn_attr_dict) for _ in ports] + self.logger.pre_cmd("Applying attributes on port(s) {0}:".format(ports)) rc = self.__set_port_attr(ports, attr_dict) self.logger.post_cmd(rc) + + if not rc: + raise STLError(rc) + + + + + @__api_check(True) + def set_service_mode (self, ports = None, enabled = True): + """ + Set service mode for port(s) + In service mode ports will respond to ARP, PING and etc. + + :parameters: + ports - for which ports to configure service mode on/off + enabled - True for activating service mode, False for disabling + :raises: + + :exe:'STLError' + + """ + # by default take all acquired ports + ports = ports if ports is not None else self.get_acquired_ports() + ports = self._validate_port_list(ports) + + if enabled: + self.logger.pre_cmd('Enabling service mode on port(s) {0}:'.format(ports)) + else: + self.logger.pre_cmd('Disabling service mode on port(s) {0}:'.format(ports)) + + rc = self.__set_service_mode(ports, enabled) + self.logger.post_cmd(rc) + + if not rc: + raise STLError(rc) + + + @__api_check(True) + def resolve (self, ports = None, retries = 0, verbose = True): + """ + Resolves ports (ARP resolution) + + :parameters: + ports - for which ports to apply a unique sniffer (each port gets a unique file) + retires - how many times to retry on each port (intervals of 100 milliseconds) + verbose - log for each request the response + :raises: + + :exe:'STLError' + + """ + # by default - resolve all the ports that are configured with IPv4 dest + ports = ports if ports is not None else self.get_resolvable_ports() + ports = self._validate_port_list(ports) + + self.logger.pre_cmd('Resolving destination on port(s) {0}:'.format(ports)) + with self.logger.supress(): + rc = self.__resolve(ports, retries) + + self.logger.post_cmd(rc) + + if not rc: + raise STLError(rc) + + # print the ARP transaction + if verbose: + self.logger.log(rc) + self.logger.log('') + + + + @__api_check(True) + def set_rx_sniffer (self, ports = None, base_filename = 'rx.pcap', limit = 1000): + """ + Sets a RX sniffer for port(s) written to a PCAP file + + :parameters: + ports - for which ports to apply a unique sniffer (each port gets a unique file) + base_filename - filename will be appended with '-<port_number>', e.g. rx.pcap --> rx-0.pcap, rx-1.pcap etc. + limit - limit how many packets will be written + :raises: + + :exe:'STLError' + + """ + ports = ports if ports is not None else self.get_acquired_ports() + ports = self._validate_port_list(ports) + + # check arguments + validate_type('base_filename', base_filename, basestring) + validate_type('limit', limit, (int)) + if limit <= 0: + raise STLError("'limit' must be a positive value") + + self.logger.pre_cmd("Setting RX sniffers on port(s) {0}:".format(ports)) + rc = self.__set_rx_sniffer(ports, base_filename, limit) + self.logger.post_cmd(rc) + if not rc: raise STLError(rc) + + + @__api_check(True) + def remove_rx_sniffer (self, ports = None): + """ + Removes RX sniffer from port(s) + + :raises: + + :exe:'STLError' + + """ + ports = ports if ports is not None else self.get_acquired_ports() + ports = self._validate_port_list(ports) + + self.logger.pre_cmd("Removing RX sniffers on port(s) {0}:".format(ports)) + rc = self.__remove_rx_sniffer(ports) + self.logger.post_cmd(rc) + + if not rc: + raise STLError(rc) + + + @__api_check(True) + def set_rx_queue (self, ports = None, size = 1000): + """ + Sets RX queue for port(s) + The queue is cyclic and will hold last 'size' packets + + :parameters: + ports - for which ports to apply a queue + size - size of the queue + :raises: + + :exe:'STLError' + + """ + ports = ports if ports is not None else self.get_acquired_ports() + ports = self._validate_port_list(ports) + + # check arguments + validate_type('size', size, (int)) + if size <= 0: + raise STLError("'size' must be a positive value") + + self.logger.pre_cmd("Setting RX queue on port(s) {0}:".format(ports)) + rc = self.__set_rx_queue(ports, size) + self.logger.post_cmd(rc) + + + if not rc: + raise STLError(rc) + + + + @__api_check(True) + def remove_rx_queue (self, ports = None): + """ + Removes RX queue from port(s) + + :parameters: + ports - for which ports to remove the RX queue + :raises: + + :exe:'STLError' + + """ + ports = ports if ports is not None else self.get_acquired_ports() + ports = self._validate_port_list(ports) + + self.logger.pre_cmd("Removing RX queue on port(s) {0}:".format(ports)) + rc = self.__remove_rx_queue(ports) + self.logger.post_cmd(rc) + + if not rc: + raise STLError(rc) + + + + @__api_check(True) + def get_rx_queue_pkts (self, ports = None): + """ + Returns any packets queued on the RX side by the server + return value is a dictonary per port + + :parameters: + ports - for which ports to fetch + """ + + ports = ports if ports is not None else self.get_acquired_ports() + ports = self._validate_port_list(ports) + + rc = self.__get_rx_queue_pkts(ports) + if not rc: + raise STLError(rc) + + # decode the data back to the user + result = {} + for port, r in zip(ports, rc.data()): + result[port] = r + + return result + + def clear_events (self): """ Clear all events @@ -2701,7 +3168,7 @@ class STLClient(object): try: rc = f(*args) except STLError as e: - client.logger.log("Log:\n" + format_text(e.brief() + "\n", 'bold')) + client.logger.log("\nAction has failed with the following error:\n" + format_text(e.brief() + "\n", 'bold')) return RC_ERR(e.brief()) # if got true - print time @@ -2715,10 +3182,30 @@ class STLClient(object): @__console def ping_line (self, line): - '''pings the server''' - self.ping() - return RC_OK() + '''pings the server / specific IP''' + + # no parameters - so ping server + if not line: + self.ping_rpc_server() + return True + + parser = parsing_opts.gen_parser(self, + "ping", + self.ping_line.__doc__, + parsing_opts.SINGLE_PORT, + parsing_opts.PING_IPV4, + parsing_opts.PKT_SIZE, + parsing_opts.PING_COUNT) + opts = parser.parse_args(line.split()) + if not opts: + return opts + + # IP ping + # source ports maps to ports as a single port + self.ping_ip(opts.ports[0], opts.ping_ipv4, opts.pkt_size, opts.count) + + @__console def shutdown_line (self, line): '''shutdown the server''' @@ -2846,13 +3333,14 @@ class STLClient(object): parser = parsing_opts.gen_parser(self, "reset", self.reset_line.__doc__, - parsing_opts.PORT_LIST_WITH_ALL) + parsing_opts.PORT_LIST_WITH_ALL, + parsing_opts.PORT_RESTART) opts = parser.parse_args(line.split(), default_ports = self.get_acquired_ports(), verify_acquired = True) if not opts: return opts - self.reset(ports = opts.ports) + self.reset(ports = opts.ports, restart = opts.restart) return RC_OK() @@ -2888,14 +3376,19 @@ class STLClient(object): # just for sanity - will be checked on the API as well self.__decode_core_mask(opts.ports, core_mask) + # for better use experience - check this first + try: + self.__pre_start_check(opts.ports, opts.force) + except STLError as e: + msg = e.brief() + self.logger.log(format_text(msg, 'bold')) + return RC_ERR(msg) + + + # stop ports if needed active_ports = list_intersect(self.get_active_ports(), opts.ports) - if active_ports: - if not opts.force: - msg = "Port(s) {0} are active - please stop them or add '--force'\n".format(active_ports) - self.logger.log(format_text(msg, 'bold')) - return RC_ERR(msg) - else: - self.stop(active_ports) + if active_ports and opts.force: + self.stop(active_ports) # process tunables @@ -2920,9 +3413,8 @@ class STLClient(object): self.add_streams(profile.get_streams(), ports = port) except STLError as e: - error = 'Unknown error.' - for line in e.brief().split('\n'): - if line: + for line in e.brief().splitlines(): + if ansi_len(line.strip()): error = line msg = format_text("\nError loading profile '{0}'".format(opts.file[0]), 'bold') self.logger.log(msg + '\n') @@ -3171,21 +3663,29 @@ class STLClient(object): @__console def push_line (self, line): '''Push a pcap file ''' + args = [self, + "push", + self.push_line.__doc__, + parsing_opts.REMOTE_FILE, + parsing_opts.PORT_LIST_WITH_ALL, + parsing_opts.COUNT, + parsing_opts.DURATION, + parsing_opts.IPG, + parsing_opts.MIN_IPG, + parsing_opts.SPEEDUP, + parsing_opts.FORCE, + parsing_opts.DUAL] + + parser = parsing_opts.gen_parser(*(args + [parsing_opts.FILE_PATH_NO_CHECK])) + opts = parser.parse_args(line.split(), verify_acquired = True) - parser = parsing_opts.gen_parser(self, - "push", - self.push_line.__doc__, - parsing_opts.FILE_PATH, - parsing_opts.REMOTE_FILE, - parsing_opts.PORT_LIST_WITH_ALL, - parsing_opts.COUNT, - parsing_opts.DURATION, - parsing_opts.IPG, - parsing_opts.SPEEDUP, - parsing_opts.FORCE, - parsing_opts.DUAL) + if not opts: + return opts + + if not opts.remote: + parser = parsing_opts.gen_parser(*(args + [parsing_opts.FILE_PATH])) + opts = parser.parse_args(line.split(), verify_acquired = True) - opts = parser.parse_args(line.split(), verify_acquired = True) if not opts: return opts @@ -3202,22 +3702,24 @@ class STLClient(object): if opts.remote: self.push_remote(opts.file[0], - ports = opts.ports, - ipg_usec = opts.ipg_usec, - speedup = opts.speedup, - count = opts.count, - duration = opts.duration, - is_dual = opts.dual) + ports = opts.ports, + ipg_usec = opts.ipg_usec, + min_ipg_usec = opts.min_ipg_usec, + speedup = opts.speedup, + count = opts.count, + duration = opts.duration, + is_dual = opts.dual) else: self.push_pcap(opts.file[0], - ports = opts.ports, - ipg_usec = opts.ipg_usec, - speedup = opts.speedup, - count = opts.count, - duration = opts.duration, - force = opts.force, - is_dual = opts.dual) + ports = opts.ports, + ipg_usec = opts.ipg_usec, + min_ipg_usec = opts.min_ipg_usec, + speedup = opts.speedup, + count = opts.count, + duration = opts.duration, + force = opts.force, + is_dual = opts.dual) @@ -3230,7 +3732,7 @@ class STLClient(object): '''Sets port attributes ''' parser = parsing_opts.gen_parser(self, - "port_attr", + "portattr", self.set_port_attr_line.__doc__, parsing_opts.PORT_LIST_WITH_ALL, parsing_opts.PROMISCUOUS, @@ -3244,18 +3746,18 @@ class STLClient(object): if not opts: return opts - opts.prom = parsing_opts.ON_OFF_DICT.get(opts.prom) - opts.link = parsing_opts.UP_DOWN_DICT.get(opts.link) - opts.led = parsing_opts.ON_OFF_DICT.get(opts.led) - opts.flow_ctrl = parsing_opts.FLOW_CTRL_DICT.get(opts.flow_ctrl) + opts.prom = parsing_opts.ON_OFF_DICT.get(opts.prom) + opts.link = parsing_opts.UP_DOWN_DICT.get(opts.link) + opts.led = parsing_opts.ON_OFF_DICT.get(opts.led) + opts.flow_ctrl = parsing_opts.FLOW_CTRL_DICT.get(opts.flow_ctrl) # if no attributes - fall back to printing the status - if not filter(lambda x:x is not None, [opts.prom, opts.link, opts.led, opts.flow_ctrl, opts.supp]): + if not list(filter(lambda x:x is not None, [opts.prom, opts.link, opts.led, opts.flow_ctrl, opts.supp])): self.show_stats_line("--ps --port {0}".format(' '.join(str(port) for port in opts.ports))) return if opts.supp: - info = self.ports[0].get_info() # assume for now all ports are same + info = self.ports[0].get_formatted_info() # assume for now all ports are same print('') print('Supported attributes for current NICs:') print(' Promiscuous: yes') @@ -3264,15 +3766,115 @@ class STLClient(object): print(' Flow control: %s' % info['fc_supported']) print('') else: - return self.set_port_attr(opts.ports, opts.prom, opts.link, opts.led, opts.flow_ctrl) + self.set_port_attr(opts.ports, + opts.prom, + opts.link, + opts.led, + opts.flow_ctrl) + + + + + @__console + def set_rx_sniffer_line (self, line): + '''Sets a port sniffer on RX channel in form of a PCAP file''' + + parser = parsing_opts.gen_parser(self, + "set_rx_sniffer", + self.set_rx_sniffer_line.__doc__, + parsing_opts.PORT_LIST_WITH_ALL, + parsing_opts.OUTPUT_FILENAME, + parsing_opts.LIMIT) + + opts = parser.parse_args(line.split(), default_ports = self.get_acquired_ports(), verify_acquired = True) + if not opts: + return opts + + self.set_rx_sniffer(opts.ports, opts.output_filename, opts.limit) + + return RC_OK() + + + @__console + def resolve_line (self, line): + '''Performs a port ARP resolution''' + + parser = parsing_opts.gen_parser(self, + "resolve", + self.resolve_line.__doc__, + parsing_opts.PORT_LIST_WITH_ALL, + parsing_opts.RETRIES) + + opts = parser.parse_args(line.split(), default_ports = self.get_resolvable_ports(), verify_acquired = True) + if not opts: + return opts + + ports = list_intersect(opts.ports, self.get_resolvable_ports()) + if not ports: + if not opts.ports: + msg = 'resolve - no ports with IPv4 destination' + else: + msg = 'pause - none of ports {0} are configured with IPv4 destination'.format(opts.ports) + + self.logger.log(msg) + return RC_ERR(msg) + + self.resolve(ports = ports, retries = opts.retries) + + return RC_OK() + + + @__console + def set_l2_mode_line (self, line): + '''Configures a port in L2 mode''' + parser = parsing_opts.gen_parser(self, + "port", + self.set_l2_mode_line.__doc__, + parsing_opts.SINGLE_PORT, + parsing_opts.DST_MAC, + ) + opts = parser.parse_args(line.split()) + if not opts: + return opts + + + # source ports maps to ports as a single port + self.set_l2_mode(opts.ports[0], dst_mac = opts.dst_mac) + + return RC_OK() + + + @__console + def set_l3_mode_line (self, line): + '''Configures a port in L3 mode''' + + parser = parsing_opts.gen_parser(self, + "port", + self.set_l3_mode_line.__doc__, + parsing_opts.SINGLE_PORT, + parsing_opts.SRC_IPV4, + parsing_opts.DST_IPV4, + ) + + opts = parser.parse_args(line.split()) + if not opts: + return opts + + + # source ports maps to ports as a single port + self.set_l3_mode(opts.ports[0], src_ipv4 = opts.src_ipv4, dst_ipv4 = opts.dst_ipv4) + + return RC_OK() + + @__console def show_profile_line (self, line): '''Shows profile information''' parser = parsing_opts.gen_parser(self, - "port", + "profile", self.show_profile_line.__doc__, parsing_opts.FILE_PATH) @@ -3364,7 +3966,38 @@ class STLClient(object): return "{0}(read-only)>".format(prefix) elif self.is_all_ports_acquired(): - return "{0}>".format(prefix) + + p = prefix + + if self.get_service_enabled_ports(): + if self.get_service_enabled_ports() == self.get_acquired_ports(): + p += '(service)' + else: + p += '(service: {0})'.format(', '.join(map(str, self.get_service_enabled_ports()))) + + return "{0}>".format(p) else: - return "{0} {1}>".format(prefix, self.get_acquired_ports()) + return "{0} (ports: {1})>".format(prefix, ', '.join(map(str, self.get_acquired_ports()))) + + + + @__console + def service_line (self, line): + '''Configures port for service mode. + In service mode ports will reply to ARP, PING + and etc. + ''' + + parser = parsing_opts.gen_parser(self, + "service", + self.service_line.__doc__, + parsing_opts.PORT_LIST_WITH_ALL, + parsing_opts.SERVICE_OFF) + + opts = parser.parse_args(line.split()) + if not opts: + return opts + + self.set_service_mode(ports = opts.ports, enabled = opts.enabled) + diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_jsonrpc_client.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_jsonrpc_client.py index 1461fcec..72c9317a 100644 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_jsonrpc_client.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_jsonrpc_client.py @@ -32,13 +32,29 @@ class BatchMessage(object): id, msg = self.rpc_client.create_jsonrpc_v2(method_name, params, api_class, encode = False) self.batch_list.append(msg) - def invoke(self, block = False): + def invoke(self, block = False, chunk_size = 500000): if not self.rpc_client.connected: return RC_ERR("Not connected to server") - msg = json.dumps(self.batch_list) - - return self.rpc_client.send_msg(msg) + if chunk_size: + response_batch = RC() + size = 0 + new_batch = [] + for msg in self.batch_list: + size += len(json.dumps(msg)) + new_batch.append(msg) + if size > chunk_size: + batch_json = json.dumps(new_batch) + response_batch.add(self.rpc_client.send_msg(batch_json)) + size = 0 + new_batch = [] + if new_batch: + batch_json = json.dumps(new_batch) + response_batch.add(self.rpc_client.send_msg(batch_json)) + return response_batch + else: + batch_json = json.dumps(self.batch_list) + return self.rpc_client.send_msg(batch_json) # JSON RPC v2.0 client @@ -130,13 +146,13 @@ class JsonRpcClient(object): if self.zipper.check_threshold(buffer): response = self.send_raw_msg(self.zipper.compress(buffer)) - if response: - response = self.zipper.decompress(response) else: response = self.send_raw_msg(buffer) if not response: return response + elif self.zipper.is_compressed(response): + response = self.zipper.decompress(response) # return to string response = response.decode() @@ -172,6 +188,10 @@ class JsonRpcClient(object): self.disconnect() return RC_ERR("*** [RPC] - Failed to send message to server") + except KeyboardInterrupt as e: + # must restore the socket to a sane state + self.reconnect() + raise e tries = 0 while True: @@ -184,6 +204,10 @@ class JsonRpcClient(object): self.disconnect() return RC_ERR("*** [RPC] - Failed to get server response from {0}".format(self.transport)) + except KeyboardInterrupt as e: + # must restore the socket to a sane state + self.reconnect() + raise e return response @@ -255,11 +279,6 @@ class JsonRpcClient(object): self.connected = True - rc = self.invoke_rpc_method('ping', api_class = None) - if not rc: - self.connected = False - return rc - return RC_OK() @@ -267,12 +286,6 @@ class JsonRpcClient(object): # connect using current values return self.connect() - if not self.connected: - return RC_ERR("Not connected to server") - - # reconnect - return self.connect(self.server, self.port) - def is_connected(self): return self.connected diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_packet_builder_scapy.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_packet_builder_scapy.py index dc06f9fb..6431b74a 100755 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_packet_builder_scapy.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_packet_builder_scapy.py @@ -486,7 +486,7 @@ class CTRexScapyPktUtl(object): if f.name == field_name: return (l_offset+f.offset,f.get_size_bytes ()); - raise CTRexPacketBuildException(-11, "No layer %s-%d." % (name, save_cnt, field_name)); + raise CTRexPacketBuildException(-11, "No layer %s-%d." % (field_name, layer_cnt)) def get_layer_offet_by_str(self, layer_des): """ @@ -827,6 +827,7 @@ class STLVmFixChecksumHw(CTRexVmDescBase): self.l3_offset = l3_offset; # could be a name of offset self.l4_offset = l4_offset; # could be a name of offset self.l4_type = l4_type + self.l2_len = 0 def get_obj (self): @@ -838,8 +839,8 @@ class STLVmFixChecksumHw(CTRexVmDescBase): if type(self.l4_offset)==str: self.l4_offset = parent._pkt_layer_offset(self.l4_offset); - assert self.l4_offset >= self.l2_len+8, 'l4_offset should be higher than l3_offset offset' - self.l3_len = self.l4_offset - self.l2_len; + assert self.l4_offset >= self.l2_len+8, 'l4_offset should be higher than l3_offset offset' + self.l3_len = self.l4_offset - self.l2_len; class STLVmFixIpv4(CTRexVmDescBase): @@ -1084,58 +1085,58 @@ class STLVmWrMaskFlowVar(CTRexVmDescBase): class STLVmTrimPktSize(CTRexVmDescBase): - """ - Trim the packet size by the stream variable size. This instruction only changes the total packet size, and does not repair the fields to match the new size. + def __init__(self,fv_name): + """ + Trim the packet size by the stream variable size. This instruction only changes the total packet size, and does not repair the fields to match the new size. - :parameters: - fv_name : string - Stream variable name. The value of this variable is the new total packet size. + :parameters: + fv_name : string + Stream variable name. The value of this variable is the new total packet size. - For Example:: + For Example:: - def create_stream (self): - # pkt - p_l2 = Ether(); - p_l3 = IP(src="16.0.0.1",dst="48.0.0.1") - p_l4 = UDP(dport=12,sport=1025) - pyld_size = max(0, self.max_pkt_size_l3 - len(p_l3/p_l4)); - base_pkt = p_l2/p_l3/p_l4/('\x55'*(pyld_size)) - - l3_len_fix =-(len(p_l2)); - l4_len_fix =-(len(p_l2/p_l3)); - - - # vm - vm = STLScVmRaw( [ STLVmFlowVar(name="fv_rand", min_value=64, - max_value=len(base_pkt), - size=2, op="inc"), + def create_stream (self): + # pkt + p_l2 = Ether(); + p_l3 = IP(src="16.0.0.1",dst="48.0.0.1") + p_l4 = UDP(dport=12,sport=1025) + pyld_size = max(0, self.max_pkt_size_l3 - len(p_l3/p_l4)); + base_pkt = p_l2/p_l3/p_l4/('\x55'*(pyld_size)) - STLVmTrimPktSize("fv_rand"), # change total packet size <<< + l3_len_fix =-(len(p_l2)); + l4_len_fix =-(len(p_l2/p_l3)); - STLVmWrFlowVar(fv_name="fv_rand", - pkt_offset= "IP.len", - add_val=l3_len_fix), # fix ip len - STLVmFixIpv4(offset = "IP"), # fix checksum + # vm + vm = STLScVmRaw( [ STLVmFlowVar(name="fv_rand", min_value=64, + max_value=len(base_pkt), + size=2, op="inc"), - STLVmWrFlowVar(fv_name="fv_rand", - pkt_offset= "UDP.len", - add_val=l4_len_fix) # fix udp len - ] - ) - - pkt = STLPktBuilder(pkt = base_pkt, - vm = vm) - - return STLStream(packet = pkt, - mode = STLTXCont()) + STLVmTrimPktSize("fv_rand"), # change total packet size <<< + STLVmWrFlowVar(fv_name="fv_rand", + pkt_offset= "IP.len", + add_val=l3_len_fix), # fix ip len - """ + STLVmFixIpv4(offset = "IP"), # fix checksum + + STLVmWrFlowVar(fv_name="fv_rand", + pkt_offset= "UDP.len", + add_val=l4_len_fix) # fix udp len + ] + ) + + pkt = STLPktBuilder(pkt = base_pkt, + vm = vm) + + return STLStream(packet = pkt, + mode = STLTXCont()) + + + """ - def __init__(self,fv_name): super(STLVmTrimPktSize, self).__init__() self.name = fv_name validate_type('fv_name', fv_name, str) diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_port.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_port.py index cec3761f..9eefc177 100644 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_port.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_port.py @@ -4,12 +4,14 @@ from collections import namedtuple, OrderedDict from .trex_stl_packet_builder_scapy import STLPktBuilder from .trex_stl_streams import STLStream from .trex_stl_types import * +from .trex_stl_rx_features import ARPResolver, PingResolver from . import trex_stl_stats from .utils.constants import FLOW_CTRL_DICT_REVERSED import base64 import copy from datetime import datetime, timedelta +import threading StreamOnPort = namedtuple('StreamOnPort', ['compiled_stream', 'metadata']) @@ -50,7 +52,9 @@ class Port(object): def __init__ (self, port_id, user, comm_link, session_id, info): self.port_id = port_id + self.state = self.STATE_IDLE + self.handler = None self.comm_link = comm_link self.transmit = comm_link.transmit @@ -62,7 +66,7 @@ class Port(object): self.streams = {} self.profile = None self.session_id = session_id - self.attr = {} + self.status = {} self.port_stats = trex_stl_stats.CPortStats(self) @@ -72,31 +76,31 @@ class Port(object): self.owner = '' self.last_factor_type = None - + + self.__attr = {} + self.attr_lock = threading.Lock() + # decorator to verify port is up def up(func): - def func_wrapper(*args): + def func_wrapper(*args, **kwargs): port = args[0] if not port.is_up(): return port.err("{0} - port is down".format(func.__name__)) - return func(*args) + return func(*args, **kwargs) return func_wrapper # owned def owned(func): - def func_wrapper(*args): + def func_wrapper(*args, **kwargs): port = args[0] - if not port.is_up(): - return port.err("{0} - port is down".format(func.__name__)) - if not port.is_acquired(): return port.err("{0} - port is not owned".format(func.__name__)) - return func(*args) + return func(*args, **kwargs) return func_wrapper @@ -106,14 +110,11 @@ class Port(object): def func_wrapper(*args, **kwargs): port = args[0] - if not port.is_up(): - return port.err("{0} - port is down".format(func.__name__)) - if not port.is_acquired(): return port.err("{0} - port is not owned".format(func.__name__)) if not port.is_writeable(): - return port.err("{0} - port is not in a writeable state".format(func.__name__)) + return port.err("{0} - port is active, please stop the port before executing command".format(func.__name__)) return func(*args, **kwargs) @@ -122,22 +123,22 @@ class Port(object): def err(self, msg): - return RC_ERR("port {0} : {1}\n".format(self.port_id, msg)) + return RC_ERR("port {0} : *** {1}".format(self.port_id, msg)) def ok(self, data = ""): return RC_OK(data) def get_speed_bps (self): - return (self.info['speed'] * 1000 * 1000 * 1000) + return (self.get_speed_gbps() * 1000 * 1000 * 1000) - def get_formatted_speed (self): - return "{0} Gbps".format(self.info['speed']) + def get_speed_gbps (self): + return self.__attr['speed'] def is_acquired(self): return (self.handler != None) def is_up (self): - return (self.state != self.STATE_DOWN) + return self.__attr['link']['up'] def is_active(self): return (self.state == self.STATE_TX ) or (self.state == self.STATE_PAUSE) or (self.state == self.STATE_PCAP_TX) @@ -165,7 +166,6 @@ class Port(object): # take the port - @up def acquire(self, force = False, sync_streams = True): params = {"port_id": self.port_id, "user": self.user, @@ -185,7 +185,6 @@ class Port(object): # sync all the streams with the server - @up def sync_streams (self): params = {"port_id": self.port_id} @@ -201,7 +200,6 @@ class Port(object): return self.ok() # release the port - @up def release(self): params = {"port_id": self.port_id, "handler": self.handler} @@ -219,7 +217,6 @@ class Port(object): - @up def sync(self): params = {"port_id": self.port_id} @@ -250,10 +247,10 @@ class Port(object): self.next_available_id = int(rc.data()['max_stream_id']) + 1 - # attributes - self.attr = rc.data()['attr'] - if 'speed' in rc.data(): - self.info['speed'] = rc.data()['speed'] // 1000 + self.status = rc.data() + + # replace the attributes in a thread safe manner + self.set_ts_attr(rc.data()['attr']) return self.ok() @@ -424,8 +421,8 @@ class Port(object): # save this for TUI self.last_factor_type = mul['type'] - - return self.ok() + + return rc # stop traffic @@ -445,8 +442,9 @@ class Port(object): return self.err(rc.err()) self.state = self.STATE_STREAMS + self.last_factor_type = None - + # timestamp for last tx self.tx_stopped_ts = datetime.now() @@ -487,6 +485,122 @@ class Port(object): return self.ok() + + @owned + def set_rx_sniffer (self, pcap_filename, limit): + + if not self.is_service_mode_on(): + return self.err('port service mode must be enabled for performing RX capturing. Please enable service mode') + + params = {"handler": self.handler, + "port_id": self.port_id, + "type": "capture", + "enabled": True, + "pcap_filename": pcap_filename, + "limit": limit} + + rc = self.transmit("set_rx_feature", params) + if rc.bad(): + return self.err(rc.err()) + + return self.ok() + + + @owned + def remove_rx_sniffer (self): + params = {"handler": self.handler, + "port_id": self.port_id, + "type": "capture", + "enabled": False} + + rc = self.transmit("set_rx_feature", params) + if rc.bad(): + return self.err(rc.err()) + + return self.ok() + + @writeable + def set_l2_mode (self, dst_mac): + if not self.is_service_mode_on(): + return self.err('port service mode must be enabled for configuring L2 mode. Please enable service mode') + + params = {"handler": self.handler, + "port_id": self.port_id, + "dst_mac": dst_mac} + + rc = self.transmit("set_l2", params) + if rc.bad(): + return self.err(rc.err()) + + return self.sync() + + + @writeable + def set_l3_mode (self, src_addr, dest_addr, resolved_mac = None): + if not self.is_service_mode_on(): + return self.err('port service mode must be enabled for configuring L3 mode. Please enable service mode') + + params = {"handler": self.handler, + "port_id": self.port_id, + "src_addr": src_addr, + "dst_addr": dest_addr} + + if resolved_mac: + params["resolved_mac"] = resolved_mac + + rc = self.transmit("set_l3", params) + if rc.bad(): + return self.err(rc.err()) + + return self.sync() + + + @owned + def set_rx_queue (self, size): + + params = {"handler": self.handler, + "port_id": self.port_id, + "type": "queue", + "enabled": True, + "size": size} + + rc = self.transmit("set_rx_feature", params) + if rc.bad(): + return self.err(rc.err()) + + return self.ok() + + @owned + def remove_rx_queue (self): + params = {"handler": self.handler, + "port_id": self.port_id, + "type": "queue", + "enabled": False} + + rc = self.transmit("set_rx_feature", params) + if rc.bad(): + return self.err(rc.err()) + + return self.ok() + + @owned + def get_rx_queue_pkts (self): + params = {"handler": self.handler, + "port_id": self.port_id} + + rc = self.transmit("get_rx_queue_pkts", params) + if rc.bad(): + return self.err(rc.err()) + + pkts = rc.data()['pkts'] + + # decode the packets from base64 to binary + for i in range(len(pkts)): + pkts[i]['binary'] = base64.b64decode(pkts[i]['binary']) + + return RC_OK(pkts) + + @owned def pause (self): @@ -568,23 +682,60 @@ class Port(object): @owned - def set_attr (self, attr_dict): + def set_attr (self, **kwargs): + + json_attr = {} + + if kwargs.get('promiscuous') is not None: + json_attr['promiscuous'] = {'enabled': kwargs.get('promiscuous')} + + if kwargs.get('link_status') is not None: + json_attr['link_status'] = {'up': kwargs.get('link_status')} + + if kwargs.get('led_status') is not None: + json_attr['led_status'] = {'on': kwargs.get('led_status')} + + if kwargs.get('flow_ctrl_mode') is not None: + json_attr['flow_ctrl_mode'] = {'mode': kwargs.get('flow_ctrl_mode')} + + if kwargs.get('rx_filter_mode') is not None: + json_attr['rx_filter_mode'] = {'mode': kwargs.get('rx_filter_mode')} + params = {"handler": self.handler, "port_id": self.port_id, - "attr": attr_dict} + "attr": json_attr} rc = self.transmit("set_port_attr", params) if rc.bad(): return self.err(rc.err()) + # update the dictionary from the server explicitly + return self.sync() - #self.attr.update(attr_dict) - + + @owned + def set_service_mode (self, enabled): + rc = self.set_attr(rx_filter_mode = 'all' if enabled else 'hw') + if not rc: + return rc + + if not enabled: + rc = self.remove_rx_queue() + if not rc: + return rc + + rc = self.remove_rx_sniffer() + if not rc: + return rc + return self.ok() + def is_service_mode_on (self): + return self.get_rx_filter_mode() == 'all' + @writeable - def push_remote (self, pcap_filename, ipg_usec, speedup, count, duration, is_dual, slave_handler): + def push_remote (self, pcap_filename, ipg_usec, speedup, count, duration, is_dual, slave_handler, min_ipg_usec): params = {"handler": self.handler, "port_id": self.port_id, @@ -594,7 +745,8 @@ class Port(object): "count": count, "duration": duration, "is_dual": is_dual, - "slave_handler": slave_handler} + "slave_handler": slave_handler, + "min_ipg_usec": min_ipg_usec if min_ipg_usec else 0} rc = self.transmit("push_remote", params) if rc.bad(): @@ -607,7 +759,16 @@ class Port(object): def get_profile (self): return self.profile - + # invalidates the current ARP + def invalidate_arp (self): + dest = self.__attr['dest'] + + if dest['type'] != 'mac': + return self.set_attr(dest = dest['ipv4']) + else: + return self.ok() + + def print_profile (self, mult, duration): if not self.get_profile(): return @@ -648,24 +809,32 @@ class Port(object): format_time(exp_time_factor_sec))) print("\n") - # generate port info - def get_info (self): + # generate formatted (console friendly) port info + def get_formatted_info (self, sync = True): + + # sync the status + if sync: + self.sync() + + # get a copy of the current attribute set (safe against manipulation) + attr = self.get_ts_attr() + info = dict(self.info) info['status'] = self.get_port_state_name() - if 'link' in self.attr: - info['link'] = 'UP' if self.attr['link']['up'] else 'DOWN' + if 'link' in attr: + info['link'] = 'UP' if attr['link']['up'] else 'DOWN' else: info['link'] = 'N/A' - if 'fc' in self.attr: - info['fc'] = FLOW_CTRL_DICT_REVERSED.get(self.attr['fc']['mode'], 'N/A') + if 'fc' in attr: + info['fc'] = FLOW_CTRL_DICT_REVERSED.get(attr['fc']['mode'], 'N/A') else: info['fc'] = 'N/A' - if 'promiscuous' in self.attr: - info['prom'] = "on" if self.attr['promiscuous']['enabled'] else "off" + if 'promiscuous' in attr: + info['prom'] = "on" if attr['promiscuous']['enabled'] else "off" else: info['prom'] = "N/A" @@ -692,34 +861,139 @@ class Port(object): else: info['is_virtual'] = 'N/A' + # speed + info['speed'] = self.get_speed_gbps() + + # RX filter mode + info['rx_filter_mode'] = 'hardware match' if attr['rx_filter_mode'] == 'hw' else 'fetch all' + + # src MAC and IPv4 + info['src_mac'] = attr['src_mac'] + info['src_ipv4'] = attr['src_ipv4'] + + if info['src_ipv4'] is None: + info['src_ipv4'] = '-' + + # dest + dest = attr['dest'] + if dest['type'] == 'mac': + info['dest'] = dest['mac'] + info['arp'] = '-' + + elif dest['type'] == 'ipv4': + info['dest'] = dest['ipv4'] + info['arp'] = dest['arp'] + + elif dest['type'] == 'ipv4_u': + info['dest'] = dest['ipv4'] + info['arp'] = 'unresolved' + + + # RX info + rx_info = self.status['rx_info'] + + # RX sniffer + sniffer = rx_info['sniffer'] + info['rx_sniffer'] = '{0}\n[{1} / {2}]'.format(sniffer['pcap_filename'], sniffer['count'], sniffer['limit']) if sniffer['is_active'] else 'off' + + + # RX queue + queue = rx_info['queue'] + info['rx_queue'] = '[{0} / {1}]'.format(queue['count'], queue['size']) if queue['is_active'] else 'off' + + # Grat ARP + grat_arp = rx_info['grat_arp'] + if grat_arp['is_active']: + info['grat_arp'] = "every {0} seconds".format(grat_arp['interval_sec']) + else: + info['grat_arp'] = "off" + + return info def get_port_state_name(self): return self.STATES_MAP.get(self.state, "Unknown") + def get_src_addr (self): + src_mac = self.__attr['src_mac'] + src_ipv4 = self.__attr['src_ipv4'] + + return {'mac': src_mac, 'ipv4': src_ipv4} + + def get_rx_filter_mode (self): + return self.__attr['rx_filter_mode'] + + def get_dst_addr (self): + dest = self.__attr['dest'] + + if dest['type'] == 'mac': + return {'ipv4': None, 'mac': dest['mac']} + + elif dest['type'] == 'ipv4': + return {'ipv4': dest['ipv4'], 'mac': dest['arp']} + + elif dest['type'] == 'ipv4_u': + return {'ipv4': dest['ipv4'], 'mac': None} + + else: + assert(0) + + + # port is considered resolved if it's dest is either MAC or resolved IPv4 + def is_resolved (self): + return (self.get_dst_addr()['mac'] is not None) + + # return True if the port is valid for resolve (has an IPv4 address as dest) + def is_resolvable (self): + return (self.get_dst_addr()['ipv4'] is not None) + + @writeable + def arp_resolve (self, retries): + if not self.is_service_mode_on(): + return self.err('port service mode must be enabled for performing ARP resolution. Please enable service mode') + + return ARPResolver(self).resolve(retries) + + @writeable + def ping (self, ping_ipv4, pkt_size): + if not self.is_service_mode_on(): + return self.err('port service mode must be enabled for performing ping. Please enable service mode') + + return PingResolver(self, ping_ipv4, pkt_size).resolve() + + ################# stats handler ###################### def generate_port_stats(self): return self.port_stats.generate_stats() def generate_port_status(self): - info = self.get_info() + info = self.get_formatted_info() - return {"driver": info['driver'], - "description": info.get('description', 'N/A')[:18], - "HW src mac": info['hw_macaddr'], - "SW src mac": info['src_macaddr'], - "SW dst mac": info['dst_macaddr'], - "PCI Address": info['pci_addr'], - "NUMA Node": info['numa'], + return {"driver": info['driver'], + "description": info.get('description', 'N/A')[:18], + "src MAC": info['src_mac'], + "src IPv4": info['src_ipv4'], + "Destination": info['dest'], + "ARP Resolution": format_text("{0}".format(info['arp']), 'bold', 'red') if info['arp'] == 'unresolved' else info['arp'], + "PCI Address": info['pci_addr'], + "NUMA Node": info['numa'], "--": "", "---": "", - "link speed": "{speed} Gb/s".format(speed=info['speed']), + "----": "", + "-----": "", + "link speed": "%g Gb/s" % info['speed'], "port status": info['status'], "link status": info['link'], "promiscuous" : info['prom'], "flow ctrl" : info['fc'], + + "RX Filter Mode": info['rx_filter_mode'], + "RX Queueing": info['rx_queue'], + "RX sniffer": info['rx_sniffer'], + "Grat ARP": info['grat_arp'], + } def clear_stats(self): @@ -756,17 +1030,54 @@ class Port(object): return {"streams" : OrderedDict(sorted(data.items())) } - + ######## attributes are a complex type (dict) that might be manipulated through the async thread ############# + + # get in a thread safe manner a duplication of attributes + def get_ts_attr (self): + with self.attr_lock: + return dict(self.__attr) + + # set in a thread safe manner a new dict of attributes + def set_ts_attr (self, new_attr): + with self.attr_lock: + self.__attr = new_attr + + ################# events handler ###################### def async_event_port_job_done (self): # until thread is locked - order is important self.tx_stopped_ts = datetime.now() self.state = self.STATE_STREAMS + self.last_factor_type = None - def async_event_port_attr_changed (self, attr): - self.info['speed'] = attr['speed'] // 1000 - self.attr = attr + def async_event_port_attr_changed (self, new_attr): + + # get a thread safe duplicate + cur_attr = self.get_ts_attr() + + # check if anything changed + if new_attr == cur_attr: + return None + + # generate before + before = self.get_formatted_info(sync = False) + + # update + self.set_ts_attr(new_attr) + + # generate after + after = self.get_formatted_info(sync = False) + + # return diff + diff = {} + for key, new_value in after.items(): + old_value = before.get(key, 'N/A') + if new_value != old_value: + diff[key] = (old_value, new_value) + + return diff + # rest of the events are used for TUI / read only sessions def async_event_port_stopped (self): @@ -792,3 +1103,4 @@ class Port(object): def async_event_released (self): self.owner = '' + diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_rx_features.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_rx_features.py new file mode 100644 index 00000000..ec83de5d --- /dev/null +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_rx_features.py @@ -0,0 +1,255 @@ + +from .trex_stl_streams import STLStream, STLTXSingleBurst +from .trex_stl_packet_builder_scapy import STLPktBuilder + +from scapy.layers.l2 import Ether, ARP +from scapy.layers.inet import IP, ICMP + +import time + +# a generic abstract class for resolving using the server +class Resolver(object): + def __init__ (self, port, queue_size = 100): + self.port = port + + # code to execute before sending any request - return RC object + def pre_send (self): + raise NotImplementedError() + + # return a list of streams for request + def generate_request (self): + raise NotImplementedError() + + # return None for more packets otherwise RC object + def on_pkt_rx (self, pkt): + raise NotImplementedError() + + # return value in case of timeout + def on_timeout_err (self, retries): + raise NotImplementedError() + + ##################### API ###################### + def resolve (self, retries = 0): + + # first cleanup + rc = self.port.remove_all_streams() + if not rc: + return rc + + # call the specific class implementation + rc = self.pre_send() + if not rc: + return rc + + # start the iteration + try: + + # add the stream(s) + self.port.add_streams(self.generate_request()) + rc = self.port.set_rx_queue(size = 100) + if not rc: + return rc + + return self.resolve_wrapper(retries) + + finally: + # best effort restore + self.port.remove_rx_queue() + self.port.remove_all_streams() + + + # main resolve function + def resolve_wrapper (self, retries): + + # retry for 'retries' + index = 0 + while True: + rc = self.resolve_iteration() + if rc is not None: + return rc + + if index >= retries: + return self.on_timeout_err(retries) + + index += 1 + time.sleep(0.1) + + + + def resolve_iteration (self): + + mult = {'op': 'abs', 'type' : 'percentage', 'value': 100} + rc = self.port.start(mul = mult, force = False, duration = -1, mask = 0xffffffff) + if not rc: + return rc + + # save the start timestamp + self.start_ts = rc.data()['ts'] + + # block until traffic finishes + while self.port.is_active(): + time.sleep(0.01) + + return self.wait_for_rx_response() + + + def wait_for_rx_response (self): + + # we try to fetch response for 5 times + polling = 5 + + while polling > 0: + + # fetch the queue + rx_pkts = self.port.get_rx_queue_pkts() + + # might be an error + if not rx_pkts: + return rx_pkts + + # for each packet - examine it + for pkt in rx_pkts.data(): + rc = self.on_pkt_rx(pkt) + if rc is not None: + return rc + + if polling == 0: + return None + + polling -= 1 + time.sleep(0.1) + + + + + +class ARPResolver(Resolver): + def __init__ (self, port_id): + super(ARPResolver, self).__init__(port_id) + + # before resolve + def pre_send (self): + self.dst = self.port.get_dst_addr() + self.src = self.port.get_src_addr() + + if self.dst['ipv4'] is None: + return self.port.err("Port has a non-IPv4 destination: '{0}'".format(self.dst['mac'])) + + if self.src['ipv4'] is None: + return self.port.err('Port must have an IPv4 source address configured') + + # invalidate the current ARP resolution (if exists) + return self.port.invalidate_arp() + + + # return a list of streams for request + def generate_request (self): + + base_pkt = Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(psrc = self.src['ipv4'], pdst = self.dst['ipv4'], hwsrc = self.src['mac']) + s1 = STLStream( packet = STLPktBuilder(pkt = base_pkt), mode = STLTXSingleBurst(total_pkts = 1) ) + + return [s1] + + + # return None in case more packets are needed else the status rc + def on_pkt_rx (self, pkt): + scapy_pkt = Ether(pkt['binary']) + if not 'ARP' in scapy_pkt: + return None + + arp = scapy_pkt['ARP'] + + # check this is the right ARP (ARP reply with the address) + if (arp.op != 2) or (arp.psrc != self.dst['ipv4']): + return None + + + # update the port with L3 full configuration + rc = self.port.set_l3_mode(self.src['ipv4'], self.dst['ipv4'], arp.hwsrc) + if not rc: + return rc + + return self.port.ok('Port {0} - Recieved ARP reply from: {1}, hw: {2}'.format(self.port.port_id, arp.psrc, arp.hwsrc)) + + + def on_timeout_err (self, retries): + return self.port.err('failed to receive ARP response ({0} retries)'.format(retries)) + + + + + #################### ping resolver #################### + +class PingResolver(Resolver): + def __init__ (self, port, ping_ip, pkt_size): + super(PingResolver, self).__init__(port) + self.ping_ip = ping_ip + self.pkt_size = pkt_size + + def pre_send (self): + + self.src = self.port.get_src_addr() + self.dst = self.port.get_dst_addr() + + if self.src['ipv4'] is None: + return self.port.err('Ping - port does not have an IPv4 address configured') + + if self.dst['mac'] is None: + return self.port.err('Ping - port has an unresolved destination, cannot determine next hop MAC address') + + return self.port.ok() + + + # return a list of streams for request + def generate_request (self): + + base_pkt = Ether(dst = self.dst['mac'])/IP(src = self.src['ipv4'], dst = self.ping_ip)/ICMP(type = 8) + pad = max(0, self.pkt_size - len(base_pkt)) + + base_pkt = base_pkt / (pad * 'x') + + #base_pkt.show2() + s1 = STLStream( packet = STLPktBuilder(pkt = base_pkt), mode = STLTXSingleBurst(total_pkts = 1) ) + + self.base_pkt = base_pkt + + return [s1] + + # return None for more packets otherwise RC object + def on_pkt_rx (self, pkt): + scapy_pkt = Ether(pkt['binary']) + if not 'ICMP' in scapy_pkt: + return None + + ip = scapy_pkt['IP'] + if ip.dst != self.src['ipv4']: + return None + + icmp = scapy_pkt['ICMP'] + + dt = pkt['ts'] - self.start_ts + + # echo reply + if icmp.type == 0: + # check seq + if icmp.seq != self.base_pkt['ICMP'].seq: + return None + return self.port.ok('Reply from {0}: bytes={1}, time={2:.2f}ms, TTL={3}'.format(ip.src, len(pkt['binary']), dt * 1000, ip.ttl)) + + # unreachable + elif icmp.type == 3: + # check seq + if icmp.payload.seq != self.base_pkt['ICMP'].seq: + return None + return self.port.ok('Reply from {0}: Destination host unreachable'.format(icmp.src)) + + else: + # skip any other types + #scapy_pkt.show2() + return None + + + + # return the str of a timeout err + def on_timeout_err (self, retries): + return self.port.ok('Request timed out.') diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py index 9f601484..c08a0af8 100644 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_stats.py @@ -670,12 +670,19 @@ class CTRexInfoGenerator(object): ("promiscuous", []), ("flow ctrl", []), ("--", []), - ("HW src mac", []), - ("SW src mac", []), - ("SW dst mac", []), + ("src IPv4", []), + ("src MAC", []), ("---", []), + ("Destination", []), + ("ARP Resolution", []), + ("----", []), ("PCI Address", []), ("NUMA Node", []), + ("-----", []), + ("RX Filter Mode", []), + ("RX Queueing", []), + ("RX sniffer", []), + ("Grat ARP", []), ] ) @@ -1103,13 +1110,7 @@ class CPortStats(CTRexStats): port_state = format_text(port_state, 'bold') if self._port_obj: - if 'link' in self._port_obj.attr: - if self._port_obj.attr.get('link', {}).get('up') == False: - link_state = format_text('DOWN', 'red', 'bold') - else: - link_state = 'UP' - else: - link_state = 'N/A' + link_state = 'UP' if self._port_obj.is_up() else format_text('DOWN', 'red', 'bold') else: link_state = '' @@ -1130,7 +1131,7 @@ class CPortStats(CTRexStats): return {"owner": owner, "state": "{0}".format(port_state), 'link': link_state, - "speed": self._port_obj.get_formatted_speed() if self._port_obj else '', + "speed": "%g Gb/s" % self._port_obj.get_speed_gbps() if self._port_obj else '', "CPU util.": "{0} {1}%".format(self.get_trend_gui("m_cpu_util", use_raw = True), format_threshold(round_float(self.get("m_cpu_util")), [85, 100], [0, 85])) if self._port_obj else '' , "--": " ", diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_streams.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_streams.py index e63f9125..26613e56 100755 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_streams.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_streams.py @@ -987,7 +987,8 @@ class STLProfile(object): loop_count = 1, vm = None, packet_hook = None, - split_mode = None): + split_mode = None, + min_ipg_usec = None): """ Convert a pcap file with a number of packets to a list of connected streams. packet1->packet2->packet3 etc @@ -1017,6 +1018,9 @@ class STLProfile(object): used for dual mode can be 'MAC' or 'IP' + min_ipg_usec : float + Minumum inter packet gap in usec. Used to guard from too small IPGs. + :return: STLProfile """ @@ -1024,9 +1028,15 @@ class STLProfile(object): # check filename if not os.path.isfile(pcap_file): raise STLError("file '{0}' does not exists".format(pcap_file)) + if speedup <= 0: + raise STLError('Speedup should not be negative.') + if min_ipg_usec and min_ipg_usec < 0: + raise STLError('min_ipg_usec should not be negative.') + - # make sure IPG is not less than 1 usec - if ipg_usec is not None and ipg_usec < 0.001: + # make sure IPG is not less than 0.001 usec + if (ipg_usec is not None and (ipg_usec < 0.001 * speedup) and + (min_ipg_usec is None or min_ipg_usec < 0.001)): raise STLError("ipg_usec cannot be less than 0.001 usec: '{0}'".format(ipg_usec)) if loop_count < 0: @@ -1039,6 +1049,7 @@ class STLProfile(object): pkts = PCAPReader(pcap_file).read_all() return STLProfile.__pkts_to_streams(pkts, ipg_usec, + min_ipg_usec, speedup, loop_count, vm, @@ -1046,8 +1057,20 @@ class STLProfile(object): else: pkts_a, pkts_b = PCAPReader(pcap_file).read_all(split_mode = split_mode) + # swap the packets if a is empty, or the ts of first packet in b is earlier + if not pkts_a: + pkts_a, pkts_b = pkts_b, pkts_a + elif (ipg_usec is None) and pkts_b: + meta = pkts_a[0][1] + start_time_a = meta[0] * 1e6 + meta[1] + meta = pkts_b[0][1] + start_time_b = meta[0] * 1e6 + meta[1] + if start_time_b < start_time_a: + pkts_a, pkts_b = pkts_b, pkts_a + profile_a = STLProfile.__pkts_to_streams(pkts_a, ipg_usec, + min_ipg_usec, speedup, loop_count, vm, @@ -1056,6 +1079,7 @@ class STLProfile(object): profile_b = STLProfile.__pkts_to_streams(pkts_b, ipg_usec, + min_ipg_usec, speedup, loop_count, vm, @@ -1070,23 +1094,27 @@ class STLProfile(object): @staticmethod - def __pkts_to_streams (pkts, ipg_usec, speedup, loop_count, vm, packet_hook, start_delay_usec = 0): + def __pkts_to_streams (pkts, ipg_usec, min_ipg_usec, speedup, loop_count, vm, packet_hook, start_delay_usec = 0): streams = [] - - # 10 ms delay before starting the PCAP - last_ts_usec = -(start_delay_usec) - if packet_hook: pkts = [(packet_hook(cap), meta) for (cap, meta) in pkts] - for i, (cap, meta) in enumerate(pkts, start = 1): # IPG - if not provided, take from cap - if ipg_usec == None: - ts_usec = (meta[0] * 1e6 + meta[1]) / float(speedup) - else: - ts_usec = (ipg_usec * i) / float(speedup) + if ipg_usec is None: + packet_time = meta[0] * 1e6 + meta[1] + if i == 1: + prev_time = packet_time + isg = (packet_time - prev_time) / float(speedup) + if min_ipg_usec and isg < min_ipg_usec: + isg = min_ipg_usec + prev_time = packet_time + else: # user specified ipg + if min_ipg_usec: + isg = min_ipg_usec + else: + isg = ipg_usec / float(speedup) # handle last packet if i == len(pkts): @@ -1100,13 +1128,11 @@ class STLProfile(object): packet = STLPktBuilder(pkt_buffer = cap, vm = vm), mode = STLTXSingleBurst(total_pkts = 1, percentage = 100), self_start = True if (i == 1) else False, - isg = (ts_usec - last_ts_usec), # seconds to usec + isg = isg, # usec action_count = action_count, next = next)) - - last_ts_usec = ts_usec - + profile = STLProfile(streams) profile.meta = {'type': 'pcap'} diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_types.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_types.py index aa6c4218..a60a7ede 100644 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_types.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/trex_stl_types.py @@ -50,11 +50,25 @@ class RC(): return (e if len(e) != 1 else e[0]) def __str__ (self): - s = "" - for x in self.rc_list: - if x.data: - s += format_text("\n{0}".format(x.data), 'bold') - return s + if self.good(): + s = "" + for x in self.rc_list: + if x.data: + s += format_text("\n{0}".format(x.data), 'bold') + return s + else: + show_count = 10 + err_list = [] + err_count = 0 + for x in filter(len, listify(self.err())): + err_count += 1 + if len(err_list) < show_count: + err_list.append(format_text(x, 'bold')) + s = '\n' if len(err_list) > 1 else '' + if err_count > show_count: + s += format_text('Occurred %s errors, showing first %s:\n' % (err_count, show_count), 'bold') + s += '\n'.join(err_list) + return s def __iter__(self): return self.rc_list.__iter__() @@ -135,6 +149,12 @@ def validate_type(arg_name, arg, valid_types): else: raise STLError('validate_type: valid_types should be type or list or tuple of types') + +def validate_choice (arg_name, arg, choices): + if arg is not None and not arg in choices: + raise STLError("validate_choice: argument '{0}' can only be one of '{1}'".format(arg_name, choices)) + + # throws STLError if not exactly one argument is present def verify_exclusive_arg (args_list): if not (len(list(filter(lambda x: x is not None, args_list))) == 1): 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 index 72ee8972..cbbacb27 100644 --- 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 @@ -3,6 +3,8 @@ import sys import string import random import time +import socket +import re try: import pwd @@ -86,3 +88,23 @@ class PassiveTimer(object): return (time.time() > self.expr_sec) +def is_valid_ipv4 (addr): + try: + socket.inet_pton(socket.AF_INET, addr) + return True + except (socket.error, TypeError): + return False + +def is_valid_mac (mac): + return bool(re.match("[0-9a-f]{2}([-:])[0-9a-f]{2}(\\1[0-9a-f]{2}){4}$", mac.lower())) + +def list_remove_dup (l): + tmp = list() + + for x in l: + if not x in tmp: + tmp.append(x) + + return tmp + + 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 7eda8635..f5dab30c 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 @@ -1,6 +1,6 @@ import argparse from collections import namedtuple, OrderedDict -from .common import list_intersect, list_difference +from .common import list_intersect, list_difference, is_valid_ipv4, is_valid_mac, list_remove_dup from .text_opts import format_text from ..trex_stl_types import * from .constants import ON_OFF_DICT, UP_DOWN_DICT, FLOW_CTRL_DICT @@ -14,56 +14,85 @@ 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 -IPG = 16 -SPEEDUP = 17 -COUNT = 18 -PROMISCUOUS = 19 -LINK_STATUS = 20 -LED_STATUS = 21 -TUNABLES = 22 -REMOTE_FILE = 23 -LOCKED = 24 -PIN_CORES = 25 -CORE_MASK = 26 -DUAL = 27 -FLOW_CTRL = 28 -SUPPORTED = 29 - -GLOBAL_STATS = 50 -PORT_STATS = 51 -PORT_STATUS = 52 -STREAMS_STATS = 53 -STATS_MASK = 54 -CPU_STATS = 55 -MBUF_STATS = 56 -EXTENDED_STATS = 57 -EXTENDED_INC_ZERO_STATS = 58 - -STREAMS_MASK = 60 -CORE_MASK_GROUP = 61 - -# ALL_STREAMS = 61 -# STREAM_LIST_WITH_ALL = 62 +_constants = ''' + +MULTIPLIER +MULTIPLIER_STRICT +PORT_LIST +ALL_PORTS +PORT_LIST_WITH_ALL +FILE_PATH +FILE_FROM_DB +SERVER_IP +STREAM_FROM_PATH_OR_FILE +DURATION +FORCE +DRY_RUN +XTERM +TOTAL +FULL_OUTPUT +IPG +MIN_IPG +SPEEDUP +COUNT +PROMISCUOUS +LINK_STATUS +LED_STATUS +TUNABLES +REMOTE_FILE +LOCKED +PIN_CORES +CORE_MASK +DUAL +FLOW_CTRL +SUPPORTED +FILE_PATH_NO_CHECK + +OUTPUT_FILENAME +LIMIT +PORT_RESTART + +RETRIES + +SINGLE_PORT +DST_MAC + +PING_IPV4 +PING_COUNT +PKT_SIZE + +SERVICE_OFF + +SRC_IPV4 +DST_IPV4 + +GLOBAL_STATS +PORT_STATS +PORT_STATUS +STREAMS_STATS +STATS_MASK +CPU_STATS +MBUF_STATS +EXTENDED_STATS +EXTENDED_INC_ZERO_STATS + +STREAMS_MASK +CORE_MASK_GROUP + +# ALL_STREAMS +# STREAM_LIST_WITH_ALL +# list of ArgumentGroup types +MUTEX +''' + +for index, line in enumerate(_constants.splitlines()): + var = line.strip().split() + if not var or '#' in var[0]: + continue + exec('%s = %s' % (var[0], index)) -# list of ArgumentGroup types -MUTEX = 1 def check_negative(value): ivalue = int(value) @@ -217,8 +246,30 @@ def is_valid_file(filename): return filename +def check_ipv4_addr (ipv4_str): + if not is_valid_ipv4(ipv4_str): + raise argparse.ArgumentTypeError("invalid IPv4 address: '{0}'".format(ipv4_str)) + return ipv4_str + +def check_pkt_size (pkt_size): + try: + pkt_size = int(pkt_size) + except ValueError: + raise argparse.ArgumentTypeError("invalid packet size type: '{0}'".format(pkt_size)) + + if (pkt_size < 64) or (pkt_size > 9216): + raise argparse.ArgumentTypeError("invalid packet size: '{0}' - valid range is 64 to 9216".format(pkt_size)) + + return pkt_size + +def check_mac_addr (addr): + if not is_valid_mac(addr): + raise argparse.ArgumentTypeError("not a valid MAC address: '{0}'".format(addr)) + + return addr + def decode_tunables (tunable_str): tunables = {} @@ -273,6 +324,11 @@ OPTIONS_DB = {MULTIPLIER: ArgumentPack(['-m', '--multiplier'], 'default': None, 'type': float}), + MIN_IPG: ArgumentPack(['--min-ipg'], + {'help': "Minimal IPG value in usec between packets. Used to guard from too small IPGs.", + 'dest': "min_ipg_usec", + 'default': None, + 'type': float}), SPEEDUP: ArgumentPack(['-s', '--speedup'], {'help': "Factor to accelerate the injection. effectively means IPG = IPG / SPEEDUP", @@ -303,6 +359,53 @@ OPTIONS_DB = {MULTIPLIER: ArgumentPack(['-m', '--multiplier'], 'dest': 'flow_ctrl', 'choices': FLOW_CTRL_DICT}), + SRC_IPV4: ArgumentPack(['--src'], + {'help': 'Configure source IPv4 address', + 'dest': 'src_ipv4', + 'required': True, + 'type': check_ipv4_addr}), + + DST_IPV4: ArgumentPack(['--dst'], + {'help': 'Configure destination IPv4 address', + 'dest': 'dst_ipv4', + 'required': True, + 'type': check_ipv4_addr}), + + + DST_MAC: ArgumentPack(['--dst'], + {'help': 'Configure destination MAC address', + 'dest': 'dst_mac', + 'required': True, + 'type': check_mac_addr}), + + RETRIES: ArgumentPack(['-r', '--retries'], + {'help': 'retries count [default is zero]', + 'dest': 'retries', + 'default': 0, + 'type': int}), + + + OUTPUT_FILENAME: ArgumentPack(['-o', '--output'], + {'help': 'Output PCAP filename', + 'dest': 'output_filename', + 'default': None, + 'required': True, + 'type': str}), + + + PORT_RESTART: ArgumentPack(['-r', '--restart'], + {'help': 'hard restart port(s)', + 'dest': 'restart', + 'default': False, + 'action': 'store_true'}), + + LIMIT: ArgumentPack(['-l', '--limit'], + {'help': 'Limit the packet count to be written to the file', + 'dest': 'limit', + 'default': 1000, + 'type': int}), + + SUPPORTED: ArgumentPack(['--supp'], {'help': 'Show which attributes are supported by current NICs', 'default': None, @@ -325,6 +428,33 @@ OPTIONS_DB = {MULTIPLIER: ArgumentPack(['-m', '--multiplier'], 'help': "A list of ports on which to apply the command", 'default': []}), + + SINGLE_PORT: ArgumentPack(['--port', '-p'], + {'dest':'ports', + 'type': int, + 'metavar': 'PORT', + 'help': 'source port for the action', + 'required': True}), + + PING_IPV4: ArgumentPack(['-d'], + {'help': 'which IPv4 to ping', + 'dest': 'ping_ipv4', + 'required': True, + 'type': check_ipv4_addr}), + + PING_COUNT: ArgumentPack(['-n', '--count'], + {'help': 'How many times to ping [default is 5]', + 'dest': 'count', + 'default': 5, + 'type': int}), + + PKT_SIZE: ArgumentPack(['-s'], + {'dest':'pkt_size', + 'help': 'packet size to use', + 'default': 64, + 'type': check_pkt_size}), + + ALL_PORTS: ArgumentPack(['-a'], {"action": "store_true", "dest": "all_ports", @@ -362,6 +492,14 @@ OPTIONS_DB = {MULTIPLIER: ArgumentPack(['-m', '--multiplier'], 'type': is_valid_file, 'help': "File path to load"}), + FILE_PATH_NO_CHECK: ArgumentPack(['-f'], + {'metavar': 'FILE', + 'dest': 'file', + 'nargs': 1, + 'required': True, + 'type': str, + 'help': "File path to load"}), + FILE_FROM_DB: ArgumentPack(['--db'], {'metavar': 'LOADED_STREAM_PACK', 'help': "A stream pack which already loaded into console cache."}), @@ -447,11 +585,18 @@ OPTIONS_DB = {MULTIPLIER: ArgumentPack(['-m', '--multiplier'], 'default': None, 'help': "Core mask - only cores responding to the bit mask will be active"}), + SERVICE_OFF: ArgumentPack(['--off'], + {'action': 'store_false', + 'dest': 'enabled', + 'default': True, + 'help': 'Deactivates services on port(s)'}), + # 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}), @@ -515,6 +660,8 @@ class CCmdArgParser(argparse.ArgumentParser): if not self.has_ports_cfg(opts): return opts + opts.ports = listify(opts.ports) + # if all ports are marked or if (getattr(opts, "all_ports", None) == True) or (getattr(opts, "ports", None) == []): if default_ports is None: @@ -522,10 +669,17 @@ class CCmdArgParser(argparse.ArgumentParser): else: opts.ports = default_ports + opts.ports = list_remove_dup(opts.ports) + # so maybe we have ports configured invalid_ports = list_difference(opts.ports, self.stateless_client.get_all_ports()) if invalid_ports: - msg = "{0}: port(s) {1} are not valid port IDs".format(self.cmd_name, invalid_ports) + + if len(invalid_ports) > 1: + msg = "{0}: port(s) {1} are not valid port IDs".format(self.cmd_name, invalid_ports) + else: + msg = "{0}: port {1} is not a valid port ID".format(self.cmd_name, invalid_ports[0]) + self.stateless_client.logger.log(format_text(msg, 'bold')) return RC_ERR(msg) 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 bfb96950..63b05bf4 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 @@ -27,6 +27,9 @@ class TextCodesStripper: def strip (s): return re.sub(TextCodesStripper.pattern, '', s) +def clear_formatting(s): + return TextCodesStripper.strip(s) + def format_num (size, suffix = "", compact = True, opts = None): if opts is None: opts = () @@ -128,11 +131,13 @@ def yellow(text): def underline(text): return text_attribute(text, 'underline') - +# apply attribute on each non-empty line def text_attribute(text, attribute): - return "{start}{txt}{stop}".format(start=TEXT_CODES[attribute]['start'], - txt=text, - stop=TEXT_CODES[attribute]['end']) + 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')]) FUNC_DICT = {'blue': blue, diff --git a/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/zipmsg.py b/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/zipmsg.py index 397ada16..a2a47927 100644 --- a/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/zipmsg.py +++ b/scripts/automation/trex_control_plane/stl/trex_stl_lib/utils/zipmsg.py @@ -6,7 +6,7 @@ class ZippedMsg: MSG_COMPRESS_THRESHOLD = 256 MSG_COMPRESS_HEADER_MAGIC = 0xABE85CEA - def check_threshold (self, msg): + def check_threshold(self, msg): return len(msg) >= self.MSG_COMPRESS_THRESHOLD def compress (self, msg): @@ -16,7 +16,7 @@ class ZippedMsg: return new_msg - def decompress (self, msg): + def decompress(self, msg): if len(msg) < 8: return None @@ -30,3 +30,15 @@ class ZippedMsg: return x + + def is_compressed(self, msg): + if len(msg) < 8: + return False + + t = struct.unpack(">II", msg[:8]) + if (t[0] != self.MSG_COMPRESS_HEADER_MAGIC): + return False + + return True + + |