Browse Source

Merge "Wrap Flask into oslo.service"

Zuul 4 months ago
parent
commit
e8fa9e3272

+ 10
- 2
ironic_inspector/cmd/all.py View File

@@ -14,16 +14,24 @@
14 14
 
15 15
 import sys
16 16
 
17
+from oslo_config import cfg
18
+from oslo_service import service
19
+
20
+from ironic_inspector.common.rpc_service import RPCService
17 21
 from ironic_inspector.common import service_utils
18 22
 from ironic_inspector import wsgi_service
19 23
 
24
+CONF = cfg.CONF
25
+
20 26
 
21 27
 def main(args=sys.argv[1:]):
22 28
     # Parse config file and command line options, then start logging
23 29
     service_utils.prepare_service(args)
24 30
 
25
-    server = wsgi_service.WSGIService()
26
-    server.run()
31
+    launcher = service.ServiceLauncher(CONF, restart_method='mutate')
32
+    launcher.launch_service(wsgi_service.WSGIService())
33
+    launcher.launch_service(RPCService(CONF.host))
34
+    launcher.wait()
27 35
 
28 36
 
29 37
 if __name__ == '__main__':

+ 6
- 0
ironic_inspector/common/service_utils.py View File

@@ -12,6 +12,7 @@
12 12
 
13 13
 from oslo_config import cfg
14 14
 from oslo_log import log
15
+from oslo_service import sslutils
15 16
 
16 17
 from ironic_inspector.conf import opts
17 18
 
@@ -26,5 +27,10 @@ def prepare_service(args=None):
26 27
     opts.parse_args(args)
27 28
     log.setup(CONF, 'ironic_inspector')
28 29
 
30
+    # TODO(kaifeng) Remove deprecated options at T* cycle.
31
+    sslutils.register_opts(CONF)
32
+    CONF.set_default('cert_file', CONF.ssl_cert_path, group='ssl')
33
+    CONF.set_default('key_file', CONF.ssl_key_path, group='ssl')
34
+
29 35
     LOG.debug("Configuration:")
30 36
     CONF.log_opt_values(LOG, log.DEBUG)

+ 7
- 1
ironic_inspector/conf/default.py View File

@@ -52,9 +52,15 @@ _OPTS = [
52 52
                 help=_('SSL Enabled/Disabled')),
53 53
     cfg.StrOpt('ssl_cert_path',
54 54
                default='',
55
+               deprecated_for_removal=True,
56
+               deprecated_reason=_('This option will be superseded by '
57
+                                   '[ssl]cert_file.'),
55 58
                help=_('Path to SSL certificate')),
56 59
     cfg.StrOpt('ssl_key_path',
57 60
                default='',
61
+               deprecated_for_removal=True,
62
+               deprecated_reason=_('This option will be superseded by '
63
+                                   '[ssl]key_file.'),
58 64
                help=_('Path to SSL key')),
59 65
     cfg.IntOpt('max_concurrency',
60 66
                default=1000, min=2,
@@ -78,7 +84,7 @@ _OPTS = [
78 84
                 help=_('Whether the current installation of ironic-inspector '
79 85
                        'can manage PXE booting of nodes. If set to False, '
80 86
                        'the API will reject introspection requests with '
81
-                       'manage_boot missing or set to True.'))
87
+                       'manage_boot missing or set to True.')),
82 88
 ]
83 89
 
84 90
 

+ 34
- 153
ironic_inspector/test/unit/test_wsgi_service.py View File

@@ -11,15 +11,12 @@
11 11
 # See the License for the specific language governing permissions and
12 12
 # limitations under the License.
13 13
 
14
-import ssl
15
-import sys
16
-import unittest
17
-
18 14
 import eventlet  # noqa
19 15
 import fixtures
20 16
 import mock
21 17
 from oslo_config import cfg
22 18
 
19
+from ironic_inspector.common import service_utils
23 20
 from ironic_inspector.test import base as test_base
24 21
 from ironic_inspector import wsgi_service
25 22
 
@@ -32,9 +29,12 @@ class BaseWSGITest(test_base.BaseTest):
32 29
         super(BaseWSGITest, self).setUp()
33 30
         self.app = self.useFixture(fixtures.MockPatchObject(
34 31
             wsgi_service.app, 'app', autospec=True)).mock
32
+        self.server = self.useFixture(fixtures.MockPatchObject(
33
+            wsgi_service.wsgi, 'Server', autospec=True)).mock
35 34
         self.mock_log = self.useFixture(fixtures.MockPatchObject(
36 35
             wsgi_service, 'LOG')).mock
37 36
         self.service = wsgi_service.WSGIService()
37
+        self.service.server = self.server
38 38
 
39 39
 
40 40
 class TestWSGIServiceInitMiddleware(BaseWSGITest):
@@ -66,174 +66,55 @@ class TestWSGIServiceInitMiddleware(BaseWSGITest):
66 66
         self.mock_add_cors_middleware.assert_called_once_with(self.app)
67 67
 
68 68
 
69
-class TestWSGIServiceRun(BaseWSGITest):
69
+class TestWSGIService(BaseWSGITest):
70 70
     def setUp(self):
71
-        super(TestWSGIServiceRun, self).setUp()
71
+        super(TestWSGIService, self).setUp()
72 72
         self.mock__init_middleware = self.useFixture(fixtures.MockPatchObject(
73 73
             self.service, '_init_middleware')).mock
74
-        self.mock__create_ssl_context = self.useFixture(
75
-            fixtures.MockPatchObject(self.service, '_create_ssl_context')).mock
76
-        self.mock_shutdown = self.useFixture(fixtures.MockPatchObject(
77
-            self.service, 'shutdown')).mock
78 74
 
79 75
         # 'positive' settings
80 76
         CONF.set_override('listen_address', '42.42.42.42')
81 77
         CONF.set_override('listen_port', 42)
82 78
 
83
-    def test_run(self):
84
-        self.service.run()
79
+    def test_start(self):
80
+        self.service.start()
85 81
 
86
-        self.mock__create_ssl_context.assert_called_once_with()
87 82
         self.mock__init_middleware.assert_called_once_with()
88
-        self.app.run.assert_called_once_with(
89
-            host=CONF.listen_address, port=CONF.listen_port,
90
-            ssl_context=self.mock__create_ssl_context.return_value)
91
-        self.mock_shutdown.assert_called_once_with()
83
+        self.server.start.assert_called_once_with()
92 84
 
93
-    def test_run_no_ssl_context(self):
94
-        self.mock__create_ssl_context.return_value = None
85
+    def test_stop(self):
86
+        self.service.stop()
87
+        self.server.stop.assert_called_once_with()
95 88
 
96
-        self.service.run()
97
-        self.mock__create_ssl_context.assert_called_once_with()
98
-        self.mock__init_middleware.assert_called_once_with()
99
-        self.app.run.assert_called_once_with(
100
-            host=CONF.listen_address, port=CONF.listen_port)
101
-        self.mock_shutdown.assert_called_once_with()
89
+    def test_wait(self):
90
+        self.service.wait()
91
+        self.server.wait.assert_called_once_with()
102 92
 
103
-    def test_run_app_error(self):
104
-        class MyError(Exception):
105
-            pass
93
+    def test_reset(self):
94
+        self.service.reset()
95
+        self.server.reset.assert_called_once_with()
106 96
 
107
-        error = MyError('Oops!')
108
-        self.app.run.side_effect = error
109
-        self.service.run()
110 97
 
111
-        self.mock__create_ssl_context.assert_called_once_with()
112
-        self.mock__init_middleware.assert_called_once_with()
113
-        self.app.run.assert_called_once_with(
114
-            host=CONF.listen_address, port=CONF.listen_port,
115
-            ssl_context=self.mock__create_ssl_context.return_value)
116
-        self.mock_shutdown.assert_called_once_with(error=str(error))
98
+@mock.patch.object(service_utils.log, 'register_options', autospec=True)
99
+class TestSSLOptions(test_base.BaseTest):
117 100
 
101
+    def test_use_deprecated_options(self, mock_log):
102
+        CONF.set_override('ssl_cert_path', 'fake_cert_file')
103
+        CONF.set_override('ssl_key_path', 'fake_key_file')
118 104
 
119
-class TestWSGIServiceShutdown(BaseWSGITest):
120
-    def setUp(self):
121
-        super(TestWSGIServiceShutdown, self).setUp()
122
-        self.service = wsgi_service.WSGIService()
123
-        self.mock_rpc_service = mock.MagicMock()
124
-        self.service.rpc_service = self.mock_rpc_service
125
-        self.mock_exit = self.useFixture(fixtures.MockPatchObject(
126
-            wsgi_service.sys, 'exit')).mock
105
+        service_utils.prepare_service()
127 106
 
128
-    def test_shutdown(self):
129
-        class MyError(Exception):
130
-            pass
131
-        error = MyError('Oops!')
107
+        self.assertEqual(CONF.ssl.cert_file, 'fake_cert_file')
108
+        self.assertEqual(CONF.ssl.key_file, 'fake_key_file')
132 109
 
133
-        self.service.shutdown(error=error)
134
-        self.mock_rpc_service.stop.assert_called_once_with()
135
-        self.mock_exit.assert_called_once_with(error)
110
+    def test_use_ssl_options(self, mock_log):
111
+        CONF.set_override('ssl_cert_path', 'fake_cert_file')
112
+        CONF.set_override('ssl_key_path', 'fake_key_file')
136 113
 
114
+        service_utils.prepare_service()
137 115
 
138
-class TestCreateSSLContext(test_base.BaseTest):
139
-    def setUp(self):
140
-        super(TestCreateSSLContext, self).setUp()
141
-        self.app = mock.Mock()
142
-        self.service = wsgi_service.WSGIService()
116
+        CONF.set_override('cert_file', 'fake_new_cert', 'ssl')
117
+        CONF.set_override('key_file', 'fake_new_key', 'ssl')
143 118
 
144
-    def test_use_ssl_false(self):
145
-        CONF.set_override('use_ssl', False)
146
-        con = self.service._create_ssl_context()
147
-        self.assertIsNone(con)
148
-
149
-    @mock.patch.object(sys, 'version_info')
150
-    def test_old_python_returns_none(self, mock_version_info):
151
-        mock_version_info.__lt__.return_value = True
152
-        CONF.set_override('use_ssl', True)
153
-        con = self.service._create_ssl_context()
154
-        self.assertIsNone(con)
155
-
156
-    @unittest.skipIf(sys.version_info[:3] < (2, 7, 9),
157
-                     'This feature is unsupported in this version of python '
158
-                     'so the tests will be skipped')
159
-    @mock.patch.object(ssl, 'create_default_context', autospec=True)
160
-    def test_use_ssl_true(self, mock_cdc):
161
-        CONF.set_override('use_ssl', True)
162
-        m_con = mock_cdc()
163
-        con = self.service._create_ssl_context()
164
-        self.assertEqual(m_con, con)
165
-
166
-    @unittest.skipIf(sys.version_info[:3] < (2, 7, 9),
167
-                     'This feature is unsupported in this version of python '
168
-                     'so the tests will be skipped')
169
-    @mock.patch.object(ssl, 'create_default_context', autospec=True)
170
-    def test_only_key_path_provided(self, mock_cdc):
171
-        CONF.set_override('use_ssl', True)
172
-        CONF.set_override('ssl_key_path', '/some/fake/path')
173
-        mock_context = mock_cdc()
174
-        con = self.service._create_ssl_context()
175
-        self.assertEqual(mock_context, con)
176
-        self.assertFalse(mock_context.load_cert_chain.called)
177
-
178
-    @unittest.skipIf(sys.version_info[:3] < (2, 7, 9),
179
-                     'This feature is unsupported in this version of python '
180
-                     'so the tests will be skipped')
181
-    @mock.patch.object(ssl, 'create_default_context', autospec=True)
182
-    def test_only_cert_path_provided(self, mock_cdc):
183
-        CONF.set_override('use_ssl', True)
184
-        CONF.set_override('ssl_cert_path', '/some/fake/path')
185
-        mock_context = mock_cdc()
186
-        con = self.service._create_ssl_context()
187
-        self.assertEqual(mock_context, con)
188
-        self.assertFalse(mock_context.load_cert_chain.called)
189
-
190
-    @unittest.skipIf(sys.version_info[:3] < (2, 7, 9),
191
-                     'This feature is unsupported in this version of python '
192
-                     'so the tests will be skipped')
193
-    @mock.patch.object(ssl, 'create_default_context', autospec=True)
194
-    def test_both_paths_provided(self, mock_cdc):
195
-        key_path = '/some/fake/path/key'
196
-        cert_path = '/some/fake/path/cert'
197
-        CONF.set_override('use_ssl', True)
198
-        CONF.set_override('ssl_key_path', key_path)
199
-        CONF.set_override('ssl_cert_path', cert_path)
200
-        mock_context = mock_cdc()
201
-        con = self.service._create_ssl_context()
202
-        self.assertEqual(mock_context, con)
203
-        mock_context.load_cert_chain.assert_called_once_with(cert_path,
204
-                                                             key_path)
205
-
206
-    @unittest.skipIf(sys.version_info[:3] < (2, 7, 9),
207
-                     'This feature is unsupported in this version of python '
208
-                     'so the tests will be skipped')
209
-    @mock.patch.object(ssl, 'create_default_context', autospec=True)
210
-    def test_load_cert_chain_fails(self, mock_cdc):
211
-        CONF.set_override('use_ssl', True)
212
-        key_path = '/some/fake/path/key'
213
-        cert_path = '/some/fake/path/cert'
214
-        CONF.set_override('use_ssl', True)
215
-        CONF.set_override('ssl_key_path', key_path)
216
-        CONF.set_override('ssl_cert_path', cert_path)
217
-        mock_context = mock_cdc()
218
-        mock_context.load_cert_chain.side_effect = IOError('Boom!')
219
-        con = self.service._create_ssl_context()
220
-        self.assertEqual(mock_context, con)
221
-        mock_context.load_cert_chain.assert_called_once_with(cert_path,
222
-                                                             key_path)
223
-
224
-
225
-class TestWSGIServiceOnSigHup(BaseWSGITest):
226
-    def setUp(self):
227
-        super(TestWSGIServiceOnSigHup, self).setUp()
228
-        self.mock_spawn = self.useFixture(fixtures.MockPatchObject(
229
-            wsgi_service.eventlet, 'spawn')).mock
230
-        self.mock_mutate_conf = self.useFixture(fixtures.MockPatchObject(
231
-            wsgi_service.CONF, 'mutate_config_files')).mock
232
-
233
-    def test_on_sighup(self):
234
-        self.service._handle_sighup()
235
-        self.mock_spawn.assert_called_once_with(self.service._handle_sighup_bg)
236
-
237
-    def test_on_sighup_bg(self):
238
-        self.service._handle_sighup_bg()
239
-        self.mock_mutate_conf.assert_called_once_with()
119
+        self.assertEqual(CONF.ssl.cert_file, 'fake_new_cert')
120
+        self.assertEqual(CONF.ssl.key_file, 'fake_new_key')

+ 24
- 77
ironic_inspector/wsgi_service.py View File

@@ -10,16 +10,11 @@
10 10
 # License for the specific language governing permissions and limitations
11 11
 # under the License.
12 12
 
13
-import signal
14
-import ssl
15
-import sys
16
-
17
-import eventlet
18 13
 from oslo_config import cfg
19 14
 from oslo_log import log
20 15
 from oslo_service import service
16
+from oslo_service import wsgi
21 17
 
22
-from ironic_inspector.common.rpc_service import RPCService
23 18
 from ironic_inspector import main as app
24 19
 from ironic_inspector import utils
25 20
 
@@ -27,21 +22,22 @@ LOG = log.getLogger(__name__)
27 22
 CONF = cfg.CONF
28 23
 
29 24
 
30
-class WSGIService(object):
25
+class WSGIService(service.Service):
31 26
     """Provides ability to launch API from wsgi app."""
32 27
 
33 28
     def __init__(self):
34 29
         self.app = app.app
35
-        signal.signal(signal.SIGHUP, self._handle_sighup)
36
-        signal.signal(signal.SIGTERM, self._handle_sigterm)
37
-        self.rpc_service = RPCService(CONF.host)
30
+        self.server = wsgi.Server(CONF, 'ironic_inspector',
31
+                                  self.app,
32
+                                  host=CONF.listen_address,
33
+                                  port=CONF.listen_port,
34
+                                  use_ssl=CONF.use_ssl)
38 35
 
39 36
     def _init_middleware(self):
40 37
         """Initialize WSGI middleware.
41 38
 
42 39
         :returns: None
43 40
         """
44
-
45 41
         if CONF.auth_strategy != 'noauth':
46 42
             utils.add_auth_middleware(self.app)
47 43
         else:
@@ -49,80 +45,31 @@ class WSGIService(object):
49 45
                         ' configuration')
50 46
         utils.add_cors_middleware(self.app)
51 47
 
52
-    def _create_ssl_context(self):
53
-        if not CONF.use_ssl:
54
-            return
55
-
56
-        MIN_VERSION = (2, 7, 9)
57
-
58
-        if sys.version_info < MIN_VERSION:
59
-            LOG.warning(('Unable to use SSL in this version of Python: '
60
-                         '%(current)s, please ensure your version of Python '
61
-                         'is greater than %(min)s to enable this feature.'),
62
-                        {'current': '.'.join(map(str, sys.version_info[:3])),
63
-                         'min': '.'.join(map(str, MIN_VERSION))})
64
-            return
65
-
66
-        context = ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH)
67
-        if CONF.ssl_cert_path and CONF.ssl_key_path:
68
-            try:
69
-                context.load_cert_chain(CONF.ssl_cert_path, CONF.ssl_key_path)
70
-            except IOError as exc:
71
-                LOG.warning('Failed to load certificate or key from defined '
72
-                            'locations: %(cert)s and %(key)s, will continue '
73
-                            'to run with the default settings: %(exc)s',
74
-                            {'cert': CONF.ssl_cert_path,
75
-                             'key': CONF.ssl_key_path,
76
-                             'exc': exc})
77
-            except ssl.SSLError as exc:
78
-                LOG.warning('There was a problem with the loaded certificate '
79
-                            'and key, will continue to run with the default '
80
-                            'settings: %s', exc)
81
-        return context
82
-
83
-    def shutdown(self, error=None):
84
-        """Stop serving API.
48
+    def start(self):
49
+        """Start serving this service using loaded configuration.
85 50
 
86 51
         :returns: None
87 52
         """
88
-        LOG.debug('Shutting down')
89
-        self.rpc_service.stop()
90
-        sys.exit(error)
53
+        self._init_middleware()
54
+        self.server.start()
91 55
 
92
-    def run(self):
93
-        """Start serving this service using loaded application.
56
+    def stop(self):
57
+        """Stop serving this API.
94 58
 
95 59
         :returns: None
96 60
         """
97
-        app_kwargs = {'host': CONF.listen_address,
98
-                      'port': CONF.listen_port}
99
-
100
-        context = self._create_ssl_context()
101
-        if context:
102
-            app_kwargs['ssl_context'] = context
103
-
104
-        self._init_middleware()
61
+        self.server.stop()
105 62
 
106
-        LOG.info('Spawning RPC service')
107
-        service.launch(CONF, self.rpc_service,
108
-                       restart_method='mutate')
63
+    def wait(self):
64
+        """Wait for the service to stop serving this API.
109 65
 
110
-        try:
111
-            self.app.run(**app_kwargs)
112
-        except Exception as e:
113
-            self.shutdown(error=str(e))
114
-        else:
115
-            self.shutdown()
116
-
117
-    def _handle_sighup_bg(self, *args):
118
-        """Reload config on SIGHUP."""
119
-        CONF.mutate_config_files()
66
+        :returns: None
67
+        """
68
+        self.server.wait()
120 69
 
121
-    def _handle_sighup(self, *args):
122
-        eventlet.spawn(self._handle_sighup_bg, *args)
70
+    def reset(self):
71
+        """Reset server greenpool size to default.
123 72
 
124
-    def _handle_sigterm(self, *args):
125
-        # This is a workaround to ensure that shutdown() is done when recieving
126
-        # SIGTERM. Raising KeyboardIntrerrupt which won't be caught by any
127
-        # 'except Exception' clauses.
128
-        raise KeyboardInterrupt
73
+        :returns: None
74
+        """
75
+        self.server.reset()

+ 1
- 1
lower-constraints.txt View File

@@ -73,7 +73,7 @@ oslo.middleware==3.31.0
73 73
 oslo.policy==1.30.0
74 74
 oslo.rootwrap==5.8.0
75 75
 oslo.serialization==2.18.0
76
-oslo.service==1.30.0
76
+oslo.service==1.24.0
77 77
 oslo.utils==3.33.0
78 78
 oslotest==3.2.0
79 79
 packaging==17.1

+ 7
- 0
releasenotes/notes/deprecate-ssl-opts-40ce8f4618c786ef.yaml View File

@@ -0,0 +1,7 @@
1
+---
2
+deprecations:
3
+  - |
4
+    Configuration options ``[DEFAULT]ssl_cert_path`` and
5
+    ``[DEFAULT]ssl_key_path`` are deprecated for ironic-inspector now uses
6
+    oslo.service as underlying HTTP service instead of Werkzeug. Please use
7
+    ``[ssl]cert_file`` and ``[ssl]key_file``.

+ 1
- 0
requirements.txt View File

@@ -29,6 +29,7 @@ oslo.middleware>=3.31.0 # Apache-2.0
29 29
 oslo.policy>=1.30.0 # Apache-2.0
30 30
 oslo.rootwrap>=5.8.0 # Apache-2.0
31 31
 oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0
32
+oslo.service!=1.28.1,>=1.24.0 # Apache-2.0
32 33
 oslo.utils>=3.33.0 # Apache-2.0
33 34
 retrying!=1.3.0,>=1.2.3 # Apache-2.0
34 35
 six>=1.10.0 # MIT

+ 4
- 1
tools/config-generator.conf View File

@@ -4,6 +4,9 @@ namespace = ironic_inspector
4 4
 namespace = keystonemiddleware.auth_token
5 5
 namespace = oslo.db
6 6
 namespace = oslo.log
7
+namespace = oslo.messaging
7 8
 namespace = oslo.middleware.cors
8 9
 namespace = oslo.policy
9
-namespace = oslo.messaging
10
+namespace = oslo.service.service
11
+namespace = oslo.service.sslutils
12
+namespace = oslo.service.wsgi

Loading…
Cancel
Save