Skip to content

Commit

Permalink
Add Service.get_minimum_version_multi() for multiple binaries
Browse files Browse the repository at this point in the history
This is an optimization to make it faster to look up a bunch of
service versions.

Change-Id: I45bfdcd7a37c8b9dad2407c958c9d7f5f828ded6
  • Loading branch information
kk7ds authored and jaypipes committed Mar 31, 2016
1 parent 8941b45 commit 11cb56a
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 31 deletions.
11 changes: 6 additions & 5 deletions nova/db/sqlalchemy/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -508,13 +508,14 @@ def service_get(context, service_id):


@pick_context_manager_reader_allow_async
def service_get_minimum_version(context, binary):
min_version = context.session.query(
def service_get_minimum_version(context, binaries):
min_versions = context.session.query(
models.Service.binary,
func.min(models.Service.version)).\
filter(models.Service.binary == binary).\
filter(models.Service.binary.in_(binaries)).\
filter(models.Service.forced_down == false()).\
scalar()
return min_version
group_by(models.Service.binary)
return dict(min_versions)


@pick_context_manager_reader
Expand Down
45 changes: 30 additions & 15 deletions nova/objects/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ class Service(base.NovaPersistentObject, base.NovaObject,
# Version 1.17: ComputeNode version 1.13
# Version 1.18: ComputeNode version 1.14
# Version 1.19: Added get_minimum_version()
VERSION = '1.19'
# Version 1.20: Added get_minimum_version_multi()
VERSION = '1.20'

fields = {
'id': fields.IntegerField(read_only=True),
Expand Down Expand Up @@ -330,32 +331,46 @@ def clear_min_version_cache(cls):

@staticmethod
@db.select_db_reader_mode
def _db_service_get_minimum_version(context, binary, use_slave=False):
return db.service_get_minimum_version(context, binary)
def _db_service_get_minimum_version(context, binaries, use_slave=False):
return db.service_get_minimum_version(context, binaries)

@base.remotable_classmethod
def get_minimum_version(cls, context, binary, use_slave=False):
if not binary.startswith('nova-'):
def get_minimum_version_multi(cls, context, binaries, use_slave=False):
if not all(binary.startswith('nova-') for binary in binaries):
LOG.warning(_LW('get_minimum_version called with likely-incorrect '
'binary `%s\''), binary)
'binaries `%s\''), ','.join(binaries))
raise exception.ObjectActionError(action='get_minimum_version',
reason='Invalid binary prefix')

if cls._SERVICE_VERSION_CACHING:
cached_version = cls._MIN_VERSION_CACHE.get(binary)
if cached_version:
return cached_version
version = cls._db_service_get_minimum_version(context, binary,
use_slave=use_slave)
if version is None:
return 0
if (not cls._SERVICE_VERSION_CACHING or
any(binary not in cls._MIN_VERSION_CACHE
for binary in binaries)):
min_versions = cls._db_service_get_minimum_version(
context, binaries, use_slave=use_slave)
if min_versions:
min_versions = {binary: version or 0
for binary, version in
min_versions.items()}
cls._MIN_VERSION_CACHE.update(min_versions)
else:
min_versions = {binary: cls._MIN_VERSION_CACHE[binary]
for binary in binaries}

if min_versions:
version = min(min_versions.values())
else:
version = 0
# NOTE(danms): Since our return value is not controlled by object
# schema, be explicit here.
version = int(version)
cls._MIN_VERSION_CACHE[binary] = version

return version

@base.remotable_classmethod
def get_minimum_version(cls, context, binary, use_slave=False):
return cls.get_minimum_version_multi(context, [binary],
use_slave=use_slave)


@base.NovaObjectRegistry.register
class ServiceList(base.ObjectListBase, base.NovaObject):
Expand Down
5 changes: 3 additions & 2 deletions nova/tests/unit/db/test_db_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3384,8 +3384,9 @@ def test_service_get_minimum_version(self):
self._create_service({'version': 3,
'host': 'host2',
'binary': 'compute'})
self.assertEqual(2, db.service_get_minimum_version(self.ctxt,
'compute'))
self.assertEqual({'compute': 2},
db.service_get_minimum_version(self.ctxt,
['compute']))

def test_service_get_not_found_exception(self):
self.assertRaises(exception.ServiceNotFound,
Expand Down
2 changes: 1 addition & 1 deletion nova/tests/unit/objects/test_objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -1180,7 +1180,7 @@ def obj_name(cls):
'SecurityGroupList': '1.0-dc8bbea01ba09a2edb6e5233eae85cbc',
'SecurityGroupRule': '1.1-ae1da17b79970012e8536f88cb3c6b29',
'SecurityGroupRuleList': '1.2-0005c47fcd0fb78dd6d7fd32a1409f5b',
'Service': '1.19-8914320cbeb4ec29f252d72ce55d07e1',
'Service': '1.20-0f9c0bf701e68640b78638fd09e2cddc',
'ServiceList': '1.18-6c52cb616621c1af2415dcc11faf5c1a',
'ServiceStatusNotification': '1.0-a73147b93b520ff0061865849d3dfa56',
'ServiceStatusPayload': '1.0-a5e7b4fd6cc5581be45b31ff1f3a3f7f',
Expand Down
43 changes: 35 additions & 8 deletions nova/tests/unit/objects/test_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,15 +304,15 @@ def test_get_minimum_version_none(self, mock_get):
self.assertEqual(0,
objects.Service.get_minimum_version(self.context,
'nova-compute'))
mock_get.assert_called_once_with(self.context, 'nova-compute')
mock_get.assert_called_once_with(self.context, ['nova-compute'])

@mock.patch('nova.db.service_get_minimum_version')
def test_get_minimum_version(self, mock_get):
mock_get.return_value = 123
mock_get.return_value = {'nova-compute': 123}
self.assertEqual(123,
objects.Service.get_minimum_version(self.context,
'nova-compute'))
mock_get.assert_called_once_with(self.context, 'nova-compute')
mock_get.assert_called_once_with(self.context, ['nova-compute'])

@mock.patch('nova.db.service_get_minimum_version')
@mock.patch('nova.objects.service.LOG')
Expand All @@ -331,7 +331,7 @@ def test_get_minimum_version_checks_binary(self, mock_log, mock_get):
@mock.patch('nova.db.service_get_minimum_version')
def test_get_minimum_version_with_caching(self, mock_get):
objects.Service.enable_min_version_cache()
mock_get.return_value = 123
mock_get.return_value = {'nova-compute': 123}
self.assertEqual(123,
objects.Service.get_minimum_version(self.context,
'nova-compute'))
Expand All @@ -340,25 +340,52 @@ def test_get_minimum_version_with_caching(self, mock_get):
self.assertEqual(123,
objects.Service.get_minimum_version(self.context,
'nova-compute'))
mock_get.assert_called_once_with(self.context, 'nova-compute')
mock_get.assert_called_once_with(self.context, ['nova-compute'])
objects.Service._SERVICE_VERSION_CACHING = False
objects.Service.clear_min_version_cache()

@mock.patch('nova.db.service_get_minimum_version', return_value=2)
@mock.patch('nova.db.service_get_minimum_version')
def test_get_min_version_multiple_with_old(self, mock_gmv):
mock_gmv.return_value = {'nova-api': None,
'nova-scheduler': 2,
'nova-conductor': 3}

binaries = ['nova-api', 'nova-api', 'nova-conductor',
'nova-conductor', 'nova-api']
minimum = objects.Service.get_minimum_version_multi(self.context,
binaries)
self.assertEqual(0, minimum)

@mock.patch('nova.db.service_get_minimum_version')
def test_get_min_version_multiple(self, mock_gmv):
mock_gmv.return_value = {'nova-api': 1,
'nova-scheduler': 2,
'nova-conductor': 3}

binaries = ['nova-api', 'nova-api', 'nova-conductor',
'nova-conductor', 'nova-api']
minimum = objects.Service.get_minimum_version_multi(self.context,
binaries)
self.assertEqual(1, minimum)

@mock.patch('nova.db.service_get_minimum_version',
return_value={'nova-compute': 2})
def test_create_above_minimum(self, mock_get):
with mock.patch('nova.objects.service.SERVICE_VERSION',
new=3):
objects.Service(context=self.context,
binary='nova-compute').create()

@mock.patch('nova.db.service_get_minimum_version', return_value=2)
@mock.patch('nova.db.service_get_minimum_version',
return_value={'nova-compute': 2})
def test_create_equal_to_minimum(self, mock_get):
with mock.patch('nova.objects.service.SERVICE_VERSION',
new=2):
objects.Service(context=self.context,
binary='nova-compute').create()

@mock.patch('nova.db.service_get_minimum_version', return_value=2)
@mock.patch('nova.db.service_get_minimum_version',
return_value={'nova-compute': 2})
def test_create_below_minimum(self, mock_get):
with mock.patch('nova.objects.service.SERVICE_VERSION',
new=1):
Expand Down

0 comments on commit 11cb56a

Please sign in to comment.