git cl: use explicit Gerrit mirrors on 404s during upload.

This will cycle through known gerrit mirrors for chromium-review host
if Gerrit HTTP RPC results in 404 after successful initial
git push refs/for/refs/...

Tested locally by intentionally using wrong change number (2000000):

    $ PATH=`pwd`:$PATH git cl upload --bypass-hooks
        ...
        remote: Processing changes: refs: 1, new: 1, done
        remote: SUCCESS
        remote: New Changes:
        remote:   https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/1227440

                * [new branch]        ea9aea5faa85b4b289b7add4bc6f4d5dd6a01caf -> refs/for/refs/heads/master%wip,m=Initial_upload,hashtag=git-cl

        WARNING:root:404 NotFound error occurred while querying POST https://chromium-review.googlesource.com/a/changes/chromium%2Ftools%2Fdepot_tools~2000000/revisions/current/review: Not Found
        WARNING:root:404 NotFound error occurred while querying POST https://ap1-mirror-chromium-review.googlesource.com/a/changes/chromium%2Ftools%2Fdepot_tools~2000000/revisions/current/review: Not Found
        WARNING:root:404 NotFound error occurred while querying POST https://us1-mirror-chromium-review.googlesource.com/a/changes/chromium%2Ftools%2Fdepot_tools~2000000/revisions/current/review: Not Found

        ^C

Bug: 881860
Change-Id: Iac7dbe4e35052007650a7a2646a394caed6bd400
Reviewed-on: https://chromium-review.googlesource.com/1227441
Commit-Queue: Andrii Shyshkalov <tandrii@chromium.org>
Reviewed-by: Edward Lesmes <ehmaldonado@chromium.org>
diff --git a/gerrit_util.py b/gerrit_util.py
index 6d45ab7..b81f7b5 100644
--- a/gerrit_util.py
+++ b/gerrit_util.py
@@ -16,6 +16,7 @@
 import logging
 import netrc
 import os
+import random
 import re
 import socket
 import stat
@@ -417,6 +418,17 @@
                 conn.req_host, conn.req_params['method'],
                 conn.req_params['uri'],
                 http_version, http_version, response.status, response.reason)
+    if response.status == 404:
+      # TODO(crbug/881860): remove this hack.
+      # HACK: try different Gerrit mirror as a workaround for potentially
+      # out-of-date mirror hit through default routing.
+      if conn.req_host == 'chromium-review.googlesource.com':
+        conn.req_params['uri'] = _UseGerritMirror(
+            conn.req_params['uri'], 'chromium-review.googlesource.com')
+        # And don't increase sleep_time in this case, since we suspect we've
+        # just asked wrong git mirror before.
+        sleep_time /= 2.0
+
     if TRY_LIMIT - idx > 1:
       LOGGER.info('Will retry in %d seconds (%d more times)...',
                   sleep_time, TRY_LIMIT - idx - 1)
@@ -914,3 +926,31 @@
   """
   assert int(change_number)
   return '%s~%s' % (urllib.quote(project, safe=''), change_number)
+
+
+# TODO(crbug/881860): remove this hack.
+_GERRIT_MIRROR_PREFIXES = ['us1', 'us2', 'us3']
+assert all(3 == len(p) for p in _GERRIT_MIRROR_PREFIXES)
+
+
+def _UseGerritMirror(url, host):
+  """Returns new url which uses randomly selected mirror for a gerrit host.
+
+  url's host should be for a given host or a result of prior call to this
+  function.
+
+  Assumes url has a single occurence of the host substring.
+  """
+  assert host in url
+  suffix = '-mirror-' + host
+  prefixes = set(_GERRIT_MIRROR_PREFIXES)
+  prefix_len = len(_GERRIT_MIRROR_PREFIXES[0])
+  st = url.find(suffix)
+  if st == -1:
+    actual_host = host
+  else:
+    # Already uses some mirror.
+    assert st >= prefix_len, (uri, host, st, prefix_len)
+    prefixes.remove(url[st-prefix_len:st])
+    actual_host = url[st-prefix_len:st+len(suffix)]
+  return url.replace(actual_host, random.choice(list(prefixes)) + suffix)