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