oslo.service/oslo_service/eventlet_backdoor.py

245 lines
7.6 KiB
Python

# Copyright (c) 2012 OpenStack Foundation.
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from __future__ import print_function
import errno
import gc
import logging
import os
import pprint
import sys
import traceback
import eventlet.backdoor
import greenlet
from eventlet.green import socket
from oslo_service._i18n import _
from oslo_service import _options
LOG = logging.getLogger(__name__)
class EventletBackdoorConfigValueError(Exception):
def __init__(self, port_range, help_msg, ex):
msg = (_('Invalid backdoor_port configuration %(range)s: %(ex)s. '
'%(help)s') %
{'range': port_range, 'ex': ex, 'help': help_msg})
super(EventletBackdoorConfigValueError, self).__init__(msg)
self.port_range = port_range
def _dont_use_this():
print("Don't use this, just disconnect instead")
def _dump_frame(f, frame_chapter):
co = f.f_code
print(" %s Frame: %s" % (frame_chapter, co.co_name))
print(" File: %s" % (co.co_filename))
print(" Captured at line number: %s" % (f.f_lineno))
co_locals = set(co.co_varnames)
if len(co_locals):
not_set = co_locals.copy()
set_locals = {}
for var_name in f.f_locals.keys():
if var_name in co_locals:
set_locals[var_name] = f.f_locals[var_name]
not_set.discard(var_name)
if set_locals:
print(" %s set local variables:" % (len(set_locals)))
for var_name in sorted(set_locals.keys()):
print(" %s => %r" % (var_name, f.f_locals[var_name]))
else:
print(" 0 set local variables.")
if not_set:
print(" %s not set local variables:" % (len(not_set)))
for var_name in sorted(not_set):
print(" %s" % (var_name))
else:
print(" 0 not set local variables.")
else:
print(" 0 Local variables.")
def _detailed_dump_frames(f, thread_index):
i = 0
while f is not None:
_dump_frame(f, "%s.%s" % (thread_index, i + 1))
f = f.f_back
i += 1
def _find_objects(t):
return [o for o in gc.get_objects() if isinstance(o, t)]
def _print_greenthreads(simple=True):
for i, gt in enumerate(_find_objects(greenlet.greenlet)):
print(i, gt)
if simple:
traceback.print_stack(gt.gr_frame)
else:
_detailed_dump_frames(gt.gr_frame, i)
print()
def _print_nativethreads():
for threadId, stack in sys._current_frames().items():
print(threadId)
traceback.print_stack(stack)
print()
def _parse_port_range(port_range):
if ':' not in port_range:
start, end = port_range, port_range
else:
start, end = port_range.split(':', 1)
try:
start, end = int(start), int(end)
if end < start:
raise ValueError
return start, end
except ValueError as ex:
raise EventletBackdoorConfigValueError(
port_range, ex, _options.help_for_backdoor_port)
def _listen_func(host, port):
# eventlet is setting SO_REUSEPORT by default from v0.20.
# But we can configure it by passing reuse_port argument
# from v0.22
try:
return eventlet.listen((host, port), reuse_port=False)
except TypeError:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind((host, port))
sock.listen(50)
return sock
def _listen(host, start_port, end_port):
try_port = start_port
while True:
try:
return _listen_func(host, try_port)
except socket.error as exc:
if (exc.errno != errno.EADDRINUSE or
try_port >= end_port):
raise
try_port += 1
def _try_open_unix_domain_socket(socket_path):
try:
return eventlet.listen(socket_path, socket.AF_UNIX)
except socket.error as e:
if e.errno != errno.EADDRINUSE:
# NOTE(harlowja): Some other non-address in use error
# occurred, since we aren't handling those, re-raise
# and give up...
raise
else:
# Attempt to remove the file before opening it again.
try:
os.unlink(socket_path)
except OSError as e:
if e.errno != errno.ENOENT:
# NOTE(harlowja): File existed, but we couldn't
# delete it, give up...
raise
return eventlet.listen(socket_path, socket.AF_UNIX)
def _initialize_if_enabled(conf):
conf.register_opts(_options.eventlet_backdoor_opts)
backdoor_locals = {
'exit': _dont_use_this, # So we don't exit the entire process
'quit': _dont_use_this, # So we don't exit the entire process
'fo': _find_objects,
'pgt': _print_greenthreads,
'pnt': _print_nativethreads,
}
if conf.backdoor_port is None and conf.backdoor_socket is None:
return None
if conf.backdoor_socket is None:
start_port, end_port = _parse_port_range(str(conf.backdoor_port))
sock = _listen('localhost', start_port, end_port)
# In the case of backdoor port being zero, a port number is assigned by
# listen(). In any case, pull the port number out here.
where_running = sock.getsockname()[1]
else:
sock = _try_open_unix_domain_socket(conf.backdoor_socket)
where_running = conf.backdoor_socket
# NOTE(johannes): The standard sys.displayhook will print the value of
# the last expression and set it to __builtin__._, which overwrites
# the __builtin__._ that gettext sets. Let's switch to using pprint
# since it won't interact poorly with gettext, and it's easier to
# read the output too.
def displayhook(val):
if val is not None:
pprint.pprint(val)
sys.displayhook = displayhook
LOG.info(
'Eventlet backdoor listening on %(where_running)s for'
' process %(pid)d',
{'where_running': where_running, 'pid': os.getpid()}
)
thread = eventlet.spawn(eventlet.backdoor.backdoor_server, sock,
locals=backdoor_locals)
return (where_running, thread)
def initialize_if_enabled(conf):
where_running_thread = _initialize_if_enabled(conf)
if not where_running_thread:
return None
else:
where_running, _thread = where_running_thread
return where_running
def _main():
import eventlet
eventlet.monkey_patch(all=True)
from oslo_config import cfg
logging.basicConfig(level=logging.DEBUG)
conf = cfg.ConfigOpts()
conf.register_cli_opts(_options.eventlet_backdoor_opts)
conf(sys.argv[1:])
where_running_thread = _initialize_if_enabled(conf)
if not where_running_thread:
raise RuntimeError(_("Did not create backdoor at requested location"))
else:
_where_running, thread = where_running_thread
thread.wait()
if __name__ == '__main__':
# simple CLI for testing
_main()