|  | #!/usr/bin/env python | 
|  |  | 
|  | # Copyright 2014 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. | 
|  |  | 
|  | import argparse | 
|  | import datetime | 
|  | import getpass | 
|  | import json | 
|  | import os | 
|  | import smtplib | 
|  | import sys | 
|  | import time | 
|  | import urllib | 
|  | import urllib2 | 
|  |  | 
|  | class Emailer: | 
|  | DEFAULT_EMAIL_PASSWORD_FILE = '.email_password' | 
|  | GMAIL_SMTP_SERVER = 'smtp.gmail.com:587' | 
|  | SUBJECT = 'Chrome GPU Bots Notification' | 
|  |  | 
|  | def __init__(self, email_from, email_to, email_password_file): | 
|  | self.email_from = email_from | 
|  | self.email_to = email_to | 
|  | self.email_password = Emailer._getEmailPassword(email_password_file) | 
|  |  | 
|  | @staticmethod | 
|  | def format_email_body(time_str, offline_str, failed_str, noteworthy_str): | 
|  | return '%s%s%s%s' % (time_str, offline_str, failed_str, noteworthy_str) | 
|  |  | 
|  | def send_email(self, body): | 
|  | message = 'From: %s\r\nTo: %s\r\nSubject: %s\r\n\r\n%s' % (self.email_from, | 
|  | ','.join(self.email_to), Emailer.SUBJECT, body) | 
|  |  | 
|  | try: | 
|  | server = smtplib.SMTP(Emailer.GMAIL_SMTP_SERVER) | 
|  | server.starttls() | 
|  | server.login(self.email_from, self.email_password) | 
|  | server.sendmail(self.email_from, self.email_to, message) | 
|  | server.quit() | 
|  | except Exception as e: | 
|  | print 'Error sending email: %s' % str(e) | 
|  |  | 
|  | def testEmailLogin(self): | 
|  | server = smtplib.SMTP(Emailer.GMAIL_SMTP_SERVER) | 
|  | server.starttls() | 
|  | server.login(self.email_from, self.email_password) | 
|  | server.quit() | 
|  |  | 
|  | @staticmethod | 
|  | def _getEmailPassword(email_password_file): | 
|  | password = '' | 
|  |  | 
|  | password_file = (email_password_file if email_password_file is not None | 
|  | else Emailer.DEFAULT_EMAIL_PASSWORD_FILE) | 
|  |  | 
|  | if os.path.isfile(password_file): | 
|  | with open(password_file, 'r') as f: | 
|  | password = f.read().strip() | 
|  | else: | 
|  | password = getpass.getpass( | 
|  | 'Please enter email password for source email account: ') | 
|  |  | 
|  | return password | 
|  |  | 
|  | class GpuBot: | 
|  | def __init__(self, waterfall_name, bot_name, bot_data): | 
|  | self.waterfall_name = waterfall_name | 
|  | self.bot_name = bot_name | 
|  | self.bot_data = bot_data | 
|  | self._end_time = None | 
|  | self._hours_since_last_run = None | 
|  | self.failure_string = None | 
|  | self.bot_url = None | 
|  | self.build_url = None | 
|  |  | 
|  | def getEndTime(self): | 
|  | return self._end_time | 
|  |  | 
|  | def setEndTime(self, end_time): | 
|  | self._end_time = end_time | 
|  | self._hours_since_last_run = \ | 
|  | roughTimeDiffInHours(end_time, time.localtime()) | 
|  |  | 
|  | def getHoursSinceLastRun(self): | 
|  | return self._hours_since_last_run | 
|  |  | 
|  | def toDict(self): | 
|  | dict = {'waterfall_name': self.waterfall_name, 'bot_name': self.bot_name} | 
|  |  | 
|  | if self._end_time is not None: | 
|  | dict['end_time'] = serialTime(self._end_time) | 
|  | dict['hours_since_last_run'] = self._hours_since_last_run | 
|  |  | 
|  | if self.failure_string is not None: | 
|  | dict['failure_string'] = self.failure_string | 
|  |  | 
|  | if self.bot_url is not None: | 
|  | dict['bot_url'] = self.bot_url | 
|  |  | 
|  | if self.build_url is not None: | 
|  | dict['build_url'] = self.build_url | 
|  |  | 
|  | return dict | 
|  |  | 
|  | @staticmethod | 
|  | def fromDict(dict): | 
|  | gpu_bot = GpuBot(dict['waterfall_name'], dict['bot_name'], None) | 
|  |  | 
|  | if 'end_time' in dict: | 
|  | gpu_bot._end_time = unserializeTime(dict['end_time']) | 
|  |  | 
|  | if 'hours_since_last_run' in dict: | 
|  | gpu_bot._hours_since_last_run = dict['hours_since_last_run'] | 
|  |  | 
|  | if 'failure_string' in dict: | 
|  | gpu_bot.failure_string = dict['failure_string'] | 
|  |  | 
|  | if 'bot_url' in dict: | 
|  | gpu_bot.bot_url = dict['bot_url'] | 
|  |  | 
|  | if 'build_url' in dict: | 
|  | gpu_bot.build_url = dict['build_url'] | 
|  |  | 
|  | return gpu_bot | 
|  |  | 
|  | def errorNoMostRecentBuild(waterfall_name, bot_name): | 
|  | print 'No most recent build available: %s::%s' % (waterfall_name, bot_name) | 
|  |  | 
|  | class Waterfall: | 
|  | BASE_URL = 'http://build.chromium.org/p/' | 
|  | BASE_BUILD_URL = BASE_URL + '%s/builders/%s' | 
|  | SPECIFIC_BUILD_URL = BASE_URL + '%s/builders/%s/builds/%s' | 
|  | BASE_JSON_BUILDERS_URL = BASE_URL + '%s/json/builders' | 
|  | BASE_JSON_BUILDS_URL = BASE_URL + '%s/json/builders/%s/builds' | 
|  | REGULAR_WATERFALLS = ['chromium.gpu', 'chromium.gpu.fyi'] | 
|  | WEBKIT_GPU_BOTS = ['GPU Win Builder', | 
|  | 'GPU Win Builder (dbg)', | 
|  | 'GPU Win7 (NVIDIA)', | 
|  | 'GPU Win7 (dbg) (NVIDIA)', | 
|  | 'GPU Mac Builder', | 
|  | 'GPU Mac Builder (dbg)', | 
|  | 'GPU Mac10.7', | 
|  | 'GPU Mac10.7 (dbg)', | 
|  | 'GPU Linux Builder', | 
|  | 'GPU Linux Builder (dbg)', | 
|  | 'GPU Linux (NVIDIA)', | 
|  | 'GPU Linux (dbg) (NVIDIA)'] | 
|  | FILTERED_WATERFALLS = [('chromium.webkit', WEBKIT_GPU_BOTS)] | 
|  |  | 
|  | @staticmethod | 
|  | def getJsonFromUrl(url): | 
|  | conn = urllib2.urlopen(url) | 
|  | result = conn.read() | 
|  | conn.close() | 
|  | return json.loads(result) | 
|  |  | 
|  | @staticmethod | 
|  | def getBuildersJsonForWaterfall(waterfall): | 
|  | querystring = '?filter' | 
|  | return (Waterfall.getJsonFromUrl((Waterfall.BASE_JSON_BUILDERS_URL + '%s') | 
|  | % (waterfall, querystring))) | 
|  |  | 
|  | @staticmethod | 
|  | def getLastNBuildsForBuilder(n, waterfall, builder): | 
|  | if n <= 0: | 
|  | return {} | 
|  |  | 
|  | querystring = '?' | 
|  |  | 
|  | for i in range(n): | 
|  | querystring += 'select=-%d&' % (i + 1) | 
|  |  | 
|  | querystring += 'filter' | 
|  |  | 
|  | return Waterfall.getJsonFromUrl((Waterfall.BASE_JSON_BUILDS_URL + '%s') % | 
|  | (waterfall, urllib.quote(builder), querystring)) | 
|  |  | 
|  | @staticmethod | 
|  | def getFilteredBuildersJsonForWaterfall(waterfall, filter): | 
|  | querystring = '?' | 
|  |  | 
|  | for bot_name in filter: | 
|  | querystring += 'select=%s&' % urllib.quote(bot_name) | 
|  |  | 
|  | querystring += 'filter' | 
|  |  | 
|  | return Waterfall.getJsonFromUrl((Waterfall.BASE_JSON_BUILDERS_URL + '%s') | 
|  | % (waterfall, querystring)) | 
|  |  | 
|  | @staticmethod | 
|  | def getAllGpuBots(): | 
|  | allbots = {k: Waterfall.getBuildersJsonForWaterfall(k) | 
|  | for k in Waterfall.REGULAR_WATERFALLS} | 
|  |  | 
|  | filteredbots = {k[0]: | 
|  | Waterfall.getFilteredBuildersJsonForWaterfall(k[0], k[1]) | 
|  | for k in Waterfall.FILTERED_WATERFALLS} | 
|  |  | 
|  | allbots.update(filteredbots) | 
|  |  | 
|  | return allbots | 
|  |  | 
|  | @staticmethod | 
|  | def getOfflineBots(bots): | 
|  | offline_bots = [] | 
|  |  | 
|  | for waterfall_name in bots: | 
|  | waterfall = bots[waterfall_name] | 
|  |  | 
|  | for bot_name in waterfall: | 
|  | bot = waterfall[bot_name] | 
|  |  | 
|  | if bot['state'] != 'offline': | 
|  | continue | 
|  |  | 
|  | gpu_bot = GpuBot(waterfall_name, bot_name, bot) | 
|  | gpu_bot.bot_url = Waterfall.BASE_BUILD_URL % (waterfall_name, | 
|  | urllib.quote(bot_name)) | 
|  |  | 
|  | most_recent_build = Waterfall.getMostRecentlyCompletedBuildForBot( | 
|  | gpu_bot) | 
|  |  | 
|  | if (most_recent_build and 'times' in most_recent_build and | 
|  | most_recent_build['times']): | 
|  | gpu_bot.setEndTime(time.localtime(most_recent_build['times'][1])) | 
|  | else: | 
|  | errorNoMostRecentBuild(waterfall_name, bot_name) | 
|  |  | 
|  | offline_bots.append(gpu_bot) | 
|  |  | 
|  | return offline_bots | 
|  |  | 
|  | @staticmethod | 
|  | def getMostRecentlyCompletedBuildForBot(bot): | 
|  | if bot.bot_data is not None and 'most_recent_build' in bot.bot_data: | 
|  | return bot.bot_data['most_recent_build'] | 
|  |  | 
|  | # Unfortunately, the JSON API doesn't provide a "most recent completed | 
|  | # build" call. We just have to get some number of the most recent (including | 
|  | # current, in-progress builds) and give up if that's not enough. | 
|  | NUM_BUILDS = 10 | 
|  | builds = Waterfall.getLastNBuildsForBuilder(NUM_BUILDS, bot.waterfall_name, | 
|  | bot.bot_name) | 
|  |  | 
|  | for i in range(NUM_BUILDS): | 
|  | current_build_name = '-%d' % (i + 1) | 
|  | current_build = builds[current_build_name] | 
|  |  | 
|  | if 'results' in current_build and current_build['results'] is not None: | 
|  | if bot.bot_data is not None: | 
|  | bot.bot_data['most_recent_build'] = current_build | 
|  |  | 
|  | return current_build | 
|  |  | 
|  | return None | 
|  |  | 
|  | @staticmethod | 
|  | def getFailedBots(bots): | 
|  | failed_bots = [] | 
|  |  | 
|  | for waterfall_name in bots: | 
|  | waterfall = bots[waterfall_name] | 
|  |  | 
|  | for bot_name in waterfall: | 
|  | bot = waterfall[bot_name] | 
|  | gpu_bot = GpuBot(waterfall_name, bot_name, bot) | 
|  | gpu_bot.bot_url = Waterfall.BASE_BUILD_URL % (waterfall_name, | 
|  | urllib.quote(bot_name)) | 
|  |  | 
|  | most_recent_build = Waterfall.getMostRecentlyCompletedBuildForBot( | 
|  | gpu_bot) | 
|  |  | 
|  | if (most_recent_build and 'text' in most_recent_build and | 
|  | 'failed' in most_recent_build['text']): | 
|  | gpu_bot.failure_string = ' '.join(most_recent_build['text']) | 
|  | gpu_bot.build_url = Waterfall.SPECIFIC_BUILD_URL % (waterfall_name, | 
|  | urllib.quote(bot_name), most_recent_build['number']) | 
|  | failed_bots.append(gpu_bot) | 
|  | elif not most_recent_build: | 
|  | errorNoMostRecentBuild(waterfall_name, bot_name) | 
|  |  | 
|  | return failed_bots | 
|  |  | 
|  | def formatTime(t): | 
|  | return time.strftime("%a, %d %b %Y %H:%M:%S", t) | 
|  |  | 
|  | def roughTimeDiffInHours(t1, t2): | 
|  | datetimes = [] | 
|  |  | 
|  | for t in [t1, t2]: | 
|  | datetimes.append(datetime.datetime(t.tm_year, t.tm_mon, t.tm_mday, | 
|  | t.tm_hour, t.tm_min, t.tm_sec)) | 
|  |  | 
|  | datetime_diff = datetimes[0] - datetimes[1] | 
|  |  | 
|  | hours = float(datetime_diff.total_seconds()) / 3600.0 | 
|  |  | 
|  | return abs(hours) | 
|  |  | 
|  | def getBotStr(bot): | 
|  | s = '  %s::%s\n' % (bot.waterfall_name, bot.bot_name) | 
|  |  | 
|  | if bot.failure_string is not None: | 
|  | s += '  failure: %s\n' % bot.failure_string | 
|  |  | 
|  | if bot.getEndTime() is not None: | 
|  | s += ('  last build end time: %s (roughly %f hours ago)\n' % | 
|  | (formatTime(bot.getEndTime()), bot.getHoursSinceLastRun())) | 
|  |  | 
|  | if bot.bot_url is not None: | 
|  | s += '  bot url: %s\n' % bot.bot_url | 
|  |  | 
|  | if bot.build_url is not None: | 
|  | s += '  build url: %s\n' % bot.build_url | 
|  |  | 
|  | s += '\n' | 
|  | return s | 
|  |  | 
|  | def getBotsStr(bots): | 
|  | s = '' | 
|  |  | 
|  | for bot in bots: | 
|  | s += getBotStr(bot) | 
|  |  | 
|  | s += '\n' | 
|  | return s | 
|  |  | 
|  | def getOfflineBotsStr(offline_bots): | 
|  | return 'Offline bots:\n%s' % getBotsStr(offline_bots) | 
|  |  | 
|  | def getFailedBotsStr(failed_bots): | 
|  | return 'Failed bots:\n%s' % getBotsStr(failed_bots) | 
|  |  | 
|  | def getBotDicts(bots): | 
|  | dicts = [] | 
|  |  | 
|  | for bot in bots: | 
|  | dicts.append(bot.toDict()) | 
|  |  | 
|  | return dicts | 
|  |  | 
|  | def unserializeTime(t): | 
|  | return time.struct_time((t['year'], t['mon'], t['day'], t['hour'], t['min'], | 
|  | t['sec'], 0, 0, 0)) | 
|  |  | 
|  | def serialTime(t): | 
|  | return {'year': t.tm_year, 'mon': t.tm_mon, 'day': t.tm_mday, | 
|  | 'hour': t.tm_hour, 'min': t.tm_min, 'sec': t.tm_sec} | 
|  |  | 
|  | def getSummary(offline_bots, failed_bots): | 
|  | offline_bot_dict = getBotDicts(offline_bots) | 
|  | failed_bot_dict = getBotDicts(failed_bots) | 
|  | return {'offline': offline_bot_dict, 'failed': failed_bot_dict} | 
|  |  | 
|  | def findBot(name, lst): | 
|  | for bot in lst: | 
|  | if bot.bot_name == name: | 
|  | return bot | 
|  |  | 
|  | return None | 
|  |  | 
|  | def getNoteworthyEvents(offline_bots, failed_bots, previous_results): | 
|  | CRITICAL_NUM_HOURS = 1.0 | 
|  |  | 
|  | previous_offline = (previous_results['offline'] if 'offline' | 
|  | in previous_results else []) | 
|  |  | 
|  | previous_failures = (previous_results['failed'] if 'failed' | 
|  | in previous_results else []) | 
|  |  | 
|  | noteworthy_offline = [] | 
|  | for bot in offline_bots: | 
|  | if bot.getHoursSinceLastRun() >= CRITICAL_NUM_HOURS: | 
|  | previous_bot = findBot(bot.bot_name, previous_offline) | 
|  |  | 
|  | if (previous_bot is None or | 
|  | previous_bot.getHoursSinceLastRun() < CRITICAL_NUM_HOURS): | 
|  | noteworthy_offline.append(bot) | 
|  |  | 
|  | noteworthy_new_failures = [] | 
|  | for bot in failed_bots: | 
|  | previous_bot = findBot(bot.bot_name, previous_failures) | 
|  |  | 
|  | if previous_bot is None: | 
|  | noteworthy_new_failures.append(bot) | 
|  |  | 
|  | noteworthy_new_offline_recoveries = [] | 
|  | for bot in previous_offline: | 
|  | if bot.getHoursSinceLastRun() < CRITICAL_NUM_HOURS: | 
|  | continue | 
|  |  | 
|  | current_bot = findBot(bot.bot_name, offline_bots) | 
|  | if current_bot is None: | 
|  | noteworthy_new_offline_recoveries.append(bot) | 
|  |  | 
|  | noteworthy_new_failure_recoveries = [] | 
|  | for bot in previous_failures: | 
|  | current_bot = findBot(bot.bot_name, failed_bots) | 
|  |  | 
|  | if current_bot is None: | 
|  | noteworthy_new_failure_recoveries.append(bot) | 
|  |  | 
|  | return {'offline': noteworthy_offline, 'failed': noteworthy_new_failures, | 
|  | 'recovered_failures': noteworthy_new_failure_recoveries, | 
|  | 'recovered_offline': noteworthy_new_offline_recoveries} | 
|  |  | 
|  | def getNoteworthyStr(noteworthy_events): | 
|  | s = '' | 
|  |  | 
|  | if noteworthy_events['offline']: | 
|  | s += 'IMPORTANT bots newly offline for over an hour:\n' | 
|  |  | 
|  | for bot in noteworthy_events['offline']: | 
|  | s += getBotStr(bot) | 
|  |  | 
|  | s += '\n' | 
|  |  | 
|  | if noteworthy_events['failed']: | 
|  | s += 'IMPORTANT new failing bots:\n' | 
|  |  | 
|  | for bot in noteworthy_events['failed']: | 
|  | s += getBotStr(bot) | 
|  |  | 
|  | s += '\n' | 
|  |  | 
|  | if noteworthy_events['recovered_offline']: | 
|  | s += 'IMPORTANT newly recovered previously offline bots:\n' | 
|  |  | 
|  | for bot in noteworthy_events['recovered_offline']: | 
|  | s += getBotStr(bot) | 
|  |  | 
|  | s += '\n' | 
|  |  | 
|  | if noteworthy_events['recovered_failures']: | 
|  | s += 'IMPORTANT newly recovered failing bots:\n' | 
|  |  | 
|  | for bot in noteworthy_events['recovered_failures']: | 
|  | s += getBotStr(bot) | 
|  |  | 
|  | s += '\n' | 
|  |  | 
|  | return s | 
|  |  | 
|  | def dictsToBots(bots): | 
|  | offline_bots = [] | 
|  | for bot in bots['offline']: | 
|  | offline_bots.append(GpuBot.fromDict(bot)) | 
|  |  | 
|  | failed_bots = [] | 
|  | for bot in bots['failed']: | 
|  | failed_bots.append(GpuBot.fromDict(bot)) | 
|  |  | 
|  | return {'offline': offline_bots, 'failed': failed_bots} | 
|  |  | 
|  | class GpuBotPoller: | 
|  | DEFAULT_PREVIOUS_RESULTS_FILE = '.check_gpu_bots_previous_results' | 
|  |  | 
|  | def __init__(self, emailer, send_email_for_recovered_offline_bots, | 
|  | send_email_for_recovered_failing_bots, send_email_on_error, | 
|  | previous_results_file): | 
|  | self.emailer = emailer | 
|  |  | 
|  | self.send_email_for_recovered_offline_bots = \ | 
|  | send_email_for_recovered_offline_bots | 
|  |  | 
|  | self.send_email_for_recovered_failing_bots = \ | 
|  | send_email_for_recovered_failing_bots | 
|  |  | 
|  | self.send_email_on_error = send_email_on_error | 
|  | self.previous_results_file = previous_results_file | 
|  |  | 
|  | def shouldEmail(self, noteworthy_events): | 
|  | if noteworthy_events['offline'] or noteworthy_events['failed']: | 
|  | return True | 
|  |  | 
|  | if (self.send_email_for_recovered_offline_bots and | 
|  | noteworthy_events['recovered_offline']): | 
|  | return True | 
|  |  | 
|  | if (self.send_email_for_recovered_failing_bots and | 
|  | noteworthy_events['recovered_failures']): | 
|  | return True | 
|  |  | 
|  | return False | 
|  |  | 
|  | def writeResults(self, summary): | 
|  | results_file = (self.previous_results_file | 
|  | if self.previous_results_file is not None | 
|  | else GpuBotPoller.DEFAULT_PREVIOUS_RESULTS_FILE) | 
|  |  | 
|  | with open(results_file, 'w') as f: | 
|  | f.write(json.dumps(summary)) | 
|  |  | 
|  | def getPreviousResults(self): | 
|  | previous_results_file = (self.previous_results_file | 
|  | if self.previous_results_file is not None | 
|  | else GpuBotPoller.DEFAULT_PREVIOUS_RESULTS_FILE) | 
|  |  | 
|  | previous_results = {} | 
|  | if os.path.isfile(previous_results_file): | 
|  | with open(previous_results_file, 'r') as f: | 
|  | previous_results = dictsToBots(json.loads(f.read())) | 
|  |  | 
|  | return previous_results | 
|  |  | 
|  | def checkBots(self): | 
|  | time_str = 'Current time: %s\n\n' % (formatTime(time.localtime())) | 
|  | print time_str | 
|  |  | 
|  | try: | 
|  | bots = Waterfall.getAllGpuBots() | 
|  |  | 
|  | offline_bots = Waterfall.getOfflineBots(bots) | 
|  | offline_str = getOfflineBotsStr(offline_bots) | 
|  | print offline_str | 
|  |  | 
|  | failed_bots = Waterfall.getFailedBots(bots) | 
|  | failed_str = getFailedBotsStr(failed_bots) | 
|  | print failed_str | 
|  |  | 
|  | previous_results = self.getPreviousResults() | 
|  | noteworthy_events = getNoteworthyEvents(offline_bots, failed_bots, | 
|  | previous_results) | 
|  |  | 
|  | noteworthy_str = getNoteworthyStr(noteworthy_events) | 
|  | print noteworthy_str | 
|  |  | 
|  | summary = getSummary(offline_bots, failed_bots) | 
|  | self.writeResults(summary) | 
|  |  | 
|  | if (self.emailer is not None and self.shouldEmail(noteworthy_events)): | 
|  | self.emailer.send_email(Emailer.format_email_body(time_str, offline_str, | 
|  | failed_str, noteworthy_str)) | 
|  | except Exception as e: | 
|  | error_str = 'Error: %s' % str(e) | 
|  | print error_str | 
|  |  | 
|  | if self.send_email_on_error: | 
|  | self.emailer.send_email(error_str) | 
|  |  | 
|  | def parseArgs(sys_args): | 
|  | parser = argparse.ArgumentParser(prog=sys_args[0], | 
|  | description='Query the Chromium GPU Bots Waterfall, output ' + | 
|  | 'potential problems, and optionally repeat automatically and/or ' + | 
|  | 'email notifications of results.') | 
|  |  | 
|  | parser.add_argument('--repeat-delay', type=int, dest='repeat_delay', | 
|  | required=False, | 
|  | help='How often to automatically re-run the script, in minutes.') | 
|  |  | 
|  | parser.add_argument('--email-from', type=str, dest='email_from', | 
|  | required=False, | 
|  | help='Email address to send from. Requires also specifying ' + | 
|  | '\'--email-to\'.') | 
|  |  | 
|  | parser.add_argument('--email-to', type=str, dest='email_to', required=False, | 
|  | nargs='+', | 
|  | help='Email address(es) to send to. Requires also specifying ' + | 
|  | '\'--email-from\'') | 
|  |  | 
|  | parser.add_argument('--send-email-for-recovered-offline-bots', | 
|  | dest='send_email_for_recovered_offline_bots', action='store_true', | 
|  | default=False, | 
|  | help='Send an email out when a bot which has been offline for more ' + | 
|  | 'than 1 hour goes back online.') | 
|  |  | 
|  | parser.add_argument('--send-email-for-recovered-failing-bots', | 
|  | dest='send_email_for_recovered_failing_bots', | 
|  | action='store_true', default=False, | 
|  | help='Send an email when a failing bot recovers.') | 
|  |  | 
|  | parser.add_argument('--send-email-on-error', | 
|  | dest='send_email_on_error', | 
|  | action='store_true', default=False, | 
|  | help='Send an email when the script has an error. For example, if ' + | 
|  | 'the server is unreachable.') | 
|  |  | 
|  | parser.add_argument('--email-password-file', | 
|  | dest='email_password_file', | 
|  | required=False, | 
|  | help=(('File containing the plaintext password of the source email ' + | 
|  | 'account. By default, \'%s\' will be tried. If it does not exist, ' + | 
|  | 'you will be prompted. If you opt to store your password on disk ' + | 
|  | 'in plaintext, use of a dummy account is strongly recommended.') | 
|  | % Emailer.DEFAULT_EMAIL_PASSWORD_FILE)) | 
|  |  | 
|  | parser.add_argument('--previous-results-file', | 
|  | dest='previous_results_file', | 
|  | required=False, | 
|  | help=(('File to store the results of the previous invocation of ' + | 
|  | 'this script. By default, \'%s\' will be used.') | 
|  | % GpuBotPoller.DEFAULT_PREVIOUS_RESULTS_FILE)) | 
|  |  | 
|  | args = parser.parse_args(sys_args[1:]) | 
|  |  | 
|  | if args.email_from is not None and args.email_to is None: | 
|  | parser.error('--email-from requires --email-to.') | 
|  | elif args.email_to is not None and args.email_from is None: | 
|  | parser.error('--email-to requires --email-from.') | 
|  | elif args.email_from is None and args.send_email_for_recovered_offline_bots: | 
|  | parser.error('--send-email-for-recovered-offline-bots requires ' + | 
|  | '--email-to and --email-from.') | 
|  | elif (args.email_from is None and args.send_email_for_recovered_failing_bots): | 
|  | parser.error('--send-email-for-recovered-failing-bots ' + | 
|  | 'requires --email-to and --email-from.') | 
|  | elif (args.email_from is None and args.send_email_on_error): | 
|  | parser.error('--send-email-on-error ' + | 
|  | 'requires --email-to and --email-from.') | 
|  | elif (args.email_password_file and | 
|  | not os.path.isfile(args.email_password_file)): | 
|  | parser.error('File does not exist: %s' % args.email_password_file) | 
|  |  | 
|  | return args | 
|  |  | 
|  | def main(sys_args): | 
|  | args = parseArgs(sys_args) | 
|  |  | 
|  | emailer = None | 
|  | if args.email_from is not None and args.email_to is not None: | 
|  | emailer = Emailer(args.email_from, args.email_to, args.email_password_file) | 
|  |  | 
|  | try: | 
|  | emailer.testEmailLogin() | 
|  | except Exception as e: | 
|  | print 'Error logging into email account: %s' % str(e) | 
|  | return 1 | 
|  |  | 
|  | poller = GpuBotPoller(emailer, | 
|  | args.send_email_for_recovered_offline_bots, | 
|  | args.send_email_for_recovered_failing_bots, | 
|  | args.send_email_on_error, | 
|  | args.previous_results_file) | 
|  |  | 
|  | while True: | 
|  | poller.checkBots() | 
|  |  | 
|  | if args.repeat_delay is None: | 
|  | break | 
|  |  | 
|  | print 'Will run again in %d minutes...\n' % args.repeat_delay | 
|  | time.sleep(args.repeat_delay * 60) | 
|  |  | 
|  | return 0 | 
|  |  | 
|  | if __name__ == '__main__': | 
|  | sys.exit(main(sys.argv)) |