OLD | NEW |
1 #!/usr/bin/env python2.7 | 1 #!/usr/bin/env python2.7 |
2 # Copyright 2015 The Chromium Authors. All rights reserved. | 2 # Copyright 2015 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 """ | 6 """ |
7 This module wraps ChromeOS's Chromite configuration, providing retrieval | 7 This module wraps ChromeOS's Chromite configuration, providing retrieval |
8 methods and Infra-aware introspection. | 8 methods and Infra-aware introspection. |
9 | 9 |
10 For any given ChromeOS build, Chromite may be pinned to a specific ChromeOS | 10 For any given ChromeOS build, Chromite may be pinned to a specific ChromeOS |
11 branch or unpinned at tip-of-tree. Consequently, one function of this module | 11 branch or unpinned at tip-of-tree. Consequently, one function of this module |
12 is to version Chromite and allow external scripts to retrieve a specific | 12 is to version Chromite and allow external scripts to retrieve a specific |
13 version of its configuration. | 13 version of its configuration. |
14 | 14 |
15 This file may also be run as a standalone executable to synchronize the pinned | 15 This file may also be run as a standalone executable to synchronize the pinned |
16 configurations in a cache directory. | 16 configurations in a cache directory. |
17 """ | 17 """ |
18 | 18 |
19 import argparse | 19 import argparse |
20 import base64 | 20 import base64 |
21 import collections | 21 import collections |
22 import json | 22 import json |
23 import logging | 23 import logging |
| 24 import os |
24 import sys | 25 import sys |
25 | 26 |
26 try: | 27 try: |
27 import requests | 28 import requests |
28 import requests.exceptions | 29 import requests.exceptions |
29 except ImportError: | 30 except ImportError: |
30 # TODO(dnj): Remove me. | 31 # TODO(dnj): Remove me. |
31 # | 32 # |
32 # crbug.com/452528: Inconsistent PYTHONPATH environments sometimes cause | 33 # crbug.com/452528: Inconsistent PYTHONPATH environments sometimes cause |
33 # slaves.cfg (and thus this file) to be parsed without 'third_party/requests' | 34 # slaves.cfg (and thus this file) to be parsed without 'third_party/requests' |
34 # present. We will add logic to gracefully become read-only when 'requests' | 35 # present. We will add logic to gracefully become read-only when 'requests' |
35 # is missing so bots don't show errors. | 36 # is missing so bots don't show errors. |
36 requests = None | 37 requests = None |
37 | 38 |
38 from common import configcache | 39 # Add 'common' to our path. |
| 40 from common import configcache, env |
39 | 41 |
40 # The name of the branch associated with tip-of-tree. | 42 # The name of the branch associated with tip-of-tree. |
41 TOT_BRANCH = 'master' | 43 TOT_BRANCH = 'master' |
42 | 44 |
43 | 45 |
44 # A map of branch names to their pinned commits. These are maintained through | 46 # Configure our pin locations. Because repository availability is dependent |
45 # a DEPS hook in <build>/DEPS. In order to update a pinned revision: | 47 # on checkout layout, pin descriptors are conditional on their repository's |
46 # - Update the value here. | 48 # availability. |
| 49 # |
| 50 # These are maintained via a DEPS hook in <build>/DEPS. In order to update a |
| 51 # pinned revision: |
| 52 # - Update the value in the respective JSON file. |
47 # - Run "gclient runhooks --force". | 53 # - Run "gclient runhooks --force". |
48 PINS = collections.OrderedDict(( | 54 PIN_JSON_PATH = os.path.join(env.Build, 'scripts', 'common', |
49 (TOT_BRANCH, 'b2d341616f5043863449ad74d8e4fb06fe9a9b58'), | 55 'cros_chromite_pins.json') |
50 | |
51 # Release waterfall branches. | |
52 # | |
53 # Note that the release waterfall instantiates only three releases. We will | |
54 # keep one branch around for stability, since internal waterfall updates are | |
55 # not atomic. Therefore, we should prune all but the FOUR newest release | |
56 # branches. | |
57 ('release-R47-7520.B', 'd047e007037383c04c4453beca4f69b82ea74188'), | |
58 ('release-R46-7390.B', '33959980025b477366d0792a4e14bfd7c0f0810c'), | |
59 ('release-R45-7262.B', 'd06f185c5383e4ffe884ca30e55df060b96b0c59'), | |
60 ('release-R44-7077.B', '6b12acdd58a3a506f58fb32bd0b78cbfe72506a3'), | |
61 )) | |
62 | 56 |
63 | 57 |
64 class ChromiteError(RuntimeError): | 58 class ChromiteError(RuntimeError): |
65 pass | 59 pass |
66 | 60 |
67 | 61 |
68 # Slave pool allocation types. | 62 # Slave pool allocation types. |
69 class SlaveType(object): | 63 class SlaveType(object): |
70 """Slave configuration expression enumeration.""" | 64 """Slave configuration expression enumeration.""" |
71 BAREMETAL = 'baremetal' | 65 BAREMETAL = 'baremetal' |
(...skipping 272 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
344 site_params = config.pop('_site_params', None) | 338 site_params = config.pop('_site_params', None) |
345 chromite_config = cls(default, templates, site_params) | 339 chromite_config = cls(default, templates, site_params) |
346 for k, v in config.iteritems(): | 340 for k, v in config.iteritems(): |
347 chromite_config.AddTarget(k, v) | 341 chromite_config.AddTarget(k, v) |
348 return chromite_config | 342 return chromite_config |
349 | 343 |
350 | 344 |
351 class ChromitePinManager(object): | 345 class ChromitePinManager(object): |
352 """Manages Chromite pinning associations.""" | 346 """Manages Chromite pinning associations.""" |
353 | 347 |
354 def __init__(self, pinned, require=False): | 348 def __init__(self, cache_name, pinned, require=False): |
355 """Instantiates a new ChromitePinManager. | 349 """Instantiates a new ChromitePinManager. |
356 | 350 |
357 Args: | 351 Args: |
358 pinned: (dict) A dictionary of [branch-name] => [pinned revision] for | 352 pinned: (dict) A dictionary of [branch-name] => [pinned revision] for |
359 pinned branch lookup. | 353 pinned branch lookup. |
360 require: (bool) If False, a requested branch without a pinned match will | 354 require: (bool) If False, a requested branch without a pinned match will |
361 return that branch name; otherwise, a ChromiteError will be returned. | 355 return that branch name; otherwise, a ChromiteError will be returned. |
362 """ | 356 """ |
| 357 self._cache_name = cache_name |
363 self._pinned = pinned | 358 self._pinned = pinned |
364 self._require = require | 359 self._require = require |
365 | 360 |
| 361 @property |
| 362 def cache_name(self): |
| 363 return self._cache_name |
| 364 |
| 365 @classmethod |
| 366 def LoadFromJSON(cls, cache_name, path, **kwargs): |
| 367 """Returns: (ChromitePinManager) a ChromitePinManager instance. |
| 368 |
| 369 Loads a ChromitePinManager configuration from a pin JSON file. |
| 370 """ |
| 371 logging.debug('Loading default pins from: %s', path) |
| 372 with open(path, 'r') as fd: |
| 373 pins = json.load(fd) |
| 374 if not isinstance(pins, dict): |
| 375 raise TypeError('JSON pins are not a dictionary: %s' % (path,)) |
| 376 return cls(cache_name, pins, **kwargs) |
| 377 |
366 def iterpins(self): | 378 def iterpins(self): |
367 """Returns: an iterator over registered (pin, commit) tuples.""" | 379 """Returns: an iterator over registered (pin, commit) tuples.""" |
368 return self._pinned.iteritems() | 380 return self._pinned.iteritems() |
369 | 381 |
370 def GetPinnedBranch(self, branch): | 382 def GetPinnedBranch(self, branch): |
371 """Returns: (str) the pinned version for a given branch, if available. | 383 """Returns: (str) the pinned version for a given branch, if available. |
372 | 384 |
373 Args: | 385 Args: |
374 branch: The pinned branch name to retrieve. | 386 branch: The pinned branch name to retrieve. |
375 | 387 |
376 This method will return the pinned version of a given branch name. If no | 388 This method will return the pinned version of a given branch name. If no |
377 pin for that branch is registered, the branch will be used directly. | 389 pin for that branch is registered, the branch will be used directly. |
378 """ | 390 """ |
379 value = self._pinned.get(branch) | 391 value = self._pinned.get(branch) |
380 if not value: | 392 if not value: |
381 if self._require: | 393 if self._require: |
382 raise ChromiteError('No pinned Chromite commit for [%s]' % ( | 394 raise ChromiteError('No pinned Chromite commit for [%s]' % ( |
383 branch,)) | 395 branch,)) |
384 value = branch | 396 value = branch |
385 return value | 397 return value |
386 | 398 |
| 399 def Get(self, branch=None, allow_fetch=True): |
| 400 """Returns: (ChromiteConfig) the Chromite configuration for a given branch. |
| 401 |
| 402 Args: |
| 403 branch: (str) The name of the branch to retrieve. If None, use |
| 404 tip-of-tree. |
| 405 allow_fetch: (bool) If True, allow a Get miss to fetch a new cache value. |
| 406 """ |
| 407 cache_manager = _GetCacheManager( |
| 408 self, |
| 409 allow_fetch=allow_fetch) |
| 410 |
| 411 try: |
| 412 _UpdateCache(cache_manager, self) |
| 413 except configcache.ReadOnlyError as e: |
| 414 raise ChromiteError("Cannot update read-only config cache. Run " |
| 415 "`gclient runhooks --force`: %s" % (e,)) |
| 416 |
| 417 return ChromiteConfigManager( |
| 418 cache_manager, |
| 419 pinned=self, |
| 420 ).GetConfig(branch) |
| 421 |
| 422 def List(self): |
| 423 """Returns: (list) a list of the configured Chromite pin branch names.""" |
| 424 return self._pinned.keys() |
| 425 |
387 | 426 |
388 class ChromiteConfigManager(object): | 427 class ChromiteConfigManager(object): |
389 """Manages Chromite configuration options and Chromite config fetching.""" | 428 """Manages Chromite configuration options and Chromite config fetching.""" |
390 | 429 |
391 def __init__(self, cache_manager, pinned=None): | 430 def __init__(self, cache_manager, pinned=None): |
392 """Instantiates a new ChromiteConfigManager instance. | 431 """Instantiates a new ChromiteConfigManager instance. |
393 | 432 |
394 Args: | 433 Args: |
395 cache_manager: (cachemanager.CacheManager) The cache manager to use. | 434 cache_manager: (cachemanager.CacheManager) The cache manager to use. |
396 pinned: (ChromitePinManager) If not None, the pin manager to use to lookup | 435 pinned: (ChromitePinManager) If not None, the pin manager to use to lookup |
(...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
485 self.CHROMITE_CONFIG_PATH) | 524 self.CHROMITE_CONFIG_PATH) |
486 data = self._GetText(url) | 525 data = self._GetText(url) |
487 try: | 526 try: |
488 data = base64.b64decode(data) | 527 data = base64.b64decode(data) |
489 except (TypeError, UnicodeEncodeError) as e: | 528 except (TypeError, UnicodeEncodeError) as e: |
490 raise GitilesError(url, 'Failed to decode Base64: %s' % (e,)) | 529 raise GitilesError(url, 'Failed to decode Base64: %s' % (e,)) |
491 return data, version | 530 return data, version |
492 | 531 |
493 | 532 |
494 # Default ChromitePinManager instance. | 533 # Default ChromitePinManager instance. |
495 DefaultChromitePinManager = ChromitePinManager(PINS) | 534 _DEFAULT_PIN_MANAGER = None |
| 535 def DefaultChromitePinManager(): |
| 536 global _DEFAULT_PIN_MANAGER |
| 537 if not _DEFAULT_PIN_MANAGER: |
| 538 _DEFAULT_PIN_MANAGER = ChromitePinManager.LoadFromJSON('chromite', |
| 539 PIN_JSON_PATH) |
| 540 return _DEFAULT_PIN_MANAGER |
496 | 541 |
497 | 542 |
498 def Get(branch=None, allow_fetch=True): | 543 def Get(branch=None, allow_fetch=True): |
499 """Returns: (ChromiteConfig) the Chromite configuration for a given branch. | 544 """Returns: (ChromiteConfig) the Chromite configuration for a given branch. |
500 | 545 |
501 Args: | 546 Args: |
502 branch: (str) The name of the branch to retrieve. If None, use tip-of-tree. | 547 branch: (str) The name of the branch to retrieve. If None, use tip-of-tree. |
503 allow_fetch: (bool) If True, allow a Get miss to fetch a new cache value. | 548 allow_fetch: (bool) If True, allow a Get miss to fetch a new cache value. |
504 """ | 549 """ |
505 cache_manager = _GetCacheManager( | 550 return DefaultChromitePinManager().Get( |
506 DefaultChromitePinManager, | 551 branch=branch, |
507 allow_fetch=allow_fetch) | 552 allow_fetch=allow_fetch) |
508 | 553 |
509 try: | |
510 _UpdateCache(cache_manager, DefaultChromitePinManager) | |
511 except configcache.ReadOnlyError as e: | |
512 raise ChromiteError("Cannot update read-only config cache. Run " | |
513 "`gclient runhooks --force`: %s" % (e,)) | |
514 | 554 |
515 return ChromiteConfigManager( | 555 def List(): |
516 cache_manager, | 556 """Returns: (list) a list of the configured Chromite pin branch names.""" |
517 pinned=DefaultChromitePinManager, | 557 return DefaultChromitePinManager().List() |
518 ).GetConfig(branch) | |
519 | 558 |
520 | 559 |
521 def _UpdateCache(cache_manager, pin_manager, force=False): | 560 def _UpdateCache(cache_manager, pin_manager, force=False): |
522 """Fetches all default pinned versions. | 561 """Fetches all default pinned versions. |
523 | 562 |
524 Args: | 563 Args: |
525 cache_manager: (configcache.CacheManager) The cache manager to use. | 564 cache_manager: (configcache.CacheManager) The cache manager to use. |
526 pin_manager: (ChromitePinManager) The pin manager to use. | 565 pin_manager: (ChromitePinManager) The pin manager to use. |
527 force: (bool) If True, reload the entire cache instead of performing an | 566 force: (bool) If True, reload the entire cache instead of performing an |
528 incremental update. | 567 incremental update. |
(...skipping 17 matching lines...) Expand all Loading... |
546 """Returns: (CacheManager) A CacheManager instance. | 585 """Returns: (CacheManager) A CacheManager instance. |
547 | 586 |
548 This function will return a configured CacheManager instance. If 'allow_fetch' | 587 This function will return a configured CacheManager instance. If 'allow_fetch' |
549 is None or if the 'requests' module could not be imported (crbug.com/452528), | 588 is None or if the 'requests' module could not be imported (crbug.com/452528), |
550 the returned CacheManager will be read-only (i.e., no ChromiteFetcher). | 589 the returned CacheManager will be read-only (i.e., no ChromiteFetcher). |
551 | 590 |
552 Args: | 591 Args: |
553 pin_manager: The ChromitePinManager to use. | 592 pin_manager: The ChromitePinManager to use. |
554 """ | 593 """ |
555 return configcache.CacheManager( | 594 return configcache.CacheManager( |
556 'chromite', | 595 pin_manager.cache_name, |
557 # TODO(dnj): Remove the 'requests' test (crbug.com/452258). | 596 # TODO(dnj): Remove the 'requests' test (crbug.com/452258). |
558 fetcher=(ChromiteFetcher(pin_manager) if allow_fetch and requests | 597 fetcher=(ChromiteFetcher(pin_manager) if allow_fetch and requests |
559 else None), | 598 else None), |
560 **kwargs) | 599 **kwargs) |
561 | 600 |
562 | 601 |
563 def main(): | 602 def main(argv, pin_manager_gen): |
564 parser = argparse.ArgumentParser() | 603 parser = argparse.ArgumentParser() |
565 parser.add_argument('-v', '--verbose', action='count', default=0, | 604 parser.add_argument('-v', '--verbose', action='count', default=0, |
566 help='Increase process verbosity. This can be specified multiple times.') | 605 help='Increase process verbosity. This can be specified multiple times.') |
567 parser.add_argument('-D', '--cache-directory', | 606 parser.add_argument('-D', '--cache-directory', |
568 help='The base cache directory to download pinned configurations into.') | 607 help='The base cache directory to download pinned configurations into.') |
569 parser.add_argument('-f', '--force', action='store_true', | 608 parser.add_argument('-f', '--force', action='store_true', |
570 help='Forces an update, even if the cached already contains an artifact.') | 609 help='Forces an update, even if the cached already contains an artifact.') |
571 args = parser.parse_args() | 610 args = parser.parse_args(argv) |
572 | 611 |
573 # Handle verbosity. | 612 # Handle verbosity. |
574 if args.verbose == 0: | 613 if args.verbose == 0: |
575 loglevel = logging.WARNING | 614 loglevel = logging.WARNING |
576 elif args.verbose == 1: | 615 elif args.verbose == 1: |
577 loglevel = logging.INFO | 616 loglevel = logging.INFO |
578 else: | 617 else: |
579 loglevel = logging.DEBUG | 618 loglevel = logging.DEBUG |
580 logging.getLogger().setLevel(loglevel) | 619 logging.getLogger().setLevel(loglevel) |
581 | 620 |
582 pm = DefaultChromitePinManager | 621 pm = pin_manager_gen() |
583 cm = _GetCacheManager(pm, allow_fetch=True, cache_dir=args.cache_directory) | 622 cm = _GetCacheManager(pm, allow_fetch=True, cache_dir=args.cache_directory) |
584 updated = _UpdateCache(cm, pm, force=args.force) | 623 updated = _UpdateCache(cm, pm, force=args.force) |
585 logging.info('Updated %d cache artifact(s).', len(updated)) | 624 logging.info('Updated %d cache artifact(s).', len(updated)) |
586 return 0 | 625 return 0 |
587 | 626 |
588 | 627 |
589 # Allow this script to be used as a bootstrap to fetch/cache Chromite | 628 # Allow this script to be used as a bootstrap to fetch/cache Chromite |
590 # artifacts (gclient runhooks). | 629 # artifacts (gclient runhooks). |
591 if __name__ == '__main__': | 630 if __name__ == '__main__': |
592 logging.basicConfig() | 631 logging.basicConfig() |
593 try: | 632 try: |
594 sys.exit(main()) | 633 sys.exit(main(sys.argv[1:], DefaultChromitePinManager)) |
595 except Exception as e: | 634 except Exception as e: |
596 logging.exception("Uncaught execption: %s", e) | 635 logging.exception("Uncaught execption: %s", e) |
597 sys.exit(1) | 636 sys.exit(1) |
OLD | NEW |