X-Git-Url: https://gerrit.fd.io/r/gitweb?p=csit.git;a=blobdiff_plain;f=resources%2Flibraries%2Fpython%2FTrafficGenerator.py;h=ee65c1bf2afc08444136d32592a853cdb33b7c6a;hp=a8083381d1abd6d3e197ca43701d99dac4c9da5f;hb=39b4a07718ecab94ea331362edb62dfcf678bd09;hpb=92cbb44a89ca808df32e4a4cb137bed076a68a94 diff --git a/resources/libraries/python/TrafficGenerator.py b/resources/libraries/python/TrafficGenerator.py index a8083381d1..ee65c1bf2a 100644 --- a/resources/libraries/python/TrafficGenerator.py +++ b/resources/libraries/python/TrafficGenerator.py @@ -16,14 +16,17 @@ from robot.api import logger from robot.libraries.BuiltIn import BuiltIn -from resources.libraries.python.constants import Constants -from resources.libraries.python.ssh import SSH -from resources.libraries.python.topology import NodeType -from resources.libraries.python.topology import NodeSubTypeTG -from resources.libraries.python.topology import Topology -from resources.libraries.python.DropRateSearch import DropRateSearch +from .DropRateSearch import DropRateSearch +from .constants import Constants +from .ssh import SSH +from .topology import NodeType +from .topology import NodeSubTypeTG +from .topology import Topology +from .search.AbstractMeasurer import AbstractMeasurer +from .search.MultipleLossRatioSearch import MultipleLossRatioSearch +from .search.ReceiveRateMeasurement import ReceiveRateMeasurement -__all__ = ['TrafficGenerator', 'TGDropRateSearchImpl'] +__all__ = ['TGDropRateSearchImpl', 'TrafficGenerator', 'OptimizedSearch'] class TGDropRateSearchImpl(DropRateSearch): @@ -33,23 +36,26 @@ class TGDropRateSearchImpl(DropRateSearch): super(TGDropRateSearchImpl, self).__init__() def measure_loss(self, rate, frame_size, loss_acceptance, - loss_acceptance_type, traffic_type): + loss_acceptance_type, traffic_type, skip_warmup=False): """Runs the traffic and evaluate the measured results. :param rate: Offered traffic load. :param frame_size: Size of frame. :param loss_acceptance: Permitted drop ratio or frames count. :param loss_acceptance_type: Type of permitted loss. - :param traffic_type: Traffic profile ([2,3]-node-L[2,3], ...). - :type rate: int + :param traffic_type: Module name as a traffic type identifier. + See resources/traffic_profiles/trex for implemented modules. + :param skip_warmup: Start TRex without warmup traffic if true. + :type rate: float :type frame_size: str :type loss_acceptance: float :type loss_acceptance_type: LossAcceptanceType :type traffic_type: str + :type skip_warmup: bool :returns: Drop threshold exceeded? (True/False) :rtype: bool - :raises: NotImplementedError if TG is not supported. - :raises: RuntimeError if TG is not specified. + :raises NotImplementedError: If TG is not supported. + :raises RuntimeError: If TG is not specified. """ # we need instance of TrafficGenerator instantiated by Robot Framework # to be able to use trex_stl-*() @@ -60,9 +66,15 @@ class TGDropRateSearchImpl(DropRateSearch): raise RuntimeError('TG subtype not defined') elif tg_instance.node['subtype'] == NodeSubTypeTG.TREX: unit_rate = str(rate) + self.get_rate_type_str() - tg_instance.trex_stl_start_remote_exec(self.get_duration(), - unit_rate, frame_size, - traffic_type) + if skip_warmup: + tg_instance.trex_stl_start_remote_exec(self.get_duration(), + unit_rate, frame_size, + traffic_type, + warmup_time=0.0) + else: + tg_instance.trex_stl_start_remote_exec(self.get_duration(), + unit_rate, frame_size, + traffic_type) loss = tg_instance.get_loss() sent = tg_instance.get_sent() if self.loss_acceptance_type_is_percentage(): @@ -89,8 +101,13 @@ class TGDropRateSearchImpl(DropRateSearch): return tg_instance.get_latency_int() -class TrafficGenerator(object): - """Traffic Generator.""" +class TrafficGenerator(AbstractMeasurer): + """Traffic Generator. + + FIXME: Describe API.""" + + # TODO: Decrease friction between various search and rate provider APIs. + # FIXME: Remove "trex" from lines which could work with other TGs. # use one instance of TrafficGenerator for all tests in test suite ROBOT_LIBRARY_SCOPE = 'TEST SUITE' @@ -104,6 +121,10 @@ class TrafficGenerator(object): self._node = None # T-REX interface order mapping self._ifaces_reordered = False + # Parameters not given by measure(). + self.frame_size = None + self.traffic_type = None + self.warmup_time = None @property def node(self): @@ -174,7 +195,7 @@ class TrafficGenerator(object): :type tg_if1_dst_mac: str :type tg_if2_dst_mac: str :returns: nothing - :raises: RuntimeError in case of issue during initialization. + :raises RuntimeError: In case of issue during initialization. """ if tg_node['type'] != NodeType.TG: raise RuntimeError('Node type is not a TG') @@ -185,8 +206,9 @@ class TrafficGenerator(object): ssh.connect(tg_node) (ret, _, _) = ssh.exec_command( - "sudo -E sh -c '{}/resources/tools/trex/" - "trex_installer.sh'".format(Constants.REMOTE_FW_DIR), + "sudo -E sh -c '{0}/resources/tools/trex/" + "trex_installer.sh {1}'".format(Constants.REMOTE_FW_DIR, + Constants.TREX_INSTALL_VERSION), timeout=1800) if int(ret) != 0: raise RuntimeError('TRex installation failed.') @@ -306,6 +328,24 @@ class TrafficGenerator(object): # critical error occurred raise RuntimeError('t-rex-64 startup failed') + @staticmethod + def is_trex_running(node): + """Check if TRex is running using pidof. + + :param node: Traffic generator node. + :type node: dict + :returns: True if TRex is running otherwise False. + :rtype: bool + :raises RuntimeError: If node type is not a TG. + """ + if node['type'] != NodeType.TG: + raise RuntimeError('Node type is not a TG') + + ssh = SSH() + ssh.connect(node) + ret, _, _ = ssh.exec_command_sudo("pidof t-rex") + return bool(int(ret) == 0) + @staticmethod def teardown_traffic_generator(node): """TG teardown. @@ -313,8 +353,8 @@ class TrafficGenerator(object): :param node: Traffic generator node. :type node: dict :returns: nothing - :raises: RuntimeError if TRex teardown failed. - :raises: RuntimeError if node type is not a TG. + :raises RuntimeError: If node type is not a TG, + or if TRex teardown fails. """ if node['type'] != NodeType.TG: raise RuntimeError('Node type is not a TG') @@ -333,7 +373,7 @@ class TrafficGenerator(object): :param node: TRex generator node. :type node: dict :returns: Nothing - :raises: RuntimeError if stop traffic script fails. + :raises RuntimeError: If stop traffic script fails. """ ssh = SSH() ssh.connect(node) @@ -347,25 +387,26 @@ class TrafficGenerator(object): def trex_stl_start_remote_exec(self, duration, rate, framesize, traffic_type, async_call=False, - latency=True, warmup_time=5): + latency=True, warmup_time=5.0): """Execute script on remote node over ssh to start traffic. :param duration: Time expresed in seconds for how long to send traffic. :param rate: Traffic rate expressed with units (pps, %) :param framesize: L2 frame size to send (without padding and IPG). - :param traffic_type: Traffic profile. + :param traffic_type: Module name as a traffic type identifier. + See resources/traffic_profiles/trex for implemented modules. :param async_call: If enabled then don't wait for all incomming trafic. :param latency: With latency measurement. :param warmup_time: Warmup time period. - :type duration: int + :type duration: float :type rate: str :type framesize: str :type traffic_type: str :type async_call: bool :type latency: bool - :type warmup_time: int + :type warmup_time: float :returns: Nothing - :raises: RuntimeError in case of TG driver issue. + :raises RuntimeError: In case of TG driver issue. """ ssh = SSH() ssh.connect(self._node) @@ -391,7 +432,7 @@ class TrafficGenerator(object): "{9}'". # --latency format(Constants.REMOTE_FW_DIR, profile_path, duration, framesize, rate, warmup_time, _p0 - 1, _p1 - 1, _async, _latency), - timeout=int(duration) + 60) + timeout=float(duration) + 60) if int(ret) != 0: raise RuntimeError('TRex stateless runtime error') @@ -420,7 +461,7 @@ class TrafficGenerator(object): """Stop all traffic on TG. :returns: Nothing - :raises: RuntimeError if TG is not set. + :raises RuntimeError: If TG is not set. """ if self._node is None: raise RuntimeError("TG is not set") @@ -435,7 +476,8 @@ class TrafficGenerator(object): :param duration: Duration of test traffic generation in seconds. :param rate: Offered load per interface (e.g. 1%, 3gbps, 4mpps, ...). :param framesize: Frame size (L2) in Bytes. - :param traffic_type: Traffic profile. + :param traffic_type: Module name as a traffic type identifier. + See resources/traffic_profiles/trex for implemented modules. :param warmup_time: Warmup phase in seconds. :param async_call: Async mode. :param latency: With latency measurement. @@ -443,14 +485,14 @@ class TrafficGenerator(object): :type rate: str :type framesize: str :type traffic_type: str - :type warmup_time: int + :type warmup_time: float :type async_call: bool :type latency: bool :returns: TG output. :rtype: str - :raises: RuntimeError if TG is not set. - :raises: RuntimeError if node is not TG or subtype is not specified. - :raises: NotImplementedError if TG is not supported. + :raises RuntimeError: If TG is not set, or if node is not TG, + or if subtype is not specified. + :raises NotImplementedError: If TG is not supported. """ node = self._node @@ -463,7 +505,7 @@ class TrafficGenerator(object): if node['subtype'] is None: raise RuntimeError('TG subtype not defined') elif node['subtype'] == NodeSubTypeTG.TREX: - self.trex_stl_start_remote_exec(int(duration), rate, framesize, + self.trex_stl_start_remote_exec(duration, rate, framesize, traffic_type, async_call, latency, warmup_time=warmup_time) else: @@ -475,12 +517,23 @@ class TrafficGenerator(object): """Fail if loss occurred in traffic run. :returns: nothing - :raises: Exception if loss occured. + :raises Exception: If loss occured. """ if self._loss is None: - raise Exception('The traffic generation has not been issued') + raise RuntimeError('The traffic generation has not been issued') if self._loss != '0': - raise Exception('Traffic loss occurred: {0}'.format(self._loss)) + raise RuntimeError('Traffic loss occurred: {0}'.format(self._loss)) + + def fail_if_no_traffic_forwarded(self): + """Fail if no traffic forwarded. + + :returns: nothing + :raises Exception: If no traffic forwarded. + """ + if self._received is None: + raise RuntimeError('The traffic generation has not been issued') + if self._received == '0': + raise RuntimeError('No traffic forwarded') def partial_traffic_loss_accepted(self, loss_acceptance, loss_acceptance_type): @@ -491,7 +544,7 @@ class TrafficGenerator(object): :type loss_acceptance: float :type loss_acceptance_type: LossAcceptanceType :returns: nothing - :raises: Exception if loss is above acceptance criteria. + :raises Exception: If loss is above acceptance criteria. """ if self._loss is None: raise Exception('The traffic generation has not been issued') @@ -506,3 +559,105 @@ class TrafficGenerator(object): if loss > float(loss_acceptance): raise Exception("Traffic loss {} above loss acceptance: {}".format( loss, loss_acceptance)) + + def set_rate_provider_defaults(self, frame_size, traffic_type, + warmup_time=0.0): + """Store values accessed by measure(). + + :param frame_size: Frame size identifier or value [B]. + :param traffic_type: Module name as a traffic type identifier. + See resources/traffic_profiles/trex for implemented modules. + :param warmup_time: Traffic duration before measurement starts [s]. + :type frame_size: str or int + :type traffic_type: str + :type warmup_time: float + """ + self.frame_size = frame_size + self.traffic_type = str(traffic_type) + self.warmup_time = float(warmup_time) + + def measure(self, duration, transmit_rate): + """Run bi-directional measurement, parse and return results. + + :param duration: Trial duration [s]. + :param transmit_rate: Target bidirectional transmit rate [pps]. + :type duration: float + :type transmit_rate: float + :returns: Structure containing the result of the measurement. + :rtype: ReceiveRateMeasurement + :raises RuntimeError: If TG is not set, or if node is not TG, + or if subtype is not specified. + :raises NotImplementedError: If TG is not supported. + """ + duration = float(duration) + transmit_rate = float(transmit_rate) + # Trex needs target Tr per stream, but reports aggregate Tx and Dx. + unit_rate = str(transmit_rate / 2.0) + "pps" + self.send_traffic_on_tg( + duration, unit_rate, self.frame_size, self.traffic_type, + self.warmup_time, latency=True) + transmit_count = int(self.get_sent()) + loss_count = int(self.get_loss()) + measurement = ReceiveRateMeasurement( + duration, transmit_rate, transmit_count, loss_count) + measurement.latency = self.get_latency_int() + return measurement + + +class OptimizedSearch(object): + """Class to be imported as Robot Library, containing a single keyword.""" + + @staticmethod + def perform_optimized_ndrpdr_search( + frame_size, traffic_type, minimum_transmit_rate, + maximum_transmit_rate, packet_loss_ratio=0.005, + final_relative_width=0.005, final_trial_duration=30.0, + initial_trial_duration=1.0, number_of_intermediate_phases=2, + timeout=600.0): + """Setup initialized TG, perform optimized search, return intervals. + + :param frame_size: Frame size identifier or value [B]. + :param traffic_type: Module name as a traffic type identifier. + See resources/traffic_profiles/trex for implemented modules. + :param minimum_transmit_rate: Minimal bidirectional + target transmit rate [pps]. + :param maximum_transmit_rate: Maximal bidirectional + target transmit rate [pps]. + :param packet_loss_ratio: Fraction of packets lost, for PDR [1]. + :param final_relative_width: Final lower bound transmit rate + cannot be more distant that this multiple of upper bound [1]. + :param final_trial_duration: Trial duration for the final phase [s]. + :param initial_trial_duration: Trial duration for the initial phase + and also for the first intermediate phase [s]. + :param number_of_intermediate_phases: Number of intermediate phases + to perform before the final phase [1]. + :param timeout: The search will fail itself when not finished + before this overall time [s]. + :type frame_size: str or int + :type traffic_type: str + :type minimum_transmit_rate: float + :type maximum_transmit_rate: float + :type packet_loss_ratio: float + :type final_relative_width: float + :type final_trial_duration: float + :type initial_trial_duration: float + :type number_of_intermediate_phases: int + :type timeout: float + :returns: Structure containing narrowed down NDR and PDR intervals + and their measurements. + :rtype: NdrPdrResult + :raises RuntimeError: If total duration is larger than timeout. + """ + # we need instance of TrafficGenerator instantiated by Robot Framework + # to be able to use trex_stl-*() + tg_instance = BuiltIn().get_library_instance( + 'resources.libraries.python.TrafficGenerator') + tg_instance.set_rate_provider_defaults(frame_size, traffic_type) + algorithm = MultipleLossRatioSearch( + measurer=tg_instance, final_trial_duration=final_trial_duration, + final_relative_width=final_relative_width, + number_of_intermediate_phases=number_of_intermediate_phases, + initial_trial_duration=initial_trial_duration, timeout=timeout) + result = algorithm.narrow_down_ndr_and_pdr( + minimum_transmit_rate, maximum_transmit_rate, packet_loss_ratio) + return result