Implementing Floating IP Pools for Horizon

- vishy's nova branch: https://review.openstack.org/#change,2892
  - jakedahn's novaclient branch: https://review.openstack.org/#change,2917

Change-Id: I41fb7359e841cbe5921db864dd4e754e9fe0874d
This commit is contained in:
jakedahn 2012-01-16 15:26:05 -08:00
parent ad36c057f4
commit 29b70fbf92
9 changed files with 124 additions and 24 deletions

View File

@ -5,6 +5,7 @@
# All Rights Reserved.
#
# Copyright 2011 Nebula, Inc.
# Copyright (c) 2011 X.commerce, a business unit of eBay Inc.
#
# 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
@ -45,8 +46,13 @@ class Flavor(APIResourceWrapper):
class FloatingIp(APIResourceWrapper):
"""Simple wrapper for floating ip pools"""
_attrs = ['ip', 'fixed_ip', 'instance_id', 'id', 'pool']
class FloatingIpPool(APIResourceWrapper):
"""Simple wrapper for floating ips"""
_attrs = ['ip', 'fixed_ip', 'instance_id', 'id']
_attrs = ['name']
class KeyPair(APIResourceWrapper):
@ -179,6 +185,13 @@ def tenant_floating_ip_list(request):
return [FloatingIp(ip) for ip in novaclient(request).floating_ips.list()]
def floating_ip_pools_list(request):
"""
Fetches a list of all floating ip pools.
"""
return [FloatingIpPool(pool)
for pool in novaclient(request).floating_ip_pools.list()]
def tenant_floating_ip_get(request, floating_ip_id):
"""
Fetches a floating ip.
@ -186,11 +199,12 @@ def tenant_floating_ip_get(request, floating_ip_id):
return novaclient(request).floating_ips.get(floating_ip_id)
def tenant_floating_ip_allocate(request):
def tenant_floating_ip_allocate(request, pool=None):
"""
Allocates a floating ip to tenant.
Optionally you may provide a pool for which you would like the IP.
"""
return novaclient(request).floating_ips.create()
return novaclient(request).floating_ips.create(pool=pool)
def tenant_floating_ip_release(request, floating_ip_id):

View File

@ -5,6 +5,7 @@
# All Rights Reserved.
#
# Copyright 2011 Nebula, Inc.
# Copyright (c) 2011 X.commerce, a business unit of eBay Inc.
#
# 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
@ -26,6 +27,7 @@ from django.utils.translation import ugettext as _
from novaclient import exceptions as novaclient_exceptions
from horizon import api
from horizon import exceptions
from horizon import forms
@ -60,3 +62,29 @@ class FloatingIpAssociate(forms.SelfHandlingForm):
LOG.exception("ClientException in FloatingIpAssociate")
messages.error(request, _('Error associating Floating IP: %s') % e)
return shortcuts.redirect('horizon:nova:access_and_security:index')
class FloatingIpAllocate(forms.SelfHandlingForm):
tenant_id = forms.CharField(widget=forms.HiddenInput())
pool = forms.ChoiceField()
def __init__(self, *args, **kwargs):
super(FloatingIpAllocate, self).__init__(*args, **kwargs)
floating_pool_list = kwargs.get('initial', {}).get('pool_list', [])
self.fields['pool'].choices = floating_pool_list
def handle(self, request, data):
try:
fip = api.tenant_floating_ip_allocate(request,
pool=data.get('pool', None))
LOG.info('Allocating Floating IP "%s" to tenant "%s"'
% (fip.ip, data['tenant_id']))
messages.success(request,
_('Successfully allocated Floating IP "%(ip)s"\
to tenant "%(tenant)s"')
% {"ip": fip.ip, "tenant": data['tenant_id']})
except:
exceptions.handle(request, _('Unable to allocate Floating IP.'))
return shortcuts.redirect(
'horizon:nova:access_and_security:index')

View File

@ -1,6 +1,7 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 Nebula, Inc.
# Copyright (c) 2011 X.commerce, a business unit of eBay Inc.
#
# 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
@ -28,25 +29,13 @@ from horizon import tables
LOG = logging.getLogger(__name__)
class AllocateIP(tables.Action):
class AllocateIP(tables.LinkAction):
name = "allocate"
verbose_name = _("Allocate IP To Tenant")
requires_input = False
attrs = {"class": "ajax-modal btn primary small"}
url = "horizon:nova:access_and_security:floating_ips:allocate"
def single(self, data_table, request, *args):
tenant_id = request.user.tenant_id
try:
fip = api.tenant_floating_ip_allocate(request)
LOG.info('Allocating Floating IP "%s" to tenant "%s".'
% (fip.ip, tenant_id))
messages.success(request, _('Successfully allocated Floating IP '
'"%(ip)s" to tenant "%(tenant)s".')
% {"ip": fip.ip, "tenant": tenant_id})
except novaclient_exceptions.ClientException, e:
LOG.exception("ClientException in FloatingIpAllocate")
messages.error(request, _('Unable to allocate Floating IP '
'"%(ip)s" to tenant "%(tenant)s".')
% {"ip": fip.ip, "tenant": tenant_id})
return shortcuts.redirect('horizon:nova:access_and_security:index')
@ -102,6 +91,9 @@ class FloatingIPsTable(tables.DataTable):
instance = tables.Column("instance_id",
verbose_name=_("Instance"),
empty_value="-")
pool = tables.Column("pool",
verbose_name=_("Floating Ip Pool"),
empty_value="-")
def sanitize_id(self, obj_id):
return int(obj_id)

View File

@ -5,6 +5,7 @@
# All Rights Reserved.
#
# Copyright 2011 Nebula, Inc.
# Copyright (c) 2011 X.commerce, a business unit of eBay Inc.
#
# 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

View File

@ -20,11 +20,14 @@
from django.conf.urls.defaults import patterns, url
from .views import AssociateView
from .views import AssociateView, AllocateView
urlpatterns = patterns('',
url(r'^(?P<ip_id>[^/]+)/associate/$',
AssociateView.as_view(),
name='associate')
name='associate'),
url(r'^allocate/$',
AllocateView.as_view(),
name='allocate')
)

View File

@ -5,6 +5,7 @@
# All Rights Reserved.
#
# Copyright 2011 Nebula, Inc.
# Copyright (c) 2011 X.commerce, a business unit of eBay Inc.
#
# 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
@ -28,7 +29,7 @@ from django.utils.translation import ugettext as _
from horizon import api
from horizon import forms
from .forms import FloatingIpAssociate
from .forms import FloatingIpAssociate, FloatingIpAllocate
LOG = logging.getLogger(__name__)
@ -56,3 +57,19 @@ class AssociateView(forms.ModalFormView):
return {'floating_ip_id': self.object.id,
'floating_ip': self.object.ip,
'instances': instances}
class AllocateView(forms.ModalFormView):
form_class = FloatingIpAllocate
template_name = 'nova/access_and_security/floating_ips/allocate.html'
context_object_name = 'floating_ip'
def get_initial(self):
pools = api.floating_ip_pools_list(self.request)
if pools:
pool_list = [(pool.name, pool.name)
for pool in api.floating_ip_pools_list(self.request)]
else:
pool_list = [(None, _("There are no Floating IP Pools"))]
return {'tenant_id': self.request.user.tenant_id,
'pool_list': pool_list}

View File

@ -0,0 +1,24 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% block form_id %}associate_floating_ip_form{% endblock %}
{% block form_action %}{% url horizon:nova:access_and_security:floating_ips:allocate %}{% endblock %}
{% block modal-header %}{% trans "Allocate 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>{% trans "Allocate a floating IP from a given floating ip pool." %}</p>
</div>
{% endblock %}
{% block modal-footer %}
<input class="btn primary pull-right" type="submit" value="{% trans "Allocate IP" %}" />
<a href="{% url horizon:nova:access_and_security:index %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
{% endblock %}

View File

@ -0,0 +1,7 @@
{% extends 'nova/base.html' %}
{% load i18n %}
{% block title %}{% trans "Allocate Floating IP" %}{% endblock %}
{% block dash_main %}
{% include 'nova/access_and_security/floating_ips/_allocate.html' %}
{% endblock %}

View File

@ -5,6 +5,7 @@
# All Rights Reserved.
#
# Copyright 2011 Nebula, Inc.
# Copyright (c) 2011 X.commerce, a business unit of eBay Inc.
#
# 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
@ -22,7 +23,7 @@ from __future__ import absolute_import
from django import http
from django.conf import settings
from mox import IsA
from mox import IsA, IgnoreArg
from openstackx import admin as OSAdmin
from openstackx import auth as OSAuth
from openstackx import extras as OSExtras
@ -477,17 +478,30 @@ class APIExtensionTests(APITestCase):
self.assertIsInstance(floating_ip, api.FloatingIp)
def test_tenant_floating_ip_allocate(self):
def test_tenant_floating_ip_allocate_without_pool(self):
novaclient = self.stub_novaclient()
novaclient.floating_ips = self.mox.CreateMockAnything()
novaclient.floating_ips.create().AndReturn(self.floating_ip)
novaclient.floating_ips.create(pool=IgnoreArg()).\
AndReturn(self.floating_ip)
self.mox.ReplayAll()
floating_ip = api.tenant_floating_ip_allocate(self.request)
self.assertIsInstance(floating_ip, api.FloatingIp)
def test_tenant_floating_ip_allocate_with_pool(self):
novaclient = self.stub_novaclient()
novaclient.floating_ips = self.mox.CreateMockAnything()
novaclient.floating_ips.create(pool="nova").AndReturn(self.floating_ip)
self.mox.ReplayAll()
floating_ip = api.tenant_floating_ip_allocate(self.request,
pool='nova')
self.assertIsInstance(floating_ip, api.FloatingIp)
def test_tenant_floating_ip_release(self):
novaclient = self.stub_novaclient()