Server-side filtering networks

Implements server-side filtering networks in:
  - Admin->System->Networks
  - Project->Network->Networks

Implements blueprint: server-side-filtering

Co-Authored-By: Akihiro Motoki <amotoki@gmail.com>
Change-Id: Ie05f58a77b62b12dbf087395fbaf4ff0dc1f8562
This commit is contained in:
jlopezgu 2016-06-13 15:55:33 -05:00 committed by Akihiro Motoki
parent 9612cbaee0
commit 1a0e284106
6 changed files with 161 additions and 44 deletions

View File

@ -638,20 +638,34 @@ def network_list_for_tenant(request, tenant_id, include_external=False,
LOG.debug("network_list_for_tenant(): tenant_id=%s, params=%s"
% (tenant_id, params))
# If a user has admin role, network list returned by Neutron API
# contains networks that do not belong to that tenant.
# So we need to specify tenant_id when calling network_list().
networks = network_list(request, tenant_id=tenant_id,
shared=False, **params)
networks = []
shared = params.get('shared')
if shared is not None:
del params['shared']
# In the current Neutron API, there is no way to retrieve
# both owner networks and public networks in a single API call.
networks += network_list(request, shared=True, **params)
if shared in (None, False):
# If a user has admin role, network list returned by Neutron API
# contains networks that do not belong to that tenant.
# So we need to specify tenant_id when calling network_list().
networks += network_list(request, tenant_id=tenant_id,
shared=False, **params)
if include_external:
if shared in (None, True):
# In the current Neutron API, there is no way to retrieve
# both owner networks and public networks in a single API call.
networks += network_list(request, shared=True, **params)
params['router:external'] = params.get('router:external', True)
if params['router:external'] and include_external:
if shared is not None:
params['shared'] = shared
fetched_net_ids = [n.id for n in networks]
ext_nets = network_list(request, **{'router:external': True})
networks += [n for n in ext_nets if n.id not in fetched_net_ids]
# Retrieves external networks when router:external is not specified
# in (filtering) params or router:external=True filter is specified.
# When router:external=False is specified there is no need to query
# networking API because apparently nothing will match the filter.
ext_nets = network_list(request, **params)
networks += [n for n in ext_nets if
n.id not in fetched_net_ids]
return networks

View File

@ -84,6 +84,12 @@ DISPLAY_CHOICES = (
)
class AdminNetworksFilterAction(project_tables.ProjectNetworksFilterAction):
name = "filter_admin_networks"
filter_choices = (('project', _("Project ="), True),) +\
project_tables.ProjectNetworksFilterAction.filter_choices
class NetworksTable(tables.DataTable):
tenant = tables.Column("tenant_name", verbose_name=_("Project"))
name = tables.WrappingColumn("name_or_id", verbose_name=_("Network Name"),
@ -108,7 +114,7 @@ class NetworksTable(tables.DataTable):
name = "networks"
verbose_name = _("Networks")
table_actions = (CreateNetwork, DeleteNetwork,
project_tables.NetworksFilterAction)
AdminNetworksFilterAction)
row_actions = (EditNetwork, DeleteNetwork)
def __init__(self, request, data=None, needs_form_wrapper=None, **kwargs):

View File

@ -44,6 +44,9 @@ class IndexView(tables.DataTableView):
table_class = networks_tables.NetworksTable
template_name = 'admin/networks/index.html'
page_title = _("Networks")
FILTERS_MAPPING = {'shared': {_("yes"): True, _("no"): False},
'router:external': {_("yes"): True, _("no"): False},
'admin_state_up': {_("up"): True, _("down"): False}}
@memoized.memoized_method
def _get_tenant_list(self):
@ -78,7 +81,8 @@ class IndexView(tables.DataTableView):
def get_data(self):
try:
networks = api.neutron.network_list(self.request)
search_opts = self.get_filters(filters_map=self.FILTERS_MAPPING)
networks = api.neutron.network_list(self.request, **search_opts)
except Exception:
networks = []
msg = _('Network list can not be retrieved.')
@ -93,6 +97,18 @@ class IndexView(tables.DataTableView):
n.num_agents = self._get_agents_data(n.id)
return networks
def get_filters(self, filters=None, filters_map=None):
filters = super(IndexView, self).get_filters(filters, filters_map)
if 'project' in filters:
tenants = api.keystone.tenant_list(self.request)[0]
tenant_filter_ids = [t.id for t in tenants
if t.name == filters['project']]
if not tenant_filter_ids:
return []
del filters['project']
filters['tenant_id'] = tenant_filter_ids
return filters
class CreateView(forms.ModalFormView):
form_class = project_forms.CreateNetwork

View File

@ -158,13 +158,17 @@ STATUS_DISPLAY_CHOICES = (
)
class NetworksFilterAction(tables.FilterAction):
def filter(self, table, networks, filter_string):
"""Naive case-insensitive search."""
query = filter_string.lower()
return [network for network in networks
if query in network.name.lower()]
class ProjectNetworksFilterAction(tables.FilterAction):
name = "filter_project_networks"
filter_type = "server"
filter_choices = (('name', _("Name ="), True),
('shared', _("Shared ="), True,
_("e.g. Yes / No")),
('router:external', _("External ="), True,
_("e.g. Yes / No")),
('status', _("Status ="), True),
('admin_state_up', _("Admin State ="), True,
_("e.g. UP / DOWN")))
class NetworksTable(tables.DataTable):
@ -187,5 +191,5 @@ class NetworksTable(tables.DataTable):
name = "networks"
verbose_name = _("Networks")
table_actions = (CreateNetwork, DeleteNetwork,
NetworksFilterAction)
ProjectNetworksFilterAction)
row_actions = (EditNetwork, CreateSubnet, DeleteNetwork)

View File

@ -44,12 +44,16 @@ class IndexView(tables.DataTableView):
table_class = project_tables.NetworksTable
template_name = 'project/networks/index.html'
page_title = _("Networks")
FILTERS_MAPPING = {'shared': {_("yes"): True, _("no"): False},
'router:external': {_("yes"): True, _("no"): False},
'admin_state_up': {_("up"): True, _("down"): False}}
def get_data(self):
try:
tenant_id = self.request.user.tenant_id
search_opts = self.get_filters(filters_map=self.FILTERS_MAPPING)
networks = api.neutron.network_list_for_tenant(
self.request, tenant_id, include_external=True)
self.request, tenant_id, include_external=True, **search_opts)
except Exception:
networks = []
msg = _('Network list can not be retrieved.')

View File

@ -42,26 +42,44 @@ class NeutronApiTests(test.APITestCase):
@test.create_stubs({api.neutron: ('network_list',
'subnet_list')})
def _test_network_list_for_tenant(self, include_external):
def _test_network_list_for_tenant(
self, include_external,
filter_params, should_called):
"""Convenient method to test network_list_for_tenant.
:param include_external: Passed to network_list_for_tenant.
:param filter_params: Filters passed to network_list_for_tenant
:param should_called: this argument specifies which methods
should be called. Methods in this list should be called.
Valid values are non_shared, shared, and external.
"""
filter_params = filter_params or {}
all_networks = self.networks.list()
tenant_id = '1'
api.neutron.network_list(
IsA(http.HttpRequest),
tenant_id=tenant_id,
shared=False).AndReturn([
network for network in all_networks
if network['tenant_id'] == tenant_id
])
api.neutron.network_list(
IsA(http.HttpRequest),
shared=True).AndReturn([
network for network in all_networks
if network.get('shared')
])
if include_external:
if 'non_shared' in should_called:
params = filter_params.copy()
params['shared'] = False
api.neutron.network_list(
IsA(http.HttpRequest),
**{'router:external': True}).AndReturn([
tenant_id=tenant_id,
**params).AndReturn([
network for network in all_networks
if network['tenant_id'] == tenant_id
])
if 'shared' in should_called:
params = filter_params.copy()
params['shared'] = True
api.neutron.network_list(
IsA(http.HttpRequest),
**params).AndReturn([
network for network in all_networks
if network.get('shared')
])
if 'external' in should_called:
params = filter_params.copy()
params['router:external'] = True
api.neutron.network_list(
IsA(http.HttpRequest), **params).AndReturn([
network for network in all_networks
if network.get('router:external')
])
@ -69,19 +87,74 @@ class NeutronApiTests(test.APITestCase):
ret_val = api.neutron.network_list_for_tenant(
self.request, tenant_id,
include_external=include_external)
include_external=include_external,
**filter_params)
expected = [n for n in all_networks
if (n['tenant_id'] == tenant_id or
n['shared'] or
(include_external and n['router:external']))]
if (('non_shared' in should_called and
n['tenant_id'] == tenant_id) or
('shared' in should_called and n['shared']) or
('external' in should_called and
include_external and n['router:external']))]
self.assertEqual(set(n.id for n in expected),
set(n.id for n in ret_val))
def test_network_list_for_tenant(self):
self._test_network_list_for_tenant(include_external=False)
self._test_network_list_for_tenant(
include_external=False, filter_params=None,
should_called=['non_shared', 'shared'])
def test_network_list_for_tenant_with_external(self):
self._test_network_list_for_tenant(include_external=True)
self._test_network_list_for_tenant(
include_external=True, filter_params=None,
should_called=['non_shared', 'shared', 'external'])
def test_network_list_for_tenant_with_filters_shared_false_wo_incext(self):
self._test_network_list_for_tenant(
include_external=False, filter_params={'shared': True},
should_called=['shared'])
def test_network_list_for_tenant_with_filters_shared_true_w_incext(self):
self._test_network_list_for_tenant(
include_external=True, filter_params={'shared': True},
should_called=['shared', 'external'])
def test_network_list_for_tenant_with_filters_ext_false_wo_incext(self):
self._test_network_list_for_tenant(
include_external=False, filter_params={'router:external': False},
should_called=['non_shared', 'shared'])
def test_network_list_for_tenant_with_filters_ext_true_wo_incext(self):
self._test_network_list_for_tenant(
include_external=False, filter_params={'router:external': True},
should_called=['non_shared', 'shared'])
def test_network_list_for_tenant_with_filters_ext_false_w_incext(self):
self._test_network_list_for_tenant(
include_external=True, filter_params={'router:external': False},
should_called=['non_shared', 'shared'])
def test_network_list_for_tenant_with_filters_ext_true_w_incext(self):
self._test_network_list_for_tenant(
include_external=True, filter_params={'router:external': True},
should_called=['non_shared', 'shared', 'external'])
def test_network_list_for_tenant_with_filters_both_shared_ext(self):
# To check 'shared' filter is specified in network_list
# to look up external networks.
self._test_network_list_for_tenant(
include_external=True,
filter_params={'router:external': True, 'shared': True},
should_called=['shared', 'external'])
def test_network_list_for_tenant_with_other_filters(self):
# To check filter parameters other than shared and
# router:external are passed as expected.
self._test_network_list_for_tenant(
include_external=True,
filter_params={'router:external': True, 'shared': False,
'foo': 'bar'},
should_called=['non_shared', 'external'])
def test_network_get(self):
network = {'network': self.api_networks.first()}