nodepool/nodepool/cmd/__init__.py

250 lines
8.0 KiB
Python

# Copyright 2012 Hewlett-Packard Development Company, L.P.
# Copyright 2013 OpenStack Foundation
#
# 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.
import argparse
import daemon
import errno
import extras
import io
import logging
import logging.config
import os
import signal
import sys
import threading
import traceback
from nodepool.version import version_info as npd_version_info
from nodepool import logconfig
yappi = extras.try_import('yappi')
objgraph = extras.try_import('objgraph')
# as of python-daemon 1.6 it doesn't bundle pidlockfile anymore
# instead it depends on lockfile-0.9.1 which uses pidfile.
pid_file_module = extras.try_imports(['daemon.pidlockfile', 'daemon.pidfile'])
def is_pidfile_stale(pidfile):
""" Determine whether a PID file is stale.
Return 'True' ("stale") if the contents of the PID file are
valid but do not match the PID of a currently-running process;
otherwise return 'False'.
"""
result = False
pidfile_pid = pidfile.read_pid()
if pidfile_pid is not None:
try:
os.kill(pidfile_pid, 0)
except OSError as exc:
if exc.errno == errno.ESRCH:
# The specified PID does not exist
result = True
return result
def stack_dump_handler(signum, frame):
signal.signal(signal.SIGUSR2, signal.SIG_IGN)
log = logging.getLogger("nodepool.stack_dump")
log.debug("Beginning debug handler")
try:
threads = {}
for t in threading.enumerate():
threads[t.ident] = t
log_str = ""
for thread_id, stack_frame in sys._current_frames().items():
thread = threads.get(thread_id)
if thread:
thread_name = thread.name
thread_is_daemon = str(thread.daemon)
else:
thread_name = '(Unknown)'
thread_is_daemon = '(Unknown)'
log_str += "Thread: %s %s d: %s\n"\
% (thread_id, thread_name, thread_is_daemon)
log_str += "".join(traceback.format_stack(stack_frame))
log.debug(log_str)
except Exception:
log.exception("Thread dump error:")
try:
if yappi:
if not yappi.is_running():
log.debug("Starting Yappi")
yappi.start()
else:
log.debug("Stopping Yappi")
yappi.stop()
yappi_out = io.StringIO()
yappi.get_func_stats().print_all(out=yappi_out)
yappi.get_thread_stats().print_all(out=yappi_out)
log.debug(yappi_out.getvalue())
yappi_out.close()
yappi.clear_stats()
except Exception:
log.exception("Yappi error:")
try:
if objgraph:
log.debug("Most common types:")
objgraph_out = io.StringIO()
objgraph.show_growth(limit=100, file=objgraph_out)
log.debug(objgraph_out.getvalue())
objgraph_out.close()
except Exception:
log.exception("Objgraph error:")
log.debug("End debug handler")
signal.signal(signal.SIGUSR2, stack_dump_handler)
class NodepoolApp(object):
app_name = None
app_description = 'Node pool.'
def __init__(self):
self.parser = None
self.args = None
def get_path(self, path):
if path is None:
return None
return os.path.abspath(os.path.expanduser(path))
def _get_version(self):
return "Nodepool version: %s" % npd_version_info.release_string()
def create_parser(self):
parser = argparse.ArgumentParser(
description=self.app_description,
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('-l',
dest='logconfig',
help='path to log config file')
parser.add_argument('--version', dest='version', action='version',
version=self._get_version(),
help='show nodepool version')
return parser
def parse_args(self):
self.args = self.parser.parse_args()
self.logconfig = self.get_path(self.args.logconfig)
# The arguments debug and foreground both lead to nodaemon mode so
# set nodaemon if one of them is set.
if ((hasattr(self.args, 'debug') and self.args.debug) or
(hasattr(self.args, 'foreground') and self.args.foreground)):
self.args.nodaemon = True
else:
self.args.nodaemon = False
def setup_logging(self):
if self.logconfig:
logging_config = logconfig.load_config(self.logconfig)
else:
# If someone runs in the foreground and doesn't give a logging
# config, leave the config set to emit to stdout.
if hasattr(self.args, 'nodaemon') and self.args.nodaemon:
logging_config = logconfig.ServerLoggingConfig()
if hasattr(self.args, 'debug') and self.args.debug:
logging_config.setDebug()
else:
# Setting a server value updates the defaults to use
# WatchedFileHandler on /var/log/nodepool/{server}-debug.log
# and /var/log/nodepool/{server}.log
logging_config = logconfig.ServerLoggingConfig(
server=self.app_name)
logging_config.apply()
def _main(self, argv=None):
if argv is None:
argv = sys.argv[1:]
self.parser = self.create_parser()
self.parse_args()
return self._do_run()
def _do_run(self):
# NOTE(jamielennox): setup logging a bit late so it's not done until
# after a DaemonContext is created.
self.setup_logging()
return self.run()
@classmethod
def main(cls, argv=None):
return cls()._main(argv=argv)
def run(self):
"""The app's primary function, override it with your logic."""
raise NotImplementedError()
class NodepoolDaemonApp(NodepoolApp):
def create_parser(self):
parser = super(NodepoolDaemonApp, self).create_parser()
parser.add_argument('-p',
dest='pidfile',
help='path to pid file',
default='/var/run/nodepool/%s.pid' % self.app_name)
parser.add_argument('-d',
dest='debug',
action='store_true',
help='do not run as a daemon with debug logging')
parser.add_argument('-f',
dest='foreground',
action='store_true',
help='do not run as a daemon')
return parser
def parse_args(self):
super().parse_args()
self.pidfile = self.get_path(self.args.pidfile)
def _do_run(self):
if self.args.nodaemon:
return super(NodepoolDaemonApp, self)._do_run()
else:
pid = pid_file_module.TimeoutPIDLockFile(self.pidfile, 10)
if is_pidfile_stale(pid):
pid.break_lock()
# Exercise the pidfile before we do anything else (including
# logging or daemonizing)
with pid:
pass
with daemon.DaemonContext(pidfile=pid):
return super(NodepoolDaemonApp, self)._do_run()
@classmethod
def main(cls, argv=None):
signal.signal(signal.SIGUSR2, stack_dump_handler)
return super(NodepoolDaemonApp, cls).main(argv)