Skip to content

Commit b233aed

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "Fix GET /servers/detail host_status performance regression"
2 parents 1f46faf + ab7d923 commit b233aed

File tree

4 files changed

+106
-8
lines changed

4 files changed

+106
-8
lines changed

nova/api/openstack/compute/views/servers.py

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -373,10 +373,6 @@ def detail(self, request, instances, cell_down_support=False):
373373
show_extra_specs = False
374374
show_extended_attr = context.can(
375375
esa_policies.BASE_POLICY_NAME, fatal=False)
376-
show_host_status = False
377-
if (api_version_request.is_supported(request, min_version='2.16')):
378-
show_host_status = context.can(
379-
servers_policies.SERVERS % 'show:host_status', fatal=False)
380376

381377
instance_uuids = [inst['uuid'] for inst in instances]
382378
bdms = self._get_instance_bdms_in_multiple_cells(context,
@@ -389,11 +385,17 @@ def detail(self, request, instances, cell_down_support=False):
389385
servers_dict = self._list_view(self.show, request, instances,
390386
coll_name, show_extra_specs,
391387
show_extended_attr=show_extended_attr,
392-
show_host_status=show_host_status,
388+
# We process host_status in aggregate.
389+
show_host_status=False,
393390
show_sec_grp=False,
394391
bdms=bdms,
395392
cell_down_support=cell_down_support)
396393

394+
if (api_version_request.is_supported(request, min_version='2.16') and
395+
context.can(servers_policies.SERVERS % 'show:host_status',
396+
fatal=False)):
397+
self._add_host_status(list(servers_dict["servers"]), instances)
398+
397399
self._add_security_grps(request, list(servers_dict["servers"]),
398400
instances)
399401
return servers_dict
@@ -562,6 +564,27 @@ def _get_fault(self, request, instance):
562564

563565
return fault_dict
564566

567+
def _add_host_status(self, servers, instances):
568+
"""Adds the ``host_status`` field to the list of servers
569+
570+
This method takes care to filter instances from down cells since they
571+
do not have a host set and as such we cannot determine the host status.
572+
573+
:param servers: list of detailed server dicts for the API response
574+
body; this list is modified by reference by updating the server
575+
dicts within the list
576+
:param instances: list of Instance objects
577+
"""
578+
# Filter out instances from down cells which do not have a host field.
579+
instances = [instance for instance in instances if 'host' in instance]
580+
# Get the dict, keyed by instance.uuid, of host status values.
581+
host_statuses = self.compute_api.get_instances_host_statuses(instances)
582+
for server in servers:
583+
# Filter out anything that is not in the resulting dict because
584+
# we had to filter the list of instances above for down cells.
585+
if server['id'] in host_statuses:
586+
server['host_status'] = host_statuses[server['id']]
587+
565588
def _add_security_grps(self, req, servers, instances):
566589
if not len(servers):
567590
return

nova/compute/api.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4825,6 +4825,21 @@ def get_instance_host_status(self, instance):
48254825
host_status = fields_obj.HostStatus.NONE
48264826
return host_status
48274827

4828+
def get_instances_host_statuses(self, instance_list):
4829+
host_status_dict = dict()
4830+
host_statuses = dict()
4831+
for instance in instance_list:
4832+
if instance.host:
4833+
if instance.host not in host_status_dict:
4834+
host_status = self.get_instance_host_status(instance)
4835+
host_status_dict[instance.host] = host_status
4836+
else:
4837+
host_status = host_status_dict[instance.host]
4838+
else:
4839+
host_status = fields_obj.HostStatus.NONE
4840+
host_statuses[instance.uuid] = host_status
4841+
return host_statuses
4842+
48284843

48294844
def target_host_cell(fn):
48304845
"""Target a host-based function to a cell.

nova/tests/unit/api/openstack/compute/test_serversV21.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2049,6 +2049,7 @@ def setUp(self):
20492049
super(ServersControllerTestV216, self).setUp()
20502050
self.mock_get.side_effect = fakes.fake_compute_get(
20512051
id=2, uuid=FAKE_UUID,
2052+
host="node-fake",
20522053
node="node-fake",
20532054
reservation_id="r-1", launch_index=0,
20542055
kernel_id=UUID1, ramdisk_id=UUID2,
@@ -2069,9 +2070,10 @@ def setUp(self):
20692070
task_state=None,
20702071
vm_state=vm_states.ACTIVE,
20712072
power_state=1)
2072-
self.useFixture(fixtures.MockPatchObject(
2073-
compute_api.API, 'get_instance_host_status',
2074-
return_value='UP')).mock
2073+
self.mock_get_instance_host_status = self.useFixture(
2074+
fixtures.MockPatchObject(
2075+
compute_api.API, 'get_instance_host_status',
2076+
return_value='UP')).mock
20752077

20762078
def _get_server_data_dict(self, uuid, image_bookmark, flavor_bookmark,
20772079
status="ACTIVE", progress=100):
@@ -2084,6 +2086,9 @@ def _get_server_data_dict(self, uuid, image_bookmark, flavor_bookmark,
20842086
server_dict['server']['locked'] = False
20852087
server_dict['server']["host_status"] = "UP"
20862088
server_dict['server']["OS-EXT-SRV-ATTR:hostname"] = "server2"
2089+
server_dict['server']['hostId'] = nova_utils.generate_hostid(
2090+
'node-fake', server_dict['server']['tenant_id'])
2091+
server_dict['server']["OS-EXT-SRV-ATTR:host"] = "node-fake"
20872092
server_dict['server'][
20882093
"OS-EXT-SRV-ATTR:hypervisor_hostname"] = "node-fake"
20892094
server_dict['server']["OS-EXT-SRV-ATTR:kernel_id"] = UUID1
@@ -2120,6 +2125,7 @@ def fake_get_all(context, search_opts=None,
21202125
for i in range(2):
21212126
server = fakes.stub_instance_obj(context,
21222127
id=2, uuid=FAKE_UUID,
2128+
host="node-fake",
21232129
node="node-fake",
21242130
reservation_id="r-1", launch_index=0,
21252131
kernel_id=UUID1, ramdisk_id=UUID2,
@@ -2152,6 +2158,7 @@ def fake_get_all(context, search_opts=None,
21522158

21532159
req = self.req('/fake/servers/detail')
21542160
servers_list = self.controller.detail(req)
2161+
self.assertEqual(2, len(servers_list['servers']))
21552162
image_bookmark = "http://localhost/fake/images/10"
21562163
flavor_bookmark = "http://localhost/fake/flavors/2"
21572164
expected_server = self._get_server_data_dict(FAKE_UUID,
@@ -2160,6 +2167,9 @@ def fake_get_all(context, search_opts=None,
21602167
progress=0)
21612168

21622169
self.assertIn(expected_server['server'], servers_list['servers'])
2170+
# We should have only gotten the host status once per host (and the
2171+
# 2 servers in the response are using the same host).
2172+
self.mock_get_instance_host_status.assert_called_once()
21632173

21642174

21652175
class ServersControllerTestV219(ServersControllerTest):

nova/tests/unit/compute/test_compute_api.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5028,6 +5028,56 @@ def test_populate_instance_names_host_name_is_empty_multi(self):
50285028
self.compute_api._populate_instance_names(instance, 2, 1)
50295029
self.assertEqual('Server-%s' % instance.uuid, instance.hostname)
50305030

5031+
def test_host_statuses(self):
5032+
five_min_ago = timeutils.utcnow() - datetime.timedelta(minutes=5)
5033+
instances = [
5034+
objects.Instance(uuid=uuids.instance_1, host='host1', services=
5035+
self._obj_to_list_obj(objects.ServiceList(
5036+
self.context), objects.Service(id=0, host='host1',
5037+
disabled=True, forced_down=True,
5038+
binary='nova-compute'))),
5039+
objects.Instance(uuid=uuids.instance_2, host='host2', services=
5040+
self._obj_to_list_obj(objects.ServiceList(
5041+
self.context), objects.Service(id=0, host='host2',
5042+
disabled=True, forced_down=False,
5043+
binary='nova-compute'))),
5044+
objects.Instance(uuid=uuids.instance_3, host='host3', services=
5045+
self._obj_to_list_obj(objects.ServiceList(
5046+
self.context), objects.Service(id=0, host='host3',
5047+
disabled=False, last_seen_up=five_min_ago,
5048+
forced_down=False, binary='nova-compute'))),
5049+
objects.Instance(uuid=uuids.instance_4, host='host4', services=
5050+
self._obj_to_list_obj(objects.ServiceList(
5051+
self.context), objects.Service(id=0, host='host4',
5052+
disabled=False, last_seen_up=timeutils.utcnow(),
5053+
forced_down=False, binary='nova-compute'))),
5054+
objects.Instance(uuid=uuids.instance_5, host='host5', services=
5055+
objects.ServiceList()),
5056+
objects.Instance(uuid=uuids.instance_6, host=None, services=
5057+
self._obj_to_list_obj(objects.ServiceList(
5058+
self.context), objects.Service(id=0, host='host6',
5059+
disabled=True, forced_down=False,
5060+
binary='nova-compute'))),
5061+
objects.Instance(uuid=uuids.instance_7, host='host2', services=
5062+
self._obj_to_list_obj(objects.ServiceList(
5063+
self.context), objects.Service(id=0, host='host2',
5064+
disabled=True, forced_down=False,
5065+
binary='nova-compute')))
5066+
]
5067+
5068+
host_statuses = self.compute_api.get_instances_host_statuses(
5069+
instances)
5070+
expect_statuses = {uuids.instance_1: fields_obj.HostStatus.DOWN,
5071+
uuids.instance_2: fields_obj.HostStatus.MAINTENANCE,
5072+
uuids.instance_3: fields_obj.HostStatus.UNKNOWN,
5073+
uuids.instance_4: fields_obj.HostStatus.UP,
5074+
uuids.instance_5: fields_obj.HostStatus.NONE,
5075+
uuids.instance_6: fields_obj.HostStatus.NONE,
5076+
uuids.instance_7: fields_obj.HostStatus.MAINTENANCE}
5077+
for instance in instances:
5078+
self.assertEqual(expect_statuses[instance.uuid],
5079+
host_statuses[instance.uuid])
5080+
50315081
@mock.patch.object(objects.Migration, 'get_by_id_and_instance')
50325082
@mock.patch.object(objects.InstanceAction, 'action_start')
50335083
def test_live_migrate_force_complete_succeeded(

0 commit comments

Comments
 (0)