Skip to content

Commit

Permalink
Converts all of Access & Security to use new panels, modals, views, etc.
Browse files Browse the repository at this point in the history
Adds empty table message, multi-table CBV, improved testing facilities.

Fixes bug 905376. Fixes bug 905399.

Change-Id: Ib93a5b9d09c9b98b0a6365f7d468efb05e28e676
  • Loading branch information
gabrielhurley committed Jan 10, 2012
1 parent 120b43b commit 22d80f7
Show file tree
Hide file tree
Showing 44 changed files with 650 additions and 839 deletions.
10 changes: 10 additions & 0 deletions docs/source/ref/tables.rst
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,13 @@ Actions

.. autoclass:: FilterAction
:members:

Class-Based Views
=================

Several class-based views are provided to make working with DataTables
easier in your UI.

.. autoclass:: DataTableView

.. autoclass:: MultiTableView
13 changes: 3 additions & 10 deletions horizon/horizon/api/nova.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,16 +132,9 @@ class SecurityGroup(APIResourceWrapper):
_attrs = ['id', 'name', 'description', 'tenant_id', 'rules']


class SecurityGroupRule(APIResourceWrapper):
"""Simple wrapper around
openstackx.extras.security_groups.SecurityGroupRule"""
_attrs = ['id', 'parent_group_id', 'group_id', 'ip_protocol',
'from_port', 'to_port', 'groups', 'ip_ranges']


class SecurityGroupRule(APIResourceWrapper):
"""Simple wrapper around openstackx.extras.users.User"""
_attrs = ['id', 'name', 'description', 'tenant_id', 'security_group_rules']
class SecurityGroupRule(APIDictWrapper):
""" Simple wrapper for individual rules in a SecurityGroup. """
_attrs = ['ip_protocol', 'from_port', 'to_port', 'ip_range']


def novaclient(request):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,22 +33,6 @@
LOG = logging.getLogger(__name__)


class ReleaseFloatingIp(forms.SelfHandlingForm):
floating_ip_id = forms.CharField(widget=forms.HiddenInput())

def handle(self, request, data):
try:
LOG.info('Releasing Floating IP "%s"' % data['floating_ip_id'])
api.tenant_floating_ip_release(request, data['floating_ip_id'])
messages.info(request, _('Successfully released Floating IP: %s')
% data['floating_ip_id'])
except novaclient_exceptions.ClientException, e:
LOG.exception("ClientException in ReleaseFloatingIp")
messages.error(request, _('Error releasing Floating IP '
'from tenant: %s') % e.message)
return shortcuts.redirect(request.build_absolute_uri())


class FloatingIpAssociate(forms.SelfHandlingForm):
floating_ip_id = forms.CharField(widget=forms.HiddenInput())
floating_ip = forms.CharField(widget=forms.TextInput(
Expand All @@ -69,58 +53,11 @@ def handle(self, request, data):
data['floating_ip_id'])
LOG.info('Associating Floating IP "%s" with Instance "%s"'
% (data['floating_ip'], data['instance_id']))
messages.info(request, _('Successfully associated Floating IP: \
messages.info(request, _('Successfully associated Floating IP \
%(ip)s with Instance: %(inst)s'
% {"ip": data['floating_ip'],
"inst": data['instance_id']}))
except novaclient_exceptions.ClientException, e:
LOG.exception("ClientException in FloatingIpAssociate")
messages.error(request, _('Error associating Floating IP: %s')
% e.message)
return shortcuts.redirect(
'horizon:nova:access_and_security:index')


class FloatingIpDisassociate(forms.SelfHandlingForm):
floating_ip_id = forms.CharField(widget=forms.HiddenInput())

def handle(self, request, data):
try:
fip = api.tenant_floating_ip_get(request, data['floating_ip_id'])
api.server_remove_floating_ip(request, fip.instance_id, fip.id)

LOG.info('Disassociating Floating IP "%s"'
% data['floating_ip_id'])

messages.info(request,
_('Successfully disassociated Floating IP: %s')
% data['floating_ip_id'])
except novaclient_exceptions.ClientException, e:
LOG.exception("ClientException in FloatingIpAssociate")
messages.error(request, _('Error disassociating Floating IP: %s')
% e.message)
return shortcuts.redirect(
'horizon:nova:access_and_security:index')


class FloatingIpAllocate(forms.SelfHandlingForm):
tenant_id = forms.CharField(widget=forms.HiddenInput())

def handle(self, request, data):
try:
fip = api.tenant_floating_ip_allocate(request)
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 novaclient_exceptions.ClientException, e:
LOG.exception("ClientException in FloatingIpAllocate")
messages.error(request, _('Error allocating Floating IP "%(ip)s"\
to tenant "%(tenant)s": %(msg)s') %
{"ip": fip.ip, "tenant": data['tenant_id'], "msg": e.message})
return shortcuts.redirect(
'horizon:nova:access_and_security:index')
messages.error(request, _('Error associating Floating IP: %s') % e)
return shortcuts.redirect('horizon:nova:access_and_security:index')
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4

# Copyright 2011 Nebula, 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
# 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.contrib import messages
from django.core.urlresolvers import reverse
from novaclient import exceptions as novaclient_exceptions

from horizon import api
from horizon import tables


LOG = logging.getLogger(__name__)


class AllocateIP(tables.Action):
name = "allocate"
verbose_name = _("Allocate IP To Tenant")
requires_input = False

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


class ReleaseIP(tables.Action):
name = "release"
verbose_name = _("Release IP")
classes = ('danger',)

def handle(self, table, request, object_ids):
released = []
for obj_id in object_ids:
LOG.info('Releasing Floating IP "%s".' % obj_id)
try:
api.tenant_floating_ip_release(request, obj_id)
# Floating IP ids are returned from the API as integers
released.append(table.get_object_by_id(int(obj_id)))
except novaclient_exceptions.ClientException, e:
LOG.exception("ClientException in ReleaseFloatingIp")
messages.error(request, _('Unable to release Floating IP '
'from tenant.'))
if released:
messages.info(request,
_('Successfully released floating IPs: %s.')
% ", ".join([ip.ip for ip in released]))
return shortcuts.redirect('horizon:nova:access_and_security:index')


class AssociateIP(tables.LinkAction):
name = "associate"
verbose_name = _("Associate IP")
url = "horizon:nova:access_and_security:floating_ips:associate"
attrs = {"class": "ajax-modal"}

def allowed(self, request, fip):
if fip.instance_id:
return False
return True


class DisassociateIP(tables.Action):
name = "disassociate"
verbose_name = _("Disassociate IP")

def allowed(self, request, fip):
if fip.instance_id:
return True
return False

def single(self, table, request, obj_id):
try:
fip = table.get_object_by_id(int(obj_id))
api.server_remove_floating_ip(request, fip.instance_id, fip.id)
LOG.info('Disassociating Floating IP "%s".' % obj_id)
messages.info(request,
_('Successfully disassociated Floating IP: %s')
% obj_id)
except novaclient_exceptions.ClientException, e:
LOG.exception("ClientException in FloatingIpAssociate")
messages.error(request, _('Error disassociating Floating IP: %s')
% e.message)
return shortcuts.redirect('horizon:nova:access_and_security:index')


class FloatingIPsTable(tables.DataTable):
ip = tables.Column("ip", verbose_name=_("IP Address"))
instance = tables.Column("instance_id",
verbose_name=_("Instance"),
empty_value="-")

class Meta:
name = "floating_ips"
verbose_name = _("Floating IPs")
table_actions = (AllocateIP, ReleaseIP)
row_actions = (AssociateIP, DisassociateIP, ReleaseIP)
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import datetime

from django import http
from django.contrib import messages
from django.core.urlresolvers import reverse
from django.shortcuts import redirect
from mox import IsA, IgnoreArg
Expand All @@ -33,7 +32,7 @@
FloatingIpAssociate


FLOATING_IPS_INDEX = reverse('horizon:nova:access_and_security:index')
INDEX_URL = reverse('horizon:nova:access_and_security:index')


class FloatingIpViewTests(test.BaseViewTests):
Expand Down Expand Up @@ -111,7 +110,7 @@ def test_associate_post(self):
'floating_ip': self.floating_ip.ip,
'method': 'FloatingIpAssociate'})

self.assertRedirects(res, FLOATING_IPS_INDEX)
self.assertRedirects(res, INDEX_URL)

def test_associate_post_with_exception(self):
server = self.server
Expand Down Expand Up @@ -152,70 +151,51 @@ def test_associate_post_with_exception(self):
'method': 'FloatingIpAssociate'})
self.assertRaises(novaclient_exceptions.ClientException)

self.assertRedirects(res, FLOATING_IPS_INDEX)

def test_disassociate(self):
res = self.client.get(
reverse('horizon:nova:access_and_security:floating_ips:disassociate',
args=[1]))
self.assertTemplateUsed(res,
'nova/access_and_security/floating_ips/associate.html')
self.assertRedirects(res, INDEX_URL)

def test_disassociate_post(self):
self.mox.StubOutWithMock(api, 'tenant_floating_ip_list')
api.tenant_floating_ip_list(IsA(http.HttpRequest)).\
AndReturn(self.floating_ips)
self.mox.StubOutWithMock(api.nova, 'keypair_list')
self.mox.StubOutWithMock(api, 'security_group_list')
self.mox.StubOutWithMock(api, 'tenant_floating_ip_list')
self.mox.StubOutWithMock(api, 'tenant_floating_ip_get')
self.mox.StubOutWithMock(api, 'server_remove_floating_ip')

api.nova.keypair_list(IsA(http.HttpRequest)).AndReturn(self.keypairs)
api.security_group_list(IsA(http.HttpRequest)).\
AndReturn(self.security_groups)
self.mox.StubOutWithMock(api.nova, 'keypair_list')
api.nova.keypair_list(IsA(http.HttpRequest)).AndReturn(self.keypairs)
api.tenant_floating_ip_list(IsA(http.HttpRequest)).\
AndReturn(self.floating_ips)

self.mox.StubOutWithMock(api, 'server_remove_floating_ip')
api.server_remove_floating_ip = self.mox.CreateMockAnything()
api.server_remove_floating_ip(IsA(http.HttpRequest), IsA(int),
IsA(int)).\
AndReturn(None)
self.mox.StubOutWithMock(messages, 'info')
messages.info(IsA(http.HttpRequest), IsA(unicode))

self.mox.StubOutWithMock(api, 'tenant_floating_ip_get')
api.tenant_floating_ip_get = self.mox.CreateMockAnything()
api.tenant_floating_ip_get(IsA(http.HttpRequest), IsA(unicode)).\
AndReturn(self.floating_ip)
self.mox.ReplayAll()
res = self.client.post(
reverse('horizon:nova:access_and_security:floating_ips:disassociate',
args=[1]),
{'floating_ip_id': self.floating_ip.id,
'method': 'FloatingIpDisassociate'})
self.assertRedirects(res, FLOATING_IPS_INDEX)

action = "floating_ips__disassociate__%s" % self.floating_ip.id
res = self.client.post(INDEX_URL, {"action": action})
self.assertRedirectsNoFollow(res, INDEX_URL)

def test_disassociate_post_with_exception(self):
self.mox.StubOutWithMock(api, 'tenant_floating_ip_list')
api.tenant_floating_ip_list(IsA(http.HttpRequest)).\
AndReturn(self.floating_ips)
self.mox.StubOutWithMock(api.nova, 'keypair_list')
self.mox.StubOutWithMock(api, 'security_group_list')
self.mox.StubOutWithMock(api, 'tenant_floating_ip_list')
self.mox.StubOutWithMock(api, 'tenant_floating_ip_get')
self.mox.StubOutWithMock(api, 'server_remove_floating_ip')

api.nova.keypair_list(IsA(http.HttpRequest)).AndReturn(self.keypairs)
api.security_group_list(IsA(http.HttpRequest)).\
AndReturn(self.security_groups)
self.mox.StubOutWithMock(api.nova, 'keypair_list')
api.nova.keypair_list(IsA(http.HttpRequest)).AndReturn(self.keypairs)
api.tenant_floating_ip_list(IsA(http.HttpRequest)).\
AndReturn(self.floating_ips)

self.mox.StubOutWithMock(api, 'server_remove_floating_ip')
exception = novaclient_exceptions.ClientException('ClientException',
message='clientException')
api.server_remove_floating_ip(IsA(http.HttpRequest),
IsA(int),
IsA(int)).AndRaise(exception)
self.mox.StubOutWithMock(api, 'tenant_floating_ip_get')
api.tenant_floating_ip_get = self.mox.CreateMockAnything()
api.tenant_floating_ip_get(IsA(http.HttpRequest), IsA(unicode)).\
AndReturn(self.floating_ip)
self.mox.ReplayAll()
res = self.client.post(
reverse('horizon:nova:access_and_security:floating_ips:disassociate',
args=[1]),
{'floating_ip_id': self.floating_ip.id,
'method': 'FloatingIpDisassociate'})

action = "floating_ips__disassociate__%s" % self.floating_ip.id
res = self.client.post(INDEX_URL, {"action": action})
self.assertRaises(novaclient_exceptions.ClientException)
self.assertRedirects(res, FLOATING_IPS_INDEX)
self.assertRedirectsNoFollow(res, INDEX_URL)
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@

from django.conf.urls.defaults import patterns, url

from .views import AssociateView

urlpatterns = patterns(
'horizon.dashboards.nova.access_and_security.floating_ips.views',
url(r'^$', 'index', name='index'),
url(r'^(?P<ip_id>[^/]+)/associate/$', 'associate', name='associate'),
url(r'^(?P<ip_id>[^/]+)/disassociate/$', 'disassociate',
name='disassociate'))

urlpatterns = patterns('',
url(r'^(?P<ip_id>[^/]+)/associate/$',
AssociateView.as_view(),
name='associate')
)
Loading

0 comments on commit 22d80f7

Please sign in to comment.