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