Skip to content

Commit ab7d923

Browse files
committed
Fix GET /servers/detail host_status performance regression
Change I82b11b8866ac82b05eae04351605d52fa8b91453 moved the host_status extended server attribute processing from an extension to the main servers view builder. This, however, caused a regression in the detailed listing of servers because it didn't incorporate the caching mechanism used previously by the extension so now for each server with details when microversion 2.16 or greater is used (and the request passes the policy check), we get the host status per server even if we have multiple servers on the same host. This moves the host_status processing out of the show() method when listing servers with details and processes them in aggregate similar to security groups and attached volumes. One catch is the show() method handles instances from down cells for us so we have to handle that separately in the new host_status processing, but it's trivial (just don't get host_status for instances without a host field set). This reverts commit 0cecd2a. Change-Id: I8278d4ea993ed1600919e34c9759600c8c7dbb41 Closes-Bug: #1830260
1 parent 1f85a1f commit ab7d923

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
@@ -4824,6 +4824,21 @@ def get_instance_host_status(self, instance):
48244824
host_status = fields_obj.HostStatus.NONE
48254825
return host_status
48264826

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

48284843
def target_host_cell(fn):
48294844
"""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)