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

commit 590a75d0c9d61b0b07f8a3807225c40eb8189a0b
Author: Dragos Schebesch <[email protected]>
AuthorDate: Tue Aug 21 10:42:52 2018 -0700

    Refactored verify-reviews.py to use commons.py and argparse.
    
    Review: https://reviews.apache.org/r/67505/
---
 support/python3/verify-reviews.py | 241 +++++++++++++++++---------------------
 1 file changed, 110 insertions(+), 131 deletions(-)

diff --git a/support/python3/verify-reviews.py 
b/support/python3/verify-reviews.py
index 2e92590..14e5f9b 100755
--- a/support/python3/verify-reviews.py
+++ b/support/python3/verify-reviews.py
@@ -33,7 +33,9 @@ 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
@@ -43,39 +45,46 @@ 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):
@@ -85,38 +94,13 @@ def shell(command):
         command, stderr=subprocess.STDOUT, shell=True)
 
 
-HEAD = shell("git rev-parse HEAD")
-
-
-def api(url, data=None):
-    """Call the ReviewBoard API."""
-    try:
-        auth_handler = urllib.request.HTTPBasicAuthHandler()
-        auth_handler.add_password(
-            realm="Web API",
-            uri="reviews.apache.org",
-            user=USER,
-            passwd=PASSWORD)
-
-        opener = urllib.request.build_opener(auth_handler)
-        urllib.request.install_opener(opener)
-
-        return json.loads(urllib.request.urlopen(url, data=data).read())
-    except urllib.error.HTTPError as err:
-        print("Error handling URL %s: %s (%s)" % (url, err.reason, err.read()))
-        exit(1)
-    except urllib.error.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)
     shell("python support/python3/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,34 +118,37 @@ 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)
 
     review_url = review_request["links"]["reviews"]["href"]
     data = urllib.parse.urlencode({'body_top': message, 'public': 'true'})
-    api(review_url, data)
+    handler.api(review_url, data)
 
 
[email protected]
+# @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))
 
 
-def verify_review(review_request):
+def verify_review(review_request, handler):
     """Verify a review."""
     print("Verifying review %s" % review_request["id"])
     build_output = "build_" + str(review_request["id"])
@@ -169,11 +156,10 @@ def verify_review(review_request):
     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
@@ -239,80 +225,73 @@ def verify_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 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."""
-    review_requests_url = \
-        "%s/api/review-requests/%s" % (REVIEWBOARD_URL, QUERY_PARAMS)
-
-    review_requests = api(review_requests_url)
+    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()

Reply via email to