| # 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 |