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:
parent
ae464221bc
commit
6dd3ad50b6
@ -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
|
||||
|
@ -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 "
|
||||
|
40
oslo_service/tests/ssl_cert/ca.crt
Normal file
40
oslo_service/tests/ssl_cert/ca.crt
Normal 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-----
|
51
oslo_service/tests/ssl_cert/ca.key
Normal file
51
oslo_service/tests/ssl_cert/ca.key
Normal file
@ -0,0 +1,51 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
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
|
||||
-----END RSA PRIVATE KEY-----
|
41
oslo_service/tests/ssl_cert/certificate.crt
Normal file
41
oslo_service/tests/ssl_cert/certificate.crt
Normal 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-----
|
51
oslo_service/tests/ssl_cert/privatekey.key
Normal file
51
oslo_service/tests/ssl_cert/privatekey.key
Normal file
@ -0,0 +1,51 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
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
|
||||
-----END RSA PRIVATE KEY-----
|
369
oslo_service/tests/test_wsgi.py
Normal file
369
oslo_service/tests/test_wsgi.py
Normal 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
318
oslo_service/wsgi.py
Normal 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)
|
@ -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
|
||||
|
@ -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
10
tox.ini
@ -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}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user