def image_cluster(self, cluster, commit_id, build_url, user_email, hyp_type, hyp_version, svm_version, build_type): if build_url: request = ClientPhoenixRequest( user_email=user_email, cluster=cluster, commit_id=commit_id, svm_installer_url=build_url, hyp_type=hyp_type, hyp_version=hyp_version, svm_version=svm_version, build_type=build_type) else: build_url = self.get_svm_installer(commit_id, svm_version, build_type) if not build_url: INFO("Could not get build url to phoenix") return False request = ClientPhoenixRequest( user_email=user_email, cluster=cluster, hyp_type=hyp_type, hyp_version=hyp_version, svm_version=svm_version, build_type=build_type, svm_installer_url=build_url) phoenix = PhoenixClient() INFO("Imaging cluster %s to build %s" % (cluster, svm_version)) if not phoenix.image_cluster(request): return False return True
def get_svm_installer(self, commit_id, branch, build_type): """ Get the build location for th given commit id and branch. """ INFO("Getting installer for commit %s" % commit_id) filer = FLAGS.lab jita=JitaRest() build_url = jita.get_build_url(commit_id, build_type, filer) if build_url: INFO("Found build %s" % build_url) return build_url INFO("Checking if build is in progress") build_task_id = jita.check_build_in_progress(commit_id, branch, build_type, filer) if build_task_id: status = jita.get_build_task(build_task_id).get_status() if not build_task_id or status in ["failed", "aborted", "skipped"]: INFO("Could not find an existing build. Hence requesting build") res = jita.request_build(commit_id, branch, build_type, filer) if res.get_status(): build_task_id = res.get_build_task_id() else: ERROR("Build request for commit id %s failed" % commit_id) return False if self.wait_for_build_to_complete(build_task_id): return jita.get_build_url(commit_id, build_type, filer) return False
def execute_command(uvm, cmd, fatal_on_error=False, username="******", password="******", timeout_secs=180): """ Execute command 'cmd' on the Windows VM with the supplied credentials 'username' and 'password' (which is needed to access the Windows VM). If 'fatal_on_error' is true, then we fatal if the out returns a non-zero return value or contains anything in the stderr stream. Returns a tuple consisting of the return value, stdout and stderr. """ remote_shell = BasicWsmanRemoteShell(uvm.ip(), username, password) try: ret, stdout, stderr = remote_shell.posh_execute( cmd, timeout_secs=timeout_secs) INFO("cmd returned %s and %s and %s" %(ret,stdout,stderr)) stdout = stdout.strip() stderr = stderr.strip() if FLAGS.agave_verbose: INFO("Result of executing command %s: ret %s, stdout %s, stderr %s" % (cmd, ret, stdout, stderr)) if fatal_on_error and (ret != 0 or stderr.strip()): FATAL("Execution of %s on VM with IP %s failed with ret %s, " "stdout %s and stderr %s" % (cmd, uvm.ip(), ret, stdout, stderr)) return ret, stdout, stderr except WsmanRemoteShellException as wsre: if FLAGS.agave_verbose: INFO("Unable to execute command %s due to %s" % (cmd, str(wsre))) if fatal_on_error: FATAL("Execution of %s on VM %s failed due to %s" % (cmd, uvm.ip(), str(wsre))) return 255, '', str(wsre)
def run_test_on_jita( self, cluster, commit_id=None, build_url=None, job_ids_list=None, index=None, hypervisor=None, hypervisor_version=None, branch=None, input_type="build_url", build_type="opt", test=None, username=None, test_upgrade=None, base_commit=None): # NEEDS CHANGE if not hypervisor: hypervisor = FLAGS.hypervisor if not hypervisor_version: hypervisor_version = FLAGS.hypervisor_version if not branch: branch = FLAGS.branch if not test: test = FLAGS.test_name if not username: username = USERNAME test_framework = FLAGS.test_framework if test_upgrade is None: test_upgrade = FLAGS.upgrade_test base_commit=FLAGS.upgrade_base_commit if test_upgrade: phoenix_commit = base_commit else: phoenix_commit = commit_id if not self.image_cluster( cluster, phoenix_commit, build_url, USERNAME, hypervisor, hypervisor_version, branch, build_type): job_ids_list[index] = False ERROR("Imaging failed on cluster %s" % cluster) return False jita = JitaRest() jita_test = jita.get_test(test,test_framework) if test_upgrade: svm_installer_url = self.get_svm_installer(commit_id, branch, build_type) INFO("Found build %s " % svm_installer_url) if not svm_installer_url: return False args_map = {"target_rel" : svm_installer_url} else: args_map = None INFO("Going to submit task for commit id: %s" % commit_id) response_obj = jita.submit_task( cluster, commit_id, branch, [jita_test.get_id()], test_framework=test_framework, label="auto_bisector", args_map=args_map) if response_obj.is_success(): job_ids_list[index] = response_obj.get_task_id() return True else: ERROR("Task submit failed: %s" % response_obj.get_message()) return False
def wait_for_jita_test_results(self, job_info, timeout=18000, poll_period=600): # Sleep for 10 minutes before starting polling for results. expired_time = 0 unfinished_jobs = job_info.values() jita = JitaRest() while len(unfinished_jobs) and expired_time < timeout: for job_id in job_info.values(): if self.did_jita_task_finish(job_id): if job_id in unfinished_jobs: unfinished_jobs.remove(job_id) time.sleep(poll_period) expired_time += poll_period for commit, job_id in job_info.iteritems(): response = jita.get_task(job_id) if response.get_status() not in ["passed", "completed"]: self.global_results_dict[commit] = False else: test_results = response.get_test_results() #test_results = response.get_test_result_dict() for test, result in test_results.test_result_dict.iteritems(): if result.get_status() != "Succeeded": INFO("Test %s failed with status %s" % (test, result.get_status())) self.global_results_dict[commit] = False break self.global_results_dict[commit] = True return True
def wait_for_test_result(job_id, timeout=18000, poll_period=600): """ Wait for results from nucloud and then populate the results. """ expired_time = 0 nucloud = NuCloudREST() while expired_time < timeout: if did_nucloud_job_finish(job_id): break time.sleep(poll_period) expired_time += poll_period response = nucloud.get_jobs(job_id) if response.get_status() != "passed" or response.get_status( ) != "completed": return False else: response = response.get_test_results(job_id) test_results = response.get_test_result_dict() for test, result in test_results.iteritems(): if result.get_status() != "Succeeded": INFO("Test %s failed with status %s" % (test, result.get_status())) return False return True
def find_offending_commit(self, commits_file=None): input_type = FLAGS.input_type self.get_commit_search_list(commits_file, input_type) if FLAGS.use_nucloud: INFO("Using nucloud to run the tests") offending_commit = self.binary_search_commit( self.commits_dict.keys(), input_type) else: clusters = FLAGS.clusters.split(",") INFO("Using Jita to run the tests on clusters %s" % clusters) offending_commit = self.binary_search_commit_using_jita( self.commits_dict.keys(), input_type, clusters) if offending_commit == None: INFO("The commit cannot be None! BUG.") return offending_commit
def wait_for_test_results(self, job_info, timeout=18000, poll_period=600): """ Wait for results from nucloud and then populate the results. """ expired_time = 0 unfinished_jobs = job_info.values() nucloud = NuCloudREST() while len(unfinished_jobs) and expired_time < timeout: for job_id in job_info.values(): if self.did_nucloud_job_finish(job_id): if job_id in unfinished_jobs: unfinished_jobs.remove(job_id) time.sleep(poll_period) expired_time += poll_period for commit, job_id in job_info.iteritems(): response = nucloud.get_jobs(job_id) if response.get_status() not in ["passed", "completed"]: self.global_results_dict[commit] = False else: response = nucloud.get_test_results(job_id) test_results = response.get_test_result_dict() for test, result in test_results.iteritems(): if test != "send_test_results_email" and \ result.get_status() != "Succeeded": INFO("Test %s failed with status %s" % (test, result.get_status())) self.global_results_dict[commit] = False break self.global_results_dict[commit] = True return True
def did_nucloud_job_finish(self, job_id): """ Helper function to check if a particular job has finished execution in nucloud. """ nucloud = NuCloudREST() response = nucloud.get_jobs(job_id) status = response.get_status() INFO("Response from nucloud for job id %s is %s" % (job_id, status)) if status not in ["passed", "killed", "failed", "completed"]: return False else: return True
def run_test_on_nucloud(test=None, username=None, node_pool=None, branch=None, build_url=None, build_type="opt", preferred_hypervisor=None, commit_id=None): """ Run the given test on nucloud and return the job id if submit succeeds. """ if not test: test = FLAGS.test_name if not username: username = USERNAME if not node_pool: node_pool = NODE_POOL if not branch: branch = FLAGS.branch if not preferred_hypervisor: preferred_hypervisor = FLAGS.hypervisor nucloud = NuCloudREST() INFO("Submitting job to Nucloud for commit id %s" % commit_id) response_obj = nucloud.submit_job( [test], username, node_pool, branch=branch, build_url=build_url, build_type=build_type, preferred_hypervisor=preferred_hypervisor, commit_id=commit_id, skip_email_report=True) print response_obj.get_message() if response_obj.is_success(): return response_obj.get_job_id() else: ERROR("Failed to trigger job in nucloud %s " % response_obj.get_message()) return False
def run_test_on_nucloud( self, test=None, username=None, node_pool=None, branch=None, build_url=None, build_type="opt", preferred_hypervisor=None, commit_id=None, input_type="build_url", test_upgrade=None, image_commit=None): """ Run the given test on nucloud and return the job id if submit succeeds. """ if not test: test=FLAGS.test_name if not username: username=USERNAME if not node_pool: node_pool=NODE_POOL if not branch: branch = FLAGS.branch if not preferred_hypervisor: preferred_hypervisor = FLAGS.hypervisor if not test_upgrade: test_upgrade = FLAGS.upgrade_test image_commit = FLAGS.upgrade_base_commit if input_type=="change_id": change_id = commit_id commit_id = None else: change_id = None nucloud = NuCloudREST() INFO("Submitting job to Nucloud for commit id %s" % commit_id) response_obj = nucloud.submit_job( [test], username, node_pool, branch=branch, build_url=build_url, build_type=build_type, preferred_hypervisor=preferred_hypervisor, commit_id=commit_id, gerrit_change_id=change_id, test_upgrade=test_upgrade, image_commit=image_commit, skip_email_report=True) if response_obj.is_success(): return response_obj.get_job_id() else: return False
def get_commit_search_list(self, commits_file=None, input_type=None): """ Get the commits list from the file and create a dict with commit as the key and build_url as the value. If the build_url isnt specified then the value would be null """ if self.good_commit: commits = self.get_commits_in_range(self.good_commit, self.last_bad_commit) for commit in commits: self.commits_dict[commit] = None else: with open(commits_file) as f: lines = f.read().splitlines() for line in lines: if not line: continue commit = line.split()[0] if input_type == "build_url": self.commits_dict[commit] = line.split()[1] else: self.commits_dict[commit] = None INFO("Commits map: \n%s " % self.commits_dict)
def log_info(self, msg): """ See superclass for documentation. """ INFO(msg)
def transfer_powershell_module(uvm, out_dir, port, username="******", password="******"): """ Transfer Powershell modules to the VM at ${Env:ProgramFiles}\WindowsPowershell\Modules. uvm: Windows VM for which the Powershell module needs to be transferred. out_dir: Test runner output directory from where the files should be copied. port: Http server port from which the Windows VM will be able to pull the modules. username: Username of the account to access the Windows VM password: Password for the above account. Returns True on success, False on failure. """ hyperv_ps_modules_dir = ( "%s/%s" % (top_dir(), WindowsVMCommandUtil.WINDOWS_VM_MODULE_PATH)) agave_posh_modules = [] for (root, _, filenames) in os.walk(hyperv_ps_modules_dir): for filename in filenames: if filename.endswith(".psm1"): module_path = os.path.join(root, filename) agave_posh_modules.append(module_path) for agave_posh_module in agave_posh_modules: try: module_basename = os.path.basename(agave_posh_module) os.symlink(agave_posh_module, "%s/%s" % (out_dir, module_basename)) except OSError as ose: if ose.errno != errno.EEXIST: ERROR("Failed to create a symlink to the Hyper-V Agave PS module " "in %s: %s" % (out_dir, str(ose))) succeeded = True for agave_posh_module in agave_posh_modules: module_basename = os.path.basename(agave_posh_module) module_name_no_extension = module_basename.rsplit(".psm1")[0] remote_path = ("${Env:ProgramFiles}\WindowsPowershell\Modules\%s" % module_name_no_extension) INFO("Transferring the NutanixWindows Powershell modules to %s" % uvm.ip()) # Determine the local IP that has reachability to this host. sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.connect((uvm.ip(), 80)) # Port number doesn't matter. local_ip = sock.getsockname()[0] sock.close() install_posh_module_cmd = """ $ErrorActionPreference = "Stop" $dest = New-Item -ItemType Directory -Path {remote_path} -Force Invoke-WebRequest http://{ip}:{port}/output/{module_name} ` -OutFile $dest\\{module_name}""".format(remote_path=remote_path, ip=local_ip, port=port, module_name=module_basename) if FLAGS.agave_verbose: INFO("Transferring the %s Powershell module to %s using command: %s" % (module_name_no_extension, uvm.ip(), install_posh_module_cmd)) ret, _, stderr = WindowsVMCommandUtil.execute_command( uvm, install_posh_module_cmd, username=username, password=password) if ret or stderr.strip(): ERROR("Failed transferring %s Powershell module to %s: %s" % (module_name_no_extension, uvm.ip(), stderr)) succeeded = succeeded and False return succeeded
def binary_search_commit(self, commit_search_list, input_type): """ This is the main function that implements the binary search flow through the given commit_search_list. Params: commit_search_list: The list of gerrit change ids or commits to be searched test: test set name node_pool: The node_pool to use to trigger the tests input_type: String to indicate type of input for the commits branch: The branch on which this commit appears hypervisor: The hypervisor that needs to be used """ INFO("Below are the list of commits being searched:\n%s" % pprint.pformat(commit_search_list)) if len(commit_search_list) == 0: ERROR ("Length of commit_search_list is 0, this is probably a bug") return None elif len(commit_search_list) == 1: INFO("Returning %s " % commit_search_list[0]) return commit_search_list[0] elif len(commit_search_list) == 2: if commit_search_list[0] in self.global_results_dict.keys(): if self.global_results_dict[commit_search_list[0]]: return commit_search_list[1] else: return commit_search_list[0] if commit_search_list[1] in self.global_results_dict.keys(): if self.global_results_dict[commit_search_list[1]]: return commit_search_list[0] # We reach here if we don't already have the test results for at least the # first commit response_map = {} response_map[commit_search_list[0]] = self.run_test_on_nucloud( build_url=self.commits_dict[commit_search_list[0]], commit_id=commit_search_list[0], input_type=input_type) self.wait_for_test_results(response_map) if self.global_results_dict[commit_search_list[0]]: return commit_search_list[1] else: return commit_search_list[0] # Get the number of free clusters from the pool. free_nodes = self.get_free_nodes_from_jarvis() num_free_clusters = len(free_nodes)/3 INFO("free clusters in nucloud: %s " % num_free_clusters) search_list_len = len(commit_search_list) # If the number of free clusters is less than the number of commits, # let us do a binary search. if num_free_clusters < search_list_len: mid_commit_index = search_list_len / 2 first_half_mid_commit_index = mid_commit_index / 2 second_half_mid_commit_index = ( mid_commit_index + 1 + search_list_len) / 2 index_list = [mid_commit_index, first_half_mid_commit_index, second_half_mid_commit_index] INFO("Commits selected for verification are: %s, %s and %s" % ( commit_search_list[mid_commit_index], commit_search_list[ first_half_mid_commit_index], commit_search_list[ second_half_mid_commit_index])) response_map = {} for index in index_list: # If we already have the test result for this commit don't run the test # again. if commit_search_list[index] in self.global_results_dict.keys(): continue response_map[commit_search_list[index]] = self.run_test_on_nucloud( build_url=self.commits_dict[commit_search_list[index]], commit_id=commit_search_list[index], input_type=input_type) self.wait_for_test_results(response_map) INFO("Results from the run are: %s" % self.global_results_dict) # Based on the test result, call the function again. if not self.global_results_dict[commit_search_list[ first_half_mid_commit_index]]: INFO("Narrowing the search based on the results to commits between " "%s and %s" % (commit_search_list[0], commit_search_list[ first_half_mid_commit_index])) return self.binary_search_commit(commit_search_list[ 0:(first_half_mid_commit_index+1)], input_type) elif not self.global_results_dict[commit_search_list[mid_commit_index]]: INFO("Narrowing the search based on the results to commits between " "%s and %s" % (commit_search_list[first_half_mid_commit_index], commit_search_list[mid_commit_index])) return self.binary_search_commit( commit_search_list[first_half_mid_commit_index:(mid_commit_index+1)], input_type) elif not self.global_results_dict[commit_search_list[ second_half_mid_commit_index]]: INFO("Narrowing the search based on the results to commits between " "%s and %s" % (commit_search_list[mid_commit_index], commit_search_list[second_half_mid_commit_index])) return self.binary_search_commit( commit_search_list[mid_commit_index:(second_half_mid_commit_index+1)], input_type) else: INFO("Narrowing the search based on the results to commits between " "%s and %s" % (commit_search_list[second_half_mid_commit_index], commit_search_list[-1])) return self.binary_search_commit( commit_search_list[second_half_mid_commit_index:], input_type) else: # We have enough clusters. Trigger all the runs in parallel. response_map = {} for commit in commit_search_list: if commit in self.global_results_dict.keys(): continue response_map[commit] = self.run_test_on_nucloud( build_url=self.commits_dict[commit], commit_id=commit, input_type=input_type) self.wait_for_test_results(response_map) INFO("Results from the run are: %s" % self.global_results_dict) for commit in commit_search_list: if not self.global_results_dict[commit]: INFO("Returning the offending commit %s" % commit) return commit
if flag: commit_range.append(commit['commit_id']) commit_range.reverse() return commit_range def main(argv): try: argv = FLAGS(argv) except gflags.FlagsError, err: print "%s\nUsage: %s ARGS\n%s" % (err, sys.argv[0], FLAGS) sys.exit(1) if not FLAGS.commits_file and not ( FLAGS.last_good_commit and FLAGS.latest_bad_commit): print "Exiting! One of commits_file or last_good_commit and " "latest_bad_commit is a required parameter." sys.exit(1) file_template = "auto_bisector_log_%d" % (int(time.time())) bisect = Auto_Bisect() bisect.setup_logging(file_template) commits_file = FLAGS.commits_file offending_commit = bisect.find_offending_commit(commits_file) INFO("Found the offending commit: %s" % offending_commit) if __name__ == "__main__": main(sys.argv)
def binary_search_commit_using_jita(self, commit_search_list, input_type, clusters): """ This is the main function that implements the binary search flow through the given commit_search_list. Params: commit_search_list: The list of gerrit change ids or commits to be searched input_type: String to indicate type of input for the commits clusters: list of clusters to run the test on """ INFO("Below are the list of commits being searched:\n%s" % pprint.pformat(commit_search_list)) if len(commit_search_list) == 0: print "Length of commit_search_list is 0, this is probably a bug" return None elif len(commit_search_list) == 1: INFO ("returning the only commit passed to the binary_search is %s " % commit_search_list[0]) return commit_search_list[0] elif len(commit_search_list) == 2: if commit_search_list[0] in self.global_results_dict.keys(): if self.global_results_dict[commit_search_list[0]]: return commit_search_list[1] else: return commit_search_list[0] if commit_search_list[1] in self.global_results_dict.keys(): if self.global_results_dict[commit_search_list[1]]: return commit_search_list[0] else: return commit_search_list[1] # We reach here if we don't already have the test results for either of # the commits.Run the test on jita. response_map = {} job_ids_list = [None] self.run_test_on_jita(clusters[0], commit_search_list[0], build_url=self.commits_dict[commit_search_list[0]], job_ids_list=job_ids_list, index=0) response_map[commit_search_list[0]] = job_ids_list[0] self.wait_for_jita_test_results(response_map) if self.global_results_dict[commit_search_list[0]]: return commit_search_list[1] else: return commit_search_list[0] search_list_len = len(commit_search_list) # If there are less free clusters, let us do a binary search. if len(clusters) < search_list_len and len(clusters) >= 3: mid_commit_index = search_list_len / 2 first_half_mid_commit_index = mid_commit_index / 2 second_half_mid_commit_index = ( mid_commit_index + 1 + search_list_len) / 2 index_list = [mid_commit_index, first_half_mid_commit_index, second_half_mid_commit_index] response_map = {} threads = [None] * search_list_len job_ids_list = [None] * search_list_len for index in index_list: # If we already have the test result for this commit don't run the test # again. if commit_search_list[index] in self.global_results_dict.keys(): continue threads[index] = threading.Thread( target=self.run_test_on_jita, name="jita_thread", args=(clusters.pop(), commit_search_list[index], self.commits_dict[commit_search_list[index]], job_ids_list, index)) threads[index].start() for index in index_list: threads[index].join() if job_ids_list[index]: return None response_map[commit_search_list[index]] = job_ids_list[index] self.wait_for_jita_test_results(response_map) # Based on the test result, call the function again. if not self.global_results_dict[commit_search_list[ first_half_mid_commit_index]]: return self.binary_search_commit_using_jita( commit_search_list[0:(first_half_mid_commit_index+1)], input_type, clusters) elif not self.global_results_dict[commit_search_list[mid_commit_index]]: return self.binary_search_commit_using_jita( commit_search_list[first_half_mid_commit_index:(mid_commit_index+1)], test, username, node_pool, branch, hypervisor, input_type) elif not self.global_results_dict[commit_search_list[ second_half_mid_commit_index]]: return self.binary_search_commit_using_jita( commit_search_list[mid_commit_index:(second_half_mid_commit_index+1)], input_type, clusters) else: return self.binary_search_commit_using_jita( commit_search_list[second_half_mid_commit_index:], input_type, clusters) elif len(clusters) >= search_list_len: # We have enough clusters. Trigger all the runs in parallel. response_map = {} threads = [] job_ids_list = [None] * search_list_len for index in xrange(len(commit_search_list)): threads[index] = threading.Thread( target=self.run_test_on_jita, name="jita_thread", args=(clusters.pop(), commit_search_list[index], self.commits_dict[commit_search_list[index]], job_ids_list, index)) threads[index].start() for index in index_list: threads[index].join() response_map[commit_search_list[index]] = job_ids_list[index] self.wait_for_jita_test_results(response_map) for commit in commit_search_list: if not self.global_results_dict[commit]: return commit else: # We just have one cluster. This is going to take forever. mid_commit_index = search_list_len / 2 response_map = {} job_ids_list = [None] self.run_test_on_jita( clusters[0], commit_search_list[mid_commit_index], build_url=self.commits_dict[commit_search_list[mid_commit_index]], job_ids_list=job_ids_list, index=0) response_map[commit_search_list[mid_commit_index]] = job_ids_list[0] self.wait_for_jita_test_results(response_map) if not self.global_results_dict[commit_search_list[mid_commit_index]]: self.binary_search_commit_using_jita( commit_search_list[0:(mid_commit_index+1)], input_type, clusters) else: self.binary_search_commit_using_jita( commit_search_list[mid_commit_index:], input_type, clusters)