Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 #!/usr/bin/python | |
| 2 # Copyright 2015 Google Inc. All Rights Reserved. | |
| 3 # pylint: disable=F0401 | |
| 4 | |
| 5 """Parse, validate and query the desired master state json.""" | |
| 6 | |
| 7 import bisect | |
| 8 import json | |
| 9 import logging | |
| 10 import operator | |
| 11 import os | |
| 12 | |
| 13 from infra.libs.buildbot import master | |
| 14 from infra.libs.time_functions import timestamp | |
| 15 from infra.services.master_lifecycle import buildbot_state | |
| 16 | |
| 17 | |
| 18 LOGGER = logging.getLogger(__name__) | |
| 19 | |
| 20 | |
| 21 class InvalidDesiredMasterState(ValueError): | |
| 22 pass | |
| 23 | |
| 24 | |
| 25 def load_desired_state_file(filename): # pragma: no cover | |
|
agable
2015/05/07 17:59:31
Seems like this could be tested with a couple vali
ghost stip (do not use)
2015/05/07 19:49:39
Done.
| |
| 26 with open(filename) as f: | |
| 27 desired_state = json.load(f) | |
| 28 if not desired_master_state_is_valid(desired_state): | |
| 29 raise InvalidDesiredMasterState() | |
| 30 return desired_state | |
| 31 | |
| 32 | |
| 33 def desired_master_state_is_valid(desired_state): | |
| 34 """Verify that the desired_master_state file is valid.""" | |
| 35 now = timestamp.utcnow_ts() | |
| 36 | |
| 37 for mastername, states in desired_state.iteritems(): | |
| 38 # Verify desired_state and transition_time_utc are present. | |
| 39 for k in ('desired_state', 'transition_time_utc'): | |
| 40 if not all(k in state for state in states): | |
| 41 LOGGER.error( | |
| 42 'one or more states for master %s do not contain %s', mastername, k) | |
| 43 return False | |
| 44 | |
| 45 # Verify the list is properly sorted. | |
| 46 sorted_states = sorted( | |
| 47 states, key=operator.itemgetter('transition_time_utc')) | |
| 48 if sorted_states != states: | |
| 49 LOGGER.error('master %s does not have states sorted by timestamp', | |
| 50 mastername) | |
| 51 LOGGER.error('should be:\n%s', json.dumps(sorted_states, indent=2)) | |
| 52 return False | |
| 53 | |
| 54 # Verify desired_state and timestamp are valid. | |
|
agable
2015/05/07 17:59:31
Seems like the first "check for presence" loop cou
ghost stip (do not use)
2015/05/07 19:49:39
Done.
| |
| 55 for state in states: | |
| 56 if (state['desired_state'] not in | |
| 57 buildbot_state.STATES['desired_buildbot_state']): | |
| 58 LOGGER.error( | |
| 59 'desired_state \'%s\' is not one of %s', | |
| 60 state['desired_state'], | |
| 61 buildbot_state.STATES['desired_buildbot_state']) | |
| 62 return False | |
| 63 | |
| 64 if not isinstance(state['transition_time_utc'], (int, float)): | |
| 65 LOGGER.error( | |
| 66 'transition_time_utc \'%s\' is not an int or float', | |
| 67 state['transition_time_utc']) | |
| 68 return False | |
| 69 | |
| 70 # Verify there is at least one state in the past. | |
| 71 if not get_master_state(states, now=now): | |
| 72 LOGGER.error( | |
| 73 'master %s does not have a state older than %s', mastername, now) | |
| 74 return False | |
| 75 | |
| 76 return True | |
| 77 | |
| 78 | |
| 79 def get_master_state(states, now=None): | |
| 80 """Returns the latest state earlier than the current (or specified) time. | |
| 81 | |
| 82 If there are three items, each with transition times of 100, 200 and 300: | |
| 83 * calling when 'now' is 50 will return None | |
| 84 * calling when 'now' is 150 will return the first item | |
| 85 * calling when 'now' is 400 will return the third item | |
| 86 """ | |
| 87 now = now or timestamp.utcnow_ts() | |
| 88 | |
| 89 times = [x['transition_time_utc'] for x in states] | |
| 90 index = bisect.bisect_left(times, now) | |
| 91 if index: | |
|
agable
2015/05/07 17:59:31
bisect_left is guaranteed to return an int, so ple
ghost stip (do not use)
2015/05/07 19:49:39
Done.
| |
| 92 return states[index - 1] | |
| 93 return None | |
| 94 | |
| 95 | |
| 96 def get_masters_for_host(desired_state, build_dir, hostname): | |
| 97 """Identify which masters on this host should be managed. | |
| 98 | |
| 99 Returns triggered_masters and ignored_masters (a set and a list). | |
|
agable
2015/05/07 17:59:31
(a list and a set, respectively).
ghost stip (do not use)
2015/05/07 19:49:39
Done.
| |
| 100 | |
| 101 triggered_masters are masters on this host which have a corresponding entry in | |
| 102 the desired_master_state file. Any master running assigned to this host that | |
| 103 does *not* have an entry in the desired_master_state file is considered | |
| 104 'ignored.' | |
| 105 | |
| 106 triggered_masters is a list of dicts. Each dict is the full dict from | |
| 107 mastermap.py with two extra keys: 'fulldir' (the absolute path to the master | |
| 108 directory), and 'states' (a list of desired states sorted by transition time, | |
| 109 pulled from the desired states file). | |
| 110 | |
| 111 ignored_masters is a set of 'dirname' strings (ex: master.chromium). | |
| 112 """ | |
| 113 triggered_masters = [] | |
| 114 ignored_masters = set() | |
| 115 for master_dict in master.get_mastermap_for_host( | |
| 116 build_dir, hostname): | |
| 117 if master_dict['dirname'] in desired_state: | |
| 118 if master_dict['internal']: | |
| 119 master_dir = os.path.abspath(os.path.join( | |
| 120 build_dir, os.pardir, 'build_internal', 'masters', | |
| 121 master_dict['dirname'])) | |
| 122 else: | |
| 123 master_dir = os.path.abspath(os.path.join( | |
| 124 build_dir, 'masters', master_dict['dirname'])) | |
| 125 master_dict['fulldir'] = master_dir | |
| 126 master_dict['states'] = desired_state[master_dict['dirname']] | |
| 127 | |
| 128 triggered_masters.append(master_dict) | |
| 129 else: | |
| 130 ignored_masters.add(master_dict['dirname']) | |
| 131 return triggered_masters, ignored_masters | |
| OLD | NEW |