Avoid eventlet_backdoor listing on same port

Oslo.service is binding to same port when we provide a port range
as eventlet is internally setting SO_REUSEPORT flag (starting from
eventlet version v0.20). And there is a flag (reuse_port)
introduced in v0.22 to give control to user to avoid SO_REUSEPORT.
In this patch, first we try passing reuse_port=False, if this fails
then directly open socket and listen instead of eventlist.listen.

Closes-Bug: #1810280
Change-Id: Idc842acc7e430199c76fe12785b0bf0e7a58e121
This commit is contained in:
venkata anil 2019-01-02 05:11:12 -05:00
parent d987a4a84c
commit 811650783d
2 changed files with 25 additions and 4 deletions

View File

@ -21,13 +21,13 @@ import gc
import logging import logging
import os import os
import pprint import pprint
import socket
import sys import sys
import traceback import traceback
import eventlet.backdoor import eventlet.backdoor
import greenlet import greenlet
from eventlet.green import socket
from oslo_service._i18n import _ from oslo_service._i18n import _
from oslo_service import _options from oslo_service import _options
@ -121,11 +121,24 @@ def _parse_port_range(port_range):
port_range, ex, _options.help_for_backdoor_port) port_range, ex, _options.help_for_backdoor_port)
def _listen(host, start_port, end_port, listen_func): def _listen_func(host, port):
# eventlet is setting SO_REUSEPORT by default from v0.20.
# But we can configure it by passing reuse_port argument
# from v0.22
try:
return eventlet.listen((host, port), reuse_port=False)
except TypeError:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind((host, port))
sock.listen(50)
return sock
def _listen(host, start_port, end_port):
try_port = start_port try_port = start_port
while True: while True:
try: try:
return listen_func((host, try_port)) return _listen_func(host, try_port)
except socket.error as exc: except socket.error as exc:
if (exc.errno != errno.EADDRINUSE or if (exc.errno != errno.EADDRINUSE or
try_port >= end_port): try_port >= end_port):
@ -169,7 +182,7 @@ def _initialize_if_enabled(conf):
if conf.backdoor_socket is None: if conf.backdoor_socket is None:
start_port, end_port = _parse_port_range(str(conf.backdoor_port)) start_port, end_port = _parse_port_range(str(conf.backdoor_port))
sock = _listen('localhost', start_port, end_port, eventlet.listen) sock = _listen('localhost', start_port, end_port)
# In the case of backdoor port being zero, a port number is assigned by # In the case of backdoor port being zero, a port number is assigned by
# listen(). In any case, pull the port number out here. # listen(). In any case, pull the port number out here.
where_running = sock.getsockname()[1] where_running = sock.getsockname()[1]

View File

@ -105,6 +105,14 @@ class BackdoorPortTest(base.ServiceBaseTestCase):
self.assertRaises(socket.error, self.assertRaises(socket.error,
eventlet_backdoor.initialize_if_enabled, self.conf) eventlet_backdoor.initialize_if_enabled, self.conf)
@mock.patch.object(eventlet, 'spawn')
def test_backdoor_port_range_inuse(self, spawn_mock):
self.config(backdoor_port='8800:8801')
port = eventlet_backdoor.initialize_if_enabled(self.conf)
self.assertEqual(8800, port)
port = eventlet_backdoor.initialize_if_enabled(self.conf)
self.assertEqual(8801, port)
@mock.patch.object(eventlet, 'spawn') @mock.patch.object(eventlet, 'spawn')
@mock.patch.object(eventlet, 'listen') @mock.patch.object(eventlet, 'listen')
def test_backdoor_port_range(self, listen_mock, spawn_mock): def test_backdoor_port_range(self, listen_mock, spawn_mock):