Merging Floating Ips, Keypairs, and Security Groups into a single panel.

Change-Id: Ia5b755f2e89a121fc67ca0ddb1a173e3ea97a889
This commit is contained in:
jakedahn 2011-12-10 12:04:13 -08:00
parent 8a121c8d17
commit 3f6e168e8e
50 changed files with 440 additions and 245 deletions

View File

@ -77,7 +77,8 @@ class FloatingIpAssociate(forms.SelfHandlingForm):
LOG.exception("ClientException in FloatingIpAssociate")
messages.error(request, _('Error associating Floating IP: %s')
% e.message)
return shortcuts.redirect('horizon:nova:floating_ips:index')
return shortcuts.redirect(
'horizon:nova:access_and_security:index')
class FloatingIpDisassociate(forms.SelfHandlingForm):
@ -98,7 +99,8 @@ class FloatingIpDisassociate(forms.SelfHandlingForm):
LOG.exception("ClientException in FloatingIpAssociate")
messages.error(request, _('Error disassociating Floating IP: %s')
% e.message)
return shortcuts.redirect('horizon:nova:floating_ips:index')
return shortcuts.redirect(
'horizon:nova:access_and_security:index')
class FloatingIpAllocate(forms.SelfHandlingForm):
@ -120,4 +122,5 @@ class FloatingIpAllocate(forms.SelfHandlingForm):
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:floating_ips:index')
return shortcuts.redirect(
'horizon:nova:access_and_security:index')

View File

@ -29,16 +29,21 @@ from novaclient import exceptions as novaclient_exceptions
from horizon import api
from horizon import test
from horizon.dashboards.nova.floating_ips.forms import FloatingIpAssociate
from horizon.dashboards.nova.access_and_security.floating_ips.forms import \
FloatingIpAssociate
FLOATING_IPS_INDEX = reverse('horizon:nova:floating_ips:index')
FLOATING_IPS_INDEX = reverse('horizon:nova:access_and_security:index')
class FloatingIpViewTests(test.BaseViewTests):
def setUp(self):
super(FloatingIpViewTests, self).setUp()
keypair = api.KeyPair(None)
keypair.name = 'keyName'
self.keypairs = (keypair,)
server = api.Server(None, self.request)
server.id = 1
server.name = 'serverName'
@ -54,15 +59,10 @@ class FloatingIpViewTests(test.BaseViewTests):
self.floating_ip = floating_ip
self.floating_ips = [floating_ip, ]
def test_index(self):
self.mox.StubOutWithMock(api, 'tenant_floating_ip_list')
api.tenant_floating_ip_list(IsA(http.HttpRequest)).\
AndReturn(self.floating_ips)
self.mox.ReplayAll()
res = self.client.get(FLOATING_IPS_INDEX)
self.assertTemplateUsed(res, 'nova/floating_ips/index.html')
self.assertItemsEqual(res.context['floating_ips'], self.floating_ips)
security_group = api.SecurityGroup(None)
security_group.id = '1'
security_group.name = 'default'
self.security_groups = (security_group,)
def test_associate(self):
self.mox.StubOutWithMock(api, 'server_list')
@ -75,9 +75,11 @@ class FloatingIpViewTests(test.BaseViewTests):
AndReturn(self.floating_ip)
self.mox.ReplayAll()
res = self.client.get(reverse('horizon:nova:floating_ips:associate',
args=[1]))
self.assertTemplateUsed(res, 'nova/floating_ips/associate.html')
res = self.client.get(
reverse('horizon:nova:access_and_security:floating_ips:associate',
args=[1]))
self.assertTemplateUsed(res,
'nova/access_and_security/floating_ips/associate.html')
def test_associate_post(self):
server = self.server
@ -104,8 +106,9 @@ class FloatingIpViewTests(test.BaseViewTests):
AndReturn(self.floating_ip)
self.mox.ReplayAll()
res = self.client.post(reverse('horizon:nova:floating_ips:associate',
args=[1]),
res = self.client.post(
reverse('horizon:nova:access_and_security:floating_ips:associate',
args=[1]),
{'instance_id': 1,
'floating_ip_id': self.floating_ip.id,
'floating_ip': self.floating_ip.ip,
@ -123,6 +126,11 @@ class FloatingIpViewTests(test.BaseViewTests):
self.mox.StubOutWithMock(api, 'tenant_floating_ip_list')
api.tenant_floating_ip_list(IsA(http.HttpRequest)).\
AndReturn(self.floating_ips)
self.mox.StubOutWithMock(api, 'security_group_list')
api.security_group_list(IsA(http.HttpRequest)).\
AndReturn(self.security_groups)
self.mox.StubOutWithMock(api, 'keypair_list')
api.keypair_list(IsA(http.HttpRequest)).AndReturn(self.keypairs)
self.mox.StubOutWithMock(api, 'server_add_floating_ip')
api.server_add_floating_ip = self.mox.CreateMockAnything()
@ -141,8 +149,8 @@ class FloatingIpViewTests(test.BaseViewTests):
AndReturn(self.floating_ip)
self.mox.ReplayAll()
res = self.client.post(reverse('horizon:nova:floating_ips:associate',
args=[1]),
res = self.client.post(reverse(
'horizon:nova:access_and_security:floating_ips:associate',args=[1]),
{'instance_id': 1,
'floating_ip_id': self.floating_ip.id,
'floating_ip': self.floating_ip.ip,
@ -152,14 +160,21 @@ class FloatingIpViewTests(test.BaseViewTests):
self.assertRedirects(res, FLOATING_IPS_INDEX)
def test_disassociate(self):
res = self.client.get(reverse('horizon:nova:floating_ips:disassociate',
args=[1]))
self.assertTemplateUsed(res, 'nova/floating_ips/associate.html')
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')
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, 'security_group_list')
api.security_group_list(IsA(http.HttpRequest)).\
AndReturn(self.security_groups)
self.mox.StubOutWithMock(api, 'keypair_list')
api.keypair_list(IsA(http.HttpRequest)).AndReturn(self.keypairs)
self.mox.StubOutWithMock(api, 'server_remove_floating_ip')
api.server_remove_floating_ip = self.mox.CreateMockAnything()
@ -175,7 +190,8 @@ class FloatingIpViewTests(test.BaseViewTests):
AndReturn(self.floating_ip)
self.mox.ReplayAll()
res = self.client.post(
reverse('horizon:nova:floating_ips:disassociate', args=[1]),
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)
@ -184,6 +200,11 @@ class FloatingIpViewTests(test.BaseViewTests):
self.mox.StubOutWithMock(api, 'tenant_floating_ip_list')
api.tenant_floating_ip_list(IsA(http.HttpRequest)).\
AndReturn(self.floating_ips)
self.mox.StubOutWithMock(api, 'security_group_list')
api.security_group_list(IsA(http.HttpRequest)).\
AndReturn(self.security_groups)
self.mox.StubOutWithMock(api, 'keypair_list')
api.keypair_list(IsA(http.HttpRequest)).AndReturn(self.keypairs)
self.mox.StubOutWithMock(api, 'server_remove_floating_ip')
exception = novaclient_exceptions.ClientException('ClientException',
@ -200,7 +221,8 @@ class FloatingIpViewTests(test.BaseViewTests):
AndReturn(self.floating_ip)
self.mox.ReplayAll()
res = self.client.post(
reverse('horizon:nova:floating_ips:disassociate', args=[1]),
reverse('horizon:nova:access_and_security:floating_ips:disassociate',
args=[1]),
{'floating_ip_id': self.floating_ip.id,
'method': 'FloatingIpDisassociate'})
self.assertRaises(novaclient_exceptions.ClientException)

View File

@ -21,7 +21,8 @@
from django.conf.urls.defaults import patterns, url
urlpatterns = patterns('horizon.dashboards.nova.floating_ips.views',
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',

View File

@ -31,8 +31,9 @@ from django.utils.translation import ugettext as _
from novaclient import exceptions as novaclient_exceptions
from horizon import api
from horizon.dashboards.nova.floating_ips.forms import (ReleaseFloatingIp,
FloatingIpAssociate, FloatingIpDisassociate, FloatingIpAllocate)
from horizon.dashboards.nova.access_and_security.floating_ips.forms import \
(ReleaseFloatingIp, FloatingIpAssociate,
FloatingIpDisassociate, FloatingIpAllocate)
LOG = logging.getLogger(__name__)
@ -54,11 +55,11 @@ def index(request):
allocate_form = FloatingIpAllocate(initial={
'tenant_id': request.user.tenant_id})
return shortcuts.render(request,
'nova/floating_ips/index.html', {
'allocate_form': allocate_form,
'disassociate_form': FloatingIpDisassociate(),
'floating_ips': floating_ips,
'release_form': ReleaseFloatingIp()})
'nova/access_and_security/floating_ips/index.html', {
'allocate_form': allocate_form,
'disassociate_form': FloatingIpDisassociate(),
'floating_ips': floating_ips,
'release_form': ReleaseFloatingIp()})
@login_required
@ -78,10 +79,10 @@ def associate(request, ip_id):
'form': form}
if request.is_ajax():
template = 'nova/floating_ips/_associate.html'
template = 'nova/access_and_security/floating_ips/_associate.html'
context['hide'] = True
else:
template = 'nova/floating_ips/associate.html'
template = 'nova/access_and_security/floating_ips/associate.html'
return shortcuts.render(request, template, context)
@ -92,5 +93,6 @@ def disassociate(request, ip_id):
if handled:
return handled
return shortcuts.render(request, 'nova/floating_ips/associate.html', {
'floating_ip_id': ip_id})
return shortcuts.render(request,
'nova/access_and_security/floating_ips/associate.html', {
'floating_ip_id': ip_id})

View File

@ -90,7 +90,8 @@ class ImportKeypair(forms.SelfHandlingForm):
api.keypair_import(request, data['name'], data['public_key'])
messages.success(request, _('Successfully imported public key: %s')
% data['name'])
return shortcuts.redirect('horizon:nova:keypairs:index')
return shortcuts.redirect(
'horizon:nova:access_and_security:index')
except novaclient_exceptions.ClientException, e:
LOG.exception("ClientException in ImportKeypair")
messages.error(request,

View File

@ -35,32 +35,6 @@ class KeyPairViewTests(test.BaseViewTests):
keypair.name = 'keyName'
self.keypairs = (keypair,)
def test_index(self):
self.mox.StubOutWithMock(api, 'keypair_list')
api.keypair_list(IsA(http.HttpRequest)).AndReturn(self.keypairs)
self.mox.ReplayAll()
res = self.client.get(reverse('horizon:nova:keypairs:index'))
self.assertTemplateUsed(res, 'nova/keypairs/index.html')
self.assertItemsEqual(res.context['keypairs'], self.keypairs)
def test_index_exception(self):
exception = novaclient_exceptions.ClientException('clientException',
message='clientException')
self.mox.StubOutWithMock(api, 'keypair_list')
api.keypair_list(IsA(http.HttpRequest)).AndRaise(exception)
self.mox.StubOutWithMock(messages, 'error')
messages.error(IsA(http.HttpRequest), IsA(basestring))
self.mox.ReplayAll()
res = self.client.get(reverse('horizon:nova:keypairs:index'))
self.assertTemplateUsed(res, 'nova/keypairs/index.html')
self.assertEqual(len(res.context['keypairs']), 0)
def test_delete_keypair(self):
KEYPAIR_ID = self.keypairs[0].name
@ -73,11 +47,12 @@ class KeyPairViewTests(test.BaseViewTests):
self.mox.ReplayAll()
res = self.client.post(reverse('horizon:nova:keypairs:index'),
res = self.client.post(
reverse('horizon:nova:access_and_security:index'),
formData)
self.assertRedirectsNoFollow(res,
reverse('horizon:nova:keypairs:index'))
reverse('horizon:nova:access_and_security:index'))
def test_delete_keypair_exception(self):
KEYPAIR_ID = self.keypairs[0].name
@ -93,16 +68,19 @@ class KeyPairViewTests(test.BaseViewTests):
self.mox.ReplayAll()
res = self.client.post(reverse('horizon:nova:keypairs:index'),
res = self.client.post(
reverse('horizon:nova:access_and_security:index'),
formData)
self.assertRedirectsNoFollow(res,
reverse('horizon:nova:keypairs:index'))
reverse('horizon:nova:access_and_security:index'))
def test_create_keypair_get(self):
res = self.client.get(reverse('horizon:nova:keypairs:create'))
res = self.client.get(
reverse('horizon:nova:access_and_security:keypairs:create'))
self.assertTemplateUsed(res, 'nova/keypairs/create.html')
self.assertTemplateUsed(res,
'nova/access_and_security/keypairs/create.html')
def test_create_keypair_post(self):
KEYPAIR_NAME = 'newKeypair'
@ -122,8 +100,9 @@ class KeyPairViewTests(test.BaseViewTests):
self.mox.ReplayAll()
res = self.client.post(reverse('horizon:nova:keypairs:create'),
formData)
res = self.client.post(
reverse('horizon:nova:access_and_security:keypairs:create'),
formData)
self.assertTrue(res.has_header('Content-Disposition'))
@ -142,8 +121,9 @@ class KeyPairViewTests(test.BaseViewTests):
self.mox.ReplayAll()
res = self.client.post(reverse('horizon:nova:keypairs:create'),
formData)
res = self.client.post(
reverse('horizon:nova:access_and_security:keypairs:create'),
formData)
self.assertRedirectsNoFollow(res,
reverse('horizon:nova:keypairs:create'))
reverse('horizon:nova:access_and_security:keypairs:create'))

View File

@ -21,7 +21,8 @@
from django.conf.urls.defaults import patterns, url
urlpatterns = patterns('horizon.dashboards.nova.keypairs.views',
urlpatterns = patterns(
'horizon.dashboards.nova.access_and_security.keypairs.views',
url(r'^$', 'index', name='index'),
url(r'^create/$', 'create', name='create'),
url(r'^import/$', 'import_keypair', name='import'),

View File

@ -31,8 +31,8 @@ from django.utils.translation import ugettext as _
from novaclient import exceptions as novaclient_exceptions
from horizon import api
from horizon.dashboards.nova.keypairs.forms import (CreateKeypair,
DeleteKeypair, ImportKeypair)
from horizon.dashboards.nova.access_and_security.keypairs.forms import \
(CreateKeypair, DeleteKeypair, ImportKeypair)
LOG = logging.getLogger(__name__)
@ -57,10 +57,10 @@ def index(request):
context = {'keypairs': keypairs, 'delete_form': delete_form}
if request.is_ajax():
template = 'nova/keypairs/_list.html'
template = 'nova/access_and_security/keypairs/_list.html'
context['hide'] = True
else:
template = 'nova/keypairs/index.html'
template = 'nova/access_and_security/keypairs/index.html'
return shortcuts.render(request, template, context)
@ -74,10 +74,10 @@ def create(request):
context = {'form': form}
if request.is_ajax():
template = 'nova/keypairs/_create.html'
template = 'nova/access_and_security/keypairs/_create.html'
context['hide'] = True
else:
template = 'nova/keypairs/create.html'
template = 'nova/access_and_security/keypairs/create.html'
return shortcuts.render(request, template, context)
@ -91,9 +91,9 @@ def import_keypair(request):
context = {'form': form}
if request.is_ajax():
template = 'nova/keypairs/_import.html'
template = 'nova/access_and_security/keypairs/_import.html'
context['hide'] = True
else:
template = 'nova/keypairs/import.html'
template = 'nova/access_and_security/keypairs/import.html'
return shortcuts.render(request, template, context)

View File

@ -1,10 +1,7 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# Copyright 2011 Nebula, Inc.
# Copyright 2011 Openstack LLC
#
# 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,9 +19,9 @@ import horizon
from horizon.dashboards.nova import dashboard
class Keypairs(horizon.Panel):
name = "Keypairs"
slug = 'keypairs'
class AccessAndSecurity(horizon.Panel):
name = "Access & Security"
slug = 'access_and_security'
dashboard.Nova.register(Keypairs)
dashboard.Nova.register(AccessAndSecurity)

View File

@ -48,7 +48,8 @@ class CreateGroup(forms.SelfHandlingForm):
messages.success(request,
_('Successfully created security_group: %s')
% data['name'])
return shortcuts.redirect('horizon:nova:security_groups:index')
return shortcuts.redirect(
'horizon:nova:access_and_security:index')
except novaclient_exceptions.ClientException, e:
LOG.exception("ClientException in CreateGroup")
messages.error(request, _('Error creating security group: %s') %
@ -72,7 +73,8 @@ class DeleteGroup(forms.SelfHandlingForm):
LOG.exception("ClientException in DeleteGroup")
messages.error(request, _('Error deleting security group: %s')
% e.message)
return shortcuts.redirect('horizon:nova:security_groups:index')
return shortcuts.redirect(
'horizon:nova:access_and_security:index')
class AddRule(forms.SelfHandlingForm):

View File

@ -29,9 +29,11 @@ from horizon import api
from horizon import test
SECGROUP_ID = '1'
SG_INDEX_URL = reverse('horizon:nova:security_groups:index')
SG_CREATE_URL = reverse('horizon:nova:security_groups:create')
SG_EDIT_RULE_URL = reverse('horizon:nova:security_groups:edit_rules',
SG_INDEX_URL = reverse('horizon:nova:access_and_security:index')
SG_CREATE_URL = \
reverse('horizon:nova:access_and_security:security_groups:create')
SG_EDIT_RULE_URL = \
reverse('horizon:nova:access_and_security:security_groups:edit_rules',
args=[SECGROUP_ID])
@ -44,36 +46,11 @@ class SecurityGroupsViewTests(test.BaseViewTests):
security_group.name = 'default'
self.security_groups = (security_group,)
def test_index(self):
self.mox.StubOutWithMock(api, 'security_group_list')
api.security_group_list(IsA(http.HttpRequest)).\
AndReturn(self.security_groups)
self.mox.ReplayAll()
res = self.client.get(SG_INDEX_URL)
self.assertTemplateUsed(res, 'nova/security_groups/index.html')
self.assertItemsEqual(res.context['security_groups'],
self.security_groups)
def test_index_exception(self):
exception = novaclient_exceptions.ClientException('ClientException',
message='ClientException')
self.mox.StubOutWithMock(api, 'security_group_list')
api.security_group_list(IsA(http.HttpRequest)).AndRaise(exception)
self.mox.ReplayAll()
res = self.client.get(SG_INDEX_URL)
self.assertTemplateUsed(res, 'nova/security_groups/index.html')
self.assertEqual(len(res.context['security_groups']), 0)
def test_create_security_groups_get(self):
res = self.client.get(SG_CREATE_URL)
self.assertTemplateUsed(res, 'nova/security_groups/create.html')
self.assertTemplateUsed(res,
'nova/access_and_security/security_groups/create.html')
def test_create_security_groups_post(self):
SECGROUP_NAME = 'fakegroup'
@ -119,7 +96,8 @@ class SecurityGroupsViewTests(test.BaseViewTests):
res = self.client.post(SG_CREATE_URL, formData)
self.assertTemplateUsed(res, 'nova/security_groups/create.html')
self.assertTemplateUsed(res,
'nova/access_and_security/security_groups/create.html')
def test_edit_rules_get(self):
@ -131,7 +109,8 @@ class SecurityGroupsViewTests(test.BaseViewTests):
res = self.client.get(SG_EDIT_RULE_URL)
self.assertTemplateUsed(res, 'nova/security_groups/edit_rules.html')
self.assertTemplateUsed(res,
'nova/access_and_security/security_groups/edit_rules.html')
self.assertItemsEqual(res.context['security_group'].name,
self.security_groups[0].name)

View File

@ -21,7 +21,8 @@
from django.conf.urls.defaults import patterns, url
urlpatterns = patterns('horizon.dashboards.nova.security_groups.views',
urlpatterns = patterns(
'horizon.dashboards.nova.access_and_security.security_groups.views',
url(r'^$', 'index', name='index'),
url(r'^create/$', 'create', name='create'),
url(r'^(?P<security_group_id>[^/]+)/edit_rules/$', 'edit_rules',

View File

@ -30,8 +30,8 @@ from django.utils.translation import ugettext as _
from novaclient import exceptions as novaclient_exceptions
from horizon import api
from horizon.dashboards.nova.security_groups.forms import (CreateGroup,
DeleteGroup, AddRule, DeleteRule)
from horizon.dashboards.nova.access_and_security.security_groups.forms import \
(CreateGroup, DeleteGroup, AddRule, DeleteRule)
LOG = logging.getLogger(__name__)
@ -55,10 +55,10 @@ def index(request):
% e.message)
return shortcuts.render(request,
'nova/security_groups/index.html', {
'security_groups': security_groups,
'form': form,
'delete_form': delete_form})
'nova/access_and_security/security_groups/index.html', {
'security_groups': security_groups,
'form': form,
'delete_form': delete_form})
@login_required
@ -82,13 +82,13 @@ def edit_rules(request, security_group_id):
LOG.exception("ClientException in security_groups rules edit")
messages.error(request, _('Error getting security_group: %s')
% e.message)
return shortcuts.redirect('horizon:nova:security_groups:index')
return shortcuts.redirect('horizon:nova:access_and_security:index')
return shortcuts.render(request,
'nova/security_groups/edit_rules.html', {
'security_group': security_group,
'delete_form': delete_form,
'form': add_form})
'nova/access_and_security/security_groups/edit_rules.html', {
'security_group': security_group,
'delete_form': delete_form,
'form': add_form})
@login_required
@ -100,5 +100,5 @@ def create(request):
return handled
return shortcuts.render(request,
'nova/security_groups/create.html', {
'form': form})
'nova/access_and_security/security_groups/create.html', {
'form': form})

View File

@ -0,0 +1,77 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# 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.
from django import http
from django.contrib import messages
from django.core.urlresolvers import reverse
from mox import IsA
from novaclient import exceptions as novaclient_exceptions
from horizon import api
from horizon import test
class AccessAndSecurityTests(test.BaseViewTests):
def setUp(self):
super(AccessAndSecurityTests, self).setUp()
keypair = api.KeyPair(None)
keypair.name = 'keyName'
self.keypairs = (keypair,)
server = api.Server(None, self.request)
server.id = 1
server.name = 'serverName'
self.server = server
self.servers = (server, )
floating_ip = api.FloatingIp(None)
floating_ip.id = 1
floating_ip.fixed_ip = '10.0.0.4'
floating_ip.instance_id = 1
floating_ip.ip = '58.58.58.58'
self.floating_ip = floating_ip
self.floating_ips = [floating_ip, ]
security_group = api.SecurityGroup(None)
security_group.id = '1'
security_group.name = 'default'
self.security_groups = (security_group,)
def test_index(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, 'security_group_list')
api.security_group_list(IsA(http.HttpRequest)).\
AndReturn(self.security_groups)
self.mox.StubOutWithMock(api, 'keypair_list')
api.keypair_list(IsA(http.HttpRequest)).AndReturn(self.keypairs)
self.mox.ReplayAll()
res = self.client.get(
reverse('horizon:nova:access_and_security:index'))
self.assertTemplateUsed(res, 'nova/access_and_security/index.html')
self.assertItemsEqual(res.context['keypairs'], self.keypairs)
self.assertItemsEqual(res.context['security_groups'],
self.security_groups)
self.assertItemsEqual(res.context['floating_ips'], self.floating_ips)

View File

@ -0,0 +1,38 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# 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.
from django.conf.urls.defaults import *
import horizon
from horizon.dashboards.nova.access_and_security.keypairs import urls \
as keypair_urls
from horizon.dashboards.nova.access_and_security.floating_ips import urls \
as fip_urls
from horizon.dashboards.nova.access_and_security.security_groups import urls \
as sec_group_urls
urlpatterns = patterns('horizon.dashboards.nova.access_and_security',
url(r'^$', 'views.index', name='index'),
url(r'keypairs/', include(keypair_urls, namespace='keypairs')),
url(r'floating_ips/', include(fip_urls, namespace='floating_ips')),
url(r'security_groups/',
include(sec_group_urls, namespace='security_groups')),
)

View File

@ -0,0 +1,103 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# Copyright 2011 Nebula, Inc.
# Copyright 2011 OpenStack LLC
#
# 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.
"""
Views for Instances and Volumes.
"""
import datetime
import logging
from django import http
from django import shortcuts
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.utils.translation import ugettext as _
from novaclient import exceptions as novaclient_exceptions
import openstackx.api.exceptions as api_exceptions
from horizon import api
from horizon import forms
from horizon import test
from horizon.dashboards.nova.access_and_security.keypairs.forms import \
(DeleteKeypair)
from horizon.dashboards.nova.access_and_security.security_groups.forms import \
(CreateGroup,
DeleteGroup)
from horizon.dashboards.nova.access_and_security.floating_ips.forms import \
(ReleaseFloatingIp,
FloatingIpDisassociate,
FloatingIpAllocate)
LOG = logging.getLogger(__name__)
@login_required
def index(request):
tenant_id = request.user.tenant_id
for f in (CreateGroup, DeleteGroup, DeleteKeypair, ReleaseFloatingIp,
FloatingIpDisassociate, FloatingIpAllocate):
_unused, handled = f.maybe_handle(request)
if handled:
return handled
try:
security_groups = api.security_group_list(request)
except novaclient_exceptions.ClientException, e:
security_groups = []
LOG.exception("ClientException in security_groups index")
messages.error(request, _('Error fetching security_groups: %s')
% e.message)
try:
floating_ips = api.tenant_floating_ip_list(request)
except novaclient_exceptions.ClientException, e:
floating_ips = []
LOG.exception("ClientException in floating ip index")
messages.error(request,
_('Error fetching floating ips: %s') % e.message)
allocate_form = FloatingIpAllocate(initial={
'tenant_id': request.user.tenant_id})
try:
keypairs = api.keypair_list(request)
except novaclient_exceptions.ClientException, e:
keypairs = []
LOG.exception("ClientException in keypair index")
messages.error(request, _('Error fetching keypairs: %s') % e.message)
context = {'keypairs': keypairs,
'floating_ips': floating_ips,
'security_groups': security_groups,
'keypair_delete_form': DeleteKeypair(),
'allocate_form': FloatingIpAllocate(),
'disassociate_form': FloatingIpDisassociate(),
'release_form': ReleaseFloatingIp(),
'sec_group_create_form': CreateGroup(
initial={'tenant_id': tenant_id}),
'sec_group_delete_form': DeleteGroup.maybe_handle(request,
initial={'tenant_id': tenant_id})}
if request.is_ajax():
template = 'nova/access_and_security/index_ajax.html'
context['hide'] = True
else:
template = 'nova/access_and_security/index.html'
return shortcuts.render(request, template, context)

View File

@ -23,8 +23,8 @@ class Nova(horizon.Dashboard):
name = "Dashboard"
slug = "nova"
panels = {_("Manage Compute"): ('overview', 'instances_and_volumes',
'images', 'snapshots', 'keypairs',
'floating_ips', 'security_groups',),
'images', 'snapshots',
'access_and_security',),
_("Network"): ('networks',),
_("Object Store"): ('containers',)}
default_panel = 'overview'

View File

@ -1,30 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# 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 horizon
from horizon.dashboards.nova import dashboard
class FloatingIPs(horizon.Panel):
name = "Floating IPs"
slug = 'floating_ips'
dashboard.Nova.register(FloatingIPs)

View File

@ -1,30 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# 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 horizon
from horizon.dashboards.nova import dashboard
class SecurityGroups(horizon.Panel):
name = "Security Groups"
slug = 'security_groups'
dashboard.Nova.register(SecurityGroups)

View File

@ -2,7 +2,7 @@
{% load i18n %}
{% block form_id %}associate_floating_ip_form{% endblock %}
{% block form_action %}{% url horizon:nova:floating_ips:associate floating_ip_id %}{% endblock %}
{% block form_action %}{% url horizon:nova:access_and_security:floating_ips:associate floating_ip_id %}{% endblock %}
{% block modal-header %}Associate Floating IP{% endblock %}
@ -20,5 +20,5 @@
{% block modal-footer %}
<input class="btn primary pull-right" type="submit" value="{%trans "Associate IP"%}" />
<a href="{% url horizon:nova:floating_ips:index %}" class="btn secondary cancel close">Cancel</a>
<a href="{% url horizon:nova:access_and_security:index %}" class="btn secondary cancel close">Cancel</a>
{% endblock %}

View File

@ -3,7 +3,7 @@
<div class="table_title">
<h3>Floating IPs</h3>
<div class="table_actions">
{% include "nova/floating_ips/_allocate.html" with form=allocate_form %}
{% include "nova/access_and_security/floating_ips/_allocate.html" with form=allocate_form %}
<div class="floating_ips table_search">
<form action="#">
<input class="span3" type="text">
@ -40,15 +40,15 @@
</td>
<td>
{% if ip.fixed_ip %}
{% include "nova/floating_ips/_disassociate.html" with form=disassociate_form %}
{% include "nova/access_and_security/floating_ips/_disassociate.html" with form=disassociate_form %}
{% else %}
<a class="btn primary small ajax-modal" href="{% url horizon:nova:floating_ips:associate ip.id %}">{% trans "Associate to instance" %}</a>
<a class="btn primary small ajax-modal" href="{% url horizon:nova:access_and_security:floating_ips:associate ip.id %}">{% trans "Associate to instance" %}</a>
{% endif %}
</td>
<td id="name_{{ip.name}}" class="actions">
<a class="more-actions" href="#">View</a>
<a class="more-actions" href="#">More</a>
<ul>
<li class="form">{% include "nova/floating_ips/_release.html" with form=release_form %}</li>
<li class="form">{% include "nova/access_and_security/floating_ips/_release.html" with form=release_form %}</li>
</ul>
</td>
</tr>

View File

@ -7,5 +7,5 @@
{% endblock page_header %}
{% block dash_main %}
{% include 'nova/floating_ips/_associate.html' %}
{% include 'nova/access_and_security/floating_ips/_associate.html' %}
{% endblock %}

View File

@ -2,19 +2,19 @@
{% load i18n %}
{% block page_header %}
{% url horizon:nova:floating_ips:index as refresh_link %}
{% url horizon:nova:access_and_security:floating_ips:index as refresh_link %}
{# to make searchable false, just remove it from the include statement #}
{% include "horizon/common/_page_header.html" with title=_("Floating IPs") refresh_link=refresh_link searchable="true" %}
{% endblock page_header %}
{% block dash_main %}
{% if floating_ips %}
{% include 'nova/floating_ips/_list.html' %}
{% include 'nova/access_and_security/floating_ips/_list.html' %}
{% else %}
<div class="alert-message block-message info">
<p><strong>{% trans "Info: "%}</strong>{% trans "There are currently no floating ips assigned to your tenant."%}</p>
<div class="alert-actions">
{% include "nova/floating_ips/_allocate.html" with form=allocate_form %}
{% include "nova/access_and_security/floating_ips/_allocate.html" with form=allocate_form %}
</div>
</div>
{% endif %}

View File

@ -0,0 +1,48 @@
{% extends 'nova/base.html' %}
{% load i18n %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Access &amp; Security") %}
{% endblock page_header %}
{% block dash_main %}
<div id="floating_ips">
{% if floating_ips %}
{% include 'nova/access_and_security/floating_ips/_list.html' %}
{% else %}
<div class="alert-message block-message info">
<p><strong>{% trans "Info: "%}</strong>{% trans "There are currently no floating ips assigned to your tenant."%}</p>
<div class="alert-actions">
{% include "nova/access_and_security/floating_ips/_allocate.html" with form=allocate_form %}
</div>
</div>
{% endif %}
</div>
<div id="security_groups">
{% url horizon:nova:access_and_security:security_groups:create as create_sec_url %}
{% if security_groups %}
{% include 'nova/access_and_security/security_groups/_list.html' %}
{% else %}
<div class="message_box info">
<h2>{% trans "Info"%}</h2>
<p>{% blocktrans %}There are currently no security groups. <a href='{{ create_sec_url }}'>Create A Security Group</a>{% endblocktrans %}</p>
</div>
{% endif %}
</div>
<div id="keypairs">
{% if keypairs %}
{% include 'nova/access_and_security/keypairs/_list.html' %}
{% else %}
<div class="alert-message block-message info">
<p><strong>{% trans "Info: "%}</strong>{% trans "There are currently no keypairs."%}</p>
<div class="alert-actions">
<a id="keypairs_create_link" class="btn primary small" href="{% url horizon:nova:access_and_security:keypairs:create %}">{% trans "Create New Keypair"%}</a>
<a id="keypairs_import_link" class="btn small" href="{% url horizon:nova:access_and_security:keypairs:import %}">{% trans "Import Keypair"%}</a>
</div>
</div>
{% endif %}
</div>
{% endblock %}

View File

@ -2,7 +2,7 @@
{% load i18n %}
{% block form_id %}create_keypair_form{% endblock %}
{% block form_action %}{% url horizon:nova:keypairs:create %}{% endblock %}
{% block form_action %}{% url horizon:nova:access_and_security:keypairs:create %}{% endblock %}
{% block modal-header %}{% trans "Create Keypair" %}{% endblock %}
@ -21,5 +21,5 @@
{% block modal-footer %}
<input class="btn primary pull-right" type="submit" value="{% trans "Create Keypair" %}" />
<a href="{% url horizon:nova:keypairs:index %}" class="btn secondary cancel close">Cancel</a>
<a href="{% url horizon:nova:access_and_security:index %}" class="btn secondary cancel close">Cancel</a>
{% endblock %}

View File

@ -2,7 +2,7 @@
{% load i18n %}
{% block form_id %}import_keypair_form{% endblock %}
{% block form_action %}{% url horizon:nova:keypairs:import %}{% endblock %}
{% block form_action %}{% url horizon:nova:access_and_security:keypairs:import %}{% endblock %}
{% block modal-header %}{% trans "Import Keypair" %}{% endblock %}
@ -21,5 +21,5 @@
{% block modal-footer %}
<input class="btn primary pull-right" type="submit" value="{% trans "Import Keypair" %}" />
<a href="{% url horizon:nova:keypairs:index %}" class="btn secondary cancel close">Cancel</a>
<a href="{% url horizon:nova:access_and_security:index %}" class="btn secondary cancel close">Cancel</a>
{% endblock %}

View File

@ -4,8 +4,8 @@
<h3>{% trans "Keypairs" %}</h3>
<div class="table_actions">
<a id="keypairs_import_link" class="btn primary small ajax-modal" data-controls-modal="import_keypair_modal" data-backdrop="static" href="{% url horizon:nova:keypairs:import %}">{% trans "Import Keypair"%}</a>
<a id="keypairs_create_link" class="btn small ajax-modal" data-controls-modal="create_keypair_modal" data-backdrop="static" href="{% url horizon:nova:keypairs:create %}">{% trans "Create New Keypair"%}</a>
<a id="keypairs_import_link" class="btn primary small ajax-modal" data-controls-modal="import_keypair_modal" data-backdrop="static" href="{% url horizon:nova:access_and_security:keypairs:import %}">{% trans "Import Keypair"%}</a>
<a id="keypairs_create_link" class="btn small ajax-modal" data-controls-modal="create_keypair_modal" data-backdrop="static" href="{% url horizon:nova:access_and_security:keypairs:create %}">{% trans "Create New Keypair"%}</a>
<div class="keypairs table_search">
<form action="#">
@ -31,7 +31,7 @@
<td>{{ keypair.fingerprint }}</td>
<td id="actions" class="single">
<ul>
<li class="form">{% include "nova/keypairs/_delete.html" with form=delete_form %}</li>
<li class="form">{% include "nova/access_and_security/keypairs/_delete.html" with form=keypair_delete_form %}</li>
</ul>
</td>
</tr>

View File

@ -6,6 +6,6 @@
{% endblock page_header %}
{% block dash_main %}
{% include 'nova/keypairs/_create.html' %}
{% include 'nova/access_and_security/keypairs/_create.html' %}
{% endblock %}

View File

@ -6,6 +6,6 @@
{% endblock page_header %}
{% block dash_main %}
{% include 'nova/keypairs/_import.html' %}
{% include 'nova/access_and_security/keypairs/_import.html' %}
{% endblock %}

View File

@ -7,13 +7,13 @@
{% block dash_main %}
{% if keypairs %}
{% include 'nova/keypairs/_list.html' %}
{% include 'nova/access_and_security/keypairs/_list.html' %}
{% else %}
<div class="alert-message block-message info">
<p><strong>{% trans "Info: "%}</strong>{% trans "There are currently no keypairs."%}</p>
<div class="alert-actions">
<a id="keypairs_create_link" class="btn primary small ajax-modal" href="{% url horizon:nova:keypairs:create %}">{% trans "Create New Keypair"%}</a>
<a id="keypairs_import_link" class="btn small ajax-modal" href="{% url horizon:nova:keypairs:import %}">{% trans "Import Keypair"%}</a>
<a id="keypairs_create_link" class="btn primary small ajax-modal" href="{% url horizon:nova:access_and_security:keypairs:create %}">{% trans "Create New Keypair"%}</a>
<a id="keypairs_import_link" class="btn small ajax-modal" href="{% url horizon:nova:access_and_security:keypairs:import %}">{% trans "Import Keypair"%}</a>
</div>
</div>
{% endif %}

View File

@ -2,7 +2,7 @@
{% load i18n %}
{% block form_id %}create_security_group_form{% endblock %}
{% block form_action %}{% url horizon:nova:security_groups:create %}{% endblock %}
{% block form_action %}{% url horizon:nova:access_and_security:security_groups:create %}{% endblock %}
{% block modal-header %}{% trans "Create Security Group" %}{% endblock %}
{% block modal_id %}create_security_group_modal{% endblock %}
@ -21,5 +21,5 @@
{% block modal-footer %}
<input class="btn primary pull-right" type="submit" value="{% trans "Create Security Group" %}" />
<a href="{% url horizon:nova:security_groups:index %}" class="btn secondary cancel close">Cancel</a>
<a href="{% url horizon:nova:access_and_security:index %}" class="btn secondary cancel close">Cancel</a>
{% endblock %}

View File

@ -2,7 +2,7 @@
{% load i18n %}
{% block form_id %}security_group_rule_form{% endblock %}
{% block form_action %}{% url horizon:nova:security_groups:edit_rules security_group.id %}{% endblock %}
{% block form_action %}{% url horizon:nova:access_and_security:security_groups:edit_rules security_group.id %}{% endblock %}
{% block modal_id %}security_group_rule_modal{% endblock %}
{% block modal-header %}Edit Security Group Rules{% endblock %}
@ -32,7 +32,7 @@
<td>{{rule.ip_range.cidr}}</td>
<td id="actions">
<ul>
<li class="form">{% include "nova/security_groups/_delete_rule.html" with form=delete_form %}</li>
<li class="form">{% include "nova/access_and_security/security_groups/_delete_rule.html" with form=delete_form %}</li>
</ul>
</td>
</tr>
@ -49,5 +49,5 @@
{% block modal-footer %}
<input class="btn primary pull-right" type="submit" value="{%trans "Add Rule"%}" />
<a href="{% url horizon:nova:security_groups:index %}" class="btn secondary cancel close">Cancel</a>
<a href="{% url horizon:nova:access_and_security:index %}" class="btn secondary cancel close">Cancel</a>
{% endblock %}

View File

@ -4,7 +4,7 @@
<h3>{% trans "Security Groups" %}</h3>
<div class="table_actions">
<a id="security_groups_create_link" class="btn primary small" href="{{ create_sec_url }}" data-controls-modal="create_security_group_modal" data-backdrop="static">{% trans "Create Security Group" %}</a>
<a id="security_groups_create_link" class="btn primary small" href="{{ create_sec_url }}">{% trans "Create Security Group" %}</a>
<div class="security_group table_search">
<form action="#">
@ -30,16 +30,16 @@
<td class="select">
<input type="checkbox" name="security_group_{{security_group.id}}" value="security_group_{{security_group.id}}" id="security_group_select_{{security_group.id}}" />
</td>
<td><a href="{% url horizon:nova:security_groups:edit_rules security_group.id %}">{{ security_group.name }}</a></td>
<td><a href="{% url horizon:nova:access_and_security:security_groups:edit_rules security_group.id %}">{{ security_group.name }}</a></td>
<td>{{ security_group.description }}</td>
<td>
<a class="btn primary small" href="{% url horizon:nova:security_groups:edit_rules security_group.id %}">{% trans "Edit Rules"%}</a>
<a class="btn primary small" href="{% url horizon:nova:access_and_security:security_groups:edit_rules security_group.id %}">{% trans "Edit Rules"%}</a>
</td>
<td id="name_{{security_group.name}}" class="actions">
{% if security_group.name != 'default' %}
<a class="more-actions" href="#">View</a>
<a class="more-actions" href="#">More</a>
<ul>
<li class="form">{% include "nova/security_groups/_delete.html" with form=delete_form %}</li>
<li class="form">{% include "nova/access_and_security/security_groups/_delete.html" with form=delete_form %}</li>
</ul>
{% endif %}
</td>

View File

@ -6,5 +6,5 @@
{% endblock page_header %}
{% block dash_main %}
{% include 'nova/security_groups/_create.html' with form=form %}
{% include 'nova/access_and_security/security_groups/_create.html' with form=form %}
{% endblock %}

View File

@ -6,5 +6,5 @@
{% endblock page_header %}
{% block dash_main %}
{% include "nova/security_groups/_edit_rules.html" %}
{% include "nova/access_and_security/security_groups/_edit_rules.html" %}
{% endblock %}

View File

@ -7,16 +7,16 @@
{% block dash_main %}
{% if security_groups %}
{% url horizon:nova:security_groups:create as create_sec_url %}
{% include 'nova/security_groups/_list.html' %}
{% url horizon:nova:access_and_security:security_groups:create as create_sec_url %}
{% include 'nova/access_and_security/security_groups/_list.html' %}
{% else %}
<div class="message_box info">
{% url horizon:nova:security_groups:create as dash_sec_url %}
{% url horizon:nova:access_and_security:security_groups:create as dash_sec_url %}
<h2>{% trans "Info"%}</h2>
<p>{% blocktrans %}There are currently no security groups. <a href='{{ dash_sec_url }}' data-controls-modal="create_security_group_modal" data-backdrop="static">Create A Security Group</a>{% endblocktrans %}</p>
</div>
{% endif %}
{% include 'nova/security_groups/_create.html' with form=form hide=True%}
{% include 'nova/access_and_security/security_groups/_create.html' with form=form hide=True%}
{% endblock %}

View File

@ -36,7 +36,7 @@
</td>
<td id="name_{{image.name}}" class="actions">
{% if image.owner == request.user.tenant_id %}
<a class="more-actions" href="#">View</a>
<a class="more-actions" href="#">More</a>
<ul>
<li><a class='btn small' href="{% url horizon:nova:images:update image.id %}">{% trans "Edit" %}</a></li>
<li class="form">{% include "nova/images/_delete.html" with form=delete_form %}</li>

View File

@ -39,7 +39,7 @@
</td>
<td>{{instance.status|lower|capfirst}}</td>
<td id="name_{{instance.name}}" class="actions">
<a class="more-actions" href="#">View</a>
<a class="more-actions" href="#">More</a>
<ul>
<li><a class="btn small" target='_blank' href='{% url horizon:nova:instances_and_volumes:instances:vnc instance.id %}'>{% trans 'VNC Console'%}</a></li>
<li><a class='btn small' target='_blank' href='{% url horizon:nova:instances_and_volumes:instances:console instance.id %}'>{% trans 'Log'%}</a></li>

View File

@ -48,7 +48,7 @@
<td id="name_{{instance.name}}" class="actions">
{% if volume.status == "in-use" or volume.status == "available" %}
<a class="more-actions" href="#">View</a>
<a class="more-actions" href="#">More</a>
<ul>
{% if volume.status == "in-use" %}
{% for attachment in volume.attachments %}