Keystone service throws error on receiving SIGHUP

This patch resolves following erorrs:
1. AttributeError: 'Server' object has no attribute 'reset'.
2. error: [Errno 9] Bad file descriptor
3. Can't dup an SSL object

When the SIGHUP signal is received by the service launcher in
common service framework, it calls the server's reset method.
As reset method is not present in Sever class of
keystone.common.environment.eventlet_server module, it raises
AttributeError: 'Server' object has no attribute 'reset'.

After adding reset method when SIGHUP signal is sent to service
parent process, it stops the service and then calls service start
method again. When it stops the service, it kills the eventlet
thread, which internally closes the wsgi server socket object.
This server socket object is now not usable again and it throws
following error, while restarting the service:

error: [Errno 9] Bad file descriptor

To resolve 'Bad file descriptor' error, creating duplicate
socket object, every time service starts.

As SSL object can not be duplicated, creating duplicate
socket object before converting a regular socket into an
SSL socket.

Closes-Bug: #1337850
Change-Id: I52caacc01a94428e4986ef68d032ad317e09b276
This commit is contained in:
abhishekkekane 2014-07-04 06:41:12 -07:00
parent 7ad7451c3e
commit 825f3e7820
3 changed files with 64 additions and 56 deletions

View File

@ -71,16 +71,6 @@ class Server(object):
self.keepidle = keepidle
self.socket = None
def start(self, key=None, backlog=128):
"""Run a WSGI server with the given application."""
if self.socket is None:
self.listen(key=key, backlog=backlog)
self.greenthread = self.pool.spawn(self._run,
self.application,
self.socket)
def listen(self, key=None, backlog=128):
"""Create and start listening on socket.
@ -89,49 +79,65 @@ class Server(object):
Raises Exception if this has already been called.
"""
if self.socket is not None:
raise Exception(_('Server can only listen once.'))
# TODO(dims): eventlet's green dns/socket module does not actually
# support IPv6 in getaddrinfo(). We need to get around this in the
# future or monitor upstream for a fix.
# Please refer below link
# (https://bitbucket.org/eventlet/eventlet/
# src/e0f578180d7d82d2ed3d8a96d520103503c524ec/eventlet/support/
# greendns.py?at=0.12#cl-163)
info = socket.getaddrinfo(self.host,
self.port,
socket.AF_UNSPEC,
socket.SOCK_STREAM)[0]
try:
self.socket = eventlet.listen(info[-1], family=info[0],
backlog=backlog)
except EnvironmentError:
LOG.error(_("Could not bind to %(host)s:%(port)s"),
{'host': self.host, 'port': self.port})
raise
LOG.info(_('Starting %(arg0)s on %(host)s:%(port)s'),
{'arg0': sys.argv[0],
'host': self.host,
'port': self.port})
# TODO(dims): eventlet's green dns/socket module does not actually
# support IPv6 in getaddrinfo(). We need to get around this in the
# future or monitor upstream for a fix
info = socket.getaddrinfo(self.host,
self.port,
socket.AF_UNSPEC,
socket.SOCK_STREAM)[0]
_socket = eventlet.listen(info[-1],
family=info[0],
backlog=backlog)
def start(self, key=None, backlog=128):
"""Run a WSGI server with the given application."""
if self.socket is None:
self.listen(key=key, backlog=backlog)
dup_socket = self.socket.dup()
if key:
self.socket_info[key] = _socket.getsockname()
self.socket_info[key] = self.socket.getsockname()
# SSL is enabled
if self.do_ssl:
if self.cert_required:
cert_reqs = ssl.CERT_REQUIRED
else:
cert_reqs = ssl.CERT_NONE
sslsocket = eventlet.wrap_ssl(_socket, certfile=self.certfile,
keyfile=self.keyfile,
server_side=True,
cert_reqs=cert_reqs,
ca_certs=self.ca_certs)
_socket = sslsocket
dup_socket = eventlet.wrap_ssl(dup_socket, certfile=self.certfile,
keyfile=self.keyfile,
server_side=True,
cert_reqs=cert_reqs,
ca_certs=self.ca_certs)
# Optionally enable keepalive on the wsgi socket.
if self.keepalive:
_socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
dup_socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
# This option isn't available in the OS X version of eventlet
if hasattr(socket, 'TCP_KEEPIDLE') and self.keepidle is not None:
_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE,
self.keepidle)
dup_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE,
self.keepidle)
self.socket = _socket
self.greenthread = self.pool.spawn(self._run,
self.application,
dup_socket)
def set_ssl(self, certfile, keyfile=None, ca_certs=None,
cert_required=True):
@ -141,13 +147,10 @@ class Server(object):
self.cert_required = cert_required
self.do_ssl = True
def kill(self):
def stop(self):
if self.greenthread is not None:
self.greenthread.kill()
def stop(self):
self.kill()
def wait(self):
"""Wait until all servers have completed running."""
try:
@ -157,6 +160,17 @@ class Server(object):
except greenlet.GreenletExit:
pass
def reset(self):
"""Required by the service interface.
The service interface is used by the launcher when receiving a
SIGHUP. The service interface is defined in
keystone.openstack.common.service.Service.
Keystone does not need to do anything here.
"""
pass
def _run(self, application, socket):
"""Start a WSGI server in a new green thread."""
logger = log.getLogger('eventlet.wsgi.server')

View File

@ -56,7 +56,7 @@ class AppServer(fixtures.Fixture):
self.port = self.server.socket_info['socket'][1]
self._update_config_opt()
self.addCleanup(self.server.kill)
self.addCleanup(self.server.stop)
def _setup_SSL_if_requested(self):
# TODO(dstanek): fix environment.Server to take a SSLOpts instance

View File

@ -382,40 +382,34 @@ class ServerTest(tests.TestCase):
@mock.patch('socket.getaddrinfo')
def test_keepalive_unset(self, mock_getaddrinfo, mock_listen):
mock_getaddrinfo.return_value = [(1, 2, 3, 4, 5)]
mock_sock = mock.Mock()
mock_sock.setsockopt = mock.Mock()
mock_sock_dup = mock_listen.return_value.dup.return_value
mock_listen.return_value = mock_sock
server = environment.Server(mock.MagicMock(), host=self.host,
port=self.port)
server.start()
self.assertTrue(mock_listen.called)
self.assertFalse(mock_sock.setsockopt.called)
self.assertFalse(mock_sock_dup.setsockopt.called)
@mock.patch('eventlet.listen')
@mock.patch('socket.getaddrinfo')
def test_keepalive_set(self, mock_getaddrinfo, mock_listen):
mock_getaddrinfo.return_value = [(1, 2, 3, 4, 5)]
mock_sock = mock.Mock()
mock_sock.setsockopt = mock.Mock()
mock_sock_dup = mock_listen.return_value.dup.return_value
mock_listen.return_value = mock_sock
server = environment.Server(mock.MagicMock(), host=self.host,
port=self.port, keepalive=True)
server.start()
mock_sock.setsockopt.assert_called_once_with(socket.SOL_SOCKET,
socket.SO_KEEPALIVE,
1)
mock_sock_dup.setsockopt.assert_called_once_with(socket.SOL_SOCKET,
socket.SO_KEEPALIVE,
1)
self.assertTrue(mock_listen.called)
@mock.patch('eventlet.listen')
@mock.patch('socket.getaddrinfo')
def test_keepalive_and_keepidle_set(self, mock_getaddrinfo, mock_listen):
mock_getaddrinfo.return_value = [(1, 2, 3, 4, 5)]
mock_sock = mock.Mock()
mock_sock.setsockopt = mock.Mock()
mock_sock_dup = mock_listen.return_value.dup.return_value
mock_listen.return_value = mock_sock
server = environment.Server(mock.MagicMock(), host=self.host,
port=self.port, keepalive=True,
keepidle=1)
@ -423,13 +417,13 @@ class ServerTest(tests.TestCase):
# keepidle isn't available in the OS X version of eventlet
if hasattr(socket, 'TCP_KEEPIDLE'):
self.assertEqual(mock_sock.setsockopt.call_count, 2)
self.assertEqual(mock_sock_dup.setsockopt.call_count, 2)
# Test the last set of call args i.e. for the keepidle
mock_sock.setsockopt.assert_called_with(socket.IPPROTO_TCP,
socket.TCP_KEEPIDLE,
1)
mock_sock_dup.setsockopt.assert_called_with(socket.IPPROTO_TCP,
socket.TCP_KEEPIDLE,
1)
else:
self.assertEqual(mock_sock.setsockopt.call_count, 1)
self.assertEqual(mock_sock_dup.setsockopt.call_count, 1)
self.assertTrue(mock_listen.called)