Merge "make stack traces tool find individual traces"
diff --git a/HACKING.rst b/HACKING.rst
index a546f8c..eafa81b 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -153,6 +153,19 @@
kwarg2=dict_of_numbers)
+Test Skips
+----------
+If a test is broken because of a bug it is appropriate to skip the test until
+bug has been fixed. However, the skip message should be formatted so that
+Tempest's skip tracking tool can watch the bug status. The skip message should
+contain the string 'Bug' immediately followed by a space. Then the bug number
+should be included in the message '#' in front of the number.
+
+Example::
+
+ @testtools.skip("Skipped until the Bug #980688 is resolved")
+
+
openstack-common
----------------
diff --git a/cli/__init__.py b/cli/__init__.py
index 6ffe229..7a92260 100644
--- a/cli/__init__.py
+++ b/cli/__init__.py
@@ -60,16 +60,22 @@
return self.cmd_with_auth(
'nova', action, flags, params, admin, fail_ok)
- def nova_manage(self, action, flags='', params='', fail_ok=False):
+ def nova_manage(self, action, flags='', params='', fail_ok=False,
+ merge_stderr=False):
"""Executes nova-manage command for the given action."""
return self.cmd(
- 'nova-manage', action, flags, params, fail_ok)
+ 'nova-manage', action, flags, params, fail_ok, merge_stderr)
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 glance(self, action, flags='', params='', admin=True, fail_ok=False):
+ """Executes glance command for the given action."""
+ return self.cmd_with_auth(
+ 'glance', 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,14 +87,19 @@
flags = creds + ' ' + flags
return self.cmd(cmd, action, flags, params, fail_ok)
- def cmd(self, cmd, action, flags='', params='', fail_ok=False):
+ def cmd(self, cmd, action, flags='', params='', fail_ok=False,
+ merge_stderr=False):
"""Executes specified command for the given action."""
cmd = ' '.join([CONF.cli.cli_dir + cmd,
flags, action, params])
LOG.info("running: '%s'" % cmd)
cmd = shlex.split(cmd)
try:
- result = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
+ if merge_stderr:
+ result = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
+ else:
+ devnull = open('/dev/null', 'w')
+ result = subprocess.check_output(cmd, stderr=devnull)
except subprocess.CalledProcessError, e:
LOG.error("command output:\n%s" % e.output)
raise
diff --git a/cli/simple_read_only/test_compute.py b/cli/simple_read_only/test_compute.py
index 43c3c45..d301d38 100644
--- a/cli/simple_read_only/test_compute.py
+++ b/cli/simple_read_only/test_compute.py
@@ -22,7 +22,6 @@
import testtools
import cli
-from tempest import config
CONF = cfg.CONF
@@ -73,7 +72,7 @@
def test_admin_dns_domains(self):
self.nova('dns-domains')
- @testtools.skip("Test needs parameters, Bug: 1157349")
+ @testtools.skip("Test needs parameters, Bug #1157349")
def test_admin_dns_list(self):
self.nova('dns-list')
@@ -111,7 +110,7 @@
def test_admin_image_list(self):
self.nova('image-list')
- @testtools.skip("Test needs parameters, Bug: 1157349")
+ @testtools.skip("Test needs parameters, Bug #1157349")
def test_admin_interface_list(self):
self.nova('interface-list')
@@ -136,7 +135,7 @@
def test_admin_secgroup_list(self):
self.nova('secgroup-list')
- @testtools.skip("Test needs parameters, Bug: 1157349")
+ @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
index 17b3bf6..bbcc5b1 100644
--- a/cli/simple_read_only/test_compute_manage.py
+++ b/cli/simple_read_only/test_compute_manage.py
@@ -18,8 +18,6 @@
import logging
import subprocess
-import testtools
-
import cli
@@ -49,9 +47,20 @@
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'))
+ # Bug 1159957: nova-manage --version writes to stderr
+ self.assertNotEqual("", self.nova_manage('', '--version',
+ merge_stderr=True))
+ self.assertEqual(self.nova_manage('version'),
+ self.nova_manage('', '--version', merge_stderr=True))
+
+ def test_debug_flag(self):
+ self.assertNotEqual("", self.nova_manage('instance_type list',
+ '--debug'))
+
+ def test_verbose_flag(self):
+ self.assertNotEqual("", self.nova_manage('instance_type list',
+ '--verbose'))
# test actions
def test_version(self):
@@ -59,3 +68,16 @@
def test_flavor_list(self):
self.assertNotEqual("", self.nova_manage('flavor list'))
+ self.assertEqual(self.nova_manage('instance_type list'),
+ self.nova_manage('flavor list'))
+
+ def test_db_archive_deleted_rows(self):
+ # make sure command doesn't error out
+ self.nova_manage('db archive_deleted_rows 50')
+
+ def test_db_sync(self):
+ # make sure command doesn't error out
+ self.nova_manage('db sync')
+
+ def test_db_version(self):
+ self.assertNotEqual("", self.nova_manage('db version'))
diff --git a/cli/simple_read_only/test_glance.py b/cli/simple_read_only/test_glance.py
new file mode 100644
index 0000000..f9822cc
--- /dev/null
+++ b/cli/simple_read_only/test_glance.py
@@ -0,0 +1,66 @@
+# 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 SimpleReadOnlyGlanceClientTest(cli.ClientTestBase):
+ """Basic, read-only tests for Glance 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_glance_fake_action(self):
+ self.assertRaises(subprocess.CalledProcessError,
+ self.glance,
+ 'this-does-not-exist')
+
+ def test_glance_image_list(self):
+ out = self.glance('image-list')
+ endpoints = self.parser.listing(out)
+ self.assertTableStruct(endpoints, [
+ 'ID', 'Name', 'Disk Format', 'Container Format',
+ 'Size', 'Status'])
+
+ def test_glance_help(self):
+ help_text = self.glance('help')
+ lines = help_text.split('\n')
+ self.assertTrue(lines[0].startswith('usage: glance'))
+
+ 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(('image-create', 'image-delete', 'help',
+ 'image-download', 'image-show', 'image-update',
+ 'member-add', 'member-create', 'member-delete',
+ 'member-list'))
+ self.assertFalse(wanted_commands - commands)
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index 02bfdcb..ac18490 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -35,9 +35,9 @@
# This should be the username of a user WITH administrative privileges
admin_username = admin
-# The above non-administrative user's password
+# The above administrative user's password
admin_password = secret
-# The above non-administrative user's tenant name
+# The above administrative user's tenant name
admin_tenant_name = admin
[compute]
@@ -178,7 +178,7 @@
tenant_network_cidr = 10.100.0.0/16
# The mask bits used to partition the tenant block.
-tenant_network_mask_bits = 29
+tenant_network_mask_bits = 28
# If tenant networks are reachable, connectivity checks will be
# performed directly against addresses on those networks.
diff --git a/run_tests.sh b/run_tests.sh
index 93edfaf..25b9729 100755
--- a/run_tests.sh
+++ b/run_tests.sh
@@ -33,7 +33,7 @@
config_file=""
update=0
-if ! options=$(getopt -o VNnfuswcphdsC: -l virtual-env,no-virtual-env,no-site-packages,force,update,smoke,whitebox,nova-coverage,pep8,help,debug,stdout,config: -- "$@")
+if ! options=$(getopt -o VNnfuswcphdSC: -l virtual-env,no-virtual-env,no-site-packages,force,update,smoke,whitebox,nova-coverage,pep8,help,debug,stdout,config: -- "$@")
then
# parse error
usage
@@ -95,14 +95,7 @@
function run_pep8 {
echo "Running pep8 ..."
- srcfiles="`find tempest -type f -name "*.py"`"
- srcfiles+=" `find tools -type f -name "*.py"`"
- srcfiles+=" `find stress -type f -name "*.py"`"
- srcfiles+=" setup.py"
-
- ignore='--ignore=E121,E122,E125,E126'
-
- ${wrapper} python tools/hacking.py ${ignore} ${srcfiles}
+ ${wrapper} tools/check_source.sh
}
function run_coverage_start {
diff --git a/setup.py b/setup.py
index 1f071bb..1507797 100755
--- a/setup.py
+++ b/setup.py
@@ -25,7 +25,7 @@
depend_links = common_setup.parse_dependency_links()
setuptools.setup(name='tempest',
- version="2012.2",
+ version=common_setup.get_version('tempest', "2013.2"),
description='Integration test tools',
author='OpenStack',
author_email='openstack-qa@lists.launchpad.net',
diff --git a/tempest/clients.py b/tempest/clients.py
index b3b5906..7d9a263 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -48,8 +48,11 @@
from tempest.services.compute.xml.servers_client import ServersClientXML
from tempest.services.compute.xml.volumes_extensions_client import \
VolumesExtensionsClientXML
+from tempest.services.identity.v3.json.endpoints_client import \
+ EndPointClientJSON
from tempest.services.identity.json.identity_client import IdentityClientJSON
from tempest.services.identity.json.identity_client import TokenClientJSON
+from tempest.services.identity.v3.xml.endpoints_client import EndPointClientXML
from tempest.services.identity.xml.identity_client import IdentityClientXML
from tempest.services.identity.xml.identity_client import TokenClientXML
from tempest.services.image.v1.json.image_client import ImageClientJSON
@@ -157,6 +160,11 @@
"xml": InterfacesClientXML,
}
+ENDPOINT_CLIENT = {
+ "json": EndPointClientJSON,
+ "xml": EndPointClientXML,
+}
+
class Manager(object):
@@ -219,6 +227,7 @@
self.security_groups_client = \
SECURITY_GROUPS_CLIENT[interface](*client_args)
self.interfaces_client = INTERFACES_CLIENT[interface](*client_args)
+ self.endpoints_client = ENDPOINT_CLIENT[interface](*client_args)
except KeyError:
msg = "Unsupported interface type `%s'" % interface
raise exceptions.InvalidConfiguration(msg)
diff --git a/tempest/common/rest_client.py b/tempest/common/rest_client.py
index d68b9ed..fba3b0f 100644
--- a/tempest/common/rest_client.py
+++ b/tempest/common/rest_client.py
@@ -135,12 +135,14 @@
headers = {'Content-Type': 'application/json'}
body = json.dumps(creds)
- resp, body = self.http_obj.request(auth_url, 'POST',
- headers=headers, body=body)
+ self._log_request('POST', auth_url, headers, body)
+ resp, resp_body = self.http_obj.request(auth_url, 'POST',
+ headers=headers, body=body)
+ self._log_response(resp, resp_body)
if resp.status == 200:
try:
- auth_data = json.loads(body)['access']
+ auth_data = json.loads(resp_body)['access']
token = auth_data['token']['id']
except Exception, e:
print "Failed to obtain token for user: %s" % e
@@ -155,23 +157,18 @@
mgmt_url = _ep[self.endpoint_url]
if not mgmt_url:
mgmt_url = ep['endpoints'][0][self.endpoint_url]
- tenant_id = auth_data['token']['tenant']['id']
break
if mgmt_url is None:
raise exceptions.EndpointNotFound(service)
- if service == 'network':
- # Keystone does not return the correct endpoint for
- # quantum. Handle this separately.
- mgmt_url = (mgmt_url + self.config.network.api_version +
- "/tenants/" + tenant_id)
-
return token, mgmt_url
elif resp.status == 401:
raise exceptions.AuthenticationFailure(user=user,
password=password)
+ raise exceptions.IdentityError('Unexpected status code {0}'.format(
+ resp.status))
def post(self, url, body, headers):
return self.request('POST', url, headers, body)
@@ -182,6 +179,9 @@
def delete(self, url, headers=None):
return self.request('DELETE', url, headers)
+ def patch(self, url, body, headers):
+ return self.request('PATCH', url, headers, body)
+
def put(self, url, body, headers):
return self.request('PUT', url, headers, body)
@@ -236,7 +236,7 @@
def response_checker(self, method, url, headers, body, resp, resp_body):
if (resp.status in set((204, 205, 304)) or resp.status < 200 or
- method.upper() == 'HEAD') and resp_body:
+ method.upper() == 'HEAD') and resp_body:
raise exceptions.ResponseWithNonEmptyBody(status=resp.status)
#NOTE(afazekas):
# If the HTTP Status Code is 205
@@ -320,6 +320,10 @@
except KeyError:
ctype = 'application/json'
+ # It is not an error response
+ if resp.status < 400:
+ return
+
JSON_ENC = ['application/json; charset=UTF-8', 'application/json',
'application/json; charset=utf-8']
# NOTE(mtreinish): This is for compatibility with Glance and swift
@@ -390,7 +394,7 @@
def is_absolute_limit(self, resp, resp_body):
if (not isinstance(resp_body, collections.Mapping) or
- 'retry-after' not in resp):
+ 'retry-after' not in resp):
return True
over_limit = resp_body.get('overLimit', None)
if not over_limit:
@@ -424,6 +428,6 @@
def is_absolute_limit(self, resp, resp_body):
if (not isinstance(resp_body, collections.Mapping) or
- 'retry-after' not in resp):
+ 'retry-after' not in resp):
return True
return 'exceed' in resp_body.get('message', 'blabla')
diff --git a/tempest/common/utils/data_utils.py b/tempest/common/utils/data_utils.py
index 82982b8..e75b54f 100644
--- a/tempest/common/utils/data_utils.py
+++ b/tempest/common/utils/data_utils.py
@@ -24,10 +24,10 @@
def rand_name(name='test'):
- return name + str(random.randint(1, 999999))
+ return name + str(random.randint(1, 0x7fffffff))
-def rand_int_id(start=0, end=999999):
+def rand_int_id(start=0, end=0x7fffffff):
return random.randint(start, end)
diff --git a/tempest/config.py b/tempest/config.py
index 3a4a8c9..9c41660 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -260,9 +260,6 @@
cfg.StrOpt('catalog_type',
default='network',
help='Catalog type of the Quantum service.'),
- cfg.StrOpt('api_version',
- default="v1.1",
- help="Version of Quantum API"),
cfg.StrOpt('tenant_network_cidr',
default="10.100.0.0/16",
help="The cidr block to allocate tenant networks from"),
diff --git a/tempest/services/compute/json/flavors_client.py b/tempest/services/compute/json/flavors_client.py
index 955068f..1b965f3 100644
--- a/tempest/services/compute/json/flavors_client.py
+++ b/tempest/services/compute/json/flavors_client.py
@@ -106,3 +106,29 @@
"""Unsets extra Specs from the mentioned flavor."""
return self.delete('flavors/%s/os-extra_specs/%s' % (str(flavor_id),
key))
+
+ def add_flavor_access(self, flavor_id, tenant_id):
+ """Add flavor access for the specified tenant."""
+ post_body = {
+ 'addTenantAccess': {
+ 'tenant': tenant_id
+ }
+ }
+ post_body = json.dumps(post_body)
+ resp, body = self.post('flavors/%s/action' % flavor_id,
+ post_body, self.headers)
+ body = json.loads(body)
+ return resp, body['flavor_access']
+
+ def remove_flavor_access(self, flavor_id, tenant_id):
+ """Remove flavor access from the specified tenant."""
+ post_body = {
+ 'removeTenantAccess': {
+ 'tenant': tenant_id
+ }
+ }
+ post_body = json.dumps(post_body)
+ resp, body = self.post('flavors/%s/action' % flavor_id,
+ post_body, self.headers)
+ body = json.loads(body)
+ return resp, body['flavor_access']
diff --git a/tempest/services/compute/json/floating_ips_client.py b/tempest/services/compute/json/floating_ips_client.py
index d73b8a9..27733ac 100644
--- a/tempest/services/compute/json/floating_ips_client.py
+++ b/tempest/services/compute/json/floating_ips_client.py
@@ -47,10 +47,12 @@
raise exceptions.NotFound(body)
return resp, body['floating_ip']
- def create_floating_ip(self):
+ def create_floating_ip(self, pool_name=None):
"""Allocate a floating IP to the project."""
url = 'os-floating-ips'
- resp, body = self.post(url, None, None)
+ post_body = {'pool': pool_name}
+ post_body = json.dumps(post_body)
+ resp, body = self.post(url, post_body, self.headers)
body = json.loads(body)
return resp, body['floating_ip']
diff --git a/tempest/services/compute/json/quotas_client.py b/tempest/services/compute/json/quotas_client.py
index 330e80b..37d4131 100644
--- a/tempest/services/compute/json/quotas_client.py
+++ b/tempest/services/compute/json/quotas_client.py
@@ -37,7 +37,7 @@
def update_quota_set(self, tenant_id, injected_file_content_bytes=None,
metadata_items=None, ram=None, floating_ips=None,
- key_pairs=None, instances=None,
+ fixed_ips=None, key_pairs=None, instances=None,
security_group_rules=None, injected_files=None,
cores=None, injected_file_path_bytes=None,
security_groups=None):
@@ -59,6 +59,9 @@
if floating_ips is not None:
post_body['floating_ips'] = floating_ips
+ if fixed_ips is not None:
+ post_body['fixed_ips'] = fixed_ips
+
if key_pairs is not None:
post_body['key_pairs'] = key_pairs
diff --git a/tempest/services/compute/json/security_groups_client.py b/tempest/services/compute/json/security_groups_client.py
index 7f430d8..1dc11c4 100644
--- a/tempest/services/compute/json/security_groups_client.py
+++ b/tempest/services/compute/json/security_groups_client.py
@@ -19,6 +19,7 @@
import urllib
from tempest.common.rest_client import RestClient
+from tempest import exceptions
class SecurityGroupsClientJSON(RestClient):
diff --git a/tempest/services/compute/json/servers_client.py b/tempest/services/compute/json/servers_client.py
index bc9d9bd..9e71f3d 100644
--- a/tempest/services/compute/json/servers_client.py
+++ b/tempest/services/compute/json/servers_client.py
@@ -52,6 +52,7 @@
min_count: Count of minimum number of instances to launch.
max_count: Count of maximum number of instances to launch.
disk_config: Determines if user or admin controls disk configuration.
+ return_reservation_id: Enable/Disable the return of reservation id
"""
post_body = {
'name': name,
@@ -63,7 +64,8 @@
'security_groups', 'networks', 'user_data',
'availability_zone', 'accessIPv4', 'accessIPv6',
'min_count', 'max_count', ('metadata', 'meta'),
- ('OS-DCF:diskConfig', 'disk_config')]:
+ ('OS-DCF:diskConfig', 'disk_config'),
+ 'return_reservation_id']:
if isinstance(option, tuple):
post_param = option[0]
key = option[1]
@@ -77,6 +79,10 @@
resp, body = self.post('servers', post_body, self.headers)
body = json.loads(body)
+ # NOTE(maurosr): this deals with the case of multiple server create
+ # with return reservation id set True
+ if 'reservation_id' in body:
+ return resp, body
return resp, body['server']
def update_server(self, server_id, name=None, meta=None, accessIPv4=None,
diff --git a/tempest/services/compute/xml/common.py b/tempest/services/compute/xml/common.py
index 4b1b11a..cb24917 100644
--- a/tempest/services/compute/xml/common.py
+++ b/tempest/services/compute/xml/common.py
@@ -1,6 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
-# Copyright 2012 IBM
+# Copyright 2012 IBM Corp.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
diff --git a/tempest/services/compute/xml/flavors_client.py b/tempest/services/compute/xml/flavors_client.py
index ece362b..a6451df 100644
--- a/tempest/services/compute/xml/flavors_client.py
+++ b/tempest/services/compute/xml/flavors_client.py
@@ -147,3 +147,28 @@
"""Unsets an extra spec based on the mentioned flavor and key."""
return self.delete('flavors/%s/os-extra_specs/%s' % (str(flavor_id),
key))
+
+ def _parse_array_access(self, node):
+ return [xml_to_json(x) for x in node]
+
+ def add_flavor_access(self, flavor_id, tenant_id):
+ """Add flavor access for the specified tenant."""
+ doc = Document()
+ server = Element("addTenantAccess")
+ doc.append(server)
+ server.add_attr("tenant", tenant_id)
+ resp, body = self.post('flavors/%s/action' % str(flavor_id),
+ str(doc), self.headers)
+ body = self._parse_array_access(etree.fromstring(body))
+ return resp, body
+
+ def remove_flavor_access(self, flavor_id, tenant_id):
+ """Remove flavor access from the specified tenant."""
+ doc = Document()
+ server = Element("removeTenantAccess")
+ doc.append(server)
+ server.add_attr("tenant", tenant_id)
+ resp, body = self.post('flavors/%s/action' % str(flavor_id),
+ str(doc), self.headers)
+ body = self._parse_array_access(etree.fromstring(body))
+ return resp, body
diff --git a/tempest/services/compute/xml/floating_ips_client.py b/tempest/services/compute/xml/floating_ips_client.py
index 74b4be2..278cc88 100644
--- a/tempest/services/compute/xml/floating_ips_client.py
+++ b/tempest/services/compute/xml/floating_ips_client.py
@@ -1,6 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
-# Copyright 2012 IBM
+# Copyright 2012 IBM Corp.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -22,6 +22,7 @@
from tempest import exceptions
from tempest.services.compute.xml.common import Document
from tempest.services.compute.xml.common import Element
+from tempest.services.compute.xml.common import Text
from tempest.services.compute.xml.common import xml_to_json
@@ -60,10 +61,17 @@
raise exceptions.NotFound(body)
return resp, body
- def create_floating_ip(self):
+ def create_floating_ip(self, pool_name=None):
"""Allocate a floating IP to the project."""
url = 'os-floating-ips'
- resp, body = self.post(url, None, self.headers)
+ if pool_name:
+ doc = Document()
+ pool = Element("pool")
+ pool.append(Text(pool_name))
+ doc.append(pool)
+ resp, body = self.post(url, str(doc), self.headers)
+ else:
+ resp, body = self.post(url, None, self.headers)
body = self._parse_floating_ip(etree.fromstring(body))
return resp, body
diff --git a/tempest/services/compute/xml/images_client.py b/tempest/services/compute/xml/images_client.py
index 3b01efb..c7e337b 100644
--- a/tempest/services/compute/xml/images_client.py
+++ b/tempest/services/compute/xml/images_client.py
@@ -1,6 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
-# Copyright 2012 IBM
+# Copyright 2012 IBM Corp.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -77,6 +77,19 @@
data['images'].append(self._parse_image(image))
return data
+ def _parse_key_value(self, node):
+ """Parse <foo key='key'>value</foo> data into {'key': 'value'}."""
+ data = {}
+ for node in node.getchildren():
+ data[node.get('key')] = node.text
+ return data
+
+ def _parse_metadata(self, node):
+ """Parse the response body without children."""
+ data = {}
+ data[node.get('key')] = node.text
+ return data
+
def create_image(self, server_id, name, meta=None):
"""Creates an image of the original server."""
post_body = Element('createImage', name=name)
@@ -153,51 +166,53 @@
if int(time.time()) - start >= self.build_timeout:
raise exceptions.TimeoutException
+ def _metadata_body(self, meta):
+ post_body = Element('metadata')
+ for k, v in meta.items():
+ data = Element('meta', key=k)
+ data.append(Text(v))
+ post_body.append(data)
+ return post_body
+
def list_image_metadata(self, image_id):
"""Lists all metadata items for an image."""
resp, body = self.get("images/%s/metadata" % str(image_id),
self.headers)
- body = xml_to_json(etree.fromstring(body))
- return resp, body['metadata']
-
- def _metadata_body(image_id, meta):
- post_body = Document('metadata')
- for k, v in meta:
- text = Text(v)
- metadata = Element('meta', text, key=k)
- post_body.append(metadata)
- return post_body
+ body = self._parse_key_value(etree.fromstring(body))
+ return resp, body
def set_image_metadata(self, image_id, meta):
"""Sets the metadata for an image."""
- post_body = self._metadata_body(image_id, meta)
- resp, body = self.put('images/%s/metadata' % str(image_id),
- post_body, self.headers)
- body = xml_to_json(etree.fromstring(body))
- return resp, body['metadata']
+ post_body = self._metadata_body(meta)
+ resp, body = self.put('images/%s/metadata' % image_id,
+ str(Document(post_body)), self.headers)
+ body = self._parse_key_value(etree.fromstring(body))
+ return resp, body
def update_image_metadata(self, image_id, meta):
"""Updates the metadata for an image."""
- post_body = self._metadata_body(image_id, meta)
+ post_body = self._metadata_body(meta)
resp, body = self.post('images/%s/metadata' % str(image_id),
- post_body, self.headers)
- body = xml_to_json(etree.fromstring(body))
- return resp, body['metadata']
+ str(Document(post_body)), self.headers)
+ body = self._parse_key_value(etree.fromstring(body))
+ return resp, body
def get_image_metadata_item(self, image_id, key):
"""Returns the value for a specific image metadata key."""
resp, body = self.get("images/%s/metadata/%s.xml" %
(str(image_id), key), self.headers)
- body = xml_to_json(etree.fromstring(body))
- return resp, body['meta']
+ body = self._parse_metadata(etree.fromstring(body))
+ return resp, body
def set_image_metadata_item(self, image_id, key, meta):
"""Sets the value for a specific image metadata key."""
- post_body = Document('meta', Text(meta), key=key)
- resp, body = self.post('images/%s/metadata/%s' % (str(image_id), key),
- post_body, self.headers)
+ for k, v in meta.items():
+ post_body = Element('meta', key=key)
+ post_body.append(Text(v))
+ resp, body = self.put('images/%s/metadata/%s' % (str(image_id), key),
+ str(Document(post_body)), self.headers)
body = xml_to_json(etree.fromstring(body))
- return resp, body['meta']
+ return resp, body
def update_image_metadata_item(self, image_id, key, meta):
"""Sets the value for a specific image metadata key."""
@@ -209,6 +224,5 @@
def delete_image_metadata_item(self, image_id, key):
"""Deletes a single image metadata key/value pair."""
- resp, body = self.delete("images/%s/metadata/%s" % (str(image_id), key,
- self.headers))
- return resp, body
+ return self.delete("images/%s/metadata/%s" % (str(image_id), key),
+ self.headers)
diff --git a/tempest/services/compute/xml/keypairs_client.py b/tempest/services/compute/xml/keypairs_client.py
index d258537..0157245 100644
--- a/tempest/services/compute/xml/keypairs_client.py
+++ b/tempest/services/compute/xml/keypairs_client.py
@@ -1,6 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
-# Copyright 2012 IBM
+# Copyright 2012 IBM Corp.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
diff --git a/tempest/services/compute/xml/limits_client.py b/tempest/services/compute/xml/limits_client.py
index d233bba..704de52 100644
--- a/tempest/services/compute/xml/limits_client.py
+++ b/tempest/services/compute/xml/limits_client.py
@@ -1,6 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
-# Copyright 2012 IBM
+# Copyright 2012 IBM Corp.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
diff --git a/tempest/services/compute/xml/quotas_client.py b/tempest/services/compute/xml/quotas_client.py
index 0437205..20e04b4 100644
--- a/tempest/services/compute/xml/quotas_client.py
+++ b/tempest/services/compute/xml/quotas_client.py
@@ -57,7 +57,7 @@
def update_quota_set(self, tenant_id, injected_file_content_bytes=None,
metadata_items=None, ram=None, floating_ips=None,
- key_pairs=None, instances=None,
+ fixed_ips=None, key_pairs=None, instances=None,
security_group_rules=None, injected_files=None,
cores=None, injected_file_path_bytes=None,
security_groups=None):
@@ -80,6 +80,9 @@
if floating_ips is not None:
post_body.add_attr('floating_ips', floating_ips)
+ if fixed_ips is not None:
+ post_body.add_attr('fixed_ips', fixed_ips)
+
if key_pairs is not None:
post_body.add_attr('key_pairs', key_pairs)
diff --git a/tempest/services/compute/xml/security_groups_client.py b/tempest/services/compute/xml/security_groups_client.py
index 7db60a1..4fccc29 100644
--- a/tempest/services/compute/xml/security_groups_client.py
+++ b/tempest/services/compute/xml/security_groups_client.py
@@ -1,6 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
-# Copyright 2012 IBM
+# Copyright 2012 IBM Corp.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -19,6 +19,7 @@
import urllib
from tempest.common.rest_client import RestClientXML
+from tempest import exceptions
from tempest.services.compute.xml.common import Document
from tempest.services.compute.xml.common import Element
from tempest.services.compute.xml.common import Text
diff --git a/tempest/services/compute/xml/servers_client.py b/tempest/services/compute/xml/servers_client.py
index fceeb28..f5fd4a6 100644
--- a/tempest/services/compute/xml/servers_client.py
+++ b/tempest/services/compute/xml/servers_client.py
@@ -1,6 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
-# Copyright 2012 IBM
+# Copyright 2012 IBM Corp.
# Copyright 2013 Hewlett-Packard Development Company, L.P.
# All Rights Reserved.
#
@@ -94,6 +94,11 @@
json['addresses'] = json_addresses
else:
json = xml_to_json(xml_dom)
+ diskConfig = '{http://docs.openstack.org/compute/ext/disk_config/api/v1.1'\
+ '}diskConfig'
+ if diskConfig in json:
+ json['OS-DCF:diskConfig'] = json[diskConfig]
+ del json[diskConfig]
return json
@@ -230,10 +235,16 @@
name=name)
for attr in ["adminPass", "accessIPv4", "accessIPv6", "key_name",
- "user_data", "availability_zone"]:
+ "user_data", "availability_zone", "min_count",
+ "max_count", "return_reservation_id"]:
if attr in kwargs:
server.add_attr(attr, kwargs[attr])
+ if 'disk_config' in kwargs:
+ server.add_attr('xmlns:OS-DCF', "http://docs.openstack.org/"
+ "compute/ext/disk_config/api/v1.1")
+ server.add_attr('OS-DCF:diskConfig', kwargs['disk_config'])
+
if 'security_groups' in kwargs:
secgroups = Element("security_groups")
server.append(secgroups)
@@ -357,6 +368,12 @@
def rebuild(self, server_id, image_ref, **kwargs):
kwargs['imageRef'] = image_ref
+ if 'disk_config' in kwargs:
+ kwargs['OS-DCF:diskConfig'] = kwargs['disk_config']
+ del kwargs['disk_config']
+ kwargs['xmlns:OS-DCF'] = "http://docs.openstack.org/"\
+ "compute/ext/disk_config/api/v1.1"
+ kwargs['xmlns:atom'] = "http://www.w3.org/2005/Atom"
if 'xmlns' not in kwargs:
kwargs['xmlns'] = XMLNS_11
@@ -381,8 +398,11 @@
def resize(self, server_id, flavor_ref, **kwargs):
if 'disk_config' in kwargs:
- raise NotImplementedError("Sorry, disk_config not "
- "supported via XML yet")
+ kwargs['OS-DCF:diskConfig'] = kwargs['disk_config']
+ del kwargs['disk_config']
+ kwargs['xmlns:OS-DCF'] = "http://docs.openstack.org/"\
+ "compute/ext/disk_config/api/v1.1"
+ kwargs['xmlns:atom'] = "http://www.w3.org/2005/Atom"
kwargs['flavorRef'] = flavor_ref
return self.action(server_id, 'resize', None, **kwargs)
@@ -401,6 +421,19 @@
def remove_security_group(self, server_id, name):
return self.action(server_id, 'removeSecurityGroup', None, name=name)
+ def live_migrate_server(self, server_id, dest_host, use_block_migration):
+ """This should be called with administrator privileges ."""
+
+ req_body = Element("os-migrateLive",
+ xmlns=XMLNS_11,
+ disk_over_commit=False,
+ block_migration=use_block_migration,
+ host=dest_host)
+
+ resp, body = self.post("servers/%s/action" % str(server_id),
+ str(Document(req_body)), self.headers)
+ return resp, body
+
def list_server_metadata(self, server_id):
resp, body = self.get("servers/%s/metadata" % str(server_id),
self.headers)
diff --git a/tempest/services/compute/xml/volumes_extensions_client.py b/tempest/services/compute/xml/volumes_extensions_client.py
index 06cfcfb..4cdc4f0 100644
--- a/tempest/services/compute/xml/volumes_extensions_client.py
+++ b/tempest/services/compute/xml/volumes_extensions_client.py
@@ -1,6 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
-# Copyright 2012 IBM
+# Copyright 2012 IBM Corp.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
diff --git a/tempest/services/identity/v3/__init__.py b/tempest/services/identity/v3/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/services/identity/v3/__init__.py
diff --git a/tempest/services/identity/v3/json/__init__.py b/tempest/services/identity/v3/json/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/services/identity/v3/json/__init__.py
diff --git a/tempest/services/identity/v3/json/endpoints_client.py b/tempest/services/identity/v3/json/endpoints_client.py
new file mode 100755
index 0000000..3cb8f90
--- /dev/null
+++ b/tempest/services/identity/v3/json/endpoints_client.py
@@ -0,0 +1,87 @@
+# 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 json
+from urlparse import urlparse
+
+from tempest.common.rest_client import RestClient
+
+
+class EndPointClientJSON(RestClient):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(EndPointClientJSON, self).__init__(config,
+ username, password,
+ auth_url, tenant_name)
+ self.service = self.config.identity.catalog_type
+ self.endpoint_url = 'adminURL'
+
+ def request(self, method, url, headers=None, body=None, wait=None):
+ """Overriding the existing HTTP request in super class rest_client."""
+ self._set_auth()
+ self.base_url = self.base_url.replace(urlparse(self.base_url).path,
+ "/v3")
+ return super(EndPointClientJSON, self).request(method, url,
+ headers=headers,
+ body=body)
+
+ def list_endpoints(self):
+ """GET endpoints."""
+ resp, body = self.get('endpoints')
+ body = json.loads(body)
+ return resp, body['endpoints']
+
+ def create_endpoint(self, service_id, interface, url, **kwargs):
+ """Create endpoint."""
+ region = kwargs.get('region', None)
+ enabled = kwargs.get('enabled', None)
+ post_body = {
+ 'service_id': service_id,
+ 'interface': interface,
+ 'url': url,
+ 'region': region,
+ 'enabled': enabled
+ }
+ post_body = json.dumps({'endpoint': post_body})
+ resp, body = self.post('endpoints', post_body, self.headers)
+ body = json.loads(body)
+ return resp, body['endpoint']
+
+ def update_endpoint(self, endpoint_id, service_id=None, interface=None,
+ url=None, region=None, enabled=None):
+ """Updates an endpoint with given parameters."""
+ post_body = {}
+ if service_id is not None:
+ post_body['service_id'] = service_id
+ if interface is not None:
+ post_body['interface'] = interface
+ if url is not None:
+ post_body['url'] = url
+ if region is not None:
+ post_body['region'] = region
+ if enabled is not None:
+ post_body['enabled'] = enabled
+ post_body = json.dumps({'endpoint': post_body})
+ resp, body = self.patch('endpoints/%s' % endpoint_id, post_body,
+ self.headers)
+ body = json.loads(body)
+ return resp, body['endpoint']
+
+ def delete_endpoint(self, endpoint_id):
+ """Delete endpoint."""
+ resp_header, resp_body = self.delete('endpoints/%s' % endpoint_id)
+ return resp_header, resp_body
diff --git a/tempest/services/identity/v3/xml/__init__.py b/tempest/services/identity/v3/xml/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/services/identity/v3/xml/__init__.py
diff --git a/tempest/services/identity/v3/xml/endpoints_client.py b/tempest/services/identity/v3/xml/endpoints_client.py
new file mode 100755
index 0000000..8400976
--- /dev/null
+++ b/tempest/services/identity/v3/xml/endpoints_client.py
@@ -0,0 +1,107 @@
+# 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.
+from urlparse import urlparse
+
+import httplib2
+from lxml import etree
+
+from tempest.common.rest_client import RestClientXML
+from tempest.services.compute.xml.common import Document
+from tempest.services.compute.xml.common import Element
+from tempest.services.compute.xml.common import xml_to_json
+
+XMLNS = "http://docs.openstack.org/identity/api/v3"
+
+
+class EndPointClientXML(RestClientXML):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(EndPointClientXML, self).__init__(config, username, password,
+ auth_url, tenant_name)
+ self.service = self.config.identity.catalog_type
+ self.endpoint_url = 'adminURL'
+
+ def _parse_array(self, node):
+ array = []
+ for child in node.getchildren():
+ tag_list = child.tag.split('}', 1)
+ if tag_list[1] == "endpoint":
+ array.append(xml_to_json(child))
+ return array
+
+ def _parse_body(self, body):
+ json = xml_to_json(body)
+ return json
+
+ def request(self, method, url, headers=None, body=None, wait=None):
+ """Overriding the existing HTTP request in super class RestClient."""
+ dscv = self.config.identity.disable_ssl_certificate_validation
+ self.http_obj = httplib2.Http(disable_ssl_certificate_validation=dscv)
+ self._set_auth()
+ self.base_url = self.base_url.replace(urlparse(self.base_url).path,
+ "/v3")
+ return super(EndPointClientXML, self).request(method, url,
+ headers=headers,
+ body=body)
+
+ def list_endpoints(self):
+ """Get the list of endpoints."""
+ resp, body = self.get("endpoints", self.headers)
+ body = self._parse_array(etree.fromstring(body))
+ return resp, body
+
+ def create_endpoint(self, service_id, interface, url, **kwargs):
+ """Create endpoint."""
+ region = kwargs.get('region', None)
+ enabled = kwargs.get('enabled', None)
+ create_endpoint = Element("endpoint",
+ xmlns=XMLNS,
+ service_id=service_id,
+ interface=interface,
+ url=url, region=region,
+ enabled=enabled)
+ resp, body = self.post('endpoints', str(Document(create_endpoint)),
+ self.headers)
+ body = self._parse_body(etree.fromstring(body))
+ return resp, body
+
+ def update_endpoint(self, endpoint_id, service_id=None, interface=None,
+ url=None, region=None, enabled=None):
+ """Updates an endpoint with given parameters."""
+ doc = Document()
+ endpoint = Element("endpoint")
+ doc.append(endpoint)
+
+ if service_id:
+ endpoint.add_attr("service_id", service_id)
+ if interface:
+ endpoint.add_attr("interface", interface)
+ if url:
+ endpoint.add_attr("url", url)
+ if region:
+ endpoint.add_attr("region", region)
+ if enabled is not None:
+ endpoint.add_attr("enabled", enabled)
+ resp, body = self.patch('endpoints/%s' % str(endpoint_id),
+ str(doc), self.headers)
+ body = self._parse_body(etree.fromstring(body))
+ return resp, body
+
+ def delete_endpoint(self, endpoint_id):
+ """Delete endpoint."""
+ resp_header, resp_body = self.delete('endpoints/%s' % endpoint_id)
+ return resp_header, resp_body
diff --git a/tempest/services/identity/xml/identity_client.py b/tempest/services/identity/xml/identity_client.py
index 2431282..6f1b1b3 100644
--- a/tempest/services/identity/xml/identity_client.py
+++ b/tempest/services/identity/xml/identity_client.py
@@ -1,6 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
-# Copyright 2012 IBM
+# Copyright 2012 IBM Corp.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
diff --git a/tempest/services/image/v1/json/image_client.py b/tempest/services/image/v1/json/image_client.py
index 77c9cd2..a3b3e96 100644
--- a/tempest/services/image/v1/json/image_client.py
+++ b/tempest/services/image/v1/json/image_client.py
@@ -1,6 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
-# Copyright 2013 IBM
+# Copyright 2013 IBM Corp.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -18,13 +18,17 @@
import copy
import errno
import json
+import logging
import os
+import time
import urllib
from tempest.common import glance_http
from tempest.common.rest_client import RestClient
from tempest import exceptions
+LOG = logging.getLogger(__name__)
+
class ImageClientJSON(RestClient):
@@ -59,8 +63,13 @@
def _image_meta_to_headers(self, fields):
headers = {}
fields_copy = copy.deepcopy(fields)
+ copy_from = fields_copy.pop('copy_from', None)
+ if copy_from is not None:
+ headers['x-glance-api-copy-from'] = copy_from
for key, value in fields_copy.pop('properties', {}).iteritems():
headers['x-image-meta-property-%s' % key] = str(value)
+ for key, value in fields_copy.pop('api', {}).iteritems():
+ headers['x-glance-api-property-%s' % key] = str(value)
for key, value in fields_copy.iteritems():
headers['x-image-meta-%s' % key] = str(value)
return headers
@@ -130,7 +139,7 @@
headers = {}
- for option in ['is_public', 'location', 'properties']:
+ for option in ['is_public', 'location', 'properties', 'copy_from']:
if option in kwargs:
params[option] = kwargs.get(option)
@@ -187,10 +196,15 @@
body = json.loads(body)
return resp, body['images']
+ def get_image_meta(self, image_id):
+ url = 'v1/images/%s' % image_id
+ resp, __ = self.head(url)
+ body = self._image_meta_from_headers(resp)
+ return resp, body
+
def get_image(self, image_id):
url = 'v1/images/%s' % image_id
- resp, __ = self.get(url)
- body = self._image_meta_from_headers(resp)
+ resp, body = self.get(url)
return resp, body
def is_resource_deleted(self, id):
@@ -231,3 +245,34 @@
resp, data = self.put(url, body, self.headers)
data = json.loads(data)
return resp, data
+
+ #NOTE(afazekas): just for the wait function
+ def _get_image_status(self, image_id):
+ resp, meta = self.get_image_meta(image_id)
+ status = meta['status']
+ return status
+
+ #NOTE(afazkas): Wait reinvented again. It is not in the correct layer
+ def wait_for_image_status(self, image_id, status):
+ """Waits for a Image to reach a given status."""
+ start_time = time.time()
+ old_value = value = self._get_image_status(image_id)
+ while True:
+ dtime = time.time() - start_time
+ time.sleep(self.build_interval)
+ if value != old_value:
+ LOG.info('Value transition from "%s" to "%s"'
+ 'in %d second(s).', old_value,
+ value, dtime)
+ if (value == status):
+ return value
+
+ if dtime > self.build_timeout:
+ message = ('Time Limit Exceeded! (%ds)'
+ 'while waiting for %s, '
+ 'but we got %s.' %
+ (self.build_timeout, status, value))
+ raise exceptions.TimeoutException(message)
+ time.sleep(self.build_interval)
+ old_value = value
+ value = self._get_image_status(image_id)
diff --git a/tempest/services/image/v2/json/image_client.py b/tempest/services/image/v2/json/image_client.py
index 2c50a8d..f0531ec 100644
--- a/tempest/services/image/v2/json/image_client.py
+++ b/tempest/services/image/v2/json/image_client.py
@@ -1,6 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
-# Copyright 2013 IBM
+# Copyright 2013 IBM Corp.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
diff --git a/tempest/services/network/json/network_client.py b/tempest/services/network/json/network_client.py
index 34342c9..4758ddd 100644
--- a/tempest/services/network/json/network_client.py
+++ b/tempest/services/network/json/network_client.py
@@ -4,98 +4,112 @@
class NetworkClient(RestClient):
+ """
+ Tempest REST client for Quantum. Uses v2 of the Quantum API, since the
+ V1 API has been removed from the code base.
+
+ Implements the following operations for each one of the basic Quantum
+ abstractions (networks, sub-networks and ports):
+
+ create
+ delete
+ list
+ show
+ """
+
def __init__(self, config, username, password, auth_url, tenant_name=None):
super(NetworkClient, self).__init__(config, username, password,
auth_url, tenant_name)
self.service = self.config.network.catalog_type
+ self.version = '2.0'
+ self.uri_prefix = "v%s" % (self.version)
def list_networks(self):
- resp, body = self.get('networks')
+ uri = '%s/networks' % (self.uri_prefix)
+ resp, body = self.get(uri, self.headers)
body = json.loads(body)
return resp, body
- def create_network(self, name, key="network"):
+ def create_network(self, name):
post_body = {
- key: {
+ 'network': {
'name': name,
}
}
- headers = {'Content-Type': 'application/json'}
body = json.dumps(post_body)
- resp, body = self.post('networks', headers=headers, body=body)
+ uri = '%s/networks' % (self.uri_prefix)
+ resp, body = self.post(uri, headers=self.headers, body=body)
body = json.loads(body)
return resp, body
- def list_networks_details(self):
- resp, body = self.get('networks/detail')
- body = json.loads(body)
- return resp, body
-
- def get_network(self, uuid):
- resp, body = self.get('networks/%s' % uuid)
- body = json.loads(body)
- return resp, body
-
- def get_network_details(self, uuid):
- resp, body = self.get('networks/%s/detail' % uuid)
+ def show_network(self, uuid):
+ uri = '%s/networks/%s' % (self.uri_prefix, uuid)
+ resp, body = self.get(uri, self.headers)
body = json.loads(body)
return resp, body
def delete_network(self, uuid):
- resp, body = self.delete('networks/%s' % uuid)
+ uri = '%s/networks/%s' % (self.uri_prefix, uuid)
+ resp, body = self.delete(uri, self.headers)
return resp, body
- def create_port(self, network_id, zone, state=None, key='port'):
+ def create_subnet(self, net_uuid, cidr):
+ post_body = dict(
+ subnet=dict(
+ ip_version=4,
+ network_id=net_uuid,
+ cidr=cidr),)
+ body = json.dumps(post_body)
+ uri = '%s/subnets' % (self.uri_prefix)
+ resp, body = self.post(uri, headers=self.headers, body=body)
+ body = json.loads(body)
+ return resp, body
+
+ def delete_subnet(self, uuid):
+ uri = '%s/subnets/%s' % (self.uri_prefix, uuid)
+ resp, body = self.delete(uri, self.headers)
+ return resp, body
+
+ def list_subnets(self):
+ uri = '%s/subnets' % (self.uri_prefix)
+ resp, body = self.get(uri, self.headers)
+ body = json.loads(body)
+ return resp, body
+
+ def show_subnet(self, uuid):
+ uri = '%s/subnets/%s' % (self.uri_prefix, uuid)
+ resp, body = self.get(uri, self.headers)
+ body = json.loads(body)
+ return resp, body
+
+ def create_port(self, network_id, state=None):
if not state:
- state = 'ACTIVE'
+ state = True
post_body = {
- key: {
- 'state': state,
- 'nova_id': zone
+ 'port': {
+ 'network_id': network_id,
+ 'admin_state_up': state,
}
}
- headers = {'Content-Type': 'application/json'}
body = json.dumps(post_body)
- resp, body = self.post('networks/%s/ports.json' % network_id,
- headers=headers, body=body)
+ uri = '%s/ports' % (self.uri_prefix)
+ resp, body = self.post(uri, headers=self.headers, body=body)
body = json.loads(body)
return resp, body
- def delete_port(self, network_id, port_id):
- resp, body = self.delete('networks/%s/ports/%s.json' %
- (network_id, port_id))
+ def delete_port(self, port_id):
+ uri = '%s/ports/%s' % (self.uri_prefix, port_id)
+ resp, body = self.delete(uri, self.headers)
return resp, body
- def list_ports(self, network_id):
- resp, body = self.get('networks/%s/ports.json' % network_id)
+ def list_ports(self):
+ uri = '%s/ports' % (self.uri_prefix)
+ resp, body = self.get(uri, self.headers)
body = json.loads(body)
return resp, body
- def list_port_details(self, network_id):
- url = 'networks/%s/ports/detail.json' % network_id
- resp, body = self.get(url)
- body = json.loads(body)
- return resp, body
-
- def attach_port(self, network_id, port_id, interface_id):
- post_body = {
- 'attachment': {
- 'id': interface_id
- }
- }
- headers = {'Content-Type': 'application/json'}
- body = json.dumps(post_body)
- url = 'networks/%s/ports/%s/attachment.json' % (network_id, port_id)
- resp, body = self.put(url, headers=headers, body=body)
- return resp, body
-
- def detach_port(self, network_id, port_id):
- url = 'networks/%s/ports/%s/attachment.json' % (network_id, port_id)
- resp, body = self.delete(url)
- return resp, body
-
- def list_port_attachment(self, network_id, port_id):
- url = 'networks/%s/ports/%s/attachment.json' % (network_id, port_id)
- resp, body = self.get(url)
+ def show_port(self, port_id):
+ uri = '%s/ports/%s' % (self.uri_prefix, port_id)
+ resp, body = self.get(uri, self.headers)
body = json.loads(body)
return resp, body
diff --git a/tempest/services/volume/json/volumes_client.py b/tempest/services/volume/json/volumes_client.py
index 6b0befd..87c0eba 100644
--- a/tempest/services/volume/json/volumes_client.py
+++ b/tempest/services/volume/json/volumes_client.py
@@ -72,6 +72,7 @@
metadata: A dictionary of values to be used as metadata.
volume_type: Optional Name of volume_type for the volume
snapshot_id: When specified the volume is created from this snapshot
+ imageRef: When specified the volume is created from this image
"""
post_body = {'size': size}
post_body.update(kwargs)
diff --git a/tempest/services/volume/xml/admin/volume_types_client.py b/tempest/services/volume/xml/admin/volume_types_client.py
index 49cbadb..42e7b9a 100644
--- a/tempest/services/volume/xml/admin/volume_types_client.py
+++ b/tempest/services/volume/xml/admin/volume_types_client.py
@@ -1,6 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
-# Copyright 2012 IBM
+# Copyright 2012 IBM Corp.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
diff --git a/tempest/services/volume/xml/volumes_client.py b/tempest/services/volume/xml/volumes_client.py
index 4c15256..8eda26b 100644
--- a/tempest/services/volume/xml/volumes_client.py
+++ b/tempest/services/volume/xml/volumes_client.py
@@ -1,6 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
-# Copyright 2012 IBM
+# Copyright 2012 IBM Corp.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -100,6 +100,8 @@
:param volume_type: Optional Name of volume_type for the volume
:param snapshot_id: When specified the volume is created from
this snapshot
+ :param imageRef: When specified the volume is created from this
+ image
"""
#NOTE(afazekas): it should use a volume namespace
volume = Element("volume", xmlns=XMLNS_11, size=size)
diff --git a/tempest/smoke.py b/tempest/smoke.py
deleted file mode 100644
index 0d4043f..0000000
--- a/tempest/smoke.py
+++ /dev/null
@@ -1,85 +0,0 @@
-# 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 logging
-
-from tempest import test
-
-LOG = logging.getLogger(__name__)
-
-
-class SmokeTest(object):
-
- """
- Base test case class mixin for "smoke tests"
-
- Smoke tests are tests that have the following characteristics:
-
- * Test basic operations of an API, typically in an order that
- a regular user would perform those operations
- * Test only the correct inputs and action paths -- no fuzz or
- random input data is sent, only valid inputs.
- * Use only the default client tool for calling an API
- """
- pass
-
-
-class DefaultClientSmokeTest(test.DefaultClientTest, SmokeTest):
-
- """
- Base smoke test case class that provides the default clients to
- access the various OpenStack APIs.
- """
-
- @classmethod
- def tearDownClass(cls):
- # NOTE(jaypipes): Because smoke tests are typically run in a specific
- # order, and because test methods in smoke tests generally create
- # resources in a particular order, we destroy resources in the reverse
- # order in which resources are added to the smoke test class object
- while cls.os_resources:
- thing = cls.os_resources.pop()
- LOG.debug("Deleting %r from shared resources of %s" %
- (thing, cls.__name__))
-
- try:
- # OpenStack resources are assumed to have a delete()
- # method which destroys the resource...
- thing.delete()
- except Exception as e:
- # If the resource is already missing, mission accomplished.
- if e.__class__.__name__ == 'NotFound':
- continue
- raise
-
- def is_deletion_complete():
- # Deletion testing is only required for objects whose
- # existence cannot be checked via retrieval.
- if isinstance(thing, dict):
- return True
- try:
- thing.get()
- except Exception as e:
- # Clients are expected to return an exception
- # called 'NotFound' if retrieval fails.
- if e.__class__.__name__ == 'NotFound':
- return True
- raise
- return False
-
- # Block until resource deletion has completed or timed-out
- test.call_until_true(is_deletion_complete, 10, 1)
diff --git a/tempest/test.py b/tempest/test.py
index e0639b6..ccb2251 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -115,42 +115,91 @@
return False
-class DefaultClientTest(TestCase):
+def status_timeout(things, thing_id, expected_status):
+ """
+ Given a thing and an expected status, do a loop, sleeping
+ for a configurable amount of time, checking for the
+ expected status to show. At any time, if the returned
+ status of the thing is ERROR, fail out.
+ """
+ def check_status():
+ # python-novaclient has resources available to its client
+ # that all implement a get() method taking an identifier
+ # for the singular resource to retrieve.
+ thing = things.get(thing_id)
+ new_status = thing.status
+ if new_status == 'ERROR':
+ self.fail("%s failed to get to expected status."
+ "In ERROR state."
+ % thing)
+ elif new_status == expected_status:
+ return True # All good.
+ LOG.debug("Waiting for %s to get to %s status. "
+ "Currently in %s status",
+ thing, expected_status, new_status)
+ conf = config.TempestConfig()
+ if not call_until_true(check_status,
+ conf.compute.build_timeout,
+ conf.compute.build_interval):
+ self.fail("Timed out waiting for thing %s to become %s"
+ % (thing_id, expected_status))
+
+
+class DefaultClientSmokeTest(TestCase):
"""
- Base test case class that provides the default clients to access
- the various OpenStack APIs.
+ Base smoke test case class that provides the default clients to
+ access the various OpenStack APIs.
+
+ Smoke tests are tests that have the following characteristics:
+
+ * Test basic operations of an API, typically in an order that
+ a regular user would perform those operations
+ * Test only the correct inputs and action paths -- no fuzz or
+ random input data is sent, only valid inputs.
+ * Use only the default client tool for calling an API
"""
manager_class = manager.DefaultClientManager
- def status_timeout(self, things, thing_id, expected_status):
- """
- Given a thing and an expected status, do a loop, sleeping
- for a configurable amount of time, checking for the
- expected status to show. At any time, if the returned
- status of the thing is ERROR, fail out.
- """
- def check_status():
- # python-novaclient has resources available to its client
- # that all implement a get() method taking an identifier
- # for the singular resource to retrieve.
- thing = things.get(thing_id)
- new_status = thing.status
- if new_status == 'ERROR':
- self.fail("%s failed to get to expected status."
- "In ERROR state."
- % thing)
- elif new_status == expected_status:
- return True # All good.
- LOG.debug("Waiting for %s to get to %s status. "
- "Currently in %s status",
- thing, expected_status, new_status)
- if not call_until_true(check_status,
- self.config.compute.build_timeout,
- self.config.compute.build_interval):
- self.fail("Timed out waiting for thing %s to become %s"
- % (thing_id, expected_status))
+ @classmethod
+ def tearDownClass(cls):
+ # NOTE(jaypipes): Because smoke tests are typically run in a specific
+ # order, and because test methods in smoke tests generally create
+ # resources in a particular order, we destroy resources in the reverse
+ # order in which resources are added to the smoke test class object
+ while cls.os_resources:
+ thing = cls.os_resources.pop()
+ LOG.debug("Deleting %r from shared resources of %s" %
+ (thing, cls.__name__))
+
+ try:
+ # OpenStack resources are assumed to have a delete()
+ # method which destroys the resource...
+ thing.delete()
+ except Exception as e:
+ # If the resource is already missing, mission accomplished.
+ if e.__class__.__name__ == 'NotFound':
+ continue
+ raise
+
+ def is_deletion_complete():
+ # Deletion testing is only required for objects whose
+ # existence cannot be checked via retrieval.
+ if isinstance(thing, dict):
+ return True
+ try:
+ thing.get()
+ except Exception as e:
+ # Clients are expected to return an exception
+ # called 'NotFound' if retrieval fails.
+ if e.__class__.__name__ == 'NotFound':
+ return True
+ raise
+ return False
+
+ # Block until resource deletion has completed or timed-out
+ call_until_true(is_deletion_complete, 10, 1)
class ComputeFuzzClientTest(TestCase):
@@ -161,46 +210,3 @@
"""
manager_class = manager.ComputeFuzzClientManager
-
- def status_timeout(self, client_get_method, thing_id, expected_status):
- """
- Given a method to get a resource and an expected status, do a loop,
- sleeping for a configurable amount of time, checking for the
- expected status to show. At any time, if the returned
- status of the thing is ERROR, fail out.
-
- :param client_get_method: The callable that will retrieve the thing
- with ID :param:thing_id
- :param thing_id: The ID of the thing to get
- :param expected_status: String value of the expected status of the
- thing that we are looking for.
-
- :code ..
-
- Usage:
-
- def test_some_server_action(self):
- client = self.servers_client
- resp, server = client.create_server('random_server')
- self.status_timeout(client.get_server, server['id'], 'ACTIVE')
- """
- def check_status():
- # Tempest REST client has resources available to its client
- # that all implement a various get_$resource() methods taking
- # an identifier for the singular resource to retrieve.
- thing = client_get_method(thing_id)
- new_status = thing['status']
- if new_status == 'ERROR':
- self.fail("%s failed to get to expected status."
- "In ERROR state."
- % thing)
- elif new_status == expected_status:
- return True # All good.
- LOG.debug("Waiting for %s to get to %s status. "
- "Currently in %s status",
- thing, expected_status, new_status)
- if not call_until_true(check_status,
- self.config.compute.build_timeout,
- self.config.compute.build_interval):
- self.fail("Timed out waiting for thing %s to become %s"
- % (thing_id, expected_status))
diff --git a/tempest/tests/boto/test_s3_objects.py b/tempest/tests/boto/test_s3_objects.py
index c735215..dcb7c86 100644
--- a/tempest/tests/boto/test_s3_objects.py
+++ b/tempest/tests/boto/test_s3_objects.py
@@ -18,7 +18,6 @@
from contextlib import closing
from boto.s3.key import Key
-import testtools
from tempest import clients
from tempest.common.utils.data_utils import rand_name
diff --git a/tempest/tests/compute/admin/test_flavors.py b/tempest/tests/compute/admin/test_flavors.py
index 7fabf7a..7957009 100644
--- a/tempest/tests/compute/admin/test_flavors.py
+++ b/tempest/tests/compute/admin/test_flavors.py
@@ -39,6 +39,7 @@
raise cls.skipException(msg)
cls.client = cls.os_adm.flavors_client
+ cls.user_client = cls.os.flavors_client
cls.flavor_name_prefix = 'test_flavor_'
cls.ram = 512
cls.vcpus = 1
@@ -47,6 +48,11 @@
cls.swap = 1024
cls.rxtx = 2
+ def flavor_clean_up(self, flavor_id):
+ resp, body = self.client.delete_flavor(flavor_id)
+ self.assertEqual(resp.status, 202)
+ self.client.wait_for_resource_deletion(flavor_id)
+
@attr(type='positive')
def test_create_flavor(self):
# Create a flavor and ensure it is listed
@@ -54,43 +60,37 @@
flavor_name = rand_name(self.flavor_name_prefix)
new_flavor_id = rand_int_id(start=1000)
- try:
- #Create the flavor
- resp, flavor = self.client.create_flavor(flavor_name,
- self.ram, self.vcpus,
- self.disk,
- new_flavor_id,
- ephemeral=self.ephemeral,
- swap=self.swap,
- rxtx=self.rxtx)
- self.assertEqual(200, resp.status)
- self.assertEqual(flavor['name'], flavor_name)
- self.assertEqual(flavor['vcpus'], self.vcpus)
- self.assertEqual(flavor['disk'], self.disk)
- self.assertEqual(flavor['ram'], self.ram)
- self.assertEqual(int(flavor['id']), new_flavor_id)
- self.assertEqual(flavor['swap'], self.swap)
- self.assertEqual(flavor['rxtx_factor'], self.rxtx)
- self.assertEqual(flavor['OS-FLV-EXT-DATA:ephemeral'],
- self.ephemeral)
- if self._interface == "xml":
- XMLNS_OS_FLV_ACCESS = "http://docs.openstack.org/compute/ext/"\
- "flavor_access/api/v2"
- key = "{" + XMLNS_OS_FLV_ACCESS + "}is_public"
- self.assertEqual(flavor[key], "True")
- if self._interface == "json":
- self.assertEqual(flavor['os-flavor-access:is_public'], True)
+ #Create the flavor
+ resp, flavor = self.client.create_flavor(flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ ephemeral=self.ephemeral,
+ swap=self.swap,
+ rxtx=self.rxtx)
+ self.addCleanup(self.flavor_clean_up, flavor['id'])
+ self.assertEqual(200, resp.status)
+ self.assertEqual(flavor['name'], flavor_name)
+ self.assertEqual(flavor['vcpus'], self.vcpus)
+ self.assertEqual(flavor['disk'], self.disk)
+ self.assertEqual(flavor['ram'], self.ram)
+ self.assertEqual(int(flavor['id']), new_flavor_id)
+ self.assertEqual(flavor['swap'], self.swap)
+ self.assertEqual(flavor['rxtx_factor'], self.rxtx)
+ self.assertEqual(flavor['OS-FLV-EXT-DATA:ephemeral'],
+ self.ephemeral)
+ if self._interface == "xml":
+ XMLNS_OS_FLV_ACCESS = "http://docs.openstack.org/compute/ext/"\
+ "flavor_access/api/v2"
+ key = "{" + XMLNS_OS_FLV_ACCESS + "}is_public"
+ self.assertEqual(flavor[key], "True")
+ if self._interface == "json":
+ self.assertEqual(flavor['os-flavor-access:is_public'], True)
- #Verify flavor is retrieved
- resp, flavor = self.client.get_flavor_details(new_flavor_id)
- self.assertEqual(resp.status, 200)
- self.assertEqual(flavor['name'], flavor_name)
-
- finally:
- #Delete the flavor
- resp, body = self.client.delete_flavor(new_flavor_id)
- self.assertEqual(resp.status, 202)
- self.client.wait_for_resource_deletion(new_flavor_id)
+ #Verify flavor is retrieved
+ resp, flavor = self.client.get_flavor_details(new_flavor_id)
+ self.assertEqual(resp.status, 200)
+ self.assertEqual(flavor['name'], flavor_name)
@attr(type='positive')
def test_create_flavor_verify_entry_in_list_details(self):
@@ -99,29 +99,23 @@
flavor_name = rand_name(self.flavor_name_prefix)
new_flavor_id = rand_int_id(start=1000)
- try:
- #Create the flavor
- resp, flavor = self.client.create_flavor(flavor_name,
- self.ram, self.vcpus,
- self.disk,
- new_flavor_id,
- ephemeral=self.ephemeral,
- swap=self.swap,
- rxtx=self.rxtx)
- flag = False
- #Verify flavor is retrieved
- resp, flavors = self.client.list_flavors_with_detail()
- self.assertEqual(resp.status, 200)
- for flavor in flavors:
- if flavor['name'] == flavor_name:
- flag = True
- self.assertTrue(flag)
-
- finally:
- #Delete the flavor
- resp, body = self.client.delete_flavor(new_flavor_id)
- self.assertEqual(resp.status, 202)
- self.client.wait_for_resource_deletion(new_flavor_id)
+ #Create the flavor
+ resp, flavor = self.client.create_flavor(flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ ephemeral=self.ephemeral,
+ swap=self.swap,
+ rxtx=self.rxtx)
+ self.addCleanup(self.flavor_clean_up, flavor['id'])
+ flag = False
+ #Verify flavor is retrieved
+ resp, flavors = self.client.list_flavors_with_detail()
+ self.assertEqual(resp.status, 200)
+ for flavor in flavors:
+ if flavor['name'] == flavor_name:
+ flag = True
+ self.assertTrue(flag)
@attr(type='negative')
def test_get_flavor_details_for_deleted_flavor(self):
@@ -137,11 +131,11 @@
ephemeral=self.ephemeral,
swap=self.swap,
rxtx=self.rxtx)
- self.assertEquals(200, resp.status)
-
# Delete the flavor
- resp, _ = self.client.delete_flavor(new_flavor_id)
- self.assertEqual(resp.status, 202)
+ new_flavor_id = flavor['id']
+ resp_delete, body = self.client.delete_flavor(new_flavor_id)
+ self.assertEquals(200, resp.status)
+ self.assertEquals(202, resp_delete.status)
# Deleted flavors can be seen via detailed GET
resp, flavor = self.client.get_flavor_details(new_flavor_id)
@@ -163,46 +157,40 @@
flavor_name = rand_name(self.flavor_name_prefix)
new_flavor_id = rand_int_id(start=1000)
- try:
- #Create the flavor
- resp, flavor = self.client.create_flavor(flavor_name,
- self.ram, self.vcpus,
- self.disk,
- new_flavor_id)
- self.assertEqual(200, resp.status)
- self.assertEqual(flavor['name'], flavor_name)
- self.assertEqual(flavor['ram'], self.ram)
- self.assertEqual(flavor['vcpus'], self.vcpus)
- self.assertEqual(flavor['disk'], self.disk)
- self.assertEqual(int(flavor['id']), new_flavor_id)
- self.assertEqual(flavor['swap'], '')
- self.assertEqual(int(flavor['rxtx_factor']), 1)
- self.assertEqual(int(flavor['OS-FLV-EXT-DATA:ephemeral']), 0)
- if self._interface == "xml":
- XMLNS_OS_FLV_ACCESS = "http://docs.openstack.org/compute/ext/"\
- "flavor_access/api/v2"
- key = "{" + XMLNS_OS_FLV_ACCESS + "}is_public"
- self.assertEqual(flavor[key], "True")
- if self._interface == "json":
- self.assertEqual(flavor['os-flavor-access:is_public'], True)
+ #Create the flavor
+ resp, flavor = self.client.create_flavor(flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id)
+ self.addCleanup(self.flavor_clean_up, flavor['id'])
+ self.assertEqual(200, resp.status)
+ self.assertEqual(flavor['name'], flavor_name)
+ self.assertEqual(flavor['ram'], self.ram)
+ self.assertEqual(flavor['vcpus'], self.vcpus)
+ self.assertEqual(flavor['disk'], self.disk)
+ self.assertEqual(int(flavor['id']), new_flavor_id)
+ self.assertEqual(flavor['swap'], '')
+ self.assertEqual(int(flavor['rxtx_factor']), 1)
+ self.assertEqual(int(flavor['OS-FLV-EXT-DATA:ephemeral']), 0)
+ if self._interface == "xml":
+ XMLNS_OS_FLV_ACCESS = "http://docs.openstack.org/compute/ext/"\
+ "flavor_access/api/v2"
+ key = "{" + XMLNS_OS_FLV_ACCESS + "}is_public"
+ self.assertEqual(flavor[key], "True")
+ if self._interface == "json":
+ self.assertEqual(flavor['os-flavor-access:is_public'], True)
- #Verify flavor is retrieved
- resp, flavor = self.client.get_flavor_details(new_flavor_id)
- self.assertEqual(resp.status, 200)
- self.assertEqual(flavor['name'], flavor_name)
- #Check if flavor is present in list
- resp, flavors = self.client.list_flavors_with_detail()
- self.assertEqual(resp.status, 200)
- for flavor in flavors:
- if flavor['name'] == flavor_name:
- flag = True
- self.assertTrue(flag)
-
- finally:
- #Delete the flavor
- resp, body = self.client.delete_flavor(new_flavor_id)
- self.assertEqual(resp.status, 202)
- self.client.wait_for_resource_deletion(new_flavor_id)
+ #Verify flavor is retrieved
+ resp, flavor = self.client.get_flavor_details(new_flavor_id)
+ self.assertEqual(resp.status, 200)
+ self.assertEqual(flavor['name'], flavor_name)
+ #Check if flavor is present in list
+ resp, flavors = self.client.list_flavors_with_detail()
+ self.assertEqual(resp.status, 200)
+ for flavor in flavors:
+ if flavor['name'] == flavor_name:
+ flag = True
+ self.assertTrue(flag)
@attr(type='positive')
def test_flavor_not_public_verify_entry_not_in_list_details(self):
@@ -212,25 +200,21 @@
flavor_name = rand_name(self.flavor_name_prefix)
new_flavor_id = rand_int_id(start=1000)
- try:
- #Create the flavor
- resp, flavor = self.client.create_flavor(flavor_name,
- self.ram, self.vcpus,
- self.disk,
- new_flavor_id,
- is_public="False")
- flag = False
- #Verify flavor is retrieved
- resp, flavors = self.client.list_flavors_with_detail()
- self.assertEqual(resp.status, 200)
- for flavor in flavors:
- if flavor['name'] == flavor_name:
- flag = True
- self.assertFalse(flag)
- finally:
- #Delete the flavor
- resp, body = self.client.delete_flavor(new_flavor_id)
- self.assertEqual(resp.status, 202)
+ #Create the flavor
+ resp, flavor = self.client.create_flavor(flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ is_public="False")
+ self.addCleanup(self.flavor_clean_up, flavor['id'])
+ flag = False
+ #Verify flavor is retrieved
+ resp, flavors = self.client.list_flavors_with_detail()
+ self.assertEqual(resp.status, 200)
+ for flavor in flavors:
+ if flavor['name'] == flavor_name:
+ flag = True
+ self.assertFalse(flag)
def test_list_public_flavor_with_other_user(self):
#Create a Flavor with public access.
@@ -238,76 +222,65 @@
flavor_name = rand_name(self.flavor_name_prefix)
new_flavor_id = rand_int_id(start=1000)
- try:
#Create the flavor
- resp, flavor = self.client.create_flavor(flavor_name,
- self.ram, self.vcpus,
- self.disk,
- new_flavor_id,
- is_public="True")
- flag = False
- self.new_client = self.flavors_client
- #Verify flavor is retrieved with new user
- resp, flavors = self.new_client.list_flavors_with_detail()
- self.assertEqual(resp.status, 200)
- for flavor in flavors:
- if flavor['name'] == flavor_name:
- flag = True
- self.assertTrue(flag)
- finally:
- #Delete the flavor
- resp, body = self.client.delete_flavor(new_flavor_id)
- self.assertEqual(resp.status, 202)
- self.client.wait_for_resource_deletion(new_flavor_id)
+ resp, flavor = self.client.create_flavor(flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ is_public="True")
+ self.addCleanup(self.flavor_clean_up, flavor['id'])
+ flag = False
+ self.new_client = self.flavors_client
+ #Verify flavor is retrieved with new user
+ resp, flavors = self.new_client.list_flavors_with_detail()
+ self.assertEqual(resp.status, 200)
+ for flavor in flavors:
+ if flavor['name'] == flavor_name:
+ flag = True
+ self.assertTrue(flag)
@attr(type='positive')
def test_is_public_string_variations(self):
- try:
- flavor_id_not_public = rand_int_id(start=1000)
- flavor_name_not_public = rand_name(self.flavor_name_prefix)
- flavor_id_public = rand_int_id(start=1000)
- flavor_name_public = rand_name(self.flavor_name_prefix)
+ flavor_id_not_public = rand_int_id(start=1000)
+ flavor_name_not_public = rand_name(self.flavor_name_prefix)
+ flavor_id_public = rand_int_id(start=1000)
+ flavor_name_public = rand_name(self.flavor_name_prefix)
- # Create a non public flavor
- resp, flavor = self.client.create_flavor(flavor_name_not_public,
- self.ram, self.vcpus,
- self.disk,
- flavor_id_not_public,
- is_public="False")
+ # Create a non public flavor
+ resp, flavor = self.client.create_flavor(flavor_name_not_public,
+ self.ram, self.vcpus,
+ self.disk,
+ flavor_id_not_public,
+ is_public="False")
+ self.addCleanup(self.flavor_clean_up, flavor['id'])
- # Create a public flavor
- resp, flavor = self.client.create_flavor(flavor_name_public,
- self.ram, self.vcpus,
- self.disk,
- flavor_id_public,
- is_public="True")
+ # Create a public flavor
+ resp, flavor = self.client.create_flavor(flavor_name_public,
+ self.ram, self.vcpus,
+ self.disk,
+ flavor_id_public,
+ is_public="True")
+ self.addCleanup(self.flavor_clean_up, flavor['id'])
- def _flavor_lookup(flavors, flavor_name):
- for flavor in flavors:
- if flavor['name'] == flavor_name:
- return flavor
- return None
+ def _flavor_lookup(flavors, flavor_name):
+ for flavor in flavors:
+ if flavor['name'] == flavor_name:
+ return flavor
+ return None
- def _test_string_variations(variations, flavor_name):
- for string in variations:
- params = {'is_public': string}
- r, flavors = self.client.list_flavors_with_detail(params)
- self.assertEqual(r.status, 200)
- flavor = _flavor_lookup(flavors, flavor_name)
- self.assertNotEqual(flavor, None)
+ def _test_string_variations(variations, flavor_name):
+ for string in variations:
+ params = {'is_public': string}
+ r, flavors = self.client.list_flavors_with_detail(params)
+ self.assertEqual(r.status, 200)
+ flavor = _flavor_lookup(flavors, flavor_name)
+ self.assertNotEqual(flavor, None)
- _test_string_variations(['f', 'false', 'no', '0'],
- flavor_name_not_public)
+ _test_string_variations(['f', 'false', 'no', '0'],
+ flavor_name_not_public)
- _test_string_variations(['t', 'true', 'yes', '1'],
- flavor_name_public)
-
- finally:
- # Delete flavors
- for flavor_id in [flavor_id_not_public, flavor_id_public]:
- resp, body = self.client.delete_flavor(flavor_id)
- self.assertEqual(resp.status, 202)
- self.client.wait_for_resource_deletion(flavor_id)
+ _test_string_variations(['t', 'true', 'yes', '1'],
+ flavor_name_public)
@attr(type='negative')
def test_invalid_is_public_string(self):
@@ -315,7 +288,22 @@
self.client.list_flavors_with_detail,
{'is_public': 'invalid'})
-#TODO(afazekas): Negative tests with regular user
+ @attr(type='negative')
+ def test_create_flavor_as_user(self):
+ flavor_name = rand_name(self.flavor_name_prefix)
+ new_flavor_id = rand_int_id(start=1000)
+
+ self.assertRaises(exceptions.Unauthorized,
+ self.user_client.create_flavor,
+ flavor_name, self.ram, self.vcpus, self.disk,
+ new_flavor_id, ephemeral=self.ephemeral,
+ swap=self.swap, rxtx=self.rxtx)
+
+ @attr(type='negative')
+ def test_delete_flavor_as_user(self):
+ self.assertRaises(exceptions.Unauthorized,
+ self.user_client.delete_flavor,
+ self.flavor_ref_alt)
class FlavorsAdminTestXML(FlavorsAdminTestJSON):
diff --git a/tempest/tests/compute/admin/test_flavors_access.py b/tempest/tests/compute/admin/test_flavors_access.py
new file mode 100644
index 0000000..4cc3f57
--- /dev/null
+++ b/tempest/tests/compute/admin/test_flavors_access.py
@@ -0,0 +1,128 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 NEC Corporation.
+# 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 tempest.common.utils.data_utils import rand_int_id
+from tempest.common.utils.data_utils import rand_name
+from tempest import exceptions
+from tempest.test import attr
+from tempest.tests import compute
+from tempest.tests.compute import base
+
+
+class FlavorsAccessTestJSON(base.BaseComputeAdminTest):
+
+ """
+ Tests Flavor Access API extension.
+ Add and remove Flavor Access require admin privileges.
+ """
+
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(FlavorsAccessTestJSON, cls).setUpClass()
+ if not compute.FLAVOR_EXTRA_DATA_ENABLED:
+ msg = "FlavorExtraData extension not enabled."
+ raise cls.skipException(msg)
+
+ cls.client = cls.os_adm.flavors_client
+ admin_client = cls._get_identity_admin_client()
+ resp, tenants = admin_client.list_tenants()
+ cls.tenant_id = [tnt['id'] for tnt in tenants if tnt['name'] ==
+ cls.flavors_client.tenant_name][0]
+
+ cls.flavor_name_prefix = 'test_flavor_access_'
+ cls.ram = 512
+ cls.vcpus = 1
+ cls.disk = 10
+
+ @attr('positive')
+ def test_flavor_access_add_remove(self):
+ #Test to add and remove flavor access to a given tenant.
+ flavor_name = rand_name(self.flavor_name_prefix)
+ new_flavor_id = rand_int_id(start=1000)
+ resp, new_flavor = self.client.create_flavor(flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ is_public='False')
+ self.addCleanup(self.client.delete_flavor, new_flavor['id'])
+ #Add flavor access to a tenant.
+ resp_body = {
+ "tenant_id": str(self.tenant_id),
+ "flavor_id": str(new_flavor['id']),
+ }
+ add_resp, add_body = \
+ self.client.add_flavor_access(new_flavor['id'], self.tenant_id)
+ self.assertEqual(add_resp.status, 200)
+ self.assertIn(resp_body, add_body)
+
+ #The flavor is present in list.
+ resp, flavors = self.flavors_client.list_flavors_with_detail()
+ self.assertEqual(resp.status, 200)
+ self.assertIn(new_flavor['id'], map(lambda x: x['id'], flavors))
+
+ #Remove flavor access from a tenant.
+ remove_resp, remove_body = \
+ self.client.remove_flavor_access(new_flavor['id'], self.tenant_id)
+ self.assertEqual(remove_resp.status, 200)
+ self.assertNotIn(resp_body, remove_body)
+
+ #The flavor is not present in list.
+ resp, flavors = self.flavors_client.list_flavors_with_detail()
+ self.assertEqual(resp.status, 200)
+ self.assertNotIn(new_flavor['id'], map(lambda x: x['id'], flavors))
+
+ @attr('negative')
+ def test_flavor_non_admin_add(self):
+ #Test to add flavor access as a user without admin privileges.
+ flavor_name = rand_name(self.flavor_name_prefix)
+ new_flavor_id = rand_int_id(start=1000)
+ resp, new_flavor = self.client.create_flavor(flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ is_public='False')
+ self.addCleanup(self.client.delete_flavor, new_flavor['id'])
+ self.assertRaises(exceptions.Unauthorized,
+ self.flavors_client.add_flavor_access,
+ new_flavor['id'],
+ self.tenant_id)
+
+ @attr('negative')
+ def test_flavor_non_admin_remove(self):
+ #Test to remove flavor access as a user without admin privileges.
+ flavor_name = rand_name(self.flavor_name_prefix)
+ new_flavor_id = rand_int_id(start=1000)
+ resp, new_flavor = self.client.create_flavor(flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ is_public='False')
+ self.addCleanup(self.client.delete_flavor, new_flavor['id'])
+ #Add flavor access to a tenant.
+ self.client.add_flavor_access(new_flavor['id'], self.tenant_id)
+ self.addCleanup(self.client.remove_flavor_access,
+ new_flavor['id'], self.tenant_id)
+ self.assertRaises(exceptions.Unauthorized,
+ self.flavors_client.remove_flavor_access,
+ new_flavor['id'],
+ self.tenant_id)
+
+
+class FlavorsAdminTestXML(FlavorsAccessTestJSON):
+ _interface = 'xml'
diff --git a/tempest/tests/compute/admin/test_quotas.py b/tempest/tests/compute/admin/test_quotas.py
index befcad3..5a9b6f9 100644
--- a/tempest/tests/compute/admin/test_quotas.py
+++ b/tempest/tests/compute/admin/test_quotas.py
@@ -44,7 +44,7 @@
cls.default_quota_set = {'injected_file_content_bytes': 10240,
'metadata_items': 128, 'injected_files': 5,
'ram': 51200, 'floating_ips': 10,
- 'fixed_ips': 10, 'key_pairs': 100,
+ 'fixed_ips': -1, 'key_pairs': 100,
'injected_file_path_bytes': 255,
'instances': 10, 'security_group_rules': 20,
'cores': 20, 'security_groups': 10}
@@ -60,9 +60,6 @@
@attr(type='smoke')
def test_get_default_quotas(self):
- # Tempest two step
- self.skipTest('Skipped until the Bug 1125468 is resolved')
-
# Admin can get the default resource quota set for a tenant
expected_quota_set = self.default_quota_set.copy()
expected_quota_set['id'] = self.demo_tenant_id
@@ -74,9 +71,6 @@
self.fail("Admin could not get the default quota set for a tenant")
def test_update_all_quota_resources_for_tenant(self):
- # Tempest two step
- self.skipTest('Skipped until the Bug 1125468 is resolved')
-
# Admin can update all the resource quota limits for a tenant
new_quota_set = {'injected_file_content_bytes': 20480,
'metadata_items': 256, 'injected_files': 10,
@@ -89,6 +83,8 @@
resp, quota_set = self.adm_client.update_quota_set(
self.demo_tenant_id,
**new_quota_set)
+ self.addCleanup(self.adm_client.update_quota_set,
+ self.demo_tenant_id, **self.default_quota_set)
self.assertEqual(200, resp.status)
self.assertEqual(new_quota_set, quota_set)
except Exception:
@@ -103,12 +99,11 @@
#TODO(afazekas): merge these test cases
def test_get_updated_quotas(self):
- # Tempest two step
- self.skipTest('Skipped until the Bug 1125468 is resolved')
-
# Verify that GET shows the updated quota set
self.adm_client.update_quota_set(self.demo_tenant_id,
ram='5120')
+ self.addCleanup(self.adm_client.update_quota_set,
+ self.demo_tenant_id, **self.default_quota_set)
try:
resp, quota_set = self.client.get_quota_set(self.demo_tenant_id)
self.assertEqual(200, resp.status)
diff --git a/tempest/tests/compute/base.py b/tempest/tests/compute/base.py
index 3b2026e..87aa889 100644
--- a/tempest/tests/compute/base.py
+++ b/tempest/tests/compute/base.py
@@ -96,47 +96,45 @@
operate in an isolated tenant container.
"""
admin_client = cls._get_identity_admin_client()
- rand_name_root = rand_name(cls.__name__)
- if cls.isolated_creds:
- # Main user already created. Create the alt one...
- rand_name_root += '-alt'
- username = rand_name_root + "-user"
- email = rand_name_root + "@example.com"
- tenant_name = rand_name_root + "-tenant"
- tenant_desc = tenant_name + "-desc"
password = "pass"
- try:
- resp, tenant = admin_client.create_tenant(name=tenant_name,
- description=tenant_desc)
- except exceptions.Duplicate:
- if cls.config.compute.allow_tenant_reuse:
- tenant = admin_client.get_tenant_by_name(tenant_name)
- LOG.info('Re-using existing tenant %s' % tenant)
- else:
- msg = ('Unable to create isolated tenant %s because ' +
- 'it already exists. If this is related to a ' +
- 'previous test failure, try using ' +
- 'allow_tenant_reuse in tempest.conf') % tenant_name
- raise exceptions.Duplicate(msg)
+ while True:
+ try:
+ rand_name_root = rand_name(cls.__name__)
+ if cls.isolated_creds:
+ # Main user already created. Create the alt one...
+ rand_name_root += '-alt'
+ tenant_name = rand_name_root + "-tenant"
+ tenant_desc = tenant_name + "-desc"
- try:
- resp, user = admin_client.create_user(username,
- password,
- tenant['id'],
- email)
- except exceptions.Duplicate:
- if cls.config.compute.allow_tenant_reuse:
- user = admin_client.get_user_by_username(tenant['id'],
- username)
- LOG.info('Re-using existing user %s' % user)
- else:
- msg = ('Unable to create isolated user %s because ' +
- 'it already exists. If this is related to a ' +
- 'previous test failure, try using ' +
- 'allow_tenant_reuse in tempest.conf') % tenant_name
- raise exceptions.Duplicate(msg)
+ resp, tenant = admin_client.create_tenant(
+ name=tenant_name, description=tenant_desc)
+ break
+ except exceptions.Duplicate:
+ if cls.config.compute.allow_tenant_reuse:
+ tenant = admin_client.get_tenant_by_name(tenant_name)
+ LOG.info('Re-using existing tenant %s', tenant)
+ break
+ while True:
+ try:
+ rand_name_root = rand_name(cls.__name__)
+ if cls.isolated_creds:
+ # Main user already created. Create the alt one...
+ rand_name_root += '-alt'
+ username = rand_name_root + "-user"
+ email = rand_name_root + "@example.com"
+ resp, user = admin_client.create_user(username,
+ password,
+ tenant['id'],
+ email)
+ break
+ except exceptions.Duplicate:
+ if cls.config.compute.allow_tenant_reuse:
+ user = admin_client.get_user_by_username(tenant['id'],
+ username)
+ LOG.info('Re-using existing user %s', user)
+ break
# Store the complete creds (including UUID ids...) for later
# but return just the username, tenant_name, password tuple
# that the various clients will use.
@@ -184,12 +182,12 @@
resp, server = cls.servers_client.create_server(
name, image_id, flavor, **kwargs)
+ cls.servers.append(server)
if 'wait_until' in kwargs:
cls.servers_client.wait_for_server_status(
server['id'], kwargs['wait_until'])
- cls.servers.append(server)
return resp, server
def wait_for(self, condition):
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 888481a..2b21710 100644
--- a/tempest/tests/compute/floating_ips/test_floating_ips_actions.py
+++ b/tempest/tests/compute/floating_ips/test_floating_ips_actions.py
@@ -73,6 +73,14 @@
#Deleting the floating IP which is created in this method
self.client.delete_floating_ip(floating_ip_id_allocated)
+ @attr(type='negative')
+ def test_allocate_floating_ip_from_nonexistent_pool(self):
+ # Positive test:Allocation of a new floating IP from a nonexistent_pool
+ #to a project should fail
+ self.assertRaises(exceptions.NotFound,
+ self.client.create_floating_ip,
+ "non_exist_pool")
+
@attr(type='positive')
def test_delete_floating_ip(self):
# Positive test:Deletion of valid floating IP from project
diff --git a/tempest/tests/compute/images/test_image_metadata.py b/tempest/tests/compute/images/test_image_metadata.py
index 918075c..5d6439b 100644
--- a/tempest/tests/compute/images/test_image_metadata.py
+++ b/tempest/tests/compute/images/test_image_metadata.py
@@ -21,12 +21,12 @@
from tempest.tests.compute import base
-class ImagesMetadataTest(base.BaseComputeTest):
+class ImagesMetadataTestJSON(base.BaseComputeTest):
_interface = 'json'
@classmethod
def setUpClass(cls):
- super(ImagesMetadataTest, cls).setUpClass()
+ super(ImagesMetadataTestJSON, cls).setUpClass()
cls.servers_client = cls.servers_client
cls.client = cls.images_client
@@ -44,10 +44,10 @@
@classmethod
def tearDownClass(cls):
cls.client.delete_image(cls.image_id)
- super(ImagesMetadataTest, cls).tearDownClass()
+ super(ImagesMetadataTestJSON, cls).tearDownClass()
def setUp(self):
- super(ImagesMetadataTest, self).setUp()
+ super(ImagesMetadataTestJSON, self).setUp()
meta = {'key1': 'value1', 'key2': 'value2'}
resp, _ = self.client.set_image_metadata(self.image_id, meta)
self.assertEqual(resp.status, 200)
@@ -143,3 +143,7 @@
# item from nonexistant image
self.assertRaises(exceptions.NotFound,
self.client.delete_image_metadata_item, 999, 'key1')
+
+
+class ImagesMetadataTestXML(ImagesMetadataTestJSON):
+ _interface = 'xml'
diff --git a/tempest/tests/compute/images/test_images.py b/tempest/tests/compute/images/test_images.py
index fb0364a..d9e4153 100644
--- a/tempest/tests/compute/images/test_images.py
+++ b/tempest/tests/compute/images/test_images.py
@@ -108,7 +108,7 @@
self.assertRaises(exceptions.Duplicate, self.client.create_image,
server['id'], snapshot_name)
- @testtools.skip("Until Bug 1039739 is fixed")
+ @testtools.skip("Until Bug #1039739 is fixed")
@attr(type='negative')
def test_create_image_when_server_is_rebooting(self):
# Return error when creating an image of server that is rebooting
diff --git a/tempest/tests/compute/images/test_images_oneserver.py b/tempest/tests/compute/images/test_images_oneserver.py
index f7008f0..ca3dbb5 100644
--- a/tempest/tests/compute/images/test_images_oneserver.py
+++ b/tempest/tests/compute/images/test_images_oneserver.py
@@ -41,7 +41,12 @@
super(ImagesOneServerTestJSON, cls).setUpClass()
cls.client = cls.images_client
cls.servers_client = cls.servers_client
- resp, cls.server = cls.create_server(wait_until='ACTIVE')
+
+ try:
+ resp, cls.server = cls.create_server(wait_until='ACTIVE')
+ except Exception:
+ cls.tearDownClass()
+ raise
cls.image_ids = []
@@ -58,7 +63,7 @@
cls.alt_client = cls.alt_manager.images_client
@attr(type='negative')
- @testtools.skip("Until Bug 1006725 is fixed")
+ @testtools.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
snapshot_name = rand_name('\xef\xbb\xbf')
@@ -67,7 +72,7 @@
snapshot_name)
@attr(type='negative')
- @testtools.skip("Until Bug 1005423 is fixed")
+ @testtools.skip("Until Bug #1005423 is fixed")
def test_create_image_specify_invalid_metadata(self):
# Return an error when creating image with invalid metadata
snapshot_name = rand_name('test-snap-')
@@ -76,7 +81,7 @@
self.server['id'], snapshot_name, meta)
@attr(type='negative')
- @testtools.skip("Until Bug 1005423 is fixed")
+ @testtools.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
snapshot_name = rand_name('test-snap-')
diff --git a/tempest/tests/compute/images/test_list_image_filters.py b/tempest/tests/compute/images/test_list_image_filters.py
index 472f7fb..b1a6f77 100644
--- a/tempest/tests/compute/images/test_list_image_filters.py
+++ b/tempest/tests/compute/images/test_list_image_filters.py
@@ -22,50 +22,61 @@
from tempest.tests.compute import base
-class ListImageFiltersTest(base.BaseComputeTest):
+class ListImageFiltersTestJSON(base.BaseComputeTest):
_interface = 'json'
@classmethod
def setUpClass(cls):
- super(ListImageFiltersTest, cls).setUpClass()
+ super(ListImageFiltersTestJSON, cls).setUpClass()
cls.client = cls.images_client
- resp, cls.server1 = cls.create_server()
- resp, cls.server2 = cls.create_server(wait_until='ACTIVE')
- # NOTE(sdague) this is faster than doing the sync wait_util on both
- cls.servers_client.wait_for_server_status(cls.server1['id'], 'ACTIVE')
+ try:
+ resp, cls.server1 = cls.create_server()
+ resp, cls.server2 = cls.create_server(wait_until='ACTIVE')
+ # NOTE(sdague) this is faster than doing the sync wait_util on both
+ cls.servers_client.wait_for_server_status(cls.server1['id'],
+ 'ACTIVE')
- # Create images to be used in the filter tests
- image1_name = rand_name('image')
- resp, body = cls.client.create_image(cls.server1['id'], image1_name)
- cls.image1_id = parse_image_id(resp['location'])
- cls.client.wait_for_image_resp_code(cls.image1_id, 200)
- cls.client.wait_for_image_status(cls.image1_id, 'ACTIVE')
- resp, cls.image1 = cls.client.get_image(cls.image1_id)
+ # Create images to be used in the filter tests
+ image1_name = rand_name('image')
+ resp, body = cls.client.create_image(cls.server1['id'],
+ image1_name)
+ cls.image1_id = parse_image_id(resp['location'])
+ cls.client.wait_for_image_resp_code(cls.image1_id, 200)
+ cls.client.wait_for_image_status(cls.image1_id, 'ACTIVE')
+ resp, cls.image1 = cls.client.get_image(cls.image1_id)
- # Servers have a hidden property for when they are being imaged
- # Performing back-to-back create image calls on a single
- # server will sometimes cause failures
- image3_name = rand_name('image')
- resp, body = cls.client.create_image(cls.server2['id'], image3_name)
- cls.image3_id = parse_image_id(resp['location'])
- cls.client.wait_for_image_resp_code(cls.image3_id, 200)
- cls.client.wait_for_image_status(cls.image3_id, 'ACTIVE')
- resp, cls.image3 = cls.client.get_image(cls.image3_id)
+ # Servers have a hidden property for when they are being imaged
+ # Performing back-to-back create image calls on a single
+ # server will sometimes cause failures
+ image3_name = rand_name('image')
+ resp, body = cls.client.create_image(cls.server2['id'],
+ image3_name)
+ cls.image3_id = parse_image_id(resp['location'])
+ cls.client.wait_for_image_resp_code(cls.image3_id, 200)
+ cls.client.wait_for_image_status(cls.image3_id, 'ACTIVE')
+ resp, cls.image3 = cls.client.get_image(cls.image3_id)
- image2_name = rand_name('image')
- resp, body = cls.client.create_image(cls.server1['id'], image2_name)
- cls.image2_id = parse_image_id(resp['location'])
- cls.client.wait_for_image_resp_code(cls.image2_id, 200)
- cls.client.wait_for_image_status(cls.image2_id, 'ACTIVE')
- resp, cls.image2 = cls.client.get_image(cls.image2_id)
+ image2_name = rand_name('image')
+ resp, body = cls.client.create_image(cls.server1['id'],
+ image2_name)
+ cls.image2_id = parse_image_id(resp['location'])
+ cls.client.wait_for_image_resp_code(cls.image2_id, 200)
+ cls.client.wait_for_image_status(cls.image2_id, 'ACTIVE')
+ resp, cls.image2 = cls.client.get_image(cls.image2_id)
+ except Exception:
+ cls.clear_servers()
+ cls.client.delete_image(cls.image1_id)
+ cls.client.delete_image(cls.image2_id)
+ cls.client.delete_image(cls.image3_id)
+ raise
@classmethod
def tearDownClass(cls):
cls.client.delete_image(cls.image1_id)
cls.client.delete_image(cls.image2_id)
cls.client.delete_image(cls.image3_id)
- super(ListImageFiltersTest, cls).tearDownClass()
+ super(ListImageFiltersTestJSON, cls).tearDownClass()
@attr(type='negative')
def test_get_image_not_existing(self):
@@ -140,7 +151,9 @@
# Verify only the expected number of results are returned
params = {'limit': '1'}
resp, images = self.client.list_images(params)
- self.assertEqual(1, len(images))
+ #when _interface='xml', one element for images_links in images
+ #ref: Question #224349
+ self.assertEqual(1, len([x for x in images if 'id' in x]))
@attr(type='positive')
def test_list_images_filter_by_changes_since(self):
@@ -226,3 +239,7 @@
def test_get_nonexistant_image(self):
# Negative test: GET on non existant image should fail
self.assertRaises(exceptions.NotFound, self.client.get_image, 999)
+
+
+class ListImageFiltersTestXML(ListImageFiltersTestJSON):
+ _interface = 'xml'
diff --git a/tempest/tests/compute/images/test_list_images.py b/tempest/tests/compute/images/test_list_images.py
index d583a95..07c48fe 100644
--- a/tempest/tests/compute/images/test_list_images.py
+++ b/tempest/tests/compute/images/test_list_images.py
@@ -19,18 +19,14 @@
from tempest.tests.compute import base
-class ListImagesTest(base.BaseComputeTest):
+class ListImagesTestJSON(base.BaseComputeTest):
_interface = 'json'
@classmethod
def setUpClass(cls):
- super(ListImagesTest, cls).setUpClass()
+ super(ListImagesTestJSON, cls).setUpClass()
cls.client = cls.images_client
- @classmethod
- def tearDownClass(cls):
- super(ListImagesTest, cls).tearDownClass()
-
@attr(type='smoke')
def test_get_image(self):
# Returns the correct details for a single image
@@ -50,3 +46,7 @@
resp, images = self.client.list_images_with_detail()
found = any([i for i in images if i['id'] == self.image_ref])
self.assertTrue(found)
+
+
+class ListImagesTestXML(ListImagesTestJSON):
+ _interface = 'xml'
diff --git a/tempest/tests/compute/security_groups/test_security_group_rules.py b/tempest/tests/compute/security_groups/test_security_group_rules.py
index 99d9a5d..c2032d4 100644
--- a/tempest/tests/compute/security_groups/test_security_group_rules.py
+++ b/tempest/tests/compute/security_groups/test_security_group_rules.py
@@ -33,30 +33,24 @@
def test_security_group_rules_create(self):
# Positive test: Creation of Security Group rule
# should be successfull
- try:
- #Creating a Security Group to add rules to it
- s_name = rand_name('securitygroup-')
- s_description = rand_name('description-')
- resp, securitygroup = \
- self.client.create_security_group(s_name, s_description)
- securitygroup_id = securitygroup['id']
- #Adding rules to the created Security Group
- parent_group_id = securitygroup['id']
- ip_protocol = 'tcp'
- from_port = 22
- to_port = 22
- resp, rule = \
- self.client.create_security_group_rule(parent_group_id,
- ip_protocol,
- from_port,
- to_port)
- self.assertEqual(200, resp.status)
- finally:
- #Deleting the Security Group rule, created in this method
- group_rule_id = rule['id']
- self.client.delete_security_group_rule(group_rule_id)
- #Deleting the Security Group created in this method
- resp, _ = self.client.delete_security_group(securitygroup_id)
+ #Creating a Security Group to add rules to it
+ s_name = rand_name('securitygroup-')
+ s_description = rand_name('description-')
+ resp, securitygroup = \
+ self.client.create_security_group(s_name, s_description)
+ securitygroup_id = securitygroup['id']
+ self.addCleanup(self.client.delete_security_group, securitygroup_id)
+ #Adding rules to the created Security Group
+ ip_protocol = 'tcp'
+ from_port = 22
+ to_port = 22
+ resp, rule = \
+ self.client.create_security_group_rule(securitygroup_id,
+ ip_protocol,
+ from_port,
+ to_port)
+ self.addCleanup(self.client.delete_security_group_rule, rule['id'])
+ self.assertEqual(200, resp.status)
@attr(type='positive')
def test_security_group_rules_create_with_optional_arguments(self):
@@ -64,75 +58,38 @@
# with optional arguments
# should be successfull
- rule_id = None
secgroup1 = None
secgroup2 = None
- try:
- #Creating a Security Group to add rules to it
- s_name = rand_name('securitygroup-')
- s_description = rand_name('description-')
- resp, securitygroup = \
- self.client.create_security_group(s_name, s_description)
- secgroup1 = securitygroup['id']
- #Creating a Security Group so as to assign group_id to the rule
- s_name2 = rand_name('securitygroup-')
- s_description2 = rand_name('description-')
- resp, securitygroup = \
- self.client.create_security_group(s_name2, s_description2)
- secgroup2 = securitygroup['id']
- #Adding rules to the created Security Group with optional arguments
- parent_group_id = secgroup1
- ip_protocol = 'tcp'
- from_port = 22
- to_port = 22
- cidr = '10.2.3.124/24'
- group_id = secgroup2
- resp, rule = \
- self.client.create_security_group_rule(parent_group_id,
- ip_protocol,
- from_port,
- to_port,
- cidr=cidr,
- group_id=group_id)
- rule_id = rule['id']
- self.assertEqual(200, resp.status)
- finally:
- #Deleting the Security Group rule, created in this method
- if rule_id:
- self.client.delete_security_group_rule(rule_id)
- #Deleting the Security Groups created in this method
- if secgroup1:
- self.client.delete_security_group(secgroup1)
- if secgroup2:
- self.client.delete_security_group(secgroup2)
-
- @attr(type='positive')
- def test_security_group_rules_create_delete(self):
- # Positive test: Deletion of Security Group rule
- # should be successfull
- try:
- #Creating a Security Group to add rule to it
- s_name = rand_name('securitygroup-')
- s_description = rand_name('description-')
- resp, securitygroup = \
- self.client.create_security_group(s_name, s_description)
- securitygroup_id = securitygroup['id']
- #Adding rules to the created Security Group
- parent_group_id = securitygroup['id']
- ip_protocol = 'tcp'
- from_port = 22
- to_port = 22
- resp, rule = \
- self.client.create_security_group_rule(parent_group_id,
- ip_protocol,
- from_port,
- to_port)
- finally:
- #Deleting the Security Group rule, created in this method
- group_rule_id = rule['id']
- self.client.delete_security_group_rule(group_rule_id)
- #Deleting the Security Group created in this method
- resp, _ = self.client.delete_security_group(securitygroup_id)
+ #Creating a Security Group to add rules to it
+ s_name = rand_name('securitygroup-')
+ s_description = rand_name('description-')
+ resp, securitygroup = \
+ self.client.create_security_group(s_name, s_description)
+ secgroup1 = securitygroup['id']
+ self.addCleanup(self.client.delete_security_group, secgroup1)
+ #Creating a Security Group so as to assign group_id to the rule
+ s_name2 = rand_name('securitygroup-')
+ s_description2 = rand_name('description-')
+ resp, securitygroup = \
+ self.client.create_security_group(s_name2, s_description2)
+ secgroup2 = securitygroup['id']
+ self.addCleanup(self.client.delete_security_group, secgroup2)
+ #Adding rules to the created Security Group with optional arguments
+ parent_group_id = secgroup1
+ ip_protocol = 'tcp'
+ from_port = 22
+ to_port = 22
+ cidr = '10.2.3.124/24'
+ group_id = secgroup2
+ resp, rule = \
+ self.client.create_security_group_rule(parent_group_id,
+ ip_protocol,
+ from_port,
+ to_port,
+ cidr=cidr,
+ group_id=group_id)
+ self.addCleanup(self.client.delete_security_group_rule, rule['id'])
+ self.assertEqual(200, resp.status)
@attr(type='negative')
def test_security_group_rules_create_with_invalid_id(self):
diff --git a/tempest/tests/compute/security_groups/test_security_groups.py b/tempest/tests/compute/security_groups/test_security_groups.py
index 70a01a0..d0afde4 100644
--- a/tempest/tests/compute/security_groups/test_security_groups.py
+++ b/tempest/tests/compute/security_groups/test_security_groups.py
@@ -29,79 +29,80 @@
super(SecurityGroupsTestJSON, cls).setUpClass()
cls.client = cls.security_groups_client
+ def _delete_security_group(self, securitygroup_id):
+ resp, _ = self.client.delete_security_group(securitygroup_id)
+ self.assertEqual(202, resp.status)
+
@attr(type='positive')
def test_security_groups_create_list_delete(self):
# Positive test:Should return the list of Security Groups
- try:
- #Create 3 Security Groups
- security_group_list = list()
- for i in range(3):
- s_name = rand_name('securitygroup-')
- s_description = rand_name('description-')
- resp, securitygroup = \
- self.client.create_security_group(s_name, s_description)
- self.assertEqual(200, resp.status)
- security_group_list.append(securitygroup)
- #Fetch all Security Groups and verify the list
- #has all created Security Groups
- resp, fetched_list = self.client.list_security_groups()
- self.assertEqual(200, resp.status)
- #Now check if all the created Security Groups are in fetched list
- missing_sgs = \
- [sg for sg in security_group_list if sg not in fetched_list]
- self.assertFalse(missing_sgs,
- "Failed to find Security Group %s in fetched "
- "list" % ', '.join(m_group['name']
- for m_group in missing_sgs))
- finally:
- #Delete all the Security Groups created in this method
- for securitygroup in security_group_list:
- resp, _ = \
- self.client.delete_security_group(securitygroup['id'])
- self.assertEqual(202, resp.status)
-
- @attr(type='positive')
- def test_security_group_create_delete(self):
- # Security Group should be created, verified and deleted
- try:
+ #Create 3 Security Groups
+ security_group_list = list()
+ for i in range(3):
s_name = rand_name('securitygroup-')
s_description = rand_name('description-')
resp, securitygroup = \
self.client.create_security_group(s_name, s_description)
self.assertEqual(200, resp.status)
- self.assertTrue('id' in securitygroup)
- securitygroup_id = securitygroup['id']
- self.assertFalse(securitygroup_id is None)
- self.assertTrue('name' in securitygroup)
- securitygroup_name = securitygroup['name']
- self.assertEqual(securitygroup_name, s_name,
- "The created Security Group name is "
- "not equal to the requested name")
- finally:
- #Delete Security Group created in this method
- resp, _ = self.client.delete_security_group(securitygroup['id'])
- self.assertEqual(202, resp.status)
+ self.addCleanup(self._delete_security_group,
+ securitygroup['id'])
+ security_group_list.append(securitygroup)
+ #Fetch all Security Groups and verify the list
+ #has all created Security Groups
+ resp, fetched_list = self.client.list_security_groups()
+ self.assertEqual(200, resp.status)
+ #Now check if all the created Security Groups are in fetched list
+ missing_sgs = \
+ [sg for sg in security_group_list if sg not in fetched_list]
+ self.assertFalse(missing_sgs,
+ "Failed to find Security Group %s in fetched "
+ "list" % ', '.join(m_group['name']
+ for m_group in missing_sgs))
+
+ #TODO(afazekas): scheduled for delete,
+ #test_security_group_create_get_delete covers it
+ @attr(type='positive')
+ def test_security_group_create_delete(self):
+ # Security Group should be created, verified and deleted
+ s_name = rand_name('securitygroup-')
+ s_description = rand_name('description-')
+ resp, securitygroup = \
+ self.client.create_security_group(s_name, s_description)
+ self.assertTrue('id' in securitygroup)
+ securitygroup_id = securitygroup['id']
+ self.addCleanup(self._delete_security_group,
+ securitygroup_id)
+ self.assertEqual(200, resp.status)
+ self.assertFalse(securitygroup_id is None)
+ self.assertTrue('name' in securitygroup)
+ securitygroup_name = securitygroup['name']
+ self.assertEqual(securitygroup_name, s_name,
+ "The created Security Group name is "
+ "not equal to the requested name")
@attr(type='positive')
def test_security_group_create_get_delete(self):
# Security Group should be created, fetched and deleted
- try:
- s_name = rand_name('securitygroup-')
- s_description = rand_name('description-')
- resp, securitygroup = \
- self.client.create_security_group(s_name, s_description)
- self.assertEqual(200, resp.status)
- #Now fetch the created Security Group by its 'id'
- resp, fetched_group = \
- self.client.get_security_group(securitygroup['id'])
- self.assertEqual(200, resp.status)
- self.assertEqual(securitygroup, fetched_group,
- "The fetched Security Group is different "
- "from the created Group")
- finally:
- #Delete the Security Group created in this method
- resp, _ = self.client.delete_security_group(securitygroup['id'])
- self.assertEqual(202, resp.status)
+ s_name = rand_name('securitygroup-')
+ s_description = rand_name('description-')
+ resp, securitygroup = \
+ self.client.create_security_group(s_name, s_description)
+ self.addCleanup(self._delete_security_group,
+ securitygroup['id'])
+
+ self.assertEqual(200, resp.status)
+ self.assertTrue('name' in securitygroup)
+ securitygroup_name = securitygroup['name']
+ self.assertEqual(securitygroup_name, s_name,
+ "The created Security Group name is "
+ "not equal to the requested name")
+ #Now fetch the created Security Group by its 'id'
+ resp, fetched_group = \
+ self.client.get_security_group(securitygroup['id'])
+ self.assertEqual(200, resp.status)
+ self.assertEqual(securitygroup, fetched_group,
+ "The fetched Security Group is different "
+ "from the created Group")
@attr(type='negative')
def test_security_group_get_nonexistant_group(self):
diff --git a/tempest/tests/compute/servers/test_create_server.py b/tempest/tests/compute/servers/test_create_server.py
index aaab9fa..b522992 100644
--- a/tempest/tests/compute/servers/test_create_server.py
+++ b/tempest/tests/compute/servers/test_create_server.py
@@ -33,7 +33,7 @@
class ServersTestJSON(base.BaseComputeTest):
_interface = 'json'
run_ssh = tempest.config.TempestConfig().compute.run_ssh
- disk_config = None
+ disk_config = 'AUTO'
@classmethod
def setUpClass(cls):
@@ -118,18 +118,6 @@
@attr(type='positive')
-class ServersTestAutoDisk(ServersTestJSON):
- disk_config = 'AUTO'
-
- @classmethod
- def setUpClass(cls):
- if not compute.DISK_CONFIG_ENABLED:
- msg = "DiskConfig extension not enabled."
- raise cls.skipException(msg)
- super(ServersTestAutoDisk, cls).setUpClass()
-
-
-@attr(type='positive')
class ServersTestManualDisk(ServersTestJSON):
disk_config = 'MANUAL'
diff --git a/tempest/tests/compute/servers/test_disk_config.py b/tempest/tests/compute/servers/test_disk_config.py
index 2fbb876..fe1c271 100644
--- a/tempest/tests/compute/servers/test_disk_config.py
+++ b/tempest/tests/compute/servers/test_disk_config.py
@@ -22,7 +22,7 @@
from tempest.tests.compute import base
-class TestServerDiskConfig(base.BaseComputeTest):
+class ServerDiskConfigTestJSON(base.BaseComputeTest):
_interface = 'json'
@classmethod
@@ -30,7 +30,7 @@
if not compute.DISK_CONFIG_ENABLED:
msg = "DiskConfig extension not enabled."
raise cls.skipException(msg)
- super(TestServerDiskConfig, cls).setUpClass()
+ super(ServerDiskConfigTestJSON, cls).setUpClass()
cls.client = cls.os.servers_client
@attr(type='positive')
@@ -120,3 +120,7 @@
#Delete the server
resp, body = self.client.delete_server(server['id'])
+
+
+class ServerDiskConfigTestXML(ServerDiskConfigTestJSON):
+ _interface = 'xml'
diff --git a/tempest/tests/compute/servers/test_list_server_filters.py b/tempest/tests/compute/servers/test_list_server_filters.py
index ff599fe..4d2b99f 100644
--- a/tempest/tests/compute/servers/test_list_server_filters.py
+++ b/tempest/tests/compute/servers/test_list_server_filters.py
@@ -126,11 +126,12 @@
self.assertIn(self.s3['id'], map(lambda x: x['id'], servers))
@attr(type='positive')
- def test_list_servers_detailed_filter_by_limit(self):
+ def test_list_servers_filter_by_limit(self):
# Verify only the expected number of servers are returned
params = {'limit': 1}
- resp, servers = self.client.list_servers_with_detail(params)
- self.assertEqual(1, len(servers['servers']))
+ resp, servers = self.client.list_servers(params)
+ #when _interface='xml', one element for servers_links in servers
+ self.assertEqual(1, len([x for x in servers['servers'] if 'id' in x]))
@utils.skip_unless_attr('multiple_images', 'Only one image found')
@attr(type='positive')
diff --git a/tempest/tests/compute/servers/test_list_servers_negative.py b/tempest/tests/compute/servers/test_list_servers_negative.py
index 01b11e0..0559206 100644
--- a/tempest/tests/compute/servers/test_list_servers_negative.py
+++ b/tempest/tests/compute/servers/test_list_servers_negative.py
@@ -22,12 +22,12 @@
from tempest.tests.compute import base
-class ListServersNegativeTest(base.BaseComputeTest):
+class ListServersNegativeTestJSON(base.BaseComputeTest):
_interface = 'json'
@classmethod
def setUpClass(cls):
- super(ListServersNegativeTest, cls).setUpClass()
+ super(ListServersNegativeTestJSON, cls).setUpClass()
cls.client = cls.servers_client
cls.servers = []
@@ -138,7 +138,8 @@
# List servers by specifying limits
resp, body = self.client.list_servers({'limit': 1})
self.assertEqual('200', resp['status'])
- self.assertEqual(1, len(body['servers']))
+ #when _interface='xml', one element for servers_links in servers
+ self.assertEqual(1, len([x for x in body['servers'] if 'id' in x]))
def test_list_servers_by_limits_greater_than_actual_count(self):
# List servers by specifying a greater value for limit
@@ -187,3 +188,7 @@
if srv['id'] in deleted_ids]
self.assertEqual('200', resp['status'])
self.assertEqual([], actual)
+
+
+class ListServersNegativeTestXML(ListServersNegativeTestJSON):
+ _interface = 'xml'
diff --git a/tempest/tests/compute/servers/test_multiple_create.py b/tempest/tests/compute/servers/test_multiple_create.py
new file mode 100644
index 0000000..ad5d604
--- /dev/null
+++ b/tempest/tests/compute/servers/test_multiple_create.py
@@ -0,0 +1,117 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 IBM Corp
+# 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 tempest.common.utils.data_utils import rand_name
+from tempest import exceptions
+from tempest.test import attr
+from tempest.tests.compute import base
+
+
+class MultipleCreateTestJSON(base.BaseComputeTest):
+ _interface = 'json'
+ _name = 'multiple-create-test'
+
+ def _get_created_servers(self, name):
+ """Get servers created which name match with name param."""
+ resp, body = self.servers_client.list_servers()
+ servers = body['servers']
+ servers_created = []
+ for server in servers:
+ if server['name'].startswith(name):
+ servers_created.append(server)
+ return servers_created
+
+ def _generate_name(self):
+ return rand_name(self._name)
+
+ def _create_multiple_servers(self, name=None, wait_until=None, **kwargs):
+ """
+ This is the right way to create_multiple servers and manage to get the
+ created servers into the servers list to be cleaned up after all.
+ """
+ kwargs['name'] = kwargs.get('name', self._generate_name())
+ resp, body = self.create_server(**kwargs)
+ created_servers = self._get_created_servers(kwargs['name'])
+ # NOTE(maurosr): append it to cls.servers list from base.BaseCompute
+ # class.
+ self.servers.append(created_servers)
+ # NOTE(maurosr): get a server list, check status of the ones with names
+ # that match and wait for them become active. At a first look, since
+ # they are building in parallel, wait inside the for doesn't seem be
+ # harmful to the performance
+ if wait_until is not None:
+ for server in created_servers:
+ self.servers_client.wait_for_server_status(server['id'],
+ wait_until)
+
+ return resp, body
+
+ @attr(type='positive')
+ def test_multiple_create(self):
+ resp, body = self._create_multiple_servers(wait_until='ACTIVE',
+ min_count=1,
+ max_count=2)
+ # NOTE(maurosr): do status response check and also make sure that
+ # reservation_id is not in the response body when the request send
+ # contains return_reservation_id=False
+ self.assertEqual('202', resp['status'])
+ self.assertFalse('reservation_id' in body)
+
+ @attr(type='negative')
+ def test_min_count_less_than_one(self):
+ invalid_min_count = 0
+ self.assertRaises(exceptions.BadRequest, self._create_multiple_servers,
+ min_count=invalid_min_count)
+
+ @attr(type='negative')
+ def test_min_count_non_integer(self):
+ invalid_min_count = 2.5
+ self.assertRaises(exceptions.BadRequest, self._create_multiple_servers,
+ min_count=invalid_min_count)
+
+ @attr(type='negative')
+ def test_max_count_less_than_one(self):
+ invalid_max_count = 0
+ self.assertRaises(exceptions.BadRequest, self._create_multiple_servers,
+ max_count=invalid_max_count)
+
+ @attr(type='negative')
+ def test_max_count_non_integer(self):
+ invalid_max_count = 2.5
+ self.assertRaises(exceptions.BadRequest, self._create_multiple_servers,
+ max_count=invalid_max_count)
+
+ @attr(type='negative')
+ def test_max_count_less_than_min_count(self):
+ min_count = 3
+ max_count = 2
+ self.assertRaises(exceptions.BadRequest, self._create_multiple_servers,
+ min_count=min_count,
+ max_count=max_count)
+
+ @attr(type='positive')
+ def test_multiple_create_with_reservation_return(self):
+ resp, body = self._create_multiple_servers(wait_until='ACTIVE',
+ min_count=1,
+ max_count=2,
+ return_reservation_id=True)
+ self.assertTrue(resp['status'], 202)
+ self.assertIn('reservation_id', body)
+
+
+class MultipleCreateTestXML(MultipleCreateTestJSON):
+ _interface = 'xml'
diff --git a/tempest/tests/compute/servers/test_server_actions.py b/tempest/tests/compute/servers/test_server_actions.py
index d5b2650..5634784 100644
--- a/tempest/tests/compute/servers/test_server_actions.py
+++ b/tempest/tests/compute/servers/test_server_actions.py
@@ -87,7 +87,7 @@
self.assertGreater(new_boot_time, boot_time)
@attr(type='smoke')
- @testtools.skip('Until bug 1014647 is dealt with.')
+ @testtools.skip('Until Bug #1014647 is dealt with.')
def test_reboot_server_soft(self):
# The server should be signaled to reboot gracefully
if self.run_ssh:
@@ -239,7 +239,7 @@
'!@#$%^&*()', 10)
@attr(type='positive')
- @testtools.skip('Until tempest bug 1014683 is fixed.')
+ @testtools.skip('Until tempest Bug #1014683 is fixed.')
def test_get_console_output_server_id_in_reboot_status(self):
# Positive test:Should be able to GET the console output
# for a given server_id in reboot status
diff --git a/tempest/tests/compute/servers/test_server_addresses.py b/tempest/tests/compute/servers/test_server_addresses.py
index cb8e85e..05fa320 100644
--- a/tempest/tests/compute/servers/test_server_addresses.py
+++ b/tempest/tests/compute/servers/test_server_addresses.py
@@ -15,7 +15,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
from tempest.test import attr
from tempest.tests.compute import base
diff --git a/tempest/tests/compute/servers/test_server_advanced_ops.py b/tempest/tests/compute/servers/test_server_advanced_ops.py
index f949f2e..ac0d7be 100644
--- a/tempest/tests/compute/servers/test_server_advanced_ops.py
+++ b/tempest/tests/compute/servers/test_server_advanced_ops.py
@@ -24,7 +24,7 @@
LOG = logging.getLogger(__name__)
-class TestServerAdvancedOps(test.DefaultClientTest):
+class TestServerAdvancedOps(test.DefaultClientSmokeTest):
"""
This test case stresses some advanced server instance operations:
@@ -66,16 +66,16 @@
self.assertEqual(self.instance.status, 'BUILD')
instance_id = self.get_resource('instance').id
- self.status_timeout(self.compute_client.servers, instance_id, 'ACTIVE')
+ test.status_timeout(self.compute_client.servers, instance_id, 'ACTIVE')
instance = self.get_resource('instance')
instance_id = instance.id
resize_flavor = self.config.compute.flavor_ref_alt
LOG.debug("Resizing instance %s from flavor %s to flavor %s",
instance.id, instance.flavor, resize_flavor)
instance.resize(resize_flavor)
- self.status_timeout(self.compute_client.servers, instance_id,
+ test.status_timeout(self.compute_client.servers, instance_id,
'VERIFY_RESIZE')
LOG.debug("Confirming resize of instance %s", instance_id)
instance.confirm_resize()
- self.status_timeout(self.compute_client.servers, instance_id, 'ACTIVE')
+ test.status_timeout(self.compute_client.servers, instance_id, 'ACTIVE')
diff --git a/tempest/tests/compute/servers/test_server_basic_ops.py b/tempest/tests/compute/servers/test_server_basic_ops.py
index 2183193..c7fad7a 100644
--- a/tempest/tests/compute/servers/test_server_basic_ops.py
+++ b/tempest/tests/compute/servers/test_server_basic_ops.py
@@ -18,12 +18,12 @@
import logging
from tempest.common.utils.data_utils import rand_name
-from tempest import smoke
+from tempest import test
LOG = logging.getLogger(__name__)
-class TestServerBasicOps(smoke.DefaultClientSmokeTest):
+class TestServerBasicOps(test.DefaultClientSmokeTest):
"""
This smoke test case follows this basic set of operations:
@@ -101,7 +101,7 @@
def wait_on_active(self):
instance_id = self.get_resource('instance').id
- self.status_timeout(self.compute_client.servers, instance_id, 'ACTIVE')
+ test.status_timeout(self.compute_client.servers, instance_id, 'ACTIVE')
def pause_server(self):
instance = self.get_resource('instance')
@@ -109,7 +109,7 @@
LOG.debug("Pausing instance %s. Current status: %s",
instance_id, instance.status)
instance.pause()
- self.status_timeout(self.compute_client.servers, instance_id, 'PAUSED')
+ test.status_timeout(self.compute_client.servers, instance_id, 'PAUSED')
def unpause_server(self):
instance = self.get_resource('instance')
@@ -117,7 +117,7 @@
LOG.debug("Unpausing instance %s. Current status: %s",
instance_id, instance.status)
instance.unpause()
- self.status_timeout(self.compute_client.servers, instance_id, 'ACTIVE')
+ test.status_timeout(self.compute_client.servers, instance_id, 'ACTIVE')
def suspend_server(self):
instance = self.get_resource('instance')
@@ -125,7 +125,7 @@
LOG.debug("Suspending instance %s. Current status: %s",
instance_id, instance.status)
instance.suspend()
- self.status_timeout(self.compute_client.servers,
+ test.status_timeout(self.compute_client.servers,
instance_id, 'SUSPENDED')
def resume_server(self):
@@ -134,7 +134,7 @@
LOG.debug("Resuming instance %s. Current status: %s",
instance_id, instance.status)
instance.resume()
- self.status_timeout(self.compute_client.servers, instance_id, 'ACTIVE')
+ test.status_timeout(self.compute_client.servers, instance_id, 'ACTIVE')
def terminate_instance(self):
instance = self.get_resource('instance')
diff --git a/tempest/tests/compute/servers/test_server_rescue.py b/tempest/tests/compute/servers/test_server_rescue.py
index 9230305..91010ce 100644
--- a/tempest/tests/compute/servers/test_server_rescue.py
+++ b/tempest/tests/compute/servers/test_server_rescue.py
@@ -15,17 +15,12 @@
# License for the specific language governing permissions and limitations
# under the License.
-import base64
-import time
-
import testtools
from tempest.common.utils.data_utils import rand_name
-from tempest.common.utils.linux.remote_client import RemoteClient
import tempest.config
from tempest import exceptions
from tempest.test import attr
-from tempest.tests import compute
from tempest.tests.compute import base
@@ -57,16 +52,16 @@
cls.volumes_extensions_client.create_volume(1,
display_name=
'test_attach')
- cls.volumes_extensions_client.wait_for_volume_status
- (cls.volume_to_attach['id'], 'available')
+ cls.volumes_extensions_client.wait_for_volume_status(
+ cls.volume_to_attach['id'], 'available')
# Create a volume and wait for it to become ready for attach
resp, cls.volume_to_detach = \
cls.volumes_extensions_client.create_volume(1,
display_name=
'test_detach')
- cls.volumes_extensions_client.wait_for_volume_status
- (cls.volume_to_detach['id'], 'available')
+ cls.volumes_extensions_client.wait_for_volume_status(
+ cls.volume_to_detach['id'], 'available')
# Server for positive tests
resp, server = cls.create_server(image_id=cls.image_ref,
@@ -112,6 +107,11 @@
def _delete(self, volume_id):
self.volumes_extensions_client.delete_volume(volume_id)
+ def _unrescue(self, server_id):
+ resp, body = self.servers_client.unrescue_server(server_id)
+ self.assertEqual(202, resp.status)
+ self.servers_client.wait_for_server_status(server_id, 'ACTIVE')
+
@attr(type='smoke')
def test_rescue_unrescue_instance(self):
resp, body = self.servers_client.rescue_server(
@@ -123,9 +123,8 @@
self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE')
@attr(type='negative')
- @testtools.skip("Skipped until Bug:1126163 is resolved")
def test_rescued_vm_reboot(self):
- self.assertRaises(exceptions.BadRequest, self.servers_client.reboot,
+ self.assertRaises(exceptions.Duplicate, self.servers_client.reboot,
self.rescue_id, 'HARD')
@attr(type='negative')
@@ -135,61 +134,42 @@
self.rescue_id,
self.image_ref_alt)
- @attr(type='positive')
- @testtools.skip("Skipped due to Bug:1126187")
+ @attr(type='negative')
def test_rescued_vm_attach_volume(self):
- client = self.volumes_extensions_client
-
# Rescue the server
self.servers_client.rescue_server(self.server_id, self.password)
self.servers_client.wait_for_server_status(self.server_id, 'RESCUE')
+ self.addCleanup(self._unrescue, self.server_id)
# Attach the volume to the server
- resp, body = \
- self.servers_client.attach_volume(self.server_id,
- self.volume_to_attach['id'],
- device='/dev/%s' % self.device)
- self.assertEqual(200, resp.status)
- client.wait_for_volume_status(self.volume_to_attach['id'], 'in-use')
+ self.assertRaises(exceptions.Duplicate,
+ self.servers_client.attach_volume,
+ self.server_id,
+ self.volume_to_attach['id'],
+ device='/dev/%s' % self.device)
- # Detach the volume to the server
- resp, body = \
- self.servers_client.detach_volume(self.server_id,
- self.volume_to_attach['id'])
- self.assertEqual(202, resp.status)
- client.wait_for_volume_status(self.volume_to_attach['id'], 'available')
-
- # Unrescue the server
- resp, body = self.servers_client.unrescue_server(self.server_id)
- self.assertEqual(202, resp.status)
- self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE')
-
- @attr(type='positive')
- @testtools.skip("Skipped until Bug:1126187 is resolved")
+ @attr(type='negative')
def test_rescued_vm_detach_volume(self):
# Attach the volume to the server
self.servers_client.attach_volume(self.server_id,
self.volume_to_detach['id'],
device='/dev/%s' % self.device)
- self.volumes_extensions_client.wait_for_volume_status
- (self.volume_to_detach['id'], 'in-use')
+ self.volumes_extensions_client.wait_for_volume_status(
+ self.volume_to_detach['id'], 'in-use')
# Rescue the server
self.servers_client.rescue_server(self.server_id, self.password)
self.servers_client.wait_for_server_status(self.server_id, 'RESCUE')
+ #addCleanup is a LIFO queue
+ self.addCleanup(self._detach, self.server_id,
+ self.volume_to_detach['id'])
+ self.addCleanup(self._unrescue, self.server_id)
- # Detach the volume to the server
- resp, body = \
- self.servers_client.detach_volume(self.server_id,
- self.volume_to_detach['id'])
- self.assertEqual(202, resp.status)
- client = self.volumes_extensions_client
- client.wait_for_volume_status(self.volume_to_detach['id'], 'available')
-
- # Unrescue the server
- resp, body = self.servers_client.unrescue_server(self.server_id)
- self.assertEqual(202, resp.status)
- self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE')
+ # Detach the volume from the server expecting failure
+ self.assertRaises(exceptions.Duplicate,
+ self.servers_client.detach_volume,
+ self.server_id,
+ self.volume_to_detach['id'])
@attr(type='positive')
def test_rescued_vm_associate_dissociate_floating_ip(self):
@@ -197,6 +177,7 @@
self.servers_client.rescue_server(
self.server_id, self.password)
self.servers_client.wait_for_server_status(self.server_id, 'RESCUE')
+ self.addCleanup(self._unrescue, self.server_id)
#Association of floating IP to a rescued vm
client = self.floating_ips_client
@@ -211,13 +192,8 @@
self.server_id)
self.assertEqual(202, resp.status)
- # Unrescue the server
- resp, body = self.servers_client.unrescue_server(self.server_id)
- self.assertEqual(202, resp.status)
- 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/test_authorization.py b/tempest/tests/compute/test_authorization.py
index 4ca197a..91cf39f 100644
--- a/tempest/tests/compute/test_authorization.py
+++ b/tempest/tests/compute/test_authorization.py
@@ -15,8 +15,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import testtools
-
from tempest import clients
from tempest.common.utils.data_utils import parse_image_id
from tempest.common.utils.data_utils import rand_name
@@ -25,7 +23,7 @@
from tempest.tests.compute import base
-class AuthorizationTest(base.BaseComputeTest):
+class AuthorizationTestJSON(base.BaseComputeTest):
_interface = 'json'
@classmethod
@@ -34,7 +32,7 @@
msg = "Need >1 user"
raise cls.skipException(msg)
- super(AuthorizationTest, cls).setUpClass()
+ super(AuthorizationTestJSON, cls).setUpClass()
cls.client = cls.os.servers_client
cls.images_client = cls.os.images_client
@@ -73,18 +71,15 @@
name = rand_name('security')
description = rand_name('description')
- resp, cls.security_group = \
- cls.security_client.create_security_group(name, description)
+ resp, cls.security_group = cls.security_client.create_security_group(
+ name, description)
parent_group_id = cls.security_group['id']
ip_protocol = 'tcp'
from_port = 22
to_port = 22
- resp, cls.rule =\
- cls.security_client.create_security_group_rule(
- parent_group_id,
- ip_protocol, from_port,
- to_port)
+ resp, cls.rule = cls.security_client.create_security_group_rule(
+ parent_group_id, ip_protocol, from_port, to_port)
@classmethod
def tearDownClass(cls):
@@ -92,7 +87,7 @@
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'])
- super(AuthorizationTest, cls).tearDownClass()
+ super(AuthorizationTestJSON, cls).tearDownClass()
def test_get_server_for_alt_account_fails(self):
# A GET request for a server on another user's account should fail
@@ -280,7 +275,7 @@
self.alt_security_client.base_url = self.saved_base_url
if resp['status'] is not None:
self.alt_security_client.delete_security_group_rule(
- body['id']) # BUG
+ body['id']) # BUG
self.fail("Create security group rule request should not "
"happen if the tenant id does not match the"
" current user")
@@ -354,3 +349,7 @@
self.assertRaises(exceptions.NotFound,
self.alt_client.get_console_output,
self.server['id'], 10)
+
+
+class AuthorizationTestXML(AuthorizationTestJSON):
+ _interface = 'xml'
diff --git a/tempest/tests/compute/test_live_block_migration.py b/tempest/tests/compute/test_live_block_migration.py
index abaaf85..e438098 100644
--- a/tempest/tests/compute/test_live_block_migration.py
+++ b/tempest/tests/compute/test_live_block_migration.py
@@ -27,7 +27,8 @@
@attr(category='live-migration')
-class LiveBlockMigrationTest(base.BaseComputeAdminTest):
+class LiveBlockMigrationTestJSON(base.BaseComputeAdminTest):
+ _host_key = 'OS-EXT-SRV-ATTR:host'
_interface = 'json'
live_migration_available = (
@@ -38,7 +39,7 @@
@classmethod
def setUpClass(cls):
- super(LiveBlockMigrationTest, cls).setUpClass()
+ super(LiveBlockMigrationTestJSON, cls).setUpClass()
cls.admin_hosts_client = cls.os_adm.hosts_client
cls.admin_servers_client = cls.os_adm.servers_client
@@ -58,7 +59,7 @@
return body
def _get_host_for_server(self, server_id):
- return self._get_server_details(server_id)['OS-EXT-SRV-ATTR:host']
+ return self._get_server_details(server_id)[self._host_key]
def _migrate_server_to(self, server_id, dest_host):
_resp, body = self.admin_servers_client.live_migrate_server(
@@ -125,4 +126,10 @@
for server_id in cls.created_server_ids:
cls.servers_client.delete_server(server_id)
- super(LiveBlockMigrationTest, cls).tearDownClass()
+ super(LiveBlockMigrationTestJSON, cls).tearDownClass()
+
+
+class LiveBlockMigrationTestXML(LiveBlockMigrationTestJSON):
+ _host_key = (
+ '{http://docs.openstack.org/compute/ext/extended_status/api/v1.1}host')
+ _interface = 'xml'
diff --git a/tempest/tests/compute/test_quotas.py b/tempest/tests/compute/test_quotas.py
index 233d639..a84d041 100644
--- a/tempest/tests/compute/test_quotas.py
+++ b/tempest/tests/compute/test_quotas.py
@@ -33,14 +33,11 @@
@attr(type='smoke')
def test_get_default_quotas(self):
- # Tempest two step
- self.skipTest('Skipped until the Bug 1125468 is resolved')
-
# User can get the default quota set for it's tenant
expected_quota_set = {'injected_file_content_bytes': 10240,
'metadata_items': 128, 'injected_files': 5,
'ram': 51200, 'floating_ips': 10,
- 'fixed_ips': 10, 'key_pairs': 100,
+ 'fixed_ips': -1, 'key_pairs': 100,
'injected_file_path_bytes': 255, 'instances': 10,
'security_group_rules': 20, 'cores': 20,
'id': self.tenant_id, 'security_groups': 10}
diff --git a/tempest/tests/compute/volumes/test_attach_volume.py b/tempest/tests/compute/volumes/test_attach_volume.py
index d9abe41..cb0a964 100644
--- a/tempest/tests/compute/volumes/test_attach_volume.py
+++ b/tempest/tests/compute/volumes/test_attach_volume.py
@@ -1,6 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
-# Copyright 2012 IBM
+# Copyright 2012 IBM Corp.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -17,7 +17,6 @@
import testtools
-from tempest.common.utils.data_utils import rand_name
from tempest.common.utils.linux.remote_client import RemoteClient
import tempest.config
from tempest.test import attr
@@ -92,7 +91,7 @@
self.assertTrue(self.device in partitions)
self._detach(server['id'], volume['id'])
- attached = False
+ self.attached = False
self.servers_client.stop(server['id'])
self.servers_client.wait_for_server_status(server['id'], 'SHUTOFF')
diff --git a/tempest/tests/identity/admin/test_users.py b/tempest/tests/identity/admin/test_users.py
index 224272e..0573b21 100644
--- a/tempest/tests/identity/admin/test_users.py
+++ b/tempest/tests/identity/admin/test_users.py
@@ -77,7 +77,7 @@
self.data.tenant['id'], self.data.test_email)
@attr(type='negative')
- @testtools.skip("Until Bug 999084 is fixed")
+ @testtools.skip("Until Bug #999084 is fixed")
def test_create_user_with_empty_password(self):
# User with an empty password should not be created
self.data.setup_test_tenant()
@@ -86,7 +86,7 @@
self.alt_email)
@attr(type='nagative')
- @testtools.skip("Until Bug 999084 is fixed")
+ @testtools.skip("Until Bug #999084 is fixed")
def test_create_user_with_long_password(self):
# User having password exceeding max length should not be created
self.data.setup_test_tenant()
@@ -95,7 +95,7 @@
self.alt_email)
@attr(type='negative')
- @testtools.skip("Until Bug 999084 is fixed")
+ @testtools.skip("Until Bug #999084 is fixed")
def test_create_user_with_invalid_email_format(self):
# Email format should be validated while creating a user
self.data.setup_test_tenant()
@@ -326,17 +326,9 @@
invalid_id.append(rand_name("dddd@#%%^$"))
invalid_id.append('!@#()$%^&*?<>{}[]')
#List the users with invalid tenant id
- fail = list()
for invalid in invalid_id:
- try:
- resp, body = self.client.list_users_for_tenant(invalid)
- except exceptions.NotFound:
- pass
- else:
- fail.append(invalid)
- if len(fail) != 0:
- self.fail('Should raise Not Found when list users with invalid'
- 'tenant ids %s' % fail)
+ self.assertRaises(exceptions.NotFound,
+ self.client.list_users_for_tenant, invalid)
class UsersTestXML(UsersTestJSON):
diff --git a/tempest/tests/identity/admin/v3/__init__.py b/tempest/tests/identity/admin/v3/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/tests/identity/admin/v3/__init__.py
diff --git a/tempest/tests/identity/admin/v3/test_endpoints.py b/tempest/tests/identity/admin/v3/test_endpoints.py
new file mode 100755
index 0000000..98fab57
--- /dev/null
+++ b/tempest/tests/identity/admin/v3/test_endpoints.py
@@ -0,0 +1,149 @@
+# 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.
+
+from tempest.common.utils.data_utils import rand_name
+from tempest.test import attr
+from tempest.tests.identity import base
+
+
+class EndPointsTestJSON(base.BaseIdentityAdminTest):
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(EndPointsTestJSON, cls).setUpClass()
+ cls.identity_client = cls.client
+ cls.client = cls.endpoints_client
+ cls.service_ids = list()
+ s_name = rand_name('service-')
+ s_type = rand_name('type--')
+ s_description = rand_name('description-')
+ resp, cls.service_data =\
+ cls.identity_client.create_service(s_name, s_type,
+ description=s_description)
+ cls.service_id = cls.service_data['id']
+ cls.service_ids.append(cls.service_id)
+ #Create endpoints so as to use for LIST and GET test cases
+ cls.setup_endpoints = list()
+ for i in range(2):
+ region = rand_name('region')
+ url = rand_name('url')
+ interface = 'public'
+ resp, endpoint = cls.client.create_endpoint(
+ cls.service_id, interface, url, region=region, enabled=True)
+ cls.setup_endpoints.append(endpoint)
+
+ @classmethod
+ def tearDownClass(cls):
+ for e in cls.setup_endpoints:
+ cls.client.delete_endpoint(e['id'])
+ for s in cls.service_ids:
+ cls.identity_client.delete_service(s)
+
+ @attr('positive')
+ def test_list_endpoints(self):
+ # Get a list of endpoints
+ resp, fetched_endpoints = self.client.list_endpoints()
+ #Asserting LIST Endpoint
+ self.assertEqual(resp['status'], '200')
+ missing_endpoints =\
+ [e for e in self.setup_endpoints if e not in fetched_endpoints]
+ self.assertEqual(0, len(missing_endpoints),
+ "Failed to find endpoint %s in fetched list" %
+ ', '.join(str(e) for e in missing_endpoints))
+
+ @attr('positive')
+ def test_create_delete_endpoint(self):
+ region = rand_name('region')
+ url = rand_name('url')
+ interface = 'public'
+ create_flag = False
+ matched = False
+ try:
+ resp, endpoint =\
+ self.client.create_endpoint(self.service_id, interface, url,
+ region=region, enabled=True)
+ create_flag = True
+ #Asserting Create Endpoint response body
+ self.assertEqual(resp['status'], '201')
+ self.assertEqual(region, endpoint['region'])
+ self.assertEqual(url, endpoint['url'])
+ #Checking if created endpoint is present in the list of endpoints
+ resp, fetched_endpoints = self.client.list_endpoints()
+ for e in fetched_endpoints:
+ if endpoint['id'] == e['id']:
+ matched = True
+ if not matched:
+ self.fail("Created endpoint does not appear in the list"
+ " of endpoints")
+ finally:
+ if create_flag:
+ matched = False
+ #Deleting the endpoint created in this method
+ resp_header, resp_body =\
+ self.client.delete_endpoint(endpoint['id'])
+ self.assertEqual(resp_header['status'], '204')
+ self.assertEqual(resp_body, '')
+ #Checking whether endpoint is deleted successfully
+ resp, fetched_endpoints = self.client.list_endpoints()
+ for e in fetched_endpoints:
+ if endpoint['id'] == e['id']:
+ matched = True
+ if matched:
+ self.fail("Delete endpoint is not successful")
+
+ @attr('smoke')
+ def test_update_endpoint(self):
+ #Creating an endpoint so as to check update endpoint
+ #with new values
+ region1 = rand_name('region')
+ url1 = rand_name('url')
+ interface1 = 'public'
+ resp, endpoint_for_update =\
+ self.client.create_endpoint(self.service_id, interface1,
+ url1, region=region1,
+ enabled=True)
+ #Creating service so as update endpoint with new service ID
+ s_name = rand_name('service-')
+ s_type = rand_name('type--')
+ s_description = rand_name('description-')
+ resp, self.service2 =\
+ self.identity_client.create_service(s_name, s_type,
+ description=s_description)
+ self.service_ids.append(self.service2['id'])
+ #Updating endpoint with new values
+ service_id = self.service2['id']
+ region2 = rand_name('region')
+ url2 = rand_name('url')
+ interface2 = 'internal'
+ resp, endpoint = \
+ self.client.update_endpoint(endpoint_for_update['id'],
+ service_id=self.service2['id'],
+ interface=interface2, url=url2,
+ region=region2, enabled=False)
+ self.assertEqual(resp['status'], '200')
+ #Asserting if the attributes of endpoint are updated
+ self.assertEqual(self.service2['id'], endpoint['service_id'])
+ self.assertEqual(interface2, endpoint['interface'])
+ self.assertEqual(url2, endpoint['url'])
+ self.assertEqual(region2, endpoint['region'])
+ self.assertEqual('False', str(endpoint['enabled']))
+ self.addCleanup(self.client.delete_endpoint, endpoint_for_update['id'])
+
+
+class EndPointsTestXML(EndPointsTestJSON):
+ _interface = 'xml'
diff --git a/tempest/tests/identity/base.py b/tempest/tests/identity/base.py
index 168b2ff..64b8993 100644
--- a/tempest/tests/identity/base.py
+++ b/tempest/tests/identity/base.py
@@ -28,6 +28,7 @@
os = clients.AdminManager(interface=cls._interface)
cls.client = os.identity_client
cls.token_client = os.token_client
+ cls.endpoints_client = os.endpoints_client
if not cls.client.has_admin_extensions():
raise cls.skipException("Admin extensions disabled")
diff --git a/tempest/tests/image/base.py b/tempest/tests/image/base.py
index 65d81b6..f12e957 100644
--- a/tempest/tests/image/base.py
+++ b/tempest/tests/image/base.py
@@ -15,7 +15,6 @@
# under the License.
import logging
-import time
from tempest import clients
from tempest.common.utils.data_utils import rand_name
diff --git a/tempest/tests/image/v1/test_images.py b/tempest/tests/image/v1/test_images.py
index af09b79..1b6fa10 100644
--- a/tempest/tests/image/v1/test_images.py
+++ b/tempest/tests/image/v1/test_images.py
@@ -17,7 +17,6 @@
import cStringIO as StringIO
-from tempest import clients
from tempest import exceptions
from tempest.test import attr
from tempest.tests.image import base
@@ -68,13 +67,55 @@
container_format='bare',
disk_format='raw', is_public=True,
location='http://example.com'
- '/someimage.iso')
+ '/someimage.iso',
+ properties={'key1': 'value1',
+ 'key2': 'value2'})
self.assertTrue('id' in body)
image_id = body.get('id')
self.created_images.append(image_id)
self.assertEqual('New Remote Image', body.get('name'))
self.assertTrue(body.get('is_public'))
self.assertEqual('active', body.get('status'))
+ properties = body.get('properties')
+ self.assertEqual(properties['key1'], 'value1')
+ self.assertEqual(properties['key2'], 'value2')
+
+ def test_register_http_image(self):
+ container_client = self.os.container_client
+ object_client = self.os.object_client
+ container_name = "image_container"
+ object_name = "test_image.img"
+ container_client.create_container(container_name)
+ self.addCleanup(container_client.delete_container, container_name)
+ cont_headers = {'X-Container-Read': '.r:*'}
+ resp, _ = container_client.update_container_metadata(
+ container_name,
+ metadata=cont_headers,
+ metadata_prefix='')
+ self.assertEqual(resp['status'], '204')
+
+ data = "TESTIMAGE"
+ resp, _ = object_client.create_object(container_name,
+ object_name, data)
+ self.addCleanup(object_client.delete_object, container_name,
+ object_name)
+ self.assertEqual(resp['status'], '201')
+ object_url = '/'.join((object_client.base_url,
+ container_name,
+ object_name))
+ resp, body = self.create_image(name='New Http Image',
+ container_format='bare',
+ disk_format='raw', is_public=True,
+ copy_from=object_url)
+ self.assertTrue('id' in body)
+ image_id = body.get('id')
+ self.created_images.append(image_id)
+ self.assertEqual('New Http Image', body.get('name'))
+ self.assertTrue(body.get('is_public'))
+ self.client.wait_for_image_status(image_id, 'active')
+ resp, body = self.client.get_image(image_id)
+ self.assertEqual(resp['status'], '200')
+ self.assertEqual(body, data)
class ListImagesTest(base.BaseV1ImageTest):
diff --git a/tempest/tests/image/v2/test_images.py b/tempest/tests/image/v2/test_images.py
index 19a7a95..15db519 100644
--- a/tempest/tests/image/v2/test_images.py
+++ b/tempest/tests/image/v2/test_images.py
@@ -2,7 +2,7 @@
# Copyright 2013 OpenStack, LLC
# All Rights Reserved.
-# Copyright 2013 IBM Corp
+# 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
@@ -19,7 +19,6 @@
import cStringIO as StringIO
import random
-from tempest import clients
from tempest import exceptions
from tempest.test import attr
from tempest.tests.image import base
diff --git a/tempest/tests/network/base.py b/tempest/tests/network/base.py
index 1b09513..e0e40cb 100644
--- a/tempest/tests/network/base.py
+++ b/tempest/tests/network/base.py
@@ -15,31 +15,73 @@
# License for the specific language governing permissions and limitations
# under the License.
+import netaddr
from tempest import clients
from tempest.common.utils.data_utils import rand_name
+from tempest import exceptions
import tempest.test
class BaseNetworkTest(tempest.test.BaseTestCase):
+ """
+ Base class for the Quantum tests that use the Tempest Quantum REST client
+
+ Per the Quantum API Guide, API v1.x was removed from the source code tree
+ (docs.openstack.org/api/openstack-network/2.0/content/Overview-d1e71.html)
+ Therefore, v2.x of the Quantum API is assumed. It is also assumed that the
+ following options are defined in the [network] section of etc/tempest.conf:
+
+ tenant_network_cidr with a block of cidr's from which smaller blocks
+ can be allocated for tenant networks
+
+ tenant_network_mask_bits with the mask bits to be used to partition the
+ block defined by tenant-network_cidr
+ """
+
@classmethod
def setUpClass(cls):
os = clients.Manager()
-
- if not os.config.network.quantum_available:
+ cls.network_cfg = os.config.network
+ if not cls.network_cfg.quantum_available:
raise cls.skipException("Quantum support is required")
+ cls.client = os.network_client
+ cls.networks = []
+ cls.subnets = []
@classmethod
def tearDownClass(cls):
+ for subnet in cls.subnets:
+ cls.client.delete_subnet(subnet['id'])
for network in cls.networks:
cls.client.delete_network(network['id'])
- def create_network(self, network_name=None):
+ @classmethod
+ def create_network(cls, network_name=None):
"""Wrapper utility that returns a test network."""
- network_name = network_name or rand_name('test-network')
+ network_name = network_name or rand_name('test-network-')
- resp, body = self.client.create_network(network_name)
+ resp, body = cls.client.create_network(network_name)
network = body['network']
- self.networks.append(network)
+ cls.networks.append(network)
return network
+
+ @classmethod
+ def create_subnet(cls, network):
+ """Wrapper utility that returns a test subnet."""
+ cidr = netaddr.IPNetwork(cls.network_cfg.tenant_network_cidr)
+ mask_bits = cls.network_cfg.tenant_network_mask_bits
+ # Find a cidr that is not in use yet and create a subnet with it
+ for subnet_cidr in cidr.subnet(mask_bits):
+ try:
+ resp, body = cls.client.create_subnet(network['id'],
+ str(subnet_cidr))
+ break
+ except exceptions.BadRequest as e:
+ is_overlapping_cidr = 'overlaps with another subnet' in str(e)
+ if not is_overlapping_cidr:
+ raise
+ subnet = body['subnet']
+ cls.subnets.append(subnet)
+ return subnet
diff --git a/tempest/tests/network/common.py b/tempest/tests/network/common.py
new file mode 100644
index 0000000..1cff2c4
--- /dev/null
+++ b/tempest/tests/network/common.py
@@ -0,0 +1,315 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 Hewlett-Packard Development Company, L.P.
+# 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 subprocess
+
+import netaddr
+
+from quantumclient.common import exceptions as exc
+from tempest.common.utils.data_utils import rand_name
+from tempest import test
+
+
+class AttributeDict(dict):
+
+ """
+ Provide attribute access (dict.key) to dictionary values.
+ """
+
+ def __getattr__(self, name):
+ """Allow attribute access for all keys in the dict."""
+ if name in self:
+ return self[name]
+ return super(AttributeDict, self).__getattribute__(name)
+
+
+class DeletableResource(AttributeDict):
+
+ """
+ Support deletion of quantum resources (networks, subnets) via a
+ delete() method, as is supported by keystone and nova resources.
+ """
+
+ def __init__(self, *args, **kwargs):
+ self.client = kwargs.pop('client', None)
+ super(DeletableResource, self).__init__(*args, **kwargs)
+
+ def __str__(self):
+ return '<%s id="%s" name="%s">' % (self.__class__.__name__,
+ self.id, self.name)
+
+ def delete(self):
+ raise NotImplemented()
+
+
+class DeletableNetwork(DeletableResource):
+
+ def delete(self):
+ self.client.delete_network(self.id)
+
+
+class DeletableSubnet(DeletableResource):
+
+ _router_ids = set()
+
+ def add_to_router(self, router_id):
+ self._router_ids.add(router_id)
+ body = dict(subnet_id=self.id)
+ self.client.add_interface_router(router_id, body=body)
+
+ def delete(self):
+ for router_id in self._router_ids.copy():
+ body = dict(subnet_id=self.id)
+ self.client.remove_interface_router(router_id, body=body)
+ self._router_ids.remove(router_id)
+ self.client.delete_subnet(self.id)
+
+
+class DeletableRouter(DeletableResource):
+
+ def add_gateway(self, network_id):
+ body = dict(network_id=network_id)
+ self.client.add_gateway_router(self.id, body=body)
+
+ def delete(self):
+ self.client.remove_gateway_router(self.id)
+ self.client.delete_router(self.id)
+
+
+class DeletableFloatingIp(DeletableResource):
+
+ def delete(self):
+ self.client.delete_floatingip(self.id)
+
+
+class DeletablePort(DeletableResource):
+
+ def delete(self):
+ self.client.delete_port(self.id)
+
+
+class TestNetworkSmokeCommon(test.DefaultClientSmokeTest):
+ """
+ Base class for network smoke tests
+ """
+
+ @classmethod
+ def check_preconditions(cls):
+ if (cls.config.network.quantum_available):
+ cls.enabled = True
+ #verify that quantum_available is telling the truth
+ try:
+ cls.network_client.list_networks()
+ except exc.EndpointNotFound:
+ cls.enabled = False
+ raise
+ else:
+ cls.enabled = False
+ msg = 'Quantum not available'
+ raise cls.skipException(msg)
+
+ @classmethod
+ def setUpClass(cls):
+ super(TestNetworkSmokeCommon, cls).setUpClass()
+ cfg = cls.config.network
+ cls.tenant_id = cls.manager._get_identity_client(
+ cls.config.identity.username,
+ cls.config.identity.password,
+ cls.config.identity.tenant_name).tenant_id
+
+ def _create_keypair(self, client, namestart='keypair-smoke-'):
+ kp_name = rand_name(namestart)
+ keypair = client.keypairs.create(kp_name)
+ try:
+ self.assertEqual(keypair.id, kp_name)
+ self.set_resource(kp_name, keypair)
+ except AttributeError:
+ self.fail("Keypair object not successfully created.")
+ return keypair
+
+ def _create_security_group(self, client, namestart='secgroup-smoke-'):
+ # Create security group
+ sg_name = rand_name(namestart)
+ sg_desc = sg_name + " description"
+ secgroup = client.security_groups.create(sg_name, sg_desc)
+ try:
+ self.assertEqual(secgroup.name, sg_name)
+ self.assertEqual(secgroup.description, sg_desc)
+ self.set_resource(sg_name, secgroup)
+ except AttributeError:
+ self.fail("SecurityGroup object not successfully created.")
+
+ # Add rules to the security group
+ rulesets = [
+ {
+ # ssh
+ 'ip_protocol': 'tcp',
+ 'from_port': 22,
+ 'to_port': 22,
+ 'cidr': '0.0.0.0/0',
+ 'group_id': secgroup.id
+ },
+ {
+ # ping
+ 'ip_protocol': 'icmp',
+ 'from_port': -1,
+ 'to_port': -1,
+ 'cidr': '0.0.0.0/0',
+ 'group_id': secgroup.id
+ }
+ ]
+ for ruleset in rulesets:
+ try:
+ client.security_group_rules.create(secgroup.id, **ruleset)
+ except Exception:
+ self.fail("Failed to create rule in security group.")
+
+ return secgroup
+
+ def _create_network(self, tenant_id, namestart='network-smoke-'):
+ name = rand_name(namestart)
+ body = dict(
+ network=dict(
+ name=name,
+ tenant_id=tenant_id,
+ ),
+ )
+ result = self.network_client.create_network(body=body)
+ network = DeletableNetwork(client=self.network_client,
+ **result['network'])
+ self.assertEqual(network.name, name)
+ self.set_resource(name, network)
+ return network
+
+ def _list_networks(self):
+ nets = self.network_client.list_networks()
+ return nets['networks']
+
+ def _list_subnets(self):
+ subnets = self.network_client.list_subnets()
+ return subnets['subnets']
+
+ def _list_routers(self):
+ routers = self.network_client.list_routers()
+ return routers['routers']
+
+ def _create_subnet(self, network, namestart='subnet-smoke-'):
+ """
+ Create a subnet for the given network within the cidr block
+ configured for tenant networks.
+ """
+ cfg = self.config.network
+ tenant_cidr = netaddr.IPNetwork(cfg.tenant_network_cidr)
+ result = None
+ # Repeatedly attempt subnet creation with sequential cidr
+ # blocks until an unallocated block is found.
+ for subnet_cidr in tenant_cidr.subnet(cfg.tenant_network_mask_bits):
+ body = dict(
+ subnet=dict(
+ ip_version=4,
+ network_id=network.id,
+ tenant_id=network.tenant_id,
+ cidr=str(subnet_cidr),
+ ),
+ )
+ try:
+ result = self.network_client.create_subnet(body=body)
+ break
+ except exc.QuantumClientException as e:
+ is_overlapping_cidr = 'overlaps with another subnet' in str(e)
+ if not is_overlapping_cidr:
+ raise
+ self.assertIsNotNone(result, 'Unable to allocate tenant network')
+ subnet = DeletableSubnet(client=self.network_client,
+ **result['subnet'])
+ self.assertEqual(subnet.cidr, str(subnet_cidr))
+ self.set_resource(rand_name(namestart), subnet)
+ return subnet
+
+ def _create_port(self, network, namestart='port-quotatest-'):
+ name = rand_name(namestart)
+ body = dict(
+ port=dict(name=name,
+ network_id=network.id,
+ tenant_id=network.tenant_id))
+ try:
+ result = self.network_client.create_port(body=body)
+ except Exception as e:
+ raise
+ self.assertIsNotNone(result, 'Unable to allocate port')
+ port = DeletablePort(client=self.network_client,
+ **result['port'])
+ self.set_resource(name, port)
+ return port
+
+ def _create_server(self, client, network, name, key_name, security_groups):
+ flavor_id = self.config.compute.flavor_ref
+ base_image_id = self.config.compute.image_ref
+ create_kwargs = {
+ 'nics': [
+ {'net-id': network.id},
+ ],
+ 'key_name': key_name,
+ 'security_groups': security_groups,
+ }
+ server = client.servers.create(name, base_image_id, flavor_id,
+ **create_kwargs)
+ try:
+ self.assertEqual(server.name, name)
+ self.set_resource(name, server)
+ except AttributeError:
+ self.fail("Server not successfully created.")
+ test.status_timeout(client.servers, server.id, 'ACTIVE')
+ # The instance retrieved on creation is missing network
+ # details, necessitating retrieval after it becomes active to
+ # ensure correct details.
+ server = client.servers.get(server.id)
+ self.set_resource(name, server)
+ return server
+
+ def _create_floating_ip(self, server, external_network_id):
+ result = self.network_client.list_ports(device_id=server.id)
+ ports = result.get('ports', [])
+ self.assertEqual(len(ports), 1,
+ "Unable to determine which port to target.")
+ port_id = ports[0]['id']
+ body = dict(
+ floatingip=dict(
+ floating_network_id=external_network_id,
+ port_id=port_id,
+ tenant_id=server.tenant_id,
+ )
+ )
+ result = self.network_client.create_floatingip(body=body)
+ floating_ip = DeletableFloatingIp(client=self.network_client,
+ **result['floatingip'])
+ self.set_resource(rand_name('floatingip-'), floating_ip)
+ return floating_ip
+
+ def _ping_ip_address(self, ip_address):
+ cmd = ['ping', '-c1', '-w1', ip_address]
+
+ def ping():
+ proc = subprocess.Popen(cmd,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ proc.wait()
+ if proc.returncode == 0:
+ return True
+
+ # TODO(mnewby) Allow configuration of execution and sleep duration.
+ return test.call_until_true(ping, 20, 1)
diff --git a/tempest/tests/network/test_network_basic_ops.py b/tempest/tests/network/test_network_basic_ops.py
index aed368e..3afe8e3 100644
--- a/tempest/tests/network/test_network_basic_ops.py
+++ b/tempest/tests/network/test_network_basic_ops.py
@@ -1,6 +1,7 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 OpenStack, LLC
+# Copyright 2013 Hewlett-Packard Development Company, L.P.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -15,94 +16,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-import logging
-import subprocess
-
-import netaddr
-
-from quantumclient.common import exceptions as exc
-
from tempest.common.utils.data_utils import rand_name
-from tempest import smoke
-from tempest import test
+import tempest.tests.network.common as net_common
-LOG = logging.getLogger(__name__)
-
-
-class AttributeDict(dict):
-
- """
- Provide attribute access (dict.key) to dictionary values.
- """
-
- def __getattr__(self, name):
- """Allow attribute access for all keys in the dict."""
- if name in self:
- return self[name]
- return super(AttributeDict, self).__getattribute__(name)
-
-
-class DeletableResource(AttributeDict):
-
- """
- Support deletion of quantum resources (networks, subnets) via a
- delete() method, as is supported by keystone and nova resources.
- """
-
- def __init__(self, *args, **kwargs):
- self.client = kwargs.pop('client', None)
- super(DeletableResource, self).__init__(*args, **kwargs)
-
- def __str__(self):
- return '<%s id="%s" name="%s">' % (self.__class__.__name__,
- self.id, self.name)
-
- def delete(self):
- raise NotImplemented()
-
-
-class DeletableNetwork(DeletableResource):
-
- def delete(self):
- self.client.delete_network(self.id)
-
-
-class DeletableSubnet(DeletableResource):
-
- _router_ids = set()
-
- def add_to_router(self, router_id):
- self._router_ids.add(router_id)
- body = dict(subnet_id=self.id)
- self.client.add_interface_router(router_id, body=body)
-
- def delete(self):
- for router_id in self._router_ids.copy():
- body = dict(subnet_id=self.id)
- self.client.remove_interface_router(router_id, body=body)
- self._router_ids.remove(router_id)
- self.client.delete_subnet(self.id)
-
-
-class DeletableRouter(DeletableResource):
-
- def add_gateway(self, network_id):
- body = dict(network_id=network_id)
- self.client.add_gateway_router(self.id, body=body)
-
- def delete(self):
- self.client.remove_gateway_router(self.id)
- self.client.delete_router(self.id)
-
-
-class DeletableFloatingIp(DeletableResource):
-
- def delete(self):
- self.client.delete_floatingip(self.id)
-
-
-class TestNetworkBasicOps(smoke.DefaultClientSmokeTest):
+class TestNetworkBasicOps(net_common.TestNetworkSmokeCommon):
"""
This smoke test suite assumes that Nova has been configured to
@@ -165,19 +83,12 @@
@classmethod
def check_preconditions(cls):
+ super(TestNetworkBasicOps, cls).check_preconditions()
cfg = cls.config.network
- msg = None
if not (cfg.tenant_networks_reachable or cfg.public_network_id):
msg = ('Either tenant_networks_reachable must be "true", or '
'public_network_id must be defined.')
- else:
- try:
- cls.network_client.list_networks()
- except exc.QuantumClientException:
- msg = 'Unable to connect to Quantum service.'
-
- cls.enabled = not bool(msg)
- if msg:
+ cls.enabled = False
raise cls.skipException(msg)
@classmethod
@@ -198,55 +109,6 @@
cls.servers = []
cls.floating_ips = {}
- def _create_keypair(self, client):
- kp_name = rand_name('keypair-smoke-')
- keypair = client.keypairs.create(kp_name)
- try:
- self.assertEqual(keypair.id, kp_name)
- self.set_resource(kp_name, keypair)
- except AttributeError:
- self.fail("Keypair object not successfully created.")
- return keypair
-
- def _create_security_group(self, client):
- # Create security group
- sg_name = rand_name('secgroup-smoke-')
- sg_desc = sg_name + " description"
- secgroup = client.security_groups.create(sg_name, sg_desc)
- try:
- self.assertEqual(secgroup.name, sg_name)
- self.assertEqual(secgroup.description, sg_desc)
- self.set_resource(sg_name, secgroup)
- except AttributeError:
- self.fail("SecurityGroup object not successfully created.")
-
- # Add rules to the security group
- rulesets = [
- {
- # ssh
- 'ip_protocol': 'tcp',
- 'from_port': 22,
- 'to_port': 22,
- 'cidr': '0.0.0.0/0',
- 'group_id': secgroup.id
- },
- {
- # ping
- 'ip_protocol': 'icmp',
- 'from_port': -1,
- 'to_port': -1,
- 'cidr': '0.0.0.0/0',
- 'group_id': secgroup.id
- }
- ]
- for ruleset in rulesets:
- try:
- client.security_group_rules.create(secgroup.id, **ruleset)
- except Exception:
- self.fail("Failed to create rule in security group.")
-
- return secgroup
-
def _get_router(self, tenant_id):
"""Retrieve a router for the given tenant id.
@@ -261,7 +123,7 @@
network_id = self.config.network.public_network_id
if router_id:
result = self.network_client.show_router(router_id)
- return AttributeDict(**result['router'])
+ return net_common.AttributeDict(**result['router'])
elif network_id:
router = self._create_router(tenant_id)
router.add_gateway(network_id)
@@ -270,8 +132,8 @@
raise Exception("Neither of 'public_router_id' or "
"'public_network_id' has been defined.")
- def _create_router(self, tenant_id):
- name = rand_name('router-smoke-')
+ def _create_router(self, tenant_id, namestart='router-smoke-'):
+ name = rand_name(namestart)
body = dict(
router=dict(
name=name,
@@ -280,130 +142,12 @@
),
)
result = self.network_client.create_router(body=body)
- router = DeletableRouter(client=self.network_client,
- **result['router'])
+ router = net_common.DeletableRouter(client=self.network_client,
+ **result['router'])
self.assertEqual(router.name, name)
self.set_resource(name, router)
return router
- def _create_network(self, tenant_id):
- name = rand_name('network-smoke-')
- body = dict(
- network=dict(
- name=name,
- tenant_id=tenant_id,
- ),
- )
- result = self.network_client.create_network(body=body)
- network = DeletableNetwork(client=self.network_client,
- **result['network'])
- self.assertEqual(network.name, name)
- self.set_resource(name, network)
- return network
-
- def _list_networks(self):
- nets = self.network_client.list_networks()
- return nets['networks']
-
- def _list_subnets(self):
- subnets = self.network_client.list_subnets()
- return subnets['subnets']
-
- def _list_routers(self):
- routers = self.network_client.list_routers()
- return routers['routers']
-
- def _create_subnet(self, network):
- """
- Create a subnet for the given network within the cidr block
- configured for tenant networks.
- """
- cfg = self.config.network
- tenant_cidr = netaddr.IPNetwork(cfg.tenant_network_cidr)
- result = None
- # Repeatedly attempt subnet creation with sequential cidr
- # blocks until an unallocated block is found.
- for subnet_cidr in tenant_cidr.subnet(cfg.tenant_network_mask_bits):
- body = dict(
- subnet=dict(
- ip_version=4,
- network_id=network.id,
- tenant_id=network.tenant_id,
- cidr=str(subnet_cidr),
- ),
- )
- try:
- result = self.network_client.create_subnet(body=body)
- break
- except exc.QuantumClientException as e:
- is_overlapping_cidr = 'overlaps with another subnet' in str(e)
- if not is_overlapping_cidr:
- raise
- self.assertIsNotNone(result, 'Unable to allocate tenant network')
- subnet = DeletableSubnet(client=self.network_client,
- **result['subnet'])
- self.assertEqual(subnet.cidr, str(subnet_cidr))
- self.set_resource(rand_name('subnet-smoke-'), subnet)
- return subnet
-
- def _create_server(self, client, network, name, key_name, security_groups):
- flavor_id = self.config.compute.flavor_ref
- base_image_id = self.config.compute.image_ref
- create_kwargs = {
- 'nics': [
- {'net-id': network.id},
- ],
- 'key_name': key_name,
- 'security_groups': security_groups,
- }
- server = client.servers.create(name, base_image_id, flavor_id,
- **create_kwargs)
- try:
- self.assertEqual(server.name, name)
- self.set_resource(name, server)
- except AttributeError:
- self.fail("Server not successfully created.")
- self.status_timeout(client.servers, server.id, 'ACTIVE')
- # The instance retrieved on creation is missing network
- # details, necessitating retrieval after it becomes active to
- # ensure correct details.
- server = client.servers.get(server.id)
- self.set_resource(name, server)
- return server
-
- def _create_floating_ip(self, server, external_network_id):
- result = self.network_client.list_ports(device_id=server.id)
- ports = result.get('ports', [])
- self.assertEqual(len(ports), 1,
- "Unable to determine which port to target.")
- port_id = ports[0]['id']
- body = dict(
- floatingip=dict(
- floating_network_id=external_network_id,
- port_id=port_id,
- tenant_id=server.tenant_id,
- )
- )
- result = self.network_client.create_floatingip(body=body)
- floating_ip = DeletableFloatingIp(client=self.network_client,
- **result['floatingip'])
- self.set_resource(rand_name('floatingip-'), floating_ip)
- return floating_ip
-
- def _ping_ip_address(self, ip_address):
- cmd = ['ping', '-c1', '-w1', ip_address]
-
- def ping():
- proc = subprocess.Popen(cmd,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
- proc.wait()
- if proc.returncode == 0:
- return True
-
- # TODO(mnewby) Allow configuration of execution and sleep duration.
- return test.call_until_true(ping, 20, 1)
-
def test_001_create_keypairs(self):
self.keypairs[self.tenant_id] = self._create_keypair(
self.compute_client)
@@ -428,31 +172,21 @@
seen_names = [n['name'] for n in seen_nets]
seen_ids = [n['id'] for n in seen_nets]
for mynet in self.networks:
- assert mynet.name in seen_names, \
- "Did not see expected network with name %s" % mynet.name
- assert mynet.id in seen_ids, \
- "Did not see expected network with id %s" % mynet.id
+ self.assertIn(mynet.name, seen_names)
+ self.assertIn(mynet.id, seen_ids)
seen_subnets = self._list_subnets()
seen_net_ids = [n['network_id'] for n in seen_subnets]
seen_subnet_ids = [n['id'] for n in seen_subnets]
for mynet in self.networks:
- assert mynet.id in seen_net_ids, \
- "Did not see subnet belonging to network %s/%s" % \
- (mynet.name, mynet.id)
+ self.assertIn(mynet.id, seen_net_ids)
for mysubnet in self.subnets:
- assert mysubnet.id in seen_subnet_ids, \
- "Did not see expected subnet with id %s" % \
- mysubnet.id
+ self.assertIn(mysubnet.id, seen_subnet_ids)
seen_routers = self._list_routers()
seen_router_ids = [n['id'] for n in seen_routers]
seen_router_names = [n['name'] for n in seen_routers]
for myrouter in self.routers:
- assert myrouter.name in seen_router_names, \
- "Did not see expected router with name %s" % \
- myrouter.name
- assert myrouter.id in seen_router_ids, \
- "Did not see expected router with id %s" % \
- myrouter.id
+ self.assertIn(myrouter.name, seen_router_names)
+ self.assertIn(myrouter.id, seen_router_ids)
def test_005_create_servers(self):
if not (self.keypairs or self.security_groups or self.networks):
diff --git a/tempest/tests/network/test_network_quota_basic.py b/tempest/tests/network/test_network_quota_basic.py
new file mode 100644
index 0000000..eaec708
--- /dev/null
+++ b/tempest/tests/network/test_network_quota_basic.py
@@ -0,0 +1,92 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 Hewlett-Packard Development Company, L.P.
+# 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 quantumclient.common import exceptions as exc
+from tempest.tests.network.common import TestNetworkSmokeCommon
+
+MAX_REASONABLE_ITERATIONS = 51 # more than enough. Default for port is 50.
+
+
+class TestNetworkQuotaBasic(TestNetworkSmokeCommon):
+ """
+ This test suite contains tests that each loop trying to grab a
+ particular resource until a quota limit is hit.
+ For sanity, there is a maximum number of iterations - if this is hit
+ the test fails. Covers network, subnet, port.
+ """
+
+ @classmethod
+ def check_preconditions(cls):
+ super(TestNetworkQuotaBasic, cls).check_preconditions()
+
+ @classmethod
+ def setUpClass(cls):
+ super(TestNetworkQuotaBasic, cls).setUpClass()
+ cls.check_preconditions()
+ cls.networks = []
+ cls.subnets = []
+ cls.ports = []
+
+ def test_create_network_until_quota_hit(self):
+ hit_limit = False
+ for n in xrange(MAX_REASONABLE_ITERATIONS):
+ try:
+ self.networks.append(
+ self._create_network(self.tenant_id,
+ namestart='network-quotatest-'))
+ except exc.QuantumClientException as e:
+ if (e.status_code != 409):
+ raise
+ hit_limit = True
+ break
+ self.assertTrue(hit_limit, "Failed: Did not hit quota limit !")
+
+ def test_create_subnet_until_quota_hit(self):
+ if not self.networks:
+ self.networks.append(
+ self._create_network(self.tenant_id,
+ namestart='network-quotatest-'))
+ hit_limit = False
+ for n in xrange(MAX_REASONABLE_ITERATIONS):
+ try:
+ self.subnets.append(
+ self._create_subnet(self.networks[0],
+ namestart='subnet-quotatest-'))
+ except exc.QuantumClientException as e:
+ if (e.status_code != 409):
+ raise
+ hit_limit = True
+ break
+ self.assertTrue(hit_limit, "Failed: Did not hit quota limit !")
+
+ def test_create_ports_until_quota_hit(self):
+ if not self.networks:
+ self.networks.append(
+ self._create_network(self.tenant_id,
+ namestart='network-quotatest-'))
+ hit_limit = False
+ for n in xrange(MAX_REASONABLE_ITERATIONS):
+ try:
+ self.ports.append(
+ self._create_port(self.networks[0],
+ namestart='port-quotatest-'))
+ except exc.QuantumClientException as e:
+ if (e.status_code != 409):
+ raise
+ hit_limit = True
+ break
+ self.assertTrue(hit_limit, "Failed: Did not hit quota limit !")
diff --git a/tempest/tests/network/test_networks.py b/tempest/tests/network/test_networks.py
index 136279f..e61bc62 100644
--- a/tempest/tests/network/test_networks.py
+++ b/tempest/tests/network/test_networks.py
@@ -15,51 +15,84 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.test import attr
+import netaddr
from tempest.common.utils.data_utils import rand_name
+from tempest import exceptions
+from tempest.test import attr
from tempest.tests.network import base
class NetworksTest(base.BaseNetworkTest):
+ """
+ Tests the following operations in the Quantum API using the REST client for
+ Quantum:
+
+ create a network for a tenant
+ list tenant's networks
+ show a tenant network details
+ create a subnet for a tenant
+ list tenant's subnets
+ show a tenant subnet details
+
+ v2.0 of the Quantum API is assumed. It is also assumed that the following
+ options are defined in the [network] section of etc/tempest.conf:
+
+ tenant_network_cidr with a block of cidr's from which smaller blocks
+ can be allocated for tenant networks
+
+ tenant_network_mask_bits with the mask bits to be used to partition the
+ block defined by tenant-network_cidr
+ """
+
@classmethod
def setUpClass(cls):
super(NetworksTest, cls).setUpClass()
cls.network = cls.create_network()
cls.name = cls.network['name']
+ cls.subnet = cls.create_subnet(cls.network)
+ cls.cidr = cls.subnet['cidr']
@attr(type='positive')
- def test_create_delete_network(self):
- # Creates and deletes a network for a tenant
- name = rand_name('network')
+ def test_create_delete_network_subnet(self):
+ # Creates a network
+ name = rand_name('network-')
resp, body = self.client.create_network(name)
- self.assertEqual('202', resp['status'])
+ self.assertEqual('201', resp['status'])
network = body['network']
self.assertTrue(network['id'] is not None)
+ # Find a cidr that is not in use yet and create a subnet with it
+ cidr = netaddr.IPNetwork(self.network_cfg.tenant_network_cidr)
+ mask_bits = self.network_cfg.tenant_network_mask_bits
+ for subnet_cidr in cidr.subnet(mask_bits):
+ try:
+ resp, body = self.client.create_subnet(network['id'],
+ str(subnet_cidr))
+ break
+ except exceptions.BadRequest as e:
+ is_overlapping_cidr = 'overlaps with another subnet' in str(e)
+ if not is_overlapping_cidr:
+ raise
+ self.assertEqual('201', resp['status'])
+ subnet = body['subnet']
+ self.assertTrue(subnet['id'] is not None)
+ #Deletes subnet and network
+ resp, body = self.client.delete_subnet(subnet['id'])
+ self.assertEqual('204', resp['status'])
resp, body = self.client.delete_network(network['id'])
self.assertEqual('204', resp['status'])
@attr(type='positive')
def test_show_network(self):
# Verifies the details of a network
- resp, body = self.client.get_network(self.network['id'])
+ resp, body = self.client.show_network(self.network['id'])
self.assertEqual('200', resp['status'])
network = body['network']
self.assertEqual(self.network['id'], network['id'])
self.assertEqual(self.name, network['name'])
@attr(type='positive')
- def test_show_network_details(self):
- # Verifies the full details of a network
- resp, body = self.client.get_network_details(self.network['id'])
- self.assertEqual('200', resp['status'])
- network = body['network']
- self.assertEqual(self.network['id'], network['id'])
- self.assertEqual(self.name, network['name'])
- self.assertEqual(len(network['ports']), 0)
-
- @attr(type='positive')
def test_list_networks(self):
# Verify the network exists in the list of all networks
resp, body = self.client.list_networks()
@@ -68,9 +101,18 @@
self.assertTrue(found)
@attr(type='positive')
- def test_list_networks_with_detail(self):
- # Verify the network exists in the detailed list of all networks
- resp, body = self.client.list_networks_details()
- networks = body['networks']
- found = any(n for n in networks if n['id'] == self.network['id'])
+ def test_show_subnet(self):
+ # Verifies the details of a subnet
+ resp, body = self.client.show_subnet(self.subnet['id'])
+ self.assertEqual('200', resp['status'])
+ subnet = body['subnet']
+ self.assertEqual(self.subnet['id'], subnet['id'])
+ self.assertEqual(self.cidr, subnet['cidr'])
+
+ @attr(type='positive')
+ def test_list_subnets(self):
+ # Verify the subnet exists in the list of all subnets
+ resp, body = self.client.list_subnets()
+ subnets = body['subnets']
+ found = any(n for n in subnets if n['id'] == self.subnet['id'])
self.assertTrue(found)
diff --git a/tempest/tests/object_storage/test_container_sync.py b/tempest/tests/object_storage/test_container_sync.py
index dad6309..d5fa96c 100644
--- a/tempest/tests/object_storage/test_container_sync.py
+++ b/tempest/tests/object_storage/test_container_sync.py
@@ -61,7 +61,7 @@
#Attempt to delete the container
resp, _ = client[0].delete_container(cont_name)
- @testtools.skip('Until Bug 1093743 is resolved.')
+ @testtools.skip('Until Bug #1093743 is resolved.')
@attr(type='positive')
def test_container_synchronization(self):
#Container to container synchronization
diff --git a/tempest/tests/object_storage/test_object_expiry.py b/tempest/tests/object_storage/test_object_expiry.py
index 8411ef5..c12ec3d 100644
--- a/tempest/tests/object_storage/test_object_expiry.py
+++ b/tempest/tests/object_storage/test_object_expiry.py
@@ -55,7 +55,7 @@
#Attempt to delete the container
resp, _ = cls.container_client.delete_container(cls.container_name)
- @testtools.skip('Until bug 1069849 is resolved.')
+ @testtools.skip('Until Bug #1069849 is resolved.')
@attr(type='regression')
def test_get_object_after_expiry_time(self):
# GET object after expiry time
diff --git a/tempest/tests/object_storage/test_object_services.py b/tempest/tests/object_storage/test_object_services.py
index 76fab0b..1edce92 100644
--- a/tempest/tests/object_storage/test_object_services.py
+++ b/tempest/tests/object_storage/test_object_services.py
@@ -590,7 +590,7 @@
self.container_name, object_name,
metadata=self.custom_headers)
- @testtools.skip('Until bug 1097137 is resolved.')
+ @testtools.skip('Until Bug #1097137 is resolved.')
@attr(type='positive')
def test_get_object_using_temp_url(self):
#Access object using temp url within expiry time
diff --git a/tempest/tests/volume/admin/test_volume_types.py b/tempest/tests/volume/admin/test_volume_types.py
index 6eb3629..38ac74a 100644
--- a/tempest/tests/volume/admin/test_volume_types.py
+++ b/tempest/tests/volume/admin/test_volume_types.py
@@ -37,10 +37,6 @@
auth_url,
adm_tenant)
- @classmethod
- def tearDownClass(cls):
- super(VolumeTypesTest, cls).tearDownClass()
-
def test_volume_type_list(self):
# List Volume types.
try:
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 f528cec..13fcbbf 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
@@ -15,7 +15,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import testtools
import uuid
from tempest.common.utils.data_utils import rand_name
diff --git a/tempest/tests/volume/admin/test_volume_types_negative.py b/tempest/tests/volume/admin/test_volume_types_negative.py
index 1b11d68..daf804d 100644
--- a/tempest/tests/volume/admin/test_volume_types_negative.py
+++ b/tempest/tests/volume/admin/test_volume_types_negative.py
@@ -15,7 +15,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import testtools
import uuid
from tempest import exceptions
diff --git a/tempest/tests/volume/test_volumes_get.py b/tempest/tests/volume/test_volumes_get.py
index a246afe..8e80e18 100644
--- a/tempest/tests/volume/test_volumes_get.py
+++ b/tempest/tests/volume/test_volumes_get.py
@@ -29,17 +29,22 @@
super(VolumesGetTest, cls).setUpClass()
cls.client = cls.volumes_client
- @attr(type='smoke')
- def test_volume_create_get_delete(self):
+ def _volume_create_get_delete(self, image_ref=None):
# Create a volume, Get it's details and Delete the volume
try:
volume = {}
v_name = rand_name('Volume-')
metadata = {'Type': 'work'}
#Create a volume
- resp, volume = self.client.create_volume(size=1,
- display_name=v_name,
- metadata=metadata)
+ if not image_ref:
+ resp, volume = self.client.create_volume(size=1,
+ display_name=v_name,
+ metadata=metadata)
+ else:
+ resp, volume = self.client.create_volume(size=1,
+ display_name=v_name,
+ metadata=metadata,
+ imageRef=image_ref)
self.assertEqual(200, resp.status)
self.assertTrue('id' in volume)
self.assertTrue('display_name' in volume)
@@ -100,6 +105,14 @@
self.assertEqual(202, resp.status)
self.client.wait_for_resource_deletion(volume['id'])
+ @attr(type='smoke')
+ def test_volume_create_get_delete(self):
+ self._volume_create_get_delete(image_ref=None)
+
+ @attr(type='smoke')
+ def test_volume_from_image(self):
+ self._volume_create_get_delete(image_ref=self.config.compute.image_ref)
+
class VolumesGetTestXML(VolumesGetTest):
_interface = "xml"
diff --git a/tools/check_source.sh b/tools/check_source.sh
new file mode 100755
index 0000000..089ad70
--- /dev/null
+++ b/tools/check_source.sh
@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+
+python tools/hacking.py --ignore=E122,E125,E126 --repeat --show-source --exclude=.venv,.tox,dist,doc,openstack,*egg .
+pep8_ret=$?
+
+pyflakes tempest stress setup.py tools cli bin | grep "imported but unused"
+unused_ret=$?
+
+ret=0
+if [ $pep8_ret != 0 ]; then
+ echo "hacking.py/pep8 test FAILED!" >&2
+ (( ret += 1 ))
+else
+ echo "hacking.py/pep8 test OK!" >&2
+fi
+
+if [ $unused_ret == 0 ]; then
+ echo "Unused import test FAILED!" >&2
+ (( ret += 2 ))
+else
+ echo "Unused import test OK!" >&2
+fi
+
+exit $ret
diff --git a/tools/hacking.py b/tools/hacking.py
index 617682d..7e46b74 100755
--- a/tools/hacking.py
+++ b/tools/hacking.py
@@ -21,7 +21,6 @@
built on top of pep8.py
"""
-import fnmatch
import inspect
import logging
import os
@@ -323,6 +322,30 @@
return (pos, "T404: test functions must "
"not have doc strings")
+SKIP_DECORATOR = '@testtools.skip('
+
+
+def tempest_skip_bugs(physical_line):
+ """Check skip lines for proper bug entries
+
+ T601: Bug not in skip line
+ T602: Bug in message formatted incorrectly
+ """
+
+ pos = physical_line.find(SKIP_DECORATOR)
+
+ skip_re = re.compile(r'^\s*@testtools.skip.*')
+
+ if pos != -1 and skip_re.match(physical_line):
+ bug = re.compile(r'^.*\bbug\b.*', re.IGNORECASE)
+ if bug.match(physical_line) is None:
+ return (pos, 'T601: skips must have an associated bug')
+
+ bug_re = re.compile(r'.*skip\(.*Bug\s\#\d+', re.IGNORECASE)
+
+ if bug_re.match(physical_line) is None:
+ return (pos, 'T602: Bug number formatted incorrectly')
+
FORMAT_RE = re.compile("%(?:"
"%|" # Ignore plain percents
diff --git a/tools/install_venv.py b/tools/install_venv.py
index 20dcefa..ef7b0a8 100644
--- a/tools/install_venv.py
+++ b/tools/install_venv.py
@@ -21,9 +21,7 @@
"""Installation script for Tempest's development virtualenv."""
-import optparse
import os
-import subprocess
import sys
import install_venv_common as install_venv
diff --git a/tools/pip-requires b/tools/pip-requires
index e85cced..758442c 100644
--- a/tools/pip-requires
+++ b/tools/pip-requires
@@ -14,3 +14,6 @@
keyring
testrepository
oslo.config>=1.1.0
+# Needed for whitebox testing
+sqlalchemy
+MySQL-python
diff --git a/tools/tempest_coverage.py b/tools/tempest_coverage.py
index a46d0fb..9dcbd46 100755
--- a/tools/tempest_coverage.py
+++ b/tools/tempest_coverage.py
@@ -1,6 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
-# Copyright 2012 IBM
+# Copyright 2012 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
@@ -16,7 +16,6 @@
import json
import os
-import re
import shutil
import sys
@@ -24,7 +23,6 @@
from tempest.common.rest_client import RestClient
from tempest import config
-from tempest.tests.compute import base
CONF = config.TempestConfig()
diff --git a/tools/test-requires b/tools/test-requires
index 4801391..f701dab 100644
--- a/tools/test-requires
+++ b/tools/test-requires
@@ -1,6 +1,5 @@
pep8==1.3.3
pylint==0.19
-# Needed for whitebox testing
-sqlalchemy
-MySQL-python
+#TODO(afazekas): ensure pg_config installed
psycopg2
+pyflakes
diff --git a/tox.ini b/tox.ini
index 92ce6bc..85a0d86 100644
--- a/tox.ini
+++ b/tox.ini
@@ -19,4 +19,4 @@
python -m tools/tempest_coverage -c report --html
[testenv:pep8]
-commands = python tools/hacking.py --ignore=E122,E125,E126 --repeat --show-source --exclude=.venv,.tox,dist,doc,openstack,*egg .
+commands = bash tools/check_source.sh