summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLIU Yulong <liuyulong@letv.com>2014-12-22 15:10:38 +0800
committerLIU Yulong <liuyulong@le.com>2016-08-19 12:15:55 +0800
commit5c238e9117b1333c3d66798e5ad7e9e0fe44fc22 (patch)
treea12353734503f64a52fa290be9d7c3ccebc639e9
parent3f35caf180e1f9fa7e490bf341bf112014d2e6fc (diff)
Add floating IP panel to admin dashboard
Now system administrators have CRUD abilities to manage floating IP. 1.floating IP list table 2.allocate floating IP to specific tenant 3.release/delete floating IP Partially implements blueprint: manage-ips Partially implements blueprint: syspanel-floating-ip-list Change-Id: Ie5ec59740887d3845b933b37e6e875dbf08a4918
Notes
Notes (review): Code-Review+1: Kenji Ishii <ken-ishii@sx.jp.nec.com> Code-Review+2: Akihiro Motoki <amotoki@gmail.com> Code-Review+1: caoyuan <cao.yuan@99cloud.net> Code-Review+2: David Lyle <dklyle0@gmail.com> Workflow+1: David Lyle <dklyle0@gmail.com> Verified+2: Jenkins Submitted-by: Jenkins Submitted-at: Wed, 24 Aug 2016 12:29:01 +0000 Reviewed-on: https://review.openstack.org/143628 Project: openstack/horizon Branch: refs/heads/master
-rw-r--r--openstack_dashboard/api/network.py10
-rw-r--r--openstack_dashboard/api/network_base.py6
-rw-r--r--openstack_dashboard/api/neutron.py13
-rw-r--r--openstack_dashboard/api/nova.py10
-rw-r--r--openstack_dashboard/dashboards/admin/floating_ips/__init__.py0
-rw-r--r--openstack_dashboard/dashboards/admin/floating_ips/forms.py64
-rw-r--r--openstack_dashboard/dashboards/admin/floating_ips/panel.py30
-rw-r--r--openstack_dashboard/dashboards/admin/floating_ips/tables.py91
-rw-r--r--openstack_dashboard/dashboards/admin/floating_ips/templates/floating_ips/_allocate.html9
-rw-r--r--openstack_dashboard/dashboards/admin/floating_ips/templates/floating_ips/allocate.html7
-rw-r--r--openstack_dashboard/dashboards/admin/floating_ips/templates/floating_ips/detail.html48
-rw-r--r--openstack_dashboard/dashboards/admin/floating_ips/templates/floating_ips/index.html7
-rw-r--r--openstack_dashboard/dashboards/admin/floating_ips/tests.py275
-rw-r--r--openstack_dashboard/dashboards/admin/floating_ips/urls.py26
-rw-r--r--openstack_dashboard/dashboards/admin/floating_ips/views.py189
-rw-r--r--openstack_dashboard/enabled/_2111_admin_floating_ips_panel.py10
-rw-r--r--openstack_dashboard/test/api_tests/network_tests.py2
-rw-r--r--openstack_dashboard/test/test_data/neutron_data.py10
-rw-r--r--releasenotes/notes/bp-admin-manage-fips-5aa409d3502b031a.yaml4
19 files changed, 793 insertions, 18 deletions
diff --git a/openstack_dashboard/api/network.py b/openstack_dashboard/api/network.py
index 015bf3f..7389531 100644
--- a/openstack_dashboard/api/network.py
+++ b/openstack_dashboard/api/network.py
@@ -50,16 +50,18 @@ def floating_ip_pools_list(request):
50 return NetworkClient(request).floating_ips.list_pools() 50 return NetworkClient(request).floating_ips.list_pools()
51 51
52 52
53def tenant_floating_ip_list(request): 53def tenant_floating_ip_list(request, all_tenants=False):
54 return NetworkClient(request).floating_ips.list() 54 return NetworkClient(request).floating_ips.list(all_tenants=all_tenants)
55 55
56 56
57def tenant_floating_ip_get(request, floating_ip_id): 57def tenant_floating_ip_get(request, floating_ip_id):
58 return NetworkClient(request).floating_ips.get(floating_ip_id) 58 return NetworkClient(request).floating_ips.get(floating_ip_id)
59 59
60 60
61def tenant_floating_ip_allocate(request, pool=None): 61def tenant_floating_ip_allocate(request, pool=None, tenant_id=None, **params):
62 return NetworkClient(request).floating_ips.allocate(pool) 62 return NetworkClient(request).floating_ips.allocate(pool,
63 tenant_id,
64 **params)
63 65
64 66
65def tenant_floating_ip_release(request, floating_ip_id): 67def 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 af4a28f..f6d4046 100644
--- a/openstack_dashboard/api/network_base.py
+++ b/openstack_dashboard/api/network_base.py
@@ -51,8 +51,8 @@ class FloatingIpManager(object):
51 pass 51 pass
52 52
53 @abc.abstractmethod 53 @abc.abstractmethod
54 def list(self): 54 def list(self, all_tenants=False):
55 """Fetches a list all floating IPs. 55 """Fetches a list of all floating IPs.
56 56
57 A returned value is a list of FloatingIp object. 57 A returned value is a list of FloatingIp object.
58 """ 58 """
@@ -67,7 +67,7 @@ class FloatingIpManager(object):
67 pass 67 pass
68 68
69 @abc.abstractmethod 69 @abc.abstractmethod
70 def allocate(self, pool=None): 70 def allocate(self, pool=None, tenant_id=None, **params):
71 """Allocates a floating IP to the tenant. 71 """Allocates a floating IP to the tenant.
72 72
73 You must provide a pool name or id for which you would like to 73 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 9bc0b10..ce07242 100644
--- a/openstack_dashboard/api/neutron.py
+++ b/openstack_dashboard/api/neutron.py
@@ -417,10 +417,15 @@ class FloatingIpManager(network_base.FloatingIpManager):
417 self._set_instance_info(fip) 417 self._set_instance_info(fip)
418 return FloatingIp(fip) 418 return FloatingIp(fip)
419 419
420 def allocate(self, pool): 420 def allocate(self, pool, tenant_id=None, **params):
421 body = {'floatingip': {'floating_network_id': pool, 421 if not tenant_id:
422 'tenant_id': self.request.user.project_id}} 422 tenant_id = self.request.user.project_id
423 fip = self.client.create_floatingip(body).get('floatingip') 423 create_dict = {'floating_network_id': pool,
424 'tenant_id': tenant_id}
425 if 'floating_ip_address' in params:
426 create_dict['floating_ip_address'] = params['floating_ip_address']
427 fip = self.client.create_floatingip(
428 {'floatingip': create_dict}).get('floatingip')
424 self._set_instance_info(fip) 429 self._set_instance_info(fip)
425 return FloatingIp(fip) 430 return FloatingIp(fip)
426 431
diff --git a/openstack_dashboard/api/nova.py b/openstack_dashboard/api/nova.py
index 47da4d1..7ed9e63 100644
--- a/openstack_dashboard/api/nova.py
+++ b/openstack_dashboard/api/nova.py
@@ -415,14 +415,16 @@ class FloatingIpManager(network_base.FloatingIpManager):
415 return [FloatingIpPool(pool) 415 return [FloatingIpPool(pool)
416 for pool in self.client.floating_ip_pools.list()] 416 for pool in self.client.floating_ip_pools.list()]
417 417
418 def list(self): 418 def list(self, all_tenants=False):
419 return [FloatingIp(fip) 419 return [FloatingIp(fip) for fip in
420 for fip in self.client.floating_ips.list()] 420 self.client.floating_ips.list(
421 all_tenants=all_tenants)]
421 422
422 def get(self, floating_ip_id): 423 def get(self, floating_ip_id):
423 return FloatingIp(self.client.floating_ips.get(floating_ip_id)) 424 return FloatingIp(self.client.floating_ips.get(floating_ip_id))
424 425
425 def allocate(self, pool): 426 def allocate(self, pool, tenant_id=None, **params):
427 # NOTE: tenant_id will never be used here.
426 return FloatingIp(self.client.floating_ips.create(pool=pool)) 428 return FloatingIp(self.client.floating_ips.create(pool=pool))
427 429
428 def release(self, floating_ip_id): 430 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 0000000..e69de29
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/floating_ips/__init__.py
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 0000000..8729d3f
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/floating_ips/forms.py
@@ -0,0 +1,64 @@
1# Copyright 2016 Letv Cloud Computing
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
16from django.core.urlresolvers import reverse
17from django.utils.translation import ugettext_lazy as _
18
19from horizon import exceptions
20from horizon import forms
21from horizon import messages
22
23from openstack_dashboard import api
24
25
26class AdminFloatingIpAllocate(forms.SelfHandlingForm):
27 pool = forms.ChoiceField(label=_("Pool"))
28 tenant = forms.ChoiceField(label=_("Project"))
29 floating_ip_address = forms.IPField(
30 label=_("Floating IP Address (optional)"),
31 required=False,
32 initial="",
33 help_text=_("The IP address of the new floating IP (e.g. 202.2.3.4). "
34 "You need to specify an explicit address which is under "
35 "the public network CIDR (e.g. 202.2.3.0/24)."),
36 mask=False)
37
38 def __init__(self, *args, **kwargs):
39 super(AdminFloatingIpAllocate, self).__init__(*args, **kwargs)
40 floating_pool_list = kwargs.get('initial', {}).get('pool_list', [])
41 self.fields['pool'].choices = floating_pool_list
42 tenant_list = kwargs.get('initial', {}).get('tenant_list', [])
43 self.fields['tenant'].choices = tenant_list
44
45 def handle(self, request, data):
46 try:
47 # Admin ignore quota
48 param = {}
49 if data['floating_ip_address']:
50 param['floating_ip_address'] = data['floating_ip_address']
51 # TODO(liuyulong): use subnet id to allocate floating IP.
52 fip = api.network.tenant_floating_ip_allocate(
53 request,
54 pool=data['pool'],
55 tenant_id=data['tenant'],
56 **param)
57 messages.success(
58 request,
59 _('Allocated floating IP %(ip)s.') % {"ip": fip.ip})
60 return fip
61 except Exception:
62 redirect = reverse('horizon:admin:floating_ips:index')
63 msg = _('Unable to allocate floating IP.')
64 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 0000000..2503c79
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/floating_ips/panel.py
@@ -0,0 +1,30 @@
1# Copyright 2016 Letv Cloud Computing
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
16from django.conf import settings
17from django.utils.translation import ugettext_lazy as _
18
19import horizon
20
21
22class AdminFloatingIps(horizon.Panel):
23 name = _("Floating IPs")
24 slug = 'floating_ips'
25 permissions = ('openstack.services.network', )
26
27 @staticmethod
28 def can_register():
29 network_config = getattr(settings, 'OPENSTACK_NEUTRON_NETWORK', {})
30 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 0000000..162277f
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/floating_ips/tables.py
@@ -0,0 +1,91 @@
1# Copyright 2016 Letv Cloud Computing
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
16import logging
17
18from django import shortcuts
19from django.utils.translation import ugettext_lazy as _
20
21from horizon import exceptions
22from horizon import messages
23from horizon import tables
24
25from openstack_dashboard import api
26from openstack_dashboard import policy
27from openstack_dashboard.dashboards.project.access_and_security.\
28 floating_ips import tables as project_tables
29from openstack_dashboard.utils import filters
30
31
32LOG = logging.getLogger(__name__)
33
34
35class FloatingIPFilterAction(tables.FilterAction):
36
37 def filter(self, table, fips, filter_string):
38 """Naive case-insensitive search."""
39 q = filter_string.lower()
40 return [ip for ip in fips
41 if q in ip.ip.lower()]
42
43
44class AdminAllocateFloatingIP(project_tables.AllocateIP):
45 url = "horizon:admin:floating_ips:allocate"
46
47 def single(self, data_table, request, *args):
48 return shortcuts.redirect('horizon:admin:floating_ips:index')
49
50 def allowed(self, request, fip=None):
51 policy_rules = (("network", "create_floatingip"),)
52 return policy.check(policy_rules, request)
53
54
55class AdminReleaseFloatingIP(project_tables.ReleaseIPs):
56 pass
57
58
59class AdminSimpleDisassociateIP(project_tables.DisassociateIP):
60
61 def single(self, table, request, obj_id):
62 try:
63 fip = table.get_object_by_id(filters.get_int_or_uuid(obj_id))
64 api.network.floating_ip_disassociate(request, fip.id)
65 LOG.info('Disassociating Floating IP "%s".' % obj_id)
66 messages.success(request,
67 _('Successfully disassociated Floating IP: %s')
68 % fip.ip)
69 except Exception:
70 exceptions.handle(request,
71 _('Unable to disassociate floating IP.'))
72 return shortcuts.redirect('horizon:admin:floating_ips:index')
73
74
75class FloatingIPsTable(project_tables.FloatingIPsTable):
76 tenant = tables.Column("tenant_name", verbose_name=_("Project"))
77 ip = tables.Column("ip",
78 link=("horizon:admin:floating_ips:detail"),
79 verbose_name=_("IP Address"),
80 attrs={'data-type': "ip"})
81
82 class Meta(object):
83 name = "floating_ips"
84 verbose_name = _("Floating IPs")
85 status_columns = ["status"]
86 table_actions = (FloatingIPFilterAction,
87 AdminAllocateFloatingIP,
88 AdminReleaseFloatingIP)
89 row_actions = (AdminSimpleDisassociateIP,
90 AdminReleaseFloatingIP)
91 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 0000000..3998de5
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/floating_ips/templates/floating_ips/_allocate.html
@@ -0,0 +1,9 @@
1{% extends "horizon/common/_modal_form.html" %}
2{% load i18n %}
3
4{% block modal-header %}{% trans "Allocate Floating IP" %}{% endblock %}
5
6{% block modal-body-right %}
7 <h3>{% trans "Description:" %}</h3>
8 <p>{% trans "From here you can allocate a floating IP to a specific project." %}</p>
9{% 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 0000000..fb7f9e6
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/floating_ips/templates/floating_ips/allocate.html
@@ -0,0 +1,7 @@
1{% extends 'base.html' %}
2{% load i18n %}
3{% block title %}{% trans "Allocate Floating IP" %}{% endblock %}
4
5{% block main %}
6 {% include 'admin/floating_ips/_allocate.html' %}
7{% 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 0000000..8ea9a7e
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/floating_ips/templates/floating_ips/detail.html
@@ -0,0 +1,48 @@
1{% extends 'base.html' %}
2{% load i18n sizeformat %}
3
4{% block title %}{% trans "Floating IP Details"%}{% endblock %}
5
6{% block page_header %}
7 {% include "horizon/common/_detail_header.html" %}
8{% endblock %}
9
10{% block main %}
11<div class="detail">
12 <dl class="dl-horizontal">
13 <dt>{% trans "ID" %}</dt>
14 <dd>{{ floating_ip.id|default:_("None") }}</dd>
15
16 <dt>{% trans "Project ID" %}</dt>
17 <dd>{{ floating_ip.tenant_id|default:"-" }}</dd>
18
19 <dt>{% trans "Floating IP address" %}</dt>
20 <dd>{{ floating_ip.ip|default:_("None") }}</dd>
21 <dt>{% trans "Status" %}</dt>
22 <dd>{{ floating_ip.status|default:_("None") }}</dd>
23
24 <dt>{% trans "Pool" %}</dt>
25 {% url 'horizon:admin:networks:detail' floating_ip.pool as network_url %}
26 <dd><a href="{{ network_url }}">{{ floating_ip.pool_name|default:_("None") }}</a></dd>
27
28 <dt>{% trans "Mapped IP Address" %}</dt>
29 {% if floating_ip.instance_id and floating_ip.instance_type == 'compute' %}
30 {% url 'horizon:admin:instances:detail' floating_ip.instance_id as instance_url %}
31 <dd><a href="{{ instance_url }}">{{ floating_ip.mapped_fixed_ip }}</a></dd>
32 {% elif floating_ip.port_id and floating_ip.fixed_ip and floating_ip.instance_type != 'compute' %}
33 {% url 'horizon:admin:networks:ports:detail' floating_ip.port_id as port_url %}
34 <dd><a href="{{ port_url }}">{{ floating_ip.fixed_ip }}</a></dd>
35 {% else %}
36 <dd>{% trans "No associated fixed IP" %}</dd>
37 {% endif %}
38
39 <dt>{% trans "Router" %}</dt>
40 {% if floating_ip.router_id %}
41 {% url 'horizon:admin:routers:detail' floating_ip.router_id as router_url %}
42 <dd><a href="{{ router_url }}">{{ floating_ip.router_name }}</a></dd>
43 {% else %}
44 <dd>{% trans "No router" %}</dd>
45 {% endif %}
46 </dl>
47</div>
48{% 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 0000000..24dbc00
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/floating_ips/templates/floating_ips/index.html
@@ -0,0 +1,7 @@
1{% extends 'base.html' %}
2{% load i18n %}
3{% block title %}{% trans "Floating IPs" %}{% endblock %}
4
5{% block main %}
6 {{ table.render }}
7{% 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 0000000..a67b901
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/floating_ips/tests.py
@@ -0,0 +1,275 @@
1# Copyright 2016 Letv Cloud Computing
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
16from django.core.urlresolvers import reverse
17from django import http
18from mox3.mox import IsA # noqa
19
20from openstack_dashboard import api
21from openstack_dashboard.test import helpers as test
22
23INDEX_URL = reverse('horizon:admin:floating_ips:index')
24
25
26class AdminFloatingIpViewTest(test.BaseAdminViewTests):
27 @test.create_stubs({api.network: ('tenant_floating_ip_list', ),
28 api.nova: ('server_list', ),
29 api.keystone: ('tenant_list', ),
30 api.neutron: ('network_list', )})
31 def test_index(self):
32 # Use neutron test data
33 fips = self.q_floating_ips.list()
34 servers = self.servers.list()
35 tenants = self.tenants.list()
36 api.network.tenant_floating_ip_list(IsA(http.HttpRequest),
37 all_tenants=True).AndReturn(fips)
38 api.nova.server_list(IsA(http.HttpRequest), all_tenants=True) \
39 .AndReturn([servers, False])
40 api.keystone.tenant_list(IsA(http.HttpRequest))\
41 .AndReturn([tenants, False])
42 params = {"router:external": True}
43 api.neutron.network_list(IsA(http.HttpRequest), **params) \
44 .AndReturn(self.networks.list())
45
46 self.mox.ReplayAll()
47
48 res = self.client.get(INDEX_URL)
49 self.assertTemplateUsed(res, 'admin/floating_ips/index.html')
50 self.assertIn('floating_ips_table', res.context)
51 floating_ips_table = res.context['floating_ips_table']
52 floating_ips = floating_ips_table.data
53 self.assertEqual(len(floating_ips), 2)
54
55 row_actions = floating_ips_table.get_row_actions(floating_ips[0])
56 self.assertEqual(len(row_actions), 1)
57 row_actions = floating_ips_table.get_row_actions(floating_ips[1])
58 self.assertEqual(len(row_actions), 2)
59
60 @test.create_stubs({api.network: ('tenant_floating_ip_get', ),
61 api.neutron: ('network_get', )})
62 def test_floating_ip_detail_get(self):
63 fip = self.q_floating_ips.first()
64 network = self.networks.first()
65 api.network.tenant_floating_ip_get(
66 IsA(http.HttpRequest), fip.id).AndReturn(fip)
67 api.neutron.network_get(
68 IsA(http.HttpRequest), fip.pool).AndReturn(network)
69 self.mox.ReplayAll()
70
71 res = self.client.get(reverse('horizon:admin:floating_ips:detail',
72 args=[fip.id]))
73 self.assertTemplateUsed(res,
74 'admin/floating_ips/detail.html')
75 self.assertEqual(res.context['floating_ip'].ip, fip.ip)
76
77 @test.create_stubs({api.network: ('tenant_floating_ip_get',)})
78 def test_floating_ip_detail_exception(self):
79 fip = self.q_floating_ips.first()
80 # Only supported by neutron, so raise a neutron exception
81 api.network.tenant_floating_ip_get(
82 IsA(http.HttpRequest),
83 fip.id).AndRaise(self.exceptions.neutron)
84
85 self.mox.ReplayAll()
86
87 res = self.client.get(reverse('horizon:admin:floating_ips:detail',
88 args=[fip.id]))
89
90 self.assertRedirectsNoFollow(res, INDEX_URL)
91
92 @test.create_stubs({api.network: ('tenant_floating_ip_list', )})
93 def test_index_no_floating_ips(self):
94 api.network.tenant_floating_ip_list(IsA(http.HttpRequest),
95 all_tenants=True).AndReturn([])
96 self.mox.ReplayAll()
97
98 res = self.client.get(INDEX_URL)
99 self.assertTemplateUsed(res, 'admin/floating_ips/index.html')
100
101 @test.create_stubs({api.network: ('tenant_floating_ip_list', )})
102 def test_index_error(self):
103 api.network.tenant_floating_ip_list(IsA(http.HttpRequest),
104 all_tenants=True) \
105 .AndRaise(self.exceptions.neutron)
106 self.mox.ReplayAll()
107
108 res = self.client.get(INDEX_URL)
109 self.assertTemplateUsed(res, 'admin/floating_ips/index.html')
110
111 @test.create_stubs({api.neutron: ('network_list',),
112 api.keystone: ('tenant_list',)})
113 def test_admin_allocate_get(self):
114 pool = self.networks.first()
115 tenants = self.tenants.list()
116
117 api.keystone.tenant_list(IsA(http.HttpRequest))\
118 .AndReturn([tenants, False])
119 search_opts = {'router:external': True}
120 api.neutron.network_list(IsA(http.HttpRequest), **search_opts) \
121 .AndReturn([pool])
122
123 self.mox.ReplayAll()
124
125 url = reverse('horizon:admin:floating_ips:allocate')
126 res = self.client.get(url)
127 self.assertTemplateUsed(res, 'admin/floating_ips/allocate.html')
128 allocate_form = res.context['form']
129
130 pool_choices = allocate_form.fields['pool'].choices
131 self.assertEqual(len(pool_choices), 1)
132 tenant_choices = allocate_form.fields['tenant'].choices
133 self.assertEqual(len(tenant_choices), 3)
134
135 @test.create_stubs({api.neutron: ('network_list',),
136 api.keystone: ('tenant_list',)})
137 def test_admin_allocate_post_invalid_ip_version(self):
138 tenant = self.tenants.first()
139 pool = self.networks.first()
140 tenants = self.tenants.list()
141
142 api.keystone.tenant_list(IsA(http.HttpRequest))\
143 .AndReturn([tenants, False])
144 search_opts = {'router:external': True}
145 api.neutron.network_list(IsA(http.HttpRequest), **search_opts) \
146 .AndReturn([pool])
147 self.mox.ReplayAll()
148
149 form_data = {'pool': pool.id,
150 'tenant': tenant.id,
151 'floating_ip_address': 'fc00::1'}
152 url = reverse('horizon:admin:floating_ips:allocate')
153 res = self.client.post(url, form_data)
154 self.assertContains(res, "Invalid version for IP address")
155
156 @test.create_stubs({api.network: ('tenant_floating_ip_allocate',),
157 api.neutron: ('network_list',),
158 api.keystone: ('tenant_list',)})
159 def test_admin_allocate_post(self):
160 tenant = self.tenants.first()
161 floating_ip = self.floating_ips.first()
162 pool = self.networks.first()
163 tenants = self.tenants.list()
164
165 api.keystone.tenant_list(IsA(http.HttpRequest))\
166 .AndReturn([tenants, False])
167 search_opts = {'router:external': True}
168 api.neutron.network_list(IsA(http.HttpRequest), **search_opts) \
169 .AndReturn([pool])
170 api.network.tenant_floating_ip_allocate(
171 IsA(http.HttpRequest),
172 pool=pool.id,
173 tenant_id=tenant.id).AndReturn(floating_ip)
174 self.mox.ReplayAll()
175
176 form_data = {'pool': pool.id,
177 'tenant': tenant.id}
178 url = reverse('horizon:admin:floating_ips:allocate')
179 res = self.client.post(url, form_data)
180 self.assertRedirectsNoFollow(res, INDEX_URL)
181
182 @test.create_stubs({api.network: ('tenant_floating_ip_list',
183 'floating_ip_disassociate'),
184 api.nova: ('server_list', ),
185 api.keystone: ('tenant_list', ),
186 api.neutron: ('network_list', )})
187 def test_admin_disassociate_floatingip(self):
188 # Use neutron test data
189 fips = self.q_floating_ips.list()
190 floating_ip = self.q_floating_ips.list()[1]
191 servers = self.servers.list()
192 tenants = self.tenants.list()
193 api.network.tenant_floating_ip_list(IsA(http.HttpRequest),
194 all_tenants=True).AndReturn(fips)
195 api.nova.server_list(IsA(http.HttpRequest), all_tenants=True) \
196 .AndReturn([servers, False])
197 api.keystone.tenant_list(IsA(http.HttpRequest))\
198 .AndReturn([tenants, False])
199 params = {"router:external": True}
200 api.neutron.network_list(IsA(http.HttpRequest), **params) \
201 .AndReturn(self.networks.list())
202 api.network.floating_ip_disassociate(IsA(http.HttpRequest),
203 floating_ip.id)
204 self.mox.ReplayAll()
205
206 form_data = {
207 "action":
208 "floating_ips__disassociate__%s" % floating_ip.id}
209 res = self.client.post(INDEX_URL, form_data)
210
211 self.assertNoFormErrors(res)
212
213 @test.create_stubs({api.network: ('tenant_floating_ip_list', ),
214 api.nova: ('server_list', ),
215 api.keystone: ('tenant_list', ),
216 api.neutron: ('network_list', )})
217 def test_admin_delete_floatingip(self):
218 # Use neutron test data
219 fips = self.q_floating_ips.list()
220 floating_ip = self.q_floating_ips.list()[1]
221 servers = self.servers.list()
222 tenants = self.tenants.list()
223 api.network.tenant_floating_ip_list(IsA(http.HttpRequest),
224 all_tenants=True).AndReturn(fips)
225 api.nova.server_list(IsA(http.HttpRequest), all_tenants=True) \
226 .AndReturn([servers, False])
227 api.keystone.tenant_list(IsA(http.HttpRequest))\
228 .AndReturn([tenants, False])
229 params = {"router:external": True}
230 api.neutron.network_list(IsA(http.HttpRequest), **params) \
231 .AndReturn(self.networks.list())
232
233 self.mox.ReplayAll()
234
235 form_data = {
236 "action":
237 "floating_ips__delete__%s" % floating_ip.id}
238 res = self.client.post(INDEX_URL, form_data)
239
240 self.assertNoFormErrors(res)
241
242 @test.create_stubs({api.network: ('tenant_floating_ip_list', ),
243 api.nova: ('server_list', ),
244 api.keystone: ('tenant_list', ),
245 api.neutron: ('network_list', )})
246 def test_floating_ip_table_actions(self):
247 # Use neutron test data
248 fips = self.q_floating_ips.list()
249 servers = self.servers.list()
250 tenants = self.tenants.list()
251 api.network.tenant_floating_ip_list(IsA(http.HttpRequest),
252 all_tenants=True).AndReturn(fips)
253 api.nova.server_list(IsA(http.HttpRequest), all_tenants=True) \
254 .AndReturn([servers, False])
255 api.keystone.tenant_list(IsA(http.HttpRequest))\
256 .AndReturn([tenants, False])
257 params = {"router:external": True}
258 api.neutron.network_list(IsA(http.HttpRequest), **params) \
259 .AndReturn(self.networks.list())
260
261 self.mox.ReplayAll()
262
263 res = self.client.get(INDEX_URL)
264 self.assertTemplateUsed(res, 'admin/floating_ips/index.html')
265 self.assertIn('floating_ips_table', res.context)
266 floating_ips_table = res.context['floating_ips_table']
267 floating_ips = floating_ips_table.data
268 self.assertEqual(len(floating_ips), 2)
269 # table actions
270 self.assertContains(res, 'id="floating_ips__action_allocate"')
271 self.assertContains(res, 'id="floating_ips__action_release"')
272 # row actions
273 self.assertContains(res, 'floating_ips__release__%s' % fips[0].id)
274 self.assertContains(res, 'floating_ips__release__%s' % fips[1].id)
275 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 0000000..8c19a52
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/floating_ips/urls.py
@@ -0,0 +1,26 @@
1# Copyright 2016 Letv Cloud Computing
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
16from django.conf.urls import url
17
18from openstack_dashboard.dashboards.admin.floating_ips import views
19
20
21urlpatterns = [
22 url(r'^$', views.IndexView.as_view(), name='index'),
23 url(r'^allocate/$', views.AllocateView.as_view(), name='allocate'),
24 url(r'^(?P<floating_ip_id>[^/]+)/detail/$',
25 views.DetailView.as_view(), name='detail')
26]
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 0000000..05919d2
--- /dev/null
+++ b/openstack_dashboard/dashboards/admin/floating_ips/views.py
@@ -0,0 +1,189 @@
1# Copyright 2016 Letv Cloud Computing
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
16from collections import OrderedDict
17
18from django.core.urlresolvers import reverse
19from django.core.urlresolvers import reverse_lazy
20from django.utils.translation import ugettext_lazy as _
21import netaddr
22
23from horizon import exceptions
24from horizon import forms
25from horizon import tables
26from horizon.utils import memoized
27from horizon import views
28
29from openstack_dashboard import api
30
31from openstack_dashboard.dashboards.admin.floating_ips \
32 import forms as fip_forms
33from openstack_dashboard.dashboards.admin.floating_ips \
34 import tables as fip_tables
35from openstack_dashboard.dashboards.project.access_and_security.\
36 floating_ips import tables as project_tables
37
38
39def get_floatingip_pools(request):
40 pools = []
41 try:
42 search_opts = {'router:external': True}
43 pools = api.neutron.network_list(request, **search_opts)
44 except Exception:
45 exceptions.handle(request,
46 _("Unable to retrieve floating IP pools."))
47 return pools
48
49
50def get_tenant_list(request):
51 tenants = []
52 try:
53 tenants, has_more = api.keystone.tenant_list(request)
54 except Exception:
55 msg = _('Unable to retrieve project list.')
56 exceptions.handle(request, msg)
57 return tenants
58
59
60class IndexView(tables.DataTableView):
61 table_class = fip_tables.FloatingIPsTable
62 template_name = 'admin/floating_ips/index.html'
63 page_title = _("Floating IPs")
64
65 @memoized.memoized_method
66 def get_data(self):
67 floating_ips = []
68 try:
69 floating_ips = api.network.tenant_floating_ip_list(
70 self.request,
71 all_tenants=True)
72 except Exception:
73 exceptions.handle(self.request,
74 _('Unable to retrieve floating IP list.'))
75
76 if floating_ips:
77 instances = []
78 try:
79 instances, has_more = api.nova.server_list(self.request,
80 all_tenants=True)
81 except Exception:
82 exceptions.handle(
83 self.request,
84 _('Unable to retrieve instance list.'))
85 instances_dict = dict([(obj.id, obj.name) for obj in instances])
86
87 tenants = get_tenant_list(self.request)
88 tenant_dict = OrderedDict([(t.id, t) for t in tenants])
89
90 pools = get_floatingip_pools(self.request)
91 pool_dict = dict([(obj.id, obj.name) for obj in pools])
92
93 for ip in floating_ips:
94 ip.instance_name = instances_dict.get(ip.instance_id)
95 ip.pool_name = pool_dict.get(ip.pool, ip.pool)
96 tenant = tenant_dict.get(ip.tenant_id, None)
97 ip.tenant_name = getattr(tenant, "name", None)
98
99 return floating_ips
100
101
102class DetailView(views.HorizonTemplateView):
103 template_name = 'admin/floating_ips/detail.html'
104 page_title = _("Floating IP Details")
105
106 def _get_corresponding_data(self, resource, resource_id):
107 function_dict = {"floating IP": api.network.tenant_floating_ip_get,
108 "instance": api.nova.server_get,
109 "network": api.neutron.network_get,
110 "router": api.neutron.router_get}
111 url = reverse('horizon:admin:floating_ips:index')
112 try:
113 res = function_dict[resource](
114 self.request, resource_id)
115 if resource in ["network", "router"]:
116 res.set_id_as_name_if_empty(length=0)
117 return res
118 except KeyError:
119 msg = _('Unknow resource type for detail API.')
120 exceptions.handle(self.request, msg, redirect=url)
121 except Exception:
122 msg = _('Unable to retrieve details for '
123 '%(resource)s "%(resource_id)s".') % {
124 "resource": resource,
125 "resource_id": resource_id}
126 exceptions.handle(self.request, msg, redirect=url)
127
128 def get_context_data(self, **kwargs):
129 context = super(DetailView, self).get_context_data(**kwargs)
130
131 floating_ip_id = self.kwargs['floating_ip_id']
132 floating_ip = self._get_corresponding_data("floating IP",
133 floating_ip_id)
134
135 network = self._get_corresponding_data("network", floating_ip.pool)
136 floating_ip.pool_name = network.name
137
138 if floating_ip.instance_id and floating_ip.instance_type == 'compute':
139 instance = self._get_corresponding_data(
140 "instance", floating_ip.instance_id)
141 floating_ip.instance_name = instance.name
142 floating_ip.mapped_fixed_ip = project_tables.get_instance_info(
143 floating_ip)
144
145 if floating_ip.router_id:
146 router = self._get_corresponding_data("router",
147 floating_ip.router_id)
148 floating_ip.router_name = router.name
149 table = fip_tables.FloatingIPsTable(self.request)
150 context['floating_ip'] = floating_ip
151 context["url"] = reverse('horizon:admin:floating_ips:index')
152 context["actions"] = table.render_row_actions(floating_ip)
153 return context
154
155
156class AllocateView(forms.ModalFormView):
157 form_class = fip_forms.AdminFloatingIpAllocate
158 form_id = "allocate_floating_ip_form"
159 template_name = 'admin/floating_ips/allocate.html'
160 modal_header = _("Allocate Floating IP")
161 submit_label = _("Allocate Floating IP")
162 submit_url = reverse_lazy("horizon:admin:floating_ips:allocate")
163 cancel_url = reverse_lazy('horizon:admin:floating_ips:index')
164 success_url = reverse_lazy('horizon:admin:floating_ips:index')
165 page_title = _("Allocate Floating IP")
166
167 @memoized.memoized_method
168 def get_initial(self):
169 tenants = get_tenant_list(self.request)
170 tenant_list = [(t.id, t.name) for t in tenants]
171 if not tenant_list:
172 tenant_list = [(None, _("No project available"))]
173
174 pools = get_floatingip_pools(self.request)
175 pool_list = []
176 for pool in pools:
177 for subnet in pool.subnets:
178 if netaddr.IPNetwork(subnet.cidr).version != 4:
179 continue
180 pool_display_name = (_("%(cidr)s %(pool_name)s")
181 % {'cidr': subnet.cidr,
182 'pool_name': pool.name})
183 pool_list.append((pool.id, pool_display_name))
184 if not pool_list:
185 pool_list = [
186 (None, _("No floating IP pools with IPv4 subnet available"))]
187
188 return {'pool_list': pool_list,
189 '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 0000000..a3b03a4
--- /dev/null
+++ b/openstack_dashboard/enabled/_2111_admin_floating_ips_panel.py
@@ -0,0 +1,10 @@
1# The slug of the panel to be added to HORIZON_CONFIG. Required.
2PANEL = 'floating_ips'
3# The slug of the dashboard the PANEL associated with. Required.
4PANEL_DASHBOARD = 'admin'
5# The slug of the panel group the PANEL is associated with.
6PANEL_GROUP = 'admin'
7
8# Python panel class of the PANEL to be added.
9ADD_PANEL = \
10 '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 70d386d..b841661 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):
127 fips = self.api_floating_ips.list() 127 fips = self.api_floating_ips.list()
128 novaclient = self.stub_novaclient() 128 novaclient = self.stub_novaclient()
129 novaclient.floating_ips = self.mox.CreateMockAnything() 129 novaclient.floating_ips = self.mox.CreateMockAnything()
130 novaclient.floating_ips.list().AndReturn(fips) 130 novaclient.floating_ips.list(all_tenants=False).AndReturn(fips)
131 self.mox.ReplayAll() 131 self.mox.ReplayAll()
132 132
133 ret = api.network.tenant_floating_ip_list(self.request) 133 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 b192db1..e005a71 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):
440 'port_id': None, 440 'port_id': None,
441 'router_id': None} 441 'router_id': None}
442 TEST.api_q_floating_ips.add(fip_dict) 442 TEST.api_q_floating_ips.add(fip_dict)
443 TEST.q_floating_ips.add(neutron.FloatingIp(fip_dict)) 443 fip_with_instance = copy.deepcopy(fip_dict)
444 fip_with_instance.update({'instance_id': None,
445 'instance_type': None})
446 TEST.q_floating_ips.add(neutron.FloatingIp(fip_with_instance))
444 447
445 # Associated (with compute port on 1st network). 448 # Associated (with compute port on 1st network).
446 fip_dict = {'tenant_id': '1', 449 fip_dict = {'tenant_id': '1',
@@ -451,7 +454,10 @@ def data(TEST):
451 'port_id': assoc_port['id'], 454 'port_id': assoc_port['id'],
452 'router_id': router_dict['id']} 455 'router_id': router_dict['id']}
453 TEST.api_q_floating_ips.add(fip_dict) 456 TEST.api_q_floating_ips.add(fip_dict)
454 TEST.q_floating_ips.add(neutron.FloatingIp(fip_dict)) 457 fip_with_instance = copy.deepcopy(fip_dict)
458 fip_with_instance.update({'instance_id': '1',
459 'instance_type': 'compute'})
460 TEST.q_floating_ips.add(neutron.FloatingIp(fip_with_instance))
455 461
456 # Security group. 462 # Security group.
457 463
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 0000000..6237468
--- /dev/null
+++ b/releasenotes/notes/bp-admin-manage-fips-5aa409d3502b031a.yaml
@@ -0,0 +1,4 @@
1---
2features:
3 - >
4 [`blueprint manage-ips Add ability to manage floating IPs in syspanel <https://blueprints.launchpad.net/horizon/+spec/manage-ips>`_] Admin dashboard Floating IPs panel has been added to Horizon.