Merge "Server-side filtering for Instances (Project/Admin)"
This commit is contained in:
commit
14005e3b76
@ -101,14 +101,11 @@ class AdminInstanceFilterAction(tables.FilterAction):
|
||||
# session property used for persisting the filter.
|
||||
name = "filter_admin_instances"
|
||||
filter_type = "server"
|
||||
filter_choices = (('project', _("Project ="), True),
|
||||
('host', _("Host ="), True),
|
||||
('name', _("Name ="), True),
|
||||
('ip', _("IPv4 Address ="), True),
|
||||
('ip6', _("IPv6 Address ="), True),
|
||||
('status', _("Status ="), True),
|
||||
('image', _("Image ID ="), True),
|
||||
('flavor', _("Flavor ID ="), True))
|
||||
filter_choices = (
|
||||
('project', _("Project Name ="), True),
|
||||
('tenant_id', _("Project ID ="), True),
|
||||
('host', _("Host Name ="), True),
|
||||
) + project_tables.INSTANCE_FILTER_CHOICES
|
||||
|
||||
|
||||
class AdminInstancesTable(tables.DataTable):
|
||||
|
@ -29,14 +29,17 @@ INDEX_URL = reverse('horizon:admin:instances:index')
|
||||
|
||||
|
||||
class InstanceViewTest(test.BaseAdminViewTests):
|
||||
@test.create_stubs({api.nova: ('flavor_list', 'server_list',
|
||||
'extension_supported',),
|
||||
api.keystone: ('tenant_list',),
|
||||
api.network: ('servers_update_addresses',)})
|
||||
@test.create_stubs({
|
||||
api.nova: ('flavor_list', 'server_list', 'extension_supported',),
|
||||
api.keystone: ('tenant_list',),
|
||||
api.network: ('servers_update_addresses',),
|
||||
api.glance: ('image_list_detailed',),
|
||||
})
|
||||
def test_index(self):
|
||||
servers = self.servers.list()
|
||||
flavors = self.flavors.list()
|
||||
tenants = self.tenants.list()
|
||||
images = self.images.list()
|
||||
api.nova.extension_supported('AdminActions', IsA(http.HttpRequest)) \
|
||||
.MultipleTimes().AndReturn(True)
|
||||
api.nova.extension_supported('Shelve', IsA(http.HttpRequest)) \
|
||||
@ -44,12 +47,14 @@ class InstanceViewTest(test.BaseAdminViewTests):
|
||||
api.keystone.tenant_list(IsA(http.HttpRequest)).\
|
||||
AndReturn([tenants, False])
|
||||
search_opts = {'marker': None, 'paginate': True}
|
||||
api.glance.image_list_detailed(IsA(http.HttpRequest))\
|
||||
.AndReturn(images)
|
||||
api.nova.flavor_list(IsA(http.HttpRequest)).AndReturn(flavors)
|
||||
api.nova.server_list(IsA(http.HttpRequest),
|
||||
all_tenants=True, search_opts=search_opts) \
|
||||
.AndReturn([servers, False])
|
||||
api.network.servers_update_addresses(IsA(http.HttpRequest), servers,
|
||||
all_tenants=True)
|
||||
api.nova.flavor_list(IsA(http.HttpRequest)).AndReturn(flavors)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(INDEX_URL)
|
||||
@ -57,16 +62,18 @@ class InstanceViewTest(test.BaseAdminViewTests):
|
||||
instances = res.context['table'].data
|
||||
self.assertItemsEqual(instances, servers)
|
||||
|
||||
@test.create_stubs({api.nova: ('flavor_list', 'flavor_get',
|
||||
'server_list', 'extension_supported',),
|
||||
api.keystone: ('tenant_list',),
|
||||
api.network: ('servers_update_addresses',)})
|
||||
@test.create_stubs({
|
||||
api.nova: ('flavor_list', 'flavor_get', 'server_list',
|
||||
'extension_supported',),
|
||||
api.keystone: ('tenant_list',),
|
||||
api.network: ('servers_update_addresses',),
|
||||
api.glance: ('image_list_detailed',),
|
||||
})
|
||||
def test_index_flavor_list_exception(self):
|
||||
servers = self.servers.list()
|
||||
tenants = self.tenants.list()
|
||||
flavors = self.flavors.list()
|
||||
full_flavors = OrderedDict([(f.id, f) for f in flavors])
|
||||
|
||||
search_opts = {'marker': None, 'paginate': True}
|
||||
api.nova.server_list(IsA(http.HttpRequest),
|
||||
all_tenants=True, search_opts=search_opts) \
|
||||
@ -92,19 +99,26 @@ class InstanceViewTest(test.BaseAdminViewTests):
|
||||
instances = res.context['table'].data
|
||||
self.assertItemsEqual(instances, servers)
|
||||
|
||||
@test.create_stubs({api.nova: ('flavor_list', 'flavor_get',
|
||||
'server_list', 'extension_supported', ),
|
||||
api.keystone: ('tenant_list',),
|
||||
api.network: ('servers_update_addresses',)})
|
||||
@test.create_stubs({
|
||||
api.nova: ('flavor_list', 'flavor_get', 'server_list',
|
||||
'extension_supported',),
|
||||
api.keystone: ('tenant_list',),
|
||||
api.network: ('servers_update_addresses',),
|
||||
api.glance: ('image_list_detailed',),
|
||||
})
|
||||
def test_index_flavor_get_exception(self):
|
||||
servers = self.servers.list()
|
||||
flavors = self.flavors.list()
|
||||
images = self.images.list()
|
||||
tenants = self.tenants.list()
|
||||
# UUIDs generated using indexes are unlikely to match
|
||||
# any of existing flavor ids and are guaranteed to be deterministic.
|
||||
for i, server in enumerate(servers):
|
||||
server.flavor['id'] = str(uuid.UUID(int=i))
|
||||
|
||||
api.glance.image_list_detailed(IsA(http.HttpRequest))\
|
||||
.AndReturn(images)
|
||||
api.nova.flavor_list(IsA(http.HttpRequest)).AndReturn(flavors)
|
||||
search_opts = {'marker': None, 'paginate': True}
|
||||
api.nova.server_list(IsA(http.HttpRequest),
|
||||
all_tenants=True, search_opts=search_opts) \
|
||||
@ -115,8 +129,6 @@ class InstanceViewTest(test.BaseAdminViewTests):
|
||||
.MultipleTimes().AndReturn(True)
|
||||
api.nova.extension_supported('Shelve', IsA(http.HttpRequest)) \
|
||||
.MultipleTimes().AndReturn(True)
|
||||
api.nova.flavor_list(IsA(http.HttpRequest)). \
|
||||
AndReturn(flavors)
|
||||
api.keystone.tenant_list(IsA(http.HttpRequest)).\
|
||||
AndReturn([tenants, False])
|
||||
for server in servers:
|
||||
@ -133,10 +145,14 @@ class InstanceViewTest(test.BaseAdminViewTests):
|
||||
self.assertMessageCount(res, error=1)
|
||||
self.assertItemsEqual(instances, servers)
|
||||
|
||||
@test.create_stubs({api.nova: ('server_list',),
|
||||
api.keystone: ('tenant_list',)})
|
||||
@test.create_stubs({
|
||||
api.nova: ('server_list', 'flavor_list',),
|
||||
api.keystone: ('tenant_list',),
|
||||
api.glance: ('image_list_detailed',),
|
||||
})
|
||||
def test_index_server_list_exception(self):
|
||||
tenants = self.tenants.list()
|
||||
|
||||
search_opts = {'marker': None, 'paginate': True}
|
||||
api.nova.server_list(IsA(http.HttpRequest),
|
||||
all_tenants=True, search_opts=search_opts) \
|
||||
@ -188,14 +204,21 @@ class InstanceViewTest(test.BaseAdminViewTests):
|
||||
self.assertContains(res, "Active", 1, 200)
|
||||
self.assertContains(res, "Running", 1, 200)
|
||||
|
||||
@test.create_stubs({api.nova: ('flavor_list', 'server_list',
|
||||
'extension_supported', ),
|
||||
api.keystone: ('tenant_list',),
|
||||
api.network: ('servers_update_addresses',)})
|
||||
@test.create_stubs({
|
||||
api.nova: ('flavor_list', 'server_list', 'extension_supported', ),
|
||||
api.keystone: ('tenant_list',),
|
||||
api.network: ('servers_update_addresses',),
|
||||
api.glance: ('image_list_detailed',),
|
||||
})
|
||||
def test_index_options_before_migrate(self):
|
||||
servers = self.servers.list()
|
||||
images = self.images.list()
|
||||
flavors = self.flavors.list()
|
||||
api.keystone.tenant_list(IsA(http.HttpRequest)).\
|
||||
AndReturn([self.tenants.list(), False])
|
||||
api.glance.image_list_detailed(IsA(http.HttpRequest)) \
|
||||
.AndReturn(images)
|
||||
api.nova.flavor_list(IsA(http.HttpRequest)).AndReturn(flavors)
|
||||
search_opts = {'marker': None, 'paginate': True}
|
||||
api.nova.server_list(IsA(http.HttpRequest),
|
||||
all_tenants=True, search_opts=search_opts) \
|
||||
@ -206,8 +229,6 @@ class InstanceViewTest(test.BaseAdminViewTests):
|
||||
.MultipleTimes().AndReturn(True)
|
||||
api.nova.extension_supported('Shelve', IsA(http.HttpRequest)) \
|
||||
.MultipleTimes().AndReturn(True)
|
||||
api.nova.flavor_list(IsA(http.HttpRequest)).\
|
||||
AndReturn(self.flavors.list())
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(INDEX_URL)
|
||||
@ -215,18 +236,25 @@ class InstanceViewTest(test.BaseAdminViewTests):
|
||||
self.assertNotContains(res, "instances__confirm")
|
||||
self.assertNotContains(res, "instances__revert")
|
||||
|
||||
@test.create_stubs({api.nova: ('flavor_list', 'server_list',
|
||||
'extension_supported', ),
|
||||
api.keystone: ('tenant_list',),
|
||||
api.network: ('servers_update_addresses',)})
|
||||
@test.create_stubs({
|
||||
api.nova: ('flavor_list', 'server_list', 'extension_supported',),
|
||||
api.keystone: ('tenant_list',),
|
||||
api.network: ('servers_update_addresses',),
|
||||
api.glance: ('image_list_detailed',),
|
||||
})
|
||||
def test_index_options_after_migrate(self):
|
||||
servers = self.servers.list()
|
||||
server1 = servers[0]
|
||||
server1.status = "VERIFY_RESIZE"
|
||||
server2 = servers[2]
|
||||
server2.status = "VERIFY_RESIZE"
|
||||
images = self.images.list()
|
||||
flavors = self.flavors.list()
|
||||
api.keystone.tenant_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn([self.tenants.list(), False])
|
||||
api.glance.image_list_detailed(IsA(http.HttpRequest)) \
|
||||
.AndReturn(images)
|
||||
api.nova.flavor_list(IsA(http.HttpRequest)).AndReturn(flavors)
|
||||
search_opts = {'marker': None, 'paginate': True}
|
||||
api.nova.extension_supported('AdminActions', IsA(http.HttpRequest)) \
|
||||
.MultipleTimes().AndReturn(True)
|
||||
@ -237,8 +265,6 @@ class InstanceViewTest(test.BaseAdminViewTests):
|
||||
.AndReturn([servers, False])
|
||||
api.network.servers_update_addresses(IsA(http.HttpRequest), servers,
|
||||
all_tenants=True)
|
||||
api.nova.flavor_list(IsA(http.HttpRequest)).\
|
||||
AndReturn(self.flavors.list())
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(INDEX_URL)
|
||||
|
@ -30,6 +30,7 @@ from horizon import tables
|
||||
from horizon.utils import memoized
|
||||
|
||||
from openstack_dashboard import api
|
||||
|
||||
from openstack_dashboard.dashboards.admin.instances \
|
||||
import forms as project_forms
|
||||
from openstack_dashboard.dashboards.admin.instances \
|
||||
@ -59,6 +60,11 @@ def rdp(args, **kvargs):
|
||||
return views.rdp(args, **kvargs)
|
||||
|
||||
|
||||
# re-use get_resource_id_by_name from project.instances.views
|
||||
def swap_filter(resources, filters, fake_field, real_field):
|
||||
return views.swap_filter(resources, filters, fake_field, real_field)
|
||||
|
||||
|
||||
class AdminUpdateView(views.UpdateView):
|
||||
workflow_class = update_instance.AdminUpdateInstance
|
||||
success_url = reverse_lazy("horizon:admin:instances:index")
|
||||
@ -102,15 +108,32 @@ class AdminIndexView(tables.DataTableView):
|
||||
msg = _('Unable to retrieve instance project information.')
|
||||
exceptions.handle(self.request, msg)
|
||||
|
||||
if 'project' in search_opts:
|
||||
ten_filter_ids = [t.id for t in tenants
|
||||
if t.name == search_opts['project']]
|
||||
del search_opts['project']
|
||||
if len(ten_filter_ids) > 0:
|
||||
search_opts['tenant_id'] = ten_filter_ids[0]
|
||||
else:
|
||||
# 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.")
|
||||
|
||||
# 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 = []
|
||||
|
||||
if 'project' in search_opts and \
|
||||
not swap_filter(tenants, search_opts, 'project', 'tenant_id'):
|
||||
self._more = False
|
||||
return []
|
||||
return instances
|
||||
elif 'image_name' in search_opts and \
|
||||
not swap_filter(images, search_opts, 'image_name', 'image'):
|
||||
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
|
||||
|
||||
try:
|
||||
instances, self._more = api.nova.server_list(
|
||||
@ -131,13 +154,6 @@ class AdminIndexView(tables.DataTableView):
|
||||
message=_('Unable to retrieve IP addresses from Neutron.'),
|
||||
ignore=True)
|
||||
|
||||
# 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 = []
|
||||
|
||||
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.
|
||||
|
@ -1171,13 +1171,27 @@ POWER_DISPLAY_CHOICES = (
|
||||
("BUILDING", pgettext_lazy("Power state of an Instance", u"Building")),
|
||||
)
|
||||
|
||||
INSTANCE_FILTER_CHOICES = (
|
||||
('uuid', _("Instance ID ="), True),
|
||||
('name', _("Instance Name"), True),
|
||||
('image', _("Image ID ="), True),
|
||||
('image_name', _("Image Name ="), True),
|
||||
('ip', _("IPv4 Address"), True),
|
||||
('ip6', _("IPv6 Address"), True),
|
||||
('flavor', _("Flavor ID ="), True),
|
||||
('flavor_name', _("Flavor Name ="), True),
|
||||
('key_name', _("Key Pair Name"), True),
|
||||
('status', _("Status ="), True),
|
||||
('availability_zone', _("Availability Zone"), True),
|
||||
('changes-since', _("Changes Since"), True,
|
||||
_("Filter by an ISO 8061 formatted time, e.g. 2016-06-14T06:27:59Z")),
|
||||
('vcpus', _("vCPUs ="), True),
|
||||
)
|
||||
|
||||
|
||||
class InstancesFilterAction(tables.FilterAction):
|
||||
filter_type = "server"
|
||||
filter_choices = (('name', _("Instance Name ="), True),
|
||||
('status', _("Status ="), True),
|
||||
('image', _("Image ID ="), True),
|
||||
('flavor', _("Flavor ID ="), True))
|
||||
filter_choices = INSTANCE_FILTER_CHOICES
|
||||
|
||||
|
||||
class InstancesTable(tables.DataTable):
|
||||
|
@ -114,10 +114,18 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
|
||||
self.assertItemsEqual(instances, self.servers.list())
|
||||
self.assertNotContains(res, "Launch Instance (Quota exceeded)")
|
||||
|
||||
@helpers.create_stubs({api.nova: ('server_list',
|
||||
'tenant_absolute_limits',)})
|
||||
@helpers.create_stubs({
|
||||
api.nova: ('server_list', 'tenant_absolute_limits', 'flavor_list'),
|
||||
api.glance: ('image_list_detailed',),
|
||||
})
|
||||
def test_index_server_list_exception(self):
|
||||
search_opts = {'marker': None, 'paginate': True}
|
||||
flavors = self.flavors.list()
|
||||
images = self.images.list()
|
||||
api.nova.flavor_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(flavors)
|
||||
api.glance.image_list_detailed(IsA(http.HttpRequest)) \
|
||||
.AndReturn(images)
|
||||
api.nova.server_list(IsA(http.HttpRequest), search_opts=search_opts) \
|
||||
.AndRaise(self.exceptions.nova)
|
||||
api.nova.tenant_absolute_limits(IsA(http.HttpRequest), reserved=True) \
|
||||
|
@ -64,9 +64,35 @@ 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)
|
||||
|
||||
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'):
|
||||
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
|
||||
|
||||
# Gather our instances
|
||||
try:
|
||||
instances, self._more = api.nova.server_list(
|
||||
@ -86,22 +112,6 @@ class IndexView(tables.DataTableView):
|
||||
self.request,
|
||||
message=_('Unable to retrieve IP addresses from Neutron.'),
|
||||
ignore=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)
|
||||
|
||||
try:
|
||||
# TODO(gabriel): Handle pagination.
|
||||
images, more, prev = api.glance.image_list_detailed(
|
||||
self.request)
|
||||
except Exception:
|
||||
images = []
|
||||
exceptions.handle(self.request, ignore=True)
|
||||
|
||||
full_flavors = OrderedDict([(str(flavor.id), flavor)
|
||||
for flavor in flavors])
|
||||
image_map = OrderedDict([(str(image.id), image)
|
||||
@ -114,7 +124,6 @@ class IndexView(tables.DataTableView):
|
||||
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:
|
||||
@ -131,6 +140,16 @@ class IndexView(tables.DataTableView):
|
||||
return instances
|
||||
|
||||
|
||||
def swap_filter(resources, filters, fake_field, real_field):
|
||||
if fake_field in filters:
|
||||
filter_string = filters[fake_field]
|
||||
for resource in resources:
|
||||
if resource.name.lower() == filter_string.lower():
|
||||
filters[real_field] = resource.id
|
||||
del filters[fake_field]
|
||||
return True
|
||||
|
||||
|
||||
class LaunchInstanceView(workflows.WorkflowView):
|
||||
workflow_class = project_workflows.LaunchInstance
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user