From 6e1919c3aebabc0977a8ab40b5c60cbd0e7114d0 Mon Sep 17 00:00:00 2001 From: imarom Date: Sun, 13 Nov 2016 17:17:36 +0200 Subject: RX features - pre-resolve stage Signed-off-by: imarom --- .../stl/trex_stl_lib/trex_stl_client.py | 132 ++++++++--------- .../stl/trex_stl_lib/trex_stl_port.py | 156 +++++++++++++++++---- .../stl/trex_stl_lib/trex_stl_stats.py | 8 +- .../stl/trex_stl_lib/utils/common.py | 5 + .../stl/trex_stl_lib/utils/parsing_opts.py | 33 ++++- 5 files changed, 234 insertions(+), 100 deletions(-) (limited to 'scripts/automation') 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 d4d09cd7..2ee5225c 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,7 +12,7 @@ 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, is_valid_ipv4 +from .utils.common import list_intersect, list_difference, is_sub_list, PassiveTimer, is_valid_ipv4, is_valid_mac from .utils.text_opts import * from functools import wraps @@ -326,24 +326,22 @@ class EventsHandler(object): 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_formatted_info(sync = False) - self.__async_event_port_attr_changed(port_id, data['attr']) - - new_info = self.client.ports[port_id].get_formatted_info(sync = False) + + 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.get(key, 'N/A') - if old_val != new_val: - ev += '\n {key}: {old} -> {new}'.format( + 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): @@ -399,7 +397,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): @@ -812,7 +810,7 @@ class STLClient(object): 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)) + rc.add(self.ports[port_id].set_attr(**port_attr_dict)) return rc @@ -1772,10 +1770,19 @@ class STLClient(object): raise STLError(rc) def test (self): + #rc = self.ports[0].resolve() + #if not rc: + # raise STLError(rc) + #return - self.reset(ports = [0, 1]) + self.reset(ports = [0]) - self.set_port_attr(ports = [0, 1], ipv4 = ['5.5.5.5', '6.6.6.6']) + attr = self.ports[0].get_ts_attr() + src_ipv4 = attr['src_ipv4'] + src_mac = attr['src_mac'] + dest = attr['dest'] + print(src_ipv4, src_mac, dest) + #self.set_port_attr(ports = [0, 1], ipv4 = ['5.5.5.5', '6.6.6.6']) return self.set_rx_queue(ports = [0], size = 1000, rxf = 'all') @@ -2727,7 +2734,7 @@ class STLClient(object): flow_ctrl = None, rxf = None, ipv4 = None, - default_gateway = None, + dest = None, ): """ Set port attributes @@ -2740,8 +2747,8 @@ class STLClient(object): rxf - 'hw' for hardware rules matching packets only or 'all' all packets ipv4 - configure IPv4 address for port(s). for multiple ports should be a list of IPv4 addresses in the same length of the ports array - default_gateway - configure default gateway for port(s). for multiple ports should be a list - in the same length of the ports array + dest - configure destination address for port(s) in either IPv4 or MAC format. + for multiple ports should be a list in the same length of the ports array :raises: + :exe:'STLError' @@ -2759,54 +2766,39 @@ class STLClient(object): # common attributes for all ports cmn_attr_dict = {} - if promiscuous is not None: - cmn_attr_dict['promiscuous'] = {'enabled': promiscuous} - - if link_up is not None: - cmn_attr_dict['link_status'] = {'up': link_up} - - if led_on is not None: - cmn_attr_dict['led_status'] = {'on': led_on} - - if flow_ctrl is not None: - cmn_attr_dict['flow_ctrl_mode'] = {'mode': flow_ctrl} - - if rxf is not None: - cmn_attr_dict['rx_filter_mode'] = {'mode': rxf} + 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 + cmn_attr_dict['rx_filter_mode'] = rxf + # each port starts with a set of the common attributes attr_dict = [dict(cmn_attr_dict) for _ in ports] + + # default value for IPv4 / dest is none for all ports + if ipv4 is None: + ipv4 = [None] * len(ports) + if dest is None: + dest = [None] * len(ports) - # IPv4 - if ipv4 is not None: - ipv4_list = listify(ipv4) - - if len(ipv4_list) != len(ports): - raise STLError("'ipv4' must be a list in the same length of ports - 'ports': {0}, 'ip': {1}".format(ports, ipv4_list)) + ipv4 = listify(ipv4) + if len(ipv4) != len(ports): + raise STLError("'ipv4' must be a list in the same length of ports - 'ports': {0}, 'ip': {1}".format(ports, ipv4)) - for ipv4, port_attr in zip(ipv4_list, attr_dict): - if not is_valid_ipv4(ipv4): - raise STLError("invalid IPv4 address provided: '{0}'".format(ipv4)) - port_attr['ipv4'] = {'addr': ipv4} - + dest = listify(dest) + if len(dest) != len(ports): + raise STLError("'dest' must be a list in the same length of ports - 'ports': {0}, 'dest': {1}".format(ports, dest)) - # default gateway - if default_gateway is not None: - dg_list = listfy(default_gateway) + # update each port attribute with ipv4 + for addr, port_attr in zip(ipv4, attr_dict): + port_attr['ipv4'] = addr + + # update each port attribute with dest + for addr, port_attr in zip(dest, attr_dict): + port_attr['dest'] = addr - if len(dg_list) != len(ports): - raise STLError("'default_gateway' must be a list in the same length of ports - 'ports': {0}, 'default_gateway': {1}".format(ports, dg_list)) - - for dg, port_attr in zip(dg_list, attr_dict): - if not is_valid_ipv4(dg): - raise STLError("invalid IPv4 address provided: '{0}'".format(ipv4)) - port_attr['default_gateway'] = {'addr': dg} - - - # no attributes to set - if not any(attr_dict): - return - + 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) @@ -2815,7 +2807,6 @@ class STLClient(object): raise STLError(rc) - @__api_check(True) def set_rx_sniffer (self, ports = None, base_filename = 'rx_capture', limit = 1000, rxf = None): """ @@ -3515,6 +3506,8 @@ class STLClient(object): parsing_opts.FLOW_CTRL, parsing_opts.SUPPORTED, parsing_opts.RX_FILTER_MODE, + parsing_opts.IPV4, + parsing_opts.DEST ) opts = parser.parse_args(line.split(), default_ports = self.get_acquired_ports(), verify_acquired = True) @@ -3527,12 +3520,12 @@ class STLClient(object): opts.flow_ctrl = parsing_opts.FLOW_CTRL_DICT.get(opts.flow_ctrl) # if no attributes - fall back to printing the status - if not list(filter(lambda x:x is not None, [opts.prom, opts.link, opts.led, opts.flow_ctrl, opts.supp, opts.rx_filter_mode])): + if not list(filter(lambda x:x is not None, [opts.prom, opts.link, opts.led, opts.flow_ctrl, opts.supp, opts.rx_filter_mode, opts.ipv4, opts.dest])): 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') @@ -3541,7 +3534,14 @@ 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, opts.rx_filter_mode) + return self.set_port_attr(opts.ports, + opts.prom, + opts.link, + opts.led, + opts.flow_ctrl, + opts.rx_filter_mode, + opts.ipv4, + opts.dest) 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 418ee5a6..3fd00391 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 @@ -10,6 +10,7 @@ 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 +51,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 @@ -63,7 +66,7 @@ class Port(object): self.profile = None self.session_id = session_id self.status = {} - self.attr = {} + self.__attr = {} self.port_stats = trex_stl_stats.CPortStats(self) @@ -74,6 +77,8 @@ class Port(object): self.owner = '' self.last_factor_type = None + self.attr_lock = threading.Lock() + # decorator to verify port is up def up(func): def func_wrapper(*args, **kwargs): @@ -88,7 +93,7 @@ class Port(object): # owned def owned(func): - def func_wrapper(*args): + def func_wrapper(*args, **kwargs): port = args[0] if not port.is_up(): @@ -97,7 +102,7 @@ class Port(object): 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 @@ -129,10 +134,10 @@ class Port(object): return RC_OK(data) def get_speed_bps (self): - return (self.attr['speed'] * 1000 * 1000 * 1000) + return (self.__attr['speed'] * 1000 * 1000 * 1000) def get_formatted_speed (self): - return "%g Gb/s" % (self.attr['speed'] / 1000) + return "%g Gb/s" % (self.__attr['speed'] / 1000) def is_acquired(self): return (self.handler != None) @@ -253,7 +258,8 @@ class Port(object): self.status = rc.data() - self.attr = rc.data()['attr'] + # replace the attributes in a thread safe manner + self.set_ts_attr(rc.data()['attr']) return self.ok() @@ -645,19 +651,44 @@ 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_up') is not None: + json_attr['link_status'] = {'up': kwargs.get('link_up')} + + if kwargs.get('led_on') is not None: + json_attr['led_status'] = {'on': kwargs.get('led_on')} + + if kwargs.get('flow_ctrl_mode') is not None: + json_attr['flow_ctrl_mode'] = {'on': 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')} + + if kwargs.get('ipv4') is not None: + json_attr['ipv4'] = {'addr': kwargs.get('ipv4')} + + if kwargs.get('dest') is not None: + json_attr['dest'] = {'addr': kwargs.get('dest')} + 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()) - return self.ok() - + # update the dictionary from the server explicitly + return self.sync() + @writeable def push_remote (self, pcap_filename, ipg_usec, speedup, count, duration, is_dual, slave_handler): @@ -730,7 +761,8 @@ class Port(object): if sync: self.sync() - attr = self.attr + # get a copy of the current attribute set (safe against manipulation) + attr = self.get_ts_attr() info = dict(self.info) @@ -781,15 +813,35 @@ class Port(object): if 'rx_filter_mode' in attr: - info['rx_filter_mode'] = 'Hardware Match' if attr['rx_filter_mode'] == 'hw' else 'Fetch All' + info['rx_filter_mode'] = 'hardware match' if attr['rx_filter_mode'] == 'hw' else 'fetch all' else: info['rx_filter_mode'] = 'N/A' + # src MAC and IPv4 + info['src_mac'] = attr.get('src_mac', 'N/A') + + info['src_ipv4'] = attr.get('src_ipv4', 'N/A') + if info['src_ipv4'] == 'none': + info['src_ipv4'] = 'Not Configured' + + # dest + dest = attr.get('dest', {}) + info['dest'] = dest.get('addr', 'N/A') + + if dest['type'] == 'mac': + info['arp'] = '-' + else: + info['arp'] = dest.get('arp', 'N/A') + + + if info['dest'] == 'none': + info['dest'] = 'Not Configured' + + + if info['arp'] == 'none': + info['arp'] = 'unresolved' + - info['mac_addr'] = attr.get('mac_addr', 'N/A') - info['ipv4'] = attr.get('ipv4', 'N/A') - info['default_gateway'] = attr.get('default_gateway', 'N/A') - info['next_hop_mac'] = attr.get('next_hop_mac', 'N/A') # RX info rx_info = self.status['rx_info'] @@ -816,6 +868,17 @@ class Port(object): def get_port_state_name(self): return self.STATES_MAP.get(self.state, "Unknown") + def get_src_ipv4 (self): + src_ipv4 = self.__attr['src_ipv4'] + if src_ipv4 == 'none': + src_ipv4 = None + + return src_ipv4 + + def get_src_mac (self): + return self.__attr['src_mac'] + + ################# stats handler ###################### def generate_port_stats(self): return self.port_stats.generate_stats() @@ -824,14 +887,14 @@ class Port(object): info = self.get_formatted_info() - return {"driver": info['driver'], - "description": info.get('description', 'N/A')[:18], - "MAC addr": info['mac_addr'], - "Next hop MAC": info['next_hop_mac'], - "IPv4": info['ipv4'], - "Default gateway": info['default_gateway'], - "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": info['arp'], + "PCI Address": info['pci_addr'], + "NUMA Node": info['numa'], "--": "", "---": "", "----": "", @@ -881,8 +944,20 @@ 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 @@ -890,8 +965,33 @@ class Port(object): self.state = self.STATE_STREAMS self.last_factor_type = None - def async_event_port_attr_changed (self, attr): - 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): 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 55620689..f0293016 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,11 +670,11 @@ class CTRexInfoGenerator(object): ("promiscuous", []), ("flow ctrl", []), ("--", []), - ("MAC addr", []), - ("Next hop MAC", []), + ("src IPv4", []), + ("src MAC", []), ("---", []), - ("IPv4", []), - ("Default gateway", []), + ("Destination", []), + ("ARP Resolution", []), ("----", []), ("PCI Address", []), ("NUMA Node", []), 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 f0da9a08..d4ac973d 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 @@ -4,6 +4,7 @@ import string import random import time import socket +import re try: import pwd @@ -93,3 +94,7 @@ def is_valid_ipv4 (addr): return True except socket.error: 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())) + 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 b93a797d..80260d4a 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 from .text_opts import format_text from ..trex_stl_types import * from .constants import ON_OFF_DICT, UP_DOWN_DICT, FLOW_CTRL_DICT @@ -45,12 +45,14 @@ FLOW_CTRL = 28 SUPPORTED = 29 RX_FILTER_MODE = 30 - OUTPUT_FILENAME = 31 ALL_FILES = 32 LIMIT = 33 PORT_RESTART = 34 +IPV4 = 35 +DEST = 36 + GLOBAL_STATS = 50 PORT_STATS = 51 PORT_STATUS = 52 @@ -224,8 +226,20 @@ 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_dest_addr (addr): + if not (is_valid_ipv4(addr) or is_valid_mac(addr)): + raise argparse.ArgumentTypeError("not a valid IPv4 or MAC address: '{0}'".format(addr)) + + return addr + def decode_tunables (tunable_str): tunables = {} @@ -316,6 +330,21 @@ OPTIONS_DB = {MULTIPLIER: ArgumentPack(['-m', '--multiplier'], 'choices': ['hw', 'all']}), + IPV4: ArgumentPack(['--ipv4'], + {'help': 'IPv4 address(s) for the port(s)', + 'dest': 'ipv4', + 'nargs': '+', + 'default': None, + 'type': check_ipv4_addr}), + + DEST: ArgumentPack(['--dest'], + {'help': 'Destination address(s) for the port(s) in either IPv4 or MAC format', + 'dest': 'dest', + 'nargs': '+', + 'default': None, + 'type': check_dest_addr}), + + OUTPUT_FILENAME: ArgumentPack(['-o', '--output'], {'help': 'Output PCAP filename', 'dest': 'output_filename', -- cgit 1.2.3-korg