Merge "Convert these tests of ServerBasicOps into one test."
diff --git a/.gitignore b/.gitignore
index b4dca86..c154603 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
+AUTHORS
+ChangeLog
*.pyc
etc/tempest.conf
include/swift_objects/swift_small
@@ -6,7 +8,7 @@
*.log
*.swp
*.swo
-*.egg-info
+*.egg*
.tox
.venv
dist
diff --git a/.mailmap b/.mailmap
new file mode 100644
index 0000000..9a22c71
--- /dev/null
+++ b/.mailmap
@@ -0,0 +1,8 @@
+Ravikumar Venkatesan <ravikumar.venkatesan@hp.com> ravikumar-venkatesan <ravikumar.venkatesan@hp.com>
+Ravikumar Venkatesan <ravikumar.venkatesan@hp.com> ravikumar venkatesan <ravikumar.venkatesan@hp.com>
+Rohit Karajgi <rohit.karajgi@nttdata.com> Rohit Karajgi <rohit.karajgi@vertex.co.in>
+Jay Pipes <jaypipes@gmail.com> Jay Pipes <jpipes@librebox.gateway.2wire.net>
+<brian.waldon@rackspace.com> <bcwaldon@gmail.com>
+Daryl Walleck <daryl.walleck@rackspace.com> dwalleck <daryl.walleck@rackspace.com>
+<jeblair@hp.com> <corvus@inaugust.com>
+<jeblair@hp.com> <james.blair@rackspace.com>
diff --git a/run_tests.sh b/run_tests.sh
index 0df8a99..6b7ebec 100755
--- a/run_tests.sh
+++ b/run_tests.sh
@@ -10,6 +10,7 @@
echo " -f, --force Force a clean re-build of the virtual environment. Useful when dependencies have been added."
echo " -s, --smoke Only run smoke tests"
echo " -w, --whitebox Only run whitebox tests"
+ echo " -c, --nova-coverage Enable Nova coverage collection"
echo " -p, --pep8 Just run pep8"
echo " -h, --help Print this usage message"
echo " -d, --debug Debug this script -- set -o xtrace"
@@ -25,6 +26,7 @@
-s|--no-site-packages) no_site_packages=1;;
-f|--force) force=1;;
-d|--debug) set -o xtrace;;
+ -c|--nova-coverage) let nova_coverage=1;;
-p|--pep8) let just_pep8=1;;
-s|--smoke) noseargs="$noseargs --attr=type=smoke";;
-w|--whitebox) noseargs="$noseargs --attr=type=whitebox";;
@@ -42,7 +44,7 @@
no_site_packages=0
force=0
wrapper=""
-
+nova_coverage=0
export NOSE_WITH_OPENSTACK=1
export NOSE_OPENSTACK_COLOR=1
@@ -75,11 +77,22 @@
echo "Running pep8 ..."
srcfiles="`find tempest -type f -name "*.py"`"
srcfiles+=" `find tools -type f -name "*.py"`"
+ srcfiles+=" `find stress -type f -name "*.py"`"
srcfiles+=" setup.py"
- ignore='--ignore=N4,E121,E122,E125,E126'
+ ignore='--ignore=E121,E122,E125,E126'
- ${wrapper} python tools/hacking.py ${ignore} ${srcfiles}
+ ${wrapper} python tools/hacking.py ${ignore} ${srcfiles}
+}
+
+function run_coverage_start {
+ echo "Starting nova-coverage"
+ ${wrapper} python tools/tempest_coverage.py -c start
+}
+
+function run_coverage_report {
+ echo "Generating nova-coverage report"
+ ${wrapper} python tools/tempest_coverage.py -c report
}
NOSETESTS="nosetests $noseargs"
@@ -115,7 +128,15 @@
exit
fi
-run_tests || exit
+if [ $nova_coverage -eq 1 ]; then
+ run_coverage_start
+fi
+
+run_tests
+
+if [ $nova_coverage -eq 1 ]; then
+ run_coverage_report
+fi
if [ -z "$noseargs" ]; then
run_pep8
diff --git a/stress/config.py b/stress/config.py
index 64091cd..ca86ce5 100755
--- a/stress/config.py
+++ b/stress/config.py
@@ -39,15 +39,15 @@
@property
def nova_logdir(self):
- """Directory containing log files on the compute nodes"""
+ """Directory containing log files on the compute nodes."""
return self.get("nova_logdir", None)
@property
def controller(self):
- """Controller host"""
+ """Controller host."""
return self.get("controller", None)
@property
def max_instances(self):
- """Maximum number of instances to create during test"""
+ """Maximum number of instances to create during test."""
return self.get("max_instances", 16)
diff --git a/stress/pending_action.py b/stress/pending_action.py
index 635d2cc..abfa74d 100644
--- a/stress/pending_action.py
+++ b/stress/pending_action.py
@@ -46,7 +46,7 @@
return False
def check_timeout(self):
- """Check for timeouts of TestCase actions"""
+ """Check for timeouts of TestCase actions."""
time_diff = time.time() - self._start_time
if time_diff > self._timeout:
self._logger.error('%s exceeded timeout of %d' %
@@ -76,7 +76,7 @@
self._target = target_server
def _check_for_status(self, state_string):
- """Check to see if the machine has transitioned states"""
+ """Check to see if the machine has transitioned states."""
t = time.time() # for debugging
target = self._target
_resp, body = self._manager.servers_client.get_server(target['id'])
diff --git a/stress/test_case.py b/stress/test_case.py
index fe510d5..d04ace0 100644
--- a/stress/test_case.py
+++ b/stress/test_case.py
@@ -25,5 +25,5 @@
self._logger = logging.getLogger(self.__class__.__name__)
def run(self, nova_manager, state_obj, *pargs, **kargs):
- """Nova API methods to call that would modify state of the cluster"""
+ """Nova API methods to call that would modify state of the cluster."""
return
diff --git a/stress/test_floating_ips.py b/stress/test_floating_ips.py
index 9d89510..97e4382 100755
--- a/stress/test_floating_ips.py
+++ b/stress/test_floating_ips.py
@@ -61,7 +61,7 @@
class VerifyChangeFloatingIp(pending_action.PendingAction):
- """Verify that floating ip was changed"""
+ """Verify that floating ip was changed."""
def __init__(self, manager, floating_ip, timeout, add=None):
super(VerifyChangeFloatingIp, self).__init__(manager, timeout=timeout)
self.floating_ip = floating_ip
diff --git a/stress/test_server_actions.py b/stress/test_server_actions.py
index 3b4e71c..a2032f0 100644
--- a/stress/test_server_actions.py
+++ b/stress/test_server_actions.py
@@ -27,7 +27,7 @@
class TestRebootVM(test_case.StressTestCase):
- """Reboot a server"""
+ """Reboot a server."""
def run(self, manager, state, *pargs, **kwargs):
"""
@@ -132,7 +132,7 @@
# This code needs to be tested against a cluster that supports resize.
#class TestResizeVM(test_case.StressTestCase):
-# """Resize a server (change flavors)"""
+# """Resize a server (change flavors)."""
#
# def run(self, manager, state, *pargs, **kwargs):
# """
@@ -193,7 +193,7 @@
# timeout=_timeout)
#
#class VerifyResizeVM(pending_action.PendingServerAction):
-# """Verify that resizing of a VM was successful"""
+# """Verify that resizing of a VM was successful."""
# States = enum('VERIFY_RESIZE_CHECK', 'ACTIVE_CHECK')
#
# def __init__(self, manager, state, created_server,
diff --git a/stress/test_servers.py b/stress/test_servers.py
index 9957cdb..e1cb4d1 100644
--- a/stress/test_servers.py
+++ b/stress/test_servers.py
@@ -96,7 +96,7 @@
class VerifyCreateVM(pending_action.PendingServerAction):
- """Verify that VM was built and is running"""
+ """Verify that VM was built and is running."""
def __init__(self, manager,
state,
created_server,
@@ -175,7 +175,7 @@
class VerifyKillActiveVM(pending_action.PendingServerAction):
- """Verify that server was destroyed"""
+ """Verify that server was destroyed."""
def retry(self):
"""
@@ -238,7 +238,7 @@
class TestUpdateVMName(test_case.StressTestCase):
- """Class to change the name of the active server"""
+ """Class to change the name of the active server."""
def run(self, manager, state, *pargs, **kwargs):
"""
Issue HTTP POST request to change the name of active server.
@@ -288,7 +288,7 @@
class VerifyUpdateVMName(pending_action.PendingServerAction):
- """Check that VM has new name"""
+ """Check that VM has new name."""
def retry(self):
"""
Check that VM has new name. Update local view of `state` to RUNNING.
diff --git a/stress/tests/floating_ips.py b/stress/tests/floating_ips.py
index d62e892..6a4452c 100755
--- a/stress/tests/floating_ips.py
+++ b/stress/tests/floating_ips.py
@@ -11,7 +11,7 @@
# 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.
-"""Stress test that associates/disasssociates floating ips"""
+"""Stress test that associates/disasssociates floating ips."""
from stress.basher import BasherAction
from stress.driver import *
diff --git a/stress/tests/hard_reboots.py b/stress/tests/hard_reboots.py
index f35c6de..fe57be1 100644
--- a/stress/tests/hard_reboots.py
+++ b/stress/tests/hard_reboots.py
@@ -11,7 +11,7 @@
# 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.
-"""Test that reboots random instances in a Nova cluster"""
+"""Test that reboots random instances in a Nova cluster."""
from stress.test_servers import *
diff --git a/stress/utils/util.py b/stress/utils/util.py
index f90796d..ec63b99 100644
--- a/stress/utils/util.py
+++ b/stress/utils/util.py
@@ -50,6 +50,6 @@
def enum(*sequential, **named):
- """Create auto-incremented enumerated types"""
+ """Create auto-incremented enumerated types."""
enums = dict(zip(sequential, range(len(sequential))), **named)
return type('Enum', (), enums)
diff --git a/tempest/common/rest_client.py b/tempest/common/rest_client.py
index c96fc4f..287ef56 100644
--- a/tempest/common/rest_client.py
+++ b/tempest/common/rest_client.py
@@ -303,7 +303,7 @@
return resp, resp_body
def wait_for_resource_deletion(self, id):
- """Waits for a resource to be deleted"""
+ """Waits for a resource to be deleted."""
start_time = int(time.time())
while True:
if self.is_resource_deleted(id):
diff --git a/tempest/common/ssh.py b/tempest/common/ssh.py
index 1b40ddc..151060f 100644
--- a/tempest/common/ssh.py
+++ b/tempest/common/ssh.py
@@ -48,7 +48,7 @@
self.buf_size = 1024
def _get_ssh_connection(self):
- """Returns an ssh connection to the specified host"""
+ """Returns an ssh connection to the specified host."""
_timeout = True
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(
@@ -79,7 +79,7 @@
return (time.time() - timeout) > start_time
def connect_until_closed(self):
- """Connect to the server and wait until connection is lost"""
+ """Connect to the server and wait until connection is lost."""
try:
ssh = self._get_ssh_connection()
_transport = ssh.get_transport()
@@ -137,7 +137,7 @@
return ''.join(out_data)
def test_connection_auth(self):
- """ Returns true if ssh can connect to server"""
+ """Returns true if ssh can connect to server."""
try:
connection = self._get_ssh_connection()
connection.close()
diff --git a/tempest/common/utils/data_utils.py b/tempest/common/utils/data_utils.py
index 951fb61..3a7661c 100644
--- a/tempest/common/utils/data_utils.py
+++ b/tempest/common/utils/data_utils.py
@@ -29,7 +29,7 @@
def build_url(host, port, api_version=None, path=None,
params=None, use_ssl=False):
- """Build the request URL from given host, port, path and parameters"""
+ """Build the request URL from given host, port, path and parameters."""
pattern = 'v\d\.\d'
if re.match(pattern, path):
@@ -59,7 +59,7 @@
def parse_image_id(image_ref):
- """Return the image id from a given image ref"""
+ """Return the image id from a given image ref."""
return image_ref.rsplit('/')[-1]
diff --git a/tempest/config.py b/tempest/config.py
index 2b0eb70..8233dd5 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -458,7 +458,7 @@
# TODO(jaypipes): Move this to a common utils (not data_utils...)
def singleton(cls):
- """Simple wrapper for classes that should only have a single instance"""
+ """Simple wrapper for classes that should only have a single instance."""
instances = {}
def getinstance():
diff --git a/tempest/exceptions.py b/tempest/exceptions.py
index c9e2f95..178c2f2 100644
--- a/tempest/exceptions.py
+++ b/tempest/exceptions.py
@@ -118,7 +118,7 @@
class SSHExecCommandFailed(TempestException):
- ''' Raised when remotely executed command returns nonzero status. '''
+ """Raised when remotely executed command returns nonzero status."""
message = ("Command '%(command)s', exit status: %(exit_status)d, "
"Error:\n%(strerror)s")
diff --git a/tempest/openstack/common/cfg.py b/tempest/openstack/common/cfg.py
index 25667a4..1bbfe6a 100644
--- a/tempest/openstack/common/cfg.py
+++ b/tempest/openstack/common/cfg.py
@@ -1417,7 +1417,7 @@
logger.log(lvl, "=" * 80)
def _sanitize(opt, value):
- """Obfuscate values of options declared secret"""
+ """Obfuscate values of options declared secret."""
return value if not opt.secret else '*' * len(str(value))
for opt_name in sorted(self._opts):
diff --git a/tempest/openstack/common/iniparser.py b/tempest/openstack/common/iniparser.py
index 2412844..b5cb604 100644
--- a/tempest/openstack/common/iniparser.py
+++ b/tempest/openstack/common/iniparser.py
@@ -100,15 +100,15 @@
self._assignment(key, value)
def assignment(self, key, value):
- """Called when a full assignment is parsed"""
+ """Called when a full assignment is parsed."""
raise NotImplementedError()
def new_section(self, section):
- """Called when a new section is started"""
+ """Called when a new section is started."""
raise NotImplementedError()
def comment(self, comment):
- """Called when a comment is parsed"""
+ """Called when a comment is parsed."""
pass
def error_invalid_assignment(self, line):
diff --git a/tempest/services/boto/__init__.py b/tempest/services/boto/__init__.py
index 58a2b37..1365435 100644
--- a/tempest/services/boto/__init__.py
+++ b/tempest/services/boto/__init__.py
@@ -35,8 +35,8 @@
auth_url=None, tenant_name=None,
*args, **kwargs):
- self.connection_timeout = config.boto.http_socket_timeout
- self.num_retries = config.boto.num_retries
+ self.connection_timeout = str(config.boto.http_socket_timeout)
+ self.num_retries = str(config.boto.num_retries)
self.build_timeout = config.boto.build_timeout
# We do not need the "path": "/token" part
if auth_url:
@@ -73,7 +73,7 @@
boto.config.set("Boto", "num_retries", retries)
def __getattr__(self, name):
- """Automatically creates methods for the allowed methods set"""
+ """Automatically creates methods for the allowed methods set."""
if name in self.ALLOWED_METHODS:
def func(self, *args, **kwargs):
with closing(self.get_connection()) as conn:
diff --git a/tempest/services/compute/json/flavors_client.py b/tempest/services/compute/json/flavors_client.py
index 1c8d4f3..bd339b2 100644
--- a/tempest/services/compute/json/flavors_client.py
+++ b/tempest/services/compute/json/flavors_client.py
@@ -51,20 +51,23 @@
body = json.loads(body)
return resp, body['flavor']
- def create_flavor(self, name, ram, vcpus, disk, ephemeral, flavor_id,
- swap, rxtx):
- """Creates a new flavor or instance type"""
+ def create_flavor(self, name, ram, vcpus, disk, flavor_id, **kwargs):
+ """Creates a new flavor or instance type."""
post_body = {
'name': name,
'ram': ram,
'vcpus': vcpus,
'disk': disk,
- 'OS-FLV-EXT-DATA:ephemeral': ephemeral,
'id': flavor_id,
- 'swap': swap,
- 'rxtx_factor': rxtx,
}
-
+ if kwargs.get('ephemeral'):
+ post_body['OS-FLV-EXT-DATA:ephemeral'] = kwargs.get('ephemeral')
+ if kwargs.get('swap'):
+ post_body['swap'] = kwargs.get('swap')
+ if kwargs.get('rxtx'):
+ post_body['rxtx_factor'] = kwargs.get('rxtx')
+ if kwargs.get('is_public'):
+ post_body['os-flavor-access:is_public'] = kwargs.get('is_public')
post_body = json.dumps({'flavor': post_body})
resp, body = self.post('flavors', post_body, self.headers)
@@ -72,5 +75,15 @@
return resp, body['flavor']
def delete_flavor(self, flavor_id):
- """Deletes the given flavor"""
+ """Deletes the given flavor."""
return self.delete("flavors/%s" % str(flavor_id))
+
+ def is_resource_deleted(self, id):
+ #Did not use get_flavor_details(id) for verification as it gives
+ #200 ok even for deleted id. LP #981263
+ #we can remove the loop here and use get by ID when bug gets sortedout
+ resp, flavors = self.list_flavors_with_detail()
+ for flavor in flavors:
+ if flavor['id'] == id:
+ return False
+ return True
diff --git a/tempest/services/compute/json/floating_ips_client.py b/tempest/services/compute/json/floating_ips_client.py
index 15882c7..d73b8a9 100644
--- a/tempest/services/compute/json/floating_ips_client.py
+++ b/tempest/services/compute/json/floating_ips_client.py
@@ -29,7 +29,7 @@
self.service = self.config.compute.catalog_type
def list_floating_ips(self, params=None):
- """Returns a list of all floating IPs filtered by any parameters"""
+ """Returns a list of all floating IPs filtered by any parameters."""
url = 'os-floating-ips'
if params:
url += '?%s' % urllib.urlencode(params)
@@ -39,7 +39,7 @@
return resp, body['floating_ips']
def get_floating_ip_details(self, floating_ip_id):
- """Get the details of a floating IP"""
+ """Get the details of a floating IP."""
url = "os-floating-ips/%s" % str(floating_ip_id)
resp, body = self.get(url)
body = json.loads(body)
@@ -48,20 +48,20 @@
return resp, body['floating_ip']
def create_floating_ip(self):
- """Allocate a floating IP to the project"""
+ """Allocate a floating IP to the project."""
url = 'os-floating-ips'
resp, body = self.post(url, None, None)
body = json.loads(body)
return resp, body['floating_ip']
def delete_floating_ip(self, floating_ip_id):
- """Deletes the provided floating IP from the project"""
+ """Deletes the provided floating IP from the project."""
url = "os-floating-ips/%s" % str(floating_ip_id)
resp, body = self.delete(url)
return resp, body
def associate_floating_ip_to_server(self, floating_ip, server_id):
- """Associate the provided floating IP to a specific server"""
+ """Associate the provided floating IP to a specific server."""
url = "servers/%s/action" % str(server_id)
post_body = {
'addFloatingIp': {
@@ -74,7 +74,7 @@
return resp, body
def disassociate_floating_ip_from_server(self, floating_ip, server_id):
- """Disassociate the provided floating IP from a specific server"""
+ """Disassociate the provided floating IP from a specific server."""
url = "servers/%s/action" % str(server_id)
post_body = {
'removeFloatingIp': {
diff --git a/tempest/services/compute/json/hosts_client.py b/tempest/services/compute/json/hosts_client.py
index 517e230..dc3c524 100644
--- a/tempest/services/compute/json/hosts_client.py
+++ b/tempest/services/compute/json/hosts_client.py
@@ -11,7 +11,7 @@
self.service = self.config.compute.catalog_type
def list_hosts(self):
- """Lists all hosts"""
+ """Lists all hosts."""
url = 'os-hosts'
resp, body = self.get(url)
diff --git a/tempest/services/compute/json/images_client.py b/tempest/services/compute/json/images_client.py
index 452400a..376dafc 100644
--- a/tempest/services/compute/json/images_client.py
+++ b/tempest/services/compute/json/images_client.py
@@ -33,7 +33,7 @@
self.build_timeout = self.config.compute.build_timeout
def create_image(self, server_id, name, meta=None):
- """Creates an image of the original server"""
+ """Creates an image of the original server."""
post_body = {
'createImage': {
@@ -50,7 +50,7 @@
return resp, body
def list_images(self, params=None):
- """Returns a list of all images filtered by any parameters"""
+ """Returns a list of all images filtered by any parameters."""
url = 'images'
if params:
url += '?%s' % urllib.urlencode(params)
@@ -60,7 +60,7 @@
return resp, body['images']
def list_images_with_detail(self, params=None):
- """Returns a detailed list of images filtered by any parameters"""
+ """Returns a detailed list of images filtered by any parameters."""
url = 'images/detail'
if params:
url += '?%s' % urllib.urlencode(params)
@@ -70,13 +70,13 @@
return resp, body['images']
def get_image(self, image_id):
- """Returns the details of a single image"""
+ """Returns the details of a single image."""
resp, body = self.get("images/%s" % str(image_id))
body = json.loads(body)
return resp, body['image']
def delete_image(self, image_id):
- """Deletes the provided image"""
+ """Deletes the provided image."""
return self.delete("images/%s" % str(image_id))
def wait_for_image_resp_code(self, image_id, code):
@@ -110,13 +110,13 @@
raise exceptions.TimeoutException
def list_image_metadata(self, image_id):
- """Lists all metadata items for an image"""
+ """Lists all metadata items for an image."""
resp, body = self.get("images/%s/metadata" % str(image_id))
body = json.loads(body)
return resp, body['metadata']
def set_image_metadata(self, image_id, meta):
- """Sets the metadata for an image"""
+ """Sets the metadata for an image."""
post_body = json.dumps({'metadata': meta})
resp, body = self.put('images/%s/metadata' % str(image_id),
post_body, self.headers)
@@ -124,7 +124,7 @@
return resp, body['metadata']
def update_image_metadata(self, image_id, meta):
- """Updates the metadata for an image"""
+ """Updates the metadata for an image."""
post_body = json.dumps({'metadata': meta})
resp, body = self.post('images/%s/metadata' % str(image_id),
post_body, self.headers)
@@ -132,13 +132,13 @@
return resp, body['metadata']
def get_image_metadata_item(self, image_id, key):
- """Returns the value for a specific image metadata key"""
+ """Returns the value for a specific image metadata key."""
resp, body = self.get("images/%s/metadata/%s" % (str(image_id), key))
body = json.loads(body)
return resp, body['meta']
def set_image_metadata_item(self, image_id, key, meta):
- """Sets the value for a specific image metadata key"""
+ """Sets the value for a specific image metadata key."""
post_body = json.dumps({'meta': meta})
resp, body = self.put('images/%s/metadata/%s' % (str(image_id), key),
post_body, self.headers)
@@ -146,7 +146,7 @@
return resp, body['meta']
def delete_image_metadata_item(self, image_id, key):
- """Deletes a single image metadata key/value pair"""
+ """Deletes a single image metadata key/value pair."""
resp, body = self.delete("images/%s/metadata/%s" %
(str(image_id), key))
return resp, body
diff --git a/tempest/services/compute/json/quotas_client.py b/tempest/services/compute/json/quotas_client.py
index 2cc417f..543b015 100644
--- a/tempest/services/compute/json/quotas_client.py
+++ b/tempest/services/compute/json/quotas_client.py
@@ -28,7 +28,7 @@
self.service = self.config.compute.catalog_type
def get_quota_set(self, tenant_id):
- """List the quota set for a tenant"""
+ """List the quota set for a tenant."""
url = 'os-quota-sets/%s' % str(tenant_id)
resp, body = self.get(url)
diff --git a/tempest/services/compute/json/security_groups_client.py b/tempest/services/compute/json/security_groups_client.py
index f2586e5..95f2831 100644
--- a/tempest/services/compute/json/security_groups_client.py
+++ b/tempest/services/compute/json/security_groups_client.py
@@ -30,7 +30,7 @@
self.service = self.config.compute.catalog_type
def list_security_groups(self, params=None):
- """List all security groups for a user"""
+ """List all security groups for a user."""
url = 'os-security-groups'
if params:
@@ -41,7 +41,7 @@
return resp, body['security_groups']
def get_security_group(self, security_group_id):
- """Get the details of a Security Group"""
+ """Get the details of a Security Group."""
url = "os-security-groups/%s" % str(security_group_id)
resp, body = self.get(url)
body = json.loads(body)
@@ -63,7 +63,7 @@
return resp, body['security_group']
def delete_security_group(self, security_group_id):
- """Deletes the provided Security Group"""
+ """Deletes the provided Security Group."""
return self.delete('os-security-groups/%s' % str(security_group_id))
def create_security_group_rule(self, parent_group_id, ip_proto, from_port,
@@ -93,5 +93,5 @@
return resp, body['security_group_rule']
def delete_security_group_rule(self, group_rule_id):
- """Deletes the provided Security Group rule"""
+ """Deletes the provided Security Group rule."""
return self.delete('os-security-group-rules/%s' % str(group_rule_id))
diff --git a/tempest/services/compute/json/servers_client.py b/tempest/services/compute/json/servers_client.py
index 2e34ef8..b832af0 100644
--- a/tempest/services/compute/json/servers_client.py
+++ b/tempest/services/compute/json/servers_client.py
@@ -110,17 +110,17 @@
return resp, body['server']
def get_server(self, server_id):
- """Returns the details of an existing server"""
+ """Returns the details of an existing server."""
resp, body = self.get("servers/%s" % str(server_id))
body = json.loads(body)
return resp, body['server']
def delete_server(self, server_id):
- """Deletes the given server"""
+ """Deletes the given server."""
return self.delete("servers/%s" % str(server_id))
def list_servers(self, params=None):
- """Lists all servers for a user"""
+ """Lists all servers for a user."""
url = 'servers'
if params:
@@ -131,7 +131,7 @@
return resp, body
def list_servers_with_detail(self, params=None):
- """Lists all servers in detail for a user"""
+ """Lists all servers in detail for a user."""
url = 'servers/detail'
if params:
@@ -142,7 +142,7 @@
return resp, body
def wait_for_server_status(self, server_id, status):
- """Waits for a server to reach a given status"""
+ """Waits for a server to reach a given status."""
resp, body = self.get_server(server_id)
server_status = body['status']
start = int(time.time())
@@ -165,7 +165,7 @@
raise exceptions.TimeoutException(message)
def wait_for_server_termination(self, server_id, ignore_error=False):
- """Waits for server to reach termination"""
+ """Waits for server to reach termination."""
start_time = int(time.time())
while True:
try:
@@ -183,20 +183,20 @@
time.sleep(self.build_interval)
def list_addresses(self, server_id):
- """Lists all addresses for a server"""
+ """Lists all addresses for a server."""
resp, body = self.get("servers/%s/ips" % str(server_id))
body = json.loads(body)
return resp, body['addresses']
def list_addresses_by_network(self, server_id, network_id):
- """Lists all addresses of a specific network type for a server"""
+ """Lists all addresses of a specific network type for a server."""
resp, body = self.get("servers/%s/ips/%s" %
(str(server_id), network_id))
body = json.loads(body)
return resp, body
def change_password(self, server_id, password):
- """Changes the root password for the server"""
+ """Changes the root password for the server."""
post_body = {
'changePassword': {
'adminPass': password,
@@ -208,7 +208,7 @@
post_body, self.headers)
def reboot(self, server_id, reboot_type):
- """Reboots a server"""
+ """Reboots a server."""
post_body = {
'reboot': {
'type': reboot_type,
@@ -221,7 +221,7 @@
def rebuild(self, server_id, image_ref, name=None, meta=None,
personality=None, adminPass=None, disk_config=None):
- """Rebuilds a server with a new image"""
+ """Rebuilds a server with a new image."""
post_body = {
'imageRef': image_ref,
}
@@ -264,7 +264,7 @@
return resp, body
def confirm_resize(self, server_id):
- """Confirms the flavor change for a server"""
+ """Confirms the flavor change for a server."""
post_body = {
'confirmResize': None,
}
@@ -275,7 +275,7 @@
return resp, body
def revert_resize(self, server_id):
- """Reverts a server back to its original flavor"""
+ """Reverts a server back to its original flavor."""
post_body = {
'revertResize': None,
}
@@ -286,7 +286,7 @@
return resp, body
def create_image(self, server_id, image_name):
- """Creates an image of the given server"""
+ """Creates an image of the given server."""
post_body = {
'createImage': {
'name': image_name,
@@ -345,7 +345,7 @@
post_body, self.headers)
def attach_volume(self, server_id, volume_id, device='/dev/vdz'):
- """Attaches a volume to a server instance"""
+ """Attaches a volume to a server instance."""
post_body = json.dumps({
'volumeAttachment': {
'volumeId': volume_id,
@@ -357,13 +357,13 @@
return resp, body
def detach_volume(self, server_id, volume_id):
- """Detaches a volume from a server instance"""
+ """Detaches a volume from a server instance."""
resp, body = self.delete('servers/%s/os-volume_attachments/%s' %
(server_id, volume_id))
return resp, body
def add_security_group(self, server_id, security_group_name):
- """Adds a security group to the server"""
+ """Adds a security group to the server."""
post_body = {
'addSecurityGroup': {
'name': security_group_name
@@ -374,7 +374,7 @@
post_body, self.headers)
def remove_security_group(self, server_id, security_group_name):
- """Removes a security group from the server"""
+ """Removes a security group from the server."""
post_body = {
'removeSecurityGroup': {
'name': security_group_name
@@ -385,7 +385,7 @@
post_body, self.headers)
def live_migrate_server(self, server_id, dest_host, use_block_migration):
- """ This should be called with administrator privileges """
+ """This should be called with administrator privileges ."""
migrate_params = {
"disk_over_commit": False,
@@ -409,7 +409,7 @@
return resp, body['servers']
def migrate_server(self, server_id):
- """Migrates a server to a new host"""
+ """Migrates a server to a new host."""
post_body = {'migrate': 'null'}
post_body = json.dumps(post_body)
resp, body = self.post('servers/%s/action' % server_id,
@@ -417,7 +417,7 @@
return resp, body
def confirm_migration(self, server_id):
- """Confirms the migration of a server"""
+ """Confirms the migration of a server."""
post_body = {'confirmResize': 'null'}
post_body = json.dumps(post_body)
resp, body = self.post('servers/%s/action' % server_id,
@@ -425,63 +425,63 @@
return resp, body
def lock_server(self, server_id):
- """Locks the given server"""
+ """Locks the given server."""
post_body = {'lock': 'null'}
post_body = json.dumps(post_body)
resp, body = self.post('servers/%s/action' % server_id,
post_body, self.headers)
def unlock_server(self, server_id):
- """UNlocks the given server"""
+ """UNlocks the given server."""
post_body = {'unlock': 'null'}
post_body = json.dumps(post_body)
resp, body = self.post('servers/%s/action' % server_id,
post_body, self.headers)
def start_server(self, server_id):
- """Starts the given server"""
+ """Starts the given server."""
post_body = {'os-start': 'null'}
post_body = json.dumps(post_body)
resp, body = self.post('servers/%s/action' % server_id,
post_body, self.headers)
def stop_server(self, server_id):
- """Stops the given server"""
+ """Stops the given server."""
post_body = {'os-stop': 'null'}
post_body = json.dumps(post_body)
resp, body = self.post('servers/%s/action' % server_id,
post_body, self.headers)
def suspend_server(self, server_id):
- """Suspends the provded server"""
+ """Suspends the provded server."""
post_body = {'suspend': 'null'}
post_body = json.dumps(post_body)
resp, body = self.post('servers/%s/action' % server_id,
post_body, self.headers)
def resume_server(self, server_id):
- """Un-suspends the provded server"""
+ """Un-suspends the provded server."""
post_body = {'resume': 'null'}
post_body = json.dumps(post_body)
resp, body = self.post('servers/%s/action' % server_id,
post_body, self.headers)
def pause_server(self, server_id):
- """Pauses the provded server"""
+ """Pauses the provded server."""
post_body = {'pause': 'null'}
post_body = json.dumps(post_body)
resp, body = self.post('servers/%s/action' % server_id,
post_body, self.headers)
def unpause_server(self, server_id):
- """Un-pauses the provded server"""
+ """Un-pauses the provded server."""
post_body = {'unpause': 'null'}
post_body = json.dumps(post_body)
resp, body = self.post('servers/%s/action' % server_id,
post_body, self.headers)
def reset_state(self, server_id, new_state='error'):
- """Resets the state of a server to active/error"""
+ """Resets the state of a server to active/error."""
post_body = {
'os-resetState': {
'state': new_state
diff --git a/tempest/services/compute/json/volumes_extensions_client.py b/tempest/services/compute/json/volumes_extensions_client.py
index 6cf6c23..a5f6ec3 100644
--- a/tempest/services/compute/json/volumes_extensions_client.py
+++ b/tempest/services/compute/json/volumes_extensions_client.py
@@ -34,7 +34,7 @@
self.build_timeout = self.config.volume.build_timeout
def list_volumes(self, params=None):
- """List all the volumes created"""
+ """List all the volumes created."""
url = 'os-volumes'
if params:
url += '?%s' % urllib.urlencode(params)
@@ -44,7 +44,7 @@
return resp, body['volumes']
def list_volumes_with_detail(self, params=None):
- """List all the details of volumes"""
+ """List all the details of volumes."""
url = 'os-volumes/detail'
if params:
url += '?%s' % urllib.urlencode(params)
@@ -54,7 +54,7 @@
return resp, body['volumes']
def get_volume(self, volume_id, wait=None):
- """Returns the details of a single volume"""
+ """Returns the details of a single volume."""
url = "os-volumes/%s" % str(volume_id)
resp, body = self.get(url, wait=wait)
body = json.loads(body)
@@ -80,11 +80,11 @@
return resp, body['volume']
def delete_volume(self, volume_id):
- """Deletes the Specified Volume"""
+ """Deletes the Specified Volume."""
return self.delete("os-volumes/%s" % str(volume_id))
def wait_for_volume_status(self, volume_id, status):
- """Waits for a Volume to reach a given status"""
+ """Waits for a Volume to reach a given status."""
resp, body = self.get_volume(volume_id)
volume_name = body['displayName']
volume_status = body['status']
diff --git a/tempest/services/compute/xml/flavors_client.py b/tempest/services/compute/xml/flavors_client.py
index 1dc34a5..c011fe4 100644
--- a/tempest/services/compute/xml/flavors_client.py
+++ b/tempest/services/compute/xml/flavors_client.py
@@ -27,7 +27,9 @@
XMLNS_OS_FLV_EXT_DATA = \
- "http://docs.openstack.org/compute/ext/flavor_extra_data/api/v1.1"
+ "http://docs.openstack.org/compute/ext/flavor_extra_data/api/v1.1"
+XMLNS_OS_FLV_ACCESS = \
+ "http://docs.openstack.org/compute/ext/flavor_access/api/v1.1"
class FlavorsClientXML(RestClientXML):
@@ -84,26 +86,42 @@
flavor = self._format_flavor(body)
return resp, flavor
- def create_flavor(self, name, ram, vcpus, disk, ephemeral, flavor_id,
- swap, rxtx):
- """Creates a new flavor or instance type"""
+ def create_flavor(self, name, ram, vcpus, disk, flavor_id, **kwargs):
+ """Creates a new flavor or instance type."""
flavor = Element("flavor",
xmlns=XMLNS_11,
ram=ram,
vcpus=vcpus,
disk=disk,
id=flavor_id,
- swap=swap,
- rxtx_factor=rxtx,
name=name)
+ if kwargs.get('rxtx'):
+ flavor.add_attr('rxtx_factor', kwargs.get('rxtx'))
+ if kwargs.get('swap'):
+ flavor.add_attr('swap', kwargs.get('swap'))
+ if kwargs.get('ephemeral'):
+ flavor.add_attr('OS-FLV-EXT-DATA:ephemeral',
+ kwargs.get('ephemeral'))
+ if kwargs.get('is_public'):
+ flavor.add_attr('os-flavor-access:is_public',
+ kwargs.get('is_public'))
flavor.add_attr('xmlns:OS-FLV-EXT-DATA', XMLNS_OS_FLV_EXT_DATA)
- flavor.add_attr('OS-FLV-EXT-DATA:ephemeral', ephemeral)
-
+ flavor.add_attr('xmlns:os-flavor-access', XMLNS_OS_FLV_ACCESS)
resp, body = self.post('flavors', str(Document(flavor)), self.headers)
body = xml_to_json(etree.fromstring(body))
flavor = self._format_flavor(body)
return resp, flavor
def delete_flavor(self, flavor_id):
- """Deletes the given flavor"""
+ """Deletes the given flavor."""
return self.delete("flavors/%s" % str(flavor_id), self.headers)
+
+ def is_resource_deleted(self, id):
+ #Did not use get_flavor_details(id) for verification as it gives
+ #200 ok even for deleted id. LP #981263
+ #we can remove the loop here and use get by ID when bug gets sortedout
+ resp, flavors = self.list_flavors_with_detail()
+ for flavor in flavors:
+ if flavor['id'] == id:
+ return False
+ return True
diff --git a/tempest/services/compute/xml/floating_ips_client.py b/tempest/services/compute/xml/floating_ips_client.py
index a0059a8..74b4be2 100644
--- a/tempest/services/compute/xml/floating_ips_client.py
+++ b/tempest/services/compute/xml/floating_ips_client.py
@@ -42,7 +42,7 @@
return json
def list_floating_ips(self, params=None):
- """Returns a list of all floating IPs filtered by any parameters"""
+ """Returns a list of all floating IPs filtered by any parameters."""
url = 'os-floating-ips'
if params:
url += '?%s' % urllib.urlencode(params)
@@ -52,7 +52,7 @@
return resp, body
def get_floating_ip_details(self, floating_ip_id):
- """Get the details of a floating IP"""
+ """Get the details of a floating IP."""
url = "os-floating-ips/%s" % str(floating_ip_id)
resp, body = self.get(url, self.headers)
body = self._parse_floating_ip(etree.fromstring(body))
@@ -61,20 +61,20 @@
return resp, body
def create_floating_ip(self):
- """Allocate a floating IP to the project"""
+ """Allocate a floating IP to the project."""
url = 'os-floating-ips'
resp, body = self.post(url, None, self.headers)
body = self._parse_floating_ip(etree.fromstring(body))
return resp, body
def delete_floating_ip(self, floating_ip_id):
- """Deletes the provided floating IP from the project"""
+ """Deletes the provided floating IP from the project."""
url = "os-floating-ips/%s" % str(floating_ip_id)
resp, body = self.delete(url, self.headers)
return resp, body
def associate_floating_ip_to_server(self, floating_ip, server_id):
- """Associate the provided floating IP to a specific server"""
+ """Associate the provided floating IP to a specific server."""
url = "servers/%s/action" % str(server_id)
doc = Document()
server = Element("addFloatingIp")
@@ -84,7 +84,7 @@
return resp, body
def disassociate_floating_ip_from_server(self, floating_ip, server_id):
- """Disassociate the provided floating IP from a specific server"""
+ """Disassociate the provided floating IP from a specific server."""
url = "servers/%s/action" % str(server_id)
doc = Document()
server = Element("removeFloatingIp")
diff --git a/tempest/services/compute/xml/images_client.py b/tempest/services/compute/xml/images_client.py
index efed3fb..bde9e16 100644
--- a/tempest/services/compute/xml/images_client.py
+++ b/tempest/services/compute/xml/images_client.py
@@ -43,7 +43,7 @@
return self._parse_links(node, json)
def _parse_image(self, node):
- """Parses detailed XML image information into dictionary"""
+ """Parses detailed XML image information into dictionary."""
json = xml_to_json(node)
self._parse_links(node, json)
@@ -61,7 +61,7 @@
return json
def _parse_links(self, node, json):
- """Append multiple links under a list"""
+ """Append multiple links under a list."""
# look for links
if 'link' in json:
# remove single link element
@@ -70,8 +70,15 @@
node.findall('{http://www.w3.org/2005/Atom}link')]
return json
+ def _parse_images(self, xml):
+ json = {'images': []}
+ images = xml.getchildren()
+ for image in images:
+ json['images'].append(self._parse_image(image))
+ return json
+
def create_image(self, server_id, name, meta=None):
- """Creates an image of the original server"""
+ """Creates an image of the original server."""
post_body = Element('createImage', name=name)
if meta:
@@ -86,17 +93,17 @@
return resp, body
def list_images(self, params=None):
- """Returns a list of all images filtered by any parameters"""
+ """Returns a list of all images filtered by any parameters."""
url = 'images'
if params:
url += '?%s' % urllib.urlencode(params)
resp, body = self.get(url, self.headers)
- body = xml_to_json(etree.fromstring(body))
+ body = self._parse_images(etree.fromstring(body))
return resp, body['images']
def list_images_with_detail(self, params=None):
- """Returns a detailed list of images filtered by any parameters"""
+ """Returns a detailed list of images filtered by any parameters."""
url = 'images/detail'
if params:
param_list = urllib.urlencode(params)
@@ -104,17 +111,17 @@
url = "images/detail?" + param_list
resp, body = self.get(url, self.headers)
- body = xml_to_json(etree.fromstring(body))
+ body = self._parse_images(etree.fromstring(body))
return resp, body['images']
def get_image(self, image_id):
- """Returns the details of a single image"""
+ """Returns the details of a single image."""
resp, body = self.get("images/%s" % str(image_id), self.headers)
body = self._parse_image(etree.fromstring(body))
return resp, body
def delete_image(self, image_id):
- """Deletes the provided image"""
+ """Deletes the provided image."""
return self.delete("images/%s" % str(image_id), self.headers)
def wait_for_image_resp_code(self, image_id, code):
@@ -147,14 +154,14 @@
raise exceptions.TimeoutException
def list_image_metadata(self, image_id):
- """Lists all metadata items for an image"""
+ """Lists all metadata items for an image."""
resp, body = self.get("images/%s/metadata" % str(image_id),
self.headers)
body = xml_to_json(etree.fromstring(body))
return resp, body['metadata']
def set_image_metadata(self, image_id, meta):
- """Sets the metadata for an image"""
+ """Sets the metadata for an image."""
post_body = json.dumps({'metadata': meta})
resp, body = self.put('images/%s/metadata' % str(image_id),
post_body, self.headers)
@@ -162,7 +169,7 @@
return resp, body['metadata']
def update_image_metadata(self, image_id, meta):
- """Updates the metadata for an image"""
+ """Updates the metadata for an image."""
post_body = Element('metadata', meta)
for k, v in meta:
metadata = Element('meta', key=k)
@@ -176,14 +183,14 @@
return resp, body['metadata']
def get_image_metadata_item(self, image_id, key):
- """Returns the value for a specific image metadata key"""
+ """Returns the value for a specific image metadata key."""
resp, body = self.get("images/%s/metadata/%s.xml" %
(str(image_id), key), self.headers)
body = xml_to_json(etree.fromstring(body))
return resp, body['meta']
def set_image_metadata_item(self, image_id, key, meta):
- """Sets the value for a specific image metadata key"""
+ """Sets the value for a specific image metadata key."""
post_body = json.dumps({'meta': meta})
resp, body = self.put('images/%s/metadata/%s' % (str(image_id), key),
post_body, self.headers)
@@ -191,7 +198,7 @@
return resp, body['meta']
def delete_image_metadata_item(self, image_id, key):
- """Deletes a single image metadata key/value pair"""
+ """Deletes a single image metadata key/value pair."""
resp, body = self.delete("images/%s/metadata/%s" % (str(image_id), key,
self.headers))
return resp, body
diff --git a/tempest/services/compute/xml/security_groups_client.py b/tempest/services/compute/xml/security_groups_client.py
index bfd6c7a..ac70f1b 100644
--- a/tempest/services/compute/xml/security_groups_client.py
+++ b/tempest/services/compute/xml/security_groups_client.py
@@ -44,7 +44,7 @@
return json
def list_security_groups(self, params=None):
- """List all security groups for a user"""
+ """List all security groups for a user."""
url = 'os-security-groups'
if params:
@@ -55,7 +55,7 @@
return resp, body
def get_security_group(self, security_group_id):
- """Get the details of a Security Group"""
+ """Get the details of a Security Group."""
url = "os-security-groups/%s" % str(security_group_id)
resp, body = self.get(url, self.headers)
body = self._parse_body(etree.fromstring(body))
@@ -78,7 +78,7 @@
return resp, body
def delete_security_group(self, security_group_id):
- """Deletes the provided Security Group"""
+ """Deletes the provided Security Group."""
return self.delete('os-security-groups/%s' %
str(security_group_id), self.headers)
@@ -125,6 +125,6 @@
return resp, body
def delete_security_group_rule(self, group_rule_id):
- """Deletes the provided Security Group rule"""
+ """Deletes the provided Security Group rule."""
return self.delete('os-security-group-rules/%s' %
str(group_rule_id), self.headers)
diff --git a/tempest/services/compute/xml/servers_client.py b/tempest/services/compute/xml/servers_client.py
index b33335d..4a84646 100644
--- a/tempest/services/compute/xml/servers_client.py
+++ b/tempest/services/compute/xml/servers_client.py
@@ -33,6 +33,69 @@
LOG = logging.getLogger(__name__)
+def _translate_ip_xml_json(ip):
+ """
+ Convert the address version to int.
+ """
+ ip = dict(ip)
+ version = ip.get('version')
+ if version:
+ ip['version'] = int(version)
+ return ip
+
+
+def _translate_network_xml_to_json(network):
+ return [_translate_ip_xml_json(ip.attrib)
+ for ip in network.findall('{%s}ip' % XMLNS_11)]
+
+
+def _translate_addresses_xml_to_json(xml_addresses):
+ return dict((network.attrib['id'], _translate_network_xml_to_json(network))
+ for network in xml_addresses.findall('{%s}network' % XMLNS_11))
+
+
+def _translate_server_xml_to_json(xml_dom):
+ """ Convert server XML to server JSON.
+
+ The addresses collection does not convert well by the dumb xml_to_json.
+ This method does some pre and post-processing to deal with that.
+
+ Translate XML addresses subtree to JSON.
+
+ Having xml_doc similar to
+ <api:server xmlns:api="http://docs.openstack.org/compute/api/v1.1">
+ <api:addresses>
+ <api:network id="foo_novanetwork">
+ <api:ip version="4" addr="192.168.0.4"/>
+ </api:network>
+ <api:network id="bar_novanetwork">
+ <api:ip version="4" addr="10.1.0.4"/>
+ <api:ip version="6" addr="2001:0:0:1:2:3:4:5"/>
+ </api:network>
+ </api:addresses>
+ </api:server>
+
+ the _translate_server_xml_to_json(etree.fromstring(xml_doc)) should produce
+ something like
+
+ {'addresses': {'bar_novanetwork': [{'addr': '10.1.0.4', 'version': 4},
+ {'addr': '2001:0:0:1:2:3:4:5',
+ 'version': 6}],
+ 'foo_novanetwork': [{'addr': '192.168.0.4', 'version': 4}]}}
+ """
+ nsmap = {'api': XMLNS_11}
+ addresses = xml_dom.xpath('/api:server/api:addresses', namespaces=nsmap)
+ if addresses:
+ if len(addresses) > 1:
+ raise ValueError('Expected only single `addresses` element.')
+ json_addresses = _translate_addresses_xml_to_json(addresses[0])
+ json = xml_to_json(xml_dom)
+ json['addresses'] = json_addresses
+ else:
+ json = xml_to_json(xml_dom)
+ return json
+
+
class ServersClientXML(RestClientXML):
def __init__(self, config, username, password, auth_url, tenant_name=None):
@@ -41,7 +104,7 @@
self.service = self.config.compute.catalog_type
def _parse_key_value(self, node):
- """Parse <foo key='key'>value</foo> data into {'key': 'value'}"""
+ """Parse <foo key='key'>value</foo> data into {'key': 'value'}."""
data = {}
for node in node.getchildren():
data[node.get('key')] = node.text
@@ -54,7 +117,8 @@
json['links'].append(xml_to_json(linknode))
def _parse_server(self, body):
- json = xml_to_json(body)
+ json = _translate_server_xml_to_json(body)
+
if 'metadata' in json and json['metadata']:
# NOTE(danms): if there was metadata, we need to re-parse
# that as a special type
@@ -65,17 +129,16 @@
for sub in ['image', 'flavor']:
if sub in json and 'link' in json[sub]:
self._parse_links(body, json[sub])
-
return json
def get_server(self, server_id):
- """Returns the details of an existing server"""
+ """Returns the details of an existing server."""
resp, body = self.get("servers/%s" % str(server_id), self.headers)
server = self._parse_server(etree.fromstring(body))
return resp, server
def delete_server(self, server_id):
- """Deletes the given server"""
+ """Deletes the given server."""
return self.delete("servers/%s" % str(server_id))
def _parse_array(self, node):
@@ -179,7 +242,7 @@
return resp, server
def wait_for_server_status(self, server_id, status):
- """Waits for a server to reach a given status"""
+ """Waits for a server to reach a given status."""
resp, body = self.get_server(server_id)
server_status = body['status']
start = int(time.time())
@@ -202,7 +265,7 @@
raise exceptions.TimeoutException(message)
def wait_for_server_termination(self, server_id, ignore_error=False):
- """Waits for server to reach termination"""
+ """Waits for server to reach termination."""
start_time = int(time.time())
while True:
try:
@@ -227,7 +290,7 @@
return {node.get('id'): addrs}
def list_addresses(self, server_id):
- """Lists all addresses for a server"""
+ """Lists all addresses for a server."""
resp, body = self.get("servers/%s/ips" % str(server_id), self.headers)
networks = {}
@@ -238,7 +301,7 @@
return resp, networks
def list_addresses_by_network(self, server_id, network_id):
- """Lists all addresses of a specific network type for a server"""
+ """Lists all addresses of a specific network type for a server."""
resp, body = self.get("servers/%s/ips/%s" % (str(server_id),
network_id),
self.headers)
diff --git a/tempest/services/compute/xml/volumes_extensions_client.py b/tempest/services/compute/xml/volumes_extensions_client.py
index 1e8c738..60ef398 100644
--- a/tempest/services/compute/xml/volumes_extensions_client.py
+++ b/tempest/services/compute/xml/volumes_extensions_client.py
@@ -54,7 +54,7 @@
return vol
def list_volumes(self, params=None):
- """List all the volumes created"""
+ """List all the volumes created."""
url = 'os-volumes'
if params:
@@ -68,7 +68,7 @@
return resp, volumes
def list_volumes_with_detail(self, params=None):
- """List all the details of volumes"""
+ """List all the details of volumes."""
url = 'os-volumes/detail'
if params:
@@ -82,7 +82,7 @@
return resp, volumes
def get_volume(self, volume_id, wait=None):
- """Returns the details of a single volume"""
+ """Returns the details of a single volume."""
url = "os-volumes/%s" % str(volume_id)
resp, body = self.get(url, self.headers, wait=wait)
body = etree.fromstring(body)
@@ -116,11 +116,11 @@
return resp, body
def delete_volume(self, volume_id):
- """Deletes the Specified Volume"""
+ """Deletes the Specified Volume."""
return self.delete("os-volumes/%s" % str(volume_id))
def wait_for_volume_status(self, volume_id, status):
- """Waits for a Volume to reach a given status"""
+ """Waits for a Volume to reach a given status."""
resp, body = self.get_volume(volume_id)
volume_name = body['displayName']
volume_status = body['status']
diff --git a/tempest/services/identity/json/admin_client.py b/tempest/services/identity/json/admin_client.py
index 7ea33b5..c4e6c95 100644
--- a/tempest/services/identity/json/admin_client.py
+++ b/tempest/services/identity/json/admin_client.py
@@ -25,7 +25,7 @@
return self._has_admin_extensions
def create_role(self, name):
- """Create a role"""
+ """Create a role."""
post_body = {
'name': name,
}
@@ -52,19 +52,19 @@
return resp, body['tenant']
def delete_role(self, role_id):
- """Delete a role"""
+ """Delete a role."""
resp, body = self.delete('OS-KSADM/roles/%s' % str(role_id))
return resp, body
def list_user_roles(self, tenant_id, user_id):
- """Returns a list of roles assigned to a user for a tenant"""
+ """Returns a list of roles assigned to a user for a tenant."""
url = '/tenants/%s/users/%s/roles' % (tenant_id, user_id)
resp, body = self.get(url)
body = json.loads(body)
return resp, body['roles']
def assign_user_role(self, tenant_id, user_id, role_id):
- """Add roles to a user on a tenant"""
+ """Add roles to a user on a tenant."""
post_body = json.dumps({})
resp, body = self.put('/tenants/%s/users/%s/roles/OS-KSADM/%s' %
(tenant_id, user_id, role_id), post_body,
@@ -73,29 +73,29 @@
return resp, body['role']
def remove_user_role(self, tenant_id, user_id, role_id):
- """Removes a role assignment for a user on a tenant"""
+ """Removes a role assignment for a user on a tenant."""
return self.delete('/tenants/%s/users/%s/roles/OS-KSADM/%s' %
(tenant_id, user_id, role_id))
def delete_tenant(self, tenant_id):
- """Delete a tenant"""
+ """Delete a tenant."""
resp, body = self.delete('tenants/%s' % str(tenant_id))
return resp, body
def get_tenant(self, tenant_id):
- """Get tenant details"""
+ """Get tenant details."""
resp, body = self.get('tenants/%s' % str(tenant_id))
body = json.loads(body)
return resp, body['tenant']
def list_roles(self):
- """Returns roles"""
+ """Returns roles."""
resp, body = self.get('OS-KSADM/roles')
body = json.loads(body)
return resp, body['roles']
def list_tenants(self):
- """Returns tenants"""
+ """Returns tenants."""
resp, body = self.get('tenants')
body = json.loads(body)
return resp, body['tenants']
@@ -108,7 +108,7 @@
raise exceptions.NotFound('No such tenant')
def update_tenant(self, tenant_id, **kwargs):
- """Updates a tenant"""
+ """Updates a tenant."""
resp, body = self.get_tenant(tenant_id)
name = kwargs.get('name', body['name'])
desc = kwargs.get('description', body['description'])
@@ -126,7 +126,7 @@
return resp, body['tenant']
def create_user(self, name, password, tenant_id, email):
- """Create a user"""
+ """Create a user."""
post_body = {
'name': name,
'password': password,
@@ -139,18 +139,18 @@
return resp, body['user']
def delete_user(self, user_id):
- """Delete a user"""
+ """Delete a user."""
resp, body = self.delete("users/%s" % user_id)
return resp, body
def get_users(self):
- """Get the list of users"""
+ """Get the list of users."""
resp, body = self.get("users")
body = json.loads(body)
return resp, body['users']
def enable_disable_user(self, user_id, enabled):
- """Enables or disables a user"""
+ """Enables or disables a user."""
put_body = {
'enabled': enabled
}
@@ -161,12 +161,12 @@
return resp, body
def delete_token(self, token_id):
- """Delete a token"""
+ """Delete a token."""
resp, body = self.delete("tokens/%s" % token_id)
return resp, body
def list_users_for_tenant(self, tenant_id):
- """List users for a Tenant"""
+ """List users for a Tenant."""
resp, body = self.get('/tenants/%s/users' % tenant_id)
body = json.loads(body)
return resp, body['users']
@@ -179,7 +179,7 @@
raise exceptions.NotFound('No such user')
def create_service(self, name, type, **kwargs):
- """Create a service"""
+ """Create a service."""
post_body = {
'name': name,
'type': type,
@@ -191,14 +191,14 @@
return resp, body['OS-KSADM:service']
def get_service(self, service_id):
- """Get Service"""
+ """Get Service."""
url = '/OS-KSADM/services/%s' % service_id
resp, body = self.get(url)
body = json.loads(body)
return resp, body['OS-KSADM:service']
def delete_service(self, service_id):
- """Delete Service"""
+ """Delete Service."""
url = '/OS-KSADM/services/%s' % service_id
return self.delete(url)
diff --git a/tempest/services/identity/xml/admin_client.py b/tempest/services/identity/xml/admin_client.py
index 3a947b6..60897e9 100644
--- a/tempest/services/identity/xml/admin_client.py
+++ b/tempest/services/identity/xml/admin_client.py
@@ -63,7 +63,7 @@
return self._has_admin_extensions
def create_role(self, name):
- """Create a role"""
+ """Create a role."""
create_role = Element("role", xmlns=XMLNS, name=name)
resp, body = self.post('OS-KSADM/roles', str(Document(create_role)),
self.headers)
@@ -89,55 +89,62 @@
return resp, body
def delete_role(self, role_id):
- """Delete a role"""
+ """Delete a role."""
resp, body = self.delete('OS-KSADM/roles/%s' % str(role_id),
self.headers)
return resp, body
def list_user_roles(self, tenant_id, user_id):
- """Returns a list of roles assigned to a user for a tenant"""
+ """Returns a list of roles assigned to a user for a tenant."""
url = '/tenants/%s/users/%s/roles' % (tenant_id, user_id)
resp, body = self.get(url, self.headers)
body = self._parse_array(etree.fromstring(body))
return resp, body
def assign_user_role(self, tenant_id, user_id, role_id):
- """Add roles to a user on a tenant"""
+ """Add roles to a user on a tenant."""
resp, body = self.put('/tenants/%s/users/%s/roles/OS-KSADM/%s' %
(tenant_id, user_id, role_id), '', self.headers)
body = self._parse_body(etree.fromstring(body))
return resp, body
def remove_user_role(self, tenant_id, user_id, role_id):
- """Removes a role assignment for a user on a tenant"""
+ """Removes a role assignment for a user on a tenant."""
return self.delete('/tenants/%s/users/%s/roles/OS-KSADM/%s' %
(tenant_id, user_id, role_id), self.headers)
def delete_tenant(self, tenant_id):
- """Delete a tenant"""
+ """Delete a tenant."""
resp, body = self.delete('tenants/%s' % str(tenant_id), self.headers)
return resp, body
def get_tenant(self, tenant_id):
- """Get tenant details"""
+ """Get tenant details."""
resp, body = self.get('tenants/%s' % str(tenant_id), self.headers)
body = self._parse_body(etree.fromstring(body))
return resp, body
def list_roles(self):
- """Returns roles"""
+ """Returns roles."""
resp, body = self.get('OS-KSADM/roles', self.headers)
body = self._parse_array(etree.fromstring(body))
return resp, body
def list_tenants(self):
- """Returns tenants"""
+ """Returns tenants."""
resp, body = self.get('tenants', self.headers)
body = self._parse_array(etree.fromstring(body))
return resp, body
+ def get_tenant_by_name(self, tenant_name):
+ resp, tenants = self.list_tenants()
+ for tenant in tenants:
+ if tenant['name'] == tenant_name:
+ return tenant
+ raise exceptions.NotFound('No such tenant')
+
def update_tenant(self, tenant_id, **kwargs):
- """Updates a tenant"""
+ """Updates a tenant."""
resp, body = self.get_tenant(tenant_id)
name = kwargs.get('name', body['name'])
desc = kwargs.get('description', body['description'])
@@ -156,7 +163,7 @@
return resp, body
def create_user(self, name, password, tenant_id, email):
- """Create a user"""
+ """Create a user."""
create_user = Element("user",
xmlns=XMLNS,
name=name,
@@ -169,18 +176,18 @@
return resp, body
def delete_user(self, user_id):
- """Delete a user"""
+ """Delete a user."""
resp, body = self.delete("users/%s" % user_id, self.headers)
return resp, body
def get_users(self):
- """Get the list of users"""
+ """Get the list of users."""
resp, body = self.get("users", self.headers)
body = self._parse_array(etree.fromstring(body))
return resp, body
def enable_disable_user(self, user_id, enabled):
- """Enables or disables a user"""
+ """Enables or disables a user."""
enable_user = Element("user", enabled=str(enabled).lower())
resp, body = self.put('users/%s/enabled' % user_id,
str(Document(enable_user)), self.headers)
@@ -188,18 +195,25 @@
return resp, body
def delete_token(self, token_id):
- """Delete a token"""
+ """Delete a token."""
resp, body = self.delete("tokens/%s" % token_id, self.headers)
return resp, body
def list_users_for_tenant(self, tenant_id):
- """List users for a Tenant"""
+ """List users for a Tenant."""
resp, body = self.get('/tenants/%s/users' % tenant_id, self.headers)
body = self._parse_array(etree.fromstring(body))
return resp, body
+ def get_user_by_username(self, tenant_id, username):
+ resp, users = self.list_users_for_tenant(tenant_id)
+ for user in users:
+ if user['name'] == username:
+ return user
+ raise exceptions.NotFound('No such user')
+
def create_service(self, name, type, **kwargs):
- """Create a service"""
+ """Create a service."""
OS_KSADM = "http://docs.openstack.org/identity/api/ext/OS-KSADM/v1.0"
create_service = Element("service",
xmlns=OS_KSADM,
@@ -213,14 +227,14 @@
return resp, body
def get_service(self, service_id):
- """Get Service"""
+ """Get Service."""
url = '/OS-KSADM/services/%s' % service_id
resp, body = self.get(url, self.headers)
body = self._parse_body(etree.fromstring(body))
return resp, body
def delete_service(self, service_id):
- """Delete Service"""
+ """Delete Service."""
url = '/OS-KSADM/services/%s' % service_id
return self.delete(url, self.headers)
diff --git a/tempest/services/object_storage/account_client.py b/tempest/services/object_storage/account_client.py
index 0ab5cd4..26f8329 100644
--- a/tempest/services/object_storage/account_client.py
+++ b/tempest/services/object_storage/account_client.py
@@ -40,7 +40,7 @@
def create_account_metadata(self, metadata,
metadata_prefix='X-Account-Meta-'):
- """Creates an account metadata entry"""
+ """Creates an account metadata entry."""
headers = {}
for key in metadata:
headers[metadata_prefix + key] = metadata[key]
diff --git a/tempest/services/object_storage/container_client.py b/tempest/services/object_storage/container_client.py
index 6b5342a..7b5efff 100644
--- a/tempest/services/object_storage/container_client.py
+++ b/tempest/services/object_storage/container_client.py
@@ -48,14 +48,14 @@
return resp, body
def delete_container(self, container_name):
- """Deletes the container (if it's empty)"""
+ """Deletes the container (if it's empty)."""
url = str(container_name)
resp, body = self.delete(url)
return resp, body
def update_container_metadata(self, container_name, metadata,
metadata_prefix='X-Container-Meta-'):
- """Updates arbitrary metadata on container"""
+ """Updates arbitrary metadata on container."""
url = str(container_name)
headers = {}
@@ -68,7 +68,7 @@
def delete_container_metadata(self, container_name, metadata,
metadata_prefix='X-Remove-Container-Meta-'):
- """Deletes arbitrary metadata on container"""
+ """Deletes arbitrary metadata on container."""
url = str(container_name)
headers = {}
diff --git a/tempest/services/object_storage/object_client.py b/tempest/services/object_storage/object_client.py
index 0047b50..c05c905 100644
--- a/tempest/services/object_storage/object_client.py
+++ b/tempest/services/object_storage/object_client.py
@@ -30,25 +30,25 @@
self.service = self.config.object_storage.catalog_type
def create_object(self, container, object_name, data):
- """Create storage object"""
+ """Create storage object."""
url = "%s/%s" % (str(container), str(object_name))
resp, body = self.put(url, data, self.headers)
return resp, body
def update_object(self, container, object_name, data):
- """Upload data to replace current storage object"""
+ """Upload data to replace current storage object."""
return create_object(container, object_name, data)
def delete_object(self, container, object_name):
- """Delete storage object"""
+ """Delete storage object."""
url = "%s/%s" % (str(container), str(object_name))
resp, body = self.delete(url)
return resp, body
def update_object_metadata(self, container, object_name, metadata,
metadata_prefix='X-Object-Meta-'):
- """Add, remove, or change X-Object-Meta metadata for storage object"""
+ """Add, remove, or change X-Object-Meta metadata for storage object."""
headers = {}
for key in metadata:
@@ -59,7 +59,7 @@
return resp, body
def list_object_metadata(self, container, object_name):
- """List all storage object X-Object-Meta- metadata"""
+ """List all storage object X-Object-Meta- metadata."""
url = "%s/%s" % (str(container), str(object_name))
resp, body = self.head(url)
@@ -74,7 +74,7 @@
def copy_object_in_same_container(self, container, src_object_name,
dest_object_name, metadata=None):
- """Copy storage object's data to the new object using PUT"""
+ """Copy storage object's data to the new object using PUT."""
url = "{0}/{1}".format(container, dest_object_name)
headers = {}
@@ -91,7 +91,7 @@
def copy_object_across_containers(self, src_container, src_object_name,
dst_container, dst_object_name,
metadata=None):
- """Copy storage object's data to the new object using PUT"""
+ """Copy storage object's data to the new object using PUT."""
url = "{0}/{1}".format(dst_container, dst_object_name)
headers = {}
@@ -107,7 +107,7 @@
def copy_object_2d_way(self, container, src_object_name, dest_object_name,
metadata=None):
- """Copy storage object's data to the new object using COPY"""
+ """Copy storage object's data to the new object using COPY."""
url = "{0}/{1}".format(container, src_object_name)
headers = {}
@@ -161,7 +161,7 @@
return resp, body
def create_object(self, container, object_name, data, metadata=None):
- """Create storage object"""
+ """Create storage object."""
headers = {}
if metadata:
@@ -173,7 +173,7 @@
return resp, body
def delete_object(self, container, object_name):
- """Delete storage object"""
+ """Delete storage object."""
url = "%s/%s" % (str(container), str(object_name))
resp, body = self.delete(url)
diff --git a/tempest/services/volume/json/admin/volume_types_client.py b/tempest/services/volume/json/admin/volume_types_client.py
index fc8897f..0cadcb5 100644
--- a/tempest/services/volume/json/admin/volume_types_client.py
+++ b/tempest/services/volume/json/admin/volume_types_client.py
@@ -35,7 +35,7 @@
self.build_timeout = self.config.volume.build_timeout
def list_volume_types(self, params=None):
- """List all the volume_types created"""
+ """List all the volume_types created."""
url = 'types'
if params is not None:
url += '?%s' % urllib.urlencode(params)
@@ -45,7 +45,7 @@
return resp, body['volume_types']
def get_volume_type(self, volume_id):
- """Returns the details of a single volume_type"""
+ """Returns the details of a single volume_type."""
url = "types/%s" % str(volume_id)
resp, body = self.get(url)
body = json.loads(body)
@@ -69,11 +69,11 @@
return resp, body['volume_type']
def delete_volume_type(self, volume_id):
- """Deletes the Specified Volume_type"""
+ """Deletes the Specified Volume_type."""
return self.delete("types/%s" % str(volume_id))
def list_volume_types_extra_specs(self, vol_type_id, params=None):
- """List all the volume_types extra specs created"""
+ """List all the volume_types extra specs created."""
url = 'types/%s/extra_specs' % str(vol_type_id)
if params is not None:
url += '?%s' % urllib.urlencode(params)
@@ -83,7 +83,7 @@
return resp, body['extra_specs']
def get_volume_type_extra_specs(self, vol_type_id, extra_spec_name):
- """Returns the details of a single volume_type extra spec"""
+ """Returns the details of a single volume_type extra spec."""
url = "types/%s/extra_specs/%s" % (str(vol_type_id),
str(extra_spec_name))
resp, body = self.get(url)
@@ -103,7 +103,7 @@
return resp, body['extra_specs']
def delete_volume_type_extra_specs(self, vol_id, extra_spec_name):
- """Deletes the Specified Volume_type extra spec"""
+ """Deletes the Specified Volume_type extra spec."""
return self.delete("types/%s/extra_specs/%s" % ((str(vol_id)),
str(extra_spec_name)))
diff --git a/tempest/services/volume/json/volumes_client.py b/tempest/services/volume/json/volumes_client.py
index 962fe72..cc5a115 100644
--- a/tempest/services/volume/json/volumes_client.py
+++ b/tempest/services/volume/json/volumes_client.py
@@ -37,7 +37,7 @@
self.build_timeout = self.config.volume.build_timeout
def list_volumes(self, params=None):
- """List all the volumes created"""
+ """List all the volumes created."""
url = 'volumes'
if params:
url += '?%s' % urllib.urlencode(params)
@@ -47,7 +47,7 @@
return resp, body['volumes']
def list_volumes_with_detail(self, params=None):
- """List the details of all volumes"""
+ """List the details of all volumes."""
url = 'volumes/detail'
if params:
url += '?%s' % urllib.urlencode(params)
@@ -57,7 +57,7 @@
return resp, body['volumes']
def get_volume(self, volume_id, wait=None):
- """Returns the details of a single volume"""
+ """Returns the details of a single volume."""
url = "volumes/%s" % str(volume_id)
resp, body = self.get(url, wait=wait)
body = json.loads(body)
@@ -85,11 +85,11 @@
return resp, body['volume']
def delete_volume(self, volume_id):
- """Deletes the Specified Volume"""
+ """Deletes the Specified Volume."""
return self.delete("volumes/%s" % str(volume_id))
def attach_volume(self, volume_id, instance_uuid, mountpoint):
- """Attaches a volume to a given instance on a given mountpoint"""
+ """Attaches a volume to a given instance on a given mountpoint."""
post_body = {
'instance_uuid': instance_uuid,
'mountpoint': mountpoint,
@@ -100,7 +100,7 @@
return resp, body
def detach_volume(self, volume_id):
- """Detaches a volume from an instance"""
+ """Detaches a volume from an instance."""
post_body = {}
post_body = json.dumps({'os-detach': post_body})
url = 'volumes/%s/action' % (volume_id)
@@ -108,7 +108,7 @@
return resp, body
def wait_for_volume_status(self, volume_id, status):
- """Waits for a Volume to reach a given status"""
+ """Waits for a Volume to reach a given status."""
resp, body = self.get_volume(volume_id)
volume_name = body['display_name']
volume_status = body['status']
diff --git a/tempest/services/volume/xml/admin/volume_types_client.py b/tempest/services/volume/xml/admin/volume_types_client.py
index 3da1af0..74d4631 100644
--- a/tempest/services/volume/xml/admin/volume_types_client.py
+++ b/tempest/services/volume/xml/admin/volume_types_client.py
@@ -67,7 +67,7 @@
return extra_spec
def list_volume_types(self, params=None):
- """List all the volume_types created"""
+ """List all the volume_types created."""
url = 'types'
if params:
url += '?%s' % urllib.urlencode(params)
@@ -81,7 +81,7 @@
return resp, volume_types
def get_volume_type(self, type_id):
- """Returns the details of a single volume_type"""
+ """Returns the details of a single volume_type."""
url = "types/%s" % str(type_id)
resp, body = self.get(url, self.headers)
body = etree.fromstring(body)
@@ -114,11 +114,11 @@
return resp, body
def delete_volume_type(self, type_id):
- """Deletes the Specified Volume_type"""
+ """Deletes the Specified Volume_type."""
return self.delete("types/%s" % str(type_id))
def list_volume_types_extra_specs(self, vol_type_id, params=None):
- """List all the volume_types extra specs created"""
+ """List all the volume_types extra specs created."""
url = 'types/%s/extra_specs' % str(vol_type_id)
if params:
@@ -133,7 +133,7 @@
return resp, extra_specs
def get_volume_type_extra_specs(self, vol_type_id, extra_spec_name):
- """Returns the details of a single volume_type extra spec"""
+ """Returns the details of a single volume_type extra spec."""
url = "types/%s/extra_specs/%s" % (str(vol_type_id),
str(extra_spec_name))
resp, body = self.get(url, self.headers)
@@ -161,7 +161,7 @@
return resp, body
def delete_volume_type_extra_specs(self, vol_id, extra_spec_name):
- """Deletes the Specified Volume_type extra spec"""
+ """Deletes the Specified Volume_type extra spec."""
return self.delete("types/%s/extra_specs/%s" % ((str(vol_id)),
str(extra_spec_name)))
diff --git a/tempest/services/volume/xml/volumes_client.py b/tempest/services/volume/xml/volumes_client.py
index 91d0fc7..b0104e0 100644
--- a/tempest/services/volume/xml/volumes_client.py
+++ b/tempest/services/volume/xml/volumes_client.py
@@ -56,7 +56,7 @@
return vol
def list_volumes(self, params=None):
- """List all the volumes created"""
+ """List all the volumes created."""
url = 'volumes'
if params:
@@ -70,7 +70,7 @@
return resp, volumes
def list_volumes_with_detail(self, params=None):
- """List all the details of volumes"""
+ """List all the details of volumes."""
url = 'volumes/detail'
if params:
@@ -84,7 +84,7 @@
return resp, volumes
def get_volume(self, volume_id, wait=None):
- """Returns the details of a single volume"""
+ """Returns the details of a single volume."""
url = "volumes/%s" % str(volume_id)
resp, body = self.get(url, self.headers, wait=wait)
body = etree.fromstring(body)
@@ -116,11 +116,11 @@
return resp, body
def delete_volume(self, volume_id):
- """Deletes the Specified Volume"""
+ """Deletes the Specified Volume."""
return self.delete("volumes/%s" % str(volume_id))
def wait_for_volume_status(self, volume_id, status):
- """Waits for a Volume to reach a given status"""
+ """Waits for a Volume to reach a given status."""
resp, body = self.get_volume(volume_id)
volume_name = body['displayName']
volume_status = body['status']
diff --git a/tempest/testboto.py b/tempest/testboto.py
index 3a0eb25..c38bf99 100644
--- a/tempest/testboto.py
+++ b/tempest/testboto.py
@@ -122,7 +122,7 @@
class BotoTestCase(unittest.TestCase):
- """Recommended to use as base class for boto related test"""
+ """Recommended to use as base class for boto related test."""
@classmethod
def setUpClass(cls):
# The trash contains cleanup functions and paramaters in tuples
@@ -148,7 +148,7 @@
@classmethod
def cancelResourceCleanUp(cls, key):
- """Cancel Clean up request"""
+ """Cancel Clean up request."""
del cls._resource_trash_bin[key]
#TODO(afazekas): Add "with" context handling
@@ -293,7 +293,7 @@
@classmethod
def destroy_bucket(cls, connection_data, bucket):
- """Destroys the bucket and its content, just for teardown"""
+ """Destroys the bucket and its content, just for teardown."""
exc_num = 0
try:
with closing(boto.connect_s3(**connection_data)) as conn:
@@ -316,7 +316,7 @@
@classmethod
def destroy_reservation(cls, reservation):
- """Terminate instances in a reservation, just for teardown"""
+ """Terminate instances in a reservation, just for teardown."""
exc_num = 0
def _instance_state():
@@ -383,7 +383,7 @@
@classmethod
def destroy_snapshot_wait(cls, snapshot):
- """delete snaphot, wait until not exists"""
+ """delete snaphot, wait until not exists."""
snapshot.delete()
def _update():
diff --git a/tempest/tests/boto/test_ec2_instance_run.py b/tempest/tests/boto/test_ec2_instance_run.py
index 95ef23c..331e54c 100644
--- a/tempest/tests/boto/test_ec2_instance_run.py
+++ b/tempest/tests/boto/test_ec2_instance_run.py
@@ -121,6 +121,7 @@
self.cancelResourceCleanUp(rcuk)
@attr(type='smoke')
+ @unittest.skip("Skipped until the Bug #1098112 is resolved")
def test_run_terminate_instance(self):
# EC2 run, terminate immediately
image_ami = self.ec2_client.get_image(self.images["ami"]
diff --git a/tempest/tests/boto/utils/wait.py b/tempest/tests/boto/utils/wait.py
index 951b5bf..77fe037 100644
--- a/tempest/tests/boto/utils/wait.py
+++ b/tempest/tests/boto/utils/wait.py
@@ -62,7 +62,7 @@
def re_search_wait(lfunction, regexp):
- """Stops waiting on success"""
+ """Stops waiting on success."""
start_time = time.time()
while True:
text = lfunction()
@@ -84,7 +84,7 @@
def wait_no_exception(lfunction, exc_class=None, exc_matcher=None):
- """Stops waiting on success"""
+ """Stops waiting on success."""
start_time = time.time()
if exc_matcher is not None:
exc_class = BotoServerError
@@ -114,7 +114,7 @@
#NOTE(afazekas): EC2/boto normally raise exception instead of empty list
def wait_exception(lfunction):
- """Returns with the exception or raises one"""
+ """Returns with the exception or raises one."""
start_time = time.time()
while True:
try:
diff --git a/tempest/tests/compute/admin/test_flavors.py b/tempest/tests/compute/admin/test_flavors.py
index b5ee13a..8172bd4 100644
--- a/tempest/tests/compute/admin/test_flavors.py
+++ b/tempest/tests/compute/admin/test_flavors.py
@@ -29,8 +29,8 @@
Tests Flavors API Create and Delete that require admin privileges
"""
- @staticmethod
- def setUpClass(cls):
+ @classmethod
+ def setUpClass(self, cls):
if not compute.FLAVOR_EXTRA_DATA_ENABLED:
msg = "FlavorExtraData extension not enabled."
raise nose.SkipTest(msg)
@@ -43,61 +43,77 @@
cls.ephemeral = 10
cls.new_flavor_id = 1234
cls.swap = 1024
- cls.rxtx = 1
+ cls.rxtx = 2
@attr(type='positive')
def test_create_flavor(self):
# Create a flavor and ensure it is listed
# This operation requires the user to have 'admin' role
- #Create the flavor
- resp, flavor = self.client.create_flavor(self.flavor_name,
- self.ram, self.vcpus,
- self.disk,
- self.ephemeral,
- self.new_flavor_id,
- self.swap, self.rxtx)
- self.assertEqual(200, resp.status)
- self.assertEqual(flavor['name'], self.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']), self.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)
+ try:
+ #Create the flavor
+ resp, flavor = self.client.create_flavor(self.flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ self.new_flavor_id,
+ ephemeral=self.ephemeral,
+ swap=self.swap,
+ rxtx=self.rxtx)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(flavor['name'], self.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']), self.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(self.new_flavor_id)
- self.assertEqual(resp.status, 200)
- self.assertEqual(flavor['name'], self.flavor_name)
+ #Verify flavor is retrieved
+ resp, flavor = self.client.get_flavor_details(self.new_flavor_id)
+ self.assertEqual(resp.status, 200)
+ self.assertEqual(flavor['name'], self.flavor_name)
- #Delete the flavor
- resp, body = self.client.delete_flavor(flavor['id'])
- self.assertEqual(resp.status, 202)
+ finally:
+ #Delete the flavor
+ resp, body = self.client.delete_flavor(self.new_flavor_id)
+ self.assertEqual(resp.status, 202)
+ self.client.wait_for_resource_deletion(self.new_flavor_id)
@attr(type='positive')
def test_create_flavor_verify_entry_in_list_details(self):
# Create a flavor and ensure it's details are listed
# This operation requires the user to have 'admin' role
- #Create the flavor
- resp, flavor = self.client.create_flavor(self.flavor_name,
- self.ram, self.vcpus,
- self.disk,
- self.ephemeral,
- self.new_flavor_id,
- self.swap, 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'] == self.flavor_name:
- flag = True
- self.assertTrue(flag)
+ try:
+ #Create the flavor
+ resp, flavor = self.client.create_flavor(self.flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ self.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'] == self.flavor_name:
+ flag = True
+ self.assertTrue(flag)
- #Delete the flavor
- resp, body = self.client.delete_flavor(self.new_flavor_id)
- self.assertEqual(resp.status, 202)
+ finally:
+ #Delete the flavor
+ resp, body = self.client.delete_flavor(self.new_flavor_id)
+ self.assertEqual(resp.status, 202)
+ self.client.wait_for_resource_deletion(self.new_flavor_id)
@attr(type='negative')
def test_get_flavor_details_for_deleted_flavor(self):
@@ -106,9 +122,10 @@
resp, flavor = self.client.create_flavor(self.flavor_name,
self.ram,
self.vcpus, self.disk,
- self.ephemeral,
self.new_flavor_id,
- self.swap, self.rxtx)
+ ephemeral=self.ephemeral,
+ swap=self.swap,
+ rxtx=self.rxtx)
self.assertEquals(200, resp.status)
# Delete the flavor
@@ -129,20 +146,118 @@
flag = False
self.assertTrue(flag)
+ def test_create_list_flavor_without_extra_data(self):
+ #Create a flavor and ensure it is listed
+ #This operation requires the user to have 'admin' role
+ try:
+ #Create the flavor
+ resp, flavor = self.client.create_flavor(self.flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ self.new_flavor_id)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(flavor['name'], self.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']), self.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(self.new_flavor_id)
+ self.assertEqual(resp.status, 200)
+ self.assertEqual(flavor['name'], self.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'] == self.flavor_name:
+ flag = True
+ self.assertTrue(flag)
+
+ finally:
+ #Delete the flavor
+ resp, body = self.client.delete_flavor(self.new_flavor_id)
+ self.assertEqual(resp.status, 202)
+ self.client.wait_for_resource_deletion(self.new_flavor_id)
+
+ @attr(type='positive')
+ def test_flavor_not_public_verify_entry_not_in_list_details(self):
+ #Create a flavor with os-flavor-access:is_public false should not
+ #be present in list_details.
+ #This operation requires the user to have 'admin' role
+ try:
+ #Create the flavor
+ resp, flavor = self.client.create_flavor(self.flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ self.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'] == self.flavor_name:
+ flag = True
+ self.assertFalse(flag)
+ finally:
+ #Delete the flavor
+ resp, body = self.client.delete_flavor(self.new_flavor_id)
+ self.assertEqual(resp.status, 202)
+
+ def test_list_public_flavor_with_other_user(self):
+ #Create a Flavor with public access.
+ #Try to List/Get flavor with another user
+ try:
+ #Create the flavor
+ resp, flavor = self.client.create_flavor(self.flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ self.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'] == self.flavor_name:
+ flag = True
+ self.assertTrue(flag)
+ finally:
+ #Delete the flavor
+ resp, body = self.client.delete_flavor(self.new_flavor_id)
+ self.assertEqual(resp.status, 202)
+ self.client.wait_for_resource_deletion(self.new_flavor_id)
+
class FlavorsAdminTestXML(base.BaseComputeAdminTestXML,
+ base.BaseComputeTestXML,
FlavorsAdminTestBase):
@classmethod
def setUpClass(cls):
super(FlavorsAdminTestXML, cls).setUpClass()
+ base.BaseComputeTestXML.setUpClass()
FlavorsAdminTestBase.setUpClass(cls)
class FlavorsAdminTestJSON(base.BaseComputeAdminTestJSON,
+ base.BaseComputeTestJSON,
FlavorsAdminTestBase):
@classmethod
def setUpClass(cls):
super(FlavorsAdminTestJSON, cls).setUpClass()
+ base.BaseComputeTestJSON.setUpClass()
FlavorsAdminTestBase.setUpClass(cls)
diff --git a/tempest/tests/compute/base.py b/tempest/tests/compute/base.py
index 4040705..8044d01 100644
--- a/tempest/tests/compute/base.py
+++ b/tempest/tests/compute/base.py
@@ -34,7 +34,7 @@
class BaseCompTest(unittest.TestCase):
- """Base test case class for all Compute API tests"""
+ """Base test case class for all Compute API tests."""
@classmethod
def setUpClass(cls):
@@ -179,7 +179,7 @@
@classmethod
def create_server(cls, image_id=None, flavor=None):
- """Wrapper utility that returns a test server"""
+ """Wrapper utility that returns a test server."""
server_name = rand_name(cls.__name__ + "-instance")
if not flavor:
@@ -212,7 +212,7 @@
return resp, server
def wait_for(self, condition):
- """Repeatedly calls condition() until a timeout"""
+ """Repeatedly calls condition() until a timeout."""
start_time = int(time.time())
while True:
try:
@@ -246,7 +246,7 @@
class BaseComputeAdminTest(unittest.TestCase):
- """Base test case class for all Compute Admin API tests"""
+ """Base test case class for all Compute Admin API tests."""
@classmethod
def setUpClass(cls):
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 e5b4e0d..9a9914a 100644
--- a/tempest/tests/compute/floating_ips/test_floating_ips_actions.py
+++ b/tempest/tests/compute/floating_ips/test_floating_ips_actions.py
@@ -111,11 +111,9 @@
@attr(type='negative')
def test_delete_nonexistant_floating_ip(self):
- """
+ # Negative test:Deletion of a nonexistent floating IP
+ # from project should fail
- Negative test:Deletion of a nonexistent floating IP
- from project should fail
- """
#Deleting the non existent floating IP
try:
resp, body = self.client.delete_floating_ip(self.non_exist_id)
diff --git a/tempest/tests/compute/images/test_images.py b/tempest/tests/compute/images/test_images.py
index 46c8eee..2557f16 100644
--- a/tempest/tests/compute/images/test_images.py
+++ b/tempest/tests/compute/images/test_images.py
@@ -31,7 +31,7 @@
class ImagesTestBase(object):
def tearDown(self):
- """Terminate test instances created after a test is executed"""
+ """Terminate test instances created after a test is executed."""
for server in self.servers:
resp, body = self.servers_client.delete_server(server['id'])
if resp['status'] == '204':
@@ -42,39 +42,6 @@
self.client.delete_image(image_id)
self.image_ids.remove(image_id)
- @attr(type='smoke')
- @unittest.skipUnless(compute.CREATE_IMAGE_ENABLED,
- 'Environment unable to create images.')
- def test_create_delete_image(self):
- # An image for the provided server should be created
- server_name = rand_name('server')
- resp, server = self.servers_client.create_server(server_name,
- self.image_ref,
- self.flavor_ref)
- self.servers_client.wait_for_server_status(server['id'], 'ACTIVE')
-
- # Create a new image
- name = rand_name('image')
- meta = {'image_type': 'test'}
- resp, body = self.client.create_image(server['id'], name, meta)
- image_id = parse_image_id(resp['location'])
- self.client.wait_for_image_resp_code(image_id, 200)
- self.client.wait_for_image_status(image_id, 'ACTIVE')
-
- # Verify the image was created correctly
- resp, image = self.client.get_image(image_id)
- self.assertEqual(name, image['name'])
- self.assertEqual('test', image['metadata']['image_type'])
-
- # Verify minRAM and minDisk values are the same as the original image
- resp, original_image = self.client.get_image(self.image_ref)
- self.assertEqual(original_image['minRam'], image['minRam'])
- self.assertEqual(original_image['minDisk'], image['minDisk'])
-
- # Teardown
- self.client.delete_image(image['id'])
- self.servers_client.delete_server(server['id'])
-
@attr(type='negative')
def test_create_image_from_deleted_server(self):
# An image should not be created if the server instance is removed
@@ -125,13 +92,13 @@
"with invalid server id")
@attr(type='negative')
- @unittest.skipUnless(compute.MULTI_USER, 'Second user not configured')
- def test_create_image_for_server_in_another_tenant(self):
- # Creating image of another tenant's server should be return error
+ def test_create_image_when_server_is_terminating(self):
+ # Return an error when creating image of server that is terminating
server = self.create_server()
+ self.servers_client.delete_server(server['id'])
snapshot_name = rand_name('test-snap-')
- self.assertRaises(exceptions.NotFound, self.alt_client.create_image,
+ self.assertRaises(exceptions.Duplicate, self.client.create_image,
server['id'], snapshot_name)
@attr(type='negative')
@@ -158,52 +125,6 @@
server['id'], snapshot_name)
@attr(type='negative')
- def test_create_image_when_server_is_terminating(self):
- # Return an error when creating image of server that is terminating
- server = self.create_server()
- self.servers_client.delete_server(server['id'])
-
- snapshot_name = rand_name('test-snap-')
- self.assertRaises(exceptions.Duplicate, self.client.create_image,
- server['id'], snapshot_name)
-
- @attr(type='negative')
- def test_create_second_image_when_first_image_is_being_saved(self):
- # Disallow creating another image when first image is being saved
- server = self.create_server()
-
- try:
- # Create first snapshot
- snapshot_name = rand_name('test-snap-')
- resp, body = self.client.create_image(server['id'], snapshot_name)
- image_id = parse_image_id(resp['location'])
- self.image_ids.append(image_id)
-
- # Create second snapshot
- alt_snapshot_name = rand_name('test-snap-')
- self.client.create_image(server['id'], alt_snapshot_name)
- except exceptions.Duplicate:
- pass
-
- else:
- self.fail("Should allow creating an image when another image of"
- "the server is still being saved")
-
- @attr(type='negative')
- @unittest.skip("Until Bug 1004564 is fixed")
- def test_create_image_specify_name_over_256_chars(self):
- # Return an error if snapshot name over 256 characters is passed
- server = self.create_server()
-
- try:
- snapshot_name = rand_name('a' * 260)
- self.assertRaises(exceptions.BadRequest, self.client.create_image,
- server['id'], snapshot_name)
- except Exception:
- self.fail("Should return 400 Bad Request if image name is over 256"
- " characters")
-
- @attr(type='negative')
def test_create_image_specify_uuid_35_characters_or_less(self):
# Return an error if Image ID passed is 35 characters or less
try:
@@ -228,51 +149,6 @@
" characters or more")
@attr(type='negative')
- @unittest.skip("Until Bug 1006725 is fixed")
- def test_create_image_specify_multibyte_character_image_name(self):
- # Return an error if the image name has multi-byte characters
- server = self.create_server()
-
- try:
- snapshot_name = rand_name('\xef\xbb\xbf')
- self.assertRaises(exceptions.BadRequest,
- self.client.create_image, server['id'],
- snapshot_name)
- except Exception:
- self.fail("Should return 400 Bad Request if multi byte characters"
- " are used for image name")
-
- @attr(type='negative')
- @unittest.skip("Until Bug 1005423 is fixed")
- def test_create_image_specify_invalid_metadata(self):
- # Return an error when creating image with invalid metadata
- server = self.create_server()
-
- try:
- snapshot_name = rand_name('test-snap-')
- meta = {'': ''}
- self.assertRaises(exceptions.BadRequest, self.client.create_image,
- server['id'], snapshot_name, meta)
-
- except Exception:
- self.fail("Should raise 400 Bad Request if meta data is invalid")
-
- @attr(type='negative')
- @unittest.skip("Until Bug 1005423 is fixed")
- def test_create_image_specify_metadata_over_limits(self):
- # Return an error when creating image with meta data over 256 chars
- server = self.create_server()
-
- try:
- snapshot_name = rand_name('test-snap-')
- meta = {'a' * 260: 'b' * 260}
- self.assertRaises(exceptions.OverLimit, self.client.create_image,
- server['id'], snapshot_name, meta)
-
- except Exception:
- self.fail("Should raise 413 Over Limit if meta data was too long")
-
- @attr(type='negative')
def test_delete_image_with_invalid_image_id(self):
# An image should not be deleted with invalid image id
try:
@@ -336,42 +212,6 @@
self.fail("Did not return HTTP 404 NotFound for image id that "
"exceeds 35 character ID length limit")
- @attr(type='negative')
- @unittest.skipUnless(compute.MULTI_USER, 'Second user not configured')
- def test_delete_image_of_another_tenant(self):
- # Return an error while trying to delete another tenant's image
-
- server = self.create_server()
-
- snapshot_name = rand_name('test-snap-')
- resp, body = self.client.create_image(server['id'], snapshot_name)
- image_id = parse_image_id(resp['location'])
- self.image_ids.append(image_id)
- self.client.wait_for_image_resp_code(image_id, 200)
- self.client.wait_for_image_status(image_id, 'ACTIVE')
-
- # Delete image
- self.assertRaises(exceptions.NotFound,
- self.alt_client.delete_image, image_id)
-
- @attr(type='negative')
- def test_delete_image_that_is_not_yet_active(self):
- # Return an error while trying to delete an active that is creating
-
- server = self.create_server()
-
- snapshot_name = rand_name('test-snap-')
- resp, body = self.client.create_image(server['id'], snapshot_name)
- image_id = parse_image_id(resp['location'])
- self.image_ids.append(image_id)
-
- # Do not wait, attempt to delete the image, ensure it's successful
- resp, body = self.client.delete_image(image_id)
- self.assertEqual('204', resp['status'])
- self.image_ids.remove(image_id)
-
- self.assertRaises(exceptions.NotFound, self.client.get_image, image_id)
-
class ImagesTestJSON(base.BaseComputeTestJSON,
ImagesTestBase):
@@ -380,7 +220,6 @@
@classmethod
def setUpClass(cls):
- raise nose.SkipTest("Until Bug 1046870 is fixed")
super(ImagesTestJSON, cls).setUpClass()
cls.client = cls.images_client
cls.servers_client = cls.servers_client
@@ -407,7 +246,6 @@
@classmethod
def setUpClass(cls):
- raise nose.SkipTest("Until Bug 1046870 is fixed")
super(ImagesTestXML, cls).setUpClass()
cls.client = cls.images_client
cls.servers_client = cls.servers_client
diff --git a/tempest/tests/compute/images/test_images_oneserver.py b/tempest/tests/compute/images/test_images_oneserver.py
new file mode 100644
index 0000000..2841a21
--- /dev/null
+++ b/tempest/tests/compute/images/test_images_oneserver.py
@@ -0,0 +1,239 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack, LLC
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import nose
+from nose.plugins.attrib import attr
+import unittest2 as unittest
+
+from tempest import clients
+from tempest.common.utils.data_utils import parse_image_id
+from tempest.common.utils.data_utils import rand_name
+import tempest.config
+from tempest import exceptions
+from tempest.tests import compute
+from tempest.tests.compute import base
+
+
+class ImagesOneServerTestBase(object):
+ def tearDownClass(cls):
+ """Terminate test instances created after a test is executed."""
+ resp, body = self.servers_client.delete_server(cls.server['id'])
+ if resp['status'] == '204':
+ self.servers.remove(server)
+ self.servers_client.wait_for_server_termination(cls.server['id'])
+
+ def tearDown(self):
+ """Terminate test instances created after a test is executed."""
+ for image_id in self.image_ids:
+ self.client.delete_image(image_id)
+ self.image_ids.remove(image_id)
+
+ @attr(type='negative')
+ @unittest.skip("Until Bug 1006725 is fixed")
+ def test_create_image_specify_multibyte_character_image_name(self):
+ # Return an error if the image name has multi-byte characters
+ try:
+ snapshot_name = rand_name('\xef\xbb\xbf')
+ self.assertRaises(exceptions.BadRequest,
+ self.client.create_image, self.server['id'],
+ snapshot_name)
+ except Exception:
+ self.fail("Should return 400 Bad Request if multi byte characters"
+ " are used for image name")
+
+ @attr(type='negative')
+ @unittest.skip("Until Bug 1005423 is fixed")
+ def test_create_image_specify_invalid_metadata(self):
+ # Return an error when creating image with invalid metadata
+ try:
+ snapshot_name = rand_name('test-snap-')
+ meta = {'': ''}
+ self.assertRaises(exceptions.BadRequest, self.client.create_image,
+ self.server['id'], snapshot_name, meta)
+
+ except Exception:
+ self.fail("Should raise 400 Bad Request if meta data is invalid")
+
+ @attr(type='negative')
+ @unittest.skip("Until Bug 1005423 is fixed")
+ def test_create_image_specify_metadata_over_limits(self):
+ # Return an error when creating image with meta data over 256 chars
+ try:
+ snapshot_name = rand_name('test-snap-')
+ meta = {'a' * 260: 'b' * 260}
+ self.assertRaises(exceptions.OverLimit, self.client.create_image,
+ self.server['id'], snapshot_name, meta)
+
+ except Exception:
+ self.fail("Should raise 413 Over Limit if meta data was too long")
+
+ @attr(type='negative')
+ def test_delete_image_of_another_tenant(self):
+ # Return an error while trying to delete another tenant's image
+ self.servers_client.wait_for_server_status(self.server['id'], 'ACTIVE')
+ snapshot_name = rand_name('test-snap-')
+ resp, body = self.client.create_image(self.server['id'], snapshot_name)
+ image_id = parse_image_id(resp['location'])
+ self.image_ids.append(image_id)
+ self.client.wait_for_image_resp_code(image_id, 200)
+ self.client.wait_for_image_status(image_id, 'ACTIVE')
+
+ # Delete image
+ self.assertRaises(exceptions.NotFound,
+ self.alt_client.delete_image, image_id)
+
+ @attr(type='smoke')
+ @unittest.skipUnless(compute.CREATE_IMAGE_ENABLED,
+ 'Environment unable to create images.')
+ def test_create_delete_image(self):
+
+ # Create a new image
+ name = rand_name('image')
+ meta = {'image_type': 'test'}
+ resp, body = self.client.create_image(self.server['id'], name, meta)
+ self.assertEqual(202, resp.status)
+ image_id = parse_image_id(resp['location'])
+ self.client.wait_for_image_resp_code(image_id, 200)
+ self.client.wait_for_image_status(image_id, 'ACTIVE')
+
+ # Verify the image was created correctly
+ resp, image = self.client.get_image(image_id)
+ self.assertEqual(name, image['name'])
+ self.assertEqual('test', image['metadata']['image_type'])
+
+ # Verify minRAM and minDisk values are the same as the original image
+ resp, original_image = self.client.get_image(self.image_ref)
+ self.assertEqual(original_image['minRam'], image['minRam'])
+ self.assertEqual(original_image['minDisk'], image['minDisk'])
+
+ @attr(type='negative')
+ def test_create_image_for_server_in_another_tenant(self):
+ # Creating image of another tenant's server should be return error
+
+ snapshot_name = rand_name('test-snap-')
+ self.assertRaises(exceptions.NotFound, self.alt_client.create_image,
+ self.server['id'], snapshot_name)
+
+ @attr(type='negative')
+ def test_create_second_image_when_first_image_is_being_saved(self):
+ # Disallow creating another image when first image is being saved
+
+ try:
+ # Create first snapshot
+ snapshot_name = rand_name('test-snap-')
+ resp, body = self.client.create_image(self.server['id'],
+ snapshot_name)
+ self.assertEqual(202, resp.status)
+ image_id = parse_image_id(resp['location'])
+ self.image_ids.append(image_id)
+
+ # Create second snapshot
+ alt_snapshot_name = rand_name('test-snap-')
+ self.client.create_image(self.server['id'],
+ alt_snapshot_name)
+ except exceptions.Duplicate:
+ self.client.wait_for_image_status(image_id, 'ACTIVE')
+
+ else:
+ self.fail("Should not allow creating an image when another image "
+ "of the server is still being saved")
+
+ @attr(type='negative')
+ @unittest.skip("Until Bug 1004564 is fixed")
+ def test_create_image_specify_name_over_256_chars(self):
+ # Return an error if snapshot name over 256 characters is passed
+
+ try:
+ snapshot_name = rand_name('a' * 260)
+ self.assertRaises(exceptions.BadRequest, self.client.create_image,
+ self.server['id'], snapshot_name)
+ except Exception:
+ self.fail("Should return 400 Bad Request if image name is over 256"
+ " characters")
+
+ @attr(type='negative')
+ def test_delete_image_that_is_not_yet_active(self):
+ # Return an error while trying to delete an image what is creating
+
+ snapshot_name = rand_name('test-snap-')
+ resp, body = self.client.create_image(self.server['id'], snapshot_name)
+ self.assertEqual(202, resp.status)
+ image_id = parse_image_id(resp['location'])
+ self.image_ids.append(image_id)
+
+ # Do not wait, attempt to delete the image, ensure it's successful
+ resp, body = self.client.delete_image(image_id)
+ self.assertEqual('204', resp['status'])
+ self.image_ids.remove(image_id)
+
+ self.assertRaises(exceptions.NotFound, self.client.get_image, image_id)
+
+
+class ImagesOneServerTestJSON(base.BaseComputeTestJSON,
+ ImagesOneServerTestBase):
+
+ def tearDown(self):
+ ImagesOneServerTestBase.tearDown(self)
+
+ @classmethod
+ def setUpClass(cls):
+ super(ImagesOneServerTestJSON, cls).setUpClass()
+ cls.client = cls.images_client
+ cls.servers_client = cls.servers_client
+ cls.server = cls.create_server()
+
+ cls.image_ids = []
+
+ if compute.MULTI_USER:
+ if cls.config.compute.allow_tenant_isolation:
+ creds = cls._get_isolated_creds()
+ username, tenant_name, password = creds
+ cls.alt_manager = clients.Manager(username=username,
+ password=password,
+ tenant_name=tenant_name)
+ else:
+ # Use the alt_XXX credentials in the config file
+ cls.alt_manager = clients.AltManager()
+ cls.alt_client = cls.alt_manager.images_client
+
+
+class ImagesOneServerTestXML(base.BaseComputeTestXML,
+ ImagesOneServerTestBase):
+
+ def tearDown(self):
+ ImagesOneServerTestBase.tearDown(self)
+
+ @classmethod
+ def setUpClass(cls):
+ super(ImagesOneServerTestXML, cls).setUpClass()
+ cls.client = cls.images_client
+ cls.servers_client = cls.servers_client
+ cls.server = cls.create_server()
+
+ cls.image_ids = []
+
+ if compute.MULTI_USER:
+ if cls.config.compute.allow_tenant_isolation:
+ creds = cls._get_isolated_creds()
+ username, tenant_name, password = creds
+ cls.alt_manager = clients.Manager(username=username,
+ password=password,
+ tenant_name=tenant_name)
+ else:
+ # Use the alt_XXX credentials in the config file
+ cls.alt_manager = clients.AltManager()
+ cls.alt_client = cls.alt_manager.images_client
diff --git a/tempest/tests/compute/images/test_images_whitebox.py b/tempest/tests/compute/images/test_images_whitebox.py
index f409970..2987534 100644
--- a/tempest/tests/compute/images/test_images_whitebox.py
+++ b/tempest/tests/compute/images/test_images_whitebox.py
@@ -36,7 +36,7 @@
@classmethod
def tearDownClass(cls):
- """Terminate test instances created after a test is executed"""
+ """Terminate test instances created after a test is executed."""
for server in cls.servers:
cls.update_state(server['id'], "active", None)
@@ -51,7 +51,7 @@
@classmethod
def update_state(self, server_id, vm_state, task_state, deleted=False):
- """Update states of an instance in database for validation"""
+ """Update states of an instance in database for validation."""
if not task_state:
task_state = "NULL"
@@ -64,7 +64,7 @@
self.connection.execute(stmt, autocommit=True)
def _test_create_image_409_base(self, vm_state, task_state, deleted=False):
- """Base method for create image tests based on vm and task states"""
+ """Base method for create image tests based on vm and task states."""
try:
self.update_state(self.shared_server['id'], vm_state,
task_state, deleted)
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 fdf2892..805adf4 100644
--- a/tempest/tests/compute/security_groups/test_security_group_rules.py
+++ b/tempest/tests/compute/security_groups/test_security_group_rules.py
@@ -58,11 +58,10 @@
@attr(type='positive')
def test_security_group_rules_create_with_optional_arguments(self):
- """
- Positive test: Creation of Security Group rule
- with optional arguments
- should be successfull
- """
+ # Positive test: Creation of Security Group rule
+ # with optional arguments
+ # should be successfull
+
rule_id = None
secgroup1 = None
secgroup2 = None
diff --git a/tempest/tests/compute/servers/test_create_server.py b/tempest/tests/compute/servers/test_create_server.py
index 88ace2d..c5a54dc 100644
--- a/tempest/tests/compute/servers/test_create_server.py
+++ b/tempest/tests/compute/servers/test_create_server.py
@@ -43,7 +43,7 @@
cls.accessIPv6canon = '::babe:dc0c:1602'
cls.name = rand_name('server')
file_contents = 'This is a test file.'
- personality = [{'path': '/etc/test.txt',
+ personality = [{'path': '/test.txt',
'contents': base64.b64encode(file_contents)}]
cls.client = cls.servers_client
cli_resp = cls.client.create_server(cls.name,
diff --git a/tempest/tests/compute/servers/test_server_actions.py b/tempest/tests/compute/servers/test_server_actions.py
index f4e62b1..91f0674 100644
--- a/tempest/tests/compute/servers/test_server_actions.py
+++ b/tempest/tests/compute/servers/test_server_actions.py
@@ -44,7 +44,7 @@
self.client.wait_for_server_status(self.server_id, 'ACTIVE')
def tearDown(self):
- self.client.delete_server(self.server_id)
+ self.clear_servers()
@attr(type='smoke')
@unittest.skipUnless(compute.CHANGE_PASSWORD_AVAILABLE,
@@ -67,11 +67,9 @@
# The server should be power cycled
if self.run_ssh:
# Get the time the server was last rebooted,
- # waiting for one minute as who doesn't have seconds precision
resp, server = self.client.get_server(self.server_id)
linux_client = RemoteClient(server, self.ssh_user, self.password)
boot_time = linux_client.get_boot_time()
- time.sleep(60)
resp, body = self.client.reboot(self.server_id, 'HARD')
self.assertEqual(202, resp.status)
@@ -89,11 +87,9 @@
# The server should be signaled to reboot gracefully
if self.run_ssh:
# Get the time the server was last rebooted,
- # waiting for one minute as who doesn't have seconds precision
resp, server = self.client.get_server(self.server_id)
linux_client = RemoteClient(server, self.ssh_user, self.password)
boot_time = linux_client.get_boot_time()
- time.sleep(60)
resp, body = self.client.reboot(self.server_id, 'SOFT')
self.assertEqual(202, resp.status)
diff --git a/tempest/tests/compute/servers/test_servers_whitebox.py b/tempest/tests/compute/servers/test_servers_whitebox.py
index 8aca745..3ff4df6 100644
--- a/tempest/tests/compute/servers/test_servers_whitebox.py
+++ b/tempest/tests/compute/servers/test_servers_whitebox.py
@@ -117,7 +117,7 @@
self.connection.execute(stmt, autocommit=True)
def update_state(self, server_id, vm_state, task_state, deleted=False):
- """Update states of an instance in database for validation"""
+ """Update states of an instance in database for validation."""
if not task_state:
task_state = 'NULL'
diff --git a/tempest/tests/identity/admin/test_tenants.py b/tempest/tests/identity/admin/test_tenants.py
index 8fba7e3..578af4a 100644
--- a/tempest/tests/identity/admin/test_tenants.py
+++ b/tempest/tests/identity/admin/test_tenants.py
@@ -17,6 +17,7 @@
import unittest2 as unittest
+from nose.plugins.attrib import attr
from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
from tempest.tests.identity import base
@@ -24,21 +25,6 @@
class TenantsTestBase(object):
- @staticmethod
- def setUpClass(cls):
- for _ in xrange(5):
- resp, tenant = cls.client.create_tenant(rand_name('tenant-'))
- cls.data.tenants.append(tenant)
-
- def test_list_tenants(self):
- # Return a list of all tenants
- resp, body = self.client.list_tenants()
- found = [tenant for tenant in body if tenant in self.data.tenants]
- self.assertTrue(any(found), 'List did not return newly created '
- 'tenants')
- self.assertEqual(len(found), len(self.data.tenants))
- self.assertTrue(resp['status'].startswith('2'))
-
def test_list_tenants_by_unauthorized_user(self):
# Non-admin user should not be able to list tenants
self.assertRaises(exceptions.Unauthorized,
@@ -51,41 +37,50 @@
self.assertRaises(exceptions.Unauthorized, self.client.list_tenants)
self.client.clear_auth()
- def test_tenant_delete(self):
+ def test_tenant_list_delete(self):
# Create several tenants and delete them
tenants = []
- for _ in xrange(5):
- resp, body = self.client.create_tenant(rand_name('tenant-new'))
- tenants.append(body['id'])
-
+ for _ in xrange(3):
+ resp, tenant = self.client.create_tenant(rand_name('tenant-new'))
+ self.data.tenants.append(tenant)
+ tenants.append(tenant)
+ tenant_ids = map(lambda x: x['id'], tenants)
resp, body = self.client.list_tenants()
- found_1 = [tenant for tenant in body if tenant['id'] in tenants]
- for tenant_id in tenants:
- resp, body = self.client.delete_tenant(tenant_id)
+ self.assertTrue(resp['status'].startswith('2'))
+ found = [tenant for tenant in body if tenant['id'] in tenant_ids]
+ self.assertEqual(len(found), len(tenants), 'Tenants not created')
+
+ for tenant in tenants:
+ resp, body = self.client.delete_tenant(tenant['id'])
self.assertTrue(resp['status'].startswith('2'))
+ self.data.tenants.remove(tenant)
resp, body = self.client.list_tenants()
- found_2 = [tenant for tenant in body if tenant['id'] in tenants]
- self.assertTrue(any(found_1), 'Tenants not created')
- self.assertFalse(any(found_2), 'Tenants failed to delete')
+ found = [tenant for tenant in body if tenant['id'] in tenant_ids]
+ self.assertFalse(any(found), 'Tenants failed to delete')
+ @attr(type='negative')
def test_tenant_delete_by_unauthorized_user(self):
# Non-admin user should not be able to delete a tenant
tenant_name = rand_name('tenant-')
resp, tenant = self.client.create_tenant(tenant_name)
+ self.data.tenants.append(tenant)
self.assertRaises(exceptions.Unauthorized,
self.non_admin_client.delete_tenant, tenant['id'])
+ @attr(type='negative')
def test_tenant_delete_request_without_token(self):
# Request to delete a tenant without a valid token should fail
tenant_name = rand_name('tenant-')
resp, tenant = self.client.create_tenant(tenant_name)
+ self.data.tenants.append(tenant)
token = self.client.get_auth()
self.client.delete_token(token)
self.assertRaises(exceptions.Unauthorized, self.client.delete_tenant,
tenant['id'])
self.client.clear_auth()
+ @attr(type='negative')
def test_delete_non_existent_tenant(self):
# Attempt to delete a non existent tenant should fail
self.assertRaises(exceptions.NotFound, self.client.delete_tenant,
@@ -97,6 +92,8 @@
tenant_desc = rand_name('desc-')
resp, body = self.client.create_tenant(tenant_name,
description=tenant_desc)
+ tenant = body
+ self.data.tenants.append(tenant)
st1 = resp['status']
tenant_id = body['id']
desc1 = body['description']
@@ -108,11 +105,14 @@
self.assertEqual(desc2, tenant_desc, 'Description does not appear'
'to be set')
self.client.delete_tenant(tenant_id)
+ self.data.tenants.remove(tenant)
def test_tenant_create_enabled(self):
# Create a tenant that is enabled
tenant_name = rand_name('tenant-')
resp, body = self.client.create_tenant(tenant_name, enabled=True)
+ tenant = body
+ self.data.tenants.append(tenant)
tenant_id = body['id']
st1 = resp['status']
en1 = body['enabled']
@@ -122,11 +122,14 @@
en2 = body['enabled']
self.assertTrue(en2, 'Enable should be True in lookup')
self.client.delete_tenant(tenant_id)
+ self.data.tenants.remove(tenant)
def test_tenant_create_not_enabled(self):
# Create a tenant that is not enabled
tenant_name = rand_name('tenant-')
resp, body = self.client.create_tenant(tenant_name, enabled=False)
+ tenant = body
+ self.data.tenants.append(tenant)
tenant_id = body['id']
st1 = resp['status']
en1 = body['enabled']
@@ -138,11 +141,15 @@
self.assertEqual('false', str(en2).lower(),
'Enable should be False in lookup')
self.client.delete_tenant(tenant_id)
+ self.data.tenants.remove(tenant)
+ @attr(type='negative')
def test_tenant_create_duplicate(self):
# Tenant names should be unique
tenant_name = rand_name('tenant-dup-')
resp, body = self.client.create_tenant(tenant_name)
+ tenant = body
+ self.data.tenants.append(tenant)
tenant1_id = body.get('id')
try:
@@ -151,15 +158,17 @@
self.fail('Should not be able to create a duplicate tenant name')
except exceptions.Duplicate:
pass
- if tenant1_id:
- self.client.delete_tenant(tenant1_id)
+ self.client.delete_tenant(tenant1_id)
+ self.data.tenants.remove(tenant)
+ @attr(type='negative')
def test_create_tenant_by_unauthorized_user(self):
# Non-admin user should not be authorized to create a tenant
tenant_name = rand_name('tenant-')
self.assertRaises(exceptions.Unauthorized,
self.non_admin_client.create_tenant, tenant_name)
+ @attr(type='negative')
def test_create_tenant_request_without_token(self):
# Create tenant request without a token should not be authorized
tenant_name = rand_name('tenant-')
@@ -169,6 +178,7 @@
tenant_name)
self.client.clear_auth()
+ @attr(type='negative')
def test_create_tenant_with_empty_name(self):
# Tenant name should not be empty
self.assertRaises(exceptions.BadRequest, self.client.create_tenant,
@@ -184,6 +194,9 @@
# Update name attribute of a tenant
t_name1 = rand_name('tenant-')
resp, body = self.client.create_tenant(t_name1)
+ tenant = body
+ self.data.tenants.append(tenant)
+
t_id = body['id']
resp1_name = body['name']
@@ -202,12 +215,16 @@
self.assertEqual(resp2_name, resp3_name)
self.client.delete_tenant(t_id)
+ self.data.tenants.remove(tenant)
def test_tenant_update_desc(self):
# Update description attribute of a tenant
t_name = rand_name('tenant-')
t_desc = rand_name('desc-')
resp, body = self.client.create_tenant(t_name, description=t_desc)
+ tenant = body
+ self.data.tenants.append(tenant)
+
t_id = body['id']
resp1_desc = body['description']
@@ -226,12 +243,16 @@
self.assertEqual(resp2_desc, resp3_desc)
self.client.delete_tenant(t_id)
+ self.data.tenants.remove(tenant)
def test_tenant_update_enable(self):
# Update the enabled attribute of a tenant
t_name = rand_name('tenant-')
t_en = False
resp, body = self.client.create_tenant(t_name, enabled=t_en)
+ tenant = body
+ self.data.tenants.append(tenant)
+
t_id = body['id']
resp1_en = body['enabled']
@@ -250,6 +271,7 @@
self.assertEqual(resp2_en, resp3_en)
self.client.delete_tenant(t_id)
+ self.data.tenants.remove(tenant)
class TenantsTestJSON(base.BaseIdentityAdminTestJSON,
@@ -258,7 +280,6 @@
@classmethod
def setUpClass(cls):
super(TenantsTestJSON, cls).setUpClass()
- TenantsTestBase.setUpClass(cls)
class TenantsTestXML(base.BaseIdentityAdminTestXML, TenantsTestBase):
@@ -266,4 +287,3 @@
@classmethod
def setUpClass(cls):
super(TenantsTestXML, cls).setUpClass()
- TenantsTestBase.setUpClass(cls)
diff --git a/tempest/tests/identity/base.py b/tempest/tests/identity/base.py
index 3867f0a..ce160da 100644
--- a/tempest/tests/identity/base.py
+++ b/tempest/tests/identity/base.py
@@ -95,7 +95,7 @@
self.role_name = None
def setup_test_user(self):
- """Set up a test user"""
+ """Set up a test user."""
self.setup_test_tenant()
self.test_user = rand_name('test_user_')
self.test_password = rand_name('pass_')
@@ -107,7 +107,7 @@
self.users.append(self.user)
def setup_test_tenant(self):
- """Set up a test tenant"""
+ """Set up a test tenant."""
self.test_tenant = rand_name('test_tenant_')
self.test_description = rand_name('desc_')
resp, self.tenant = self.client.create_tenant(
@@ -116,7 +116,7 @@
self.tenants.append(self.tenant)
def setup_test_role(self):
- """Set up a test role"""
+ """Set up a test role."""
self.test_role = rand_name('role')
resp, self.role = self.client.create_role(self.test_role)
self.roles.append(self.role)
diff --git a/tempest/tests/network/base.py b/tempest/tests/network/base.py
index f993441..90b351d 100644
--- a/tempest/tests/network/base.py
+++ b/tempest/tests/network/base.py
@@ -48,7 +48,7 @@
cls.client.delete_network(network['id'])
def create_network(self, network_name=None):
- """Wrapper utility that returns a test network"""
+ """Wrapper utility that returns a test network."""
network_name = network_name or rand_name('test-network')
resp, body = self.client.create_network(network_name)
diff --git a/tempest/tests/utils.py b/tempest/tests/utils.py
index 8adaa51..571fc2a 100644
--- a/tempest/tests/utils.py
+++ b/tempest/tests/utils.py
@@ -15,7 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""Common utilities used in testing"""
+"""Common utilities used in testing."""
import nose.plugins.skip
diff --git a/tempest/tests/volume/admin/base.py b/tempest/tests/volume/admin/base.py
index 420da42..81c7c78 100644
--- a/tempest/tests/volume/admin/base.py
+++ b/tempest/tests/volume/admin/base.py
@@ -27,7 +27,7 @@
class BaseVolumeAdminTest(BaseVolumeTest):
- """Base test case class for all Volume Admin API tests"""
+ """Base test case class for all Volume Admin API tests."""
@classmethod
def setUpClass(cls):
super(BaseVolumeAdminTest, cls).setUpClass()
diff --git a/tempest/tests/volume/admin/test_volume_types.py b/tempest/tests/volume/admin/test_volume_types.py
index 8ebb78f..65c975a 100644
--- a/tempest/tests/volume/admin/test_volume_types.py
+++ b/tempest/tests/volume/admin/test_volume_types.py
@@ -126,7 +126,8 @@
try:
body = {}
name = rand_name("volume-type-")
- extra_specs = {"Spec1": "Val1", "Spec2": "Val2"}
+ extra_specs = {"storage_protocol": "iSCSI",
+ "vendor_name": "Open Source"}
resp, body = self.client.\
create_volume_type(name, extra_specs=extra_specs)
self.assertEqual(200, resp.status)
diff --git a/tempest/tests/volume/base.py b/tempest/tests/volume/base.py
index 549f541..8657db8 100644
--- a/tempest/tests/volume/base.py
+++ b/tempest/tests/volume/base.py
@@ -31,7 +31,7 @@
class BaseVolumeTest(unittest.TestCase):
- """Base test case class for all Cinder API tests"""
+ """Base test case class for all Cinder API tests."""
@classmethod
def setUpClass(cls):
@@ -122,7 +122,7 @@
cls.clear_isolated_creds()
def create_volume(self, size=1, metadata={}):
- """Wrapper utility that returns a test volume"""
+ """Wrapper utility that returns a test volume."""
display_name = rand_name(self.__class__.__name__ + "-volume")
cli_resp = self.volumes_client.create_volume(size=size,
display_name=display_name,
@@ -133,7 +133,7 @@
return volume
def wait_for(self, condition):
- """Repeatedly calls condition() until a timeout"""
+ """Repeatedly calls condition() until a timeout."""
start_time = int(time.time())
while True:
try:
diff --git a/tempest/tests/volume/test_volumes_actions.py b/tempest/tests/volume/test_volumes_actions.py
index 7eddb67..155acb6 100644
--- a/tempest/tests/volume/test_volumes_actions.py
+++ b/tempest/tests/volume/test_volumes_actions.py
@@ -46,7 +46,10 @@
super(VolumesActionsTest, cls).tearDownClass()
# Delete the test instance and volume
cls.client.delete_volume(cls.volume['id'])
+ cls.client.wait_for_resource_deletion(cls.volume['id'])
+
cls.servers_client.delete_server(cls.server['id'])
+ cls.client.wait_for_resource_deletion(cls.server['id'])
@attr(type='smoke')
def test_attach_detach_volume_to_instance(self):
diff --git a/tempest/tests/volume/test_volumes_list.py b/tempest/tests/volume/test_volumes_list.py
index 26a85b7..2fc1353 100644
--- a/tempest/tests/volume/test_volumes_list.py
+++ b/tempest/tests/volume/test_volumes_list.py
@@ -87,8 +87,9 @@
# because the backing file size of the volume group is
# too small. So, here, we clean up whatever we did manage
# to create and raise a SkipTest
- for volume in cls.volume_id_list:
- cls.client.delete_volume(volume)
+ for volid in cls.volume_id_list:
+ cls.client.delete_volume(volid)
+ cls.client.wait_for_resource_deletion(volid)
msg = ("Failed to create ALL necessary volumes to run "
"test. This typically means that the backing file "
"size of the nova-volumes group is too small to "
@@ -99,9 +100,9 @@
@classmethod
def tearDownClass(cls):
# Delete the created volumes
- for volume in cls.volume_id_list:
- resp, _ = cls.client.delete_volume(volume)
- cls.client.wait_for_resource_deletion(volume)
+ for volid in cls.volume_id_list:
+ resp, _ = cls.client.delete_volume(volid)
+ cls.client.wait_for_resource_deletion(volid)
super(VolumeListTestXML, cls).tearDownClass()
@@ -133,8 +134,9 @@
# because the backing file size of the volume group is
# too small. So, here, we clean up whatever we did manage
# to create and raise a SkipTest
- for volume in cls.volume_id_list:
- cls.client.delete_volume(volume)
+ for volid in cls.volume_id_list:
+ cls.client.delete_volume(volid)
+ cls.client.wait_for_resource_deletion(volid)
msg = ("Failed to create ALL necessary volumes to run "
"test. This typically means that the backing file "
"size of the nova-volumes group is too small to "
@@ -145,7 +147,7 @@
@classmethod
def tearDownClass(cls):
# Delete the created volumes
- for volume in cls.volume_id_list:
- resp, _ = cls.client.delete_volume(volume)
- cls.client.wait_for_resource_deletion(volume)
+ for volid in cls.volume_id_list:
+ resp, _ = cls.client.delete_volume(volid)
+ cls.client.wait_for_resource_deletion(volid)
super(VolumeListTestJSON, cls).tearDownClass()
diff --git a/tempest/whitebox.py b/tempest/whitebox.py
index 39cd666..d78b9e0 100644
--- a/tempest/whitebox.py
+++ b/tempest/whitebox.py
@@ -106,7 +106,7 @@
@classmethod
def create_server(cls, image_id=None):
- """Wrapper utility that returns a test server"""
+ """Wrapper utility that returns a test server."""
server_name = rand_name(cls.__name__ + "-instance")
flavor = cls.flavor_ref
if not image_id:
@@ -120,7 +120,7 @@
@classmethod
def get_db_handle_and_meta(cls, database='nova'):
- """Return a connection handle and metadata of an OpenStack database"""
+ """Return a connection handle and metadata of an OpenStack database."""
engine_args = {"echo": False,
"convert_unicode": True,
"pool_recycle": 3600
@@ -138,7 +138,7 @@
return connection, meta
def nova_manage(self, category, action, params):
- """Executes nova-manage command for the given action"""
+ """Executes nova-manage command for the given action."""
nova_manage_path = os.path.join(self.compute_bin_dir, 'nova-manage')
cmd = ' '.join([nova_manage_path, category, action, params])
@@ -161,7 +161,7 @@
return result
def get_ssh_connection(self, host, username, password):
- """Create an SSH connection object to a host"""
+ """Create an SSH connection object to a host."""
ssh_timeout = self.config.compute.ssh_timeout
ssh_client = Client(host, username, password, ssh_timeout)
if not ssh_client.test_connection_auth():
diff --git a/tools/hacking.py b/tools/hacking.py
index 6e66005..617682d 100755
--- a/tools/hacking.py
+++ b/tools/hacking.py
@@ -36,13 +36,13 @@
# Don't need this for testing
logging.disable('LOG')
-#N1xx comments
-#N2xx except
-#N3xx imports
-#N4xx docstrings
-#N5xx dictionaries/lists
-#N6xx calling methods
-#N7xx localization
+#T1xx comments
+#T2xx except
+#T3xx imports
+#T4xx docstrings
+#T5xx dictionaries/lists
+#T6xx calling methods
+#T7xx localization
#N8xx git commit messages
IMPORT_EXCEPTIONS = ['sqlalchemy', 'migrate']
@@ -110,13 +110,13 @@
tempest HACKING guide recommendation for TODO:
Include your name with TODOs as in "#TODO(termie)"
- N101
+ 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, "TEMPEST N101: Use TODO(NAME)"
+ return pos, "T101: Use TODO(NAME)"
def tempest_except_format(logical_line):
@@ -124,10 +124,10 @@
tempest HACKING guide recommends not using except:
Do not write "except:", use "except Exception:" at the very least
- N201
+ T201
"""
if logical_line.startswith("except:"):
- yield 6, "TEMPEST N201: no 'except:' at least use 'except Exception:'"
+ yield 6, "T201: no 'except:' at least use 'except Exception:'"
def tempest_except_format_assert(logical_line):
@@ -135,10 +135,10 @@
tempest HACKING guide recommends not using assertRaises(Exception...):
Do not use overly broad Exception type
- N202
+ T202
"""
if logical_line.startswith("self.assertRaises(Exception"):
- yield 1, "TEMPEST N202: assertRaises Exception too broad"
+ yield 1, "T202: assertRaises Exception too broad"
def tempest_one_import_per_line(logical_line):
@@ -149,14 +149,14 @@
Examples:
BAD: from tempest.common.rest_client import RestClient, RestClientXML
- N301
+ 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, "TEMPEST N301: one import per line"
+ yield pos, "T301: one import per line"
_missingImport = set([])
@@ -166,9 +166,9 @@
tempest HACKING guide recommends importing only modules:
Do not import objects, only modules
- N302 import only modules
- N303 Invalid Import
- N304 Relative Import
+ T302 import only modules
+ T303 Invalid Import
+ T304 Relative Import
"""
def importModuleCheck(mod, parent=None, added=False):
"""
@@ -193,12 +193,12 @@
if added:
sys.path.pop()
added = False
- return logical_line.find(mod), ("TEMPEST N304: No "
+ return logical_line.find(mod), ("T304: No "
"relative imports. "
"'%s' is a relative "
"import"
% logical_line)
- return logical_line.find(mod), ("TEMPEST N302: import only"
+ return logical_line.find(mod), ("T302: import only"
" modules. '%s' does not "
"import a module"
% logical_line)
@@ -222,7 +222,7 @@
except AttributeError:
# Invalid import
- return logical_line.find(mod), ("TEMPEST N303: Invalid import, "
+ return logical_line.find(mod), ("T303: Invalid import, "
"AttributeError raised")
# convert "from x import y" to " import x.y"
@@ -240,7 +240,7 @@
# TODO(jogo) handle "from x import *"
-#TODO(jogo): import template: N305
+#TODO(jogo): import template: T305
def tempest_import_alphabetical(logical_line, line_number, lines):
@@ -248,7 +248,7 @@
Tempest HACKING guide recommendation for imports:
imports in human alphabetical order
- N306
+ T306
"""
# handle import x
# use .lower since capitalization shouldn't dictate order
@@ -260,7 +260,7 @@
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, "TEMPEST N306: imports not in alphabetical order"
+ yield (0, "T306: imports not in alphabetical order"
" (%s, %s)"
% (split_previous[1], split_line[1]))
@@ -270,12 +270,13 @@
tempest HACKING guide recommendation for docstring:
Docstring should not start with space
- N401
+ T401
"""
pos = max([physical_line.find(i) for i in DOCSTRING_TRIPLE]) # start
- if (pos != -1 and len(physical_line) > pos + 1):
+ 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, "TEMPEST N401: one line docstring should not start"
+ return (pos, "T401: one line docstring should not start"
" with a space")
@@ -284,13 +285,13 @@
tempest HACKING guide recommendation for one line docstring:
A one line docstring looks like this and ends in a period.
- N402
+ 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, "TEMPEST N402: one line docstring needs a period"
+ return pos, "T402: one line docstring needs a period"
def tempest_docstring_multiline_end(physical_line):
@@ -298,12 +299,29 @@
Tempest HACKING guide recommendation for docstring:
Docstring should end on a new line
- N403
+ 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, "TEMPEST N403: multi line docstring end on new line")
+ 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")
FORMAT_RE = re.compile("%(?:"
@@ -353,25 +371,25 @@
if not format_string:
raise LocalizationError(start,
- "TEMEPST N701: Empty localization "
+ "T701: Empty localization "
"string")
if token_type != tokenize.OP:
raise LocalizationError(start,
- "TEMPEST N701: Invalid localization "
+ "T701: Invalid localization "
"call")
if text != ")":
if text == "%":
raise LocalizationError(start,
- "TEMPEST N702: Formatting "
+ "T702: Formatting "
"operation should be outside"
" of localization method call")
elif text == "+":
raise LocalizationError(start,
- "TEMPEST N702: Use bare string "
+ "T702: Use bare string "
"concatenation instead of +")
else:
raise LocalizationError(start,
- "TEMPEST N702: Argument to _ must"
+ "T702: Argument to _ must"
" be just a string")
format_specs = FORMAT_RE.findall(format_string)
@@ -380,16 +398,16 @@
# not spec means %%, key means %(smth)s
if len(positional_specs) > 1:
raise LocalizationError(start,
- "TEMPEST N703: Multiple positional "
+ "T703: Multiple positional "
"placeholders")
def tempest_localization_strings(logical_line, tokens):
"""Check localization in line.
- N701: bad localization call
- N702: complex expression instead of string as argument to _()
- N703: multiple positional placeholders
+ T701: bad localization call
+ T702: complex expression instead of string as argument to _()
+ T703: multiple positional placeholders
"""
gen = check_i18n()
@@ -431,8 +449,8 @@
tempest HACKING recommends not referencing a bug or blueprint
in first line, it should provide an accurate description of the change
- N801
- N802 Title limited to 50 chars
+ T801
+ T802 Title limited to 50 chars
"""
#Get title of most recent commit
@@ -453,12 +471,12 @@
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 ("N801: git commit title ('%s') should provide an accurate "
+ 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 ("N802: git commit title ('%s') should be under 50 chars"
+ print ("T802: git commit title ('%s') should be under 50 chars"
% title.strip())
error = True
return error
@@ -468,8 +486,8 @@
sys.path.append(os.getcwd())
#Run once tests (not per line)
once_error = once_git_check_commit_title()
- #TEMPEST error codes start with an N
- pep8.ERRORCODE_REGEX = re.compile(r'[EWN]\d{3}')
+ #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
diff --git a/tools/skip_tracker.py b/tools/skip_tracker.py
old mode 100644
new mode 100755
index 9b12eb7..e890e92
--- a/tools/skip_tracker.py
+++ b/tools/skip_tracker.py
@@ -1,3 +1,4 @@
+#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 OpenStack, LLC
diff --git a/tools/tempest_coverage.py b/tools/tempest_coverage.py
new file mode 100755
index 0000000..73dcfbc
--- /dev/null
+++ b/tools/tempest_coverage.py
@@ -0,0 +1,194 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 IBM
+#
+# 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 os
+import re
+import shutil
+import sys
+
+from tempest.common.rest_client import RestClient
+from tempest import config
+from tempest.openstack.common import cfg
+from tempest.tests.compute import base
+
+CONF = config.TempestConfig()
+
+
+class CoverageClientJSON(RestClient):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(CoverageClientJSON, self).__init__(config, username, password,
+ auth_url, tenant_name)
+ self.service = self.config.compute.catalog_type
+
+ def start_coverage(self):
+ post_body = {
+ 'start': {},
+ }
+ post_body = json.dumps(post_body)
+ return self.post('os-coverage/action', post_body, self.headers)
+
+ def start_coverage_combine(self):
+ post_body = {
+ 'start': {
+ 'combine': True,
+ },
+ }
+ post_body = json.dumps(post_body)
+ return self.post('os-coverage/action', post_body, self.headers)
+
+ def stop_coverage(self):
+ post_body = {
+ 'stop': {},
+ }
+ post_body = json.dumps(post_body)
+ resp, body = self.post('os-coverage/action', post_body, self.headers)
+ body = json.loads(body)
+ return resp, body
+
+ def report_coverage_xml(self, file=None):
+ post_body = {
+ 'report': {
+ 'file': 'coverage.report',
+ 'xml': True,
+ },
+ }
+ if file:
+ post_body['report']['file'] = file
+ post_body = json.dumps(post_body)
+ resp, body = self.post('os-coverage/action', post_body, self.headers)
+ body = json.loads(body)
+ return resp, body
+
+ def report_coverage(self, file=None):
+ post_body = {
+ 'report': {
+ 'file': 'coverage.report',
+ },
+ }
+ if file:
+ post_body['report']['file'] = file
+ post_body = json.dumps(post_body)
+ resp, body = self.post('os-coverage/action', post_body, self.headers)
+ body = json.loads(body)
+ return resp, body
+
+ def report_coverage_html(self, file=None):
+ post_body = {
+ 'report': {
+ 'file': 'coverage.report',
+ 'html': True,
+ },
+ }
+ if file:
+ post_body['report']['file'] = file
+ post_body = json.dumps(post_body)
+ resp, body = self.post('os-coverage/action', post_body, self.headers)
+ body = json.loads(body)
+ return resp, body
+
+
+def parse_opts(argv):
+ cli_opts = [
+ cfg.StrOpt('command',
+ short='c',
+ default='',
+ help="This required argument is used to specify the "
+ "coverage command to run. Only 'start', "
+ "'stop', or 'report' are valid fields."),
+ cfg.StrOpt('filename',
+ default='tempest-coverage',
+ help="Specify a filename to be used for generated report "
+ "files"),
+ cfg.BoolOpt('xml',
+ default=False,
+ help='Generate XML reports instead of text'),
+ cfg.BoolOpt('html',
+ default=False,
+ help='Generate HTML reports instead of text'),
+ cfg.BoolOpt('combine',
+ default=False,
+ help='Generate a single report for all services'),
+ cfg.StrOpt('output',
+ short='o',
+ default=None,
+ help='Optional directory to copy generated coverage data or'
+ ' reports into. This directory must not already exist '
+ 'it will be created')
+ ]
+ CLI = cfg.ConfigOpts()
+ CLI.register_cli_opts(cli_opts)
+ CLI(argv[1:])
+ return CLI
+
+
+def main(argv):
+ CLI = parse_opts(argv)
+ client_args = (CONF, CONF.compute_admin.username,
+ CONF.compute_admin.password, CONF.identity.auth_url,
+ CONF.compute_admin.tenant_name)
+ coverage_client = CoverageClientJSON(*client_args)
+
+ if CLI.command == 'start':
+ if CLI.combine:
+ coverage_client.start_coverage_combine()
+ else:
+ coverage_client.start_coverage()
+
+ elif CLI.command == 'stop':
+ resp, body = coverage_client.stop_coverage()
+ if not resp['status'] == '200':
+ print 'coverage stop failed with: %s:' % (resp['status'] + ': '
+ + body)
+ exit(int(resp['status']))
+ path = body['path']
+ if CLI.output:
+ shutil.copytree(path, CLI.output)
+ else:
+ print "Data files located at: %s" % path
+
+ elif CLI.command == 'report':
+ if CLI.xml:
+ resp, body = coverage_client.report_coverage_xml(file=CLI.filename)
+ elif CLI.html:
+ resp, body = coverage_client.report_coverage_html(
+ file=CLI.filename)
+ else:
+ resp, body = coverage_client.report_coverage(file=CLI.filename)
+ if not resp['status'] == '200':
+ print 'coverage report failed with: %s:' % (resp['status'] + ': '
+ + body)
+ exit(int(resp['status']))
+ path = body['path']
+ if CLI.output:
+ if CLI.html:
+ shutil.copytree(path, CLI.output)
+ else:
+ path = os.path.dirname(path)
+ shutil.copytree(path, CLI.output)
+ else:
+ if not CLI.html:
+ path = os.path.dirname(path)
+ print 'Report files located at: %s' % path
+
+ else:
+ print 'Invalid command'
+ exit(1)
+
+
+if __name__ == "__main__":
+ main(sys.argv)
diff --git a/tox.ini b/tox.ini
index 2d8e627..1b18586 100644
--- a/tox.ini
+++ b/tox.ini
@@ -13,6 +13,11 @@
-r{toxinidir}/tools/test-requires
commands = nosetests {posargs}
+[testenv:coverage]
+commands = python -m tools/tempest_coverage -c start --combine
+ nosetests {posargs}
+ python -m tools/tempest_coverage -c report --html
+
[testenv:pep8]
deps = pep8==1.3.3
-commands = python tools/hacking.py --ignore=N4,E122,E125,E126 --repeat --show-source --exclude=.venv,.tox,dist,doc,openstack,*egg .
+commands = python tools/hacking.py --ignore=E122,E125,E126 --repeat --show-source --exclude=.venv,.tox,dist,doc,openstack,*egg .