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 gv.valid_value(ROBOT_TEST_BASE_DIR_PATH) 168 gp.dprint_vars( 169 ROBOT_TEST_RUNNING_FROM_SB, 170 ROBOT_TEST_BASE_DIR_PATH, 171 ) 172 gv.valid_dir_path(ROBOT_TEST_BASE_DIR_PATH) 173 174 ROBOT_TEST_BASE_DIR_PATH = gm.add_trailing_slash(ROBOT_TEST_BASE_DIR_PATH) 175 gm.set_mod_global(ROBOT_TEST_BASE_DIR_PATH) 176 os.environ["ROBOT_TEST_BASE_DIR_PATH"] = ROBOT_TEST_BASE_DIR_PATH 177 178 gm.set_mod_global(ROBOT_TEST_RUNNING_FROM_SB) 179 os.environ["ROBOT_TEST_RUNNING_FROM_SB"] = str(ROBOT_TEST_RUNNING_FROM_SB) 180 181 182raw_robot_file_search_path = ( 183 "${ROBOT_TEST_BASE_DIR_PATH}:" 184 + "${ROBOT_TEST_BASE_DIR_PATH}tests:${ROBOT_TEST_BASE_DIR_PATH}extended:" 185 + "${ROBOT_TEST_BASE_DIR_PATH}scratch:${PATH}" 186) 187 188 189def init_robot_file_path(robot_file_path): 190 r""" 191 Determine full path name for the file path passed in robot_file_path and return it. 192 193 If robot_file_path contains a fully qualified path name, this function will verify that the file exists. 194 If robot_file_path contains a relative path, this function will search for the file and set 195 robot_file_path so that it contains the absolute path to the robot file. This function will search for 196 the robot file using the raw_robot_file_search_path (defined above). Note that if 197 ROBOT_TEST_BASE_DIR_PATH is not set, this function will call init_robot_test_base_dir_path to set it. 198 199 Description of arguments: 200 robot_file_path The absolute or relative path to a robot file. 201 """ 202 203 gv.valid_value(robot_file_path) 204 205 try: 206 if ROBOT_TEST_BASE_DIR_PATH is NONE: 207 init_robot_test_base_dir_path() 208 except NameError: 209 init_robot_test_base_dir_path() 210 211 if not re.match(r".*\.(robot|py)$", robot_file_path): 212 # No suffix so we'll assign one of "\.robot". 213 robot_file_path = robot_file_path + ".robot" 214 215 abs_path = 0 216 if robot_file_path[0:1] == "/": 217 abs_path = 1 218 219 gp.dprint_vars(abs_path, robot_file_path) 220 221 if not abs_path: 222 cmd_buf = 'echo -n "' + raw_robot_file_search_path + '"' 223 shell_rc, out_buf = gc.shell_cmd( 224 cmd_buf, quiet=(not debug), print_output=0 225 ) 226 robot_file_search_paths = out_buf 227 gp.dprint_var(robot_file_search_paths) 228 robot_file_search_paths_list = robot_file_search_paths.split(":") 229 for search_path in robot_file_search_paths_list: 230 search_path = gm.add_trailing_slash(search_path) 231 candidate_file_path = search_path + robot_file_path 232 gp.dprint_var(candidate_file_path) 233 if os.path.isfile(candidate_file_path): 234 gp.dprint_timen("Found full path to " + robot_file_path + ".") 235 robot_file_path = candidate_file_path 236 break 237 238 gp.dprint_var(robot_file_path) 239 gv.valid_file_path(robot_file_path) 240 241 return robot_file_path 242 243 244def get_robot_parm_names(): 245 r""" 246 Return a list containing all of the long parm names (e.g. --outputdir) supported by the robot program. 247 Double dashes are not included in the names returned. 248 """ 249 250 cmd_buf = ( 251 "robot -h | egrep " 252 + "'^([ ]\\-[a-zA-Z0-9])?[ ]+--[a-zA-Z0-9]+[ ]+' | sed -re" 253 + " s'/.*\\-\\-//g' -e s'/ .*//g' | sort -u" 254 ) 255 shell_rc, out_buf = gc.shell_cmd(cmd_buf, quiet=1, print_output=0) 256 257 return out_buf.split("\n") 258 259 260def create_robot_cmd_string(robot_file_path, *parms): 261 r""" 262 Create a robot command string and return it. On failure, return an empty string. 263 264 Description of arguments: 265 robot_file_path The path to the robot file to be run. 266 parms The list of parms to be included in the command string. The name of each 267 variable in this list must be the same as the name of the corresponding 268 parm. This function figures out that name. This function is also able 269 to distinguish robot parms (e.g. --outputdir) from robot program parms 270 (all other parms which will be passed as "-v PARM_NAME:parm_value").. 271 272 Example: 273 274 The following call to this function... 275 cmd_buf = create_robot_cmd_string("tools/start_sol_console.robot", OPENBMC_HOST, quiet, test_mode, debug, 276 outputdir, output, log, report) 277 278 Would return a string something like this. 279 robot -v OPENBMC_HOST:beye6 -v quiet:0 -v test_mode:1 -v debug:1 280 --outputdir=/gsa/ausgsa/projects/a/status --output=beye6.OS_Console.output.xml 281 --log=beye6.OS_Console.log.html --report=beye6.OS_Console.report.html tools/start_sol_console.robot 282 """ 283 284 robot_file_path = init_robot_file_path(robot_file_path) 285 286 robot_parm_names = get_robot_parm_names() 287 288 robot_parm_list = [] 289 290 stack_frame = 2 291 ix = 2 292 for arg in parms: 293 parm = arg 294 parm = gm.quote_bash_parm(gm.escape_bash_quotes(str(parm))) 295 var_name = gp.get_arg_name(None, ix, stack_frame) 296 if var_name in robot_parm_names: 297 p_string = "--" + var_name + "=" + str(parm) 298 robot_parm_list.append(p_string) 299 else: 300 p_string = "-v " + var_name + ":" + str(parm) 301 robot_parm_list.append(p_string) 302 ix += 1 303 304 robot_cmd_buf = ( 305 "robot " + " ".join(robot_parm_list) + " " + robot_file_path 306 ) 307 308 return robot_cmd_buf 309 310 311# Global variables to aid in cleanup after running robot_cmd_fnc. 312gcr_last_robot_cmd_buf = "" 313gcr_last_robot_rc = 0 314 315 316def process_robot_output_files(robot_cmd_buf=None, robot_rc=None, gzip=None): 317 r""" 318 Process robot output files which can involve several operations: 319 - If the files are in a temporary location, using SAVE_STATUS_POLICY to decide whether to move them to a 320 permanent location or to delete them. 321 - Gzipping them. 322 323 Description of argument(s): 324 robot_cmd_buf The complete command string used to invoke robot. 325 robot_rc The return code from running the robot command string. 326 gzip Indicates whether robot-generated output should be gzipped. 327 """ 328 329 robot_cmd_buf = gm.dft(robot_cmd_buf, gcr_last_robot_cmd_buf) 330 robot_rc = gm.dft(robot_rc, gcr_last_robot_rc) 331 gzip = gm.dft(gzip, int(os.environ.get("GZIP_ROBOT", "1"))) 332 333 if robot_cmd_buf == "": 334 # This can legitimately occur if this function is called from an exit_function without the program 335 # having ever run robot_cmd_fnc. 336 return 337 338 SAVE_STATUS_POLICY = os.environ.get("SAVE_STATUS_POLICY", "ALWAYS") 339 gp.qprint_vars(SAVE_STATUS_POLICY) 340 341 # When SAVE_STATUS_POLICY is "NEVER" robot output files don't even get generated. 342 if SAVE_STATUS_POLICY == "NEVER": 343 return 344 345 # Compose file_list based on robot command buffer passed in. 346 robot_cmd_buf_dict = gc.parse_command_string(robot_cmd_buf) 347 outputdir = robot_cmd_buf_dict["outputdir"] 348 outputdir = gm.add_trailing_slash(outputdir) 349 file_list = ( 350 outputdir 351 + robot_cmd_buf_dict["output"] 352 + " " 353 + outputdir 354 + robot_cmd_buf_dict["log"] 355 + " " 356 + outputdir 357 + robot_cmd_buf_dict["report"] 358 ) 359 360 # Double checking that files are present. 361 shell_rc, out_buf = gc.shell_cmd( 362 "ls -1 " + file_list + " 2>/dev/null", show_err=0 363 ) 364 file_list = re.sub("\n", " ", out_buf.rstrip("\n")) 365 366 if file_list == "": 367 gp.qprint_timen( 368 "No robot output files were found in " + outputdir + "." 369 ) 370 return 371 gp.qprint_var(robot_rc, gp.hexa()) 372 if SAVE_STATUS_POLICY == "FAIL" and robot_rc == 0: 373 gp.qprint_timen( 374 "The call to robot produced no failures." 375 + " Deleting robot output files." 376 ) 377 gc.shell_cmd("rm -rf " + file_list) 378 return 379 380 if gzip: 381 gc.shell_cmd("gzip -f " + file_list) 382 # Update the values in file_list. 383 file_list = re.sub(" ", ".gz ", file_list) + ".gz" 384 385 # It TMP_ROBOT_DIR_PATH is set, it means the caller wanted the robot output initially directed to 386 # TMP_ROBOT_DIR_PATH but later moved to FFDC_DIR_PATH. Otherwise, we're done. 387 388 if os.environ.get("TMP_ROBOT_DIR_PATH", "") == "": 389 return 390 391 # We're directing these to the FFDC dir path so that they'll be subjected to FFDC cleanup. 392 target_dir_path = os.environ.get( 393 "FFDC_DIR_PATH", os.environ.get("HOME", ".") + "/ffdc" 394 ) 395 target_dir_path = gm.add_trailing_slash(target_dir_path) 396 397 targ_file_list = [ 398 re.sub(".*/", target_dir_path, x) for x in file_list.split(" ") 399 ] 400 401 gc.shell_cmd( 402 "mv " + file_list + " " + target_dir_path + " >/dev/null", time_out=600 403 ) 404 405 gp.qprint_timen("New robot log file locations:") 406 gp.qprintn("\n".join(targ_file_list)) 407 408 409def robot_cmd_fnc( 410 robot_cmd_buf, 411 robot_jail=os.environ.get("ROBOT_JAIL", ""), 412 quiet=None, 413 test_mode=0, 414): 415 r""" 416 Run the robot command string. 417 418 This function will set the various PATH variables correctly so that you are running the proper version of 419 all imported files, etc. 420 421 Description of argument(s): 422 robot_cmd_buf The complete robot command string. 423 robot_jail Indicates that this is to run in "robot jail" meaning without visibility 424 to any apolloxxx import files, programs, etc. 425 test_mode If test_mode is set, this function will not actually run the command. 426 """ 427 428 quiet = int(gm.dft(quiet, gp.get_stack_var("quiet", 0))) 429 gv.valid_value(robot_cmd_buf) 430 431 # Set global variables to aid in cleanup with process_robot_output_files. 432 global gcr_last_robot_cmd_buf 433 global gcr_last_robot_rc 434 gcr_last_robot_cmd_buf = robot_cmd_buf 435 436 # Get globals set by init_robot_test_base_dir_path(). 437 module = sys.modules["__main__"] 438 try: 439 ROBOT_TEST_BASE_DIR_PATH = getattr(module, "ROBOT_TEST_BASE_DIR_PATH") 440 except NameError: 441 init_robot_test_base_dir_path() 442 ROBOT_TEST_BASE_DIR_PATH = getattr(module, "ROBOT_TEST_BASE_DIR_PATH") 443 444 ROBOT_TEST_RUNNING_FROM_SB = gm.get_mod_global( 445 "ROBOT_TEST_RUNNING_FROM_SB" 446 ) 447 448 if robot_jail == "": 449 if ROBOT_TEST_RUNNING_FROM_SB: 450 robot_jail = 0 451 else: 452 robot_jail = 1 453 454 robot_jail = int(robot_jail) 455 ROBOT_JAIL = os.environ.get("ROBOT_JAIL", "") 456 gp.dprint_vars( 457 ROBOT_TEST_BASE_DIR_PATH, 458 ROBOT_TEST_RUNNING_FROM_SB, 459 ROBOT_JAIL, 460 robot_jail, 461 ) 462 463 # Save PATH and PYTHONPATH to be restored later. 464 os.environ["SAVED_PYTHONPATH"] = os.environ.get("PYTHONPATH", "") 465 os.environ["SAVED_PATH"] = os.environ.get("PATH", "") 466 467 if robot_jail: 468 # Make sure required programs like python and robot can be found in the new restricted PATH. 469 required_programs = "python robot" 470 # It is expected that there will be a "python" program in the tool base bin path which is really a 471 # link to select_version. Ditto for "robot". Call each with the --print_only option to get the 472 # paths to the "real" programs. 473 cmd_buf = ( 474 "for program in " 475 + required_programs 476 + " ; do dirname $(${program} --print_only) ; done 2>/dev/null" 477 ) 478 rc, out_buf = gc.shell_cmd(cmd_buf, quiet=1, print_output=0) 479 PYTHONPATH = ROBOT_TEST_BASE_DIR_PATH + "lib" 480 NEW_PATH_LIST = [ROBOT_TEST_BASE_DIR_PATH + "bin"] 481 NEW_PATH_LIST.extend(list(set(out_buf.rstrip("\n").split("\n")))) 482 NEW_PATH_LIST.extend( 483 [ 484 "/usr/local/sbin", 485 "/usr/local/bin", 486 "/usr/sbin", 487 "/usr/bin", 488 "/sbin", 489 "/bin", 490 ] 491 ) 492 PATH = ":".join(NEW_PATH_LIST) 493 else: 494 PYTHONPATH = ( 495 os.environ.get("PYTHONPATH", "") 496 + ":" 497 + ROBOT_TEST_BASE_DIR_PATH 498 + "lib" 499 ) 500 PATH = ( 501 os.environ.get("PATH", "") + ":" + ROBOT_TEST_BASE_DIR_PATH + "bin" 502 ) 503 504 os.environ["PYTHONPATH"] = PYTHONPATH 505 os.environ["PATH"] = PATH 506 gp.dprint_vars(PATH, PYTHONPATH) 507 508 os.environ["FFDC_DIR_PATH_STYLE"] = os.environ.get( 509 "FFDC_DIR_PATH_STYLE", "1" 510 ) 511 gp.qpissuing(robot_cmd_buf, test_mode) 512 if test_mode: 513 os.environ["PATH"] = os.environ.get("SAVED_PATH", "") 514 os.environ["PYTHONPATH"] = os.environ.get("SAVED_PYTHONPATH", "") 515 return True 516 517 if quiet: 518 DEVNULL = open(os.devnull, "wb") 519 stdout = DEVNULL 520 else: 521 stdout = None 522 sub_proc = subprocess.Popen(robot_cmd_buf, stdout=stdout, shell=True) 523 sub_proc.communicate() 524 shell_rc = sub_proc.returncode 525 os.environ["PATH"] = os.environ.get("SAVED_PATH", "") 526 os.environ["PYTHONPATH"] = os.environ.get("SAVED_PYTHONPATH", "") 527 gcr_last_robot_rc = shell_rc 528 process_robot_output_files() 529 if shell_rc != 0: 530 gp.print_var(shell_rc, gp.hexa()) 531 return False 532 533 return True 534