# Copyright 2017 The Chromium OS Authors. All rights reserved.
|
# Use of this source code is governed by a BSD-style license that can be
|
# found in the LICENSE file.
|
"""Utilities for launching and accessing ChromeOS buildbots."""
|
|
from __future__ import print_function
|
|
import ast
|
import json
|
import os
|
import re
|
import time
|
|
from cros_utils import command_executer
|
from cros_utils import logger
|
|
INITIAL_SLEEP_TIME = 7200 # 2 hours; wait time before polling buildbot.
|
SLEEP_TIME = 600 # 10 minutes; time between polling of buildbot.
|
|
# Some of our slower builders (llmv-next) are taking more
|
# than 8 hours. So, increase this TIME_OUT to 9 hours.
|
TIME_OUT = 32400 # Decide the build is dead or will never finish
|
|
|
class BuildbotTimeout(Exception):
|
"""Exception to throw when a buildbot operation timesout."""
|
pass
|
|
|
def RunCommandInPath(path, cmd):
|
ce = command_executer.GetCommandExecuter()
|
cwd = os.getcwd()
|
os.chdir(path)
|
status, stdout, stderr = ce.RunCommandWOutput(cmd, print_to_console=False)
|
os.chdir(cwd)
|
return status, stdout, stderr
|
|
|
def PeekTrybotImage(chromeos_root, buildbucket_id):
|
"""Get the artifact URL of a given tryjob.
|
|
Args:
|
buildbucket_id: buildbucket-id
|
chromeos_root: root dir of chrome os checkout
|
|
Returns:
|
(status, url) where status can be 'pass', 'fail', 'running',
|
and url looks like:
|
gs://chromeos-image-archive/trybot-elm-release-tryjob/R67-10468.0.0-b20789
|
"""
|
command = (
|
'cros buildresult --report json --buildbucket-id %s' % buildbucket_id)
|
rc, out, _ = RunCommandInPath(chromeos_root, command)
|
|
# Current implementation of cros buildresult returns fail when a job is still
|
# running.
|
if rc != 0:
|
return ('running', None)
|
|
results = json.loads(out)[buildbucket_id]
|
|
return (results['status'], results['artifacts_url'].rstrip('/'))
|
|
|
def ParseTryjobBuildbucketId(msg):
|
"""Find the buildbucket-id in the messages from `cros tryjob`.
|
|
Args:
|
msg: messages from `cros tryjob`
|
|
Returns:
|
buildbucket-id, which will be passed to `cros buildresult`
|
"""
|
output_list = ast.literal_eval(msg)
|
output_dict = output_list[0]
|
if 'buildbucket_id' in output_dict:
|
return output_dict['buildbucket_id']
|
return None
|
|
|
def SubmitTryjob(chromeos_root,
|
buildbot_name,
|
patch_list,
|
tryjob_flags=None,
|
build_toolchain=False):
|
"""Calls `cros tryjob ...`
|
|
Args:
|
chromeos_root: the path to the ChromeOS root, needed for finding chromite
|
and launching the buildbot.
|
buildbot_name: the name of the buildbot queue, such as lumpy-release or
|
daisy-paladin.
|
patch_list: a python list of the patches, if any, for the buildbot to use.
|
tryjob_flags: See cros tryjob --help for available options.
|
build_toolchain: builds and uses the latest toolchain, rather than the
|
prebuilt one in SDK.
|
|
Returns:
|
buildbucket id
|
"""
|
patch_arg = ''
|
if patch_list:
|
for p in patch_list:
|
patch_arg = patch_arg + ' -g ' + repr(p)
|
if not tryjob_flags:
|
tryjob_flags = []
|
if build_toolchain:
|
tryjob_flags.append('--latest-toolchain')
|
tryjob_flags = ' '.join(tryjob_flags)
|
|
# Launch buildbot with appropriate flags.
|
build = buildbot_name
|
command = ('cros tryjob --yes --json --nochromesdk %s %s %s' %
|
(tryjob_flags, patch_arg, build))
|
print('CMD: %s' % command)
|
_, out, _ = RunCommandInPath(chromeos_root, command)
|
buildbucket_id = ParseTryjobBuildbucketId(out)
|
print('buildbucket_id: %s' % repr(buildbucket_id))
|
if not buildbucket_id:
|
logger.GetLogger().LogFatal('Error occurred while launching trybot job: '
|
'%s' % command)
|
return buildbucket_id
|
|
|
def GetTrybotImage(chromeos_root,
|
buildbot_name,
|
patch_list,
|
tryjob_flags=None,
|
build_toolchain=False,
|
async=False):
|
"""Launch buildbot and get resulting trybot artifact name.
|
|
This function launches a buildbot with the appropriate flags to
|
build the test ChromeOS image, with the current ToT mobile compiler. It
|
checks every 10 minutes to see if the trybot has finished. When the trybot
|
has finished, it parses the resulting report logs to find the trybot
|
artifact (if one was created), and returns that artifact name.
|
|
Args:
|
chromeos_root: the path to the ChromeOS root, needed for finding chromite
|
and launching the buildbot.
|
buildbot_name: the name of the buildbot queue, such as lumpy-release or
|
daisy-paladin.
|
patch_list: a python list of the patches, if any, for the buildbot to use.
|
tryjob_flags: See cros tryjob --help for available options.
|
build_toolchain: builds and uses the latest toolchain, rather than the
|
prebuilt one in SDK.
|
async: don't wait for artifacts; just return the buildbucket id
|
|
Returns:
|
(buildbucket id, partial image url) e.g.
|
(8952271933586980528, trybot-elm-release-tryjob/R67-10480.0.0-b2373596)
|
"""
|
buildbucket_id = SubmitTryjob(chromeos_root, buildbot_name, patch_list,
|
tryjob_flags, build_toolchain)
|
if async:
|
return buildbucket_id, ' '
|
|
# The trybot generally takes more than 2 hours to finish.
|
# Wait two hours before polling the status.
|
time.sleep(INITIAL_SLEEP_TIME)
|
elapsed = INITIAL_SLEEP_TIME
|
status = 'running'
|
image = ''
|
while True:
|
status, image = PeekTrybotImage(chromeos_root, buildbucket_id)
|
if status == 'running':
|
if elapsed > TIME_OUT:
|
logger.GetLogger().LogFatal(
|
'Unable to get build result for target %s.' % buildbot_name)
|
else:
|
wait_msg = 'Unable to find build result; job may be running.'
|
logger.GetLogger().LogOutput(wait_msg)
|
logger.GetLogger().LogOutput('{0} minutes elapsed.'.format(elapsed / 60))
|
logger.GetLogger().LogOutput('Sleeping {0} seconds.'.format(SLEEP_TIME))
|
time.sleep(SLEEP_TIME)
|
elapsed += SLEEP_TIME
|
else:
|
break
|
|
if not buildbot_name.endswith('-toolchain') and status == 'fail':
|
# For rotating testers, we don't care about their status
|
# result, because if any HWTest failed it will be non-zero.
|
#
|
# The nightly performance tests do not run HWTests, so if
|
# their status is non-zero, we do care. In this case
|
# non-zero means the image itself probably did not build.
|
image = ''
|
|
if not image:
|
logger.GetLogger().LogError(
|
'Trybot job (buildbucket id: %s) failed with'
|
'status %s; no trybot image generated. ' % (buildbucket_id, status))
|
else:
|
# Convert full gs path to what crosperf expects. For example, convert
|
# gs://chromeos-image-archive/trybot-elm-release-tryjob/R67-10468.0.0-b20789
|
# to
|
# trybot-elm-release-tryjob/R67-10468.0.0-b20789
|
image = '/'.join(image.split('/')[-2:])
|
|
logger.GetLogger().LogOutput("image is '%s'" % image)
|
logger.GetLogger().LogOutput('status is %s' % status)
|
return buildbucket_id, image
|
|
|
def DoesImageExist(chromeos_root, build):
|
"""Check if the image for the given build exists."""
|
|
ce = command_executer.GetCommandExecuter()
|
command = ('gsutil ls gs://chromeos-image-archive/%s'
|
'/chromiumos_test_image.tar.xz' % (build))
|
ret = ce.ChrootRunCommand(chromeos_root, command, print_to_console=False)
|
return not ret
|
|
|
def WaitForImage(chromeos_root, build):
|
"""Wait for an image to be ready."""
|
|
elapsed_time = 0
|
while elapsed_time < TIME_OUT:
|
if DoesImageExist(chromeos_root, build):
|
return
|
logger.GetLogger().LogOutput(
|
'Image %s not ready, waiting for 10 minutes' % build)
|
time.sleep(SLEEP_TIME)
|
elapsed_time += SLEEP_TIME
|
|
logger.GetLogger().LogOutput(
|
'Image %s not found, waited for %d hours' % (build, (TIME_OUT / 3600)))
|
raise BuildbotTimeout('Timeout while waiting for image %s' % build)
|
|
|
def GetLatestImage(chromeos_root, path):
|
"""Get latest image"""
|
|
fmt = re.compile(r'R([0-9]+)-([0-9]+).([0-9]+).([0-9]+)')
|
|
ce = command_executer.GetCommandExecuter()
|
command = ('gsutil ls gs://chromeos-image-archive/%s' % path)
|
_, out, _ = ce.ChrootRunCommandWOutput(
|
chromeos_root, command, print_to_console=False)
|
candidates = [l.split('/')[-2] for l in out.split()]
|
candidates = map(fmt.match, candidates)
|
candidates = [[int(r) for r in m.group(1, 2, 3, 4)] for m in candidates if m]
|
candidates.sort(reverse=True)
|
for c in candidates:
|
build = '%s/R%d-%d.%d.%d' % (path, c[0], c[1], c[2], c[3])
|
if DoesImageExist(chromeos_root, build):
|
return build
|