diff options
Diffstat (limited to 'yardstick/network_services/traffic_profile')
7 files changed, 656 insertions, 68 deletions
diff --git a/yardstick/network_services/traffic_profile/__init__.py b/yardstick/network_services/traffic_profile/__init__.py index 72a61b6b4..85b3d54a0 100644 --- a/yardstick/network_services/traffic_profile/__init__.py +++ b/yardstick/network_services/traffic_profile/__init__.py @@ -30,6 +30,8 @@ def register_modules(): 'yardstick.network_services.traffic_profile.rfc2544', 'yardstick.network_services.traffic_profile.pktgen', 'yardstick.network_services.traffic_profile.landslide_profile', + 'yardstick.network_services.traffic_profile.vpp_rfc2544', + 'yardstick.network_services.traffic_profile.sip', ] for module in modules: diff --git a/yardstick/network_services/traffic_profile/http_ixload.py b/yardstick/network_services/traffic_profile/http_ixload.py index b88aadff7..ec0762500 100644 --- a/yardstick/network_services/traffic_profile/http_ixload.py +++ b/yardstick/network_services/traffic_profile/http_ixload.py @@ -1,4 +1,4 @@ -# Copyright (c) 2016-2017 Intel Corporation +# Copyright (c) 2016-2019 Intel Corporation # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,14 +16,6 @@ import sys import os import logging import collections -import subprocess -try: - libs = subprocess.check_output( - 'python -c "import site; print(site.getsitepackages())"', shell=True) - - sys.path.extend(libs[1:-1].replace("'", "").split(',')) -except subprocess.CalledProcessError: - pass # ixload uses its own py2. So importing jsonutils fails. So adding below # workaround to support call from yardstick @@ -32,14 +24,26 @@ try: except ImportError: import json as jsonutils -from yardstick.common import exceptions #pylint: disable=wrong-import-position + +class ErrorClass(object): + + def __init__(self, *args, **kwargs): + if 'test' not in kwargs: + raise RuntimeError + + def __getattr__(self, item): + raise AttributeError + + +class InvalidRxfFile(Exception): + message = 'Loaded rxf file has unexpected format' + try: from IxLoad import IxLoad, StatCollectorUtils except ImportError: - IxLoad = exceptions.ErrorClass - StatCollectorUtils = exceptions.ErrorClass - + IxLoad = ErrorClass + StatCollectorUtils = ErrorClass LOG = logging.getLogger(__name__) CSV_FILEPATH_NAME = 'IxL_statResults.csv' @@ -205,7 +209,7 @@ class IXLOADHttpTest(object): ipAddress=address, gatewayAddress=gateway) except Exception: - raise exceptions.InvalidRxfFile + raise InvalidRxfFile def update_network_mac_address(self, net_traffic, mac): """Update MACaddress for net_traffic object @@ -233,7 +237,7 @@ class IXLOADHttpTest(object): "MacRange") mac_range.config(mac=mac) except Exception: - raise exceptions.InvalidRxfFile + raise InvalidRxfFile def update_network_param(self, net_traffic, param): """Update net_traffic by parameters specified in param""" @@ -301,7 +305,7 @@ class IXLOADHttpTest(object): ix_http_command = activity.agent.actionList[0] ix_http_command.config(pageObject=page_object) except Exception: - raise exceptions.InvalidRxfFile + raise InvalidRxfFile def update_user_count(self, net_traffic, user_count): """Update userObjectiveValue field in activity object in net_traffic @@ -318,7 +322,7 @@ class IXLOADHttpTest(object): activity = net_traffic.activityList[0] activity.config(userObjectiveValue=user_count) except Exception: - raise exceptions.InvalidRxfFile + raise InvalidRxfFile def start_http_test(self): self.ix_load = IxLoad() diff --git a/yardstick/network_services/traffic_profile/ixia_rfc2544.py b/yardstick/network_services/traffic_profile/ixia_rfc2544.py index 35038891b..ca45b500d 100644 --- a/yardstick/network_services/traffic_profile/ixia_rfc2544.py +++ b/yardstick/network_services/traffic_profile/ixia_rfc2544.py @@ -1,4 +1,4 @@ -# Copyright (c) 2016-2017 Intel Corporation +# Copyright (c) 2016-2019 Intel Corporation # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -28,7 +28,6 @@ class IXIARFC2544Profile(trex_traffic_profile.TrexProfile): UPLINK = 'uplink' DOWNLINK = 'downlink' DROP_PERCENT_ROUND = 6 - RATE_ROUND = 5 STATUS_SUCCESS = "Success" STATUS_FAIL = "Failure" @@ -36,6 +35,7 @@ class IXIARFC2544Profile(trex_traffic_profile.TrexProfile): super(IXIARFC2544Profile, self).__init__(yaml_data) self.rate = self.config.frame_rate self.rate_unit = self.config.rate_unit + self.iteration = 0 self.full_profile = {} def _get_ip_and_mask(self, ip_range): @@ -146,12 +146,16 @@ class IXIARFC2544Profile(trex_traffic_profile.TrexProfile): return result - def _ixia_traffic_generate(self, traffic, ixia_obj): + def _ixia_traffic_generate(self, traffic, ixia_obj, traffic_gen): ixia_obj.update_frame(traffic, self.config.duration) ixia_obj.update_ip_packet(traffic) ixia_obj.update_l4(traffic) + self._update_traffic_tracking_options(traffic_gen) ixia_obj.start_traffic() + def _update_traffic_tracking_options(self, traffic_gen): + traffic_gen.update_tracking_options() + def update_traffic_profile(self, traffic_generator): def port_generator(): for vld_id, intfs in sorted(traffic_generator.networks.items()): @@ -176,25 +180,34 @@ class IXIARFC2544Profile(trex_traffic_profile.TrexProfile): self.max_rate = self.rate self.min_rate = 0.0 else: - self.rate = round(float(self.max_rate + self.min_rate) / 2.0, - self.RATE_ROUND) + self.rate = self._get_next_rate() + self.iteration = traffic_generator.rfc_helper.iteration.value traffic = self._get_ixia_traffic_profile(self.full_profile, mac) - self._ixia_traffic_generate(traffic, ixia_obj) + self._ixia_traffic_generate(traffic, ixia_obj, traffic_generator) return first_run + # pylint: disable=unused-argument def get_drop_percentage(self, samples, tol_min, tolerance, precision, - first_run=False): + resolution, first_run=False, tc_rfc2544_opts=None): completed = False - drop_percent = 100 + drop_percent = 100.0 num_ifaces = len(samples) duration = self.config.duration in_packets_sum = sum( - [samples[iface]['in_packets'] for iface in samples]) + [samples[iface]['InPackets'] for iface in samples]) out_packets_sum = sum( - [samples[iface]['out_packets'] for iface in samples]) + [samples[iface]['OutPackets'] for iface in samples]) + in_bytes_sum = sum( + [samples[iface]['InBytes'] for iface in samples]) + out_bytes_sum = sum( + [samples[iface]['OutBytes'] for iface in samples]) rx_throughput = round(float(in_packets_sum) / duration, 3) tx_throughput = round(float(out_packets_sum) / duration, 3) + # Rx throughput in Bps + rx_throughput_bps = round(float(in_bytes_sum) / duration, 3) + # Tx throughput in Bps + tx_throughput_bps = round(float(out_bytes_sum) / duration, 3) packet_drop = abs(out_packets_sum - in_packets_sum) try: @@ -217,19 +230,23 @@ class IXIARFC2544Profile(trex_traffic_profile.TrexProfile): else: completed = True + last_rate = self.rate + next_rate = self._get_next_rate() + if abs(next_rate - self.rate) < resolution: + LOG.debug("rate=%s, next_rate=%s, resolution=%s", self.rate, + next_rate, resolution) + # stop test if the difference between the rate transmission + # in two iterations is smaller than the value of the resolution + completed = True + LOG.debug("tolerance=%s, tolerance_precision=%s drop_percent=%s " "completed=%s", tolerance, precision, drop_percent, completed) - latency_ns_avg = float( - sum([samples[iface]['Store-Forward_Avg_latency_ns'] - for iface in samples])) / num_ifaces - latency_ns_min = float( - sum([samples[iface]['Store-Forward_Min_latency_ns'] - for iface in samples])) / num_ifaces - latency_ns_max = float( - sum([samples[iface]['Store-Forward_Max_latency_ns'] - for iface in samples])) / num_ifaces + latency_ns_avg = float(sum( + [samples[iface]['LatencyAvg'] for iface in samples])) / num_ifaces + latency_ns_min = min([samples[iface]['LatencyMin'] for iface in samples]) + latency_ns_max = max([samples[iface]['LatencyMax'] for iface in samples]) samples['Status'] = self.STATUS_FAIL if round(drop_percent, precision) <= tolerance: @@ -237,10 +254,15 @@ class IXIARFC2544Profile(trex_traffic_profile.TrexProfile): samples['TxThroughput'] = tx_throughput samples['RxThroughput'] = rx_throughput + samples['TxThroughputBps'] = tx_throughput_bps + samples['RxThroughputBps'] = rx_throughput_bps samples['DropPercentage'] = drop_percent - samples['latency_ns_avg'] = latency_ns_avg - samples['latency_ns_min'] = latency_ns_min - samples['latency_ns_max'] = latency_ns_max + samples['LatencyAvg'] = latency_ns_avg + samples['LatencyMin'] = latency_ns_min + samples['LatencyMax'] = latency_ns_max + samples['Rate'] = last_rate + samples['PktSize'] = self._get_framesize() + samples['Iteration'] = self.iteration return completed, samples @@ -264,12 +286,140 @@ class IXIARFC2544PppoeScenarioProfile(IXIARFC2544Profile): self.full_profile.update({downlink: self.params[downlink]}) def update_traffic_profile(self, traffic_generator): + + networks = collections.OrderedDict() + + # Sort network interfaces pairs + for i in range(len(traffic_generator.networks)): + uplink = '_'.join([self.UPLINK, str(i)]) + downlink = '_'.join([self.DOWNLINK, str(i)]) + if uplink in traffic_generator.networks: + networks[uplink] = traffic_generator.networks[uplink] + if downlink in traffic_generator.networks: + networks[downlink] = traffic_generator.networks[downlink] + def port_generator(): - for vld_id, intfs in sorted(traffic_generator.networks.items()): - if not vld_id.startswith((self.UPLINK, self.DOWNLINK)): - continue + for intfs in networks.values(): for intf in intfs: yield traffic_generator.vnfd_helper.port_num(intf) self._get_flow_groups_params() self.ports = [port for port in port_generator()] + + def _get_prio_flows_drop_percentage(self, stats): + drop_percent = 100 + for prio_id in stats: + prio_flow = stats[prio_id] + sum_packet_drop = abs(prio_flow['OutPackets'] - prio_flow['InPackets']) + try: + drop_percent = round( + (sum_packet_drop / float(prio_flow['OutPackets'])) * 100, + self.DROP_PERCENT_ROUND) + except ZeroDivisionError: + LOG.info('No traffic is flowing') + prio_flow['DropPercentage'] = drop_percent + return stats + + def _get_summary_pppoe_subs_counters(self, samples): + result = {} + keys = ['SessionsUp', + 'SessionsDown', + 'SessionsNotStarted', + 'SessionsTotal'] + for key in keys: + result[key] = \ + sum([samples[port][key] for port in samples + if key in samples[port]]) + return result + + def get_drop_percentage(self, samples, tol_min, tolerance, precision, + resolution, first_run=False, tc_rfc2544_opts=None): + completed = False + sum_drop_percent = 100 + num_ifaces = len(samples) + duration = self.config.duration + last_rate = self.rate + priority_stats = samples.pop('priority_stats') + priority_stats = self._get_prio_flows_drop_percentage(priority_stats) + summary_subs_stats = self._get_summary_pppoe_subs_counters(samples) + in_packets_sum = sum( + [samples[iface]['InPackets'] for iface in samples]) + out_packets_sum = sum( + [samples[iface]['OutPackets'] for iface in samples]) + in_bytes_sum = sum( + [samples[iface]['InBytes'] for iface in samples]) + out_bytes_sum = sum( + [samples[iface]['OutBytes'] for iface in samples]) + rx_throughput = round(float(in_packets_sum) / duration, 3) + tx_throughput = round(float(out_packets_sum) / duration, 3) + # Rx throughput in Bps + rx_throughput_bps = round(float(in_bytes_sum) / duration, 3) + # Tx throughput in Bps + tx_throughput_bps = round(float(out_bytes_sum) / duration, 3) + sum_packet_drop = abs(out_packets_sum - in_packets_sum) + + try: + sum_drop_percent = round( + (sum_packet_drop / float(out_packets_sum)) * 100, + self.DROP_PERCENT_ROUND) + except ZeroDivisionError: + LOG.info('No traffic is flowing') + + latency_ns_avg = float(sum( + [samples[iface]['LatencyAvg'] for iface in samples])) / num_ifaces + latency_ns_min = min([samples[iface]['LatencyMin'] for iface in samples]) + latency_ns_max = max([samples[iface]['LatencyMax'] for iface in samples]) + + samples['TxThroughput'] = tx_throughput + samples['RxThroughput'] = rx_throughput + samples['TxThroughputBps'] = tx_throughput_bps + samples['RxThroughputBps'] = rx_throughput_bps + samples['DropPercentage'] = sum_drop_percent + samples['LatencyAvg'] = latency_ns_avg + samples['LatencyMin'] = latency_ns_min + samples['LatencyMax'] = latency_ns_max + samples['Priority'] = priority_stats + samples['Rate'] = last_rate + samples['PktSize'] = self._get_framesize() + samples['Iteration'] = self.iteration + samples.update(summary_subs_stats) + + if tc_rfc2544_opts: + priority = tc_rfc2544_opts.get('priority') + if priority: + drop_percent = samples['Priority'][priority]['DropPercentage'] + else: + drop_percent = sum_drop_percent + else: + drop_percent = sum_drop_percent + + if first_run: + completed = True if drop_percent <= tolerance else False + if (first_run and + self.rate_unit == tp_base.TrafficProfileConfig.RATE_FPS): + self.rate = float(out_packets_sum) / duration / num_ifaces + + if drop_percent > tolerance: + self.max_rate = self.rate + elif drop_percent < tol_min: + self.min_rate = self.rate + else: + completed = True + + next_rate = self._get_next_rate() + if abs(next_rate - self.rate) < resolution: + LOG.debug("rate=%s, next_rate=%s, resolution=%s", self.rate, + next_rate, resolution) + # stop test if the difference between the rate transmission + # in two iterations is smaller than the value of the resolution + completed = True + + LOG.debug("tolerance=%s, tolerance_precision=%s drop_percent=%s " + "completed=%s", tolerance, precision, drop_percent, + completed) + + samples['Status'] = self.STATUS_FAIL + if round(drop_percent, precision) <= tolerance: + samples['Status'] = self.STATUS_SUCCESS + + return completed, samples diff --git a/yardstick/network_services/traffic_profile/rfc2544.py b/yardstick/network_services/traffic_profile/rfc2544.py index e33c437c9..aaa491b75 100644 --- a/yardstick/network_services/traffic_profile/rfc2544.py +++ b/yardstick/network_services/traffic_profile/rfc2544.py @@ -23,7 +23,7 @@ from yardstick.common import constants from yardstick.network_services.traffic_profile import trex_traffic_profile -LOGGING = logging.getLogger(__name__) +LOG = logging.getLogger(__name__) SRC_PORT = 'sport' DST_PORT = 'dport' @@ -72,14 +72,16 @@ class RFC2544Profile(trex_traffic_profile.TrexProfile): """TRex RFC2544 traffic profile""" TOLERANCE_LIMIT = 0.01 + STATUS_SUCCESS = "Success" + STATUS_FAIL = "Failure" def __init__(self, traffic_generator): super(RFC2544Profile, self).__init__(traffic_generator) self.generator = None + self.iteration = 0 self.rate = self.config.frame_rate self.max_rate = self.config.frame_rate self.min_rate = 0 - self.drop_percent_max = 0 def register_generator(self, generator): self.generator = generator @@ -126,6 +128,7 @@ class RFC2544Profile(trex_traffic_profile.TrexProfile): self.generator.client.start(ports=ports, duration=self.config.duration, force=True) + self.iteration = self.generator.rfc2544_helper.iteration.value return ports, port_pg_id def _create_profile(self, profile_data, rate, port_pg_id, enable_latency): @@ -142,7 +145,7 @@ class RFC2544Profile(trex_traffic_profile.TrexProfile): return trex_stl_streams.STLProfile(streams) def _create_imix_data(self, imix, - weight_mode=constants.DISTRIBUTION_IN_PACKETS): + weight_mode=constants.DISTRIBUTION_IN_BYTES): """Generate the IMIX distribution for a STL profile The input information is the framesize dictionary in a test case @@ -192,13 +195,13 @@ class RFC2544Profile(trex_traffic_profile.TrexProfile): imix_dip = {size: float(weight) / weight_normalize for size, weight in imix_count.items()} - if weight_mode == constants.DISTRIBUTION_IN_BYTES: + if weight_mode == constants.DISTRIBUTION_IN_PACKETS: return imix_dip byte_total = sum([int(size) * weight - for size, weight in imix_dip.items()]) - return {size: (int(size) * weight * 100) / byte_total - for size, weight in imix_dip.items()} + for size, weight in imix_count.items()]) + return {size: float(int(size) * weight * 100) / byte_total + for size, weight in imix_count.items()} def _create_vm(self, packet_definition): """Create the STL Raw instructions""" @@ -271,17 +274,24 @@ class RFC2544Profile(trex_traffic_profile.TrexProfile): return streams def get_drop_percentage(self, samples, tol_low, tol_high, - correlated_traffic): + correlated_traffic, resolution): # pylint: disable=unused-argument """Calculate the drop percentage and run the traffic""" completed = False + status = self.STATUS_FAIL out_pkt_end = sum(port['out_packets'] for port in samples[-1].values()) in_pkt_end = sum(port['in_packets'] for port in samples[-1].values()) out_pkt_ini = sum(port['out_packets'] for port in samples[0].values()) in_pkt_ini = sum(port['in_packets'] for port in samples[0].values()) + in_bytes_ini = sum(port['in_bytes'] for port in samples[0].values()) + out_bytes_ini = sum(port['out_bytes'] for port in samples[0].values()) + in_bytes_end = sum(port['in_bytes'] for port in samples[-1].values()) + out_bytes_end = sum(port['out_bytes'] for port in samples[-1].values()) time_diff = (list(samples[-1].values())[0]['timestamp'] - list(samples[0].values())[0]['timestamp']).total_seconds() out_packets = out_pkt_end - out_pkt_ini in_packets = in_pkt_end - in_pkt_ini + out_bytes = out_bytes_end - out_bytes_ini + in_bytes = in_bytes_end - in_bytes_ini tx_rate_fps = float(out_packets) / time_diff rx_rate_fps = float(in_packets) / time_diff drop_percent = 100.0 @@ -298,26 +308,55 @@ class RFC2544Profile(trex_traffic_profile.TrexProfile): elif drop_percent < tol_low: self.min_rate = self.rate else: + status = self.STATUS_SUCCESS completed = True last_rate = self.rate - self.rate = round(float(self.max_rate + self.min_rate) / 2.0, 5) - - throughput = rx_rate_fps * 2 if correlated_traffic else rx_rate_fps - - if drop_percent > self.drop_percent_max: - self.drop_percent_max = drop_percent - - latency = {port_num: value['latency'] - for port_num, value in samples[-1].items()} + self.rate = self._get_next_rate() + if abs(last_rate - self.rate) < resolution: + # stop test if the difference between the rate transmission + # in two iterations is smaller than the value of the resolution + completed = True + LOG.debug("rate=%s, next_rate=%s, resolution=%s, completed=%s", + last_rate, self.rate, resolution, completed) + + ports = samples[-1].keys() + num_ports = len(ports) + + output = {} + for port in ports: + output[port] = {} + first = samples[0][port] + last = samples[-1][port] + output[port]['InPackets'] = last['in_packets'] - first['in_packets'] + output[port]['OutPackets'] = last['out_packets'] - first['out_packets'] + output[port]['InBytes'] = last['in_bytes'] - first['in_bytes'] + output[port]['OutBytes'] = last['out_bytes'] - first['out_bytes'] + if self.config.enable_latency: + output[port]['LatencyAvg'] = float(sum( + [last['latency'][id]['average'] for id in + last['latency']]) * 1000) / len(last['latency']) + output[port]['LatencyMin'] = min( + [last['latency'][id]['total_min'] for id in + last['latency']]) * 1000 + output[port]['LatencyMax'] = max( + [last['latency'][id]['total_max'] for id in + last['latency']]) * 1000 + + output['TxThroughput'] = tx_rate_fps + output['RxThroughput'] = rx_rate_fps + output['RxThroughputBps'] = round(float(in_bytes) / time_diff, 3) + output['TxThroughputBps'] = round(float(out_bytes) / time_diff, 3) + output['DropPercentage'] = drop_percent + output['Rate'] = last_rate + output['PktSize'] = self._get_framesize() + output['Iteration'] = self.iteration + output['Status'] = status + + if self.config.enable_latency: + output['LatencyAvg'] = float( + sum([output[port]['LatencyAvg'] for port in ports])) / num_ports + output['LatencyMin'] = min([output[port]['LatencyMin'] for port in ports]) + output['LatencyMax'] = max([output[port]['LatencyMax'] for port in ports]) - output = { - 'TxThroughput': tx_rate_fps, - 'RxThroughput': rx_rate_fps, - 'CurrentDropPercentage': drop_percent, - 'Throughput': throughput, - 'DropPercentage': self.drop_percent_max, - 'Rate': last_rate, - 'Latency': latency - } return completed, output diff --git a/yardstick/network_services/traffic_profile/sip.py b/yardstick/network_services/traffic_profile/sip.py new file mode 100644 index 000000000..d18574090 --- /dev/null +++ b/yardstick/network_services/traffic_profile/sip.py @@ -0,0 +1,32 @@ +# Copyright (c) 2019 Viosoft Corporation +# +# 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. + +from yardstick.network_services.traffic_profile import base + + +class SipProfile(base.TrafficProfile): + """ Sipp Traffic profile """ + + def __init__(self, yaml_data): + super(SipProfile, self).__init__(yaml_data) + self.generator = None + + def execute_traffic(self, traffic_generator=None): + if traffic_generator is not None and self.generator is None: + self.generator = traffic_generator + + def is_ended(self): + if self.generator is not None: + return self.generator.is_ended() + return False diff --git a/yardstick/network_services/traffic_profile/trex_traffic_profile.py b/yardstick/network_services/traffic_profile/trex_traffic_profile.py index ed0355fa5..cf538d488 100644 --- a/yardstick/network_services/traffic_profile/trex_traffic_profile.py +++ b/yardstick/network_services/traffic_profile/trex_traffic_profile.py @@ -52,6 +52,7 @@ class TrexProfile(base.TrafficProfile): IPv6: ('ip6_packet', Pkt.IPv6), UDP: ('udp_packet', Pkt.UDP), } + RATE_ROUND = 5 def _general_single_action_partial(self, protocol): def f(field): @@ -186,6 +187,8 @@ class TrexProfile(base.TrafficProfile): self.qinq = False self.vm_flow_vars = [] self.packets = [] + self.max_rate = 0 + self.min_rate = 0 self._map_proto_actions = { # the tuple is (single value function, range value function, if the values should be @@ -337,6 +340,25 @@ class TrexProfile(base.TrafficProfile): if 'dstport' in outer_l4: self._set_proto_addr(UDP, DST_PORT, outer_l4['dstport'], outer_l4['count']) + def _get_next_rate(self): + rate = round(float(self.max_rate + self.min_rate)/2.0, self.RATE_ROUND) + return rate + + def _get_framesize(self): + framesizes = [] + for traffickey, value in self.params.items(): + if not traffickey.startswith((self.UPLINK, self.DOWNLINK)): + continue + for _, data in value.items(): + framesize = data['outer_l2']['framesize'] + for size in (s for s, w in framesize.items() if int(w) != 0): + framesizes.append(size) + if len(set(framesizes)) == 0: + return '' + elif len(set(framesizes)) == 1: + return framesizes[0] + return 'IMIX' + @classmethod def _count_ip(cls, start_ip, end_ip): start = ipaddress.ip_address(six.u(start_ip)) diff --git a/yardstick/network_services/traffic_profile/vpp_rfc2544.py b/yardstick/network_services/traffic_profile/vpp_rfc2544.py new file mode 100644 index 000000000..412e4e69a --- /dev/null +++ b/yardstick/network_services/traffic_profile/vpp_rfc2544.py @@ -0,0 +1,339 @@ +# Copyright (c) 2019 Viosoft Corporation +# +# 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 datetime +import ipaddress +import logging +import random +import string + +from trex_stl_lib import api as Pkt +from trex_stl_lib import trex_stl_client +from trex_stl_lib import trex_stl_packet_builder_scapy +from trex_stl_lib import trex_stl_streams + +from yardstick.common import constants +from yardstick.network_services.helpers.vpp_helpers.multiple_loss_ratio_search import \ + MultipleLossRatioSearch +from yardstick.network_services.traffic_profile.rfc2544 import RFC2544Profile, \ + PortPgIDMap +from yardstick.network_services.traffic_profile.trex_traffic_profile import IP, \ + DST + +LOGGING = logging.getLogger(__name__) + + +class VppRFC2544Profile(RFC2544Profile): + + def __init__(self, traffic_generator): + super(VppRFC2544Profile, self).__init__(traffic_generator) + + tp_cfg = traffic_generator["traffic_profile"] + self.number_of_intermediate_phases = tp_cfg.get("intermediate_phases", + 2) + + self.duration = self.config.duration + self.precision = self.config.test_precision + self.lower_bound = self.config.lower_bound + self.upper_bound = self.config.upper_bound + self.step_interval = self.config.step_interval + self.enable_latency = self.config.enable_latency + + self.pkt_size = None + self.flow = None + + self.tolerance_low = 0 + self.tolerance_high = 0 + + self.queue = None + self.port_pg_id = None + + self.current_lower = self.lower_bound + self.current_upper = self.upper_bound + + self.ports = [] + self.profiles = {} + + @property + def delta(self): + return self.current_upper - self.current_lower + + @property + def mid_point(self): + return (self.current_lower + self.current_upper) / 2 + + @staticmethod + def calculate_frame_size(imix): + if not imix: + return 64, 100 + + imix_count = {size.upper().replace('B', ''): int(weight) + for size, weight in imix.items()} + imix_sum = sum(imix_count.values()) + if imix_sum <= 0: + return 64, 100 + packets_total = sum([int(size) * weight + for size, weight in imix_count.items()]) + return packets_total / imix_sum, imix_sum + + @staticmethod + def _gen_payload(length): + payload = "" + for _ in range(length): + payload += random.choice(string.ascii_letters) + + return payload + + def bounds_iterator(self, logger=None): + self.current_lower = self.lower_bound + self.current_upper = self.upper_bound + + test_value = self.current_upper + while abs(self.delta) >= self.precision: + if logger: + logger.debug("New interval [%s, %s), precision: %d", + self.current_lower, + self.current_upper, self.step_interval) + logger.info("Testing with value %s", test_value) + + yield test_value + test_value = self.mid_point + + def register_generator(self, generator): + super(VppRFC2544Profile, self).register_generator(generator) + self.init_traffic_params(generator) + + def init_queue(self, queue): + self.queue = queue + self.queue.cancel_join_thread() + + def init_traffic_params(self, generator): + if generator.rfc2544_helper.latency: + self.enable_latency = True + self.tolerance_low = generator.rfc2544_helper.tolerance_low + self.tolerance_high = generator.rfc2544_helper.tolerance_high + self.max_rate = generator.scenario_helper.all_options.get('vpp_config', + {}).get( + 'max_rate', self.rate) + + def create_profile(self, profile_data, current_port): + streams = [] + for packet_name in profile_data: + imix = (profile_data[packet_name]. + get('outer_l2', {}).get('framesize')) + self.pkt_size, imix_sum = self.calculate_frame_size(imix) + self._create_vm(profile_data[packet_name]) + if self.max_rate > 100: + imix_data = self._create_imix_data(imix, + constants.DISTRIBUTION_IN_PACKETS) + else: + imix_data = self._create_imix_data(imix) + _streams = self._create_single_stream(current_port, imix_data, + imix_sum) + streams.extend(_streams) + return trex_stl_streams.STLProfile(streams) + + def _set_outer_l3v4_fields(self, outer_l3v4): + """ setup outer l3v4 fields from traffic profile """ + ip_params = {} + if 'proto' in outer_l3v4: + ip_params['proto'] = outer_l3v4['proto'] + self._set_proto_fields(IP, **ip_params) + + self.flow = int(outer_l3v4['count']) + src_start_ip, _ = outer_l3v4['srcip4'].split('-') + dst_start_ip, _ = outer_l3v4['dstip4'].split('-') + + self.ip_packet = Pkt.IP(src=src_start_ip, + dst=dst_start_ip, + proto=outer_l3v4['proto']) + if self.flow > 1: + dst_start_int = int(ipaddress.ip_address(str(dst_start_ip))) + dst_end_ip_new = ipaddress.ip_address( + dst_start_int + self.flow - 1) + # self._set_proto_addr(IP, SRC, outer_l3v4['srcip4'], outer_l3v4['count']) + self._set_proto_addr(IP, DST, + "{start_ip}-{end_ip}".format( + start_ip=dst_start_ip, + end_ip=str(dst_end_ip_new)), + self.flow) + + def _create_single_packet(self, size=64): + ether_packet = self.ether_packet + ip_packet = self.ip6_packet if self.ip6_packet else self.ip_packet + base_pkt = ether_packet / ip_packet + payload_len = max(0, size - len(base_pkt) - 4) + packet = trex_stl_packet_builder_scapy.STLPktBuilder( + pkt=base_pkt / self._gen_payload(payload_len), + vm=self.trex_vm) + packet_lat = trex_stl_packet_builder_scapy.STLPktBuilder( + pkt=base_pkt / self._gen_payload(payload_len)) + + return packet, packet_lat + + def _create_single_stream(self, current_port, imix_data, imix_sum, + isg=0.0): + streams = [] + for size, weight in ((int(size), float(weight)) for (size, weight) + in imix_data.items() if float(weight) > 0): + if current_port == 1: + isg += 10.0 + if self.max_rate > 100: + mode = trex_stl_streams.STLTXCont( + pps=int(weight * imix_sum / 100)) + mode_lat = mode + else: + mode = trex_stl_streams.STLTXCont( + percentage=weight * self.max_rate / 100) + mode_lat = trex_stl_streams.STLTXCont(pps=9000) + + packet, packet_lat = self._create_single_packet(size) + streams.append( + trex_stl_client.STLStream(isg=isg, packet=packet, mode=mode)) + if self.enable_latency: + pg_id = self.port_pg_id.increase_pg_id(current_port) + stl_flow = trex_stl_streams.STLFlowLatencyStats(pg_id=pg_id) + stream_lat = trex_stl_client.STLStream(isg=isg, + packet=packet_lat, + mode=mode_lat, + flow_stats=stl_flow) + streams.append(stream_lat) + return streams + + def execute_traffic(self, traffic_generator=None): + if traffic_generator is not None and self.generator is None: + self.generator = traffic_generator + + self.ports = [] + self.profiles = {} + self.port_pg_id = PortPgIDMap() + for vld_id, intfs in sorted(self.generator.networks.items()): + profile_data = self.params.get(vld_id) + if not profile_data: + continue + if (vld_id.startswith(self.DOWNLINK) and + self.generator.rfc2544_helper.correlated_traffic): + continue + for intf in intfs: + current_port = int(self.generator.port_num(intf)) + self.port_pg_id.add_port(current_port) + profile = self.create_profile(profile_data, current_port) + self.generator.client.add_streams(profile, + ports=[current_port]) + + self.ports.append(current_port) + self.profiles[current_port] = profile + + timeout = self.generator.scenario_helper.scenario_cfg["runner"][ + "duration"] + test_data = { + "test_duration": timeout, + "test_precision": self.precision, + "tolerated_loss": self.tolerance_high, + "duration": self.duration, + "packet_size": self.pkt_size, + "flow": self.flow + } + + if self.max_rate > 100: + self.binary_search_with_optimized(self.generator, self.duration, + timeout, test_data) + else: + self.binary_search(self.generator, self.duration, + self.tolerance_high, test_data) + + def binary_search_with_optimized(self, traffic_generator, duration, + timeout, test_data): + self.queue.cancel_join_thread() + algorithm = MultipleLossRatioSearch( + measurer=traffic_generator, latency=self.enable_latency, + pkt_size=self.pkt_size, + final_trial_duration=duration, + final_relative_width=self.step_interval / 100, + number_of_intermediate_phases=self.number_of_intermediate_phases, + initial_trial_duration=1, + timeout=timeout) + algorithm.init_generator(self.ports, self.port_pg_id, self.profiles, + test_data, self.queue) + return algorithm.narrow_down_ndr_and_pdr(10000, self.max_rate, + self.tolerance_high) + + def binary_search(self, traffic_generator, duration, tolerance_value, + test_data): + theor_max_thruput = 0 + result_samples = {} + + for test_value in self.bounds_iterator(LOGGING): + stats = traffic_generator.send_traffic_on_tg(self.ports, + self.port_pg_id, + duration, + str( + test_value / self.max_rate / 2), + latency=self.enable_latency) + traffic_generator.client.reset(ports=self.ports) + traffic_generator.client.clear_stats(ports=self.ports) + traffic_generator.client.remove_all_streams(ports=self.ports) + for port, profile in self.profiles.items(): + traffic_generator.client.add_streams(profile, ports=[port]) + + loss_ratio = (float(traffic_generator.loss) / float( + traffic_generator.sent)) * 100 + + samples = traffic_generator.generate_samples(stats, self.ports, + self.port_pg_id, + self.enable_latency) + samples.update(test_data) + LOGGING.info("Collect TG KPIs %s %s %s", datetime.datetime.now(), + test_value, samples) + self.queue.put(samples) + + if float(loss_ratio) > float(tolerance_value): + LOGGING.debug("Failure... Decreasing upper bound") + self.current_upper = test_value + else: + LOGGING.debug("Success! Increasing lower bound") + self.current_lower = test_value + + rate_total = float(traffic_generator.sent) / float(duration) + bandwidth_total = float(rate_total) * ( + float(self.pkt_size) + 20) * 8 / (10 ** 9) + + success_samples = {'Result_' + key: value for key, value in + samples.items()} + success_samples["Result_{}".format('PDR')] = { + "rate_total_pps": float(rate_total), + "bandwidth_total_Gbps": float(bandwidth_total), + "packet_loss_ratio": float(loss_ratio), + "packets_lost": int(traffic_generator.loss), + } + self.queue.put(success_samples) + + # Store Actual throughput for result samples + for intf in traffic_generator.vnfd_helper.interfaces: + name = intf["name"] + result_samples[name] = { + "Result_Actual_throughput": float( + success_samples["Result_{}".format(name)][ + "rx_throughput_bps"]), + } + + for intf in traffic_generator.vnfd_helper.interfaces: + name = intf["name"] + if theor_max_thruput < samples[name]["tx_throughput_bps"]: + theor_max_thruput = samples[name]['tx_throughput_bps'] + self.queue.put({'theor_max_throughput': theor_max_thruput}) + + result_samples["Result_theor_max_throughput"] = theor_max_thruput + self.queue.put(result_samples) + return result_samples |