From 0c5a4348a31e0e8d76dd1fcf378cb2c0a2867f59 Mon Sep 17 00:00:00 2001 From: Dan Klein Date: Thu, 15 Oct 2015 09:57:35 +0300 Subject: updated yaml utils and stream object --- .../client_utils/external_packages.py | 3 +- .../trex_control_plane/client_utils/yaml_utils.py | 118 +++++++++++++ .../trex_control_plane/common/external_packages.py | 28 +++ .../trex_control_plane/common/rpc_defaults.yaml | 107 ++++++++++++ .../trex_control_plane/common/trex_status.py | 8 + .../trex_control_plane/common/trex_streams.py | 190 +++++++++++++++++++++ 6 files changed, 453 insertions(+), 1 deletion(-) create mode 100644 scripts/automation/trex_control_plane/client_utils/yaml_utils.py create mode 100755 scripts/automation/trex_control_plane/common/external_packages.py create mode 100644 scripts/automation/trex_control_plane/common/rpc_defaults.yaml create mode 100644 scripts/automation/trex_control_plane/common/trex_status.py create mode 100644 scripts/automation/trex_control_plane/common/trex_streams.py 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 4b10609b..e2bb37a5 100755 --- a/scripts/automation/trex_control_plane/client_utils/external_packages.py +++ b/scripts/automation/trex_control_plane/client_utils/external_packages.py @@ -8,7 +8,8 @@ ROOT_PATH = os.path.abspath(os.path.join(CURRENT_PATH, os.pardir)) PATH_TO_PYTHON_LIB = os.path.abspath(os.path.join(ROOT_PATH, os.pardir, os.pardir, 'external_libs')) CLIENT_UTILS_MODULES = ['zmq', - 'dpkt-1.8.6' + 'dpkt-1.8.6', + 'PyYAML-3.01/lib' ] def import_client_utils_modules(): diff --git a/scripts/automation/trex_control_plane/client_utils/yaml_utils.py b/scripts/automation/trex_control_plane/client_utils/yaml_utils.py new file mode 100644 index 00000000..f04449ac --- /dev/null +++ b/scripts/automation/trex_control_plane/client_utils/yaml_utils.py @@ -0,0 +1,118 @@ + +''' +Dan Klein +Cisco Systems, Inc. + +Copyright (c) 2015-2015 Cisco Systems, Inc. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +''' + +import external_packages +import yaml + +def yaml_obj_validator(evaluated_obj, yaml_reference_file_path, root_obj, fill_defaults=True): + """ + validate SINGLE ROOT object with yaml reference file. + Fills up default values if hasn't been assigned by user and available. + + :param evaluated_obj: python object that should match the yaml reference file + :param yaml_reference_file_path: + :param fill_defaults: + :return: a python representation object of the YAML file if OK + """ + type_dict = {"double":float, + "int":int, + "array":list, + "string":str, + "boolean":bool} + + def validator_rec_helper(obj_to_eval, ref_obj, root_key): + ref_item = ref_obj.get(root_key) + if ref_item is not None: + if "type" in obj_to_eval: + ref_item = ref_item[obj_to_eval.get("type")] + if isinstance(ref_item, dict) and "type" not in ref_item: # this is not a terminal + result_obj = {} + # iterate over key-value pairs + for k, v in ref_item.items(): + if k in obj_to_eval: + # need to validate with ref obj + tmp_type = v.get('type') + if tmp_type == "object": + # go deeper into nesting hierarchy + result_obj[k] = validator_rec_helper(obj_to_eval.get(k), ref_obj, k) + elif isinstance(tmp_type, list): + # item can be one of multiple types + python_types = set() + for t in tmp_type: + if t in type_dict: + python_types.add(type_dict.get(t)) + else: + raise TypeError("Unknown resolving for type {0}".format(t)) + if type(obj_to_eval[k]) not in python_types: + raise TypeError("Type of object field '{0}' is not allowed".format(k)) + + else: + # this is a single type field + python_type = type_dict.get(tmp_type) + if not isinstance(obj_to_eval[k], python_type): + raise TypeError("Type of object field '{0}' is not allowed".format(k)) + else: + # WE'RE OK! + result_obj[k] = obj_to_eval[k] + else: + # this is an object field that wasn't specified by the user + if v.get('has_default'): + # WE'RE OK! + result_obj[k] = v.get('default') + else: + # This is a mandatory field! + raise ValueError("The {0} field is mandatory and must be specified explicitly".format(v)) + return result_obj + elif isinstance(ref_item, list): + return [] + else: + + + + else: + raise KeyError("The given key is not ") + + + + + pass + pass + elif isinstance(obj_to_eval, list): + # iterate as list sequence + pass + else: + # evaluate as single item + pass + pass + + try: + yaml_ref = yaml.load(file(yaml_reference_file_path, 'r')) + result = validator_rec_helper(evaluated_obj, yaml_ref, root_obj) + + except yaml.YAMLError as e: + raise + except Exception: + raise + +def yaml_loader(file_path): + pass + +def yaml_exporter(file_path): + pass + +if __name__ == "__main__": + pass diff --git a/scripts/automation/trex_control_plane/common/external_packages.py b/scripts/automation/trex_control_plane/common/external_packages.py new file mode 100755 index 00000000..62121d4f --- /dev/null +++ b/scripts/automation/trex_control_plane/common/external_packages.py @@ -0,0 +1,28 @@ +#!/router/bin/python + +import sys +import os + +CURRENT_PATH = os.path.dirname(os.path.realpath(__file__)) +ROOT_PATH = os.path.abspath(os.path.join(CURRENT_PATH, os.pardir)) # path to trex_control_plane directory +PATH_TO_PYTHON_LIB = os.path.abspath(os.path.join(ROOT_PATH, os.pardir, os.pardir, 'external_libs')) + +CLIENT_UTILS_MODULES = ['PyYAML-3.01/lib' + ] + +def import_common_modules(): + # must be in a higher priority + sys.path.insert(0, PATH_TO_PYTHON_LIB) + sys.path.append(ROOT_PATH) + import_module_list(CLIENT_UTILS_MODULES) + + +def import_module_list(modules_list): + assert(isinstance(modules_list, list)) + for p in modules_list: + full_path = os.path.join(PATH_TO_PYTHON_LIB, p) + fix_path = os.path.normcase(full_path) + sys.path.insert(1, full_path) + +import_common_modules() + diff --git a/scripts/automation/trex_control_plane/common/rpc_defaults.yaml b/scripts/automation/trex_control_plane/common/rpc_defaults.yaml new file mode 100644 index 00000000..fbaca40e --- /dev/null +++ b/scripts/automation/trex_control_plane/common/rpc_defaults.yaml @@ -0,0 +1,107 @@ +############################################################## +#### TRex RPC stream list default values #### +############################################################## + +# this document is based on TRex RPC server spec and its fields: +# http://trex-tgn.cisco.com/trex/doc/trex_rpc_server_spec.html + +### HOW TO READ THIS FILE +# 1. Each key represents an object type +# 2. Each value can be either a value field or another object +# 2.1. If a value field, read as: +# + type: type of field +# + has_default: if the value has any default +# + default: the default value (Only appears if has_default field is 'YES') +# 2.2. If an object type, jump to correspoding object key. +# 3. If an object has more than one instance type, another layer with the type shall be added. +# For example, 'mode' object has 3 types: 'continuous', 'single_burst', 'multi_burst' +# So, 3 mode objects will be defined, named: +# - mode['continuous'] +# - mode['single_burst'] +# - mode['multi_burst'] +# In this case, there's no default for the 'type' field on the object +# 4. Some values has 'multiplier' property attached. +# In such case, the loaded value will be multiplied by the multiplier +# For example, if the mode's 'pps' field value is 10, and its multiplier is 5, +# the loaded pps value will be 10*5=50 + + +stream: + enabled: + type: boolean + has_default: YES + default: True + self_start: + type: boolean + has_default: YES + default: True + isg: + type: double + has_default: YES + default: 0.0 + next_stream_id: + type: [int, string] # string to allow naming binding + has_default: YES + default: -1 # no next streams + packet: + type: object + mode: + type: object + vm: + type: array + has_default: YES + default: [] # no ranging instructions + rx_stats: + type: object + +packet: + binary: + type: [array,string] + has_default: NO + meta: + type: string + has_default: YES + default: "" + +mode: + continuous: + pps: + type: double + has_default: NO + multiplier: 1.0 + single_burst: + pps: + type: int + has_default: NO + total_pkts: + type: int + has_default: NO + multiplier: 1.0 + multi_burst: + pps: + type: int + has_default: NO + pkts_per_burst: + type: int + has_default: NO + ibg: + type: double + has_default: YES + default: 100.0 + count: + type: int + has_default: YES + default: 0 # loop forever + multiplier: 1.0 + +rx_stats: + enabled: + type: boolean + has_default: YES + default: False + seq_enabled: + type: boolean + has_default: NO + latency_enabled: + type: boolean + has_default: NO \ No newline at end of file diff --git a/scripts/automation/trex_control_plane/common/trex_status.py b/scripts/automation/trex_control_plane/common/trex_status.py new file mode 100644 index 00000000..f132720c --- /dev/null +++ b/scripts/automation/trex_control_plane/common/trex_status.py @@ -0,0 +1,8 @@ +#!/router/bin/python + +# define the states in which a T-Rex can hold during its lifetime +# TRexStatus = Enum('TRexStatus', 'Idle Starting Running') + +IDLE = 1 +STARTING = 2 +RUNNING = 3 diff --git a/scripts/automation/trex_control_plane/common/trex_streams.py b/scripts/automation/trex_control_plane/common/trex_streams.py new file mode 100644 index 00000000..3b0e7376 --- /dev/null +++ b/scripts/automation/trex_control_plane/common/trex_streams.py @@ -0,0 +1,190 @@ +#!/router/bin/python + +import external_packages +from client_utils.packet_builder import CTRexPktBuilder +from collections import OrderedDict + + +class CStreamList(object): + + def __init__(self): + self.streams_list = OrderedDict() + self._stream_id = 0 + # self._stream_by_name = {} + + 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.") + self.streams_list[name]=stream_obj + return + + def remove_stream(self, name): + return self.streams_list.pop(name) + + def export_to_yaml(self, file_path): + pass + + def load_yaml(self, file_path): + # clear all existing streams linked to this object + self.streams_list.clear() + # self._stream_id = 0 + # load from YAML file the streams one by one + try: + with open(file_path, 'r') as f: + loaded_streams = yaml.load(f) + + # assume at this point that YAML file is according to rules and correct + + + except yaml.YAMLError as e: + print "Error in YAML configuration file:", e + print "Aborting YAML loading, no changes made to stream list" + return + + + pass + + + + +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 -- cgit 1.2.3-korg