Blob Blame History Raw
From bedcf16700210711395ccd1c7d5e8bd7bf3f359e 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 3860e32..47bc3d8 100755
--- a/glance/cmd/api.py
+++ b/glance/cmd/api.py
@@ -48,6 +48,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
 import glance.store
 
 CONF = cfg.CONF
@@ -80,6 +81,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 95694ea..3df34a8 100755
--- a/glance/cmd/scrubber.py
+++ b/glance/cmd/scrubber.py
@@ -34,6 +34,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
 import glance.store
 
@@ -65,6 +66,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)