| 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): |
| 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 timestamp are valid. |
| 39 for state in states: |
| 40 # Verify desired_state and transition_time_utc are present. |
| 41 for k in ('desired_state', 'transition_time_utc'): |
| 42 if not k in state: |
| 43 LOGGER.error( |
| 44 'one or more states for master %s do not contain %s', |
| 45 mastername, k) |
| 46 return False |
| 47 |
| 48 # Verify the desired state is in the allowed set. |
| 49 if (state['desired_state'] not in |
| 50 buildbot_state.STATES['desired_buildbot_state']): |
| 51 LOGGER.error( |
| 52 'desired_state \'%s\' is not one of %s', |
| 53 state['desired_state'], |
| 54 buildbot_state.STATES['desired_buildbot_state']) |
| 55 return False |
| 56 |
| 57 # Verify the timestamp is a number. |
| 58 if not isinstance(state['transition_time_utc'], (int, float)): |
| 59 LOGGER.error( |
| 60 'transition_time_utc \'%s\' is not an int or float', |
| 61 state['transition_time_utc']) |
| 62 return False |
| 63 |
| 64 # Verify the list is properly sorted. |
| 65 sorted_states = sorted( |
| 66 states, key=operator.itemgetter('transition_time_utc')) |
| 67 if sorted_states != states: |
| 68 LOGGER.error('master %s does not have states sorted by timestamp', |
| 69 mastername) |
| 70 LOGGER.error('should be:\n%s', json.dumps(sorted_states, indent=2)) |
| 71 return False |
| 72 |
| 73 # Verify there is at least one state in the past. |
| 74 if not get_master_state(states, now=now): |
| 75 LOGGER.error( |
| 76 'master %s does not have a state older than %s', mastername, now) |
| 77 return False |
| 78 |
| 79 return True |
| 80 |
| 81 |
| 82 def get_master_state(states, now=None): |
| 83 """Returns the latest state earlier than the current (or specified) time. |
| 84 |
| 85 If there are three items, each with transition times of 100, 200 and 300: |
| 86 * calling when 'now' is 50 will return None |
| 87 * calling when 'now' is 150 will return the first item |
| 88 * calling when 'now' is 400 will return the third item |
| 89 """ |
| 90 now = now or timestamp.utcnow_ts() |
| 91 |
| 92 times = [x['transition_time_utc'] for x in states] |
| 93 index = bisect.bisect_left(times, now) |
| 94 if index > 0: # An index of 0 means all timestamps are in the future. |
| 95 return states[index - 1] |
| 96 return None |
| 97 |
| 98 |
| 99 def get_masters_for_host(desired_state, build_dir, hostname): |
| 100 """Identify which masters on this host should be managed. |
| 101 |
| 102 Returns triggered_masters and ignored_masters (a list and a set respectively). |
| 103 |
| 104 triggered_masters are masters on this host which have a corresponding entry in |
| 105 the desired_master_state file. Any master running assigned to this host that |
| 106 does *not* have an entry in the desired_master_state file is considered |
| 107 'ignored.' |
| 108 |
| 109 triggered_masters is a list of dicts. Each dict is the full dict from |
| 110 mastermap.py with two extra keys: 'fulldir' (the absolute path to the master |
| 111 directory), and 'states' (a list of desired states sorted by transition time, |
| 112 pulled from the desired states file). |
| 113 |
| 114 ignored_masters is a set of 'dirname' strings (ex: master.chromium). |
| 115 """ |
| 116 triggered_masters = [] |
| 117 ignored_masters = set() |
| 118 for master_dict in master.get_mastermap_for_host( |
| 119 build_dir, hostname): |
| 120 if master_dict['dirname'] in desired_state: |
| 121 if master_dict['internal']: |
| 122 master_dir = os.path.abspath(os.path.join( |
| 123 build_dir, os.pardir, 'build_internal', 'masters', |
| 124 master_dict['dirname'])) |
| 125 else: |
| 126 master_dir = os.path.abspath(os.path.join( |
| 127 build_dir, 'masters', master_dict['dirname'])) |
| 128 master_dict['fulldir'] = master_dir |
| 129 master_dict['states'] = desired_state[master_dict['dirname']] |
| 130 |
| 131 triggered_masters.append(master_dict) |
| 132 else: |
| 133 ignored_masters.add(master_dict['dirname']) |
| 134 return triggered_masters, ignored_masters |
| OLD | NEW |