Merge "Support simple FIP disassociation (with FIP release)"

This commit is contained in:
Zuul 2018-04-19 11:56:31 +00:00 committed by Gerrit Code Review
commit 0e0db28969
11 changed files with 149 additions and 93 deletions

View File

@ -428,31 +428,6 @@ there are any.
This setting allows you to set rules for passwords if your organization
requires them.
simple_ip_management
~~~~~~~~~~~~~~~~~~~~
.. versionadded:: 2013.1(Grizzly)
Default: ``True``
Enable or disable simplified floating IP address management.
"Simple" floating IP address management means that the user does not ever have
to select the specific IP addresses they wish to use, and the process of
allocating an IP and assigning it to an instance is one-click.
The "advanced" floating IP management allows users to select the floating IP
pool from which the IP should be allocated and to select a specific IP address
when associating one with an instance.
.. note::
Currently "simple" floating IP address management is not compatible with
Neutron. There are two reasons for this. First, Neutron does not support
the default floating IP pool at the moment. Second, a Neutron floating IP
can be associated with each VIF and we need to check whether there is only
one VIF for an instance to enable simple association support.
user_home
~~~~~~~~~

View File

@ -45,9 +45,6 @@ HORIZON_CONFIG = {
'password_autocomplete': 'off',
# Enable or disable simplified floating IP address management.
'simple_ip_management': True,
'integration_tests_support':
getattr(settings, 'INTEGRATION_TESTS_SUPPORT', False)
}

View File

@ -15,6 +15,7 @@
from django.template.defaultfilters import filesizeformat
from django.urls import reverse
from django.urls import reverse_lazy
from django.utils.translation import ugettext_lazy as _
from django.views.decorators.debug import sensitive_variables
@ -418,3 +419,54 @@ class DetachInterface(forms.SelfHandlingForm):
exceptions.handle(request, _("Unable to detach interface."),
redirect=redirect)
return True
class Disassociate(forms.SelfHandlingForm):
fip = forms.ThemableChoiceField(label=_('Floating IP'))
is_release = forms.BooleanField(label=_('Release Floating IP'),
required=False)
def __init__(self, request, *args, **kwargs):
super(Disassociate, self).__init__(request, *args, **kwargs)
instance_id = self.initial['instance_id']
targets = api.neutron.floating_ip_target_list_by_instance(
request, instance_id)
target_ids = [t.port_id for t in targets]
self.fips = [fip for fip
in api.neutron.tenant_floating_ip_list(request)
if fip.port_id in target_ids]
fip_choices = [(fip.id, fip.ip) for fip in self.fips]
fip_choices.insert(0, ('', _('Select a floating IP to disassociate')))
self.fields['fip'].choices = fip_choices
self.fields['fip'].initial = self.fips[0].id
def handle(self, request, data):
redirect = reverse_lazy('horizon:project:instances:index')
fip_id = data['fip']
fips = [fip for fip in self.fips if fip.id == fip_id]
if not fips:
messages.error(request,
_("The specified floating IP no longer exists."),
redirect=redirect)
fip = fips[0]
try:
if data['is_release']:
api.neutron.tenant_floating_ip_release(request, fip_id)
messages.success(
request,
_("Successfully disassociated and released "
"floating IP %s") % fip.ip)
else:
api.neutron.floating_ip_disassociate(request, fip_id)
messages.success(
request,
_("Successfully disassociated floating IP %s") % fip.ip)
except Exception:
exceptions.handle(
request,
_('Unable to disassociate floating IP %s') % fip.ip,
redirect=redirect)
return True

View File

@ -17,7 +17,6 @@ import logging
from django.conf import settings
from django.http import HttpResponse
from django import shortcuts
from django import template
from django.template.defaultfilters import title
from django import urls
@ -29,7 +28,6 @@ from django.utils.translation import string_concat
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext_lazy
from horizon import conf
from horizon import exceptions
from horizon import messages
from horizon import tables
@ -655,14 +653,11 @@ class AssociateIP(policy.PolicyTargetMixin, tables.LinkAction):
return "?".join([base_url, params])
# TODO(amotoki): [drop-nova-network] The current SimpleDisassociateIP
# just disassociates the first found FIP. It looks better to have a form
# which allows to choose which FIP should be disassociated.
# HORIZON_CONFIG['simple_ip_management'] can be dropped then.
class SimpleDisassociateIP(policy.PolicyTargetMixin, tables.Action):
class DisassociateIP(tables.LinkAction):
name = "disassociate"
verbose_name = _("Disassociate Floating IP")
classes = ("btn-disassociate",)
url = "horizon:project:instances:disassociate"
classes = ("btn-disassociate", 'ajax-modal')
policy_rules = (("network", "update_floatingip"),)
action_type = "danger"
@ -671,38 +666,12 @@ class SimpleDisassociateIP(policy.PolicyTargetMixin, tables.Action):
return False
if not api.neutron.floating_ip_supported(request):
return False
if not conf.HORIZON_CONFIG["simple_ip_management"]:
return False
for addresses in instance.addresses.values():
for address in addresses:
if address.get('OS-EXT-IPS:type') == "floating":
return not is_deleting(instance)
return False
def single(self, table, request, instance_id):
try:
targets = api.neutron.floating_ip_target_list_by_instance(
request, instance_id)
target_ids = [t.port_id for t in targets]
fips = [fip for fip in api.neutron.tenant_floating_ip_list(request)
if fip.port_id in target_ids]
# Removing multiple floating IPs at once doesn't work, so this pops
# off the first one.
if fips:
fip = fips.pop()
api.neutron.floating_ip_disassociate(request, fip.id)
messages.success(request,
_("Successfully disassociated "
"floating IP: %s") % fip.ip)
else:
messages.info(request, _("No floating IPs to disassociate."))
except Exception:
exceptions.handle(request,
_("Unable to disassociate floating IP."))
return shortcuts.redirect(request.get_full_path())
class UpdateMetadata(policy.PolicyTargetMixin, tables.LinkAction):
name = "update_metadata"
@ -1280,10 +1249,10 @@ class InstancesTable(tables.DataTable):
table_actions = launch_actions + (DeleteInstance,
InstancesFilterAction)
row_actions = (StartInstance, ConfirmResize, RevertResize,
CreateSnapshot, AssociateIP,
SimpleDisassociateIP, AttachInterface,
DetachInterface, EditInstance, AttachVolume,
DetachVolume, UpdateMetadata, DecryptInstancePassword,
CreateSnapshot, AssociateIP, DisassociateIP,
AttachInterface, DetachInterface, EditInstance,
AttachVolume, DetachVolume,
UpdateMetadata, DecryptInstancePassword,
EditInstanceSecurityGroups, ConsoleLink, LogLink,
TogglePause, ToggleSuspend, ToggleShelve,
ResizeLink, LockInstance, UnlockInstance,

View File

@ -0,0 +1,28 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% block form_id %}disassociate_fip_form{% endblock %}
{% block form_action %}{% url "horizon:project:instances:disassociate" instance_id %}{% endblock %}
{% block modal_id %}disassocaite_fip_modal{% endblock %}
{% block modal-header %}{% trans "Disassociate Floating IP" %}{% endblock %}
{% block modal-body %}
<div class="left">
<fieldset>
{% include "horizon/common/_form_fields.html" %}
</fieldset>
</div>
<div class="right">
<h3>{% trans "Description:" %}</h3>
<p>{% blocktrans trimmed %}
Select the floating IP to be disassociated from the instance.
{% endblocktrans %}</p>
<dl>
<dt>{% trans "Release Floating IP" %}</dt>
<dd>{% blocktrans trimmed %}
If checked, the selected floating IP will be released at the same time.
{% endblocktrans %}</dd>
</dl>
</div>
{% endblock %}

View File

@ -0,0 +1,7 @@
{% extends "base.html" %}
{% load i18n %}
{% block title %}{% trans "Disassociate Floating IP" %}{% endblock %}
{% block main %}
{% include "project/instances/_disassociate.html" %}
{% endblock %}

View File

@ -4208,13 +4208,10 @@ class InstanceTests2(InstanceTestBase, InstanceTableTestMixin):
@helpers.create_mocks({
api.neutron: ('floating_ip_target_list_by_instance',
'tenant_floating_ip_list',
'floating_ip_disassociate',),
api.network: ('servers_update_addresses',),
api.glance: ('image_list_detailed',),
api.nova: ('server_list',
'flavor_list'),
'floating_ip_disassociate',
'tenant_floating_ip_release'),
})
def test_disassociate_floating_ip(self):
def _test_disassociate_floating_ip(self, is_release):
servers = self.servers.list()
server = servers[0]
port = [p for p in self.ports.list() if p.device_id == server.id][0]
@ -4223,35 +4220,40 @@ class InstanceTests2(InstanceTestBase, InstanceTableTestMixin):
fip = self.floating_ips.first()
fip.port_id = port.id
self.mock_server_list.return_value = [servers, False]
self.mock_servers_update_addresses.return_value = None
self.mock_flavor_list.return_value = self.flavors.list()
self.mock_image_list_detailed.return_value = (self.images.list(),
False, False)
self.mock_floating_ip_target_list_by_instance.return_value = \
[fip_target]
self.mock_tenant_floating_ip_list.return_value = [fip]
self.mock_floating_ip_disassociate.return_value = None
self.mock_tenant_floating_ip_release.return_value = None
formData = {'action': 'instances__disassociate__%s' % server.id}
res = self.client.post(INDEX_URL, formData)
url = reverse('horizon:project:instances:disassociate',
args=[server.id])
form_data = {'fip': fip.id,
'is_release': is_release}
res = self.client.post(url, form_data)
self.assertRedirectsNoFollow(res, INDEX_URL)
search_opts = {'marker': None, 'paginate': True}
self.mock_server_list.assert_called_once_with(
helpers.IsHttpRequest(), search_opts=search_opts)
self.mock_servers_update_addresses.assert_called_once_with(
helpers.IsHttpRequest(), servers)
self.mock_flavor_list.assert_called_once_with(helpers.IsHttpRequest())
self.mock_image_list_detailed.assert_called_once_with(
helpers.IsHttpRequest())
self.mock_floating_ip_target_list_by_instance.assert_called_once_with(
helpers.IsHttpRequest(), server.id)
self.mock_tenant_floating_ip_list.assert_called_once_with(
helpers.IsHttpRequest())
self.mock_floating_ip_disassociate.assert_called_once_with(
helpers.IsHttpRequest(), fip.id)
if is_release:
self.mock_floating_ip_disassociate.assert_not_called()
self.mock_tenant_floating_ip_release.assert_called_once_with(
helpers.IsHttpRequest(), fip.id)
else:
self.mock_floating_ip_disassociate.assert_called_once_with(
helpers.IsHttpRequest(), fip.id)
self.mock_tenant_floating_ip_release.assert_not_called()
@helpers.create_mocks({api.neutron: ('floating_ip_disassociate',)})
def test_disassociate_floating_ip(self):
self._test_disassociate_floating_ip(is_release=False)
@helpers.create_mocks({api.neutron: ('tenant_floating_ip_release',)})
def test_disassociate_floating_ip_with_release(self):
self._test_disassociate_floating_ip(is_release=True)
@helpers.create_mocks({api.nova: ('server_get',
'flavor_list',

View File

@ -41,6 +41,8 @@ urlpatterns = [
url(INSTANCES % 'resize', views.ResizeView.as_view(), name='resize'),
url(INSTANCES_KEYPAIR % 'decryptpassword',
views.DecryptPasswordView.as_view(), name='decryptpassword'),
url(INSTANCES % 'disassociate',
views.DisassociateView.as_view(), name='disassociate'),
url(INSTANCES % 'attach_interface',
views.AttachInterfaceView.as_view(), name='attach_interface'),
url(INSTANCES % 'detach_interface',

View File

@ -389,6 +389,22 @@ class DecryptPasswordView(forms.ModalFormView):
'keypair_name': self.kwargs['keypair_name']}
class DisassociateView(forms.ModalFormView):
form_class = project_forms.Disassociate
template_name = 'project/instances/disassociate.html'
success_url = reverse_lazy('horizon:project:instances:index')
page_title = _("Disassociate floating IP")
submit_label = _("Disassocaite")
def get_context_data(self, **kwargs):
context = super(DisassociateView, self).get_context_data(**kwargs)
context['instance_id'] = self.kwargs['instance_id']
return context
def get_initial(self):
return {'instance_id': self.kwargs['instance_id']}
class DetailView(tabs.TabView):
tab_group_class = project_tabs.InstanceDetailTabs
template_name = 'horizon/common/_detail.html'

View File

@ -128,10 +128,6 @@ WEBROOT = '/'
# "help_text": _("Your password does not meet the requirements."),
#}
# Disable simplified floating IP address management for deployments with
# multiple floating IP pools or complex network requirements.
#HORIZON_CONFIG["simple_ip_management"] = False
# Turn off browser autocompletion for forms including the login form and
# the database creation workflow if so desired.
#HORIZON_CONFIG["password_autocomplete"] = "off"

View File

@ -0,0 +1,12 @@
---
features:
- |
Floating IP can be released when it is disassociated from a server.
"Release Floating IP" checkbox is now available in "Disassociate
Floating IP" form.
upgrade:
- |
``simple_ip_management`` setting in ``HORIZON_CONFIG`` was dropped.
This actually has no meaning after nova-network support was dropped in Pike.
If you use this setting to hide ``Disaccoaite Floating IP`` button in the
instance table, use the policy file instead.