1# Copyright 2017 The Chromium OS 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"""Utilities for launching and accessing ChromeOS buildbots.""" 5 6from __future__ import print_function 7 8import ast 9import json 10import os 11import re 12import time 13 14from cros_utils import command_executer 15from cros_utils import logger 16 17INITIAL_SLEEP_TIME = 7200 # 2 hours; wait time before polling buildbot. 18SLEEP_TIME = 600 # 10 minutes; time between polling of buildbot. 19 20# Some of our slower builders (llmv-next) are taking more 21# than 8 hours. So, increase this TIME_OUT to 9 hours. 22TIME_OUT = 32400 # Decide the build is dead or will never finish 23 24 25class BuildbotTimeout(Exception): 26 """Exception to throw when a buildbot operation timesout.""" 27 pass 28 29 30def RunCommandInPath(path, cmd): 31 ce = command_executer.GetCommandExecuter() 32 cwd = os.getcwd() 33 os.chdir(path) 34 status, stdout, stderr = ce.RunCommandWOutput(cmd, print_to_console=False) 35 os.chdir(cwd) 36 return status, stdout, stderr 37 38 39def PeekTrybotImage(chromeos_root, buildbucket_id): 40 """Get the artifact URL of a given tryjob. 41 42 Args: 43 buildbucket_id: buildbucket-id 44 chromeos_root: root dir of chrome os checkout 45 46 Returns: 47 (status, url) where status can be 'pass', 'fail', 'running', 48 and url looks like: 49 gs://chromeos-image-archive/trybot-elm-release-tryjob/R67-10468.0.0-b20789 50 """ 51 command = ( 52 'cros buildresult --report json --buildbucket-id %s' % buildbucket_id) 53 rc, out, _ = RunCommandInPath(chromeos_root, command) 54 55 # Current implementation of cros buildresult returns fail when a job is still 56 # running. 57 if rc != 0: 58 return ('running', None) 59 60 results = json.loads(out)[buildbucket_id] 61 62 return (results['status'], results['artifacts_url'].rstrip('/')) 63 64 65def ParseTryjobBuildbucketId(msg): 66 """Find the buildbucket-id in the messages from `cros tryjob`. 67 68 Args: 69 msg: messages from `cros tryjob` 70 71 Returns: 72 buildbucket-id, which will be passed to `cros buildresult` 73 """ 74 output_list = ast.literal_eval(msg) 75 output_dict = output_list[0] 76 if 'buildbucket_id' in output_dict: 77 return output_dict['buildbucket_id'] 78 return None 79 80 81def SubmitTryjob(chromeos_root, 82 buildbot_name, 83 patch_list, 84 tryjob_flags=None, 85 build_toolchain=False): 86 """Calls `cros tryjob ...` 87 88 Args: 89 chromeos_root: the path to the ChromeOS root, needed for finding chromite 90 and launching the buildbot. 91 buildbot_name: the name of the buildbot queue, such as lumpy-release or 92 daisy-paladin. 93 patch_list: a python list of the patches, if any, for the buildbot to use. 94 tryjob_flags: See cros tryjob --help for available options. 95 build_toolchain: builds and uses the latest toolchain, rather than the 96 prebuilt one in SDK. 97 98 Returns: 99 buildbucket id 100 """ 101 patch_arg = '' 102 if patch_list: 103 for p in patch_list: 104 patch_arg = patch_arg + ' -g ' + repr(p) 105 if not tryjob_flags: 106 tryjob_flags = [] 107 if build_toolchain: 108 tryjob_flags.append('--latest-toolchain') 109 tryjob_flags = ' '.join(tryjob_flags) 110 111 # Launch buildbot with appropriate flags. 112 build = buildbot_name 113 command = ('cros tryjob --yes --json --nochromesdk %s %s %s' % 114 (tryjob_flags, patch_arg, build)) 115 print('CMD: %s' % command) 116 _, out, _ = RunCommandInPath(chromeos_root, command) 117 buildbucket_id = ParseTryjobBuildbucketId(out) 118 print('buildbucket_id: %s' % repr(buildbucket_id)) 119 if not buildbucket_id: 120 logger.GetLogger().LogFatal('Error occurred while launching trybot job: ' 121 '%s' % command) 122 return buildbucket_id 123 124 125def GetTrybotImage(chromeos_root, 126 buildbot_name, 127 patch_list, 128 tryjob_flags=None, 129 build_toolchain=False, 130 async=False): 131 """Launch buildbot and get resulting trybot artifact name. 132 133 This function launches a buildbot with the appropriate flags to 134 build the test ChromeOS image, with the current ToT mobile compiler. It 135 checks every 10 minutes to see if the trybot has finished. When the trybot 136 has finished, it parses the resulting report logs to find the trybot 137 artifact (if one was created), and returns that artifact name. 138 139 Args: 140 chromeos_root: the path to the ChromeOS root, needed for finding chromite 141 and launching the buildbot. 142 buildbot_name: the name of the buildbot queue, such as lumpy-release or 143 daisy-paladin. 144 patch_list: a python list of the patches, if any, for the buildbot to use. 145 tryjob_flags: See cros tryjob --help for available options. 146 build_toolchain: builds and uses the latest toolchain, rather than the 147 prebuilt one in SDK. 148 async: don't wait for artifacts; just return the buildbucket id 149 150 Returns: 151 (buildbucket id, partial image url) e.g. 152 (8952271933586980528, trybot-elm-release-tryjob/R67-10480.0.0-b2373596) 153 """ 154 buildbucket_id = SubmitTryjob(chromeos_root, buildbot_name, patch_list, 155 tryjob_flags, build_toolchain) 156 if async: 157 return buildbucket_id, ' ' 158 159 # The trybot generally takes more than 2 hours to finish. 160 # Wait two hours before polling the status. 161 time.sleep(INITIAL_SLEEP_TIME) 162 elapsed = INITIAL_SLEEP_TIME 163 status = 'running' 164 image = '' 165 while True: 166 status, image = PeekTrybotImage(chromeos_root, buildbucket_id) 167 if status == 'running': 168 if elapsed > TIME_OUT: 169 logger.GetLogger().LogFatal( 170 'Unable to get build result for target %s.' % buildbot_name) 171 else: 172 wait_msg = 'Unable to find build result; job may be running.' 173 logger.GetLogger().LogOutput(wait_msg) 174 logger.GetLogger().LogOutput('{0} minutes elapsed.'.format(elapsed / 60)) 175 logger.GetLogger().LogOutput('Sleeping {0} seconds.'.format(SLEEP_TIME)) 176 time.sleep(SLEEP_TIME) 177 elapsed += SLEEP_TIME 178 else: 179 break 180 181 if not buildbot_name.endswith('-toolchain') and status == 'fail': 182 # For rotating testers, we don't care about their status 183 # result, because if any HWTest failed it will be non-zero. 184 # 185 # The nightly performance tests do not run HWTests, so if 186 # their status is non-zero, we do care. In this case 187 # non-zero means the image itself probably did not build. 188 image = '' 189 190 if not image: 191 logger.GetLogger().LogError( 192 'Trybot job (buildbucket id: %s) failed with' 193 'status %s; no trybot image generated. ' % (buildbucket_id, status)) 194 else: 195 # Convert full gs path to what crosperf expects. For example, convert 196 # gs://chromeos-image-archive/trybot-elm-release-tryjob/R67-10468.0.0-b20789 197 # to 198 # trybot-elm-release-tryjob/R67-10468.0.0-b20789 199 image = '/'.join(image.split('/')[-2:]) 200 201 logger.GetLogger().LogOutput("image is '%s'" % image) 202 logger.GetLogger().LogOutput('status is %s' % status) 203 return buildbucket_id, image 204 205 206def DoesImageExist(chromeos_root, build): 207 """Check if the image for the given build exists.""" 208 209 ce = command_executer.GetCommandExecuter() 210 command = ('gsutil ls gs://chromeos-image-archive/%s' 211 '/chromiumos_test_image.tar.xz' % (build)) 212 ret = ce.ChrootRunCommand(chromeos_root, command, print_to_console=False) 213 return not ret 214 215 216def WaitForImage(chromeos_root, build): 217 """Wait for an image to be ready.""" 218 219 elapsed_time = 0 220 while elapsed_time < TIME_OUT: 221 if DoesImageExist(chromeos_root, build): 222 return 223 logger.GetLogger().LogOutput( 224 'Image %s not ready, waiting for 10 minutes' % build) 225 time.sleep(SLEEP_TIME) 226 elapsed_time += SLEEP_TIME 227 228 logger.GetLogger().LogOutput( 229 'Image %s not found, waited for %d hours' % (build, (TIME_OUT / 3600))) 230 raise BuildbotTimeout('Timeout while waiting for image %s' % build) 231 232 233def GetLatestImage(chromeos_root, path): 234 """Get latest image""" 235 236 fmt = re.compile(r'R([0-9]+)-([0-9]+).([0-9]+).([0-9]+)') 237 238 ce = command_executer.GetCommandExecuter() 239 command = ('gsutil ls gs://chromeos-image-archive/%s' % path) 240 _, out, _ = ce.ChrootRunCommandWOutput( 241 chromeos_root, command, print_to_console=False) 242 candidates = [l.split('/')[-2] for l in out.split()] 243 candidates = map(fmt.match, candidates) 244 candidates = [[int(r) for r in m.group(1, 2, 3, 4)] for m in candidates if m] 245 candidates.sort(reverse=True) 246 for c in candidates: 247 build = '%s/R%d-%d.%d.%d' % (path, c[0], c[1], c[2], c[3]) 248 if DoesImageExist(chromeos_root, build): 249 return build 250