1e7e9171eSGeorge Keishing#!/usr/bin/env python3 2c3b512e5SMichael Walsh 3c3b512e5SMichael Walshr""" 4c3b512e5SMichael WalshThis module provides command execution functions such as cmd_fnc and cmd_fnc_u. 5c3b512e5SMichael Walsh""" 6c3b512e5SMichael Walsh 7e635ddc0SGeorge Keishingimport collections 8e635ddc0SGeorge Keishingimport inspect 920f38712SPatrick Williamsimport os 1020f38712SPatrick Williamsimport re 1120f38712SPatrick Williamsimport signal 1220f38712SPatrick Williamsimport subprocess 1320f38712SPatrick Williamsimport sys 1420f38712SPatrick Williamsimport time 15c3b512e5SMichael Walsh 1620f38712SPatrick Williamsimport func_args as fa 1720f38712SPatrick Williamsimport gen_misc as gm 18c3b512e5SMichael Walshimport gen_print as gp 19c3b512e5SMichael Walshimport gen_valid as gv 20d690150cSMichael Walsh 21d690150cSMichael Walshrobot_env = gp.robot_env 22d690150cSMichael Walsh 23afc53a2cSMichael Walshif robot_env: 24a3e2f533SMichael Walsh from robot.libraries.BuiltIn import BuiltIn 25c3b512e5SMichael Walsh 26c3b512e5SMichael Walsh 279e042ad3SMichael Walsh# cmd_fnc and cmd_fnc_u should now be considered deprecated. shell_cmd and t_shell_cmd should be used 289e042ad3SMichael Walsh# instead. 2920f38712SPatrick Williamsdef cmd_fnc( 3020f38712SPatrick Williams cmd_buf, 31c3b512e5SMichael Walsh quiet=None, 32c3b512e5SMichael Walsh test_mode=None, 33afc53a2cSMichael Walsh debug=0, 34c3b512e5SMichael Walsh print_output=1, 35cfe9fedcSMichael Walsh show_err=1, 36a3e2f533SMichael Walsh return_stderr=0, 3720f38712SPatrick Williams ignore_err=1, 3820f38712SPatrick Williams): 39c3b512e5SMichael Walsh r""" 409e042ad3SMichael Walsh Run the given command in a shell and return the shell return code and the output. 41c3b512e5SMichael Walsh 42c3b512e5SMichael Walsh Description of arguments: 43c3b512e5SMichael Walsh cmd_buf The command string to be run in a shell. 449e042ad3SMichael Walsh quiet Indicates whether this function should run the print_issuing() function 459e042ad3SMichael Walsh which prints "Issuing: <cmd string>" to stdout. 469e042ad3SMichael Walsh test_mode If test_mode is set, this function will not actually run the command. If 479e042ad3SMichael Walsh print_output is set, it will print "(test_mode) Issuing: <cmd string>" to 48cfe9fedcSMichael Walsh stdout. 499e042ad3SMichael Walsh debug If debug is set, this function will print extra debug info. 509e042ad3SMichael Walsh print_output If this is set, this function will print the stdout/stderr generated by 519e042ad3SMichael Walsh the shell command. 529e042ad3SMichael Walsh show_err If show_err is set, this function will print a standardized error report 539e042ad3SMichael Walsh if the shell command returns non-zero. 549e042ad3SMichael Walsh return_stderr If return_stderr is set, this function will process the stdout and stderr 559e042ad3SMichael Walsh streams from the shell command separately. It will also return stderr in 569e042ad3SMichael Walsh addition to the return code and the stdout. 57c3b512e5SMichael Walsh """ 58c3b512e5SMichael Walsh 59cfe9fedcSMichael Walsh # Determine default values. 60c3b512e5SMichael Walsh quiet = int(gm.global_default(quiet, 0)) 61c3b512e5SMichael Walsh test_mode = int(gm.global_default(test_mode, 0)) 62c3b512e5SMichael Walsh 63c3b512e5SMichael Walsh if debug: 64c3b512e5SMichael Walsh gp.print_vars(cmd_buf, quiet, test_mode, debug) 65c3b512e5SMichael Walsh 66ec01a6f6SMichael Walsh err_msg = gv.valid_value(cmd_buf) 67c3b512e5SMichael Walsh if err_msg != "": 68c3b512e5SMichael Walsh raise ValueError(err_msg) 69c3b512e5SMichael Walsh 70c3b512e5SMichael Walsh if not quiet: 71c3b512e5SMichael Walsh gp.pissuing(cmd_buf, test_mode) 72c3b512e5SMichael Walsh 73c3b512e5SMichael Walsh if test_mode: 74cfe9fedcSMichael Walsh if return_stderr: 75cfe9fedcSMichael Walsh return 0, "", "" 76cfe9fedcSMichael Walsh else: 77c3b512e5SMichael Walsh return 0, "" 78c3b512e5SMichael Walsh 79cfe9fedcSMichael Walsh if return_stderr: 80cfe9fedcSMichael Walsh err_buf = "" 81cfe9fedcSMichael Walsh stderr = subprocess.PIPE 82cfe9fedcSMichael Walsh else: 83cfe9fedcSMichael Walsh stderr = subprocess.STDOUT 84cfe9fedcSMichael Walsh 8520f38712SPatrick Williams sub_proc = subprocess.Popen( 8620f38712SPatrick Williams cmd_buf, 87c3b512e5SMichael Walsh bufsize=1, 88c3b512e5SMichael Walsh shell=True, 89e7e9171eSGeorge Keishing universal_newlines=True, 9020f38712SPatrick Williams executable="/bin/bash", 91c3b512e5SMichael Walsh stdout=subprocess.PIPE, 9220f38712SPatrick Williams stderr=stderr, 9320f38712SPatrick Williams ) 94c3b512e5SMichael Walsh out_buf = "" 95cfe9fedcSMichael Walsh if return_stderr: 96cfe9fedcSMichael Walsh for line in sub_proc.stderr: 9736efbc04SGeorge Keishing try: 9836efbc04SGeorge Keishing err_buf += line 9936efbc04SGeorge Keishing except TypeError: 10036efbc04SGeorge Keishing line = line.decode("utf-8") 101cfe9fedcSMichael Walsh err_buf += line 102cfe9fedcSMichael Walsh if not print_output: 103cfe9fedcSMichael Walsh continue 104c108e429SMichael Walsh gp.gp_print(line) 105c3b512e5SMichael Walsh for line in sub_proc.stdout: 10636efbc04SGeorge Keishing try: 10736efbc04SGeorge Keishing out_buf += line 10836efbc04SGeorge Keishing except TypeError: 10936efbc04SGeorge Keishing line = line.decode("utf-8") 110c3b512e5SMichael Walsh out_buf += line 111c3b512e5SMichael Walsh if not print_output: 112c3b512e5SMichael Walsh continue 113c108e429SMichael Walsh gp.gp_print(line) 114c3b512e5SMichael Walsh if print_output and not robot_env: 115c3b512e5SMichael Walsh sys.stdout.flush() 116c3b512e5SMichael Walsh sub_proc.communicate() 117c3b512e5SMichael Walsh shell_rc = sub_proc.returncode 118a3e2f533SMichael Walsh if shell_rc != 0: 119a3e2f533SMichael Walsh err_msg = "The prior shell command failed.\n" 1201429e127SMichael Walsh err_msg += gp.sprint_var(shell_rc, gp.hexa()) 121cfe9fedcSMichael Walsh if not print_output: 122cfe9fedcSMichael Walsh err_msg += "out_buf:\n" + out_buf 123c3b512e5SMichael Walsh 124a3e2f533SMichael Walsh if show_err: 125cfe9fedcSMichael Walsh gp.print_error_report(err_msg) 126a3e2f533SMichael Walsh if not ignore_err: 127a3e2f533SMichael Walsh if robot_env: 128a3e2f533SMichael Walsh BuiltIn().fail(err_msg) 129a3e2f533SMichael Walsh else: 130a3e2f533SMichael Walsh raise ValueError(err_msg) 131cfe9fedcSMichael Walsh 132cfe9fedcSMichael Walsh if return_stderr: 133cfe9fedcSMichael Walsh return shell_rc, out_buf, err_buf 134cfe9fedcSMichael Walsh else: 135c3b512e5SMichael Walsh return shell_rc, out_buf 136c3b512e5SMichael Walsh 137c3b512e5SMichael Walsh 13820f38712SPatrick Williamsdef cmd_fnc_u( 13920f38712SPatrick Williams cmd_buf, 140c3b512e5SMichael Walsh quiet=None, 141c3b512e5SMichael Walsh debug=None, 142c3b512e5SMichael Walsh print_output=1, 143cfe9fedcSMichael Walsh show_err=1, 144a3e2f533SMichael Walsh return_stderr=0, 14520f38712SPatrick Williams ignore_err=1, 14620f38712SPatrick Williams): 147c3b512e5SMichael Walsh r""" 148c3b512e5SMichael Walsh Call cmd_fnc with test_mode=0. See cmd_fnc (above) for details. 149c3b512e5SMichael Walsh 150c3b512e5SMichael Walsh Note the "u" in "cmd_fnc_u" stands for "unconditional". 151c3b512e5SMichael Walsh """ 152c3b512e5SMichael Walsh 15320f38712SPatrick Williams return cmd_fnc( 15420f38712SPatrick Williams cmd_buf, 15520f38712SPatrick Williams test_mode=0, 15620f38712SPatrick Williams quiet=quiet, 15720f38712SPatrick Williams debug=debug, 15820f38712SPatrick Williams print_output=print_output, 15920f38712SPatrick Williams show_err=show_err, 16020f38712SPatrick Williams return_stderr=return_stderr, 16120f38712SPatrick Williams ignore_err=ignore_err, 16220f38712SPatrick Williams ) 163c3b512e5SMichael Walsh 164f41fac81SMichael Walsh 165f41fac81SMichael Walshdef parse_command_string(command_string): 166f41fac81SMichael Walsh r""" 1679e042ad3SMichael Walsh Parse a bash command-line command string and return the result as a dictionary of parms. 168f41fac81SMichael Walsh 1699e042ad3SMichael Walsh This can be useful for answering questions like "What did the user specify as the value for parm x in the 1709e042ad3SMichael Walsh command string?". 171f41fac81SMichael Walsh 1729e042ad3SMichael Walsh This function expects the command string to follow the following posix conventions: 173f41fac81SMichael Walsh - Short parameters: 174f41fac81SMichael Walsh -<parm name><space><arg value> 175f41fac81SMichael Walsh - Long parameters: 176f41fac81SMichael Walsh --<parm name>=<arg value> 177f41fac81SMichael Walsh 1789e042ad3SMichael Walsh The first item in the string will be considered to be the command. All values not conforming to the 1799e042ad3SMichael Walsh specifications above will be considered positional parms. If there are multiple parms with the same 1809e042ad3SMichael Walsh name, they will be put into a list (see illustration below where "-v" is specified multiple times). 181f41fac81SMichael Walsh 182f41fac81SMichael Walsh Description of argument(s): 1839e042ad3SMichael Walsh command_string The complete command string including all parameters and arguments. 184f41fac81SMichael Walsh 185f41fac81SMichael Walsh Sample input: 186f41fac81SMichael Walsh 1879e042ad3SMichael Walsh robot_cmd_buf: robot -v OPENBMC_HOST:dummy1 -v keyword_string:'Set 1889e042ad3SMichael Walsh Auto Reboot no' -v lib_file_path:/home/user1/git/openbmc-test-automation/lib/utils.robot -v quiet:0 -v 1899e042ad3SMichael Walsh test_mode:0 -v debug:0 --outputdir='/home/user1/status/children/' 1909e042ad3SMichael Walsh --output=dummy1.Auto_reboot.170802.124544.output.xml --log=dummy1.Auto_reboot.170802.124544.log.html 191f41fac81SMichael Walsh --report=dummy1.Auto_reboot.170802.124544.report.html 192f41fac81SMichael Walsh /home/user1/git/openbmc-test-automation/extended/run_keyword.robot 193f41fac81SMichael Walsh 194f41fac81SMichael Walsh Sample output: 195f41fac81SMichael Walsh 196f41fac81SMichael Walsh robot_cmd_buf_dict: 197f41fac81SMichael Walsh robot_cmd_buf_dict[command]: robot 198f41fac81SMichael Walsh robot_cmd_buf_dict[v]: 199f41fac81SMichael Walsh robot_cmd_buf_dict[v][0]: OPENBMC_HOST:dummy1 2009e042ad3SMichael Walsh robot_cmd_buf_dict[v][1]: keyword_string:Set Auto Reboot no 201f41fac81SMichael Walsh robot_cmd_buf_dict[v][2]: 202f41fac81SMichael Walsh lib_file_path:/home/user1/git/openbmc-test-automation/lib/utils.robot 203f41fac81SMichael Walsh robot_cmd_buf_dict[v][3]: quiet:0 204f41fac81SMichael Walsh robot_cmd_buf_dict[v][4]: test_mode:0 205f41fac81SMichael Walsh robot_cmd_buf_dict[v][5]: debug:0 2069e042ad3SMichael Walsh robot_cmd_buf_dict[outputdir]: /home/user1/status/children/ 2079e042ad3SMichael Walsh robot_cmd_buf_dict[output]: dummy1.Auto_reboot.170802.124544.output.xml 2089e042ad3SMichael Walsh robot_cmd_buf_dict[log]: dummy1.Auto_reboot.170802.124544.log.html 2099e042ad3SMichael Walsh robot_cmd_buf_dict[report]: dummy1.Auto_reboot.170802.124544.report.html 210f41fac81SMichael Walsh robot_cmd_buf_dict[positional]: 211f41fac81SMichael Walsh /home/user1/git/openbmc-test-automation/extended/run_keyword.robot 212f41fac81SMichael Walsh """ 213f41fac81SMichael Walsh 2149e042ad3SMichael Walsh # We want the parms in the string broken down the way bash would do it, so we'll call upon bash to do 2159e042ad3SMichael Walsh # that by creating a simple inline bash function. 21620f38712SPatrick Williams bash_func_def = ( 21720f38712SPatrick Williams 'function parse { for parm in "${@}" ; do' + " echo $parm ; done ; }" 21820f38712SPatrick Williams ) 219f41fac81SMichael Walsh 22020f38712SPatrick Williams rc, outbuf = cmd_fnc_u( 22120f38712SPatrick Williams bash_func_def + " ; parse " + command_string, quiet=1, print_output=0 22220f38712SPatrick Williams ) 223f41fac81SMichael Walsh command_string_list = outbuf.rstrip("\n").split("\n") 224f41fac81SMichael Walsh 225f41fac81SMichael Walsh command_string_dict = collections.OrderedDict() 226f41fac81SMichael Walsh ix = 1 22720f38712SPatrick Williams command_string_dict["command"] = command_string_list[0] 228f41fac81SMichael Walsh while ix < len(command_string_list): 229f41fac81SMichael Walsh if command_string_list[ix].startswith("--"): 230f41fac81SMichael Walsh key, value = command_string_list[ix].split("=") 231f41fac81SMichael Walsh key = key.lstrip("-") 232f41fac81SMichael Walsh elif command_string_list[ix].startswith("-"): 233f41fac81SMichael Walsh key = command_string_list[ix].lstrip("-") 234f41fac81SMichael Walsh ix += 1 235f41fac81SMichael Walsh try: 236f41fac81SMichael Walsh value = command_string_list[ix] 237f41fac81SMichael Walsh except IndexError: 238f41fac81SMichael Walsh value = "" 239f41fac81SMichael Walsh else: 24020f38712SPatrick Williams key = "positional" 241f41fac81SMichael Walsh value = command_string_list[ix] 242f41fac81SMichael Walsh if key in command_string_dict: 243004ad3c9SJoy Onyerikwu if isinstance(command_string_dict[key], str): 244f41fac81SMichael Walsh command_string_dict[key] = [command_string_dict[key]] 245f41fac81SMichael Walsh command_string_dict[key].append(value) 246f41fac81SMichael Walsh else: 247f41fac81SMichael Walsh command_string_dict[key] = value 248f41fac81SMichael Walsh ix += 1 249f41fac81SMichael Walsh 250f41fac81SMichael Walsh return command_string_dict 25121083d28SMichael Walsh 25221083d28SMichael Walsh 25321083d28SMichael Walsh# Save the original SIGALRM handler for later restoration by shell_cmd. 25421083d28SMichael Walshoriginal_sigalrm_handler = signal.getsignal(signal.SIGALRM) 25521083d28SMichael Walsh 25621083d28SMichael Walsh 25720f38712SPatrick Williamsdef shell_cmd_timed_out(signal_number, frame): 25821083d28SMichael Walsh r""" 25921083d28SMichael Walsh Handle an alarm signal generated during the shell_cmd function. 26021083d28SMichael Walsh """ 26121083d28SMichael Walsh 26221083d28SMichael Walsh gp.dprint_executing() 2633fb26180SMichael Walsh global command_timed_out 2643fb26180SMichael Walsh command_timed_out = True 26521083d28SMichael Walsh # Get subprocess pid from shell_cmd's call stack. 26620f38712SPatrick Williams sub_proc = gp.get_stack_var("sub_proc", 0) 26721083d28SMichael Walsh pid = sub_proc.pid 2683fb26180SMichael Walsh gp.dprint_var(pid) 2693fb26180SMichael Walsh # Terminate the child process group. 2703fb26180SMichael Walsh os.killpg(pid, signal.SIGKILL) 27121083d28SMichael Walsh # Restore the original SIGALRM handler. 27221083d28SMichael Walsh signal.signal(signal.SIGALRM, original_sigalrm_handler) 27321083d28SMichael Walsh 27421083d28SMichael Walsh return 27521083d28SMichael Walsh 27621083d28SMichael Walsh 27720f38712SPatrick Williamsdef shell_cmd( 27820f38712SPatrick Williams command_string, 27921083d28SMichael Walsh quiet=None, 28055abd1b1SMichael Walsh print_output=None, 28121083d28SMichael Walsh show_err=1, 28221083d28SMichael Walsh test_mode=0, 28321083d28SMichael Walsh time_out=None, 28421083d28SMichael Walsh max_attempts=1, 28521083d28SMichael Walsh retry_sleep_time=5, 2863fb26180SMichael Walsh valid_rcs=[0], 28721083d28SMichael Walsh ignore_err=None, 288faafa9c5SMichael Walsh return_stderr=0, 2899e042ad3SMichael Walsh fork=0, 29020f38712SPatrick Williams error_regexes=None, 29120f38712SPatrick Williams): 29221083d28SMichael Walsh r""" 2939e042ad3SMichael Walsh Run the given command string in a shell and return a tuple consisting of the shell return code and the 2949e042ad3SMichael Walsh output. 29521083d28SMichael Walsh 29621083d28SMichael Walsh Description of argument(s): 2979e042ad3SMichael Walsh command_string The command string to be run in a shell (e.g. "ls /tmp"). 2989e042ad3SMichael Walsh quiet If set to 0, this function will print "Issuing: <cmd string>" to stdout. 2999e042ad3SMichael Walsh When the quiet argument is set to None, this function will assign a 3009e042ad3SMichael Walsh default value by searching upward in the stack for the quiet variable 3019e042ad3SMichael Walsh value. If no such value is found, quiet is set to 0. 3029e042ad3SMichael Walsh print_output If this is set, this function will print the stdout/stderr generated by 3039e042ad3SMichael Walsh the shell command to stdout. 3049e042ad3SMichael Walsh show_err If show_err is set, this function will print a standardized error report 3059e042ad3SMichael Walsh if the shell command fails (i.e. if the shell command returns a shell_rc 3069e042ad3SMichael Walsh that is not in valid_rcs). Note: Error text is only printed if ALL 3079e042ad3SMichael Walsh attempts to run the command_string fail. In other words, if the command 3089e042ad3SMichael Walsh execution is ultimately successful, initial failures are hidden. 3099e042ad3SMichael Walsh test_mode If test_mode is set, this function will not actually run the command. If 3109e042ad3SMichael Walsh print_output is also set, this function will print "(test_mode) Issuing: 3119e042ad3SMichael Walsh <cmd string>" to stdout. A caller should call shell_cmd directly if they 3129e042ad3SMichael Walsh wish to have the command string run unconditionally. They should call 3139e042ad3SMichael Walsh the t_shell_cmd wrapper (defined below) if they wish to run the command 3149e042ad3SMichael Walsh string only if the prevailing test_mode variable is set to 0. 3159e042ad3SMichael Walsh time_out A time-out value expressed in seconds. If the command string has not 3169e042ad3SMichael Walsh finished executing within <time_out> seconds, it will be halted and 3179e042ad3SMichael Walsh counted as an error. 3189e042ad3SMichael Walsh max_attempts The max number of attempts that should be made to run the command string. 3199e042ad3SMichael Walsh retry_sleep_time The number of seconds to sleep between attempts. 3209e042ad3SMichael Walsh valid_rcs A list of integers indicating which shell_rc values are not to be 3219e042ad3SMichael Walsh considered errors. 3229e042ad3SMichael Walsh ignore_err Ignore error means that a failure encountered by running the command 3239e042ad3SMichael Walsh string will not be raised as a python exception. When the ignore_err 3249e042ad3SMichael Walsh argument is set to None, this function will assign a default value by 3259e042ad3SMichael Walsh searching upward in the stack for the ignore_err variable value. If no 3269e042ad3SMichael Walsh such value is found, ignore_err is set to 1. 3279e042ad3SMichael Walsh return_stderr If return_stderr is set, this function will process the stdout and stderr 3289e042ad3SMichael Walsh streams from the shell command separately. In such a case, the tuple 3299e042ad3SMichael Walsh returned by this function will consist of three values rather than just 3309e042ad3SMichael Walsh two: rc, stdout, stderr. 3319e042ad3SMichael Walsh fork Run the command string asynchronously (i.e. don't wait for status of the 3329e042ad3SMichael Walsh child process and don't try to get stdout/stderr) and return the Popen 3339e042ad3SMichael Walsh object created by the subprocess.popen() function. See the kill_cmd 3349e042ad3SMichael Walsh function for details on how to process the popen object. 3359e042ad3SMichael Walsh error_regexes A list of regular expressions to be used to identify errors in the 3369e042ad3SMichael Walsh command output. If there is a match for any of these regular 3379e042ad3SMichael Walsh expressions, the command will be considered a failure and the shell_rc 3389e042ad3SMichael Walsh will be set to -1. For example, if error_regexes = ['ERROR:'] and the 3399e042ad3SMichael Walsh command output contains 'ERROR: Unrecognized option', it will be counted 3409e042ad3SMichael Walsh as an error even if the command returned 0. This is useful when running 3419e042ad3SMichael Walsh commands that do not always return non-zero on error. 34221083d28SMichael Walsh """ 34321083d28SMichael Walsh 3443fb26180SMichael Walsh err_msg = gv.valid_value(command_string) 3453fb26180SMichael Walsh if err_msg: 3463fb26180SMichael Walsh raise ValueError(err_msg) 3473fb26180SMichael Walsh 34821083d28SMichael Walsh # Assign default values to some of the arguments to this function. 34920f38712SPatrick Williams quiet = int(gm.dft(quiet, gp.get_stack_var("quiet", 0))) 35055abd1b1SMichael Walsh print_output = int(gm.dft(print_output, not quiet)) 35145fead49SMichael Walsh show_err = int(show_err) 35220f38712SPatrick Williams ignore_err = int(gm.dft(ignore_err, gp.get_stack_var("ignore_err", 1))) 35321083d28SMichael Walsh 3543fb26180SMichael Walsh gp.qprint_issuing(command_string, test_mode) 35521083d28SMichael Walsh if test_mode: 3563fb26180SMichael Walsh return (0, "", "") if return_stderr else (0, "") 35721083d28SMichael Walsh 3583fb26180SMichael Walsh # Convert a string python dictionary definition to a dictionary. 3593fb26180SMichael Walsh valid_rcs = fa.source_to_object(valid_rcs) 36021083d28SMichael Walsh # Convert each list entry to a signed value. 3613fb26180SMichael Walsh valid_rcs = [gm.to_signed(x) for x in valid_rcs] 36221083d28SMichael Walsh 3633fb26180SMichael Walsh stderr = subprocess.PIPE if return_stderr else subprocess.STDOUT 36421083d28SMichael Walsh 3659e042ad3SMichael Walsh # Write all output to func_out_history_buf rather than directly to stdout. This allows us to decide 3669e042ad3SMichael Walsh # what to print after all attempts to run the command string have been made. func_out_history_buf will 3679e042ad3SMichael Walsh # contain the complete history from the current invocation of this function. 3683fb26180SMichael Walsh global command_timed_out 3693fb26180SMichael Walsh command_timed_out = False 3703fb26180SMichael Walsh func_out_history_buf = "" 37121083d28SMichael Walsh for attempt_num in range(1, max_attempts + 1): 37220f38712SPatrick Williams sub_proc = subprocess.Popen( 37320f38712SPatrick Williams command_string, 37421083d28SMichael Walsh bufsize=1, 37521083d28SMichael Walsh shell=True, 3763fb26180SMichael Walsh universal_newlines=True, 37720f38712SPatrick Williams executable="/bin/bash", 378e7e9171eSGeorge Keishing stdin=subprocess.PIPE, 37921083d28SMichael Walsh stdout=subprocess.PIPE, 38020f38712SPatrick Williams stderr=stderr, 38120f38712SPatrick Williams ) 382faafa9c5SMichael Walsh if fork: 3834d5f6686SMichael Walsh return sub_proc 3843fb26180SMichael Walsh 3853fb26180SMichael Walsh if time_out: 38621083d28SMichael Walsh command_timed_out = False 38721083d28SMichael Walsh # Designate a SIGALRM handling function and set alarm. 38821083d28SMichael Walsh signal.signal(signal.SIGALRM, shell_cmd_timed_out) 38921083d28SMichael Walsh signal.alarm(time_out) 39021083d28SMichael Walsh try: 3913fb26180SMichael Walsh stdout_buf, stderr_buf = sub_proc.communicate() 39221083d28SMichael Walsh except IOError: 39321083d28SMichael Walsh command_timed_out = True 39421083d28SMichael Walsh # Restore the original SIGALRM handler and clear the alarm. 39521083d28SMichael Walsh signal.signal(signal.SIGALRM, original_sigalrm_handler) 39621083d28SMichael Walsh signal.alarm(0) 3973fb26180SMichael Walsh 3989e042ad3SMichael Walsh # Output from this loop iteration is written to func_out_buf for later processing. This can include 3999e042ad3SMichael Walsh # stdout, stderr and our own error messages. 4003fb26180SMichael Walsh func_out_buf = "" 4013fb26180SMichael Walsh if print_output: 4023fb26180SMichael Walsh if return_stderr: 4033fb26180SMichael Walsh func_out_buf += stderr_buf 4043fb26180SMichael Walsh func_out_buf += stdout_buf 4053fb26180SMichael Walsh shell_rc = sub_proc.returncode 4063fb26180SMichael Walsh if shell_rc in valid_rcs: 4079e042ad3SMichael Walsh # Check output for text indicating there is an error. 40820f38712SPatrick Williams if error_regexes and re.match("|".join(error_regexes), stdout_buf): 4099e042ad3SMichael Walsh shell_rc = -1 4109e042ad3SMichael Walsh else: 41121083d28SMichael Walsh break 41221083d28SMichael Walsh err_msg = "The prior shell command failed.\n" 41321083d28SMichael Walsh err_msg += gp.sprint_var(attempt_num) 4143fb26180SMichael Walsh err_msg += gp.sprint_vars(command_string, command_timed_out, time_out) 4153fb26180SMichael Walsh err_msg += gp.sprint_varx("child_pid", sub_proc.pid) 4163fb26180SMichael Walsh err_msg += gp.sprint_vars(shell_rc, valid_rcs, fmt=gp.hexa()) 4179e042ad3SMichael Walsh if error_regexes: 4189e042ad3SMichael Walsh err_msg += gp.sprint_vars(error_regexes) 41921083d28SMichael Walsh if not print_output: 42021083d28SMichael Walsh if return_stderr: 4213fb26180SMichael Walsh err_msg += "stderr_buf:\n" + stderr_buf 4223fb26180SMichael Walsh err_msg += "stdout_buf:\n" + stdout_buf 42321083d28SMichael Walsh if show_err: 4243fb26180SMichael Walsh func_out_buf += gp.sprint_error_report(err_msg) 42521083d28SMichael Walsh if attempt_num < max_attempts: 4263fb26180SMichael Walsh cmd_buf = "time.sleep(" + str(retry_sleep_time) + ")" 4273fb26180SMichael Walsh if show_err: 4283fb26180SMichael Walsh func_out_buf += gp.sprint_issuing(cmd_buf) 4293fb26180SMichael Walsh exec(cmd_buf) 4303fb26180SMichael Walsh func_out_history_buf += func_out_buf 43121083d28SMichael Walsh 4323fb26180SMichael Walsh if shell_rc in valid_rcs: 4333fb26180SMichael Walsh gp.gp_print(func_out_buf) 4343fb26180SMichael Walsh else: 4353fb26180SMichael Walsh if show_err: 43620f38712SPatrick Williams gp.gp_print(func_out_history_buf, stream="stderr") 4373fb26180SMichael Walsh else: 4389e042ad3SMichael Walsh # There is no error information to show so just print output from last loop iteration. 4393fb26180SMichael Walsh gp.gp_print(func_out_buf) 44021083d28SMichael Walsh if not ignore_err: 4419e042ad3SMichael Walsh # If the caller has already asked to show error info, avoid repeating that in the failure message. 44220f38712SPatrick Williams err_msg = ( 44320f38712SPatrick Williams "The prior shell command failed.\n" if show_err else err_msg 44420f38712SPatrick Williams ) 44521083d28SMichael Walsh if robot_env: 44621083d28SMichael Walsh BuiltIn().fail(err_msg) 44721083d28SMichael Walsh else: 4483fb26180SMichael Walsh raise ValueError(err_msg) 44921083d28SMichael Walsh 45020f38712SPatrick Williams return ( 45120f38712SPatrick Williams (shell_rc, stdout_buf, stderr_buf) 45220f38712SPatrick Williams if return_stderr 4533fb26180SMichael Walsh else (shell_rc, stdout_buf) 45420f38712SPatrick Williams ) 45521083d28SMichael Walsh 45621083d28SMichael Walsh 45721083d28SMichael Walshdef t_shell_cmd(command_string, **kwargs): 45821083d28SMichael Walsh r""" 4599e042ad3SMichael Walsh Search upward in the the call stack to obtain the test_mode argument, add it to kwargs and then call 4609e042ad3SMichael Walsh shell_cmd and return the result. 46121083d28SMichael Walsh 46221083d28SMichael Walsh See shell_cmd prolog for details on all arguments. 46321083d28SMichael Walsh """ 46421083d28SMichael Walsh 46520f38712SPatrick Williams if "test_mode" in kwargs: 46620f38712SPatrick Williams error_message = ( 46720f38712SPatrick Williams "Programmer error - test_mode is not a valid" 46820f38712SPatrick Williams + " argument to this function." 46920f38712SPatrick Williams ) 47021083d28SMichael Walsh gp.print_error_report(error_message) 47121083d28SMichael Walsh exit(1) 47221083d28SMichael Walsh 47320f38712SPatrick Williams test_mode = int(gp.get_stack_var("test_mode", 0)) 47420f38712SPatrick Williams kwargs["test_mode"] = test_mode 47521083d28SMichael Walsh 47621083d28SMichael Walsh return shell_cmd(command_string, **kwargs) 47716244c28SGeorge Keishing 47816244c28SGeorge Keishing 4794d5f6686SMichael Walshdef kill_cmd(popen, sig=signal.SIGTERM): 4804d5f6686SMichael Walsh r""" 4819e042ad3SMichael Walsh Kill the subprocess represented by the Popen object and return a tuple consisting of the shell return 4829e042ad3SMichael Walsh code and the output. 4834d5f6686SMichael Walsh 4849e042ad3SMichael Walsh This function is meant to be used as the follow-up for a call to shell_cmd(..., fork=1). 4854d5f6686SMichael Walsh 4864d5f6686SMichael Walsh Example: 4874d5f6686SMichael Walsh popen = shell_cmd("some_pgm.py", fork=1) 4884d5f6686SMichael Walsh ... 4894d5f6686SMichael Walsh shell_rc, output = kill_cmd(popen) 4904d5f6686SMichael Walsh 4914d5f6686SMichael Walsh Description of argument(s): 4929e042ad3SMichael Walsh popen A Popen object returned by the subprocess.Popen() command. 4934d5f6686SMichael Walsh sig The signal to be sent to the child process. 4944d5f6686SMichael Walsh """ 4954d5f6686SMichael Walsh 4964d5f6686SMichael Walsh gp.dprint_var(popen.pid) 4974d5f6686SMichael Walsh os.killpg(popen.pid, sig) 4984d5f6686SMichael Walsh stdout, stderr = popen.communicate() 4994d5f6686SMichael Walsh shell_rc = popen.returncode 5004d5f6686SMichael Walsh return (shell_rc, stdout, stderr) if stderr else (shell_rc, stdout) 5014d5f6686SMichael Walsh 5024d5f6686SMichael Walsh 50316244c28SGeorge Keishingdef re_order_kwargs(stack_frame_ix, **kwargs): 50416244c28SGeorge Keishing r""" 5059e042ad3SMichael Walsh Re-order the kwargs to match the order in which they were specified on a function invocation and return 5069e042ad3SMichael Walsh as an ordered dictionary. 50716244c28SGeorge Keishing 5089e042ad3SMichael Walsh Note that this re_order_kwargs function should not be necessary in python versions 3.6 and beyond. 50916244c28SGeorge Keishing 51016244c28SGeorge Keishing Example: 51116244c28SGeorge Keishing 51216244c28SGeorge Keishing The caller calls func1 like this: 51316244c28SGeorge Keishing 51416244c28SGeorge Keishing func1('mike', arg1='one', arg2='two', arg3='three') 51516244c28SGeorge Keishing 51616244c28SGeorge Keishing And func1 is defined as follows: 51716244c28SGeorge Keishing 51816244c28SGeorge Keishing def func1(first_arg, **kwargs): 51916244c28SGeorge Keishing 52016244c28SGeorge Keishing kwargs = re_order_kwargs(first_arg_num=2, stack_frame_ix=3, **kwargs) 52116244c28SGeorge Keishing 5229e042ad3SMichael Walsh The kwargs dictionary before calling re_order_kwargs (where order is not guaranteed): 52316244c28SGeorge Keishing 52416244c28SGeorge Keishing kwargs: 52516244c28SGeorge Keishing kwargs[arg3]: three 52616244c28SGeorge Keishing kwargs[arg2]: two 52716244c28SGeorge Keishing kwargs[arg1]: one 52816244c28SGeorge Keishing 52916244c28SGeorge Keishing The kwargs dictionary after calling re_order_kwargs: 53016244c28SGeorge Keishing 53116244c28SGeorge Keishing kwargs: 53216244c28SGeorge Keishing kwargs[arg1]: one 53316244c28SGeorge Keishing kwargs[arg2]: two 53416244c28SGeorge Keishing kwargs[arg3]: three 53516244c28SGeorge Keishing 5369e042ad3SMichael Walsh Note that the re-ordered kwargs match the order specified on the call to func1. 53716244c28SGeorge Keishing 53816244c28SGeorge Keishing Description of argument(s): 5399e042ad3SMichael Walsh stack_frame_ix The stack frame of the function whose kwargs values must be re-ordered. 5409e042ad3SMichael Walsh 0 is the stack frame of re_order_kwargs, 1 is the stack from of its 5419e042ad3SMichael Walsh caller and so on. 5429e042ad3SMichael Walsh kwargs The keyword argument dictionary which is to be re-ordered. 54316244c28SGeorge Keishing """ 54416244c28SGeorge Keishing 54516244c28SGeorge Keishing new_kwargs = collections.OrderedDict() 54616244c28SGeorge Keishing 54716244c28SGeorge Keishing # Get position number of first keyword on the calling line of code. 54820f38712SPatrick Williams (args, varargs, keywords, locals) = inspect.getargvalues( 54920f38712SPatrick Williams inspect.stack()[stack_frame_ix][0] 55020f38712SPatrick Williams ) 55116244c28SGeorge Keishing first_kwarg_pos = 1 + len(args) 55216244c28SGeorge Keishing if varargs is not None: 55316244c28SGeorge Keishing first_kwarg_pos += len(locals[varargs]) 55416244c28SGeorge Keishing for arg_num in range(first_kwarg_pos, first_kwarg_pos + len(kwargs)): 55516244c28SGeorge Keishing # This will result in an arg_name value such as "arg1='one'". 55616244c28SGeorge Keishing arg_name = gp.get_arg_name(None, arg_num, stack_frame_ix + 2) 55716244c28SGeorge Keishing # Continuing with the prior example, the following line will result 55816244c28SGeorge Keishing # in key being set to 'arg1'. 55920f38712SPatrick Williams key = arg_name.split("=")[0] 56016244c28SGeorge Keishing new_kwargs[key] = kwargs[key] 56116244c28SGeorge Keishing 56216244c28SGeorge Keishing return new_kwargs 56316244c28SGeorge Keishing 56416244c28SGeorge Keishing 56516244c28SGeorge Keishingdef default_arg_delim(arg_dashes): 56616244c28SGeorge Keishing r""" 56716244c28SGeorge Keishing Return the default argument delimiter value for the given arg_dashes value. 56816244c28SGeorge Keishing 5699e042ad3SMichael Walsh Note: this function is useful for functions that manipulate bash command line arguments (e.g. --parm=1 or 5709e042ad3SMichael Walsh -parm 1). 57116244c28SGeorge Keishing 57216244c28SGeorge Keishing Description of argument(s): 5739e042ad3SMichael Walsh arg_dashes The argument dashes specifier (usually, "-" or "--"). 57416244c28SGeorge Keishing """ 57516244c28SGeorge Keishing 57616244c28SGeorge Keishing if arg_dashes == "--": 57716244c28SGeorge Keishing return "=" 57816244c28SGeorge Keishing 57916244c28SGeorge Keishing return " " 58016244c28SGeorge Keishing 58116244c28SGeorge Keishing 58216244c28SGeorge Keishingdef create_command_string(command, *pos_parms, **options): 58316244c28SGeorge Keishing r""" 5849e042ad3SMichael Walsh Create and return a bash command string consisting of the given arguments formatted as text. 58516244c28SGeorge Keishing 58616244c28SGeorge Keishing The default formatting of options is as follows: 58716244c28SGeorge Keishing 58816244c28SGeorge Keishing <single dash><option name><space delim><option value> 58916244c28SGeorge Keishing 59016244c28SGeorge Keishing Example: 59116244c28SGeorge Keishing 59216244c28SGeorge Keishing -parm value 59316244c28SGeorge Keishing 5949e042ad3SMichael Walsh The caller can change the kind of dashes/delimiters used by specifying "arg_dashes" and/or "arg_delims" 5959e042ad3SMichael Walsh as options. These options are processed specially by the create_command_string function and do NOT get 5969e042ad3SMichael Walsh inserted into the resulting command string. All options following the arg_dashes/arg_delims options will 5979e042ad3SMichael Walsh then use the specified values for dashes/delims. In the special case of arg_dashes equal to "--", the 59816244c28SGeorge Keishing arg_delim will automatically be changed to "=". See examples below. 59916244c28SGeorge Keishing 60016244c28SGeorge Keishing Quoting rules: 60116244c28SGeorge Keishing 6029e042ad3SMichael Walsh The create_command_string function will single quote option values as needed to prevent bash expansion. 6039e042ad3SMichael Walsh If the caller wishes to defeat this action, they may single or double quote the option value themselves. 6049e042ad3SMichael Walsh See examples below. 60516244c28SGeorge Keishing 6069e042ad3SMichael Walsh pos_parms are NOT automatically quoted. The caller is advised to either explicitly add quotes or to use 6079e042ad3SMichael Walsh the quote_bash_parm functions to quote any pos_parms. 60816244c28SGeorge Keishing 60916244c28SGeorge Keishing Examples: 61016244c28SGeorge Keishing 61116244c28SGeorge Keishing command_string = create_command_string('cd', '~') 61216244c28SGeorge Keishing 61316244c28SGeorge Keishing Result: 61416244c28SGeorge Keishing cd ~ 61516244c28SGeorge Keishing 6169e042ad3SMichael Walsh Note that the pos_parm ("~") does NOT get quoted, as per the aforementioned rules. If quotes are 6179e042ad3SMichael Walsh desired, they may be added explicitly by the caller: 61816244c28SGeorge Keishing 61916244c28SGeorge Keishing command_string = create_command_string('cd', '\'~\'') 62016244c28SGeorge Keishing 62116244c28SGeorge Keishing Result: 62216244c28SGeorge Keishing cd '~' 62316244c28SGeorge Keishing 62416244c28SGeorge Keishing command_string = create_command_string('grep', '\'^[^ ]*=\'', 62516244c28SGeorge Keishing '/tmp/myfile', i=None, m='1', arg_dashes='--', color='always') 62616244c28SGeorge Keishing 62716244c28SGeorge Keishing Result: 62816244c28SGeorge Keishing grep -i -m 1 --color=always '^[^ ]*=' /tmp/myfile 62916244c28SGeorge Keishing 6309e042ad3SMichael Walsh In the preceding example, note the use of None to cause the "i" parm to be treated as a flag (i.e. no 6319e042ad3SMichael Walsh argument value is generated). Also, note the use of arg_dashes to change the type of dashes used on all 6329e042ad3SMichael Walsh subsequent options. The following example is equivalent to the prior. Note that quote_bash_parm is used 6339e042ad3SMichael Walsh instead of including the quotes explicitly. 63416244c28SGeorge Keishing 63516244c28SGeorge Keishing command_string = create_command_string('grep', quote_bash_parm('^[^ ]*='), 63616244c28SGeorge Keishing '/tmp/myfile', i=None, m='1', arg_dashes='--', color='always') 63716244c28SGeorge Keishing 63816244c28SGeorge Keishing Result: 63916244c28SGeorge Keishing grep -i -m 1 --color=always '^[^ ]*=' /tmp/myfile 64016244c28SGeorge Keishing 6419e042ad3SMichael Walsh In the following example, note the automatic quoting of the password option, as per the aforementioned 6429e042ad3SMichael Walsh rules. 64316244c28SGeorge Keishing 64416244c28SGeorge Keishing command_string = create_command_string('my_pgm', '/tmp/myfile', i=None, 64516244c28SGeorge Keishing m='1', arg_dashes='--', password='${my_pw}') 64616244c28SGeorge Keishing 6479e042ad3SMichael Walsh However, let's say that the caller wishes to have bash expand the password value. To achieve this, the 6489e042ad3SMichael Walsh caller can use double quotes: 64916244c28SGeorge Keishing 65016244c28SGeorge Keishing command_string = create_command_string('my_pgm', '/tmp/myfile', i=None, 65116244c28SGeorge Keishing m='1', arg_dashes='--', password='"${my_pw}"') 65216244c28SGeorge Keishing 65316244c28SGeorge Keishing Result: 65416244c28SGeorge Keishing my_pgm -i -m 1 --password="${my_pw}" /tmp/myfile 65516244c28SGeorge Keishing 65616244c28SGeorge Keishing command_string = create_command_string('ipmitool', 'power status', 657*7ac1f56fSGeorge Keishing I='lanplus', C='3', 'p=623', U='root', P='********', H='xx.xx.xx.xx') 65816244c28SGeorge Keishing 65916244c28SGeorge Keishing Result: 660*7ac1f56fSGeorge Keishing ipmitool -I lanplus -C 3 -p 623 -U root -P ********* -H xx.xx.xx.xx power status 66116244c28SGeorge Keishing 6629e042ad3SMichael Walsh By default create_command_string will take measures to preserve the order of the callers options. In 6639e042ad3SMichael Walsh some cases, this effort may fail (as when calling directly from a robot program). In this case, the 6649e042ad3SMichael Walsh caller can accept the responsibility of keeping an ordered list of options by calling this function with 6659e042ad3SMichael Walsh the last positional parm as some kind of dictionary (preferably an OrderedDict) and avoiding the use of 6669e042ad3SMichael Walsh any actual option args. 66716244c28SGeorge Keishing 66816244c28SGeorge Keishing Example: 66916244c28SGeorge Keishing kwargs = collections.OrderedDict([('pass', 0), ('fail', 0)]) 67016244c28SGeorge Keishing command_string = create_command_string('my program', 'pos_parm1', kwargs) 67116244c28SGeorge Keishing 67216244c28SGeorge Keishing Result: 67316244c28SGeorge Keishing 67416244c28SGeorge Keishing my program -pass 0 -fail 0 pos_parm1 67516244c28SGeorge Keishing 6769e042ad3SMichael Walsh Note to programmers who wish to write a wrapper to this function: If the python version is less than 6779e042ad3SMichael Walsh 3.6, to get the options to be processed correctly, the wrapper function must include a _stack_frame_ix_ 6789e042ad3SMichael Walsh keyword argument to allow this function to properly re-order options: 67916244c28SGeorge Keishing 68016244c28SGeorge Keishing def create_ipmi_ext_command_string(command, **kwargs): 68116244c28SGeorge Keishing 68216244c28SGeorge Keishing return create_command_string('ipmitool', command, _stack_frame_ix_=2, 68316244c28SGeorge Keishing **kwargs) 68416244c28SGeorge Keishing 68516244c28SGeorge Keishing Example call of wrapper function: 68616244c28SGeorge Keishing 6879e042ad3SMichael Walsh command_string = create_ipmi_ext_command_string('power status', I='lanplus') 68816244c28SGeorge Keishing 68916244c28SGeorge Keishing Description of argument(s): 6909e042ad3SMichael Walsh command The command (e.g. "cat", "sort", "ipmitool", etc.). 6919e042ad3SMichael Walsh pos_parms The positional parms for the command (e.g. PATTERN, FILENAME, etc.). 6929e042ad3SMichael Walsh These will be placed at the end of the resulting command string. 6939e042ad3SMichael Walsh options The command options (e.g. "-m 1", "--max-count=NUM", etc.). Note that if 6949e042ad3SMichael Walsh the value of any option is None, then it will be understood to be a flag 6959e042ad3SMichael Walsh (for which no value is required). 69616244c28SGeorge Keishing """ 69716244c28SGeorge Keishing 69816244c28SGeorge Keishing arg_dashes = "-" 69916244c28SGeorge Keishing delim = default_arg_delim(arg_dashes) 70016244c28SGeorge Keishing 70116244c28SGeorge Keishing command_string = command 70216244c28SGeorge Keishing 70323ac9b4cSMichael Walsh if len(pos_parms) > 0 and gp.is_dict(pos_parms[-1]): 70416244c28SGeorge Keishing # Convert pos_parms from tuple to list. 70516244c28SGeorge Keishing pos_parms = list(pos_parms) 7069e042ad3SMichael Walsh # Re-assign options to be the last pos_parm value (which is a dictionary). 70716244c28SGeorge Keishing options = pos_parms[-1] 70816244c28SGeorge Keishing # Now delete the last pos_parm. 70916244c28SGeorge Keishing del pos_parms[-1] 71016244c28SGeorge Keishing else: 7119e042ad3SMichael Walsh # Either get stack_frame_ix from the caller via options or set it to the default value. 71220f38712SPatrick Williams stack_frame_ix = options.pop("_stack_frame_ix_", 1) 713fffddca1SMichael Walsh if gm.python_version < gm.ordered_dict_version: 7149e042ad3SMichael Walsh # Re-establish the original options order as specified on the original line of code. This 7159e042ad3SMichael Walsh # function depends on correct order. 71616244c28SGeorge Keishing options = re_order_kwargs(stack_frame_ix, **options) 71716244c28SGeorge Keishing for key, value in options.items(): 71816244c28SGeorge Keishing # Check for special values in options and process them. 71916244c28SGeorge Keishing if key == "arg_dashes": 72016244c28SGeorge Keishing arg_dashes = str(value) 72116244c28SGeorge Keishing delim = default_arg_delim(arg_dashes) 72216244c28SGeorge Keishing continue 72316244c28SGeorge Keishing if key == "arg_delim": 72416244c28SGeorge Keishing delim = str(value) 72516244c28SGeorge Keishing continue 72616244c28SGeorge Keishing # Format the options elements into the command string. 72716244c28SGeorge Keishing command_string += " " + arg_dashes + key 72816244c28SGeorge Keishing if value is not None: 72916244c28SGeorge Keishing command_string += delim 73016244c28SGeorge Keishing if re.match(r'^(["].*["]|[\'].*[\'])$', str(value)): 73116244c28SGeorge Keishing # Already quoted. 73216244c28SGeorge Keishing command_string += str(value) 73316244c28SGeorge Keishing else: 73416244c28SGeorge Keishing command_string += gm.quote_bash_parm(str(value)) 7359e042ad3SMichael Walsh # Finally, append the pos_parms to the end of the command_string. Use filter to eliminate blank pos 7369e042ad3SMichael Walsh # parms. 73720f38712SPatrick Williams command_string = " ".join([command_string] + list(filter(None, pos_parms))) 73816244c28SGeorge Keishing 73916244c28SGeorge Keishing return command_string 740