Skip to content

Commit cb4963b

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "Warn when starting services with older than N-1 computes" into stable/victoria
2 parents c70d974 + 0c5ca35 commit cb4963b

File tree

12 files changed

+242
-1
lines changed

12 files changed

+242
-1
lines changed

doc/source/contributor/ptl-guide.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,9 @@ Immediately after RC
257257

258258
* Example: https://review.opendev.org/543580
259259

260+
* Bump the oldest supported compute service version
261+
* https://review.opendev.org/#/c/738482/
262+
260263
* Create the launchpad series for the next cycle
261264

262265
* Set the development focus of the project to the new cycle series

nova/api/openstack/wsgi_app.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from nova import exception
2424
from nova import objects
2525
from nova import service
26+
from nova import utils
2627

2728
CONF = cfg.CONF
2829

@@ -40,6 +41,11 @@ def _get_config_files(env=None):
4041

4142

4243
def _setup_service(host, name):
44+
try:
45+
utils.raise_if_old_compute()
46+
except exception.TooOldComputeService as e:
47+
logging.getLogger(__name__).warning(str(e))
48+
4349
binary = name if name.startswith('nova-') else "nova-%s" % name
4450

4551
ctxt = context.get_admin_context()

nova/exception.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,14 @@ class ServiceTooOld(Invalid):
559559
"Unable to continue.")
560560

561561

562+
class TooOldComputeService(Invalid):
563+
msg_fmt = _("Current Nova version does not support computes older than "
564+
"%(oldest_supported_version)s but the minimum compute service "
565+
"level in your %(scope)s is %(min_service_level)d and the "
566+
"oldest supported service level is "
567+
"%(oldest_supported_service)d.")
568+
569+
562570
class DestinationDiskExists(Invalid):
563571
msg_fmt = _("The supplied disk path (%(path)s) already exists, "
564572
"it is expected not to exist.")

nova/objects/service.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,13 @@
192192
{'compute_rpc': '5.12'},
193193
)
194194

195+
# This is used to raise an error at service startup if older than N-1 computes
196+
# are detected. Update this at the beginning of every release cycle
197+
OLDEST_SUPPORTED_SERVICE_VERSION = 'Ussuri'
198+
SERVICE_VERSION_ALIASES = {
199+
'Ussuri': 41
200+
}
201+
195202

196203
# TODO(berrange): Remove NovaObjectDictCompat
197204
@base.NovaObjectRegistry.register

nova/service.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,17 @@ def create(cls, host=None, binary=None, topic=None, manager=None,
255255
periodic_fuzzy_delay=periodic_fuzzy_delay,
256256
periodic_interval_max=periodic_interval_max)
257257

258+
# NOTE(gibi): This have to be after the service object creation as
259+
# that is the point where we can safely use the RPC to the conductor.
260+
# E.g. the Service.__init__ actually waits for the conductor to start
261+
# up before it allows the service to be created. The
262+
# raise_if_old_compute() depends on the RPC to be up and does not
263+
# implement its own retry mechanism to connect to the conductor.
264+
try:
265+
utils.raise_if_old_compute()
266+
except exception.TooOldComputeService as e:
267+
LOG.warning(str(e))
268+
258269
return service_obj
259270

260271
def kill(self):

nova/tests/functional/test_service.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@
1010
# License for the specific language governing permissions and limitations
1111
# under the License.
1212

13+
from unittest import mock
14+
1315
from nova import context as nova_context
16+
from nova.objects import service
1417
from nova import test
1518
from nova.tests import fixtures as nova_fixtures
1619
from nova.tests.functional import fixtures as func_fixtures
@@ -97,3 +100,40 @@ def test_service_start_resets_cell_cache(self):
97100
self.metadata.start()
98101
# Cell cache should be empty after the service reset.
99102
self.assertEqual({}, nova_context.CELL_CACHE)
103+
104+
105+
class TestOldComputeCheck(
106+
test.TestCase, integrated_helpers.InstanceHelperMixin):
107+
108+
def test_conductor_warns_if_old_compute(self):
109+
old_version = service.SERVICE_VERSION_ALIASES[
110+
service.OLDEST_SUPPORTED_SERVICE_VERSION] - 1
111+
with mock.patch(
112+
"nova.objects.service.get_minimum_version_all_cells",
113+
return_value=old_version):
114+
self.start_service('conductor')
115+
self.assertIn(
116+
'Current Nova version does not support computes older than',
117+
self.stdlog.logger.output)
118+
119+
def test_api_warns_if_old_compute(self):
120+
old_version = service.SERVICE_VERSION_ALIASES[
121+
service.OLDEST_SUPPORTED_SERVICE_VERSION] - 1
122+
with mock.patch(
123+
"nova.objects.service.get_minimum_version_all_cells",
124+
return_value=old_version):
125+
self.useFixture(nova_fixtures.OSAPIFixture(api_version='v2.1'))
126+
self.assertIn(
127+
'Current Nova version does not support computes older than',
128+
self.stdlog.logger.output)
129+
130+
def test_compute_warns_if_old_compute(self):
131+
old_version = service.SERVICE_VERSION_ALIASES[
132+
service.OLDEST_SUPPORTED_SERVICE_VERSION] - 1
133+
with mock.patch(
134+
"nova.objects.service.get_minimum_version_all_cells",
135+
return_value=old_version):
136+
self._start_compute('host1')
137+
self.assertIn(
138+
'Current Nova version does not support computes older than',
139+
self.stdlog.logger.output)

nova/tests/unit/api/openstack/test_requestlog.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ def setUp(self):
4242
# this is the minimal set of magic mocks needed to convince
4343
# the API service it can start on it's own without a database.
4444
mocks = ['nova.objects.Service.get_by_host_and_binary',
45-
'nova.objects.Service.create']
45+
'nova.objects.Service.create',
46+
'nova.utils.raise_if_old_compute']
4647
self.stdlog = fixtures.StandardLogging()
4748
self.useFixture(self.stdlog)
4849
for m in mocks:

nova/tests/unit/test_fixtures.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ def test_debug_logging(self):
9090
class TestOSAPIFixture(testtools.TestCase):
9191
@mock.patch('nova.objects.Service.get_by_host_and_binary')
9292
@mock.patch('nova.objects.Service.create')
93+
@mock.patch('nova.utils.raise_if_old_compute', new=mock.Mock())
9394
def test_responds_to_version(self, mock_service_create, mock_get):
9495
"""Ensure the OSAPI server responds to calls sensibly."""
9596
self.useFixture(output.CaptureOutput())

nova/tests/unit/test_service.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,24 @@ def test_reset(self):
268268
serv.reset()
269269
mock_reset.assert_called_once_with()
270270

271+
@mock.patch('nova.conductor.api.API.wait_until_ready')
272+
@mock.patch('nova.utils.raise_if_old_compute')
273+
def test_old_compute_version_check_happens_after_wait_for_conductor(
274+
self, mock_check_old, mock_wait):
275+
obj_base.NovaObject.indirection_api = mock.MagicMock()
276+
277+
def fake_wait(*args, **kwargs):
278+
mock_check_old.assert_not_called()
279+
280+
mock_wait.side_effect = fake_wait
281+
282+
service.Service.create(
283+
self.host, self.binary, self.topic,
284+
'nova.tests.unit.test_service.FakeManager')
285+
286+
mock_check_old.assert_called_once_with()
287+
mock_wait.assert_called_once_with(mock.ANY)
288+
271289

272290
class TestWSGIService(test.NoDBTestCase):
273291

nova/tests/unit/test_utils.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
from nova import exception
3838
from nova.objects import base as obj_base
3939
from nova.objects import instance as instance_obj
40+
from nova.objects import service as service_obj
4041
from nova import test
4142
from nova.tests import fixtures as nova_fixtures
4243
from nova.tests.unit.objects import test_objects
@@ -1207,3 +1208,89 @@ def test_get_sdk_adapter_conf_group_fail(self):
12071208
self.mock_get_confgrp.assert_called_once_with(self.service_type)
12081209
self.mock_connection.assert_not_called()
12091210
self.mock_get_auth_sess.assert_not_called()
1211+
1212+
1213+
class TestOldComputeCheck(test.NoDBTestCase):
1214+
1215+
@mock.patch('nova.objects.service.get_minimum_version_all_cells')
1216+
def test_no_compute(self, mock_get_min_service):
1217+
mock_get_min_service.return_value = 0
1218+
1219+
utils.raise_if_old_compute()
1220+
1221+
mock_get_min_service.assert_called_once_with(
1222+
mock.ANY, ['nova-compute'])
1223+
1224+
@mock.patch('nova.objects.service.get_minimum_version_all_cells')
1225+
def test_old_but_supported_compute(self, mock_get_min_service):
1226+
oldest = service_obj.SERVICE_VERSION_ALIASES[
1227+
service_obj.OLDEST_SUPPORTED_SERVICE_VERSION]
1228+
mock_get_min_service.return_value = oldest
1229+
1230+
utils.raise_if_old_compute()
1231+
1232+
mock_get_min_service.assert_called_once_with(
1233+
mock.ANY, ['nova-compute'])
1234+
1235+
@mock.patch('nova.objects.service.get_minimum_version_all_cells')
1236+
def test_new_compute(self, mock_get_min_service):
1237+
mock_get_min_service.return_value = service_obj.SERVICE_VERSION
1238+
1239+
utils.raise_if_old_compute()
1240+
1241+
mock_get_min_service.assert_called_once_with(
1242+
mock.ANY, ['nova-compute'])
1243+
1244+
@mock.patch('nova.objects.service.Service.get_minimum_version')
1245+
def test_too_old_compute_cell(self, mock_get_min_service):
1246+
self.flags(group='api_database', connection=None)
1247+
oldest = service_obj.SERVICE_VERSION_ALIASES[
1248+
service_obj.OLDEST_SUPPORTED_SERVICE_VERSION]
1249+
mock_get_min_service.return_value = oldest - 1
1250+
1251+
ex = self.assertRaises(
1252+
exception.TooOldComputeService, utils.raise_if_old_compute)
1253+
1254+
self.assertIn('cell', str(ex))
1255+
mock_get_min_service.assert_called_once_with(mock.ANY, 'nova-compute')
1256+
1257+
@mock.patch('nova.objects.service.get_minimum_version_all_cells')
1258+
def test_too_old_compute_top_level(self, mock_get_min_service):
1259+
self.flags(group='api_database', connection='fake db connection')
1260+
oldest = service_obj.SERVICE_VERSION_ALIASES[
1261+
service_obj.OLDEST_SUPPORTED_SERVICE_VERSION]
1262+
mock_get_min_service.return_value = oldest - 1
1263+
1264+
ex = self.assertRaises(
1265+
exception.TooOldComputeService, utils.raise_if_old_compute)
1266+
1267+
self.assertIn('system', str(ex))
1268+
mock_get_min_service.assert_called_once_with(
1269+
mock.ANY, ['nova-compute'])
1270+
1271+
@mock.patch.object(utils.LOG, 'warning')
1272+
@mock.patch('nova.objects.service.Service.get_minimum_version')
1273+
@mock.patch('nova.objects.service.get_minimum_version_all_cells')
1274+
def test_api_db_is_configured_but_the_service_cannot_access_db(
1275+
self, mock_get_all, mock_get, mock_warn):
1276+
# This is the case when the nova-compute service is wrongly configured
1277+
# with db credentials but nova-compute is never allowed to access the
1278+
# db directly.
1279+
mock_get_all.side_effect = exception.DBNotAllowed(
1280+
binary='nova-compute')
1281+
1282+
oldest = service_obj.SERVICE_VERSION_ALIASES[
1283+
service_obj.OLDEST_SUPPORTED_SERVICE_VERSION]
1284+
mock_get.return_value = oldest - 1
1285+
1286+
ex = self.assertRaises(
1287+
exception.TooOldComputeService, utils.raise_if_old_compute)
1288+
1289+
self.assertIn('cell', str(ex))
1290+
mock_get_all.assert_called_once_with(mock.ANY, ['nova-compute'])
1291+
mock_get.assert_called_once_with(mock.ANY, 'nova-compute')
1292+
mock_warn.assert_called_once_with(
1293+
'This service is configured for access to the API database but is '
1294+
'not allowed to directly access the database. You should run this '
1295+
'service without the [api_database]/connection config option. The '
1296+
'service version check will only query the local cell.')

0 commit comments

Comments
 (0)