Preparing for service splitting

Creates new WSGIService class which keeps base API sercice
initialization functionality and serve flask application. Also
it will configure application for wsgi container[1].

Also creates new `cmd` directory for storing console scripts.

[1] https://governance.openstack.org/tc/goals/pike/deploy-api-in-wsgi.html

Related-Bug: #1525218
Change-Id: Ia64228c47a79a3008d435e8323a964f2bc45dfa7
This commit is contained in:
Anton Arefiev 2017-03-16 09:59:20 +02:00
parent 1583d70ef9
commit 50ed0bdbae
9 changed files with 472 additions and 326 deletions

View File

@ -0,0 +1,2 @@
import eventlet # noqa
eventlet.monkey_patch()

View File

@ -0,0 +1,29 @@
# 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.
"""The Ironic Inspector service."""
import sys
from ironic_inspector.common import service_utils
from ironic_inspector import wsgi_service
def main(args=sys.argv[1:]):
# Parse config file and command line options, then start logging
service_utils.prepare_service(args)
server = wsgi_service.WSGIService()
server.run()
if __name__ == '__main__':
sys.exit(main())

View File

@ -0,0 +1,35 @@
# 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_config import cfg
from oslo_log import log
LOG = log.getLogger(__name__)
CONF = cfg.CONF
def prepare_service(args):
log.register_options(CONF)
log.set_defaults(default_log_levels=['sqlalchemy=WARNING',
'iso8601=WARNING',
'requests=WARNING',
'urllib3.connectionpool=WARNING',
'keystonemiddleware=WARNING',
'swiftclient=WARNING',
'keystoneauth=WARNING',
'ironicclient=WARNING'])
CONF(args, project='ironic-inspector')
log.setup(CONF, 'ironic_inspector')
LOG.debug("Configuration:")
CONF.log_opt_values(LOG, log.DEBUG)

View File

@ -11,19 +11,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import eventlet # noqa
eventlet.monkey_patch()
import functools
import os
import re
import ssl
import sys
import flask
from futurist import periodics
from oslo_config import cfg
from oslo_log import log
from oslo_utils import uuidutils
import werkzeug
@ -32,11 +25,8 @@ from ironic_inspector.common.i18n import _
from ironic_inspector.common import ironic as ir_utils
from ironic_inspector.common import swift
from ironic_inspector import conf # noqa
from ironic_inspector import db
from ironic_inspector import firewall
from ironic_inspector import introspect
from ironic_inspector import node_cache
from ironic_inspector.plugins import base as plugins_base
from ironic_inspector import process
from ironic_inspector import rules
from ironic_inspector import utils
@ -350,161 +340,3 @@ def api_rule(uuid):
@app.errorhandler(404)
def handle_404(error):
return error_response(error, code=404)
def periodic_update(): # pragma: no cover
try:
firewall.update_filters()
except Exception:
LOG.exception('Periodic update of firewall rules failed')
def periodic_clean_up(): # pragma: no cover
try:
if node_cache.clean_up():
firewall.update_filters()
sync_with_ironic()
except Exception:
LOG.exception('Periodic clean up of node cache failed')
def sync_with_ironic():
ironic = ir_utils.get_client()
# TODO(yuikotakada): pagination
ironic_nodes = ironic.node.list(limit=0)
ironic_node_uuids = {node.uuid for node in ironic_nodes}
node_cache.delete_nodes_not_in_list(ironic_node_uuids)
def create_ssl_context():
if not CONF.use_ssl:
return
MIN_VERSION = (2, 7, 9)
if sys.version_info < MIN_VERSION:
LOG.warning('Unable to use SSL in this version of Python: '
'%(current)s, please ensure your version of Python is '
'greater than %(min)s to enable this feature.',
{'current': '.'.join(map(str, sys.version_info[:3])),
'min': '.'.join(map(str, MIN_VERSION))})
return
context = ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH)
if CONF.ssl_cert_path and CONF.ssl_key_path:
try:
context.load_cert_chain(CONF.ssl_cert_path, CONF.ssl_key_path)
except IOError as exc:
LOG.warning('Failed to load certificate or key from defined '
'locations: %(cert)s and %(key)s, will continue '
'to run with the default settings: %(exc)s',
{'cert': CONF.ssl_cert_path, 'key': CONF.ssl_key_path,
'exc': exc})
except ssl.SSLError as exc:
LOG.warning('There was a problem with the loaded certificate '
'and key, will continue to run with the default '
'settings: %s', exc)
return context
class Service(object):
_periodics_worker = None
def setup_logging(self, args):
log.register_options(CONF)
CONF(args, project='ironic-inspector')
log.set_defaults(default_log_levels=[
'sqlalchemy=WARNING',
'iso8601=WARNING',
'requests=WARNING',
'urllib3.connectionpool=WARNING',
'keystonemiddleware=WARNING',
'swiftclient=WARNING',
'keystoneauth=WARNING',
'ironicclient=WARNING'
])
log.setup(CONF, 'ironic_inspector')
LOG.debug("Configuration:")
CONF.log_opt_values(LOG, log.DEBUG)
def init(self):
if CONF.auth_strategy != 'noauth':
utils.add_auth_middleware(app)
else:
LOG.warning('Starting unauthenticated, please check'
' configuration')
if CONF.processing.store_data == 'none':
LOG.warning('Introspection data will not be stored. Change '
'"[processing] store_data" option if this is not '
'the desired behavior')
elif CONF.processing.store_data == 'swift':
LOG.info('Introspection data will be stored in Swift in the '
'container %s', CONF.swift.container)
utils.add_cors_middleware(app)
db.init()
try:
hooks = plugins_base.validate_processing_hooks()
except Exception as exc:
LOG.critical(str(exc))
sys.exit(1)
LOG.info('Enabled processing hooks: %s', [h.name for h in hooks])
if CONF.firewall.manage_firewall:
firewall.init()
periodic_update_ = periodics.periodic(
spacing=CONF.firewall.firewall_update_period,
enabled=CONF.firewall.manage_firewall
)(periodic_update)
periodic_clean_up_ = periodics.periodic(
spacing=CONF.clean_up_period
)(periodic_clean_up)
self._periodics_worker = periodics.PeriodicWorker(
callables=[(periodic_update_, None, None),
(periodic_clean_up_, None, None)],
executor_factory=periodics.ExistingExecutor(utils.executor()))
utils.executor().submit(self._periodics_worker.start)
def shutdown(self):
LOG.debug('Shutting down')
firewall.clean_up()
if self._periodics_worker is not None:
self._periodics_worker.stop()
self._periodics_worker.wait()
self._periodics_worker = None
if utils.executor().alive:
utils.executor().shutdown(wait=True)
LOG.info('Shut down successfully')
def run(self, args, application):
self.setup_logging(args)
app_kwargs = {'host': CONF.listen_address,
'port': CONF.listen_port}
context = create_ssl_context()
if context:
app_kwargs['ssl_context'] = context
self.init()
try:
application.run(**app_kwargs)
finally:
self.shutdown()
def main(args=sys.argv[1:]): # pragma: no cover
service = Service()
service.run(args, app)

View File

@ -34,6 +34,7 @@ import requests
import six
from six.moves import urllib
from ironic_inspector.cmd import all as inspector_cmd
from ironic_inspector.common import ironic as ir_utils
from ironic_inspector.common import swift
from ironic_inspector import db
@ -779,7 +780,7 @@ def mocked_server():
cfg.CONF.reset()
cfg.CONF.unregister_opt(dbsync.command_opt)
eventlet.greenthread.spawn_n(main.main,
eventlet.greenthread.spawn_n(inspector_cmd.main,
args=['--config-file', conf_file])
eventlet.greenthread.sleep(1)
# Wait for service to start up to 30 seconds

View File

@ -13,8 +13,6 @@
import datetime
import json
import ssl
import sys
import unittest
import mock
@ -22,8 +20,6 @@ from oslo_utils import uuidutils
from ironic_inspector.common import ironic as ir_utils
from ironic_inspector import conf
from ironic_inspector import db
from ironic_inspector import firewall
from ironic_inspector import introspect
from ironic_inspector import introspection_state as istate
from ironic_inspector import main
@ -648,155 +644,3 @@ class TestPlugins(unittest.TestCase):
def test_manager_is_cached(self):
self.assertIs(plugins_base.processing_hooks_manager(),
plugins_base.processing_hooks_manager())
@mock.patch.object(firewall, 'init')
@mock.patch.object(utils, 'add_auth_middleware')
@mock.patch.object(ir_utils, 'get_client')
@mock.patch.object(db, 'init')
class TestInit(test_base.BaseTest):
def setUp(self):
super(TestInit, self).setUp()
# Tests default to a synchronous executor which can't be used here
utils._EXECUTOR = None
self.service = main.Service()
@mock.patch.object(firewall, 'clean_up', lambda: None)
def tearDown(self):
self.service.shutdown()
super(TestInit, self).tearDown()
def test_ok(self, mock_node_cache, mock_get_client, mock_auth,
mock_firewall):
CONF.set_override('auth_strategy', 'keystone')
self.service.init()
mock_auth.assert_called_once_with(main.app)
mock_node_cache.assert_called_once_with()
mock_firewall.assert_called_once_with()
def test_init_without_authenticate(self, mock_node_cache, mock_get_client,
mock_auth, mock_firewall):
CONF.set_override('auth_strategy', 'noauth')
self.service.init()
self.assertFalse(mock_auth.called)
@mock.patch.object(main.LOG, 'warning')
def test_init_with_no_data_storage(self, mock_log, mock_node_cache,
mock_get_client, mock_auth,
mock_firewall):
msg = ('Introspection data will not be stored. Change '
'"[processing] store_data" option if this is not the '
'desired behavior')
self.service.init()
mock_log.assert_called_once_with(msg)
@mock.patch.object(main.LOG, 'info')
def test_init_with_swift_storage(self, mock_log, mock_node_cache,
mock_get_client, mock_auth,
mock_firewall):
CONF.set_override('store_data', 'swift', 'processing')
msg = mock.call('Introspection data will be stored in Swift in the '
'container %s', CONF.swift.container)
self.service.init()
self.assertIn(msg, mock_log.call_args_list)
def test_init_without_manage_firewall(self, mock_node_cache,
mock_get_client, mock_auth,
mock_firewall):
CONF.set_override('manage_firewall', False, 'firewall')
self.service.init()
self.assertFalse(mock_firewall.called)
@mock.patch.object(main.LOG, 'critical')
def test_init_failed_processing_hook(self, mock_log, mock_node_cache,
mock_get_client, mock_auth,
mock_firewall):
CONF.set_override('processing_hooks', 'foo!', 'processing')
plugins_base._HOOKS_MGR = None
self.assertRaises(SystemExit, self.service.init)
mock_log.assert_called_once_with(
'The following hook(s) are missing or failed to load: foo!')
class TestCreateSSLContext(test_base.BaseTest):
def test_use_ssl_false(self):
CONF.set_override('use_ssl', False)
con = main.create_ssl_context()
self.assertIsNone(con)
@mock.patch.object(sys, 'version_info')
def test_old_python_returns_none(self, mock_version_info):
mock_version_info.__lt__.return_value = True
CONF.set_override('use_ssl', True)
con = main.create_ssl_context()
self.assertIsNone(con)
@unittest.skipIf(sys.version_info[:3] < (2, 7, 9),
'This feature is unsupported in this version of python '
'so the tests will be skipped')
@mock.patch.object(ssl, 'create_default_context', autospec=True)
def test_use_ssl_true(self, mock_cdc):
CONF.set_override('use_ssl', True)
m_con = mock_cdc()
con = main.create_ssl_context()
self.assertEqual(m_con, con)
@unittest.skipIf(sys.version_info[:3] < (2, 7, 9),
'This feature is unsupported in this version of python '
'so the tests will be skipped')
@mock.patch.object(ssl, 'create_default_context', autospec=True)
def test_only_key_path_provided(self, mock_cdc):
CONF.set_override('use_ssl', True)
CONF.set_override('ssl_key_path', '/some/fake/path')
mock_context = mock_cdc()
con = main.create_ssl_context()
self.assertEqual(mock_context, con)
self.assertFalse(mock_context.load_cert_chain.called)
@unittest.skipIf(sys.version_info[:3] < (2, 7, 9),
'This feature is unsupported in this version of python '
'so the tests will be skipped')
@mock.patch.object(ssl, 'create_default_context', autospec=True)
def test_only_cert_path_provided(self, mock_cdc):
CONF.set_override('use_ssl', True)
CONF.set_override('ssl_cert_path', '/some/fake/path')
mock_context = mock_cdc()
con = main.create_ssl_context()
self.assertEqual(mock_context, con)
self.assertFalse(mock_context.load_cert_chain.called)
@unittest.skipIf(sys.version_info[:3] < (2, 7, 9),
'This feature is unsupported in this version of python '
'so the tests will be skipped')
@mock.patch.object(ssl, 'create_default_context', autospec=True)
def test_both_paths_provided(self, mock_cdc):
key_path = '/some/fake/path/key'
cert_path = '/some/fake/path/cert'
CONF.set_override('use_ssl', True)
CONF.set_override('ssl_key_path', key_path)
CONF.set_override('ssl_cert_path', cert_path)
mock_context = mock_cdc()
con = main.create_ssl_context()
self.assertEqual(mock_context, con)
mock_context.load_cert_chain.assert_called_once_with(cert_path,
key_path)
@unittest.skipIf(sys.version_info[:3] < (2, 7, 9),
'This feature is unsupported in this version of python '
'so the tests will be skipped')
@mock.patch.object(ssl, 'create_default_context', autospec=True)
def test_load_cert_chain_fails(self, mock_cdc):
CONF.set_override('use_ssl', True)
key_path = '/some/fake/path/key'
cert_path = '/some/fake/path/cert'
CONF.set_override('use_ssl', True)
CONF.set_override('ssl_key_path', key_path)
CONF.set_override('ssl_cert_path', cert_path)
mock_context = mock_cdc()
mock_context.load_cert_chain.side_effect = IOError('Boom!')
con = main.create_ssl_context()
self.assertEqual(mock_context, con)
mock_context.load_cert_chain.assert_called_once_with(cert_path,
key_path)

View File

@ -0,0 +1,207 @@
# 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 ssl
import sys
import unittest
import eventlet # noqa
import fixtures
import mock
from oslo_config import cfg
from ironic_inspector import db
from ironic_inspector import firewall
from ironic_inspector import main
from ironic_inspector.plugins import base as plugins_base
from ironic_inspector.test import base as test_base
from ironic_inspector import utils
from ironic_inspector import wsgi_service
CONF = cfg.CONF
@mock.patch.object(firewall, 'clean_up', lambda: None)
@mock.patch.object(db, 'init', lambda: None)
@mock.patch.object(wsgi_service.WSGIService, '_init_host', lambda x: None)
@mock.patch.object(utils, 'add_auth_middleware')
class TestWSGIService(test_base.BaseTest):
def setUp(self):
super(TestWSGIService, self).setUp()
self.app = self.useFixture(fixtures.MockPatchObject(
main, 'app', autospec=True)).mock
self.service = wsgi_service.WSGIService()
def test_init_middleware(self, mock_auth):
CONF.set_override('auth_strategy', 'keystone')
self.service._init_middleware()
mock_auth.assert_called_once_with(self.app)
@mock.patch.object(wsgi_service.WSGIService, '_init_middleware')
def test_run_ok(self, mock_init_middlw, mock_auth):
self.service.run()
mock_init_middlw.assert_called_once_with()
self.app.run.assert_called_once_with(host='0.0.0.0', port=5050)
@mock.patch.object(wsgi_service.LOG, 'info')
def test_init_with_swift_storage(self, mock_log, mock_auth):
CONF.set_override('store_data', 'swift', 'processing')
msg = mock.call('Introspection data will be stored in Swift in the '
'container %s', CONF.swift.container)
self.service.run()
self.assertIn(msg, mock_log.call_args_list)
def test_init_without_authenticate(self, mock_auth):
CONF.set_override('auth_strategy', 'noauth')
self.service.run()
self.assertFalse(mock_auth.called)
@mock.patch.object(wsgi_service.LOG, 'warning')
def test_init_with_no_data_storage(self, mock_log, mock_auth):
msg = ('Introspection data will not be stored. Change '
'"[processing] store_data" option if this is not the '
'desired behavior')
self.service.run()
mock_log.assert_called_once_with(msg)
class TestCreateSSLContext(test_base.BaseTest):
def setUp(self):
super(TestCreateSSLContext, self).setUp()
self.app = mock.Mock()
self.service = wsgi_service.WSGIService()
def test_use_ssl_false(self):
CONF.set_override('use_ssl', False)
con = self.service._create_ssl_context()
self.assertIsNone(con)
@mock.patch.object(sys, 'version_info')
def test_old_python_returns_none(self, mock_version_info):
mock_version_info.__lt__.return_value = True
CONF.set_override('use_ssl', True)
con = self.service._create_ssl_context()
self.assertIsNone(con)
@unittest.skipIf(sys.version_info[:3] < (2, 7, 9),
'This feature is unsupported in this version of python '
'so the tests will be skipped')
@mock.patch.object(ssl, 'create_default_context', autospec=True)
def test_use_ssl_true(self, mock_cdc):
CONF.set_override('use_ssl', True)
m_con = mock_cdc()
con = self.service._create_ssl_context()
self.assertEqual(m_con, con)
@unittest.skipIf(sys.version_info[:3] < (2, 7, 9),
'This feature is unsupported in this version of python '
'so the tests will be skipped')
@mock.patch.object(ssl, 'create_default_context', autospec=True)
def test_only_key_path_provided(self, mock_cdc):
CONF.set_override('use_ssl', True)
CONF.set_override('ssl_key_path', '/some/fake/path')
mock_context = mock_cdc()
con = self.service._create_ssl_context()
self.assertEqual(mock_context, con)
self.assertFalse(mock_context.load_cert_chain.called)
@unittest.skipIf(sys.version_info[:3] < (2, 7, 9),
'This feature is unsupported in this version of python '
'so the tests will be skipped')
@mock.patch.object(ssl, 'create_default_context', autospec=True)
def test_only_cert_path_provided(self, mock_cdc):
CONF.set_override('use_ssl', True)
CONF.set_override('ssl_cert_path', '/some/fake/path')
mock_context = mock_cdc()
con = self.service._create_ssl_context()
self.assertEqual(mock_context, con)
self.assertFalse(mock_context.load_cert_chain.called)
@unittest.skipIf(sys.version_info[:3] < (2, 7, 9),
'This feature is unsupported in this version of python '
'so the tests will be skipped')
@mock.patch.object(ssl, 'create_default_context', autospec=True)
def test_both_paths_provided(self, mock_cdc):
key_path = '/some/fake/path/key'
cert_path = '/some/fake/path/cert'
CONF.set_override('use_ssl', True)
CONF.set_override('ssl_key_path', key_path)
CONF.set_override('ssl_cert_path', cert_path)
mock_context = mock_cdc()
con = self.service._create_ssl_context()
self.assertEqual(mock_context, con)
mock_context.load_cert_chain.assert_called_once_with(cert_path,
key_path)
@unittest.skipIf(sys.version_info[:3] < (2, 7, 9),
'This feature is unsupported in this version of python '
'so the tests will be skipped')
@mock.patch.object(ssl, 'create_default_context', autospec=True)
def test_load_cert_chain_fails(self, mock_cdc):
CONF.set_override('use_ssl', True)
key_path = '/some/fake/path/key'
cert_path = '/some/fake/path/cert'
CONF.set_override('use_ssl', True)
CONF.set_override('ssl_key_path', key_path)
CONF.set_override('ssl_cert_path', cert_path)
mock_context = mock_cdc()
mock_context.load_cert_chain.side_effect = IOError('Boom!')
con = self.service._create_ssl_context()
self.assertEqual(mock_context, con)
mock_context.load_cert_chain.assert_called_once_with(cert_path,
key_path)
@mock.patch.object(firewall, 'init')
@mock.patch.object(db, 'init')
class TestInit(test_base.BaseTest):
def setUp(self):
super(TestInit, self).setUp()
# Tests default to a synchronous executor which can't be used here
utils._EXECUTOR = None
# Monkey patch for periodic tasks
eventlet.monkey_patch()
self.wsgi = wsgi_service.WSGIService()
@mock.patch.object(firewall, 'clean_up', lambda: None)
def tearDown(self):
self.wsgi.shutdown()
super(TestInit, self).tearDown()
def test_ok(self, mock_db, mock_firewall):
self.wsgi._init_host()
mock_db.assert_called_once_with()
mock_firewall.assert_called_once_with()
def test_init_without_manage_firewall(self, mock_db, mock_firewall):
CONF.set_override('manage_firewall', False, 'firewall')
self.wsgi._init_host()
self.assertFalse(mock_firewall.called)
@mock.patch.object(wsgi_service.LOG, 'critical')
def test_init_failed_processing_hook(self, mock_log,
mock_db, mock_firewall):
CONF.set_override('processing_hooks', 'foo!', 'processing')
plugins_base._HOOKS_MGR = None
self.assertRaises(SystemExit, self.wsgi._init_host)
mock_log.assert_called_once_with(
'The following hook(s) are missing or failed to load: foo!')

View File

@ -0,0 +1,196 @@
# 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 ssl
import sys
from futurist import periodics
from oslo_config import cfg
from oslo_log import log
from ironic_inspector.common import ironic as ir_utils
from ironic_inspector import db
from ironic_inspector import firewall
from ironic_inspector import main as app
from ironic_inspector import node_cache
from ironic_inspector.plugins import base as plugins_base
from ironic_inspector import utils
LOG = log.getLogger(__name__)
CONF = cfg.CONF
class WSGIService(object):
"""Provides ability to launch API from wsgi app."""
def __init__(self):
self.app = app.app
self._periodics_worker = None
def _init_middleware(self):
"""Initialize WSGI middleware.
:returns: None
"""
if CONF.auth_strategy != 'noauth':
utils.add_auth_middleware(self.app)
else:
LOG.warning('Starting unauthenticated, please check'
' configuration')
# TODO(aarefiev): move to WorkerService once we split service
if CONF.processing.store_data == 'none':
LOG.warning('Introspection data will not be stored. Change '
'"[processing] store_data" option if this is not '
'the desired behavior')
elif CONF.processing.store_data == 'swift':
LOG.info('Introspection data will be stored in Swift in the '
'container %s', CONF.swift.container)
utils.add_cors_middleware(self.app)
def _create_ssl_context(self):
if not CONF.use_ssl:
return
MIN_VERSION = (2, 7, 9)
if sys.version_info < MIN_VERSION:
LOG.warning(('Unable to use SSL in this version of Python: '
'%(current)s, please ensure your version of Python '
'is greater than %(min)s to enable this feature.'),
{'current': '.'.join(map(str, sys.version_info[:3])),
'min': '.'.join(map(str, MIN_VERSION))})
return
context = ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH)
if CONF.ssl_cert_path and CONF.ssl_key_path:
try:
context.load_cert_chain(CONF.ssl_cert_path, CONF.ssl_key_path)
except IOError as exc:
LOG.warning('Failed to load certificate or key from defined '
'locations: %(cert)s and %(key)s, will continue '
'to run with the default settings: %(exc)s',
{'cert': CONF.ssl_cert_path,
'key': CONF.ssl_key_path,
'exc': exc})
except ssl.SSLError as exc:
LOG.warning('There was a problem with the loaded certificate '
'and key, will continue to run with the default '
'settings: %s', exc)
return context
# TODO(aarefiev): move init code to WorkerService
def _init_host(self):
"""Initialize Worker host
Init db connection, load and validate processing
hooks, runs periodic tasks.
:returns None
"""
db.init()
try:
hooks = plugins_base.validate_processing_hooks()
except Exception as exc:
LOG.critical(str(exc))
sys.exit(1)
LOG.info('Enabled processing hooks: %s', [h.name for h in hooks])
if CONF.firewall.manage_firewall:
firewall.init()
periodic_update_ = periodics.periodic(
spacing=CONF.firewall.firewall_update_period,
enabled=CONF.firewall.manage_firewall
)(periodic_update)
periodic_clean_up_ = periodics.periodic(
spacing=CONF.clean_up_period
)(periodic_clean_up)
self._periodics_worker = periodics.PeriodicWorker(
callables=[(periodic_update_, None, None),
(periodic_clean_up_, None, None)],
executor_factory=periodics.ExistingExecutor(utils.executor()))
utils.executor().submit(self._periodics_worker.start)
def shutdown(self):
"""Stop serving API, clean up.
:returns: None
"""
# TODO(aarefiev): move shutdown code to WorkerService
LOG.debug('Shutting down')
firewall.clean_up()
if self._periodics_worker is not None:
try:
self._periodics_worker.stop()
self._periodics_worker.wait()
except Exception as e:
LOG.exception('Service error occurred when stopping '
'periodic workers. Error: %s', e)
self._periodics_worker = None
if utils.executor().alive:
utils.executor().shutdown(wait=True)
LOG.info('Shut down successfully')
def run(self):
"""Start serving this service using loaded application.
:returns: None
"""
app_kwargs = {'host': CONF.listen_address,
'port': CONF.listen_port}
context = self._create_ssl_context()
if context:
app_kwargs['ssl_context'] = context
self._init_middleware()
self._init_host()
try:
self.app.run(**app_kwargs)
finally:
self.shutdown()
def periodic_update(): # pragma: no cover
try:
firewall.update_filters()
except Exception:
LOG.exception('Periodic update of firewall rules failed')
def periodic_clean_up(): # pragma: no cover
try:
if node_cache.clean_up():
firewall.update_filters()
sync_with_ironic()
except Exception:
LOG.exception('Periodic clean up of node cache failed')
def sync_with_ironic():
ironic = ir_utils.get_client()
# TODO(yuikotakada): pagination
ironic_nodes = ironic.node.list(limit=0)
ironic_node_uuids = {node.uuid for node in ironic_nodes}
node_cache.delete_nodes_not_in_list(ironic_node_uuids)

View File

@ -22,7 +22,7 @@ packages =
[entry_points]
console_scripts =
ironic-inspector = ironic_inspector.main:main
ironic-inspector = ironic_inspector.cmd.all:main
ironic-inspector-dbsync = ironic_inspector.dbsync:main
ironic-inspector-rootwrap = oslo_rootwrap.cmd:main
ironic_inspector.hooks.processing =