Minimal construct plumbing for nova list when a cell is down

This patch augments the existing behavior of nova list which
ignores a down cell to returning a partial construct consisting of
uuid, project_id and created_at fields obtained from the API
instance_mappings table for the instances in the down cell by
introducing a new API microversion. This way when the user
gets partial results for certain records, he/she will know
that that particular cell is unreachable. Note that the other
keys will be missing from the API response. Also note that this
is not externally poke-able until the microversion and API change
later in this series.

Related to blueprint handling-down-cell

Change-Id: Ie9a9aed0676d469e5180d4c2d2631cf339335b2d
This commit is contained in:
Surya Seetharaman 2018-08-22 12:26:11 +02:00 committed by Matt Riedemann
parent d4f8040117
commit 37a5ef5113
9 changed files with 309 additions and 108 deletions

View File

@ -270,8 +270,8 @@ class ServersController(wsgi.Controller):
try:
instance_list = self.compute_api.get_all(elevated or context,
search_opts=search_opts, limit=limit, marker=marker,
expected_attrs=expected_attrs,
sort_keys=sort_keys, sort_dirs=sort_dirs)
expected_attrs=expected_attrs, sort_keys=sort_keys,
sort_dirs=sort_dirs, cell_down_support=False)
except exception.MarkerNotFound:
msg = _('marker [%s] not found') % marker
raise exc.HTTPBadRequest(explanation=msg)

View File

@ -2339,6 +2339,43 @@ class API(base.Base):
self.compute_rpcapi.trigger_crash_dump(context, instance)
def _generate_minimal_construct_for_down_cells(self, context,
down_cell_uuids,
project, limit):
"""Generate a list of minimal instance constructs for a given list of
cells that did not respond to a list operation. This will list
every instance mapping in the affected cells and return a minimal
objects.Instance for each (non-queued-for-delete) mapping.
:param context: RequestContext
:param down_cell_uuids: A list of cell UUIDs that did not respond
:param project: A project ID to filter mappings
:param limit: A numeric limit on the number of results, or None
:returns: An InstanceList() of partial Instance() objects
"""
unavailable_servers = objects.InstanceList()
for cell_uuid in down_cell_uuids:
LOG.warning("Cell %s is not responding and hence only "
"partial results are available from this "
"cell if any.", cell_uuid)
instance_mappings = (objects.InstanceMappingList.
get_not_deleted_by_cell_and_project(context, cell_uuid,
project, limit=limit))
for im in instance_mappings:
unavailable_servers.objects.append(
objects.Instance(
context=context,
uuid=im.instance_uuid,
project_id=im.project_id,
created_at=im.created_at
)
)
if limit is not None:
limit -= len(instance_mappings)
if limit <= 0:
break
return unavailable_servers
def _get_instance_map_or_none(self, context, instance_uuid):
try:
inst_map = objects.InstanceMapping.get_by_instance_uuid(
@ -2435,7 +2472,8 @@ class API(base.Base):
return instance
def get_all(self, context, search_opts=None, limit=None, marker=None,
expected_attrs=None, sort_keys=None, sort_dirs=None):
expected_attrs=None, sort_keys=None, sort_dirs=None,
cell_down_support=False):
"""Get all instances filtered by one of the given parameters.
If there is no filter and the context is an admin, it will retrieve
@ -2449,6 +2487,13 @@ class API(base.Base):
secondary sort ket, etc.). For each sort key, the associated sort
direction is based on the list of sort directions in the 'sort_dirs'
parameter.
:param cell_down_support: True if the API (and caller) support
returning a minimal instance
construct if the relevant cell is
down. If False, instances from
unreachable cells will be omitted.
"""
if search_opts is None:
search_opts = {}
@ -2577,7 +2622,7 @@ class API(base.Base):
context, filters, limit, marker, fields, sort_keys,
sort_dirs)
else:
insts = instance_list.get_instance_objects_sorted(
insts, down_cell_uuids = instance_list.get_instance_objects_sorted(
context, filters, limit, marker, fields, sort_keys, sort_dirs)
def _get_unique_filter_method():
@ -2604,6 +2649,15 @@ class API(base.Base):
if filter_ip:
instances = self._ip_filter(instances, filters, orig_limit)
if cell_down_support:
# API and client want minimal construct instances for any cells
# that didn't return, so generate and prefix those to the actual
# results.
project = search_opts.get('project_id', context.project_id)
limit = (orig_limit - len(instances)) if limit else limit
return (self._generate_minimal_construct_for_down_cells(context,
down_cell_uuids, project, limit) + instances)
return instances
def _do_old_style_instance_list_for_poor_cellsv1_users(self,

View File

@ -92,10 +92,12 @@ class InstanceLister(multi_cell_list.CrossCellLister):
def get_instances_sorted(ctx, filters, limit, marker, columns_to_join,
sort_keys, sort_dirs, cell_mappings=None,
batch_size=None):
return InstanceLister(sort_keys, sort_dirs,
cells=cell_mappings,
batch_size=batch_size).get_records_sorted(
instance_lister = InstanceLister(sort_keys, sort_dirs,
cells=cell_mappings,
batch_size=batch_size)
instance_generator = instance_lister.get_records_sorted(
ctx, filters, limit, marker, columns_to_join=columns_to_join)
return instance_lister, instance_generator
def get_instance_list_cells_batch_size(limit, cells):
@ -131,7 +133,15 @@ def get_instance_list_cells_batch_size(limit, cells):
def get_instance_objects_sorted(ctx, filters, limit, marker, expected_attrs,
sort_keys, sort_dirs):
"""Same as above, but return an InstanceList."""
"""Return a list of instances and information about down cells.
This returns a tuple of (objects.InstanceList, list(of down cell
uuids) for the requested operation. The instances returned are
those that were collected from the cells that responded. The uuids
of any cells that did not respond (or raised an error) are included
in the list as the second element of the tuple. That list is empty
if all cells responded.
"""
query_cell_subset = CONF.api.instance_list_per_project_cells
# NOTE(danms): Replicated in part from instance_get_all_by_sort_filters(),
# where if we're not admin we're restricted to our context's project
@ -149,17 +159,18 @@ def get_instance_objects_sorted(ctx, filters, limit, marker, expected_attrs,
batch_size = get_instance_list_cells_batch_size(limit, cell_mappings)
columns_to_join = instance_obj._expected_cols(expected_attrs)
instance_generator = get_instances_sorted(ctx, filters, limit, marker,
columns_to_join, sort_keys,
sort_dirs,
cell_mappings=cell_mappings,
batch_size=batch_size)
instance_lister, instance_generator = get_instances_sorted(ctx, filters,
limit, marker, columns_to_join, sort_keys, sort_dirs,
cell_mappings=cell_mappings, batch_size=batch_size)
if 'fault' in expected_attrs:
# We join fault above, so we need to make sure we don't ask
# make_instance_list to do it again for us
expected_attrs = copy.copy(expected_attrs)
expected_attrs.remove('fault')
return instance_obj._make_instance_list(ctx, objects.InstanceList(),
instance_generator,
expected_attrs)
instance_list = instance_obj._make_instance_list(ctx,
objects.InstanceList(), instance_generator, expected_attrs)
down_cell_uuids = (instance_lister.cells_failed +
instance_lister.cells_timed_out)
return instance_list, down_cell_uuids

View File

@ -80,9 +80,9 @@ class InstanceListTestCase(test.TestCase):
columns = []
sort_keys = ['uuid']
sort_dirs = ['asc']
insts = instance_list.get_instances_sorted(self.context, filters,
limit, marker, columns,
sort_keys, sort_dirs)
obj, insts = instance_list.get_instances_sorted(self.context, filters,
limit, marker, columns,
sort_keys, sort_dirs)
uuids = [inst['uuid'] for inst in insts]
self.assertEqual(sorted(uuids), uuids)
self.assertEqual(len(self.instances), len(uuids))
@ -94,9 +94,9 @@ class InstanceListTestCase(test.TestCase):
columns = []
sort_keys = ['uuid']
sort_dirs = ['desc']
insts = instance_list.get_instances_sorted(self.context, filters,
limit, marker, columns,
sort_keys, sort_dirs)
obj, insts = instance_list.get_instances_sorted(self.context, filters,
limit, marker, columns,
sort_keys, sort_dirs)
uuids = [inst['uuid'] for inst in insts]
self.assertEqual(list(reversed(sorted(uuids))), uuids)
self.assertEqual(len(self.instances), len(uuids))
@ -108,9 +108,9 @@ class InstanceListTestCase(test.TestCase):
columns = []
sort_keys = ['uuid']
sort_dirs = ['asc']
insts = instance_list.get_instances_sorted(self.context, filters,
limit, marker, columns,
sort_keys, sort_dirs)
obj, insts = instance_list.get_instances_sorted(self.context, filters,
limit, marker, columns,
sort_keys, sort_dirs)
uuids = [inst['uuid'] for inst in insts]
expected = [inst['uuid'] for inst in self.instances
if inst['instance_type_id'] == 1]
@ -123,35 +123,35 @@ class InstanceListTestCase(test.TestCase):
columns = []
sort_keys = None
sort_dirs = None
insts = instance_list.get_instances_sorted(self.context, filters,
limit, marker, columns,
sort_keys, sort_dirs)
obj, insts = instance_list.get_instances_sorted(self.context, filters,
limit, marker, columns,
sort_keys, sort_dirs)
uuids = set([inst['uuid'] for inst in insts])
expected = set([inst['uuid'] for inst in self.instances])
self.assertEqual(expected, uuids)
def test_get_sorted_with_limit(self):
insts = instance_list.get_instances_sorted(self.context, {},
5, None,
[], ['uuid'], ['asc'])
obj, insts = instance_list.get_instances_sorted(self.context, {},
5, None,
[], ['uuid'], ['asc'])
uuids = [inst['uuid'] for inst in insts]
had_uuids = [inst.uuid for inst in self.instances]
self.assertEqual(sorted(had_uuids)[:5], uuids)
self.assertEqual(5, len(uuids))
def test_get_sorted_with_large_limit(self):
insts = instance_list.get_instances_sorted(self.context, {},
5000, None,
[], ['uuid'], ['asc'])
obj, insts = instance_list.get_instances_sorted(self.context, {},
5000, None,
[], ['uuid'], ['asc'])
uuids = [inst['uuid'] for inst in insts]
self.assertEqual(sorted(uuids), uuids)
self.assertEqual(len(self.instances), len(uuids))
def test_get_sorted_with_large_limit_batched(self):
insts = instance_list.get_instances_sorted(self.context, {},
5000, None,
[], ['uuid'], ['asc'],
batch_size=2)
obj, insts = instance_list.get_instances_sorted(self.context, {},
5000, None,
[], ['uuid'], ['asc'],
batch_size=2)
uuids = [inst['uuid'] for inst in insts]
self.assertEqual(sorted(uuids), uuids)
self.assertEqual(len(self.instances), len(uuids))
@ -191,7 +191,8 @@ class InstanceListTestCase(test.TestCase):
batch = list(
instance_list.get_instances_sorted(self.context, {},
limit, marker,
[], [sort_by], [sort_dir]))
[], [sort_by],
[sort_dir])[1])
if not batch:
# This should only happen when we've pulled the last empty
# page because we used the marker of the last instance. If
@ -295,14 +296,14 @@ class InstanceListTestCase(test.TestCase):
before = list(
instance_list.get_instances_sorted(self.context, {},
None, marker,
[], None, None))
[], None, None)[1])
db.instance_destroy(self.context, marker)
after = list(
instance_list.get_instances_sorted(self.context, {},
None, marker,
[], None, None))
[], None, None)[1])
self.assertEqual(before, after)
@ -310,7 +311,7 @@ class InstanceListTestCase(test.TestCase):
self.assertRaises(exception.MarkerNotFound,
list, instance_list.get_instances_sorted(
self.context, {}, None, 'not-a-marker',
[], None, None))
[], None, None)[1])
def test_get_sorted_with_purged_instance(self):
"""Test that we handle a mapped but purged instance."""
@ -323,7 +324,7 @@ class InstanceListTestCase(test.TestCase):
self.assertRaises(exception.MarkerNotFound,
list, instance_list.get_instances_sorted(
self.context, {}, None, uuids.missing,
[], None, None))
[], None, None)[1])
def _test_get_paginated_with_filter(self, filters):
@ -337,7 +338,7 @@ class InstanceListTestCase(test.TestCase):
filters,
1, marker, [],
['hostname'],
['asc']))
['asc'])[1])
if not batch:
break
found_uuids.extend([x['uuid'] for x in batch])
@ -401,7 +402,7 @@ class InstanceListTestCase(test.TestCase):
instance_list.get_instances_sorted(self.context, {},
None, None,
['fault'],
['hostname'], ['asc']))
['hostname'], ['asc'])[1])
# Two of the instances in each cell have faults (0th and 2nd)
expected_faults = self.NUMBER_OF_CELLS * 2
@ -425,7 +426,7 @@ class InstanceListTestCase(test.TestCase):
instance_list.get_instances_sorted(self.context, {},
1, marker,
['fault'],
['hostname'], ['asc']))
['hostname'], ['asc'])[1])
if not batch:
break
insts.extend(batch)
@ -448,7 +449,8 @@ class InstanceListTestCase(test.TestCase):
instance_list.get_instances_sorted(self.context, {},
None, None, [],
['uuid'], ['asc'],
cell_mappings=self.cells[:-1]))
cell_mappings=self.cells[:-1])
[1])
found_uuids = [inst['hostname'] for inst in instances]
had_uuids = [inst['hostname'] for inst in self.instances
if inst['uuid'] not in last_cell_uuids]
@ -511,7 +513,7 @@ class TestInstanceListObjects(test.TestCase):
expected_attrs = []
sort_keys = ['uuid']
sort_dirs = ['asc']
insts = instance_list.get_instance_objects_sorted(
insts, down_cell_uuids = instance_list.get_instance_objects_sorted(
self.context, filters, limit, marker, expected_attrs,
sort_keys, sort_dirs)
found_uuids = [x.uuid for x in insts]
@ -529,7 +531,7 @@ class TestInstanceListObjects(test.TestCase):
expected_attrs = ['fault']
sort_keys = ['uuid']
sort_dirs = ['asc']
insts = instance_list.get_instance_objects_sorted(
insts, down_cell_uuids = instance_list.get_instance_objects_sorted(
self.context, filters, limit, marker, expected_attrs,
sort_keys, sort_dirs)
found_uuids = [x.uuid for x in insts]
@ -549,11 +551,11 @@ class TestInstanceListObjects(test.TestCase):
have a stable ordering, even when we only claim to care about
created_at.
"""
instp1 = instance_list.get_instance_objects_sorted(
instp1, down_cell_uuids = instance_list.get_instance_objects_sorted(
self.context, {}, None, None, [],
['created_at'], ['asc'])
self.assertEqual(len(self.instances), len(instp1))
instp2 = instance_list.get_instance_objects_sorted(
instp2, down_cell_uuids = instance_list.get_instance_objects_sorted(
self.context, {}, None, instp1[-1]['uuid'], [],
['created_at'], ['asc'])
self.assertEqual(0, len(instp2))

View File

@ -699,7 +699,8 @@ class ServersControllerTest(ControllerTest):
self.mock_get_all.assert_called_once_with(
req.environ['nova.context'], expected_attrs=[], limit=1000,
marker=None, search_opts={'deleted': False, 'project_id': 'fake'},
sort_dirs=['desc'], sort_keys=['created_at'])
sort_dirs=['desc'], sort_keys=['created_at'],
cell_down_support=False)
def test_get_server_list_with_reservation_id(self):
req = self.req('/fake/servers?reservation_id=foo')
@ -791,7 +792,8 @@ class ServersControllerTest(ControllerTest):
expected_attrs=expected_attrs,
limit=1000, marker=None,
search_opts={'deleted': False, 'project_id': 'fake'},
sort_dirs=['desc'], sort_keys=['created_at'])
sort_dirs=['desc'], sort_keys=['created_at'],
cell_down_support=False)
def test_get_server_details_with_bad_name(self):
req = self.req('/fake/servers/detail?name=%2Binstance')
@ -909,7 +911,8 @@ class ServersControllerTest(ControllerTest):
self.controller.index(req)
self.mock_get_all.assert_called_once_with(
mock.ANY, search_opts=mock.ANY, limit=mock.ANY, marker=mock.ANY,
expected_attrs=mock.ANY, sort_keys=[], sort_dirs=[])
expected_attrs=mock.ANY, sort_keys=[], sort_dirs=[],
cell_down_support=False)
def test_get_servers_ignore_sort_key_only_one_dir(self):
req = self.req(
@ -918,21 +921,23 @@ class ServersControllerTest(ControllerTest):
self.mock_get_all.assert_called_once_with(
mock.ANY, search_opts=mock.ANY, limit=mock.ANY, marker=mock.ANY,
expected_attrs=mock.ANY, sort_keys=['user_id'],
sort_dirs=['asc'])
sort_dirs=['asc'], cell_down_support=False)
def test_get_servers_ignore_sort_key_with_no_sort_dir(self):
req = self.req('/fake/servers?sort_key=vcpus&sort_key=user_id')
self.controller.index(req)
self.mock_get_all.assert_called_once_with(
mock.ANY, search_opts=mock.ANY, limit=mock.ANY, marker=mock.ANY,
expected_attrs=mock.ANY, sort_keys=['user_id'], sort_dirs=[])
expected_attrs=mock.ANY, sort_keys=['user_id'], sort_dirs=[],
cell_down_support=False)
def test_get_servers_ignore_sort_key_with_bad_sort_dir(self):
req = self.req('/fake/servers?sort_key=vcpus&sort_dir=bad_dir')
self.controller.index(req)
self.mock_get_all.assert_called_once_with(
mock.ANY, search_opts=mock.ANY, limit=mock.ANY, marker=mock.ANY,
expected_attrs=mock.ANY, sort_keys=[], sort_dirs=[])
expected_attrs=mock.ANY, sort_keys=[], sort_dirs=[],
cell_down_support=False)
def test_get_servers_non_admin_with_admin_only_sort_key(self):
req = self.req('/fake/servers?sort_key=host&sort_dir=desc')
@ -945,12 +950,14 @@ class ServersControllerTest(ControllerTest):
self.controller.detail(req)
self.mock_get_all.assert_called_once_with(
mock.ANY, search_opts=mock.ANY, limit=mock.ANY, marker=mock.ANY,
expected_attrs=mock.ANY, sort_keys=['node'], sort_dirs=['desc'])
expected_attrs=mock.ANY, sort_keys=['node'], sort_dirs=['desc'],
cell_down_support=False)
def test_get_servers_with_bad_option(self):
def fake_get_all(context, search_opts=None,
limit=None, marker=None,
expected_attrs=None, sort_keys=None, sort_dirs=None):
expected_attrs=None, sort_keys=None, sort_dirs=None,
cell_down_support=False):
db_list = [fakes.stub_instance(100, uuid=uuids.fake)]
return instance_obj._make_instance_list(
context, objects.InstanceList(), db_list, FIELDS)
@ -966,12 +973,14 @@ class ServersControllerTest(ControllerTest):
req.environ['nova.context'], expected_attrs=[],
limit=1000, marker=None,
search_opts={'deleted': False, 'project_id': 'fake'},
sort_dirs=['desc'], sort_keys=['created_at'])
sort_dirs=['desc'], sort_keys=['created_at'],
cell_down_support=False)
def test_get_servers_allows_image(self):
def fake_get_all(context, search_opts=None,
limit=None, marker=None,
expected_attrs=None, sort_keys=None, sort_dirs=None):
expected_attrs=None, sort_keys=None, sort_dirs=None,
cell_down_support=False):
self.assertIsNotNone(search_opts)
self.assertIn('image', search_opts)
self.assertEqual(search_opts['image'], '12345')
@ -1124,7 +1133,8 @@ class ServersControllerTest(ControllerTest):
def test_get_servers_allows_flavor(self):
def fake_get_all(context, search_opts=None,
limit=None, marker=None,
expected_attrs=None, sort_keys=None, sort_dirs=None):
expected_attrs=None, sort_keys=None, sort_dirs=None,
cell_down_support=False):
self.assertIsNotNone(search_opts)
self.assertIn('flavor', search_opts)
# flavor is an integer ID
@ -1159,7 +1169,8 @@ class ServersControllerTest(ControllerTest):
def test_get_servers_allows_status(self):
def fake_get_all(context, search_opts=None,
limit=None, marker=None,
expected_attrs=None, sort_keys=None, sort_dirs=None):
expected_attrs=None, sort_keys=None, sort_dirs=None,
cell_down_support=False):
self.assertIsNotNone(search_opts)
self.assertIn('vm_state', search_opts)
self.assertEqual(search_opts['vm_state'], [vm_states.ACTIVE])
@ -1177,7 +1188,8 @@ class ServersControllerTest(ControllerTest):
def test_get_servers_allows_task_status(self):
def fake_get_all(context, search_opts=None,
limit=None, marker=None,
expected_attrs=None, sort_keys=None, sort_dirs=None):
expected_attrs=None, sort_keys=None, sort_dirs=None,
cell_down_support=False):
self.assertIsNotNone(search_opts)
self.assertIn('task_state', search_opts)
self.assertEqual([task_states.REBOOT_PENDING,
@ -1200,7 +1212,8 @@ class ServersControllerTest(ControllerTest):
# Test when resize status, it maps list of vm states.
def fake_get_all(context, search_opts=None,
limit=None, marker=None,
expected_attrs=None, sort_keys=None, sort_dirs=None):
expected_attrs=None, sort_keys=None, sort_dirs=None,
cell_down_support=False):
self.assertIn('vm_state', search_opts)
self.assertEqual(search_opts['vm_state'],
[vm_states.ACTIVE, vm_states.STOPPED])
@ -1232,7 +1245,8 @@ class ServersControllerTest(ControllerTest):
def test_get_servers_deleted_status_as_admin(self):
def fake_get_all(context, search_opts=None,
limit=None, marker=None,
expected_attrs=None, sort_keys=None, sort_dirs=None):
expected_attrs=None, sort_keys=None, sort_dirs=None,
cell_down_support=False):
self.assertIn('vm_state', search_opts)
self.assertEqual(search_opts['vm_state'], ['deleted'])
@ -1290,7 +1304,8 @@ class ServersControllerTest(ControllerTest):
def test_get_servers_allows_name(self):
def fake_get_all(context, search_opts=None,
limit=None, marker=None,
expected_attrs=None, sort_keys=None, sort_dirs=None):
expected_attrs=None, sort_keys=None, sort_dirs=None,
cell_down_support=False):
self.assertIsNotNone(search_opts)
self.assertIn('name', search_opts)
self.assertEqual(search_opts['name'], 'whee.*')
@ -1317,7 +1332,8 @@ class ServersControllerTest(ControllerTest):
def test_get_servers_allows_changes_since(self):
def fake_get_all(context, search_opts=None,
limit=None, marker=None,
expected_attrs=None, sort_keys=None, sort_dirs=None):
expected_attrs=None, sort_keys=None, sort_dirs=None,
cell_down_support=False):
self.assertIsNotNone(search_opts)
self.assertIn('changes-since', search_opts)
changes_since = datetime.datetime(2011, 1, 24, 17, 8, 1,
@ -1356,7 +1372,8 @@ class ServersControllerTest(ControllerTest):
"""
def fake_get_all(context, search_opts=None,
limit=None, marker=None,
expected_attrs=None, sort_keys=None, sort_dirs=None):
expected_attrs=None, sort_keys=None, sort_dirs=None,
cell_down_support=False):
self.assertIsNotNone(search_opts)
# Allowed by user
self.assertIn('name', search_opts)
@ -1384,7 +1401,8 @@ class ServersControllerTest(ControllerTest):
"""
def fake_get_all(context, search_opts=None,
limit=None, marker=None,
expected_attrs=None, sort_keys=None, sort_dirs=None):
expected_attrs=None, sort_keys=None, sort_dirs=None,
cell_down_support=False):
self.assertIsNotNone(search_opts)
# Allowed by user
self.assertIn('name', search_opts)
@ -1416,7 +1434,8 @@ class ServersControllerTest(ControllerTest):
def fake_get_all(context, search_opts=None,
limit=None, marker=None,
expected_attrs=None, sort_keys=None, sort_dirs=None):
expected_attrs=None, sort_keys=None, sort_dirs=None,
cell_down_support=False):
self.assertIsNotNone(search_opts)
# Allowed by user
self.assertIn('name', search_opts)
@ -1449,7 +1468,8 @@ class ServersControllerTest(ControllerTest):
"""Test getting servers by ip."""
def fake_get_all(context, search_opts=None,
limit=None, marker=None,
expected_attrs=None, sort_keys=None, sort_dirs=None):
expected_attrs=None, sort_keys=None, sort_dirs=None,
cell_down_support=False):
self.assertIsNotNone(search_opts)
self.assertIn('ip', search_opts)
self.assertEqual(search_opts['ip'], '10\..*')
@ -1470,7 +1490,8 @@ class ServersControllerTest(ControllerTest):
"""
def fake_get_all(context, search_opts=None,
limit=None, marker=None,
expected_attrs=None, sort_keys=None, sort_dirs=None):
expected_attrs=None, sort_keys=None, sort_dirs=None,
cell_down_support=False):
self.assertIsNotNone(search_opts)
self.assertIn('ip6', search_opts)
self.assertEqual(search_opts['ip6'], 'ffff.*')
@ -1492,7 +1513,8 @@ class ServersControllerTest(ControllerTest):
"""
def fake_get_all(context, search_opts=None,
limit=None, marker=None,
expected_attrs=None, sort_keys=None, sort_dirs=None):
expected_attrs=None, sort_keys=None, sort_dirs=None,
cell_down_support=False):
self.assertIsNotNone(search_opts)
self.assertIn('ip6', search_opts)
self.assertEqual(search_opts['ip6'], 'ffff.*')
@ -1514,7 +1536,8 @@ class ServersControllerTest(ControllerTest):
"""
def fake_get_all(context, search_opts=None,
limit=None, marker=None,
expected_attrs=None, sort_keys=None, sort_dirs=None):
expected_attrs=None, sort_keys=None, sort_dirs=None,
cell_down_support=False):
self.assertIsNotNone(search_opts)
self.assertIn('access_ip_v4', search_opts)
self.assertEqual(search_opts['access_ip_v4'], 'ffff.*')
@ -1536,7 +1559,8 @@ class ServersControllerTest(ControllerTest):
"""
def fake_get_all(context, search_opts=None,
limit=None, marker=None,
expected_attrs=None, sort_keys=None, sort_dirs=None):
expected_attrs=None, sort_keys=None, sort_dirs=None,
cell_down_support=False):
self.assertIsNotNone(search_opts)
self.assertIn('access_ip_v6', search_opts)
self.assertEqual(search_opts['access_ip_v6'], 'ffff.*')
@ -1674,7 +1698,8 @@ class ServersControllerTest(ControllerTest):
def test_get_servers_joins_services(self):
def fake_get_all(context, search_opts=None,
limit=None, marker=None,
expected_attrs=None, sort_keys=None, sort_dirs=None):
expected_attrs=None, sort_keys=None, sort_dirs=None,
cell_down_support=False):
cur = api_version_request.APIVersionRequest(self.wsgi_api_version)
v216 = api_version_request.APIVersionRequest('2.16')
if cur >= v216:
@ -2397,7 +2422,8 @@ class ServerControllerTestV266(ControllerTest):
def test_get_servers_allows_changes_before(self):
def fake_get_all(context, search_opts=None,
limit=None, marker=None,
expected_attrs=None, sort_keys=None, sort_dirs=None):
expected_attrs=None, sort_keys=None, sort_dirs=None,
cell_down_support=False):
self.assertIsNotNone(search_opts)
self.assertIn('changes-before', search_opts)
changes_before = datetime.datetime(2011, 1, 24, 17, 8, 1,
@ -2434,7 +2460,8 @@ class ServerControllerTestV266(ControllerTest):
def test_get_servers_allows_changes_since_and_changes_before(self):
def fake_get_all(context, search_opts=None,
limit=None, marker=None,
expected_attrs=None, sort_keys=None, sort_dirs=None):
expected_attrs=None, sort_keys=None, sort_dirs=None,
cell_down_support=False):
self.assertIsNotNone(search_opts)
self.assertIn('changes-since', search_opts)
changes_since = datetime.datetime(2011, 1, 23, 17, 8, 1,

View File

@ -418,7 +418,7 @@ def fake_instance_get_all_by_filters(num_servers=5, **kwargs):
def fake_compute_get_all(num_servers=5, **kwargs):
def _return_servers_objs(context, search_opts=None, limit=None,
marker=None, expected_attrs=None, sort_keys=None,
sort_dirs=None):
sort_dirs=None, cell_down_support=False):
db_insts = fake_instance_get_all_by_filters()(None,
limit=limit,
marker=marker)

View File

@ -11573,7 +11573,7 @@ class ComputeAPIIpFilterTestCase(test.NoDBTestCase):
# Limit is not supplied to the DB when using an IP filter
with mock.patch('nova.compute.instance_list.'
'get_instance_objects_sorted') as m_get:
m_get.return_value = objects.InstanceList(objects=[])
m_get.return_value = objects.InstanceList(objects=[]), list()
self.compute_api.get_all(c, search_opts={'ip': '.10'}, limit=1)
self.assertEqual(1, m_get.call_count)
args = m_get.call_args[0]
@ -11588,7 +11588,7 @@ class ComputeAPIIpFilterTestCase(test.NoDBTestCase):
# No IP filter, verify that the limit is passed
with mock.patch('nova.compute.instance_list.'
'get_instance_objects_sorted') as m_get:
m_get.return_value = objects.InstanceList(objects=[])
m_get.return_value = objects.InstanceList(objects=[]), list()
self.compute_api.get_all(c, search_opts={}, limit=1)
self.assertEqual(1, m_get.call_count)
args = m_get.call_args[0]

View File

@ -5350,7 +5350,7 @@ class _ComputeAPIUnitTestMixIn(object):
mock_buildreq_get):
mock_cell_map_get.side_effect = exception.CellMappingNotFound(
uuid='fake')
mock_get.return_value = objects.InstanceList(objects=[])
mock_get.return_value = objects.InstanceList(objects=[]), list()
api = compute_api.API()
api.get_all(self.context, search_opts={'tenant_id': 'foo'})
filters = mock_get.call_args_list[0][0][1]
@ -5635,6 +5635,107 @@ class _ComputeAPIUnitTestMixIn(object):
mock_get.assert_called_once_with(self.context, inst.uuid)
mock_save.assert_called_once_with()
@mock.patch.object(objects.InstanceMappingList,
'get_not_deleted_by_cell_and_project')
def test_generate_minimal_construct_for_down_cells(self, mock_get_ims):
im1 = objects.InstanceMapping(instance_uuid=uuids.inst1, cell_id=1,
project_id='fake', created_at=None, queued_for_delete=False)
mock_get_ims.return_value = [im1]
down_cell_uuids = [uuids.cell1, uuids.cell2, uuids.cell3]
result = self.compute_api._generate_minimal_construct_for_down_cells(
self.context, down_cell_uuids, [self.context.project_id], None)
for inst in result:
self.assertEqual(inst.uuid, im1.instance_uuid)
self.assertIn('created_at', inst)
# minimal construct doesn't contain the usual keys
self.assertNotIn('display_name', inst)
self.assertEqual(3, mock_get_ims.call_count)
@mock.patch.object(objects.InstanceMappingList,
'get_not_deleted_by_cell_and_project')
def test_generate_minimal_construct_for_down_cells_limited(self,
mock_get_ims):
im1 = objects.InstanceMapping(instance_uuid=uuids.inst1, cell_id=1,
project_id='fake', created_at=None, queued_for_delete=False)
# If this gets called a third time, it'll explode, thus asserting
# that we break out of the loop once the limit is reached
mock_get_ims.side_effect = [[im1, im1], [im1]]
down_cell_uuids = [uuids.cell1, uuids.cell2, uuids.cell3]
result = self.compute_api._generate_minimal_construct_for_down_cells(
self.context, down_cell_uuids, [self.context.project_id], 3)
for inst in result:
self.assertEqual(inst.uuid, im1.instance_uuid)
self.assertIn('created_at', inst)
# minimal construct doesn't contain the usual keys
self.assertNotIn('display_name', inst)
# Two instances at limit 3 from first cell, one at limit 1 from the
# second, no third call.
self.assertEqual(2, mock_get_ims.call_count)
mock_get_ims.assert_has_calls([
mock.call(self.context, uuids.cell1, [self.context.project_id],
limit=3),
mock.call(self.context, uuids.cell2, [self.context.project_id],
limit=1),
])
@mock.patch.object(objects.BuildRequestList, 'get_by_filters')
@mock.patch.object(objects.InstanceMappingList,
'get_not_deleted_by_cell_and_project')
def test_get_all_without_cell_down_support(self, mock_get_ims,
mock_buildreq_get):
mock_buildreq_get.return_value = objects.BuildRequestList()
im1 = objects.InstanceMapping(instance_uuid=uuids.inst1, cell_id=1,
project_id='fake', created_at=None, queued_for_delete=False)
mock_get_ims.return_value = [im1]
cell_instances = self._list_of_instances(2)
with mock.patch('nova.compute.instance_list.'
'get_instance_objects_sorted') as mock_inst_get:
mock_inst_get.return_value = objects.InstanceList(
self.context, objects=cell_instances), [uuids.cell1]
insts = self.compute_api.get_all(self.context,
cell_down_support=False)
fields = ['metadata', 'info_cache', 'security_groups']
mock_inst_get.assert_called_once_with(self.context, {}, None, None,
fields, None, None)
for i, instance in enumerate(cell_instances):
self.assertEqual(instance, insts[i])
mock_get_ims.assert_not_called()
@mock.patch.object(objects.BuildRequestList, 'get_by_filters')
@mock.patch.object(objects.InstanceMappingList,
'get_not_deleted_by_cell_and_project')
def test_get_all_with_cell_down_support(self, mock_get_ims,
mock_buildreq_get):
mock_buildreq_get.return_value = objects.BuildRequestList()
im = objects.InstanceMapping(context=self.context,
instance_uuid=uuids.inst1, cell_id=1,
project_id='fake', created_at=None, queued_for_delete=False)
mock_get_ims.return_value = [im]
cell_instances = self._list_of_instances(2)
full_instances = objects.InstanceList(self.context,
objects=cell_instances)
inst = objects.Instance(context=self.context, uuid=im.instance_uuid,
project_id=im.project_id, created_at=im.created_at)
partial_instances = objects.InstanceList(self.context, objects=[inst])
with mock.patch('nova.compute.instance_list.'
'get_instance_objects_sorted') as mock_inst_get:
mock_inst_get.return_value = objects.InstanceList(
self.context, objects=cell_instances), [uuids.cell1]
insts = self.compute_api.get_all(self.context, limit=3,
cell_down_support=True)
fields = ['metadata', 'info_cache', 'security_groups']
mock_inst_get.assert_called_once_with(self.context, {},
3, None, fields, None,
None)
for i, instance in enumerate(partial_instances + full_instances):
self.assertTrue(obj_base.obj_equal_prims(instance, insts[i]))
# With an original limit of 3, and 0 build requests but 2 instances
# from "up" cells, we should only get at most 1 instance mapping
# to fill the limit.
mock_get_ims.assert_called_once_with(self.context, uuids.cell1,
self.context.project_id,
limit=1)
@mock.patch.object(objects.InstanceMapping, 'get_by_instance_uuid',
side_effect=exception.InstanceMappingNotFound(uuid='fake'))
@mock.patch.object(objects.BuildRequest, 'get_by_instance_uuid')
@ -5835,7 +5936,7 @@ class _ComputeAPIUnitTestMixIn(object):
with mock.patch('nova.compute.instance_list.'
'get_instance_objects_sorted') as mock_inst_get:
mock_inst_get.return_value = objects.InstanceList(
self.context, objects=cell_instances)
self.context, objects=cell_instances), list()
instances = self.compute_api.get_all(
self.context, search_opts={'foo': 'bar'},
@ -5869,8 +5970,8 @@ class _ComputeAPIUnitTestMixIn(object):
with mock.patch('nova.compute.instance_list.'
'get_instance_objects_sorted') as mock_inst_get:
# Insert one of the build_req_instances here so it shows up twice
mock_inst_get.return_value = objects.InstanceList(
self.context, objects=build_req_instances[:1] + cell_instances)
mock_inst_get.return_value = objects.InstanceList(self.context,
objects=build_req_instances[:1] + cell_instances), list()
instances = self.compute_api.get_all(
self.context, search_opts={'foo': 'bar'},
@ -5905,7 +6006,7 @@ class _ComputeAPIUnitTestMixIn(object):
with mock.patch('nova.compute.instance_list.'
'get_instance_objects_sorted') as mock_inst_get:
mock_inst_get.return_value = objects.InstanceList(
self.context, objects=cell_instances)
self.context, objects=cell_instances), list()
instances = self.compute_api.get_all(
self.context, search_opts={'foo': 'bar'},
@ -5940,9 +6041,9 @@ class _ComputeAPIUnitTestMixIn(object):
with mock.patch('nova.compute.instance_list.'
'get_instance_objects_sorted') as mock_inst_get:
mock_inst_get.side_effect = [objects.InstanceList(
mock_inst_get.return_value = objects.InstanceList(
self.context,
objects=cell_instances)]
objects=cell_instances), []
instances = self.compute_api.get_all(
self.context, search_opts={'foo': 'bar'},
@ -6378,7 +6479,7 @@ class ComputeAPIUnitTestCase(_ComputeAPIUnitTestMixIn, test.NoDBTestCase):
with mock.patch('nova.compute.instance_list.'
'get_instance_objects_sorted') as mock_inst_get:
mock_inst_get.return_value = objects.InstanceList(
self.context, objects=cell_instances)
self.context, objects=cell_instances), list()
self.compute_api.get_all(
self.context, search_opts={'ip': 'fake'},
@ -6406,7 +6507,7 @@ class ComputeAPIUnitTestCase(_ComputeAPIUnitTestMixIn, test.NoDBTestCase):
with mock.patch('nova.compute.instance_list.'
'get_instance_objects_sorted') as mock_inst_get:
mock_inst_get.return_value = objects.InstanceList(
self.context, objects=cell_instances)
self.context, objects=cell_instances), list()
self.compute_api.get_all(
self.context, search_opts={'ip6': 'fake'},
@ -6435,7 +6536,7 @@ class ComputeAPIUnitTestCase(_ComputeAPIUnitTestMixIn, test.NoDBTestCase):
with mock.patch('nova.compute.instance_list.'
'get_instance_objects_sorted') as mock_inst_get:
mock_inst_get.return_value = objects.InstanceList(
self.context, objects=cell_instances)
self.context, objects=cell_instances), list()
self.compute_api.get_all(
self.context, search_opts={'ip': 'fake1', 'ip6': 'fake2'},
@ -6766,6 +6867,12 @@ class Cellsv1DeprecatedTestMixIn(object):
for i, instance in enumerate(cell_instances):
self.assertEqual(instance, instances[i])
def test_get_all_with_cell_down_support(self):
self.skipTest("Cell down handling is not supported for cells_v1.")
def test_get_all_without_cell_down_support(self):
self.skipTest("Cell down handling is not supported for cells_v1.")
class ComputeAPIAPICellUnitTestCase(Cellsv1DeprecatedTestMixIn,
_ComputeAPIUnitTestMixIn,

View File

@ -69,9 +69,9 @@ class TestInstanceList(test.NoDBTestCase):
insts_by_cell = self.insts.values()
mock_inst.side_effect = insts_by_cell
insts = instance_list.get_instances_sorted(self.context, {},
None, None,
[], ['hostname'], ['asc'])
obj, insts = instance_list.get_instances_sorted(self.context, {},
None, None, [],
['hostname'], ['asc'])
insts_one = [inst['hostname'] for inst in insts]
# Reverse the order that we get things from the cells so we can
@ -80,9 +80,9 @@ class TestInstanceList(test.NoDBTestCase):
mock_inst.reset_mock()
mock_inst.side_effect = insts_by_cell
insts = instance_list.get_instances_sorted(self.context, {},
None, None,
[], ['hostname'], ['asc'])
obj, insts = instance_list.get_instances_sorted(self.context, {},
None, None, [],
['hostname'], ['asc'])
insts_two = [inst['hostname'] for inst in insts]
self.assertEqual(insts_one, insts_two)
@ -92,7 +92,7 @@ class TestInstanceList(test.NoDBTestCase):
@mock.patch('nova.objects.CellMappingList.get_by_project_id')
def test_user_gets_subset_of_cells(self, mock_cm, mock_gi, mock_br):
self.flags(instance_list_per_project_cells=True, group='api')
mock_gi.return_value = []
mock_gi.return_value = instance_list.InstanceLister(None, None), []
mock_br.return_value = []
user_context = nova_context.RequestContext('fake', 'fake')
instance_list.get_instance_objects_sorted(
@ -108,7 +108,7 @@ class TestInstanceList(test.NoDBTestCase):
@mock.patch('nova.compute.instance_list.get_instances_sorted')
@mock.patch('nova.objects.CellMappingList.get_by_project_id')
def test_admin_gets_all_cells(self, mock_cm, mock_gi, mock_br, mock_lc):
mock_gi.return_value = []
mock_gi.return_value = instance_list.InstanceLister(None, None), []
mock_br.return_value = []
admin_context = nova_context.RequestContext('fake', 'fake',
is_admin=True)
@ -128,7 +128,7 @@ class TestInstanceList(test.NoDBTestCase):
@mock.patch('nova.objects.CellMappingList.get_by_project_id')
def test_user_gets_all_cells(self, mock_cm, mock_gi, mock_br, mock_lc):
self.flags(instance_list_per_project_cells=False, group='api')
mock_gi.return_value = []
mock_gi.return_value = instance_list.InstanceLister(None, None), []
mock_br.return_value = []
user_context = nova_context.RequestContext('fake', 'fake')
instance_list.get_instance_objects_sorted(
@ -147,7 +147,7 @@ class TestInstanceList(test.NoDBTestCase):
def test_admin_gets_all_cells_anyway(self, mock_cm, mock_gi, mock_br,
mock_lc):
self.flags(instance_list_per_project_cells=True, group='api')
mock_gi.return_value = []
mock_gi.return_value = instance_list.InstanceLister(None, None), []
mock_br.return_value = []
admin_context = nova_context.RequestContext('fake', 'fake',
is_admin=True)
@ -179,8 +179,8 @@ class TestInstanceList(test.NoDBTestCase):
ret_val[uuids.cell2] = [wrap(nova_context.did_not_respond_sentinel)]
mock_sg.return_value = ret_val
res = instance_list.get_instances_sorted(self.context, {}, None, None,
[], None, None)
obj, res = instance_list.get_instances_sorted(self.context, {}, None,
None, [], None, None)
uuid_final = [inst['uuid'] for inst in res]
@ -315,10 +315,10 @@ class TestInstanceListBig(test.NoDBTestCase):
yield self.insts.pop()
mock_inst.side_effect = fake_get_insts
insts = instance_list.get_instances_sorted(self.context, {},
50, None,
[], ['hostname'], ['desc'],
batch_size=10)
obj, insts = instance_list.get_instances_sorted(self.context, {},
50, None, [],
['hostname'], ['desc'],
batch_size=10)
# Make sure we returned exactly how many were requested
insts = list(insts)