From 1e1138cdc36ab308568e51314d967f7d13bdacc5 Mon Sep 17 00:00:00 2001 From: "Sridhar K. N. Rao" Date: Wed, 14 Sep 2016 22:42:05 +0530 Subject: pkt_gen: Spirent Testcenter RFC 2889 Support The changes/additions, apart from spirent testcenter-specific, also includes to some files in conf (01_testcase and 03_traffic) and core (result- constants, traffic_controller and component_factory) folders. Currently, only RFC2889 Forwarding testcase is supported. Incorporated following review suggestions: 1. Log level fixes 2. Removing unused function in results_constants.py 3. Added documentation to docs/configguide/trafficgen.rst. Userguide will be updated once other RFC2889 tests are implemented. 4. string matching in component_factory. 5. Remove Trailing Whitespaces JIRA: VSPERF-286 Change-Id: I0195720ab2f8cf2c3a5aa490d66166bdca0afcb0 Signed-off-by: Sridhar K. N. Rao --- conf/01_testcases.conf | 7 + conf/03_traffic.conf | 6 + core/component_factory.py | 6 +- core/results/results_constants.py | 5 + core/traffic_controller_rfc2889.py | 146 ++++++++++ docs/configguide/trafficgen.rst | 36 +++ .../pkt_gen/testcenter/testcenter-rfc2889-rest.py | 304 +++++++++++++++++++++ tools/pkt_gen/testcenter/testcenter.py | 86 ++++++ 8 files changed, 595 insertions(+), 1 deletion(-) create mode 100644 core/traffic_controller_rfc2889.py create mode 100644 tools/pkt_gen/testcenter/testcenter-rfc2889-rest.py diff --git a/conf/01_testcases.conf b/conf/01_testcases.conf index b9c59a11..55cce1cf 100755 --- a/conf/01_testcases.conf +++ b/conf/01_testcases.conf @@ -131,6 +131,13 @@ PERFORMANCE_TESTS = [ "biDirectional": "True", "Description": "LTD.Throughput.RFC2544.PacketLossRatio", }, + { + "Name": "phy2phy_forwarding", + "Traffic Type": "rfc2889", + "Deployment": "p2p", + "biDirectional": "True", + "Description": "LTD.Forwarding.RFC2889.MaxForwardingRate", + }, { "Name": "back2back", "Traffic Type": "back2back", diff --git a/conf/03_traffic.conf b/conf/03_traffic.conf index fd0f589d..04266923 100644 --- a/conf/03_traffic.conf +++ b/conf/03_traffic.conf @@ -75,6 +75,12 @@ TRAFFICGEN_STC_TESTCENTER_PATH = os.path.join(ROOT_DIR, 'tools/pkt_gen/testcente # Name of the TestCenter RFC2544 Tput helper python script TRAFFICGEN_STC_RFC2544_TPUT_TEST_FILE_NAME = "testcenter-rfc2544-throughput.py" +# Name of the Testcenter RFC2899 Tput Helper Python Scripts +TRAFFICGEN_STC_RFC2889_TEST_FILE_NAME = "testcenter-rfc2889-rest.py" + +# 2889 Port Locations +TRAFFICGEN_STC_RFC2889_LOCATION = "" + # The address of the Spirent Lab Server to use TRAFFICGEN_STC_LAB_SERVER_ADDR = "" diff --git a/core/component_factory.py b/core/component_factory.py index 7f453bd2..ef7ba86f 100644 --- a/core/component_factory.py +++ b/core/component_factory.py @@ -16,6 +16,7 @@ """ from core.traffic_controller_rfc2544 import TrafficControllerRFC2544 +from core.traffic_controller_rfc2889 import TrafficControllerRFC2889 from core.vswitch_controller_clean import VswitchControllerClean from core.vswitch_controller_p2p import VswitchControllerP2P from core.vswitch_controller_pxp import VswitchControllerPXP @@ -47,7 +48,10 @@ def create_traffic(traffic_type, trafficgen_class): :param trafficgen_class: Reference to traffic generator class to be used. :return: A new ITrafficController """ - return TrafficControllerRFC2544(trafficgen_class) + if traffic_type.lower().startswith('rfc2889'): + return TrafficControllerRFC2889(trafficgen_class) + else: + return TrafficControllerRFC2544(trafficgen_class) def create_vswitch(deployment_scenario, vswitch_class, traffic, diff --git a/core/results/results_constants.py b/core/results/results_constants.py index b7ab7052..864712bc 100644 --- a/core/results/results_constants.py +++ b/core/results/results_constants.py @@ -58,6 +58,11 @@ class ResultsConstants(object): SCAL_STREAM_COUNT = 'stream_count' SCAL_STREAM_TYPE = 'match_type' SCAL_PRE_INSTALLED_FLOWS = 'pre-installed_flows' + # RFC2889 Forwarding, Address-Caching and Congestion + FORWARDING_RATE_FPS = 'forwarding_rate_fps' + ADDRS_COUNT_FLOOD_COUNT_RATIO = 'addrs_count_flood_count_ratio' + CONGESTION_CONTROL_EXISTS = 'congestion_control_exists' + PORTS_MAP = 'ports_map' TEST_RUN_TIME = "test_execution_time" diff --git a/core/traffic_controller_rfc2889.py b/core/traffic_controller_rfc2889.py new file mode 100644 index 00000000..a97a47d3 --- /dev/null +++ b/core/traffic_controller_rfc2889.py @@ -0,0 +1,146 @@ +"""RFC2889 Traffic Controller implementation. +""" +import logging + +from core.traffic_controller import ITrafficController +from core.results.results_constants import ResultsConstants +from core.results.results import IResults +from conf import settings +from conf import get_test_param + + +class TrafficControllerRFC2889(ITrafficController, IResults): + """Traffic controller for RFC2889 traffic + + Used to setup and control a traffic generator for an RFC2889 deployment + traffic scenario. + """ + + def __init__(self, traffic_gen_class): + """Initialise the trafficgen and store. + + :param traffic_gen_class: The traffic generator class to be used. + """ + self._logger = logging.getLogger(__name__) + self._logger.debug("__init__") + self._traffic_gen_class = traffic_gen_class() + self._traffic_started = False + self._traffic_started_call_count = 0 + self._trials = int(get_test_param('rfc2889_trials', 1)) + self._duration = int(get_test_param('duration', 30)) + self._results = [] + + # If set, comma separated packet_sizes value from --test_params + # on cli takes precedence over value in settings file. + self._packet_sizes = None + packet_sizes_cli = get_test_param('pkt_sizes') + if packet_sizes_cli: + self._packet_sizes = [int(x.strip()) + for x in packet_sizes_cli.split(',')] + else: + self._packet_sizes = settings.getValue('TRAFFICGEN_PKT_SIZES') + + def __enter__(self): + """Call initialisation function. + """ + self._traffic_gen_class.connect() + + def __exit__(self, type_, value, traceback): + """Stop traffic, clean up. + """ + if self._traffic_started: + self.stop_traffic() + + @staticmethod + def _append_results(result_dict, packet_size): + """Adds common values to traffic generator results. + + :param result_dict: Dictionary containing results from trafficgen + :param packet_size: Packet size value. + + :returns: dictionary of results with additional entries. + """ + + ret_value = result_dict + + ret_value[ResultsConstants.TYPE] = 'rfc2889' + ret_value[ResultsConstants.PACKET_SIZE] = str(packet_size) + + return ret_value + + def send_traffic(self, traffic): + """See ITrafficController for description + """ + self._logger.debug('send_traffic with ' + + str(self._traffic_gen_class)) + + for packet_size in self._packet_sizes: + # Merge framesize with the default traffic definition + if 'l2' in traffic: + traffic['l2'] = dict(traffic['l2'], + **{'framesize': packet_size}) + else: + traffic['l2'] = {'framesize': packet_size} + + if traffic['traffic_type'] == 'caching': + result = self._traffic_gen_class.send_rfc2889_caching( + traffic, trials=self._trials, duration=self._duration) + elif traffic['traffic_type'] == 'congestion': + result = self._traffic_gen_class.send_rfc2889_congestion( + traffic, duration=self._duration) + else: + result = self._traffic_gen_class.send_rfc2889_forwarding( + traffic, tests=self._trials, duration=self._duration) + + result = TrafficControllerRFC2889._append_results(result, + packet_size) + self._results.append(result) + + def send_traffic_async(self, traffic, function): + """See ITrafficController for description + """ + self._logger.debug('send_traffic_async with ' + + str(self._traffic_gen_class)) + + for packet_size in self._packet_sizes: + traffic['l2'] = {'framesize': packet_size} + self._traffic_gen_class.start_rfc2889_forwarding( + traffic, + trials=self._trials, + duration=self._duration) + self._traffic_started = True + if len(function['args']) > 0: + function['function'](function['args']) + else: + function['function']() + result = self._traffic_gen_class.wait_rfc2889_forwarding( + traffic, trials=self._trials, duration=self._duration) + result = TrafficControllerRFC2889._append_results(result, + packet_size) + self._results.append(result) + + def stop_traffic(self): + """Kills traffic being sent from the traffic generator. + """ + self._logger.debug("stop_traffic()") + + def print_results(self): + """IResult interface implementation. + """ + counter = 0 + for item in self._results: + logging.info("Record: " + str(counter)) + counter += 1 + for(key, value) in list(item.items()): + logging.info(" Key: " + str(key) + + ", Value: " + str(value)) + + def get_results(self): + """IResult interface implementation. + """ + return self._results + + def validate_send_traffic(self, result, traffic): + """Verify that send traffic has succeeded + """ + return True diff --git a/docs/configguide/trafficgen.rst b/docs/configguide/trafficgen.rst index 28b34a6b..6ede7f2f 100644 --- a/docs/configguide/trafficgen.rst +++ b/docs/configguide/trafficgen.rst @@ -323,6 +323,42 @@ install the package. Once installed, the scripts named with 'rest' keyword can be used. For example: testcenter-rfc2544-rest.py can be used to run RFC 2544 tests using the REST interface. +Configuration: +~~~~~~~~~~~~~~ +The mandatory configurations are enlisted below. + +1. The Labserver and license server addresses. These parameters applies to + all the tests and are mandatory. + +.. code-block:: console + + TRAFFICGEN_STC_LAB_SERVER_ADDR = " " + TRAFFICGEN_STC_LICENSE_SERVER_ADDR = " " + +2. For RFC2544 tests, the following parameters are mandatory + + +.. code-block:: console + + TRAFFICGEN_STC_RFC2544_TPUT_TEST_FILE_NAME = " " + TRAFFICGEN_STC_EAST_CHASSIS_ADDR = " " + TRAFFICGEN_STC_EAST_SLOT_NUM = " " + TRAFFICGEN_STC_EAST_PORT_NUM = " " + TRAFFICGEN_STC_EAST_INTF_ADDR = " " + TRAFFICGEN_STC_EAST_INTF_GATEWAY_ADDR = " " + TRAFFICGEN_STC_WEST_CHASSIS_ADDR = "" + TRAFFICGEN_STC_WEST_SLOT_NUM = " " + TRAFFICGEN_STC_WEST_PORT_NUM = " " + TRAFFICGEN_STC_WEST_INTF_ADDR = " " + TRAFFICGEN_STC_WEST_INTF_GATEWAY_ADDR = " " + +3. For RFC2889 tests, specifying the locations of the ports is mandatory. + +.. code-block:: console + + TRAFFICGEN_STC_RFC2889_TEST_FILE_NAME = " " + TRAFFICGEN_STC_RFC2889_LOCATIONS= " " + Xena Networks ------------- diff --git a/tools/pkt_gen/testcenter/testcenter-rfc2889-rest.py b/tools/pkt_gen/testcenter/testcenter-rfc2889-rest.py new file mode 100644 index 00000000..cfa425e8 --- /dev/null +++ b/tools/pkt_gen/testcenter/testcenter-rfc2889-rest.py @@ -0,0 +1,304 @@ +# Copyright 2016 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. + +''' +@author Spirent Communications + +This test automates the RFC2544 tests using the Spirent +TestCenter REST APIs. This test supports Python 3.4 + +''' +import argparse +import logging +import os + +# Logger Configuration +logger = logging.getLogger(__name__) + + +def create_dir(path): + """Create the directory as specified in path """ + if not os.path.exists(path): + try: + os.makedirs(path) + except OSError as e: + logger.error("Failed to create directory %s: %s", path, str(e)) + raise + + +def write_query_results_to_csv(results_path, csv_results_file_prefix, + query_results): + """ Write the results of the query to the CSV """ + create_dir(results_path) + filec = os.path.join(results_path, csv_results_file_prefix + ".csv") + with open(filec, "wb") as f: + f.write(query_results["Columns"].replace(" ", ",") + "\n") + for row in (query_results["Output"].replace("} {", ","). + replace("{", "").replace("}", "").split(",")): + f.write(row.replace(" ", ",") + "\n") + + +def positive_int(value): + """ Positive Integer type for Arguments """ + ivalue = int(value) + if ivalue <= 0: + raise argparse.ArgumentTypeError( + "%s is an invalid positive int value" % value) + return ivalue + + +def percent_float(value): + """ Floating type for Arguments """ + pvalue = float(value) + if pvalue < 0.0 or pvalue > 100.0: + raise argparse.ArgumentTypeError( + "%s not in range [0.0, 100.0]" % pvalue) + return pvalue + + +def main(): + """ Read the arguments, Invoke Test and Return the results""" + parser = argparse.ArgumentParser() + # Required parameters + required_named = parser.add_argument_group("required named arguments") + required_named.add_argument("--lab_server_addr", + required=True, + help=("The IP address of the " + "Spirent Lab Server"), + dest="lab_server_addr") + required_named.add_argument("--license_server_addr", + required=True, + help=("The IP address of the Spirent " + "License Server"), + dest="license_server_addr") + required_named.add_argument("--location_list", + required=True, + help=("A comma-delimited list of test port " + "locations"), + dest="location_list") + # Optional parameters + optional_named = parser.add_argument_group("optional named arguments") + optional_named.add_argument("--metric", + required=False, + help=("One among - Forwarding,\ + Address Caching and Congestion"), + choices=["forwarding", "caching", + "congestion"], + default="forwarding", + dest="metric") + optional_named.add_argument("--test_session_name", + required=False, + default="Rfc2889Ses", + help=("The friendly name to identify " + "the Spirent Lab Server test session"), + dest="test_session_name") + + optional_named.add_argument("--test_user_name", + required=False, + default="Rfc2889Usr", + help=("The friendly name to identify the " + "Spirent Lab Server test user"), + dest="test_user_name") + optional_named.add_argument("--results_dir", + required=False, + default="./Results", + help="The directory to copy results to", + dest="results_dir") + optional_named.add_argument("--csv_results_file_prefix", + required=False, + default="Rfc2889MaxFor", + help="The prefix for the CSV results files", + dest="csv_results_file_prefix") + optional_named.add_argument("--num_trials", + type=positive_int, + required=False, + default=1, + help=("The number of trials to execute during " + "the test"), + dest="num_trials") + optional_named.add_argument("--trial_duration_sec", + type=positive_int, + required=False, + default=60, + help=("The duration of each trial executed " + "during the test"), + dest="trial_duration_sec") + optional_named.add_argument("--traffic_pattern", + required=False, + choices=["BACKBONE", "MESH", "PAIR"], + default="MESH", + help="The traffic pattern between endpoints", + dest="traffic_pattern") + optional_named.add_argument("--frame_size_list", + type=lambda s: [int(item) + for item in s.split(',')], + required=False, + default=[256], + help="A comma-delimited list of frame sizes", + dest="frame_size_list") + parser.add_argument("-v", + "--verbose", + required=False, + default=True, + help="More output during operation when present", + action="store_true", + dest="verbose") + args = parser.parse_args() + + if args.verbose: + logger.debug("Creating results directory") + create_dir(args.results_dir) + locationList = [str(item) for item in args.location_list.split(',')] + + session_name = args.test_session_name + user_name = args.test_user_name + + try: + # Load Spirent REST Library + from stcrestclient import stchttp + + stc = stchttp.StcHttp(args.lab_server_addr) + session_id = stc.new_session(user_name, session_name) + stc.join_session(session_id) + except RuntimeError as e: + logger.error(e) + raise + + # Retrieve and display the server information + if args.verbose: + logger.debug("SpirentTestCenter system version: %s", + stc.get("system1", "version")) + + try: + if args.verbose: + logger.debug("Bring up license server") + license_mgr = stc.get("system1", "children-licenseservermanager") + if args.verbose: + logger.debug("license_mgr = %s", license_mgr) + stc.create("LicenseServer", under=license_mgr, attributes={ + "server": args.license_server_addr}) + + # Create the root project object + if args.verbose: + logger.debug("Creating project ...") + project = stc.get("System1", "children-Project") + + # Create ports + if args.verbose: + logger.debug("Creating ports ...") + + for location in locationList: + stc.perform("CreateAndReservePorts", params={"locationList": + location, + "RevokeOwner": + "FALSE"}) + + port_list_get = stc.get("System1.project", "children-port") + + if args.verbose: + logger.debug("Adding Host Gen PArams") + gen_params = stc.create("EmulatedDeviceGenParams", + under=project, + attributes={"Port": port_list_get}) + + # Create the DeviceGenEthIIIfParams object + stc.create("DeviceGenEthIIIfParams", + under=gen_params) + # Configuring Ipv4 interfaces + stc.create("DeviceGenIpv4IfParams", + under=gen_params) + + stc.perform("DeviceGenConfigExpand", + params={"DeleteExisting": "No", + "GenParams": gen_params}) + + if args.verbose: + logger.debug("Set up the RFC2889 Forwarding test...") + stc.perform("Rfc2889SetupMaxForwardingRateTestCommand", + params={"Duration": args.trial_duration_sec, + "FrameSizeList": args.frame_size_list, + "NumOfTrials": args.num_trials, + "TrafficPattern": args.traffic_pattern}) + + # Save the configuration + stc.perform("SaveToTcc", params={"Filename": "2889.tcc"}) + # Connect to the hardware... + stc.perform("AttachPorts", params={"portList": stc.get( + "system1.project", "children-port"), "autoConnect": "TRUE"}) + # Apply configuration. + if args.verbose: + logger.debug("Apply configuration...") + stc.apply() + + if args.verbose: + logger.debug("Starting the sequencer...") + stc.perform("SequencerStart") + + # Wait for sequencer to finish + logger.info( + "Starting test... Please wait for the test to complete...") + stc.wait_until_complete() + logger.info("The test has completed... Saving results...") + + # Determine what the results database filename is... + lab_server_resultsdb = stc.get( + "system1.project.TestResultSetting", "CurrentResultFileName") + + if args.verbose: + logger.debug("The lab server results database is %s", + lab_server_resultsdb) + + stc.perform("CSSynchronizeFiles", + params={"DefaultDownloadDir": args.results_dir}) + + resultsdb = args.results_dir + \ + lab_server_resultsdb.split("/Results")[1] + + logger.info( + "The local summary DB file has been saved to %s", resultsdb) + + resultsdict = ( + stc.perform("QueryResult", + params={ + "DatabaseConnectionString": + resultsdb, + "ResultPath": + ("RFC2889MaxForwardingRateTestResultDetailed" + "SummaryView")})) + if args.verbose: + logger.debug("resultsdict[\"Columns\"]: %s", + resultsdict["Columns"]) + logger.debug("resultsdict[\"Output\"]: %s", resultsdict["Output"]) + logger.debug("Result paths: %s", + stc.perform("GetTestResultSettingPaths")) + + # Write results to csv + if args.verbose: + logger.debug("Writing CSV file to results directory %s", + args.results_dir) + write_query_results_to_csv( + args.results_dir, args.csv_results_file_prefix, resultsdict) + + except RuntimeError as err: + logger.error(err) + + if args.verbose: + logger.debug("Destroy session on lab server") + + stc.end_session() + + logger.info("Test complete!") + +if __name__ == "__main__": + main() diff --git a/tools/pkt_gen/testcenter/testcenter.py b/tools/pkt_gen/testcenter/testcenter.py index a6cea2e1..701d451c 100644 --- a/tools/pkt_gen/testcenter/testcenter.py +++ b/tools/pkt_gen/testcenter/testcenter.py @@ -115,6 +115,25 @@ def get_rfc2544_custom_settings(framesize, custom_tr, tests): return args +def get_rfc2889_settings(framesize, tests, duration): + args = [settings.getValue("TRAFFICGEN_STC_PYTHON2_PATH"), + os.path.join( + settings.getValue("TRAFFICGEN_STC_TESTCENTER_PATH"), + settings.getValue( + "TRAFFICGEN_STC_RFC2889_TEST_FILE_NAME")), + "--lab_server_addr", + settings.getValue("TRAFFICGEN_STC_LAB_SERVER_ADDR"), + "--license_server_addr", + settings.getValue("TRAFFICGEN_STC_LICENSE_SERVER_ADDR"), + "--location_list", + settings.getValue("TRAFFICGEN_STC_RFC2889_LOCATIONS"), + "--frame_size_list", + str(framesize), + "--num_trials", + str(tests)] + return args + + class TestCenter(trafficgen.ITrafficGenerator): """ Spirent TestCenter @@ -139,6 +158,48 @@ class TestCenter(trafficgen.ITrafficGenerator): """ return None + def send_rfc2889_congestion(self, traffic=None, tests=1, duration=20): + """ + Do nothing. + """ + return None + + def send_rfc2889_caching(self, traffic=None, tests=1, duration=20): + """ + Do nothing. + """ + return None + + def get_rfc2889_results(self, filename): + """ + Reads the CSV file and return the results + """ + result = {} + with open(filename, "r") as csvfile: + csvreader = csv.DictReader(csvfile) + for row in csvreader: + self._logger.info("Row: %s", row) + duration = int((float(row["TxSignatureFrameCount"])) / + (float(row["OfferedLoad(fps)"]))) + tx_fps = (float(row["OfferedLoad(fps)"])) + rx_fps = float((float(row["RxFrameCount"])) / + float(duration)) + tx_mbps = ((tx_fps * float(row["FrameSize"])) / + (1000000.0)) + rx_mbps = ((rx_fps * float(row["FrameSize"])) / + (1000000.0)) + result[ResultsConstants.TX_RATE_FPS] = tx_fps + result[ResultsConstants.THROUGHPUT_RX_FPS] = rx_fps + result[ResultsConstants.TX_RATE_MBPS] = tx_mbps + result[ResultsConstants.THROUGHPUT_RX_MBPS] = rx_mbps + result[ResultsConstants.TX_RATE_PERCENT] = float( + row["OfferedLoad(%)"]) + result[ResultsConstants.FRAME_LOSS_PERCENT] = float( + row["PercentFrameLoss(%)"]) + result[ResultsConstants.FORWARDING_RATE_FPS] = float( + row["ForwardingRate(fps)"]) + return result + def get_rfc2544_results(self, filename): """ Reads the CSV file and return the results @@ -211,6 +272,31 @@ class TestCenter(trafficgen.ITrafficGenerator): return self.get_rfc2544_results(filec) + def send_rfc2889_forwarding(self, traffic=None, tests=1, duration=20): + """ + Send traffic per RFC2544 throughput test specifications. + """ + framesize = settings.getValue("TRAFFICGEN_STC_FRAME_SIZE") + if traffic and 'l2' in traffic: + if 'framesize' in traffic['l2']: + framesize = traffic['l2']['framesize'] + args = get_rfc2889_settings(framesize, tests, duration) + if settings.getValue("TRAFFICGEN_STC_VERBOSE") is "True": + args.append("--verbose") + verbose = True + self._logger.debug("Arguments used to call test: %s", args) + subprocess.check_call(args) + + filec = os.path.join(settings.getValue("TRAFFICGEN_STC_RESULTS_DIR"), + settings.getValue( + "TRAFFICGEN_STC_CSV_RESULTS_FILE_PREFIX") + + ".csv") + + if verbose: + self._logger.debug("file: %s", filec) + + return self.get_rfc2889_results(filec) + def send_rfc2544_throughput(self, traffic=None, tests=1, duration=20, lossrate=0.0): """ -- cgit 1.2.3-korg