summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRob Cresswell <robert.cresswell@outlook.com>2017-01-26 11:17:59 +0000
committerRob Cresswell <robert.cresswell@outlook.com>2017-02-01 18:57:44 +0000
commit99849ad88fef36ed978d16f1d20a5e3b66200da1 (patch)
tree704be740f5e7e5b9f92e96e2c1bf3d878a31917e
parent4f654e30c314e25dded653a9d3d9e0a02ba1d42b (diff)
Move Floating IPs from Access & Security to panel
This patch makes the Floating IPs tab in Access & Security its own panel under Project > Network Change-Id: Ibb83ae5a0448d2824c10f867e620cec8219b7b72 Implements: blueprint reorganise-access-and-security
Notes
Notes (review): Code-Review+2: David Lyle <dklyle0@gmail.com> Code-Review+2: Richard Jones <r1chardj0n3s@gmail.com> Workflow+1: Richard Jones <r1chardj0n3s@gmail.com> Verified+2: Jenkins Submitted-by: Jenkins Submitted-at: Thu, 02 Feb 2017 02:38:02 +0000 Reviewed-on: https://review.openstack.org/425783 Project: openstack/horizon Branch: refs/heads/master
-rw-r--r--openstack_dashboard/dashboards/admin/floating_ips/tables.py4
-rw-r--r--openstack_dashboard/dashboards/admin/floating_ips/views.py4
-rw-r--r--openstack_dashboard/dashboards/project/access_and_security/tabs.py59
-rw-r--r--openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/floating_ips/allocate.html7
-rw-r--r--openstack_dashboard/dashboards/project/access_and_security/tests.py106
-rw-r--r--openstack_dashboard/dashboards/project/access_and_security/urls.py3
-rw-r--r--openstack_dashboard/dashboards/project/floating_ips/__init__.py (renamed from openstack_dashboard/dashboards/project/access_and_security/floating_ips/__init__.py)0
-rw-r--r--openstack_dashboard/dashboards/project/floating_ips/forms.py (renamed from openstack_dashboard/dashboards/project/access_and_security/floating_ips/forms.py)0
-rw-r--r--openstack_dashboard/dashboards/project/floating_ips/panel.py27
-rw-r--r--openstack_dashboard/dashboards/project/floating_ips/tables.py (renamed from openstack_dashboard/dashboards/project/access_and_security/floating_ips/tables.py)8
-rw-r--r--openstack_dashboard/dashboards/project/floating_ips/templates/floating_ips/_allocate.html (renamed from openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/floating_ips/_allocate.html)0
-rw-r--r--openstack_dashboard/dashboards/project/floating_ips/templates/floating_ips/allocate.html6
-rw-r--r--openstack_dashboard/dashboards/project/floating_ips/tests.py (renamed from openstack_dashboard/dashboards/project/access_and_security/floating_ips/tests.py)42
-rw-r--r--openstack_dashboard/dashboards/project/floating_ips/urls.py (renamed from openstack_dashboard/dashboards/project/access_and_security/floating_ips/urls.py)5
-rw-r--r--openstack_dashboard/dashboards/project/floating_ips/views.py (renamed from openstack_dashboard/dashboards/project/access_and_security/floating_ips/views.py)66
-rw-r--r--openstack_dashboard/dashboards/project/floating_ips/workflows.py (renamed from openstack_dashboard/dashboards/project/access_and_security/floating_ips/workflows.py)8
-rw-r--r--openstack_dashboard/dashboards/project/instances/tables.py5
-rw-r--r--openstack_dashboard/enabled/_1490_project_floating_ips_panel.py6
18 files changed, 125 insertions, 231 deletions
diff --git a/openstack_dashboard/dashboards/admin/floating_ips/tables.py b/openstack_dashboard/dashboards/admin/floating_ips/tables.py
index 162277f..e9007d6 100644
--- a/openstack_dashboard/dashboards/admin/floating_ips/tables.py
+++ b/openstack_dashboard/dashboards/admin/floating_ips/tables.py
@@ -23,9 +23,9 @@ from horizon import messages
23from horizon import tables 23from horizon import tables
24 24
25from openstack_dashboard import api 25from openstack_dashboard import api
26from openstack_dashboard.dashboards.project.floating_ips \
27 import tables as project_tables
26from openstack_dashboard import policy 28from 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 29from openstack_dashboard.utils import filters
30 30
31 31
diff --git a/openstack_dashboard/dashboards/admin/floating_ips/views.py b/openstack_dashboard/dashboards/admin/floating_ips/views.py
index e84ac7a..ec1ab82 100644
--- a/openstack_dashboard/dashboards/admin/floating_ips/views.py
+++ b/openstack_dashboard/dashboards/admin/floating_ips/views.py
@@ -32,8 +32,8 @@ from openstack_dashboard.dashboards.admin.floating_ips \
32 import forms as fip_forms 32 import forms as fip_forms
33from openstack_dashboard.dashboards.admin.floating_ips \ 33from openstack_dashboard.dashboards.admin.floating_ips \
34 import tables as fip_tables 34 import tables as fip_tables
35from openstack_dashboard.dashboards.project.access_and_security.\ 35from openstack_dashboard.dashboards.project.floating_ips \
36 floating_ips import tables as project_tables 36 import tables as project_tables
37 37
38 38
39def get_floatingip_pools(request): 39def get_floatingip_pools(request):
diff --git a/openstack_dashboard/dashboards/project/access_and_security/tabs.py b/openstack_dashboard/dashboards/project/access_and_security/tabs.py
index c82ccfc..aa1ac0c 100644
--- a/openstack_dashboard/dashboards/project/access_and_security/tabs.py
+++ b/openstack_dashboard/dashboards/project/access_and_security/tabs.py
@@ -25,10 +25,6 @@ from horizon import tabs
25from neutronclient.common import exceptions as neutron_exc 25from neutronclient.common import exceptions as neutron_exc
26 26
27from openstack_dashboard.api import network 27from openstack_dashboard.api import network
28from openstack_dashboard.api import nova
29
30from openstack_dashboard.dashboards.project.access_and_security.\
31 floating_ips.tables import FloatingIPsTable
32from openstack_dashboard.dashboards.project.access_and_security.\ 28from openstack_dashboard.dashboards.project.access_and_security.\
33 security_groups.tables import SecurityGroupsTable 29 security_groups.tables import SecurityGroupsTable
34 30
@@ -53,60 +49,7 @@ class SecurityGroupsTab(tabs.TableTab):
53 return sorted(security_groups, key=lambda group: group.name) 49 return sorted(security_groups, key=lambda group: group.name)
54 50
55 51
56class FloatingIPsTab(tabs.TableTab):
57 table_classes = (FloatingIPsTable,)
58 name = _("Floating IPs")
59 slug = "floating_ips_tab"
60 template_name = "horizon/common/_detail_table.html"
61 permissions = ('openstack.services.compute',)
62
63 def get_floating_ips_data(self):
64 try:
65 floating_ips = network.tenant_floating_ip_list(self.request)
66 except neutron_exc.ConnectionFailed:
67 floating_ips = []
68 exceptions.handle(self.request)
69 except Exception:
70 floating_ips = []
71 exceptions.handle(self.request,
72 _('Unable to retrieve floating IP addresses.'))
73
74 try:
75 floating_ip_pools = network.floating_ip_pools_list(self.request)
76 except neutron_exc.ConnectionFailed:
77 floating_ip_pools = []
78 exceptions.handle(self.request)
79 except Exception:
80 floating_ip_pools = []
81 exceptions.handle(self.request,
82 _('Unable to retrieve floating IP pools.'))
83 pool_dict = dict([(obj.id, obj.name) for obj in floating_ip_pools])
84
85 attached_instance_ids = [ip.instance_id for ip in floating_ips
86 if ip.instance_id is not None]
87 if attached_instance_ids:
88 instances = []
89 try:
90 # TODO(tsufiev): we should pass attached_instance_ids to
91 # nova.server_list as soon as Nova API allows for this
92 instances, has_more = nova.server_list(self.request)
93 except Exception:
94 exceptions.handle(self.request,
95 _('Unable to retrieve instance list.'))
96
97 instances_dict = dict([(obj.id, obj.name) for obj in instances])
98
99 for ip in floating_ips:
100 ip.instance_name = instances_dict.get(ip.instance_id)
101 ip.pool_name = pool_dict.get(ip.pool, ip.pool)
102
103 return floating_ips
104
105 def allowed(self, request):
106 return network.floating_ip_supported(request)
107
108
109class AccessAndSecurityTabs(tabs.TabGroup): 52class AccessAndSecurityTabs(tabs.TabGroup):
110 slug = "access_security_tabs" 53 slug = "access_security_tabs"
111 tabs = (SecurityGroupsTab, FloatingIPsTab) 54 tabs = (SecurityGroupsTab,)
112 sticky = True 55 sticky = True
diff --git a/openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/floating_ips/allocate.html b/openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/floating_ips/allocate.html
deleted file mode 100644
index 8679b9e..0000000
--- a/openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/floating_ips/allocate.html
+++ /dev/null
@@ -1,7 +0,0 @@
1{% extends 'base.html' %}
2{% load i18n %}
3{% block title %}{% trans "Allocate Floating IP" %}{% endblock %}
4
5{% block main %}
6 {% include 'project/access_and_security/floating_ips/_allocate.html' %}
7{% endblock %}
diff --git a/openstack_dashboard/dashboards/project/access_and_security/tests.py b/openstack_dashboard/dashboards/project/access_and_security/tests.py
index 60d4d69..c525d16 100644
--- a/openstack_dashboard/dashboards/project/access_and_security/tests.py
+++ b/openstack_dashboard/dashboards/project/access_and_security/tests.py
@@ -23,7 +23,6 @@ from django import http
23from mox3.mox import IsA # noqa 23from mox3.mox import IsA # noqa
24import six 24import six
25 25
26from horizon.workflows import views
27from openstack_dashboard import api 26from openstack_dashboard import api
28from openstack_dashboard.test import helpers as test 27from openstack_dashboard.test import helpers as test
29from openstack_dashboard.usage import quotas 28from openstack_dashboard.usage import quotas
@@ -35,32 +34,14 @@ class AccessAndSecurityTests(test.TestCase):
35 def setUp(self): 34 def setUp(self):
36 super(AccessAndSecurityTests, self).setUp() 35 super(AccessAndSecurityTests, self).setUp()
37 36
38 @test.create_stubs({api.network: ('floating_ip_supported', 37 @test.create_stubs({api.network: ('security_group_list',),
39 'tenant_floating_ip_list',
40 'floating_ip_pools_list',
41 'security_group_list',),
42 api.nova: ('server_list',),
43 api.base: ('is_service_enabled',), 38 api.base: ('is_service_enabled',),
44 quotas: ('tenant_quota_usages',)}) 39 quotas: ('tenant_quota_usages',)})
45 def _test_index(self, instanceless_ips=False): 40 def _test_index(self):
46 sec_groups = self.security_groups.list() 41 sec_groups = self.security_groups.list()
47 floating_ips = self.floating_ips.list()
48 floating_pools = self.pools.list()
49 if instanceless_ips:
50 for fip in floating_ips:
51 fip.instance_id = None
52 quota_data = self.quota_usages.first() 42 quota_data = self.quota_usages.first()
53 quota_data['security_groups']['available'] = 10 43 quota_data['security_groups']['available'] = 10
54 44
55 api.network.floating_ip_supported(IsA(http.HttpRequest)) \
56 .AndReturn(True)
57 if not instanceless_ips:
58 api.nova.server_list(IsA(http.HttpRequest)) \
59 .AndReturn([self.servers.list(), False])
60 api.network.tenant_floating_ip_list(IsA(http.HttpRequest)) \
61 .AndReturn(floating_ips)
62 api.network.floating_ip_pools_list(IsA(http.HttpRequest)) \
63 .AndReturn(floating_pools)
64 api.network.security_group_list(IsA(http.HttpRequest)) \ 45 api.network.security_group_list(IsA(http.HttpRequest)) \
65 .AndReturn(sec_groups) 46 .AndReturn(sec_groups)
66 quotas.tenant_quota_usages(IsA(http.HttpRequest)).MultipleTimes() \ 47 quotas.tenant_quota_usages(IsA(http.HttpRequest)).MultipleTimes() \
@@ -74,8 +55,6 @@ class AccessAndSecurityTests(test.TestCase):
74 res = self.client.get(INDEX_URL) 55 res = self.client.get(INDEX_URL)
75 56
76 self.assertTemplateUsed(res, 'project/access_and_security/index.html') 57 self.assertTemplateUsed(res, 'project/access_and_security/index.html')
77 self.assertItemsEqual(res.context['floating_ips_table'].data,
78 floating_ips)
79 58
80 # Security groups 59 # Security groups
81 sec_groups_from_ctx = res.context['security_groups_table'].data 60 sec_groups_from_ctx = res.context['security_groups_table'].data
@@ -93,81 +72,22 @@ class AccessAndSecurityTests(test.TestCase):
93 def test_index(self): 72 def test_index(self):
94 self._test_index() 73 self._test_index()
95 74
96 def test_index_with_instanceless_fips(self):
97 self._test_index(instanceless_ips=True)
98
99 @test.create_stubs({api.network: ('floating_ip_target_list',
100 'tenant_floating_ip_list',)})
101 def test_association(self):
102 servers = [api.nova.Server(s, self.request)
103 for s in self.servers.list()]
104 # Add duplicate instance name to test instance name with [ID]
105 # Change id and private IP
106 server3 = api.nova.Server(self.servers.first(), self.request)
107 server3.id = 101
108 server3.addresses = deepcopy(server3.addresses)
109 server3.addresses['private'][0]['addr'] = "10.0.0.5"
110 servers.append(server3)
111
112 targets = [api.nova.FloatingIpTarget(s) for s in servers]
113
114 api.network.tenant_floating_ip_list(
115 IsA(http.HttpRequest)) \
116 .AndReturn(self.floating_ips.list())
117 api.network.floating_ip_target_list(
118 IsA(http.HttpRequest)) \
119 .AndReturn(targets)
120
121 self.mox.ReplayAll()
122
123 res = self.client.get(reverse("horizon:project:access_and_security:"
124 "floating_ips:associate"))
125
126 self.assertTemplateUsed(res, views.WorkflowView.template_name)
127 self.assertContains(res, '<option value="1">server_1 (1)</option>')
128 self.assertContains(res, '<option value="101">server_1 (101)</option>')
129 self.assertContains(res, '<option value="2">server_2 (2)</option>')
130
131
132class AccessAndSecurityNeutronProxyTests(AccessAndSecurityTests):
133 def setUp(self):
134 super(AccessAndSecurityNeutronProxyTests, self).setUp()
135 self.floating_ips = self.floating_ips_uuid
136
137 75
138class SecurityGroupTabTests(test.TestCase): 76class SecurityGroupTabTests(test.TestCase):
139 def setUp(self): 77 def setUp(self):
140 super(SecurityGroupTabTests, self).setUp() 78 super(SecurityGroupTabTests, self).setUp()
141 79
142 @test.create_stubs({api.network: ('floating_ip_supported', 80 @test.create_stubs({api.network: ('security_group_list',),
143 'tenant_floating_ip_list',
144 'security_group_list',
145 'floating_ip_pools_list',),
146 api.nova: ('server_list',),
147 quotas: ('tenant_quota_usages',), 81 quotas: ('tenant_quota_usages',),
148 api.base: ('is_service_enabled',)}) 82 api.base: ('is_service_enabled',)})
149 def test_create_button_attributes(self): 83 def test_create_button_attributes(self):
150 floating_ips = self.floating_ips.list()
151 floating_pools = self.pools.list()
152 sec_groups = self.security_groups.list() 84 sec_groups = self.security_groups.list()
153 quota_data = self.quota_usages.first() 85 quota_data = self.quota_usages.first()
154 quota_data['security_groups']['available'] = 10 86 quota_data['security_groups']['available'] = 10
155 87
156 api.network.floating_ip_supported(
157 IsA(http.HttpRequest)) \
158 .AndReturn(True)
159 api.network.tenant_floating_ip_list(
160 IsA(http.HttpRequest)) \
161 .AndReturn(floating_ips)
162 api.network.floating_ip_pools_list(
163 IsA(http.HttpRequest)) \
164 .AndReturn(floating_pools)
165 api.network.security_group_list( 88 api.network.security_group_list(
166 IsA(http.HttpRequest)) \ 89 IsA(http.HttpRequest)) \
167 .AndReturn(sec_groups) 90 .AndReturn(sec_groups)
168 api.nova.server_list(
169 IsA(http.HttpRequest)) \
170 .AndReturn([self.servers.list(), False])
171 quotas.tenant_quota_usages( 91 quotas.tenant_quota_usages(
172 IsA(http.HttpRequest)).MultipleTimes() \ 92 IsA(http.HttpRequest)).MultipleTimes() \
173 .AndReturn(quota_data) 93 .AndReturn(quota_data)
@@ -195,36 +115,18 @@ class SecurityGroupTabTests(test.TestCase):
195 url = 'horizon:project:access_and_security:security_groups:create' 115 url = 'horizon:project:access_and_security:security_groups:create'
196 self.assertEqual(url, create_action.url) 116 self.assertEqual(url, create_action.url)
197 117
198 @test.create_stubs({api.network: ('floating_ip_supported', 118 @test.create_stubs({api.network: ('security_group_list',),
199 'tenant_floating_ip_list',
200 'security_group_list',
201 'floating_ip_pools_list',),
202 api.nova: ('server_list',),
203 quotas: ('tenant_quota_usages',), 119 quotas: ('tenant_quota_usages',),
204 api.base: ('is_service_enabled',)}) 120 api.base: ('is_service_enabled',)})
205 def _test_create_button_disabled_when_quota_exceeded(self, 121 def _test_create_button_disabled_when_quota_exceeded(self,
206 network_enabled): 122 network_enabled):
207 floating_ips = self.floating_ips.list()
208 floating_pools = self.pools.list()
209 sec_groups = self.security_groups.list() 123 sec_groups = self.security_groups.list()
210 quota_data = self.quota_usages.first() 124 quota_data = self.quota_usages.first()
211 quota_data['security_groups']['available'] = 0 125 quota_data['security_groups']['available'] = 0
212 126
213 api.network.floating_ip_supported(
214 IsA(http.HttpRequest)) \
215 .AndReturn(True)
216 api.network.tenant_floating_ip_list(
217 IsA(http.HttpRequest)) \
218 .AndReturn(floating_ips)
219 api.network.floating_ip_pools_list(
220 IsA(http.HttpRequest)) \
221 .AndReturn(floating_pools)
222 api.network.security_group_list( 127 api.network.security_group_list(
223 IsA(http.HttpRequest)) \ 128 IsA(http.HttpRequest)) \
224 .AndReturn(sec_groups) 129 .AndReturn(sec_groups)
225 api.nova.server_list(
226 IsA(http.HttpRequest)) \
227 .AndReturn([self.servers.list(), False])
228 quotas.tenant_quota_usages( 130 quotas.tenant_quota_usages(
229 IsA(http.HttpRequest)).MultipleTimes() \ 131 IsA(http.HttpRequest)).MultipleTimes() \
230 .AndReturn(quota_data) 132 .AndReturn(quota_data)
diff --git a/openstack_dashboard/dashboards/project/access_and_security/urls.py b/openstack_dashboard/dashboards/project/access_and_security/urls.py
index 76e3cc9..81f452f 100644
--- a/openstack_dashboard/dashboards/project/access_and_security/urls.py
+++ b/openstack_dashboard/dashboards/project/access_and_security/urls.py
@@ -20,15 +20,12 @@ from django.conf.urls import include
20from django.conf.urls import url 20from django.conf.urls import url
21 21
22from openstack_dashboard.dashboards.project.access_and_security.\ 22from openstack_dashboard.dashboards.project.access_and_security.\
23 floating_ips import urls as fip_urls
24from openstack_dashboard.dashboards.project.access_and_security.\
25 security_groups import urls as sec_group_urls 23 security_groups import urls as sec_group_urls
26from openstack_dashboard.dashboards.project.access_and_security import views 24from openstack_dashboard.dashboards.project.access_and_security import views
27 25
28 26
29urlpatterns = [ 27urlpatterns = [
30 url(r'^$', views.IndexView.as_view(), name='index'), 28 url(r'^$', views.IndexView.as_view(), name='index'),
31 url(r'floating_ips/', include(fip_urls, namespace='floating_ips')),
32 url(r'security_groups/', 29 url(r'security_groups/',
33 include(sec_group_urls, namespace='security_groups')), 30 include(sec_group_urls, namespace='security_groups')),
34] 31]
diff --git a/openstack_dashboard/dashboards/project/access_and_security/floating_ips/__init__.py b/openstack_dashboard/dashboards/project/floating_ips/__init__.py
index e69de29..e69de29 100644
--- a/openstack_dashboard/dashboards/project/access_and_security/floating_ips/__init__.py
+++ b/openstack_dashboard/dashboards/project/floating_ips/__init__.py
diff --git a/openstack_dashboard/dashboards/project/access_and_security/floating_ips/forms.py b/openstack_dashboard/dashboards/project/floating_ips/forms.py
index d933c7a..d933c7a 100644
--- a/openstack_dashboard/dashboards/project/access_and_security/floating_ips/forms.py
+++ b/openstack_dashboard/dashboards/project/floating_ips/forms.py
diff --git a/openstack_dashboard/dashboards/project/floating_ips/panel.py b/openstack_dashboard/dashboards/project/floating_ips/panel.py
new file mode 100644
index 0000000..f8a241c
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/floating_ips/panel.py
@@ -0,0 +1,27 @@
1# Copyright 2017 Cisco Systems, Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14
15from django.conf import settings
16from django.utils.translation import ugettext_lazy as _
17import horizon
18
19
20class FloatingIps(horizon.Panel):
21 name = _("Floating IPs")
22 slug = 'floating_ips'
23
24 @staticmethod
25 def can_register():
26 network_config = getattr(settings, 'OPENSTACK_NEUTRON_NETWORK', {})
27 return network_config.get('enable_router', True)
diff --git a/openstack_dashboard/dashboards/project/access_and_security/floating_ips/tables.py b/openstack_dashboard/dashboards/project/floating_ips/tables.py
index eec9f78..c894b8d 100644
--- a/openstack_dashboard/dashboards/project/access_and_security/floating_ips/tables.py
+++ b/openstack_dashboard/dashboards/project/floating_ips/tables.py
@@ -41,10 +41,10 @@ class AllocateIP(tables.LinkAction):
41 verbose_name = _("Allocate IP To Project") 41 verbose_name = _("Allocate IP To Project")
42 classes = ("ajax-modal",) 42 classes = ("ajax-modal",)
43 icon = "link" 43 icon = "link"
44 url = "horizon:project:access_and_security:floating_ips:allocate" 44 url = "horizon:project:floating_ips:allocate"
45 45
46 def single(self, data_table, request, *args): 46 def single(self, data_table, request, *args):
47 return shortcuts.redirect('horizon:project:access_and_security:index') 47 return shortcuts.redirect('horizon:project:floating_ips:index')
48 48
49 def allowed(self, request, fip=None): 49 def allowed(self, request, fip=None):
50 usages = quotas.tenant_quota_usages(request) 50 usages = quotas.tenant_quota_usages(request)
@@ -106,7 +106,7 @@ class ReleaseIPs(tables.BatchAction):
106class AssociateIP(tables.LinkAction): 106class AssociateIP(tables.LinkAction):
107 name = "associate" 107 name = "associate"
108 verbose_name = _("Associate") 108 verbose_name = _("Associate")
109 url = "horizon:project:access_and_security:floating_ips:associate" 109 url = "horizon:project:floating_ips:associate"
110 classes = ("ajax-modal",) 110 classes = ("ajax-modal",)
111 icon = "link" 111 icon = "link"
112 112
@@ -152,7 +152,7 @@ class DisassociateIP(tables.Action):
152 except Exception: 152 except Exception:
153 exceptions.handle(request, 153 exceptions.handle(request,
154 _('Unable to disassociate floating IP.')) 154 _('Unable to disassociate floating IP.'))
155 return shortcuts.redirect('horizon:project:access_and_security:index') 155 return shortcuts.redirect('horizon:project:floating_ips:index')
156 156
157 157
158def get_instance_info(fip): 158def get_instance_info(fip):
diff --git a/openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/floating_ips/_allocate.html b/openstack_dashboard/dashboards/project/floating_ips/templates/floating_ips/_allocate.html
index 13abcd1..13abcd1 100644
--- a/openstack_dashboard/dashboards/project/access_and_security/templates/access_and_security/floating_ips/_allocate.html
+++ b/openstack_dashboard/dashboards/project/floating_ips/templates/floating_ips/_allocate.html
diff --git a/openstack_dashboard/dashboards/project/floating_ips/templates/floating_ips/allocate.html b/openstack_dashboard/dashboards/project/floating_ips/templates/floating_ips/allocate.html
new file mode 100644
index 0000000..17b7957
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/floating_ips/templates/floating_ips/allocate.html
@@ -0,0 +1,6 @@
1{% extends 'base.html' %}
2{% load i18n %}
3
4{% block main %}
5 {% include 'project/floating_ips/_allocate.html' %}
6{% endblock %}
diff --git a/openstack_dashboard/dashboards/project/access_and_security/floating_ips/tests.py b/openstack_dashboard/dashboards/project/floating_ips/tests.py
index ad2895a..6e3882b 100644
--- a/openstack_dashboard/dashboards/project/access_and_security/floating_ips/tests.py
+++ b/openstack_dashboard/dashboards/project/floating_ips/tests.py
@@ -31,8 +31,8 @@ from openstack_dashboard.usage import quotas
31from horizon.workflows import views 31from horizon.workflows import views
32 32
33 33
34INDEX_URL = reverse('horizon:project:access_and_security:index') 34INDEX_URL = reverse('horizon:project:floating_ips:index')
35NAMESPACE = "horizon:project:access_and_security:floating_ips" 35NAMESPACE = "horizon:project:floating_ips"
36 36
37 37
38class FloatingIpViewTests(test.TestCase): 38class FloatingIpViewTests(test.TestCase):
@@ -167,7 +167,6 @@ class FloatingIpViewTests(test.TestCase):
167 167
168 @test.create_stubs({api.nova: ('server_list',), 168 @test.create_stubs({api.nova: ('server_list',),
169 api.network: ('floating_ip_disassociate', 169 api.network: ('floating_ip_disassociate',
170 'floating_ip_supported',
171 'tenant_floating_ip_get', 170 'tenant_floating_ip_get',
172 'tenant_floating_ip_list',), 171 'tenant_floating_ip_list',),
173 api.neutron: ('is_extension_supported',)}) 172 api.neutron: ('is_extension_supported',)})
@@ -176,8 +175,6 @@ class FloatingIpViewTests(test.TestCase):
176 175
177 api.nova.server_list(IsA(http.HttpRequest)) \ 176 api.nova.server_list(IsA(http.HttpRequest)) \
178 .AndReturn([self.servers.list(), False]) 177 .AndReturn([self.servers.list(), False])
179 api.network.floating_ip_supported(IsA(http.HttpRequest)) \
180 .AndReturn(True)
181 api.network.tenant_floating_ip_list(IsA(http.HttpRequest)) \ 178 api.network.tenant_floating_ip_list(IsA(http.HttpRequest)) \
182 .AndReturn(self.floating_ips.list()) 179 .AndReturn(self.floating_ips.list())
183 api.neutron.is_extension_supported(IsA(http.HttpRequest), 180 api.neutron.is_extension_supported(IsA(http.HttpRequest),
@@ -194,7 +191,6 @@ class FloatingIpViewTests(test.TestCase):
194 191
195 @test.create_stubs({api.nova: ('server_list',), 192 @test.create_stubs({api.nova: ('server_list',),
196 api.network: ('floating_ip_disassociate', 193 api.network: ('floating_ip_disassociate',
197 'floating_ip_supported',
198 'tenant_floating_ip_get', 194 'tenant_floating_ip_get',
199 'tenant_floating_ip_list',), 195 'tenant_floating_ip_list',),
200 api.neutron: ('is_extension_supported',)}) 196 api.neutron: ('is_extension_supported',)})
@@ -203,8 +199,6 @@ class FloatingIpViewTests(test.TestCase):
203 199
204 api.nova.server_list(IsA(http.HttpRequest)) \ 200 api.nova.server_list(IsA(http.HttpRequest)) \
205 .AndReturn([self.servers.list(), False]) 201 .AndReturn([self.servers.list(), False])
206 api.network.floating_ip_supported(IsA(http.HttpRequest)) \
207 .AndReturn(True)
208 api.network.tenant_floating_ip_list(IsA(http.HttpRequest)) \ 202 api.network.tenant_floating_ip_list(IsA(http.HttpRequest)) \
209 .AndReturn(self.floating_ips.list()) 203 .AndReturn(self.floating_ips.list())
210 api.neutron.is_extension_supported(IsA(http.HttpRequest), 204 api.neutron.is_extension_supported(IsA(http.HttpRequest),
@@ -220,9 +214,7 @@ class FloatingIpViewTests(test.TestCase):
220 res = self.client.post(INDEX_URL, {"action": action}) 214 res = self.client.post(INDEX_URL, {"action": action})
221 self.assertRedirectsNoFollow(res, INDEX_URL) 215 self.assertRedirectsNoFollow(res, INDEX_URL)
222 216
223 @test.create_stubs({api.network: ('floating_ip_supported', 217 @test.create_stubs({api.network: ('tenant_floating_ip_list',
224 'tenant_floating_ip_list',
225 'security_group_list',
226 'floating_ip_pools_list',), 218 'floating_ip_pools_list',),
227 api.nova: ('server_list',), 219 api.nova: ('server_list',),
228 quotas: ('tenant_quota_usages',), 220 quotas: ('tenant_quota_usages',),
@@ -232,17 +224,10 @@ class FloatingIpViewTests(test.TestCase):
232 floating_pools = self.pools.list() 224 floating_pools = self.pools.list()
233 quota_data = self.quota_usages.first() 225 quota_data = self.quota_usages.first()
234 quota_data['floating_ips']['available'] = 10 226 quota_data['floating_ips']['available'] = 10
235 sec_groups = self.security_groups.list()
236 227
237 api.network.floating_ip_supported(
238 IsA(http.HttpRequest)) \
239 .AndReturn(True)
240 api.network.tenant_floating_ip_list( 228 api.network.tenant_floating_ip_list(
241 IsA(http.HttpRequest)) \ 229 IsA(http.HttpRequest)) \
242 .AndReturn(floating_ips) 230 .AndReturn(floating_ips)
243 api.network.security_group_list(
244 IsA(http.HttpRequest)).MultipleTimes()\
245 .AndReturn(sec_groups)
246 api.network.floating_ip_pools_list( 231 api.network.floating_ip_pools_list(
247 IsA(http.HttpRequest)) \ 232 IsA(http.HttpRequest)) \
248 .AndReturn(floating_pools) 233 .AndReturn(floating_pools)
@@ -252,7 +237,6 @@ class FloatingIpViewTests(test.TestCase):
252 quotas.tenant_quota_usages( 237 quotas.tenant_quota_usages(
253 IsA(http.HttpRequest)).MultipleTimes() \ 238 IsA(http.HttpRequest)).MultipleTimes() \
254 .AndReturn(quota_data) 239 .AndReturn(quota_data)
255
256 api.base.is_service_enabled( 240 api.base.is_service_enabled(
257 IsA(http.HttpRequest), 241 IsA(http.HttpRequest),
258 'network').MultipleTimes() \ 242 'network').MultipleTimes() \
@@ -260,8 +244,7 @@ class FloatingIpViewTests(test.TestCase):
260 244
261 self.mox.ReplayAll() 245 self.mox.ReplayAll()
262 246
263 res = self.client.get(INDEX_URL + 247 res = self.client.get(INDEX_URL)
264 "?tab=access_security_tabs__floating_ips_tab")
265 248
266 allocate_action = self.getAndAssertTableAction(res, 'floating_ips', 249 allocate_action = self.getAndAssertTableAction(res, 'floating_ips',
267 'allocate') 250 'allocate')
@@ -270,12 +253,10 @@ class FloatingIpViewTests(test.TestCase):
270 six.text_type(allocate_action.verbose_name)) 253 six.text_type(allocate_action.verbose_name))
271 self.assertIsNone(allocate_action.policy_rules) 254 self.assertIsNone(allocate_action.policy_rules)
272 255
273 url = 'horizon:project:access_and_security:floating_ips:allocate' 256 url = 'horizon:project:floating_ips:allocate'
274 self.assertEqual(url, allocate_action.url) 257 self.assertEqual(url, allocate_action.url)
275 258
276 @test.create_stubs({api.network: ('floating_ip_supported', 259 @test.create_stubs({api.network: ('tenant_floating_ip_list',
277 'tenant_floating_ip_list',
278 'security_group_list',
279 'floating_ip_pools_list',), 260 'floating_ip_pools_list',),
280 api.nova: ('server_list',), 261 api.nova: ('server_list',),
281 quotas: ('tenant_quota_usages',), 262 quotas: ('tenant_quota_usages',),
@@ -285,17 +266,10 @@ class FloatingIpViewTests(test.TestCase):
285 floating_pools = self.pools.list() 266 floating_pools = self.pools.list()
286 quota_data = self.quota_usages.first() 267 quota_data = self.quota_usages.first()
287 quota_data['floating_ips']['available'] = 0 268 quota_data['floating_ips']['available'] = 0
288 sec_groups = self.security_groups.list()
289 269
290 api.network.floating_ip_supported(
291 IsA(http.HttpRequest)) \
292 .AndReturn(True)
293 api.network.tenant_floating_ip_list( 270 api.network.tenant_floating_ip_list(
294 IsA(http.HttpRequest)) \ 271 IsA(http.HttpRequest)) \
295 .AndReturn(floating_ips) 272 .AndReturn(floating_ips)
296 api.network.security_group_list(
297 IsA(http.HttpRequest)).MultipleTimes()\
298 .AndReturn(sec_groups)
299 api.network.floating_ip_pools_list( 273 api.network.floating_ip_pools_list(
300 IsA(http.HttpRequest)) \ 274 IsA(http.HttpRequest)) \
301 .AndReturn(floating_pools) 275 .AndReturn(floating_pools)
@@ -305,7 +279,6 @@ class FloatingIpViewTests(test.TestCase):
305 quotas.tenant_quota_usages( 279 quotas.tenant_quota_usages(
306 IsA(http.HttpRequest)).MultipleTimes() \ 280 IsA(http.HttpRequest)).MultipleTimes() \
307 .AndReturn(quota_data) 281 .AndReturn(quota_data)
308
309 api.base.is_service_enabled( 282 api.base.is_service_enabled(
310 IsA(http.HttpRequest), 283 IsA(http.HttpRequest),
311 'network').MultipleTimes() \ 284 'network').MultipleTimes() \
@@ -313,8 +286,7 @@ class FloatingIpViewTests(test.TestCase):
313 286
314 self.mox.ReplayAll() 287 self.mox.ReplayAll()
315 288
316 res = self.client.get(INDEX_URL + 289 res = self.client.get(INDEX_URL)
317 "?tab=access_security_tabs__floating_ips_tab")
318 290
319 allocate_action = self.getAndAssertTableAction(res, 'floating_ips', 291 allocate_action = self.getAndAssertTableAction(res, 'floating_ips',
320 'allocate') 292 'allocate')
diff --git a/openstack_dashboard/dashboards/project/access_and_security/floating_ips/urls.py b/openstack_dashboard/dashboards/project/floating_ips/urls.py
index af3cef8..5d7ca61 100644
--- a/openstack_dashboard/dashboards/project/access_and_security/floating_ips/urls.py
+++ b/openstack_dashboard/dashboards/project/floating_ips/urls.py
@@ -18,11 +18,10 @@
18 18
19from django.conf.urls import url 19from django.conf.urls import url
20 20
21from openstack_dashboard.dashboards.project.access_and_security.\ 21from openstack_dashboard.dashboards.project.floating_ips import views
22 floating_ips import views
23
24 22
25urlpatterns = [ 23urlpatterns = [
24 url(r'^$', views.IndexView.as_view(), name='index'),
26 url(r'^associate/$', views.AssociateView.as_view(), name='associate'), 25 url(r'^associate/$', views.AssociateView.as_view(), name='associate'),
27 url(r'^allocate/$', views.AllocateView.as_view(), name='allocate'), 26 url(r'^allocate/$', views.AllocateView.as_view(), name='allocate'),
28] 27]
diff --git a/openstack_dashboard/dashboards/project/access_and_security/floating_ips/views.py b/openstack_dashboard/dashboards/project/floating_ips/views.py
index 4c094e3..061aa8c 100644
--- a/openstack_dashboard/dashboards/project/access_and_security/floating_ips/views.py
+++ b/openstack_dashboard/dashboards/project/floating_ips/views.py
@@ -28,15 +28,18 @@ from neutronclient.common import exceptions as neutron_exc
28 28
29from horizon import exceptions 29from horizon import exceptions
30from horizon import forms 30from horizon import forms
31from horizon import tables
31from horizon import workflows 32from horizon import workflows
32 33
33from openstack_dashboard import api 34from openstack_dashboard import api
34from openstack_dashboard.usage import quotas 35from openstack_dashboard.usage import quotas
35 36
36from openstack_dashboard.dashboards.project.access_and_security.\ 37from openstack_dashboard.dashboards.project.floating_ips \
37 floating_ips import forms as project_forms 38 import forms as project_forms
38from openstack_dashboard.dashboards.project.access_and_security.\ 39from openstack_dashboard.dashboards.project.floating_ips \
39 floating_ips import workflows as project_workflows 40 import tables as project_tables
41from openstack_dashboard.dashboards.project.floating_ips \
42 import workflows as project_workflows
40 43
41 44
42class AssociateView(workflows.WorkflowView): 45class AssociateView(workflows.WorkflowView):
@@ -47,11 +50,10 @@ class AllocateView(forms.ModalFormView):
47 form_class = project_forms.FloatingIpAllocate 50 form_class = project_forms.FloatingIpAllocate
48 form_id = "associate_floating_ip_form" 51 form_id = "associate_floating_ip_form"
49 page_title = _("Allocate Floating IP") 52 page_title = _("Allocate Floating IP")
50 template_name = 'project/access_and_security/floating_ips/allocate.html' 53 template_name = 'project/floating_ips/allocate.html'
51 submit_label = _("Allocate IP") 54 submit_label = _("Allocate IP")
52 submit_url = reverse_lazy( 55 submit_url = reverse_lazy("horizon:project:floating_ips:allocate")
53 "horizon:project:access_and_security:floating_ips:allocate") 56 success_url = reverse_lazy('horizon:project:floating_ips:index')
54 success_url = reverse_lazy('horizon:project:access_and_security:index')
55 57
56 def get_object_display(self, obj): 58 def get_object_display(self, obj):
57 return obj.ip 59 return obj.ip
@@ -78,3 +80,51 @@ class AllocateView(forms.ModalFormView):
78 if not pool_list: 80 if not pool_list:
79 pool_list = [(None, _("No floating IP pools available"))] 81 pool_list = [(None, _("No floating IP pools available"))]
80 return {'pool_list': pool_list} 82 return {'pool_list': pool_list}
83
84
85class IndexView(tables.DataTableView):
86 table_class = project_tables.FloatingIPsTable
87 page_title = _("Floating IPs")
88
89 def get_data(self):
90 try:
91 floating_ips = api.network.tenant_floating_ip_list(self.request)
92 except neutron_exc.ConnectionFailed:
93 floating_ips = []
94 exceptions.handle(self.request)
95 except Exception:
96 floating_ips = []
97 exceptions.handle(self.request,
98 _('Unable to retrieve floating IP addresses.'))
99
100 try:
101 floating_ip_pools = \
102 api.network.floating_ip_pools_list(self.request)
103 except neutron_exc.ConnectionFailed:
104 floating_ip_pools = []
105 exceptions.handle(self.request)
106 except Exception:
107 floating_ip_pools = []
108 exceptions.handle(self.request,
109 _('Unable to retrieve floating IP pools.'))
110 pool_dict = dict([(obj.id, obj.name) for obj in floating_ip_pools])
111
112 attached_instance_ids = [ip.instance_id for ip in floating_ips
113 if ip.instance_id is not None]
114 if attached_instance_ids:
115 instances = []
116 try:
117 # TODO(tsufiev): we should pass attached_instance_ids to
118 # nova.server_list as soon as Nova API allows for this
119 instances, has_more = api.nova.server_list(self.request)
120 except Exception:
121 exceptions.handle(self.request,
122 _('Unable to retrieve instance list.'))
123
124 instances_dict = dict([(obj.id, obj.name) for obj in instances])
125
126 for ip in floating_ips:
127 ip.instance_name = instances_dict.get(ip.instance_id)
128 ip.pool_name = pool_dict.get(ip.pool, ip.pool)
129
130 return floating_ips
diff --git a/openstack_dashboard/dashboards/project/access_and_security/floating_ips/workflows.py b/openstack_dashboard/dashboards/project/floating_ips/workflows.py
index 7ee001f..c62ae3e 100644
--- a/openstack_dashboard/dashboards/project/access_and_security/floating_ips/workflows.py
+++ b/openstack_dashboard/dashboards/project/floating_ips/workflows.py
@@ -26,7 +26,7 @@ from openstack_dashboard import api
26from openstack_dashboard.utils import filters 26from openstack_dashboard.utils import filters
27 27
28 28
29ALLOCATE_URL = "horizon:project:access_and_security:floating_ips:allocate" 29ALLOCATE_URL = "horizon:project:floating_ips:allocate"
30 30
31 31
32class AssociateIPAction(workflows.Action): 32class AssociateIPAction(workflows.Action):
@@ -72,7 +72,7 @@ class AssociateIPAction(workflows.Action):
72 72
73 def populate_ip_id_choices(self, request, context): 73 def populate_ip_id_choices(self, request, context):
74 ips = [] 74 ips = []
75 redirect = reverse('horizon:project:access_and_security:index') 75 redirect = reverse('horizon:project:floating_ips:index')
76 try: 76 try:
77 ips = api.network.tenant_floating_ip_list(self.request) 77 ips = api.network.tenant_floating_ip_list(self.request)
78 except neutron_exc.ConnectionFailed: 78 except neutron_exc.ConnectionFailed:
@@ -95,7 +95,7 @@ class AssociateIPAction(workflows.Action):
95 try: 95 try:
96 targets = api.network.floating_ip_target_list(self.request) 96 targets = api.network.floating_ip_target_list(self.request)
97 except Exception: 97 except Exception:
98 redirect = reverse('horizon:project:access_and_security:index') 98 redirect = reverse('horizon:project:floating_ips:index')
99 exceptions.handle(self.request, 99 exceptions.handle(self.request,
100 _('Unable to retrieve instance list.'), 100 _('Unable to retrieve instance list.'),
101 redirect=redirect) 101 redirect=redirect)
@@ -146,7 +146,7 @@ class IPAssociationWorkflow(workflows.Workflow):
146 finalize_button_name = _("Associate") 146 finalize_button_name = _("Associate")
147 success_message = _('IP address %s associated.') 147 success_message = _('IP address %s associated.')
148 failure_message = _('Unable to associate IP address %s.') 148 failure_message = _('Unable to associate IP address %s.')
149 success_url = "horizon:project:access_and_security:index" 149 success_url = "horizon:project:floating_ips:index"
150 default_steps = (AssociateIP,) 150 default_steps = (AssociateIP,)
151 151
152 def format_status_message(self, message): 152 def format_status_message(self, message):
diff --git a/openstack_dashboard/dashboards/project/instances/tables.py b/openstack_dashboard/dashboards/project/instances/tables.py
index e3e56ee..f01741e 100644
--- a/openstack_dashboard/dashboards/project/instances/tables.py
+++ b/openstack_dashboard/dashboards/project/instances/tables.py
@@ -36,8 +36,7 @@ from horizon.templatetags import sizeformat
36from horizon.utils import filters 36from horizon.utils import filters
37 37
38from openstack_dashboard import api 38from openstack_dashboard import api
39from openstack_dashboard.dashboards.project.access_and_security.floating_ips \ 39from openstack_dashboard.dashboards.project.floating_ips import workflows
40 import workflows
41from openstack_dashboard.dashboards.project.instances import tabs 40from openstack_dashboard.dashboards.project.instances import tabs
42from openstack_dashboard.dashboards.project.instances.workflows \ 41from openstack_dashboard.dashboards.project.instances.workflows \
43 import resize_instance 42 import resize_instance
@@ -617,7 +616,7 @@ class DecryptInstancePassword(tables.LinkAction):
617class AssociateIP(policy.PolicyTargetMixin, tables.LinkAction): 616class AssociateIP(policy.PolicyTargetMixin, tables.LinkAction):
618 name = "associate" 617 name = "associate"
619 verbose_name = _("Associate Floating IP") 618 verbose_name = _("Associate Floating IP")
620 url = "horizon:project:access_and_security:floating_ips:associate" 619 url = "horizon:project:floating_ips:associate"
621 classes = ("ajax-modal",) 620 classes = ("ajax-modal",)
622 icon = "link" 621 icon = "link"
623 policy_rules = (("compute", "network:associate_floating_ip"),) 622 policy_rules = (("compute", "network:associate_floating_ip"),)
diff --git a/openstack_dashboard/enabled/_1490_project_floating_ips_panel.py b/openstack_dashboard/enabled/_1490_project_floating_ips_panel.py
new file mode 100644
index 0000000..872ead7
--- /dev/null
+++ b/openstack_dashboard/enabled/_1490_project_floating_ips_panel.py
@@ -0,0 +1,6 @@
1PANEL_DASHBOARD = 'project'
2PANEL_GROUP = 'network'
3PANEL = 'floating_ips'
4
5ADD_PANEL = \
6 'openstack_dashboard.dashboards.project.floating_ips.panel.FloatingIps'