nova/nova/tests/unit/objects/test_service.py
Matt Riedemann de5c338792 Remove service_uuids_online_data_migration
A blocker migration was added in Train [1] to force
deployments to make sure they have completed the
services.uuid online migration (added in Pike). Now
that we're in Ussuri we can drop that online data
migration code.

Note that InstanceListWithServicesTestCase is removed
because the scenario is now invalid with the blocker
DB migration.

[1] I8927b8a4513dab242d34953d13dd2cc95393dc80

Change-Id: If77702f0c3212f904443f627037782f9ad7b3b55
2019-11-08 11:38:48 -05:00

570 lines
25 KiB
Python

# Copyright 2013 IBM Corp.
#
# 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 mock
from oslo_utils.fixture import uuidsentinel
from oslo_utils import timeutils
from oslo_versionedobjects import base as ovo_base
from oslo_versionedobjects import exception as ovo_exc
import six
from nova.compute import manager as compute_manager
from nova import context
from nova.db import api as db
from nova import exception
from nova import objects
from nova.objects import aggregate
from nova.objects import service
from nova import test
from nova.tests import fixtures
from nova.tests.unit.objects import test_compute_node
from nova.tests.unit.objects import test_objects
NOW = timeutils.utcnow().replace(microsecond=0)
def _fake_service(**kwargs):
fake_service = {
'created_at': NOW,
'updated_at': None,
'deleted_at': None,
'deleted': False,
'id': 123,
'uuid': uuidsentinel.service,
'host': 'fake-host',
'binary': 'nova-compute',
'topic': 'fake-service-topic',
'report_count': 1,
'forced_down': False,
'disabled': False,
'disabled_reason': None,
'last_seen_up': None,
'version': service.SERVICE_VERSION,
}
fake_service.update(kwargs)
return fake_service
fake_service = _fake_service()
OPTIONAL = ['availability_zone', 'compute_node']
class _TestServiceObject(object):
def supported_hv_specs_comparator(self, expected, obj_val):
obj_val = [inst.to_list() for inst in obj_val]
self.assertJsonEqual(expected, obj_val)
def pci_device_pools_comparator(self, expected, obj_val):
obj_val = obj_val.obj_to_primitive()
self.assertJsonEqual(expected, obj_val)
def comparators(self):
return {'stats': self.assertJsonEqual,
'host_ip': self.assertJsonEqual,
'supported_hv_specs': self.supported_hv_specs_comparator,
'pci_device_pools': self.pci_device_pools_comparator}
def subs(self):
return {'supported_hv_specs': 'supported_instances',
'pci_device_pools': 'pci_stats'}
def _test_query(self, db_method, obj_method, *args, **kwargs):
db_exception = kwargs.pop('db_exception', None)
if db_exception:
with mock.patch.object(db, db_method, side_effect=db_exception) \
as mock_db_method:
obj = getattr(service.Service, obj_method)(self.context, *args,
**kwargs)
self.assertIsNone(obj)
mock_db_method.assert_called_once_with(self.context, *args,
**kwargs)
else:
with mock.patch.object(db, db_method, return_value=fake_service) \
as mock_db_method:
obj = getattr(service.Service, obj_method)(self.context, *args,
**kwargs)
self.compare_obj(obj, fake_service, allow_missing=OPTIONAL)
mock_db_method.assert_called_once_with(self.context, *args,
**kwargs)
def test_get_by_id(self):
self._test_query('service_get', 'get_by_id', 123)
def test_get_by_uuid(self):
self._test_query('service_get_by_uuid', 'get_by_uuid',
uuidsentinel.service_uuid)
def test_get_by_host_and_topic(self):
self._test_query('service_get_by_host_and_topic',
'get_by_host_and_topic', 'fake-host', 'fake-topic')
def test_get_by_host_and_binary(self):
self._test_query('service_get_by_host_and_binary',
'get_by_host_and_binary', 'fake-host', 'fake-binary')
def test_get_by_host_and_binary_raises(self):
self._test_query('service_get_by_host_and_binary',
'get_by_host_and_binary', 'fake-host', 'fake-binary',
db_exception=exception.HostBinaryNotFound(
host='fake-host', binary='fake-binary'))
def test_get_by_compute_host(self):
self._test_query('service_get_by_compute_host', 'get_by_compute_host',
'fake-host')
def test_get_by_args(self):
self._test_query('service_get_by_host_and_binary', 'get_by_args',
'fake-host', 'fake-binary')
@mock.patch.object(db, 'service_create', return_value=fake_service)
def test_create(self, mock_service_create):
service_obj = service.Service(context=self.context)
service_obj.host = 'fake-host'
service_obj.uuid = uuidsentinel.service2
service_obj.create()
self.assertEqual(fake_service['id'], service_obj.id)
self.assertEqual(service.SERVICE_VERSION, service_obj.version)
mock_service_create.assert_called_once_with(
self.context, {'host': 'fake-host',
'uuid': uuidsentinel.service2,
'version': fake_service['version']})
@mock.patch('nova.objects.service.uuidutils.generate_uuid',
return_value=uuidsentinel.service3)
@mock.patch.object(db, 'service_create', return_value=fake_service)
def test_create_without_uuid_generates_one(
self, mock_service_create, generate_uuid):
service_obj = service.Service(context=self.context)
service_obj.create()
create_args = mock_service_create.call_args[0][1]
self.assertEqual(generate_uuid.return_value, create_args['uuid'])
@mock.patch.object(db, 'service_create', return_value=fake_service)
def test_recreate_fails(self, mock_service_create):
service_obj = service.Service(context=self.context)
service_obj.host = 'fake-host'
service_obj.create()
self.assertRaises(exception.ObjectActionError, service_obj.create)
mock_service_create(self.context, {'host': 'fake-host',
'version': fake_service['version']})
@mock.patch('nova.objects.Service._send_notification')
@mock.patch.object(db, 'service_update', return_value=fake_service)
def test_save(self, mock_service_update, mock_notify):
service_obj = service.Service(context=self.context)
service_obj.id = 123
service_obj.host = 'fake-host'
service_obj.save()
self.assertEqual(service.SERVICE_VERSION, service_obj.version)
mock_service_update.assert_called_once_with(
self.context, 123, {'host': 'fake-host',
'version': fake_service['version']})
@mock.patch.object(db, 'service_create',
return_value=fake_service)
def test_set_id_failure(self, db_mock):
service_obj = service.Service(context=self.context,
binary='nova-compute')
service_obj.create()
self.assertRaises(ovo_exc.ReadOnlyFieldError, setattr,
service_obj, 'id', 124)
@mock.patch('nova.objects.Service._send_notification')
@mock.patch.object(db, 'service_destroy')
def _test_destroy(self, mock_service_destroy, mock_notify):
service_obj = service.Service(context=self.context)
service_obj.id = 123
service_obj.destroy()
mock_service_destroy.assert_called_once_with(self.context, 123)
def test_destroy(self):
# The test harness needs db.service_destroy to work,
# so avoid leaving it broken here after we're done
orig_service_destroy = db.service_destroy
try:
self._test_destroy()
finally:
db.service_destroy = orig_service_destroy
@mock.patch.object(db, 'service_get_all_by_topic',
return_value=[fake_service])
def test_get_by_topic(self, mock_service_get):
services = service.ServiceList.get_by_topic(self.context, 'fake-topic')
self.assertEqual(1, len(services))
self.compare_obj(services[0], fake_service, allow_missing=OPTIONAL)
mock_service_get.assert_called_once_with(self.context, 'fake-topic')
@mock.patch('nova.db.api.service_get_all_by_binary')
def test_get_by_binary(self, mock_get):
mock_get.return_value = [fake_service]
services = service.ServiceList.get_by_binary(self.context,
'fake-binary')
self.assertEqual(1, len(services))
mock_get.assert_called_once_with(self.context,
'fake-binary',
include_disabled=False)
@mock.patch('nova.db.api.service_get_all_by_binary')
def test_get_by_binary_disabled(self, mock_get):
mock_get.return_value = [_fake_service(disabled=True)]
services = service.ServiceList.get_by_binary(self.context,
'fake-binary',
include_disabled=True)
self.assertEqual(1, len(services))
mock_get.assert_called_once_with(self.context,
'fake-binary',
include_disabled=True)
@mock.patch('nova.db.api.service_get_all_by_binary')
def test_get_by_binary_both(self, mock_get):
mock_get.return_value = [_fake_service(),
_fake_service(disabled=True)]
services = service.ServiceList.get_by_binary(self.context,
'fake-binary',
include_disabled=True)
self.assertEqual(2, len(services))
mock_get.assert_called_once_with(self.context,
'fake-binary',
include_disabled=True)
@mock.patch.object(db, 'service_get_all_by_host',
return_value=[fake_service])
def test_get_by_host(self, mock_service_get):
services = service.ServiceList.get_by_host(self.context, 'fake-host')
self.assertEqual(1, len(services))
self.compare_obj(services[0], fake_service, allow_missing=OPTIONAL)
mock_service_get.assert_called_once_with(self.context, 'fake-host')
@mock.patch.object(db, 'service_get_all', return_value=[fake_service])
def test_get_all(self, mock_get_all):
services = service.ServiceList.get_all(self.context, disabled=False)
self.assertEqual(1, len(services))
self.compare_obj(services[0], fake_service, allow_missing=OPTIONAL)
mock_get_all.assert_called_once_with(self.context, disabled=False)
@mock.patch.object(db, 'service_get_all')
@mock.patch.object(aggregate.AggregateList, 'get_by_metadata_key')
def test_get_all_with_az(self, mock_get_by_key, mock_get_all):
agg = aggregate.Aggregate(context=self.context)
agg.name = 'foo'
agg.metadata = {'availability_zone': 'test-az'}
agg.create()
agg.hosts = [fake_service['host']]
mock_get_by_key.return_value = [agg]
mock_get_all.return_value = [dict(fake_service, topic='compute')]
services = service.ServiceList.get_all(self.context, set_zones=True)
self.assertEqual(1, len(services))
self.assertEqual('test-az', services[0].availability_zone)
mock_get_all.assert_called_once_with(self.context, disabled=None)
mock_get_by_key.assert_called_once_with(self.context,
'availability_zone', hosts=set(agg.hosts))
@mock.patch.object(objects.ComputeNodeList, 'get_all_by_host')
def test_compute_node(self, mock_get):
fake_compute_node = objects.ComputeNode._from_db_object(
self.context, objects.ComputeNode(),
test_compute_node.fake_compute_node)
mock_get.return_value = [fake_compute_node]
service_obj = service.Service(id=123, host="fake-host",
binary="nova-compute")
service_obj._context = self.context
self.assertEqual(service_obj.compute_node,
fake_compute_node)
# Make sure it doesn't re-fetch this
service_obj.compute_node
mock_get.assert_called_once_with(self.context, 'fake-host')
@mock.patch.object(db, 'service_get_all_computes_by_hv_type')
def test_get_all_computes_by_hv_type(self, mock_get_all):
mock_get_all.return_value = [fake_service]
services = service.ServiceList.get_all_computes_by_hv_type(
self.context, 'hv-type')
self.assertEqual(1, len(services))
self.compare_obj(services[0], fake_service, allow_missing=OPTIONAL)
mock_get_all.assert_called_once_with(self.context, 'hv-type',
include_disabled=False)
def test_load_when_orphaned(self):
service_obj = service.Service()
service_obj.id = 123
self.assertRaises(exception.OrphanedObjectError,
getattr, service_obj, 'compute_node')
@mock.patch.object(objects.ComputeNodeList, 'get_all_by_host')
def test_obj_make_compatible_for_compute_node(self, get_all_by_host):
service_obj = objects.Service(context=self.context)
fake_service_dict = fake_service.copy()
fake_compute_obj = objects.ComputeNode(host=fake_service['host'],
service_id=fake_service['id'])
get_all_by_host.return_value = [fake_compute_obj]
versions = ovo_base.obj_tree_get_versions('Service')
versions['ComputeNode'] = '1.10'
service_obj.obj_make_compatible_from_manifest(fake_service_dict, '1.9',
versions)
self.assertEqual(
fake_compute_obj.obj_to_primitive(target_version='1.10',
version_manifest=versions),
fake_service_dict['compute_node'])
@mock.patch('nova.db.api.service_get_minimum_version')
def test_get_minimum_version_none(self, mock_get):
mock_get.return_value = None
self.assertEqual(0,
objects.Service.get_minimum_version(self.context,
'nova-compute'))
mock_get.assert_called_once_with(self.context, ['nova-compute'])
@mock.patch('nova.db.api.service_get_minimum_version')
def test_get_minimum_version(self, mock_get):
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.patch('nova.db.api.service_get_minimum_version')
@mock.patch('nova.objects.service.LOG')
def test_get_minimum_version_checks_binary(self, mock_log, mock_get):
mock_get.return_value = None
self.assertEqual(0,
objects.Service.get_minimum_version(self.context,
'nova-compute'))
self.assertFalse(mock_log.warning.called)
self.assertRaises(exception.ObjectActionError,
objects.Service.get_minimum_version,
self.context,
'compute')
self.assertTrue(mock_log.warning.called)
@mock.patch('nova.db.api.service_get_minimum_version')
def test_get_minimum_version_with_caching(self, mock_get):
objects.Service.enable_min_version_cache()
mock_get.return_value = {'nova-compute': 123}
self.assertEqual(123,
objects.Service.get_minimum_version(self.context,
'nova-compute'))
self.assertEqual({"nova-compute": 123},
objects.Service._MIN_VERSION_CACHE)
self.assertEqual(123,
objects.Service.get_minimum_version(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.api.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.api.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.objects.Service._send_notification')
@mock.patch('nova.db.api.service_get_minimum_version',
return_value={'nova-compute': 2})
def test_create_above_minimum(self, mock_get, mock_notify):
with mock.patch('nova.objects.service.SERVICE_VERSION',
new=3):
objects.Service(context=self.context,
binary='nova-compute').create()
@mock.patch('nova.objects.Service._send_notification')
@mock.patch('nova.db.api.service_get_minimum_version',
return_value={'nova-compute': 2})
def test_create_equal_to_minimum(self, mock_get, mock_notify):
with mock.patch('nova.objects.service.SERVICE_VERSION',
new=2):
objects.Service(context=self.context,
binary='nova-compute').create()
@mock.patch('nova.db.api.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):
self.assertRaises(exception.ServiceTooOld,
objects.Service(context=self.context,
binary='nova-compute',
).create)
@mock.patch('nova.objects.base.NovaObject'
'.obj_make_compatible_from_manifest', new=mock.Mock())
def test_obj_make_compatible_from_manifest_strips_uuid(self):
s = service.Service()
primitive = {'uuid': uuidsentinel.service}
s.obj_make_compatible_from_manifest(primitive, '1.20', mock.Mock())
self.assertNotIn('uuid', primitive)
class TestServiceObject(test_objects._LocalTest,
_TestServiceObject):
pass
class TestRemoteServiceObject(test_objects._RemoteTest,
_TestServiceObject):
pass
class TestServiceVersion(test.NoDBTestCase):
def setUp(self):
self.ctxt = context.get_admin_context()
super(TestServiceVersion, self).setUp()
def _collect_things(self):
data = {
'compute_rpc': compute_manager.ComputeManager.target.version,
}
return data
def test_version(self):
calculated = self._collect_things()
self.assertEqual(
len(service.SERVICE_VERSION_HISTORY), service.SERVICE_VERSION + 1,
'Service version %i has no history. Please update '
'nova.objects.service.SERVICE_VERSION_HISTORY '
'and add %s to it' % (service.SERVICE_VERSION, repr(calculated)))
current = service.SERVICE_VERSION_HISTORY[service.SERVICE_VERSION]
self.assertEqual(
current, calculated,
'Changes detected that require a SERVICE_VERSION change. Please '
'increment nova.objects.service.SERVICE_VERSION, and make sure it '
'is equal to nova.compute.manager.ComputeManager.target.version.')
def test_version_in_init(self):
self.assertRaises(exception.ObjectActionError,
objects.Service,
version=123)
def test_version_set_on_init(self):
self.assertEqual(service.SERVICE_VERSION,
objects.Service().version)
def test_version_loaded_from_db(self):
fake_version = fake_service['version'] + 1
fake_different_service = dict(fake_service)
fake_different_service['version'] = fake_version
obj = objects.Service()
obj._from_db_object(self.ctxt, obj, fake_different_service)
self.assertEqual(fake_version, obj.version)
class TestServiceVersionCells(test.TestCase):
def setUp(self):
self.context = context.get_admin_context()
super(TestServiceVersionCells, self).setUp()
def _setup_cells(self):
# NOTE(danms): Override the base class's cell setup so we can have two
self.cells = fixtures.CellDatabases()
self.cells.add_cell_database(uuidsentinel.cell1, default=True)
self.cells.add_cell_database(uuidsentinel.cell2)
self.useFixture(self.cells)
cm = objects.CellMapping(context=self.context,
uuid=uuidsentinel.cell1,
name='cell1',
transport_url='fake://nowhere/',
database_connection=uuidsentinel.cell1)
cm.create()
cm = objects.CellMapping(context=self.context,
uuid=uuidsentinel.cell2,
name='cell2',
transport_url='fake://nowhere/',
database_connection=uuidsentinel.cell2)
cm.create()
def _create_services(self, *versions):
cells = objects.CellMappingList.get_all(self.context)
index = 0
for version in versions:
service = objects.Service(context=self.context,
binary='nova-compute')
service.version = version
cell = cells[index % len(cells)]
with context.target_cell(self.context, cell):
service.create()
index += 1
@mock.patch('nova.objects.Service._send_notification')
@mock.patch('nova.objects.Service._check_minimum_version')
def test_version_all_cells(self, mock_check, mock_notify):
self._create_services(16, 16, 13, 16)
self.assertEqual(13, service.get_minimum_version_all_cells(
self.context, ['nova-compute']))
@mock.patch('nova.objects.service.LOG')
def test_get_minimum_version_checks_binary(self, mock_log):
ex = self.assertRaises(exception.ObjectActionError,
service.get_minimum_version_all_cells,
self.context, ['compute'])
self.assertIn('Invalid binary prefix', six.text_type(ex))
self.assertTrue(mock_log.warning.called)
@mock.patch('nova.context.scatter_gather_all_cells')
def test_version_all_cells_with_fail(self, mock_scatter):
mock_scatter.return_value = {
'foo': {'nova-compute': 13},
'bar': exception.ServiceNotFound(service_id='fake'),
}
self.assertEqual(13, service.get_minimum_version_all_cells(
self.context, ['nova-compute']))
self.assertRaises(exception.CellTimeout,
service.get_minimum_version_all_cells,
self.context, ['nova-compute'],
require_all=True)
@mock.patch('nova.context.scatter_gather_all_cells')
def test_version_all_cells_with_timeout(self, mock_scatter):
mock_scatter.return_value = {
'foo': {'nova-compute': 13},
'bar': context.did_not_respond_sentinel,
}
self.assertEqual(13, service.get_minimum_version_all_cells(
self.context, ['nova-compute']))
self.assertRaises(exception.CellTimeout,
service.get_minimum_version_all_cells,
self.context, ['nova-compute'],
require_all=True)
@mock.patch('nova.context.scatter_gather_all_cells')
def test_version_all_cells_exclude_zero_service(self, mock_scatter):
mock_scatter.return_value = {
'foo': {'nova-compute': 13},
'bar': {'nova-compute': 0},
}
self.assertEqual(13, service.get_minimum_version_all_cells(
self.context, ['nova-compute']))