1# Copyright 2015 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5"""A Model that represents one bisect or perf test try job. 6 7TryJob entities are checked in /update_bug_with_results to check completed 8bisect jobs and update bugs with results. 9 10They are also used in /auto_bisect to restart unsuccessful bisect jobs. 11""" 12 13import datetime 14import json 15import logging 16 17from google.appengine.ext import ndb 18 19from dashboard import bisect_stats 20from dashboard import buildbucket_service 21from dashboard.models import bug_data 22from dashboard.models import internal_only_model 23 24 25class TryJob(internal_only_model.InternalOnlyModel): 26 """Stores config and tracking info about a single try job.""" 27 bot = ndb.StringProperty() 28 config = ndb.TextProperty() 29 bug_id = ndb.IntegerProperty() 30 email = ndb.StringProperty() 31 rietveld_issue_id = ndb.IntegerProperty() 32 rietveld_patchset_id = ndb.IntegerProperty() 33 master_name = ndb.StringProperty(default='ChromiumPerf', indexed=False) 34 buildbucket_job_id = ndb.StringProperty() 35 internal_only = ndb.BooleanProperty(default=False, indexed=True) 36 37 # Bisect run status (e.g., started, failed). 38 status = ndb.StringProperty( 39 default='pending', 40 choices=[ 41 'pending', # Created, but job start has not been confirmed. 42 'started', # Job is confirmed started. 43 'failed', # Job terminated, red build. 44 'staled', # No updates from bots. 45 'completed', # Job terminated, green build. 46 'aborted', # Job terminated with abort (purple, early abort). 47 ], 48 indexed=True) 49 50 # Number of times this job has been tried. 51 run_count = ndb.IntegerProperty(default=0) 52 53 # Last time this job was started. 54 last_ran_timestamp = ndb.DateTimeProperty() 55 56 job_type = ndb.StringProperty( 57 default='bisect', 58 choices=['bisect', 'bisect-fyi', 'perf-try']) 59 60 # job_name attribute is used by try jobs of bisect FYI. 61 job_name = ndb.StringProperty(default=None) 62 63 # Results data coming from bisect bots. 64 results_data = ndb.JsonProperty(indexed=False) 65 66 log_record_id = ndb.StringProperty(indexed=False) 67 68 # Sets of emails of users who has confirmed this TryJob result is bad. 69 bad_result_emails = ndb.PickleProperty() 70 71 def SetStarted(self): 72 self.status = 'started' 73 self.run_count += 1 74 self.last_ran_timestamp = datetime.datetime.now() 75 self.put() 76 if self.bug_id: 77 bug_data.SetBisectStatus(self.bug_id, 'started') 78 79 def SetFailed(self): 80 self.status = 'failed' 81 self.put() 82 if self.bug_id: 83 bug_data.SetBisectStatus(self.bug_id, 'failed') 84 bisect_stats.UpdateBisectStats(self.bot, 'failed') 85 86 def SetStaled(self): 87 self.status = 'staled' 88 self.put() 89 logging.info('Updated status to staled') 90 # TODO(sullivan, dtu): what is the purpose of 'staled' status? Doesn't it 91 # just prevent updating jobs older than 24 hours??? 92 # TODO(chrisphan): Add 'staled' state to bug_data and bisect_stats. 93 if self.bug_id: 94 bug_data.SetBisectStatus(self.bug_id, 'failed') 95 bisect_stats.UpdateBisectStats(self.bot, 'failed') 96 97 def SetCompleted(self): 98 logging.info('Updated status to completed') 99 self.status = 'completed' 100 self.put() 101 if self.bug_id: 102 bug_data.SetBisectStatus(self.bug_id, 'completed') 103 bisect_stats.UpdateBisectStats(self.bot, 'completed') 104 105 def GetConfigDict(self): 106 return json.loads(self.config.split('=', 1)[1]) 107 108 def CheckFailureFromBuildBucket(self): 109 # Buildbucket job id is not always set. 110 if not self.buildbucket_job_id: 111 return 112 job_info = buildbucket_service.GetJobStatus(self.buildbucket_job_id) 113 data = job_info.get('build', {}) 114 115 # Since the job is completed successfully, results_data must 116 # have been set appropriately by the bisector. 117 # The buildbucket job's 'status' and 'result' fields are documented here: 118 # https://goto.google.com/bb_status 119 if data.get('status') == 'COMPLETED' and data.get('result') == 'SUCCESS': 120 return 121 122 # Proceed if the job failed or cancelled 123 logging.info('Job failed. Buildbucket id %s', self.buildbucket_job_id) 124 data['result_details'] = json.loads(data['result_details_json']) 125 # There are various failure and cancellation reasons for a buildbucket 126 # job to fail as listed in https://goto.google.com/bb_status. 127 job_updates = { 128 'status': 'failed', 129 'failure_reason': (data.get('cancelation_reason') or 130 data.get('failure_reason')), 131 'buildbot_log_url': data.get('url') 132 } 133 details = data.get('result_details') 134 if details: 135 properties = details.get('properties') 136 if properties: 137 job_updates['bisect_bot'] = properties.get('buildername') 138 job_updates['extra_result_code'] = properties.get( 139 'extra_result_code') 140 bisect_config = properties.get('bisect_config') 141 if bisect_config: 142 job_updates['try_job_id'] = bisect_config.get('try_job_id') 143 job_updates['bug_id'] = bisect_config.get('bug_id') 144 job_updates['command'] = bisect_config.get('command') 145 job_updates['test_type'] = bisect_config.get('test_type') 146 job_updates['metric'] = bisect_config.get('metric') 147 job_updates['good_revision'] = bisect_config.get('good_revision') 148 job_updates['bad_revision'] = bisect_config.get('bad_revision') 149 if not self.results_data: 150 self.results_data = {} 151 self.results_data.update(job_updates) 152 self.status = 'failed' 153 self.last_ran_timestamp = datetime.datetime.fromtimestamp( 154 float(data['updated_ts'])/1000000) 155 self.put() 156 logging.info('updated status to failed.') 157 158