Dan Shi | eada904 | 2013-12-02 15:44:59 -0800 | [diff] [blame] | 1 | #!/usr/bin/python |
| 2 | # |
| 3 | # Copyright (c) 2013 The Chromium OS Authors. All rights reserved. |
| 4 | # Use of this source code is governed by a BSD-style license that can be |
| 5 | # found in the LICENSE file. |
| 6 | |
| 7 | """Script to stage servo image and full payload in all devservers. |
| 8 | |
| 9 | This script should be executed in a server running autotest scheduler. It is |
| 10 | used to accomplish following tasks: |
| 11 | 1. Get the latest staged build in devserver as the build that's currently used |
| 12 | by servo. |
| 13 | 2. Stage the latest or a specified beaglebone build from Google Storage |
| 14 | in all devservers. |
| 15 | 3. Show an error if a specified build was successfully staged, but a devserver |
| 16 | has newer build staged. |
| 17 | |
| 18 | """ |
| 19 | |
| 20 | import argparse |
| 21 | import logging |
| 22 | import sys |
| 23 | |
| 24 | import common |
| 25 | from autotest_lib.client.common_lib import global_config |
| 26 | from autotest_lib.client.common_lib.cros import dev_server |
| 27 | from devserver import gsutil_util |
| 28 | |
| 29 | |
Dan Shi | a63c6bc | 2014-01-22 13:42:59 -0800 | [diff] [blame] | 30 | DEFAULT_BUILD_TARGET = global_config.global_config.get_config_value( |
| 31 | 'CROS', 'servo_builder') |
Dan Shi | eada904 | 2013-12-02 15:44:59 -0800 | [diff] [blame] | 32 | IMAGE_STORAGE_SERVER = global_config.global_config.get_config_value('CROS', |
| 33 | 'image_storage_server', type=str) |
| 34 | |
| 35 | |
| 36 | def get_all_devservers(): |
| 37 | """Get a list of ImageServer objects for all available devservers. |
| 38 | |
| 39 | @return: A list of ImageServer objects for all available devservers. |
| 40 | """ |
| 41 | return [dev_server.ImageServer(server) for server in |
| 42 | dev_server._get_dev_server_list()] |
| 43 | |
| 44 | |
| 45 | def get_latest_build(build_target): |
| 46 | """Get the latest build of beaglebone image in Google Storage. |
| 47 | |
| 48 | @param build_target: build target to look up for the latest build. |
| 49 | @return: build name of latest beaglebone image in Google Storage, |
Dan Shi | b942848 | 2014-01-07 17:10:17 -0800 | [diff] [blame] | 50 | e.g., beaglebone_servo-release/R33-4936.0.0 |
Dan Shi | eada904 | 2013-12-02 15:44:59 -0800 | [diff] [blame] | 51 | """ |
| 52 | archive_url = IMAGE_STORAGE_SERVER + build_target |
| 53 | return gsutil_util.GetLatestVersionFromGSDir(archive_url) |
| 54 | |
| 55 | |
| 56 | def stage_build(devserver, build, build_target=DEFAULT_BUILD_TARGET): |
| 57 | """Stage build and its respective image in a devserver |
| 58 | |
| 59 | @param devserver: an instance of ImageServer. |
| 60 | @param build: name of the beaglebone build to stage, e.g., R33-4936.0.0. |
| 61 | @param build_target: build target of the build to be staged, default |
Dan Shi | b942848 | 2014-01-07 17:10:17 -0800 | [diff] [blame] | 62 | is set to DEFAULT_BUILD_TARGET. |
Dan Shi | eada904 | 2013-12-02 15:44:59 -0800 | [diff] [blame] | 63 | @raise DevServerException: upon any return code that's not HTTP OK when |
| 64 | making stage_artifacts RPC. |
| 65 | """ |
| 66 | image = '%s/%s' % (build_target, build) |
| 67 | # Stage base_image will download image.zip, |
| 68 | # unzip chromiumos_base_image.tar.xz, which contains |
| 69 | # chromiumos_base_image.bin. |
| 70 | artifacts = ['full_payload', 'base_image'] |
| 71 | logging.info('Staging %s using devserver %s...', image, devserver.url()) |
| 72 | devserver.stage_artifacts(image=image, artifacts=artifacts) |
| 73 | logging.info('Staging completed successfully.') |
| 74 | |
| 75 | |
| 76 | def parse_arguments(argv): |
| 77 | """ |
| 78 | Parse command line arguments |
| 79 | |
| 80 | @param argv: argument list to parse |
| 81 | @returns: parsed arguments. |
| 82 | @raises SystemExit if arguments are malformed, or required arguments |
| 83 | are not present. |
| 84 | """ |
| 85 | description = 'Stage latest or a specific build at all devservers.' |
| 86 | parser = argparse.ArgumentParser(description=description) |
| 87 | parser.add_argument('-i', '--build', action='store', default=None, |
| 88 | help='Optional argument to specify a build to stage, ' |
| 89 | 'e.g., R33-5079.0.0. Do not include the build ' |
| 90 | 'target here.') |
| 91 | parser.add_argument('-t', '--build_target', action='store', |
| 92 | default=DEFAULT_BUILD_TARGET, |
| 93 | help='Optional argument to specify a build target, ' |
Dan Shi | b942848 | 2014-01-07 17:10:17 -0800 | [diff] [blame] | 94 | 'e.g., trybot-beaglebone_servo-release. Default ' |
| 95 | 'value is set to %s.' % DEFAULT_BUILD_TARGET) |
Dan Shi | eada904 | 2013-12-02 15:44:59 -0800 | [diff] [blame] | 96 | return parser.parse_args(argv) |
| 97 | |
| 98 | |
| 99 | def main(argv): |
| 100 | """Main entrance of the script. |
| 101 | |
| 102 | @param argv: arguments list |
| 103 | """ |
| 104 | arguments = parse_arguments(argv) |
| 105 | build_target = arguments.build_target |
| 106 | devservers = get_all_devservers() |
| 107 | if not devservers: |
| 108 | raise Exception('No devserver found. Script failed to stage any build. ' |
| 109 | 'Please check your shadow_config.ini file about ' |
| 110 | 'devserver setting.') |
| 111 | |
| 112 | if arguments.build: |
| 113 | build_to_stage = arguments.build |
| 114 | else: |
| 115 | build_to_stage = get_latest_build(build_target) |
| 116 | logging.info('Latest build for %s is %s.', build_target, build_to_stage) |
| 117 | |
| 118 | try: |
| 119 | # The build must be staged in all devservers. Any devserver failed to |
| 120 | # stage the build shall fail the script. When in prod, a beaglebone may |
| 121 | # hit any available devserver to update the latest image staged in the |
| 122 | # devserver, without requesting to stage the build. That's why it's |
| 123 | # essential for all devservers to have the same build staged. |
| 124 | for devserver in devservers: |
| 125 | stage_build(devserver, build_to_stage, build_target) |
| 126 | except dev_server.DevServerException as e: |
| 127 | logging.error('Staging build %s failed with error: \n%s', |
| 128 | build_to_stage, e) |
| 129 | # Check if latest_staged_builds are all the same, if so, delete any |
| 130 | # staged |build_to_stage| in all devservers. |
| 131 | latest_staged_builds = [devserver.get_latest_build_in_server( |
| 132 | build_target) for devserver in devservers] |
| 133 | if not all(b == latest_staged_builds[0] for b in latest_staged_builds): |
| 134 | logging.error(('devservers have different latest builds for build ' |
| 135 | 'target %s. You should try to fix the problem, and ' |
| 136 | 'run this script again to make sure all devservers ' |
| 137 | 'have the same latest build staged.'), build_target) |
| 138 | sys.exit(1) |
| 139 | |
| 140 | # Save the latest build in each devserver in a list, will be used to check |
| 141 | # if they are newer builds compared to the build to be staged. |
| 142 | latest_staged_builds = [devserver.get_latest_build_in_server(build_target) |
| 143 | for devserver in devservers] |
| 144 | |
| 145 | # Compare the previous latest build staged in devserver and build_to_stage. |
| 146 | # If the previous latest builder is newer, post a warning that user should |
| 147 | # manually delete any newer builds in each devserver. |
| 148 | if not all(b == build_to_stage for b in latest_staged_builds): |
| 149 | logging.error('Following devservers have staged newer build. You need ' |
| 150 | 'to manually delete any build newer than %s in each ' |
| 151 | 'devserver to guarantee beaglebone can be upgraded to the' |
| 152 | ' desired build.\n', build_to_stage) |
| 153 | |
| 154 | for devserver, build in zip(devservers, latest_staged_builds): |
| 155 | if build != build_to_stage: |
| 156 | logging.error('%s\t%s', devserver.url(), build) |
| 157 | |
| 158 | |
| 159 | if __name__ == '__main__': |
| 160 | main(sys.argv[1:]) |