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

Unified Diff: infra/libs/buildbot/master.py

Issue 1097233002: Add libs to get information about or manipulate a buildbot master. (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@timebrug
Patch Set: Add tests. Created 5 years, 8 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
« no previous file with comments | « infra/libs/buildbot/__init__.py ('k') | infra/libs/buildbot/test/__init__.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: infra/libs/buildbot/master.py
diff --git a/infra/libs/buildbot/master.py b/infra/libs/buildbot/master.py
new file mode 100644
index 0000000000000000000000000000000000000000..2655f9a4f711f1b2fd7fafcc3dff0f2d584f450e
--- /dev/null
+++ b/infra/libs/buildbot/master.py
@@ -0,0 +1,178 @@
+# Copyright 2015 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
agable 2015/04/27 20:01:45 Since the only file in this directory is called ma
ghost stip (do not use) 2015/04/27 23:42:18 Done.
+"""Get information about a buildbot master and manipulate it."""
+
+
+import errno
+import os
+import json
+from dateutil.parser import parse
agable 2015/04/27 20:01:45 nit: "from" import statements come in a separate b
ghost stip (do not use) 2015/04/27 23:42:18 Done.
+import re
+import requests
+import simplejson
+import subprocess
+import sys
+
+from infra.libs.time_functions import timestamp
+
+######## Getting information about the master.
+
+
+# Derived from http://stackoverflow.com/a/6940314.
agable 2015/04/27 20:01:45 Include in the comment here or in the docstring th
ghost stip (do not use) 2015/04/27 23:42:17 Done.
+def _pid_is_alive(pid): # pragma: no cover
+ """Determine if the given pid is still running."""
+ if sys.platform.startswith('win'):
+ raise NotImplementedError
+ if pid <= 0:
+ return False
+ try:
+ os.kill(pid, 0)
+ except OSError as err:
+ if err.errno == errno.ESRCH:
+ return False
+ elif err.errno == errno.EPERM:
+ return True
+ else:
+ raise
+ else:
+ return True
+
+
+def buildbot_is_running(directory):
+ """Determine if the twisted.pid in a directory is running."""
+ pidfile = os.path.join(directory, 'twistd.pid')
+ if not os.path.exists(pidfile):
+ return False
+ with open(pidfile) as f:
+ pid = int(f.read().strip())
+ return _pid_is_alive(pid)
+
+
+def _get_last_action(directory, action):
+ """Get the last time the given action was run in the actions.log."""
+ actions_log_file = os.path.join(directory, 'actions.log')
+ if not os.path.exists(actions_log_file):
+ return None
+ last_line = None
+ with open(actions_log_file) as f:
+ for line in f:
agable 2015/04/27 20:01:45 Read the file backwards and search for the first l
ghost stip (do not use) 2015/04/27 23:42:18 it's way cleaner now. nice
+ if action in line:
+ last_line = line
+ if not last_line:
+ return None
+
+ unparsed_date = last_line.split(action)[0][len('**'):]
+ return timestamp.utctimestamp(parse(unparsed_date))
+
+
+def get_last_boot(directory):
+ """Determine the last time the master was started."""
+ return max(
+ _get_last_action(directory, 'make restart'),
+ _get_last_action(directory, 'make start'))
+
+
+def get_last_no_new_builds(directory):
+ """Determine the last time a *current* make no-new-builds was called.
+
+ If a 'make start' or 'make restart' was issued after the no-new-builds, it is
+ not valid (since the make start or make restart nullified the no-new-builds).
+ """
+ last_boot = get_last_boot(directory)
+ last_no_new_builds = _get_last_action(directory, 'make no-new-builds')
+ if not last_no_new_builds:
+ return None
+ if last_boot > last_no_new_builds:
+ return None
+ return last_no_new_builds
+
+
+def _get_mastermap_data(directory):
+ """Get mastermap JSON from a master directory."""
+
+ build_dir = os.path.join(directory, os.pardir, os.pardir, os.pardir, 'build')
+ runit = os.path.join(build_dir, 'scripts', 'tools', 'runit.py')
+ build_internal_dir = os.path.join(build_dir, os.pardir, 'build_internal')
+
+ script_path = os.path.join(build_dir, 'scripts', 'tools', 'mastermap.py')
+ if os.path.exists(build_internal_dir):
agable 2015/04/27 20:01:45 I'm a little leery about infra.git knowing about b
ghost stip (do not use) 2015/04/27 23:42:18 yeah. I actually with mastermap.py would just auto
+ script_path = os.path.join(
+ build_internal_dir, 'scripts', 'tools', 'mastermap_internal.py')
+
+ script_data = json.loads(subprocess.check_output(
+ [runit, script_path, '-f', 'json']))
+
+ short_dirname = os.path.basename(directory)
+ matches = [x for x in script_data if x.get('dirname') == short_dirname]
+ assert len(matches) < 2
agable 2015/04/27 20:01:45 Add an error message, plain asserts in libraries m
ghost stip (do not use) 2015/04/27 23:42:18 Done.
+ if matches:
+ return matches[0]
+ return None
+
+
+def _get_master_web_port(directory):
+ """Determine the web port of the master running in the given directory."""
+ mastermap_data = _get_mastermap_data(directory)
+ if mastermap_data:
+ return mastermap_data['port']
+ return None
+
+
+def get_accepting_builds(directory, timeout=30):
+ """Determine whether the master is accepting new builds or not."""
agable 2015/04/27 20:01:45 Note in big huge words that this only works for ma
ghost stip (do not use) 2015/04/27 23:42:18 Done.
+ port = _get_master_web_port(directory)
+ if port is not None:
+ try:
+ res = requests.get(
+ 'http://localhost:%d/json/accepting_builds' % port,
+ timeout=timeout)
+ if res.status_code == 200:
+ try:
+ return res.json().get('accepting_builds')
+ except simplejson.scanner.JSONDecodeError:
+ pass
+ except requests.exceptions.Timeout:
+ pass
+ return None
+
+
+######## Performing actions on the master.
+
+
+GclientSync, MakeStop, MakeWait, MakeStart, MakeNoNewBuilds = range(5)
+
+
+def convert_action_items_to_cli(
+ action_items, directory, enable_gclient=False):
+
+ def cmd_dict(cmd, lockfile_prefix):
+ # Using the same lockfile prefix for two actions will make them mutually
+ # exclusive. So setting 'make' for all the make commands tries to prevent
+ # two make actions happening at once in the same master directory.
+ lockfile = '%s_%s' % (
+ lockfile_prefix,
+ re.sub('[^\w]', '_', directory.lower()))
+ return {
+ 'cwd': directory,
+ 'cmd': cmd,
+ 'lockfile': lockfile,
+ }
+
+ for action_item in action_items:
+ if action_item == GclientSync:
+ if enable_gclient:
+ yield cmd_dict(
+ ['gclient', 'sync', '--reset', '--force', '--auto_rebase'],
+ 'gclient')
+ elif action_item == MakeStop:
+ yield cmd_dict(['make', 'stop'], 'make')
+ elif action_item == MakeWait:
+ yield cmd_dict(['make', 'wait'], 'make')
+ elif action_item == MakeStart:
+ yield cmd_dict(['make', 'start'], 'make')
+ elif action_item == MakeNoNewBuilds:
+ yield cmd_dict(['make', 'no-new-builds'], 'make')
+ else:
+ raise ValueError('Invalid action item %s' % action_item)
« no previous file with comments | « infra/libs/buildbot/__init__.py ('k') | infra/libs/buildbot/test/__init__.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698