Merge "Make API calls in Volumes view parallel"
This commit is contained in:
@@ -15,7 +15,6 @@
|
|||||||
"""
|
"""
|
||||||
Admin views for managing volumes and snapshots.
|
Admin views for managing volumes and snapshots.
|
||||||
"""
|
"""
|
||||||
from collections import OrderedDict
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
@@ -37,6 +36,7 @@ from openstack_dashboard.dashboards.admin.volumes \
|
|||||||
import tabs as volumes_tabs
|
import tabs as volumes_tabs
|
||||||
from openstack_dashboard.dashboards.project.volumes \
|
from openstack_dashboard.dashboards.project.volumes \
|
||||||
import views as volumes_views
|
import views as volumes_views
|
||||||
|
from openstack_dashboard.utils import futurist_utils
|
||||||
|
|
||||||
|
|
||||||
class VolumesView(tables.PagedTableMixin, volumes_views.VolumeTableMixIn,
|
class VolumesView(tables.PagedTableMixin, volumes_views.VolumeTableMixIn,
|
||||||
@@ -61,10 +61,55 @@ class VolumesView(tables.PagedTableMixin, volumes_views.VolumeTableMixIn,
|
|||||||
self.table.needs_filter_first = True
|
self.table.needs_filter_first = True
|
||||||
return volumes
|
return volumes
|
||||||
|
|
||||||
|
volumes = []
|
||||||
|
attached_instance_ids = []
|
||||||
|
tenants = []
|
||||||
|
tenant_dict = {}
|
||||||
|
instances = []
|
||||||
|
volume_ids_with_snapshots = []
|
||||||
|
|
||||||
|
def _task_get_tenants():
|
||||||
|
# Gather our tenants to correlate against IDs
|
||||||
|
try:
|
||||||
|
tmp_tenants, __ = 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 volume project information.')
|
||||||
|
exceptions.handle(self.request, msg)
|
||||||
|
|
||||||
|
def _task_get_instances():
|
||||||
|
# As long as Nova API does not allow passing attached_instance_ids
|
||||||
|
# to nova.server_list, this call can be forged to pass anything
|
||||||
|
# != None
|
||||||
|
instances.extend(self._get_instances(
|
||||||
|
search_opts={'all_tenants': True}))
|
||||||
|
|
||||||
|
# In volumes tab we don't need to know about the assignment
|
||||||
|
# instance-image, therefore fixing it to an empty value
|
||||||
|
for instance in instances:
|
||||||
|
if hasattr(instance, 'image'):
|
||||||
|
if isinstance(instance.image, dict):
|
||||||
|
instance.image['name'] = "-"
|
||||||
|
|
||||||
|
def _task_get_volumes_snapshots():
|
||||||
|
volume_ids_with_snapshots.extend(
|
||||||
|
self._get_volumes_ids_with_snapshots(
|
||||||
|
search_opts={'all_tenants': True}
|
||||||
|
))
|
||||||
|
|
||||||
|
def _task_get_volumes():
|
||||||
|
volumes.extend(self._get_volumes(search_opts=filters))
|
||||||
|
attached_instance_ids.extend(
|
||||||
|
self._get_attached_instance_ids(volumes))
|
||||||
|
|
||||||
if 'project' in filters:
|
if 'project' in filters:
|
||||||
# Keystone returns a tuple ([],false) where the first element is
|
futurist_utils.call_functions_parallel(
|
||||||
# tenant list that's why the 0 is hardcoded below
|
_task_get_tenants,
|
||||||
tenants = keystone.tenant_list(self.request)[0]
|
_task_get_instances,
|
||||||
|
_task_get_volumes_snapshots
|
||||||
|
)
|
||||||
|
|
||||||
tenant_ids = [t.id for t in tenants
|
tenant_ids = [t.id for t in tenants
|
||||||
if t.name == filters['project']]
|
if t.name == filters['project']]
|
||||||
if not tenant_ids:
|
if not tenant_ids:
|
||||||
@@ -73,26 +118,18 @@ class VolumesView(tables.PagedTableMixin, volumes_views.VolumeTableMixIn,
|
|||||||
for id in tenant_ids:
|
for id in tenant_ids:
|
||||||
filters['project_id'] = id
|
filters['project_id'] = id
|
||||||
volumes += self._get_volumes(search_opts=filters)
|
volumes += self._get_volumes(search_opts=filters)
|
||||||
|
attached_instance_ids = self._get_attached_instance_ids(volumes)
|
||||||
else:
|
else:
|
||||||
volumes = self._get_volumes(search_opts=filters)
|
futurist_utils.call_functions_parallel(
|
||||||
|
_task_get_volumes,
|
||||||
|
_task_get_tenants,
|
||||||
|
_task_get_instances,
|
||||||
|
_task_get_volumes_snapshots
|
||||||
|
)
|
||||||
|
|
||||||
attached_instance_ids = self._get_attached_instance_ids(volumes)
|
|
||||||
instances = self._get_instances(search_opts={'all_tenants': True},
|
|
||||||
instance_ids=attached_instance_ids)
|
|
||||||
volume_ids_with_snapshots = self._get_volumes_ids_with_snapshots(
|
|
||||||
search_opts={'all_tenants': True})
|
|
||||||
self._set_volume_attributes(
|
self._set_volume_attributes(
|
||||||
volumes, instances, volume_ids_with_snapshots)
|
volumes, instances, volume_ids_with_snapshots)
|
||||||
|
|
||||||
# Gather our tenants to correlate against IDs
|
|
||||||
try:
|
|
||||||
tenants, has_more = keystone.tenant_list(self.request)
|
|
||||||
except Exception:
|
|
||||||
tenants = []
|
|
||||||
msg = _('Unable to retrieve volume project information.')
|
|
||||||
exceptions.handle(self.request, msg)
|
|
||||||
|
|
||||||
tenant_dict = OrderedDict([(t.id, t) for t in tenants])
|
|
||||||
for volume in volumes:
|
for volume in volumes:
|
||||||
tenant_id = getattr(volume, "os-vol-tenant-attr:tenant_id", None)
|
tenant_id = getattr(volume, "os-vol-tenant-attr:tenant_id", None)
|
||||||
tenant = tenant_dict.get(tenant_id, None)
|
tenant = tenant_dict.get(tenant_id, None)
|
||||||
|
@@ -1968,6 +1968,7 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
|
|||||||
self.assertEqual(7, self.mock_tenant_absolute_limits.call_count)
|
self.assertEqual(7, self.mock_tenant_absolute_limits.call_count)
|
||||||
|
|
||||||
@test.create_mocks({
|
@test.create_mocks({
|
||||||
|
api.nova: ['server_list'],
|
||||||
cinder: ['volume_list_paged',
|
cinder: ['volume_list_paged',
|
||||||
'volume_snapshot_list',
|
'volume_snapshot_list',
|
||||||
'tenant_absolute_limits',
|
'tenant_absolute_limits',
|
||||||
@@ -1987,6 +1988,10 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
|
|||||||
transfer.id,
|
transfer.id,
|
||||||
transfer.auth_key)
|
transfer.auth_key)
|
||||||
self.assertEqual(2, self.mock_tenant_absolute_limits.call_count)
|
self.assertEqual(2, self.mock_tenant_absolute_limits.call_count)
|
||||||
|
self.mock_server_list.assert_called_once()
|
||||||
|
self.mock_volume_list_paged.assert_called_once()
|
||||||
|
self.mock_volume_snapshot_list.assert_called_once()
|
||||||
|
self.mock_transfer_accept.assert_called_once()
|
||||||
|
|
||||||
@mock.patch.object(cinder, 'transfer_get')
|
@mock.patch.object(cinder, 'transfer_get')
|
||||||
def test_download_transfer_credentials(self, mock_transfer):
|
def test_download_transfer_credentials(self, mock_transfer):
|
||||||
|
@@ -41,6 +41,7 @@ from openstack_dashboard.api import nova
|
|||||||
from openstack_dashboard import exceptions as dashboard_exception
|
from openstack_dashboard import exceptions as dashboard_exception
|
||||||
from openstack_dashboard.usage import quotas
|
from openstack_dashboard.usage import quotas
|
||||||
from openstack_dashboard.utils import filters
|
from openstack_dashboard.utils import filters
|
||||||
|
from openstack_dashboard.utils import futurist_utils
|
||||||
|
|
||||||
from openstack_dashboard.dashboards.project.volumes \
|
from openstack_dashboard.dashboards.project.volumes \
|
||||||
import forms as volume_forms
|
import forms as volume_forms
|
||||||
@@ -71,9 +72,7 @@ class VolumeTableMixIn(object):
|
|||||||
_('Unable to retrieve volume list.'))
|
_('Unable to retrieve volume list.'))
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def _get_instances(self, search_opts=None, instance_ids=None):
|
def _get_instances(self, search_opts=None):
|
||||||
if not instance_ids:
|
|
||||||
return []
|
|
||||||
try:
|
try:
|
||||||
# TODO(tsufiev): we should pass attached_instance_ids to
|
# TODO(tsufiev): we should pass attached_instance_ids to
|
||||||
# nova.server_list as soon as Nova API allows for this
|
# nova.server_list as soon as Nova API allows for this
|
||||||
@@ -149,10 +148,38 @@ class VolumesView(tables.PagedTableMixin, VolumeTableMixIn,
|
|||||||
page_title = _("Volumes")
|
page_title = _("Volumes")
|
||||||
|
|
||||||
def get_data(self):
|
def get_data(self):
|
||||||
volumes = self._get_volumes()
|
volumes = []
|
||||||
attached_instance_ids = self._get_attached_instance_ids(volumes)
|
attached_instance_ids = []
|
||||||
instances = self._get_instances(instance_ids=attached_instance_ids)
|
instances = []
|
||||||
volume_ids_with_snapshots = self._get_volumes_ids_with_snapshots()
|
volume_ids_with_snapshots = []
|
||||||
|
|
||||||
|
def _task_get_volumes():
|
||||||
|
volumes.extend(self._get_volumes())
|
||||||
|
attached_instance_ids.extend(
|
||||||
|
self._get_attached_instance_ids(volumes))
|
||||||
|
|
||||||
|
def _task_get_instances():
|
||||||
|
# As long as Nova API does not allow passing attached_instance_ids
|
||||||
|
# to nova.server_list, this call can be forged to pass anything
|
||||||
|
# != None
|
||||||
|
instances.extend(self._get_instances())
|
||||||
|
|
||||||
|
# In volumes tab we don't need to know about the assignment
|
||||||
|
# instance-image, therefore fixing it to an empty value
|
||||||
|
for instance in instances:
|
||||||
|
if hasattr(instance, 'image'):
|
||||||
|
if isinstance(instance.image, dict):
|
||||||
|
instance.image['name'] = "-"
|
||||||
|
|
||||||
|
def _task_get_volumes_snapshots():
|
||||||
|
volume_ids_with_snapshots.extend(
|
||||||
|
self._get_volumes_ids_with_snapshots())
|
||||||
|
|
||||||
|
futurist_utils.call_functions_parallel(
|
||||||
|
_task_get_volumes,
|
||||||
|
_task_get_instances,
|
||||||
|
_task_get_volumes_snapshots)
|
||||||
|
|
||||||
self._set_volume_attributes(
|
self._set_volume_attributes(
|
||||||
volumes, instances, volume_ids_with_snapshots)
|
volumes, instances, volume_ids_with_snapshots)
|
||||||
self._get_groups(volumes)
|
self._get_groups(volumes)
|
||||||
|
Reference in New Issue
Block a user