| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2013 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 import argparse | 6 import argparse |
| 7 import collections | 7 import collections |
| 8 import contextlib | 8 import contextlib |
| 9 import json | 9 import json |
| 10 import logging | 10 import logging |
| 11 import os | 11 import os |
| 12 import shutil | 12 import shutil |
| 13 import socket | 13 import socket |
| 14 import subprocess | 14 import subprocess |
| 15 import sys | 15 import sys |
| 16 import tempfile | 16 import tempfile |
| 17 | 17 |
| 18 | 18 |
| 19 # Install Infra build environment. | 19 # Install Infra build environment. |
| 20 BUILD_ROOT = os.path.dirname(os.path.dirname(os.path.dirname( | 20 BUILD_ROOT = os.path.dirname(os.path.dirname(os.path.dirname( |
| 21 os.path.abspath(__file__)))) | 21 os.path.abspath(__file__)))) |
| 22 sys.path.insert(0, os.path.join(BUILD_ROOT, 'scripts')) | 22 sys.path.insert(0, os.path.join(BUILD_ROOT, 'scripts')) |
| 23 | 23 |
| 24 from common import annotator | 24 from common import annotator |
| 25 from common import chromium_utils | 25 from common import chromium_utils |
| 26 from common import env | 26 from common import env |
| 27 from common import master_cfg_utils | 27 from common import master_cfg_utils |
| 28 from slave import gce | 28 from slave import gce |
| 29 from slave import infra_platform | 29 from slave import infra_platform |
| 30 from slave import robust_tempdir |
| 30 from slave import update_scripts | 31 from slave import update_scripts |
| 31 | 32 |
| 32 # Logging instance. | 33 # Logging instance. |
| 33 LOGGER = logging.getLogger('annotated_run') | 34 LOGGER = logging.getLogger('annotated_run') |
| 34 | 35 |
| 35 # Return codes used by Butler/Annotee to indicate their failure (as opposed to | 36 # Return codes used by Butler/Annotee to indicate their failure (as opposed to |
| 36 # a forwarded return code from the underlying process). | 37 # a forwarded return code from the underlying process). |
| 37 LOGDOG_ERROR_RETURNCODES = ( | 38 LOGDOG_ERROR_RETURNCODES = ( |
| 38 # Butler runtime error. | 39 # Butler runtime error. |
| 39 250, | 40 250, |
| (...skipping 125 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 165 # the recipe engine. | 166 # the recipe engine. |
| 166 Config = collections.namedtuple('Config', ( | 167 Config = collections.namedtuple('Config', ( |
| 167 'run_cmd', | 168 'run_cmd', |
| 168 'logdog_host', | 169 'logdog_host', |
| 169 'logdog_pubsub_topic', | 170 'logdog_pubsub_topic', |
| 170 'logdog_max_buffer_age', | 171 'logdog_max_buffer_age', |
| 171 'logdog_platform', | 172 'logdog_platform', |
| 172 )) | 173 )) |
| 173 | 174 |
| 174 | 175 |
| 175 class Runtime(object): | |
| 176 """Runtime is the runtime context of the recipe execution. | |
| 177 | |
| 178 It is a ContextManager that tracks generated files and cleans them up at | |
| 179 exit. | |
| 180 """ | |
| 181 | |
| 182 def __init__(self, leak=False): | |
| 183 self._tempdirs = [] | |
| 184 self._leak = leak | |
| 185 | |
| 186 def cleanup(self, path): | |
| 187 self._tempdirs.append(path) | |
| 188 | |
| 189 def tempdir(self, base=None): | |
| 190 """Creates a temporary recipe-local working directory and yields it. | |
| 191 | |
| 192 This creates a temporary directory for this annotation run. Directory | |
| 193 cleanup is appended to the supplied Runtime. | |
| 194 | |
| 195 This creates two levels of directory: | |
| 196 <base>/.recipe_runtime | |
| 197 <base>/.recipe_runtime/tmpFOO | |
| 198 | |
| 199 On termination, the entire "<base>/.recipe_runtime" directory is deleted, | |
| 200 removing the subdirectory created by this instance as well as cleaning up | |
| 201 any other temporary subdirectories leaked by previous executions. | |
| 202 | |
| 203 Args: | |
| 204 rt (Runtime): Process-wide runtime. | |
| 205 base (str/None): The directory under which the tempdir should be created. | |
| 206 If None, the default temporary directory root will be used. | |
| 207 """ | |
| 208 base = base or tempfile.gettempdir() | |
| 209 basedir = ensure_directory(base, '.recipe_runtime') | |
| 210 self.cleanup(basedir) | |
| 211 tdir = tempfile.mkdtemp(dir=basedir) | |
| 212 return tdir | |
| 213 | |
| 214 def __enter__(self): | |
| 215 return self | |
| 216 | |
| 217 def __exit__(self, _et, _ev, _tb): | |
| 218 self.close() | |
| 219 | |
| 220 def close(self): | |
| 221 if self._leak: | |
| 222 LOGGER.warning('(--leak) Leaking temporary paths: %s', self._tempdirs) | |
| 223 else: | |
| 224 for path in reversed(self._tempdirs): | |
| 225 try: | |
| 226 if os.path.isdir(path): | |
| 227 LOGGER.debug('Cleaning up temporary directory [%s].', path) | |
| 228 chromium_utils.RemoveDirectory(path) | |
| 229 except BaseException: | |
| 230 LOGGER.exception('Failed to clean up temporary directory [%s].', | |
| 231 path) | |
| 232 del(self._tempdirs[:]) | |
| 233 | |
| 234 | |
| 235 def get_config(): | 176 def get_config(): |
| 236 """Returns (Config): The constructed Config object. | 177 """Returns (Config): The constructed Config object. |
| 237 | 178 |
| 238 The Config object is constructed by cascading the PLATFORM_CONFIG fields | 179 The Config object is constructed by cascading the PLATFORM_CONFIG fields |
| 239 together based on current OS/Architecture. | 180 together based on current OS/Architecture. |
| 240 | 181 |
| 241 Raises: | 182 Raises: |
| 242 KeyError: if a required configuration key/parameter is not available. | 183 KeyError: if a required configuration key/parameter is not available. |
| 243 """ | 184 """ |
| 244 # Cascade the platform configuration. | 185 # Cascade the platform configuration. |
| (...skipping 16 matching lines...) Expand all Loading... |
| 261 path = os.path.join(*path) | 202 path = os.path.join(*path) |
| 262 if not os.path.isdir(path): | 203 if not os.path.isdir(path): |
| 263 os.makedirs(path) | 204 os.makedirs(path) |
| 264 return path | 205 return path |
| 265 | 206 |
| 266 | 207 |
| 267 def _logdog_get_streamserver_uri(rt, typ): | 208 def _logdog_get_streamserver_uri(rt, typ): |
| 268 """Returns (str): The Butler StreamServer URI. | 209 """Returns (str): The Butler StreamServer URI. |
| 269 | 210 |
| 270 Args: | 211 Args: |
| 271 rt (Runtime): Process-wide runtime. | 212 rt (RobustTempdir): context for temporary directories. |
| 272 typ (str): The type of URI to generate. One of: ['unix']. | 213 typ (str): The type of URI to generate. One of: ['unix']. |
| 273 Raises: | 214 Raises: |
| 274 LogDogBootstrapError: if |typ| is not a known type. | 215 LogDogBootstrapError: if |typ| is not a known type. |
| 275 """ | 216 """ |
| 276 if typ == 'unix': | 217 if typ == 'unix': |
| 277 # We have to use a custom temporary directory here. This is due to the path | 218 # We have to use a custom temporary directory here. This is due to the path |
| 278 # length limitation on UNIX domain sockets, which is generally 104-108 | 219 # length limitation on UNIX domain sockets, which is generally 104-108 |
| 279 # characters. We can't make that assumption about our standard recipe | 220 # characters. We can't make that assumption about our standard recipe |
| 280 # temporary directory. | 221 # temporary directory. |
| 281 # | 222 # |
| (...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 338 def _get_service_account_json(opts, credential_path): | 279 def _get_service_account_json(opts, credential_path): |
| 339 """Returns (str/None): If specified, the path to the service account JSON. | 280 """Returns (str/None): If specified, the path to the service account JSON. |
| 340 | 281 |
| 341 This method probes the local environment and returns a (possibly empty) list | 282 This method probes the local environment and returns a (possibly empty) list |
| 342 of arguments to add to the Butler command line for authentication. | 283 of arguments to add to the Butler command line for authentication. |
| 343 | 284 |
| 344 If we're running on a GCE instance, no arguments will be returned, as GCE | 285 If we're running on a GCE instance, no arguments will be returned, as GCE |
| 345 service account is implicitly authenticated. If we're running on Baremetal, | 286 service account is implicitly authenticated. If we're running on Baremetal, |
| 346 a path to those credentials will be returned. | 287 a path to those credentials will be returned. |
| 347 | 288 |
| 348 Args: | |
| 349 rt (RecipeRuntime): The runtime environment. | |
| 350 Raises: | 289 Raises: |
| 351 |LogDogBootstrapError| if no credentials could be found. | 290 |LogDogBootstrapError| if no credentials could be found. |
| 352 """ | 291 """ |
| 353 path = opts.logdog_service_account_json | 292 path = opts.logdog_service_account_json |
| 354 if path: | 293 if path: |
| 355 return path | 294 return path |
| 356 | 295 |
| 357 if gce.Authenticator.is_gce(): | 296 if gce.Authenticator.is_gce(): |
| 358 LOGGER.info('Running on GCE. No credentials necessary.') | 297 LOGGER.info('Running on GCE. No credentials necessary.') |
| 359 return None | 298 return None |
| (...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 437 | 376 |
| 438 This method executes the recipe engine, bootstrapping it through | 377 This method executes the recipe engine, bootstrapping it through |
| 439 LogDog/Annotee so its output and annotations are streamed to LogDog. The | 378 LogDog/Annotee so its output and annotations are streamed to LogDog. The |
| 440 bootstrap is configured to tee the annotations through STDOUT/STDERR so they | 379 bootstrap is configured to tee the annotations through STDOUT/STDERR so they |
| 441 will still be sent to BuildBot. | 380 will still be sent to BuildBot. |
| 442 | 381 |
| 443 The overall setup here is: | 382 The overall setup here is: |
| 444 [annotated_run.py] => [logdog_butler] => [logdog_annotee] => [recipes.py] | 383 [annotated_run.py] => [logdog_butler] => [logdog_annotee] => [recipes.py] |
| 445 | 384 |
| 446 Args: | 385 Args: |
| 447 rt (Runtime): Process-wide runtime. | 386 rt (RobustTempdir): context for temporary directories. |
| 448 opts (argparse.Namespace): Command-line options. | 387 opts (argparse.Namespace): Command-line options. |
| 449 basedir (str): The base (non-temporary) recipe directory. | 388 basedir (str): The base (non-temporary) recipe directory. |
| 450 tempdir (str): The path to the session temporary directory. | 389 tempdir (str): The path to the session temporary directory. |
| 451 config (Config): Recipe runtime configuration. | 390 config (Config): Recipe runtime configuration. |
| 452 properties (dict): Build properties. | 391 properties (dict): Build properties. |
| 453 cmd (list): The recipe runner command list to bootstrap. | 392 cmd (list): The recipe runner command list to bootstrap. |
| 454 | 393 |
| 455 Returns (int): The return code of the recipe runner process. | 394 Returns (int): The return code of the recipe runner process. |
| 456 | 395 |
| 457 Raises: | 396 Raises: |
| (...skipping 417 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 875 if opts.verbose == 0: | 814 if opts.verbose == 0: |
| 876 level = logging.INFO | 815 level = logging.INFO |
| 877 else: | 816 else: |
| 878 level = logging.DEBUG | 817 level = logging.DEBUG |
| 879 logging.getLogger().setLevel(level) | 818 logging.getLogger().setLevel(level) |
| 880 | 819 |
| 881 clean_old_recipe_engine() | 820 clean_old_recipe_engine() |
| 882 | 821 |
| 883 # Enter our runtime environment. | 822 # Enter our runtime environment. |
| 884 basedir = os.getcwd() | 823 basedir = os.getcwd() |
| 885 with Runtime(leak=opts.leak) as rt: | 824 with robust_tempdir.RobustTempdir( |
| 825 prefix='.recipe_runtime', leak=opts.leak) as rt: |
| 886 tdir = rt.tempdir(base=basedir) | 826 tdir = rt.tempdir(base=basedir) |
| 887 LOGGER.debug('Using temporary directory: [%s].', tdir) | 827 LOGGER.debug('Using temporary directory: [%s].', tdir) |
| 888 | 828 |
| 889 # Load factory properties and configuration. | 829 # Load factory properties and configuration. |
| 890 # TODO(crbug.com/551165): remove flag "factory_properties". | 830 # TODO(crbug.com/551165): remove flag "factory_properties". |
| 891 use_factory_properties_from_disk = (opts.use_factory_properties_from_disk or | 831 use_factory_properties_from_disk = (opts.use_factory_properties_from_disk or |
| 892 bool(opts.factory_properties)) | 832 bool(opts.factory_properties)) |
| 893 properties = get_recipe_properties( | 833 properties = get_recipe_properties( |
| 894 tdir, opts.build_properties, use_factory_properties_from_disk) | 834 tdir, opts.build_properties, use_factory_properties_from_disk) |
| 895 LOGGER.debug('Loaded properties: %s', properties) | 835 LOGGER.debug('Loaded properties: %s', properties) |
| (...skipping 17 matching lines...) Expand all Loading... |
| 913 # Re-execute with the updated annotated_run.py. | 853 # Re-execute with the updated annotated_run.py. |
| 914 rv, _ = _run_command([sys.executable] + argv) | 854 rv, _ = _run_command([sys.executable] + argv) |
| 915 return rv | 855 return rv |
| 916 else: | 856 else: |
| 917 return main(argv[1:]) | 857 return main(argv[1:]) |
| 918 | 858 |
| 919 | 859 |
| 920 if __name__ == '__main__': | 860 if __name__ == '__main__': |
| 921 logging.basicConfig(level=logging.INFO) | 861 logging.basicConfig(level=logging.INFO) |
| 922 sys.exit(shell_main(sys.argv)) | 862 sys.exit(shell_main(sys.argv)) |
| OLD | NEW |