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