def handle_pending_transplants(dbconn): cursor = dbconn.cursor() now = datetime.datetime.now() query = """ SELECT id, destination, request FROM Transplant WHERE landed IS NULL AND (last_updated IS NULL OR last_updated<=%(time)s) ORDER BY created """ transplant_retry_delay = TRANSPLANT_RETRY_DELAY if config.testing(): transplant_retry_delay = datetime.timedelta(seconds=1) cursor.execute(query, ({'time': now - transplant_retry_delay})) current_treestatus = {} finished_revisions = [] mozreview_updates = [] retry_revisions = [] def handle_tree_retry(reason, transplant_id, tree, rev, destination, trysyntax): retry_revisions.append((now, transplant_id)) data = { 'request_id': transplant_id, 'tree': tree, 'rev': rev, 'destination': destination, 'trysyntax': trysyntax, 'landed': False, 'error_msg': '', 'result': reason, } mozreview_updates.append([transplant_id, json.dumps(data)]) # This code is a bit messy because we have to deal with the fact that the # the tree could close between the call to tree_is_open and when we # actually attempt the revision. # # We keep a list of revisions to retry called retry_revisions which we # append to whenever we detect a closed tree. These revisions have their # last_updated field updated so we will retry them after a suitable delay. # # The other list we keep is for transplant attempts that either succeeded # or failed due to a reason other than a closed tree, which is called # finished_revisions. Successful or not, we're finished with them, they # will not be retried. for row in cursor.fetchall(): transplant_id, destination, request = row # Many of these values are used as command arguments. So convert # to binary because command arguments aren't unicode. destination = destination.encode('ascii') requester = request['ldap_username'] tree = request['tree'].encode('ascii') rev = request['rev'].encode('ascii') trysyntax = request.get('trysyntax', '') push_bookmark = request.get('push_bookmark', '').encode('ascii') commit_descriptions = request.get('commit_descriptions') repo_config = config.get_repo(tree) if not repo_config['tree']: # Trees not present on treestatus cannot be closed. tree_open = True else: # When pushing to try we need to check if try is open, not the # tree for the source repo. tree_name = 'try' if trysyntax else repo_config['tree'] tree_open = current_treestatus.setdefault( destination, treestatus.tree_is_open(tree_name)) if not tree_open: handle_tree_retry('Tree %s is closed - retrying later.' % tree, transplant_id, tree, rev, destination, trysyntax) continue attempts = 0 started = datetime.datetime.now() landed = False while attempts < MAX_TRANSPLANT_ATTEMPTS: logger.info('initiating transplant from tree: %s rev: %s ' 'to destination: %s, attempt %s' % (tree, rev, destination, attempts + 1)) # TODO: We should break the transplant call into two steps, one # to pull down the commits to transplant, and another # one to rebase it and attempt to push so we don't # duplicate work unnecessarily if we have to rebase more # than once. os.environ['AUTOLAND_REQUEST_USER'] = requester try: with Transplant(tree, destination, rev) as tp: if trysyntax: result = tp.push_try(str(trysyntax)) elif push_bookmark: result = tp.push_bookmark(commit_descriptions, push_bookmark) else: result = tp.push(commit_descriptions) landed = True except Exception as e: result = str(e) landed = False finally: del os.environ['AUTOLAND_REQUEST_USER'] logger.info('transplant from tree: %s rev: %s attempt: %s: %s' % (tree, rev, attempts + 1, result)) if landed or 'abort: push creates new remote head' not in result: break attempts += 1 if landed: logger.info('transplant successful - new revision: %s' % result) else: if 'is CLOSED!' in result: reason = 'Tree %s is closed - retrying later.' % tree logger.info('transplant failed: %s' % reason) current_treestatus[destination] = False handle_tree_retry(reason, transplant_id, tree, rev, destination, trysyntax) continue elif 'APPROVAL REQUIRED' in result: reason = 'Tree %s is set to "approval required" - retrying ' \ 'later.' % tree logger.info('transplant failed: %s' % reason) current_treestatus[destination] = False handle_tree_retry(reason, transplant_id, tree, rev, destination, trysyntax) continue elif 'abort: push creates new remote head' in result: logger.info('transplant failed: we lost a push race') retry_revisions.append((now, transplant_id)) continue elif 'unresolved conflicts (see hg resolve' in result: logger.info('transplant failed - manual rebase required: ' 'tree: %s rev: %s destination: %s error: %s' % (tree, rev, destination, result)) # This is the only autoland error for which we expect the # user to take action. We should make things nicer than the # raw mercurial error. header = ("We're sorry, Autoland could not rebase your " "commits for you automatically. Please manually " "rebase your commits and try again.\n\n") result = header + result else: logger.info('transplant failed: tree: %s rev: %s ' 'destination: %s error: %s' % (tree, rev, destination, result)) completed = datetime.datetime.now() logger.info('elapsed transplant time: %s' % (completed - started)) # set up data to be posted back to mozreview data = { 'request_id': transplant_id, 'tree': tree, 'rev': rev, 'destination': destination, 'trysyntax': trysyntax, 'landed': landed, 'error_msg': '', 'result': '' } if landed: data['result'] = result else: data['error_msg'] = result mozreview_updates.append([transplant_id, json.dumps(data)]) finished_revisions.append([landed, result, transplant_id]) if retry_revisions: query = """ update Transplant set last_updated=%s where id=%s """ cursor.executemany(query, retry_revisions) dbconn.commit() if finished_revisions: query = """ update Transplant set landed=%s,result=%s where id=%s """ cursor.executemany(query, finished_revisions) dbconn.commit() if mozreview_updates: query = """ insert into MozreviewUpdate(transplant_id,data) values(%s,%s) """ cursor.executemany(query, mozreview_updates) dbconn.commit()
def handle_pending_transplants(logger, dbconn): cursor = dbconn.cursor() now = datetime.datetime.now() query = """ select id, destination, request from Transplant where landed is null and (last_updated is null or last_updated<=%(time)s) """ transplant_retry_delay = TRANSPLANT_RETRY_DELAY if config.testing(): transplant_retry_delay = datetime.timedelta(seconds=1) cursor.execute(query, ({'time': now - transplant_retry_delay})) current_treestatus = {} finished_revisions = [] mozreview_updates = [] retry_revisions = [] def handle_treeclosed(transplant_id, tree, rev, destination, trysyntax, pingback_url): retry_revisions.append((now, transplant_id)) data = { 'request_id': transplant_id, 'tree': tree, 'rev': rev, 'destination': destination, 'trysyntax': trysyntax, 'landed': False, 'error_msg': '', 'result': 'Tree %s is closed - retrying later.' % tree } mozreview_updates.append([transplant_id, json.dumps(data)]) # This code is a bit messy because we have to deal with the fact that the # the tree could close between the call to tree_is_open and when we # actually attempt the revision. # # We keep a list of revisions to retry called retry_revisions which we # append to whenever we detect a closed tree. These revisions have their # last_updated field updated so we will retry them after a suitable delay. # # The other list we keep is for transplant attempts that either succeeded # or failed due to a reason other than a closed tree, which is called # finished_revisions. Successful or not, we're finished with them, they # will not be retried. for row in cursor.fetchall(): transplant_id, destination, request = row requester = request['ldap_username'] tree = request['tree'] rev = request['rev'] trysyntax = request.get('trysyntax', '') push_bookmark = request.get('push_bookmark', '') pingback_url = request.get('pingback_url', '') commit_descriptions = request.get('commit_descriptions') tree_open = current_treestatus.setdefault(destination, treestatus.tree_is_open(destination)) if not tree_open: handle_treeclosed(transplant_id, tree, rev, destination, trysyntax, pingback_url) continue attempts = 0 logger.info('initiating transplant from tree: %s rev: %s ' 'to destination: %s' % (tree, rev, destination)) started = datetime.datetime.now() while attempts < MAX_TRANSPLANT_ATTEMPTS: # TODO: We should break the transplant call into two steps, one # to pull down the commits to transplant, and another # one to rebase it and attempt to push so we don't # duplicate work unnecessarily if we have to rebase more # than once. os.environ['AUTOLAND_REQUEST_USER'] = requester landed, result = transplant.transplant(logger, tree, destination, rev, trysyntax, push_bookmark, commit_descriptions) del os.environ['AUTOLAND_REQUEST_USER'] if landed or 'abort: push creates new remote head' not in result: break attempts += 1 if landed: logger.info('transplant successful - new revision: %s' % result) else: if 'is CLOSED!' in result: logger.info('transplant failed: tree: %s is closed - ' ' retrying later.' % tree) current_treestatus[destination] = False handle_treeclosed(transplant_id, tree, rev, destination, trysyntax, pingback_url) continue elif 'abort: push creates new remote head' in result: logger.info('transplant failed: we lost a push race') retry_revisions.append((now, transplant_id)) continue elif 'unresolved conflicts (see hg resolve' in result: logger.info('transplant failed - manual rebase required: ' 'tree: %s rev: %s destination: %s error: %s' % (tree, rev, destination, result)) # This is the only autoland error for which we expect the # user to take action. We should make things nicer than the # raw mercurial error. # TODO: sad trombone sound header = ('We\'re sorry, Autoland could not rebase your ' 'commits for you automatically. Please manually ' 'rebase your commits and try again.\n\n') result = header + result else: logger.info('transplant failed: tree: %s rev: %s ' 'destination: %s error: %s' % (tree, rev, destination, result)) completed = datetime.datetime.now() logger.info('elapsed transplant time: %s' % (completed - started)) # set up data to be posted back to mozreview data = { 'request_id': transplant_id, 'tree': tree, 'rev': rev, 'destination': destination, 'trysyntax': trysyntax, 'landed': landed, 'error_msg': '', 'result': '' } if landed: data['result'] = result else: data['error_msg'] = result mozreview_updates.append([transplant_id, json.dumps(data)]) finished_revisions.append([landed, result, transplant_id]) if retry_revisions: query = """ update Transplant set last_updated=%s where id=%s """ cursor.executemany(query, retry_revisions) dbconn.commit() if finished_revisions: query = """ update Transplant set landed=%s,result=%s where id=%s """ cursor.executemany(query, finished_revisions) dbconn.commit() if mozreview_updates: query = """ insert into MozreviewUpdate(transplant_id,data) values(%s,%s) """ cursor.executemany(query, mozreview_updates) dbconn.commit()
def handle_pending_transplants(dbconn): cursor = dbconn.cursor() now = datetime.datetime.now() query = """ SELECT id, destination, request FROM Transplant WHERE landed IS NULL AND (last_updated IS NULL OR last_updated<=%(time)s) ORDER BY created """ transplant_retry_delay = TRANSPLANT_RETRY_DELAY if config.testing(): transplant_retry_delay = datetime.timedelta(seconds=1) cursor.execute(query, ({"time": now - transplant_retry_delay})) current_treestatus = {} finished_revisions = [] mozreview_updates = [] retry_revisions = [] def handle_tree_retry(reason, transplant_id, tree, rev, destination, trysyntax): retry_revisions.append((now, transplant_id)) data = { "request_id": transplant_id, "tree": tree, "rev": rev, "destination": destination, "trysyntax": trysyntax, "landed": False, "error_msg": "", "result": reason, } mozreview_updates.append([transplant_id, json.dumps(data)]) # This code is a bit messy because we have to deal with the fact that the # the tree could close between the call to tree_is_open and when we # actually attempt the revision. # # We keep a list of revisions to retry called retry_revisions which we # append to whenever we detect a closed tree. These revisions have their # last_updated field updated so we will retry them after a suitable delay. # # The other list we keep is for transplant attempts that either succeeded # or failed due to a reason other than a closed tree, which is called # finished_revisions. Successful or not, we're finished with them, they # will not be retried. for row in cursor.fetchall(): transplant_id, destination, request = row # Many of these values are used as command arguments. So convert # to binary because command arguments aren't unicode. destination = destination.encode("ascii") requester = request["ldap_username"] tree = request["tree"].encode("ascii") rev = request["rev"].encode("ascii") trysyntax = request.get("trysyntax", "") push_bookmark = request.get("push_bookmark", "").encode("ascii") commit_descriptions = request.get("commit_descriptions") patch_urls = [u.encode("ascii") for u in request.get("patch_urls", [])] repo_config = config.get_repo(tree) if trysyntax: # When pushing to try we need to check if try is open, not the # tree for the source repo. tree_name = "try" else: tree_name = repo_config.get("tree") if not tree_name: # Trees not present on treestatus cannot be closed. tree_open = True else: tree_open = current_treestatus.setdefault( destination, treestatus.tree_is_open(tree_name)) if not tree_open: handle_tree_retry( "Tree %s is closed - retrying later." % tree, transplant_id, tree, rev, destination, trysyntax, ) continue attempts = 0 started = datetime.datetime.now() landed = False while attempts < MAX_TRANSPLANT_ATTEMPTS: logger.info("initiating transplant from tree: %s rev: %s " "to destination: %s, attempt %s" % (tree, rev, destination, attempts + 1)) os.environ["AUTOLAND_REQUEST_USER"] = requester try: if config.testing() and request.get("patch"): tp = PatchTransplant( tree, destination, rev, None, base64.b64decode(request.get("patch")), ) elif patch_urls: tp = PatchTransplant(tree, destination, rev, patch_urls) else: tp = RepoTransplant(tree, destination, rev, commit_descriptions) with tp: if trysyntax: result = tp.push_try(str(trysyntax)) elif push_bookmark: result = tp.push_bookmark(push_bookmark) else: result = tp.push() landed = True except Exception as e: logger.exception(e) result = str(e) landed = False finally: del os.environ["AUTOLAND_REQUEST_USER"] logger.info("transplant from tree: %s rev: %s attempt: %s: %s" % (tree, rev, attempts + 1, result)) if landed or "abort: push creates new remote head" not in result: break attempts += 1 if landed: logger.info("transplant successful - new revision: %s" % result) else: if "is CLOSED!" in result: reason = "Tree %s is closed - retrying later." % tree logger.info("transplant failed: %s" % reason) current_treestatus[destination] = False handle_tree_retry(reason, transplant_id, tree, rev, destination, trysyntax) continue elif "APPROVAL REQUIRED" in result: reason = ('Tree %s is set to "approval required" - retrying ' "later." % tree) logger.info("transplant failed: %s" % reason) current_treestatus[destination] = False handle_tree_retry(reason, transplant_id, tree, rev, destination, trysyntax) continue elif ("abort: push creates new remote head" in result or "repository changed while pushing" in result): logger.info("transplant failed: we lost a push race") logger.info(result) retry_revisions.append((now, transplant_id)) continue elif ("unresolved conflicts (see hg resolve" in result or "hunk FAILED -- saving rejects to file" in result or "hunks FAILED -- saving rejects to file" in result): logger.info("transplant failed - manual rebase required: " "tree: %s rev: %s destination: %s error: %s" % (tree, rev, destination, result)) # This is the only autoland error for which we expect the # user to take action. We should make things nicer than the # raw mercurial error. header = ("We're sorry, Autoland could not rebase your " "commits for you automatically. Please manually " "rebase your commits and try again.\n\n") result = header + result else: logger.info("transplant failed: tree: %s rev: %s " "destination: %s error: %s" % (tree, rev, destination, result)) completed = datetime.datetime.now() logger.info("elapsed transplant time: %s" % (completed - started)) # set up data to be posted back to mozreview data = { "request_id": transplant_id, "tree": tree, "rev": rev, "destination": destination, "trysyntax": trysyntax, "landed": landed, "error_msg": "", "result": "", } if landed: data["result"] = result else: data["error_msg"] = result mozreview_updates.append([transplant_id, json.dumps(data)]) finished_revisions.append([landed, result, transplant_id]) if retry_revisions: query = """ update Transplant set last_updated=%s where id=%s """ cursor.executemany(query, retry_revisions) dbconn.commit() if finished_revisions: query = """ update Transplant set landed=%s,result=%s where id=%s """ cursor.executemany(query, finished_revisions) dbconn.commit() if mozreview_updates: query = """ insert into MozreviewUpdate(transplant_id,data) values(%s,%s) """ cursor.executemany(query, mozreview_updates) dbconn.commit()
def handle_pending_transplants(logger, dbconn): cursor = dbconn.cursor() query = """ select id,tree,rev,destination,trysyntax,push_bookmark,pingback_url from Transplant where landed is null """ cursor.execute(query) landed_revisions = [] mozreview_updates = [] for row in cursor.fetchall(): (transplant_id, tree, rev, destination, trysyntax, push_bookmark, pingback_url) = row if not treestatus.tree_is_open(destination): continue landed, result = transplant.transplant(tree, destination, rev, trysyntax, push_bookmark) if landed: logger.info(('transplanted from tree: %s rev: %s' ' to destination: %s new revision: %s') % (tree, rev, destination, result)) else: if 'is CLOSED!' in result: logger.info('transplant failed: tree: %s is closed - ' ' retrying later.' % tree) # continuing here will skip updating the autoland request # so we will attempt to land it again later. continue else: logger.info('transplant failed: tree: %s rev: %s ' 'destination: %s error: %s' % (tree, rev, destination, result)) # set up data to be posted back to mozreview data = { 'request_id': transplant_id, 'tree': tree, 'rev': rev, 'destination': destination, 'trysyntax': trysyntax, 'landed': landed, 'error_msg': '', 'result': '' } if landed: data['result'] = result else: data['error_msg'] = result mozreview_updates.append([transplant_id, pingback_url, json.dumps(data)]) landed_revisions.append([landed, result, transplant_id]) if landed_revisions: query = """ update Transplant set landed=%s,result=%s where id=%s """ cursor.executemany(query, landed_revisions) dbconn.commit() if mozreview_updates: query = """ insert into MozreviewUpdate(request_id,pingback_url,data) values(%s,%s,%s) """ cursor.executemany(query, mozreview_updates) dbconn.commit()