diff options
Diffstat (limited to 'yardstick/network_services')
7 files changed, 316 insertions, 366 deletions
diff --git a/yardstick/network_services/traffic_profile/base.py b/yardstick/network_services/traffic_profile/base.py index 4c3b29754..f4b5b178c 100644 --- a/yardstick/network_services/traffic_profile/base.py +++ b/yardstick/network_services/traffic_profile/base.py @@ -21,16 +21,21 @@ class TrafficProfileConfig(object): This object will parse and validate the traffic profile information. """ + + DEFAULT_SCHEMA = 'nsb:traffic_profile:0.1' + DEFAULT_FRAME_RATE = 100 + DEFAULT_DURATION = 30 + def __init__(self, tp_config): - self.schema = tp_config.get('schema', 'nsb:traffic_profile:0.1') + self.schema = tp_config.get('schema', self.DEFAULT_SCHEMA) self.name = tp_config.get('name') self.description = tp_config.get('description') tprofile = tp_config['traffic_profile'] self.traffic_type = tprofile.get('traffic_type') - self.frame_rate = tprofile.get('frame_rate') + self.frame_rate = tprofile.get('frame_rate', self.DEFAULT_FRAME_RATE) self.test_precision = tprofile.get('test_precision') self.packet_sizes = tprofile.get('packet_sizes') - self.duration = tprofile.get('duration') + self.duration = tprofile.get('duration', self.DEFAULT_DURATION) self.lower_bound = tprofile.get('lower_bound') self.upper_bound = tprofile.get('upper_bound') self.step_interval = tprofile.get('step_interval') @@ -43,7 +48,6 @@ class TrafficProfile(object): """ UPLINK = "uplink" DOWNLINK = "downlink" - DEFAULT_DURATION = 30 @staticmethod def get(tp_config): diff --git a/yardstick/network_services/traffic_profile/ixia_rfc2544.py b/yardstick/network_services/traffic_profile/ixia_rfc2544.py index 73806f958..e105c2f55 100644 --- a/yardstick/network_services/traffic_profile/ixia_rfc2544.py +++ b/yardstick/network_services/traffic_profile/ixia_rfc2544.py @@ -95,7 +95,6 @@ class IXIARFC2544Profile(TrexProfile): if not profile_data: continue self.profile_data = profile_data - self.get_streams(self.profile_data) self.full_profile.update({vld_id: self.profile_data}) for intf in intfs: yield traffic_generator.vnfd_helper.port_num(intf) diff --git a/yardstick/network_services/traffic_profile/rfc2544.py b/yardstick/network_services/traffic_profile/rfc2544.py index 83020c85c..c24e2f65a 100644 --- a/yardstick/network_services/traffic_profile/rfc2544.py +++ b/yardstick/network_services/traffic_profile/rfc2544.py @@ -11,190 +11,288 @@ # 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. -""" RFC2544 Throughput implemenation """ -from __future__ import absolute_import -from __future__ import division import logging -from trex_stl_lib.trex_stl_client import STLStream -from trex_stl_lib.trex_stl_streams import STLFlowLatencyStats -from trex_stl_lib.trex_stl_streams import STLTXCont +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.network_services.traffic_profile import trex_traffic_profile -from yardstick.network_services.traffic_profile.trex_traffic_profile \ - import TrexProfile LOGGING = logging.getLogger(__name__) +SRC_PORT = 'sport' +DST_PORT = 'dport' + + +class PortPgIDMap(object): + """Port and pg_id mapping class + + "pg_id" is the identification STL library gives to each stream. In the + RFC2544Profile class, the traffic has a STLProfile per port, which contains + one or several streams, one per packet size defined in the IMIX test case + description. + + Example of port <-> pg_id map: + self._port_pg_id_map = { + 0: [1, 2, 3, 4], + 1: [5, 6, 7, 8] + } + """ + + def __init__(self): + self._pg_id = 0 + self._last_port = None + self._port_pg_id_map = {} + + def add_port(self, port): + self._last_port = port + self._port_pg_id_map[port] = [] + + def get_pg_ids(self, port): + return self._port_pg_id_map.get(port) + + def increase_pg_id(self, port=None): + port = self._last_port if not port else port + if port is None: + return + pg_id_list = self._port_pg_id_map.get(port) + if not pg_id_list: + self.add_port(port) + pg_id_list = self._port_pg_id_map[port] + self._pg_id += 1 + pg_id_list.append(self._pg_id) + return self._pg_id -class RFC2544Profile(TrexProfile): - """ This class handles rfc2544 implemenation. """ +class RFC2544Profile(trex_traffic_profile.TrexProfile): + """TRex RFC2544 traffic profile""" + + TOLERANCE_LIMIT = 0.05 def __init__(self, traffic_generator): super(RFC2544Profile, self).__init__(traffic_generator) self.generator = None - self.max_rate = None - self.min_rate = None - self.ports = None - self.rate = 100 - self.drop_percent_at_max_tx = None - self.throughput_max = None + 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 - def execute_traffic(self, traffic_generator=None): - """ Generate the stream and run traffic on the given ports """ + def stop_traffic(self, traffic_generator=None): + """"Stop traffic injection, reset counters and remove streams""" if traffic_generator is not None and self.generator is None: self.generator = traffic_generator - if self.ports is not None: - return + self.generator.client.stop() + self.generator.client.reset() + self.generator.client.remove_all_streams() + + def execute_traffic(self, traffic_generator=None): + """Generate the stream and run traffic on the given ports + + :param traffic_generator: (TrexTrafficGenRFC) traffic generator + :return ports: (list of int) indexes of ports + port_pg_id: (dict) port indexes and pg_id [1] map + [1] https://trex-tgn.cisco.com/trex/doc/cp_stl_docs/api/ + profile_code.html#stlstream-modes + """ + if traffic_generator is not None and self.generator is None: + self.generator = traffic_generator - self.ports = [] + port_pg_id = PortPgIDMap() + ports = [] for vld_id, intfs in sorted(self.generator.networks.items()): profile_data = self.params.get(vld_id) - # no profile for this port if not profile_data: continue - # correlated traffic doesn't use public traffic? - if vld_id.startswith(self.DOWNLINK) and \ - self.generator.rfc2544_helper.correlated_traffic: + if (vld_id.startswith(self.DOWNLINK) and + self.generator.rfc2544_helper.correlated_traffic): continue for intf in intfs: - port = self.generator.port_num(intf) - self.ports.append(port) - self.generator.client.add_streams(self.get_streams(profile_data), ports=port) - - self.max_rate = self.rate - self.min_rate = 0 - self.generator.client.start(ports=self.ports, mult=self.get_multiplier(), - duration=30, force=True) - self.drop_percent_at_max_tx = 0 - self.throughput_max = 0 - - def get_multiplier(self): - """ Get the rate at which next iteration to run """ - self.rate = round((self.max_rate + self.min_rate) / 2.0, 2) - multiplier = round(self.rate / self.pps, 2) - return str(multiplier) - - def get_drop_percentage(self, generator=None): - """ Calculate the drop percentage and run the traffic """ - if generator is None: - generator = self.generator - run_duration = self.generator.RUN_DURATION - samples = self.generator.generate_samples(self.ports) - - in_packets = sum([value['in_packets'] for value in samples.values()]) - out_packets = sum([value['out_packets'] for value in samples.values()]) - - packet_drop = abs(out_packets - in_packets) - drop_percent = 100.0 - try: - drop_percent = round((packet_drop / float(out_packets)) * 100, 5) - except ZeroDivisionError: - LOGGING.info('No traffic is flowing') + port_num = int(self.generator.port_num(intf)) + ports.append(port_num) + port_pg_id.add_port(port_num) + profile = self._create_profile(profile_data, + self.rate, port_pg_id) + self.generator.client.add_streams(profile, ports=[port_num]) + + self.generator.client.start(ports=ports, + duration=self.config.duration, + force=True) + return ports, port_pg_id + + def _create_profile(self, profile_data, rate, port_pg_id): + """Create a STL profile (list of streams) for a port""" + streams = [] + for packet_name in profile_data: + imix = (profile_data[packet_name]. + get('outer_l2', {}).get('framesize')) + imix_data = self._create_imix_data(imix) + self._create_vm(profile_data[packet_name]) + _streams = self._create_streams(imix_data, rate, port_pg_id) + streams.extend(_streams) + return trex_stl_streams.STLProfile(streams) + + def _create_imix_data(self, imix): + """Generate the IMIX distribution for a STL profile + + The input information is the framesize dictionary in a test case + traffic profile definition. E.g.: + downlink_0: + ipv4: + id: 2 + outer_l2: + framesize: + 64B: 10 + 128B: 20 + ... + + This function normalizes the sum of framesize weights to 100 and + returns a dictionary of frame sizes in bytes and weight in percentage. + E.g.: + imix_count = {64: 25, 128: 75} + + :param imix: (dict) IMIX size and weight + """ + imix_count = {} + if not imix: + return imix_count + + imix_count = {size.upper().replace('B', ''): int(weight) + for size, weight in imix.items()} + imix_sum = sum(imix_count.values()) + if imix_sum <= 0: + imix_count = {64: 100} + imix_sum = 100 + + weight_normalize = float(imix_sum) / 100 + return {size: float(weight) / weight_normalize + for size, weight in imix_count.items()} + + def _create_vm(self, packet_definition): + """Create the STL Raw instructions""" + self.ether_packet = Pkt.Ether() + self.ip_packet = Pkt.IP() + self.ip6_packet = None + self.udp_packet = Pkt.UDP() + self.udp[DST_PORT] = 'UDP.dport' + self.udp[SRC_PORT] = 'UDP.sport' + self.qinq = False + self.vm_flow_vars = [] + outer_l2 = packet_definition.get('outer_l2') + outer_l3v4 = packet_definition.get('outer_l3v4') + outer_l3v6 = packet_definition.get('outer_l3v6') + outer_l4 = packet_definition.get('outer_l4') + if outer_l2: + self._set_outer_l2_fields(outer_l2) + if outer_l3v4: + self._set_outer_l3v4_fields(outer_l3v4) + if outer_l3v6: + self._set_outer_l3v6_fields(outer_l3v6) + if outer_l4: + self._set_outer_l4_fields(outer_l4) + self.trex_vm = trex_stl_packet_builder_scapy.STLScVmRaw( + self.vm_flow_vars) + + def _create_single_packet(self, size=64): + size -= 4 + ether_packet = self.ether_packet + ip_packet = self.ip6_packet if self.ip6_packet else self.ip_packet + udp_packet = self.udp_packet + if self.qinq: + qinq_packet = self.qinq_packet + base_pkt = ether_packet / qinq_packet / ip_packet / udp_packet + else: + base_pkt = ether_packet / ip_packet / udp_packet + pad = max(0, size - len(base_pkt)) * 'x' + return trex_stl_packet_builder_scapy.STLPktBuilder( + pkt=base_pkt / pad, vm=self.trex_vm) + + def _create_streams(self, imix_data, rate, port_pg_id): + """Create a list of streams per packet size + + The STL TX mode speed of the generated streams will depend on the frame + weight and the frame rate. Both the frame weight and the total frame + rate are normalized to 100. The STL TX mode speed, defined in + percentage, is the combitation of both percentages. E.g.: + frame weight = 100 + rate = 90 + --> STLTXmode percentage = 10 (%) + + frame weight = 80 + rate = 50 + --> STLTXmode percentage = 40 (%) + + :param imix_data: (dict) IMIX size and weight + :param rate: (float) normalized [0..100] total weight + :param pg_id: (PortPgIDMap) port / pg_id (list) map + """ + streams = [] + for size, weight in ((int(size), float(weight)) for (size, weight) + in imix_data.items() if float(weight) > 0): + packet = self._create_single_packet(size) + pg_id = port_pg_id.increase_pg_id() + stl_flow = trex_stl_streams.STLFlowLatencyStats(pg_id=pg_id) + mode = trex_stl_streams.STLTXCont(percentage=weight * rate / 100) + streams.append(trex_stl_client.STLStream( + packet=packet, flow_stats=stl_flow, mode=mode)) + return streams + + def get_drop_percentage(self, samples, tol_low, tol_high, + correlated_traffic): + """Calculate the drop percentage and run the traffic""" + tx_rate_fps = 0 + rx_rate_fps = 0 + for sample in samples: + tx_rate_fps += sum( + port['tx_throughput_fps'] for port in sample.values()) + rx_rate_fps += sum( + port['rx_throughput_fps'] for port in sample.values()) + tx_rate_fps = round(float(tx_rate_fps) / len(samples), 2) + rx_rate_fps = round(float(rx_rate_fps) / len(samples), 2) # TODO(esm): RFC2544 doesn't tolerate packet loss, why do we? - tolerance_low = generator.rfc2544_helper.tolerance_low - tolerance_high = generator.rfc2544_helper.tolerance_high - - tx_rate = out_packets / run_duration - rx_rate = in_packets / run_duration - - throughput_max = self.throughput_max - drop_percent_at_max_tx = self.drop_percent_at_max_tx + out_packets = sum(port['out_packets'] for port in samples[-1].values()) + in_packets = sum(port['in_packets'] for port in samples[-1].values()) + drop_percent = 100.0 - if self.drop_percent_at_max_tx is None: - self.rate = tx_rate - self.first_run = False + # https://tools.ietf.org/html/rfc2544#section-26.3 + if out_packets: + drop_percent = round( + (float(abs(out_packets - in_packets)) / out_packets) * 100, 5) - if drop_percent > tolerance_high: - # TODO(esm): why don't we discard results that are out of tolerance? + tol_high = tol_high if tol_high > self.TOLERANCE_LIMIT else tol_high + tol_low = tol_low if tol_low > self.TOLERANCE_LIMIT else tol_low + if drop_percent > tol_high: self.max_rate = self.rate - if throughput_max == 0: - throughput_max = rx_rate - drop_percent_at_max_tx = drop_percent - - elif drop_percent >= tolerance_low: - # TODO(esm): why do we update the samples dict in this case - # and not update our tracking values? - throughput_max = rx_rate - drop_percent_at_max_tx = drop_percent - - elif drop_percent >= self.drop_percent_at_max_tx: - # TODO(esm): why don't we discard results that are out of tolerance? + elif drop_percent < tol_low: self.min_rate = self.rate - self.drop_percent_at_max_tx = drop_percent_at_max_tx = drop_percent - self.throughput_max = throughput_max = rx_rate + # else: + # NOTE(ralonsoh): the test should finish here + # pass + last_rate = self.rate + self.rate = round(float(self.max_rate + self.min_rate) / 2.0, 5) - else: - # TODO(esm): why don't we discard results that are out of tolerance? - self.min_rate = self.rate + throughput = rx_rate_fps * 2 if correlated_traffic else rx_rate_fps - generator.clear_client_stats(self.ports) - generator.start_client(self.ports, mult=self.get_multiplier(), - duration=run_duration, force=True) + if drop_percent > self.drop_percent_max: + self.drop_percent_max = drop_percent - # if correlated traffic update the Throughput - if generator.rfc2544_helper.correlated_traffic: - throughput_max *= 2 + latency = {port_num: value['latency'] + for port_num, value in samples[-1].items()} - samples.update({ - 'TxThroughput': tx_rate, - 'RxThroughput': rx_rate, + output = { + 'TxThroughput': tx_rate_fps, + 'RxThroughput': rx_rate_fps, 'CurrentDropPercentage': drop_percent, - 'Throughput': throughput_max, - 'DropPercentage': drop_percent_at_max_tx, - }) - - return samples - - def execute_latency(self, generator=None, samples=None): - if generator is not None and self.generator is None: - self.generator = generator - - if samples is None: - samples = self.generator.generate_samples() - - self.pps, multiplier = self.calculate_pps(samples) - self.ports = [] - self.pg_id = self.params['traffic_profile'].get('pg_id', 1) - for vld_id, intfs in sorted(self.generator.networks.items()): - profile_data = self.params.get(vld_id) - if not profile_data: - continue - # correlated traffic doesn't use public traffic? - if vld_id.startswith(self.DOWNLINK) and \ - self.generator.rfc2544_helper.correlated_traffic: - continue - for intf in intfs: - port = self.generator.port_num(intf) - self.ports.append(port) - self.generator.client.add_streams(self.get_streams(profile_data), ports=port) - - self.generator.start_client(ports=self.ports, mult=str(multiplier), - duration=120, force=True) - self.first_run = False - - def calculate_pps(self, samples): - pps = round(samples['Throughput'] / 2, 2) - multiplier = round(self.rate / self.pps, 2) - return pps, multiplier - - def create_single_stream(self, packet_size, pps, isg=0): - packet = self._create_single_packet(packet_size) - if pps: - stl_mode = STLTXCont(pps=pps) - else: - stl_mode = STLTXCont(pps=self.pps) - if self.pg_id: - LOGGING.debug("pg_id: %s", self.pg_id) - stl_flow_stats = STLFlowLatencyStats(pg_id=self.pg_id) - stream = STLStream(isg=isg, packet=packet, mode=stl_mode, - flow_stats=stl_flow_stats) - self.pg_id += 1 - else: - stream = STLStream(isg=isg, packet=packet, mode=stl_mode) - return stream + 'Throughput': throughput, + 'DropPercentage': self.drop_percent_max, + 'Rate': last_rate, + 'Latency': latency + } + return output diff --git a/yardstick/network_services/traffic_profile/trex_traffic_profile.py b/yardstick/network_services/traffic_profile/trex_traffic_profile.py index f5e3923d5..ed0355fa5 100644 --- a/yardstick/network_services/traffic_profile/trex_traffic_profile.py +++ b/yardstick/network_services/traffic_profile/trex_traffic_profile.py @@ -19,21 +19,16 @@ from random import SystemRandom import ipaddress import six - -from yardstick.common import exceptions as y_exc -from yardstick.network_services.traffic_profile import base -from trex_stl_lib.trex_stl_client import STLStream -from trex_stl_lib.trex_stl_streams import STLFlowLatencyStats -from trex_stl_lib.trex_stl_streams import STLTXCont -from trex_stl_lib.trex_stl_streams import STLProfile from trex_stl_lib.trex_stl_packet_builder_scapy import STLVmWrFlowVar from trex_stl_lib.trex_stl_packet_builder_scapy import STLVmFlowVarRepeatableRandom from trex_stl_lib.trex_stl_packet_builder_scapy import STLVmFlowVar -from trex_stl_lib.trex_stl_packet_builder_scapy import STLPktBuilder -from trex_stl_lib.trex_stl_packet_builder_scapy import STLScVmRaw from trex_stl_lib.trex_stl_packet_builder_scapy import STLVmFixIpv4 from trex_stl_lib import api as Pkt +from yardstick.common import exceptions as y_exc +from yardstick.network_services.traffic_profile import base + + SRC = 'src' DST = 'dst' ETHERNET = 'Ethernet' @@ -342,115 +337,6 @@ class TrexProfile(base.TrafficProfile): if 'dstport' in outer_l4: self._set_proto_addr(UDP, DST_PORT, outer_l4['dstport'], outer_l4['count']) - def generate_imix_data(self, packet_definition): - """ generate packet size for a given traffic profile """ - imix_count = {} - imix_data = {} - if not packet_definition: - return imix_count - imix = packet_definition.get('framesize') - if imix: - for size in imix: - data = imix[size] - imix_data[int(size[:-1])] = int(data) - imix_sum = sum(imix_data.values()) - if imix_sum > 100: - raise SystemExit("Error in IMIX data") - elif imix_sum < 100: - imix_data[64] = imix_data.get(64, 0) + (100 - imix_sum) - - avg_size = 0.0 - for size in imix_data: - count = int(imix_data[size]) - if count: - avg_size += round(size * count / 100, 2) - pps = round(self.pps * count / 100, 0) - imix_count[size] = pps - self.rate = round(1342177280 / avg_size, 0) * 2 - logging.debug("Imax: %s rate: %s", imix_count, self.rate) - return imix_count - - def get_streams(self, profile_data): - """ generate trex stream - :param profile_data: - :type profile_data: - """ - self.streams = [] - self.pps = self.params['traffic_profile'].get('frame_rate', 100) - for packet_name in profile_data: - outer_l2 = profile_data[packet_name].get('outer_l2') - imix_data = self.generate_imix_data(outer_l2) - if not imix_data: - imix_data = {64: self.pps} - self.generate_vm(profile_data[packet_name]) - for size in imix_data: - self._generate_streams(size, imix_data[size]) - self._generate_profile() - return self.profile - - def generate_vm(self, packet_definition): - """ generate trex vm with flows setup """ - self.ether_packet = Pkt.Ether() - self.ip_packet = Pkt.IP() - self.ip6_packet = None - self.udp_packet = Pkt.UDP() - self.udp[DST_PORT] = 'UDP.dport' - self.udp[SRC_PORT] = 'UDP.sport' - self.qinq = False - self.vm_flow_vars = [] - outer_l2 = packet_definition.get('outer_l2', None) - outer_l3v4 = packet_definition.get('outer_l3v4', None) - outer_l3v6 = packet_definition.get('outer_l3v6', None) - outer_l4 = packet_definition.get('outer_l4', None) - if outer_l2: - self._set_outer_l2_fields(outer_l2) - if outer_l3v4: - self._set_outer_l3v4_fields(outer_l3v4) - if outer_l3v6: - self._set_outer_l3v6_fields(outer_l3v6) - if outer_l4: - self._set_outer_l4_fields(outer_l4) - self.trex_vm = STLScVmRaw(self.vm_flow_vars) - - def generate_packets(self): - """ generate packets from trex TG """ - base_pkt = self.base_pkt - size = self.fsize - 4 - pad = max(0, size - len(base_pkt)) * 'x' - self.packets = [STLPktBuilder(pkt=base_pkt / pad, - vm=vm) for vm in self.vms] - - def _create_single_packet(self, size=64): - size = size - 4 - ether_packet = self.ether_packet - ip_packet = self.ip6_packet if self.ip6_packet else self.ip_packet - udp_packet = self.udp_packet - if self.qinq: - qinq_packet = self.qinq_packet - base_pkt = ether_packet / qinq_packet / ip_packet / udp_packet - else: - base_pkt = ether_packet / ip_packet / udp_packet - pad = max(0, size - len(base_pkt)) * 'x' - packet = STLPktBuilder(pkt=base_pkt / pad, vm=self.trex_vm) - return packet - - def _create_single_stream(self, packet_size, pps, isg=0): - packet = self._create_single_packet(packet_size) - if self.pg_id: - self.pg_id += 1 - stl_flow = STLFlowLatencyStats(pg_id=self.pg_id) - stream = STLStream(isg=isg, packet=packet, mode=STLTXCont(pps=pps), - flow_stats=stl_flow) - else: - stream = STLStream(isg=isg, packet=packet, mode=STLTXCont(pps=pps)) - return stream - - def _generate_streams(self, packet_size, pps): - self.streams.append(self._create_single_stream(packet_size, pps)) - - def _generate_profile(self): - self.profile = STLProfile(self.streams) - @classmethod def _count_ip(cls, start_ip, end_ip): start = ipaddress.ip_address(six.u(start_ip)) diff --git a/yardstick/network_services/vnf_generic/vnf/sample_vnf.py b/yardstick/network_services/vnf_generic/vnf/sample_vnf.py index ef8b3f126..8e0e29675 100644 --- a/yardstick/network_services/vnf_generic/vnf/sample_vnf.py +++ b/yardstick/network_services/vnf_generic/vnf/sample_vnf.py @@ -11,20 +11,17 @@ # 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. -""" Base class implementation for generic vnf implementation """ -from collections import Mapping import logging from multiprocessing import Queue, Value, Process import os import posixpath import re +import six import subprocess import time -import six - from trex_stl_lib.trex_stl_client import LoggerApi from trex_stl_lib.trex_stl_client import STLClient from trex_stl_lib.trex_stl_exceptions import STLError @@ -375,39 +372,14 @@ class ClientResourceHelper(ResourceHelper): LOG.error('TRex client not connected') return {} - def generate_samples(self, ports, key=None, default=None): - # needs to be used ports - last_result = self.get_stats(ports) - key_value = last_result.get(key, default) - - if not isinstance(last_result, Mapping): # added for mock unit test - self._terminated.value = 1 - return {} - - samples = {} - # recalculate port for interface and see if it matches ports provided - for intf in self.vnfd_helper.interfaces: - name = intf["name"] - port = self.vnfd_helper.port_num(name) - if port in ports: - xe_value = last_result.get(port, {}) - samples[name] = { - "rx_throughput_fps": float(xe_value.get("rx_pps", 0.0)), - "tx_throughput_fps": float(xe_value.get("tx_pps", 0.0)), - "rx_throughput_mbps": float(xe_value.get("rx_bps", 0.0)), - "tx_throughput_mbps": float(xe_value.get("tx_bps", 0.0)), - "in_packets": int(xe_value.get("ipackets", 0)), - "out_packets": int(xe_value.get("opackets", 0)), - } - if key: - samples[name][key] = key_value - return samples + def _get_samples(self, ports, port_pg_id=False): + raise NotImplementedError() def _run_traffic_once(self, traffic_profile): traffic_profile.execute_traffic(self) self.client_started.value = 1 time.sleep(self.RUN_DURATION) - samples = self.generate_samples(traffic_profile.ports) + samples = self._get_samples(traffic_profile.ports) time.sleep(self.QUEUE_WAIT_TIME) self._queue.put(samples) diff --git a/yardstick/network_services/vnf_generic/vnf/tg_rfc2544_trex.py b/yardstick/network_services/vnf_generic/vnf/tg_rfc2544_trex.py index 4e9f4bdc1..07cec6745 100644 --- a/yardstick/network_services/vnf_generic/vnf/tg_rfc2544_trex.py +++ b/yardstick/network_services/vnf_generic/vnf/tg_rfc2544_trex.py @@ -11,74 +11,45 @@ # 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. -""" Trex traffic generation definitions which implements rfc2544 """ -from __future__ import absolute_import -from __future__ import print_function -import time import logging -from collections import Mapping - -from yardstick.network_services.vnf_generic.vnf.tg_trex import TrexTrafficGen -from yardstick.network_services.vnf_generic.vnf.sample_vnf import Rfc2544ResourceHelper -from yardstick.network_services.vnf_generic.vnf.tg_trex import TrexResourceHelper - -LOGGING = logging.getLogger(__name__) +import time +from yardstick.common import utils +from yardstick.network_services.vnf_generic.vnf import sample_vnf +from yardstick.network_services.vnf_generic.vnf import tg_trex -class TrexRfc2544ResourceHelper(Rfc2544ResourceHelper): - def is_done(self): - return self.latency and self.iteration.value > 10 +LOGGING = logging.getLogger(__name__) -class TrexRfcResourceHelper(TrexResourceHelper): +class TrexRfcResourceHelper(tg_trex.TrexResourceHelper): - LATENCY_TIME_SLEEP = 120 - RUN_DURATION = 30 - WAIT_TIME = 3 + SAMPLING_PERIOD = 2 + TRANSIENT_PERIOD = 10 - def __init__(self, setup_helper, rfc_helper_type=None): + def __init__(self, setup_helper): super(TrexRfcResourceHelper, self).__init__(setup_helper) - - if rfc_helper_type is None: - rfc_helper_type = TrexRfc2544ResourceHelper - - self.rfc2544_helper = rfc_helper_type(self.scenario_helper) + self.rfc2544_helper = sample_vnf.Rfc2544ResourceHelper( + self.scenario_helper) def _run_traffic_once(self, traffic_profile): - if self._terminated.value: - return - - traffic_profile.execute_traffic(self) self.client_started.value = 1 - time.sleep(self.RUN_DURATION) - self.client.stop(traffic_profile.ports) - time.sleep(self.WAIT_TIME) - samples = traffic_profile.get_drop_percentage(self) - self._queue.put(samples) - - if not self.rfc2544_helper.is_done(): - return - - self.client.stop(traffic_profile.ports) - self.client.reset(ports=traffic_profile.ports) - self.client.remove_all_streams(traffic_profile.ports) - traffic_profile.execute_traffic_latency(samples=samples) - multiplier = traffic_profile.calculate_pps(samples)[1] - for _ in range(5): - time.sleep(self.LATENCY_TIME_SLEEP) - self.client.stop(traffic_profile.ports) - time.sleep(self.WAIT_TIME) - last_res = self.client.get_stats(traffic_profile.ports) - if not isinstance(last_res, Mapping): - self._terminated.value = 1 - continue - self.generate_samples(traffic_profile.ports, 'latency', {}) - self._queue.put(samples) - self.client.start(mult=str(multiplier), - ports=traffic_profile.ports, - duration=120, force=True) + ports, port_pg_id = traffic_profile.execute_traffic(self) + + samples = [] + timeout = int(traffic_profile.config.duration) - self.TRANSIENT_PERIOD + time.sleep(self.TRANSIENT_PERIOD) + for _ in utils.Timer(timeout=timeout): + samples.append(self._get_samples(ports, port_pg_id=port_pg_id)) + time.sleep(self.SAMPLING_PERIOD) + + traffic_profile.stop_traffic(self) + output = traffic_profile.get_drop_percentage( + samples, self.rfc2544_helper.tolerance_low, + self.rfc2544_helper.tolerance_high, + self.rfc2544_helper.correlated_traffic) + self._queue.put(output) def start_client(self, ports, mult=None, duration=None, force=True): self.client.start(ports=ports, mult=mult, duration=duration, force=force) @@ -86,12 +57,8 @@ class TrexRfcResourceHelper(TrexResourceHelper): def clear_client_stats(self, ports): self.client.clear_stats(ports=ports) - def collect_kpi(self): - self.rfc2544_helper.iteration.value += 1 - return super(TrexRfcResourceHelper, self).collect_kpi() - -class TrexTrafficGenRFC(TrexTrafficGen): +class TrexTrafficGenRFC(tg_trex.TrexTrafficGen): """ This class handles mapping traffic profile and generating traffic for rfc2544 testcase. diff --git a/yardstick/network_services/vnf_generic/vnf/tg_trex.py b/yardstick/network_services/vnf_generic/vnf/tg_trex.py index 0084a124c..80b42e22d 100644 --- a/yardstick/network_services/vnf_generic/vnf/tg_trex.py +++ b/yardstick/network_services/vnf_generic/vnf/tg_trex.py @@ -13,7 +13,6 @@ # limitations under the License. """ Trex acts as traffic generation and vnf definitions based on IETS Spec """ -from __future__ import absolute_import import logging import os @@ -25,6 +24,7 @@ from yardstick.network_services.vnf_generic.vnf.sample_vnf import SampleVNFTraff from yardstick.network_services.vnf_generic.vnf.sample_vnf import ClientResourceHelper from yardstick.network_services.vnf_generic.vnf.sample_vnf import DpdkVnfSetupEnvHelper + LOG = logging.getLogger(__name__) @@ -165,6 +165,30 @@ class TrexResourceHelper(ClientResourceHelper): cmd = "sudo fuser -n tcp %s %s -k > /dev/null 2>&1" self.ssh_helper.execute(cmd % (self.SYNC_PORT, self.ASYNC_PORT)) + def _get_samples(self, ports, port_pg_id=None): + stats = self.get_stats(ports) + samples = {} + for pname in (intf['name'] for intf in self.vnfd_helper.interfaces): + port_num = self.vnfd_helper.port_num(pname) + port_stats = stats.get(port_num, {}) + samples[pname] = { + 'rx_throughput_fps': float(port_stats.get('rx_pps', 0.0)), + 'tx_throughput_fps': float(port_stats.get('tx_pps', 0.0)), + 'rx_throughput_bps': float(port_stats.get('rx_bps', 0.0)), + 'tx_throughput_bps': float(port_stats.get('tx_bps', 0.0)), + 'in_packets': int(port_stats.get('ipackets', 0)), + 'out_packets': int(port_stats.get('opackets', 0)), + } + + pg_id_list = port_pg_id.get_pg_ids(port_num) + samples[pname]['latency'] = {} + for pg_id in pg_id_list: + latency_global = stats.get('latency', {}) + pg_latency = latency_global.get(pg_id, {}).get('latency') + samples[pname]['latency'][pg_id] = pg_latency + + return samples + class TrexTrafficGen(SampleVNFTrafficGen): """ |