mozreviewpulse: add support for analysis results with text only (Bug 1287537). r?glob draft
authorSteven MacLeod <smacleod@mozilla.com>
Sat, 29 Oct 2016 21:21:02 -0400
changeset 9817 9599f08b3ee8540b18225c859f813f5ed64a01b0
parent 9816 d94100184ed8161232fc5be4678af4e92ea7c8c5
push id1335
push usersmacleod@mozilla.com
push dateMon, 31 Oct 2016 16:12:21 +0000
reviewersglob
bugs1287537
mozreviewpulse: add support for analysis results with text only (Bug 1287537). r?glob The pulse consumer can now post reviews to Review Board using the provided 'header' text. Once the message format has been finalized support for diff comments will be added. MozReview-Commit-ID: 8Tgu1DCe9Gh
pylib/mozreviewpulse/mozreviewpulse/__main__.py
pylib/mozreviewpulse/mozreviewpulse/analysis.py
pylib/mozreviewpulse/mozreviewpulse/consumer.py
pylib/mozreviewpulse/mozreviewpulse/rb.py
pylib/mozreviewpulse/sample-config.ini
--- a/pylib/mozreviewpulse/mozreviewpulse/__main__.py
+++ b/pylib/mozreviewpulse/mozreviewpulse/__main__.py
@@ -36,15 +36,17 @@ def main():
         pulse_port=config.get('pulse', 'port'),
         pulse_userid=config.get('pulse', 'userid'),
         pulse_password=config.get('pulse', 'password'),
         pulse_ssl=config.get('pulse', 'ssl'),
         pulse_timeout=float(config.get('pulse', 'timeout')),
         analysis_exchange=config.get('analysis', 'exchange'),
         analysis_queue=config.get('analysis', 'queue'),
         analysis_routing_key=config.get('analysis', 'routing_key'),
+        rb_url=config.get('reviewboard', 'url'),
+        bugzilla_user=config.get('bugzilla', 'user'),
+        bugzilla_apikey=config.get('bugzill', 'apikey')
     )
     consumer.consume(n=(None if args.loop else 1))
 
 
-
 if __name__ == '__main__':
     main()
new file mode 100644
--- /dev/null
+++ b/pylib/mozreviewpulse/mozreviewpulse/analysis.py
@@ -0,0 +1,32 @@
+from mozreviewpulse import BatchReview
+
+
+def process_analysis_results(consumer, body, message):
+    """Callback for analysis result messages."""
+    try:
+        payload = body['payload']
+        rrid = payload['review_request_id']
+        diff_revision = payload['diffset_revision']
+
+        rbc = consumer.get_rb_client()
+        rb_root = rbc.get_root()
+        # TODO: Add support for not acking when RB can't be
+        # reached. If that's the case we will automatically
+        # retry the message when we pull from the queue again.
+        # We'll need to add some sort of delay though so we
+        # don't rapidly re-attempt.
+
+        review = BatchReview(rb_root, rrid, diff_revision)
+        # TODO: Add support for diff comments after the message
+        # format is finalized.
+
+        header = payload['header']
+        review.publish(body_top=header)
+    except:
+        # This prevents the queue from growing indefinitely but
+        # prevents us from fixing whatever caused the exception
+        # and restarting the bot to handle the message.
+        message.ack()
+        raise
+
+    message.ack()
--- a/pylib/mozreviewpulse/mozreviewpulse/consumer.py
+++ b/pylib/mozreviewpulse/mozreviewpulse/consumer.py
@@ -1,54 +1,66 @@
+from functools import partial
 import logging
 import sys
 
 from kombu import (
     Connection,
     Consumer,
     Exchange,
     Queue,
 )
 from kombu.common import eventloop
 from kombu.utils import nested
 
+from mozreviewpulse.analysis import process_analysis_results
+from mozreviewpulse.rb import get_rb_client
+
 
 class PulseConsumer(object):
     """Handle pulse consumption for MozReview."""
 
     def __init__(self, pulse_host, pulse_port, pulse_userid, pulse_password,
                  pulse_ssl=False, pulse_timeout=1.0, logger=None,
                  analysis_exchange=None, analysis_queue=None,
-                 analysis_routing_key=None):
+                 analysis_routing_key=None, rb_url=None, bugzilla_user=None,
+                 bugzilla_apikey=None):
         self.logger = logger or logging.getLogger('mozreviewpulse')
         self.pulse_timeout = pulse_timeout
         self.conn = Connection(hostname=pulse_host, port=pulse_port,
                                userid=pulse_userid, password=pulse_password,
                                ssl=pulse_ssl, auto_declare=False)
+
+        if not (rb_url and bugzilla_user and bugzilla_apikey):
+            self.get_rb_client = None
+            self.logger.warning('Insufficient configuration provided to '
+                                'communicate with Review Board')
+        else:
+            # Curry a method to make fetching an RBClient simple.
+            self.get_rb_client = partial(
+                get_rb_client, rb_url, user=bugzilla_user,
+                apikey=bugzilla_apikey)
+
         self.queues = []
 
         # Exchange and queue setup for analysis results.
-        if not (analysis_exchange and analysis_queue and analysis_routing_key):
+        if not (analysis_exchange and analysis_queue and
+                analysis_routing_key and self.get_rb_client):
             self.logger.warning('Insufficient configuration provided to '
                                 'consume analysis messages')
         else:
             self.analysis_exchange = Exchange(
                 analysis_exchange, type='topic', durable=True, passive=True)
             # self.analysis_exchange.passive = True
             self.analysis_queue = Queue(
                 name=analysis_queue, exchange=self.analysis_exchange,
                 durable=True, routing_key=analysis_routing_key,
                 exclusive=False, auto_delete=False)
-            self.queues.append((self.analysis_queue, self.on_analysis))
-
-    def on_analysis(self, body, message):
-        """Callback for analysis result messages."""
-        # TODO: Post results of analysis as a review.
-        print "Consuming Analysis"
-        print body
+            self.queues.append((self.analysis_queue,
+                                partial(process_analysis_results, self)))
 
     def consume(self, n=None):
         """Consume pulse messages"""
         if not self.queues:
             self.logger.error('No queues configured for consumption.')
             sys.exit(1)
 
         with nested(*[Consumer(self.conn, queues=[q], callbacks=[callback])
new file mode 100644
--- /dev/null
+++ b/pylib/mozreviewpulse/mozreviewpulse/rb.py
@@ -0,0 +1,12 @@
+from rbtools.api.client import RBClient
+
+
+def get_rb_client(url, bugzilla_user=None, bugzilla_apikey=None):
+    """Return an authenticated RBClient instance."""
+    rbc = RBClient(url, save_cookies=False, allow_caching=False)
+    login_resource = rbc.get_path(
+        'extensions/mozreview.extension.MozReviewExtension/'
+        'bugzilla-api-key-logins/')
+    login_resource.create(username=bugzilla_user, api_key=bugzilla_apikey)
+
+    return rbc
--- a/pylib/mozreviewpulse/sample-config.ini
+++ b/pylib/mozreviewpulse/sample-config.ini
@@ -5,8 +5,15 @@ userid = public
 password = public
 ssl = True
 timeout = 1.0
 
 [analysis]
 exchange = exchange/mozreview/
 queue = queue/public/
 routing_key = #
+
+[reviewboard]
+url = https://reviewboard.mozilla.org
+
+[bugzilla]
+username=mrpulse@example.com
+apikey=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX