diff --git a/README-Valkyrie2544.md b/README-Valkyrie2544.md new file mode 100644 index 0000000..8b71b2a --- /dev/null +++ b/README-Valkyrie2544.md @@ -0,0 +1,75 @@ +# Valkyrie2544 + + +## Installation / Setup + +1. Download the trafficgen git repository + ``` + git clone https://github.com/atheurer/trafficgen + ``` + +2. For Linux, install Mono (https://www.mono-project.com/): + * instructions are for CentOS/RHEL/Fedora, other distros please refer to https://www.mono-project.com/download/stable/#download-lin + * commands should be run in a root shell + * Add Mono repo to system + ```bash + rpmkeys --import "http://pool.sks-keyservers.net/pks/lookup?op=get&search=0x3fa7e0328081bff6a14da29aa6a19b38d3d831ef" + su -c 'curl https://download.mono-project.com/repo/centos8-stable.repo | tee /etc/yum.repos.d/mono-centos8-stable.repo' + ``` + * Install Mono + ```bash + yum install mono-complete + ``` +3. Download the latest copy of Valkyrie2544.exe and the x2544 config file from the control box + +4. Make sure both Valkyrie2544.exe and config file are present in the local trafficgen repo folder + +## Running +Arguments: +* `--traffic-profile ` : saved from Valkyrie2544.exe GUI with your config. + +* `--valkyrie2544-smart_search` : enable smart search, if verify fails will resume the search at the half way point between the last verify attempt and the minimum search value. Otherwise the search will resume at the last verify attempt value, minus the value threshhold. + +* `--validation-runtime` : sets the length of verification in seconds + > Default : 600 (10 minutes) + +* `--max-retries` : Maximum number of verify attempts before giving up + > Default : 1 + +* `--valkyrie2544-pdf_output` : Output PDF file. By default output of PDF report is disabled. Will cause a crash on linux usually as a pdf renderer is not installed. + +* `--valkyrie2544-windows_mode` : Enable windows mode. By default the mono package will be used to run the .exe file. If running on windows this is not necessary. + +* `--search-runtime` : Modify original config to use the duration specified. + +* `--valkyrie2544-packet_sizes` : Customize packet sizes for throughput testing + +* `--max-loss-pct` : Specify number of packages which can be lost as a percentage ([0 - 100]) + +* `--valkyrie2544-save_file_name` : Save config file which was created with the new arguments passed to this command. + > Default : `./2bUsed.x2544` + +* `--valkyrie2544-initial_tput` : Specify initial rate for throughput test + +* `--rate` : Specify maximum rate for throughput test + +* `--min-rate` : Specify minimum rate for throughput test + +* `--valkyrie2544-resolution_tput` : Specify resolution for throughput testing + +* `--src-macs`: MAC address that becomes source of first active entity and destination for second (if two exist). + +* `--dst-macs` : If specified, becomes destination of first active entity and source for second + +* `--src-ips` : IP address that becomes source of first active entity and destination for second (if two exist). + +* `--dst-ips` : If specified, becomes destination of first active entity (src-ips) and source for second + +* `--use-src-ip-flows` / `--use-dst-ip-flows` : Apply flows to both MAC and IP addresses (if enabled overrides MAC flows option). Invoking either src-ip or dst-ip, or both, yields the same result + > Default : 1 + +* `--use-src-mac-flows` / `--use-dst-mac-flows` : Apply flows to MAC addresses only (overridden if IP flows are enabled). Invoking either src-mac or dst-mac, or both, yields the same result + > Default: 1 + +* `--xena_module` : Specify int corresponding to the Xena chassis module number. + diff --git a/README-XenaVerify.md b/README-XenaVerify.md new file mode 100644 index 0000000..f7a28c4 --- /dev/null +++ b/README-XenaVerify.md @@ -0,0 +1,114 @@ +# Xena2544ThroughputVerify +## Setup: + +1. For linux Install Mono -> + + ```bash + rpm --import "http://keyserver.ubuntu.com/pks/lookup?op=get&search=0x3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF" + ``` + + ```bash + yum-config-manager --add-repo http://download.mono-project.com/repo/centos/ + ``` + + ```bash + yum -y install mono-complete-5.8.0.127-0.xamarin.3.epel7.x86_64 + ``` + +2. If python 3 not installed, install python 3. For RHEL instructions are below-> + + ``` + cat <<'EOT' >> /etc/yum.repos.d/python34.repo + + [centos-sclo-rh] + + name=CentOS-7 - SCLo rh + + baseurl=http://mirror.centos.org/centos/7/sclo/$basearch/rh/ + + gpgcheck=0 + + enabled=1 + + gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-SIG-SCLo + + EOT + ``` + + # install python34 scl package + + ```bash + yum -y install rh-python34 rh-python34-python-tkinter + ``` + + # cleanup python 34 repo file + + ```bash + rm -f /etc/yum.repos.d/python34.repo + ``` + +3. Enable python34 -> `scl enable rh-python34 bash` + +4. Make sure Valkyrie2544.exe is present in the current folder (formerly Xena2544.exe) + +5. Copy your x2544 config file to the script folder + +## Arguments to run this script: + + * `-f ` : saved from Valkyrie2544.exe GUI with your config. + + * `[-s]` : enable smart search, if verify fails will resume the search at the half way point between the last verify attempt and the minimum search value. Otherwise it will just resume at the last verify attempt value minus the value threshhold. + + * `[-l ]` : + > Default : 7200 (2 hours) + + * `[-r ]` : Maximum number of verify attempts for giving up + > Default : 10 + + * `[-d]` : Enable debug mode + + * `[-p]` : Output PDF file. By default output of PDF report is disabled. Will cause a crash on linux usually as a pdf renderer is not installed. + + * `[-w]` : Enable windows mode. By default it will use the mono package to run the exe file. If running on windows this is not necessary. + + * `[-t ]` : Modify original config to use the duration specified. + > Default : 0 + + * `[-k +]` : Customize packet sizes for throughput testing + + * `[-a ]` : Specify number of packages which can be lost as a percentage ([0 - 100]) + + * `[-v ]` : Save config file which was created with the new arguments passed to this command. + > Default : `./2bUsed.x2544` + + * `[-i ]` : Specify initial rate for throughput test + + * `[-M ]` : Specify maximum rate for throughput test + + * `[-m ]` : Specify minimum rate for throughput test + + * `[-o ]` : Specify resolution for throughput testing + + * `[-n []]` : First MAC address becomes source of first active entity and destination for second (if two exist). Vice versa for the optional second argument. + + * `[-c []]` : First IP address becomes source of first active entity and destination for second (if two exist). Vice versa for the optional second argument. + + * `[-u {1|1k|4k|10k|100k|1M}]` : Specify hardware modifier flows. Default behavior is to apply this to source and destination IP addresses + * `[-b]` : Apply flows to both MAC and IP addresses (overrides `[-e]`) + * `[-e]` : Apply flows to MAC addresses only + + * `--module` : Specify int corresponding to the Xena chassis module number. + +## Sample execution: + + > Runs a 60 second trial with a 600 second verify using the myconfig.x2544 configuration file. + + ```bash + python XenaVerify.py -f myconfig.x2544 -s -l 600 -t 60 + ``` + +#### Improvements to be done + +* Add debug logging + +* Add more customized options for modifying the running config diff --git a/XenaVerify.py b/XenaVerify.py new file mode 100644 index 0000000..3bc3422 --- /dev/null +++ b/XenaVerify.py @@ -0,0 +1,651 @@ +# Copyright 2016-2017 Red Hat Inc & Xena Networks. +# +# 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. + +# Contributors: +# Christian Trautman, Red Hat Inc. +# + +import argparse +import json +import locale +import logging +import os +import subprocess +import sys +from time import sleep +import xml.etree.ElementTree as ET +import base64 +#import pdb # debug + +import pprint + +pp = pprint.PrettyPrinter(indent=4) + +_LOGGER = logging.getLogger(__name__) +_LOCALE = locale.getlocale()[1] +_XENA_USER = 'TestUser' +_PYTHON_2 = sys.version_info[0] < 3 + +_FLOWS = { + '1': { + 'flows': 1, + 'stops': [0], + 'masks': ['//8='], + 'repeats': [1], + 'offsets': [2], + }, + '1k': { + 'flows': 1000, + 'stops': [999], + 'masks': ['//8='], + 'repeats': [1], + 'offsets': [2] + }, + '4k': { + 'flows': 4000, + 'stops': [3999], + 'masks': ['//8='], + 'repeats': [1], + 'offsets': [2] + }, + '10k': { + 'flows': 10000, + 'stops': [9999], + 'masks': ['//8='], + 'repeats': [1], + 'offsets': [2] + }, + '100k': { + 'flows': 100000, + 'stops': [999, 99], + 'masks': ['D/8=', '//A='], + 'repeats': [1, 1000], + 'offsets': [2, 1] + }, + '1M': { + 'flows': 1000000, + 'stops': [999, 999], + 'masks': ['D/8=', '//A='], + 'repeats': [1, 1000], + 'offsets': [2, 1] + } +} + +class XenaJSON(object): + """ + Class to modify and read Xena JSON configuration files. + """ + def __init__(self, json_path): + """ + Constructor + :param json_path: path to JSON file to read. Expected files must have + two module ports with each port having its own stream config profile. + :return: XenaJSON object + """ + self.json_data = read_json_file(json_path) + self.min_tput = self.json_data['TestOptions']['TestTypeOptionMap'][ + 'Throughput']['RateIterationOptions']['MinimumValue'] + self.init_tput = self.json_data['TestOptions']['TestTypeOptionMap'][ + 'Throughput']['RateIterationOptions']['InitialValue'] + self.max_tput = self.json_data['TestOptions']['TestTypeOptionMap'][ + 'Throughput']['RateIterationOptions']['MaximumValue'] + self.value_thresh = self.json_data['TestOptions']['TestTypeOptionMap'][ + 'Throughput']['RateIterationOptions']['ValueResolution'] + self.duration = self.json_data['TestOptions']['TestTypeOptionMap'][ + 'Throughput']['Duration'] + + self.packet_sizes = self.json_data['TestOptions']['PacketSizes'][ + 'CustomPacketSizes'] + self.accept_loss = self.json_data['TestOptions']['TestTypeOptionMap'][ + 'Throughput']['RateIterationOptions']['AcceptableLoss'] + self.active_ids = list(self.json_data['StreamProfileHandler'][ + 'ProfileAssignmentMap'].values()) # Assume 1 <= len(active_ids) <=2 + self.entities = self.json_data['StreamProfileHandler']['EntityList'] + self.active_entities = [x for x in self.entities if x['ItemID'] in self.active_ids] + + # pylint: disable=too-many-arguments + def modify_2544_tput_options(self, initial_value=None, value_resolution = None, + minimum_value=None, maximum_value=None): + + + self.init_tput = initial_value = self.init_tput if initial_value == None else initial_value + self.min_tput = minimum_value = self.min_tput if minimum_value == None else minimum_value + self.max_tput = maximum_value = self.max_tput if maximum_value == None else maximum_value + self.value_thresh = value_resolution = self.value_thresh if value_resolution == None else value_resolution + """ + modify_2544_tput_options + """ + self.json_data['TestOptions']['TestTypeOptionMap']['Throughput'][ + 'RateIterationOptions']['InitialValue'] = initial_value + self.json_data['TestOptions']['TestTypeOptionMap']['Throughput'][ + 'RateIterationOptions']['MinimumValue'] = minimum_value + self.json_data['TestOptions']['TestTypeOptionMap']['Throughput'][ + 'RateIterationOptions']['MaximumValue'] = maximum_value + + + def modify_module(self, module): + """ + Modify module + :param module: the module number to be used as int + :return: None + """ + for entity in self.json_data['PortHandler']['EntityList']: + entity['PortRef']['ModuleIndex'] = module + + def modify_duration(self, duration): + """ + Modify test duration + :param duration: test time duration in seconds as int + :return: None + """ + self.json_data['TestOptions']['TestTypeOptionMap']['Throughput'][ + 'Duration'] = duration + + def modify_latency(self): + """ + Enable Latency in json file + :return: None + """ + self.json_data['TestOptions']['TestTypeOptionMap']['Throughput'][ + 'ReportPropertyOptions'] = ['LatencyCounters'] + + def modify_packet_size(self, packet_sizes): + """ + Modify custom packet sizes + :return: None + """ + self.json_data['TestOptions']['PacketSizes']['CustomPacketSizes'] = packet_sizes + + def modify_acceptable_loss(self, acceptable_loss): + """ + Modify acceptable loss + :return: None + """ + self.json_data['TestOptions']['TestTypeOptionMap'][ + 'Throughput']['RateIterationOptions']['AcceptableLoss'] = acceptable_loss + + def modify_mac_address(self, new_mac_addresses): + """ + Modify source and destination mac addresses + :param mac_addresses: list of either one or two mac addresses + :return: None + """ + + list_new_macs = [x.split(':') for x in new_mac_addresses] + curr_macs = [list(base64.b64decode(x['StreamConfig']['HeaderSegments'][ + 0]['SegmentValue'])) + for x in self.active_entities] + for i in range(0, 6): + curr_macs[0][6 + i] = int(list_new_macs[0][i], 16) + if len(curr_macs) == 2: + curr_macs[1][i] = int(list_new_macs[0][i], 16) + + if len(list_new_macs) == 2: + for i in range(0, 6): + curr_ips[0][i] = int(list_new_macs[1][i], 16) + if len(curr_macs) == 2: + curr_macs[1][6 + i] = int(list_new_macs[1][i], 16) + + index_entities = 0 + index_macs = 0 + + for entity in self.entities: + if entity['ItemID'] in self.active_ids: + self.json_data['StreamProfileHandler']['EntityList'][ + index_entities]['StreamConfig']['HeaderSegments'][ + 0]['SegmentValue'] = base64.b64encode(bytes(curr_macs[index_macs])).decode('ascii') + index_macs += 1 + + index_entities += 1 + + def modify_ip_address(self, new_ips): + """ + Modify source and destination ip addresses + :param ips: list of either one or two ip adresses + :return: None + """ + + list_new_ips = [x.split('.') for x in new_ips] + curr_ips = [list(base64.b64decode(x['StreamConfig']['HeaderSegments'][ + 1]['SegmentValue'])) + for x in self.active_entities] + + for i in range(0, 4): + curr_ips[0][12 + i] = int(list_new_ips[0][i]) + if len(curr_ips) == 2: + curr_ips[1][16 + i] = int(list_new_ips[0][i]) + + if len(list_new_ips) == 2: + for i in range(0, 4): + curr_ips[0][16 + i] = int(list_new_ips[1][i]) + if len(curr_ips) == 2: + curr_ips[1][12 + i] = int(list_new_ips[1][i]) + + index_entities = 0 + index_ips = 0 + + for entity in self.entities: + if entity['ItemID'] in self.active_ids: + self.json_data['StreamProfileHandler']['EntityList'][ + index_entities]['StreamConfig']['HeaderSegments'][ + 1]['SegmentValue'] = base64.b64encode(bytes(curr_ips[index_ips])).decode('ascii') + index_ips += 1 + + index_entities += 1 + + def modify_flows(self, flow_count, use_ip, use_mac): + + + for i in range(len(self.json_data['StreamProfileHandler']['EntityList'])): + self.json_data['StreamProfileHandler']['EntityList'][i][ + 'StreamConfig']['HwModifiers'] = [] + + if use_ip: + self.modify_ip_flow(flow_count) + if use_mac: + self.modify_mac_flow(flow_count) + + def modify_ip_flow(self, flow_count): + flow = _FLOWS[flow_count] + + field_names = ['Src IP Addr', 'Dest IP Addr'] + + common = { + 'Action': 'INC', + 'StartValue': 0, + 'StepValue': 1 + } + + entity_index = 0 + + for entity in self.entities: + if entity['ItemID'] in self.active_ids: + + ip_id = entity['StreamConfig']['HeaderSegments'][ + 1]['ItemID'] + + to_append = [] + + for i in range(len(flow['stops'])): + for j in field_names: + to_append.append({ + 'Mask': flow['masks'][i], + 'Action': common['Action'], + 'Offset': flow['offsets'][i], + 'StartValue': common['StartValue'], + 'StepValue': common['StepValue'], + 'StopValue': flow['stops'][i], + 'RepeatCount': flow['repeats'][i], + 'SegmentId': ip_id, + 'FieldName': j + }) + self.json_data['StreamProfileHandler']['EntityList'][ + entity_index]['StreamConfig']['HwModifiers'] += to_append + + entity_index += 1 + + + def modify_mac_flow(self, flow_count): + flow = _FLOWS[flow_count] + + field_names = ['Src MAC addr', 'Dst MAC addr'] + + common = { + 'Action': 'INC', + 'StartValue': 0, + 'StepValue': 1, + 'Mask': '//8=' + } + + entity_index = 0 + + for entity in self.entities: + if entity['ItemID'] in self.active_ids: + + mac_id = entity['StreamConfig']['HeaderSegments'][ + 0]['ItemID'] + + to_append = [] + + for i in range(len(flow['stops'])): + for j in field_names: + to_append.append({ + 'Mask': common['Mask'], + 'Action': common['Action'], + 'Offset': flow['offsets'][i]*2, + 'StartValue': common['StartValue'], + 'StepValue': common['StepValue'], + 'StopValue': flow['stops'][i], + 'RepeatCount': flow['repeats'][i], + 'SegmentId': mac_id, + 'FieldName': j + }) + self.json_data['StreamProfileHandler']['EntityList'][ + entity_index]['StreamConfig']['HwModifiers'] += to_append + + entity_index += 1 + + + + def modify_reporting(self, pdf_enable=True, csv_enable=False, + xml_enable=True, html_enable=False, + timestamp_enable=False, int_results=False): + """ + Modify the reporting options + :param pdf_enable: Enable pdf output, should disable for linux + :param csv_enable: Enable csv output + :param xml_enable: Enable xml output + :param html_enable: Enable html output + :param timestamp_enable: Enable timestamp to report + :param int_results: Enable intermediate results + :return: None + """ + self.json_data['ReportConfig'][ + 'GeneratePdf'] = 'true' if pdf_enable else 'false' + self.json_data['ReportConfig'][ + 'GenerateCsv'] = 'true' if csv_enable else 'false' + self.json_data['ReportConfig'][ + 'GenerateXml'] = 'true' if xml_enable else 'false' + self.json_data['ReportConfig'][ + 'GenerateHtml'] = 'true' if html_enable else 'false' + self.json_data['ReportConfig'][ + 'AppendTimestamp'] = 'true' if timestamp_enable else 'false' + self.json_data['ReportConfig'][ + 'SaveIntermediateResults'] = 'true' if int_results else 'false' + + def write_config(self, path='./2bUsed.x2544'): + """ + Write the config to out as file + :param path: Output file to export the json data to + :return: None + """ + if not write_json_file(self.json_data, path): + raise RuntimeError("Could not write out file, please check config") + + +def main(args): + _LOGGER.setLevel(logging.DEBUG if args.debug else logging.INFO) + stream_logger = logging.StreamHandler(sys.stdout) + stream_logger.setFormatter(logging.Formatter( + '[%(levelname)-5s] %(asctime)s : (%(name)s) - %(message)s')) + _LOGGER.addHandler(stream_logger) + # get the current json config into an object + xena_current = XenaJSON(args.config_file) + # Modify to output xml always as its needed to parse, turn off PDF output + # unless user specifies it. Usually not supported on Linux. Also need to + # disable the timestamp + xena_current.modify_reporting(True if args.pdf_output else False, + True, True, False, False, True) + if args.search_trial_duration: + xena_current.modify_duration(args.search_trial_duration) + if args.collect_latency: + xena_current.modify_latency() + if args.packet_sizes: + xena_current.modify_packet_size(args.packet_sizes) + if args.acceptable_loss: + xena_current.modify_acceptable_loss(args.acceptable_loss) + if args.initial_tput: + xena_current.modify_2544_tput_options(initial_value=args.initial_tput) + if args.max_tput: + xena_current.modify_2544_tput_options(maximum_value=args.max_tput) + if args.min_tput: + xena_current.modify_2544_tput_options(minimum_value=args.min_tput) + if args.resolution_tput: + xena_current.modify_2544_tput_options(value_resolution=args.resolution_tput) + if args.mac_address: + xena_current.modify_mac_address(args.mac_address) + if args.connection_ips: + xena_current.modify_ip_address(args.connection_ips) + if args.flow_count: + xena_current.modify_flows(args.flow_count, not args.use_mac_flows or args.use_both_flows, args.use_mac_flows or args.use_both_flows) + if args.module: + print(args.module) + + xena_current.write_config(args.save_file_name) + + #pdb.set_trace() + + result = run_xena(args.save_file_name, args.windows_mode) + + # now run the verification step by creating a new config with the desired + # params + for _ in range(1, args.retry_attempts +1): + if result[0] != 'PASS': + _LOGGER.error('Valkyrie2544.exe Test failed. Please check test config.') + break + _LOGGER.info('Verify attempt {}'.format(_)) + old_min = xena_current.min_tput # need this if verify fails + old_duration = xena_current.duration + xena_current.modify_2544_tput_options(initial_value=result[1], minimum_value=result[1], + maximum_value=result[1]) + xena_current.modify_duration(args.verify_duration) + xena_current.write_config('./verify.x2544') + # run verify step + _LOGGER.info('Running verify for {} seconds'.format( + args.verify_duration)) + verify_result = run_xena('./verify.x2544', args.windows_mode) + if verify_result[0] == 'PASS': + _LOGGER.info('Verify passed. Packets lost = {} Exiting'.format( + verify_result[3])) + _LOGGER.info('Pass result transmit rate = {}'.format( + verify_result[1])) + _LOGGER.info('Pass result transmit fps = {}'.format( + verify_result[2])) + if args.collect_latency: + for x in verify_result[4]: + _LOGGER.info('Port {}'.format(x.get('ID'))) + _LOGGER.info('Latency Min = {} micsec'.format(x.get('MinLatency'))) + _LOGGER.info('Latency Max = {} micsec'.format(x.get('MaxLatency'))) + _LOGGER.info('Latency Avg = {} micsec'.format(x.get('AvgLatency'))) + break + else: + _LOGGER.warning('Verify failed. Packets lost = {}'.format( + verify_result[3])) + _LOGGER.info('Restarting Valkyrie2544.exe with new values') + if args.smart_search: + new_init = (verify_result[1] - old_min) / 2 + else: + new_init = result[1] - xena_current.value_thresh + xena_current.modify_2544_tput_options( + initial_value=new_init, minimum_value=old_min, + maximum_value=result[1] - xena_current.value_thresh) + xena_current.modify_duration( + args.search_trial_duration if args.search_trial_duration else + old_duration) + _LOGGER.info('New minimum value: {}'.format(old_min)) + _LOGGER.info('New maximum value: {}'.format( + result[1] - xena_current.value_thresh)) + _LOGGER.info('New initial rate: {}'.format(new_init)) + xena_current.write_config('./verify.x2544') + result = run_xena('./verify.x2544', args.windows_mode) + else: + _LOGGER.error('Maximum number of verify retries attempted. Exiting...') + + +def read_json_file(json_file): + """ + Read the json file path and return a dictionary of the data + :param json_file: path to json file + :return: dictionary of json data + """ + try: + if _PYTHON_2: + with open(json_file, 'r') as data_file: + file_data = json.loads(data_file.read()) + else: + with open(json_file, 'r', encoding=_LOCALE) as data_file: + file_data = json.loads(data_file.read()) + except ValueError as exc: + # general json exception, Python 3.5 adds new exception type + _LOGGER.exception("Exception with json read: %s", exc) + raise + except IOError as exc: + _LOGGER.exception( + 'Exception during file open: %s file=%s', exc, json_file) + raise + return file_data + + +def run_xena(config_file, windows_mode=False): + """ + Run Valkyrie2544.exe with the config file specified. + :param config_file: config file to use + :param windows_mode: enable windows mode which bypasses the usage of mono + :return: Tuple of pass or fail result as str, and current transmit rate as + float, transmit fps, and packets lost + """ + user_home = os.path.expanduser('~') + log_path = '{}/Xena/Valkrie2544/Logs/valkyrie2544.log'.format(user_home) + # make the folder and log file if they doesn't exist + if not os.path.exists(log_path): + os.makedirs(os.path.dirname(log_path)) + + # empty the file contents + open(log_path, 'w').close() + + # setup the xena command line + args = ["mono" if not windows_mode else "", + "Valkyrie2544.exe", "-c", config_file, "-e", "-r", "./", "-u", + _XENA_USER] + + # Sometimes Valkyrie2544.exe completes, but mono holds the process without + # releasing it, this can cause a deadlock of the main thread. Use the + # xena log file as a way to detect this. + log_handle = open(log_path, 'r') + # read the contents of the log before we start so the next read in the + # wait method are only looking at the text from this test instance + log_handle.read() + print('XENAVERFIY mono_pipe args: ', args) + mono_pipe = subprocess.Popen(args, stdout=sys.stdout) + data = '' + if _PYTHON_2: + _LOGGER.error('Not supported yet for python 2...') + else: + while True: + try: + mono_pipe.wait(60) + log_handle.close() + break + except subprocess.TimeoutExpired: + # check the log to see if Valkrie2544 has completed and mono is + # deadlocked. + data += log_handle.read() + if 'TestCompletedSuccessfully' in data: + log_handle.close() + mono_pipe.terminate() + break + + # parse the result file and return the needed data + root = ET.parse(r'./valkyrie2544-report.xml').getroot() + return (root[0][1][0].get('TestState'), + float(root[0][1][0].get('TotalTxRatePcnt')), + float(root[0][1][0].get('TotalTxRateFps')), + root[0][1][0].get('TotalLossFrames'), + root[0][1][0], # return whole element + ) + + +def write_json_file(json_data, output_path): + """ + Write out the dictionary of data to a json file + :param json_data: dictionary of json data + :param output_path: file path to write output + :return: Boolean if success + """ + try: + if _PYTHON_2: + with open(output_path, 'w') as fileh: + json.dump(json_data, fileh, indent=2, sort_keys=True, + ensure_ascii=True) + else: + with open(output_path, 'w', encoding=_LOCALE) as fileh: + json.dump(json_data, fileh, indent=2, sort_keys=True, + ensure_ascii=True) + return True + except ValueError as exc: + # general json exception, Python 3.5 adds new exception type + _LOGGER.exception( + "Exception with json write: %s", exc) + return False + except IOError as exc: + _LOGGER.exception( + 'Exception during file write: %s file=%s', exc, output_path) + return False + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('-f', '--config_file', type=str, required=True, + help='Xena/Valkyrie 2544 json config file name') + parser.add_argument('-d', '--debug', action='store_true', required=False, + help='Enable debug logging') + parser.add_argument('-w', '--windows_mode', required=False, + action='store_true', help='Use windows mode, no mono') + parser.add_argument('-l', '--verify_duration', required=False, + type=int, default=600, + help='Verification duration in seconds') + parser.add_argument('-r', '--retry_attempts', type=int, default=5, + required=False, help='Maximum verify attempts') + parser.add_argument('-s', '--smart_search', action='store_true', + required=False, help='Enable smart search', + default=False) + parser.add_argument('-p', '--pdf_output', action='store_true', + required=False, + help='Generate PDF report, do not use on Linux!', + default=False) + parser.add_argument('-t', '--search_trial_duration', required=False, + help='Search trial duration in seconds', type=int, + default=0) + parser.add_argument('-z', '--collect_latency', required=False, + help='Enable Latency counters', action='store_true', + default=False) + parser.add_argument('-k', '--packet_sizes', required=False, nargs='+', + type=int, default=False, + help='Specify custom packet sizes for test') + parser.add_argument('-a', '--acceptable_loss', required=False, type=float, + help='Specify acceptable loss in terms of percent of packages lost') + parser.add_argument('-v', '--save_file_name', required=False, type=str, + default='./2bUsed.x2544', + help='File name to save new config file as') + parser.add_argument('-i', '--initial_tput', required=False, type=float, + help='Specify initial throughput for test') + parser.add_argument('-M', '--max_tput', required=False, type=float, + help='Specify maximum throughput for test') + parser.add_argument('-m', '--min_tput', required=False, type=float, + help='Specify minimum throughput for test') + parser.add_argument('-n', '--mac_address', required=False, nargs='+', + type=str, help='Set src and destination mac address') + parser.add_argument('-c', '--connection_ips', required=False, nargs='+', + type=str, help='Set src and destination ip address') + parser.add_argument('-o', '--resolution_tput', required=False, type=float, + help='Specify resolution rate for throughput test') + parser.add_argument('-u', '--flow_count', required=False, choices=list(_FLOWS.keys()), + help='Choose number of flows to run') + parser.add_argument('-b', '--use_both_flows', required=False, + default=False, action='store_true', + help='Use value passed to --flow_count for both MAC and IP does not work for 100k and 1M') + parser.add_argument('-e', '--use_mac_flows', required=False, + default=False, action='store_true', + help='Use value passed to --flow_count for MAC') + parser.add_argument('--module', required=False, type=int, dest='module', + help='Set module number to use for test') + + args = parser.parse_args() + print('XENAVERFIY ARGS: ', args) + if args.debug: + print("DEBUG ENABLED!!!") + main(args) + + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/binary-search.py b/binary-search.py index 54b6320..76f1160 100755 --- a/binary-search.py +++ b/binary-search.py @@ -495,6 +495,51 @@ def process_options (): help='Argument for use with Xena; specifies the IP address of the Xena chassis to connect to', type=str ) + parser.add_argument('--xena_module', + dest='xena_module', + help='Argument for use with Xena; specifies the chassis module to use', + type=int + ) + parser.add_argument('--valkyrie2544-windows_mode', + dest='valkyrie2544_windows_mode', + required=False, + action='store_true', + help='Valkyrie2455: Use windows mode, no mono' + ) + parser.add_argument('--valkyrie2544-smart_search', + action='store_true', + dest='valkyrie2544_smart_search', + required=False, + help='Valkyrie2455: Enable smart search', + default=False) + parser.add_argument('--valkyrie2544-pdf_output', + action='store_true', + dest='valkyrie2544_pdf_output', + required=False, + help='Valkyrie2455: Generate PDF report, do not use on Linux!', + default=False) + parser.add_argument('--valkyrie2544-packet_sizes', + required=False, + dest='valkyrie2544_packet_sizes', + type=int, + default=False, + help='Valkyrie2455: Specify custom packet sizes for test') + parser.add_argument('--valkyrie2544-save_file_name', + required=False, + dest='valkyrie2544_save_file_name', + type=str, + default='', + help='Valkyrie2455: File name to save new config file as') + parser.add_argument('--valkyrie2544-initial_tput', + required=False, + dest='valkyrie2544_initial_tput', + type=float, + help='Valkyrie2455: Specify initial throughput for test') + parser.add_argument('--valkyrie2544-resolution_tput', + required=False, + dest='valkyrie2544_resolution_tput', + type=float, + help='Specify resolution rate for throughput test') t_global.args = parser.parse_args(); if t_global.args.frame_size == "IMIX": @@ -1961,6 +2006,71 @@ def main(): # empty for now foo = None + # valkyrie2544 - Xena throughput test functionality + if t_global.args.traffic_generator == 'valkyrie2544': + setup_config_var('traffic_profile', t_global.args.traffic_profile, trial_params) + setup_config_var('valkyrie2544_windows_mode', t_global.args.valkyrie2544_windows_mode, trial_params) + setup_config_var('validation_runtime', t_global.args.validation_runtime, trial_params) + setup_config_var('max_retries', t_global.args.max_retries, trial_params) + setup_config_var('valkyrie2544_smart_search', t_global.args.valkyrie2544_smart_search, trial_params) + setup_config_var('valkyrie2544_pdf_output', t_global.args.valkyrie2544_pdf_output, trial_params) + setup_config_var('search_runtime', t_global.args.search_runtime, trial_params) + setup_config_var('measure_latency', t_global.args.measure_latency, trial_params) + setup_config_var('valkyrie2544_packet_sizes', t_global.args.valkyrie2544_packet_sizes, trial_params) + setup_config_var('max_loss_pct', t_global.args.max_loss_pct, trial_params) + setup_config_var('valkyrie2544-save_file_name', t_global.args.valkyrie2544_save_file_name, trial_params) + setup_config_var('valkyrie2544_initial_tput', t_global.args.valkyrie2544_initial_tput, trial_params) + setup_config_var('rate', t_global.args.rate, trial_params) + setup_config_var('min_rate', t_global.args.min_rate, trial_params) + setup_config_var('xena_module', t_global.args.xena_module, trial_params) + setup_config_var('src_macs', t_global.args.src_macs, trial_params) + setup_config_var('dst_macs', t_global.args.dst_macs, trial_params) + setup_config_var('src_ips', t_global.args.src_ips, trial_params) + setup_config_var('dst_ips', t_global.args.dst_ips, trial_params) + setup_config_var('valkyrie2544_resolution_tput', t_global.args.valkyrie2544_resolution_tput, trial_params) + setup_config_var('num_flows', t_global.args.num_flows, trial_params) + setup_config_var('use_src_ip_flows', t_global.args.use_src_ip_flows, trial_params) + setup_config_var('use_dst_ip_flows', t_global.args.use_dst_ip_flows, trial_params) + setup_config_var('use_src_mac_flows', t_global.args.use_src_mac_flows, trial_params) + setup_config_var('use_dst_mac_flows', t_global.args.use_dst_mac_flows, trial_params) + + cmd = 'python -u ' + t_global.trafficgen_dir + 'valkyrie2544-helper.py' + cmd = cmd + ' ' + ' --traffic-profile ' + str(trial_params['traffic_profile']) + if t_global.args.valkyrie2544_windows_mode: # --windows_mode + cmd = cmd + ' --valkyrie2544-windows_mode' + if t_global.args.validation_runtime: # --verify_duration + cmd = cmd + ' --validation-runtime ' + str(t_global.args.validation_runtime) + if t_global.args.max_retries: # --retry_attempts + cmd = cmd + ' --max-retries ' + str(t_global.args.max_retries) + if t_global.args.valkyrie2544_smart_search: # --smart_search + cmd = cmd + ' --valkyrie2544-smart_search' + if t_global.args.valkyrie2544_pdf_output: # --pdf_output + cmd = cmd + ' --pdf_output' + if t_global.args.search_runtime: # --search_trial_duration + cmd = cmd + ' --search-runtime ' + str(t_global.args.search_runtime) + if t_global.args.measure_latency: # --collect_latency + cmd = cmd + ' --measure-latency ' + str(t_global.args.measure_latency) + if t_global.args.valkyrie2544_packet_sizes: # --packet_sizes + cmd = cmd + ' --valkyrie2544-packet_sizes ' + if t_global.args.max_loss_pct: # --acceptable_loss + cmd = cmd + ' --max-loss-pct ' + str(t_global.args.max_loss_pct) + if t_global.args.valkyrie2544_save_file_name: # --save_file_name + cmd = cmd + ' --valkyrie2544-save_file_name' + if t_global.args.valkyrie2544_initial_tput: # --initial_tput + cmd = cmd + ' --valkyrie2544-initial_tput' + if t_global.args.rate: # --max_tput + cmd = cmd + ' --rate ' + str(t_global.args.rate) + if t_global.args.min_rate: # --min_tput + cmd = cmd + ' --min_tput ' + str(t_global.args.min_rate) + if t_global.args.dst_macs: # --mac_address + cmd = cmd + ' --dst-macs ' + str(t_global.args.dst_macs) + if t_global.args.src_macs: + cmd = cmd + ' --src-macs ' + str(args.src_macs) + tg_process = subprocess.Popen(cmd, shell=True, stdout=sys.stdout) + tg_process.wait() + bs_logger_cleanup(bs_logger_exit, bs_logger_thread) + return + # set configuration from the argument parser if t_global.args.traffic_generator == 'xena': # first four are bare minimum - required to prevent script crash diff --git a/valkyrie2544-helper.py b/valkyrie2544-helper.py new file mode 100644 index 0000000..52f7593 --- /dev/null +++ b/valkyrie2544-helper.py @@ -0,0 +1,247 @@ +# Copyright 2016-2017 Red Hat Inc & Xena Networks. +# +# 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 argparse +import json +import locale +import logging +import os +import subprocess +import sys +from time import sleep +import xml.etree.ElementTree as ET +import base64 + +import pprint + +pp = pprint.PrettyPrinter(indent=4) + +_LOGGER = logging.getLogger(__name__) +_DEBUG = False + +def main(args): + cmd = 'python -u XenaVerify.py' + cmd = cmd + ' --config_file ' + args.traffic_profile # --config_file + if _DEBUG: # --debug + cmd = cmd + ' --debug' + if args.xena_module: + cmd = cmd + ' --module ' + str(args.xena_module) + if args.valkyrie2544_windows_mode: # --windows_mode + cmd = cmd + ' --windows_mode' + if args.validation_runtime: # --verify_duration + cmd = cmd + ' --verify_duration ' + str(args.validation_runtime) + if args.max_retries: # --retry_attempts + cmd = cmd + ' --retry_attempts ' + str(int(args.max_retries)) + if args.smart_search: # --smart_search + cmd = cmd + ' --smart_search' + if args.pdf: # --pdf_output + cmd = cmd + ' --pdf_output' + if args.search_runtime: # --search_trial_duration + cmd = cmd + ' --search_trial_duration ' + str(args.search_runtime) + if args.measure_latency == 0: # --collect_latency + cmd = cmd + ' --collect_latency' + if args.valkyrie2544_packet_sizes: # --packet_sizes + cmd = cmd + ' --packet_sizes ' + str(args.valkyrie2544_packet_sizes) + if args.max_loss_pct: # --acceptable_loss + cmd = cmd + ' --acceptable_loss ' + str(args.max_loss_pct) + if args.valkyrie2544_save_file_name: # --save_file_name + cmd = cmd + ' --save_file_name ' + args.valkyrie2544_save_file_name + if args.valkyrie2544_initial_tput: # --initial_tput + cmd = cmd + ' --initial_tput ' + str(args.valkyrie2544_initial_tput) + if args.rate: # --max_tput + cmd = cmd + ' --max_tput ' + str(args.rate) + if args.min_rate: # --min_tput + cmd = cmd + ' --min_tput ' + str(args.min_rate) + if args.dst_macs and args.src_macs: # --mac_address + cmd = cmd + ' --mac_address ' + str(args.dst_macs) + ' ' + str(args.src_macs) + if args.src_ips and args.dst_ips: # --connection_ips + cmd = cmd + ' ' + str(args.src_ips) + ' ' + str(args.dst_ips) + if args.valkyrie2544_resolution_tput: # --resolution_tput + cmd = cmd + ' --resolution_tput ' + str(args.valkyrie2544_resolution_tput) + if args.num_flows: # --flow_count + # round num_flows to nearest compatible option + # binary-search checks that flows are within range 1-1M + if round(args.num_flows, -6): # checks if rounds to 1,000,000 + flow_count = '1M' + elif round(args.num_flows, -5): # catches values rounding to 100,000 + flow_count = '100k' + elif round(args.num_flows, -4): # catches values rounding to 10,000 + flow_count = '10k' + elif round(args.num_flows, -3): # catches values rounding to 1,000 + flow_count = '1k' + else: # anything else is rounded down to 1 + flow_count = '1' + cmd = cmd + ' --flow_count ' + flow_count + + if args.use_src_ip_flows or args.use_dst_ip_flows: # use ip flows, test addition + ip_flows = True + + if (args.use_src_mac_flows != 1) or (args.use_dst_mac_flows != 1): # --use_mac_flows + cmd = cmd + ' --use_mac_flows' + mac_flows = True + else: + mac_flows = False + + if ip_flows and mac_flows: # --use_both_flows + cmd = cmd + ' --use_both_flows' + + XenaVerify_pipe = subprocess.Popen(cmd, shell=True, stdout=sys.stdout) + print('VALKYRIE2544HELPER cmd: ', cmd) + + data = '' + + while True: + try: + XenaVerify_pipe.wait(60) + # log_handle.close() + break + except subprocess.TimeoutExpired: + # check the log to see if Valkrie2544 has completed and mono is + # deadlocked. + # data += log_handle.read() + if 'TestCompletedSuccessfully' in data: + # log_handle.close() + XenaVerify_pipe.terminate() + break + + return + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--traffic-profile', + dest='traffic_profile', + help='Xena/Valkyrie 2544 json config file name', + default = '', + type = str + ) + parser.add_argument('--valkyrie2544-windows_mode', required=False, + action='store_true', help='Use windows mode, no mono') + parser.add_argument('--validation-runtime', + dest='validation_runtime', + help='trial period in seconds during final validation', + default=30, + type = int, + ) + parser.add_argument('-l', '--verify_duration', required=False, + type=int, default=7200, + help='Verification duration in seconds') + parser.add_argument('--max-retries', + dest='max_retries', + help='Maximum number of trial retries before aborting', + default = 1, + type = int + ) + parser.add_argument('--valkyrie2544-smart_search', action='store_true', + required=False, help='Enable smart search', + dest='smart_search', default=False) + parser.add_argument('--valkyrie2544-pdf_output', action='store_true', + dest='pdf', required=False, + help='Generate PDF report, do not use on Linux!', + default=False) + parser.add_argument('--search-runtime', + dest='search_runtime', + help='trial period in seconds during binary search', + default=30, + type = int, + ) + parser.add_argument('--measure-latency', + dest='measure_latency', + help='Collect latency statistics or not', + default = 1, + type = int + ) + parser.add_argument('--valkyrie2544-packet_sizes', required=False, nargs='+', + type=int, default=False, + help='Specify custom packet sizes for test') + parser.add_argument('--max-loss-pct', + dest='max_loss_pct', + help='maximum percentage of packet loss', + type = float + ) + parser.add_argument('--valkyrie2544-save_file_name', required=False, type=str, + default='./2bUsed.x2544', + help='File name to save new config file as') + parser.add_argument('--valkyrie2544-initial_tput', required=False, type=float, + help='Specify initial throughput for test') + parser.add_argument('--rate', required=False, type=float, dest='rate', + help='Specify maximum throughput for test') + parser.add_argument('--min-rate', + dest='min_rate', + help='minimum rate per device', + type = float + ) + parser.add_argument('--src-macs', + dest='src_macs', + help='comma separated list of src MACs, 1 per device', + default="" + ) + parser.add_argument('--dst-macs', + dest='dst_macs', + help='comma separated list of destination MACs, 1 per device', + default="" + ) + parser.add_argument('--src-ips', + dest='src_ips', + help='comma separated list of src IPs, 1 per device', + default="" + ) + parser.add_argument('--dst-ips', + dest='dst_ips', + help='comma separated list of destination IPs 1 per device', + default="" + ) + parser.add_argument('--valkyrie2544-resolution_tput', required=False, type=float, + help='Specify resolution rate for throughput test') + parser.add_argument('--num-flows', + dest='num_flows', + help='Choose number of flows to run', + default=1024, + type = int, + ) + parser.add_argument('--use-src-ip-flows', + dest='use_src_ip_flows', + help='implement flows by source IP', + default=1, + type = int, + ) + parser.add_argument('--use-dst-ip-flows', + dest='use_dst_ip_flows', + help='implement flows by destination IP', + default=1, + type = int, + ) + parser.add_argument('--use-src-mac-flows', + dest='use_src_mac_flows', + help='implement flows by source MAC', + default=1, + type = int, + ) + parser.add_argument('--use-dst-mac-flows', + dest='use_dst_mac_flows', + help='implement flows by destination MAC', + default=1, + type = int, + ) + parser.add_argument('--xena_module', + dest='xena_module', + help='Argument for use with Xena; specify module number of a Xena chassis' + ) + + args = parser.parse_args() + if args.debug: + print("HELPER DEBUG ENABLED!!!") + main(args) + + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4