1#!/usr/bin/env python3 2 3r""" 4This module provides functions which are useful to plug-ins call-point programs that wish to make external 5robot program calls. 6""" 7 8import imp 9import os 10import re 11import subprocess 12import sys 13import time 14 15import gen_cmd as gc 16import gen_misc as gm 17import gen_print as gp 18import gen_valid as gv 19 20base_path = ( 21 os.path.dirname(os.path.dirname(imp.find_module("gen_robot_print")[1])) 22 + os.sep 23) 24 25 26def init_robot_out_parms(extra_prefix=""): 27 r""" 28 Initialize robot output parms such as outputdir, output, etc. 29 30 This function will set global values for the following robot output parms. 31 32 outputdir, output, log, report, loglevel, consolecolors, consolemarkers 33 34 This function would typically be called prior to calling create_robot_cmd_string. 35 36 Description of argument(s): 37 extra_prefix An extra prefix to be appended to the default prefix for output file 38 names. 39 """ 40 41 gp.dprint_executing() 42 AUTOBOOT_OPENBMC_NICKNAME = gm.get_mod_global("AUTOBOOT_OPENBMC_NICKNAME") 43 44 # Set values for call to create_robot_cmd_string. 45 # Environment variable TMP_ROBOT_DIR_PATH can be set by the user to indicate that robot-generated output 46 # should initially be written to the specified temporary directory and then moved to the normal output 47 # location after completion. 48 outputdir = os.environ.get( 49 "TMP_ROBOT_DIR_PATH", 50 os.environ.get( 51 "STATUS_DIR_PATH", os.environ.get("HOME", ".") + "/status" 52 ), 53 ) 54 outputdir = gm.add_trailing_slash(outputdir) 55 seconds = time.time() 56 loc_time = time.localtime(seconds) 57 time_string = time.strftime("%y%m%d.%H%M%S", loc_time) 58 file_prefix = ( 59 AUTOBOOT_OPENBMC_NICKNAME + "." + extra_prefix + time_string + "." 60 ) 61 # Environment variable SAVE_STATUS_POLICY governs when robot-generated output files (e.g. the log.html) 62 # will be moved from TMP_ROBOT_DIR_PATH to FFDC_DIR_PATH. Valid values are "ALWAYS", "NEVER" and "FAIL". 63 SAVE_STATUS_POLICY = os.environ.get("SAVE_STATUS_POLICY", "ALWAYS") 64 if SAVE_STATUS_POLICY == "NEVER": 65 output = "NONE" 66 log = "NONE" 67 report = "NONE" 68 else: 69 output = file_prefix + "output.xml" 70 log = file_prefix + "log.html" 71 report = file_prefix + "report.html" 72 loglevel = "TRACE" 73 consolecolors = "off" 74 consolemarkers = "off" 75 76 # Make create_robot_cmd_string values global. 77 gm.set_mod_global(outputdir) 78 gm.set_mod_global(output) 79 gm.set_mod_global(log) 80 gm.set_mod_global(report) 81 gm.set_mod_global(loglevel) 82 gm.set_mod_global(consolecolors) 83 gm.set_mod_global(consolemarkers) 84 85 return ( 86 outputdir, 87 output, 88 log, 89 report, 90 loglevel, 91 consolecolors, 92 consolemarkers, 93 ) 94 95 96def init_robot_test_base_dir_path(): 97 r""" 98 Initialize and validate the environment variable, ROBOT_TEST_BASE_DIR_PATH and set corresponding global 99 variable ROBOT_TEST_RUNNING_FROM_SB. 100 101 If ROBOT_TEST_BASE_DIR_PATH is already set, this function will merely validate it. This function will 102 also set environment variable ROBOT_TEST_RUNNING_FROM_SB when ROBOT_TEST_BASE_DIR_PATH is not pre-set. 103 """ 104 105 # ROBOT_TEST_BASE_DIR_PATH will be set as follows: 106 # This function will determine whether we are running in a user sandbox or from a standard apolloxxx 107 # environment. 108 # - User sandbox: 109 # If there is a <developer's home dir>/git/openbmc-test-automation/, ROBOT_TEST_BASE_DIR_PATH will be 110 # set to that path. Otherwise, we set it to <program dir path>/git/openbmc-test-automation/ 111 # - Not in user sandbox: 112 # ROBOT_TEST_BASE_DIR_PATH will be set to <program dir path>/git/openbmc-test-automation/ 113 114 ROBOT_TEST_BASE_DIR_PATH = os.environ.get("ROBOT_TEST_BASE_DIR_PATH", "") 115 ROBOT_TEST_RUNNING_FROM_SB = int( 116 os.environ.get("ROBOT_TEST_RUNNING_FROM_SB", "0") 117 ) 118 if ROBOT_TEST_BASE_DIR_PATH == "": 119 # ROBOT_TEST_BASE_DIR_PATH was not set by user/caller. 120 AUTOIPL_VERSION = os.environ.get("AUTOIPL_VERSION", "") 121 if AUTOIPL_VERSION == "": 122 ROBOT_TEST_BASE_DIR_PATH = base_path 123 else: 124 suffix = "git/openbmc-test-automation/" 125 126 # Determine whether we're running out of a developer sandbox or simply out of an apolloxxx/bin 127 # path. 128 shell_rc, out_buf = gc.shell_cmd( 129 "dirname $(which gen_print.py)", 130 quiet=(not debug), 131 print_output=0, 132 ) 133 executable_base_dir_path = os.path.realpath(out_buf.rstrip()) + "/" 134 apollo_dir_path = ( 135 os.environ["AUTO_BASE_PATH"] + AUTOIPL_VERSION + "/bin/" 136 ) 137 developer_home_dir_path = re.sub( 138 "/sandbox.*", "", executable_base_dir_path 139 ) 140 developer_home_dir_path = gm.add_trailing_slash( 141 developer_home_dir_path 142 ) 143 gp.dprint_vars( 144 executable_base_dir_path, 145 developer_home_dir_path, 146 apollo_dir_path, 147 ) 148 149 ROBOT_TEST_RUNNING_FROM_SB = 0 150 if executable_base_dir_path != apollo_dir_path: 151 ROBOT_TEST_RUNNING_FROM_SB = 1 152 gp.dprint_vars(ROBOT_TEST_RUNNING_FROM_SB) 153 ROBOT_TEST_BASE_DIR_PATH = developer_home_dir_path + suffix 154 if not os.path.isdir(ROBOT_TEST_BASE_DIR_PATH): 155 gp.dprint_timen( 156 "NOTE: Sandbox directory " 157 + ROBOT_TEST_BASE_DIR_PATH 158 + " does not" 159 + " exist." 160 ) 161 # Fall back to the apollo dir path. 162 ROBOT_TEST_BASE_DIR_PATH = apollo_dir_path + suffix 163 else: 164 # Use to the apollo dir path. 165 ROBOT_TEST_BASE_DIR_PATH = apollo_dir_path + suffix 166 167 OBMC_TOOLS_BASE_DIR_PATH = ( 168 os.path.dirname(ROBOT_TEST_BASE_DIR_PATH.rstrip("/")) 169 + "/openbmc-tools/" 170 ) 171 OPENBMCTOOL_DIR_PATH = OBMC_TOOLS_BASE_DIR_PATH + "openbmctool/" 172 JSON_CHECKER_TOOLS_DIR_PATH = ( 173 OBMC_TOOLS_BASE_DIR_PATH + "expectedJsonChecker/" 174 ) 175 176 gv.valid_value(ROBOT_TEST_BASE_DIR_PATH) 177 gp.dprint_vars( 178 ROBOT_TEST_RUNNING_FROM_SB, 179 ROBOT_TEST_BASE_DIR_PATH, 180 OBMC_TOOLS_BASE_DIR_PATH, 181 OPENBMCTOOL_DIR_PATH, 182 JSON_CHECKER_TOOLS_DIR_PATH, 183 ) 184 gv.valid_dir_path(ROBOT_TEST_BASE_DIR_PATH) 185 186 ROBOT_TEST_BASE_DIR_PATH = gm.add_trailing_slash(ROBOT_TEST_BASE_DIR_PATH) 187 gm.set_mod_global(ROBOT_TEST_BASE_DIR_PATH) 188 os.environ["ROBOT_TEST_BASE_DIR_PATH"] = ROBOT_TEST_BASE_DIR_PATH 189 190 gm.set_mod_global(ROBOT_TEST_RUNNING_FROM_SB) 191 os.environ["ROBOT_TEST_RUNNING_FROM_SB"] = str(ROBOT_TEST_RUNNING_FROM_SB) 192 193 gm.set_mod_global(OBMC_TOOLS_BASE_DIR_PATH) 194 os.environ["OBMC_TOOLS_BASE_DIR_PATH"] = str(OBMC_TOOLS_BASE_DIR_PATH) 195 196 gm.set_mod_global(OPENBMCTOOL_DIR_PATH) 197 os.environ["OPENBMCTOOL_DIR_PATH"] = str(OPENBMCTOOL_DIR_PATH) 198 199 gm.set_mod_global(JSON_CHECKER_TOOLS_DIR_PATH) 200 os.environ["JSON_CHECKER_TOOLS_DIR_PATH"] = str( 201 JSON_CHECKER_TOOLS_DIR_PATH 202 ) 203 204 205raw_robot_file_search_path = ( 206 "${ROBOT_TEST_BASE_DIR_PATH}:" 207 + "${ROBOT_TEST_BASE_DIR_PATH}tests:${ROBOT_TEST_BASE_DIR_PATH}extended:" 208 + "${ROBOT_TEST_BASE_DIR_PATH}scratch:${PATH}" 209) 210 211 212def init_robot_file_path(robot_file_path): 213 r""" 214 Determine full path name for the file path passed in robot_file_path and return it. 215 216 If robot_file_path contains a fully qualified path name, this function will verify that the file exists. 217 If robot_file_path contains a relative path, this function will search for the file and set 218 robot_file_path so that it contains the absolute path to the robot file. This function will search for 219 the robot file using the raw_robot_file_search_path (defined above). Note that if 220 ROBOT_TEST_BASE_DIR_PATH is not set, this function will call init_robot_test_base_dir_path to set it. 221 222 Description of arguments: 223 robot_file_path The absolute or relative path to a robot file. 224 """ 225 226 gv.valid_value(robot_file_path) 227 228 try: 229 if ROBOT_TEST_BASE_DIR_PATH is NONE: 230 init_robot_test_base_dir_path() 231 except NameError: 232 init_robot_test_base_dir_path() 233 234 if not re.match(r".*\.(robot|py)$", robot_file_path): 235 # No suffix so we'll assign one of "\.robot". 236 robot_file_path = robot_file_path + ".robot" 237 238 abs_path = 0 239 if robot_file_path[0:1] == "/": 240 abs_path = 1 241 242 gp.dprint_vars(abs_path, robot_file_path) 243 244 if not abs_path: 245 cmd_buf = 'echo -n "' + raw_robot_file_search_path + '"' 246 shell_rc, out_buf = gc.shell_cmd( 247 cmd_buf, quiet=(not debug), print_output=0 248 ) 249 robot_file_search_paths = out_buf 250 gp.dprint_var(robot_file_search_paths) 251 robot_file_search_paths_list = robot_file_search_paths.split(":") 252 for search_path in robot_file_search_paths_list: 253 search_path = gm.add_trailing_slash(search_path) 254 candidate_file_path = search_path + robot_file_path 255 gp.dprint_var(candidate_file_path) 256 if os.path.isfile(candidate_file_path): 257 gp.dprint_timen("Found full path to " + robot_file_path + ".") 258 robot_file_path = candidate_file_path 259 break 260 261 gp.dprint_var(robot_file_path) 262 gv.valid_file_path(robot_file_path) 263 264 return robot_file_path 265 266 267def get_robot_parm_names(): 268 r""" 269 Return a list containing all of the long parm names (e.g. --outputdir) supported by the robot program. 270 Double dashes are not included in the names returned. 271 """ 272 273 cmd_buf = ( 274 "robot -h | egrep " 275 + "'^([ ]\\-[a-zA-Z0-9])?[ ]+--[a-zA-Z0-9]+[ ]+' | sed -re" 276 + " s'/.*\\-\\-//g' -e s'/ .*//g' | sort -u" 277 ) 278 shell_rc, out_buf = gc.shell_cmd(cmd_buf, quiet=1, print_output=0) 279 280 return out_buf.split("\n") 281 282 283def create_robot_cmd_string(robot_file_path, *parms): 284 r""" 285 Create a robot command string and return it. On failure, return an empty string. 286 287 Description of arguments: 288 robot_file_path The path to the robot file to be run. 289 parms The list of parms to be included in the command string. The name of each 290 variable in this list must be the same as the name of the corresponding 291 parm. This function figures out that name. This function is also able 292 to distinguish robot parms (e.g. --outputdir) from robot program parms 293 (all other parms which will be passed as "-v PARM_NAME:parm_value").. 294 295 Example: 296 297 The following call to this function... 298 cmd_buf = create_robot_cmd_string("tools/start_sol_console.robot", OPENBMC_HOST, quiet, test_mode, debug, 299 outputdir, output, log, report) 300 301 Would return a string something like this. 302 robot -v OPENBMC_HOST:beye6 -v quiet:0 -v test_mode:1 -v debug:1 303 --outputdir=/gsa/ausgsa/projects/a/status --output=beye6.OS_Console.output.xml 304 --log=beye6.OS_Console.log.html --report=beye6.OS_Console.report.html tools/start_sol_console.robot 305 """ 306 307 robot_file_path = init_robot_file_path(robot_file_path) 308 309 robot_parm_names = get_robot_parm_names() 310 311 robot_parm_list = [] 312 313 stack_frame = 2 314 ix = 2 315 for arg in parms: 316 parm = arg 317 parm = gm.quote_bash_parm(gm.escape_bash_quotes(str(parm))) 318 var_name = gp.get_arg_name(None, ix, stack_frame) 319 if var_name in robot_parm_names: 320 p_string = "--" + var_name + "=" + str(parm) 321 robot_parm_list.append(p_string) 322 else: 323 p_string = "-v " + var_name + ":" + str(parm) 324 robot_parm_list.append(p_string) 325 ix += 1 326 327 robot_cmd_buf = ( 328 "robot " + " ".join(robot_parm_list) + " " + robot_file_path 329 ) 330 331 return robot_cmd_buf 332 333 334# Global variables to aid in cleanup after running robot_cmd_fnc. 335gcr_last_robot_cmd_buf = "" 336gcr_last_robot_rc = 0 337 338 339def process_robot_output_files(robot_cmd_buf=None, robot_rc=None, gzip=None): 340 r""" 341 Process robot output files which can involve several operations: 342 - If the files are in a temporary location, using SAVE_STATUS_POLICY to decide whether to move them to a 343 permanent location or to delete them. 344 - Gzipping them. 345 346 Description of argument(s): 347 robot_cmd_buf The complete command string used to invoke robot. 348 robot_rc The return code from running the robot command string. 349 gzip Indicates whether robot-generated output should be gzipped. 350 """ 351 352 robot_cmd_buf = gm.dft(robot_cmd_buf, gcr_last_robot_cmd_buf) 353 robot_rc = gm.dft(robot_rc, gcr_last_robot_rc) 354 gzip = gm.dft(gzip, int(os.environ.get("GZIP_ROBOT", "1"))) 355 356 if robot_cmd_buf == "": 357 # This can legitimately occur if this function is called from an exit_function without the program 358 # having ever run robot_cmd_fnc. 359 return 360 361 SAVE_STATUS_POLICY = os.environ.get("SAVE_STATUS_POLICY", "ALWAYS") 362 gp.qprint_vars(SAVE_STATUS_POLICY) 363 364 # When SAVE_STATUS_POLICY is "NEVER" robot output files don't even get generated. 365 if SAVE_STATUS_POLICY == "NEVER": 366 return 367 368 # Compose file_list based on robot command buffer passed in. 369 robot_cmd_buf_dict = gc.parse_command_string(robot_cmd_buf) 370 outputdir = robot_cmd_buf_dict["outputdir"] 371 outputdir = gm.add_trailing_slash(outputdir) 372 file_list = ( 373 outputdir 374 + robot_cmd_buf_dict["output"] 375 + " " 376 + outputdir 377 + robot_cmd_buf_dict["log"] 378 + " " 379 + outputdir 380 + robot_cmd_buf_dict["report"] 381 ) 382 383 # Double checking that files are present. 384 shell_rc, out_buf = gc.shell_cmd( 385 "ls -1 " + file_list + " 2>/dev/null", show_err=0 386 ) 387 file_list = re.sub("\n", " ", out_buf.rstrip("\n")) 388 389 if file_list == "": 390 gp.qprint_timen( 391 "No robot output files were found in " + outputdir + "." 392 ) 393 return 394 gp.qprint_var(robot_rc, gp.hexa()) 395 if SAVE_STATUS_POLICY == "FAIL" and robot_rc == 0: 396 gp.qprint_timen( 397 "The call to robot produced no failures." 398 + " Deleting robot output files." 399 ) 400 gc.shell_cmd("rm -rf " + file_list) 401 return 402 403 if gzip: 404 gc.shell_cmd("gzip -f " + file_list) 405 # Update the values in file_list. 406 file_list = re.sub(" ", ".gz ", file_list) + ".gz" 407 408 # It TMP_ROBOT_DIR_PATH is set, it means the caller wanted the robot output initially directed to 409 # TMP_ROBOT_DIR_PATH but later moved to FFDC_DIR_PATH. Otherwise, we're done. 410 411 if os.environ.get("TMP_ROBOT_DIR_PATH", "") == "": 412 return 413 414 # We're directing these to the FFDC dir path so that they'll be subjected to FFDC cleanup. 415 target_dir_path = os.environ.get( 416 "FFDC_DIR_PATH", os.environ.get("HOME", ".") + "/ffdc" 417 ) 418 target_dir_path = gm.add_trailing_slash(target_dir_path) 419 420 targ_file_list = [ 421 re.sub(".*/", target_dir_path, x) for x in file_list.split(" ") 422 ] 423 424 gc.shell_cmd( 425 "mv " + file_list + " " + target_dir_path + " >/dev/null", time_out=600 426 ) 427 428 gp.qprint_timen("New robot log file locations:") 429 gp.qprintn("\n".join(targ_file_list)) 430 431 432def robot_cmd_fnc( 433 robot_cmd_buf, 434 robot_jail=os.environ.get("ROBOT_JAIL", ""), 435 quiet=None, 436 test_mode=0, 437): 438 r""" 439 Run the robot command string. 440 441 This function will set the various PATH variables correctly so that you are running the proper version of 442 all imported files, etc. 443 444 Description of argument(s): 445 robot_cmd_buf The complete robot command string. 446 robot_jail Indicates that this is to run in "robot jail" meaning without visibility 447 to any apolloxxx import files, programs, etc. 448 test_mode If test_mode is set, this function will not actually run the command. 449 """ 450 451 quiet = int(gm.dft(quiet, gp.get_stack_var("quiet", 0))) 452 gv.valid_value(robot_cmd_buf) 453 454 # Set global variables to aid in cleanup with process_robot_output_files. 455 global gcr_last_robot_cmd_buf 456 global gcr_last_robot_rc 457 gcr_last_robot_cmd_buf = robot_cmd_buf 458 459 # Get globals set by init_robot_test_base_dir_path(). 460 module = sys.modules["__main__"] 461 try: 462 ROBOT_TEST_BASE_DIR_PATH = getattr(module, "ROBOT_TEST_BASE_DIR_PATH") 463 except NameError: 464 init_robot_test_base_dir_path() 465 ROBOT_TEST_BASE_DIR_PATH = getattr(module, "ROBOT_TEST_BASE_DIR_PATH") 466 467 ROBOT_TEST_RUNNING_FROM_SB = gm.get_mod_global( 468 "ROBOT_TEST_RUNNING_FROM_SB" 469 ) 470 OPENBMCTOOL_DIR_PATH = gm.get_mod_global("OPENBMCTOOL_DIR_PATH") 471 472 if robot_jail == "": 473 if ROBOT_TEST_RUNNING_FROM_SB: 474 robot_jail = 0 475 else: 476 robot_jail = 1 477 478 robot_jail = int(robot_jail) 479 ROBOT_JAIL = os.environ.get("ROBOT_JAIL", "") 480 gp.dprint_vars( 481 ROBOT_TEST_BASE_DIR_PATH, 482 ROBOT_TEST_RUNNING_FROM_SB, 483 ROBOT_JAIL, 484 robot_jail, 485 ) 486 487 # Save PATH and PYTHONPATH to be restored later. 488 os.environ["SAVED_PYTHONPATH"] = os.environ.get("PYTHONPATH", "") 489 os.environ["SAVED_PATH"] = os.environ.get("PATH", "") 490 491 if robot_jail: 492 # Make sure required programs like python and robot can be found in the new restricted PATH. 493 required_programs = "python robot" 494 # It is expected that there will be a "python" program in the tool base bin path which is really a 495 # link to select_version. Ditto for "robot". Call each with the --print_only option to get the 496 # paths to the "real" programs. 497 cmd_buf = ( 498 "for program in " 499 + required_programs 500 + " ; do dirname $(${program} --print_only) ; done 2>/dev/null" 501 ) 502 rc, out_buf = gc.shell_cmd(cmd_buf, quiet=1, print_output=0) 503 PYTHONPATH = ROBOT_TEST_BASE_DIR_PATH + "lib" 504 NEW_PATH_LIST = [ROBOT_TEST_BASE_DIR_PATH + "bin"] 505 NEW_PATH_LIST.extend(list(set(out_buf.rstrip("\n").split("\n")))) 506 NEW_PATH_LIST.extend( 507 [ 508 "/usr/local/sbin", 509 "/usr/local/bin", 510 "/usr/sbin", 511 "/usr/bin", 512 "/sbin", 513 "/bin", 514 OPENBMCTOOL_DIR_PATH.rstrip("/"), 515 ] 516 ) 517 PATH = ":".join(NEW_PATH_LIST) 518 else: 519 PYTHONPATH = ( 520 os.environ.get("PYTHONPATH", "") 521 + ":" 522 + ROBOT_TEST_BASE_DIR_PATH 523 + "lib" 524 ) 525 PATH = ( 526 os.environ.get("PATH", "") 527 + ":" 528 + ROBOT_TEST_BASE_DIR_PATH 529 + "bin" 530 + ":" 531 + OPENBMCTOOL_DIR_PATH.rstrip("/") 532 ) 533 534 os.environ["PYTHONPATH"] = PYTHONPATH 535 os.environ["PATH"] = PATH 536 gp.dprint_vars(PATH, PYTHONPATH) 537 538 os.environ["FFDC_DIR_PATH_STYLE"] = os.environ.get( 539 "FFDC_DIR_PATH_STYLE", "1" 540 ) 541 gp.qpissuing(robot_cmd_buf, test_mode) 542 if test_mode: 543 os.environ["PATH"] = os.environ.get("SAVED_PATH", "") 544 os.environ["PYTHONPATH"] = os.environ.get("SAVED_PYTHONPATH", "") 545 return True 546 547 if quiet: 548 DEVNULL = open(os.devnull, "wb") 549 stdout = DEVNULL 550 else: 551 stdout = None 552 sub_proc = subprocess.Popen(robot_cmd_buf, stdout=stdout, shell=True) 553 sub_proc.communicate() 554 shell_rc = sub_proc.returncode 555 os.environ["PATH"] = os.environ.get("SAVED_PATH", "") 556 os.environ["PYTHONPATH"] = os.environ.get("SAVED_PYTHONPATH", "") 557 gcr_last_robot_rc = shell_rc 558 process_robot_output_files() 559 if shell_rc != 0: 560 gp.print_var(shell_rc, gp.hexa()) 561 return False 562 563 return True 564