Exemplo n.º 1
0
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()
Exemplo n.º 2
0
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()
Exemplo n.º 3
0
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()
Exemplo n.º 4
0
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()