From 490ea11a69c10a5a4396044f3fbc538f0e69f6a2 Mon Sep 17 00:00:00 2001
From: Alan Pevec <apevec@redhat.com>
Date: Tue, 11 Feb 2014 22:36:00 +0100
Subject: [PATCH] notify calling process we are ready to serve
Systemd notification should be sent in-process, otherwise systemd might
miss the subprocess sending notification.
See systemd bug https://bugzilla.redhat.com/show_bug.cgi?id=820448
Taken from keystone project commit
abc06716d027d68f0da3b0f559fa7c85a21804d5
Improvements from Keystone version:
* add unset_environment parameter
New parameter unset_environment was added to sd_notify
http://www.freedesktop.org/software/systemd/man/sd_notify.html
to ensure service readiness is sent only once.
* add onready() method to simulate systemd environment
For testing purposes and optional use with SysV initscripts.
* unit test added
* docstrings for notification methods
Patch includes deployment in openstack.common.service.
It does not have an effect when running the service outside
the systemd environment.
Implements: blueprint service-readiness
Change-Id: I80f325c9be9c171c2dc8d5526570bf64f0f87c78
---
glance/cmd/api.py | 2 +
glance/cmd/registry.py | 2 +
glance/cmd/scrubber.py | 2 +
glance/openstack/common/systemd.py | 104 +++++++++++++++++++++++++++++++++++++
4 files changed, 110 insertions(+)
create mode 100644 glance/openstack/common/systemd.py
diff --git a/glance/cmd/api.py b/glance/cmd/api.py
index 557dc3b..3457269 100755
--- a/glance/cmd/api.py
+++ b/glance/cmd/api.py
@@ -49,6 +49,7 @@ from glance.common import exception
from glance.common import wsgi
from glance import notifier
from glance.openstack.common import log
+from glance.openstack.common import systemd
CONF = cfg.CONF
CONF.import_group("profiler", "glance.common.wsgi")
@@ -81,6 +82,7 @@ def main():
server = wsgi.Server()
server.start(config.load_paste_app('glance-api'), default_port=9292)
+ systemd.notify_once()
server.wait()
except exception.WorkerCreationFailure as e:
fail(2, e)
diff --git a/glance/cmd/registry.py b/glance/cmd/registry.py
index 06cea6a..e0dfbab 100755
--- a/glance/cmd/registry.py
+++ b/glance/cmd/registry.py
@@ -44,6 +44,7 @@ from glance.common import config
from glance.common import wsgi
from glance import notifier
from glance.openstack.common import log
+from glance.openstack.common import systemd
CONF = cfg.CONF
CONF.import_group("profiler", "glance.common.wsgi")
@@ -69,6 +70,7 @@ def main():
server = wsgi.Server()
server.start(config.load_paste_app('glance-registry'),
default_port=9191)
+ systemd.notify_once()
server.wait()
except RuntimeError as e:
sys.exit("ERROR: %s" % e)
diff --git a/glance/cmd/scrubber.py b/glance/cmd/scrubber.py
index 850a185..38f1c16 100755
--- a/glance/cmd/scrubber.py
+++ b/glance/cmd/scrubber.py
@@ -35,6 +35,7 @@ from oslo.config import cfg
from glance.common import config
from glance.openstack.common import log
+from glance.openstack.common import systemd
from glance import scrubber
@@ -58,6 +59,7 @@ def main():
if CONF.daemon:
server = scrubber.Daemon(CONF.wakeup_time)
server.start(app)
+ systemd.notify_once()
server.wait()
else:
import eventlet
diff --git a/glance/openstack/common/systemd.py b/glance/openstack/common/systemd.py
new file mode 100644
index 0000000..47612a9
--- /dev/null
+++ b/glance/openstack/common/systemd.py
@@ -0,0 +1,104 @@
+# Copyright 2012-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.
+
+"""
+Helper module for systemd service readiness notification.
+"""
+
+import os
+import socket
+import sys
+
+from glance.openstack.common import log as logging
+
+
+LOG = logging.getLogger(__name__)
+
+
+def _abstractify(socket_name):
+ if socket_name.startswith('@'):
+ # abstract namespace socket
+ socket_name = '\0%s' % socket_name[1:]
+ return socket_name
+
+
+def _sd_notify(unset_env, msg):
+ notify_socket = os.getenv('NOTIFY_SOCKET')
+ if notify_socket:
+ sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
+ try:
+ sock.connect(_abstractify(notify_socket))
+ sock.sendall(msg)
+ if unset_env:
+ del os.environ['NOTIFY_SOCKET']
+ except EnvironmentError:
+ LOG.debug("Systemd notification failed", exc_info=True)
+ finally:
+ sock.close()
+
+
+def notify():
+ """Send notification to Systemd that service is ready.
+ For details see
+ http://www.freedesktop.org/software/systemd/man/sd_notify.html
+ """
+ _sd_notify(False, 'READY=1')
+
+
+def notify_once():
+ """Send notification once to Systemd that service is ready.
+ Systemd sets NOTIFY_SOCKET environment variable with the name of the
+ socket listening for notifications from services.
+ This method removes the NOTIFY_SOCKET environment variable to ensure
+ notification is sent only once.
+ """
+ _sd_notify(True, 'READY=1')
+
+
+def onready(notify_socket, timeout):
+ """Wait for systemd style notification on the socket.
+
+ :param notify_socket: local socket address
+ :type notify_socket: string
+ :param timeout: socket timeout
+ :type timeout: float
+ :returns: 0 service ready
+ 1 service not ready
+ 2 timeout occured
+ """
+ sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
+ sock.settimeout(timeout)
+ sock.bind(_abstractify(notify_socket))
+ try:
+ msg = sock.recv(512)
+ except socket.timeout:
+ return 2
+ finally:
+ sock.close()
+ if 'READY=1' in msg:
+ return 0
+ else:
+ return 1
+
+
+if __name__ == '__main__':
+ # simple CLI for testing
+ if len(sys.argv) == 1:
+ notify()
+ elif len(sys.argv) >= 2:
+ timeout = float(sys.argv[1])
+ notify_socket = os.getenv('NOTIFY_SOCKET')
+ if notify_socket:
+ retval = onready(notify_socket, timeout)
+ sys.exit(retval)