From 1a0e28410604d392c5969198e060f150b9450e05 Mon Sep 17 00:00:00 2001 From: jlopezgu Date: Mon, 13 Jun 2016 15:55:33 -0500 Subject: [PATCH] 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 Change-Id: Ie05f58a77b62b12dbf087395fbaf4ff0dc1f8562 --- openstack_dashboard/api/neutron.py | 36 ++++-- .../dashboards/admin/networks/tables.py | 8 +- .../dashboards/admin/networks/views.py | 18 ++- .../dashboards/project/networks/tables.py | 20 +-- .../dashboards/project/networks/views.py | 6 +- .../test/api_tests/neutron_tests.py | 117 ++++++++++++++---- 6 files changed, 161 insertions(+), 44 deletions(-) diff --git a/openstack_dashboard/api/neutron.py b/openstack_dashboard/api/neutron.py index 9bc0b1065e..0a68bdea68 100644 --- a/openstack_dashboard/api/neutron.py +++ b/openstack_dashboard/api/neutron.py @@ -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 diff --git a/openstack_dashboard/dashboards/admin/networks/tables.py b/openstack_dashboard/dashboards/admin/networks/tables.py index 8517b92f7f..5ff9517236 100644 --- a/openstack_dashboard/dashboards/admin/networks/tables.py +++ b/openstack_dashboard/dashboards/admin/networks/tables.py @@ -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): diff --git a/openstack_dashboard/dashboards/admin/networks/views.py b/openstack_dashboard/dashboards/admin/networks/views.py index 831030d822..bc5ee62e0e 100644 --- a/openstack_dashboard/dashboards/admin/networks/views.py +++ b/openstack_dashboard/dashboards/admin/networks/views.py @@ -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 diff --git a/openstack_dashboard/dashboards/project/networks/tables.py b/openstack_dashboard/dashboards/project/networks/tables.py index e3cd0de19d..b8f48302e0 100644 --- a/openstack_dashboard/dashboards/project/networks/tables.py +++ b/openstack_dashboard/dashboards/project/networks/tables.py @@ -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) diff --git a/openstack_dashboard/dashboards/project/networks/views.py b/openstack_dashboard/dashboards/project/networks/views.py index 2a74746ff2..e0bd082e72 100644 --- a/openstack_dashboard/dashboards/project/networks/views.py +++ b/openstack_dashboard/dashboards/project/networks/views.py @@ -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.') diff --git a/openstack_dashboard/test/api_tests/neutron_tests.py b/openstack_dashboard/test/api_tests/neutron_tests.py index f727a4a852..d5c7a618c8 100644 --- a/openstack_dashboard/test/api_tests/neutron_tests.py +++ b/openstack_dashboard/test/api_tests/neutron_tests.py @@ -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()}