Browse Source

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
tags/10.0.0.0b3
LIU Yulong 4 years ago
parent
commit
5c238e9117

+ 6
- 4
openstack_dashboard/api/network.py View File

@@ -50,16 +50,18 @@ def floating_ip_pools_list(request):
50 50
     return NetworkClient(request).floating_ips.list_pools()
51 51
 
52 52
 
53
-def tenant_floating_ip_list(request):
54
-    return NetworkClient(request).floating_ips.list()
53
+def tenant_floating_ip_list(request, all_tenants=False):
54
+    return NetworkClient(request).floating_ips.list(all_tenants=all_tenants)
55 55
 
56 56
 
57 57
 def tenant_floating_ip_get(request, floating_ip_id):
58 58
     return NetworkClient(request).floating_ips.get(floating_ip_id)
59 59
 
60 60
 
61
-def tenant_floating_ip_allocate(request, pool=None):
62
-    return NetworkClient(request).floating_ips.allocate(pool)
61
+def tenant_floating_ip_allocate(request, pool=None, tenant_id=None, **params):
62
+    return NetworkClient(request).floating_ips.allocate(pool,
63
+                                                        tenant_id,
64
+                                                        **params)
63 65
 
64 66
 
65 67
 def tenant_floating_ip_release(request, floating_ip_id):

+ 3
- 3
openstack_dashboard/api/network_base.py View File

@@ -51,8 +51,8 @@ class FloatingIpManager(object):
51 51
         pass
52 52
 
53 53
     @abc.abstractmethod
54
-    def list(self):
55
-        """Fetches a list all floating IPs.
54
+    def list(self, all_tenants=False):
55
+        """Fetches a list of all floating IPs.
56 56
 
57 57
         A returned value is a list of FloatingIp object.
58 58
         """
@@ -67,7 +67,7 @@ class FloatingIpManager(object):
67 67
         pass
68 68
 
69 69
     @abc.abstractmethod
70
-    def allocate(self, pool=None):
70
+    def allocate(self, pool=None, tenant_id=None, **params):
71 71
         """Allocates a floating IP to the tenant.
72 72
 
73 73
         You must provide a pool name or id for which you would like to

+ 9
- 4
openstack_dashboard/api/neutron.py View File

@@ -417,10 +417,15 @@ class FloatingIpManager(network_base.FloatingIpManager):
417 417
         self._set_instance_info(fip)
418 418
         return FloatingIp(fip)
419 419
 
420
-    def allocate(self, pool):
421
-        body = {'floatingip': {'floating_network_id': pool,
422
-                               'tenant_id': self.request.user.project_id}}
423
-        fip = self.client.create_floatingip(body).get('floatingip')
420
+    def allocate(self, pool, tenant_id=None, **params):
421
+        if not tenant_id:
422
+            tenant_id = self.request.user.project_id
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 429
         self._set_instance_info(fip)
425 430
         return FloatingIp(fip)
426 431
 

+ 6
- 4
openstack_dashboard/api/nova.py View File

@@ -415,14 +415,16 @@ class FloatingIpManager(network_base.FloatingIpManager):
415 415
         return [FloatingIpPool(pool)
416 416
                 for pool in self.client.floating_ip_pools.list()]
417 417
 
418
-    def list(self):
419
-        return [FloatingIp(fip)
420
-                for fip in self.client.floating_ips.list()]
418
+    def list(self, all_tenants=False):
419
+        return [FloatingIp(fip) for fip in
420
+                self.client.floating_ips.list(
421
+                    all_tenants=all_tenants)]
421 422
 
422 423
     def get(self, floating_ip_id):
423 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 428
         return FloatingIp(self.client.floating_ips.create(pool=pool))
427 429
 
428 430
     def release(self, floating_ip_id):

+ 0
- 0
openstack_dashboard/dashboards/admin/floating_ips/__init__.py View File


+ 64
- 0
openstack_dashboard/dashboards/admin/floating_ips/forms.py View File

@@ -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
+
16
+from django.core.urlresolvers import reverse
17
+from django.utils.translation import ugettext_lazy as _
18
+
19
+from horizon import exceptions
20
+from horizon import forms
21
+from horizon import messages
22
+
23
+from openstack_dashboard import api
24
+
25
+
26
+class 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)

+ 30
- 0
openstack_dashboard/dashboards/admin/floating_ips/panel.py View File

@@ -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
+
16
+from django.conf import settings
17
+from django.utils.translation import ugettext_lazy as _
18
+
19
+import horizon
20
+
21
+
22
+class 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)

+ 91
- 0
openstack_dashboard/dashboards/admin/floating_ips/tables.py View File

@@ -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
+
16
+import logging
17
+
18
+from django import shortcuts
19
+from django.utils.translation import ugettext_lazy as _
20
+
21
+from horizon import exceptions
22
+from horizon import messages
23
+from horizon import tables
24
+
25
+from openstack_dashboard import api
26
+from openstack_dashboard import policy
27
+from openstack_dashboard.dashboards.project.access_and_security.\
28
+    floating_ips import tables as project_tables
29
+from openstack_dashboard.utils import filters
30
+
31
+
32
+LOG = logging.getLogger(__name__)
33
+
34
+
35
+class 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
+
44
+class 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
+
55
+class AdminReleaseFloatingIP(project_tables.ReleaseIPs):
56
+    pass
57
+
58
+
59
+class 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
+
75
+class 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')

+ 9
- 0
openstack_dashboard/dashboards/admin/floating_ips/templates/floating_ips/_allocate.html View File

@@ -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 %}

+ 7
- 0
openstack_dashboard/dashboards/admin/floating_ips/templates/floating_ips/allocate.html View File

@@ -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 %}

+ 48
- 0
openstack_dashboard/dashboards/admin/floating_ips/templates/floating_ips/detail.html View File

@@ -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 %}

+ 7
- 0
openstack_dashboard/dashboards/admin/floating_ips/templates/floating_ips/index.html View File

@@ -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 %}

+ 275
- 0
openstack_dashboard/dashboards/admin/floating_ips/tests.py View File

@@ -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
+
16
+from django.core.urlresolvers import reverse
17
+from django import http
18
+from mox3.mox import IsA  # noqa
19
+
20
+from openstack_dashboard import api
21
+from openstack_dashboard.test import helpers as test
22
+
23
+INDEX_URL = reverse('horizon:admin:floating_ips:index')
24
+
25
+
26
+class 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)

+ 26
- 0
openstack_dashboard/dashboards/admin/floating_ips/urls.py View File

@@ -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
+
16
+from django.conf.urls import url
17
+
18
+from openstack_dashboard.dashboards.admin.floating_ips import views
19
+
20
+
21
+urlpatterns = [
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
+]

+ 189
- 0
openstack_dashboard/dashboards/admin/floating_ips/views.py View File

@@ -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
+
16
+from collections import OrderedDict
17
+
18
+from django.core.urlresolvers import reverse
19
+from django.core.urlresolvers import reverse_lazy
20
+from django.utils.translation import ugettext_lazy as _
21
+import netaddr
22
+
23
+from horizon import exceptions
24
+from horizon import forms
25
+from horizon import tables
26
+from horizon.utils import memoized
27
+from horizon import views
28
+
29
+from openstack_dashboard import api
30
+
31
+from openstack_dashboard.dashboards.admin.floating_ips \
32
+    import forms as fip_forms
33
+from openstack_dashboard.dashboards.admin.floating_ips \
34
+    import tables as fip_tables
35
+from openstack_dashboard.dashboards.project.access_and_security.\
36
+    floating_ips import tables as project_tables
37
+
38
+
39
+def 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
+
50
+def 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
+
60
+class 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
+
102
+class 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
+
156
+class 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}

+ 10
- 0
openstack_dashboard/enabled/_2111_admin_floating_ips_panel.py View File

@@ -0,0 +1,10 @@
1
+# The slug of the panel to be added to HORIZON_CONFIG. Required.
2
+PANEL = 'floating_ips'
3
+# The slug of the dashboard the PANEL associated with. Required.
4
+PANEL_DASHBOARD = 'admin'
5
+# The slug of the panel group the PANEL is associated with.
6
+PANEL_GROUP = 'admin'
7
+
8
+# Python panel class of the PANEL to be added.
9
+ADD_PANEL = \
10
+    'openstack_dashboard.dashboards.admin.floating_ips.panel.AdminFloatingIps'

+ 1
- 1
openstack_dashboard/test/api_tests/network_tests.py View File

@@ -127,7 +127,7 @@ class NetworkApiNovaFloatingIpTests(NetworkApiNovaTestBase):
127 127
         fips = self.api_floating_ips.list()
128 128
         novaclient = self.stub_novaclient()
129 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 131
         self.mox.ReplayAll()
132 132
 
133 133
         ret = api.network.tenant_floating_ip_list(self.request)

+ 8
- 2
openstack_dashboard/test/test_data/neutron_data.py View File

@@ -440,7 +440,10 @@ def data(TEST):
440 440
                 'port_id': None,
441 441
                 'router_id': None}
442 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 448
     # Associated (with compute port on 1st network).
446 449
     fip_dict = {'tenant_id': '1',
@@ -451,7 +454,10 @@ def data(TEST):
451 454
                 'port_id': assoc_port['id'],
452 455
                 'router_id': router_dict['id']}
453 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 462
     # Security group.
457 463
 

+ 4
- 0
releasenotes/notes/bp-admin-manage-fips-5aa409d3502b031a.yaml View File

@@ -0,0 +1,4 @@
1
+---
2
+features:
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.

Loading…
Cancel
Save