Use file modification events instead of signal handler
In case if application is under Apache+mod_wsgi it is not recommended to use signals [1]. We need to have configuration option for handling 'touch file' event instead of signal. Alternative solutions: 1) watchdog: can monitor only directories, has issues with eventlet [2]. 2) inotify: works only with linux-based systems. [1] https://code.google.com/p/modwsgi/wiki/ConfigurationDirectives#WSGIRestrictSignal [2] https://github.com/gorakhargosh/watchdog/issues/332 Change-Id: I6ef02457f21da8e6fbd50e57bfa503b3c31ddd76 Implements: blueprint guru-meditation-report-file-touch
This commit is contained in:
parent
71ce8f5f30
commit
d23e0a65b2
@ -63,11 +63,15 @@ import inspect
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import signal
|
import signal
|
||||||
|
import stat
|
||||||
import sys
|
import sys
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from oslo_utils import timeutils
|
from oslo_utils import timeutils
|
||||||
|
|
||||||
|
from oslo_reports._i18n import _LE
|
||||||
from oslo_reports._i18n import _LW
|
from oslo_reports._i18n import _LW
|
||||||
from oslo_reports.generators import conf as cgen
|
from oslo_reports.generators import conf as cgen
|
||||||
from oslo_reports.generators import process as prgen
|
from oslo_reports.generators import process as prgen
|
||||||
@ -75,6 +79,7 @@ from oslo_reports.generators import threading as tgen
|
|||||||
from oslo_reports.generators import version as pgen
|
from oslo_reports.generators import version as pgen
|
||||||
from oslo_reports import report
|
from oslo_reports import report
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@ -84,7 +89,7 @@ class GuruMeditation(object):
|
|||||||
This class is a base class for Guru Meditation Reports.
|
This class is a base class for Guru Meditation Reports.
|
||||||
It provides facilities for registering sections and
|
It provides facilities for registering sections and
|
||||||
setting up functionality to auto-run the report on
|
setting up functionality to auto-run the report on
|
||||||
a certain signal.
|
a certain signal or use file modification events.
|
||||||
|
|
||||||
This class should always be used in conjunction with
|
This class should always be used in conjunction with
|
||||||
a Report class via multiple inheritance. It should
|
a Report class via multiple inheritance. It should
|
||||||
@ -124,7 +129,8 @@ class GuruMeditation(object):
|
|||||||
|
|
||||||
This method sets up the Guru Meditation Report to automatically
|
This method sets up the Guru Meditation Report to automatically
|
||||||
get dumped to stderr or a file in a given dir when the given signal
|
get dumped to stderr or a file in a given dir when the given signal
|
||||||
is received.
|
is received. It can also use file modification events instead of
|
||||||
|
signals.
|
||||||
|
|
||||||
:param version: the version object for the current product
|
:param version: the version object for the current product
|
||||||
:param service_name: this program name used to construct logfile name
|
:param service_name: this program name used to construct logfile name
|
||||||
@ -140,16 +146,52 @@ class GuruMeditation(object):
|
|||||||
cls._setup_signal(signum, version, service_name, log_dir)
|
cls._setup_signal(signum, version, service_name, log_dir)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if conf and conf.oslo_reports.file_event_handler:
|
||||||
|
cls._setup_file_watcher(
|
||||||
|
conf.oslo_reports.file_event_handler,
|
||||||
|
conf.oslo_reports.file_event_handler_interval,
|
||||||
|
version, service_name, log_dir)
|
||||||
|
else:
|
||||||
if hasattr(signal, 'SIGUSR1'):
|
if hasattr(signal, 'SIGUSR1'):
|
||||||
# TODO(dims) We need to remove this in the "O" release cycle
|
# TODO(dims) We need to remove this in the "O" release cycle
|
||||||
LOG.warning(_LW("Guru mediation now registers SIGUSR1 and SIGUSR2 "
|
LOG.warning(_LW("Guru meditation now registers SIGUSR1 and "
|
||||||
"by default for backward compatibility. SIGUSR1 "
|
"SIGUSR2 by default for backward "
|
||||||
"will no longer be registered in a future "
|
"compatibility. SIGUSR1 will no longer be "
|
||||||
"release, so please use SIGUSR2 to "
|
"registered in a future release, so please "
|
||||||
"generate reports."))
|
"use SIGUSR2 to generate reports."))
|
||||||
cls._setup_signal(signal.SIGUSR1, version, service_name, log_dir)
|
cls._setup_signal(signal.SIGUSR1,
|
||||||
|
version, service_name, log_dir)
|
||||||
if hasattr(signal, 'SIGUSR2'):
|
if hasattr(signal, 'SIGUSR2'):
|
||||||
cls._setup_signal(signal.SIGUSR2, version, service_name, log_dir)
|
cls._setup_signal(signal.SIGUSR2,
|
||||||
|
version, service_name, log_dir)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _setup_file_watcher(cls, filepath, interval, version, service_name,
|
||||||
|
log_dir):
|
||||||
|
|
||||||
|
st = os.stat(filepath)
|
||||||
|
if not bool(st.st_mode & stat.S_IRGRP):
|
||||||
|
LOG.error(_LE("Guru Meditation Report does not have read "
|
||||||
|
"permissions to '%s' file."), filepath)
|
||||||
|
|
||||||
|
def _handler():
|
||||||
|
mtime = time.time()
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
stat = os.stat(filepath)
|
||||||
|
if stat.st_mtime > mtime:
|
||||||
|
cls.handle_signal(version, service_name, log_dir, None)
|
||||||
|
mtime = stat.st_mtime
|
||||||
|
except OSError:
|
||||||
|
msg = ("Guru Meditation Report cannot read " +
|
||||||
|
"'{0}' file".format(filepath))
|
||||||
|
raise IOError(msg)
|
||||||
|
finally:
|
||||||
|
time.sleep(interval)
|
||||||
|
|
||||||
|
th = threading.Thread(target=_handler)
|
||||||
|
th.daemon = True
|
||||||
|
th.start()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _setup_signal(cls, signum, version, service_name, log_dir):
|
def _setup_signal(cls, signum, version, service_name, log_dir):
|
||||||
|
@ -27,6 +27,16 @@ _option_group = 'oslo_reports'
|
|||||||
_options = [
|
_options = [
|
||||||
cfg.StrOpt('log_dir',
|
cfg.StrOpt('log_dir',
|
||||||
help=_('Path to a log directory where to create a file')),
|
help=_('Path to a log directory where to create a file')),
|
||||||
|
cfg.StrOpt('file_event_handler',
|
||||||
|
help=_('The path to a file to watch for changes to trigger '
|
||||||
|
'the reports, instead of signals. Setting this option '
|
||||||
|
'disables the signal trigger for the reports. If '
|
||||||
|
'application is running as a WSGI application it is '
|
||||||
|
'recommended to use this instead of signals.')),
|
||||||
|
cfg.IntOpt('file_event_handler_interval',
|
||||||
|
default=1,
|
||||||
|
help=_('How many seconds to wait between polls when '
|
||||||
|
'file_event_handler is set'))
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ import os
|
|||||||
import re
|
import re
|
||||||
import signal
|
import signal
|
||||||
import sys
|
import sys
|
||||||
|
import threading
|
||||||
|
|
||||||
# needed to get greenthreads
|
# needed to get greenthreads
|
||||||
import fixtures
|
import fixtures
|
||||||
@ -27,8 +28,15 @@ import mock
|
|||||||
from oslotest import base
|
from oslotest import base
|
||||||
import six
|
import six
|
||||||
|
|
||||||
|
import oslo_config
|
||||||
|
from oslo_config import fixture
|
||||||
from oslo_reports import guru_meditation_report as gmr
|
from oslo_reports import guru_meditation_report as gmr
|
||||||
from oslo_reports.models import with_default_views as mwdv
|
from oslo_reports.models import with_default_views as mwdv
|
||||||
|
from oslo_reports import opts
|
||||||
|
|
||||||
|
|
||||||
|
CONF = oslo_config.cfg.CONF
|
||||||
|
opts.set_defaults(CONF)
|
||||||
|
|
||||||
|
|
||||||
class FakeVersionObj(object):
|
class FakeVersionObj(object):
|
||||||
@ -51,6 +59,24 @@ def skip_body_lines(start_line, report_lines):
|
|||||||
return curr_line
|
return curr_line
|
||||||
|
|
||||||
|
|
||||||
|
class GmrConfigFixture(fixture.Config):
|
||||||
|
def setUp(self):
|
||||||
|
super(GmrConfigFixture, self).setUp()
|
||||||
|
|
||||||
|
self.conf.set_override(
|
||||||
|
'file_event_handler',
|
||||||
|
'/tmp/file',
|
||||||
|
group='oslo_reports')
|
||||||
|
self.conf.set_override(
|
||||||
|
'file_event_handler_interval',
|
||||||
|
10,
|
||||||
|
group='oslo_reports')
|
||||||
|
self.conf.set_override(
|
||||||
|
'log_dir',
|
||||||
|
'/var/fake_log',
|
||||||
|
group='oslo_reports')
|
||||||
|
|
||||||
|
|
||||||
class TestGuruMeditationReport(base.BaseTestCase):
|
class TestGuruMeditationReport(base.BaseTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestGuruMeditationReport, self).setUp()
|
super(TestGuruMeditationReport, self).setUp()
|
||||||
@ -61,6 +87,8 @@ class TestGuruMeditationReport(base.BaseTestCase):
|
|||||||
|
|
||||||
self.old_stderr = None
|
self.old_stderr = None
|
||||||
|
|
||||||
|
self.CONF = self.useFixture(GmrConfigFixture(CONF)).conf
|
||||||
|
|
||||||
def test_basic_report(self):
|
def test_basic_report(self):
|
||||||
report_lines = self.report.run().split('\n')
|
report_lines = self.report.run().split('\n')
|
||||||
|
|
||||||
@ -168,6 +196,28 @@ class TestGuruMeditationReport(base.BaseTestCase):
|
|||||||
os.kill(os.getpid(), signal.SIGUSR2)
|
os.kill(os.getpid(), signal.SIGUSR2)
|
||||||
self.assertIn('Guru Meditation', sys.stderr.getvalue())
|
self.assertIn('Guru Meditation', sys.stderr.getvalue())
|
||||||
|
|
||||||
|
@mock.patch.object(gmr.TextGuruMeditation, '_setup_file_watcher')
|
||||||
|
def test_register_autorun_without_signals(self, mock_setup_fh):
|
||||||
|
version = FakeVersionObj()
|
||||||
|
gmr.TextGuruMeditation.setup_autorun(version, conf=self.CONF)
|
||||||
|
mock_setup_fh.assert_called_once_with(
|
||||||
|
'/tmp/file', 10, version, None, '/var/fake_log')
|
||||||
|
|
||||||
|
@mock.patch('os.stat')
|
||||||
|
@mock.patch('time.sleep')
|
||||||
|
@mock.patch.object(threading.Thread, 'start')
|
||||||
|
def test_setup_file_watcher(self, mock_thread, mock_sleep, mock_stat):
|
||||||
|
version = FakeVersionObj()
|
||||||
|
mock_stat.return_value.st_mtime = 3
|
||||||
|
|
||||||
|
gmr.TextGuruMeditation._setup_file_watcher(
|
||||||
|
self.CONF.oslo_reports.file_event_handler,
|
||||||
|
self.CONF.oslo_reports.file_event_handler_interval,
|
||||||
|
version, None, self.CONF.oslo_reports.log_dir)
|
||||||
|
|
||||||
|
mock_stat.assert_called_once_with('/tmp/file')
|
||||||
|
self.assertEqual(1, mock_thread.called)
|
||||||
|
|
||||||
@mock.patch('oslo_utils.timeutils.utcnow',
|
@mock.patch('oslo_utils.timeutils.utcnow',
|
||||||
return_value=datetime.datetime(2014, 1, 1, 12, 0, 0))
|
return_value=datetime.datetime(2014, 1, 1, 12, 0, 0))
|
||||||
def test_register_autorun_log_dir(self, mock_strtime):
|
def test_register_autorun_log_dir(self, mock_strtime):
|
||||||
|
Loading…
Reference in New Issue
Block a user