Merge "Server-side filtering networks"
This commit is contained in:
commit
3bd7852746
@ -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
|
||||||
|
|
||||||
|
@ -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):
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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.')
|
||||||
|
@ -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()}
|
||||||
|
Loading…
Reference in New Issue
Block a user