Merge "Fixes bug 1006405-Additional test cases to be added to test_volumes_negative.py"
diff --git a/tempest/openstack.py b/tempest/openstack.py
index 18af647..e23373f 100644
--- a/tempest/openstack.py
+++ b/tempest/openstack.py
@@ -31,6 +31,8 @@
from tempest.services.nova.json.floating_ips_client import FloatingIPsClient
from tempest.services.nova.json.keypairs_client import KeyPairsClient
from tempest.services.nova.json.volumes_client import VolumesClient
+from tempest.services.nova.json.console_output_client \
+import ConsoleOutputsClient
from tempest.services.identity.json.admin_client import AdminClient
from tempest.services.identity.json.admin_client import TokenClient
@@ -82,6 +84,7 @@
self.security_groups_client = SecurityGroupsClient(*client_args)
self.floating_ips_client = FloatingIPsClient(*client_args)
self.volumes_client = VolumesClient(*client_args)
+ self.console_outputs_client = ConsoleOutputsClient(*client_args)
self.admin_client = AdminClient(*client_args)
self.token_client = TokenClient(self.config)
self.network_client = NetworkClient(*client_args)
diff --git a/tempest/services/nova/json/console_output_client.py b/tempest/services/nova/json/console_output_client.py
new file mode 100644
index 0000000..4a8795b
--- /dev/null
+++ b/tempest/services/nova/json/console_output_client.py
@@ -0,0 +1,18 @@
+from tempest.common.rest_client import RestClient
+import json
+
+
+class ConsoleOutputsClient(RestClient):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(ConsoleOutputsClient, self).__init__(config, username, password,
+ auth_url, tenant_name)
+ self.service = self.config.compute.catalog_type
+
+ def get_console_output(self, server_id, length):
+ post_body = {'os-getConsoleOutput': {'length': length}}
+ url = "/servers/%s/action" % server_id
+ post_body = json.dumps(post_body)
+ resp, body = self.post(url, post_body, self.headers)
+ body = json.loads(body)
+ return resp, body['output']
diff --git a/tempest/tests/base_compute_test.py b/tempest/tests/base_compute_test.py
index 2b16d8c..d7dac10 100644
--- a/tempest/tests/base_compute_test.py
+++ b/tempest/tests/base_compute_test.py
@@ -15,6 +15,7 @@
keypairs_client = os.keypairs_client
floating_ips_client = os.floating_ips_client
security_groups_client = os.security_groups_client
+ console_outputs_client = os.console_outputs_client
limits_client = os.limits_client
config = os.config
build_interval = config.compute.build_interval
diff --git a/tempest/tests/network/base.py b/tempest/tests/network/base.py
new file mode 100644
index 0000000..1046cd6
--- /dev/null
+++ b/tempest/tests/network/base.py
@@ -0,0 +1,59 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack, LLC
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import nose
+import unittest2 as unittest
+
+from tempest import exceptions
+from tempest import openstack
+from tempest.common.utils.data_utils import rand_name
+
+
+class BaseNetworkTest(unittest.TestCase):
+
+ os = openstack.Manager()
+ client = os.network_client
+ config = os.config
+ networks = []
+ enabled = True
+
+ # Validate that there is even an endpoint configured
+ # for networks, and mark the attr for skipping if not
+ try:
+ client.list_networks()
+ except exceptions.EndpointNotFound:
+ enabled = False
+ skip_msg = "No network endpoint"
+
+ @classmethod
+ def setUpClass(cls):
+ if not cls.enabled:
+ raise nose.SkipTest(cls.skip_msg)
+
+ @classmethod
+ def tearDownClass(cls):
+ for network in cls.networks:
+ cls.client.delete_network(network['id'])
+
+ def create_network(self, network_name=None):
+ """Wrapper utility that returns a test network"""
+ network_name = network_name or rand_name('test-network')
+
+ resp, body = self.client.create_network(network_name)
+ network = body['network']
+ self.networks.append(network)
+ return network
diff --git a/tempest/tests/network/test_networks.py b/tempest/tests/network/test_networks.py
index 6adbb6b..5476551 100644
--- a/tempest/tests/network/test_networks.py
+++ b/tempest/tests/network/test_networks.py
@@ -1,23 +1,33 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack, LLC
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
from nose.plugins.attrib import attr
-from tempest import openstack
+
from tempest.common.utils.data_utils import rand_name
-import unittest2 as unittest
+from tempest.tests.network import base
-class NetworksTest(unittest.TestCase):
+class NetworksTest(base.BaseNetworkTest):
@classmethod
def setUpClass(cls):
- cls.os = openstack.Manager()
- cls.client = cls.os.network_client
- cls.config = cls.os.config
- cls.name = rand_name('network')
- resp, body = cls.client.create_network(cls.name)
- cls.network = body['network']
-
- @classmethod
- def tearDownClass(cls):
- cls.client.delete_network(cls.network['id'])
+ super(NetworksTest, cls).setUpClass()
+ cls.network = cls.create_network()
+ cls.name = cls.network['name']
@attr(type='positive')
def test_create_delete_network(self):
diff --git a/tempest/tests/test_authorization.py b/tempest/tests/test_authorization.py
index b21e0d3..8a0cf1b 100644
--- a/tempest/tests/test_authorization.py
+++ b/tempest/tests/test_authorization.py
@@ -18,6 +18,7 @@
cls.images_client = cls.os.images_client
cls.keypairs_client = cls.os.keypairs_client
cls.security_client = cls.os.security_groups_client
+ cls.console_outputs_client = cls.os.console_outputs_client
cls.config = cls.os.config
cls.image_ref = cls.config.compute.image_ref
cls.flavor_ref = cls.config.compute.flavor_ref
@@ -42,6 +43,8 @@
cls.other_keypairs_client = cls.other_manager.keypairs_client
cls.other_security_client = \
cls.other_manager.security_groups_client
+ cls.other_console_outputs_client = \
+ cls.other_manager.console_outputs_client
except exceptions.AuthenticationFailure:
# multi_user is already set to false, just fall through
pass
@@ -420,3 +423,13 @@
resp, body = \
self.images_client.delete_image_metadata_item(self.image['id'],
'meta1')
+
+ @raises(exceptions.NotFound)
+ @attr(type='negative')
+ @utils.skip_unless_attr('multi_user', 'Second user not configured')
+ def test_get_console_output_of_other_account_server_fails(self):
+ """
+ A Get Console Output for another user's server should fail
+ """
+ self.other_console_outputs_client.get_console_output(self.server['id'],
+ 10)
diff --git a/tempest/tests/test_console_output.py b/tempest/tests/test_console_output.py
new file mode 100644
index 0000000..1aa4a1c
--- /dev/null
+++ b/tempest/tests/test_console_output.py
@@ -0,0 +1,59 @@
+from nose.plugins.attrib import attr
+from tempest import openstack
+from tempest import exceptions
+from base_compute_test import BaseComputeTest
+from tempest.common.utils.data_utils import rand_name
+
+
+class ConsoleOutputTest(BaseComputeTest):
+
+ @classmethod
+ def setUpClass(cls):
+ cls.client = cls.console_outputs_client
+ cls.servers_client = cls.servers_client
+ cls.name = rand_name('server')
+ resp, server = cls.servers_client.create_server(cls.name,
+ cls.image_ref,
+ cls.flavor_ref)
+ cls.server_id = server['id']
+
+ cls.servers_client.wait_for_server_status(cls.server_id, 'ACTIVE')
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.servers_client.delete_server(cls.server_id)
+
+ @attr(type='positive')
+ def test_get_console_output(self):
+ resp, output = self.client.get_console_output(self.server_id, 10)
+ self.assertEqual(200, resp.status)
+ self.assertNotEqual(output, None)
+ lines = len(output.split('\n'))
+ self.assertEqual(lines, 10)
+
+ @attr(type='negative')
+ def test_get_console_output_invalid_server_id(self):
+ try:
+ resp, output = self.client.get_console_output('!@#$%^&*()', 10)
+ except exceptions.NotFound:
+ pass
+
+ @attr(type='positive')
+ def test_get_console_output_server_id_in_reboot_status(self):
+ try:
+ resp, output = self.servers_client.reboot(self.server_id, 'SOFT')
+ self.servers_client.wait_for_server_status(self.server_id,
+ 'REBOOT')
+ resp, server = self.servers_client.get_server(self.server_id)
+ if (server['status'] == 'REBOOT'):
+ resp, output = self.client.get_console_output(self.server_id,
+ 10)
+ self.assertEqual(200, resp.status)
+ self.assertNotEqual(output, None)
+ lines = len(output.split('\n'))
+ self.assertEqual(lines, 10)
+ else:
+ self.fail("Could not capture instance in Reboot status")
+ finally:
+ self.servers_client.wait_for_server_status(self.server_id,
+ 'ACTIVE')
diff --git a/tempest/tests/test_flavors.py b/tempest/tests/test_flavors.py
index d5d598f..6a38e3b 100644
--- a/tempest/tests/test_flavors.py
+++ b/tempest/tests/test_flavors.py
@@ -4,6 +4,7 @@
class FlavorsTest(BaseComputeTest):
+ _multiprocess_shared_ = True
@classmethod
def setUpClass(cls):
diff --git a/tempest/tests/test_images.py b/tempest/tests/test_images.py
index 65fd4be..c5ea509 100644
--- a/tempest/tests/test_images.py
+++ b/tempest/tests/test_images.py
@@ -7,6 +7,7 @@
from tempest import openstack
from tempest.common.utils import data_utils
from tempest import exceptions
+from tempest.tests import utils
class ImagesTest(BaseComputeTest):
@@ -18,7 +19,37 @@
def setUpClass(cls):
cls.client = cls.images_client
cls.servers_client = cls.servers_client
- cls.create_image_enabled = cls.config.compute.create_image_enabled
+
+ cls.user1 = cls.config.compute.username
+ cls.user2 = cls.config.compute.alt_username
+ cls.user2_password = cls.config.compute.alt_password
+ cls.user2_tenant_name = cls.config.compute.alt_tenant_name
+ cls.multi_user = False
+ cls.image_ids = []
+
+ if (cls.user2 and cls.user1 != cls.user2 and cls.user2_password \
+ and cls.user2_tenant_name):
+
+ try:
+ cls.alt_manager = openstack.AltManager()
+ cls.alt_client = cls.alt_manager.images_client
+ except exceptions.AuthenticationFailure:
+ # multi_user is already set to false, just fall through
+ pass
+ else:
+ cls.multi_user = True
+
+ def tearDown(self):
+ """Terminate test instances created after a test is executed"""
+ for server in self.servers:
+ resp, body = self.servers_client.delete_server(server['id'])
+ if resp['status'] == '204':
+ self.servers.remove(server)
+ self.servers_client.wait_for_server_termination(server['id'])
+
+ for image_id in self.image_ids:
+ self.client.delete_image(image_id)
+ self.image_ids.remove(image_id)
@attr(type='smoke')
@unittest.skipUnless(create_image_enabled,
@@ -103,6 +134,155 @@
" with invalid server id")
@attr(type='negative')
+ @utils.skip_unless_attr('multi_user', 'Second user not configured')
+ def test_create_image_for_server_in_another_tenant(self):
+ """Creating image of another tenant's server should be return error"""
+ server = self.create_server()
+
+ snapshot_name = rand_name('test-snap-')
+ self.assertRaises(exceptions.NotFound, self.alt_client.create_image,
+ server['id'], snapshot_name)
+
+ @attr(type='negative')
+ def test_create_image_when_server_is_building(self):
+ """Return error when creating an image of a server that is building"""
+ server_name = rand_name('test-vm-')
+ resp, server = self.servers_client.create_server(server_name,
+ self.image_ref,
+ self.flavor_ref)
+ self.servers.append(server)
+ snapshot_name = rand_name('test-snap-')
+ self.assertRaises(exceptions.Duplicate, self.client.create_image,
+ server['id'], snapshot_name)
+
+ @attr(type='negative')
+ def test_create_image_when_server_is_rebooting(self):
+ """Return error when creating an image of server that is rebooting"""
+ server = self.create_server()
+ self.servers_client.reboot(server['id'], 'HARD')
+
+ snapshot_name = rand_name('test-snap-')
+ self.assertRaises(exceptions.Duplicate, self.client.create_image,
+ server['id'], snapshot_name)
+
+ @attr(type='negative')
+ def test_create_image_when_server_is_terminating(self):
+ """Return an error when creating image of server that is terminating"""
+ server = self.create_server()
+ self.servers_client.delete_server(server['id'])
+
+ snapshot_name = rand_name('test-snap-')
+ self.assertRaises(exceptions.Duplicate, self.client.create_image,
+ server['id'], snapshot_name)
+
+ @attr(type='negative')
+ def test_create_second_image_when_first_image_is_being_saved(self):
+ """Disallow creating another image when first image is being saved"""
+ server = self.create_server()
+
+ try:
+ # Create first snapshot
+ snapshot_name = rand_name('test-snap-')
+ resp, body = self.client.create_image(server['id'], snapshot_name)
+ image_id = data_utils.parse_image_id(resp['location'])
+ self.image_ids.append(image_id)
+
+ # Create second snapshot
+ alt_snapshot_name = rand_name('test-snap-')
+ self.client.create_image(server['id'], alt_snapshot_name)
+ except exceptions.Duplicate:
+ pass
+
+ else:
+ self.fail("Should allow creating an image when another image of"
+ "the server is still being saved")
+
+ @attr(type='negative')
+ @unittest.skip("Until Bug 1004564 is fixed")
+ def test_create_image_specify_name_over_256_chars(self):
+ """Return an error if snapshot name over 256 characters is passed"""
+ server = self.create_server()
+
+ try:
+ snapshot_name = rand_name('a' * 260)
+ self.assertRaises(exceptions.BadRequest, self.client.create_image,
+ server['id'], snapshot_name)
+ except:
+ self.fail("Should return 400 Bad Request if image name is over 256"
+ " characters")
+
+ @attr(type='negative')
+ @unittest.skip("Until Bug 1005397 is fixed")
+ def test_create_image_specify_uuid_35_characters_or_less(self):
+ """Return an error if Image ID passed is 35 characters or less"""
+ try:
+ snapshot_name = rand_name('test-snap-')
+ test_uuid = ('a' * 35)
+ self.assertRaises(exceptions.BadRequest, self.client.create_image,
+ test_uuid, snapshot_name)
+ except:
+ self.fail("Should return 400 Bad Request if server uuid is 35"
+ " characters or less")
+
+ @attr(type='negative')
+ @unittest.skip("Until Bug 1005397 is fixed")
+ def test_create_image_specify_uuid_37_characters_or_more(self):
+ """Return an error if Image ID passed is 37 characters or more"""
+ try:
+ snapshot_name = rand_name('test-snap-')
+ test_uuid = ('a' * 37)
+ self.assertRaises(exceptions.BadRequest, self.client.create_image,
+ test_uuid, snapshot_name)
+ except:
+ self.fail("Should return 400 Bad Request if server uuid is 37"
+ " characters or more")
+
+ @attr(type='negative')
+ @unittest.skip("Until Bug 1006725 is fixed")
+ def test_create_image_specify_multibyte_character_image_name(self):
+ """Return an error if the image name has multi-byte characters"""
+ server = self.create_server()
+
+ try:
+ snapshot_name = rand_name('\xef\xbb\xbf')
+ self.assertRaises(exceptions.BadRequest,
+ self.client.create_image, server['id'],
+ snapshot_name)
+ except:
+ self.fail("Should return 400 Bad Request if multi byte characters"
+ " are used for image name")
+
+ @attr(type='negative')
+ @unittest.skip("Until Bug 1005423 is fixed")
+ def test_create_image_specify_invalid_metadata(self):
+ """Return an error when creating image with invalid metadata"""
+ server = self.create_server()
+
+ try:
+ snapshot_name = rand_name('test-snap-')
+ meta = {'': ''}
+ self.assertRaises(exceptions.BadRequest, self.client.create_image,
+ server['id'], snapshot_name, meta)
+
+ except:
+ self.fail("Should raise 400 Bad Request if meta data is invalid")
+
+ @attr(type='negative')
+ @unittest.skip("Until Bug 1005423 is fixed")
+ def test_create_image_specify_metadata_over_limits(self):
+ """Return an error when creating image with meta data over 256 chars"""
+ server = self.create_server()
+
+ try:
+ snapshot_name = rand_name('test-snap-')
+ meta = {'a' * 260: 'b' * 260}
+ self.assertRaises(exceptions.OverLimit, self.client.create_image,
+ server['id'], snapshot_name, meta)
+
+ except:
+ self.fail("Should raise 413 Over Limit if meta data was too long")
+
+ @attr(type='negative')
def test_delete_image_with_invalid_image_id(self):
"""An image should not be deleted with invalid image id"""
try:
@@ -115,3 +295,94 @@
else:
self.fail("DELETE image request should rasie NotFound exception"
"when requested with invalid image")
+
+ @attr(type='negative')
+ def test_delete_non_existent_image(self):
+ """Return an error while trying to delete a non-existent image"""
+
+ non_existent_image_id = '11a22b9-12a9-5555-cc11-00ab112223fa'
+ self.assertRaises(exceptions.NotFound, self.client.delete_image,
+ non_existent_image_id)
+
+ @attr(type='negative')
+ @unittest.skip("Until Bug 1006033 is fixed")
+ def test_delete_image_blank_id(self):
+ """Return an error while trying to delete an image with blank Id"""
+
+ try:
+ self.assertRaises(exceptions.BadRequest, self.client.delete_image,
+ '')
+ except:
+ self.fail("Did not return HTTP 400 BadRequest for blank image id")
+
+ @attr(type='negative')
+ @unittest.skip("Until Bug 1006033 is fixed")
+ def test_delete_image_non_hex_string_id(self):
+ """Return an error while trying to delete an image with non hex id"""
+
+ image_id = '11a22b9-120q-5555-cc11-00ab112223gj'
+ try:
+ self.assertRaises(exceptions.BadRequest, self.client.delete_image,
+ image_id)
+ except:
+ self.fail("Did not return HTTP 400 BadRequest for non hex image")
+
+ @attr(type='negative')
+ @unittest.skip("Until Bug 1006033 is fixed")
+ def test_delete_image_negative_image_id(self):
+ """Return an error while trying to delete an image with negative id"""
+
+ try:
+ self.assertRaises(exceptions.BadRequest, self.client.delete_image,
+ -1)
+ except:
+ self.fail("Did not return HTTP 400 BadRequest for negative image "
+ "id")
+
+ @attr(type='negative')
+ @unittest.skip("Until Bug 1006033 is fixed")
+ def test_delete_image_id_is_over_35_character_limit(self):
+ """Return an error while trying to delete image with id over limit"""
+
+ try:
+ self.assertRaises(exceptions.OverLimit, self.client.delete_image,
+ '11a22b9-120q-5555-cc11-00ab112223gj-3fac')
+ except:
+ self.fail("Did not return HTTP 413 OverLimit for image id that "
+ "exceeds 35 character ID length limit")
+
+ @attr(type='negative')
+ @utils.skip_unless_attr('multi_user', 'Second user not configured')
+ def test_delete_image_of_another_tenant(self):
+ """Return an error while trying to delete another tenant's image"""
+
+ server = self.create_server()
+
+ snapshot_name = rand_name('test-snap-')
+ resp, body = self.client.create_image(server['id'], snapshot_name)
+ image_id = data_utils.parse_image_id(resp['location'])
+ self.image_ids.append(image_id)
+ self.client.wait_for_image_resp_code(image_id, 200)
+ self.client.wait_for_image_status(image_id, 'ACTIVE')
+
+ # Delete image
+ self.assertRaises(exceptions.NotFound,
+ self.alt_client.delete_image, image_id)
+
+ @attr(type='negative')
+ def test_delete_image_that_is_not_yet_active(self):
+ """Return an error while trying to delete an active that is creating"""
+
+ server = self.create_server()
+
+ snapshot_name = rand_name('test-snap-')
+ resp, body = self.client.create_image(server['id'], snapshot_name)
+ image_id = data_utils.parse_image_id(resp['location'])
+ self.image_ids.append(image_id)
+
+ # Do not wait, attempt to delete the image, ensure it's successful
+ resp, body = self.client.delete_image(image_id)
+ self.assertEqual('204', resp['status'])
+ self.image_ids.remove(image_id)
+
+ self.assertRaises(exceptions.NotFound, self.client.get_image, image_id)
diff --git a/tempest/tests/test_keypairs.py b/tempest/tests/test_keypairs.py
index d3ba213..809aa8e 100644
--- a/tempest/tests/test_keypairs.py
+++ b/tempest/tests/test_keypairs.py
@@ -7,6 +7,7 @@
class KeyPairsTest(BaseComputeTest):
+ _multiprocess_shared_ = True
@classmethod
def setUpClass(cls):
diff --git a/tempest/tests/test_list_images.py b/tempest/tests/test_list_images.py
index 810e933..098667c 100644
--- a/tempest/tests/test_list_images.py
+++ b/tempest/tests/test_list_images.py
@@ -6,6 +6,7 @@
class ListImagesTest(BaseComputeTest):
+ _multiprocess_shared_ = True
@classmethod
def setUpClass(cls):
diff --git a/tempest/tests/test_list_servers.py b/tempest/tests/test_list_servers.py
index fbdce4b..eaca477 100644
--- a/tempest/tests/test_list_servers.py
+++ b/tempest/tests/test_list_servers.py
@@ -10,6 +10,7 @@
class ServerDetailsTest(BaseComputeTest):
+ _multiprocess_shared_ = True
@classmethod
def setUpClass(cls):
diff --git a/tempest/tests/test_server_personality.py b/tempest/tests/test_server_personality.py
index c790a6c..d4a66ad 100644
--- a/tempest/tests/test_server_personality.py
+++ b/tempest/tests/test_server_personality.py
@@ -7,6 +7,7 @@
class ServerPersonalityTest(BaseComputeTest):
+ _multiprocess_shared_ = True
@classmethod
def setUpClass(cls):
diff --git a/tempest/tests/test_servers.py b/tempest/tests/test_servers.py
index ec9911f..42e6f77 100644
--- a/tempest/tests/test_servers.py
+++ b/tempest/tests/test_servers.py
@@ -4,6 +4,7 @@
class ServersTest(BaseComputeTest):
+ _multiprocess_shared_ = True
@classmethod
def setUpClass(cls):
diff --git a/tempest/tests/test_servers_negative.py b/tempest/tests/test_servers_negative.py
index 87f184f..48c22d3 100644
--- a/tempest/tests/test_servers_negative.py
+++ b/tempest/tests/test_servers_negative.py
@@ -112,6 +112,7 @@
self.flavor_ref)
self.server_id = create_server['id']
self.client.delete_server(self.server_id)
+ self.client.wait_for_server_termination(self.server_id)
try:
resp1, reboot_server = self.client.reboot(self.server_id, 'SOFT')
except exceptions.NotFound:
@@ -128,6 +129,7 @@
self.flavor_ref)
self.server_id = create_server['id']
self.client.delete_server(self.server_id)
+ self.client.wait_for_server_termination(self.server_id)
try:
resp1, rebuild_server = self.client.rebuild(self.server_id,
self.image_ref_alt)