From 29be8d6d16f155875532d878702566023f208fff Mon Sep 17 00:00:00 2001 From: Assaf Muller Date: Thu, 12 Mar 2015 19:50:24 -0400 Subject: [PATCH] Move Unix domain socket helpers to a common place As part of the all consuming report-ha-router-master, a new per router neutron-keepalived-state-change daemon will alert the L3 agent on every keepalived state change. Since it will use the Unix domain socket helpers, and they're currently located in metadata related places, this patch moves them to a common location. Also, the UnixDomainHTTPConnection connection string may now be overridden. Partially-Implements: blueprint report-ha-router-master Change-Id: Ib2cde90059f4e089064b2def2838e9bcf9af30de --- neutron/agent/linux/utils.py | 64 +++++++++++++++++++ neutron/agent/metadata/agent.py | 57 ++--------------- neutron/agent/metadata/namespace_proxy.py | 20 +----- .../tests/functional/agent/test_l3_agent.py | 3 +- neutron/tests/unit/agent/linux/test_utils.py | 61 ++++++++++++++++++ neutron/tests/unit/test_metadata_agent.py | 53 ++------------- .../unit/test_metadata_namespace_proxy.py | 33 ++-------- 7 files changed, 143 insertions(+), 148 deletions(-) diff --git a/neutron/agent/linux/utils.py b/neutron/agent/linux/utils.py index 3795522f0..c5aa4c7e0 100644 --- a/neutron/agent/linux/utils.py +++ b/neutron/agent/linux/utils.py @@ -15,6 +15,7 @@ import fcntl import glob +import httplib import os import shlex import socket @@ -27,6 +28,7 @@ from eventlet.green import subprocess from eventlet import greenthread from oslo_config import cfg from oslo_log import log as logging +from oslo_log import loggers from oslo_rootwrap import client from oslo_utils import excutils @@ -34,6 +36,7 @@ from neutron.agent.common import config from neutron.common import constants from neutron.common import utils from neutron.i18n import _LE +from neutron import wsgi LOG = logging.getLogger(__name__) @@ -316,3 +319,64 @@ def wait_until_true(predicate, timeout=60, sleep=1, exception=None): with eventlet.timeout.Timeout(timeout, exception): while not predicate(): eventlet.sleep(sleep) + + +def ensure_directory_exists_without_file(path): + dirname = os.path.dirname(path) + if os.path.isdir(dirname): + try: + os.unlink(path) + except OSError: + with excutils.save_and_reraise_exception() as ctxt: + if not os.path.exists(path): + ctxt.reraise = False + else: + ensure_dir(dirname) + + +class UnixDomainHTTPConnection(httplib.HTTPConnection): + """Connection class for HTTP over UNIX domain socket.""" + def __init__(self, host, port=None, strict=None, timeout=None, + proxy_info=None): + httplib.HTTPConnection.__init__(self, host, port, strict) + self.timeout = timeout + self.socket_path = cfg.CONF.metadata_proxy_socket + + def connect(self): + self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + if self.timeout: + self.sock.settimeout(self.timeout) + self.sock.connect(self.socket_path) + + +class UnixDomainHttpProtocol(eventlet.wsgi.HttpProtocol): + def __init__(self, request, client_address, server): + if client_address == '': + client_address = ('', 0) + # base class is old-style, so super does not work properly + eventlet.wsgi.HttpProtocol.__init__(self, request, client_address, + server) + + +class UnixDomainWSGIServer(wsgi.Server): + def __init__(self, name): + self._socket = None + self._launcher = None + self._server = None + super(UnixDomainWSGIServer, self).__init__(name) + + def start(self, application, file_socket, workers, backlog): + self._socket = eventlet.listen(file_socket, + family=socket.AF_UNIX, + backlog=backlog) + + self._launch(application, workers=workers) + + def _run(self, application, socket): + """Start a WSGI service in a new green thread.""" + logger = logging.getLogger('eventlet.wsgi.server') + eventlet.wsgi.server(socket, + application, + max_size=self.num_threads, + protocol=UnixDomainHttpProtocol, + log=loggers.WritableLogger(logger)) diff --git a/neutron/agent/metadata/agent.py b/neutron/agent/metadata/agent.py index 44ea9b062..062acbaa0 100644 --- a/neutron/agent/metadata/agent.py +++ b/neutron/agent/metadata/agent.py @@ -14,21 +14,16 @@ import hashlib import hmac -import os -import socket -import eventlet import httplib2 from neutronclient.v2_0 import client from oslo_config import cfg from oslo_log import log as logging -from oslo_log import loggers import oslo_messaging -from oslo_utils import excutils import six.moves.urllib.parse as urlparse import webob -from neutron.agent.linux import utils as linux_utils +from neutron.agent.linux import utils as agent_utils from neutron.agent import rpc as agent_rpc from neutron.common import constants as n_const from neutron.common import rpc as n_rpc @@ -38,7 +33,6 @@ from neutron import context from neutron.i18n import _LE, _LW from neutron.openstack.common.cache import cache from neutron.openstack.common import loopingcall -from neutron import wsgi LOG = logging.getLogger(__name__) @@ -266,55 +260,12 @@ class MetadataProxyHandler(object): hashlib.sha256).hexdigest() -class UnixDomainHttpProtocol(eventlet.wsgi.HttpProtocol): - def __init__(self, request, client_address, server): - if client_address == '': - client_address = ('', 0) - # base class is old-style, so super does not work properly - eventlet.wsgi.HttpProtocol.__init__(self, request, client_address, - server) - - -class UnixDomainWSGIServer(wsgi.Server): - def __init__(self, name): - self._socket = None - self._launcher = None - self._server = None - super(UnixDomainWSGIServer, self).__init__(name) - - def start(self, application, file_socket, workers, backlog): - self._socket = eventlet.listen(file_socket, - family=socket.AF_UNIX, - backlog=backlog) - - self._launch(application, workers=workers) - - def _run(self, application, socket): - """Start a WSGI service in a new green thread.""" - logger = logging.getLogger('eventlet.wsgi.server') - eventlet.wsgi.server(socket, - application, - max_size=self.num_threads, - protocol=UnixDomainHttpProtocol, - log=loggers.WritableLogger(logger)) - - class UnixDomainMetadataProxy(object): def __init__(self, conf): self.conf = conf - - dirname = os.path.dirname(cfg.CONF.metadata_proxy_socket) - if os.path.isdir(dirname): - try: - os.unlink(cfg.CONF.metadata_proxy_socket) - except OSError: - with excutils.save_and_reraise_exception() as ctxt: - if not os.path.exists(cfg.CONF.metadata_proxy_socket): - ctxt.reraise = False - else: - linux_utils.ensure_dir(dirname) - + agent_utils.ensure_directory_exists_without_file( + cfg.CONF.metadata_proxy_socket) self._init_state_reporting() def _init_state_reporting(self): @@ -355,7 +306,7 @@ class UnixDomainMetadataProxy(object): self.agent_state.pop('start_flag', None) def run(self): - server = UnixDomainWSGIServer('neutron-metadata-agent') + server = agent_utils.UnixDomainWSGIServer('neutron-metadata-agent') server.start(MetadataProxyHandler(self.conf), self.conf.metadata_proxy_socket, workers=self.conf.metadata_workers, diff --git a/neutron/agent/metadata/namespace_proxy.py b/neutron/agent/metadata/namespace_proxy.py index be1d32adf..032e489fb 100644 --- a/neutron/agent/metadata/namespace_proxy.py +++ b/neutron/agent/metadata/namespace_proxy.py @@ -12,9 +12,6 @@ # License for the specific language governing permissions and limitations # under the License. -import httplib -import socket - import httplib2 from oslo_config import cfg from oslo_log import log as logging @@ -22,6 +19,7 @@ import six.moves.urllib.parse as urlparse import webob from neutron.agent.linux import daemon +from neutron.agent.linux import utils as agent_utils from neutron.common import config from neutron.common import exceptions from neutron.common import utils @@ -31,20 +29,6 @@ from neutron import wsgi LOG = logging.getLogger(__name__) -class UnixDomainHTTPConnection(httplib.HTTPConnection): - """Connection class for HTTP over UNIX domain socket.""" - def __init__(self, host, port=None, strict=None, timeout=None, - proxy_info=None): - httplib.HTTPConnection.__init__(self, host, port, strict) - self.timeout = timeout - - def connect(self): - self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - if self.timeout: - self.sock.settimeout(self.timeout) - self.sock.connect(cfg.CONF.metadata_proxy_socket) - - class NetworkMetadataProxyHandler(object): """Proxy AF_INET metadata request through Unix Domain socket. @@ -98,7 +82,7 @@ class NetworkMetadataProxyHandler(object): method=method, headers=headers, body=body, - connection_type=UnixDomainHTTPConnection) + connection_type=agent_utils.UnixDomainHTTPConnection) if resp.status == 200: LOG.debug(resp) diff --git a/neutron/tests/functional/agent/test_l3_agent.py b/neutron/tests/functional/agent/test_l3_agent.py index 8e81f9608..331406175 100755 --- a/neutron/tests/functional/agent/test_l3_agent.py +++ b/neutron/tests/functional/agent/test_l3_agent.py @@ -35,7 +35,6 @@ from neutron.agent.linux import external_process from neutron.agent.linux import ip_lib from neutron.agent.linux import ovs_lib from neutron.agent.linux import utils -from neutron.agent.metadata import agent as metadata_agent from neutron.common import config as common_config from neutron.common import constants as l3_constants from neutron.common import utils as common_utils @@ -544,7 +543,7 @@ class MetadataFakeProxyHandler(object): class MetadataL3AgentTestCase(L3AgentTestFramework): def _create_metadata_fake_server(self, status): - server = metadata_agent.UnixDomainWSGIServer('metadata-fake-server') + server = utils.UnixDomainWSGIServer('metadata-fake-server') self.addCleanup(server.stop) server.start(MetadataFakeProxyHandler(status), self.agent.conf.metadata_proxy_socket, diff --git a/neutron/tests/unit/agent/linux/test_utils.py b/neutron/tests/unit/agent/linux/test_utils.py index 4f374f357..c66d48ec6 100644 --- a/neutron/tests/unit/agent/linux/test_utils.py +++ b/neutron/tests/unit/agent/linux/test_utils.py @@ -11,9 +11,11 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. + import os import mock +import socket import testtools from neutron.agent.linux import utils @@ -221,3 +223,62 @@ class TestBaseOSUtils(base.BaseTestCase): utils.ensure_dir('/the') isdir.assert_called_once_with('/the') self.assertFalse(makedirs.called) + + +class TestUnixDomainHttpConnection(base.BaseTestCase): + def test_connect(self): + with mock.patch.object(utils, 'cfg') as cfg: + cfg.CONF.metadata_proxy_socket = '/the/path' + with mock.patch('socket.socket') as socket_create: + conn = utils.UnixDomainHTTPConnection('169.254.169.254', + timeout=3) + conn.connect() + + socket_create.assert_has_calls([ + mock.call(socket.AF_UNIX, socket.SOCK_STREAM), + mock.call().settimeout(3), + mock.call().connect('/the/path')] + ) + self.assertEqual(conn.timeout, 3) + + +class TestUnixDomainHttpProtocol(base.BaseTestCase): + def test_init_empty_client(self): + u = utils.UnixDomainHttpProtocol(mock.Mock(), '', mock.Mock()) + self.assertEqual(u.client_address, ('', 0)) + + def test_init_with_client(self): + u = utils.UnixDomainHttpProtocol(mock.Mock(), 'foo', mock.Mock()) + self.assertEqual(u.client_address, 'foo') + + +class TestUnixDomainWSGIServer(base.BaseTestCase): + def setUp(self): + super(TestUnixDomainWSGIServer, self).setUp() + self.eventlet_p = mock.patch.object(utils, 'eventlet') + self.eventlet = self.eventlet_p.start() + self.server = utils.UnixDomainWSGIServer('test') + + def test_start(self): + mock_app = mock.Mock() + with mock.patch.object(self.server, '_launch') as launcher: + self.server.start(mock_app, '/the/path', workers=5, backlog=128) + self.eventlet.assert_has_calls([ + mock.call.listen( + '/the/path', + family=socket.AF_UNIX, + backlog=128 + )] + ) + launcher.assert_called_once_with(mock_app, workers=5) + + def test_run(self): + self.server._run('app', 'sock') + + self.eventlet.wsgi.server.assert_called_once_with( + 'sock', + 'app', + protocol=utils.UnixDomainHttpProtocol, + log=mock.ANY, + max_size=self.server.num_threads + ) diff --git a/neutron/tests/unit/test_metadata_agent.py b/neutron/tests/unit/test_metadata_agent.py index 27a450a75..1a314c00e 100644 --- a/neutron/tests/unit/test_metadata_agent.py +++ b/neutron/tests/unit/test_metadata_agent.py @@ -13,13 +13,12 @@ # under the License. import contextlib -import socket import mock import testtools import webob -from neutron.agent.linux import utils as linux_utils +from neutron.agent.linux import utils as agent_utils from neutron.agent.metadata import agent from neutron.agent import metadata_agent from neutron.common import constants @@ -511,50 +510,6 @@ class TestMetadataProxyHandlerNoCache(TestMetadataProxyHandlerCache): 2, self.qclient.return_value.list_ports.call_count) -class TestUnixDomainHttpProtocol(base.BaseTestCase): - def test_init_empty_client(self): - u = agent.UnixDomainHttpProtocol(mock.Mock(), '', mock.Mock()) - self.assertEqual(u.client_address, ('', 0)) - - def test_init_with_client(self): - u = agent.UnixDomainHttpProtocol(mock.Mock(), 'foo', mock.Mock()) - self.assertEqual(u.client_address, 'foo') - - -class TestUnixDomainWSGIServer(base.BaseTestCase): - def setUp(self): - super(TestUnixDomainWSGIServer, self).setUp() - self.eventlet_p = mock.patch.object(agent, 'eventlet') - self.eventlet = self.eventlet_p.start() - self.server = agent.UnixDomainWSGIServer('test') - - def test_start(self): - mock_app = mock.Mock() - with mock.patch.object(self.server, '_launch') as launcher: - self.server.start(mock_app, '/the/path', workers=5, backlog=128) - self.eventlet.assert_has_calls([ - mock.call.listen( - '/the/path', - family=socket.AF_UNIX, - backlog=128 - )] - ) - launcher.assert_called_once_with(mock_app, workers=5) - - def test_run(self): - with mock.patch.object(agent, 'logging') as logging: - self.server._run('app', 'sock') - - self.eventlet.wsgi.server.assert_called_once_with( - 'sock', - 'app', - protocol=agent.UnixDomainHttpProtocol, - log=mock.ANY, - max_size=self.server.num_threads - ) - self.assertTrue(len(logging.mock_calls)) - - class TestUnixDomainMetadataProxy(base.BaseTestCase): def setUp(self): super(TestUnixDomainMetadataProxy, self).setUp() @@ -567,7 +522,7 @@ class TestUnixDomainMetadataProxy(base.BaseTestCase): self.cfg.CONF.metadata_workers = 0 self.cfg.CONF.metadata_backlog = 128 - @mock.patch.object(linux_utils, 'ensure_dir') + @mock.patch.object(agent_utils, 'ensure_dir') def test_init_doesnot_exists(self, ensure_dir): agent.UnixDomainMetadataProxy(mock.Mock()) ensure_dir.assert_called_once_with('/the') @@ -603,8 +558,8 @@ class TestUnixDomainMetadataProxy(base.BaseTestCase): unlink.assert_called_once_with('/the/path') @mock.patch.object(agent, 'MetadataProxyHandler') - @mock.patch.object(agent, 'UnixDomainWSGIServer') - @mock.patch.object(linux_utils, 'ensure_dir') + @mock.patch.object(agent_utils, 'UnixDomainWSGIServer') + @mock.patch.object(agent_utils, 'ensure_dir') def test_run(self, ensure_dir, server, handler): p = agent.UnixDomainMetadataProxy(self.cfg.CONF) p.run() diff --git a/neutron/tests/unit/test_metadata_namespace_proxy.py b/neutron/tests/unit/test_metadata_namespace_proxy.py index 4a707a503..ce9c2bca9 100644 --- a/neutron/tests/unit/test_metadata_namespace_proxy.py +++ b/neutron/tests/unit/test_metadata_namespace_proxy.py @@ -12,12 +12,11 @@ # License for the specific language governing permissions and limitations # under the License. -import socket - import mock import testtools import webob +from neutron.agent.linux import utils as agent_utils from neutron.agent.metadata import namespace_proxy as ns_proxy from neutron.common import exceptions from neutron.common import utils @@ -36,24 +35,6 @@ class FakeConf(object): metadata_proxy_shared_secret = 'secret' -class TestUnixDomainHttpConnection(base.BaseTestCase): - def test_connect(self): - with mock.patch.object(ns_proxy, 'cfg') as cfg: - cfg.CONF.metadata_proxy_socket = '/the/path' - with mock.patch('socket.socket') as socket_create: - conn = ns_proxy.UnixDomainHTTPConnection('169.254.169.254', - timeout=3) - - conn.connect() - - socket_create.assert_has_calls([ - mock.call(socket.AF_UNIX, socket.SOCK_STREAM), - mock.call().settimeout(3), - mock.call().connect('/the/path')] - ) - self.assertEqual(conn.timeout, 3) - - class TestNetworkMetadataProxyHandler(base.BaseTestCase): def setUp(self): super(TestNetworkMetadataProxyHandler, self).setUp() @@ -111,7 +92,7 @@ class TestNetworkMetadataProxyHandler(base.BaseTestCase): 'X-Forwarded-For': '192.168.1.1', 'X-Neutron-Router-ID': 'router_id' }, - connection_type=ns_proxy.UnixDomainHTTPConnection, + connection_type=agent_utils.UnixDomainHTTPConnection, body='' )] ) @@ -141,7 +122,7 @@ class TestNetworkMetadataProxyHandler(base.BaseTestCase): 'X-Forwarded-For': '192.168.1.1', 'X-Neutron-Network-ID': 'network_id' }, - connection_type=ns_proxy.UnixDomainHTTPConnection, + connection_type=agent_utils.UnixDomainHTTPConnection, body='' )] ) @@ -171,7 +152,7 @@ class TestNetworkMetadataProxyHandler(base.BaseTestCase): 'X-Forwarded-For': '192.168.1.1', 'X-Neutron-Network-ID': 'network_id' }, - connection_type=ns_proxy.UnixDomainHTTPConnection, + connection_type=agent_utils.UnixDomainHTTPConnection, body='' )] ) @@ -211,7 +192,7 @@ class TestNetworkMetadataProxyHandler(base.BaseTestCase): 'X-Forwarded-For': '192.168.1.1', 'X-Neutron-Network-ID': 'network_id' }, - connection_type=ns_proxy.UnixDomainHTTPConnection, + connection_type=agent_utils.UnixDomainHTTPConnection, body='' )] ) @@ -240,7 +221,7 @@ class TestNetworkMetadataProxyHandler(base.BaseTestCase): 'X-Forwarded-For': '192.168.1.1', 'X-Neutron-Network-ID': 'network_id' }, - connection_type=ns_proxy.UnixDomainHTTPConnection, + connection_type=agent_utils.UnixDomainHTTPConnection, body='' )] ) @@ -267,7 +248,7 @@ class TestNetworkMetadataProxyHandler(base.BaseTestCase): 'X-Forwarded-For': '192.168.1.1', 'X-Neutron-Network-ID': 'network_id' }, - connection_type=ns_proxy.UnixDomainHTTPConnection, + connection_type=agent_utils.UnixDomainHTTPConnection, body='' )] ) -- 2.45.2