diff options
Diffstat (limited to 'tools')
26 files changed, 752 insertions, 256 deletions
diff --git a/tools/collectors/collectd/collectd.py b/tools/collectors/collectd/collectd.py index 90df6b04..5e996d3a 100644 --- a/tools/collectors/collectd/collectd.py +++ b/tools/collectors/collectd/collectd.py @@ -20,6 +20,7 @@ Plot the values of the stored samples once the test is completed import copy import csv +import glob import logging import multiprocessing import os @@ -30,6 +31,7 @@ import matplotlib.pyplot as plt import numpy as np import tools.collectors.collectd.collectd_bucky as cb from tools.collectors.collector import collector +from tools import tasks from conf import settings # The y-lables. Keys in this dictionary are used as y-labels. @@ -47,6 +49,7 @@ def get_label(sample): for label in YLABELS: if any(r in sample for r in YLABELS[label]): return label + return None def plot_graphs(dict_of_arrays): @@ -194,6 +197,7 @@ class Receiver(multiprocessing.Process): val = self.pd_dict[sample[1]] val.append((sample[2], sample[3])) self.pd_dict[sample[1]] = val + logging.debug("COLLECTD %s", ' '.join(str(p) for p in sample)) def stop(self): """ @@ -216,13 +220,27 @@ class Collectd(collector.ICollector): """ Initialize collection of statistics """ - self._log = os.path.join(results_dir, - settings.getValue('LOG_FILE_COLLECTD') + - '_' + test_name + '.log') + self.logger = logging.getLogger(__name__) + self.resultsdir = results_dir + self.testname = test_name self.results = {} self.sample_dict = multiprocessing.Manager().dict() self.control = multiprocessing.Value('b', False) self.receiver = Receiver(self.sample_dict, self.control) + self.cleanup_metrics() + # Assumption: collected is installed at /opt/collectd + # And collected is configured to write to csv at /tmp/csv + self.pid = tasks.run_background_task( + ['sudo', '/opt/collectd/sbin/collectd'], + self.logger, 'Staring Collectd') + + def cleanup_metrics(self): + """ + Cleaup the old or archived metrics + """ + for name in glob.glob(os.path.join('/tmp/csv/', '*')): + tasks.run_task(['sudo', 'rm', '-rf', name], self.logger, + 'Cleaning up Metrics', True) def start(self): """ @@ -235,6 +253,11 @@ class Collectd(collector.ICollector): """ Stop receiving samples """ + tasks.terminate_task_subtree(self.pid, logger=self.logger) + # At times collectd fails to fully terminate. + # Killing process by name too helps. + tasks.run_task(['sudo', 'pkill', '--signal', '2', 'collectd'], + self.logger, 'Stopping Collectd', True) self.control.value = True self.receiver.stop() self.receiver.server.join(5) @@ -244,6 +267,12 @@ class Collectd(collector.ICollector): if self.receiver.is_alive(): self.receiver.terminate() self.results = copy.deepcopy(self.sample_dict) + # Backup the collectd-metrics for this test into a zipfile + filename = ('/tmp/collectd-' + settings.getValue('LOG_TIMESTAMP') + + '.tar.gz') + tasks.run_task(['sudo', 'tar', '-czvf', filename, '/tmp/csv/'], + self.logger, 'Zipping File', True) + self.cleanup_metrics() def get_results(self): """ @@ -259,7 +288,7 @@ class Collectd(collector.ICollector): plot_graphs(self.results) proc_stats = get_results_to_print(self.results) for process in proc_stats: - logging.info("Process: " + '_'.join(process.split('_')[:-1])) + logging.info("Process: %s", '_'.join(process.split('_')[:-1])) for(key, value) in proc_stats[process].items(): logging.info(" Statistic: " + str(key) + ", Value: " + str(value)) diff --git a/tools/collectors/collectd/collectd_bucky.py b/tools/collectors/collectd/collectd_bucky.py index bac24ed7..f6061c55 100644 --- a/tools/collectors/collectd/collectd_bucky.py +++ b/tools/collectors/collectd/collectd_bucky.py @@ -498,6 +498,7 @@ class CollectDCrypto(object): return self.parse_signed(part_len, data) if sec_level == 2: return self.parse_encrypted(part_len, data) + return None def parse_signed(self, part_len, data): """ @@ -574,12 +575,12 @@ class CollectDConverter(object): try: name_parts = handler(sample) if name_parts is None: - return # treat None as "ignore sample" + return None # treat None as "ignore sample" name = '.'.join(name_parts) except (AttributeError, IndexError, MemoryError, RuntimeError): LOG.exception("Exception in sample handler %s (%s):", sample["plugin"], handler) - return + return None host = sample.get("host", "") return ( host, @@ -655,7 +656,7 @@ class CollectDHandler(object): Check the value range """ if val is None: - return + return None try: vmin, vmax = self.parser.types.type_ranges[stype][vname] except KeyError: @@ -664,11 +665,11 @@ class CollectDHandler(object): if vmin is not None and val < vmin: LOG.debug("Invalid value %s (<%s) for %s", val, vmin, vname) LOG.debug("Last sample: %s", self.last_sample) - return + return None if vmax is not None and val > vmax: LOG.debug("Invalid value %s (>%s) for %s", val, vmax, vname) LOG.debug("Last sample: %s", self.last_sample) - return + return None return val def calculate(self, host, name, vtype, val, time): @@ -684,7 +685,7 @@ class CollectDHandler(object): if vtype not in handlers: LOG.error("Invalid value type %s for %s", vtype, name) LOG.info("Last sample: %s", self.last_sample) - return + return None return handlers[vtype](host, name, val, time) def _calc_counter(self, host, name, val, time): @@ -694,13 +695,13 @@ class CollectDHandler(object): key = (host, name) if key not in self.prev_samples: self.prev_samples[key] = (val, time) - return + return None pval, ptime = self.prev_samples[key] self.prev_samples[key] = (val, time) if time <= ptime: LOG.error("Invalid COUNTER update for: %s:%s", key[0], key[1]) LOG.info("Last sample: %s", self.last_sample) - return + return None if val < pval: # this is supposed to handle counter wrap around # see https://collectd.org/wiki/index.php/Data_source @@ -719,13 +720,13 @@ class CollectDHandler(object): key = (host, name) if key not in self.prev_samples: self.prev_samples[key] = (val, time) - return + return None pval, ptime = self.prev_samples[key] self.prev_samples[key] = (val, time) if time <= ptime: LOG.debug("Invalid DERIVE update for: %s:%s", key[0], key[1]) LOG.debug("Last sample: %s", self.last_sample) - return + return None return float(abs(val - pval)) / (time - ptime) def _calc_absolute(self, host, name, val, time): @@ -735,13 +736,13 @@ class CollectDHandler(object): key = (host, name) if key not in self.prev_samples: self.prev_samples[key] = (val, time) - return + return None _, ptime = self.prev_samples[key] self.prev_samples[key] = (val, time) if time <= ptime: LOG.error("Invalid ABSOLUTE update for: %s:%s", key[0], key[1]) LOG.info("Last sample: %s", self.last_sample) - return + return None return float(val) / (time - ptime) diff --git a/tools/collectors/sysmetrics/pidstat.py b/tools/collectors/sysmetrics/pidstat.py index 99341ccf..277fdb11 100644 --- a/tools/collectors/sysmetrics/pidstat.py +++ b/tools/collectors/sysmetrics/pidstat.py @@ -70,13 +70,13 @@ class Pidstat(collector.ICollector): into the file in directory with test results """ monitor = settings.getValue('PIDSTAT_MONITOR') - self._logger.info('Statistics are requested for: ' + ', '.join(monitor)) + self._logger.info('Statistics are requested for: %s', ', '.join(monitor)) pids = systeminfo.get_pids(monitor) if pids: with open(self._log, 'w') as logfile: cmd = ['sudo', 'LC_ALL=' + settings.getValue('DEFAULT_CMD_LOCALE'), 'pidstat', settings.getValue('PIDSTAT_OPTIONS'), - '-p', ','.join(pids), + '-t', '-p', ','.join(pids), str(settings.getValue('PIDSTAT_SAMPLE_INTERVAL'))] self._logger.debug('%s', ' '.join(cmd)) self._pid = subprocess.Popen(cmd, stdout=logfile, bufsize=0).pid @@ -116,16 +116,48 @@ class Pidstat(collector.ICollector): # combine stored header fields with actual values tmp_res = OrderedDict(zip(tmp_header, line[8:].split())) - # use process's name and its pid as unique key - key = tmp_res.pop('Command') + '_' + tmp_res['PID'] - # store values for given command into results dict - if key in self._results: - self._results[key].update(tmp_res) - else: - self._results[key] = tmp_res + cmd = tmp_res.pop('Command') + # remove unused fields (given by option '-t') + tmp_res.pop('UID') + tmp_res.pop('TID') + if '|_' not in cmd: # main process + # use process's name and its pid as unique key + tmp_pid = tmp_res.pop('TGID') + tmp_key = "%s_%s" % (cmd, tmp_pid) + # do not trust cpu usage of pid + # see VSPERF-569 for more details + if 'CPU' not in tmp_header: + self.update_results(tmp_key, tmp_res, False) + else: # thread + # accumulate cpu usage of all threads + if 'CPU' in tmp_header: + tmp_res.pop('TGID') + self.update_results(tmp_key, tmp_res, True) line = logfile.readline() + def update_results(self, key, result, accumulate=False): + """ + Update final results dictionary. If ``accumulate`` param is set to + ``True``, try to accumulate existing values. + """ + # store values for given command into results dict + if key not in self._results: + self._results[key] = result + elif accumulate: + for field in result: + if field not in self._results[key]: + self._results[key][field] = result[field] + else: + try: + val = float(self._results[key][field]) + float(result[field]) + self._results[key][field] = '{0:.2f}'.format(val) + except ValueError: + # cannot cast to float, let's update with the previous value + self._results[key][field] = result[field] + else: + self._results[key].update(result) + def get_results(self): """Returns collected statistics. """ @@ -135,7 +167,7 @@ class Pidstat(collector.ICollector): """Logs collected statistics. """ for process in self._results: - logging.info("Process: " + '_'.join(process.split('_')[:-1])) + logging.info("Process: %s", '_'.join(process.split('_')[:-1])) for(key, value) in self._results[process].items(): logging.info(" Statistic: " + str(key) + ", Value: " + str(value)) diff --git a/tools/functions.py b/tools/functions.py index d35f1f84..65c9978b 100644 --- a/tools/functions.py +++ b/tools/functions.py @@ -127,7 +127,7 @@ def settings_update_paths(): # expand OS wildcards in paths if needed if glob.has_magic(tmp_tool): tmp_glob = glob.glob(tmp_tool) - if len(tmp_glob) == 0: + if not tmp_glob: raise RuntimeError('Path to the {} is not valid: {}.'.format(tool, tmp_tool)) elif len(tmp_glob) > 1: raise RuntimeError('Path to the {} is ambiguous {}'.format(tool, tmp_glob)) diff --git a/tools/load_gen/stress_ng/stress_ng.py b/tools/load_gen/stress_ng/stress_ng.py index c2592dd1..41bfe990 100644 --- a/tools/load_gen/stress_ng/stress_ng.py +++ b/tools/load_gen/stress_ng/stress_ng.py @@ -30,6 +30,3 @@ class StressNg(Stress): 'name': 'stress-ng' } _logger = logging.getLogger(__name__) - - def __init__(self, stress_config): - super(StressNg, self).__init__(stress_config) diff --git a/tools/load_gen/stressorvm/__init__.py b/tools/load_gen/stressorvm/__init__.py new file mode 100644 index 00000000..6a22d81c --- /dev/null +++ b/tools/load_gen/stressorvm/__init__.py @@ -0,0 +1,16 @@ +# Copyright 2017-2018 Spirent Communications +# +# 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. + +"""Package with wrapper for Stressor-VMs +""" diff --git a/tools/load_gen/stressorvm/stressor_vm.py b/tools/load_gen/stressorvm/stressor_vm.py new file mode 100644 index 00000000..f4936743 --- /dev/null +++ b/tools/load_gen/stressorvm/stressor_vm.py @@ -0,0 +1,116 @@ +# Copyright 2017-2018 Spirent Communications. +# +# 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. + +""" +Wrapper file to create and manage Stressor-VM as loadgen +""" + +import logging +import os +from tools import tasks +from tools.load_gen.load_gen import ILoadGenerator +from conf import settings as S + + +class QemuVM(tasks.Process): + """ + Class for controling an instance of QEMU + """ + def __init__(self, index): + self._running = False + self._logger = logging.getLogger(__name__) + self._number = index + pnumber = int(S.getValue('NN_BASE_VNC_PORT')) + self._number + cpumask = ",".join(S.getValue('NN_CORE_BINDING')[self._number]) + self._monitor = '%s/vm%dmonitor' % ('/tmp', pnumber) + self._logfile = (os.path.join(S.getValue('LOG_DIR'), + S.getValue('NN_LOG_FILE')) + + str(self._number)) + self._log_prefix = 'vnf_%d_cmd : ' % pnumber + name = 'NN%d' % index + vnc = ':%d' % pnumber + self._shared_dir = '%s/qemu%d_share' % ('/tmp', pnumber) + if not os.path.exists(self._shared_dir): + try: + os.makedirs(self._shared_dir) + except OSError as exp: + raise OSError("Failed to create shared directory %s: %s" % + self._shared_dir, exp) + + self.nics_nr = S.getValue('NN_NICS_NR')[self._number] + self.image = S.getValue('NN_IMAGE')[self._number] + self._cmd = ['sudo', '-E', 'taskset', '-c', cpumask, + S.getValue('TOOLS')['qemu-system'], + '-m', S.getValue('NN_MEMORY')[self._number], + '-smp', S.getValue('NN_SMP')[self._number], + '-cpu', 'host,migratable=off', + '-drive', 'if={},file='.format( + S.getValue('NN_BOOT_DRIVE_TYPE')[self._number]) + + self.image, '-boot', + 'c', '--enable-kvm', + '-monitor', 'unix:%s,server,nowait' % self._monitor, + '-nographic', '-vnc', str(vnc), '-name', name, + '-snapshot', '-net none', '-no-reboot', + '-drive', + 'if=%s,format=raw,file=fat:rw:%s,snapshot=off' % + (S.getValue('NN_SHARED_DRIVE_TYPE')[self._number], + self._shared_dir) + ] + + def start(self): + """ + Start QEMU instance + """ + super(QemuVM, self).start() + self._running = True + + def stop(self, sig, slp): + """ + Stops VNF instance. + """ + if self._running: + self._logger.info('Killing VNF...') + # force termination of VNF and wait to terminate; It will avoid + # sporadic reboot of host. + super(QemuVM, self).kill(signal=sig, sleep=slp) + # remove shared dir if it exists to avoid issues with file consistency + if os.path.exists(self._shared_dir): + tasks.run_task(['rm', '-f', '-r', self._shared_dir], self._logger, + 'Removing content of shared directory...', True) + self._running = False + + +# pylint: disable=super-init-not-called +class StressorVM(ILoadGenerator): + """ + Wrapper Class for Load-Generation through stressor-vm + """ + def __init__(self, _config): + self.qvm_list = [] + for vmindex in range(int(S.getValue('NN_COUNT'))): + qvm = QemuVM(vmindex) + self.qvm_list.append(qvm) + + def start(self): + """Start stressor VMs + """ + for nvm in self.qvm_list: + nvm.start() + + def kill(self, signal='-9', sleep=2): + """ + Stop Stressor VMs + """ + for nvm in self.qvm_list: + nvm.stop(signal, sleep) diff --git a/tools/module_manager.py b/tools/module_manager.py index dd1d92be..943399ba 100644 --- a/tools/module_manager.py +++ b/tools/module_manager.py @@ -160,7 +160,7 @@ class ModuleManager(object): self._logger.info('Unable to get list of dependecies for module \'%s\'.', module) # ...and try to continue, just for case that dependecies are already loaded - if len(deps): + if deps: return deps.split(',') else: return [] diff --git a/tools/namespace.py b/tools/namespace.py index 9131398f..50374b95 100644 --- a/tools/namespace.py +++ b/tools/namespace.py @@ -135,9 +135,8 @@ def reset_port_to_root(port, name): port, name), False) -# pylint: disable=unused-argument # pylint: disable=invalid-name -def validate_add_ip_to_namespace_eth(result, port, name, ip_addr, cidr): +def validate_add_ip_to_namespace_eth(_result, port, name, ip_addr, cidr): """ Validation function for integration testcases """ @@ -147,7 +146,7 @@ def validate_add_ip_to_namespace_eth(result, port, name, ip_addr, cidr): _LOGGER, 'Validating ip address in namespace...', False)) -def validate_assign_port_to_namespace(result, port, name, port_up=False): +def validate_assign_port_to_namespace(_result, port, name, _port_up=False): """ Validation function for integration testcases """ @@ -157,14 +156,14 @@ def validate_assign_port_to_namespace(result, port, name, port_up=False): _LOGGER, 'Validating port in namespace...')) -def validate_create_namespace(result, name): +def validate_create_namespace(_result, name): """ Validation function for integration testcases """ return name in get_system_namespace_list() -def validate_delete_namespace(result, name): +def validate_delete_namespace(_result, name): """ Validation function for integration testcases """ diff --git a/tools/networkcard.py b/tools/networkcard.py index 2cd296fb..758010d2 100644 --- a/tools/networkcard.py +++ b/tools/networkcard.py @@ -191,7 +191,7 @@ def get_mac(pci_handle): """ mac_path = glob.glob(os.path.join(_PCI_DIR, _PCI_NET, '*', 'address').format(pci_handle)) # kernel driver is loaded and MAC can be read - if len(mac_path) and os.path.isfile(mac_path[0]): + if mac_path and os.path.isfile(mac_path[0]): with open(mac_path[0], 'r') as _file: return _file.readline().rstrip('\n') diff --git a/tools/pkt_gen/dummy/dummy.py b/tools/pkt_gen/dummy/dummy.py index 3dc5448e..ef4b37d9 100755 --- a/tools/pkt_gen/dummy/dummy.py +++ b/tools/pkt_gen/dummy/dummy.py @@ -25,6 +25,7 @@ own. import json +from collections import OrderedDict from conf import settings from conf import merge_spec from tools.pkt_gen import trafficgen @@ -108,41 +109,41 @@ class Dummy(trafficgen.ITrafficGenerator): """ pass - def send_burst_traffic(self, traffic=None, numpkts=100, duration=20): + def send_burst_traffic(self, traffic=None, duration=20): """ Send a burst of traffic. """ traffic_ = self.traffic_defaults.copy() - result = {} + result = OrderedDict() if traffic: traffic_ = merge_spec(traffic_, traffic) results = get_user_traffic( 'burst', - '%dpkts, %dmS' % (numpkts, duration), + '%dpkts, %dmS' % (traffic['burst_size'], duration), traffic_, ('frames rx', 'payload errors', 'sequence errors')) # builds results by using user-supplied values where possible # and guessing remainder using available info - result[ResultsConstants.TX_FRAMES] = numpkts + result[ResultsConstants.TX_FRAMES] = traffic['burst_size'] result[ResultsConstants.RX_FRAMES] = results[0] result[ResultsConstants.TX_BYTES] = traffic_['l2']['framesize'] \ - * numpkts + * traffic['burst_size'] result[ResultsConstants.RX_BYTES] = traffic_['l2']['framesize'] \ * results[0] result[ResultsConstants.PAYLOAD_ERR] = results[1] result[ResultsConstants.SEQ_ERR] = results[2] - return results + return result def send_cont_traffic(self, traffic=None, duration=30): """ Send a continuous flow of traffic. """ traffic_ = self.traffic_defaults.copy() - result = {} + result = OrderedDict() if traffic: traffic_ = merge_spec(traffic_, traffic) @@ -179,7 +180,7 @@ class Dummy(trafficgen.ITrafficGenerator): Send traffic per RFC2544 throughput test specifications. """ traffic_ = self.traffic_defaults.copy() - result = {} + result = OrderedDict() if traffic: traffic_ = merge_spec(traffic_, traffic) @@ -216,7 +217,7 @@ class Dummy(trafficgen.ITrafficGenerator): Send traffic per RFC2544 back2back test specifications. """ traffic_ = self.traffic_defaults.copy() - result = {} + result = OrderedDict() if traffic: traffic_ = merge_spec(traffic_, traffic) @@ -273,4 +274,5 @@ if __name__ == '__main__': print(dev.send_cont_traffic(traffic=TRAFFIC)) print(dev.send_rfc2544_throughput(traffic=TRAFFIC)) print(dev.send_rfc2544_back2back(traffic=TRAFFIC)) + # pylint: disable=no-member print(dev.send_rfc(traffic=TRAFFIC)) diff --git a/tools/pkt_gen/ixia/ixia.py b/tools/pkt_gen/ixia/ixia.py index d4ca56f2..31f51246 100755 --- a/tools/pkt_gen/ixia/ixia.py +++ b/tools/pkt_gen/ixia/ixia.py @@ -157,8 +157,8 @@ class Ixia(trafficgen.ITrafficGenerator): return NotImplementedError( 'Ixia start back2back traffic not implemented') - def send_rfc2544_back2back(self, traffic=None, duration=60, - lossrate=0.0, tests=1): + def send_rfc2544_back2back(self, traffic=None, tests=1, duration=60, + lossrate=0.0): return NotImplementedError( 'Ixia send back2back traffic not implemented') @@ -242,11 +242,11 @@ class Ixia(trafficgen.ITrafficGenerator): return result - def send_burst_traffic(self, traffic=None, numpkts=100, duration=20): + def send_burst_traffic(self, traffic=None, duration=20): """See ITrafficGenerator for description """ flow = { - 'numpkts': numpkts, + 'numpkts': traffic['burst_size'], 'duration': duration, 'type': 'stopStream', 'framerate': traffic['frame_rate'], @@ -254,9 +254,9 @@ class Ixia(trafficgen.ITrafficGenerator): result = self._send_traffic(flow, traffic) - assert len(result) == 6 # fail-fast if underlying Tcl code changes + assert len(result) == 10 # fail-fast if underlying Tcl code changes - #NOTE - implement Burst results setting via TrafficgenResults. + return Ixia._create_result(result) def send_cont_traffic(self, traffic=None, duration=30): """See ITrafficGenerator for description @@ -317,20 +317,25 @@ class Ixia(trafficgen.ITrafficGenerator): :returns: dictionary strings representing results from traffic generator. """ - assert len(result) == 8 # fail-fast if underlying Tcl code changes + assert len(result) == 8 or len(result) == 10 # fail-fast if underlying Tcl code changes + + # content of result common for all tests + # [framesSent, framesRecv, bytesSent, bytesRecv, sendRate, recvRate, sendRateBytes, recvRateBytes] + # burst test has additional two values at the end: payError, seqError if float(result[0]) == 0: loss_rate = 100 else: - loss_rate = (float(result[0]) - float(result[1])) / float(result[0]) * 100 + loss_rate = round((float(result[0]) - float(result[1])) / float(result[0]) * 100, 5) result_dict = OrderedDict() - # drop the first 4 elements as we don't use/need them. In - # addition, IxExplorer does not support latency or % line rate + # IxExplorer does not support latency or % line rate # metrics so we have to return dummy values for these metrics - result_dict[ResultsConstants.THROUGHPUT_RX_FPS] = result[4] - result_dict[ResultsConstants.TX_RATE_FPS] = result[5] - result_dict[ResultsConstants.THROUGHPUT_RX_MBPS] = str(round(int(result[6]) / 1000000, 3)) - result_dict[ResultsConstants.TX_RATE_MBPS] = str(round(int(result[7]) / 1000000, 3)) + result_dict[ResultsConstants.TX_FRAMES] = result[0] + result_dict[ResultsConstants.RX_FRAMES] = result[1] + result_dict[ResultsConstants.TX_RATE_FPS] = result[4] + result_dict[ResultsConstants.THROUGHPUT_RX_FPS] = result[5] + result_dict[ResultsConstants.TX_RATE_MBPS] = str(round(int(result[6]) * 8 / 1e6, 3)) + result_dict[ResultsConstants.THROUGHPUT_RX_MBPS] = str(round(int(result[7]) * 8 / 1e6, 3)) result_dict[ResultsConstants.FRAME_LOSS_PERCENT] = loss_rate result_dict[ResultsConstants.TX_RATE_PERCENT] = \ ResultsConstants.UNKNOWN_VALUE diff --git a/tools/pkt_gen/ixnet/ixnet.py b/tools/pkt_gen/ixnet/ixnet.py index d1ba9096..87fb2c65 100755 --- a/tools/pkt_gen/ixnet/ixnet.py +++ b/tools/pkt_gen/ixnet/ixnet.py @@ -370,7 +370,7 @@ class IxNet(trafficgen.ITrafficGenerator): next(reader) for row in reader: #Replace null entries added by Ixia with 0s. - row = [entry if len(entry) > 0 else '0' for entry in row] + row = [entry if entry else '0' for entry in row] # tx_fps and tx_mps cannot be reliably calculated # as the DUT may be modifying the frame size @@ -528,7 +528,7 @@ class IxNet(trafficgen.ITrafficGenerator): return parse_ixnet_rfc_results(parse_result_string(output[0])) - def send_burst_traffic(self, traffic=None, numpkts=100, duration=20): + def send_burst_traffic(self, traffic=None, duration=20): return NotImplementedError('IxNet does not implement send_burst_traffic') if __name__ == '__main__': diff --git a/tools/pkt_gen/moongen/moongen.py b/tools/pkt_gen/moongen/moongen.py index 570720e8..b7d55c4d 100644 --- a/tools/pkt_gen/moongen/moongen.py +++ b/tools/pkt_gen/moongen/moongen.py @@ -64,46 +64,46 @@ class Moongen(ITrafficGenerator): :param one_shot: No RFC 2544 binary search, just packet flow at traffic specifics """ - logging.debug("traffic['frame_rate'] = " + \ + logging.debug("traffic['frame_rate'] = %s", \ str(traffic['frame_rate'])) - logging.debug("traffic['multistream'] = " + \ + logging.debug("traffic['multistream'] = %s", \ str(traffic['multistream'])) - logging.debug("traffic['stream_type'] = " + \ + logging.debug("traffic['stream_type'] = %s", \ str(traffic['stream_type'])) - logging.debug("traffic['l2']['srcmac'] = " + \ + logging.debug("traffic['l2']['srcmac'] = %s", \ str(traffic['l2']['srcmac'])) - logging.debug("traffic['l2']['dstmac'] = " + \ + logging.debug("traffic['l2']['dstmac'] = %s", \ str(traffic['l2']['dstmac'])) - logging.debug("traffic['l3']['proto'] = " + \ + logging.debug("traffic['l3']['proto'] = %s", \ str(traffic['l3']['proto'])) - logging.debug("traffic['l3']['srcip'] = " + \ + logging.debug("traffic['l3']['srcip'] = %s", \ str(traffic['l3']['srcip'])) - logging.debug("traffic['l3']['dstip'] = " + \ + logging.debug("traffic['l3']['dstip'] = %s", \ str(traffic['l3']['dstip'])) - logging.debug("traffic['l4']['srcport'] = " + \ + logging.debug("traffic['l4']['srcport'] = %s", \ str(traffic['l4']['srcport'])) - logging.debug("traffic['l4']['dstport'] = " + \ + logging.debug("traffic['l4']['dstport'] = %s", \ str(traffic['l4']['dstport'])) - logging.debug("traffic['vlan']['enabled'] = " + \ + logging.debug("traffic['vlan']['enabled'] = %s", \ str(traffic['vlan']['enabled'])) - logging.debug("traffic['vlan']['id'] = " + \ + logging.debug("traffic['vlan']['id'] = %s", \ str(traffic['vlan']['id'])) - logging.debug("traffic['vlan']['priority'] = " + \ + logging.debug("traffic['vlan']['priority'] = %s", \ str(traffic['vlan']['priority'])) - logging.debug("traffic['vlan']['cfi'] = " + \ + logging.debug("traffic['vlan']['cfi'] = %s", \ str(traffic['vlan']['cfi'])) logging.debug(traffic['l2']['framesize']) @@ -160,9 +160,9 @@ class Moongen(ITrafficGenerator): (traffic['frame_rate'] / 100) * (self._moongen_line_speed / \ (8 * (traffic['l2']['framesize'] + 20)) / math.pow(10, 6))) - logging.debug("startRate = " + start_rate) + logging.debug("startRate = %s", start_rate) - out_file.write("startRate = " + \ + out_file.write("startRate = %s" % \ start_rate + "\n") out_file.write("}" + "\n") @@ -240,14 +240,13 @@ class Moongen(ITrafficGenerator): """ self._logger.info("MOONGEN: In moongen disconnect method") - def send_burst_traffic(self, traffic=None, numpkts=100, duration=20): + def send_burst_traffic(self, traffic=None, duration=20): """Send a burst of traffic. - Send a ``numpkts`` packets of traffic, using ``traffic`` + Send a ``traffic['burst_traffic']`` packets of traffic, using ``traffic`` configuration, with a timeout of ``time``. :param traffic: Detailed "traffic" spec, i.e. IP address, VLAN tags - :param numpkts: Number of packets to send :param duration: Time to wait to receive packets :returns: dictionary of strings with following data: @@ -508,8 +507,8 @@ class Moongen(ITrafficGenerator): return moongen_results - def send_rfc2544_throughput(self, traffic=None, duration=20, - lossrate=0.0, tests=1): + def send_rfc2544_throughput(self, traffic=None, tests=1, duration=20, + lossrate=0.0): # # Send traffic per RFC2544 throughput test specifications. # @@ -631,8 +630,8 @@ class Moongen(ITrafficGenerator): """ self._logger.info('In moongen wait_rfc2544_throughput') - def send_rfc2544_back2back(self, traffic=None, duration=60, - lossrate=0.0, tests=1): + def send_rfc2544_back2back(self, traffic=None, tests=1, duration=60, + lossrate=0.0): """Send traffic per RFC2544 back2back test specifications. Send packets at a fixed rate, using ``traffic`` diff --git a/tools/pkt_gen/testcenter/testcenter.py b/tools/pkt_gen/testcenter/testcenter.py index 9980ae7c..487566bf 100644 --- a/tools/pkt_gen/testcenter/testcenter.py +++ b/tools/pkt_gen/testcenter/testcenter.py @@ -182,7 +182,7 @@ class TestCenter(trafficgen.ITrafficGenerator): """ pass - def send_burst_traffic(self, traffic=None, numpkts=100, duration=20): + def send_burst_traffic(self, traffic=None, duration=20): """ Do nothing. """ @@ -246,8 +246,7 @@ class TestCenter(trafficgen.ITrafficGenerator): row["ForwardingRate(fps)"]) return result - # pylint: disable=unused-argument - def send_rfc2889_forwarding(self, traffic=None, tests=1, duration=20): + def send_rfc2889_forwarding(self, traffic=None, tests=1, _duration=20): """ Send traffic per RFC2889 Forwarding test specifications. """ @@ -257,7 +256,7 @@ class TestCenter(trafficgen.ITrafficGenerator): framesize = traffic['l2']['framesize'] args = get_rfc2889_common_settings(framesize, tests, traffic['traffic_type']) - if settings.getValue("TRAFFICGEN_STC_VERBOSE") is "True": + if settings.getValue("TRAFFICGEN_STC_VERBOSE") == "True": args.append("--verbose") verbose = True self._logger.debug("Arguments used to call test: %s", args) @@ -273,7 +272,7 @@ class TestCenter(trafficgen.ITrafficGenerator): return self.get_rfc2889_forwarding_results(filec) - def send_rfc2889_caching(self, traffic=None, tests=1, duration=20): + def send_rfc2889_caching(self, traffic=None, tests=1, _duration=20): """ Send as per RFC2889 Addr-Caching test specifications. """ @@ -286,7 +285,7 @@ class TestCenter(trafficgen.ITrafficGenerator): custom_args = get_rfc2889_custom_settings() args = common_args + custom_args - if settings.getValue("TRAFFICGEN_STC_VERBOSE") is "True": + if settings.getValue("TRAFFICGEN_STC_VERBOSE") == "True": args.append("--verbose") verbose = True self._logger.debug("Arguments used to call test: %s", args) @@ -302,7 +301,7 @@ class TestCenter(trafficgen.ITrafficGenerator): return self.get_rfc2889_addr_caching_results(filec) - def send_rfc2889_learning(self, traffic=None, tests=1, duration=20): + def send_rfc2889_learning(self, traffic=None, tests=1, _duration=20): """ Send traffic per RFC2889 Addr-Learning test specifications. """ @@ -315,7 +314,7 @@ class TestCenter(trafficgen.ITrafficGenerator): custom_args = get_rfc2889_custom_settings() args = common_args + custom_args - if settings.getValue("TRAFFICGEN_STC_VERBOSE") is "True": + if settings.getValue("TRAFFICGEN_STC_VERBOSE") == "True": args.append("--verbose") verbose = True self._logger.debug("Arguments used to call test: %s", args) @@ -387,7 +386,7 @@ class TestCenter(trafficgen.ITrafficGenerator): custom, 1) args = rfc2544_common_args + stc_common_args + rfc2544_custom_args - if settings.getValue("TRAFFICGEN_STC_VERBOSE") is "True": + if settings.getValue("TRAFFICGEN_STC_VERBOSE") == "True": args.append("--verbose") verbose = True self._logger.debug("Arguments used to call test: %s", args) @@ -420,7 +419,7 @@ class TestCenter(trafficgen.ITrafficGenerator): tests) args = rfc2544_common_args + stc_common_args + rfc2544_custom_args - if settings.getValue("TRAFFICGEN_STC_VERBOSE") is "True": + if settings.getValue("TRAFFICGEN_STC_VERBOSE") == "True": args.append("--verbose") verbose = True self._logger.debug("Arguments used to call test: %s", args) @@ -453,7 +452,7 @@ class TestCenter(trafficgen.ITrafficGenerator): tests) args = rfc2544_common_args + stc_common_args + rfc2544_custom_args - if settings.getValue("TRAFFICGEN_STC_VERBOSE") is "True": + if settings.getValue("TRAFFICGEN_STC_VERBOSE") == "True": args.append("--verbose") verbose = True self._logger.info("Arguments used to call test: %s", args) @@ -498,4 +497,5 @@ if __name__ == '__main__': } with TestCenter() as dev: print(dev.send_rfc2544_throughput(traffic=TRAFFIC)) + # pylint: disable=no-member print(dev.send_rfc2544_backtoback(traffic=TRAFFIC)) diff --git a/tools/pkt_gen/trafficgen/trafficgen.py b/tools/pkt_gen/trafficgen/trafficgen.py index 262df71d..a6f7edcc 100755 --- a/tools/pkt_gen/trafficgen/trafficgen.py +++ b/tools/pkt_gen/trafficgen/trafficgen.py @@ -81,15 +81,14 @@ class ITrafficGenerator(object): """ raise NotImplementedError('Please call an implementation.') - def send_burst_traffic(self, traffic=None, numpkts=100, duration=20): + def send_burst_traffic(self, traffic=None, duration=20): """Send a burst of traffic. - Send a ``numpkts`` packets of traffic, using ``traffic`` + Send a ``traffic['burst_size']`` packets of traffic, using ``traffic`` configuration, for ``duration`` seconds. Attributes: :param traffic: Detailed "traffic" spec, see design docs for details - :param numpkts: Number of packets to send :param duration: Time to wait to receive packets :returns: dictionary of strings with following data: diff --git a/tools/pkt_gen/trex/trex.py b/tools/pkt_gen/trex/trex.py index cfe54b78..94b793d6 100644 --- a/tools/pkt_gen/trex/trex.py +++ b/tools/pkt_gen/trex/trex.py @@ -15,12 +15,14 @@ """ Trex Traffic Generator Model """ + # pylint: disable=undefined-variable import logging import subprocess import sys import time import os +import re from collections import OrderedDict # pylint: disable=unused-import import netaddr @@ -33,6 +35,7 @@ try: # pylint: disable=wrong-import-position, import-error sys.path.append(settings.getValue('PATHS')['trafficgen']['Trex']['src']['path']) from trex_stl_lib.api import * + from trex_stl_lib import trex_stl_exceptions except ImportError: # VSPERF performs detection of T-Rex api during testcase initialization. So if # T-Rex is requsted and API is not available it will fail before this code @@ -68,6 +71,21 @@ _EMPTY_STATS = { 'tx_pps': 0.0, 'tx_util': 0.0,}} +# Default frame definition, which can be overridden by TRAFFIC['scapy']. +# The content of the frame and its network layers are driven by TRAFFIC +# dictionary, i.e. 'l2', 'l3, 'l4' and 'vlan' parts. +_SCAPY_FRAME = { + '0' : 'Ether(src={Ether_src}, dst={Ether_dst})/' + 'Dot1Q(prio={Dot1Q_prio}, id={Dot1Q_id}, vlan={Dot1Q_vlan})/' + 'IP(proto={IP_proto}, src={IP_src}, dst={IP_dst})/' + '{IP_PROTO}(sport={IP_PROTO_sport}, dport={IP_PROTO_dport})', + '1' : 'Ether(src={Ether_dst}, dst={Ether_src})/' + 'Dot1Q(prio={Dot1Q_prio}, id={Dot1Q_id}, vlan={Dot1Q_vlan})/' + 'IP(proto={IP_proto}, src={IP_dst}, dst={IP_src})/' + '{IP_PROTO}(sport={IP_PROTO_dport}, dport={IP_PROTO_sport})', +} + + class Trex(ITrafficGenerator): """Trex Traffic generator wrapper.""" _logger = logging.getLogger(__name__) @@ -84,6 +102,20 @@ class Trex(ITrafficGenerator): self._trex_user = settings.getValue('TRAFFICGEN_TREX_USER') self._stlclient = None self._verification_params = None + self._show_packet_data = False + + def show_packet_info(self, packet_a, packet_b): + """ + Log packet layers to screen + :param packet_a: Scapy.layers packet + :param packet_b: Scapy.layers packet + :return: None + """ + # we only want to show packet data once per test + if self._show_packet_data: + self._show_packet_data = False + self._logger.info(packet_a.show()) + self._logger.info(packet_b.show()) def connect(self): '''Connect to Trex traffic generator @@ -149,35 +181,77 @@ class Trex(ITrafficGenerator): self._logger.info("T-Rex: In trex disconnect method") self._stlclient.disconnect(stop_traffic=True, release_ports=True) - @staticmethod - def create_packets(traffic, ports_info): + def create_packets(self, traffic, ports_info): """Create base packet according to traffic specification. If traffic haven't specified srcmac and dstmac fields - packet will be create with mac address of trex server. + packet will be created with mac address of trex server. """ - mac_add = [li['hw_mac'] for li in ports_info] - - if traffic and traffic['l2']['framesize'] > 0: - if traffic['l2']['dstmac'] == '00:00:00:00:00:00' and \ - traffic['l2']['srcmac'] == '00:00:00:00:00:00': - base_pkt_a = Ether(src=mac_add[0], dst=mac_add[1])/ \ - IP(proto=traffic['l3']['proto'], src=traffic['l3']['srcip'], - dst=traffic['l3']['dstip'])/ \ - UDP(dport=traffic['l4']['dstport'], sport=traffic['l4']['srcport']) - base_pkt_b = Ether(src=mac_add[1], dst=mac_add[0])/ \ - IP(proto=traffic['l3']['proto'], src=traffic['l3']['dstip'], - dst=traffic['l3']['srcip'])/ \ - UDP(dport=traffic['l4']['srcport'], sport=traffic['l4']['dstport']) - else: - base_pkt_a = Ether(src=traffic['l2']['srcmac'], dst=traffic['l2']['dstmac'])/ \ - IP(proto=traffic['l3']['proto'], src=traffic['l3']['dstip'], - dst=traffic['l3']['srcip'])/ \ - UDP(dport=traffic['l4']['dstport'], sport=traffic['l4']['srcport']) + if not traffic or traffic['l2']['framesize'] <= 0: + return (None, None) + + if traffic['l2']['dstmac'] == '00:00:00:00:00:00' and \ + traffic['l2']['srcmac'] == '00:00:00:00:00:00': + + mac_add = [li['hw_mac'] for li in ports_info] + src_mac = mac_add[0] + dst_mac = mac_add[1] + else: + src_mac = traffic['l2']['srcmac'] + dst_mac = traffic['l2']['dstmac'] - base_pkt_b = Ether(src=traffic['l2']['dstmac'], dst=traffic['l2']['srcmac'])/ \ - IP(proto=traffic['l3']['proto'], src=traffic['l3']['dstip'], - dst=traffic['l3']['srcip'])/ \ - UDP(dport=traffic['l4']['srcport'], sport=traffic['l4']['dstport']) + if traffic['scapy']['enabled']: + base_pkt_a = traffic['scapy']['0'] + base_pkt_b = traffic['scapy']['1'] + else: + base_pkt_a = _SCAPY_FRAME['0'] + base_pkt_b = _SCAPY_FRAME['1'] + + # check and remove network layers disabled by TRAFFIC dictionary + # Note: In general, it is possible to remove layers from scapy object by + # e.g. del base_pkt_a['IP']. However it doesn't work for all layers + # (e.g. Dot1Q). Thus it is safer to modify string with scapy frame definition + # directly, before it is converted to the real scapy object. + if not traffic['vlan']['enabled']: + self._logger.info('VLAN headers are disabled by TRAFFIC') + base_pkt_a = re.sub(r'(^|\/)Dot1Q?\([^\)]*\)', '', base_pkt_a) + base_pkt_b = re.sub(r'(^|\/)Dot1Q?\([^\)]*\)', '', base_pkt_b) + if not traffic['l3']['enabled']: + self._logger.info('IP headers are disabled by TRAFFIC') + base_pkt_a = re.sub(r'(^|\/)IP(v6)?\([^\)]*\)', '', base_pkt_a) + base_pkt_b = re.sub(r'(^|\/)IP(v6)?\([^\)]*\)', '', base_pkt_b) + if not traffic['l4']['enabled']: + self._logger.info('%s headers are disabled by TRAFFIC', + traffic['l3']['proto'].upper()) + base_pkt_a = re.sub(r'(^|\/)(UDP|TCP|SCTP|{{IP_PROTO}}|{})\([^\)]*\)'.format( + traffic['l3']['proto'].upper()), '', base_pkt_a) + base_pkt_b = re.sub(r'(^|\/)(UDP|TCP|SCTP|{{IP_PROTO}}|{})\([^\)]*\)'.format( + traffic['l3']['proto'].upper()), '', base_pkt_b) + + # pylint: disable=eval-used + base_pkt_a = eval(base_pkt_a.format( + Ether_src=repr(src_mac), + Ether_dst=repr(dst_mac), + Dot1Q_prio=traffic['vlan']['priority'], + Dot1Q_id=traffic['vlan']['cfi'], + Dot1Q_vlan=traffic['vlan']['id'], + IP_proto=repr(traffic['l3']['proto']), + IP_PROTO=traffic['l3']['proto'].upper(), + IP_src=repr(traffic['l3']['srcip']), + IP_dst=repr(traffic['l3']['dstip']), + IP_PROTO_sport=traffic['l4']['srcport'], + IP_PROTO_dport=traffic['l4']['dstport'])) + base_pkt_b = eval(base_pkt_b.format( + Ether_src=repr(src_mac), + Ether_dst=repr(dst_mac), + Dot1Q_prio=traffic['vlan']['priority'], + Dot1Q_id=traffic['vlan']['cfi'], + Dot1Q_vlan=traffic['vlan']['id'], + IP_proto=repr(traffic['l3']['proto']), + IP_PROTO=traffic['l3']['proto'].upper(), + IP_src=repr(traffic['l3']['srcip']), + IP_dst=repr(traffic['l3']['dstip']), + IP_PROTO_sport=traffic['l4']['srcport'], + IP_PROTO_dport=traffic['l4']['dstport'])) return (base_pkt_a, base_pkt_b) @@ -232,22 +306,48 @@ class Trex(ITrafficGenerator): pkt_a = STLPktBuilder(pkt=base_pkt_a / payload_a) pkt_b = STLPktBuilder(pkt=base_pkt_b / payload_b) - stream_1 = STLStream(packet=pkt_a, - name='stream_1', - mode=STLTXCont(percentage=traffic['frame_rate'])) - stream_2 = STLStream(packet=pkt_b, - name='stream_2', - mode=STLTXCont(percentage=traffic['frame_rate'])) lat_pps = settings.getValue('TRAFFICGEN_TREX_LATENCY_PPS') - if lat_pps > 0: - stream_1_lat = STLStream(packet=pkt_a, + if traffic['traffic_type'] == 'burst': + if lat_pps > 0: + # latency statistics are requested; in case of frame burst we can enable + # statistics for all frames + stream_1 = STLStream(packet=pkt_a, flow_stats=STLFlowLatencyStats(pg_id=0), - name='stream_1_lat', - mode=STLTXCont(pps=lat_pps)) - stream_2_lat = STLStream(packet=pkt_b, + name='stream_1', + mode=STLTXSingleBurst(percentage=traffic['frame_rate'], + total_pkts=traffic['burst_size'])) + stream_2 = STLStream(packet=pkt_b, flow_stats=STLFlowLatencyStats(pg_id=1), - name='stream_2_lat', - mode=STLTXCont(pps=lat_pps)) + name='stream_2', + mode=STLTXSingleBurst(percentage=traffic['frame_rate'], + total_pkts=traffic['burst_size'])) + else: + stream_1 = STLStream(packet=pkt_a, + name='stream_1', + mode=STLTXSingleBurst(percentage=traffic['frame_rate'], + total_pkts=traffic['burst_size'])) + stream_2 = STLStream(packet=pkt_b, + name='stream_2', + mode=STLTXSingleBurst(percentage=traffic['frame_rate'], + total_pkts=traffic['burst_size'])) + else: + stream_1 = STLStream(packet=pkt_a, + name='stream_1', + mode=STLTXCont(percentage=traffic['frame_rate'])) + stream_2 = STLStream(packet=pkt_b, + name='stream_2', + mode=STLTXCont(percentage=traffic['frame_rate'])) + # workaround for latency statistics, which can't be enabled for streams + # with high framerate due to the huge performance impact + if lat_pps > 0: + stream_1_lat = STLStream(packet=pkt_a, + flow_stats=STLFlowLatencyStats(pg_id=0), + name='stream_1_lat', + mode=STLTXCont(pps=lat_pps)) + stream_2_lat = STLStream(packet=pkt_b, + flow_stats=STLFlowLatencyStats(pg_id=1), + name='stream_2_lat', + mode=STLTXCont(pps=lat_pps)) return (stream_1, stream_2, stream_1_lat, stream_2_lat) @@ -262,11 +362,29 @@ class Trex(ITrafficGenerator): self._stlclient.set_service_mode(ports=my_ports, enabled=False) ports_info = self._stlclient.get_port_info(my_ports) + + # get max support speed + max_speed = 0 + if settings.getValue('TRAFFICGEN_TREX_FORCE_PORT_SPEED'): + max_speed = settings.getValue('TRAFFICGEN_TREX_PORT_SPEED') + elif ports_info[0]['supp_speeds']: + max_speed_1 = max(ports_info[0]['supp_speeds']) + max_speed_2 = max(ports_info[1]['supp_speeds']) + else: + # if max supported speed not in port info or set manually, just assume 10G + max_speed = 10000 + if not max_speed: + # since we can only control both ports at once take the lower of the two + max_speed = min(max_speed_1, max_speed_2) + gbps_speed = (max_speed / 1000) * (float(traffic['frame_rate']) / 100.0) + self._logger.debug('Starting traffic at %s Gbps speed', gbps_speed) + # for SR-IOV if settings.getValue('TRAFFICGEN_TREX_PROMISCUOUS'): self._stlclient.set_port_attr(my_ports, promiscuous=True) - packet_1, packet_2 = Trex.create_packets(traffic, ports_info) + packet_1, packet_2 = self.create_packets(traffic, ports_info) + self.show_packet_info(packet_1, packet_2) stream_1, stream_2, stream_1_lat, stream_2_lat = Trex.create_streams(packet_1, packet_2, traffic) self._stlclient.add_streams(stream_1, ports=[0]) self._stlclient.add_streams(stream_2, ports=[1]) @@ -289,7 +407,13 @@ class Trex(ITrafficGenerator): pcap_id[pcap_dir] = self._stlclient.start_capture(**capture) self._stlclient.clear_stats() - self._stlclient.start(ports=my_ports, force=True, duration=duration) + # if the user did not start up T-Rex server with more than default cores, use default mask. + # Otherwise use mask to take advantage of multiple cores. + try: + self._stlclient.start(ports=my_ports, force=True, duration=duration, mult="{}gbps".format(gbps_speed), + core_mask=self._stlclient.CORE_MASK_PIN) + except STLError: + self._stlclient.start(ports=my_ports, force=True, duration=duration, mult="{}gbps".format(gbps_speed)) self._stlclient.wait_on_traffic(ports=my_ports) stats = self._stlclient.get_stats(sync_now=True) @@ -342,20 +466,29 @@ class Trex(ITrafficGenerator): result[ResultsConstants.FRAME_LOSS_PERCENT] = 100 if settings.getValue('TRAFFICGEN_TREX_LATENCY_PPS') > 0 and stats['latency']: - result[ResultsConstants.MIN_LATENCY_NS] = ( - '{:.3f}'.format( - (float(min(stats["latency"][0]["latency"]["total_min"], - stats["latency"][1]["latency"]["total_min"]))))) - - result[ResultsConstants.MAX_LATENCY_NS] = ( - '{:.3f}'.format( - (float(max(stats["latency"][0]["latency"]["total_max"], - stats["latency"][1]["latency"]["total_max"]))))) - - result[ResultsConstants.AVG_LATENCY_NS] = ( - '{:.3f}'.format( - float((stats["latency"][0]["latency"]["average"]+ - stats["latency"][1]["latency"]["average"])/2))) + try: + result[ResultsConstants.MIN_LATENCY_NS] = ( + '{:.3f}'.format( + (float(min(stats["latency"][0]["latency"]["total_min"], + stats["latency"][1]["latency"]["total_min"]))))) + except TypeError: + result[ResultsConstants.MIN_LATENCY_NS] = 'Unknown' + + try: + result[ResultsConstants.MAX_LATENCY_NS] = ( + '{:.3f}'.format( + (float(max(stats["latency"][0]["latency"]["total_max"], + stats["latency"][1]["latency"]["total_max"]))))) + except TypeError: + result[ResultsConstants.MAX_LATENCY_NS] = 'Unknown' + + try: + result[ResultsConstants.AVG_LATENCY_NS] = ( + '{:.3f}'.format( + float((stats["latency"][0]["latency"]["average"]+ + stats["latency"][1]["latency"]["average"])/2))) + except TypeError: + result[ResultsConstants.AVG_LATENCY_NS] = 'Unknown' else: result[ResultsConstants.MIN_LATENCY_NS] = 'Unknown' @@ -414,9 +547,11 @@ class Trex(ITrafficGenerator): if test_lossrate <= lossrate: # save the last passing trial for verification self._verification_params = copy.deepcopy(new_params) - self._logger.debug("Iteration: %s, frame rate: %s, throughput_rx_fps: %s, frame_loss_percent: %s", - iteration, "{:.3f}".format(new_params['frame_rate']), stats['total']['rx_pps'], - "{:.3f}".format(test_lossrate)) + packets_lost = stats['total']['opackets'] - stats['total']['ipackets'] + self._logger.debug("Iteration: %s, frame rate: %s, throughput_rx_fps: %s," + + " frames lost %s, frame_loss_percent: %s", iteration, + "{:.3f}".format(new_params['frame_rate']), stats['total']['rx_pps'], + packets_lost, "{:.3f}".format(test_lossrate)) if test_lossrate == 0.0 and new_params['frame_rate'] == traffic['frame_rate']: return copy.deepcopy(stats) elif test_lossrate > lossrate: @@ -439,6 +574,8 @@ class Trex(ITrafficGenerator): self._logger.info("In Trex send_cont_traffic method") self._params.clear() + self._show_packet_data = True + self._params['traffic'] = self.traffic_defaults.copy() if traffic: self._params['traffic'] = merge_spec( @@ -467,6 +604,7 @@ class Trex(ITrafficGenerator): """ self._logger.info("In Trex send_rfc2544_throughput method") self._params.clear() + self._show_packet_data = True self._params['traffic'] = self.traffic_defaults.copy() if traffic: self._params['traffic'] = merge_spec( @@ -523,9 +661,25 @@ class Trex(ITrafficGenerator): raise NotImplementedError( 'Trex wait rfc2544 throughput not implemented') - def send_burst_traffic(self, traffic=None, numpkts=100, duration=5): - raise NotImplementedError( - 'Trex send burst traffic not implemented') + def send_burst_traffic(self, traffic=None, duration=20): + """See ITrafficGenerator for description + """ + self._logger.info("In Trex send_burst_traffic method") + self._params.clear() + + self._params['traffic'] = self.traffic_defaults.copy() + if traffic: + self._params['traffic'] = merge_spec( + self._params['traffic'], traffic) + + if settings.getValue('TRAFFICGEN_TREX_LEARNING_MODE'): + self.learning_packets(traffic) + self._logger.info("T-Rex sending traffic") + stats = self.generate_traffic(traffic, duration) + + time.sleep(3) # allow packets to complete before reading stats + + return self.calculate_results(stats) def send_rfc2544_back2back(self, traffic=None, tests=1, duration=30, lossrate=0.0): diff --git a/tools/pkt_gen/xena/XenaDriver.py b/tools/pkt_gen/xena/XenaDriver.py index 6e39e47a..ac9cef1c 100644 --- a/tools/pkt_gen/xena/XenaDriver.py +++ b/tools/pkt_gen/xena/XenaDriver.py @@ -30,6 +30,7 @@ through socket commands and returning different statistics. """ import locale import logging +import math import socket import struct import sys @@ -86,6 +87,26 @@ CMD_VERSION = 'c_versionno ?' _LOCALE = locale.getlocale()[1] _LOGGER = logging.getLogger(__name__) +class ModSet(object): + """ + Mod set attribute tracker + """ + def __init__(self, **kwargs): + """ Constructor + All mods default to False + :param kwargs: Any class attribute can be set here. + """ + self.mod_src_mac = False + self.mod_dst_mac = False + self.mod_src_ip = False + self.mod_dst_ip = False + self.mod_src_port = False + self.mod_dst_port = False + + for (key, value) in kwargs.items(): + if hasattr(self, key): + setattr(self, key, value) + class SimpleSocket(object): """ @@ -170,8 +191,7 @@ class KeepAliveThread(threading.Thread): self.finished = threading.Event() self.setDaemon(True) _LOGGER.debug( - 'Xena Socket keep alive thread initiated, interval ' + - '{} seconds'.format(self.interval)) + 'Xena Socket keep alive thread initiated, interval %s seconds', self.interval) def stop(self): """ Thread stop. See python thread docs for more info @@ -640,57 +660,98 @@ class XenaStream(object): """ return self._stream_id - def enable_multistream(self, flows, layer): + def enable_multistream(self, flows, mod_class): """ - Basic implementation of multi stream. Enable multi stream by setting - modifiers on the stream - :param flows: Numbers of flows or end range - :param layer: layer to enable multi stream as str. Acceptable values - are L2, L3, or L4 + Implementation of multi stream. Enable multi stream by setting + modifiers on the stream. If no mods are selected, src_ip mod will be used. + :param flows: Numbers of flows, Values greater than 65535 will square rooted + to the closest value. Xena mods are limited to 4 bytes. + :param mod_class: ModSet object :return: True if success False otherwise """ if not self._header_protocol: raise RuntimeError( "Please set a protocol header before calling this method.") - - # byte offsets for setting the modifier - offsets = { - 'L2': [0, 6], - 'L3': [32, 36] if 'VLAN' in self._header_protocol else [28, 32], - 'L4': [38, 40] if 'VLAN' in self._header_protocol else [34, 36] - } - - responses = list() - if layer in offsets.keys() and flows > 0: - command = make_port_command( - CMD_STREAM_MODIFIER_COUNT + ' [{}]'.format(self._stream_id) + - ' 2', self._xena_port) - responses.append(self._manager.driver.ask_verify(command)) - command = make_port_command( - CMD_STREAM_MODIFIER + ' [{},0] {} 0xFFFF0000 INC 1'.format( - self._stream_id, offsets[layer][0]), self._xena_port) - responses.append(self._manager.driver.ask_verify(command)) - command = make_port_command( - CMD_STREAM_MODIFIER_RANGE + ' [{},0] 0 1 {}'.format( - self._stream_id, flows), self._xena_port) - responses.append(self._manager.driver.ask_verify(command)) - command = make_port_command( - CMD_STREAM_MODIFIER + ' [{},1] {} 0xFFFF0000 INC 1'.format( - self._stream_id, offsets[layer][1]), self._xena_port) - responses.append(self._manager.driver.ask_verify(command)) - command = make_port_command( - CMD_STREAM_MODIFIER_RANGE + ' [{},1] 0 1 {}'.format( - self._stream_id, flows), self._xena_port) - responses.append(self._manager.driver.ask_verify(command)) - return all(responses) # return True if they all worked - elif flows < 1: - _LOGGER.warning( - 'No flows specified in enable multistream. Bypassing...') - return False + # maximum value for a Xena modifier is 65535 (unsigned int). If flows + # is greater than 65535 we have to do two mods getting as close as we + # can with square rooting the flow count. + if flows > 4294836225: + _LOGGER.debug('Flow mods exceeds highest value, changing to 4294836225') + flows = 4294836225 + if flows <= 65535: + mod1 = flows + mod2 = 0 else: - raise NotImplementedError( - "Non-implemented stream layer in method enable multistream ", - "layer=", layer) + mod1, mod2 = int(math.sqrt(flows)), int(math.sqrt(flows)) + _LOGGER.debug('Flow count modified to %s', mod1*mod2) + offset_list = list() + if not any([mod_class.mod_src_mac, mod_class.mod_dst_mac, mod_class.mod_src_ip, + mod_class.mod_dst_ip, mod_class.mod_src_port, mod_class.mod_dst_port]): + # no mods were selected, default to src ip only + mod_class.mod_src_ip = True + if mod_class.mod_src_mac: + offset_list.append(3) + if mod_class.mod_dst_mac: + offset_list.append(9) + if mod_class.mod_src_ip: + offset_list.append(32 if 'VLAN' in self._header_protocol else 28) + if mod_class.mod_dst_ip: + offset_list.append(36 if 'VLAN' in self._header_protocol else 32) + if mod_class.mod_src_port: + offset_list.append(38 if 'VLAN' in self._header_protocol else 34) + if mod_class.mod_dst_port: + offset_list.append(40 if 'VLAN' in self._header_protocol else 36) + # calculate how many mods we have to do + countertotal = len(offset_list) + if mod2: + # to handle flows greater than 65535 we will need more mods for + # layer 2 and 3 + for mod in [mod_class.mod_src_mac, mod_class.mod_dst_mac, + mod_class.mod_src_ip, mod_class.mod_dst_ip]: + if mod: + countertotal += 1 + command = make_port_command( + CMD_STREAM_MODIFIER_COUNT + ' [{}]'.format(self._stream_id) + + ' {}'.format(countertotal), self._xena_port) + responses = list() + responses.append(self._manager.driver.ask_verify(command)) + modcounter = 0 + for offset in offset_list: + if (mod_class.mod_dst_port or mod_class.mod_src_port) and \ + (offset >= 38 if 'VLAN' in self._header_protocol else 34): + # only do a 1 mod for udp ports at max 65535 + newmod1 = 65535 if flows >= 65535 else flows + command = make_port_command( + CMD_STREAM_MODIFIER + ' [{},{}] {} 0xFFFF0000 INC 1'.format( + self._stream_id, modcounter, offset), self._xena_port) + responses.append(self._manager.driver.ask_verify(command)) + command = make_port_command( + CMD_STREAM_MODIFIER_RANGE + ' [{},{}] 0 1 {}'.format( + self._stream_id, modcounter, newmod1 - 1), self._xena_port) + responses.append(self._manager.driver.ask_verify(command)) + else: + command = make_port_command( + CMD_STREAM_MODIFIER + ' [{},{}] {} 0xFFFF0000 INC 1'.format( + self._stream_id, modcounter, offset), self._xena_port) + responses.append(self._manager.driver.ask_verify(command)) + command = make_port_command( + CMD_STREAM_MODIFIER_RANGE + ' [{},{}] 0 1 {}'.format( + self._stream_id, modcounter, mod1 - 1), self._xena_port) + responses.append(self._manager.driver.ask_verify(command)) + # if we have a second modifier set the modifier to mod2 and to + # incremement once every full rotation of mod 1 + if mod2: + modcounter += 1 + command = make_port_command( + CMD_STREAM_MODIFIER + ' [{},{}] {} 0xFFFF0000 INC {}'.format( + self._stream_id, modcounter, offset-2, mod1), self._xena_port) + responses.append(self._manager.driver.ask_verify(command)) + command = make_port_command( + CMD_STREAM_MODIFIER_RANGE + ' [{},{}] 0 1 {}'.format( + self._stream_id, modcounter, mod2), self._xena_port) + responses.append(self._manager.driver.ask_verify(command)) + modcounter += 1 + return all(responses) # return True if they all worked def get_stream_data(self): """ @@ -904,7 +965,7 @@ class XenaRXStats(object): statdict[entry_id] = self._pack_stats(param, 3) elif param[1] == 'PR_TPLDS': tid_list = self._pack_tplds_stats(param, 2) - if len(tid_list): + if tid_list: statdict['pr_tplds'] = tid_list elif param[1] == 'PR_TPLDTRAFFIC': if 'pr_tpldstraffic' in statdict: diff --git a/tools/pkt_gen/xena/json/xena_json.py b/tools/pkt_gen/xena/json/xena_json.py index b1eed720..e56b4125 100644 --- a/tools/pkt_gen/xena/json/xena_json.py +++ b/tools/pkt_gen/xena/json/xena_json.py @@ -26,10 +26,9 @@ Xena JSON module from collections import OrderedDict import locale import logging +import math import os -import scapy.layers.inet as inet - from tools.pkt_gen.xena.json import json_utilities _LOGGER = logging.getLogger(__name__) @@ -73,30 +72,87 @@ class XenaJSON(object): 3: ('Dest IP Addr', 'Src IP Addr'), 4: ('Dest Port', 'Src Port') } - segments = [ - { - "Offset": 0, - "Mask": "//8=", # mask of 255/255 - "Action": "INC", - "StartValue": 0, - "StopValue": stop_value, - "StepValue": 1, - "RepeatCount": 1, - "SegmentId": seg_uuid, - "FieldName": field_name[int(layer)][0] - }, - { - "Offset": 0, - "Mask": "//8=", # mask of 255/255 - "Action": "INC", - "StartValue": 0, - "StopValue": stop_value, - "StepValue": 1, - "RepeatCount": 1, - "SegmentId": seg_uuid, - "FieldName": field_name[int(layer)][1] - } - ] + + if stop_value > 4294836225: + _LOGGER.debug('Flow mods exceeds highest value, changing to 4294836225') + stop_value = 4294836225 + + if stop_value <= 65535 or layer == 4: + segments = [ + { + "Offset": 0 if layer == 4 else 2, + "Mask": "//8=", # mask of 255/255 + "Action": "INC", + "StartValue": 0, + "StopValue": stop_value - 1, + "StepValue": 1, + "RepeatCount": 1, + "SegmentId": seg_uuid, + "FieldName": field_name[int(layer)][0] + }, + { + "Offset": 0 if layer == 4 else 2, + "Mask": "//8=", # mask of 255/255 + "Action": "INC", + "StartValue": 0, + "StopValue": stop_value - 1, + "StepValue": 1, + "RepeatCount": 1, + "SegmentId": seg_uuid, + "FieldName": field_name[int(layer)][1] + } + ] + else: + stop_value = int(math.sqrt(stop_value)) + _LOGGER.debug('Flow count modified to %s', stop_value * stop_value) + segments = [ + { + "Offset": 0 if layer == 3 else 1, + "Mask": "//8=", # mask of 255/255 + "Action": "INC", + "StartValue": 0, + "StopValue": stop_value - 1, + "StepValue": 1, + "RepeatCount": stop_value, + "SegmentId": seg_uuid, + "FieldName": field_name[int(layer)][0] + }, + { + "Offset": 2 if layer == 3 else 3, + "Mask": "//8=", # mask of 255/255 + "Action": "INC", + "StartValue": 0, + "StopValue": stop_value - 1, + "StepValue": 1, + "RepeatCount": 1, + "SegmentId": seg_uuid, + "FieldName": field_name[int(layer)][0] + }, + { + "Offset": 0 if layer == 3 else 1, + "Mask": "//8=", # mask of 255/255 + "Action": "INC", + "StartValue": 0, + "StopValue": stop_value - 1, + "StepValue": 1, + "RepeatCount": stop_value, + "SegmentId": seg_uuid, + "FieldName": field_name[int(layer)][1] + }, + { + "Offset": 2 if layer == 3 else 3, + "Mask": "//8=", # mask of 255/255 + "Action": "INC", + "StartValue": 0, + "StopValue": stop_value - 1, + "StepValue": 1, + "RepeatCount": 1, + "SegmentId": seg_uuid, + "FieldName": field_name[int(layer)][1] + } + ] + + self.json_data['StreamProfileHandler']['EntityList'][entity][ 'StreamConfig']['HwModifiers'] = (segments) @@ -279,6 +335,10 @@ class XenaJSON(object): :param kwargs: Extra params per scapy usage. :return: None """ + # import can't be performed at module level, because it conflicts with import + # of customized scapy version by T-Rex + import scapy.layers.inet as inet + self.packet_data['layer2'] = [ inet.Ether(dst=dst_mac, src=src_mac, **kwargs), inet.Ether(dst=src_mac, src=dst_mac, **kwargs)] @@ -293,6 +353,10 @@ class XenaJSON(object): :param kwargs: Extra params per scapy usage :return: None """ + # import can't be performed at module level, because it conflicts with import + # of customized scapy version by T-Rex + import scapy.layers.inet as inet + self.packet_data['layer3'] = [ inet.IP(src=src_ip, dst=dst_ip, proto=protocol.lower(), **kwargs), inet.IP(src=dst_ip, dst=src_ip, proto=protocol.lower(), **kwargs)] @@ -305,6 +369,10 @@ class XenaJSON(object): :param kwargs: Extra params per scapy usage :return: None """ + # import can't be performed at module level, because it conflicts with import + # of customized scapy version by T-Rex + import scapy.layers.inet as inet + self.packet_data['layer4'] = [ inet.UDP(sport=source_port, dport=destination_port, **kwargs), inet.UDP(sport=source_port, dport=destination_port, **kwargs)] @@ -316,6 +384,10 @@ class XenaJSON(object): :param kwargs: Extra params per scapy usage :return: None """ + # import can't be performed at module level, because it conflicts with import + # of customized scapy version by T-Rex + import scapy.layers.inet as inet + self.packet_data['vlan'] = [ inet.Dot1Q(vlan=vlan_id, **kwargs), inet.Dot1Q(vlan=vlan_id, **kwargs)] diff --git a/tools/pkt_gen/xena/xena.py b/tools/pkt_gen/xena/xena.py index 19b44f0b..3adc8294 100755 --- a/tools/pkt_gen/xena/xena.py +++ b/tools/pkt_gen/xena/xena.py @@ -32,8 +32,6 @@ import xml.etree.ElementTree as ET from collections import OrderedDict from time import sleep -import scapy.layers.inet as inet - from conf import merge_spec from conf import settings from core.results.results_constants import ResultsConstants @@ -41,6 +39,7 @@ from tools.pkt_gen.trafficgen.trafficgen import ITrafficGenerator from tools.pkt_gen.xena.XenaDriver import ( aggregate_stats, line_percentage, + ModSet, XenaSocketDriver, XenaManager, ) @@ -149,6 +148,10 @@ class Xena(ITrafficGenerator): :param reverse: Swap source and destination info when building header :return: packet header in hex """ + # import can't be performed at module level, because it conflicts with import + # of customized scapy version by T-Rex + import scapy.layers.inet as inet + srcmac = self._params['traffic']['l2'][ 'srcmac'] if not reverse else self._params['traffic']['l2'][ 'dstmac'] @@ -274,10 +277,6 @@ class Xena(ITrafficGenerator): enable the pairs topology :return: None """ - # set duplex mode, this code is valid, pylint complaining with a - # warning that many have complained about online. - # pylint: disable=redefined-variable-type - try: if self._params['traffic']['bidir'] == "True": j_file = XenaJSONMesh() @@ -285,6 +284,9 @@ class Xena(ITrafficGenerator): j_file = XenaJSONBlocks() elif bonding_test: j_file = XenaJSONPairs() + else: # just default to mesh config + self._logger.error('Invalid traffic type defaulting to Mesh config') + j_file = XenaJSONMesh() j_file.set_chassis_info( settings.getValue('TRAFFICGEN_XENA_IP'), @@ -348,7 +350,7 @@ class Xena(ITrafficGenerator): id=self._params['traffic']['vlan']['cfi'], prio=self._params['traffic']['vlan']['priority']) j_file.add_header_segments( - flows=self._params['traffic']['multistream'], + flows=self._params['traffic']['multistream'] - 1, multistream_layer=self._params['traffic']['stream_type']) j_file.write_config(os.path.join( @@ -456,9 +458,17 @@ class Xena(ITrafficGenerator): port.micro_tpld_enable() if self._params['traffic']['multistream']: + if self._params['traffic']['stream_type'] == 'L2': + modobj = ModSet(mod_src_mac=True, mod_dst_mac=True) + elif self._params['traffic']['stream_type'] == 'L3': + modobj = ModSet(mod_src_ip=True, mod_dst_ip=True) + elif self._params['traffic']['stream_type'] == 'L4': + modobj = ModSet(mod_src_port=True, mod_dst_port=True) + else: + self._logger.error('Invalid segment for multistream. Using L2..') + modobj = ModSet(mod_src_mac=True, mod_dst_mac=True) stream.enable_multistream( - flows=self._params['traffic']['multistream'], - layer=self._params['traffic']['stream_type']) + flows=self._params['traffic']['multistream'], mod_class=modobj) s1_p0 = self.xmanager.ports[0].add_stream() setup_stream(s1_p0, self.xmanager.ports[0], 0) @@ -568,7 +578,7 @@ class Xena(ITrafficGenerator): self._xsocket.disconnect() self._xsocket = None - def send_burst_traffic(self, traffic=None, numpkts=100, duration=20): + def send_burst_traffic(self, traffic=None, duration=20): """Send a burst of traffic. See ITrafficGenerator for description @@ -579,7 +589,7 @@ class Xena(ITrafficGenerator): if traffic: self._params['traffic'] = merge_spec(self._params['traffic'], traffic) - self._start_traffic_api(numpkts) + self._start_traffic_api(traffic['burst_size']) return self._stop_api_traffic() def send_cont_traffic(self, traffic=None, duration=20): diff --git a/tools/report/report.py b/tools/report/report.py index b3f15c1b..5d05e7ad 100644 --- a/tools/report/report.py +++ b/tools/report/report.py @@ -137,7 +137,6 @@ def generate(testcase): 'tests': tests, } i = 0 - # pylint: disable=no-member for output_file in output_files: template = template_env.get_template(_TEMPLATE_FILES[i]) output_text = template.render(template_vars) diff --git a/tools/report/report_foot.rst b/tools/report/report_foot.rst index 5045e186..a49e3452 100644 --- a/tools/report/report_foot.rst +++ b/tools/report/report_foot.rst @@ -5,6 +5,7 @@ Rationale for decisions ======================= + The tests conducted do not have pass/fail/conditional-pass criteria. The test is simply conducted and the results are reported. @@ -12,6 +13,7 @@ is simply conducted and the results are reported. Conclusions and recommendations =============================== + The test results are stable. The vsperf CI jobs that were used to obtain the results can be found at https://wiki.opnfv.org/wiki/vsperf_results. @@ -20,11 +22,13 @@ General Glossary -------- + - NFV - Network Function Virtualization - Mbps - 1,000,000bps Document change procedures and history -------------------------------------- + =============================================== ================= ============= Document ID Author Date Modified =============================================== ================= ============= diff --git a/tools/report/report_rst.jinja b/tools/report/report_rst.jinja index eda0c01e..6b51807a 100644 --- a/tools/report/report_rst.jinja +++ b/tools/report/report_rst.jinja @@ -90,7 +90,9 @@ Testing Activities/Events ~~~~~~~~~~~~~~~~~~~~~~~~~ pidstat is used to collect the process statistics, as such some values such as %CPU and %USER maybe > 100% as the values are summed across multiple cores. For -more info on pidstat please see: http://linux.die.net/man/1/pidstat. +more info on pidstat please see: http://linux.die.net/man/1/pidstat. Please +note that vsperf recalculates the CPU consumption of a process by aggregating +the CPU usage of each thread. Known issues: Some reported metrics have the value "unkown". These values are marked unknown as they are not values retrieved from the external tester diff --git a/tools/systeminfo.py b/tools/systeminfo.py index f34bcce6..6020d0e2 100644 --- a/tools/systeminfo.py +++ b/tools/systeminfo.py @@ -191,7 +191,7 @@ def get_bin_version(binary, regex): return None versions = re.findall(regex, output) - if len(versions): + if versions: return versions[0] else: return None @@ -297,7 +297,7 @@ def get_version(app_name): if not '16' in release: tmp_ver[2] += line.rstrip('\n').split(' ')[2] - if len(tmp_ver[0]): + if tmp_ver[0]: app_version = '.'.join(tmp_ver) app_git_tag = get_git_tag(S.getValue('TOOLS')['dpdk_src']) elif app_name.lower().startswith('qemu'): diff --git a/tools/teststepstools.py b/tools/teststepstools.py index 33db8f79..db2d53e6 100644 --- a/tools/teststepstools.py +++ b/tools/teststepstools.py @@ -43,7 +43,7 @@ class TestStepsTools(object): return True @staticmethod - def validate_Assert(result, dummy_condition): + def validate_Assert(result, _dummy_condition): """ Validate evaluation of given `condition' """ return result @@ -56,7 +56,7 @@ class TestStepsTools(object): return eval(expression) @staticmethod - def validate_Eval(result, dummy_expression): + def validate_Eval(result, _dummy_expression): """ Validate result of python `expression' evaluation """ return result is not None @@ -76,7 +76,7 @@ class TestStepsTools(object): return True @staticmethod - def validate_Exec_Python(result, dummy_code): + def validate_Exec_Python(result, _dummy_code): """ Validate result of python `code' execution """ return result @@ -99,7 +99,7 @@ class TestStepsTools(object): return output @staticmethod - def validate_Exec_Shell(result, dummy_command, dummy_regex=None): + def validate_Exec_Shell(result, _dummy_command, _dummy_regex=None): """ validate result of shell `command' execution """ return result is not None @@ -115,7 +115,7 @@ class TestStepsTools(object): return None @staticmethod - def validate_Exec_Shell_Background(result, dummy_command, dummy_regex=None): + def validate_Exec_Shell_Background(result, _dummy_command, _dummy_regex=None): """ validate result of shell `command' execution on the background """ return result is not None diff --git a/tools/veth.py b/tools/veth.py index 6418d11a..6d7c9962 100644 --- a/tools/veth.py +++ b/tools/veth.py @@ -84,8 +84,7 @@ def del_veth_port(port, peer_port): port, peer_port), False) -# pylint: disable=unused-argument -def validate_add_veth_port(result, port, peer_port): +def validate_add_veth_port(_result, port, peer_port): """ Validation function for integration testcases """ @@ -93,7 +92,7 @@ def validate_add_veth_port(result, port, peer_port): return all([port in devs, peer_port in devs]) -def validate_bring_up_eth_port(result, eth_port, namespace=None): +def validate_bring_up_eth_port(_result, eth_port, namespace=None): """ Validation function for integration testcases """ @@ -110,7 +109,7 @@ def validate_bring_up_eth_port(result, eth_port, namespace=None): return True -def validate_del_veth_port(result, port, peer_port): +def validate_del_veth_port(_result, port, peer_port): """ Validation function for integration testcases """ |