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()
|
return NetworkClient(request).floating_ips.list_pools()
|
||||||
|
|
||||||
|
|
||||||
def tenant_floating_ip_list(request):
|
def tenant_floating_ip_list(request, all_tenants=False):
|
||||||
return NetworkClient(request).floating_ips.list()
|
return NetworkClient(request).floating_ips.list(all_tenants=all_tenants)
|
||||||
|
|
||||||
|
|
||||||
def tenant_floating_ip_get(request, floating_ip_id):
|
def tenant_floating_ip_get(request, floating_ip_id):
|
||||||
return NetworkClient(request).floating_ips.get(floating_ip_id)
|
return NetworkClient(request).floating_ips.get(floating_ip_id)
|
||||||
|
|
||||||
|
|
||||||
def tenant_floating_ip_allocate(request, pool=None):
|
def tenant_floating_ip_allocate(request, pool=None, tenant_id=None, **params):
|
||||||
return NetworkClient(request).floating_ips.allocate(pool)
|
return NetworkClient(request).floating_ips.allocate(pool,
|
||||||
|
tenant_id,
|
||||||
|
**params)
|
||||||
|
|
||||||
|
|
||||||
def tenant_floating_ip_release(request, floating_ip_id):
|
def tenant_floating_ip_release(request, floating_ip_id):
|
||||||
|
@ -51,8 +51,8 @@ class FloatingIpManager(object):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def list(self):
|
def list(self, all_tenants=False):
|
||||||
"""Fetches a list all floating IPs.
|
"""Fetches a list of all floating IPs.
|
||||||
|
|
||||||
A returned value is a list of FloatingIp object.
|
A returned value is a list of FloatingIp object.
|
||||||
"""
|
"""
|
||||||
@ -67,7 +67,7 @@ class FloatingIpManager(object):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def allocate(self, pool=None):
|
def allocate(self, pool=None, tenant_id=None, **params):
|
||||||
"""Allocates a floating IP to the tenant.
|
"""Allocates a floating IP to the tenant.
|
||||||
|
|
||||||
You must provide a pool name or id for which you would like to
|
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)
|
self._set_instance_info(fip)
|
||||||
return FloatingIp(fip)
|
return FloatingIp(fip)
|
||||||
|
|
||||||
def allocate(self, pool):
|
def allocate(self, pool, tenant_id=None, **params):
|
||||||
body = {'floatingip': {'floating_network_id': pool,
|
if not tenant_id:
|
||||||
'tenant_id': self.request.user.project_id}}
|
tenant_id = self.request.user.project_id
|
||||||
fip = self.client.create_floatingip(body).get('floatingip')
|
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)
|
self._set_instance_info(fip)
|
||||||
return FloatingIp(fip)
|
return FloatingIp(fip)
|
||||||
|
|
||||||
|
@ -415,14 +415,16 @@ class FloatingIpManager(network_base.FloatingIpManager):
|
|||||||
return [FloatingIpPool(pool)
|
return [FloatingIpPool(pool)
|
||||||
for pool in self.client.floating_ip_pools.list()]
|
for pool in self.client.floating_ip_pools.list()]
|
||||||
|
|
||||||
def list(self):
|
def list(self, all_tenants=False):
|
||||||
return [FloatingIp(fip)
|
return [FloatingIp(fip) for fip in
|
||||||
for fip in self.client.floating_ips.list()]
|
self.client.floating_ips.list(
|
||||||
|
all_tenants=all_tenants)]
|
||||||
|
|
||||||
def get(self, floating_ip_id):
|
def get(self, floating_ip_id):
|
||||||
return FloatingIp(self.client.floating_ips.get(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))
|
return FloatingIp(self.client.floating_ips.create(pool=pool))
|
||||||
|
|
||||||
def release(self, floating_ip_id):
|
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()
|
fips = self.api_floating_ips.list()
|
||||||
novaclient = self.stub_novaclient()
|
novaclient = self.stub_novaclient()
|
||||||
novaclient.floating_ips = self.mox.CreateMockAnything()
|
novaclient.floating_ips = self.mox.CreateMockAnything()
|
||||||
novaclient.floating_ips.list().AndReturn(fips)
|
novaclient.floating_ips.list(all_tenants=False).AndReturn(fips)
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
ret = api.network.tenant_floating_ip_list(self.request)
|
ret = api.network.tenant_floating_ip_list(self.request)
|
||||||
|
@ -440,7 +440,10 @@ def data(TEST):
|
|||||||
'port_id': None,
|
'port_id': None,
|
||||||
'router_id': None}
|
'router_id': None}
|
||||||
TEST.api_q_floating_ips.add(fip_dict)
|
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).
|
# Associated (with compute port on 1st network).
|
||||||
fip_dict = {'tenant_id': '1',
|
fip_dict = {'tenant_id': '1',
|
||||||
@ -451,7 +454,10 @@ def data(TEST):
|
|||||||
'port_id': assoc_port['id'],
|
'port_id': assoc_port['id'],
|
||||||
'router_id': router_dict['id']}
|
'router_id': router_dict['id']}
|
||||||
TEST.api_q_floating_ips.add(fip_dict)
|
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.
|
# 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