From 4adc3cfb56db9e5c97014064b1f2bb5710e1295f Mon Sep 17 00:00:00 2001 From: hardik Date: Wed, 20 Jul 2016 13:00:11 +0530 Subject: [PATCH] Added support for SSL connection in mistra-api server Change-Id: I4e114a11191821f4ef9dec967fb76b0a34179119 Implements: blueprint mistral-api-server-https --- mistral/api/service.py | 55 +++++++++ mistral/cmd/launch.py | 33 +----- mistral/config.py | 12 ++ .../tests/unit/api/test_cors_middleware.py | 2 +- mistral/tests/unit/api/test_service.py | 109 ++++++++++++++++++ mistral/tests/unit/base.py | 5 + tools/config/config-generator.mistral.conf | 1 + 7 files changed, 188 insertions(+), 29 deletions(-) create mode 100644 mistral/api/service.py create mode 100644 mistral/tests/unit/api/test_service.py diff --git a/mistral/api/service.py b/mistral/api/service.py new file mode 100644 index 000000000..7242ebcb7 --- /dev/null +++ b/mistral/api/service.py @@ -0,0 +1,55 @@ +# Copyright 2016 NEC Corporation. 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. + +from oslo_concurrency import processutils +from oslo_config import cfg +from oslo_service import service +from oslo_service import wsgi + +from mistral.api import app + + +class WSGIService(service.ServiceBase): + """Provides ability to launch Mistral API from wsgi app.""" + + def __init__(self, name): + self.name = name + self.app = app.setup_app() + self.workers = (cfg.CONF.api.api_workers or + processutils.get_worker_count()) + + self.server = wsgi.Server( + cfg.CONF, + name, + self.app, + host=cfg.CONF.api.host, + port=cfg.CONF.api.port, + use_ssl=cfg.CONF.api.enable_ssl_api + ) + + def start(self): + self.server.start() + + def stop(self): + self.server.stop() + + def wait(self): + self.server.wait() + + def reset(self): + self.server.reset() + + +def process_launcher(): + return service.ProcessLauncher(cfg.CONF) diff --git a/mistral/cmd/launch.py b/mistral/cmd/launch.py index 63cbbe890..73ab49fec 100644 --- a/mistral/cmd/launch.py +++ b/mistral/cmd/launch.py @@ -27,7 +27,6 @@ eventlet.monkey_patch( import os -import six # If ../mistral/__init__.py exists, add ../ to Python search path, so that @@ -41,15 +40,7 @@ if os.path.exists(os.path.join(POSSIBLE_TOPDIR, 'mistral', '__init__.py')): from oslo_config import cfg from oslo_log import log as logging -if six.PY3 is True: - import socketserver -else: - import SocketServer as socketserver - -from wsgiref import simple_server -from wsgiref.simple_server import WSGIServer - -from mistral.api import app +from mistral.api import service as mistral_service from mistral import config from mistral.db.v2 import api as db_api from mistral.engine import default_engine as def_eng @@ -139,25 +130,11 @@ def launch_event_engine(): print("Stopping event_engine service...") -class ThreadingWSGIServer(socketserver.ThreadingMixIn, WSGIServer): - pass - - def launch_api(): - host = cfg.CONF.api.host - port = cfg.CONF.api.port - - api_server = simple_server.make_server( - host, - port, - app.setup_app(), - ThreadingWSGIServer - ) - - LOG.info("Mistral API is serving on http://%s:%s (PID=%s)" % - (host, port, os.getpid())) - - api_server.serve_forever() + launcher = mistral_service.process_launcher() + server = mistral_service.WSGIService('mistral_api') + launcher.launch_service(server, workers=server.workers) + launcher.wait() def launch_any(options): diff --git a/mistral/config.py b/mistral/config.py index 3806db58d..17ecc9e5b 100644 --- a/mistral/config.py +++ b/mistral/config.py @@ -65,6 +65,18 @@ api_opts = [ default=False, help='Enables the ability to delete action_execution which ' 'has no relationship with workflows.' + ), + cfg.BoolOpt( + 'enable_ssl_api', + default=False, + help='Enable the integrated stand-alone API to service requests' + 'via HTTPS instead of HTTP.' + ), + cfg.IntOpt( + 'api_workers', + help='Number of workers for Mistral API service ' + 'default is equal to the number of CPUs available if that can ' + 'be determined, else a default worker count of 1 is returned.' ) ] diff --git a/mistral/tests/unit/api/test_cors_middleware.py b/mistral/tests/unit/api/test_cors_middleware.py index 4c7260e18..45256b569 100644 --- a/mistral/tests/unit/api/test_cors_middleware.py +++ b/mistral/tests/unit/api/test_cors_middleware.py @@ -32,7 +32,7 @@ class TestCORSMiddleware(base.APITest): cfg.CONF.register_opts(cors_middleware.CORS_OPTS, 'cors') # Load up our valid domain values before the application is created. - cfg.CONF.set_override( + self.override_config( "allowed_origin", "http://valid.example.com", group='cors' diff --git a/mistral/tests/unit/api/test_service.py b/mistral/tests/unit/api/test_service.py new file mode 100644 index 000000000..df02a8caa --- /dev/null +++ b/mistral/tests/unit/api/test_service.py @@ -0,0 +1,109 @@ +# Copyright 2016 NEC Corporation. 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. + +import mock +from oslo_concurrency import processutils +from oslo_config import cfg +import pecan + +from mistral.api import service +from mistral.tests.unit import base + + +class TestWSGIService(base.BaseTest): + @mock.patch('mistral.api.app.setup_app') + @mock.patch.object(service.wsgi, 'Server') + def test_workers_set_default(self, wsgi_server, mock_app): + service_name = "mistral_api" + mock_app.return_value = pecan.testing.load_test_app({ + 'app': { + 'root': cfg.CONF.pecan.root, + 'modules': cfg.CONF.pecan.modules, + 'debug': cfg.CONF.pecan.debug, + 'auth_enable': cfg.CONF.pecan.auth_enable, + 'disable_cron_trigger_thread': True + } + }) + test_service = service.WSGIService(service_name) + + wsgi_server.assert_called_once_with( + cfg.CONF, + service_name, + test_service.app, + host='0.0.0.0', + port=8989, + use_ssl=False + ) + + @mock.patch('mistral.api.app.setup_app') + @mock.patch.object(service.wsgi, 'Server') + def test_workers_set_correct_setting(self, wsgi_server, mock_app): + self.override_config('api_workers', 8, group='api') + + mock_app.return_value = pecan.testing.load_test_app({ + 'app': { + 'root': cfg.CONF.pecan.root, + 'modules': cfg.CONF.pecan.modules, + 'debug': cfg.CONF.pecan.debug, + 'auth_enable': cfg.CONF.pecan.auth_enable, + 'disable_cron_trigger_thread': True + } + }) + test_service = service.WSGIService("mistral_api") + + self.assertEqual(8, test_service.workers) + + @mock.patch('mistral.api.app.setup_app') + @mock.patch.object(service.wsgi, 'Server') + def test_workers_set_zero_setting(self, wsgi_server, mock_app): + self.override_config('api_workers', 0, group='api') + + mock_app.return_value = pecan.testing.load_test_app({ + 'app': { + 'root': cfg.CONF.pecan.root, + 'modules': cfg.CONF.pecan.modules, + 'debug': cfg.CONF.pecan.debug, + 'auth_enable': cfg.CONF.pecan.auth_enable, + 'disable_cron_trigger_thread': True + } + }) + test_service = service.WSGIService("mistral_api") + + self.assertEqual(processutils.get_worker_count(), test_service.workers) + + @mock.patch('mistral.api.app.setup_app') + @mock.patch.object(service.wsgi, 'Server') + def test_wsgi_service_with_ssl_enabled(self, wsgi_server, mock_app): + self.override_config('enable_ssl_api', True, group='api') + + mock_app.return_value = pecan.testing.load_test_app({ + 'app': { + 'root': cfg.CONF.pecan.root, + 'modules': cfg.CONF.pecan.modules, + 'debug': cfg.CONF.pecan.debug, + 'auth_enable': cfg.CONF.pecan.auth_enable, + 'disable_cron_trigger_thread': True + } + }) + service_name = 'mistral_api' + srv = service.WSGIService(service_name) + + wsgi_server.assert_called_once_with( + cfg.CONF, + service_name, + srv.app, + host='0.0.0.0', + port=8989, + use_ssl=True + ) diff --git a/mistral/tests/unit/base.py b/mistral/tests/unit/base.py index f6a8fe9d9..497fc8305 100644 --- a/mistral/tests/unit/base.py +++ b/mistral/tests/unit/base.py @@ -202,6 +202,11 @@ class BaseTest(base.BaseTestCase): def _sleep(self, seconds): time.sleep(seconds) + def override_config(self, name, override, group=None): + """Cleanly override CONF variables.""" + cfg.CONF.set_override(name, override, group) + self.addCleanup(cfg.CONF.clear_override, name, group) + class DbTestCase(BaseTest): is_heavy_init_called = False diff --git a/tools/config/config-generator.mistral.conf b/tools/config/config-generator.mistral.conf index aee9ace83..677a2301a 100644 --- a/tools/config/config-generator.mistral.conf +++ b/tools/config/config-generator.mistral.conf @@ -7,3 +7,4 @@ namespace = keystonemiddleware.auth_token namespace = periodic.config namespace = oslo.log namespace = oslo.policy +namespace = oslo.service.sslutils