Merge "add find_stack_traces tool"
diff --git a/tempest/common/rest_client.py b/tempest/common/rest_client.py
index 5ce3be6..6147aa1 100644
--- a/tempest/common/rest_client.py
+++ b/tempest/common/rest_client.py
@@ -192,6 +192,13 @@
def copy(self, url, headers=None):
return self.request('COPY', url, headers)
+ def get_versions(self):
+ resp, body = self.get('')
+ body = self._parse_resp(body)
+ body = body['versions']
+ versions = map(lambda x: x['id'], body)
+ return resp, versions
+
def _log_request(self, method, req_url, headers, body):
self.LOG.info('Request: ' + method + ' ' + req_url)
if headers:
diff --git a/tempest/services/compute/xml/common.py b/tempest/services/compute/xml/common.py
index bbc4e38..4b1b11a 100644
--- a/tempest/services/compute/xml/common.py
+++ b/tempest/services/compute/xml/common.py
@@ -100,7 +100,8 @@
"""
json = {}
for attr in node.keys():
- json[attr] = node.get(attr)
+ if not attr.startswith("xmlns"):
+ json[attr] = node.get(attr)
if not node.getchildren():
return node.text or json
for child in node.getchildren():
diff --git a/tempest/services/image/v1/json/image_client.py b/tempest/services/image/v1/json/image_client.py
index 45e93e2..77c9cd2 100644
--- a/tempest/services/image/v1/json/image_client.py
+++ b/tempest/services/image/v1/json/image_client.py
@@ -121,28 +121,25 @@
body = json.loads(''.join([c for c in body_iter]))
return resp, body['image']
- def create_image(self, name, container_format, disk_format, is_public=True,
- location=None, properties=None, data=None):
+ def create_image(self, name, container_format, disk_format, **kwargs):
params = {
"name": name,
"container_format": container_format,
"disk_format": disk_format,
- "is_public": is_public,
}
+
headers = {}
- if location is not None:
- params['location'] = location
-
- if properties is not None:
- params['properties'] = properties
+ for option in ['is_public', 'location', 'properties']:
+ if option in kwargs:
+ params[option] = kwargs.get(option)
headers.update(self._image_meta_to_headers(params))
- if data is not None:
- return self._create_with_data(headers, data)
+ if 'data' in kwargs:
+ return self._create_with_data(headers, kwargs.get('data'))
- resp, body = self.post('v1/images', data, headers)
+ resp, body = self.post('v1/images', None, headers)
body = json.loads(body)
return resp, body['image']
diff --git a/tempest/services/image/v2/json/image_client.py b/tempest/services/image/v2/json/image_client.py
index bcae79b..2c50a8d 100644
--- a/tempest/services/image/v2/json/image_client.py
+++ b/tempest/services/image/v2/json/image_client.py
@@ -63,17 +63,20 @@
jsonschema.validate(body, schema)
- def create_image(self, name, container_format, disk_format, is_public=True,
- properties=None):
+ def create_image(self, name, container_format, disk_format, **kwargs):
params = {
"name": name,
"container_format": container_format,
"disk_format": disk_format,
}
- if is_public:
- params["visibility"] = "public"
- else:
- params["visibility"] = "private"
+
+ for option in ['visibility']:
+ if option in kwargs:
+ value = kwargs.get(option)
+ if isinstance(value, dict) or isinstance(value, tuple):
+ params.update(value)
+ else:
+ params[option] = value
data = json.dumps(params)
self._validate_schema(data)
diff --git a/tempest/tests/compute/admin/test_quotas.py b/tempest/tests/compute/admin/test_quotas.py
index 7430a7c..7f64d15 100644
--- a/tempest/tests/compute/admin/test_quotas.py
+++ b/tempest/tests/compute/admin/test_quotas.py
@@ -122,15 +122,10 @@
resp, quota_set = self.adm_client.update_quota_set(self.demo_tenant_id,
cores=vcpu_quota)
- try:
- self.create_server()
- except exceptions.OverLimit:
- pass
- else:
- self.fail("Could create servers over the VCPU quota limit")
- finally:
- self.adm_client.update_quota_set(self.demo_tenant_id,
- cores=default_vcpu_quota)
+
+ self.addCleanup(self.adm_client.update_quota_set, self.demo_tenant_id,
+ cores=default_vcpu_quota)
+ self.assertRaises(exceptions.OverLimit, self.create_server)
def test_create_server_when_memory_quota_is_full(self):
# Disallow server creation when tenant's memory quota is full
@@ -140,15 +135,10 @@
self.adm_client.update_quota_set(self.demo_tenant_id,
ram=mem_quota)
- try:
- self.create_server()
- except exceptions.OverLimit:
- pass
- else:
- self.fail("Could create servers over the memory quota limit")
- finally:
- self.adm_client.update_quota_set(self.demo_tenant_id,
- ram=default_mem_quota)
+
+ self.addCleanup(self.adm_client.update_quota_set, self.demo_tenant_id,
+ ram=default_mem_quota)
+ self.assertRaises(exceptions.OverLimit, self.create_server)
#TODO(afazekas): Add test that tried to update the quota_set as a regular user
diff --git a/tempest/tests/compute/servers/test_server_rescue.py b/tempest/tests/compute/servers/test_server_rescue.py
index 70e3b7c..9230305 100644
--- a/tempest/tests/compute/servers/test_server_rescue.py
+++ b/tempest/tests/compute/servers/test_server_rescue.py
@@ -123,7 +123,7 @@
self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE')
@attr(type='negative')
- @testtools.skip("Skipped until BUG:1126163 is resolved")
+ @testtools.skip("Skipped until Bug:1126163 is resolved")
def test_rescued_vm_reboot(self):
self.assertRaises(exceptions.BadRequest, self.servers_client.reboot,
self.rescue_id, 'HARD')
@@ -136,6 +136,7 @@
self.image_ref_alt)
@attr(type='positive')
+ @testtools.skip("Skipped due to Bug:1126187")
def test_rescued_vm_attach_volume(self):
client = self.volumes_extensions_client
@@ -164,7 +165,7 @@
self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE')
@attr(type='positive')
- @testtools.skip("Skipped until BUG:1126187 is resolved")
+ @testtools.skip("Skipped until Bug:1126187 is resolved")
def test_rescued_vm_detach_volume(self):
# Attach the volume to the server
self.servers_client.attach_volume(self.server_id,
@@ -216,7 +217,7 @@
self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE')
@attr(type='positive')
- @testtools.skip("Skipped until BUG: 1126257 is resolved")
+ @testtools.skip("Skipped until Bug: 1126257 is resolved")
def test_rescued_vm_add_remove_security_group(self):
#Add Security group
resp, body = self.servers_client.add_security_group(self.server_id,
diff --git a/tempest/tests/compute/volumes/test_attach_volume.py b/tempest/tests/compute/volumes/test_attach_volume.py
index 2679312..7c1a2d1 100644
--- a/tempest/tests/compute/volumes/test_attach_volume.py
+++ b/tempest/tests/compute/volumes/test_attach_volume.py
@@ -28,6 +28,12 @@
_interface = 'json'
run_ssh = tempest.config.TempestConfig().compute.run_ssh
+ def __init__(self, *args, **kwargs):
+ super(AttachVolumeTestJSON, self).__init__(*args, **kwargs)
+ self.server = None
+ self.volume = None
+ self.attached = False
+
@classmethod
def setUpClass(cls):
super(AttachVolumeTestJSON, cls).setUpClass()
@@ -37,9 +43,13 @@
self.servers_client.detach_volume(server_id, volume_id)
self.volumes_client.wait_for_volume_status(volume_id, 'available')
- def _delete(self, server_id, volume_id):
- self.volumes_client.delete_volume(volume_id)
- self.servers_client.delete_server(server_id)
+ def _delete(self, server, volume):
+ if self.volume:
+ self.volumes_client.delete_volume(self.volume['id'])
+ self.volume = None
+ if self.server:
+ self.servers_client.delete_server(self.server['id'])
+ self.server = None
def _create_and_attach(self):
name = rand_name('server')
@@ -49,6 +59,7 @@
self.image_ref,
self.flavor_ref,
adminPass='password')
+ self.server = server
self.servers_client.wait_for_server_status(server['id'], 'ACTIVE')
# Record addresses so that we can ssh later
@@ -58,6 +69,7 @@
# Create a volume and wait for it to become ready
resp, volume = self.volumes_client.create_volume(1,
display_name='test')
+ self.volume = volume
self.volumes_client.wait_for_volume_status(volume['id'], 'available')
# Attach the volume to the server
@@ -65,18 +77,18 @@
device='/dev/%s' % self.device)
self.volumes_client.wait_for_volume_status(volume['id'], 'in-use')
- return server, volume
+ self.attached = True
@attr(type='positive')
@testtools.skipIf(not run_ssh, 'SSH required for this test')
def test_attach_detach_volume(self):
# Stop and Start a server with an attached volume, ensuring that
# the volume remains attached.
- server, volume = self._create_and_attach()
-
- attached = True
-
try:
+ self._create_and_attach()
+ server = self.server
+ volume = self.volume
+
self.servers_client.stop(server['id'])
self.servers_client.wait_for_server_status(server['id'], 'SHUTOFF')
@@ -101,10 +113,12 @@
self.ssh_user, server['adminPass'])
partitions = linux_client.get_partitions()
self.assertFalse(self.device in partitions)
+ except Exception:
+ self.fail("The test_attach_detach_volume is faild!")
finally:
- if attached:
+ if self.attached:
self._detach(server['id'], volume['id'])
- self._delete(server['id'], volume['id'])
+ self._delete(self.server, self.volume)
class AttachVolumeTestXML(AttachVolumeTestJSON):
diff --git a/tempest/tests/image/base.py b/tempest/tests/image/base.py
new file mode 100644
index 0000000..65d81b6
--- /dev/null
+++ b/tempest/tests/image/base.py
@@ -0,0 +1,94 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 IBM Corp.
+#
+# 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 logging
+import time
+
+from tempest import clients
+from tempest.common.utils.data_utils import rand_name
+from tempest import exceptions
+import tempest.test
+
+LOG = logging.getLogger(__name__)
+
+
+class BaseImageTest(tempest.test.BaseTestCase):
+ """Base test class for Image API tests."""
+
+ @classmethod
+ def setUpClass(cls):
+ cls.os = clients.Manager()
+ cls.created_images = []
+
+ @classmethod
+ def tearDownClass(cls):
+ for image_id in cls.created_images:
+ try:
+ cls.client.delete_image(image_id)
+ except exceptions.NotFound:
+ pass
+
+ for image_id in cls.created_images:
+ cls.client.wait_for_resource_deletion(image_id)
+
+ @classmethod
+ def create_image(cls, **kwargs):
+ """Wrapper that returns a test image."""
+ name = rand_name(cls.__name__ + "-instance")
+
+ if 'name' in kwargs:
+ name = kwargs.pop('name')
+
+ container_format = kwargs.pop('container_format')
+ disk_format = kwargs.pop('disk_format')
+
+ resp, image = cls.client.create_image(name, container_format,
+ disk_format, **kwargs)
+ cls.created_images.append(image['id'])
+ return resp, image
+
+ @classmethod
+ def _check_version(cls, version):
+ __, versions = cls.client.get_versions()
+ if version == 'v2.0':
+ if 'v2.0' in versions:
+ return True
+ elif version == 'v1.0':
+ if 'v1.1' in versions or 'v1.0' in versions:
+ return True
+ return False
+
+
+class BaseV1ImageTest(BaseImageTest):
+
+ @classmethod
+ def setUpClass(cls):
+ super(BaseV1ImageTest, cls).setUpClass()
+ cls.client = cls.os.image_client
+ if not cls._check_version('v1.0'):
+ msg = "Glance API v1 not supported"
+ raise cls.skipException(msg)
+
+
+class BaseV2ImageTest(BaseImageTest):
+
+ @classmethod
+ def setUpClass(cls):
+ super(BaseV2ImageTest, cls).setUpClass()
+ cls.client = cls.os.image_client_v2
+ if not cls._check_version('v2.0'):
+ msg = "Glance API v2 not supported"
+ raise cls.skipException(msg)
diff --git a/tempest/tests/image/v1/test_image_members.py b/tempest/tests/image/v1/test_image_members.py
index 30fa6c6..92052fc 100644
--- a/tempest/tests/image/v1/test_image_members.py
+++ b/tempest/tests/image/v1/test_image_members.py
@@ -17,42 +17,32 @@
import cStringIO as StringIO
from tempest import clients
-import tempest.test
+from tempest.tests.image import base
-class ImageMembersTests(tempest.test.BaseTestCase):
+class ImageMembersTests(base.BaseV1ImageTest):
@classmethod
def setUpClass(cls):
- cls.os = clients.Manager()
- cls.client = cls.os.image_client
+ super(ImageMembersTests, cls).setUpClass()
admin = clients.AdminManager(interface='json')
cls.admin_client = admin.identity_client
- cls.created_images = []
cls.tenants = cls._get_tenants()
@classmethod
- def tearDownClass(cls):
- for image_id in cls.created_images:
- cls.client.delete_image(image_id)
- cls.client.wait_for_resource_deletion(image_id)
-
- @classmethod
def _get_tenants(cls):
resp, tenants = cls.admin_client.list_tenants()
tenants = map(lambda x: x['id'], tenants)
return tenants
- def _create_image(self, name=None):
+ def _create_image(self):
image_file = StringIO.StringIO('*' * 1024)
- if name is not None:
- name = 'New Standard Image with Members'
- resp, image = self.client.create_image(name,
- 'bare', 'raw',
- is_public=True, data=image_file)
+ resp, image = self.create_image(container_format='bare',
+ disk_format='raw',
+ is_public=True,
+ data=image_file)
self.assertEquals(201, resp.status)
image_id = image['id']
- self.created_images.append(image_id)
return image_id
def test_add_image_member(self):
@@ -69,8 +59,7 @@
image = self._create_image()
resp = self.client.add_member(self.tenants[0], image)
self.assertEquals(204, resp.status)
- name = 'Shared Image'
- share_image = self._create_image(name=name)
+ share_image = self._create_image()
resp = self.client.add_member(self.tenants[0], share_image)
self.assertEquals(204, resp.status)
resp, body = self.client.get_shared_images(self.tenants[0])
@@ -81,8 +70,7 @@
self.assertIn(image, images)
def test_remove_member(self):
- name = 'Shared Image for Delete Test'
- image_id = self._create_image(name=name)
+ image_id = self._create_image()
resp = self.client.add_member(self.tenants[0], image_id)
self.assertEquals(204, resp.status)
resp = self.client.delete_member(self.tenants[0], image_id)
diff --git a/tempest/tests/image/v1/test_images.py b/tempest/tests/image/v1/test_images.py
index 84bb650..af09b79 100644
--- a/tempest/tests/image/v1/test_images.py
+++ b/tempest/tests/image/v1/test_images.py
@@ -19,26 +19,12 @@
from tempest import clients
from tempest import exceptions
-import tempest.test
from tempest.test import attr
+from tempest.tests.image import base
-class CreateRegisterImagesTest(tempest.test.BaseTestCase):
-
- """
- Here we test the registration and creation of images
- """
-
- @classmethod
- def setUpClass(cls):
- cls.os = clients.Manager()
- cls.client = cls.os.image_client
- cls.created_images = []
-
- @classmethod
- def tearDownClass(cls):
- for image_id in cls.created_images:
- cls.client.delete(image_id)
+class CreateRegisterImagesTest(base.BaseV1ImageTest):
+ """Here we test the registration and creation of images."""
@attr(type='negative')
def test_register_with_invalid_container_format(self):
@@ -55,19 +41,17 @@
def test_register_then_upload(self):
# Register, then upload an image
properties = {'prop1': 'val1'}
- resp, body = self.client.create_image('New Name', 'bare', 'raw',
- is_public=True,
- properties=properties)
+ resp, body = self.create_image(name='New Name',
+ container_format='bare',
+ disk_format='raw',
+ is_public=True,
+ properties=properties)
self.assertTrue('id' in body)
image_id = body.get('id')
self.created_images.append(image_id)
- self.assertTrue('name' in body)
self.assertEqual('New Name', body.get('name'))
- self.assertTrue('is_public' in body)
self.assertTrue(body.get('is_public'))
- self.assertTrue('status' in body)
self.assertEqual('queued', body.get('status'))
- self.assertTrue('properties' in body)
for key, val in properties.items():
self.assertEqual(val, body.get('properties')[key])
@@ -80,22 +64,20 @@
@attr(type='image')
def test_register_remote_image(self):
# Register a new remote image
- resp, body = self.client.create_image('New Remote Image', 'bare',
- 'raw', is_public=True,
- location='http://example.com'
- '/someimage.iso')
+ resp, body = self.create_image(name='New Remote Image',
+ container_format='bare',
+ disk_format='raw', is_public=True,
+ location='http://example.com'
+ '/someimage.iso')
self.assertTrue('id' in body)
image_id = body.get('id')
self.created_images.append(image_id)
- self.assertTrue('name' in body)
self.assertEqual('New Remote Image', body.get('name'))
- self.assertTrue('is_public' in body)
self.assertTrue(body.get('is_public'))
- self.assertTrue('status' in body)
self.assertEqual('active', body.get('status'))
-class ListImagesTest(tempest.test.BaseTestCase):
+class ListImagesTest(base.BaseV1ImageTest):
"""
Here we test the listing of image information
@@ -103,9 +85,7 @@
@classmethod
def setUpClass(cls):
- cls.os = clients.Manager()
- cls.client = cls.os.image_client
- cls.created_images = []
+ super(ListImagesTest, cls).setUpClass()
# We add a few images here to test the listing functionality of
# the images API
@@ -132,12 +112,6 @@
cls.dup_set = set((img3, img4))
@classmethod
- def tearDownClass(cls):
- for image_id in cls.created_images:
- cls.client.delete_image(image_id)
- cls.client.wait_for_resource_deletion(image_id)
-
- @classmethod
def _create_remote_image(cls, name, container_format, disk_format):
"""
Create a new remote image and return the ID of the newly-registered
@@ -145,12 +119,12 @@
"""
name = 'New Remote Image %s' % name
location = 'http://example.com/someimage_%s.iso' % name
- resp, image = cls.client.create_image(name,
- container_format, disk_format,
- is_public=True,
- location=location)
+ resp, image = cls.create_image(name=name,
+ container_format=container_format,
+ disk_format=disk_format,
+ is_public=True,
+ location=location)
image_id = image['id']
- cls.created_images.append(image_id)
return image_id
@classmethod
@@ -163,11 +137,11 @@
"""
image_file = StringIO.StringIO('*' * size)
name = 'New Standard Image %s' % name
- resp, image = cls.client.create_image(name,
- container_format, disk_format,
- is_public=True, data=image_file)
+ resp, image = cls.create_image(name=name,
+ container_format=container_format,
+ disk_format=disk_format,
+ is_public=True, data=image_file)
image_id = image['id']
- cls.created_images.append(image_id)
return image_id
@attr(type='image')
diff --git a/tempest/tests/image/v2/test_images.py b/tempest/tests/image/v2/test_images.py
index 9abf28d..19a7a95 100644
--- a/tempest/tests/image/v2/test_images.py
+++ b/tempest/tests/image/v2/test_images.py
@@ -21,27 +21,16 @@
from tempest import clients
from tempest import exceptions
-import tempest.test
from tempest.test import attr
+from tempest.tests.image import base
-class CreateRegisterImagesTest(tempest.test.BaseTestCase):
+class CreateRegisterImagesTest(base.BaseV2ImageTest):
"""
Here we test the registration and creation of images
"""
- @classmethod
- def setUpClass(cls):
- cls.os = clients.Manager()
- cls.client = cls.os.image_client_v2
- cls.created_images = []
-
- @classmethod
- def tearDownClass(cls):
- for image_id in cls.created_images:
- cls.client.delete(image_id)
-
@attr(type='negative')
def test_register_with_invalid_container_format(self):
# Negative tests for invalid data supplied to POST /images
@@ -56,8 +45,10 @@
@attr(type='image')
def test_register_then_upload(self):
# Register, then upload an image
- resp, body = self.client.create_image('New Name', 'bare', 'raw',
- is_public=True)
+ resp, body = self.create_image(name='New Name',
+ container_format='bare',
+ disk_format='raw',
+ visibility='public')
self.assertTrue('id' in body)
image_id = body.get('id')
self.created_images.append(image_id)
@@ -77,7 +68,7 @@
self.assertEqual(1024, body.get('size'))
-class ListImagesTest(tempest.test.BaseTestCase):
+class ListImagesTest(base.BaseV2ImageTest):
"""
Here we test the listing of image information
@@ -85,22 +76,13 @@
@classmethod
def setUpClass(cls):
- cls.os = clients.Manager()
- cls.client = cls.os.image_client_v2
- cls.created_images = []
-
+ super(ListImagesTest, cls).setUpClass()
# We add a few images here to test the listing functionality of
# the images API
for x in xrange(0, 10):
cls.created_images.append(cls._create_standard_image(x))
@classmethod
- def tearDownClass(cls):
- for image_id in cls.created_images:
- cls.client.delete_image(image_id)
- cls.client.wait_for_resource_deletion(image_id)
-
- @classmethod
def _create_standard_image(cls, number):
"""
Create a new standard image and return the ID of the newly-registered
@@ -109,8 +91,9 @@
"""
image_file = StringIO.StringIO('*' * random.randint(1024, 4096))
name = 'New Standard Image %s' % number
- resp, body = cls.client.create_image(name, 'bare', 'raw',
- is_public=True)
+ resp, body = cls.create_image(name=name, container_format='bare',
+ disk_format='raw',
+ visibility='public')
image_id = body['id']
resp, body = cls.client.store_image(image_id, data=image_file)
diff --git a/tools/skip_tracker.py b/tools/skip_tracker.py
index e890e92..12d29b0 100755
--- a/tools/skip_tracker.py
+++ b/tools/skip_tracker.py
@@ -89,6 +89,7 @@
results = find_skips()
unique_bugs = sorted(set([bug for (method, bug) in results]))
unskips = []
+ duplicates = []
info("Total bug skips found: %d", len(results))
info("Total unique bugs causing skips: %d", len(unique_bugs))
lp = launchpad.Launchpad.login_anonymously('grabbing bugs',
@@ -96,12 +97,26 @@
LPCACHEDIR)
for bug_no in unique_bugs:
bug = lp.bugs[bug_no]
+ duplicate = bug.duplicate_of_link
+ if duplicate is not None:
+ dup_id = duplicate.split('/')[-1]
+ duplicates.append((bug_no, dup_id))
for task in bug.bug_tasks:
info("Bug #%7s (%12s - %12s)", bug_no,
task.importance, task.status)
if task.status in ('Fix Released', 'Fix Committed'):
unskips.append(bug_no)
+ for bug_id, dup_id in duplicates:
+ if bug_id not in unskips:
+ dup_bug = lp.bugs[dup_id]
+ for task in dup_bug.bug_tasks:
+ info("Bug #%7s is a duplicate of Bug#%7s (%12s - %12s)",
+ bug_id, dup_id, task.importance, task.status)
+ if task.status in ('Fix Released', 'Fix Committed'):
+ unskips.append(bug_id)
+
+ unskips = sorted(set(unskips))
if unskips:
print "The following bugs have been fixed and the corresponding skips"
print "should be removed from the test cases:"