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
29 _LOGGER = logging.getLogger(__name__)
33 """Create the directory as specified in path """
34 if not os.path.exists(path):
38 _LOGGER.error("Failed to create directory %s: %s", path, str(ex))
42 def write_histogram_to_csv(results_path, csv_results_file_prefix,
44 """ Write the results of the query to the CSV """
45 filec = os.path.join(results_path, csv_results_file_prefix + ".csv")
46 with open(filec, "wb") as result_file:
48 result_file.write(str(key) + "\n")
49 result_file.write(str(ranges) + "\n")
50 result_file.write(str(counts[key]) + "\n")
53 def write_query_results_to_csv(results_path, csv_results_file_prefix,
55 """ Write the results of the query to the CSV """
56 create_dir(results_path)
57 filec = os.path.join(results_path, csv_results_file_prefix + ".csv")
58 with open(filec, "wb") as result_file:
59 result_file.write(query_results["Columns"].replace(" ", ",") + "\n")
60 for row in (query_results["Output"].replace("} {", ",").
61 replace("{", "").replace("}", "").split(",")):
62 result_file.write(row.replace(" ", ",") + "\n")
65 def positive_int(value):
66 """ Positive Integer type for Arguments """
69 raise argparse.ArgumentTypeError(
70 "%s is an invalid positive int value" % value)
74 def percent_float(value):
75 """ Floating type for Arguments """
77 if pvalue < 0.0 or pvalue > 100.0:
78 raise argparse.ArgumentTypeError(
79 "%s not in range [0.0, 100.0]" % pvalue)
83 # pylint: disable=too-many-branches, too-many-statements, too-many-locals
85 """ Read the arguments, Invoke Test and Return the results"""
86 parser = argparse.ArgumentParser()
88 required_named = parser.add_argument_group("required named arguments")
89 required_named.add_argument("--lab_server_addr",
91 help=("The IP address of the"
92 "Spirent Lab Server"),
93 dest="lab_server_addr")
94 required_named.add_argument("--license_server_addr",
96 help=("The IP address of the Spirent"
98 dest="license_server_addr")
99 required_named.add_argument("--east_chassis_addr",
101 help=("The TestCenter chassis IP address to"
102 "use for the east test port"),
103 dest="east_chassis_addr")
104 required_named.add_argument("--east_slot_num",
107 help=("The TestCenter slot number to"
108 "use for the east test port"),
109 dest="east_slot_num")
110 required_named.add_argument("--east_port_num",
113 help=("The TestCenter port number to use"
114 "for the east test port"),
115 dest="east_port_num")
116 required_named.add_argument("--west_chassis_addr",
118 help=("The TestCenter chassis IP address"
119 "to use for the west test port"),
120 dest="west_chassis_addr")
121 required_named.add_argument("--west_slot_num",
124 help=("The TestCenter slot number to use"
125 "for the west test port"),
126 dest="west_slot_num")
127 required_named.add_argument("--west_port_num",
130 help=("The TestCenter port number to"
131 "use for the west test port"),
132 dest="west_port_num")
133 # Optional parameters
134 optional_named = parser.add_argument_group("optional named arguments")
135 optional_named.add_argument("--metric",
137 help=("One among - throughput, latency,\
138 backtoback and frameloss"),
139 choices=["throughput", "latency",
140 "backtoback", "frameloss"],
141 default="throughput",
143 optional_named.add_argument("--test_session_name",
145 default="RFC2544 East-West Throughput",
146 help=("The friendly name to identify"
147 "the Spirent Lab Server test session"),
148 dest="test_session_name")
150 optional_named.add_argument("--test_user_name",
152 default="RFC2544 East-West User",
153 help=("The friendly name to identify the"
154 "Spirent Lab Server test user"),
155 dest="test_user_name")
156 optional_named.add_argument("--results_dir",
159 help="The directory to copy results to",
161 optional_named.add_argument("--vsperf_results_dir",
164 help="The directory to copy results to",
165 dest="vsperf_results_dir")
166 optional_named.add_argument("--csv_results_file_prefix",
168 default="Rfc2544Tput",
169 help="The prefix for the CSV results files",
170 dest="csv_results_file_prefix")
171 optional_named.add_argument("--num_trials",
175 help=("The number of trials to execute during"
178 optional_named.add_argument("--trial_duration_sec",
182 help=("The duration of each trial executed"
184 dest="trial_duration_sec")
185 optional_named.add_argument("--traffic_pattern",
187 choices=["BACKBONE", "MESH", "PAIR"],
189 help="The traffic pattern between endpoints",
190 dest="traffic_pattern")
191 optional_named.add_argument("--traffic_custom",
194 help="The traffic pattern between endpoints",
195 dest="traffic_custom")
196 optional_named.add_argument("--search_mode",
198 choices=["COMBO", "STEP", "BINARY"],
200 help=("The search mode used to find the"
203 optional_named.add_argument("--learning_mode",
205 choices=["AUTO", "L2_LEARNING",
206 "L3_LEARNING", "NONE"],
208 help=("The learning mode used during the test,"
209 "default is 'NONE'"),
210 dest="learning_mode")
211 optional_named.add_argument("--rate_lower_limit_pct",
215 help=("The minimum percent line rate that"
216 "will be used during the test"),
217 dest="rate_lower_limit_pct")
218 optional_named.add_argument("--rate_upper_limit_pct",
222 help=("The maximum percent line rate that"
223 "will be used during the test"),
224 dest="rate_upper_limit_pct")
225 optional_named.add_argument("--rate_initial_pct",
229 help=("If Search Mode is BINARY, the percent"
230 "line rate that will be used at the"
231 "start of the test"),
232 dest="rate_initial_pct")
233 optional_named.add_argument("--rate_step_pct",
237 help=("If SearchMode is STEP, the percent"
238 "load increase per step"),
239 dest="rate_step_pct")
240 optional_named.add_argument("--resolution_pct",
244 help=("The minimum percentage of load"
245 "adjustment between iterations"),
246 dest="resolution_pct")
247 optional_named.add_argument("--frame_size_list",
248 type=lambda s: [int(item)
249 for item in s.split(',')],
252 help="A comma-delimited list of frame sizes",
253 dest="frame_size_list")
254 optional_named.add_argument("--acceptable_frame_loss_pct",
258 help=("The maximum acceptable frame loss"
259 "percent in any iteration"),
260 dest="acceptable_frame_loss_pct")
261 optional_named.add_argument("--east_intf_addr",
263 default="192.85.1.3",
264 help=("The address to assign to the first"
265 "emulated device interface on the first"
267 dest="east_intf_addr")
268 optional_named.add_argument("--east_intf_gateway_addr",
270 default="192.85.1.53",
271 help=("The gateway address to assign to the"
272 "first emulated device interface on the"
274 dest="east_intf_gateway_addr")
275 optional_named.add_argument("--west_intf_addr",
277 default="192.85.1.53",
278 help=("The address to assign to the first"
279 "emulated device interface on the"
281 dest="west_intf_addr")
282 optional_named.add_argument("--west_intf_gateway_addr",
284 default="192.85.1.53",
285 help=("The gateway address to assign to"
286 "the first emulated device interface"
287 "on the first west port"),
288 dest="west_intf_gateway_addr")
289 optional_named.add_argument("--latency_histogram",
292 help="latency histogram is required in output?",
293 dest="latency_histogram")
294 parser.add_argument("-v",
298 help="More output during operation when present",
301 args = parser.parse_args()
304 _LOGGER.debug("Creating results directory")
305 create_dir(args.results_dir)
307 session_name = args.test_session_name
308 user_name = args.test_user_name
309 # pylint: disable=import-error
311 # Load Spirent REST Library
312 from stcrestclient import stchttp
314 stc = stchttp.StcHttp(args.lab_server_addr)
315 session_id = stc.new_session(user_name, session_name)
316 stc.join_session(session_id)
317 except RuntimeError as err:
321 # Get STC system info.
322 tx_port_loc = "//%s/%s/%s" % (args.east_chassis_addr,
325 rx_port_loc = "//%s/%s/%s" % (args.west_chassis_addr,
329 # Retrieve and display the server information
331 _LOGGER.debug("SpirentTestCenter system version: %s",
332 stc.get("system1", "version"))
334 # pylint: disable=too-many-nested-blocks
339 _LOGGER.debug("Bring up license server")
340 license_mgr = stc.get("system1", "children-licenseservermanager")
342 _LOGGER.debug("license_mgr = %s", license_mgr)
343 stc.create("LicenseServer", under=license_mgr, attributes={
344 "server": args.license_server_addr})
346 # Create the root project object
348 _LOGGER.debug("Creating project ...")
349 project = stc.get("System1", "children-Project")
351 # Configure any custom traffic parameters
352 if args.traffic_custom == "cont":
354 _LOGGER.debug("Configure Continuous Traffic")
355 stc.create("ContinuousTestConfig", under=project)
359 _LOGGER.debug("Creating ports ...")
360 east_chassis_port = stc.create('port', project)
362 _LOGGER.debug("Configuring TX port ...")
363 stc.config(east_chassis_port, {'location': tx_port_loc})
364 port_list.append(east_chassis_port)
366 west_chassis_port = stc.create('port', project)
368 _LOGGER.debug("Configuring RX port ...")
369 stc.config(west_chassis_port, {'location': rx_port_loc})
370 port_list.append(west_chassis_port)
372 # Create emulated genparam for east port
373 east_device_gen_params = stc.create("EmulatedDeviceGenParams",
377 # Create the DeviceGenEthIIIfParams object
378 stc.create("DeviceGenEthIIIfParams",
379 under=east_device_gen_params,
380 attributes={'UseDefaultPhyMac':True})
382 # Configuring Ipv4 interfaces
383 stc.create("DeviceGenIpv4IfParams",
384 under=east_device_gen_params,
385 attributes={"Addr": args.east_intf_addr,
386 "Gateway": args.east_intf_gateway_addr})
387 # Create Devices using the Device Wizard
388 device_gen_config = stc.perform("DeviceGenConfigExpand",
389 params={"DeleteExisting": "No",
391 east_device_gen_params})
392 # Append to the device list
393 device_list.append(device_gen_config['ReturnList'])
395 # Create emulated genparam for west port
396 west_device_gen_params = stc.create("EmulatedDeviceGenParams",
400 # Create the DeviceGenEthIIIfParams object
401 stc.create("DeviceGenEthIIIfParams",
402 under=west_device_gen_params,
403 attributes={'UseDefaultPhyMac':True})
405 # Configuring Ipv4 interfaces
406 stc.create("DeviceGenIpv4IfParams",
407 under=west_device_gen_params,
408 attributes={"Addr": args.west_intf_addr,
409 "Gateway": args.west_intf_gateway_addr})
410 # Create Devices using the Device Wizard
411 device_gen_config = stc.perform("DeviceGenConfigExpand",
412 params={"DeleteExisting": "No",
414 west_device_gen_params})
415 # Append to the device list
416 device_list.append(device_gen_config['ReturnList'])
418 _LOGGER.debug(device_list)
420 # Configure Histogram
421 if args.latency_histogram:
422 # Generic Configuration
423 histResOptions = stc.get("project1", 'children-ResultOptions')
424 stc.config(histResOptions, {'ResultViewMode': 'HISTOGRAM'})
425 # East Port Configuration
426 histAnaEast = stc.get(east_chassis_port, 'children-Analyzer')
427 histAnaEastConfig = stc.get(histAnaEast, 'children-AnalyzerConfig')
428 stc.config(histAnaEastConfig, {'HistogramMode': 'LATENCY'})
429 eLatHist = stc.get(histAnaEastConfig, 'children-LatencyHistogram')
430 stc.config(eLatHist, {'ConfigMode': 'CONFIG_LIMIT_MODE',
431 'BucketSizeUnit': 'ten_nanoseconds',
433 'DistributionMode': 'CENTERED_MODE'})
434 # West Port Configuration
435 histAnaWest = stc.get(west_chassis_port, 'children-Analyzer')
436 histAnaWestConfig = stc.get(histAnaWest, 'children-AnalyzerConfig')
437 stc.config(histAnaWestConfig, {'HistogramMode': 'LATENCY'})
438 wLatHist = stc.get(histAnaWestConfig, 'children-LatencyHistogram')
439 stc.config(wLatHist, {'ConfigMode': 'CONFIG_LIMIT_MODE',
440 'BucketSizeUnit': 'ten_nanoseconds',
442 'DistributionMode': 'CENTERED_MODE'})
443 gBucketSizeList = stc.get(wLatHist, 'BucketSizeList')
444 # gLimitSizeList = stc.get(wLatHist, 'LimitList')
446 # Create the RFC 2544 'metric test
447 if args.metric == "throughput":
449 _LOGGER.debug("Set up the RFC2544 throughput test...")
450 stc.perform("Rfc2544SetupThroughputTestCommand",
451 params={"AcceptableFrameLoss":
452 args.acceptable_frame_loss_pct,
453 "Duration": args.trial_duration_sec,
454 "FrameSizeList": args.frame_size_list,
455 "LearningMode": args.learning_mode,
456 "NumOfTrials": args.num_trials,
457 "RateInitial": args.rate_initial_pct,
458 "RateLowerLimit": args.rate_lower_limit_pct,
459 "RateStep": args.rate_step_pct,
460 "RateUpperLimit": args.rate_upper_limit_pct,
461 "Resolution": args.resolution_pct,
462 "SearchMode": args.search_mode,
463 "TrafficPattern": args.traffic_pattern})
464 elif args.metric == "backtoback":
465 stc.perform("Rfc2544SetupBackToBackTestCommand",
466 params={"AcceptableFrameLoss":
467 args.acceptable_frame_loss_pct,
468 "Duration": args.trial_duration_sec,
469 "FrameSizeList": args.frame_size_list,
470 "LearningMode": args.learning_mode,
471 "LatencyType": args.latency_type,
472 "NumOfTrials": args.num_trials,
473 "RateInitial": args.rate_initial_pct,
474 "RateLowerLimit": args.rate_lower_limit_pct,
475 "RateStep": args.rate_step_pct,
476 "RateUpperLimit": args.rate_upper_limit_pct,
477 "Resolution": args.resolution_pct,
478 "SearchMode": args.search_mode,
479 "TrafficPattern": args.traffic_pattern})
480 elif args.metric == "frameloss":
481 stc.perform("Rfc2544SetupFrameLossTestCommand",
482 params={"AcceptableFrameLoss":
483 args.acceptable_frame_loss_pct,
484 "Duration": args.trial_duration_sec,
485 "FrameSizeList": args.frame_size_list,
486 "LearningMode": args.learning_mode,
487 "LatencyType": args.latency_type,
488 "NumOfTrials": args.num_trials,
489 "RateInitial": args.rate_initial_pct,
490 "RateLowerLimit": args.rate_lower_limit_pct,
491 "RateStep": args.rate_step_pct,
492 "RateUpperLimit": args.rate_upper_limit_pct,
493 "Resolution": args.resolution_pct,
494 "SearchMode": args.search_mode,
495 "TrafficPattern": args.traffic_pattern})
496 elif args.metric == "latency":
497 stc.perform("Rfc2544SetupLatencyTestCommand",
498 params={"AcceptableFrameLoss":
499 args.acceptable_frame_loss_pct,
500 "Duration": args.trial_duration_sec,
501 "FrameSizeList": args.frame_size_list,
502 "LearningMode": args.learning_mode,
503 "LatencyType": args.latency_type,
504 "NumOfTrials": args.num_trials,
505 "RateInitial": args.rate_initial_pct,
506 "RateLowerLimit": args.rate_lower_limit_pct,
507 "RateStep": args.rate_step_pct,
508 "RateUpperLimit": args.rate_upper_limit_pct,
509 "Resolution": args.resolution_pct,
510 "SearchMode": args.search_mode,
511 "TrafficPattern": args.traffic_pattern})
513 # Save the configuration
514 stc.perform("SaveToTcc", params={"Filename": "2544.tcc"})
515 # Connect to the hardware...
516 stc.perform("AttachPorts", params={"portList": stc.get(
517 "system1.project", "children-port"), "autoConnect": "TRUE"})
518 # Apply configuration.
520 _LOGGER.debug("Apply configuration...")
524 _LOGGER.debug("Starting the sequencer...")
525 stc.perform("SequencerStart")
527 # Wait for sequencer to finish
529 "Starting test... Please wait for the test to complete...")
530 stc.wait_until_complete()
531 _LOGGER.info("The test has completed... Saving results...")
533 # Determine what the results database filename is...
534 lab_server_resultsdb = stc.get(
535 "system1.project.TestResultSetting", "CurrentResultFileName")
538 _LOGGER.debug("The lab server results database is %s",
539 lab_server_resultsdb)
541 # Create Latency Histogram CSV file()
542 if args.latency_histogram:
543 hist_dict_counts = {}
544 for file_url in stc.files():
545 if '-FrameSize-' in file_url:
546 stc.download(file_url)
547 filename = file_url.split('/')[-1]
548 if os.path.exists(os.getcwd() + '/' + filename):
549 conn = sqlite3.connect(os.getcwd() + '/' + filename)
550 # cursor = conn.execute(
551 # 'select * from RxEotStreamResults')
552 # names = [desc[0] for desc in cursor.description]
553 counts = conn.execute("SELECT \
554 HistBin1Count, HistBin2Count,\
555 HistBin3Count, HistBin4Count,\
556 HistBin5Count, HistBin6Count,\
557 HistBin7Count, HistBin8Count,\
558 HistBin9Count, HistBin10Count,\
559 HistBin11Count, HistBin12Count,\
560 HistBin13Count, HistBin14Count, \
561 HistBin15Count, HistBin16Count \
562 from RxEotStreamResults")
563 strs = filename.split('-')
564 key = strs[strs.index('FrameSize')+1]
565 if key in hist_dict_counts:
566 hist_dict_counts[key] = [a+b for a, b in
567 zip(counts.fetchone(),
568 hist_dict_counts[key])]
570 hist_dict_counts[key] = counts.fetchone()
573 write_histogram_to_csv(args.vsperf_results_dir, 'Histogram',
577 stc.perform("CSSynchronizeFiles",
578 params={"DefaultDownloadDir": args.results_dir})
580 resultsdb = args.results_dir + \
581 lab_server_resultsdb.split("/Results")[1]
583 if not os.path.exists(resultsdb):
584 resultsdb = lab_server_resultsdb
585 _LOGGER.info("Failed to create the local summary DB File, using"
586 " the remote DB file instead.")
589 "The local summary DB file has been saved to %s", resultsdb)
591 # The returns the "RFC2544ThroughputTestResultDetailedSummaryView"
592 # table view from the results database.
593 # There are other views available.
595 if args.metric == "throughput":
597 stc.perform("QueryResult",
599 "DatabaseConnectionString":
602 ("RFC2544ThroughputTestResultDetailed"
605 # The returns the "RFC2544BacktoBackTestResultDetailedSummaryView"
606 # table view from the results database.
607 # There are other views available.
608 elif args.metric == "backtoback":
610 stc.perform("QueryResult",
612 "DatabaseConnectionString":
615 ("RFC2544Back2BackTestResultDetailed"
618 # The returns the "RFC2544LatencyTestResultDetailedSummaryView"
619 # table view from the results database.
620 # There are other views available.
621 elif args.metric == "latency":
623 stc.perform("QueryResult",
625 "DatabaseConnectionString":
628 ("RFC2544LatencyTestResultDetailed"
631 # The returns the "RFC2544FrameLossTestResultDetailedSummaryView"
632 # table view from the results database.
633 # There are other views available.
634 elif args.metric == "frameloss":
636 stc.perform("QueryResult",
638 "DatabaseConnectionString":
641 ("RFC2544FrameLossTestResultDetailed"
644 _LOGGER.debug("resultsdict[\"Columns\"]: %s",
645 resultsdict["Columns"])
646 _LOGGER.debug("resultsdict[\"Output\"]: %s", resultsdict["Output"])
647 _LOGGER.debug("Result paths: %s",
648 stc.perform("GetTestResultSettingPaths"))
650 # Write results to csv
651 _LOGGER.debug("Writing CSV file to results directory %s",
653 write_query_results_to_csv(
654 args.results_dir, args.csv_results_file_prefix, resultsdict)
656 except RuntimeError as e:
660 _LOGGER.debug("Destroy session on lab server")
663 _LOGGER.info("Test complete!")
665 if __name__ == "__main__":