Merge "Server-side filtering networks"

This commit is contained in:
Jenkins 2016-08-25 04:23:03 +00:00 committed by Gerrit Code Review
commit 3bd7852746
6 changed files with 161 additions and 44 deletions

View File

@ -643,20 +643,34 @@ def network_list_for_tenant(request, tenant_id, include_external=False,
LOG.debug("network_list_for_tenant(): tenant_id=%s, params=%s" LOG.debug("network_list_for_tenant(): tenant_id=%s, params=%s"
% (tenant_id, params)) % (tenant_id, params))
# If a user has admin role, network list returned by Neutron API networks = []
# contains networks that do not belong to that tenant. shared = params.get('shared')
# So we need to specify tenant_id when calling network_list(). if shared is not None:
networks = network_list(request, tenant_id=tenant_id, del params['shared']
shared=False, **params)
# In the current Neutron API, there is no way to retrieve if shared in (None, False):
# both owner networks and public networks in a single API call. # If a user has admin role, network list returned by Neutron API
networks += network_list(request, shared=True, **params) # 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] fetched_net_ids = [n.id for n in networks]
ext_nets = network_list(request, **{'router:external': True}) # Retrieves external networks when router:external is not specified
networks += [n for n in ext_nets if n.id not in fetched_net_ids] # 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 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): class NetworksTable(tables.DataTable):
tenant = tables.Column("tenant_name", verbose_name=_("Project")) tenant = tables.Column("tenant_name", verbose_name=_("Project"))
name = tables.WrappingColumn("name_or_id", verbose_name=_("Network Name"), name = tables.WrappingColumn("name_or_id", verbose_name=_("Network Name"),
@ -108,7 +114,7 @@ class NetworksTable(tables.DataTable):
name = "networks" name = "networks"
verbose_name = _("Networks") verbose_name = _("Networks")
table_actions = (CreateNetwork, DeleteNetwork, table_actions = (CreateNetwork, DeleteNetwork,
project_tables.NetworksFilterAction) AdminNetworksFilterAction)
row_actions = (EditNetwork, DeleteNetwork) row_actions = (EditNetwork, DeleteNetwork)
def __init__(self, request, data=None, needs_form_wrapper=None, **kwargs): 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 table_class = networks_tables.NetworksTable
template_name = 'admin/networks/index.html' template_name = 'admin/networks/index.html'
page_title = _("Networks") 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 @memoized.memoized_method
def _get_tenant_list(self): def _get_tenant_list(self):
@ -78,7 +81,8 @@ class IndexView(tables.DataTableView):
def get_data(self): def get_data(self):
try: 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: except Exception:
networks = [] networks = []
msg = _('Network list can not be retrieved.') msg = _('Network list can not be retrieved.')
@ -93,6 +97,18 @@ class IndexView(tables.DataTableView):
n.num_agents = self._get_agents_data(n.id) n.num_agents = self._get_agents_data(n.id)
return networks 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): class CreateView(forms.ModalFormView):
form_class = project_forms.CreateNetwork form_class = project_forms.CreateNetwork

View File

@ -158,13 +158,17 @@ STATUS_DISPLAY_CHOICES = (
) )
class NetworksFilterAction(tables.FilterAction): class ProjectNetworksFilterAction(tables.FilterAction):
name = "filter_project_networks"
def filter(self, table, networks, filter_string): filter_type = "server"
"""Naive case-insensitive search.""" filter_choices = (('name', _("Name ="), True),
query = filter_string.lower() ('shared', _("Shared ="), True,
return [network for network in networks _("e.g. Yes / No")),
if query in network.name.lower()] ('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): class NetworksTable(tables.DataTable):
@ -187,5 +191,5 @@ class NetworksTable(tables.DataTable):
name = "networks" name = "networks"
verbose_name = _("Networks") verbose_name = _("Networks")
table_actions = (CreateNetwork, DeleteNetwork, table_actions = (CreateNetwork, DeleteNetwork,
NetworksFilterAction) ProjectNetworksFilterAction)
row_actions = (EditNetwork, CreateSubnet, DeleteNetwork) row_actions = (EditNetwork, CreateSubnet, DeleteNetwork)

View File

@ -44,12 +44,16 @@ class IndexView(tables.DataTableView):
table_class = project_tables.NetworksTable table_class = project_tables.NetworksTable
template_name = 'project/networks/index.html' template_name = 'project/networks/index.html'
page_title = _("Networks") 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): def get_data(self):
try: try:
tenant_id = self.request.user.tenant_id tenant_id = self.request.user.tenant_id
search_opts = self.get_filters(filters_map=self.FILTERS_MAPPING)
networks = api.neutron.network_list_for_tenant( 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: except Exception:
networks = [] networks = []
msg = _('Network list can not be retrieved.') msg = _('Network list can not be retrieved.')

View File

@ -42,26 +42,44 @@ class NeutronApiTests(test.APITestCase):
@test.create_stubs({api.neutron: ('network_list', @test.create_stubs({api.neutron: ('network_list',
'subnet_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() all_networks = self.networks.list()
tenant_id = '1' tenant_id = '1'
api.neutron.network_list( if 'non_shared' in should_called:
IsA(http.HttpRequest), params = filter_params.copy()
tenant_id=tenant_id, params['shared'] = False
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:
api.neutron.network_list( api.neutron.network_list(
IsA(http.HttpRequest), 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 network for network in all_networks
if network.get('router:external') if network.get('router:external')
]) ])
@ -69,19 +87,74 @@ class NeutronApiTests(test.APITestCase):
ret_val = api.neutron.network_list_for_tenant( ret_val = api.neutron.network_list_for_tenant(
self.request, tenant_id, self.request, tenant_id,
include_external=include_external) include_external=include_external,
**filter_params)
expected = [n for n in all_networks expected = [n for n in all_networks
if (n['tenant_id'] == tenant_id or if (('non_shared' in should_called and
n['shared'] or n['tenant_id'] == tenant_id) or
(include_external and n['router:external']))] ('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), self.assertEqual(set(n.id for n in expected),
set(n.id for n in ret_val)) set(n.id for n in ret_val))
def test_network_list_for_tenant(self): 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): 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): def test_network_get(self):
network = {'network': self.api_networks.first()} network = {'network': self.api_networks.first()}