summaryrefslogtreecommitdiffstats
path: root/scripts/automation
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/automation')
-rw-r--r--scripts/automation/trex_control_plane/client/trex_async_client.py284
-rwxr-xr-xscripts/automation/trex_control_plane/client/trex_client.py30
-rwxr-xr-x[-rw-r--r--]scripts/automation/trex_control_plane/client/trex_hltapi.py297
-rw-r--r--scripts/automation/trex_control_plane/client/trex_port.py408
-rwxr-xr-xscripts/automation/trex_control_plane/client/trex_stateless_client.py1500
-rwxr-xr-xscripts/automation/trex_control_plane/client_utils/external_packages.py3
-rwxr-xr-xscripts/automation/trex_control_plane/client_utils/general_utils.py18
-rwxr-xr-xscripts/automation/trex_control_plane/client_utils/jsonrpc_client.py351
-rwxr-xr-xscripts/automation/trex_control_plane/client_utils/parsing_opts.py304
-rw-r--r--scripts/automation/trex_control_plane/client_utils/text_tables.py34
-rwxr-xr-xscripts/automation/trex_control_plane/common/text_opts.py144
-rwxr-xr-xscripts/automation/trex_control_plane/common/trex_stats.py308
-rwxr-xr-xscripts/automation/trex_control_plane/common/trex_streams.py91
-rw-r--r--scripts/automation/trex_control_plane/common/trex_types.py66
-rwxr-xr-xscripts/automation/trex_control_plane/console/trex_console.py846
-rw-r--r--scripts/automation/trex_control_plane/console/trex_status.py442
-rw-r--r--scripts/automation/trex_control_plane/console/trex_tui.py395
-rwxr-xr-xscripts/automation/trex_control_plane/examples/stateless_example.py30
-rwxr-xr-xscripts/automation/trex_control_plane/server/extended_daemon_runner.py8
-rwxr-xr-xscripts/automation/trex_control_plane/server/trex_daemon_server.py10
-rwxr-xr-xscripts/automation/trex_control_plane/server/trex_server.py9
21 files changed, 4185 insertions, 1393 deletions
diff --git a/scripts/automation/trex_control_plane/client/trex_async_client.py b/scripts/automation/trex_control_plane/client/trex_async_client.py
new file mode 100644
index 00000000..8fdf7c9b
--- /dev/null
+++ b/scripts/automation/trex_control_plane/client/trex_async_client.py
@@ -0,0 +1,284 @@
+#!/router/bin/python
+
+try:
+ # support import for Python 2
+ import outer_packages
+except ImportError:
+ # support import for Python 3
+ import client.outer_packages
+from client_utils.jsonrpc_client import JsonRpcClient, BatchMessage
+
+from common.text_opts import *
+
+import json
+import threading
+import time
+import datetime
+import zmq
+import re
+
+from common.trex_stats import *
+from common.trex_streams import *
+
+# basic async stats class
+class CTRexAsyncStats(object):
+ def __init__ (self):
+ self.ref_point = None
+ self.current = {}
+ self.last_update_ts = datetime.datetime.now()
+
+ @staticmethod
+ def format_num (size, suffix = ""):
+
+ for unit in ['','K','M','G','T','P']:
+ if abs(size) < 1000.0:
+ return "%3.2f %s%s" % (size, unit, suffix)
+ size /= 1000.0
+
+ return "NaN"
+
+ def update (self, snapshot):
+
+ #update
+ self.last_update_ts = datetime.datetime.now()
+
+ self.current = snapshot
+
+ if self.ref_point == None:
+ self.ref_point = self.current
+
+ def clear(self):
+ self.ref_point = self.current
+
+
+ def get(self, field, format=False, suffix=""):
+
+ if not field in self.current:
+ return "N/A"
+
+ if not format:
+ return self.current[field]
+ else:
+ return self.format_num(self.current[field], suffix)
+
+ def get_rel (self, field, format=False, suffix=""):
+ if not field in self.current:
+ return "N/A"
+
+ if not format:
+ return (self.current[field] - self.ref_point[field])
+ else:
+ return self.format_num(self.current[field] - self.ref_point[field], suffix)
+
+
+ # return true if new data has arrived in the past 2 seconds
+ def is_online (self):
+ delta_ms = (datetime.datetime.now() - self.last_update_ts).total_seconds() * 1000
+ return (delta_ms < 2000)
+
+# describes the general stats provided by TRex
+class CTRexAsyncStatsGeneral(CTRexAsyncStats):
+ def __init__ (self):
+ super(CTRexAsyncStatsGeneral, self).__init__()
+
+
+# per port stats
+class CTRexAsyncStatsPort(CTRexAsyncStats):
+ def __init__ (self):
+ super(CTRexAsyncStatsPort, self).__init__()
+
+ def get_stream_stats (self, stream_id):
+ return None
+
+# stats manager
+class CTRexAsyncStatsManager():
+ def __init__ (self):
+
+ self.general_stats = CTRexAsyncStatsGeneral()
+ self.port_stats = {}
+
+
+ def get_general_stats(self):
+ return self.general_stats
+
+ def get_port_stats (self, port_id):
+
+ if not str(port_id) in self.port_stats:
+ return None
+
+ return self.port_stats[str(port_id)]
+
+
+ def update(self, data):
+ self.__handle_snapshot(data)
+
+ def __handle_snapshot(self, snapshot):
+
+ general_stats = {}
+ port_stats = {}
+
+ # filter the values per port and general
+ for key, value in snapshot.iteritems():
+
+ # match a pattern of ports
+ m = re.search('(.*)\-([0-8])', key)
+ if m:
+
+ port_id = m.group(2)
+ field_name = m.group(1)
+
+ if not port_id in port_stats:
+ port_stats[port_id] = {}
+
+ port_stats[port_id][field_name] = value
+
+ else:
+ # no port match - general stats
+ general_stats[key] = value
+
+ # update the general object with the snapshot
+ self.general_stats.update(general_stats)
+
+ # update all ports
+ for port_id, data in port_stats.iteritems():
+
+ if not port_id in self.port_stats:
+ self.port_stats[port_id] = CTRexAsyncStatsPort()
+
+ self.port_stats[port_id].update(data)
+
+
+
+
+
+class CTRexAsyncClient():
+ def __init__ (self, server, port, stateless_client):
+
+ self.port = port
+ self.server = server
+ self.stateless_client = stateless_client
+
+ self.raw_snapshot = {}
+
+ self.stats = CTRexAsyncStatsManager()
+
+ self.connected = False
+
+ # connects the async channel
+ def connect (self):
+
+ if self.connected:
+ self.disconnect()
+
+ self.tr = "tcp://{0}:{1}".format(self.server, self.port)
+ print "\nConnecting To ZMQ Publisher On {0}".format(self.tr)
+
+ # Socket to talk to server
+ self.context = zmq.Context()
+ self.socket = self.context.socket(zmq.SUB)
+
+
+ # before running the thread - mark as active
+ self.active = True
+ self.alive = False
+ self.t = threading.Thread(target = self._run)
+
+ # kill this thread on exit and don't add it to the join list
+ self.t.setDaemon(True)
+ self.t.start()
+
+ self.connected = True
+
+
+ # wait for data streaming from the server
+ timeout = time.time() + 5
+ while not self.alive:
+ time.sleep(0.01)
+ if time.time() > timeout:
+ self.disconnect()
+ return False, "*** [subscriber] - no data flow from server at : " + self.tr
+
+ return True, ""
+
+
+ # disconnect
+ def disconnect (self):
+ if not self.connected:
+ return
+
+ # signal that the context was destroyed (exit the thread loop)
+ self.context.term()
+
+ # mark for join and join
+ self.active = False
+ self.t.join()
+
+ # done
+ self.connected = False
+
+ # thread function
+ def _run (self):
+
+ # no data yet...
+ self.alive = False
+
+ # socket must be created on the same thread
+ self.socket.connect(self.tr)
+ self.socket.setsockopt(zmq.SUBSCRIBE, '')
+ self.socket.setsockopt(zmq.RCVTIMEO, 5000)
+
+ while self.active:
+ try:
+
+ line = self.socket.recv_string()
+
+ if not self.alive:
+ self.stateless_client.on_async_alive()
+ self.alive = True
+
+ # got a timeout - mark as not alive and retry
+ except zmq.Again:
+
+ if self.alive:
+ self.stateless_client.on_async_dead()
+ self.alive = False
+
+ continue
+
+ except zmq.ContextTerminated:
+ # outside thread signaled us to exit
+ self.alive = False
+ break
+
+ msg = json.loads(line)
+
+ name = msg['name']
+ data = msg['data']
+ type = msg['type']
+ self.raw_snapshot[name] = data
+
+ self.__dispatch(name, type, data)
+
+
+ # closing of socket must be from the same thread
+ self.socket.close(linger = 0)
+
+
+ def get_stats (self):
+ return self.stats
+
+ def get_raw_snapshot (self):
+ return self.raw_snapshot
+
+ # dispatch the message to the right place
+ def __dispatch (self, name, type, data):
+ # stats
+ if name == "trex-global":
+ self.stateless_client.handle_async_stats_update(data)
+ # events
+ elif name == "trex-event":
+ self.stateless_client.handle_async_event(type, data)
+ else:
+ pass
+
+
diff --git a/scripts/automation/trex_control_plane/client/trex_client.py b/scripts/automation/trex_control_plane/client/trex_client.py
index 160abdec..77b11c37 100755
--- a/scripts/automation/trex_control_plane/client/trex_client.py
+++ b/scripts/automation/trex_control_plane/client/trex_client.py
@@ -131,7 +131,9 @@ class CTRexClient(object):
raise ValueError('d parameter must be integer, specifying how long TRex run, and must be larger than 30 secs.')
trex_cmd_options.update( {'f' : f, 'd' : d} )
-
+ if not trex_cmd_options.get('l'):
+ self.result_obj.latency_checked = False
+
self.result_obj.clear_results()
try:
issue_time = time.time()
@@ -767,6 +769,7 @@ class CTRexResult(object):
"""
self._history = deque(maxlen = max_history_size)
self.clear_results()
+ self.latency_checked = True
def __repr__(self):
return ("Is valid history? {arg}\n".format( arg = self.is_valid_hist() ) +
@@ -1032,18 +1035,19 @@ class CTRexResult(object):
self._done_warmup = True
# handle latency data
- latency_pre = "trex-latency"
- self._max_latency = self.get_last_value("{latency}.data".format(latency = latency_pre), ".*max-")#None # TBC
- # support old typo
- if self._max_latency is None:
- latency_pre = "trex-latecny"
- self._max_latency = self.get_last_value("{latency}.data".format(latency = latency_pre), ".*max-")
-
- self._avg_latency = self.get_last_value("{latency}.data".format(latency = latency_pre), "avg-")#None # TBC
- self._avg_latency = CTRexResult.__avg_all_and_rename_keys(self._avg_latency)
-
- avg_win_latency_list = self.get_value_list("{latency}.data".format(latency = latency_pre), "avg-")
- self._avg_window_latency = CTRexResult.__calc_latency_win_stats(avg_win_latency_list)
+ if self.latency_checked:
+ latency_pre = "trex-latency"
+ self._max_latency = self.get_last_value("{latency}.data".format(latency = latency_pre), ".*max-")#None # TBC
+ # support old typo
+ if self._max_latency is None:
+ latency_pre = "trex-latecny"
+ self._max_latency = self.get_last_value("{latency}.data".format(latency = latency_pre), ".*max-")
+
+ self._avg_latency = self.get_last_value("{latency}.data".format(latency = latency_pre), "avg-")#None # TBC
+ self._avg_latency = CTRexResult.__avg_all_and_rename_keys(self._avg_latency)
+
+ avg_win_latency_list = self.get_value_list("{latency}.data".format(latency = latency_pre), "avg-")
+ self._avg_window_latency = CTRexResult.__calc_latency_win_stats(avg_win_latency_list)
tx_pkts = CTRexResult.__get_value_by_path(latest_dump, "trex-global.data.m_total_tx_pkts")
rx_pkts = CTRexResult.__get_value_by_path(latest_dump, "trex-global.data.m_total_rx_pkts")
diff --git a/scripts/automation/trex_control_plane/client/trex_hltapi.py b/scripts/automation/trex_control_plane/client/trex_hltapi.py
index 46c283f8..92768ca4 100644..100755
--- a/scripts/automation/trex_control_plane/client/trex_hltapi.py
+++ b/scripts/automation/trex_control_plane/client/trex_hltapi.py
@@ -2,16 +2,305 @@
import trex_root_path
from client_utils.packet_builder import CTRexPktBuilder
+from trex_stateless_client import CTRexStatelessClient
+from common.trex_streams import *
+from client_utils.general_utils import id_count_gen
+import dpkt
-print "done!"
class CTRexHltApi(object):
def __init__(self):
- pass
+ self.trex_client = None
+ self.connected = False
+ # self._stream_db = CStreamList()
+ self._port_data = {}
+
+ # ----- session functions ----- #
+
+ def connect(self, device, port_list, username, port=5050, reset=False, break_locks=False):
+ ret_dict = {"status": 0}
+ self.trex_client = CTRexStatelessClient(username, device, port)
+ res_ok, msg = self.trex_client.connect()
+ if not res_ok:
+ self.trex_client = None
+ ret_dict.update({"log": msg})
+ return ret_dict
+ # arrived here, connection successfully created with server
+ # next, try acquiring ports of TRex
+ port_list = self.parse_port_list(port_list)
+ response = self.trex_client.acquire(port_list, force=break_locks)
+ res_ok, log = CTRexHltApi.process_response(port_list, response)
+ if not res_ok:
+ self.trex_client.disconnect()
+ self.trex_client = None
+ ret_dict.update({"log": log})
+ # TODO: should revert taken ports?!
+ return ret_dict
+ # arrived here, all desired ports were successfully acquired
+ print log
+ if reset:
+ # remove all port traffic configuration from TRex
+ response = self.trex_client.remove_all_streams(port_list)
+ res_ok, log = CTRexHltApi.process_response(port_list, response)
+ if not res_ok:
+ self.trex_client.disconnect()
+ self.trex_client = None
+ ret_dict.update({"log": log})
+ return ret_dict
+ print log
+ port_handle = {device: {port: port # since only supporting single TRex at the moment, 1:1 map
+ for port in port_list}
+ }
+ ret_dict.update({"status": 1,
+ "log": None,
+ "port_handle": port_handle,
+ "offline": 0}) # ports are online
+ self.connected = True
+ self._port_data_init(port_list)
+ return ret_dict
+
+ def cleanup_session(self, port_list, maintain_lock=False):
+ ret_dict = {"status": 0}
+ if not maintain_lock:
+ # release taken ports
+ if port_list == "all":
+ port_list = self.trex_client.get_acquired_ports()
+ else:
+ port_list = self.parse_port_list(port_list)
+ response = self.trex_client.release(port_list)
+ res_ok, log = CTRexHltApi.process_response(port_list, response)
+ print log
+ if not res_ok:
+ ret_dict.update({"log": log})
+ return ret_dict
+ ret_dict.update({"status": 1,
+ "log": None})
+ self.trex_client.disconnect()
+ self.trex_client = None
+ self.connected = False
+ return ret_dict
+
+ def interface_config(self, port_handle, mode="config"):
+ ALLOWED_MODES = ["config", "modify", "destroy"]
+ if mode not in ALLOWED_MODES:
+ raise ValueError("mode must be one of the following values: {modes}".format(modes=ALLOWED_MODES))
+ # pass this function for now...
+ return {"status": 1, "log": None}
+
+ # ----- traffic functions ----- #
+ def traffic_config(self, mode, port_handle,
+ l2_encap="ethernet_ii", mac_src="00:00:01:00:00:01", mac_dst="00:00:00:00:00:00",
+ l3_protocol="ipv4", ip_src_addr="0.0.0.0", ip_dst_addr="192.0.0.1", l3_length=110,
+ transmit_mode="continuous", rate_pps=100,
+ **kwargs):
+ ALLOWED_MODES = ["create", "modify", "remove", "enable", "disable", "reset"]
+ if mode not in ALLOWED_MODES:
+ raise ValueError("mode must be one of the following values: {modes}".format(modes=ALLOWED_MODES))
+ if mode == "create":
+ # create a new stream with desired attributes, starting by creating packet
+ try:
+ packet = CTRexHltApi.generate_stream(l2_encap, mac_src, mac_dst,
+ l3_protocol, ip_src_addr, ip_dst_addr, l3_length)
+ # set transmission attributes
+ tx_mode = CTxMode(transmit_mode, rate_pps, **kwargs)
+ # set rx_stats
+ rx_stats = CRxStats() # defaults with disabled
+ # join the generated data into stream
+ stream_obj = CStream()
+ stream_obj_params = {"enabled": True,
+ "self_start": True,
+ "next_stream_id": -1,
+ "isg": 0.0,
+ "mode": tx_mode,
+ "rx_stats": rx_stats,
+ "packet": packet} # vm is excluded from this list since CTRexPktBuilder obj is passed
+ stream_obj.load_data(**stream_obj_params)
+ except Exception as e:
+ # some exception happened during the stream creation
+ return {"status": 0, "log": str(e)}
+ # try adding the stream, until free stream_id is found
+ port_data = self._port_data.get(port_handle)
+ id_candidate = None
+ # TODO: change this to better implementation
+ while True:
+ id_candidate = port_data["stream_id_gen"].next()
+ response = self.trex_client.add_stream(stream_id=id_candidate,
+ stream_obj=stream_obj,
+ port_id=port_handle)
+ res_ok, log = CTRexHltApi.process_response(port_handle, response)
+ if res_ok:
+ # found non-taken stream_id on server
+ # save it for modifying needs
+ port_data["streams"].update({id_candidate: stream_obj})
+ break
+ else:
+ # proceed to another iteration to use another id
+ continue
+ return {"status": 1,
+ "stream_id": id_candidate,
+ "log": None}
+ else:
+ raise NotImplementedError("mode '{0}' is not supported yet on TRex".format(mode))
+
+ def traffic_control(self, action, port_handle):
+ ALLOWED_ACTIONS = ["clear_stats", "run", "stop", "sync_run"]
+ if action not in ALLOWED_ACTIONS:
+ raise ValueError("action must be one of the following values: {actions}".format(actions=ALLOWED_ACTIONS))
+ # ret_dict = {"status": 0, "stopped": 1}
+ port_list = self.parse_port_list(port_handle)
+ if action == "run":
+ response = self.trex_client.start_traffic(port_id=port_list)
+ res_ok, log = CTRexHltApi.process_response(port_list, response)
+ if res_ok:
+ return {"status": 1,
+ "stopped": 0,
+ "log": None}
+ elif action == "stop":
+ response = self.trex_client.stop_traffic(port_id=port_list)
+ res_ok, log = CTRexHltApi.process_response(port_list, response)
+ if res_ok:
+ return {"status": 1,
+ "stopped": 1,
+ "log": None}
+ else:
+ raise NotImplementedError("action '{0}' is not supported yet on TRex".format(action))
+
+ # if we arrived here, this means that operation FAILED!
+ return {"status": 0,
+ "log": log}
+
+
+ def traffic_stats(self, port_handle, mode):
+ ALLOWED_MODES = ["aggregate", "streams", "all"]
+ if mode not in ALLOWED_MODES:
+ raise ValueError("mode must be one of the following values: {modes}".format(modes=ALLOWED_MODES))
+ # pass this function for now...
+ if mode == "aggregate":
+ # create a new stream with desired attributes, starting by creating packet
+ try:
+ packet = CTRexHltApi.generate_stream(l2_encap, mac_src, mac_dst,
+ l3_protocol, ip_src_addr, ip_dst_addr, l3_length)
+ # set transmission attributes
+ tx_mode = CTxMode(transmit_mode, rate_pps, **kwargs)
+ # set rx_stats
+ rx_stats = CRxStats() # defaults with disabled
+ # join the generated data into stream
+ stream_obj = CStream()
+ stream_obj_params = {"enabled": True,
+ "self_start": True,
+ "next_stream_id": -1,
+ "isg": 0.0,
+ "mode": tx_mode,
+ "rx_stats": rx_stats,
+ "packet": packet} # vm is excluded from this list since CTRexPktBuilder obj is passed
+ stream_obj.load_data(**stream_obj_params)
+ except Exception as e:
+ # some exception happened during the stream creation
+ return {"status": 0, "log": str(e)}
+ # try adding the stream, until free stream_id is found
+ port_data = self._port_data.get(port_handle)
+ id_candidate = None
+ # TODO: change this to better implementation
+ while True:
+ id_candidate = port_data["stream_id_gen"].next()
+ response = self.trex_client.add_stream(stream_id=id_candidate,
+ stream_obj=stream_obj,
+ port_id=port_handle)
+ res_ok, log = CTRexHltApi.process_response(port_handle, response)
+ if res_ok:
+ # found non-taken stream_id on server
+ # save it for modifying needs
+ port_data["streams"].update({id_candidate: stream_obj})
+ break
+ else:
+ # proceed to another iteration to use another id
+ continue
+ return {"status": 1,
+ "stream_id": id_candidate,
+ "log": None}
+ else:
+ raise NotImplementedError("mode '{0}' is not supported yet on TRex".format(mode))
+
+ def get_aggregate_port_stats(self, port_handle):
+ return self.traffic_stats(port_handle, mode="aggregate")
+
+ def get_stream_stats(self, port_handle):
+ return self.traffic_stats(port_handle, mode="streams")
+
+ # ----- internal functions ----- #
+ def _port_data_init(self, port_list):
+ for port in port_list:
+ self._port_data[port] = {"stream_id_gen": id_count_gen(),
+ "streams": {}}
+
+ @staticmethod
+ def process_response(port_list, response):
+ if isinstance(port_list, list):
+ res_ok, response = response
+ log = CTRexHltApi.join_batch_response(response)
+ else:
+ res_ok = response.success
+ log = str(response)
+ return res_ok, log
+
+ @staticmethod
+ def parse_port_list(port_list):
+ if isinstance(port_list, str):
+ return [int(port)
+ for port in port_list.split()]
+ elif isinstance(port_list, list):
+ return [int(port)
+ for port in port_list]
+ else:
+ return port_list
+
+ @staticmethod
+ def join_batch_response(responses):
+ return "\n".join([str(response)
+ for response in responses])
+
+ @staticmethod
+ def generate_stream(l2_encap, mac_src, mac_dst,
+ l3_protocol, ip_src_addr, ip_dst_addr, l3_length):
+ ALLOWED_L3_PROTOCOL = {"ipv4": dpkt.ethernet.ETH_TYPE_IP,
+ "ipv6": dpkt.ethernet.ETH_TYPE_IP6,
+ "arp": dpkt.ethernet.ETH_TYPE_ARP}
+ ALLOWED_L4_PROTOCOL = {"tcp": dpkt.ip.IP_PROTO_TCP,
+ "udp": dpkt.ip.IP_PROTO_UDP,
+ "icmp": dpkt.ip.IP_PROTO_ICMP,
+ "icmpv6": dpkt.ip.IP_PROTO_ICMP6,
+ "igmp": dpkt.ip.IP_PROTO_IGMP,
+ "rtp": dpkt.ip.IP_PROTO_IRTP,
+ "isis": dpkt.ip.IP_PROTO_ISIS,
+ "ospf": dpkt.ip.IP_PROTO_OSPF}
+
+ pkt_bld = CTRexPktBuilder()
+ if l2_encap == "ethernet_ii":
+ pkt_bld.add_pkt_layer("l2", dpkt.ethernet.Ethernet())
+ # set Ethernet layer attributes
+ pkt_bld.set_eth_layer_addr("l2", "src", mac_src)
+ pkt_bld.set_eth_layer_addr("l2", "dst", mac_dst)
+ else:
+ raise NotImplementedError("l2_encap does not support the desired encapsulation '{0}'".format(l2_encap))
+ # set l3 on l2
+ if l3_protocol not in ALLOWED_L3_PROTOCOL:
+ raise ValueError("l3_protocol must be one of the following: {0}".format(ALLOWED_L3_PROTOCOL))
+ pkt_bld.set_layer_attr("l2", "type", ALLOWED_L3_PROTOCOL[l3_protocol])
+
+ # set l3 attributes
+ if l3_protocol == "ipv4":
+ pkt_bld.add_pkt_layer("l3", dpkt.ip.IP())
+ pkt_bld.set_ip_layer_addr("l3", "src", ip_src_addr)
+ pkt_bld.set_ip_layer_addr("l3", "dst", ip_dst_addr)
+ pkt_bld.set_layer_attr("l3", "len", l3_length)
+ else:
+ raise NotImplementedError("l3_protocol '{0}' is not supported by TRex yet.".format(l3_protocol))
+
+ pkt_bld.dump_pkt_to_pcap("stream_test.pcap")
+ return pkt_bld
+
- def config_traffic(self):
- pass
if __name__ == "__main__":
pass
diff --git a/scripts/automation/trex_control_plane/client/trex_port.py b/scripts/automation/trex_control_plane/client/trex_port.py
new file mode 100644
index 00000000..5c5702dd
--- /dev/null
+++ b/scripts/automation/trex_control_plane/client/trex_port.py
@@ -0,0 +1,408 @@
+
+from collections import namedtuple
+from common.trex_types import *
+from common import trex_stats
+
+# describes a single port
+class Port(object):
+ STATE_DOWN = 0
+ STATE_IDLE = 1
+ STATE_STREAMS = 2
+ STATE_TX = 3
+ STATE_PAUSE = 4
+ PortState = namedtuple('PortState', ['state_id', 'state_name'])
+ STATES_MAP = {STATE_DOWN: "DOWN",
+ STATE_IDLE: "IDLE",
+ STATE_STREAMS: "IDLE",
+ STATE_TX: "ACTIVE",
+ STATE_PAUSE: "PAUSE"}
+
+
+ def __init__ (self, port_id, speed, driver, user, comm_link):
+ self.port_id = port_id
+ self.state = self.STATE_IDLE
+ self.handler = None
+ self.comm_link = comm_link
+ self.transmit = comm_link.transmit
+ self.transmit_batch = comm_link.transmit_batch
+ self.user = user
+ self.driver = driver
+ self.speed = speed
+ self.streams = {}
+ self.profile = None
+
+ self.port_stats = trex_stats.CPortStats(self)
+
+
+ def err(self, msg):
+ return RC_ERR("port {0} : {1}".format(self.port_id, msg))
+
+ def ok(self, data = "ACK"):
+ return RC_OK(data)
+
+ def get_speed_bps (self):
+ return (self.speed * 1000 * 1000 * 1000)
+
+ # take the port
+ def acquire(self, force = False):
+ params = {"port_id": self.port_id,
+ "user": self.user,
+ "force": force}
+
+ command = RpcCmdData("acquire", params)
+ rc = self.transmit(command.method, command.params)
+ if rc.success:
+ self.handler = rc.data
+ return self.ok()
+ else:
+ return self.err(rc.data)
+
+ # release the port
+ def release(self):
+ params = {"port_id": self.port_id,
+ "handler": self.handler}
+
+ command = RpcCmdData("release", params)
+ rc = self.transmit(command.method, command.params)
+ self.handler = None
+
+ if rc.success:
+ return self.ok()
+ else:
+ return self.err(rc.data)
+
+ def is_acquired(self):
+ return (self.handler != None)
+
+ def is_active(self):
+ return(self.state == self.STATE_TX ) or (self.state == self.STATE_PAUSE)
+
+ def is_transmitting (self):
+ return (self.state == self.STATE_TX)
+
+ def is_paused (self):
+ return (self.state == self.STATE_PAUSE)
+
+
+ def sync(self):
+ params = {"port_id": self.port_id}
+
+ command = RpcCmdData("get_port_status", params)
+ rc = self.transmit(command.method, command.params)
+ if not rc.success:
+ return self.err(rc.data)
+
+ # sync the port
+ port_state = rc.data['state']
+
+ if port_state == "DOWN":
+ self.state = self.STATE_DOWN
+ elif port_state == "IDLE":
+ self.state = self.STATE_IDLE
+ elif port_state == "STREAMS":
+ self.state = self.STATE_STREAMS
+ elif port_state == "TX":
+ self.state = self.STATE_TX
+ elif port_state == "PAUSE":
+ self.state = self.STATE_PAUSE
+ else:
+ raise Exception("port {0}: bad state received from server '{1}'".format(self.port_id, sync_data['state']))
+
+ return self.ok()
+
+
+ # return TRUE if write commands
+ def is_port_writable (self):
+ # operations on port can be done on state idle or state streams
+ return ((self.state == self.STATE_IDLE) or (self.state == self.STATE_STREAMS))
+
+ # add stream to the port
+ def add_stream (self, stream_id, stream_obj):
+
+ if not self.is_port_writable():
+ return self.err("Please stop port before attempting to add streams")
+
+
+ params = {"handler": self.handler,
+ "port_id": self.port_id,
+ "stream_id": stream_id,
+ "stream": stream_obj}
+
+ rc, data = self.transmit("add_stream", params)
+ if not rc:
+ r = self.err(data)
+ print r.good()
+
+ # add the stream
+ self.streams[stream_id] = stream_obj
+
+ # the only valid state now
+ self.state = self.STATE_STREAMS
+
+ return self.ok()
+
+ # add multiple streams
+ def add_streams (self, streams_list):
+ batch = []
+
+ for stream in streams_list:
+ params = {"handler": self.handler,
+ "port_id": self.port_id,
+ "stream_id": stream.stream_id,
+ "stream": stream.stream}
+
+ cmd = RpcCmdData('add_stream', params)
+ batch.append(cmd)
+
+ rc, data = self.transmit_batch(batch)
+
+ if not rc:
+ return self.err(data)
+
+ # add the stream
+ for stream in streams_list:
+ self.streams[stream.stream_id] = stream.stream
+
+ # the only valid state now
+ self.state = self.STATE_STREAMS
+
+ return self.ok()
+
+ # remove stream from port
+ def remove_stream (self, stream_id):
+
+ if not stream_id in self.streams:
+ return self.err("stream {0} does not exists".format(stream_id))
+
+ params = {"handler": self.handler,
+ "port_id": self.port_id,
+ "stream_id": stream_id}
+
+
+ rc, data = self.transmit("remove_stream", params)
+ if not rc:
+ return self.err(data)
+
+ self.streams[stream_id] = None
+
+ self.state = self.STATE_STREAMS if len(self.streams > 0) else self.STATE_IDLE
+
+ return self.ok()
+
+ # remove all the streams
+ def remove_all_streams (self):
+
+ params = {"handler": self.handler,
+ "port_id": self.port_id}
+
+ rc, data = self.transmit("remove_all_streams", params)
+ if not rc:
+ return self.err(data)
+
+ self.streams = {}
+
+ self.state = self.STATE_IDLE
+
+ return self.ok()
+
+ # get a specific stream
+ def get_stream (self, stream_id):
+ if stream_id in self.streams:
+ return self.streams[stream_id]
+ else:
+ return None
+
+ def get_all_streams (self):
+ return self.streams
+
+ # start traffic
+ def start (self, mul, duration):
+ if self.state == self.STATE_DOWN:
+ return self.err("Unable to start traffic - port is down")
+
+ if self.state == self.STATE_IDLE:
+ return self.err("Unable to start traffic - no streams attached to port")
+
+ if self.state == self.STATE_TX:
+ return self.err("Unable to start traffic - port is already transmitting")
+
+ params = {"handler": self.handler,
+ "port_id": self.port_id,
+ "mul": mul,
+ "duration": duration}
+
+ rc, data = self.transmit("start_traffic", params)
+ if not rc:
+ return self.err(data)
+
+ self.state = self.STATE_TX
+
+ return self.ok()
+
+ # stop traffic
+ # with force ignores the cached state and sends the command
+ def stop (self, force = False):
+
+ if (not force) and (self.state != self.STATE_TX) and (self.state != self.STATE_PAUSE):
+ return self.err("port is not transmitting")
+
+ params = {"handler": self.handler,
+ "port_id": self.port_id}
+
+ rc, data = self.transmit("stop_traffic", params)
+ if not rc:
+ return self.err(data)
+
+ # only valid state after stop
+ self.state = self.STATE_STREAMS
+
+ return self.ok()
+
+ def pause (self):
+
+ if (self.state != self.STATE_TX) :
+ return self.err("port is not transmitting")
+
+ params = {"handler": self.handler,
+ "port_id": self.port_id}
+
+ rc, data = self.transmit("pause_traffic", params)
+ if not rc:
+ return self.err(data)
+
+ # only valid state after stop
+ self.state = self.STATE_PAUSE
+
+ return self.ok()
+
+
+ def resume (self):
+
+ if (self.state != self.STATE_PAUSE) :
+ return self.err("port is not in pause mode")
+
+ params = {"handler": self.handler,
+ "port_id": self.port_id}
+
+ rc, data = self.transmit("resume_traffic", params)
+ if not rc:
+ return self.err(data)
+
+ # only valid state after stop
+ self.state = self.STATE_TX
+
+ return self.ok()
+
+
+ def update (self, mul):
+ if (self.state != self.STATE_TX) :
+ return self.err("port is not transmitting")
+
+ params = {"handler": self.handler,
+ "port_id": self.port_id,
+ "mul": mul}
+
+ rc, data = self.transmit("update_traffic", params)
+ if not rc:
+ return self.err(data)
+
+ return self.ok()
+
+
+ def validate (self):
+
+ if (self.state == self.STATE_DOWN):
+ return self.err("port is down")
+
+ if (self.state == self.STATE_IDLE):
+ return self.err("no streams attached to port")
+
+ params = {"handler": self.handler,
+ "port_id": self.port_id}
+
+ rc, data = self.transmit("validate", params)
+ if not rc:
+ return self.err(data)
+
+ self.profile = data
+
+ return self.ok()
+
+ def get_profile (self):
+ return self.profile
+
+
+ def print_profile (self, mult, duration):
+ if not self.get_profile():
+ return
+
+ rate = self.get_profile()['rate']
+ graph = self.get_profile()['graph']
+
+ print format_text("Profile Map Per Port\n", 'underline', 'bold')
+
+ factor = mult_to_factor(mult, rate['max_bps'], rate['max_pps'], rate['max_line_util'])
+
+ print "Profile max BPS (base / req): {:^12} / {:^12}".format(format_num(rate['max_bps'], suffix = "bps"),
+ format_num(rate['max_bps'] * factor, suffix = "bps"))
+
+ print "Profile max PPS (base / req): {:^12} / {:^12}".format(format_num(rate['max_pps'], suffix = "pps"),
+ format_num(rate['max_pps'] * factor, suffix = "pps"),)
+
+ print "Profile line util. (base / req): {:^12} / {:^12}".format(format_percentage(rate['max_line_util'] * 100),
+ format_percentage(rate['max_line_util'] * factor * 100))
+
+
+ # duration
+ exp_time_base_sec = graph['expected_duration'] / (1000 * 1000)
+ exp_time_factor_sec = exp_time_base_sec / factor
+
+ # user configured a duration
+ if duration > 0:
+ if exp_time_factor_sec > 0:
+ exp_time_factor_sec = min(exp_time_factor_sec, duration)
+ else:
+ exp_time_factor_sec = duration
+
+
+ print "Duration (base / req): {:^12} / {:^12}".format(format_time(exp_time_base_sec),
+ format_time(exp_time_factor_sec))
+ print "\n"
+
+
+ def get_port_state_name(self):
+ return self.STATES_MAP.get(self.state, "Unknown")
+
+ ################# stats handler ######################
+ def generate_port_stats(self):
+ return self.port_stats.generate_stats()
+ pass
+
+ def generate_port_status(self):
+ return {"port-type": self.driver,
+ "maximum": "{speed} Gb/s".format(speed=self.speed),
+ "port-status": self.get_port_state_name()
+ }
+
+ def clear_stats(self):
+ return self.port_stats.clear_stats()
+
+
+ ################# events handler ######################
+ def async_event_port_stopped (self):
+ self.state = self.STATE_STREAMS
+
+
+ def async_event_port_started (self):
+ self.state = self.STATE_TX
+
+
+ def async_event_port_paused (self):
+ self.state = self.STATE_PAUSE
+
+
+ def async_event_port_resumed (self):
+ self.state = self.STATE_TX
+
+ def async_event_forced_acquired (self):
+ self.handler = None
diff --git a/scripts/automation/trex_control_plane/client/trex_stateless_client.py b/scripts/automation/trex_control_plane/client/trex_stateless_client.py
index 334496d1..a2b1f6d9 100755
--- a/scripts/automation/trex_control_plane/client/trex_stateless_client.py
+++ b/scripts/automation/trex_control_plane/client/trex_stateless_client.py
@@ -6,324 +6,1175 @@ try:
except ImportError:
# support import for Python 3
import client.outer_packages
+
from client_utils.jsonrpc_client import JsonRpcClient, BatchMessage
from client_utils.packet_builder import CTRexPktBuilder
import json
-from common.trex_stats import *
+
+from common.trex_streams import *
from collections import namedtuple
+from common.text_opts import *
+from common import trex_stats
+from client_utils import parsing_opts, text_tables
+import time
+import datetime
+import re
+import random
+from trex_port import Port
+from common.trex_types import *
+
+from trex_async_client import CTRexAsyncClient
+
+
+########## utlity ############
+def mult_to_factor (mult, max_bps, max_pps, line_util):
+ if mult['type'] == 'raw':
+ return mult['value']
+
+ if mult['type'] == 'bps':
+ return mult['value'] / max_bps
+
+ if mult['type'] == 'pps':
+ return mult['value'] / max_pps
+
+ if mult['type'] == 'percentage':
+ return mult['value'] / line_util
+
class CTRexStatelessClient(object):
"""docstring for CTRexStatelessClient"""
- RpcCmdData = namedtuple('RpcCmdData', ['method', 'params'])
- def __init__(self, username, server="localhost", port=5050, virtual=False):
+ # verbose levels
+ VERBOSE_SILENCE = 0
+ VERBOSE_REGULAR = 1
+ VERBOSE_HIGH = 2
+
+ def __init__(self, username, server="localhost", sync_port = 5050, async_port = 4500, virtual=False):
super(CTRexStatelessClient, self).__init__()
+
self.user = username
- self.tx_link = CTRexStatelessClient.CTxLink(server, port, virtual)
- self._conn_handler = {}
- self._active_ports = set()
- self._stats = CTRexStatsManager("port", "stream")
- self._system_info = None
-
- # ----- decorator methods ----- #
- def force_status(owned=True, active_and_owned=False):
- def wrapper(func):
- def wrapper_f(self, *args, **kwargs):
- port_ids = kwargs.get("port_id")
- if isinstance(port_ids, int):
- # make sure port_ids is a list
- port_ids = [port_ids]
- bad_ids = set()
- for port_id in port_ids:
- port_owned = self._conn_handler.get(kwargs.get(port_id))
- if owned and not port_owned:
- bad_ids.add(port_ids)
- elif active_and_owned: # stronger condition than just owned, hence gets precedence
- if port_owned and port_id in self._active_ports:
- continue
- else:
- bad_ids.add(port_ids)
- else:
- continue
- if bad_ids:
- # Some port IDs are not according to desires status
- raise RuntimeError("The requested method ('{0}') cannot be invoked since port IDs {1} are not"
- "at allowed stated".format(func.__name__))
+
+ self.comm_link = CTRexStatelessClient.CCommLink(server, sync_port, virtual)
+
+ # default verbose level
+ self.verbose = self.VERBOSE_REGULAR
+
+ self.ports = {}
+ self._connection_info = {"server": server,
+ "sync_port": sync_port,
+ "async_port": async_port}
+ self.system_info = {}
+ self.server_version = {}
+ self.__err_log = None
+
+ self.async_client = CTRexAsyncClient(server, async_port, self)
+
+ self.streams_db = CStreamsDB()
+ self.global_stats = trex_stats.CGlobalStats(self._connection_info,
+ self.server_version,
+ self.ports)
+ self.stats_generator = trex_stats.CTRexStatsGenerator(self.global_stats,
+ self.ports)
+
+ self.events = []
+
+
+ self.read_only = False
+ self.connected = False
+
+
+
+ # returns the port object
+ def get_port (self, port_id):
+ return self.ports.get(port_id, None)
+
+
+ def get_server (self):
+ return self.comm_link.get_server()
+
+ ################# events handler ######################
+ def add_event_log (self, msg, ev_type, show = False):
+
+ if ev_type == "server":
+ prefix = "[server]"
+ elif ev_type == "local":
+ prefix = "[local]"
+
+ ts = time.time()
+ st = datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S')
+ self.events.append("{:<10} - {:^8} - {:}".format(st, prefix, format_text(msg, 'bold')))
+
+ if show and self.check_verbose(self.VERBOSE_REGULAR):
+ print format_text("\n{:^8} - {:}".format(prefix, format_text(msg, 'bold')))
+
+
+ def handle_async_stats_update(self, dump_data):
+ global_stats = {}
+ port_stats = {}
+
+ # filter the values per port and general
+ for key, value in dump_data.iteritems():
+ # match a pattern of ports
+ m = re.search('(.*)\-([0-8])', key)
+ if m:
+ port_id = int(m.group(2))
+ field_name = m.group(1)
+ if self.ports.has_key(port_id):
+ if not port_id in port_stats:
+ port_stats[port_id] = {}
+ port_stats[port_id][field_name] = value
else:
- func(self, *args, **kwargs)
- return wrapper_f
- return wrapper
+ continue
+ else:
+ # no port match - general stats
+ global_stats[key] = value
- @property
- def system_info(self):
- if not self._system_info:
- self._system_info = self.get_system_info()
- return self._system_info
+ # update the general object with the snapshot
+ self.global_stats.update(global_stats)
+ # update all ports
+ for port_id, data in port_stats.iteritems():
+ self.ports[port_id].port_stats.update(data)
- # ----- user-access methods ----- #
- def ping(self):
- return self.transmit("ping")
+
+
+ def handle_async_event (self, type, data):
+ # DP stopped
+
+ show_event = False
+
+ # port started
+ if (type == 0):
+ port_id = int(data['port_id'])
+ ev = "Port {0} has started".format(port_id)
+ self.async_event_port_started(port_id)
+
+ # port stopped
+ elif (type == 1):
+ port_id = int(data['port_id'])
+ ev = "Port {0} has stopped".format(port_id)
+
+ # call the handler
+ self.async_event_port_stopped(port_id)
+
+
+ # port paused
+ elif (type == 2):
+ port_id = int(data['port_id'])
+ ev = "Port {0} has paused".format(port_id)
+
+ # call the handler
+ self.async_event_port_paused(port_id)
+
+ # port resumed
+ elif (type == 3):
+ port_id = int(data['port_id'])
+ ev = "Port {0} has resumed".format(port_id)
+
+ # call the handler
+ self.async_event_port_resumed(port_id)
+
+ # port finished traffic
+ elif (type == 4):
+ port_id = int(data['port_id'])
+ ev = "Port {0} job done".format(port_id)
+
+ # call the handler
+ self.async_event_port_stopped(port_id)
+ show_event = True
+
+ # port was stolen...
+ elif (type == 5):
+ port_id = int(data['port_id'])
+ ev = "Port {0} was forcely taken".format(port_id)
+
+ # call the handler
+ self.async_event_port_forced_acquired(port_id)
+ show_event = True
+
+ # server stopped
+ elif (type == 100):
+ ev = "Server has stopped"
+ self.async_event_server_stopped()
+ show_event = True
+
+
+ else:
+ # unknown event - ignore
+ return
+
+
+ self.add_event_log(ev, 'server', show_event)
+
+
+ def async_event_port_stopped (self, port_id):
+ self.ports[port_id].async_event_port_stopped()
+
+
+ def async_event_port_started (self, port_id):
+ self.ports[port_id].async_event_port_started()
+
+
+ def async_event_port_paused (self, port_id):
+ self.ports[port_id].async_event_port_paused()
+
+
+ def async_event_port_resumed (self, port_id):
+ self.ports[port_id].async_event_port_resumed()
+
+
+ def async_event_port_forced_acquired (self, port_id):
+ self.ports[port_id].async_event_forced_acquired()
+ self.read_only = True
+
+ def async_event_server_stopped (self):
+ self.connected = False
+
+
+ def get_events (self):
+ return self.events
+
+ def clear_events (self):
+ self.events = []
+
+ ############# helper functions section ##############
+
+ # measure time for functions
+ def timing(f):
+ def wrap(*args):
+ time1 = time.time()
+ ret = f(*args)
+
+ # don't want to print on error
+ if ret.bad():
+ return ret
+
+ delta = time.time() - time1
+ print format_time(delta) + "\n"
+
+ return ret
+
+ return wrap
+
+
+ def validate_port_list(self, port_id_list):
+ if not isinstance(port_id_list, list):
+ print type(port_id_list)
+ return False
+
+ # check each item of the sequence
+ return all([ (port_id >= 0) and (port_id < self.get_port_count())
+ for port_id in port_id_list ])
+
+ # some preprocessing for port argument
+ def __ports (self, port_id_list):
+
+ # none means all
+ if port_id_list == None:
+ return range(0, self.get_port_count())
+
+ # always list
+ if isinstance(port_id_list, int):
+ port_id_list = [port_id_list]
+
+ if not isinstance(port_id_list, list):
+ raise ValueError("bad port id list: {0}".format(port_id_list))
+
+ for port_id in port_id_list:
+ if not isinstance(port_id, int) or (port_id < 0) or (port_id > self.get_port_count()):
+ raise ValueError("bad port id {0}".format(port_id))
+
+ return port_id_list
+
+ ############ boot up section ################
+
+ # connection sequence
+
+ # mode can be RW - read / write, RWF - read write with force , RO - read only
+ def connect(self, mode = "RW"):
+
+ if self.is_connected():
+ self.disconnect()
+
+ # clear this flag
+ self.connected = False
+
+ # connect sync channel
+ rc, data = self.comm_link.connect()
+ if not rc:
+ return RC_ERR(data)
+
+ # connect async channel
+ rc, data = self.async_client.connect()
+ if not rc:
+ return RC_ERR(data)
+
+ # version
+ rc, data = self.transmit("get_version")
+ if not rc:
+ return RC_ERR(data)
+
+ self.server_version = data
+ self.global_stats.server_version = data
+
+ # cache system info
+ rc, data = self.transmit("get_system_info")
+ if not rc:
+ return RC_ERR(data)
+ self.system_info = data
+
+ # cache supported commands
+ rc, data = self.transmit("get_supported_cmds")
+ if not rc:
+ return RC_ERR(data)
+
+ self.supported_cmds = data
+
+ # create ports
+ for port_id in xrange(self.get_port_count()):
+ speed = self.system_info['ports'][port_id]['speed']
+ driver = self.system_info['ports'][port_id]['driver']
+
+ self.ports[port_id] = Port(port_id, speed, driver, self.user, self.comm_link)
+
+
+ # sync the ports
+ rc = self.sync_ports()
+ if rc.bad():
+ return rc
+
+ # acquire all ports
+ if mode == "RW":
+ rc = self.acquire(force = False)
+
+ # fallback to read only if failed
+ if rc.bad():
+ rc.annotate(show_status = False)
+ print format_text("Switching to read only mode - only few commands will be available", 'bold')
+
+ self.release(self.get_acquired_ports())
+ self.read_only = True
+ else:
+ self.read_only = False
+
+ elif mode == "RWF":
+ rc = self.acquire(force = True)
+ if rc.bad():
+ return rc
+ self.read_only = False
+
+ elif mode == "RO":
+ # no acquire on read only
+ rc = RC_OK()
+ self.read_only = True
+
+
+
+ self.connected = True
+ return RC_OK()
+
+
+ def is_read_only (self):
+ return self.read_only
+
+ def is_connected (self):
+ return self.connected and self.comm_link.is_connected
+
+
+ def disconnect(self):
+ # release any previous acquired ports
+ if self.is_connected():
+ self.release(self.get_acquired_ports())
+
+ self.comm_link.disconnect()
+ self.async_client.disconnect()
+
+ self.connected = False
+
+ return RC_OK()
+
+
+ def on_async_dead (self):
+ if self.connected:
+ msg = 'lost connection to server'
+ self.add_event_log(msg, 'local', True)
+ self.connected = False
+
+ def on_async_alive (self):
+ pass
+
+ ########### cached queries (no server traffic) ###########
def get_supported_cmds(self):
- return self.transmit("get_supported_cmds")
+ return self.supported_cmds
def get_version(self):
- return self.transmit("get_version")
+ return self.server_version
def get_system_info(self):
- return self.transmit("get_system_info")
+ return self.system_info
def get_port_count(self):
return self.system_info.get("port_count")
- def acquire(self, port_id, force=False):
- if not self._is_ports_valid(port_id):
- raise ValueError("Provided illegal port id input")
- if isinstance(port_id, list) or isinstance(port_id, set):
- # handle as batch mode
- port_ids = set(port_id) # convert to set to avoid duplications
- commands = [self.RpcCmdData("acquire", {"port_id": p_id, "user": self.user, "force": force})
- for p_id in port_ids]
- rc, resp_list = self.transmit_batch(commands)
- if rc:
- self._process_batch_result(commands, resp_list, self._handle_acquire_response)
- else:
- params = {"port_id": port_id,
- "user": self.user,
- "force": force}
- command = self.RpcCmdData("acquire", params)
- self._handle_acquire_response(command, self.transmit(command.method, command.params))
- return self._conn_handler.get(port_id)
-
- @force_status(owned=True)
- def release(self, port_id=None):
- if not self._is_ports_valid(port_id):
- raise ValueError("Provided illegal port id input")
- if isinstance(port_id, list) or isinstance(port_id, set):
- # handle as batch mode
- port_ids = set(port_id) # convert to set to avoid duplications
- commands = [self.RpcCmdData("release", {"handler": self._conn_handler.get(p_id), "port_id": p_id})
- for p_id in port_ids]
- rc, resp_list = self.transmit_batch(commands)
- if rc:
- self._process_batch_result(commands, resp_list, self._handle_release_response)
+ def get_port_ids(self, as_str=False):
+ port_ids = range(self.get_port_count())
+ if as_str:
+ return " ".join(str(p) for p in port_ids)
else:
- self._conn_handler.pop(port_id)
- params = {"handler": self._conn_handler.get(port_id),
- "port_id": port_id}
- command = self.RpcCmdData("release", params)
- self._handle_release_response(command, self.transmit(command.method, command.params))
- return
+ return port_ids
- @force_status(owned=True)
- def add_stream(self, stream_id, stream_obj, port_id=None):
- if not self._is_ports_valid(port_id):
- raise ValueError("Provided illegal port id input")
- assert isinstance(stream_obj, CStream)
- params = {"handler": self._conn_handler.get(port_id),
- "port_id": port_id,
- "stream_id": stream_id,
- "stream": stream_obj.dump()}
- return self.transmit("add_stream", params)
-
- @force_status(owned=True)
- def remove_stream(self, stream_id, port_id=None):
- if not self._is_ports_valid(port_id):
- raise ValueError("Provided illegal port id input")
- params = {"handler": self._conn_handler.get(port_id),
- "port_id": port_id,
- "stream_id": stream_id}
- return self.transmit("remove_stream", params)
-
- @force_status(owned=True, active_and_owned=True)
- def get_stream_id_list(self, port_id=None):
- if not self._is_ports_valid(port_id):
- raise ValueError("Provided illegal port id input")
- params = {"handler": self._conn_handler.get(port_id),
- "port_id": port_id}
- return self.transmit("get_stream_list", params)
-
- @force_status(owned=True, active_and_owned=True)
- def get_stream(self, stream_id, port_id=None):
- if not self._is_ports_valid(port_id):
- raise ValueError("Provided illegal port id input")
- params = {"handler": self._conn_handler.get(port_id),
- "port_id": port_id,
- "stream_id": stream_id}
- return self.transmit("get_stream_list", params)
-
- @force_status(owned=True)
- def start_traffic(self, port_id=None):
- if not self._is_ports_valid(port_id):
- raise ValueError("Provided illegal port id input")
- if isinstance(port_id, list) or isinstance(port_id, set):
- # handle as batch mode
- port_ids = set(port_id) # convert to set to avoid duplications
- commands = [self.RpcCmdData("start_traffic", {"handler": self._conn_handler.get(p_id), "port_id": p_id})
- for p_id in port_ids]
- rc, resp_list = self.transmit_batch(commands)
- if rc:
- self._process_batch_result(commands, resp_list, self._handle_start_traffic_response)
- else:
- params = {"handler": self._conn_handler.get(port_id),
- "port_id": port_id}
- command = self.RpcCmdData("start_traffic", params)
- self._handle_start_traffic_response(command, self.transmit(command.method, command.params))
- return
+ def get_stats_async (self):
+ return self.async_client.get_stats()
+
+ def get_connection_port (self):
+ return self.comm_link.port
+
+ def get_connection_ip (self):
+ return self.comm_link.server
- @force_status(owned=False, active_and_owned=True)
- def stop_traffic(self, port_id=None):
- if not self._is_ports_valid(port_id):
- raise ValueError("Provided illegal port id input")
- if isinstance(port_id, list) or isinstance(port_id, set):
- # handle as batch mode
- port_ids = set(port_id) # convert to set to avoid duplications
- commands = [self.RpcCmdData("stop_traffic", {"handler": self._conn_handler.get(p_id), "port_id": p_id})
- for p_id in port_ids]
- rc, resp_list = self.transmit_batch(commands)
- if rc:
- self._process_batch_result(commands, resp_list, self._handle_stop_traffic_response)
+ def get_all_ports (self):
+ return [port_id for port_id, port_obj in self.ports.iteritems()]
+
+ def get_acquired_ports(self):
+ return [port_id
+ for port_id, port_obj in self.ports.iteritems()
+ if port_obj.is_acquired()]
+
+ def get_active_ports(self):
+ return [port_id
+ for port_id, port_obj in self.ports.iteritems()
+ if port_obj.is_active()]
+
+ def get_paused_ports (self):
+ return [port_id
+ for port_id, port_obj in self.ports.iteritems()
+ if port_obj.is_paused()]
+
+ def get_transmitting_ports (self):
+ return [port_id
+ for port_id, port_obj in self.ports.iteritems()
+ if port_obj.is_transmitting()]
+
+ def set_verbose(self, mode):
+
+ # on high - enable link verbose
+ if mode == self.VERBOSE_HIGH:
+ self.comm_link.set_verbose(True)
else:
- params = {"handler": self._conn_handler.get(port_id),
- "port_id": port_id}
- command = self.RpcCmdData("stop_traffic", params)
- self._handle_start_traffic_response(command, self.transmit(command.method, command.params))
- return
+ self.comm_link.set_verbose(False)
+
+ self.verbose = mode
+
+
+ def check_verbose (self, level):
+ return (self.verbose >= level)
+
+ def get_verbose (self):
+ return self.verbose
+
+ ############# server actions ################
+
+ # ping server
+ def ping(self):
+ rc, info = self.transmit("ping")
+ return RC(rc, info)
+
+
def get_global_stats(self):
- command = self.RpcCmdData("get_global_stats", {})
- return self._handle_get_global_stats_response(command, self.transmit(command.method, command.params))
- # return self.transmit("get_global_stats")
+ rc, info = self.transmit("get_global_stats")
+ return RC(rc, info)
+
+
+ ########## port commands ##############
+ def sync_ports (self, port_id_list = None, force = False):
+ port_id_list = self.__ports(port_id_list)
+
+ rc = RC()
+
+ for port_id in port_id_list:
+ rc.add(self.ports[port_id].sync())
+
+ return rc
+
+ # acquire ports, if port_list is none - get all
+ def acquire (self, port_id_list = None, force = False):
+ port_id_list = self.__ports(port_id_list)
+
+ rc = RC()
+
+ for port_id in port_id_list:
+ rc.add(self.ports[port_id].acquire(force))
+
+ return rc
+
+ # release ports
+ def release (self, port_id_list = None):
+ port_id_list = self.__ports(port_id_list)
+
+ rc = RC()
+
+ for port_id in port_id_list:
+ rc.add(self.ports[port_id].release())
+
+ return rc
+
+
+ def add_stream(self, stream_id, stream_obj, port_id_list = None):
+
+ port_id_list = self.__ports(port_id_list)
+
+ rc = RC()
+
+ for port_id in port_id_list:
+ rc.add(self.ports[port_id].add_stream(stream_id, stream_obj))
+
+ return rc
+
+
+
+ def add_stream_pack(self, stream_pack_list, port_id_list = None):
+
+ port_id_list = self.__ports(port_id_list)
+
+ rc = RC()
+
+ for port_id in port_id_list:
+ rc.add(self.ports[port_id].add_streams(stream_pack_list))
+
+ return rc
+
+
+
+ def remove_stream(self, stream_id, port_id_list = None):
+ port_id_list = self.__ports(port_id_list)
+
+ rc = RC()
+
+ for port_id in port_id_list:
+ rc.add(self.ports[port_id].remove_stream(stream_id))
+
+ return rc
+
+
+
+ def remove_all_streams(self, port_id_list = None):
+ port_id_list = self.__ports(port_id_list)
+
+ rc = RC()
+
+ for port_id in port_id_list:
+ rc.add(self.ports[port_id].remove_all_streams())
+
+ return rc
+
+
+ def get_stream(self, stream_id, port_id, get_pkt = False):
+
+ return self.ports[port_id].get_stream(stream_id)
+
+
+ def get_all_streams(self, port_id, get_pkt = False):
+
+ return self.ports[port_id].get_all_streams()
+
+
+ def get_stream_id_list(self, port_id):
+
+ return self.ports[port_id].get_stream_id_list()
+
+
+ def start_traffic (self, multiplier, duration, port_id_list = None):
+
+ port_id_list = self.__ports(port_id_list)
+
+ rc = RC()
+
+ for port_id in port_id_list:
+ rc.add(self.ports[port_id].start(multiplier, duration))
+
+ return rc
+
+
+ def resume_traffic (self, port_id_list = None, force = False):
+
+ port_id_list = self.__ports(port_id_list)
+ rc = RC()
+
+ for port_id in port_id_list:
+ rc.add(self.ports[port_id].resume())
+
+ return rc
+
+ def pause_traffic (self, port_id_list = None, force = False):
+
+ port_id_list = self.__ports(port_id_list)
+ rc = RC()
+
+ for port_id in port_id_list:
+ rc.add(self.ports[port_id].pause())
+
+ return rc
+
+ def stop_traffic (self, port_id_list = None, force = False):
+
+ port_id_list = self.__ports(port_id_list)
+ rc = RC()
+
+ for port_id in port_id_list:
+ rc.add(self.ports[port_id].stop(force))
+
+ return rc
+
+
+ def update_traffic (self, mult, port_id_list = None, force = False):
+
+ port_id_list = self.__ports(port_id_list)
+ rc = RC()
+
+ for port_id in port_id_list:
+ rc.add(self.ports[port_id].update(mult))
+
+ return rc
+
+
+ def validate (self, port_id_list = None):
+ port_id_list = self.__ports(port_id_list)
+
+ rc = RC()
+
+ for port_id in port_id_list:
+ rc.add(self.ports[port_id].validate())
+
+ return rc
+
- @force_status(owned=True, active_and_owned=True)
def get_port_stats(self, port_id=None):
- if not self._is_ports_valid(port_id):
- raise ValueError("Provided illegal port id input")
- if isinstance(port_id, list) or isinstance(port_id, set):
- # handle as batch mode
- port_ids = set(port_id) # convert to set to avoid duplications
- commands = [self.RpcCmdData("get_port_stats", {"handler": self._conn_handler.get(p_id), "port_id": p_id})
- for p_id in port_ids]
- rc, resp_list = self.transmit_batch(commands)
- if rc:
- self._process_batch_result(commands, resp_list, self._handle_get_port_stats_response)
- else:
- params = {"handler": self._conn_handler.get(port_id),
- "port_id": port_id}
- command = self.RpcCmdData("get_port_stats", params)
- return self._handle_get_port_stats_response(command, self.transmit(command.method, command.params))
+ pass
- @force_status(owned=True, active_and_owned=True)
def get_stream_stats(self, port_id=None):
- if not self._is_ports_valid(port_id):
- raise ValueError("Provided illegal port id input")
- if isinstance(port_id, list) or isinstance(port_id, set):
- # handle as batch mode
- port_ids = set(port_id) # convert to set to avoid duplications
- commands = [self.RpcCmdData("get_stream_stats", {"handler": self._conn_handler.get(p_id), "port_id": p_id})
- for p_id in port_ids]
- rc, resp_list = self.transmit_batch(commands)
- if rc:
- self._process_batch_result(commands, resp_list, self._handle_get_stream_stats_response)
- else:
- params = {"handler": self._conn_handler.get(port_id),
- "port_id": port_id}
- command = self.RpcCmdData("get_stream_stats", params)
- return self._handle_get_stream_stats_response(command, self.transmit(command.method, command.params))
+ pass
+
- # ----- internal methods ----- #
def transmit(self, method_name, params={}):
- return self.tx_link.transmit(method_name, params)
+ return self.comm_link.transmit(method_name, params)
+
def transmit_batch(self, batch_list):
- return self.tx_link.transmit_batch(batch_list)
+ return self.comm_link.transmit_batch(batch_list)
- @staticmethod
- def _object_decoder(obj_type, obj_data):
- if obj_type == "global":
- return CGlobalStats(**obj_data)
- elif obj_type == "port":
- return CPortStats(**obj_data)
- elif obj_type == "stream":
- return CStreamStats(**obj_data)
- else:
- # Do not serialize the data into class
- return obj_data
+ ######################### Console (high level) API #########################
- @staticmethod
- def default_success_test(result_obj):
- if result_obj.success:
- return True
- else:
- return False
+ @timing
+ def cmd_ping(self):
+ rc = self.ping()
+ rc.annotate("Pinging the server on '{0}' port '{1}': ".format(self.get_connection_ip(), self.get_connection_port()))
+ return rc
- # ----- handler internal methods ----- #
- def _handle_acquire_response(self, request, response):
- if response.success:
- self._conn_handler[request.get("port_id")] = response.data
+ def cmd_connect(self, mode = "RW"):
+ rc = self.connect(mode)
+ rc.annotate()
+ return rc
- def _handle_release_response(self, request, response):
- if response.success:
- del self._conn_handler[request.get("port_id")]
+ def cmd_disconnect(self):
+ rc = self.disconnect()
+ rc.annotate()
+ return rc
- def _handle_start_traffic_response(self, request, response):
- if response.success:
- self._active_ports.add(request.get("port_id"))
+ # reset
+ def cmd_reset(self):
- def _handle_stop_traffic_response(self, request, response):
- if response.success:
- self._active_ports.remove(request.get("port_id"))
- def _handle_get_global_stats_response(self, request, response):
- if response.success:
- return CGlobalStats(**response.success)
- else:
- return False
+ rc = self.acquire(force = True)
+ rc.annotate("Force acquiring all ports:")
+ if rc.bad():
+ return rc
+
+
+ # force stop all ports
+ rc = self.stop_traffic(self.get_port_ids(), True)
+ rc.annotate("Stop traffic on all ports:")
+ if rc.bad():
+ return rc
+
+
+ # remove all streams
+ rc = self.remove_all_streams(self.get_port_ids())
+ rc.annotate("Removing all streams from all ports:")
+ if rc.bad():
+ return rc
+
+ # TODO: clear stats
+ return RC_OK()
+
+
+ # stop cmd
+ def cmd_stop (self, port_id_list):
+
+ # find the relveant ports
+ active_ports = list(set(self.get_active_ports()).intersection(port_id_list))
+
+ if not active_ports:
+ msg = "No active traffic on provided ports"
+ print format_text(msg, 'bold')
+ return RC_ERR(msg)
- def _handle_get_port_stats_response(self, request, response):
- if response.success:
- return CPortStats(**response.success)
+ rc = self.stop_traffic(active_ports)
+ rc.annotate("Stopping traffic on port(s) {0}:".format(port_id_list))
+ if rc.bad():
+ return rc
+
+ return RC_OK()
+
+ # update cmd
+ def cmd_update (self, port_id_list, mult):
+
+ # find the relevant ports
+ active_ports = list(set(self.get_active_ports()).intersection(port_id_list))
+
+ if not active_ports:
+ msg = "No active traffic on provided ports"
+ print format_text(msg, 'bold')
+ return RC_ERR(msg)
+
+ rc = self.update_traffic(mult, active_ports)
+ rc.annotate("Updating traffic on port(s) {0}:".format(port_id_list))
+
+ return rc
+
+ # clear stats
+ def cmd_clear(self, port_id_list):
+
+ for port_id in port_id_list:
+ self.ports[port_id].clear_stats()
+
+ self.global_stats.clear_stats()
+
+ return RC_OK()
+
+
+ # pause cmd
+ def cmd_pause (self, port_id_list):
+
+ # find the relevant ports
+ active_ports = list(set(self.get_active_ports()).intersection(port_id_list))
+
+ if not active_ports:
+ msg = "No active traffic on provided ports"
+ print format_text(msg, 'bold')
+ return RC_ERR(msg)
+
+ rc = self.pause_traffic(active_ports)
+ rc.annotate("Pausing traffic on port(s) {0}:".format(port_id_list))
+ return rc
+
+
+
+ # resume cmd
+ def cmd_resume (self, port_id_list):
+
+ # find the relveant ports
+ active_ports = list(set(self.get_active_ports()).intersection(port_id_list))
+
+ if not active_ports:
+ msg = "No active traffic on porvided ports"
+ print format_text(msg, 'bold')
+ return RC_ERR(msg)
+
+ rc = self.resume_traffic(active_ports)
+ rc.annotate("Resume traffic on port(s) {0}:".format(port_id_list))
+ return rc
+
+
+ # start cmd
+ def cmd_start (self, port_id_list, stream_list, mult, force, duration, dry):
+
+ active_ports = list(set(self.get_active_ports()).intersection(port_id_list))
+
+ if active_ports:
+ if not force:
+ msg = "Port(s) {0} are active - please stop them or add '--force'".format(active_ports)
+ print format_text(msg, 'bold')
+ return RC_ERR(msg)
+ else:
+ rc = self.cmd_stop(active_ports)
+ if not rc:
+ return rc
+
+
+ rc = self.remove_all_streams(port_id_list)
+ rc.annotate("Removing all streams from port(s) {0}:".format(port_id_list))
+ if rc.bad():
+ return rc
+
+
+ rc = self.add_stream_pack(stream_list.compiled, port_id_list)
+ rc.annotate("Attaching {0} streams to port(s) {1}:".format(len(stream_list.compiled), port_id_list))
+ if rc.bad():
+ return rc
+
+ # when not on dry - start the traffic , otherwise validate only
+ if not dry:
+ rc = self.start_traffic(mult, duration, port_id_list)
+ rc.annotate("Starting traffic on port(s) {0}:".format(port_id_list))
+
+ return rc
else:
- return False
+ rc = self.validate(port_id_list)
+ rc.annotate("Validating traffic profile on port(s) {0}:".format(port_id_list))
+
+ if rc.bad():
+ return rc
+
+ # show a profile on one port for illustration
+ self.ports[port_id_list[0]].print_profile(mult, duration)
+
+ return rc
+
+
+ # validate port(s) profile
+ def cmd_validate (self, port_id_list):
+ rc = self.validate(port_id_list)
+ rc.annotate("Validating streams on port(s) {0}:".format(port_id_list))
+ return rc
+
+
+ # stats
+ def cmd_stats(self, port_id_list, stats_mask=set()):
+ stats_opts = trex_stats.ALL_STATS_OPTS.intersection(stats_mask)
+
+ stats_obj = {}
+ for stats_type in stats_opts:
+ stats_obj.update(self.stats_generator.generate_single_statistic(port_id_list, stats_type))
+ return stats_obj
+
+
+ ############## High Level API With Parser ################
+
+ def cmd_connect_line (self, line):
+ '''Connects to the TRex server'''
+ # define a parser
+ parser = parsing_opts.gen_parser(self,
+ "connect",
+ self.cmd_connect_line.__doc__,
+ parsing_opts.FORCE)
- def _handle_get_stream_stats_response(self, request, response):
- if response.success:
- return CStreamStats(**response.success)
+ opts = parser.parse_args(line.split())
+
+ if opts is None:
+ return RC_ERR("bad command line parameters")
+
+ if opts.force:
+ rc = self.cmd_connect(mode = "RWF")
else:
- return False
+ rc = self.cmd_connect(mode = "RW")
+
+ @timing
+ def cmd_start_line (self, line):
+ '''Start selected traffic in specified ports on TRex\n'''
+ # define a parser
+ parser = parsing_opts.gen_parser(self,
+ "start",
+ self.cmd_start_line.__doc__,
+ parsing_opts.PORT_LIST_WITH_ALL,
+ parsing_opts.TOTAL,
+ parsing_opts.FORCE,
+ parsing_opts.STREAM_FROM_PATH_OR_FILE,
+ parsing_opts.DURATION,
+ parsing_opts.MULTIPLIER_STRICT,
+ parsing_opts.DRY_RUN)
+
+ opts = parser.parse_args(line.split())
+
+ if opts is None:
+ return RC_ERR("bad command line parameters")
+
+
+ if opts.dry:
+ print format_text("\n*** DRY RUN ***", 'bold')
+
+ if opts.db:
+ stream_list = self.streams_db.get_stream_pack(opts.db)
+ rc = RC(stream_list != None)
+ rc.annotate("Load stream pack (from DB):")
+ if rc.bad():
+ return RC_ERR("Failed to load stream pack")
- def _is_ports_valid(self, port_id):
- if isinstance(port_id, list) or isinstance(port_id, set):
- # check each item of the sequence
- return all([self._is_ports_valid(port)
- for port in port_id])
- elif (isinstance(port_id, int)) and (port_id > 0) and (port_id <= self.get_port_count()):
- return True
else:
- return False
+ # load streams from file
+ stream_list = self.streams_db.load_yaml_file(opts.file[0])
+ rc = RC(stream_list != None)
+ rc.annotate("Load stream pack (from file):")
+ if stream_list == None:
+ return RC_ERR("Failed to load stream pack")
+
+
+ # total has no meaning with percentage - its linear
+ if opts.total and (opts.mult['type'] != 'percentage'):
+ # if total was set - divide it between the ports
+ opts.mult['value'] = opts.mult['value'] / len(opts.ports)
+
+ return self.cmd_start(opts.ports, stream_list, opts.mult, opts.force, opts.duration, opts.dry)
+
+ @timing
+ def cmd_resume_line (self, line):
+ '''Resume active traffic in specified ports on TRex\n'''
+ parser = parsing_opts.gen_parser(self,
+ "resume",
+ self.cmd_stop_line.__doc__,
+ parsing_opts.PORT_LIST_WITH_ALL)
+
+ opts = parser.parse_args(line.split())
+ if opts is None:
+ return RC_ERR("bad command line parameters")
+
+ return self.cmd_resume(opts.ports)
+
+
+ @timing
+ def cmd_stop_line (self, line):
+ '''Stop active traffic in specified ports on TRex\n'''
+ parser = parsing_opts.gen_parser(self,
+ "stop",
+ self.cmd_stop_line.__doc__,
+ parsing_opts.PORT_LIST_WITH_ALL)
+
+ opts = parser.parse_args(line.split())
+ if opts is None:
+ return RC_ERR("bad command line parameters")
+
+ return self.cmd_stop(opts.ports)
+
+
+ @timing
+ def cmd_pause_line (self, line):
+ '''Pause active traffic in specified ports on TRex\n'''
+ parser = parsing_opts.gen_parser(self,
+ "pause",
+ self.cmd_stop_line.__doc__,
+ parsing_opts.PORT_LIST_WITH_ALL)
+
+ opts = parser.parse_args(line.split())
+ if opts is None:
+ return RC_ERR("bad command line parameters")
+
+ return self.cmd_pause(opts.ports)
+
+
+ @timing
+ def cmd_update_line (self, line):
+ '''Update port(s) speed currently active\n'''
+ parser = parsing_opts.gen_parser(self,
+ "update",
+ self.cmd_update_line.__doc__,
+ parsing_opts.PORT_LIST_WITH_ALL,
+ parsing_opts.MULTIPLIER,
+ parsing_opts.TOTAL)
+
+ opts = parser.parse_args(line.split())
+ if opts is None:
+ return RC_ERR("bad command line paramters")
+
+ # total has no meaning with percentage - its linear
+ if opts.total and (opts.mult['type'] != 'percentage'):
+ # if total was set - divide it between the ports
+ opts.mult['value'] = opts.mult['value'] / len(opts.ports)
+
+ return self.cmd_update(opts.ports, opts.mult)
+
+ @timing
+ def cmd_reset_line (self, line):
+ return self.cmd_reset()
+
+
+ def cmd_clear_line (self, line):
+ '''Clear cached local statistics\n'''
+ # define a parser
+ parser = parsing_opts.gen_parser(self,
+ "clear",
+ self.cmd_clear_line.__doc__,
+ parsing_opts.PORT_LIST_WITH_ALL)
+
+ opts = parser.parse_args(line.split())
+
+ if opts is None:
+ return RC_ERR("bad command line parameters")
+ return self.cmd_clear(opts.ports)
+
+
+ def cmd_stats_line (self, line):
+ '''Fetch statistics from TRex server by port\n'''
+ # define a parser
+ parser = parsing_opts.gen_parser(self,
+ "stats",
+ self.cmd_stats_line.__doc__,
+ parsing_opts.PORT_LIST_WITH_ALL,
+ parsing_opts.STATS_MASK)
+
+ opts = parser.parse_args(line.split())
+
+ if opts is None:
+ return RC_ERR("bad command line parameters")
+
+ # determine stats mask
+ mask = self._get_mask_keys(**self._filter_namespace_args(opts, trex_stats.ALL_STATS_OPTS))
+ if not mask:
+ # set to show all stats if no filter was given
+ mask = trex_stats.ALL_STATS_OPTS
+
+ stats = self.cmd_stats(opts.ports, mask)
+
+ # print stats to screen
+ for stat_type, stat_data in stats.iteritems():
+ text_tables.print_table_with_header(stat_data.text_table, stat_type)
+
+
+ return RC_OK()
+
+
+
+ @timing
+ def cmd_validate_line (self, line):
+ '''validates port(s) stream configuration\n'''
+
+ parser = parsing_opts.gen_parser(self,
+ "validate",
+ self.cmd_validate_line.__doc__,
+ parsing_opts.PORT_LIST_WITH_ALL)
+
+ opts = parser.parse_args(line.split())
+ if opts is None:
+ return RC_ERR("bad command line paramters")
+
+ rc = self.cmd_validate(opts.ports)
+ return rc
+
+
+ def cmd_exit_line (self, line):
+ print format_text("Exiting\n", 'bold')
+ # a way to exit
+ return RC_ERR("exit")
+
- def _process_batch_result(self, req_list, resp_list, handler_func=None, success_test=default_success_test):
- for i, response in enumerate(resp_list):
- # testing each result with success test so that a conclusion report could be deployed in future.
- if success_test(response):
- # run handler method with its params
- handler_func(req_list[i], response)
+ def cmd_wait_line (self, line):
+ '''wait for a period of time\n'''
+
+ parser = parsing_opts.gen_parser(self,
+ "wait",
+ self.cmd_wait_line.__doc__,
+ parsing_opts.DURATION)
+
+ opts = parser.parse_args(line.split())
+ if opts is None:
+ return RC_ERR("bad command line parameters")
+
+ delay_sec = opts.duration if (opts.duration > 0) else 1
+
+ print format_text("Waiting for {0} seconds...\n".format(delay_sec), 'bold')
+ time.sleep(delay_sec)
+
+ return RC_OK()
+
+ # run a script of commands
+ def run_script_file (self, filename):
+
+ print format_text("\nRunning script file '{0}'...".format(filename), 'bold')
+
+ rc = self.cmd_connect()
+ if rc.bad():
+ return
+
+ with open(filename) as f:
+ script_lines = f.readlines()
+
+ cmd_table = {}
+
+ # register all the commands
+ cmd_table['start'] = self.cmd_start_line
+ cmd_table['stop'] = self.cmd_stop_line
+ cmd_table['reset'] = self.cmd_reset_line
+ cmd_table['wait'] = self.cmd_wait_line
+ cmd_table['exit'] = self.cmd_exit_line
+
+ for index, line in enumerate(script_lines, start = 1):
+ line = line.strip()
+ if line == "":
+ continue
+ if line.startswith("#"):
+ continue
+
+ sp = line.split(' ', 1)
+ cmd = sp[0]
+ if len(sp) == 2:
+ args = sp[1]
else:
- continue # TODO: mark in this case somehow the bad result
+ args = ""
+
+ print format_text("Executing line {0} : '{1}'\n".format(index, line))
+ if not cmd in cmd_table:
+ print "\n*** Error at line {0} : '{1}'\n".format(index, line)
+ print format_text("unknown command '{0}'\n".format(cmd), 'bold')
+ return False
+ rc = cmd_table[cmd](args)
+ if rc.bad():
+ return False
+
+ print format_text("\n[Done]", 'bold')
+
+ return True
+
+
+ #################################
+ # ------ private methods ------ #
+ @staticmethod
+ def _get_mask_keys(ok_values={True}, **kwargs):
+ masked_keys = set()
+ for key, val in kwargs.iteritems():
+ if val in ok_values:
+ masked_keys.add(key)
+ return masked_keys
+
+ @staticmethod
+ def _filter_namespace_args(namespace, ok_values):
+ return {k: v for k, v in namespace.__dict__.items() if k in ok_values}
+
+
+ #################################
# ------ private classes ------ #
- class CTxLink(object):
+ class CCommLink(object):
"""describes the connectivity of the stateless client method"""
def __init__(self, server="localhost", port=5050, virtual=False):
- super(CTRexStatelessClient.CTxLink, self).__init__()
+ super(CTRexStatelessClient.CCommLink, self).__init__()
self.virtual = virtual
self.server = server
self.port = port
+ self.verbose = False
self.rpc_link = JsonRpcClient(self.server, self.port)
+
+ @property
+ def is_connected(self):
+ if not self.virtual:
+ return self.rpc_link.connected
+ else:
+ return True
+
+ def get_server (self):
+ return self.server
+
+ def set_verbose(self, mode):
+ self.verbose = mode
+ return self.rpc_link.set_verbose(mode)
+
+ def connect(self):
+ if not self.virtual:
+ return self.rpc_link.connect()
+
+ def disconnect(self):
if not self.virtual:
- self.rpc_link.connect()
+ return self.rpc_link.disconnect()
def transmit(self, method_name, params={}):
if self.virtual:
@@ -352,144 +1203,5 @@ class CTRexStatelessClient(object):
port=self.port)
-class CStream(object):
- """docstring for CStream"""
- DEFAULTS = {"rx_stats": CRxStats,
- "mode": CTxMode,
- "isg": 5.0,
- "next_stream": -1,
- "self_start": True,
- "enabled": True}
-
- def __init__(self, **kwargs):
- super(CStream, self).__init__()
- for k, v in kwargs.items():
- setattr(self, k, v)
- # set default values to unset attributes, according to DEFAULTS dict
- set_keys = set(kwargs.keys())
- keys_to_set = [x
- for x in self.DEFAULTS
- if x not in set_keys]
- for key in keys_to_set:
- default = self.DEFAULTS.get(key)
- if type(default) == type:
- setattr(self, key, default())
- else:
- setattr(self, key, default)
-
- @property
- def packet(self):
- return self._packet
-
- @packet.setter
- def packet(self, packet_obj):
- assert isinstance(packet_obj, CTRexPktBuilder)
- self._packet = packet_obj
-
- @property
- def enabled(self):
- return self._enabled
-
- @enabled.setter
- def enabled(self, bool_value):
- self._enabled = bool(bool_value)
-
- @property
- def self_start(self):
- return self._self_start
-
- @self_start.setter
- def self_start(self, bool_value):
- self._self_start = bool(bool_value)
-
- @property
- def next_stream(self):
- return self._next_stream
-
- @next_stream.setter
- def next_stream(self, value):
- self._next_stream = int(value)
-
- def dump(self):
- pass
- return {"enabled": self.enabled,
- "self_start": self.self_start,
- "isg": self.isg,
- "next_stream": self.next_stream,
- "packet": self.packet.dump_pkt(),
- "mode": self.mode.dump(),
- "vm": self.packet.get_vm_data(),
- "rx_stats": self.rx_stats.dump()}
-
-class CRxStats(object):
-
- def __init__(self, enabled=False, seq_enabled=False, latency_enabled=False):
- self._rx_dict = {"enabled": enabled,
- "seq_enabled": seq_enabled,
- "latency_enabled": latency_enabled}
-
- @property
- def enabled(self):
- return self._rx_dict.get("enabled")
-
- @enabled.setter
- def enabled(self, bool_value):
- self._rx_dict['enabled'] = bool(bool_value)
-
- @property
- def seq_enabled(self):
- return self._rx_dict.get("seq_enabled")
-
- @seq_enabled.setter
- def seq_enabled(self, bool_value):
- self._rx_dict['seq_enabled'] = bool(bool_value)
-
- @property
- def latency_enabled(self):
- return self._rx_dict.get("latency_enabled")
-
- @latency_enabled.setter
- def latency_enabled(self, bool_value):
- self._rx_dict['latency_enabled'] = bool(bool_value)
-
- def dump(self):
- return {k: v
- for k, v in self._rx_dict.items()
- if v
- }
-
-
-class CTxMode(object):
- """docstring for CTxMode"""
- def __init__(self, tx_mode, pps):
- super(CTxMode, self).__init__()
- if tx_mode not in ["continuous", "single_burst", "multi_burst"]:
- raise ValueError("Unknown TX mode ('{0}')has been initialized.".format(tx_mode))
- self._tx_mode = tx_mode
- self._fields = {'pps': float(pps)}
- if tx_mode == "single_burst":
- self._fields['total_pkts'] = 0
- elif tx_mode == "multi_burst":
- self._fields['pkts_per_burst'] = 0
- self._fields['ibg'] = 0.0
- self._fields['count'] = 0
- else:
- pass
-
- def set_tx_mode_attr(self, attr, val):
- if attr in self._fields:
- self._fields[attr] = type(self._fields.get(attr))(val)
- else:
- raise ValueError("The provided attribute ('{0}') is not a legal attribute in selected TX mode ('{1}')".
- format(attr, self._tx_mode))
-
- def dump(self):
- dump = {"type": self._tx_mode}
- dump.update({k: v
- for k, v in self._fields.items()
- })
- return dump
-
-
if __name__ == "__main__":
pass
diff --git a/scripts/automation/trex_control_plane/client_utils/external_packages.py b/scripts/automation/trex_control_plane/client_utils/external_packages.py
index e2bb37a5..3c6eb449 100755
--- a/scripts/automation/trex_control_plane/client_utils/external_packages.py
+++ b/scripts/automation/trex_control_plane/client_utils/external_packages.py
@@ -9,7 +9,8 @@ PATH_TO_PYTHON_LIB = os.path.abspath(os.path.join(ROOT_PATH, os.pardir, os.pard
CLIENT_UTILS_MODULES = ['zmq',
'dpkt-1.8.6',
- 'PyYAML-3.01/lib'
+ 'PyYAML-3.01/lib',
+ 'texttable-0.8.4'
]
def import_client_utils_modules():
diff --git a/scripts/automation/trex_control_plane/client_utils/general_utils.py b/scripts/automation/trex_control_plane/client_utils/general_utils.py
index 5488b9dd..69ad14b2 100755
--- a/scripts/automation/trex_control_plane/client_utils/general_utils.py
+++ b/scripts/automation/trex_control_plane/client_utils/general_utils.py
@@ -24,7 +24,7 @@ def user_input():
def get_current_user():
if pwd:
- return pwd.getpwuid( os.geteuid() ).pw_name
+ return pwd.getpwuid(os.geteuid()).pw_name
else:
return getpass.getuser()
@@ -75,6 +75,22 @@ def random_id_gen(length=8):
return_id += random.choice(id_chars)
yield return_id
+def id_count_gen():
+ """
+ A generator for creating an increasing id for objects, starting from 0
+
+ :parameters:
+ None
+
+ :return:
+ an id (unsigned int) with each next() request.
+ """
+ return_id = 0
+ while True:
+ yield return_id
+ return_id += 1
+
+
if __name__ == "__main__":
pass
diff --git a/scripts/automation/trex_control_plane/client_utils/jsonrpc_client.py b/scripts/automation/trex_control_plane/client_utils/jsonrpc_client.py
index ed14e6f8..3de0bb5f 100755
--- a/scripts/automation/trex_control_plane/client_utils/jsonrpc_client.py
+++ b/scripts/automation/trex_control_plane/client_utils/jsonrpc_client.py
@@ -6,6 +6,9 @@ import json
import general_utils
import re
from time import sleep
+from collections import namedtuple
+
+CmdResponse = namedtuple('CmdResponse', ['success', 'data'])
class bcolors:
BLUE = '\033[94m'
@@ -23,22 +26,22 @@ class BatchMessage(object):
self.rpc_client = rpc_client
self.batch_list = []
- def add (self, method_name, params = {}):
+ def add (self, method_name, params={}):
id, msg = self.rpc_client.create_jsonrpc_v2(method_name, params, encode = False)
self.batch_list.append(msg)
- def invoke (self, block = False):
+ def invoke(self, block = False):
if not self.rpc_client.connected:
return False, "Not connected to server"
msg = json.dumps(self.batch_list)
- rc, resp_list = self.rpc_client.send_raw_msg(msg, block = False)
+ rc, resp_list = self.rpc_client.send_raw_msg(msg)
if len(self.batch_list) == 1:
- return True, [(rc, resp_list)]
+ return CmdResponse(True, [CmdResponse(rc, resp_list)])
else:
- return rc, resp_list
+ return CmdResponse(rc, resp_list)
# JSON RPC v2.0 client
@@ -47,7 +50,7 @@ class JsonRpcClient(object):
def __init__ (self, default_server, default_port):
self.verbose = False
self.connected = False
-
+
# default values
self.port = default_port
self.server = default_server
@@ -73,7 +76,7 @@ class JsonRpcClient(object):
# float
pretty_str = re.sub(r'([ ]*:[ ]+)(\-?[1-9][0-9]*\.[0-9]+)',r'\1{0}\2{1}'.format(bcolors.MAGENTA, bcolors.ENDC), pretty_str)
# strings
-
+
pretty_str = re.sub(r'([ ]*:[ ]+)("[^"]*")',r'\1{0}\2{1}'.format(bcolors.RED, bcolors.ENDC), pretty_str)
pretty_str = re.sub(r"('[^']*')", r'{0}\1{1}'.format(bcolors.MAGENTA, bcolors.RED), pretty_str)
except :
@@ -107,45 +110,43 @@ class JsonRpcClient(object):
return id, msg
- def invoke_rpc_method (self, method_name, params = {}, block = False):
+ def invoke_rpc_method (self, method_name, params = {}):
if not self.connected:
- return False, "Not connected to server"
+ return CmdResponse(False, "Not connected to server")
id, msg = self.create_jsonrpc_v2(method_name, params)
- return self.send_raw_msg(msg, block)
+ return self.send_raw_msg(msg)
+
-
# low level send of string message
- def send_raw_msg (self, msg, block = False):
+ def send_raw_msg (self, msg):
+
self.verbose_msg("Sending Request To Server:\n\n" + self.pretty_json(msg) + "\n")
- if block:
- self.socket.send(msg)
- else:
+ tries = 0
+ while True:
try:
- self.socket.send(msg, flags = zmq.NOBLOCK)
- except zmq.error.ZMQError as e:
- self.disconnect()
- return False, "Failed To Get Send Message"
+ self.socket.send(msg)
+ break
+ except zmq.Again:
+ tries += 1
+ if tries > 10:
+ self.disconnect()
+ return CmdResponse(False, "*** [RPC] - Failed to send message to server")
- got_response = False
- if block:
- response = self.socket.recv()
- got_response = True
- else:
- for i in xrange(0 ,10):
- try:
- response = self.socket.recv(flags = zmq.NOBLOCK)
- got_response = True
- break
- except zmq.Again:
- sleep(0.2)
-
- if not got_response:
- self.disconnect()
- return False, "Failed To Get Server Response"
+ tries = 0
+ while True:
+ try:
+ response = self.socket.recv()
+ break
+ except zmq.Again:
+ tries += 1
+ if tries > 10:
+ self.disconnect()
+ return CmdResponse(False, "*** [RPC] - Failed to get server response")
+
self.verbose_msg("Server Response:\n\n" + self.pretty_json(response) + "\n")
@@ -159,19 +160,19 @@ class JsonRpcClient(object):
for single_response in response_json:
rc, msg = self.process_single_response(single_response)
- rc_list.append( (rc, msg) )
+ rc_list.append( CmdResponse(rc, msg) )
- return True, rc_list
+ return CmdResponse(True, rc_list)
else:
rc, msg = self.process_single_response(response_json)
- return rc, msg
+ return CmdResponse(rc, msg)
def process_single_response (self, response_json):
if (response_json.get("jsonrpc") != "2.0"):
- return False, "Malfromed Response ({0})".format(str(response))
+ return False, "Malformed Response ({0})".format(str(response_json))
# error reported by server
if ("error" in response_json):
@@ -182,7 +183,7 @@ class JsonRpcClient(object):
# if no error there should be a result
if ("result" not in response_json):
- return False, "Malfromed Response ({0})".format(str(response))
+ return False, "Malformed Response ({0})".format(str(response_json))
return True, response_json["result"]
@@ -200,7 +201,7 @@ class JsonRpcClient(object):
else:
return False, "Not connected to server"
- def connect(self, server = None, port = None):
+ def connect(self, server=None, port=None):
if self.connected:
self.disconnect()
@@ -220,6 +221,8 @@ class JsonRpcClient(object):
except zmq.error.ZMQError as e:
return False, "ZMQ Error: Bad server or port name: " + str(e)
+ self.socket.setsockopt(zmq.SNDTIMEO, 1000)
+ self.socket.setsockopt(zmq.RCVTIMEO, 1000)
self.connected = True
@@ -245,269 +248,3 @@ class JsonRpcClient(object):
if hasattr(self, "context"):
self.context.destroy(linger=0)
-# MOVE THIS TO DAN'S FILE
-class TrexStatelessClient(JsonRpcClient):
-
- def __init__ (self, server, port, user):
-
- super(TrexStatelessClient, self).__init__(server, port)
-
- self.user = user
- self.port_handlers = {}
-
- self.supported_cmds = []
- self.system_info = None
- self.server_version = None
-
-
- def whoami (self):
- return self.user
-
- def ping_rpc_server(self):
-
- return self.invoke_rpc_method("ping", block = False)
-
- def get_rpc_server_version (self):
- return self.server_version
-
- def get_system_info (self):
- if not self.system_info:
- return {}
-
- return self.system_info
-
- def get_supported_cmds(self):
- if not self.supported_cmds:
- return {}
-
- return self.supported_cmds
-
- def get_port_count (self):
- if not self.system_info:
- return 0
-
- return self.system_info["port_count"]
-
- # sync the client with all the server required data
- def sync (self):
-
- # get server version
- rc, msg = self.invoke_rpc_method("get_version")
- if not rc:
- self.disconnect()
- return rc, msg
-
- self.server_version = msg
-
- # get supported commands
- rc, msg = self.invoke_rpc_method("get_supported_cmds")
- if not rc:
- self.disconnect()
- return rc, msg
-
- self.supported_cmds = [str(x) for x in msg if x]
-
- # get system info
- rc, msg = self.invoke_rpc_method("get_system_info")
- if not rc:
- self.disconnect()
- return rc, msg
-
- self.system_info = msg
-
- return True, ""
-
- def connect (self):
- rc, err = super(TrexStatelessClient, self).connect()
- if not rc:
- return rc, err
-
- return self.sync()
-
-
- # take ownership over ports
- def take_ownership (self, port_id_array, force = False):
- if not self.connected:
- return False, "Not connected to server"
-
- batch = self.create_batch()
-
- for port_id in port_id_array:
- batch.add("acquire", params = {"port_id":port_id, "user":self.user, "force":force})
-
- rc, resp_list = batch.invoke()
- if not rc:
- return rc, resp_list
-
- for i, rc in enumerate(resp_list):
- if rc[0]:
- self.port_handlers[port_id_array[i]] = rc[1]
-
- return True, resp_list
-
-
- def release_ports (self, port_id_array):
- batch = self.create_batch()
-
- for port_id in port_id_array:
-
- # let the server handle un-acquired errors
- if self.port_handlers.get(port_id):
- handler = self.port_handlers[port_id]
- else:
- handler = ""
-
- batch.add("release", params = {"port_id":port_id, "handler":handler})
-
-
- rc, resp_list = batch.invoke()
- if not rc:
- return rc, resp_list
-
- for i, rc in enumerate(resp_list):
- if rc[0]:
- self.port_handlers.pop(port_id_array[i])
-
- return True, resp_list
-
- def get_owned_ports (self):
- return self.port_handlers.keys()
-
- # fetch port stats
- def get_port_stats (self, port_id_array):
- if not self.connected:
- return False, "Not connected to server"
-
- batch = self.create_batch()
-
- # empty list means all
- if port_id_array == []:
- port_id_array = list([x for x in xrange(0, self.system_info["port_count"])])
-
- for port_id in port_id_array:
-
- # let the server handle un-acquired errors
- if self.port_handlers.get(port_id):
- handler = self.port_handlers[port_id]
- else:
- handler = ""
-
- batch.add("get_port_stats", params = {"port_id":port_id, "handler":handler})
-
-
- rc, resp_list = batch.invoke()
-
- return rc, resp_list
-
- # snapshot will take a snapshot of all your owned ports for streams and etc.
- def snapshot(self):
-
-
- if len(self.get_owned_ports()) == 0:
- return {}
-
- snap = {}
-
- batch = self.create_batch()
-
- for port_id in self.get_owned_ports():
-
- batch.add("get_port_stats", params = {"port_id": port_id, "handler": self.port_handlers[port_id]})
- batch.add("get_stream_list", params = {"port_id": port_id, "handler": self.port_handlers[port_id]})
-
- rc, resp_list = batch.invoke()
- if not rc:
- return rc, resp_list
-
- # split the list to 2s
- index = 0
- for port_id in self.get_owned_ports():
- if not resp_list[index] or not resp_list[index + 1]:
- snap[port_id] = None
- continue
-
- # fetch the first two
- stats = resp_list[index][1]
- stream_list = resp_list[index + 1][1]
-
- port = {}
- port['status'] = stats['status']
- port['stream_list'] = []
-
- # get all the streams
- if len(stream_list) > 0:
- batch = self.create_batch()
- for stream_id in stream_list:
- batch.add("get_stream", params = {"port_id": port_id, "stream_id": stream_id, "handler": self.port_handlers[port_id]})
-
- rc, stream_resp_list = batch.invoke()
- if not rc:
- port = {}
-
- port['streams'] = {}
- for i, resp in enumerate(stream_resp_list):
- if resp[0]:
- port['streams'][stream_list[i]] = resp[1]
-
- snap[port_id] = port
-
- # move to next one
- index += 2
-
-
- return snap
-
- # add stream
- # def add_stream (self, port_id, stream_id, isg, next_stream_id, packet, vm=[]):
- # if not port_id in self.get_owned_ports():
- # return False, "Port {0} is not owned... please take ownership before adding streams".format(port_id)
- #
- # handler = self.port_handlers[port_id]
- #
- # stream = {}
- # stream['enabled'] = True
- # stream['self_start'] = True
- # stream['isg'] = isg
- # stream['next_stream_id'] = next_stream_id
- # stream['packet'] = {}
- # stream['packet']['binary'] = packet
- # stream['packet']['meta'] = ""
- # stream['vm'] = vm
- # stream['rx_stats'] = {}
- # stream['rx_stats']['enabled'] = False
- #
- # stream['mode'] = {}
- # stream['mode']['type'] = 'continuous'
- # stream['mode']['pps'] = 10.0
- #
- # params = {}
- # params['handler'] = handler
- # params['stream'] = stream
- # params['port_id'] = port_id
- # params['stream_id'] = stream_id
- #
- # print params
- # return self.invoke_rpc_method('add_stream', params = params)
-
- def add_stream(self, port_id_array, stream_pack_list):
- batch = self.create_batch()
-
- for port_id in port_id_array:
- for stream_pack in stream_pack_list:
- params = {"port_id": port_id,
- "handler": self.port_handlers[port_id],
- "stream_id": stream_pack.stream_id,
- "stream": stream_pack.stream}
- batch.add("add_stream", params=params)
- rc, resp_list = batch.invoke()
- if not rc:
- return rc, resp_list
-
- for i, rc in enumerate(resp_list):
- if rc[0]:
- print "Stream {0} - {1}".format(i, rc[1])
- # self.port_handlers[port_id_array[i]] = rc[1]
-
- return True, resp_list
-
- # return self.invoke_rpc_method('add_stream', params = params)
diff --git a/scripts/automation/trex_control_plane/client_utils/parsing_opts.py b/scripts/automation/trex_control_plane/client_utils/parsing_opts.py
new file mode 100755
index 00000000..7ac9e312
--- /dev/null
+++ b/scripts/automation/trex_control_plane/client_utils/parsing_opts.py
@@ -0,0 +1,304 @@
+import argparse
+from collections import namedtuple
+import sys
+import re
+import os
+
+ArgumentPack = namedtuple('ArgumentPack', ['name_or_flags', 'options'])
+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
+TOTAL = 13
+
+GLOBAL_STATS = 14
+PORT_STATS = 15
+PORT_STATUS = 16
+STATS_MASK = 17
+
+# list of ArgumentGroup types
+MUTEX = 1
+
+def check_negative(value):
+ ivalue = int(value)
+ if ivalue < 0:
+ raise argparse.ArgumentTypeError("non positive value provided: '{0}'".format(value))
+ return ivalue
+
+def match_time_unit(val):
+ '''match some val against time shortcut inputs '''
+ match = re.match("^(\d+)([m|h]?)$", val)
+ if match:
+ digit = int(match.group(1))
+ unit = match.group(2)
+ if not unit:
+ return digit
+ elif unit == 'm':
+ return digit*60
+ else:
+ return digit*60*60
+ else:
+ raise argparse.ArgumentTypeError("Duration should be passed in the following format: \n"
+ "-d 100 : in sec \n"
+ "-d 10m : in min \n"
+ "-d 1h : in hours")
+
+match_multiplier_help = """Multiplier should be passed in the following format:
+ [number][<empty> | bps | kbps | mbps | gbps | pps | kpps | mpps | %% ].
+ no suffix will provide an absoulute factor and percentage
+ will provide a percentage of the line rate. examples
+ : '-m 10', '-m 10kbps', '-m 10mpps', '-m 23%%' """
+
+def match_multiplier_common(val, strict_abs = True):
+
+ # on strict absolute we do not allow +/-
+ if strict_abs:
+ match = re.match("^(\d+(\.\d+)?)(bps|kbps|mbps|gbps|pps|kpps|mpps|%?)$", val)
+ op = None
+ else:
+ match = re.match("^(\d+(\.\d+)?)(bps|kbps|mbps|gbps|pps|kpps|mpps|%?)([\+\-])?$", val)
+ op = match.group(4)
+
+ result = {}
+
+ if match:
+
+ value = float(match.group(1))
+ unit = match.group(3)
+
+
+
+ # raw type (factor)
+ if not unit:
+ result['type'] = 'raw'
+ result['value'] = value
+
+ elif unit == 'bps':
+ result['type'] = 'bps'
+ result['value'] = value
+
+ elif unit == 'kbps':
+ result['type'] = 'bps'
+ result['value'] = value * 1000
+
+ elif unit == 'mbps':
+ result['type'] = 'bps'
+ result['value'] = value * 1000 * 1000
+
+ elif unit == 'gbps':
+ result['type'] = 'bps'
+ result['value'] = value * 1000 * 1000 * 1000
+
+ elif unit == 'pps':
+ result['type'] = 'pps'
+ result['value'] = value
+
+ elif unit == "kpps":
+ result['type'] = 'pps'
+ result['value'] = value * 1000
+
+ elif unit == "mpps":
+ result['type'] = 'pps'
+ result['value'] = value * 1000 * 1000
+
+ elif unit == "%":
+ result['type'] = 'percentage'
+ result['value'] = value
+
+
+ if op == "+":
+ result['op'] = "add"
+ elif op == "-":
+ result['op'] = "sub"
+ else:
+ result['op'] = "abs"
+
+ return result
+
+ else:
+ raise argparse.ArgumentTypeError(match_multiplier_help)
+
+
+def match_multiplier(val):
+ '''match some val against multiplier shortcut inputs '''
+ return match_multiplier_common(val, strict_abs = False)
+
+def match_multiplier_strict(val):
+ '''match some val against multiplier shortcut inputs '''
+ return match_multiplier_common(val, strict_abs = True)
+
+def is_valid_file(filename):
+ if not os.path.isfile(filename):
+ raise argparse.ArgumentTypeError("The file '%s' does not exist" % filename)
+
+ return filename
+
+
+OPTIONS_DB = {MULTIPLIER: ArgumentPack(['-m', '--multiplier'],
+ {'help': match_multiplier_help,
+ 'dest': "mult",
+ 'default': {'type':'raw', 'value':1, 'op': 'abs'},
+ 'type': match_multiplier}),
+
+ MULTIPLIER_STRICT: ArgumentPack(['-m', '--multiplier'],
+ {'help': match_multiplier_help,
+ 'dest': "mult",
+ 'default': {'type':'raw', 'value':1, 'op': 'abs'},
+ 'type': match_multiplier_strict}),
+
+ TOTAL: ArgumentPack(['-t', '--total'],
+ {'help': "traffic will be divided between all ports specified",
+ 'dest': "total",
+ 'default': False,
+ 'action': "store_true"}),
+
+ PORT_LIST: ArgumentPack(['--port'],
+ {"nargs": '+',
+ 'dest':'ports',
+ 'metavar': 'PORTS',
+ 'type': int,
+ 'help': "A list of ports on which to apply the command",
+ 'default': []}),
+
+ ALL_PORTS: ArgumentPack(['-a'],
+ {"action": "store_true",
+ "dest": "all_ports",
+ 'help': "Set this flag to apply the command on all available ports"}),
+ DURATION: ArgumentPack(['-d'],
+ {'action': "store",
+ 'metavar': 'TIME',
+ 'dest': 'duration',
+ 'type': match_time_unit,
+ 'default': -1.0,
+ 'help': "Set duration time for TRex."}),
+
+ FORCE: ArgumentPack(['--force'],
+ {"action": "store_true",
+ 'default': False,
+ 'help': "Set if you want to stop active ports before applying new TRex run on them."}),
+
+ FILE_PATH: ArgumentPack(['-f'],
+ {'metavar': 'FILE',
+ 'dest': 'file',
+ 'nargs': 1,
+ 'type': is_valid_file,
+ 'help': "File path to YAML file that describes a stream pack. "}),
+
+ FILE_FROM_DB: ArgumentPack(['--db'],
+ {'metavar': 'LOADED_STREAM_PACK',
+ 'help': "A stream pack which already loaded into console cache."}),
+
+ SERVER_IP: ArgumentPack(['--server'],
+ {'metavar': 'SERVER',
+ 'help': "server IP"}),
+
+ DRY_RUN: ArgumentPack(['-n', '--dry'],
+ {'action': 'store_true',
+ 'dest': 'dry',
+ 'default': False,
+ 'help': "Dry run - no traffic will be injected"}),
+
+ GLOBAL_STATS: ArgumentPack(['-g'],
+ {'action': 'store_true',
+ 'help': "Fetch only global statistics"}),
+
+ PORT_STATS: ArgumentPack(['-p'],
+ {'action': 'store_true',
+ 'help': "Fetch only port statistics"}),
+
+ PORT_STATUS: ArgumentPack(['--ps'],
+ {'action': 'store_true',
+ 'help': "Fetch only port status data"}),
+
+
+ # advanced options
+ PORT_LIST_WITH_ALL: ArgumentGroup(MUTEX, [PORT_LIST,
+ ALL_PORTS],
+ {'required': True}),
+ STREAM_FROM_PATH_OR_FILE: ArgumentGroup(MUTEX, [FILE_PATH,
+ FILE_FROM_DB],
+ {'required': True}),
+ STATS_MASK: ArgumentGroup(MUTEX, [GLOBAL_STATS,
+ PORT_STATS,
+ PORT_STATUS],
+ {})
+ }
+
+
+class CCmdArgParser(argparse.ArgumentParser):
+
+ def __init__(self, stateless_client, *args, **kwargs):
+ super(CCmdArgParser, self).__init__(*args, **kwargs)
+ self.stateless_client = stateless_client
+
+ def parse_args(self, args=None, namespace=None):
+ try:
+ opts = super(CCmdArgParser, self).parse_args(args, namespace)
+ if opts is None:
+ return None
+
+ if getattr(opts, "all_ports", None):
+ opts.ports = self.stateless_client.get_port_ids()
+
+ if getattr(opts, "ports", None):
+ for port in opts.ports:
+ if not self.stateless_client.validate_port_list([port]):
+ self.error("port id '{0}' is not a valid port id\n".format(port))
+
+ return opts
+
+ except SystemExit:
+ # recover from system exit scenarios, such as "help", or bad arguments.
+ return None
+
+
+def get_flags (opt):
+ return OPTIONS_DB[opt].name_or_flags
+
+def gen_parser(stateless_client, op_name, description, *args):
+ parser = CCmdArgParser(stateless_client, prog=op_name, conflict_handler='resolve',
+ description=description)
+ for param in args:
+ try:
+
+ if isinstance(param, int):
+ argument = OPTIONS_DB[param]
+ else:
+ argument = param
+
+ if isinstance(argument, ArgumentGroup):
+ if argument.type == MUTEX:
+ # handle as mutually exclusive group
+ group = parser.add_mutually_exclusive_group(**argument.options)
+ for sub_argument in argument.args:
+ group.add_argument(*OPTIONS_DB[sub_argument].name_or_flags,
+ **OPTIONS_DB[sub_argument].options)
+ else:
+ # ignore invalid objects
+ continue
+ elif isinstance(argument, ArgumentPack):
+ parser.add_argument(*argument.name_or_flags,
+ **argument.options)
+ else:
+ # ignore invalid objects
+ continue
+ except KeyError as e:
+ cause = e.args[0]
+ raise KeyError("The attribute '{0}' is missing as a field of the {1} option.\n".format(cause, param))
+ return parser
+
+
+if __name__ == "__main__":
+ pass \ No newline at end of file
diff --git a/scripts/automation/trex_control_plane/client_utils/text_tables.py b/scripts/automation/trex_control_plane/client_utils/text_tables.py
new file mode 100644
index 00000000..2debca38
--- /dev/null
+++ b/scripts/automation/trex_control_plane/client_utils/text_tables.py
@@ -0,0 +1,34 @@
+
+import external_packages
+from texttable import Texttable
+from common.text_opts import format_text
+
+class TRexTextTable(Texttable):
+
+ def __init__(self):
+ Texttable.__init__(self)
+ # set class attributes so that it'll be more like TRex standard output
+ self.set_chars(['-', '|', '-', '-'])
+ self.set_deco(Texttable.HEADER | Texttable.VLINES)
+
+class TRexTextInfo(Texttable):
+
+ def __init__(self):
+ Texttable.__init__(self)
+ # set class attributes so that it'll be more like TRex standard output
+ self.set_chars(['-', ':', '-', '-'])
+ self.set_deco(Texttable.VLINES)
+
+def generate_trex_stats_table():
+ pass
+
+def print_table_with_header(texttable_obj, header=""):
+ header = header.replace("_", " ").title()
+ print format_text(header, 'cyan', 'underline') + "\n"
+ print texttable_obj.draw() + "\n"
+
+ pass
+
+if __name__ == "__main__":
+ pass
+
diff --git a/scripts/automation/trex_control_plane/common/text_opts.py b/scripts/automation/trex_control_plane/common/text_opts.py
new file mode 100755
index 00000000..5a86149c
--- /dev/null
+++ b/scripts/automation/trex_control_plane/common/text_opts.py
@@ -0,0 +1,144 @@
+import json
+import re
+
+TEXT_CODES = {'bold': {'start': '\x1b[1m',
+ 'end': '\x1b[22m'},
+ 'cyan': {'start': '\x1b[36m',
+ 'end': '\x1b[39m'},
+ 'blue': {'start': '\x1b[34m',
+ 'end': '\x1b[39m'},
+ 'red': {'start': '\x1b[31m',
+ 'end': '\x1b[39m'},
+ 'magenta': {'start': '\x1b[35m',
+ 'end': '\x1b[39m'},
+ 'green': {'start': '\x1b[32m',
+ 'end': '\x1b[39m'},
+ 'yellow': {'start': '\x1b[33m',
+ 'end': '\x1b[39m'},
+ 'underline': {'start': '\x1b[4m',
+ 'end': '\x1b[24m'}}
+
+
+def format_num (size, suffix = ""):
+ for unit in ['','K','M','G','T','P']:
+ if abs(size) < 1000.0:
+ return "%3.2f %s%s" % (size, unit, suffix)
+ size /= 1000.0
+
+ return "NaN"
+
+def format_time (t_sec):
+ if t_sec < 0:
+ return "infinite"
+
+ if t_sec < 1:
+ # low numbers
+ for unit in ['ms', 'usec', 'ns']:
+ t_sec *= 1000.0
+ if t_sec >= 1.0:
+ return '{:,.2f} [{:}]'.format(t_sec, unit)
+
+ return "NaN"
+
+ else:
+ # seconds
+ if t_sec < 60.0:
+ return '{:,.2f} [{:}]'.format(t_sec, 'sec')
+
+ # minutes
+ t_sec /= 60.0
+ if t_sec < 60.0:
+ return '{:,.2f} [{:}]'.format(t_sec, 'minutes')
+
+ # hours
+ t_sec /= 60.0
+ if t_sec < 24.0:
+ return '{:,.2f} [{:}]'.format(t_sec, 'hours')
+
+ # days
+ t_sec /= 24.0
+ return '{:,.2f} [{:}]'.format(t_sec, 'days')
+
+
+def format_percentage (size):
+ return "%0.2f %%" % (size)
+
+def bold(text):
+ return text_attribute(text, 'bold')
+
+
+def cyan(text):
+ return text_attribute(text, 'cyan')
+
+
+def blue(text):
+ return text_attribute(text, 'blue')
+
+
+def red(text):
+ return text_attribute(text, 'red')
+
+
+def magenta(text):
+ return text_attribute(text, 'magenta')
+
+
+def green(text):
+ return text_attribute(text, 'green')
+
+def yellow(text):
+ return text_attribute(text, 'yellow')
+
+def underline(text):
+ return text_attribute(text, 'underline')
+
+
+def text_attribute(text, attribute):
+ return "{start}{txt}{stop}".format(start=TEXT_CODES[attribute]['start'],
+ txt=text,
+ stop=TEXT_CODES[attribute]['end'])
+
+
+FUNC_DICT = {'blue': blue,
+ 'bold': bold,
+ 'green': green,
+ 'yellow': yellow,
+ 'cyan': cyan,
+ 'magenta': magenta,
+ 'underline': underline,
+ 'red': red}
+
+
+def format_text(text, *args):
+ return_string = text
+ for i in args:
+ func = FUNC_DICT.get(i)
+ if func:
+ return_string = func(return_string)
+ return return_string
+
+# pretty print for JSON
+def pretty_json (json_str, use_colors = True):
+ pretty_str = json.dumps(json.loads(json_str), indent = 4, separators=(',', ': '), sort_keys = True)
+
+ if not use_colors:
+ return pretty_str
+
+ try:
+ # int numbers
+ pretty_str = re.sub(r'([ ]*:[ ]+)(\-?[1-9][0-9]*[^.])',r'\1{0}'.format(blue(r'\2')), pretty_str)
+ # float
+ pretty_str = re.sub(r'([ ]*:[ ]+)(\-?[1-9][0-9]*\.[0-9]+)',r'\1{0}'.format(magenta(r'\2')), pretty_str)
+ # # strings
+ #
+ pretty_str = re.sub(r'([ ]*:[ ]+)("[^"]*")',r'\1{0}'.format(red(r'\2')), pretty_str)
+ pretty_str = re.sub(r"('[^']*')", r'{0}\1{1}'.format(TEXT_CODES['magenta']['start'],
+ TEXT_CODES['red']['start']), pretty_str)
+ except :
+ pass
+
+ return pretty_str
+
+
+if __name__ == "__main__":
+ pass
diff --git a/scripts/automation/trex_control_plane/common/trex_stats.py b/scripts/automation/trex_control_plane/common/trex_stats.py
index b7e768c1..2f6ea38d 100755
--- a/scripts/automation/trex_control_plane/common/trex_stats.py
+++ b/scripts/automation/trex_control_plane/common/trex_stats.py
@@ -1,59 +1,299 @@
#!/router/bin/python
+from collections import namedtuple, OrderedDict
+from client_utils import text_tables
+from common.text_opts import format_text
+from client.trex_async_client import CTRexAsyncStats
import copy
+import datetime
+import time
+import re
+GLOBAL_STATS = 'g'
+PORT_STATS = 'p'
+PORT_STATUS = 'ps'
+ALL_STATS_OPTS = {GLOBAL_STATS, PORT_STATS, PORT_STATUS}
+COMPACT = {GLOBAL_STATS, PORT_STATS}
-class CTRexStatsManager(object):
+ExportableStats = namedtuple('ExportableStats', ['raw_data', 'text_table'])
- def __init__(self, *args):
- for stat_type in args:
- # register stat handler for each stats type
- setattr(self, stat_type, CTRexStatsManager.CSingleStatsHandler())
- def __getitem__(self, item):
- stats_obj = getattr(self, item)
- if stats_obj:
- return stats_obj.get_stats()
+class CTRexStatsGenerator(object):
+ """
+ This object is responsible of generating stats from objects maintained at
+ CTRexStatelessClient and the ports.
+ """
+
+ def __init__(self, global_stats_ref, ports_dict_ref):
+ self._global_stats = global_stats_ref
+ self._ports_dict = ports_dict_ref
+
+ def generate_single_statistic(self, port_id_list, statistic_type):
+ if statistic_type == GLOBAL_STATS:
+ return self._generate_global_stats()
+ elif statistic_type == PORT_STATS:
+ return self._generate_port_stats(port_id_list)
+ pass
+ elif statistic_type == PORT_STATUS:
+ return self._generate_port_status(port_id_list)
else:
- return None
+ # ignore by returning empty object
+ return {}
- class CSingleStatsHandler(object):
+ def _generate_global_stats(self):
+ # stats_obj = self._async_stats.get_general_stats()
+ stats_data = self._global_stats.generate_stats()
- def __init__(self):
- self._stats = {}
+ # build table representation
+ stats_table = text_tables.TRexTextInfo()
+ stats_table.set_cols_align(["l", "l"])
+ stats_table.add_rows([[k.replace("_", " ").title(), v]
+ for k, v in stats_data.iteritems()],
+ header=False)
+
+ return {"global_statistics": ExportableStats(stats_data, stats_table)}
+
+ def _generate_port_stats(self, port_id_list):
+ relevant_ports = self.__get_relevant_ports(port_id_list)
+
+ return_stats_data = {}
+ per_field_stats = OrderedDict([("owner", []),
+ ("state", []),
+ ("--", []),
+ ("opackets", []),
+ ("obytes", []),
+ ("ipackets", []),
+ ("ibytes", []),
+ ("ierrors", []),
+ ("oerrors", []),
+ ("tx-bytes", []),
+ ("rx-bytes", []),
+ ("tx-pkts", []),
+ ("rx-pkts", []),
+ ("---", []),
+ ("Tx bps", []),
+ ("Rx bps", []),
+ ("----", []),
+ ("Tx pps", []),
+ ("Rx pps", [])
+ ]
+ )
+
+ for port_obj in relevant_ports:
+ # fetch port data
+ port_stats = port_obj.generate_port_stats()
+
+ # populate to data structures
+ return_stats_data[port_obj.port_id] = port_stats
+ self.__update_per_field_dict(port_stats, per_field_stats)
+
+ stats_table = text_tables.TRexTextTable()
+ stats_table.set_cols_align(["l"] + ["r"]*len(relevant_ports))
+ stats_table.set_cols_width([10] + [20] * len(relevant_ports))
+ stats_table.set_cols_dtype(['t'] + ['t'] * len(relevant_ports))
+
+ stats_table.add_rows([[k] + v
+ for k, v in per_field_stats.iteritems()],
+ header=False)
+ stats_table.header(["port"] + [port.port_id
+ for port in relevant_ports])
+
+ return {"port_statistics": ExportableStats(return_stats_data, stats_table)}
+
+ def _generate_port_status(self, port_id_list):
+ relevant_ports = self.__get_relevant_ports(port_id_list)
+
+ return_stats_data = {}
+ per_field_status = OrderedDict([("port-type", []),
+ ("maximum", []),
+ ("port-status", [])
+ ]
+ )
+
+ for port_obj in relevant_ports:
+ # fetch port data
+ # port_stats = self._async_stats.get_port_stats(port_obj.port_id)
+ port_status = port_obj.generate_port_status()
+
+ # populate to data structures
+ return_stats_data[port_obj.port_id] = port_status
+
+ self.__update_per_field_dict(port_status, per_field_status)
+
+ stats_table = text_tables.TRexTextTable()
+ stats_table.set_cols_align(["l"] + ["c"]*len(relevant_ports))
+ stats_table.set_cols_width([10] + [20] * len(relevant_ports))
+
+ stats_table.add_rows([[k] + v
+ for k, v in per_field_status.iteritems()],
+ header=False)
+ stats_table.header(["port"] + [port.port_id
+ for port in relevant_ports])
+
+ return {"port_status": ExportableStats(return_stats_data, stats_table)}
+
+ def __get_relevant_ports(self, port_id_list):
+ # fetch owned ports
+ ports = [port_obj
+ for _, port_obj in self._ports_dict.iteritems()
+ if port_obj.port_id in port_id_list]
+
+ # display only the first FOUR options, by design
+ if len(ports) > 4:
+ print format_text("[WARNING]: ", 'magenta', 'bold'), format_text("displaying up to 4 ports", 'magenta')
+ ports = ports[:4]
+ return ports
+
+ def __update_per_field_dict(self, dict_src_data, dict_dest_ref):
+ for key, val in dict_src_data.iteritems():
+ if key in dict_dest_ref:
+ dict_dest_ref[key].append(val)
- def update(self, obj_id, stats_obj):
- assert isinstance(stats_obj, CTRexStats)
- self._stats[obj_id] = stats_obj
- def get_stats(self, obj_id=None):
- if obj_id:
- return copy.copy(self._stats.pop(obj_id))
- else:
- return copy.copy(self._stats)
class CTRexStats(object):
- def __init__(self, **kwargs):
- for k, v in kwargs.items():
- setattr(self, k, v)
+ """ This is an abstract class to represent a stats object """
+
+ def __init__(self):
+ self.reference_stats = None
+ self.latest_stats = {}
+ self.last_update_ts = time.time()
+
+
+ def __getitem__(self, item):
+ # override this to allow quick and clean access to fields
+ if not item in self.latest_stats:
+ return "N/A"
+
+ # item must exist
+ m = re.search('_(([a-z])ps)$', item)
+ if m:
+ # this is a non-relative item
+ unit = m.group(2)
+ if unit == "b":
+ return self.get(item, format=True, suffix="b/sec")
+ elif unit == "p":
+ return self.get(item, format=True, suffix="pkt/sec")
+ else:
+ return self.get(item, format=True, suffix=m.group(1))
+
+ m = re.search('^[i|o](a-z+)$', item)
+ if m:
+ # this is a non-relative item
+ type = m.group(1)
+ if type == "bytes":
+ return self.get_rel(item, format=True, suffix="B")
+ elif type == "packets":
+ return self.get_rel(item, format=True, suffix="pkts")
+ else:
+ # do not format with suffix
+ return self.get_rel(item, format=True)
+
+ # can't match to any known pattern, return N/A
+ return "N/A"
+
+ @staticmethod
+ def format_num(size, suffix = ""):
+ for unit in ['','K','M','G','T','P']:
+ if abs(size) < 1000.0:
+ return "%3.2f %s%s" % (size, unit, suffix)
+ size /= 1000.0
+ return "NaN"
+
+ def generate_stats(self):
+ # must be implemented by designated classes (such as port/ global stats)
+ raise NotImplementedError()
+
+ def update(self, snapshot):
+ # update
+ self.latest_stats = snapshot
+
+ diff_time = time.time() - self.last_update_ts
+
+ # 3 seconds is too much - this is the new reference
+ if (self.reference_stats == None) or (diff_time > 3):
+ self.reference_stats = self.latest_stats
+
+ self.last_update_ts = time.time()
+
+ def clear_stats(self):
+ self.reference_stats = self.latest_stats
+
+ def get(self, field, format=False, suffix=""):
+ if not field in self.latest_stats:
+ return "N/A"
+ if not format:
+ return self.latest_stats[field]
+ else:
+ return self.format_num(self.latest_stats[field], suffix)
+
+ def get_rel(self, field, format=False, suffix=""):
+ if not field in self.latest_stats:
+ return "N/A"
+
+ if not format:
+ return (self.latest_stats[field] - self.reference_stats[field])
+ else:
+ return self.format_num(self.latest_stats[field] - self.reference_stats[field], suffix)
class CGlobalStats(CTRexStats):
- def __init__(self, **kwargs):
- super(CGlobalStats, self).__init__(kwargs)
- pass
+ pass
+
+ def __init__(self, connection_info, server_version, ports_dict_ref):
+ super(CGlobalStats, self).__init__()
+ self.connection_info = connection_info
+ self.server_version = server_version
+ self._ports_dict = ports_dict_ref
+ def generate_stats(self):
+ return OrderedDict([("connection", "{host}, Port {port}".format(host=self.connection_info.get("server"),
+ port=self.connection_info.get("sync_port"))),
+ ("version", "{ver}, UUID: {uuid}".format(ver=self.server_version.get("version", "N/A"),
+ uuid="N/A")),
+ ("cpu_util", "{0}%".format(self.get("m_cpu_util"))),
+ ("total_tx", self.get("m_tx_bps", format=True, suffix="b/sec")),
+ ("total_rx", self.get("m_rx_bps", format=True, suffix="b/sec")),
+ ("total_pps", self.format_num(self.get("m_tx_pps") + self.get("m_rx_pps"),
+ suffix="pkt/sec")),
+ ("total_streams", sum([len(port_obj.streams)
+ for _, port_obj in self._ports_dict.iteritems()])),
+ ("active_ports", sum([port_obj.is_active()
+ for _, port_obj in self._ports_dict.iteritems()]))
+ ]
+ )
class CPortStats(CTRexStats):
- def __init__(self, **kwargs):
- super(CPortStats, self).__init__(kwargs)
- pass
+ pass
+
+ def __init__(self, port_obj):
+ super(CPortStats, self).__init__()
+ self._port_obj = port_obj
+
+ def generate_stats(self):
+ return {"owner": self._port_obj.user,
+ "state": self._port_obj.get_port_state_name(),
+ "--": "",
+ "opackets" : self.get_rel("opackets"),
+ "obytes" : self.get_rel("obytes"),
+ "ipackets" : self.get_rel("ipackets"),
+ "ibytes" : self.get_rel("ibytes"),
+ "ierrors" : self.get_rel("ierrors"),
+ "oerrors" : self.get_rel("oerrors"),
+
+ "tx-bytes": self.get_rel("obytes", format = True, suffix = "B"),
+ "rx-bytes": self.get_rel("ibytes", format = True, suffix = "B"),
+ "tx-pkts": self.get_rel("opackets", format = True, suffix = "pkts"),
+ "rx-pkts": self.get_rel("ipackets", format = True, suffix = "pkts"),
+ "---": "",
+ "Tx bps": self.get("m_total_tx_bps", format = True, suffix = "bps"),
+ "Rx bps": self.get("m_total_rx_bps", format = True, suffix = "bps"),
+ "----": "",
+ "Tx pps": self.get("m_total_tx_pps", format = True, suffix = "pps"),
+ "Rx pps": self.get("m_total_rx_pps", format = True, suffix = "pps"),
+ }
-class CStreamStats(CTRexStats):
- def __init__(self, **kwargs):
- super(CStreamStats, self).__init__(kwargs)
- pass
if __name__ == "__main__":
diff --git a/scripts/automation/trex_control_plane/common/trex_streams.py b/scripts/automation/trex_control_plane/common/trex_streams.py
index 783f2769..86eee1f4 100755
--- a/scripts/automation/trex_control_plane/common/trex_streams.py
+++ b/scripts/automation/trex_control_plane/common/trex_streams.py
@@ -10,20 +10,33 @@ import copy
import os
StreamPack = namedtuple('StreamPack', ['stream_id', 'stream'])
+LoadedStreamList = namedtuple('LoadedStreamList', ['loaded', 'compiled'])
class CStreamList(object):
def __init__(self):
- self.streams_list = {}
+ self.streams_list = OrderedDict()
self.yaml_loader = CTRexYAMLLoader(os.path.join(os.path.dirname(os.path.realpath(__file__)),
"rpc_defaults.yaml"))
+ def generate_numbered_name (self, name):
+ prefix = name.rstrip('01234567890')
+ suffix = name[len(prefix):]
+ if suffix == "":
+ n = "_1"
+ else:
+ n = int(suffix) + 1
+ return prefix + str(n)
+
def append_stream(self, name, stream_obj):
assert isinstance(stream_obj, CStream)
- if name in self.streams_list:
- raise NameError("A stream with this name already exists on this list.")
+
+ # if name exists simply add numbered suffix to it
+ while name in self.streams_list:
+ name = self.generate_numbered_name(name)
+
self.streams_list[name]=stream_obj
- return
+ return name
def remove_stream(self, name):
popped = self.streams_list.pop(name)
@@ -48,6 +61,7 @@ class CStreamList(object):
self.streams_list.clear()
streams_data = load_yaml_to_obj(file_path)
assert isinstance(streams_data, list)
+ new_streams_data = []
for stream in streams_data:
stream_name = stream.get("name")
raw_stream = stream.get("stream")
@@ -58,16 +72,18 @@ class CStreamList(object):
new_stream_data = self.yaml_loader.validate_yaml(raw_stream,
"stream",
multiplier= multiplier)
+ new_streams_data.append(new_stream_data)
new_stream_obj = CStream()
new_stream_obj.load_data(**new_stream_data)
self.append_stream(stream_name, new_stream_obj)
- return new_stream_data
+ return new_streams_data
def compile_streams(self):
# first, assign an id to each stream
stream_ids = {}
for idx, stream_name in enumerate(self.streams_list):
stream_ids[stream_name] = idx
+
# next, iterate over the streams and transform them from working with names to ids.
# with that build a new dict with old stream_name as the key, and StreamPack as the stored value
compiled_streams = {}
@@ -156,7 +172,6 @@ class CStream(object):
"""docstring for CStream"""
FIELDS = ["enabled", "self_start", "next_stream_id", "isg", "mode", "rx_stats", "packet", "vm"]
- # COMPILE_FIELDS = ["enabled", "self_start", "next_stream_id", "isg", "mode", "rx_stats", "packet", "vm"]
def __init__(self):
self.is_loaded = False
@@ -183,6 +198,7 @@ class CStream(object):
if isinstance(kwargs[k], CTRexPktBuilder):
if "vm" not in kwargs:
self.load_packet_obj(kwargs[k])
+ break # vm field check is skipped
else:
raise ValueError("When providing packet object with a CTRexPktBuilder, vm parameter "
"should not be supplied")
@@ -226,8 +242,7 @@ class CStream(object):
return
- def dump(self, compilation=False):
- # fields = CStream.COMPILE_FIELDS if compilation else CStream.FIELDS
+ def dump(self):
if self.is_loaded:
dump = {}
for key in CStream.FIELDS:
@@ -239,10 +254,62 @@ class CStream(object):
else:
raise RuntimeError("CStream object isn't loaded with data. Use 'load_data' method.")
- def dump_compiled(self):
- return self.dump(compilation=True)
+# describes a stream DB
+class CStreamsDB(object):
+
+ def __init__(self):
+ self.stream_packs = {}
+
+ def load_yaml_file(self, filename):
+
+ stream_pack_name = filename
+ if stream_pack_name in self.get_loaded_streams_names():
+ self.remove_stream_packs(stream_pack_name)
+
+ stream_list = CStreamList()
+ loaded_obj = stream_list.load_yaml(filename)
+
+ try:
+ compiled_streams = stream_list.compile_streams()
+ rc = self.load_streams(stream_pack_name,
+ LoadedStreamList(loaded_obj,
+ [StreamPack(v.stream_id, v.stream.dump())
+ for k, v in compiled_streams.items()]))
+
+ except Exception as e:
+ return None
+
+ return self.get_stream_pack(stream_pack_name)
+
+ def load_streams(self, name, LoadedStreamList_obj):
+ if name in self.stream_packs:
+ return False
+ else:
+ self.stream_packs[name] = LoadedStreamList_obj
+ return True
+
+ def remove_stream_packs(self, *names):
+ removed_streams = []
+ for name in names:
+ removed = self.stream_packs.pop(name)
+ if removed:
+ removed_streams.append(name)
+ return removed_streams
+
+ def clear(self):
+ self.stream_packs.clear()
+
+ def get_loaded_streams_names(self):
+ return self.stream_packs.keys()
+
+ def stream_pack_exists (self, name):
+ return name in self.get_loaded_streams_names()
+
+ def get_stream_pack(self, name):
+ if not self.stream_pack_exists(name):
+ return None
+ else:
+ return self.stream_packs.get(name)
-if __name__ == "__main__":
- pass
diff --git a/scripts/automation/trex_control_plane/common/trex_types.py b/scripts/automation/trex_control_plane/common/trex_types.py
new file mode 100644
index 00000000..3de36e4c
--- /dev/null
+++ b/scripts/automation/trex_control_plane/common/trex_types.py
@@ -0,0 +1,66 @@
+
+from collections import namedtuple
+from common.text_opts import *
+
+RpcCmdData = namedtuple('RpcCmdData', ['method', 'params'])
+
+class RpcResponseStatus(namedtuple('RpcResponseStatus', ['success', 'id', 'msg'])):
+ __slots__ = ()
+ def __str__(self):
+ return "{id:^3} - {msg} ({stat})".format(id=self.id,
+ msg=self.msg,
+ stat="success" if self.success else "fail")
+
+# simple class to represent complex return value
+class RC():
+
+ def __init__ (self, rc = None, data = None):
+ self.rc_list = []
+
+ if (rc != None) and (data != None):
+ tuple_rc = namedtuple('RC', ['rc', 'data'])
+ self.rc_list.append(tuple_rc(rc, data))
+
+ def add (self, rc):
+ self.rc_list += rc.rc_list
+
+ def good (self):
+ return all([x.rc for x in self.rc_list])
+
+ def bad (self):
+ return not self.good()
+
+ def data (self):
+ return [x.data if x.rc else "" for x in self.rc_list]
+
+ def err (self):
+ return [x.data if not x.rc else "" for x in self.rc_list]
+
+ def annotate (self, desc = None, show_status = True):
+ if desc:
+ print format_text('\n{:<60}'.format(desc), 'bold'),
+ else:
+ print ""
+
+ if self.bad():
+ # print all the errors
+ print ""
+ for x in self.rc_list:
+ if not x.rc:
+ print format_text("\n{0}".format(x.data), 'bold')
+
+ print ""
+ if show_status:
+ print format_text("[FAILED]\n", 'red', 'bold')
+
+
+ else:
+ if show_status:
+ print format_text("[SUCCESS]\n", 'green', 'bold')
+
+
+def RC_OK(data = ""):
+ return RC(True, data)
+def RC_ERR (err):
+ return RC(False, err)
+
diff --git a/scripts/automation/trex_control_plane/console/trex_console.py b/scripts/automation/trex_control_plane/console/trex_console.py
index a9ac040b..0ecfce9c 100755
--- a/scripts/automation/trex_control_plane/console/trex_console.py
+++ b/scripts/automation/trex_control_plane/console/trex_console.py
@@ -17,575 +17,587 @@ See the License for the specific language governing permissions and
limitations under the License.
"""
+
import cmd
import json
import ast
import argparse
import random
+import readline
import string
import os
import sys
import tty, termios
import trex_root_path
from common.trex_streams import *
+from client.trex_stateless_client import CTRexStatelessClient
+from common.text_opts import *
+from client_utils.general_utils import user_input, get_current_user
+from client_utils import parsing_opts
+import trex_tui
+from functools import wraps
-from client_utils.jsonrpc_client import TrexStatelessClient
-import trex_status
-from collections import namedtuple
-
-LoadedStreamList = namedtuple('LoadedStreamList', ['loaded', 'compiled'])
+__version__ = "1.1"
-#
-def readch (choices = []):
-
- fd = sys.stdin.fileno()
- old_settings = termios.tcgetattr(fd)
- try:
- tty.setraw(sys.stdin.fileno())
- while True:
- ch = sys.stdin.read(1)
- if (ord(ch) == 3) or (ord(ch) == 4):
- return None
- if ch in choices:
- return ch
- finally:
- termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
-
- return None
+class TRexGeneralCmd(cmd.Cmd):
+ def __init__(self):
+ cmd.Cmd.__init__(self)
+ # configure history behaviour
+ self._history_file_dir = "/tmp/trex/console/"
+ self._history_file = self.get_history_file_full_path()
+ readline.set_history_length(100)
+ # load history, if any
+ self.load_console_history()
-class YesNoMenu(object):
- def __init__ (self, caption):
- self.caption = caption
- def show (self):
- print "{0}".format(self.caption)
- sys.stdout.write("[Y/y/N/n] : ")
- ch = readch(choices = ['y', 'Y', 'n', 'N'])
- if ch == None:
- return None
-
- print "\n"
- if ch == 'y' or ch == 'Y':
- return True
- else:
- return False
+ def get_console_identifier(self):
+ return self.__class__.__name__
-# multi level cmd menu
-class CmdMenu(object):
- def __init__ (self):
- self.menus = []
+ def get_history_file_full_path(self):
+ return "{dir}{filename}.hist".format(dir=self._history_file_dir,
+ filename=self.get_console_identifier())
+ def load_console_history(self):
+ if os.path.exists(self._history_file):
+ readline.read_history_file(self._history_file)
+ return
- def add_menu (self, caption, options):
- menu = {}
- menu['caption'] = caption
- menu['options'] = options
- self.menus.append(menu)
+ def save_console_history(self):
+ if not os.path.exists(self._history_file_dir):
+ os.makedirs(self._history_file_dir)
+ # os.mknod(self._history_file)
+ readline.write_history_file(self._history_file)
+ return
- def show (self):
- cur_level = 0
- print "\n"
+ def print_history (self):
+
+ length = readline.get_current_history_length()
- selected_path = []
- for menu in self.menus:
- # show all the options
- print "{0}\n".format(menu['caption'])
- for i, option in enumerate(menu['options']):
- print "{0}. {1}".format(i + 1, option)
+ for i in xrange(1, length + 1):
+ cmd = readline.get_history_item(i)
+ print "{:<5} {:}".format(i, cmd)
- #print "\nPlease select an option: "
+ def get_history_item (self, index):
+ length = readline.get_current_history_length()
+ if index > length:
+ print format_text("please select an index between {0} and {1}".format(0, length))
+ return None
- choices = range(0, len(menu['options']))
- choices = [ chr(x + 48) for x in choices]
+ return readline.get_history_item(index)
- print ""
- ch = readch(choices)
- print ""
- if ch == None:
- return None
+ def emptyline(self):
+ """Called when an empty line is entered in response to the prompt.
- selected_path.append(int(ch) - 1)
+ This overriding is such that when empty line is passed, **nothing happens**.
+ """
+ return
- return selected_path
+ def completenames(self, text, *ignored):
+ """
+ This overriding is such that a space is added to name completion.
+ """
+ dotext = 'do_'+text
+ return [a[3:]+' ' for a in self.get_names() if a.startswith(dotext)]
+ def precmd(self, line):
+ # before doing anything, save history snapshot of the console
+ # this is done before executing the command in case of ungraceful application exit
+ self.save_console_history()
+ return line
-class AddStreamMenu(CmdMenu):
- def __init__ (self):
- super(AddStreamMenu, self).__init__()
- self.add_menu('Please select type of stream', ['a', 'b', 'c'])
- self.add_menu('Please select ISG', ['d', 'e', 'f'])
+#
# main console object
-class TrexConsole(cmd.Cmd):
+class TRexConsole(TRexGeneralCmd):
"""Trex Console"""
-
- def __init__(self, rpc_client):
- cmd.Cmd.__init__(self)
- self.rpc_client = rpc_client
+ def __init__(self, stateless_client, verbose=False):
+ self.stateless_client = stateless_client
+ TRexGeneralCmd.__init__(self)
- self.do_connect("")
+ self.tui = trex_tui.TrexTUI(stateless_client)
- self.intro = "\n-=TRex Console V1.0=-\n"
- self.intro += "\nType 'help' or '?' for supported actions\n"
+ self.verbose = verbose
- self.verbose = False
+ self.intro = "\n-=TRex Console v{ver}=-\n".format(ver=__version__)
+ self.intro += "\nType 'help' or '?' for supported actions\n"
self.postcmd(False, "")
- self.user_streams = {}
-
- # a cool hack - i stole this function and added space
- def completenames(self, text, *ignored):
- dotext = 'do_'+text
- return [a[3:]+' ' for a in self.get_names() if a.startswith(dotext)]
+ ################### internal section ########################
- # set verbose on / off
- def do_verbose (self, line):
- '''Shows or set verbose mode\n'''
- if line == "":
- print "\nverbose is " + ("on\n" if self.verbose else "off\n")
+ def verify_connected(f):
+ @wraps(f)
+ def wrap(*args):
+ inst = args[0]
+ func_name = f.__name__
+ if func_name.startswith("do_"):
+ func_name = func_name[3:]
- elif line == "on":
- self.verbose = True
- self.rpc_client.set_verbose(True)
- print "\nverbose set to on\n"
+ if not inst.stateless_client.is_connected():
+ print format_text("\n'{0}' cannot be executed on offline mode\n".format(func_name), 'bold')
+ return
- elif line == "off":
- self.verbose = False
- self.rpc_client.set_verbose(False)
- print "\nverbose set to off\n"
+ ret = f(*args)
+ return ret
- else:
- print "\nplease specify 'on' or 'off'\n"
+ return wrap
- # query the server for registered commands
- def do_query_server(self, line):
- '''query the RPC server for supported remote commands\n'''
+ # TODO: remove this ugly duplication
+ def verify_connected_and_rw (f):
+ @wraps(f)
+ def wrap(*args):
+ inst = args[0]
+ func_name = f.__name__
+ if func_name.startswith("do_"):
+ func_name = func_name[3:]
- rc, msg = self.rpc_client.query_rpc_server()
- if not rc:
- print "\n*** " + msg + "\n"
- return
+ if not inst.stateless_client.is_connected():
+ print format_text("\n'{0}' cannot be executed on offline mode\n".format(func_name), 'bold')
+ return
- print "\nRPC server supports the following commands: \n\n"
- for func in msg:
- if func:
- print func
- print "\n"
+ if inst.stateless_client.is_read_only():
+ print format_text("\n'{0}' cannot be executed on read only mode\n".format(func_name), 'bold')
+ return
- def do_ping (self, line):
- '''Pings the RPC server\n'''
+ ret = f(*args)
+ return ret
- print "\n-> Pinging RPC server"
+ return wrap
- rc, msg = self.rpc_client.ping_rpc_server()
- if rc:
- print "[SUCCESS]\n"
- else:
- print "\n*** " + msg + "\n"
- return
- def do_force_acquire (self, line):
- '''Acquires ports by force\n'''
+ def get_console_identifier(self):
+ return "{context}_{server}".format(context=self.__class__.__name__,
+ server=self.stateless_client.get_server())
+
+ def register_main_console_methods(self):
+ main_names = set(self.trex_console.get_names()).difference(set(dir(self.__class__)))
+ for name in main_names:
+ for prefix in 'do_', 'help_', 'complete_':
+ if name.startswith(prefix):
+ self.__dict__[name] = getattr(self.trex_console, name)
- self.do_acquire(line, True)
+ def postcmd(self, stop, line):
- def parse_ports_from_line (self, line):
- port_list = set()
+ if not self.stateless_client.is_connected():
+ self.prompt = "TRex (offline) > "
+ self.supported_rpc = None
+ return stop
- if line:
- for port_id in line.split(' '):
- if (not port_id.isdigit()) or (int(port_id) < 0) or (int(port_id) >= self.rpc_client.get_port_count()):
- print "Please provide a list of ports seperated by spaces between 0 and {0}".format(self.rpc_client.get_port_count() - 1)
- return None
+ if self.stateless_client.is_read_only():
+ self.prompt = "TRex (read only) > "
+ return stop
- port_list.add(int(port_id))
- port_list = list(port_list)
+ self.prompt = "TRex > "
- else:
- port_list = [i for i in xrange(0, self.rpc_client.get_port_count())]
+ return stop
- return port_list
+ def default(self, line):
+ print "'{0}' is an unrecognized command. type 'help' or '?' for a list\n".format(line)
- def do_acquire (self, line, force = False):
- '''Acquire ports\n'''
+ @staticmethod
+ def tree_autocomplete(text):
+ dir = os.path.dirname(text)
+ if dir:
+ path = dir
+ else:
+ path = "."
- # make sure that the user wants to acquire all
- if line == "":
- ask = YesNoMenu('Do you want to acquire all ports ? ')
- rc = ask.show()
- if rc == False:
- return
- port_list = self.parse_ports_from_line(line)
- if not port_list:
- return
+ start_string = os.path.basename(text)
+
+ targets = []
- print "\nTrying to acquire ports: " + (" ".join(str(x) for x in port_list)) + "\n"
+ for x in os.listdir(path):
+ if x.startswith(start_string):
+ y = os.path.join(path, x)
+ if os.path.isfile(y):
+ targets.append(x + ' ')
+ elif os.path.isdir(y):
+ targets.append(x + '/')
- rc, resp_list = self.rpc_client.take_ownership(port_list, force)
+ return targets
- if not rc:
- print "\n*** " + resp_list + "\n"
+ # annotation method
+ @staticmethod
+ def annotate (desc, rc = None, err_log = None, ext_err_msg = None):
+ print format_text('\n{:<40}'.format(desc), 'bold'),
+ if rc == None:
+ print "\n"
return
- for i, rc in enumerate(resp_list):
- if rc[0]:
- print "Port {0} - Acquired".format(port_list[i])
- else:
- print "Port {0} - ".format(port_list[i]) + rc[1]
+ if rc == False:
+ # do we have a complex log object ?
+ if isinstance(err_log, list):
+ print ""
+ for func in err_log:
+ if func:
+ print func
+ print ""
- print "\n"
+ elif isinstance(err_log, str):
+ print "\n" + err_log + "\n"
- def do_release (self, line):
- '''Release ports\n'''
+ print format_text("[FAILED]\n", 'red', 'bold')
+ if ext_err_msg:
+ print format_text(ext_err_msg + "\n", 'blue', 'bold')
+
+ return False
- if line:
- port_list = self.parse_ports_from_line(line)
else:
- port_list = self.rpc_client.get_owned_ports()
+ print format_text("[SUCCESS]\n", 'green', 'bold')
+ return True
- if not port_list:
+
+ ####################### shell commands #######################
+ @verify_connected
+ def do_ping (self, line):
+ '''Ping the server\n'''
+ rc = self.stateless_client.cmd_ping()
+ if rc.bad():
return
- rc, resp_list = self.rpc_client.release_ports(port_list)
+ # set verbose on / off
+ def do_verbose(self, line):
+ '''Shows or set verbose mode\n'''
+ if line == "":
+ print "\nverbose is " + ("on\n" if self.verbose else "off\n")
- print "\n"
+ elif line == "on":
+ self.verbose = True
+ self.stateless_client.set_verbose(self.stateless_client.VERBOSE_HIGH)
+ print format_text("\nverbose set to on\n", 'green', 'bold')
- for i, rc in enumerate(resp_list):
- if rc[0]:
- print "Port {0} - Released".format(port_list[i])
- else:
- print "Port {0} - Failed to release port, probably not owned by you or port is under traffic"
+ elif line == "off":
+ self.verbose = False
+ self.stateless_client.set_verbose(self.stateless_client.VERBOSE_REGULAR)
+ print format_text("\nverbose set to off\n", 'green', 'bold')
- print "\n"
+ else:
+ print format_text("\nplease specify 'on' or 'off'\n", 'bold')
- def do_get_port_stats (self, line):
- '''Get ports stats\n'''
+ # show history
+ def help_history (self):
+ self.do_history("-h")
- port_list = self.parse_ports_from_line(line)
- if not port_list:
- return
+ def do_history (self, line):
+ '''Manage the command history\n'''
+
+ item = parsing_opts.ArgumentPack(['item'],
+ {"nargs": '?',
+ 'metavar': 'item',
+ 'type': parsing_opts.check_negative,
+ 'help': "an history item index",
+ 'default': 0})
- rc, resp_list = self.rpc_client.get_port_stats(port_list)
+ parser = parsing_opts.gen_parser(self,
+ "history",
+ self.do_history.__doc__,
+ item)
- if not rc:
- print "\n*** " + resp_list + "\n"
+ opts = parser.parse_args(line.split())
+ if opts is None:
return
- for i, rc in enumerate(resp_list):
- if rc[0]:
- print "\nPort {0} stats:\n{1}\n".format(port_list[i], self.rpc_client.pretty_json(json.dumps(rc[1])))
- else:
- print "\nPort {0} - ".format(i) + rc[1] + "\n"
+ if opts.item == 0:
+ self.print_history()
+ else:
+ cmd = self.get_history_item(opts.item)
+ if cmd == None:
+ return
+
+ self.onecmd(cmd)
- print "\n"
+ ############### connect
def do_connect (self, line):
'''Connects to the server\n'''
- if line == "":
- rc, msg = self.rpc_client.connect()
- else:
- sp = line.split()
- if (len(sp) != 2):
- print "\n[usage] connect [server] [port] or without parameters\n"
- return
+ self.stateless_client.cmd_connect_line(line)
- rc, msg = self.rpc_client.connect(sp[0], sp[1])
- if rc:
- print "[SUCCESS]\n"
- else:
- print "\n*** " + msg + "\n"
- return
+ def do_disconnect (self, line):
+ '''Disconnect from the server\n'''
- self.supported_rpc = self.rpc_client.get_supported_cmds()
+ self.stateless_client.cmd_disconnect()
- def do_rpc (self, line):
- '''Launches a RPC on the server\n'''
+
+ ############### start
- if line == "":
- print "\nUsage: [method name] [param dict as string]\n"
- print "Example: rpc test_add {'x': 12, 'y': 17}\n"
- return
+ def complete_start(self, text, line, begidx, endidx):
+ s = line.split()
+ l = len(s)
- sp = line.split(' ', 1)
- method = sp[0]
+ file_flags = parsing_opts.get_flags(parsing_opts.FILE_PATH)
- params = None
- bad_parse = False
- if len(sp) > 1:
+ if (l > 1) and (s[l - 1] in file_flags):
+ return TRexConsole.tree_autocomplete("")
- try:
- params = ast.literal_eval(sp[1])
- if not isinstance(params, dict):
- bad_parse = True
+ if (l > 2) and (s[l - 2] in file_flags):
+ return TRexConsole.tree_autocomplete(s[l - 1])
- except ValueError as e1:
- bad_parse = True
- except SyntaxError as e2:
- bad_parse = True
+ @verify_connected_and_rw
+ def do_start(self, line):
+ '''Start selected traffic in specified port(s) on TRex\n'''
- if bad_parse:
- print "\nValue should be a valid dict: '{0}'".format(sp[1])
- print "\nUsage: [method name] [param dict as string]\n"
- print "Example: rpc test_add {'x': 12, 'y': 17}\n"
- return
+ self.stateless_client.cmd_start_line(line)
- rc, msg = self.rpc_client.invoke_rpc_method(method, params)
- if rc:
- print "\nServer Response:\n\n" + self.rpc_client.pretty_json(json.dumps(msg)) + "\n"
- else:
- print "\n*** " + msg + "\n"
- #print "Please try 'reconnect' to reconnect to server"
+ def help_start(self):
+ self.do_start("-h")
- def complete_rpc (self, text, line, begidx, endidx):
- return [x for x in self.supported_rpc if x.startswith(text)]
+ ############# stop
+ @verify_connected_and_rw
+ def do_stop(self, line):
+ '''stops port(s) transmitting traffic\n'''
- def do_status (self, line):
- '''Shows a graphical console\n'''
+ self.stateless_client.cmd_stop_line(line)
- self.do_verbose('off')
- trex_status.show_trex_status(self.rpc_client)
+ def help_stop(self):
+ self.do_stop("-h")
- def do_quit(self, line):
- '''Exit the client\n'''
- return True
+ ############# update
+ @verify_connected_and_rw
+ def do_update(self, line):
+ '''update speed of port(s)currently transmitting traffic\n'''
- def do_disconnect (self, line):
- '''Disconnect from the server\n'''
- if not self.rpc_client.is_connected():
- print "Not connected to server\n"
- return
+ self.stateless_client.cmd_update_line(line)
- rc, msg = self.rpc_client.disconnect()
- if rc:
- print "[SUCCESS]\n"
- else:
- print msg + "\n"
+ def help_update (self):
+ self.do_update("-h")
- def do_whoami (self, line):
- '''Prints console user name\n'''
- print "\n" + self.rpc_client.whoami() + "\n"
-
- def postcmd(self, stop, line):
- if self.rpc_client.is_connected():
- self.prompt = "TRex > "
- else:
- self.supported_rpc = None
- self.prompt = "TRex (offline) > "
+ ############# pause
+ @verify_connected_and_rw
+ def do_pause(self, line):
+ '''pause port(s) transmitting traffic\n'''
- return stop
+ self.stateless_client.cmd_pause_line(line)
- def default(self, line):
- print "'{0}' is an unrecognized command. type 'help' or '?' for a list\n".format(line)
+ ############# resume
+ @verify_connected_and_rw
+ def do_resume(self, line):
+ '''resume port(s) transmitting traffic\n'''
- def do_help (self, line):
- '''Shows This Help Screen\n'''
- if line:
- try:
- func = getattr(self, 'help_' + line)
- except AttributeError:
- try:
- doc = getattr(self, 'do_' + line).__doc__
- if doc:
- self.stdout.write("%s\n"%str(doc))
- return
- except AttributeError:
- pass
- self.stdout.write("%s\n"%str(self.nohelp % (line,)))
- return
- func()
- return
+ self.stateless_client.cmd_resume_line(line)
- print "\nSupported Console Commands:"
- print "----------------------------\n"
-
- cmds = [x[3:] for x in self.get_names() if x.startswith("do_")]
- for cmd in cmds:
- if cmd == "EOF":
- continue
-
- try:
- doc = getattr(self, 'do_' + cmd).__doc__
- if doc:
- help = str(doc)
- else:
- help = "*** Undocumented Function ***\n"
- except AttributeError:
- help = "*** Undocumented Function ***\n"
-
- print "{:<30} {:<30}".format(cmd + " - ", help)
-
- def do_load_stream_list(self, line):
- '''Loads a YAML stream list serialization into user console \n'''
- args = line.split()
- if args >= 2:
- name = args[0]
- yaml_path = args[1]
- try:
- multiplier = args[2]
- except IndexError:
- multiplier = 1
- stream_list = CStreamList()
- loaded_obj = stream_list.load_yaml(yaml_path, multiplier)
- # print self.rpc_client.pretty_json(json.dumps(loaded_obj))
- if name in self.user_streams:
- print "Picked name already exist. Please pick another name."
- else:
- try:
- compiled_streams = stream_list.compile_streams()
- self.user_streams[name] = LoadedStreamList(loaded_obj,
- [StreamPack(v.stream_id, v.stream.dump_compiled())
- for k, v in compiled_streams.items()])
-
- print "Stream '{0}' loaded successfully".format(name)
- except Exception as e:
- raise
- return
- else:
- print "please provide load name and YAML path, separated by space.\n" \
- "Optionally, you may provide a third argument to specify multiplier."
+
- @staticmethod
- def tree_autocomplete(text):
- dir = os.path.dirname(text)
- if dir:
- path = dir
- else:
- path = "."
- start_string = os.path.basename(text)
- return [x
- for x in os.listdir(path)
- if x.startswith(start_string)]
+ ########## reset
+ @verify_connected_and_rw
+ def do_reset (self, line):
+ '''force stop all ports\n'''
+ self.stateless_client.cmd_reset_line(line)
- def complete_load_stream_list(self, text, line, begidx, endidx):
- arg_num = len(line.split()) - 1
- if arg_num == 2:
- return TrexConsole.tree_autocomplete(line.split()[-1])
- else:
- return [text]
-
- def do_show_stream_list(self, line):
- '''Shows the loaded stream list named [name] \n'''
- args = line.split()
- if args:
- list_name = args[0]
- try:
- stream = self.user_streams[list_name]
- if len(args) >= 2 and args[1] == "full":
- print self.rpc_client.pretty_json(json.dumps(stream.compiled))
- else:
- print self.rpc_client.pretty_json(json.dumps(stream.loaded))
- except KeyError as e:
- print "Unknown stream list name provided"
- else:
- print "\nAvailable stream lists:\n{0}".format(', '.join([x
- for x in self.user_streams.keys()]))
-
- def complete_show_stream_list(self, text, line, begidx, endidx):
- return [x
- for x in self.user_streams.keys()
- if x.startswith(text)]
-
- def do_attach(self, line):
- args = line.split()
- if len(args) >= 1:
- try:
- stream_list = self.user_streams[args[0]]
- port_list = self.parse_ports_from_line(' '.join(args[1:]))
- owned = set(self.rpc_client.get_owned_ports())
- if set(port_list).issubset(owned):
- rc, resp_list = self.rpc_client.add_stream(port_list, stream_list.compiled)
- if not rc:
- print "\n*** " + resp_list + "\n"
- return
- else:
- print "Not all desired ports are aquired.\n" \
- "Acquired ports are: {acq}\n" \
- "Requested ports: {req}\n" \
- "Missing ports: {miss}".format(acq=list(owned),
- req=port_list,
- miss=list(set(port_list).difference(owned)))
- except KeyError as e:
- cause = e.args[0]
- print "Provided stream list name '{0}' doesn't exists.".format(cause)
- else:
- print "Please provide list name and ports to attach to, or leave empty to attach to all ports."
+ ######### validate
+ @verify_connected
+ def do_validate (self, line):
+ '''validates port(s) stream configuration\n'''
+ self.stateless_client.cmd_validate_line(line)
+ @verify_connected
+ def do_stats(self, line):
+ '''Fetch statistics from TRex server by port\n'''
+ self.stateless_client.cmd_stats_line(line)
+ def help_stats(self):
+ self.do_stats("-h")
+ @verify_connected
+ def do_clear(self, line):
+ '''Clear cached local statistics\n'''
+ self.stateless_client.cmd_clear_line(line)
+ def help_clear(self):
+ self.do_clear("-h")
+
+ def help_events (self):
+ self.do_events("-h")
+ def do_events (self, line):
+ '''shows events recieved from server\n'''
+ x = parsing_opts.ArgumentPack(['-c','--clear'],
+ {'action' : "store_true",
+ 'default': False,
+ 'help': "clear the events log"})
- # adds a very simple stream
- def do_add_simple_stream (self, line):
- if line == "":
- add_stream = AddStreamMenu()
- add_stream.show()
+ parser = parsing_opts.gen_parser(self,
+ "events",
+ self.do_events.__doc__,
+ x)
+
+ opts = parser.parse_args(line.split())
+ if opts is None:
return
- params = line.split()
- port_id = int(params[0])
- stream_id = int(params[1])
+ events = self.stateless_client.get_events()
+ for ev in events:
+ print ev
- packet = [0xFF,0xFF,0xFF]
- rc, msg = self.rpc_client.add_stream(port_id = port_id, stream_id = stream_id, isg = 1.1, next_stream_id = -1, packet = packet)
- if rc:
- print "\nServer Response:\n\n" + self.rpc_client.pretty_json(json.dumps(msg)) + "\n"
- else:
- print "\n*** " + msg + "\n"
+ if opts.clear:
+ self.stateless_client.clear_events()
+ print format_text("\n\nEvent log was cleared\n\n")
+
+ # tui
+ @verify_connected
+ def do_tui (self, line):
+ '''Shows a graphical console\n'''
+
+ save_verbose = self.stateless_client.get_verbose()
+
+ self.stateless_client.set_verbose(self.stateless_client.VERBOSE_SILENCE)
+ self.tui.show()
+ self.stateless_client.set_verbose(save_verbose)
+
+ # quit function
+ def do_quit(self, line):
+ '''Exit the client\n'''
+ return True
- # aliasing
+
+ def do_help (self, line):
+ '''Shows This Help Screen\n'''
+ if line:
+ try:
+ func = getattr(self, 'help_' + line)
+ except AttributeError:
+ try:
+ doc = getattr(self, 'do_' + line).__doc__
+ if doc:
+ self.stdout.write("%s\n"%str(doc))
+ return
+ except AttributeError:
+ pass
+ self.stdout.write("%s\n"%str(self.nohelp % (line,)))
+ return
+ func()
+ return
+
+ print "\nSupported Console Commands:"
+ print "----------------------------\n"
+
+ cmds = [x[3:] for x in self.get_names() if x.startswith("do_")]
+ for cmd in cmds:
+ if ( (cmd == "EOF") or (cmd == "q") or (cmd == "exit") or (cmd == "h")):
+ continue
+
+ try:
+ doc = getattr(self, 'do_' + cmd).__doc__
+ if doc:
+ help = str(doc)
+ else:
+ help = "*** Undocumented Function ***\n"
+ except AttributeError:
+ help = "*** Undocumented Function ***\n"
+
+ print "{:<30} {:<30}".format(cmd + " - ", help)
+
+ # aliases
do_exit = do_EOF = do_q = do_quit
+ do_h = do_history
+
+#
+def is_valid_file(filename):
+ if not os.path.isfile(filename):
+ raise argparse.ArgumentTypeError("The file '%s' does not exist" % filename)
+
+ return filename
-def setParserOptions ():
+
+def setParserOptions():
parser = argparse.ArgumentParser(prog="trex_console.py")
parser.add_argument("-s", "--server", help = "TRex Server [default is localhost]",
default = "localhost",
type = str)
- parser.add_argument("-p", "--port", help = "TRex Server Port [default is 5050]\n",
- default = 5050,
+ parser.add_argument("-p", "--port", help = "TRex Server Port [default is 4501]\n",
+ default = 4501,
+ type = int)
+
+ parser.add_argument("--async_port", help = "TRex ASync Publisher Port [default is 4500]\n",
+ default = 4500,
+ dest='pub',
type = int)
- parser.add_argument("-u", "--user", help = "User Name [default is random generated]\n",
- default = 'user_' + ''.join(random.choice(string.digits) for _ in range(5)),
+ parser.add_argument("-u", "--user", help = "User Name [default is currently logged in user]\n",
+ default = get_current_user(),
type = str)
+ parser.add_argument("--verbose", dest="verbose",
+ action="store_true", help="Switch ON verbose option. Default is: OFF.",
+ default = False)
+
+
+ parser.add_argument("--no_acquire", dest="acquire",
+ action="store_false", help="Acquire all ports on connect. Default is: ON.",
+ default = True)
+
+ parser.add_argument("--batch", dest="batch",
+ nargs = 1,
+ type = is_valid_file,
+ help = "Run the console in a batch mode with file",
+ default = None)
+
+ parser.add_argument("-t", "--tui", dest="tui",
+ action="store_true", help="Starts with TUI mode",
+ default = False)
+
return parser
-def main ():
+
+def main():
parser = setParserOptions()
- options = parser.parse_args(sys.argv[1:])
+ options = parser.parse_args()
+
+ # Stateless client connection
+ stateless_client = CTRexStatelessClient(options.user, options.server, options.port, options.pub)
+
+ print "\nlogged as {0}".format(format_text(options.user, 'bold'))
- # RPC client
- rpc_client = TrexStatelessClient(options.server, options.port, options.user)
+ # TUI or no acquire will give us READ ONLY mode
+ if options.tui or not options.acquire:
+ rc = stateless_client.connect("RO")
+ else:
+ rc = stateless_client.connect("RW")
+ # unable to connect - bye
+ if rc.bad():
+ rc.annotate()
+ return
+
+
+ # a script mode
+ if options.batch:
+ cont = stateless_client.run_script_file(options.batch[0])
+ if not cont:
+ return
+
# console
try:
- console = TrexConsole(rpc_client)
- console.cmdloop()
+ console = TRexConsole(stateless_client, options.verbose)
+ if options.tui:
+ console.do_tui("")
+ else:
+ console.cmdloop()
+
except KeyboardInterrupt as e:
print "\n\n*** Caught Ctrl + C... Exiting...\n\n"
- return
+
+ finally:
+ stateless_client.disconnect()
if __name__ == '__main__':
main()
diff --git a/scripts/automation/trex_control_plane/console/trex_status.py b/scripts/automation/trex_control_plane/console/trex_status.py
index 2c5a648f..cdf3fb69 100644
--- a/scripts/automation/trex_control_plane/console/trex_status.py
+++ b/scripts/automation/trex_control_plane/console/trex_status.py
@@ -11,22 +11,27 @@ import datetime
g_curses_active = False
+################### utils #################
+
# simple percetange show
def percentage (a, total):
x = int ((float(a) / total) * 100)
return str(x) + "%"
-# simple float to human readable
-def float_to_human_readable (size, suffix = "bps"):
- for unit in ['','K','M','G']:
- if abs(size) < 1024.0:
- return "%3.1f %s%s" % (size, unit, suffix)
- size /= 1024.0
- return "NaN"
+################### panels #################
# panel object
class TrexStatusPanel(object):
- def __init__ (self, h, l, y, x, headline):
+ def __init__ (self, h, l, y, x, headline, status_obj):
+
+ self.status_obj = status_obj
+
+ self.log = status_obj.log
+ self.stateless_client = status_obj.stateless_client
+
+ self.stats = status_obj.stats
+ self.general_stats = status_obj.general_stats
+
self.h = h
self.l = l
self.y = y
@@ -53,64 +58,26 @@ class TrexStatusPanel(object):
return self.win
-# total stats (ports + global)
-class Stats():
- def __init__ (self, rpc_client, port_list, interval = 100):
-
- self.rpc_client = rpc_client
-
- self.port_list = port_list
- self.port_stats = {}
-
- self.interval = interval
- self.delay_count = 0
-
- def get_port_stats (self, port_id):
- if self.port_stats.get(port_id):
- return self.port_stats[port_id]
- else:
- return None
-
- def query_sync (self):
- self.delay_count += 1
- if self.delay_count < self.interval:
- return
-
- self.delay_count = 0
-
- # query global stats
-
- # query port stats
-
- rc, resp_list = self.rpc_client.get_port_stats(self.port_list)
- if not rc:
- return
-
- for i, rc in enumerate(resp_list):
- if rc[0]:
- self.port_stats[self.port_list[i]] = rc[1]
-
-
# various kinds of panels
# Server Info Panel
class ServerInfoPanel(TrexStatusPanel):
def __init__ (self, h, l, y, x, status_obj):
- super(ServerInfoPanel, self).__init__(h, l, y ,x ,"Server Info:")
-
- self.status_obj = status_obj
+ super(ServerInfoPanel, self).__init__(h, l, y ,x ,"Server Info:", status_obj)
def draw (self):
- if self.status_obj.server_version == None:
+ if not self.status_obj.server_version :
return
- self.clear()
+ if not self.status_obj.server_sys_info:
+ return
- connection_details = self.status_obj.rpc_client.get_connection_details()
- self.getwin().addstr(3, 2, "{:<30} {:30}".format("Server:",self.status_obj.server_sys_info["hostname"] + ":" + str(connection_details['port'])))
+ self.clear()
+
+ self.getwin().addstr(3, 2, "{:<30} {:30}".format("Server:",self.status_obj.server_sys_info["hostname"] + ":" + str(self.stateless_client.get_connection_port())))
self.getwin().addstr(4, 2, "{:<30} {:30}".format("Version:", self.status_obj.server_version["version"]))
self.getwin().addstr(5, 2, "{:<30} {:30}".format("Build:",
self.status_obj.server_version["build_date"] + " @ " +
@@ -123,7 +90,7 @@ class ServerInfoPanel(TrexStatusPanel):
self.getwin().addstr(9, 2, "{:<30} {:<30}".format("Ports Count:", self.status_obj.server_sys_info["port_count"]))
- ports_owned = " ".join(str(x) for x in self.status_obj.rpc_client.get_owned_ports())
+ ports_owned = " ".join(str(x) for x in self.status_obj.owned_ports_list)
if not ports_owned:
ports_owned = "None"
@@ -134,92 +101,124 @@ class ServerInfoPanel(TrexStatusPanel):
class GeneralInfoPanel(TrexStatusPanel):
def __init__ (self, h, l, y, x, status_obj):
- super(GeneralInfoPanel, self).__init__(h, l, y ,x ,"General Info:")
-
- self.status_obj = status_obj
+ super(GeneralInfoPanel, self).__init__(h, l, y ,x ,"General Info:", status_obj)
def draw (self):
- pass
+ self.clear()
+
+ if not self.general_stats.is_online():
+ self.getwin().addstr(3, 2, "No Published Data From TRex Server")
+ return
+
+ self.getwin().addstr(3, 2, "{:<30} {:0.2f} %".format("CPU util.:", self.general_stats.get("m_cpu_util")))
+
+ self.getwin().addstr(6, 2, "{:<30} {:} / {:}".format("Total Tx. rate:",
+ self.general_stats.get("m_tx_bps", format = True, suffix = "bps"),
+ self.general_stats.get("m_tx_pps", format = True, suffix = "pps")))
+
+
+ self.getwin().addstr(8, 2, "{:<30} {:} / {:}".format("Total Tx:",
+ self.general_stats.get_rel("m_total_tx_bytes", format = True, suffix = "B"),
+ self.general_stats.get_rel("m_total_tx_pkts", format = True, suffix = "pkts")))
+
+ self.getwin().addstr(11, 2, "{:<30} {:} / {:}".format("Total Rx. rate:",
+ self.general_stats.get("m_rx_bps", format = True, suffix = "bps"),
+ self.general_stats.get("m_rx_pps", format = True, suffix = "pps")))
+
+
+ self.getwin().addstr(13, 2, "{:<30} {:} / {:}".format("Total Rx:",
+ self.general_stats.get_rel("m_total_rx_bytes", format = True, suffix = "B"),
+ self.general_stats.get_rel("m_total_rx_pkts", format = True, suffix = "pkts")))
# all ports stats
class PortsStatsPanel(TrexStatusPanel):
def __init__ (self, h, l, y, x, status_obj):
- super(PortsStatsPanel, self).__init__(h, l, y ,x ,"Trex Ports:")
+ super(PortsStatsPanel, self).__init__(h, l, y ,x ,"Trex Ports:", status_obj)
- self.status_obj = status_obj
def draw (self):
self.clear()
- owned_ports = self.status_obj.rpc_client.get_owned_ports()
+ owned_ports = self.status_obj.owned_ports_list
if not owned_ports:
self.getwin().addstr(3, 2, "No Owned Ports - Please Acquire One Or More Ports")
return
# table header
- self.getwin().addstr(3, 2, "{:^15} {:^15} {:^15} {:^15} {:^15} {:^15} {:^15}".format(
- "Port ID", "Tx [pps]", "Tx [bps]", "Tx [bytes]", "Rx [pps]", "Rx [bps]", "Rx [bytes]"))
+ self.getwin().addstr(3, 2, "{:^15} {:^30} {:^30} {:^30}".format(
+ "Port ID", "Tx Rate [bps/pps]", "Rx Rate [bps/pps]", "Total Bytes [tx/rx]"))
+
- # port loop
- self.status_obj.stats.query_sync()
for i, port_index in enumerate(owned_ports):
port_stats = self.status_obj.stats.get_port_stats(port_index)
if port_stats:
- self.getwin().addstr(5 + (i * 4), 2, "{:^15} {:^15,.2f} {:^15,.2f} {:^15,} {:^15,.2f} {:^15,.2f} {:^15,}".format(
+ self.getwin().addstr(5 + (i * 4), 2, "{:^15} {:^30} {:^30} {:^30}".format(
"{0} ({1})".format(str(port_index), self.status_obj.server_sys_info["ports"][port_index]["speed"]),
- port_stats["tx_pps"],
- port_stats["tx_bps"],
- port_stats["total_tx_bytes"],
- port_stats["rx_pps"],
- port_stats["rx_bps"],
- port_stats["total_rx_bytes"]))
-
+ "{0} / {1}".format(port_stats.get("m_total_tx_bps", format = True, suffix = "bps"),
+ port_stats.get("m_total_tx_pps", format = True, suffix = "pps")),
+
+ "{0} / {1}".format(port_stats.get("m_total_rx_bps", format = True, suffix = "bps"),
+ port_stats.get("m_total_rx_pps", format = True, suffix = "pps")),
+ "{0} / {1}".format(port_stats.get_rel("obytes", format = True, suffix = "B"),
+ port_stats.get_rel("ibytes", format = True, suffix = "B"))))
+
else:
- self.getwin().addstr(5 + (i * 4), 2, "{:^15} {:^15} {:^15} {:^15} {:^15} {:^15} {:^15}".format(
+
+ self.getwin().addstr(5 + (i * 4), 2, "{:^15} {:^30} {:^30} {:^30}".format(
"{0} ({1})".format(str(port_index), self.status_obj.server_sys_info["ports"][port_index]["speed"]),
"N/A",
"N/A",
"N/A",
- "N/A",
- "N/A",
"N/A"))
+
+ # old format
+# if port_stats:
+# self.getwin().addstr(5 + (i * 4), 2, "{:^15} {:^15} {:^15} {:^15} {:^15} {:^15} {:^15}".format(
+# "{0} ({1})".format(str(port_index), self.status_obj.server_sys_info["ports"][port_index]["speed"]),
+# port_stats.get("m_total_tx_pps", format = True, suffix = "pps"),
+# port_stats.get("m_total_tx_bps", format = True, suffix = "bps"),
+# port_stats.get_rel("obytes", format = True, suffix = "B"),
+# port_stats.get("m_total_rx_pps", format = True, suffix = "pps"),
+# port_stats.get("m_total_rx_bps", format = True, suffix = "bps"),
+# port_stats.get_rel("ibytes", format = True, suffix = "B")))
+#
+# else:
+# self.getwin().addstr(5 + (i * 4), 2, "{:^15} {:^15} {:^15} {:^15} {:^15} {:^15} {:^15}".format(
+# "{0} ({1})".format(str(port_index), self.status_obj.server_sys_info["ports"][port_index]["speed"]),
+# "N/A",
+# "N/A",
+# "N/A",
+# "N/A",
+# "N/A",
+# "N/A"))
+
# control panel
class ControlPanel(TrexStatusPanel):
def __init__ (self, h, l, y, x, status_obj):
- super(ControlPanel, self).__init__(h, l, y, x, "")
+ super(ControlPanel, self).__init__(h, l, y, x, "", status_obj)
- self.status_obj = status_obj
def draw (self):
self.clear()
self.getwin().addstr(1, 2, "'g' - general, '0-{0}' - specific port, 'f' - freeze, 'c' - clear stats, 'p' - ping server, 'q' - quit"
- .format(self.status_obj.rpc_client.get_port_count() - 1))
-
- index = 3
+ .format(self.status_obj.stateless_client.get_port_count() - 1))
- cut = len(self.status_obj.log) - 4
- if cut < 0:
- cut = 0
-
- for l in self.status_obj.log[cut:]:
- self.getwin().addstr(index, 2, l)
- index += 1
+ self.log.draw(self.getwin(), 2, 3)
# specific ports panels
class SinglePortPanel(TrexStatusPanel):
def __init__ (self, h, l, y, x, status_obj, port_id):
- super(SinglePortPanel, self).__init__(h, l, y, x, "Port {0}".format(port_id))
+ super(SinglePortPanel, self).__init__(h, l, y, x, "Port {0}".format(port_id), status_obj)
- self.status_obj = status_obj
self.port_id = port_id
def draw (self):
@@ -227,7 +226,7 @@ class SinglePortPanel(TrexStatusPanel):
self.clear()
- if not self.port_id in self.status_obj.rpc_client.get_owned_ports():
+ if not self.port_id in self.status_obj.owned_ports_list:
self.getwin().addstr(y, 2, "Port {0} is not owned by you, please acquire the port for more info".format(self.port_id))
return
@@ -241,16 +240,19 @@ class SinglePortPanel(TrexStatusPanel):
y += 2
# streams
- if 'streams' in self.status_obj.snapshot[self.port_id]:
- for stream_id, stream in self.status_obj.snapshot[self.port_id]['streams'].iteritems():
+
+ if 'streams' in self.status_obj.owned_ports[str(self.port_id)]:
+ stream_info = self.status_obj.owned_ports[str(self.port_id)]['streams']
+
+ for stream_id, stream in sorted(stream_info.iteritems(), key=operator.itemgetter(0)):
self.getwin().addstr(y, 2, "{:^15} {:^15} {:^15} {:^15} {:^15} {:^15} {:^15}".format(
stream_id,
- ("True" if stream['stream']['enabled'] else "False"),
- stream['stream']['mode']['type'],
- ("True" if stream['stream']['self_start'] else "False"),
- stream['stream']['isg'],
- (stream['stream']['next_stream_id'] if stream['stream']['next_stream_id'] != -1 else "None"),
- ("{0} instr.".format(len(stream['stream']['vm'])) if stream['stream']['vm'] else "None")))
+ ("True" if stream['enabled'] else "False"),
+ stream['mode']['type'],
+ ("True" if stream['self_start'] else "False"),
+ stream['isg'],
+ (stream['next_stream_id'] if stream['next_stream_id'] != -1 else "None"),
+ ("{0} instr.".format(len(stream['vm'])) if stream['vm'] else "None")))
y += 1
@@ -260,128 +262,174 @@ class SinglePortPanel(TrexStatusPanel):
self.getwin().addstr(y, 2, "Traffic:", curses.A_UNDERLINE)
y += 2
- self.status_obj.stats.query_sync()
- port_stats = self.status_obj.stats.get_port_stats(self.port_id)
- # table header
- self.getwin().addstr(y, 2, "{:^15} {:^15} {:^15} {:^15} {:^15} {:^15} {:^15}".format(
- "Port ID", "Tx [pps]", "Tx [bps]", "Tx [bytes]", "Rx [pps]", "Rx [bps]", "Rx [bytes]"))
+ # table header
+ self.getwin().addstr(y, 2, "{:^15} {:^30} {:^30} {:^30}".format(
+ "Port ID", "Tx Rate [bps/pps]", "Rx Rate [bps/pps]", "Total Bytes [tx/rx]"))
+
y += 2
- if port_stats:
- self.getwin().addstr(y, 2, "{:^15} {:^15,} {:^15,} {:^15,} {:^15,} {:^15,} {:^15,}".format(
- "{0} ({1})".format(str(self.port_id), self.status_obj.server_sys_info["ports"][self.port_id]["speed"]),
- port_stats["tx_pps"],
- port_stats["tx_bps"],
- port_stats["total_tx_bytes"],
- port_stats["rx_pps"],
- port_stats["rx_bps"],
- port_stats["total_rx_bytes"]))
+ port_stats = self.status_obj.stats.get_port_stats(self.port_id)
+ if port_stats:
+ self.getwin().addstr(y, 2, "{:^15} {:^30} {:^30} {:^30}".format(
+ "{0} ({1})".format(str(self.port_id), self.status_obj.server_sys_info["ports"][self.port_id]["speed"]),
+ "{0} / {1}".format(port_stats.get("m_total_tx_bps", format = True, suffix = "bps"),
+ port_stats.get("m_total_tx_pps", format = True, suffix = "pps")),
+
+ "{0} / {1}".format(port_stats.get("m_total_rx_bps", format = True, suffix = "bps"),
+ port_stats.get("m_total_rx_pps", format = True, suffix = "pps")),
+ "{0} / {1}".format(port_stats.get_rel("obytes", format = True, suffix = "B"),
+ port_stats.get_rel("ibytes", format = True, suffix = "B"))))
+
else:
- self.getwin().addstr(y, 2, "{:^15} {:^15} {:^15} {:^15} {:^15} {:^15} {:^15}".format(
+ self.getwin().addstr(y, 2, "{:^15} {:^30} {:^30} {:^30}".format(
"{0} ({1})".format(str(self.port_id), self.status_obj.server_sys_info["ports"][self.port_id]["speed"]),
"N/A",
"N/A",
"N/A",
- "N/A",
- "N/A",
"N/A"))
- y += 2
-# status object
-class TrexStatus():
- def __init__ (self, stdscr, rpc_client):
- self.stdscr = stdscr
+################### main objects #################
+
+# status log
+class TrexStatusLog():
+ def __init__ (self):
self.log = []
- self.rpc_client = rpc_client
- self.snapshot = self.rpc_client.snapshot()
+ def add_event (self, msg):
+ self.log.append("[{0}] {1}".format(str(datetime.datetime.now().time()), msg))
- # fetch server info
- self.get_server_info()
+ def draw (self, window, x, y, max_lines = 4):
+ index = y
- # create stats objects
- self.stats = Stats(rpc_client, self.rpc_client.get_owned_ports())
+ cut = len(self.log) - max_lines
+ if cut < 0:
+ cut = 0
+
+ for msg in self.log[cut:]:
+ window.addstr(index, x, msg)
+ index += 1
+
+# status commands
+class TrexStatusCommands():
+ def __init__ (self, status_object):
+
+ self.status_object = status_object
+
+ self.stateless_client = status_object.stateless_client
+ self.log = self.status_object.log
- # register actions
self.actions = {}
- self.actions[ord('q')] = self.action_quit
- self.actions[ord('p')] = self.action_ping
- self.actions[ord('f')] = self.action_freeze
+ self.actions[ord('q')] = self._quit
+ self.actions[ord('p')] = self._ping
+ self.actions[ord('f')] = self._freeze
- self.actions[ord('g')] = self.action_show_ports_stats
+ self.actions[ord('g')] = self._show_ports_stats
- for port_id in xrange(0, self.rpc_client.get_port_count()):
- self.actions[ord('0') + port_id] = self.action_show_port_generator(port_id)
+ # register all the available ports shortcuts
+ for port_id in xrange(0, self.stateless_client.get_port_count()):
+ self.actions[ord('0') + port_id] = self._show_port_generator(port_id)
+
+
+ # handle a key pressed
+ def handle (self, ch):
+ if ch in self.actions:
+ return self.actions[ch]()
+ else:
+ self.log.add_event("Unknown key pressed, please see legend")
+ return True
+
+ # show all ports
+ def _show_ports_stats (self):
+ self.log.add_event("Switching to all ports view")
+ self.status_object.stats_panel = self.status_object.ports_stats_panel
-
- # all ports stats
- def action_show_ports_stats (self):
- self.add_log_event("Switching to all ports view")
- self.stats_panel = self.ports_stats_panel
-
return True
- # function generator for different ports requests
- def action_show_port_generator (self, port_id):
- def action_show_port():
- self.add_log_event("Switching panel to port {0}".format(port_id))
- self.stats_panel = self.ports_panels[port_id]
+
+ # function generator for different ports requests
+ def _show_port_generator (self, port_id):
+ def _show_port():
+ self.log.add_event("Switching panel to port {0}".format(port_id))
+ self.status_object.stats_panel = self.status_object.ports_panels[port_id]
return True
- return action_show_port
+ return _show_port
- def action_freeze (self):
- self.update_active = not self.update_active
- self.add_log_event("Update continued" if self.update_active else "Update stopped")
+ def _freeze (self):
+ self.status_object.update_active = not self.status_object.update_active
+ self.log.add_event("Update continued" if self.status_object.update_active else "Update stopped")
return True
- def action_quit(self):
+ def _quit(self):
return False
- def action_ping (self):
- self.add_log_event("Pinging RPC server")
+ def _ping (self):
+ self.log.add_event("Pinging RPC server")
- rc, msg = self.rpc_client.ping_rpc_server()
+ rc, msg = self.stateless_client.ping()
if rc:
- self.add_log_event("Server replied: '{0}'".format(msg))
+ self.log.add_event("Server replied: '{0}'".format(msg))
else:
- self.add_log_event("Failed to get reply")
+ self.log.add_event("Failed to get reply")
return True
- def get_server_info (self):
+# status object
+#
+#
+#
+class CTRexStatus():
+ def __init__ (self, stdscr, stateless_client):
+ self.stdscr = stdscr
- self.server_version = self.rpc_client.get_rpc_server_version()
- self.server_sys_info = self.rpc_client.get_system_info()
+ self.stateless_client = stateless_client
+ self.log = TrexStatusLog()
+ self.cmds = TrexStatusCommands(self)
- def add_log_event (self, msg):
- self.log.append("[{0}] {1}".format(str(datetime.datetime.now().time()), msg))
+ self.stats = stateless_client.get_stats_async()
+ self.general_stats = stateless_client.get_stats_async().get_general_stats()
- # control panel
- def update_control (self):
- self.control_panel.clear()
+ # fetch server info
+ self.server_sys_info = self.stateless_client.get_system_info()
- self.control_panel.getwin().addstr(1, 2, "'g' - general, '0-{0}' - specific port, 'f' - freeze, 'c' - clear stats, 'p' - ping server, 'q' - quit"
- .format(self.rpc_client.get_port_count() - 1))
+ self.server_version = self.stateless_client.get_version()
- index = 3
+ # list of owned ports
+ self.owned_ports_list = self.stateless_client.get_acquired_ports()
+
+ # data per port
+ self.owned_ports = {}
- cut = len(self.log) - 4
- if cut < 0:
- cut = 0
+ for port_id in self.owned_ports_list:
+ self.owned_ports[str(port_id)] = {}
+ self.owned_ports[str(port_id)]['streams'] = {}
- for l in self.log[cut:]:
- self.control_panel.getwin().addstr(index, 2, l)
- index += 1
+ stream_list = self.stateless_client.get_all_streams(port_id)
+ self.owned_ports[str(port_id)] = stream_list
+
+
+ try:
+ curses.curs_set(0)
+ except:
+ pass
+
+ curses.use_default_colors()
+ self.stdscr.nodelay(1)
+ curses.nonl()
+ curses.noecho()
+
+ self.generate_layout()
+
+
def generate_layout (self):
self.max_y = self.stdscr.getmaxyx()[0]
self.max_x = self.stdscr.getmaxyx()[1]
@@ -394,7 +442,7 @@ class TrexStatus():
self.ports_stats_panel = PortsStatsPanel(int(self.max_y * 0.8), self.max_x / 2, 0, 0, self)
self.ports_panels = {}
- for i in xrange(0, self.rpc_client.get_port_count()):
+ for i in xrange(0, self.stateless_client.get_port_count()):
self.ports_panels[i] = SinglePortPanel(int(self.max_y * 0.8), self.max_x / 2, 0, 0, self, i)
# at start time we point to the main one
@@ -411,28 +459,25 @@ class TrexStatus():
# no key , continue
if ch == curses.ERR:
return True
-
- # check for registered function
- if ch in self.actions:
- return self.actions[ch]()
- else:
- self.add_log_event("Unknown key pressed, please see legend")
-
- return True
+
+ return self.cmds.handle(ch)
# main run entry point
def run (self):
- try:
- curses.curs_set(0)
- except:
- pass
- curses.use_default_colors()
- self.stdscr.nodelay(1)
- curses.nonl()
- curses.noecho()
+ # list of owned ports
+ self.owned_ports_list = self.stateless_client.get_acquired_ports()
- self.generate_layout()
+ # data per port
+ self.owned_ports = {}
+
+ for port_id in self.owned_ports_list:
+ self.owned_ports[str(port_id)] = {}
+ self.owned_ports[str(port_id)]['streams'] = {}
+
+ stream_list = self.stateless_client.get_all_streams(port_id)
+
+ self.owned_ports[str(port_id)] = stream_list
self.update_active = True
while (True):
@@ -449,19 +494,26 @@ class TrexStatus():
self.stats_panel.panel.top()
self.stats_panel.draw()
- panel.update_panels();
+ panel.update_panels()
self.stdscr.refresh()
sleep(0.01)
-def show_trex_status_internal (stdscr, rpc_client):
- trex_status = TrexStatus(stdscr, rpc_client)
+# global container
+trex_status = None
+
+def show_trex_status_internal (stdscr, stateless_client):
+ global trex_status
+
+ if trex_status == None:
+ trex_status = CTRexStatus(stdscr, stateless_client)
+
trex_status.run()
-def show_trex_status (rpc_client):
+def show_trex_status (stateless_client):
try:
- curses.wrapper(show_trex_status_internal, rpc_client)
+ curses.wrapper(show_trex_status_internal, stateless_client)
except KeyboardInterrupt:
curses.endwin()
diff --git a/scripts/automation/trex_control_plane/console/trex_tui.py b/scripts/automation/trex_control_plane/console/trex_tui.py
new file mode 100644
index 00000000..2e6be4a6
--- /dev/null
+++ b/scripts/automation/trex_control_plane/console/trex_tui.py
@@ -0,0 +1,395 @@
+import termios
+import sys
+import os
+import time
+from common.text_opts import *
+from common import trex_stats
+from client_utils import text_tables
+from collections import OrderedDict
+import datetime
+
+# base type of a panel
+class TrexTUIPanel(object):
+ def __init__ (self, mng, name):
+
+ self.mng = mng
+ self.name = name
+ self.stateless_client = mng.stateless_client
+
+
+ def show (self):
+ raise Exception("must implement this")
+
+ def get_key_actions (self):
+ raise Exception("must implement this")
+
+ def get_name (self):
+ return self.name
+
+
+# dashboard panel
+class TrexTUIDashBoard(TrexTUIPanel):
+ def __init__ (self, mng):
+ super(TrexTUIDashBoard, self).__init__(mng, "dashboard")
+
+ self.key_actions = OrderedDict()
+
+ self.key_actions['c'] = {'action': self.action_clear, 'legend': 'clear', 'show': True}
+ self.key_actions['p'] = {'action': self.action_pause, 'legend': 'pause', 'show': True}
+ self.key_actions['r'] = {'action': self.action_resume, 'legend': 'resume', 'show': True}
+ self.key_actions['+'] = {'action': self.action_raise, 'legend': 'up 5%', 'show': True}
+ self.key_actions['-'] = {'action': self.action_lower, 'legend': 'low 5%', 'show': True}
+
+ self.ports = self.stateless_client.get_all_ports()
+
+
+ def show (self):
+ stats = self.stateless_client.cmd_stats(self.ports, trex_stats.COMPACT)
+ # print stats to screen
+ for stat_type, stat_data in stats.iteritems():
+ text_tables.print_table_with_header(stat_data.text_table, stat_type)
+
+
+ def get_key_actions (self):
+ allowed = {}
+
+ allowed['c'] = self.key_actions['c']
+
+ # thats it for read only
+ if self.stateless_client.is_read_only():
+ return allowed
+
+ if len(self.stateless_client.get_transmitting_ports()) > 0:
+ allowed['p'] = self.key_actions['p']
+ allowed['+'] = self.key_actions['+']
+ allowed['-'] = self.key_actions['-']
+
+
+ if len(self.stateless_client.get_paused_ports()) > 0:
+ allowed['r'] = self.key_actions['r']
+
+ return allowed
+
+
+ ######### actions
+ def action_pause (self):
+ rc = self.stateless_client.pause_traffic(self.mng.ports)
+
+ ports_succeeded = []
+ for rc_single, port_id in zip(rc.rc_list, self.mng.ports):
+ if rc_single.rc:
+ ports_succeeded.append(port_id)
+
+ if len(ports_succeeded) > 0:
+ return "paused traffic on port(s): {0}".format(ports_succeeded)
+ else:
+ return ""
+
+
+ def action_resume (self):
+ rc = self.stateless_client.resume_traffic(self.mng.ports)
+
+ ports_succeeded = []
+ for rc_single, port_id in zip(rc.rc_list, self.mng.ports):
+ if rc_single.rc:
+ ports_succeeded.append(port_id)
+
+ if len(ports_succeeded) > 0:
+ return "resumed traffic on port(s): {0}".format(ports_succeeded)
+ else:
+ return ""
+
+
+ def action_raise (self):
+ mul = {'type': 'percentage', 'value': 5, 'op': 'add'}
+ rc = self.stateless_client.update_traffic(mul, self.mng.ports)
+
+ ports_succeeded = []
+ for rc_single, port_id in zip(rc.rc_list, self.mng.ports):
+ if rc_single.rc:
+ ports_succeeded.append(port_id)
+
+ if len(ports_succeeded) > 0:
+ return "raised B/W by %5 on port(s): {0}".format(ports_succeeded)
+ else:
+ return ""
+
+ def action_lower (self):
+ mul = {'type': 'percentage', 'value': 5, 'op': 'sub'}
+ rc = self.stateless_client.update_traffic(mul, self.mng.ports)
+
+ ports_succeeded = []
+ for rc_single, port_id in zip(rc.rc_list, self.mng.ports):
+ if rc_single.rc:
+ ports_succeeded.append(port_id)
+
+ if len(ports_succeeded) > 0:
+ return "lowered B/W by %5 on port(s): {0}".format(ports_succeeded)
+ else:
+ return ""
+
+
+ def action_clear (self):
+ self.stateless_client.cmd_clear(self.mng.ports)
+ return "cleared all stats"
+
+
+# port panel
+class TrexTUIPort(TrexTUIPanel):
+ def __init__ (self, mng, port_id):
+ super(TrexTUIPort, self).__init__(mng, "port {0}".format(port_id))
+
+ self.port_id = port_id
+ self.port = self.mng.stateless_client.get_port(port_id)
+
+ self.key_actions = OrderedDict()
+
+ self.key_actions['c'] = {'action': self.action_clear, 'legend': 'clear', 'show': True}
+ self.key_actions['p'] = {'action': self.action_pause, 'legend': 'pause', 'show': True}
+ self.key_actions['r'] = {'action': self.action_resume, 'legend': 'resume', 'show': True}
+ self.key_actions['+'] = {'action': self.action_raise, 'legend': 'up 5%', 'show': True}
+ self.key_actions['-'] = {'action': self.action_lower, 'legend': 'low 5%', 'show': True}
+
+
+ def show (self):
+ stats = self.stateless_client.cmd_stats([self.port_id], trex_stats.COMPACT)
+ # print stats to screen
+ for stat_type, stat_data in stats.iteritems():
+ text_tables.print_table_with_header(stat_data.text_table, stat_type)
+
+ def get_key_actions (self):
+
+ allowed = {}
+
+ allowed['c'] = self.key_actions['c']
+
+ # thats it for read only
+ if self.stateless_client.is_read_only():
+ return allowed
+
+ if self.port.state == self.port.STATE_TX:
+ allowed['p'] = self.key_actions['p']
+ allowed['+'] = self.key_actions['+']
+ allowed['-'] = self.key_actions['-']
+
+ elif self.port.state == self.port.STATE_PAUSE:
+ allowed['r'] = self.key_actions['r']
+
+
+ return allowed
+
+ # actions
+ def action_pause (self):
+ rc = self.stateless_client.pause_traffic([self.port_id])
+ if rc.good():
+ return "port {0}: paused traffic".format(self.port_id)
+ else:
+ return ""
+
+ def action_resume (self):
+ rc = self.stateless_client.resume_traffic([self.port_id])
+ if rc.good():
+ return "port {0}: resumed traffic".format(self.port_id)
+ else:
+ return ""
+
+ def action_raise (self):
+ mul = {'type': 'percentage', 'value': 5, 'op': 'add'}
+ rc = self.stateless_client.update_traffic(mul, [self.port_id])
+
+ if rc.good():
+ return "port {0}: raised B/W by 5%".format(self.port_id)
+ else:
+ return ""
+
+ def action_lower (self):
+ mul = {'type': 'percentage', 'value': 5, 'op': 'sub'}
+ rc = self.stateless_client.update_traffic(mul, [self.port_id])
+
+ if rc.good():
+ return "port {0}: lowered B/W by 5%".format(self.port_id)
+ else:
+ return ""
+
+ def action_clear (self):
+ self.stateless_client.cmd_clear([self.port_id])
+ return "port {0}: cleared stats".format(self.port_id)
+
+# log
+class TrexTUILog():
+ def __init__ (self):
+ self.log = []
+
+ def add_event (self, msg):
+ self.log.append("[{0}] {1}".format(str(datetime.datetime.now().time()), msg))
+
+ def show (self, max_lines = 4):
+ cut = len(self.log) - max_lines
+ if cut < 0:
+ cut = 0
+
+ print format_text("\nLog:", 'bold', 'underline')
+
+ for msg in self.log[cut:]:
+ print msg
+
+
+# Panels manager (contains server panels)
+class TrexTUIPanelManager():
+ def __init__ (self, tui):
+ self.tui = tui
+ self.stateless_client = tui.stateless_client
+ self.ports = self.stateless_client.get_all_ports()
+
+
+ self.panels = {}
+ self.panels['dashboard'] = TrexTUIDashBoard(self)
+
+ self.key_actions = OrderedDict()
+ self.key_actions['q'] = {'action': self.action_quit, 'legend': 'quit', 'show': True}
+ self.key_actions['g'] = {'action': self.action_show_dash, 'legend': 'dashboard', 'show': True}
+
+ for port_id in self.ports:
+ self.key_actions[str(port_id)] = {'action': self.action_show_port(port_id), 'legend': 'port {0}'.format(port_id), 'show': False}
+ self.panels['port {0}'.format(port_id)] = TrexTUIPort(self, port_id)
+
+ # start with dashboard
+ self.main_panel = self.panels['dashboard']
+
+ # log object
+ self.log = TrexTUILog()
+
+ self.generate_legend()
+
+
+ def generate_legend (self):
+ self.legend = "\n{:<12}".format("browse:")
+
+ for k, v in self.key_actions.iteritems():
+ if v['show']:
+ x = "'{0}' - {1}, ".format(k, v['legend'])
+ self.legend += "{:}".format(x)
+
+ self.legend += "'0-{0}' - port display".format(len(self.ports) - 1)
+
+
+ self.legend += "\n{:<12}".format(self.main_panel.get_name() + ":")
+ for k, v in self.main_panel.get_key_actions().iteritems():
+ if v['show']:
+ x = "'{0}' - {1}, ".format(k, v['legend'])
+ self.legend += "{:}".format(x)
+
+
+ def print_legend (self):
+ print format_text(self.legend, 'bold')
+
+
+ # on window switch or turn on / off of the TUI we call this
+ def init (self):
+ self.generate_legend()
+
+ def show (self):
+ self.main_panel.show()
+ self.print_legend()
+ self.log.show()
+
+
+ def handle_key (self, ch):
+ # check for the manager registered actions
+ if ch in self.key_actions:
+ msg = self.key_actions[ch]['action']()
+
+ # check for main panel actions
+ elif ch in self.main_panel.get_key_actions():
+ msg = self.main_panel.get_key_actions()[ch]['action']()
+
+ else:
+ msg = ""
+
+ self.generate_legend()
+
+ if msg == None:
+ return False
+ else:
+ if msg:
+ self.log.add_event(msg)
+ return True
+
+
+ # actions
+
+ def action_quit (self):
+ return None
+
+ def action_show_dash (self):
+ self.main_panel = self.panels['dashboard']
+ self.init()
+ return ""
+
+ def action_show_port (self, port_id):
+ def action_show_port_x ():
+ self.main_panel = self.panels['port {0}'.format(port_id)]
+ self.init()
+ return ""
+
+ return action_show_port_x
+
+
+
+# shows a textual top style window
+class TrexTUI():
+ def __init__ (self, stateless_client):
+ self.stateless_client = stateless_client
+
+ self.pm = TrexTUIPanelManager(self)
+
+
+
+ def handle_key_input (self):
+ # try to read a single key
+ ch = os.read(sys.stdin.fileno(), 1)
+ if ch != None and len(ch) > 0:
+ return self.pm.handle_key(ch)
+
+ else:
+ return True
+
+
+ def clear_screen (self):
+ os.system('clear')
+
+
+
+ def show (self):
+ # init termios
+ old_settings = termios.tcgetattr(sys.stdin)
+ new_settings = termios.tcgetattr(sys.stdin)
+ new_settings[3] = new_settings[3] & ~(termios.ECHO | termios.ICANON) # lflags
+ new_settings[6][termios.VMIN] = 0 # cc
+ new_settings[6][termios.VTIME] = 0 # cc
+ termios.tcsetattr(sys.stdin, termios.TCSADRAIN, new_settings)
+
+ self.pm.init()
+
+ try:
+ while True:
+ self.clear_screen()
+
+ cont = self.handle_key_input()
+ self.pm.show()
+
+ if not cont:
+ break
+
+ time.sleep(0.1)
+
+ finally:
+ # restore
+ termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)
+
+ print ""
+
+ # key actions
+ def action_quit (self):
+ return False
+
diff --git a/scripts/automation/trex_control_plane/examples/stateless_example.py b/scripts/automation/trex_control_plane/examples/stateless_example.py
new file mode 100755
index 00000000..bb0fe983
--- /dev/null
+++ b/scripts/automation/trex_control_plane/examples/stateless_example.py
@@ -0,0 +1,30 @@
+#!/router/bin/python
+
+import trex_root_path
+from client.trex_hltapi import CTRexHltApi
+
+if __name__ == "__main__":
+ port_list = [1,2]
+ try:
+ hlt_client = CTRexHltApi()
+ con = hlt_client.connect("localhost", port_list, "danklei", break_locks=True, reset=True)#, port=6666)
+ print con
+
+ res = hlt_client.traffic_config("create", 1)#, ip_src_addr="2000.2.2")
+ print res
+ res = hlt_client.traffic_config("create", 2)#, ip_src_addr="2000.2.2")
+ print res
+
+ res = hlt_client.traffic_control("run", [1, 2])#, ip_src_addr="2000.2.2")
+ print res
+
+ res = hlt_client.traffic_control("stop", [1, 2])#, ip_src_addr="2000.2.2")
+ print res
+
+
+
+ except Exception as e:
+ raise
+ finally:
+ res = hlt_client.cleanup_session(port_list)
+ print res \ No newline at end of file
diff --git a/scripts/automation/trex_control_plane/server/extended_daemon_runner.py b/scripts/automation/trex_control_plane/server/extended_daemon_runner.py
index 734fa22e..7bc25aac 100755
--- a/scripts/automation/trex_control_plane/server/extended_daemon_runner.py
+++ b/scripts/automation/trex_control_plane/server/extended_daemon_runner.py
@@ -19,7 +19,6 @@ def daemonize_parser(parser_obj, action_funcs, help_menu):
parser_obj.usage = None
parser_obj.add_argument("action", choices=action_funcs,
action="store", help=help_menu)
- return
class ExtendedDaemonRunner(runner.DaemonRunner):
@@ -76,7 +75,12 @@ class ExtendedDaemonRunner(runner.DaemonRunner):
self.app = app
self.daemon_context = daemon.DaemonContext()
self.daemon_context.stdin = open(app.stdin_path, 'rt')
- self.daemon_context.stdout = open(app.stdout_path, 'w+t')
+ try:
+ self.daemon_context.stdout = open(app.stdout_path, 'w+t')
+ except IOError as err:
+ # catch 'tty' error when launching server from remote location
+ app.stdout_path = "/dev/null"
+ self.daemon_context.stdout = open(app.stdout_path, 'w+t')
self.daemon_context.stderr = open(app.stderr_path,
'a+t', buffering=0)
diff --git a/scripts/automation/trex_control_plane/server/trex_daemon_server.py b/scripts/automation/trex_control_plane/server/trex_daemon_server.py
index ec07cb8a..9784d42a 100755
--- a/scripts/automation/trex_control_plane/server/trex_daemon_server.py
+++ b/scripts/automation/trex_control_plane/server/trex_daemon_server.py
@@ -57,15 +57,7 @@ def main ():
print "Launching user must have sudo privileges in order to run TRex daemon.\nTerminating daemon process."
exit(-1)
- try:
- daemon_runner = ExtendedDaemonRunner(trex_app, trex_parser)
- except IOError as err:
- # catch 'tty' error when launching server from remote location
- if err.errno == errno.ENXIO:
- trex_app.stdout_path = "/dev/null"
- daemon_runner = ExtendedDaemonRunner(trex_app, trex_parser)
- else:
- raise
+ daemon_runner = ExtendedDaemonRunner(trex_app, trex_parser)
#This ensures that the logger file handle does not get closed during daemonization
daemon_runner.daemon_context.files_preserve=[handler.stream]
diff --git a/scripts/automation/trex_control_plane/server/trex_server.py b/scripts/automation/trex_control_plane/server/trex_server.py
index 7dee89e9..b1ce54e1 100755
--- a/scripts/automation/trex_control_plane/server/trex_server.py
+++ b/scripts/automation/trex_control_plane/server/trex_server.py
@@ -340,11 +340,12 @@ class CTRexServer(object):
Parameters
----------
- trex_cmd_options : str
- Defines the exact command to run on the t-rex
- Example: "-c 2 -m 0.500000 -d 100 -f cap2/sfr.yaml --nc -p -l 1000"
+ kwargs: dictionary
+ Dictionary of parameters for trex. For example: (c=1, nc=True, l_pkt_mode=3).
+ Notice that when sending command line parameters that has -, you need to replace it with _.
+ for example, to have on command line "--l-pkt-mode 3", you need to send l_pkt_mode=3
export_path : str
- a full system path to which the results of the trex-run will be logged.
+ Full system path to which the results of the trex-run will be logged.
"""
if 'results_file_path' in kwargs: