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

Side by Side 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 unified diff | 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 »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 # Copyright 2015 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
4
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.
5 """Get information about a buildbot master and manipulate it."""
6
7
8 import errno
9 import os
10 import json
11 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.
12 import re
13 import requests
14 import simplejson
15 import subprocess
16 import sys
17
18 from infra.libs.time_functions import timestamp
19
20 ######## Getting information about the master.
21
22
23 # 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.
24 def _pid_is_alive(pid): # pragma: no cover
25 """Determine if the given pid is still running."""
26 if sys.platform.startswith('win'):
27 raise NotImplementedError
28 if pid <= 0:
29 return False
30 try:
31 os.kill(pid, 0)
32 except OSError as err:
33 if err.errno == errno.ESRCH:
34 return False
35 elif err.errno == errno.EPERM:
36 return True
37 else:
38 raise
39 else:
40 return True
41
42
43 def buildbot_is_running(directory):
44 """Determine if the twisted.pid in a directory is running."""
45 pidfile = os.path.join(directory, 'twistd.pid')
46 if not os.path.exists(pidfile):
47 return False
48 with open(pidfile) as f:
49 pid = int(f.read().strip())
50 return _pid_is_alive(pid)
51
52
53 def _get_last_action(directory, action):
54 """Get the last time the given action was run in the actions.log."""
55 actions_log_file = os.path.join(directory, 'actions.log')
56 if not os.path.exists(actions_log_file):
57 return None
58 last_line = None
59 with open(actions_log_file) as f:
60 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
61 if action in line:
62 last_line = line
63 if not last_line:
64 return None
65
66 unparsed_date = last_line.split(action)[0][len('**'):]
67 return timestamp.utctimestamp(parse(unparsed_date))
68
69
70 def get_last_boot(directory):
71 """Determine the last time the master was started."""
72 return max(
73 _get_last_action(directory, 'make restart'),
74 _get_last_action(directory, 'make start'))
75
76
77 def get_last_no_new_builds(directory):
78 """Determine the last time a *current* make no-new-builds was called.
79
80 If a 'make start' or 'make restart' was issued after the no-new-builds, it is
81 not valid (since the make start or make restart nullified the no-new-builds).
82 """
83 last_boot = get_last_boot(directory)
84 last_no_new_builds = _get_last_action(directory, 'make no-new-builds')
85 if not last_no_new_builds:
86 return None
87 if last_boot > last_no_new_builds:
88 return None
89 return last_no_new_builds
90
91
92 def _get_mastermap_data(directory):
93 """Get mastermap JSON from a master directory."""
94
95 build_dir = os.path.join(directory, os.pardir, os.pardir, os.pardir, 'build')
96 runit = os.path.join(build_dir, 'scripts', 'tools', 'runit.py')
97 build_internal_dir = os.path.join(build_dir, os.pardir, 'build_internal')
98
99 script_path = os.path.join(build_dir, 'scripts', 'tools', 'mastermap.py')
100 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
101 script_path = os.path.join(
102 build_internal_dir, 'scripts', 'tools', 'mastermap_internal.py')
103
104 script_data = json.loads(subprocess.check_output(
105 [runit, script_path, '-f', 'json']))
106
107 short_dirname = os.path.basename(directory)
108 matches = [x for x in script_data if x.get('dirname') == short_dirname]
109 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.
110 if matches:
111 return matches[0]
112 return None
113
114
115 def _get_master_web_port(directory):
116 """Determine the web port of the master running in the given directory."""
117 mastermap_data = _get_mastermap_data(directory)
118 if mastermap_data:
119 return mastermap_data['port']
120 return None
121
122
123 def get_accepting_builds(directory, timeout=30):
124 """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.
125 port = _get_master_web_port(directory)
126 if port is not None:
127 try:
128 res = requests.get(
129 'http://localhost:%d/json/accepting_builds' % port,
130 timeout=timeout)
131 if res.status_code == 200:
132 try:
133 return res.json().get('accepting_builds')
134 except simplejson.scanner.JSONDecodeError:
135 pass
136 except requests.exceptions.Timeout:
137 pass
138 return None
139
140
141 ######## Performing actions on the master.
142
143
144 GclientSync, MakeStop, MakeWait, MakeStart, MakeNoNewBuilds = range(5)
145
146
147 def convert_action_items_to_cli(
148 action_items, directory, enable_gclient=False):
149
150 def cmd_dict(cmd, lockfile_prefix):
151 # Using the same lockfile prefix for two actions will make them mutually
152 # exclusive. So setting 'make' for all the make commands tries to prevent
153 # two make actions happening at once in the same master directory.
154 lockfile = '%s_%s' % (
155 lockfile_prefix,
156 re.sub('[^\w]', '_', directory.lower()))
157 return {
158 'cwd': directory,
159 'cmd': cmd,
160 'lockfile': lockfile,
161 }
162
163 for action_item in action_items:
164 if action_item == GclientSync:
165 if enable_gclient:
166 yield cmd_dict(
167 ['gclient', 'sync', '--reset', '--force', '--auto_rebase'],
168 'gclient')
169 elif action_item == MakeStop:
170 yield cmd_dict(['make', 'stop'], 'make')
171 elif action_item == MakeWait:
172 yield cmd_dict(['make', 'wait'], 'make')
173 elif action_item == MakeStart:
174 yield cmd_dict(['make', 'start'], 'make')
175 elif action_item == MakeNoNewBuilds:
176 yield cmd_dict(['make', 'no-new-builds'], 'make')
177 else:
178 raise ValueError('Invalid action item %s' % action_item)
OLDNEW
« 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