You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mesos.apache.org by an...@apache.org on 2018/08/21 17:48:27 UTC

[mesos] branch master updated (6cac5ad -> 590a75d)

This is an automated email from the ASF dual-hosted git repository.

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


    from 6cac5ad  Windows: Made `libwinio` the default option for Mesos.
     new 21bddc3  Refactored ReviewBoard API functionality into separate module.
     new 062b34e  Added support helper for fetching review ids.
     new e80fa6f  Added support script to post build results.
     new 590a75d  Refactored verify-reviews.py to use commons.py and argparse.

The 4 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 support/python3/common.py            | 176 +++++++++++++++++++++++++
 support/python3/get-review-ids.py    |  62 +++++++++
 support/python3/post-build-result.py | 113 ++++++++++++++++
 support/python3/verify-reviews.py    | 241 ++++++++++++++++-------------------
 4 files changed, 461 insertions(+), 131 deletions(-)
 create mode 100644 support/python3/common.py
 create mode 100755 support/python3/get-review-ids.py
 create mode 100755 support/python3/post-build-result.py


[mesos] 01/04: Refactored ReviewBoard API functionality into separate module.

Posted by an...@apache.org.
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 21bddc30d756ae673a062c7cf23c0f064bafb275
Author: Dragos Schebesch <v-...@microsoft.com>
AuthorDate: Tue Aug 21 10:42:46 2018 -0700

    Refactored ReviewBoard API functionality into separate module.
    
    Review: https://reviews.apache.org/r/67502/
---
 support/python3/common.py | 176 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 176 insertions(+)

diff --git a/support/python3/common.py b/support/python3/common.py
new file mode 100644
index 0000000..ed62c67
--- /dev/null
+++ b/support/python3/common.py
@@ -0,0 +1,176 @@
+#!/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.
+
+"""
+These common helper classes help to manage the connection between the
+machine and ReviewBoard.
+"""
+
+from datetime import datetime
+import json
+import sys
+import urllib.request as urllib2
+from urllib.parse import urlencode
+
+REVIEWBOARD_URL = "https://reviews.apache.org"
+
+
+class ReviewError(Exception):
+    """Custom exception raised when a review is bad"""
+    pass
+
+
+class ReviewBoardHandler(object):
+    """Handler class for ReviewBoard API operations."""
+
+    def __init__(self, user=None, password=None):
+        self.user = user
+        self.password = password
+        self._opener_installed = False
+
+    def _review_ids(self, review_request, review_ids=None):
+        """Helper function for the 'get_review_ids' method."""
+        if review_ids is None:
+            review_ids = []
+        if review_request["status"] != "submitted":
+            review_ids.append(review_request["id"])
+        else:
+            print("The review request %s is already "
+                  "submitted" % (review_request["id"]))
+        for review in review_request["depends_on"]:
+            review_url = review["href"]
+            print("Dependent review: %s" % review_url)
+            dependent_review = self.api(review_url)["review_request"]
+            if dependent_review["id"] in review_ids:
+                raise ReviewError("Circular dependency detected for "
+                                  "review %s. Please fix the 'depends_on' "
+                                  "field." % review_request["id"])
+            self._review_ids(dependent_review, review_ids)
+
+    def api(self, url, data=None):
+        """Calls the ReviewBoard API."""
+        if self._opener_installed is False:
+            auth_handler = urllib2.HTTPBasicAuthHandler()
+            auth_handler.add_password(
+                realm="Web API",
+                uri="reviews.apache.org",
+                user=self.user,
+                passwd=self.password)
+            opener = urllib2.build_opener(auth_handler)
+            urllib2.install_opener(opener)
+            self._opener_installed = True
+        if data is not None:
+            data = data.encode(sys.getdefaultencoding())
+        try:
+            return json.loads(urllib2.urlopen(url, data=data).read().decode(
+                sys.getdefaultencoding()))
+        except Exception as err:
+            print("Error handling URL %s: %s" % (url, err))
+            # raise the error after printing the message
+            raise
+
+    def get_dependent_review_ids(self, review_request):
+        """Returns the review requests' ids (together with any potential
+           dependent review requests' ids) that need to be applied for the
+           current review request. Their order is ascending with respect to
+           how they should be applied. This function raises a ReviewError
+           exception if a cyclic dependency is found."""
+        review_ids = []
+        self._review_ids(review_request, review_ids)
+        return list(reversed(review_ids))
+
+    def post_review(self, review_request, message, text_type='markdown'):
+        """Posts a review on the review board."""
+        valid_text_types = ['markdown', 'plain']
+        if text_type not in valid_text_types:
+            raise Exception("Invalid %s text type when trying"
+                            " to post review. Valid text"
+                            " types are: %s" % (text_type, valid_text_types))
+        review_request_url = "%s/r/%s" % (REVIEWBOARD_URL,
+                                          review_request['id'])
+        print("Posting to review request: %s\n%s" % (review_request_url,
+                                                     message))
+        review_url = review_request["links"]["reviews"]["href"]
+        data = urlencode({'body_top': message,
+                          'body_top_text_type': text_type,
+                          'public': 'true'})
+        self.api(review_url, data)
+
+    def needs_verification(self, review_request):
+        """Returns True if this review request needs to be verified."""
+        print("Checking if review %s needs verification" % (
+            review_request["id"]))
+        rb_date_format = "%Y-%m-%dT%H:%M:%SZ"
+
+        # Now apply this review if not yet submitted.
+        if review_request["status"] == "submitted":
+            print("The review is already submitted")
+            return False
+
+        # Skip if the review blocks another review.
+        if review_request["blocks"]:
+            print("Skipping blocking review %s" % review_request["id"])
+            return False
+
+        # Get the timestamp of the latest review from this script.
+        reviews_url = review_request["links"]["reviews"]["href"]
+        reviews = self.api(reviews_url + "?max-results=200")
+        review_time = None
+        for review in reversed(reviews["reviews"]):
+            if review["links"]["user"]["title"] == self.user:
+                timestamp = review["timestamp"]
+                review_time = datetime.strptime(timestamp, rb_date_format)
+                print("Latest review timestamp: %s" % review_time)
+                break
+        if not review_time:
+            # Never reviewed, the review request needs to be verified.
+            print("Patch never verified, needs verification")
+            return True
+
+        # Every patch must have a diff.
+        latest_diff = self.api(review_request["links"]["diffs"]["href"])
+
+        # Get the timestamp of the latest diff.
+        timestamp = latest_diff["diffs"][-1]["timestamp"]
+        diff_time = datetime.strptime(timestamp, rb_date_format)
+        print("Latest diff timestamp: %s" % diff_time)
+
+        # NOTE: We purposefully allow the bot to run again on empty reviews
+        # so that users can re-trigger the build.
+        if review_time < diff_time:
+            # There is a new diff, needs verification.
+            print("This patch has been updated since its last review, needs"
+                  " verification.")
+            return True
+
+        # TODO(dragoshsch): Apply this check recursively up the dependency
+        # chain.
+        changes_url = review_request["links"]["changes"]["href"]
+        changes = self.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 dependency_time and review_time < dependency_time


[mesos] 03/04: Added support script to post build results.

Posted by an...@apache.org.
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 e80fa6f4d5d1c2feb62e6a83ee1d09befc2e251a
Author: Dragos Schebesch <v-...@microsoft.com>
AuthorDate: Tue Aug 21 10:42:50 2018 -0700

    Added support script to post build results.
    
    Review: https://reviews.apache.org/r/67504/
---
 support/python3/post-build-result.py | 113 +++++++++++++++++++++++++++++++++++
 1 file changed, 113 insertions(+)

diff --git a/support/python3/post-build-result.py b/support/python3/post-build-result.py
new file mode 100755
index 0000000..ebf2e0f
--- /dev/null
+++ b/support/python3/post-build-result.py
@@ -0,0 +1,113 @@
+#!/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 file is used to post review results to the ReviewBoard.
+"""
+
+import argparse
+import sys
+import urllib.request as urllib2
+
+from common import ReviewBoardHandler, REVIEWBOARD_URL  # noqa
+
+LOG_TAIL_LIMIT = 30
+
+
+def parse_parameters():
+    """Method for parsing arguments for argparse."""
+    parser = argparse.ArgumentParser(
+        description="Post review results to 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", "--review-id", type=str, required=True,
+                        help="Review ID")
+    parser.add_argument("-m", "--message", type=str, required=True,
+                        help="The post message")
+    parser.add_argument("-o", "--outputs-url", type=str, required=True,
+                        help="The output build artifacts URL")
+    parser.add_argument("-l", "--logs-urls", type=str, required=False,
+                        help="The URLs for the logs to be included in the"
+                             " posted build message")
+    parser.add_argument("--applied-reviews", type=str, required=False,
+                        help="The Review IDs that have been applied for the"
+                             " current patch")
+    parser.add_argument("--failed-command", type=str, required=False,
+                        help="The command that failed during the build"
+                             " process")
+    return parser.parse_args()
+
+
+def get_build_message(message, outputs_url, logs_urls=None,
+                      applied_reviews=None,
+                      failed_command=None):
+    """Retrieves build message."""
+    if logs_urls is None:
+        logs_urls = []
+    if applied_reviews is None:
+        applied_reviews = []
+    build_msg = "%s\n\n" % message
+    if applied_reviews:
+        build_msg += "Reviews applied: `%s`\n\n" % applied_reviews
+    if failed_command:
+        build_msg += "Failed command: `%s`\n\n" % failed_command
+    build_msg += ("All the build artifacts available"
+                  " at: %s\n\n" % (outputs_url))
+    logs_msg = ''
+    for url in logs_urls:
+        response = urllib2.urlopen(url)
+        log_content = response.read().decode(sys.getdefaultencoding())
+        if log_content == '':
+            continue
+        file_name = url.split('/')[-1]
+        logs_msg += "- [%s](%s):\n\n" % (file_name, url)
+        logs_msg += "```\n"
+        log_tail = log_content.split("\n")[-LOG_TAIL_LIMIT:]
+        logs_msg += "\n".join(log_tail)
+        logs_msg += "```\n\n"
+    if logs_msg == '':
+        return build_msg
+    build_msg += "Relevant logs:\n\n%s" % (logs_msg)
+    return build_msg.encode(sys.getdefaultencoding())
+
+
+def main():
+    """Posts build result to the ReviewBoard"""
+    parameters = parse_parameters()
+    review_request_url = "%s/api/review-requests/%s/" % (REVIEWBOARD_URL,
+                                                         parameters.review_id)
+    handler = ReviewBoardHandler(parameters.user, parameters.password)
+    review_request = handler.api(review_request_url)["review_request"]
+    logs_urls = []
+    if parameters.logs_urls:
+        logs_urls = parameters.logs_urls.split('|')
+    applied_reviews = []
+    if parameters.applied_reviews:
+        applied_reviews = parameters.applied_reviews.split('|')
+    message = get_build_message(message=parameters.message,
+                                logs_urls=logs_urls,
+                                applied_reviews=applied_reviews,
+                                outputs_url=parameters.outputs_url,
+                                failed_command=parameters.failed_command)
+    handler.post_review(review_request, message)
+
+
+if __name__ == '__main__':
+    main()


[mesos] 02/04: Added support helper for fetching review ids.

Posted by an...@apache.org.
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 062b34eabb7f437b9d32b54544b51689a918c021
Author: Dragos Schebesch <v-...@microsoft.com>
AuthorDate: Tue Aug 21 10:42:48 2018 -0700

    Added support helper for fetching review ids.
    
    Review: https://reviews.apache.org/r/67503/
---
 support/python3/get-review-ids.py | 62 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 62 insertions(+)

diff --git a/support/python3/get-review-ids.py b/support/python3/get-review-ids.py
new file mode 100755
index 0000000..8cf15c3
--- /dev/null
+++ b/support/python3/get-review-ids.py
@@ -0,0 +1,62 @@
+#!/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 file is used to get the dependent review IDs of a specific review
+request on the ReviewBoard.
+"""
+
+import argparse
+
+from common import ReviewBoardHandler
+from common import REVIEWBOARD_URL
+
+
+def parse_parameters():
+    """Method for parsing CLI arguments using argparse."""
+    parser = argparse.ArgumentParser(
+        description="Get all dependent review IDs")
+    parser.add_argument("-r", "--review-id", type=str, required=True,
+                        help="Review ID")
+    parser.add_argument("-o", "--out-file", type=str, required=False,
+                        help="The out file with the reviews IDs")
+    return parser.parse_args()
+
+
+def main():
+    """
+    Main method to get dependent review IDs of a specific review request
+    on the ReviewBoard.
+    """
+    parameters = parse_parameters()
+    review_request_url = "%s/api/review-requests/%s/" % (REVIEWBOARD_URL,
+                                                         parameters.review_id)
+    handler = ReviewBoardHandler()
+    review_request = handler.api(review_request_url)["review_request"]
+    review_ids = handler.get_dependent_review_ids(review_request)
+    if parameters.out_file:
+        with open(parameters.out_file, 'w') as f:
+            for r_id in review_ids:
+                f.write("%s\n" % (str(r_id)))
+    else:
+        for r_id in review_ids:
+            print("%s\n" % (str(r_id)))
+
+
+if __name__ == '__main__':
+    main()


[mesos] 04/04: Refactored verify-reviews.py to use commons.py and argparse.

Posted by an...@apache.org.
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 <v-...@microsoft.com>
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)
 
 
-@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))
 
 
-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()