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 5 years ago
parent
commit
5c238e9117
19 changed files with 793 additions and 18 deletions
  1. +6
    -4
      openstack_dashboard/api/network.py
  2. +3
    -3
      openstack_dashboard/api/network_base.py
  3. +9
    -4
      openstack_dashboard/api/neutron.py
  4. +6
    -4
      openstack_dashboard/api/nova.py
  5. +0
    -0
      openstack_dashboard/dashboards/admin/floating_ips/__init__.py
  6. +64
    -0
      openstack_dashboard/dashboards/admin/floating_ips/forms.py
  7. +30
    -0
      openstack_dashboard/dashboards/admin/floating_ips/panel.py
  8. +91
    -0
      openstack_dashboard/dashboards/admin/floating_ips/tables.py
  9. +9
    -0
      openstack_dashboard/dashboards/admin/floating_ips/templates/floating_ips/_allocate.html
  10. +7
    -0
      openstack_dashboard/dashboards/admin/floating_ips/templates/floating_ips/allocate.html
  11. +48
    -0
      openstack_dashboard/dashboards/admin/floating_ips/templates/floating_ips/detail.html
  12. +7
    -0
      openstack_dashboard/dashboards/admin/floating_ips/templates/floating_ips/index.html
  13. +275
    -0
      openstack_dashboard/dashboards/admin/floating_ips/tests.py
  14. +26
    -0
      openstack_dashboard/dashboards/admin/floating_ips/urls.py
  15. +189
    -0
      openstack_dashboard/dashboards/admin/floating_ips/views.py
  16. +10
    -0
      openstack_dashboard/enabled/_2111_admin_floating_ips_panel.py
  17. +1
    -1
      openstack_dashboard/test/api_tests/network_tests.py
  18. +8
    -2
      openstack_dashboard/test/test_data/neutron_data.py
  19. +4
    -0
      releasenotes/notes/bp-admin-manage-fips-5aa409d3502b031a.yaml

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

@@ -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):

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

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

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

@@ -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)


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

@@ -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):

+ 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 @@
# 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
- 0
openstack_dashboard/dashboards/admin/floating_ips/panel.py View 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
- 0
openstack_dashboard/dashboards/admin/floating_ips/tables.py View 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')

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

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

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

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

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

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

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

@@ -0,0 +1,7 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Floating IPs" %}{% endblock %}

{% block main %}
{{ table.render }}
{% endblock %}

+ 275
- 0
openstack_dashboard/dashboards/admin/floating_ips/tests.py View 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
- 0
openstack_dashboard/dashboards/admin/floating_ips/urls.py View 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
- 0
openstack_dashboard/dashboards/admin/floating_ips/views.py View 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}

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

@@ -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'

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

@@ -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)

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

@@ -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.


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

@@ -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…
Cancel
Save