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

View File

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

View File

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

View File

@ -18,7 +18,6 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from copy import deepcopy
from django import http from django import http
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from mox import IsA from mox import IsA
@ -28,14 +27,15 @@ from horizon import test
class VolumeViewTests(test.TestCase): class VolumeViewTests(test.TestCase):
@test.create_stubs({api: ('volume_get',), api.nova: ('server_list',)})
def test_edit_attachments(self): def test_edit_attachments(self):
volume = self.volumes.first() volume = self.volumes.first()
servers = self.servers.list() 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) \ api.volume_get(IsA(http.HttpRequest), volume.id) \
.AndReturn(volume) .AndReturn(volume)
api.nova.server_list(IsA(http.HttpRequest)).AndReturn(servers) api.nova.server_list(IsA(http.HttpRequest)).AndReturn(servers)
self.mox.ReplayAll() self.mox.ReplayAll()
url = reverse('horizon:nova:instances_and_volumes:volumes:attach', url = reverse('horizon:nova:instances_and_volumes:volumes:attach',
@ -47,54 +47,42 @@ class VolumeViewTests(test.TestCase):
2) 2)
self.assertEqual(res.status_code, 200) 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): def test_edit_attachments_attached_volume(self):
server = self.servers.first() server = self.servers.first()
servers = deepcopy(self.servers) volume = self.volumes.list()[0]
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)
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) \ api.volume_get(IsA(http.HttpRequest), volume.id) \
.AndReturn(volume) .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() self.mox.ReplayAll()
url = reverse('horizon:nova:instances_and_volumes:volumes:attach', url = reverse('horizon:nova:instances_and_volumes:volumes:attach',
args=[volume.id]) args=[volume.id])
res = self.client.get(url) 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'].\ self.assertEqual(res.context['form'].\
fields['instance']._choices[0][1], fields['instance']._choices[0][1],
"Select an instance") "Select an instance")
# The instance choice should not be server_id = 3 self.assertEqual(len(res.context['form'].fields['instance'].choices),
self.assertNotEqual(res.context['form'].\ 2)
fields['instance']._choices[1][0], self.assertEqual(res.context['form'].fields['instance']._choices[1][0],
volume.attachments[0]['server_id']) server.id)
self.assertEqual(res.status_code, 200) self.assertEqual(res.status_code, 200)
@test.create_stubs({api.nova: ('volume_get', 'server_get',)})
def test_detail_view(self): def test_detail_view(self):
volume = self.volumes.first() volume = self.volumes.first()
server = self.servers.first() server = self.servers.first()
volume.attachments = [{"server_id": server.id}] 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.volume_get(IsA(http.HttpRequest), volume.id).AndReturn(volume)
api.nova.server_get(IsA(http.HttpRequest), server.id).AndReturn(server) api.nova.server_get(IsA(http.HttpRequest), server.id).AndReturn(server)
self.mox.ReplayAll() self.mox.ReplayAll()
url = reverse('horizon:nova:instances_and_volumes:volumes:detail', url = reverse('horizon:nova:instances_and_volumes:volumes:detail',
@ -102,7 +90,8 @@ class VolumeViewTests(test.TestCase):
res = self.client.get(url) res = self.client.get(url)
self.assertContains(res, "<dd>Volume name</dd>", 1, 200) 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>Available</dd>", 1, 200)
self.assertContains(res, "<dd>40 GB</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) 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.conf import settings
from django.contrib import messages from django.contrib import messages
from django.core import urlresolvers from django.core import urlresolvers
from django.template.defaultfilters import truncatechars
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.utils import http from django.utils import http
from django.utils.datastructures import SortedDict 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. A dict of HTML attribute strings which should be added to this column.
Example: ``attrs={"data-foo": "bar"}``. 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 = { summation_methods = {
"sum": sum, "sum": sum,
@ -172,31 +181,35 @@ class Column(html.HTMLElement):
def __init__(self, transform, verbose_name=None, sortable=True, def __init__(self, transform, verbose_name=None, sortable=True,
link=None, hidden=False, attrs=None, status=False, link=None, hidden=False, attrs=None, status=False,
status_choices=None, display_choices=None, empty_value=None, 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", [])) self.classes = list(classes or getattr(self, "classes", []))
super(Column, self).__init__() super(Column, self).__init__()
self.attrs.update(attrs or {}) self.attrs.update(attrs or {})
self.auto = auto
if callable(transform): if callable(transform):
self.transform = transform self.transform = transform
self.name = transform.__name__ self.name = transform.__name__
else: else:
self.transform = unicode(transform) self.transform = unicode(transform)
self.name = self.transform self.name = self.transform
self.sortable = sortable
# Empty string is a valid value for verbose_name # Empty string is a valid value for verbose_name
if verbose_name is None: if verbose_name is None:
verbose_name = self.transform.title() verbose_name = self.transform.title()
else: else:
verbose_name = verbose_name verbose_name = verbose_name
self.auto = auto
self.sortable = sortable
self.verbose_name = verbose_name self.verbose_name = verbose_name
self.link = link self.link = link
self.hidden = hidden self.hidden = hidden
self.status = status self.status = status
self.empty_value = empty_value or '-' self.empty_value = empty_value or '-'
self.filters = filters or [] self.filters = filters or []
self.truncate = truncate
if status_choices: if status_choices:
self.status_choices = status_choices self.status_choices = status_choices
self.display_choices = display_choices self.display_choices = display_choices
@ -257,20 +270,29 @@ class Column(html.HTMLElement):
method for this column. method for this column.
""" """
datum_id = self.table.get_object_id(datum) datum_id = self.table.get_object_id(datum)
if datum_id in self.table._data_cache[self]: if datum_id in self.table._data_cache[self]:
return self.table._data_cache[self][datum_id] return self.table._data_cache[self][datum_id]
data = self.get_raw_data(datum) data = self.get_raw_data(datum)
display_value = None display_value = None
if self.display_choices: if self.display_choices:
display_value = [display for (value, display) in display_value = [display for (value, display) in
self.display_choices self.display_choices
if value.lower() == (data or '').lower()] if value.lower() == (data or '').lower()]
if display_value: if display_value:
data = display_value[0] data = display_value[0]
else: else:
for filter_func in self.filters: for filter_func in self.filters:
data = filter_func(data) data = filter_func(data)
if data and self.truncate:
data = truncatechars(data, self.truncate)
self.table._data_cache[self][datum_id] = data self.table._data_cache[self][datum_id] = data
return self.table._data_cache[self][datum_id] return self.table._data_cache[self][datum_id]
def get_link_url(self, datum): def get_link_url(self, datum):

View File

@ -56,6 +56,11 @@ TEST_DATA_4 = (
FakeObject('2', 'object_2', 4, 'up'), 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): class MyLinkAction(tables.LinkAction):
name = "login" name = "login"
@ -153,7 +158,8 @@ class MyTable(tables.DataTable):
sortable=True, sortable=True,
link='http://example.com/', link='http://example.com/',
attrs={'class': 'green blue'}, attrs={'class': 'green blue'},
summation="average") summation="average",
truncate=35)
status = tables.Column('status', link=get_link) status = tables.Column('status', link=get_link)
optional = tables.Column('optional', empty_value='N/A') optional = tables.Column('optional', empty_value='N/A')
excluded = tables.Column('excluded') excluded = tables.Column('excluded')
@ -379,6 +385,14 @@ class DataTableTests(test.TestCase):
self.assertEqual(row.cells['status'].get_status_class(cell_status), self.assertEqual(row.cells['status'].get_status_class(cell_status),
'status_up') '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): def test_table_rendering(self):
self.table = MyTable(self.request, TEST_DATA) self.table = MyTable(self.request, TEST_DATA)
# Table actions # Table actions

View File

@ -146,14 +146,38 @@ def data(TEST):
# Volumes # Volumes
volume = volumes.Volume(volumes.VolumeManager(None), volume = volumes.Volume(volumes.VolumeManager(None),
dict(id="1", dict(id="41023e92-8008-4c8b-8059-7f2293ff3775",
name='test_volume', name='test_volume',
status='available', status='available',
size=40, size=40,
display_name='Volume name', display_name='Volume name',
created_at='2012-04-01 10:30:00', 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(volume)
TEST.volumes.add(nameless_volume)
TEST.volumes.add(attached_volume)
# Flavors # Flavors
flavor_1 = flavors.Flavor(flavors.FlavorManager(None), flavor_1 = flavors.Flavor(flavors.FlavorManager(None),