diff --git a/openstack_dashboard/api/network.py b/openstack_dashboard/api/network.py
index 015bf3f4c6..7389531b43 100644
--- a/openstack_dashboard/api/network.py
+++ b/openstack_dashboard/api/network.py
@@ -50,16 +50,18 @@ def floating_ip_pools_list(request):
return NetworkClient(request).floating_ips.list_pools()
-def tenant_floating_ip_list(request):
- return NetworkClient(request).floating_ips.list()
+def tenant_floating_ip_list(request, all_tenants=False):
+ return NetworkClient(request).floating_ips.list(all_tenants=all_tenants)
def tenant_floating_ip_get(request, floating_ip_id):
return NetworkClient(request).floating_ips.get(floating_ip_id)
-def tenant_floating_ip_allocate(request, pool=None):
- return NetworkClient(request).floating_ips.allocate(pool)
+def tenant_floating_ip_allocate(request, pool=None, tenant_id=None, **params):
+ return NetworkClient(request).floating_ips.allocate(pool,
+ tenant_id,
+ **params)
def tenant_floating_ip_release(request, floating_ip_id):
diff --git a/openstack_dashboard/api/network_base.py b/openstack_dashboard/api/network_base.py
index af4a28fd82..f6d4046cde 100644
--- a/openstack_dashboard/api/network_base.py
+++ b/openstack_dashboard/api/network_base.py
@@ -51,8 +51,8 @@ class FloatingIpManager(object):
pass
@abc.abstractmethod
- def list(self):
- """Fetches a list all floating IPs.
+ def list(self, all_tenants=False):
+ """Fetches a list of all floating IPs.
A returned value is a list of FloatingIp object.
"""
@@ -67,7 +67,7 @@ class FloatingIpManager(object):
pass
@abc.abstractmethod
- def allocate(self, pool=None):
+ def allocate(self, pool=None, tenant_id=None, **params):
"""Allocates a floating IP to the tenant.
You must provide a pool name or id for which you would like to
diff --git a/openstack_dashboard/api/neutron.py b/openstack_dashboard/api/neutron.py
index 9bc0b1065e..ce07242650 100644
--- a/openstack_dashboard/api/neutron.py
+++ b/openstack_dashboard/api/neutron.py
@@ -417,10 +417,15 @@ class FloatingIpManager(network_base.FloatingIpManager):
self._set_instance_info(fip)
return FloatingIp(fip)
- def allocate(self, pool):
- body = {'floatingip': {'floating_network_id': pool,
- 'tenant_id': self.request.user.project_id}}
- fip = self.client.create_floatingip(body).get('floatingip')
+ def allocate(self, pool, tenant_id=None, **params):
+ if not tenant_id:
+ tenant_id = self.request.user.project_id
+ create_dict = {'floating_network_id': pool,
+ 'tenant_id': tenant_id}
+ if 'floating_ip_address' in params:
+ create_dict['floating_ip_address'] = params['floating_ip_address']
+ fip = self.client.create_floatingip(
+ {'floatingip': create_dict}).get('floatingip')
self._set_instance_info(fip)
return FloatingIp(fip)
diff --git a/openstack_dashboard/api/nova.py b/openstack_dashboard/api/nova.py
index 47da4d15d4..7ed9e63702 100644
--- a/openstack_dashboard/api/nova.py
+++ b/openstack_dashboard/api/nova.py
@@ -415,14 +415,16 @@ class FloatingIpManager(network_base.FloatingIpManager):
return [FloatingIpPool(pool)
for pool in self.client.floating_ip_pools.list()]
- def list(self):
- return [FloatingIp(fip)
- for fip in self.client.floating_ips.list()]
+ def list(self, all_tenants=False):
+ return [FloatingIp(fip) for fip in
+ self.client.floating_ips.list(
+ all_tenants=all_tenants)]
def get(self, floating_ip_id):
return FloatingIp(self.client.floating_ips.get(floating_ip_id))
- def allocate(self, pool):
+ def allocate(self, pool, tenant_id=None, **params):
+ # NOTE: tenant_id will never be used here.
return FloatingIp(self.client.floating_ips.create(pool=pool))
def release(self, floating_ip_id):
diff --git a/openstack_dashboard/dashboards/admin/floating_ips/__init__.py b/openstack_dashboard/dashboards/admin/floating_ips/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/openstack_dashboard/dashboards/admin/floating_ips/forms.py b/openstack_dashboard/dashboards/admin/floating_ips/forms.py
new file mode 100644
index 0000000000..8729d3f441
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/floating_ips/forms.py
@@ -0,0 +1,64 @@
+# Copyright 2016 Letv Cloud Computing
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from django.core.urlresolvers import reverse
+from django.utils.translation import ugettext_lazy as _
+
+from horizon import exceptions
+from horizon import forms
+from horizon import messages
+
+from openstack_dashboard import api
+
+
+class AdminFloatingIpAllocate(forms.SelfHandlingForm):
+ pool = forms.ChoiceField(label=_("Pool"))
+ tenant = forms.ChoiceField(label=_("Project"))
+ floating_ip_address = forms.IPField(
+ label=_("Floating IP Address (optional)"),
+ required=False,
+ initial="",
+ help_text=_("The IP address of the new floating IP (e.g. 202.2.3.4). "
+ "You need to specify an explicit address which is under "
+ "the public network CIDR (e.g. 202.2.3.0/24)."),
+ mask=False)
+
+ def __init__(self, *args, **kwargs):
+ super(AdminFloatingIpAllocate, self).__init__(*args, **kwargs)
+ floating_pool_list = kwargs.get('initial', {}).get('pool_list', [])
+ self.fields['pool'].choices = floating_pool_list
+ tenant_list = kwargs.get('initial', {}).get('tenant_list', [])
+ self.fields['tenant'].choices = tenant_list
+
+ def handle(self, request, data):
+ try:
+ # Admin ignore quota
+ param = {}
+ if data['floating_ip_address']:
+ param['floating_ip_address'] = data['floating_ip_address']
+ # TODO(liuyulong): use subnet id to allocate floating IP.
+ fip = api.network.tenant_floating_ip_allocate(
+ request,
+ pool=data['pool'],
+ tenant_id=data['tenant'],
+ **param)
+ messages.success(
+ request,
+ _('Allocated floating IP %(ip)s.') % {"ip": fip.ip})
+ return fip
+ except Exception:
+ redirect = reverse('horizon:admin:floating_ips:index')
+ msg = _('Unable to allocate floating IP.')
+ exceptions.handle(request, msg, redirect=redirect)
diff --git a/openstack_dashboard/dashboards/admin/floating_ips/panel.py b/openstack_dashboard/dashboards/admin/floating_ips/panel.py
new file mode 100644
index 0000000000..2503c795cd
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/floating_ips/panel.py
@@ -0,0 +1,30 @@
+# Copyright 2016 Letv Cloud Computing
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from django.conf import settings
+from django.utils.translation import ugettext_lazy as _
+
+import horizon
+
+
+class AdminFloatingIps(horizon.Panel):
+ name = _("Floating IPs")
+ slug = 'floating_ips'
+ permissions = ('openstack.services.network', )
+
+ @staticmethod
+ def can_register():
+ network_config = getattr(settings, 'OPENSTACK_NEUTRON_NETWORK', {})
+ return network_config.get('enable_router', True)
diff --git a/openstack_dashboard/dashboards/admin/floating_ips/tables.py b/openstack_dashboard/dashboards/admin/floating_ips/tables.py
new file mode 100644
index 0000000000..162277f5e2
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/floating_ips/tables.py
@@ -0,0 +1,91 @@
+# Copyright 2016 Letv Cloud Computing
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import logging
+
+from django import shortcuts
+from django.utils.translation import ugettext_lazy as _
+
+from horizon import exceptions
+from horizon import messages
+from horizon import tables
+
+from openstack_dashboard import api
+from openstack_dashboard import policy
+from openstack_dashboard.dashboards.project.access_and_security.\
+ floating_ips import tables as project_tables
+from openstack_dashboard.utils import filters
+
+
+LOG = logging.getLogger(__name__)
+
+
+class FloatingIPFilterAction(tables.FilterAction):
+
+ def filter(self, table, fips, filter_string):
+ """Naive case-insensitive search."""
+ q = filter_string.lower()
+ return [ip for ip in fips
+ if q in ip.ip.lower()]
+
+
+class AdminAllocateFloatingIP(project_tables.AllocateIP):
+ url = "horizon:admin:floating_ips:allocate"
+
+ def single(self, data_table, request, *args):
+ return shortcuts.redirect('horizon:admin:floating_ips:index')
+
+ def allowed(self, request, fip=None):
+ policy_rules = (("network", "create_floatingip"),)
+ return policy.check(policy_rules, request)
+
+
+class AdminReleaseFloatingIP(project_tables.ReleaseIPs):
+ pass
+
+
+class AdminSimpleDisassociateIP(project_tables.DisassociateIP):
+
+ def single(self, table, request, obj_id):
+ try:
+ fip = table.get_object_by_id(filters.get_int_or_uuid(obj_id))
+ api.network.floating_ip_disassociate(request, fip.id)
+ LOG.info('Disassociating Floating IP "%s".' % obj_id)
+ messages.success(request,
+ _('Successfully disassociated Floating IP: %s')
+ % fip.ip)
+ except Exception:
+ exceptions.handle(request,
+ _('Unable to disassociate floating IP.'))
+ return shortcuts.redirect('horizon:admin:floating_ips:index')
+
+
+class FloatingIPsTable(project_tables.FloatingIPsTable):
+ tenant = tables.Column("tenant_name", verbose_name=_("Project"))
+ ip = tables.Column("ip",
+ link=("horizon:admin:floating_ips:detail"),
+ verbose_name=_("IP Address"),
+ attrs={'data-type': "ip"})
+
+ class Meta(object):
+ name = "floating_ips"
+ verbose_name = _("Floating IPs")
+ status_columns = ["status"]
+ table_actions = (FloatingIPFilterAction,
+ AdminAllocateFloatingIP,
+ AdminReleaseFloatingIP)
+ row_actions = (AdminSimpleDisassociateIP,
+ AdminReleaseFloatingIP)
+ columns = ('tenant', 'ip', 'fixed_ip', 'pool', 'status')
diff --git a/openstack_dashboard/dashboards/admin/floating_ips/templates/floating_ips/_allocate.html b/openstack_dashboard/dashboards/admin/floating_ips/templates/floating_ips/_allocate.html
new file mode 100644
index 0000000000..3998de5815
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/floating_ips/templates/floating_ips/_allocate.html
@@ -0,0 +1,9 @@
+{% extends "horizon/common/_modal_form.html" %}
+{% load i18n %}
+
+{% block modal-header %}{% trans "Allocate Floating IP" %}{% endblock %}
+
+{% block modal-body-right %}
+
{% trans "Description:" %}
+ {% trans "From here you can allocate a floating IP to a specific project." %}
+{% endblock %}
diff --git a/openstack_dashboard/dashboards/admin/floating_ips/templates/floating_ips/allocate.html b/openstack_dashboard/dashboards/admin/floating_ips/templates/floating_ips/allocate.html
new file mode 100644
index 0000000000..fb7f9e628e
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/floating_ips/templates/floating_ips/allocate.html
@@ -0,0 +1,7 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}{% trans "Allocate Floating IP" %}{% endblock %}
+
+{% block main %}
+ {% include 'admin/floating_ips/_allocate.html' %}
+{% endblock %}
diff --git a/openstack_dashboard/dashboards/admin/floating_ips/templates/floating_ips/detail.html b/openstack_dashboard/dashboards/admin/floating_ips/templates/floating_ips/detail.html
new file mode 100644
index 0000000000..8ea9a7efed
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/floating_ips/templates/floating_ips/detail.html
@@ -0,0 +1,48 @@
+{% extends 'base.html' %}
+{% load i18n sizeformat %}
+
+{% block title %}{% trans "Floating IP Details"%}{% endblock %}
+
+{% block page_header %}
+ {% include "horizon/common/_detail_header.html" %}
+{% endblock %}
+
+{% block main %}
+
+
+ - {% trans "ID" %}
+ - {{ floating_ip.id|default:_("None") }}
+
+ - {% trans "Project ID" %}
+ - {{ floating_ip.tenant_id|default:"-" }}
+
+ - {% trans "Floating IP address" %}
+ - {{ floating_ip.ip|default:_("None") }}
+ - {% trans "Status" %}
+ - {{ floating_ip.status|default:_("None") }}
+
+ - {% trans "Pool" %}
+ {% url 'horizon:admin:networks:detail' floating_ip.pool as network_url %}
+ - {{ floating_ip.pool_name|default:_("None") }}
+
+ - {% trans "Mapped IP Address" %}
+ {% if floating_ip.instance_id and floating_ip.instance_type == 'compute' %}
+ {% url 'horizon:admin:instances:detail' floating_ip.instance_id as instance_url %}
+ - {{ floating_ip.mapped_fixed_ip }}
+ {% elif floating_ip.port_id and floating_ip.fixed_ip and floating_ip.instance_type != 'compute' %}
+ {% url 'horizon:admin:networks:ports:detail' floating_ip.port_id as port_url %}
+ - {{ floating_ip.fixed_ip }}
+ {% else %}
+ - {% trans "No associated fixed IP" %}
+ {% endif %}
+
+ - {% trans "Router" %}
+ {% if floating_ip.router_id %}
+ {% url 'horizon:admin:routers:detail' floating_ip.router_id as router_url %}
+ - {{ floating_ip.router_name }}
+ {% else %}
+ - {% trans "No router" %}
+ {% endif %}
+
+
+{% endblock %}
diff --git a/openstack_dashboard/dashboards/admin/floating_ips/templates/floating_ips/index.html b/openstack_dashboard/dashboards/admin/floating_ips/templates/floating_ips/index.html
new file mode 100644
index 0000000000..24dbc00d6a
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/floating_ips/templates/floating_ips/index.html
@@ -0,0 +1,7 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}{% trans "Floating IPs" %}{% endblock %}
+
+{% block main %}
+ {{ table.render }}
+{% endblock %}
diff --git a/openstack_dashboard/dashboards/admin/floating_ips/tests.py b/openstack_dashboard/dashboards/admin/floating_ips/tests.py
new file mode 100644
index 0000000000..a67b9018c6
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/floating_ips/tests.py
@@ -0,0 +1,275 @@
+# Copyright 2016 Letv Cloud Computing
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from django.core.urlresolvers import reverse
+from django import http
+from mox3.mox import IsA # noqa
+
+from openstack_dashboard import api
+from openstack_dashboard.test import helpers as test
+
+INDEX_URL = reverse('horizon:admin:floating_ips:index')
+
+
+class AdminFloatingIpViewTest(test.BaseAdminViewTests):
+ @test.create_stubs({api.network: ('tenant_floating_ip_list', ),
+ api.nova: ('server_list', ),
+ api.keystone: ('tenant_list', ),
+ api.neutron: ('network_list', )})
+ def test_index(self):
+ # Use neutron test data
+ fips = self.q_floating_ips.list()
+ servers = self.servers.list()
+ tenants = self.tenants.list()
+ api.network.tenant_floating_ip_list(IsA(http.HttpRequest),
+ all_tenants=True).AndReturn(fips)
+ api.nova.server_list(IsA(http.HttpRequest), all_tenants=True) \
+ .AndReturn([servers, False])
+ api.keystone.tenant_list(IsA(http.HttpRequest))\
+ .AndReturn([tenants, False])
+ params = {"router:external": True}
+ api.neutron.network_list(IsA(http.HttpRequest), **params) \
+ .AndReturn(self.networks.list())
+
+ self.mox.ReplayAll()
+
+ res = self.client.get(INDEX_URL)
+ self.assertTemplateUsed(res, 'admin/floating_ips/index.html')
+ self.assertIn('floating_ips_table', res.context)
+ floating_ips_table = res.context['floating_ips_table']
+ floating_ips = floating_ips_table.data
+ self.assertEqual(len(floating_ips), 2)
+
+ row_actions = floating_ips_table.get_row_actions(floating_ips[0])
+ self.assertEqual(len(row_actions), 1)
+ row_actions = floating_ips_table.get_row_actions(floating_ips[1])
+ self.assertEqual(len(row_actions), 2)
+
+ @test.create_stubs({api.network: ('tenant_floating_ip_get', ),
+ api.neutron: ('network_get', )})
+ def test_floating_ip_detail_get(self):
+ fip = self.q_floating_ips.first()
+ network = self.networks.first()
+ api.network.tenant_floating_ip_get(
+ IsA(http.HttpRequest), fip.id).AndReturn(fip)
+ api.neutron.network_get(
+ IsA(http.HttpRequest), fip.pool).AndReturn(network)
+ self.mox.ReplayAll()
+
+ res = self.client.get(reverse('horizon:admin:floating_ips:detail',
+ args=[fip.id]))
+ self.assertTemplateUsed(res,
+ 'admin/floating_ips/detail.html')
+ self.assertEqual(res.context['floating_ip'].ip, fip.ip)
+
+ @test.create_stubs({api.network: ('tenant_floating_ip_get',)})
+ def test_floating_ip_detail_exception(self):
+ fip = self.q_floating_ips.first()
+ # Only supported by neutron, so raise a neutron exception
+ api.network.tenant_floating_ip_get(
+ IsA(http.HttpRequest),
+ fip.id).AndRaise(self.exceptions.neutron)
+
+ self.mox.ReplayAll()
+
+ res = self.client.get(reverse('horizon:admin:floating_ips:detail',
+ args=[fip.id]))
+
+ self.assertRedirectsNoFollow(res, INDEX_URL)
+
+ @test.create_stubs({api.network: ('tenant_floating_ip_list', )})
+ def test_index_no_floating_ips(self):
+ api.network.tenant_floating_ip_list(IsA(http.HttpRequest),
+ all_tenants=True).AndReturn([])
+ self.mox.ReplayAll()
+
+ res = self.client.get(INDEX_URL)
+ self.assertTemplateUsed(res, 'admin/floating_ips/index.html')
+
+ @test.create_stubs({api.network: ('tenant_floating_ip_list', )})
+ def test_index_error(self):
+ api.network.tenant_floating_ip_list(IsA(http.HttpRequest),
+ all_tenants=True) \
+ .AndRaise(self.exceptions.neutron)
+ self.mox.ReplayAll()
+
+ res = self.client.get(INDEX_URL)
+ self.assertTemplateUsed(res, 'admin/floating_ips/index.html')
+
+ @test.create_stubs({api.neutron: ('network_list',),
+ api.keystone: ('tenant_list',)})
+ def test_admin_allocate_get(self):
+ pool = self.networks.first()
+ tenants = self.tenants.list()
+
+ api.keystone.tenant_list(IsA(http.HttpRequest))\
+ .AndReturn([tenants, False])
+ search_opts = {'router:external': True}
+ api.neutron.network_list(IsA(http.HttpRequest), **search_opts) \
+ .AndReturn([pool])
+
+ self.mox.ReplayAll()
+
+ url = reverse('horizon:admin:floating_ips:allocate')
+ res = self.client.get(url)
+ self.assertTemplateUsed(res, 'admin/floating_ips/allocate.html')
+ allocate_form = res.context['form']
+
+ pool_choices = allocate_form.fields['pool'].choices
+ self.assertEqual(len(pool_choices), 1)
+ tenant_choices = allocate_form.fields['tenant'].choices
+ self.assertEqual(len(tenant_choices), 3)
+
+ @test.create_stubs({api.neutron: ('network_list',),
+ api.keystone: ('tenant_list',)})
+ def test_admin_allocate_post_invalid_ip_version(self):
+ tenant = self.tenants.first()
+ pool = self.networks.first()
+ tenants = self.tenants.list()
+
+ api.keystone.tenant_list(IsA(http.HttpRequest))\
+ .AndReturn([tenants, False])
+ search_opts = {'router:external': True}
+ api.neutron.network_list(IsA(http.HttpRequest), **search_opts) \
+ .AndReturn([pool])
+ self.mox.ReplayAll()
+
+ form_data = {'pool': pool.id,
+ 'tenant': tenant.id,
+ 'floating_ip_address': 'fc00::1'}
+ url = reverse('horizon:admin:floating_ips:allocate')
+ res = self.client.post(url, form_data)
+ self.assertContains(res, "Invalid version for IP address")
+
+ @test.create_stubs({api.network: ('tenant_floating_ip_allocate',),
+ api.neutron: ('network_list',),
+ api.keystone: ('tenant_list',)})
+ def test_admin_allocate_post(self):
+ tenant = self.tenants.first()
+ floating_ip = self.floating_ips.first()
+ pool = self.networks.first()
+ tenants = self.tenants.list()
+
+ api.keystone.tenant_list(IsA(http.HttpRequest))\
+ .AndReturn([tenants, False])
+ search_opts = {'router:external': True}
+ api.neutron.network_list(IsA(http.HttpRequest), **search_opts) \
+ .AndReturn([pool])
+ api.network.tenant_floating_ip_allocate(
+ IsA(http.HttpRequest),
+ pool=pool.id,
+ tenant_id=tenant.id).AndReturn(floating_ip)
+ self.mox.ReplayAll()
+
+ form_data = {'pool': pool.id,
+ 'tenant': tenant.id}
+ url = reverse('horizon:admin:floating_ips:allocate')
+ res = self.client.post(url, form_data)
+ self.assertRedirectsNoFollow(res, INDEX_URL)
+
+ @test.create_stubs({api.network: ('tenant_floating_ip_list',
+ 'floating_ip_disassociate'),
+ api.nova: ('server_list', ),
+ api.keystone: ('tenant_list', ),
+ api.neutron: ('network_list', )})
+ def test_admin_disassociate_floatingip(self):
+ # Use neutron test data
+ fips = self.q_floating_ips.list()
+ floating_ip = self.q_floating_ips.list()[1]
+ servers = self.servers.list()
+ tenants = self.tenants.list()
+ api.network.tenant_floating_ip_list(IsA(http.HttpRequest),
+ all_tenants=True).AndReturn(fips)
+ api.nova.server_list(IsA(http.HttpRequest), all_tenants=True) \
+ .AndReturn([servers, False])
+ api.keystone.tenant_list(IsA(http.HttpRequest))\
+ .AndReturn([tenants, False])
+ params = {"router:external": True}
+ api.neutron.network_list(IsA(http.HttpRequest), **params) \
+ .AndReturn(self.networks.list())
+ api.network.floating_ip_disassociate(IsA(http.HttpRequest),
+ floating_ip.id)
+ self.mox.ReplayAll()
+
+ form_data = {
+ "action":
+ "floating_ips__disassociate__%s" % floating_ip.id}
+ res = self.client.post(INDEX_URL, form_data)
+
+ self.assertNoFormErrors(res)
+
+ @test.create_stubs({api.network: ('tenant_floating_ip_list', ),
+ api.nova: ('server_list', ),
+ api.keystone: ('tenant_list', ),
+ api.neutron: ('network_list', )})
+ def test_admin_delete_floatingip(self):
+ # Use neutron test data
+ fips = self.q_floating_ips.list()
+ floating_ip = self.q_floating_ips.list()[1]
+ servers = self.servers.list()
+ tenants = self.tenants.list()
+ api.network.tenant_floating_ip_list(IsA(http.HttpRequest),
+ all_tenants=True).AndReturn(fips)
+ api.nova.server_list(IsA(http.HttpRequest), all_tenants=True) \
+ .AndReturn([servers, False])
+ api.keystone.tenant_list(IsA(http.HttpRequest))\
+ .AndReturn([tenants, False])
+ params = {"router:external": True}
+ api.neutron.network_list(IsA(http.HttpRequest), **params) \
+ .AndReturn(self.networks.list())
+
+ self.mox.ReplayAll()
+
+ form_data = {
+ "action":
+ "floating_ips__delete__%s" % floating_ip.id}
+ res = self.client.post(INDEX_URL, form_data)
+
+ self.assertNoFormErrors(res)
+
+ @test.create_stubs({api.network: ('tenant_floating_ip_list', ),
+ api.nova: ('server_list', ),
+ api.keystone: ('tenant_list', ),
+ api.neutron: ('network_list', )})
+ def test_floating_ip_table_actions(self):
+ # Use neutron test data
+ fips = self.q_floating_ips.list()
+ servers = self.servers.list()
+ tenants = self.tenants.list()
+ api.network.tenant_floating_ip_list(IsA(http.HttpRequest),
+ all_tenants=True).AndReturn(fips)
+ api.nova.server_list(IsA(http.HttpRequest), all_tenants=True) \
+ .AndReturn([servers, False])
+ api.keystone.tenant_list(IsA(http.HttpRequest))\
+ .AndReturn([tenants, False])
+ params = {"router:external": True}
+ api.neutron.network_list(IsA(http.HttpRequest), **params) \
+ .AndReturn(self.networks.list())
+
+ self.mox.ReplayAll()
+
+ res = self.client.get(INDEX_URL)
+ self.assertTemplateUsed(res, 'admin/floating_ips/index.html')
+ self.assertIn('floating_ips_table', res.context)
+ floating_ips_table = res.context['floating_ips_table']
+ floating_ips = floating_ips_table.data
+ self.assertEqual(len(floating_ips), 2)
+ # table actions
+ self.assertContains(res, 'id="floating_ips__action_allocate"')
+ self.assertContains(res, 'id="floating_ips__action_release"')
+ # row actions
+ self.assertContains(res, 'floating_ips__release__%s' % fips[0].id)
+ self.assertContains(res, 'floating_ips__release__%s' % fips[1].id)
+ self.assertContains(res, 'floating_ips__disassociate__%s' % fips[1].id)
diff --git a/openstack_dashboard/dashboards/admin/floating_ips/urls.py b/openstack_dashboard/dashboards/admin/floating_ips/urls.py
new file mode 100644
index 0000000000..8c19a5274d
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/floating_ips/urls.py
@@ -0,0 +1,26 @@
+# Copyright 2016 Letv Cloud Computing
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from django.conf.urls import url
+
+from openstack_dashboard.dashboards.admin.floating_ips import views
+
+
+urlpatterns = [
+ url(r'^$', views.IndexView.as_view(), name='index'),
+ url(r'^allocate/$', views.AllocateView.as_view(), name='allocate'),
+ url(r'^(?P[^/]+)/detail/$',
+ views.DetailView.as_view(), name='detail')
+]
diff --git a/openstack_dashboard/dashboards/admin/floating_ips/views.py b/openstack_dashboard/dashboards/admin/floating_ips/views.py
new file mode 100644
index 0000000000..05919d2c78
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/floating_ips/views.py
@@ -0,0 +1,189 @@
+# Copyright 2016 Letv Cloud Computing
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from collections import OrderedDict
+
+from django.core.urlresolvers import reverse
+from django.core.urlresolvers import reverse_lazy
+from django.utils.translation import ugettext_lazy as _
+import netaddr
+
+from horizon import exceptions
+from horizon import forms
+from horizon import tables
+from horizon.utils import memoized
+from horizon import views
+
+from openstack_dashboard import api
+
+from openstack_dashboard.dashboards.admin.floating_ips \
+ import forms as fip_forms
+from openstack_dashboard.dashboards.admin.floating_ips \
+ import tables as fip_tables
+from openstack_dashboard.dashboards.project.access_and_security.\
+ floating_ips import tables as project_tables
+
+
+def get_floatingip_pools(request):
+ pools = []
+ try:
+ search_opts = {'router:external': True}
+ pools = api.neutron.network_list(request, **search_opts)
+ except Exception:
+ exceptions.handle(request,
+ _("Unable to retrieve floating IP pools."))
+ return pools
+
+
+def get_tenant_list(request):
+ tenants = []
+ try:
+ tenants, has_more = api.keystone.tenant_list(request)
+ except Exception:
+ msg = _('Unable to retrieve project list.')
+ exceptions.handle(request, msg)
+ return tenants
+
+
+class IndexView(tables.DataTableView):
+ table_class = fip_tables.FloatingIPsTable
+ template_name = 'admin/floating_ips/index.html'
+ page_title = _("Floating IPs")
+
+ @memoized.memoized_method
+ def get_data(self):
+ floating_ips = []
+ try:
+ floating_ips = api.network.tenant_floating_ip_list(
+ self.request,
+ all_tenants=True)
+ except Exception:
+ exceptions.handle(self.request,
+ _('Unable to retrieve floating IP list.'))
+
+ if floating_ips:
+ instances = []
+ try:
+ instances, has_more = api.nova.server_list(self.request,
+ all_tenants=True)
+ except Exception:
+ exceptions.handle(
+ self.request,
+ _('Unable to retrieve instance list.'))
+ instances_dict = dict([(obj.id, obj.name) for obj in instances])
+
+ tenants = get_tenant_list(self.request)
+ tenant_dict = OrderedDict([(t.id, t) for t in tenants])
+
+ pools = get_floatingip_pools(self.request)
+ pool_dict = dict([(obj.id, obj.name) for obj in pools])
+
+ for ip in floating_ips:
+ ip.instance_name = instances_dict.get(ip.instance_id)
+ ip.pool_name = pool_dict.get(ip.pool, ip.pool)
+ tenant = tenant_dict.get(ip.tenant_id, None)
+ ip.tenant_name = getattr(tenant, "name", None)
+
+ return floating_ips
+
+
+class DetailView(views.HorizonTemplateView):
+ template_name = 'admin/floating_ips/detail.html'
+ page_title = _("Floating IP Details")
+
+ def _get_corresponding_data(self, resource, resource_id):
+ function_dict = {"floating IP": api.network.tenant_floating_ip_get,
+ "instance": api.nova.server_get,
+ "network": api.neutron.network_get,
+ "router": api.neutron.router_get}
+ url = reverse('horizon:admin:floating_ips:index')
+ try:
+ res = function_dict[resource](
+ self.request, resource_id)
+ if resource in ["network", "router"]:
+ res.set_id_as_name_if_empty(length=0)
+ return res
+ except KeyError:
+ msg = _('Unknow resource type for detail API.')
+ exceptions.handle(self.request, msg, redirect=url)
+ except Exception:
+ msg = _('Unable to retrieve details for '
+ '%(resource)s "%(resource_id)s".') % {
+ "resource": resource,
+ "resource_id": resource_id}
+ exceptions.handle(self.request, msg, redirect=url)
+
+ def get_context_data(self, **kwargs):
+ context = super(DetailView, self).get_context_data(**kwargs)
+
+ floating_ip_id = self.kwargs['floating_ip_id']
+ floating_ip = self._get_corresponding_data("floating IP",
+ floating_ip_id)
+
+ network = self._get_corresponding_data("network", floating_ip.pool)
+ floating_ip.pool_name = network.name
+
+ if floating_ip.instance_id and floating_ip.instance_type == 'compute':
+ instance = self._get_corresponding_data(
+ "instance", floating_ip.instance_id)
+ floating_ip.instance_name = instance.name
+ floating_ip.mapped_fixed_ip = project_tables.get_instance_info(
+ floating_ip)
+
+ if floating_ip.router_id:
+ router = self._get_corresponding_data("router",
+ floating_ip.router_id)
+ floating_ip.router_name = router.name
+ table = fip_tables.FloatingIPsTable(self.request)
+ context['floating_ip'] = floating_ip
+ context["url"] = reverse('horizon:admin:floating_ips:index')
+ context["actions"] = table.render_row_actions(floating_ip)
+ return context
+
+
+class AllocateView(forms.ModalFormView):
+ form_class = fip_forms.AdminFloatingIpAllocate
+ form_id = "allocate_floating_ip_form"
+ template_name = 'admin/floating_ips/allocate.html'
+ modal_header = _("Allocate Floating IP")
+ submit_label = _("Allocate Floating IP")
+ submit_url = reverse_lazy("horizon:admin:floating_ips:allocate")
+ cancel_url = reverse_lazy('horizon:admin:floating_ips:index')
+ success_url = reverse_lazy('horizon:admin:floating_ips:index')
+ page_title = _("Allocate Floating IP")
+
+ @memoized.memoized_method
+ def get_initial(self):
+ tenants = get_tenant_list(self.request)
+ tenant_list = [(t.id, t.name) for t in tenants]
+ if not tenant_list:
+ tenant_list = [(None, _("No project available"))]
+
+ pools = get_floatingip_pools(self.request)
+ pool_list = []
+ for pool in pools:
+ for subnet in pool.subnets:
+ if netaddr.IPNetwork(subnet.cidr).version != 4:
+ continue
+ pool_display_name = (_("%(cidr)s %(pool_name)s")
+ % {'cidr': subnet.cidr,
+ 'pool_name': pool.name})
+ pool_list.append((pool.id, pool_display_name))
+ if not pool_list:
+ pool_list = [
+ (None, _("No floating IP pools with IPv4 subnet available"))]
+
+ return {'pool_list': pool_list,
+ 'tenant_list': tenant_list}
diff --git a/openstack_dashboard/enabled/_2111_admin_floating_ips_panel.py b/openstack_dashboard/enabled/_2111_admin_floating_ips_panel.py
new file mode 100644
index 0000000000..a3b03a4f45
--- /dev/null
+++ b/openstack_dashboard/enabled/_2111_admin_floating_ips_panel.py
@@ -0,0 +1,10 @@
+# The slug of the panel to be added to HORIZON_CONFIG. Required.
+PANEL = 'floating_ips'
+# The slug of the dashboard the PANEL associated with. Required.
+PANEL_DASHBOARD = 'admin'
+# The slug of the panel group the PANEL is associated with.
+PANEL_GROUP = 'admin'
+
+# Python panel class of the PANEL to be added.
+ADD_PANEL = \
+ 'openstack_dashboard.dashboards.admin.floating_ips.panel.AdminFloatingIps'
diff --git a/openstack_dashboard/test/api_tests/network_tests.py b/openstack_dashboard/test/api_tests/network_tests.py
index 70d386dfb8..b8416619d4 100644
--- a/openstack_dashboard/test/api_tests/network_tests.py
+++ b/openstack_dashboard/test/api_tests/network_tests.py
@@ -127,7 +127,7 @@ class NetworkApiNovaFloatingIpTests(NetworkApiNovaTestBase):
fips = self.api_floating_ips.list()
novaclient = self.stub_novaclient()
novaclient.floating_ips = self.mox.CreateMockAnything()
- novaclient.floating_ips.list().AndReturn(fips)
+ novaclient.floating_ips.list(all_tenants=False).AndReturn(fips)
self.mox.ReplayAll()
ret = api.network.tenant_floating_ip_list(self.request)
diff --git a/openstack_dashboard/test/test_data/neutron_data.py b/openstack_dashboard/test/test_data/neutron_data.py
index b192db11f4..e005a716b8 100644
--- a/openstack_dashboard/test/test_data/neutron_data.py
+++ b/openstack_dashboard/test/test_data/neutron_data.py
@@ -440,7 +440,10 @@ def data(TEST):
'port_id': None,
'router_id': None}
TEST.api_q_floating_ips.add(fip_dict)
- TEST.q_floating_ips.add(neutron.FloatingIp(fip_dict))
+ fip_with_instance = copy.deepcopy(fip_dict)
+ fip_with_instance.update({'instance_id': None,
+ 'instance_type': None})
+ TEST.q_floating_ips.add(neutron.FloatingIp(fip_with_instance))
# Associated (with compute port on 1st network).
fip_dict = {'tenant_id': '1',
@@ -451,7 +454,10 @@ def data(TEST):
'port_id': assoc_port['id'],
'router_id': router_dict['id']}
TEST.api_q_floating_ips.add(fip_dict)
- TEST.q_floating_ips.add(neutron.FloatingIp(fip_dict))
+ fip_with_instance = copy.deepcopy(fip_dict)
+ fip_with_instance.update({'instance_id': '1',
+ 'instance_type': 'compute'})
+ TEST.q_floating_ips.add(neutron.FloatingIp(fip_with_instance))
# Security group.
diff --git a/releasenotes/notes/bp-admin-manage-fips-5aa409d3502b031a.yaml b/releasenotes/notes/bp-admin-manage-fips-5aa409d3502b031a.yaml
new file mode 100644
index 0000000000..62374688a7
--- /dev/null
+++ b/releasenotes/notes/bp-admin-manage-fips-5aa409d3502b031a.yaml
@@ -0,0 +1,4 @@
+---
+features:
+ - >
+ [`blueprint manage-ips Add ability to manage floating IPs in syspanel `_] Admin dashboard Floating IPs panel has been added to Horizon.