Fixing Nameless Volume Display

When a volume has no name, it now shows the
ID instead. I also added a fixture data that
represents such a volume being in the system.

Fixes bug #1012380

You can also now use truncate=i on DataTable
Columns in order to have the value truncated if
it goes beyond i characters.

On top of this I fixed/made-clearer a bunch of
the tests relating to volumes and instances.
Please do NOT use deepcopy() of fixture data
in tests any more, instead just create a new
fixture for the data you want.

Change-Id: If2f92b5d04b04f08f5cacca03f614fce5ea38702
This commit is contained in:
John Postlethwait 2012-06-13 14:50:38 -07:00
parent 2d0315030e
commit c17b06d08a
8 changed files with 312 additions and 206 deletions

View File

@ -25,6 +25,7 @@ from copy import deepcopy
from horizon import api
from horizon import test
from .tabs import InstanceDetailTabs
from .workflows import LaunchInstance
@ -41,218 +42,251 @@ class InstanceViewTests(test.TestCase):
super(InstanceViewTests, self).tearDown()
self.reset_times()
@test.create_stubs({api: ('server_list',
'flavor_list',
'server_delete',
'volume_list',)})
def test_terminate_instance(self):
server = self.servers.first()
self.mox.StubOutWithMock(api, 'server_list')
self.mox.StubOutWithMock(api, 'flavor_list')
self.mox.StubOutWithMock(api, 'server_delete')
self.mox.StubOutWithMock(api, 'volume_list')
api.volume_list(IsA(http.HttpRequest)).AndReturn(self.volumes.list())
api.server_list(IsA(http.HttpRequest)).AndReturn(self.servers.list())
api.flavor_list(IgnoreArg()).AndReturn(self.flavors.list())
api.server_delete(IsA(http.HttpRequest), server.id)
self.mox.ReplayAll()
formData = {'action': 'instances__terminate__%s' % server.id}
res = self.client.post(INDEX_URL, formData)
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({api: ('server_list',
'flavor_list',
'server_delete',
'volume_list',)})
def test_terminate_instance_exception(self):
server = self.servers.first()
self.mox.StubOutWithMock(api, 'server_list')
self.mox.StubOutWithMock(api, 'flavor_list')
self.mox.StubOutWithMock(api, 'server_delete')
self.mox.StubOutWithMock(api, 'volume_list')
api.volume_list(IsA(http.HttpRequest)).AndReturn(self.volumes.list())
api.server_list(IsA(http.HttpRequest)).AndReturn(self.servers.list())
api.flavor_list(IgnoreArg()).AndReturn(self.flavors.list())
api.server_delete(IsA(http.HttpRequest), server.id) \
.AndRaise(self.exceptions.nova)
self.mox.ReplayAll()
formData = {'action': 'instances__terminate__%s' % server.id}
res = self.client.post(INDEX_URL, formData)
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({api: ('server_pause',
'server_list',
'volume_list',
'flavor_list',)})
def test_pause_instance(self):
server = self.servers.first()
self.mox.StubOutWithMock(api, 'server_pause')
self.mox.StubOutWithMock(api, 'server_list')
self.mox.StubOutWithMock(api, 'volume_list')
self.mox.StubOutWithMock(api, 'flavor_list')
api.flavor_list(IsA(http.HttpRequest)).AndReturn(self.flavors.list())
api.volume_list(IsA(http.HttpRequest)).AndReturn(self.volumes.list())
api.server_list(IsA(http.HttpRequest)).AndReturn(self.servers.list())
api.server_pause(IsA(http.HttpRequest), server.id)
self.mox.ReplayAll()
formData = {'action': 'instances__pause__%s' % server.id}
res = self.client.post(INDEX_URL, formData)
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({api: ('server_pause',
'server_list',
'volume_list',
'flavor_list',)})
def test_pause_instance_exception(self):
server = self.servers.first()
self.mox.StubOutWithMock(api, 'volume_list')
self.mox.StubOutWithMock(api, 'server_pause')
self.mox.StubOutWithMock(api, 'server_list')
self.mox.StubOutWithMock(api, 'flavor_list')
api.flavor_list(IsA(http.HttpRequest)).AndReturn(self.flavors.list())
api.volume_list(IsA(http.HttpRequest)).AndReturn(self.volumes.list())
api.server_list(IsA(http.HttpRequest)).AndReturn(self.servers.list())
api.server_pause(IsA(http.HttpRequest), server.id) \
.AndRaise(self.exceptions.nova)
self.mox.ReplayAll()
formData = {'action': 'instances__pause__%s' % server.id}
res = self.client.post(INDEX_URL, formData)
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({api: ('volume_list',
'server_unpause',
'server_list',
'flavor_list',)})
def test_unpause_instance(self):
server = self.servers.first()
server.status = "PAUSED"
self.mox.StubOutWithMock(api, 'volume_list')
self.mox.StubOutWithMock(api, 'server_unpause')
self.mox.StubOutWithMock(api, 'server_list')
self.mox.StubOutWithMock(api, 'flavor_list')
api.flavor_list(IsA(http.HttpRequest)).AndReturn(self.flavors.list())
api.volume_list(IsA(http.HttpRequest)).AndReturn(self.volumes.list())
api.server_list(IsA(http.HttpRequest)).AndReturn(self.servers.list())
api.server_unpause(IsA(http.HttpRequest), server.id)
self.mox.ReplayAll()
formData = {'action': 'instances__pause__%s' % server.id}
res = self.client.post(INDEX_URL, formData)
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({api: ('volume_list',
'server_unpause',
'server_list',
'flavor_list',)})
def test_unpause_instance_exception(self):
server = self.servers.first()
server.status = "PAUSED"
self.mox.StubOutWithMock(api, 'volume_list')
self.mox.StubOutWithMock(api, 'server_list')
self.mox.StubOutWithMock(api, 'server_unpause')
self.mox.StubOutWithMock(api, 'flavor_list')
api.flavor_list(IsA(http.HttpRequest)).AndReturn(self.flavors.list())
api.volume_list(IsA(http.HttpRequest)).AndReturn(self.volumes.list())
api.server_list(IsA(http.HttpRequest)).AndReturn(self.servers.list())
api.server_unpause(IsA(http.HttpRequest), server.id) \
.AndRaise(self.exceptions.nova)
self.mox.ReplayAll()
formData = {'action': 'instances__pause__%s' % server.id}
res = self.client.post(INDEX_URL, formData)
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({api: ('volume_list',
'server_reboot',
'server_list',
'flavor_list',)})
def test_reboot_instance(self):
server = self.servers.first()
self.mox.StubOutWithMock(api, 'server_reboot')
self.mox.StubOutWithMock(api, 'server_list')
self.mox.StubOutWithMock(api, 'volume_list')
self.mox.StubOutWithMock(api, 'flavor_list')
api.flavor_list(IsA(http.HttpRequest)).AndReturn(self.flavors.list())
api.volume_list(IsA(http.HttpRequest)).AndReturn(self.volumes.list())
api.server_list(IsA(http.HttpRequest)).AndReturn(self.servers.list())
api.server_reboot(IsA(http.HttpRequest), server.id)
self.mox.ReplayAll()
formData = {'action': 'instances__reboot__%s' % server.id}
res = self.client.post(INDEX_URL, formData)
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({api: ('volume_list',
'server_reboot',
'server_list',
'flavor_list',)})
def test_reboot_instance_exception(self):
server = self.servers.first()
self.mox.StubOutWithMock(api, 'server_reboot')
self.mox.StubOutWithMock(api, 'server_list')
self.mox.StubOutWithMock(api, 'volume_list')
self.mox.StubOutWithMock(api, 'flavor_list')
api.flavor_list(IsA(http.HttpRequest)).AndReturn(self.flavors.list())
api.volume_list(IsA(http.HttpRequest)).AndReturn(self.volumes.list())
api.server_list(IsA(http.HttpRequest)).AndReturn(self.servers.list())
api.server_reboot(IsA(http.HttpRequest), server.id) \
.AndRaise(self.exceptions.nova)
self.mox.ReplayAll()
formData = {'action': 'instances__reboot__%s' % server.id}
res = self.client.post(INDEX_URL, formData)
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({api: ('volume_list',
'server_suspend',
'server_list',
'flavor_list',)})
def test_suspend_instance(self):
server = self.servers.first()
self.mox.StubOutWithMock(api, 'server_suspend')
self.mox.StubOutWithMock(api, 'server_list')
self.mox.StubOutWithMock(api, 'volume_list')
self.mox.StubOutWithMock(api, 'flavor_list')
api.flavor_list(IsA(http.HttpRequest)).AndReturn(self.flavors.list())
api.volume_list(IsA(http.HttpRequest)).AndReturn(self.volumes.list())
api.server_list(IsA(http.HttpRequest)).AndReturn(self.servers.list())
api.server_suspend(IsA(http.HttpRequest), unicode(server.id))
self.mox.ReplayAll()
formData = {'action': 'instances__suspend__%s' % server.id}
res = self.client.post(INDEX_URL, formData)
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({api: ('volume_list',
'server_suspend',
'server_list',
'flavor_list',)})
def test_suspend_instance_exception(self):
server = self.servers.first()
self.mox.StubOutWithMock(api, 'server_suspend')
self.mox.StubOutWithMock(api, 'server_list')
self.mox.StubOutWithMock(api, 'volume_list')
self.mox.StubOutWithMock(api, 'flavor_list')
api.flavor_list(IsA(http.HttpRequest)).AndReturn(self.flavors.list())
api.volume_list(IsA(http.HttpRequest)).AndReturn(self.volumes.list())
api.server_list(IsA(http.HttpRequest)).AndReturn(self.servers.list())
api.server_suspend(IsA(http.HttpRequest),
unicode(server.id)).AndRaise(self.exceptions.nova)
self.mox.ReplayAll()
formData = {'action': 'instances__suspend__%s' % server.id}
res = self.client.post(INDEX_URL, formData)
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({api: ('volume_list',
'server_resume',
'server_list',
'flavor_list',)})
def test_resume_instance(self):
server = self.servers.first()
server.status = "SUSPENDED"
self.mox.StubOutWithMock(api, 'server_resume')
self.mox.StubOutWithMock(api, 'server_list')
self.mox.StubOutWithMock(api, 'volume_list')
self.mox.StubOutWithMock(api, 'flavor_list')
api.flavor_list(IsA(http.HttpRequest)).AndReturn(self.flavors.list())
api.volume_list(IsA(http.HttpRequest)).AndReturn(self.volumes.list())
api.server_list(IsA(http.HttpRequest)).AndReturn(self.servers.list())
api.server_resume(IsA(http.HttpRequest), unicode(server.id))
self.mox.ReplayAll()
formData = {'action': 'instances__suspend__%s' % server.id}
res = self.client.post(INDEX_URL, formData)
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({api: ('volume_list',
'server_resume',
'server_list',
'flavor_list',)})
def test_resume_instance_exception(self):
server = self.servers.first()
server.status = "SUSPENDED"
self.mox.StubOutWithMock(api, 'server_resume')
self.mox.StubOutWithMock(api, 'server_list')
self.mox.StubOutWithMock(api, 'volume_list')
self.mox.StubOutWithMock(api, 'flavor_list')
api.flavor_list(IsA(http.HttpRequest)).AndReturn(self.flavors.list())
api.volume_list(IsA(http.HttpRequest)).AndReturn(self.volumes.list())
api.server_list(IsA(http.HttpRequest)).AndReturn(self.servers.list())
api.server_resume(IsA(http.HttpRequest),
unicode(server.id)).AndRaise(self.exceptions.nova)
self.mox.ReplayAll()
formData = {'action': 'instances__suspend__%s' % server.id}
res = self.client.post(INDEX_URL, formData)
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({api: ("server_get", "volume_instance_list",
"flavor_get", "server_security_groups")})
@test.create_stubs({api: ("server_get",
"volume_instance_list",
"flavor_get",
"server_security_groups")})
def test_instance_details_volumes(self):
server = self.servers.first()
volumes = deepcopy(self.volumes.list())
volumes[0].device = "/dev/hdk"
second_vol = deepcopy(volumes[0])
second_vol.id = 2
second_vol.device = "/dev/hdb"
volumes.append(second_vol)
volumes = [self.volumes.list()[1]]
api.server_get(IsA(http.HttpRequest), server.id).AndReturn(server)
api.volume_instance_list(IsA(http.HttpRequest),
@ -267,15 +301,41 @@ class InstanceViewTests(test.TestCase):
url = reverse('horizon:nova:instances_and_volumes:instances:detail',
args=[server.id])
res = self.client.get(url)
self.assertItemsEqual(res.context['instance'].volumes, volumes)
@test.create_stubs({api: ("server_get",
"volume_instance_list",
"flavor_get",
"server_security_groups")})
def test_instance_details_volume_sorting(self):
server = self.servers.first()
volumes = self.volumes.list()[1:3]
api.server_get(IsA(http.HttpRequest), server.id).AndReturn(server)
api.volume_instance_list(IsA(http.HttpRequest),
server.id).AndReturn(volumes)
api.flavor_get(IsA(http.HttpRequest),
server.flavor['id']).AndReturn(self.flavors.first())
api.server_security_groups(IsA(http.HttpRequest),
server.id).AndReturn(self.security_groups.first())
self.mox.ReplayAll()
url = reverse('horizon:nova:instances_and_volumes:instances:detail',
args=[server.id])
res = self.client.get(url)
self.assertItemsEqual(res.context['instance'].volumes, volumes)
# Test device ordering
self.assertEquals(res.context['instance'].volumes[0].device,
"/dev/hdb")
"/dev/hda")
self.assertEquals(res.context['instance'].volumes[1].device,
"/dev/hdk")
@test.create_stubs({api: ("server_get", "volume_instance_list",
"flavor_get", "server_security_groups")})
@test.create_stubs({api: ("server_get",
"volume_instance_list",
"flavor_get",
"server_security_groups",)})
def test_instance_details_metadata(self):
server = self.servers.first()
@ -294,26 +354,25 @@ class InstanceViewTests(test.TestCase):
tg = InstanceDetailTabs(self.request, instance=server)
qs = "?%s=%s" % (tg.param_name, tg.get_tab("overview").get_id())
res = self.client.get(url + qs)
# Key name
self.assertContains(res, "<dd>keyName</dd>", 1)
# Meta data
self.assertContains(res, "<dt>someMetaLabel</dt>", 1)
self.assertContains(res, "<dd>someMetaData</dd>", 1)
# Test escaping of html characters in names
self.assertContains(res, "<dt>some&lt;b&gt;html&lt;/b&gt;label</dt>",
1)
self.assertContains(res, "<dd>&lt;!--</dd>", 1)
self.assertContains(res, "<dt>empty</dt>", 1)
self.assertContains(res, "<dd><em>N/A</em></dd>", 1)
@test.create_stubs({api: ('server_console_output',)})
def test_instance_log(self):
server = self.servers.first()
CONSOLE_OUTPUT = 'output'
self.mox.StubOutWithMock(api, 'server_console_output')
api.server_console_output(IsA(http.HttpRequest),
server.id, tail_length=None) \
.AndReturn(CONSOLE_OUTPUT)
self.mox.ReplayAll()
url = reverse('horizon:nova:instances_and_volumes:instances:console',
@ -321,17 +380,19 @@ class InstanceViewTests(test.TestCase):
tg = InstanceDetailTabs(self.request, instance=server)
qs = "?%s=%s" % (tg.param_name, tg.get_tab("log").get_id())
res = self.client.get(url + qs)
self.assertNoMessages()
self.assertIsInstance(res, http.HttpResponse)
self.assertContains(res, CONSOLE_OUTPUT)
@test.create_stubs({api: ('server_console_output',)})
def test_instance_log_exception(self):
server = self.servers.first()
self.mox.StubOutWithMock(api, 'server_console_output')
api.server_console_output(IsA(http.HttpRequest),
server.id, tail_length=None) \
.AndRaise(self.exceptions.nova)
self.mox.ReplayAll()
url = reverse('horizon:nova:instances_and_volumes:instances:console',
@ -339,6 +400,7 @@ class InstanceViewTests(test.TestCase):
tg = InstanceDetailTabs(self.request, instance=server)
qs = "?%s=%s" % (tg.param_name, tg.get_tab("log").get_id())
res = self.client.get(url + qs)
self.assertContains(res, "Unable to get log for")
def test_instance_vnc(self):
@ -360,33 +422,36 @@ class InstanceViewTests(test.TestCase):
redirect = CONSOLE_OUTPUT + '&title=%s(1)' % server.name
self.assertRedirectsNoFollow(res, redirect)
@test.create_stubs({api: ('server_vnc_console',)})
def test_instance_vnc_exception(self):
server = self.servers.first()
self.mox.StubOutWithMock(api, 'server_vnc_console')
api.server_vnc_console(IsA(http.HttpRequest), server.id) \
.AndRaise(self.exceptions.nova)
self.mox.ReplayAll()
url = reverse('horizon:nova:instances_and_volumes:instances:vnc',
args=[server.id])
res = self.client.get(url)
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({api: ('server_get',
'snapshot_create',
'snapshot_list_detailed',
'image_list_detailed',
'volume_snapshot_list',
'server_list',
'flavor_list',
'server_delete',
'volume_list',)})
def test_create_instance_snapshot(self):
server = self.servers.first()
snapshot_server = deepcopy(server)
setattr(snapshot_server, 'OS-EXT-STS:task_state',
"IMAGE_SNAPSHOT")
self.mox.StubOutWithMock(api, 'server_get')
self.mox.StubOutWithMock(api, 'snapshot_create')
self.mox.StubOutWithMock(api, 'snapshot_list_detailed')
self.mox.StubOutWithMock(api, 'image_list_detailed')
self.mox.StubOutWithMock(api, 'volume_snapshot_list')
self.mox.StubOutWithMock(api, 'server_list')
self.mox.StubOutWithMock(api, 'flavor_list')
self.mox.StubOutWithMock(api, 'server_delete')
self.mox.StubOutWithMock(api, 'volume_list')
api.server_get(IsA(http.HttpRequest), server.id).AndReturn(server)
api.snapshot_create(IsA(http.HttpRequest),
server.id,
@ -397,10 +462,10 @@ class InstanceViewTests(test.TestCase):
api.image_list_detailed(IsA(http.HttpRequest),
marker=None).AndReturn([[], False])
api.volume_snapshot_list(IsA(http.HttpRequest)).AndReturn([])
api.volume_list(IsA(http.HttpRequest)).AndReturn(self.volumes.list())
api.server_list(IsA(http.HttpRequest)).AndReturn([snapshot_server])
api.flavor_list(IgnoreArg()).AndReturn(self.flavors.list())
self.mox.ReplayAll()
formData = {'instance_id': server.id,
@ -412,42 +477,48 @@ class InstanceViewTests(test.TestCase):
redir_url = reverse('horizon:nova:images_and_snapshots:index')
res = self.client.post(url, formData)
self.assertRedirects(res, redir_url)
res = self.client.get(INDEX_URL)
self.assertContains(res, '<td class="status_unknown sortable">'
'Snapshotting</td>', 1)
@test.create_stubs({api: ('server_get',)})
def test_instance_update_get(self):
server = self.servers.first()
self.mox.StubOutWithMock(api, 'server_get')
api.server_get(IsA(http.HttpRequest), server.id).AndReturn(server)
self.mox.ReplayAll()
url = reverse('horizon:nova:instances_and_volumes:instances:update',
args=[server.id])
res = self.client.get(url)
self.assertTemplateUsed(res,
'nova/instances_and_volumes/instances/update.html')
@test.create_stubs({api: ('server_get',)})
def test_instance_update_get_server_get_exception(self):
server = self.servers.first()
self.mox.StubOutWithMock(api, 'server_get')
api.server_get(IsA(http.HttpRequest), server.id) \
.AndRaise(self.exceptions.nova)
self.mox.ReplayAll()
url = reverse('horizon:nova:instances_and_volumes:instances:update',
args=[server.id])
res = self.client.get(url)
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({api: ('server_get', 'server_update')})
def test_instance_update_post(self):
server = self.servers.first()
self.mox.StubOutWithMock(api, 'server_get')
self.mox.StubOutWithMock(api, 'server_update')
api.server_get(IsA(http.HttpRequest), server.id).AndReturn(server)
api.server_update(IsA(http.HttpRequest), server.id, server.name)
self.mox.ReplayAll()
formData = {'method': 'UpdateInstance',
@ -457,16 +528,17 @@ class InstanceViewTests(test.TestCase):
url = reverse('horizon:nova:instances_and_volumes:instances:update',
args=[server.id])
res = self.client.post(url, formData)
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({api: ('server_get', 'server_update')})
def test_instance_update_post_api_exception(self):
server = self.servers.first()
self.mox.StubOutWithMock(api, 'server_get')
self.mox.StubOutWithMock(api, 'server_update')
api.server_get(IsA(http.HttpRequest), server.id).AndReturn(server)
api.server_update(IsA(http.HttpRequest), server.id, server.name) \
.AndRaise(self.exceptions.nova)
self.mox.ReplayAll()
formData = {'method': 'UpdateInstance',
@ -476,20 +548,19 @@ class InstanceViewTests(test.TestCase):
url = reverse('horizon:nova:instances_and_volumes:instances:update',
args=[server.id])
res = self.client.post(url, formData)
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({api.nova: ('tenant_quota_usages',
'flavor_list',
'keypair_list',
'security_group_list',
'volume_snapshot_list',
'volume_list',),
api.glance: ('image_list_detailed',)})
def test_launch_get(self):
quota_usages = self.quota_usages.first()
self.mox.StubOutWithMock(api.glance, 'image_list_detailed')
self.mox.StubOutWithMock(api.nova, 'tenant_quota_usages')
# Two flavor_list calls, however, flavor_list is now memoized.
self.mox.StubOutWithMock(api.nova, 'flavor_list')
self.mox.StubOutWithMock(api.nova, 'keypair_list')
self.mox.StubOutWithMock(api.nova, 'security_group_list')
self.mox.StubOutWithMock(api.nova, 'volume_snapshot_list')
self.mox.StubOutWithMock(api.nova, 'volume_list')
api.nova.volume_list(IsA(http.HttpRequest)) \
.AndReturn(self.volumes.list())
api.nova.volume_snapshot_list(IsA(http.HttpRequest)) \
@ -510,20 +581,29 @@ class InstanceViewTests(test.TestCase):
.AndReturn(self.keypairs.list())
api.nova.security_group_list(IsA(http.HttpRequest)) \
.AndReturn(self.security_groups.list())
self.mox.ReplayAll()
url = reverse('horizon:nova:instances_and_volumes:instances:launch')
res = self.client.get(url)
self.assertTemplateUsed(res,
'nova/instances_and_volumes/instances/launch.html')
workflow = res.context['workflow']
self.assertEqual(workflow.name, LaunchInstance.name)
self.assertQuerysetEqual(workflow.steps,
self.assertEqual(res.context['workflow'].name, LaunchInstance.name)
self.assertQuerysetEqual(res.context['workflow'].steps,
['<SetInstanceDetails: setinstancedetailsaction>',
'<SetAccessControls: setaccesscontrolsaction>',
'<VolumeOptions: volumeoptionsaction>',
'<PostCreationStep: customizeaction>'])
@test.create_stubs({api.glance: ('image_list_detailed',),
api.nova: ('flavor_list',
'keypair_list',
'security_group_list',
'volume_list',
'volume_snapshot_list',
'tenant_quota_usages',
'server_create',)})
def test_launch_post(self):
flavor = self.flavors.first()
image = self.images.first()
@ -536,15 +616,6 @@ class InstanceViewTests(test.TestCase):
volume_choice = "%s:vol" % volume.id
block_device_mapping = {device_name: u"%s::0" % volume_choice}
self.mox.StubOutWithMock(api.glance, 'image_list_detailed')
self.mox.StubOutWithMock(api.nova, 'flavor_list')
self.mox.StubOutWithMock(api.nova, 'keypair_list')
self.mox.StubOutWithMock(api.nova, 'security_group_list')
self.mox.StubOutWithMock(api.nova, 'volume_list')
self.mox.StubOutWithMock(api.nova, 'volume_snapshot_list')
self.mox.StubOutWithMock(api.nova, 'tenant_quota_usages')
self.mox.StubOutWithMock(api.nova, 'server_create')
api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list())
api.nova.keypair_list(IsA(http.HttpRequest)) \
@ -569,6 +640,7 @@ class InstanceViewTests(test.TestCase):
[sec_group.name],
block_device_mapping,
instance_count=IsA(int))
self.mox.ReplayAll()
form_data = {'flavor': flavor.id,
@ -586,19 +658,19 @@ class InstanceViewTests(test.TestCase):
'count': 1}
url = reverse('horizon:nova:instances_and_volumes:instances:launch')
res = self.client.post(url, form_data)
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res,
reverse('horizon:nova:instances_and_volumes:index'))
@test.create_stubs({api.glance: ('image_list_detailed',),
api.nova: ('tenant_quota_usages',
'flavor_list',
'keypair_list',
'volume_list',
'security_group_list',
'volume_snapshot_list',)})
def test_launch_flavorlist_error(self):
self.mox.StubOutWithMock(api.glance, 'image_list_detailed')
self.mox.StubOutWithMock(api.nova, 'tenant_quota_usages')
self.mox.StubOutWithMock(api.nova, 'flavor_list')
self.mox.StubOutWithMock(api.nova, 'keypair_list')
self.mox.StubOutWithMock(api.nova, 'security_group_list')
self.mox.StubOutWithMock(api.nova, 'volume_snapshot_list')
self.mox.StubOutWithMock(api.nova, 'volume_list')
api.nova.volume_list(IsA(http.HttpRequest)) \
.AndReturn(self.volumes.list())
api.nova.volume_snapshot_list(IsA(http.HttpRequest)) \
@ -619,13 +691,22 @@ class InstanceViewTests(test.TestCase):
.AndReturn(self.keypairs.list())
api.nova.security_group_list(IsA(http.HttpRequest)) \
.AndReturn(self.security_groups.list())
self.mox.ReplayAll()
url = reverse('horizon:nova:instances_and_volumes:instances:launch')
res = self.client.get(url)
self.assertTemplateUsed(res,
'nova/instances_and_volumes/instances/launch.html')
@test.create_stubs({api.glance: ('image_list_detailed',),
api.nova: ('flavor_list',
'keypair_list',
'security_group_list',
'volume_list',
'server_create',
'volume_snapshot_list',)})
def test_launch_form_keystone_exception(self):
flavor = self.flavors.first()
image = self.images.first()
@ -634,14 +715,6 @@ class InstanceViewTests(test.TestCase):
sec_group = self.security_groups.first()
customization_script = 'userData'
self.mox.StubOutWithMock(api.glance, 'image_list_detailed')
self.mox.StubOutWithMock(api.nova, 'flavor_list')
self.mox.StubOutWithMock(api.nova, 'keypair_list')
self.mox.StubOutWithMock(api.nova, 'security_group_list')
self.mox.StubOutWithMock(api.nova, 'server_create')
self.mox.StubOutWithMock(api.nova, 'volume_list')
self.mox.StubOutWithMock(api.nova, 'volume_snapshot_list')
api.nova.volume_snapshot_list(IsA(http.HttpRequest)) \
.AndReturn(self.volumes.list())
api.nova.flavor_list(IgnoreArg()).AndReturn(self.flavors.list())
@ -665,6 +738,7 @@ class InstanceViewTests(test.TestCase):
None,
instance_count=IsA(int)) \
.AndRaise(self.exceptions.keystone)
self.mox.ReplayAll()
form_data = {'flavor': flavor.id,
@ -680,8 +754,16 @@ class InstanceViewTests(test.TestCase):
'count': 1}
url = reverse('horizon:nova:instances_and_volumes:instances:launch')
res = self.client.post(url, form_data)
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({api.glance: ('image_list_detailed',),
api.nova: ('flavor_list',
'keypair_list',
'security_group_list',
'volume_list',
'tenant_quota_usages',
'volume_snapshot_list',)})
def test_launch_form_instance_count_error(self):
flavor = self.flavors.first()
image = self.images.first()
@ -693,14 +775,6 @@ class InstanceViewTests(test.TestCase):
device_name = u'vda'
volume_choice = "%s:vol" % volume.id
self.mox.StubOutWithMock(api.glance, 'image_list_detailed')
self.mox.StubOutWithMock(api.nova, 'flavor_list')
self.mox.StubOutWithMock(api.nova, 'keypair_list')
self.mox.StubOutWithMock(api.nova, 'security_group_list')
self.mox.StubOutWithMock(api.nova, 'volume_list')
self.mox.StubOutWithMock(api.nova, 'volume_snapshot_list')
self.mox.StubOutWithMock(api.nova, 'tenant_quota_usages')
api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list())
api.nova.keypair_list(IsA(http.HttpRequest)) \
@ -721,6 +795,7 @@ class InstanceViewTests(test.TestCase):
.AndReturn(self.flavors.list())
api.nova.tenant_quota_usages(IsA(http.HttpRequest)) \
.AndReturn(self.quota_usages.first())
self.mox.ReplayAll()
form_data = {'flavor': flavor.id,
@ -738,4 +813,5 @@ class InstanceViewTests(test.TestCase):
'count': 0}
url = reverse('horizon:nova:instances_and_volumes:instances:launch')
res = self.client.post(url, form_data)
self.assertContains(res, "greater than or equal to 1")

View File

@ -18,7 +18,6 @@
# License for the specific language governing permissions and limitations
# under the License.
from copy import deepcopy
from django import http
from django.core.urlresolvers import reverse
from mox import IsA
@ -28,10 +27,8 @@ from horizon import test
class InstancesAndVolumesViewTest(test.TestCase):
@test.create_stubs({api: ('flavor_list', 'server_list', 'volume_list',)})
def test_index(self):
self.mox.StubOutWithMock(api, 'flavor_list')
self.mox.StubOutWithMock(api, 'server_list')
self.mox.StubOutWithMock(api, 'volume_list')
api.flavor_list(IsA(http.HttpRequest)).AndReturn(self.flavors.list())
api.server_list(IsA(http.HttpRequest)).AndReturn(self.servers.list())
api.volume_list(IsA(http.HttpRequest)).AndReturn(self.volumes.list())
@ -49,23 +46,12 @@ class InstancesAndVolumesViewTest(test.TestCase):
self.assertItemsEqual(instances, self.servers.list())
self.assertItemsEqual(volumes, self.volumes.list())
@test.create_stubs({api: ('flavor_list', 'server_list', 'volume_list',)})
def test_attached_volume(self):
volumes = deepcopy(self.volumes.list())
attached_volume = deepcopy(self.volumes.list()[0])
attached_volume.id = "2"
attached_volume.display_name = "Volume2 name"
attached_volume.size = "80"
attached_volume.status = "in-use"
attached_volume.attachments = [{"server_id": "1",
"device": "/dev/hdn"}]
volumes.append(attached_volume)
self.mox.StubOutWithMock(api, 'server_list')
self.mox.StubOutWithMock(api, 'volume_list')
self.mox.StubOutWithMock(api, 'flavor_list')
api.flavor_list(IsA(http.HttpRequest)).AndReturn(self.flavors.list())
api.server_list(IsA(http.HttpRequest)).AndReturn(self.servers.list())
api.volume_list(IsA(http.HttpRequest)).AndReturn(volumes)
api.volume_list(IsA(http.HttpRequest)) \
.AndReturn(self.volumes.list()[1:3])
self.mox.ReplayAll()
@ -78,21 +64,20 @@ class InstancesAndVolumesViewTest(test.TestCase):
resp_volumes = res.context['volumes_table'].data
self.assertItemsEqual(instances, self.servers.list())
self.assertItemsEqual(resp_volumes, volumes)
self.assertItemsEqual(resp_volumes, self.volumes.list()[1:3])
self.assertContains(res, ">Volume name<", 1, 200)
self.assertContains(res, ">40GB<", 1, 200)
self.assertContains(res, ">Available<", 1, 200)
self.assertContains(res, ">Volume2 name<", 1, 200)
self.assertContains(res, ">80GB<", 1, 200)
self.assertContains(res, ">In-Use<", 1, 200)
self.assertContains(res, ">server_1<", 2, 200)
self.assertContains(res, "on /dev/hdn", 1, 200)
self.assertContains(res, ">My Volume<", 1, 200)
self.assertContains(res, ">30GB<", 1, 200)
self.assertContains(res, ">3b189ac8-9166-ac7f-90c9-16c8bf9e01ac<",
1,
200)
self.assertContains(res, ">10GB<", 1, 200)
self.assertContains(res, ">In-Use<", 2, 200)
self.assertContains(res, "on /dev/hda", 1, 200)
self.assertContains(res, "on /dev/hdk", 1, 200)
@test.create_stubs({api: ('server_list', 'volume_list',)})
def test_index_server_list_exception(self):
self.mox.StubOutWithMock(api, 'server_list')
self.mox.StubOutWithMock(api, 'volume_list')
api.server_list(IsA(http.HttpRequest)).AndRaise(self.exceptions.nova)
api.volume_list(IsA(http.HttpRequest)).AndReturn(self.volumes.list())
api.server_list(IsA(http.HttpRequest)).AndReturn(self.servers.list())

View File

@ -22,7 +22,6 @@
"""
Views for Instances and Volumes.
"""
import re
import logging
from django.utils.translation import ugettext_lazy as _
@ -73,16 +72,12 @@ class IndexView(tables.MultiTableView):
instances = SortedDict([(inst.id, inst) for inst in
self._get_instances()])
for volume in volumes:
# Truncate the description for proper display.
if len(getattr(volume, 'display_description', '')) > 33:
truncated_string = volume.display_description[:30].strip()
# Remove non-word, and underscore characters, from the end
# of the string before we add the ellepsis.
truncated_string = re.sub(ur'[^\w\s]+$',
'',
truncated_string)
# It is possible to create a volume with no name through the
# EC2 API, use the ID in those cases.
if not volume.display_name:
volume.display_name = volume.id
volume.display_description = truncated_string + u'...'
description = getattr(volume, 'display_description', '')
for att in volume.attachments:
server_id = att.get('server_id', None)

View File

@ -135,7 +135,8 @@ class VolumesTableBase(tables.DataTable):
name = tables.Column("display_name", verbose_name=_("Name"),
link="%s:volumes:detail" % URL_PREFIX)
description = tables.Column("display_description",
verbose_name=_("Description"))
verbose_name=_("Description"),
truncate=40)
size = tables.Column(get_size, verbose_name=_("Size"))
status = tables.Column("status",
filters=(title,),

View File

@ -18,7 +18,6 @@
# License for the specific language governing permissions and limitations
# under the License.
from copy import deepcopy
from django import http
from django.core.urlresolvers import reverse
from mox import IsA
@ -28,14 +27,15 @@ from horizon import test
class VolumeViewTests(test.TestCase):
@test.create_stubs({api: ('volume_get',), api.nova: ('server_list',)})
def test_edit_attachments(self):
volume = self.volumes.first()
servers = self.servers.list()
self.mox.StubOutWithMock(api, 'volume_get')
self.mox.StubOutWithMock(api.nova, 'server_list')
api.volume_get(IsA(http.HttpRequest), volume.id) \
.AndReturn(volume)
api.nova.server_list(IsA(http.HttpRequest)).AndReturn(servers)
self.mox.ReplayAll()
url = reverse('horizon:nova:instances_and_volumes:volumes:attach',
@ -47,54 +47,42 @@ class VolumeViewTests(test.TestCase):
2)
self.assertEqual(res.status_code, 200)
@test.create_stubs({api: ('volume_get',),
api.nova: ('server_get', 'server_list',)})
def test_edit_attachments_attached_volume(self):
server = self.servers.first()
servers = deepcopy(self.servers)
active_server = deepcopy(self.servers.first())
active_server.status = 'ACTIVE'
active_server.id = "3"
servers.add(active_server)
volumes = deepcopy(self.volumes)
volume = deepcopy(self.volumes.first())
volume.id = "2"
volume.status = "in-use"
volume.attachments = [{"id": "1", "server_id": server.id,
"device": "/dev/hdn"}]
volumes.add(volume)
volume = self.volumes.list()[0]
self.mox.StubOutWithMock(api, 'volume_get')
self.mox.StubOutWithMock(api.nova, 'server_list')
self.mox.StubOutWithMock(api.nova, 'server_get')
api.nova.server_get(IsA(http.HttpRequest), server.id).AndReturn(server)
api.volume_get(IsA(http.HttpRequest), volume.id) \
.AndReturn(volume)
api.nova.server_list(IsA(http.HttpRequest)).AndReturn(servers.list())
api.nova.server_list(IsA(http.HttpRequest)) \
.AndReturn(self.servers.list())
self.mox.ReplayAll()
url = reverse('horizon:nova:instances_and_volumes:volumes:attach',
args=[volume.id])
res = self.client.get(url)
# Asserting length of 1 instance (plus 'Select ..' item).
# The other instance is already attached to this volume
self.assertEqual(len(res.context['form'].fields['instance']._choices),
2)
self.assertEqual(res.context['form'].\
fields['instance']._choices[0][1],
"Select an instance")
# The instance choice should not be server_id = 3
self.assertNotEqual(res.context['form'].\
fields['instance']._choices[1][0],
volume.attachments[0]['server_id'])
self.assertEqual(len(res.context['form'].fields['instance'].choices),
2)
self.assertEqual(res.context['form'].fields['instance']._choices[1][0],
server.id)
self.assertEqual(res.status_code, 200)
@test.create_stubs({api.nova: ('volume_get', 'server_get',)})
def test_detail_view(self):
volume = self.volumes.first()
server = self.servers.first()
volume.attachments = [{"server_id": server.id}]
self.mox.StubOutWithMock(api.nova, 'volume_get')
self.mox.StubOutWithMock(api.nova, 'server_get')
api.nova.volume_get(IsA(http.HttpRequest), volume.id).AndReturn(volume)
api.nova.server_get(IsA(http.HttpRequest), server.id).AndReturn(server)
self.mox.ReplayAll()
url = reverse('horizon:nova:instances_and_volumes:volumes:detail',
@ -102,7 +90,8 @@ class VolumeViewTests(test.TestCase):
res = self.client.get(url)
self.assertContains(res, "<dd>Volume name</dd>", 1, 200)
self.assertContains(res, "<dd>1</dd>", 1, 200)
self.assertContains(res, "<dd>41023e92-8008-4c8b-8059-" \
"7f2293ff3775</dd>", 1, 200)
self.assertContains(res, "<dd>Available</dd>", 1, 200)
self.assertContains(res, "<dd>40 GB</dd>", 1, 200)
self.assertContains(res, "<dd>04/01/12 at 10:30:00</dd>", 1, 200)

View File

@ -26,6 +26,7 @@ from django import template
from django.conf import settings
from django.contrib import messages
from django.core import urlresolvers
from django.template.defaultfilters import truncatechars
from django.template.loader import render_to_string
from django.utils import http
from django.utils.datastructures import SortedDict
@ -142,6 +143,14 @@ class Column(html.HTMLElement):
A dict of HTML attribute strings which should be added to this column.
Example: ``attrs={"data-foo": "bar"}``.
.. attribute:: truncate
An integer for the maximum length of the string in this column. If the
data in this column is larger than the supplied number, the data for
this column will be truncated and an ellipsis will be appended to the
truncated data.
Defaults to ``None``.
"""
summation_methods = {
"sum": sum,
@ -172,31 +181,35 @@ class Column(html.HTMLElement):
def __init__(self, transform, verbose_name=None, sortable=True,
link=None, hidden=False, attrs=None, status=False,
status_choices=None, display_choices=None, empty_value=None,
filters=None, classes=None, summation=None, auto=None):
filters=None, classes=None, summation=None, auto=None,
truncate=None):
self.classes = list(classes or getattr(self, "classes", []))
super(Column, self).__init__()
self.attrs.update(attrs or {})
self.auto = auto
if callable(transform):
self.transform = transform
self.name = transform.__name__
else:
self.transform = unicode(transform)
self.name = self.transform
self.sortable = sortable
# Empty string is a valid value for verbose_name
if verbose_name is None:
verbose_name = self.transform.title()
else:
verbose_name = verbose_name
self.auto = auto
self.sortable = sortable
self.verbose_name = verbose_name
self.link = link
self.hidden = hidden
self.status = status
self.empty_value = empty_value or '-'
self.filters = filters or []
self.truncate = truncate
if status_choices:
self.status_choices = status_choices
self.display_choices = display_choices
@ -257,20 +270,29 @@ class Column(html.HTMLElement):
method for this column.
"""
datum_id = self.table.get_object_id(datum)
if datum_id in self.table._data_cache[self]:
return self.table._data_cache[self][datum_id]
data = self.get_raw_data(datum)
display_value = None
if self.display_choices:
display_value = [display for (value, display) in
self.display_choices
if value.lower() == (data or '').lower()]
if display_value:
data = display_value[0]
else:
for filter_func in self.filters:
data = filter_func(data)
if data and self.truncate:
data = truncatechars(data, self.truncate)
self.table._data_cache[self][datum_id] = data
return self.table._data_cache[self][datum_id]
def get_link_url(self, datum):

View File

@ -56,6 +56,11 @@ TEST_DATA_4 = (
FakeObject('2', 'object_2', 4, 'up'),
)
TEST_DATA_5 = (
FakeObject('1', 'object_1', 'A Value That is longer than 35 characters!',
'down', 'optional_1'),
)
class MyLinkAction(tables.LinkAction):
name = "login"
@ -153,7 +158,8 @@ class MyTable(tables.DataTable):
sortable=True,
link='http://example.com/',
attrs={'class': 'green blue'},
summation="average")
summation="average",
truncate=35)
status = tables.Column('status', link=get_link)
optional = tables.Column('optional', empty_value='N/A')
excluded = tables.Column('excluded')
@ -379,6 +385,14 @@ class DataTableTests(test.TestCase):
self.assertEqual(row.cells['status'].get_status_class(cell_status),
'status_up')
def test_table_column_truncation(self):
self.table = MyTable(self.request, TEST_DATA_5)
row = self.table.get_rows()[0]
self.assertEqual(len(row.cells['value'].data), 35)
self.assertEqual(row.cells['value'].data,
u'A Value That is longer than 35 c...')
def test_table_rendering(self):
self.table = MyTable(self.request, TEST_DATA)
# Table actions

View File

@ -146,14 +146,38 @@ def data(TEST):
# Volumes
volume = volumes.Volume(volumes.VolumeManager(None),
dict(id="1",
dict(id="41023e92-8008-4c8b-8059-7f2293ff3775",
name='test_volume',
status='available',
size=40,
display_name='Volume name',
created_at='2012-04-01 10:30:00',
attachments={}))
attachments=[]))
nameless_volume = volumes.Volume(volumes.VolumeManager(None),
dict(id="3b189ac8-9166-ac7f-90c9-16c8bf9e01ac",
name='',
status='in-use',
size=10,
display_name='',
display_description='',
device="/dev/hda",
created_at='2010-11-21 18:34:25',
attachments=[{"id": "1", "server_id": '1',
"device": "/dev/hda"}]))
attached_volume = volumes.Volume(volumes.VolumeManager(None),
dict(id="8cba67c1-2741-6c79-5ab6-9c2bf8c96ab0",
name='my_volume',
status='in-use',
size=30,
display_name='My Volume',
display_description='',
device="/dev/hdk",
created_at='2011-05-01 11:54:33',
attachments=[{"id": "2", "server_id": '1',
"device": "/dev/hdk"}]))
TEST.volumes.add(volume)
TEST.volumes.add(nameless_volume)
TEST.volumes.add(attached_volume)
# Flavors
flavor_1 = flavors.Flavor(flavors.FlavorManager(None),