Added wsgi functionality

Because of copy-pasted wsgi functionality in projects(nova, cinder,
glance, etc.) it is added to oslo.service with perspective to remove
it from other projects.
DocImpact

Change-Id: If8840168f10cc3561f4f01e6d456d6b4fd1de8b5
This commit is contained in:
Marian Horban 2015-07-11 11:18:48 -04:00
parent ae464221bc
commit 6dd3ad50b6
11 changed files with 926 additions and 0 deletions

View File

@ -58,6 +58,9 @@ files.
* :func:`oslo.service.sslutils <oslo_service.sslutils.list_opts>`
The options from the sslutils module for the [ssl] section.
* :func:`oslo.service.wsgi <oslo_service.wsgi.list_opts>`
The options from the wsgi module for the [DEFAULT] section.
**ATTENTION:** The library doesn't provide an oslo.service entry point.
.. code-block:: bash

View File

@ -41,6 +41,42 @@ service_opts = [
'options when starting a service (at DEBUG level).'),
]
wsgi_opts = [
cfg.StrOpt('api_paste_config',
default="api-paste.ini",
help='File name for the paste.deploy config for api service'),
cfg.StrOpt('wsgi_log_format',
default='%(client_ip)s "%(request_line)s" status: '
'%(status_code)s len: %(body_length)s time:'
' %(wall_seconds).7f',
help='A python format string that is used as the template to '
'generate log lines. The following values can be'
'formatted into it: client_ip, date_time, request_line, '
'status_code, body_length, wall_seconds.'),
cfg.IntOpt('tcp_keepidle',
default=600,
help="Sets the value of TCP_KEEPIDLE in seconds for each "
"server socket. Not supported on OS X."),
cfg.IntOpt('wsgi_default_pool_size',
default=1000,
help="Size of the pool of greenthreads used by wsgi"),
cfg.IntOpt('max_header_line',
default=16384,
help="Maximum line size of message headers to be accepted. "
"max_header_line may need to be increased when using "
"large tokens (typically those generated by the "
"Keystone v3 API with big service catalogs)."),
cfg.BoolOpt('wsgi_keep_alive',
default=True,
help="If False, closes the client socket connection "
"explicitly."),
cfg.IntOpt('client_socket_timeout', default=900,
help="Timeout for client connections' socket operations. "
"If an incoming connection is idle for this number of "
"seconds it will be closed. A value of '0' means "
"wait forever."),
]
ssl_opts = [
cfg.StrOpt('ca_file',
help="CA certificate file to use to verify "

View File

@ -0,0 +1,40 @@
-----BEGIN CERTIFICATE-----
MIIHADCCBOigAwIBAgIJAOjPGLL9VDhjMA0GCSqGSIb3DQEBDQUAMIGwMQswCQYD
VQQGEwJVUzEOMAwGA1UECBMFVGV4YXMxDzANBgNVBAcTBkF1c3RpbjEdMBsGA1UE
ChMUT3BlblN0YWNrIEZvdW5kYXRpb24xHTAbBgNVBAsTFE9wZW5TdGFjayBEZXZl
bG9wZXJzMRAwDgYDVQQDEwdUZXN0IENBMTAwLgYJKoZIhvcNAQkBFiFvcGVuc3Rh
Y2stZGV2QGxpc3RzLm9wZW5zdGFjay5vcmcwHhcNMTUwMTA4MDIyOTEzWhcNMjUw
MTA4MDIyOTEzWjCBsDELMAkGA1UEBhMCVVMxDjAMBgNVBAgTBVRleGFzMQ8wDQYD
VQQHEwZBdXN0aW4xHTAbBgNVBAoTFE9wZW5TdGFjayBGb3VuZGF0aW9uMR0wGwYD
VQQLExRPcGVuU3RhY2sgRGV2ZWxvcGVyczEQMA4GA1UEAxMHVGVzdCBDQTEwMC4G
CSqGSIb3DQEJARYhb3BlbnN0YWNrLWRldkBsaXN0cy5vcGVuc3RhY2sub3JnMIIC
IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAwILIMebpHYK1E1zhyi6713GG
TQ9DFeLOE1T25+XTJqAkO7efQzZfB8QwCXy/8bmbhmKgQQ7APuuDci8SKCkYeWCx
qJRGmg0tZVlj5gCfrV2u+olwS+XyaOGCFkYScs6D34BaE2rGD2GDryoSPc2feAt6
X4+ZkDPZnvaHQP6j9Ofq/4WmsECEas0IO5X8SDF8afA47U9ZXFkcgQK6HCHDcokL
aaZxEyZFSaPex6ZAESNthkGOxEThRPxAkJhqYCeMl3Hff98XEUcFNzuAOmcnQJJg
RemwJO2hS5KS3Y3p9/nBRlh3tSAG1nbY5kXSpyaq296D9x/esnXlt+9JUmn1rKyv
maFBC/SbzyyQoO3MT5r8rKte0bulLw1bZOZNlhxSv2KCg5RD6vlNrnpsZszw4nj2
8fBroeFp0JMeT8jcqGs3qdm8sXLcBgiTalLYtiCNV9wZjOduQotuFN6mDwZvfa6h
zZjcBNfqeLyTEnFb5k6pIla0wydWx/jvBAzoxOkEcVjak747A+p/rriD5hVUBH0B
uNaWcEgKe9jcHnLvU8hUxFtgPxUHOOR+eMa+FS3ApKf9sJ/zVUq0uxyA9hUnsvnq
v/CywLSvaNKBiKQTL0QLEXnw6EQb7g/XuwC5mmt+l30wGh9M1U/QMaU/+YzT4sVL
TXIHJ7ExRTbEecbNbjsCAwEAAaOCARkwggEVMB0GA1UdDgQWBBQTWz2WEB0sJg9c
xfM5JeJMIAJq0jCB5QYDVR0jBIHdMIHagBQTWz2WEB0sJg9cxfM5JeJMIAJq0qGB
tqSBszCBsDELMAkGA1UEBhMCVVMxDjAMBgNVBAgTBVRleGFzMQ8wDQYDVQQHEwZB
dXN0aW4xHTAbBgNVBAoTFE9wZW5TdGFjayBGb3VuZGF0aW9uMR0wGwYDVQQLExRP
cGVuU3RhY2sgRGV2ZWxvcGVyczEQMA4GA1UEAxMHVGVzdCBDQTEwMC4GCSqGSIb3
DQEJARYhb3BlbnN0YWNrLWRldkBsaXN0cy5vcGVuc3RhY2sub3JnggkA6M8Ysv1U
OGMwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQ0FAAOCAgEAIfAD6uVorT5WomG1
2DWRm3kuwa+EDimgVF6VRvxCzyHx7e/6KJQj149KpMQ6e0ZPjqQw+pZ+jJSgq6TP
MEjCHgIDwdKhi9LmQWIlo8xdzgfZW2VQkVLvwkqAnWWhCy9oGc/Ypk8pjiZfCx+/
DSJBbFnopI9f8epAKMq7N3jJyEMoTctzmI0KckrZnJ1Gq4MZpoxGmkJiGhWoUk8p
r8apXZ6B1DzO1XxpGw2BIcrUC3bQS/vPrg5/XbyaAu2BSgu6iF7ULqkBsEd0yK/L
i2gO9eTacaX3zJBQOlMJFsIAgIiVw6Rq6BuhU9zxDoopY4feta/NDOpk1OjY3MV7
4rcLTU6XYaItMDRe+dmjBOK+xspsaCU4kHEkA7mHL5YZhEEWLHj6QY8tAiIQMVQZ
RuTpQIbNkjLW8Ls+CbwL2LkUFB19rKu9tFpzEJ1IIeFmt5HZsL5ri6W2qkSPIbIe
Qq15kl/a45jgBbgn2VNA5ecjW20hhXyaS9AKWXK+AeFBaFIFDUrB2UP4YSDbJWUJ
0LKe+QuumXdl+iRdkgb1Tll7qme8gXAeyzVGHK2AsaBg+gkEeSyVLRKIixceyy+3
6yqlKJhk2qeV3ceOfVm9ZdvRlzWyVctaTcGIpDFqf4y8YyVhL1e2KGKcmYtbLq+m
rtku4CM3HldxcM4wqSB1VcaTX8o=
-----END CERTIFICATE-----

View File

@ -0,0 +1,51 @@
-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,41 @@
-----BEGIN CERTIFICATE-----
MIIHHjCCBQagAwIBAgIBATANBgkqhkiG9w0BAQ0FADCBsDELMAkGA1UEBhMCVVMx
DjAMBgNVBAgTBVRleGFzMQ8wDQYDVQQHEwZBdXN0aW4xHTAbBgNVBAoTFE9wZW5T
dGFjayBGb3VuZGF0aW9uMR0wGwYDVQQLExRPcGVuU3RhY2sgRGV2ZWxvcGVyczEQ
MA4GA1UEAxMHVGVzdCBDQTEwMC4GCSqGSIb3DQEJARYhb3BlbnN0YWNrLWRldkBs
aXN0cy5vcGVuc3RhY2sub3JnMB4XDTE1MDEwODAyNTQzNVoXDTI1MDEwODAyNTQz
NVoweDELMAkGA1UEBhMCVVMxDjAMBgNVBAgTBVRleGFzMQ8wDQYDVQQHEwZBdXN0
aW4xHTAbBgNVBAoTFE9wZW5TdGFjayBGb3VuZGF0aW9uMR0wGwYDVQQLExRPcGVu
U3RhY2sgRGV2ZWxvcGVyczEKMAgGA1UEAxQBKjCCAiIwDQYJKoZIhvcNAQEBBQAD
ggIPADCCAgoCggIBANBJtvyhMKBn397hE7x9Ce/Ny+4ENQfr9VrHuvGNCR3W/uUb
QafdNdZCYNAGPrq2T3CEYK0IJxZjr2HuTcSK9StBMFauTeIPqVUVkO3Tjq1Rkv+L
np/e6DhHkjCU6Eq/jIw3ic0QoxLygTybGxXgJgVoBzGsJufzOQ14tfkzGeGyE3L5
z5DpCNQqWLWF7soMx3kM5hBm+LWeoiBPjmsEXQY+UYiDlSLW/6I855X/wwDW5+Ot
P6/1lWUfcyAyIqj3t0pmxZeY7xQnabWjhXT2dTK+dlwRjb77w665AgeF1R5lpTvU
yT1aQwgH1kd9GeQbkBDwWSVLH9boPPgdMLtX2ipUgQAAEhIOUWXOYZVHVNXhV6Cr
jAgvfdF39c9hmuXovPP24ikW4L+d5RPE7Vq9KJ4Uzijw9Ghu4lQQCRZ8SCNZIYJn
Tz53+6fs93WwnnEPto9tFRKeNWt3jx/wjluDFhhBTZO4snNIq9xnCYSEQAIsRBVW
Ahv7LqWLigUy7a9HMIyi3tQEZN9NCDy4BNuJDu33XWLLVMwNrIiR5mdCUFoRKt/E
+YPj7bNlzZMTSGLoBFPM71Lnfym9HazHDE1KxvT4gzYMubK4Y07meybiL4QNvU08
ITgFU6DAGob+y/GHqw+bmez5y0F/6FlyV+SiSrbVEEtzp9Ewyrxb85OJFK0tAgMB
AAGjggF4MIIBdDBLBgNVHREERDBCgglsb2NhbGhvc3SCDWlwNi1sb2NhbGhvc3SC
CTEyNy4wLjAuMYIDOjoxhwR/AAABhxAAAAAAAAAAAAAAAAAAAAABMB0GA1UdDgQW
BBSjWxD0qedj9eeGUWyGphy5PU67dDCB5QYDVR0jBIHdMIHagBQTWz2WEB0sJg9c
xfM5JeJMIAJq0qGBtqSBszCBsDELMAkGA1UEBhMCVVMxDjAMBgNVBAgTBVRleGFz
MQ8wDQYDVQQHEwZBdXN0aW4xHTAbBgNVBAoTFE9wZW5TdGFjayBGb3VuZGF0aW9u
MR0wGwYDVQQLExRPcGVuU3RhY2sgRGV2ZWxvcGVyczEQMA4GA1UEAxMHVGVzdCBD
QTEwMC4GCSqGSIb3DQEJARYhb3BlbnN0YWNrLWRldkBsaXN0cy5vcGVuc3RhY2su
b3JnggkA6M8Ysv1UOGMwCQYDVR0TBAIwADATBgNVHSUEDDAKBggrBgEFBQcDATAN
BgkqhkiG9w0BAQ0FAAOCAgEAIGx/acXQEiGYFBJUduE6/Y6LBuHEVMcj0yfbLzja
Eb35xKWHuX7tgQPwXy6UGlYM8oKIptIp/9eEuYXte6u5ncvD7e/JldCUVd0fW8hm
fBOhfqVstcTmlfZ6WqTJD6Bp/FjUH+8qf8E+lsjNy7i0EsmcQOeQm4mkocHG1AA4
MEeuDg33lV6XCjW450BoZ/FTfwZSuTlGgFlEzUUrAe/ETdajF9G9aJ+0OvXzE1tU
pvbvkU8eg4pLXxrzboOhyQMEmCikdkMYjo/0ZQrXrrJ1W8mCinkJdz6CToc7nUkU
F8tdAY0rKMEM8SYHngMJU2943lpGbQhE5B4oms8I+SMTyCVz2Vu5I43Px68Y0GUN
Bn5qu0w2Vj8eradoPF8pEAIVICIvlbiRepPbNZ7FieSsY2TEfLtxBd2DLE1YWeE5
p/RDBxqcDrGQuSg6gFSoLEhYgQcGnYgD75EIE8f/LrHFOAeSYEOhibFbK5G8p/2h
EHcKZ9lvTgqwHn0FiTqZ3LWxVFsZiTsiyXErpJ2Nu2WTzo0k1xJMUpJqHuUZraei
N5fA5YuDp2ShXRoZyVieRvp0TCmm6sHL8Pn0K8weJchYrvV1yvPKeuISN/fVCQev
88yih5Rh5R2szwoY3uVImpd99bMm0e1bXrQug43ZUz9rC4ABN6+lZvuorDWRVI7U
I1M=
-----END CERTIFICATE-----

View File

@ -0,0 +1,51 @@
-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,369 @@
# Copyright 2011 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# 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.
"""Unit tests for `wsgi`."""
import os.path
import platform
import socket
import tempfile
import testtools
import eventlet
import eventlet.wsgi
import mock
import requests
import webob
from oslo_config import cfg
from oslo_config import fixture as config
from oslo_service import _options
from oslo_service import sslutils
from oslo_service import wsgi
from oslo_utils import netutils
from oslotest import base as test_base
from oslotest import moxstubout
SSL_CERT_DIR = os.path.normpath(os.path.join(
os.path.dirname(os.path.abspath(__file__)),
'ssl_cert'))
CONF = cfg.CONF
class WsgiTestCase(test_base.BaseTestCase):
"""Base class for WSGI tests."""
def setUp(self):
super(WsgiTestCase, self).setUp()
self.conf_fixture = self.useFixture(config.Config())
self.conf_fixture.register_opts(_options.wsgi_opts)
self.conf = self.conf_fixture.conf
self.config = self.conf_fixture.config
self.conf(args=[], default_config_files=[])
class TestLoaderNothingExists(WsgiTestCase):
"""Loader tests where os.path.exists always returns False."""
def setUp(self):
super(TestLoaderNothingExists, self).setUp()
mox_fixture = self.useFixture(moxstubout.MoxStubout())
self.stubs = mox_fixture.stubs
self.stubs.Set(os.path, 'exists', lambda _: False)
def test_relpath_config_not_found(self):
self.config(api_paste_config='api-paste.ini')
self.assertRaises(
wsgi.ConfigNotFound,
wsgi.Loader,
self.conf
)
def test_asbpath_config_not_found(self):
self.config(api_paste_config='/etc/openstack-srv/api-paste.ini')
self.assertRaises(
wsgi.ConfigNotFound,
wsgi.Loader,
self.conf
)
class TestLoaderNormalFilesystem(WsgiTestCase):
"""Loader tests with normal filesystem (unmodified os.path module)."""
_paste_config = """
[app:test_app]
use = egg:Paste#static
document_root = /tmp
"""
def setUp(self):
super(TestLoaderNormalFilesystem, self).setUp()
self.paste_config = tempfile.NamedTemporaryFile(mode="w+t")
self.paste_config.write(self._paste_config.lstrip())
self.paste_config.seek(0)
self.paste_config.flush()
self.config(api_paste_config=self.paste_config.name)
self.loader = wsgi.Loader(CONF)
def test_config_found(self):
self.assertEqual(self.paste_config.name, self.loader.config_path)
def test_app_not_found(self):
self.assertRaises(
wsgi.PasteAppNotFound,
self.loader.load_app,
"nonexistent app",
)
def test_app_found(self):
url_parser = self.loader.load_app("test_app")
self.assertEqual("/tmp", url_parser.directory)
def tearDown(self):
self.paste_config.close()
super(TestLoaderNormalFilesystem, self).tearDown()
class TestWSGIServer(WsgiTestCase):
"""WSGI server tests."""
def setUp(self):
super(TestWSGIServer, self).setUp()
def test_no_app(self):
server = wsgi.Server(self.conf, "test_app", None)
self.assertEqual("test_app", server.name)
def test_custom_max_header_line(self):
self.config(max_header_line=4096) # Default value is 16384
wsgi.Server(self.conf, "test_custom_max_header_line", None)
self.assertEqual(self.conf.max_header_line,
eventlet.wsgi.MAX_HEADER_LINE)
def test_start_random_port(self):
server = wsgi.Server(self.conf, "test_random_port", None,
host="127.0.0.1", port=0)
server.start()
self.assertNotEqual(0, server.port)
server.stop()
server.wait()
@testtools.skipIf(not netutils.is_ipv6_enabled(), "no ipv6 support")
def test_start_random_port_with_ipv6(self):
server = wsgi.Server(self.conf, "test_random_port", None,
host="::1", port=0)
server.start()
self.assertEqual("::1", server.host)
self.assertNotEqual(0, server.port)
server.stop()
server.wait()
@testtools.skipIf(platform.mac_ver()[0] != '',
'SO_REUSEADDR behaves differently '
'on OSX, see bug 1436895')
def test_socket_options_for_simple_server(self):
# test normal socket options has set properly
self.config(tcp_keepidle=500)
server = wsgi.Server(self.conf, "test_socket_options", None,
host="127.0.0.1", port=0)
server.start()
sock = server._socket
self.assertEqual(1, sock.getsockopt(socket.SOL_SOCKET,
socket.SO_REUSEADDR))
self.assertEqual(1, sock.getsockopt(socket.SOL_SOCKET,
socket.SO_KEEPALIVE))
if hasattr(socket, 'TCP_KEEPIDLE'):
self.assertEqual(self.conf.tcp_keepidle,
sock.getsockopt(socket.IPPROTO_TCP,
socket.TCP_KEEPIDLE))
self.assertFalse(server._server.dead)
server.stop()
server.wait()
self.assertTrue(server._server.dead)
def test_server_pool_waitall(self):
# test pools waitall method gets called while stopping server
server = wsgi.Server(self.conf, "test_server", None, host="127.0.0.1")
server.start()
with mock.patch.object(server._pool,
'waitall') as mock_waitall:
server.stop()
server.wait()
mock_waitall.assert_called_once_with()
def test_uri_length_limit(self):
eventlet.monkey_patch(os=False, thread=False)
server = wsgi.Server(self.conf, "test_uri_length_limit", None,
host="127.0.0.1", max_url_len=16384, port=33337)
server.start()
self.assertFalse(server._server.dead)
uri = "http://127.0.0.1:%d/%s" % (server.port, 10000 * 'x')
resp = requests.get(uri, proxies={"http": ""})
eventlet.sleep(0)
self.assertNotEqual(resp.status_code,
requests.codes.REQUEST_URI_TOO_LARGE)
uri = "http://127.0.0.1:%d/%s" % (server.port, 20000 * 'x')
resp = requests.get(uri, proxies={"http": ""})
eventlet.sleep(0)
self.assertEqual(resp.status_code,
requests.codes.REQUEST_URI_TOO_LARGE)
server.stop()
server.wait()
def test_reset_pool_size_to_default(self):
server = wsgi.Server(self.conf, "test_resize", None,
host="127.0.0.1", max_url_len=16384)
server.start()
# Stopping the server, which in turn sets pool size to 0
server.stop()
self.assertEqual(server._pool.size, 0)
# Resetting pool size to default
server.reset()
server.start()
self.assertEqual(server._pool.size, CONF.wsgi_default_pool_size)
def test_client_socket_timeout(self):
self.config(client_socket_timeout=5)
# mocking eventlet spawn method to check it is called with
# configured 'client_socket_timeout' value.
with mock.patch.object(eventlet,
'spawn') as mock_spawn:
server = wsgi.Server(self.conf, "test_app", None,
host="127.0.0.1", port=0)
server.start()
_, kwargs = mock_spawn.call_args
self.assertEqual(self.conf.client_socket_timeout,
kwargs['socket_timeout'])
server.stop()
def test_wsgi_keep_alive(self):
self.config(wsgi_keep_alive=False)
# mocking eventlet spawn method to check it is called with
# configured 'wsgi_keep_alive' value.
with mock.patch.object(eventlet,
'spawn') as mock_spawn:
server = wsgi.Server(self.conf, "test_app", None,
host="127.0.0.1", port=0)
server.start()
_, kwargs = mock_spawn.call_args
self.assertEqual(self.conf.wsgi_keep_alive,
kwargs['keepalive'])
server.stop()
class TestWSGIServerWithSSL(WsgiTestCase):
"""WSGI server with SSL tests."""
def setUp(self):
super(TestWSGIServerWithSSL, self).setUp()
self.conf_fixture.register_opts(_options.ssl_opts,
sslutils.config_section)
cert_file_name = os.path.join(SSL_CERT_DIR, 'certificate.crt')
key_file_name = os.path.join(SSL_CERT_DIR, 'privatekey.key')
eventlet.monkey_patch(os=False, thread=False)
self.config(cert_file=cert_file_name,
key_file=key_file_name,
group=sslutils.config_section)
def test_ssl_server(self):
def test_app(env, start_response):
start_response('200 OK', {})
return ['PONG']
fake_ssl_server = wsgi.Server(self.conf, "fake_ssl", test_app,
host="127.0.0.1", port=0, use_ssl=True)
fake_ssl_server.start()
self.assertNotEqual(0, fake_ssl_server.port)
cli = eventlet.connect(("localhost", fake_ssl_server.port))
ca_certs_name = os.path.join(SSL_CERT_DIR, 'ca.crt')
cli = eventlet.wrap_ssl(cli, ca_certs=ca_certs_name)
cli.write('POST / HTTP/1.1\r\nHost: localhost\r\n'
'Connection: close\r\nContent-length:4\r\n\r\nPING')
response = cli.read(8192)
self.assertEqual(response[-4:], "PONG")
fake_ssl_server.stop()
fake_ssl_server.wait()
def test_two_servers(self):
def test_app(env, start_response):
start_response('200 OK', {})
return ['PONG']
fake_ssl_server = wsgi.Server(self.conf, "fake_ssl", test_app,
host="127.0.0.1", port=0, use_ssl=True)
fake_ssl_server.start()
self.assertNotEqual(0, fake_ssl_server.port)
fake_server = wsgi.Server(self.conf, "fake", test_app,
host="127.0.0.1", port=0)
fake_server.start()
self.assertNotEqual(0, fake_server.port)
cli = eventlet.connect(("localhost", fake_ssl_server.port))
cli = eventlet.wrap_ssl(cli,
ca_certs=os.path.join(SSL_CERT_DIR, 'ca.crt'))
cli.write('POST / HTTP/1.1\r\nHost: localhost\r\n'
'Connection: close\r\nContent-length:4\r\n\r\nPING')
response = cli.read(8192)
self.assertEqual(response[-4:], "PONG")
cli = eventlet.connect(("localhost", fake_server.port))
cli.sendall('POST / HTTP/1.1\r\nHost: localhost\r\n'
'Connection: close\r\nContent-length:4\r\n\r\nPING')
response = cli.recv(8192)
self.assertEqual(response[-4:], "PONG")
fake_ssl_server.stop()
fake_ssl_server.wait()
@testtools.skipIf(platform.mac_ver()[0] != '',
'SO_REUSEADDR behaves differently '
'on OSX, see bug 1436895')
def test_socket_options_for_ssl_server(self):
# test normal socket options has set properly
self.config(tcp_keepidle=500)
server = wsgi.Server(self.conf, "test_socket_options", None,
host="127.0.0.1", port=0, use_ssl=True)
server.start()
sock = server._socket
self.assertEqual(1, sock.getsockopt(socket.SOL_SOCKET,
socket.SO_REUSEADDR))
self.assertEqual(1, sock.getsockopt(socket.SOL_SOCKET,
socket.SO_KEEPALIVE))
if hasattr(socket, 'TCP_KEEPIDLE'):
self.assertEqual(CONF.tcp_keepidle,
sock.getsockopt(socket.IPPROTO_TCP,
socket.TCP_KEEPIDLE))
server.stop()
server.wait()
@testtools.skipIf(not netutils.is_ipv6_enabled(), "no ipv6 support")
def test_app_using_ipv6_and_ssl(self):
greetings = 'Hello, World!!!'
@webob.dec.wsgify
def hello_world(req):
return greetings
server = wsgi.Server(self.conf, "fake_ssl",
hello_world,
host="::1",
port=0,
use_ssl=True)
server.start()
response = requests.get('https://[::1]:%d/' % server.port,
verify=os.path.join(SSL_CERT_DIR, 'ca.crt'))
self.assertEqual(greetings, response.text)
server.stop()
server.wait()

318
oslo_service/wsgi.py Normal file
View File

@ -0,0 +1,318 @@
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# Copyright 2010 OpenStack Foundation
# All Rights Reserved.
#
# 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.
"""Utility methods for working with WSGI servers."""
from __future__ import print_function
import copy
import os
import socket
import eventlet
import eventlet.wsgi
import greenlet
from paste import deploy
import routes.middleware
import webob.dec
import webob.exc
from oslo_log import log as logging
from oslo_service import _options
from oslo_service import sslutils
from oslo_service._i18n import _, _LE, _LI
LOG = logging.getLogger(__name__)
def list_opts():
"""Entry point for oslo-config-generator."""
return [(None, copy.deepcopy(_options.wsgi_opts))]
class InvalidInput(Exception):
message = _("Invalid input received: "
"Unexpected argument for periodic task creation: %(arg)s.")
class Server(object):
"""Server class to manage a WSGI server, serving a WSGI application."""
def __init__(self, conf, name, app, host='0.0.0.0', port=0, pool_size=None,
protocol=eventlet.wsgi.HttpProtocol, backlog=128,
use_ssl=False, max_url_len=None):
"""Initialize, but do not start, a WSGI server.
:param name: Pretty name for logging.
:param app: The WSGI application to serve.
:param host: IP address to serve the application.
:param port: Port number to server the application.
:param pool_size: Maximum number of eventlets to spawn concurrently.
:param backlog: Maximum number of queued connections.
:param max_url_len: Maximum length of permitted URLs.
:returns: None
:raises: InvalidInput
"""
self.conf = conf
self.conf.register_opts(_options.wsgi_opts)
self.default_pool_size = self.conf.wsgi_default_pool_size
# Allow operators to customize http requests max header line size.
eventlet.wsgi.MAX_HEADER_LINE = conf.max_header_line
self.name = name
self.app = app
self._server = None
self._protocol = protocol
self.pool_size = pool_size or self.default_pool_size
self._pool = eventlet.GreenPool(self.pool_size)
self._logger = logging.getLogger("eventlet.wsgi.server")
self._use_ssl = use_ssl
self._max_url_len = max_url_len
self.client_socket_timeout = conf.client_socket_timeout or None
self.default_pool_size = conf.wsgi_default_pool_size
if backlog < 1:
raise InvalidInput(reason='The backlog must be more than 0')
bind_addr = (host, 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
try:
info = socket.getaddrinfo(bind_addr[0],
bind_addr[1],
socket.AF_UNSPEC,
socket.SOCK_STREAM)[0]
family = info[0]
bind_addr = info[-1]
except Exception:
family = socket.AF_INET
if self._use_ssl:
sslutils.is_enabled(conf)
try:
self._socket = eventlet.listen(bind_addr, family, backlog=backlog)
except EnvironmentError:
LOG.error(_LE("Could not bind to %(host)s:%(port)s"),
{'host': host, 'port': port})
raise
(self.host, self.port) = self._socket.getsockname()[0:2]
LOG.info(_LI("%(name)s listening on %(host)s:%(port)s"),
{'name': self.name, 'host': self.host, 'port': self.port})
def start(self):
"""Start serving a WSGI application.
:returns: None
"""
# The server socket object will be closed after server exits,
# but the underlying file descriptor will remain open, and will
# give bad file descriptor error. So duplicating the socket object,
# to keep file descriptor usable.
self.dup_socket = self._socket.dup()
self.dup_socket = self._set_socket_opts(self.dup_socket)
if self._use_ssl:
self.dup_socket = sslutils.wrap(self.conf, self.dup_socket)
wsgi_kwargs = {
'func': eventlet.wsgi.server,
'sock': self.dup_socket,
'site': self.app,
'protocol': self._protocol,
'custom_pool': self._pool,
'log': self._logger,
'log_format': self.conf.wsgi_log_format,
'debug': False,
'keepalive': self.conf.wsgi_keep_alive,
'socket_timeout': self.client_socket_timeout
}
if self._max_url_len:
wsgi_kwargs['url_length_limit'] = self._max_url_len
self._server = eventlet.spawn(**wsgi_kwargs)
def _set_socket_opts(self, _socket):
_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# sockets can hang around forever without keepalive
_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'):
_socket.setsockopt(socket.IPPROTO_TCP,
socket.TCP_KEEPIDLE,
self.conf.tcp_keepidle)
return _socket
def reset(self):
"""Reset server greenpool size to default.
:returns: None
"""
self._pool.resize(self.pool_size)
def stop(self):
"""Stops eventlet server. Doesn't allow accept new connecting.
:returns: None
"""
LOG.info(_LI("Stopping WSGI server."))
if self._server is not None:
# let eventlet close socket
self._pool.resize(0)
self._server.kill()
def wait(self):
"""Block, until the server has stopped.
Waits on the server's eventlet to finish, then returns.
:returns: None
"""
try:
if self._server is not None:
num = self._pool.running()
LOG.debug("Waiting WSGI server to finish %d requests.", num)
self._pool.waitall()
except greenlet.GreenletExit:
LOG.info(_LI("WSGI server has stopped."))
class Request(webob.Request):
pass
class Router(object):
"""WSGI middleware that maps incoming requests to WSGI apps."""
def __init__(self, mapper):
"""Create a router for the given routes.Mapper.
Each route in `mapper` must specify a 'controller', which is a
WSGI app to call. You'll probably want to specify an 'action' as
well and have your controller be an object that can route
the request to the action-specific method.
Examples:
mapper = routes.Mapper()
sc = ServerController()
# Explicit mapping of one route to a controller+action
mapper.connect(None, '/svrlist', controller=sc, action='list')
# Actions are all implicitly defined
mapper.resource('server', 'servers', controller=sc)
# Pointing to an arbitrary WSGI app. You can specify the
# {path_info:.*} parameter so the target app can be handed just that
# section of the URL.
mapper.connect(None, '/v1.0/{path_info:.*}', controller=BlogApp())
"""
self.map = mapper
self._router = routes.middleware.RoutesMiddleware(self._dispatch,
self.map)
@webob.dec.wsgify(RequestClass=Request)
def __call__(self, req):
"""Route the incoming request to a controller based on self.map.
If no match, return a 404.
"""
return self._router
@staticmethod
@webob.dec.wsgify(RequestClass=Request)
def _dispatch(req):
"""Dispatch the request to the appropriate controller.
Called by self._router after matching the incoming request to a route
and putting the information into req.environ. Either returns 404
or the routed WSGI app's response.
"""
match = req.environ['wsgiorg.routing_args'][1]
if not match:
return webob.exc.HTTPNotFound()
app = match['controller']
return app
class ConfigNotFound(Exception):
def __init__(self, path):
msg = _('Could not find config at %(path)s') % {'path': path}
super(ConfigNotFound, self).__init__(msg)
class PasteAppNotFound(Exception):
def __init__(self, name, path):
msg = (_("Could not load paste app '%(name)s' from %(path)s") %
{'name': name, 'path': path})
super(PasteAppNotFound, self).__init__(msg)
class Loader(object):
"""Used to load WSGI applications from paste configurations."""
def __init__(self, conf):
"""Initialize the loader, and attempt to find the config.
:param conf
:returns: None
"""
conf.register_opts(_options.wsgi_opts)
self.config_path = None
config_path = conf.api_paste_config
if not os.path.isabs(config_path):
self.config_path = conf.find_file(config_path)
elif os.path.exists(config_path):
self.config_path = config_path
if not self.config_path:
raise ConfigNotFound(path=config_path)
def load_app(self, name):
"""Return the paste URLMap wrapped WSGI application.
:param name: Name of the application to load.
:returns: Paste URLMap object wrapping the requested application.
:raises: `PasteAppNotFound`
"""
try:
LOG.debug("Loading app %(name)s from %(path)s",
{'name': name, 'path': self.config_path})
return deploy.loadapp("config:%s" % self.config_path, name=name)
except LookupError:
LOG.exception(_LE("Couldn't lookup app: %s"), name)
raise PasteAppNotFound(name=name, path=self.config_path)

View File

@ -3,11 +3,17 @@
# process, which may cause wedges in the gate later.
Babel>=1.3
WebOb>=1.2.3
eventlet>=0.17.4
greenlet>=0.3.2
monotonic>=0.3 # Apache-2.0
oslo.utils>=2.0.0 # Apache-2.0
oslo.concurrency>=2.3.0 # Apache-2.0
oslo.config>=2.1.0 # Apache-2.0
oslo.log>=1.8.0 # Apache-2.0
six>=1.9.0
oslo.i18n>=1.5.0 # Apache-2.0
PasteDeploy>=1.5.0
Routes!=2.0,!=2.1,>=1.12.3;python_version=='2.7'
Routes!=2.0,>=1.12.3;python_version!='2.7'
Paste

View File

@ -32,6 +32,7 @@ oslo.config.opts =
oslo.service.periodic_task = oslo_service.periodic_task:list_opts
oslo.service.service = oslo_service.service:list_opts
oslo.service.sslutils = oslo_service.sslutils:list_opts
oslo.service.wsgi = oslo_service.wsgi:list_opts
[build_sphinx]
source-dir = doc/source

10
tox.ini
View File

@ -19,6 +19,16 @@ commands = python setup.py testr --slowest --testr-args='{posargs}'
[testenv:pep8]
commands = flake8
[testenv:py34]
commands =
python -m testtools.run \
oslo_service.tests.test_eventlet_backdoor \
oslo_service.tests.test_loopingcall \
oslo_service.tests.test_periodic \
oslo_service.tests.test_service \
oslo_service.tests.test_systemd \
oslo_service.tests.test_threadgroup
[testenv:venv]
commands = {posargs}