mesos-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From andsc...@apache.org
Subject [mesos] branch master updated: Defaulted support scripts to Python 3.
Date Tue, 04 Sep 2018 20:48:05 GMT
This is an automated email from the ASF dual-hosted git repository.

andschwa pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mesos.git


The following commit(s) were added to refs/heads/master by this push:
     new 9c7eb90  Defaulted support scripts to Python 3.
9c7eb90 is described below

commit 9c7eb909aad99e6ea6de0b1fd2a55a798764b00b
Author: Armand Grillet <agrillet@mesosphere.io>
AuthorDate: Tue Sep 4 13:46:23 2018 -0700

    Defaulted support scripts to Python 3.
    
    Use the new Python 3 support scripts by default, removing the deprecated
    ones using Python 2. The new scripts offer feature-parity and more, they
    require Python 3.6 or newer in order to run.
    
    Review: https://reviews.apache.org/r/68619/
---
 docs/windows.md                            |   2 +-
 support/README.md                          |   6 +-
 support/apply-reviews.py                   |  41 +-
 support/build-virtualenv                   |  28 +-
 support/check-python3.py                   |  74 ----
 support/{python3 => }/common.py            |   0
 support/generate-endpoint-help.py          |  29 +-
 support/{python3 => }/get-review-ids.py    |   0
 support/hooks/post-rewrite                 |  14 +-
 support/hooks/pre-commit                   |  13 +-
 support/jsonurl.py                         |  21 +-
 support/mesos-gtest-runner.py              | 129 +++----
 support/mesos-split.py                     |  23 +-
 support/mesos-style.py                     | 182 ++++++---
 support/pip-requirements.txt               |   5 +-
 support/{python3 => }/post-build-result.py |   0
 support/post-reviews.py                    |  82 ++--
 support/push-commits.py                    | 103 ++---
 support/python3/apply-reviews.py           | 453 ----------------------
 support/python3/generate-endpoint-help.py  | 405 -------------------
 support/python3/jsonurl.py                 |  55 ---
 support/python3/mesos-gtest-runner.py      | 293 --------------
 support/python3/mesos-split.py             |  79 ----
 support/python3/mesos-style.py             | 597 -----------------------------
 support/python3/post-reviews.py            | 433 ---------------------
 support/python3/push-commits.py            | 167 --------
 support/python3/test-upgrade.py            | 254 ------------
 support/python3/verify-reviews.py          | 297 --------------
 support/test-upgrade.py                    |  46 +--
 support/verify-reviews.py                  | 275 ++++++-------
 30 files changed, 494 insertions(+), 3612 deletions(-)

diff --git a/docs/windows.md b/docs/windows.md
index 2edab6d..35b12dd 100644
--- a/docs/windows.md
+++ b/docs/windows.md
@@ -26,7 +26,7 @@ Mesos 1.0.0 introduced experimental support for Windows.
    For example, `C:/Program Files (x86)/mesos` is an invalid build directory.
 
 6. If developing Mesos, install [Python 3](https://www.python.org/downloads/)
-   (not Python 2), in order to use our `support/python3` scripts (e.g.
+   (not Python 2), in order to use our `support` scripts (e.g.
    to post and apply patches, or lint source code).
 
 ### Build Instructions
diff --git a/support/README.md b/support/README.md
index 0eae4d3..66256be 100644
--- a/support/README.md
+++ b/support/README.md
@@ -1,10 +1,6 @@
 # Supporting tools
 
-This directory contains various helper scripts.
+This directory contains various helper scripts requiring Python 3.
 
 The scripts directly in this directory are intended to be used by Mesos
 developers on the command-line.
-
-**To use the Python 3 version of these scripts, set the environment
-variable `MESOS_SUPPORT_PYTHON` to `3` and run again the script
-`support/build-virtualenv`.**
diff --git a/support/apply-reviews.py b/support/apply-reviews.py
index de22b70..92ad859 100755
--- a/support/apply-reviews.py
+++ b/support/apply-reviews.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 #
 # Licensed to the Apache Software Foundation (ASF) under one
 # or more contributor license agreements.  See the NOTICE file
@@ -32,7 +32,9 @@ import re
 import ssl
 import subprocess
 import sys
-import urllib2
+import urllib.request
+import urllib.error
+import urllib.parse
 
 
 REVIEWBOARD_REVIEW_URL = 'https://reviews.apache.org/r'
@@ -83,7 +85,7 @@ def patch_url(options):
         return '{base}/{review}/diff/raw/'.format(
             base=REVIEWBOARD_REVIEW_URL,
             review=options['review_id'])
-    elif options['github']:
+    if options['github']:
         return '{base}/{patch}.patch'.format(
             base=GITHUB_PATCH_URL,
             patch=options['github'])
@@ -92,7 +94,7 @@ def patch_url(options):
 
 def url_to_json(url):
     """Performs HTTP request and returns JSON-ified response."""
-    json_str = urllib2.urlopen(url)
+    json_str = urllib.request.urlopen(url)
     return json.loads(json_str.read())
 
 
@@ -101,8 +103,7 @@ def extract_review_id(url):
     review_id = re.search(REVIEWBOARD_API_URL + r'/(\d+)/', url)
     if review_id:
         return review_id.group(1)
-    return None
-
+    return ''
 
 def review_chain(review_id):
     """Returns a parent review chain for a given review ID."""
@@ -131,10 +132,9 @@ def review_chain(review_id):
         review = (review_id, json_obj.get('review_request').get('summary'))
         if review not in review_list:
             return review_list + [review]
-        else:
-            sys.stderr.write('Found a circular dependency in the chain starting'
-                             ' at {review}\n'.format(review=review_id))
-            sys.exit(1)
+        sys.stderr.write('Found a circular dependency in the chain starting'
+                         ' at {review}\n'.format(review=review_id))
+        sys.exit(1)
 
 
 def shell(command, dry_run):
@@ -143,7 +143,7 @@ def shell(command, dry_run):
     is set (in which case it just prints the command).
     """
     if dry_run:
-        print command
+        print(command)
         return
 
     error_code = subprocess.call(command, stderr=subprocess.STDOUT, shell=True)
@@ -196,7 +196,7 @@ def fetch_patch(options):
     # pylint: disable=unexpected-keyword-arg
     if platform.system() == 'Windows':
         # This call requires Python >= 2.7.9.
-        response = urllib2.urlopen(
+        response = urllib.request.urlopen(
             patch_url(options),
             context=ssl_create_default_context())
 
@@ -283,10 +283,10 @@ def commit_patch(options):
                 True)
         message.write(data['message'])
 
-    cmd = u'git commit' \
-          u' --author \"{author}\"' \
-          u' {amend} -aF \"{message}\"' \
-          u' {verify}'.format(
+    cmd = 'git commit' \
+          ' --author \"{author}\"' \
+          ' {amend} -aF \"{message}\"' \
+          ' {verify}'.format(
               author=quote(data['author']),
               amend=amend,
               message=message_file,
@@ -302,7 +302,7 @@ def patch_data(options):
     """
     if options['review_id']:
         return reviewboard_data(options)
-    elif options['github']:
+    if options['github']:
         return github_data(options)
     return None
 
@@ -357,7 +357,7 @@ def reviewboard_data(options):
         message_data.append(review.get('description'))
     message_data.append('Review: {review_url}'.format(review_url=url))
 
-    author = u'{author} <{email}>'.format(
+    author = '{author} <{email}>'.format(
         author=user.get('fullname'),
         email=user.get('email'))
     message = '\n\n'.join(message_data)
@@ -444,11 +444,6 @@ def main():
     """
     options = parse_options()
 
-    # TODO(ArmandGrillet): Remove this when we'll have switched to Python 3.
-    dir_path = os.path.dirname(os.path.realpath(__file__))
-    script_path = os.path.join(dir_path, 'check-python3.py')
-    subprocess.call('python ' + script_path, shell=True, cwd=dir_path)
-
     if options['review_id']:
         reviewboard(options)
     else:
diff --git a/support/build-virtualenv b/support/build-virtualenv
index 90e1fc8..7dc03b0 100755
--- a/support/build-virtualenv
+++ b/support/build-virtualenv
@@ -21,7 +21,7 @@ PYTHONPATH=""
 # bail out and advise the user to deactivate.
 OLD_VIRTUAL_ENV="${VIRTUAL_ENV}"
 if [ "${OLD_VIRTUAL_ENV}" != "" ]; then
-  echo "Please deactivate your current virtual environment in order to continue!"
+  echo "Please deactivate your current virtual environment in order to continue."
   echo "source deactivate"
   exit 1
 fi
@@ -50,32 +50,8 @@ if [ "${PYTHON_MAJOR}" = "3" ]; then
     # Set up a virtual environment for the linters.
     ${PYTHON} -m venv --prompt="${VIRTUALENV_NAME}" ${VIRTUALENV_DIRECTORY}
   fi
-elif [ "${PYTHON_MAJOR}" = "2" ]; then
-  if [ "${PYTHON_MINOR}" -lt "6" ]; then
-    echo "You must be running python 2.6 or 2.7 in order to continue."
-    echo "Consider running as 'PYTHON=python2 ${0} or similar."
-    exit 1
-  else
-    if [ "${VIRTUALENV}" = "" ]; then
-      # Search for a locally installed virtualenv.
-      # See https://docs.python.org/2/library/site.html#site.USER_SITE for details.
-      VIRTUALENV=$(${PYTHON} -c "import site; print site.USER_SITE")/virtualenv.py
-    fi
-
-    if [ ! -f "${VIRTUALENV}" ]; then
-      echo "You must have virtualenv installed in order to continue..."
-      exit 1
-    fi
-
-    # Set up a virtual environment for the linters.
-    ${PYTHON} ${VIRTUALENV} --python=${PYTHON} \
-                      --no-site-packages \
-                      --prompt="(${VIRTUALENV_NAME}) " \
-                      ${VIRTUALENV_DIRECTORY}
-  fi
 else
-  echo "You must be running either python 2.6, 2.7,"
-  echo "or python 3.6 or newer in order to continue."
+  echo "You must be running python 3.6 or newer in order to continue."
   echo "Consider running as 'PYTHON=python3 ${0}' or similar."
   exit 1
 fi
diff --git a/support/check-python3.py b/support/check-python3.py
deleted file mode 100644
index 898e543..0000000
--- a/support/check-python3.py
+++ /dev/null
@@ -1,74 +0,0 @@
-#!/usr/bin/env python
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""
-Checks if Python 3.6 is available on the machine.
-"""
-# pylint: disable=superfluous-parens
-
-from __future__ import print_function
-
-import os
-import subprocess
-import sys
-
-def _print(message):
-  print(message, file=sys.stderr)
-
-def print_error():
-    """Prints a warning requesting to install Python 3.6."""
-    _print("The support scripts will be upgraded to Python 3 by July 1st.")
-    _print("Make sure to install Python 3.6 on your machine before.")
-
-def print_warning():
-    """Prints a warning requesting to use the Python 3 scripts."""
-    _print("Congratulations! You have Python 3 installed correctly.")
-    _print("Please start using the scripts in `support/python3`.")
-    # NOTE: This is only either unset, or set to 3.
-    if "MESOS_SUPPORT_PYTHON" not in os.environ:
-        _print("Please also set the environment variable `MESOS_SUPPORT_PYTHON` to `3`")
-        _print("so that the Git hooks use the Python 3 scripts.")
-
-if sys.version_info[0] < 3:
-    # On Windows, system-wide installations of Python 3.6 gives a tools called
-    # py and that we can use to know if Python 3 is installed.
-    if os.name == "nt":
-        PY = subprocess.call(["WHERE", "py"], stdout=open(os.devnull, "wb"))
-    else:
-        # We are not using Python 3 as python, let's check if python3 exists.
-        PY = subprocess.call(["which", "python3"],
-                             stdout=open(os.devnull, "wb"))
-    if PY != 0:
-        print_error()
-    else:
-        # It does exist, let's check its version.
-        if os.name == "nt":
-            VERSION = subprocess.check_output("py -3 --version", shell=True)
-        else:
-            VERSION = subprocess.check_output("python3 --version", shell=True)
-        # x goes from 0 to 5 so that we can check for Python < 3.6.
-        for x in range(0, 6):
-            if "3.%d." % (x) in VERSION:
-                print_error()
-                sys.exit()
-        # This script only gets invoked by the Python 2 scripts, so we
-        # can assume we need to warn the user to start using the
-        # Python 3 scripts.
-        print_warning()
-elif sys.version_info[1] < 6:
-    # python is by default Python 3 but it's < 3.6.
-    print_error()
diff --git a/support/python3/common.py b/support/common.py
similarity index 100%
rename from support/python3/common.py
rename to support/common.py
diff --git a/support/generate-endpoint-help.py b/support/generate-endpoint-help.py
index 8b38f73..91c13d7 100755
--- a/support/generate-endpoint-help.py
+++ b/support/generate-endpoint-help.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 #
 # Licensed to the Apache Software Foundation (ASF) under one
 # or more contributor license agreements.  See the NOTICE file
@@ -31,7 +31,9 @@ import shutil
 import subprocess
 import sys
 import time
-import urllib2
+import urllib.request
+import urllib.error
+import urllib.parse
 
 
 # The host ip and master and agent ports.
@@ -79,7 +81,7 @@ RECEIVE_TIMEOUT = 10
 RETRY_INTERVAL = 0.10
 
 
-class Subprocess(object):
+class Subprocess():
     """The process running using this script."""
     def __init__(self):
         self.current = None
@@ -92,12 +94,14 @@ class Subprocess(object):
 
 # A pointer to the top level directory of the mesos project.
 GIT_TOP_DIR = subprocess.check_output(
-    ['git', 'rev-parse', '--show-cdup']).strip()
+    ['git',
+     'rev-parse',
+     '--show-cdup']).decode(sys.stdout.encoding).strip()
 
 with open(os.path.join(GIT_TOP_DIR, 'CHANGELOG'), 'r') as f:
     if 'mesos' not in f.readline().lower():
-        print >> sys.stderr, ('You must run this command from within'
-                              ' the Mesos source repository!')
+        print(('You must run this command from within'
+               ' the Mesos source repository!'), file=sys.stderr)
         sys.exit(1)
 
 
@@ -139,14 +143,14 @@ def get_url_until_success(url):
     time_spent = 0
     while time_spent < RECEIVE_TIMEOUT:
         try:
-            helps = urllib2.urlopen(url)
+            helps = urllib.request.urlopen(url)
             break
         except Exception:
             time.sleep(RETRY_INTERVAL)
             time_spent += RETRY_INTERVAL
 
     if time_spent >= RECEIVE_TIMEOUT:
-        print >> sys.stderr, 'Timeout attempting to hit url: %s' % (url)
+        print('Timeout attempting to hit url: %s' % (url), file=sys.stderr)
         sys.exit(1)
 
     return helps.read()
@@ -180,7 +184,7 @@ def get_endpoint_path(p_id, name):
     """
     # Tokenize the endpoint by '/' (filtering
     # out any empty strings between '/'s)
-    path_parts = filter(None, name.split('/'))
+    path_parts = [_f for _f in name.split('/') if _f]
 
     # Conditionally prepend the 'id' to the list of path parts.
     # Following the notion of a 'delegate' in Mesos, we want our
@@ -219,7 +223,7 @@ def get_relative_md_path(p_id, name):
 
 def write_markdown(path, output, title):
     """Writes 'output' to the file at 'path'."""
-    print 'generating: %s' % (path)
+    print('generating: %s' % (path))
 
     dirname = os.path.dirname(path)
     if not os.path.exists(dirname):
@@ -377,11 +381,6 @@ def main():
     # A dictionary of the command line options passed in.
     options = parse_options()
 
-    # TODO(ArmandGrillet): Remove this when we'll have switched to Python 3.
-    dir_path = os.path.dirname(os.path.realpath(__file__))
-    script_path = os.path.join(dir_path, 'check-python3.py')
-    subprocess.call('python ' + script_path, shell=True, cwd=dir_path)
-
     # A pointer to the current subprocess for the master or agent.
     # This is useful for tracking the master or agent subprocesses so
     # that we can kill them if the script exits prematurely.
diff --git a/support/python3/get-review-ids.py b/support/get-review-ids.py
similarity index 100%
rename from support/python3/get-review-ids.py
rename to support/get-review-ids.py
diff --git a/support/hooks/post-rewrite b/support/hooks/post-rewrite
index 81f3454..1ab14ab 100755
--- a/support/hooks/post-rewrite
+++ b/support/hooks/post-rewrite
@@ -30,21 +30,11 @@ if [ "$ADDED_OR_MODIFIED" ]; then
     # many implementations do not support the `-r` flag, (which instructs
     # `xargs` to not run the script if the arguments are empty), so we also
     # cannot use that.
-    # TODO(ArmandGrillet): Remove the if to really switch to Python 3.
-    if [ "$MESOS_SUPPORT_PYTHON" = "3" ]; then
-        ./support/python3/mesos-style.py $ADDED_OR_MODIFIED || exit 1
-    else
-       ./support/mesos-style.py $ADDED_OR_MODIFIED || exit 1
-    fi
+    ./support/mesos-style.py $ADDED_OR_MODIFIED || exit 1
 fi
 
 # Check that the commits are properly split between mesos, libprocess and stout.
 ## In git, '@' represent current head, '@~' represent previous commit. We check
 ## the style of changes between current head and previous commit after a commit
 ## is rewritten.
-# TODO(ArmandGrillet): Remove the if to really switch to Python 3.
-if [ "$MESOS_SUPPORT_PYTHON" = "3" ]; then
-    git diff --name-only --diff-filter=AMD @~..@ | xargs ./support/python3/mesos-split.py || exit 1
-else
-    git diff --name-only --diff-filter=AMD @~..@ | xargs ./support/mesos-split.py || exit 1
-fi
+git diff --name-only --diff-filter=AMD @~..@ | xargs ./support/mesos-split.py || exit 1
diff --git a/support/hooks/pre-commit b/support/hooks/pre-commit
index ac0ff24..519567b 100755
--- a/support/hooks/pre-commit
+++ b/support/hooks/pre-commit
@@ -33,18 +33,9 @@ if [ -n "$ADDED_OR_MODIFIED" ]; then
     # many implementations do not support the `-r` flag, (which instructs
     # `xargs` to not run the script if the arguments are empty), so we also
     # cannot use that.
-    # TODO(ArmandGrillet): Remove the if to really switch to Python 3.
-    if [ "$MESOS_SUPPORT_PYTHON" = "3" ]; then
-        ./support/python3/mesos-style.py $ADDED_OR_MODIFIED || exit 1
-    else
-       ./support/mesos-style.py $ADDED_OR_MODIFIED || exit 1
-    fi
+    ./support/mesos-style.py $ADDED_OR_MODIFIED || exit 1
 fi
 
 # Check that the commits are properly split between mesos, libprocess and stout.
 # TODO(ArmandGrillet): Remove the if to really switch to Python 3.
-if [ "$MESOS_SUPPORT_PYTHON" = "3" ]; then
-    git diff --cached --name-only --diff-filter=AMD | xargs ./support/python3/mesos-split.py || exit 1
-else
-    git diff --cached --name-only --diff-filter=AMD | xargs ./support/mesos-split.py || exit 1
-fi
+git diff --cached --name-only --diff-filter=AMD | xargs ./support/mesos-split.py || exit 1
diff --git a/support/jsonurl.py b/support/jsonurl.py
index c9cf1d9..ffa3e36 100755
--- a/support/jsonurl.py
+++ b/support/jsonurl.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 #
 # Licensed to the Apache Software Foundation (ASF) under one
 # or more contributor license agreements.  See the NOTICE file
@@ -25,36 +25,31 @@ play off of 'curl'.
 """
 
 import json
-import os
-import subprocess
 import sys
-import urllib2
+import urllib.request
+import urllib.error
+import urllib.parse
 
 
 def main():
     """Expects at least one argument on the command line."""
     if len(sys.argv) < 2:
-        print >> sys.stderr, "USAGE: {} URL [KEY...]".format(sys.argv[0])
+        print("USAGE: {} URL [KEY...]".format(sys.argv[0]), file=sys.stderr)
         sys.exit(1)
 
-    # TODO(ArmandGrillet): Remove this when we'll have switched to Python 3.
-    dir_path = os.path.dirname(os.path.realpath(__file__))
-    script_path = os.path.join(dir_path, 'check-python3.py')
-    subprocess.call('python ' + script_path, shell=True, cwd=dir_path)
-
     url = sys.argv[1]
 
-    data = json.loads(urllib2.urlopen(url).read())
+    data = json.loads(urllib.request.urlopen(url).read())
 
     for arg in sys.argv[2:]:
         try:
             temp = data[arg]
             data = temp
         except KeyError:
-            print >> sys.stderr, "'" + arg + "' was not found"
+            print("'" + arg + "' was not found", file=sys.stderr)
             sys.exit(1)
 
-    print data.encode("utf-8")
+    print(data.encode("utf-8"))
 
 if __name__ == '__main__':
     main()
diff --git a/support/mesos-gtest-runner.py b/support/mesos-gtest-runner.py
index 9cabbdf..9cf72af 100755
--- a/support/mesos-gtest-runner.py
+++ b/support/mesos-gtest-runner.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 #
 # Licensed to the Apache Software Foundation (ASF) under one
 # or more contributor license agreements.  See the NOTICE file
@@ -27,11 +27,8 @@ program invocations ("shards"). This script provides a convenient
 wrapper around that functionality and stream-lined output.
 """
 
-
-from __future__ import print_function
-
+import argparse
 import multiprocessing
-import optparse
 import os
 import shlex
 import signal
@@ -39,10 +36,7 @@ import subprocess
 import sys
 
 
-DEFAULT_NUM_JOBS = int(multiprocessing.cpu_count() * 1.5)
-
-
-class Bcolors(object):
+class Bcolors:
     """
     A collection of tty output modifiers.
 
@@ -98,80 +92,90 @@ def run_test(opts):
         sys.stdout.flush()
         return False, error.output
 
-
 def parse_arguments():
     """Return the executable to work on, and a list of options."""
-    parser = optparse.OptionParser(
-        usage='Usage: %prog [options] <test> [-- <test_options>]')
 
-    parser.add_option(
-        '-j', '--jobs', type='int',
-        default=DEFAULT_NUM_JOBS,
+    # If the environment variable `MESOS_GTEST_RUNNER_FLAGS` is set we
+    # use it to set a default set of flags to pass. Flags passed on
+    # the command line always have precedence over these defaults.
+    #
+    # We manually construct `args` here and make use of the fact that
+    # in `optparser`'s implementation flags passed later on the
+    # command line overrule identical flags passed earlier.
+
+    env_var = ''
+    if 'MESOS_GTEST_RUNNER_FLAGS' in os.environ:
+        env_var = os.environ['MESOS_GTEST_RUNNER_FLAGS']
+
+    env_parser = argparse.ArgumentParser()
+    env_parser.add_argument('-j', '--jobs', type=int,
+                            default=int(multiprocessing.cpu_count() * 1.5))
+    env_parser.add_argument('-s', '--sequential', type=str, default='')
+    env_parser.add_argument('-v', '--verbosity', type=int, default=1)
+
+    env_args = env_parser.parse_args(shlex.split(env_var))
+
+    # We have set the default values using the environment variable if
+    # possible, we can now parse the arguments.
+    parser = argparse.ArgumentParser()
+    parser.add_argument(
+        '-j', '--jobs', type=int,
+        default=env_args.jobs,
         help='number of parallel jobs to spawn. DEFAULT: {default_}'
-        .format(default_=DEFAULT_NUM_JOBS))
+        .format(default_=env_args.jobs))
 
-    parser.add_option(
-        '-s', '--sequential', type='string',
-        default='',
+    parser.add_argument(
+        '-s', '--sequential', type=str,
+        default=env_args.sequential,
         help='gtest filter for tests to run sequentially')
 
-    parser.add_option(
-        '-v', '--verbosity', type='int',
-        default=1,
+    parser.add_argument(
+        '-v', '--verbosity', type=int,
+        default=env_args.verbosity,
         help='output verbosity:'
         ' 0 only shows summarized information,'
         ' 1 also shows full logs of failed shards, and anything'
         ' >1 shows all output. DEFAULT: 1')
+    parser.add_argument("executable", help="the executable to work on")
 
     parser.epilog = (
         'The environment variable MESOS_GTEST_RUNNER_FLAGS '
         'can be used to set a default set of flags. Flags passed on the '
         'command line always have precedence over these defaults.')
 
-    # If the environment variable `MESOS_GTEST_RUNNER_FLAGS` is set we
-    # use it to set a default set of flags to pass. Flags passed on
-    # the command line always have precedence over these defaults.
-    #
-    # We manually construct `args` here and make use of the fact that
-    # in `optparser`'s implementation flags passed later on the
-    # command line overrule identical flags passed earlier.
-    args = []
-    if 'MESOS_GTEST_RUNNER_FLAGS' in os.environ:
-        args.extend(shlex.split(os.environ['MESOS_GTEST_RUNNER_FLAGS']))
-    args.extend(sys.argv[1:])
+    args = parser.parse_args()
 
-    (options, executable) = parser.parse_args(args)
 
-    if not executable:
+    if not args.executable:
         parser.print_usage()
         sys.exit(1)
 
-    if not os.path.isfile(executable[0]):
+    if not os.path.isfile(args.executable):
         print(
             Bcolors.colorize(
                 "ERROR: File '{file}' does not exists"
-                .format(file=executable[0]), Bcolors.FAIL),
+                .format(file=args.executable), Bcolors.FAIL),
             file=sys.stderr)
         sys.exit(1)
 
-    if not os.access(executable[0], os.X_OK):
+    if not os.access(args.executable, os.X_OK):
         print(
             Bcolors.colorize(
                 "ERROR: File '{file}' is not executable"
-                .format(file=executable[0]), Bcolors.FAIL),
+                .format(file=args.executable), Bcolors.FAIL),
             file=sys.stderr)
         sys.exit(1)
 
-    if options.sequential and options.sequential.count(':-'):
+    if args.sequential and args.sequential.count(':-'):
         print(
             Bcolors.colorize(
                 "ERROR: Cannot use negative filters in "
                 "'sequential' parameter: '{filter}'"
-                .format(filter=options.sequential), Bcolors.FAIL),
+                .format(filter=args.sequential), Bcolors.FAIL),
             file=sys.stderr)
         sys.exit(1)
 
-    if options.sequential and os.environ.get('GTEST_FILTER') and \
+    if args.sequential and os.environ.get('GTEST_FILTER') and \
             os.environ['GTEST_FILTER'].count(':-'):
         print(
             Bcolors.colorize(
@@ -184,39 +188,34 @@ def parse_arguments():
 
     # Since empty strings are falsy, directly compare against `None`
     # to preserve an empty string passed via `GTEST_FILTER`.
-    if os.environ.get('GTEST_FILTER') != None:
-        options.parallel = '{env_filter}:-{sequential_filter}'\
+    if os.environ.get('GTEST_FILTER') is not None:
+        args.parallel = '{env_filter}:-{sequential_filter}'\
                          .format(env_filter=os.environ['GTEST_FILTER'],
-                                 sequential_filter=options.sequential)
+                                 sequential_filter=args.sequential)
     else:
-        options.parallel = '*:-{sequential_filter}'\
-                         .format(sequential_filter=options.sequential)
+        args.parallel = '*:-{sequential_filter}'\
+                         .format(sequential_filter=args.sequential)
 
-    return executable, options
+    executable = args.executable
+    delattr(args, 'executable')
+    return executable, args
 
 
 if __name__ == '__main__':
     EXECUTABLE, OPTIONS = parse_arguments()
 
-    # TODO(ArmandGrillet): Remove this when we'll have switched to Python 3.
-    dir_path = os.path.dirname(os.path.realpath(__file__))
-    script_path = os.path.join(dir_path, 'check-python3.py')
-    subprocess.call('python ' + script_path, shell=True, cwd=dir_path)
-
     def options_gen(executable, filter_, jobs):
         """Generator for options for a certain shard.
 
         Here we set up GoogleTest specific flags, and generate
         distinct shard indices.
         """
-        opts = range(jobs)
+        opts = list(range(jobs))
 
         # If we run in a terminal, enable colored test output. We
         # still allow users to disable this themselves via extra args.
         if sys.stdout.isatty():
-            args = executable[1:]
-            executable = '{exe} --gtest_color=yes {args}'\
-                         .format(exe=executable[0], args=args if args else '')
+            executable = '{exe} --gtest_color=yes'.format(exe=executable)
 
         if filter_:
             executable = '{exe} --gtest_filter={filter}'\
@@ -231,25 +230,17 @@ if __name__ == '__main__':
         POOL = multiprocessing.Pool(processes=OPTIONS.jobs)
 
         # Run parallel tests.
-        #
-        # Multiprocessing's `map` cannot properly handle `KeyboardInterrupt` in
-        # some python versions. Use `map_async` with an explicit timeout
-        # instead. See http://stackoverflow.com/a/1408476.
         RESULTS.extend(
-            POOL.map_async(
+            POOL.map(
                 run_test,
-                options_gen(
-                    EXECUTABLE, OPTIONS.parallel, OPTIONS.jobs)).get(
-                        timeout=sys.maxint))
+                options_gen(EXECUTABLE, OPTIONS.parallel, OPTIONS.jobs)))
 
         # Now run sequential tests.
         if OPTIONS.sequential:
             RESULTS.extend(
-                POOL.map_async(
+                POOL.map(
                     run_test,
-                    options_gen(
-                        EXECUTABLE, OPTIONS.sequential, 1)).get(
-                            timeout=sys.maxint))
+                    options_gen(EXECUTABLE, OPTIONS.sequential, 1)))
 
         # Count the number of failed shards and print results from
         # failed shards.
diff --git a/support/mesos-split.py b/support/mesos-split.py
index f83986d..0a77c25 100755
--- a/support/mesos-split.py
+++ b/support/mesos-split.py
@@ -1,4 +1,5 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
+#
 # Licensed to the Apache Software Foundation (ASF) under one
 # or more contributor license agreements.  See the NOTICE file
 # distributed with this work for additional information
@@ -21,12 +22,10 @@ the projects which make up mesos.
 """
 
 from collections import defaultdict
-import os
-import subprocess
 import sys
 
 if len(sys.argv) < 2:
-    print "Usage: ./mesos-split.py <filename>..."
+    print("Usage: ./mesos-split.py <filename>...")
 
 BASE_PROJECT = "mesos"
 
@@ -48,7 +47,7 @@ def find_project(filename):
     # Find longest prefix match.
     found_path_len = 0
     found_project = BASE_PROJECT
-    for project, path in SUBPROJECTS.iteritems():
+    for project, path in SUBPROJECTS.items():
         if filename.startswith(path) and len(path) > found_path_len:
             found_path_len = len(path)
             found_project = project
@@ -62,22 +61,16 @@ def main():
 
     See `support/hooks/pre-commit` for the canonical usage of this method.
     """
-
-    # TODO(ArmandGrillet): Remove this when we'll have switched to Python 3.
-    dir_path = os.path.dirname(os.path.realpath(__file__))
-    script_path = os.path.join(dir_path, 'check-python3.py')
-    subprocess.call('python ' + script_path, shell=True, cwd=dir_path)
-
     touched_projects = defaultdict(list)
     for filename in sys.argv[1:]:
         touched_projects[find_project(filename)].append(filename)
 
     if len(touched_projects) > 1:
-        print ERROR
-        for project in touched_projects.iterkeys():
-            print "%s:" % project
+        print(ERROR)
+        for project in touched_projects.keys():
+            print("%s:" % project)
             for filename in touched_projects[project]:
-                print "  %s" % filename
+                print("  %s" % filename)
         sys.exit(1)
 
     sys.exit(0)
diff --git a/support/mesos-style.py b/support/mesos-style.py
index 7dbf96a..4fea672 100755
--- a/support/mesos-style.py
+++ b/support/mesos-style.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 #
 # Licensed to the Apache Software Foundation (ASF) under one
 # or more contributor license agreements.  See the NOTICE file
@@ -19,13 +19,16 @@
 """Runs checks for mesos style."""
 
 import os
+import platform
 import re
 import string
 import subprocess
 import sys
 
+from pathlib import PurePath
 
-class LinterBase(object):
+
+class LinterBase():
     """
     This is an abstract class that provides the base functionality for
     linting files in the mesos project. Its 'main()' function
@@ -104,13 +107,14 @@ class LinterBase(object):
             with open(path) as source_file:
                 # We read the three first lines of the file as the
                 # first line could be a shebang and the second line empty.
-                head = "".join([next(source_file) for _ in xrange(3)])
+                head = "".join([next(source_file) for _ in range(3)])
 
                 # TODO(bbannier) We allow `Copyright` for
                 # currently deviating files. This should be
                 # removed one we have a uniform license format.
                 regex = r'^{comment_prefix} [Licensed|Copyright]'.format(
                     comment_prefix=self.comment_prefix)
+                # pylint: disable=no-member
                 regex = re.compile(regex, re.MULTILINE)
 
                 if not regex.search(head):
@@ -148,12 +152,17 @@ class LinterBase(object):
         given command and return its output.
         """
         virtualenv = os.path.join('support', '.virtualenv')
-        command = '. {virtualenv_path}/bin/activate; {cmd}'.format(
-            virtualenv_path=virtualenv, cmd=command)
-        process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE)
 
-        return process
+        if platform.system() == 'Windows':
+            command = r'{virtualenv_path}\Scripts\activate.bat & {cmd}'.format(
+                virtualenv_path=virtualenv, cmd=command)
+        else:
+            command = '. {virtualenv_path}/bin/activate; {cmd}'.format(
+                virtualenv_path=virtualenv, cmd=command)
+
+        return subprocess.Popen(command, shell=True, stdout=subprocess.PIPE)
 
+    # pylint: disable=unused-argument
     def run_lint(self, source_paths):
         """
         A custom function to provide linting for 'linter_type'.
@@ -163,7 +172,7 @@ class LinterBase(object):
         It should print any errors as it encounters them to provide
         feedback to the caller.
         """
-        pass
+        return 0
 
     def main(self, modified_files):
         """
@@ -176,8 +185,8 @@ class LinterBase(object):
         # the style checker from other (possibly nested) paths.
         for source_dir in self.source_dirs:
             if not os.path.exists(source_dir):
-                print "Could not find '{dir}'".format(dir=source_dir)
-                print 'Please run from the root of the mesos source directory'
+                print("Could not find '{dir}'".format(dir=source_dir))
+                print('Please run from the root of the mesos source directory')
                 exit(1)
 
         # Add all source file candidates to candidates list.
@@ -186,8 +195,8 @@ class LinterBase(object):
             for candidate in self.find_candidates(source_dir):
                 candidates.append(candidate)
 
-        # Normalize paths of any files give.
-        modified_files = map(os.path.normpath, modified_files)
+        # Normalize paths of any files given.
+        modified_files = [os.fspath(PurePath(f)) for f in modified_files]
 
         # If file paths are specified, check all file paths that are
         # candidates; else check all candidates.
@@ -201,14 +210,11 @@ class LinterBase(object):
             candidates_set)
 
         if filtered_candidates_set:
-            if len(filtered_candidates_set) == 1:
-                plural = ''
-            else:
-                plural = 's'
-            print 'Checking {num_files} {linter} file{plural}'.format(
+            plural = '' if len(filtered_candidates_set) == 1 else 's'
+            print('Checking {num_files} {linter} file{plural}'.format(
                 num_files=len(filtered_candidates_set),
                 linter=self.linter_type,
-                plural=plural)
+                plural=plural))
 
             license_errors = self.check_license_header(filtered_candidates_set)
             encoding_errors = self.check_encoding(list(filtered_candidates_set))
@@ -219,9 +225,9 @@ class LinterBase(object):
                 num_errors=total_errors))
 
             return total_errors
-        else:
-            print "No {linter} files to lint".format(linter=self.linter_type)
-            return 0
+
+        print("No {linter} files to lint".format(linter=self.linter_type))
+        return 0
 
 
 class CppLinter(LinterBase):
@@ -261,21 +267,20 @@ class CppLinter(LinterBase):
             'build/deprecated',
             'build/endif_comment',
             'build/nullptr',
-            'readability/inheritance',
-            'readability/namespace',
             'readability/todo',
+            'readability/namespace',
             'runtime/vlog',
             'whitespace/blank_line',
             'whitespace/comma',
-            'whitespace/comments',
-            'whitespace/ending_newline',
             'whitespace/end_of_line',
+            'whitespace/ending_newline',
             'whitespace/forcolon',
             'whitespace/indent',
             'whitespace/line_length',
             'whitespace/operators',
             'whitespace/semicolon',
             'whitespace/tab',
+            'whitespace/comments',
             'whitespace/todo']
 
         rules_filter = '--filter=-,+' + ',+'.join(active_rules)
@@ -283,13 +288,15 @@ class CppLinter(LinterBase):
         # We do not use a version of cpplint available through pip as
         # we use a custom version (see cpplint.path) to lint C++ files.
         process = subprocess.Popen(
-            ['python', 'support/cpplint.py', rules_filter] + source_paths,
+            [sys.executable, 'support/cpplint.py',
+             rules_filter] + source_paths,
             stderr=subprocess.PIPE,
             close_fds=True)
 
         # Lines are stored and filtered, only showing found errors instead
         # of e.g., 'Done processing XXX.' which tends to be dominant output.
         for line in process.stderr:
+            line = line.decode(sys.stdout.encoding)
             if re.match('^(Done processing |Total errors found: )', line):
                 continue
             sys.stderr.write(line)
@@ -340,6 +347,7 @@ class JsLinter(LinterBase):
         )
 
         for line in process.stdout:
+            line = line.decode(sys.stdout.encoding)
             if "Error -" in line or "Warning -" in line:
                 sys.stderr.write(line)
                 if "Error -" in line:
@@ -352,7 +360,12 @@ class PyLinter(LinterBase):
     """The linter for Python files, uses pylint."""
     linter_type = 'Python'
 
-    source_dirs = ['support']
+    cli_dir = os.path.join('src', 'python', 'cli_new')
+    lib_dir = os.path.join('src', 'python', 'lib')
+    support_dir = os.path.join('support')
+    source_dirs_to_lint_with_venv = [support_dir]
+    source_dirs_to_lint_with_tox = [cli_dir, lib_dir]
+    source_dirs = source_dirs_to_lint_with_tox + source_dirs_to_lint_with_venv
 
     exclude_files = '(' \
                     r'protobuf\-2\.4\.1|' \
@@ -362,8 +375,7 @@ class PyLinter(LinterBase):
                     r'libev\-4\.15|' \
                     r'java/jni|' \
                     r'\.virtualenv|' \
-                    r'\.tox|' \
-                    r'python3' \
+                    r'\.tox' \
                     ')'
 
     source_files = r'\.(py)$'
@@ -372,6 +384,29 @@ class PyLinter(LinterBase):
 
     pylint_config = os.path.abspath(os.path.join('support', 'pylint.config'))
 
+    def run_tox(self, configfile, args, tox_env=None, recreate=False):
+        """
+        Runs tox with given configfile and args. Optionally set tox env
+        and/or recreate the tox-managed virtualenv.
+        """
+        support_dir = os.path.dirname(__file__)
+        bin_dir = 'Script' if platform.system() == 'Windows' else 'bin'
+
+        cmd = [os.path.join(support_dir, '.virtualenv', bin_dir, 'tox')]
+        cmd += ['-qq']
+        cmd += ['-c', configfile]
+        if tox_env is not None:
+            cmd += ['-e', tox_env]
+        if recreate:
+            cmd += ['--recreate']
+        cmd += ['--']
+        cmd += args
+
+        # We do not use `run_command_in_virtualenv()` here, as we
+        # directly call `tox` from inside the virtual environment bin
+        # directory without activating the virtualenv.
+        return subprocess.Popen(cmd, stdout=subprocess.PIPE)
+
     def filter_source_files(self, source_dir, source_files):
         """
         Filters out files starting with source_dir.
@@ -390,14 +425,21 @@ class PyLinter(LinterBase):
         if not filtered_source_files:
             return 0
 
-        process = self.run_command_in_virtualenv(
-            'pylint --score=n --rcfile={rcfile} {files}'.format(
-                rcfile=self.pylint_config,
-                files=' '.join(filtered_source_files)))
+        if source_dir in self.source_dirs_to_lint_with_tox:
+            process = self.run_tox(
+                configfile=os.path.join(source_dir, 'tox.ini'),
+                args=['--rcfile='+self.pylint_config] + filtered_source_files,
+                tox_env='py3-lint')
+        else:
+            process = self.run_command_in_virtualenv(
+                'pylint --score=n --rcfile={rcfile} {files}'.format(
+                    rcfile=self.pylint_config,
+                    files=' '.join(filtered_source_files)))
 
         num_errors = 0
         for line in process.stdout:
-            if re.match(r'^[RCWEF]: *[\d]+', line):
+            line = line.decode(sys.stdout.encoding)
+            if re.search(r'[RCWEF][0-9]{4}:', line):
                 num_errors += 1
             sys.stderr.write(line)
 
@@ -426,24 +468,26 @@ def should_build_virtualenv(modified_files):
     arguments (meaning that the entire codebase should be linted).
     """
     # NOTE: If the file list is empty, we are linting the entire test
-    # codebase. We should always rebuild the vI've irtualenv in this case.
+    # codebase. We should always rebuild the virtualenv in this case.
     if not modified_files:
         return True
 
     support_dir = os.path.dirname(__file__)
+    bin_dir = 'Script' if platform.system() == 'Windows' else 'bin'
+
     interpreter = os.path.basename(sys.executable)
-    interpreter = os.path.join(support_dir, '.virtualenv', 'bin', interpreter)
+    interpreter = os.path.join(support_dir, '.virtualenv', bin_dir, interpreter)
     if not os.path.isfile(interpreter):
         return True
 
     basenames = [os.path.basename(path) for path in modified_files]
 
     if 'pip-requirements.txt' in basenames:
-        print 'The "pip-requirements.txt" file has changed.'
+        print('The "pip-requirements.txt" file has changed.')
         return True
 
     if 'build-virtualenv' in basenames:
-        print 'The "build-virtualenv" file has changed.'
+        print('The "build-virtualenv" file has changed.')
         return True
 
     # The JS and Python linters require a virtual environment.
@@ -457,7 +501,7 @@ def should_build_virtualenv(modified_files):
 
         for basename in basenames:
             if js_and_python_files_regex.search(basename) is not None:
-                print 'Virtualenv not detected and required... building'
+                print('Virtualenv not detected and required... building')
                 return True
 
     return False
@@ -468,15 +512,29 @@ def build_virtualenv():
     Rebuild the virtualenv by running a bootstrap script.
     This will exit the program if there is a failure.
     """
-    print 'Rebuilding virtualenv...'
+    print('Rebuilding virtualenv...')
+
+    python3_env = os.environ.copy()
+    python3_env["PYTHON"] = sys.executable
+
+    build_virtualenv_file = [os.path.join('support', 'build-virtualenv')]
+
+    if platform.system() == 'Windows':
+        # TODO(andschwa): Port more of the `build-virtualenv` Bash script.
+        python_dir = os.path.dirname(sys.executable)
+        virtualenv = os.path.join(python_dir, 'Scripts', 'virtualenv.exe')
+        build_virtualenv_file = [virtualenv,
+                                 '--no-site-packages',
+                                 'support/.virtualenv']
 
     process = subprocess.Popen(
-        [os.path.join('support', 'build-virtualenv')],
+        build_virtualenv_file,
+        env=python3_env,
         stdout=subprocess.PIPE)
 
     output = ''
     for line in process.stdout:
-        output += line
+        output += line.decode(sys.stdout.encoding)
 
     process.wait()
 
@@ -484,15 +542,45 @@ def build_virtualenv():
         sys.stderr.write(output)
         sys.exit(1)
 
+    # TODO(andschwa): Move this into a script like above.
+    if platform.system() == 'Windows':
+        def run_command_in_virtualenv(command):
+            """
+            Stolen from `PyLinter`, runs command in virtualenv.
+            """
+            virtualenv = os.path.join('support',
+                                      '.virtualenv',
+                                      'Scripts',
+                                      'activate.bat')
+            command = '{virtualenv_path} & {cmd}'.format(
+                virtualenv_path=virtualenv, cmd=command)
+
+            return subprocess.Popen(command, shell=True, stdout=subprocess.PIPE)
+
+        pip_install_pip = 'python.exe -m pip install --upgrade pip'
+        process = run_command_in_virtualenv(pip_install_pip)
+        for line in process.stdout:
+            output += line.decode(sys.stdout.encoding)
+        process.wait()
+
+        if process.returncode != 0:
+            sys.stderr.write(output)
+            sys.exit(1)
+
+        pip_reqs = 'python.exe -m pip install -r support/pip-requirements.txt'
+        process = run_command_in_virtualenv(pip_reqs)
+        for line in process.stdout:
+            output += line.decode(sys.stdout.encoding)
+        process.wait()
+
+        if process.returncode != 0:
+            sys.stderr.write(output)
+            sys.exit(1)
+
 if __name__ == '__main__':
     if should_build_virtualenv(sys.argv[1:]):
         build_virtualenv()
 
-    # TODO(ArmandGrillet): Remove this when we'll have switched to Python 3.
-    dir_path = os.path.dirname(os.path.realpath(__file__))
-    script_path = os.path.join(dir_path, 'check-python3.py')
-    subprocess.call('python ' + script_path, shell=True, cwd=dir_path)
-
     # TODO(ArmandGrillet): We should only instantiate the linters
     # required to lint the files to analyze. See MESOS-8351.
     CPP_LINTER = CppLinter()
diff --git a/support/pip-requirements.txt b/support/pip-requirements.txt
index e3fbcc2..c8839fd 100644
--- a/support/pip-requirements.txt
+++ b/support/pip-requirements.txt
@@ -1,4 +1,3 @@
 nodeenv==1.3.1
-pylint==1.9.2; python_version < '3.0'
-pylint==2.1.1; python_version >= '3.0'
-tox==3.2.1; python_version >= '3.0'
+pylint==2.1.1
+tox==3.2.1
diff --git a/support/python3/post-build-result.py b/support/post-build-result.py
similarity index 100%
rename from support/python3/post-build-result.py
rename to support/post-build-result.py
diff --git a/support/post-reviews.py b/support/post-reviews.py
index 94ece85..799f20d 100755
--- a/support/post-reviews.py
+++ b/support/post-reviews.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 #
 # Licensed to the Apache Software Foundation (ASF) under one
 # or more contributor license agreements.  See the NOTICE file
@@ -40,13 +40,13 @@ $ ./support/post-reviews.py
 
 import argparse
 import atexit
-import imp
+import importlib.machinery
+import importlib.util
 import os
 import platform
 import re
-import subprocess
 import sys
-import urlparse
+import urllib.parse
 
 from distutils.version import LooseVersion
 
@@ -68,19 +68,20 @@ def execute(command, ignore_errors=False):
         return None
 
     data, _ = process.communicate()
+    data = data.decode(sys.stdout.encoding)
     status = process.wait()
     if status != 0 and not ignore_errors:
         cmdline = ' '.join(command) if isinstance(command, list) else command
         need_login = 'Please log in to the Review Board' \
                      ' server at reviews.apache.org.'
         if need_login in data:
-            print need_login, '\n'
-            print "You can either:"
-            print "  (1) Run 'rbt login', or"
-            print "  (2) Set the default USERNAME/PASSWORD in '.reviewboardrc'"
+            print(need_login, '\n')
+            print("You can either:")
+            print("  (1) Run 'rbt login', or")
+            print("  (2) Set the default USERNAME/PASSWORD in '.reviewboardrc'")
         else:
-            print 'Failed to execute: \'' + cmdline + '\':'
-            print data
+            print('Failed to execute: \'' + cmdline + '\':')
+            print(data)
         sys.exit(1)
     elif status != 0:
         return None
@@ -91,11 +92,6 @@ def main():
     """Main function, post commits added to this branch as review requests."""
     # TODO(benh): Make sure this is a git repository, apologize if not.
 
-    # TODO(ArmandGrillet): Remove this when we'll have switched to Python 3.
-    dir_path = os.path.dirname(os.path.realpath(__file__))
-    script_path = os.path.join(dir_path, 'check-python3.py')
-    subprocess.call('python ' + script_path, shell=True, cwd=dir_path)
-
     # Choose 'rbt' if available, otherwise choose 'post-review'.
     post_review = None
 
@@ -111,23 +107,21 @@ def main():
     elif execute(['post-review', '--version'], ignore_errors=True):
         post_review = ['post-review']
     else:
-        print 'Please install RBTools before proceeding'
+        print('Please install RBTools before proceeding')
         sys.exit(1)
 
     # Warn if people have unstaged changes.
     diff_stat = execute(['git', 'diff', '--shortstat']).strip()
 
     if diff_stat:
-        print >> sys.stderr, \
-            'WARNING: Worktree contains unstaged changes, continuing anyway.'
+        print('WARNING: Worktree contains unstaged changes, continuing anyway.', file=sys.stderr)
 
     # Warn if people have uncommitted changes.
     diff_stat = execute(['git', 'diff', '--shortstat', '--staged']).strip()
 
     if diff_stat:
-        print >> sys.stderr, \
-            'WARNING: Worktree contains staged but uncommitted changes, ' \
-            'continuing anyway.'
+        print('WARNING: Worktree contains staged but uncommitted changes, ' \
+            'continuing anyway.', file=sys.stderr)
 
     # Grab a reference to the repo's git directory. Usually this is simply
     # .git in the repo's top level directory. However, when submodules are
@@ -171,7 +165,11 @@ def main():
     if os.path.exists(reviewboardrc_filepath):
         # Prevent generation of '.reviewboardrcc'.
         sys.dont_write_bytecode = True
-        reviewboardrc = imp.load_source('reviewboardrc', reviewboardrc_filepath)
+        loader = importlib.machinery.SourceFileLoader(
+            'reviewboardrc', reviewboardrc_filepath)
+        spec = importlib.util.spec_from_loader(loader.name, loader)
+        reviewboardrc = importlib.util.module_from_spec(spec)
+        loader.exec_module(reviewboardrc)
 
     if args.server:
         reviewboard_url = args.server
@@ -192,8 +190,8 @@ def main():
 
     # Do not work on the tracking branch.
     if branch == tracking_branch:
-        print "We're expecting you to be working on another branch" \
-              " from {}!".format(tracking_branch)
+        print("We're expecting you to be working on another branch" \
+              " from {}!".format(tracking_branch))
         sys.exit(1)
 
     temporary_branch = '_post-reviews_' + branch
@@ -209,12 +207,11 @@ def main():
     if execute([
             'git', 'merge-base', '--is-ancestor', tracking_branch, branch_ref],
             ignore_errors=True) is None:
-        print >> sys.stderr, \
-            "WARNING: Tracking branch '%s' is no direct ancestor of HEAD." \
-            " Did you forget to rebase?" % tracking_branch
+        print("WARNING: Tracking branch '%s' is no direct ancestor of HEAD." \
+            " Did you forget to rebase?" % tracking_branch, file=sys.stderr)
 
         try:
-            raw_input("Press enter to continue or 'Ctrl-C' to abort.\n")
+            input("Press enter to continue or 'Ctrl-C' to abort.\n")
         except KeyboardInterrupt:
             sys.exit(0)
 
@@ -229,8 +226,8 @@ def main():
         '(yellow)%d%Creset %s %Cgreen(%cr)%Creset',
         merge_base + '..HEAD'])
 
-    print 'Running \'%s\' across all of ...' % " ".join(post_review)
-    print output
+    print('Running \'%s\' across all of ...' % " ".join(post_review))
+    sys.stdout.buffer.write(output)
 
     log = execute(['git',
                    '--no-pager',
@@ -241,7 +238,7 @@ def main():
                    merge_base + '..HEAD']).strip()
 
     if len(log) <= 0:
-        print "No new changes compared with master branch!"
+        print("No new changes compared with master branch!")
         sys.exit(1)
 
     shas = []
@@ -266,11 +263,11 @@ def main():
         pos = message.find('Review:')
         if pos != -1:
             regex = 'Review: ({url})$'.format(
-                url=urlparse.urljoin(reviewboard_url, 'r/[0-9]+'))
+                url=urllib.parse.urljoin(reviewboard_url, 'r/[0-9]+'))
             pattern = re.compile(regex)
             match = pattern.search(message[pos:].strip().strip('/'))
             if match is None:
-                print "\nInvalid ReviewBoard URL: '{}'".format(message[pos:])
+                print("\nInvalid ReviewBoard URL: '{}'".format(message[pos:]))
                 sys.exit(1)
 
             url = match.group(1)
@@ -284,8 +281,8 @@ def main():
                 'log',
                 '--pretty=format:%Cred%H%Creset -%C(yellow)%d%Creset %s',
                 previous + '..' + sha])
-            print '\nCreating diff of:'
-            print output
+            print('\nCreating diff of:')
+            sys.stdout.buffer.write(output)
         else:
             output = check_output([
                 'git',
@@ -294,8 +291,8 @@ def main():
                 '--pretty=format:%Cred%H%Creset -%C'
                 '(yellow)%d%Creset %s %Cgreen(%cr)%Creset',
                 previous + '..' + sha])
-            print '\nUpdating diff of:'
-            print output
+            print('\nUpdating diff of:')
+            sys.stdout.buffer.write(output)
 
         # Show the "parent" commit(s).
         output = check_output([
@@ -307,11 +304,11 @@ def main():
             tracking_branch + '..' + previous])
 
         if output:
-            print '\n... with parent diff created from:'
-            print output
+            print('\n... with parent diff created from:')
+            sys.stdout.buffer.write(output)
 
         try:
-            raw_input('\nPress enter to continue or \'Ctrl-C\' to skip.\n')
+            input('\nPress enter to continue or \'Ctrl-C\' to skip.\n')
         except KeyboardInterrupt:
             i = i + 1
             previous = sha
@@ -368,9 +365,10 @@ def main():
                 ['--revision-range=' + revision_range] + \
                 sys.argv[1:]
 
-        output = execute(command).strip()
+        output = execute(command)
 
-        print output
+        # Output is a string, we convert it to a byte string before writing it.
+        sys.stdout.buffer.write(output.encode())
 
         # If we already have a request_id, continue on to the next commit in the
         # chain. We update 'previous' from the shas[] array because we have
diff --git a/support/push-commits.py b/support/push-commits.py
index b10e802..dcda5f1 100755
--- a/support/push-commits.py
+++ b/support/push-commits.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 #
 # Licensed to the Apache Software Foundation (ASF) under one
 # or more contributor license agreements.  See the NOTICE file
@@ -34,39 +34,43 @@ Example Usage:
 
 import argparse
 import os
+import platform
 import re
-import subprocess
-from subprocess import check_output
-
 import sys
+import urllib.parse
 
+from subprocess import check_output
 
 REVIEWBOARD_URL = 'https://reviews.apache.org'
 
+def _check_output(args):
+    return check_output(args).decode(sys.stdout.encoding)
 
 def get_reviews(revision_range):
     """Return the list of reviews found in the commits in the revision range."""
     reviews = [] # List of (review id, commit log) tuples
 
-    rev_list = check_output(['git',
-                             'rev-list',
-                             '--reverse',
-                             revision_range]).strip().split('\n')
+    rev_list = _check_output(['git',
+                              'rev-list',
+                              '--reverse',
+                              revision_range]).strip().split('\n')
     for rev in rev_list:
-        commit_log = check_output(['git',
-                                   '--no-pager',
-                                   'show',
-                                   '--no-color',
-                                   '--no-patch',
-                                   rev]).strip()
+        commit_log = _check_output(['git',
+                                    '--no-pager',
+                                    'show',
+                                    '--no-color',
+                                    '--no-patch',
+                                    rev]).strip()
 
         pos = commit_log.find('Review: ')
         if pos != -1:
-            pattern = re.compile('Review: ({url})$'.format(
-                url=os.path.join(REVIEWBOARD_URL, 'r', '[0-9]+')))
+            regex = 'Review: ({url})$'.format(
+                url=urllib.parse.urljoin(REVIEWBOARD_URL, 'r/[0-9]+'))
+            pattern = re.compile(regex)
             match = pattern.search(commit_log.strip().strip('/'))
             if match is None:
-                print "\nInvalid ReviewBoard URL: '{}'".format(commit_log[pos:])
+                print("\nInvalid ReviewBoard URL: '{}'".format(
+                    commit_log[pos:]))
                 sys.exit(1)
 
             url = match.group(1)
@@ -78,13 +82,17 @@ def get_reviews(revision_range):
 def close_reviews(reviews, options):
     """Mark the given reviews as submitted on ReviewBoard."""
     for review_id, commit_log in reviews:
-        print 'Closing review', review_id
+        print('Closing review', review_id)
         if not options['dry_run']:
-            check_output(['rbt',
-                          'close',
-                          '--description',
-                          commit_log,
-                          review_id])
+            rbt_command = 'rbt'
+            # Windows command name must have `cmd` extension.
+            if platform.system() == 'Windows':
+                rbt_command = 'rbt.cmd'
+            _check_output([rbt_command,
+                           'close',
+                           '--description',
+                           commit_log,
+                           review_id])
 
 
 def parse_options():
@@ -108,54 +116,49 @@ def main():
     """Main function to push the commits in this branch as review requests."""
     options = parse_options()
 
-    # TODO(ArmandGrillet): Remove this when we'll have switched to Python 3.
-    dir_path = os.path.dirname(os.path.realpath(__file__))
-    script_path = os.path.join(dir_path, 'check-python3.py')
-    subprocess.call('python ' + script_path, shell=True, cwd=dir_path)
-
-    current_branch_ref = check_output(['git', 'symbolic-ref', 'HEAD']).strip()
+    current_branch_ref = _check_output(['git', 'symbolic-ref', 'HEAD']).strip()
     current_branch = current_branch_ref.replace('refs/heads/', '', 1)
 
     if current_branch != 'master':
-        print 'Please run this script from master branch'
+        print('Please run this script from master branch')
         sys.exit(1)
 
-    remote_tracking_branch = check_output(['git',
-                                           'rev-parse',
-                                           '--abbrev-ref',
-                                           'master@{upstream}']).strip()
+    remote_tracking_branch = _check_output(['git',
+                                            'rev-parse',
+                                            '--abbrev-ref',
+                                            'master@{upstream}']).strip()
 
-    merge_base = check_output([
+    merge_base = _check_output([
         'git',
         'merge-base',
         remote_tracking_branch,
         'master']).strip()
 
     if merge_base == current_branch_ref:
-        print 'No new commits found to push'
+        print('No new commits found to push')
         sys.exit(1)
 
     reviews = get_reviews(merge_base + ".." + current_branch_ref)
 
     # Push the current branch to remote master.
-    remote = check_output(['git',
-                           'config',
-                           '--get',
-                           'branch.master.remote']).strip()
+    remote = _check_output(['git',
+                            'config',
+                            '--get',
+                            'branch.master.remote']).strip()
 
-    print 'Pushing commits to', remote
+    print('Pushing commits to', remote)
 
     if options['dry_run']:
-        check_output(['git',
-                      'push',
-                      '--dry-run',
-                      remote,
-                      'master:master'])
+        _check_output(['git',
+                       'push',
+                       '--dry-run',
+                       remote,
+                       'master:master'])
     else:
-        check_output(['git',
-                      'push',
-                      remote,
-                      'master:master'])
+        _check_output(['git',
+                       'push',
+                       remote,
+                       'master:master'])
 
     # Now mark the reviews as submitted.
     close_reviews(reviews, options)
diff --git a/support/python3/apply-reviews.py b/support/python3/apply-reviews.py
deleted file mode 100755
index 92ad859..0000000
--- a/support/python3/apply-reviews.py
+++ /dev/null
@@ -1,453 +0,0 @@
-#!/usr/bin/env python3
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""
-This script translates reviews on ReviewBoard into commits
-on the current branch.
-"""
-
-import argparse
-import atexit
-import json
-import linecache
-import os
-import pipes
-import platform
-import re
-import ssl
-import subprocess
-import sys
-import urllib.request
-import urllib.error
-import urllib.parse
-
-
-REVIEWBOARD_REVIEW_URL = 'https://reviews.apache.org/r'
-REVIEWBOARD_API_URL =\
-  'https://reviews.apache.org/api/review-requests'
-REVIEWBOARD_USER_URL = 'https://reviews.apache.org/api/users'
-
-
-GITHUB_URL = 'https://api.github.com/repos/apache/mesos/pulls'
-GITHUB_PATCH_URL =\
-  'https://patch-diff.githubusercontent.com/raw/apache/mesos/pull'
-
-
-def review_api_url(review_id):
-    """Returns a Review Board API URL given a review ID."""
-    # Reviewboard REST API expects '/' at the end of the URL.
-    return '{base}/{review}/'.format(
-        base=REVIEWBOARD_API_URL,
-        review=review_id)
-
-
-def review_url(review_id):
-    """Returns a Review Board UI URL given a review ID."""
-    return '{base}/{review}/'.format(
-        base=REVIEWBOARD_REVIEW_URL,
-        review=review_id)
-
-
-def pull_request_url(pull_request_number):
-    """Returns a GitHub pull request URL given a PR number."""
-    return '{base}/{pr}'.format(
-        base=GITHUB_URL,
-        pr=pull_request_number)
-
-
-def reviewboard_user_url(username):
-    """Returns a Review Board URL for a user given a username."""
-    # Reviewboard REST API expects '/' at the end of the URL.
-    return '{base}/{user}/'.format(
-        base=REVIEWBOARD_USER_URL,
-        user=username)
-
-
-def patch_url(options):
-    """Returns a Review Board or a GitHub URL for a patch."""
-    if options['review_id']:
-        # Reviewboard REST API expects '/' at the end of the URL.
-        return '{base}/{review}/diff/raw/'.format(
-            base=REVIEWBOARD_REVIEW_URL,
-            review=options['review_id'])
-    if options['github']:
-        return '{base}/{patch}.patch'.format(
-            base=GITHUB_PATCH_URL,
-            patch=options['github'])
-    return None
-
-
-def url_to_json(url):
-    """Performs HTTP request and returns JSON-ified response."""
-    json_str = urllib.request.urlopen(url)
-    return json.loads(json_str.read())
-
-
-def extract_review_id(url):
-    """Extracts review ID from Review Board URL."""
-    review_id = re.search(REVIEWBOARD_API_URL + r'/(\d+)/', url)
-    if review_id:
-        return review_id.group(1)
-    return ''
-
-def review_chain(review_id):
-    """Returns a parent review chain for a given review ID."""
-    json_obj = url_to_json(review_api_url(review_id))
-
-    # Stop as soon as we stumble upon a submitted request.
-    status = json_obj.get('review_request').get('status')
-    if status == "submitted":
-        sys.stderr.write('Warning: Review {review} has already'
-                         ' been submitted and did not get applied'
-                         ' to you current work-tree\n'.format(review=review_id))
-        return []
-
-    # Verify that the review has exactly one parent.
-    parent = json_obj.get('review_request').get('depends_on')
-    if len(parent) > 1:
-        sys.stderr.write('Error: Review {review} has more than'
-                         ' one parent'.format(review=review_id))
-        sys.exit(1)
-    elif not parent:
-        return [(review_id, json_obj.get('review_request').get('summary'))]
-    else:
-        # The review has exactly one parent.
-        review_list = review_chain(extract_review_id(parent[0].get('href')))
-
-        review = (review_id, json_obj.get('review_request').get('summary'))
-        if review not in review_list:
-            return review_list + [review]
-        sys.stderr.write('Found a circular dependency in the chain starting'
-                         ' at {review}\n'.format(review=review_id))
-        sys.exit(1)
-
-
-def shell(command, dry_run):
-    """
-    Runs a command in a shell, unless the dry-run option
-    is set (in which case it just prints the command).
-    """
-    if dry_run:
-        print(command)
-        return
-
-    error_code = subprocess.call(command, stderr=subprocess.STDOUT, shell=True)
-    if error_code != 0:
-        sys.exit(error_code)
-
-
-def apply_review(options):
-    """Applies a review with a given ID locally."""
-    # Make sure we don't leave the patch behind in case of failure.
-    # We store the patch ID in a local variable to ensure the lambda
-    # captures the current patch ID.
-    patch_file = '%s.patch' % patch_id(options)
-    if not options["keep_patches"]:
-        atexit.register(
-            lambda: os.path.exists(patch_file) and os.remove(patch_file))
-
-    fetch_patch(options)
-    apply_patch(options)
-    commit_patch(options)
-
-
-def ssl_create_default_context():
-    """
-    Equivalent to `ssl.create_default_context` with default arguments and
-    certificate/hostname verification disabled.
-    See: https://github.com/python/cpython/blob/2.7/Lib/ssl.py#L410
-    This function requires Python >= 2.7.9.
-    """
-    # pylint: disable=no-member
-    context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
-
-    # SSLv2 considered harmful.
-    context.options |= ssl.OP_NO_SSLv2
-
-    # SSLv3 has problematic security and is only required for really old
-    # clients such as IE6 on Windows XP.
-    context.options |= ssl.OP_NO_SSLv3
-
-    # Disable compression to prevent CRIME attacks (OpenSSL 1.0+).
-    context.options |= getattr(ssl, "OP_NO_COMPRESSION", 0)
-
-    # Disable certificate and hostname verification.
-    context.verify_mode = ssl.CERT_NONE
-    context.check_hostname = False
-
-
-def fetch_patch(options):
-    """Fetches a patch from Review Board or GitHub."""
-    # pylint: disable=unexpected-keyword-arg
-    if platform.system() == 'Windows':
-        # This call requires Python >= 2.7.9.
-        response = urllib.request.urlopen(
-            patch_url(options),
-            context=ssl_create_default_context())
-
-        with open('%s.patch' % patch_id(options), 'wb') as patch:
-            patch.write(response.read())
-    else:
-        # NOTE: SSL contexts are only supported in Python 2.7.9+. The version
-        # of Python running on the non-Windows ASF CI machines is sometimes
-        # older. Hence, we fall back to `wget` on non-Windows machines.
-        cmd = ' '.join([
-            'wget',
-            '--no-check-certificate',
-            '--no-verbose',
-            '-O '
-            '{review_id}.patch',
-            '{url}']).format(
-                review_id=patch_id(options),
-                url=patch_url(options))
-
-        # In case of GitHub we always need to fetch the patch to extract
-        # username and email, so we ignore the dry_run option by setting the
-        # second parameter to False.
-        if options['github']:
-            shell(cmd, False)
-        else:
-            shell(cmd, options['dry_run'])
-
-
-def patch_id(options):
-    """Returns the review ID or the GitHub pull request number."""
-    return options['review_id'] or options['github']
-
-
-def apply_patch(options):
-    """Applies patch locally."""
-    cmd = 'git apply --index {review_id}.patch'.format(
-        review_id=patch_id(options))
-
-    if options['3way']:
-        cmd += ' --3way'
-
-    if platform.system() == 'Windows':
-        # NOTE: Depending on the Git settings, there may or may not be
-        # carriage returns in files and in the downloaded patch.
-        # We ignore these errors on Windows.
-        cmd += ' --ignore-whitespace'
-
-    shell(cmd, options['dry_run'])
-
-
-def quote(string):
-    """Quote a variable so it can be safely used in shell."""
-    return string.replace("'", "'\\''")
-
-
-def commit_patch(options):
-    """Commits patch locally."""
-    data = patch_data(options)
-
-    # Check whether we need to amend the commit message.
-    if options['no_amend']:
-        amend = ''
-    else:
-        amend = '-e'
-
-    # Check whether we should skip the commit hooks.
-    if options['skip_hooks']:
-        verify = '-n'
-    else:
-        verify = ''
-
-    # NOTE: Windows does not support multi-line commit messages via the shell.
-    message_file = '%s.message' % patch_id(options)
-    atexit.register(
-        lambda: os.path.exists(message_file) and os.remove(message_file))
-
-    with open(message_file, 'w') as message:
-        # Add a shell command creating the message file for dry-run mode.
-        if options["dry_run"]:
-            shell(
-                "printf {msg} > {file}".format(
-                    msg=pipes.quote(data['message']).replace('\n', '\\n'),
-                    file=message_file),
-                True)
-        message.write(data['message'])
-
-    cmd = 'git commit' \
-          ' --author \"{author}\"' \
-          ' {amend} -aF \"{message}\"' \
-          ' {verify}'.format(
-              author=quote(data['author']),
-              amend=amend,
-              message=message_file,
-              verify=verify)
-
-    shell(cmd, options['dry_run'])
-
-
-def patch_data(options):
-    """
-    Populates and returns a dictionary with data necessary for
-    committing the patch (such as the message, the author, etc.).
-    """
-    if options['review_id']:
-        return reviewboard_data(options)
-    if options['github']:
-        return github_data(options)
-    return None
-
-
-def get_author(patch):
-    """Reads the author name and email from the .patch file"""
-    author = linecache.getline(patch, 2)
-    return author.replace('From: ', '').rstrip()
-
-
-def github_data(options):
-    """Fetches pull request data and populates internal data structure."""
-    pull_request_number = options['github']
-    pull_request = url_to_json(pull_request_url(pull_request_number))
-
-    title = pull_request.get('title')
-    description = pull_request.get('body')
-    url = '{url}/{pr}'.format(url=GITHUB_URL, pr=pull_request_number)
-    author = get_author('{pr}.patch'.format(pr=pull_request_number))
-    message = '\n\n'.join([
-        title,
-        description,
-        'This closes #{pr}'.format(pr=pull_request_number)])
-
-    review_data = {
-        "summary": title,
-        "description": description,
-        "url": url,
-        "author": author,
-        "message": message
-    }
-
-    return review_data
-
-
-def reviewboard_data(options):
-    """Fetches review data and populates internal data structure."""
-    review_id = options['review_id']
-
-    # Populate review object.
-    review = url_to_json(review_api_url(review_id)).get('review_request')
-
-    url = review_url(review_id)
-
-    # Populate user object.
-    user = url_to_json(reviewboard_user_url(
-        review.get('links').get('submitter').get('title'))).get('user')
-
-    # Only include a description if it is not identical to the summary.
-    message_data = [review.get('summary')]
-    if review.get('description') != review.get('summary'):
-        message_data.append(review.get('description'))
-    message_data.append('Review: {review_url}'.format(review_url=url))
-
-    author = '{author} <{email}>'.format(
-        author=user.get('fullname'),
-        email=user.get('email'))
-    message = '\n\n'.join(message_data)
-
-    review_data = {
-        "summary": review.get('summary'),
-        "description": review.get('description'),
-        "url": url,
-        "author": author,
-        "message": message
-    }
-
-    return review_data
-
-
-def parse_options():
-    """Parses command line options and returns an option dictionary."""
-    options = {}
-
-    parser = argparse.ArgumentParser(
-        description='Recursively apply Review Board reviews'
-                    ' and GitHub pull requests.')
-
-    parser.add_argument('-d', '--dry-run',
-                        action='store_true',
-                        help='Perform a dry run.')
-    parser.add_argument('-k', '--keep-patches',
-                        action='store_true',
-                        help="Do not delete downloaded patch files.")
-    parser.add_argument('-n', '--no-amend',
-                        action='store_true',
-                        help='Do not amend commit message.')
-    parser.add_argument('-c', '--chain',
-                        action='store_true',
-                        help='Recursively apply parent review chain.')
-    parser.add_argument('-s', '--skip-hooks',
-                        action='store_true',
-                        help='Skip the commit hooks (e.g., Mesos style check).')
-    parser.add_argument('-3', '--3way',
-                        dest='three_way',
-                        action='store_true',
-                        help='Use 3 way merge in git apply.')
-
-    # Add -g and -r and make them mutually exclusive.
-    group = parser.add_mutually_exclusive_group(required=True)
-    group.add_argument('-g', '--github',
-                       metavar='PULL_REQUEST',
-                       help='Pull request number')
-    group.add_argument('-r', '--review-id',
-                       metavar='REVIEW_ID',
-                       help='Numeric review ID')
-
-    args = parser.parse_args()
-
-    options['review_id'] = args.review_id
-    options['dry_run'] = args.dry_run
-    options['keep_patches'] = args.keep_patches
-    options['no_amend'] = args.no_amend
-    options['github'] = args.github
-    options['chain'] = args.chain
-    options['skip_hooks'] = args.skip_hooks
-    options['3way'] = args.three_way
-
-    return options
-
-
-def reviewboard(options):
-    """Applies either a chain of reviewboard patches or a single patch."""
-    if options['chain']:
-        # Retrieve the list of reviews to apply.
-        applied = set()
-        for review_id, _ in review_chain(options['review_id']):
-            if review_id not in applied:
-                applied.add(review_id)
-                options['review_id'] = review_id
-                apply_review(options)
-    else:
-        apply_review(options)
-
-
-def main():
-    """
-    Main function to apply reviews.
-    """
-    options = parse_options()
-
-    if options['review_id']:
-        reviewboard(options)
-    else:
-        apply_review(options)
-
-if __name__ == "__main__":
-    main()
diff --git a/support/python3/generate-endpoint-help.py b/support/python3/generate-endpoint-help.py
deleted file mode 100755
index 2db9e14..0000000
--- a/support/python3/generate-endpoint-help.py
+++ /dev/null
@@ -1,405 +0,0 @@
-#!/usr/bin/env python3
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""
-Autogenerate documentation for all process endpoints spawned by a
-Mesos master and agent.
-"""
-
-import argparse
-import atexit
-import json
-import os
-import posixpath
-import re
-import shutil
-import subprocess
-import sys
-import time
-import urllib.request
-import urllib.error
-import urllib.parse
-
-
-# The host ip and master and agent ports.
-HOST_IP = "127.0.0.1"
-MASTER_PORT = 5050
-AGENT_PORT = 5051
-
-# The master and agent programs to launch.
-# We considered making the parameters to these commands something that
-# a user could specify on the command line, but ultimately chose to
-# hard code them.  Different parameters may cause different endpoints
-# to become available, and we should modify this script to ensure that
-# we cover all of them instead of leaving that up to the user.
-MASTER_COMMAND = [
-    'mesos-master.sh',
-    '--ip=%s' % (HOST_IP),
-    '--registry=in_memory',
-    '--work_dir=/tmp/mesos'
-]
-
-# NOTE: The agent flags here ensure that this script can run inside docker.
-AGENT_COMMAND = [
-    'mesos-agent.sh',
-    '--master=%s:%s' % (HOST_IP, MASTER_PORT),
-    '--work_dir=/tmp/mesos',
-    '--systemd_enable_support=false',
-    '--launcher=posix'
-]
-
-
-# A header to add onto all generated markdown files.
-MARKDOWN_HEADER = """---
-title: %s
-layout: documentation
----
-<!--- This is an automatically generated file. DO NOT EDIT! --->
-"""
-
-# A template of the title to add onto all generated markdown files.
-MARKDOWN_TITLE = "Apache Mesos - HTTP Endpoints%s"
-
-# A global timeout as well as a retry interval when hitting any http
-# endpoints on the master or agent (in seconds).
-RECEIVE_TIMEOUT = 10
-RETRY_INTERVAL = 0.10
-
-
-class Subprocess():
-    """The process running using this script."""
-    def __init__(self):
-        self.current = None
-
-    def cleanup(self):
-        """Kill the process running once the script is done."""
-        if self.current:
-            self.current.kill()
-
-
-# A pointer to the top level directory of the mesos project.
-GIT_TOP_DIR = subprocess.check_output(
-    ['git',
-     'rev-parse',
-     '--show-cdup']).decode(sys.stdout.encoding).strip()
-
-with open(os.path.join(GIT_TOP_DIR, 'CHANGELOG'), 'r') as f:
-    if 'mesos' not in f.readline().lower():
-        print(('You must run this command from within'
-               ' the Mesos source repository!'), file=sys.stderr)
-        sys.exit(1)
-
-
-def parse_options():
-    """Parses command line options and populates the dictionary."""
-
-    options = {}
-
-    parser = argparse.ArgumentParser(
-        formatter_class=argparse.RawTextHelpFormatter,
-        description='Generate markdown files from all installed HTTP '
-                    'endpoints on a Mesos master and agent process.')
-
-    parser.add_argument(
-        '-c', '--command-path',
-        metavar='COMMAND_PATH',
-        default=os.path.join(GIT_TOP_DIR, "build/bin"),
-        help='Path to the Mesos master and agent commands.\n'
-             '(default: %(default)s)')
-
-    parser.add_argument(
-        '-o', '--output-path',
-        metavar='OUTPUT_PATH',
-        default=os.path.join(GIT_TOP_DIR, "docs/endpoints"),
-        help='Path to the top level directory where all\n'
-             'generated markdown files will be placed.\n'
-             '(default: %(default)s)')
-
-    args = parser.parse_args()
-
-    options['command_path'] = args.command_path
-    options['output_path'] = args.output_path
-
-    return options
-
-
-def get_url_until_success(url):
-    """Continuously tries to open a url until it succeeds or times out."""
-    time_spent = 0
-    while time_spent < RECEIVE_TIMEOUT:
-        try:
-            helps = urllib.request.urlopen(url)
-            break
-        except Exception:
-            time.sleep(RETRY_INTERVAL)
-            time_spent += RETRY_INTERVAL
-
-    if time_spent >= RECEIVE_TIMEOUT:
-        print('Timeout attempting to hit url: %s' % (url), file=sys.stderr)
-        sys.exit(1)
-
-    return helps.read()
-
-
-def get_help(ip, port):
-    """
-    Grabs the help strings for all endpoints at http://ip:port as a JSON object.
-    """
-    url = 'http://%s:%d/help?format=json' % (ip, port)
-    return json.loads(get_url_until_success(url))
-
-
-def generalize_endpoint_id(p_id):
-    """Generalizes the id of the form e.g. process(1) to process(id)."""
-    return re.sub(r'\([0-9]+\)', '(id)', p_id)
-
-
-def normalize_endpoint_id(p_id):
-    """Normalizes the id of the form e.g. process(id) to process."""
-    return re.sub(r'\([0-9]+\)', '', p_id)
-
-
-def get_endpoint_path(p_id, name):
-    """
-    Generates the canonical endpoint path, given id and name.
-
-    Examples: ('process', '/')         -> '/process'
-              ('process(id)', '/')     -> '/process(id)'
-              ('process', '/endpoint') -> '/process/endpoint'
-    """
-    # Tokenize the endpoint by '/' (filtering
-    # out any empty strings between '/'s)
-    path_parts = [_f for _f in name.split('/') if _f]
-
-    # Conditionally prepend the 'id' to the list of path parts.
-    # Following the notion of a 'delegate' in Mesos, we want our
-    # preferred endpoint paths for the delegate process to be
-    # '/endpoint' instead of '/process/endpoint'. Since this script only
-    # starts 1 master and 1 agent, our only delegate processes are
-    # "master" and "slave(id)". If the id matches one of these, we don't
-    # prepend it, otherwise we do.
-    p_id = generalize_endpoint_id(p_id)
-    delegates = ["master", "slave(id)"]
-    if p_id not in delegates:
-        path_parts = [p_id] + path_parts
-
-    return posixpath.join('/', *path_parts)
-
-
-def get_relative_md_path(p_id, name):
-    """
-    Generates the relative path of the generated .md file from id and name.
-
-    This path is relative to the options['output_path'] directory.
-
-    Examples: master/health.md
-              master/maintenance/schedule.md
-              registrar/registry.md
-              version.md
-    """
-    new_id = normalize_endpoint_id(p_id)
-    # Strip the leading slash
-    new_name = name[1:]
-
-    if new_name:
-        return os.path.join(new_id, new_name + '.md')
-    return os.path.join(new_id + '.md')
-
-
-def write_markdown(path, output, title):
-    """Writes 'output' to the file at 'path'."""
-    print('generating: %s' % (path))
-
-    dirname = os.path.dirname(path)
-    if not os.path.exists(dirname):
-        os.makedirs(dirname)
-
-    outfile = open(path, 'w+')
-
-    # Add our header and remove all '\n's at the end of the output if
-    # there are any.
-    output = (MARKDOWN_HEADER % title) + '\n' + output.rstrip()
-
-    outfile.write(output)
-    outfile.close()
-
-
-def dump_index_markdown(master_help, agent_help, options):
-    """
-    Dumps an index for linking to the master and agent help files.
-
-    This file is dumped into a directory rooted at
-    options['output_path'].
-    """
-
-    # The output template for the HTTP endpoints index.
-    # We use a helper function below to insert text into the '%s' format
-    # strings contained in the "Master Endpoints" and "Agent Endpoints"
-    # sections of this template.
-    output = """# HTTP Endpoints #
-
-Below is a list of HTTP endpoints available for a given Mesos process.
-
-Depending on your configuration, some subset of these endpoints will be
-available on your Mesos master or agent. Additionally, a `/help`
-endpoint will be available that displays help similar to what you see
-below.
-
-** NOTE: ** If you are using Mesos 1.1 or later, we recommend using the
-new [v1 Operator HTTP API](../operator-http-api.md) instead of the
-unversioned REST endpoints listed below. These endpoints will be
-deprecated in the future.
-
-
-** NOTE: ** The documentation for these endpoints is auto-generated from
-the Mesos source code. See `support/python3/generate-endpoint-help.py`.
-
-## Master Endpoints ##
-
-Below are the endpoints that are available on a Mesos master. These
-endpoints are reachable at the address `http://ip:port/endpoint`.
-
-For example, `http://master.com:5050/files/browse`.
-
-%s
-
-## Agent Endpoints ##
-
-Below are the endpoints that are available on a Mesos agent. These
-endpoints are reachable at the address `http://ip:port/endpoint`.
-
-For example, `http://agent.com:5051/files/browse`.
-
-%s
-"""
-
-    def generate_links(master_or_agent_help):
-        """
-        Iterates over the input JSON and creates a list of links to
-        to the markdown files generated by this script. These links
-        are grouped per process, with the process's name serving as a
-        header for each group. All links are relative to
-        options['output_path'].
-
-        For example:
-        ### profiler ###
-        * [/profiler/start] (profiler/start.md)
-        * [/profiler/stop] (profiler/stop.md)
-
-        ### version ###
-        * [/version] (version.md)
-        """
-        output = ""
-        for process in master_or_agent_help['processes']:
-            p_id = process['id']
-            output += '### %s ###\n' % (generalize_endpoint_id(p_id))
-            for endpoint in process['endpoints']:
-                name = endpoint['name']
-                output += '* [%s](%s)\n' % (get_endpoint_path(p_id, name),
-                                            get_relative_md_path(p_id, name))
-            output += '\n'
-
-        # Remove any trailing newlines
-        return output.rstrip()
-
-    output = output % (generate_links(master_help),
-                       generate_links(agent_help))
-
-    path = os.path.join(options['output_path'], 'index.md')
-    write_markdown(path, output, MARKDOWN_TITLE % "")
-
-
-def dump_markdown(master_or_agent_help, options):
-    """
-    Dumps JSON encoded help strings into markdown files.
-
-    These files are dumped into a directory rooted at
-    options['output_path'].
-    """
-    for process in master_or_agent_help['processes']:
-        p_id = process['id']
-        for endpoint in process['endpoints']:
-            name = endpoint['name']
-            text = endpoint['text']
-            title = get_endpoint_path(p_id, name)
-
-            relative_path = get_relative_md_path(p_id, name)
-            path = os.path.join(options['output_path'], relative_path)
-            write_markdown(path, text, MARKDOWN_TITLE % (" - " + title))
-
-
-def start_master(options):
-    """
-    Starts the Mesos master using the specified command.
-
-    This method returns the Popen object used to start it so it can
-    be killed later on.
-    """
-    cmd = os.path.join('.', options['command_path'], MASTER_COMMAND[0])
-    master = subprocess.Popen([cmd] + MASTER_COMMAND[1:])
-
-    # Wait for the master to become responsive
-    get_url_until_success("http://%s:%d/health" % (HOST_IP, MASTER_PORT))
-    return master
-
-
-def start_agent(options):
-    """
-    Starts the Mesos agent using the specified command.
-
-    This method returns the Popen object used to start it so it can
-    be killed later on.
-    """
-    cmd = os.path.join('.', options['command_path'], AGENT_COMMAND[0])
-    agent = subprocess.Popen([cmd] + AGENT_COMMAND[1:])
-
-    # Wait for the agent to become responsive.
-    get_url_until_success('http://%s:%d/health' % (HOST_IP, AGENT_PORT))
-    return agent
-
-
-def main():
-    """
-    Called when the Python script is used, we do not directly write code
-    after 'if __name__ == "__main__"' as we cannot set variables in that case.
-    """
-    # A dictionary of the command line options passed in.
-    options = parse_options()
-
-    # A pointer to the current subprocess for the master or agent.
-    # This is useful for tracking the master or agent subprocesses so
-    # that we can kill them if the script exits prematurely.
-    subproc = Subprocess()
-    atexit.register(subproc.cleanup)
-    subproc.current = start_master(options)
-    master_help = get_help(HOST_IP, MASTER_PORT)
-    subproc.current.kill()
-
-    subproc.current = start_agent(options)
-    agent_help = get_help(HOST_IP, AGENT_PORT)
-    subproc.current.kill()
-
-    shutil.rmtree(options['output_path'], ignore_errors=True)
-    os.makedirs(options['output_path'])
-
-    dump_index_markdown(master_help, agent_help, options)
-    dump_markdown(master_help, options)
-    dump_markdown(agent_help, options)
-
-if __name__ == '__main__':
-    main()
diff --git a/support/python3/jsonurl.py b/support/python3/jsonurl.py
deleted file mode 100755
index ffa3e36..0000000
--- a/support/python3/jsonurl.py
+++ /dev/null
@@ -1,55 +0,0 @@
-#!/usr/bin/env python3
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""
-A utility that expects JSON data at a particular URL and lets you
-recursively extract keys from the JSON object as specified on the
-command line (each argument on the command line after the first will
-be used to recursively index into the JSON object). The name is a
-play off of 'curl'.
-"""
-
-import json
-import sys
-import urllib.request
-import urllib.error
-import urllib.parse
-
-
-def main():
-    """Expects at least one argument on the command line."""
-    if len(sys.argv) < 2:
-        print("USAGE: {} URL [KEY...]".format(sys.argv[0]), file=sys.stderr)
-        sys.exit(1)
-
-    url = sys.argv[1]
-
-    data = json.loads(urllib.request.urlopen(url).read())
-
-    for arg in sys.argv[2:]:
-        try:
-            temp = data[arg]
-            data = temp
-        except KeyError:
-            print("'" + arg + "' was not found", file=sys.stderr)
-            sys.exit(1)
-
-    print(data.encode("utf-8"))
-
-if __name__ == '__main__':
-    main()
diff --git a/support/python3/mesos-gtest-runner.py b/support/python3/mesos-gtest-runner.py
deleted file mode 100755
index 9cf72af..0000000
--- a/support/python3/mesos-gtest-runner.py
+++ /dev/null
@@ -1,293 +0,0 @@
-#!/usr/bin/env python3
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-"""
-Parallel test runner for GoogleTest programs.
-
-This script allows one to execute GoogleTest tests in parallel.
-GoogleTest programs come with built-in support for running in parallel.
-Here tests can automatically be partitioned across a number of test
-program invocations ("shards"). This script provides a convenient
-wrapper around that functionality and stream-lined output.
-"""
-
-import argparse
-import multiprocessing
-import os
-import shlex
-import signal
-import subprocess
-import sys
-
-
-class Bcolors:
-    """
-    A collection of tty output modifiers.
-
-    To switch the output of a string, prefix it with the desired
-    modifier, and terminate it with 'ENDC'.
-    """
-
-    HEADER = '\033[95m' if sys.stdout.isatty() else ''
-    OKBLUE = '\033[94m' if sys.stdout.isatty() else ''
-    OKGREEN = '\033[92m' if sys.stdout.isatty() else ''
-    WARNING = '\033[93m' if sys.stdout.isatty() else ''
-    FAIL = '\033[91m'if sys.stdout.isatty() else ''
-    ENDC = '\033[0m' if sys.stdout.isatty() else ''
-    BOLD = '\033[1m' if sys.stdout.isatty() else ''
-    UNDERLINE = '\033[4m' if sys.stdout.isatty() else ''
-
-    @staticmethod
-    def colorize(string, *color_codes):
-        """Decorate a string with a number of color codes."""
-        colors = ''.join(color_codes)
-        return '{begin}{string}{end}'.format(
-            begin=colors if sys.stdout.isatty() else '',
-            string=string,
-            end=Bcolors.ENDC if sys.stdout.isatty() else '')
-
-
-def run_test(opts):
-    """
-    Perform an actual run of the test executable.
-
-    Expects a list of parameters giving the number of the current
-    shard, the total number of shards, and the executable to run.
-    """
-    shard, nshards, executable = opts
-
-    signal.signal(signal.SIGINT, signal.SIG_IGN)
-
-    env = os.environ.copy()
-    env['GTEST_TOTAL_SHARDS'] = str(nshards)
-    env['GTEST_SHARD_INDEX'] = str(shard)
-
-    try:
-        output = subprocess.check_output(
-            executable.split(),
-            stderr=subprocess.STDOUT,
-            env=env,
-            universal_newlines=True)
-        print(Bcolors.colorize('.', Bcolors.OKGREEN), end='')
-        sys.stdout.flush()
-        return True, output
-    except subprocess.CalledProcessError as error:
-        print(Bcolors.colorize('.', Bcolors.FAIL), end='')
-        sys.stdout.flush()
-        return False, error.output
-
-def parse_arguments():
-    """Return the executable to work on, and a list of options."""
-
-    # If the environment variable `MESOS_GTEST_RUNNER_FLAGS` is set we
-    # use it to set a default set of flags to pass. Flags passed on
-    # the command line always have precedence over these defaults.
-    #
-    # We manually construct `args` here and make use of the fact that
-    # in `optparser`'s implementation flags passed later on the
-    # command line overrule identical flags passed earlier.
-
-    env_var = ''
-    if 'MESOS_GTEST_RUNNER_FLAGS' in os.environ:
-        env_var = os.environ['MESOS_GTEST_RUNNER_FLAGS']
-
-    env_parser = argparse.ArgumentParser()
-    env_parser.add_argument('-j', '--jobs', type=int,
-                            default=int(multiprocessing.cpu_count() * 1.5))
-    env_parser.add_argument('-s', '--sequential', type=str, default='')
-    env_parser.add_argument('-v', '--verbosity', type=int, default=1)
-
-    env_args = env_parser.parse_args(shlex.split(env_var))
-
-    # We have set the default values using the environment variable if
-    # possible, we can now parse the arguments.
-    parser = argparse.ArgumentParser()
-    parser.add_argument(
-        '-j', '--jobs', type=int,
-        default=env_args.jobs,
-        help='number of parallel jobs to spawn. DEFAULT: {default_}'
-        .format(default_=env_args.jobs))
-
-    parser.add_argument(
-        '-s', '--sequential', type=str,
-        default=env_args.sequential,
-        help='gtest filter for tests to run sequentially')
-
-    parser.add_argument(
-        '-v', '--verbosity', type=int,
-        default=env_args.verbosity,
-        help='output verbosity:'
-        ' 0 only shows summarized information,'
-        ' 1 also shows full logs of failed shards, and anything'
-        ' >1 shows all output. DEFAULT: 1')
-    parser.add_argument("executable", help="the executable to work on")
-
-    parser.epilog = (
-        'The environment variable MESOS_GTEST_RUNNER_FLAGS '
-        'can be used to set a default set of flags. Flags passed on the '
-        'command line always have precedence over these defaults.')
-
-    args = parser.parse_args()
-
-
-    if not args.executable:
-        parser.print_usage()
-        sys.exit(1)
-
-    if not os.path.isfile(args.executable):
-        print(
-            Bcolors.colorize(
-                "ERROR: File '{file}' does not exists"
-                .format(file=args.executable), Bcolors.FAIL),
-            file=sys.stderr)
-        sys.exit(1)
-
-    if not os.access(args.executable, os.X_OK):
-        print(
-            Bcolors.colorize(
-                "ERROR: File '{file}' is not executable"
-                .format(file=args.executable), Bcolors.FAIL),
-            file=sys.stderr)
-        sys.exit(1)
-
-    if args.sequential and args.sequential.count(':-'):
-        print(
-            Bcolors.colorize(
-                "ERROR: Cannot use negative filters in "
-                "'sequential' parameter: '{filter}'"
-                .format(filter=args.sequential), Bcolors.FAIL),
-            file=sys.stderr)
-        sys.exit(1)
-
-    if args.sequential and os.environ.get('GTEST_FILTER') and \
-            os.environ['GTEST_FILTER'].count(':-'):
-        print(
-            Bcolors.colorize(
-                "ERROR: Cannot specify both 'sequential' ""option "
-                "and environment variable 'GTEST_FILTER' "
-                "containing negative filters",
-                Bcolors.FAIL),
-            file=sys.stderr)
-        sys.exit(1)
-
-    # Since empty strings are falsy, directly compare against `None`
-    # to preserve an empty string passed via `GTEST_FILTER`.
-    if os.environ.get('GTEST_FILTER') is not None:
-        args.parallel = '{env_filter}:-{sequential_filter}'\
-                         .format(env_filter=os.environ['GTEST_FILTER'],
-                                 sequential_filter=args.sequential)
-    else:
-        args.parallel = '*:-{sequential_filter}'\
-                         .format(sequential_filter=args.sequential)
-
-    executable = args.executable
-    delattr(args, 'executable')
-    return executable, args
-
-
-if __name__ == '__main__':
-    EXECUTABLE, OPTIONS = parse_arguments()
-
-    def options_gen(executable, filter_, jobs):
-        """Generator for options for a certain shard.
-
-        Here we set up GoogleTest specific flags, and generate
-        distinct shard indices.
-        """
-        opts = list(range(jobs))
-
-        # If we run in a terminal, enable colored test output. We
-        # still allow users to disable this themselves via extra args.
-        if sys.stdout.isatty():
-            executable = '{exe} --gtest_color=yes'.format(exe=executable)
-
-        if filter_:
-            executable = '{exe} --gtest_filter={filter}'\
-                         .format(exe=executable, filter=filter_)
-
-        for opt in opts:
-            yield opt, jobs, executable
-
-    try:
-        RESULTS = []
-
-        POOL = multiprocessing.Pool(processes=OPTIONS.jobs)
-
-        # Run parallel tests.
-        RESULTS.extend(
-            POOL.map(
-                run_test,
-                options_gen(EXECUTABLE, OPTIONS.parallel, OPTIONS.jobs)))
-
-        # Now run sequential tests.
-        if OPTIONS.sequential:
-            RESULTS.extend(
-                POOL.map(
-                    run_test,
-                    options_gen(EXECUTABLE, OPTIONS.sequential, 1)))
-
-        # Count the number of failed shards and print results from
-        # failed shards.
-        #
-        # NOTE: The `RESULTS` array stores the result for each
-        # `run_test` invocation returning a tuple (success, output).
-        NFAILED = len([success for success, __ in RESULTS if not success])
-
-        # TODO(bbannier): Introduce a verbosity which prints results
-        # as they arrive; this likely requires some output parsing to
-        # ensure results from different tests do not interleave.
-        for result in RESULTS:
-            if not result[0]:
-                if OPTIONS.verbosity > 0:
-                    print(result[1], file=sys.stderr)
-            else:
-                if OPTIONS.verbosity > 1:
-                    print(result[1], file=sys.stdout)
-
-        if NFAILED > 0:
-            print(Bcolors.colorize(
-                '\n[FAIL]: {nfailed} shard(s) have failed tests'.format(
-                    nfailed=NFAILED),
-                Bcolors.FAIL, Bcolors.BOLD),
-                  file=sys.stderr)
-        else:
-            print(Bcolors.colorize('\n[PASS]', Bcolors.OKGREEN, Bcolors.BOLD))
-
-        sys.exit(NFAILED)
-
-    except KeyboardInterrupt:
-        # Force a newline after intermediate test reports.
-        print()
-
-        print('Caught KeyboardInterrupt, terminating workers')
-
-        POOL.terminate()
-        POOL.join()
-
-        sys.exit(1)
-
-    except OSError as error:
-        print(Bcolors.colorize(
-            '\nERROR: {err}'.format(err=error),
-            Bcolors.FAIL, Bcolors.BOLD))
-
-        POOL.terminate()
-        POOL.join()
-
-        sys.exit(1)
diff --git a/support/python3/mesos-split.py b/support/python3/mesos-split.py
deleted file mode 100755
index 0a77c25..0000000
--- a/support/python3/mesos-split.py
+++ /dev/null
@@ -1,79 +0,0 @@
-#!/usr/bin/env python3
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""
-Errors if a list of files spans across
-the projects which make up mesos.
-"""
-
-from collections import defaultdict
-import sys
-
-if len(sys.argv) < 2:
-    print("Usage: ./mesos-split.py <filename>...")
-
-BASE_PROJECT = "mesos"
-
-SUBPROJECTS = {
-    "libprocess": "3rdparty/libprocess",
-    "stout": "3rdparty/stout"
-}
-
-ERROR = """ERROR: Commit spanning multiple projects.
-
-Please use separate commits for mesos, libprocess and stout.
-
-Paths grouped by project:"""
-
-
-def find_project(filename):
-    """Find a project using its filename."""
-
-    # Find longest prefix match.
-    found_path_len = 0
-    found_project = BASE_PROJECT
-    for project, path in SUBPROJECTS.items():
-        if filename.startswith(path) and len(path) > found_path_len:
-            found_path_len = len(path)
-            found_project = project
-
-    return found_project
-
-
-def main():
-    """
-    Expects a list of filenames on the command line.
-
-    See `support/hooks/pre-commit` for the canonical usage of this method.
-    """
-    touched_projects = defaultdict(list)
-    for filename in sys.argv[1:]:
-        touched_projects[find_project(filename)].append(filename)
-
-    if len(touched_projects) > 1:
-        print(ERROR)
-        for project in touched_projects.keys():
-            print("%s:" % project)
-            for filename in touched_projects[project]:
-                print("  %s" % filename)
-        sys.exit(1)
-
-    sys.exit(0)
-
-if __name__ == '__main__':
-    main()
diff --git a/support/python3/mesos-style.py b/support/python3/mesos-style.py
deleted file mode 100755
index c201038..0000000
--- a/support/python3/mesos-style.py
+++ /dev/null
@@ -1,597 +0,0 @@
-#!/usr/bin/env python3
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""Runs checks for mesos style."""
-
-import os
-import platform
-import re
-import string
-import subprocess
-import sys
-
-from pathlib import PurePath
-
-
-class LinterBase():
-    """
-    This is an abstract class that provides the base functionality for
-    linting files in the mesos project. Its 'main()' function
-    walks through the set of files passed to it and runs some
-    standard linting over them. This includes checking for license headers
-    and checking for non-supported characters. From there it calls a
-    'run_lint()' function that can be overridden to provide
-    customizable style checks for a specific class of files (e.g. C++,
-    Python, etc.).
-
-    Any class that extends from 'LinterBase' should override the
-    following class variables / functions:
-
-    linter_type
-    source_dirs
-    exclude_files
-    source_files
-    comment_prefix
-
-    run_lint()
-
-    Please see the comments below for details on how to override each
-    variable.
-    """
-    # The name of the linter to help with printing which linter files
-    # are currently being processed by.
-    linter_type = ''
-
-    # Root source paths (will be traversed recursively).
-    source_dirs = []
-
-    # Add file paths and patterns which should not be checked
-    # This should include 3rdparty libraries, includes and machine generated
-    # source.
-    exclude_files = ''
-
-    # A regex of possible matches for your source files.
-    source_files = ''
-
-    # A prefix at the beginning of the line to demark comments (e.g. '//')
-    comment_prefix = ''
-
-    def check_encoding(self, source_paths):
-        """
-        Checks for encoding errors in the given files. Source
-        code files must contain only printable ascii characters.
-        This excludes the extended ascii characters 128-255.
-        http://www.asciitable.com/
-        """
-        error_count = 0
-        for path in source_paths:
-            with open(path) as source_file:
-                for line_number, line in enumerate(source_file):
-                    # If we find an error, add 1 to both the character and
-                    # the line offset to give them 1-based indexing
-                    # instead of 0 (as is common in most editors).
-                    char_errors = [offset for offset, char in enumerate(line)
-                                   if char not in string.printable]
-                    if char_errors:
-                        sys.stderr.write(
-                            "{path}:{line_number}:  Non-printable characters"
-                            " found at [{offsets}]: \"{line}\"\n".format(
-                                path=path,
-                                line_number=line_number + 1,
-                                offsets=', '.join([str(offset + 1) for offset
-                                                   in char_errors]),
-                                line=line.rstrip()))
-                        error_count += 1
-
-        return error_count
-
-    def check_license_header(self, source_paths):
-        """Checks the license headers of the given files."""
-        error_count = 0
-        for path in source_paths:
-            with open(path) as source_file:
-                # We read the three first lines of the file as the
-                # first line could be a shebang and the second line empty.
-                head = "".join([next(source_file) for _ in range(3)])
-
-                # TODO(bbannier) We allow `Copyright` for
-                # currently deviating files. This should be
-                # removed one we have a uniform license format.
-                regex = r'^{comment_prefix} [Licensed|Copyright]'.format(
-                    comment_prefix=self.comment_prefix)
-                # pylint: disable=no-member
-                regex = re.compile(regex, re.MULTILINE)
-
-                if not regex.search(head):
-                    sys.stderr.write(
-                        "{path}:1: A license header should appear's on one of"
-                        " the first line of the file starting with"
-                        " '{comment_prefix} Licensed'.: {head}".format(
-                            path=path,
-                            head=head,
-                            comment_prefix=self.comment_prefix))
-                    error_count += 1
-
-        return error_count
-
-    def find_candidates(self, root_dir):
-        """
-        Search through the all files rooted at 'root_dir' and compare
-        them against 'self.exclude_files' and 'self.source_files' to
-        come up with a set of candidate files to lint.
-        """
-        exclude_file_regex = re.compile(self.exclude_files)
-        source_criteria_regex = re.compile(self.source_files)
-        for root, _, files in os.walk(root_dir):
-            for name in files:
-                path = os.path.join(root, name)
-                if exclude_file_regex.search(path) is not None:
-                    continue
-
-                if source_criteria_regex.search(name) is not None:
-                    yield path
-
-    def run_command_in_virtualenv(self, command):
-        """
-        Activate the virtual environment, run the
-        given command and return its output.
-        """
-        virtualenv = os.path.join('support', '.virtualenv')
-
-        if platform.system() == 'Windows':
-            command = r'{virtualenv_path}\Scripts\activate.bat & {cmd}'.format(
-                virtualenv_path=virtualenv, cmd=command)
-        else:
-            command = '. {virtualenv_path}/bin/activate; {cmd}'.format(
-                virtualenv_path=virtualenv, cmd=command)
-
-        return subprocess.Popen(command, shell=True, stdout=subprocess.PIPE)
-
-    # pylint: disable=unused-argument
-    def run_lint(self, source_paths):
-        """
-        A custom function to provide linting for 'linter_type'.
-        It takes a list of source files to lint and returns the number
-        of errors found during the linting process.
-
-        It should print any errors as it encounters them to provide
-        feedback to the caller.
-        """
-        return 0
-
-    def main(self, modified_files):
-        """
-        This function takes a list of files and lints them for the
-        class of files defined by 'linter_type'.
-        """
-
-        # Verify that source roots are accessible from current
-        # working directory. A common error could be to call
-        # the style checker from other (possibly nested) paths.
-        for source_dir in self.source_dirs:
-            if not os.path.exists(source_dir):
-                print("Could not find '{dir}'".format(dir=source_dir))
-                print('Please run from the root of the mesos source directory')
-                exit(1)
-
-        # Add all source file candidates to candidates list.
-        candidates = []
-        for source_dir in self.source_dirs:
-            for candidate in self.find_candidates(source_dir):
-                candidates.append(candidate)
-
-        # Normalize paths of any files given.
-        modified_files = [os.fspath(PurePath(f)) for f in modified_files]
-
-        # If file paths are specified, check all file paths that are
-        # candidates; else check all candidates.
-        file_paths = modified_files if modified_files else candidates
-
-        # Compute the set intersect of the input file paths and candidates.
-        # This represents the reduced set of candidates to run lint on.
-        candidates_set = set(candidates)
-        clean_file_paths_set = set(path.rstrip() for path in file_paths)
-        filtered_candidates_set = clean_file_paths_set.intersection(
-            candidates_set)
-
-        if filtered_candidates_set:
-            plural = '' if len(filtered_candidates_set) == 1 else 's'
-            print('Checking {num_files} {linter} file{plural}'.format(
-                num_files=len(filtered_candidates_set),
-                linter=self.linter_type,
-                plural=plural))
-
-            license_errors = self.check_license_header(filtered_candidates_set)
-            encoding_errors = self.check_encoding(list(filtered_candidates_set))
-            lint_errors = self.run_lint(list(filtered_candidates_set))
-            total_errors = license_errors + encoding_errors + lint_errors
-
-            sys.stderr.write('Total errors found: {num_errors}\n'.format(
-                num_errors=total_errors))
-
-            return total_errors
-
-        print("No {linter} files to lint".format(linter=self.linter_type))
-        return 0
-
-
-class CppLinter(LinterBase):
-    """The linter for C++ files, uses cpplint."""
-    linter_type = 'C++'
-
-    source_dirs = ['src',
-                   'include',
-                   os.path.join('3rdparty', 'libprocess'),
-                   os.path.join('3rdparty', 'stout')]
-
-    exclude_files = '(' \
-                    r'elfio\-3\.2|' \
-                    r'protobuf\-2\.4\.1|' \
-                    r'googletest\-release\-1\.8\.0|' \
-                    r'glog\-0\.3\.3|' \
-                    r'boost\-1\.53\.0|' \
-                    r'libev\-4\.15|' \
-                    r'java/jni|' \
-                    r'\.pb\.cc|\.pb\.h|\.md|\.virtualenv' \
-                    ')'
-
-    source_files = r'\.(cpp|hpp|cc|h)$'
-
-    comment_prefix = r'\/\/'
-
-    def run_lint(self, source_paths):
-        """
-        Runs cpplint over given files.
-
-        http://google-styleguide.googlecode.com/svn/trunk/cpplint/cpplint.py
-        """
-
-        # See cpplint.py for full list of rules.
-        active_rules = [
-            'build/class',
-            'build/deprecated',
-            'build/endif_comment',
-            'build/nullptr',
-            'readability/todo',
-            'readability/namespace',
-            'runtime/vlog',
-            'whitespace/blank_line',
-            'whitespace/comma',
-            'whitespace/end_of_line',
-            'whitespace/ending_newline',
-            'whitespace/forcolon',
-            'whitespace/indent',
-            'whitespace/line_length',
-            'whitespace/operators',
-            'whitespace/semicolon',
-            'whitespace/tab',
-            'whitespace/comments',
-            'whitespace/todo']
-
-        rules_filter = '--filter=-,+' + ',+'.join(active_rules)
-
-        # We do not use a version of cpplint available through pip as
-        # we use a custom version (see cpplint.path) to lint C++ files.
-        process = subprocess.Popen(
-            ['python3', 'support/cpplint.py',
-             rules_filter] + source_paths,
-            stderr=subprocess.PIPE,
-            close_fds=True)
-
-        # Lines are stored and filtered, only showing found errors instead
-        # of e.g., 'Done processing XXX.' which tends to be dominant output.
-        for line in process.stderr:
-            line = line.decode(sys.stdout.encoding)
-            if re.match('^(Done processing |Total errors found: )', line):
-                continue
-            sys.stderr.write(line)
-
-        process.wait()
-        return process.returncode
-
-
-class JsLinter(LinterBase):
-    """The linter for JavaScript files, uses eslint."""
-    linter_type = 'JavaScript'
-
-    source_dirs = [os.path.join('src', 'webui')]
-
-    exclude_files = '(' \
-                    r'angular\-1\.2\.32|' \
-                    r'angular\-route\-1\.2\.32|' \
-                    r'bootstrap\-table\-1\.11\.1|' \
-                    r'clipboard\-1\.5\.16|' \
-                    r'jquery\-3\.2\.1|' \
-                    r'relative\-date|' \
-                    r'ui\-bootstrap\-tpls\-0\.9\.0|' \
-                    r'angular\-route\-1\.2\.32|' \
-                    r'underscore\-1\.4\.3' \
-                    ')'
-
-    source_files = r'\.(js)$'
-
-    comment_prefix = '//'
-
-    def run_lint(self, source_paths):
-        """
-        Runs eslint over given files.
-
-        https://eslint.org/docs/user-guide/configuring
-        """
-
-        num_errors = 0
-
-        source_files = ' '.join(source_paths)
-        config_path = os.path.join('support', '.eslintrc.js')
-
-        process = self.run_command_in_virtualenv(
-            'eslint {files} -c {config} -f compact'.format(
-                files=source_files,
-                config=config_path
-            )
-        )
-
-        for line in process.stdout:
-            line = line.decode(sys.stdout.encoding)
-            if "Error -" in line or "Warning -" in line:
-                sys.stderr.write(line)
-                if "Error -" in line:
-                    num_errors += 1
-
-        return num_errors
-
-
-class PyLinter(LinterBase):
-    """The linter for Python files, uses pylint."""
-    linter_type = 'Python'
-
-    cli_dir = os.path.join('src', 'python', 'cli_new')
-    lib_dir = os.path.join('src', 'python', 'lib')
-    support_dir = os.path.join('support', 'python3')
-    source_dirs_to_lint_with_venv = [support_dir]
-    source_dirs_to_lint_with_tox = [cli_dir, lib_dir]
-    source_dirs = source_dirs_to_lint_with_tox + source_dirs_to_lint_with_venv
-
-    exclude_files = '(' \
-                    r'protobuf\-2\.4\.1|' \
-                    r'googletest\-release\-1\.8\.0|' \
-                    r'glog\-0\.3\.3|' \
-                    r'boost\-1\.53\.0|' \
-                    r'libev\-4\.15|' \
-                    r'java/jni|' \
-                    r'\.virtualenv|' \
-                    r'\.tox' \
-                    ')'
-
-    source_files = r'\.(py)$'
-
-    comment_prefix = '#'
-
-    pylint_config = os.path.abspath(os.path.join('support', 'pylint.config'))
-
-    def run_tox(self, configfile, args, tox_env=None, recreate=False):
-        """
-        Runs tox with given configfile and args. Optionally set tox env
-        and/or recreate the tox-managed virtualenv.
-        """
-        # TODO(ArmandGrillet): Remove one of the calls to
-        # `os.path.dirname` below once the python 3 support scripts
-        # are moved from `support/python3/` to `support/`.
-        support_dir = os.path.dirname(os.path.dirname(__file__))
-        bin_dir = 'Script' if platform.system() == 'Windows' else 'bin'
-
-        cmd = [os.path.join(support_dir, '.virtualenv', bin_dir, 'tox')]
-        cmd += ['-qq']
-        cmd += ['-c', configfile]
-        if tox_env is not None:
-            cmd += ['-e', tox_env]
-        if recreate:
-            cmd += ['--recreate']
-        cmd += ['--']
-        cmd += args
-
-        # We do not use `run_command_in_virtualenv()` here, as we
-        # directly call `tox` from inside the virtual environment bin
-        # directory without activating the virtualenv.
-        return subprocess.Popen(cmd, stdout=subprocess.PIPE)
-
-    def filter_source_files(self, source_dir, source_files):
-        """
-        Filters out files starting with source_dir.
-        """
-        return [f for f in source_files if f.startswith(source_dir)]
-
-    def lint_source_files_under_source_dir(self, source_dir, source_files):
-        """
-        Runs pylint directly or indirectly throgh tox on source_files which
-        are under source_dir. If tox is to be used, it must be configured
-        in source_dir, i.e. a tox.ini must be present.
-        """
-        filtered_source_files = self.filter_source_files(
-            source_dir, source_files)
-
-        if not filtered_source_files:
-            return 0
-
-        if source_dir in self.source_dirs_to_lint_with_tox:
-            process = self.run_tox(
-                configfile=os.path.join(source_dir, 'tox.ini'),
-                args=['--rcfile='+self.pylint_config] + filtered_source_files,
-                tox_env='py3-lint')
-        else:
-            process = self.run_command_in_virtualenv(
-                'pylint --score=n --rcfile={rcfile} {files}'.format(
-                    rcfile=self.pylint_config,
-                    files=' '.join(filtered_source_files)))
-
-        num_errors = 0
-        for line in process.stdout:
-            line = line.decode(sys.stdout.encoding)
-            if re.search(r'[RCWEF][0-9]{4}:', line):
-                num_errors += 1
-            sys.stderr.write(line)
-
-        return num_errors
-
-    def run_lint(self, source_paths):
-        """
-        Runs pylint over given files.
-
-        https://google.github.io/styleguide/pyguide.html
-        """
-        num_errors = 0
-
-        for source_dir in self.source_dirs:
-            num_errors += self.lint_source_files_under_source_dir(
-                source_dir, source_paths)
-
-        return num_errors
-
-
-def should_build_virtualenv(modified_files):
-    """
-    Check if we should build the virtual environment required.
-    This is the case if the requirements of the environment
-    have changed or if the support script is run with no
-    arguments (meaning that the entire codebase should be linted).
-    """
-    # NOTE: If the file list is empty, we are linting the entire test
-    # codebase. We should always rebuild the virtualenv in this case.
-    if not modified_files:
-        return True
-
-    # TODO(ArmandGrillet): Remove one os.path.dirname once python 3
-    # support scripts are moved from support/python3/ to support/.
-    support_dir = os.path.dirname(os.path.dirname(__file__))
-    bin_dir = 'Script' if platform.system() == 'Windows' else 'bin'
-
-    interpreter = os.path.basename(sys.executable)
-    interpreter = os.path.join(support_dir, '.virtualenv', bin_dir, interpreter)
-    if not os.path.isfile(interpreter):
-        return True
-
-    basenames = [os.path.basename(path) for path in modified_files]
-
-    if 'pip-requirements.txt' in basenames:
-        print('The "pip-requirements.txt" file has changed.')
-        return True
-
-    if 'build-virtualenv' in basenames:
-        print('The "build-virtualenv" file has changed.')
-        return True
-
-    # The JS and Python linters require a virtual environment.
-    # If all the files modified are not JS or Python files,
-    # we do not need to build the virtual environment.
-    # TODO(ArmandGrillet): There should be no duplicated logic to know
-    # which linters to instantiate depending on the files to analyze.
-    if not os.path.isdir(os.path.join('support', '.virtualenv')):
-        js_and_python_files = [JsLinter().source_files, PyLinter().source_files]
-        js_and_python_files_regex = re.compile('|'.join(js_and_python_files))
-
-        for basename in basenames:
-            if js_and_python_files_regex.search(basename) is not None:
-                print('Virtualenv not detected and required... building')
-                return True
-
-    return False
-
-
-def build_virtualenv():
-    """
-    Rebuild the virtualenv by running a bootstrap script.
-    This will exit the program if there is a failure.
-    """
-    print('Rebuilding virtualenv...')
-
-    python3_env = os.environ.copy()
-    python3_env["PYTHON"] = sys.executable
-
-    build_virtualenv_file = [os.path.join('support', 'build-virtualenv')]
-
-    if platform.system() == 'Windows':
-        # TODO(andschwa): Port more of the `build-virtualenv` Bash script.
-        python_dir = os.path.dirname(sys.executable)
-        virtualenv = os.path.join(python_dir, 'Scripts', 'virtualenv.exe')
-        build_virtualenv_file = [virtualenv,
-                                 '--no-site-packages',
-                                 'support/.virtualenv']
-
-    process = subprocess.Popen(
-        build_virtualenv_file,
-        env=python3_env,
-        stdout=subprocess.PIPE)
-
-    output = ''
-    for line in process.stdout:
-        output += line.decode(sys.stdout.encoding)
-
-    process.wait()
-
-    if process.returncode != 0:
-        sys.stderr.write(output)
-        sys.exit(1)
-
-    # TODO(andschwa): Move this into a script like above.
-    if platform.system() == 'Windows':
-        def run_command_in_virtualenv(command):
-            """
-            Stolen from `PyLinter`, runs command in virtualenv.
-            """
-            virtualenv = os.path.join('support',
-                                      '.virtualenv',
-                                      'Scripts',
-                                      'activate.bat')
-            command = '{virtualenv_path} & {cmd}'.format(
-                virtualenv_path=virtualenv, cmd=command)
-
-            return subprocess.Popen(command, shell=True, stdout=subprocess.PIPE)
-
-        pip_install_pip = 'python.exe -m pip install --upgrade pip'
-        process = run_command_in_virtualenv(pip_install_pip)
-        for line in process.stdout:
-            output += line.decode(sys.stdout.encoding)
-        process.wait()
-
-        if process.returncode != 0:
-            sys.stderr.write(output)
-            sys.exit(1)
-
-        pip_reqs = 'python.exe -m pip install -r support/pip-requirements.txt'
-        process = run_command_in_virtualenv(pip_reqs)
-        for line in process.stdout:
-            output += line.decode(sys.stdout.encoding)
-        process.wait()
-
-        if process.returncode != 0:
-            sys.stderr.write(output)
-            sys.exit(1)
-
-if __name__ == '__main__':
-    if should_build_virtualenv(sys.argv[1:]):
-        build_virtualenv()
-
-    # TODO(ArmandGrillet): We should only instantiate the linters
-    # required to lint the files to analyze. See MESOS-8351.
-    CPP_LINTER = CppLinter()
-    CPP_ERRORS = CPP_LINTER.main(sys.argv[1:])
-    JS_LINTER = JsLinter()
-    JS_ERRORS = JS_LINTER.main(sys.argv[1:])
-    PY_LINTER = PyLinter()
-    PY_ERRORS = PY_LINTER.main(sys.argv[1:])
-    sys.exit(CPP_ERRORS + JS_ERRORS + PY_ERRORS)
diff --git a/support/python3/post-reviews.py b/support/python3/post-reviews.py
deleted file mode 100755
index feb7d51..0000000
--- a/support/python3/post-reviews.py
+++ /dev/null
@@ -1,433 +0,0 @@
-#!/usr/bin/env python3
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""
-Wrapper around the post-review/rbt tool provided by Review Board.
-
-This script provides the ability to send one review for each
-commit on the current branch, instead of squashing all changes
-into a single large review. We encourage contributors to create
-logical commits which can be reviewed independently.
-
-Options to pass onto 'rbt' can be placed in a '.reviewboardrc'
-file at the top of the Mesos source directory.  A default
-'.reviewboardrc' can be found at 'support/reviewboardrc'.
-Running './bootstrap' will populate this file for you.
-
-To use this script, first install 'RBTools' from Review Board:
-http://www.reviewboard.org/downloads/rbtools/
-
-$ cd /path/to/mesos
-$ [ do some work on your branch off of master, make commit(s) ]
-$ ./support/post-reviews.py
-"""
-# pylint: skip-file
-
-import argparse
-import atexit
-import imp
-import os
-import platform
-import re
-import sys
-import urllib.parse
-
-from distutils.version import LooseVersion
-
-from subprocess import check_output, Popen, PIPE, STDOUT
-
-
-def execute(command, ignore_errors=False):
-    """Execute a process and leave."""
-    process = None
-    try:
-        process = Popen(command,
-                        stdin=PIPE,
-                        stdout=PIPE,
-                        stderr=STDOUT,
-                        shell=False)
-    except Exception:
-        if not ignore_errors:
-            raise
-        return None
-
-    data, _ = process.communicate()
-    data = data.decode(sys.stdout.encoding)
-    status = process.wait()
-    if status != 0 and not ignore_errors:
-        cmdline = ' '.join(command) if isinstance(command, list) else command
-        need_login = 'Please log in to the Review Board' \
-                     ' server at reviews.apache.org.'
-        if need_login in data:
-            print(need_login, '\n')
-            print("You can either:")
-            print("  (1) Run 'rbt login', or")
-            print("  (2) Set the default USERNAME/PASSWORD in '.reviewboardrc'")
-        else:
-            print('Failed to execute: \'' + cmdline + '\':')
-            print(data)
-        sys.exit(1)
-    elif status != 0:
-        return None
-    return data
-
-
-def main():
-    """Main function, post commits added to this branch as review requests."""
-    # TODO(benh): Make sure this is a git repository, apologize if not.
-
-    # Choose 'rbt' if available, otherwise choose 'post-review'.
-    post_review = None
-
-    rbt_command = 'rbt'
-    # Windows command name must have `cmd` extension.
-    if platform.system() == 'Windows':
-        rbt_command = 'rbt.cmd'
-
-    rbt_version = execute([rbt_command, '--version'], ignore_errors=True)
-    if rbt_version:
-        rbt_version = LooseVersion(rbt_version)
-        post_review = [rbt_command, 'post']
-    elif execute(['post-review', '--version'], ignore_errors=True):
-        post_review = ['post-review']
-    else:
-        print('Please install RBTools before proceeding')
-        sys.exit(1)
-
-    # Warn if people have unstaged changes.
-    diff_stat = execute(['git', 'diff', '--shortstat']).strip()
-
-    if diff_stat:
-        print('WARNING: Worktree contains unstaged changes, continuing anyway.', file=sys.stderr)
-
-    # Warn if people have uncommitted changes.
-    diff_stat = execute(['git', 'diff', '--shortstat', '--staged']).strip()
-
-    if diff_stat:
-        print('WARNING: Worktree contains staged but uncommitted changes, ' \
-            'continuing anyway.', file=sys.stderr)
-
-    # Grab a reference to the repo's git directory. Usually this is simply
-    # .git in the repo's top level directory. However, when submodules are
-    # used, it may appear elsewhere. The most up-to-date way of finding this
-    # directory is to use `git rev-parse --git-common-dir`. This is necessary
-    # to support things like git worktree in addition to git submodules.
-    # However, as of January 2016, support for the '--git-common-dir' flag is
-    # fairly new, forcing us to fall back to the '--git-dir' flag if
-    # '--git-common-dir' is not supported. We do this by checking the output of
-    # git rev-parse --git-common-dir` and check if it gives a valid directory.
-    # If not, we set the git directory using the '--git-dir' flag instead.
-    git_dir = execute(['git', 'rev-parse', '--git-common-dir']).strip()
-    if not os.path.isdir(git_dir):
-        git_dir = execute(['git', 'rev-parse', '--git-dir']).strip()
-
-    # Grab a reference to the top level directory of this repo.
-    top_level_dir = execute(['git', 'rev-parse', '--show-toplevel']).strip()
-
-    # Use the tracking_branch specified by the user if exists.
-    parser = argparse.ArgumentParser(add_help=True)
-    parser.add_argument(
-        '--server',
-        help='Specifies the Review Board server to use.')
-    parser.add_argument(
-        '--no-markdown',
-        action='store_true',
-        help='Specifies if the commit text should not be treated as Markdown.')
-    parser.add_argument(
-        '--bugs-closed',
-        help='The comma-separated list of bug IDs closed.')
-    parser.add_argument(
-        '--target-people',
-        help='The usernames of the people who should perform the review.')
-    parser.add_argument(
-        '--tracking-branch',
-        help='The remote tracking branch from which your local branch is derived.')
-    args, _ = parser.parse_known_args()
-
-    # Try to read the .reviewboardrc in the top-level directory.
-    reviewboardrc_filepath = os.path.join(top_level_dir, '.reviewboardrc')
-    if os.path.exists(reviewboardrc_filepath):
-        # Prevent generation of '.reviewboardrcc'.
-        sys.dont_write_bytecode = True
-        reviewboardrc = imp.load_source('reviewboardrc', reviewboardrc_filepath)
-
-    if args.server:
-        reviewboard_url = args.server
-    elif 'REVIEWBOARD_URL' in dir(reviewboardrc):
-        reviewboard_url = reviewboardrc.REVIEWBOARD_URL
-    else:
-        reviewboard_url = 'https://reviews.apache.org'
-
-    if args.tracking_branch:
-        tracking_branch = args.tracking_branch
-    elif 'TRACKING_BRANCH' in dir(reviewboardrc):
-        tracking_branch = reviewboardrc.TRACKING_BRANCH
-    else:
-        tracking_branch = 'master'
-
-    branch_ref = execute(['git', 'symbolic-ref', 'HEAD']).strip()
-    branch = branch_ref.replace('refs/heads/', '', 1)
-
-    # Do not work on the tracking branch.
-    if branch == tracking_branch:
-        print("We're expecting you to be working on another branch" \
-              " from {}!".format(tracking_branch))
-        sys.exit(1)
-
-    temporary_branch = '_post-reviews_' + branch
-
-    # Always delete the temporary branch.
-    atexit.register(
-        lambda: execute(['git', 'branch', '-D', temporary_branch], True))
-
-    # Always put us back on the original branch.
-    atexit.register(lambda: execute(['git', 'checkout', branch]))
-
-    # Warn if the tracking branch is no direct ancestor of this review chain.
-    if execute([
-            'git', 'merge-base', '--is-ancestor', tracking_branch, branch_ref],
-            ignore_errors=True) is None:
-        print("WARNING: Tracking branch '%s' is no direct ancestor of HEAD." \
-            " Did you forget to rebase?" % tracking_branch, file=sys.stderr)
-
-        try:
-            input("Press enter to continue or 'Ctrl-C' to abort.\n")
-        except KeyboardInterrupt:
-            sys.exit(0)
-
-    merge_base = execute(
-        ['git', 'merge-base', tracking_branch, branch_ref]).strip()
-
-    output = check_output([
-        'git',
-        '--no-pager',
-        'log',
-        '--pretty=format:%Cred%H%Creset -%C'
-        '(yellow)%d%Creset %s %Cgreen(%cr)%Creset',
-        merge_base + '..HEAD'])
-
-    print('Running \'%s\' across all of ...' % " ".join(post_review))
-    sys.stdout.buffer.write(output)
-
-    log = execute(['git',
-                   '--no-pager',
-                   'log',
-                   '--no-color',
-                   '--pretty=oneline',
-                   '--reverse',
-                   merge_base + '..HEAD']).strip()
-
-    if len(log) <= 0:
-        print("No new changes compared with master branch!")
-        sys.exit(1)
-
-    shas = []
-
-    for line in log.split('\n'):
-        sha = line.split()[0]
-        shas.append(sha)
-
-    previous = merge_base
-    parent_review_request_id = None
-    for i, sha in enumerate(shas):
-        execute(['git', 'branch', '-D', temporary_branch], True)
-
-        message = execute(['git',
-                           '--no-pager',
-                           'log',
-                           '--pretty=format:%s%n%n%b',
-                           previous + '..' + sha])
-
-        review_request_id = None
-
-        pos = message.find('Review:')
-        if pos != -1:
-            regex = 'Review: ({url})$'.format(
-                url=urllib.parse.urljoin(reviewboard_url, 'r/[0-9]+'))
-            pattern = re.compile(regex)
-            match = pattern.search(message[pos:].strip().strip('/'))
-            if match is None:
-                print("\nInvalid ReviewBoard URL: '{}'".format(message[pos:]))
-                sys.exit(1)
-
-            url = match.group(1)
-            review_request_id = url.split('/')[-1]
-
-        # Show the commit.
-        if review_request_id is None:
-            output = check_output([
-                'git',
-                '--no-pager',
-                'log',
-                '--pretty=format:%Cred%H%Creset -%C(yellow)%d%Creset %s',
-                previous + '..' + sha])
-            print('\nCreating diff of:')
-            sys.stdout.buffer.write(output)
-        else:
-            output = check_output([
-                'git',
-                '--no-pager',
-                'log',
-                '--pretty=format:%Cred%H%Creset -%C'
-                '(yellow)%d%Creset %s %Cgreen(%cr)%Creset',
-                previous + '..' + sha])
-            print('\nUpdating diff of:')
-            sys.stdout.buffer.write(output)
-
-        # Show the "parent" commit(s).
-        output = check_output([
-            'git',
-            '--no-pager',
-            'log',
-            '--pretty=format:%Cred%H%Creset -%C'
-            '(yellow)%d%Creset %s %Cgreen(%cr)%Creset',
-            tracking_branch + '..' + previous])
-
-        if output:
-            print('\n... with parent diff created from:')
-            sys.stdout.buffer.write(output)
-
-        try:
-            input('\nPress enter to continue or \'Ctrl-C\' to skip.\n')
-        except KeyboardInterrupt:
-            i = i + 1
-            previous = sha
-            parent_review_request_id = review_request_id
-            continue
-
-        # Strip the review url from the commit message, so that
-        # it is not included in the summary message when GUESS_FIELDS
-        # is set in .reviewboardc. Update the SHA appropriately.
-        if review_request_id:
-            stripped_message = message[:pos]
-            execute(['git', 'checkout', sha])
-            execute(['git', 'commit', '--amend', '-m', stripped_message])
-            sha = execute(['git', 'rev-parse', 'HEAD']).strip()
-            execute(['git', 'checkout', branch])
-
-        revision_range = previous + ':' + sha
-
-        # Build the post-review/rbt command up
-        # to the point where they are common.
-        command = post_review
-
-        if not args.no_markdown:
-            command = command + ['--markdown']
-
-        if args.bugs_closed:
-            command = command + ['--bugs-closed=' + args.bugs_closed]
-
-        if args.target_people:
-            command = command + ['--target-people=' + args.target_people]
-
-        if args.tracking_branch is None:
-            command = command + ['--tracking-branch=' + tracking_branch]
-
-        if review_request_id:
-            command = command + ['--review-request-id=' + review_request_id]
-
-        # Determine how to specify the revision range.
-        if rbt_command in post_review and \
-           rbt_version >= LooseVersion('RBTools 0.6'):
-            # rbt >= 0.6.1 supports '--depends-on' argument.
-            # Only set the "depends on" if this
-            # is not  the first review in the chain.
-            if rbt_version >= LooseVersion('RBTools 0.6.1') and \
-               parent_review_request_id:
-                command = command + ['--depends-on=' + parent_review_request_id]
-
-            # rbt >= 0.6 revisions are passed in as args.
-            command = command + sys.argv[1:] + [previous, sha]
-        else:
-            # post-review and rbt < 0.6 revisions are
-            # passed in using the revision range option.
-            command = command + \
-                ['--revision-range=' + revision_range] + \
-                sys.argv[1:]
-
-        output = execute(command)
-
-        # Output is a string, we convert it to a byte string before writing it.
-        sys.stdout.buffer.write(output.encode())
-
-        # If we already have a request_id, continue on to the next commit in the
-        # chain. We update 'previous' from the shas[] array because we have
-        # overwritten the temporary sha variable above.
-        if review_request_id is not None:
-            previous = shas[i]
-            parent_review_request_id = review_request_id
-            i = i + 1
-            continue
-
-        # Otherwise, get the request_id from the output of post-review, append
-        # it to the commit message and rebase all other commits on top of it.
-        lines = output.split('\n')
-
-        # The last line of output in post-review is the review url.
-        # The second to the last line of output in rbt is the review url.
-        url = lines[len(lines) - 2] if rbt_command in post_review \
-            else lines[len(lines) - 1]
-
-        # Using rbt >= 0.6.3 on Linux prints out two URLs where the second
-        # one has /diff/ at the end. We want to remove this so that a
-        # subsequent call to post-reviews does not fail when looking up
-        # the reviewboard entry to edit.
-        url = url.replace('diff/', '')
-        url = url.strip('/')
-        review_request_id = os.path.basename(url)
-
-        # Construct new commit message.
-        message = message + '\n' + 'Review: ' + url + '\n'
-
-        execute(['git', 'checkout', '-b', temporary_branch])
-        execute(['git', 'reset', '--hard', sha])
-        execute(['git', 'commit', '--amend', '-m', message])
-
-        # Now rebase all remaining shas on top of this amended commit.
-        j = i + 1
-        old_sha = execute(
-            ['git', 'rev-parse', '--verify', temporary_branch]).strip()
-        previous = old_sha
-        while j < len(shas):
-            execute(['git', 'checkout', shas[j]])
-            execute(['git', 'rebase', temporary_branch])
-            # Get the sha for our detached HEAD.
-            new_sha = execute([
-                'git',
-                '--no-pager',
-                'log',
-                '--pretty=format:%H', '-n', '1', 'HEAD']).strip()
-            execute(['git',
-                     'update-ref',
-                     'refs/heads/' + temporary_branch,
-                     new_sha,
-                     old_sha])
-            old_sha = new_sha
-            shas[j] = new_sha
-            j = j + 1
-
-        # Okay, now update the actual branch to our temporary branch.
-        new_sha = old_sha
-        old_sha = execute(['git', 'rev-parse', '--verify', branch]).strip()
-        execute(['git', 'update-ref', 'refs/heads/' + branch, new_sha, old_sha])
-
-        i = i + 1
-        parent_review_request_id = review_request_id
-
-if __name__ == '__main__':
-    main()
diff --git a/support/python3/push-commits.py b/support/python3/push-commits.py
deleted file mode 100755
index 7be67e3..0000000
--- a/support/python3/push-commits.py
+++ /dev/null
@@ -1,167 +0,0 @@
-#!/usr/bin/env python3
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""
-This script is typically used by Mesos committers to push a locally applied
-review chain to ASF git repo and mark the reviews as submitted on ASF
-ReviewBoard.
-
-Example Usage:
-
-> git checkout master
-> git pull origin
-> ./support/python3/apply-reviews.py -c -r 1234
-> ./support/python3/push-commits.py
-"""
-
-# TODO(vinod): Also post the commit message to the corresponding ASF JIRA
-# tickets and resolve them if necessary.
-
-import argparse
-import os
-import platform
-import re
-import sys
-import urllib.parse
-
-from subprocess import check_output
-
-REVIEWBOARD_URL = 'https://reviews.apache.org'
-
-def _check_output(args):
-    return check_output(args).decode(sys.stdout.encoding)
-
-def get_reviews(revision_range):
-    """Return the list of reviews found in the commits in the revision range."""
-    reviews = [] # List of (review id, commit log) tuples
-
-    rev_list = _check_output(['git',
-                              'rev-list',
-                              '--reverse',
-                              revision_range]).strip().split('\n')
-    for rev in rev_list:
-        commit_log = _check_output(['git',
-                                    '--no-pager',
-                                    'show',
-                                    '--no-color',
-                                    '--no-patch',
-                                    rev]).strip()
-
-        pos = commit_log.find('Review: ')
-        if pos != -1:
-            regex = 'Review: ({url})$'.format(
-                url=urllib.parse.urljoin(REVIEWBOARD_URL, 'r/[0-9]+'))
-            pattern = re.compile(regex)
-            match = pattern.search(commit_log.strip().strip('/'))
-            if match is None:
-                print("\nInvalid ReviewBoard URL: '{}'".format(
-                    commit_log[pos:]))
-                sys.exit(1)
-
-            url = match.group(1)
-            reviews.append((os.path.basename(url), commit_log))
-
-    return reviews
-
-
-def close_reviews(reviews, options):
-    """Mark the given reviews as submitted on ReviewBoard."""
-    for review_id, commit_log in reviews:
-        print('Closing review', review_id)
-        if not options['dry_run']:
-            rbt_command = 'rbt'
-            # Windows command name must have `cmd` extension.
-            if platform.system() == 'Windows':
-                rbt_command = 'rbt.cmd'
-            _check_output([rbt_command,
-                           'close',
-                           '--description',
-                           commit_log,
-                           review_id])
-
-
-def parse_options():
-    """Return a dictionary of options parsed from command line arguments."""
-    parser = argparse.ArgumentParser()
-
-    parser.add_argument('-n',
-                        '--dry-run',
-                        action='store_true',
-                        help='Perform a dry run.')
-
-    args = parser.parse_args()
-
-    options = {}
-    options['dry_run'] = args.dry_run
-
-    return options
-
-
-def main():
-    """Main function to push the commits in this branch as review requests."""
-    options = parse_options()
-
-    current_branch_ref = _check_output(['git', 'symbolic-ref', 'HEAD']).strip()
-    current_branch = current_branch_ref.replace('refs/heads/', '', 1)
-
-    if current_branch != 'master':
-        print('Please run this script from master branch')
-        sys.exit(1)
-
-    remote_tracking_branch = _check_output(['git',
-                                            'rev-parse',
-                                            '--abbrev-ref',
-                                            'master@{upstream}']).strip()
-
-    merge_base = _check_output([
-        'git',
-        'merge-base',
-        remote_tracking_branch,
-        'master']).strip()
-
-    if merge_base == current_branch_ref:
-        print('No new commits found to push')
-        sys.exit(1)
-
-    reviews = get_reviews(merge_base + ".." + current_branch_ref)
-
-    # Push the current branch to remote master.
-    remote = _check_output(['git',
-                            'config',
-                            '--get',
-                            'branch.master.remote']).strip()
-
-    print('Pushing commits to', remote)
-
-    if options['dry_run']:
-        _check_output(['git',
-                       'push',
-                       '--dry-run',
-                       remote,
-                       'master:master'])
-    else:
-        _check_output(['git',
-                       'push',
-                       remote,
-                       'master:master'])
-
-    # Now mark the reviews as submitted.
-    close_reviews(reviews, options)
-
-if __name__ == '__main__':
-    main()
diff --git a/support/python3/test-upgrade.py b/support/python3/test-upgrade.py
deleted file mode 100755
index 3a7bcac..0000000
--- a/support/python3/test-upgrade.py
+++ /dev/null
@@ -1,254 +0,0 @@
-#!/usr/bin/env python3
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""Script to test the upgrade path between two versions of Mesos."""
-
-import argparse
-import os
-import subprocess
-import sys
-import tempfile
-import time
-
-DEFAULT_PRINCIPAL = 'foo'
-DEFAULT_SECRET = 'bar'
-
-
-class Process():
-    """
-    Helper class to keep track of process lifecycles.
-
-    This class allows to start processes, capture their
-    output, and check their liveness during delays/sleep.
-    """
-
-    def __init__(self, args, environment=None):
-        """Initialize the Process."""
-        outfile = tempfile.mktemp()
-        fout = open(outfile, 'w')
-        print('Run %s, output: %s' % (args, outfile))
-
-        # TODO(nnielsen): Enable glog verbose logging.
-        self.process = subprocess.Popen(args,
-                                        stdout=fout,
-                                        stderr=subprocess.STDOUT,
-                                        env=environment)
-
-    def sleep(self, seconds):
-        """
-        Poll the process for the specified number of seconds.
-
-        If the process ends during that time, this method returns the process's
-        return value. If the process is still running after that time period,
-        this method returns `True`.
-        """
-        poll_time = 0.1
-        while seconds > 0:
-            seconds -= poll_time
-            time.sleep(poll_time)
-            poll = self.process.poll()
-            if poll is not None:
-                return poll
-        return True
-
-    def __del__(self):
-        """Kill the Process."""
-        if self.process.poll() is None:
-            self.process.kill()
-
-
-class Agent(Process):
-    """Class representing an agent process."""
-
-    def __init__(self, path, work_dir, credfile):
-        """Initialize a Mesos agent by running mesos-slave.sh."""
-        Process.__init__(self, [os.path.join(path, 'bin', 'mesos-slave.sh'),
-                                '--master=127.0.0.1:5050',
-                                '--credential=' + credfile,
-                                '--work_dir=' + work_dir,
-                                '--resources=disk:2048;mem:2048;cpus:2'])
-
-
-class Master(Process):
-    """Class representing a master process."""
-
-    def __init__(self, path, work_dir, credfile):
-        """Initialize a Mesos master by running mesos-master.sh."""
-        Process.__init__(self, [os.path.join(path, 'bin', 'mesos-master.sh'),
-                                '--ip=127.0.0.1',
-                                '--work_dir=' + work_dir,
-                                '--authenticate',
-                                '--credentials=' + credfile,
-                                '--roles=test'])
-
-
-# TODO(greggomann): Add support for multiple frameworks.
-class Framework(Process):
-    """Class representing a framework instance (the test-framework for now)."""
-
-    def __init__(self, path):
-        """Initialize a framework."""
-        # The test-framework can take these parameters as environment variables,
-        # but not as command-line parameters.
-        environment = {
-            # In Mesos 0.28.0, the `MESOS_BUILD_DIR` environment variable in the
-            # test framework was changed to `MESOS_HELPER_DIR`, and the '/src'
-            # subdirectory was added to the variable's path. Both are included
-            # here for backwards compatibility.
-            'MESOS_BUILD_DIR': path,
-            'MESOS_HELPER_DIR': os.path.join(path, 'src'),
-            # MESOS_AUTHENTICATE is deprecated in favor of
-            # MESOS_AUTHENTICATE_FRAMEWORKS, although 0.28.x still expects
-            # previous one, therefore adding both.
-            'MESOS_AUTHENTICATE': '1',
-            'MESOS_AUTHENTICATE_FRAMEWORKS': '1',
-            'DEFAULT_PRINCIPAL': DEFAULT_PRINCIPAL,
-            'DEFAULT_SECRET': DEFAULT_SECRET
-        }
-
-        Process.__init__(self, [os.path.join(path, 'src', 'test-framework'),
-                                '--master=127.0.0.1:5050'], environment)
-
-
-def version(path):
-    """Get the Mesos version from the built executables."""
-    mesos_master_path = os.path.join(path, 'bin', 'mesos-master.sh')
-    process = subprocess.Popen([mesos_master_path, '--version'],
-                               stdout=subprocess.PIPE,
-                               stderr=subprocess.PIPE)
-    output, _ = process.communicate()
-    return_code = process.returncode
-    if return_code != 0:
-        return False
-
-    return output[:-1]
-
-
-def create_master(master_version, build_path, work_dir, credfile):
-    """Create a master using a specific version."""
-    print('##### Starting %s master #####' % master_version)
-    master = Master(build_path, work_dir, credfile)
-    if not master.sleep(0.5):
-        print('%s master exited prematurely' % master_version)
-        sys.exit(1)
-    return master
-
-
-def create_agent(agent_version, build_path, work_dir, credfile):
-    """Create an agent using a specific version."""
-    print('##### Starting %s agent #####' % agent_version)
-    agent = Agent(build_path, work_dir, credfile)
-    if not agent.sleep(0.5):
-        print('%s agent exited prematurely' % agent_version)
-        sys.exit(1)
-    return agent
-
-
-def test_framework(framework_version, build_path):
-    """Run a version of the test framework on a specified version of Mesos."""
-    print('##### Starting %s framework #####' % framework_version)
-    print('Waiting for %s framework to complete (10 sec max)...' % (
-        framework_version))
-    framework = Framework(build_path)
-    if framework.sleep(10) != 0:
-        print('%s framework failed' % framework_version)
-        sys.exit(1)
-
-
-# TODO(nnielsen): Add support for zookeeper and failover of master.
-# TODO(nnielsen): Add support for testing scheduler live upgrade/failover.
-def main():
-    """Main function to test the upgrade between two Mesos builds."""
-    parser = argparse.ArgumentParser(
-        description='Test upgrade path between two mesos builds')
-    parser.add_argument('--prev',
-                        type=str,
-                        help='Build path to mesos version to upgrade from',
-                        required=True)
-
-    parser.add_argument('--next',
-                        type=str,
-                        help='Build path to mesos version to upgrade to',
-                        required=True)
-    args = parser.parse_args()
-
-    # Get the version strings from the built executables.
-    prev_version = version(args.prev)
-    next_version = version(args.__next__)
-
-    if not prev_version or not next_version:
-        print('Could not get mesos version numbers')
-        sys.exit(1)
-
-    # Write credentials to temporary file.
-    credfile = tempfile.mktemp()
-    with open(credfile, 'w') as fout:
-        fout.write(DEFAULT_PRINCIPAL + ' ' + DEFAULT_SECRET)
-
-    # Create a work directory for the master.
-    master_work_dir = tempfile.mkdtemp()
-
-    # Create a work directory for the agent.
-    agent_work_dir = tempfile.mkdtemp()
-
-    print('Running upgrade test from %s to %s' % (prev_version, next_version))
-
-    print("""\
-+--------------+----------------+----------------+---------------+
-| Test case    |   Framework    |     Master     |     Agent     |
-+--------------+----------------+----------------+---------------+
-|    #1        |  %s\t| %s\t | %s\t |
-|    #2        |  %s\t| %s\t | %s\t |
-|    #3        |  %s\t| %s\t | %s\t |
-|    #4        |  %s\t| %s\t | %s\t |
-+--------------+----------------+----------------+---------------+
-
-NOTE: live denotes that master process keeps running from previous case.
-    """ % (prev_version, prev_version, prev_version,
-           prev_version, next_version, prev_version,
-           prev_version, next_version, next_version,
-           next_version, next_version, next_version))
-
-    # Test case 1.
-    master = create_master(prev_version, args.prev, master_work_dir, credfile)
-    agent = create_agent(prev_version, args.prev, agent_work_dir, credfile)
-    test_framework(prev_version, args.prev)
-
-    # Test case 2.
-    # NOTE: Need to stop and start the agent because standalone detector does
-    # not detect master failover.
-    agent.process.kill()
-    master.process.kill()
-    master = create_master(next_version, args.__next__, master_work_dir,
-                           credfile)
-    agent = create_agent(prev_version, args.prev, agent_work_dir, credfile)
-    test_framework(prev_version, args.prev)
-
-    # Test case 3.
-    agent.process.kill()
-    agent = create_agent(next_version, args.__next__, agent_work_dir, credfile)
-    test_framework(prev_version, args.prev)
-
-    # Test case 4.
-    test_framework(next_version, args.__next__)
-
-    # Tests passed.
-    sys.exit(0)
-
-if __name__ == '__main__':
-    main()
diff --git a/support/python3/verify-reviews.py b/support/python3/verify-reviews.py
deleted file mode 100755
index 14e5f9b..0000000
--- a/support/python3/verify-reviews.py
+++ /dev/null
@@ -1,297 +0,0 @@
-#!/usr/bin/env python3
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""
-This script is used to build and test (verify) reviews that are posted
-to ReviewBoard. The script is intended for use by automated "ReviewBots"
-that are run on ASF infrastructure (or by anyone that wishes to donate
-some compute power). For example, see 'support/jenkins/reviewbot.sh'.
-
-The script performs the following sequence:
-* A query grabs review IDs from Reviewboard.
-* In reverse order (most recent first), the script determines if the
-  review needs verification (if the review has been updated or changed
-  since the last run through this script).
-* For each review that needs verification:
-  * The review is applied (via 'support/python3/apply-reviews.py').
-  * Mesos is built and unit tests are run.
-  * The result is posted to ReviewBoard.
-"""
-
-import argparse
-import time
-import datetime
-import json
-import os
-import platform
-import subprocess
-import sys
-import urllib.error
-import urllib.parse
-import urllib.request
-
-from common import ReviewBoardHandler, ReviewError, REVIEWBOARD_URL
-
-REVIEW_SIZE = 1000000  # 1 MB in bytes.
-# This is the mesos repo ID obtained from querying the reviews.apache.org API
-MESOS_REPOSITORY_ID = 122
-
-
-def parse_parameters():
-    """Method to parse the arguments for argparse."""
-    parser = argparse.ArgumentParser(
-        description="Reviews that need verification from the Review Board")
-    parser.add_argument("-u", "--user", type=str, required=True,
-                        help="Review Board user name")
-    parser.add_argument("-p", "--password", type=str, required=True,
-                        help="Review Board user password")
-    parser.add_argument("-r", "--reviews", type=int, required=False,
-                        default=-1, help="The number of reviews to fetch,"
-                                         " that will need verification")
-    default_hours_behind = 8
-    datetime_before = (datetime.datetime.now() -
-                       datetime.timedelta(hours=default_hours_behind))
-    datetime_before_string = datetime_before.isoformat()
-    default_query = {"status": "pending", "repository": MESOS_REPOSITORY_ID,
-                     "last-updated-from": datetime_before_string.split(".")[0]}
-    parser.add_argument("-q", "--query", type=str, required=False,
-                        help="Query parameters, passed as string in JSON"
-                             " format. Example: '%s'" % json.dumps(
-                                 default_query),
-                        default=json.dumps(default_query))
-
-    subparsers = parser.add_subparsers(title="The script plug-in type")
-
-    file_parser = subparsers.add_parser(
-        "file", description="File plug-in just writes to a file all"
-                            " the review ids that need verification")
-    file_parser.add_argument("-o", "--out-file", type=str, required=True,
-                             help="The out file with the reviews IDs that"
-                                  " need verification")
-
-    return parser.parse_args()
-
-
-def shell(command):
-    """Run a shell command."""
-    print(command)
-    return subprocess.check_output(
-        command, stderr=subprocess.STDOUT, shell=True)
-
-
-def apply_review(review_id):
-    """Apply a review using the script apply-reviews.py."""
-    print("Applying review %s" % review_id)
-    shell("python support/python3/apply-reviews.py -n -r %s" % review_id)
-
-
-def apply_reviews(review_request, reviews, handler):
-    """Apply multiple reviews at once."""
-    # If there are no reviewers specified throw an error.
-    if not review_request["target_people"]:
-        raise ReviewError("No reviewers specified. Please find a reviewer by"
-                          " asking on JIRA or the mailing list.")
-
-    # If there is a circular dependency throw an error.`
-    if review_request["id"] in reviews:
-        raise ReviewError("Circular dependency detected for review %s."
-                          "Please fix the 'depends_on' field."
-                          % review_request["id"])
-    else:
-        reviews.append(review_request["id"])
-
-    # First recursively apply the dependent reviews.
-    for review in review_request["depends_on"]:
-        review_url = review["href"]
-        print("Dependent review: %s" % review_url)
-        apply_reviews(handler.api(review_url)["review_request"],
-                      reviews, handler)
-
-    # Now apply this review if not yet submitted.
-    if review_request["status"] != "submitted":
-        apply_review(review_request["id"])
-
-
-def post_review(review_request, message, handler):
-    """Post a review on the review board."""
-    print("Posting review: %s" % message)
-
-    review_url = review_request["links"]["reviews"]["href"]
-    data = urllib.parse.urlencode({'body_top': message, 'public': 'true'})
-    handler.api(review_url, data)
-
-
-# @atexit.register
-def cleanup():
-    """Clean the git repository."""
-    try:
-        shell("git clean -fd")
-        HEAD = shell("git rev-parse HEAD")
-        print(HEAD)
-        shell("git checkout HEAD -- %s" % HEAD)
-    except subprocess.CalledProcessError as err:
-        print("Failed command: %s\n\nError: %s" % (err.cmd, err.output))
-
-
-def verify_review(review_request, handler):
-    """Verify a review."""
-    print("Verifying review %s" % review_request["id"])
-    build_output = "build_" + str(review_request["id"])
-
-    try:
-        # Recursively apply the review and its dependents.
-        reviews = []
-        apply_reviews(review_request, reviews, handler)
-
-        reviews.reverse()  # Reviews are applied in the reverse order.
-
-        if platform.system() == 'Windows':
-            command = "support\\windows-build.bat"
-
-            # There is no equivalent to `tee` on Windows.
-            subprocess.check_call(
-                ['cmd', '/c', '%s 2>&1 > %s' % (command, build_output)])
-        else:
-            # Launch docker build script.
-
-            # TODO(jojy): Launch 'docker_build.sh' in subprocess so that
-            # verifications can be run in parallel for various configurations.
-            configuration = ("export "
-                             "OS='ubuntu:14.04' "
-                             "BUILDTOOL='autotools' "
-                             "COMPILER='gcc' "
-                             "CONFIGURATION='--verbose "
-                             "--disable-libtool-wrappers' "
-                             "ENVIRONMENT='GLOG_v=1 MESOS_VERBOSE=1'")
-
-            command = "%s; ./support/docker-build.sh" % configuration
-
-            # `tee` the output so that the console can log the whole build
-            # output. `pipefail` ensures that the exit status of the build
-            # command ispreserved even after tee'ing.
-            subprocess.check_call(['bash', '-c',
-                                   'set -o pipefail; %s 2>&1 | tee %s'
-                                   % (command, build_output)])
-
-        # Success!
-        post_review(
-            review_request,
-            "Patch looks great!\n\n"
-            "Reviews applied: %s\n\n"
-            "Passed command: %s" % (reviews, command), handler)
-    except subprocess.CalledProcessError as err:
-        # If we are here because the docker build command failed, read the
-        # output from `build_output` file. For all other command failures read
-        # the output from `e.output`.
-        if os.path.exists(build_output):
-            output = open(build_output).read()
-        else:
-            output = err.output
-
-        if platform.system() == 'Windows':
-            # We didn't output anything during the build (because `tee`
-            # doesn't exist), so we print the output to stdout upon error.
-
-            # Pylint raises a no-member error on that line due to a bug
-            # fixed in pylint 1.7.
-            # TODO(ArmandGrillet): Remove this once pylint updated to >= 1.7.
-            # pylint: disable=no-member
-            sys.stdout.buffer.write(output)
-
-        # Truncate the output when posting the review as it can be very large.
-        if len(output) > REVIEW_SIZE:
-            output = "...<truncated>...\n" + output[-REVIEW_SIZE:]
-
-        output += "\nFull log: "
-        output += urllib.parse.urljoin(os.environ['BUILD_URL'], 'console')
-
-        post_review(
-            review_request,
-            "Bad patch!\n\n" \
-            "Reviews applied: %s\n\n" \
-            "Failed command: %s\n\n" \
-            "Error:\n%s" % (reviews, err.cmd, output), handler)
-    except ReviewError as err:
-        post_review(
-            review_request,
-            "Bad review!\n\n" \
-            "Reviews applied: %s\n\n" \
-            "Error:\n%s" % (reviews, err.args[0]), handler)
-
-    # Clean up.
-    # cleanup()
-
-
-def verification_needed_write(review_ids, parameters):
-    """Write the IDs of the review requests that need verification."""
-    num_reviews = len(review_ids)
-    print("%s review requests need verification" % num_reviews)
-    # out_file parameter is mandatory to be passed
-    try:
-        # Using file plug-in
-        with open(parameters.out_file, 'w') as f:
-            f.write('\n'.join(review_ids))
-    except Exception:
-        print("Failed opening file '%s' for writing" % parameters.out_file)
-        raise
-
-
-def main():
-    """Main function to verify the submitted reviews."""
-    parameters = parse_parameters()
-    print("\n%s - Running %s" % (time.strftime('%m-%d-%y_%T'),
-                                 os.path.abspath(__file__)))
-    # The colon from timestamp gets encoded and we don't want it to be encoded.
-    # Replacing %3A with colon.
-    query_string = urllib.parse.urlencode(
-        json.loads(parameters.query)).replace("%3A", ":")
-    review_requests_url = "%s/api/review-requests/?%s" % (REVIEWBOARD_URL,
-                                                          query_string)
-    handler = ReviewBoardHandler(parameters.user, parameters.password)
-    num_reviews = 0
-    review_ids = []
-    review_requests = handler.api(review_requests_url)
-    for review_request in reversed(review_requests["review_requests"]):
-        if parameters.reviews == -1 or num_reviews < parameters.reviews:
-            try:
-                needs_verification = handler.needs_verification(review_request)
-                if not needs_verification:
-                    continue
-                # An exception is raised if cyclic dependencies are found
-                handler.get_dependent_review_ids(review_request)
-            except ReviewError as err:
-                message = ("Bad review!\n\n"
-                           "Error:\n%s" % (err.args[0]))
-                handler.post_review(review_request, message, handler)
-                continue
-            except Exception as err:
-                print("Error occured: %s" % err)
-                needs_verification = False
-                print("WARNING: Cannot find if review %s needs"
-                      " verification" % (review_request["id"]))
-            if not needs_verification:
-                continue
-            review_ids.append(str(review_request["id"]))
-            num_reviews += 1
-            verify_review(review_request, handler)
-
-    verification_needed_write(review_ids, parameters)
-
-
-if __name__ == '__main__':
-    main()
diff --git a/support/test-upgrade.py b/support/test-upgrade.py
index b7c6612..3a7bcac 100755
--- a/support/test-upgrade.py
+++ b/support/test-upgrade.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 #
 # Licensed to the Apache Software Foundation (ASF) under one
 # or more contributor license agreements.  See the NOTICE file
@@ -29,7 +29,7 @@ DEFAULT_PRINCIPAL = 'foo'
 DEFAULT_SECRET = 'bar'
 
 
-class Process(object):
+class Process():
     """
     Helper class to keep track of process lifecycles.
 
@@ -41,7 +41,7 @@ class Process(object):
         """Initialize the Process."""
         outfile = tempfile.mktemp()
         fout = open(outfile, 'w')
-        print 'Run %s, output: %s' % (args, outfile)
+        print('Run %s, output: %s' % (args, outfile))
 
         # TODO(nnielsen): Enable glog verbose logging.
         self.process = subprocess.Popen(args,
@@ -62,7 +62,7 @@ class Process(object):
             seconds -= poll_time
             time.sleep(poll_time)
             poll = self.process.poll()
-            if poll != None:
+            if poll is not None:
                 return poll
         return True
 
@@ -141,32 +141,32 @@ def version(path):
 
 def create_master(master_version, build_path, work_dir, credfile):
     """Create a master using a specific version."""
-    print '##### Starting %s master #####' % master_version
+    print('##### Starting %s master #####' % master_version)
     master = Master(build_path, work_dir, credfile)
     if not master.sleep(0.5):
-        print '%s master exited prematurely' % master_version
+        print('%s master exited prematurely' % master_version)
         sys.exit(1)
     return master
 
 
 def create_agent(agent_version, build_path, work_dir, credfile):
     """Create an agent using a specific version."""
-    print '##### Starting %s agent #####' % agent_version
+    print('##### Starting %s agent #####' % agent_version)
     agent = Agent(build_path, work_dir, credfile)
     if not agent.sleep(0.5):
-        print '%s agent exited prematurely' % agent_version
+        print('%s agent exited prematurely' % agent_version)
         sys.exit(1)
     return agent
 
 
 def test_framework(framework_version, build_path):
     """Run a version of the test framework on a specified version of Mesos."""
-    print '##### Starting %s framework #####' % framework_version
-    print 'Waiting for %s framework to complete (10 sec max)...' % (
-        framework_version)
+    print('##### Starting %s framework #####' % framework_version)
+    print('Waiting for %s framework to complete (10 sec max)...' % (
+        framework_version))
     framework = Framework(build_path)
     if framework.sleep(10) != 0:
-        print '%s framework failed' % framework_version
+        print('%s framework failed' % framework_version)
         sys.exit(1)
 
 
@@ -187,17 +187,12 @@ def main():
                         required=True)
     args = parser.parse_args()
 
-    # TODO(ArmandGrillet): Remove this when we'll have switched to Python 3.
-    dir_path = os.path.dirname(os.path.realpath(__file__))
-    script_path = os.path.join(dir_path, 'check-python3.py')
-    subprocess.call('python ' + script_path, shell=True, cwd=dir_path)
-
     # Get the version strings from the built executables.
     prev_version = version(args.prev)
-    next_version = version(args.next)
+    next_version = version(args.__next__)
 
     if not prev_version or not next_version:
-        print 'Could not get mesos version numbers'
+        print('Could not get mesos version numbers')
         sys.exit(1)
 
     # Write credentials to temporary file.
@@ -211,9 +206,9 @@ def main():
     # Create a work directory for the agent.
     agent_work_dir = tempfile.mkdtemp()
 
-    print 'Running upgrade test from %s to %s' % (prev_version, next_version)
+    print('Running upgrade test from %s to %s' % (prev_version, next_version))
 
-    print """\
+    print("""\
 +--------------+----------------+----------------+---------------+
 | Test case    |   Framework    |     Master     |     Agent     |
 +--------------+----------------+----------------+---------------+
@@ -227,7 +222,7 @@ NOTE: live denotes that master process keeps running from previous case.
     """ % (prev_version, prev_version, prev_version,
            prev_version, next_version, prev_version,
            prev_version, next_version, next_version,
-           next_version, next_version, next_version)
+           next_version, next_version, next_version))
 
     # Test case 1.
     master = create_master(prev_version, args.prev, master_work_dir, credfile)
@@ -239,17 +234,18 @@ NOTE: live denotes that master process keeps running from previous case.
     # not detect master failover.
     agent.process.kill()
     master.process.kill()
-    master = create_master(next_version, args.next, master_work_dir, credfile)
+    master = create_master(next_version, args.__next__, master_work_dir,
+                           credfile)
     agent = create_agent(prev_version, args.prev, agent_work_dir, credfile)
     test_framework(prev_version, args.prev)
 
     # Test case 3.
     agent.process.kill()
-    agent = create_agent(next_version, args.next, agent_work_dir, credfile)
+    agent = create_agent(next_version, args.__next__, agent_work_dir, credfile)
     test_framework(prev_version, args.prev)
 
     # Test case 4.
-    test_framework(next_version, args.next)
+    test_framework(next_version, args.__next__)
 
     # Tests passed.
     sys.exit(0)
diff --git a/support/verify-reviews.py b/support/verify-reviews.py
index 3d8c67e..eb91eb9 100755
--- a/support/verify-reviews.py
+++ b/support/verify-reviews.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 #
 # Licensed to the Apache Software Foundation (ASF) under one
 # or more contributor license agreements.  See the NOTICE file
@@ -33,90 +33,74 @@ The script performs the following sequence:
   * The result is posted to ReviewBoard.
 """
 
-import atexit
+import argparse
+import time
+import datetime
 import json
 import os
 import platform
 import subprocess
 import sys
-import urllib
-import urllib2
-import urlparse
+import urllib.error
+import urllib.parse
+import urllib.request
 
-from datetime import datetime
+from common import ReviewBoardHandler, ReviewError, REVIEWBOARD_URL
 
-REVIEWBOARD_URL = "https://reviews.apache.org"
 REVIEW_SIZE = 1000000  # 1 MB in bytes.
-
-# TODO(vinod): Use 'argparse' module.
-# Get the user and password from command line.
-if len(sys.argv) < 3:
-    print("Usage: ./verify-reviews.py <user>"
-          "<password> [num-reviews] [query-params]")
-    sys.exit(1)
-
-USER = sys.argv[1]
-PASSWORD = sys.argv[2]
-
-# Number of reviews to verify.
-NUM_REVIEWS = -1  # All possible reviews.
-if len(sys.argv) >= 4:
-    NUM_REVIEWS = int(sys.argv[3])
-
-# Unless otherwise specified consider pending review requests to Mesos updated
-# since 03/01/2014.
-GROUP = "mesos"
-LAST_UPDATED = "2014-03-01T00:00:00"
-QUERY_PARAMS = "?to-groups=%s&status=pending&last-updated-from=%s" \
-    % (GROUP, LAST_UPDATED)
-if len(sys.argv) >= 5:
-    QUERY_PARAMS = sys.argv[4]
-
-
-class ReviewError(Exception):
-    """Exception returned by post_review()."""
-    pass
+# This is the mesos repo ID obtained from querying the reviews.apache.org API
+MESOS_REPOSITORY_ID = 122
+
+
+def parse_parameters():
+    """Method to parse the arguments for argparse."""
+    parser = argparse.ArgumentParser(
+        description="Reviews that need verification from the Review Board")
+    parser.add_argument("-u", "--user", type=str, required=True,
+                        help="Review Board user name")
+    parser.add_argument("-p", "--password", type=str, required=True,
+                        help="Review Board user password")
+    parser.add_argument("-r", "--reviews", type=int, required=False,
+                        default=-1, help="The number of reviews to fetch,"
+                                         " that will need verification")
+    default_hours_behind = 8
+    datetime_before = (datetime.datetime.now() -
+                       datetime.timedelta(hours=default_hours_behind))
+    datetime_before_string = datetime_before.isoformat()
+    default_query = {"status": "pending", "repository": MESOS_REPOSITORY_ID,
+                     "last-updated-from": datetime_before_string.split(".")[0]}
+    parser.add_argument("-q", "--query", type=str, required=False,
+                        help="Query parameters, passed as string in JSON"
+                             " format. Example: '%s'" % json.dumps(
+                                 default_query),
+                        default=json.dumps(default_query))
+
+    subparsers = parser.add_subparsers(title="The script plug-in type")
+
+    file_parser = subparsers.add_parser(
+        "file", description="File plug-in just writes to a file all"
+                            " the review ids that need verification")
+    file_parser.add_argument("-o", "--out-file", type=str, required=True,
+                             help="The out file with the reviews IDs that"
+                                  " need verification")
+
+    return parser.parse_args()
 
 
 def shell(command):
     """Run a shell command."""
-    print command
+    print(command)
     return subprocess.check_output(
         command, stderr=subprocess.STDOUT, shell=True)
 
 
-HEAD = shell("git rev-parse HEAD")
-
-
-def api(url, data=None):
-    """Call the ReviewBoard API."""
-    try:
-        auth_handler = urllib2.HTTPBasicAuthHandler()
-        auth_handler.add_password(
-            realm="Web API",
-            uri="reviews.apache.org",
-            user=USER,
-            passwd=PASSWORD)
-
-        opener = urllib2.build_opener(auth_handler)
-        urllib2.install_opener(opener)
-
-        return json.loads(urllib2.urlopen(url, data=data).read())
-    except urllib2.HTTPError as err:
-        print "Error handling URL %s: %s (%s)" % (url, err.reason, err.read())
-        exit(1)
-    except urllib2.URLError as err:
-        print "Error handling URL %s: %s" % (url, err.reason)
-        exit(1)
-
-
 def apply_review(review_id):
     """Apply a review using the script apply-reviews.py."""
-    print "Applying review %s" % review_id
+    print("Applying review %s" % review_id)
     shell("python support/apply-reviews.py -n -r %s" % review_id)
 
 
-def apply_reviews(review_request, reviews):
+def apply_reviews(review_request, reviews, handler):
     """Apply multiple reviews at once."""
     # If there are no reviewers specified throw an error.
     if not review_request["target_people"]:
@@ -134,46 +118,48 @@ def apply_reviews(review_request, reviews):
     # First recursively apply the dependent reviews.
     for review in review_request["depends_on"]:
         review_url = review["href"]
-        print "Dependent review: %s " % review_url
-        apply_reviews(api(review_url)["review_request"], reviews)
+        print("Dependent review: %s" % review_url)
+        apply_reviews(handler.api(review_url)["review_request"],
+                      reviews, handler)
 
     # Now apply this review if not yet submitted.
     if review_request["status"] != "submitted":
         apply_review(review_request["id"])
 
 
-def post_review(review_request, message):
+def post_review(review_request, message, handler):
     """Post a review on the review board."""
-    print "Posting review: %s" % message
+    print("Posting review: %s" % message)
 
     review_url = review_request["links"]["reviews"]["href"]
-    data = urllib.urlencode({'body_top': message, 'public': 'true'})
-    api(review_url, data)
+    data = urllib.parse.urlencode({'body_top': message, 'public': 'true'})
+    handler.api(review_url, data)
 
 
-@atexit.register
+# @atexit.register
 def cleanup():
     """Clean the git repository."""
     try:
         shell("git clean -fd")
-        shell("git reset --hard %s" % HEAD)
+        HEAD = shell("git rev-parse HEAD")
+        print(HEAD)
+        shell("git checkout HEAD -- %s" % HEAD)
     except subprocess.CalledProcessError as err:
-        print "Failed command: %s\n\nError: %s" % (err.cmd, err.output)
+        print("Failed command: %s\n\nError: %s" % (err.cmd, err.output))
 
 
-def verify_review(review_request):
+def verify_review(review_request, handler):
     """Verify a review."""
-    print "Verifying review %s" % review_request["id"]
+    print("Verifying review %s" % review_request["id"])
     build_output = "build_" + str(review_request["id"])
 
     try:
         # Recursively apply the review and its dependents.
         reviews = []
-        apply_reviews(review_request, reviews)
+        apply_reviews(review_request, reviews, handler)
 
         reviews.reverse()  # Reviews are applied in the reverse order.
 
-        command = ""
         if platform.system() == 'Windows':
             command = "support\\windows-build.bat"
 
@@ -199,15 +185,15 @@ def verify_review(review_request):
             # output. `pipefail` ensures that the exit status of the build
             # command ispreserved even after tee'ing.
             subprocess.check_call(['bash', '-c',
-                                   ('set -o pipefail; %s 2>&1 | tee %s')
+                                   'set -o pipefail; %s 2>&1 | tee %s'
                                    % (command, build_output)])
 
         # Success!
         post_review(
             review_request,
-            "Patch looks great!\n\n" \
-            "Reviews applied: %s\n\n" \
-            "Passed command: %s" % (reviews, command))
+            "Patch looks great!\n\n"
+            "Reviews applied: %s\n\n"
+            "Passed command: %s" % (reviews, command), handler)
     except subprocess.CalledProcessError as err:
         # If we are here because the docker build command failed, read the
         # output from `build_output` file. For all other command failures read
@@ -220,99 +206,92 @@ def verify_review(review_request):
         if platform.system() == 'Windows':
             # We didn't output anything during the build (because `tee`
             # doesn't exist), so we print the output to stdout upon error.
-            print output
+
+            # Pylint raises a no-member error on that line due to a bug
+            # fixed in pylint 1.7.
+            # TODO(ArmandGrillet): Remove this once pylint updated to >= 1.7.
+            # pylint: disable=no-member
+            sys.stdout.buffer.write(output)
 
         # Truncate the output when posting the review as it can be very large.
         if len(output) > REVIEW_SIZE:
             output = "...<truncated>...\n" + output[-REVIEW_SIZE:]
 
         output += "\nFull log: "
-        output += urlparse.urljoin(os.environ['BUILD_URL'], 'console')
+        output += urllib.parse.urljoin(os.environ['BUILD_URL'], 'console')
 
         post_review(
             review_request,
             "Bad patch!\n\n" \
             "Reviews applied: %s\n\n" \
             "Failed command: %s\n\n" \
-            "Error:\n%s" % (reviews, err.cmd, output))
+            "Error:\n%s" % (reviews, err.cmd, output), handler)
     except ReviewError as err:
         post_review(
             review_request,
             "Bad review!\n\n" \
             "Reviews applied: %s\n\n" \
-            "Error:\n%s" % (reviews, err.args[0]))
+            "Error:\n%s" % (reviews, err.args[0]), handler)
 
     # Clean up.
-    cleanup()
-
-
-def needs_verification(review_request):
-    """Return True if this review request needs to be verified."""
-    print "Checking if review: %s needs verification" % review_request["id"]
-
-    # Skip if the review blocks another review.
-    if review_request["blocks"]:
-        print "Skipping blocking review %s" % review_request["id"]
-        return False
-
-    diffs_url = review_request["links"]["diffs"]["href"]
-    diffs = api(diffs_url)
-
-    if not diffs["diffs"]:  # No diffs attached!
-        print "Skipping review %s as it has no diffs" % review_request["id"]
-        return False
-
-    # Get the timestamp of the latest diff.
-    timestamp = diffs["diffs"][-1]["timestamp"]
-    rb_date_format = "%Y-%m-%dT%H:%M:%SZ"
-    diff_time = datetime.strptime(timestamp, rb_date_format)
-    print "Latest diff timestamp: %s" % diff_time
-
-    # Get the timestamp of the latest review from this script.
-    reviews_url = review_request["links"]["reviews"]["href"]
-    reviews = api(reviews_url + "?max-results=200")
-    review_time = None
-    for review in reversed(reviews["reviews"]):
-        if review["links"]["user"]["title"] == USER:
-            timestamp = review["timestamp"]
-            review_time = datetime.strptime(timestamp, rb_date_format)
-            print "Latest review timestamp: %s" % review_time
-            break
-
-    # TODO: Apply this check recursively up the dependency chain.
-    changes_url = review_request["links"]["changes"]["href"]
-    changes = api(changes_url)
-    dependency_time = None
-    for change in changes["changes"]:
-        if "depends_on" in change["fields_changed"]:
-            timestamp = change["timestamp"]
-            dependency_time = datetime.strptime(timestamp, rb_date_format)
-            print "Latest dependency change timestamp: %s" % dependency_time
-            break
-
-    # Needs verification if there is a new diff, or if the dependencies changed,
-    # after the last time it was verified.
-    return not review_time or review_time < diff_time or \
-        (dependency_time and review_time < dependency_time)
+    # cleanup()
 
 
-def main():
-    """Main function to verify the submitted reviews."""
-    # TODO(ArmandGrillet): Remove this when we'll have switched to Python 3.
-    dir_path = os.path.dirname(os.path.realpath(__file__))
-    script_path = os.path.join(dir_path, 'check-python3.py')
-    subprocess.call('python ' + script_path, shell=True, cwd=dir_path)
+def verification_needed_write(review_ids, parameters):
+    """Write the IDs of the review requests that need verification."""
+    num_reviews = len(review_ids)
+    print("%s review requests need verification" % num_reviews)
+    # out_file parameter is mandatory to be passed
+    try:
+        # Using file plug-in
+        with open(parameters.out_file, 'w') as f:
+            f.write('\n'.join(review_ids))
+    except Exception:
+        print("Failed opening file '%s' for writing" % parameters.out_file)
+        raise
 
-    review_requests_url = \
-        "%s/api/review-requests/%s" % (REVIEWBOARD_URL, QUERY_PARAMS)
 
-    review_requests = api(review_requests_url)
+def main():
+    """Main function to verify the submitted reviews."""
+    parameters = parse_parameters()
+    print("\n%s - Running %s" % (time.strftime('%m-%d-%y_%T'),
+                                 os.path.abspath(__file__)))
+    # The colon from timestamp gets encoded and we don't want it to be encoded.
+    # Replacing %3A with colon.
+    query_string = urllib.parse.urlencode(
+        json.loads(parameters.query)).replace("%3A", ":")
+    review_requests_url = "%s/api/review-requests/?%s" % (REVIEWBOARD_URL,
+                                                          query_string)
+    handler = ReviewBoardHandler(parameters.user, parameters.password)
     num_reviews = 0
+    review_ids = []
+    review_requests = handler.api(review_requests_url)
     for review_request in reversed(review_requests["review_requests"]):
-        if (NUM_REVIEWS == -1 or num_reviews < NUM_REVIEWS) and \
-           needs_verification(review_request):
-            verify_review(review_request)
+        if parameters.reviews == -1 or num_reviews < parameters.reviews:
+            try:
+                needs_verification = handler.needs_verification(review_request)
+                if not needs_verification:
+                    continue
+                # An exception is raised if cyclic dependencies are found
+                handler.get_dependent_review_ids(review_request)
+            except ReviewError as err:
+                message = ("Bad review!\n\n"
+                           "Error:\n%s" % (err.args[0]))
+                handler.post_review(review_request, message, handler)
+                continue
+            except Exception as err:
+                print("Error occured: %s" % err)
+                needs_verification = False
+                print("WARNING: Cannot find if review %s needs"
+                      " verification" % (review_request["id"]))
+            if not needs_verification:
+                continue
+            review_ids.append(str(review_request["id"]))
             num_reviews += 1
+            verify_review(review_request, handler)
+
+    verification_needed_write(review_ids, parameters)
+
 
 if __name__ == '__main__':
     main()


Mime
View raw message