blob: e1144494ea2d85749c926bc78603716cfac57867 [file] [log] [blame] [edit]
#!/usr/bin/env python
# Copyright 2020 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.
import errno
import os
import signal
import subprocess
import time
def start_cloudtail(args):
"""Write process id of started cloudtail to file object.
Args:
args: any type so long as it provides these fields:
* cloudtail_path
* cloudtail_project_id
* cloudtail_log_id
* cloudtail_log_path
* pid_file
* cloudtail_service_account_json (optional)
"""
kwargs = {}
if subprocess.mswindows:
kwargs['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP
cloudtail_cmd = [
args.cloudtail_path,
'tail',
'--project-id',
args.cloudtail_project_id,
'--log-id',
args.cloudtail_log_id,
'--path',
args.cloudtail_log_path,
]
if args.cloudtail_service_account_json:
cloudtail_cmd.extend([
'--service-account-json',
args.cloudtail_service_account_json,
])
proc = subprocess.Popen(cloudtail_cmd, **kwargs)
with open(args.pid_file, 'w') as f:
pidstr = str(proc.pid)
f.write(pidstr)
print('cloudtail started pid=%s' % pidstr)
def is_running_posix(pid):
"""Returns True if process of pid is running.
Args:
pid(int): pid of process which this function checks
whether it is running or not.
Returns:
bool: True if process of pid is running.
Raises:
OSError if something happens in os.kill(pid, 0)
"""
try:
os.kill(pid, 0)
except OSError as e:
if e.errno == errno.ESRCH or e.errno == errno.EPERM:
return False
raise e
return True
class NotDiedError(Exception):
def __str__(self):
return "NotDiedError"
class Error(Exception):
"""Raised on something unexpected happens."""
def wait_termination_win(pid):
"""Send CTRL_C_EVENT or SIGINT to pid and wait termination of pid.
Args:
pid(int): pid of process which this function waits termination.
Raises:
Error: WaitForSingleObject to wait process termination returns neigher of
WAIT_TIMEOUT or WAIT_OBJECT_0 (i.e. termination succeeded).
NotDiedError: if cloudtail kept on running 10 seconds after it signaled.
"""
import win32api
import win32con
import win32event
import winerror
import pywintypes
handle = None
try:
handle = win32api.OpenProcess(
win32con.PROCESS_QUERY_INFORMATION | win32con.SYNCHRONIZE, False, pid
)
try:
os.kill(pid, signal.CTRL_C_EVENT)
print(
'CTRL_C_EVENT has been sent to process %d. '
'Going to wait for the process finishes.' % pid
)
except WindowsError as e: # pylint: disable=E0602
# If a target process does not share terminal, we cannot send Ctrl-C.
if e[0] == winerror.ERROR_INVALID_PARAMETER:
os.kill(pid, signal.SIGINT)
print('SIGINT has been sent to process %d.' % pid)
ret = win32event.WaitForSingleObject(handle, 10 * 10**3)
if ret == win32event.WAIT_TIMEOUT:
print('process %d running more than 10 seconds' % pid)
raise NotDiedError()
elif ret == win32event.WAIT_OBJECT_0:
return
raise Error('Unexpected return code %d for pid %d.' % (ret, pid))
except pywintypes.error as e:
if e[0] == winerror.ERROR_INVALID_PARAMETER and e[1] == 'OpenProcess':
print('Can\'t open process %d. Already dead? error %s.' % (pid, e))
return
raise
except OSError as e:
if e.errno in (errno.ECHILD, errno.EPERM, errno.ESRCH):
print(
'Can\'t send SIGINT to process %d. Already dead? Errno %d.' %
(pid, e.errno)
)
return
raise
finally:
if handle:
win32api.CloseHandle(handle)
def wait_termination(pid):
"""Send SIGINT to pid and wait termination of pid.
Args:
pid(int): pid of process which this function waits termination.
Raises:
OSError: is_running_posix, os.waitpid and os.kill may throw OSError.
NotDiedError: if cloudtail is running after 10 seconds waiting,
NotDiedError is raised.
"""
if os.name == 'nt':
wait_termination_win(pid)
else:
try:
os.kill(pid, signal.SIGINT)
except OSError as e:
if e.errno in (errno.ECHILD, errno.EPERM, errno.ESRCH):
print(
'Can\'t send SIGINT to process %d. Already dead? Errno %d.' %
(pid, e.errno)
)
return
raise
for _ in xrange(10):
time.sleep(1)
if not is_running_posix(pid):
return
print('process %d running more than 10 seconds' % pid)
raise NotDiedError()
def stop_cloudtail(args):
"""Stops the cloudtail process identified in the PID file
Args:
args: any type so long as it provides these fields:
* killed_pid_file
"""
with open(args.killed_pid_file) as f:
# cloudtail flushes log and terminates
# within 5 seconds when it recieves SIGINT.
pid = int(f.read())
try:
wait_termination(pid)
except Exception as e:
print('Going to send SIGTERM to process %d due to Error %s' % (pid, e))
# Since Windows does not have SIGKILL, we need to use SIGTERM.
try:
os.kill(pid, signal.SIGTERM)
except OSError as e:
print('Failed to send SIGTERM to process %d: %s' % (pid, e))
# We do not reraise because I believe not suspending the process
# is more important than completely killing cloudtail.