summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKaifeng Wang <kaifeng.w@gmail.com>2018-07-23 15:56:27 +0800
committerKaifeng Wang <kaifeng.w@gmail.com>2018-10-23 09:17:42 +0800
commita228912827f0cda149fd8b79abbdccaf1c15d2b3 (patch)
tree6b8d6aaaba40f50a5221368a0e248dc3ac8e4ef9
parent054f300290c45035bb1cc1b222b2cc8cf26c3002 (diff)
Wrap rpc server into oslo.service
This patch is part of work splitting inspector api and worker. The rpc server is wrapped into oslo.service, and launched from api process. Now api and worker belongs to separate thread, functional tests use the fake messaging driver, while devstack jobs use rabbitmq. Change-Id: Ie03d16f9d115c3561723463dea6a57d2a763bcc6 Story: #2001842 Task: #23109
Notes
Notes (review): Code-Review+2: Julia Kreger <juliaashleykreger@gmail.com> Code-Review+2: Dmitry Tantsur <divius.inside@gmail.com> Workflow+1: Dmitry Tantsur <divius.inside@gmail.com> Verified+2: Zuul Submitted-by: Zuul Submitted-at: Thu, 15 Nov 2018 14:00:04 +0000 Reviewed-on: https://review.openstack.org/584758 Project: openstack/ironic-inspector Branch: refs/heads/master
-rw-r--r--devstack/plugin.sh2
-rwxr-xr-xdevstack/upgrade/upgrade.sh3
-rw-r--r--ironic_inspector/common/rpc.py29
-rw-r--r--ironic_inspector/common/rpc_service.py62
-rw-r--r--ironic_inspector/conductor/manager.py108
-rw-r--r--ironic_inspector/conf/default.py10
-rw-r--r--ironic_inspector/test/functional.py1
-rw-r--r--ironic_inspector/test/unit/test_manager.py203
-rw-r--r--ironic_inspector/test/unit/test_wsgi_service.py214
-rw-r--r--ironic_inspector/wsgi_service.py117
-rw-r--r--releasenotes/notes/rpc-backends-0e7405aa1c7723a0.yaml7
-rw-r--r--tools/config-generator.conf1
12 files changed, 419 insertions, 338 deletions
diff --git a/devstack/plugin.sh b/devstack/plugin.sh
index d5170a4..68581f4 100644
--- a/devstack/plugin.sh
+++ b/devstack/plugin.sh
@@ -267,6 +267,8 @@ function configure_inspector {
267 inspector_iniset iptables dnsmasq_interface $IRONIC_INSPECTOR_INTERFACE 267 inspector_iniset iptables dnsmasq_interface $IRONIC_INSPECTOR_INTERFACE
268 inspector_iniset database connection `database_connection_url ironic_inspector` 268 inspector_iniset database connection `database_connection_url ironic_inspector`
269 269
270 iniset_rpc_backend ironic-inspector $IRONIC_INSPECTOR_CONF_FILE
271
270 if is_service_enabled swift; then 272 if is_service_enabled swift; then
271 configure_inspector_swift 273 configure_inspector_swift
272 fi 274 fi
diff --git a/devstack/upgrade/upgrade.sh b/devstack/upgrade/upgrade.sh
index 7138f2d..949bb8f 100755
--- a/devstack/upgrade/upgrade.sh
+++ b/devstack/upgrade/upgrade.sh
@@ -45,6 +45,7 @@ source $TARGET_DEVSTACK_DIR/lib/neutron-legacy
45source $TARGET_DEVSTACK_DIR/lib/apache 45source $TARGET_DEVSTACK_DIR/lib/apache
46source $TARGET_DEVSTACK_DIR/lib/keystone 46source $TARGET_DEVSTACK_DIR/lib/keystone
47source $TARGET_DEVSTACK_DIR/lib/database 47source $TARGET_DEVSTACK_DIR/lib/database
48source $TARGET_DEVSTACK_DIR/lib/rpc_backend
48 49
49# Inspector relies on couple of Ironic variables 50# Inspector relies on couple of Ironic variables
50source $TARGET_RELEASE_DIR/ironic/devstack/lib/ironic 51source $TARGET_RELEASE_DIR/ironic/devstack/lib/ironic
@@ -84,6 +85,8 @@ $IRONIC_INSPECTOR_DBSYNC_BIN_FILE --config-file $IRONIC_INSPECTOR_CONF_FILE upgr
84# calls upgrade inspector for specific release 85# calls upgrade inspector for specific release
85upgrade_project ironic-inspector $RUN_DIR $BASE_DEVSTACK_BRANCH $TARGET_DEVSTACK_BRANCH 86upgrade_project ironic-inspector $RUN_DIR $BASE_DEVSTACK_BRANCH $TARGET_DEVSTACK_BRANCH
86 87
88# setup transport_url for rpc messaging
89iniset_rpc_backend ironic-inspector $IRONIC_INSPECTOR_CONF_FILE
87 90
88start_inspector 91start_inspector
89if is_inspector_dhcp_required; then 92if is_inspector_dhcp_required; then
diff --git a/ironic_inspector/common/rpc.py b/ironic_inspector/common/rpc.py
index 5586d8d..31e5311 100644
--- a/ironic_inspector/common/rpc.py
+++ b/ironic_inspector/common/rpc.py
@@ -19,38 +19,31 @@ from oslo_messaging.rpc import dispatcher
19from ironic_inspector.conductor import manager 19from ironic_inspector.conductor import manager
20 20
21CONF = cfg.CONF 21CONF = cfg.CONF
22
23_SERVER = None
24TRANSPORT = None 22TRANSPORT = None
25TOPIC = 'ironic-inspector-worker'
26SERVER_NAME = 'ironic-inspector-rpc-server'
27 23
28 24
29def get_transport(): 25def get_transport():
30 global TRANSPORT 26 global TRANSPORT
31 27
32 if TRANSPORT is None: 28 if TRANSPORT is None:
33 TRANSPORT = messaging.get_rpc_transport(CONF, url='fake://') 29 TRANSPORT = messaging.get_rpc_transport(CONF)
34 return TRANSPORT 30 return TRANSPORT
35 31
36 32
37def get_client(): 33def get_client():
38 target = messaging.Target(topic=TOPIC, server=SERVER_NAME, 34 """Get a RPC client instance."""
35 target = messaging.Target(topic=manager.MANAGER_TOPIC, server=CONF.host,
39 version='1.1') 36 version='1.1')
40 transport = get_transport() 37 transport = get_transport()
41 return messaging.RPCClient(transport, target) 38 return messaging.RPCClient(transport, target)
42 39
43 40
44def get_server(): 41def get_server(endpoints):
45 """Get the singleton RPC server.""" 42 """Get a RPC server instance."""
46 global _SERVER
47 43
48 if _SERVER is None: 44 transport = get_transport()
49 transport = get_transport() 45 target = messaging.Target(topic=manager.MANAGER_TOPIC, server=CONF.host,
50 target = messaging.Target(topic=TOPIC, server=SERVER_NAME, 46 version='1.1')
51 version='1.1') 47 return messaging.get_rpc_server(
52 mgr = manager.ConductorManager() 48 transport, target, endpoints, executor='eventlet',
53 _SERVER = messaging.get_rpc_server( 49 access_policy=dispatcher.DefaultRPCAccessPolicy)
54 transport, target, [mgr], executor='eventlet',
55 access_policy=dispatcher.DefaultRPCAccessPolicy)
56 return _SERVER
diff --git a/ironic_inspector/common/rpc_service.py b/ironic_inspector/common/rpc_service.py
new file mode 100644
index 0000000..5035106
--- /dev/null
+++ b/ironic_inspector/common/rpc_service.py
@@ -0,0 +1,62 @@
1# Licensed under the Apache License, Version 2.0 (the "License");
2# you may not use this file except in compliance with the License.
3# You may obtain a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS,
9# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
10# implied.
11# See the License for the specific language governing permissions and
12# limitations under the License.
13
14from oslo_config import cfg
15from oslo_log import log
16from oslo_service import service
17
18from ironic_inspector.common import rpc
19from ironic_inspector.conductor import manager
20
21CONF = cfg.CONF
22LOG = log.getLogger(__name__)
23
24SERVER_NAME = 'ironic-inspector-rpc-server'
25
26
27class RPCService(service.Service):
28
29 def __init__(self, host):
30 super(RPCService, self).__init__()
31 self.host = host
32 self.manager = manager.ConductorManager()
33 self.rpcserver = None
34
35 def start(self):
36 super(RPCService, self).start()
37 self.rpcserver = rpc.get_server([self.manager])
38 self.rpcserver.start()
39
40 self.manager.init_host()
41 LOG.info('Created RPC server for service %(service)s on host '
42 '%(host)s.',
43 {'service': manager.MANAGER_TOPIC, 'host': self.host})
44
45 def stop(self):
46 try:
47 self.rpcserver.stop()
48 self.rpcserver.wait()
49 except Exception as e:
50 LOG.exception('Service error occurred when stopping the '
51 'RPC server. Error: %s', e)
52
53 try:
54 self.manager.del_host()
55 except Exception as e:
56 LOG.exception('Service error occurred when cleaning up '
57 'the RPC manager. Error: %s', e)
58
59 super(RPCService, self).stop(graceful=True)
60 LOG.info('Stopped RPC server for service %(service)s on host '
61 '%(host)s.',
62 {'service': manager.MANAGER_TOPIC, 'host': self.host})
diff --git a/ironic_inspector/conductor/manager.py b/ironic_inspector/conductor/manager.py
index 84061cb..c4ab958 100644
--- a/ironic_inspector/conductor/manager.py
+++ b/ironic_inspector/conductor/manager.py
@@ -11,12 +11,30 @@
11# See the License for the specific language governing permissions and 11# See the License for the specific language governing permissions and
12# limitations under the License. 12# limitations under the License.
13 13
14import sys
15import traceback as traceback_mod
16
17import eventlet
18from eventlet import semaphore
19from futurist import periodics
20from oslo_config import cfg
21from oslo_log import log
14import oslo_messaging as messaging 22import oslo_messaging as messaging
23from oslo_utils import reflection
15 24
25from ironic_inspector.common import ironic as ir_utils
26from ironic_inspector import db
16from ironic_inspector import introspect 27from ironic_inspector import introspect
28from ironic_inspector import node_cache
29from ironic_inspector.plugins import base as plugins_base
17from ironic_inspector import process 30from ironic_inspector import process
31from ironic_inspector.pxe_filter import base as pxe_filter
18from ironic_inspector import utils 32from ironic_inspector import utils
19 33
34LOG = log.getLogger(__name__)
35CONF = cfg.CONF
36MANAGER_TOPIC = 'ironic-inspector-conductor'
37
20 38
21class ConductorManager(object): 39class ConductorManager(object):
22 """ironic inspector conductor manager""" 40 """ironic inspector conductor manager"""
@@ -24,6 +42,79 @@ class ConductorManager(object):
24 42
25 target = messaging.Target(version=RPC_API_VERSION) 43 target = messaging.Target(version=RPC_API_VERSION)
26 44
45 def __init__(self):
46 self._periodics_worker = None
47 self._shutting_down = semaphore.Semaphore()
48
49 def init_host(self):
50 """Initialize Worker host
51
52 Init db connection, load and validate processing
53 hooks, runs periodic tasks.
54
55 :returns None
56 """
57 if CONF.processing.store_data == 'none':
58 LOG.warning('Introspection data will not be stored. Change '
59 '"[processing] store_data" option if this is not '
60 'the desired behavior')
61 elif CONF.processing.store_data == 'swift':
62 LOG.info('Introspection data will be stored in Swift in the '
63 'container %s', CONF.swift.container)
64
65 db.init()
66
67 try:
68 hooks = plugins_base.validate_processing_hooks()
69 except Exception as exc:
70 LOG.critical(str(exc))
71 sys.exit(1)
72 LOG.info('Enabled processing hooks: %s', [h.name for h in hooks])
73
74 driver = pxe_filter.driver()
75 driver.init_filter()
76
77 periodic_clean_up_ = periodics.periodic(
78 spacing=CONF.clean_up_period
79 )(periodic_clean_up)
80
81 self._periodics_worker = periodics.PeriodicWorker(
82 callables=[(driver.get_periodic_sync_task(), None, None),
83 (periodic_clean_up_, None, None)],
84 executor_factory=periodics.ExistingExecutor(utils.executor()),
85 on_failure=self._periodics_watchdog)
86 utils.executor().submit(self._periodics_worker.start)
87
88 def del_host(self):
89
90 if not self._shutting_down.acquire(blocking=False):
91 LOG.warning('Attempted to shut down while already shutting down')
92 return
93
94 pxe_filter.driver().tear_down_filter()
95 if self._periodics_worker is not None:
96 try:
97 self._periodics_worker.stop()
98 self._periodics_worker.wait()
99 except Exception as e:
100 LOG.exception('Service error occurred when stopping '
101 'periodic workers. Error: %s', e)
102 self._periodics_worker = None
103
104 if utils.executor().alive:
105 utils.executor().shutdown(wait=True)
106
107 self._shutting_down.release()
108 LOG.info('Shut down successfully')
109
110 def _periodics_watchdog(self, callable_, activity, spacing, exc_info,
111 traceback=None):
112 LOG.exception("The periodic %(callable)s failed with: %(exception)s", {
113 'exception': ''.join(traceback_mod.format_exception(*exc_info)),
114 'callable': reflection.get_callable_name(callable_)})
115 # NOTE(milan): spawn new thread otherwise waiting would block
116 eventlet.spawn(self.del_host)
117
27 @messaging.expected_exceptions(utils.Error) 118 @messaging.expected_exceptions(utils.Error)
28 def do_introspection(self, context, node_id, token=None, 119 def do_introspection(self, context, node_id, token=None,
29 manage_boot=True): 120 manage_boot=True):
@@ -36,3 +127,20 @@ class ConductorManager(object):
36 @messaging.expected_exceptions(utils.Error) 127 @messaging.expected_exceptions(utils.Error)
37 def do_reapply(self, context, node_id, token=None): 128 def do_reapply(self, context, node_id, token=None):
38 process.reapply(node_id) 129 process.reapply(node_id)
130
131
132def periodic_clean_up(): # pragma: no cover
133 try:
134 if node_cache.clean_up():
135 pxe_filter.driver().sync(ir_utils.get_client())
136 sync_with_ironic()
137 except Exception:
138 LOG.exception('Periodic clean up of node cache failed')
139
140
141def sync_with_ironic():
142 ironic = ir_utils.get_client()
143 # TODO(yuikotakada): pagination
144 ironic_nodes = ironic.node.list(limit=0)
145 ironic_node_uuids = {node.uuid for node in ironic_nodes}
146 node_cache.delete_nodes_not_in_list(ironic_node_uuids)
diff --git a/ironic_inspector/conf/default.py b/ironic_inspector/conf/default.py
index b840341..ef2b9c2 100644
--- a/ironic_inspector/conf/default.py
+++ b/ironic_inspector/conf/default.py
@@ -11,6 +11,8 @@
11# See the License for the specific language governing permissions and 11# See the License for the specific language governing permissions and
12# limitations under the License. 12# limitations under the License.
13 13
14import socket
15
14from oslo_config import cfg 16from oslo_config import cfg
15 17
16from ironic_inspector.common.i18n import _ 18from ironic_inspector.common.i18n import _
@@ -23,6 +25,14 @@ _OPTS = [
23 cfg.PortOpt('listen_port', 25 cfg.PortOpt('listen_port',
24 default=5050, 26 default=5050,
25 help=_('Port to listen on.')), 27 help=_('Port to listen on.')),
28 cfg.StrOpt('host',
29 default=socket.getfqdn(),
30 sample_default='localhost',
31 help=_('Name of this node. This can be an opaque identifier. '
32 'It is not necessarily a hostname, FQDN, or IP address. '
33 'However, the node name must be valid within '
34 'an AMQP key, and if using ZeroMQ, a valid '
35 'hostname, FQDN, or IP address.')),
26 cfg.StrOpt('auth_strategy', 36 cfg.StrOpt('auth_strategy',
27 default='keystone', 37 default='keystone',
28 choices=('keystone', 'noauth'), 38 choices=('keystone', 'noauth'),
diff --git a/ironic_inspector/test/functional.py b/ironic_inspector/test/functional.py
index b26f338..cdae214 100644
--- a/ironic_inspector/test/functional.py
+++ b/ironic_inspector/test/functional.py
@@ -57,6 +57,7 @@ driver = noop
57debug = True 57debug = True
58introspection_delay = 0 58introspection_delay = 0
59auth_strategy=noauth 59auth_strategy=noauth
60transport_url=fake://
60[database] 61[database]
61connection = sqlite:///%(db_file)s 62connection = sqlite:///%(db_file)s
62[processing] 63[processing]
diff --git a/ironic_inspector/test/unit/test_manager.py b/ironic_inspector/test/unit/test_manager.py
index e898a51..71131f7 100644
--- a/ironic_inspector/test/unit/test_manager.py
+++ b/ironic_inspector/test/unit/test_manager.py
@@ -11,6 +11,7 @@
11# See the License for the specific language governing permissions and 11# See the License for the specific language governing permissions and
12# limitations under the License. 12# limitations under the License.
13 13
14import fixtures
14import mock 15import mock
15import oslo_messaging as messaging 16import oslo_messaging as messaging
16 17
@@ -27,11 +28,213 @@ CONF = ironic_inspector.conf.CONF
27class BaseManagerTest(test_base.NodeTest): 28class BaseManagerTest(test_base.NodeTest):
28 def setUp(self): 29 def setUp(self):
29 super(BaseManagerTest, self).setUp() 30 super(BaseManagerTest, self).setUp()
31 self.mock_log = self.useFixture(fixtures.MockPatchObject(
32 manager, 'LOG')).mock
33 self.mock__shutting_down = (self.useFixture(fixtures.MockPatchObject(
34 manager.semaphore, 'Semaphore', autospec=True))
35 .mock.return_value)
36 self.mock__shutting_down.acquire.return_value = True
30 self.manager = manager.ConductorManager() 37 self.manager = manager.ConductorManager()
31 self.context = {} 38 self.context = {}
32 self.token = None 39 self.token = None
33 40
34 41
42class TestManagerInitHost(BaseManagerTest):
43 def setUp(self):
44 super(TestManagerInitHost, self).setUp()
45 self.mock_db_init = self.useFixture(fixtures.MockPatchObject(
46 manager.db, 'init')).mock
47 self.mock_validate_processing_hooks = self.useFixture(
48 fixtures.MockPatchObject(manager.plugins_base,
49 'validate_processing_hooks')).mock
50 self.mock_filter = self.useFixture(fixtures.MockPatchObject(
51 manager.pxe_filter, 'driver')).mock.return_value
52 self.mock_periodic = self.useFixture(fixtures.MockPatchObject(
53 manager.periodics, 'periodic')).mock
54 self.mock_PeriodicWorker = self.useFixture(fixtures.MockPatchObject(
55 manager.periodics, 'PeriodicWorker')).mock
56 self.mock_executor = self.useFixture(fixtures.MockPatchObject(
57 manager.utils, 'executor')).mock
58 self.mock_ExistingExecutor = self.useFixture(fixtures.MockPatchObject(
59 manager.periodics, 'ExistingExecutor')).mock
60 self.mock_exit = self.useFixture(fixtures.MockPatchObject(
61 manager.sys, 'exit')).mock
62
63 def assert_periodics(self):
64 outer_cleanup_decorator_call = mock.call(
65 spacing=CONF.clean_up_period)
66 self.mock_periodic.assert_has_calls([
67 outer_cleanup_decorator_call,
68 mock.call()(manager.periodic_clean_up)])
69
70 inner_decorator = self.mock_periodic.return_value
71 inner_cleanup_decorator_call = mock.call(
72 manager.periodic_clean_up)
73 inner_decorator.assert_has_calls([inner_cleanup_decorator_call])
74
75 self.mock_ExistingExecutor.assert_called_once_with(
76 self.mock_executor.return_value)
77
78 periodic_worker = self.mock_PeriodicWorker.return_value
79
80 periodic_sync = self.mock_filter.get_periodic_sync_task.return_value
81 callables = [(periodic_sync, None, None),
82 (inner_decorator.return_value, None, None)]
83 self.mock_PeriodicWorker.assert_called_once_with(
84 callables=callables,
85 executor_factory=self.mock_ExistingExecutor.return_value,
86 on_failure=self.manager._periodics_watchdog)
87 self.assertIs(periodic_worker, self.manager._periodics_worker)
88
89 self.mock_executor.return_value.submit.assert_called_once_with(
90 self.manager._periodics_worker.start)
91
92 def test_no_introspection_data_store(self):
93 CONF.set_override('store_data', 'none', 'processing')
94 self.manager.init_host()
95 self.mock_log.warning.assert_called_once_with(
96 'Introspection data will not be stored. Change "[processing] '
97 'store_data" option if this is not the desired behavior')
98
99 def test_init_host(self):
100 self.manager.init_host()
101 self.mock_db_init.asset_called_once_with()
102 self.mock_validate_processing_hooks.assert_called_once_with()
103 self.mock_filter.init_filter.assert_called_once_with()
104 self.assert_periodics()
105
106 def test_init_host_validate_processing_hooks_exception(self):
107 class MyError(Exception):
108 pass
109
110 error = MyError('Oops!')
111 self.mock_validate_processing_hooks.side_effect = error
112
113 # NOTE(milan): have to stop executing the test case at this point to
114 # simulate a real sys.exit() call
115 self.mock_exit.side_effect = SystemExit('Stop!')
116 self.assertRaisesRegex(SystemExit, 'Stop!', self.manager.init_host)
117
118 self.mock_db_init.assert_called_once_with()
119 self.mock_log.critical.assert_called_once_with(str(error))
120 self.mock_exit.assert_called_once_with(1)
121 self.mock_filter.init_filter.assert_not_called()
122
123
124class TestManagerDelHost(BaseManagerTest):
125 def setUp(self):
126 super(TestManagerDelHost, self).setUp()
127 self.mock_filter = self.useFixture(fixtures.MockPatchObject(
128 manager.pxe_filter, 'driver')).mock.return_value
129 self.mock_executor = mock.Mock()
130 self.mock_executor.alive = True
131 self.mock_get_executor = self.useFixture(fixtures.MockPatchObject(
132 manager.utils, 'executor')).mock
133 self.mock_get_executor.return_value = self.mock_executor
134 self.mock__periodic_worker = self.useFixture(fixtures.MockPatchObject(
135 self.manager, '_periodics_worker')).mock
136 self.mock_exit = self.useFixture(fixtures.MockPatchObject(
137 manager.sys, 'exit')).mock
138
139 def test_del_host(self):
140 self.manager.del_host()
141
142 self.mock__shutting_down.acquire.assert_called_once_with(
143 blocking=False)
144 self.mock__periodic_worker.stop.assert_called_once_with()
145 self.mock__periodic_worker.wait.assert_called_once_with()
146 self.assertIsNone(self.manager._periodics_worker)
147 self.mock_executor.shutdown.assert_called_once_with(wait=True)
148 self.mock_filter.tear_down_filter.assert_called_once_with()
149 self.mock__shutting_down.release.assert_called_once_with()
150
151 def test_del_host_race(self):
152 self.mock__shutting_down.acquire.return_value = False
153
154 self.manager.del_host()
155
156 self.mock__shutting_down.acquire.assert_called_once_with(
157 blocking=False)
158 self.mock_log.warning.assert_called_once_with(
159 'Attempted to shut down while already shutting down')
160 self.mock__periodic_worker.stop.assert_not_called()
161 self.mock__periodic_worker.wait.assert_not_called()
162 self.assertIs(self.mock__periodic_worker,
163 self.manager._periodics_worker)
164 self.mock_executor.shutdown.assert_not_called()
165 self.mock_filter.tear_down_filter.assert_not_called()
166 self.mock__shutting_down.release.assert_not_called()
167 self.mock_exit.assert_not_called()
168
169 def test_del_host_worker_exception(self):
170 class MyError(Exception):
171 pass
172
173 error = MyError('Oops!')
174 self.mock__periodic_worker.wait.side_effect = error
175
176 self.manager.del_host()
177
178 self.mock__shutting_down.acquire.assert_called_once_with(
179 blocking=False)
180 self.mock__periodic_worker.stop.assert_called_once_with()
181 self.mock__periodic_worker.wait.assert_called_once_with()
182 self.mock_log.exception.assert_called_once_with(
183 'Service error occurred when stopping periodic workers. Error: %s',
184 error)
185 self.assertIsNone(self.manager._periodics_worker)
186 self.mock_executor.shutdown.assert_called_once_with(wait=True)
187 self.mock_filter.tear_down_filter.assert_called_once_with()
188 self.mock__shutting_down.release.assert_called_once_with()
189
190 def test_del_host_no_worker(self):
191 self.manager._periodics_worker = None
192
193 self.manager.del_host()
194
195 self.mock__shutting_down.acquire.assert_called_once_with(
196 blocking=False)
197 self.mock__periodic_worker.stop.assert_not_called()
198 self.mock__periodic_worker.wait.assert_not_called()
199 self.assertIsNone(self.manager._periodics_worker)
200 self.mock_executor.shutdown.assert_called_once_with(wait=True)
201 self.mock_filter.tear_down_filter.assert_called_once_with()
202 self.mock__shutting_down.release.assert_called_once_with()
203
204 def test_del_host_stopped_executor(self):
205 self.mock_executor.alive = False
206
207 self.manager.del_host()
208
209 self.mock__shutting_down.acquire.assert_called_once_with(
210 blocking=False)
211 self.mock__periodic_worker.stop.assert_called_once_with()
212 self.mock__periodic_worker.wait.assert_called_once_with()
213 self.assertIsNone(self.manager._periodics_worker)
214 self.mock_executor.shutdown.assert_not_called()
215 self.mock_filter.tear_down_filter.assert_called_once_with()
216 self.mock__shutting_down.release.assert_called_once_with()
217
218
219class TestManagerPeriodicWatchDog(BaseManagerTest):
220 def setUp(self):
221 super(TestManagerPeriodicWatchDog, self).setUp()
222 self.mock_get_callable_name = self.useFixture(fixtures.MockPatchObject(
223 manager.reflection, 'get_callable_name')).mock
224 self.mock_spawn = self.useFixture(fixtures.MockPatchObject(
225 manager.eventlet, 'spawn')).mock
226
227 def test__periodics_watchdog(self):
228 error = RuntimeError('Oops!')
229
230 self.manager._periodics_watchdog(
231 callable_=None, activity=None, spacing=None,
232 exc_info=(None, error, None), traceback=None)
233
234 self.mock_get_callable_name.assert_called_once_with(None)
235 self.mock_spawn.assert_called_once_with(self.manager.del_host)
236
237
35class TestManagerIntrospect(BaseManagerTest): 238class TestManagerIntrospect(BaseManagerTest):
36 @mock.patch.object(introspect, 'introspect', autospec=True) 239 @mock.patch.object(introspect, 'introspect', autospec=True)
37 def test_do_introspect(self, introspect_mock): 240 def test_do_introspect(self, introspect_mock):
diff --git a/ironic_inspector/test/unit/test_wsgi_service.py b/ironic_inspector/test/unit/test_wsgi_service.py
index 6f37e79..3cb83db 100644
--- a/ironic_inspector/test/unit/test_wsgi_service.py
+++ b/ironic_inspector/test/unit/test_wsgi_service.py
@@ -20,11 +20,9 @@ import fixtures
20import mock 20import mock
21from oslo_config import cfg 21from oslo_config import cfg
22 22
23from ironic_inspector.common import rpc
24from ironic_inspector.test import base as test_base 23from ironic_inspector.test import base as test_base
25from ironic_inspector import wsgi_service 24from ironic_inspector import wsgi_service
26 25
27
28CONF = cfg.CONF 26CONF = cfg.CONF
29 27
30 28
@@ -34,15 +32,9 @@ class BaseWSGITest(test_base.BaseTest):
34 super(BaseWSGITest, self).setUp() 32 super(BaseWSGITest, self).setUp()
35 self.app = self.useFixture(fixtures.MockPatchObject( 33 self.app = self.useFixture(fixtures.MockPatchObject(
36 wsgi_service.app, 'app', autospec=True)).mock 34 wsgi_service.app, 'app', autospec=True)).mock
37 self.mock__shutting_down = (self.useFixture(fixtures.MockPatchObject(
38 wsgi_service.semaphore, 'Semaphore', autospec=True))
39 .mock.return_value)
40 self.mock__shutting_down.acquire.return_value = True
41 self.mock_log = self.useFixture(fixtures.MockPatchObject( 35 self.mock_log = self.useFixture(fixtures.MockPatchObject(
42 wsgi_service, 'LOG')).mock 36 wsgi_service, 'LOG')).mock
43 self.service = wsgi_service.WSGIService() 37 self.service = wsgi_service.WSGIService()
44 self.mock_rpc_server = self.useFixture(fixtures.MockPatchObject(
45 rpc, 'get_server')).mock
46 38
47 39
48class TestWSGIServiceInitMiddleware(BaseWSGITest): 40class TestWSGIServiceInitMiddleware(BaseWSGITest):
@@ -73,118 +65,10 @@ class TestWSGIServiceInitMiddleware(BaseWSGITest):
73 'Starting unauthenticated, please check configuration') 65 'Starting unauthenticated, please check configuration')
74 self.mock_add_cors_middleware.assert_called_once_with(self.app) 66 self.mock_add_cors_middleware.assert_called_once_with(self.app)
75 67
76 def test_init_middleware_no_store(self):
77 CONF.set_override('store_data', 'none', 'processing')
78 self.service._init_middleware()
79
80 self.mock_add_auth_middleware.assert_called_once_with(self.app)
81 self.mock_log.warning.assert_called_once_with(
82 'Introspection data will not be stored. Change "[processing] '
83 'store_data" option if this is not the desired behavior')
84 self.mock_add_cors_middleware.assert_called_once_with(self.app)
85
86
87class TestWSGIServiceInitHost(BaseWSGITest):
88 def setUp(self):
89 super(TestWSGIServiceInitHost, self).setUp()
90 self.mock_db_init = self.useFixture(fixtures.MockPatchObject(
91 wsgi_service.db, 'init')).mock
92 self.mock_validate_processing_hooks = self.useFixture(
93 fixtures.MockPatchObject(wsgi_service.plugins_base,
94 'validate_processing_hooks')).mock
95 self.mock_filter = self.useFixture(fixtures.MockPatchObject(
96 wsgi_service.pxe_filter, 'driver')).mock.return_value
97 self.mock_periodic = self.useFixture(fixtures.MockPatchObject(
98 wsgi_service.periodics, 'periodic')).mock
99 self.mock_PeriodicWorker = self.useFixture(fixtures.MockPatchObject(
100 wsgi_service.periodics, 'PeriodicWorker')).mock
101 self.mock_executor = self.useFixture(fixtures.MockPatchObject(
102 wsgi_service.utils, 'executor')).mock
103 self.mock_ExistingExecutor = self.useFixture(fixtures.MockPatchObject(
104 wsgi_service.periodics, 'ExistingExecutor')).mock
105 self.mock_exit = self.useFixture(fixtures.MockPatchObject(
106 wsgi_service.sys, 'exit')).mock
107
108 def assert_periodics(self):
109 outer_cleanup_decorator_call = mock.call(
110 spacing=CONF.clean_up_period)
111 self.mock_periodic.assert_has_calls([
112 outer_cleanup_decorator_call,
113 mock.call()(wsgi_service.periodic_clean_up)])
114
115 inner_decorator = self.mock_periodic.return_value
116 inner_cleanup_decorator_call = mock.call(
117 wsgi_service.periodic_clean_up)
118 inner_decorator.assert_has_calls([inner_cleanup_decorator_call])
119
120 self.mock_ExistingExecutor.assert_called_once_with(
121 self.mock_executor.return_value)
122
123 periodic_worker = self.mock_PeriodicWorker.return_value
124
125 periodic_sync = self.mock_filter.get_periodic_sync_task.return_value
126 callables = [(periodic_sync, None, None),
127 (inner_decorator.return_value, None, None)]
128 self.mock_PeriodicWorker.assert_called_once_with(
129 callables=callables,
130 executor_factory=self.mock_ExistingExecutor.return_value,
131 on_failure=self.service._periodics_watchdog)
132 self.assertIs(periodic_worker, self.service._periodics_worker)
133
134 self.mock_executor.return_value.submit.assert_called_once_with(
135 self.service._periodics_worker.start)
136
137 def test_init_host(self):
138 self.service._init_host()
139
140 self.mock_db_init.asset_called_once_with()
141 self.mock_validate_processing_hooks.assert_called_once_with()
142 self.mock_filter.init_filter.assert_called_once_with()
143 self.assert_periodics()
144
145 def test_init_host_validate_processing_hooks_exception(self):
146 class MyError(Exception):
147 pass
148
149 error = MyError('Oops!')
150 self.mock_validate_processing_hooks.side_effect = error
151
152 # NOTE(milan): have to stop executing the test case at this point to
153 # simulate a real sys.exit() call
154 self.mock_exit.side_effect = SystemExit('Stop!')
155 self.assertRaisesRegex(SystemExit, 'Stop!', self.service._init_host)
156
157 self.mock_db_init.assert_called_once_with()
158 self.mock_log.critical.assert_called_once_with(str(error))
159 self.mock_exit.assert_called_once_with(1)
160 self.mock_filter.init_filter.assert_not_called()
161
162
163class TestWSGIServicePeriodicWatchDog(BaseWSGITest):
164 def setUp(self):
165 super(TestWSGIServicePeriodicWatchDog, self).setUp()
166 self.mock_get_callable_name = self.useFixture(fixtures.MockPatchObject(
167 wsgi_service.reflection, 'get_callable_name')).mock
168 self.mock_spawn = self.useFixture(fixtures.MockPatchObject(
169 wsgi_service.eventlet, 'spawn')).mock
170
171 def test__periodics_watchdog(self):
172 error = RuntimeError('Oops!')
173
174 self.service._periodics_watchdog(
175 callable_=None, activity=None, spacing=None,
176 exc_info=(None, error, None), traceback=None)
177
178 self.mock_get_callable_name.assert_called_once_with(None)
179 self.mock_spawn.assert_called_once_with(self.service.shutdown,
180 error=str(error))
181
182 68
183class TestWSGIServiceRun(BaseWSGITest): 69class TestWSGIServiceRun(BaseWSGITest):
184 def setUp(self): 70 def setUp(self):
185 super(TestWSGIServiceRun, self).setUp() 71 super(TestWSGIServiceRun, self).setUp()
186 self.mock__init_host = self.useFixture(fixtures.MockPatchObject(
187 self.service, '_init_host')).mock
188 self.mock__init_middleware = self.useFixture(fixtures.MockPatchObject( 72 self.mock__init_middleware = self.useFixture(fixtures.MockPatchObject(
189 self.service, '_init_middleware')).mock 73 self.service, '_init_middleware')).mock
190 self.mock__create_ssl_context = self.useFixture( 74 self.mock__create_ssl_context = self.useFixture(
@@ -201,9 +85,6 @@ class TestWSGIServiceRun(BaseWSGITest):
201 85
202 self.mock__create_ssl_context.assert_called_once_with() 86 self.mock__create_ssl_context.assert_called_once_with()
203 self.mock__init_middleware.assert_called_once_with() 87 self.mock__init_middleware.assert_called_once_with()
204 self.mock__init_host.assert_called_once_with()
205 self.mock_rpc_server.assert_called_once_with()
206 self.service.rpc_server.start.assert_called_once_with()
207 self.app.run.assert_called_once_with( 88 self.app.run.assert_called_once_with(
208 host=CONF.listen_address, port=CONF.listen_port, 89 host=CONF.listen_address, port=CONF.listen_port,
209 ssl_context=self.mock__create_ssl_context.return_value) 90 ssl_context=self.mock__create_ssl_context.return_value)
@@ -215,7 +96,6 @@ class TestWSGIServiceRun(BaseWSGITest):
215 self.service.run() 96 self.service.run()
216 self.mock__create_ssl_context.assert_called_once_with() 97 self.mock__create_ssl_context.assert_called_once_with()
217 self.mock__init_middleware.assert_called_once_with() 98 self.mock__init_middleware.assert_called_once_with()
218 self.mock__init_host.assert_called_once_with()
219 self.app.run.assert_called_once_with( 99 self.app.run.assert_called_once_with(
220 host=CONF.listen_address, port=CONF.listen_port) 100 host=CONF.listen_address, port=CONF.listen_port)
221 self.mock_shutdown.assert_called_once_with() 101 self.mock_shutdown.assert_called_once_with()
@@ -230,7 +110,6 @@ class TestWSGIServiceRun(BaseWSGITest):
230 110
231 self.mock__create_ssl_context.assert_called_once_with() 111 self.mock__create_ssl_context.assert_called_once_with()
232 self.mock__init_middleware.assert_called_once_with() 112 self.mock__init_middleware.assert_called_once_with()
233 self.mock__init_host.assert_called_once_with()
234 self.app.run.assert_called_once_with( 113 self.app.run.assert_called_once_with(
235 host=CONF.listen_address, port=CONF.listen_port, 114 host=CONF.listen_address, port=CONF.listen_port,
236 ssl_context=self.mock__create_ssl_context.return_value) 115 ssl_context=self.mock__create_ssl_context.return_value)
@@ -240,108 +119,21 @@ class TestWSGIServiceRun(BaseWSGITest):
240class TestWSGIServiceShutdown(BaseWSGITest): 119class TestWSGIServiceShutdown(BaseWSGITest):
241 def setUp(self): 120 def setUp(self):
242 super(TestWSGIServiceShutdown, self).setUp() 121 super(TestWSGIServiceShutdown, self).setUp()
243 self.mock_filter = self.useFixture(fixtures.MockPatchObject(
244 wsgi_service.pxe_filter, 'driver')).mock.return_value
245 self.mock_executor = mock.Mock()
246 self.mock_executor.alive = True
247 self.mock_get_executor = self.useFixture(fixtures.MockPatchObject(
248 wsgi_service.utils, 'executor')).mock
249 self.mock_get_executor.return_value = self.mock_executor
250 self.service = wsgi_service.WSGIService() 122 self.service = wsgi_service.WSGIService()
251 self.mock__periodic_worker = self.useFixture(fixtures.MockPatchObject( 123 self.mock_rpc_service = mock.MagicMock()
252 self.service, '_periodics_worker')).mock 124 self.service.rpc_service = self.mock_rpc_service
253 self.mock_exit = self.useFixture(fixtures.MockPatchObject( 125 self.mock_exit = self.useFixture(fixtures.MockPatchObject(
254 wsgi_service.sys, 'exit')).mock 126 wsgi_service.sys, 'exit')).mock
255 self.service.rpc_server = self.mock_rpc_server
256 127
257 def test_shutdown(self): 128 def test_shutdown(self):
258 class MyError(Exception): 129 class MyError(Exception):
259 pass 130 pass
260
261 error = MyError('Oops!') 131 error = MyError('Oops!')
262 132
263 self.service.shutdown(error=error) 133 self.service.shutdown(error=error)
264 134 self.mock_rpc_service.stop.assert_called_once_with()
265 self.mock__shutting_down.acquire.assert_called_once_with(
266 blocking=False)
267 self.mock__periodic_worker.stop.assert_called_once_with()
268 self.mock__periodic_worker.wait.assert_called_once_with()
269 self.assertIsNone(self.service._periodics_worker)
270 self.mock_executor.shutdown.assert_called_once_with(wait=True)
271 self.mock_filter.tear_down_filter.assert_called_once_with()
272 self.mock__shutting_down.release.assert_called_once_with()
273 self.mock_exit.assert_called_once_with(error) 135 self.mock_exit.assert_called_once_with(error)
274 136
275 def test_shutdown_race(self):
276 self.mock__shutting_down.acquire.return_value = False
277
278 self.service.shutdown()
279
280 self.mock__shutting_down.acquire.assert_called_once_with(
281 blocking=False)
282 self.mock_log.warning.assert_called_once_with(
283 'Attempted to shut down while already shutting down')
284 self.mock__periodic_worker.stop.assert_not_called()
285 self.mock__periodic_worker.wait.assert_not_called()
286 self.assertIs(self.mock__periodic_worker,
287 self.service._periodics_worker)
288 self.mock_executor.shutdown.assert_not_called()
289 self.mock_filter.tear_down_filter.assert_not_called()
290 self.mock__shutting_down.release.assert_not_called()
291 self.mock_exit.assert_not_called()
292
293 def test_shutdown_worker_exception(self):
294 class MyError(Exception):
295 pass
296
297 error = MyError('Oops!')
298 self.mock__periodic_worker.wait.side_effect = error
299
300 self.service.shutdown()
301
302 self.mock__shutting_down.acquire.assert_called_once_with(
303 blocking=False)
304 self.mock__periodic_worker.stop.assert_called_once_with()
305 self.mock__periodic_worker.wait.assert_called_once_with()
306 self.mock_log.exception.assert_called_once_with(
307 'Service error occurred when stopping periodic workers. Error: %s',
308 error)
309 self.assertIsNone(self.service._periodics_worker)
310 self.mock_executor.shutdown.assert_called_once_with(wait=True)
311 self.mock_filter.tear_down_filter.assert_called_once_with()
312 self.mock__shutting_down.release.assert_called_once_with()
313 self.mock_exit.assert_called_once_with(None)
314
315 def test_shutdown_no_worker(self):
316 self.service._periodics_worker = None
317
318 self.service.shutdown()
319
320 self.mock__shutting_down.acquire.assert_called_once_with(
321 blocking=False)
322 self.mock__periodic_worker.stop.assert_not_called()
323 self.mock__periodic_worker.wait.assert_not_called()
324 self.assertIsNone(self.service._periodics_worker)
325 self.mock_executor.shutdown.assert_called_once_with(wait=True)
326 self.mock_filter.tear_down_filter.assert_called_once_with()
327 self.mock__shutting_down.release.assert_called_once_with()
328 self.mock_exit.assert_called_once_with(None)
329
330 def test_shutdown_stopped_executor(self):
331 self.mock_executor.alive = False
332
333 self.service.shutdown()
334
335 self.mock__shutting_down.acquire.assert_called_once_with(
336 blocking=False)
337 self.mock__periodic_worker.stop.assert_called_once_with()
338 self.mock__periodic_worker.wait.assert_called_once_with()
339 self.assertIsNone(self.service._periodics_worker)
340 self.mock_executor.shutdown.assert_not_called()
341 self.mock_filter.tear_down_filter.assert_called_once_with()
342 self.mock__shutting_down.release.assert_called_once_with()
343 self.mock_exit.assert_called_once_with(None)
344
345 137
346class TestCreateSSLContext(test_base.BaseTest): 138class TestCreateSSLContext(test_base.BaseTest):
347 def setUp(self): 139 def setUp(self):
diff --git a/ironic_inspector/wsgi_service.py b/ironic_inspector/wsgi_service.py
index 33fc407..8513e16 100644
--- a/ironic_inspector/wsgi_service.py
+++ b/ironic_inspector/wsgi_service.py
@@ -13,25 +13,16 @@
13import signal 13import signal
14import ssl 14import ssl
15import sys 15import sys
16import traceback as traceback_mod
17 16
18import eventlet 17import eventlet
19from eventlet import semaphore
20from futurist import periodics
21from oslo_config import cfg 18from oslo_config import cfg
22from oslo_log import log 19from oslo_log import log
23from oslo_utils import reflection 20from oslo_service import service
24 21
25from ironic_inspector.common import ironic as ir_utils 22from ironic_inspector.common.rpc_service import RPCService
26from ironic_inspector.common import rpc
27from ironic_inspector import db
28from ironic_inspector import main as app 23from ironic_inspector import main as app
29from ironic_inspector import node_cache
30from ironic_inspector.plugins import base as plugins_base
31from ironic_inspector.pxe_filter import base as pxe_filter
32from ironic_inspector import utils 24from ironic_inspector import utils
33 25
34
35LOG = log.getLogger(__name__) 26LOG = log.getLogger(__name__)
36CONF = cfg.CONF 27CONF = cfg.CONF
37 28
@@ -41,10 +32,9 @@ class WSGIService(object):
41 32
42 def __init__(self): 33 def __init__(self):
43 self.app = app.app 34 self.app = app.app
44 self._periodics_worker = None
45 self._shutting_down = semaphore.Semaphore()
46 signal.signal(signal.SIGHUP, self._handle_sighup) 35 signal.signal(signal.SIGHUP, self._handle_sighup)
47 signal.signal(signal.SIGTERM, self._handle_sigterm) 36 signal.signal(signal.SIGTERM, self._handle_sigterm)
37 self.rpc_service = RPCService(CONF.host)
48 38
49 def _init_middleware(self): 39 def _init_middleware(self):
50 """Initialize WSGI middleware. 40 """Initialize WSGI middleware.
@@ -57,15 +47,6 @@ class WSGIService(object):
57 else: 47 else:
58 LOG.warning('Starting unauthenticated, please check' 48 LOG.warning('Starting unauthenticated, please check'
59 ' configuration') 49 ' configuration')
60
61 # TODO(aarefiev): move to WorkerService once we split service
62 if CONF.processing.store_data == 'none':
63 LOG.warning('Introspection data will not be stored. Change '
64 '"[processing] store_data" option if this is not '
65 'the desired behavior')
66 elif CONF.processing.store_data == 'swift':
67 LOG.info('Introspection data will be stored in Swift in the '
68 'container %s', CONF.swift.container)
69 utils.add_cors_middleware(self.app) 50 utils.add_cors_middleware(self.app)
70 51
71 def _create_ssl_context(self): 52 def _create_ssl_context(self):
@@ -99,77 +80,13 @@ class WSGIService(object):
99 'settings: %s', exc) 80 'settings: %s', exc)
100 return context 81 return context
101 82
102 # TODO(aarefiev): move init code to WorkerService
103 def _init_host(self):
104 """Initialize Worker host
105
106 Init db connection, load and validate processing
107 hooks, runs periodic tasks.
108
109 :returns None
110 """
111 db.init()
112
113 try:
114 hooks = plugins_base.validate_processing_hooks()
115 except Exception as exc:
116 LOG.critical(str(exc))
117 sys.exit(1)
118
119 LOG.info('Enabled processing hooks: %s', [h.name for h in hooks])
120
121 driver = pxe_filter.driver()
122 driver.init_filter()
123
124 periodic_clean_up_ = periodics.periodic(
125 spacing=CONF.clean_up_period
126 )(periodic_clean_up)
127
128 self._periodics_worker = periodics.PeriodicWorker(
129 callables=[(driver.get_periodic_sync_task(), None, None),
130 (periodic_clean_up_, None, None)],
131 executor_factory=periodics.ExistingExecutor(utils.executor()),
132 on_failure=self._periodics_watchdog)
133 utils.executor().submit(self._periodics_worker.start)
134
135 def _periodics_watchdog(self, callable_, activity, spacing, exc_info,
136 traceback=None):
137 LOG.exception("The periodic %(callable)s failed with: %(exception)s", {
138 'exception': ''.join(traceback_mod.format_exception(*exc_info)),
139 'callable': reflection.get_callable_name(callable_)})
140 # NOTE(milan): spawn new thread otherwise waiting would block
141 eventlet.spawn(self.shutdown, error=str(exc_info[1]))
142
143 def shutdown(self, error=None): 83 def shutdown(self, error=None):
144 """Stop serving API, clean up. 84 """Stop serving API.
145 85
146 :returns: None 86 :returns: None
147 """ 87 """
148 # TODO(aarefiev): move shutdown code to WorkerService
149 if not self._shutting_down.acquire(blocking=False):
150 LOG.warning('Attempted to shut down while already shutting down')
151 return
152
153 LOG.debug('Shutting down') 88 LOG.debug('Shutting down')
154 89 self.rpc_service.stop()
155 self.rpc_server.stop()
156
157 if self._periodics_worker is not None:
158 try:
159 self._periodics_worker.stop()
160 self._periodics_worker.wait()
161 except Exception as e:
162 LOG.exception('Service error occurred when stopping '
163 'periodic workers. Error: %s', e)
164 self._periodics_worker = None
165
166 if utils.executor().alive:
167 utils.executor().shutdown(wait=True)
168
169 pxe_filter.driver().tear_down_filter()
170
171 self._shutting_down.release()
172 LOG.info('Shut down successfully')
173 sys.exit(error) 90 sys.exit(error)
174 91
175 def run(self): 92 def run(self):
@@ -186,10 +103,9 @@ class WSGIService(object):
186 103
187 self._init_middleware() 104 self._init_middleware()
188 105
189 self._init_host() 106 LOG.info('Spawning RPC service')
190 107 service.launch(CONF, self.rpc_service,
191 self.rpc_server = rpc.get_server() 108 restart_method='mutate')
192 self.rpc_server.start()
193 109
194 try: 110 try:
195 self.app.run(**app_kwargs) 111 self.app.run(**app_kwargs)
@@ -210,20 +126,3 @@ class WSGIService(object):
210 # SIGTERM. Raising KeyboardIntrerrupt which won't be caught by any 126 # SIGTERM. Raising KeyboardIntrerrupt which won't be caught by any
211 # 'except Exception' clauses. 127 # 'except Exception' clauses.
212 raise KeyboardInterrupt 128 raise KeyboardInterrupt
213
214
215def periodic_clean_up(): # pragma: no cover
216 try:
217 if node_cache.clean_up():
218 pxe_filter.driver().sync(ir_utils.get_client())
219 sync_with_ironic()
220 except Exception:
221 LOG.exception('Periodic clean up of node cache failed')
222
223
224def sync_with_ironic():
225 ironic = ir_utils.get_client()
226 # TODO(yuikotakada): pagination
227 ironic_nodes = ironic.node.list(limit=0)
228 ironic_node_uuids = {node.uuid for node in ironic_nodes}
229 node_cache.delete_nodes_not_in_list(ironic_node_uuids)
diff --git a/releasenotes/notes/rpc-backends-0e7405aa1c7723a0.yaml b/releasenotes/notes/rpc-backends-0e7405aa1c7723a0.yaml
new file mode 100644
index 0000000..57abafb
--- /dev/null
+++ b/releasenotes/notes/rpc-backends-0e7405aa1c7723a0.yaml
@@ -0,0 +1,7 @@
1---
2upgrade:
3 - |
4 Adds rpc related configuration options for the communication between
5 ironic-inspector API and worker. It needs to be configured properly
6 during upgrade. Set ``[DEFAULT]transport_url`` to ``fake://`` if a
7 rpc backend is not available or not desired. \ No newline at end of file
diff --git a/tools/config-generator.conf b/tools/config-generator.conf
index fae4268..47fd222 100644
--- a/tools/config-generator.conf
+++ b/tools/config-generator.conf
@@ -6,3 +6,4 @@ namespace = oslo.db
6namespace = oslo.log 6namespace = oslo.log
7namespace = oslo.middleware.cors 7namespace = oslo.middleware.cors
8namespace = oslo.policy 8namespace = oslo.policy
9namespace = oslo.messaging