From cceda95a35f18f5f7b52daaf0662a4cd3768b3ac Mon Sep 17 00:00:00 2001 From: Lon Hohberger Date: Tue, 18 Feb 2014 13:44:56 -0500 Subject: [PATCH] Add systemd notification support to heat This mirrors work done in other OpenStack projects to add systemd notification support to signal that a given process is ready to accept requests. The heat-api, heat-api-cfn, heat-engine, and heat-api-cloudwatch processes are all covered by a new, single, 'onready' configuration file option. Additionally, the sample configuration file has been updated to match. Implements: blueprint systemd-integration Change-Id: I90d2b915e2568a65ed9ed78923c9982e19f9c948 Signed-off-by: Lon Hohberger --- bin/heat-api | 2 ++ bin/heat-api-cfn | 2 ++ bin/heat-api-cloudwatch | 2 ++ bin/heat-engine | 3 +++ etc/heat/heat.conf.sample | 8 +++++++ heat/common/config.py | 9 +++++++- heat/common/notify.py | 38 +++++++++++++++++++++++++++++++++ heat/common/systemd.py | 44 +++++++++++++++++++++++++++++++++++++++ 8 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 heat/common/notify.py create mode 100644 heat/common/systemd.py diff --git a/bin/heat-api b/bin/heat-api index d9f7495e22..44e0cf486d 100755 --- a/bin/heat-api +++ b/bin/heat-api @@ -40,6 +40,7 @@ from oslo.config import cfg from heat.common import config from heat.common import wsgi +from heat.common import notify from heat.openstack.common import log as logging @@ -62,6 +63,7 @@ if __name__ == '__main__': LOG.info('Starting Heat ReST API on %s:%s' % (host, port)) server = wsgi.Server() server.start(app, cfg.CONF.heat_api, default_port=port) + notify.startup_notify(cfg.CONF.onready) server.wait() except RuntimeError as e: sys.exit("ERROR: %s" % e) diff --git a/bin/heat-api-cfn b/bin/heat-api-cfn index 54bdf637f7..07315f9035 100755 --- a/bin/heat-api-cfn +++ b/bin/heat-api-cfn @@ -42,6 +42,7 @@ from oslo.config import cfg from heat.common import config from heat.common import wsgi +from heat.common import notify from heat.openstack.common import log as logging @@ -64,6 +65,7 @@ if __name__ == '__main__': LOG.info('Starting Heat API on %s:%s' % (host, port)) server = wsgi.Server() server.start(app, cfg.CONF.heat_api_cfn, default_port=port) + notify.startup_notify(cfg.CONF.onready) server.wait() except RuntimeError as e: sys.exit("ERROR: %s" % e) diff --git a/bin/heat-api-cloudwatch b/bin/heat-api-cloudwatch index 79feb70a9f..2f3230b79e 100755 --- a/bin/heat-api-cloudwatch +++ b/bin/heat-api-cloudwatch @@ -42,6 +42,7 @@ from oslo.config import cfg from heat.common import config from heat.common import wsgi +from heat.common import notify from heat.openstack.common import log as logging @@ -64,6 +65,7 @@ if __name__ == '__main__': LOG.info('Starting Heat CloudWatch API on %s:%s' % (host, port)) server = wsgi.Server() server.start(app, cfg.CONF.heat_api_cloudwatch, default_port=port) + notify.startup_notify(cfg.CONF.onready) server.wait() except RuntimeError as e: sys.exit("ERROR: %s" % e) diff --git a/bin/heat-engine b/bin/heat-engine index 185d48ee1f..426d79ada3 100755 --- a/bin/heat-engine +++ b/bin/heat-engine @@ -41,6 +41,8 @@ gettextutils.install('heat', lazy=True) from oslo.config import cfg +from heat.common import notify + from heat.openstack.common import log as logging from heat.openstack.common import service @@ -64,4 +66,5 @@ if __name__ == '__main__': srv = engine.EngineService(cfg.CONF.host, rpc_api.ENGINE_TOPIC) launcher = service.launch(srv) + notify.startup_notify(cfg.CONF.onready) launcher.wait() diff --git a/etc/heat/heat.conf.sample b/etc/heat/heat.conf.sample index 896925ba87..7f1663c249 100644 --- a/etc/heat/heat.conf.sample +++ b/etc/heat/heat.conf.sample @@ -58,6 +58,14 @@ # stack locking. (integer value) #engine_life_check_timeout=2 +# onready allows you to send a notification when the heat +# processes are ready to serve. This is either a module with +# the notify() method or a shell command. To enable +# notifications with systemd, one may use the 'systemd-notify +# --ready' shell command or the 'heat.common.systemd' +# notification module. (string value) +#onready= + # Name of the engine node. This can be an opaque identifier. # It is not necessarily a hostname, FQDN, or IP address. # (string value) diff --git a/heat/common/config.py b/heat/common/config.py index 1cfb50fddf..31d348e50e 100644 --- a/heat/common/config.py +++ b/heat/common/config.py @@ -118,7 +118,14 @@ engine_opts = [ cfg.IntOpt('engine_life_check_timeout', default=2, help=_('RPC timeout for the engine liveness check that is used' - ' for stack locking.'))] + ' for stack locking.')), + cfg.StrOpt('onready', + help=_('onready allows you to send a notification when the' + ' heat processes are ready to serve. This is either a' + ' module with the notify() method or a shell command. ' + ' To enable notifications with systemd, one may use' + ' the \'systemd-notify --ready\' shell command or' + ' the \'heat.common.systemd\' notification module.'))] rpc_opts = [ cfg.StrOpt('host', diff --git a/heat/common/notify.py b/heat/common/notify.py new file mode 100644 index 0000000000..d168482c40 --- /dev/null +++ b/heat/common/notify.py @@ -0,0 +1,38 @@ +# Copyright 2014 Red Hat, Inc. +# +# 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. + +""" +Startup notification using a shell script or systemd NOTIFY_SOCKET +style notification +""" + +from heat.openstack.common import log as logging +from heat.openstack.common import importutils +from heat.openstack.common import processutils + +logger = logging.getLogger(__name__) + + +def startup_notify(notify_param): + if not notify_param or notify_param == "": + return + try: + notifier = importutils.import_module(notify_param) + except ImportError: + try: + processutils.execute(notify_param, shell=True) + except Exception as e: + logger.error(_('Failed to execute onready command: %s') % str(e)) + else: + notifier.notify() diff --git a/heat/common/systemd.py b/heat/common/systemd.py new file mode 100644 index 0000000000..f094dd7778 --- /dev/null +++ b/heat/common/systemd.py @@ -0,0 +1,44 @@ +# Copyright 2012 Red Hat, Inc. +# +# 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. + +""" +Helper module for systemd start-up completion notification. +Used for "onready" configuration parameter in heat.conf +""" + +import os +import socket + +from heat.openstack.common import log as logging + +logger = logging.getLogger(__name__) + + +def _sd_notify(msg): + sysd = os.getenv('NOTIFY_SOCKET') + if sysd: + sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) + if sysd.startswith('@'): + # abstract namespace socket + sysd = '\0%s' % sysd[1:] + sock.connect(sysd) + sock.sendall(msg) + sock.close() + else: + logger.warning(_('Unable to notify systemd of startup completion:' + ' NOTIFY_SOCKET not set')) + + +def notify(): + _sd_notify('READY=1')