Make API calls in Instances view parallel
In order to increase rendering speed, make api.nova.server_list, api.network.servers_update_addresses, api.nova.flavor_list and api.glance.image_list_detailed parallel. For admin panel also api.keystone.tenant_list is parallelized. Closes-bug: #1655307 Change-Id: I83150d3963f1233edc9efbcdac299520dedbed3c
This commit is contained in:
parent
d07a02a60c
commit
df194c8b4c
|
@ -140,9 +140,9 @@ class InstanceViewTest(test.BaseAdminViewTests):
|
|||
instances = res.context['table'].data
|
||||
self.assertTemplateUsed(res, INDEX_TEMPLATE)
|
||||
# Since error messages produced for each instance are identical,
|
||||
# there will be only one error message for all instances
|
||||
# there will be only two error messages for all instances
|
||||
# (messages de-duplication).
|
||||
self.assertMessageCount(res, error=1)
|
||||
self.assertMessageCount(res, error=2)
|
||||
self.assertItemsEqual(instances, servers)
|
||||
|
||||
@test.create_stubs({
|
||||
|
|
|
@ -17,8 +17,6 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
|
@ -39,6 +37,8 @@ from openstack_dashboard.dashboards.project.instances import views
|
|||
from openstack_dashboard.dashboards.project.instances.workflows \
|
||||
import update_instance
|
||||
|
||||
import futurist
|
||||
|
||||
|
||||
# re-use console from project.instances.views to make reflection work
|
||||
def console(args, **kvargs):
|
||||
|
@ -82,6 +82,12 @@ class AdminIndexView(tables.DataTableView):
|
|||
|
||||
def get_data(self):
|
||||
instances = []
|
||||
tenants = []
|
||||
tenant_dict = {}
|
||||
images = []
|
||||
flavors = []
|
||||
full_flavors = {}
|
||||
|
||||
marker = self.request.GET.get(
|
||||
project_tables.AdminInstancesTable._meta.pagination_param, None)
|
||||
default_search_opts = {'marker': marker, 'paginate': True}
|
||||
|
@ -99,27 +105,72 @@ class AdminIndexView(tables.DataTableView):
|
|||
return instances
|
||||
|
||||
self._needs_filter_first = False
|
||||
# Gather our tenants to correlate against IDs
|
||||
try:
|
||||
tenants, has_more = api.keystone.tenant_list(self.request)
|
||||
except Exception:
|
||||
tenants = []
|
||||
msg = _('Unable to retrieve instance project information.')
|
||||
exceptions.handle(self.request, msg)
|
||||
|
||||
# Gather our images to correlate againts IDs
|
||||
try:
|
||||
images = api.glance.image_list_detailed(self.request)[0]
|
||||
except Exception:
|
||||
images = []
|
||||
msg = _("Unable to retrieve image list.")
|
||||
def _task_get_tenants():
|
||||
# Gather our tenants to correlate against IDs
|
||||
try:
|
||||
tmp_tenants, __ = api.keystone.tenant_list(self.request)
|
||||
tenants.extend(tmp_tenants)
|
||||
tenant_dict.update([(t.id, t) for t in tenants])
|
||||
except Exception:
|
||||
msg = _('Unable to retrieve instance project information.')
|
||||
exceptions.handle(self.request, msg)
|
||||
|
||||
# Gather our flavors to correlate against IDs
|
||||
try:
|
||||
flavors = api.nova.flavor_list(self.request)
|
||||
except Exception:
|
||||
# If fails to retrieve flavor list, creates an empty list.
|
||||
flavors = []
|
||||
def _task_get_images():
|
||||
# Gather our images to correlate againts IDs
|
||||
try:
|
||||
tmp_images = api.glance.image_list_detailed(self.request)[0]
|
||||
images.extend(tmp_images)
|
||||
except Exception:
|
||||
msg = _("Unable to retrieve image list.")
|
||||
exceptions.handle(self.request, msg)
|
||||
|
||||
def _task_get_flavors():
|
||||
# Gather our flavors to correlate against IDs
|
||||
try:
|
||||
tmp_flavors = api.nova.flavor_list(self.request)
|
||||
flavors.extend(tmp_flavors)
|
||||
full_flavors.update([(str(flavor.id), flavor)
|
||||
for flavor in flavors])
|
||||
except Exception:
|
||||
msg = _("Unable to retrieve flavor list.")
|
||||
exceptions.handle(self.request, msg)
|
||||
|
||||
def _task_get_instances():
|
||||
try:
|
||||
tmp_instances, self._more = api.nova.server_list(
|
||||
self.request,
|
||||
search_opts=search_opts,
|
||||
all_tenants=True)
|
||||
instances.extend(tmp_instances)
|
||||
except Exception:
|
||||
self._more = False
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve instance list.'))
|
||||
# In case of exception when calling nova.server_list
|
||||
# don't call api.network
|
||||
return
|
||||
|
||||
try:
|
||||
api.network.servers_update_addresses(self.request, instances,
|
||||
all_tenants=True)
|
||||
except Exception:
|
||||
exceptions.handle(
|
||||
self.request,
|
||||
message=_('Unable to retrieve IP addresses from Neutron.'),
|
||||
ignore=True)
|
||||
|
||||
with futurist.ThreadPoolExecutor(max_workers=4) as e:
|
||||
e.submit(fn=_task_get_tenants)
|
||||
e.submit(fn=_task_get_images)
|
||||
e.submit(fn=_task_get_flavors)
|
||||
e.submit(fn=_task_get_instances)
|
||||
|
||||
# This code gets activated only in case of filtering by nonexistent
|
||||
# project, image or flavor. Executing it before _task_get_instances
|
||||
# would make Horizon make less API calls, but as a drawback would make
|
||||
# it impossible to parallelize the request. Executing it after is
|
||||
# a tradeoff, as it happens less often to filter by nonexistent values.
|
||||
|
||||
if 'project' in search_opts and \
|
||||
not swap_filter(tenants, search_opts, 'project', 'tenant_id'):
|
||||
|
@ -134,43 +185,22 @@ class AdminIndexView(tables.DataTableView):
|
|||
self._more = False
|
||||
return instances
|
||||
|
||||
try:
|
||||
instances, self._more = api.nova.server_list(
|
||||
self.request,
|
||||
search_opts=search_opts,
|
||||
all_tenants=True)
|
||||
except Exception:
|
||||
self._more = False
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve instance list.'))
|
||||
if instances:
|
||||
# Loop through instances to get flavor and tenant info.
|
||||
for inst in instances:
|
||||
flavor_id = inst.flavor["id"]
|
||||
try:
|
||||
api.network.servers_update_addresses(self.request, instances,
|
||||
all_tenants=True)
|
||||
if flavor_id in full_flavors:
|
||||
inst.full_flavor = full_flavors[flavor_id]
|
||||
else:
|
||||
# If the flavor_id is not in full_flavors list,
|
||||
# gets it via nova api.
|
||||
inst.full_flavor = api.nova.flavor_get(
|
||||
self.request, flavor_id)
|
||||
except Exception:
|
||||
exceptions.handle(
|
||||
self.request,
|
||||
message=_('Unable to retrieve IP addresses from Neutron.'),
|
||||
ignore=True)
|
||||
|
||||
full_flavors = OrderedDict([(f.id, f) for f in flavors])
|
||||
tenant_dict = OrderedDict([(t.id, t) for t in tenants])
|
||||
# Loop through instances to get flavor and tenant info.
|
||||
for inst in instances:
|
||||
flavor_id = inst.flavor["id"]
|
||||
try:
|
||||
if flavor_id in full_flavors:
|
||||
inst.full_flavor = full_flavors[flavor_id]
|
||||
else:
|
||||
# If the flavor_id is not in full_flavors list,
|
||||
# gets it via nova api.
|
||||
inst.full_flavor = api.nova.flavor_get(
|
||||
self.request, flavor_id)
|
||||
except Exception:
|
||||
msg = _('Unable to retrieve instance size information.')
|
||||
exceptions.handle(self.request, msg)
|
||||
tenant = tenant_dict.get(inst.tenant_id, None)
|
||||
inst.tenant_name = getattr(tenant, "name", None)
|
||||
msg = _('Unable to retrieve instance size information.')
|
||||
exceptions.handle(self.request, msg)
|
||||
tenant = tenant_dict.get(inst.tenant_id, None)
|
||||
inst.tenant_name = getattr(tenant, "name", None)
|
||||
return instances
|
||||
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
Views for managing instances.
|
||||
"""
|
||||
from collections import OrderedDict
|
||||
import futurist
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
|
@ -63,47 +64,29 @@ class IndexView(tables.DataTableView):
|
|||
return self._more
|
||||
|
||||
def get_data(self):
|
||||
instances = []
|
||||
marker = self.request.GET.get(
|
||||
project_tables.InstancesTable._meta.pagination_param, None)
|
||||
|
||||
search_opts = self.get_filters({'marker': marker, 'paginate': True})
|
||||
|
||||
# Gather our flavors and images and correlate our instances to them
|
||||
try:
|
||||
flavors = api.nova.flavor_list(self.request)
|
||||
except Exception:
|
||||
flavors = []
|
||||
exceptions.handle(self.request, ignore=True)
|
||||
instances = []
|
||||
full_flavors = {}
|
||||
image_map = {}
|
||||
|
||||
try:
|
||||
# TODO(gabriel): Handle pagination.
|
||||
images = api.glance.image_list_detailed(self.request)[0]
|
||||
except Exception:
|
||||
images = []
|
||||
exceptions.handle(self.request, ignore=True)
|
||||
|
||||
if 'image_name' in search_opts and \
|
||||
not swap_filter(images, search_opts, 'image_name', 'image'):
|
||||
def _task_get_instances():
|
||||
# Gather our instances
|
||||
try:
|
||||
tmp_instances, self._more = api.nova.server_list(
|
||||
self.request,
|
||||
search_opts=search_opts)
|
||||
instances.extend(tmp_instances)
|
||||
except Exception:
|
||||
self._more = False
|
||||
return instances
|
||||
elif 'flavor_name' in search_opts and \
|
||||
not swap_filter(flavors, search_opts, 'flavor_name', 'flavor'):
|
||||
self._more = False
|
||||
return instances
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve instances.'))
|
||||
# In case of exception when calling nova.server_list
|
||||
# don't call api.network
|
||||
return
|
||||
|
||||
# Gather our instances
|
||||
try:
|
||||
instances, self._more = api.nova.server_list(
|
||||
self.request,
|
||||
search_opts=search_opts)
|
||||
except Exception:
|
||||
self._more = False
|
||||
instances = []
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve instances.'))
|
||||
|
||||
if instances:
|
||||
try:
|
||||
api.network.servers_update_addresses(self.request, instances)
|
||||
except Exception:
|
||||
|
@ -111,31 +94,57 @@ class IndexView(tables.DataTableView):
|
|||
self.request,
|
||||
message=_('Unable to retrieve IP addresses from Neutron.'),
|
||||
ignore=True)
|
||||
full_flavors = OrderedDict([(str(flavor.id), flavor)
|
||||
for flavor in flavors])
|
||||
image_map = OrderedDict([(str(image.id), image)
|
||||
for image in images])
|
||||
|
||||
# Loop through instances to get flavor info.
|
||||
for instance in instances:
|
||||
if hasattr(instance, 'image'):
|
||||
# Instance from image returns dict
|
||||
if isinstance(instance.image, dict):
|
||||
if instance.image.get('id') in image_map:
|
||||
instance.image = image_map[instance.image['id']]
|
||||
try:
|
||||
flavor_id = instance.flavor["id"]
|
||||
if flavor_id in full_flavors:
|
||||
instance.full_flavor = full_flavors[flavor_id]
|
||||
def _task_get_flavors():
|
||||
# Gather our flavors to correlate our instances to them
|
||||
try:
|
||||
flavors = api.nova.flavor_list(self.request)
|
||||
full_flavors.update([(str(flavor.id), flavor)
|
||||
for flavor in flavors])
|
||||
except Exception:
|
||||
exceptions.handle(self.request, ignore=True)
|
||||
|
||||
def _task_get_images():
|
||||
# Gather our images to correlate our instances to them
|
||||
try:
|
||||
# TODO(gabriel): Handle pagination.
|
||||
images = api.glance.image_list_detailed(self.request)[0]
|
||||
image_map.update([(str(image.id), image) for image in images])
|
||||
except Exception:
|
||||
exceptions.handle(self.request, ignore=True)
|
||||
|
||||
with futurist.ThreadPoolExecutor(max_workers=3) as e:
|
||||
e.submit(fn=_task_get_instances)
|
||||
e.submit(fn=_task_get_flavors)
|
||||
e.submit(fn=_task_get_images)
|
||||
|
||||
# Loop through instances to get flavor info.
|
||||
for instance in instances:
|
||||
if hasattr(instance, 'image'):
|
||||
# Instance from image returns dict
|
||||
if isinstance(instance.image, dict):
|
||||
if instance.image.get('id') in image_map:
|
||||
instance.image = image_map[instance.image.get('id')]
|
||||
# In case image not found in image_map, set name to empty
|
||||
# to avoid fallback API call to Glance in api/nova.py
|
||||
# until the call is deprecated in api itself
|
||||
else:
|
||||
# If the flavor_id is not in full_flavors list,
|
||||
# get it via nova api.
|
||||
instance.full_flavor = api.nova.flavor_get(
|
||||
self.request, flavor_id)
|
||||
except Exception:
|
||||
msg = ('Unable to retrieve flavor "%s" for instance "%s".'
|
||||
% (flavor_id, instance.id))
|
||||
LOG.info(msg)
|
||||
instance.image['name'] = _("-")
|
||||
|
||||
try:
|
||||
flavor_id = instance.flavor["id"]
|
||||
if flavor_id in full_flavors:
|
||||
instance.full_flavor = full_flavors[flavor_id]
|
||||
else:
|
||||
# If the flavor_id is not in full_flavors list,
|
||||
# get it via nova api.
|
||||
instance.full_flavor = api.nova.flavor_get(
|
||||
self.request, flavor_id)
|
||||
except Exception:
|
||||
msg = ('Unable to retrieve flavor "%s" for instance "%s".'
|
||||
% (flavor_id, instance.id))
|
||||
LOG.info(msg)
|
||||
|
||||
return instances
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue