Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(578)

Unified Diff: infra/tools/master_manager_launcher/desired_state_parser.py

Issue 1128783003: Add master_manager_launch script which launches master_manager scripts for each master on a host. (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@master
Patch Set: Add coverage. Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: infra/tools/master_manager_launcher/desired_state_parser.py
diff --git a/infra/tools/master_manager_launcher/desired_state_parser.py b/infra/tools/master_manager_launcher/desired_state_parser.py
new file mode 100644
index 0000000000000000000000000000000000000000..2230be9225224834808d275398496a8a3cf0299f
--- /dev/null
+++ b/infra/tools/master_manager_launcher/desired_state_parser.py
@@ -0,0 +1,122 @@
+#!/usr/bin/python
+# Copyright 2015 Google Inc. All Rights Reserved.
+# pylint: disable=F0401
+
+"""Parse, validate and query the desired master state json."""
+
+import bisect
+import json
+import logging
+import operator
+import os
+
+from infra.libs.buildbot import master
+from infra.libs.time_functions import timestamp
+from infra.services.master_lifecycle import buildbot_state
+
+
+LOGGER = logging.getLogger(__name__)
+
+
+def load_desired_state_file(filename): # pragma: no cover
agable 2015/05/06 23:29:30 Feels odd. Not finding the file is exception-worth
ghost stip (do not use) 2015/05/07 06:50:57 Done.
+ with open(filename) as f:
+ desired_state = json.load(f)
+ if not desired_master_state_is_valid(desired_state):
+ return None
+ return desired_state
+
+
+def desired_master_state_is_valid(desired_state):
+ """Verify that the desired_master_state file is valid."""
+ now = timestamp.utcnow_ts()
+
+ for mastername, states in desired_state.iteritems():
+ # Verify desired_state and transition_time_utc are present.
+ for k in ('desired_state', 'transition_time_utc'):
+ if not all(k in state for state in states):
+ LOGGER.error(
+ 'one or more states for master %s do not contain %s', mastername, k)
+ return False
+
+ # Verify the list is properly sorted.
+ sorted_states = sorted(
agable 2015/05/06 23:29:30 Why does it matter if they're sorted in the file w
ghost stip (do not use) 2015/05/07 06:50:57 Because a human editing / reading the file would b
agable 2015/05/07 17:59:31 Fair enough, that makes sense.
+ states, key=operator.itemgetter('transition_time_utc'))
+ if sorted_states != states:
+ LOGGER.error('master %s does not have states sorted by timestamp',
+ mastername)
+ LOGGER.error('should be:\n%s', json.dumps(sorted_states, indent=2))
+ return False
+
+ # Verify desired_state and timestamp are valid.
+ for state in states:
+ if (state['desired_state'] not in
+ buildbot_state.STATES['desired_buildbot_state']):
+ LOGGER.error(
+ 'desired_state \'%s\' is not one of %s',
+ state['desired_state'],
+ buildbot_state.STATES['desired_buildbot_state'])
+ return False
+
+ if not isinstance(state['transition_time_utc'], (int, float)):
+ LOGGER.error(
+ 'transition_time_utc \'%s\' is not an int or float',
+ state['transition_time_utc'])
+ return False
+
+ # Verify there is at least one state in the past.
+ if not get_master_state(states, now=now):
agable 2015/05/06 23:29:30 Why? Why can't I create the file containing just a
iannucci 2015/05/06 23:48:05 Because then the script doesn't know what state it
ghost stip (do not use) 2015/05/07 06:50:57 Right, this is a discussion Robbie and I had in mo
agable 2015/05/07 17:59:31 I don't see how my proposal goes against your disc
ghost stip (do not use) 2015/05/07 19:49:39 There is only one case where a person who wants to
agable 2015/05/08 00:14:53 Case 1.5: The file used to contain a bunch of entr
ghost stip (do not use) 2015/05/08 00:22:13 You can't clean it out for readability fully, beca
+ LOGGER.error(
+ 'master %s does not have a state older than %s', mastername, now)
+ return False
+
+ return True
+
+
+def get_master_state(states, now=None):
+ """Returns the latest state earlier than the current (or specified) time.
+
+ If there are three items, each with transition times of 100, 200 and 300:
+ * calling when 'now' is 50 will return None
+ * calling when 'now' is 150 will return the first item
+ * calling when 'now' is 400 will return the third item
+ """
+ now = now or timestamp.utcnow_ts()
+
+ times = [x['transition_time_utc'] for x in states]
+ index = bisect.bisect_left(times, now)
+ if index:
+ return states[index - 1]
+ return None
+
+
+def get_masters_for_host(desired_state, build_dir, hostname):
+ """Identify which masters on this host should be managed.
+
+ Returns two lists: triggered_masters and ignored_masters.
agable 2015/05/06 23:29:30 Any reason they're sets instead of lists? Ordering
ghost stip (do not use) 2015/05/07 06:50:57 TypeError: unhashable type: 'dict' ignored_master
+
+ triggered_masters is a list of dicts. Each dict is the full dict from
+ mastermap.py with two extra keys: 'fulldir' (the absolute path to the master
+ directory), and 'states' (a list of desired states sorted by transition time,
+ pulled from the desired states file).
+
+ ignored_masters is merely a list of 'dirname' strings (ex: master.chromium).
agable 2015/05/06 23:29:30 Docstring should explain why they're ignored
ghost stip (do not use) 2015/05/07 06:50:57 Done.
+ """
+ triggered_masters = []
+ ignored_masters = []
+ for master_dict in master.get_mastermap_for_host(
+ build_dir, hostname):
+ if master_dict['dirname'] in desired_state:
+ if master_dict['internal']:
+ master_dir = os.path.abspath(os.path.join(
+ build_dir, os.pardir, 'build_internal', 'masters',
+ master_dict['dirname']))
+ else:
+ master_dir = os.path.abspath(os.path.join(
+ build_dir, 'masters', master_dict['dirname']))
+ master_dict['fulldir'] = master_dir
+ master_dict['states'] = desired_state[master_dict['dirname']]
+
+ triggered_masters.append(master_dict)
+ else:
+ ignored_masters.append(master_dict['dirname'])
+ return triggered_masters, ignored_masters

Powered by Google App Engine
This is Rietveld 408576698