pkt_gen: Testcenter traffic generation using STC-REST API -Import Issue.
[vswitchperf.git] / tools / pkt_gen / testcenter / testcenter-rfc2544-rest.py
1 # Copyright 2016 Spirent Communications.
2 #
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
6 #
7 #   http://www.apache.org/licenses/LICENSE-2.0
8 #
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.
14
15 '''
16 @author Spirent Communications
17
18 This test automates the RFC2544 tests using the Spirent
19 TestCenter REST APIs. This test supports Python 3.4
20
21 '''
22 import argparse
23 import logging
24 import os
25
26
27 logger = logging.getLogger(__name__)
28
29
30 def create_dir(path):
31     """Create the directory as specified in path """
32     if not os.path.exists(path):
33         try:
34             os.makedirs(path)
35         except OSError as e:
36             logger.error("Failed to create directory %s: %s", path, str(e))
37             raise
38
39
40 def write_query_results_to_csv(results_path, csv_results_file_prefix,
41                                query_results):
42     """ Write the results of the query to the CSV """
43     create_dir(results_path)
44     filec = os.path.join(results_path, csv_results_file_prefix + ".csv")
45     with open(filec, "wb") as f:
46         f.write(query_results["Columns"].replace(" ", ",") + "\n")
47         for row in (query_results["Output"].replace("} {", ",").
48                     replace("{", "").replace("}", "").split(",")):
49             f.write(row.replace(" ", ",") + "\n")
50
51
52 def positive_int(value):
53     """ Positive Integer type for Arguments """
54     ivalue = int(value)
55     if ivalue <= 0:
56         raise argparse.ArgumentTypeError(
57             "%s is an invalid positive int value" % value)
58     return ivalue
59
60
61 def percent_float(value):
62     """ Floating type for Arguments """
63     pvalue = float(value)
64     if pvalue < 0.0 or pvalue > 100.0:
65         raise argparse.ArgumentTypeError(
66             "%s not in range [0.0, 100.0]" % pvalue)
67     return pvalue
68
69
70 def main():
71     """ Read the arguments, Invoke Test and Return the results"""
72     parser = argparse.ArgumentParser()
73     # Required parameters
74     required_named = parser.add_argument_group("required named arguments")
75     required_named.add_argument("--lab_server_addr",
76                                 required=True,
77                                 help=("The IP address of the"
78                                       "Spirent Lab Server"),
79                                 dest="lab_server_addr")
80     required_named.add_argument("--license_server_addr",
81                                 required=True,
82                                 help=("The IP address of the Spirent"
83                                       "License Server"),
84                                 dest="license_server_addr")
85     required_named.add_argument("--east_chassis_addr",
86                                 required=True,
87                                 help=("The TestCenter chassis IP address to"
88                                       "use for the east test port"),
89                                 dest="east_chassis_addr")
90     required_named.add_argument("--east_slot_num",
91                                 type=positive_int,
92                                 required=True,
93                                 help=("The TestCenter slot number to"
94                                       "use for the east test port"),
95                                 dest="east_slot_num")
96     required_named.add_argument("--east_port_num",
97                                 type=positive_int,
98                                 required=True,
99                                 help=("The TestCenter port number to use"
100                                       "for the east test port"),
101                                 dest="east_port_num")
102     required_named.add_argument("--west_chassis_addr",
103                                 required=True,
104                                 help=("The TestCenter chassis IP address"
105                                       "to use for the west test port"),
106                                 dest="west_chassis_addr")
107     required_named.add_argument("--west_slot_num",
108                                 type=positive_int,
109                                 required=True,
110                                 help=("The TestCenter slot number to use"
111                                       "for the west test port"),
112                                 dest="west_slot_num")
113     required_named.add_argument("--west_port_num",
114                                 type=positive_int,
115                                 required=True,
116                                 help=("The TestCenter port number to"
117                                       "use for the west test port"),
118                                 dest="west_port_num")
119     # Optional parameters
120     optional_named = parser.add_argument_group("optional named arguments")
121     optional_named.add_argument("--metric",
122                                 required=False,
123                                 help=("One among - throughput, latency,\
124                                       backtoback and frameloss"),
125                                 choices=["throughput", "latency",
126                                          "backtoback", "frameloss"],
127                                 default="throughput",
128                                 dest="metric")
129     optional_named.add_argument("--test_session_name",
130                                 required=False,
131                                 default="RFC2544 East-West Throughput",
132                                 help=("The friendly name to identify"
133                                       "the Spirent Lab Server test session"),
134                                 dest="test_session_name")
135
136     optional_named.add_argument("--test_user_name",
137                                 required=False,
138                                 default="RFC2544 East-West User",
139                                 help=("The friendly name to identify the"
140                                       "Spirent Lab Server test user"),
141                                 dest="test_user_name")
142     optional_named.add_argument("--results_dir",
143                                 required=False,
144                                 default="./Results",
145                                 help="The directory to copy results to",
146                                 dest="results_dir")
147     optional_named.add_argument("--csv_results_file_prefix",
148                                 required=False,
149                                 default="Rfc2544Tput",
150                                 help="The prefix for the CSV results files",
151                                 dest="csv_results_file_prefix")
152     optional_named.add_argument("--num_trials",
153                                 type=positive_int,
154                                 required=False,
155                                 default=1,
156                                 help=("The number of trials to execute during"
157                                       "the test"),
158                                 dest="num_trials")
159     optional_named.add_argument("--trial_duration_sec",
160                                 type=positive_int,
161                                 required=False,
162                                 default=60,
163                                 help=("The duration of each trial executed"
164                                       "during the test"),
165                                 dest="trial_duration_sec")
166     optional_named.add_argument("--traffic_pattern",
167                                 required=False,
168                                 choices=["BACKBONE", "MESH", "PAIR"],
169                                 default="PAIR",
170                                 help="The traffic pattern between endpoints",
171                                 dest="traffic_pattern")
172     optional_named.add_argument("--search_mode",
173                                 required=False,
174                                 choices=["COMBO", "STEP", "BINARY"],
175                                 default="BINARY",
176                                 help=("The search mode used to find the"
177                                       "throughput rate"),
178                                 dest="search_mode")
179     optional_named.add_argument("--learning_mode",
180                                 required=False,
181                                 choices=["AUTO", "L2_LEARNING",
182                                          "L3_LEARNING", "NONE"],
183                                 default="AUTO",
184                                 help=("The learning mode used during the test,"
185                                       "default is 'NONE'"),
186                                 dest="learning_mode")
187     optional_named.add_argument("--rate_lower_limit_pct",
188                                 type=percent_float,
189                                 required=False,
190                                 default=1.0,
191                                 help=("The minimum percent line rate that"
192                                       "will be used during the test"),
193                                 dest="rate_lower_limit_pct")
194     optional_named.add_argument("--rate_upper_limit_pct",
195                                 type=percent_float,
196                                 required=False,
197                                 default=99.0,
198                                 help=("The maximum percent line rate that"
199                                       "will be used during the test"),
200                                 dest="rate_upper_limit_pct")
201     optional_named.add_argument("--rate_initial_pct",
202                                 type=percent_float,
203                                 required=False,
204                                 default=99.0,
205                                 help=("If Search Mode is BINARY, the percent"
206                                       "line rate that will be used at the"
207                                       "start of the test"),
208                                 dest="rate_initial_pct")
209     optional_named.add_argument("--rate_step_pct",
210                                 type=percent_float,
211                                 required=False,
212                                 default=10.0,
213                                 help=("If SearchMode is STEP, the percent"
214                                       "load increase per step"),
215                                 dest="rate_step_pct")
216     optional_named.add_argument("--resolution_pct",
217                                 type=percent_float,
218                                 required=False,
219                                 default=1.0,
220                                 help=("The minimum percentage of load"
221                                       "adjustment between iterations"),
222                                 dest="resolution_pct")
223     optional_named.add_argument("--frame_size_list",
224                                 type=lambda s: [int(item)
225                                                 for item in s.split(',')],
226                                 required=False,
227                                 default=[256],
228                                 help="A comma-delimited list of frame sizes",
229                                 dest="frame_size_list")
230     optional_named.add_argument("--acceptable_frame_loss_pct",
231                                 type=percent_float,
232                                 required=False,
233                                 default=0.0,
234                                 help=("The maximum acceptable frame loss"
235                                       "percent in any iteration"),
236                                 dest="acceptable_frame_loss_pct")
237     optional_named.add_argument("--east_intf_addr",
238                                 required=False,
239                                 default="192.85.1.3",
240                                 help=("The address to assign to the first"
241                                       "emulated device interface on the first"
242                                       "east port"),
243                                 dest="east_intf_addr")
244     optional_named.add_argument("--east_intf_gateway_addr",
245                                 required=False,
246                                 default="192.85.1.53",
247                                 help=("The gateway address to assign to the"
248                                       "first emulated device interface on the"
249                                       "first east port"),
250                                 dest="east_intf_gateway_addr")
251     optional_named.add_argument("--west_intf_addr",
252                                 required=False,
253                                 default="192.85.1.53",
254                                 help=("The address to assign to the first"
255                                       "emulated device interface on the"
256                                       "first west port"),
257                                 dest="west_intf_addr")
258     optional_named.add_argument("--west_intf_gateway_addr",
259                                 required=False,
260                                 default="192.85.1.53",
261                                 help=("The gateway address to assign to"
262                                       "the first emulated device interface"
263                                       "on the first west port"),
264                                 dest="west_intf_gateway_addr")
265     parser.add_argument("-v",
266                         "--verbose",
267                         required=False,
268                         default=True,
269                         help="More output during operation when present",
270                         action="store_true",
271                         dest="verbose")
272     args = parser.parse_args()
273
274     if args.verbose:
275         logger.debug("Creating results directory")
276     create_dir(args.results_dir)
277
278     session_name = args.test_session_name
279     user_name = args.test_user_name
280
281     try:
282         # Load Spirent REST Library
283         from stcrestclient import stchttp
284
285         stc = stchttp.StcHttp(args.lab_server_addr)
286         session_id = stc.new_session(user_name, session_name)
287         stc.join_session(session_id)
288     except RuntimeError as e:
289         logger.error(e)
290         raise
291
292     # Get STC system info.
293     tx_port_loc = "//%s/%s/%s" % (args.east_chassis_addr,
294                                   args.east_slot_num,
295                                   args.east_port_num)
296     rx_port_loc = "//%s/%s/%s" % (args.west_chassis_addr,
297                                   args.west_slot_num,
298                                   args.west_port_num)
299
300     # Retrieve and display the server information
301     if args.verbose:
302         logger.debug("SpirentTestCenter system version: %s",
303                      stc.get("system1", "version"))
304
305     try:
306         device_list = []
307         port_list = []
308         if args.verbose:
309             logger.debug("Bring up license server")
310         license_mgr = stc.get("system1", "children-licenseservermanager")
311         if args.verbose:
312             logger.debug("license_mgr = %s", license_mgr)
313         stc.create("LicenseServer", under=license_mgr, attributes={
314                    "server": args.license_server_addr})
315
316         # Create the root project object
317         if args.verbose:
318             logger.debug("Creating project ...")
319         project = stc.get("System1", "children-Project")
320
321         # Create ports
322         if args.verbose:
323             logger.debug("Creating ports ...")
324         east_chassis_port = stc.create('port', project)
325         if args.verbose:
326             logger.debug("Configuring TX port ...")
327         stc.config(east_chassis_port, {'location': tx_port_loc})
328         port_list.append(east_chassis_port)
329
330         west_chassis_port = stc.create('port', project)
331         if args.verbose:
332             logger.debug("Configuring RX port ...")
333         stc.config(west_chassis_port, {'location': rx_port_loc})
334         port_list.append(west_chassis_port)
335
336         # Create emulated genparam for east port
337         east_device_gen_params = stc.create("EmulatedDeviceGenParams",
338                                             under=project,
339                                             attributes={"Port":
340                                                         east_chassis_port})
341         # Create the DeviceGenEthIIIfParams object
342         stc.create("DeviceGenEthIIIfParams",
343                    under=east_device_gen_params)
344         # Configuring Ipv4 interfaces
345         stc.create("DeviceGenIpv4IfParams",
346                    under=east_device_gen_params,
347                    attributes={"Addr": args.east_intf_addr,
348                                "Gateway": args.east_intf_gateway_addr})
349         # Create Devices using the Device Wizard
350         device_gen_config = stc.perform("DeviceGenConfigExpand",
351                                         params={"DeleteExisting": "No",
352                                                 "GenParams":
353                                                 east_device_gen_params})
354         # Append to the device list
355         device_list.append(device_gen_config['ReturnList'])
356
357         # Create emulated genparam for west port
358         west_device_gen_params = stc.create("EmulatedDeviceGenParams",
359                                             under=project,
360                                             attributes={"Port":
361                                                         west_chassis_port})
362         # Create the DeviceGenEthIIIfParams object
363         stc.create("DeviceGenEthIIIfParams",
364                    under=west_device_gen_params)
365         # Configuring Ipv4 interfaces
366         stc.create("DeviceGenIpv4IfParams",
367                    under=west_device_gen_params,
368                    attributes={"Addr": args.west_intf_addr,
369                                "Gateway": args.west_intf_gateway_addr})
370         # Create Devices using the Device Wizard
371         device_gen_config = stc.perform("DeviceGenConfigExpand",
372                                         params={"DeleteExisting": "No",
373                                                 "GenParams":
374                                                 west_device_gen_params})
375         # Append to the device list
376         device_list.append(device_gen_config['ReturnList'])
377         if args.verbose:
378             logger.debug(device_list)
379
380         # Create the RFC 2544 'metric test
381         if args.metric == "throughput":
382             if args.verbose:
383                 logger.debug("Set up the RFC2544 throughput test...")
384             stc.perform("Rfc2544SetupThroughputTestCommand",
385                         params={"AcceptableFrameLoss":
386                                 args.acceptable_frame_loss_pct,
387                                 "Duration": args.trial_duration_sec,
388                                 "FrameSizeList": args.frame_size_list,
389                                 "LearningMode": args.learning_mode,
390                                 "NumOfTrials": args.num_trials,
391                                 "RateInitial": args.rate_initial_pct,
392                                 "RateLowerLimit": args.rate_lower_limit_pct,
393                                 "RateStep": args.rate_step_pct,
394                                 "RateUpperLimit": args.rate_upper_limit_pct,
395                                 "Resolution": args.resolution_pct,
396                                 "SearchMode": args.search_mode,
397                                 "TrafficPattern": args.traffic_pattern})
398         elif args.metric == "backtoback":
399             stc.perform("Rfc2544SetupBackToBackTestCommand",
400                         params={"AcceptableFrameLoss":
401                                 args.acceptable_frame_loss_pct,
402                                 "Duration": args.trial_duration_sec,
403                                 "FrameSizeList": args.frame_size_list,
404                                 "LearningMode": args.learning_mode,
405                                 "LatencyType": args.latency_type,
406                                 "NumOfTrials": args.num_trials,
407                                 "RateInitial": args.rate_initial_pct,
408                                 "RateLowerLimit": args.rate_lower_limit_pct,
409                                 "RateStep": args.rate_step_pct,
410                                 "RateUpperLimit": args.rate_upper_limit_pct,
411                                 "Resolution": args.resolution_pct,
412                                 "SearchMode": args.search_mode,
413                                 "TrafficPattern": args.traffic_pattern})
414         elif args.metric == "frameloss":
415             stc.perform("Rfc2544SetupFrameLossTestCommand",
416                         params={"AcceptableFrameLoss":
417                                 args.acceptable_frame_loss_pct,
418                                 "Duration": args.trial_duration_sec,
419                                 "FrameSizeList": args.frame_size_list,
420                                 "LearningMode": args.learning_mode,
421                                 "LatencyType": args.latency_type,
422                                 "NumOfTrials": args.num_trials,
423                                 "RateInitial": args.rate_initial_pct,
424                                 "RateLowerLimit": args.rate_lower_limit_pct,
425                                 "RateStep": args.rate_step_pct,
426                                 "RateUpperLimit": args.rate_upper_limit_pct,
427                                 "Resolution": args.resolution_pct,
428                                 "SearchMode": args.search_mode,
429                                 "TrafficPattern": args.traffic_pattern})
430         elif args.metric == "latency":
431             stc.perform("Rfc2544SetupLatencyTestCommand",
432                         params={"AcceptableFrameLoss":
433                                 args.acceptable_frame_loss_pct,
434                                 "Duration": args.trial_duration_sec,
435                                 "FrameSizeList": args.frame_size_list,
436                                 "LearningMode": args.learning_mode,
437                                 "LatencyType": args.latency_type,
438                                 "NumOfTrials": args.num_trials,
439                                 "RateInitial": args.rate_initial_pct,
440                                 "RateLowerLimit": args.rate_lower_limit_pct,
441                                 "RateStep": args.rate_step_pct,
442                                 "RateUpperLimit": args.rate_upper_limit_pct,
443                                 "Resolution": args.resolution_pct,
444                                 "SearchMode": args.search_mode,
445                                 "TrafficPattern": args.traffic_pattern})
446
447         # Save the configuration
448         stc.perform("SaveToTcc", params={"Filename": "2544.tcc"})
449         # Connect to the hardware...
450         stc.perform("AttachPorts", params={"portList": stc.get(
451             "system1.project", "children-port"), "autoConnect": "TRUE"})
452         # Apply configuration.
453         if args.verbose:
454             logger.debug("Apply configuration...")
455         stc.apply()
456
457         if args.verbose:
458             logger.debug("Starting the sequencer...")
459         stc.perform("SequencerStart")
460
461         # Wait for sequencer to finish
462         logger.info(
463             "Starting test... Please wait for the test to complete...")
464         stc.wait_until_complete()
465         logger.info("The test has completed... Saving results...")
466
467         # Determine what the results database filename is...
468         lab_server_resultsdb = stc.get(
469             "system1.project.TestResultSetting", "CurrentResultFileName")
470
471         if args.verbose:
472             logger.debug("The lab server results database is %s",
473                          lab_server_resultsdb)
474
475         stc.perform("CSSynchronizeFiles",
476                     params={"DefaultDownloadDir": args.results_dir})
477
478         resultsdb = args.results_dir + \
479             lab_server_resultsdb.split("/Results")[1]
480
481         logger.info(
482             "The local summary DB file has been saved to %s", resultsdb)
483
484         # The returns the "RFC2544ThroughputTestResultDetailedSummaryView"
485         # table view from the results database.
486         # There are other views available.
487
488         if args.metric == "throughput":
489             resultsdict = (
490                 stc.perform("QueryResult",
491                             params={
492                                 "DatabaseConnectionString":
493                                 resultsdb,
494                                 "ResultPath":
495                                 ("RFC2544ThroughputTestResultDetailed"
496                                  "SummaryView")}))
497
498         # The returns the "RFC2544BacktoBackTestResultDetailedSummaryView"
499         # table view from the results database.
500         # There are other views available.
501         elif args.metric == "backtoback":
502             resultsdict = (
503                 stc.perform("QueryResult",
504                             params={
505                                 "DatabaseConnectionString":
506                                 resultsdb,
507                                 "ResultPath":
508                                 ("RFC2544Back2BackTestResultDetailed"
509                                  "SummaryView")}))
510
511         # The returns the "RFC2544LatencyTestResultDetailedSummaryView"
512         # table view from the results database.
513         # There are other views available.
514         elif args.metric == "latency":
515             resultsdict = (
516                 stc.perform("QueryResult",
517                             params={
518                                 "DatabaseConnectionString":
519                                 resultsdb,
520                                 "ResultPath":
521                                 ("RFC2544LatencyTestResultDetailed"
522                                  "SummaryView")}))
523
524         # The returns the "RFC2544FrameLossTestResultDetailedSummaryView"
525         # table view from the results database.
526         # There are other views available.
527         elif args.metric == "frameloss":
528             resultsdict = (
529                 stc.perform("QueryResult",
530                             params={
531                                 "DatabaseConnectionString":
532                                 resultsdb,
533                                 "ResultPath":
534                                 ("RFC2544FrameLossTestResultDetailed"
535                                  "SummaryView")}))
536         if args.verbose:
537             logger.debug("resultsdict[\"Columns\"]: %s",
538                          resultsdict["Columns"])
539             logger.debug("resultsdict[\"Output\"]: %s", resultsdict["Output"])
540             logger.debug("Result paths: %s",
541                          stc.perform("GetTestResultSettingPaths"))
542
543             # Write results to csv
544             logger.debug("Writing CSV file to results directory %s",
545                          args.results_dir)
546         write_query_results_to_csv(
547             args.results_dir, args.csv_results_file_prefix, resultsdict)
548
549     except RuntimeError as e:
550         logger.error(e)
551
552     if args.verbose:
553         logger.debug("Destroy session on lab server")
554     stc.end_session()
555
556     logger.info("Test complete!")
557
558 if __name__ == "__main__":
559     main()