From 0b807ac1d0c76d768fe9b076b7fe4ffabc27fd64 Mon Sep 17 00:00:00 2001 From: Ankur Gupta Date: Mon, 22 Feb 2016 11:03:57 -0600 Subject: [PATCH] Add Network IP Availability Extension Network IP Availability has been merged in Neutron, and, as a result, a new extension is available [1]. There is a patch in Neutron for adding this api extension [2] and a patch for adding commands for its usage [3]. This change basically implements the new api that can be used to check network usage stats of all the networks or specific networks created by Neutron. This patch will add the API extensions to Horizon, and specifically, add IP usage stats to the subnets network table. Reference --------- [1] https://review.openstack.org/#/c/180803/ [2] https://review.openstack.org/#/c/212955/ [3] https://review.openstack.org/#/c/269926/ Partially Implements Blueprint: ip-capacity Change-Id: I4b88adedc8de975d4aca9aeeb5622ec3410686ec Co-Authored-By: Dariusz Smigiel --- openstack_dashboard/api/neutron.py | 6 ++ .../dashboards/admin/networks/agents/tests.py | 14 +++ .../dashboards/admin/networks/ports/tests.py | 16 ++++ .../admin/networks/subnets/tables.py | 25 ++++++ .../admin/networks/subnets/tests.py | 86 ++++++++++++++++++- .../dashboards/admin/networks/tables.py | 5 -- .../dashboards/admin/networks/tests.py | 39 +++++++++ .../dashboards/admin/networks/views.py | 44 ++++++++-- .../test/api_tests/neutron_tests.py | 29 +++++++ .../test/test_data/neutron_data.py | 26 ++++++ .../ip-availability-be217ba59cc02b40.yaml | 8 ++ 11 files changed, 287 insertions(+), 11 deletions(-) create mode 100644 releasenotes/notes/ip-availability-be217ba59cc02b40.yaml diff --git a/openstack_dashboard/api/neutron.py b/openstack_dashboard/api/neutron.py index 3ca9f77ea..e13281021 100644 --- a/openstack_dashboard/api/neutron.py +++ b/openstack_dashboard/api/neutron.py @@ -1064,6 +1064,12 @@ def list_l3_agent_hosting_router(request, router, **params): return [Agent(a) for a in agents['agents']] +def show_network_ip_availability(request, network_id): + ip_availability = neutronclient(request).show_network_ip_availability( + network_id) + return ip_availability + + def add_network_to_dhcp_agent(request, dhcp_agent, network_id): body = {'network_id': network_id} return neutronclient(request).add_network_to_dhcp_agent(dhcp_agent, body) diff --git a/openstack_dashboard/dashboards/admin/networks/agents/tests.py b/openstack_dashboard/dashboards/admin/networks/agents/tests.py index ef088c2e1..f335c8e23 100644 --- a/openstack_dashboard/dashboards/admin/networks/agents/tests.py +++ b/openstack_dashboard/dashboards/admin/networks/agents/tests.py @@ -109,6 +109,7 @@ class NetworkAgentTests(test.BaseAdminViewTests): @test.create_stubs({api.neutron: ('subnet_list', 'port_list', 'list_dhcp_agent_hosting_networks', + 'show_network_ip_availability', 'is_extension_supported', 'remove_network_from_dhcp_agent',)}) def test_agent_delete(self): @@ -123,9 +124,15 @@ class NetworkAgentTests(test.BaseAdminViewTests): .AndReturn([self.ports.first()]) api.neutron.remove_network_from_dhcp_agent(IsA(http.HttpRequest), agent_id, network_id) + api.neutron.is_extension_supported( + IsA(http.HttpRequest), + 'network-ip-availability').AndReturn(True) api.neutron.is_extension_supported(IsA(http.HttpRequest), 'mac-learning')\ .AndReturn(False) + api.neutron.is_extension_supported(IsA(http.HttpRequest), + 'network-ip-availability')\ + .AndReturn(True) api.neutron.is_extension_supported(IsA(http.HttpRequest), 'dhcp_agent_scheduler')\ .AndReturn(True) @@ -140,6 +147,7 @@ class NetworkAgentTests(test.BaseAdminViewTests): @test.create_stubs({api.neutron: ('subnet_list', 'port_list', 'list_dhcp_agent_hosting_networks', + 'show_network_ip_availability', 'is_extension_supported', 'remove_network_from_dhcp_agent',)}) def test_agent_delete_exception(self): @@ -155,9 +163,15 @@ class NetworkAgentTests(test.BaseAdminViewTests): api.neutron.remove_network_from_dhcp_agent(IsA(http.HttpRequest), agent_id, network_id)\ .AndRaise(self.exceptions.neutron) + api.neutron.is_extension_supported( + IsA(http.HttpRequest), + 'network-ip-availability').AndReturn(True) api.neutron.is_extension_supported(IsA(http.HttpRequest), 'mac-learning')\ .AndReturn(False) + api.neutron.is_extension_supported(IsA(http.HttpRequest), + 'network-ip-availability')\ + .AndReturn(True) api.neutron.is_extension_supported(IsA(http.HttpRequest), 'dhcp_agent_scheduler')\ .AndReturn(True) diff --git a/openstack_dashboard/dashboards/admin/networks/ports/tests.py b/openstack_dashboard/dashboards/admin/networks/ports/tests.py index 8e1ee2fe4..44c426b67 100644 --- a/openstack_dashboard/dashboards/admin/networks/ports/tests.py +++ b/openstack_dashboard/dashboards/admin/networks/ports/tests.py @@ -376,6 +376,7 @@ class NetworkPortTests(test.BaseAdminViewTests): @test.create_stubs({api.neutron: ('port_delete', 'subnet_list', 'port_list', + 'show_network_ip_availability', 'is_extension_supported', 'list_dhcp_agent_hosting_networks',)}) def test_port_delete(self): @@ -384,6 +385,7 @@ class NetworkPortTests(test.BaseAdminViewTests): @test.create_stubs({api.neutron: ('port_delete', 'subnet_list', 'port_list', + 'show_network_ip_availability', 'is_extension_supported', 'list_dhcp_agent_hosting_networks',)}) def test_port_delete_with_mac_learning(self): @@ -400,9 +402,15 @@ class NetworkPortTests(test.BaseAdminViewTests): .AndReturn([self.subnets.first()]) api.neutron.port_list(IsA(http.HttpRequest), network_id=network_id)\ .AndReturn([self.ports.first()]) + api.neutron.is_extension_supported( + IsA(http.HttpRequest), + 'network-ip-availability').AndReturn(True) api.neutron.is_extension_supported(IsA(http.HttpRequest), 'mac-learning')\ .AndReturn(mac_learning) + api.neutron.is_extension_supported(IsA(http.HttpRequest), + 'network-ip-availability')\ + .AndReturn(True) api.neutron.is_extension_supported(IsA(http.HttpRequest), 'dhcp_agent_scheduler')\ .AndReturn(True) @@ -417,6 +425,7 @@ class NetworkPortTests(test.BaseAdminViewTests): @test.create_stubs({api.neutron: ('port_delete', 'subnet_list', 'port_list', + 'show_network_ip_availability', 'is_extension_supported', 'list_dhcp_agent_hosting_networks',)}) def test_port_delete_exception(self): @@ -425,6 +434,7 @@ class NetworkPortTests(test.BaseAdminViewTests): @test.create_stubs({api.neutron: ('port_delete', 'subnet_list', 'port_list', + 'show_network_ip_availability', 'is_extension_supported', 'list_dhcp_agent_hosting_networks')}) def test_port_delete_exception_with_mac_learning(self): @@ -442,9 +452,15 @@ class NetworkPortTests(test.BaseAdminViewTests): .AndReturn([self.subnets.first()]) api.neutron.port_list(IsA(http.HttpRequest), network_id=network_id)\ .AndReturn([self.ports.first()]) + api.neutron.is_extension_supported( + IsA(http.HttpRequest), + 'network-ip-availability').AndReturn(True) api.neutron.is_extension_supported(IsA(http.HttpRequest), 'mac-learning')\ .AndReturn(mac_learning) + api.neutron.is_extension_supported(IsA(http.HttpRequest), + 'network-ip-availability')\ + .AndReturn(True) api.neutron.is_extension_supported(IsA(http.HttpRequest), 'dhcp_agent_scheduler')\ .AndReturn(True) diff --git a/openstack_dashboard/dashboards/admin/networks/subnets/tables.py b/openstack_dashboard/dashboards/admin/networks/subnets/tables.py index d0f91486f..bfe37aefa 100644 --- a/openstack_dashboard/dashboards/admin/networks/subnets/tables.py +++ b/openstack_dashboard/dashboards/admin/networks/subnets/tables.py @@ -88,12 +88,27 @@ class UpdateSubnet(proj_tables.SubnetPolicyTargetMixin, tables.LinkAction): return reverse(self.url, args=(network_id, subnet.id)) +def subnet_ip_availability(availability): + subnet_availability = availability.get("free_ips") + if subnet_availability: + if subnet_availability > 10000: + return ">10000" + else: + return str(subnet_availability) + else: + return str("Not Available") + + class SubnetsTable(tables.DataTable): name = tables.Column("name_or_id", verbose_name=_("Name"), link='horizon:admin:networks:subnets:detail') cidr = tables.Column("cidr", verbose_name=_("CIDR")) ip_version = tables.Column("ipver_str", verbose_name=_("IP Version")) gateway_ip = tables.Column("gateway_ip", verbose_name=_("Gateway IP")) + subnet_used_ips = tables.Column("used_ips", + verbose_name=_("Used IPs")) + subnet_free_ips = tables.Column(subnet_ip_availability, + verbose_name=_("Free IPs")) failure_url = reverse_lazy('horizon:admin:networks:index') def get_object_display(self, subnet): @@ -117,3 +132,13 @@ class SubnetsTable(tables.DataTable): table_actions = (CreateSubnet, DeleteSubnet) row_actions = (UpdateSubnet, DeleteSubnet,) hidden_title = False + + def __init__(self, request, data=None, needs_form_wrapper=None, **kwargs): + super(SubnetsTable, self).__init__( + request, data=data, + needs_form_wrapper=needs_form_wrapper, + **kwargs) + if not api.neutron.is_extension_supported(request, + 'network-ip-availability'): + del self.columns['subnet_used_ips'] + del self.columns['subnet_free_ips'] diff --git a/openstack_dashboard/dashboards/admin/networks/subnets/tests.py b/openstack_dashboard/dashboards/admin/networks/subnets/tests.py index 43d97945c..ff814ef76 100644 --- a/openstack_dashboard/dashboards/admin/networks/subnets/tests.py +++ b/openstack_dashboard/dashboards/admin/networks/subnets/tests.py @@ -31,7 +31,9 @@ NETWORKS_DETAIL_URL = 'horizon:admin:networks:detail' class NetworkSubnetTests(test.BaseAdminViewTests): - @test.create_stubs({api.neutron: ('network_get', 'subnet_get',)}) + @test.create_stubs({api.neutron: ('network_get', + 'subnet_get', + 'is_extension_supported')}) def test_subnet_detail(self): network = self.networks.first() subnet = self.subnets.first() @@ -41,6 +43,10 @@ class NetworkSubnetTests(test.BaseAdminViewTests): api.neutron.subnet_get(IsA(http.HttpRequest), subnet.id)\ .AndReturn(subnet) + api.neutron.is_extension_supported( + IsA(http.HttpRequest), + 'network-ip-availability').AndReturn(True) + self.mox.ReplayAll() url = reverse(DETAIL_URL, args=[subnet.id]) @@ -292,6 +298,7 @@ class NetworkSubnetTests(test.BaseAdminViewTests): 'subnet_list', 'port_list', 'is_extension_supported', + 'show_network_ip_availability', 'list_dhcp_agent_hosting_networks',)}) def test_subnet_delete(self): self._test_subnet_delete() @@ -300,6 +307,7 @@ class NetworkSubnetTests(test.BaseAdminViewTests): 'subnet_list', 'port_list', 'is_extension_supported', + 'show_network_ip_availability', 'list_dhcp_agent_hosting_networks',)}) def test_subnet_delete_with_mac_learning(self): self._test_subnet_delete(mac_learning=True) @@ -307,6 +315,10 @@ class NetworkSubnetTests(test.BaseAdminViewTests): def _test_subnet_delete(self, mac_learning=False): subnet = self.subnets.first() network_id = subnet.network_id + ip_availability = self.ip_availability.get() + api.neutron.show_network_ip_availability(IsA(http.HttpRequest), + network_id).\ + MultipleTimes().AndReturn(ip_availability) api.neutron.list_dhcp_agent_hosting_networks(IsA(http.HttpRequest), network_id).\ AndReturn(self.agents.list()) @@ -315,9 +327,15 @@ class NetworkSubnetTests(test.BaseAdminViewTests): .AndReturn([self.subnets.first()]) api.neutron.port_list(IsA(http.HttpRequest), network_id=network_id)\ .AndReturn([self.ports.first()]) + api.neutron.is_extension_supported( + IsA(http.HttpRequest), + 'network-ip-availability').AndReturn(True) api.neutron.is_extension_supported(IsA(http.HttpRequest), 'mac-learning')\ .AndReturn(mac_learning) + api.neutron.is_extension_supported(IsA(http.HttpRequest), + 'network-ip-availability')\ + .MultipleTimes().AndReturn(True) api.neutron.is_extension_supported(IsA(http.HttpRequest), 'dhcp_agent_scheduler')\ .AndReturn(True) @@ -333,6 +351,7 @@ class NetworkSubnetTests(test.BaseAdminViewTests): 'subnet_list', 'port_list', 'is_extension_supported', + 'show_network_ip_availability', 'list_dhcp_agent_hosting_networks',)}) def test_subnet_delete_exception(self): self._test_subnet_delete_exception() @@ -341,6 +360,7 @@ class NetworkSubnetTests(test.BaseAdminViewTests): 'subnet_list', 'port_list', 'is_extension_supported', + 'show_network_ip_availability', 'list_dhcp_agent_hosting_networks',)}) def test_subnet_delete_exception_with_mac_learning(self): self._test_subnet_delete_exception(mac_learning=True) @@ -348,6 +368,10 @@ class NetworkSubnetTests(test.BaseAdminViewTests): def _test_subnet_delete_exception(self, mac_learning=False): subnet = self.subnets.first() network_id = subnet.network_id + ip_availability = self.ip_availability.get() + api.neutron.show_network_ip_availability(IsA(http.HttpRequest), + network_id).\ + MultipleTimes().AndReturn(ip_availability) api.neutron.list_dhcp_agent_hosting_networks(IsA(http.HttpRequest), network_id).\ AndReturn(self.agents.list()) @@ -357,9 +381,15 @@ class NetworkSubnetTests(test.BaseAdminViewTests): .AndReturn([self.subnets.first()]) api.neutron.port_list(IsA(http.HttpRequest), network_id=network_id)\ .AndReturn([self.ports.first()]) + api.neutron.is_extension_supported( + IsA(http.HttpRequest), + 'network-ip-availability').AndReturn(True) api.neutron.is_extension_supported(IsA(http.HttpRequest), 'mac-learning')\ .AndReturn(mac_learning) + api.neutron.is_extension_supported(IsA(http.HttpRequest), + 'network-ip-availability')\ + .MultipleTimes().AndReturn(True) api.neutron.is_extension_supported(IsA(http.HttpRequest), 'dhcp_agent_scheduler')\ .AndReturn(True) @@ -370,3 +400,57 @@ class NetworkSubnetTests(test.BaseAdminViewTests): res = self.client.post(url, form_data) self.assertRedirectsNoFollow(res, url) + + @test.create_stubs({api.neutron: ('network_get', + 'subnet_list', + 'port_list', + 'is_extension_supported', + 'show_network_ip_availability', + 'list_dhcp_agent_hosting_networks',)}) + def test_network_detail_ip_availability_exception(self): + self._test_network_detail_ip_availability_exception() + + @test.create_stubs({api.neutron: ('network_get', + 'subnet_list', + 'port_list', + 'is_extension_supported', + 'show_network_ip_availability', + 'list_dhcp_agent_hosting_networks',)}) + def test_network_detail_ip_availability_exception_with_mac_learning(self): + self._test_network_detail_ip_availability_exception(mac_learning=True) + + def _test_network_detail_ip_availability_exception(self, + mac_learning=False): + network_id = self.networks.first().id + api.neutron.show_network_ip_availability(IsA(http.HttpRequest), + network_id).\ + MultipleTimes().AndRaise(self.exceptions.neutron) + api.neutron.list_dhcp_agent_hosting_networks(IsA(http.HttpRequest), + network_id).\ + AndReturn(self.agents.list()) + api.neutron.network_get(IsA(http.HttpRequest), network_id).\ + AndReturn(self.networks.first()) + api.neutron.subnet_list(IsA(http.HttpRequest), network_id=network_id).\ + AndReturn([self.subnets.first()]) + api.neutron.port_list(IsA(http.HttpRequest), network_id=network_id).\ + AndReturn([self.ports.first()]) + api.neutron.is_extension_supported( + IsA(http.HttpRequest), + 'network-ip-availability').AndReturn(True) + api.neutron.is_extension_supported(IsA(http.HttpRequest), + 'mac-learning')\ + .AndReturn(mac_learning) + api.neutron.is_extension_supported( + IsA(http.HttpRequest), + 'network-ip-availability').AndReturn(True) + api.neutron.is_extension_supported(IsA(http.HttpRequest), + 'dhcp_agent_scheduler')\ + .MultipleTimes().AndReturn(True) + self.mox.ReplayAll() + + res = self.client.get(reverse('horizon:admin:networks:detail', + args=[network_id])) + + self.assertTemplateUsed(res, 'project/networks/detail.html') + subnets = res.context['subnets_table'].data + self.assertItemsEqual(subnets, [self.subnets.first()]) diff --git a/openstack_dashboard/dashboards/admin/networks/tables.py b/openstack_dashboard/dashboards/admin/networks/tables.py index f80ac7b52..b7707af77 100644 --- a/openstack_dashboard/dashboards/admin/networks/tables.py +++ b/openstack_dashboard/dashboards/admin/networks/tables.py @@ -78,11 +78,6 @@ class EditNetwork(policy.PolicyTargetMixin, tables.LinkAction): policy_rules = (("network", "update_network"),) -# def _get_subnets(network): -# cidrs = [subnet.get('cidr') for subnet in network.subnets] -# return ','.join(cidrs) - - DISPLAY_CHOICES = ( ("up", pgettext_lazy("Admin state of a Network", u"UP")), ("down", pgettext_lazy("Admin state of a Network", u"DOWN")), diff --git a/openstack_dashboard/dashboards/admin/networks/tests.py b/openstack_dashboard/dashboards/admin/networks/tests.py index c316e79be..e40b1f544 100644 --- a/openstack_dashboard/dashboards/admin/networks/tests.py +++ b/openstack_dashboard/dashboards/admin/networks/tests.py @@ -74,6 +74,7 @@ class NetworkTests(test.BaseAdminViewTests): @test.create_stubs({api.neutron: ('network_get', 'subnet_list', 'port_list', + 'show_network_ip_availability', 'list_dhcp_agent_hosting_networks', 'is_extension_supported')}) def test_network_detail(self): @@ -83,12 +84,17 @@ class NetworkTests(test.BaseAdminViewTests): 'subnet_list', 'port_list', 'is_extension_supported', + 'show_network_ip_availability', 'list_dhcp_agent_hosting_networks',)}) def test_network_detail_with_mac_learning(self): self._test_network_detail(mac_learning=True) def _test_network_detail(self, mac_learning=False): network_id = self.networks.first().id + ip_availability = self.ip_availability.get() + api.neutron.show_network_ip_availability(IsA(http.HttpRequest), + network_id).\ + MultipleTimes().AndReturn(ip_availability) api.neutron.list_dhcp_agent_hosting_networks(IsA(http.HttpRequest), network_id).\ AndReturn(self.agents.list()) @@ -98,9 +104,15 @@ class NetworkTests(test.BaseAdminViewTests): .AndReturn([self.subnets.first()]) api.neutron.port_list(IsA(http.HttpRequest), network_id=network_id)\ .AndReturn([self.ports.first()]) + api.neutron.is_extension_supported( + IsA(http.HttpRequest), + 'network-ip-availability').AndReturn(True) api.neutron.is_extension_supported(IsA(http.HttpRequest), 'mac-learning')\ .AndReturn(mac_learning) + api.neutron.is_extension_supported( + IsA(http.HttpRequest), + 'network-ip-availability').AndReturn(True) api.neutron.is_extension_supported( IsA(http.HttpRequest), 'dhcp_agent_scheduler').AndReturn(True) @@ -126,6 +138,7 @@ class NetworkTests(test.BaseAdminViewTests): 'subnet_list', 'port_list', 'is_extension_supported', + 'show_network_ip_availability', 'list_dhcp_agent_hosting_networks',)}) def test_network_detail_network_exception(self): self._test_network_detail_network_exception() @@ -134,12 +147,17 @@ class NetworkTests(test.BaseAdminViewTests): 'subnet_list', 'port_list', 'is_extension_supported', + 'show_network_ip_availability', 'list_dhcp_agent_hosting_networks',)}) def test_network_detail_network_exception_with_mac_learning(self): self._test_network_detail_network_exception(mac_learning=True) def _test_network_detail_network_exception(self, mac_learning=False): network_id = self.networks.first().id + ip_availability = self.ip_availability.get() + api.neutron.show_network_ip_availability(IsA(http.HttpRequest), + network_id).\ + MultipleTimes().AndReturn(ip_availability) api.neutron.network_get(IsA(http.HttpRequest), network_id)\ .AndRaise(self.exceptions.neutron) api.neutron.subnet_list(IsA(http.HttpRequest), network_id=network_id)\ @@ -149,9 +167,15 @@ class NetworkTests(test.BaseAdminViewTests): api.neutron.list_dhcp_agent_hosting_networks(IsA(http.HttpRequest), network_id).\ AndReturn(self.agents.list()) + api.neutron.is_extension_supported( + IsA(http.HttpRequest), + 'network-ip-availability').AndReturn(True) api.neutron.is_extension_supported(IsA(http.HttpRequest), 'mac-learning')\ .AndReturn(mac_learning) + api.neutron.is_extension_supported( + IsA(http.HttpRequest), + 'network-ip-availability').AndReturn(True) api.neutron.is_extension_supported( IsA(http.HttpRequest), 'dhcp_agent_scheduler').AndReturn(True) @@ -191,6 +215,9 @@ class NetworkTests(test.BaseAdminViewTests): AndRaise(self.exceptions.neutron) api.neutron.port_list(IsA(http.HttpRequest), network_id=network_id).\ AndReturn([self.ports.first()]) + api.neutron.is_extension_supported( + IsA(http.HttpRequest), + 'network-ip-availability').AndReturn(True) api.neutron.is_extension_supported(IsA(http.HttpRequest), 'mac-learning')\ .AndReturn(mac_learning) @@ -219,6 +246,7 @@ class NetworkTests(test.BaseAdminViewTests): 'subnet_list', 'port_list', 'is_extension_supported', + 'show_network_ip_availability', 'list_dhcp_agent_hosting_networks',)}) def test_network_detail_port_exception(self): self._test_network_detail_port_exception() @@ -227,12 +255,17 @@ class NetworkTests(test.BaseAdminViewTests): 'subnet_list', 'port_list', 'is_extension_supported', + 'show_network_ip_availability', 'list_dhcp_agent_hosting_networks',)}) def test_network_detail_port_exception_with_mac_learning(self): self._test_network_detail_port_exception(mac_learning=True) def _test_network_detail_port_exception(self, mac_learning=False): network_id = self.networks.first().id + ip_availability = self.ip_availability.get() + api.neutron.show_network_ip_availability(IsA(http.HttpRequest), + network_id).\ + MultipleTimes().AndReturn(ip_availability) api.neutron.list_dhcp_agent_hosting_networks(IsA(http.HttpRequest), network_id).\ AndReturn(self.agents.list()) @@ -242,9 +275,15 @@ class NetworkTests(test.BaseAdminViewTests): AndReturn([self.subnets.first()]) api.neutron.port_list(IsA(http.HttpRequest), network_id=network_id).\ AndRaise(self.exceptions.neutron) + api.neutron.is_extension_supported( + IsA(http.HttpRequest), + 'network-ip-availability').AndReturn(True) api.neutron.is_extension_supported(IsA(http.HttpRequest), 'mac-learning')\ .AndReturn(mac_learning) + api.neutron.is_extension_supported( + IsA(http.HttpRequest), + 'network-ip-availability').AndReturn(True) api.neutron.is_extension_supported(IsA(http.HttpRequest), 'dhcp_agent_scheduler')\ .AndReturn(True) diff --git a/openstack_dashboard/dashboards/admin/networks/views.py b/openstack_dashboard/dashboards/admin/networks/views.py index f11c739a3..8b3e8a255 100644 --- a/openstack_dashboard/dashboards/admin/networks/views.py +++ b/openstack_dashboard/dashboards/admin/networks/views.py @@ -59,6 +59,7 @@ class IndexView(tables.DataTableView): def _get_agents_data(self, network): agents = [] data = _("Unknown") + try: if api.neutron.is_extension_supported(self.request, 'dhcp_agent_scheduler'): @@ -69,7 +70,8 @@ class IndexView(tables.DataTableView): self.request, network) data = len(agents) except Exception: - self.exception = True + msg = _('Unable to list dhcp agents hosting network.') + exceptions.handle(self.request, msg) return data def get_data(self): @@ -87,10 +89,6 @@ class IndexView(tables.DataTableView): tenant = tenant_dict.get(n.tenant_id, None) n.tenant_name = getattr(tenant, 'name', None) n.num_agents = self._get_agents_data(n.id) - - if self.exception: - msg = _('Unable to list dhcp agents hosting network.') - exceptions.handle(self.request, msg) return networks @@ -108,11 +106,47 @@ class DetailView(tables.MultiTableView): template_name = 'project/networks/detail.html' page_title = '{{ network.name | default:network.id }}' + def _get_subnet_availability(self, network_id): + subnet_availabilities_list = {} + try: + availability = api.neutron.\ + show_network_ip_availability(self.request, network_id) + availabilities = availability.get("network_ip_availability", + {}) + subnet_availabilities_list = availabilities.\ + get("subnet_ip_availability", []) + except Exception: + msg = _("Unable to retrieve IP availability.") + exceptions.handle(self.request, msg) + return subnet_availabilities_list + + def _add_subnet_availability(self, subnet_usage_list, subnets_dict): + try: + for subnet_usage in subnet_usage_list: + subnet_id = subnet_usage.get("subnet_id") + subnet_used_ips = subnet_usage.get("used_ips") + subnet_total_ips = subnet_usage.get("total_ips") + subnet_free_ips = subnet_total_ips - subnet_used_ips + for item in subnets_dict: + id = item.get("id") + if id == subnet_id: + item._apidict.update({"used_ips": subnet_used_ips}) + item._apidict.update({"free_ips": subnet_free_ips}) + except Exception: + msg = _("Unable to update subnets with availability.") + exceptions.handle(self.request, msg) + return subnets_dict + def get_subnets_data(self): try: network_id = self.kwargs['network_id'] subnets = api.neutron.subnet_list(self.request, network_id=network_id) + if api.neutron.is_extension_supported(self.request, + 'network-ip-availability'): + subnets_list = self._get_subnet_availability(network_id) + subnets = self._add_subnet_availability(subnets_list, subnets) + except Exception: subnets = [] msg = _('Subnet 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 a14a20701..51838dd24 100644 --- a/openstack_dashboard/test/api_tests/neutron_tests.py +++ b/openstack_dashboard/test/api_tests/neutron_tests.py @@ -155,6 +155,35 @@ class NeutronApiTests(test.APITestCase): api.neutron.network_delete(self.request, network_id) + def test_get_network_ip_availability(self): + network = {'network': self.api_networks.first()} + mock_ip_availability = self.ip_availability.get() + neutronclient = self.stub_neutronclient() + neutronclient.show_network_ip_availability(network).\ + AndReturn(mock_ip_availability) + + self.mox.ReplayAll() + ret_val = api.neutron.show_network_ip_availability(self.request, + network) + + self.assertIsInstance(ret_val, dict) + + def test_subnet_network_ip_availability(self): + network = {'network': self.api_networks.first()} + mock_ip_availability = self.ip_availability.get() + neutronclient = self.stub_neutronclient() + neutronclient.show_network_ip_availability(network).\ + AndReturn(mock_ip_availability) + + self.mox.ReplayAll() + ip_availability = api.neutron. \ + show_network_ip_availability(self.request, network) + availabilities = ip_availability.get("network_ip_availability", + {}) + ret_val = availabilities.get("subnet_ip_availability", []) + + self.assertIsInstance(ret_val, list) + def test_subnet_list(self): subnets = {'subnets': self.api_subnets.list()} diff --git a/openstack_dashboard/test/test_data/neutron_data.py b/openstack_dashboard/test/test_data/neutron_data.py index 743d4db0c..b192db11f 100644 --- a/openstack_dashboard/test/test_data/neutron_data.py +++ b/openstack_dashboard/test/test_data/neutron_data.py @@ -55,6 +55,7 @@ def data(TEST): TEST.firewalls = utils.TestDataContainer() TEST.fw_policies = utils.TestDataContainer() TEST.fw_rules = utils.TestDataContainer() + TEST.ip_availability = utils.TestDataContainer() # Data return by neutronclient. TEST.api_agents = utils.TestDataContainer() @@ -83,6 +84,7 @@ def data(TEST): TEST.api_firewalls = utils.TestDataContainer() TEST.api_fw_policies = utils.TestDataContainer() TEST.api_fw_rules = utils.TestDataContainer() + TEST.api_ip_availability = utils.TestDataContainer() # 1st network. network_dict = {'admin_state_up': True, @@ -1317,3 +1319,27 @@ def data(TEST): 'binding:host_id': 'host'} TEST.api_ports.add(port_dict) TEST.ports.add(neutron.Port(port_dict)) + + availability = {'network_ip_availability': { + 'used_ips': 2, + 'subnet_ip_availability': [{ + 'used_ips': 1, + 'subnet_id': '2c90f321-9cc7-41b4-a3cf-88110f120a94', + 'subnet_name': 'ipv6-public-subnet', + 'ip_version': 6, + 'cidr': '2001:db8::/64', + 'total_ips': 18446744073709551614}, + {'used_ips': 1, + 'subnet_id': '4d77d5fb-c26c-4ac5-b2ca-fca2f89b0fc1', + 'subnet_name': 'public-subnet', + 'ip_version': 4, + 'cidr': '172.24.4.0/24', + 'total_ips': 253}], + 'network_id': 'd87d5be5-cfca-486f-8db5-a446330e4513', + 'tenant_id': 'd564b2a4fc0544fb89f8a0434dd96863', + 'network_name': 'public', + 'total_ips': 18446744073709551867} + } + + TEST.ip_availability.add(availability) + TEST.api_ip_availability.add(availability) diff --git a/releasenotes/notes/ip-availability-be217ba59cc02b40.yaml b/releasenotes/notes/ip-availability-be217ba59cc02b40.yaml new file mode 100644 index 000000000..1ff9973df --- /dev/null +++ b/releasenotes/notes/ip-availability-be217ba59cc02b40.yaml @@ -0,0 +1,8 @@ +--- +features: + - Horizon support for network IP availability feature. + Enable Horizon admin network dashboard to be able to + display IP availability. Enables 2 columns in the + admin network subnets table to display the allocated + IPs in a given subnet and unallocated free IPs for + each subnet in the network.