diff options
author | 2015-12-13 17:18:02 +0200 | |
---|---|---|
committer | 2015-12-13 17:18:02 +0200 | |
commit | 9738e267d806223ee25e013b5959ccac26c1a14a (patch) | |
tree | 590c8f329f2ab68c7da3f1f8f4c55f81243a08bc /scripts/automation/trex_control_plane/client_utils | |
parent | a573adc6395c9ad8d96978508a07a654ef48c7a9 (diff) | |
parent | 301341ddb1bf17387d7fea19667bedd40fce4509 (diff) |
Merge branch 'master' into get_logs_and_version
Diffstat (limited to 'scripts/automation/trex_control_plane/client_utils')
5 files changed, 401 insertions, 309 deletions
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 + |