DevTools FE uploader: create checker instance to validate uploads

Runs on a separate GCE instance from the uploader and periodically checks
that earlier commits have been uploaded to the devtools frontend appspot instance.

This runs a simple HTTP server in a separate daemon thread so GCP/Stackdriver
can do a periodic uptime check. Ideally, because the HTTP server and checker thread
run in the same process, they should have similar uptime.

BUG=none
R=dgozman@chromium.org

Review URL: https://codereview.chromium.org/2358303002 .
diff --git a/gce/checker.py b/gce/checker.py
new file mode 100755
index 0000000..04d2317
--- /dev/null
+++ b/gce/checker.py
@@ -0,0 +1,114 @@
+#!/usr/bin/python
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
+import logging
+import logging.handlers
+from os import path
+import subprocess
+import sys
+import threading
+import time
+import urllib2
+
+import uploader_mail
+
+# How many seconds to wait after a cycle.
+CYCLE_DELAY = 1800
+
+# Verifies a commit this long ago has been uploaded to devtools-frontend appspot
+CHECKER_DELAY = "12 hours"
+
+CYCLES_PER_24_HOURS = (86400 / CYCLE_DELAY)
+
+CHECKER_PATH = path.join(path.dirname(path.abspath(__file__)), '..', '..')
+CHROMIUM_CHECKOUT_PATH = path.join(CHECKER_PATH, 'src')
+LOG_PATH = path.join(CHECKER_PATH, 'logs', 'checker.log')
+
+consecutive_success = 0
+
+def main():
+  logfile = init_logger()
+  start_uptime_check_server()
+  while True:
+    logfile.doRollover()
+    logging.info('Starting iteration cycle')
+    call(['git', 'pull', 'origin', 'master'])
+    yesterday_commit_hash = call(['git', 'rev-list', '-n1', '--before={}'.format(CHECKER_DELAY), '--first-parent', 'HEAD'])
+    check_commit(yesterday_commit_hash.strip())
+    logging.info('Finished iteration cycle')
+    time.sleep(CYCLE_DELAY)
+
+def init_logger():
+  logger = logging.getLogger()
+  logger.setLevel(logging.INFO)
+
+  console = logging.StreamHandler()
+  console.setLevel(logging.INFO)
+  logger.addHandler(console)
+
+  logfile = logging.handlers.RotatingFileHandler(LOG_PATH, backupCount=30)
+  logfile.setLevel(logging.INFO)
+  logger.addHandler(logfile)
+
+  formatter = logging.Formatter('%(asctime)s:%(message)s',
+                                datefmt='%Y-%m-%d %H:%M:%S')
+  console.setFormatter(formatter)
+  logfile.setFormatter(formatter)
+  return logfile
+
+def check_commit(commit_hash):
+  global consecutive_success
+  url = 'https://chrome-devtools-frontend.appspot.com/serve_file/@{}/inspector.html'.format(commit_hash)
+  request = urllib2.Request(url)
+  try:
+    response = urllib2.urlopen(request)
+    summary = format_summary('Success', commit_hash, response.getcode())
+    logging.info(summary)
+    # Send an email on first success after start-up or getting an error
+    if consecutive_success == 0:
+      send_email(subject=summary, body='Response body:\n' + response.read())
+    consecutive_success += 1
+    if consecutive_success >= CYCLES_PER_24_HOURS:
+      consecutive_success = 0
+  except urllib2.HTTPError as error:
+    summary = format_summary('Error', commit_hash, error.code)
+    logging.info(summary)
+    send_email(subject=summary, body='Error message:\n' + error.reason)
+    consecutive_success = 0
+
+def send_email(subject, body):
+  mail_config = uploader_mail.ParseMailConfig(call(
+      ['gsutil', 'cat', 'gs://chrome-devtools-frontend/mail_config'], log_output=False))
+  uploader_mail.SendMail(mail_config, subject, body)
+  logging.info('Sent email with subject: {} and body: {}'.format(subject, body))
+
+def call(args, log_output=True, cwd=CHROMIUM_CHECKOUT_PATH):
+  process = subprocess.Popen(args, stdout=subprocess.PIPE,
+                             stderr=subprocess.STDOUT, cwd=cwd)
+  out, err = process.communicate()
+  if process.returncode != 0:
+    logging.info('Error {} from {}'.format(process.returncode, args))
+  if log_output:
+    logging.info(out)
+  return out
+
+def format_summary(status, commit_hash, status_code):
+  return '[devtools-frontend-checker] {} for commit {} - HTTP status {}'.format(
+      status, commit_hash, status_code)
+
+def start_uptime_check_server():
+  class Handler(BaseHTTPRequestHandler):
+    def do_GET(self):
+        self.send_response(200)
+        self.send_header('Content-Type', 'text/html')
+        self.end_headers()
+  server = HTTPServer(('', 80), Handler)
+  thread = threading.Thread(target=server.serve_forever)
+  thread.daemon = True
+  thread.start()
+
+if __name__ == '__main__':
+  sys.exit(main())
diff --git a/gce/setup_checker.sh b/gce/setup_checker.sh
new file mode 100644
index 0000000..22c07d3
--- /dev/null
+++ b/gce/setup_checker.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+sudo apt-get install -y git
+cd ~/home
+sudo mkdir checker
+cd checker
+sudo git clone https://chromium.googlesource.com/chromium/tools/chrome-devtools-frontend
+sudo git clone https://chromium.googlesource.com/chromium/src --depth 500
+sudo mkdir logs
+cd chrome-devtools-frontend/gce && sudo ./start_checker.sh
diff --git a/gce/start_checker.sh b/gce/start_checker.sh
new file mode 100755
index 0000000..4643ed6
--- /dev/null
+++ b/gce/start_checker.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+echo "Killing previous instance of checker.py (if applicable)"
+pkill -f checker.py
+nohup ./checker.py >/dev/null 2>&1 &
+echo "Started checker process in background"