Merge "Updating the try/except blocks to assertRaises."
diff --git a/cli/__init__.py b/cli/__init__.py
index 2548f24..6ffe229 100644
--- a/cli/__init__.py
+++ b/cli/__init__.py
@@ -21,6 +21,7 @@
from oslo.config import cfg
+import cli.output_parser
import tempest.test
@@ -51,6 +52,7 @@
super(ClientTestBase, cls).setUpClass()
def __init__(self, *args, **kwargs):
+ self.parser = cli.output_parser
super(ClientTestBase, self).__init__(*args, **kwargs)
def nova(self, action, flags='', params='', admin=True, fail_ok=False):
@@ -58,6 +60,16 @@
return self.cmd_with_auth(
'nova', action, flags, params, admin, fail_ok)
+ def nova_manage(self, action, flags='', params='', fail_ok=False):
+ """Executes nova-manage command for the given action."""
+ return self.cmd(
+ 'nova-manage', action, flags, params, fail_ok)
+
+ def keystone(self, action, flags='', params='', admin=True, fail_ok=False):
+ """Executes keystone command for the given action."""
+ return self.cmd_with_auth(
+ 'keystone', action, flags, params, admin, fail_ok)
+
def cmd_with_auth(self, cmd, action, flags='', params='',
admin=True, fail_ok=False):
"""Executes given command with auth attributes appended."""
@@ -81,3 +93,9 @@
LOG.error("command output:\n%s" % e.output)
raise
return result
+
+ def assertTableStruct(self, items, field_names):
+ """Verify that all items has keys listed in field_names."""
+ for item in items:
+ for field in field_names:
+ self.assertIn(field, item)
diff --git a/cli/output_parser.py b/cli/output_parser.py
new file mode 100644
index 0000000..840839b
--- /dev/null
+++ b/cli/output_parser.py
@@ -0,0 +1,168 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack Foundation
+# 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.
+
+"""Collection of utilities for parsing CLI clients output."""
+
+
+import logging
+import re
+
+
+LOG = logging.getLogger(__name__)
+
+
+delimiter_line = re.compile('^\+\-[\+\-]+\-\+$')
+
+
+def details_multiple(output_lines, with_label=False):
+ """Return list of dicts with item details from cli output tables.
+
+ If with_label is True, key '__label' is added to each items dict.
+ For more about 'label' see OutputParser.tables().
+ """
+ items = []
+ tables_ = tables(output_lines)
+ for table_ in tables_:
+ if 'Property' not in table_['headers'] \
+ or 'Value' not in table_['headers']:
+ raise Exception('Invalid structure of table with details')
+ item = {}
+ for value in table_['values']:
+ item[value[0]] = value[1]
+ if with_label:
+ item['__label'] = table_['label']
+ items.append(item)
+ return items
+
+
+def details(output_lines, with_label=False):
+ """Return dict with details of first item (table) found in output."""
+ items = details_multiple(output_lines, with_label)
+ return items[0]
+
+
+def listing(output_lines):
+ """Return list of dicts with basic item info parsed from cli output.
+ """
+
+ items = []
+ table_ = table(output_lines)
+ for row in table_['values']:
+ item = {}
+ for col_idx, col_key in enumerate(table_['headers']):
+ item[col_key] = row[col_idx]
+ items.append(item)
+ return items
+
+
+def tables(output_lines):
+ """Find all ascii-tables in output and parse them.
+
+ Return list of tables parsed from cli output as dicts.
+ (see OutputParser.table())
+
+ And, if found, label key (separated line preceding the table)
+ is added to each tables dict.
+ """
+ tables_ = []
+
+ table_ = []
+ label = None
+
+ start = False
+ header = False
+
+ if not isinstance(output_lines, list):
+ output_lines = output_lines.split('\n')
+
+ for line in output_lines:
+ if delimiter_line.match(line):
+ if not start:
+ start = True
+ elif not header:
+ # we are after head area
+ header = True
+ else:
+ # table ends here
+ start = header = None
+ table_.append(line)
+
+ parsed = table(table_)
+ parsed['label'] = label
+ tables_.append(parsed)
+
+ table_ = []
+ label = None
+ continue
+ if start:
+ table_.append(line)
+ else:
+ if label is None:
+ label = line
+ else:
+ LOG.warn('Invalid line between tables: %s' % line)
+ if len(table_) > 0:
+ LOG.warn('Missing end of table')
+
+ return tables_
+
+
+def table(output_lines):
+ """Parse single table from cli output.
+
+ Return dict with list of column names in 'headers' key and
+ rows in 'values' key.
+ """
+ table_ = {'headers': [], 'values': []}
+ columns = None
+
+ if not isinstance(output_lines, list):
+ output_lines = output_lines.split('\n')
+
+ for line in output_lines:
+ if delimiter_line.match(line):
+ columns = _table_columns(line)
+ continue
+ if '|' not in line:
+ LOG.warn('skipping invalid table line: %s' % line)
+ continue
+ row = []
+ for col in columns:
+ row.append(line[col[0]:col[1]].strip())
+ if table_['headers']:
+ table_['values'].append(row)
+ else:
+ table_['headers'] = row
+
+ return table_
+
+
+def _table_columns(first_table_row):
+ """Find column ranges in output line.
+
+ Return list of touples (start,end) for each column
+ detected by plus (+) characters in delimiter line.
+ """
+ positions = []
+ start = 1 # there is '+' at 0
+ while start < len(first_table_row):
+ end = first_table_row.find('+', start)
+ if end == -1:
+ break
+ positions.append((start, end))
+ start = end + 1
+ return positions
diff --git a/cli/simple_read_only/test_compute.py b/cli/simple_read_only/test_compute.py
index bcdd2c5..43c3c45 100644
--- a/cli/simple_read_only/test_compute.py
+++ b/cli/simple_read_only/test_compute.py
@@ -56,12 +56,13 @@
def test_admin_absolute_limites(self):
self.nova('absolute-limits')
+ self.nova('absolute-limits', params='--reserved')
def test_admin_aggregate_list(self):
self.nova('aggregate-list')
def test_admin_availability_zone_list(self):
- self.nova('availability-zone-list')
+ self.assertIn("internal", self.nova('availability-zone-list'))
def test_admin_cloudpipe_list(self):
self.nova('cloudpipe-list')
@@ -72,7 +73,7 @@
def test_admin_dns_domains(self):
self.nova('dns-domains')
- @testtools.skip("needs parameters")
+ @testtools.skip("Test needs parameters, Bug: 1157349")
def test_admin_dns_list(self):
self.nova('dns-list')
@@ -90,7 +91,7 @@
params='--flavor m1.tiny')
def test_admin_flavor_list(self):
- self.nova('flavor-list')
+ self.assertIn("Memory_MB", self.nova('flavor-list'))
def test_admin_floating_ip_bulk_list(self):
self.nova('floating-ip-bulk-list')
@@ -110,7 +111,7 @@
def test_admin_image_list(self):
self.nova('image-list')
- @testtools.skip("needs parameters")
+ @testtools.skip("Test needs parameters, Bug: 1157349")
def test_admin_interface_list(self):
self.nova('interface-list')
@@ -135,7 +136,7 @@
def test_admin_secgroup_list(self):
self.nova('secgroup-list')
- @testtools.skip("needs parameters")
+ @testtools.skip("Test needs parameters, Bug: 1157349")
def test_admin_secgroup_list_rules(self):
self.nova('secgroup-list-rules')
diff --git a/cli/simple_read_only/test_compute_manage.py b/cli/simple_read_only/test_compute_manage.py
new file mode 100644
index 0000000..17b3bf6
--- /dev/null
+++ b/cli/simple_read_only/test_compute_manage.py
@@ -0,0 +1,61 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack Foundation
+# 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 logging
+import subprocess
+
+import testtools
+
+import cli
+
+
+LOG = logging.getLogger(__name__)
+
+
+class SimpleReadOnlyNovaManageTest(cli.ClientTestBase):
+
+ """
+ This is a first pass at a simple read only nova-manage test. This
+ only exercises client commands that are read only.
+
+ This should test commands:
+ * with and without optional parameters
+ * initially just check return codes, and later test command outputs
+
+ """
+
+ def test_admin_fake_action(self):
+ self.assertRaises(subprocess.CalledProcessError,
+ self.nova_manage,
+ 'this-does-nova-exist')
+
+ #NOTE(jogo): Commands in order listed in 'nova-manage -h'
+
+ # test flags
+ def test_help_flag(self):
+ self.nova_manage('', '-h')
+
+ @testtools.skip("version is empty, bug 1138844")
+ def test_version_flag(self):
+ self.assertNotEqual("", self.nova_manage('', '--version'))
+
+ # test actions
+ def test_version(self):
+ self.assertNotEqual("", self.nova_manage('version'))
+
+ def test_flavor_list(self):
+ self.assertNotEqual("", self.nova_manage('flavor list'))
diff --git a/cli/simple_read_only/test_keystone.py b/cli/simple_read_only/test_keystone.py
new file mode 100644
index 0000000..4b14c3c
--- /dev/null
+++ b/cli/simple_read_only/test_keystone.py
@@ -0,0 +1,109 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack Foundation
+# 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 logging
+import re
+import subprocess
+
+import cli
+
+
+LOG = logging.getLogger(__name__)
+
+
+class SimpleReadOnlyKeystoneClientTest(cli.ClientTestBase):
+ """Basic, read-only tests for Keystone CLI client.
+
+ Checks return values and output of read-only commands.
+ These tests do not presume any content, nor do they create
+ their own. They only verify the structure of output if present.
+ """
+
+ def test_admin_fake_action(self):
+ self.assertRaises(subprocess.CalledProcessError,
+ self.keystone,
+ 'this-does-not-exist')
+
+ def test_admin_catalog_list(self):
+ out = self.keystone('catalog')
+ catalog = self.parser.details_multiple(out, with_label=True)
+ for svc in catalog:
+ self.assertTrue(svc['__label'].startswith('Service:'))
+
+ def test_admin_endpoint_list(self):
+ out = self.keystone('endpoint-list')
+ endpoints = self.parser.listing(out)
+ self.assertTableStruct(endpoints, [
+ 'id', 'region', 'publicurl', 'internalurl',
+ 'adminurl', 'service_id'])
+
+ def test_admin_endpoint_service_match(self):
+ endpoints = self.parser.listing(self.keystone('endpoint-list'))
+ services = self.parser.listing(self.keystone('service-list'))
+ svc_by_id = {}
+ for svc in services:
+ svc_by_id[svc['id']] = svc
+ for endpoint in endpoints:
+ self.assertIn(endpoint['service_id'], svc_by_id)
+
+ def test_admin_role_list(self):
+ roles = self.parser.listing(self.keystone('role-list'))
+ self.assertTableStruct(roles, ['id', 'name'])
+
+ def test_admin_service_list(self):
+ services = self.parser.listing(self.keystone('service-list'))
+ self.assertTableStruct(services, ['id', 'name', 'type', 'description'])
+
+ def test_admin_tenant_list(self):
+ tenants = self.parser.listing(self.keystone('tenant-list'))
+ self.assertTableStruct(tenants, ['id', 'name', 'enabled'])
+
+ def test_admin_user_list(self):
+ users = self.parser.listing(self.keystone('user-list'))
+ self.assertTableStruct(users, [
+ 'id', 'name', 'enabled', 'email'])
+
+ def test_admin_user_role_list(self):
+ user_roles = self.parser.listing(self.keystone('user-role-list'))
+ self.assertTableStruct(user_roles, [
+ 'id', 'name', 'user_id', 'tenant_id'])
+
+ def test_admin_discover(self):
+ discovered = self.keystone('discover')
+ self.assertIn('Keystone found at http', discovered)
+ self.assertIn('supports version', discovered)
+
+ def test_admin_help(self):
+ help_text = self.keystone('help')
+ lines = help_text.split('\n')
+ self.assertTrue(lines[0].startswith('usage: keystone'))
+
+ commands = []
+ cmds_start = lines.index('Positional arguments:')
+ cmds_end = lines.index('Optional arguments:')
+ command_pattern = re.compile('^ {4}([a-z0-9\-\_]+)')
+ for line in lines[cmds_start:cmds_end]:
+ match = command_pattern.match(line)
+ if match:
+ commands.append(match.group(1))
+ commands = set(commands)
+ wanted_commands = set(('catalog', 'endpoint-list', 'help',
+ 'token-get', 'discover', 'bootstrap'))
+ self.assertFalse(wanted_commands - commands)
+
+ def test_admin_bashcompletion(self):
+ self.keystone('bash-completion')
diff --git a/tempest/common/rest_client.py b/tempest/common/rest_client.py
index 6147aa1..d68b9ed 100644
--- a/tempest/common/rest_client.py
+++ b/tempest/common/rest_client.py
@@ -15,6 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import collections
import hashlib
import httplib2
import json
@@ -60,6 +61,8 @@
'location', 'proxy-authenticate',
'retry-after', 'server',
'vary', 'www-authenticate'))
+ dscv = self.config.identity.disable_ssl_certificate_validation
+ self.http_obj = httplib2.Http(disable_ssl_certificate_validation=dscv)
def _set_auth(self):
"""
@@ -105,8 +108,6 @@
params['headers'] = {'User-Agent': 'Test-Client', 'X-Auth-User': user,
'X-Auth-Key': password}
- dscv = self.config.identity.disable_ssl_certificate_validation
- self.http_obj = httplib2.Http(disable_ssl_certificate_validation=dscv)
resp, body = self.http_obj.request(auth_url, 'GET', **params)
try:
return resp['x-auth-token'], resp['x-server-management-url']
@@ -132,8 +133,6 @@
}
}
- dscv = self.config.identity.disable_ssl_certificate_validation
- self.http_obj = httplib2.Http(disable_ssl_certificate_validation=dscv)
headers = {'Content-Type': 'application/json'}
body = json.dumps(creds)
resp, body = self.http_obj.request(auth_url, 'POST',
@@ -259,23 +258,14 @@
# Usually RFC2616 says error responses SHOULD contain an explanation.
# The warning is normal for SHOULD/SHOULD NOT case
- # Likely it will cause error
- if not body and resp.status >= 400:
+ # Likely it will cause an error
+ if not resp_body and resp.status >= 400:
self.LOG.warning("status >= 400 response with empty body")
- def request(self, method, url,
- headers=None, body=None, depth=0):
+ def _request(self, method, url,
+ headers=None, body=None):
"""A simple HTTP request interface."""
- if (self.token is None) or (self.base_url is None):
- self._set_auth()
-
- dscv = self.config.identity.disable_ssl_certificate_validation
- self.http_obj = httplib2.Http(disable_ssl_certificate_validation=dscv)
- if headers is None:
- headers = {}
- headers['X-Auth-Token'] = self.token
-
req_url = "%s/%s" % (self.base_url, url)
self._log_request(method, req_url, headers, body)
resp, resp_body = self.http_obj.request(req_url, method,
@@ -283,12 +273,37 @@
self._log_response(resp, resp_body)
self.response_checker(method, url, headers, body, resp, resp_body)
- self._error_checker(method, url, headers, body, resp, resp_body, depth)
+ return resp, resp_body
+ def request(self, method, url,
+ headers=None, body=None):
+ retry = 0
+ if (self.token is None) or (self.base_url is None):
+ self._set_auth()
+
+ if headers is None:
+ headers = {}
+ headers['X-Auth-Token'] = self.token
+
+ resp, resp_body = self._request(method, url,
+ headers=headers, body=body)
+
+ while (resp.status == 413 and
+ 'retry-after' in resp and
+ not self.is_absolute_limit(
+ resp, self._parse_resp(resp_body)) and
+ retry < MAX_RECURSION_DEPTH):
+ retry += 1
+ delay = int(resp['retry-after'])
+ time.sleep(delay)
+ resp, resp_body = self._request(method, url,
+ headers=headers, body=body)
+ self._error_checker(method, url, headers, body,
+ resp, resp_body)
return resp, resp_body
def _error_checker(self, method, url,
- headers, body, resp, resp_body, depth=0):
+ headers, body, resp, resp_body):
# NOTE(mtreinish): Check for httplib response from glance_http. The
# object can't be used here because importing httplib breaks httplib2.
@@ -340,9 +355,10 @@
if resp.status == 413:
if parse_resp:
resp_body = self._parse_resp(resp_body)
- #Checking whether Absolute/Rate limit
- return self.check_over_limit(resp_body, method, url, headers, body,
- depth)
+ if self.is_absolute_limit(resp, resp_body):
+ raise exceptions.OverLimit(resp_body)
+ else:
+ raise exceptions.RateLimitExceeded(resp_body)
if resp.status == 422:
if parse_resp:
@@ -372,34 +388,14 @@
resp_body = self._parse_resp(resp_body)
raise exceptions.RestClientException(str(resp.status))
- def check_over_limit(self, resp_body, method, url,
- headers, body, depth):
- self.is_absolute_limit(resp_body['overLimit'])
- return self.is_rate_limit_retry_max_recursion_depth(
- resp_body['overLimit'], method, url, headers,
- body, depth)
-
- def is_absolute_limit(self, resp_body):
- if 'exceeded' in resp_body['message']:
- raise exceptions.OverLimit(resp_body['message'])
- else:
- return
-
- def is_rate_limit_retry_max_recursion_depth(self, resp_body, method,
- url, headers, body, depth):
- if 'retryAfter' in resp_body:
- if depth < MAX_RECURSION_DEPTH:
- delay = resp_body['retryAfter']
- time.sleep(int(delay))
- return self.request(method, url, headers=headers,
- body=body,
- depth=depth + 1)
- else:
- raise exceptions.RateLimitExceeded(
- message=resp_body['overLimitFault']['message'],
- details=resp_body['overLimitFault']['details'])
- else:
- raise exceptions.OverLimit(resp_body['message'])
+ def is_absolute_limit(self, resp, resp_body):
+ if (not isinstance(resp_body, collections.Mapping) or
+ 'retry-after' not in resp):
+ return True
+ over_limit = resp_body.get('overLimit', None)
+ if not over_limit:
+ return True
+ return 'exceed' in over_limit.get('message', 'blabla')
def wait_for_resource_deletion(self, id):
"""Waits for a resource to be deleted."""
@@ -426,9 +422,8 @@
def _parse_resp(self, body):
return xml_to_json(etree.fromstring(body))
- def check_over_limit(self, resp_body, method, url,
- headers, body, depth):
- self.is_absolute_limit(resp_body)
- return self.is_rate_limit_retry_max_recursion_depth(
- resp_body, method, url, headers,
- body, depth)
+ def is_absolute_limit(self, resp, resp_body):
+ if (not isinstance(resp_body, collections.Mapping) or
+ 'retry-after' not in resp):
+ return True
+ return 'exceed' in resp_body.get('message', 'blabla')
diff --git a/tempest/services/volume/xml/admin/volume_types_client.py b/tempest/services/volume/xml/admin/volume_types_client.py
index ba4ba67..49cbadb 100644
--- a/tempest/services/volume/xml/admin/volume_types_client.py
+++ b/tempest/services/volume/xml/admin/volume_types_client.py
@@ -149,11 +149,16 @@
url = "types/%s/extra_specs" % str(vol_type_id)
extra_specs = Element("extra_specs", xmlns=XMLNS_11)
if extra_spec:
- for key, value in extra_spec.items():
- spec = Element('extra_spec')
- spec.add_attr('key', key)
- spec.append(Text(value))
- extra_specs.append(spec)
+ if isinstance(extra_spec, list):
+ extra_specs.append(extra_spec)
+ else:
+ for key, value in extra_spec.items():
+ spec = Element('extra_spec')
+ spec.add_attr('key', key)
+ spec.append(Text(value))
+ extra_specs.append(spec)
+ else:
+ extra_specs = None
resp, body = self.post(url, str(Document(extra_specs)),
self.headers)
diff --git a/tempest/tests/boto/test_s3_objects.py b/tempest/tests/boto/test_s3_objects.py
index d50dc45..c735215 100644
--- a/tempest/tests/boto/test_s3_objects.py
+++ b/tempest/tests/boto/test_s3_objects.py
@@ -35,7 +35,6 @@
cls.os = clients.Manager()
cls.client = cls.os.s3_client
- @testtools.skip("Skipped until the Bug #1076534 is resolved")
@attr(type='smoke')
def test_create_get_delete_object(self):
# S3 Create, get and delete object
diff --git a/tempest/tests/compute/floating_ips/test_floating_ips_actions.py b/tempest/tests/compute/floating_ips/test_floating_ips_actions.py
index 0ff81e1..888481a 100644
--- a/tempest/tests/compute/floating_ips/test_floating_ips_actions.py
+++ b/tempest/tests/compute/floating_ips/test_floating_ips_actions.py
@@ -33,10 +33,7 @@
cls.servers_client = cls.servers_client
#Server creation
- resp, server = cls.servers_client.create_server('floating-server',
- cls.image_ref,
- cls.flavor_ref)
- cls.servers_client.wait_for_server_status(server['id'], 'ACTIVE')
+ resp, server = cls.create_server(wait_until='ACTIVE')
cls.server_id = server['id']
resp, body = cls.servers_client.get_server(server['id'])
#Floating IP creation
@@ -55,10 +52,8 @@
@classmethod
def tearDownClass(cls):
- super(FloatingIPsTestJSON, cls).tearDownClass()
- #Deleting the server which is created in this method
- resp, body = cls.servers_client.delete_server(cls.server_id)
#Deleting the floating IP which is created in this method
+ super(FloatingIPsTestJSON, cls).tearDownClass()
resp, body = cls.client.delete_floating_ip(cls.floating_ip_id)
@attr(type='positive')
diff --git a/tempest/tests/compute/images/test_image_metadata.py b/tempest/tests/compute/images/test_image_metadata.py
index 311ee8e..918075c 100644
--- a/tempest/tests/compute/images/test_image_metadata.py
+++ b/tempest/tests/compute/images/test_image_metadata.py
@@ -30,14 +30,9 @@
cls.servers_client = cls.servers_client
cls.client = cls.images_client
- name = rand_name('server')
- resp, server = cls.servers_client.create_server(name, cls.image_ref,
- cls.flavor_ref)
+ resp, server = cls.create_server(wait_until='ACTIVE')
cls.server_id = server['id']
- #Wait for the server to become active
- cls.servers_client.wait_for_server_status(cls.server_id, 'ACTIVE')
-
# Snapshot the server once to save time
name = rand_name('image')
resp, _ = cls.client.create_image(cls.server_id, name, {})
@@ -49,7 +44,6 @@
@classmethod
def tearDownClass(cls):
cls.client.delete_image(cls.image_id)
- cls.servers_client.delete_server(cls.server_id)
super(ImagesMetadataTest, cls).tearDownClass()
def setUp(self):
diff --git a/tempest/tests/compute/images/test_images.py b/tempest/tests/compute/images/test_images.py
index a61cef6..fb0364a 100644
--- a/tempest/tests/compute/images/test_images.py
+++ b/tempest/tests/compute/images/test_images.py
@@ -56,6 +56,14 @@
self.image_ids.remove(image_id)
super(ImagesTestJSON, self).tearDown()
+ def __create_image__(self, server_id, name, meta=None):
+ resp, body = self.client.create_image(server_id, name, meta)
+ image_id = parse_image_id(resp['location'])
+ self.client.wait_for_image_resp_code(image_id, 200)
+ self.client.wait_for_image_status(image_id, 'ACTIVE')
+ self.image_ids.append(image_id)
+ return resp, body
+
@attr(type='negative')
def test_create_image_from_deleted_server(self):
# An image should not be created if the server instance is removed
@@ -63,43 +71,24 @@
# Delete server before trying to create server
self.servers_client.delete_server(server['id'])
-
- try:
- # Create a new image after server is deleted
- name = rand_name('image')
- meta = {'image_type': 'test'}
- resp, body = self.client.create_image(server['id'], name, meta)
-
- except Exception:
- pass
-
- else:
- image_id = parse_image_id(resp['location'])
- self.client.wait_for_image_resp_code(image_id, 200)
- self.client.wait_for_image_status(image_id, 'ACTIVE')
- self.client.delete_image(image_id)
- self.fail("Should not create snapshot from deleted instance!")
+ self.servers_client.wait_for_server_termination(server['id'])
+ # Create a new image after server is deleted
+ name = rand_name('image')
+ meta = {'image_type': 'test'}
+ self.assertRaises(exceptions.NotFound,
+ self.__create_image__,
+ server['id'], name, meta)
@attr(type='negative')
def test_create_image_from_invalid_server(self):
# An image should not be created with invalid server id
- try:
- # Create a new image with invalid server id
- name = rand_name('image')
- meta = {'image_type': 'test'}
- resp = {}
- resp['status'] = None
- resp, body = self.client.create_image('!@#$%^&*()', name, meta)
-
- except exceptions.NotFound:
- pass
-
- finally:
- if (resp['status'] is not None):
- image_id = parse_image_id(resp['location'])
- resp, _ = self.client.delete_image(image_id)
- self.fail("An image should not be created "
- "with invalid server id")
+ # Create a new image with invalid server id
+ name = rand_name('image')
+ meta = {'image_type': 'test'}
+ resp = {}
+ resp['status'] = None
+ self.assertRaises(exceptions.NotFound, self.__create_image__,
+ '!@#$%^&*()', name, meta)
@attr(type='negative')
def test_create_image_when_server_is_terminating(self):
diff --git a/tempest/tests/compute/images/test_images_whitebox.py b/tempest/tests/compute/images/test_images_whitebox.py
index 8af812c..105a38a 100644
--- a/tempest/tests/compute/images/test_images_whitebox.py
+++ b/tempest/tests/compute/images/test_images_whitebox.py
@@ -36,15 +36,8 @@
@classmethod
def tearDownClass(cls):
- """Terminate test instances created after a test is executed."""
-
- for server in cls.servers:
- cls.update_state(server['id'], "active", None)
- resp, body = cls.servers_client.delete_server(server['id'])
- if resp['status'] == '204':
- cls.servers.remove(server)
- cls.servers_client.wait_for_server_termination(server['id'])
-
+ """Delete images after a test is executed."""
+ super(ImagesWhiteboxTest, cls).tearDownClass()
for image_id in cls.image_ids:
cls.client.delete_image(image_id)
cls.image_ids.remove(image_id)
diff --git a/tempest/tests/compute/keypairs/test_keypairs.py b/tempest/tests/compute/keypairs/test_keypairs.py
index b48b439..87c71aa 100644
--- a/tempest/tests/compute/keypairs/test_keypairs.py
+++ b/tempest/tests/compute/keypairs/test_keypairs.py
@@ -15,8 +15,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import testtools
-
from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
from tempest.test import attr
@@ -82,7 +80,6 @@
self.assertEqual(202, resp.status)
@attr(type='positive')
- @testtools.skip("Skipped until the Bug #980688 is resolved")
def test_get_keypair_detail(self):
# Keypair should be created, Got details by name and deleted
k_name = rand_name('keypair-')
@@ -138,7 +135,6 @@
self.client.create_keypair, k_name, pub_key)
@attr(type='negative')
- @testtools.skip("Skipped until the Bug #1086980 is resolved")
def test_keypair_delete_nonexistant_key(self):
# Non-existant key deletion should throw a proper error
k_name = rand_name("keypair-non-existant-")
diff --git a/tempest/tests/compute/servers/test_server_addresses.py b/tempest/tests/compute/servers/test_server_addresses.py
index 4807d1e..cb8e85e 100644
--- a/tempest/tests/compute/servers/test_server_addresses.py
+++ b/tempest/tests/compute/servers/test_server_addresses.py
@@ -29,16 +29,7 @@
super(ServerAddressesTest, cls).setUpClass()
cls.client = cls.servers_client
- cls.name = rand_name('server')
- resp, cls.server = cls.client.create_server(cls.name,
- cls.image_ref,
- cls.flavor_ref)
- cls.client.wait_for_server_status(cls.server['id'], 'ACTIVE')
-
- @classmethod
- def tearDownClass(cls):
- cls.client.delete_server(cls.server['id'])
- super(ServerAddressesTest, cls).tearDownClass()
+ resp, cls.server = cls.create_server(wait_until='ACTIVE')
@attr(type='negative', category='server-addresses')
def test_list_server_addresses_invalid_server_id(self):
diff --git a/tempest/tests/compute/servers/test_server_metadata.py b/tempest/tests/compute/servers/test_server_metadata.py
index bc523de..69c0ad9 100644
--- a/tempest/tests/compute/servers/test_server_metadata.py
+++ b/tempest/tests/compute/servers/test_server_metadata.py
@@ -32,18 +32,10 @@
resp, tenants = cls.admin_client.list_tenants()
cls.tenant_id = [tnt['id'] for tnt in tenants if tnt['name'] ==
cls.client.tenant_name][0]
- resp, server = cls.create_server(meta={})
+ resp, server = cls.create_server(meta={}, wait_until='ACTIVE')
cls.server_id = server['id']
- #Wait for the server to become active
- cls.client.wait_for_server_status(cls.server_id, 'ACTIVE')
-
- @classmethod
- def tearDownClass(cls):
- cls.client.delete_server(cls.server_id)
- super(ServerMetadataTestJSON, cls).tearDownClass()
-
def setUp(self):
super(ServerMetadataTestJSON, self).setUp()
meta = {'key1': 'value1', 'key2': 'value2'}
diff --git a/tempest/tests/compute/servers/test_servers.py b/tempest/tests/compute/servers/test_servers.py
index a8d28df..4796e86 100644
--- a/tempest/tests/compute/servers/test_servers.py
+++ b/tempest/tests/compute/servers/test_servers.py
@@ -28,111 +28,81 @@
super(ServersTestJSON, cls).setUpClass()
cls.client = cls.servers_client
+ def tearDown(self):
+ self.clear_servers()
+ super(ServersTestJSON, self).tearDown()
+
@attr(type='positive')
def test_create_server_with_admin_password(self):
# If an admin password is provided on server creation, the server's
# root password should be set to that password.
+ resp, server = self.create_server(adminPass='testpassword')
- try:
- server = None
- resp, server = self.create_server(adminPass='testpassword')
-
- #Verify the password is set correctly in the response
- self.assertEqual('testpassword', server['adminPass'])
-
- #Teardown
- finally:
- if server:
- self.client.delete_server(server['id'])
+ # Verify the password is set correctly in the response
+ self.assertEqual('testpassword', server['adminPass'])
def test_create_with_existing_server_name(self):
# Creating a server with a name that already exists is allowed
# TODO(sdague): clear out try, we do cleanup one layer up
- try:
- id1 = None
- id2 = None
- server_name = rand_name('server')
- resp, server = self.create_server(name=server_name,
- wait_until='ACTIVE')
- id1 = server['id']
- resp, server = self.create_server(name=server_name,
- wait_until='ACTIVE')
- id2 = server['id']
- self.assertNotEqual(id1, id2, "Did not create a new server")
- resp, server = self.client.get_server(id1)
- name1 = server['name']
- resp, server = self.client.get_server(id2)
- name2 = server['name']
- self.assertEqual(name1, name2)
- finally:
- for server_id in (id1, id2):
- if server_id:
- self.client.delete_server(server_id)
+ server_name = rand_name('server')
+ resp, server = self.create_server(name=server_name,
+ wait_until='ACTIVE')
+ id1 = server['id']
+ resp, server = self.create_server(name=server_name,
+ wait_until='ACTIVE')
+ id2 = server['id']
+ self.assertNotEqual(id1, id2, "Did not create a new server")
+ resp, server = self.client.get_server(id1)
+ name1 = server['name']
+ resp, server = self.client.get_server(id2)
+ name2 = server['name']
+ self.assertEqual(name1, name2)
@attr(type='positive')
def test_create_specify_keypair(self):
# Specify a keypair while creating a server
- try:
- server = None
- key_name = rand_name('key')
- resp, keypair = self.keypairs_client.create_keypair(key_name)
- resp, body = self.keypairs_client.list_keypairs()
- resp, server = self.create_server(key_name=key_name)
- self.assertEqual('202', resp['status'])
- self.client.wait_for_server_status(server['id'], 'ACTIVE')
- resp, server = self.client.get_server(server['id'])
- self.assertEqual(key_name, server['key_name'])
- finally:
- if server:
- self.client.delete_server(server['id'])
+ key_name = rand_name('key')
+ resp, keypair = self.keypairs_client.create_keypair(key_name)
+ resp, body = self.keypairs_client.list_keypairs()
+ resp, server = self.create_server(key_name=key_name)
+ self.assertEqual('202', resp['status'])
+ self.client.wait_for_server_status(server['id'], 'ACTIVE')
+ resp, server = self.client.get_server(server['id'])
+ self.assertEqual(key_name, server['key_name'])
@attr(type='positive')
def test_update_server_name(self):
# The server name should be changed to the the provided value
- try:
- server = None
- resp, server = self.create_server(wait_until='ACTIVE')
+ resp, server = self.create_server(wait_until='ACTIVE')
- #Update the server with a new name
- resp, server = self.client.update_server(server['id'],
- name='newname')
- self.assertEquals(200, resp.status)
- self.client.wait_for_server_status(server['id'], 'ACTIVE')
+ # Update the server with a new name
+ resp, server = self.client.update_server(server['id'],
+ name='newname')
+ self.assertEquals(200, resp.status)
+ self.client.wait_for_server_status(server['id'], 'ACTIVE')
- #Verify the name of the server has changed
- resp, server = self.client.get_server(server['id'])
- self.assertEqual('newname', server['name'])
-
- #Teardown
- finally:
- if server:
- self.client.delete_server(server['id'])
+ # Verify the name of the server has changed
+ resp, server = self.client.get_server(server['id'])
+ self.assertEqual('newname', server['name'])
@attr(type='positive')
def test_update_access_server_address(self):
# The server's access addresses should reflect the provided values
- try:
- server = None
- resp, server = self.create_server(wait_until='ACTIVE')
+ resp, server = self.create_server(wait_until='ACTIVE')
- #Update the IPv4 and IPv6 access addresses
- resp, body = self.client.update_server(server['id'],
- accessIPv4='1.1.1.1',
- accessIPv6='::babe:202:202')
- self.assertEqual(200, resp.status)
- self.client.wait_for_server_status(server['id'], 'ACTIVE')
+ # Update the IPv4 and IPv6 access addresses
+ resp, body = self.client.update_server(server['id'],
+ accessIPv4='1.1.1.1',
+ accessIPv6='::babe:202:202')
+ self.assertEqual(200, resp.status)
+ self.client.wait_for_server_status(server['id'], 'ACTIVE')
- #Verify the access addresses have been updated
- resp, server = self.client.get_server(server['id'])
- self.assertEqual('1.1.1.1', server['accessIPv4'])
- self.assertEqual('::babe:202:202', server['accessIPv6'])
-
- #Teardown
- finally:
- if server:
- self.client.delete_server(server['id'])
+ # Verify the access addresses have been updated
+ resp, server = self.client.get_server(server['id'])
+ self.assertEqual('1.1.1.1', server['accessIPv4'])
+ self.assertEqual('::babe:202:202', server['accessIPv6'])
def test_delete_server_while_in_building_state(self):
# Delete a server while it's VM state is Building
diff --git a/tempest/tests/compute/servers/test_virtual_interfaces.py b/tempest/tests/compute/servers/test_virtual_interfaces.py
index 4c48366..476a556 100644
--- a/tempest/tests/compute/servers/test_virtual_interfaces.py
+++ b/tempest/tests/compute/servers/test_virtual_interfaces.py
@@ -30,20 +30,10 @@
@classmethod
def setUpClass(cls):
super(VirtualInterfacesTestJSON, cls).setUpClass()
- cls.name = rand_name('server')
cls.client = cls.servers_client
- resp, server = cls.servers_client.create_server(cls.name,
- cls.image_ref,
- cls.flavor_ref)
+ resp, server = cls.create_server(wait_until='ACTIVE')
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)
- super(VirtualInterfacesTestJSON, cls).tearDownClass()
-
@attr(type='positive')
def test_list_virtual_interfaces(self):
# Positive test:Should be able to GET the virtual interfaces list
diff --git a/tempest/tests/compute/test_authorization.py b/tempest/tests/compute/test_authorization.py
index dfe7983..4ca197a 100644
--- a/tempest/tests/compute/test_authorization.py
+++ b/tempest/tests/compute/test_authorization.py
@@ -57,10 +57,7 @@
cls.alt_security_client = cls.alt_manager.security_groups_client
cls.alt_security_client._set_auth()
- name = rand_name('server')
- resp, server = cls.client.create_server(name, cls.image_ref,
- cls.flavor_ref)
- cls.client.wait_for_server_status(server['id'], 'ACTIVE')
+ resp, server = cls.create_server(wait_until='ACTIVE')
resp, cls.server = cls.client.get_server(server['id'])
name = rand_name('image')
@@ -92,7 +89,6 @@
@classmethod
def tearDownClass(cls):
if compute.MULTI_USER:
- cls.client.delete_server(cls.server['id'])
cls.images_client.delete_image(cls.image['id'])
cls.keypairs_client.delete_keypair(cls.keypairname)
cls.security_client.delete_security_group(cls.security_group['id'])
@@ -207,7 +203,6 @@
self.alt_keypairs_client.get_keypair,
self.keypairname)
- @testtools.skip("Skipped until the Bug #1086980 is resolved")
def test_delete_keypair_of_alt_account_fails(self):
# A DELETE request for another user's keypair should fail
self.assertRaises(exceptions.NotFound,
diff --git a/tempest/tests/compute/volumes/test_attach_volume.py b/tempest/tests/compute/volumes/test_attach_volume.py
index 7c1a2d1..d9abe41 100644
--- a/tempest/tests/compute/volumes/test_attach_volume.py
+++ b/tempest/tests/compute/volumes/test_attach_volume.py
@@ -43,24 +43,15 @@
self.servers_client.detach_volume(server_id, volume_id)
self.volumes_client.wait_for_volume_status(volume_id, 'available')
- def _delete(self, server, volume):
+ def _delete(self, 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')
-
# Start a server and wait for it to become ready
- resp, server = self.servers_client.create_server(name,
- self.image_ref,
- self.flavor_ref,
- adminPass='password')
- self.server = server
- self.servers_client.wait_for_server_status(server['id'], 'ACTIVE')
+ resp, server = self.create_server(wait_until='ACTIVE',
+ adminPass='password')
# Record addresses so that we can ssh later
resp, server['addresses'] = \
@@ -118,7 +109,9 @@
finally:
if self.attached:
self._detach(server['id'], volume['id'])
- self._delete(self.server, self.volume)
+ # NOTE(maurosr): here we do the cleanup for volume, servers are
+ # dealt on BaseComputeTest.tearDownClass
+ self._delete(self.volume)
class AttachVolumeTestXML(AttachVolumeTestJSON):
diff --git a/tempest/tests/object_storage/test_object_services.py b/tempest/tests/object_storage/test_object_services.py
index e0a2fbb..76fab0b 100644
--- a/tempest/tests/object_storage/test_object_services.py
+++ b/tempest/tests/object_storage/test_object_services.py
@@ -326,7 +326,6 @@
self.assertIn('x-container-read', resp)
self.assertEqual(resp['x-container-read'], 'x')
- @testtools.skip('Until Bug 1091669 is resolved.')
@attr(type='smoke')
def test_access_public_object_with_another_user_creds(self):
#Make container public-readable, and access the object
diff --git a/tempest/tests/volume/admin/test_volume_types_extra_specs_negative.py b/tempest/tests/volume/admin/test_volume_types_extra_specs_negative.py
index 6b274c6..f528cec 100644
--- a/tempest/tests/volume/admin/test_volume_types_extra_specs_negative.py
+++ b/tempest/tests/volume/admin/test_volume_types_extra_specs_negative.py
@@ -79,14 +79,12 @@
self.client.create_volume_type_extra_specs,
str(uuid.uuid4()), extra_specs)
- @testtools.skip('Until bug 1090322 is fixed')
def test_create_none_body(self):
# Should not create volume type extra spec for none POST body.
self.assertRaises(exceptions.BadRequest,
self.client.create_volume_type_extra_specs,
self.volume_type['id'], None)
- @testtools.skip('Until bug 1090322 is fixed')
def test_create_invalid_body(self):
# Should not create volume type extra spec for invalid POST body.
self.assertRaises(exceptions.BadRequest,
diff --git a/tempest/tests/volume/admin/test_volume_types_negative.py b/tempest/tests/volume/admin/test_volume_types_negative.py
index c706f3d..1b11d68 100644
--- a/tempest/tests/volume/admin/test_volume_types_negative.py
+++ b/tempest/tests/volume/admin/test_volume_types_negative.py
@@ -32,7 +32,6 @@
display_name=str(uuid.uuid4()),
volume_type=str(uuid.uuid4()))
- @testtools.skip('Until bug 1090356 is fixed')
def test_create_with_empty_name(self):
# Should not be able to create volume type with an empty name.
self.assertRaises(exceptions.BadRequest,
diff --git a/tools/skip_tracker.py b/tools/skip_tracker.py
index 12d29b0..a4cf394 100755
--- a/tools/skip_tracker.py
+++ b/tools/skip_tracker.py
@@ -61,7 +61,7 @@
"""
Return the skip tuples in a test file
"""
- BUG_RE = re.compile(r'.*skip\(.*[bB]ug\s*(\d+)')
+ BUG_RE = re.compile(r'.*skip\(.*bug:*\s*\#*(\d+)', re.IGNORECASE)
DEF_RE = re.compile(r'.*def (\w+)\(')
bug_found = False
results = []