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