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