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:
parent
7ad7451c3e
commit
825f3e7820
@ -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')
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user