diff --git a/horizon/dashboards/nova/instances_and_volumes/instances/tests.py b/horizon/dashboards/nova/instances_and_volumes/instances/tests.py index ff718518e..eee16c0b5 100644 --- a/horizon/dashboards/nova/instances_and_volumes/instances/tests.py +++ b/horizon/dashboards/nova/instances_and_volumes/instances/tests.py @@ -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, "
keyName
", 1) - # Meta data self.assertContains(res, "
someMetaLabel
", 1) self.assertContains(res, "
someMetaData
", 1) - # Test escaping of html characters in names self.assertContains(res, "
some<b>html</b>label
", 1) self.assertContains(res, "
<!--
", 1) self.assertContains(res, "
empty
", 1) self.assertContains(res, "
N/A
", 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, '' 'Snapshotting', 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, ['', '', '', '']) + @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") diff --git a/horizon/dashboards/nova/instances_and_volumes/tests.py b/horizon/dashboards/nova/instances_and_volumes/tests.py index 169ea86f4..e08fc9fce 100644 --- a/horizon/dashboards/nova/instances_and_volumes/tests.py +++ b/horizon/dashboards/nova/instances_and_volumes/tests.py @@ -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()) diff --git a/horizon/dashboards/nova/instances_and_volumes/views.py b/horizon/dashboards/nova/instances_and_volumes/views.py index 4ba6ab313..5c79d43f8 100644 --- a/horizon/dashboards/nova/instances_and_volumes/views.py +++ b/horizon/dashboards/nova/instances_and_volumes/views.py @@ -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) diff --git a/horizon/dashboards/nova/instances_and_volumes/volumes/tables.py b/horizon/dashboards/nova/instances_and_volumes/volumes/tables.py index e7bca4cd1..0632db149 100644 --- a/horizon/dashboards/nova/instances_and_volumes/volumes/tables.py +++ b/horizon/dashboards/nova/instances_and_volumes/volumes/tables.py @@ -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,), diff --git a/horizon/dashboards/nova/instances_and_volumes/volumes/tests.py b/horizon/dashboards/nova/instances_and_volumes/volumes/tests.py index 3e52458e9..9d90f3f18 100644 --- a/horizon/dashboards/nova/instances_and_volumes/volumes/tests.py +++ b/horizon/dashboards/nova/instances_and_volumes/volumes/tests.py @@ -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, "
Volume name
", 1, 200) - self.assertContains(res, "
1
", 1, 200) + self.assertContains(res, "
41023e92-8008-4c8b-8059-" \ + "7f2293ff3775
", 1, 200) self.assertContains(res, "
Available
", 1, 200) self.assertContains(res, "
40 GB
", 1, 200) self.assertContains(res, "
04/01/12 at 10:30:00
", 1, 200) diff --git a/horizon/tables/base.py b/horizon/tables/base.py index 940fab6cc..178447fcc 100644 --- a/horizon/tables/base.py +++ b/horizon/tables/base.py @@ -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): diff --git a/horizon/tests/table_tests.py b/horizon/tests/table_tests.py index 701fee1fa..b7a108a0c 100644 --- a/horizon/tests/table_tests.py +++ b/horizon/tests/table_tests.py @@ -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 diff --git a/horizon/tests/test_data/nova_data.py b/horizon/tests/test_data/nova_data.py index 5cdbeb29c..f641628ed 100644 --- a/horizon/tests/test_data/nova_data.py +++ b/horizon/tests/test_data/nova_data.py @@ -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),