Merge "Removed duplicate usage of TempestConfig()"
diff --git a/.gitignore b/.gitignore
index c154603..f5f51ab 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@
ChangeLog
*.pyc
etc/tempest.conf
+etc/logging.conf
include/swift_objects/swift_small
include/swift_objects/swift_medium
include/swift_objects/swift_large
diff --git a/cli/__init__.py b/cli/__init__.py
index 7a92260..a3038d2 100644
--- a/cli/__init__.py
+++ b/cli/__init__.py
@@ -87,6 +87,15 @@
flags = creds + ' ' + flags
return self.cmd(cmd, action, flags, params, fail_ok)
+ def check_output(self, cmd, **kwargs):
+ # substitutes subprocess.check_output which is not in python2.6
+ kwargs['stdout'] = subprocess.PIPE
+ proc = subprocess.Popen(cmd, **kwargs)
+ output = proc.communicate()[0]
+ if proc.returncode != 0:
+ raise CommandFailed(proc.returncode, cmd, output)
+ return output
+
def cmd(self, cmd, action, flags='', params='', fail_ok=False,
merge_stderr=False):
"""Executes specified command for the given action."""
@@ -96,10 +105,10 @@
cmd = shlex.split(cmd)
try:
if merge_stderr:
- result = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
+ result = self.check_output(cmd, stderr=subprocess.STDOUT)
else:
- devnull = open('/dev/null', 'w')
- result = subprocess.check_output(cmd, stderr=devnull)
+ with open('/dev/null', 'w') as devnull:
+ result = self.check_output(cmd, stderr=devnull)
except subprocess.CalledProcessError, e:
LOG.error("command output:\n%s" % e.output)
raise
@@ -110,3 +119,10 @@
for item in items:
for field in field_names:
self.assertIn(field, item)
+
+
+class CommandFailed(subprocess.CalledProcessError):
+ # adds output attribute for python2.6
+ def __init__(self, returncode, cmd, output):
+ super(CommandFailed, self).__init__(returncode, cmd)
+ self.output = output
diff --git a/etc/logging.conf.sample b/etc/logging.conf.sample
new file mode 100644
index 0000000..5c1ea5f
--- /dev/null
+++ b/etc/logging.conf.sample
@@ -0,0 +1,30 @@
+[loggers]
+keys=root
+
+[formatters]
+keys=normal,debug
+
+[handlers]
+keys=file,devel
+
+[logger_root]
+level=NOTSET
+handlers=file
+
+[handler_file]
+class=FileHandler
+level=DEBUG
+formatter=normal
+args=('tempest.log', 'w')
+
+[handler_devel]
+class=StreamHandler
+level=DEBUG
+formatter=debug
+args=(sys.stdout,)
+
+[formatter_normal]
+format=%(asctime)s %(levelname)s %(message)s
+
+[formatter_debug]
+format=%(asctime)s %(levelname)s %(module)s %(funcName)s %(message)s
diff --git a/run_tests.sh b/run_tests.sh
index 25b9729..56a6e6e 100755
--- a/run_tests.sh
+++ b/run_tests.sh
@@ -17,6 +17,8 @@
echo " -h, --help Print this usage message"
echo " -d, --debug Debug this script -- set -o xtrace"
echo " -S, --stdout Don't capture stdout"
+ echo " -l, --logging Enable logging"
+ echo " -L, --logging-config Logging config file location. Default is etc/logging.conf"
echo " -- [NOSEOPTIONS] After the first '--' you can pass arbitrary arguments to nosetests "
}
@@ -32,8 +34,10 @@
nova_coverage=0
config_file=""
update=0
+logging=0
+logging_config=etc/logging.conf
-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:lL: -l virtual-env,no-virtual-env,no-site-packages,force,update,smoke,whitebox,nova-coverage,pep8,help,debug,stdout,config:,logging,logging-config: -- "$@")
then
# parse error
usage
@@ -57,6 +61,8 @@
-s|--smoke) noseargs="$noseargs --attr=type=smoke";;
-w|--whitebox) noseargs="$noseargs --attr=type=whitebox";;
-S|--stdout) noseargs="$noseargs -s";;
+ -l|--logging) logging=1;;
+ -L|--logging-config) logging_config=$2; shift;;
--) [ "yes" == "$first_uu" ] || noseargs="$noseargs $1"; first_uu=no ;;
*) noseargs="$noseargs $1"
esac
@@ -78,6 +84,14 @@
export NOSE_OPENSTACK_SHOW_ELAPSED=1
export NOSE_OPENSTACK_STDOUT=1
+if [ $logging -eq 1 ]; then
+ if [ ! -f "$logging_config" ]; then
+ echo "No such logging config file: $logging_config"
+ exit
+ fi
+ noseargs="$noseargs --logging-config=$logging_config"
+fi
+
if [ $no_site_packages -eq 1 ]; then
installvenvopts="--no-site-packages"
fi
@@ -95,7 +109,7 @@
function run_pep8 {
echo "Running pep8 ..."
- ${wrapper} tools/check_source.sh
+ ${wrapper} flake8
}
function run_coverage_start {
diff --git a/stress/driver.py b/stress/driver.py
index f80e765..533c000 100644
--- a/stress/driver.py
+++ b/stress/driver.py
@@ -18,7 +18,7 @@
import datetime
import random
import time
-from urlparse import urlparse
+import urlparse
from config import StressConfig
from state import ClusterState
@@ -174,7 +174,7 @@
keypath = stress_config.host_private_key_path
user = stress_config.host_admin_user
logdir = stress_config.nova_logdir
- host = urlparse(manager.config.identity.uri).hostname
+ host = urlparse.urlparse(manager.config.identity.uri).hostname
computes = _get_compute_nodes(keypath, user, host)
stress.utils.execute_on_all(keypath, user, computes,
"rm -f %s/*.log" % logdir)
@@ -261,7 +261,7 @@
state.delete_instance_state(kill_id)
for floating_ip_state in state.get_floating_ips():
manager.floating_ips_client.delete_floating_ip(
- floating_ip_state.resource_id)
+ floating_ip_state.resource_id)
for keypair_state in state.get_keypairs():
manager.keypairs_client.delete_keypair(keypair_state.name)
for volume_state in state.get_volumes():
diff --git a/stress/test_floating_ips.py b/stress/test_floating_ips.py
index 6774e81..c5bad95 100755
--- a/stress/test_floating_ips.py
+++ b/stress/test_floating_ips.py
@@ -15,8 +15,8 @@
import random
import telnetlib
-import pending_action
-import test_case
+from stress import pending_action
+from stress import test_case
class TestChangeFloatingIp(test_case.StressTestCase):
diff --git a/stress/test_server_actions.py b/stress/test_server_actions.py
index f4ddf23..3a7094d 100644
--- a/stress/test_server_actions.py
+++ b/stress/test_server_actions.py
@@ -19,10 +19,10 @@
import random
-import pending_action
+from stress import pending_action
+from stress import test_case
import stress.utils
from tempest.exceptions import Duplicate
-import test_case
class TestRebootVM(test_case.StressTestCase):
diff --git a/stress/test_servers.py b/stress/test_servers.py
index 25cbbb0..1dd72f1 100644
--- a/stress/test_servers.py
+++ b/stress/test_servers.py
@@ -19,8 +19,8 @@
import random
-import pending_action
-import test_case
+from stress import pending_action
+from stress import test_case
class TestCreateVM(test_case.StressTestCase):
diff --git a/tempest/clients.py b/tempest/clients.py
index b3b5906..2934f81 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -20,13 +20,20 @@
from tempest import config
from tempest import exceptions
from tempest.services import botoclients
+from tempest.services.compute.json.aggregates_client import \
+ AggregatesClientJSON
+from tempest.services.compute.json.availability_zone_client import \
+ AvailabilityZoneClientJSON
from tempest.services.compute.json.extensions_client import \
ExtensionsClientJSON
+from tempest.services.compute.json.fixed_ips_client import FixedIPsClientJSON
from tempest.services.compute.json.flavors_client import FlavorsClientJSON
from tempest.services.compute.json.floating_ips_client import \
FloatingIPsClientJSON
from tempest.services.compute.json.hosts_client import HostsClientJSON
from tempest.services.compute.json.images_client import ImagesClientJSON
+from tempest.services.compute.json.interfaces_client import \
+ InterfacesClientJSON
from tempest.services.compute.json.keypairs_client import KeyPairsClientJSON
from tempest.services.compute.json.limits_client import LimitsClientJSON
from tempest.services.compute.json.quotas_client import QuotasClientJSON
@@ -35,11 +42,16 @@
from tempest.services.compute.json.servers_client import ServersClientJSON
from tempest.services.compute.json.volumes_extensions_client import \
VolumesExtensionsClientJSON
+from tempest.services.compute.xml.availability_zone_client import \
+ AvailabilityZoneClientXML
from tempest.services.compute.xml.extensions_client import ExtensionsClientXML
+from tempest.services.compute.xml.fixed_ips_client import FixedIPsClientXML
from tempest.services.compute.xml.flavors_client import FlavorsClientXML
from tempest.services.compute.xml.floating_ips_client import \
FloatingIPsClientXML
from tempest.services.compute.xml.images_client import ImagesClientXML
+from tempest.services.compute.xml.interfaces_client import \
+ InterfacesClientXML
from tempest.services.compute.xml.keypairs_client import KeyPairsClientXML
from tempest.services.compute.xml.limits_client import LimitsClientXML
from tempest.services.compute.xml.quotas_client import QuotasClientXML
@@ -50,6 +62,13 @@
VolumesExtensionsClientXML
from tempest.services.identity.json.identity_client import IdentityClientJSON
from tempest.services.identity.json.identity_client import TokenClientJSON
+from tempest.services.identity.v3.json.endpoints_client import \
+ EndPointClientJSON
+from tempest.services.identity.v3.json.identity_client import \
+ IdentityV3ClientJSON
+from tempest.services.identity.v3.xml.endpoints_client import EndPointClientXML
+from tempest.services.identity.v3.xml.identity_client import \
+ IdentityV3ClientXML
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
@@ -70,10 +89,6 @@
VolumeTypesClientXML
from tempest.services.volume.xml.snapshots_client import SnapshotsClientXML
from tempest.services.volume.xml.volumes_client import VolumesClientXML
-from tempest.services.compute.json.interfaces_client import \
- InterfacesClientJSON
-from tempest.services.compute.xml.interfaces_client import \
- InterfacesClientXML
LOG = logging.getLogger(__name__)
@@ -142,6 +157,11 @@
"xml": IdentityClientXML,
}
+IDENTITY_V3_CLIENT = {
+ "json": IdentityV3ClientJSON,
+ "xml": IdentityV3ClientXML,
+}
+
TOKEN_CLIENT = {
"json": TokenClientJSON,
"xml": TokenClientXML,
@@ -157,6 +177,21 @@
"xml": InterfacesClientXML,
}
+ENDPOINT_CLIENT = {
+ "json": EndPointClientJSON,
+ "xml": EndPointClientXML,
+}
+
+FIXED_IPS_CLIENT = {
+ "json": FixedIPsClientJSON,
+ "xml": FixedIPsClientXML
+}
+
+AVAILABILITY_ZONE_CLIENT = {
+ "json": AvailabilityZoneClientJSON,
+ "xml": AvailabilityZoneClientXML,
+}
+
class Manager(object):
@@ -215,10 +250,16 @@
self.volume_types_client = \
VOLUME_TYPES_CLIENTS[interface](*client_args)
self.identity_client = IDENTITY_CLIENT[interface](*client_args)
+ self.identity_v3_client = \
+ IDENTITY_V3_CLIENT[interface](*client_args)
self.token_client = TOKEN_CLIENT[interface](self.config)
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)
+ self.fixed_ips_client = FIXED_IPS_CLIENT[interface](*client_args)
+ self.availability_zone_client = \
+ AVAILABILITY_ZONE_CLIENT[interface](*client_args)
except KeyError:
msg = "Unsupported interface type `%s'" % interface
raise exceptions.InvalidConfiguration(msg)
@@ -234,6 +275,7 @@
self.custom_object_client = ObjectClientCustomizedHeader(*client_args)
self.custom_account_client = \
AccountClientCustomizedHeader(*client_args)
+ self.aggregates_client = AggregatesClientJSON(*client_args)
class AltManager(Manager):
diff --git a/tempest/common/glance_http.py b/tempest/common/glance_http.py
index 0902239..4ddaf17 100644
--- a/tempest/common/glance_http.py
+++ b/tempest/common/glance_http.py
@@ -136,7 +136,7 @@
# Read body into string if it isn't obviously image data
if resp.getheader('content-type', None) != 'application/octet-stream':
- body_str = ''.join([chunk for chunk in body_iter])
+ body_str = ''.join([body_chunk for body_chunk in body_iter])
body_iter = StringIO.StringIO(body_str)
self._log_response(resp, None)
else:
diff --git a/tempest/common/rest_client.py b/tempest/common/rest_client.py
index 366e467..fba3b0f 100644
--- a/tempest/common/rest_client.py
+++ b/tempest/common/rest_client.py
@@ -179,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)
diff --git a/tempest/common/ssh.py b/tempest/common/ssh.py
index be6fe27..448708e 100644
--- a/tempest/common/ssh.py
+++ b/tempest/common/ssh.py
@@ -16,7 +16,7 @@
# under the License.
-from cStringIO import StringIO
+import cStringIO
import select
import socket
import time
@@ -28,7 +28,6 @@
with warnings.catch_warnings():
warnings.simplefilter("ignore")
import paramiko
- from paramiko import RSAKey
class Client(object):
@@ -39,7 +38,8 @@
self.username = username
self.password = password
if isinstance(pkey, basestring):
- pkey = RSAKey.from_private_key(StringIO(str(pkey)))
+ pkey = paramiko.RSAKey.from_private_key(
+ cStringIO.StringIO(str(pkey)))
self.pkey = pkey
self.look_for_keys = look_for_keys
self.key_filename = key_filename
@@ -117,8 +117,8 @@
ready = select.select(*select_params)
if not any(ready):
raise exceptions.TimeoutException(
- "Command: '{0}' executed on host '{1}'.".format(
- cmd, self.host))
+ "Command: '{0}' executed on host '{1}'.".format(
+ cmd, self.host))
if not ready[0]: # If there is nothing to read.
continue
out_chunk = err_chunk = None
@@ -133,8 +133,8 @@
exit_status = channel.recv_exit_status()
if 0 != exit_status:
raise exceptions.SSHExecCommandFailed(
- command=cmd, exit_status=exit_status,
- strerror=''.join(err_data))
+ command=cmd, exit_status=exit_status,
+ strerror=''.join(err_data))
return ''.join(out_data)
def test_connection_auth(self):
diff --git a/tempest/config.py b/tempest/config.py
index 9c41660..556e2a7 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -420,6 +420,9 @@
def __init__(self):
"""Initialize a configuration from a conf directory and conf file."""
+ config_files = []
+
+ failsafe_path = "/etc/tempest/" + self.DEFAULT_CONFIG_FILE
# Environment variables override defaults...
conf_dir = os.environ.get('TEMPEST_CONFIG_DIR',
@@ -431,16 +434,17 @@
if not (os.path.isfile(path) or
'TEMPEST_CONFIG_DIR' in os.environ or
'TEMPEST_CONFIG' in os.environ):
- path = "/etc/tempest/" + self.DEFAULT_CONFIG_FILE
+ path = failsafe_path
LOG.info("Using tempest config file %s" % path)
if not os.path.exists(path):
msg = "Config file %(path)s not found" % locals()
print >> sys.stderr, RuntimeError(msg)
- sys.exit(os.EX_NOINPUT)
+ else:
+ config_files.append(path)
- cfg.CONF([], project='tempest', default_config_files=[path])
+ cfg.CONF([], project='tempest', default_config_files=config_files)
register_compute_opts(cfg.CONF)
register_identity_opts(cfg.CONF)
diff --git a/tempest/services/botoclients.py b/tempest/services/botoclients.py
index 0870c96..628151a 100644
--- a/tempest/services/botoclients.py
+++ b/tempest/services/botoclients.py
@@ -96,7 +96,7 @@
ec2_cred.secret
else:
raise exceptions.InvalidConfiguration(
- "Unable to get access and secret keys")
+ "Unable to get access and secret keys")
return self.connect_method(**self.connection_data)
diff --git a/tempest/services/compute/json/aggregates_client.py b/tempest/services/compute/json/aggregates_client.py
new file mode 100644
index 0000000..7ae1eee
--- /dev/null
+++ b/tempest/services/compute/json/aggregates_client.py
@@ -0,0 +1,86 @@
+# 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.
+
+import json
+
+from tempest.common.rest_client import RestClient
+from tempest import exceptions
+
+
+class AggregatesClientJSON(RestClient):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(AggregatesClientJSON, self).__init__(config, username, password,
+ auth_url, tenant_name)
+ self.service = self.config.compute.catalog_type
+
+ def list_aggregates(self):
+ """Get aggregate list."""
+ resp, body = self.get("os-aggregates")
+ body = json.loads(body)
+ return resp, body['aggregates']
+
+ def get_aggregate(self, aggregate_id):
+ """Get details of the given aggregate."""
+ resp, body = self.get("os-aggregates/%s" % str(aggregate_id))
+ body = json.loads(body)
+ return resp, body['aggregate']
+
+ def create_aggregate(self, name, availability_zone=None):
+ """Creates a new aggregate."""
+ post_body = {
+ 'name': name,
+ 'availability_zone': availability_zone,
+ }
+ post_body = json.dumps({'aggregate': post_body})
+ resp, body = self.post('os-aggregates', post_body, self.headers)
+
+ body = json.loads(body)
+ return resp, body['aggregate']
+
+ def delete_aggregate(self, aggregate_id):
+ """Deletes the given aggregate."""
+ return self.delete("os-aggregates/%s" % str(aggregate_id))
+
+ def is_resource_deleted(self, id):
+ try:
+ self.get_aggregate(id)
+ except exceptions.NotFound:
+ return True
+ return False
+
+ def add_host(self, aggregate_id, host):
+ """Adds a host to the given aggregate."""
+ post_body = {
+ 'host': host,
+ }
+ post_body = json.dumps({'add_host': post_body})
+ resp, body = self.post('os-aggregates/%s/action' % aggregate_id,
+ post_body, self.headers)
+ body = json.loads(body)
+ return resp, body['aggregate']
+
+ def remove_host(self, aggregate_id, host):
+ """Removes a host from the given aggregate."""
+ post_body = {
+ 'host': host,
+ }
+ post_body = json.dumps({'remove_host': post_body})
+ resp, body = self.post('os-aggregates/%s/action' % aggregate_id,
+ post_body, self.headers)
+ body = json.loads(body)
+ return resp, body['aggregate']
diff --git a/tempest/services/compute/json/availability_zone_client.py b/tempest/services/compute/json/availability_zone_client.py
new file mode 100644
index 0000000..b11871b
--- /dev/null
+++ b/tempest/services/compute/json/availability_zone_client.py
@@ -0,0 +1,39 @@
+# 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.
+
+import json
+
+from tempest.common.rest_client import RestClient
+
+
+class AvailabilityZoneClientJSON(RestClient):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(AvailabilityZoneClientJSON, self).__init__(config, username,
+ password, auth_url,
+ tenant_name)
+ self.service = self.config.compute.catalog_type
+
+ def get_availability_zone_list(self):
+ resp, body = self.get('os-availability-zone')
+ body = json.loads(body)
+ return resp, body['availabilityZoneInfo']
+
+ def get_availability_zone_list_detail(self):
+ resp, body = self.get('os-availability-zone/detail')
+ body = json.loads(body)
+ return resp, body['availabilityZoneInfo']
diff --git a/tempest/services/compute/json/fixed_ips_client.py b/tempest/services/compute/json/fixed_ips_client.py
new file mode 100644
index 0000000..4ef7c4c
--- /dev/null
+++ b/tempest/services/compute/json/fixed_ips_client.py
@@ -0,0 +1,40 @@
+# 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.
+
+import json
+
+from tempest.common.rest_client import RestClient
+
+
+class FixedIPsClientJSON(RestClient):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(FixedIPsClientJSON, self).__init__(config, username, password,
+ auth_url, tenant_name)
+ self.service = self.config.compute.catalog_type
+
+ def get_fixed_ip_details(self, fixed_ip):
+ url = "os-fixed-ips/%s" % (fixed_ip)
+ resp, body = self.get(url)
+ body = json.loads(body)
+ return resp, body['fixed_ip']
+
+ def reserve_fixed_ip(self, ip, body):
+ """This reserves and unreserves fixed ips."""
+ url = "os-fixed-ips/%s/action" % (ip)
+ resp, body = self.post(url, json.dumps(body), self.headers)
+ return resp, body
diff --git a/tempest/services/compute/json/interfaces_client.py b/tempest/services/compute/json/interfaces_client.py
index 468a5c2..06e6476 100644
--- a/tempest/services/compute/json/interfaces_client.py
+++ b/tempest/services/compute/json/interfaces_client.py
@@ -14,8 +14,10 @@
# under the License.
import json
+import time
from tempest.common.rest_client import RestClient
+from tempest import exceptions
class InterfacesClientJSON(RestClient):
@@ -55,3 +57,24 @@
resp, body = self.delete('servers/%s/os-interface/%s' % (server,
port_id))
return resp, body
+
+ def wait_for_interface_status(self, server, port_id, status):
+ """Waits for a interface to reach a given status."""
+ resp, body = self.show_interface(server, port_id)
+ interface_status = body['port_state']
+ start = int(time.time())
+
+ while(interface_status != status):
+ time.sleep(self.build_interval)
+ resp, body = self.show_interface(server, port_id)
+ interface_status = body['port_state']
+
+ timed_out = int(time.time()) - start >= self.build_timeout
+
+ if interface_status != status and timed_out:
+ message = ('Interface %s failed to reach %s status within '
+ 'the required time (%s s).' %
+ (port_id, status, self.build_timeout))
+ raise exceptions.TimeoutException(message)
+
+ return resp, body
diff --git a/tempest/services/compute/json/quotas_client.py b/tempest/services/compute/json/quotas_client.py
index 37d4131..5b1e48f 100644
--- a/tempest/services/compute/json/quotas_client.py
+++ b/tempest/services/compute/json/quotas_client.py
@@ -35,6 +35,14 @@
body = json.loads(body)
return resp, body['quota_set']
+ def get_default_quota_set(self, tenant_id):
+ """List the default quota set for a tenant."""
+
+ url = 'os-quota-sets/%s/defaults' % str(tenant_id)
+ resp, body = self.get(url)
+ body = json.loads(body)
+ return resp, body['quota_set']
+
def update_quota_set(self, tenant_id, injected_file_content_bytes=None,
metadata_items=None, ram=None, floating_ips=None,
fixed_ips=None, key_pairs=None, instances=None,
diff --git a/tempest/services/compute/json/servers_client.py b/tempest/services/compute/json/servers_client.py
index bc9d9bd..3569b50 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,
@@ -384,3 +390,17 @@
def unrescue_server(self, server_id):
"""Unrescue the provided server."""
return self.action(server_id, 'unrescue', None)
+
+ def list_instance_actions(self, server_id):
+ """List the provided server action."""
+ resp, body = self.get("servers/%s/os-instance-actions" %
+ str(server_id))
+ body = json.loads(body)
+ return resp, body['instanceActions']
+
+ def get_instance_action(self, server_id, request_id):
+ """Returns the action details of the provided server."""
+ resp, body = self.get("servers/%s/os-instance-actions/%s" %
+ (str(server_id), str(request_id)))
+ body = json.loads(body)
+ return resp, body['instanceAction']
diff --git a/tempest/services/compute/xml/availability_zone_client.py b/tempest/services/compute/xml/availability_zone_client.py
new file mode 100644
index 0000000..ae93774
--- /dev/null
+++ b/tempest/services/compute/xml/availability_zone_client.py
@@ -0,0 +1,43 @@
+# 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 lxml import etree
+
+from tempest.common.rest_client import RestClientXML
+from tempest.services.compute.xml.common import xml_to_json
+
+
+class AvailabilityZoneClientXML(RestClientXML):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(AvailabilityZoneClientXML, self).__init__(config, username,
+ password, auth_url,
+ tenant_name)
+ self.service = self.config.compute.catalog_type
+
+ def _parse_array(self, node):
+ return [xml_to_json(x) for x in node]
+
+ def get_availability_zone_list(self):
+ resp, body = self.get('os-availability-zone', self.headers)
+ availability_zone = self._parse_array(etree.fromstring(body))
+ return resp, availability_zone
+
+ def get_availability_zone_list_detail(self):
+ resp, body = self.get('os-availability-zone/detail', self.headers)
+ availability_zone = self._parse_array(etree.fromstring(body))
+ return resp, availability_zone
diff --git a/tempest/services/compute/xml/fixed_ips_client.py b/tempest/services/compute/xml/fixed_ips_client.py
new file mode 100644
index 0000000..ef023f0
--- /dev/null
+++ b/tempest/services/compute/xml/fixed_ips_client.py
@@ -0,0 +1,54 @@
+# 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 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 Text
+from tempest.services.compute.xml.common import xml_to_json
+
+
+class FixedIPsClientXML(RestClientXML):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(FixedIPsClientXML, self).__init__(config, username, password,
+ auth_url, tenant_name)
+ self.service = self.config.compute.catalog_type
+
+ def _parse_fixed_ip_details(self, body):
+ body = xml_to_json(etree.fromstring(body))
+ return body
+
+ def get_fixed_ip_details(self, fixed_ip):
+ url = "os-fixed-ips/%s" % (fixed_ip)
+ resp, body = self.get(url, self.headers)
+ body = self._parse_resp(body)
+ return resp, body
+
+ def reserve_fixed_ip(self, ip, body):
+ """This reserves and unreserves fixed ips."""
+ url = "os-fixed-ips/%s/action" % (ip)
+ # NOTE(maurosr): First converts the dict body to a json string then
+ # accept any action key value here to permit tests to cover cases with
+ # invalid actions raising badrequest.
+ key, value = body.popitem()
+ xml_body = Element(key)
+ xml_body.append(Text(value))
+ resp, body = self.post(url, str(Document(xml_body)), self.headers)
+ return resp, body
diff --git a/tempest/services/compute/xml/interfaces_client.py b/tempest/services/compute/xml/interfaces_client.py
index 4a692a1..a84e0bd 100644
--- a/tempest/services/compute/xml/interfaces_client.py
+++ b/tempest/services/compute/xml/interfaces_client.py
@@ -13,9 +13,12 @@
# License for the specific language governing permissions and limitations
# under the License.
+import time
+
from lxml import etree
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
@@ -80,3 +83,23 @@
resp, body = self.delete('servers/%s/os-interface/%s' % (server,
port_id))
return resp, body
+
+ def wait_for_interface_status(self, server, port_id, status):
+ """Waits for a interface to reach a given status."""
+ resp, body = self.show_interface(server, port_id)
+ interface_status = body['port_state']
+ start = int(time.time())
+
+ while(interface_status != status):
+ time.sleep(self.build_interval)
+ resp, body = self.show_interface(server, port_id)
+ interface_status = body['port_state']
+
+ timed_out = int(time.time()) - start >= self.build_timeout
+
+ if interface_status != status and timed_out:
+ message = ('Interface %s failed to reach %s status within '
+ 'the required time (%s s).' %
+ (port_id, status, self.build_timeout))
+ raise exceptions.TimeoutException(message)
+ return resp, body
diff --git a/tempest/services/compute/xml/quotas_client.py b/tempest/services/compute/xml/quotas_client.py
index 20e04b4..8912443 100644
--- a/tempest/services/compute/xml/quotas_client.py
+++ b/tempest/services/compute/xml/quotas_client.py
@@ -55,6 +55,15 @@
body = self._format_quota(body)
return resp, body
+ def get_default_quota_set(self, tenant_id):
+ """List the default quota set for a tenant."""
+
+ url = 'os-quota-sets/%s/defaults' % str(tenant_id)
+ resp, body = self.get(url, self.headers)
+ body = xml_to_json(etree.fromstring(body))
+ body = self._format_quota(body)
+ return resp, body
+
def update_quota_set(self, tenant_id, injected_file_content_bytes=None,
metadata_items=None, ram=None, floating_ips=None,
fixed_ips=None, key_pairs=None, instances=None,
diff --git a/tempest/services/compute/xml/security_groups_client.py b/tempest/services/compute/xml/security_groups_client.py
index 4fccc29..08b381c 100644
--- a/tempest/services/compute/xml/security_groups_client.py
+++ b/tempest/services/compute/xml/security_groups_client.py
@@ -31,8 +31,8 @@
def __init__(self, config, username, password, auth_url, tenant_name=None):
super(SecurityGroupsClientXML, self).__init__(
- config, username, password,
- auth_url, tenant_name)
+ config, username, password,
+ auth_url, tenant_name)
self.service = self.config.compute.catalog_type
def _parse_array(self, node):
diff --git a/tempest/services/compute/xml/servers_client.py b/tempest/services/compute/xml/servers_client.py
index e6c2a6c..f7e8915 100644
--- a/tempest/services/compute/xml/servers_client.py
+++ b/tempest/services/compute/xml/servers_client.py
@@ -42,6 +42,13 @@
version = ip.get('version')
if version:
ip['version'] = int(version)
+ # NOTE(maurosr): just a fast way to avoid the xml version with the
+ # expanded xml namespace.
+ type_ns_prefix = ('{http://docs.openstack.org/compute/ext/extended_ips/'
+ 'api/v1.1}type')
+ if type_ns_prefix in ip:
+ ip['OS-EXT-IPS:type'] = ip[type_ns_prefix]
+ ip.pop(type_ns_prefix)
return ip
@@ -235,7 +242,8 @@
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])
@@ -519,3 +527,17 @@
resp, body = self.delete('servers/%s/os-volume_attachments/%s' %
(server_id, volume_id), headers)
return resp, body
+
+ def list_instance_actions(self, server_id):
+ """List the provided server action."""
+ resp, body = self.get("servers/%s/os-instance-actions" % server_id,
+ self.headers)
+ body = self._parse_array(etree.fromstring(body))
+ return resp, body
+
+ def get_instance_action(self, server_id, request_id):
+ """Returns the action details of the provided server."""
+ resp, body = self.get("servers/%s/os-instance-actions/%s" %
+ (server_id, request_id), self.headers)
+ body = xml_to_json(etree.fromstring(body))
+ return resp, body
diff --git a/tempest/services/identity/json/identity_client.py b/tempest/services/identity/json/identity_client.py
index 5b6eaa0..a216b55 100644
--- a/tempest/services/identity/json/identity_client.py
+++ b/tempest/services/identity/json/identity_client.py
@@ -138,6 +138,12 @@
body = json.loads(body)
return resp, body['user']
+ def get_user(self, user_id):
+ """GET a user."""
+ resp, body = self.get("users/%s" % user_id)
+ body = json.loads(body)
+ return resp, body['user']
+
def delete_user(self, user_id):
"""Delete a user."""
resp, body = self.delete("users/%s" % user_id)
@@ -152,7 +158,7 @@
def enable_disable_user(self, user_id, enabled):
"""Enables or disables a user."""
put_body = {
- 'enabled': enabled
+ 'enabled': enabled
}
put_body = json.dumps({'user': put_body})
resp, body = self.put('users/%s/enabled' % user_id,
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..cf26d0a
--- /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
+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.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/json/identity_client.py b/tempest/services/identity/v3/json/identity_client.py
new file mode 100644
index 0000000..014df1e
--- /dev/null
+++ b/tempest/services/identity/v3/json/identity_client.py
@@ -0,0 +1,162 @@
+# 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 IdentityV3ClientJSON(RestClient):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(IdentityV3ClientJSON, 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(IdentityV3ClientJSON, self).request(method, url,
+ headers=headers,
+ body=body)
+
+ def create_user(self, user_name, **kwargs):
+ """Creates a user."""
+ password = kwargs.get('password', None)
+ email = kwargs.get('email', None)
+ en = kwargs.get('enabled', True)
+ project_id = kwargs.get('project_id', None)
+ description = kwargs.get('description', None)
+ domain_id = kwargs.get('domain_id', 'default')
+ post_body = {
+ 'project_id': project_id,
+ 'description': description,
+ 'domain_id': domain_id,
+ 'email': email,
+ 'enabled': en,
+ 'name': user_name,
+ 'password': password
+ }
+ post_body = json.dumps({'user': post_body})
+ resp, body = self.post('users', post_body,
+ self.headers)
+ body = json.loads(body)
+ return resp, body['user']
+
+ def update_user(self, user_id, name, **kwargs):
+ """Updates a user."""
+ email = kwargs.get('email', None)
+ en = kwargs.get('enabled', True)
+ project_id = kwargs.get('project_id', None)
+ description = kwargs.get('description', None)
+ domain_id = kwargs.get('domain_id', 'default')
+ post_body = {
+ 'name': name,
+ 'email': email,
+ 'enabled': en,
+ 'project_id': project_id,
+ 'id': user_id,
+ 'domain_id': domain_id,
+ 'description': description
+ }
+ post_body = json.dumps({'user': post_body})
+ resp, body = self.patch('users/%s' % user_id, post_body,
+ self.headers)
+ body = json.loads(body)
+ return resp, body['user']
+
+ def list_user_projects(self, user_id):
+ """Lists the projects on which a user has roles assigned."""
+ resp, body = self.get('users/%s/projects' % user_id, self.headers)
+ body = json.loads(body)
+ return resp, body['projects']
+
+ def get_users(self):
+ """Get the list of users."""
+ resp, body = self.get("users")
+ body = json.loads(body)
+ return resp, body['users']
+
+ def get_user(self, user_id):
+ """GET a user."""
+ resp, body = self.get("users/%s" % user_id)
+ body = json.loads(body)
+ return resp, body['user']
+
+ def delete_user(self, user_id):
+ """Deletes a User."""
+ resp, body = self.delete("users/%s" % user_id)
+ return resp, body
+
+ def create_project(self, name, **kwargs):
+ """Creates a project."""
+ description = kwargs.get('description', None)
+ en = kwargs.get('enabled', True)
+ domain_id = kwargs.get('domain_id', 'default')
+ post_body = {
+ 'description': description,
+ 'domain_id': domain_id,
+ 'enabled': en,
+ 'name': name
+ }
+ post_body = json.dumps({'project': post_body})
+ resp, body = self.post('projects', post_body, self.headers)
+ body = json.loads(body)
+ return resp, body['project']
+
+ def get_project(self, project_id):
+ """GET a Project."""
+ resp, body = self.get("projects/%s" % project_id)
+ body = json.loads(body)
+ return resp, body['project']
+
+ def delete_project(self, project_id):
+ """Delete a project."""
+ resp, body = self.delete('projects/%s' % str(project_id))
+ return resp, body
+
+ def create_role(self, name):
+ """Create a Role."""
+ post_body = {
+ 'name': name
+ }
+ post_body = json.dumps({'role': post_body})
+ resp, body = self.post('roles', post_body, self.headers)
+ body = json.loads(body)
+ return resp, body['role']
+
+ def get_role(self, role_id):
+ """GET a Role."""
+ resp, body = self.get('roles/%s' % str(role_id))
+ body = json.loads(body)
+ return resp, body['role']
+
+ def delete_role(self, role_id):
+ """Delete a role."""
+ resp, body = self.delete('roles/%s' % str(role_id))
+ return resp, body
+
+ def assign_user_role(self, project_id, user_id, role_id):
+ """Add roles to a user on a project."""
+ resp, body = self.put('projects/%s/users/%s/roles/%s' %
+ (project_id, user_id, role_id), None,
+ self.headers)
+ return 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..f81fccf
--- /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.
+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.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/v3/xml/identity_client.py b/tempest/services/identity/v3/xml/identity_client.py
new file mode 100644
index 0000000..92151dd
--- /dev/null
+++ b/tempest/services/identity/v3/xml/identity_client.py
@@ -0,0 +1,187 @@
+# 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
+
+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 IdentityV3ClientXML(RestClientXML):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(IdentityV3ClientXML, self).__init__(config, username, password,
+ auth_url, tenant_name)
+ self.service = self.config.identity.catalog_type
+ self.endpoint_url = 'adminURL'
+
+ def _parse_projects(self, node):
+ array = []
+ for child in node.getchildren():
+ tag_list = child.tag.split('}', 1)
+ if tag_list[1] == "project":
+ array.append(xml_to_json(child))
+ return array
+
+ def _parse_array(self, node):
+ array = []
+ for child in node.getchildren():
+ 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."""
+ self._set_auth()
+ self.base_url = self.base_url.replace(urlparse(self.base_url).path,
+ "/v3")
+ return super(IdentityV3ClientXML, self).request(method, url,
+ headers=headers,
+ body=body)
+
+ def create_user(self, user_name, **kwargs):
+ """Creates a user."""
+ password = kwargs.get('password', None)
+ email = kwargs.get('email', None)
+ en = kwargs.get('enabled', 'true')
+ project_id = kwargs.get('project_id', None)
+ description = kwargs.get('description', None)
+ domain_id = kwargs.get('domain_id', 'default')
+ post_body = Element("user",
+ xmlns=XMLNS,
+ name=user_name,
+ password=password,
+ description=description,
+ email=email,
+ enabled=str(en).lower(),
+ project_id=project_id,
+ domain_id=domain_id)
+ resp, body = self.post('users', str(Document(post_body)),
+ self.headers)
+ body = self._parse_body(etree.fromstring(body))
+ return resp, body
+
+ def update_user(self, user_id, name, **kwargs):
+ """Updates a user."""
+ email = kwargs.get('email', None)
+ en = kwargs.get('enabled', True)
+ project_id = kwargs.get('project_id', None)
+ domain_id = kwargs.get('domain_id', 'default')
+ description = kwargs.get('description', None)
+ update_user = Element("user",
+ xmlns=XMLNS,
+ name=name,
+ email=email,
+ project_id=project_id,
+ domain_id=domain_id,
+ description=description,
+ enabled=str(en).lower())
+ resp, body = self.patch('users/%s' % user_id,
+ str(Document(update_user)),
+ self.headers)
+ body = self._parse_body(etree.fromstring(body))
+ return resp, body
+
+ def list_user_projects(self, user_id):
+ """Lists the projects on which a user has roles assigned."""
+ resp, body = self.get('users/%s/projects' % user_id, self.headers)
+ body = self._parse_projects(etree.fromstring(body))
+ return resp, body
+
+ def get_users(self):
+ """Get the list of users."""
+ resp, body = self.get("users", self.headers)
+ body = self._parse_array(etree.fromstring(body))
+ return resp, body
+
+ def get_user(self, user_id):
+ """GET a user."""
+ resp, body = self.get("users/%s" % user_id, self.headers)
+ body = self._parse_body(etree.fromstring(body))
+ return resp, body
+
+ def delete_user(self, user_id):
+ """Deletes a User."""
+ resp, body = self.delete("users/%s" % user_id, self.headers)
+ return resp, body
+
+ def create_project(self, name, **kwargs):
+ """Creates a project."""
+ description = kwargs.get('description', None)
+ en = kwargs.get('enabled', 'true')
+ domain_id = kwargs.get('domain_id', 'default')
+ post_body = Element("project",
+ xmlns=XMLNS,
+ description=description,
+ domain_id=domain_id,
+ enabled=str(en).lower(),
+ name=name)
+ resp, body = self.post('projects',
+ str(Document(post_body)),
+ self.headers)
+ body = self._parse_body(etree.fromstring(body))
+ return resp, body
+
+ def get_project(self, project_id):
+ """GET a Project."""
+ resp, body = self.get("projects/%s" % project_id, self.headers)
+ body = self._parse_body(etree.fromstring(body))
+ return resp, body
+
+ def delete_project(self, project_id):
+ """Delete a project."""
+ resp, body = self.delete('projects/%s' % str(project_id))
+ return resp, body
+
+ def create_role(self, name):
+ """Create a Role."""
+ post_body = Element("role",
+ xmlns=XMLNS,
+ name=name)
+ resp, body = self.post('roles',
+ str(Document(post_body)),
+ self.headers)
+ body = self._parse_body(etree.fromstring(body))
+ return resp, body
+
+ def get_role(self, role_id):
+ """GET a Role."""
+ resp, body = self.get('roles/%s' % str(role_id), self.headers)
+ body = self._parse_body(etree.fromstring(body))
+ return resp, body
+
+ def delete_role(self, role_id):
+ """Delete a role."""
+ resp, body = self.delete('roles/%s' % str(role_id),
+ self.headers)
+ return resp, body
+
+ def assign_user_role(self, project_id, user_id, role_id):
+ """Add roles to a user on a tenant."""
+ resp, body = self.put('projects/%s/users/%s/roles/%s' %
+ (project_id, user_id, role_id), '', self.headers)
+ return resp, body
diff --git a/tempest/services/identity/xml/identity_client.py b/tempest/services/identity/xml/identity_client.py
index 6f1b1b3..99a155a 100644
--- a/tempest/services/identity/xml/identity_client.py
+++ b/tempest/services/identity/xml/identity_client.py
@@ -172,6 +172,12 @@
body = self._parse_body(etree.fromstring(body))
return resp, body
+ def get_user(self, user_id):
+ """GET a user."""
+ resp, body = self.get("users/%s" % user_id, self.headers)
+ body = self._parse_body(etree.fromstring(body))
+ return resp, body
+
def delete_user(self, user_id):
"""Delete a user."""
resp, body = self.delete("users/%s" % user_id, self.headers)
diff --git a/tempest/services/image/v1/json/image_client.py b/tempest/services/image/v1/json/image_client.py
index a3b3e96..a8fab7f 100644
--- a/tempest/services/image/v1/json/image_client.py
+++ b/tempest/services/image/v1/json/image_client.py
@@ -139,7 +139,8 @@
headers = {}
- for option in ['is_public', 'location', 'properties', 'copy_from']:
+ for option in ['is_public', 'location', 'properties',
+ 'copy_from', 'min_ram']:
if option in kwargs:
params[option] = kwargs.get(option)
diff --git a/tempest/services/object_storage/object_client.py b/tempest/services/object_storage/object_client.py
index 9626b6b..69df472 100644
--- a/tempest/services/object_storage/object_client.py
+++ b/tempest/services/object_storage/object_client.py
@@ -15,10 +15,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from hashlib import sha1
+import hashlib
import hmac
import httplib2
-from urlparse import urlparse
+import urlparse
from tempest.common.rest_client import RestClient
from tempest import exceptions
@@ -127,10 +127,10 @@
self._set_auth()
method = 'GET'
- path = "%s/%s/%s" % (urlparse(self.base_url).path, container,
+ path = "%s/%s/%s" % (urlparse.urlparse(self.base_url).path, container,
object_name)
hmac_body = '%s\n%s\n%s' % (method, expires, path)
- sig = hmac.new(key, hmac_body, sha1).hexdigest()
+ sig = hmac.new(key, hmac_body, hashlib.sha1).hexdigest()
url = "%s/%s?temp_url_sig=%s&temp_url_expires=%s" % (container,
object_name,
diff --git a/tempest/services/volume/json/snapshots_client.py b/tempest/services/volume/json/snapshots_client.py
index 9545d0b..db614f1 100644
--- a/tempest/services/volume/json/snapshots_client.py
+++ b/tempest/services/volume/json/snapshots_client.py
@@ -84,7 +84,7 @@
# state in a "normal" lifecycle
if (status == 'error'):
raise exceptions.SnapshotBuildErrorException(
- snapshot_id=snapshot_id)
+ snapshot_id=snapshot_id)
return status
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/snapshots_client.py b/tempest/services/volume/xml/snapshots_client.py
index 89ea89f..2209fc7 100644
--- a/tempest/services/volume/xml/snapshots_client.py
+++ b/tempest/services/volume/xml/snapshots_client.py
@@ -93,7 +93,7 @@
# state in a "normal" lifecycle
if (status == 'error'):
raise exceptions.SnapshotBuildErrorException(
- snapshot_id=snapshot_id)
+ snapshot_id=snapshot_id)
return status
diff --git a/tempest/services/volume/xml/volumes_client.py b/tempest/services/volume/xml/volumes_client.py
index 6fd1397..8eda26b 100644
--- a/tempest/services/volume/xml/volumes_client.py
+++ b/tempest/services/volume/xml/volumes_client.py
@@ -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..4db9827 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -56,6 +56,11 @@
#NOTE(afazekas): inspection workaround
BaseTestCase.config = config.TempestConfig()
+ @classmethod
+ def setUpClass(cls):
+ if hasattr(super(BaseTestCase, cls), 'setUpClass'):
+ super(BaseTestCase, cls).setUpClass()
+
class TestCase(BaseTestCase):
"""Base test case class for all Tempest tests
@@ -115,42 +120,91 @@
return False
-class DefaultClientTest(TestCase):
+def status_timeout(testcase, 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':
+ testcase.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):
+ testcase.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 +215,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/testboto.py b/tempest/testboto.py
index cee8843..9e652cb 100644
--- a/tempest/testboto.py
+++ b/tempest/testboto.py
@@ -15,7 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from contextlib import closing
+import contextlib
import logging
import os
import re
@@ -282,9 +282,10 @@
@classmethod
def get_lfunction_gone(cls, obj):
- """ If the object is instance of a well know type returns back with
+ """If the object is instance of a well know type returns back with
with the correspoding function otherwise it assumes the obj itself
- is the function"""
+ is the function.
+ """
ec = cls.ec2_error_code
if isinstance(obj, ec2.instance.Instance):
colusure_matcher = ec.client.InvalidInstanceID.NotFound
@@ -412,7 +413,8 @@
"""Destroys the bucket and its content, just for teardown."""
exc_num = 0
try:
- with closing(boto.connect_s3(**connection_data)) as conn:
+ with contextlib.closing(
+ boto.connect_s3(**connection_data)) as conn:
if isinstance(bucket, basestring):
bucket = conn.lookup(bucket)
assert isinstance(bucket, s3.bucket.Bucket)
@@ -442,7 +444,7 @@
return "_GONE"
except exception.EC2ResponseError as exc:
if cls.ec2_error_code.\
- client.InvalidInstanceID.NotFound.match(exc):
+ client.InvalidInstanceID.NotFound.match(exc):
return "_GONE"
#NOTE(afazekas): incorrect code,
# but the resource must be destoreyd
diff --git a/tempest/tests/boto/test_ec2_instance_run.py b/tempest/tests/boto/test_ec2_instance_run.py
index 3293dea..08dc330 100644
--- a/tempest/tests/boto/test_ec2_instance_run.py
+++ b/tempest/tests/boto/test_ec2_instance_run.py
@@ -73,8 +73,8 @@
"location": cls.bucket_name + "/" + ari_manifest}}
for image in cls.images.itervalues():
image["image_id"] = cls.ec2_client.register_image(
- name=image["name"],
- image_location=image["location"])
+ name=image["name"],
+ image_location=image["location"])
cls.addResourceCleanUp(cls.ec2_client.deregister_image,
image["image_id"])
@@ -151,13 +151,15 @@
group_desc)
self.addResourceCleanUp(self.destroy_security_group_wait,
security_group)
- self.assertTrue(self.ec2_client.authorize_security_group(
+ self.assertTrue(
+ self.ec2_client.authorize_security_group(
sec_group_name,
ip_protocol="icmp",
cidr_ip="0.0.0.0/0",
from_port=-1,
to_port=-1))
- self.assertTrue(self.ec2_client.authorize_security_group(
+ self.assertTrue(
+ self.ec2_client.authorize_security_group(
sec_group_name,
ip_protocol="tcp",
cidr_ip="0.0.0.0/0",
diff --git a/tempest/tests/boto/test_s3_ec2_images.py b/tempest/tests/boto/test_s3_ec2_images.py
index 4068aba..f77743e 100644
--- a/tempest/tests/boto/test_s3_ec2_images.py
+++ b/tempest/tests/boto/test_s3_ec2_images.py
@@ -63,12 +63,12 @@
"location": self.bucket_name + "/" + self.ami_manifest,
"type": "ami"}
image["image_id"] = self.images_client.register_image(
- name=image["name"],
- image_location=image["location"])
+ name=image["name"],
+ image_location=image["location"])
#Note(afazekas): delete_snapshot=True might trigger boto lib? bug
image["cleanUp"] = self.addResourceCleanUp(
- self.images_client.deregister_image,
- image["image_id"])
+ self.images_client.deregister_image,
+ image["image_id"])
self.assertEqual(image["image_id"][0:3], image["type"])
retrieved_image = self.images_client.get_image(image["image_id"])
self.assertTrue(retrieved_image.name == image["name"])
@@ -90,11 +90,11 @@
"location": self.bucket_name + "/" + self.ari_manifest,
"type": "aki"}
image["image_id"] = self.images_client.register_image(
- name=image["name"],
- image_location=image["location"])
+ name=image["name"],
+ image_location=image["location"])
image["cleanUp"] = self.addResourceCleanUp(
- self.images_client.deregister_image,
- image["image_id"])
+ self.images_client.deregister_image,
+ image["image_id"])
self.assertEqual(image["image_id"][0:3], image["type"])
retrieved_image = self.images_client.get_image(image["image_id"])
self.assertTrue(retrieved_image.name == image["name"])
@@ -115,11 +115,11 @@
"location": "/" + self.bucket_name + "/" + self.ari_manifest,
"type": "ari"}
image["image_id"] = self.images_client.register_image(
- name=image["name"],
- image_location=image["location"])
+ name=image["name"],
+ image_location=image["location"])
image["cleanUp"] = self.addResourceCleanUp(
- self.images_client.deregister_image,
- image["image_id"])
+ self.images_client.deregister_image,
+ image["image_id"])
self.assertEqual(image["image_id"][0:3], image["type"])
retrieved_image = self.images_client.get_image(image["image_id"])
self.assertIn(retrieved_image.state, self.valid_image_state)
diff --git a/tempest/tests/boto/test_s3_objects.py b/tempest/tests/boto/test_s3_objects.py
index dcb7c86..9d4d79c 100644
--- a/tempest/tests/boto/test_s3_objects.py
+++ b/tempest/tests/boto/test_s3_objects.py
@@ -15,9 +15,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from contextlib import closing
+import contextlib
-from boto.s3.key import Key
+import boto.s3.key
from tempest import clients
from tempest.common.utils.data_utils import rand_name
@@ -46,7 +46,7 @@
bucket_name)
self.assertTrue(bucket.name == bucket_name)
- with closing(Key(bucket)) as key:
+ with contextlib.closing(boto.s3.key.Key(bucket)) as key:
key.key = object_name
key.set_contents_from_string(content)
readback = key.get_contents_as_string()
diff --git a/tempest/tests/boto/utils/s3.py b/tempest/tests/boto/utils/s3.py
index 4c3229b..ea9869b 100644
--- a/tempest/tests/boto/utils/s3.py
+++ b/tempest/tests/boto/utils/s3.py
@@ -15,24 +15,24 @@
# License for the specific language governing permissions and limitations
# under the License.
-from contextlib import closing
+import contextlib
import logging
import os
import re
import boto
-from boto.s3.key import Key
+import boto.s3.key
LOG = logging.getLogger(__name__)
def s3_upload_dir(bucket, path, prefix="", connection_data=None):
if isinstance(bucket, basestring):
- with closing(boto.connect_s3(**connection_data)) as conn:
+ with contextlib.closing(boto.connect_s3(**connection_data)) as conn:
bucket = conn.lookup(bucket)
for root, dirs, files in os.walk(path):
for fil in files:
- with closing(Key(bucket)) as key:
+ with contextlib.closing(boto.s3.key.Key(bucket)) as key:
source = root + os.sep + fil
target = re.sub("^" + re.escape(path) + "?/", prefix, source)
if os.sep != '/':
diff --git a/tempest/tests/boto/utils/wait.py b/tempest/tests/boto/utils/wait.py
index c2d4ea3..6cd17a9 100644
--- a/tempest/tests/boto/utils/wait.py
+++ b/tempest/tests/boto/utils/wait.py
@@ -19,7 +19,7 @@
import re
import time
-from boto.exception import BotoServerError
+import boto.exception
from testtools import TestCase
import tempest.config
@@ -87,7 +87,7 @@
"""Stops waiting on success."""
start_time = time.time()
if exc_matcher is not None:
- exc_class = BotoServerError
+ exc_class = boto.exception.BotoServerError
if exc_class is None:
exc_class = BaseException
diff --git a/tempest/tests/compute/admin/test_aggregates.py b/tempest/tests/compute/admin/test_aggregates.py
new file mode 100644
index 0000000..06acc41
--- /dev/null
+++ b/tempest/tests/compute/admin/test_aggregates.py
@@ -0,0 +1,256 @@
+# 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_name
+from tempest import exceptions
+from tempest.test import attr
+from tempest.tests.compute import base
+
+
+class AggregatesAdminTestJSON(base.BaseComputeAdminTest):
+
+ """
+ Tests Aggregates API that require admin privileges
+ """
+
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(AggregatesAdminTestJSON, cls).setUpClass()
+ cls.client = cls.os_adm.aggregates_client
+ cls.user_client = cls.os.aggregates_client
+ cls.aggregate_name_prefix = 'test_aggregate_'
+ cls.az_name_prefix = 'test_az_'
+
+ resp, hosts_all = cls.os_adm.hosts_client.list_hosts()
+ hosts = map(lambda x: x['host_name'],
+ filter(lambda y: y['service'] == 'compute', hosts_all))
+ cls.host = hosts[0]
+
+ @attr(type='positive')
+ def test_aggregate_create_delete(self):
+ # Create and delete an aggregate.
+ aggregate_name = rand_name(self.aggregate_name_prefix)
+ resp, aggregate = self.client.create_aggregate(aggregate_name)
+ self.assertEquals(200, resp.status)
+ self.assertEquals(aggregate_name, aggregate['name'])
+ self.assertEquals(None, aggregate['availability_zone'])
+
+ resp, _ = self.client.delete_aggregate(aggregate['id'])
+ self.assertEquals(200, resp.status)
+ self.client.wait_for_resource_deletion(aggregate['id'])
+
+ @attr(type='positive')
+ def test_aggregate_create_delete_with_az(self):
+ # Create and delete an aggregate.
+ aggregate_name = rand_name(self.aggregate_name_prefix)
+ az_name = rand_name(self.az_name_prefix)
+ resp, aggregate = self.client.create_aggregate(aggregate_name, az_name)
+ self.assertEquals(200, resp.status)
+ self.assertEquals(aggregate_name, aggregate['name'])
+ self.assertEquals(az_name, aggregate['availability_zone'])
+
+ resp, _ = self.client.delete_aggregate(aggregate['id'])
+ self.assertEquals(200, resp.status)
+ self.client.wait_for_resource_deletion(aggregate['id'])
+
+ @attr(type='positive')
+ def test_aggregate_create_verify_entry_in_list(self):
+ # Create an aggregate and ensure it is listed.
+ aggregate_name = rand_name(self.aggregate_name_prefix)
+ resp, aggregate = self.client.create_aggregate(aggregate_name)
+ self.addCleanup(self.client.delete_aggregate, aggregate['id'])
+
+ resp, aggregates = self.client.list_aggregates()
+ self.assertEquals(200, resp.status)
+ self.assertIn((aggregate['id'], aggregate['availability_zone']),
+ map(lambda x: (x['id'], x['availability_zone']),
+ aggregates))
+
+ @attr(type='positive')
+ def test_aggregate_create_get_details(self):
+ # Create an aggregate and ensure its details are returned.
+ aggregate_name = rand_name(self.aggregate_name_prefix)
+ resp, aggregate = self.client.create_aggregate(aggregate_name)
+ self.addCleanup(self.client.delete_aggregate, aggregate['id'])
+
+ resp, body = self.client.get_aggregate(aggregate['id'])
+ self.assertEquals(200, resp.status)
+ self.assertEquals(aggregate['name'], body['name'])
+ self.assertEquals(aggregate['availability_zone'],
+ body['availability_zone'])
+
+ @attr(type='negative')
+ def test_aggregate_create_as_user(self):
+ # Regular user is not allowed to create an aggregate.
+ aggregate_name = rand_name(self.aggregate_name_prefix)
+ self.assertRaises(exceptions.Unauthorized,
+ self.user_client.create_aggregate,
+ aggregate_name)
+
+ @attr(type='negative')
+ def test_aggregate_delete_as_user(self):
+ # Regular user is not allowed to delete an aggregate.
+ aggregate_name = rand_name(self.aggregate_name_prefix)
+ resp, aggregate = self.client.create_aggregate(aggregate_name)
+ self.addCleanup(self.client.delete_aggregate, aggregate['id'])
+
+ self.assertRaises(exceptions.Unauthorized,
+ self.user_client.delete_aggregate,
+ aggregate['id'])
+
+ @attr(type='negative')
+ def test_aggregate_list_as_user(self):
+ # Regular user is not allowed to list aggregates.
+ self.assertRaises(exceptions.Unauthorized,
+ self.user_client.list_aggregates)
+
+ @attr(type='negative')
+ def test_aggregate_get_details_as_user(self):
+ # Regular user is not allowed to get aggregate details.
+ aggregate_name = rand_name(self.aggregate_name_prefix)
+ resp, aggregate = self.client.create_aggregate(aggregate_name)
+ self.addCleanup(self.client.delete_aggregate, aggregate['id'])
+
+ self.assertRaises(exceptions.Unauthorized,
+ self.user_client.get_aggregate,
+ aggregate['id'])
+
+ @attr(type='negative')
+ def test_aggregate_delete_with_invalid_id(self):
+ # Delete an aggregate with invalid id should raise exceptions.
+ self.assertRaises(exceptions.NotFound,
+ self.client.delete_aggregate, -1)
+
+ @attr(type='negative')
+ def test_aggregate_get_details_with_invalid_id(self):
+ # Get aggregate details with invalid id should raise exceptions.
+ self.assertRaises(exceptions.NotFound,
+ self.client.get_aggregate, -1)
+
+ @attr(type='positive')
+ def test_aggregate_add_remove_host(self):
+ # Add an host to the given aggregate and remove.
+ aggregate_name = rand_name(self.aggregate_name_prefix)
+ resp, aggregate = self.client.create_aggregate(aggregate_name)
+ self.addCleanup(self.client.delete_aggregate, aggregate['id'])
+
+ resp, body = self.client.add_host(aggregate['id'], self.host)
+ self.assertEquals(200, resp.status)
+ self.assertEquals(aggregate_name, body['name'])
+ self.assertEquals(aggregate['availability_zone'],
+ body['availability_zone'])
+ self.assertIn(self.host, body['hosts'])
+
+ resp, body = self.client.remove_host(aggregate['id'], self.host)
+ self.assertEquals(200, resp.status)
+ self.assertEquals(aggregate_name, body['name'])
+ self.assertEquals(aggregate['availability_zone'],
+ body['availability_zone'])
+ self.assertNotIn(self.host, body['hosts'])
+
+ @attr(type='positive')
+ def test_aggregate_add_host_list(self):
+ # Add an host to the given aggregate and list.
+ aggregate_name = rand_name(self.aggregate_name_prefix)
+ resp, aggregate = self.client.create_aggregate(aggregate_name)
+ self.addCleanup(self.client.delete_aggregate, aggregate['id'])
+ self.client.add_host(aggregate['id'], self.host)
+ self.addCleanup(self.client.remove_host, aggregate['id'], self.host)
+
+ resp, aggregates = self.client.list_aggregates()
+ aggs = filter(lambda x: x['id'] == aggregate['id'], aggregates)
+ self.assertEquals(1, len(aggs))
+ agg = aggs[0]
+ self.assertEquals(aggregate_name, agg['name'])
+ self.assertEquals(None, agg['availability_zone'])
+ self.assertIn(self.host, agg['hosts'])
+
+ @attr(type='positive')
+ def test_aggregate_add_host_get_details(self):
+ # Add an host to the given aggregate and get details.
+ aggregate_name = rand_name(self.aggregate_name_prefix)
+ resp, aggregate = self.client.create_aggregate(aggregate_name)
+ self.addCleanup(self.client.delete_aggregate, aggregate['id'])
+ self.client.add_host(aggregate['id'], self.host)
+ self.addCleanup(self.client.remove_host, aggregate['id'], self.host)
+
+ resp, body = self.client.get_aggregate(aggregate['id'])
+ self.assertEquals(aggregate_name, body['name'])
+ self.assertEquals(None, body['availability_zone'])
+ self.assertIn(self.host, body['hosts'])
+
+ @attr(type='positive')
+ def test_aggregate_add_host_create_server_with_az(self):
+ # Add an host to the given aggregate and create a server.
+ aggregate_name = rand_name(self.aggregate_name_prefix)
+ az_name = rand_name(self.az_name_prefix)
+ resp, aggregate = self.client.create_aggregate(aggregate_name, az_name)
+ self.addCleanup(self.client.delete_aggregate, aggregate['id'])
+ self.client.add_host(aggregate['id'], self.host)
+ self.addCleanup(self.client.remove_host, aggregate['id'], self.host)
+
+ server_name = rand_name('test_server_')
+ servers_client = self.servers_client
+ admin_servers_client = self.os_adm.servers_client
+ resp, server = self.create_server(name=server_name,
+ availability_zone=az_name)
+ servers_client.wait_for_server_status(server['id'], 'ACTIVE')
+ resp, body = admin_servers_client.get_server(server['id'])
+ self.assertEqual(self.host, body['OS-EXT-SRV-ATTR:host'])
+
+ @attr(type='negative')
+ def test_aggregate_add_non_exist_host(self):
+ # Adding a non-exist host to an aggregate should raise exceptions.
+ resp, hosts_all = self.os_adm.hosts_client.list_hosts()
+ hosts = map(lambda x: x['host_name'], hosts_all)
+ while True:
+ non_exist_host = rand_name('nonexist_host_')
+ if non_exist_host not in hosts:
+ break
+
+ aggregate_name = rand_name(self.aggregate_name_prefix)
+ resp, aggregate = self.client.create_aggregate(aggregate_name)
+ self.addCleanup(self.client.delete_aggregate, aggregate['id'])
+
+ self.assertRaises(exceptions.NotFound, self.client.add_host,
+ aggregate['id'], non_exist_host)
+
+ @attr(type='negative')
+ def test_aggregate_add_host_as_user(self):
+ # Regular user is not allowed to add a host to an aggregate.
+ aggregate_name = rand_name(self.aggregate_name_prefix)
+ resp, aggregate = self.client.create_aggregate(aggregate_name)
+ self.addCleanup(self.client.delete_aggregate, aggregate['id'])
+
+ self.assertRaises(exceptions.Unauthorized,
+ self.user_client.add_host,
+ aggregate['id'], self.host)
+
+ @attr(type='negative')
+ def test_aggregate_remove_host_as_user(self):
+ # Regular user is not allowed to remove a host from an aggregate.
+ aggregate_name = rand_name(self.aggregate_name_prefix)
+ resp, aggregate = self.client.create_aggregate(aggregate_name)
+ self.addCleanup(self.client.delete_aggregate, aggregate['id'])
+ self.client.add_host(aggregate['id'], self.host)
+ self.addCleanup(self.client.remove_host, aggregate['id'], self.host)
+
+ self.assertRaises(exceptions.Unauthorized,
+ self.user_client.remove_host,
+ aggregate['id'], self.host)
diff --git a/tempest/tests/compute/admin/test_availability_zone.py b/tempest/tests/compute/admin/test_availability_zone.py
new file mode 100644
index 0000000..98ad49c
--- /dev/null
+++ b/tempest/tests/compute/admin/test_availability_zone.py
@@ -0,0 +1,69 @@
+# 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 import exceptions
+from tempest.test import attr
+from tempest.tests.compute import base
+
+
+class AvailabilityZoneAdminTestJSON(base.BaseComputeAdminTest):
+
+ """
+ Tests Availability Zone API List that require admin privileges
+ """
+
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(AvailabilityZoneAdminTestJSON, cls).setUpClass()
+ cls.client = cls.os_adm.availability_zone_client
+ cls.non_adm_client = cls.availability_zone_client
+
+ @attr('positive')
+ def test_get_availability_zone_list(self):
+ # List of availability zone
+ resp, availability_zone = self.client.get_availability_zone_list()
+ self.assertEqual(200, resp.status)
+ self.assertTrue(len(availability_zone) > 0)
+
+ @attr('positive')
+ def test_get_availability_zone_list_detail(self):
+ # List of availability zones and available services
+ resp, availability_zone = \
+ self.client.get_availability_zone_list_detail()
+ self.assertEqual(200, resp.status)
+ self.assertTrue(len(availability_zone) > 0)
+
+ @attr('positive')
+ def test_get_availability_zone_list_with_non_admin_user(self):
+ # List of availability zone with non admin user
+ resp, availability_zone = \
+ self.non_adm_client.get_availability_zone_list()
+ self.assertEqual(200, resp.status)
+ self.assertTrue(len(availability_zone) > 0)
+
+ @attr('negative')
+ def test_get_availability_zone_list_detail_with_non_admin_user(self):
+ # List of availability zones and available services with non admin user
+ self.assertRaises(
+ exceptions.Unauthorized,
+ self.non_adm_client.get_availability_zone_list_detail)
+
+
+class AvailabilityZoneAdminTestXML(AvailabilityZoneAdminTestJSON):
+ _interface = 'xml'
diff --git a/tempest/tests/compute/admin/test_fixed_ips.py b/tempest/tests/compute/admin/test_fixed_ips.py
new file mode 100644
index 0000000..d8b1359
--- /dev/null
+++ b/tempest/tests/compute/admin/test_fixed_ips.py
@@ -0,0 +1,108 @@
+# 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 import exceptions
+from tempest.test import attr
+from tempest.tests.compute import base
+
+
+class FixedIPsBase(base.BaseComputeAdminTest):
+ _interface = 'json'
+ ip = None
+
+ @classmethod
+ def setUpClass(cls):
+ super(FixedIPsBase, cls).setUpClass()
+ # NOTE(maurosr): The idea here is: the server creation is just an
+ # auxiliary element to the ip details or reservation, there was no way
+ # (at least none in my mind) to get an valid and existing ip except
+ # by creating a server and using its ip. So the intention is to create
+ # fewer server possible (one) and use it to both: json and xml tests.
+ # This decreased time to run both tests, in my test machine, from 53
+ # secs to 29 (agains 23 secs when running only json tests)
+ if cls.ip is None:
+ cls.client = cls.os_adm.fixed_ips_client
+ cls.non_admin_client = cls.fixed_ips_client
+ resp, server = cls.create_server(wait_until='ACTIVE')
+ resp, server = cls.servers_client.get_server(server['id'])
+ for ip_set in server['addresses']:
+ for ip in server['addresses'][ip_set]:
+ if ip['OS-EXT-IPS:type'] == 'fixed':
+ cls.ip = ip['addr']
+ break
+ if cls.ip:
+ break
+
+
+class FixedIPsTestJson(FixedIPsBase):
+ _interface = 'json'
+
+ @attr(type='positive')
+ def test_list_fixed_ip_details(self):
+ resp, fixed_ip = self.client.get_fixed_ip_details(self.ip)
+ self.assertEqual(fixed_ip['address'], self.ip)
+
+ @attr(type='negative')
+ def test_list_fixed_ip_details_with_non_admin_user(self):
+ self.assertRaises(exceptions.Unauthorized,
+ self.non_admin_client.get_fixed_ip_details, self.ip)
+
+ @attr(type='positive')
+ def test_set_reserve(self):
+ body = {"reserve": "None"}
+ resp, body = self.client.reserve_fixed_ip(self.ip, body)
+ self.assertEqual(resp.status, 202)
+
+ @attr(type='positive')
+ def test_set_unreserve(self):
+ body = {"unreserve": "None"}
+ resp, body = self.client.reserve_fixed_ip(self.ip, body)
+ self.assertEqual(resp.status, 202)
+
+ @attr(type='negative')
+ def test_set_reserve_with_non_admin_user(self):
+ body = {"reserve": "None"}
+ self.assertRaises(exceptions.Unauthorized,
+ self.non_admin_client.reserve_fixed_ip,
+ self.ip, body)
+
+ @attr(type='negative')
+ def test_set_unreserve_with_non_admin_user(self):
+ body = {"unreserve": "None"}
+ self.assertRaises(exceptions.Unauthorized,
+ self.non_admin_client.reserve_fixed_ip,
+ self.ip, body)
+
+ @attr(type='negative')
+ def test_set_reserve_with_invalid_ip(self):
+ # NOTE(maurosr): since this exercises the same code snippet, we do it
+ # only for reserve action
+ body = {"reserve": "None"}
+ self.assertRaises(exceptions.NotFound,
+ self.client.reserve_fixed_ip,
+ "my.invalid.ip", body)
+
+ @attr(type='negative')
+ def test_fixed_ip_with_invalid_action(self):
+ body = {"invalid_action": "None"}
+ self.assertRaises(exceptions.BadRequest,
+ self.client.reserve_fixed_ip,
+ self.ip, body)
+
+
+class FixedIPsTestXml(FixedIPsTestJson):
+ _interface = 'xml'
diff --git a/tempest/tests/compute/admin/test_flavors.py b/tempest/tests/compute/admin/test_flavors.py
index 32b06f8..7957009 100644
--- a/tempest/tests/compute/admin/test_flavors.py
+++ b/tempest/tests/compute/admin/test_flavors.py
@@ -48,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
@@ -55,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):
@@ -100,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):
@@ -138,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)
@@ -164,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):
@@ -213,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.
@@ -239,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):
diff --git a/tempest/tests/compute/admin/test_flavors_extra_specs.py b/tempest/tests/compute/admin/test_flavors_extra_specs.py
index 01bff98..31a2511 100644
--- a/tempest/tests/compute/admin/test_flavors_extra_specs.py
+++ b/tempest/tests/compute/admin/test_flavors_extra_specs.py
@@ -92,9 +92,9 @@
def test_flavor_non_admin_get_keys(self):
specs = {"key1": "value1", "key2": "value2"}
set_resp, set_body = self.client.set_flavor_extra_spec(
- self.flavor['id'], specs)
+ self.flavor['id'], specs)
resp, body = self.flavors_client.get_flavor_extra_spec(
- self.flavor['id'])
+ self.flavor['id'])
self.assertEqual(resp.status, 200)
for key in specs:
self.assertEquals(body[key], specs[key])
@@ -103,7 +103,7 @@
def test_flavor_non_admin_unset_keys(self):
specs = {"key1": "value1", "key2": "value2"}
set_resp, set_body = self.client.set_flavor_extra_spec(
- self.flavor['id'], specs)
+ self.flavor['id'], specs)
self.assertRaises(exceptions.Unauthorized,
self.flavors_client.unset_flavor_extra_spec,
diff --git a/tempest/tests/compute/admin/test_quotas.py b/tempest/tests/compute/admin/test_quotas.py
index 5a9b6f9..8f520f9 100644
--- a/tempest/tests/compute/admin/test_quotas.py
+++ b/tempest/tests/compute/admin/test_quotas.py
@@ -63,12 +63,10 @@
# 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
- try:
- resp, quota_set = self.client.get_quota_set(self.demo_tenant_id)
- self.assertEqual(200, resp.status)
- self.assertEqual(expected_quota_set, quota_set)
- except Exception:
- self.fail("Admin could not get the default quota set for a tenant")
+ resp, quota_set = self.client.get_default_quota_set(
+ self.demo_tenant_id)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(expected_quota_set, quota_set)
def test_update_all_quota_resources_for_tenant(self):
# Admin can update all the resource quota limits for a tenant
diff --git a/tempest/tests/compute/base.py b/tempest/tests/compute/base.py
index 87aa889..221cfb6 100644
--- a/tempest/tests/compute/base.py
+++ b/tempest/tests/compute/base.py
@@ -60,6 +60,8 @@
cls.volumes_extensions_client = os.volumes_extensions_client
cls.volumes_client = os.volumes_client
cls.interfaces_client = os.interfaces_client
+ cls.fixed_ips_client = os.fixed_ips_client
+ cls.availability_zone_client = os.availability_zone_client
cls.build_interval = cls.config.compute.build_interval
cls.build_timeout = cls.config.compute.build_timeout
cls.ssh_user = cls.config.compute.ssh_user
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 2b21710..d800fb5 100644
--- a/tempest/tests/compute/floating_ips/test_floating_ips_actions.py
+++ b/tempest/tests/compute/floating_ips/test_floating_ips_actions.py
@@ -102,14 +102,14 @@
# to a specific server should be successful
#Association of floating IP to fixed IP address
- resp, body =\
- self.client.associate_floating_ip_to_server(self.floating_ip,
- self.server_id)
+ resp, body = self.client.associate_floating_ip_to_server(
+ self.floating_ip,
+ self.server_id)
self.assertEqual(202, resp.status)
#Disassociation of floating IP that was associated in this method
- resp, body = \
- self.client.disassociate_floating_ip_from_server(self.floating_ip,
- self.server_id)
+ resp, body = self.client.disassociate_floating_ip_from_server(
+ self.floating_ip,
+ self.server_id)
self.assertEqual(202, resp.status)
@attr(type='negative')
@@ -150,13 +150,13 @@
self.new_server_id = body['id']
#Associating floating IP for the first time
- resp, _ = \
- self.client.associate_floating_ip_to_server(self.floating_ip,
- self.server_id)
+ resp, _ = self.client.associate_floating_ip_to_server(
+ self.floating_ip,
+ self.server_id)
#Associating floating IP for the second time
- resp, body = \
- self.client.associate_floating_ip_to_server(self.floating_ip,
- self.new_server_id)
+ resp, body = self.client.associate_floating_ip_to_server(
+ self.floating_ip,
+ self.new_server_id)
self.addCleanup(self.servers_client.delete_server, self.new_server_id)
if (resp['status'] is not None):
diff --git a/tempest/tests/compute/images/test_images_oneserver.py b/tempest/tests/compute/images/test_images_oneserver.py
index ca3dbb5..dfc16f4 100644
--- a/tempest/tests/compute/images/test_images_oneserver.py
+++ b/tempest/tests/compute/images/test_images_oneserver.py
@@ -72,7 +72,6 @@
snapshot_name)
@attr(type='negative')
- @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-')
@@ -81,12 +80,11 @@
self.server['id'], snapshot_name, meta)
@attr(type='negative')
- @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-')
meta = {'a' * 260: 'b' * 260}
- self.assertRaises(exceptions.OverLimit, self.client.create_image,
+ self.assertRaises(exceptions.BadRequest, self.client.create_image,
self.server['id'], snapshot_name, meta)
@attr(type='negative')
@@ -130,6 +128,11 @@
self.assertEqual(original_image['minRam'], image['minRam'])
self.assertEqual(original_image['minDisk'], image['minDisk'])
+ # Verify the image was deleted correctly
+ resp, body = self.client.delete_image(image_id)
+ self.assertEqual('204', resp['status'])
+ self.assertRaises(exceptions.NotFound, self.client.get_image, image_id)
+
@attr(type='negative')
@testtools.skipUnless(compute.MULTI_USER,
'Need multiple users for this test.')
diff --git a/tempest/tests/compute/limits/test_absolute_limits.py b/tempest/tests/compute/limits/test_absolute_limits.py
index 2b31680..6933fd7 100644
--- a/tempest/tests/compute/limits/test_absolute_limits.py
+++ b/tempest/tests/compute/limits/test_absolute_limits.py
@@ -15,6 +15,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+from tempest import exceptions
+from tempest.test import attr
from tempest.tests.compute import base
@@ -25,6 +27,7 @@
def setUpClass(cls):
super(AbsoluteLimitsTestJSON, cls).setUpClass()
cls.client = cls.limits_client
+ cls.server_client = cls.servers_client
def test_absLimits_get(self):
# To check if all limits are present in the response
@@ -45,6 +48,24 @@
"Failed to find element %s in absolute limits list"
% ', '.join(ele for ele in missing_elements))
+ @attr(type='negative')
+ def test_max_image_meta_exceed_limit(self):
+ #We should not create vm with image meta over maxImageMeta limit
+ # Get max limit value
+ max_meta = self.client.get_specific_absolute_limit('maxImageMeta')
+
+ #Create server should fail, since we are passing > metadata Limit!
+ max_meta_data = int(max_meta) + 1
+
+ meta_data = {}
+ for xx in range(max_meta_data):
+ meta_data[str(xx)] = str(xx)
+
+ self.assertRaises(exceptions.OverLimit,
+ self.server_client.create_server,
+ name='test', meta=meta_data, flavor_ref='84',
+ image_ref='9e6a2e3b-1601-42a5-985f-c3a2f93a5ec3')
+
class AbsoluteLimitsTestXML(AbsoluteLimitsTestJSON):
_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_attach_interfaces.py b/tempest/tests/compute/servers/test_attach_interfaces.py
index 47c0575..5e447c4 100644
--- a/tempest/tests/compute/servers/test_attach_interfaces.py
+++ b/tempest/tests/compute/servers/test_attach_interfaces.py
@@ -25,7 +25,7 @@
@classmethod
def setUpClass(cls):
super(AttachInterfacesTestJSON, cls).setUpClass()
- os = clients.Manager()
+ os = clients.Manager(interface=cls._interface)
if not os.config.network.quantum_available:
raise cls.skipException("Quantum is required")
cls.client = os.interfaces_client
@@ -41,13 +41,18 @@
self.assertEqual(iface['fixed_ips'][0]['ip_address'], fixed_ip)
def _create_server_get_interfaces(self):
- server = self.create_server()
+ resp, server = self.create_server()
self.os.servers_client.wait_for_server_status(server['id'], 'ACTIVE')
resp, ifs = self.client.list_interfaces(server['id'])
+ resp, body = self.client.wait_for_interface_status(
+ server['id'], ifs[0]['port_id'], 'ACTIVE')
+ ifs[0]['port_state'] = body['port_state']
return server, ifs
def _test_create_interface(self, server):
resp, iface = self.client.create_interface(server['id'])
+ resp, iface = self.client.wait_for_interface_status(
+ server['id'], iface['port_id'], 'ACTIVE')
self._check_interface(iface)
return iface
@@ -55,6 +60,8 @@
network_id = ifs[0]['net_id']
resp, iface = self.client.create_interface(server['id'],
network_id=network_id)
+ resp, iface = self.client.wait_for_interface_status(
+ server['id'], iface['port_id'], 'ACTIVE')
self._check_interface(iface, network_id=network_id)
return iface
diff --git a/tempest/tests/compute/servers/test_instance_actions.py b/tempest/tests/compute/servers/test_instance_actions.py
new file mode 100644
index 0000000..e7e31e8
--- /dev/null
+++ b/tempest/tests/compute/servers/test_instance_actions.py
@@ -0,0 +1,69 @@
+# 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 import exceptions
+from tempest.test import attr
+from tempest.tests.compute import base
+
+
+class InstanceActionsTestJSON(base.BaseComputeTest):
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(InstanceActionsTestJSON, cls).setUpClass()
+ cls.client = cls.servers_client
+ resp, server = cls.create_server(wait_until='ACTIVE')
+ cls.request_id = resp['x-compute-request-id']
+ cls.server_id = server['id']
+
+ @attr(type='positive')
+ def test_list_instance_actions(self):
+ # List actions of the provided server
+ resp, body = self.client.reboot(self.server_id, 'HARD')
+ self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+
+ resp, body = self.client.list_instance_actions(self.server_id)
+ self.assertEqual(200, resp.status)
+ self.assertTrue(len(body) == 2)
+ self.assertTrue(any([i for i in body if i['action'] == 'create']))
+ self.assertTrue(any([i for i in body if i['action'] == 'reboot']))
+
+ @attr(type='positive')
+ def test_get_instance_action(self):
+ # Get the action details of the provided server
+ resp, body = self.client.get_instance_action(self.server_id,
+ self.request_id)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(self.server_id, body['instance_uuid'])
+ self.assertEqual('create', body['action'])
+
+ @attr(type='negative')
+ def test_list_instance_actions_invalid_server(self):
+ # List actions of the invalid server id
+ self.assertRaises(exceptions.NotFound,
+ self.client.list_instance_actions, 'server-999')
+
+ @attr(type='negative')
+ def test_get_instance_action_invalid_request(self):
+ # Get the action details of the provided server with invalid request
+ self.assertRaises(exceptions.NotFound, self.client.get_instance_action,
+ self.server_id, '999')
+
+
+class InstanceActionsTestXML(InstanceActionsTestJSON):
+ _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 4d2b99f..852288e 100644
--- a/tempest/tests/compute/servers/test_list_server_filters.py
+++ b/tempest/tests/compute/servers/test_list_server_filters.py
@@ -22,6 +22,8 @@
from tempest.tests.compute import base
from tempest.tests import utils
+import testtools
+
class ListServerFiltersTestJSON(base.BaseComputeTest):
_interface = 'json'
@@ -180,6 +182,56 @@
self.assertEqual(['ACTIVE'] * 3, [x['status'] for x in servers])
@attr(type='positive')
+ def test_list_servers_filtered_by_name_wildcard(self):
+ # List all servers that contains 'server' in name
+ params = {'name': 'server'}
+ resp, body = self.client.list_servers(params)
+ servers = body['servers']
+
+ self.assertIn(self.s1_name, map(lambda x: x['name'], servers))
+ self.assertIn(self.s2_name, map(lambda x: x['name'], servers))
+ self.assertIn(self.s3_name, map(lambda x: x['name'], servers))
+
+ # Let's take random part of name and try to search it
+ part_name = self.s1_name[6:-1]
+
+ params = {'name': part_name}
+ resp, body = self.client.list_servers(params)
+ servers = body['servers']
+
+ self.assertIn(self.s1_name, map(lambda x: x['name'], servers))
+ self.assertNotIn(self.s2_name, map(lambda x: x['name'], servers))
+ self.assertNotIn(self.s3_name, map(lambda x: x['name'], servers))
+
+ @testtools.skip('Until Bug #1170718 is resolved.')
+ @attr(type='positive', bug='lp1170718')
+ def test_list_servers_filtered_by_ip(self):
+ # Filter servers by ip
+ # Here should be listed 1 server
+ ip = self.s1['addresses']['private'][0]['addr']
+ params = {'ip': ip}
+ resp, body = self.client.list_servers(params)
+ servers = body['servers']
+
+ self.assertIn(self.s1_name, map(lambda x: x['name'], servers))
+ self.assertNotIn(self.s2_name, map(lambda x: x['name'], servers))
+ self.assertNotIn(self.s3_name, map(lambda x: x['name'], servers))
+
+ @attr(type='positive')
+ def test_list_servers_filtered_by_ip_regex(self):
+ # Filter servers by regex ip
+ # List all servers filtered by part of ip address.
+ # Here should be listed all servers
+ ip = self.s1['addresses']['private'][0]['addr'][0:-3]
+ params = {'ip': ip}
+ resp, body = self.client.list_servers(params)
+ servers = body['servers']
+
+ self.assertIn(self.s1_name, map(lambda x: x['name'], servers))
+ self.assertIn(self.s2_name, map(lambda x: x['name'], servers))
+ self.assertIn(self.s3_name, map(lambda x: x['name'], servers))
+
+ @attr(type='positive')
def test_list_servers_detailed_limit_results(self):
# Verify only the expected number of detailed results are returned
params = {'limit': 1}
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..47a38b1
--- /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.extend(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_advanced_ops.py b/tempest/tests/compute/servers/test_server_advanced_ops.py
index f949f2e..8be9c54 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:
@@ -57,7 +57,7 @@
flavor_id = self.config.compute.flavor_ref
base_image_id = self.config.compute.image_ref
self.instance = self.compute_client.servers.create(
- i_name, base_image_id, flavor_id)
+ i_name, base_image_id, flavor_id)
try:
self.assertEqual(self.instance.name, i_name)
self.set_resource('instance', self.instance)
@@ -66,16 +66,18 @@
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, 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, 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, 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..e4e246a 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:
@@ -78,7 +78,7 @@
for ruleset in rulesets:
try:
self.compute_client.security_group_rules.create(
- self.secgroup.id, **ruleset)
+ self.secgroup.id, **ruleset)
except Exception:
self.fail("Failed to create rule in security group.")
@@ -90,7 +90,7 @@
'key_name': self.get_resource('keypair').id
}
self.instance = self.compute_client.servers.create(
- i_name, base_image_id, flavor_id, **create_kwargs)
+ i_name, base_image_id, flavor_id, **create_kwargs)
try:
self.assertEqual(self.instance.name, i_name)
self.set_resource('instance', self.instance)
@@ -101,7 +101,8 @@
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, self.compute_client.servers, instance_id, 'ACTIVE')
def pause_server(self):
instance = self.get_resource('instance')
@@ -109,7 +110,8 @@
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, self.compute_client.servers, instance_id, 'PAUSED')
def unpause_server(self):
instance = self.get_resource('instance')
@@ -117,7 +119,8 @@
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, self.compute_client.servers, instance_id, 'ACTIVE')
def suspend_server(self):
instance = self.get_resource('instance')
@@ -125,7 +128,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, self.compute_client.servers,
instance_id, 'SUSPENDED')
def resume_server(self):
@@ -134,7 +137,8 @@
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, 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 29c9944..04c5b27 100644
--- a/tempest/tests/compute/servers/test_server_rescue.py
+++ b/tempest/tests/compute/servers/test_server_rescue.py
@@ -15,8 +15,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import testtools
-
from tempest.common.utils.data_utils import rand_name
import tempest.config
from tempest import exceptions
@@ -43,25 +41,25 @@
cls.sg_name = rand_name('sg')
cls.sg_desc = rand_name('sg-desc')
resp, cls.sg = \
- cls.security_groups_client.create_security_group(cls.sg_name,
- cls.sg_desc)
+ cls.security_groups_client.create_security_group(cls.sg_name,
+ cls.sg_desc)
cls.sg_id = cls.sg['id']
# Create a volume and wait for it to become ready for attach
resp, cls.volume_to_attach = \
- 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.create_volume(1,
+ display_name=
+ 'test_attach')
+ 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.create_volume(1,
+ display_name=
+ 'test_detach')
+ 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,
@@ -93,8 +91,8 @@
client = cls.volumes_extensions_client
client.delete_volume(str(cls.volume_to_attach['id']).strip())
client.delete_volume(str(cls.volume_to_detach['id']).strip())
- resp, cls.sg = \
- cls.security_groups_client.delete_security_group(cls.sg_id)
+ resp, cls.sg = cls.security_groups_client.delete_security_group(
+ cls.sg_id)
def tearDown(self):
super(ServerRescueTestJSON, self).tearDown()
@@ -154,12 +152,15 @@
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 from the server expecting failure
@@ -174,12 +175,12 @@
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
- resp, body =\
- client.associate_floating_ip_to_server(self.floating_ip,
- self.server_id)
+ resp, body = client.associate_floating_ip_to_server(self.floating_ip,
+ self.server_id)
self.assertEqual(202, resp.status)
#Disassociation of floating IP that was associated in this method
@@ -188,14 +189,13 @@
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")
def test_rescued_vm_add_remove_security_group(self):
+ # Rescue the server
+ self.servers_client.rescue_server(
+ self.server_id, self.password)
+ self.servers_client.wait_for_server_status(self.server_id, 'RESCUE')
+
#Add Security group
resp, body = self.servers_client.add_security_group(self.server_id,
self.sg_name)
@@ -203,7 +203,7 @@
#Delete Security group
resp, body = self.servers_client.remove_security_group(self.server_id,
- self.sg_id)
+ self.sg_name)
self.assertEqual(202, resp.status)
# Unrescue the server
diff --git a/tempest/tests/compute/servers/test_servers_whitebox.py b/tempest/tests/compute/servers/test_servers_whitebox.py
index 9b75cd5..6b192dd 100644
--- a/tempest/tests/compute/servers/test_servers_whitebox.py
+++ b/tempest/tests/compute/servers/test_servers_whitebox.py
@@ -55,9 +55,9 @@
def test_create_server_vcpu_quota_full(self):
# Disallow server creation when tenant's vcpu quota is full
quotas = self.meta.tables['quotas']
- stmt = quotas.select().where(
- quotas.c.project_id == self.tenant_id).where(
- quotas.c.resource == 'cores')
+ stmt = (quotas.select().
+ where(quotas.c.project_id == self.tenant_id).
+ where(quotas.c.resource == 'cores'))
result = self.connection.execute(stmt).first()
# Set vcpu quota for tenant if not already set
@@ -87,9 +87,9 @@
def test_create_server_memory_quota_full(self):
# Disallow server creation when tenant's memory quota is full
quotas = self.meta.tables['quotas']
- stmt = quotas.select().where(
- quotas.c.project_id == self.tenant_id).where(
- quotas.c.resource == 'ram')
+ stmt = (quotas.select().
+ where(quotas.c.project_id == self.tenant_id).
+ where(quotas.c.resource == 'ram'))
result = self.connection.execute(stmt).first()
# Set memory quota for tenant if not already set
diff --git a/tempest/tests/compute/test_authorization.py b/tempest/tests/compute/test_authorization.py
index 91cf39f..6edc946 100644
--- a/tempest/tests/compute/test_authorization.py
+++ b/tempest/tests/compute/test_authorization.py
@@ -234,8 +234,7 @@
# Reset the base_url...
self.alt_security_client.base_url = self.saved_base_url
if resp['status'] is not None:
- #TODO(afazekas): body not defined
- self.alt_security_client.delete_security_group(body['id'])
+ self.alt_security_client.delete_security_group(resp['id'])
self.fail("Create Security Group request should not happen if"
"the tenant id does not match the current user")
@@ -274,8 +273,7 @@
# Reset the base_url...
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
+ self.alt_security_client.delete_security_group_rule(resp['id'])
self.fail("Create security group rule request should not "
"happen if the tenant id does not match the"
" current user")
diff --git a/tempest/tests/compute/test_quotas.py b/tempest/tests/compute/test_quotas.py
index a84d041..92e5a70 100644
--- a/tempest/tests/compute/test_quotas.py
+++ b/tempest/tests/compute/test_quotas.py
@@ -30,23 +30,31 @@
resp, tenants = cls.admin_client.list_tenants()
cls.tenant_id = [tnt['id'] for tnt in tenants if tnt['name'] ==
cls.client.tenant_name][0]
+ cls.default_quota_set = {'injected_file_content_bytes': 10240,
+ 'metadata_items': 128, 'injected_files': 5,
+ 'ram': 51200, 'floating_ips': 10,
+ 'fixed_ips': -1, 'key_pairs': 100,
+ 'injected_file_path_bytes': 255,
+ 'instances': 10, 'security_group_rules': 20,
+ 'cores': 20, 'security_groups': 10}
+
+ @attr(type='smoke')
+ def test_get_quotas(self):
+ # User can get the quota set for it's tenant
+ expected_quota_set = self.default_quota_set.copy()
+ expected_quota_set['id'] = self.tenant_id
+ resp, quota_set = self.client.get_quota_set(self.tenant_id)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(expected_quota_set, quota_set)
@attr(type='smoke')
def test_get_default_quotas(self):
# 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': -1, 'key_pairs': 100,
- 'injected_file_path_bytes': 255, 'instances': 10,
- 'security_group_rules': 20, 'cores': 20,
- 'id': self.tenant_id, 'security_groups': 10}
- try:
- resp, quota_set = self.client.get_quota_set(self.tenant_id)
- self.assertEqual(200, resp.status)
- self.assertEqual(expected_quota_set, quota_set)
- except Exception:
- self.fail("Quota set for tenant did not have default limits")
+ expected_quota_set = self.default_quota_set.copy()
+ expected_quota_set['id'] = self.tenant_id
+ resp, quota_set = self.client.get_default_quota_set(self.tenant_id)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(expected_quota_set, quota_set)
class QuotasTestXML(QuotasTestJSON):
diff --git a/tempest/tests/identity/admin/test_users.py b/tempest/tests/identity/admin/test_users.py
index 0573b21..f9772ac 100644
--- a/tempest/tests/identity/admin/test_users.py
+++ b/tempest/tests/identity/admin/test_users.py
@@ -309,8 +309,8 @@
for i in body:
fetched_user_ids.append(i['id'])
#verifying the user Id in the list
- missing_users =\
- [user for user in user_ids if user not in fetched_user_ids]
+ missing_users = [missing_user for missing_user in user_ids
+ if missing_user not in fetched_user_ids]
self.assertEqual(0, len(missing_users),
"Failed to find user %s in fetched list" %
', '.join(m_user for m_user in missing_users))
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..3ad9b28
--- /dev/null
+++ b/tempest/tests/identity/admin/v3/test_endpoints.py
@@ -0,0 +1,148 @@
+# 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
+ 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/admin/v3/test_users.py b/tempest/tests/identity/admin/v3/test_users.py
new file mode 100644
index 0000000..7118241
--- /dev/null
+++ b/tempest/tests/identity/admin/v3/test_users.py
@@ -0,0 +1,121 @@
+# 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 UsersV3TestJSON(base.BaseIdentityAdminTest):
+ _interface = 'json'
+
+ @attr('smoke')
+ def test_user_update(self):
+ # Test case to check if updating of user attributes is successful.
+ #Creating first user
+ u_name = rand_name('user-')
+ u_desc = u_name + 'description'
+ u_email = u_name + '@testmail.tm'
+ u_password = rand_name('pass-')
+ resp, user = self.v3_client.create_user(
+ u_name, description=u_desc, password=u_password,
+ email=u_email, enabled=False)
+ # Delete the User at the end of this method
+ self.addCleanup(self.v3_client.delete_user, user['id'])
+ #Creating second project for updation
+ resp, project = self.v3_client.create_project(
+ rand_name('project-'), description=rand_name('project-desc-'))
+ # Delete the Project at the end of this method
+ self.addCleanup(self.v3_client.delete_project, project['id'])
+ #Updating user details with new values
+ u_name2 = rand_name('user2-')
+ u_email2 = u_name2 + '@testmail.tm'
+ u_description2 = u_name2 + ' description'
+ resp, update_user = self.v3_client.update_user(
+ user['id'], name=u_name2, description=u_description2,
+ project_id=project['id'],
+ email=u_email2, enabled=False)
+ #Assert response body of update user.
+ self.assertEqual(200, resp.status)
+ self.assertEqual(u_name2, update_user['name'])
+ self.assertEqual(u_description2, update_user['description'])
+ self.assertEqual(project['id'],
+ update_user['project_id'])
+ self.assertEqual(u_email2, update_user['email'])
+ self.assertEqual('false', str(update_user['enabled']).lower())
+ #GET by id after updation
+ resp, new_user_get = self.v3_client.get_user(user['id'])
+ #Assert response body of GET after updation
+ self.assertEqual(u_name2, new_user_get['name'])
+ self.assertEqual(u_description2, new_user_get['description'])
+ self.assertEqual(project['id'],
+ new_user_get['project_id'])
+ self.assertEqual(u_email2, new_user_get['email'])
+ self.assertEqual('false', str(new_user_get['enabled']).lower())
+
+ @attr('smoke')
+ def test_list_user_projects(self):
+ #List the projects that a user has access upon
+ assigned_project_ids = list()
+ fetched_project_ids = list()
+ _, u_project = self.v3_client.create_project(
+ rand_name('project-'), description=rand_name('project-desc-'))
+ #Create a user.
+ u_name = rand_name('user-')
+ u_desc = u_name + 'description'
+ u_email = u_name + '@testmail.tm'
+ u_password = rand_name('pass-')
+ _, user_body = self.v3_client.create_user(
+ u_name, description=u_desc, password=u_password,
+ email=u_email, enabled=False, project_id=u_project['id'])
+ # Delete the User at the end of this method
+ self.addCleanup(self.v3_client.delete_user, user_body['id'])
+ # Creating Role
+ _, role_body = self.v3_client.create_role(rand_name('role-'))
+ # Delete the Role at the end of this method
+ self.addCleanup(self.v3_client.delete_role, role_body['id'])
+
+ _, user = self.v3_client.get_user(user_body['id'])
+ _, role = self.v3_client.get_role(role_body['id'])
+ for i in range(2):
+ # Creating project so as to assign role
+ _, project_body = self.v3_client.create_project(
+ rand_name('project-'), description=rand_name('project-desc-'))
+ _, project = self.v3_client.get_project(project_body['id'])
+ # Delete the Project at the end of this method
+ self.addCleanup(self.v3_client.delete_project, project_body['id'])
+ #Assigning roles to user on project
+ self.v3_client.assign_user_role(project['id'],
+ user['id'],
+ role['id'])
+ assigned_project_ids.append(project['id'])
+ resp, body = self.v3_client.list_user_projects(user['id'])
+ self.assertEqual(200, resp.status)
+ for i in body:
+ fetched_project_ids.append(i['id'])
+ #verifying the project ids in list
+ missing_projects =\
+ [p for p in assigned_project_ids
+ if p not in fetched_project_ids]
+ self.assertEqual(0, len(missing_projects),
+ "Failed to find project %s in fetched list" %
+ ', '.join(m_project for m_project
+ in missing_projects))
+
+
+class UsersV3TestXML(UsersV3TestJSON):
+ _interface = 'xml'
diff --git a/tempest/tests/identity/base.py b/tempest/tests/identity/base.py
index 168b2ff..718b74f 100644
--- a/tempest/tests/identity/base.py
+++ b/tempest/tests/identity/base.py
@@ -28,6 +28,8 @@
os = clients.AdminManager(interface=cls._interface)
cls.client = os.identity_client
cls.token_client = os.token_client
+ cls.endpoints_client = os.endpoints_client
+ cls.v3_client = os.identity_v3_client
if not cls.client.has_admin_extensions():
raise cls.skipException("Admin extensions disabled")
diff --git a/tempest/tests/image/v1/test_images.py b/tempest/tests/image/v1/test_images.py
index 1b6fa10..c01aeaf 100644
--- a/tempest/tests/image/v1/test_images.py
+++ b/tempest/tests/image/v1/test_images.py
@@ -89,9 +89,9 @@
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='')
+ container_name,
+ metadata=cont_headers,
+ metadata_prefix='')
self.assertEqual(resp['status'], '204')
data = "TESTIMAGE"
@@ -117,6 +117,26 @@
self.assertEqual(resp['status'], '200')
self.assertEqual(body, data)
+ @attr(type='image')
+ def test_register_image_with_min_ram(self):
+ # Register an image with min ram
+ properties = {'prop1': 'val1'}
+ resp, body = self.create_image(name='New_image_with_min_ram',
+ container_format='bare',
+ disk_format='raw',
+ is_public=True,
+ min_ram=40,
+ properties=properties)
+ self.assertTrue('id' in body)
+ image_id = body.get('id')
+ self.created_images.append(image_id)
+ self.assertEqual('New_image_with_min_ram', body.get('name'))
+ self.assertTrue(body.get('is_public'))
+ self.assertEqual('queued', body.get('status'))
+ self.assertEqual(40, body.get('min_ram'))
+ for key, val in properties.items():
+ self.assertEqual(val, body.get('properties')[key])
+
class ListImagesTest(base.BaseV1ImageTest):
@@ -250,7 +270,7 @@
@attr(type='image')
def test_index_name(self):
resp, images_list = self.client.image_list_detail(
- name='New Remote Image dup')
+ name='New Remote Image dup')
self.assertEqual(resp['status'], '200')
result_set = set(map(lambda x: x['id'], images_list))
for image in images_list:
diff --git a/tempest/tests/network/common.py b/tempest/tests/network/common.py
index 0bb806f..6246f54 100644
--- a/tempest/tests/network/common.py
+++ b/tempest/tests/network/common.py
@@ -21,7 +21,6 @@
from quantumclient.common import exceptions as exc
from tempest.common.utils.data_utils import rand_name
-from tempest import smoke
from tempest import test
@@ -103,7 +102,7 @@
self.client.delete_port(self.id)
-class TestNetworkSmokeCommon(smoke.DefaultClientSmokeTest):
+class TestNetworkSmokeCommon(test.DefaultClientSmokeTest):
"""
Base class for network smoke tests
"""
@@ -126,7 +125,6 @@
@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,
@@ -247,10 +245,7 @@
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
+ result = self.network_client.create_port(body=body)
self.assertIsNotNone(result, 'Unable to allocate port')
port = DeletablePort(client=self.network_client,
**result['port'])
@@ -274,7 +269,7 @@
self.set_resource(name, server)
except AttributeError:
self.fail("Server not successfully created.")
- self.status_timeout(client.servers, server.id, 'ACTIVE')
+ test.status_timeout(self, client.servers, server.id, 'ACTIVE')
# The instance retrieved on creation is missing network
# details, necessitating retrieval after it becomes active to
# ensure correct details.
diff --git a/tempest/tests/network/test_network_basic_ops.py b/tempest/tests/network/test_network_basic_ops.py
index a38a5c0..92ca65f 100644
--- a/tempest/tests/network/test_network_basic_ops.py
+++ b/tempest/tests/network/test_network_basic_ops.py
@@ -17,11 +17,11 @@
# under the License.
from tempest.common.utils.data_utils import rand_name
-from tempest.tests.network.common import DeletableRouter
-from tempest.tests.network.common import TestNetworkSmokeCommon
+from tempest.test import attr
+import tempest.tests.network.common as net_common
-class TestNetworkBasicOps(TestNetworkSmokeCommon):
+class TestNetworkBasicOps(net_common.TestNetworkSmokeCommon):
"""
This smoke test suite assumes that Nova has been configured to
@@ -124,7 +124,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)
@@ -143,20 +143,23 @@
),
)
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
+ @attr(type='smoke')
def test_001_create_keypairs(self):
self.keypairs[self.tenant_id] = self._create_keypair(
self.compute_client)
+ @attr(type='smoke')
def test_002_create_security_groups(self):
self.security_groups[self.tenant_id] = self._create_security_group(
self.compute_client)
+ @attr(type='smoke')
def test_003_create_networks(self):
network = self._create_network(self.tenant_id)
router = self._get_router(self.tenant_id)
@@ -166,6 +169,7 @@
self.subnets.append(subnet)
self.routers.append(router)
+ @attr(type='smoke')
def test_004_check_networks(self):
#Checks that we see the newly created network/subnet/router via
#checking the result of list_[networks,routers,subnets]
@@ -189,6 +193,7 @@
self.assertIn(myrouter.name, seen_router_names)
self.assertIn(myrouter.id, seen_router_ids)
+ @attr(type='smoke')
def test_005_create_servers(self):
if not (self.keypairs or self.security_groups or self.networks):
raise self.skipTest('Necessary resources have not been defined')
@@ -201,6 +206,7 @@
name, keypair_name, security_groups)
self.servers.append(server)
+ @attr(type='smoke')
def test_006_check_tenant_network_connectivity(self):
if not self.config.network.tenant_networks_reachable:
msg = 'Tenant networks not configured to be reachable.'
@@ -214,6 +220,7 @@
"Timed out waiting for %s's ip to become "
"reachable" % server.name)
+ @attr(type='smoke')
def test_007_assign_floating_ips(self):
public_network_id = self.config.network.public_network_id
if not public_network_id:
@@ -225,6 +232,7 @@
self.floating_ips.setdefault(server, [])
self.floating_ips[server].append(floating_ip)
+ @attr(type='smoke')
def test_008_check_public_network_connectivity(self):
if not self.floating_ips:
raise self.skipTest('No floating ips have been allocated.')
diff --git a/tempest/tests/object_storage/test_container_services.py b/tempest/tests/object_storage/test_container_services.py
index 2c5b1ff..223744c 100644
--- a/tempest/tests/object_storage/test_container_services.py
+++ b/tempest/tests/object_storage/test_container_services.py
@@ -124,7 +124,7 @@
# List container metadata
resp, _ = self.container_client.list_container_metadata(
- container_name)
+ container_name)
self.assertEqual(resp['status'], '204')
self.assertIn('x-container-meta-name', resp)
self.assertIn('x-container-meta-description', resp)
@@ -132,10 +132,9 @@
self.assertEqual(resp['x-container-meta-description'], 'Travel')
# Delete container metadata
- resp, _ = \
- self.container_client.delete_container_metadata(
- container_name,
- metadata=metadata.keys())
+ resp, _ = self.container_client.delete_container_metadata(
+ container_name,
+ metadata=metadata.keys())
self.assertEqual(resp['status'], '204')
resp, _ = self.container_client.list_container_metadata(container_name)
diff --git a/tempest/tests/object_storage/test_object_expiry.py b/tempest/tests/object_storage/test_object_expiry.py
index c12ec3d..e1b1dbd 100644
--- a/tempest/tests/object_storage/test_object_expiry.py
+++ b/tempest/tests/object_storage/test_object_expiry.py
@@ -21,7 +21,7 @@
from tempest.test import attr
from tempest.tests.object_storage import base
import testtools
-from time import sleep
+import time
class ObjectExpiryTest(base.BaseObjectTest):
@@ -88,7 +88,7 @@
# Check data
self.assertEqual(body, data)
# Sleep for over 5 seconds, so that object is expired
- sleep(5)
+ time.sleep(5)
# Verification of raised exception after object gets expired
self.assertRaises(exceptions.NotFound, self.object_client.get_object,
self.container_name, object_name)
diff --git a/tempest/tests/object_storage/test_object_services.py b/tempest/tests/object_storage/test_object_services.py
index 1edce92..4fcc617 100644
--- a/tempest/tests/object_storage/test_object_services.py
+++ b/tempest/tests/object_storage/test_object_services.py
@@ -21,7 +21,7 @@
from tempest.test import attr
from tempest.tests.object_storage import base
import testtools
-from time import time
+import time
class ObjectTest(base.BaseObjectTest):
@@ -617,7 +617,7 @@
self.object_client.create_object(self.container_name,
object_name, data)
- expires = int(time() + 10)
+ expires = int(time.time() + 10)
#Trying to GET object using temp URL with in expiry time
_, body = self.object_client.get_object_using_temp_url(
diff --git a/tempest/tests/object_storage/test_object_version.py b/tempest/tests/object_storage/test_object_version.py
index bc1c045..80cfc27 100644
--- a/tempest/tests/object_storage/test_object_version.py
+++ b/tempest/tests/object_storage/test_object_version.py
@@ -59,7 +59,7 @@
# Create a containers
vers_container_name = rand_name(name='TestVersionContainer')
resp, body = self.container_client.create_container(
- vers_container_name)
+ vers_container_name)
self.containers.append(vers_container_name)
self.assertIn(resp['status'], ('202', '201'))
self.assertContainer(vers_container_name, '0', '0',
@@ -68,9 +68,9 @@
base_container_name = rand_name(name='TestBaseContainer')
headers = {'X-versions-Location': vers_container_name}
resp, body = self.container_client.create_container(
- base_container_name,
- metadata=headers,
- metadata_prefix='')
+ base_container_name,
+ metadata=headers,
+ metadata_prefix='')
self.containers.append(base_container_name)
self.assertIn(resp['status'], ('202', '201'))
self.assertContainer(base_container_name, '0', '0',
diff --git a/tempest/tests/volume/admin/test_volume_types.py b/tempest/tests/volume/admin/test_volume_types.py
index 38ac74a..13efca7 100644
--- a/tempest/tests/volume/admin/test_volume_types.py
+++ b/tempest/tests/volume/admin/test_volume_types.py
@@ -55,15 +55,15 @@
extra_specs = {"storage_protocol": "iSCSI",
"vendor_name": "Open Source"}
body = {}
- resp, body = self.client.create_volume_type(vol_type_name,
- extra_specs=
- extra_specs)
+ resp, body = self.client.create_volume_type(
+ vol_type_name,
+ extra_specs=extra_specs)
self.assertEqual(200, resp.status)
self.assertTrue('id' in body)
self.assertTrue('name' in body)
- resp, volume = self.volumes_client.\
- create_volume(size=1, display_name=vol_name,
- volume_type=vol_type_name)
+ resp, volume = self.volumes_client.create_volume(
+ size=1, display_name=vol_name,
+ volume_type=vol_type_name)
self.assertEqual(200, resp.status)
self.assertTrue('id' in volume)
self.assertTrue('display_name' in volume)
@@ -74,8 +74,7 @@
"Field volume id is empty or not found.")
self.volumes_client.wait_for_volume_status(volume['id'],
'available')
- resp, fetched_volume = self.volumes_client.\
- get_volume(volume['id'])
+ resp, fetched_volume = self.volumes_client.get_volume(volume['id'])
self.assertEqual(200, resp.status)
self.assertEqual(vol_name, fetched_volume['display_name'],
'The fetched Volume is different '
@@ -104,8 +103,9 @@
name = rand_name("volume-type-")
extra_specs = {"storage_protocol": "iSCSI",
"vendor_name": "Open Source"}
- resp, body = self.client.\
- create_volume_type(name, extra_specs=extra_specs)
+ resp, body = self.client.create_volume_type(
+ name,
+ extra_specs=extra_specs)
self.assertEqual(200, resp.status)
self.assertTrue('id' in body)
self.assertTrue('name' in body)
@@ -114,8 +114,7 @@
"to the requested name")
self.assertTrue(body['id'] is not None,
"Field volume_type id is empty or not found.")
- resp, fetched_volume_type = self.client.\
- delete_volume_type(body['id'])
+ resp, _ = self.client.delete_volume_type(body['id'])
self.assertEqual(202, resp.status)
except Exception:
self.fail("Could not create a volume_type")
@@ -127,8 +126,9 @@
name = rand_name("volume-type-")
extra_specs = {"storage_protocol": "iSCSI",
"vendor_name": "Open Source"}
- resp, body = self.client.\
- create_volume_type(name, extra_specs=extra_specs)
+ resp, body = self.client.create_volume_type(
+ name,
+ extra_specs=extra_specs)
self.assertEqual(200, resp.status)
self.assertTrue('id' in body)
self.assertTrue('name' in body)
diff --git a/tempest/tests/volume/admin/test_volume_types_extra_specs.py b/tempest/tests/volume/admin/test_volume_types_extra_specs.py
index 31e2879..c8cf8d9 100644
--- a/tempest/tests/volume/admin/test_volume_types_extra_specs.py
+++ b/tempest/tests/volume/admin/test_volume_types_extra_specs.py
@@ -37,13 +37,13 @@
# List Volume types extra specs.
try:
extra_specs = {"spec1": "val1"}
- resp, body = self.client.\
- create_volume_type_extra_specs(self.volume_type['id'], extra_specs)
+ resp, body = self.client.create_volume_type_extra_specs(
+ self.volume_type['id'], extra_specs)
self.assertEqual(200, resp.status)
self.assertEqual(extra_specs, body,
"Volume type extra spec incorrectly created")
- resp, body = self.client.\
- list_volume_types_extra_specs(self.volume_type['id'])
+ resp, body = self.client.list_volume_types_extra_specs(
+ self.volume_type['id'])
self.assertEqual(200, resp.status)
self.assertTrue(type(body), dict)
self.assertTrue('spec1' in body, "Incorrect volume type extra"
@@ -55,17 +55,17 @@
# Update volume type extra specs
try:
extra_specs = {"spec2": "val1"}
- resp, body = self.client.\
- create_volume_type_extra_specs(self.volume_type['id'], extra_specs)
+ resp, body = self.client.create_volume_type_extra_specs(
+ self.volume_type['id'], extra_specs)
self.assertEqual(200, resp.status)
self.assertEqual(extra_specs, body,
"Volume type extra spec incorrectly created")
extra_spec = {"spec2": "val2"}
- resp, body = self.client.\
- update_volume_type_extra_specs(self.volume_type['id'],
- extra_spec.keys()[0],
- extra_spec)
+ resp, body = self.client.update_volume_type_extra_specs(
+ self.volume_type['id'],
+ extra_spec.keys()[0],
+ extra_spec)
self.assertEqual(200, resp.status)
self.assertTrue('spec2' in body,
"Volume type extra spec incorrectly updated")
@@ -78,22 +78,23 @@
# Create/Get/Delete volume type extra spec.
try:
extra_specs = {"spec3": "val1"}
- resp, body = self.client.\
- create_volume_type_extra_specs(self.volume_type['id'], extra_specs)
+ resp, body = self.client.create_volume_type_extra_specs(
+ self.volume_type['id'],
+ extra_specs)
self.assertEqual(200, resp.status)
self.assertEqual(extra_specs, body,
"Volume type extra spec incorrectly created")
- resp, fetched_vol_type_extra_spec = self.client.\
- get_volume_type_extra_specs(self.volume_type['id'],
- extra_specs.keys()[0])
+ resp, _ = self.client.get_volume_type_extra_specs(
+ self.volume_type['id'],
+ extra_specs.keys()[0])
self.assertEqual(200, resp.status)
self.assertEqual(extra_specs, body,
"Volume type extra spec incorrectly fetched")
- resp, _ = self.client.\
- delete_volume_type_extra_specs(self.volume_type['id'],
- extra_specs.keys()[0])
+ resp, _ = self.client.delete_volume_type_extra_specs(
+ self.volume_type['id'],
+ extra_specs.keys()[0])
self.assertEqual(202, resp.status)
except Exception:
self.fail("Could not create a volume_type extra spec")
diff --git a/tempest/tests/volume/base.py b/tempest/tests/volume/base.py
index 00e8668..978ec53 100644
--- a/tempest/tests/volume/base.py
+++ b/tempest/tests/volume/base.py
@@ -149,13 +149,13 @@
def clear_volumes(cls):
for volume in cls.volumes:
try:
- cls.volume_client.delete_volume(volume['id'])
+ cls.volumes_client.delete_volume(volume['id'])
except Exception:
pass
for volume in cls.volumes:
try:
- cls.servers_client.wait_for_resource_deletion(volume['id'])
+ cls.volumes_client.wait_for_resource_deletion(volume['id'])
except Exception:
pass
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/tempest/whitebox.py b/tempest/whitebox.py
index bfcc373..cf9fff0 100644
--- a/tempest/whitebox.py
+++ b/tempest/whitebox.py
@@ -111,7 +111,7 @@
image_id = cls.image_ref
resp, server = cls.servers_client.create_server(
- server_name, image_id, flavor)
+ server_name, image_id, flavor)
cls.servers_client.wait_for_server_status(server['id'], 'ACTIVE')
cls.servers.append(server)
return server
diff --git a/tools/check_source.sh b/tools/check_source.sh
deleted file mode 100755
index 089ad70..0000000
--- a/tools/check_source.sh
+++ /dev/null
@@ -1,24 +0,0 @@
-#!/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/find_stack_traces.py b/tools/find_stack_traces.py
index e6c1990..3129484 100755
--- a/tools/find_stack_traces.py
+++ b/tools/find_stack_traces.py
@@ -22,6 +22,46 @@
import sys
import urllib2
+import pprint
+pp = pprint.PrettyPrinter()
+
+NOVA_TIMESTAMP = r"\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d"
+
+NOVA_REGEX = r"(?P<timestamp>%s) (?P<pid>\d+ )?(?P<level>(ERROR|TRACE)) " \
+ "(?P<module>[\w\.]+) (?P<msg>.*)" % (NOVA_TIMESTAMP)
+
+
+class StackTrace(object):
+ timestamp = None
+ pid = None
+ level = ""
+ module = ""
+ msg = ""
+
+ def __init__(self, timestamp=None, pid=None, level="", module="",
+ msg=""):
+ self.timestamp = timestamp
+ self.pid = pid
+ self.level = level
+ self.module = module
+ self.msg = msg
+
+ def append(self, msg):
+ self.msg = self.msg + msg
+
+ def is_same(self, data):
+ return (data['timestamp'] == self.timestamp and
+ data['level'] == self.level)
+
+ def not_none(self):
+ return self.timestamp is not None
+
+ def __str__(self):
+ buff = "<%s %s %s>\n" % (self.timestamp, self.level, self.module)
+ for line in self.msg.splitlines():
+ buff = buff + line + "\n"
+ return buff
+
def hunt_for_stacktrace(url):
"""Return TRACE or ERROR lines out of logs."""
@@ -29,11 +69,33 @@
buf = StringIO.StringIO(page.read())
f = gzip.GzipFile(fileobj=buf)
content = f.read()
- traces = re.findall('^(.*? (TRACE|ERROR) .*?)$', content, re.MULTILINE)
- tracelist = map(lambda x: x[0], traces)
- # filter out log definitions as false possitives
- return filter(lambda x: not re.search('logging_exception_prefix', x),
- tracelist)
+
+ traces = []
+ trace = StackTrace()
+ for line in content.splitlines():
+ m = re.match(NOVA_REGEX, line)
+ if m:
+ data = m.groupdict()
+ if trace.not_none() and trace.is_same(data):
+ trace.append(data['msg'] + "\n")
+ else:
+ trace = StackTrace(
+ timestamp=data.get('timestamp'),
+ pid=data.get('pid'),
+ level=data.get('level'),
+ module=data.get('module'),
+ msg=data.get('msg'))
+
+ else:
+ if trace.not_none():
+ traces.append(trace)
+ trace = StackTrace()
+
+ # once more at the end to pick up any stragglers
+ if trace.not_none():
+ traces.append(trace)
+
+ return traces
def log_url(url, log):
@@ -60,6 +122,18 @@
sys.exit(0)
+def print_stats(items, fname, verbose=False):
+ errors = len(filter(lambda x: x.level == "ERROR", items))
+ traces = len(filter(lambda x: x.level == "TRACE", items))
+ print "%d ERRORS found in %s" % (errors, fname)
+ print "%d TRACES found in %s" % (traces, fname)
+
+ if verbose:
+ for item in items:
+ print item
+ print "\n\n"
+
+
def main():
if len(sys.argv) == 2:
url = sys.argv[1]
@@ -72,10 +146,10 @@
for log in loglist:
logurl = log_url(url, log)
traces = hunt_for_stacktrace(logurl)
+
if traces:
- print "\n\nTRACES found in %s\n" % log
- for line in traces:
- print line
+ print_stats(traces, log, verbose=True)
+
else:
usage()
diff --git a/tools/hacking.py b/tools/hacking.py
deleted file mode 100755
index 7e46b74..0000000
--- a/tools/hacking.py
+++ /dev/null
@@ -1,525 +0,0 @@
-#!/usr/bin/env python
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-# Copyright (c) 2012, Cloudscaling
-# 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.
-
-"""tempest HACKING file compliance testing
-
-built on top of pep8.py
-"""
-
-import inspect
-import logging
-import os
-import re
-import subprocess
-import sys
-import tokenize
-import warnings
-
-import pep8
-
-# Don't need this for testing
-logging.disable('LOG')
-
-#T1xx comments
-#T2xx except
-#T3xx imports
-#T4xx docstrings
-#T5xx dictionaries/lists
-#T6xx calling methods
-#T7xx localization
-#N8xx git commit messages
-
-IMPORT_EXCEPTIONS = ['sqlalchemy', 'migrate']
-DOCSTRING_TRIPLE = ['"""', "'''"]
-VERBOSE_MISSING_IMPORT = os.getenv('HACKING_VERBOSE_MISSING_IMPORT', 'False')
-
-
-# Monkey patch broken excluded filter in pep8
-# See https://github.com/jcrocholl/pep8/pull/111
-def excluded(self, filename):
- """
- Check if options.exclude contains a pattern that matches filename.
- """
- basename = os.path.basename(filename)
- return any((pep8.filename_match(filename, self.options.exclude,
- default=False),
- pep8.filename_match(basename, self.options.exclude,
- default=False)))
-
-
-def input_dir(self, dirname):
- """Check all files in this directory and all subdirectories."""
- dirname = dirname.rstrip('/')
- if self.excluded(dirname):
- return 0
- counters = self.options.report.counters
- verbose = self.options.verbose
- filepatterns = self.options.filename
- runner = self.runner
- for root, dirs, files in os.walk(dirname):
- if verbose:
- print('directory ' + root)
- counters['directories'] += 1
- for subdir in sorted(dirs):
- if self.excluded(os.path.join(root, subdir)):
- dirs.remove(subdir)
- for filename in sorted(files):
- # contain a pattern that matches?
- if ((pep8.filename_match(filename, filepatterns) and
- not self.excluded(filename))):
- runner(os.path.join(root, filename))
-
-
-def is_import_exception(mod):
- return (mod in IMPORT_EXCEPTIONS or
- any(mod.startswith(m + '.') for m in IMPORT_EXCEPTIONS))
-
-
-def import_normalize(line):
- # convert "from x import y" to "import x.y"
- # handle "from x import y as z" to "import x.y as z"
- split_line = line.split()
- if ("import" in line and line.startswith("from ") and "," not in line and
- split_line[2] == "import" and split_line[3] != "*" and
- split_line[1] != "__future__" and
- (len(split_line) == 4 or
- (len(split_line) == 6 and split_line[4] == "as"))):
- return "import %s.%s" % (split_line[1], split_line[3])
- else:
- return line
-
-
-def tempest_todo_format(physical_line):
- """Check for 'TODO()'.
-
- tempest HACKING guide recommendation for TODO:
- Include your name with TODOs as in "#TODO(termie)"
- T101
- """
- pos = physical_line.find('TODO')
- pos1 = physical_line.find('TODO(')
- pos2 = physical_line.find('#') # make sure it's a comment
- if (pos != pos1 and pos2 >= 0 and pos2 < pos):
- return pos, "T101: Use TODO(NAME)"
-
-
-def tempest_except_format(logical_line):
- """Check for 'except:'.
-
- tempest HACKING guide recommends not using except:
- Do not write "except:", use "except Exception:" at the very least
- T201
- """
- if logical_line.startswith("except:"):
- yield 6, "T201: no 'except:' at least use 'except Exception:'"
-
-
-def tempest_except_format_assert(logical_line):
- """Check for 'assertRaises(Exception'.
-
- tempest HACKING guide recommends not using assertRaises(Exception...):
- Do not use overly broad Exception type
- T202
- """
- if logical_line.startswith("self.assertRaises(Exception"):
- yield 1, "T202: assertRaises Exception too broad"
-
-
-def tempest_one_import_per_line(logical_line):
- """Check for import format.
-
- tempest HACKING guide recommends one import per line:
- Do not import more than one module per line
-
- Examples:
- BAD: from tempest.common.rest_client import RestClient, RestClientXML
- T301
- """
- pos = logical_line.find(',')
- parts = logical_line.split()
- if (pos > -1 and (parts[0] == "import" or
- parts[0] == "from" and parts[2] == "import") and
- not is_import_exception(parts[1])):
- yield pos, "T301: one import per line"
-
-_missingImport = set([])
-
-
-def tempest_import_module_only(logical_line):
- """Check for import module only.
-
- tempest HACKING guide recommends importing only modules:
- Do not import objects, only modules
- T302 import only modules
- T303 Invalid Import
- T304 Relative Import
- """
- def importModuleCheck(mod, parent=None, added=False):
- """
- If can't find module on first try, recursively check for relative
- imports
- """
- current_path = os.path.dirname(pep8.current_file)
- try:
- with warnings.catch_warnings():
- warnings.simplefilter('ignore', DeprecationWarning)
- valid = True
- if parent:
- if is_import_exception(parent):
- return
- parent_mod = __import__(parent, globals(), locals(),
- [mod], -1)
- valid = inspect.ismodule(getattr(parent_mod, mod))
- else:
- __import__(mod, globals(), locals(), [], -1)
- valid = inspect.ismodule(sys.modules[mod])
- if not valid:
- if added:
- sys.path.pop()
- added = False
- return logical_line.find(mod), ("T304: No "
- "relative imports. "
- "'%s' is a relative "
- "import"
- % logical_line)
- return logical_line.find(mod), ("T302: import only"
- " modules. '%s' does not "
- "import a module"
- % logical_line)
-
- except (ImportError, NameError) as exc:
- if not added:
- added = True
- sys.path.append(current_path)
- return importModuleCheck(mod, parent, added)
- else:
- name = logical_line.split()[1]
- if name not in _missingImport:
- if VERBOSE_MISSING_IMPORT != 'False':
- print >> sys.stderr, ("ERROR: import '%s' in %s "
- "failed: %s" %
- (name, pep8.current_file, exc))
- _missingImport.add(name)
- added = False
- sys.path.pop()
- return
-
- except AttributeError:
- # Invalid import
- return logical_line.find(mod), ("T303: Invalid import, "
- "AttributeError raised")
-
- # convert "from x import y" to " import x.y"
- # convert "from x import y as z" to " import x.y"
- import_normalize(logical_line)
- split_line = logical_line.split()
-
- if (logical_line.startswith("import ") and "," not in logical_line and
- (len(split_line) == 2 or
- (len(split_line) == 4 and split_line[2] == "as"))):
- mod = split_line[1]
- rval = importModuleCheck(mod)
- if rval is not None:
- yield rval
-
- # TODO(jogo) handle "from x import *"
-
-#TODO(jogo): import template: T305
-
-
-def tempest_import_alphabetical(logical_line, line_number, lines):
- """Check for imports in alphabetical order.
-
- Tempest HACKING guide recommendation for imports:
- imports in human alphabetical order
- T306
- """
- # handle import x
- # use .lower since capitalization shouldn't dictate order
- split_line = import_normalize(logical_line.strip()).lower().split()
- split_previous = import_normalize(lines[
- line_number - 2]).strip().lower().split()
- # with or without "as y"
- length = [2, 4]
- if (len(split_line) in length and len(split_previous) in length and
- split_line[0] == "import" and split_previous[0] == "import"):
- if split_line[1] < split_previous[1]:
- yield (0, "T306: imports not in alphabetical order"
- " (%s, %s)"
- % (split_previous[1], split_line[1]))
-
-
-def tempest_docstring_start_space(physical_line):
- """Check for docstring not start with space.
-
- tempest HACKING guide recommendation for docstring:
- Docstring should not start with space
- T401
- """
- pos = max([physical_line.find(i) for i in DOCSTRING_TRIPLE]) # start
- end = max([physical_line[-4:-1] == i for i in DOCSTRING_TRIPLE]) # end
- if (pos != -1 and end and len(physical_line) > pos + 4):
- if (physical_line[pos + 3] == ' '):
- return (pos, "T401: one line docstring should not start"
- " with a space")
-
-
-def tempest_docstring_one_line(physical_line):
- """Check one line docstring end.
-
- tempest HACKING guide recommendation for one line docstring:
- A one line docstring looks like this and ends in a period.
- T402
- """
- pos = max([physical_line.find(i) for i in DOCSTRING_TRIPLE]) # start
- end = max([physical_line[-4:-1] == i for i in DOCSTRING_TRIPLE]) # end
- if (pos != -1 and end and len(physical_line) > pos + 4):
- if (physical_line[-5] != '.'):
- return pos, "T402: one line docstring needs a period"
-
-
-def tempest_docstring_multiline_end(physical_line):
- """Check multi line docstring end.
-
- Tempest HACKING guide recommendation for docstring:
- Docstring should end on a new line
- T403
- """
- pos = max([physical_line.find(i) for i in DOCSTRING_TRIPLE]) # start
- if (pos != -1 and len(physical_line) == pos):
- if (physical_line[pos + 3] == ' '):
- return (pos, "T403: multi line docstring end on new line")
-
-
-def tempest_no_test_docstring(physical_line, previous_logical, filename):
- """Check that test_ functions don't have docstrings
-
- This ensure we get better results out of tempest, instead
- of them being hidden behind generic descriptions of the
- functions.
-
- T404
- """
- if "tempest/test" in filename:
- pos = max([physical_line.find(i) for i in DOCSTRING_TRIPLE])
- if pos != -1:
- if previous_logical.startswith("def test_"):
- 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
- "(\(\w+\))?" # mapping key
- "([#0 +-]?" # flag
- "(?:\d+|\*)?" # width
- "(?:\.\d+)?" # precision
- "[hlL]?" # length mod
- "\w))") # type
-
-
-class LocalizationError(Exception):
- pass
-
-
-def check_i18n():
- """Generator that checks token stream for localization errors.
-
- Expects tokens to be ``send``ed one by one.
- Raises LocalizationError if some error is found.
- """
- while True:
- try:
- token_type, text, _, _, line = yield
- except GeneratorExit:
- return
- if (token_type == tokenize.NAME and text == "_" and
- not line.startswith('def _(msg):')):
-
- while True:
- token_type, text, start, _, _ = yield
- if token_type != tokenize.NL:
- break
- if token_type != tokenize.OP or text != "(":
- continue # not a localization call
-
- format_string = ''
- while True:
- token_type, text, start, _, _ = yield
- if token_type == tokenize.STRING:
- format_string += eval(text)
- elif token_type == tokenize.NL:
- pass
- else:
- break
-
- if not format_string:
- raise LocalizationError(start,
- "T701: Empty localization "
- "string")
- if token_type != tokenize.OP:
- raise LocalizationError(start,
- "T701: Invalid localization "
- "call")
- if text != ")":
- if text == "%":
- raise LocalizationError(start,
- "T702: Formatting "
- "operation should be outside"
- " of localization method call")
- elif text == "+":
- raise LocalizationError(start,
- "T702: Use bare string "
- "concatenation instead of +")
- else:
- raise LocalizationError(start,
- "T702: Argument to _ must"
- " be just a string")
-
- format_specs = FORMAT_RE.findall(format_string)
- positional_specs = [(key, spec) for key, spec in format_specs
- if not key and spec]
- # not spec means %%, key means %(smth)s
- if len(positional_specs) > 1:
- raise LocalizationError(start,
- "T703: Multiple positional "
- "placeholders")
-
-
-def tempest_localization_strings(logical_line, tokens):
- """Check localization in line.
-
- T701: bad localization call
- T702: complex expression instead of string as argument to _()
- T703: multiple positional placeholders
- """
-
- gen = check_i18n()
- next(gen)
- try:
- map(gen.send, tokens)
- gen.close()
- except LocalizationError as e:
- yield e.args
-
-#TODO(jogo) Dict and list objects
-
-current_file = ""
-
-
-def readlines(filename):
- """Record the current file being tested."""
- pep8.current_file = filename
- return open(filename).readlines()
-
-
-def add_tempest():
- """Monkey patch in tempest guidelines.
-
- Look for functions that start with tempest_ and have arguments
- and add them to pep8 module
- Assumes you know how to write pep8.py checks
- """
- for name, function in globals().items():
- if not inspect.isfunction(function):
- continue
- args = inspect.getargspec(function)[0]
- if args and name.startswith("tempest"):
- exec("pep8.%s = %s" % (name, name))
-
-
-def once_git_check_commit_title():
- """Check git commit messages.
-
- tempest HACKING recommends not referencing a bug or blueprint
- in first line, it should provide an accurate description of the change
- T801
- T802 Title limited to 50 chars
- """
- #Get title of most recent commit
-
- subp = subprocess.Popen(['git', 'log', '--no-merges', '--pretty=%s', '-1'],
- stdout=subprocess.PIPE)
- title = subp.communicate()[0]
- if subp.returncode:
- raise Exception("git log failed with code %s" % subp.returncode)
-
- #From https://github.com/openstack/openstack-ci-puppet
- # /blob/master/modules/gerrit/manifests/init.pp#L74
- #Changeid|bug|blueprint
- git_keywords = (r'(I[0-9a-f]{8,40})|'
- '([Bb]ug|[Ll][Pp])[\s\#:]*(\d+)|'
- '([Bb]lue[Pp]rint|[Bb][Pp])[\s\#:]*([A-Za-z0-9\\-]+)')
- GIT_REGEX = re.compile(git_keywords)
-
- error = False
- #NOTE(jogo) if match regex but over 3 words, acceptable title
- if GIT_REGEX.search(title) is not None and len(title.split()) <= 3:
- print ("T801: git commit title ('%s') should provide an accurate "
- "description of the change, not just a reference to a bug "
- "or blueprint" % title.strip())
- error = True
- if len(title.decode('utf-8')) > 72:
- print ("T802: git commit title ('%s') should be under 50 chars"
- % title.strip())
- error = True
- return error
-
-if __name__ == "__main__":
- #include tempest path
- sys.path.append(os.getcwd())
- #Run once tests (not per line)
- once_error = once_git_check_commit_title()
- #TEMPEST error codes start with a T
- pep8.ERRORCODE_REGEX = re.compile(r'[EWT]\d{3}')
- add_tempest()
- pep8.current_file = current_file
- pep8.readlines = readlines
- pep8.StyleGuide.excluded = excluded
- pep8.StyleGuide.input_dir = input_dir
- try:
- pep8._main()
- sys.exit(once_error)
- finally:
- if len(_missingImport) > 0:
- print >> sys.stderr, ("%i imports missing in this test environment"
- % len(_missingImport))
diff --git a/tools/install_venv.py b/tools/install_venv.py
index ef7b0a8..5d4b290 100644
--- a/tools/install_venv.py
+++ b/tools/install_venv.py
@@ -3,7 +3,7 @@
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
-#
+# flake8: noqa
# Copyright 2010 OpenStack, LLC
# Copyright 2013 IBM Corp.
#
diff --git a/tools/tempest_coverage.py b/tools/tempest_coverage.py
index 9dcbd46..5b926f9 100755
--- a/tools/tempest_coverage.py
+++ b/tools/tempest_coverage.py
@@ -165,7 +165,7 @@
resp, body = coverage_client.report_coverage_xml(file=CLI.filename)
elif CLI.html:
resp, body = coverage_client.report_coverage_html(
- file=CLI.filename)
+ file=CLI.filename)
else:
resp, body = coverage_client.report_coverage(file=CLI.filename)
if not resp['status'] == '200':
diff --git a/tools/test-requires b/tools/test-requires
index f701dab..cba42a4 100644
--- a/tools/test-requires
+++ b/tools/test-requires
@@ -1,5 +1,8 @@
-pep8==1.3.3
-pylint==0.19
+# Install bounded pep8/pyflakes first, then let flake8 install
+pep8==1.4.5
+pyflakes==0.7.2
+flake8==2.0
+hacking>=0.5.3,<0.6
+#
#TODO(afazekas): ensure pg_config installed
psycopg2
-pyflakes
diff --git a/tox.ini b/tox.ini
index 85a0d86..4a2f80e 100644
--- a/tox.ini
+++ b/tox.ini
@@ -19,4 +19,9 @@
python -m tools/tempest_coverage -c report --html
[testenv:pep8]
-commands = bash tools/check_source.sh
+commands = flake8
+
+[flake8]
+ignore = E125,H302,H404
+show-source = True
+exclude = .git,.venv,.tox,dist,doc,openstack,*egg