1 # Copyright 2016-2017 Spirent Communications.
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
15 # Invalid name of file, must be used '_' instead '-'
16 # pylint: disable=invalid-name
18 @author Spirent Communications
20 This test automates the RFC2544 tests using the Spirent
21 TestCenter REST APIs. This test supports Python 3.4
30 _LOGGER = logging.getLogger(__name__)
32 GENOME_PKTSIZE_ENCODING = {"a": 64, "b": 128, "c": 256, "d": 512,
33 "e": 1024, "f": 1280, "g": 1518, "h": 2112}
36 def genome2weights(sequence):
37 """ Convert genome sequence to packetsize weights"""
38 weights = collections.defaultdict(int)
39 for char in GENOME_PKTSIZE_ENCODING:
40 charcount = sequence.count(char)
42 weights[GENOME_PKTSIZE_ENCODING[char]] = charcount
47 """Create the directory as specified in path """
48 if not os.path.exists(path):
52 _LOGGER.error("Failed to create directory %s: %s", path, str(ex))
56 def write_histogram_to_csv(results_path, csv_results_file_prefix,
58 """ Write the results of the query to the CSV """
59 filec = os.path.join(results_path, csv_results_file_prefix + ".csv")
60 with open(filec, "wb") as result_file:
62 result_file.write(str(key) + "\n")
63 result_file.write(str(ranges) + "\n")
64 result_file.write(str(counts[key]) + "\n")
67 def write_query_results_to_csv(results_path, csv_results_file_prefix,
69 """ Write the results of the query to the CSV """
70 create_dir(results_path)
71 filec = os.path.join(results_path, csv_results_file_prefix + ".csv")
72 with open(filec, "wb") as result_file:
73 result_file.write(query_results["Columns"].replace(" ", ",") + "\n")
74 for row in (query_results["Output"].replace("} {", ",").
75 replace("{", "").replace("}", "").split(",")):
76 result_file.write(row.replace(" ", ",") + "\n")
79 def positive_int(value):
80 """ Positive Integer type for Arguments """
83 raise argparse.ArgumentTypeError(
84 "%s is an invalid positive int value" % value)
88 def percent_float(value):
89 """ Floating type for Arguments """
91 if pvalue < 0.0 or pvalue > 100.0:
92 raise argparse.ArgumentTypeError(
93 "%s not in range [0.0, 100.0]" % pvalue)
97 # pylint: disable=too-many-branches, too-many-statements, too-many-locals
99 """ Read the arguments, Invoke Test and Return the results"""
100 parser = argparse.ArgumentParser()
101 # Required parameters
102 required_named = parser.add_argument_group("required named arguments")
103 required_named.add_argument("--lab_server_addr",
105 help=("The IP address of the"
106 "Spirent Lab Server"),
107 dest="lab_server_addr")
108 required_named.add_argument("--license_server_addr",
110 help=("The IP address of the Spirent"
112 dest="license_server_addr")
113 required_named.add_argument("--east_chassis_addr",
115 help=("The TestCenter chassis IP address to"
116 "use for the east test port"),
117 dest="east_chassis_addr")
118 required_named.add_argument("--east_slot_num",
121 help=("The TestCenter slot number to"
122 "use for the east test port"),
123 dest="east_slot_num")
124 required_named.add_argument("--east_port_num",
127 help=("The TestCenter port number to use"
128 "for the east test port"),
129 dest="east_port_num")
130 required_named.add_argument("--west_chassis_addr",
132 help=("The TestCenter chassis IP address"
133 "to use for the west test port"),
134 dest="west_chassis_addr")
135 required_named.add_argument("--west_slot_num",
138 help=("The TestCenter slot number to use"
139 "for the west test port"),
140 dest="west_slot_num")
141 required_named.add_argument("--west_port_num",
144 help=("The TestCenter port number to"
145 "use for the west test port"),
146 dest="west_port_num")
147 # Optional parameters
148 optional_named = parser.add_argument_group("optional named arguments")
149 optional_named.add_argument("--metric",
151 help=("One among - throughput, latency,\
152 backtoback and frameloss"),
153 choices=["throughput", "latency",
154 "backtoback", "frameloss"],
155 default="throughput",
157 optional_named.add_argument("--test_session_name",
159 default="RFC2544 East-West Throughput",
160 help=("The friendly name to identify"
161 "the Spirent Lab Server test session"),
162 dest="test_session_name")
164 optional_named.add_argument("--test_user_name",
166 default="RFC2544 East-West User",
167 help=("The friendly name to identify the"
168 "Spirent Lab Server test user"),
169 dest="test_user_name")
170 optional_named.add_argument("--results_dir",
173 help="The directory to copy results to",
175 optional_named.add_argument("--vsperf_results_dir",
178 help="The directory to copy results to",
179 dest="vsperf_results_dir")
180 optional_named.add_argument("--csv_results_file_prefix",
182 default="Rfc2544Tput",
183 help="The prefix for the CSV results files",
184 dest="csv_results_file_prefix")
185 optional_named.add_argument("--num_trials",
189 help=("The number of trials to execute during"
192 optional_named.add_argument("--trial_duration_sec",
196 help=("The duration of each trial executed"
198 dest="trial_duration_sec")
199 optional_named.add_argument("--traffic_pattern",
201 choices=["BACKBONE", "MESH", "PAIR"],
203 help="The traffic pattern between endpoints",
204 dest="traffic_pattern")
205 optional_named.add_argument("--traffic_custom",
208 help="The traffic pattern between endpoints",
209 dest="traffic_custom")
210 optional_named.add_argument("--search_mode",
212 choices=["COMBO", "STEP", "BINARY"],
214 help=("The search mode used to find the"
217 optional_named.add_argument("--learning_mode",
219 choices=["AUTO", "L2_LEARNING",
220 "L3_LEARNING", "NONE"],
222 help=("The learning mode used during the test,"
223 "default is 'NONE'"),
224 dest="learning_mode")
225 optional_named.add_argument("--rate_lower_limit_pct",
229 help=("The minimum percent line rate that"
230 "will be used during the test"),
231 dest="rate_lower_limit_pct")
232 optional_named.add_argument("--rate_upper_limit_pct",
236 help=("The maximum percent line rate that"
237 "will be used during the test"),
238 dest="rate_upper_limit_pct")
239 optional_named.add_argument("--rate_initial_pct",
243 help=("If Search Mode is BINARY, the percent"
244 "line rate that will be used at the"
245 "start of the test"),
246 dest="rate_initial_pct")
247 optional_named.add_argument("--rate_step_pct",
251 help=("If SearchMode is STEP, the percent"
252 "load increase per step"),
253 dest="rate_step_pct")
254 optional_named.add_argument("--resolution_pct",
258 help=("The minimum percentage of load"
259 "adjustment between iterations"),
260 dest="resolution_pct")
261 optional_named.add_argument("--frame_size_list",
262 type=lambda s: [int(item)
263 for item in s.split(',')],
266 help="A comma-delimited list of frame sizes",
267 dest="frame_size_list")
268 optional_named.add_argument("--acceptable_frame_loss_pct",
272 help=("The maximum acceptable frame loss"
273 "percent in any iteration"),
274 dest="acceptable_frame_loss_pct")
275 optional_named.add_argument("--east_intf_addr",
277 default="192.85.1.3",
278 help=("The address to assign to the first"
279 "emulated device interface on the first"
281 dest="east_intf_addr")
282 optional_named.add_argument("--east_intf_gateway_addr",
284 default="192.85.1.53",
285 help=("The gateway address to assign to the"
286 "first emulated device interface on the"
288 dest="east_intf_gateway_addr")
289 optional_named.add_argument("--west_intf_addr",
291 default="192.85.1.53",
292 help=("The address to assign to the first"
293 "emulated device interface on the"
295 dest="west_intf_addr")
296 optional_named.add_argument("--west_intf_gateway_addr",
298 default="192.85.1.53",
299 help=("The gateway address to assign to"
300 "the first emulated device interface"
301 "on the first west port"),
302 dest="west_intf_gateway_addr")
303 optional_named.add_argument("--latency_histogram",
306 help="latency histogram is required in output?",
307 dest="latency_histogram")
308 optional_named.add_argument("--imix",
311 help=("IMIX specification as genome"
312 "Encoding - RFC 6985"),
314 parser.add_argument("-v",
318 help="More output during operation when present",
321 args = parser.parse_args()
324 _LOGGER.debug("Creating results directory")
325 create_dir(args.results_dir)
327 session_name = args.test_session_name
328 user_name = args.test_user_name
329 # pylint: disable=import-error
331 # Load Spirent REST Library
332 from stcrestclient import stchttp
334 stc = stchttp.StcHttp(args.lab_server_addr)
335 session_id = stc.new_session(user_name, session_name)
336 stc.join_session(session_id)
337 except RuntimeError as err:
341 # Get STC system info.
342 tx_port_loc = "//%s/%s/%s" % (args.east_chassis_addr,
345 rx_port_loc = "//%s/%s/%s" % (args.west_chassis_addr,
349 # Retrieve and display the server information
351 _LOGGER.debug("SpirentTestCenter system version: %s",
352 stc.get("system1", "version"))
354 # pylint: disable=too-many-nested-blocks
359 _LOGGER.debug("Bring up license server")
360 license_mgr = stc.get("system1", "children-licenseservermanager")
362 _LOGGER.debug("license_mgr = %s", license_mgr)
363 stc.create("LicenseServer", under=license_mgr, attributes={
364 "server": args.license_server_addr})
366 # Create the root project object
368 _LOGGER.debug("Creating project ...")
369 project = stc.get("System1", "children-Project")
371 # Configure any custom traffic parameters
372 if args.traffic_custom == "cont":
374 _LOGGER.debug("Configure Continuous Traffic")
375 stc.create("ContinuousTestConfig", under=project)
379 _LOGGER.debug("Creating ports ...")
380 east_chassis_port = stc.create('port', project)
382 _LOGGER.debug("Configuring TX port ...")
383 stc.config(east_chassis_port, {'location': tx_port_loc})
384 port_list.append(east_chassis_port)
386 west_chassis_port = stc.create('port', project)
388 _LOGGER.debug("Configuring RX port ...")
389 stc.config(west_chassis_port, {'location': rx_port_loc})
390 port_list.append(west_chassis_port)
392 # Create emulated genparam for east port
393 east_device_gen_params = stc.create("EmulatedDeviceGenParams",
397 # Create the DeviceGenEthIIIfParams object
398 stc.create("DeviceGenEthIIIfParams",
399 under=east_device_gen_params,
400 attributes={'UseDefaultPhyMac': True})
402 # Configuring Ipv4 interfaces
403 stc.create("DeviceGenIpv4IfParams",
404 under=east_device_gen_params,
405 attributes={"Addr": args.east_intf_addr,
406 "Gateway": args.east_intf_gateway_addr})
407 # Create Devices using the Device Wizard
408 device_gen_config = stc.perform("DeviceGenConfigExpand",
409 params={"DeleteExisting": "No",
411 east_device_gen_params})
412 # Append to the device list
413 device_list.append(device_gen_config['ReturnList'])
415 # Create emulated genparam for west port
416 west_device_gen_params = stc.create("EmulatedDeviceGenParams",
420 # Create the DeviceGenEthIIIfParams object
421 stc.create("DeviceGenEthIIIfParams",
422 under=west_device_gen_params,
423 attributes={'UseDefaultPhyMac': True})
425 # Configuring Ipv4 interfaces
426 stc.create("DeviceGenIpv4IfParams",
427 under=west_device_gen_params,
428 attributes={"Addr": args.west_intf_addr,
429 "Gateway": args.west_intf_gateway_addr})
430 # Create Devices using the Device Wizard
431 device_gen_config = stc.perform("DeviceGenConfigExpand",
432 params={"DeleteExisting": "No",
434 west_device_gen_params})
435 # Append to the device list
436 device_list.append(device_gen_config['ReturnList'])
438 _LOGGER.debug(device_list)
440 # Configure Histogram
441 if args.latency_histogram:
442 # Generic Configuration
443 histResOptions = stc.get("project1", 'children-ResultOptions')
444 stc.config(histResOptions, {'ResultViewMode': 'HISTOGRAM'})
445 # East Port Configuration
446 histAnaEast = stc.get(east_chassis_port, 'children-Analyzer')
447 histAnaEastConfig = stc.get(histAnaEast, 'children-AnalyzerConfig')
448 stc.config(histAnaEastConfig, {'HistogramMode': 'LATENCY'})
449 eLatHist = stc.get(histAnaEastConfig, 'children-LatencyHistogram')
450 stc.config(eLatHist, {'ConfigMode': 'CONFIG_LIMIT_MODE',
451 'BucketSizeUnit': 'ten_nanoseconds',
453 'DistributionMode': 'CENTERED_MODE'})
454 # West Port Configuration
455 histAnaWest = stc.get(west_chassis_port, 'children-Analyzer')
456 histAnaWestConfig = stc.get(histAnaWest, 'children-AnalyzerConfig')
457 stc.config(histAnaWestConfig, {'HistogramMode': 'LATENCY'})
458 wLatHist = stc.get(histAnaWestConfig, 'children-LatencyHistogram')
459 stc.config(wLatHist, {'ConfigMode': 'CONFIG_LIMIT_MODE',
460 'BucketSizeUnit': 'ten_nanoseconds',
462 'DistributionMode': 'CENTERED_MODE'})
463 gBucketSizeList = stc.get(wLatHist, 'BucketSizeList')
464 # gLimitSizeList = stc.get(wLatHist, 'LimitList')
469 args.frame_size_list = []
470 weights = genome2weights(args.imix)
471 fld = stc.create('FrameLengthDistribution', under=project)
472 def_slots = stc.get(fld, "children-framelengthdistributionslot")
473 stc.perform("Delete", params={"ConfigList": def_slots})
474 for fsize in weights:
475 stc.create('framelengthdistributionslot', under=fld,
476 attributes={'FixedFrameLength': fsize,
477 'Weight': weights[fsize]})
479 # Create the RFC 2544 'metric test
480 if args.metric == "throughput":
482 _LOGGER.debug("Set up the RFC2544 throughput test...")
483 stc.perform("Rfc2544SetupThroughputTestCommand",
484 params={"AcceptableFrameLoss":
485 args.acceptable_frame_loss_pct,
486 "Duration": args.trial_duration_sec,
487 "FrameSizeList": args.frame_size_list,
488 "LearningMode": args.learning_mode,
489 "NumOfTrials": args.num_trials,
490 "RateInitial": args.rate_initial_pct,
491 "RateLowerLimit": args.rate_lower_limit_pct,
492 "RateStep": args.rate_step_pct,
493 "RateUpperLimit": args.rate_upper_limit_pct,
494 "Resolution": args.resolution_pct,
495 "SearchMode": args.search_mode,
496 "TrafficPattern": args.traffic_pattern,
497 "FrameSizeDistributionList": fld})
498 elif args.metric == "backtoback":
499 stc.perform("Rfc2544SetupBackToBackTestCommand",
500 params={"AcceptableFrameLoss":
501 args.acceptable_frame_loss_pct,
502 "Duration": args.trial_duration_sec,
503 "FrameSizeList": args.frame_size_list,
504 "LearningMode": args.learning_mode,
505 "LatencyType": args.latency_type,
506 "NumOfTrials": args.num_trials,
507 "RateInitial": args.rate_initial_pct,
508 "RateLowerLimit": args.rate_lower_limit_pct,
509 "RateStep": args.rate_step_pct,
510 "RateUpperLimit": args.rate_upper_limit_pct,
511 "Resolution": args.resolution_pct,
512 "SearchMode": args.search_mode,
513 "TrafficPattern": args.traffic_pattern})
514 elif args.metric == "frameloss":
515 stc.perform("Rfc2544SetupFrameLossTestCommand",
516 params={"AcceptableFrameLoss":
517 args.acceptable_frame_loss_pct,
518 "Duration": args.trial_duration_sec,
519 "FrameSizeList": args.frame_size_list,
520 "LearningMode": args.learning_mode,
521 "LatencyType": args.latency_type,
522 "NumOfTrials": args.num_trials,
523 "RateInitial": args.rate_initial_pct,
524 "RateLowerLimit": args.rate_lower_limit_pct,
525 "RateStep": args.rate_step_pct,
526 "RateUpperLimit": args.rate_upper_limit_pct,
527 "Resolution": args.resolution_pct,
528 "SearchMode": args.search_mode,
529 "TrafficPattern": args.traffic_pattern})
530 elif args.metric == "latency":
531 stc.perform("Rfc2544SetupLatencyTestCommand",
532 params={"AcceptableFrameLoss":
533 args.acceptable_frame_loss_pct,
534 "Duration": args.trial_duration_sec,
535 "FrameSizeList": args.frame_size_list,
536 "LearningMode": args.learning_mode,
537 "LatencyType": args.latency_type,
538 "NumOfTrials": args.num_trials,
539 "RateInitial": args.rate_initial_pct,
540 "RateLowerLimit": args.rate_lower_limit_pct,
541 "RateStep": args.rate_step_pct,
542 "RateUpperLimit": args.rate_upper_limit_pct,
543 "Resolution": args.resolution_pct,
544 "SearchMode": args.search_mode,
545 "TrafficPattern": args.traffic_pattern})
547 # Save the configuration
548 stc.perform("SaveToTcc", params={"Filename": "2544.tcc"})
549 # Connect to the hardware...
550 stc.perform("AttachPorts", params={"portList": stc.get(
551 "system1.project", "children-port"), "autoConnect": "TRUE"})
552 # Apply configuration.
554 _LOGGER.debug("Apply configuration...")
558 _LOGGER.debug("Starting the sequencer...")
559 stc.perform("SequencerStart")
561 # Wait for sequencer to finish
563 "Starting test... Please wait for the test to complete...")
564 stc.wait_until_complete()
565 _LOGGER.info("The test has completed... Saving results...")
567 # Determine what the results database filename is...
568 lab_server_resultsdb = stc.get(
569 "system1.project.TestResultSetting", "CurrentResultFileName")
572 _LOGGER.debug("The lab server results database is %s",
573 lab_server_resultsdb)
575 # Create Latency Histogram CSV file()
576 if args.latency_histogram:
577 hist_dict_counts = {}
578 for file_url in stc.files():
579 if '-FrameSize-' in file_url:
580 stc.download(file_url)
581 filename = file_url.split('/')[-1]
582 if os.path.exists(os.getcwd() + '/' + filename):
583 conn = sqlite3.connect(os.getcwd() + '/' + filename)
584 # cursor = conn.execute(
585 # 'select * from RxEotStreamResults')
586 # names = [desc[0] for desc in cursor.description]
587 counts = conn.execute("SELECT \
588 HistBin1Count, HistBin2Count,\
589 HistBin3Count, HistBin4Count,\
590 HistBin5Count, HistBin6Count,\
591 HistBin7Count, HistBin8Count,\
592 HistBin9Count, HistBin10Count,\
593 HistBin11Count, HistBin12Count,\
594 HistBin13Count, HistBin14Count, \
595 HistBin15Count, HistBin16Count \
596 from RxEotStreamResults")
597 strs = filename.split('-')
598 key = strs[strs.index('FrameSize')+1]
599 if key in hist_dict_counts:
600 hist_dict_counts[key] = [a+b for a, b in
601 zip(counts.fetchone(),
602 hist_dict_counts[key])]
604 hist_dict_counts[key] = counts.fetchone()
607 write_histogram_to_csv(args.vsperf_results_dir, 'Histogram',
611 stc.perform("CSSynchronizeFiles",
612 params={"DefaultDownloadDir": args.results_dir})
614 resultsdb = args.results_dir + \
615 lab_server_resultsdb.split("/Results")[1]
617 if not os.path.exists(resultsdb):
618 resultsdb = lab_server_resultsdb
619 _LOGGER.info("Failed to create the local summary DB File, using"
620 " the remote DB file instead.")
623 "The local summary DB file has been saved to %s", resultsdb)
625 # The returns the "RFC2544ThroughputTestResultDetailedSummaryView"
626 # table view from the results database.
627 # There are other views available.
629 if args.metric == "throughput":
631 stc.perform("QueryResult",
633 "DatabaseConnectionString":
636 ("RFC2544ThroughputTestResultDetailed"
639 # The returns the "RFC2544BacktoBackTestResultDetailedSummaryView"
640 # table view from the results database.
641 # There are other views available.
642 elif args.metric == "backtoback":
644 stc.perform("QueryResult",
646 "DatabaseConnectionString":
649 ("RFC2544Back2BackTestResultDetailed"
652 # The returns the "RFC2544LatencyTestResultDetailedSummaryView"
653 # table view from the results database.
654 # There are other views available.
655 elif args.metric == "latency":
657 stc.perform("QueryResult",
659 "DatabaseConnectionString":
662 ("RFC2544LatencyTestResultDetailed"
665 # The returns the "RFC2544FrameLossTestResultDetailedSummaryView"
666 # table view from the results database.
667 # There are other views available.
668 elif args.metric == "frameloss":
670 stc.perform("QueryResult",
672 "DatabaseConnectionString":
675 ("RFC2544FrameLossTestResultDetailed"
678 _LOGGER.debug("resultsdict[\"Columns\"]: %s",
679 resultsdict["Columns"])
680 _LOGGER.debug("resultsdict[\"Output\"]: %s", resultsdict["Output"])
681 _LOGGER.debug("Result paths: %s",
682 stc.perform("GetTestResultSettingPaths"))
684 # Write results to csv
685 _LOGGER.debug("Writing CSV file to results directory %s",
687 write_query_results_to_csv(
688 args.results_dir, args.csv_results_file_prefix, resultsdict)
690 except RuntimeError as e:
694 _LOGGER.debug("Destroy session on lab server")
697 _LOGGER.info("Test complete!")
699 if __name__ == "__main__":