Use werkzeug to run Magnum API with SSL

wsgiref.simple_server is mono threaded process that can not
support SSL context. This patch aim to replace wsgiref.simple_server
with werkzeug for running development API server supporting SSL.

Change-Id: Ib4360d77030e4cce8abf5ea543d87b7982e0e285
Closes-Bug: #1614596
This commit is contained in:
Hieu LE 2016-08-23 10:57:24 +07:00
parent e974f5aae7
commit 70c803bfc1
5 changed files with 113 additions and 16 deletions

View File

@ -36,7 +36,17 @@ API_SERVICE_OPTS = [
cfg.StrOpt('api_paste_config',
default="api-paste.ini",
help="Configuration file for WSGI definition of API."
)
),
cfg.StrOpt('ssl_cert_file',
help="This option allows setting path to the SSL certificate "
"of API server. "),
cfg.StrOpt('ssl_key_file',
help="This option specifies the path to the file where SSL "
"private key of API server is stored when SSL is in "
"effect. "),
cfg.BoolOpt('enabled_ssl',
default=False,
help='Enable SSL Magnum API service')
]
CONF = cfg.CONF

View File

@ -16,22 +16,41 @@
import os
import sys
from wsgiref import simple_server
from oslo_config import cfg
from oslo_log import log as logging
from oslo_reports import guru_meditation_report as gmr
from werkzeug import serving
from magnum.api import app as api_app
from magnum.common import service
from magnum.i18n import _
from magnum.i18n import _LI
from magnum.objects import base
from magnum import version
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
def _get_ssl_configs(use_ssl):
if use_ssl:
cert_file = CONF.api.ssl_cert_file
key_file = CONF.api.ssl_key_file
if cert_file and not os.path.exists(cert_file):
raise RuntimeError(
_("Unable to find cert_file : %s") % cert_file)
if key_file and not os.path.exists(key_file):
raise RuntimeError(
_("Unable to find key_file : %s") % key_file)
return cert_file, key_file
else:
return None
def main():
service.prepare_service(sys.argv)
@ -42,15 +61,18 @@ def main():
app = api_app.load_app()
# SSL configuration
use_ssl = CONF.api.enabled_ssl
# Create the WSGI server and start it
host, port = cfg.CONF.api.host, cfg.CONF.api.port
srv = simple_server.make_server(host, port, app)
LOG.info(_LI('Starting server in PID %s'), os.getpid())
LOG.debug("Configuration:")
cfg.CONF.log_opt_values(LOG, logging.DEBUG)
LOG.info(_LI('serving on http://%(host)s:%(port)s'),
dict(host=host, port=port))
LOG.info(_LI('Serving on %(proto)s://%(host)s:%(port)s'),
dict(proto="https" if use_ssl else "http", host=host, port=port))
srv.serve_forever()
serving.run_simple(host, port, app,
ssl_context=_get_ssl_configs(use_ssl))

View File

@ -18,21 +18,79 @@ from magnum.cmd import api
from magnum.tests import base
# NOTE(hieulq): need to mock MagnumObject, otherwise other test cases
# will be failed because of setting wrong ovo indirection api
@mock.patch('magnum.objects.base.MagnumObject')
class TestMagnumAPI(base.TestCase):
# NOTE(hieulq): need to mock MagnumObject, otherwise other test cases
# will be failed because of setting wrong ovo indirection api
@mock.patch('magnum.objects.base.MagnumObject')
@mock.patch('wsgiref.simple_server.make_server')
@mock.patch('werkzeug.serving.run_simple')
@mock.patch.object(api, 'api_app')
@mock.patch('magnum.common.service.prepare_service')
def test_api(self, mock_prep, mock_app, mock_make, mock_base):
def test_api_http(self, mock_prep, mock_app, mock_run, mock_base):
api.main()
app = mock_app.load_app.return_value
server = mock_make.return_value
mock_prep.assert_called_once_with(mock.ANY)
mock_app.load_app.assert_called_once_with()
mock_make.assert_called_once_with(base.CONF.api.host,
base.CONF.api.port, app)
server.serve_forever.assert_called_once_with()
mock_run.assert_called_once_with(base.CONF.api.host,
base.CONF.api.port,
app, ssl_context=None)
@mock.patch('os.path.exists')
@mock.patch('werkzeug.serving.run_simple')
@mock.patch.object(api, 'api_app')
@mock.patch('magnum.common.service.prepare_service')
def test_api_https_no_cert(self, mock_prep, mock_app, mock_run,
mock_exist, mock_base):
self.config(enabled_ssl=True,
ssl_cert_file='tmp_crt',
group='api')
mock_exist.return_value = False
self.assertRaises(RuntimeError, api.main)
mock_prep.assert_called_once_with(mock.ANY)
mock_app.load_app.assert_called_once_with()
mock_run.assert_not_called()
mock_exist.assert_called_once_with('tmp_crt')
@mock.patch('os.path.exists')
@mock.patch('werkzeug.serving.run_simple')
@mock.patch.object(api, 'api_app')
@mock.patch('magnum.common.service.prepare_service')
def test_api_https_no_key(self, mock_prep, mock_app, mock_run,
mock_exist, mock_base):
self.config(enabled_ssl=True,
ssl_cert_file='tmp_crt',
ssl_key_file='tmp_key',
group='api')
mock_exist.side_effect = [True, False]
self.assertRaises(RuntimeError, api.main)
mock_prep.assert_called_once_with(mock.ANY)
mock_app.load_app.assert_called_once_with()
mock_run.assert_not_called()
mock_exist.assert_has_calls([mock.call('tmp_crt'),
mock.call('tmp_key')])
@mock.patch('os.path.exists')
@mock.patch('werkzeug.serving.run_simple')
@mock.patch.object(api, 'api_app')
@mock.patch('magnum.common.service.prepare_service')
def test_api_https(self, mock_prep, mock_app, mock_run,
mock_exist, mock_base):
self.config(enabled_ssl=True,
ssl_cert_file='tmp_crt',
ssl_key_file='tmp_key',
group='api')
mock_exist.side_effect = [True, True]
api.main()
app = mock_app.load_app.return_value
mock_prep.assert_called_once_with(mock.ANY)
mock_app.load_app.assert_called_once_with()
mock_exist.assert_has_calls([mock.call('tmp_crt'),
mock.call('tmp_key')])
mock_run.assert_called_once_with(base.CONF.api.host,
base.CONF.api.port, app,
ssl_context=('tmp_crt', 'tmp_key'))

View File

@ -0,0 +1,6 @@
---
upgrade:
- Magnum now support SSL for API service. User can enable SSL for API
via new 3 config options 'enabled_ssl', 'ssl_cert_file' and 'ssl_key_file'.
- Change default API development service from wsgiref simple_server to
werkzeug for better supporting SSL.

View File

@ -55,3 +55,4 @@ stevedore>=1.16.0 # Apache-2.0
taskflow>=1.26.0 # Apache-2.0
cryptography!=1.3.0,>=1.0 # BSD/Apache-2.0
urllib3>=1.15.1 # MIT
Werkzeug>=0.7 # BSD License