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

Side by Side Diff: scripts/launch_slaves.py

Issue 648353002: Remove Skia's forked buildbot code (Closed) Base URL: https://skia.googlesource.com/buildbot.git@master
Patch Set: Fix launch_slaves, remove more stuff Created 6 years, 2 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
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 6
7 """ Launch multiple buildbot slave instances on a single machine. This script 7 """ Launch multiple buildbot slave instances on a single machine. This script
8 is intended to be run at boot time. """ 8 is intended to be run at boot time. """
9 9
10 10
11 import multiprocessing 11 import multiprocessing
12 import os 12 import os
13 import shutil 13 import shutil
14 import socket 14 import socket
15 import subprocess 15 import subprocess
16 import sys 16 import sys
17 import time 17 import time
18 18
19 19
20 buildbot_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 20 buildbot_path = os.path.abspath(os.path.join(os.path.dirname(__file__),
21 os.pardir)) 21 os.pardir))
22 sys.path.append(os.path.join(buildbot_path, 'master'))
23 sys.path.append(os.path.join(buildbot_path, 'site_config')) 22 sys.path.append(os.path.join(buildbot_path, 'site_config'))
24 sys.path.append(os.path.join(buildbot_path, 'third_party', 'chromium_buildbot',
25 'scripts'))
26 sys.path.append(os.path.join(buildbot_path, 'third_party', 'chromium_buildbot',
27 'site_config'))
28 sys.path.append(os.path.join(buildbot_path, 'third_party', 'chromium_buildbot',
29 'third_party', 'buildbot_8_4p1'))
30 sys.path.append(os.path.join(buildbot_path, 'third_party', 'chromium_buildbot',
31 'third_party', 'jinja2'))
32 sys.path.append(os.path.join(buildbot_path, 'third_party', 'chromium_buildbot',
33 'third_party', 'twisted_8_1'))
34 23
35
36 import config
37 import slave_hosts_cfg 24 import slave_hosts_cfg
38 import slaves_cfg
39 25
40 26
41 CHROME_INTERNAL = 'https://chrome-internal.googlesource.com/' 27 CHROME_INTERNAL = 'https://chrome-internal.googlesource.com/'
42 CHROME_SLAVE_DEPS_URL = CHROME_INTERNAL + 'chrome/tools/build/slave.DEPS' 28 CHROME_SLAVE_DEPS_URL = CHROME_INTERNAL + 'chrome/tools/build/slave.DEPS'
43 CHROME_SLAVE_INTERNAL_DEPS_URL = ( 29 CHROME_SLAVE_INTERNAL_DEPS_URL = (
44 CHROME_INTERNAL + 'chrome/tools/build/internal.DEPS') 30 CHROME_INTERNAL + 'chrome/tools/build/internal.DEPS')
45 SKIA_URL = 'https://skia.googlesource.com/buildbot.git'
46 GCLIENT = 'gclient.bat' if os.name == 'nt' else 'gclient' 31 GCLIENT = 'gclient.bat' if os.name == 'nt' else 'gclient'
47 GIT = 'git.bat' if os.name == 'nt' else 'git'
48 NEW_MASTER_NAME = {
49 'Skia': 'Skia',
50 'AndroidSkia': 'SkiaAndroid',
51 'CompileSkia': 'SkiaCompile',
52 'FYISkia': 'SkiaFYI',
53 'PrivateSkia': 'SkiaInternal',
54 }
55 UPSTREAM_MASTER_PREFIX = 'client.skia'
56
57 32
58 # How often we should check each buildslave's keepalive conditions, in seconds. 33 # How often we should check each buildslave's keepalive conditions, in seconds.
59 DEFAULT_POLL_INTERVAL = 60 34 PID_FILE = os.path.join('build', 'slave', 'twistd.pid')
60 PID_FILE = os.path.join('buildbot', 'third_party', 'chromium_buildbot', 'slave', 35
61 'twistd.pid')
62 # Maximum time (in seconds) to wait for PID_FILE to be written after the slave 36 # Maximum time (in seconds) to wait for PID_FILE to be written after the slave
63 # is launched. If PID_FILE is not written by then, we assume an error occurred. 37 # is launched. If PID_FILE is not written by then, we assume an error occurred.
64 PID_TIMEOUT = 60.0 38 PID_TIMEOUT = 60.0
65 39
66 40
67
68 logger = None 41 logger = None
69 42
70 43
71 # TODO(borenet): Share this code with launch_master.py.
72 def IsRunning(pid): 44 def IsRunning(pid):
73 """ Determine whether a process with the given PID is running. 45 """ Determine whether a process with the given PID is running.
74 46
75 pid: string; the PID to test. If pid is None, return False. 47 pid: string; the PID to test. If pid is None, return False.
76 """ 48 """
77 if pid is None: 49 if pid is None:
78 return False 50 return False
79 if os.name == 'nt': 51 if os.name == 'nt':
80 cmd = ['tasklist', '/FI', '"PID eq %s"' % pid] 52 cmd = ['tasklist', '/FI', '"PID eq %s"' % pid]
81 output = subprocess.check_output(cmd, stdout=subprocess.PIPE, 53 output = subprocess.check_output(cmd, stdout=subprocess.PIPE,
82 stderr=subprocess.STDOUT) 54 stderr=subprocess.STDOUT)
83 is_running = pid in output 55 is_running = pid in output
84 else: 56 else:
85 cmd = ['cat', '/proc/%s/stat' % pid] 57 cmd = ['cat', '/proc/%s/stat' % pid]
86 is_running = subprocess.call(cmd, stdout=subprocess.PIPE, 58 is_running = subprocess.call(cmd, stdout=subprocess.PIPE,
87 stderr=subprocess.STDOUT) == 0 59 stderr=subprocess.STDOUT) == 0
88 return is_running 60 return is_running
89 61
90 62
63 def _IsRunning():
64 """Determine if the BuildSlave is running in CWD
65
66 If so, return its PID. Otherwise, return None.
67 """
68 if os.path.isfile(PID_FILE):
69 with open(PID_FILE) as f:
70 pid = str(f.read()).rstrip()
71 if IsRunning(pid):
72 return pid
73 return None
74
75
76 def _KillSlave():
77 """Kill the BuildSlave running in CWD."""
78 pid = _IsRunning()
79 if not pid:
80 print '_KillSlave: Slave not running.'
81 return
82 if os.name == 'nt':
83 cmd = ['taskkill', '/F', '/T', '/PID', str(pid)]
84 else:
85 cmd = ['make', 'stop']
86 subprocess.check_call(cmd, cwd=os.path.join('buildbot', 'slave'))
87
88
91 class BuildSlaveManager(multiprocessing.Process): 89 class BuildSlaveManager(multiprocessing.Process):
92 """ Manager process for BuildSlaves. Periodically checks that any 90 """Manager process for BuildSlaves."""
93 keepalive_conditions are met and kills or starts the slave accordingly. """
94 91
95 def __init__(self, slavename, checkout_path, copies, copy_src_dir, 92 def __init__(self, slavename, checkout_path, copies, copy_src_dir,
96 master_name, keepalive_conditions, poll_interval): 93 is_internal):
97 """ Construct the BuildSlaveManager. 94 """ Construct the BuildSlaveManager.
98 95
99 slavename: string; the name of the slave to start. 96 slavename: string; the name of the slave to start.
100 checkout_path: string; the directory in which to launch the slave. 97 checkout_path: string; the directory in which to launch the slave.
101 copies: list of dictionaries; files to copy into the slave's source 98 copies: list of dictionaries; files to copy into the slave's source
102 checkout. 99 checkout.
103 copy_src_dir: string; directory in which the files to copy reside. 100 copy_src_dir: string; directory in which the files to copy reside.
104 master_name: string; name of the master to which this build slave connects. 101 is_internal: bool; whether this buildslave uses internal code.
105 This is NOT the hostname of the master, which is obtained from the
106 master class in config_private.py.
107 keepalive_conditions: list; commands which must succeed in order for the
108 slave to stay alive.
109 poll_interval: number; how often to verify the keepalive_conditions, in
110 seconds.
111 """ 102 """
112 self._slavename = slavename 103 self._slavename = slavename
113 self._checkout_path = checkout_path 104 self._checkout_path = checkout_path
114 self._copies = copies 105 self._copies = copies
115 self._copy_src_dir = os.path.abspath(copy_src_dir) 106 self._copy_src_dir = os.path.abspath(copy_src_dir)
116 self._keepalive_conditions = keepalive_conditions 107 self._is_internal = is_internal
117 self._poll_interval = poll_interval
118 self._master_name = master_name
119 multiprocessing.Process.__init__(self) 108 multiprocessing.Process.__init__(self)
120 109
121 def _GClientConfig(self): 110 def _GClientConfig(self):
122 """Run 'gclient config'.""" 111 """Run 'gclient config'."""
123 subprocess.check_call([GCLIENT, 'config', SKIA_URL]) 112 config_url = (CHROME_SLAVE_INTERNAL_DEPS_URL if self._is_internal
113 else CHROME_SLAVE_DEPS_URL)
114 cmd = [GCLIENT, 'config', config_url, '--deps-file', '.DEPS.git']
115 print 'Running command: %s' % ' '.join(cmd)
116 subprocess.check_call(cmd)
124 117
125 def _SyncSources(self): 118 def _SyncSources(self):
126 """ Run 'gclient sync' on the buildbot sources. """ 119 """ Run 'gclient sync' on the buildbot sources. """
127 # Check out or update the buildbot scripts 120 # Check out or update the buildbot scripts
128 self._GClientConfig() 121 self._GClientConfig()
129 subprocess.check_call([GCLIENT, 'sync', '-j1', '--force']) 122 subprocess.check_call([GCLIENT, 'sync', '-j1', '--force'])
130 123
131 if os.name == 'nt': 124 if os.name == 'nt':
132 os.environ['WIN_TOOLS_FORCE'] = '1' 125 os.environ['WIN_TOOLS_FORCE'] = '1'
133 subprocess.check_call([self.local_gclient]) 126 subprocess.check_call([os.path.join(os.getcwd(), 'depot_tools', GCLIENT)])
134 del os.environ['WIN_TOOLS_FORCE'] 127 del os.environ['WIN_TOOLS_FORCE']
135 128
136 # Perform Copies 129 # Perform Copies
137 if self._copies: 130 if self._copies:
138 for copy in self._copies: 131 for copy in self._copies:
139 src = os.path.join(self._copy_src_dir, os.path.normpath(copy['source'])) 132 src = os.path.join(self._copy_src_dir, os.path.normpath(copy['source']))
140 dest = os.path.normpath(copy['destination']) 133 dest = os.path.normpath(copy['destination'])
141 print 'Copying %s to %s' % (src, dest) 134 print 'Copying %s to %s' % (src, dest)
142 shutil.copy(src, dest) 135 shutil.copy(src, dest)
143 136
144 @property
145 def master_host(self):
146 """Return the hostname of the master for this buildslave."""
147 return config.Master.set_active_master(self._master_name).master_host
148
149 @property
150 def slave_dir(self):
151 """Directory in which to launch the slave."""
152 return os.path.join('buildbot', 'slave')
153
154 @property
155 def local_gclient(self):
156 """Path to the local version of gclient within this slave's checkout."""
157 return os.path.join(os.getcwd(), 'buildbot', 'third_party', 'depot_tools',
158 GCLIENT)
159
160 def _LaunchSlave(self): 137 def _LaunchSlave(self):
161 """ Launch the BuildSlave. """ 138 """ Launch the BuildSlave. """
162 self._KillSlave() 139 _KillSlave()
163 140
164 self._SyncSources() 141 self._SyncSources()
165 142
166 if os.name == 'nt': 143 if os.name == 'nt':
167 # We run different commands for the Windows shell 144 # We run different commands for the Windows shell
168 cmd = 'setlocal&&' 145 cmd = 'setlocal&&'
169 cmd += 'set TESTING_SLAVENAME=%s&&' % self._slavename 146 cmd += 'set TESTING_SLAVENAME=%s&&' % self._slavename
170 cmd += 'set TESTING_MASTER=%s&&' % self._master_name
171 if self.master_host:
172 cmd += 'set TESTING_MASTER_HOST=%s&&' % self.master_host
173 cmd += 'run_slave.bat' 147 cmd += 'run_slave.bat'
174 cmd += '&& endlocal' 148 cmd += '&& endlocal'
175 else: 149 else:
176 cmd = 'TESTING_SLAVENAME=%s ' % self._slavename 150 cmd = 'TESTING_SLAVENAME=%s ' % self._slavename
177 cmd += 'TESTING_MASTER=%s ' % self._master_name
178 if self.master_host:
179 cmd += 'TESTING_MASTER_HOST=%s ' % self.master_host
180 cmd += 'make start' 151 cmd += 'make start'
181 print 'Running cmd: %s' % cmd 152 print 'Running cmd: %s' % cmd
182 subprocess.check_call(cmd, shell=True, cwd=self.slave_dir) 153 subprocess.check_call(cmd, shell=True, cwd=os.path.join('build', 'slave'))
183 154
184 start_time = time.time() 155 start_time = time.time()
185 while not self._IsRunning(): 156 while not _IsRunning():
186 if time.time() - start_time > PID_TIMEOUT: 157 if time.time() - start_time > PID_TIMEOUT:
187 raise Exception('Failed to launch %s' % self._slavename) 158 raise Exception('Failed to launch %s' % self._slavename)
188 time.sleep(1) 159 time.sleep(1)
189 160
190 def _IsRunning(self):
191 """ Determine if this BuildSlave is running. If so, return its PID,
192 otherwise, return None. """
193 if os.path.isfile(PID_FILE):
194 with open(PID_FILE) as f:
195 pid = str(f.read()).rstrip()
196 if IsRunning(pid):
197 return pid
198 return None
199
200 def _KillSlave(self):
201 """ Kill the BuildSlave. """
202 pid = self._IsRunning()
203 if not pid:
204 print 'BuildSlaveManager._KillSlave: Slave not running.'
205 return
206 if os.name == 'nt':
207 cmd = ['taskkill', '/F', '/T', '/PID', str(pid)]
208 else:
209 cmd = ['make', 'stop']
210 subprocess.check_call(cmd, cwd=os.path.join('buildbot', 'slave'))
211
212 def run(self): 161 def run(self):
213 """ Run the BuildSlaveManager. This overrides multiprocessing.Process's 162 """ Run the BuildSlaveManager. This overrides multiprocessing.Process's
214 run() method. """ 163 run() method. """
215 os.chdir(self._checkout_path) 164 os.chdir(self._checkout_path)
216 self._SyncSources() 165 self._SyncSources()
217 self._checkout_path = os.path.abspath(os.curdir) 166 self._checkout_path = os.path.abspath(os.curdir)
218 self._KillSlave() 167 _KillSlave()
219 while True: 168 self._LaunchSlave()
220 print 'Checking keepalive conditions for %s' % self._slavename 169 print 'Successfully launched slave %s.' % self._slavename
borenet 2014/10/17 18:04:18 We lose keepalive_conditions as part of this chang
rmistry 2014/10/17 18:19:21 Acknowledged.
221 slave_can_run = True
222 for keepalive_condition in self._keepalive_conditions:
223 print 'Executing keepalive condition: %s' % keepalive_condition
224 proc = subprocess.Popen(keepalive_condition, stdout=subprocess.PIPE,
225 stderr=subprocess.STDOUT)
226 if proc.wait() != 0:
227 print 'Keepalive condition failed for %s: %s' % (self._slavename,
228 keepalive_condition)
229 print proc.communicate()[0]
230 slave_can_run = False
231 break
232 print proc.communicate()[0]
233 if not slave_can_run and self._IsRunning():
234 self._KillSlave()
235 elif slave_can_run and not self._IsRunning():
236 self._LaunchSlave()
237 print 'Successfully launched slave %s.' % self._slavename
238 time.sleep(self._poll_interval)
239 print 'Slave process for %s has finished.' % self._slavename 170 print 'Slave process for %s has finished.' % self._slavename
240 171
241 172
242 class ChromeBuildSlaveManager(BuildSlaveManager):
243 """BuildSlaveManager for slaves using Chromium build code."""
244
245 def _GClientConfig(self):
246 """Run 'gclient config'."""
247 config_url = (
248 CHROME_SLAVE_INTERNAL_DEPS_URL if self._master_name == 'SkiaInternal'
249 else CHROME_SLAVE_DEPS_URL)
250 cmd = [GCLIENT, 'config', config_url, '--deps-file', '.DEPS.git']
251 print 'Running command: %s' % ' '.join(cmd)
252 subprocess.check_call(cmd)
253
254 @property
255 def master_host(self):
256 """Return the hostname of the master for this buildslave."""
257 return None # Just use the default.
258
259 @property
260 def slave_dir(self):
261 """Directory from which to launch the buildslave."""
262 return os.path.join('build', 'slave')
263
264 @property
265 def local_gclient(self):
266 """Path to the local version of gclient within this slave's checkout."""
267 return os.path.join(os.getcwd(), 'depot_tools', GCLIENT)
268
269
270 def ReadSlavesCfg(slaves_cfg_path): 173 def ReadSlavesCfg(slaves_cfg_path):
271 """Read the given slaves.cfg path and return the slaves dict.""" 174 """Read the given slaves.cfg path and return the slaves dict."""
272 cfg = {} 175 cfg = {}
273 execfile(slaves_cfg_path, cfg) 176 execfile(slaves_cfg_path, cfg)
274 return cfg['slaves'] 177 return cfg['slaves']
275 178
276 179
277 def RunSlave(slavename, slave_num, connects_to_new_master=False): 180 def RunSlave(slavename, slave_num, is_internal):
278 """ Launch a single slave, checking out the buildbot tree if necessary. 181 """ Launch a single slave, checking out the buildbot tree if necessary.
279 182
280 slavename: string indicating the hostname of the build slave to launch. 183 slavename: string indicating the hostname of the build slave to launch.
281 copies: dictionary with 'source' and 'destination' keys whose values are the 184 copies: dictionary with 'source' and 'destination' keys whose values are the
rmistry 2014/10/17 18:19:21 Can you replace copies with slave_num.
borenet 2014/10/17 18:39:26 Done.
282 current location and destination location within the buildbot checkout of 185 current location and destination location within the buildbot checkout of
283 files to be copied. 186 files to be copied.
187 is_internal: bool; whether this slave uses internal code.
284 """ 188 """
285 print 'Starting slave: %s%s' % ( 189 print 'Starting slave: %s' % slavename
286 slavename, ' (new)' if connects_to_new_master else '')
287 start_dir = os.path.realpath(os.curdir) 190 start_dir = os.path.realpath(os.curdir)
288 slave_dir = os.path.join(start_dir, slavename) 191 slave_dir = os.path.join(start_dir, slavename)
289 if connects_to_new_master and os.name == 'nt': 192 if os.name == 'nt':
290 slave_dir = os.path.join('c:\\', slave_num) 193 slave_dir = os.path.join('c:\\', slave_num)
291 copies = (slave_hosts_cfg.CHROMEBUILD_COPIES if connects_to_new_master 194 copies = slave_hosts_cfg.CHROMEBUILD_COPIES
292 else slave_hosts_cfg.DEFAULT_COPIES)
293 195
294 # Create the slave directory if needed 196 # Create the slave directory if needed
295 if not os.path.isdir(slave_dir): 197 if not os.path.isdir(slave_dir):
296 print 'Creating directory: %s' % slave_dir 198 print 'Creating directory: %s' % slave_dir
297 os.makedirs(slave_dir) 199 os.makedirs(slave_dir)
298 200
299 # Find the slave config dict and BuildSlaveManager type for this slave.
300 slave_cfg = {}
301 manager = (ChromeBuildSlaveManager if connects_to_new_master
302 else BuildSlaveManager)
303 for cfg in slaves_cfg.SLAVES:
304 if cfg['hostname'] == slavename:
305 slave_cfg = cfg
306 break
307 if not slave_cfg:
308 # Try looking at upstream masters.
309 upstream_masters_dir = os.path.join('third_party',
310 'chromium_buildbot_tot',
311 'masters')
312 for master_dir in os.listdir(upstream_masters_dir):
313 if master_dir.startswith('master.%s' % UPSTREAM_MASTER_PREFIX):
314 slaves_cfg_path = os.path.join(
315 upstream_masters_dir, master_dir, 'slaves.cfg')
316 for cfg in ReadSlavesCfg(slaves_cfg_path):
317 if cfg['hostname'] == slavename:
318 slave_cfg = cfg
319 break
320 if not slave_cfg:
321 raise Exception('No buildslave config found for %s!' % slavename)
322
323 # Launch the buildslave. 201 # Launch the buildslave.
324 master_name = slave_cfg['master'] 202 BuildSlaveManager(slavename, slave_dir, copies, os.pardir, is_internal
325 if connects_to_new_master: 203 ).start()
326 master_name = NEW_MASTER_NAME[master_name]
327 manager(slavename, slave_dir, copies, os.pardir, master_name,
328 slave_cfg.get('keepalive_conditions', []), DEFAULT_POLL_INTERVAL
329 ).start()
330 204
331 205
332 class FileLogger: 206 class FileLogger:
333 """ Write stdout to a log file. """ 207 """ Write stdout to a log file. """
334 def __init__(self, log_file_name): 208 def __init__(self, log_file_name):
335 # Open the log file. 209 # Open the log file.
336 self._logfile = open(log_file_name, 'w') 210 self._logfile = open(log_file_name, 'w')
337 self._stdout = sys.stdout 211 self._stdout = sys.stdout
338 sys.stdout = self 212 sys.stdout = self
339 213
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after
392 if os.name == 'nt': 266 if os.name == 'nt':
393 os.environ['HOME'] = os.path.join('c:\\', 'Users', 'chrome-bot') 267 os.environ['HOME'] = os.path.join('c:\\', 'Users', 'chrome-bot')
394 268
395 # Sync the buildbot code. 269 # Sync the buildbot code.
396 subprocess.check_call([GCLIENT, 'sync', '--force', '-j1']) 270 subprocess.check_call([GCLIENT, 'sync', '--force', '-j1'])
397 271
398 # Obtain configuration information about this build slave host machine. 272 # Obtain configuration information about this build slave host machine.
399 slave_host = slave_hosts_cfg.get_slave_host_config(socket.gethostname()) 273 slave_host = slave_hosts_cfg.get_slave_host_config(socket.gethostname())
400 slaves = slave_host.slaves 274 slaves = slave_host.slaves
401 print 'Attempting to launch build slaves:' 275 print 'Attempting to launch build slaves:'
402 for slavename, _, connects_to_new_master in slaves: 276 for slavename, _, _ in slaves:
403 print ' %s%s' % (slavename, 277 print ' %s' % slavename
404 (' (new master)' if connects_to_new_master else ''))
405 278
406 # Launch the build slaves 279 # Launch the build slaves
407 for slavename, slave_num, connects_to_new_master in slaves: 280 for slavename, slave_num, is_internal in slaves:
408 RunSlave(slavename, slave_num, connects_to_new_master) 281 RunSlave(slavename, slave_num, is_internal)
409 282
410 283
411 if '__main__' == __name__: 284 if '__main__' == __name__:
412 # Pipe all output to a log file. 285 # Pipe all output to a log file.
413 logfile = 'launch_slaves.log' 286 logfile = 'launch_slaves.log'
414 if os.path.isfile(logfile): 287 if os.path.isfile(logfile):
415 num = 1 288 num = 1
416 new_filename = logfile + '.' + str(num) 289 new_filename = logfile + '.' + str(num)
417 while os.path.isfile(new_filename): 290 while os.path.isfile(new_filename):
418 num += 1 291 num += 1
419 new_filename = logfile + '.' + str(num) 292 new_filename = logfile + '.' + str(num)
420 os.rename(logfile, new_filename) 293 os.rename(logfile, new_filename)
421 logger = FileLogger(logfile) 294 logger = FileLogger(logfile)
422 sys.exit(main()) 295 sys.exit(main())
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698