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

Side by Side Diff: infra/services/service_manager/service.py

Issue 1365583002: Have service_manager start a cloudtail attached to each service it starts. (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@master
Patch Set: Just use the default project ID Created 5 years, 3 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 # Copyright (c) 2015 The Chromium Authors. All rights reserved. 1 # Copyright (c) 2015 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 import errno 5 import errno
6 import json 6 import json
7 import logging 7 import logging
8 import os.path 8 import os.path
9 import platform 9 import platform
10 import signal 10 import signal
(...skipping 163 matching lines...) Expand 10 before | Expand all | Expand 10 after
174 """Controls a running service process. 174 """Controls a running service process.
175 175
176 Starts and stops the process, checks whether it is running, and creates and 176 Starts and stops the process, checks whether it is running, and creates and
177 deletes its state file in the state_directory. 177 deletes its state file in the state_directory.
178 178
179 State files are like UNIX PID files, except they also contain the starttime 179 State files are like UNIX PID files, except they also contain the starttime
180 (read from /proc/$PID/stat) of the process to protect against the race 180 (read from /proc/$PID/stat) of the process to protect against the race
181 condition of a different process reusing the same PID. 181 condition of a different process reusing the same PID.
182 """ 182 """
183 183
184 def __init__(self, state_directory, service_config, time_fn=time.time, 184 def __init__(self, state_directory, service_config, cloudtail_path,
185 sleep_fn=time.sleep): 185 time_fn=time.time, sleep_fn=time.sleep):
186 """ 186 """
187 Args: 187 Args:
188 state_directory: A file will be created in this directory (with the same 188 state_directory: A file will be created in this directory (with the same
189 name as the service) when it is running containing its PID and 189 name as the service) when it is running containing its PID and
190 starttime. 190 starttime.
191 service_config: A dictionary containing the service's config. See README 191 service_config: A dictionary containing the service's config. See README
192 for a description of the fields. 192 for a description of the fields.
193 cloudtail_path: Path to the cloudtail binary to use for logging, or None
194 if logging is disabled.
193 """ 195 """
194 196
195 self.config = service_config 197 self.config = service_config
196 self.name = service_config['name'] 198 self.name = service_config['name']
197 self.root_directory = service_config['root_directory'] 199 self.root_directory = service_config['root_directory']
198 self.tool = service_config['tool'] 200 self.tool = service_config['tool']
199 201
200 self.args = service_config.get('args', []) 202 self.args = service_config.get('args', [])
201 self.stop_time = int(service_config.get('stop_time', 10)) 203 self.stop_time = int(service_config.get('stop_time', 10))
202 204
205 if cloudtail_path is None:
206 self.cloudtail_args = None
207 else:
208 self.cloudtail_args = [
209 cloudtail_path, 'pipe',
210 '--log-id', self.name,
Vadim Sh. 2015/09/23 17:40:21 they have limit on number of these, as far as I un
dsansome 2015/09/24 04:34:13 Yep yep
211 '--local-log-level', 'info',
212 ]
213
203 self._state_file = os.path.join(state_directory, self.name) 214 self._state_file = os.path.join(state_directory, self.name)
204 self._time_fn = time_fn 215 self._time_fn = time_fn
205 self._sleep_fn = sleep_fn 216 self._sleep_fn = sleep_fn
206 217
207 self._process_creator = ProcessCreator.for_platform(self) 218 self._process_creator = ProcessCreator.for_platform(self)
208 219
209 def get_running_process_state(self): 220 def get_running_process_state(self):
210 """Returns a ProcessState object about about the process. 221 """Returns a ProcessState object about about the process.
211 222
212 Raises some subclass of ProcessStateError if the process is not running. 223 Raises some subclass of ProcessStateError if the process is not running.
(...skipping 119 matching lines...) Expand 10 before | Expand all | Expand 10 after
332 343
333 Makes the get_running_process_state() functionality available for the 344 Makes the get_running_process_state() functionality available for the
334 service_manager process. 345 service_manager process.
335 """ 346 """
336 347
337 def __init__(self, state_directory, root_directory, **kwargs): 348 def __init__(self, state_directory, root_directory, **kwargs):
338 super(OwnService, self).__init__(state_directory, { 349 super(OwnService, self).__init__(state_directory, {
339 'name': 'service_manager', 350 'name': 'service_manager',
340 'tool': '', 351 'tool': '',
341 'root_directory': root_directory, 352 'root_directory': root_directory,
342 }, **kwargs) 353 }, None, '', **kwargs)
343 self._state_directory = state_directory 354 self._state_directory = state_directory
344 355
345 def start(self): 356 def start(self):
346 """Writes a state file, returns False if we're already running.""" 357 """Writes a state file, returns False if we're already running."""
347 358
348 # Use a flock here to avoid a race condition where two service_managers 359 # Use a flock here to avoid a race condition where two service_managers
349 # start at the same time and both write their own state file. 360 # start at the same time and both write their own state file.
350 try: 361 try:
351 with daemon.flock('service_manager.flock', self._state_directory): 362 with daemon.flock('service_manager.flock', self._state_directory):
352 try: 363 try:
(...skipping 12 matching lines...) Expand all
365 raise NotImplementedError 376 raise NotImplementedError
366 377
367 378
368 class UnixProcessCreator(ProcessCreator): # pragma: no cover 379 class UnixProcessCreator(ProcessCreator): # pragma: no cover
369 """Implementation of ProcessCreator for Unix systems. 380 """Implementation of ProcessCreator for Unix systems.
370 381
371 Forks twice and sends the PID of the service over a pipe to the parent. 382 Forks twice and sends the PID of the service over a pipe to the parent.
372 """ 383 """
373 384
374 def start(self): 385 def start(self):
375 pipe_r, pipe_w = os.pipe() 386 control_r, control_w = os.pipe()
376 child_pid = os.fork() 387 child_pid = os.fork()
377 if child_pid == 0: 388 if child_pid == 0:
378 os.close(pipe_r) 389 os.close(control_r)
379 try: 390 try:
380 self._start_child(os.fdopen(pipe_w, 'w')) 391 self._start_child(os.fdopen(control_w, 'w'))
381 finally: 392 finally:
382 os._exit(1) 393 os._exit(1)
383 else: 394 else:
384 os.close(pipe_w) 395 os.close(control_w)
385 return self._start_parent(os.fdopen(pipe_r, 'r'), child_pid) 396 return self._start_parent(os.fdopen(control_r, 'r'), child_pid)
386 397
387 def _start_child(self, pipe): 398 def _start_child(self, control_fh):
388 """The part of start() that runs in the child process. 399 """The part of start() that runs in the child process.
389 400
390 Daemonises the current process, writes the new PID to the pipe, and execs 401 Daemonises the current process, writes the new PID to the pipe, and execs
391 the service executable. 402 the service executable.
392 """ 403 """
393 404
394 # Detach from the parent and close all FDs except that of the pipe. 405 # Detach from the parent but keep FDs open so we can write to the log still.
395 daemon.become_daemon(keep_fds={pipe.fileno()}) 406 daemon.become_daemon(keep_fds=True)
396 407
397 # Write our new PID to the pipe and close it. 408 # Write our new PID to the pipe and close it.
398 json.dump({'pid': os.getpid()}, pipe) 409 json.dump({'pid': os.getpid()}, control_fh)
399 pipe.close() 410 control_fh.close()
411
412 # Start cloudtail. This needs to be done after become_daemon so it's a
413 # child of the service itself, not service_manager.
414 # Cloudtail will inherit service_manager's stdout and stderr, so its errors
415 # will go to service_manager's log.
416 if self.service.cloudtail_args is not None:
417 log_r, log_w = os.pipe()
418 try:
419 handle = subprocess.Popen(
420 self.service.cloudtail_args, stdin=log_r, close_fds=True)
421 except OSError:
422 log_w = None
423 logging.exception('Failed to start cloudtail with args %s',
424 self.service.cloudtail_args)
425 else:
426 logging.info('Started cloudtail for %s with PID %d',
427 self.service.name, handle.pid)
428 else:
429 log_w = None
430
431 # Pipe our stdout and stderr to cloudtail if configured. Close all other
432 # FDs.
433 if log_w is not None:
434 os.dup2(log_w, 1)
435 os.dup2(log_w, 2)
436 daemon.close_all_fds(keep_fds={1, 2})
437 else:
438 daemon.close_all_fds(keep_fds=None)
400 439
401 # Exec the service. 440 # Exec the service.
402 runpy = os.path.join(self.service.root_directory, 'run.py') 441 runpy = os.path.join(self.service.root_directory, 'run.py')
403 os.execv(runpy, [runpy, self.service.tool] + self.service.args) 442 os.execv(runpy, [runpy, self.service.tool] + self.service.args)
404 443
405 def _start_parent(self, pipe, child_pid): 444 def _start_parent(self, pipe, child_pid):
406 """The part of start() that runs in the parent process. 445 """The part of start() that runs in the parent process.
407 446
408 Waits for the child to start and writes a state file for the process. 447 Waits for the child to start and writes a state file for the process.
409 """ 448 """
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after
448 stdout=null_fh, 487 stdout=null_fh,
449 args=[ 488 args=[
450 os.path.join(self.service.root_directory, 489 os.path.join(self.service.root_directory,
451 'ENV/Scripts/python.exe'), 490 'ENV/Scripts/python.exe'),
452 os.path.join(self.service.root_directory, 'run.py'), 491 os.path.join(self.service.root_directory, 'run.py'),
453 self.service.tool, 492 self.service.tool,
454 ] + self.service.args, 493 ] + self.service.args,
455 ) 494 )
456 495
457 return handle.pid 496 return handle.pid
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698